From fc3fd073d3a0acf9933c3994b660ebd7b5833f65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Sat, 12 Mar 2022 15:31:37 +0100 Subject: [PATCH 001/484] Update clap to the latest version (#11017) * Update clap to the latest version Besides that it also removes some `structopt` leftovers from some docs. * Fix compile errors * More fixes --- Cargo.lock | 40 ++++++------ bin/node-template/node/Cargo.toml | 2 +- bin/node/bench/Cargo.toml | 2 +- bin/node/cli/Cargo.toml | 4 +- bin/node/cli/build.rs | 4 +- bin/node/inspect/Cargo.toml | 2 +- bin/utils/chain-spec-builder/Cargo.toml | 2 +- bin/utils/subkey/Cargo.toml | 2 +- client/cli/Cargo.toml | 2 +- client/cli/src/arg_enums.rs | 8 +++ client/cli/src/lib.rs | 62 +++++++++---------- client/cli/src/params/import_params.rs | 9 +-- primitives/npos-elections/fuzzer/Cargo.toml | 2 +- utils/frame/benchmarking-cli/Cargo.toml | 2 +- utils/frame/benchmarking-cli/src/lib.rs | 8 +-- utils/frame/frame-utilities-cli/Cargo.toml | 2 +- .../generate-bags/node-runtime/Cargo.toml | 2 +- utils/frame/try-runtime/cli/Cargo.toml | 2 +- utils/frame/try-runtime/cli/src/lib.rs | 16 ++--- 19 files changed, 87 insertions(+), 86 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index db6edfa81d539..1ad59291eca27 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -931,7 +931,7 @@ name = "chain-spec-builder" version = "2.0.0" dependencies = [ "ansi_term", - "clap 3.0.7", + "clap 3.1.6", "node-cli", "rand 0.8.4", "sc-chain-spec", @@ -1006,9 +1006,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.0.7" +version = "3.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12e8611f9ae4e068fa3e56931fded356ff745e70987ff76924a6e0ab1c8ef2e3" +checksum = "d8c93436c21e4698bacadf42917db28b23017027a4deccb35dbe47a7e7840123" dependencies = [ "atty", "bitflags", @@ -1018,7 +1018,7 @@ dependencies = [ "os_str_bytes", "strsim", "termcolor", - "textwrap 0.14.2", + "textwrap 0.15.0", ] [[package]] @@ -1027,14 +1027,14 @@ version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a394f7ec0715b42a4e52b294984c27c9a61f77c8d82f7774c5198350be143f19" dependencies = [ - "clap 3.0.7", + "clap 3.1.6", ] [[package]] name = "clap_derive" -version = "3.0.5" +version = "3.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a0645a430ec9136d2d701e54a95d557de12649a9dd7109ced3187e648ac824" +checksum = "da95d038ede1a964ce99f49cbe27a7fb538d1da595e4b4f70b8c8f338d17bf16" dependencies = [ "heck 0.4.0", "proc-macro-error", @@ -2126,7 +2126,7 @@ version = "4.0.0-dev" dependencies = [ "Inflector", "chrono", - "clap 3.0.7", + "clap 3.1.6", "frame-benchmarking", "frame-support", "handlebars", @@ -4751,7 +4751,7 @@ dependencies = [ name = "node-bench" version = "0.9.0-dev" dependencies = [ - "clap 3.0.7", + "clap 3.1.6", "derive_more", "fs_extra", "futures 0.3.19", @@ -4790,7 +4790,7 @@ version = "3.0.0-dev" dependencies = [ "assert_cmd", "async-std", - "clap 3.0.7", + "clap 3.1.6", "clap_complete", "criterion", "frame-benchmarking-cli", @@ -4904,7 +4904,7 @@ dependencies = [ name = "node-inspect" version = "0.9.0-dev" dependencies = [ - "clap 3.0.7", + "clap 3.1.6", "parity-scale-codec", "sc-cli", "sc-client-api", @@ -5050,7 +5050,7 @@ dependencies = [ name = "node-runtime-generate-bags" version = "3.0.0" dependencies = [ - "clap 3.0.7", + "clap 3.1.6", "generate-bags", "node-runtime", ] @@ -5059,7 +5059,7 @@ dependencies = [ name = "node-template" version = "4.0.0-dev" dependencies = [ - "clap 3.0.7", + "clap 3.1.6", "frame-benchmarking", "frame-benchmarking-cli", "jsonrpc-core", @@ -8083,7 +8083,7 @@ name = "sc-cli" version = "0.10.0-dev" dependencies = [ "chrono", - "clap 3.0.7", + "clap 3.1.6", "fdlimit", "futures 0.3.19", "hex", @@ -10068,7 +10068,7 @@ dependencies = [ name = "sp-npos-elections-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 3.0.7", + "clap 3.1.6", "honggfuzz", "parity-scale-codec", "rand 0.8.4", @@ -10525,7 +10525,7 @@ dependencies = [ name = "subkey" version = "2.0.1" dependencies = [ - "clap 3.0.7", + "clap 3.1.6", "sc-cli", ] @@ -10553,7 +10553,7 @@ dependencies = [ name = "substrate-frame-cli" version = "4.0.0-dev" dependencies = [ - "clap 3.0.7", + "clap 3.1.6", "frame-support", "frame-system", "sc-cli", @@ -10843,9 +10843,9 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.14.2" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" [[package]] name = "thiserror" @@ -11367,7 +11367,7 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" name = "try-runtime-cli" version = "0.10.0-dev" dependencies = [ - "clap 3.0.7", + "clap 3.1.6", "jsonrpsee 0.4.1", "log 0.4.14", "parity-scale-codec", diff --git a/bin/node-template/node/Cargo.toml b/bin/node-template/node/Cargo.toml index 98e8af96d3f8d..4549a5b613da2 100644 --- a/bin/node-template/node/Cargo.toml +++ b/bin/node-template/node/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] name = "node-template" [dependencies] -clap = { version = "3.0", features = ["derive"] } +clap = { version = "3.1.6", features = ["derive"] } sc-cli = { version = "0.10.0-dev", path = "../../../client/cli", features = ["wasmtime"] } sp-core = { version = "6.0.0", path = "../../../primitives/core" } diff --git a/bin/node/bench/Cargo.toml b/bin/node/bench/Cargo.toml index 2c5dea2cc28a6..5b728258dd03b 100644 --- a/bin/node/bench/Cargo.toml +++ b/bin/node/bench/Cargo.toml @@ -9,7 +9,7 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -clap = { version = "3.0", features = ["derive"] } +clap = { version = "3.1.6", features = ["derive"] } log = "0.4.8" node-primitives = { version = "2.0.0", path = "../primitives" } node-testing = { version = "3.0.0-dev", path = "../testing" } diff --git a/bin/node/cli/Cargo.toml b/bin/node/cli/Cargo.toml index b4a91712fd16c..24e069d21f694 100644 --- a/bin/node/cli/Cargo.toml +++ b/bin/node/cli/Cargo.toml @@ -34,7 +34,7 @@ crate-type = ["cdylib", "rlib"] [dependencies] # third-party dependencies -clap = { version = "3.0", features = ["derive"], optional = true } +clap = { version = "3.1.6", features = ["derive"], optional = true } codec = { package = "parity-scale-codec", version = "3.0.0" } serde = { version = "1.0.136", features = ["derive"] } futures = "0.3.19" @@ -133,7 +133,7 @@ remote-externalities = { path = "../../../utils/frame/remote-externalities" } pallet-timestamp = { version = "4.0.0-dev", path = "../../../frame/timestamp" } [build-dependencies] -clap = { version = "3.0", optional = true } +clap = { version = "3.1.6", optional = true } clap_complete = { version = "3.0", optional = true } node-inspect = { version = "0.9.0-dev", optional = true, path = "../inspect" } frame-benchmarking-cli = { version = "4.0.0-dev", optional = true, path = "../../../utils/frame/benchmarking-cli" } diff --git a/bin/node/cli/build.rs b/bin/node/cli/build.rs index 6a010d8858fe5..6a3d13dda6a00 100644 --- a/bin/node/cli/build.rs +++ b/bin/node/cli/build.rs @@ -25,7 +25,7 @@ fn main() { mod cli { include!("src/cli.rs"); - use clap::{ArgEnum, IntoApp}; + use clap::{ArgEnum, CommandFactory}; use clap_complete::{generate_to, Shell}; use std::{env, fs, path::Path}; use substrate_build_script_utils::{generate_cargo_keys, rerun_if_git_head_changed}; @@ -61,6 +61,6 @@ mod cli { fs::create_dir(&path).ok(); - let _ = generate_to(*shell, &mut Cli::into_app(), "substrate-node", &path); + let _ = generate_to(*shell, &mut Cli::command(), "substrate-node", &path); } } diff --git a/bin/node/inspect/Cargo.toml b/bin/node/inspect/Cargo.toml index 5e7ed16efdcf4..c41681d11be1e 100644 --- a/bin/node/inspect/Cargo.toml +++ b/bin/node/inspect/Cargo.toml @@ -11,7 +11,7 @@ repository = "https://github.com/paritytech/substrate/" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -clap = { version = "3.0", features = ["derive"] } +clap = { version = "3.1.6", features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.0.0" } thiserror = "1.0" sc-cli = { version = "0.10.0-dev", path = "../../../client/cli" } diff --git a/bin/utils/chain-spec-builder/Cargo.toml b/bin/utils/chain-spec-builder/Cargo.toml index 9537317198483..1ea1c402151dd 100644 --- a/bin/utils/chain-spec-builder/Cargo.toml +++ b/bin/utils/chain-spec-builder/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] ansi_term = "0.12.1" -clap = { version = "3.0", features = ["derive"] } +clap = { version = "3.1.6", features = ["derive"] } rand = "0.8" sc-keystore = { version = "4.0.0-dev", path = "../../../client/keystore" } diff --git a/bin/utils/subkey/Cargo.toml b/bin/utils/subkey/Cargo.toml index 85f864cfbfc13..c8132fe7b5b4b 100644 --- a/bin/utils/subkey/Cargo.toml +++ b/bin/utils/subkey/Cargo.toml @@ -17,5 +17,5 @@ path = "src/main.rs" name = "subkey" [dependencies] -clap = { version = "3.0", features = ["derive"] } +clap = { version = "3.1.6", features = ["derive"] } sc-cli = { version = "0.10.0-dev", path = "../../../client/cli" } diff --git a/client/cli/Cargo.toml b/client/cli/Cargo.toml index 813215321dfc0..8fdb192b43fb6 100644 --- a/client/cli/Cargo.toml +++ b/client/cli/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] chrono = "0.4.10" -clap = { version = "3.0", features = ["derive"] } +clap = { version = "3.1.6", features = ["derive"] } fdlimit = "0.2.1" futures = "0.3.19" hex = "0.4.2" diff --git a/client/cli/src/arg_enums.rs b/client/cli/src/arg_enums.rs index 6b0a029dd4fe5..df4b68ff5c325 100644 --- a/client/cli/src/arg_enums.rs +++ b/client/cli/src/arg_enums.rs @@ -86,6 +86,14 @@ impl Into for WasmExecutionMethod { } } +/// The default [`WasmExecutionMethod`]. +#[cfg(feature = "wasmtime")] +pub const DEFAULT_WASM_EXECUTION_METHOD: &str = "Compiled"; + +/// The default [`WasmExecutionMethod`]. +#[cfg(not(feature = "wasmtime"))] +pub const DEFAULT_WASM_EXECUTION_METHOD: &str = "interpreted-i-know-what-i-do"; + #[allow(missing_docs)] #[derive(Debug, Copy, Clone, PartialEq, Eq, ArgEnum)] #[clap(rename_all = "PascalCase")] diff --git a/client/cli/src/lib.rs b/client/cli/src/lib.rs index c242050dbf32a..c920c6cd52cff 100644 --- a/client/cli/src/lib.rs +++ b/client/cli/src/lib.rs @@ -22,6 +22,9 @@ #![warn(unused_extern_crates)] #![warn(unused_imports)] +use clap::{CommandFactory, FromArgMatches, Parser}; +use sc_service::Configuration; + pub mod arg_enums; mod commands; mod config; @@ -31,33 +34,31 @@ mod runner; pub use arg_enums::*; pub use clap; -use clap::{AppSettings, FromArgMatches, IntoApp, Parser}; pub use commands::*; pub use config::*; pub use error::*; pub use params::*; pub use runner::*; -use sc_service::Configuration; pub use sc_service::{ChainSpec, Role}; pub use sc_tracing::logging::LoggerBuilder; pub use sp_version::RuntimeVersion; /// Substrate client CLI /// -/// This trait needs to be defined on the root structopt of the application. It will provide the -/// implementation name, version, executable name, description, author, support_url, copyright start -/// year and most importantly: how to load the chain spec. -/// -/// StructOpt must not be in scope to use from_args (or the similar methods). This trait provides -/// its own implementation that will fill the necessary field based on the trait's functions. +/// This trait needs to be implemented on the root CLI struct of the application. It will provide +/// the implementation `name`, `version`, `executable name`, `description`, `author`, `support_url`, +/// `copyright start year` and most importantly: how to load the chain spec. pub trait SubstrateCli: Sized { /// Implementation name. fn impl_name() -> String; /// Implementation version. /// - /// By default this will look like this: 2.0.0-b950f731c-x86_64-linux-gnu where the hash is the - /// short commit hash of the commit of in the Git repository. + /// By default this will look like this: + /// + /// `2.0.0-b950f731c-x86_64-linux-gnu` + /// + /// Where the hash is the short commit hash of the commit of in the Git repository. fn impl_version() -> String; /// Executable file name. @@ -88,14 +89,13 @@ pub trait SubstrateCli: Sized { fn load_spec(&self, id: &str) -> std::result::Result, String>; /// Helper function used to parse the command line arguments. This is the equivalent of - /// `structopt`'s `from_iter()` except that it takes a `VersionInfo` argument to provide the - /// name of the application, author, "about" and version. It will also set - /// `AppSettings::GlobalVersion`. + /// [`clap::Parser::parse()`]. /// - /// To allow running the node without subcommand, tt also sets a few more settings: - /// `AppSettings::ArgsNegateSubcommands` and `AppSettings::SubcommandsNegateReqs`. + /// To allow running the node without subcommand, it also sets a few more settings: + /// [`clap::Command::propagate_version`], [`clap::Command::args_conflicts_with_subcommands`], + /// [`clap::Command::subcommand_negates_reqs`]. /// - /// Gets the struct from the command line arguments. Print the + /// Creates `Self` from the command line arguments. Print the /// error message and quit the program in case of failure. fn from_args() -> Self where @@ -105,14 +105,13 @@ pub trait SubstrateCli: Sized { } /// Helper function used to parse the command line arguments. This is the equivalent of - /// `structopt`'s `from_iter()` except that it takes a `VersionInfo` argument to provide the - /// name of the application, author, "about" and version. It will also set - /// `AppSettings::GlobalVersion`. + /// [`clap::Parser::parse_from`]. /// /// To allow running the node without subcommand, it also sets a few more settings: - /// `AppSettings::ArgsNegateSubcommands` and `AppSettings::SubcommandsNegateReqs`. + /// [`clap::Command::propagate_version`], [`clap::Command::args_conflicts_with_subcommands`], + /// [`clap::Command::subcommand_negates_reqs`]. /// - /// Gets the struct from any iterator such as a `Vec` of your making. + /// Creates `Self` from any iterator over arguments. /// Print the error message and quit the program in case of failure. fn from_iter(iter: I) -> Self where @@ -120,7 +119,7 @@ pub trait SubstrateCli: Sized { I: IntoIterator, I::Item: Into + Clone, { - let app = ::into_app(); + let app = ::command(); let mut full_version = Self::impl_version(); full_version.push_str("\n"); @@ -133,11 +132,9 @@ pub trait SubstrateCli: Sized { .author(author.as_str()) .about(about.as_str()) .version(full_version.as_str()) - .setting( - AppSettings::PropagateVersion | - AppSettings::ArgsNegateSubcommands | - AppSettings::SubcommandsNegateReqs, - ); + .propagate_version(true) + .args_conflicts_with_subcommands(true) + .subcommand_negates_reqs(true); let matches = app.try_get_matches_from(iter).unwrap_or_else(|e| e.exit()); @@ -145,14 +142,13 @@ pub trait SubstrateCli: Sized { } /// Helper function used to parse the command line arguments. This is the equivalent of - /// `structopt`'s `from_iter()` except that it takes a `VersionInfo` argument to provide the - /// name of the application, author, "about" and version. It will also set - /// `AppSettings::GlobalVersion`. + /// [`clap::Parser::try_parse_from`] /// /// To allow running the node without subcommand, it also sets a few more settings: - /// `AppSettings::ArgsNegateSubcommands` and `AppSettings::SubcommandsNegateReqs`. + /// [`clap::Command::propagate_version`], [`clap::Command::args_conflicts_with_subcommands`], + /// [`clap::Command::subcommand_negates_reqs`]. /// - /// Gets the struct from any iterator such as a `Vec` of your making. + /// Creates `Self` from any iterator over arguments. /// Print the error message and quit the program in case of failure. /// /// **NOTE:** This method WILL NOT exit when `--help` or `--version` (or short versions) are @@ -165,7 +161,7 @@ pub trait SubstrateCli: Sized { I: IntoIterator, I::Item: Into + Clone, { - let app = ::into_app(); + let app = ::command(); let mut full_version = Self::impl_version(); full_version.push_str("\n"); diff --git a/client/cli/src/params/import_params.rs b/client/cli/src/params/import_params.rs index 1ec79800136d3..1df11cff8d79f 100644 --- a/client/cli/src/params/import_params.rs +++ b/client/cli/src/params/import_params.rs @@ -21,6 +21,7 @@ use crate::{ ExecutionStrategy, WasmExecutionMethod, DEFAULT_EXECUTION_BLOCK_CONSTRUCTION, DEFAULT_EXECUTION_IMPORT_BLOCK, DEFAULT_EXECUTION_IMPORT_BLOCK_VALIDATOR, DEFAULT_EXECUTION_OFFCHAIN_WORKER, DEFAULT_EXECUTION_OTHER, DEFAULT_EXECUTION_SYNCING, + DEFAULT_WASM_EXECUTION_METHOD, }, params::{DatabaseParams, PruningParams}, }; @@ -28,12 +29,6 @@ use clap::Args; use sc_client_api::execution_extensions::ExecutionStrategies; use std::path::PathBuf; -#[cfg(feature = "wasmtime")] -const WASM_METHOD_DEFAULT: &str = "Compiled"; - -#[cfg(not(feature = "wasmtime"))] -const WASM_METHOD_DEFAULT: &str = "interpreted-i-know-what-i-do"; - /// Parameters for block import. #[derive(Debug, Clone, Args)] pub struct ImportParams { @@ -59,7 +54,7 @@ pub struct ImportParams { value_name = "METHOD", possible_values = WasmExecutionMethod::variants(), ignore_case = true, - default_value = WASM_METHOD_DEFAULT + default_value = DEFAULT_WASM_EXECUTION_METHOD, )] pub wasm_method: WasmExecutionMethod, diff --git a/primitives/npos-elections/fuzzer/Cargo.toml b/primitives/npos-elections/fuzzer/Cargo.toml index afa331b0676e0..2f2bae2dd44d4 100644 --- a/primitives/npos-elections/fuzzer/Cargo.toml +++ b/primitives/npos-elections/fuzzer/Cargo.toml @@ -14,7 +14,7 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -clap = { version = "3.0", features = ["derive"] } +clap = { version = "3.1.6", features = ["derive"] } honggfuzz = "0.5" rand = { version = "0.8", features = ["std", "small_rng"] } diff --git a/utils/frame/benchmarking-cli/Cargo.toml b/utils/frame/benchmarking-cli/Cargo.toml index 20122c4279366..81e7396db3e68 100644 --- a/utils/frame/benchmarking-cli/Cargo.toml +++ b/utils/frame/benchmarking-cli/Cargo.toml @@ -33,7 +33,7 @@ sp-std = { version = "4.0.0", default-features = false, path = "../../../primiti sp-state-machine = { version = "0.12.0", path = "../../../primitives/state-machine" } sp-trie = { version = "6.0.0", path = "../../../primitives/trie" } codec = { version = "3.0.0", package = "parity-scale-codec" } -clap = { version = "3.0", features = ["derive"] } +clap = { version = "3.1.6", features = ["derive"] } chrono = "0.4" serde = "1.0.136" serde_json = "1.0.74" diff --git a/utils/frame/benchmarking-cli/src/lib.rs b/utils/frame/benchmarking-cli/src/lib.rs index 56aab0321ccd0..640b1770f5c3f 100644 --- a/utils/frame/benchmarking-cli/src/lib.rs +++ b/utils/frame/benchmarking-cli/src/lib.rs @@ -19,7 +19,7 @@ mod command; mod storage; mod writer; -use sc_cli::{ExecutionStrategy, WasmExecutionMethod}; +use sc_cli::{ExecutionStrategy, WasmExecutionMethod, DEFAULT_WASM_EXECUTION_METHOD}; use std::{fmt::Debug, path::PathBuf}; pub use storage::StorageCmd; @@ -46,11 +46,11 @@ pub struct BenchmarkCmd { pub steps: u32, /// Indicates lowest values for each of the component ranges. - #[clap(long = "low", use_delimiter = true)] + #[clap(long = "low", use_value_delimiter = true)] pub lowest_range_values: Vec, /// Indicates highest values for each of the component ranges. - #[clap(long = "high", use_delimiter = true)] + #[clap(long = "high", use_value_delimiter = true)] pub highest_range_values: Vec, /// Select how many repetitions of this benchmark should run from within the wasm. @@ -130,7 +130,7 @@ pub struct BenchmarkCmd { value_name = "METHOD", possible_values = WasmExecutionMethod::variants(), ignore_case = true, - default_value = "compiled" + default_value = DEFAULT_WASM_EXECUTION_METHOD, )] pub wasm_method: WasmExecutionMethod, diff --git a/utils/frame/frame-utilities-cli/Cargo.toml b/utils/frame/frame-utilities-cli/Cargo.toml index bc673105b78bf..43c8b31898959 100644 --- a/utils/frame/frame-utilities-cli/Cargo.toml +++ b/utils/frame/frame-utilities-cli/Cargo.toml @@ -11,7 +11,7 @@ documentation = "https://docs.rs/substrate-frame-cli" readme = "README.md" [dependencies] -clap = { version = "3.0", features = ["derive"] } +clap = { version = "3.1.6", features = ["derive"] } sp-core = { version = "6.0.0", path = "../../../primitives/core" } sc-cli = { version = "0.10.0-dev", path = "../../../client/cli" } diff --git a/utils/frame/generate-bags/node-runtime/Cargo.toml b/utils/frame/generate-bags/node-runtime/Cargo.toml index 11dee7b8b68ec..d5c8cab7ba0d8 100644 --- a/utils/frame/generate-bags/node-runtime/Cargo.toml +++ b/utils/frame/generate-bags/node-runtime/Cargo.toml @@ -14,4 +14,4 @@ node-runtime = { version = "3.0.0-dev", path = "../../../../bin/node/runtime" } generate-bags = { version = "4.0.0-dev", path = "../" } # third-party -clap = { version = "3.0", features = ["derive"] } +clap = { version = "3.1.6", features = ["derive"] } diff --git a/utils/frame/try-runtime/cli/Cargo.toml b/utils/frame/try-runtime/cli/Cargo.toml index 04b6d336f7406..6f72bd3b9d7f2 100644 --- a/utils/frame/try-runtime/cli/Cargo.toml +++ b/utils/frame/try-runtime/cli/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -clap = { version = "3.0", features = ["derive"] } +clap = { version = "3.1.6", features = ["derive"] } log = "0.4.8" parity-scale-codec = "3.0.0" serde = "1.0.136" diff --git a/utils/frame/try-runtime/cli/src/lib.rs b/utils/frame/try-runtime/cli/src/lib.rs index 92721228c9291..2298fa5c042ee 100644 --- a/utils/frame/try-runtime/cli/src/lib.rs +++ b/utils/frame/try-runtime/cli/src/lib.rs @@ -270,7 +270,9 @@ use remote_externalities::{ Builder, Mode, OfflineConfig, OnlineConfig, SnapshotConfig, TestExternalities, }; use sc_chain_spec::ChainSpec; -use sc_cli::{CliConfiguration, ExecutionStrategy, WasmExecutionMethod}; +use sc_cli::{ + CliConfiguration, ExecutionStrategy, WasmExecutionMethod, DEFAULT_WASM_EXECUTION_METHOD, +}; use sc_executor::NativeElseWasmExecutor; use sc_service::{Configuration, NativeExecutionDispatch}; use sp_core::{ @@ -394,7 +396,7 @@ pub struct SharedParams { value_name = "METHOD", possible_values = WasmExecutionMethod::variants(), ignore_case = true, - default_value = "Compiled" + default_value = DEFAULT_WASM_EXECUTION_METHOD, )] pub wasm_method: WasmExecutionMethod, @@ -458,15 +460,15 @@ pub enum State { snapshot_path: Option, /// The pallets to scrape. If empty, entire chain state will be scraped. - #[clap(short, long, require_delimiter = true)] - pallets: Option>, + #[clap(short, long, multiple_values = true)] + pallets: Vec, /// Fetch the child-keys as well. /// - /// Default is `false`, if specific `pallets` are specified, true otherwise. In other + /// Default is `false`, if specific `--pallets` are specified, `true` otherwise. In other /// words, if you scrape the whole state the child tree data is included out of the box. /// Otherwise, it must be enabled explicitly using this flag. - #[clap(long, require_delimiter = true)] + #[clap(long)] child_tree: bool, }, } @@ -492,7 +494,7 @@ impl State { .mode(Mode::Online(OnlineConfig { transport: uri.to_owned().into(), state_snapshot: snapshot_path.as_ref().map(SnapshotConfig::new), - pallets: pallets.clone().unwrap_or_default(), + pallets: pallets.clone(), scrape_children: true, at, })) From 925c8756478de80e045ce2cd18d874cfb812c40f Mon Sep 17 00:00:00 2001 From: Davirain Date: Thu, 11 Aug 2022 16:58:47 +0800 Subject: [PATCH 002/484] add substrate-ibc --- Cargo.lock | 2385 +++++++----- Cargo.toml | 1 + frame/ibc/Cargo.lock | 3462 +++++++++++++++++ frame/ibc/Cargo.toml | 84 + frame/ibc/LICENSE | 201 + frame/ibc/Makefile | 2 + frame/ibc/README.md | 106 + frame/ibc/src/benchmarking.rs | 24 + frame/ibc/src/context.rs | 48 + frame/ibc/src/events.rs | 339 ++ frame/ibc/src/lib.rs | 679 ++++ frame/ibc/src/mock.rs | 86 + frame/ibc/src/module/applications/mod.rs | 1 + .../module/applications/transfer/channel.rs | 289 ++ .../src/module/applications/transfer/mod.rs | 259 ++ .../transfer/transfer_handle_callback.rs | 170 + .../module/clients/ics07_tendermint/header.rs | 339 ++ .../module/clients/ics07_tendermint/mod.rs | 1 + frame/ibc/src/module/clients/mod.rs | 2 + frame/ibc/src/module/core/ics02_client.rs | 4 + .../src/module/core/ics02_client/context.rs | 297 ++ .../src/module/core/ics02_client/events.rs | 22 + .../src/module/core/ics02_client/header.rs | 11 + frame/ibc/src/module/core/ics03_connection.rs | 153 + frame/ibc/src/module/core/ics04_channel.rs | 615 +++ frame/ibc/src/module/core/ics05_port.rs | 25 + frame/ibc/src/module/core/ics23_commitment.rs | 1 + frame/ibc/src/module/core/ics24_host.rs | 246 ++ frame/ibc/src/module/core/ics26_routing.rs | 72 + frame/ibc/src/module/core/mod.rs | 7 + frame/ibc/src/module/mod.rs | 4 + frame/ibc/src/module/relayer/mod.rs | 47 + frame/ibc/src/tests.rs | 637 +++ frame/ibc/src/traits.rs | 9 + frame/ibc/src/utils.rs | 41 + 35 files changed, 9662 insertions(+), 1007 deletions(-) create mode 100644 frame/ibc/Cargo.lock create mode 100644 frame/ibc/Cargo.toml create mode 100644 frame/ibc/LICENSE create mode 100644 frame/ibc/Makefile create mode 100644 frame/ibc/README.md create mode 100644 frame/ibc/src/benchmarking.rs create mode 100644 frame/ibc/src/context.rs create mode 100644 frame/ibc/src/events.rs create mode 100644 frame/ibc/src/lib.rs create mode 100644 frame/ibc/src/mock.rs create mode 100644 frame/ibc/src/module/applications/mod.rs create mode 100644 frame/ibc/src/module/applications/transfer/channel.rs create mode 100644 frame/ibc/src/module/applications/transfer/mod.rs create mode 100644 frame/ibc/src/module/applications/transfer/transfer_handle_callback.rs create mode 100644 frame/ibc/src/module/clients/ics07_tendermint/header.rs create mode 100644 frame/ibc/src/module/clients/ics07_tendermint/mod.rs create mode 100644 frame/ibc/src/module/clients/mod.rs create mode 100644 frame/ibc/src/module/core/ics02_client.rs create mode 100644 frame/ibc/src/module/core/ics02_client/context.rs create mode 100644 frame/ibc/src/module/core/ics02_client/events.rs create mode 100644 frame/ibc/src/module/core/ics02_client/header.rs create mode 100644 frame/ibc/src/module/core/ics03_connection.rs create mode 100644 frame/ibc/src/module/core/ics04_channel.rs create mode 100644 frame/ibc/src/module/core/ics05_port.rs create mode 100644 frame/ibc/src/module/core/ics23_commitment.rs create mode 100644 frame/ibc/src/module/core/ics24_host.rs create mode 100644 frame/ibc/src/module/core/ics26_routing.rs create mode 100644 frame/ibc/src/module/core/mod.rs create mode 100644 frame/ibc/src/module/mod.rs create mode 100644 frame/ibc/src/module/relayer/mod.rs create mode 100644 frame/ibc/src/tests.rs create mode 100644 frame/ibc/src/traits.rs create mode 100644 frame/ibc/src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index 38063e8060835..49074130c01d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,7 +18,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" dependencies = [ - "gimli 0.26.1", + "gimli", ] [[package]] @@ -29,30 +29,30 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aead" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e3e798aa0c8239776f54415bc06f3d74b1850f3f830b45c35cfc80556973f70" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.6", ] [[package]] name = "aes" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "495ee669413bfbe9e8cace80f4d3d78e6d8c8d99579f97fb93bde351b185f2d4" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ "cfg-if 1.0.0", "cipher", - "cpufeatures 0.1.5", + "cpufeatures", "opaque-debug 0.3.0", ] [[package]] name = "aes-gcm" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a930fd487faaa92a30afa92cc9dd1526a5cff67124abbbb1c617ce070f4dcf" +checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" dependencies = [ "aead", "aes", @@ -68,7 +68,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.3", + "getrandom 0.2.7", "once_cell", "version_check", ] @@ -82,6 +82,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "android_system_properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7ed72e1635e121ca3e79420540282af22da58be50de153d36f81ddc6b83aa9e" +dependencies = [ + "libc", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -93,24 +102,24 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.38" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afddf7f520a80dbf76e6f50a35bca42a2331ef227a28b3b6dc5c2e2338d114b1" +checksum = "508b352bb5c066aac251f6daf6b36eccd03e8a88e8081cd44959ea277a3af9a8" [[package]] name = "approx" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "072df7202e63b127ab55acfe16ce97013d5b97bf160489336d3f1840fd78e99e" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" dependencies = [ "num-traits", ] [[package]] name = "arbitrary" -version = "1.0.0" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "698b65a961a9d730fb45b6b0327e20207810c9f61ee421b082b27ba003f49e2b" +checksum = "5a7924531f38b1970ff630f03eb20a2fde69db5c590c93b0f3482e95dcc5fd60" [[package]] name = "arrayref" @@ -141,15 +150,15 @@ checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" [[package]] name = "asn1_der" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6e24d2cce90c53b948c46271bfb053e4bdc2db9b5d3f65e20f8cf28a1b7fc3" +checksum = "e22d1f4b888c298a027c99dc9048015fac177587de20fc30232a057dfbe24a21" [[package]] name = "assert_cmd" -version = "2.0.2" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e996dc7940838b7ef1096b882e29ec30a3149a3a443cdc8dba19ed382eca1fe2" +checksum = "93ae1ddd39efd67689deb1979d80bad3bf7f2b09c6e6117c8d1f2443b5e2f83e" dependencies = [ "bstr", "doc-comment", @@ -177,9 +186,9 @@ dependencies = [ [[package]] name = "async-channel" -version = "1.6.1" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" +checksum = "e14485364214912d3b19cc3435dde4df66065127f05fa0d75c712f36f12c2f28" dependencies = [ "concurrent-queue", "event-listener", @@ -188,28 +197,28 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb877970c7b440ead138f6321a3b5395d6061183af779340b65e20c0fede9146" +checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965" dependencies = [ "async-task", "concurrent-queue", "fastrand", "futures-lite", "once_cell", - "vec-arena", + "slab", ] [[package]] name = "async-global-executor" -version = "2.0.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9586ec52317f36de58453159d48351bc244bc24ced3effc1fce22f3d48664af6" +checksum = "5262ed948da60dd8956c6c5aca4d4163593dddb7b32d73267c93dab7b2e98940" dependencies = [ "async-channel", "async-executor", "async-io", - "async-mutex", + "async-lock", "blocking", "futures-lite", "num_cpus", @@ -218,9 +227,9 @@ dependencies = [ [[package]] name = "async-io" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a811e6a479f2439f0c04038796b5cfb3d2ad56c230e0f2d3f7b04d68cfee607b" +checksum = "e5e18f61464ae81cde0a23e713ae8fd299580c54d697a35820cfd0625b8b0e07" dependencies = [ "concurrent-queue", "futures-lite", @@ -244,15 +253,6 @@ dependencies = [ "event-listener", ] -[[package]] -name = "async-mutex" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" -dependencies = [ - "event-listener", -] - [[package]] name = "async-process" version = "1.4.0" @@ -272,9 +272,9 @@ dependencies = [ [[package]] name = "async-std" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52580991739c5cdb36cde8b2a516371c0a3b70dda36d916cc08b82372916808c" +checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" dependencies = [ "async-attributes", "async-channel", @@ -291,9 +291,8 @@ dependencies = [ "kv-log-macro", "log", "memchr", - "num_cpus", "once_cell", - "pin-project-lite 0.2.6", + "pin-project-lite 0.2.9", "pin-utils", "slab", "wasm-bindgen-futures", @@ -316,9 +315,9 @@ dependencies = [ [[package]] name = "async-stream" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625" +checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" dependencies = [ "async-stream-impl", "futures-core", @@ -326,9 +325,9 @@ dependencies = [ [[package]] name = "async-stream-impl" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308" +checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" dependencies = [ "proc-macro2", "quote", @@ -337,15 +336,15 @@ dependencies = [ [[package]] name = "async-task" -version = "4.0.3" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0" +checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" [[package]] name = "async-trait" -version = "0.1.51" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e" +checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" dependencies = [ "proc-macro2", "quote", @@ -362,7 +361,7 @@ dependencies = [ "futures-sink", "futures-util", "memchr", - "pin-project-lite 0.2.6", + "pin-project-lite 0.2.9", ] [[package]] @@ -384,30 +383,30 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.64" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e121dee8023ce33ab248d9ce1493df03c3b38a659b240096fcbd7048ff9c31f" +checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" dependencies = [ "addr2line", "cc", "cfg-if 1.0.0", "libc", "miniz_oxide", - "object 0.27.1", + "object 0.29.0", "rustc-demangle", ] [[package]] name = "base-x" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" +checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" [[package]] name = "base16ct" @@ -429,9 +428,9 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "beef" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bed554bd50246729a1ec158d08aa3235d1b69d94ad120ebe187e28894787e736" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" dependencies = [ "serde", ] @@ -448,7 +447,7 @@ dependencies = [ "hex", "log", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "sc-block-builder", "sc-chain-spec", "sc-client-api", @@ -491,7 +490,7 @@ dependencies = [ "jsonrpsee", "log", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "sc-rpc", "sc-utils", "serde", @@ -534,17 +533,16 @@ dependencies = [ [[package]] name = "bimap" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50ae17cabbc8a38a1e3e4c1a6a664e9a09672dc14d0896fa8d865d3a5a446b07" +checksum = "bc0455254eb5c6964c4545d8bac815e1a1be4f3afe0ae695ea539c12d728d44b" [[package]] name = "bincode" -version = "1.3.2" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d175dfa69e619905c4c3cdb7c3c203fa3bdd5d51184e3afdb2742c0280493772" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ - "byteorder", "serde", ] @@ -575,9 +573,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitvec" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1489fcb93a5bb47da0462ca93ad252ad6af2145cce58d10d46a83931ba9f016b" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" dependencies = [ "funty", "radium", @@ -587,9 +585,9 @@ dependencies = [ [[package]] name = "blake2" -version = "0.10.2" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94ba84325db59637ffc528bbe8c7f86c02c57cff5c0e2b9b00f9a851f42f309" +checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388" dependencies = [ "digest 0.10.3", ] @@ -658,16 +656,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ "block-padding 0.2.1", - "generic-array 0.14.4", + "generic-array 0.14.6", ] [[package]] name = "block-buffer" -version = "0.10.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1d36a02058e76b040de25a4464ba1c80935655595b661505c8b39b664828b95" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.6", ] [[package]] @@ -687,9 +685,9 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] name = "blocking" -version = "1.0.2" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e170dbede1f740736619b776d7251cb1b9095c435c34d8ca9f57fcd2f335e9" +checksum = "c6ccb65d468978a086b69884437ded69a90faab3bbe6e67f242173ea728acccc" dependencies = [ "async-channel", "async-task", @@ -707,9 +705,9 @@ checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" [[package]] name = "bstr" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a40b47ad93e1a5404e6c18dec46b628214fee441c70f4ab5d6942142cc268a3d" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" dependencies = [ "lazy_static", "memchr", @@ -728,15 +726,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.6.1" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" [[package]] name = "byte-slice-cast" -version = "1.0.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65c1bf4a04a88c54f589125563643d773f3254b5c38571395e2b591c693bbc81" +checksum = "87c5fdd0166095e1d463fc6cc01aa8ce547ad77a4e84d42eb6762b084e28067e" [[package]] name = "byte-tools" @@ -746,9 +744,9 @@ checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" [[package]] name = "bytecheck" -version = "0.6.7" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "314889ea31cda264cb7c3d6e6e5c9415a987ecb0e72c17c00d36fbb881d34abe" +checksum = "d11cac2c12b5adc6570dad2ee1b87eff4955dac476fe12d81e5fdd352e52406f" dependencies = [ "bytecheck_derive", "ptr_meta", @@ -756,9 +754,9 @@ dependencies = [ [[package]] name = "bytecheck_derive" -version = "0.6.7" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a2b3b92c135dae665a6f760205b89187638e83bed17ef3e44e83c712cf30600" +checksum = "13e576ebe98e605500b3c8041bb888e966653577172df6dd97398714eb30b9bf" dependencies = [ "proc-macro2", "quote", @@ -767,15 +765,15 @@ dependencies = [ [[package]] name = "byteorder" -version = "1.3.4" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" [[package]] name = "bzip2-sys" @@ -790,24 +788,24 @@ dependencies = [ [[package]] name = "cache-padded" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" +checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" [[package]] name = "camino" -version = "1.0.4" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4648c6d00a709aa069a236adcaae4f605a6241c72bf5bee79331a4b625921a9" +checksum = "869119e97797867fd90f5e22af7d0bd274bd4635ebb9eb68c04f3f513ae6c412" dependencies = [ "serde", ] [[package]] name = "cargo-platform" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0226944a63d1bf35a3b5f948dd7c59e263db83695c9e8bffc4037de02e30f1d7" +checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" dependencies = [ "serde", ] @@ -820,25 +818,22 @@ checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" dependencies = [ "camino", "cargo-platform", - "semver 1.0.4", + "semver 1.0.13", "serde", "serde_json", ] [[package]] name = "cast" -version = "0.2.3" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0" -dependencies = [ - "rustc_version 0.2.3", -] +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.71" +version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" dependencies = [ "jobserver", ] @@ -866,21 +861,21 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chacha20" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01b72a433d0cf2aef113ba70f62634c56fddb0f244e6377185c56a7cadbd8f91" +checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" dependencies = [ "cfg-if 1.0.0", "cipher", - "cpufeatures 0.2.1", + "cpufeatures", "zeroize", ] [[package]] name = "chacha20poly1305" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b84ed6d1d5f7aa9bdde921a5090e0ca4d934d250ea3b402a5fab3a994e28a2a" +checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5" dependencies = [ "aead", "chacha20", @@ -894,9 +889,9 @@ name = "chain-spec-builder" version = "2.0.0" dependencies = [ "ansi_term", - "clap 3.1.18", + "clap 3.2.16", "node-cli", - "rand 0.8.4", + "rand 0.8.5", "sc-chain-spec", "sc-keystore", "sp-core", @@ -905,22 +900,24 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "3f725f340c3854e3cb3ab736dc21f0cca183303acea3b3ffec30f141503ac8eb" dependencies = [ - "libc", + "iana-time-zone", + "js-sys", "num-integer", "num-traits", - "time", + "time 0.1.44", + "wasm-bindgen", "winapi", ] [[package]] name = "cid" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a52cffa791ce5cf490ac3b2d6df970dc04f931b04e727be3c3e220e17164dfc4" +checksum = "f6ed9c8b2d17acb8110c46f1da5bf4a696d745e1474a16db0cd2b49cd0249bf2" dependencies = [ "core2", "multibase", @@ -935,7 +932,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.6", ] [[package]] @@ -949,13 +946,13 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.2.0" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "853eda514c284c2287f4bf20ae614f8781f40a81d32ecda6e91449304dfe077c" +checksum = "5a050e2153c5be08febd6734e29298e844fdb0fa21aeddd63b4eb7baa106c69b" dependencies = [ "glob", "libc", - "libloading 0.7.0", + "libloading 0.7.3", ] [[package]] @@ -971,16 +968,16 @@ dependencies = [ [[package]] name = "clap" -version = "3.1.18" +version = "3.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2dbdf4bdacb33466e854ce889eee8dfd5729abf7ccd7664d0a2d60cd384440b" +checksum = "a3dbbb6653e7c55cc8595ad3e1f7be8f32aba4eb7ff7f0fd1163d4f3d137c0a9" dependencies = [ "atty", "bitflags", "clap_derive", "clap_lex", "indexmap", - "lazy_static", + "once_cell", "strsim", "termcolor", "textwrap 0.15.0", @@ -988,18 +985,18 @@ dependencies = [ [[package]] name = "clap_complete" -version = "3.0.2" +version = "3.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a394f7ec0715b42a4e52b294984c27c9a61f77c8d82f7774c5198350be143f19" +checksum = "ead064480dfc4880a10764488415a97fdd36a4cf1bb022d372f02e8faf8386e1" dependencies = [ - "clap 3.1.18", + "clap 3.2.16", ] [[package]] name = "clap_derive" -version = "3.1.18" +version = "3.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25320346e922cffe59c0bbc5410c8d8784509efb321488971081313cb1e1a33c" +checksum = "9ba52acd3b0a5c33aeada5cdaa3267cdc7c594a98731d4268cdc1532f4264cb4" dependencies = [ "heck", "proc-macro-error", @@ -1010,18 +1007,18 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.2.0" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" dependencies = [ "os_str_bytes", ] [[package]] name = "cmake" -version = "0.1.46" +version = "0.1.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7b858541263efe664aead4a5209a4ae5c5d2811167d4ed4ee0944503f8d2089" +checksum = "e8ad8cef104ac57b68b89df3208164d228503abbdce70f6880ffa3d970e7443a" dependencies = [ "cc", ] @@ -1039,9 +1036,9 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "1.2.2" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" +checksum = "af4780a44ab5696ea9e28294517f1fffb421a83a25af521333c838635509db9c" dependencies = [ "cache-padded", ] @@ -1084,68 +1081,65 @@ dependencies = [ ] [[package]] -name = "cpp_demangle" -version = "0.3.2" +name = "corosensei" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44919ecaf6f99e8e737bc239408931c9a01e9a6c74814fee8242dd2506b65390" +checksum = "9847f90f32a50b0dcbd68bc23ff242798b13080b97b0569f6ed96a45ce4cf2cd" dependencies = [ + "autocfg", "cfg-if 1.0.0", - "glob", + "libc", + "scopeguard", + "windows-sys 0.33.0", ] [[package]] -name = "cpufeatures" -version = "0.1.5" +name = "cpp_demangle" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef" +checksum = "eeaa953eaad386a53111e47172c2fedba671e5684c8dd601a5f474f4f118710f" dependencies = [ - "libc", + "cfg-if 1.0.0", ] [[package]] name = "cpufeatures" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" dependencies = [ "libc", ] -[[package]] -name = "cpuid-bool" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" - [[package]] name = "cranelift-bforest" -version = "0.76.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e6bea67967505247f54fa2c85cf4f6e0e31c4e5692c9b70e4ae58e339067333" +checksum = "38faa2a16616c8e78a18d37b4726b98bfd2de192f2fdc8a39ddf568a408a0f75" dependencies = [ - "cranelift-entity 0.76.0", + "cranelift-entity 0.82.3", ] [[package]] name = "cranelift-bforest" -version = "0.85.0" +version = "0.85.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899dc8d22f7771e7f887fb8bafa0c0d3ac1dea0c7f2c0ded6e20a855a7a1e890" +checksum = "749d0d6022c9038dccf480bdde2a38d435937335bf2bb0f14e815d94517cdce8" dependencies = [ - "cranelift-entity 0.85.0", + "cranelift-entity 0.85.3", ] [[package]] name = "cranelift-codegen" -version = "0.76.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48194035d2752bdd5bdae429e3ab88676e95f52a2b1355a5d4e809f9e39b1d74" +checksum = "26f192472a3ba23860afd07d2b0217dc628f21fcc72617aa1336d98e1671f33b" dependencies = [ - "cranelift-bforest 0.76.0", - "cranelift-codegen-meta 0.76.0", - "cranelift-codegen-shared 0.76.0", - "cranelift-entity 0.76.0", - "gimli 0.25.0", + "cranelift-bforest 0.82.3", + "cranelift-codegen-meta 0.82.3", + "cranelift-codegen-shared 0.82.3", + "cranelift-entity 0.82.3", + "gimli", "log", "regalloc", "smallvec", @@ -1154,16 +1148,16 @@ dependencies = [ [[package]] name = "cranelift-codegen" -version = "0.85.0" +version = "0.85.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dbdc03f695cf67e7bc45da57155528274f47390b85060af8107eb304ef167c4" +checksum = "e94370cc7b37bf652ccd8bb8f09bd900997f7ccf97520edfc75554bb5c4abbea" dependencies = [ - "cranelift-bforest 0.85.0", - "cranelift-codegen-meta 0.85.0", - "cranelift-codegen-shared 0.85.0", - "cranelift-entity 0.85.0", + "cranelift-bforest 0.85.3", + "cranelift-codegen-meta 0.85.3", + "cranelift-codegen-shared 0.85.3", + "cranelift-entity 0.85.3", "cranelift-isle", - "gimli 0.26.1", + "gimli", "log", "regalloc2", "smallvec", @@ -1172,57 +1166,56 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.76.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976efb22fcab4f2cd6bd4e9913764616a54d895c1a23530128d04e03633c555f" +checksum = "0f32ddb89e9b89d3d9b36a5b7d7ea3261c98235a76ac95ba46826b8ec40b1a24" dependencies = [ - "cranelift-codegen-shared 0.76.0", - "cranelift-entity 0.76.0", + "cranelift-codegen-shared 0.82.3", ] [[package]] name = "cranelift-codegen-meta" -version = "0.85.0" +version = "0.85.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea66cbba3eb7fcb3ec9f42839a6d381bd40cf97780397e7167daf9725d4ffa0" +checksum = "e0a3cea8fdab90e44018c5b9a1dfd460d8ee265ac354337150222a354628bdb6" dependencies = [ - "cranelift-codegen-shared 0.85.0", + "cranelift-codegen-shared 0.85.3", ] [[package]] name = "cranelift-codegen-shared" -version = "0.76.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dabb5fe66e04d4652e434195b45ae65b5c8172d520247b8f66d8df42b2b45dc" +checksum = "01fd0d9f288cc1b42d9333b7a776b17e278fc888c28e6a0f09b5573d45a150bc" [[package]] name = "cranelift-codegen-shared" -version = "0.85.0" +version = "0.85.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712fbebd119a476f59122b4ba51fdce893a66309b5c92bd5506bfb11a0587496" +checksum = "5ac72f76f2698598951ab26d8c96eaa854810e693e7dd52523958b5909fde6b2" [[package]] name = "cranelift-entity" -version = "0.76.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3329733e4d4b8e91c809efcaa4faee80bf66f20164e3dd16d707346bd3494799" +checksum = "9e3bfe172b83167604601faf9dc60453e0d0a93415b57a9c4d1a7ae6849185cf" [[package]] name = "cranelift-entity" -version = "0.85.0" +version = "0.85.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cb8b95859c4e14c9e860db78d596a904fdbe9261990233b62bd526346cb56cb" +checksum = "09eaeacfcd2356fe0e66b295e8f9d59fdd1ac3ace53ba50de14d628ec902f72d" dependencies = [ "serde", ] [[package]] name = "cranelift-frontend" -version = "0.76.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279afcc0d3e651b773f94837c3d581177b348c8d69e928104b2e9fccb226f921" +checksum = "a006e3e32d80ce0e4ba7f1f9ddf66066d052a8c884a110b91d05404d6ce26dce" dependencies = [ - "cranelift-codegen 0.76.0", + "cranelift-codegen 0.82.3", "log", "smallvec", "target-lexicon", @@ -1230,11 +1223,11 @@ dependencies = [ [[package]] name = "cranelift-frontend" -version = "0.85.0" +version = "0.85.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7b91b19a7d1221a73f190c0e865c12be77a84f661cac89abfd4ab5820142886" +checksum = "dba69c9980d5ffd62c18a2bde927855fcd7c8dc92f29feaf8636052662cbd99c" dependencies = [ - "cranelift-codegen 0.85.0", + "cranelift-codegen 0.85.3", "log", "smallvec", "target-lexicon", @@ -1242,30 +1235,30 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.85.0" +version = "0.85.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86d4f53bc86fb458e59c695c6a95ce8346e6a8377ee7ffc058e3ac08b5f94cb1" +checksum = "d2920dc1e05cac40304456ed3301fde2c09bd6a9b0210bcfa2f101398d628d5b" [[package]] name = "cranelift-native" -version = "0.85.0" +version = "0.85.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "592f035d0ed41214dfeeb37abd536233536a27be6b4c2d39f380cd402f0cff4f" +checksum = "f04dfa45f9b2a6f587c564d6b63388e00cd6589d2df6ea2758cf79e1a13285e6" dependencies = [ - "cranelift-codegen 0.85.0", + "cranelift-codegen 0.85.3", "libc", "target-lexicon", ] [[package]] name = "cranelift-wasm" -version = "0.85.0" +version = "0.85.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "295add6bf0b527a8bc50d02e31ff878585d2d2db53cb7e8754d6d82b84480086" +checksum = "31a46513ae6f26f3f267d8d75b5373d555fbbd1e68681f348d99df43f747ec54" dependencies = [ - "cranelift-codegen 0.85.0", - "cranelift-entity 0.85.0", - "cranelift-frontend 0.85.0", + "cranelift-codegen 0.85.3", + "cranelift-entity 0.85.3", + "cranelift-frontend 0.85.3", "itertools", "log", "smallvec", @@ -1275,18 +1268,18 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if 1.0.0", ] [[package]] name = "criterion" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1604dafd25fba2fe2d5895a9da139f8dc9b319a5fe5354ca137cbbce4e178d10" +checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f" dependencies = [ "atty", "cast", @@ -1312,9 +1305,9 @@ dependencies = [ [[package]] name = "criterion-plot" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d00996de9f2f7559f7f4dc286073197f83e92256a59ed395f9aac01fe717da57" +checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876" dependencies = [ "cast", "itertools", @@ -1322,9 +1315,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.0" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" dependencies = [ "cfg-if 1.0.0", "crossbeam-utils", @@ -1332,9 +1325,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", @@ -1343,25 +1336,26 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.5" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" +checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1" dependencies = [ + "autocfg", "cfg-if 1.0.0", "crossbeam-utils", - "lazy_static", "memoffset", + "once_cell", "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.8" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" +checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" dependencies = [ "cfg-if 1.0.0", - "lazy_static", + "once_cell", ] [[package]] @@ -1376,19 +1370,19 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" dependencies = [ - "generic-array 0.14.4", - "rand_core 0.6.2", + "generic-array 0.14.6", + "rand_core 0.6.3", "subtle", "zeroize", ] [[package]] name = "crypto-common" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.6", "typenum", ] @@ -1398,7 +1392,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.6", "subtle", ] @@ -1408,7 +1402,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.6", "subtle", ] @@ -1436,9 +1430,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.1.19" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8f45d9ad417bcef4817d614a501ab55cdd96a6fdb24f49aab89a54acfd66b19" +checksum = "cdffe87e1d521a10f9696f833fe502293ea446d7f256c06128293a4119bdf4cb" dependencies = [ "quote", "syn", @@ -1466,9 +1460,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434e1720189a637d44fe464f4df1e6eb900b4835255b14354497c78af37d9bb8" +checksum = "4a9b85542f99a2dfa2a1b8e192662741c9859a846b296bef1c92ef9b58b5a216" dependencies = [ "byteorder", "digest 0.8.1", @@ -1479,9 +1473,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "3.0.2" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f627126b946c25a4638eec0ea634fc52506dea98db118aae985118ce7c3d723f" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" dependencies = [ "byteorder", "digest 0.9.0", @@ -1498,16 +1492,16 @@ checksum = "4033478fbf70d6acf2655ac70da91ee65852d69daf7a67bf7a2f518fb47aafcf" dependencies = [ "byteorder", "digest 0.9.0", - "rand_core 0.6.2", + "rand_core 0.6.3", "subtle", "zeroize", ] [[package]] name = "darling" -version = "0.13.0" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "757c0ded2af11d8e739c4daea1ac623dd1624b06c844cf3f5a39f1bdbd99bb12" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" dependencies = [ "darling_core", "darling_macro", @@ -1515,23 +1509,22 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.13.0" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c34d8efb62d0c2d7f60ece80f75e5c63c1588ba68032740494b0b9a996466e3" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "strsim", "syn", ] [[package]] name = "darling_macro" -version = "0.13.0" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade7bff147130fe5e6d39f089c6bd49ec0250f35d70b2eebf72afdfc919f15cc" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" dependencies = [ "darling_core", "quote", @@ -1546,9 +1539,9 @@ checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" [[package]] name = "data-encoding-macro" -version = "0.1.10" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a94feec3d2ba66c0b6621bca8bc6f68415b1e5c69af3586fdd0af9fd9f29b17" +checksum = "86927b7cd2fe88fa698b87404b287ab98d1a0063a34071d92e575b72d3029aca" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -1556,9 +1549,9 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f83e699727abca3c56e187945f303389590305ab2f0185ea445aa66e8d5f2a" +checksum = "a5bbed42daaa95e780b60a50546aa345b8413a1e46f9a40a12907d3598f038db" dependencies = [ "data-encoding", "syn", @@ -1586,9 +1579,9 @@ dependencies = [ [[package]] name = "diff" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" [[package]] name = "difflib" @@ -1611,7 +1604,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.6", ] [[package]] @@ -1620,7 +1613,7 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" dependencies = [ - "block-buffer 0.10.0", + "block-buffer 0.10.2", "crypto-common", "subtle", ] @@ -1646,9 +1639,9 @@ dependencies = [ [[package]] name = "dirs-sys" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" dependencies = [ "libc", "redox_users", @@ -1668,9 +1661,9 @@ dependencies = [ [[package]] name = "dissimilar" -version = "1.0.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4b29f4b9bb94bf267d57269fd0706d343a160937108e9619fe380645428abb" +checksum = "8c97b9233581d84b8e1e689cdd3a47b6f69770084fc246e86a7f78b0d9c1d4a5" [[package]] name = "dns-parser" @@ -1679,7 +1672,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4d33be9473d06f75f58220f71f7a9317aca647dc061dbd3c361b0bef505fbea" dependencies = [ "byteorder", - "quick-error 1.2.3", + "quick-error", ] [[package]] @@ -1696,9 +1689,9 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] name = "dtoa" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5caaa75cbd2b960ff1e5392d2cfb1f44717fffe12fc1f32b7b5d1267f99732a6" +checksum = "c6053ff46b5639ceb91756a85a4c8914668393a03170efd79c8884a529d80656" [[package]] name = "dyn-clonable" @@ -1723,9 +1716,9 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.5" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e50f3adc76d6a43f5ed73b698a87d0760ca74617f60f7c3b879003536fdd28" +checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2" [[package]] name = "dynasm" @@ -1750,7 +1743,7 @@ checksum = "64fba5a42bd76a17cad4bfa00de168ee1cbfa06a5e8ce992ae880218c05641a9" dependencies = [ "byteorder", "dynasm", - "memmap2 0.5.0", + "memmap2 0.5.5", ] [[package]] @@ -1767,9 +1760,9 @@ dependencies = [ [[package]] name = "ed25519" -version = "1.0.3" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c66a534cbb46ab4ea03477eae19d5c22c01da8258030280b7bd9d8433fb6ef" +checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" dependencies = [ "signature", ] @@ -1780,19 +1773,19 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" dependencies = [ - "curve25519-dalek 3.0.2", + "curve25519-dalek 3.2.0", "ed25519", "rand 0.7.3", "serde", - "sha2 0.9.8", + "sha2 0.9.9", "zeroize", ] [[package]] name = "either" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" [[package]] name = "elliptic-curve" @@ -1804,9 +1797,9 @@ dependencies = [ "crypto-bigint", "der", "ff", - "generic-array 0.14.4", + "generic-array 0.14.6", "group", - "rand_core 0.6.2", + "rand_core 0.6.3", "sec1", "subtle", "zeroize", @@ -1846,9 +1839,9 @@ dependencies = [ [[package]] name = "enumflags2" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b3ab37dc79652c9d85f1f7b6070d77d321d2467f5fe7b00d6b7a86c57b092ae" +checksum = "e75d4cd21b95383444831539909fbb14b9dc3fdceb2a6f5d36577329a1f55ccb" dependencies = [ "enumflags2_derive", ] @@ -1866,18 +1859,18 @@ dependencies = [ [[package]] name = "enumset" -version = "1.0.7" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e76129da36102af021b8e5000dab2c1c30dbef85c1e482beeff8da5dde0e0b0" +checksum = "4799cdb24d48f1f8a7a98d06b7fde65a85a2d1e42b25a889f5406aa1fbefe074" dependencies = [ "enumset_derive", ] [[package]] name = "enumset_derive" -version = "0.5.5" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6451128aa6655d880755345d085494cf7561a6bee7c8dc821e5d77e6d267ecd4" +checksum = "ea83a3fbdc1d999ccfbcbee717eab36f8edf2d71693a23ce0d7cca19e085304c" dependencies = [ "darling", "proc-macro2", @@ -1917,19 +1910,19 @@ dependencies = [ [[package]] name = "errno-dragonfly" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" dependencies = [ - "gcc", + "cc", "libc", ] [[package]] name = "event-listener" -version = "2.5.1" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "exit-future" @@ -1940,6 +1933,16 @@ dependencies = [ "futures", ] +[[package]] +name = "eyre" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "fake-simd" version = "0.1.2" @@ -1954,9 +1957,9 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fastrand" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" dependencies = [ "instant", ] @@ -1972,11 +1975,11 @@ dependencies = [ [[package]] name = "ff" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2958d04124b9f27f175eaeb9a9f383d026098aa837eadd8ba22c11f13a05b9e" +checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" dependencies = [ - "rand_core 0.6.2", + "rand_core 0.6.3", "subtle", ] @@ -1992,14 +1995,14 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0408e2626025178a6a7f7ffc05a25bc47103229f19c113755de7bf63816290c" +checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c" dependencies = [ "cfg-if 1.0.0", "libc", "redox_syscall", - "winapi", + "windows-sys 0.36.1", ] [[package]] @@ -2014,8 +2017,8 @@ dependencies = [ "log", "num-traits", "parity-scale-codec", - "parking_lot 0.12.0", - "rand 0.8.4", + "parking_lot 0.12.1", + "rand 0.8.5", "scale-info", ] @@ -2026,30 +2029,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" dependencies = [ "byteorder", - "rand 0.8.4", + "rand 0.8.5", "rustc-hex", "static_assertions", ] [[package]] name = "fixedbitset" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "398ea4fabe40b9b0d885340a2a991a44c8a645624075ad966d21f88688e2b69e" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.20" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0" +checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" dependencies = [ - "cfg-if 1.0.0", "crc32fast", - "libc", "libz-sys", "miniz_oxide", ] +[[package]] +name = "flex-error" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c606d892c9de11507fa0dcffc116434f94e105d0bbdc4e405b61519464c49d7b" +dependencies = [ + "eyre", + "paste 1.0.8", +] + [[package]] name = "fnv" version = "1.0.7" @@ -2083,7 +2094,7 @@ dependencies = [ "linregress", "log", "parity-scale-codec", - "paste 1.0.6", + "paste 1.0.8", "scale-info", "serde", "sp-api", @@ -2102,7 +2113,7 @@ version = "4.0.0-dev" dependencies = [ "Inflector", "chrono", - "clap 3.1.18", + "clap 3.2.16", "comfy-table", "frame-benchmarking", "frame-support", @@ -2118,7 +2129,7 @@ dependencies = [ "log", "memory-db", "parity-scale-codec", - "rand 0.8.4", + "rand 0.8.5", "rand_pcg 0.3.1", "sc-block-builder", "sc-cli", @@ -2184,13 +2195,13 @@ dependencies = [ name = "frame-election-solution-type-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 3.1.18", + "clap 3.2.16", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-support", "honggfuzz", "parity-scale-codec", - "rand 0.8.4", + "rand 0.8.5", "scale-info", "sp-arithmetic", "sp-npos-elections", @@ -2244,7 +2255,7 @@ dependencies = [ "once_cell", "parity-scale-codec", "parity-util-mem", - "paste 1.0.6", + "paste 1.0.8", "pretty_assertions", "scale-info", "serde", @@ -2476,16 +2487,16 @@ checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" [[package]] name = "futures-lite" -version = "1.11.3" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4481d0cd0de1d204a4fa55e7d45f07b1d958abcb06714b3446438e2eff695fb" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" dependencies = [ "fastrand", "futures-core", "futures-io", "memchr", "parking", - "pin-project-lite 0.2.6", + "pin-project-lite 0.2.9", "waker-fn", ] @@ -2502,9 +2513,9 @@ dependencies = [ [[package]] name = "futures-rustls" -version = "0.22.1" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01fe9932a224b72b45336d96040aa86386d674a31d0af27d800ea7bc8ca97fe" +checksum = "d2411eed028cdf8c8034eaf21f9915f956b6c3abec4d4c7949ee67f0721127bd" dependencies = [ "futures-io", "rustls", @@ -2542,7 +2553,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project-lite 0.2.6", + "pin-project-lite 0.2.9", "pin-utils", "slab", ] @@ -2556,12 +2567,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "gcc" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" - [[package]] name = "generate-bags" version = "4.0.0-dev" @@ -2587,9 +2592,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.4" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", "version_check", @@ -2620,20 +2625,20 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.3" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi 0.10.0+wasi-snapshot-preview1", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] name = "ghash" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b442c439366184de619215247d24e908912b175e824a530253845ac4c251a5c1" +checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" dependencies = [ "opaque-debug 0.3.0", "polyval", @@ -2641,20 +2646,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7" -dependencies = [ - "fallible-iterator", - "indexmap", - "stable_deref_trait", -] - -[[package]] -name = "gimli" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" dependencies = [ "fallible-iterator", "indexmap", @@ -2663,9 +2657,9 @@ dependencies = [ [[package]] name = "git2" -version = "0.14.2" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3826a6e0e2215d7a41c2bfc7c9244123969273f3476b939a226aac0ab56e9e3c" +checksum = "d0155506aab710a86160ddb504a480d2964d7ab5b9e62419be69e0032bc5931c" dependencies = [ "bitflags", "libc", @@ -2682,9 +2676,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "globset" -version = "0.4.6" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c152169ef1e421390738366d2f796655fec62621dabbd0fd476f905934061e4a" +checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a" dependencies = [ "aho-corasick", "bstr", @@ -2695,15 +2689,14 @@ dependencies = [ [[package]] name = "gloo-timers" -version = "0.2.1" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f" +checksum = "5fb7d06c1c8cc2a29bee7ec961009a0b2caa0793ee4900c2ffb348734ba1c8f9" dependencies = [ "futures-channel", "futures-core", "js-sys", "wasm-bindgen", - "web-sys", ] [[package]] @@ -2713,10 +2706,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" dependencies = [ "ff", - "rand_core 0.6.2", + "rand_core 0.6.3", "subtle", ] +[[package]] +name = "gumdrop" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bc700f989d2f6f0248546222d9b4258f5b02a171a431f8285a81c08142629e3" +dependencies = [ + "gumdrop_derive", +] + +[[package]] +name = "gumdrop_derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "729f9bd3449d77e7831a18abfb7ba2f99ee813dfd15b8c2167c9a54ba20aa99d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "h2" version = "0.3.13" @@ -2738,22 +2751,22 @@ dependencies = [ [[package]] name = "half" -version = "1.7.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "handlebars" -version = "4.2.2" +version = "4.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d6a30320f094710245150395bc763ad23128d6a1ebbad7594dc4164b62c56b" +checksum = "360d9740069b2f6cbb63ce2dbaa71a20d3185350cbb990d7bebeb9318415eb17" dependencies = [ "log", "pest", "pest_derive", - "quick-error 2.0.0", "serde", "serde_json", + "thiserror", ] [[package]] @@ -2782,9 +2795,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.12.0" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c21d40587b92fa6a6c6e3c1bdbf87d75511db5672f9c93175574b3a00df1758" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ "ahash", ] @@ -2797,9 +2810,9 @@ checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" [[package]] name = "hermit-abi" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] @@ -2849,7 +2862,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" dependencies = [ "digest 0.9.0", - "generic-array 0.14.4", + "generic-array 0.14.6", "hmac 0.8.1", ] @@ -2883,31 +2896,31 @@ checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ "bytes", "fnv", - "itoa 1.0.1", + "itoa 1.0.3", ] [[package]] name = "http-body" -version = "0.4.2" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60daa14be0e0786db0f03a9e57cb404c9d756eed2b6c62b9ea98ec5743ec75a9" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", "http", - "pin-project-lite 0.2.6", + "pin-project-lite 0.2.9", ] [[package]] name = "httparse" -version = "1.5.1" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" +checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" [[package]] name = "httpdate" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "humantime" @@ -2917,9 +2930,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.16" +version = "0.14.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7ec3e62bdc98a2f0393a5048e4c30ef659440ea6e0e572965103e72bd836f55" +checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" dependencies = [ "bytes", "futures-channel", @@ -2930,8 +2943,8 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 0.4.8", - "pin-project-lite 0.2.6", + "itoa 1.0.3", + "pin-project-lite 0.2.9", "socket2", "tokio", "tower-service", @@ -2954,6 +2967,78 @@ dependencies = [ "tokio-rustls", ] +[[package]] +name = "iana-time-zone" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9512e544c25736b82aebbd2bf739a47c8a1c935dfcc3a6adcde10e35cd3cd468" +dependencies = [ + "android_system_properties", + "core-foundation", + "js-sys", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "ibc" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ab02a177672699b494b7dd8396b633224614100cb216c41bc0f7175e2ff13cc" +dependencies = [ + "bytes", + "derive_more", + "flex-error", + "ibc-proto", + "ics23", + "num-traits", + "primitive-types", + "prost 0.11.0", + "prost-types 0.11.1", + "safe-regex", + "serde", + "serde_derive", + "serde_json", + "sha2 0.10.2", + "subtle-encoding", + "tendermint", + "tendermint-light-client-verifier", + "tendermint-proto", + "tendermint-testgen", + "time 0.3.11", + "tracing", + "uint", +] + +[[package]] +name = "ibc-proto" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e031bfb42dea291f45e17b8a547f3b8490e1aad15c0279134dd2ed08d15cf18" +dependencies = [ + "base64", + "bytes", + "prost 0.11.0", + "prost-types 0.11.1", + "serde", + "tendermint-proto", +] + +[[package]] +name = "ics23" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "149f3093d710c18c749ba68de3bc8131927aca00410ab90f3368e0524d01fe90" +dependencies = [ + "anyhow", + "bytes", + "hex", + "prost 0.11.0", + "ripemd", + "sha2 0.10.2", + "sha3 0.10.2", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -2983,9 +3068,9 @@ dependencies = [ [[package]] name = "if-watch" -version = "1.0.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae8f4a3c3d4c89351ca83e120c1c00b27df945d38e05695668c9d4b4f7bc52f3" +checksum = "015a7df1eb6dda30df37f34b63ada9b7b352984b0e84de2a20ed526345000791" dependencies = [ "async-io", "core-foundation", @@ -3010,9 +3095,9 @@ dependencies = [ [[package]] name = "impl-serde" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b47ca4d2b6931707a55fce5cf66aff80e2178c8b63bbb4ecb5695cbc870ddf6f" +checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" dependencies = [ "serde", ] @@ -3028,14 +3113,20 @@ dependencies = [ "syn", ] +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + [[package]] name = "indexmap" -version = "1.8.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg", - "hashbrown 0.11.2", + "hashbrown 0.12.3", "serde", ] @@ -3110,24 +3201,24 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" [[package]] name = "jobserver" -version = "0.1.21" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2" +checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.54" +version = "0.3.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1866b355d9c878e5e607473cbe3f63282c0b7aad2db1dbebf55076c686918254" +checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" dependencies = [ "wasm-bindgen", ] @@ -3187,8 +3278,8 @@ dependencies = [ "hyper", "jsonrpsee-types", "lazy_static", - "parking_lot 0.12.0", - "rand 0.8.4", + "parking_lot 0.12.1", + "rand 0.8.5", "rustc-hash", "serde", "serde_json", @@ -3290,9 +3381,9 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" +checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" [[package]] name = "keccak-hasher" @@ -3426,7 +3517,7 @@ checksum = "ece7e668abd21387aeb6628130a6f4c802787f014fa46bc83221448322250357" dependencies = [ "kvdb", "parity-util-mem", - "parking_lot 0.12.0", + "parking_lot 0.12.1", ] [[package]] @@ -3441,7 +3532,7 @@ dependencies = [ "num_cpus", "owning_ref", "parity-util-mem", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "regex", "rocksdb", "smallvec", @@ -3461,21 +3552,21 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "leb128" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3576a87f2ba00f6f106fdfcd16db1d698d648a26ad8e0573cad8537c3c362d2a" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.126" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +checksum = "64de3cc433455c14174d42e554d4027ee631c4d046d43e3ecc6efc4636cdc7a7" [[package]] name = "libgit2-sys" -version = "0.13.2+1.4.2" +version = "0.13.4+1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a42de9a51a5c12e00fc0e4ca6bc2ea43582fc6418488e8f615e905d886f258b" +checksum = "d0fa6563431ede25f5cc7f6d803c6afbc1c5d3ad3d4925d12c882bf2b526f5d1" dependencies = [ "cc", "libc", @@ -3495,9 +3586,9 @@ dependencies = [ [[package]] name = "libloading" -version = "0.7.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" +checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" dependencies = [ "cfg-if 1.0.0", "winapi", @@ -3505,9 +3596,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.1" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" +checksum = "292a948cd991e376cf75541fe5b97a1081d713c618b4f1b9500f8844e49eb565" [[package]] name = "libp2p" @@ -3518,7 +3609,7 @@ dependencies = [ "bytes", "futures", "futures-timer", - "getrandom 0.2.3", + "getrandom 0.2.7", "instant", "lazy_static", "libp2p-autonat", @@ -3547,7 +3638,7 @@ dependencies = [ "libp2p-websocket", "libp2p-yamux", "multiaddr", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "pin-project", "rand 0.7.3", "smallvec", @@ -3567,9 +3658,9 @@ dependencies = [ "libp2p-request-response", "libp2p-swarm", "log", - "prost", + "prost 0.10.4", "prost-build", - "rand 0.8.4", + "rand 0.8.5", ] [[package]] @@ -3592,11 +3683,11 @@ dependencies = [ "multiaddr", "multihash", "multistream-select", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "pin-project", - "prost", + "prost 0.10.4", "prost-build", - "rand 0.8.4", + "rand 0.8.5", "ring", "rw-stream-sink", "sha2 0.10.2", @@ -3628,7 +3719,7 @@ dependencies = [ "futures", "libp2p-core", "log", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "smallvec", "trust-dns-resolver", ] @@ -3645,7 +3736,7 @@ dependencies = [ "libp2p-core", "libp2p-swarm", "log", - "prost", + "prost 0.10.4", "prost-build", "rand 0.7.3", "smallvec", @@ -3669,7 +3760,7 @@ dependencies = [ "libp2p-swarm", "log", "prometheus-client", - "prost", + "prost 0.10.4", "prost-build", "rand 0.7.3", "regex", @@ -3692,7 +3783,7 @@ dependencies = [ "libp2p-swarm", "log", "lru", - "prost", + "prost 0.10.4", "prost-build", "prost-codec", "smallvec", @@ -3717,7 +3808,7 @@ dependencies = [ "libp2p-core", "libp2p-swarm", "log", - "prost", + "prost 0.10.4", "prost-build", "rand 0.7.3", "sha2 0.10.2", @@ -3743,7 +3834,7 @@ dependencies = [ "libp2p-core", "libp2p-swarm", "log", - "rand 0.8.4", + "rand 0.8.5", "smallvec", "socket2", "void", @@ -3777,7 +3868,7 @@ dependencies = [ "libp2p-core", "log", "nohash-hasher", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "rand 0.7.3", "smallvec", "unsigned-varint", @@ -3790,14 +3881,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "762408cb5d84b49a600422d7f9a42c18012d8da6ebcd570f9a4a4290ba41fb6f" dependencies = [ "bytes", - "curve25519-dalek 3.0.2", + "curve25519-dalek 3.2.0", "futures", "lazy_static", "libp2p-core", "log", - "prost", + "prost 0.10.4", "prost-build", - "rand 0.8.4", + "rand 0.8.5", "sha2 0.10.2", "snow", "static_assertions", @@ -3832,7 +3923,7 @@ dependencies = [ "futures", "libp2p-core", "log", - "prost", + "prost 0.10.4", "prost-build", "unsigned-varint", "void", @@ -3868,10 +3959,10 @@ dependencies = [ "libp2p-swarm", "log", "pin-project", - "prost", + "prost 0.10.4", "prost-build", "prost-codec", - "rand 0.8.4", + "rand 0.8.5", "smallvec", "static_assertions", "thiserror", @@ -3892,9 +3983,9 @@ dependencies = [ "libp2p-core", "libp2p-swarm", "log", - "prost", + "prost 0.10.4", "prost-build", - "rand 0.8.4", + "rand 0.8.5", "sha2 0.10.2", "thiserror", "unsigned-varint", @@ -4003,7 +4094,7 @@ dependencies = [ "futures-rustls", "libp2p-core", "log", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "quicksink", "rw-stream-sink", "soketto", @@ -4019,7 +4110,7 @@ checksum = "c6dea686217a06072033dc025631932810e2f6ad784e4fafa42e27d311c7a81c" dependencies = [ "futures", "libp2p-core", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "thiserror", "yamux", ] @@ -4041,9 +4132,9 @@ dependencies = [ [[package]] name = "libsecp256k1" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0452aac8bab02242429380e9b2f94ea20cea2b37e2c1777a1358799bbe97f37" +checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" dependencies = [ "arrayref", "base64", @@ -4052,9 +4143,9 @@ dependencies = [ "libsecp256k1-core", "libsecp256k1-gen-ecmult", "libsecp256k1-gen-genmult", - "rand 0.8.4", + "rand 0.8.5", "serde", - "sha2 0.9.8", + "sha2 0.9.9", "typenum", ] @@ -4089,9 +4180,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.2" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "602113192b08db8f38796c4e85c39e960c145965140e918018bcde1952429655" +checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" dependencies = [ "cc", "libc", @@ -4101,9 +4192,9 @@ dependencies = [ [[package]] name = "linked-hash-map" -version = "0.5.4" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linked_hash_set" @@ -4156,10 +4247,11 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" dependencies = [ + "autocfg", "scopeguard", ] @@ -4196,11 +4288,11 @@ dependencies = [ [[package]] name = "lru" -version = "0.7.5" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32613e41de4c47ab04970c348ca7ae7382cf116625755af070b008a15516a889" +checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a" dependencies = [ - "hashbrown 0.11.2", + "hashbrown 0.12.3", ] [[package]] @@ -4214,9 +4306,9 @@ dependencies = [ [[package]] name = "lz4" -version = "1.23.2" +version = "1.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac20ed6991e01bf6a2e68cc73df2b389707403662a8ba89f68511fb340f724c" +checksum = "4edcb94251b1c375c459e5abe9fb0168c1c826c3370172684844f8f3f8d1a885" dependencies = [ "libc", "lz4-sys", @@ -4224,9 +4316,9 @@ dependencies = [ [[package]] name = "lz4-sys" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca79aa95d8b3226213ad454d328369853be3a1382d89532a854f4d69640acae" +checksum = "d7be8908e2ed6f31c02db8a9fa962f03e36c53fbfde437363eae3306b85d7e17" dependencies = [ "cc", "libc", @@ -4241,12 +4333,6 @@ dependencies = [ "libc", ] -[[package]] -name = "maplit" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" - [[package]] name = "match_cfg" version = "0.1.0" @@ -4264,24 +4350,24 @@ dependencies = [ [[package]] name = "matches" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "matrixmultiply" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a8a15b776d9dfaecd44b03c5828c2199cddff5247215858aac14624f8d6b741" +checksum = "add85d4dd35074e6fedc608f8c8f513a3548619a9024b751949ef0e8e45a4d84" dependencies = [ "rawpointer", ] [[package]] name = "memchr" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memfd" @@ -4304,27 +4390,27 @@ dependencies = [ [[package]] name = "memmap2" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04e3e85b970d650e2ae6d70592474087051c11c54da7f7b4949725c5735fbcc6" +checksum = "723e3ebdcdc5c023db1df315364573789f8857c11b631a2fdfad7c00f5c046b4" dependencies = [ "libc", ] [[package]] name = "memmap2" -version = "0.5.0" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4647a11b578fead29cdbb34d4adef8dd3dc35b876c9c6d5240d83f205abfe96e" +checksum = "3a79b39c93a7a5a27eeaf9a23b5ff43f1b9e0ad6b1cdd441140ae53c35613fc7" dependencies = [ "libc", ] [[package]] name = "memoffset" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" dependencies = [ "autocfg", ] @@ -4336,7 +4422,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6566c70c1016f525ced45d7b7f97730a2bafb037c788211d0c186ef5b2189f0a" dependencies = [ "hash-db", - "hashbrown 0.12.0", + "hashbrown 0.12.3", "parity-util-mem", ] @@ -4366,12 +4452,11 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.4.4" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" dependencies = [ "adler", - "autocfg", ] [[package]] @@ -4388,9 +4473,9 @@ dependencies = [ [[package]] name = "more-asserts" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0debeb9fcf88823ea64d64e4a815ab1643f33127d995978e099942ce38f25238" +checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" [[package]] name = "multiaddr" @@ -4434,7 +4519,7 @@ dependencies = [ "digest 0.10.3", "multihash-derive", "sha2 0.10.2", - "sha3 0.10.0", + "sha3 0.10.2", "unsigned-varint", ] @@ -4454,9 +4539,9 @@ dependencies = [ [[package]] name = "multimap" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1255076139a83bb467426e7f8d0134968a8118844faa755985e077cf31850333" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "multistream-select" @@ -4482,9 +4567,9 @@ dependencies = [ "matrixmultiply", "nalgebra-macros", "num-complex", - "num-rational 0.4.0", + "num-rational 0.4.1", "num-traits", - "rand 0.8.4", + "rand 0.8.5", "rand_distr", "simba", "typenum", @@ -4507,7 +4592,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7d66043b25d4a6cccb23619d10c19c25304b355a7dccd4a8e11423dd2382146" dependencies = [ - "rand 0.8.4", + "rand 0.8.5", ] [[package]] @@ -4524,9 +4609,9 @@ dependencies = [ [[package]] name = "netlink-packet-route" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "733ea73609acfd7fa7ddadfb7bf709b0471668c456ad9513685af543a06342b2" +checksum = "d9ea4302b9759a7a88242299225ea3688e63c85ea136371bb6cf94fd674efaab" dependencies = [ "anyhow", "bitflags", @@ -4544,29 +4629,30 @@ checksum = "25af9cf0dc55498b7bd94a1508af7a78706aa0ab715a73c5169273e03c84845e" dependencies = [ "anyhow", "byteorder", - "paste 1.0.6", + "paste 1.0.8", "thiserror", ] [[package]] name = "netlink-proto" -version = "0.9.2" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef8785b8141e8432aa45fceb922a7e876d7da3fad37fa7e7ec702ace3aa0826b" +checksum = "65b4b14489ab424703c092062176d52ba55485a89c076b4f9db05092b7223aa6" dependencies = [ "bytes", "futures", "log", "netlink-packet-core", "netlink-sys", + "thiserror", "tokio", ] [[package]] name = "netlink-sys" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c9f9547a08241bee7b6558b9b98e1f290d187de8b7cfca2bbb4937bcaa8f8" +checksum = "92b654097027250401127914afb37cb1f311df6610a9891ff07a757e94199027" dependencies = [ "async-io", "bytes", @@ -4577,9 +4663,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.22.3" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf" +checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" dependencies = [ "bitflags", "cc", @@ -4590,22 +4676,20 @@ dependencies = [ [[package]] name = "nix" -version = "0.23.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" +checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc" dependencies = [ "bitflags", - "cc", "cfg-if 1.0.0", "libc", - "memoffset", ] [[package]] name = "node-bench" version = "0.9.0-dev" dependencies = [ - "clap 3.1.18", + "clap 3.2.16", "derive_more", "fs_extra", "futures", @@ -4644,7 +4728,7 @@ version = "3.0.0-dev" dependencies = [ "assert_cmd", "async-std", - "clap 3.1.18", + "clap 3.2.16", "clap_complete", "criterion", "frame-benchmarking-cli", @@ -4667,7 +4751,7 @@ dependencies = [ "pallet-transaction-payment", "parity-scale-codec", "platforms", - "rand 0.8.4", + "rand 0.8.5", "regex", "remote-externalities", "sc-authority-discovery", @@ -4762,7 +4846,7 @@ dependencies = [ name = "node-inspect" version = "0.9.0-dev" dependencies = [ - "clap 3.1.18", + "clap 3.2.16", "parity-scale-codec", "sc-cli", "sc-client-api", @@ -4821,7 +4905,7 @@ dependencies = [ name = "node-runtime-generate-bags" version = "3.0.0" dependencies = [ - "clap 3.1.18", + "clap 3.2.16", "generate-bags", "kitchensink-runtime", ] @@ -4830,7 +4914,7 @@ dependencies = [ name = "node-template" version = "4.0.0-dev" dependencies = [ - "clap 3.1.18", + "clap 3.2.16", "frame-benchmarking", "frame-benchmarking-cli", "frame-system", @@ -4953,13 +5037,12 @@ checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" [[package]] name = "nom" -version = "7.1.0" +version = "7.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" dependencies = [ "memchr", "minimal-lexical", - "version_check", ] [[package]] @@ -4975,13 +5058,24 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085" +checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" dependencies = [ "num-traits", ] +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "num-format" version = "0.4.0" @@ -4994,9 +5088,9 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", "num-traits", @@ -5016,9 +5110,9 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" dependencies = [ "autocfg", "num-integer", @@ -5046,19 +5140,19 @@ dependencies = [ ] [[package]] -name = "object" -version = "0.27.1" +name = "num_threads" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" dependencies = [ - "memchr", + "libc", ] [[package]] name = "object" -version = "0.28.3" +version = "0.28.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40bec70ba014595f99f7aa110b84331ffe1ee9aece7fe6f387cc7e3ecda4d456" +checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424" dependencies = [ "crc32fast", "hashbrown 0.11.2", @@ -5066,11 +5160,20 @@ dependencies = [ "memchr", ] +[[package]] +name = "object" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" +checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" [[package]] name = "oorandom" @@ -5092,21 +5195,21 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl-probe" -version = "0.1.2" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "os_str_bytes" -version = "6.0.0" +version = "6.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" +checksum = "648001efe5d5c0102d8cea768e348da85d90af8ba91f0bea908f157951493cd4" [[package]] name = "output_vt100" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" +checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" dependencies = [ "winapi", ] @@ -5299,7 +5402,7 @@ dependencies = [ "frame-election-provider-support", "honggfuzz", "pallet-bags-list", - "rand 0.8.4", + "rand 0.8.5", ] [[package]] @@ -5453,7 +5556,7 @@ dependencies = [ "pallet-utility", "parity-scale-codec", "pretty_assertions", - "rand 0.8.4", + "rand 0.8.5", "rand_pcg 0.3.1", "scale-info", "serde", @@ -5570,7 +5673,7 @@ dependencies = [ "pallet-balances", "pallet-election-provider-support-benchmarking", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "rand 0.7.3", "scale-info", "sp-arithmetic", @@ -5712,6 +5815,36 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-ibc" +version = "3.0.0-pre.0" +dependencies = [ + "chrono", + "flex-error", + "frame-benchmarking", + "frame-support", + "frame-system", + "hex", + "ibc", + "ibc-proto", + "log", + "parity-scale-codec", + "prost 0.11.0", + "prost-types 0.11.1", + "scale-info", + "serde", + "serde_json", + "sha2 0.10.2", + "sp-core", + "sp-io", + "sp-keyring", + "sp-runtime", + "sp-std", + "sp-tracing", + "tendermint-proto", + "time 0.3.11", +] + [[package]] name = "pallet-identity" version = "4.0.0-dev" @@ -6268,7 +6401,7 @@ dependencies = [ "log", "pallet-balances", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "remote-externalities", "scale-info", "serde", @@ -6499,9 +6632,9 @@ dependencies = [ [[package]] name = "parity-db" -version = "0.3.13" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55a7901b85874402471e131de3332dde0e51f38432c69a3853627c8e25433048" +checksum = "2bb474d0ed0836e185cb998a6b140ed1073d1fbf27d690ecf9ede8030289382c" dependencies = [ "blake2-rfc", "crc32fast", @@ -6510,17 +6643,17 @@ dependencies = [ "libc", "log", "lz4", - "memmap2 0.2.1", + "memmap2 0.2.3", "parking_lot 0.11.2", - "rand 0.8.4", + "rand 0.8.5", "snap", ] [[package]] name = "parity-scale-codec" -version = "3.1.3" +version = "3.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04bc9583b5e01cc8c70d89acc9af14ef9b1c29ee3a0074b2a9eea8c0fa396690" +checksum = "9182e4a71cae089267ab03e67c99368db7cd877baf50f931e5d6d4b71e195ac0" dependencies = [ "arrayvec 0.7.2", "bitvec", @@ -6556,10 +6689,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c32561d248d352148124f036cac253a644685a21dc9fea383eb4907d7bd35a8f" dependencies = [ "cfg-if 1.0.0", - "hashbrown 0.12.0", + "hashbrown 0.12.3", "impl-trait-for-tuples", "parity-util-mem-derive", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "primitive-types", "smallvec", "winapi", @@ -6610,12 +6743,12 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.1", + "parking_lot_core 0.9.3", ] [[package]] @@ -6634,15 +6767,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.1" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" dependencies = [ "cfg-if 1.0.0", "libc", "redox_syscall", "smallvec", - "windows-sys 0.32.0", + "windows-sys 0.36.1", ] [[package]] @@ -6657,9 +6790,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" +checksum = "9423e2b32f7a043629287a536f21951e8c6a82482d0acb1eeebfc90bc2225b22" [[package]] name = "paste-impl" @@ -6702,18 +6835,19 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pest" -version = "2.1.3" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +checksum = "69486e2b8c2d2aeb9762db7b4e00b0331156393555cff467f4163ff06821eef8" dependencies = [ + "thiserror", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.1.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" +checksum = "b13570633aff33c6d22ce47dd566b10a3b9122c2fe9d8e7501895905be532b91" dependencies = [ "pest", "pest_generator", @@ -6721,9 +6855,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.1.3" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" +checksum = "b3c567e5702efdc79fb18859ea74c3eb36e14c43da7b8c1f098a4ed6514ec7a0" dependencies = [ "pest", "pest_meta", @@ -6734,20 +6868,20 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.1.3" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" +checksum = "5eb32be5ee3bbdafa8c7a18b0a8a8d962b66cfa2ceee4037f49267a50ee821fe" dependencies = [ - "maplit", + "once_cell", "pest", - "sha-1 0.8.2", + "sha-1 0.10.0", ] [[package]] name = "petgraph" -version = "0.6.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a13a2fa9d0b63e5f22328828741e523766fff0ee9e779316902290dff3f824f" +checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" dependencies = [ "fixedbitset", "indexmap", @@ -6755,18 +6889,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" +checksum = "78203e83c48cffbe01e4a2d35d566ca4de445d79a85372fc64e378bfc812a260" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" +checksum = "710faf75e1b33345361201d36d04e98ac1ed8909151a017ed384700836104c74" dependencies = [ "proc-macro2", "quote", @@ -6781,9 +6915,9 @@ checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" [[package]] name = "pin-project-lite" -version = "0.2.6" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pin-utils" @@ -6793,9 +6927,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.19" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" [[package]] name = "platforms" @@ -6805,9 +6939,9 @@ checksum = "e8d0eef3571242013a0d5dc84861c3ae4a652e56e12adf8bdc26ff5f8cb34c94" [[package]] name = "plotters" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a3fd9ec30b9749ce28cd91f255d569591cdf937fe280c312143e3c4bad6f2a" +checksum = "9428003b84df1496fb9d6eeee9c5f8145cb41ca375eb0dad204328888832811f" dependencies = [ "num-traits", "plotters-backend", @@ -6818,66 +6952,66 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.0" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b07fffcddc1cb3a1de753caa4e4df03b79922ba43cf882acc1bdd7e8df9f4590" +checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" [[package]] name = "plotters-svg" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b38a02e23bd9604b842a812063aec4ef702b57989c37b655254bb61c471ad211" +checksum = "e0918736323d1baff32ee0eade54984f6f201ad7e97d5cfb5d6ab4a358529615" dependencies = [ "plotters-backend", ] [[package]] name = "polling" -version = "2.0.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2a7bc6b2a29e632e45451c941832803a18cce6781db04de8a04696cdca8bde4" +checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", "libc", "log", - "wepoll-sys", + "wepoll-ffi", "winapi", ] [[package]] name = "poly1305" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fcffab1f78ebbdf4b93b68c1ffebc24037eedf271edaca795732b24e5e4e349" +checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" dependencies = [ - "cpufeatures 0.1.5", + "cpufeatures", "opaque-debug 0.3.0", "universal-hash", ] [[package]] name = "polyval" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6ba6a405ef63530d6cb12802014b22f9c5751bd17cdcddbe9e46d5c8ae83287" +checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" dependencies = [ "cfg-if 1.0.0", - "cpufeatures 0.1.5", + "cpufeatures", "opaque-debug 0.3.0", "universal-hash", ] [[package]] name = "ppv-lite86" -version = "0.2.10" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "predicates" -version = "2.0.2" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c143348f141cc87aab5b950021bac6145d0e5ae754b0591de23244cee42c9308" +checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c" dependencies = [ "difflib", "itertools", @@ -6886,18 +7020,18 @@ dependencies = [ [[package]] name = "predicates-core" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57e35a3326b75e49aa85f5dc6ec15b41108cf5aee58eabb1f274dd18b73c2451" +checksum = "da1c2388b1513e1b605fcec39a95e0a9e8ef088f71443ef37099fa9ae6673fcb" [[package]] name = "predicates-tree" -version = "1.0.2" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15f553275e5721409451eb85e15fd9a860a6e5ab4496eb215987502b5f5391f2" +checksum = "4d86de6de25020a36c6d3643a86d9a6a9f552107c0559c60ea03551b5e16c032" dependencies = [ "predicates-core", - "treeline", + "termtree", ] [[package]] @@ -6927,10 +7061,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" dependencies = [ + "once_cell", "thiserror", "toml", ] @@ -6967,24 +7102,24 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro2" -version = "1.0.39" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" dependencies = [ "unicode-ident", ] [[package]] name = "prometheus" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f64969ffd5dd8f39bd57a68ac53c163a095ed9d0fb707146da1b27025a3504" +checksum = "cface98dfa6d645ea4c789839f176e4b072265d085bfcc48eaa8d137f58d3c39" dependencies = [ "cfg-if 1.0.0", "fnv", "lazy_static", "memchr", - "parking_lot 0.11.2", + "parking_lot 0.12.1", "thiserror", ] @@ -6995,7 +7130,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac1abe0255c04d15f571427a2d1e00099016506cf3297b53853acd2b7eb87825" dependencies = [ "dtoa", - "itoa 1.0.1", + "itoa 1.0.3", "owning_ref", "prometheus-client-derive-text-encode", ] @@ -7013,12 +7148,22 @@ dependencies = [ [[package]] name = "prost" -version = "0.10.3" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71adf41db68aa0daaefc69bb30bcd68ded9b9abaad5d1fbb6304c4fb390e083e" +dependencies = [ + "bytes", + "prost-derive 0.10.1", +] + +[[package]] +name = "prost" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc03e116981ff7d8da8e5c220e374587b98d294af7ba7dd7fda761158f00086f" +checksum = "399c3c31cdec40583bb68f0b18403400d01ec4289c383aa047560439952c4dd7" dependencies = [ "bytes", - "prost-derive", + "prost-derive 0.11.0", ] [[package]] @@ -7036,8 +7181,8 @@ dependencies = [ "log", "multimap", "petgraph", - "prost", - "prost-types", + "prost 0.10.4", + "prost-types 0.10.1", "regex", "tempfile", "which", @@ -7051,7 +7196,7 @@ checksum = "00af1e92c33b4813cc79fda3f2dbf56af5169709be0202df730e9ebc3e4cd007" dependencies = [ "asynchronous-codec", "bytes", - "prost", + "prost 0.10.4", "thiserror", "unsigned-varint", ] @@ -7069,6 +7214,19 @@ dependencies = [ "syn", ] +[[package]] +name = "prost-derive" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7345d5f0e08c0536d7ac7229952590239e77abf0a0100a1b1d890add6ea96364" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "prost-types" version = "0.10.1" @@ -7076,14 +7234,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d0a014229361011dc8e69c8a1ec6c2e8d0f2af7c91e3ea3f5b2170298461e68" dependencies = [ "bytes", - "prost", + "prost 0.10.4", +] + +[[package]] +name = "prost-types" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dfaa718ad76a44b3415e6c4d53b17c8f99160dcb3a99b10470fce8ad43f6e3e" +dependencies = [ + "bytes", + "prost 0.11.0", ] [[package]] name = "psm" -version = "0.1.12" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3abf49e5417290756acfd26501536358560c4a5cc4a0934d390939acb3e7083a" +checksum = "f446d0a6efba22928558c4fb4ce0b3fd6c89b0061343e390bf01a703742b8125" dependencies = [ "cc", ] @@ -7114,19 +7282,13 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" -[[package]] -name = "quick-error" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ac73b1112776fc109b2e61909bc46c7e1bf0d7f690ffb1676553acce16d5cda" - [[package]] name = "quickcheck" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" dependencies = [ - "rand 0.8.4", + "rand 0.8.5", ] [[package]] @@ -7142,9 +7304,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.18" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ "proc-macro2", ] @@ -7165,20 +7327,19 @@ dependencies = [ "libc", "rand_chacha 0.2.2", "rand_core 0.5.1", - "rand_hc 0.2.0", + "rand_hc", "rand_pcg 0.2.1", ] [[package]] name = "rand" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha 0.3.0", - "rand_core 0.6.2", - "rand_hc 0.3.0", + "rand_chacha 0.3.1", + "rand_core 0.6.3", ] [[package]] @@ -7193,12 +7354,12 @@ dependencies = [ [[package]] name = "rand_chacha" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.2", + "rand_core 0.6.3", ] [[package]] @@ -7212,11 +7373,11 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" dependencies = [ - "getrandom 0.2.3", + "getrandom 0.2.7", ] [[package]] @@ -7226,7 +7387,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" dependencies = [ "num-traits", - "rand 0.8.4", + "rand 0.8.5", ] [[package]] @@ -7238,15 +7399,6 @@ dependencies = [ "rand_core 0.5.1", ] -[[package]] -name = "rand_hc" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" -dependencies = [ - "rand_core 0.6.2", -] - [[package]] name = "rand_pcg" version = "0.2.1" @@ -7262,7 +7414,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" dependencies = [ - "rand_core 0.6.2", + "rand_core 0.6.3", ] [[package]] @@ -7273,9 +7425,9 @@ checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" [[package]] name = "rayon" -version = "1.5.0" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674" +checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" dependencies = [ "autocfg", "crossbeam-deque", @@ -7285,50 +7437,50 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.9.0" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" +checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" dependencies = [ "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "lazy_static", "num_cpus", ] [[package]] name = "redox_syscall" -version = "0.2.10" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] [[package]] name = "redox_users" -version = "0.4.0" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.3", + "getrandom 0.2.7", "redox_syscall", + "thiserror", ] [[package]] name = "ref-cast" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300f2a835d808734ee295d45007adacb9ebb29dd3ae2424acfa17930cae541da" +checksum = "ed13bcd201494ab44900a96490291651d200730904221832b9547d24a87d332b" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c38e3aecd2b21cb3959637b883bb3714bc7e43f0268b9a29d3743ee3e55cdd2" +checksum = "5234cd6063258a5e32903b53b1b6ac043a0541c8adc1f610f67b0326c7a578fa" dependencies = [ "proc-macro2", "quote", @@ -7337,9 +7489,9 @@ dependencies = [ [[package]] name = "regalloc" -version = "0.0.31" +version = "0.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "571f7f397d61c4755285cd37853fe8e03271c243424a907415909379659381c5" +checksum = "62446b1d3ebf980bdc68837700af1d77b37bc430e524bf95319c6eada2a4cc02" dependencies = [ "log", "rustc-hash", @@ -7348,9 +7500,9 @@ dependencies = [ [[package]] name = "regalloc2" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d37148700dbb38f994cd99a1431613057f37ed934d7e4d799b7ab758c482461" +checksum = "4a8d23b35d7177df3b9d31ed8a9ab4bf625c668be77a319d4f5efd4a5257701c" dependencies = [ "fxhash", "log", @@ -7360,9 +7512,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.5.5" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" dependencies = [ "aho-corasick", "memchr", @@ -7371,19 +7523,18 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ - "byteorder", "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.6.25" +version = "0.6.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" [[package]] name = "region" @@ -7453,7 +7604,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" dependencies = [ "hostname", - "quick-error 1.2.3", + "quick-error", ] [[package]] @@ -7482,14 +7633,23 @@ dependencies = [ "winapi", ] +[[package]] +name = "ripemd" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1facec54cb5e0dc08553501fa740091086d0259ad0067e0d4103448e4cb22ed3" +dependencies = [ + "digest 0.10.3", +] + [[package]] name = "rkyv" -version = "0.7.37" +version = "0.7.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f08c8062c1fe1253064043b8fc07bfea1b9702b71b4a86c11ea3588183b12e1" +checksum = "cec2b3485b07d96ddfd3134767b8a447b45ea4eb91448d0a35180ec0ffd5ed15" dependencies = [ "bytecheck", - "hashbrown 0.12.0", + "hashbrown 0.12.3", "ptr_meta", "rend", "rkyv_derive", @@ -7498,9 +7658,9 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.7.37" +version = "0.7.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e289706df51226e84814bf6ba1a9e1013112ae29bc7a9878f73fce360520c403" +checksum = "6eaedadc88b53e36dd32d940ed21ae4d850d5916f2581526921f553a72ac34c4" dependencies = [ "proc-macro2", "quote", @@ -7529,24 +7689,24 @@ dependencies = [ [[package]] name = "rtnetlink" -version = "0.9.1" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f54290e54521dac3de4149d83ddf9f62a359b3cc93bcb494a794a41e6f4744b" +checksum = "322c53fd76a18698f1c27381d58091de3a043d356aa5bd0d510608b565f469a0" dependencies = [ "async-global-executor", "futures", "log", "netlink-packet-route", "netlink-proto", - "nix 0.22.3", + "nix 0.24.2", "thiserror", ] [[package]] name = "rustc-demangle" -version = "0.1.18" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" [[package]] name = "rustc-hash" @@ -7575,7 +7735,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.4", + "semver 1.0.13", ] [[package]] @@ -7594,9 +7754,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.35.6" +version = "0.35.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef258c11e17f5c01979a10543a30a4e12faef6aab217a74266e747eefa3aed88" +checksum = "d51cc38aa10f6bbb377ed28197aa052aa4e2b762c22be9d3153d01822587e787" dependencies = [ "bitflags", "errno", @@ -7608,9 +7768,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.20.2" +version = "0.20.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d37e5e2290f3e040b594b1a9e04377c2c671f1a1cfd9bfdef82106ac1c113f84" +checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" dependencies = [ "log", "ring", @@ -7620,9 +7780,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca9ebdfa27d3fc180e42879037b5338ab1c040c06affd00d8338598e7800943" +checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" dependencies = [ "openssl-probe", "rustls-pemfile", @@ -7632,18 +7792,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "0.2.1" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" +checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" dependencies = [ "base64", ] [[package]] name = "rustversion" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" +checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" [[package]] name = "rw-stream-sink" @@ -7658,9 +7818,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.5" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" [[package]] name = "safe-mix" @@ -7671,6 +7831,53 @@ dependencies = [ "rustc_version 0.2.3", ] +[[package]] +name = "safe-proc-macro2" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "814c536dcd27acf03296c618dab7ad62d28e70abd7ba41d3f34a2ce707a2c666" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "safe-quote" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77e530f7831f3feafcd5f1aae406ac205dd998436b4007c8e80f03eca78a88f7" +dependencies = [ + "safe-proc-macro2", +] + +[[package]] +name = "safe-regex" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15289bf322e0673d52756a18194167f2378ec1a15fe884af6e2d2cb934822b0" +dependencies = [ + "safe-regex-macro", +] + +[[package]] +name = "safe-regex-compiler" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba76fae590a2aa665279deb1f57b5098cbace01a0c5e60e262fcf55f7c51542" +dependencies = [ + "safe-proc-macro2", + "safe-quote", +] + +[[package]] +name = "safe-regex-macro" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c2e96b5c03f158d1b16ba79af515137795f4ad4e8de3f790518aae91f1d127" +dependencies = [ + "safe-proc-macro2", + "safe-regex-compiler", +] + [[package]] name = "salsa20" version = "0.9.0" @@ -7709,7 +7916,7 @@ dependencies = [ "libp2p", "log", "parity-scale-codec", - "prost", + "prost 0.10.4", "prost-build", "quickcheck", "rand 0.7.3", @@ -7736,7 +7943,7 @@ dependencies = [ "futures-timer", "log", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "sc-block-builder", "sc-client-api", "sc-proposer-metrics", @@ -7774,7 +7981,7 @@ name = "sc-chain-spec" version = "4.0.0-dev" dependencies = [ "impl-trait-for-tuples", - "memmap2 0.5.0", + "memmap2 0.5.5", "parity-scale-codec", "sc-chain-spec-derive", "sc-network", @@ -7800,7 +8007,7 @@ name = "sc-cli" version = "0.10.0-dev" dependencies = [ "chrono", - "clap 3.1.18", + "clap 3.2.16", "fdlimit", "futures", "hex", @@ -7843,7 +8050,7 @@ dependencies = [ "hash-db", "log", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "sc-executor", "sc-transaction-pool-api", "sc-utils", @@ -7876,7 +8083,7 @@ dependencies = [ "log", "parity-db", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "quickcheck", "sc-client-api", "sc-state-db", @@ -7901,7 +8108,7 @@ dependencies = [ "futures-timer", "libp2p", "log", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "sc-client-api", "sc-utils", "serde", @@ -7924,7 +8131,7 @@ dependencies = [ "futures", "log", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "sc-block-builder", "sc-client-api", "sc-consensus", @@ -7966,7 +8173,7 @@ dependencies = [ "num-rational 0.2.4", "num-traits", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "rand 0.7.3", "rand_chacha 0.2.2", "sc-block-builder", @@ -8088,7 +8295,7 @@ dependencies = [ "futures-timer", "log", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "sc-client-api", "sc-consensus", "sp-api", @@ -8149,8 +8356,8 @@ dependencies = [ "lru", "num_cpus", "parity-scale-codec", - "parking_lot 0.12.0", - "paste 1.0.6", + "parking_lot 0.12.1", + "paste 1.0.8", "regex", "sc-executor-common", "sc-executor-wasmi", @@ -8220,8 +8427,8 @@ dependencies = [ "once_cell", "parity-scale-codec", "parity-wasm 0.42.2", - "paste 1.0.6", - "rustix 0.35.6", + "paste 1.0.8", + "rustix 0.35.7", "sc-allocator", "sc-executor-common", "sc-runtime-test", @@ -8249,8 +8456,8 @@ dependencies = [ "hex", "log", "parity-scale-codec", - "parking_lot 0.12.0", - "rand 0.8.4", + "parking_lot 0.12.1", + "rand 0.8.5", "sc-block-builder", "sc-chain-spec", "sc-client-api", @@ -8329,7 +8536,7 @@ version = "4.0.0-dev" dependencies = [ "async-trait", "hex", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "serde_json", "sp-application-crypto", "sp-core", @@ -8362,9 +8569,9 @@ dependencies = [ "log", "lru", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "pin-project", - "prost", + "prost 0.10.4", "prost-build", "rand 0.7.3", "sc-block-builder", @@ -8444,7 +8651,7 @@ dependencies = [ "libp2p", "log", "parity-scale-codec", - "prost", + "prost 0.10.4", "prost-build", "sc-client-api", "sc-network-common", @@ -8466,7 +8673,7 @@ dependencies = [ "log", "lru", "parity-scale-codec", - "prost", + "prost 0.10.4", "prost-build", "quickcheck", "sc-block-builder", @@ -8497,7 +8704,7 @@ dependencies = [ "futures-timer", "libp2p", "log", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "rand 0.7.3", "sc-block-builder", "sc-client-api", @@ -8532,7 +8739,7 @@ dependencies = [ "num_cpus", "once_cell", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "rand 0.7.3", "sc-block-builder", "sc-client-api", @@ -8587,7 +8794,7 @@ dependencies = [ "lazy_static", "log", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "sc-block-builder", "sc-chain-spec", "sc-client-api", @@ -8621,7 +8828,7 @@ dependencies = [ "jsonrpsee", "log", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "sc-chain-spec", "sc-transaction-pool-api", "scale-info", @@ -8651,7 +8858,7 @@ dependencies = [ name = "sc-runtime-test" version = "2.0.0" dependencies = [ - "paste 1.0.6", + "paste 1.0.8", "sp-core", "sp-io", "sp-runtime", @@ -8676,7 +8883,7 @@ dependencies = [ "log", "parity-scale-codec", "parity-util-mem", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "pin-project", "rand 0.7.3", "sc-block-builder", @@ -8740,7 +8947,7 @@ dependencies = [ "hex-literal", "log", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "sc-block-builder", "sc-client-api", "sc-client-db", @@ -8775,7 +8982,7 @@ dependencies = [ "parity-scale-codec", "parity-util-mem", "parity-util-mem-derive", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "sc-client-api", "sp-core", ] @@ -8824,7 +9031,7 @@ dependencies = [ "futures", "libp2p", "log", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "pin-project", "rand 0.7.3", "serde", @@ -8845,7 +9052,7 @@ dependencies = [ "libc", "log", "once_cell", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "regex", "rustc-hash", "sc-client-api", @@ -8887,7 +9094,7 @@ dependencies = [ "log", "parity-scale-codec", "parity-util-mem", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "sc-block-builder", "sc-client-api", "sc-transaction-pool-api", @@ -8927,16 +9134,16 @@ dependencies = [ "futures-timer", "lazy_static", "log", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "prometheus", "tokio-test", ] [[package]] name = "scale-info" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8980cafbe98a7ee7a9cc16b32ebce542c77883f512d83fbf2ddc8f6a85ea74c9" +checksum = "c46be926081c9f4dd5dd9b6f1d3e3229f2360bc6502dd8836f84a93b7c75e99a" dependencies = [ "bitvec", "cfg-if 1.0.0", @@ -8948,9 +9155,9 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4260c630e8a8a33429d1688eff2f163f24c65a4e1b1578ef6b565061336e4b6f" +checksum = "50e334bb10a245e28e5fd755cabcafd96cfcd167c99ae63a46924ca8d8703a3c" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -8960,12 +9167,12 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" dependencies = [ "lazy_static", - "winapi", + "windows-sys 0.36.1", ] [[package]] @@ -8976,7 +9183,7 @@ checksum = "021b403afe70d81eea68f6ea12f6b3c9588e5d536a94c3bf80f15e7faa267862" dependencies = [ "arrayref", "arrayvec 0.5.2", - "curve25519-dalek 2.1.2", + "curve25519-dalek 2.1.3", "getrandom 0.1.16", "merlin", "rand 0.7.3", @@ -9015,7 +9222,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" dependencies = [ "der", - "generic-array 0.14.4", + "generic-array 0.14.6", "subtle", "zeroize", ] @@ -9049,9 +9256,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.3.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b239a3d5db51252f6f48f42172c65317f37202f4a21021bf5f9d40a408f4592c" +checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" dependencies = [ "bitflags", "core-foundation", @@ -9062,9 +9269,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.3.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e4effb91b4b8b6fb7732e670b6cee160278ff8e6bf485c7805d9e319d76e284" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" dependencies = [ "core-foundation-sys", "libc", @@ -9090,9 +9297,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.4" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012" +checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711" dependencies = [ "serde", ] @@ -9105,27 +9312,27 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.136" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553" dependencies = [ "serde_derive", ] [[package]] name = "serde_bytes" -version = "0.11.5" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9" +checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b" dependencies = [ "serde", ] [[package]] name = "serde_cbor" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e18acfa2f90e8b735b2836ab8d538de304cbb6729a7360729ea5a895d15a622" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" dependencies = [ "half", "serde", @@ -9133,9 +9340,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.136" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391" dependencies = [ "proc-macro2", "quote", @@ -9144,11 +9351,11 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.79" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" +checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7" dependencies = [ - "itoa 1.0.1", + "itoa 1.0.3", "ryu", "serde", ] @@ -9163,30 +9370,40 @@ dependencies = [ ] [[package]] -name = "sha-1" -version = "0.8.2" +name = "serde_repr" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" dependencies = [ - "block-buffer 0.7.3", - "digest 0.8.1", - "fake-simd", - "opaque-debug 0.2.3", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "sha-1" -version = "0.9.4" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfebf75d25bd900fd1e7d11501efab59bc846dbc76196839663e6637bba9f25f" +checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" dependencies = [ "block-buffer 0.9.0", "cfg-if 1.0.0", - "cpuid-bool", + "cpufeatures", "digest 0.9.0", "opaque-debug 0.3.0", ] +[[package]] +name = "sha-1" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.10.3", +] + [[package]] name = "sha2" version = "0.8.2" @@ -9201,13 +9418,13 @@ dependencies = [ [[package]] name = "sha2" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", "cfg-if 1.0.0", - "cpufeatures 0.2.1", + "cpufeatures", "digest 0.9.0", "opaque-debug 0.3.0", ] @@ -9219,7 +9436,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" dependencies = [ "cfg-if 1.0.0", - "cpufeatures 0.2.1", + "cpufeatures", "digest 0.10.3", ] @@ -9237,9 +9454,9 @@ dependencies = [ [[package]] name = "sha3" -version = "0.10.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f935e31cf406e8c0e96c2815a5516181b7004ae8c5f296293221e9b1e356bd" +checksum = "0a31480366ec990f395a61b7c08122d99bd40544fdb5abcfc1b06bb29994312c" dependencies = [ "digest 0.10.3", "keccak", @@ -9247,24 +9464,24 @@ dependencies = [ [[package]] name = "sharded-slab" -version = "0.1.1" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79c719719ee05df97490f80a45acfc99e5a30ce98a1e4fb67aee422745ae14e3" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" dependencies = [ "lazy_static", ] [[package]] name = "shlex" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a568c8f2cd051a4d283bd6eb0343ac214c1b0f1ac19f93e1175b2dee38c73d" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" [[package]] name = "signal-hook" -version = "0.3.8" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef33d6d0cd06e0840fba9985aab098c147e67e05cee14d412d3345ed14ff30ac" +checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" dependencies = [ "libc", "signal-hook-registry", @@ -9272,9 +9489,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" dependencies = [ "libc", ] @@ -9286,7 +9503,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" dependencies = [ "digest 0.9.0", - "rand_core 0.6.2", + "rand_core 0.6.3", ] [[package]] @@ -9298,14 +9515,23 @@ dependencies = [ "approx", "num-complex", "num-traits", - "paste 1.0.6", + "paste 1.0.8", ] +[[package]] +name = "simple-error" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc47a29ce97772ca5c927f75bac34866b16d64e07f330c3248e2d7226623901b" + [[package]] name = "slab" -version = "0.4.2" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] [[package]] name = "slice-group-by" @@ -9315,9 +9541,9 @@ checksum = "03b634d87b960ab1a38c4fe143b508576f075e7c978bfad18217645ebfdfa2ec" [[package]] name = "smallvec" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" [[package]] name = "snap" @@ -9335,7 +9561,7 @@ dependencies = [ "blake2", "chacha20poly1305", "curve25519-dalek 4.0.0-pre.1", - "rand_core 0.6.2", + "rand_core 0.6.3", "ring", "rustc_version 0.4.0", "sha2 0.10.2", @@ -9364,8 +9590,8 @@ dependencies = [ "futures", "httparse", "log", - "rand 0.8.4", - "sha-1 0.9.4", + "rand 0.8.5", + "sha-1 0.9.8", ] [[package]] @@ -9511,7 +9737,7 @@ dependencies = [ "log", "lru", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "sp-api", "sp-consensus", "sp-database", @@ -9638,7 +9864,7 @@ dependencies = [ "num-traits", "parity-scale-codec", "parity-util-mem", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "primitive-types", "rand 0.7.3", "regex", @@ -9672,7 +9898,7 @@ dependencies = [ "byteorder", "digest 0.10.3", "sha2 0.10.2", - "sha3 0.10.0", + "sha3 0.10.2", "sp-std", "twox-hash", ] @@ -9692,7 +9918,7 @@ name = "sp-database" version = "4.0.0-dev" dependencies = [ "kvdb", - "parking_lot 0.12.0", + "parking_lot 0.12.1", ] [[package]] @@ -9755,7 +9981,7 @@ dependencies = [ "libsecp256k1", "log", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "secp256k1", "sp-core", "sp-externalities", @@ -9788,7 +10014,7 @@ dependencies = [ "futures", "merlin", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "rand 0.7.3", "rand_chacha 0.2.2", "schnorrkel", @@ -9840,10 +10066,10 @@ dependencies = [ name = "sp-npos-elections-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 3.1.18", + "clap 3.2.16", "honggfuzz", "parity-scale-codec", - "rand 0.8.4", + "rand 0.8.5", "scale-info", "sp-npos-elections", "sp-runtime", @@ -9887,7 +10113,7 @@ dependencies = [ "log", "parity-scale-codec", "parity-util-mem", - "paste 1.0.6", + "paste 1.0.8", "rand 0.7.3", "scale-info", "serde", @@ -10033,7 +10259,7 @@ dependencies = [ "log", "num-traits", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "pretty_assertions", "rand 0.7.3", "smallvec", @@ -10205,9 +10431,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "ss58-registry" -version = "1.18.0" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceb8b72a924ccfe7882d0e26144c114503760a4d1248bb5cd06c8ab2d55404cc" +checksum = "a039906277e0d8db996cd9d1ef19278c10209d994ecfc1025ced16342873a17c" dependencies = [ "Inflector", "num-format", @@ -10240,7 +10466,7 @@ dependencies = [ "lazy_static", "nalgebra", "num-traits", - "rand 0.8.4", + "rand 0.8.5", ] [[package]] @@ -10260,9 +10486,9 @@ dependencies = [ [[package]] name = "strum_macros" -version = "0.24.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4faebde00e8ff94316c01800f9054fd2ba77d30d9e922541913051d1d978918b" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ "heck", "proc-macro2", @@ -10275,7 +10501,7 @@ dependencies = [ name = "subkey" version = "2.0.2" dependencies = [ - "clap 3.1.18", + "clap 3.2.16", "sc-cli", ] @@ -10288,7 +10514,7 @@ dependencies = [ "hmac 0.11.0", "pbkdf2 0.8.0", "schnorrkel", - "sha2 0.9.8", + "sha2 0.9.9", "zeroize", ] @@ -10303,7 +10529,7 @@ dependencies = [ name = "substrate-frame-cli" version = "4.0.0-dev" dependencies = [ - "clap 3.1.18", + "clap 3.2.16", "frame-support", "frame-system", "sc-cli", @@ -10482,7 +10708,7 @@ version = "2.0.0" dependencies = [ "futures", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "sc-transaction-pool", "sc-transaction-pool-api", "sp-blockchain", @@ -10539,15 +10765,24 @@ dependencies = [ [[package]] name = "subtle" -version = "2.4.0" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "subtle-encoding" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2" +checksum = "7dcb1ed7b8330c5eed5441052651dd7a12c75e2ed88f2ec024ae1fa3a5e59945" +dependencies = [ + "zeroize", +] [[package]] name = "syn" -version = "1.0.98" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" dependencies = [ "proc-macro2", "quote", @@ -10556,9 +10791,9 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.12.4" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2", "quote", @@ -10595,9 +10830,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "target-lexicon" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fa7e55043acb85fca6b3c01485a2eeb6b69c5d21002e273c79e465f43b7ac1" +checksum = "c02424087780c9b71cc96799eaeddff35af2bc513278cda5c99fc1f5d026d3c1" [[package]] name = "tempfile" @@ -10613,15 +10848,97 @@ dependencies = [ "winapi", ] +[[package]] +name = "tendermint" +version = "0.23.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "467f82178deeebcd357e1273a0c0b77b9a8a0313ef7c07074baebe99d87851f4" +dependencies = [ + "async-trait", + "bytes", + "ed25519", + "ed25519-dalek", + "flex-error", + "futures", + "num-traits", + "once_cell", + "prost 0.11.0", + "prost-types 0.11.1", + "serde", + "serde_bytes", + "serde_json", + "serde_repr", + "sha2 0.9.9", + "signature", + "subtle", + "subtle-encoding", + "tendermint-proto", + "time 0.3.11", + "zeroize", +] + +[[package]] +name = "tendermint-light-client-verifier" +version = "0.23.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ecb58905bc27f977d28d281594c71a8905da1d8cd88f1db31703f1e9e5ad" +dependencies = [ + "derive_more", + "flex-error", + "serde", + "tendermint", + "time 0.3.11", +] + +[[package]] +name = "tendermint-proto" +version = "0.23.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ce80bf536476db81ecc9ebab834dc329c9c1509a694f211a73858814bfe023" +dependencies = [ + "bytes", + "flex-error", + "num-derive", + "num-traits", + "prost 0.11.0", + "prost-types 0.11.1", + "serde", + "serde_bytes", + "subtle-encoding", + "time 0.3.11", +] + +[[package]] +name = "tendermint-testgen" +version = "0.23.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5966190343ff29a6529052b2b841448d90b60831aaaf529a6153b608a2d1370b" +dependencies = [ + "ed25519-dalek", + "gumdrop", + "serde", + "serde_json", + "simple-error", + "tempfile", + "tendermint", + "time 0.3.11", +] + [[package]] name = "termcolor" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" dependencies = [ "winapi-util", ] +[[package]] +name = "termtree" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" + [[package]] name = "textwrap" version = "0.11.0" @@ -10639,18 +10956,18 @@ checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" dependencies = [ "proc-macro2", "quote", @@ -10683,9 +11000,9 @@ dependencies = [ [[package]] name = "tikv-jemalloc-sys" -version = "0.4.2+5.2.1-patched.2" +version = "0.4.3+5.2.1-patched.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5844e429d797c62945a566f8da4e24c7fe3fbd5d6617fd8bf7a0b7dc1ee0f22e" +checksum = "a1792ccb507d955b46af42c123ea8863668fae24d03721e40cad6a41773dbb49" dependencies = [ "cc", "fs_extra", @@ -10703,6 +11020,23 @@ dependencies = [ "winapi", ] +[[package]] +name = "time" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" +dependencies = [ + "libc", + "num_threads", + "time-macros", +] + +[[package]] +name = "time-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" + [[package]] name = "tiny-bip39" version = "0.8.2" @@ -10715,7 +11049,7 @@ dependencies = [ "pbkdf2 0.4.0", "rand 0.7.3", "rustc-hash", - "sha2 0.9.8", + "sha2 0.9.9", "thiserror", "unicode-normalization", "wasm-bindgen", @@ -10743,9 +11077,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.1.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317cca572a0e89c3ce0ca1f1bdc9369547fe318a683418e42ac8f59d14701023" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] @@ -10758,18 +11092,19 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.17.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" +checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" dependencies = [ + "autocfg", "bytes", "libc", "memchr", "mio", "num_cpus", "once_cell", - "parking_lot 0.12.0", - "pin-project-lite 0.2.6", + "parking_lot 0.12.1", + "pin-project-lite 0.2.9", "signal-hook-registry", "socket2", "tokio-macros", @@ -10778,9 +11113,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" dependencies = [ "proc-macro2", "quote", @@ -10789,9 +11124,9 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.23.2" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a27d5f2b839802bd8267fa19b0530f5a08b9c08cd417976be2a65d130fe1c11b" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ "rustls", "tokio", @@ -10800,12 +11135,12 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.7" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b2f3f698253f03119ac0102beaa64f67a67e08074d03a22d18784104543727f" +checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9" dependencies = [ "futures-core", - "pin-project-lite 0.2.6", + "pin-project-lite 0.2.9", "tokio", ] @@ -10824,43 +11159,43 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0edfdeb067411dba2044da6d1cb2df793dd35add7888d73c16e3381ded401764" +checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" dependencies = [ "bytes", "futures-core", "futures-io", "futures-sink", - "pin-project-lite 0.2.6", + "pin-project-lite 0.2.9", "tokio", "tracing", ] [[package]] name = "toml" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" dependencies = [ "serde", ] [[package]] name = "tower-service" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.35" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" +checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" dependencies = [ "cfg-if 1.0.0", "log", - "pin-project-lite 0.2.6", + "pin-project-lite 0.2.9", "tracing-attributes", "tracing-core", ] @@ -10878,9 +11213,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" +checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" dependencies = [ "once_cell", "valuable", @@ -10909,9 +11244,9 @@ dependencies = [ [[package]] name = "tracing-serde" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb65ea441fbb84f9f6748fd496cf7f63ec9af5bca94dd86456978d055e8eb28b" +checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" dependencies = [ "serde", "tracing-core", @@ -10940,12 +11275,6 @@ dependencies = [ "tracing-serde", ] -[[package]] -name = "treeline" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41" - [[package]] name = "trie-bench" version = "0.30.0" @@ -10969,7 +11298,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d32d034c0d3db64b43c31de38e945f15b40cd4ca6d2dcfc26d4798ce8de4ab83" dependencies = [ "hash-db", - "hashbrown 0.12.0", + "hashbrown 0.12.3", "log", "rustc-hex", "smallvec", @@ -11011,7 +11340,7 @@ dependencies = [ "ipnet", "lazy_static", "log", - "rand 0.8.4", + "rand 0.8.5", "smallvec", "thiserror", "tinyvec", @@ -11030,7 +11359,7 @@ dependencies = [ "lazy_static", "log", "lru-cache", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "resolv-conf", "smallvec", "thiserror", @@ -11047,7 +11376,7 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" name = "try-runtime-cli" version = "0.10.0-dev" dependencies = [ - "clap 3.1.18", + "clap 3.2.16", "jsonrpsee", "log", "parity-scale-codec", @@ -11069,9 +11398,9 @@ dependencies = [ [[package]] name = "trybuild" -version = "1.0.60" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0da18123d1316f5a65fc9b94e30a0fcf58afb1daff1b8e18f41dc30f5bfc38c8" +checksum = "e7f408301c7480f9e6294eb779cfc907f54bd901a9660ef24d7f233ed5376485" dependencies = [ "dissimilar", "glob", @@ -11097,7 +11426,7 @@ checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ "cfg-if 1.0.0", "digest 0.10.3", - "rand 0.8.4", + "rand 0.8.5", "static_assertions", ] @@ -11109,15 +11438,15 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "ucd-trie" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +checksum = "89570599c4fe5585de2b388aab47e99f7fa4e9238a1399f707a02e356058141c" [[package]] name = "uint" -version = "0.9.0" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e11fe9a9348741cf134085ad57c249508345fe16411b3d7fb4ff2da2f1d6382e" +checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" dependencies = [ "byteorder", "crunchy", @@ -11136,33 +11465,30 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.4" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" -dependencies = [ - "matches", -] +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" +checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" [[package]] name = "unicode-normalization" -version = "0.1.17" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef" +checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" dependencies = [ "tinyvec", ] [[package]] name = "unicode-width" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" [[package]] name = "unicode-xid" @@ -11172,11 +11498,11 @@ checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" [[package]] name = "universal-hash" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8326b2c654932e3e4f9196e69d08fdf7cfd718e1dc6f66b347e6024a0c961402" +checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.6", "subtle", ] @@ -11200,9 +11526,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.2.1" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ccd964113622c8e9322cfac19eb1004a07e636c545f325da085d5cdde6f1f8b" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" dependencies = [ "form_urlencoded", "idna", @@ -11228,21 +11554,15 @@ dependencies = [ [[package]] name = "vcpkg" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb" - -[[package]] -name = "vec-arena" -version = "1.0.0" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eafc1b9b2dfc6f5529177b62cf806484db55b32dc7c9658a118e11bbeb33061d" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "void" @@ -11306,9 +11626,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.77" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e68338db6becec24d3c7977b5bf8a48be992c934b5d07177e3931f5dc9b076c" +checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -11316,13 +11636,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.77" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f34c405b4f0658583dba0c1c7c9b694f3cac32655db463b56c254a1c75269523" +checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" dependencies = [ "bumpalo", - "lazy_static", "log", + "once_cell", "proc-macro2", "quote", "syn", @@ -11331,9 +11651,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.20" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3de431a2910c86679c34283a33f66f4e4abd7e0aec27b6669060148872aadf94" +checksum = "fa76fb221a1f8acddf5b54ace85912606980ad661ac7a503b4570ffd3a624dad" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -11343,9 +11663,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.77" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d5a6580be83b19dc570a8f9c324251687ab2184e57086f71625feb57ec77c8" +checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -11353,9 +11673,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.77" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3775a030dc6f5a0afd8a84981a21cc92a781eb429acef9ecce476d0c9113e92" +checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" dependencies = [ "proc-macro2", "quote", @@ -11366,9 +11686,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.77" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" + +[[package]] +name = "wasm-encoder" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c279e376c7a8e8752a8f1eaa35b7b0bee6bb9fb0cdacfa97cc3f1f289c87e2b4" +checksum = "8905fd25fdadeb0e7e8bf43a9f46f9f972d6291ad0c7a32573b88dd13a6cfa6b" +dependencies = [ + "leb128", +] [[package]] name = "wasm-gc-api" @@ -11407,9 +11736,9 @@ dependencies = [ [[package]] name = "wasmer" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f727a39e7161f7438ddb8eafe571b67c576a8c2fb459f666d9053b5bba4afdea" +checksum = "ea8d8361c9d006ea3d7797de7bd6b1492ffd0f91a22430cfda6c1658ad57bedf" dependencies = [ "cfg-if 1.0.0", "indexmap", @@ -11419,6 +11748,7 @@ dependencies = [ "target-lexicon", "thiserror", "wasm-bindgen", + "wasmer-artifact", "wasmer-compiler", "wasmer-compiler-cranelift", "wasmer-compiler-singlepass", @@ -11432,11 +11762,24 @@ dependencies = [ "winapi", ] +[[package]] +name = "wasmer-artifact" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aaf9428c29c1d8ad2ac0e45889ba8a568a835e33fd058964e5e500f2f7ce325" +dependencies = [ + "enumset", + "loupe", + "thiserror", + "wasmer-compiler", + "wasmer-types", +] + [[package]] name = "wasmer-compiler" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e9951599222eb12bd13d4d91bcded0a880e4c22c2dfdabdf5dc7e5e803b7bf3" +checksum = "e67a6cd866aed456656db2cfea96c18baabbd33f676578482b85c51e1ee19d2c" dependencies = [ "enumset", "loupe", @@ -11447,20 +11790,19 @@ dependencies = [ "target-lexicon", "thiserror", "wasmer-types", - "wasmer-vm", - "wasmparser 0.78.2", + "wasmparser 0.83.0", ] [[package]] name = "wasmer-compiler-cranelift" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c83273bce44e668f3a2b9ccb7f1193db918b1d6806f64acc5ff71f6ece5f20" +checksum = "48be2f9f6495f08649e4f8b946a2cbbe119faf5a654aa1457f9504a99d23dae0" dependencies = [ - "cranelift-codegen 0.76.0", - "cranelift-entity 0.76.0", - "cranelift-frontend 0.76.0", - "gimli 0.25.0", + "cranelift-codegen 0.82.3", + "cranelift-entity 0.82.3", + "cranelift-frontend 0.82.3", + "gimli", "loupe", "more-asserts", "rayon", @@ -11469,18 +11811,18 @@ dependencies = [ "tracing", "wasmer-compiler", "wasmer-types", - "wasmer-vm", ] [[package]] name = "wasmer-compiler-singlepass" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5432e993840cdb8e6875ddc8c9eea64e7a129579b4706bd91b8eb474d9c4a860" +checksum = "29ca2a35204d8befa85062bc7aac259a8db8070b801b8a783770ba58231d729e" dependencies = [ "byteorder", "dynasm", "dynasmrt", + "gimli", "lazy_static", "loupe", "more-asserts", @@ -11488,14 +11830,13 @@ dependencies = [ "smallvec", "wasmer-compiler", "wasmer-types", - "wasmer-vm", ] [[package]] name = "wasmer-derive" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458dbd9718a837e6dbc52003aef84487d79eedef5fa28c7d28b6784be98ac08e" +checksum = "00e50405cc2a2f74ff574584710a5f2c1d5c93744acce2ca0866084739284b51" dependencies = [ "proc-macro-error", "proc-macro2", @@ -11505,21 +11846,22 @@ dependencies = [ [[package]] name = "wasmer-engine" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed603a6d037ebbb14014d7f739ae996a78455a4b86c41cfa4e81c590a1253b9" +checksum = "3f98f010978c244db431b392aeab0661df7ea0822343334f8f2a920763548e45" dependencies = [ "backtrace", "enumset", "lazy_static", "loupe", - "memmap2 0.5.0", + "memmap2 0.5.5", "more-asserts", "rustc-demangle", "serde", "serde_bytes", "target-lexicon", "thiserror", + "wasmer-artifact", "wasmer-compiler", "wasmer-types", "wasmer-vm", @@ -11527,21 +11869,22 @@ dependencies = [ [[package]] name = "wasmer-engine-dylib" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccd7fdc60e252a795c849b3f78a81a134783051407e7e279c10b7019139ef8dc" +checksum = "ad0358af9c154724587731175553805648d9acb8f6657880d165e378672b7e53" dependencies = [ "cfg-if 1.0.0", "enum-iterator", "enumset", "leb128", - "libloading 0.7.0", + "libloading 0.7.3", "loupe", - "object 0.28.3", + "object 0.28.4", "rkyv", "serde", "tempfile", "tracing", + "wasmer-artifact", "wasmer-compiler", "wasmer-engine", "wasmer-object", @@ -11552,12 +11895,11 @@ dependencies = [ [[package]] name = "wasmer-engine-universal" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcff0cd2c01a8de6009fd863b14ea883132a468a24f2d2ee59dc34453d3a31b5" +checksum = "440dc3d93c9ca47865a4f4edd037ea81bf983b5796b59b3d712d844b32dbef15" dependencies = [ "cfg-if 1.0.0", - "enum-iterator", "enumset", "leb128", "loupe", @@ -11565,18 +11907,35 @@ dependencies = [ "rkyv", "wasmer-compiler", "wasmer-engine", + "wasmer-engine-universal-artifact", "wasmer-types", "wasmer-vm", "winapi", ] +[[package]] +name = "wasmer-engine-universal-artifact" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f1db3f54152657eb6e86c44b66525ff7801dad8328fe677da48dd06af9ad41" +dependencies = [ + "enum-iterator", + "enumset", + "loupe", + "rkyv", + "thiserror", + "wasmer-artifact", + "wasmer-compiler", + "wasmer-types", +] + [[package]] name = "wasmer-object" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ce18ac2877050e59580d27ee1a88f3192d7a31e77fbba0852abc7888d6e0b5" +checksum = "8d831335ff3a44ecf451303f6f891175c642488036b92ceceb24ac8623a8fa8b" dependencies = [ - "object 0.28.3", + "object 0.28.4", "thiserror", "wasmer-compiler", "wasmer-types", @@ -11584,12 +11943,15 @@ dependencies = [ [[package]] name = "wasmer-types" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "659fa3dd6c76f62630deff4ac8c7657b07f0b1e4d7e0f8243a552b9d9b448e24" +checksum = "39df01ea05dc0a9bab67e054c7cb01521e53b35a7bb90bd02eca564ed0b2667f" dependencies = [ + "backtrace", + "enum-iterator", "indexmap", "loupe", + "more-asserts", "rkyv", "serde", "thiserror", @@ -11597,23 +11959,28 @@ dependencies = [ [[package]] name = "wasmer-vm" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afdc46158517c2769f9938bc222a7d41b3bb330824196279d8aa2d667cd40641" +checksum = "30d965fa61f4dc4cdb35a54daaf7ecec3563fbb94154a6c35433f879466247dd" dependencies = [ "backtrace", "cc", "cfg-if 1.0.0", + "corosensei", "enum-iterator", "indexmap", + "lazy_static", "libc", "loupe", + "mach", "memoffset", "more-asserts", "region 3.0.0", "rkyv", + "scopeguard", "serde", "thiserror", + "wasmer-artifact", "wasmer-types", "winapi", ] @@ -11637,18 +12004,18 @@ dependencies = [ [[package]] name = "wasmi-validation" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb8e860796d8be48efef530b60eebf84e74a88bce107374fffb0da97d504b8" +checksum = "165343ecd6c018fc09ebcae280752702c9a2ef3e6f8d02f1cfcbdb53ef6d7937" dependencies = [ "parity-wasm 0.42.2", ] [[package]] name = "wasmparser" -version = "0.78.2" +version = "0.83.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52144d4c78e5cf8b055ceab8e5fa22814ce4315d6002ad32cfd914f37c12fd65" +checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" [[package]] name = "wasmparser" @@ -11661,9 +12028,9 @@ dependencies = [ [[package]] name = "wasmtime" -version = "0.38.0" +version = "0.38.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c842f9c8e190fe01300fc8d715e9368c775670fb9856247c67abffdb5236d6db" +checksum = "1f50eadf868ab6a04b7b511460233377d0bfbb92e417b2f6a98b98fef2e098f5" dependencies = [ "anyhow", "backtrace", @@ -11673,9 +12040,9 @@ dependencies = [ "lazy_static", "libc", "log", - "object 0.28.3", + "object 0.28.4", "once_cell", - "paste 1.0.6", + "paste 1.0.8", "psm", "rayon", "region 2.2.0", @@ -11692,9 +12059,9 @@ dependencies = [ [[package]] name = "wasmtime-cache" -version = "0.38.0" +version = "0.38.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cce2aa752e864a33eef2a6629edc59554e75f0bc1719431dac5e49eed516af69" +checksum = "d1df23c642e1376892f3b72f311596976979cbf8b85469680cdd3a8a063d12a2" dependencies = [ "anyhow", "base64", @@ -11704,7 +12071,7 @@ dependencies = [ "log", "rustix 0.33.7", "serde", - "sha2 0.9.8", + "sha2 0.9.9", "toml", "winapi", "zstd", @@ -11712,20 +12079,20 @@ dependencies = [ [[package]] name = "wasmtime-cranelift" -version = "0.38.0" +version = "0.38.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "922361eb8c03cea8909bc922471202f6c6bc2f0c682fac2fe473740441c86b3b" +checksum = "f264ff6b4df247d15584f2f53d009fbc90032cfdc2605b52b961bffc71b6eccd" dependencies = [ "anyhow", - "cranelift-codegen 0.85.0", - "cranelift-entity 0.85.0", - "cranelift-frontend 0.85.0", + "cranelift-codegen 0.85.3", + "cranelift-entity 0.85.3", + "cranelift-frontend 0.85.3", "cranelift-native", "cranelift-wasm", - "gimli 0.26.1", + "gimli", "log", "more-asserts", - "object 0.28.3", + "object 0.28.4", "target-lexicon", "thiserror", "wasmparser 0.85.0", @@ -11734,17 +12101,17 @@ dependencies = [ [[package]] name = "wasmtime-environ" -version = "0.38.0" +version = "0.38.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e602f1120fc40a3f016f1f69d08c86cfeff7b867bed1462901953e6871f85167" +checksum = "839d2820e4b830f4b9e7aa08d4c0acabf4a5036105d639f6dfa1c6891c73bdc6" dependencies = [ "anyhow", - "cranelift-entity 0.85.0", - "gimli 0.26.1", + "cranelift-entity 0.85.3", + "gimli", "indexmap", "log", "more-asserts", - "object 0.28.3", + "object 0.28.4", "serde", "target-lexicon", "thiserror", @@ -11754,18 +12121,18 @@ dependencies = [ [[package]] name = "wasmtime-jit" -version = "0.38.0" +version = "0.38.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49af1445759a8e797a92f27dd0983c155615648263052e0b80d69e7d223896b7" +checksum = "ef0a0bcbfa18b946d890078ba0e1bc76bcc53eccfb40806c0020ec29dcd1bd49" dependencies = [ "addr2line", "anyhow", "bincode", "cfg-if 1.0.0", "cpp_demangle", - "gimli 0.26.1", + "gimli", "log", - "object 0.28.3", + "object 0.28.4", "region 2.2.0", "rustc-demangle", "rustix 0.33.7", @@ -11780,20 +12147,20 @@ dependencies = [ [[package]] name = "wasmtime-jit-debug" -version = "0.38.0" +version = "0.38.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5dd480cc6dc0a401653e45b79796a3317f8228990d84bc2271bdaf0810071" +checksum = "4f4779d976206c458edd643d1ac622b6c37e4a0800a8b1d25dfbf245ac2f2cac" dependencies = [ "lazy_static", - "object 0.28.3", + "object 0.28.4", "rustix 0.33.7", ] [[package]] name = "wasmtime-runtime" -version = "0.38.0" +version = "0.38.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e875bcd02d1ecfc7d099dd58354d55d73467652eb2b103ff470fe3aecb7d0381" +checksum = "b7eb6ffa169eb5dcd18ac9473c817358cd57bc62c244622210566d473397954a" dependencies = [ "anyhow", "backtrace", @@ -11806,7 +12173,7 @@ dependencies = [ "memfd", "memoffset", "more-asserts", - "rand 0.8.4", + "rand 0.8.5", "region 2.2.0", "rustix 0.33.7", "thiserror", @@ -11817,11 +12184,11 @@ dependencies = [ [[package]] name = "wasmtime-types" -version = "0.38.0" +version = "0.38.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd63a19ba61ac7448add4dc1fecb8d78304812af2a52dad04b89f887791b156" +checksum = "8d932b0ac5336f7308d869703dd225610a6a3aeaa8e968c52b43eed96cefb1c2" dependencies = [ - "cranelift-entity 0.85.0", + "cranelift-entity 0.85.3", "serde", "thiserror", "wasmparser 0.85.0", @@ -11829,27 +12196,30 @@ dependencies = [ [[package]] name = "wast" -version = "38.0.0" +version = "45.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ebc29df4629f497e0893aacd40f13a4a56b85ef6eb4ab6d603f07244f1a7bf2" +checksum = "186c474c4f9bb92756b566d592a16591b4526b1a4841171caa3f31d7fe330d96" dependencies = [ "leb128", + "memchr", + "unicode-width", + "wasm-encoder", ] [[package]] name = "wat" -version = "1.0.40" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcfaeb27e2578d2c6271a45609f4a055e6d7ba3a12eff35b1fd5ba147bdf046" +checksum = "c2d4bc4724b4f02a482c8cab053dac5ef26410f264c06ce914958f9a42813556" dependencies = [ "wast", ] [[package]] name = "web-sys" -version = "0.3.54" +version = "0.3.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a84d70d1ec7d2da2d26a5bd78f4bca1b8c3254805363ce743b7a05bc30d195a" +checksum = "ed055ab27f941423197eb86b2035720b1a3ce40504df082cac2ecc6ed73335a1" dependencies = [ "js-sys", "wasm-bindgen", @@ -11867,30 +12237,31 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.22.2" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "552ceb903e957524388c4d3475725ff2c8b7960922063af6ce53c9a43da07449" +checksum = "f1c760f0d366a6c24a02ed7816e23e691f5d92291f94d15e836006fd11b04daf" dependencies = [ "webpki", ] [[package]] -name = "wepoll-sys" -version = "3.0.1" +name = "wepoll-ffi" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcb14dea929042224824779fbc82d9fab8d2e6d3cbc0ac404de8edf489e77ff" +checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" dependencies = [ "cc", ] [[package]] name = "which" -version = "4.0.2" +version = "4.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87c14ef7e1b8b8ecfc75d5eca37949410046e66f15d185c01d70824f1f8111ef" +checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" dependencies = [ + "either", + "lazy_static", "libc", - "thiserror", ] [[package]] @@ -11932,28 +12303,28 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.29.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac7fef12f4b59cd0a29339406cc9203ab44e440ddff6b3f5a41455349fa9cf3" +checksum = "45296b64204227616fdbf2614cefa4c236b98ee64dfaaaa435207ed99fe7829f" dependencies = [ - "windows_aarch64_msvc 0.29.0", - "windows_i686_gnu 0.29.0", - "windows_i686_msvc 0.29.0", - "windows_x86_64_gnu 0.29.0", - "windows_x86_64_msvc 0.29.0", + "windows_aarch64_msvc 0.34.0", + "windows_i686_gnu 0.34.0", + "windows_i686_msvc 0.34.0", + "windows_x86_64_gnu 0.34.0", + "windows_x86_64_msvc 0.34.0", ] [[package]] name = "windows-sys" -version = "0.32.0" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6" +checksum = "43dbb096663629518eb1dfa72d80243ca5a6aca764cae62a2df70af760a9be75" dependencies = [ - "windows_aarch64_msvc 0.32.0", - "windows_i686_gnu 0.32.0", - "windows_i686_msvc 0.32.0", - "windows_x86_64_gnu 0.32.0", - "windows_x86_64_msvc 0.32.0", + "windows_aarch64_msvc 0.33.0", + "windows_i686_gnu 0.33.0", + "windows_i686_msvc 0.33.0", + "windows_x86_64_gnu 0.33.0", + "windows_x86_64_msvc 0.33.0", ] [[package]] @@ -11971,15 +12342,15 @@ dependencies = [ [[package]] name = "windows_aarch64_msvc" -version = "0.29.0" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d027175d00b01e0cbeb97d6ab6ebe03b12330a35786cbaca5252b1c4bf5d9b" +checksum = "cd761fd3eb9ab8cc1ed81e56e567f02dd82c4c837e48ac3b2181b9ffc5060807" [[package]] name = "windows_aarch64_msvc" -version = "0.32.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" +checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" [[package]] name = "windows_aarch64_msvc" @@ -11989,15 +12360,15 @@ checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" [[package]] name = "windows_i686_gnu" -version = "0.29.0" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8793f59f7b8e8b01eda1a652b2697d87b93097198ae85f823b969ca5b89bba58" +checksum = "cab0cf703a96bab2dc0c02c0fa748491294bf9b7feb27e1f4f96340f208ada0e" [[package]] name = "windows_i686_gnu" -version = "0.32.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" +checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" [[package]] name = "windows_i686_gnu" @@ -12007,15 +12378,15 @@ checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" [[package]] name = "windows_i686_msvc" -version = "0.29.0" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8602f6c418b67024be2996c512f5f995de3ba417f4c75af68401ab8756796ae4" +checksum = "8cfdbe89cc9ad7ce618ba34abc34bbb6c36d99e96cae2245b7943cd75ee773d0" [[package]] name = "windows_i686_msvc" -version = "0.32.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" +checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" [[package]] name = "windows_i686_msvc" @@ -12025,15 +12396,15 @@ checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" [[package]] name = "windows_x86_64_gnu" -version = "0.29.0" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d615f419543e0bd7d2b3323af0d86ff19cbc4f816e6453f36a2c2ce889c354" +checksum = "b4dd9b0c0e9ece7bb22e84d70d01b71c6d6248b81a3c60d11869451b4cb24784" [[package]] name = "windows_x86_64_gnu" -version = "0.32.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" +checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" [[package]] name = "windows_x86_64_gnu" @@ -12043,15 +12414,15 @@ checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" [[package]] name = "windows_x86_64_msvc" -version = "0.29.0" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d95421d9ed3672c280884da53201a5c46b7b2765ca6faf34b0d71cf34a3561" +checksum = "ff1e4aa646495048ec7f3ffddc411e1d829c026a2ec62b39da15c1055e406eaa" [[package]] name = "windows_x86_64_msvc" -version = "0.32.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" +checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" [[package]] name = "windows_x86_64_msvc" @@ -12079,34 +12450,34 @@ dependencies = [ [[package]] name = "x25519-dalek" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc614d95359fd7afc321b66d2107ede58b246b844cf5d8a0adcca413e439f088" +checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f" dependencies = [ - "curve25519-dalek 3.0.2", + "curve25519-dalek 3.2.0", "rand_core 0.5.1", "zeroize", ] [[package]] name = "yamux" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0608f53c1dc0bad505d03a34bbd49fbf2ad7b51eb036123e896365532745a1" +checksum = "e5d9ba232399af1783a58d8eb26f6b5006fbefe2dc9ef36bd283324792d03ea5" dependencies = [ "futures", "log", "nohash-hasher", - "parking_lot 0.12.0", - "rand 0.8.4", + "parking_lot 0.12.1", + "rand 0.8.5", "static_assertions", ] [[package]] name = "zeroize" -version = "1.4.3" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" dependencies = [ "zeroize_derive", ] diff --git a/Cargo.toml b/Cargo.toml index 1f22343c002a8..59237be64922d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -153,6 +153,7 @@ members = [ "frame/utility", "frame/vesting", "frame/whitelist", + "frame/ibc", "primitives/api", "primitives/api/proc-macro", "primitives/api/test", diff --git a/frame/ibc/Cargo.lock b/frame/ibc/Cargo.lock new file mode 100644 index 0000000000000..e2df74bf1d4f1 --- /dev/null +++ b/frame/ibc/Cargo.lock @@ -0,0 +1,3462 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[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 = "addr2line" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom 0.2.7", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91f1f46651137be86f3a2b9a8359f9ab421d04d941c62b5982e1ca21113adf9" + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + +[[package]] +name = "async-trait" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" +dependencies = [ + "addr2line", + "cc", + "cfg-if 1.0.0", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base58" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "beefy-light-client" +version = "0.1.0" +source = "git+https://github.com/octopus-network/beefy-light-client.git?branch=main#113215ed340d3cc3f61dd9f4e8b8a6138f5ab736" +dependencies = [ + "beefy-merkle-tree", + "blake2-rfc", + "borsh", + "ckb-merkle-mountain-range", + "hex", + "libsecp256k1", + "parity-scale-codec", + "scale-info", + "serde", +] + +[[package]] +name = "beefy-merkle-tree" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "tiny-keccak", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake2" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388" +dependencies = [ + "digest 0.10.3", +] + +[[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 0.1.5", + "byte-tools", + "byteorder", + "generic-array 0.12.4", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "block-padding 0.2.1", + "generic-array 0.14.6", +] + +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array 0.14.6", +] + +[[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 = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + +[[package]] +name = "borsh" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15bf3650200d8bffa99015595e10f1fbd17de07abbc25bb067da79e769939bfa" +dependencies = [ + "borsh-derive", + "hashbrown 0.11.2", +] + +[[package]] +name = "borsh-derive" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6441c552f230375d18e3cc377677914d2ca2b0d36e52129fe15450a2dce46775" +dependencies = [ + "borsh-derive-internal", + "borsh-schema-derive-internal", + "proc-macro-crate 0.1.5", + "proc-macro2", + "syn", +] + +[[package]] +name = "borsh-derive-internal" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "borsh-schema-derive-internal" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "build-helper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdce191bf3fa4995ce948c8c83b4640a1745457a149e73c6db75b4ffe36aad5f" +dependencies = [ + "semver 0.6.0", +] + +[[package]] +name = "bumpalo" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" + +[[package]] +name = "byte-slice-cast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87c5fdd0166095e1d463fc6cc01aa8ce547ad77a4e84d42eb6762b084e28067e" + +[[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.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" + +[[package]] +name = "camino" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "869119e97797867fd90f5e22af7d0bd274bd4635ebb9eb68c04f3f513ae6c412" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" +dependencies = [ + "camino", + "cargo-platform", + "semver 1.0.13", + "serde", + "serde_json", +] + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +dependencies = [ + "jobserver", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time 0.1.44", + "winapi", +] + +[[package]] +name = "ckb-merkle-mountain-range" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "530710c310c455ced794feaa13f284c4c5d40ae5d82875392a83da7c5f84a8db" +dependencies = [ + "cfg-if 0.1.10", +] + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "cpufeatures" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array 0.14.6", + "typenum", +] + +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array 0.14.6", + "subtle", +] + +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array 0.14.6", + "subtle", +] + +[[package]] +name = "curve25519-dalek" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b85542f99a2dfa2a1b8e192662741c9859a846b296bef1c92ef9b58b5a216" +dependencies = [ + "byteorder", + "digest 0.8.1", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +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 0.12.4", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array 0.14.6", +] + +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer 0.10.2", + "crypto-common", + "subtle", +] + +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + +[[package]] +name = "dyn-clonable" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e9232f0e607a262ceb9bd5141a3dfb3e4db6994b31989bbfd845878cba59fd4" +dependencies = [ + "dyn-clonable-impl", + "dyn-clone", +] + +[[package]] +name = "dyn-clonable-impl" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "558e40ea573c374cf53507fd240b7ee2f5477df7cfebdb97323ec61c719399c5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dyn-clone" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2" + +[[package]] +name = "ed25519" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" +dependencies = [ + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" +dependencies = [ + "curve25519-dalek 3.2.0", + "ed25519", + "rand 0.7.3", + "serde", + "sha2 0.9.9", + "zeroize", +] + +[[package]] +name = "either" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" + +[[package]] +name = "environmental" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b91989ae21441195d7d9b9993a2f9295c7e1a8c96255d8b729accddc124797" + +[[package]] +name = "eyre" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + +[[package]] +name = "fixed-hash" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "flex-error" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c606d892c9de11507fa0dcffc116434f94e105d0bbdc4e405b61519464c49d7b" +dependencies = [ + "eyre", + "paste", +] + +[[package]] +name = "frame-benchmarking" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "frame-support", + "frame-system", + "linregress", + "log", + "parity-scale-codec", + "paste", + "scale-info", + "serde", + "sp-api", + "sp-application-crypto", + "sp-io", + "sp-runtime", + "sp-runtime-interface", + "sp-std 4.0.0", + "sp-storage", +] + +[[package]] +name = "frame-metadata" +version = "15.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df6bb8542ef006ef0de09a5c4420787d79823c0ed7924225822362fd2bf2ff2d" +dependencies = [ + "cfg-if 1.0.0", + "parity-scale-codec", + "scale-info", + "serde", +] + +[[package]] +name = "frame-support" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "bitflags", + "frame-metadata", + "frame-support-procedural", + "impl-trait-for-tuples", + "log", + "once_cell", + "parity-scale-codec", + "paste", + "scale-info", + "serde", + "smallvec", + "sp-arithmetic", + "sp-core", + "sp-core-hashing-proc-macro", + "sp-inherents", + "sp-io", + "sp-runtime", + "sp-staking", + "sp-state-machine", + "sp-std 4.0.0", + "sp-tracing", + "tt-call", +] + +[[package]] +name = "frame-support-procedural" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "Inflector", + "frame-support-procedural-tools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "frame-support-procedural-tools" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "frame-support-procedural-tools-derive", + "proc-macro-crate 1.2.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "frame-support-procedural-tools-derive" +version = "3.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "frame-system" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "frame-support", + "log", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std 4.0.0", + "sp-version", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" + +[[package]] +name = "futures-executor" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", + "num_cpus", +] + +[[package]] +name = "futures-io" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" + +[[package]] +name = "futures-macro" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "gimli" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" + +[[package]] +name = "gumdrop" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bc700f989d2f6f0248546222d9b4258f5b02a171a431f8285a81c08142629e3" +dependencies = [ + "gumdrop_derive", +] + +[[package]] +name = "gumdrop_derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "729f9bd3449d77e7831a18abfb7ba2f99ee813dfd15b8c2167c9a54ba20aa99d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" +dependencies = [ + "crypto-mac 0.8.0", + "digest 0.9.0", +] + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac 0.11.1", + "digest 0.9.0", +] + +[[package]] +name = "hmac-drbg" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" +dependencies = [ + "digest 0.9.0", + "generic-array 0.14.6", + "hmac 0.8.1", +] + +[[package]] +name = "ibc" +version = "0.16.0" +source = "git+https://github.com/octopus-network/ibc-rs.git?branch=feature/ics20-davirian-base-0.9.18#36808084c389b0a0bba008723018c5990500f098" +dependencies = [ + "beefy-light-client", + "beefy-merkle-tree", + "blake2-rfc", + "bytes", + "derive_more", + "flex-error", + "frame-support", + "frame-system", + "hash-db", + "ibc-proto", + "ics23", + "num-traits", + "parity-scale-codec", + "primitive-types", + "prost 0.10.4", + "prost-types 0.10.1", + "safe-regex", + "serde", + "serde_derive", + "serde_json", + "sha2 0.10.2", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std 4.0.0", + "sp-trie", + "subtle-encoding", + "tendermint", + "tendermint-light-client-verifier", + "tendermint-proto", + "tendermint-testgen", + "time 0.3.11", + "tracing", + "uint", +] + +[[package]] +name = "ibc-proto" +version = "0.19.0" +source = "git+https://github.com/octopus-network/ibc-rs.git?branch=feature/ics20-davirian-base-0.9.18#36808084c389b0a0bba008723018c5990500f098" +dependencies = [ + "base64", + "bytes", + "prost 0.10.4", + "prost-types 0.10.1", + "serde", + "tendermint-proto", +] + +[[package]] +name = "ics23" +version = "0.8.0-alpha" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a435f2471c1b2ce14771da465d47321c5905fac866d0effa9e0a3eb5d94fcf" +dependencies = [ + "anyhow", + "bytes", + "hex", + "prost 0.10.4", + "ripemd160", + "sha2 0.9.9", + "sha3 0.9.1", + "sp-std 3.0.0", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-serde" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "integer-sqrt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" +dependencies = [ + "num-traits", +] + +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "itoa" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" + +[[package]] +name = "jobserver" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" + +[[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.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "505e71a4706fa491e9b1b55f51b95d4037d0821ee40131190475f692b35b009b" + +[[package]] +name = "libm" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da83a57f3f5ba3680950aa3cbc806fc297bc0b289d42e8942ed528ace71b8145" + +[[package]] +name = "libsecp256k1" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" +dependencies = [ + "arrayref", + "base64", + "digest 0.9.0", + "hmac-drbg", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand 0.8.5", + "serde", + "sha2 0.9.9", + "typenum", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "linregress" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6c601a85f5ecd1aba625247bca0031585fb1c446461b142878a16f8245ddeb8" +dependencies = [ + "nalgebra", + "statrs", +] + +[[package]] +name = "lock_api" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "matchers" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matrixmultiply" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add85d4dd35074e6fedc608f8c8f513a3548619a9024b751949ef0e8e45a4d84" +dependencies = [ + "rawpointer", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memory-db" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6566c70c1016f525ced45d7b7f97730a2bafb037c788211d0c186ef5b2189f0a" +dependencies = [ + "hash-db", + "hashbrown 0.12.3", + "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 = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e261cf0f8b3c42ded9f7d2bb59dea03aa52bc8a1cbc7482f9fc3fd1229d3b42" +dependencies = [ + "byteorder", + "keccak", + "rand_core 0.5.1", + "zeroize", +] + +[[package]] +name = "miniz_oxide" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +dependencies = [ + "adler", +] + +[[package]] +name = "nalgebra" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "462fffe4002f4f2e1f6a9dcf12cc1a6fc0e15989014efc02a941d3e0f5dc2120" +dependencies = [ + "approx", + "matrixmultiply", + "nalgebra-macros", + "num-complex", + "num-rational 0.4.1", + "num-traits", + "rand 0.8.5", + "rand_distr", + "simba", + "typenum", +] + +[[package]] +name = "nalgebra-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01fcc0b8149b4632adc89ac3b7b31a12fb6099a0317a4eb2ebff574ef7de7218" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-format" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bafe4179722c2894288ee77a9f044f02811c86af699344c498b0840c698a2465" +dependencies = [ + "arrayvec 0.4.12", + "itoa 0.4.8", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + +[[package]] +name = "object" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" + +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "pallet-ibc" +version = "3.0.0-pre.0" +dependencies = [ + "beefy-light-client", + "chrono", + "flex-error", + "frame-benchmarking", + "frame-support", + "frame-system", + "hex", + "ibc", + "ibc-proto", + "log", + "parity-scale-codec", + "prost 0.9.0", + "prost-types 0.9.0", + "scale-info", + "serde", + "serde_json", + "sha2 0.10.2", + "sp-core", + "sp-io", + "sp-keyring", + "sp-runtime", + "sp-std 4.0.0", + "sp-tracing", + "substrate-wasm-builder", + "tendermint-proto", + "time 0.3.11", +] + +[[package]] +name = "parity-scale-codec" +version = "3.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9182e4a71cae089267ab03e67c99368db7cd877baf50f931e5d6d4b71e195ac0" +dependencies = [ + "arrayvec 0.7.2", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" +dependencies = [ + "proc-macro-crate 1.2.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "parity-util-mem" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c32561d248d352148124f036cac253a644685a21dc9fea383eb4907d7bd35a8f" +dependencies = [ + "cfg-if 1.0.0", + "hashbrown 0.12.3", + "impl-trait-for-tuples", + "parity-util-mem-derive", + "parking_lot", + "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.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16ad52817c4d343339b3bc2e26861bd21478eda0b7509acf83505727000512ac" +dependencies = [ + "byteorder", +] + +[[package]] +name = "parity-wasm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "paste" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9423e2b32f7a043629287a536f21951e8c6a82482d0acb1eeebfc90bc2225b22" + +[[package]] +name = "pbkdf2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216eaa586a190f0a738f2f918511eecfa90f13295abec0e457cdebcceda80cbd" +dependencies = [ + "crypto-mac 0.8.0", +] + +[[package]] +name = "pbkdf2" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95f5254224e617595d2cc3cc73ff0a5eaf2637519e25f03388154e9378b6ffa" +dependencies = [ + "crypto-mac 0.11.1", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "primitive-types" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28720988bff275df1f51b171e1b2a18c30d194c4d2b61defdacecd625a5d94a" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-serde", + "scale-info", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro-crate" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d50bfb8c23f23915855a00d98b5a35ef2e0b871bb52937bacadb798fbb66c8" +dependencies = [ + "once_cell", + "thiserror", + "toml", +] + +[[package]] +name = "proc-macro2" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" +dependencies = [ + "bytes", + "prost-derive 0.9.0", +] + +[[package]] +name = "prost" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71adf41db68aa0daaefc69bb30bcd68ded9b9abaad5d1fbb6304c4fb390e083e" +dependencies = [ + "bytes", + "prost-derive 0.10.1", +] + +[[package]] +name = "prost-derive" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-derive" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b670f45da57fb8542ebdbb6105a925fe571b67f9e7ed9f47a06a84e72b4e7cc" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" +dependencies = [ + "bytes", + "prost 0.9.0", +] + +[[package]] +name = "prost-types" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d0a014229361011dc8e69c8a1ec6c2e8d0f2af7c91e3ea3f5b2170298461e68" +dependencies = [ + "bytes", + "prost 0.10.4", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.3", +] + +[[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_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.3", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom 0.2.7", +] + +[[package]] +name = "rand_distr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[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_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ref-cast" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed13bcd201494ab44900a96490291651d200730904221832b9547d24a87d332b" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5234cd6063258a5e32903b53b1b6ac043a0541c8adc1f610f67b0326c7a578fa" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "regex" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "ripemd160" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eca4ecc81b7f313189bf73ce724400a07da2a6dac19588b03c8bd76a2dcc251" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "opaque-debug 0.3.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + +[[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 = "rustversion" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" + +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[package]] +name = "safe-proc-macro2" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "814c536dcd27acf03296c618dab7ad62d28e70abd7ba41d3f34a2ce707a2c666" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "safe-quote" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77e530f7831f3feafcd5f1aae406ac205dd998436b4007c8e80f03eca78a88f7" +dependencies = [ + "safe-proc-macro2", +] + +[[package]] +name = "safe-regex" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15289bf322e0673d52756a18194167f2378ec1a15fe884af6e2d2cb934822b0" +dependencies = [ + "safe-regex-macro", +] + +[[package]] +name = "safe-regex-compiler" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba76fae590a2aa665279deb1f57b5098cbace01a0c5e60e262fcf55f7c51542" +dependencies = [ + "safe-proc-macro2", + "safe-quote", +] + +[[package]] +name = "safe-regex-macro" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c2e96b5c03f158d1b16ba79af515137795f4ad4e8de3f790518aae91f1d127" +dependencies = [ + "safe-proc-macro2", + "safe-regex-compiler", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scale-info" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c46be926081c9f4dd5dd9b6f1d3e3229f2360bc6502dd8836f84a93b7c75e99a" +dependencies = [ + "bitvec", + "cfg-if 1.0.0", + "derive_more", + "parity-scale-codec", + "scale-info-derive", + "serde", +] + +[[package]] +name = "scale-info-derive" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e334bb10a245e28e5fd755cabcafd96cfcd167c99ae63a46924ca8d8703a3c" +dependencies = [ + "proc-macro-crate 1.2.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "schnorrkel" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "021b403afe70d81eea68f6ea12f6b3c9588e5d536a94c3bf80f15e7faa267862" +dependencies = [ + "arrayref", + "arrayvec 0.5.2", + "curve25519-dalek 2.1.3", + "getrandom 0.1.16", + "merlin", + "rand 0.7.3", + "rand_core 0.5.1", + "sha2 0.8.2", + "subtle", + "zeroize", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "secp256k1" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c42e6f1735c5f00f51e43e28d6634141f2bcad10931b2609ddd74a86d751260" +dependencies = [ + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957da2573cde917463ece3570eab4a0b3f19de6f1646cde62e6fd3868f566036" +dependencies = [ + "cc", +] + +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "zeroize", +] + +[[package]] +name = "semver" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a3186ec9e65071a2095434b1f5bb24838d4e8e130f584c790f6033c79943537" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711" +dependencies = [ + "serde", +] + +[[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.142" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590c437916fb6b221e1d00df6e3294f3fccd70ca7e92541c475d6ed6ef5fee2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.142" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b5b8d809babe02f538c2cfec6f2c1ed10804c0e5a6a041a049a4f5588ccc2e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7" +dependencies = [ + "itoa 1.0.3", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sha2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" +dependencies = [ + "block-buffer 0.7.3", + "digest 0.8.1", + "fake-simd", + "opaque-debug 0.2.3", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.9.0", + "opaque-debug 0.3.0", +] + +[[package]] +name = "sha2" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.10.3", +] + +[[package]] +name = "sha3" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "keccak", + "opaque-debug 0.3.0", +] + +[[package]] +name = "sha3" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a31480366ec990f395a61b7c08122d99bd40544fdb5abcfc1b06bb29994312c" +dependencies = [ + "digest 0.10.3", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signature" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f054c6c1a6e95179d6f23ed974060dcefb2d9388bb7256900badad682c499de4" + +[[package]] +name = "simba" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e82063457853d00243beda9952e910b82593e4b07ae9f721b9278a99a0d3d5c" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", +] + +[[package]] +name = "simple-error" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc47a29ce97772ca5c927f75bac34866b16d64e07f330c3248e2d7226623901b" + +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" + +[[package]] +name = "sp-api" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "hash-db", + "log", + "parity-scale-codec", + "sp-api-proc-macro", + "sp-core", + "sp-runtime", + "sp-state-machine", + "sp-std 4.0.0", + "sp-version", + "thiserror", +] + +[[package]] +name = "sp-api-proc-macro" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "blake2", + "proc-macro-crate 1.2.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sp-application-crypto" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-std 4.0.0", +] + +[[package]] +name = "sp-arithmetic" +version = "5.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "integer-sqrt", + "num-traits", + "parity-scale-codec", + "scale-info", + "serde", + "sp-debug-derive", + "sp-std 4.0.0", + "static_assertions", +] + +[[package]] +name = "sp-core" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "base58", + "bitflags", + "blake2-rfc", + "byteorder", + "dyn-clonable", + "ed25519-dalek", + "futures", + "hash-db", + "hash256-std-hasher", + "hex", + "impl-serde", + "lazy_static", + "libsecp256k1", + "log", + "merlin", + "num-traits", + "parity-scale-codec", + "parity-util-mem", + "parking_lot", + "primitive-types", + "rand 0.7.3", + "regex", + "scale-info", + "schnorrkel", + "secp256k1", + "secrecy", + "serde", + "sp-core-hashing", + "sp-debug-derive", + "sp-externalities", + "sp-runtime-interface", + "sp-std 4.0.0", + "sp-storage", + "ss58-registry", + "substrate-bip39", + "thiserror", + "tiny-bip39", + "wasmi", + "zeroize", +] + +[[package]] +name = "sp-core-hashing" +version = "4.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "blake2", + "byteorder", + "digest 0.10.3", + "sha2 0.10.2", + "sha3 0.10.2", + "sp-std 4.0.0", + "twox-hash", +] + +[[package]] +name = "sp-core-hashing-proc-macro" +version = "5.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "proc-macro2", + "quote", + "sp-core-hashing", + "syn", +] + +[[package]] +name = "sp-debug-derive" +version = "4.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sp-externalities" +version = "0.12.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "environmental", + "parity-scale-codec", + "sp-std 4.0.0", + "sp-storage", +] + +[[package]] +name = "sp-inherents" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "async-trait", + "impl-trait-for-tuples", + "parity-scale-codec", + "sp-core", + "sp-runtime", + "sp-std 4.0.0", + "thiserror", +] + +[[package]] +name = "sp-io" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "futures", + "hash-db", + "libsecp256k1", + "log", + "parity-scale-codec", + "parking_lot", + "secp256k1", + "sp-core", + "sp-externalities", + "sp-keystore", + "sp-runtime-interface", + "sp-state-machine", + "sp-std 4.0.0", + "sp-tracing", + "sp-trie", + "sp-wasm-interface", + "tracing", + "tracing-core", +] + +[[package]] +name = "sp-keyring" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "lazy_static", + "sp-core", + "sp-runtime", + "strum", +] + +[[package]] +name = "sp-keystore" +version = "0.12.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "async-trait", + "futures", + "merlin", + "parity-scale-codec", + "parking_lot", + "schnorrkel", + "sp-core", + "sp-externalities", + "thiserror", +] + +[[package]] +name = "sp-maybe-compressed-blob" +version = "4.1.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "thiserror", + "zstd", +] + +[[package]] +name = "sp-panic-handler" +version = "4.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "backtrace", + "lazy_static", + "regex", +] + +[[package]] +name = "sp-runtime" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "either", + "hash256-std-hasher", + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "parity-util-mem", + "paste", + "rand 0.7.3", + "scale-info", + "serde", + "sp-application-crypto", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-std 4.0.0", +] + +[[package]] +name = "sp-runtime-interface" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "impl-trait-for-tuples", + "parity-scale-codec", + "primitive-types", + "sp-externalities", + "sp-runtime-interface-proc-macro", + "sp-std 4.0.0", + "sp-storage", + "sp-tracing", + "sp-wasm-interface", + "static_assertions", +] + +[[package]] +name = "sp-runtime-interface-proc-macro" +version = "5.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "Inflector", + "proc-macro-crate 1.2.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sp-staking" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "parity-scale-codec", + "scale-info", + "sp-runtime", + "sp-std 4.0.0", +] + +[[package]] +name = "sp-state-machine" +version = "0.12.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "hash-db", + "log", + "num-traits", + "parity-scale-codec", + "parking_lot", + "rand 0.7.3", + "smallvec", + "sp-core", + "sp-externalities", + "sp-panic-handler", + "sp-std 4.0.0", + "sp-trie", + "thiserror", + "tracing", + "trie-db", + "trie-root", +] + +[[package]] +name = "sp-std" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35391ea974fa5ee869cb094d5b437688fbf3d8127d64d1b9fed5822a1ed39b12" + +[[package]] +name = "sp-std" +version = "4.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" + +[[package]] +name = "sp-storage" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "impl-serde", + "parity-scale-codec", + "ref-cast", + "serde", + "sp-debug-derive", + "sp-std 4.0.0", +] + +[[package]] +name = "sp-tracing" +version = "5.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "parity-scale-codec", + "sp-std 4.0.0", + "tracing", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "sp-trie" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "hash-db", + "memory-db", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-std 4.0.0", + "thiserror", + "trie-db", + "trie-root", +] + +[[package]] +name = "sp-version" +version = "5.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "impl-serde", + "parity-scale-codec", + "parity-wasm 0.42.2", + "scale-info", + "serde", + "sp-core-hashing-proc-macro", + "sp-runtime", + "sp-std 4.0.0", + "sp-version-proc-macro", + "thiserror", +] + +[[package]] +name = "sp-version-proc-macro" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "parity-scale-codec", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sp-wasm-interface" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "sp-std 4.0.0", + "wasmi", +] + +[[package]] +name = "ss58-registry" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a039906277e0d8db996cd9d1ef19278c10209d994ecfc1025ced16342873a17c" +dependencies = [ + "Inflector", + "num-format", + "proc-macro2", + "quote", + "serde", + "serde_json", + "unicode-xid", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "statrs" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05bdbb8e4e78216a85785a85d3ec3183144f98d0097b9281802c019bb07a6f05" +dependencies = [ + "approx", + "lazy_static", + "nalgebra", + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "strum" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cae14b91c7d11c9a851d3fbc80a963198998c2a64eec840477fa92d8ce9b70bb" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bb0dc7ee9c15cea6199cde9a127fa16a4c5819af85395457ad72d68edc85a38" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "substrate-bip39" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49eee6965196b32f882dd2ee85a92b1dbead41b04e53907f269de3b0dc04733c" +dependencies = [ + "hmac 0.11.0", + "pbkdf2 0.8.0", + "schnorrkel", + "sha2 0.9.9", + "zeroize", +] + +[[package]] +name = "substrate-wasm-builder" +version = "5.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "ansi_term", + "build-helper", + "cargo_metadata", + "sp-maybe-compressed-blob", + "strum", + "tempfile", + "toml", + "walkdir", + "wasm-gc-api", +] + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "subtle-encoding" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcb1ed7b8330c5eed5441052651dd7a12c75e2ed88f2ec024ae1fa3a5e59945" +dependencies = [ + "zeroize", +] + +[[package]] +name = "syn" +version = "1.0.97" +source = "git+https://github.com/DaviRain-Su/syn.git?branch=branch-1.0.97#17682c67bd1c4de8662fda06a3254231dfeed86c" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if 1.0.0", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "tendermint" +version = "0.23.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ca881fa4dedd2b46334f13be7fbc8cc1549ba4be5a833fe4e73d1a1baaf7949" +dependencies = [ + "async-trait", + "bytes", + "ed25519", + "ed25519-dalek", + "flex-error", + "futures", + "num-traits", + "once_cell", + "prost 0.10.4", + "prost-types 0.10.1", + "serde", + "serde_bytes", + "serde_json", + "serde_repr", + "sha2 0.9.9", + "signature", + "subtle", + "subtle-encoding", + "tendermint-proto", + "time 0.3.11", + "zeroize", +] + +[[package]] +name = "tendermint-light-client-verifier" +version = "0.23.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ae030a759b89cca84860d497d4d4e491615d8a9243cc04c61cd89335ba9b593" +dependencies = [ + "derive_more", + "flex-error", + "serde", + "tendermint", + "time 0.3.11", +] + +[[package]] +name = "tendermint-proto" +version = "0.23.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71f925d74903f4abbdc4af0110635a307b3cb05b175fdff4a7247c14a4d0874" +dependencies = [ + "bytes", + "flex-error", + "num-derive", + "num-traits", + "prost 0.10.4", + "prost-types 0.10.1", + "serde", + "serde_bytes", + "subtle-encoding", + "time 0.3.11", +] + +[[package]] +name = "tendermint-testgen" +version = "0.23.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442ede2d01e61466e515fd7f1d0aac7c3c86b3066535479caa86a43afb5e2e17" +dependencies = [ + "ed25519-dalek", + "gumdrop", + "serde", + "serde_json", + "simple-error", + "tempfile", + "tendermint", + "time 0.3.11", +] + +[[package]] +name = "thiserror" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "time" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" +dependencies = [ + "libc", + "num_threads", + "time-macros", +] + +[[package]] +name = "time-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" + +[[package]] +name = "tiny-bip39" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc59cb9dfc85bb312c3a78fd6aa8a8582e310b0fa885d5bb877f6dcc601839d" +dependencies = [ + "anyhow", + "hmac 0.8.1", + "once_cell", + "pbkdf2 0.4.0", + "rand 0.7.3", + "rustc-hash", + "sha2 0.9.9", + "thiserror", + "unicode-normalization", + "wasm-bindgen", + "zeroize", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + +[[package]] +name = "tracing" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" +dependencies = [ + "cfg-if 1.0.0", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" +dependencies = [ + "ansi_term", + "chrono", + "lazy_static", + "matchers", + "regex", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "trie-db" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d32d034c0d3db64b43c31de38e945f15b40cd4ca6d2dcfc26d4798ce8de4ab83" +dependencies = [ + "hash-db", + "hashbrown 0.12.3", + "log", + "rustc-hex", + "smallvec", +] + +[[package]] +name = "trie-root" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a36c5ca3911ed3c9a5416ee6c679042064b93fc637ded67e25f92e68d783891" +dependencies = [ + "hash-db", +] + +[[package]] +name = "tt-call" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e66dcbec4290c69dd03c57e76c2469ea5c7ce109c6dd4351c13055cf71ea055" + +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if 1.0.0", + "digest 0.10.3", + "rand 0.8.5", + "static_assertions", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "uint" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-ident" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" + +[[package]] +name = "unicode-normalization" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" + +[[package]] +name = "unicode-xid" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" + +[[package]] +name = "wasm-gc-api" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0c32691b6c7e6c14e7f8fd55361a9088b507aa49620fcd06c09b3a1082186b9" +dependencies = [ + "log", + "parity-wasm 0.32.0", + "rustc-demangle", +] + +[[package]] +name = "wasmi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca00c5147c319a8ec91ec1a0edbec31e566ce2c9cc93b3f9bb86a9efd0eb795d" +dependencies = [ + "downcast-rs", + "libc", + "memory_units", + "num-rational 0.2.4", + "num-traits", + "parity-wasm 0.42.2", + "wasmi-validation", +] + +[[package]] +name = "wasmi-validation" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "165343ecd6c018fc09ebcae280752702c9a2ef3e6f8d02f1cfcbdb53ef6d7937" +dependencies = [ + "parity-wasm 0.42.2", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +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-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[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 = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "wyz" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" +dependencies = [ + "tap", +] + +[[package]] +name = "zeroize" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zstd" +version = "0.9.2+zstd.1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2390ea1bf6c038c39674f22d95f0564725fc06034a47129179810b2fc58caa54" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "4.1.3+zstd.1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e99d81b99fb3c2c2c794e3fe56c305c63d5173a16a46b5850b07c935ffc7db79" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "1.6.2+zstd.1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2daf2f248d9ea44454bfcb2516534e8b8ad2fc91bf818a1885495fc42bc8ac9f" +dependencies = [ + "cc", + "libc", +] diff --git a/frame/ibc/Cargo.toml b/frame/ibc/Cargo.toml new file mode 100644 index 0000000000000..c4a1389ce4069 --- /dev/null +++ b/frame/ibc/Cargo.toml @@ -0,0 +1,84 @@ +[package] +name = 'pallet-ibc' +version = "3.0.0-pre.0" +authors = ['Octopus Network '] +edition = '2021' +license = "Apache-2.0" +homepage = "https://oct.network" +repository = "https://github.com/octopus-network/substrate-ibc/" +description = "An IBC implementation on Substrate." +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +# [build-dependencies.substrate-wasm-builder] +# branch = 'polkadot-v0.9.18' +# git = 'https://github.com/paritytech/substrate.git' + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.2", default-features = false, features = ["derive"] } +log = { version = "0.4.0", default-features = false } +prost-types = { version = "0.11", default-features = false } +prost = { version = "0.11", default-features = false } +serde_json = { version = "1.0", default-features = false } +serde = { version = "1.0", default-features = false } +flex-error = { version = "0.4.4", default-features = false } +hex = {version = "0.4.0", default-features = false } + +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } +sp-core = { version = "6.0.0", path = "../../primitives/core" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-tracing = { version = "5.0.0", default-features = false, path = "../../primitives/tracing" } +time = { version = "0.3.11", features = ["macros","parsing"], default-features = false} + +ibc = { version = "0.18.0", default-features = false } +ibc-proto = { version = "0.20.0", default-features = false } +tendermint-proto = { version = "=0.23.9", default-features = false } +# beefy-light-client = { git = "https://github.com/octopus-network/beefy-light-client.git", branch="main", default-features = false } + +frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } + +[dev-dependencies] +hex = '0.4.0' +sha2 = '0.10.2' +serde = { version = "1.0" } +ibc = { version = "0.18.0", features = ["mocks"] } +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +sp-keyring = { version = "6.0.0", default-features = false, path = "../../primitives/keyring" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +chrono = "0.4.19" + +[features] +default = ['std'] +std = [ + 'codec/std', + 'log/std', + "scale-info/std", + 'frame-benchmarking/std', + 'frame-support/std', + 'frame-system/std', + 'sp-core/std', + 'sp-runtime/std', + 'sp-std/std', + 'sp-io/std', + 'sp-tracing/std', + 'prost-types/std', + 'prost/std', + 'ibc/std', + 'ibc-proto/std', + # 'beefy-light-client/std', + 'serde_json/std', + 'serde/std', + 'flex-error/std', + 'hex/std', + 'time/std', +] +runtime-benchmarks = ["frame-benchmarking"] +try-runtime = ["frame-support/try-runtime"] \ No newline at end of file diff --git a/frame/ibc/LICENSE b/frame/ibc/LICENSE new file mode 100644 index 0000000000000..261eeb9e9f8b2 --- /dev/null +++ b/frame/ibc/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/frame/ibc/Makefile b/frame/ibc/Makefile new file mode 100644 index 0000000000000..622944ab4571e --- /dev/null +++ b/frame/ibc/Makefile @@ -0,0 +1,2 @@ +check: + cargo check --no-default-features --target=wasm32-unknown-unknown \ No newline at end of file diff --git a/frame/ibc/README.md b/frame/ibc/README.md new file mode 100644 index 0000000000000..9451f9c1bafa8 --- /dev/null +++ b/frame/ibc/README.md @@ -0,0 +1,106 @@ +# Substrate IBC Pallet (work in progress) +[![crates.io](https://img.shields.io/crates/v/pallet-ibc.svg)](https://crates.io/crates/pallet-ibc) +[![Released API docs](https://docs.rs/pallet-ibc/badge.svg)](https://docs.rs/pallet-ibc) + +This project is [funded by Interchain Foundation](https://interchain-io.medium.com/ibc-on-substrate-with-cdot-a7025e521028). + +## Purpose + +This pallet implements the standard [IBC protocol](https://github.com/cosmos/ics). + +The goal of this pallet is to allow the blockchains built on Substrate to gain the ability to interact with other chains in a trustless way via IBC protocol. + +This project is currently in an early stage and will eventually be submitted to upstream. + +The pallet implements the chain specific logic of [ICS spec](https://github.com/cosmos/ibc/tree/51f0c9e8d8ebcbe6f7f023a8b80f65a8fab705e3/spec), and is integrated with [ibc-rs](https://github.com/informalsystems/ibc-rs), which implements the generic cross-chain logic in [ICS spec](https://github.com/cosmos/ibc/tree/51f0c9e8d8ebcbe6f7f023a8b80f65a8fab705e3/spec). + +The chain specific logic of the modules in ICS spec implemented: +* ics-002-client-semantics +* ics-003-connection-semantics +* ics-004-channel-and-packet-semantics +* ics-005-port-allocation +* ics-010-grandpa-client +* ics-018-relayer-algorithms +* ics-023-vector-commitments +* ics-024-host-requirements +* ics-025-handler-interface +* ics-026-routing-module + +Here is a [demo](~~https://github.com/cdot-network/ibc-demo~~) for showing how to utilize this pallet, which initializes a series of steps for cross-chain communication, from client creation to sending packet data. + +## Design Overview +The ibc pallet is integrated with the [modules in ibc-rs](https://github.com/octopus-network/ibc-rs/tree/b98094a57620d0b3d9f8d2caced09abfc14ab00f/modules), which implements the [ibc spec](https://github.com/cosmos/ibc/tree/7046202b645c65b1a2b7f293312bca5d651a13a4/spec) and leave the chain specific logics, which are named `???Readers` and `???Keepers`, to the ibc pallet. + +List of `Readers` and `Keepers` of on-chain storage: +* [ClientReader](https://github.com/octopus-network/ibc-rs/blob/b98094a57620d0b3d9f8d2caced09abfc14ab00f/modules/src/ics02_client/context.rs?_pjax=%23js-repo-pjax-container%2C%20div%5Bitemtype%3D%22http%3A%2F%2Fschema.org%2FSoftwareSourceCode%22%5D%20main%2C%20%5Bdata-pjax-container%5D#L14) & [ClientKeeper](https://github.com/octopus-network/ibc-rs/blob/b98094a57620d0b3d9f8d2caced09abfc14ab00f/modules/src/ics02_client/context.rs?_pjax=%23js-repo-pjax-container%2C%20div%5Bitemtype%3D%22http%3A%2F%2Fschema.org%2FSoftwareSourceCode%22%5D%20main%2C%20%5Bdata-pjax-container%5D#L29) +* [ConnectionReader](https://github.com/octopus-network/ibc-rs/blob/b98094a57620d0b3d9f8d2caced09abfc14ab00f/modules/src/ics03_connection/context.rs?_pjax=%23js-repo-pjax-container%2C%20div%5Bitemtype%3D%22http%3A%2F%2Fschema.org%2FSoftwareSourceCode%22%5D%20main%2C%20%5Bdata-pjax-container%5D#L17) & [ConnectionKeeper](https://github.com/octopus-network/ibc-rs/blob/b98094a57620d0b3d9f8d2caced09abfc14ab00f/modules/src/ics03_connection/context.rs?_pjax=%23js-repo-pjax-container%2C%20div%5Bitemtype%3D%22http%3A%2F%2Fschema.org%2FSoftwareSourceCode%22%5D%20main%2C%20%5Bdata-pjax-container%5D#L67) +* [ChannelReader](https://github.com/octopus-network/ibc-rs/blob/b98094a57620d0b3d9f8d2caced09abfc14ab00f/modules/src/ics04_channel/context.rs?_pjax=%23js-repo-pjax-container%2C%20div%5Bitemtype%3D%22http%3A%2F%2Fschema.org%2FSoftwareSourceCode%22%5D%20main%2C%20%5Bdata-pjax-container%5D#L20) & [ChannelKeeper](https://github.com/octopus-network/ibc-rs/blob/b98094a57620d0b3d9f8d2caced09abfc14ab00f/modules/src/ics04_channel/context.rs?_pjax=%23js-repo-pjax-container%2C%20div%5Bitemtype%3D%22http%3A%2F%2Fschema.org%2FSoftwareSourceCode%22%5D%20main%2C%20%5Bdata-pjax-container%5D#L82) + +## Installation +Thie section describe the modification of your substrate chain needed to integrate pallet ibc. + +### `Cargo.toml` +Specify some versions of ibc relevant crate +```toml +[patch.crates-io] +tendermint = { git = "https://github.com/informalsystems/tendermint-rs", branch = "v0.23.x" } +tendermint-rpc = { git = "https://github.com/informalsystems/tendermint-rs", branch = "v0.23.x" } +tendermint-proto = { git = "https://github.com/informalsystems/tendermint-rs", branch = "v0.23.x" } +tendermint-light-client = { git = "https://github.com/informalsystems/tendermint-rs", branch = "v0.23.x" } +tendermint-light-client-verifier = { git = "https://github.com/informalsystems/tendermint-rs", branch = "v0.23.x" } +tendermint-testgen = { git = "https://github.com/informalsystems/tendermint-rs", branch = "v0.23.x" } +``` + +#### Runtime's `Cargo.toml` +To add this pallet to your runtime, include the following to your runtime's `Cargo.toml` file: + +```TOML +pallet-ibc = { git = "https://github.com/octopus-network/substrate-ibc", branch = "master", default-features = false} +``` + +and update your runtime's `std` feature to include this pallet: + +```TOML +std = [ + # --snip-- + "pallet-ibc/std", +] +``` + +### Runtime `lib.rs` +A custom structure that implements the pallet_ibc::ModuleCallbacks must be defined to dispatch messages to receiving module. +```rust +pub struct ModuleCallbacksImpl; + +impl pallet_ibc::ModuleCallbacks for ModuleCallbacksImpl {} + +impl pallet_ibc::Config for Runtime { + type Event = Event; + type ModuleCallbacks = ModuleCallbacksImpl; + type TimeProvider = pallet_timestamp::Pallet; +} +``` + +You should include it in your `construct_runtime!` macro: + +```rust +Ibc: pallet_ibc::{Pallet, Call, Storage, Event}, +``` + +### Genesis Configuration + +This pallet does not have any genesis configuration. + +## How to Interact with the Pallet +The Hermes (IBC Relayer CLI) offers commands to send reqeusts to pallet ibc to trigger the standard ibc communications defined in [ibc spce](https://github.com/cosmos/ibc/tree/ee71d0640c23ec4e05e924f52f557b5e06c1d82f/spec). +[Hermes Command List](https://hermes.informal.systems/commands/raw/index.html). + +## Reference Docs + +You can view the reference docs for this pallet by running: + +``` +cargo doc --open +``` + +or by visiting this site: https://docs.rs/pallet-ibc \ No newline at end of file diff --git a/frame/ibc/src/benchmarking.rs b/frame/ibc/src/benchmarking.rs new file mode 100644 index 0000000000000..741d94d17cf34 --- /dev/null +++ b/frame/ibc/src/benchmarking.rs @@ -0,0 +1,24 @@ +//! Benchmarking setup for pallet-template + +// use super::*; + +// #[allow(unused)] +// use crate::Pallet as Template; +// use frame_benchmarking::{benchmarks, impl_benchmark_test_suite, whitelisted_caller}; +// use frame_system::RawOrigin; + +// benchmarks! { +// deliver { +// let b in 0..u8::MAX.into(); +// let m in 0..u8::MAX.into(); +// let mut v1: Vec = Vec::new(); +// let mut v2: Vec = Vec::new(); +// for i in 0..m { +// v1.push(b.try_into().unwrap()); +// v2.push(b.try_into().unwrap()); +// } +// let any = Any {type_url: v1, value: v2}; +// let caller = whitelisted_caller(); +// }: deliver(RawOrigin::Signed(caller), vec![any], b.try_into().unwrap()) +// } +// impl_benchmark_test_suite!(Template, crate::mock::new_test_ext(), crate::mock::Test,); diff --git a/frame/ibc/src/context.rs b/frame/ibc/src/context.rs new file mode 100644 index 0000000000000..ccff087884e6a --- /dev/null +++ b/frame/ibc/src/context.rs @@ -0,0 +1,48 @@ +use crate::*; +use alloc::{ + borrow::{Borrow, Cow, ToOwned}, + collections::BTreeMap, + sync::Arc, +}; +use scale_info::TypeInfo; + +use crate::module::applications::transfer::transfer_handle_callback::TransferModule; +use ibc::{ + applications::transfer::{context::Ics20Context, error::Error as ICS20Error, MODULE_ID_STR}, + core::{ + ics04_channel::{ + channel::{Counterparty, Order}, + error::Error as Ics04Error, + Version, + }, + ics24_host::identifier::{ChannelId, ConnectionId, PortId}, + ics26_routing::context::{ + Ics26Context, Module, ModuleId, ModuleOutputBuilder, RouterBuilder, + }, + }, +}; + +use crate::module::core::ics26_routing::{MockRouter, MockRouterBuilder}; +#[derive(Clone, Debug)] +pub struct Context { + pub _pd: PhantomData, + pub router: MockRouter, +} + +impl Context { + pub fn new() -> Self { + + let r = MockRouterBuilder::default() + .add_route(MODULE_ID_STR.parse().unwrap(), TransferModule(PhantomData::)) // register transfer Module + .unwrap() + .build(); + + Self { _pd: PhantomData::default(), router: r } + } +} + +impl Default for Context { + fn default() -> Self { + Self::new() + } +} diff --git a/frame/ibc/src/events.rs b/frame/ibc/src/events.rs new file mode 100644 index 0000000000000..e06a0eea128b8 --- /dev/null +++ b/frame/ibc/src/events.rs @@ -0,0 +1,339 @@ +use crate::*; +use core::borrow::Borrow; +use ibc::{core::ics26_routing, events::IbcEvent as RawIbcEvent}; + + +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct ModuleEvent { + pub kind: Vec, + pub module_name: ModuleId, + pub attributes: Vec, +} + +impl From for ModuleEvent { + fn from(module_event: ibc::events::ModuleEvent) -> Self { + Self { + kind: module_event.kind.as_bytes().to_vec(), + module_name: module_event.module_name.into(), + attributes: module_event.attributes.into_iter().map(|event| event.into()).collect(), + } + } +} + +impl From for ibc::events::ModuleEvent { + fn from(module_event: ModuleEvent) -> Self { + Self { + kind: String::from_utf8(module_event.kind).expect("never failed"), + module_name: module_event.module_name.into(), + attributes: module_event.attributes.into_iter().map(|event| event.into()).collect(), + } + } +} + +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct ModuleId(pub Vec); + +impl From for ModuleId { + fn from(module_id: ics26_routing::context::ModuleId) -> Self { + Self(format!("{}", module_id).as_bytes().to_vec()) + } +} + +impl From for ics26_routing::context::ModuleId { + fn from(module_id: ModuleId) -> Self { + ics26_routing::context::ModuleId::from_str(&String::from_utf8(module_id.0).unwrap()) + .expect("should never fiaild") + } +} + +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct ModuleEventAttribute { + pub key: Vec, + pub value: Vec, +} + +impl From for ModuleEventAttribute { + fn from(module_event_attribute: ibc::events::ModuleEventAttribute) -> Self { + Self { + key: module_event_attribute.key.as_bytes().to_vec(), + value: module_event_attribute.value.as_bytes().to_vec(), + } + } +} + +impl From for ibc::events::ModuleEventAttribute { + fn from(module_event_attribute: ModuleEventAttribute) -> Self { + Self { + key: String::from_utf8(module_event_attribute.key).expect("should not be filled"), + value: String::from_utf8(module_event_attribute.value).expect("should not be filled"), + } + } +} + +impl From for Event { + fn from(value: RawIbcEvent) -> Self { + match value { + RawIbcEvent::NewBlock(value) => Event::::NewBlock { height: value.height.into() }, + RawIbcEvent::CreateClient(value) => { + let height = value.0.height; + let client_id = value.0.client_id; + let client_type = value.0.client_type; + let consensus_height = value.0.consensus_height; + Event::::CreateClient { + height: height.into(), + client_id: client_id.into(), + client_type: client_type.into(), + consensus_height: consensus_height.into(), + } + }, + RawIbcEvent::UpdateClient(value) => { + let height = value.common.height; + let client_id = value.common.client_id; + let client_type = value.common.client_type; + let consensus_height = value.common.consensus_height; + Event::::UpdateClient { + height: height.into(), + client_id: client_id.into(), + client_type: client_type.into(), + consensus_height: consensus_height.into(), + } + }, + // Upgrade client events are not currently being used + RawIbcEvent::UpgradeClient(value) => { + let height = value.0.height; + let client_id = value.0.client_id; + let client_type = value.0.client_type; + let consensus_height = value.0.consensus_height; + Event::::UpgradeClient { + height: height.into(), + client_id: client_id.into(), + client_type: client_type.into(), + consensus_height: consensus_height.into(), + } + }, + RawIbcEvent::ClientMisbehaviour(value) => { + let height = value.0.height; + let client_id = value.0.client_id; + let client_type = value.0.client_type; + let consensus_height = value.0.consensus_height; + Event::::ClientMisbehaviour { + height: height.into(), + client_id: client_id.into(), + client_type: client_type.into(), + consensus_height: consensus_height.into(), + } + }, + RawIbcEvent::OpenInitConnection(value) => { + let height = value.attributes().height; + let connection_id: Option = + value.attributes().connection_id.clone().map(|val| val.into()); + let client_id = value.attributes().client_id.clone(); + let counterparty_connection_id: Option = + value.attributes().counterparty_connection_id.clone().map(|val| val.into()); + + let counterparty_client_id = value.attributes().counterparty_client_id.clone(); + Event::::OpenInitConnection { + height: height.into(), + connection_id, + client_id: client_id.into(), + counterparty_connection_id, + counterparty_client_id: counterparty_client_id.into(), + } + }, + RawIbcEvent::OpenTryConnection(value) => { + let height = value.attributes().height; + let connection_id: Option = + value.attributes().connection_id.clone().map(|val| val.into()); + let client_id = value.attributes().client_id.clone(); + let counterparty_connection_id: Option = + value.attributes().counterparty_connection_id.clone().map(|val| val.into()); + + let counterparty_client_id = value.attributes().counterparty_client_id.clone(); + Event::::OpenTryConnection { + height: height.into(), + connection_id, + client_id: client_id.into(), + counterparty_connection_id, + counterparty_client_id: counterparty_client_id.into(), + } + }, + RawIbcEvent::OpenAckConnection(value) => { + let height = value.attributes().height; + let connection_id: Option = + value.attributes().connection_id.clone().map(|val| val.into()); + let client_id = value.attributes().client_id.clone(); + let counterparty_connection_id: Option = + value.attributes().counterparty_connection_id.clone().map(|val| val.into()); + + let counterparty_client_id = value.attributes().counterparty_client_id.clone(); + Event::::OpenAckConnection { + height: height.into(), + connection_id, + client_id: client_id.into(), + counterparty_connection_id, + counterparty_client_id: counterparty_client_id.into(), + } + }, + RawIbcEvent::OpenConfirmConnection(value) => { + let height = value.attributes().height; + let connection_id: Option = + value.attributes().connection_id.clone().map(|val| val.into()); + let client_id = value.attributes().client_id.clone(); + let counterparty_connection_id: Option = + value.attributes().counterparty_connection_id.clone().map(|val| val.into()); + + let counterparty_client_id = value.attributes().counterparty_client_id.clone(); + Event::::OpenConfirmConnection { + height: height.into(), + connection_id, + client_id: client_id.into(), + counterparty_connection_id, + counterparty_client_id: counterparty_client_id.into(), + } + }, + RawIbcEvent::OpenInitChannel(value) => { + let height = value.height; + let port_id = value.port_id.clone(); + let channel_id: Option = value.channel_id.clone().map(|val| val.into()); + let connection_id = value.connection_id.clone(); + let counterparty_port_id = value.counterparty_port_id.clone(); + let counterparty_channel_id: Option = + value.channel_id.map(|val| val.into()); + Event::::OpenInitChannel { + height: height.into(), + port_id: port_id.into(), + channel_id, + connection_id: connection_id.into(), + counterparty_port_id: counterparty_port_id.into(), + counterparty_channel_id, + } + }, + RawIbcEvent::OpenTryChannel(value) => { + let height = value.height; + let port_id = value.port_id.clone(); + let channel_id: Option = value.channel_id.clone().map(|val| val.into()); + let connection_id = value.connection_id.clone(); + let counterparty_port_id = value.counterparty_port_id.clone(); + let counterparty_channel_id: Option = + value.channel_id.map(|val| val.into()); + Event::::OpenTryChannel { + height: height.into(), + port_id: port_id.into(), + channel_id, + connection_id: connection_id.into(), + counterparty_port_id: counterparty_port_id.into(), + counterparty_channel_id, + } + }, + RawIbcEvent::OpenAckChannel(value) => { + let height = value.height; + let port_id = value.port_id.clone(); + let channel_id: Option = value.channel_id.clone().map(|val| val.into()); + let connection_id = value.connection_id.clone(); + let counterparty_port_id = value.counterparty_port_id.clone(); + let counterparty_channel_id: Option = + value.channel_id.map(|val| val.into()); + Event::::OpenAckChannel { + height: height.into(), + port_id: port_id.into(), + channel_id, + connection_id: connection_id.into(), + counterparty_port_id: counterparty_port_id.into(), + counterparty_channel_id, + } + }, + RawIbcEvent::OpenConfirmChannel(value) => { + let height = value.height; + let port_id = value.port_id.clone(); + let channel_id: Option = value.channel_id.clone().map(|val| val.into()); + let connection_id = value.connection_id.clone(); + let counterparty_port_id = value.counterparty_port_id; + let counterparty_channel_id: Option = + value.channel_id.map(|val| val.into()); + Event::::OpenConfirmChannel { + height: height.into(), + port_id: port_id.into(), + channel_id, + connection_id: connection_id.into(), + counterparty_port_id: counterparty_port_id.into(), + counterparty_channel_id, + } + }, + RawIbcEvent::CloseInitChannel(value) => { + let height = value.height; + let port_id = value.port_id.clone(); + let channel_id: Option = Some(value.channel_id.into()); + let connection_id = value.connection_id.clone(); + let counterparty_port_id = value.counterparty_port_id; + let counterparty_channel_id: Option = + value.counterparty_channel_id.map(|val| val.into()); + Event::::CloseInitChannel { + height: height.into(), + port_id: port_id.into(), + channel_id, + connection_id: connection_id.into(), + counterparty_port_id: counterparty_port_id.into(), + counterparty_channel_id, + } + }, + RawIbcEvent::CloseConfirmChannel(value) => { + let height = value.height; + let port_id = value.port_id.clone(); + let channel_id: Option = value.channel_id.clone().map(|val| val.into()); + let connection_id = value.connection_id.clone(); + let counterparty_port_id = value.counterparty_port_id.clone(); + let counterparty_channel_id: Option = + value.channel_id.map(|val| val.into()); + Event::::CloseConfirmChannel { + height: height.into(), + port_id: port_id.into(), + channel_id, + connection_id: connection_id.into(), + counterparty_port_id: counterparty_port_id.into(), + counterparty_channel_id, + } + }, + RawIbcEvent::SendPacket(value) => { + let height = value.height; + let packet = value.packet; + Event::::SendPacket { height: height.into(), packet: packet.into() } + }, + RawIbcEvent::ReceivePacket(value) => { + let height = value.height; + let packet = value.packet; + Event::::ReceivePacket { height: height.into(), packet: packet.into() } + }, + RawIbcEvent::WriteAcknowledgement(value) => { + let height = value.height; + let packet = value.packet; + let ack = value.ack; + + Event::::WriteAcknowledgement { height: height.into(), packet: packet.into(), ack } + }, + RawIbcEvent::AcknowledgePacket(value) => { + let height = value.height; + let packet = value.packet; + Event::::AcknowledgePacket { height: height.into(), packet: packet.into() } + }, + RawIbcEvent::TimeoutPacket(value) => { + let height = value.height; + let packet = value.packet; + Event::::TimeoutPacket { height: height.into(), packet: packet.into() } + }, + RawIbcEvent::TimeoutOnClosePacket(value) => { + let height = value.height; + let packet = value.packet; + Event::::TimeoutOnClosePacket { height: height.into(), packet: packet.into() } + }, + RawIbcEvent::AppModule(value) => Event::::AppModule(value.into()), + RawIbcEvent::ChainError(value) => Event::::ChainError(value.as_bytes().to_vec()), + } + } +} + +// impl From> for Event { +// fn from(events: Vec) -> Self { +// let events: Vec> = events.into_iter().map(|ev| ev.into()).collect(); +// Self::IbcEvents { events } +// } +// } diff --git a/frame/ibc/src/lib.rs b/frame/ibc/src/lib.rs new file mode 100644 index 0000000000000..fc403024c74c0 --- /dev/null +++ b/frame/ibc/src/lib.rs @@ -0,0 +1,679 @@ +#![cfg_attr(not(feature = "std"), no_std)] +// todo need in future to remove +#![allow(unreachable_code)] +#![allow(unreachable_patterns)] +#![allow(clippy::type_complexity)] +#![allow(non_camel_case_types)] +#![allow(dead_code)] +#![allow(unused_assignments)] +#![allow(unused_imports)] +#![allow(unused_variables)] +#![allow(clippy::too_many_arguments)] + +//! # Overview +//! +//! The goal of this pallet is to allow the blockchains built on Substrate to gain the ability to +//! interact with other chains in a trustees way via IBC protocol +//! +//! The pallet implements the chain specific logic of [ICS spec](https://github.com/cosmos/ibc/tree/ee71d0640c23ec4e05e924f52f557b5e06c1d82f), +//! and is integrated with [ibc-rs](https://github.com/informalsystems/ibc-rs), +//! which implements the generic cross-chain logic in [ICS spec](https://github.com/cosmos/ibc/tree/ee71d0640c23ec4e05e924f52f557b5e06c1d82f). + +extern crate alloc; +extern crate core; + +pub use pallet::*; + +use alloc::{ + format, + string::{String, ToString}, +}; +use core::{marker::PhantomData, str::FromStr}; +use scale_info::{prelude::vec, TypeInfo}; +use serde::{Deserialize, Serialize}; + +use codec::{Codec, Decode, Encode}; + +use frame_support::{ + sp_runtime::traits::{AtLeast32BitUnsigned, CheckedConversion}, + sp_std::fmt::Debug, + traits::{tokens::fungibles, Currency, ExistenceRequirement::AllowDeath}, + PalletId, +}; +use frame_system::ensure_signed; +use sp_runtime::{traits::AccountIdConversion, DispatchError, RuntimeDebug, TypeId}; +use sp_std::prelude::*; + +use ibc::{ + applications::transfer::msgs::transfer::MsgTransfer, + core::{ + ics02_client::{client_state::AnyClientState, height}, + ics04_channel::timeout::TimeoutHeight, + ics24_host::identifier::{self, ChainId as ICS24ChainId, ChannelId as IbcChannelId}, + ics26_routing::msgs::Ics26Envelope, + }, + timestamp, + tx_msg::Msg, +}; + +use tendermint_proto::Protobuf; + +pub mod context; +pub mod events; +pub mod module; +pub mod traits; +pub mod utils; + +use crate::{context::Context, traits::AssetIdAndNameProvider}; + +use crate::module::{ + core::ics24_host::{ + ChannelId, ClientId, ClientType, ConnectionId, Height, Packet, PortId, Timestamp, + }, +}; + +pub const LOG_TARGET: &str = "runtime::pallet-ibc"; +pub const REVISION_NUMBER: u64 = 8888; + +type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; + +/// A struct corresponds to `Any` in crate "prost-types", used in ibc-rs. +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct Any { + pub type_url: Vec, + pub value: Vec, +} + +impl From for Any { + fn from(any: ibc_proto::google::protobuf::Any) -> Self { + Self { type_url: any.type_url.as_bytes().to_vec(), value: any.value } + } +} + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use crate::{ + events::ModuleEvent, + module::{ + core::ics24_host::{ + ChannelId, ClientId, ClientType, ConnectionId, Height, Packet, PortId, Sequence, + Timestamp, + }, + }, + }; + use frame_support::{ + dispatch::DispatchResult, + pallet_prelude::*, + traits::{ + fungibles::{Inspect, Mutate, Transfer}, + UnixTime, + }, + }; + use frame_system::pallet_prelude::*; + use ibc::{ + applications::transfer::context::Ics20Context, + core::{ + ics02_client::client_consensus::AnyConsensusState, + ics04_channel::{ + channel::{Counterparty, Order}, + context::ChannelKeeper, + events::WriteAcknowledgement, + Version, + }, + ics24_host::{ + identifier::{ChannelId as IbcChannelId, PortId as IbcPortId}, + path::{ClientConsensusStatePath, ClientStatePath}, + }, + ics26_routing::error::Error as Ics26Error, + }, + events::IbcEvent, + handler::{HandlerOutput, HandlerOutputBuilder}, + signer::Signer, + }; + use sp_runtime::traits::IdentifyAccount; + use crate::module::applications::transfer::transfer_handle_callback::TransferModule; + + /// Configure the pallet by specifying the parameters and types on which it depends. + #[pallet::config] + pub trait Config: frame_system::Config + Sync + Send + Debug { + /// The overarching event type. + type Event: From> + IsType<::Event>; + + type TimeProvider: UnixTime; + + /// Currency type of the runtime + type Currency: Currency; + + type AssetId: Member + + Parameter + + AtLeast32BitUnsigned + + Codec + + Copy + + Debug + + Default + + MaybeSerializeDeserialize; + + type AssetBalance: Parameter + + Member + + AtLeast32BitUnsigned + + Codec + + Default + + From + + Into + + Copy + + MaybeSerializeDeserialize + + Debug; + + type Assets: Transfer + + Mutate + + Inspect; + + type AssetIdByName: AssetIdAndNameProvider; + + /// Account Id Conversion from SS58 string or hex string + type AccountIdConversion: TryFrom + + IdentifyAccount + + Clone + + PartialEq + + Debug; + + // config native token name + const NATIVE_TOKEN_NAME: &'static [u8]; + } + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + #[pallet::without_storage_info] + pub struct Pallet(_); + + #[pallet::storage] + /// ClientStatePath(client_id) => ClientState + pub type ClientStates = + StorageMap<_, Blake2_128Concat, Vec, Vec, ValueQuery>; + + #[pallet::storage] + /// (client_id, height) => timestamp + pub type ClientProcessedTimes = StorageDoubleMap< + _, + Blake2_128Concat, + Vec, + Blake2_128Concat, + Vec, + Vec, + ValueQuery, + >; + + #[pallet::storage] + /// (client_id, height) => host_height + pub type ClientProcessedHeights = StorageDoubleMap< + _, + Blake2_128Concat, + Vec, + Blake2_128Concat, + Vec, + Vec, + ValueQuery, + >; + + #[pallet::storage] + /// ClientConsensusStatePath(client_id, Height) => ConsensusState + pub type ConsensusStates = + StorageMap<_, Blake2_128Concat, Vec, Vec, ValueQuery>; + + #[pallet::storage] + /// ConnectionsPath(connection_id) => ConnectionEnd + pub type Connections = StorageMap<_, Blake2_128Concat, Vec, Vec, ValueQuery>; + + #[pallet::storage] + /// ChannelEndPath(port_id, channel_id) => ChannelEnd + pub type Channels = StorageMap<_, Blake2_128Concat, Vec, Vec, ValueQuery>; + + #[pallet::storage] + /// ConnectionsPath(connection_id) => Vec + pub type ChannelsConnection = + StorageMap<_, Blake2_128Concat, Vec, Vec>, ValueQuery>; + + #[pallet::storage] + /// SeqSendsPath(port_id, channel_id) => sequence + pub type NextSequenceSend = + StorageMap<_, Blake2_128Concat, Vec, u64, ValueQuery>; + + #[pallet::storage] + /// SeqRecvsPath(port_id, channel_id) => sequence + pub type NextSequenceRecv = + StorageMap<_, Blake2_128Concat, Vec, u64, ValueQuery>; + + #[pallet::storage] + /// SeqAcksPath(port_id, channel_id) => sequence + pub type NextSequenceAck = StorageMap<_, Blake2_128Concat, Vec, u64, ValueQuery>; + + #[pallet::storage] + /// AcksPath(port_id, channel_id, sequence) => hash of acknowledgement + pub type Acknowledgements = + StorageMap<_, Blake2_128Concat, Vec, Vec, ValueQuery>; + + #[pallet::storage] + /// ClientTypePath(client_id) => client_type + pub type Clients = StorageMap<_, Blake2_128Concat, Vec, Vec, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn client_counter)] + /// client counter + pub type ClientCounter = StorageValue<_, u64, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn connection_counter)] + /// connection counter + pub type ConnectionCounter = StorageValue<_, u64, ValueQuery>; + + #[pallet::storage] + /// channel counter + pub type ChannelCounter = StorageValue<_, u64, ValueQuery>; + + #[pallet::storage] + /// ClientConnectionsPath(client_id) => connection_id + pub type ConnectionClient = + StorageMap<_, Blake2_128Concat, Vec, Vec, ValueQuery>; + + #[pallet::storage] + /// ReceiptsPath(port_id, channel_id, sequence) => receipt + pub type PacketReceipt = + StorageMap<_, Blake2_128Concat, Vec, Vec, ValueQuery>; + + #[pallet::storage] + /// CommitmentsPath(port_id, channel_id, sequence) => hash of (timestamp, height, packet) + pub type PacketCommitment = + StorageMap<_, Blake2_128Concat, Vec, Vec, ValueQuery>; + + #[pallet::storage] + /// (height, port_id, channel_id, sequence) => send-packet event + pub type SendPacketEvent = StorageNMap< + _, + ( + NMapKey>, + NMapKey>, + NMapKey, + ), + Vec, + ValueQuery, + >; + + #[pallet::storage] + /// (port_id, channel_id, sequence) => writ ack event + pub type WriteAckPacketEvent = StorageNMap< + _, + ( + NMapKey>, + NMapKey>, + NMapKey, + ), + Vec, + ValueQuery, + >; + + #[pallet::storage] + /// store latest height + pub type LatestHeight = StorageValue<_, Vec, ValueQuery>; + + #[pallet::storage] + pub type OldHeight = StorageValue<_, u64, ValueQuery>; + + #[pallet::storage] + /// key-value asset id with asset name + pub type AssetIdByName = + StorageMap<_, Twox64Concat, Vec, T::AssetId, ValueQuery>; + + #[pallet::genesis_config] + pub struct GenesisConfig { + pub asset_id_by_name: Vec<(String, T::AssetId)>, + } + + #[cfg(feature = "std")] + impl Default for GenesisConfig { + fn default() -> Self { + Self { asset_id_by_name: Vec::new() } + } + } + + #[pallet::genesis_build] + impl GenesisBuild for GenesisConfig { + fn build(&self) { + for (token_id, id) in self.asset_id_by_name.iter() { + >::insert(token_id.as_bytes(), id); + } + } + } + + /// Substrate IBC event list + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// New block + NewBlock { height: Height }, + /// Client Created + CreateClient { + height: Height, + client_id: ClientId, + client_type: ClientType, + consensus_height: Height, + }, + /// Client updated + UpdateClient { + height: Height, + client_id: ClientId, + client_type: ClientType, + consensus_height: Height, + }, + /// Client upgraded + UpgradeClient { + height: Height, + client_id: ClientId, + client_type: ClientType, + consensus_height: Height, + }, + /// Client misbehaviour + ClientMisbehaviour { + height: Height, + client_id: ClientId, + client_type: ClientType, + consensus_height: Height, + }, + /// Connection open init + OpenInitConnection { + height: Height, + connection_id: Option, + client_id: ClientId, + counterparty_connection_id: Option, + counterparty_client_id: ClientId, + }, + /// Connection open try + OpenTryConnection { + height: Height, + connection_id: Option, + client_id: ClientId, + counterparty_connection_id: Option, + counterparty_client_id: ClientId, + }, + /// Connection open acknowledgement + OpenAckConnection { + height: Height, + connection_id: Option, + client_id: ClientId, + counterparty_connection_id: Option, + counterparty_client_id: ClientId, + }, + /// Connection open confirm + OpenConfirmConnection { + height: Height, + connection_id: Option, + client_id: ClientId, + counterparty_connection_id: Option, + counterparty_client_id: ClientId, + }, + /// Channel open init + OpenInitChannel { + height: Height, + port_id: PortId, + channel_id: Option, + connection_id: ConnectionId, + counterparty_port_id: PortId, + counterparty_channel_id: Option, + }, + /// Channel open try + OpenTryChannel { + height: Height, + port_id: PortId, + channel_id: Option, + connection_id: ConnectionId, + counterparty_port_id: PortId, + counterparty_channel_id: Option, + }, + /// Channel open acknowledgement + OpenAckChannel { + height: Height, + port_id: PortId, + channel_id: Option, + connection_id: ConnectionId, + counterparty_port_id: PortId, + counterparty_channel_id: Option, + }, + /// Channel open confirm + OpenConfirmChannel { + height: Height, + port_id: PortId, + channel_id: Option, + connection_id: ConnectionId, + counterparty_port_id: PortId, + counterparty_channel_id: Option, + }, + /// Channel close init + CloseInitChannel { + height: Height, + port_id: PortId, + channel_id: Option, + connection_id: ConnectionId, + counterparty_port_id: PortId, + counterparty_channel_id: Option, + }, + /// Channel close confirm + CloseConfirmChannel { + height: Height, + port_id: PortId, + channel_id: Option, + connection_id: ConnectionId, + counterparty_port_id: PortId, + counterparty_channel_id: Option, + }, + /// Send packet + SendPacket { height: Height, packet: Packet }, + /// Receive packet + ReceivePacket { height: Height, packet: Packet }, + /// WriteAcknowledgement packet + WriteAcknowledgement{ height: Height, packet: Packet, ack: Vec }, + /// Acknowledgements packet + AcknowledgePacket { height: Height, packet: Packet }, + /// Timeout packet + TimeoutPacket { height: Height, packet: Packet }, + /// TimoutOnClose packet + TimeoutOnClosePacket { height: Height, packet: Packet }, + /// Empty + Empty(Vec), + /// Chain Error + ChainError(Vec), + /// App Module + AppModule(ModuleEvent), + /// transfer native token + TransferNativeToken(T::AccountIdConversion, T::AccountIdConversion, BalanceOf), + /// transfer no native token + TransferNoNativeToken( + T::AccountIdConversion, + T::AccountIdConversion, + ::AssetBalance, + ), + /// Burn cross chain token + BurnToken(T::AssetId, T::AccountIdConversion, T::AssetBalance), + /// Mint chairperson token + MintToken(T::AssetId, T::AccountIdConversion, T::AssetBalance), + } + + /// Errors in MMR verification informing users that something went wrong. + #[pallet::error] + pub enum Error { + /// update the beefy light client failure! + UpdateBeefyLightClientFailure, + /// receive mmr root block number less than client_state.latest_commitment.block_number + ReceiveMmrRootBlockNumberLessThanClientStateLatestCommitmentBlockNumber, + /// client id not found + ClientIdNotFound, + /// Encode error + InvalidEncode, + /// Decode Error + InvalidDecode, + /// FromUtf8Error + InvalidFromUtf8, + /// invalid signed_commitment + InvalidSignedCommitment, + /// empty latest_commitment + EmptyLatestCommitment, + /// invalid token id + InvalidTokenId, + /// wrong assert id + WrongAssetId, + // Parser Msg Transfer Error + ParserMsgTransferError, + } + + /// Dispatchable functions allows users to interact with the pallet and invoke state changes. + /// These functions materialize as "extrinsic", which are often compared to transactions. + /// Dispatch able functions must be annotated with a weight and must return a DispatchResult. + #[pallet::call] + impl Pallet { + /// This function acts as an entry for all of the IBC request(except MMR root update). + /// I.e., create clients, update clients, handshakes to create channels, ...etc + #[pallet::weight(0)] + pub fn deliver(origin: OriginFor, messages: Vec) -> DispatchResultWithPostInfo { + sp_tracing::within_span!( + sp_tracing::Level::TRACE, "deliver"; + { + let _sender = ensure_signed(origin)?; + let mut ctx = Context::::new(); + + let messages: Vec = messages + .into_iter() + .map(|message| ibc_proto::google::protobuf::Any { + type_url: String::from_utf8(message.type_url.clone()).unwrap(), + value: message.value, + }) + .collect(); + + log::trace!(target: LOG_TARGET, "received deliver : {:?} ", messages.iter().map(|message| message.type_url.clone()).collect::>()); + + for (_, message) in messages.clone().into_iter().enumerate() { + + match ibc::core::ics26_routing::handler::deliver(&mut ctx, message.clone()) { + Ok(ibc::core::ics26_routing::handler::MsgReceipt { events, log: _log}) => { + log::trace!(target: LOG_TARGET, "deliver events : {:?} ", events); + // deposit events about send packet event and ics20 transfer event + for event in events { + Self::deposit_event(event.clone().into()); + } + } + Err(error) => { + log::trace!(target: LOG_TARGET, "deliver error : {:?} ", error); + } + }; + } + + Ok(().into()) + }) + } + + #[pallet::weight(0)] + pub fn raw_transfer( + origin: OriginFor, + messages: Vec, + ) -> DispatchResultWithPostInfo { + let _sender = ensure_signed(origin)?; + let mut ctx = TransferModule(PhantomData::); + + let messages: Vec = messages + .into_iter() + .map(|message| ibc_proto::google::protobuf::Any { + type_url: String::from_utf8(message.type_url.clone()).unwrap(), + value: message.value, + }) + .collect(); + + log::trace!( + target: LOG_TARGET, + "raw_transfer : {:?} ", + messages.iter().map(|message| message.type_url.clone()).collect::>() + ); + + for message in messages { + let mut handle_out = HandlerOutputBuilder::new(); + let msg_transfer = MsgTransfer::try_from(message).map_err(|_|Error::::ParserMsgTransferError)?; + let result = ibc::applications::transfer::relay::send_transfer::send_transfer( + &mut ctx, + &mut handle_out, + msg_transfer, + ); + match result { + Ok(_value) => { + log::trace!(target: LOG_TARGET, "raw_transfer Successful!"); + } + Err(error) => { + log::trace!(target: LOG_TARGET, "raw_transfer Error : {:?} ", error); + } + } + + let HandlerOutput::<()> { result, log, events } = handle_out.with_result(()); + + log::trace!(target: LOG_TARGET, "raw_transfer log : {:?} ", log); + + // deposit events about send packet event and ics20 transfer event + for event in events { + Self::deposit_event(event.clone().into()); + } + } + + Ok(().into()) + } + + #[pallet::weight(0)] + pub fn delete_send_packet_event(origin: OriginFor) -> DispatchResultWithPostInfo { + log::trace!(target: LOG_TARGET, "delete_send_packet_event"); + + let _who = ensure_signed(origin)?; + >::drain(); + + Ok(().into()) + } + + #[pallet::weight(0)] + pub fn delete_ack_packet_event(origin: OriginFor) -> DispatchResultWithPostInfo { + log::trace!(target: LOG_TARGET, "delete_ack_packet_event"); + + let _who = ensure_signed(origin)?; + >::drain(); + + Ok(().into()) + } + } +} + + +impl AssetIdAndNameProvider for Pallet { + type Err = Error; + + fn try_get_asset_id(name: impl AsRef<[u8]>) -> Result<::AssetId, Self::Err> { + let asset_id = >::try_get(name.as_ref().to_vec()); + match asset_id { + Ok(id) => Ok(id), + _ => Err(Error::::InvalidTokenId), + } + } + + fn try_get_asset_name(asset_id: T::AssetId) -> Result, Self::Err> { + let token_id = >::iter().find(|p| p.1 == asset_id).map(|p| p.0); + match token_id { + Some(id) => Ok(id), + _ => Err(Error::::WrongAssetId), + } + } +} + +pub fn from_channel_id_to_vec(value: IbcChannelId) -> Vec { + value.to_string().as_bytes().to_vec() +} diff --git a/frame/ibc/src/mock.rs b/frame/ibc/src/mock.rs new file mode 100644 index 0000000000000..564c1f2ba7b5f --- /dev/null +++ b/frame/ibc/src/mock.rs @@ -0,0 +1,86 @@ +use crate as pallet_ibc; +use frame_support::parameter_types; +use frame_system as system; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentifyAccount, IdentityLookup, Verify}, + MultiSignature, +}; +use std::time::{Duration, Instant}; + +pub type Signature = MultiSignature; +pub(crate) type AccountId = <::Signer as IdentifyAccount>::AccountId; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +// Configure a mock runtime to test the pallet. +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Ibc: pallet_ibc::{Pallet, Call, Storage, Event}, + } +); + +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const SS58Prefix: u8 = 42; +} + +impl system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type Origin = Origin; + type Call = Call; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = SS58Prefix; + type OnSetCode = (); +} + +// The ModuleCallbacksImpl creates a static mapping of module index and callback functions of other +// modules. The module index is determined at the time of construct_runtime. For example, +// the index of TemplateModule is 8 in the current runtime. +// In the future, we should find a more dynamic way to create this mapping. +pub struct ModuleCallbacksImpl; +impl pallet_ibc::ModuleCallbacks for ModuleCallbacksImpl {} + +pub struct MockUnixTime; + +// maybe future to fix +impl frame_support::traits::UnixTime for MockUnixTime { + fn now() -> Duration { + Instant::now().elapsed() + } +} + +impl super::pallet::Config for Test { + type Event = Event; + type ModuleCallbacks = ModuleCallbacksImpl; + type TimeProvider = MockUnixTime; +} + +// Build genesis storage according to the mock runtime. +pub fn new_test_ext() -> sp_io::TestExternalities { + system::GenesisConfig::default().build_storage::().unwrap().into() +} diff --git a/frame/ibc/src/module/applications/mod.rs b/frame/ibc/src/module/applications/mod.rs new file mode 100644 index 0000000000000..014e52f2771e2 --- /dev/null +++ b/frame/ibc/src/module/applications/mod.rs @@ -0,0 +1 @@ +pub mod transfer; diff --git a/frame/ibc/src/module/applications/transfer/channel.rs b/frame/ibc/src/module/applications/transfer/channel.rs new file mode 100644 index 0000000000000..05cafb6e283f3 --- /dev/null +++ b/frame/ibc/src/module/applications/transfer/channel.rs @@ -0,0 +1,289 @@ +use crate::*; +use core::{str::FromStr, time::Duration}; +use log::{error, info, trace, warn}; +use super::transfer_handle_callback::TransferModule; + +use crate::context::Context; +use ibc::{ + core::{ + ics02_client::{ + client_consensus::AnyConsensusState, client_state::AnyClientState, + context::ClientReader, + }, + ics03_connection::{ + connection::ConnectionEnd, context::ConnectionReader, error::Error as ICS03Error, + }, + ics04_channel::{ + channel::ChannelEnd, + commitment::{ + AcknowledgementCommitment as IbcAcknowledgementCommitment, + PacketCommitment as IbcPacketCommitment, + }, + context::{ChannelKeeper, ChannelReader}, + error::Error as Ics04Error, + packet::{Receipt, Sequence}, + }, + ics05_port::{context::PortReader, error::Error as Ics05Error}, + ics24_host::{ + identifier::{ChannelId, ClientId, ConnectionId, PortId}, + path::{ + AcksPath, ChannelEndsPath, CommitmentsPath, ConnectionsPath, ReceiptsPath, + SeqAcksPath, SeqRecvsPath, SeqSendsPath, + }, + Path, + }, + ics26_routing::context::ModuleId, + }, + timestamp::Timestamp, + Height, +}; + +impl ChannelReader for TransferModule { + fn channel_end(&self, port_channel_id: &(PortId, ChannelId)) -> Result { + let connect = Context::::new(); + connect.channel_end(port_channel_id) + } + + fn connection_end(&self, connection_id: &ConnectionId) -> Result { + trace!(target:"runtime::pallet-ibc","in channel : [connection_end]"); + let connect = Context::::new(); + ChannelReader::connection_end(&connect,connection_id) + } + + /// Returns the `ChannelsConnection` for the given identifier `conn_id`. + fn connection_channels( + &self, + conn_id: &ConnectionId, + ) -> Result, Ics04Error> { + let connect = Context::::new(); + connect.connection_channels(conn_id) + } + + fn client_state(&self, client_id: &ClientId) -> Result { + + let connect = Context::::new(); + + ChannelReader::client_state(&connect, client_id) + } + + fn client_consensus_state( + &self, + client_id: &ClientId, + height: Height, + ) -> Result { + let connect = Context::::new(); + ChannelReader::client_consensus_state(&connect, client_id, height) + } + + fn get_next_sequence_send( + &self, + port_channel_id: &(PortId, ChannelId), + ) -> Result { + let connect = Context::::new(); + connect.get_next_sequence_send( port_channel_id) + } + + fn get_next_sequence_recv( + &self, + port_channel_id: &(PortId, ChannelId), + ) -> Result { + let connect = Context::::new(); + connect.get_next_sequence_recv( port_channel_id) + } + + fn get_next_sequence_ack( + &self, + port_channel_id: &(PortId, ChannelId), + ) -> Result { + let connect = Context::::new(); + connect.get_next_sequence_ack( port_channel_id) + } + + /// Returns the `PacketCommitment` for the given identifier `(PortId, ChannelId, Sequence)`. + fn get_packet_commitment( + &self, + key: &(PortId, ChannelId, Sequence), + ) -> Result { + let connect = Context::::new(); + connect.get_packet_commitment( key) + } + + fn get_packet_receipt( + &self, + key: &(PortId, ChannelId, Sequence), + ) -> Result { + let connect = Context::::new(); + connect.get_packet_receipt(key) + } + + /// Returns the `Acknowledgements` for the given identifier `(PortId, ChannelId, Sequence)`. + fn get_packet_acknowledgement( + &self, + key: &(PortId, ChannelId, Sequence), + ) -> Result { + let connect = Context::::new(); + connect.get_packet_acknowledgement( key) + } + + /// A hashing function for packet commitments + fn hash(&self, value: Vec) -> Vec { + let connect = Context::::new(); + connect.hash(value) + } + + /// Returns the current height of the local chain. + fn host_height(&self) -> Height { + let connect = Context::::new(); + ChannelReader::host_height(&connect) + } + + /// Returns the current timestamp of the local chain. + fn host_timestamp(&self) -> Timestamp { + let connect = Context::::new(); + ChannelReader::host_timestamp(&connect) + } + + /// Returns the `AnyConsensusState` for the given identifier `height`. + fn host_consensus_state(&self, height: Height) -> Result { + let connect = Context::::new(); + ConnectionReader::host_consensus_state(&connect, height).map_err(Ics04Error::ics03_connection) + } + + fn pending_host_consensus_state(&self) -> Result { + + let connect = Context::::new(); + ClientReader::pending_host_consensus_state(&connect) + .map_err(|e| Ics04Error::ics03_connection(ICS03Error::ics02_client(e))) + } + + /// Returns the `ClientProcessedTimes` for the given identifier `client_id` & `height`. + fn client_update_time( + &self, + client_id: &ClientId, + height: Height, + ) -> Result { + let connect = Context::::new(); + connect.client_update_time(client_id, height) + } + + fn client_update_height( + &self, + client_id: &ClientId, + height: Height, + ) -> Result { + let connect = Context::::new(); + connect.client_update_height(client_id, height) + } + + /// Returns a counter on the number of channel ids have been created thus far. + /// The value of this counter should increase only via method + /// `ChannelKeeper::increase_channel_counter`. + fn channel_counter(&self) -> Result { + let connect = Context::::new(); + connect.channel_counter() + } + + fn max_expected_time_per_block(&self) -> Duration { + let connect = Context::::new(); + connect.max_expected_time_per_block() + } +} + +impl ChannelKeeper for TransferModule { + fn store_packet_commitment( + &mut self, + key: (PortId, ChannelId, Sequence), + commitment: IbcPacketCommitment, + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + connect.store_packet_commitment(key, commitment) + } + + fn delete_packet_commitment( + &mut self, + key: (PortId, ChannelId, Sequence), + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + connect.delete_packet_commitment( key) + } + + fn store_packet_receipt( + &mut self, + key: (PortId, ChannelId, Sequence), + receipt: Receipt, + ) -> Result<(), Ics04Error> { + + let mut connect = Context::::new(); + connect.store_packet_receipt( key, receipt) + } + + fn store_packet_acknowledgement( + &mut self, + key: (PortId, ChannelId, Sequence), + ack_commitment: IbcAcknowledgementCommitment, + ) -> Result<(), Ics04Error> { + + let mut connect = Context::::new(); + + connect.store_packet_acknowledgement( key, ack_commitment) + } + + fn delete_packet_acknowledgement( + &mut self, + key: (PortId, ChannelId, Sequence), + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + connect.delete_packet_acknowledgement( key) + } + + fn store_connection_channels( + &mut self, + conn_id: ConnectionId, + port_channel_id: &(PortId, ChannelId), + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + connect.store_connection_channels( conn_id, port_channel_id) + } + + /// Stores the given channel_end at a path associated with the port_id and channel_id. + fn store_channel( + &mut self, + port_channel_id: (PortId, ChannelId), + channel_end: &ChannelEnd, + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + connect.store_channel( port_channel_id, channel_end) + } + + fn store_next_sequence_send( + &mut self, + port_channel_id: (PortId, ChannelId), + seq: Sequence, + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + connect.store_next_sequence_send(port_channel_id, seq) + } + + fn store_next_sequence_recv( + &mut self, + port_channel_id: (PortId, ChannelId), + seq: Sequence, + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + connect.store_next_sequence_recv( port_channel_id, seq) + } + + fn store_next_sequence_ack( + &mut self, + port_channel_id: (PortId, ChannelId), + seq: Sequence, + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + connect.store_next_sequence_ack(port_channel_id, seq) + } + + fn increase_channel_counter(&mut self) { + let mut connect = Context::::new(); + connect.increase_channel_counter() + } +} diff --git a/frame/ibc/src/module/applications/transfer/mod.rs b/frame/ibc/src/module/applications/transfer/mod.rs new file mode 100644 index 0000000000000..4bdc56939d15e --- /dev/null +++ b/frame/ibc/src/module/applications/transfer/mod.rs @@ -0,0 +1,259 @@ +pub mod transfer_handle_callback; +pub mod channel; + +use crate::{context::Context, *}; +use frame_support::traits::{ + fungibles::{Mutate, Transfer}, + ExistenceRequirement::AllowDeath, +}; +use log::{error, trace}; + +use crate::utils::get_channel_escrow_address; +use ibc::{ + applications::transfer::{ + context::{BankKeeper, Ics20Context, Ics20Keeper, Ics20Reader}, + error::Error as Ics20Error, + PrefixedCoin, PORT_ID_STR, + }, + core::ics24_host::identifier::PortId, + signer::Signer, +}; +use sp_runtime::{ + traits::{CheckedConversion, IdentifyAccount, Verify}, + MultiSignature, +}; + +use transfer_handle_callback::TransferModule; + +impl Ics20Keeper for TransferModule { + type AccountId = ::AccountId; +} + +impl BankKeeper for TransferModule { + type AccountId = ::AccountId; + + fn send_coins( + &mut self, + from: &Self::AccountId, + to: &Self::AccountId, + amt: &PrefixedCoin, + ) -> Result<(), Ics20Error> { + // let is_native_asset = amt.denom.trace_path().is_empty(); + // match is_native_asset { + // // transfer native token + // true => { + // let amount = amt.amount.as_u256().low_u128().checked_into().unwrap(); // TODO: FIX IN THE FUTURE + // let native_token_name = T::NATIVE_TOKEN_NAME; + // let ibc_token_name = amt.denom.base_denom().as_str().as_bytes(); + + // // assert native token name equal want to send ibc token name + // assert_eq!( + // native_token_name, ibc_token_name, + // "send ibc token name is not native token name" + // ); + + // >::transfer( + // &from.clone().into_account(), + // &to.clone().into_account(), + // amount, + // AllowDeath, + // ) + // .map_err(|error| { + // error!("❌ [send_coins] : Error: ({:?})", error); + // Ics20Error::invalid_token() + // })?; + + // // add emit transfer native token event + // Pallet::::deposit_event(Event::::TransferNativeToken( + // from.clone(), + // to.clone(), + // amount, + // )) + // }, + // // transfer non-native token + // false => { + // let amount = amt.amount.as_u256().low_u128().into(); + // let denom = amt.denom.base_denom().as_str(); + // // look cross chain asset have register in host chain + // match T::AssetIdByName::try_get_asset_id(denom) { + // Ok(token_id) => { + // >::transfer( + // token_id.into(), + // &from.clone().into_account(), + // &to.clone().into_account(), + // amount, + // true, + // ) + // .map_err(|error| { + // error!("❌ [send_coins] : Error: ({:?})", error); + // Ics20Error::invalid_token() + // })?; + + // // add emit transfer no native token event + // Pallet::::deposit_event(Event::::TransferNoNativeToken( + // from.clone(), + // to.clone(), + // amount, + // )); + // }, + // Err(_error) => { + // error!("❌ [send_coins]: denom: ({:?})", denom); + // return Err(Ics20Error::invalid_token()) + // }, + // } + // }, + // } + + Ok(()) + } + + fn mint_coins( + &mut self, + account: &Self::AccountId, + amt: &PrefixedCoin, + ) -> Result<(), Ics20Error> { + // let amount = amt.amount.as_u256().low_u128().into(); + // let denom = amt.denom.base_denom().as_str(); + // // look cross chain asset have register in host chain + // match T::AssetIdByName::try_get_asset_id(denom) { + // Ok(token_id) => { + // >::mint_into( + // token_id.into(), + // &account.clone().into_account(), + // amount, + // ) + // .map_err(|error| { + // error!("❌ [mint_coins] : Error: ({:?})", error); + // Ics20Error::invalid_token() + // })?; + + // // add mint token event + // Pallet::::deposit_event(Event::::MintToken( + // token_id, + // account.clone(), + // amount, + // )); + // }, + // Err(_error) => { + // error!("❌ [mint_coins]: denom: ({:?})", denom); + // return Err(Ics20Error::invalid_token()) + // }, + // } + Ok(()) + } + + fn burn_coins( + &mut self, + account: &Self::AccountId, + amt: &PrefixedCoin, + ) -> Result<(), Ics20Error> { + // let amount = amt.amount.as_u256().low_u128().into(); + // let denom = amt.denom.base_denom().as_str(); + // // look cross chain asset have register in host chain + // match T::AssetIdByName::try_get_asset_id(denom) { + // Ok(token_id) => { + // >::burn_from( + // token_id.into(), + // &account.clone().into_account(), + // amount, + // ) + // .map_err(|error| { + // error!("❌ [burn_coins] : Error: ({:?})", error); + // Ics20Error::invalid_token() + // })?; + + // // add burn token event + // Pallet::::deposit_event(Event::::BurnToken( + // token_id, + // account.clone(), + // amount, + // )); + // }, + // Err(_error) => { + // error!("❌ [burn_coins]: denom: ({:?})", denom); + // return Err(Ics20Error::invalid_token()) + // }, + // } + Ok(()) + } +} + +impl Ics20Reader for TransferModule { + type AccountId = ::AccountId; + + fn get_port(&self) -> Result { + PortId::from_str(PORT_ID_STR) + .map_err(|e| Ics20Error::invalid_port_id(PORT_ID_STR.to_string(), e)) + } + + fn get_channel_escrow_address( + &self, + port_id: &PortId, + channel_id: &IbcChannelId, + ) -> Result { + get_channel_escrow_address(port_id, channel_id)? + .try_into() + .map_err(|_| Ics20Error::parse_account_failure()) + } + + fn is_send_enabled(&self) -> bool { + // TODO(davirain), need according channelEnd def + true + } + + fn is_receive_enabled(&self) -> bool { + // TODO(davirain), need according channelEnd def + true + } +} + +impl Ics20Context for TransferModule { + type AccountId = ::AccountIdConversion; // Need Setting Account TODO(davirian) +} + +/// Alias to 512-bit hash when used in the context of a transaction signature on the chain. +pub type Signature = MultiSignature; + +/// Some way of identifying an account on the chain. We intentionally make it equivalent +/// to the public key of our transaction signing scheme. +pub type AccountId = <::Signer as IdentifyAccount>::AccountId; + +#[derive(Clone, Debug, PartialEq, TypeInfo, Encode, Decode)] +pub struct IbcAccount(AccountId); + +impl IdentifyAccount for IbcAccount { + type AccountId = AccountId; + fn into_account(self) -> Self::AccountId { + self.0 + } +} + +impl TryFrom for IbcAccount +where + AccountId: From<[u8; 32]>, +{ + type Error = &'static str; + + /// Convert a signer to an IBC account. + /// Only valid hex strings are supported for now. + fn try_from(signer: Signer) -> Result { + log::info!("Convert Signer : {:?} ", signer); + let acc_str = signer.as_ref(); + if acc_str.starts_with("0x") { + log::info!("Convert Signer 🎈: Successful! "); + match acc_str.strip_prefix("0x") { + Some(hex_string) => TryInto::<[u8; 32]>::try_into( + hex::decode(hex_string).map_err(|_| "Error decoding invalid hex string")?, + ) + .map_err(|_| "Invalid account id hex string") + .map(|acc| Self(acc.into())), + _ => Err("Signer does not hold a valid hex string"), + } + } + // Do SS58 decoding instead + else { + log::info!("Convert Signer ❌ : Failed! "); + Err("invalid ibc address or substrate address") + } + } +} diff --git a/frame/ibc/src/module/applications/transfer/transfer_handle_callback.rs b/frame/ibc/src/module/applications/transfer/transfer_handle_callback.rs new file mode 100644 index 0000000000000..e48b5d485cfc8 --- /dev/null +++ b/frame/ibc/src/module/applications/transfer/transfer_handle_callback.rs @@ -0,0 +1,170 @@ +use crate::*; + +use crate::utils::host_height; +use ibc::{ + applications::transfer::{acknowledgement::Acknowledgement, error::Error as Ics20Error}, + core::{ + ics04_channel::{ + channel::{Counterparty, Order}, + context::ChannelKeeper, + error::Error as Ics04Error, + msgs::acknowledgement::Acknowledgement as GenericAcknowledgement, + packet::{Packet as IbcPacket, PacketResult}, + Version, + }, + ics24_host::identifier::{ChannelId as IbcChannelId, ConnectionId, PortId}, + ics26_routing::context::{Module, ModuleOutputBuilder, OnRecvPacketAck}, + }, + events::IbcEvent, + signer::Signer, +}; + +#[derive(Debug)] +pub struct TransferModule(pub PhantomData); + +impl Module for TransferModule { + fn on_chan_open_init( + &mut self, + output: &mut ModuleOutputBuilder, + order: Order, + connection_hops: &[ConnectionId], + port_id: &PortId, + channel_id: &IbcChannelId, + counterparty: &Counterparty, + version: &Version, + ) -> Result<(), Ics04Error> { + + Ok(ibc::applications::transfer::context::on_chan_open_init( + self, + output, + order, + connection_hops, + port_id, + channel_id, + counterparty, + version, + ).unwrap()) + } + + fn on_chan_open_try( + &mut self, + output: &mut ModuleOutputBuilder, + order: Order, + connection_hops: &[ConnectionId], + port_id: &PortId, + channel_id: &IbcChannelId, + counterparty: &Counterparty, + version: &Version, + counterparty_version: &Version, + ) -> Result { + + Ok(ibc::applications::transfer::context::on_chan_open_try( + self, + output, + order, + connection_hops, + port_id, + channel_id, + counterparty, + version, + counterparty_version, + ) + .unwrap()) + } + + fn on_chan_open_ack( + &mut self, + output: &mut ModuleOutputBuilder, + port_id: &PortId, + channel_id: &IbcChannelId, + counterparty_version: &Version, + ) -> Result<(), Ics04Error> { + + Ok(ibc::applications::transfer::context::on_chan_open_ack( + self, + output, + port_id, + channel_id, + counterparty_version, + ) + .unwrap()) + } + + fn on_chan_open_confirm( + &mut self, + output: &mut ModuleOutputBuilder, + port_id: &PortId, + channel_id: &IbcChannelId, + ) -> Result<(), Ics04Error> { + + Ok(ibc::applications::transfer::context::on_chan_open_confirm( + self, output, port_id, channel_id, + ) + .unwrap()) + } + + fn on_chan_close_init( + &mut self, + output: &mut ModuleOutputBuilder, + port_id: &PortId, + channel_id: &IbcChannelId, + ) -> Result<(), Ics04Error> { + + Ok(ibc::applications::transfer::context::on_chan_close_init( + self, output, port_id, channel_id, + ) + .unwrap()) + } + + fn on_chan_close_confirm( + &mut self, + output: &mut ModuleOutputBuilder, + port_id: &PortId, + channel_id: &IbcChannelId, + ) -> Result<(), Ics04Error> { + + Ok(ibc::applications::transfer::context::on_chan_close_confirm( + self, output, port_id, channel_id, + ) + .unwrap()) + } + + fn on_recv_packet( + &self, + output: &mut ModuleOutputBuilder, + packet: &IbcPacket, + relayer: &Signer, + ) -> OnRecvPacketAck { + + ibc::applications::transfer::context::on_recv_packet(self, output, packet, relayer) + } + + fn on_acknowledgement_packet( + &mut self, + output: &mut ModuleOutputBuilder, + packet: &IbcPacket, + acknowledgement: &GenericAcknowledgement, + relayer: &Signer, + ) -> Result<(), Ics04Error> { + + Ok(ibc::applications::transfer::context::on_acknowledgement_packet( + self, + output, + packet, + acknowledgement, + relayer, + ) + .unwrap()) + } + + fn on_timeout_packet( + &mut self, + output: &mut ModuleOutputBuilder, + packet: &IbcPacket, + relayer: &Signer, + ) -> Result<(), Ics04Error> { + + Ok(ibc::applications::transfer::context::on_timeout_packet(self, output, packet, relayer) + .unwrap()) + } +} diff --git a/frame/ibc/src/module/clients/ics07_tendermint/header.rs b/frame/ibc/src/module/clients/ics07_tendermint/header.rs new file mode 100644 index 0000000000000..2bbfd4e9c3087 --- /dev/null +++ b/frame/ibc/src/module/clients/ics07_tendermint/header.rs @@ -0,0 +1,339 @@ +use time::PrimitiveDateTime; +use crate::module::core::ics24_host::Height; +use alloc::vec::Vec; +use codec::{Decode, Encode}; +use scale_info::TypeInfo; +use sp_runtime::RuntimeDebug; + + +/// Tendermint consensus header +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct Header { + pub signed_header: SignedHeader, // contains the commitment root + pub validator_set: ValidatorSet, // the validator set that signed Header + pub trusted_height: Height, // the height of a trusted header seen by client less than or equal to Header + // TODO(thane): Rename this to trusted_next_validator_set? + pub trusted_validator_set: ValidatorSet, // the last trusted validator set at trusted height +} + +/// Signed block headers +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +#[non_exhaustive] +pub struct SignedHeader { + /// Block header + pub header: block::Header, + /// Commit containing signatures for the header + pub commit: block::Commit, +} + + +/// Validator set contains a vector of validators +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct ValidatorSet { + validators: Vec, + proposer: Option, + total_voting_power: vote::Power, +} + +/// Validator information +// Todo: Remove address and make it into a function that generates it on the fly from pub_key. +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct Info { + /// Validator account address + pub address: account::Id, + + /// Validator public key + pub pub_key: PublicKey, + + /// Validator voting power + // Compatibility with genesis.json https://github.com/tendermint/tendermint/issues/5549 + pub power: vote::Power, + + /// Validator name + pub name: Option>, + + /// Validator proposer priority + pub proposer_priority: ProposerPriority, +} + +// Todo: Is there more knowledge/restrictions about proposerPriority? +/// Proposer priority +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct ProposerPriority(i64); + +mod vote { + use codec::{Decode, Encode}; + use scale_info::TypeInfo; + use sp_runtime::RuntimeDebug; + + /// Voting power + #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] + pub struct Power(u64); +} + +// Note:On the golang side this is generic in the sense that it could everything that implements +// github.com/tendermint/tendermint/crypto.PubKey +// While this is meant to be used with different key-types, it currently only uses a PubKeyEd25519 +// version. +// TODO: make this more generic + +// Warning: the custom serialization implemented here does not use TryFrom. +// it should only be used to read/write the priva_validator_key.json. +// All changes to the serialization should check both the JSON and protobuf conversions. +// Todo: Merge JSON serialization with #[serde(try_from = "RawPublicKey", into = "RawPublicKey)] +/// Public keys allowed in Tendermint protocols +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +#[non_exhaustive] +pub enum PublicKey { + /// Ed25519 keys + Ed25519, + + /// Secp256k1 keys + Secp256k1, +} + +mod account { + use codec::{Decode, Encode}; + use scale_info::TypeInfo; + use sp_runtime::RuntimeDebug; + + /// Size of an account ID in bytes + pub const LENGTH: usize = 20; + + /// Account IDs + #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] + pub struct Id([u8; LENGTH]); // JSON custom serialization for priv_validator_key.json + +} + +mod block { + use codec::{Decode, Encode}; + use scale_info::TypeInfo; + use sp_runtime::RuntimeDebug; + use time::PrimitiveDateTime; + use alloc::vec::Vec; + use crate::module::clients::ics07_tendermint::header::account; + + /// Block height for a particular chain (i.e. number of blocks created since + /// the chain began) + /// + /// A height of 0 represents a chain which has not yet produced a block. + #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] + pub struct Height(u64); + + mod chain { + use codec::{Decode, Encode}; + use alloc::vec::Vec; + use scale_info::TypeInfo; + use sp_runtime::RuntimeDebug; + + /// Chain identifier (e.g. 'gaia-9000') + #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] + pub struct Id(Vec); + } + + /// `Version` contains the protocol version for the blockchain and the + /// application. + /// + /// + #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] + pub struct Version { + /// Block version + pub block: u64, + + /// App version + pub app: u64, + } + + /// Tendermint timestamps + /// + /// A `Time` value is guaranteed to represent a valid `Timestamp` as defined + /// by Google's well-known protobuf type [specification]. Conversions and + /// operations that would result in exceeding `Timestamp`'s validity + /// range return an error or `None`. + /// + /// The string serialization format for `Time` is defined as an RFC 3339 + /// compliant string with the optional subsecond fraction part having + /// up to 9 digits and no trailing zeros, and the UTC offset denoted by Z. + /// This reproduces the behavior of Go's `time.RFC3339Nano` format. + /// + /// [specification]: https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Timestamp + // For memory efficiency, the inner member is `PrimitiveDateTime`, with assumed + // UTC offset. The `assume_utc` method is used to get the operational + // `OffsetDateTime` value. + #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] + // pub struct Time(PrimitiveDateTime); + pub struct Time; + + /// Block identifiers which contain two distinct Merkle roots of the block, + /// as well as the number of parts in the block. + /// + /// + /// + /// Default implementation is an empty Id as defined by the Go implementation in + /// . + /// + /// If the Hash is empty in BlockId, the BlockId should be empty (encoded to None). + /// This is implemented outside of this struct. Use the Default trait to check for an empty BlockId. + /// See: + #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] + pub struct Id { + /// The block's main hash is the Merkle root of all the fields in the + /// block header. + pub hash: Hash, + + /// Parts header (if available) is used for secure gossipping of the block + /// during consensus. It is the Merkle root of the complete serialized block + /// cut into parts. + /// + /// PartSet is used to split a byteslice of data into parts (pieces) for + /// transmission. By splitting data into smaller parts and computing a + /// Merkle root hash on the list, you can verify that a part is + /// legitimately part of the complete data, and the part can be forwarded + /// to other peers before all the parts are known. In short, it's a fast + /// way to propagate a large file over a gossip network. + /// + /// + /// + /// PartSetHeader in protobuf is defined as never nil using the gogoproto + /// annotations. This does not translate to Rust, but we can indicate this + /// in the domain type. + pub part_set_header: PartSetHeader, + } + + /// Block parts header + #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] + #[non_exhaustive] + pub struct PartSetHeader { + /// Number of parts in this block + pub total: u32, + + /// Hash of the parts set header, + pub hash: Hash, + } + + /// Output size for the SHA-256 hash function + pub const SHA256_HASH_SIZE: usize = 32; + + /// Hash digests + #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] + pub enum Hash { + /// SHA-256 hashes + Sha256([u8; SHA256_HASH_SIZE]), + /// Empty hash + None, + } + + + /// AppHash is usually a SHA256 hash, but in reality it can be any kind of data + #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] + pub struct AppHash(Vec); + + + + /// Block `Header` values contain metadata about the block and about the + /// consensus, as well as commitments to the data in the current block, the + /// previous block, and the results returned by the application. + /// + /// + #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] + pub struct Header { + /// Header version + pub version: Version, + + /// Chain ID + pub chain_id: chain::Id, + + /// Current block height + pub height: Height, + + /// Current timestamp + pub time: Time, + + /// Previous block info + pub last_block_id: Option, + + /// Commit from validators from the last block + pub last_commit_hash: Option, + + /// Merkle root of transaction hashes + pub data_hash: Option, + + /// Validators for the current block + pub validators_hash: Hash, + + /// Validators for the next block + pub next_validators_hash: Hash, + + /// Consensus params for the current block + pub consensus_hash: Hash, + + /// State after txs from the previous block + pub app_hash: AppHash, + + /// Root hash of all results from the txs from the previous block + pub last_results_hash: Option, + + /// Hash of evidence included in the block + pub evidence_hash: Option, + + /// Original proposer of the block + pub proposer_address: account::Id, + } + + + /// Block round for a particular chain + #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] + pub struct Round(u32); + + + /// Signatures + #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] + pub struct Signature(Vec); + + /// CommitSig represents a signature of a validator. + /// It's a part of the Commit and can be used to reconstruct the vote set given the validator set. + #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] + pub enum CommitSig { + /// no vote was received from a validator. + BlockIdFlagAbsent, + /// voted for the Commit.BlockID. + BlockIdFlagCommit { + /// Validator address + validator_address: account::Id, + /// Timestamp of vote + timestamp: Time, + /// Signature of vote + signature: Option, + }, + /// voted for nil. + BlockIdFlagNil { + /// Validator address + validator_address: account::Id, + /// Timestamp of vote + timestamp: Time, + /// Signature of vote + signature: Option, + }, + } + + /// Commit contains the justification (ie. a set of signatures) that a block was committed by a set + /// of validators. + /// TODO: Update links below! + /// + /// + #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] + pub struct Commit { + /// Block height + pub height: Height, + + /// Round + pub round: Round, + + /// Block ID + pub block_id: Id, + + /// Signatures + pub signatures: Vec, + } +} \ No newline at end of file diff --git a/frame/ibc/src/module/clients/ics07_tendermint/mod.rs b/frame/ibc/src/module/clients/ics07_tendermint/mod.rs new file mode 100644 index 0000000000000..311ecc229cd23 --- /dev/null +++ b/frame/ibc/src/module/clients/ics07_tendermint/mod.rs @@ -0,0 +1 @@ +pub mod header; \ No newline at end of file diff --git a/frame/ibc/src/module/clients/mod.rs b/frame/ibc/src/module/clients/mod.rs new file mode 100644 index 0000000000000..25470650e1860 --- /dev/null +++ b/frame/ibc/src/module/clients/mod.rs @@ -0,0 +1,2 @@ + +pub mod ics07_tendermint; diff --git a/frame/ibc/src/module/core/ics02_client.rs b/frame/ibc/src/module/core/ics02_client.rs new file mode 100644 index 0000000000000..64940c52a27ed --- /dev/null +++ b/frame/ibc/src/module/core/ics02_client.rs @@ -0,0 +1,4 @@ +pub mod context; +pub mod events; +pub mod header; + diff --git a/frame/ibc/src/module/core/ics02_client/context.rs b/frame/ibc/src/module/core/ics02_client/context.rs new file mode 100644 index 0000000000000..727a652368485 --- /dev/null +++ b/frame/ibc/src/module/core/ics02_client/context.rs @@ -0,0 +1,297 @@ +use crate::*; +use alloc::string::ToString; +use core::str::FromStr; +use log::{error, info, trace, warn}; + +use crate::context::Context; +use ibc::{ + core::{ + ics02_client::{ + client_consensus::AnyConsensusState, + client_state::AnyClientState, + client_type::ClientType, + context::{ClientKeeper, ClientReader}, + error::Error as Ics02Error, + }, + ics24_host::{ + identifier::ClientId, + path::{ClientConsensusStatePath, ClientStatePath, ClientTypePath}, + }, + }, + timestamp::Timestamp, + Height, +}; + +impl ClientReader for Context { + fn client_type(&self, client_id: &ClientId) -> Result { + trace!(target:"runtime::pallet-ibc","in client : [client_type]"); + + let client_type_path = ClientTypePath(client_id.clone()).to_string().as_bytes().to_vec(); + if >::contains_key(client_type_path.clone()) { + let data = >::get(client_type_path); + let mut data: &[u8] = &data; + let data = Vec::::decode(&mut data).unwrap(); + let data = String::from_utf8(data).unwrap(); + let client_type = ClientType::from_str(&data) + .map_err(|e| Ics02Error::unknown_client_type(e.to_string()))?; + Ok(client_type) + } else { + trace!(target:"runtime::pallet-ibc","in client : [client_type] >> read client_type is None"); + Err(Ics02Error::client_not_found(client_id.clone())) + } + } + + fn client_state(&self, client_id: &ClientId) -> Result { + trace!(target:"runtime::pallet-ibc","in client : [client_state]"); + + let client_state_path = ClientStatePath(client_id.clone()).to_string().as_bytes().to_vec(); + + if >::contains_key(&client_state_path) { + let data = >::get(&client_state_path); + let result = AnyClientState::decode_vec(&*data).unwrap(); + trace!(target:"runtime::pallet-ibc","in client : [client_state] >> any client_state: {:?}", result); + + Ok(result) + } else { + trace!(target:"runtime::pallet-ibc","in client : [client_state] >> read any client state is None"); + Err(Ics02Error::client_not_found(client_id.clone())) + } + } + + fn consensus_state( + &self, + client_id: &ClientId, + height: Height, + ) -> Result { + trace!(target:"runtime::pallet-ibc", + "in client : [consensus_state]" + ); + + // search key + let client_consensus_state_path = ClientConsensusStatePath { + client_id: client_id.clone(), + epoch: height.revision_number(), + height: height.revision_height(), + } + .to_string() + .as_bytes() + .to_vec(); + + if >::contains_key(client_consensus_state_path.clone()) { + let values = >::get(client_consensus_state_path.clone()); + let any_consensus_state = + AnyConsensusState::decode_vec(&*values).unwrap(); + trace!(target:"runtime::pallet-ibc", + "in client : [consensus_state] >> any consensus state = {:?}", + any_consensus_state + ); + Ok(any_consensus_state) + } else { + Err(Ics02Error::consensus_state_not_found(client_id.clone(), height)) + } + } + + fn next_consensus_state( + &self, + client_id: &ClientId, + height: Height, + ) -> Result, Ics02Error> { + trace!(target:"runtime::pallet-ibc","in client : [next_consensus_state]"); + + // search key + let client_consensus_state_path = ClientConsensusStatePath { + client_id: client_id.clone(), + epoch: height.revision_number(), + height: height.revision_height(), + } + .to_string() + .as_bytes() + .to_vec(); + + if >::contains_key(client_consensus_state_path.clone()) { + let values = >::get(client_consensus_state_path.clone()); + let any_consensus_state = + AnyConsensusState::decode_vec(&*values).unwrap(); + trace!(target:"runtime::pallet-ibc", + "in client : [next_consensus_state] >> any consensus state = {:?}", + any_consensus_state + ); + Ok(Some(any_consensus_state)) + } else { + trace!(target:"runtime::pallet-ibc", + "in client : [next_consensus_state] >> any consensus state is not contains at ConsensusStates : client_id = {:?}, height = {:?}", client_id, height + ); + Err(Ics02Error::consensus_state_not_found(client_id.clone(), height)) + } + } + + fn prev_consensus_state( + &self, + client_id: &ClientId, + height: Height, + ) -> Result, Ics02Error> { + trace!(target:"runtime::pallet-ibc","in client : [next_consensus_state]"); + + // search key + let client_consensus_state_path = ClientConsensusStatePath { + client_id: client_id.clone(), + epoch: height.revision_number(), + height: height.revision_height(), + } + .to_string() + .as_bytes() + .to_vec(); + + if >::contains_key(client_consensus_state_path.clone()) { + let values = >::get(client_consensus_state_path.clone()); + let any_consensus_state = + AnyConsensusState::decode_vec(&*values).unwrap(); + trace!(target:"runtime::pallet-ibc", + "in client : [prev_consensus_state] >> any consensus state = {:?}", + any_consensus_state + ); + Ok(Some(any_consensus_state)) + } else { + trace!(target:"runtime::pallet-ibc", + "in client : [prev_consensus_state] >> any consensus state is not contains at ConsensusStates : client_id = {:?}, height = {:?}", client_id, height + ); + Err(Ics02Error::consensus_state_not_found(client_id.clone(), height)) + } + } + + fn host_height(&self) -> Height { + trace!(target:"runtime::pallet-ibc","in client : [host_height]"); + let block_number = format!("{:?}", >::block_number()); + let current_height: u64 = block_number.parse().unwrap_or_default(); + + trace!(target:"runtime::pallet-ibc", + "in channel: [host_height] >> host_height = {:?}", + Height::new(REVISION_NUMBER, current_height) + ); + Height::new(REVISION_NUMBER, current_height).unwrap() + } + + fn host_consensus_state(&self, _height: Height) -> Result { + trace!(target:"runtime::pallet-ibc","in client : [consensus_state]"); + + todo!() + } + + fn pending_host_consensus_state(&self) -> Result { + trace!(target:"runtime::pallet-ibc","in client: [pending_host_consensus_state]"); + + todo!() + } + + fn client_counter(&self) -> Result { + trace!(target:"runtime::pallet-ibc","in client : [client_counter]"); + + Ok(>::get()) + } +} + +impl ClientKeeper for Context { + fn store_client_type( + &mut self, + client_id: ClientId, + client_type: ClientType, + ) -> Result<(), Ics02Error> { + info!("in client : [store_client_type]"); + + let client_type_path = ClientTypePath(client_id.clone()).to_string().as_bytes().to_vec(); + let client_type = client_type.as_str().encode(); + >::insert(client_type_path, client_type); + Ok(()) + } + + fn store_client_state( + &mut self, + client_id: ClientId, + client_state: AnyClientState, + ) -> Result<(), Ics02Error> { + trace!(target:"runtime::pallet-ibc","in client : [store_client_state]"); + + let client_state_path = ClientStatePath(client_id.clone()).to_string().as_bytes().to_vec(); + + let data = client_state.encode_vec().unwrap(); + // store client states key-value + >::insert(client_state_path.clone(), data); + + Ok(()) + } + + fn store_consensus_state( + &mut self, + client_id: ClientId, + height: Height, + consensus_state: AnyConsensusState, + ) -> Result<(), Ics02Error> { + trace!(target:"runtime::pallet-ibc","in client : [store_consensus_state]"); + + // store key + let client_consensus_state_path = ClientConsensusStatePath { + client_id: client_id.clone(), + epoch: height.revision_number(), + height: height.revision_height(), + } + .to_string() + .as_bytes() + .to_vec(); + + // store value + let consensus_state = consensus_state.encode_vec().unwrap(); + // store client_consensus_state path as key, consensus_state as value + >::insert(client_consensus_state_path, consensus_state); + + Ok(()) + } + + fn increase_client_counter(&mut self) { + info!("in client : [increase_client_counter]"); + + let ret = >::try_mutate(|val| -> Result<(), Ics02Error> { + let new = val.checked_add(1).unwrap(); + *val = new; + Ok(()) + }); + } + + fn store_update_time( + &mut self, + client_id: ClientId, + height: Height, + timestamp: Timestamp, + ) -> Result<(), Ics02Error> { + trace!(target:"runtime::pallet-ibc","in client: [store_update_time]"); + + let encode_timestamp = serde_json::to_string(×tamp) + .unwrap() + .as_bytes() + .to_vec(); + + >::insert( + client_id.as_bytes(), + height.encode_vec().unwrap(), + encode_timestamp, + ); + + Ok(()) + } + + fn store_update_height( + &mut self, + client_id: ClientId, + height: Height, + host_height: Height, + ) -> Result<(), Ics02Error> { + trace!(target:"runtime::pallet-ibc","in client: [store_update_height]"); + + >::insert( + client_id.as_bytes(), + height.encode_vec().unwrap(), + host_height.encode_vec().unwrap(), + ); + + Ok(()) + } +} diff --git a/frame/ibc/src/module/core/ics02_client/events.rs b/frame/ibc/src/module/core/ics02_client/events.rs new file mode 100644 index 0000000000000..daeca34bf7003 --- /dev/null +++ b/frame/ibc/src/module/core/ics02_client/events.rs @@ -0,0 +1,22 @@ +use crate::module::core::ics24_host::{ClientType, ClientId, Height}; +use super::header::AnyHeader; +use codec::{Decode, Encode}; +use scale_info::TypeInfo; +use sp_runtime::RuntimeDebug; + +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct Attributes { + pub height: Height, + pub client_id: ClientId, + pub client_type: ClientType, + pub consensus_height: Height, +} + +/// UpdateClient event signals a recent update of an on-chain client (IBC Client). +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct UpdateClient { + pub common: Attributes, + pub header: Option, +} + + diff --git a/frame/ibc/src/module/core/ics02_client/header.rs b/frame/ibc/src/module/core/ics02_client/header.rs new file mode 100644 index 0000000000000..d981896679f93 --- /dev/null +++ b/frame/ibc/src/module/core/ics02_client/header.rs @@ -0,0 +1,11 @@ +use crate::module::clients::ics07_tendermint::header::Header as TendermintHeader; +use codec::{Decode, Encode}; +use scale_info::TypeInfo; +use sp_runtime::RuntimeDebug; + +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +#[allow(clippy::large_enum_variant)] +pub enum AnyHeader { + Tendermint(TendermintHeader), + // Grandpa(GrandpaHeader), +} \ No newline at end of file diff --git a/frame/ibc/src/module/core/ics03_connection.rs b/frame/ibc/src/module/core/ics03_connection.rs new file mode 100644 index 0000000000000..2ce731e243850 --- /dev/null +++ b/frame/ibc/src/module/core/ics03_connection.rs @@ -0,0 +1,153 @@ +use crate::*; + +use crate::context::Context; +use log::{error, info, trace, warn}; + +use ibc::{ + core::{ + ics02_client::{ + client_consensus::AnyConsensusState, client_state::AnyClientState, + context::ClientReader, + }, + ics03_connection::{ + connection::ConnectionEnd, + context::{ConnectionKeeper, ConnectionReader}, + error::Error as Ics03Error, + }, + ics23_commitment::commitment::CommitmentPrefix, + ics24_host::{ + identifier::{ClientId, ConnectionId}, + path::{ClientConnectionsPath, ConnectionsPath}, + }, + }, + Height, +}; + +impl ConnectionReader for Context { + fn connection_end(&self, conn_id: &ConnectionId) -> Result { + trace!(target:"runtime::pallet-ibc","in connection : [connection_end]"); + + let connections_path = ConnectionsPath(conn_id.clone()).to_string().as_bytes().to_vec(); + + if >::contains_key(&connections_path) { + let data = >::get(&connections_path); + let ret = ConnectionEnd::decode_vec(&*data).unwrap(); + + trace!(target:"runtime::pallet-ibc","in connection : [connection_end] >> connection_end = {:?}", ret); + Ok(ret) + } else { + trace!(target:"runtime::pallet-ibc","in connection : [connection_end] >> read connection end returns None"); + Err(Ics03Error::connection_mismatch(conn_id.clone())) + } + } + + fn client_state(&self, client_id: &ClientId) -> Result { + trace!(target:"runtime::pallet-ibc","in connection : [client_state]"); + + ClientReader::client_state(self, client_id).map_err(Ics03Error::ics02_client) + } + + fn host_current_height(&self) -> Height { + trace!(target:"runtime::pallet-ibc","in connection : [host_current_height]"); + + let block_number = format!("{:?}", >::block_number()); + let current_height: u64 = block_number.parse().unwrap_or_default(); + + >::put(current_height); + + trace!(target:"runtime::pallet-ibc", + "in connection : [host_current_height] >> Host current height = {:?}", + Height::new(REVISION_NUMBER, current_height) + ); + Height::new(REVISION_NUMBER, current_height).unwrap() + } + + fn host_oldest_height(&self) -> Height { + trace!(target:"runtime::pallet-ibc","in connection : [host_oldest_height]"); + + let height = >::get(); + + trace!(target:"runtime::pallet-ibc", + "in connection : [host_oldest_height] >> Host oldest height = {:?}", + Height::new(0, height) + ); + Height::new(REVISION_NUMBER, height).unwrap() + } + + fn commitment_prefix(&self) -> CommitmentPrefix { + trace!(target:"runtime::pallet-ibc","in connection : [commitment_prefix]"); + + "ibc".as_bytes().to_vec().try_into().unwrap_or_default() + } + + fn client_consensus_state( + &self, + client_id: &ClientId, + height: Height, + ) -> Result { + trace!(target:"runtime::pallet-ibc","in connection : [client_consensus_state]"); + + let ret = ClientReader::consensus_state(self, client_id, height) + .map_err(Ics03Error::ics02_client); + + + Ok(ret.unwrap()) + } + + fn host_consensus_state(&self, _height: Height) -> Result { + trace!(target:"runtime::pallet-ibc","in connection : [host_consensus_state]"); + todo!() + } + + fn connection_counter(&self) -> Result { + trace!(target:"runtime::pallet-ibc", + "in connection : [connection_counter]" + ); + + Ok(>::get()) + } +} + +impl ConnectionKeeper for Context { + fn store_connection( + &mut self, + connection_id: ConnectionId, + connection_end: &ConnectionEnd, + ) -> Result<(), Ics03Error> { + trace!(target:"runtime::pallet-ibc","in connection : [store_connection]"); + + let connections_path = + ConnectionsPath(connection_id.clone()).to_string().as_bytes().to_vec(); + let data = connection_end.encode_vec().unwrap(); + + // store connection end + >::insert(connections_path, data); + + Ok(()) + } + + fn store_connection_to_client( + &mut self, + connection_id: ConnectionId, + client_id: &ClientId, + ) -> Result<(), Ics03Error> { + trace!(target:"runtime::pallet-ibc","in connection : [store_connection_to_client]"); + + let client_connection_paths = + ClientConnectionsPath(client_id.clone()).to_string().as_bytes().to_vec(); + + >::insert(client_connection_paths, connection_id.as_bytes().to_vec()); + Ok(()) + } + + fn increase_connection_counter(&mut self) { + trace!(target:"runtime::pallet-ibc","in connection : [increase_connection_counter]"); + + let ret = >::try_mutate(|val| -> Result<(), Ics03Error> { + let new = val + .checked_add(1).unwrap(); + *val = new; + Ok(()) + }); + } +} diff --git a/frame/ibc/src/module/core/ics04_channel.rs b/frame/ibc/src/module/core/ics04_channel.rs new file mode 100644 index 0000000000000..bab1286cc5535 --- /dev/null +++ b/frame/ibc/src/module/core/ics04_channel.rs @@ -0,0 +1,615 @@ +use crate::*; +use core::{str::FromStr, time::Duration}; +use log::{error, info, trace, warn}; + +use crate::context::Context; +use ibc::{ + core::{ + ics02_client::{ + client_consensus::AnyConsensusState, client_state::AnyClientState, + context::ClientReader, + }, + ics03_connection::{ + connection::ConnectionEnd, context::ConnectionReader, error::Error as ICS03Error, + }, + ics04_channel::{ + channel::ChannelEnd, + commitment::{ + AcknowledgementCommitment as IbcAcknowledgementCommitment, + PacketCommitment as IbcPacketCommitment, + }, + context::{ChannelKeeper, ChannelReader}, + error::Error as Ics04Error, + packet::{Receipt, Sequence}, + }, + ics05_port::{context::PortReader, error::Error as Ics05Error}, + ics24_host::{ + identifier::{ChannelId, ClientId, ConnectionId, PortId}, + path::{ + AcksPath, ChannelEndsPath, CommitmentsPath, ConnectionsPath, ReceiptsPath, + SeqAcksPath, SeqRecvsPath, SeqSendsPath, + }, + Path, + }, + ics26_routing::context::ModuleId, + }, + timestamp::Timestamp, + Height, +}; + +impl ChannelReader for Context { + fn channel_end(&self, port_channel_id: &(PortId, ChannelId)) -> Result { + trace!(target:"runtime::pallet-ibc","in channel : [channel_end]"); + + let channel_end_path = + ChannelEndsPath(port_channel_id.0.clone(), port_channel_id.1.clone()) + .to_string() + .as_bytes() + .to_vec(); + + let data = >::get(channel_end_path); + + let channel_end = ChannelEnd::decode_vec(&*data).map_err(|_| { + Ics04Error::channel_not_found(port_channel_id.clone().0, port_channel_id.clone().1) + })?; + + trace!(target:"runtime::pallet-ibc","in channel : [channel_end] >> channel_end = {:?}", channel_end); + Ok(channel_end) + } + + fn connection_end(&self, connection_id: &ConnectionId) -> Result { + trace!(target:"runtime::pallet-ibc","in channel : [connection_end]"); + + ConnectionReader::connection_end(self, connection_id).map_err(Ics04Error::ics03_connection) + } + + /// Returns the `ChannelsConnection` for the given identifier `conn_id`. + fn connection_channels( + &self, + conn_id: &ConnectionId, + ) -> Result, Ics04Error> { + trace!(target:"runtime::pallet-ibc","in channel : [connection_channels]"); + // store key + let connections_path = ConnectionsPath(conn_id.clone()).to_string().as_bytes().to_vec(); + + if >::contains_key(&connections_path) { + let channel_ends_paths = >::get(&connections_path); + + let mut result = vec![]; + + for item in channel_ends_paths.into_iter() { + let raw_path = String::from_utf8(item).unwrap(); + // decode key + let path = Path::from_str(&raw_path).unwrap(); + trace!(target:"runtime::pallet-ibc", "[get_channels] >> Path: {:?}", path); + match path { + Path::ChannelEnds(channel_ends_path) => { + let ChannelEndsPath(port_id, channel_id) = channel_ends_path; + result.push((port_id, channel_id)); + }, + _ => unimplemented!(), + } + } + + trace!(target:"runtime::pallet-ibc", + "in channel : [connection_channels] >> Vector<(PortId, ChannelId)> = {:?}", + result + ); + Ok(result) + } else { + Err(Ics04Error::connection_not_open(conn_id.clone())) + } + } + + fn client_state(&self, client_id: &ClientId) -> Result { + trace!(target:"runtime::pallet-ibc","in channel : [client_state]"); + + Ok(ClientReader::client_state(self, client_id).unwrap()) + } + + fn client_consensus_state( + &self, + client_id: &ClientId, + height: Height, + ) -> Result { + trace!(target:"runtime::pallet-ibc","in channel : [client_consensus_state]"); + + let ret = ClientReader::consensus_state(self, client_id, height) + .unwrap(); + + Ok(ret) + } + + fn get_next_sequence_send( + &self, + port_channel_id: &(PortId, ChannelId), + ) -> Result { + trace!(target:"runtime::pallet-ibc","in channel : [get_next_sequence]"); + + let seq_sends_path = SeqSendsPath(port_channel_id.0.clone(), port_channel_id.1.clone()) + .to_string() + .as_bytes() + .to_vec(); + + let sequence = >::get(&seq_sends_path); + + trace!(target:"runtime::pallet-ibc","in channel : [get_next_sequence] >> sequence = {:?}", sequence); + Ok(Sequence::from(sequence)) + } + + fn get_next_sequence_recv( + &self, + port_channel_id: &(PortId, ChannelId), + ) -> Result { + trace!(target:"runtime::pallet-ibc","in channel : [get_next_sequence_recv]"); + + let seq_recvs_path = SeqRecvsPath(port_channel_id.0.clone(), port_channel_id.1.clone()) + .to_string() + .as_bytes() + .to_vec(); + + let sequence = >::get(&seq_recvs_path); + + trace!(target:"runtime::pallet-ibc","in channel : [get_next_sequence_recv] >> sequence = {:?}", sequence); + Ok(Sequence::from(sequence)) + } + + fn get_next_sequence_ack( + &self, + port_channel_id: &(PortId, ChannelId), + ) -> Result { + trace!(target:"runtime::pallet-ibc","in channel : [get_next_sequence_ack]"); + + let seq_acks_path = SeqAcksPath(port_channel_id.0.clone(), port_channel_id.1.clone()) + .to_string() + .as_bytes() + .to_vec(); + + let sequence = >::get(&seq_acks_path); + + trace!(target:"runtime::pallet-ibc","in channel : [get_next_sequence_ack] >> sequence = {}", sequence); + Ok(Sequence::from(sequence)) + } + + /// Returns the `PacketCommitment` for the given identifier `(PortId, ChannelId, Sequence)`. + fn get_packet_commitment( + &self, + key: &(PortId, ChannelId, Sequence), + ) -> Result { + trace!(target:"runtime::pallet-ibc","in channel : [get_packet_commitment]"); + + let packet_commitments_path = CommitmentsPath { + port_id: key.0.clone(), + channel_id: key.1.clone(), + sequence: key.2.clone(), + } + .to_string() + .as_bytes() + .to_vec(); + + if >::contains_key(&packet_commitments_path) { + let data = >::get(&packet_commitments_path); + + let packet_commitment = IbcPacketCommitment::from(data); + + trace!(target:"runtime::pallet-ibc", + "in channel : [get_packet_commitment] >> packet_commitment = {:?}", + packet_commitment + ); + Ok(packet_commitment) + } else { + trace!(target:"runtime::pallet-ibc", + "in channel : [get_packet_commitment] >> read get packet commitment return None" + ); + Err(Ics04Error::packet_commitment_not_found(key.2)) + } + } + + fn get_packet_receipt( + &self, + key: &(PortId, ChannelId, Sequence), + ) -> Result { + trace!(target:"runtime::pallet-ibc","in channel : [get_packet_receipt]"); + + let packet_receipt_path = ReceiptsPath { + port_id: key.0.clone(), + channel_id: key.1.clone(), + sequence: key.2.clone(), + } + .to_string() + .as_bytes() + .to_vec(); + + if >::contains_key(&packet_receipt_path) { + let data = >::get(&packet_receipt_path); + let data = String::from_utf8(data).unwrap(); + let data = match data.as_ref() { + "Ok" => Receipt::Ok, + _ => unreachable!(), + }; + trace!(target:"runtime::pallet-ibc","in channel : [get_packet_receipt] >> packet_receipt = {:?}", data); + Ok(data) + } else { + error!(target:"runtime::pallet-ibc","in channel : [get_packet_receipt] >> read get packet receipt not found"); + Err(Ics04Error::packet_receipt_not_found(key.2)) + } + } + + /// Returns the `Acknowledgements` for the given identifier `(PortId, ChannelId, Sequence)`. + fn get_packet_acknowledgement( + &self, + key: &(PortId, ChannelId, Sequence), + ) -> Result { + trace!(target:"runtime::pallet-ibc","in channel : [get_packet_acknowledgement]"); + + let acks_path = + AcksPath { port_id: key.0.clone(), channel_id: key.1.clone(), sequence: key.2.clone() } + .to_string() + .as_bytes() + .to_vec(); + + if >::contains_key(&acks_path) { + let data = >::get(&acks_path); + + let acknowledgement = IbcAcknowledgementCommitment::from(data); + trace!(target:"runtime::pallet-ibc", + "in channel : [get_packet_acknowledgement] >> packet_acknowledgement = {:?}", + acknowledgement + ); + Ok(acknowledgement) + } else { + error!(target:"runtime::pallet-ibc", + "in channel : [get_packet_acknowledgement] >> get acknowledgement not found" + ); + Err(Ics04Error::packet_acknowledgement_not_found(key.2)) + } + } + + /// A hashing function for packet commitments + fn hash(&self, value: Vec) -> Vec { + trace!(target:"runtime::pallet-ibc","in channel: [hash]"); + + sp_io::hashing::sha2_256(&value).to_vec() + } + + /// Returns the current height of the local chain. + fn host_height(&self) -> Height { + trace!(target:"runtime::pallet-ibc","in channel: [host_height]"); + + //todo this can improve + let block_number = format!("{:?}", >::block_number()); + let current_height: u64 = block_number.parse().unwrap_or_default(); + + trace!(target:"runtime::pallet-ibc", + "in channel: [host_height] >> host_height = {:?}", + Height::new(REVISION_NUMBER, current_height) + ); + Height::new(REVISION_NUMBER, current_height).unwrap() + } + + /// Returns the current timestamp of the local chain. + fn host_timestamp(&self) -> Timestamp { + trace!(target:"runtime::pallet-ibc","in channel: [host_timestamp]"); + + use frame_support::traits::UnixTime; + let time = T::TimeProvider::now(); + let ts = Timestamp::from_nanoseconds(time.as_nanos() as u64) + .map_err(|e| panic!("{:?}, caused by {:?} from pallet timestamp_pallet", e, time)); + trace!(target:"runtime::pallet-ibc","in channel: [host_timestamp] >> host_timestamp = {:?}", ts.unwrap()); + + ts.unwrap() + } + + /// Returns the `AnyConsensusState` for the given identifier `height`. + fn host_consensus_state(&self, height: Height) -> Result { + trace!(target:"runtime::pallet-ibc","in channel: [host_consensus_state]"); + + ConnectionReader::host_consensus_state(self, height).map_err(Ics04Error::ics03_connection) + } + + fn pending_host_consensus_state(&self) -> Result { + trace!(target:"runtime::pallet-ibc","in channel: [pending_host_consensus_stata]"); + + ClientReader::pending_host_consensus_state(self) + .map_err(|e| Ics04Error::ics03_connection(ICS03Error::ics02_client(e))) + } + + /// Returns the `ClientProcessedTimes` for the given identifier `client_id` & `height`. + fn client_update_time( + &self, + client_id: &ClientId, + height: Height, + ) -> Result { + trace!(target:"runtime::pallet-ibc","in channel: [client_update_time]"); + + if >::contains_key( + client_id.as_bytes(), + height.encode_vec().unwrap() + ) { + let time = >::get( + client_id.as_bytes(), + height.encode_vec().unwrap() + ); + let timestamp = String::from_utf8(time).unwrap(); + let time: Timestamp = + serde_json::from_str(×tamp).unwrap(); + Ok(time) + } else { + error!(target:"runtime::pallet-ibc","in channel: [client_update_time] processed time not found"); + Err(Ics04Error::processed_time_not_found(client_id.clone(), height)) + } + } + + fn client_update_height( + &self, + client_id: &ClientId, + height: Height, + ) -> Result { + trace!(target:"runtime::pallet-ibc","in channel: [client_update_height]"); + + if >::contains_key( + client_id.as_bytes(), + height.encode_vec().unwrap() + ) { + let host_height = >::get( + client_id.as_bytes(), + height.encode_vec().unwrap(), + ); + let host_height = + Height::decode(&mut &host_height[..]).unwrap(); + Ok(host_height) + } else { + error!(target:"runtime::pallet-ibc","in channel: [client_update_height] processed height not found"); + Err(Ics04Error::processed_height_not_found(client_id.clone(), height)) + } + } + + /// Returns a counter on the number of channel ids have been created thus far. + /// The value of this counter should increase only via method + /// `ChannelKeeper::increase_channel_counter`. + fn channel_counter(&self) -> Result { + trace!(target:"runtime::pallet-ibc", + "in channel: [channel_counter]" + ); + Ok( as Store>::ChannelCounter::get()) + } + + fn max_expected_time_per_block(&self) -> Duration { + trace!(target:"runtime::pallet-ibc","in channel: [max_expected_time_per_block]"); + + Duration::from_secs(6) + } +} + +impl ChannelKeeper for Context { + fn store_packet_commitment( + &mut self, + key: (PortId, ChannelId, Sequence), + commitment: IbcPacketCommitment, + ) -> Result<(), Ics04Error> { + trace!(target:"runtime::pallet-ibc","in channel: [store_packet_commitment]. key={:?}", key); + + let packet_commitments_path = CommitmentsPath { + port_id: key.0.clone(), + channel_id: key.1.clone(), + sequence: key.2.clone(), + } + .to_string() + .as_bytes() + .to_vec(); + + // insert packet commitment key-value + >::insert(packet_commitments_path, commitment.into_vec()); + + Ok(()) + } + + fn delete_packet_commitment( + &mut self, + key: (PortId, ChannelId, Sequence), + ) -> Result<(), Ics04Error> { + trace!(target:"runtime::pallet-ibc","in channel: [delete_packet_commitment]. key={:?}", key); + + let packet_commitments_path = CommitmentsPath { + port_id: key.0.clone(), + channel_id: key.1.clone(), + sequence: key.2.clone(), + } + .to_string() + .as_bytes() + .to_vec(); + + // delete packet commitment + >::remove(&packet_commitments_path); + + Ok(()) + } + + fn store_packet_receipt( + &mut self, + key: (PortId, ChannelId, Sequence), + receipt: Receipt, + ) -> Result<(), Ics04Error> { + trace!(target:"runtime::pallet-ibc","in channel: [store_packet_receipt]"); + + let packet_receipt_path = ReceiptsPath { + port_id: key.0.clone(), + channel_id: key.1.clone(), + sequence: key.2.clone(), + } + .to_string() + .as_bytes() + .to_vec(); + + let receipt = match receipt { + Receipt::Ok => "Ok".as_bytes().to_vec(), + }; + + >::insert(packet_receipt_path, receipt); + + Ok(()) + } + + fn store_packet_acknowledgement( + &mut self, + key: (PortId, ChannelId, Sequence), + ack_commitment: IbcAcknowledgementCommitment, + ) -> Result<(), Ics04Error> { + trace!(target:"runtime::pallet-ibc","in channel: [store_packet_acknowledgement]"); + + let acks_path = + AcksPath { port_id: key.0.clone(), channel_id: key.1.clone(), sequence: key.2.clone() } + .to_string() + .as_bytes() + .to_vec(); + + // store packet acknowledgement key-value + >::insert(&acks_path, ack_commitment.into_vec()); + + Ok(()) + } + + fn delete_packet_acknowledgement( + &mut self, + key: (PortId, ChannelId, Sequence), + ) -> Result<(), Ics04Error> { + trace!(target:"runtime::pallet-ibc","in channel: [delete_packet_acknowledgement]"); + + let acks_path = + AcksPath { port_id: key.0.clone(), channel_id: key.1.clone(), sequence: key.2.clone() } + .to_string() + .as_bytes() + .to_vec(); + + // remove acknowledgements + >::remove(&acks_path); + + Ok(()) + } + + fn store_connection_channels( + &mut self, + conn_id: ConnectionId, + port_channel_id: &(PortId, ChannelId), + ) -> Result<(), Ics04Error> { + trace!(target:"runtime::pallet-ibc","in channel: [store_connection_channels]"); + + // store key + let connections_path = ConnectionsPath(conn_id.clone()).to_string().as_bytes().to_vec(); + + // store value + let channel_ends_path = + ChannelEndsPath(port_channel_id.0.clone(), port_channel_id.1.clone()) + .to_string() + .as_bytes() + .to_vec(); + + if >::contains_key(&connections_path) { + trace!(target:"runtime::pallet-ibc","in channel: [store_connection_channels] >> insert port_channel_id"); + // if connection_id exist + let ret = >::try_mutate( + &connections_path, + |val| -> Result<(), Ics04Error> { + val.push(channel_ends_path.clone()); + Ok(()) + }, + ) + .unwrap(); + } else { + // if connection_id no exist + trace!(target:"runtime::pallet-ibc","in channel: [store_connection_channels] >> init ChannelsConnection"); + >::insert(connections_path, vec![channel_ends_path]); + } + + Ok(()) + } + + /// Stores the given channel_end at a path associated with the port_id and channel_id. + fn store_channel( + &mut self, + port_channel_id: (PortId, ChannelId), + channel_end: &ChannelEnd, + ) -> Result<(), Ics04Error> { + trace!(target:"runtime::pallet-ibc","in channel: [store_channel]"); + + let channel_end_path = + ChannelEndsPath(port_channel_id.0.clone(), port_channel_id.1.clone()) + .to_string() + .as_bytes() + .to_vec(); + let channel_end = channel_end.encode_vec().unwrap(); + + // store channels key-value + >::insert(channel_end_path, channel_end); + + Ok(()) + } + + fn store_next_sequence_send( + &mut self, + port_channel_id: (PortId, ChannelId), + seq: Sequence, + ) -> Result<(), Ics04Error> { + trace!(target:"runtime::pallet-ibc","in channel: [store_next_sequence_send]"); + + let seq_sends_path = SeqSendsPath(port_channel_id.0.clone(), port_channel_id.1.clone()) + .to_string() + .as_bytes() + .to_vec(); + + let sequence = u64::from(seq); + + >::insert(seq_sends_path, sequence); + + Ok(()) + } + + fn store_next_sequence_recv( + &mut self, + port_channel_id: (PortId, ChannelId), + seq: Sequence, + ) -> Result<(), Ics04Error> { + trace!(target:"runtime::pallet-ibc","in channel: [store_next_sequence_recv]"); + + let seq_recvs_path = SeqRecvsPath(port_channel_id.0.clone(), port_channel_id.1.clone()) + .to_string() + .as_bytes() + .to_vec(); + let sequence = u64::from(seq); + + >::insert(seq_recvs_path, sequence); + + Ok(()) + } + + fn store_next_sequence_ack( + &mut self, + port_channel_id: (PortId, ChannelId), + seq: Sequence, + ) -> Result<(), Ics04Error> { + trace!(target:"runtime::pallet-ibc","in channel: [store_next_sequence_ack]"); + + let seq_acks_path = SeqAcksPath(port_channel_id.0.clone(), port_channel_id.1.clone()) + .to_string() + .as_bytes() + .to_vec(); + let sequence = u64::from(seq); + + >::insert(seq_acks_path, sequence); + + Ok(()) + } + + /// Called upon channel identifier creation (Init or Try message processing). + /// Increases the counter which keeps track of how many channels have been created. + /// Should never fail. + fn increase_channel_counter(&mut self) { + trace!(target:"runtime::pallet-ibc","in channel: [increase_channel_counter]"); + + let ret = >::try_mutate(|val| -> Result<(), Ics04Error> { + let new = val.checked_add(1).unwrap(); + *val = new; + Ok(()) + }); + } +} diff --git a/frame/ibc/src/module/core/ics05_port.rs b/frame/ibc/src/module/core/ics05_port.rs new file mode 100644 index 0000000000000..cd735a425eaa3 --- /dev/null +++ b/frame/ibc/src/module/core/ics05_port.rs @@ -0,0 +1,25 @@ +use crate::*; +use log::trace; + +use crate::context::Context; +use ibc::{ + applications::transfer::{ + MODULE_ID_STR as TRANSFER_MODULE_ID, PORT_ID_STR as TRANSFER_PORT_ID, + }, + core::{ + ics05_port::{context::PortReader, error::Error as ICS05Error}, + ics24_host::identifier::PortId, + ics26_routing::context::ModuleId, + }, +}; + +impl PortReader for Context { + fn lookup_module_by_port(&self, port_id: &PortId) -> Result { + trace!(target: LOG_TARGET, "in port: [lookup_module_by_port] >> port_id = {:?}", port_id); + match port_id.as_str() { + TRANSFER_PORT_ID => Ok(ModuleId::from_str(TRANSFER_MODULE_ID) + .map_err(|_| ICS05Error::module_not_found(port_id.clone()))?), + _ => Err(ICS05Error::module_not_found(port_id.clone())), + } + } +} diff --git a/frame/ibc/src/module/core/ics23_commitment.rs b/frame/ibc/src/module/core/ics23_commitment.rs new file mode 100644 index 0000000000000..8b137891791fe --- /dev/null +++ b/frame/ibc/src/module/core/ics23_commitment.rs @@ -0,0 +1 @@ + diff --git a/frame/ibc/src/module/core/ics24_host.rs b/frame/ibc/src/module/core/ics24_host.rs new file mode 100644 index 0000000000000..529193e864024 --- /dev/null +++ b/frame/ibc/src/module/core/ics24_host.rs @@ -0,0 +1,246 @@ +use crate::{alloc::string::ToString, Config, Event, from_channel_id_to_vec, REVISION_NUMBER}; +use alloc::string::String; +use ibc::{ + core::{ + ics02_client::{client_type::ClientType as IbcClientType, height::Height as IbcHeight}, + ics04_channel::packet::{Packet as IbcPacket, Sequence as IbcSequence}, + ics24_host::{ + error::ValidationError, + identifier::{ + ChainId as IbcChainId, ChannelId as IbcChannelId, ClientId as IbcClientId, + ConnectionId as IbcConnectionId, PortId as IbcPortId, + }, + }, + }, + timestamp::Timestamp as IbcTimestamp, +}; +use sp_std::{str::FromStr, vec::Vec}; + +use codec::{Decode, Encode}; +use scale_info::TypeInfo; + +use sp_runtime::RuntimeDebug; + +use flex_error::{define_error, DisplayOnly, TraceError}; +use ibc::core::ics04_channel::timeout::TimeoutHeight; +use tendermint_proto::Error as TendermintError; + +define_error! { + #[derive(Debug, PartialEq, Eq)] + Error { + InvalidFromUtf8 + [DisplayOnly] + | _ | { "invalid from utf8 error" }, + InvalidDecode + [DisplayOnly] + | _ | { "invalid decode error" }, + ParseTimestampFailed + [DisplayOnly] + | _ | { "invalid parse timestamp error" }, + ValidationFailed + [DisplayOnly] + | _ | { "invalid validation error"}, + InvalidChainId + [DisplayOnly] + |_| { "invalid chain id error" }, + } +} + +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct PortId(pub Vec); + +impl From for PortId { + fn from(value: IbcPortId) -> Self { + let value = value.as_str().as_bytes().to_vec(); + Self(value) + } +} + +impl From for IbcPortId { + fn from(value: PortId) -> Self { + let value = String::from_utf8(value.0).expect("convert from utf8 Error"); + IbcPortId::from_str(&value).unwrap() + } +} + +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct ChannelId(pub Vec); + +impl From for ChannelId { + fn from(value: IbcChannelId) -> Self { + let value = from_channel_id_to_vec(value); + Self(value) + } +} + +impl From for IbcChannelId { + fn from(value: ChannelId) -> Self { + let value = String::from_utf8(value.0).expect("convert from utf8 Error"); + Self::from_str(&value).expect("convert channel id from str Error") + } +} + +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct Height { + /// Previously known as "epoch" + pub revision_number: u64, + + /// The height of a block + pub revision_height: u64, +} + +impl From for Height { + fn from(ibc_height: IbcHeight) -> Self { + Height::new(ibc_height.revision_number(), ibc_height.revision_height()) + } +} + +impl From for IbcHeight { + fn from(height: Height) -> Self { + IbcHeight::new(REVISION_NUMBER, height.revision_height).expect("Contruct IbcHeight Error") + } +} + +impl Height { + pub fn new(revision_number: u64, revision_height: u64) -> Self { + Self { revision_number, revision_height } + } +} + +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub enum ClientType { + Tendermint, + Grandpa, +} + +impl From for ClientType { + fn from(value: IbcClientType) -> Self { + match value { + IbcClientType::Tendermint => ClientType::Tendermint, + _ => unreachable!(), + } + } +} + +impl ClientType { + pub fn to_ibc_client_type(self) -> IbcClientType { + match self { + ClientType::Tendermint => IbcClientType::Tendermint, + _ => unreachable!(), + } + } +} + +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct ClientId(pub Vec); + +impl From for ClientId { + fn from(value: IbcClientId) -> Self { + let value = value.as_str().as_bytes().to_vec(); + Self(value) + } +} + +impl From for IbcClientId { + fn from(value: ClientId) -> Self { + let value = String::from_utf8(value.0).expect("convert from utf8 Error"); + IbcClientId::from_str(&value).unwrap() + } +} + +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct ConnectionId(pub Vec); + +impl From for ConnectionId { + fn from(value: IbcConnectionId) -> Self { + let value = value.as_str().as_bytes().to_vec(); + Self(value) + } +} + + +impl From for IbcConnectionId { + fn from(value: ConnectionId) -> Self { + let value = String::from_utf8(value.0).expect("convert from utf8 Error"); + IbcConnectionId::from_str(&value).unwrap() + } +} + +/// Helper to convert between IBC timestamp and Substrate timestamp +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct Timestamp { + pub time: Vec, +} + +impl From for Timestamp { + fn from(val: IbcTimestamp) -> Self { + Self { time: val.nanoseconds().to_string().as_bytes().to_vec() } + } +} + +impl From for IbcTimestamp { + fn from(value: Timestamp) -> Self { + let value = String::from_utf8(value.time).expect("convert from utf8 Error"); + Self::from_str(&value).expect("convert from str Error") + } +} + +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct Sequence(u64); + +impl From for Sequence { + fn from(val: IbcSequence) -> Self { + Self(u64::from(val)) + } +} + +impl From for IbcSequence { + fn from(val: Sequence) -> Self { + IbcSequence::from(val.0) + } +} + +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct Packet { + pub sequence: Sequence, + pub source_port: PortId, + pub source_channel: ChannelId, + pub destination_port: PortId, + pub destination_channel: ChannelId, + pub data: Vec, + pub timeout_height: Height, + pub timeout_timestamp: Timestamp, +} + +impl From for Packet { + fn from(val: IbcPacket) -> Self { + Self { + sequence: val.sequence.into(), + source_port: val.source_port.into(), + source_channel: val.source_channel.into(), + destination_port: val.destination_port.into(), + destination_channel: val.destination_channel.into(), + data: val.data, + timeout_height: match val.timeout_height { + TimeoutHeight::Never => Height::new(REVISION_NUMBER, u64::MAX), + TimeoutHeight::At(value) => value.into(), + }, + timeout_timestamp: val.timeout_timestamp.into(), + } + } +} + +impl From for IbcPacket { + fn from(value: Packet) -> Self { + Self { + sequence: value.sequence.into(), + source_port: value.source_port.into(), + source_channel: value.source_channel.into(), + destination_port: value.destination_port.into(), + destination_channel: value.destination_channel.into(), + data: value.data, + timeout_height: TimeoutHeight::At(value.timeout_height.into()), + timeout_timestamp: value.timeout_timestamp.into(), + } + } +} \ No newline at end of file diff --git a/frame/ibc/src/module/core/ics26_routing.rs b/frame/ibc/src/module/core/ics26_routing.rs new file mode 100644 index 0000000000000..361b3fdd518cf --- /dev/null +++ b/frame/ibc/src/module/core/ics26_routing.rs @@ -0,0 +1,72 @@ +use crate::{context::Context, *}; +use alloc::{ + borrow::{Borrow, Cow, ToOwned}, + collections::BTreeMap, + sync::Arc, +}; +use alloc::fmt::format; +use core::fmt::Formatter; +use ibc::core::ics26_routing::context::{Ics26Context, Module, ModuleId, RouterBuilder}; +use log::{error, info, trace, warn}; +use scale_info::TypeInfo; + +#[derive(Default)] +pub struct MockRouterBuilder(MockRouter); + +impl RouterBuilder for MockRouterBuilder { + type Router = MockRouter; + + fn add_route(mut self, module_id: ModuleId, module: impl Module) -> Result { + match self.0 .0.insert(module_id, Arc::new(module)) { + None => Ok(self), + Some(_) => Err("Duplicate module_id".to_owned()), + } + } + + fn build(self) -> Self::Router { + self.0 + } +} + +#[derive(Default, Clone)] +pub struct MockRouter(BTreeMap>); + +impl Debug for MockRouter { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + let mut keys = vec![]; + for (key, _) in self.0.iter() { + keys.push(format!("{}", key)); + } + + write!(f, "MockRouter(BTreeMap(key({:?})", keys.join(",")) + } +} + +impl ibc::core::ics26_routing::context::Router for MockRouter { + fn get_route_mut(&mut self, module_id: &impl Borrow) -> Option<&mut dyn Module> { + trace!(target:"runtime::pallet-ibc","in routing: [get_route_mut]"); + + self.0.get_mut(module_id.borrow()).and_then(Arc::get_mut) + } + + fn has_route(&self, module_id: &impl Borrow) -> bool { + trace!(target:"runtime::pallet-ibc","in routing: [has_route]"); + self.0.get(module_id.borrow()).is_some() + } +} + +impl Ics26Context for Context { + type Router = MockRouter; + + fn router(&self) -> &Self::Router { + trace!(target:"runtime::pallet-ibc","in routing: [route]"); + + &self.router + } + + fn router_mut(&mut self) -> &mut Self::Router { + trace!(target:"runtime::pallet-ibc","in routing: [router_mut]"); + + &mut self.router + } +} diff --git a/frame/ibc/src/module/core/mod.rs b/frame/ibc/src/module/core/mod.rs new file mode 100644 index 0000000000000..22134b723599a --- /dev/null +++ b/frame/ibc/src/module/core/mod.rs @@ -0,0 +1,7 @@ +pub mod ics02_client; +pub mod ics03_connection; +pub mod ics04_channel; +pub mod ics05_port; +pub mod ics23_commitment; +pub mod ics24_host; +pub mod ics26_routing; diff --git a/frame/ibc/src/module/mod.rs b/frame/ibc/src/module/mod.rs new file mode 100644 index 0000000000000..a55d6ce38f614 --- /dev/null +++ b/frame/ibc/src/module/mod.rs @@ -0,0 +1,4 @@ +pub mod applications; +pub mod clients; +pub mod core; +pub mod relayer; diff --git a/frame/ibc/src/module/relayer/mod.rs b/frame/ibc/src/module/relayer/mod.rs new file mode 100644 index 0000000000000..9f520b96f15f9 --- /dev/null +++ b/frame/ibc/src/module/relayer/mod.rs @@ -0,0 +1,47 @@ +use crate::{ + alloc::string::ToString, context::Context, utils::host_height, Config, REVISION_NUMBER, +}; +use ibc::{ + core::{ + ics02_client::{client_state::AnyClientState, context::ClientReader, header::AnyHeader}, + ics24_host::identifier::ClientId, + ics26_routing::handler::{deliver, MsgReceipt}, + }, + events::IbcEvent, + relayer::ics18_relayer::{context::Ics18Context, error::Error as ICS18Error}, + signer::Signer, + Height, +}; +use ibc_proto::google::protobuf::Any; +use scale_info::prelude::{vec, vec::Vec}; + +impl Ics18Context for Context { + fn query_latest_height(&self) -> Height { + let revision_height = host_height::(); + Height::new(REVISION_NUMBER, revision_height).expect(&REVISION_NUMBER.to_string()) + } + + fn query_client_full_state(&self, client_id: &ClientId) -> Option { + // Forward call to Ics2. + ClientReader::client_state(self, client_id).ok() + } + + fn query_latest_header(&self) -> Option { + todo!() + } + + fn send(&mut self, msgs: Vec) -> Result, ICS18Error> { + // Forward call to Ics26 delivery method. + let mut all_events = vec![]; + for msg in msgs { + let MsgReceipt { mut events, .. } = + deliver(self, msg).map_err(ICS18Error::transaction_failed)?; + all_events.append(&mut events); + } + Ok(all_events) + } + + fn signer(&self) -> Signer { + "0CDA3F47EF3C4906693B170EF650EB968C5F4B2C".parse().unwrap() + } +} diff --git a/frame/ibc/src/tests.rs b/frame/ibc/src/tests.rs new file mode 100644 index 0000000000000..e08c18afef5a7 --- /dev/null +++ b/frame/ibc/src/tests.rs @@ -0,0 +1,637 @@ +use super::*; +use crate::{mock::*, routing::Context}; +use core::str::FromStr; + +use ibc::{ + applications::ics20_fungible_token_transfer::{ + context::Ics20Context, error::Error as ICS20Error, msgs::denom_trace::DenomTrace, + }, + clients::ics10_grandpa::{ + client_state::ClientState as GPClientState, + consensus_state::ConsensusState as GPConsensusState, help::ValidatorSet, + }, + core::{ + ics02_client::{ + client_consensus::AnyConsensusState, + client_state::AnyClientState, + client_type::ClientType, + context::{ClientKeeper, ClientReader}, + error::Error as ICS02Error, + }, + ics03_connection::{ + connection::{ConnectionEnd, State}, + context::{ConnectionKeeper, ConnectionReader}, + error::Error as ICS03Error, + }, + ics04_channel::{ + channel::ChannelEnd, + context::{ChannelKeeper, ChannelReader}, + error::Error as ICS04Error, + packet::Sequence, + }, + ics24_host::identifier::{ChainId, ChannelId, ClientId, ConnectionId, PortId}, + }, + timestamp::Timestamp, + Height, +}; + +// test store and read client-type +#[test] +fn test_store_client_type_ok() { + let gp_client_type = ClientType::Grandpa; + let gp_client_id = ClientId::new(gp_client_type, 0).unwrap(); + + let mut context: Context = Context::new(); + + new_test_ext().execute_with(|| { + assert!(context.store_client_type(gp_client_id.clone(), gp_client_type).is_ok()); + + let ret = context.client_type(&gp_client_id).unwrap(); + + assert_eq!(ret, gp_client_type); + }) +} + +#[test] +fn test_read_client_type_failed_by_supply_error_client_id() { + let gp_client_type = ClientType::Grandpa; + let gp_client_id = ClientId::new(gp_client_type, 0).unwrap(); + let gp_client_id_failed = ClientId::new(gp_client_type, 1).unwrap(); + let mut context: Context = Context::new(); + + new_test_ext().execute_with(|| { + assert!(context.store_client_type(gp_client_id.clone(), gp_client_type).is_ok()); + + let ret = context.client_type(&gp_client_id_failed).unwrap_err().to_string(); + + assert_eq!(ret, ICS02Error::client_not_found(gp_client_id_failed).to_string()); + }) +} + +// test store client_state +#[test] +fn test_store_client_state_ok() { + let gp_client_id = ClientId::new(ClientType::Grandpa, 0).unwrap(); + + let gp_client_state = GPClientState::new( + ChainId::new("ibc".to_string(), 0), + 0, + BlockHeader::default(), + Commitment::default(), + ValidatorSet::default(), + ) + .unwrap(); + let gp_client_state = AnyClientState::Grandpa(gp_client_state); + + let mut context: Context = Context::new(); + + new_test_ext().execute_with(|| { + assert!(context + .store_client_state(gp_client_id.clone(), gp_client_state.clone()) + .is_ok()); + + let ret = ClientReader::client_state(&context, &gp_client_id).unwrap(); + + assert_eq!(ret, gp_client_state); + }) +} + +#[test] +fn test_read_client_state_failed_by_supply_error_client_id() { + let gp_client_id = ClientId::new(ClientType::Grandpa, 0).unwrap(); + let gp_client_id_failed = ClientId::new(ClientType::Grandpa, 1).unwrap(); + let gp_client_state = GPClientState::new( + ChainId::new("ibc".to_string(), 0), + 0, + BlockHeader::default(), + Commitment::default(), + ValidatorSet::default(), + ) + .unwrap(); + let gp_client_state = AnyClientState::Grandpa(gp_client_state); + + let mut context: Context = Context::new(); + + new_test_ext().execute_with(|| { + assert!(context + .store_client_state(gp_client_id.clone(), gp_client_state.clone()) + .is_ok()); + + let ret = ClientReader::client_state(&context, &gp_client_id_failed) + .unwrap_err() + .to_string(); + + assert_eq!(ret, ICS02Error::client_not_found(gp_client_id_failed).to_string()); + }) +} + +#[test] +fn test_store_consensus_state_ok() { + let gp_client_id = ClientId::new(ClientType::Grandpa, 0).unwrap(); + let height = Height::default(); + let gp_consensus_state = GPConsensusState::default(); + let consensus_state = AnyConsensusState::Grandpa(gp_consensus_state); + + let mut context: Context = Context::new(); + + new_test_ext().execute_with(|| { + assert!(context + .store_consensus_state(gp_client_id.clone(), height, consensus_state.clone()) + .is_ok()); + + let ret = context.consensus_state(&gp_client_id, height).unwrap(); + + assert_eq!(ret, consensus_state); + }) +} + +#[test] +fn test_read_consensus_state_failed_by_supply_error_client_id() { + let gp_client_id = ClientId::new(ClientType::Grandpa, 0).unwrap(); + let gp_client_id_failed = ClientId::new(ClientType::Grandpa, 1).unwrap(); + + let height = Height::default(); + let gp_consensus_state = GPConsensusState::default(); + let consensus_state = AnyConsensusState::Grandpa(gp_consensus_state); + + let mut context: Context = Context::new(); + + new_test_ext().execute_with(|| { + assert_eq!( + context + .store_consensus_state(gp_client_id.clone(), height, consensus_state.clone()) + .is_ok(), + true + ); + + let ret = context.consensus_state(&gp_client_id_failed, height).unwrap_err().to_string(); + assert_eq!( + ret, + ICS02Error::consensus_state_not_found(gp_client_id_failed.clone(), height.clone()) + .to_string() + ); + }) +} + +#[test] +fn test_get_identified_any_client_state_ok() { + let range = (0..10).into_iter().collect::>(); + + let mut client_state_vec = vec![]; + let mut gp_client_id_vec = vec![]; + + for index in range.clone() { + let gp_client_id = ClientId::new(ClientType::Grandpa, index as u64).unwrap(); + let gp_client_state = GPClientState::new( + ChainId::new("ibc".to_string(), 0), + 0, + BlockHeader::default(), + Commitment::default(), + ValidatorSet::default(), + ) + .unwrap(); + let client_state = AnyClientState::Grandpa(gp_client_state); + + gp_client_id_vec.push(gp_client_id); + client_state_vec.push(client_state); + } + + let mut context: Context = Context::new(); + + new_test_ext().execute_with(|| { + for index in 0..range.len() { + assert!(context + .store_client_state( + gp_client_id_vec[index].clone(), + client_state_vec[index].clone() + ) + .is_ok()); + } + }) +} + +#[test] +fn test_get_packet_commitment_state_ok() { + let mut context: Context = Context::new(); + + let range = (0..10).into_iter().collect::>(); + + let mut port_id_vec = vec![]; + let mut channel_id_vec = vec![]; + let mut sequence_vec = vec![]; + + let mut timestamp_vec = vec![]; + let mut height_vec = vec![]; + let mut data_vec = vec![]; + + let mut value_vec = vec![]; + + for index in range.clone() { + let port_id = PortId::from_str(&format!("port-{}", index)).unwrap(); + port_id_vec.push(port_id); + let channel_id = ChannelId::from_str(&format!("channel-{}", index)).unwrap(); + channel_id_vec.push(channel_id); + let sequence = Sequence::from(index as u64); + sequence_vec.push(sequence); + + let timestamp = Timestamp::from_nanoseconds(index as u64).unwrap(); + timestamp_vec.push(timestamp); + let height = Height::new(0, index as u64); + height_vec.push(height); + let data = vec![index]; + data_vec.push(data.clone()); + + let input = format!("{:?},{:?},{:?}", timestamp, height, data); + let value = ChannelReader::hash(&context, input).encode(); + value_vec.push(value); + } + + new_test_ext().execute_with(|| { + for index in 0..range.len() { + assert!(context + .store_packet_commitment( + ( + port_id_vec[index].clone(), + channel_id_vec[index].clone(), + sequence_vec[index] + ), + timestamp_vec[index], + height_vec[index], + data_vec[index].clone(), + ) + .is_ok()); + } + }) +} + +#[test] +fn test_connection_ok() { + use codec::alloc::collections::HashMap; + + let mut input: HashMap = HashMap::new(); + + let connection_id0 = ConnectionId::new(0); + let connection_end0 = ConnectionEnd::default(); + + let connection_id1 = ConnectionId::new(1); + let mut connection_end1 = ConnectionEnd::default(); + + let connection_id2 = ConnectionId::new(2); + let mut connection_end2 = ConnectionEnd::default(); + + input.insert(connection_id0.clone(), connection_end0.clone()); + input.insert(connection_id1.clone(), connection_end1.clone()); + input.insert(connection_id2.clone(), connection_end2.clone()); + + let mut context: Context = Context::new(); + new_test_ext().execute_with(|| { + assert_eq!( + ConnectionKeeper::store_connection( + &mut context, + connection_id0.clone(), + input.get(&connection_id0.clone()).unwrap() + ) + .is_ok(), + true + ); + + let ret = ConnectionReader::connection_end(&mut context, &connection_id0).unwrap(); + assert_eq!(ret, *input.get(&connection_id0.clone()).unwrap()); + + assert_eq!( + ConnectionKeeper::store_connection( + &mut context, + connection_id1.clone(), + input.get(&connection_id1.clone()).unwrap() + ) + .is_ok(), + true + ); + + assert_eq!( + ConnectionKeeper::store_connection( + &mut context, + connection_id2.clone(), + input.get(&connection_id2.clone()).unwrap() + ) + .is_ok(), + true + ); + }) +} + +#[test] +fn test_connection_fail() { + let connection_id0 = ConnectionId::new(0); + let context: Context = Context::new(); + new_test_ext().execute_with(|| { + let ret = ConnectionReader::connection_end(&context, &connection_id0.clone()) + .unwrap_err() + .to_string(); + assert_eq!(ret, ICS03Error::connection_mismatch(connection_id0).to_string()); + }) +} + +#[test] +fn test_connection_client_ok() { + let gp_client_id = ClientId::new(ClientType::Grandpa, 0).unwrap(); + let connection_id = ConnectionId::new(0); + let mut context: Context = Context::new(); + + new_test_ext().execute_with(|| { + assert!(context.store_connection_to_client(connection_id, &gp_client_id).is_ok()); + }) +} + +#[test] +fn test_delete_packet_acknowledgement_ok() { + let port_id = PortId::from_str("transfer").unwrap(); + let channel_id = ChannelId::from_str("channel-0").unwrap(); + let sequence = Sequence::from(0); + let ack = vec![1, 2, 3]; + + let mut context: Context = Context::new(); + + new_test_ext().execute_with(|| { + assert!(context + .store_packet_acknowledgement( + (port_id.clone(), channel_id.clone(), sequence), + ack.clone() + ) + .is_ok()); + + assert!(context + .delete_packet_acknowledgement((port_id.clone(), channel_id.clone(), sequence)) + .is_ok()); + + let result = context + .get_packet_acknowledgement(&(port_id, channel_id, sequence)) + .unwrap_err() + .to_string(); + + assert_eq!(result, ICS04Error::packet_acknowledgement_not_found(sequence).to_string()); + }) +} + +#[test] +fn test_get_acknowledge_state() { + let range = (0..10).into_iter().collect::>(); + + let mut port_id_vec = vec![]; + let mut channel_id_vec = vec![]; + let mut sequence_vec = vec![]; + let mut ack_vec = vec![]; + + let mut value_vec = vec![]; + + let mut context: Context = Context::new(); + + for index in 0..range.len() { + let port_id = PortId::from_str(&format!("transfer-{}", index)).unwrap(); + port_id_vec.push(port_id); + let channel_id = ChannelId::from_str(&format!("channel-{}", index)).unwrap(); + channel_id_vec.push(channel_id); + let sequence = Sequence::from(index as u64); + sequence_vec.push(sequence); + ack_vec.push(vec![index as u8]); + + value_vec.push(ChannelReader::hash(&context, format!("{:?}", vec![index as u8])).encode()); + } + + new_test_ext().execute_with(|| { + for index in 0..range.len() { + assert!(context + .store_packet_acknowledgement( + ( + port_id_vec[index].clone(), + channel_id_vec[index].clone(), + sequence_vec[index] + ), + ack_vec[index].clone() + ) + .is_ok()); + } + }) +} + +#[test] +fn test_store_connection_channles_ok() { + let connection_id = ConnectionId::new(0); + let port_id = PortId::from_str(String::from_str("port-0").unwrap().as_str()).unwrap(); + let channel_id = ChannelId::from_str(String::from_str("channel-0").unwrap().as_str()).unwrap(); + + let mut context: Context = Context::new(); + new_test_ext().execute_with(|| { + assert!(context + .store_connection_channels( + connection_id.clone(), + &(port_id.clone(), channel_id.clone()) + ) + .is_ok()); + + let result = context.connection_channels(&connection_id).unwrap(); + + assert_eq!(result.len(), 1); + + assert_eq!(result[0].0, port_id); + assert_eq!(result[0].1, channel_id); + }) +} + +#[test] +fn test_next_sequence_send_ok() { + let sequence_id = Sequence::from(0); + let port_channel = (PortId::default(), ChannelId::new(0)); + let mut context: Context = Context::new(); + + new_test_ext().execute_with(|| { + assert!(context.store_next_sequence_send(port_channel.clone(), sequence_id).is_ok()); + let result = context.get_next_sequence_send(&port_channel).unwrap(); + assert_eq!(result, sequence_id); + }) +} + +#[test] +fn test_read_conection_channels_failed_by_suppley_error_conneciton_id() { + let connection_id = ConnectionId::new(0); + let connection_id_failed = ConnectionId::new(1); + let port_id = PortId::from_str(String::from_str("port-0").unwrap().as_str()).unwrap(); + let channel_id = ChannelId::from_str(String::from_str("channel-0").unwrap().as_str()).unwrap(); + + let mut context: Context = Context::new(); + new_test_ext().execute_with(|| { + assert!(context + .store_connection_channels( + connection_id.clone(), + &(port_id.clone(), channel_id.clone()) + ) + .is_ok()); + + let result = context.connection_channels(&connection_id_failed).unwrap_err().to_string(); + + assert_eq!( + result, + ICS04Error::connection_not_open(connection_id_failed.clone()).to_string() + ); + }) +} + +#[test] +fn test_store_channel_ok() { + let port_id = PortId::from_str(String::from_str("port-0").unwrap().as_str()).unwrap(); + let channel_id = ChannelId::from_str(String::from_str("channel-0").unwrap().as_str()).unwrap(); + let channel_end = ChannelEnd::default(); + + let mut context: Context = Context::new(); + + new_test_ext().execute_with(|| { + assert!(context + .store_channel((port_id.clone(), channel_id.clone()), &channel_end) + .is_ok()); + + let result = context.channel_end(&(port_id.clone(), channel_id.clone())).unwrap(); + + assert_eq!(result, channel_end); + }) +} + +#[test] + +fn test_next_sequence_send_fail() { + let port_channel = (PortId::default(), ChannelId::new(0)); + let context: Context = Context::new(); + + new_test_ext().execute_with(|| { + let result = context.get_next_sequence_send(&port_channel.clone()).unwrap_err().to_string(); + assert_eq!(result, ICS04Error::missing_next_send_seq(port_channel).to_string()); + }) +} + +#[test] +fn test_next_sequence_recv_ok() { + let sequence_id = Sequence::from(0); + let port_channel = (PortId::default(), ChannelId::new(0)); + let mut context: Context = Context::new(); + + new_test_ext().execute_with(|| { + assert!(context.store_next_sequence_recv(port_channel.clone(), sequence_id).is_ok()); + let result = context.get_next_sequence_recv(&port_channel).unwrap(); + assert_eq!(result, sequence_id); + }) +} + +#[test] +fn test_read_channel_end_failed_by_supply_error_channel_id_port_id() { + let port_id = PortId::from_str(String::from_str("port-0").unwrap().as_str()).unwrap(); + let channel_id = ChannelId::from_str(String::from_str("channel-0").unwrap().as_str()).unwrap(); + let port_id_1 = PortId::from_str(String::from_str("port-1").unwrap().as_str()).unwrap(); + let channel_id_1 = + ChannelId::from_str(String::from_str("channel-1").unwrap().as_str()).unwrap(); + + let channel_end = ChannelEnd::default(); + + let mut context: Context = Context::new(); + + new_test_ext().execute_with(|| { + assert!(context + .store_channel((port_id.clone(), channel_id.clone()), &channel_end) + .is_ok()); + + let result = context + .channel_end(&(port_id_1.clone(), channel_id.clone())) + .unwrap_err() + .to_string(); + + assert_eq!( + result, + ICS04Error::channel_not_found(port_id_1.clone(), channel_id.clone()).to_string() + ); + + let result = context + .channel_end(&(port_id.clone(), channel_id_1.clone())) + .unwrap_err() + .to_string(); + + assert_eq!( + result, + ICS04Error::channel_not_found(port_id.clone(), channel_id_1.clone()).to_string() + ); + + let result = context + .channel_end(&(port_id_1.clone(), channel_id_1.clone())) + .unwrap_err() + .to_string(); + + assert_eq!( + result, + ICS04Error::channel_not_found(port_id_1.clone(), channel_id_1.clone()).to_string() + ); + }) +} + +#[test] +fn test_get_identified_channel_end() { + let range = (0..10).into_iter().collect::>(); + + let mut port_id_vec = vec![]; + let mut channel_id_vec = vec![]; + let channel_end_vec = vec![ChannelEnd::default(); range.len()]; + + for index in 0..range.len() { + let port_id = + PortId::from_str(String::from_str(&format!("prot-{}", index)).unwrap().as_str()) + .unwrap(); + port_id_vec.push(port_id); + let channel_id = + ChannelId::from_str(String::from_str(&format!("channel-{}", index)).unwrap().as_str()) + .unwrap(); + channel_id_vec.push(channel_id); + } + + let mut context: Context = Context::new(); + new_test_ext().execute_with(|| { + for index in 0..range.len() { + assert!(context + .store_channel( + (port_id_vec[index].clone(), channel_id_vec[index].clone()), + &channel_end_vec[index].clone() + ) + .is_ok()); + } + }) +} + +#[test] +fn test_next_sequence_recv_fail() { + let port_channel = (PortId::default(), ChannelId::new(0)); + let context: Context = Context::new(); + + new_test_ext().execute_with(|| { + let result = context.get_next_sequence_recv(&port_channel.clone()).unwrap_err().to_string(); + assert_eq!(result, ICS04Error::missing_next_recv_seq(port_channel).to_string()); + }) +} + +#[test] +fn test_next_sequence_ack_ok() { + let sequence_id = Sequence::from(0); + let port_channel = (PortId::default(), ChannelId::new(0)); + let mut context: Context = Context::new(); + + new_test_ext().execute_with(|| { + assert!(context.store_next_sequence_ack(port_channel.clone(), sequence_id).is_ok()); + let result = context.get_next_sequence_ack(&port_channel).unwrap(); + assert_eq!(result, sequence_id); + }) +} + +#[test] +fn test_next_sequence_ack_fail() { + let port_channel = (PortId::default(), ChannelId::new(0)); + let context: Context = Context::new(); + + new_test_ext().execute_with(|| { + let result = context.get_next_sequence_ack(&port_channel.clone()).unwrap_err().to_string(); + assert_eq!(result, ICS04Error::missing_next_ack_seq(port_channel).to_string()); + }) +} diff --git a/frame/ibc/src/traits.rs b/frame/ibc/src/traits.rs new file mode 100644 index 0000000000000..a9977940a9299 --- /dev/null +++ b/frame/ibc/src/traits.rs @@ -0,0 +1,9 @@ +use alloc::vec::Vec; + +pub trait AssetIdAndNameProvider { + type Err; + + fn try_get_asset_id(name: impl AsRef<[u8]>) -> Result; + + fn try_get_asset_name(asset_id: AssetId) -> Result, Self::Err>; +} diff --git a/frame/ibc/src/utils.rs b/frame/ibc/src/utils.rs new file mode 100644 index 0000000000000..a10a3515cd27d --- /dev/null +++ b/frame/ibc/src/utils.rs @@ -0,0 +1,41 @@ +use crate::Config; +use codec::Encode; +use scale_info::prelude::{fmt::Debug, format, vec::Vec}; + +use super::*; +use ibc::{ + applications::transfer::{error::Error as Ics20Error, VERSION}, + core::ics24_host::identifier::{ChannelId as IbcChannelId, PortId}, + signer::Signer, +}; + +use ibc::{ + core::{ + ics02_client::msgs::ClientMsg, + ics03_connection::msgs::ConnectionMsg, + ics04_channel::msgs::{ChannelMsg, PacketMsg}, + ics26_routing::{handler, msgs::Ics26Envelope}, + }, + events::IbcEvent, +}; + +pub fn host_height() -> u64 { + let block_number = format!("{:?}", >::block_number()); + let current_height: u64 = block_number.parse().unwrap_or_default(); + current_height +} + +pub fn get_channel_escrow_address( + port_id: &PortId, + channel_id: &IbcChannelId, +) -> Result { + let contents = format!("{}/{}", port_id, channel_id); + let mut data = VERSION.as_bytes().to_vec(); + data.extend_from_slice(&[0]); + data.extend_from_slice(contents.as_bytes()); + + let hash = sp_io::hashing::sha2_256(&data).to_vec(); + let mut hex_string = hex::encode_upper(hash); + hex_string.insert_str(0, "0x"); + hex_string.parse::().map_err(Ics20Error::signer) +} From e84f5ea2d78cad6cdb0c9d7be147f766eacd767d Mon Sep 17 00:00:00 2001 From: Davirain Date: Thu, 11 Aug 2022 16:59:14 +0800 Subject: [PATCH 003/484] format code --- frame/ibc/src/context.rs | 1 - frame/ibc/src/events.rs | 7 +- frame/ibc/src/lib.rs | 18 +- .../module/applications/transfer/channel.rs | 549 +++++++++--------- .../src/module/applications/transfer/mod.rs | 6 +- .../transfer/transfer_handle_callback.rs | 12 +- .../module/clients/ics07_tendermint/header.rs | 544 +++++++++-------- .../module/clients/ics07_tendermint/mod.rs | 2 +- frame/ibc/src/module/clients/mod.rs | 1 - frame/ibc/src/module/core/ics02_client.rs | 1 - .../src/module/core/ics02_client/context.rs | 506 ++++++++-------- .../src/module/core/ics02_client/events.rs | 16 +- .../src/module/core/ics02_client/header.rs | 6 +- frame/ibc/src/module/core/ics03_connection.rs | 4 +- frame/ibc/src/module/core/ics04_channel.rs | 19 +- frame/ibc/src/module/core/ics24_host.rs | 5 +- frame/ibc/src/module/core/ics26_routing.rs | 2 +- 17 files changed, 832 insertions(+), 867 deletions(-) diff --git a/frame/ibc/src/context.rs b/frame/ibc/src/context.rs index ccff087884e6a..c1876a0eab1ca 100644 --- a/frame/ibc/src/context.rs +++ b/frame/ibc/src/context.rs @@ -31,7 +31,6 @@ pub struct Context { impl Context { pub fn new() -> Self { - let r = MockRouterBuilder::default() .add_route(MODULE_ID_STR.parse().unwrap(), TransferModule(PhantomData::)) // register transfer Module .unwrap() diff --git a/frame/ibc/src/events.rs b/frame/ibc/src/events.rs index e06a0eea128b8..3c6f968061d92 100644 --- a/frame/ibc/src/events.rs +++ b/frame/ibc/src/events.rs @@ -2,7 +2,6 @@ use crate::*; use core::borrow::Borrow; use ibc::{core::ics26_routing, events::IbcEvent as RawIbcEvent}; - #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct ModuleEvent { pub kind: Vec, @@ -308,7 +307,11 @@ impl From for Event { let packet = value.packet; let ack = value.ack; - Event::::WriteAcknowledgement { height: height.into(), packet: packet.into(), ack } + Event::::WriteAcknowledgement { + height: height.into(), + packet: packet.into(), + ack, + } }, RawIbcEvent::AcknowledgePacket(value) => { let height = value.height; diff --git a/frame/ibc/src/lib.rs b/frame/ibc/src/lib.rs index fc403024c74c0..666c2b292d2c6 100644 --- a/frame/ibc/src/lib.rs +++ b/frame/ibc/src/lib.rs @@ -66,10 +66,8 @@ pub mod utils; use crate::{context::Context, traits::AssetIdAndNameProvider}; -use crate::module::{ - core::ics24_host::{ - ChannelId, ClientId, ClientType, ConnectionId, Height, Packet, PortId, Timestamp, - }, +use crate::module::core::ics24_host::{ + ChannelId, ClientId, ClientType, ConnectionId, Height, Packet, PortId, Timestamp, }; pub const LOG_TARGET: &str = "runtime::pallet-ibc"; @@ -106,6 +104,7 @@ pub mod pallet { use crate::{ events::ModuleEvent, module::{ + applications::transfer::transfer_handle_callback::TransferModule, core::ics24_host::{ ChannelId, ClientId, ClientType, ConnectionId, Height, Packet, PortId, Sequence, Timestamp, @@ -142,7 +141,6 @@ pub mod pallet { signer::Signer, }; use sp_runtime::traits::IdentifyAccount; - use crate::module::applications::transfer::transfer_handle_callback::TransferModule; /// Configure the pallet by specifying the parameters and types on which it depends. #[pallet::config] @@ -480,7 +478,7 @@ pub mod pallet { /// Receive packet ReceivePacket { height: Height, packet: Packet }, /// WriteAcknowledgement packet - WriteAcknowledgement{ height: Height, packet: Packet, ack: Vec }, + WriteAcknowledgement { height: Height, packet: Packet, ack: Vec }, /// Acknowledgements packet AcknowledgePacket { height: Height, packet: Packet }, /// Timeout packet @@ -603,7 +601,8 @@ pub mod pallet { for message in messages { let mut handle_out = HandlerOutputBuilder::new(); - let msg_transfer = MsgTransfer::try_from(message).map_err(|_|Error::::ParserMsgTransferError)?; + let msg_transfer = MsgTransfer::try_from(message) + .map_err(|_| Error::::ParserMsgTransferError)?; let result = ibc::applications::transfer::relay::send_transfer::send_transfer( &mut ctx, &mut handle_out, @@ -612,10 +611,10 @@ pub mod pallet { match result { Ok(_value) => { log::trace!(target: LOG_TARGET, "raw_transfer Successful!"); - } + }, Err(error) => { log::trace!(target: LOG_TARGET, "raw_transfer Error : {:?} ", error); - } + }, } let HandlerOutput::<()> { result, log, events } = handle_out.with_result(()); @@ -653,7 +652,6 @@ pub mod pallet { } } - impl AssetIdAndNameProvider for Pallet { type Err = Error; diff --git a/frame/ibc/src/module/applications/transfer/channel.rs b/frame/ibc/src/module/applications/transfer/channel.rs index 05cafb6e283f3..faf84c444e1b1 100644 --- a/frame/ibc/src/module/applications/transfer/channel.rs +++ b/frame/ibc/src/module/applications/transfer/channel.rs @@ -1,289 +1,286 @@ +use super::transfer_handle_callback::TransferModule; use crate::*; use core::{str::FromStr, time::Duration}; use log::{error, info, trace, warn}; -use super::transfer_handle_callback::TransferModule; use crate::context::Context; use ibc::{ - core::{ - ics02_client::{ - client_consensus::AnyConsensusState, client_state::AnyClientState, - context::ClientReader, - }, - ics03_connection::{ - connection::ConnectionEnd, context::ConnectionReader, error::Error as ICS03Error, - }, - ics04_channel::{ - channel::ChannelEnd, - commitment::{ - AcknowledgementCommitment as IbcAcknowledgementCommitment, - PacketCommitment as IbcPacketCommitment, - }, - context::{ChannelKeeper, ChannelReader}, - error::Error as Ics04Error, - packet::{Receipt, Sequence}, - }, - ics05_port::{context::PortReader, error::Error as Ics05Error}, - ics24_host::{ - identifier::{ChannelId, ClientId, ConnectionId, PortId}, - path::{ - AcksPath, ChannelEndsPath, CommitmentsPath, ConnectionsPath, ReceiptsPath, - SeqAcksPath, SeqRecvsPath, SeqSendsPath, - }, - Path, - }, - ics26_routing::context::ModuleId, - }, - timestamp::Timestamp, - Height, + core::{ + ics02_client::{ + client_consensus::AnyConsensusState, client_state::AnyClientState, + context::ClientReader, + }, + ics03_connection::{ + connection::ConnectionEnd, context::ConnectionReader, error::Error as ICS03Error, + }, + ics04_channel::{ + channel::ChannelEnd, + commitment::{ + AcknowledgementCommitment as IbcAcknowledgementCommitment, + PacketCommitment as IbcPacketCommitment, + }, + context::{ChannelKeeper, ChannelReader}, + error::Error as Ics04Error, + packet::{Receipt, Sequence}, + }, + ics05_port::{context::PortReader, error::Error as Ics05Error}, + ics24_host::{ + identifier::{ChannelId, ClientId, ConnectionId, PortId}, + path::{ + AcksPath, ChannelEndsPath, CommitmentsPath, ConnectionsPath, ReceiptsPath, + SeqAcksPath, SeqRecvsPath, SeqSendsPath, + }, + Path, + }, + ics26_routing::context::ModuleId, + }, + timestamp::Timestamp, + Height, }; impl ChannelReader for TransferModule { - fn channel_end(&self, port_channel_id: &(PortId, ChannelId)) -> Result { - let connect = Context::::new(); - connect.channel_end(port_channel_id) - } - - fn connection_end(&self, connection_id: &ConnectionId) -> Result { - trace!(target:"runtime::pallet-ibc","in channel : [connection_end]"); - let connect = Context::::new(); - ChannelReader::connection_end(&connect,connection_id) - } - - /// Returns the `ChannelsConnection` for the given identifier `conn_id`. - fn connection_channels( - &self, - conn_id: &ConnectionId, - ) -> Result, Ics04Error> { - let connect = Context::::new(); - connect.connection_channels(conn_id) - } - - fn client_state(&self, client_id: &ClientId) -> Result { - - let connect = Context::::new(); - - ChannelReader::client_state(&connect, client_id) - } - - fn client_consensus_state( - &self, - client_id: &ClientId, - height: Height, - ) -> Result { - let connect = Context::::new(); - ChannelReader::client_consensus_state(&connect, client_id, height) - } - - fn get_next_sequence_send( - &self, - port_channel_id: &(PortId, ChannelId), - ) -> Result { - let connect = Context::::new(); - connect.get_next_sequence_send( port_channel_id) - } - - fn get_next_sequence_recv( - &self, - port_channel_id: &(PortId, ChannelId), - ) -> Result { - let connect = Context::::new(); - connect.get_next_sequence_recv( port_channel_id) - } - - fn get_next_sequence_ack( - &self, - port_channel_id: &(PortId, ChannelId), - ) -> Result { - let connect = Context::::new(); - connect.get_next_sequence_ack( port_channel_id) - } - - /// Returns the `PacketCommitment` for the given identifier `(PortId, ChannelId, Sequence)`. - fn get_packet_commitment( - &self, - key: &(PortId, ChannelId, Sequence), - ) -> Result { - let connect = Context::::new(); - connect.get_packet_commitment( key) - } - - fn get_packet_receipt( - &self, - key: &(PortId, ChannelId, Sequence), - ) -> Result { - let connect = Context::::new(); - connect.get_packet_receipt(key) - } - - /// Returns the `Acknowledgements` for the given identifier `(PortId, ChannelId, Sequence)`. - fn get_packet_acknowledgement( - &self, - key: &(PortId, ChannelId, Sequence), - ) -> Result { - let connect = Context::::new(); - connect.get_packet_acknowledgement( key) - } - - /// A hashing function for packet commitments - fn hash(&self, value: Vec) -> Vec { - let connect = Context::::new(); - connect.hash(value) - } - - /// Returns the current height of the local chain. - fn host_height(&self) -> Height { - let connect = Context::::new(); - ChannelReader::host_height(&connect) - } - - /// Returns the current timestamp of the local chain. - fn host_timestamp(&self) -> Timestamp { - let connect = Context::::new(); - ChannelReader::host_timestamp(&connect) - } - - /// Returns the `AnyConsensusState` for the given identifier `height`. - fn host_consensus_state(&self, height: Height) -> Result { - let connect = Context::::new(); - ConnectionReader::host_consensus_state(&connect, height).map_err(Ics04Error::ics03_connection) - } - - fn pending_host_consensus_state(&self) -> Result { - - let connect = Context::::new(); - ClientReader::pending_host_consensus_state(&connect) - .map_err(|e| Ics04Error::ics03_connection(ICS03Error::ics02_client(e))) - } - - /// Returns the `ClientProcessedTimes` for the given identifier `client_id` & `height`. - fn client_update_time( - &self, - client_id: &ClientId, - height: Height, - ) -> Result { - let connect = Context::::new(); - connect.client_update_time(client_id, height) - } - - fn client_update_height( - &self, - client_id: &ClientId, - height: Height, - ) -> Result { - let connect = Context::::new(); - connect.client_update_height(client_id, height) - } - - /// Returns a counter on the number of channel ids have been created thus far. - /// The value of this counter should increase only via method - /// `ChannelKeeper::increase_channel_counter`. - fn channel_counter(&self) -> Result { - let connect = Context::::new(); - connect.channel_counter() - } - - fn max_expected_time_per_block(&self) -> Duration { - let connect = Context::::new(); - connect.max_expected_time_per_block() - } + fn channel_end(&self, port_channel_id: &(PortId, ChannelId)) -> Result { + let connect = Context::::new(); + connect.channel_end(port_channel_id) + } + + fn connection_end(&self, connection_id: &ConnectionId) -> Result { + trace!(target:"runtime::pallet-ibc","in channel : [connection_end]"); + let connect = Context::::new(); + ChannelReader::connection_end(&connect, connection_id) + } + + /// Returns the `ChannelsConnection` for the given identifier `conn_id`. + fn connection_channels( + &self, + conn_id: &ConnectionId, + ) -> Result, Ics04Error> { + let connect = Context::::new(); + connect.connection_channels(conn_id) + } + + fn client_state(&self, client_id: &ClientId) -> Result { + let connect = Context::::new(); + + ChannelReader::client_state(&connect, client_id) + } + + fn client_consensus_state( + &self, + client_id: &ClientId, + height: Height, + ) -> Result { + let connect = Context::::new(); + ChannelReader::client_consensus_state(&connect, client_id, height) + } + + fn get_next_sequence_send( + &self, + port_channel_id: &(PortId, ChannelId), + ) -> Result { + let connect = Context::::new(); + connect.get_next_sequence_send(port_channel_id) + } + + fn get_next_sequence_recv( + &self, + port_channel_id: &(PortId, ChannelId), + ) -> Result { + let connect = Context::::new(); + connect.get_next_sequence_recv(port_channel_id) + } + + fn get_next_sequence_ack( + &self, + port_channel_id: &(PortId, ChannelId), + ) -> Result { + let connect = Context::::new(); + connect.get_next_sequence_ack(port_channel_id) + } + + /// Returns the `PacketCommitment` for the given identifier `(PortId, ChannelId, Sequence)`. + fn get_packet_commitment( + &self, + key: &(PortId, ChannelId, Sequence), + ) -> Result { + let connect = Context::::new(); + connect.get_packet_commitment(key) + } + + fn get_packet_receipt( + &self, + key: &(PortId, ChannelId, Sequence), + ) -> Result { + let connect = Context::::new(); + connect.get_packet_receipt(key) + } + + /// Returns the `Acknowledgements` for the given identifier `(PortId, ChannelId, Sequence)`. + fn get_packet_acknowledgement( + &self, + key: &(PortId, ChannelId, Sequence), + ) -> Result { + let connect = Context::::new(); + connect.get_packet_acknowledgement(key) + } + + /// A hashing function for packet commitments + fn hash(&self, value: Vec) -> Vec { + let connect = Context::::new(); + connect.hash(value) + } + + /// Returns the current height of the local chain. + fn host_height(&self) -> Height { + let connect = Context::::new(); + ChannelReader::host_height(&connect) + } + + /// Returns the current timestamp of the local chain. + fn host_timestamp(&self) -> Timestamp { + let connect = Context::::new(); + ChannelReader::host_timestamp(&connect) + } + + /// Returns the `AnyConsensusState` for the given identifier `height`. + fn host_consensus_state(&self, height: Height) -> Result { + let connect = Context::::new(); + ConnectionReader::host_consensus_state(&connect, height) + .map_err(Ics04Error::ics03_connection) + } + + fn pending_host_consensus_state(&self) -> Result { + let connect = Context::::new(); + ClientReader::pending_host_consensus_state(&connect) + .map_err(|e| Ics04Error::ics03_connection(ICS03Error::ics02_client(e))) + } + + /// Returns the `ClientProcessedTimes` for the given identifier `client_id` & `height`. + fn client_update_time( + &self, + client_id: &ClientId, + height: Height, + ) -> Result { + let connect = Context::::new(); + connect.client_update_time(client_id, height) + } + + fn client_update_height( + &self, + client_id: &ClientId, + height: Height, + ) -> Result { + let connect = Context::::new(); + connect.client_update_height(client_id, height) + } + + /// Returns a counter on the number of channel ids have been created thus far. + /// The value of this counter should increase only via method + /// `ChannelKeeper::increase_channel_counter`. + fn channel_counter(&self) -> Result { + let connect = Context::::new(); + connect.channel_counter() + } + + fn max_expected_time_per_block(&self) -> Duration { + let connect = Context::::new(); + connect.max_expected_time_per_block() + } } impl ChannelKeeper for TransferModule { - fn store_packet_commitment( - &mut self, - key: (PortId, ChannelId, Sequence), - commitment: IbcPacketCommitment, - ) -> Result<(), Ics04Error> { - let mut connect = Context::::new(); - connect.store_packet_commitment(key, commitment) - } - - fn delete_packet_commitment( - &mut self, - key: (PortId, ChannelId, Sequence), - ) -> Result<(), Ics04Error> { - let mut connect = Context::::new(); - connect.delete_packet_commitment( key) - } - - fn store_packet_receipt( - &mut self, - key: (PortId, ChannelId, Sequence), - receipt: Receipt, - ) -> Result<(), Ics04Error> { - - let mut connect = Context::::new(); - connect.store_packet_receipt( key, receipt) - } - - fn store_packet_acknowledgement( - &mut self, - key: (PortId, ChannelId, Sequence), - ack_commitment: IbcAcknowledgementCommitment, - ) -> Result<(), Ics04Error> { - - let mut connect = Context::::new(); - - connect.store_packet_acknowledgement( key, ack_commitment) - } - - fn delete_packet_acknowledgement( - &mut self, - key: (PortId, ChannelId, Sequence), - ) -> Result<(), Ics04Error> { - let mut connect = Context::::new(); - connect.delete_packet_acknowledgement( key) - } - - fn store_connection_channels( - &mut self, - conn_id: ConnectionId, - port_channel_id: &(PortId, ChannelId), - ) -> Result<(), Ics04Error> { - let mut connect = Context::::new(); - connect.store_connection_channels( conn_id, port_channel_id) - } - - /// Stores the given channel_end at a path associated with the port_id and channel_id. - fn store_channel( - &mut self, - port_channel_id: (PortId, ChannelId), - channel_end: &ChannelEnd, - ) -> Result<(), Ics04Error> { - let mut connect = Context::::new(); - connect.store_channel( port_channel_id, channel_end) - } - - fn store_next_sequence_send( - &mut self, - port_channel_id: (PortId, ChannelId), - seq: Sequence, - ) -> Result<(), Ics04Error> { - let mut connect = Context::::new(); - connect.store_next_sequence_send(port_channel_id, seq) - } - - fn store_next_sequence_recv( - &mut self, - port_channel_id: (PortId, ChannelId), - seq: Sequence, - ) -> Result<(), Ics04Error> { - let mut connect = Context::::new(); - connect.store_next_sequence_recv( port_channel_id, seq) - } - - fn store_next_sequence_ack( - &mut self, - port_channel_id: (PortId, ChannelId), - seq: Sequence, - ) -> Result<(), Ics04Error> { - let mut connect = Context::::new(); - connect.store_next_sequence_ack(port_channel_id, seq) - } - - fn increase_channel_counter(&mut self) { - let mut connect = Context::::new(); - connect.increase_channel_counter() - } + fn store_packet_commitment( + &mut self, + key: (PortId, ChannelId, Sequence), + commitment: IbcPacketCommitment, + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + connect.store_packet_commitment(key, commitment) + } + + fn delete_packet_commitment( + &mut self, + key: (PortId, ChannelId, Sequence), + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + connect.delete_packet_commitment(key) + } + + fn store_packet_receipt( + &mut self, + key: (PortId, ChannelId, Sequence), + receipt: Receipt, + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + connect.store_packet_receipt(key, receipt) + } + + fn store_packet_acknowledgement( + &mut self, + key: (PortId, ChannelId, Sequence), + ack_commitment: IbcAcknowledgementCommitment, + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + + connect.store_packet_acknowledgement(key, ack_commitment) + } + + fn delete_packet_acknowledgement( + &mut self, + key: (PortId, ChannelId, Sequence), + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + connect.delete_packet_acknowledgement(key) + } + + fn store_connection_channels( + &mut self, + conn_id: ConnectionId, + port_channel_id: &(PortId, ChannelId), + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + connect.store_connection_channels(conn_id, port_channel_id) + } + + /// Stores the given channel_end at a path associated with the port_id and channel_id. + fn store_channel( + &mut self, + port_channel_id: (PortId, ChannelId), + channel_end: &ChannelEnd, + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + connect.store_channel(port_channel_id, channel_end) + } + + fn store_next_sequence_send( + &mut self, + port_channel_id: (PortId, ChannelId), + seq: Sequence, + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + connect.store_next_sequence_send(port_channel_id, seq) + } + + fn store_next_sequence_recv( + &mut self, + port_channel_id: (PortId, ChannelId), + seq: Sequence, + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + connect.store_next_sequence_recv(port_channel_id, seq) + } + + fn store_next_sequence_ack( + &mut self, + port_channel_id: (PortId, ChannelId), + seq: Sequence, + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + connect.store_next_sequence_ack(port_channel_id, seq) + } + + fn increase_channel_counter(&mut self) { + let mut connect = Context::::new(); + connect.increase_channel_counter() + } } diff --git a/frame/ibc/src/module/applications/transfer/mod.rs b/frame/ibc/src/module/applications/transfer/mod.rs index 4bdc56939d15e..17ee3696157c9 100644 --- a/frame/ibc/src/module/applications/transfer/mod.rs +++ b/frame/ibc/src/module/applications/transfer/mod.rs @@ -1,5 +1,5 @@ -pub mod transfer_handle_callback; pub mod channel; +pub mod transfer_handle_callback; use crate::{context::Context, *}; use frame_support::traits::{ @@ -42,8 +42,8 @@ impl BankKeeper for TransferModule { // match is_native_asset { // // transfer native token // true => { - // let amount = amt.amount.as_u256().low_u128().checked_into().unwrap(); // TODO: FIX IN THE FUTURE - // let native_token_name = T::NATIVE_TOKEN_NAME; + // let amount = amt.amount.as_u256().low_u128().checked_into().unwrap(); // TODO: FIX IN THE + // FUTURE let native_token_name = T::NATIVE_TOKEN_NAME; // let ibc_token_name = amt.denom.base_denom().as_str().as_bytes(); // // assert native token name equal want to send ibc token name diff --git a/frame/ibc/src/module/applications/transfer/transfer_handle_callback.rs b/frame/ibc/src/module/applications/transfer/transfer_handle_callback.rs index e48b5d485cfc8..8dd69b074172b 100644 --- a/frame/ibc/src/module/applications/transfer/transfer_handle_callback.rs +++ b/frame/ibc/src/module/applications/transfer/transfer_handle_callback.rs @@ -33,7 +33,6 @@ impl Module for TransferModule { counterparty: &Counterparty, version: &Version, ) -> Result<(), Ics04Error> { - Ok(ibc::applications::transfer::context::on_chan_open_init( self, output, @@ -43,7 +42,8 @@ impl Module for TransferModule { channel_id, counterparty, version, - ).unwrap()) + ) + .unwrap()) } fn on_chan_open_try( @@ -57,7 +57,6 @@ impl Module for TransferModule { version: &Version, counterparty_version: &Version, ) -> Result { - Ok(ibc::applications::transfer::context::on_chan_open_try( self, output, @@ -79,7 +78,6 @@ impl Module for TransferModule { channel_id: &IbcChannelId, counterparty_version: &Version, ) -> Result<(), Ics04Error> { - Ok(ibc::applications::transfer::context::on_chan_open_ack( self, output, @@ -96,7 +94,6 @@ impl Module for TransferModule { port_id: &PortId, channel_id: &IbcChannelId, ) -> Result<(), Ics04Error> { - Ok(ibc::applications::transfer::context::on_chan_open_confirm( self, output, port_id, channel_id, ) @@ -109,7 +106,6 @@ impl Module for TransferModule { port_id: &PortId, channel_id: &IbcChannelId, ) -> Result<(), Ics04Error> { - Ok(ibc::applications::transfer::context::on_chan_close_init( self, output, port_id, channel_id, ) @@ -122,7 +118,6 @@ impl Module for TransferModule { port_id: &PortId, channel_id: &IbcChannelId, ) -> Result<(), Ics04Error> { - Ok(ibc::applications::transfer::context::on_chan_close_confirm( self, output, port_id, channel_id, ) @@ -135,7 +130,6 @@ impl Module for TransferModule { packet: &IbcPacket, relayer: &Signer, ) -> OnRecvPacketAck { - ibc::applications::transfer::context::on_recv_packet(self, output, packet, relayer) } @@ -146,7 +140,6 @@ impl Module for TransferModule { acknowledgement: &GenericAcknowledgement, relayer: &Signer, ) -> Result<(), Ics04Error> { - Ok(ibc::applications::transfer::context::on_acknowledgement_packet( self, output, @@ -163,7 +156,6 @@ impl Module for TransferModule { packet: &IbcPacket, relayer: &Signer, ) -> Result<(), Ics04Error> { - Ok(ibc::applications::transfer::context::on_timeout_packet(self, output, packet, relayer) .unwrap()) } diff --git a/frame/ibc/src/module/clients/ics07_tendermint/header.rs b/frame/ibc/src/module/clients/ics07_tendermint/header.rs index 2bbfd4e9c3087..46f09a587f90c 100644 --- a/frame/ibc/src/module/clients/ics07_tendermint/header.rs +++ b/frame/ibc/src/module/clients/ics07_tendermint/header.rs @@ -1,59 +1,58 @@ -use time::PrimitiveDateTime; use crate::module::core::ics24_host::Height; use alloc::vec::Vec; use codec::{Decode, Encode}; use scale_info::TypeInfo; use sp_runtime::RuntimeDebug; - +use time::PrimitiveDateTime; /// Tendermint consensus header #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct Header { - pub signed_header: SignedHeader, // contains the commitment root - pub validator_set: ValidatorSet, // the validator set that signed Header - pub trusted_height: Height, // the height of a trusted header seen by client less than or equal to Header - // TODO(thane): Rename this to trusted_next_validator_set? - pub trusted_validator_set: ValidatorSet, // the last trusted validator set at trusted height + pub signed_header: SignedHeader, // contains the commitment root + pub validator_set: ValidatorSet, // the validator set that signed Header + pub trusted_height: Height, /* the height of a trusted header seen by client less than + * or equal to Header */ + // TODO(thane): Rename this to trusted_next_validator_set? + pub trusted_validator_set: ValidatorSet, // the last trusted validator set at trusted height } /// Signed block headers #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] #[non_exhaustive] pub struct SignedHeader { - /// Block header - pub header: block::Header, - /// Commit containing signatures for the header - pub commit: block::Commit, + /// Block header + pub header: block::Header, + /// Commit containing signatures for the header + pub commit: block::Commit, } - /// Validator set contains a vector of validators #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct ValidatorSet { - validators: Vec, - proposer: Option, - total_voting_power: vote::Power, + validators: Vec, + proposer: Option, + total_voting_power: vote::Power, } /// Validator information // Todo: Remove address and make it into a function that generates it on the fly from pub_key. #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct Info { - /// Validator account address - pub address: account::Id, + /// Validator account address + pub address: account::Id, - /// Validator public key - pub pub_key: PublicKey, + /// Validator public key + pub pub_key: PublicKey, - /// Validator voting power - // Compatibility with genesis.json https://github.com/tendermint/tendermint/issues/5549 - pub power: vote::Power, + /// Validator voting power + // Compatibility with genesis.json https://github.com/tendermint/tendermint/issues/5549 + pub power: vote::Power, - /// Validator name - pub name: Option>, + /// Validator name + pub name: Option>, - /// Validator proposer priority - pub proposer_priority: ProposerPriority, + /// Validator proposer priority + pub proposer_priority: ProposerPriority, } // Todo: Is there more knowledge/restrictions about proposerPriority? @@ -62,13 +61,13 @@ pub struct Info { pub struct ProposerPriority(i64); mod vote { - use codec::{Decode, Encode}; - use scale_info::TypeInfo; - use sp_runtime::RuntimeDebug; + use codec::{Decode, Encode}; + use scale_info::TypeInfo; + use sp_runtime::RuntimeDebug; - /// Voting power - #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] - pub struct Power(u64); + /// Voting power + #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] + pub struct Power(u64); } // Note:On the golang side this is generic in the sense that it could everything that implements @@ -85,255 +84,250 @@ mod vote { #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] #[non_exhaustive] pub enum PublicKey { - /// Ed25519 keys - Ed25519, + /// Ed25519 keys + Ed25519, - /// Secp256k1 keys - Secp256k1, + /// Secp256k1 keys + Secp256k1, } mod account { - use codec::{Decode, Encode}; - use scale_info::TypeInfo; - use sp_runtime::RuntimeDebug; - - /// Size of an account ID in bytes - pub const LENGTH: usize = 20; + use codec::{Decode, Encode}; + use scale_info::TypeInfo; + use sp_runtime::RuntimeDebug; - /// Account IDs - #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] - pub struct Id([u8; LENGTH]); // JSON custom serialization for priv_validator_key.json + /// Size of an account ID in bytes + pub const LENGTH: usize = 20; + /// Account IDs + #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] + pub struct Id([u8; LENGTH]); // JSON custom serialization for priv_validator_key.json } mod block { - use codec::{Decode, Encode}; - use scale_info::TypeInfo; - use sp_runtime::RuntimeDebug; - use time::PrimitiveDateTime; - use alloc::vec::Vec; - use crate::module::clients::ics07_tendermint::header::account; - - /// Block height for a particular chain (i.e. number of blocks created since - /// the chain began) - /// - /// A height of 0 represents a chain which has not yet produced a block. - #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] - pub struct Height(u64); - - mod chain { - use codec::{Decode, Encode}; - use alloc::vec::Vec; - use scale_info::TypeInfo; - use sp_runtime::RuntimeDebug; - - /// Chain identifier (e.g. 'gaia-9000') - #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] - pub struct Id(Vec); - } - - /// `Version` contains the protocol version for the blockchain and the - /// application. - /// - /// - #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] - pub struct Version { - /// Block version - pub block: u64, - - /// App version - pub app: u64, - } - - /// Tendermint timestamps - /// - /// A `Time` value is guaranteed to represent a valid `Timestamp` as defined - /// by Google's well-known protobuf type [specification]. Conversions and - /// operations that would result in exceeding `Timestamp`'s validity - /// range return an error or `None`. - /// - /// The string serialization format for `Time` is defined as an RFC 3339 - /// compliant string with the optional subsecond fraction part having - /// up to 9 digits and no trailing zeros, and the UTC offset denoted by Z. - /// This reproduces the behavior of Go's `time.RFC3339Nano` format. - /// - /// [specification]: https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Timestamp - // For memory efficiency, the inner member is `PrimitiveDateTime`, with assumed - // UTC offset. The `assume_utc` method is used to get the operational - // `OffsetDateTime` value. - #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] - // pub struct Time(PrimitiveDateTime); - pub struct Time; - - /// Block identifiers which contain two distinct Merkle roots of the block, - /// as well as the number of parts in the block. - /// - /// - /// - /// Default implementation is an empty Id as defined by the Go implementation in - /// . - /// - /// If the Hash is empty in BlockId, the BlockId should be empty (encoded to None). - /// This is implemented outside of this struct. Use the Default trait to check for an empty BlockId. - /// See: - #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] - pub struct Id { - /// The block's main hash is the Merkle root of all the fields in the - /// block header. - pub hash: Hash, - - /// Parts header (if available) is used for secure gossipping of the block - /// during consensus. It is the Merkle root of the complete serialized block - /// cut into parts. - /// - /// PartSet is used to split a byteslice of data into parts (pieces) for - /// transmission. By splitting data into smaller parts and computing a - /// Merkle root hash on the list, you can verify that a part is - /// legitimately part of the complete data, and the part can be forwarded - /// to other peers before all the parts are known. In short, it's a fast - /// way to propagate a large file over a gossip network. - /// - /// - /// - /// PartSetHeader in protobuf is defined as never nil using the gogoproto - /// annotations. This does not translate to Rust, but we can indicate this - /// in the domain type. - pub part_set_header: PartSetHeader, - } - - /// Block parts header - #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] - #[non_exhaustive] - pub struct PartSetHeader { - /// Number of parts in this block - pub total: u32, - - /// Hash of the parts set header, - pub hash: Hash, - } - - /// Output size for the SHA-256 hash function - pub const SHA256_HASH_SIZE: usize = 32; - - /// Hash digests - #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] - pub enum Hash { - /// SHA-256 hashes - Sha256([u8; SHA256_HASH_SIZE]), - /// Empty hash - None, - } - - - /// AppHash is usually a SHA256 hash, but in reality it can be any kind of data - #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] - pub struct AppHash(Vec); - - - - /// Block `Header` values contain metadata about the block and about the - /// consensus, as well as commitments to the data in the current block, the - /// previous block, and the results returned by the application. - /// - /// - #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] - pub struct Header { - /// Header version - pub version: Version, - - /// Chain ID - pub chain_id: chain::Id, - - /// Current block height - pub height: Height, - - /// Current timestamp - pub time: Time, - - /// Previous block info - pub last_block_id: Option, - - /// Commit from validators from the last block - pub last_commit_hash: Option, - - /// Merkle root of transaction hashes - pub data_hash: Option, - - /// Validators for the current block - pub validators_hash: Hash, - - /// Validators for the next block - pub next_validators_hash: Hash, - - /// Consensus params for the current block - pub consensus_hash: Hash, - - /// State after txs from the previous block - pub app_hash: AppHash, - - /// Root hash of all results from the txs from the previous block - pub last_results_hash: Option, - - /// Hash of evidence included in the block - pub evidence_hash: Option, - - /// Original proposer of the block - pub proposer_address: account::Id, - } - - - /// Block round for a particular chain - #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] - pub struct Round(u32); - - - /// Signatures - #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] - pub struct Signature(Vec); - - /// CommitSig represents a signature of a validator. - /// It's a part of the Commit and can be used to reconstruct the vote set given the validator set. - #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] - pub enum CommitSig { - /// no vote was received from a validator. - BlockIdFlagAbsent, - /// voted for the Commit.BlockID. - BlockIdFlagCommit { - /// Validator address - validator_address: account::Id, - /// Timestamp of vote - timestamp: Time, - /// Signature of vote - signature: Option, - }, - /// voted for nil. - BlockIdFlagNil { - /// Validator address - validator_address: account::Id, - /// Timestamp of vote - timestamp: Time, - /// Signature of vote - signature: Option, - }, - } - - /// Commit contains the justification (ie. a set of signatures) that a block was committed by a set - /// of validators. - /// TODO: Update links below! - /// - /// - #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] - pub struct Commit { - /// Block height - pub height: Height, - - /// Round - pub round: Round, - - /// Block ID - pub block_id: Id, - - /// Signatures - pub signatures: Vec, - } -} \ No newline at end of file + use crate::module::clients::ics07_tendermint::header::account; + use alloc::vec::Vec; + use codec::{Decode, Encode}; + use scale_info::TypeInfo; + use sp_runtime::RuntimeDebug; + use time::PrimitiveDateTime; + + /// Block height for a particular chain (i.e. number of blocks created since + /// the chain began) + /// + /// A height of 0 represents a chain which has not yet produced a block. + #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] + pub struct Height(u64); + + mod chain { + use alloc::vec::Vec; + use codec::{Decode, Encode}; + use scale_info::TypeInfo; + use sp_runtime::RuntimeDebug; + + /// Chain identifier (e.g. 'gaia-9000') + #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] + pub struct Id(Vec); + } + + /// `Version` contains the protocol version for the blockchain and the + /// application. + /// + /// + #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] + pub struct Version { + /// Block version + pub block: u64, + + /// App version + pub app: u64, + } + + /// Tendermint timestamps + /// + /// A `Time` value is guaranteed to represent a valid `Timestamp` as defined + /// by Google's well-known protobuf type [specification]. Conversions and + /// operations that would result in exceeding `Timestamp`'s validity + /// range return an error or `None`. + /// + /// The string serialization format for `Time` is defined as an RFC 3339 + /// compliant string with the optional subsecond fraction part having + /// up to 9 digits and no trailing zeros, and the UTC offset denoted by Z. + /// This reproduces the behavior of Go's `time.RFC3339Nano` format. + /// + /// [specification]: https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Timestamp + // For memory efficiency, the inner member is `PrimitiveDateTime`, with assumed + // UTC offset. The `assume_utc` method is used to get the operational + // `OffsetDateTime` value. + #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] + // pub struct Time(PrimitiveDateTime); + pub struct Time; + + /// Block identifiers which contain two distinct Merkle roots of the block, + /// as well as the number of parts in the block. + /// + /// + /// + /// Default implementation is an empty Id as defined by the Go implementation in + /// . + /// + /// If the Hash is empty in BlockId, the BlockId should be empty (encoded to None). + /// This is implemented outside of this struct. Use the Default trait to check for an empty + /// BlockId. See: + #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] + pub struct Id { + /// The block's main hash is the Merkle root of all the fields in the + /// block header. + pub hash: Hash, + + /// Parts header (if available) is used for secure gossipping of the block + /// during consensus. It is the Merkle root of the complete serialized block + /// cut into parts. + /// + /// PartSet is used to split a byteslice of data into parts (pieces) for + /// transmission. By splitting data into smaller parts and computing a + /// Merkle root hash on the list, you can verify that a part is + /// legitimately part of the complete data, and the part can be forwarded + /// to other peers before all the parts are known. In short, it's a fast + /// way to propagate a large file over a gossip network. + /// + /// + /// + /// PartSetHeader in protobuf is defined as never nil using the gogoproto + /// annotations. This does not translate to Rust, but we can indicate this + /// in the domain type. + pub part_set_header: PartSetHeader, + } + + /// Block parts header + #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] + #[non_exhaustive] + pub struct PartSetHeader { + /// Number of parts in this block + pub total: u32, + + /// Hash of the parts set header, + pub hash: Hash, + } + + /// Output size for the SHA-256 hash function + pub const SHA256_HASH_SIZE: usize = 32; + + /// Hash digests + #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] + pub enum Hash { + /// SHA-256 hashes + Sha256([u8; SHA256_HASH_SIZE]), + /// Empty hash + None, + } + + /// AppHash is usually a SHA256 hash, but in reality it can be any kind of data + #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] + pub struct AppHash(Vec); + + /// Block `Header` values contain metadata about the block and about the + /// consensus, as well as commitments to the data in the current block, the + /// previous block, and the results returned by the application. + /// + /// + #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] + pub struct Header { + /// Header version + pub version: Version, + + /// Chain ID + pub chain_id: chain::Id, + + /// Current block height + pub height: Height, + + /// Current timestamp + pub time: Time, + + /// Previous block info + pub last_block_id: Option, + + /// Commit from validators from the last block + pub last_commit_hash: Option, + + /// Merkle root of transaction hashes + pub data_hash: Option, + + /// Validators for the current block + pub validators_hash: Hash, + + /// Validators for the next block + pub next_validators_hash: Hash, + + /// Consensus params for the current block + pub consensus_hash: Hash, + + /// State after txs from the previous block + pub app_hash: AppHash, + + /// Root hash of all results from the txs from the previous block + pub last_results_hash: Option, + + /// Hash of evidence included in the block + pub evidence_hash: Option, + + /// Original proposer of the block + pub proposer_address: account::Id, + } + + /// Block round for a particular chain + #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] + pub struct Round(u32); + + /// Signatures + #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] + pub struct Signature(Vec); + + /// CommitSig represents a signature of a validator. + /// It's a part of the Commit and can be used to reconstruct the vote set given the validator + /// set. + #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] + pub enum CommitSig { + /// no vote was received from a validator. + BlockIdFlagAbsent, + /// voted for the Commit.BlockID. + BlockIdFlagCommit { + /// Validator address + validator_address: account::Id, + /// Timestamp of vote + timestamp: Time, + /// Signature of vote + signature: Option, + }, + /// voted for nil. + BlockIdFlagNil { + /// Validator address + validator_address: account::Id, + /// Timestamp of vote + timestamp: Time, + /// Signature of vote + signature: Option, + }, + } + + /// Commit contains the justification (ie. a set of signatures) that a block was committed by a + /// set of validators. + /// TODO: Update links below! + /// + /// + #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] + pub struct Commit { + /// Block height + pub height: Height, + + /// Round + pub round: Round, + + /// Block ID + pub block_id: Id, + + /// Signatures + pub signatures: Vec, + } +} diff --git a/frame/ibc/src/module/clients/ics07_tendermint/mod.rs b/frame/ibc/src/module/clients/ics07_tendermint/mod.rs index 311ecc229cd23..f505d688b456d 100644 --- a/frame/ibc/src/module/clients/ics07_tendermint/mod.rs +++ b/frame/ibc/src/module/clients/ics07_tendermint/mod.rs @@ -1 +1 @@ -pub mod header; \ No newline at end of file +pub mod header; diff --git a/frame/ibc/src/module/clients/mod.rs b/frame/ibc/src/module/clients/mod.rs index 25470650e1860..cc3da1b010b51 100644 --- a/frame/ibc/src/module/clients/mod.rs +++ b/frame/ibc/src/module/clients/mod.rs @@ -1,2 +1 @@ - pub mod ics07_tendermint; diff --git a/frame/ibc/src/module/core/ics02_client.rs b/frame/ibc/src/module/core/ics02_client.rs index 64940c52a27ed..c68efb201404f 100644 --- a/frame/ibc/src/module/core/ics02_client.rs +++ b/frame/ibc/src/module/core/ics02_client.rs @@ -1,4 +1,3 @@ pub mod context; pub mod events; pub mod header; - diff --git a/frame/ibc/src/module/core/ics02_client/context.rs b/frame/ibc/src/module/core/ics02_client/context.rs index 727a652368485..0ba687ad35b78 100644 --- a/frame/ibc/src/module/core/ics02_client/context.rs +++ b/frame/ibc/src/module/core/ics02_client/context.rs @@ -5,293 +5,287 @@ use log::{error, info, trace, warn}; use crate::context::Context; use ibc::{ - core::{ - ics02_client::{ - client_consensus::AnyConsensusState, - client_state::AnyClientState, - client_type::ClientType, - context::{ClientKeeper, ClientReader}, - error::Error as Ics02Error, - }, - ics24_host::{ - identifier::ClientId, - path::{ClientConsensusStatePath, ClientStatePath, ClientTypePath}, - }, - }, - timestamp::Timestamp, - Height, + core::{ + ics02_client::{ + client_consensus::AnyConsensusState, + client_state::AnyClientState, + client_type::ClientType, + context::{ClientKeeper, ClientReader}, + error::Error as Ics02Error, + }, + ics24_host::{ + identifier::ClientId, + path::{ClientConsensusStatePath, ClientStatePath, ClientTypePath}, + }, + }, + timestamp::Timestamp, + Height, }; impl ClientReader for Context { - fn client_type(&self, client_id: &ClientId) -> Result { - trace!(target:"runtime::pallet-ibc","in client : [client_type]"); - - let client_type_path = ClientTypePath(client_id.clone()).to_string().as_bytes().to_vec(); - if >::contains_key(client_type_path.clone()) { - let data = >::get(client_type_path); - let mut data: &[u8] = &data; - let data = Vec::::decode(&mut data).unwrap(); - let data = String::from_utf8(data).unwrap(); - let client_type = ClientType::from_str(&data) - .map_err(|e| Ics02Error::unknown_client_type(e.to_string()))?; - Ok(client_type) - } else { - trace!(target:"runtime::pallet-ibc","in client : [client_type] >> read client_type is None"); - Err(Ics02Error::client_not_found(client_id.clone())) - } - } - - fn client_state(&self, client_id: &ClientId) -> Result { - trace!(target:"runtime::pallet-ibc","in client : [client_state]"); - - let client_state_path = ClientStatePath(client_id.clone()).to_string().as_bytes().to_vec(); - - if >::contains_key(&client_state_path) { - let data = >::get(&client_state_path); - let result = AnyClientState::decode_vec(&*data).unwrap(); - trace!(target:"runtime::pallet-ibc","in client : [client_state] >> any client_state: {:?}", result); - - Ok(result) - } else { - trace!(target:"runtime::pallet-ibc","in client : [client_state] >> read any client state is None"); - Err(Ics02Error::client_not_found(client_id.clone())) - } - } - - fn consensus_state( - &self, - client_id: &ClientId, - height: Height, - ) -> Result { - trace!(target:"runtime::pallet-ibc", + fn client_type(&self, client_id: &ClientId) -> Result { + trace!(target:"runtime::pallet-ibc","in client : [client_type]"); + + let client_type_path = ClientTypePath(client_id.clone()).to_string().as_bytes().to_vec(); + if >::contains_key(client_type_path.clone()) { + let data = >::get(client_type_path); + let mut data: &[u8] = &data; + let data = Vec::::decode(&mut data).unwrap(); + let data = String::from_utf8(data).unwrap(); + let client_type = ClientType::from_str(&data) + .map_err(|e| Ics02Error::unknown_client_type(e.to_string()))?; + Ok(client_type) + } else { + trace!(target:"runtime::pallet-ibc","in client : [client_type] >> read client_type is None"); + Err(Ics02Error::client_not_found(client_id.clone())) + } + } + + fn client_state(&self, client_id: &ClientId) -> Result { + trace!(target:"runtime::pallet-ibc","in client : [client_state]"); + + let client_state_path = ClientStatePath(client_id.clone()).to_string().as_bytes().to_vec(); + + if >::contains_key(&client_state_path) { + let data = >::get(&client_state_path); + let result = AnyClientState::decode_vec(&*data).unwrap(); + trace!(target:"runtime::pallet-ibc","in client : [client_state] >> any client_state: {:?}", result); + + Ok(result) + } else { + trace!(target:"runtime::pallet-ibc","in client : [client_state] >> read any client state is None"); + Err(Ics02Error::client_not_found(client_id.clone())) + } + } + + fn consensus_state( + &self, + client_id: &ClientId, + height: Height, + ) -> Result { + trace!(target:"runtime::pallet-ibc", "in client : [consensus_state]" ); - // search key - let client_consensus_state_path = ClientConsensusStatePath { - client_id: client_id.clone(), - epoch: height.revision_number(), - height: height.revision_height(), - } - .to_string() - .as_bytes() - .to_vec(); - - if >::contains_key(client_consensus_state_path.clone()) { - let values = >::get(client_consensus_state_path.clone()); - let any_consensus_state = - AnyConsensusState::decode_vec(&*values).unwrap(); - trace!(target:"runtime::pallet-ibc", + // search key + let client_consensus_state_path = ClientConsensusStatePath { + client_id: client_id.clone(), + epoch: height.revision_number(), + height: height.revision_height(), + } + .to_string() + .as_bytes() + .to_vec(); + + if >::contains_key(client_consensus_state_path.clone()) { + let values = >::get(client_consensus_state_path.clone()); + let any_consensus_state = AnyConsensusState::decode_vec(&*values).unwrap(); + trace!(target:"runtime::pallet-ibc", "in client : [consensus_state] >> any consensus state = {:?}", any_consensus_state ); - Ok(any_consensus_state) - } else { - Err(Ics02Error::consensus_state_not_found(client_id.clone(), height)) - } - } - - fn next_consensus_state( - &self, - client_id: &ClientId, - height: Height, - ) -> Result, Ics02Error> { - trace!(target:"runtime::pallet-ibc","in client : [next_consensus_state]"); - - // search key - let client_consensus_state_path = ClientConsensusStatePath { - client_id: client_id.clone(), - epoch: height.revision_number(), - height: height.revision_height(), - } - .to_string() - .as_bytes() - .to_vec(); - - if >::contains_key(client_consensus_state_path.clone()) { - let values = >::get(client_consensus_state_path.clone()); - let any_consensus_state = - AnyConsensusState::decode_vec(&*values).unwrap(); - trace!(target:"runtime::pallet-ibc", + Ok(any_consensus_state) + } else { + Err(Ics02Error::consensus_state_not_found(client_id.clone(), height)) + } + } + + fn next_consensus_state( + &self, + client_id: &ClientId, + height: Height, + ) -> Result, Ics02Error> { + trace!(target:"runtime::pallet-ibc","in client : [next_consensus_state]"); + + // search key + let client_consensus_state_path = ClientConsensusStatePath { + client_id: client_id.clone(), + epoch: height.revision_number(), + height: height.revision_height(), + } + .to_string() + .as_bytes() + .to_vec(); + + if >::contains_key(client_consensus_state_path.clone()) { + let values = >::get(client_consensus_state_path.clone()); + let any_consensus_state = AnyConsensusState::decode_vec(&*values).unwrap(); + trace!(target:"runtime::pallet-ibc", "in client : [next_consensus_state] >> any consensus state = {:?}", any_consensus_state ); - Ok(Some(any_consensus_state)) - } else { - trace!(target:"runtime::pallet-ibc", + Ok(Some(any_consensus_state)) + } else { + trace!(target:"runtime::pallet-ibc", "in client : [next_consensus_state] >> any consensus state is not contains at ConsensusStates : client_id = {:?}, height = {:?}", client_id, height ); - Err(Ics02Error::consensus_state_not_found(client_id.clone(), height)) - } - } - - fn prev_consensus_state( - &self, - client_id: &ClientId, - height: Height, - ) -> Result, Ics02Error> { - trace!(target:"runtime::pallet-ibc","in client : [next_consensus_state]"); - - // search key - let client_consensus_state_path = ClientConsensusStatePath { - client_id: client_id.clone(), - epoch: height.revision_number(), - height: height.revision_height(), - } - .to_string() - .as_bytes() - .to_vec(); - - if >::contains_key(client_consensus_state_path.clone()) { - let values = >::get(client_consensus_state_path.clone()); - let any_consensus_state = - AnyConsensusState::decode_vec(&*values).unwrap(); - trace!(target:"runtime::pallet-ibc", + Err(Ics02Error::consensus_state_not_found(client_id.clone(), height)) + } + } + + fn prev_consensus_state( + &self, + client_id: &ClientId, + height: Height, + ) -> Result, Ics02Error> { + trace!(target:"runtime::pallet-ibc","in client : [next_consensus_state]"); + + // search key + let client_consensus_state_path = ClientConsensusStatePath { + client_id: client_id.clone(), + epoch: height.revision_number(), + height: height.revision_height(), + } + .to_string() + .as_bytes() + .to_vec(); + + if >::contains_key(client_consensus_state_path.clone()) { + let values = >::get(client_consensus_state_path.clone()); + let any_consensus_state = AnyConsensusState::decode_vec(&*values).unwrap(); + trace!(target:"runtime::pallet-ibc", "in client : [prev_consensus_state] >> any consensus state = {:?}", any_consensus_state ); - Ok(Some(any_consensus_state)) - } else { - trace!(target:"runtime::pallet-ibc", + Ok(Some(any_consensus_state)) + } else { + trace!(target:"runtime::pallet-ibc", "in client : [prev_consensus_state] >> any consensus state is not contains at ConsensusStates : client_id = {:?}, height = {:?}", client_id, height ); - Err(Ics02Error::consensus_state_not_found(client_id.clone(), height)) - } - } + Err(Ics02Error::consensus_state_not_found(client_id.clone(), height)) + } + } - fn host_height(&self) -> Height { - trace!(target:"runtime::pallet-ibc","in client : [host_height]"); - let block_number = format!("{:?}", >::block_number()); - let current_height: u64 = block_number.parse().unwrap_or_default(); + fn host_height(&self) -> Height { + trace!(target:"runtime::pallet-ibc","in client : [host_height]"); + let block_number = format!("{:?}", >::block_number()); + let current_height: u64 = block_number.parse().unwrap_or_default(); - trace!(target:"runtime::pallet-ibc", + trace!(target:"runtime::pallet-ibc", "in channel: [host_height] >> host_height = {:?}", Height::new(REVISION_NUMBER, current_height) ); - Height::new(REVISION_NUMBER, current_height).unwrap() - } + Height::new(REVISION_NUMBER, current_height).unwrap() + } - fn host_consensus_state(&self, _height: Height) -> Result { - trace!(target:"runtime::pallet-ibc","in client : [consensus_state]"); + fn host_consensus_state(&self, _height: Height) -> Result { + trace!(target:"runtime::pallet-ibc","in client : [consensus_state]"); - todo!() - } + todo!() + } - fn pending_host_consensus_state(&self) -> Result { - trace!(target:"runtime::pallet-ibc","in client: [pending_host_consensus_state]"); + fn pending_host_consensus_state(&self) -> Result { + trace!(target:"runtime::pallet-ibc","in client: [pending_host_consensus_state]"); - todo!() - } + todo!() + } - fn client_counter(&self) -> Result { - trace!(target:"runtime::pallet-ibc","in client : [client_counter]"); + fn client_counter(&self) -> Result { + trace!(target:"runtime::pallet-ibc","in client : [client_counter]"); - Ok(>::get()) - } + Ok(>::get()) + } } impl ClientKeeper for Context { - fn store_client_type( - &mut self, - client_id: ClientId, - client_type: ClientType, - ) -> Result<(), Ics02Error> { - info!("in client : [store_client_type]"); - - let client_type_path = ClientTypePath(client_id.clone()).to_string().as_bytes().to_vec(); - let client_type = client_type.as_str().encode(); - >::insert(client_type_path, client_type); - Ok(()) - } - - fn store_client_state( - &mut self, - client_id: ClientId, - client_state: AnyClientState, - ) -> Result<(), Ics02Error> { - trace!(target:"runtime::pallet-ibc","in client : [store_client_state]"); - - let client_state_path = ClientStatePath(client_id.clone()).to_string().as_bytes().to_vec(); - - let data = client_state.encode_vec().unwrap(); - // store client states key-value - >::insert(client_state_path.clone(), data); - - Ok(()) - } - - fn store_consensus_state( - &mut self, - client_id: ClientId, - height: Height, - consensus_state: AnyConsensusState, - ) -> Result<(), Ics02Error> { - trace!(target:"runtime::pallet-ibc","in client : [store_consensus_state]"); - - // store key - let client_consensus_state_path = ClientConsensusStatePath { - client_id: client_id.clone(), - epoch: height.revision_number(), - height: height.revision_height(), - } - .to_string() - .as_bytes() - .to_vec(); - - // store value - let consensus_state = consensus_state.encode_vec().unwrap(); - // store client_consensus_state path as key, consensus_state as value - >::insert(client_consensus_state_path, consensus_state); - - Ok(()) - } - - fn increase_client_counter(&mut self) { - info!("in client : [increase_client_counter]"); - - let ret = >::try_mutate(|val| -> Result<(), Ics02Error> { - let new = val.checked_add(1).unwrap(); - *val = new; - Ok(()) - }); - } - - fn store_update_time( - &mut self, - client_id: ClientId, - height: Height, - timestamp: Timestamp, - ) -> Result<(), Ics02Error> { - trace!(target:"runtime::pallet-ibc","in client: [store_update_time]"); - - let encode_timestamp = serde_json::to_string(×tamp) - .unwrap() - .as_bytes() - .to_vec(); - - >::insert( - client_id.as_bytes(), - height.encode_vec().unwrap(), - encode_timestamp, - ); - - Ok(()) - } - - fn store_update_height( - &mut self, - client_id: ClientId, - height: Height, - host_height: Height, - ) -> Result<(), Ics02Error> { - trace!(target:"runtime::pallet-ibc","in client: [store_update_height]"); - - >::insert( - client_id.as_bytes(), - height.encode_vec().unwrap(), - host_height.encode_vec().unwrap(), - ); - - Ok(()) - } + fn store_client_type( + &mut self, + client_id: ClientId, + client_type: ClientType, + ) -> Result<(), Ics02Error> { + info!("in client : [store_client_type]"); + + let client_type_path = ClientTypePath(client_id.clone()).to_string().as_bytes().to_vec(); + let client_type = client_type.as_str().encode(); + >::insert(client_type_path, client_type); + Ok(()) + } + + fn store_client_state( + &mut self, + client_id: ClientId, + client_state: AnyClientState, + ) -> Result<(), Ics02Error> { + trace!(target:"runtime::pallet-ibc","in client : [store_client_state]"); + + let client_state_path = ClientStatePath(client_id.clone()).to_string().as_bytes().to_vec(); + + let data = client_state.encode_vec().unwrap(); + // store client states key-value + >::insert(client_state_path.clone(), data); + + Ok(()) + } + + fn store_consensus_state( + &mut self, + client_id: ClientId, + height: Height, + consensus_state: AnyConsensusState, + ) -> Result<(), Ics02Error> { + trace!(target:"runtime::pallet-ibc","in client : [store_consensus_state]"); + + // store key + let client_consensus_state_path = ClientConsensusStatePath { + client_id: client_id.clone(), + epoch: height.revision_number(), + height: height.revision_height(), + } + .to_string() + .as_bytes() + .to_vec(); + + // store value + let consensus_state = consensus_state.encode_vec().unwrap(); + // store client_consensus_state path as key, consensus_state as value + >::insert(client_consensus_state_path, consensus_state); + + Ok(()) + } + + fn increase_client_counter(&mut self) { + info!("in client : [increase_client_counter]"); + + let ret = >::try_mutate(|val| -> Result<(), Ics02Error> { + let new = val.checked_add(1).unwrap(); + *val = new; + Ok(()) + }); + } + + fn store_update_time( + &mut self, + client_id: ClientId, + height: Height, + timestamp: Timestamp, + ) -> Result<(), Ics02Error> { + trace!(target:"runtime::pallet-ibc","in client: [store_update_time]"); + + let encode_timestamp = serde_json::to_string(×tamp).unwrap().as_bytes().to_vec(); + + >::insert( + client_id.as_bytes(), + height.encode_vec().unwrap(), + encode_timestamp, + ); + + Ok(()) + } + + fn store_update_height( + &mut self, + client_id: ClientId, + height: Height, + host_height: Height, + ) -> Result<(), Ics02Error> { + trace!(target:"runtime::pallet-ibc","in client: [store_update_height]"); + + >::insert( + client_id.as_bytes(), + height.encode_vec().unwrap(), + host_height.encode_vec().unwrap(), + ); + + Ok(()) + } } diff --git a/frame/ibc/src/module/core/ics02_client/events.rs b/frame/ibc/src/module/core/ics02_client/events.rs index daeca34bf7003..4442ea54a59c5 100644 --- a/frame/ibc/src/module/core/ics02_client/events.rs +++ b/frame/ibc/src/module/core/ics02_client/events.rs @@ -1,22 +1,20 @@ -use crate::module::core::ics24_host::{ClientType, ClientId, Height}; use super::header::AnyHeader; +use crate::module::core::ics24_host::{ClientId, ClientType, Height}; use codec::{Decode, Encode}; use scale_info::TypeInfo; use sp_runtime::RuntimeDebug; #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct Attributes { - pub height: Height, - pub client_id: ClientId, - pub client_type: ClientType, - pub consensus_height: Height, + pub height: Height, + pub client_id: ClientId, + pub client_type: ClientType, + pub consensus_height: Height, } /// UpdateClient event signals a recent update of an on-chain client (IBC Client). #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct UpdateClient { - pub common: Attributes, - pub header: Option, + pub common: Attributes, + pub header: Option, } - - diff --git a/frame/ibc/src/module/core/ics02_client/header.rs b/frame/ibc/src/module/core/ics02_client/header.rs index d981896679f93..a03edcd3daa51 100644 --- a/frame/ibc/src/module/core/ics02_client/header.rs +++ b/frame/ibc/src/module/core/ics02_client/header.rs @@ -6,6 +6,6 @@ use sp_runtime::RuntimeDebug; #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] #[allow(clippy::large_enum_variant)] pub enum AnyHeader { - Tendermint(TendermintHeader), - // Grandpa(GrandpaHeader), -} \ No newline at end of file + Tendermint(TendermintHeader), + // Grandpa(GrandpaHeader), +} diff --git a/frame/ibc/src/module/core/ics03_connection.rs b/frame/ibc/src/module/core/ics03_connection.rs index 2ce731e243850..144d1c91f0f5d 100644 --- a/frame/ibc/src/module/core/ics03_connection.rs +++ b/frame/ibc/src/module/core/ics03_connection.rs @@ -90,7 +90,6 @@ impl ConnectionReader for Context { let ret = ClientReader::consensus_state(self, client_id, height) .map_err(Ics03Error::ics02_client); - Ok(ret.unwrap()) } @@ -144,8 +143,7 @@ impl ConnectionKeeper for Context { trace!(target:"runtime::pallet-ibc","in connection : [increase_connection_counter]"); let ret = >::try_mutate(|val| -> Result<(), Ics03Error> { - let new = val - .checked_add(1).unwrap(); + let new = val.checked_add(1).unwrap(); *val = new; Ok(()) }); diff --git a/frame/ibc/src/module/core/ics04_channel.rs b/frame/ibc/src/module/core/ics04_channel.rs index bab1286cc5535..fb9fc7f08ab99 100644 --- a/frame/ibc/src/module/core/ics04_channel.rs +++ b/frame/ibc/src/module/core/ics04_channel.rs @@ -114,8 +114,7 @@ impl ChannelReader for Context { ) -> Result { trace!(target:"runtime::pallet-ibc","in channel : [client_consensus_state]"); - let ret = ClientReader::consensus_state(self, client_id, height) - .unwrap(); + let ret = ClientReader::consensus_state(self, client_id, height).unwrap(); Ok(ret) } @@ -324,15 +323,12 @@ impl ChannelReader for Context { if >::contains_key( client_id.as_bytes(), - height.encode_vec().unwrap() + height.encode_vec().unwrap(), ) { - let time = >::get( - client_id.as_bytes(), - height.encode_vec().unwrap() - ); + let time = + >::get(client_id.as_bytes(), height.encode_vec().unwrap()); let timestamp = String::from_utf8(time).unwrap(); - let time: Timestamp = - serde_json::from_str(×tamp).unwrap(); + let time: Timestamp = serde_json::from_str(×tamp).unwrap(); Ok(time) } else { error!(target:"runtime::pallet-ibc","in channel: [client_update_time] processed time not found"); @@ -349,14 +345,13 @@ impl ChannelReader for Context { if >::contains_key( client_id.as_bytes(), - height.encode_vec().unwrap() + height.encode_vec().unwrap(), ) { let host_height = >::get( client_id.as_bytes(), height.encode_vec().unwrap(), ); - let host_height = - Height::decode(&mut &host_height[..]).unwrap(); + let host_height = Height::decode(&mut &host_height[..]).unwrap(); Ok(host_height) } else { error!(target:"runtime::pallet-ibc","in channel: [client_update_height] processed height not found"); diff --git a/frame/ibc/src/module/core/ics24_host.rs b/frame/ibc/src/module/core/ics24_host.rs index 529193e864024..45bb33b8ebcc5 100644 --- a/frame/ibc/src/module/core/ics24_host.rs +++ b/frame/ibc/src/module/core/ics24_host.rs @@ -1,4 +1,4 @@ -use crate::{alloc::string::ToString, Config, Event, from_channel_id_to_vec, REVISION_NUMBER}; +use crate::{alloc::string::ToString, from_channel_id_to_vec, Config, Event, REVISION_NUMBER}; use alloc::string::String; use ibc::{ core::{ @@ -158,7 +158,6 @@ impl From for ConnectionId { } } - impl From for IbcConnectionId { fn from(value: ConnectionId) -> Self { let value = String::from_utf8(value.0).expect("convert from utf8 Error"); @@ -243,4 +242,4 @@ impl From for IbcPacket { timeout_timestamp: value.timeout_timestamp.into(), } } -} \ No newline at end of file +} diff --git a/frame/ibc/src/module/core/ics26_routing.rs b/frame/ibc/src/module/core/ics26_routing.rs index 361b3fdd518cf..03ece8e67030c 100644 --- a/frame/ibc/src/module/core/ics26_routing.rs +++ b/frame/ibc/src/module/core/ics26_routing.rs @@ -2,9 +2,9 @@ use crate::{context::Context, *}; use alloc::{ borrow::{Borrow, Cow, ToOwned}, collections::BTreeMap, + fmt::format, sync::Arc, }; -use alloc::fmt::format; use core::fmt::Formatter; use ibc::core::ics26_routing::context::{Ics26Context, Module, ModuleId, RouterBuilder}; use log::{error, info, trace, warn}; From b3162e7379c2187ae22c577b28417b8992ac3fd2 Mon Sep 17 00:00:00 2001 From: Davirain Date: Mon, 22 Aug 2022 10:47:53 +0800 Subject: [PATCH 004/484] fix unwrap to remove --- frame/ibc/Cargo.toml | 4 - frame/ibc/src/benchmarking.rs | 24 -- frame/ibc/src/events.rs | 9 +- frame/ibc/src/lib.rs | 4 +- .../src/module/applications/transfer/mod.rs | 230 ++++++++++-------- .../transfer/transfer_handle_callback.rs | 41 ++-- .../src/module/core/ics02_client/context.rs | 26 +- frame/ibc/src/module/core/ics03_connection.rs | 19 +- frame/ibc/src/module/core/ics04_channel.rs | 43 ++-- frame/ibc/src/module/core/ics24_host.rs | 22 +- 10 files changed, 193 insertions(+), 229 deletions(-) delete mode 100644 frame/ibc/src/benchmarking.rs diff --git a/frame/ibc/Cargo.toml b/frame/ibc/Cargo.toml index c4a1389ce4069..96d702c59ab5a 100644 --- a/frame/ibc/Cargo.toml +++ b/frame/ibc/Cargo.toml @@ -12,10 +12,6 @@ readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] -# [build-dependencies.substrate-wasm-builder] -# branch = 'polkadot-v0.9.18' -# git = 'https://github.com/paritytech/substrate.git' - [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } scale-info = { version = "2.1.2", default-features = false, features = ["derive"] } diff --git a/frame/ibc/src/benchmarking.rs b/frame/ibc/src/benchmarking.rs deleted file mode 100644 index 741d94d17cf34..0000000000000 --- a/frame/ibc/src/benchmarking.rs +++ /dev/null @@ -1,24 +0,0 @@ -//! Benchmarking setup for pallet-template - -// use super::*; - -// #[allow(unused)] -// use crate::Pallet as Template; -// use frame_benchmarking::{benchmarks, impl_benchmark_test_suite, whitelisted_caller}; -// use frame_system::RawOrigin; - -// benchmarks! { -// deliver { -// let b in 0..u8::MAX.into(); -// let m in 0..u8::MAX.into(); -// let mut v1: Vec = Vec::new(); -// let mut v2: Vec = Vec::new(); -// for i in 0..m { -// v1.push(b.try_into().unwrap()); -// v2.push(b.try_into().unwrap()); -// } -// let any = Any {type_url: v1, value: v2}; -// let caller = whitelisted_caller(); -// }: deliver(RawOrigin::Signed(caller), vec![any], b.try_into().unwrap()) -// } -// impl_benchmark_test_suite!(Template, crate::mock::new_test_ext(), crate::mock::Test,); diff --git a/frame/ibc/src/events.rs b/frame/ibc/src/events.rs index 3c6f968061d92..8badc38bafff4 100644 --- a/frame/ibc/src/events.rs +++ b/frame/ibc/src/events.rs @@ -40,7 +40,7 @@ impl From for ModuleId { impl From for ics26_routing::context::ModuleId { fn from(module_id: ModuleId) -> Self { - ics26_routing::context::ModuleId::from_str(&String::from_utf8(module_id.0).unwrap()) + ics26_routing::context::ModuleId::from_str(&String::from_utf8(module_id.0).expect("Convert From UTF8 Never Faild")) .expect("should never fiaild") } } @@ -333,10 +333,3 @@ impl From for Event { } } } - -// impl From> for Event { -// fn from(events: Vec) -> Self { -// let events: Vec> = events.into_iter().map(|ev| ev.into()).collect(); -// Self::IbcEvents { events } -// } -// } diff --git a/frame/ibc/src/lib.rs b/frame/ibc/src/lib.rs index 666c2b292d2c6..b37ccc268ea1c 100644 --- a/frame/ibc/src/lib.rs +++ b/frame/ibc/src/lib.rs @@ -550,7 +550,7 @@ pub mod pallet { let messages: Vec = messages .into_iter() .map(|message| ibc_proto::google::protobuf::Any { - type_url: String::from_utf8(message.type_url.clone()).unwrap(), + type_url: String::from_utf8(message.type_url.clone()).expect("Convert From UTF8 Never Faild"), value: message.value, }) .collect(); @@ -588,7 +588,7 @@ pub mod pallet { let messages: Vec = messages .into_iter() .map(|message| ibc_proto::google::protobuf::Any { - type_url: String::from_utf8(message.type_url.clone()).unwrap(), + type_url: String::from_utf8(message.type_url.clone()).expect("Convert From UTF8 Never Faild"), value: message.value, }) .collect(); diff --git a/frame/ibc/src/module/applications/transfer/mod.rs b/frame/ibc/src/module/applications/transfer/mod.rs index 17ee3696157c9..b7208b7ad8e11 100644 --- a/frame/ibc/src/module/applications/transfer/mod.rs +++ b/frame/ibc/src/module/applications/transfer/mod.rs @@ -38,71 +38,79 @@ impl BankKeeper for TransferModule { to: &Self::AccountId, amt: &PrefixedCoin, ) -> Result<(), Ics20Error> { + // TODO(davirain): trace_path now is private // let is_native_asset = amt.denom.trace_path().is_empty(); - // match is_native_asset { - // // transfer native token - // true => { - // let amount = amt.amount.as_u256().low_u128().checked_into().unwrap(); // TODO: FIX IN THE - // FUTURE let native_token_name = T::NATIVE_TOKEN_NAME; - // let ibc_token_name = amt.denom.base_denom().as_str().as_bytes(); + let is_native_asset = true; + match is_native_asset { + // transfer native token + true => { + // TODO(davirain): amount now is private, and base_denom is private + // let amount = amt.amount.as_u256().low_u128().checked_into().expect("Convert MUST NOT Failed"); + // let ibc_token_name = amt.denom.base_denom().as_str().as_bytes(); + let amount = todo!(); + let ibc_token_name = &[1, 1, 2, 3]; + let native_token_name = T::NATIVE_TOKEN_NAME; - // // assert native token name equal want to send ibc token name - // assert_eq!( - // native_token_name, ibc_token_name, - // "send ibc token name is not native token name" - // ); + // assert native token name equal want to send ibc token name + assert_eq!( + native_token_name, ibc_token_name, + "send ibc token name is not native token name" + ); - // >::transfer( - // &from.clone().into_account(), - // &to.clone().into_account(), - // amount, - // AllowDeath, - // ) - // .map_err(|error| { - // error!("❌ [send_coins] : Error: ({:?})", error); - // Ics20Error::invalid_token() - // })?; + >::transfer( + &from.clone().into_account(), + &to.clone().into_account(), + amount, + AllowDeath, + ) + .map_err(|error| { + error!("❌ [send_coins] : Error: ({:?})", error); + Ics20Error::invalid_token() + })?; - // // add emit transfer native token event - // Pallet::::deposit_event(Event::::TransferNativeToken( - // from.clone(), - // to.clone(), - // amount, - // )) - // }, - // // transfer non-native token - // false => { - // let amount = amt.amount.as_u256().low_u128().into(); - // let denom = amt.denom.base_denom().as_str(); - // // look cross chain asset have register in host chain - // match T::AssetIdByName::try_get_asset_id(denom) { - // Ok(token_id) => { - // >::transfer( - // token_id.into(), - // &from.clone().into_account(), - // &to.clone().into_account(), - // amount, - // true, - // ) - // .map_err(|error| { - // error!("❌ [send_coins] : Error: ({:?})", error); - // Ics20Error::invalid_token() - // })?; + // add emit transfer native token event + Pallet::::deposit_event(Event::::TransferNativeToken( + from.clone(), + to.clone(), + amount, + )) + }, + // transfer non-native token + false => { + // TODO(davirain): amount now is private, and base_denom is private + // let amount = amt.amount.as_u256().low_u128().into(); + // let denom = amt.denom.base_denom().as_str(); + let amount = todo!(); + let denom = &[1, 1, 2, 3]; + // look cross chain asset have register in host chain + match T::AssetIdByName::try_get_asset_id(denom) { + Ok(token_id) => { + >::transfer( + token_id.into(), + &from.clone().into_account(), + &to.clone().into_account(), + amount, + true, + ) + .map_err(|error| { + error!("❌ [send_coins] : Error: ({:?})", error); + Ics20Error::invalid_token() + })?; - // // add emit transfer no native token event - // Pallet::::deposit_event(Event::::TransferNoNativeToken( - // from.clone(), - // to.clone(), - // amount, - // )); - // }, - // Err(_error) => { - // error!("❌ [send_coins]: denom: ({:?})", denom); - // return Err(Ics20Error::invalid_token()) - // }, - // } - // }, - // } + // add emit transfer no native token event + Pallet::::deposit_event(Event::::TransferNoNativeToken( + from.clone(), + to.clone(), + amount, + )); + }, + Err(_error) => { + error!("❌ [send_coins]: denom: ({:?})", denom); + return Err(Ics20Error::invalid_token()) + }, + } + }, + } Ok(()) } @@ -112,33 +120,36 @@ impl BankKeeper for TransferModule { account: &Self::AccountId, amt: &PrefixedCoin, ) -> Result<(), Ics20Error> { + // TODO(davirain): amount now is private, and base_denom is private // let amount = amt.amount.as_u256().low_u128().into(); // let denom = amt.denom.base_denom().as_str(); - // // look cross chain asset have register in host chain - // match T::AssetIdByName::try_get_asset_id(denom) { - // Ok(token_id) => { - // >::mint_into( - // token_id.into(), - // &account.clone().into_account(), - // amount, - // ) - // .map_err(|error| { - // error!("❌ [mint_coins] : Error: ({:?})", error); - // Ics20Error::invalid_token() - // })?; + let amount = todo!(); + let denom = &[1, 1, 2, 3]; + // look cross chain asset have register in host chain + match T::AssetIdByName::try_get_asset_id(denom) { + Ok(token_id) => { + >::mint_into( + token_id.into(), + &account.clone().into_account(), + amount, + ) + .map_err(|error| { + error!("❌ [mint_coins] : Error: ({:?})", error); + Ics20Error::invalid_token() + })?; - // // add mint token event - // Pallet::::deposit_event(Event::::MintToken( - // token_id, - // account.clone(), - // amount, - // )); - // }, - // Err(_error) => { - // error!("❌ [mint_coins]: denom: ({:?})", denom); - // return Err(Ics20Error::invalid_token()) - // }, - // } + // add mint token event + Pallet::::deposit_event(Event::::MintToken( + token_id, + account.clone(), + amount, + )); + }, + Err(_error) => { + error!("❌ [mint_coins]: denom: ({:?})", denom); + return Err(Ics20Error::invalid_token()) + }, + } Ok(()) } @@ -147,33 +158,36 @@ impl BankKeeper for TransferModule { account: &Self::AccountId, amt: &PrefixedCoin, ) -> Result<(), Ics20Error> { + // TODO(davirain): amount now is private, and base_denom is private // let amount = amt.amount.as_u256().low_u128().into(); // let denom = amt.denom.base_denom().as_str(); - // // look cross chain asset have register in host chain - // match T::AssetIdByName::try_get_asset_id(denom) { - // Ok(token_id) => { - // >::burn_from( - // token_id.into(), - // &account.clone().into_account(), - // amount, - // ) - // .map_err(|error| { - // error!("❌ [burn_coins] : Error: ({:?})", error); - // Ics20Error::invalid_token() - // })?; + let amount = todo!(); + let denom = &[1, 1, 2, 3]; + // look cross chain asset have register in host chain + match T::AssetIdByName::try_get_asset_id(denom) { + Ok(token_id) => { + >::burn_from( + token_id.into(), + &account.clone().into_account(), + amount, + ) + .map_err(|error| { + error!("❌ [burn_coins] : Error: ({:?})", error); + Ics20Error::invalid_token() + })?; - // // add burn token event - // Pallet::::deposit_event(Event::::BurnToken( - // token_id, - // account.clone(), - // amount, - // )); - // }, - // Err(_error) => { - // error!("❌ [burn_coins]: denom: ({:?})", denom); - // return Err(Ics20Error::invalid_token()) - // }, - // } + // add burn token event + Pallet::::deposit_event(Event::::BurnToken( + token_id, + account.clone(), + amount, + )); + }, + Err(_error) => { + error!("❌ [burn_coins]: denom: ({:?})", denom); + return Err(Ics20Error::invalid_token()) + }, + } Ok(()) } } diff --git a/frame/ibc/src/module/applications/transfer/transfer_handle_callback.rs b/frame/ibc/src/module/applications/transfer/transfer_handle_callback.rs index 8dd69b074172b..76cbe5499068c 100644 --- a/frame/ibc/src/module/applications/transfer/transfer_handle_callback.rs +++ b/frame/ibc/src/module/applications/transfer/transfer_handle_callback.rs @@ -33,7 +33,7 @@ impl Module for TransferModule { counterparty: &Counterparty, version: &Version, ) -> Result<(), Ics04Error> { - Ok(ibc::applications::transfer::context::on_chan_open_init( + ibc::applications::transfer::context::on_chan_open_init( self, output, order, @@ -42,8 +42,7 @@ impl Module for TransferModule { channel_id, counterparty, version, - ) - .unwrap()) + ).map_err(|value| Ics04Error::app_module(value.to_string())) } fn on_chan_open_try( @@ -57,7 +56,7 @@ impl Module for TransferModule { version: &Version, counterparty_version: &Version, ) -> Result { - Ok(ibc::applications::transfer::context::on_chan_open_try( + ibc::applications::transfer::context::on_chan_open_try( self, output, order, @@ -67,8 +66,7 @@ impl Module for TransferModule { counterparty, version, counterparty_version, - ) - .unwrap()) + ).map_err(|value| Ics04Error::app_module(value.to_string())) } fn on_chan_open_ack( @@ -78,14 +76,14 @@ impl Module for TransferModule { channel_id: &IbcChannelId, counterparty_version: &Version, ) -> Result<(), Ics04Error> { - Ok(ibc::applications::transfer::context::on_chan_open_ack( + ibc::applications::transfer::context::on_chan_open_ack( self, output, port_id, channel_id, counterparty_version, - ) - .unwrap()) + ).map_err(|value| Ics04Error::app_module(value.to_string())) + } fn on_chan_open_confirm( @@ -94,10 +92,9 @@ impl Module for TransferModule { port_id: &PortId, channel_id: &IbcChannelId, ) -> Result<(), Ics04Error> { - Ok(ibc::applications::transfer::context::on_chan_open_confirm( + ibc::applications::transfer::context::on_chan_open_confirm( self, output, port_id, channel_id, - ) - .unwrap()) + ).map_err(|value| Ics04Error::app_module(value.to_string())) } fn on_chan_close_init( @@ -106,10 +103,9 @@ impl Module for TransferModule { port_id: &PortId, channel_id: &IbcChannelId, ) -> Result<(), Ics04Error> { - Ok(ibc::applications::transfer::context::on_chan_close_init( + ibc::applications::transfer::context::on_chan_close_init( self, output, port_id, channel_id, - ) - .unwrap()) + ).map_err(|value| Ics04Error::app_module(value.to_string())) } fn on_chan_close_confirm( @@ -118,10 +114,10 @@ impl Module for TransferModule { port_id: &PortId, channel_id: &IbcChannelId, ) -> Result<(), Ics04Error> { - Ok(ibc::applications::transfer::context::on_chan_close_confirm( + ibc::applications::transfer::context::on_chan_close_confirm( self, output, port_id, channel_id, - ) - .unwrap()) + ).map_err(|value| Ics04Error::app_module(value.to_string())) + } fn on_recv_packet( @@ -140,14 +136,14 @@ impl Module for TransferModule { acknowledgement: &GenericAcknowledgement, relayer: &Signer, ) -> Result<(), Ics04Error> { - Ok(ibc::applications::transfer::context::on_acknowledgement_packet( + ibc::applications::transfer::context::on_acknowledgement_packet( self, output, packet, acknowledgement, relayer, - ) - .unwrap()) + ).map_err(|value| Ics04Error::app_module(value.to_string())) + } fn on_timeout_packet( @@ -156,7 +152,6 @@ impl Module for TransferModule { packet: &IbcPacket, relayer: &Signer, ) -> Result<(), Ics04Error> { - Ok(ibc::applications::transfer::context::on_timeout_packet(self, output, packet, relayer) - .unwrap()) + ibc::applications::transfer::context::on_timeout_packet(self, output, packet, relayer).map_err(|value| Ics04Error::app_module(value.to_string())) } } diff --git a/frame/ibc/src/module/core/ics02_client/context.rs b/frame/ibc/src/module/core/ics02_client/context.rs index 0ba687ad35b78..e0db70942fa64 100644 --- a/frame/ibc/src/module/core/ics02_client/context.rs +++ b/frame/ibc/src/module/core/ics02_client/context.rs @@ -30,8 +30,8 @@ impl ClientReader for Context { if >::contains_key(client_type_path.clone()) { let data = >::get(client_type_path); let mut data: &[u8] = &data; - let data = Vec::::decode(&mut data).unwrap(); - let data = String::from_utf8(data).unwrap(); + let data = Vec::::decode(&mut data).map_err(|_| Ics02Error::implementation_specific())?; + let data = String::from_utf8(data).map_err(|_| Ics02Error::implementation_specific())?; let client_type = ClientType::from_str(&data) .map_err(|e| Ics02Error::unknown_client_type(e.to_string()))?; Ok(client_type) @@ -48,7 +48,7 @@ impl ClientReader for Context { if >::contains_key(&client_state_path) { let data = >::get(&client_state_path); - let result = AnyClientState::decode_vec(&*data).unwrap(); + let result = AnyClientState::decode_vec(&*data).map_err(|_| Ics02Error::implementation_specific())?; trace!(target:"runtime::pallet-ibc","in client : [client_state] >> any client_state: {:?}", result); Ok(result) @@ -79,7 +79,7 @@ impl ClientReader for Context { if >::contains_key(client_consensus_state_path.clone()) { let values = >::get(client_consensus_state_path.clone()); - let any_consensus_state = AnyConsensusState::decode_vec(&*values).unwrap(); + let any_consensus_state = AnyConsensusState::decode_vec(&*values).map_err(|_| Ics02Error::implementation_specific())?; trace!(target:"runtime::pallet-ibc", "in client : [consensus_state] >> any consensus state = {:?}", any_consensus_state @@ -109,7 +109,7 @@ impl ClientReader for Context { if >::contains_key(client_consensus_state_path.clone()) { let values = >::get(client_consensus_state_path.clone()); - let any_consensus_state = AnyConsensusState::decode_vec(&*values).unwrap(); + let any_consensus_state = AnyConsensusState::decode_vec(&*values).map_err(|_| Ics02Error::implementation_specific())?; trace!(target:"runtime::pallet-ibc", "in client : [next_consensus_state] >> any consensus state = {:?}", any_consensus_state @@ -165,7 +165,7 @@ impl ClientReader for Context { "in channel: [host_height] >> host_height = {:?}", Height::new(REVISION_NUMBER, current_height) ); - Height::new(REVISION_NUMBER, current_height).unwrap() + Height::new(REVISION_NUMBER, current_height).expect("Contruct Heigjt Never failed") } fn host_consensus_state(&self, _height: Height) -> Result { @@ -210,7 +210,7 @@ impl ClientKeeper for Context { let client_state_path = ClientStatePath(client_id.clone()).to_string().as_bytes().to_vec(); - let data = client_state.encode_vec().unwrap(); + let data = client_state.encode_vec().map_err(|_| Ics02Error::implementation_specific())?; // store client states key-value >::insert(client_state_path.clone(), data); @@ -236,7 +236,7 @@ impl ClientKeeper for Context { .to_vec(); // store value - let consensus_state = consensus_state.encode_vec().unwrap(); + let consensus_state = consensus_state.encode_vec().map_err(|_| Ics02Error::implementation_specific())?; // store client_consensus_state path as key, consensus_state as value >::insert(client_consensus_state_path, consensus_state); @@ -247,7 +247,7 @@ impl ClientKeeper for Context { info!("in client : [increase_client_counter]"); let ret = >::try_mutate(|val| -> Result<(), Ics02Error> { - let new = val.checked_add(1).unwrap(); + let new = val.checked_add(1).expect("Never Overflow"); *val = new; Ok(()) }); @@ -261,11 +261,11 @@ impl ClientKeeper for Context { ) -> Result<(), Ics02Error> { trace!(target:"runtime::pallet-ibc","in client: [store_update_time]"); - let encode_timestamp = serde_json::to_string(×tamp).unwrap().as_bytes().to_vec(); + let encode_timestamp = serde_json::to_string(×tamp).map_err(|_| Ics02Error::implementation_specific())?.as_bytes().to_vec(); >::insert( client_id.as_bytes(), - height.encode_vec().unwrap(), + height.encode_vec().map_err(|_| Ics02Error::implementation_specific())?, encode_timestamp, ); @@ -282,8 +282,8 @@ impl ClientKeeper for Context { >::insert( client_id.as_bytes(), - height.encode_vec().unwrap(), - host_height.encode_vec().unwrap(), + height.encode_vec().map_err(|_| Ics02Error::implementation_specific())?, + host_height.encode_vec().map_err(|_| Ics02Error::implementation_specific())?, ); Ok(()) diff --git a/frame/ibc/src/module/core/ics03_connection.rs b/frame/ibc/src/module/core/ics03_connection.rs index 144d1c91f0f5d..1aefb35c858ec 100644 --- a/frame/ibc/src/module/core/ics03_connection.rs +++ b/frame/ibc/src/module/core/ics03_connection.rs @@ -31,10 +31,7 @@ impl ConnectionReader for Context { if >::contains_key(&connections_path) { let data = >::get(&connections_path); - let ret = ConnectionEnd::decode_vec(&*data).unwrap(); - - trace!(target:"runtime::pallet-ibc","in connection : [connection_end] >> connection_end = {:?}", ret); - Ok(ret) + ConnectionEnd::decode_vec(&*data).map_err(|_| Ics03Error::implementation_specific()) } else { trace!(target:"runtime::pallet-ibc","in connection : [connection_end] >> read connection end returns None"); Err(Ics03Error::connection_mismatch(conn_id.clone())) @@ -59,7 +56,7 @@ impl ConnectionReader for Context { "in connection : [host_current_height] >> Host current height = {:?}", Height::new(REVISION_NUMBER, current_height) ); - Height::new(REVISION_NUMBER, current_height).unwrap() + Height::new(REVISION_NUMBER, current_height).expect("Contruct Height Never faild") } fn host_oldest_height(&self) -> Height { @@ -71,7 +68,7 @@ impl ConnectionReader for Context { "in connection : [host_oldest_height] >> Host oldest height = {:?}", Height::new(0, height) ); - Height::new(REVISION_NUMBER, height).unwrap() + Height::new(REVISION_NUMBER, height).expect("get host oldest height Never faild") } fn commitment_prefix(&self) -> CommitmentPrefix { @@ -87,10 +84,8 @@ impl ConnectionReader for Context { ) -> Result { trace!(target:"runtime::pallet-ibc","in connection : [client_consensus_state]"); - let ret = ClientReader::consensus_state(self, client_id, height) - .map_err(Ics03Error::ics02_client); - - Ok(ret.unwrap()) + ClientReader::consensus_state(self, client_id, height) + .map_err(Ics03Error::ics02_client) } fn host_consensus_state(&self, _height: Height) -> Result { @@ -117,7 +112,7 @@ impl ConnectionKeeper for Context { let connections_path = ConnectionsPath(connection_id.clone()).to_string().as_bytes().to_vec(); - let data = connection_end.encode_vec().unwrap(); + let data = connection_end.encode_vec().map_err(|_| Ics03Error::implementation_specific())?; // store connection end >::insert(connections_path, data); @@ -143,7 +138,7 @@ impl ConnectionKeeper for Context { trace!(target:"runtime::pallet-ibc","in connection : [increase_connection_counter]"); let ret = >::try_mutate(|val| -> Result<(), Ics03Error> { - let new = val.checked_add(1).unwrap(); + let new = val.checked_add(1).expect("Never Overflow"); *val = new; Ok(()) }); diff --git a/frame/ibc/src/module/core/ics04_channel.rs b/frame/ibc/src/module/core/ics04_channel.rs index fb9fc7f08ab99..318a1864e46ec 100644 --- a/frame/ibc/src/module/core/ics04_channel.rs +++ b/frame/ibc/src/module/core/ics04_channel.rs @@ -78,9 +78,9 @@ impl ChannelReader for Context { let mut result = vec![]; for item in channel_ends_paths.into_iter() { - let raw_path = String::from_utf8(item).unwrap(); + let raw_path = String::from_utf8(item).map_err(|_| Ics04Error::implementation_specific())?; // decode key - let path = Path::from_str(&raw_path).unwrap(); + let path = Path::from_str(&raw_path).map_err(|_| Ics04Error::implementation_specific())?; trace!(target:"runtime::pallet-ibc", "[get_channels] >> Path: {:?}", path); match path { Path::ChannelEnds(channel_ends_path) => { @@ -104,7 +104,7 @@ impl ChannelReader for Context { fn client_state(&self, client_id: &ClientId) -> Result { trace!(target:"runtime::pallet-ibc","in channel : [client_state]"); - Ok(ClientReader::client_state(self, client_id).unwrap()) + ClientReader::client_state(self, client_id).map_err(|_| Ics04Error::implementation_specific()) } fn client_consensus_state( @@ -114,9 +114,7 @@ impl ChannelReader for Context { ) -> Result { trace!(target:"runtime::pallet-ibc","in channel : [client_consensus_state]"); - let ret = ClientReader::consensus_state(self, client_id, height).unwrap(); - - Ok(ret) + ClientReader::consensus_state(self, client_id, height).map_err(|_| Ics04Error::implementation_specific()) } fn get_next_sequence_send( @@ -221,7 +219,7 @@ impl ChannelReader for Context { if >::contains_key(&packet_receipt_path) { let data = >::get(&packet_receipt_path); - let data = String::from_utf8(data).unwrap(); + let data = String::from_utf8(data).map_err(|_| Ics04Error::implementation_specific())?; let data = match data.as_ref() { "Ok" => Receipt::Ok, _ => unreachable!(), @@ -283,7 +281,7 @@ impl ChannelReader for Context { "in channel: [host_height] >> host_height = {:?}", Height::new(REVISION_NUMBER, current_height) ); - Height::new(REVISION_NUMBER, current_height).unwrap() + Height::new(REVISION_NUMBER, current_height).expect("Contruct Height Never Faild") } /// Returns the current timestamp of the local chain. @@ -292,11 +290,9 @@ impl ChannelReader for Context { use frame_support::traits::UnixTime; let time = T::TimeProvider::now(); - let ts = Timestamp::from_nanoseconds(time.as_nanos() as u64) - .map_err(|e| panic!("{:?}, caused by {:?} from pallet timestamp_pallet", e, time)); - trace!(target:"runtime::pallet-ibc","in channel: [host_timestamp] >> host_timestamp = {:?}", ts.unwrap()); - ts.unwrap() + Timestamp::from_nanoseconds(time.as_nanos() as u64) + .expect("Convert Timestamp Never Faild") } /// Returns the `AnyConsensusState` for the given identifier `height`. @@ -323,12 +319,12 @@ impl ChannelReader for Context { if >::contains_key( client_id.as_bytes(), - height.encode_vec().unwrap(), + height.encode_vec().map_err(|_| Ics04Error::implementation_specific())?, ) { let time = - >::get(client_id.as_bytes(), height.encode_vec().unwrap()); - let timestamp = String::from_utf8(time).unwrap(); - let time: Timestamp = serde_json::from_str(×tamp).unwrap(); + >::get(client_id.as_bytes(), height.encode_vec().map_err(|_| Ics04Error::implementation_specific())?); + let timestamp = String::from_utf8(time).map_err(|_| Ics04Error::implementation_specific())?; + let time: Timestamp = serde_json::from_str(×tamp).map_err(|_| Ics04Error::implementation_specific())?; Ok(time) } else { error!(target:"runtime::pallet-ibc","in channel: [client_update_time] processed time not found"); @@ -345,13 +341,13 @@ impl ChannelReader for Context { if >::contains_key( client_id.as_bytes(), - height.encode_vec().unwrap(), + height.encode_vec().map_err(|_| Ics04Error::implementation_specific())?, ) { let host_height = >::get( client_id.as_bytes(), - height.encode_vec().unwrap(), + height.encode_vec().map_err(|_| Ics04Error::implementation_specific())?, ); - let host_height = Height::decode(&mut &host_height[..]).unwrap(); + let host_height = Height::decode(&mut &host_height[..]).map_err(|_| Ics04Error::implementation_specific())?; Ok(host_height) } else { error!(target:"runtime::pallet-ibc","in channel: [client_update_height] processed height not found"); @@ -502,14 +498,13 @@ impl ChannelKeeper for Context { if >::contains_key(&connections_path) { trace!(target:"runtime::pallet-ibc","in channel: [store_connection_channels] >> insert port_channel_id"); // if connection_id exist - let ret = >::try_mutate( + >::try_mutate( &connections_path, |val| -> Result<(), Ics04Error> { val.push(channel_ends_path.clone()); Ok(()) }, - ) - .unwrap(); + ).expect("channels Connection mutate Error") } else { // if connection_id no exist trace!(target:"runtime::pallet-ibc","in channel: [store_connection_channels] >> init ChannelsConnection"); @@ -532,7 +527,7 @@ impl ChannelKeeper for Context { .to_string() .as_bytes() .to_vec(); - let channel_end = channel_end.encode_vec().unwrap(); + let channel_end = channel_end.encode_vec().map_err(|_| Ics04Error::implementation_specific())?; // store channels key-value >::insert(channel_end_path, channel_end); @@ -602,7 +597,7 @@ impl ChannelKeeper for Context { trace!(target:"runtime::pallet-ibc","in channel: [increase_channel_counter]"); let ret = >::try_mutate(|val| -> Result<(), Ics04Error> { - let new = val.checked_add(1).unwrap(); + let new = val.checked_add(1).expect("Never Overflow"); *val = new; Ok(()) }); diff --git a/frame/ibc/src/module/core/ics24_host.rs b/frame/ibc/src/module/core/ics24_host.rs index 45bb33b8ebcc5..b6ae7a09fa5d6 100644 --- a/frame/ibc/src/module/core/ics24_host.rs +++ b/frame/ibc/src/module/core/ics24_host.rs @@ -58,8 +58,8 @@ impl From for PortId { impl From for IbcPortId { fn from(value: PortId) -> Self { - let value = String::from_utf8(value.0).expect("convert from utf8 Error"); - IbcPortId::from_str(&value).unwrap() + let value = String::from_utf8(value.0).expect("convert Never faild"); + IbcPortId::from_str(&value).expect("convert Never faild") } } @@ -75,8 +75,8 @@ impl From for ChannelId { impl From for IbcChannelId { fn from(value: ChannelId) -> Self { - let value = String::from_utf8(value.0).expect("convert from utf8 Error"); - Self::from_str(&value).expect("convert channel id from str Error") + let value = String::from_utf8(value.0).expect("convert Never faild"); + Self::from_str(&value).expect("convert Never faild") } } @@ -97,7 +97,7 @@ impl From for Height { impl From for IbcHeight { fn from(height: Height) -> Self { - IbcHeight::new(REVISION_NUMBER, height.revision_height).expect("Contruct IbcHeight Error") + IbcHeight::new(REVISION_NUMBER, height.revision_height).expect("Contruct IbcHeight Never faild") } } @@ -143,8 +143,8 @@ impl From for ClientId { impl From for IbcClientId { fn from(value: ClientId) -> Self { - let value = String::from_utf8(value.0).expect("convert from utf8 Error"); - IbcClientId::from_str(&value).unwrap() + let value = String::from_utf8(value.0).expect("convert Never faild"); + IbcClientId::from_str(&value).expect("convert Never faild") } } @@ -160,8 +160,8 @@ impl From for ConnectionId { impl From for IbcConnectionId { fn from(value: ConnectionId) -> Self { - let value = String::from_utf8(value.0).expect("convert from utf8 Error"); - IbcConnectionId::from_str(&value).unwrap() + let value = String::from_utf8(value.0).expect("convert Never faild"); + IbcConnectionId::from_str(&value).expect("convert Never faild") } } @@ -179,8 +179,8 @@ impl From for Timestamp { impl From for IbcTimestamp { fn from(value: Timestamp) -> Self { - let value = String::from_utf8(value.time).expect("convert from utf8 Error"); - Self::from_str(&value).expect("convert from str Error") + let value = String::from_utf8(value.time).expect("convert Never faild"); + Self::from_str(&value).expect("convert Never faild") } } From 58640f7706d99136e251a4ef8c41de38e80ea74c Mon Sep 17 00:00:00 2001 From: Davirain Date: Mon, 22 Aug 2022 17:56:15 +0800 Subject: [PATCH 005/484] fix tests --- Cargo.lock | 5 + frame/ibc/Cargo.toml | 16 +- frame/ibc/src/mock.rs | 194 ++++++++++--- frame/ibc/src/module/core/ics04_channel.rs | 37 ++- frame/ibc/src/tests.rs | 302 +++------------------ 5 files changed, 244 insertions(+), 310 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 49074130c01d9..6a12a605b1483 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5828,6 +5828,10 @@ dependencies = [ "ibc", "ibc-proto", "log", + "pallet-assets", + "pallet-babe", + "pallet-balances", + "pallet-timestamp", "parity-scale-codec", "prost 0.11.0", "prost-types 0.11.1", @@ -5841,6 +5845,7 @@ dependencies = [ "sp-runtime", "sp-std", "sp-tracing", + "sp-version", "tendermint-proto", "time 0.3.11", ] diff --git a/frame/ibc/Cargo.toml b/frame/ibc/Cargo.toml index 96d702c59ab5a..683c105de4f91 100644 --- a/frame/ibc/Cargo.toml +++ b/frame/ibc/Cargo.toml @@ -44,11 +44,17 @@ hex = '0.4.0' sha2 = '0.10.2' serde = { version = "1.0" } ibc = { version = "0.18.0", features = ["mocks"] } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-keyring = { version = "6.0.0", default-features = false, path = "../../primitives/keyring" } -frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +sp-io = { version = "6.0.0", path = "../../primitives/io" } +sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", path = "../../primitives/std" } +sp-keyring = { version = "6.0.0", path = "../../primitives/keyring" } +frame-support = { version = "4.0.0-dev", path = "../support" } +pallet-assets = { version = "4.0.0-dev", path = "../assets" } +pallet-balances = { version = "4.0.0-dev", path = "../balances" } +pallet-timestamp = { version = "4.0.0-dev", path = "../timestamp" } +pallet-babe = { version = "4.0.0-dev", path = "../babe" } +sp-version = { version = "5.0.0", path = "../../primitives/version" } +sp-core = { version = "6.0.0", path = "../../primitives/core" } chrono = "0.4.19" [features] diff --git a/frame/ibc/src/mock.rs b/frame/ibc/src/mock.rs index 564c1f2ba7b5f..d4756f898df97 100644 --- a/frame/ibc/src/mock.rs +++ b/frame/ibc/src/mock.rs @@ -1,12 +1,29 @@ use crate as pallet_ibc; -use frame_support::parameter_types; +pub use frame_support::{ + construct_runtime, parameter_types, + traits::{ + ConstU128, ConstU16, ConstU32, ConstU8, KeyOwnerProofSystem, Randomness, StorageInfo, + }, + weights::{ + constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND}, + DispatchClass, IdentityFee, Weight, + }, + StorageValue, +}; use frame_system as system; -use sp_core::H256; +use frame_system::{ + limits::{BlockLength, BlockWeights}, + EnsureRoot, +}; +use sp_core::{crypto::KeyTypeId, OpaqueMetadata, H256}; use sp_runtime::{ + create_runtime_str, + generic::{self, Era}, testing::Header, - traits::{BlakeTwo256, IdentifyAccount, IdentityLookup, Verify}, + traits::{AccountIdLookup, BlakeTwo256, IdentifyAccount, IdentityLookup, Verify}, MultiSignature, }; +use sp_version::RuntimeVersion; use std::time::{Duration, Instant}; pub type Signature = MultiSignature; @@ -16,68 +33,181 @@ type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; // Configure a mock runtime to test the pallet. -frame_support::construct_runtime!( +construct_runtime!( pub enum Test where Block = Block, NodeBlock = Block, UncheckedExtrinsic = UncheckedExtrinsic, { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Ibc: pallet_ibc::{Pallet, Call, Storage, Event}, + System: frame_system, + Assets: pallet_assets::, + Balances: pallet_balances, + Ibc: pallet_ibc, } ); +/// A hash of some data used by the chain. +pub type Hash = sp_core::H256; + parameter_types! { pub const BlockHashCount: u64 = 250; pub const SS58Prefix: u8 = 42; } -impl system::Config for Test { +/// Index of a transaction in the chain. +pub type Index = u32; +/// An index to a block. +pub type BlockNumber = u32; + +impl frame_system::Config for Test { + /// The basic call filter to use in dispatchable. type BaseCallFilter = frame_support::traits::Everything; + /// Block & extrinsics weights: base values and limits. type BlockWeights = (); + /// The maximum length of a block (in bytes). type BlockLength = (); - type DbWeight = (); - type Origin = Origin; + /// The identifier used to distinguish between accounts. + type AccountId = AccountId; + /// The aggregated dispatch type that is available for extrinsics. type Call = Call; - type Index = u64; - type BlockNumber = u64; - type Hash = H256; + /// The lookup mechanism to get account ID from whatever is passed in dispatchers. + type Lookup = AccountIdLookup; + /// The index type for storing how many extrinsics an account has signed. + type Index = Index; + /// The index type for blocks. + type BlockNumber = BlockNumber; + /// The type for hashing blocks and tries. + type Hash = Hash; + /// The hashing algorithm used. type Hashing = BlakeTwo256; - type AccountId = AccountId; - type Lookup = IdentityLookup; - type Header = Header; + /// The header type. + type Header = generic::Header; + /// The ubiquitous event type. type Event = Event; - type BlockHashCount = BlockHashCount; + /// The ubiquitous origin type. + type Origin = Origin; + /// Maximum number of block number to block hash mappings to keep (oldest pruned first). + type BlockHashCount = (); + /// The weight of database operations that the runtime can invoke. + type DbWeight = (); + /// Version of the runtime. type Version = (); + /// Converts a module to the index of the module in `construct_runtime!`. + /// + /// This type is being generated by `construct_runtime!`. type PalletInfo = PalletInfo; - type AccountData = (); + /// What to do if a new account is created. type OnNewAccount = (); + /// What to do if an account is fully reaped from the system. type OnKilledAccount = (); + /// The data to be stored in an account. + type AccountData = pallet_balances::AccountData; + /// Weight information for the extrinsics of this pallet. type SystemWeightInfo = (); - type SS58Prefix = SS58Prefix; + /// This is used as an identifier of the chain. 42 is the generic substrate prefix. + type SS58Prefix = ConstU16<42>; + /// The set code logic, just the default since we're not a parachain. type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; } -// The ModuleCallbacksImpl creates a static mapping of module index and callback functions of other -// modules. The module index is determined at the time of construct_runtime. For example, -// the index of TemplateModule is 8 in the current runtime. -// In the future, we should find a more dynamic way to create this mapping. -pub struct ModuleCallbacksImpl; -impl pallet_ibc::ModuleCallbacks for ModuleCallbacksImpl {} +pub type Balance = u128; +/// Type used for expressing timestamp. +pub type Moment = u64; -pub struct MockUnixTime; +pub const MILLICENTS: Balance = 10_000_000_000_000; +pub const CENTS: Balance = 1_000 * MILLICENTS; // assume this is worth about a cent. +pub const DOLLARS: Balance = 100 * CENTS; -// maybe future to fix -impl frame_support::traits::UnixTime for MockUnixTime { - fn now() -> Duration { - Instant::now().elapsed() - } +parameter_types! { + pub const AssetDeposit: Balance = 100 * DOLLARS; + pub const ApprovalDeposit: Balance = 1 * DOLLARS; + pub const StringLimit: u32 = 50; + pub const MetadataDepositBase: Balance = 10 * DOLLARS; + pub const MetadataDepositPerByte: Balance = 1 * DOLLARS; +} + +impl pallet_assets::Config for Test { + type Event = Event; + type Balance = AssetBalance; + type AssetId = AssetId; + type Currency = Balances; + type ForceOrigin = EnsureRoot; + type AssetDeposit = AssetDeposit; + type AssetAccountDeposit = ConstU128; + type MetadataDepositBase = MetadataDepositBase; + type MetadataDepositPerByte = MetadataDepositPerByte; + type ApprovalDeposit = ApprovalDeposit; + type StringLimit = StringLimit; + type Freezer = (); + type Extra = (); + type WeightInfo = pallet_assets::weights::SubstrateWeight; } +parameter_types! { + pub const ExistentialDeposit: Balance = 1 * DOLLARS; + // For weight estimation, we assume that the most locks on an individual account will be 50. + // This number may need to be adjusted in the future if this assumption no longer holds true. + pub const MaxLocks: u32 = 50; + pub const MaxReserves: u32 = 50; +} + +impl pallet_balances::Config for Test { + type MaxLocks = MaxLocks; + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; + /// The type for recording an account's balance. + type Balance = Balance; + /// The ubiquitous event type. + type Event = Event; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = frame_system::Pallet; + type WeightInfo = pallet_balances::weights::SubstrateWeight; +} + +parameter_types! { + pub const MinimumPeriod: Moment = SLOT_DURATION / 2; +} + +impl pallet_timestamp::Config for Test { + /// A timestamp: milliseconds since the unix epoch. + type Moment = Moment; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); +} + +parameter_types! { + pub const MaxAuthorities: u32 = 100; + pub const MaxKeys: u32 = 10_000; + pub const MaxPeerInHeartbeats: u32 = 10_000; + pub const MaxPeerDataEncodingSize: u32 = 1_000; +} + +pub const MILLISECS_PER_BLOCK: Moment = 6000; +pub const SECS_PER_BLOCK: Moment = MILLISECS_PER_BLOCK / 1000; + +// NOTE: Currently it is not possible to change the slot duration after the chain has started. +// Attempting to do so will brick block production. +pub const SLOT_DURATION: Moment = MILLISECS_PER_BLOCK; + +// 1 in 4 blocks (on average, not counting collisions) will be primary BABE blocks. +pub const PRIMARY_PROBABILITY: (u64, u64) = (1, 4); + +pub type AssetBalance = u128; +pub type AssetId = u32; + impl super::pallet::Config for Test { type Event = Event; - type ModuleCallbacks = ModuleCallbacksImpl; - type TimeProvider = MockUnixTime; + type TimeProvider = pallet_timestamp::Pallet; + type Currency = Balances; + type AssetId = AssetId; + type AssetBalance = AssetBalance; + type Assets = Assets; + type AssetIdByName = Ibc; + type AccountIdConversion = pallet_ibc::module::applications::transfer::IbcAccount; + const NATIVE_TOKEN_NAME: &'static [u8] = b"DEMO"; } // Build genesis storage according to the mock runtime. diff --git a/frame/ibc/src/module/core/ics04_channel.rs b/frame/ibc/src/module/core/ics04_channel.rs index 318a1864e46ec..ba2bd9c03ab15 100644 --- a/frame/ibc/src/module/core/ics04_channel.rs +++ b/frame/ibc/src/module/core/ics04_channel.rs @@ -121,51 +121,62 @@ impl ChannelReader for Context { &self, port_channel_id: &(PortId, ChannelId), ) -> Result { - trace!(target:"runtime::pallet-ibc","in channel : [get_next_sequence]"); + trace!(target:"runtime::pallet-ibc","in channel : [get_next_sequence] port_channel_id:{:?}",port_channel_id); let seq_sends_path = SeqSendsPath(port_channel_id.0.clone(), port_channel_id.1.clone()) .to_string() .as_bytes() .to_vec(); - let sequence = >::get(&seq_sends_path); + if >::contains_key(&seq_sends_path) { + let sequence = >::get(&seq_sends_path); - trace!(target:"runtime::pallet-ibc","in channel : [get_next_sequence] >> sequence = {:?}", sequence); - Ok(Sequence::from(sequence)) + trace!(target:"runtime::pallet-ibc","in channel : [get_next_sequence] >> sequence = {:?}", sequence); + Ok(Sequence::from(sequence)) + } else { + Err(Ics04Error::missing_next_send_seq(port_channel_id.clone())) + } } fn get_next_sequence_recv( &self, port_channel_id: &(PortId, ChannelId), ) -> Result { - trace!(target:"runtime::pallet-ibc","in channel : [get_next_sequence_recv]"); - + trace!(target:"runtime::pallet-ibc","in channel : [get_next_sequence_recv] port_channel_id:{:?}",port_channel_id); let seq_recvs_path = SeqRecvsPath(port_channel_id.0.clone(), port_channel_id.1.clone()) .to_string() .as_bytes() .to_vec(); - let sequence = >::get(&seq_recvs_path); + if >::contains_key(&seq_recvs_path) { + let sequence = >::get(&seq_recvs_path); - trace!(target:"runtime::pallet-ibc","in channel : [get_next_sequence_recv] >> sequence = {:?}", sequence); - Ok(Sequence::from(sequence)) + trace!(target:"runtime::pallet-ibc","in channel : [get_next_sequence_recv] >> sequence = {:?}", sequence); + Ok(Sequence::from(sequence)) + } else { + Err(Ics04Error::missing_next_recv_seq(port_channel_id.clone())) + } } fn get_next_sequence_ack( &self, port_channel_id: &(PortId, ChannelId), ) -> Result { - trace!(target:"runtime::pallet-ibc","in channel : [get_next_sequence_ack]"); + trace!(target:"runtime::pallet-ibc","in channel : [get_next_sequence_ack] port_channel_id:{:?}",port_channel_id); let seq_acks_path = SeqAcksPath(port_channel_id.0.clone(), port_channel_id.1.clone()) .to_string() .as_bytes() .to_vec(); - let sequence = >::get(&seq_acks_path); + if >::contains_key(&seq_acks_path) { + let sequence = >::get(&seq_acks_path); - trace!(target:"runtime::pallet-ibc","in channel : [get_next_sequence_ack] >> sequence = {}", sequence); - Ok(Sequence::from(sequence)) + trace!(target:"runtime::pallet-ibc","in channel : [get_next_sequence_ack] >> sequence = {}", sequence); + Ok(Sequence::from(sequence)) + } else { + Err(Ics04Error::missing_next_ack_seq(port_channel_id.clone())) + } } /// Returns the `PacketCommitment` for the given identifier `(PortId, ChannelId, Sequence)`. diff --git a/frame/ibc/src/tests.rs b/frame/ibc/src/tests.rs index e08c18afef5a7..27612ced9255b 100644 --- a/frame/ibc/src/tests.rs +++ b/frame/ibc/src/tests.rs @@ -1,15 +1,9 @@ use super::*; -use crate::{mock::*, routing::Context}; +use crate::{mock::*, Context}; use core::str::FromStr; use ibc::{ - applications::ics20_fungible_token_transfer::{ - context::Ics20Context, error::Error as ICS20Error, msgs::denom_trace::DenomTrace, - }, - clients::ics10_grandpa::{ - client_state::ClientState as GPClientState, - consensus_state::ConsensusState as GPConsensusState, help::ValidatorSet, - }, + applications::transfer::{context::Ics20Context, error::Error as ICS20Error}, core::{ ics02_client::{ client_consensus::AnyConsensusState, @@ -29,7 +23,7 @@ use ibc::{ error::Error as ICS04Error, packet::Sequence, }, - ics24_host::identifier::{ChainId, ChannelId, ClientId, ConnectionId, PortId}, + ics24_host::identifier::{ChainId, ChannelId, ClientId, ConnectionId, PortId}, ics23_commitment::commitment::CommitmentRoot, }, timestamp::Timestamp, Height, @@ -38,8 +32,8 @@ use ibc::{ // test store and read client-type #[test] fn test_store_client_type_ok() { - let gp_client_type = ClientType::Grandpa; - let gp_client_id = ClientId::new(gp_client_type, 0).unwrap(); + let gp_client_type = ClientType::Tendermint; + let gp_client_id = ClientId::default(); let mut context: Context = Context::new(); @@ -54,7 +48,7 @@ fn test_store_client_type_ok() { #[test] fn test_read_client_type_failed_by_supply_error_client_id() { - let gp_client_type = ClientType::Grandpa; + let gp_client_type = ClientType::Tendermint; let gp_client_id = ClientId::new(gp_client_type, 0).unwrap(); let gp_client_id_failed = ClientId::new(gp_client_type, 1).unwrap(); let mut context: Context = Context::new(); @@ -68,150 +62,13 @@ fn test_read_client_type_failed_by_supply_error_client_id() { }) } -// test store client_state -#[test] -fn test_store_client_state_ok() { - let gp_client_id = ClientId::new(ClientType::Grandpa, 0).unwrap(); - - let gp_client_state = GPClientState::new( - ChainId::new("ibc".to_string(), 0), - 0, - BlockHeader::default(), - Commitment::default(), - ValidatorSet::default(), - ) - .unwrap(); - let gp_client_state = AnyClientState::Grandpa(gp_client_state); - - let mut context: Context = Context::new(); - - new_test_ext().execute_with(|| { - assert!(context - .store_client_state(gp_client_id.clone(), gp_client_state.clone()) - .is_ok()); - - let ret = ClientReader::client_state(&context, &gp_client_id).unwrap(); - - assert_eq!(ret, gp_client_state); - }) -} - -#[test] -fn test_read_client_state_failed_by_supply_error_client_id() { - let gp_client_id = ClientId::new(ClientType::Grandpa, 0).unwrap(); - let gp_client_id_failed = ClientId::new(ClientType::Grandpa, 1).unwrap(); - let gp_client_state = GPClientState::new( - ChainId::new("ibc".to_string(), 0), - 0, - BlockHeader::default(), - Commitment::default(), - ValidatorSet::default(), - ) - .unwrap(); - let gp_client_state = AnyClientState::Grandpa(gp_client_state); - - let mut context: Context = Context::new(); - - new_test_ext().execute_with(|| { - assert!(context - .store_client_state(gp_client_id.clone(), gp_client_state.clone()) - .is_ok()); - - let ret = ClientReader::client_state(&context, &gp_client_id_failed) - .unwrap_err() - .to_string(); - - assert_eq!(ret, ICS02Error::client_not_found(gp_client_id_failed).to_string()); - }) -} - -#[test] -fn test_store_consensus_state_ok() { - let gp_client_id = ClientId::new(ClientType::Grandpa, 0).unwrap(); - let height = Height::default(); - let gp_consensus_state = GPConsensusState::default(); - let consensus_state = AnyConsensusState::Grandpa(gp_consensus_state); - - let mut context: Context = Context::new(); - - new_test_ext().execute_with(|| { - assert!(context - .store_consensus_state(gp_client_id.clone(), height, consensus_state.clone()) - .is_ok()); - - let ret = context.consensus_state(&gp_client_id, height).unwrap(); - - assert_eq!(ret, consensus_state); - }) -} - -#[test] -fn test_read_consensus_state_failed_by_supply_error_client_id() { - let gp_client_id = ClientId::new(ClientType::Grandpa, 0).unwrap(); - let gp_client_id_failed = ClientId::new(ClientType::Grandpa, 1).unwrap(); - - let height = Height::default(); - let gp_consensus_state = GPConsensusState::default(); - let consensus_state = AnyConsensusState::Grandpa(gp_consensus_state); - - let mut context: Context = Context::new(); - - new_test_ext().execute_with(|| { - assert_eq!( - context - .store_consensus_state(gp_client_id.clone(), height, consensus_state.clone()) - .is_ok(), - true - ); - - let ret = context.consensus_state(&gp_client_id_failed, height).unwrap_err().to_string(); - assert_eq!( - ret, - ICS02Error::consensus_state_not_found(gp_client_id_failed.clone(), height.clone()) - .to_string() - ); - }) -} - -#[test] -fn test_get_identified_any_client_state_ok() { - let range = (0..10).into_iter().collect::>(); - - let mut client_state_vec = vec![]; - let mut gp_client_id_vec = vec![]; - - for index in range.clone() { - let gp_client_id = ClientId::new(ClientType::Grandpa, index as u64).unwrap(); - let gp_client_state = GPClientState::new( - ChainId::new("ibc".to_string(), 0), - 0, - BlockHeader::default(), - Commitment::default(), - ValidatorSet::default(), - ) - .unwrap(); - let client_state = AnyClientState::Grandpa(gp_client_state); - - gp_client_id_vec.push(gp_client_id); - client_state_vec.push(client_state); - } - let mut context: Context = Context::new(); - new_test_ext().execute_with(|| { - for index in 0..range.len() { - assert!(context - .store_client_state( - gp_client_id_vec[index].clone(), - client_state_vec[index].clone() - ) - .is_ok()); - } - }) -} #[test] fn test_get_packet_commitment_state_ok() { + use ibc::core::ics04_channel::commitment::PacketCommitment; + let mut context: Context = Context::new(); let range = (0..10).into_iter().collect::>(); @@ -220,31 +77,13 @@ fn test_get_packet_commitment_state_ok() { let mut channel_id_vec = vec![]; let mut sequence_vec = vec![]; - let mut timestamp_vec = vec![]; - let mut height_vec = vec![]; - let mut data_vec = vec![]; - - let mut value_vec = vec![]; - for index in range.clone() { - let port_id = PortId::from_str(&format!("port-{}", index)).unwrap(); - port_id_vec.push(port_id); - let channel_id = ChannelId::from_str(&format!("channel-{}", index)).unwrap(); - channel_id_vec.push(channel_id); + port_id_vec.push( PortId::default()); + channel_id_vec.push(ChannelId::default()); let sequence = Sequence::from(index as u64); sequence_vec.push(sequence); - - let timestamp = Timestamp::from_nanoseconds(index as u64).unwrap(); - timestamp_vec.push(timestamp); - let height = Height::new(0, index as u64); - height_vec.push(height); - let data = vec![index]; - data_vec.push(data.clone()); - - let input = format!("{:?},{:?},{:?}", timestamp, height, data); - let value = ChannelReader::hash(&context, input).encode(); - value_vec.push(value); } + let com = PacketCommitment::from(vec![1, 2, 3]); new_test_ext().execute_with(|| { for index in 0..range.len() { @@ -255,9 +94,7 @@ fn test_get_packet_commitment_state_ok() { channel_id_vec[index].clone(), sequence_vec[index] ), - timestamp_vec[index], - height_vec[index], - data_vec[index].clone(), + com.clone(), ) .is_ok()); } @@ -270,14 +107,14 @@ fn test_connection_ok() { let mut input: HashMap = HashMap::new(); - let connection_id0 = ConnectionId::new(0); + let connection_id0 = ConnectionId::default(); let connection_end0 = ConnectionEnd::default(); - let connection_id1 = ConnectionId::new(1); - let mut connection_end1 = ConnectionEnd::default(); + let connection_id1 = ConnectionId::default(); + let connection_end1 = ConnectionEnd::default(); - let connection_id2 = ConnectionId::new(2); - let mut connection_end2 = ConnectionEnd::default(); + let connection_id2 = ConnectionId::default(); + let connection_end2 = ConnectionEnd::default(); input.insert(connection_id0.clone(), connection_end0.clone()); input.insert(connection_id1.clone(), connection_end1.clone()); @@ -322,7 +159,7 @@ fn test_connection_ok() { #[test] fn test_connection_fail() { - let connection_id0 = ConnectionId::new(0); + let connection_id0 = ConnectionId::default(); let context: Context = Context::new(); new_test_ext().execute_with(|| { let ret = ConnectionReader::connection_end(&context, &connection_id0.clone()) @@ -334,7 +171,7 @@ fn test_connection_fail() { #[test] fn test_connection_client_ok() { - let gp_client_id = ClientId::new(ClientType::Grandpa, 0).unwrap(); + let gp_client_id = ClientId::default(); let connection_id = ConnectionId::new(0); let mut context: Context = Context::new(); @@ -345,10 +182,12 @@ fn test_connection_client_ok() { #[test] fn test_delete_packet_acknowledgement_ok() { - let port_id = PortId::from_str("transfer").unwrap(); - let channel_id = ChannelId::from_str("channel-0").unwrap(); + use ibc::core::ics04_channel::commitment::AcknowledgementCommitment; + + let port_id = PortId::default(); + let channel_id = ChannelId::default(); let sequence = Sequence::from(0); - let ack = vec![1, 2, 3]; + let ack = AcknowledgementCommitment::from(vec![1, 2, 3]); let mut context: Context = Context::new(); @@ -375,6 +214,7 @@ fn test_delete_packet_acknowledgement_ok() { #[test] fn test_get_acknowledge_state() { + use ibc::core::ics04_channel::commitment::AcknowledgementCommitment; let range = (0..10).into_iter().collect::>(); let mut port_id_vec = vec![]; @@ -387,15 +227,12 @@ fn test_get_acknowledge_state() { let mut context: Context = Context::new(); for index in 0..range.len() { - let port_id = PortId::from_str(&format!("transfer-{}", index)).unwrap(); - port_id_vec.push(port_id); - let channel_id = ChannelId::from_str(&format!("channel-{}", index)).unwrap(); - channel_id_vec.push(channel_id); + port_id_vec.push(PortId::default()); + channel_id_vec.push(ChannelId::default()); let sequence = Sequence::from(index as u64); sequence_vec.push(sequence); - ack_vec.push(vec![index as u8]); - - value_vec.push(ChannelReader::hash(&context, format!("{:?}", vec![index as u8])).encode()); + ack_vec.push(AcknowledgementCommitment::from(vec![index as u8])); + value_vec.push(ChannelReader::hash(&context, vec![index as u8]).encode()); } new_test_ext().execute_with(|| { @@ -416,9 +253,9 @@ fn test_get_acknowledge_state() { #[test] fn test_store_connection_channles_ok() { - let connection_id = ConnectionId::new(0); - let port_id = PortId::from_str(String::from_str("port-0").unwrap().as_str()).unwrap(); - let channel_id = ChannelId::from_str(String::from_str("channel-0").unwrap().as_str()).unwrap(); + let connection_id = ConnectionId::default(); + let port_id = PortId::default(); + let channel_id = ChannelId::default(); let mut context: Context = Context::new(); new_test_ext().execute_with(|| { @@ -441,7 +278,7 @@ fn test_store_connection_channles_ok() { #[test] fn test_next_sequence_send_ok() { let sequence_id = Sequence::from(0); - let port_channel = (PortId::default(), ChannelId::new(0)); + let port_channel = (PortId::default(), ChannelId::default()); let mut context: Context = Context::new(); new_test_ext().execute_with(|| { @@ -478,8 +315,8 @@ fn test_read_conection_channels_failed_by_suppley_error_conneciton_id() { #[test] fn test_store_channel_ok() { - let port_id = PortId::from_str(String::from_str("port-0").unwrap().as_str()).unwrap(); - let channel_id = ChannelId::from_str(String::from_str("channel-0").unwrap().as_str()).unwrap(); + let port_id = PortId::default(); + let channel_id = ChannelId::default(); let channel_end = ChannelEnd::default(); let mut context: Context = Context::new(); @@ -498,7 +335,7 @@ fn test_store_channel_ok() { #[test] fn test_next_sequence_send_fail() { - let port_channel = (PortId::default(), ChannelId::new(0)); + let port_channel = (PortId::default(), ChannelId::default()); let context: Context = Context::new(); new_test_ext().execute_with(|| { @@ -510,7 +347,7 @@ fn test_next_sequence_send_fail() { #[test] fn test_next_sequence_recv_ok() { let sequence_id = Sequence::from(0); - let port_channel = (PortId::default(), ChannelId::new(0)); + let port_channel = (PortId::default(), ChannelId::default()); let mut context: Context = Context::new(); new_test_ext().execute_with(|| { @@ -520,55 +357,6 @@ fn test_next_sequence_recv_ok() { }) } -#[test] -fn test_read_channel_end_failed_by_supply_error_channel_id_port_id() { - let port_id = PortId::from_str(String::from_str("port-0").unwrap().as_str()).unwrap(); - let channel_id = ChannelId::from_str(String::from_str("channel-0").unwrap().as_str()).unwrap(); - let port_id_1 = PortId::from_str(String::from_str("port-1").unwrap().as_str()).unwrap(); - let channel_id_1 = - ChannelId::from_str(String::from_str("channel-1").unwrap().as_str()).unwrap(); - - let channel_end = ChannelEnd::default(); - - let mut context: Context = Context::new(); - - new_test_ext().execute_with(|| { - assert!(context - .store_channel((port_id.clone(), channel_id.clone()), &channel_end) - .is_ok()); - - let result = context - .channel_end(&(port_id_1.clone(), channel_id.clone())) - .unwrap_err() - .to_string(); - - assert_eq!( - result, - ICS04Error::channel_not_found(port_id_1.clone(), channel_id.clone()).to_string() - ); - - let result = context - .channel_end(&(port_id.clone(), channel_id_1.clone())) - .unwrap_err() - .to_string(); - - assert_eq!( - result, - ICS04Error::channel_not_found(port_id.clone(), channel_id_1.clone()).to_string() - ); - - let result = context - .channel_end(&(port_id_1.clone(), channel_id_1.clone())) - .unwrap_err() - .to_string(); - - assert_eq!( - result, - ICS04Error::channel_not_found(port_id_1.clone(), channel_id_1.clone()).to_string() - ); - }) -} - #[test] fn test_get_identified_channel_end() { let range = (0..10).into_iter().collect::>(); @@ -578,14 +366,8 @@ fn test_get_identified_channel_end() { let channel_end_vec = vec![ChannelEnd::default(); range.len()]; for index in 0..range.len() { - let port_id = - PortId::from_str(String::from_str(&format!("prot-{}", index)).unwrap().as_str()) - .unwrap(); - port_id_vec.push(port_id); - let channel_id = - ChannelId::from_str(String::from_str(&format!("channel-{}", index)).unwrap().as_str()) - .unwrap(); - channel_id_vec.push(channel_id); + port_id_vec.push(PortId::default()); + channel_id_vec.push(ChannelId::default()); } let mut context: Context = Context::new(); @@ -603,7 +385,7 @@ fn test_get_identified_channel_end() { #[test] fn test_next_sequence_recv_fail() { - let port_channel = (PortId::default(), ChannelId::new(0)); + let port_channel = (PortId::default(), ChannelId::default()); let context: Context = Context::new(); new_test_ext().execute_with(|| { @@ -615,7 +397,7 @@ fn test_next_sequence_recv_fail() { #[test] fn test_next_sequence_ack_ok() { let sequence_id = Sequence::from(0); - let port_channel = (PortId::default(), ChannelId::new(0)); + let port_channel = (PortId::default(), ChannelId::default()); let mut context: Context = Context::new(); new_test_ext().execute_with(|| { @@ -627,7 +409,7 @@ fn test_next_sequence_ack_ok() { #[test] fn test_next_sequence_ack_fail() { - let port_channel = (PortId::default(), ChannelId::new(0)); + let port_channel = (PortId::default(), ChannelId::default()); let context: Context = Context::new(); new_test_ext().execute_with(|| { From ee99bfa289512c8e539359c598a9fb5bcb5bbb4e Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Mon, 7 Mar 2022 09:12:03 +0100 Subject: [PATCH 006/484] Minor Uniques pallet improvements and XCM v3 preparations (#10896) * Introduce Helper to Uniques for benchmark stuff * Fixes * Formatting * Featuregate the Helper, include ContainsPair * Introduce & use EnsureOriginWithArg * Benchmarking * Docs * More ContainsBoth helpers * Formatting * Formatting * Fixes Co-authored-by: Shawn Tabrizi --- bin/node/runtime/src/lib.rs | 9 +- frame/support/src/traits.rs | 10 +- frame/support/src/traits/dispatch.rs | 48 +++++ frame/support/src/traits/members.rs | 150 ++++++++++++--- .../support/src/traits/tokens/nonfungible.rs | 9 +- .../support/src/traits/tokens/nonfungibles.rs | 6 +- frame/uniques/src/benchmarking.rs | 73 +++++--- frame/uniques/src/impl_nonfungibles.rs | 15 +- frame/uniques/src/lib.rs | 172 ++++++++++++------ frame/uniques/src/mock.rs | 5 +- frame/uniques/src/tests.rs | 18 ++ frame/uniques/src/weights.rs | 15 ++ 12 files changed, 417 insertions(+), 113 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index f12bf8a88365f..e2903c0b314da 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -27,9 +27,9 @@ use frame_election_provider_support::onchain; use frame_support::{ construct_runtime, parameter_types, traits::{ - ConstU128, ConstU16, ConstU32, Currency, EnsureOneOf, EqualPrivilegeOnly, Everything, - Imbalance, InstanceFilter, KeyOwnerProofSystem, LockIdentifier, Nothing, OnUnbalanced, - U128CurrencyToVote, + AsEnsureOriginWithArg, ConstU128, ConstU16, ConstU32, Currency, EnsureOneOf, + EqualPrivilegeOnly, Everything, Imbalance, InstanceFilter, KeyOwnerProofSystem, + LockIdentifier, Nothing, OnUnbalanced, U128CurrencyToVote, }, weights::{ constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND}, @@ -1348,6 +1348,9 @@ impl pallet_uniques::Config for Runtime { type KeyLimit = KeyLimit; type ValueLimit = ValueLimit; type WeightInfo = pallet_uniques::weights::SubstrateWeight; + #[cfg(feature = "runtime-benchmarks")] + type Helper = (); + type CreateOrigin = AsEnsureOriginWithArg>; } impl pallet_transaction_storage::Config for Runtime { diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index 88891c83276bd..a8ce78ae9dabc 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -34,8 +34,9 @@ mod members; #[allow(deprecated)] pub use members::{AllowAll, DenyAll, Filter}; pub use members::{ - AsContains, ChangeMembers, Contains, ContainsLengthBound, Everything, InitializeMembers, - IsInVec, Nothing, SortedMembers, + AsContains, ChangeMembers, Contains, ContainsLengthBound, ContainsPair, Everything, + EverythingBut, FromContainsPair, InitializeMembers, InsideBoth, IsInVec, Nothing, + SortedMembers, TheseExcept, }; mod validation; @@ -89,7 +90,10 @@ pub use storage::{ }; mod dispatch; -pub use dispatch::{EnsureOneOf, EnsureOrigin, OriginTrait, UnfilteredDispatchable}; +pub use dispatch::{ + AsEnsureOriginWithArg, EnsureOneOf, EnsureOrigin, EnsureOriginWithArg, OriginTrait, + UnfilteredDispatchable, +}; mod voting; pub use voting::{ diff --git a/frame/support/src/traits/dispatch.rs b/frame/support/src/traits/dispatch.rs index 1a4e9f6f7cc2a..250a31ebfb17a 100644 --- a/frame/support/src/traits/dispatch.rs +++ b/frame/support/src/traits/dispatch.rs @@ -27,10 +27,12 @@ use sp_runtime::{ pub trait EnsureOrigin { /// A return type. type Success; + /// Perform the origin check. fn ensure_origin(o: OuterOrigin) -> Result { Self::try_origin(o).map_err(|_| BadOrigin) } + /// Perform the origin check. fn try_origin(o: OuterOrigin) -> Result; @@ -41,6 +43,52 @@ pub trait EnsureOrigin { fn successful_origin() -> OuterOrigin; } +/// Some sort of check on the origin is performed by this object. +pub trait EnsureOriginWithArg { + /// A return type. + type Success; + + /// Perform the origin check. + fn ensure_origin(o: OuterOrigin, a: &Argument) -> Result { + Self::try_origin(o, a).map_err(|_| BadOrigin) + } + + /// Perform the origin check, returning the origin value if unsuccessful. This allows chaining. + fn try_origin(o: OuterOrigin, a: &Argument) -> Result; + + /// Returns an outer origin capable of passing `try_origin` check. + /// + /// ** Should be used for benchmarking only!!! ** + #[cfg(feature = "runtime-benchmarks")] + fn successful_origin(a: &Argument) -> OuterOrigin; +} + +pub struct AsEnsureOriginWithArg(sp_std::marker::PhantomData); +impl> + EnsureOriginWithArg for AsEnsureOriginWithArg +{ + /// A return type. + type Success = EO::Success; + + /// Perform the origin check. + fn ensure_origin(o: OuterOrigin, _: &Argument) -> Result { + EO::ensure_origin(o) + } + + /// Perform the origin check, returning the origin value if unsuccessful. This allows chaining. + fn try_origin(o: OuterOrigin, _: &Argument) -> Result { + EO::try_origin(o) + } + + /// Returns an outer origin capable of passing `try_origin` check. + /// + /// ** Should be used for benchmarking only!!! ** + #[cfg(feature = "runtime-benchmarks")] + fn successful_origin(_: &Argument) -> OuterOrigin { + EO::successful_origin() + } +} + /// Type that can be dispatched with an origin but without checking the origin filter. /// /// Implemented for pallet dispatchable type by `decl_module` and for runtime dispatchable by diff --git a/frame/support/src/traits/members.rs b/frame/support/src/traits/members.rs index ba72b4819933b..f3c586b64af04 100644 --- a/frame/support/src/traits/members.rs +++ b/frame/support/src/traits/members.rs @@ -25,6 +25,40 @@ pub trait Contains { fn contains(t: &T) -> bool; } +#[impl_trait_for_tuples::impl_for_tuples(1, 30)] +impl Contains for Tuple { + fn contains(t: &T) -> bool { + for_tuples!( #( + if Tuple::contains(t) { return true } + )* ); + false + } +} + +/// A trait for querying whether a type can be said to "contain" a pair-value. +pub trait ContainsPair { + /// Return `true` if this "contains" the pair-value `(a, b)`. + fn contains(a: &A, b: &B) -> bool; +} + +#[impl_trait_for_tuples::impl_for_tuples(0, 30)] +impl ContainsPair for Tuple { + fn contains(a: &A, b: &B) -> bool { + for_tuples!( #( + if Tuple::contains(a, b) { return true } + )* ); + false + } +} + +/// Converter `struct` to use a `ContainsPair` implementation for a `Contains` bound. +pub struct FromContainsPair(PhantomData); +impl> Contains<(A, B)> for FromContainsPair { + fn contains((ref a, ref b): &(A, B)) -> bool { + CP::contains(a, b) + } +} + /// A [`Contains`] implementation that contains every value. pub enum Everything {} impl Contains for Everything { @@ -32,6 +66,11 @@ impl Contains for Everything { true } } +impl ContainsPair for Everything { + fn contains(_: &A, _: &B) -> bool { + true + } +} /// A [`Contains`] implementation that contains no value. pub enum Nothing {} @@ -40,43 +79,112 @@ impl Contains for Nothing { false } } +impl ContainsPair for Nothing { + fn contains(_: &A, _: &B) -> bool { + false + } +} -#[deprecated = "Use `Everything` instead"] -pub type AllowAll = Everything; -#[deprecated = "Use `Nothing` instead"] -pub type DenyAll = Nothing; -#[deprecated = "Use `Contains` instead"] -pub trait Filter { - fn filter(t: &T) -> bool; +/// A [`Contains`] implementation that contains everything except the values in `Exclude`. +pub struct EverythingBut(PhantomData); +impl> Contains for EverythingBut { + fn contains(t: &T) -> bool { + !Exclude::contains(t) + } } -#[allow(deprecated)] -impl> Filter for C { - fn filter(t: &T) -> bool { - Self::contains(t) +impl> ContainsPair for EverythingBut { + fn contains(a: &A, b: &B) -> bool { + !Exclude::contains(a, b) } } -#[impl_trait_for_tuples::impl_for_tuples(1, 30)] -impl Contains for Tuple { +/// A [`Contains`] implementation that contains all members of `These` excepting any members in +/// `Except`. +pub struct TheseExcept(PhantomData<(These, Except)>); +impl, Except: Contains> Contains for TheseExcept { fn contains(t: &T) -> bool { - for_tuples!( #( - if Tuple::contains(t) { return true } - )* ); - false + These::contains(t) && !Except::contains(t) + } +} +impl, Except: ContainsPair> ContainsPair + for TheseExcept +{ + fn contains(a: &A, b: &B) -> bool { + These::contains(a, b) && !Except::contains(a, b) + } +} + +/// A [`Contains`] implementation which contains all members of `These` which are also members of +/// `Those`. +pub struct InsideBoth(PhantomData<(These, Those)>); +impl, Those: Contains> Contains for InsideBoth { + fn contains(t: &T) -> bool { + These::contains(t) && Those::contains(t) + } +} +impl, Those: ContainsPair> ContainsPair + for InsideBoth +{ + fn contains(a: &A, b: &B) -> bool { + These::contains(a, b) && Those::contains(a, b) } } /// Create a type which implements the `Contains` trait for a particular type with syntax similar /// to `matches!`. #[macro_export] -macro_rules! match_type { - ( pub type $n:ident: impl Contains<$t:ty> = { $phead:pat_param $( | $ptail:pat )* } ; ) => { +macro_rules! match_types { + ( + pub type $n:ident: impl Contains<$t:ty> = { + $phead:pat_param $( | $ptail:pat )* + }; + $( $rest:tt )* + ) => { pub struct $n; impl $crate::traits::Contains<$t> for $n { fn contains(l: &$t) -> bool { matches!(l, $phead $( | $ptail )* ) } } + $crate::match_types!( $( $rest )* ); + }; + ( + pub type $n:ident: impl ContainsPair<$a:ty, $b:ty> = { + $phead:pat_param $( | $ptail:pat )* + }; + $( $rest:tt )* + ) => { + pub struct $n; + impl $crate::traits::ContainsPair<$a, $b> for $n { + fn contains(a: &$a, b: &$b) -> bool { + matches!((a, b), $phead $( | $ptail )* ) + } + } + $crate::match_types!( $( $rest )* ); + }; + () => {} +} + +/// Create a type which implements the `Contains` trait for a particular type with syntax similar +/// to `matches!`. +#[macro_export] +#[deprecated = "Use `match_types!` instead"] +macro_rules! match_type { + ($( $x:tt )*) => { $crate::match_types!( $( $x )* ); } +} + +#[deprecated = "Use `Everything` instead"] +pub type AllowAll = Everything; +#[deprecated = "Use `Nothing` instead"] +pub type DenyAll = Nothing; +#[deprecated = "Use `Contains` instead"] +pub trait Filter { + fn filter(t: &T) -> bool; +} +#[allow(deprecated)] +impl> Filter for C { + fn filter(t: &T) -> bool { + Self::contains(t) } } @@ -84,12 +192,12 @@ macro_rules! match_type { mod tests { use super::*; - match_type! { + match_types! { pub type OneOrTenToTwenty: impl Contains = { 1 | 10..=20 }; } #[test] - fn match_type_works() { + fn match_types_works() { for i in 0..=255 { assert_eq!(OneOrTenToTwenty::contains(&i), i == 1 || i >= 10 && i <= 20); } diff --git a/frame/support/src/traits/tokens/nonfungible.rs b/frame/support/src/traits/tokens/nonfungible.rs index 08e9a3a18a4b8..5cf9638131b28 100644 --- a/frame/support/src/traits/tokens/nonfungible.rs +++ b/frame/support/src/traits/tokens/nonfungible.rs @@ -85,7 +85,10 @@ pub trait Mutate: Inspect { /// Burn some asset `instance`. /// /// By default, this is not a supported operation. - fn burn_from(_instance: &Self::InstanceId) -> DispatchResult { + fn burn( + _instance: &Self::InstanceId, + _maybe_check_owner: Option<&AccountId>, + ) -> DispatchResult { Err(TokenError::Unsupported.into()) } @@ -166,8 +169,8 @@ impl< fn mint_into(instance: &Self::InstanceId, who: &AccountId) -> DispatchResult { >::mint_into(&A::get(), instance, who) } - fn burn_from(instance: &Self::InstanceId) -> DispatchResult { - >::burn_from(&A::get(), instance) + fn burn(instance: &Self::InstanceId, maybe_check_owner: Option<&AccountId>) -> DispatchResult { + >::burn(&A::get(), instance, maybe_check_owner) } fn set_attribute(instance: &Self::InstanceId, key: &[u8], value: &[u8]) -> DispatchResult { >::set_attribute(&A::get(), instance, key, value) diff --git a/frame/support/src/traits/tokens/nonfungibles.rs b/frame/support/src/traits/tokens/nonfungibles.rs index 1172fb6022830..8bd731b20342c 100644 --- a/frame/support/src/traits/tokens/nonfungibles.rs +++ b/frame/support/src/traits/tokens/nonfungibles.rs @@ -165,7 +165,11 @@ pub trait Mutate: Inspect { /// Burn some asset `instance` of `class`. /// /// By default, this is not a supported operation. - fn burn_from(_class: &Self::ClassId, _instance: &Self::InstanceId) -> DispatchResult { + fn burn( + _class: &Self::ClassId, + _instance: &Self::InstanceId, + _maybe_check_owner: Option<&AccountId>, + ) -> DispatchResult { Err(TokenError::Unsupported.into()) } diff --git a/frame/uniques/src/benchmarking.rs b/frame/uniques/src/benchmarking.rs index f3f9b7b28df72..d6223ec88f81b 100644 --- a/frame/uniques/src/benchmarking.rs +++ b/frame/uniques/src/benchmarking.rs @@ -40,12 +40,13 @@ fn create_class, I: 'static>( ) -> (T::ClassId, T::AccountId, ::Source) { let caller: T::AccountId = whitelisted_caller(); let caller_lookup = T::Lookup::unlookup(caller.clone()); - let class = Default::default(); + let class = T::Helper::class(0); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); - assert!(Uniques::::create( - SystemOrigin::Signed(caller.clone()).into(), + assert!(Uniques::::force_create( + SystemOrigin::Root.into(), class, caller_lookup.clone(), + false, ) .is_ok()); (class, caller, caller_lookup) @@ -53,14 +54,14 @@ fn create_class, I: 'static>( fn add_class_metadata, I: 'static>( ) -> (T::AccountId, ::Source) { - let caller = Class::::get(T::ClassId::default()).unwrap().owner; + let caller = Class::::get(T::Helper::class(0)).unwrap().owner; if caller != whitelisted_caller() { whitelist_account!(caller); } let caller_lookup = T::Lookup::unlookup(caller.clone()); assert!(Uniques::::set_class_metadata( SystemOrigin::Signed(caller.clone()).into(), - Default::default(), + T::Helper::class(0), vec![0; T::StringLimit::get() as usize].try_into().unwrap(), false, ) @@ -71,15 +72,15 @@ fn add_class_metadata, I: 'static>( fn mint_instance, I: 'static>( index: u16, ) -> (T::InstanceId, T::AccountId, ::Source) { - let caller = Class::::get(T::ClassId::default()).unwrap().admin; + let caller = Class::::get(T::Helper::class(0)).unwrap().admin; if caller != whitelisted_caller() { whitelist_account!(caller); } let caller_lookup = T::Lookup::unlookup(caller.clone()); - let instance = index.into(); + let instance = T::Helper::instance(index); assert!(Uniques::::mint( SystemOrigin::Signed(caller.clone()).into(), - Default::default(), + T::Helper::class(0), instance, caller_lookup.clone(), ) @@ -90,14 +91,14 @@ fn mint_instance, I: 'static>( fn add_instance_metadata, I: 'static>( instance: T::InstanceId, ) -> (T::AccountId, ::Source) { - let caller = Class::::get(T::ClassId::default()).unwrap().owner; + let caller = Class::::get(T::Helper::class(0)).unwrap().owner; if caller != whitelisted_caller() { whitelist_account!(caller); } let caller_lookup = T::Lookup::unlookup(caller.clone()); assert!(Uniques::::set_metadata( SystemOrigin::Signed(caller.clone()).into(), - Default::default(), + T::Helper::class(0), instance, vec![0; T::StringLimit::get() as usize].try_into().unwrap(), false, @@ -109,7 +110,7 @@ fn add_instance_metadata, I: 'static>( fn add_instance_attribute, I: 'static>( instance: T::InstanceId, ) -> (BoundedVec, T::AccountId, ::Source) { - let caller = Class::::get(T::ClassId::default()).unwrap().owner; + let caller = Class::::get(T::Helper::class(0)).unwrap().owner; if caller != whitelisted_caller() { whitelist_account!(caller); } @@ -117,7 +118,7 @@ fn add_instance_attribute, I: 'static>( let key: BoundedVec<_, _> = vec![0; T::KeyLimit::get() as usize].try_into().unwrap(); assert!(Uniques::::set_attribute( SystemOrigin::Signed(caller.clone()).into(), - Default::default(), + T::Helper::class(0), Some(instance), key.clone(), vec![0; T::ValueLimit::get() as usize].try_into().unwrap(), @@ -136,20 +137,24 @@ fn assert_last_event, I: 'static>(generic_event: >:: benchmarks_instance_pallet! { create { - let caller: T::AccountId = whitelisted_caller(); - let caller_lookup = T::Lookup::unlookup(caller.clone()); + let class = T::Helper::class(0); + let origin = T::CreateOrigin::successful_origin(&class); + let caller = T::CreateOrigin::ensure_origin(origin.clone(), &class).unwrap(); + whitelist_account!(caller); + let admin = T::Lookup::unlookup(caller.clone()); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); - }: _(SystemOrigin::Signed(caller.clone()), Default::default(), caller_lookup) + let call = Call::::create { class, admin }; + }: { call.dispatch_bypass_filter(origin)? } verify { - assert_last_event::(Event::Created { class: Default::default(), creator: caller.clone(), owner: caller }.into()); + assert_last_event::(Event::Created { class: T::Helper::class(0), creator: caller.clone(), owner: caller }.into()); } force_create { let caller: T::AccountId = whitelisted_caller(); let caller_lookup = T::Lookup::unlookup(caller.clone()); - }: _(SystemOrigin::Root, Default::default(), caller_lookup, true) + }: _(SystemOrigin::Root, T::Helper::class(0), caller_lookup, true) verify { - assert_last_event::(Event::ForceCreated { class: Default::default(), owner: caller }.into()); + assert_last_event::(Event::ForceCreated { class: T::Helper::class(0), owner: caller }.into()); } destroy { @@ -163,10 +168,10 @@ benchmarks_instance_pallet! { mint_instance::(i as u16); } for i in 0..m { - add_instance_metadata::((i as u16).into()); + add_instance_metadata::(T::Helper::instance(i as u16)); } for i in 0..a { - add_instance_attribute::((i as u16).into()); + add_instance_attribute::(T::Helper::instance(i as u16)); } let witness = Class::::get(class).unwrap().destroy_witness(); }: _(SystemOrigin::Signed(caller), class, witness) @@ -176,7 +181,7 @@ benchmarks_instance_pallet! { mint { let (class, caller, caller_lookup) = create_class::(); - let instance = Default::default(); + let instance = T::Helper::instance(0); }: _(SystemOrigin::Signed(caller.clone()), class, instance, caller_lookup) verify { assert_last_event::(Event::Issued { class, instance, owner: caller }.into()); @@ -192,7 +197,7 @@ benchmarks_instance_pallet! { transfer { let (class, caller, caller_lookup) = create_class::(); - let (instance, ..) = mint_instance::(Default::default()); + let (instance, ..) = mint_instance::(0); let target: T::AccountId = account("target", 0, SEED); let target_lookup = T::Lookup::unlookup(target.clone()); @@ -222,15 +227,15 @@ benchmarks_instance_pallet! { freeze { let (class, caller, caller_lookup) = create_class::(); - let (instance, ..) = mint_instance::(Default::default()); - }: _(SystemOrigin::Signed(caller.clone()), Default::default(), Default::default()) + let (instance, ..) = mint_instance::(0); + }: _(SystemOrigin::Signed(caller.clone()), T::Helper::class(0), T::Helper::instance(0)) verify { - assert_last_event::(Event::Frozen { class: Default::default(), instance: Default::default() }.into()); + assert_last_event::(Event::Frozen { class: T::Helper::class(0), instance: T::Helper::instance(0) }.into()); } thaw { let (class, caller, caller_lookup) = create_class::(); - let (instance, ..) = mint_instance::(Default::default()); + let (instance, ..) = mint_instance::(0); Uniques::::freeze( SystemOrigin::Signed(caller.clone()).into(), class, @@ -262,6 +267,8 @@ benchmarks_instance_pallet! { let target: T::AccountId = account("target", 0, SEED); let target_lookup = T::Lookup::unlookup(target.clone()); T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance()); + let origin = SystemOrigin::Signed(target.clone()).into(); + Uniques::::set_accept_ownership(origin, Some(class.clone()))?; }: _(SystemOrigin::Signed(caller), class, target_lookup) verify { assert_last_event::(Event::OwnerChanged { class, new_owner: target }.into()); @@ -272,7 +279,7 @@ benchmarks_instance_pallet! { let target0 = T::Lookup::unlookup(account("target", 0, SEED)); let target1 = T::Lookup::unlookup(account("target", 1, SEED)); let target2 = T::Lookup::unlookup(account("target", 2, SEED)); - }: _(SystemOrigin::Signed(caller), Default::default(), target0.clone(), target1.clone(), target2.clone()) + }: _(SystemOrigin::Signed(caller), class, target0.clone(), target1.clone(), target2.clone()) verify { assert_last_event::(Event::TeamChanged{ class, @@ -379,5 +386,17 @@ benchmarks_instance_pallet! { assert_last_event::(Event::ApprovalCancelled { class, instance, owner: caller, delegate }.into()); } + set_accept_ownership { + let caller: T::AccountId = whitelisted_caller(); + T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); + let class = T::Helper::class(0); + }: _(SystemOrigin::Signed(caller.clone()), Some(class.clone())) + verify { + assert_last_event::(Event::OwnershipAcceptanceChanged { + who: caller, + maybe_class: Some(class), + }.into()); + } + impl_benchmark_test_suite!(Uniques, crate::mock::new_test_ext(), crate::mock::Test); } diff --git a/frame/uniques/src/impl_nonfungibles.rs b/frame/uniques/src/impl_nonfungibles.rs index a9695e8e898ae..89b95fb770489 100644 --- a/frame/uniques/src/impl_nonfungibles.rs +++ b/frame/uniques/src/impl_nonfungibles.rs @@ -128,8 +128,19 @@ impl, I: 'static> Mutate<::AccountId> for Pallet Self::do_mint(class.clone(), instance.clone(), who.clone(), |_| Ok(())) } - fn burn_from(class: &Self::ClassId, instance: &Self::InstanceId) -> DispatchResult { - Self::do_burn(class.clone(), instance.clone(), |_, _| Ok(())) + fn burn( + class: &Self::ClassId, + instance: &Self::InstanceId, + maybe_check_owner: Option<&T::AccountId>, + ) -> DispatchResult { + Self::do_burn(class.clone(), instance.clone(), |_, d| { + if let Some(check_owner) = maybe_check_owner { + if &d.owner != check_owner { + Err(Error::::NoPermission)?; + } + } + Ok(()) + }) } } diff --git a/frame/uniques/src/lib.rs b/frame/uniques/src/lib.rs index f35bb3fd61a0f..1e14825454193 100644 --- a/frame/uniques/src/lib.rs +++ b/frame/uniques/src/lib.rs @@ -41,8 +41,10 @@ mod types; pub mod migration; pub mod weights; -use codec::{Decode, Encode, HasCompact}; -use frame_support::traits::{BalanceStatus::Reserved, Currency, ReservableCurrency}; +use codec::{Decode, Encode}; +use frame_support::traits::{ + BalanceStatus::Reserved, Currency, EnsureOriginWithArg, ReservableCurrency, +}; use frame_system::Config as SystemConfig; use sp_runtime::{ traits::{Saturating, StaticLookup, Zero}, @@ -64,6 +66,21 @@ pub mod pallet { #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); + #[cfg(feature = "runtime-benchmarks")] + pub trait BenchmarkHelper { + fn class(i: u16) -> ClassId; + fn instance(i: u16) -> InstanceId; + } + #[cfg(feature = "runtime-benchmarks")] + impl, InstanceId: From> BenchmarkHelper for () { + fn class(i: u16) -> ClassId { + i.into() + } + fn instance(i: u16) -> InstanceId { + i.into() + } + } + #[pallet::config] /// The module configuration trait. pub trait Config: frame_system::Config { @@ -71,16 +88,10 @@ pub mod pallet { type Event: From> + IsType<::Event>; /// Identifier for the class of asset. - type ClassId: Member + Parameter + Default + Copy + HasCompact + MaxEncodedLen; + type ClassId: Member + Parameter + MaxEncodedLen + Copy; /// The type used to identify a unique asset within an asset class. - type InstanceId: Member - + Parameter - + Default - + Copy - + HasCompact - + From - + MaxEncodedLen; + type InstanceId: Member + Parameter + MaxEncodedLen + Copy; /// The currency mechanism, used for paying for reserves. type Currency: ReservableCurrency; @@ -89,6 +100,14 @@ pub mod pallet { /// attributes. type ForceOrigin: EnsureOrigin; + /// Standard class creation is only allowed if the origin attempting it and the class are + /// in this set. + type CreateOrigin: EnsureOriginWithArg< + Success = Self::AccountId, + Self::Origin, + Self::ClassId, + >; + /// The basic amount of funds that must be reserved for an asset class. #[pallet::constant] type ClassDeposit: Get>; @@ -122,6 +141,10 @@ pub mod pallet { #[pallet::constant] type ValueLimit: Get; + #[cfg(feature = "runtime-benchmarks")] + /// A set of helper functions for benchmarking. + type Helper: BenchmarkHelper; + /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; } @@ -135,6 +158,11 @@ pub mod pallet { ClassDetails>, >; + #[pallet::storage] + /// The class, if any, of which an account is willing to take ownership. + pub(super) type OwnershipAcceptance, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, T::AccountId, T::ClassId>; + #[pallet::storage] /// The assets held by any given account; set out this way so that assets owned by a single /// account can be enumerated. @@ -296,6 +324,8 @@ pub mod pallet { maybe_instance: Option, key: BoundedVec, }, + /// Ownership acceptance has changed for an account. + OwnershipAcceptanceChanged { who: T::AccountId, maybe_class: Option }, } #[pallet::error] @@ -320,6 +350,8 @@ pub mod pallet { NoDelegate, /// No approval exists that would allow the transfer. Unapproved, + /// The named owner has not signed ownership of the class is acceptable. + Unaccepted, } impl, I: 'static> Pallet { @@ -327,6 +359,11 @@ pub mod pallet { pub fn owner(class: T::ClassId, instance: T::InstanceId) -> Option { Asset::::get(class, instance).map(|i| i.owner) } + + /// Get the owner of the asset instance, if the asset exists. + pub fn class_owner(class: T::ClassId) -> Option { + Class::::get(class).map(|i| i.owner) + } } #[pallet::call] @@ -350,10 +387,10 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::create())] pub fn create( origin: OriginFor, - #[pallet::compact] class: T::ClassId, + class: T::ClassId, admin: ::Source, ) -> DispatchResult { - let owner = ensure_signed(origin)?; + let owner = T::CreateOrigin::ensure_origin(origin, &class)?; let admin = T::Lookup::lookup(admin)?; Self::do_create_class( @@ -385,7 +422,7 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::force_create())] pub fn force_create( origin: OriginFor, - #[pallet::compact] class: T::ClassId, + class: T::ClassId, owner: ::Source, free_holding: bool, ) -> DispatchResult { @@ -424,7 +461,7 @@ pub mod pallet { ))] pub fn destroy( origin: OriginFor, - #[pallet::compact] class: T::ClassId, + class: T::ClassId, witness: DestroyWitness, ) -> DispatchResultWithPostInfo { let maybe_check_owner = match T::ForceOrigin::try_origin(origin) { @@ -455,8 +492,8 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::mint())] pub fn mint( origin: OriginFor, - #[pallet::compact] class: T::ClassId, - #[pallet::compact] instance: T::InstanceId, + class: T::ClassId, + instance: T::InstanceId, owner: ::Source, ) -> DispatchResult { let origin = ensure_signed(origin)?; @@ -484,8 +521,8 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::burn())] pub fn burn( origin: OriginFor, - #[pallet::compact] class: T::ClassId, - #[pallet::compact] instance: T::InstanceId, + class: T::ClassId, + instance: T::InstanceId, check_owner: Option<::Source>, ) -> DispatchResult { let origin = ensure_signed(origin)?; @@ -520,8 +557,8 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::transfer())] pub fn transfer( origin: OriginFor, - #[pallet::compact] class: T::ClassId, - #[pallet::compact] instance: T::InstanceId, + class: T::ClassId, + instance: T::InstanceId, dest: ::Source, ) -> DispatchResult { let origin = ensure_signed(origin)?; @@ -556,7 +593,7 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::redeposit(instances.len() as u32))] pub fn redeposit( origin: OriginFor, - #[pallet::compact] class: T::ClassId, + class: T::ClassId, instances: Vec, ) -> DispatchResult { let origin = ensure_signed(origin)?; @@ -616,8 +653,8 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::freeze())] pub fn freeze( origin: OriginFor, - #[pallet::compact] class: T::ClassId, - #[pallet::compact] instance: T::InstanceId, + class: T::ClassId, + instance: T::InstanceId, ) -> DispatchResult { let origin = ensure_signed(origin)?; @@ -646,8 +683,8 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::thaw())] pub fn thaw( origin: OriginFor, - #[pallet::compact] class: T::ClassId, - #[pallet::compact] instance: T::InstanceId, + class: T::ClassId, + instance: T::InstanceId, ) -> DispatchResult { let origin = ensure_signed(origin)?; @@ -673,10 +710,7 @@ pub mod pallet { /// /// Weight: `O(1)` #[pallet::weight(T::WeightInfo::freeze_class())] - pub fn freeze_class( - origin: OriginFor, - #[pallet::compact] class: T::ClassId, - ) -> DispatchResult { + pub fn freeze_class(origin: OriginFor, class: T::ClassId) -> DispatchResult { let origin = ensure_signed(origin)?; Class::::try_mutate(class, |maybe_details| { @@ -700,10 +734,7 @@ pub mod pallet { /// /// Weight: `O(1)` #[pallet::weight(T::WeightInfo::thaw_class())] - pub fn thaw_class( - origin: OriginFor, - #[pallet::compact] class: T::ClassId, - ) -> DispatchResult { + pub fn thaw_class(origin: OriginFor, class: T::ClassId) -> DispatchResult { let origin = ensure_signed(origin)?; Class::::try_mutate(class, |maybe_details| { @@ -722,7 +753,8 @@ pub mod pallet { /// Origin must be Signed and the sender should be the Owner of the asset `class`. /// /// - `class`: The asset class whose owner should be changed. - /// - `owner`: The new Owner of this asset class. + /// - `owner`: The new Owner of this asset class. They must have called + /// `set_accept_ownership` with `class` in order for this operation to succeed. /// /// Emits `OwnerChanged`. /// @@ -730,12 +762,15 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::transfer_ownership())] pub fn transfer_ownership( origin: OriginFor, - #[pallet::compact] class: T::ClassId, + class: T::ClassId, owner: ::Source, ) -> DispatchResult { let origin = ensure_signed(origin)?; let owner = T::Lookup::lookup(owner)?; + let acceptable_class = OwnershipAcceptance::::get(&owner); + ensure!(acceptable_class.as_ref() == Some(&class), Error::::Unaccepted); + Class::::try_mutate(class, |maybe_details| { let details = maybe_details.as_mut().ok_or(Error::::UnknownClass)?; ensure!(&origin == &details.owner, Error::::NoPermission); @@ -753,6 +788,7 @@ pub mod pallet { ClassAccount::::remove(&details.owner, &class); ClassAccount::::insert(&owner, &class, ()); details.owner = owner.clone(); + OwnershipAcceptance::::remove(&owner); Self::deposit_event(Event::OwnerChanged { class, new_owner: owner }); Ok(()) @@ -774,7 +810,7 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::set_team())] pub fn set_team( origin: OriginFor, - #[pallet::compact] class: T::ClassId, + class: T::ClassId, issuer: ::Source, admin: ::Source, freezer: ::Source, @@ -811,8 +847,8 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::approve_transfer())] pub fn approve_transfer( origin: OriginFor, - #[pallet::compact] class: T::ClassId, - #[pallet::compact] instance: T::InstanceId, + class: T::ClassId, + instance: T::InstanceId, delegate: ::Source, ) -> DispatchResult { let maybe_check: Option = T::ForceOrigin::try_origin(origin) @@ -863,8 +899,8 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::cancel_approval())] pub fn cancel_approval( origin: OriginFor, - #[pallet::compact] class: T::ClassId, - #[pallet::compact] instance: T::InstanceId, + class: T::ClassId, + instance: T::InstanceId, maybe_check_delegate: Option<::Source>, ) -> DispatchResult { let maybe_check: Option = T::ForceOrigin::try_origin(origin) @@ -915,7 +951,7 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::force_asset_status())] pub fn force_asset_status( origin: OriginFor, - #[pallet::compact] class: T::ClassId, + class: T::ClassId, owner: ::Source, issuer: ::Source, admin: ::Source, @@ -964,7 +1000,7 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::set_attribute())] pub fn set_attribute( origin: OriginFor, - #[pallet::compact] class: T::ClassId, + class: T::ClassId, maybe_instance: Option, key: BoundedVec, value: BoundedVec, @@ -1027,7 +1063,7 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::clear_attribute())] pub fn clear_attribute( origin: OriginFor, - #[pallet::compact] class: T::ClassId, + class: T::ClassId, maybe_instance: Option, key: BoundedVec, ) -> DispatchResult { @@ -1077,8 +1113,8 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::set_metadata())] pub fn set_metadata( origin: OriginFor, - #[pallet::compact] class: T::ClassId, - #[pallet::compact] instance: T::InstanceId, + class: T::ClassId, + instance: T::InstanceId, data: BoundedVec, is_frozen: bool, ) -> DispatchResult { @@ -1139,8 +1175,8 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::clear_metadata())] pub fn clear_metadata( origin: OriginFor, - #[pallet::compact] class: T::ClassId, - #[pallet::compact] instance: T::InstanceId, + class: T::ClassId, + instance: T::InstanceId, ) -> DispatchResult { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) @@ -1188,7 +1224,7 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::set_class_metadata())] pub fn set_class_metadata( origin: OriginFor, - #[pallet::compact] class: T::ClassId, + class: T::ClassId, data: BoundedVec, is_frozen: bool, ) -> DispatchResult { @@ -1242,10 +1278,7 @@ pub mod pallet { /// /// Weight: `O(1)` #[pallet::weight(T::WeightInfo::clear_class_metadata())] - pub fn clear_class_metadata( - origin: OriginFor, - #[pallet::compact] class: T::ClassId, - ) -> DispatchResult { + pub fn clear_class_metadata(origin: OriginFor, class: T::ClassId) -> DispatchResult { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; @@ -1265,5 +1298,40 @@ pub mod pallet { Ok(()) }) } + + /// Set (or reset) the acceptance of ownership for a particular account. + /// + /// Origin must be `Signed` and if `maybe_class` is `Some`, then the signer must have a + /// provider reference. + /// + /// - `maybe_class`: The identifier of the asset class whose ownership the signer is willing + /// to accept, or if `None`, an indication that the signer is willing to accept no + /// ownership transferal. + /// + /// Emits `OwnershipAcceptanceChanged`. + #[pallet::weight(T::WeightInfo::set_accept_ownership())] + pub fn set_accept_ownership( + origin: OriginFor, + maybe_class: Option, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let old = OwnershipAcceptance::::get(&who); + match (old.is_some(), maybe_class.is_some()) { + (false, true) => { + frame_system::Pallet::::inc_consumers(&who)?; + }, + (true, false) => { + frame_system::Pallet::::dec_consumers(&who); + }, + _ => {}, + } + if let Some(class) = maybe_class.as_ref() { + OwnershipAcceptance::::insert(&who, class); + } else { + OwnershipAcceptance::::remove(&who); + } + Self::deposit_event(Event::OwnershipAcceptanceChanged { who, maybe_class }); + Ok(()) + } } } diff --git a/frame/uniques/src/mock.rs b/frame/uniques/src/mock.rs index 2a94fcbee347a..265142443ef48 100644 --- a/frame/uniques/src/mock.rs +++ b/frame/uniques/src/mock.rs @@ -22,7 +22,7 @@ use crate as pallet_uniques; use frame_support::{ construct_runtime, - traits::{ConstU32, ConstU64}, + traits::{AsEnsureOriginWithArg, ConstU32, ConstU64}, }; use sp_core::H256; use sp_runtime::{ @@ -89,6 +89,7 @@ impl Config for Test { type ClassId = u32; type InstanceId = u32; type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; type ForceOrigin = frame_system::EnsureRoot; type ClassDeposit = ConstU64<2>; type InstanceDeposit = ConstU64<1>; @@ -99,6 +100,8 @@ impl Config for Test { type KeyLimit = ConstU32<50>; type ValueLimit = ConstU32<50>; type WeightInfo = (); + #[cfg(feature = "runtime-benchmarks")] + type Helper = (); } pub(crate) fn new_test_ext() -> sp_io::TestExternalities { diff --git a/frame/uniques/src/tests.rs b/frame/uniques/src/tests.rs index 12f39c78bfe3d..364073ad37cde 100644 --- a/frame/uniques/src/tests.rs +++ b/frame/uniques/src/tests.rs @@ -196,6 +196,9 @@ fn origin_guards_should_work() { new_test_ext().execute_with(|| { assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); + + Balances::make_free_balance_be(&2, 100); + assert_ok!(Uniques::set_accept_ownership(Origin::signed(2), Some(0))); assert_noop!( Uniques::transfer_ownership(Origin::signed(2), 0, 2), Error::::NoPermission @@ -218,13 +221,20 @@ fn transfer_owner_should_work() { Balances::make_free_balance_be(&3, 100); assert_ok!(Uniques::create(Origin::signed(1), 0, 1)); assert_eq!(classes(), vec![(1, 0)]); + assert_noop!( + Uniques::transfer_ownership(Origin::signed(1), 0, 2), + Error::::Unaccepted + ); + assert_ok!(Uniques::set_accept_ownership(Origin::signed(2), Some(0))); assert_ok!(Uniques::transfer_ownership(Origin::signed(1), 0, 2)); + assert_eq!(classes(), vec![(2, 0)]); assert_eq!(Balances::total_balance(&1), 98); assert_eq!(Balances::total_balance(&2), 102); assert_eq!(Balances::reserved_balance(&1), 0); assert_eq!(Balances::reserved_balance(&2), 2); + assert_ok!(Uniques::set_accept_ownership(Origin::signed(1), Some(0))); assert_noop!( Uniques::transfer_ownership(Origin::signed(1), 0, 1), Error::::NoPermission @@ -234,12 +244,20 @@ fn transfer_owner_should_work() { assert_ok!(Uniques::set_class_metadata(Origin::signed(2), 0, bvec![0u8; 20], false)); assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); assert_ok!(Uniques::set_metadata(Origin::signed(2), 0, 42, bvec![0u8; 20], false)); + assert_ok!(Uniques::set_accept_ownership(Origin::signed(3), Some(0))); assert_ok!(Uniques::transfer_ownership(Origin::signed(2), 0, 3)); assert_eq!(classes(), vec![(3, 0)]); assert_eq!(Balances::total_balance(&2), 57); assert_eq!(Balances::total_balance(&3), 145); assert_eq!(Balances::reserved_balance(&2), 0); assert_eq!(Balances::reserved_balance(&3), 45); + + // 2's acceptence from before is reset when it became owner, so it cannot be transfered + // without a fresh acceptance. + assert_noop!( + Uniques::transfer_ownership(Origin::signed(3), 0, 2), + Error::::Unaccepted + ); }); } diff --git a/frame/uniques/src/weights.rs b/frame/uniques/src/weights.rs index 1df8fe0ff6650..eb9067b7133a0 100644 --- a/frame/uniques/src/weights.rs +++ b/frame/uniques/src/weights.rs @@ -68,6 +68,7 @@ pub trait WeightInfo { fn clear_class_metadata() -> Weight; fn approve_transfer() -> Weight; fn cancel_approval() -> Weight; + fn set_accept_ownership() -> Weight; } /// Weights for pallet_uniques using the Substrate node and recommended hardware. @@ -249,6 +250,13 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } + // Storage: Uniques Class (r:1 w:0) + // Storage: Uniques Asset (r:1 w:1) + fn set_accept_ownership() -> Weight { + (19_417_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } } // For backwards compatibility and tests @@ -429,4 +437,11 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } + // Storage: Uniques Class (r:1 w:0) + // Storage: Uniques Asset (r:1 w:1) + fn set_accept_ownership() -> Weight { + (19_417_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } } From 6ed39dc29cbaa4ba0c0d09fb14dcd5a57c55f29a Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Tue, 8 Mar 2022 12:31:20 +0000 Subject: [PATCH 007/484] election provider support: Update some test only types (#10983) --- frame/election-provider-support/src/lib.rs | 35 ++++------------------ 1 file changed, 6 insertions(+), 29 deletions(-) diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index 26efe5107b670..ca22e3093855c 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -262,33 +262,6 @@ pub trait ElectionDataProvider { fn clear() {} } -/// An election data provider that should only be used for testing. -#[cfg(feature = "std")] -pub struct TestDataProvider(sp_std::marker::PhantomData); - -#[cfg(feature = "std")] -impl ElectionDataProvider for TestDataProvider<(AccountId, BlockNumber)> { - type AccountId = AccountId; - type BlockNumber = BlockNumber; - type MaxVotesPerVoter = (); - - fn targets(_maybe_max_len: Option) -> data_provider::Result> { - Ok(Default::default()) - } - - fn voters(_maybe_max_len: Option) -> data_provider::Result>> { - Ok(Default::default()) - } - - fn desired_targets() -> data_provider::Result { - Ok(Default::default()) - } - - fn next_election_prediction(now: BlockNumber) -> BlockNumber { - now - } -} - /// Something that can compute the result of an election and pass it back to the caller. /// /// This trait only provides an interface to _request_ an election, i.e. @@ -340,11 +313,15 @@ pub trait InstantElectionProvider: ElectionProvider { pub struct NoElection(sp_std::marker::PhantomData); #[cfg(feature = "std")] -impl ElectionProvider for NoElection<(AccountId, BlockNumber)> { +impl ElectionProvider + for NoElection<(AccountId, BlockNumber, DataProvider)> +where + DataProvider: ElectionDataProvider, +{ type AccountId = AccountId; type BlockNumber = BlockNumber; type Error = &'static str; - type DataProvider = TestDataProvider<(AccountId, BlockNumber)>; + type DataProvider = DataProvider; fn elect() -> Result, Self::Error> { Err(" cannot do anything.") From 08b15976a1702ac95de907c159c3a4c96779fe7c Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Tue, 8 Mar 2022 08:48:30 -0400 Subject: [PATCH 008/484] Feedback from @XLC for Referenda Pallet (#10991) * feedback from @xlc * english * fmt Signed-off-by: Oliver Tale-Yazdi Co-authored-by: Oliver Tale-Yazdi --- frame/referenda/src/benchmarking.rs | 4 +-- frame/referenda/src/lib.rs | 22 +++++++--------- frame/referenda/src/mock.rs | 4 +-- frame/referenda/src/tests.rs | 39 ++++++++++++++++------------ frame/referenda/src/types.rs | 22 +--------------- frame/support/src/traits/schedule.rs | 13 ++++++++-- 6 files changed, 47 insertions(+), 57 deletions(-) diff --git a/frame/referenda/src/benchmarking.rs b/frame/referenda/src/benchmarking.rs index 76a8173f16c9a..08612e0614aeb 100644 --- a/frame/referenda/src/benchmarking.rs +++ b/frame/referenda/src/benchmarking.rs @@ -48,7 +48,7 @@ fn create_referendum() -> (T::AccountId, ReferendumIndex) { RawOrigin::Signed(caller.clone()).into(), RawOrigin::Root.into(), T::Hashing::hash_of(&0), - AtOrAfter::After(0u32.into()) + DispatchTime::After(0u32.into()) )); let index = ReferendumCount::::get() - 1; (caller, index) @@ -182,7 +182,7 @@ benchmarks! { RawOrigin::Signed(caller), RawOrigin::Root.into(), T::Hashing::hash_of(&0), - AtOrAfter::After(0u32.into()) + DispatchTime::After(0u32.into()) ) verify { let index = ReferendumCount::::get().checked_sub(1).unwrap(); assert_matches!(ReferendumInfoFor::::get(index), Some(ReferendumInfo::Ongoing(_))); diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index 2d4d29b0bc13f..fb19d2b9ed248 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -68,8 +68,8 @@ use frame_support::{ v2::{Anon as ScheduleAnon, Named as ScheduleNamed}, DispatchTime, MaybeHashed, }, - Currency, Get, LockIdentifier, LockableCurrency, OnUnbalanced, OriginTrait, PollStatus, - Polling, ReservableCurrency, VoteTally, + Currency, Get, LockIdentifier, OnUnbalanced, OriginTrait, PollStatus, Polling, + ReservableCurrency, VoteTally, }, BoundedVec, }; @@ -86,7 +86,7 @@ pub mod weights; use branch::{BeginDecidingBranch, OneFewerDecidingBranch, ServiceBranch}; pub use pallet::*; pub use types::{ - AtOrAfter, BalanceOf, CallOf, Curve, DecidingStatus, DecidingStatusOf, Deposit, InsertSorted, + BalanceOf, CallOf, Curve, DecidingStatus, DecidingStatusOf, Deposit, InsertSorted, NegativeImbalanceOf, PalletsOriginOf, ReferendumIndex, ReferendumInfo, ReferendumInfoOf, ReferendumStatus, ReferendumStatusOf, ScheduleAddressOf, TallyOf, TrackIdOf, TrackInfo, TrackInfoOf, TracksInfo, VotesOf, @@ -126,9 +126,7 @@ pub mod pallet { type Scheduler: ScheduleAnon, PalletsOriginOf, Hash = Self::Hash> + ScheduleNamed, PalletsOriginOf, Hash = Self::Hash>; /// Currency type for this pallet. - type Currency: ReservableCurrency - + LockableCurrency; - + type Currency: ReservableCurrency; // Origins and unbalances. /// Origin from which any vote may be cancelled. type CancelOrigin: EnsureOrigin; @@ -175,8 +173,6 @@ pub mod pallet { pub type ReferendumCount = StorageValue<_, ReferendumIndex, ValueQuery>; /// Information concerning any given referendum. - /// - /// TWOX-NOTE: SAFE as indexes are not under an attacker’s control. #[pallet::storage] pub type ReferendumInfoFor = StorageMap<_, Blake2_128Concat, ReferendumIndex, ReferendumInfoOf>; @@ -301,7 +297,7 @@ pub mod pallet { /// Referendum is not ongoing. NotOngoing, /// Referendum's decision deposit is already paid. - HaveDeposit, + HasDeposit, /// The track identifier given was invalid. BadTrack, /// There are already a full complement of referendums in progress for this track. @@ -338,7 +334,7 @@ pub mod pallet { origin: OriginFor, proposal_origin: PalletsOriginOf, proposal_hash: T::Hash, - enactment_moment: AtOrAfter, + enactment_moment: DispatchTime, ) -> DispatchResult { let who = ensure_signed(origin)?; @@ -385,7 +381,7 @@ pub mod pallet { ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; let mut status = Self::ensure_ongoing(index)?; - ensure!(status.decision_deposit.is_none(), Error::::HaveDeposit); + ensure!(status.decision_deposit.is_none(), Error::::HasDeposit); let track = Self::track(status.track).ok_or(Error::::NoTrack)?; status.decision_deposit = Some(Self::take_deposit(who.clone(), track.decision_deposit)?); @@ -598,7 +594,7 @@ impl Polling for Pallet { track: class, origin: frame_support::dispatch::RawOrigin::Root.into(), proposal_hash: ::hash_of(&index), - enactment: AtOrAfter::After(Zero::zero()), + enactment: DispatchTime::After(Zero::zero()), submitted: now, submission_deposit: Deposit { who: dummy_account_id, amount: Zero::zero() }, decision_deposit: None, @@ -651,7 +647,7 @@ impl Pallet { fn schedule_enactment( index: ReferendumIndex, track: &TrackInfoOf, - desired: AtOrAfter, + desired: DispatchTime, origin: PalletsOriginOf, call_hash: T::Hash, ) { diff --git a/frame/referenda/src/mock.rs b/frame/referenda/src/mock.rs index 063b124f2b71f..fdd14fdadf04d 100644 --- a/frame/referenda/src/mock.rs +++ b/frame/referenda/src/mock.rs @@ -306,7 +306,7 @@ pub fn propose_set_balance(who: u64, value: u64, delay: u64) -> DispatchResult { Origin::signed(who), frame_system::RawOrigin::Root.into(), set_balance_proposal_hash(value), - AtOrAfter::After(delay), + DispatchTime::After(delay), ) } @@ -434,7 +434,7 @@ impl RefState { Origin::signed(1), frame_support::dispatch::RawOrigin::Root.into(), set_balance_proposal_hash(1), - AtOrAfter::At(10), + DispatchTime::At(10), )); assert_ok!(Referenda::place_decision_deposit(Origin::signed(2), 0)); if matches!(self, RefState::Confirming { immediate: true }) { diff --git a/frame/referenda/src/tests.rs b/frame/referenda/src/tests.rs index cea071ced12fe..96edd4ce879ce 100644 --- a/frame/referenda/src/tests.rs +++ b/frame/referenda/src/tests.rs @@ -47,7 +47,7 @@ fn basic_happy_path_works() { Origin::signed(1), RawOrigin::Root.into(), set_balance_proposal_hash(1), - AtOrAfter::At(10), + DispatchTime::At(10), )); assert_eq!(Balances::reserved_balance(&1), 2); assert_eq!(ReferendumCount::::get(), 1); @@ -178,7 +178,7 @@ fn queueing_works() { Origin::signed(5), RawOrigin::Root.into(), set_balance_proposal_hash(0), - AtOrAfter::After(0), + DispatchTime::After(0), )); assert_ok!(Referenda::place_decision_deposit(Origin::signed(5), 0)); @@ -190,7 +190,7 @@ fn queueing_works() { Origin::signed(i), RawOrigin::Root.into(), set_balance_proposal_hash(i), - AtOrAfter::After(0), + DispatchTime::After(0), )); assert_ok!(Referenda::place_decision_deposit(Origin::signed(i), i as u32)); // TODO: decision deposit after some initial votes with a non-highest voted coming @@ -275,7 +275,7 @@ fn auto_timeout_should_happen_with_nothing_but_submit() { Origin::signed(1), RawOrigin::Root.into(), set_balance_proposal_hash(1), - AtOrAfter::At(20), + DispatchTime::At(20), )); run_to(20); assert_matches!(ReferendumInfoFor::::get(0), Some(ReferendumInfo::Ongoing(..))); @@ -295,13 +295,13 @@ fn tracks_are_distinguished() { Origin::signed(1), RawOrigin::Root.into(), set_balance_proposal_hash(1), - AtOrAfter::At(10), + DispatchTime::At(10), )); assert_ok!(Referenda::submit( Origin::signed(2), RawOrigin::None.into(), set_balance_proposal_hash(2), - AtOrAfter::At(20), + DispatchTime::At(20), )); assert_ok!(Referenda::place_decision_deposit(Origin::signed(3), 0)); @@ -318,7 +318,7 @@ fn tracks_are_distinguished() { track: 0, origin: OriginCaller::system(RawOrigin::Root), proposal_hash: set_balance_proposal_hash(1), - enactment: AtOrAfter::At(10), + enactment: DispatchTime::At(10), submitted: 1, submission_deposit: Deposit { who: 1, amount: 2 }, decision_deposit: Some(Deposit { who: 3, amount: 10 }), @@ -334,7 +334,7 @@ fn tracks_are_distinguished() { track: 1, origin: OriginCaller::system(RawOrigin::None), proposal_hash: set_balance_proposal_hash(2), - enactment: AtOrAfter::At(20), + enactment: DispatchTime::At(20), submitted: 1, submission_deposit: Deposit { who: 2, amount: 2 }, decision_deposit: Some(Deposit { who: 4, amount: 1 }), @@ -355,13 +355,18 @@ fn submit_errors_work() { let h = set_balance_proposal_hash(1); // No track for Signed origins. assert_noop!( - Referenda::submit(Origin::signed(1), RawOrigin::Signed(2).into(), h, AtOrAfter::At(10),), + Referenda::submit( + Origin::signed(1), + RawOrigin::Signed(2).into(), + h, + DispatchTime::At(10), + ), Error::::NoTrack ); // No funds for deposit assert_noop!( - Referenda::submit(Origin::signed(10), RawOrigin::Root.into(), h, AtOrAfter::At(10),), + Referenda::submit(Origin::signed(10), RawOrigin::Root.into(), h, DispatchTime::At(10),), BalancesError::::InsufficientBalance ); }); @@ -378,13 +383,13 @@ fn decision_deposit_errors_work() { Origin::signed(1), RawOrigin::Root.into(), h, - AtOrAfter::At(10), + DispatchTime::At(10), )); let e = BalancesError::::InsufficientBalance; assert_noop!(Referenda::place_decision_deposit(Origin::signed(10), 0), e); assert_ok!(Referenda::place_decision_deposit(Origin::signed(2), 0)); - let e = Error::::HaveDeposit; + let e = Error::::HasDeposit; assert_noop!(Referenda::place_decision_deposit(Origin::signed(2), 0), e); }); } @@ -400,7 +405,7 @@ fn refund_deposit_works() { Origin::signed(1), RawOrigin::Root.into(), h, - AtOrAfter::At(10), + DispatchTime::At(10), )); let e = Error::::NoDeposit; assert_noop!(Referenda::refund_decision_deposit(Origin::signed(2), 0), e); @@ -422,7 +427,7 @@ fn cancel_works() { Origin::signed(1), RawOrigin::Root.into(), h, - AtOrAfter::At(10), + DispatchTime::At(10), )); assert_ok!(Referenda::place_decision_deposit(Origin::signed(2), 0)); @@ -441,7 +446,7 @@ fn cancel_errors_works() { Origin::signed(1), RawOrigin::Root.into(), h, - AtOrAfter::At(10), + DispatchTime::At(10), )); assert_ok!(Referenda::place_decision_deposit(Origin::signed(2), 0)); assert_noop!(Referenda::cancel(Origin::signed(1), 0), BadOrigin); @@ -459,7 +464,7 @@ fn kill_works() { Origin::signed(1), RawOrigin::Root.into(), h, - AtOrAfter::At(10), + DispatchTime::At(10), )); assert_ok!(Referenda::place_decision_deposit(Origin::signed(2), 0)); @@ -479,7 +484,7 @@ fn kill_errors_works() { Origin::signed(1), RawOrigin::Root.into(), h, - AtOrAfter::At(10), + DispatchTime::At(10), )); assert_ok!(Referenda::place_decision_deposit(Origin::signed(2), 0)); assert_noop!(Referenda::kill(Origin::signed(4), 0), BadOrigin); diff --git a/frame/referenda/src/types.rs b/frame/referenda/src/types.rs index 367fa2a4ba45b..8ea9fc3faf3d0 100644 --- a/frame/referenda/src/types.rs +++ b/frame/referenda/src/types.rs @@ -185,26 +185,6 @@ pub trait TracksInfo { } } -/// Indication of either a specific moment or a delay from a implicitly defined moment. -#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] -pub enum AtOrAfter { - /// Indiciates that the event should occur at the moment given. - At(Moment), - /// Indiciates that the event should occur some period of time (defined by the parameter) after - /// a prior event. The prior event is defined by the context, but for the purposes of - /// referendum proposals, the "prior event" is the passing of the referendum. - After(Moment), -} - -impl AtOrAfter { - pub fn evaluate(&self, since: Moment) -> Moment { - match &self { - Self::At(m) => *m, - Self::After(m) => m.saturating_add(since), - } - } -} - /// Info regarding an ongoing referendum. #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct ReferendumStatus< @@ -224,7 +204,7 @@ pub struct ReferendumStatus< /// The hash of the proposal up for referendum. pub(crate) proposal_hash: Hash, /// The time the proposal should be scheduled for enactment. - pub(crate) enactment: AtOrAfter, + pub(crate) enactment: DispatchTime, /// The time of submission. Once `UndecidingTimeout` passes, it may be closed by anyone if it /// `deciding` is `None`. pub(crate) submitted: Moment, diff --git a/frame/support/src/traits/schedule.rs b/frame/support/src/traits/schedule.rs index 3b8e6da3e2ef3..c2d0d4bc3b81f 100644 --- a/frame/support/src/traits/schedule.rs +++ b/frame/support/src/traits/schedule.rs @@ -19,7 +19,7 @@ use codec::{Codec, Decode, Encode, EncodeLike, MaxEncodedLen}; use scale_info::TypeInfo; -use sp_runtime::{DispatchError, RuntimeDebug}; +use sp_runtime::{traits::Saturating, DispatchError, RuntimeDebug}; use sp_std::{fmt::Debug, prelude::*, result::Result}; /// Information relating to the period of a scheduled task. First item is the length of the @@ -32,7 +32,7 @@ pub type Period = (BlockNumber, u32); pub type Priority = u8; /// The dispatch time of a scheduled task. -#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub enum DispatchTime { /// At specified block. At(BlockNumber), @@ -40,6 +40,15 @@ pub enum DispatchTime { After(BlockNumber), } +impl DispatchTime { + pub fn evaluate(&self, since: BlockNumber) -> BlockNumber { + match &self { + Self::At(m) => *m, + Self::After(m) => m.saturating_add(since), + } + } +} + /// The highest priority. We invert the value so that normal sorting will place the highest /// priority at the beginning of the list. pub const HIGHEST_PRIORITY: Priority = 0; From 0fb7ce3a16962ba6b8f720ddd60486c3018027cc Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Wed, 9 Mar 2022 16:28:28 +0000 Subject: [PATCH 009/484] Make bags-list generic over node value and instantiable (#10997) * make instantiable * update * cargo fmt * Clean up * bags-list: Make it generic over node value * Respond to some feedback * Apply suggestions from code review Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Add back default impl for weight update worst case * Update to Score in more places' * Use VoteWeight, not u64 to reduce test diff * FMT * FullCodec implies Codec * formatting * Fixup bags list remote test Co-authored-by: doordashcon Co-authored-by: Doordashcon <90750465+Doordashcon@users.noreply.github.com> Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- bin/node/runtime/src/lib.rs | 3 +- frame/bags-list/remote-tests/src/lib.rs | 23 +- frame/bags-list/src/benchmarks.rs | 47 +++-- frame/bags-list/src/lib.rs | 146 +++++++------ frame/bags-list/src/list/mod.rs | 235 +++++++++++---------- frame/bags-list/src/list/tests.rs | 143 ++++++++++--- frame/bags-list/src/mock.rs | 15 +- frame/bags-list/src/tests.rs | 38 ++-- frame/election-provider-support/Cargo.toml | 2 +- frame/election-provider-support/src/lib.rs | 28 +-- frame/staking/src/benchmarking.rs | 2 +- frame/staking/src/mock.rs | 5 +- frame/staking/src/pallet/impls.rs | 19 +- frame/staking/src/pallet/mod.rs | 5 +- 14 files changed, 428 insertions(+), 283 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index e2903c0b314da..8c7a20af15683 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -690,9 +690,10 @@ parameter_types! { impl pallet_bags_list::Config for Runtime { type Event = Event; - type VoteWeightProvider = Staking; + type ScoreProvider = Staking; type WeightInfo = pallet_bags_list::weights::SubstrateWeight; type BagThresholds = BagThresholds; + type Score = sp_npos_elections::VoteWeight; } parameter_types! { diff --git a/frame/bags-list/remote-tests/src/lib.rs b/frame/bags-list/remote-tests/src/lib.rs index 9a88ff24f2f3b..83c322f93134e 100644 --- a/frame/bags-list/remote-tests/src/lib.rs +++ b/frame/bags-list/remote-tests/src/lib.rs @@ -17,6 +17,7 @@ //! Utilities for remote-testing pallet-bags-list. +use frame_election_provider_support::ScoreProvider; use sp_std::prelude::*; /// A common log target to use. @@ -55,8 +56,12 @@ pub fn display_and_check_bags(currency_unit: u64, currency_na let mut rebaggable = 0; let mut active_bags = 0; for vote_weight_thresh in ::BagThresholds::get() { + let vote_weight_thresh_u64: u64 = (*vote_weight_thresh) + .try_into() + .map_err(|_| "runtime must configure score to at most u64 to use this test") + .unwrap(); // threshold in terms of UNITS (e.g. KSM, DOT etc) - let vote_weight_thresh_as_unit = *vote_weight_thresh as f64 / currency_unit as f64; + let vote_weight_thresh_as_unit = vote_weight_thresh_u64 as f64 / currency_unit as f64; let pretty_thresh = format!("Threshold: {}. {}", vote_weight_thresh_as_unit, currency_name); let bag = match pallet_bags_list::Pallet::::list_bags_get(*vote_weight_thresh) { @@ -70,9 +75,13 @@ pub fn display_and_check_bags(currency_unit: u64, currency_na active_bags += 1; for id in bag.std_iter().map(|node| node.std_id().clone()) { - let vote_weight = pallet_staking::Pallet::::weight_of(&id); + let vote_weight = ::ScoreProvider::score(&id); + let vote_weight_thresh_u64: u64 = (*vote_weight_thresh) + .try_into() + .map_err(|_| "runtime must configure score to at most u64 to use this test") + .unwrap(); let vote_weight_as_balance: pallet_staking::BalanceOf = - vote_weight.try_into().map_err(|_| "can't convert").unwrap(); + vote_weight_thresh_u64.try_into().map_err(|_| "can't convert").unwrap(); if vote_weight_as_balance < min_nominator_bond { log::trace!( @@ -87,13 +96,17 @@ pub fn display_and_check_bags(currency_unit: u64, currency_na pallet_bags_list::Node::::get(&id).expect("node in bag must exist."); if node.is_misplaced(vote_weight) { rebaggable += 1; + let notional_bag = pallet_bags_list::notional_bag_for::(vote_weight); + let notional_bag_as_u64: u64 = notional_bag + .try_into() + .map_err(|_| "runtime must configure score to at most u64 to use this test") + .unwrap(); log::trace!( target: LOG_TARGET, "Account {:?} can be rebagged from {:?} to {:?}", id, vote_weight_thresh_as_unit, - pallet_bags_list::notional_bag_for::(vote_weight) as f64 / - currency_unit as f64 + notional_bag_as_u64 as f64 / currency_unit as f64 ); } } diff --git a/frame/bags-list/src/benchmarks.rs b/frame/bags-list/src/benchmarks.rs index cc575d7d1efff..b94a97093d001 100644 --- a/frame/bags-list/src/benchmarks.rs +++ b/frame/bags-list/src/benchmarks.rs @@ -20,9 +20,10 @@ use super::*; use crate::list::List; use frame_benchmarking::{account, whitelist_account, whitelisted_caller}; -use frame_election_provider_support::VoteWeightProvider; +use frame_election_provider_support::ScoreProvider; use frame_support::{assert_ok, traits::Get}; use frame_system::RawOrigin as SystemOrigin; +use sp_runtime::traits::One; frame_benchmarking::benchmarks! { rebag_non_terminal { @@ -36,7 +37,7 @@ frame_benchmarking::benchmarks! { // clear any pre-existing storage. // NOTE: safe to call outside block production - List::::unsafe_clear(); + List::::unsafe_clear(); // define our origin and destination thresholds. let origin_bag_thresh = T::BagThresholds::get()[0]; @@ -44,21 +45,21 @@ frame_benchmarking::benchmarks! { // seed items in the origin bag. let origin_head: T::AccountId = account("origin_head", 0, 0); - assert_ok!(List::::insert(origin_head.clone(), origin_bag_thresh)); + assert_ok!(List::::insert(origin_head.clone(), origin_bag_thresh)); let origin_middle: T::AccountId = account("origin_middle", 0, 0); // the node we rebag (_R_) - assert_ok!(List::::insert(origin_middle.clone(), origin_bag_thresh)); + assert_ok!(List::::insert(origin_middle.clone(), origin_bag_thresh)); let origin_tail: T::AccountId = account("origin_tail", 0, 0); - assert_ok!(List::::insert(origin_tail.clone(), origin_bag_thresh)); + assert_ok!(List::::insert(origin_tail.clone(), origin_bag_thresh)); // seed items in the destination bag. let dest_head: T::AccountId = account("dest_head", 0, 0); - assert_ok!(List::::insert(dest_head.clone(), dest_bag_thresh)); + assert_ok!(List::::insert(dest_head.clone(), dest_bag_thresh)); // the bags are in the expected state after initial setup. assert_eq!( - List::::get_bags(), + List::::get_bags(), vec![ (origin_bag_thresh, vec![origin_head.clone(), origin_middle.clone(), origin_tail.clone()]), (dest_bag_thresh, vec![dest_head.clone()]) @@ -67,12 +68,12 @@ frame_benchmarking::benchmarks! { let caller = whitelisted_caller(); // update the weight of `origin_middle` to guarantee it will be rebagged into the destination. - T::VoteWeightProvider::set_vote_weight_of(&origin_middle, dest_bag_thresh); + T::ScoreProvider::set_score_of(&origin_middle, dest_bag_thresh); }: rebag(SystemOrigin::Signed(caller), origin_middle.clone()) verify { // check the bags have updated as expected. assert_eq!( - List::::get_bags(), + List::::get_bags(), vec![ ( origin_bag_thresh, @@ -104,18 +105,18 @@ frame_benchmarking::benchmarks! { // seed items in the origin bag. let origin_head: T::AccountId = account("origin_head", 0, 0); - assert_ok!(List::::insert(origin_head.clone(), origin_bag_thresh)); + assert_ok!(List::::insert(origin_head.clone(), origin_bag_thresh)); let origin_tail: T::AccountId = account("origin_tail", 0, 0); // the node we rebag (_R_) - assert_ok!(List::::insert(origin_tail.clone(), origin_bag_thresh)); + assert_ok!(List::::insert(origin_tail.clone(), origin_bag_thresh)); // seed items in the destination bag. let dest_head: T::AccountId = account("dest_head", 0, 0); - assert_ok!(List::::insert(dest_head.clone(), dest_bag_thresh)); + assert_ok!(List::::insert(dest_head.clone(), dest_bag_thresh)); // the bags are in the expected state after initial setup. assert_eq!( - List::::get_bags(), + List::::get_bags(), vec![ (origin_bag_thresh, vec![origin_head.clone(), origin_tail.clone()]), (dest_bag_thresh, vec![dest_head.clone()]) @@ -124,12 +125,12 @@ frame_benchmarking::benchmarks! { let caller = whitelisted_caller(); // update the weight of `origin_tail` to guarantee it will be rebagged into the destination. - T::VoteWeightProvider::set_vote_weight_of(&origin_tail, dest_bag_thresh); + T::ScoreProvider::set_score_of(&origin_tail, dest_bag_thresh); }: rebag(SystemOrigin::Signed(caller), origin_tail.clone()) verify { // check the bags have updated as expected. assert_eq!( - List::::get_bags(), + List::::get_bags(), vec![ (origin_bag_thresh, vec![origin_head.clone()]), (dest_bag_thresh, vec![dest_head.clone(), origin_tail.clone()]) @@ -147,22 +148,22 @@ frame_benchmarking::benchmarks! { // insert the nodes in order let lighter: T::AccountId = account("lighter", 0, 0); - assert_ok!(List::::insert(lighter.clone(), bag_thresh)); + assert_ok!(List::::insert(lighter.clone(), bag_thresh)); let heavier_prev: T::AccountId = account("heavier_prev", 0, 0); - assert_ok!(List::::insert(heavier_prev.clone(), bag_thresh)); + assert_ok!(List::::insert(heavier_prev.clone(), bag_thresh)); let heavier: T::AccountId = account("heavier", 0, 0); - assert_ok!(List::::insert(heavier.clone(), bag_thresh)); + assert_ok!(List::::insert(heavier.clone(), bag_thresh)); let heavier_next: T::AccountId = account("heavier_next", 0, 0); - assert_ok!(List::::insert(heavier_next.clone(), bag_thresh)); + assert_ok!(List::::insert(heavier_next.clone(), bag_thresh)); - T::VoteWeightProvider::set_vote_weight_of(&lighter, bag_thresh - 1); - T::VoteWeightProvider::set_vote_weight_of(&heavier, bag_thresh); + T::ScoreProvider::set_score_of(&lighter, bag_thresh - One::one()); + T::ScoreProvider::set_score_of(&heavier, bag_thresh); assert_eq!( - List::::iter().map(|n| n.id().clone()).collect::>(), + List::::iter().map(|n| n.id().clone()).collect::>(), vec![lighter.clone(), heavier_prev.clone(), heavier.clone(), heavier_next.clone()] ); @@ -170,7 +171,7 @@ frame_benchmarking::benchmarks! { }: _(SystemOrigin::Signed(heavier.clone()), lighter.clone()) verify { assert_eq!( - List::::iter().map(|n| n.id().clone()).collect::>(), + List::::iter().map(|n| n.id().clone()).collect::>(), vec![heavier, lighter, heavier_prev, heavier_next] ) } diff --git a/frame/bags-list/src/lib.rs b/frame/bags-list/src/lib.rs index 89c54db87023f..c502245409fdb 100644 --- a/frame/bags-list/src/lib.rs +++ b/frame/bags-list/src/lib.rs @@ -17,13 +17,14 @@ //! # Bags-List Pallet //! -//! A semi-sorted list, where items hold an `AccountId` based on some `VoteWeight`. The `AccountId` -//! (`id` for short) might be synonym to a `voter` or `nominator` in some context, and `VoteWeight` -//! signifies the chance of each id being included in the final [`SortedListProvider::iter`]. +//! A semi-sorted list, where items hold an `AccountId` based on some `Score`. The +//! `AccountId` (`id` for short) might be synonym to a `voter` or `nominator` in some context, and +//! `Score` signifies the chance of each id being included in the final +//! [`SortedListProvider::iter`]. //! //! It implements [`frame_election_provider_support::SortedListProvider`] to provide a semi-sorted //! list of accounts to another pallet. It needs some other pallet to give it some information about -//! the weights of accounts via [`frame_election_provider_support::VoteWeightProvider`]. +//! the weights of accounts via [`frame_election_provider_support::ScoreProvider`]. //! //! This pallet is not configurable at genesis. Whoever uses it should call appropriate functions of //! the `SortedListProvider` (e.g. `on_insert`, or `unsafe_regenerate`) at their genesis. @@ -33,12 +34,12 @@ //! The data structure exposed by this pallet aims to be optimized for: //! //! - insertions and removals. -//! - iteration over the top* N items by weight, where the precise ordering of items doesn't +//! - iteration over the top* N items by score, where the precise ordering of items doesn't //! particularly matter. //! //! # Details //! -//! - items are kept in bags, which are delineated by their range of weight (See +//! - items are kept in bags, which are delineated by their range of score (See //! [`Config::BagThresholds`]). //! - for iteration, bags are chained together from highest to lowest and elements within the bag //! are iterated from head to tail. @@ -46,14 +47,16 @@ //! it will worsen its position in list iteration; this reduces incentives for some types of spam //! that involve consistently removing and inserting for better position. Further, ordering //! granularity is thus dictated by range between each bag threshold. -//! - if an item's weight changes to a value no longer within the range of its current bag the -//! item's position will need to be updated by an external actor with rebag (update), or removal -//! and insertion. +//! - if an item's score changes to a value no longer within the range of its current bag the item's +//! position will need to be updated by an external actor with rebag (update), or removal and +//! insertion. #![cfg_attr(not(feature = "std"), no_std)] -use frame_election_provider_support::{SortedListProvider, VoteWeight, VoteWeightProvider}; +use codec::FullCodec; +use frame_election_provider_support::{ScoreProvider, SortedListProvider}; use frame_system::ensure_signed; +use sp_runtime::traits::{AtLeast32BitUnsigned, Bounded}; use sp_std::prelude::*; #[cfg(any(feature = "runtime-benchmarks", test))] @@ -92,38 +95,38 @@ pub mod pallet { #[pallet::pallet] #[pallet::generate_store(pub(crate) trait Store)] - pub struct Pallet(_); + pub struct Pallet(_); #[pallet::config] - pub trait Config: frame_system::Config { + pub trait Config: frame_system::Config { /// The overarching event type. - type Event: From> + IsType<::Event>; + type Event: From> + IsType<::Event>; /// Weight information for extrinsics in this pallet. type WeightInfo: weights::WeightInfo; - /// Something that provides the weights of ids. - type VoteWeightProvider: VoteWeightProvider; + /// Something that provides the scores of ids. + type ScoreProvider: ScoreProvider; /// The list of thresholds separating the various bags. /// - /// Ids are separated into unsorted bags according to their vote weight. This specifies the - /// thresholds separating the bags. An id's bag is the largest bag for which the id's weight + /// Ids are separated into unsorted bags according to their score. This specifies the + /// thresholds separating the bags. An id's bag is the largest bag for which the id's score /// is less than or equal to its upper threshold. /// /// When ids are iterated, higher bags are iterated completely before lower bags. This means - /// that iteration is _semi-sorted_: ids of higher weight tend to come before ids of lower - /// weight, but peer ids within a particular bag are sorted in insertion order. + /// that iteration is _semi-sorted_: ids of higher score tend to come before ids of lower + /// score, but peer ids within a particular bag are sorted in insertion order. /// /// # Expressing the constant /// /// This constant must be sorted in strictly increasing order. Duplicate items are not /// permitted. /// - /// There is an implied upper limit of `VoteWeight::MAX`; that value does not need to be + /// There is an implied upper limit of `Score::MAX`; that value does not need to be /// specified within the bag. For any two threshold lists, if one ends with - /// `VoteWeight::MAX`, the other one does not, and they are otherwise equal, the two lists - /// will behave identically. + /// `Score::MAX`, the other one does not, and they are otherwise equal, the two + /// lists will behave identically. /// /// # Calculation /// @@ -141,52 +144,68 @@ pub mod pallet { /// the procedure given above, then the constant ratio is equal to 2. /// - If `BagThresholds::get().len() == 200`, and the thresholds are determined according to /// the procedure given above, then the constant ratio is approximately equal to 1.248. - /// - If the threshold list begins `[1, 2, 3, ...]`, then an id with weight 0 or 1 will fall - /// into bag 0, an id with weight 2 will fall into bag 1, etc. + /// - If the threshold list begins `[1, 2, 3, ...]`, then an id with score 0 or 1 will fall + /// into bag 0, an id with score 2 will fall into bag 1, etc. /// /// # Migration /// /// In the event that this list ever changes, a copy of the old bags list must be retained. /// With that `List::migrate` can be called, which will perform the appropriate migration. #[pallet::constant] - type BagThresholds: Get<&'static [VoteWeight]>; + type BagThresholds: Get<&'static [Self::Score]>; + + /// The type used to dictate a node position relative to other nodes. + type Score: Clone + + Default + + PartialEq + + Eq + + Ord + + PartialOrd + + sp_std::fmt::Debug + + Copy + + AtLeast32BitUnsigned + + Bounded + + TypeInfo + + FullCodec + + MaxEncodedLen; } /// A single node, within some bag. /// /// Nodes store links forward and back within their respective bags. #[pallet::storage] - pub(crate) type ListNodes = - CountedStorageMap<_, Twox64Concat, T::AccountId, list::Node>; + pub(crate) type ListNodes, I: 'static = ()> = + CountedStorageMap<_, Twox64Concat, T::AccountId, list::Node>; /// A bag stored in storage. /// /// Stores a `Bag` struct, which stores head and tail pointers to itself. #[pallet::storage] - pub(crate) type ListBags = StorageMap<_, Twox64Concat, VoteWeight, list::Bag>; + pub(crate) type ListBags, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::Score, list::Bag>; #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] - pub enum Event { + pub enum Event, I: 'static = ()> { /// Moved an account from one bag to another. - Rebagged { who: T::AccountId, from: VoteWeight, to: VoteWeight }, + Rebagged { who: T::AccountId, from: T::Score, to: T::Score }, } #[pallet::error] #[cfg_attr(test, derive(PartialEq))] - pub enum Error { + pub enum Error { /// Attempted to place node in front of a node in another bag. NotInSameBag, /// Id not found in list. IdNotFound, - /// An Id does not have a greater vote weight than another Id. + /// An Id does not have a greater score than another Id. NotHeavier, } #[pallet::call] - impl Pallet { + impl, I: 'static> Pallet { /// Declare that some `dislocated` account has, through rewards or penalties, sufficiently - /// changed its weight that it should properly fall into a different bag than its current + /// changed its score that it should properly fall into a different bag than its current /// one. /// /// Anyone can call this function about any potentially dislocated account. @@ -196,8 +215,8 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::rebag_non_terminal().max(T::WeightInfo::rebag_terminal()))] pub fn rebag(origin: OriginFor, dislocated: T::AccountId) -> DispatchResult { ensure_signed(origin)?; - let current_weight = T::VoteWeightProvider::vote_weight(&dislocated); - let _ = Pallet::::do_rebag(&dislocated, current_weight); + let current_score = T::ScoreProvider::score(&dislocated); + let _ = Pallet::::do_rebag(&dislocated, current_score); Ok(()) } @@ -208,16 +227,16 @@ pub mod pallet { /// /// Only works if /// - both nodes are within the same bag, - /// - and `origin` has a greater `VoteWeight` than `lighter`. + /// - and `origin` has a greater `Score` than `lighter`. #[pallet::weight(T::WeightInfo::put_in_front_of())] pub fn put_in_front_of(origin: OriginFor, lighter: T::AccountId) -> DispatchResult { let heavier = ensure_signed(origin)?; - List::::put_in_front_of(&lighter, &heavier).map_err(Into::into) + List::::put_in_front_of(&lighter, &heavier).map_err(Into::into) } } #[pallet::hooks] - impl Hooks> for Pallet { + impl, I: 'static> Hooks> for Pallet { fn integrity_test() { // ensure they are strictly increasing, this also implies that duplicates are detected. assert!( @@ -228,71 +247,70 @@ pub mod pallet { } } -impl Pallet { +impl, I: 'static> Pallet { /// Move an account from one bag to another, depositing an event on success. /// /// If the account changed bags, returns `Some((from, to))`. - pub fn do_rebag( - account: &T::AccountId, - new_weight: VoteWeight, - ) -> Option<(VoteWeight, VoteWeight)> { + pub fn do_rebag(account: &T::AccountId, new_weight: T::Score) -> Option<(T::Score, T::Score)> { // if no voter at that node, don't do anything. // the caller just wasted the fee to call this. - let maybe_movement = list::Node::::get(&account) + let maybe_movement = list::Node::::get(&account) .and_then(|node| List::update_position_for(node, new_weight)); if let Some((from, to)) = maybe_movement { - Self::deposit_event(Event::::Rebagged { who: account.clone(), from, to }); + Self::deposit_event(Event::::Rebagged { who: account.clone(), from, to }); }; maybe_movement } /// Equivalent to `ListBags::get`, but public. Useful for tests in outside of this crate. #[cfg(feature = "std")] - pub fn list_bags_get(weight: VoteWeight) -> Option> { - ListBags::get(weight) + pub fn list_bags_get(score: T::Score) -> Option> { + ListBags::get(score) } } -impl SortedListProvider for Pallet { +impl, I: 'static> SortedListProvider for Pallet { type Error = Error; + type Score = T::Score; + fn iter() -> Box> { - Box::new(List::::iter().map(|n| n.id().clone())) + Box::new(List::::iter().map(|n| n.id().clone())) } fn count() -> u32 { - ListNodes::::count() + ListNodes::::count() } fn contains(id: &T::AccountId) -> bool { - List::::contains(id) + List::::contains(id) } - fn on_insert(id: T::AccountId, weight: VoteWeight) -> Result<(), Error> { - List::::insert(id, weight) + fn on_insert(id: T::AccountId, score: T::Score) -> Result<(), Error> { + List::::insert(id, score) } - fn on_update(id: &T::AccountId, new_weight: VoteWeight) { - Pallet::::do_rebag(id, new_weight); + fn on_update(id: &T::AccountId, new_score: T::Score) { + Pallet::::do_rebag(id, new_score); } fn on_remove(id: &T::AccountId) { - List::::remove(id) + List::::remove(id) } fn unsafe_regenerate( all: impl IntoIterator, - weight_of: Box VoteWeight>, + score_of: Box T::Score>, ) -> u32 { // NOTE: This call is unsafe for the same reason as SortedListProvider::unsafe_regenerate. // I.e. because it can lead to many storage accesses. // So it is ok to call it as caller must ensure the conditions. - List::::unsafe_regenerate(all, weight_of) + List::::unsafe_regenerate(all, score_of) } #[cfg(feature = "std")] fn sanity_check() -> Result<(), &'static str> { - List::::sanity_check() + List::::sanity_check() } #[cfg(not(feature = "std"))] @@ -304,17 +322,17 @@ impl SortedListProvider for Pallet { // NOTE: This call is unsafe for the same reason as SortedListProvider::unsafe_clear. // I.e. because it can lead to many storage accesses. // So it is ok to call it as caller must ensure the conditions. - List::::unsafe_clear() + List::::unsafe_clear() } #[cfg(feature = "runtime-benchmarks")] - fn weight_update_worst_case(who: &T::AccountId, is_increase: bool) -> VoteWeight { + fn score_update_worst_case(who: &T::AccountId, is_increase: bool) -> Self::Score { use frame_support::traits::Get as _; let thresholds = T::BagThresholds::get(); - let node = list::Node::::get(who).unwrap(); + let node = list::Node::::get(who).unwrap(); let current_bag_idx = thresholds .iter() - .chain(sp_std::iter::once(&VoteWeight::MAX)) + .chain(sp_std::iter::once(&T::Score::max_value())) .position(|w| w == &node.bag_upper()) .unwrap(); diff --git a/frame/bags-list/src/list/mod.rs b/frame/bags-list/src/list/mod.rs index 8cd89b8fff1cc..4921817c7e146 100644 --- a/frame/bags-list/src/list/mod.rs +++ b/frame/bags-list/src/list/mod.rs @@ -26,9 +26,10 @@ use crate::Config; use codec::{Decode, Encode, MaxEncodedLen}; -use frame_election_provider_support::{VoteWeight, VoteWeightProvider}; +use frame_election_provider_support::ScoreProvider; use frame_support::{traits::Get, DefaultNoBound}; use scale_info::TypeInfo; +use sp_runtime::traits::{Bounded, Zero}; use sp_std::{ boxed::Box, collections::{btree_map::BTreeMap, btree_set::BTreeSet}, @@ -46,36 +47,36 @@ pub enum Error { #[cfg(test)] mod tests; -/// Given a certain vote weight, to which bag does it belong to? +/// Given a certain score, to which bag does it belong to? /// /// Bags are identified by their upper threshold; the value returned by this function is guaranteed /// to be a member of `T::BagThresholds`. /// -/// Note that even if the thresholds list does not have `VoteWeight::MAX` as its final member, this -/// function behaves as if it does. -pub fn notional_bag_for(weight: VoteWeight) -> VoteWeight { +/// Note that even if the thresholds list does not have `T::Score::max_value()` as its final member, +/// this function behaves as if it does. +pub fn notional_bag_for, I: 'static>(score: T::Score) -> T::Score { let thresholds = T::BagThresholds::get(); - let idx = thresholds.partition_point(|&threshold| weight > threshold); - thresholds.get(idx).copied().unwrap_or(VoteWeight::MAX) + let idx = thresholds.partition_point(|&threshold| score > threshold); + thresholds.get(idx).copied().unwrap_or(T::Score::max_value()) } /// The **ONLY** entry point of this module. All operations to the bags-list should happen through /// this interface. It is forbidden to access other module members directly. // -// Data structure providing efficient mostly-accurate selection of the top N id by `VoteWeight`. +// Data structure providing efficient mostly-accurate selection of the top N id by `Score`. // // It's implemented as a set of linked lists. Each linked list comprises a bag of ids of -// arbitrary and unbounded length, all having a vote weight within a particular constant range. +// arbitrary and unbounded length, all having a score within a particular constant range. // This structure means that ids can be added and removed in `O(1)` time. // // Iteration is accomplished by chaining the iteration of each bag, from greatest to least. While -// the users within any particular bag are sorted in an entirely arbitrary order, the overall vote -// weight decreases as successive bags are reached. This means that it is valid to truncate +// the users within any particular bag are sorted in an entirely arbitrary order, the overall score +// decreases as successive bags are reached. This means that it is valid to truncate // iteration at any desired point; only those ids in the lowest bag can be excluded. This // satisfies both the desire for fairness and the requirement for efficiency. -pub struct List(PhantomData); +pub struct List, I: 'static = ()>(PhantomData<(T, I)>); -impl List { +impl, I: 'static> List { /// Remove all data associated with the list from storage. /// /// ## WARNING @@ -83,8 +84,8 @@ impl List { /// this function should generally not be used in production as it could lead to a very large /// number of storage accesses. pub(crate) fn unsafe_clear() { - crate::ListBags::::remove_all(None); - crate::ListNodes::::remove_all(); + crate::ListBags::::remove_all(None); + crate::ListNodes::::remove_all(); } /// Regenerate all of the data from the given ids. @@ -98,13 +99,13 @@ impl List { /// Returns the number of ids migrated. pub fn unsafe_regenerate( all: impl IntoIterator, - weight_of: Box VoteWeight>, + score_of: Box T::Score>, ) -> u32 { // NOTE: This call is unsafe for the same reason as SortedListProvider::unsafe_regenerate. // I.e. because it can lead to many storage accesses. // So it is ok to call it as caller must ensure the conditions. Self::unsafe_clear(); - Self::insert_many(all, weight_of) + Self::insert_many(all, score_of) } /// Migrate the list from one set of thresholds to another. @@ -127,7 +128,7 @@ impl List { /// - ids whose bags change at all are implicitly rebagged into the appropriate bag in the new /// threshold set. #[allow(dead_code)] - pub fn migrate(old_thresholds: &[VoteWeight]) -> u32 { + pub fn migrate(old_thresholds: &[T::Score]) -> u32 { let new_thresholds = T::BagThresholds::get(); if new_thresholds == old_thresholds { return 0 @@ -135,11 +136,13 @@ impl List { // we can't check all preconditions, but we can check one debug_assert!( - crate::ListBags::::iter().all(|(threshold, _)| old_thresholds.contains(&threshold)), + crate::ListBags::::iter() + .all(|(threshold, _)| old_thresholds.contains(&threshold)), "not all `bag_upper` currently in storage are members of `old_thresholds`", ); debug_assert!( - crate::ListNodes::::iter().all(|(_, node)| old_thresholds.contains(&node.bag_upper)), + crate::ListNodes::::iter() + .all(|(_, node)| old_thresholds.contains(&node.bag_upper)), "not all `node.bag_upper` currently in storage are members of `old_thresholds`", ); @@ -158,7 +161,7 @@ impl List { let affected_bag = { // this recreates `notional_bag_for` logic, but with the old thresholds. let idx = old_thresholds.partition_point(|&threshold| inserted_bag > threshold); - old_thresholds.get(idx).copied().unwrap_or(VoteWeight::MAX) + old_thresholds.get(idx).copied().unwrap_or(T::Score::max_value()) }; if !affected_old_bags.insert(affected_bag) { // If the previous threshold list was [10, 20], and we insert [3, 5], then there's @@ -166,7 +169,7 @@ impl List { continue } - if let Some(bag) = Bag::::get(affected_bag) { + if let Some(bag) = Bag::::get(affected_bag) { affected_accounts.extend(bag.iter().map(|node| node.id)); } } @@ -178,17 +181,17 @@ impl List { continue } - if let Some(bag) = Bag::::get(removed_bag) { + if let Some(bag) = Bag::::get(removed_bag) { affected_accounts.extend(bag.iter().map(|node| node.id)); } } // migrate the voters whose bag has changed let num_affected = affected_accounts.len() as u32; - let weight_of = T::VoteWeightProvider::vote_weight; + let score_of = T::ScoreProvider::score; let _removed = Self::remove_many(&affected_accounts); debug_assert_eq!(_removed, num_affected); - let _inserted = Self::insert_many(affected_accounts.into_iter(), weight_of); + let _inserted = Self::insert_many(affected_accounts.into_iter(), score_of); debug_assert_eq!(_inserted, num_affected); // we couldn't previously remove the old bags because both insertion and removal assume that @@ -199,10 +202,10 @@ impl List { // lookups. for removed_bag in removed_bags { debug_assert!( - !crate::ListNodes::::iter().any(|(_, node)| node.bag_upper == removed_bag), + !crate::ListNodes::::iter().any(|(_, node)| node.bag_upper == removed_bag), "no id should be present in a removed bag", ); - crate::ListBags::::remove(removed_bag); + crate::ListBags::::remove(removed_bag); } debug_assert_eq!(Self::sanity_check(), Ok(())); @@ -212,14 +215,14 @@ impl List { /// Returns `true` if the list contains `id`, otherwise returns `false`. pub(crate) fn contains(id: &T::AccountId) -> bool { - crate::ListNodes::::contains_key(id) + crate::ListNodes::::contains_key(id) } /// Iterate over all nodes in all bags in the list. /// /// Full iteration can be expensive; it's recommended to limit the number of items with /// `.take(n)`. - pub(crate) fn iter() -> impl Iterator> { + pub(crate) fn iter() -> impl Iterator> { // We need a touch of special handling here: because we permit `T::BagThresholds` to // omit the final bound, we need to ensure that we explicitly include that threshold in the // list. @@ -228,12 +231,14 @@ impl List { // easier; they can just configure `type BagThresholds = ()`. let thresholds = T::BagThresholds::get(); let iter = thresholds.iter().copied(); - let iter: Box> = if thresholds.last() == Some(&VoteWeight::MAX) { + let iter: Box> = if thresholds.last() == + Some(&T::Score::max_value()) + { // in the event that they included it, we can just pass the iterator through unchanged. Box::new(iter.rev()) } else { // otherwise, insert it here. - Box::new(iter.chain(iter::once(VoteWeight::MAX)).rev()) + Box::new(iter.chain(iter::once(T::Score::max_value())).rev()) }; iter.filter_map(Bag::get).flat_map(|bag| bag.iter()) @@ -245,12 +250,12 @@ impl List { /// Returns the final count of number of ids inserted. fn insert_many( ids: impl IntoIterator, - weight_of: impl Fn(&T::AccountId) -> VoteWeight, + score_of: impl Fn(&T::AccountId) -> T::Score, ) -> u32 { let mut count = 0; ids.into_iter().for_each(|v| { - let weight = weight_of(&v); - if Self::insert(v, weight).is_ok() { + let score = score_of(&v); + if Self::insert(v, score).is_ok() { count += 1; } }); @@ -261,13 +266,13 @@ impl List { /// Insert a new id into the appropriate bag in the list. /// /// Returns an error if the list already contains `id`. - pub(crate) fn insert(id: T::AccountId, weight: VoteWeight) -> Result<(), Error> { + pub(crate) fn insert(id: T::AccountId, score: T::Score) -> Result<(), Error> { if Self::contains(&id) { return Err(Error::Duplicate) } - let bag_weight = notional_bag_for::(weight); - let mut bag = Bag::::get_or_make(bag_weight); + let bag_score = notional_bag_for::(score); + let mut bag = Bag::::get_or_make(bag_score); // unchecked insertion is okay; we just got the correct `notional_bag_for`. bag.insert_unchecked(id.clone()); @@ -276,11 +281,12 @@ impl List { crate::log!( debug, - "inserted {:?} with weight {} into bag {:?}, new count is {}", + "inserted {:?} with score {:? + } into bag {:?}, new count is {}", id, - weight, - bag_weight, - crate::ListNodes::::count(), + score, + bag_score, + crate::ListNodes::::count(), ); Ok(()) @@ -301,7 +307,7 @@ impl List { let mut count = 0; for id in ids.into_iter() { - let node = match Node::::get(id) { + let node = match Node::::get(id) { Some(node) => node, None => continue, }; @@ -314,7 +320,7 @@ impl List { // this node is a head or tail, so the bag needs to be updated let bag = bags .entry(node.bag_upper) - .or_insert_with(|| Bag::::get_or_make(node.bag_upper)); + .or_insert_with(|| Bag::::get_or_make(node.bag_upper)); // node.bag_upper must be correct, therefore this bag will contain this node. bag.remove_node_unchecked(&node); } @@ -341,17 +347,17 @@ impl List { /// [`self.insert`]. However, given large quantities of nodes to move, it may be more efficient /// to call [`self.remove_many`] followed by [`self.insert_many`]. pub(crate) fn update_position_for( - node: Node, - new_weight: VoteWeight, - ) -> Option<(VoteWeight, VoteWeight)> { - node.is_misplaced(new_weight).then(move || { + node: Node, + new_score: T::Score, + ) -> Option<(T::Score, T::Score)> { + node.is_misplaced(new_score).then(move || { let old_bag_upper = node.bag_upper; if !node.is_terminal() { // this node is not a head or a tail, so we can just cut it out of the list. update // and put the prev and next of this node, we do `node.put` inside `insert_note`. node.excise(); - } else if let Some(mut bag) = Bag::::get(node.bag_upper) { + } else if let Some(mut bag) = Bag::::get(node.bag_upper) { // this is a head or tail, so the bag must be updated. bag.remove_node_unchecked(&node); bag.put(); @@ -365,8 +371,8 @@ impl List { } // put the node into the appropriate new bag. - let new_bag_upper = notional_bag_for::(new_weight); - let mut bag = Bag::::get_or_make(new_bag_upper); + let new_bag_upper = notional_bag_for::(new_score); + let mut bag = Bag::::get_or_make(new_bag_upper); // prev, next, and bag_upper of the node are updated inside `insert_node`, also // `node.put` is in there. bag.insert_node_unchecked(node); @@ -377,23 +383,22 @@ impl List { } /// Put `heavier_id` to the position directly in front of `lighter_id`. Both ids must be in the - /// same bag and the `weight_of` `lighter_id` must be less than that of `heavier_id`. + /// same bag and the `score_of` `lighter_id` must be less than that of `heavier_id`. pub(crate) fn put_in_front_of( lighter_id: &T::AccountId, heavier_id: &T::AccountId, - ) -> Result<(), crate::pallet::Error> { + ) -> Result<(), crate::pallet::Error> { use crate::pallet; use frame_support::ensure; - let lighter_node = Node::::get(&lighter_id).ok_or(pallet::Error::IdNotFound)?; - let heavier_node = Node::::get(&heavier_id).ok_or(pallet::Error::IdNotFound)?; + let lighter_node = Node::::get(&lighter_id).ok_or(pallet::Error::IdNotFound)?; + let heavier_node = Node::::get(&heavier_id).ok_or(pallet::Error::IdNotFound)?; ensure!(lighter_node.bag_upper == heavier_node.bag_upper, pallet::Error::NotInSameBag); // this is the most expensive check, so we do it last. ensure!( - T::VoteWeightProvider::vote_weight(&heavier_id) > - T::VoteWeightProvider::vote_weight(&lighter_id), + T::ScoreProvider::score(&heavier_id) > T::ScoreProvider::score(&lighter_id), pallet::Error::NotHeavier ); @@ -403,7 +408,7 @@ impl List { // re-fetch `lighter_node` from storage since it may have been updated when `heavier_node` // was removed. - let lighter_node = Node::::get(&lighter_id).ok_or_else(|| { + let lighter_node = Node::::get(&lighter_id).ok_or_else(|| { debug_assert!(false, "id that should exist cannot be found"); crate::log!(warn, "id that should exist cannot be found"); pallet::Error::IdNotFound @@ -422,7 +427,7 @@ impl List { /// - this is a naive function in that it does not check if `node` belongs to the same bag as /// `at`. It is expected that the call site will check preconditions. /// - this will panic if `at.bag_upper` is not a bag that already exists in storage. - fn insert_at_unchecked(mut at: Node, mut node: Node) { + fn insert_at_unchecked(mut at: Node, mut node: Node) { // connect `node` to its new `prev`. node.prev = at.prev.clone(); if let Some(mut prev) = at.prev() { @@ -439,7 +444,7 @@ impl List { // since `node` is always in front of `at` we know that 1) there is always at least 2 // nodes in the bag, and 2) only `node` could be the head and only `at` could be the // tail. - let mut bag = Bag::::get(at.bag_upper) + let mut bag = Bag::::get(at.bag_upper) .expect("given nodes must always have a valid bag. qed."); if node.prev == None { @@ -473,8 +478,8 @@ impl List { ); let iter_count = Self::iter().count() as u32; - let stored_count = crate::ListNodes::::count(); - let nodes_count = crate::ListNodes::::iter().count() as u32; + let stored_count = crate::ListNodes::::count(); + let nodes_count = crate::ListNodes::::iter().count() as u32; ensure!(iter_count == stored_count, "iter_count != stored_count"); ensure!(stored_count == nodes_count, "stored_count != nodes_count"); @@ -482,14 +487,15 @@ impl List { let active_bags = { let thresholds = T::BagThresholds::get().iter().copied(); - let thresholds: Vec = if thresholds.clone().last() == Some(VoteWeight::MAX) { - // in the event that they included it, we don't need to make any changes - thresholds.collect() - } else { - // otherwise, insert it here. - thresholds.chain(iter::once(VoteWeight::MAX)).collect() - }; - thresholds.into_iter().filter_map(|t| Bag::::get(t)) + let thresholds: Vec = + if thresholds.clone().last() == Some(T::Score::max_value()) { + // in the event that they included it, we don't need to make any changes + thresholds.collect() + } else { + // otherwise, insert it here. + thresholds.chain(iter::once(T::Score::max_value())).collect() + }; + thresholds.into_iter().filter_map(|t| Bag::::get(t)) }; let _ = active_bags.clone().map(|b| b.sanity_check()).collect::>()?; @@ -502,7 +508,7 @@ impl List { // check that all nodes are sane. We check the `ListNodes` storage item directly in case we // have some "stale" nodes that are not in a bag. - for (_id, node) in crate::ListNodes::::iter() { + for (_id, node) in crate::ListNodes::::iter() { node.sanity_check()? } @@ -517,21 +523,24 @@ impl List { /// Returns the nodes of all non-empty bags. For testing and benchmarks. #[cfg(any(feature = "std", feature = "runtime-benchmarks"))] #[allow(dead_code)] - pub(crate) fn get_bags() -> Vec<(VoteWeight, Vec)> { + pub(crate) fn get_bags() -> Vec<(T::Score, Vec)> { use frame_support::traits::Get as _; let thresholds = T::BagThresholds::get(); let iter = thresholds.iter().copied(); - let iter: Box> = if thresholds.last() == Some(&VoteWeight::MAX) { + let iter: Box> = if thresholds.last() == + Some(&T::Score::max_value()) + { // in the event that they included it, we can just pass the iterator through unchanged. Box::new(iter) } else { // otherwise, insert it here. - Box::new(iter.chain(sp_std::iter::once(VoteWeight::MAX))) + Box::new(iter.chain(sp_std::iter::once(T::Score::max_value()))) }; iter.filter_map(|t| { - Bag::::get(t).map(|bag| (t, bag.iter().map(|n| n.id().clone()).collect::>())) + Bag::::get(t) + .map(|bag| (t, bag.iter().map(|n| n.id().clone()).collect::>())) }) .collect::>() } @@ -546,37 +555,39 @@ impl List { /// appearing within the ids set. #[derive(DefaultNoBound, Encode, Decode, MaxEncodedLen, TypeInfo)] #[codec(mel_bound())] -#[scale_info(skip_type_params(T))] +#[scale_info(skip_type_params(T, I))] #[cfg_attr(feature = "std", derive(frame_support::DebugNoBound, Clone, PartialEq))] -pub struct Bag { +pub struct Bag, I: 'static = ()> { head: Option, tail: Option, #[codec(skip)] - bag_upper: VoteWeight, + bag_upper: T::Score, + #[codec(skip)] + _phantom: PhantomData, } -impl Bag { +impl, I: 'static> Bag { #[cfg(test)] pub(crate) fn new( head: Option, tail: Option, - bag_upper: VoteWeight, + bag_upper: T::Score, ) -> Self { - Self { head, tail, bag_upper } + Self { head, tail, bag_upper, _phantom: PhantomData } } - /// Get a bag by its upper vote weight. - pub(crate) fn get(bag_upper: VoteWeight) -> Option> { - crate::ListBags::::try_get(bag_upper).ok().map(|mut bag| { + /// Get a bag by its upper score. + pub(crate) fn get(bag_upper: T::Score) -> Option> { + crate::ListBags::::try_get(bag_upper).ok().map(|mut bag| { bag.bag_upper = bag_upper; bag }) } - /// Get a bag by its upper vote weight or make it, appropriately initialized. Does not check if + /// Get a bag by its upper score or make it, appropriately initialized. Does not check if /// if `bag_upper` is a valid threshold. - fn get_or_make(bag_upper: VoteWeight) -> Bag { + fn get_or_make(bag_upper: T::Score) -> Bag { Self::get(bag_upper).unwrap_or(Bag { bag_upper, ..Default::default() }) } @@ -588,24 +599,24 @@ impl Bag { /// Put the bag back into storage. fn put(self) { if self.is_empty() { - crate::ListBags::::remove(self.bag_upper); + crate::ListBags::::remove(self.bag_upper); } else { - crate::ListBags::::insert(self.bag_upper, self); + crate::ListBags::::insert(self.bag_upper, self); } } /// Get the head node in this bag. - fn head(&self) -> Option> { + fn head(&self) -> Option> { self.head.as_ref().and_then(|id| Node::get(id)) } /// Get the tail node in this bag. - fn tail(&self) -> Option> { + fn tail(&self) -> Option> { self.tail.as_ref().and_then(|id| Node::get(id)) } /// Iterate over the nodes in this bag. - pub(crate) fn iter(&self) -> impl Iterator> { + pub(crate) fn iter(&self) -> impl Iterator> { sp_std::iter::successors(self.head(), |prev| prev.next()) } @@ -620,7 +631,13 @@ impl Bag { // insert_node will overwrite `prev`, `next` and `bag_upper` to the proper values. As long // as this bag is the correct one, we're good. All calls to this must come after getting the // correct [`notional_bag_for`]. - self.insert_node_unchecked(Node:: { id, prev: None, next: None, bag_upper: 0 }); + self.insert_node_unchecked(Node:: { + id, + prev: None, + next: None, + bag_upper: Zero::zero(), + _phantom: PhantomData, + }); } /// Insert a node into this bag. @@ -630,7 +647,7 @@ impl Bag { /// /// Storage note: this modifies storage, but only for the node. You still need to call /// `self.put()` after use. - fn insert_node_unchecked(&mut self, mut node: Node) { + fn insert_node_unchecked(&mut self, mut node: Node) { if let Some(tail) = &self.tail { if *tail == node.id { // this should never happen, but this check prevents one path to a worst case @@ -674,7 +691,7 @@ impl Bag { /// /// Storage note: this modifies storage, but only for adjacent nodes. You still need to call /// `self.put()` and `ListNodes::remove(id)` to update storage for the bag and `node`. - fn remove_node_unchecked(&mut self, node: &Node) { + fn remove_node_unchecked(&mut self, node: &Node) { // reassign neighboring nodes. node.excise(); @@ -735,7 +752,7 @@ impl Bag { /// Iterate over the nodes in this bag (public for tests). #[cfg(feature = "std")] #[allow(dead_code)] - pub fn std_iter(&self) -> impl Iterator> { + pub fn std_iter(&self) -> impl Iterator> { sp_std::iter::successors(self.head(), |prev| prev.next()) } @@ -749,24 +766,26 @@ impl Bag { /// A Node is the fundamental element comprising the doubly-linked list described by `Bag`. #[derive(Encode, Decode, MaxEncodedLen, TypeInfo)] #[codec(mel_bound())] -#[scale_info(skip_type_params(T))] +#[scale_info(skip_type_params(T, I))] #[cfg_attr(feature = "std", derive(frame_support::DebugNoBound, Clone, PartialEq))] -pub struct Node { +pub struct Node, I: 'static = ()> { id: T::AccountId, prev: Option, next: Option, - bag_upper: VoteWeight, + bag_upper: T::Score, + #[codec(skip)] + _phantom: PhantomData, } -impl Node { +impl, I: 'static> Node { /// Get a node by id. - pub fn get(id: &T::AccountId) -> Option> { - crate::ListNodes::::try_get(id).ok() + pub fn get(id: &T::AccountId) -> Option> { + crate::ListNodes::::try_get(id).ok() } /// Put the node back into storage. fn put(self) { - crate::ListNodes::::insert(self.id.clone(), self); + crate::ListNodes::::insert(self.id.clone(), self); } /// Update neighboring nodes to point to reach other. @@ -790,22 +809,22 @@ impl Node { /// /// It is naive because it does not check if the node has first been removed from its bag. fn remove_from_storage_unchecked(&self) { - crate::ListNodes::::remove(&self.id) + crate::ListNodes::::remove(&self.id) } /// Get the previous node in the bag. - fn prev(&self) -> Option> { + fn prev(&self) -> Option> { self.prev.as_ref().and_then(|id| Node::get(id)) } /// Get the next node in the bag. - fn next(&self) -> Option> { + fn next(&self) -> Option> { self.next.as_ref().and_then(|id| Node::get(id)) } /// `true` when this voter is in the wrong bag. - pub fn is_misplaced(&self, current_weight: VoteWeight) -> bool { - notional_bag_for::(current_weight) != self.bag_upper + pub fn is_misplaced(&self, current_score: T::Score) -> bool { + notional_bag_for::(current_score) != self.bag_upper } /// `true` when this voter is a bag head or tail. @@ -828,13 +847,13 @@ impl Node { /// The bag this nodes belongs to (public for benchmarks). #[cfg(feature = "runtime-benchmarks")] #[allow(dead_code)] - pub fn bag_upper(&self) -> VoteWeight { + pub fn bag_upper(&self) -> T::Score { self.bag_upper } #[cfg(feature = "std")] fn sanity_check(&self) -> Result<(), &'static str> { - let expected_bag = Bag::::get(self.bag_upper).ok_or("bag not found for node")?; + let expected_bag = Bag::::get(self.bag_upper).ok_or("bag not found for node")?; let id = self.id(); diff --git a/frame/bags-list/src/list/tests.rs b/frame/bags-list/src/list/tests.rs index aaa215b0af1ca..9b7a078b44284 100644 --- a/frame/bags-list/src/list/tests.rs +++ b/frame/bags-list/src/list/tests.rs @@ -20,14 +20,20 @@ use crate::{ mock::{test_utils::*, *}, ListBags, ListNodes, }; -use frame_election_provider_support::SortedListProvider; +use frame_election_provider_support::{SortedListProvider, VoteWeight}; use frame_support::{assert_ok, assert_storage_noop}; #[test] fn basic_setup_works() { ExtBuilder::default().build_and_execute(|| { // syntactic sugar to create a raw node - let node = |id, prev, next, bag_upper| Node:: { id, prev, next, bag_upper }; + let node = |id, prev, next, bag_upper| Node:: { + id, + prev, + next, + bag_upper, + _phantom: PhantomData, + }; assert_eq!(ListNodes::::count(), 4); assert_eq!(ListNodes::::iter().count(), 4); @@ -38,11 +44,11 @@ fn basic_setup_works() { // the state of the bags is as expected assert_eq!( ListBags::::get(10).unwrap(), - Bag:: { head: Some(1), tail: Some(1), bag_upper: 0 } + Bag:: { head: Some(1), tail: Some(1), bag_upper: 0, _phantom: PhantomData } ); assert_eq!( ListBags::::get(1_000).unwrap(), - Bag:: { head: Some(2), tail: Some(4), bag_upper: 0 } + Bag:: { head: Some(2), tail: Some(4), bag_upper: 0, _phantom: PhantomData } ); assert_eq!(ListNodes::::get(2).unwrap(), node(2, None, Some(3), 1_000)); @@ -65,24 +71,24 @@ fn basic_setup_works() { #[test] fn notional_bag_for_works() { // under a threshold gives the next threshold. - assert_eq!(notional_bag_for::(0), 10); - assert_eq!(notional_bag_for::(9), 10); + assert_eq!(notional_bag_for::(0), 10); + assert_eq!(notional_bag_for::(9), 10); // at a threshold gives that threshold. - assert_eq!(notional_bag_for::(10), 10); + assert_eq!(notional_bag_for::(10), 10); // above the threshold, gives the next threshold. - assert_eq!(notional_bag_for::(11), 20); + assert_eq!(notional_bag_for::(11), 20); let max_explicit_threshold = *::BagThresholds::get().last().unwrap(); assert_eq!(max_explicit_threshold, 10_000); - // if the max explicit threshold is less than VoteWeight::MAX, + // if the max explicit threshold is less than T::Value::max_value(), assert!(VoteWeight::MAX > max_explicit_threshold); - // then anything above it will belong to the VoteWeight::MAX bag. - assert_eq!(notional_bag_for::(max_explicit_threshold), max_explicit_threshold); - assert_eq!(notional_bag_for::(max_explicit_threshold + 1), VoteWeight::MAX); + // then anything above it will belong to the T::Value::max_value() bag. + assert_eq!(notional_bag_for::(max_explicit_threshold), max_explicit_threshold); + assert_eq!(notional_bag_for::(max_explicit_threshold + 1), VoteWeight::MAX); } #[test] @@ -388,14 +394,26 @@ mod list { #[should_panic = "given nodes must always have a valid bag. qed."] fn put_in_front_of_panics_if_bag_not_found() { ExtBuilder::default().skip_genesis_ids().build_and_execute_no_post_check(|| { - let node_10_no_bag = Node:: { id: 10, prev: None, next: None, bag_upper: 15 }; - let node_11_no_bag = Node:: { id: 11, prev: None, next: None, bag_upper: 15 }; + let node_10_no_bag = Node:: { + id: 10, + prev: None, + next: None, + bag_upper: 15, + _phantom: PhantomData, + }; + let node_11_no_bag = Node:: { + id: 11, + prev: None, + next: None, + bag_upper: 15, + _phantom: PhantomData, + }; // given ListNodes::::insert(10, node_10_no_bag); ListNodes::::insert(11, node_11_no_bag); - StakingMock::set_vote_weight_of(&10, 14); - StakingMock::set_vote_weight_of(&11, 15); + StakingMock::set_score_of(&10, 14); + StakingMock::set_score_of(&11, 15); assert!(!ListBags::::contains_key(15)); assert_eq!(List::::get_bags(), vec![]); @@ -414,8 +432,13 @@ mod list { assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); // implicitly also test that `node`'s `prev`/`next` are correctly re-assigned. - let node_42 = - Node:: { id: 42, prev: Some(1), next: Some(2), bag_upper: 1_000 }; + let node_42 = Node:: { + id: 42, + prev: Some(1), + next: Some(2), + bag_upper: 1_000, + _phantom: PhantomData, + }; assert!(!crate::ListNodes::::contains_key(42)); let node_1 = crate::ListNodes::::get(&1).unwrap(); @@ -438,7 +461,13 @@ mod list { assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); // implicitly also test that `node`'s `prev`/`next` are correctly re-assigned. - let node_42 = Node:: { id: 42, prev: Some(4), next: None, bag_upper: 1_000 }; + let node_42 = Node:: { + id: 42, + prev: Some(4), + next: None, + bag_upper: 1_000, + _phantom: PhantomData, + }; assert!(!crate::ListNodes::::contains_key(42)); let node_2 = crate::ListNodes::::get(&2).unwrap(); @@ -461,7 +490,13 @@ mod list { assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); // implicitly also test that `node`'s `prev`/`next` are correctly re-assigned. - let node_42 = Node:: { id: 42, prev: None, next: Some(2), bag_upper: 1_000 }; + let node_42 = Node:: { + id: 42, + prev: None, + next: Some(2), + bag_upper: 1_000, + _phantom: PhantomData, + }; assert!(!crate::ListNodes::::contains_key(42)); let node_3 = crate::ListNodes::::get(&3).unwrap(); @@ -484,8 +519,13 @@ mod list { assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); // implicitly also test that `node`'s `prev`/`next` are correctly re-assigned. - let node_42 = - Node:: { id: 42, prev: Some(42), next: Some(42), bag_upper: 1_000 }; + let node_42 = Node:: { + id: 42, + prev: Some(42), + next: Some(42), + bag_upper: 1_000, + _phantom: PhantomData, + }; assert!(!crate::ListNodes::::contains_key(42)); let node_4 = crate::ListNodes::::get(&4).unwrap(); @@ -512,7 +552,7 @@ mod bags { let bag = Bag::::get(bag_upper).unwrap(); let bag_ids = bag.iter().map(|n| *n.id()).collect::>(); - assert_eq!(bag, Bag:: { head, tail, bag_upper }); + assert_eq!(bag, Bag:: { head, tail, bag_upper, _phantom: PhantomData }); assert_eq!(bag_ids, ids); }; @@ -543,7 +583,13 @@ mod bags { #[test] fn insert_node_sets_proper_bag() { ExtBuilder::default().build_and_execute_no_post_check(|| { - let node = |id, bag_upper| Node:: { id, prev: None, next: None, bag_upper }; + let node = |id, bag_upper| Node:: { + id, + prev: None, + next: None, + bag_upper, + _phantom: PhantomData, + }; assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); @@ -552,7 +598,7 @@ mod bags { assert_eq!( ListNodes::::get(&42).unwrap(), - Node { bag_upper: 10, prev: Some(1), next: None, id: 42 } + Node { bag_upper: 10, prev: Some(1), next: None, id: 42, _phantom: PhantomData } ); }); } @@ -560,7 +606,13 @@ mod bags { #[test] fn insert_node_happy_paths_works() { ExtBuilder::default().build_and_execute_no_post_check(|| { - let node = |id, bag_upper| Node:: { id, prev: None, next: None, bag_upper }; + let node = |id, bag_upper| Node:: { + id, + prev: None, + next: None, + bag_upper, + _phantom: PhantomData, + }; // when inserting into a bag with 1 node let mut bag_10 = Bag::::get(10).unwrap(); @@ -581,15 +633,26 @@ mod bags { assert_eq!(bag_as_ids(&bag_20), vec![62]); // when inserting a node pointing to the accounts not in the bag - let node_61 = - Node:: { id: 61, prev: Some(21), next: Some(101), bag_upper: 20 }; + let node_61 = Node:: { + id: 61, + prev: Some(21), + next: Some(101), + bag_upper: 20, + _phantom: PhantomData, + }; bag_20.insert_node_unchecked(node_61); // then ids are in order assert_eq!(bag_as_ids(&bag_20), vec![62, 61]); // and when the node is re-fetched all the info is correct assert_eq!( Node::::get(&61).unwrap(), - Node:: { id: 61, prev: Some(62), next: None, bag_upper: 20 } + Node:: { + id: 61, + prev: Some(62), + next: None, + bag_upper: 20, + _phantom: PhantomData, + } ); // state of all bags is as expected @@ -604,7 +667,13 @@ mod bags { // Document improper ways `insert_node` may be getting used. #[test] fn insert_node_bad_paths_documented() { - let node = |id, prev, next, bag_upper| Node:: { id, prev, next, bag_upper }; + let node = |id, prev, next, bag_upper| Node:: { + id, + prev, + next, + bag_upper, + _phantom: PhantomData, + }; ExtBuilder::default().build_and_execute_no_post_check(|| { // when inserting a node with both prev & next pointing at an account in an incorrect // bag. @@ -657,7 +726,10 @@ mod bags { ); // ^^^ despite being the bags head, it has a prev - assert_eq!(bag_1000, Bag { head: Some(2), tail: Some(2), bag_upper: 1_000 }) + assert_eq!( + bag_1000, + Bag { head: Some(2), tail: Some(2), bag_upper: 1_000, _phantom: PhantomData } + ) }); } @@ -669,7 +741,13 @@ mod bags { )] fn insert_node_duplicate_tail_panics_with_debug_assert() { ExtBuilder::default().build_and_execute(|| { - let node = |id, prev, next, bag_upper| Node:: { id, prev, next, bag_upper }; + let node = |id, prev, next, bag_upper| Node:: { + id, + prev, + next, + bag_upper, + _phantom: PhantomData, + }; // given assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])],); @@ -801,6 +879,7 @@ mod bags { prev: None, next: Some(3), bag_upper: 10, // should be 1_000 + _phantom: PhantomData, }; let mut bag_1000 = Bag::::get(1_000).unwrap(); diff --git a/frame/bags-list/src/mock.rs b/frame/bags-list/src/mock.rs index aa3f549e12dec..fce1431054174 100644 --- a/frame/bags-list/src/mock.rs +++ b/frame/bags-list/src/mock.rs @@ -27,19 +27,21 @@ pub type AccountId = u32; pub type Balance = u32; parameter_types! { - // Set the vote weight for any id who's weight has _not_ been set with `set_vote_weight_of`. + // Set the vote weight for any id who's weight has _not_ been set with `set_score_of`. pub static NextVoteWeight: VoteWeight = 0; pub static NextVoteWeightMap: HashMap = Default::default(); } pub struct StakingMock; -impl frame_election_provider_support::VoteWeightProvider for StakingMock { - fn vote_weight(id: &AccountId) -> VoteWeight { +impl frame_election_provider_support::ScoreProvider for StakingMock { + type Score = VoteWeight; + + fn score(id: &AccountId) -> Self::Score { *NextVoteWeightMap::get().get(id).unwrap_or(&NextVoteWeight::get()) } #[cfg(any(feature = "runtime-benchmarks", test))] - fn set_vote_weight_of(id: &AccountId, weight: VoteWeight) { + fn set_score_of(id: &AccountId, weight: Self::Score) { NEXT_VOTE_WEIGHT_MAP.with(|m| m.borrow_mut().insert(id.clone(), weight)); } } @@ -79,7 +81,8 @@ impl bags_list::Config for Runtime { type Event = Event; type WeightInfo = (); type BagThresholds = BagThresholds; - type VoteWeightProvider = StakingMock; + type ScoreProvider = StakingMock; + type Score = VoteWeight; } type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; @@ -134,7 +137,7 @@ impl ExtBuilder { ext.execute_with(|| { for (id, weight) in ids_with_weight { frame_support::assert_ok!(List::::insert(*id, *weight)); - StakingMock::set_vote_weight_of(id, *weight); + StakingMock::set_score_of(id, *weight); } }); diff --git a/frame/bags-list/src/tests.rs b/frame/bags-list/src/tests.rs index 43397b3c120f5..99396c9cbb3e3 100644 --- a/frame/bags-list/src/tests.rs +++ b/frame/bags-list/src/tests.rs @@ -18,7 +18,7 @@ use frame_support::{assert_noop, assert_ok, assert_storage_noop, traits::IntegrityTest}; use super::*; -use frame_election_provider_support::SortedListProvider; +use frame_election_provider_support::{SortedListProvider, VoteWeight}; use list::Bag; use mock::{test_utils::*, *}; @@ -35,7 +35,7 @@ mod pallet { ); // when increasing vote weight to the level of non-existent bag - StakingMock::set_vote_weight_of(&42, 2_000); + StakingMock::set_score_of(&42, 2_000); assert_ok!(BagsList::rebag(Origin::signed(0), 42)); // then a new bag is created and the id moves into it @@ -45,7 +45,7 @@ mod pallet { ); // when decreasing weight within the range of the current bag - StakingMock::set_vote_weight_of(&42, 1_001); + StakingMock::set_score_of(&42, 1_001); assert_ok!(BagsList::rebag(Origin::signed(0), 42)); // then the id does not move @@ -55,7 +55,7 @@ mod pallet { ); // when reducing weight to the level of a non-existent bag - StakingMock::set_vote_weight_of(&42, 30); + StakingMock::set_score_of(&42, 30); assert_ok!(BagsList::rebag(Origin::signed(0), 42)); // then a new bag is created and the id moves into it @@ -65,7 +65,7 @@ mod pallet { ); // when increasing weight to the level of a pre-existing bag - StakingMock::set_vote_weight_of(&42, 500); + StakingMock::set_score_of(&42, 500); assert_ok!(BagsList::rebag(Origin::signed(0), 42)); // then the id moves into that bag @@ -85,7 +85,7 @@ mod pallet { assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); // when - StakingMock::set_vote_weight_of(&4, 10); + StakingMock::set_score_of(&4, 10); assert_ok!(BagsList::rebag(Origin::signed(0), 4)); // then @@ -93,7 +93,7 @@ mod pallet { assert_eq!(Bag::::get(1_000).unwrap(), Bag::new(Some(2), Some(3), 1_000)); // when - StakingMock::set_vote_weight_of(&3, 10); + StakingMock::set_score_of(&3, 10); assert_ok!(BagsList::rebag(Origin::signed(0), 3)); // then @@ -104,7 +104,7 @@ mod pallet { assert_eq!(get_list_as_ids(), vec![2u32, 1, 4, 3]); // when - StakingMock::set_vote_weight_of(&2, 10); + StakingMock::set_score_of(&2, 10); assert_ok!(BagsList::rebag(Origin::signed(0), 2)); // then @@ -119,7 +119,7 @@ mod pallet { fn rebag_head_works() { ExtBuilder::default().build_and_execute(|| { // when - StakingMock::set_vote_weight_of(&2, 10); + StakingMock::set_score_of(&2, 10); assert_ok!(BagsList::rebag(Origin::signed(0), 2)); // then @@ -127,7 +127,7 @@ mod pallet { assert_eq!(Bag::::get(1_000).unwrap(), Bag::new(Some(3), Some(4), 1_000)); // when - StakingMock::set_vote_weight_of(&3, 10); + StakingMock::set_score_of(&3, 10); assert_ok!(BagsList::rebag(Origin::signed(0), 3)); // then @@ -135,7 +135,7 @@ mod pallet { assert_eq!(Bag::::get(1_000).unwrap(), Bag::new(Some(4), Some(4), 1_000)); // when - StakingMock::set_vote_weight_of(&4, 10); + StakingMock::set_score_of(&4, 10); assert_ok!(BagsList::rebag(Origin::signed(0), 4)); // then @@ -241,7 +241,7 @@ mod pallet { // given assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4, 5])]); - StakingMock::set_vote_weight_of(&3, 999); + StakingMock::set_score_of(&3, 999); // when assert_ok!(BagsList::put_in_front_of(Origin::signed(4), 3)); @@ -262,7 +262,7 @@ mod pallet { vec![(10, vec![1]), (1_000, vec![2, 3, 4, 5, 6])] ); - StakingMock::set_vote_weight_of(&5, 999); + StakingMock::set_score_of(&5, 999); // when assert_ok!(BagsList::put_in_front_of(Origin::signed(3), 5)); @@ -281,7 +281,7 @@ mod pallet { // given assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); - StakingMock::set_vote_weight_of(&2, 999); + StakingMock::set_score_of(&2, 999); // when assert_ok!(BagsList::put_in_front_of(Origin::signed(3), 2)); @@ -297,7 +297,7 @@ mod pallet { // given assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); - StakingMock::set_vote_weight_of(&3, 999); + StakingMock::set_score_of(&3, 999); // when assert_ok!(BagsList::put_in_front_of(Origin::signed(4), 3)); @@ -313,7 +313,7 @@ mod pallet { // given assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4, 5])]); - StakingMock::set_vote_weight_of(&2, 999); + StakingMock::set_score_of(&2, 999); // when assert_ok!(BagsList::put_in_front_of(Origin::signed(5), 2)); @@ -329,7 +329,7 @@ mod pallet { // given assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4, 5])]); - StakingMock::set_vote_weight_of(&4, 999); + StakingMock::set_score_of(&4, 999); // when BagsList::put_in_front_of(Origin::signed(2), 4).unwrap(); @@ -359,7 +359,7 @@ mod pallet { // given assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); - StakingMock::set_vote_weight_of(&4, 999); + StakingMock::set_score_of(&4, 999); // when BagsList::put_in_front_of(Origin::signed(2), 4).unwrap(); @@ -375,7 +375,7 @@ mod pallet { // given assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); - StakingMock::set_vote_weight_of(&3, 999); + StakingMock::set_score_of(&3, 999); // then assert_noop!( diff --git a/frame/election-provider-support/Cargo.toml b/frame/election-provider-support/Cargo.toml index e66774c6b5e71..b95bd994fa70a 100644 --- a/frame/election-provider-support/Cargo.toml +++ b/frame/election-provider-support/Cargo.toml @@ -18,12 +18,12 @@ scale-info = { version = "2.0.1", default-features = false, features = ["derive" sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } sp-arithmetic = { version = "5.0.0", default-features = false, path = "../../primitives/arithmetic" } sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../primitives/npos-elections" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } [dev-dependencies] sp-npos-elections = { version = "4.0.0-dev", path = "../../primitives/npos-elections" } -sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } sp-core = { version = "6.0.0", path = "../../primitives/core" } sp-io = { version = "6.0.0", path = "../../primitives/io" } diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index ca22e3093855c..ff57eacf68fa0 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -168,6 +168,7 @@ pub mod onchain; use frame_support::{traits::Get, BoundedVec}; +use sp_runtime::traits::Bounded; use sp_std::{fmt::Debug, prelude::*}; /// Re-export some type as they are used in the interface. @@ -333,9 +334,7 @@ where /// This is generic over `AccountId` and it can represent a validator, a nominator, or any other /// entity. /// -/// To simplify the trait, the `VoteWeight` is hardcoded as the weight of each entity. The weights -/// are ascending, the higher, the better. In the long term, if this trait ends up having use cases -/// outside of the election context, it is easy enough to make it generic over the `VoteWeight`. +/// The scores (see [`Self::Score`]) are ascending, the higher, the better. /// /// Something that implements this trait will do a best-effort sort over ids, and thus can be /// used on the implementing side of [`ElectionDataProvider`]. @@ -343,6 +342,9 @@ pub trait SortedListProvider { /// The list's error type. type Error: sp_std::fmt::Debug; + /// The type used by the list to compare nodes for ordering. + type Score: Bounded; + /// An iterator over the list, which can have `take` called on it. fn iter() -> Box>; @@ -353,10 +355,10 @@ pub trait SortedListProvider { fn contains(id: &AccountId) -> bool; /// Hook for inserting a new id. - fn on_insert(id: AccountId, weight: VoteWeight) -> Result<(), Self::Error>; + fn on_insert(id: AccountId, score: Self::Score) -> Result<(), Self::Error>; /// Hook for updating a single id. - fn on_update(id: &AccountId, weight: VoteWeight); + fn on_update(id: &AccountId, score: Self::Score); /// Hook for removing am id from the list. fn on_remove(id: &AccountId); @@ -371,7 +373,7 @@ pub trait SortedListProvider { /// new list, which can lead to too many storage accesses, exhausting the block weight. fn unsafe_regenerate( all: impl IntoIterator, - weight_of: Box VoteWeight>, + score_of: Box Self::Score>, ) -> u32; /// Remove all items from the list. @@ -388,21 +390,23 @@ pub trait SortedListProvider { /// If `who` changes by the returned amount they are guaranteed to have a worst case change /// in their list position. #[cfg(feature = "runtime-benchmarks")] - fn weight_update_worst_case(_who: &AccountId, _is_increase: bool) -> VoteWeight { - VoteWeight::MAX + fn score_update_worst_case(_who: &AccountId, _is_increase: bool) -> Self::Score { + Self::Score::max_value() } } /// Something that can provide the `VoteWeight` of an account. Similar to [`ElectionProvider`] and /// [`ElectionDataProvider`], this should typically be implementing by whoever is supposed to *use* /// `SortedListProvider`. -pub trait VoteWeightProvider { - /// Get the current `VoteWeight` of `who`. - fn vote_weight(who: &AccountId) -> VoteWeight; +pub trait ScoreProvider { + type Score; + + /// Get the current `Score` of `who`. + fn score(who: &AccountId) -> Self::Score; /// For tests and benchmarks, set the `VoteWeight`. #[cfg(any(feature = "runtime-benchmarks", test))] - fn set_vote_weight_of(_: &AccountId, _: VoteWeight) {} + fn set_score_of(_: &AccountId, _: Self::Score) {} } /// Something that can compute the result to an NPoS solution. diff --git a/frame/staking/src/benchmarking.rs b/frame/staking/src/benchmarking.rs index ec7d36c820f3e..3e57b647ac80d 100644 --- a/frame/staking/src/benchmarking.rs +++ b/frame/staking/src/benchmarking.rs @@ -189,7 +189,7 @@ impl ListScenario { // find a destination weight that will trigger the worst case scenario let dest_weight_as_vote = - T::SortedListProvider::weight_update_worst_case(&origin_stash1, is_increase); + T::SortedListProvider::score_update_worst_case(&origin_stash1, is_increase); let total_issuance = T::Currency::total_issuance(); diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index 12d3804b4e303..843791a46ade2 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -18,7 +18,7 @@ //! Test utilities use crate::{self as pallet_staking, *}; -use frame_election_provider_support::{onchain, SortedListProvider}; +use frame_election_provider_support::{onchain, SortedListProvider, VoteWeight}; use frame_support::{ assert_ok, parameter_types, traits::{ @@ -240,8 +240,9 @@ parameter_types! { impl pallet_bags_list::Config for Test { type Event = Event; type WeightInfo = (); - type VoteWeightProvider = Staking; + type ScoreProvider = Staking; type BagThresholds = BagThresholds; + type Score = VoteWeight; } impl onchain::Config for Test { diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 5cd0d0107f015..a4aadb16ab1b9 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -18,8 +18,8 @@ //! Implementations for the Staking FRAME Pallet. use frame_election_provider_support::{ - data_provider, ElectionDataProvider, ElectionProvider, SortedListProvider, Supports, - VoteWeight, VoteWeightProvider, VoterOf, + data_provider, ElectionDataProvider, ElectionProvider, ScoreProvider, SortedListProvider, + Supports, VoteWeight, VoterOf, }; use frame_support::{ pallet_prelude::*, @@ -1244,13 +1244,15 @@ where } } -impl VoteWeightProvider for Pallet { - fn vote_weight(who: &T::AccountId) -> VoteWeight { +impl ScoreProvider for Pallet { + type Score = VoteWeight; + + fn score(who: &T::AccountId) -> Self::Score { Self::weight_of(who) } #[cfg(feature = "runtime-benchmarks")] - fn set_vote_weight_of(who: &T::AccountId, weight: VoteWeight) { + fn set_score_of(who: &T::AccountId, weight: Self::Score) { // this will clearly results in an inconsistent state, but it should not matter for a // benchmark. let active: BalanceOf = weight.try_into().map_err(|_| ()).unwrap(); @@ -1279,6 +1281,7 @@ impl VoteWeightProvider for Pallet { pub struct UseNominatorsMap(sp_std::marker::PhantomData); impl SortedListProvider for UseNominatorsMap { type Error = (); + type Score = VoteWeight; /// Returns iterator over voter list, which can have `take` called on it. fn iter() -> Box> { @@ -1290,11 +1293,11 @@ impl SortedListProvider for UseNominatorsMap { fn contains(id: &T::AccountId) -> bool { Nominators::::contains_key(id) } - fn on_insert(_: T::AccountId, _weight: VoteWeight) -> Result<(), Self::Error> { + fn on_insert(_: T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> { // nothing to do on insert. Ok(()) } - fn on_update(_: &T::AccountId, _weight: VoteWeight) { + fn on_update(_: &T::AccountId, _weight: Self::Score) { // nothing to do on update. } fn on_remove(_: &T::AccountId) { @@ -1302,7 +1305,7 @@ impl SortedListProvider for UseNominatorsMap { } fn unsafe_regenerate( _: impl IntoIterator, - _: Box VoteWeight>, + _: Box Self::Score>, ) -> u32 { // nothing to do upon regenerate. 0 diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 58f9fd237263b..b50ce5ed4502c 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -166,7 +166,10 @@ pub mod pallet { /// Something that can provide a sorted list of voters in a somewhat sorted way. The /// original use case for this was designed with `pallet_bags_list::Pallet` in mind. If /// the bags-list is not desired, [`impls::UseNominatorsMap`] is likely the desired option. - type SortedListProvider: SortedListProvider; + type SortedListProvider: SortedListProvider< + Self::AccountId, + Score = frame_election_provider_support::VoteWeight, + >; /// The maximum number of `unlocking` chunks a [`StakingLedger`] can have. Effectively /// determines how many unique eras a staker may be unbonding in. From 9a061d9bab1fbe85a9ab1d49e2bdd9b66eb19732 Mon Sep 17 00:00:00 2001 From: Qinxuan Chen Date: Thu, 10 Mar 2022 17:02:58 +0800 Subject: [PATCH 010/484] sc-finality-grandpa: use the #[from] attriute to remove boilerplate code (#11003) Signed-off-by: koushiro --- client/finality-grandpa/src/lib.rs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/client/finality-grandpa/src/lib.rs b/client/finality-grandpa/src/lib.rs index 8316e56b5b5e5..fef16286381ea 100644 --- a/client/finality-grandpa/src/lib.rs +++ b/client/finality-grandpa/src/lib.rs @@ -279,7 +279,7 @@ impl Config { pub enum Error { /// An error within grandpa. #[error("grandpa error: {0}")] - Grandpa(GrandpaError), + Grandpa(#[from] GrandpaError), /// A network error. #[error("network error: {0}")] @@ -291,7 +291,7 @@ pub enum Error { /// Could not complete a round on disk. #[error("could not complete a round on disk: {0}")] - Client(ClientError), + Client(#[from] ClientError), /// Could not sign outgoing message #[error("could not sign outgoing message: {0}")] @@ -310,18 +310,6 @@ pub enum Error { RuntimeApi(sp_api::ApiError), } -impl From for Error { - fn from(e: GrandpaError) -> Self { - Error::Grandpa(e) - } -} - -impl From for Error { - fn from(e: ClientError) -> Self { - Error::Client(e) - } -} - /// Something which can determine if a block is known. pub(crate) trait BlockStatus { /// Return `Ok(Some(number))` or `Ok(None)` depending on whether the block From 1819dd7020443fad920b1e9de991e68d5589a613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Thu, 10 Mar 2022 12:28:00 +0100 Subject: [PATCH 011/484] contracts: Add test to verify unique trie ids (#10914) * Add test to verify unique trie ids * Rename trie_seed to nonce * Rename AccountCounter -> Nonce * fmt --- frame/contracts/src/exec.rs | 67 +++++++++++++-------------- frame/contracts/src/lib.rs | 27 +++++++++-- frame/contracts/src/migration.rs | 24 ++++++++++ frame/contracts/src/storage.rs | 7 ++- frame/contracts/src/tests.rs | 77 ++++++++++++++++++++++++++++---- frame/contracts/src/weights.rs | 16 +++---- 6 files changed, 162 insertions(+), 56 deletions(-) diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index eb7a68d81ad50..455665687d973 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -18,7 +18,7 @@ use crate::{ gas::GasMeter, storage::{self, Storage, WriteOutcome}, - AccountCounter, BalanceOf, CodeHash, Config, ContractInfo, ContractInfoOf, Error, Event, + BalanceOf, CodeHash, Config, ContractInfo, ContractInfoOf, Error, Event, Nonce, Pallet as Contracts, Schedule, }; use frame_support::{ @@ -315,9 +315,10 @@ pub struct Stack<'a, T: Config, E> { timestamp: MomentOf, /// The block number at the time of call stack instantiation. block_number: T::BlockNumber, - /// The account counter is cached here when accessed. It is written back when the call stack - /// finishes executing. - account_counter: Option, + /// The nonce is cached here when accessed. It is written back when the call stack + /// finishes executing. Please refer to [`Nonce`] to a description of + /// the nonce itself. + nonce: Option, /// The actual call stack. One entry per nested contract called/instantiated. /// This does **not** include the [`Self::first_frame`]. frames: SmallVec, @@ -385,8 +386,8 @@ enum FrameArgs<'a, T: Config, E> { Instantiate { /// The contract or signed origin which instantiates the new contract. sender: T::AccountId, - /// The seed that should be used to derive a new trie id for the contract. - trie_seed: u64, + /// The nonce that should be used to derive a new trie id for the contract. + nonce: u64, /// The executable whose `deploy` function is run. executable: E, /// A salt used in the contract address deriviation of the new contract. @@ -571,7 +572,7 @@ where let (mut stack, executable) = Self::new( FrameArgs::Instantiate { sender: origin.clone(), - trie_seed: Self::initial_trie_seed(), + nonce: Self::initial_nonce(), executable, salt, }, @@ -596,7 +597,7 @@ where value: BalanceOf, debug_message: Option<&'a mut Vec>, ) -> Result<(Self, E), ExecError> { - let (first_frame, executable, account_counter) = + let (first_frame, executable, nonce) = Self::new_frame(args, value, gas_meter, storage_meter, 0, &schedule)?; let stack = Self { origin, @@ -605,7 +606,7 @@ where storage_meter, timestamp: T::Time::now(), block_number: >::block_number(), - account_counter, + nonce, first_frame, frames: Default::default(), debug_message, @@ -627,7 +628,7 @@ where gas_limit: Weight, schedule: &Schedule, ) -> Result<(Frame, E, Option), ExecError> { - let (account_id, contract_info, executable, delegate_caller, entry_point, account_counter) = + let (account_id, contract_info, executable, delegate_caller, entry_point, nonce) = match frame_args { FrameArgs::Call { dest, cached_info, delegated_call } => { let contract = if let Some(contract) = cached_info { @@ -645,10 +646,10 @@ where (dest, contract, executable, delegate_caller, ExportedFunction::Call, None) }, - FrameArgs::Instantiate { sender, trie_seed, executable, salt } => { + FrameArgs::Instantiate { sender, nonce, executable, salt } => { let account_id = >::contract_address(&sender, executable.code_hash(), &salt); - let trie_id = Storage::::generate_trie_id(&account_id, trie_seed); + let trie_id = Storage::::generate_trie_id(&account_id, nonce); let contract = Storage::::new_contract( &account_id, trie_id, @@ -660,7 +661,7 @@ where executable, None, ExportedFunction::Constructor, - Some(trie_seed), + Some(nonce), ) }, }; @@ -676,7 +677,7 @@ where allows_reentry: true, }; - Ok((frame, executable, account_counter)) + Ok((frame, executable, nonce)) } /// Create a subsequent nested frame. @@ -782,9 +783,9 @@ where /// This is called after running the current frame. It commits cached values to storage /// and invalidates all stale references to it that might exist further down the call stack. fn pop_frame(&mut self, persist: bool) { - // Revert the account counter in case of a failed instantiation. + // Revert changes to the nonce in case of a failed instantiation. if !persist && self.top_frame().entry_point == ExportedFunction::Constructor { - self.account_counter.as_mut().map(|c| *c = c.wrapping_sub(1)); + self.nonce.as_mut().map(|c| *c = c.wrapping_sub(1)); } // Pop the current frame from the stack and return it in case it needs to interact @@ -861,8 +862,8 @@ where if let Some(contract) = contract { >::insert(&self.first_frame.account_id, contract); } - if let Some(counter) = self.account_counter { - >::set(counter); + if let Some(nonce) = self.nonce { + >::set(nonce); } } } @@ -920,20 +921,20 @@ where !self.frames().any(|f| &f.account_id == id && !f.allows_reentry) } - /// Increments the cached account id and returns the value to be used for the trie_id. - fn next_trie_seed(&mut self) -> u64 { - let next = if let Some(current) = self.account_counter { + /// Increments and returns the next nonce. Pulls it from storage if it isn't in cache. + fn next_nonce(&mut self) -> u64 { + let next = if let Some(current) = self.nonce { current.wrapping_add(1) } else { - Self::initial_trie_seed() + Self::initial_nonce() }; - self.account_counter = Some(next); + self.nonce = Some(next); next } - /// The account seed to be used to instantiate the account counter cache. - fn initial_trie_seed() -> u64 { - >::get().wrapping_add(1) + /// Pull the current nonce from storage. + fn initial_nonce() -> u64 { + >::get().wrapping_add(1) } } @@ -1020,11 +1021,11 @@ where salt: &[u8], ) -> Result<(AccountIdOf, ExecReturnValue), ExecError> { let executable = E::from_storage(code_hash, &self.schedule, self.gas_meter())?; - let trie_seed = self.next_trie_seed(); + let nonce = self.next_nonce(); let executable = self.push_frame( FrameArgs::Instantiate { sender: self.top_frame().account_id.clone(), - trie_seed, + nonce, executable, salt, }, @@ -2445,7 +2446,7 @@ mod tests { } #[test] - fn account_counter() { + fn nonce() { let fail_code = MockLoader::insert(Constructor, |_, _| exec_trapped()); let success_code = MockLoader::insert(Constructor, |_, _| exec_success()); let succ_fail_code = MockLoader::insert(Constructor, move |ctx, _| { @@ -2495,7 +2496,7 @@ mod tests { None, ) .ok(); - assert_eq!(>::get(), 0); + assert_eq!(>::get(), 0); assert_ok!(MockStack::run_instantiate( ALICE, @@ -2508,7 +2509,7 @@ mod tests { &[], None, )); - assert_eq!(>::get(), 1); + assert_eq!(>::get(), 1); assert_ok!(MockStack::run_instantiate( ALICE, @@ -2521,7 +2522,7 @@ mod tests { &[], None, )); - assert_eq!(>::get(), 2); + assert_eq!(>::get(), 2); assert_ok!(MockStack::run_instantiate( ALICE, @@ -2534,7 +2535,7 @@ mod tests { &[], None, )); - assert_eq!(>::get(), 4); + assert_eq!(>::get(), 4); }); } diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index 037e3f1d33ae3..4edf43a672152 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -134,7 +134,7 @@ type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; /// The current storage version. -const STORAGE_VERSION: StorageVersion = StorageVersion::new(6); +const STORAGE_VERSION: StorageVersion = StorageVersion::new(7); /// Used as a sentinel value when reading and writing contract memory. /// @@ -665,9 +665,30 @@ pub mod pallet { #[pallet::storage] pub(crate) type OwnerInfoOf = StorageMap<_, Identity, CodeHash, OwnerInfo>; - /// The subtrie counter. + /// This is a **monotonic** counter incremented on contract instantiation. + /// + /// This is used in order to generate unique trie ids for contracts. + /// The trie id of a new contract is calculated from hash(account_id, nonce). + /// The nonce is required because otherwise the following sequence would lead to + /// a possible collision of storage: + /// + /// 1. Create a new contract. + /// 2. Terminate the contract. + /// 3. Immediately recreate the contract with the same account_id. + /// + /// This is bad because the contents of a trie are deleted lazily and there might be + /// storage of the old instantiation still in it when the new contract is created. Please + /// note that we can't replace the counter by the block number because the sequence above + /// can happen in the same block. We also can't keep the account counter in memory only + /// because storage is the only way to communicate across different extrinsics in the + /// same block. + /// + /// # Note + /// + /// Do not use it to determine the number of contracts. It won't be decremented if + /// a contract is destroyed. #[pallet::storage] - pub(crate) type AccountCounter = StorageValue<_, u64, ValueQuery>; + pub(crate) type Nonce = StorageValue<_, u64, ValueQuery>; /// The code associated with a given account. /// diff --git a/frame/contracts/src/migration.rs b/frame/contracts/src/migration.rs index 59d3721ab13cf..035e3b4409cf9 100644 --- a/frame/contracts/src/migration.rs +++ b/frame/contracts/src/migration.rs @@ -19,6 +19,7 @@ use crate::{BalanceOf, CodeHash, Config, Pallet, TrieId, Weight}; use codec::{Decode, Encode}; use frame_support::{ codec, generate_storage_alias, + pallet_prelude::*, storage::migration, traits::{Get, PalletInfoAccess}, Identity, Twox64Concat, @@ -47,6 +48,11 @@ pub fn migrate() -> Weight { StorageVersion::new(6).put::>(); } + if version < 7 { + weight = weight.saturating_add(v7::migrate::()); + StorageVersion::new(7).put::>(); + } + weight } @@ -249,3 +255,21 @@ mod v6 { weight } } + +/// Rename `AccountCounter` to `Nonce`. +mod v7 { + use super::*; + + pub fn migrate() -> Weight { + generate_storage_alias!( + Contracts, + AccountCounter => Value + ); + generate_storage_alias!( + Contracts, + Nonce => Value + ); + Nonce::set(AccountCounter::take()); + T::DbWeight::get().reads_writes(1, 2) + } +} diff --git a/frame/contracts/src/storage.rs b/frame/contracts/src/storage.rs index 51a43a1782425..17022e9427664 100644 --- a/frame/contracts/src/storage.rs +++ b/frame/contracts/src/storage.rs @@ -290,10 +290,9 @@ where weight_limit.saturating_sub(weight_per_key.saturating_mul(remaining_key_budget as Weight)) } - /// This generator uses inner counter for account id and applies the hash over `AccountId + - /// accountid_counter`. - pub fn generate_trie_id(account_id: &AccountIdOf, seed: u64) -> TrieId { - let buf: Vec<_> = account_id.as_ref().iter().chain(&seed.to_le_bytes()).cloned().collect(); + /// Generates a unique trie id by returning `hash(account_id ++ nonce)`. + pub fn generate_trie_id(account_id: &AccountIdOf, nonce: u64) -> TrieId { + let buf: Vec<_> = account_id.as_ref().iter().chain(&nonce.to_le_bytes()).cloned().collect(); T::Hashing::hash(&buf).as_ref().into() } diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index 8add424db0892..4c8c42c77da56 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -74,17 +74,15 @@ frame_support::construct_runtime!( #[macro_use] pub mod test_utils { use super::{Balances, Test}; - use crate::{ - exec::AccountIdOf, storage::Storage, AccountCounter, CodeHash, Config, ContractInfoOf, - }; + use crate::{exec::AccountIdOf, storage::Storage, CodeHash, Config, ContractInfoOf, Nonce}; use frame_support::traits::Currency; pub fn place_contract(address: &AccountIdOf, code_hash: CodeHash) { - let seed = >::mutate(|counter| { + let nonce = >::mutate(|counter| { *counter += 1; *counter }); - let trie_id = Storage::::generate_trie_id(address, seed); + let trie_id = Storage::::generate_trie_id(address, nonce); set_balance(address, ::Currency::minimum_balance() * 10); let contract = Storage::::new_contract(&address, trie_id, code_hash).unwrap(); >::insert(address, contract); @@ -349,6 +347,11 @@ where Ok((wasm_binary, code_hash)) } +fn initialize_block(number: u64) { + System::reset_events(); + System::initialize(&number, &[0u8; 32].into(), &Default::default()); +} + // Perform a call to a plain account. // The actual transfer fails because we can only call contracts. // Then we check that at least the base costs where charged (no runtime gas costs.) @@ -540,9 +543,67 @@ fn run_out_of_gas() { }); } -fn initialize_block(number: u64) { - System::reset_events(); - System::initialize(&number, &[0u8; 32].into(), &Default::default()); +/// Check that contracts with the same account id have different trie ids. +/// Check the `Nonce` storage item for more information. +#[test] +fn instantiate_unique_trie_id() { + let (wasm, code_hash) = compile_module::("self_destruct").unwrap(); + + ExtBuilder::default().existential_deposit(500).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + Contracts::upload_code(Origin::signed(ALICE), wasm, None).unwrap(); + let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + + // Instantiate the contract and store its trie id for later comparison. + assert_ok!(Contracts::instantiate( + Origin::signed(ALICE), + 0, + GAS_LIMIT, + None, + code_hash, + vec![], + vec![], + )); + let trie_id = ContractInfoOf::::get(&addr).unwrap().trie_id; + + // Try to instantiate it again without termination should yield an error. + assert_err_ignore_postinfo!( + Contracts::instantiate( + Origin::signed(ALICE), + 0, + GAS_LIMIT, + None, + code_hash, + vec![], + vec![], + ), + >::DuplicateContract, + ); + + // Terminate the contract. + assert_ok!(Contracts::call( + Origin::signed(ALICE), + addr.clone(), + 0, + GAS_LIMIT, + None, + vec![] + )); + + // Re-Instantiate after termination. + assert_ok!(Contracts::instantiate( + Origin::signed(ALICE), + 0, + GAS_LIMIT, + None, + code_hash, + vec![], + vec![], + )); + + // Trie ids shouldn't match or we might have a collision + assert_ne!(trie_id, ContractInfoOf::::get(&addr).unwrap().trie_id); + }); } #[test] diff --git a/frame/contracts/src/weights.rs b/frame/contracts/src/weights.rs index 7d73a3a1cecc7..b438ad51cbfca 100644 --- a/frame/contracts/src/weights.rs +++ b/frame/contracts/src/weights.rs @@ -201,7 +201,7 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Contracts CodeStorage (r:1 w:1) - // Storage: Contracts AccountCounter (r:1 w:1) + // Storage: Contracts Nonce (r:1 w:1) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Timestamp Now (r:1 w:0) // Storage: System Account (r:1 w:1) @@ -217,7 +217,7 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().writes(6 as Weight)) } // Storage: Contracts CodeStorage (r:1 w:1) - // Storage: Contracts AccountCounter (r:1 w:1) + // Storage: Contracts Nonce (r:1 w:1) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Timestamp Now (r:1 w:0) // Storage: System Account (r:1 w:1) @@ -651,7 +651,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: Contracts AccountCounter (r:1 w:1) + // Storage: Contracts Nonce (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:100 w:100) fn seal_instantiate(r: u32, ) -> Weight { (0 as Weight) @@ -666,7 +666,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:101 w:101) // Storage: Contracts CodeStorage (r:2 w:1) // Storage: Timestamp Now (r:1 w:0) - // Storage: Contracts AccountCounter (r:1 w:1) + // Storage: Contracts Nonce (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:1 w:1) fn seal_instantiate_per_transfer_salt_kb(t: u32, s: u32, ) -> Weight { (14_790_752_000 as Weight) @@ -1092,7 +1092,7 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Contracts CodeStorage (r:1 w:1) - // Storage: Contracts AccountCounter (r:1 w:1) + // Storage: Contracts Nonce (r:1 w:1) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Timestamp Now (r:1 w:0) // Storage: System Account (r:1 w:1) @@ -1108,7 +1108,7 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } // Storage: Contracts CodeStorage (r:1 w:1) - // Storage: Contracts AccountCounter (r:1 w:1) + // Storage: Contracts Nonce (r:1 w:1) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Timestamp Now (r:1 w:0) // Storage: System Account (r:1 w:1) @@ -1542,7 +1542,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: Contracts AccountCounter (r:1 w:1) + // Storage: Contracts Nonce (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:100 w:100) fn seal_instantiate(r: u32, ) -> Weight { (0 as Weight) @@ -1557,7 +1557,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:101 w:101) // Storage: Contracts CodeStorage (r:2 w:1) // Storage: Timestamp Now (r:1 w:0) - // Storage: Contracts AccountCounter (r:1 w:1) + // Storage: Contracts Nonce (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:1 w:1) fn seal_instantiate_per_transfer_salt_kb(t: u32, s: u32, ) -> Weight { (14_790_752_000 as Weight) From 14bfd588790b11fa6a4b9304f464abe4af3c09eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Thu, 10 Mar 2022 19:26:42 +0100 Subject: [PATCH 012/484] sp-core: `full_crypto` doesn't imply `std` (#11006) * sp-core: `full_crypto` doesn't imply `std` This pr changes the feature set of `secp256k1` to not use `global-context` when only the `full_crypto` is enabled. It will be slower when the `std` feature is not enabled as the context always needs to be recreated, but that is fine. * Update client/cli/src/arg_enums.rs Co-authored-by: Davide Galassi Co-authored-by: Davide Galassi --- primitives/core/Cargo.toml | 3 ++- primitives/core/src/ecdsa.rs | 32 ++++++++++++++++++++++++++------ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/primitives/core/Cargo.toml b/primitives/core/Cargo.toml index 1effb8efbf5ae..55d00362033d0 100644 --- a/primitives/core/Cargo.toml +++ b/primitives/core/Cargo.toml @@ -56,7 +56,7 @@ schnorrkel = { version = "0.9.1", features = [ hex = { version = "0.4", default-features = false, optional = true } libsecp256k1 = { version = "0.7", default-features = false, features = ["static-context"], optional = true } merlin = { version = "2.0", default-features = false, optional = true } -secp256k1 = { version = "0.21.2", default-features = false, features = ["recovery", "global-context"], optional = true } +secp256k1 = { version = "0.21.2", default-features = false, features = ["recovery", "alloc"], optional = true } ss58-registry = { version = "1.11.0", default-features = false } sp-core-hashing = { version = "4.0.0", path = "./hashing", default-features = false, optional = true } sp-runtime-interface = { version = "6.0.0", default-features = false, path = "../runtime-interface" } @@ -108,6 +108,7 @@ std = [ "regex", "num-traits/std", "secp256k1/std", + "secp256k1/global-context", "sp-core-hashing/std", "sp-debug-derive/std", "sp-externalities", diff --git a/primitives/core/src/ecdsa.rs b/primitives/core/src/ecdsa.rs index 9389a277736dd..7a4e4399913dc 100644 --- a/primitives/core/src/ecdsa.rs +++ b/primitives/core/src/ecdsa.rs @@ -34,12 +34,14 @@ use crate::{ }; #[cfg(feature = "std")] use bip39::{Language, Mnemonic, MnemonicType}; -#[cfg(feature = "full_crypto")] -use core::convert::TryFrom; +#[cfg(all(feature = "full_crypto", not(feature = "std")))] +use secp256k1::Secp256k1; +#[cfg(feature = "std")] +use secp256k1::SECP256K1; #[cfg(feature = "full_crypto")] use secp256k1::{ ecdsa::{RecoverableSignature, RecoveryId}, - Message, PublicKey, SecretKey, SECP256K1, + Message, PublicKey, SecretKey, }; #[cfg(feature = "std")] use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; @@ -334,7 +336,13 @@ impl Signature { let rid = RecoveryId::from_i32(self.0[64] as i32).ok()?; let sig = RecoverableSignature::from_compact(&self.0[..64], rid).ok()?; let message = Message::from_slice(message).expect("Message is 32 bytes; qed"); - SECP256K1 + + #[cfg(feature = "std")] + let context = SECP256K1; + #[cfg(not(feature = "std"))] + let context = Secp256k1::verification_only(); + + context .recover_ecdsa(&message, &sig) .ok() .map(|pubkey| Public(pubkey.serialize())) @@ -425,7 +433,13 @@ impl TraitPair for Pair { fn from_seed_slice(seed_slice: &[u8]) -> Result { let secret = SecretKey::from_slice(seed_slice).map_err(|_| SecretStringError::InvalidSeedLength)?; - let public = PublicKey::from_secret_key(SECP256K1, &secret); + + #[cfg(feature = "std")] + let context = SECP256K1; + #[cfg(not(feature = "std"))] + let context = Secp256k1::signing_only(); + + let public = PublicKey::from_secret_key(&context, &secret); let public = Public(public.serialize()); Ok(Pair { public, secret }) } @@ -503,7 +517,13 @@ impl Pair { /// Sign a pre-hashed message pub fn sign_prehashed(&self, message: &[u8; 32]) -> Signature { let message = Message::from_slice(message).expect("Message is 32 bytes; qed"); - SECP256K1.sign_ecdsa_recoverable(&message, &self.secret).into() + + #[cfg(feature = "std")] + let context = SECP256K1; + #[cfg(not(feature = "std"))] + let context = Secp256k1::signing_only(); + + context.sign_ecdsa_recoverable(&message, &self.secret).into() } /// Verify a signature on a pre-hashed message. Return `true` if the signature is valid From db8b159e74db62aa48783217ee27661216dbdfa8 Mon Sep 17 00:00:00 2001 From: Liu-Cheng Xu Date: Fri, 11 Mar 2022 02:35:11 +0800 Subject: [PATCH 013/484] Remove unused `parent_hash` in `OverlayedChanges::into_storage_changes` (#11011) Ref https://github.com/paritytech/substrate/pull/10922#issuecomment-1064258443 CC @cheme --- primitives/api/proc-macro/src/impl_runtime_apis.rs | 1 - primitives/state-machine/src/ext.rs | 8 +------- primitives/state-machine/src/overlayed_changes/mod.rs | 4 +--- primitives/state-machine/src/testing.rs | 1 - utils/frame/try-runtime/cli/src/commands/follow_chain.rs | 1 - 5 files changed, 2 insertions(+), 13 deletions(-) diff --git a/primitives/api/proc-macro/src/impl_runtime_apis.rs b/primitives/api/proc-macro/src/impl_runtime_apis.rs index ae18849d83ccc..dc89c9f592e57 100644 --- a/primitives/api/proc-macro/src/impl_runtime_apis.rs +++ b/primitives/api/proc-macro/src/impl_runtime_apis.rs @@ -301,7 +301,6 @@ fn generate_runtime_api_base_structures() -> Result { self.changes.replace(Default::default()).into_storage_changes( backend, - parent_hash, self.storage_transaction_cache.replace(Default::default()), state_version, ) diff --git a/primitives/state-machine/src/ext.rs b/primitives/state-machine/src/ext.rs index e87b22d4f9b76..7b7e4b47f19ba 100644 --- a/primitives/state-machine/src/ext.rs +++ b/primitives/state-machine/src/ext.rs @@ -660,7 +660,6 @@ where self.overlay .drain_storage_changes( self.backend, - Default::default(), self.storage_transaction_cache, Default::default(), // using any state ) @@ -680,12 +679,7 @@ where } let changes = self .overlay - .drain_storage_changes( - self.backend, - Default::default(), - self.storage_transaction_cache, - state_version, - ) + .drain_storage_changes(self.backend, self.storage_transaction_cache, state_version) .expect(EXT_NOT_ALLOWED_TO_FAIL); self.backend .commit( diff --git a/primitives/state-machine/src/overlayed_changes/mod.rs b/primitives/state-machine/src/overlayed_changes/mod.rs index d75feec0ced9c..59f3e1cffa5f6 100644 --- a/primitives/state-machine/src/overlayed_changes/mod.rs +++ b/primitives/state-machine/src/overlayed_changes/mod.rs @@ -500,21 +500,19 @@ impl OverlayedChanges { pub fn into_storage_changes, H: Hasher>( mut self, backend: &B, - parent_hash: H::Out, mut cache: StorageTransactionCache, state_version: StateVersion, ) -> Result, DefaultError> where H::Out: Ord + Encode + 'static, { - self.drain_storage_changes(backend, parent_hash, &mut cache, state_version) + self.drain_storage_changes(backend, &mut cache, state_version) } /// Drain all changes into a [`StorageChanges`] instance. Leave empty overlay in place. pub fn drain_storage_changes, H: Hasher>( &mut self, backend: &B, - _parent_hash: H::Out, mut cache: &mut StorageTransactionCache, state_version: StateVersion, ) -> Result, DefaultError> diff --git a/primitives/state-machine/src/testing.rs b/primitives/state-machine/src/testing.rs index 7f71d24b761a9..bc462ae01ab26 100644 --- a/primitives/state-machine/src/testing.rs +++ b/primitives/state-machine/src/testing.rs @@ -179,7 +179,6 @@ where pub fn commit_all(&mut self) -> Result<(), String> { let changes = self.overlay.drain_storage_changes::<_, _>( &self.backend, - Default::default(), &mut Default::default(), self.state_version, )?; diff --git a/utils/frame/try-runtime/cli/src/commands/follow_chain.rs b/utils/frame/try-runtime/cli/src/commands/follow_chain.rs index 82bc04880106e..7400813b9175e 100644 --- a/utils/frame/try-runtime/cli/src/commands/follow_chain.rs +++ b/utils/frame/try-runtime/cli/src/commands/follow_chain.rs @@ -150,7 +150,6 @@ where let storage_changes = changes .drain_storage_changes( &state_ext.backend, - Default::default(), &mut Default::default(), // Note that in case a block contains a runtime upgrade, // state version could potentially be incorrect here, From 06715402cc6df9326926260db0a0e574819b2fd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 11 Mar 2022 23:20:18 +0100 Subject: [PATCH 014/484] sp-api: Don't be dirty (#11015) Ensure that the sp api macros don't use functions without providing the full path to the function. This hygiene ensures that we don't actually try to call a method of an imported trait for example. --- .../api/proc-macro/src/decl_runtime_apis.rs | 26 +++--- .../api/proc-macro/src/impl_runtime_apis.rs | 88 ++++++++++--------- 2 files changed, 59 insertions(+), 55 deletions(-) diff --git a/primitives/api/proc-macro/src/decl_runtime_apis.rs b/primitives/api/proc-macro/src/decl_runtime_apis.rs index bc72aaa81681b..2301f531590ee 100644 --- a/primitives/api/proc-macro/src/decl_runtime_apis.rs +++ b/primitives/api/proc-macro/src/decl_runtime_apis.rs @@ -378,21 +378,21 @@ fn generate_call_api_at_calls(decl: &ItemTrait) -> Result { #[cfg(any(feature = "std", test))] #[allow(clippy::too_many_arguments)] pub fn #fn_name< - R: #crate_::Encode + #crate_::Decode + PartialEq, + R: #crate_::Encode + #crate_::Decode + std::cmp::PartialEq, NC: FnOnce() -> std::result::Result + std::panic::UnwindSafe, Block: #crate_::BlockT, T: #crate_::CallApiAt, >( call_runtime_at: &T, at: &#crate_::BlockId, - args: Vec, + args: std::vec::Vec, changes: &std::cell::RefCell<#crate_::OverlayedChanges>, storage_transaction_cache: &std::cell::RefCell< #crate_::StorageTransactionCache >, - native_call: Option, + native_call: std::option::Option, context: #crate_::ExecutionContext, - recorder: &Option<#crate_::ProofRecorder>, + recorder: &std::option::Option<#crate_::ProofRecorder>, ) -> std::result::Result<#crate_::NativeOrEncoded, #crate_::ApiError> { let version = call_runtime_at.runtime_version_at(at)?; @@ -412,7 +412,7 @@ fn generate_call_api_at_calls(decl: &ItemTrait) -> Result { recorder, }; - let ret = call_runtime_at.call_api_at(params)?; + let ret = #crate_::CallApiAt::::call_api_at(call_runtime_at, params)?; return Ok(ret) } @@ -429,7 +429,7 @@ fn generate_call_api_at_calls(decl: &ItemTrait) -> Result { recorder, }; - call_runtime_at.call_api_at(params) + #crate_::CallApiAt::::call_api_at(call_runtime_at, params) } )); } @@ -677,13 +677,13 @@ impl<'a> ToClientSideDecl<'a> { #native_handling }, #crate_::NativeOrEncoded::Encoded(r) => { - <#ret_type as #crate_::Decode>::decode(&mut &r[..]) - .map_err(|err| - #crate_::ApiError::FailedToDecodeReturnValue { - function: #function_name, - error: err, - } - ) + std::result::Result::map_err( + <#ret_type as #crate_::Decode>::decode(&mut &r[..]), + |err| #crate_::ApiError::FailedToDecodeReturnValue { + function: #function_name, + error: err, + } + ) } } ) diff --git a/primitives/api/proc-macro/src/impl_runtime_apis.rs b/primitives/api/proc-macro/src/impl_runtime_apis.rs index dc89c9f592e57..f594a743fcf94 100644 --- a/primitives/api/proc-macro/src/impl_runtime_apis.rs +++ b/primitives/api/proc-macro/src/impl_runtime_apis.rs @@ -207,7 +207,7 @@ fn generate_runtime_api_base_structures() -> Result { storage_transaction_cache: std::cell::RefCell< #crate_::StorageTransactionCache >, - recorder: Option<#crate_::ProofRecorder>, + recorder: std::option::Option<#crate_::ProofRecorder>, } // `RuntimeApi` itself is not threadsafe. However, an instance is only available in a @@ -233,12 +233,12 @@ fn generate_runtime_api_base_structures() -> Result { &self, call: F, ) -> R where Self: Sized { - self.changes.borrow_mut().start_transaction(); - *self.commit_on_success.borrow_mut() = false; + #crate_::OverlayedChanges::start_transaction(&mut std::cell::RefCell::borrow_mut(&self.changes)); + *std::cell::RefCell::borrow_mut(&self.commit_on_success) = false; let res = call(self); - *self.commit_on_success.borrow_mut() = true; + *std::cell::RefCell::borrow_mut(&self.commit_on_success) = true; - self.commit_or_rollback(matches!(res, #crate_::TransactionOutcome::Commit(_))); + self.commit_or_rollback(std::matches!(res, #crate_::TransactionOutcome::Commit(_))); res.into_inner() } @@ -247,9 +247,8 @@ fn generate_runtime_api_base_structures() -> Result { &self, at: &#crate_::BlockId, ) -> std::result::Result where Self: Sized { - self.call - .runtime_version_at(at) - .map(|v| v.has_api_with(&A::ID, |v| v == A::VERSION)) + #crate_::CallApiAt::::runtime_version_at(self.call, at) + .map(|v| #crate_::RuntimeVersion::has_api_with(&v, &A::ID, |v| v == A::VERSION)) } fn has_api_with bool>( @@ -257,51 +256,48 @@ fn generate_runtime_api_base_structures() -> Result { at: &#crate_::BlockId, pred: P, ) -> std::result::Result where Self: Sized { - self.call - .runtime_version_at(at) - .map(|v| v.has_api_with(&A::ID, pred)) + #crate_::CallApiAt::::runtime_version_at(self.call, at) + .map(|v| #crate_::RuntimeVersion::has_api_with(&v, &A::ID, pred)) } fn api_version( &self, at: &#crate_::BlockId, ) -> std::result::Result, #crate_::ApiError> where Self: Sized { - self.call - .runtime_version_at(at) - .map(|v| v.api_version(&A::ID)) + #crate_::CallApiAt::::runtime_version_at(self.call, at) + .map(|v| #crate_::RuntimeVersion::api_version(&v, &A::ID)) } fn record_proof(&mut self) { - self.recorder = Some(Default::default()); + self.recorder = std::option::Option::Some(std::default::Default::default()); } - fn proof_recorder(&self) -> Option<#crate_::ProofRecorder> { - self.recorder.clone() + fn proof_recorder(&self) -> std::option::Option<#crate_::ProofRecorder> { + std::clone::Clone::clone(&self.recorder) } - fn extract_proof(&mut self) -> Option<#crate_::StorageProof> { - self.recorder - .take() - .map(|recorder| recorder.to_storage_proof()) + fn extract_proof(&mut self) -> std::option::Option<#crate_::StorageProof> { + std::option::Option::take(&mut self.recorder) + .map(|recorder| #crate_::ProofRecorder::::to_storage_proof(&recorder)) } fn into_storage_changes( &self, backend: &Self::StateBackend, parent_hash: Block::Hash, - ) -> std::result::Result< + ) -> core::result::Result< #crate_::StorageChanges, String > where Self: Sized { - let at = #crate_::BlockId::Hash(parent_hash.clone()); - let state_version = self.call - .runtime_version_at(&at) - .map(|v| v.state_version()) + let at = #crate_::BlockId::Hash(std::clone::Clone::clone(&parent_hash)); + let state_version = #crate_::CallApiAt::::runtime_version_at(self.call, &at) + .map(|v| #crate_::RuntimeVersion::state_version(&v)) .map_err(|e| format!("Failed to get state version: {}", e))?; - self.changes.replace(Default::default()).into_storage_changes( + #crate_::OverlayedChanges::into_storage_changes( + std::cell::RefCell::take(&self.changes), backend, - self.storage_transaction_cache.replace(Default::default()), + core::cell::RefCell::take(&self.storage_transaction_cache), state_version, ) } @@ -321,9 +317,9 @@ fn generate_runtime_api_base_structures() -> Result { RuntimeApiImpl { call: unsafe { std::mem::transmute(call) }, commit_on_success: true.into(), - changes: Default::default(), - recorder: Default::default(), - storage_transaction_cache: Default::default(), + changes: std::default::Default::default(), + recorder: std::default::Default::default(), + storage_transaction_cache: std::default::Default::default(), }.into() } } @@ -331,20 +327,22 @@ fn generate_runtime_api_base_structures() -> Result { #[cfg(any(feature = "std", test))] impl> RuntimeApiImpl { fn call_api_at< - R: #crate_::Encode + #crate_::Decode + PartialEq, + R: #crate_::Encode + #crate_::Decode + std::cmp::PartialEq, F: FnOnce( &C, &std::cell::RefCell<#crate_::OverlayedChanges>, &std::cell::RefCell<#crate_::StorageTransactionCache>, - &Option<#crate_::ProofRecorder>, + &std::option::Option<#crate_::ProofRecorder>, ) -> std::result::Result<#crate_::NativeOrEncoded, E>, E, >( &self, call_api_at: F, ) -> std::result::Result<#crate_::NativeOrEncoded, E> { - if *self.commit_on_success.borrow() { - self.changes.borrow_mut().start_transaction(); + if *std::cell::RefCell::borrow(&self.commit_on_success) { + #crate_::OverlayedChanges::start_transaction( + &mut std::cell::RefCell::borrow_mut(&self.changes) + ); } let res = call_api_at( &self.call, @@ -353,7 +351,7 @@ fn generate_runtime_api_base_structures() -> Result { &self.recorder, ); - self.commit_or_rollback(res.is_ok()); + self.commit_or_rollback(std::result::Result::is_ok(&res)); res } @@ -362,13 +360,19 @@ fn generate_runtime_api_base_structures() -> Result { We only close a transaction when we opened one ourself. Other parts of the runtime that make use of transactions (state-machine) also balance their transactions. The runtime cannot close client initiated - transactions. qed"; - if *self.commit_on_success.borrow() { - if commit { - self.changes.borrow_mut().commit_transaction().expect(proof); + transactions; qed"; + if *std::cell::RefCell::borrow(&self.commit_on_success) { + let res = if commit { + #crate_::OverlayedChanges::commit_transaction( + &mut std::cell::RefCell::borrow_mut(&self.changes) + ) } else { - self.changes.borrow_mut().rollback_transaction().expect(proof); - } + #crate_::OverlayedChanges::rollback_transaction( + &mut std::cell::RefCell::borrow_mut(&self.changes) + ) + }; + + std::result::Result::expect(res, proof); } } } From d31901497304b3240947353ce211fb3337fc4475 Mon Sep 17 00:00:00 2001 From: Sergejs Kostjucenko <85877331+sergejparity@users.noreply.github.com> Date: Mon, 14 Mar 2022 10:42:34 +0200 Subject: [PATCH 015/484] Move scripts used in CI to the new location (#11008) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move scripts used in CI to the new location - **./scripts/ci/** * Move github scripts * Move more files * Move ci scripts and fix dependencies * Update docs/node-template-release.md Co-authored-by: João Paulo Silva de Souza <77391175+joao-paulo-parity@users.noreply.github.com> * Remove Cargo.lock * Apply suggestions from code review Co-authored-by: Denis Pisarev * Make more paths uniform Co-authored-by: João Paulo Silva de Souza <77391175+joao-paulo-parity@users.noreply.github.com> Co-authored-by: Denis Pisarev --- .github/workflows/check-labels.yml | 2 +- .github/workflows/monthly-tag.yml | 2 +- .gitignore | 2 +- .gitlab-ci.yml | 52 +++++++++---------- docs/CODEOWNERS | 2 +- docs/node-template-release.md | 4 +- {.maintain => scripts/ci}/common/lib.sh | 0 {.maintain => scripts/ci}/deny.toml | 0 .../ci}/docker/subkey.Dockerfile | 2 +- .../ci}/docker/substrate.Dockerfile | 2 +- .../ci}/github/check_labels.sh | 0 .../ci/github}/generate_changelog.sh | 0 .../ci}/gitlab/check_runtime.sh | 0 .../ci}/gitlab/check_signed.sh | 0 .../ci/gitlab}/ensure-deps.sh | 0 .../ci}/gitlab/publish_draft_release.sh | 0 .../ci}/gitlab/skip_if_draft.sh | 0 .../alerting-rules/alerting-rule-tests.yaml | 0 .../alerting-rules/alerting-rules.yaml | 0 .../grafana-dashboards/README_dashboard.md | 0 .../substrate-networking.json | 0 .../substrate-service-tasks.json | 0 .../ci}/node-template-release.sh | 2 +- .../ci}/node-template-release/Cargo.toml | 0 .../ci}/node-template-release/src/main.rs | 0 25 files changed, 35 insertions(+), 35 deletions(-) rename {.maintain => scripts/ci}/common/lib.sh (100%) rename {.maintain => scripts/ci}/deny.toml (100%) rename {.maintain => scripts/ci}/docker/subkey.Dockerfile (93%) rename {.maintain => scripts/ci}/docker/substrate.Dockerfile (96%) rename {.maintain => scripts/ci}/github/check_labels.sh (100%) rename {.maintain/gitlab => scripts/ci/github}/generate_changelog.sh (100%) rename {.maintain => scripts/ci}/gitlab/check_runtime.sh (100%) rename {.maintain => scripts/ci}/gitlab/check_signed.sh (100%) rename {.maintain => scripts/ci/gitlab}/ensure-deps.sh (100%) rename {.maintain => scripts/ci}/gitlab/publish_draft_release.sh (100%) rename {.maintain => scripts/ci}/gitlab/skip_if_draft.sh (100%) rename {.maintain => scripts/ci}/monitoring/alerting-rules/alerting-rule-tests.yaml (100%) rename {.maintain => scripts/ci}/monitoring/alerting-rules/alerting-rules.yaml (100%) rename {.maintain => scripts/ci}/monitoring/grafana-dashboards/README_dashboard.md (100%) rename {.maintain => scripts/ci}/monitoring/grafana-dashboards/substrate-networking.json (100%) rename {.maintain => scripts/ci}/monitoring/grafana-dashboards/substrate-service-tasks.json (100%) rename {.maintain => scripts/ci}/node-template-release.sh (84%) rename {.maintain => scripts/ci}/node-template-release/Cargo.toml (100%) rename {.maintain => scripts/ci}/node-template-release/src/main.rs (100%) diff --git a/.github/workflows/check-labels.yml b/.github/workflows/check-labels.yml index 7180e7b509662..74fdd9b2d8188 100644 --- a/.github/workflows/check-labels.yml +++ b/.github/workflows/check-labels.yml @@ -14,7 +14,7 @@ jobs: ref: ${{ github.event.pull_request.head.ref }} repository: ${{ github.event.pull_request.head.repo.full_name }} - name: Check labels - run: bash ${{ github.workspace }}/.maintain/github/check_labels.sh + run: bash ${{ github.workspace }}/scripts/ci/github/check_labels.sh env: GITHUB_PR: ${{ github.event.pull_request.number }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/monthly-tag.yml b/.github/workflows/monthly-tag.yml index 8736a341cecf9..9fed865396013 100644 --- a/.github/workflows/monthly-tag.yml +++ b/.github/workflows/monthly-tag.yml @@ -29,7 +29,7 @@ jobs: echo "" >> Changelog.md echo "## Changes since last snapshot (${{ steps.tags.outputs.old }})" >> Changelog.md echo "" >> Changelog.md - ./.maintain/gitlab/generate_changelog.sh ${{ steps.tags.outputs.old }} >> Changelog.md + ./scripts/ci/github/generate_changelog.sh ${{ steps.tags.outputs.old }} >> Changelog.md - name: Release snapshot id: release-snapshot uses: actions/create-release@latest diff --git a/.gitignore b/.gitignore index f1103fdab93a5..5cd013e054e4f 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,4 @@ rls*.log .cargo-remote.toml *.bin *.iml -.maintain/node-template-release/Cargo.lock +scripts/ci/node-template-release/Cargo.lock diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8dabbc288cd62..b6f9ff9486069 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -18,7 +18,7 @@ # script: # - echo "List of shell commands to run in your job" # - echo "You can also just specify a script here, like so:" -# - ./.maintain/gitlab/my_amazing_script.sh +# - ./scripts/ci/gitlab/my_amazing_script.sh stages: - check @@ -165,12 +165,12 @@ default: fi .cargo-check-benches-script: &cargo-check-benches-script - - mkdir -p artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA + - mkdir -p ./artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA - SKIP_WASM_BUILD=1 time cargo +nightly check --benches --all - 'cargo run --release -p node-bench -- ::node::import::native::sr25519::transfer_keep_alive::paritydb::small --json - | tee artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA/::node::import::native::sr25519::transfer_keep_alive::paritydb::small.json' + | tee ./artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA/::node::import::native::sr25519::transfer_keep_alive::paritydb::small.json' - 'cargo run --release -p node-bench -- ::trie::read::small --json - | tee artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA/::trie::read::small.json' + | tee ./artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA/::trie::read::small.json' - sccache -s .build-linux-substrate-script: &build-linux-substrate-script @@ -185,7 +185,7 @@ default: tee ./artifacts/substrate/VERSION; fi - sha256sum ./artifacts/substrate/substrate | tee ./artifacts/substrate/substrate.sha256 - - cp -r .maintain/docker/substrate.Dockerfile ./artifacts/substrate/ + - cp -r ./scripts/ci/docker/substrate.Dockerfile ./artifacts/substrate/ - sccache -s #### Vault secrets @@ -241,7 +241,7 @@ skip-if-draft: - echo "Commit message is ${CI_COMMIT_MESSAGE}" - echo "Ref is ${CI_COMMIT_REF_NAME}" - echo "pipeline source is ${CI_PIPELINE_SOURCE}" - - ./.maintain/gitlab/skip_if_draft.sh + - ./scripts/ci/gitlab/skip_if_draft.sh #### stage: check @@ -256,7 +256,7 @@ check-runtime: GITLAB_API: "https://gitlab.parity.io/api/v4" GITHUB_API_PROJECT: "parity%2Finfrastructure%2Fgithub-api" script: - - ./.maintain/gitlab/check_runtime.sh + - ./scripts/ci/gitlab/check_runtime.sh allow_failure: true check-signed-tag: @@ -267,7 +267,7 @@ check-signed-tag: - if: $CI_COMMIT_REF_NAME =~ /^ci-release-.*$/ - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 script: - - ./.maintain/gitlab/check_signed.sh + - ./scripts/ci/gitlab/check_signed.sh test-dependency-rules: stage: check @@ -276,7 +276,7 @@ test-dependency-rules: rules: - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs script: - - .maintain/ensure-deps.sh + - ./scripts/ci/gitlab/ensure-deps.sh test-prometheus-alerting-rules: stage: check @@ -288,11 +288,11 @@ test-prometheus-alerting-rules: - if: $CI_COMMIT_BRANCH changes: - .gitlab-ci.yml - - .maintain/monitoring/**/* + - ./scripts/ci/monitoring/**/* script: - - promtool check rules .maintain/monitoring/alerting-rules/alerting-rules.yaml - - cat .maintain/monitoring/alerting-rules/alerting-rules.yaml | - promtool test rules .maintain/monitoring/alerting-rules/alerting-rule-tests.yaml + - promtool check rules ./scripts/ci/monitoring/alerting-rules/alerting-rules.yaml + - cat ./scripts/ci/monitoring/alerting-rules/alerting-rules.yaml | + promtool test rules ./scripts/ci/monitoring/alerting-rules/alerting-rule-tests.yaml #### stage: test @@ -301,10 +301,10 @@ cargo-deny: <<: *docker-env <<: *nightly-pipeline script: - - cargo deny check --hide-inclusion-graph -c .maintain/deny.toml + - cargo deny check --hide-inclusion-graph -c ./scripts/ci/deny.toml after_script: - echo "___The complete log is in the artifacts___" - - cargo deny check -c .maintain/deny.toml 2> deny.log + - cargo deny check -c ./scripts/ci/deny.toml 2> deny.log artifacts: name: $CI_COMMIT_SHORT_SHA expire_in: 3 days @@ -404,13 +404,13 @@ test-deterministic-wasm: # build runtime - cargo build --verbose --release -p node-runtime # make checksum - - sha256sum target/release/wbuild/node-runtime/target/wasm32-unknown-unknown/release/node_runtime.wasm > checksum.sha256 + - sha256sum ./target/release/wbuild/node-runtime/target/wasm32-unknown-unknown/release/node_runtime.wasm > checksum.sha256 # clean up – FIXME: can we reuse some of the artifacts? - cargo clean # build again - cargo build --verbose --release -p node-runtime # confirm checksum - - sha256sum -c checksum.sha256 + - sha256sum -c ./checksum.sha256 - sccache -s test-linux-stable: &test-linux @@ -426,8 +426,8 @@ test-linux-stable: &test-linux WASM_BUILD_NO_COLOR: 1 script: # this job runs all tests in former runtime-benchmarks, frame-staking and wasmtime tests - - time cargo test --workspace --locked --release --verbose --features runtime-benchmarks --manifest-path bin/node/cli/Cargo.toml - - time cargo test -p frame-support-test --features=conditional-storage,no-metadata-docs --manifest-path frame/support/test/Cargo.toml --test pallet # does not reuse cache 1 min 44 sec + - time cargo test --workspace --locked --release --verbose --features runtime-benchmarks --manifest-path ./bin/node/cli/Cargo.toml + - time cargo test -p frame-support-test --features=conditional-storage,no-metadata-docs --manifest-path ./frame/support/test/Cargo.toml --test pallet # does not reuse cache 1 min 44 sec - SUBSTRATE_TEST_TIMEOUT=1 time cargo test -p substrate-test-utils --release --verbose --locked -- --ignored timeout - sccache -s @@ -443,7 +443,7 @@ test-frame-examples-compile-to-wasm: RUSTFLAGS: "-Cdebug-assertions=y" RUST_BACKTRACE: 1 script: - - cd frame/examples/offchain-worker/ + - cd ./frame/examples/offchain-worker/ - cargo +nightly build --target=wasm32-unknown-unknown --no-default-features - cd ../basic - cargo +nightly build --target=wasm32-unknown-unknown --no-default-features @@ -475,8 +475,8 @@ check-tracing: <<: *test-refs script: # with-tracing must be explicitly activated, we run a test to ensure this works as expected in both cases - - time cargo +nightly test --manifest-path primitives/tracing/Cargo.toml --no-default-features - - time cargo +nightly test --manifest-path primitives/tracing/Cargo.toml --no-default-features --features=with-tracing + - time cargo +nightly test --manifest-path ./primitives/tracing/Cargo.toml --no-default-features + - time cargo +nightly test --manifest-path ./primitives/tracing/Cargo.toml --no-default-features --features=with-tracing - sccache -s test-full-crypto-feature: @@ -568,7 +568,7 @@ build-linux-substrate: script: - *build-linux-substrate-script - printf '\n# building node-template\n\n' - - ./.maintain/node-template-release.sh ./artifacts/substrate/substrate-node-template.tar.gz + - ./scripts/ci/node-template-release.sh ./artifacts/substrate/substrate-node-template.tar.gz build-linux-subkey: &build-subkey stage: build @@ -590,7 +590,7 @@ build-linux-subkey: &build-subkey sed -n -E 's/^subkey ([0-9.]+.*)/\1/p' | tee ./artifacts/subkey/VERSION; - sha256sum ./artifacts/subkey/subkey | tee ./artifacts/subkey/subkey.sha256 - - cp -r .maintain/docker/subkey.Dockerfile ./artifacts/subkey/ + - cp -r ./scripts/ci/docker/subkey.Dockerfile ./artifacts/subkey/ - sccache -s build-macos-subkey: @@ -784,7 +784,7 @@ publish-draft-release: - if: $CI_COMMIT_REF_NAME =~ /^ci-release-.*$/ - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 script: - - ./.maintain/gitlab/publish_draft_release.sh + - ./scripts/ci/gitlab/publish_draft_release.sh allow_failure: true #### stage: deploy @@ -807,4 +807,4 @@ deploy-prometheus-alerting-rules: - if: $CI_COMMIT_REF_NAME == "master" changes: - .gitlab-ci.yml - - .maintain/monitoring/**/* + - ./scripts/ci/monitoring/**/* diff --git a/docs/CODEOWNERS b/docs/CODEOWNERS index 70009d311f1c6..0b9e6e7783058 100644 --- a/docs/CODEOWNERS +++ b/docs/CODEOWNERS @@ -19,7 +19,7 @@ # - The latest matching rule, if multiple, takes precedence. # CI -/.maintain/ @paritytech/ci +/scripts/ci/ @paritytech/ci /.github/ @paritytech/ci /.gitlab-ci.yml @paritytech/ci diff --git a/docs/node-template-release.md b/docs/node-template-release.md index 25834ae99f438..4f4977a9df03c 100644 --- a/docs/node-template-release.md +++ b/docs/node-template-release.md @@ -7,7 +7,7 @@ the existence of your current git commit ID in the remote repository. Assume you are in root directory of Substrate. Run: ```bash - cd .maintain/ + cd scripts/ci/ ./node-template-release.sh ``` @@ -50,7 +50,7 @@ commit in Substrate remote repository, such as: ``` P.S: This step can be automated if we update `node-template-release` package in - `.maintain/node-template-release`. + `scripts/ci/node-template-release`. 4. Once the three `Cargo.toml`s are updated, compile and confirm that the Node Template builds. Then commit the changes to a new branch in [Substrate Node Template](https://github.com/substrate-developer-hub/substrate-node-template), and make a PR. diff --git a/.maintain/common/lib.sh b/scripts/ci/common/lib.sh similarity index 100% rename from .maintain/common/lib.sh rename to scripts/ci/common/lib.sh diff --git a/.maintain/deny.toml b/scripts/ci/deny.toml similarity index 100% rename from .maintain/deny.toml rename to scripts/ci/deny.toml diff --git a/.maintain/docker/subkey.Dockerfile b/scripts/ci/docker/subkey.Dockerfile similarity index 93% rename from .maintain/docker/subkey.Dockerfile rename to scripts/ci/docker/subkey.Dockerfile index 5797295806d00..3483502845cf5 100644 --- a/.maintain/docker/subkey.Dockerfile +++ b/scripts/ci/docker/subkey.Dockerfile @@ -8,7 +8,7 @@ LABEL io.parity.image.authors="devops-team@parity.io" \ io.parity.image.vendor="Parity Technologies" \ io.parity.image.title="parity/subkey" \ io.parity.image.description="Subkey: key generating utility for Substrate." \ - io.parity.image.source="https://github.com/paritytech/substrate/blob/${VCS_REF}/.maintain/docker/subkey.Dockerfile" \ + io.parity.image.source="https://github.com/paritytech/substrate/blob/${VCS_REF}/scripts/ci/docker/subkey.Dockerfile" \ io.parity.image.revision="${VCS_REF}" \ io.parity.image.created="${BUILD_DATE}" \ io.parity.image.documentation="https://github.com/paritytech/substrate/tree/${VCS_REF}/subkey" diff --git a/.maintain/docker/substrate.Dockerfile b/scripts/ci/docker/substrate.Dockerfile similarity index 96% rename from .maintain/docker/substrate.Dockerfile rename to scripts/ci/docker/substrate.Dockerfile index e13dfb426adfd..b4c103ed5244b 100644 --- a/.maintain/docker/substrate.Dockerfile +++ b/scripts/ci/docker/substrate.Dockerfile @@ -8,7 +8,7 @@ LABEL io.parity.image.authors="devops-team@parity.io" \ io.parity.image.vendor="Parity Technologies" \ io.parity.image.title="parity/substrate" \ io.parity.image.description="Substrate: The platform for blockchain innovators." \ - io.parity.image.source="https://github.com/paritytech/substrate/blob/${VCS_REF}/.maintain/docker/Dockerfile" \ + io.parity.image.source="https://github.com/paritytech/substrate/blob/${VCS_REF}/scripts/ci/docker/Dockerfile" \ io.parity.image.revision="${VCS_REF}" \ io.parity.image.created="${BUILD_DATE}" \ io.parity.image.documentation="https://wiki.parity.io/Parity-Substrate" diff --git a/.maintain/github/check_labels.sh b/scripts/ci/github/check_labels.sh similarity index 100% rename from .maintain/github/check_labels.sh rename to scripts/ci/github/check_labels.sh diff --git a/.maintain/gitlab/generate_changelog.sh b/scripts/ci/github/generate_changelog.sh similarity index 100% rename from .maintain/gitlab/generate_changelog.sh rename to scripts/ci/github/generate_changelog.sh diff --git a/.maintain/gitlab/check_runtime.sh b/scripts/ci/gitlab/check_runtime.sh similarity index 100% rename from .maintain/gitlab/check_runtime.sh rename to scripts/ci/gitlab/check_runtime.sh diff --git a/.maintain/gitlab/check_signed.sh b/scripts/ci/gitlab/check_signed.sh similarity index 100% rename from .maintain/gitlab/check_signed.sh rename to scripts/ci/gitlab/check_signed.sh diff --git a/.maintain/ensure-deps.sh b/scripts/ci/gitlab/ensure-deps.sh similarity index 100% rename from .maintain/ensure-deps.sh rename to scripts/ci/gitlab/ensure-deps.sh diff --git a/.maintain/gitlab/publish_draft_release.sh b/scripts/ci/gitlab/publish_draft_release.sh similarity index 100% rename from .maintain/gitlab/publish_draft_release.sh rename to scripts/ci/gitlab/publish_draft_release.sh diff --git a/.maintain/gitlab/skip_if_draft.sh b/scripts/ci/gitlab/skip_if_draft.sh similarity index 100% rename from .maintain/gitlab/skip_if_draft.sh rename to scripts/ci/gitlab/skip_if_draft.sh diff --git a/.maintain/monitoring/alerting-rules/alerting-rule-tests.yaml b/scripts/ci/monitoring/alerting-rules/alerting-rule-tests.yaml similarity index 100% rename from .maintain/monitoring/alerting-rules/alerting-rule-tests.yaml rename to scripts/ci/monitoring/alerting-rules/alerting-rule-tests.yaml diff --git a/.maintain/monitoring/alerting-rules/alerting-rules.yaml b/scripts/ci/monitoring/alerting-rules/alerting-rules.yaml similarity index 100% rename from .maintain/monitoring/alerting-rules/alerting-rules.yaml rename to scripts/ci/monitoring/alerting-rules/alerting-rules.yaml diff --git a/.maintain/monitoring/grafana-dashboards/README_dashboard.md b/scripts/ci/monitoring/grafana-dashboards/README_dashboard.md similarity index 100% rename from .maintain/monitoring/grafana-dashboards/README_dashboard.md rename to scripts/ci/monitoring/grafana-dashboards/README_dashboard.md diff --git a/.maintain/monitoring/grafana-dashboards/substrate-networking.json b/scripts/ci/monitoring/grafana-dashboards/substrate-networking.json similarity index 100% rename from .maintain/monitoring/grafana-dashboards/substrate-networking.json rename to scripts/ci/monitoring/grafana-dashboards/substrate-networking.json diff --git a/.maintain/monitoring/grafana-dashboards/substrate-service-tasks.json b/scripts/ci/monitoring/grafana-dashboards/substrate-service-tasks.json similarity index 100% rename from .maintain/monitoring/grafana-dashboards/substrate-service-tasks.json rename to scripts/ci/monitoring/grafana-dashboards/substrate-service-tasks.json diff --git a/.maintain/node-template-release.sh b/scripts/ci/node-template-release.sh similarity index 84% rename from .maintain/node-template-release.sh rename to scripts/ci/node-template-release.sh index cb5e72e7fa98f..09ef98e04627a 100755 --- a/.maintain/node-template-release.sh +++ b/scripts/ci/node-template-release.sh @@ -11,6 +11,6 @@ if [ "$#" -ne 1 ]; then fi PATH_TO_ARCHIVE=$1 -cd $PROJECT_ROOT/.maintain/node-template-release +cd $PROJECT_ROOT/scripts/ci/node-template-release cargo run $PROJECT_ROOT/bin/node-template $PROJECT_ROOT/$PATH_TO_ARCHIVE diff --git a/.maintain/node-template-release/Cargo.toml b/scripts/ci/node-template-release/Cargo.toml similarity index 100% rename from .maintain/node-template-release/Cargo.toml rename to scripts/ci/node-template-release/Cargo.toml diff --git a/.maintain/node-template-release/src/main.rs b/scripts/ci/node-template-release/src/main.rs similarity index 100% rename from .maintain/node-template-release/src/main.rs rename to scripts/ci/node-template-release/src/main.rs From 54b75d104dd090cea44d51d622e00e709a8c58b6 Mon Sep 17 00:00:00 2001 From: Georges Date: Mon, 14 Mar 2022 10:00:41 +0000 Subject: [PATCH 016/484] Move `sp-npos-elections-solution-type` to `frame-election-provider-support` (#11016) * Move `sp-npos-elections-solution-type` to `frame-election-provider-support` First stab at it, will need to amend some more stuff * Fixing tests * Fixing tests * Fixing cargo.toml for std configuration * fmt * Committing suggested changes renaming, and re exporting macro. * Removing unneeded imports --- Cargo.lock | 47 ++- Cargo.toml | 3 +- bin/node/runtime/src/lib.rs | 2 +- .../election-provider-multi-phase/Cargo.toml | 3 +- .../election-provider-multi-phase/src/mock.rs | 2 +- frame/election-provider-support/Cargo.toml | 1 + .../solution-type/Cargo.toml | 6 +- .../solution-type/fuzzer/Cargo.toml | 30 ++ .../solution-type}/fuzzer/src/compact.rs | 3 +- .../solution-type/src/codec.rs | 0 .../src/from_assignment_helpers.rs | 0 .../solution-type/src/index_assignment.rs | 0 .../solution-type/src/lib.rs | 4 +- .../solution-type/src/mock.rs | 178 +++++++++ .../solution-type/src/single_page.rs | 0 .../solution-type/src/tests.rs | 350 ++++++++++++++++++ .../tests/ui/fail/missing_accuracy.rs | 2 +- .../tests/ui/fail/missing_accuracy.stderr | 0 .../tests/ui/fail/missing_target.rs | 2 +- .../tests/ui/fail/missing_target.stderr | 0 .../tests/ui/fail/missing_voter.rs | 2 +- .../tests/ui/fail/missing_voter.stderr | 0 .../tests/ui/fail/no_annotations.rs | 2 +- .../tests/ui/fail/no_annotations.stderr | 0 .../tests/ui/fail/swap_voter_target.rs | 2 +- .../tests/ui/fail/swap_voter_target.stderr | 0 .../tests/ui/fail/wrong_attribute.rs | 2 +- .../tests/ui/fail/wrong_attribute.stderr | 0 frame/election-provider-support/src/lib.rs | 1 + frame/staking/src/lib.rs | 6 +- primitives/npos-elections/Cargo.toml | 1 - primitives/npos-elections/fuzzer/Cargo.toml | 4 - primitives/npos-elections/src/lib.rs | 7 +- primitives/npos-elections/src/mock.rs | 161 -------- primitives/npos-elections/src/tests.rs | 331 +---------------- 35 files changed, 616 insertions(+), 536 deletions(-) rename {primitives/npos-elections => frame/election-provider-support}/solution-type/Cargo.toml (73%) create mode 100644 frame/election-provider-support/solution-type/fuzzer/Cargo.toml rename {primitives/npos-elections => frame/election-provider-support/solution-type}/fuzzer/src/compact.rs (94%) rename {primitives/npos-elections => frame/election-provider-support}/solution-type/src/codec.rs (100%) rename {primitives/npos-elections => frame/election-provider-support}/solution-type/src/from_assignment_helpers.rs (100%) rename {primitives/npos-elections => frame/election-provider-support}/solution-type/src/index_assignment.rs (100%) rename {primitives/npos-elections => frame/election-provider-support}/solution-type/src/lib.rs (98%) create mode 100644 frame/election-provider-support/solution-type/src/mock.rs rename {primitives/npos-elections => frame/election-provider-support}/solution-type/src/single_page.rs (100%) create mode 100644 frame/election-provider-support/solution-type/src/tests.rs rename {primitives/npos-elections => frame/election-provider-support}/solution-type/tests/ui/fail/missing_accuracy.rs (64%) rename {primitives/npos-elections => frame/election-provider-support}/solution-type/tests/ui/fail/missing_accuracy.stderr (100%) rename {primitives/npos-elections => frame/election-provider-support}/solution-type/tests/ui/fail/missing_target.rs (63%) rename {primitives/npos-elections => frame/election-provider-support}/solution-type/tests/ui/fail/missing_target.stderr (100%) rename {primitives/npos-elections => frame/election-provider-support}/solution-type/tests/ui/fail/missing_voter.rs (63%) rename {primitives/npos-elections => frame/election-provider-support}/solution-type/tests/ui/fail/missing_voter.stderr (100%) rename {primitives/npos-elections => frame/election-provider-support}/solution-type/tests/ui/fail/no_annotations.rs (58%) rename {primitives/npos-elections => frame/election-provider-support}/solution-type/tests/ui/fail/no_annotations.stderr (100%) rename {primitives/npos-elections => frame/election-provider-support}/solution-type/tests/ui/fail/swap_voter_target.rs (66%) rename {primitives/npos-elections => frame/election-provider-support}/solution-type/tests/ui/fail/swap_voter_target.stderr (100%) rename {primitives/npos-elections => frame/election-provider-support}/solution-type/tests/ui/fail/wrong_attribute.rs (69%) rename {primitives/npos-elections => frame/election-provider-support}/solution-type/tests/ui/fail/wrong_attribute.stderr (100%) diff --git a/Cargo.lock b/Cargo.lock index 1ad59291eca27..9f4d2f21cf9c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2160,10 +2160,26 @@ dependencies = [ "sp-trie", ] +[[package]] +name = "frame-election-provider-solution-type" +version = "4.0.0-dev" +dependencies = [ + "parity-scale-codec", + "proc-macro-crate 1.1.0", + "proc-macro2", + "quote", + "scale-info", + "sp-arithmetic", + "sp-npos-elections", + "syn", + "trybuild", +] + [[package]] name = "frame-election-provider-support" version = "4.0.0-dev" dependencies = [ + "frame-election-provider-solution-type", "frame-support", "frame-system", "parity-scale-codec", @@ -2176,6 +2192,21 @@ dependencies = [ "sp-std", ] +[[package]] +name = "frame-election-solution-type-fuzzer" +version = "2.0.0-alpha.5" +dependencies = [ + "clap 3.0.7", + "frame-election-provider-solution-type", + "honggfuzz", + "parity-scale-codec", + "rand 0.8.4", + "scale-info", + "sp-arithmetic", + "sp-npos-elections", + "sp-runtime", +] + [[package]] name = "frame-executive" version = "4.0.0-dev" @@ -10058,7 +10089,6 @@ dependencies = [ "serde", "sp-arithmetic", "sp-core", - "sp-npos-elections-solution-type", "sp-runtime", "sp-std", "substrate-test-utils", @@ -10077,21 +10107,6 @@ dependencies = [ "sp-runtime", ] -[[package]] -name = "sp-npos-elections-solution-type" -version = "4.0.0-dev" -dependencies = [ - "parity-scale-codec", - "proc-macro-crate 1.1.0", - "proc-macro2", - "quote", - "scale-info", - "sp-arithmetic", - "sp-npos-elections", - "syn", - "trybuild", -] - [[package]] name = "sp-offchain" version = "4.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index f24ff6d04980a..bce23456b27e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,6 +86,8 @@ members = [ "frame/try-runtime", "frame/election-provider-multi-phase", "frame/election-provider-support", + "frame/election-provider-support/solution-type", + "frame/election-provider-support/solution-type/fuzzer", "frame/examples/basic", "frame/examples/offchain-worker", "frame/examples/parallel", @@ -169,7 +171,6 @@ members = [ "primitives/keystore", "primitives/maybe-compressed-blob", "primitives/npos-elections", - "primitives/npos-elections/solution-type", "primitives/npos-elections/fuzzer", "primitives/offchain", "primitives/panic-handler", diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 8c7a20af15683..20b718e2fa8f7 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -588,7 +588,7 @@ parameter_types! { .get(DispatchClass::Normal); } -sp_npos_elections::generate_solution_type!( +frame_election_provider_support::generate_solution_type!( #[compact] pub struct NposSolution16::< VoterIndex = u32, diff --git a/frame/election-provider-multi-phase/Cargo.toml b/frame/election-provider-multi-phase/Cargo.toml index 38039f6926b15..25f98d965d86b 100644 --- a/frame/election-provider-multi-phase/Cargo.toml +++ b/frame/election-provider-multi-phase/Cargo.toml @@ -46,8 +46,7 @@ sp-core = { version = "6.0.0", default-features = false, path = "../../primitive sp-io = { version = "6.0.0", path = "../../primitives/io" } sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../primitives/npos-elections" } sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } -frame-election-provider-support = { version = "4.0.0-dev", features = [ -], path = "../election-provider-support" } +frame-election-provider-support = { version = "4.0.0-dev", path = "../election-provider-support" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } frame-benchmarking = { version = "4.0.0-dev", path = "../benchmarking" } diff --git a/frame/election-provider-multi-phase/src/mock.rs b/frame/election-provider-multi-phase/src/mock.rs index 7c7034ac91a83..7c4ef5d8055c3 100644 --- a/frame/election-provider-multi-phase/src/mock.rs +++ b/frame/election-provider-multi-phase/src/mock.rs @@ -68,7 +68,7 @@ pub(crate) type BlockNumber = u64; pub(crate) type VoterIndex = u32; pub(crate) type TargetIndex = u16; -sp_npos_elections::generate_solution_type!( +frame_election_provider_support::generate_solution_type!( #[compact] pub struct TestNposSolution::(16) ); diff --git a/frame/election-provider-support/Cargo.toml b/frame/election-provider-support/Cargo.toml index b95bd994fa70a..16b79dbb098d4 100644 --- a/frame/election-provider-support/Cargo.toml +++ b/frame/election-provider-support/Cargo.toml @@ -21,6 +21,7 @@ sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = ". sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +frame-election-provider-solution-type = { version = "4.0.0-dev", path = "solution-type" } [dev-dependencies] sp-npos-elections = { version = "4.0.0-dev", path = "../../primitives/npos-elections" } diff --git a/primitives/npos-elections/solution-type/Cargo.toml b/frame/election-provider-support/solution-type/Cargo.toml similarity index 73% rename from primitives/npos-elections/solution-type/Cargo.toml rename to frame/election-provider-support/solution-type/Cargo.toml index 813637e89c338..d489c7b4c10de 100644 --- a/primitives/npos-elections/solution-type/Cargo.toml +++ b/frame/election-provider-support/solution-type/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "sp-npos-elections-solution-type" +name = "frame-election-provider-solution-type" version = "4.0.0-dev" authors = ["Parity Technologies "] edition = "2021" @@ -23,7 +23,7 @@ proc-macro-crate = "1.1.0" [dev-dependencies] parity-scale-codec = "3.0.0" scale-info = "2.0.1" -sp-arithmetic = { path = "../../arithmetic", version = "5.0.0"} +sp-arithmetic = { version = "5.0.0", path = "../../../primitives/arithmetic" } # used by generate_solution_type: -sp-npos-elections = { path = "..", version = "4.0.0-dev" } +sp-npos-elections = { version = "4.0.0-dev", path = "../../../primitives/npos-elections" } trybuild = "1.0.53" diff --git a/frame/election-provider-support/solution-type/fuzzer/Cargo.toml b/frame/election-provider-support/solution-type/fuzzer/Cargo.toml new file mode 100644 index 0000000000000..f52b7f7332620 --- /dev/null +++ b/frame/election-provider-support/solution-type/fuzzer/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "frame-election-solution-type-fuzzer" +version = "2.0.0-alpha.5" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Fuzzer for phragmén solution type implementation." +publish = false + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +clap = { version = "3.0", features = ["derive"] } +honggfuzz = "0.5" +rand = { version = "0.8", features = ["std", "small_rng"] } + +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +frame-election-provider-solution-type = { version = "4.0.0-dev", path = ".." } +sp-arithmetic = { version = "5.0.0", path = "../../../../primitives/arithmetic" } +sp-runtime = { version = "6.0.0", path = "../../../../primitives/runtime" } +# used by generate_solution_type: +sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../../../primitives/npos-elections" } + +[[bin]] +name = "compact" +path = "src/compact.rs" diff --git a/primitives/npos-elections/fuzzer/src/compact.rs b/frame/election-provider-support/solution-type/fuzzer/src/compact.rs similarity index 94% rename from primitives/npos-elections/fuzzer/src/compact.rs rename to frame/election-provider-support/solution-type/fuzzer/src/compact.rs index 595048575d99c..501d241b2b80b 100644 --- a/primitives/npos-elections/fuzzer/src/compact.rs +++ b/frame/election-provider-support/solution-type/fuzzer/src/compact.rs @@ -1,5 +1,6 @@ +use frame_election_provider_solution_type::generate_solution_type; use honggfuzz::fuzz; -use sp_npos_elections::{generate_solution_type, sp_arithmetic::Percent}; +use sp_arithmetic::Percent; use sp_runtime::codec::{Encode, Error}; fn main() { diff --git a/primitives/npos-elections/solution-type/src/codec.rs b/frame/election-provider-support/solution-type/src/codec.rs similarity index 100% rename from primitives/npos-elections/solution-type/src/codec.rs rename to frame/election-provider-support/solution-type/src/codec.rs diff --git a/primitives/npos-elections/solution-type/src/from_assignment_helpers.rs b/frame/election-provider-support/solution-type/src/from_assignment_helpers.rs similarity index 100% rename from primitives/npos-elections/solution-type/src/from_assignment_helpers.rs rename to frame/election-provider-support/solution-type/src/from_assignment_helpers.rs diff --git a/primitives/npos-elections/solution-type/src/index_assignment.rs b/frame/election-provider-support/solution-type/src/index_assignment.rs similarity index 100% rename from primitives/npos-elections/solution-type/src/index_assignment.rs rename to frame/election-provider-support/solution-type/src/index_assignment.rs diff --git a/primitives/npos-elections/solution-type/src/lib.rs b/frame/election-provider-support/solution-type/src/lib.rs similarity index 98% rename from primitives/npos-elections/solution-type/src/lib.rs rename to frame/election-provider-support/solution-type/src/lib.rs index 6e632d19e171e..3de923cdffd03 100644 --- a/primitives/npos-elections/solution-type/src/lib.rs +++ b/frame/election-provider-support/solution-type/src/lib.rs @@ -57,7 +57,7 @@ pub(crate) fn syn_err(message: &'static str) -> syn::Error { /// type, `u8` target type and `Perbill` accuracy with maximum of 4 edges per voter. /// /// ``` -/// # use sp_npos_elections_solution_type::generate_solution_type; +/// # use frame_election_provider_solution_type::generate_solution_type; /// # use sp_arithmetic::per_things::Perbill; /// generate_solution_type!(pub struct TestSolution::< /// VoterIndex = u16, @@ -100,7 +100,7 @@ pub(crate) fn syn_err(message: &'static str) -> syn::Error { /// for numbers will be used, similar to how `parity-scale-codec`'s `Compact` works. /// /// ``` -/// # use sp_npos_elections_solution_type::generate_solution_type; +/// # use frame_election_provider_solution_type::generate_solution_type; /// # use sp_npos_elections::NposSolution; /// # use sp_arithmetic::per_things::Perbill; /// generate_solution_type!( diff --git a/frame/election-provider-support/solution-type/src/mock.rs b/frame/election-provider-support/solution-type/src/mock.rs new file mode 100644 index 0000000000000..c3d032f2eb257 --- /dev/null +++ b/frame/election-provider-support/solution-type/src/mock.rs @@ -0,0 +1,178 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Mock file for solution-type. + +#![cfg(test)] + +use std::{collections::HashMap, convert::TryInto, hash::Hash, HashSet}; + +use rand::{seq::SliceRandom, Rng}; + +/// The candidate mask allows easy disambiguation between voters and candidates: accounts +/// for which this bit is set are candidates, and without it, are voters. +pub const CANDIDATE_MASK: AccountId = 1 << ((std::mem::size_of::() * 8) - 1); + +pub type TestAccuracy = sp_runtime::Perbill; + +pub fn p(p: u8) -> TestAccuracy { + TestAccuracy::from_percent(p.into()) +} + +pub type MockAssignment = crate::Assignment; +pub type Voter = (AccountId, VoteWeight, Vec); + +crate::generate_solution_type! { + pub struct TestSolution::< + VoterIndex = u32, + TargetIndex = u16, + Accuracy = TestAccuracy, + >(16) +} + +/// Generate voter and assignment lists. Makes no attempt to be realistic about winner or assignment +/// fairness. +/// +/// Maintains these invariants: +/// +/// - candidate ids have `CANDIDATE_MASK` bit set +/// - voter ids do not have `CANDIDATE_MASK` bit set +/// - assignments have the same ordering as voters +/// - `assignments.distribution.iter().map(|(_, frac)| frac).sum() == One::one()` +/// - a coherent set of winners is chosen. +/// - the winner set is a subset of the candidate set. +/// - `assignments.distribution.iter().all(|(who, _)| winners.contains(who))` +pub fn generate_random_votes( + candidate_count: usize, + voter_count: usize, + mut rng: impl Rng, +) -> (Vec, Vec, Vec) { + // cache for fast generation of unique candidate and voter ids + let mut used_ids = HashSet::with_capacity(candidate_count + voter_count); + + // candidates are easy: just a completely random set of IDs + let mut candidates: Vec = Vec::with_capacity(candidate_count); + while candidates.len() < candidate_count { + let mut new = || rng.gen::() | CANDIDATE_MASK; + let mut id = new(); + // insert returns `false` when the value was already present + while !used_ids.insert(id) { + id = new(); + } + candidates.push(id); + } + + // voters are random ids, random weights, random selection from the candidates + let mut voters = Vec::with_capacity(voter_count); + while voters.len() < voter_count { + let mut new = || rng.gen::() & !CANDIDATE_MASK; + let mut id = new(); + // insert returns `false` when the value was already present + while !used_ids.insert(id) { + id = new(); + } + + let vote_weight = rng.gen(); + + // it's not interesting if a voter chooses 0 or all candidates, so rule those cases out. + // also, let's not generate any cases which result in a compact overflow. + let n_candidates_chosen = + rng.gen_range(1, candidates.len().min(::LIMIT)); + + let mut chosen_candidates = Vec::with_capacity(n_candidates_chosen); + chosen_candidates.extend(candidates.choose_multiple(&mut rng, n_candidates_chosen)); + voters.push((id, vote_weight, chosen_candidates)); + } + + // always generate a sensible number of winners: elections are uninteresting if nobody wins, + // or everybody wins + let num_winners = rng.gen_range(1, candidate_count); + let mut winners: HashSet = HashSet::with_capacity(num_winners); + winners.extend(candidates.choose_multiple(&mut rng, num_winners)); + assert_eq!(winners.len(), num_winners); + + let mut assignments = Vec::with_capacity(voters.len()); + for (voter_id, _, votes) in voters.iter() { + let chosen_winners = votes.iter().filter(|vote| winners.contains(vote)).cloned(); + let num_chosen_winners = chosen_winners.clone().count(); + + // distribute the available stake randomly + let stake_distribution = if num_chosen_winners == 0 { + continue + } else { + let mut available_stake = 1000; + let mut stake_distribution = Vec::with_capacity(num_chosen_winners); + for _ in 0..num_chosen_winners - 1 { + let stake = rng.gen_range(0, available_stake).min(1); + stake_distribution.push(TestAccuracy::from_perthousand(stake)); + available_stake -= stake; + } + stake_distribution.push(TestAccuracy::from_perthousand(available_stake)); + stake_distribution.shuffle(&mut rng); + stake_distribution + }; + + assignments.push(MockAssignment { + who: *voter_id, + distribution: chosen_winners.zip(stake_distribution).collect(), + }); + } + + (voters, assignments, candidates) +} + +fn generate_cache(voters: Voters) -> HashMap +where + Voters: Iterator, + Item: Hash + Eq + Copy, +{ + let mut cache = HashMap::new(); + for (idx, voter_id) in voters.enumerate() { + cache.insert(voter_id, idx); + } + cache +} + +/// Create a function that returns the index of a voter in the voters list. +pub fn make_voter_fn(voters: &[Voter]) -> impl Fn(&AccountId) -> Option +where + usize: TryInto, +{ + let cache = generate_cache(voters.iter().map(|(id, _, _)| *id)); + move |who| { + if cache.get(who).is_none() { + println!("WARNING: voter {} will raise InvalidIndex", who); + } + cache.get(who).cloned().and_then(|i| i.try_into().ok()) + } +} + +/// Create a function that returns the index of a candidate in the candidates list. +pub fn make_target_fn( + candidates: &[AccountId], +) -> impl Fn(&AccountId) -> Option +where + usize: TryInto, +{ + let cache = generate_cache(candidates.iter().cloned()); + move |who| { + if cache.get(who).is_none() { + println!("WARNING: target {} will raise InvalidIndex", who); + } + cache.get(who).cloned().and_then(|i| i.try_into().ok()) + } +} diff --git a/primitives/npos-elections/solution-type/src/single_page.rs b/frame/election-provider-support/solution-type/src/single_page.rs similarity index 100% rename from primitives/npos-elections/solution-type/src/single_page.rs rename to frame/election-provider-support/solution-type/src/single_page.rs diff --git a/frame/election-provider-support/solution-type/src/tests.rs b/frame/election-provider-support/solution-type/src/tests.rs new file mode 100644 index 0000000000000..f173e425b5187 --- /dev/null +++ b/frame/election-provider-support/solution-type/src/tests.rs @@ -0,0 +1,350 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Tests for solution-type. + +#![cfg(test)] + +use crate::{mock::*, IndexAssignment, NposSolution}; +use rand::SeedableRng; +use std::convert::TryInto; + +mod solution_type { + use super::*; + use codec::{Decode, Encode}; + // these need to come from the same dev-dependency `sp-npos-elections`, not from the crate. + use crate::{generate_solution_type, Assignment, Error as NposError, NposSolution}; + use sp_std::{convert::TryInto, fmt::Debug}; + + #[allow(dead_code)] + mod __private { + // This is just to make sure that the solution can be generated in a scope without any + // imports. + use crate::generate_solution_type; + generate_solution_type!( + #[compact] + struct InnerTestSolutionIsolated::(12) + ); + } + + #[test] + fn solution_struct_works_with_and_without_compact() { + // we use u32 size to make sure compact is smaller. + let without_compact = { + generate_solution_type!( + pub struct InnerTestSolution::< + VoterIndex = u32, + TargetIndex = u32, + Accuracy = TestAccuracy, + >(16) + ); + let solution = InnerTestSolution { + votes1: vec![(2, 20), (4, 40)], + votes2: vec![(1, [(10, p(80))], 11), (5, [(50, p(85))], 51)], + ..Default::default() + }; + + solution.encode().len() + }; + + let with_compact = { + generate_solution_type!( + #[compact] + pub struct InnerTestSolutionCompact::< + VoterIndex = u32, + TargetIndex = u32, + Accuracy = TestAccuracy, + >(16) + ); + let compact = InnerTestSolutionCompact { + votes1: vec![(2, 20), (4, 40)], + votes2: vec![(1, [(10, p(80))], 11), (5, [(50, p(85))], 51)], + ..Default::default() + }; + + compact.encode().len() + }; + + assert!(with_compact < without_compact); + } + + #[test] + fn solution_struct_is_codec() { + let solution = TestSolution { + votes1: vec![(2, 20), (4, 40)], + votes2: vec![(1, [(10, p(80))], 11), (5, [(50, p(85))], 51)], + ..Default::default() + }; + + let encoded = solution.encode(); + + assert_eq!(solution, Decode::decode(&mut &encoded[..]).unwrap()); + assert_eq!(solution.voter_count(), 4); + assert_eq!(solution.edge_count(), 2 + 4); + assert_eq!(solution.unique_targets(), vec![10, 11, 20, 40, 50, 51]); + } + + #[test] + fn remove_voter_works() { + let mut solution = TestSolution { + votes1: vec![(0, 2), (1, 6)], + votes2: vec![(2, [(0, p(80))], 1), (3, [(7, p(85))], 8)], + votes3: vec![(4, [(3, p(50)), (4, p(25))], 5)], + ..Default::default() + }; + + assert!(!solution.remove_voter(11)); + assert!(solution.remove_voter(2)); + assert_eq!( + solution, + TestSolution { + votes1: vec![(0, 2), (1, 6)], + votes2: vec![(3, [(7, p(85))], 8)], + votes3: vec![(4, [(3, p(50)), (4, p(25))], 5,)], + ..Default::default() + }, + ); + + assert!(solution.remove_voter(4)); + assert_eq!( + solution, + TestSolution { + votes1: vec![(0, 2), (1, 6)], + votes2: vec![(3, [(7, p(85))], 8)], + ..Default::default() + }, + ); + + assert!(solution.remove_voter(1)); + assert_eq!( + solution, + TestSolution { + votes1: vec![(0, 2)], + votes2: vec![(3, [(7, p(85))], 8),], + ..Default::default() + }, + ); + } + + #[test] + fn from_and_into_assignment_works() { + let voters = vec![2 as AccountId, 4, 1, 5, 3]; + let targets = vec![ + 10 as AccountId, + 11, + 20, // 2 + 30, + 31, // 4 + 32, + 40, // 6 + 50, + 51, // 8 + ]; + + let assignments = vec![ + Assignment { who: 2 as AccountId, distribution: vec![(20u64, p(100))] }, + Assignment { who: 4, distribution: vec![(40, p(100))] }, + Assignment { who: 1, distribution: vec![(10, p(80)), (11, p(20))] }, + Assignment { who: 5, distribution: vec![(50, p(85)), (51, p(15))] }, + Assignment { who: 3, distribution: vec![(30, p(50)), (31, p(25)), (32, p(25))] }, + ]; + + let voter_index = |a: &AccountId| -> Option { + voters.iter().position(|x| x == a).map(TryInto::try_into).unwrap().ok() + }; + let target_index = |a: &AccountId| -> Option { + targets.iter().position(|x| x == a).map(TryInto::try_into).unwrap().ok() + }; + + let solution = + TestSolution::from_assignment(&assignments, voter_index, target_index).unwrap(); + + // basically number of assignments that it is encoding. + assert_eq!(solution.voter_count(), assignments.len()); + assert_eq!( + solution.edge_count(), + assignments.iter().fold(0, |a, b| a + b.distribution.len()), + ); + + assert_eq!( + solution, + TestSolution { + votes1: vec![(0, 2), (1, 6)], + votes2: vec![(2, [(0, p(80))], 1), (3, [(7, p(85))], 8)], + votes3: vec![(4, [(3, p(50)), (4, p(25))], 5)], + ..Default::default() + } + ); + + assert_eq!(solution.unique_targets(), vec![0, 1, 2, 3, 4, 5, 6, 7, 8]); + + let voter_at = |a: u32| -> Option { + voters.get(>::try_into(a).unwrap()).cloned() + }; + let target_at = |a: u16| -> Option { + targets.get(>::try_into(a).unwrap()).cloned() + }; + + assert_eq!(solution.into_assignment(voter_at, target_at).unwrap(), assignments); + } + + #[test] + fn unique_targets_len_edge_count_works() { + // we don't really care about voters here so all duplicates. This is not invalid per se. + let solution = TestSolution { + votes1: vec![(99, 1), (99, 2)], + votes2: vec![(99, [(3, p(10))], 7), (99, [(4, p(10))], 8)], + votes3: vec![(99, [(11, p(10)), (12, p(10))], 13)], + // ensure the last one is also counted. + votes16: vec![( + 99, + [ + (66, p(10)), + (66, p(10)), + (66, p(10)), + (66, p(10)), + (66, p(10)), + (66, p(10)), + (66, p(10)), + (66, p(10)), + (66, p(10)), + (66, p(10)), + (66, p(10)), + (66, p(10)), + (66, p(10)), + (66, p(10)), + (66, p(10)), + ], + 67, + )], + ..Default::default() + }; + + assert_eq!(solution.unique_targets(), vec![1, 2, 3, 4, 7, 8, 11, 12, 13, 66, 67]); + assert_eq!(solution.edge_count(), 2 + (2 * 2) + 3 + 16); + assert_eq!(solution.voter_count(), 6); + + // this one has some duplicates. + let solution = TestSolution { + votes1: vec![(99, 1), (99, 1)], + votes2: vec![(99, [(3, p(10))], 7), (99, [(4, p(10))], 8)], + votes3: vec![(99, [(11, p(10)), (11, p(10))], 13)], + ..Default::default() + }; + + assert_eq!(solution.unique_targets(), vec![1, 3, 4, 7, 8, 11, 13]); + assert_eq!(solution.edge_count(), 2 + (2 * 2) + 3); + assert_eq!(solution.voter_count(), 5); + } + + #[test] + fn solution_into_assignment_must_report_overflow() { + // in votes2 + let solution = TestSolution { + votes1: Default::default(), + votes2: vec![(0, [(1, p(100))], 2)], + ..Default::default() + }; + + let voter_at = |a: u32| -> Option { Some(a as AccountId) }; + let target_at = |a: u16| -> Option { Some(a as AccountId) }; + + assert_eq!( + solution.into_assignment(&voter_at, &target_at).unwrap_err(), + NposError::SolutionWeightOverflow, + ); + + // in votes3 onwards + let solution = TestSolution { + votes1: Default::default(), + votes2: Default::default(), + votes3: vec![(0, [(1, p(70)), (2, p(80))], 3)], + ..Default::default() + }; + + assert_eq!( + solution.into_assignment(&voter_at, &target_at).unwrap_err(), + NposError::SolutionWeightOverflow, + ); + } + + #[test] + fn target_count_overflow_is_detected() { + let voter_index = |a: &AccountId| -> Option { Some(*a as u32) }; + let target_index = |a: &AccountId| -> Option { Some(*a as u16) }; + + let assignments = vec![Assignment { + who: 1 as AccountId, + distribution: (10..27).map(|i| (i as AccountId, p(i as u8))).collect::>(), + }]; + + let solution = TestSolution::from_assignment(&assignments, voter_index, target_index); + assert_eq!(solution.unwrap_err(), NposError::SolutionTargetOverflow); + } + + #[test] + fn zero_target_count_is_ignored() { + let voters = vec![1 as AccountId, 2]; + let targets = vec![10 as AccountId, 11]; + + let assignments = vec![ + Assignment { who: 1 as AccountId, distribution: vec![(10, p(50)), (11, p(50))] }, + Assignment { who: 2, distribution: vec![] }, + ]; + + let voter_index = |a: &AccountId| -> Option { + voters.iter().position(|x| x == a).map(TryInto::try_into).unwrap().ok() + }; + let target_index = |a: &AccountId| -> Option { + targets.iter().position(|x| x == a).map(TryInto::try_into).unwrap().ok() + }; + + let solution = + TestSolution::from_assignment(&assignments, voter_index, target_index).unwrap(); + + assert_eq!( + solution, + TestSolution { + votes1: Default::default(), + votes2: vec![(0, [(0, p(50))], 1)], + ..Default::default() + } + ); + } +} + +#[test] +fn index_assignments_generate_same_solution_as_plain_assignments() { + let rng = rand::rngs::SmallRng::seed_from_u64(0); + + let (voters, assignments, candidates) = generate_random_votes(1000, 2500, rng); + let voter_index = make_voter_fn(&voters); + let target_index = make_target_fn(&candidates); + + let solution = + TestSolution::from_assignment(&assignments, &voter_index, &target_index).unwrap(); + + let index_assignments = assignments + .into_iter() + .map(|assignment| IndexAssignment::new(&assignment, &voter_index, &target_index)) + .collect::, _>>() + .unwrap(); + + let index_compact = index_assignments.as_slice().try_into().unwrap(); + + assert_eq!(solution, index_compact); +} diff --git a/primitives/npos-elections/solution-type/tests/ui/fail/missing_accuracy.rs b/frame/election-provider-support/solution-type/tests/ui/fail/missing_accuracy.rs similarity index 64% rename from primitives/npos-elections/solution-type/tests/ui/fail/missing_accuracy.rs rename to frame/election-provider-support/solution-type/tests/ui/fail/missing_accuracy.rs index b74b857e45815..22693cd875e17 100644 --- a/primitives/npos-elections/solution-type/tests/ui/fail/missing_accuracy.rs +++ b/frame/election-provider-support/solution-type/tests/ui/fail/missing_accuracy.rs @@ -1,4 +1,4 @@ -use sp_npos_elections_solution_type::generate_solution_type; +use frame_election_provider_solution_type::generate_solution_type; generate_solution_type!(pub struct TestSolution::< VoterIndex = u16, diff --git a/primitives/npos-elections/solution-type/tests/ui/fail/missing_accuracy.stderr b/frame/election-provider-support/solution-type/tests/ui/fail/missing_accuracy.stderr similarity index 100% rename from primitives/npos-elections/solution-type/tests/ui/fail/missing_accuracy.stderr rename to frame/election-provider-support/solution-type/tests/ui/fail/missing_accuracy.stderr diff --git a/primitives/npos-elections/solution-type/tests/ui/fail/missing_target.rs b/frame/election-provider-support/solution-type/tests/ui/fail/missing_target.rs similarity index 63% rename from primitives/npos-elections/solution-type/tests/ui/fail/missing_target.rs rename to frame/election-provider-support/solution-type/tests/ui/fail/missing_target.rs index 4c9cd51a32096..8d0ca927c5fb3 100644 --- a/primitives/npos-elections/solution-type/tests/ui/fail/missing_target.rs +++ b/frame/election-provider-support/solution-type/tests/ui/fail/missing_target.rs @@ -1,4 +1,4 @@ -use sp_npos_elections_solution_type::generate_solution_type; +use frame_election_provider_solution_type::generate_solution_type; generate_solution_type!(pub struct TestSolution::< VoterIndex = u16, diff --git a/primitives/npos-elections/solution-type/tests/ui/fail/missing_target.stderr b/frame/election-provider-support/solution-type/tests/ui/fail/missing_target.stderr similarity index 100% rename from primitives/npos-elections/solution-type/tests/ui/fail/missing_target.stderr rename to frame/election-provider-support/solution-type/tests/ui/fail/missing_target.stderr diff --git a/primitives/npos-elections/solution-type/tests/ui/fail/missing_voter.rs b/frame/election-provider-support/solution-type/tests/ui/fail/missing_voter.rs similarity index 63% rename from primitives/npos-elections/solution-type/tests/ui/fail/missing_voter.rs rename to frame/election-provider-support/solution-type/tests/ui/fail/missing_voter.rs index b87037f77f1e3..ad4b7f5217794 100644 --- a/primitives/npos-elections/solution-type/tests/ui/fail/missing_voter.rs +++ b/frame/election-provider-support/solution-type/tests/ui/fail/missing_voter.rs @@ -1,4 +1,4 @@ -use sp_npos_elections_solution_type::generate_solution_type; +use frame_election_provider_solution_type::generate_solution_type; generate_solution_type!(pub struct TestSolution::< u16, diff --git a/primitives/npos-elections/solution-type/tests/ui/fail/missing_voter.stderr b/frame/election-provider-support/solution-type/tests/ui/fail/missing_voter.stderr similarity index 100% rename from primitives/npos-elections/solution-type/tests/ui/fail/missing_voter.stderr rename to frame/election-provider-support/solution-type/tests/ui/fail/missing_voter.stderr diff --git a/primitives/npos-elections/solution-type/tests/ui/fail/no_annotations.rs b/frame/election-provider-support/solution-type/tests/ui/fail/no_annotations.rs similarity index 58% rename from primitives/npos-elections/solution-type/tests/ui/fail/no_annotations.rs rename to frame/election-provider-support/solution-type/tests/ui/fail/no_annotations.rs index cfca2841db633..87673a3823513 100644 --- a/primitives/npos-elections/solution-type/tests/ui/fail/no_annotations.rs +++ b/frame/election-provider-support/solution-type/tests/ui/fail/no_annotations.rs @@ -1,4 +1,4 @@ -use sp_npos_elections_solution_type::generate_solution_type; +use frame_election_provider_solution_type::generate_solution_type; generate_solution_type!(pub struct TestSolution::< u16, diff --git a/primitives/npos-elections/solution-type/tests/ui/fail/no_annotations.stderr b/frame/election-provider-support/solution-type/tests/ui/fail/no_annotations.stderr similarity index 100% rename from primitives/npos-elections/solution-type/tests/ui/fail/no_annotations.stderr rename to frame/election-provider-support/solution-type/tests/ui/fail/no_annotations.stderr diff --git a/primitives/npos-elections/solution-type/tests/ui/fail/swap_voter_target.rs b/frame/election-provider-support/solution-type/tests/ui/fail/swap_voter_target.rs similarity index 66% rename from primitives/npos-elections/solution-type/tests/ui/fail/swap_voter_target.rs rename to frame/election-provider-support/solution-type/tests/ui/fail/swap_voter_target.rs index 443202d11b39b..f1d5d0e7bf99f 100644 --- a/primitives/npos-elections/solution-type/tests/ui/fail/swap_voter_target.rs +++ b/frame/election-provider-support/solution-type/tests/ui/fail/swap_voter_target.rs @@ -1,4 +1,4 @@ -use sp_npos_elections_solution_type::generate_solution_type; +use frame_election_provider_solution_type::generate_solution_type; generate_solution_type!(pub struct TestSolution::< TargetIndex = u16, diff --git a/primitives/npos-elections/solution-type/tests/ui/fail/swap_voter_target.stderr b/frame/election-provider-support/solution-type/tests/ui/fail/swap_voter_target.stderr similarity index 100% rename from primitives/npos-elections/solution-type/tests/ui/fail/swap_voter_target.stderr rename to frame/election-provider-support/solution-type/tests/ui/fail/swap_voter_target.stderr diff --git a/primitives/npos-elections/solution-type/tests/ui/fail/wrong_attribute.rs b/frame/election-provider-support/solution-type/tests/ui/fail/wrong_attribute.rs similarity index 69% rename from primitives/npos-elections/solution-type/tests/ui/fail/wrong_attribute.rs rename to frame/election-provider-support/solution-type/tests/ui/fail/wrong_attribute.rs index 3008277e36b74..d04cc4a7a966b 100644 --- a/primitives/npos-elections/solution-type/tests/ui/fail/wrong_attribute.rs +++ b/frame/election-provider-support/solution-type/tests/ui/fail/wrong_attribute.rs @@ -1,4 +1,4 @@ -use sp_npos_elections_solution_type::generate_solution_type; +use frame_election_provider_solution_type::generate_solution_type; generate_solution_type!( #[pages(1)] pub struct TestSolution::< diff --git a/primitives/npos-elections/solution-type/tests/ui/fail/wrong_attribute.stderr b/frame/election-provider-support/solution-type/tests/ui/fail/wrong_attribute.stderr similarity index 100% rename from primitives/npos-elections/solution-type/tests/ui/fail/wrong_attribute.stderr rename to frame/election-provider-support/solution-type/tests/ui/fail/wrong_attribute.stderr diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index ff57eacf68fa0..1bd10ba09346f 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -172,6 +172,7 @@ use sp_runtime::traits::Bounded; use sp_std::{fmt::Debug, prelude::*}; /// Re-export some type as they are used in the interface. +pub use frame_election_provider_solution_type::generate_solution_type; pub use sp_arithmetic::PerThing; pub use sp_npos_elections::{ Assignment, ElectionResult, ExtendedBalance, IdentifierT, PerThing128, Support, Supports, diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index d833ac86fe0bd..4c1bb438457e5 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -302,7 +302,7 @@ mod pallet; use codec::{Decode, Encode, HasCompact}; use frame_support::{ parameter_types, - traits::{ConstU32, Currency, Get}, + traits::{Currency, Get}, weights::Weight, BoundedVec, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, }; @@ -861,6 +861,6 @@ pub struct TestBenchmarkingConfig; #[cfg(feature = "std")] impl BenchmarkingConfig for TestBenchmarkingConfig { - type MaxValidators = ConstU32<100>; - type MaxNominators = ConstU32<100>; + type MaxValidators = frame_support::traits::ConstU32<100>; + type MaxNominators = frame_support::traits::ConstU32<100>; } diff --git a/primitives/npos-elections/Cargo.toml b/primitives/npos-elections/Cargo.toml index 3facf32196c74..13edbfe90008b 100644 --- a/primitives/npos-elections/Cargo.toml +++ b/primitives/npos-elections/Cargo.toml @@ -17,7 +17,6 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", optional = true, features = ["derive"] } sp-std = { version = "4.0.0", default-features = false, path = "../std" } -sp-npos-elections-solution-type = { version = "4.0.0-dev", path = "./solution-type" } sp-arithmetic = { version = "5.0.0", default-features = false, path = "../arithmetic" } sp-core = { version = "6.0.0", default-features = false, path = "../core" } sp-runtime = { version = "6.0.0", path = "../runtime", default-features = false } diff --git a/primitives/npos-elections/fuzzer/Cargo.toml b/primitives/npos-elections/fuzzer/Cargo.toml index 2f2bae2dd44d4..71c0c07c3032a 100644 --- a/primitives/npos-elections/fuzzer/Cargo.toml +++ b/primitives/npos-elections/fuzzer/Cargo.toml @@ -35,10 +35,6 @@ path = "src/phragmen_balancing.rs" name = "phragmms_balancing" path = "src/phragmms_balancing.rs" -[[bin]] -name = "compact" -path = "src/compact.rs" - [[bin]] name = "phragmen_pjr" path = "src/phragmen_pjr.rs" diff --git a/primitives/npos-elections/src/lib.rs b/primitives/npos-elections/src/lib.rs index 7bd1a4b7f69b6..673036c2d19d1 100644 --- a/primitives/npos-elections/src/lib.rs +++ b/primitives/npos-elections/src/lib.rs @@ -77,7 +77,9 @@ use scale_info::TypeInfo; use sp_arithmetic::{traits::Zero, Normalizable, PerThing, Rational128, ThresholdOrd}; use sp_core::RuntimeDebug; -use sp_std::{cell::RefCell, cmp::Ordering, collections::btree_map::BTreeMap, prelude::*, rc::Rc}; +use sp_std::{ + cell::RefCell, cmp::Ordering, collections::btree_map::BTreeMap, prelude::*, rc::Rc, vec, +}; use codec::{Decode, Encode, MaxEncodedLen}; #[cfg(feature = "std")] @@ -117,9 +119,6 @@ pub use sp_arithmetic; #[doc(hidden)] pub use sp_std; -// re-export the solution type macro. -pub use sp_npos_elections_solution_type::generate_solution_type; - /// The errors that might occur in the this crate and solution-type. #[derive(Eq, PartialEq, RuntimeDebug)] pub enum Error { diff --git a/primitives/npos-elections/src/mock.rs b/primitives/npos-elections/src/mock.rs index 85c970d7b418f..dd85ce9b6dfae 100644 --- a/primitives/npos-elections/src/mock.rs +++ b/primitives/npos-elections/src/mock.rs @@ -19,13 +19,6 @@ #![cfg(test)] -use std::{ - collections::{HashMap, HashSet}, - convert::TryInto, - hash::Hash, -}; - -use rand::{self, seq::SliceRandom, Rng}; use sp_arithmetic::{ traits::{One, SaturatedConversion, Zero}, PerThing, @@ -37,27 +30,6 @@ use crate::{seq_phragmen, Assignment, ElectionResult, ExtendedBalance, PerThing1 pub type AccountId = u64; -/// The candidate mask allows easy disambiguation between voters and candidates: accounts -/// for which this bit is set are candidates, and without it, are voters. -pub const CANDIDATE_MASK: AccountId = 1 << ((std::mem::size_of::() * 8) - 1); - -pub type TestAccuracy = sp_runtime::Perbill; - -crate::generate_solution_type! { - pub struct TestSolution::< - VoterIndex = u32, - TargetIndex = u16, - Accuracy = TestAccuracy, - >(16) -} - -pub fn p(p: u8) -> TestAccuracy { - TestAccuracy::from_percent(p.into()) -} - -pub type MockAssignment = crate::Assignment; -pub type Voter = (AccountId, VoteWeight, Vec); - #[derive(Default, Debug)] pub(crate) struct _Candidate { who: A, @@ -412,136 +384,3 @@ pub(crate) fn build_support_map_float( } supports } - -/// Generate voter and assignment lists. Makes no attempt to be realistic about winner or assignment -/// fairness. -/// -/// Maintains these invariants: -/// -/// - candidate ids have `CANDIDATE_MASK` bit set -/// - voter ids do not have `CANDIDATE_MASK` bit set -/// - assignments have the same ordering as voters -/// - `assignments.distribution.iter().map(|(_, frac)| frac).sum() == One::one()` -/// - a coherent set of winners is chosen. -/// - the winner set is a subset of the candidate set. -/// - `assignments.distribution.iter().all(|(who, _)| winners.contains(who))` -pub fn generate_random_votes( - candidate_count: usize, - voter_count: usize, - mut rng: impl Rng, -) -> (Vec, Vec, Vec) { - // cache for fast generation of unique candidate and voter ids - let mut used_ids = HashSet::with_capacity(candidate_count + voter_count); - - // candidates are easy: just a completely random set of IDs - let mut candidates: Vec = Vec::with_capacity(candidate_count); - while candidates.len() < candidate_count { - let mut new = || rng.gen::() | CANDIDATE_MASK; - let mut id = new(); - // insert returns `false` when the value was already present - while !used_ids.insert(id) { - id = new(); - } - candidates.push(id); - } - - // voters are random ids, random weights, random selection from the candidates - let mut voters = Vec::with_capacity(voter_count); - while voters.len() < voter_count { - let mut new = || rng.gen::() & !CANDIDATE_MASK; - let mut id = new(); - // insert returns `false` when the value was already present - while !used_ids.insert(id) { - id = new(); - } - - let vote_weight = rng.gen(); - - // it's not interesting if a voter chooses 0 or all candidates, so rule those cases out. - // also, let's not generate any cases which result in a compact overflow. - let n_candidates_chosen = - rng.gen_range(1, candidates.len().min(::LIMIT)); - - let mut chosen_candidates = Vec::with_capacity(n_candidates_chosen); - chosen_candidates.extend(candidates.choose_multiple(&mut rng, n_candidates_chosen)); - voters.push((id, vote_weight, chosen_candidates)); - } - - // always generate a sensible number of winners: elections are uninteresting if nobody wins, - // or everybody wins - let num_winners = rng.gen_range(1, candidate_count); - let mut winners: HashSet = HashSet::with_capacity(num_winners); - winners.extend(candidates.choose_multiple(&mut rng, num_winners)); - assert_eq!(winners.len(), num_winners); - - let mut assignments = Vec::with_capacity(voters.len()); - for (voter_id, _, votes) in voters.iter() { - let chosen_winners = votes.iter().filter(|vote| winners.contains(vote)).cloned(); - let num_chosen_winners = chosen_winners.clone().count(); - - // distribute the available stake randomly - let stake_distribution = if num_chosen_winners == 0 { - continue - } else { - let mut available_stake = 1000; - let mut stake_distribution = Vec::with_capacity(num_chosen_winners); - for _ in 0..num_chosen_winners - 1 { - let stake = rng.gen_range(0, available_stake).min(1); - stake_distribution.push(TestAccuracy::from_perthousand(stake)); - available_stake -= stake; - } - stake_distribution.push(TestAccuracy::from_perthousand(available_stake)); - stake_distribution.shuffle(&mut rng); - stake_distribution - }; - - assignments.push(MockAssignment { - who: *voter_id, - distribution: chosen_winners.zip(stake_distribution).collect(), - }); - } - - (voters, assignments, candidates) -} - -fn generate_cache(voters: Voters) -> HashMap -where - Voters: Iterator, - Item: Hash + Eq + Copy, -{ - let mut cache = HashMap::new(); - for (idx, voter_id) in voters.enumerate() { - cache.insert(voter_id, idx); - } - cache -} - -/// Create a function that returns the index of a voter in the voters list. -pub fn make_voter_fn(voters: &[Voter]) -> impl Fn(&AccountId) -> Option -where - usize: TryInto, -{ - let cache = generate_cache(voters.iter().map(|(id, _, _)| *id)); - move |who| { - if cache.get(who).is_none() { - println!("WARNING: voter {} will raise InvalidIndex", who); - } - cache.get(who).cloned().and_then(|i| i.try_into().ok()) - } -} - -/// Create a function that returns the index of a candidate in the candidates list. -pub fn make_target_fn( - candidates: &[AccountId], -) -> impl Fn(&AccountId) -> Option -where - usize: TryInto, -{ - let cache = generate_cache(candidates.iter().cloned()); - move |who| { - if cache.get(who).is_none() { - println!("WARNING: target {} will raise InvalidIndex", who); - } - cache.get(who).cloned().and_then(|i| i.try_into().ok()) - } -} diff --git a/primitives/npos-elections/src/tests.rs b/primitives/npos-elections/src/tests.rs index b199fdd1af77f..1cf5ea8a24920 100644 --- a/primitives/npos-elections/src/tests.rs +++ b/primitives/npos-elections/src/tests.rs @@ -19,12 +19,9 @@ use crate::{ balancing, helpers::*, mock::*, seq_phragmen, seq_phragmen_core, setup_inputs, to_support_map, - Assignment, ElectionResult, ExtendedBalance, IndexAssignment, NposSolution, StakedAssignment, - Support, Voter, + Assignment, ElectionResult, ExtendedBalance, StakedAssignment, Support, Voter, }; -use rand::{self, SeedableRng}; use sp_arithmetic::{PerU16, Perbill, Percent, Permill}; -use std::convert::TryInto; use substrate_test_utils::assert_eq_uvec; #[test] @@ -919,329 +916,3 @@ mod score { assert!(ElectionScore::from([10, 5, 15]) > ElectionScore::from([10, 5, 25])); } } - -mod solution_type { - use super::*; - use codec::{Decode, Encode}; - // these need to come from the same dev-dependency `sp-npos-elections`, not from the crate. - use crate::{generate_solution_type, Assignment, Error as NposError, NposSolution}; - use sp_std::{convert::TryInto, fmt::Debug}; - - #[allow(dead_code)] - mod __private { - // This is just to make sure that the solution can be generated in a scope without any - // imports. - use crate::generate_solution_type; - generate_solution_type!( - #[compact] - struct InnerTestSolutionIsolated::(12) - ); - } - - #[test] - fn solution_struct_works_with_and_without_compact() { - // we use u32 size to make sure compact is smaller. - let without_compact = { - generate_solution_type!( - pub struct InnerTestSolution::< - VoterIndex = u32, - TargetIndex = u32, - Accuracy = TestAccuracy, - >(16) - ); - let solution = InnerTestSolution { - votes1: vec![(2, 20), (4, 40)], - votes2: vec![(1, [(10, p(80))], 11), (5, [(50, p(85))], 51)], - ..Default::default() - }; - - solution.encode().len() - }; - - let with_compact = { - generate_solution_type!( - #[compact] - pub struct InnerTestSolutionCompact::< - VoterIndex = u32, - TargetIndex = u32, - Accuracy = TestAccuracy, - >(16) - ); - let compact = InnerTestSolutionCompact { - votes1: vec![(2, 20), (4, 40)], - votes2: vec![(1, [(10, p(80))], 11), (5, [(50, p(85))], 51)], - ..Default::default() - }; - - compact.encode().len() - }; - - assert!(with_compact < without_compact); - } - - #[test] - fn solution_struct_is_codec() { - let solution = TestSolution { - votes1: vec![(2, 20), (4, 40)], - votes2: vec![(1, [(10, p(80))], 11), (5, [(50, p(85))], 51)], - ..Default::default() - }; - - let encoded = solution.encode(); - - assert_eq!(solution, Decode::decode(&mut &encoded[..]).unwrap()); - assert_eq!(solution.voter_count(), 4); - assert_eq!(solution.edge_count(), 2 + 4); - assert_eq!(solution.unique_targets(), vec![10, 11, 20, 40, 50, 51]); - } - - #[test] - fn remove_voter_works() { - let mut solution = TestSolution { - votes1: vec![(0, 2), (1, 6)], - votes2: vec![(2, [(0, p(80))], 1), (3, [(7, p(85))], 8)], - votes3: vec![(4, [(3, p(50)), (4, p(25))], 5)], - ..Default::default() - }; - - assert!(!solution.remove_voter(11)); - assert!(solution.remove_voter(2)); - assert_eq!( - solution, - TestSolution { - votes1: vec![(0, 2), (1, 6)], - votes2: vec![(3, [(7, p(85))], 8)], - votes3: vec![(4, [(3, p(50)), (4, p(25))], 5,)], - ..Default::default() - }, - ); - - assert!(solution.remove_voter(4)); - assert_eq!( - solution, - TestSolution { - votes1: vec![(0, 2), (1, 6)], - votes2: vec![(3, [(7, p(85))], 8)], - ..Default::default() - }, - ); - - assert!(solution.remove_voter(1)); - assert_eq!( - solution, - TestSolution { - votes1: vec![(0, 2)], - votes2: vec![(3, [(7, p(85))], 8),], - ..Default::default() - }, - ); - } - - #[test] - fn from_and_into_assignment_works() { - let voters = vec![2 as AccountId, 4, 1, 5, 3]; - let targets = vec![ - 10 as AccountId, - 11, - 20, // 2 - 30, - 31, // 4 - 32, - 40, // 6 - 50, - 51, // 8 - ]; - - let assignments = vec![ - Assignment { who: 2 as AccountId, distribution: vec![(20u64, p(100))] }, - Assignment { who: 4, distribution: vec![(40, p(100))] }, - Assignment { who: 1, distribution: vec![(10, p(80)), (11, p(20))] }, - Assignment { who: 5, distribution: vec![(50, p(85)), (51, p(15))] }, - Assignment { who: 3, distribution: vec![(30, p(50)), (31, p(25)), (32, p(25))] }, - ]; - - let voter_index = |a: &AccountId| -> Option { - voters.iter().position(|x| x == a).map(TryInto::try_into).unwrap().ok() - }; - let target_index = |a: &AccountId| -> Option { - targets.iter().position(|x| x == a).map(TryInto::try_into).unwrap().ok() - }; - - let solution = - TestSolution::from_assignment(&assignments, voter_index, target_index).unwrap(); - - // basically number of assignments that it is encoding. - assert_eq!(solution.voter_count(), assignments.len()); - assert_eq!( - solution.edge_count(), - assignments.iter().fold(0, |a, b| a + b.distribution.len()), - ); - - assert_eq!( - solution, - TestSolution { - votes1: vec![(0, 2), (1, 6)], - votes2: vec![(2, [(0, p(80))], 1), (3, [(7, p(85))], 8)], - votes3: vec![(4, [(3, p(50)), (4, p(25))], 5)], - ..Default::default() - } - ); - - assert_eq!(solution.unique_targets(), vec![0, 1, 2, 3, 4, 5, 6, 7, 8]); - - let voter_at = |a: u32| -> Option { - voters.get(>::try_into(a).unwrap()).cloned() - }; - let target_at = |a: u16| -> Option { - targets.get(>::try_into(a).unwrap()).cloned() - }; - - assert_eq!(solution.into_assignment(voter_at, target_at).unwrap(), assignments); - } - - #[test] - fn unique_targets_len_edge_count_works() { - // we don't really care about voters here so all duplicates. This is not invalid per se. - let solution = TestSolution { - votes1: vec![(99, 1), (99, 2)], - votes2: vec![(99, [(3, p(10))], 7), (99, [(4, p(10))], 8)], - votes3: vec![(99, [(11, p(10)), (12, p(10))], 13)], - // ensure the last one is also counted. - votes16: vec![( - 99, - [ - (66, p(10)), - (66, p(10)), - (66, p(10)), - (66, p(10)), - (66, p(10)), - (66, p(10)), - (66, p(10)), - (66, p(10)), - (66, p(10)), - (66, p(10)), - (66, p(10)), - (66, p(10)), - (66, p(10)), - (66, p(10)), - (66, p(10)), - ], - 67, - )], - ..Default::default() - }; - - assert_eq!(solution.unique_targets(), vec![1, 2, 3, 4, 7, 8, 11, 12, 13, 66, 67]); - assert_eq!(solution.edge_count(), 2 + (2 * 2) + 3 + 16); - assert_eq!(solution.voter_count(), 6); - - // this one has some duplicates. - let solution = TestSolution { - votes1: vec![(99, 1), (99, 1)], - votes2: vec![(99, [(3, p(10))], 7), (99, [(4, p(10))], 8)], - votes3: vec![(99, [(11, p(10)), (11, p(10))], 13)], - ..Default::default() - }; - - assert_eq!(solution.unique_targets(), vec![1, 3, 4, 7, 8, 11, 13]); - assert_eq!(solution.edge_count(), 2 + (2 * 2) + 3); - assert_eq!(solution.voter_count(), 5); - } - - #[test] - fn solution_into_assignment_must_report_overflow() { - // in votes2 - let solution = TestSolution { - votes1: Default::default(), - votes2: vec![(0, [(1, p(100))], 2)], - ..Default::default() - }; - - let voter_at = |a: u32| -> Option { Some(a as AccountId) }; - let target_at = |a: u16| -> Option { Some(a as AccountId) }; - - assert_eq!( - solution.into_assignment(&voter_at, &target_at).unwrap_err(), - NposError::SolutionWeightOverflow, - ); - - // in votes3 onwards - let solution = TestSolution { - votes1: Default::default(), - votes2: Default::default(), - votes3: vec![(0, [(1, p(70)), (2, p(80))], 3)], - ..Default::default() - }; - - assert_eq!( - solution.into_assignment(&voter_at, &target_at).unwrap_err(), - NposError::SolutionWeightOverflow, - ); - } - - #[test] - fn target_count_overflow_is_detected() { - let voter_index = |a: &AccountId| -> Option { Some(*a as u32) }; - let target_index = |a: &AccountId| -> Option { Some(*a as u16) }; - - let assignments = vec![Assignment { - who: 1 as AccountId, - distribution: (10..27).map(|i| (i as AccountId, p(i as u8))).collect::>(), - }]; - - let solution = TestSolution::from_assignment(&assignments, voter_index, target_index); - assert_eq!(solution.unwrap_err(), NposError::SolutionTargetOverflow); - } - - #[test] - fn zero_target_count_is_ignored() { - let voters = vec![1 as AccountId, 2]; - let targets = vec![10 as AccountId, 11]; - - let assignments = vec![ - Assignment { who: 1 as AccountId, distribution: vec![(10, p(50)), (11, p(50))] }, - Assignment { who: 2, distribution: vec![] }, - ]; - - let voter_index = |a: &AccountId| -> Option { - voters.iter().position(|x| x == a).map(TryInto::try_into).unwrap().ok() - }; - let target_index = |a: &AccountId| -> Option { - targets.iter().position(|x| x == a).map(TryInto::try_into).unwrap().ok() - }; - - let solution = - TestSolution::from_assignment(&assignments, voter_index, target_index).unwrap(); - - assert_eq!( - solution, - TestSolution { - votes1: Default::default(), - votes2: vec![(0, [(0, p(50))], 1)], - ..Default::default() - } - ); - } -} - -#[test] -fn index_assignments_generate_same_solution_as_plain_assignments() { - let rng = rand::rngs::SmallRng::seed_from_u64(0); - - let (voters, assignments, candidates) = generate_random_votes(1000, 2500, rng); - let voter_index = make_voter_fn(&voters); - let target_index = make_target_fn(&candidates); - - let solution = - TestSolution::from_assignment(&assignments, &voter_index, &target_index).unwrap(); - - let index_assignments = assignments - .into_iter() - .map(|assignment| IndexAssignment::new(&assignment, &voter_index, &target_index)) - .collect::, _>>() - .unwrap(); - - let index_compact = index_assignments.as_slice().try_into().unwrap(); - - assert_eq!(solution, index_compact); -} From 23b19d87c50ea57384f9bce8b13c254aaf4592de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Mon, 14 Mar 2022 12:48:29 +0100 Subject: [PATCH 017/484] shell.nix: Update to a newer nightly (#11028) --- shell.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shell.nix b/shell.nix index a86af005383f7..023946ce16de4 100644 --- a/shell.nix +++ b/shell.nix @@ -2,10 +2,10 @@ let mozillaOverlay = import (builtins.fetchGit { url = "https://github.com/mozilla/nixpkgs-mozilla.git"; - rev = "4a07484cf0e49047f82d83fd119acffbad3b235f"; + rev = "15b7a05f20aab51c4ffbefddb1b448e862dccb7d"; }); nixpkgs = import { overlays = [ mozillaOverlay ]; }; - rust-nightly = with nixpkgs; ((rustChannelOf { date = "2021-09-10"; channel = "nightly"; }).rust.override { + rust-nightly = with nixpkgs; ((rustChannelOf { date = "2022-02-10"; channel = "nightly"; }).rust.override { extensions = [ "rust-src" ]; targets = [ "wasm32-unknown-unknown" ]; }); From 5f92069fccb896d01526e4f9a7db635355cbc879 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 15 Mar 2022 13:48:29 +0100 Subject: [PATCH 018/484] Update lockfile (#11035) Signed-off-by: Oliver Tale-Yazdi --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 9f4d2f21cf9c1..82dea83813d0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2196,7 +2196,7 @@ dependencies = [ name = "frame-election-solution-type-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 3.0.7", + "clap 3.1.6", "frame-election-provider-solution-type", "honggfuzz", "parity-scale-codec", From 4361b4d46bf62c5a3a567039c593121010049e55 Mon Sep 17 00:00:00 2001 From: Daan Schutte Date: Tue, 15 Mar 2022 17:21:16 +0300 Subject: [PATCH 019/484] Additional `benchmark-storage` flags (#11004) * Fix typos * Enable overwriting handlebars template * Optionally name json output or disable json altogether * Don't write to json by default * Include block id in handlebars output * Include warmups for write benchmarks * PR comments * Drop unnecessary file extension * Use more appropriate types * Use more appropriate error message * More use of more appropriate types * Rework write benchmark warmups * Run same benchmark for both read and write --- utils/frame/benchmarking-cli/src/lib.rs | 2 +- .../frame/benchmarking-cli/src/storage/cmd.rs | 63 ++++++++++++++++--- .../benchmarking-cli/src/storage/read.rs | 11 ---- .../benchmarking-cli/src/storage/record.rs | 14 +++-- .../benchmarking-cli/src/storage/template.rs | 32 +++++++--- .../benchmarking-cli/src/storage/weights.hbs | 1 + 6 files changed, 93 insertions(+), 30 deletions(-) diff --git a/utils/frame/benchmarking-cli/src/lib.rs b/utils/frame/benchmarking-cli/src/lib.rs index 640b1770f5c3f..9815fe88a7f02 100644 --- a/utils/frame/benchmarking-cli/src/lib.rs +++ b/utils/frame/benchmarking-cli/src/lib.rs @@ -67,7 +67,7 @@ pub struct BenchmarkCmd { #[clap(long = "json")] pub json_output: bool, - /// Write the raw results in JSON format into the give file. + /// Write the raw results in JSON format into the given file. #[clap(long, conflicts_with = "json-output")] pub json_file: Option, diff --git a/utils/frame/benchmarking-cli/src/storage/cmd.rs b/utils/frame/benchmarking-cli/src/storage/cmd.rs index 4376b616286a4..ad7d13a2022e4 100644 --- a/utils/frame/benchmarking-cli/src/storage/cmd.rs +++ b/utils/frame/benchmarking-cli/src/storage/cmd.rs @@ -20,6 +20,7 @@ use sc_client_api::{Backend as ClientBackend, StorageProvider, UsageProvider}; use sc_client_db::DbHash; use sc_service::Configuration; use sp_blockchain::HeaderBackend; +use sp_core::storage::StorageKey; use sp_database::{ColumnId, Database}; use sp_runtime::traits::{Block as BlockT, HashFor}; use sp_state_machine::Storage; @@ -29,7 +30,8 @@ use clap::{Args, Parser}; use log::info; use rand::prelude::*; use serde::Serialize; -use std::{fmt::Debug, sync::Arc}; +use sp_runtime::generic::BlockId; +use std::{fmt::Debug, path::PathBuf, sync::Arc}; use super::{record::StatSelect, template::TemplateData}; @@ -58,8 +60,8 @@ pub struct StorageCmd { pub struct StorageParams { /// Path to write the *weight* file to. Can be a file or directory. /// For substrate this should be `frame/support/src/weights`. - #[clap(long, default_value = ".")] - pub weight_path: String, + #[clap(long)] + pub weight_path: Option, /// Select a specific metric to calculate the final weight output. #[clap(long = "metric", default_value = "average")] @@ -83,8 +85,19 @@ pub struct StorageParams { #[clap(long)] pub skip_write: bool, + /// Specify the Handlebars template to use for outputting benchmark results. + #[clap(long)] + pub template_path: Option, + + /// Path to write the raw 'read' results in JSON format to. Can be a file or directory. + #[clap(long)] + pub json_read_path: Option, + + /// Path to write the raw 'write' results in JSON format to. Can be a file or directory. + #[clap(long)] + pub json_write_path: Option, + /// Rounds of warmups before measuring. - /// Only supported for `read` benchmarks. #[clap(long, default_value = "1")] pub warmups: u32, @@ -115,23 +128,32 @@ impl StorageCmd { { let mut template = TemplateData::new(&cfg, &self.params); + let block_id = BlockId::::Number(client.usage_info().chain.best_number); + template.set_block_number(block_id.to_string()); + if !self.params.skip_read { + self.bench_warmup(&client)?; let record = self.bench_read(client.clone())?; - record.save_json(&cfg, "read")?; + if let Some(path) = &self.params.json_read_path { + record.save_json(&cfg, path, "read")?; + } let stats = record.calculate_stats()?; info!("Time summary [ns]:\n{:?}\nValue size summary:\n{:?}", stats.0, stats.1); template.set_stats(Some(stats), None)?; } if !self.params.skip_write { + self.bench_warmup(&client)?; let record = self.bench_write(client, db, storage)?; - record.save_json(&cfg, "write")?; + if let Some(path) = &self.params.json_write_path { + record.save_json(&cfg, path, "write")?; + } let stats = record.calculate_stats()?; info!("Time summary [ns]:\n{:?}\nValue size summary:\n{:?}", stats.0, stats.1); template.set_stats(None, Some(stats))?; } - template.write(&self.params.weight_path) + template.write(&self.params.weight_path, &self.params.template_path) } /// Returns the specified state version. @@ -149,6 +171,33 @@ impl StorageCmd { info!("Using seed {}", seed); StdRng::seed_from_u64(seed) } + + /// Run some rounds of the (read) benchmark as warmup. + /// See `frame_benchmarking_cli::storage::read::bench_read` for detailed comments. + fn bench_warmup(&self, client: &Arc) -> Result<()> + where + C: UsageProvider + StorageProvider, + B: BlockT + Debug, + BA: ClientBackend, + { + let block = BlockId::Number(client.usage_info().chain.best_number); + let empty_prefix = StorageKey(Vec::new()); + let mut keys = client.storage_keys(&block, &empty_prefix)?; + let mut rng = Self::setup_rng(); + keys.shuffle(&mut rng); + + for i in 0..self.params.warmups { + info!("Warmup round {}/{}", i + 1, self.params.warmups); + for key in keys.clone() { + let _ = client + .storage(&block, &key) + .expect("Checked above to exist") + .ok_or("Value unexpectedly empty"); + } + } + + Ok(()) + } } // Boilerplate diff --git a/utils/frame/benchmarking-cli/src/storage/read.rs b/utils/frame/benchmarking-cli/src/storage/read.rs index 3974c4010f632..ca506202e1067 100644 --- a/utils/frame/benchmarking-cli/src/storage/read.rs +++ b/utils/frame/benchmarking-cli/src/storage/read.rs @@ -49,17 +49,6 @@ impl StorageCmd { let mut rng = Self::setup_rng(); keys.shuffle(&mut rng); - // Run some rounds of the benchmark as warmup. - for i in 0..self.params.warmups { - info!("Warmup round {}/{}", i + 1, self.params.warmups); - for key in keys.clone() { - let _ = client - .storage(&block, &key) - .expect("Checked above to exist") - .ok_or("Value unexpectedly empty")?; - } - } - // Interesting part here: // Read all the keys in the database and measure the time it takes to access each. info!("Reading {} keys", keys.len()); diff --git a/utils/frame/benchmarking-cli/src/storage/record.rs b/utils/frame/benchmarking-cli/src/storage/record.rs index 00a613c713007..667274bef0dd5 100644 --- a/utils/frame/benchmarking-cli/src/storage/record.rs +++ b/utils/frame/benchmarking-cli/src/storage/record.rs @@ -22,7 +22,7 @@ use sc_service::Configuration; use log::info; use serde::Serialize; -use std::{fmt, fs, result, str::FromStr, time::Duration}; +use std::{fmt, fs, path::PathBuf, result, str::FromStr, time::Duration}; /// Raw output of a Storage benchmark. #[derive(Debug, Default, Clone, Serialize)] @@ -95,12 +95,18 @@ impl BenchRecord { Ok((time, size)) // The swap of time/size here is intentional. } - /// Saves the raw results in a json file in the current directory. + /// Unless a path is specified, saves the raw results in a json file in the current directory. /// Prefixes it with the DB name and suffixed with `path_suffix`. - pub fn save_json(&self, cfg: &Configuration, path_suffix: &str) -> Result<()> { - let path = format!("{}_{}.json", cfg.database, path_suffix).to_lowercase(); + pub fn save_json(&self, cfg: &Configuration, out_path: &PathBuf, suffix: &str) -> Result<()> { + let mut path = PathBuf::from(out_path); + if path.is_dir() || path.as_os_str().is_empty() { + path.push(&format!("{}_{}", cfg.database, suffix).to_lowercase()); + path.set_extension("json"); + } + let json = serde_json::to_string_pretty(&self) .map_err(|e| format!("Serializing as JSON: {:?}", e))?; + fs::write(&path, json)?; info!("Raw data written to {:?}", fs::canonicalize(&path)?); Ok(()) diff --git a/utils/frame/benchmarking-cli/src/storage/template.rs b/utils/frame/benchmarking-cli/src/storage/template.rs index 56e0869a914a1..13b825d891a51 100644 --- a/utils/frame/benchmarking-cli/src/storage/template.rs +++ b/utils/frame/benchmarking-cli/src/storage/template.rs @@ -32,6 +32,8 @@ static TEMPLATE: &str = include_str!("./weights.hbs"); pub(crate) struct TemplateData { /// Name of the database used. db_name: String, + /// Block number that was used. + block_number: String, /// Name of the runtime. Taken from the chain spec. runtime_name: String, /// Version of the benchmarking CLI used. @@ -85,28 +87,44 @@ impl TemplateData { Ok(()) } - /// Filles out the `weights.hbs` HBS template with its own data. + /// Sets the block id that was used. + pub fn set_block_number(&mut self, block_number: String) { + self.block_number = block_number + } + + /// Fills out the `weights.hbs` or specified HBS template with its own data. /// Writes the result to `path` which can be a directory or file. - pub fn write(&self, path: &str) -> Result<()> { + pub fn write(&self, path: &Option, hbs_template: &Option) -> Result<()> { let mut handlebars = handlebars::Handlebars::new(); // Format large integers with underscore. handlebars.register_helper("underscore", Box::new(crate::writer::UnderscoreHelper)); // Don't HTML escape any characters. handlebars.register_escape_fn(|s| -> String { s.to_string() }); + // Use custom template if provided. + let template = match hbs_template { + Some(template) if template.is_file() => fs::read_to_string(template)?, + Some(_) => return Err("Handlebars template is not a valid file!".into()), + None => TEMPLATE.to_string(), + }; let out_path = self.build_path(path); let mut fd = fs::File::create(&out_path)?; info!("Writing weights to {:?}", fs::canonicalize(&out_path)?); + handlebars - .render_template_to_write(&TEMPLATE, &self, &mut fd) + .render_template_to_write(&template, &self, &mut fd) .map_err(|e| format!("HBS template write: {:?}", e).into()) } /// Builds a path for the weight file. - fn build_path(&self, weight_out: &str) -> PathBuf { - let mut path = PathBuf::from(weight_out); - if path.is_dir() { - path.push(format!("{}_weights.rs", self.db_name.to_lowercase())); + fn build_path(&self, weight_out: &Option) -> PathBuf { + let mut path = match weight_out { + Some(p) => PathBuf::from(p), + None => PathBuf::new(), + }; + + if path.is_dir() || path.as_os_str().is_empty() { + path.push(format!("{}_weights", self.db_name.to_lowercase())); path.set_extension("rs"); } path diff --git a/utils/frame/benchmarking-cli/src/storage/weights.hbs b/utils/frame/benchmarking-cli/src/storage/weights.hbs index ffeb1fe04d81c..bfb832cb847f9 100644 --- a/utils/frame/benchmarking-cli/src/storage/weights.hbs +++ b/utils/frame/benchmarking-cli/src/storage/weights.hbs @@ -19,6 +19,7 @@ //! DATE: {{date}} //! //! DATABASE: `{{db_name}}`, RUNTIME: `{{runtime_name}}` +//! BLOCK-NUM: `{{block_number}}` //! SKIP-WRITE: `{{params.skip_write}}`, SKIP-READ: `{{params.skip_read}}`, WARMUPS: `{{params.warmups}}` //! STATE-VERSION: `V{{params.state_version}}`, STATE-CACHE-SIZE: `{{params.state_cache_size}}` //! WEIGHT-PATH: `{{params.weight_path}}` From b1e8003cbed890dd7708c71c0ff214ae9f774b6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Tue, 15 Mar 2022 16:14:19 +0100 Subject: [PATCH 020/484] SharedData: Update locks to mention possible deadlocks (#11034) * SharedData: Update locks to mention possible deadlocks * Update `Cargo.lock` --- client/consensus/common/src/shared_data.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/client/consensus/common/src/shared_data.rs b/client/consensus/common/src/shared_data.rs index 17217d997e236..fb04966b5db61 100644 --- a/client/consensus/common/src/shared_data.rs +++ b/client/consensus/common/src/shared_data.rs @@ -167,6 +167,13 @@ struct SharedDataInner { /// // As we don't know the order of the threads, we need to check for both combinations /// assert!(*data == "hello world321" || *data == "hello world312"); /// ``` +/// +/// # Deadlock +/// +/// Be aware that this data structure doesn't give you any guarantees that you can not create a +/// deadlock. If you use [`release_mutex`](SharedDataLocked::release_mutex) followed by a call +/// to [`shared_data`](Self::shared_data) in the same thread will make your program dead lock. +/// The same applies when you are using a single threaded executor. pub struct SharedData { inner: Arc>>, cond_var: Arc, From 0a9979781bf16e29a6b56ee9f181ddd31858acfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Tue, 15 Mar 2022 16:26:59 +0100 Subject: [PATCH 021/484] Stabilize `seal_delegate_call` (#11037) --- frame/contracts/fixtures/delegate_call.wat | 2 +- frame/contracts/src/benchmarking/mod.rs | 2 +- frame/contracts/src/tests.rs | 1 - frame/contracts/src/wasm/mod.rs | 2 +- frame/contracts/src/wasm/runtime.rs | 7 +------ 5 files changed, 4 insertions(+), 10 deletions(-) diff --git a/frame/contracts/fixtures/delegate_call.wat b/frame/contracts/fixtures/delegate_call.wat index e1c5fa813e590..7fe422af45511 100644 --- a/frame/contracts/fixtures/delegate_call.wat +++ b/frame/contracts/fixtures/delegate_call.wat @@ -2,7 +2,7 @@ (import "seal0" "seal_input" (func $seal_input (param i32 i32))) (import "seal0" "seal_get_storage" (func $seal_get_storage (param i32 i32 i32) (result i32))) (import "seal0" "seal_set_storage" (func $seal_set_storage (param i32 i32 i32))) - (import "__unstable__" "seal_delegate_call" (func $seal_delegate_call (param i32 i32 i32 i32 i32 i32) (result i32))) + (import "seal0" "seal_delegate_call" (func $seal_delegate_call (param i32 i32 i32 i32 i32 i32) (result i32))) (import "env" "memory" (memory 3 3)) ;; [0, 32) storage key diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs index 5fdc5c2ab7846..8539978bd6b39 100644 --- a/frame/contracts/src/benchmarking/mod.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -1492,7 +1492,7 @@ benchmarks! { let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "__unstable__", + module: "seal0", name: "seal_delegate_call", params: vec![ ValueType::I32, diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index 4c8c42c77da56..2b01cbe3c7429 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -762,7 +762,6 @@ fn deploy_and_call_other_contract() { } #[test] -#[cfg(feature = "unstable-interface")] fn delegate_call() { let (caller_wasm, caller_code_hash) = compile_module::("delegate_call").unwrap(); let (callee_wasm, callee_code_hash) = compile_module::("delegate_call_lib").unwrap(); diff --git a/frame/contracts/src/wasm/mod.rs b/frame/contracts/src/wasm/mod.rs index d89e7c1c8037d..3912a936684c2 100644 --- a/frame/contracts/src/wasm/mod.rs +++ b/frame/contracts/src/wasm/mod.rs @@ -618,7 +618,7 @@ mod tests { ;; output_ptr: u32, ;; output_len_ptr: u32 ;;) -> u32 - (import "__unstable__" "seal_delegate_call" (func $seal_delegate_call (param i32 i32 i32 i32 i32 i32) (result i32))) + (import "seal0" "seal_delegate_call" (func $seal_delegate_call (param i32 i32 i32 i32 i32 i32) (result i32))) (import "env" "memory" (memory 1 1)) (func (export "call") (drop diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index d008fa3f1dac0..043b45e6a76ed 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -191,7 +191,6 @@ pub enum RuntimeCosts { /// Base weight of calling `seal_call`. CallBase, /// Weight of calling `seal_delegate_call` for the given input size. - #[cfg(feature = "unstable-interface")] DelegateCallBase, /// Weight of the transfer performed during a call. CallSurchargeTransfer, @@ -272,7 +271,6 @@ impl RuntimeCosts { .saturating_add(s.take_storage_per_byte.saturating_mul(len.into())), Transfer => s.transfer, CallBase => s.call, - #[cfg(feature = "unstable-interface")] DelegateCallBase => s.delegate_call, CallSurchargeTransfer => s.call_transfer_surcharge, CallInputCloned(len) => s.call_per_cloned_byte.saturating_mul(len.into()), @@ -389,7 +387,6 @@ bitflags! { enum CallType { /// Execute another instantiated contract Call { callee_ptr: u32, value_ptr: u32, gas: u64 }, - #[cfg(feature = "unstable-interface")] /// Execute deployed code in the context (storage, account ID, value) of the caller contract DelegateCall { code_hash_ptr: u32 }, } @@ -398,7 +395,6 @@ impl CallType { fn cost(&self) -> RuntimeCosts { match self { CallType::Call { .. } => RuntimeCosts::CallBase, - #[cfg(feature = "unstable-interface")] CallType::DelegateCall { .. } => RuntimeCosts::DelegateCallBase, } } @@ -763,7 +759,6 @@ where flags.contains(CallFlags::ALLOW_REENTRY), ) }, - #[cfg(feature = "unstable-interface")] CallType::DelegateCall { code_hash_ptr } => { if flags.contains(CallFlags::ALLOW_REENTRY) { return Err(Error::::InvalidCallFlags.into()) @@ -1129,7 +1124,7 @@ define_env!(Env, , // `ReturnCode::CalleeReverted`: Output buffer is returned. // `ReturnCode::CalleeTrapped` // `ReturnCode::CodeNotFound` - [__unstable__] seal_delegate_call( + [seal0] seal_delegate_call( ctx, flags: u32, code_hash_ptr: u32, From 2f9f908aa5c8c04aa221de9cfa1ad6feab7ff446 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Mar 2022 09:47:16 +0000 Subject: [PATCH 022/484] Bump names from 0.12.0 to 0.13.0 (#11047) Bumps [names](https://github.com/fnichol/names) from 0.12.0 to 0.13.0. - [Release notes](https://github.com/fnichol/names/releases) - [Changelog](https://github.com/fnichol/names/blob/main/CHANGELOG.md) - [Commits](https://github.com/fnichol/names/compare/v0.12.0...v0.13.0) --- updated-dependencies: - dependency-name: names dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- client/cli/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 82dea83813d0b..903b76bff8023 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4719,9 +4719,9 @@ dependencies = [ [[package]] name = "names" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a8690bf09abf659851e58cd666c3d37ac6af07c2bd7a9e332cfba471715775" +checksum = "e7d66043b25d4a6cccb23619d10c19c25304b355a7dccd4a8e11423dd2382146" dependencies = [ "rand 0.8.4", ] diff --git a/client/cli/Cargo.toml b/client/cli/Cargo.toml index 8fdb192b43fb6..c799fd6dee980 100644 --- a/client/cli/Cargo.toml +++ b/client/cli/Cargo.toml @@ -20,7 +20,7 @@ futures = "0.3.19" hex = "0.4.2" libp2p = "0.40.0" log = "0.4.11" -names = { version = "0.12.0", default-features = false } +names = { version = "0.13.0", default-features = false } rand = "0.7.3" regex = "1.5.4" rpassword = "5.0.0" From 8eecb8067876f60e42b04cecc66ae65f1111f3c6 Mon Sep 17 00:00:00 2001 From: cheme Date: Wed, 16 Mar 2022 12:43:24 +0100 Subject: [PATCH 023/484] State migration rpc (#10981) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * setting flag * flag in storage struct * fix flagging to access and insert. * added todo to fix * also missing serialize meta to storage proof * extract meta. * Isolate old trie layout. * failing test that requires storing in meta when old hash scheme is used. * old hash compatibility * Db migrate. * runing tests with both states when interesting. * fix chain spec test with serde default. * export state (missing trie function). * Pending using new branch, lacking genericity on layout resolution. * extract and set global meta * Update to branch 4 * fix iterator with root flag (no longer insert node). * fix trie root hashing of root * complete basic backend. * Remove old_hash meta from proof that do not use inner_hashing. * fix trie test for empty (force layout on empty deltas). * Root update fix. * debug on meta * Use trie key iteration that do not include value in proofs. * switch default test ext to use inner hash. * small integration test, and fix tx cache mgmt in ext. test failing * Proof scenario at state-machine level. * trace for db upgrade * try different param * act more like iter_from. * Bigger batches. * Update trie dependency. * drafting codec changes and refact * before removing unused branch no value alt hashing. more work todo rename all flag var to alt_hash, and remove extrinsic replace by storage query at every storage_root call. * alt hashing only for branch with value. * fix trie tests * Hash of value include the encoded size. * removing fields(broken) * fix trie_stream to also include value length in inner hash. * triedbmut only using alt type if inner hashing. * trie_stream to also only use alt hashing type when actually alt hashing. * Refactor meta state, logic should work with change of trie treshold. * Remove NoMeta variant. * Remove state_hashed trigger specific functions. * pending switching to using threshold, new storage root api does not make much sense. * refactoring to use state from backend (not possible payload changes). * Applying from previous state * Remove default from storage, genesis need a special build. * rem empty space * Catch problem: when using triedb with default: we should not revert nodes: otherwhise thing as trie codec cannot decode-encode without changing state. * fix compilation * Right logic to avoid switch on reencode when default layout. * Clean up some todos * remove trie meta from root upstream * update upstream and fix benches. * split some long lines. * UPdate trie crate to work with new design. * Finish update to refactored upstream. * update to latest triedb changes. * Clean up. * fix executor test. * rust fmt from master. * rust format. * rustfmt * fix * start host function driven versioning * update state-machine part * still need access to state version from runtime * state hash in mem: wrong * direction likely correct, but passing call to code exec for genesis init seem awkward. * state version serialize in runtime, wrong approach, just initialize it with no threshold for core api < 4 seems more proper. * stateversion from runtime version (core api >= 4). * update trie, fix tests * unused import * clean some TODOs * Require RuntimeVersionOf for executor * use RuntimeVersionOf to resolve genesis state version. * update runtime version test * fix state-machine tests * TODO * Use runtime version from storage wasm with fast sync. * rustfmt * fmt * fix test * revert useless changes. * clean some unused changes * fmt * removing useless trait function. * remove remaining reference to state_hash * fix some imports * Follow chain state version management. * trie update, fix and constant threshold for trie layouts. * update deps * Update to latest trie pr changes. * fix benches * Verify proof requires right layout. * update trie_root * Update trie deps to latest * Update to latest trie versioning * Removing patch * update lock * extrinsic for sc-service-test using layout v0. * Adding RuntimeVersionOf to CallExecutor works. * fmt * error when resolving version and no wasm in storage. * use existing utils to instantiate runtime code. * migration pallet * Patch to delay runtime switch. * Revert "Patch to delay runtime switch." This reverts commit d35f273b7d67b1b85a9e72973cab13c5c156c1d3. * fix test * fix child migration calls. * useless closure * remove remaining state_hash variables. * Fix and add more tests * Remove outdated comment * useless inner hash * fmt * remote tests * finally ksm works * batches are broken * clean the benchmarks * Apply suggestions from code review Co-authored-by: Guillaume Thiolliere * Apply suggestions from code review Co-authored-by: Guillaume Thiolliere * Update frame/state-trie-migration/src/lib.rs Co-authored-by: Joshy Orndorff * Update frame/state-trie-migration/src/lib.rs * brand new version * fix build * Update frame/state-trie-migration/src/lib.rs Co-authored-by: Guillaume Thiolliere * Update frame/state-trie-migration/src/lib.rs Co-authored-by: Guillaume Thiolliere * Update primitives/storage/src/lib.rs Co-authored-by: cheme * Update frame/state-trie-migration/src/lib.rs Co-authored-by: cheme * Update frame/state-trie-migration/src/lib.rs Co-authored-by: cheme * fmt and opt-in feature to apply state change. * feature gate core version, use new test feature for node and test node * Use a 'State' api version instead of Core one. * fix merge of test function * use blake macro. * Fix state api (require declaring the api in runtime). * Opt out feature, fix macro for io to select a given version instead of latest. * run test nodes on new state. * fix * new test structure * new testing stuff from emeric * Add commit_all, still not working * Fix all tests * add comment * we have PoV tracking baby * document stuff, but proof size is still wrong * FUCK YEAH * a big batch of review comments * add more tests * tweak test * update config * some remote-ext stuff * delete some of the old stuff * sync more files with master to minimize the diff * Fix all tests * make signed migration a bit more relaxed * add witness check to signed submissions * allow custom migration to also go above limit * Fix these pesky tests * ==== removal of the unsigned stuff ==== * Make all tests work again * separate the tests from the logic so it can be reused easier * fix overall build * Update frame/state-trie-migration/src/lib.rs Co-authored-by: cheme * Update frame/state-trie-migration/src/lib.rs Co-authored-by: cheme * Slightly better termination * some final tweaks * Fix tests * Restrict access to signed migrations * mig rpc * fix * better rpc name * Make rpc unsafe * address most of the review comments * fix defensive * New simplified code * Fix weights * fmt * Update frame/state-trie-migration/src/lib.rs Co-authored-by: Bastian Köcher * make the tests correctly fail * Fix build * Fix build * try and fix the benchmarks * fix build * Fix cargo file * Fix runtime deposit * make rustdoc happy * cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_state_trie_migration --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/state-trie-migration/src/weights.rs --template=./.maintain/frame-weight-template.hbs * update rpc deps, try to process empty keys. * move rpc crate * move check backend out of state machine * Add primitive crate. * module code * fix runtime test * StateMigrationStatusProvider * Pass backend to rpc. * fmt * review changes * move rpc crate * try remove primitive crate * Update utils/frame/rpc/state-trie-migration-rpc/Cargo.toml Co-authored-by: Bastian Köcher * review changes. Co-authored-by: kianenigma Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Co-authored-by: Guillaume Thiolliere Co-authored-by: Joshy Orndorff Co-authored-by: Bastian Köcher Co-authored-by: Parity Bot --- Cargo.lock | 26 ++- Cargo.toml | 2 + bin/node/cli/src/service.rs | 3 +- bin/node/rpc/Cargo.toml | 1 + bin/node/rpc/src/lib.rs | 5 + frame/state-trie-migration/Cargo.toml | 5 +- frame/state-trie-migration/src/lib.rs | 176 +++++++++--------- primitives/state-machine/Cargo.toml | 2 - .../state-machine/src/trie_backend_essence.rs | 72 +------ .../rpc/state-trie-migration-rpc/Cargo.toml | 38 ++++ .../rpc/state-trie-migration-rpc/README.md | 3 + .../rpc/state-trie-migration-rpc/src/lib.rs | 164 ++++++++++++++++ 12 files changed, 332 insertions(+), 165 deletions(-) create mode 100644 utils/frame/rpc/state-trie-migration-rpc/Cargo.toml create mode 100644 utils/frame/rpc/state-trie-migration-rpc/README.md create mode 100644 utils/frame/rpc/state-trie-migration-rpc/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 903b76bff8023..e2321d7fa99d8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4987,6 +4987,7 @@ dependencies = [ "sp-keystore", "sp-runtime", "substrate-frame-rpc-system", + "substrate-state-trie-migration-rpc", ] [[package]] @@ -6472,6 +6473,7 @@ dependencies = [ "sp-runtime", "sp-std", "sp-tracing", + "substrate-state-trie-migration-rpc", "thousands", "tokio", "zstd", @@ -10300,7 +10302,6 @@ dependencies = [ "sp-trie", "thiserror", "tracing", - "trie-db", "trie-root", ] @@ -10628,6 +10629,29 @@ dependencies = [ "tokio", ] +[[package]] +name = "substrate-state-trie-migration-rpc" +version = "4.0.0-dev" +dependencies = [ + "jsonrpc-core", + "jsonrpc-core-client", + "jsonrpc-derive", + "log 0.4.14", + "parity-scale-codec", + "sc-client-api", + "sc-rpc-api", + "scale-info", + "serde", + "serde_json", + "sp-core", + "sp-io", + "sp-runtime", + "sp-state-machine", + "sp-std", + "sp-trie", + "trie-db", +] + [[package]] name = "substrate-test-client" version = "2.0.1" diff --git a/Cargo.toml b/Cargo.toml index bce23456b27e5..13657dd1234a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -119,6 +119,7 @@ members = [ "frame/staking", "frame/staking/reward-curve", "frame/staking/reward-fn", + "frame/state-trie-migration", "frame/sudo", "frame/support", "frame/support/procedural", @@ -210,6 +211,7 @@ members = [ "utils/frame/remote-externalities", "utils/frame/frame-utilities-cli", "utils/frame/try-runtime/cli", + "utils/frame/rpc/state-trie-migration-rpc", "utils/frame/rpc/support", "utils/frame/rpc/system", "utils/frame/generate-bags", diff --git a/bin/node/cli/src/service.rs b/bin/node/cli/src/service.rs index 10d39f278f5f3..038c14029cf31 100644 --- a/bin/node/cli/src/service.rs +++ b/bin/node/cli/src/service.rs @@ -252,6 +252,7 @@ pub fn new_partial( let keystore = keystore_container.sync_keystore(); let chain_spec = config.chain_spec.cloned_box(); + let rpc_backend = backend.clone(); let rpc_extensions_builder = move |deny_unsafe, subscription_executor| { let deps = node_rpc::FullDeps { client: client.clone(), @@ -273,7 +274,7 @@ pub fn new_partial( }, }; - node_rpc::create_full(deps).map_err(Into::into) + node_rpc::create_full(deps, rpc_backend.clone()).map_err(Into::into) }; (rpc_extensions_builder, rpc_setup) diff --git a/bin/node/rpc/Cargo.toml b/bin/node/rpc/Cargo.toml index 95ea3f8174fad..6c18e70f0d634 100644 --- a/bin/node/rpc/Cargo.toml +++ b/bin/node/rpc/Cargo.toml @@ -35,3 +35,4 @@ sp-consensus-babe = { version = "0.10.0-dev", path = "../../../primitives/consen sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../../client/transaction-pool/api" } substrate-frame-rpc-system = { version = "4.0.0-dev", path = "../../../utils/frame/rpc/system" } +substrate-state-trie-migration-rpc = { version = "4.0.0-dev", path = "../../../utils/frame/rpc/state-trie-migration-rpc/" } diff --git a/bin/node/rpc/src/lib.rs b/bin/node/rpc/src/lib.rs index 31f2f41086885..09f350ed3dcf1 100644 --- a/bin/node/rpc/src/lib.rs +++ b/bin/node/rpc/src/lib.rs @@ -99,6 +99,7 @@ pub type IoHandler = jsonrpc_core::IoHandler; /// Instantiate all Full RPC extensions. pub fn create_full( deps: FullDeps, + backend: Arc, ) -> Result, Box> where C: ProvideRuntimeApi @@ -159,6 +160,10 @@ where finality_provider, ))); + io.extend_with(substrate_state_trie_migration_rpc::StateMigrationApi::to_delegate( + substrate_state_trie_migration_rpc::MigrationRpc::new(client.clone(), backend, deny_unsafe), + )); + io.extend_with(sc_sync_state_rpc::SyncStateRpcApi::to_delegate( sc_sync_state_rpc::SyncStateRpcHandler::new( chain_spec, diff --git a/frame/state-trie-migration/Cargo.toml b/frame/state-trie-migration/Cargo.toml index fb8bccb52d1f2..762fd85f13b62 100644 --- a/frame/state-trie-migration/Cargo.toml +++ b/frame/state-trie-migration/Cargo.toml @@ -21,6 +21,7 @@ sp-std = { default-features = false, path = "../../primitives/std" } sp-io = { default-features = false, path = "../../primitives/io" } sp-core = { default-features = false, path = "../../primitives/core" } sp-runtime = { default-features = false, path = "../../primitives/runtime" } +substrate-state-trie-migration-rpc = { optional = true, path = "../../utils/frame/rpc/state-trie-migration-rpc" } frame-support = { default-features = false, path = "../support" } frame-system = { default-features = false, path = "../system" } @@ -49,9 +50,9 @@ std = [ "sp-core/std", "sp-io/std", "sp-runtime/std", - "sp-std/std" + "sp-std/std", ] runtime-benchmarks = ["frame-benchmarking"] try-runtime = ["frame-support/try-runtime"] -remote-test = [ "std", "zstd", "serde", "thousands", "remote-externalities" ] +remote-test = [ "std", "zstd", "serde", "thousands", "remote-externalities", "substrate-state-trie-migration-rpc" ] diff --git a/frame/state-trie-migration/src/lib.rs b/frame/state-trie-migration/src/lib.rs index 4de130e9ac06b..a5a077c54e579 100644 --- a/frame/state-trie-migration/src/lib.rs +++ b/frame/state-trie-migration/src/lib.rs @@ -123,6 +123,13 @@ pub mod pallet { } } + #[derive(Clone, Encode, Decode, scale_info::TypeInfo, PartialEq, Eq)] + pub(crate) enum Progress { + ToStart, + LastKey(Vec), + Complete, + } + /// A migration task stored in state. /// /// It tracks the last top and child keys read. @@ -130,20 +137,13 @@ pub mod pallet { #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] pub struct MigrationTask { - /// The last top key that we migrated. + /// The current top trie migration progress. + pub(crate) progress_top: Progress, + /// The current child trie migration progress. /// - /// If it does not exist, it means that the migration is done and no further keys exist. - pub(crate) last_top: Option>, - /// The last child key that we have processed. - /// - /// This is a child key under the current `self.last_top`. - /// - /// If this is set, no further top keys are processed until the child key migration is - /// complete. - pub(crate) last_child: Option>, - - /// A marker to indicate if the previous tick was a child tree migration or not. - pub(crate) prev_tick_child: bool, + /// If `ToStart`, no further top keys are processed until the child key migration is + /// `Complete`. + pub(crate) progress_child: Progress, /// Dynamic counter for the number of items that we have processed in this execution from /// the top trie. @@ -182,18 +182,22 @@ pub mod pallet { pub(crate) _ph: sp_std::marker::PhantomData, } + impl sp_std::fmt::Debug for Progress { + fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { + match self { + Progress::ToStart => f.write_str("To start"), + Progress::LastKey(key) => + write!(f, "Last: {:?}", sp_core::hexdisplay::HexDisplay::from(key)), + Progress::Complete => f.write_str("Complete"), + } + } + } + impl sp_std::fmt::Debug for MigrationTask { fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { f.debug_struct("MigrationTask") - .field( - "top", - &self.last_top.as_ref().map(|d| sp_core::hexdisplay::HexDisplay::from(d)), - ) - .field( - "child", - &self.last_child.as_ref().map(|d| sp_core::hexdisplay::HexDisplay::from(d)), - ) - .field("prev_tick_child", &self.prev_tick_child) + .field("top", &self.progress_top) + .field("child", &self.progress_child) .field("dyn_top_items", &self.dyn_top_items) .field("dyn_child_items", &self.dyn_child_items) .field("dyn_size", &self.dyn_size) @@ -207,12 +211,11 @@ pub mod pallet { impl Default for MigrationTask { fn default() -> Self { Self { - last_top: Some(Default::default()), - last_child: Default::default(), + progress_top: Progress::ToStart, + progress_child: Progress::ToStart, dyn_child_items: Default::default(), dyn_top_items: Default::default(), dyn_size: Default::default(), - prev_tick_child: Default::default(), _ph: Default::default(), size: Default::default(), top_items: Default::default(), @@ -224,7 +227,7 @@ pub mod pallet { impl MigrationTask { /// Return true if the task is finished. pub(crate) fn finished(&self) -> bool { - self.last_top.is_none() && self.last_child.is_none() + matches!(self.progress_top, Progress::Complete) } /// Check if there's any work left, or if we have exhausted the limits already. @@ -269,51 +272,36 @@ pub mod pallet { /// /// This function is *the* core of this entire pallet. fn migrate_tick(&mut self) { - match (self.last_top.as_ref(), self.last_child.as_ref()) { - (Some(_), Some(_)) => { + match (&self.progress_top, &self.progress_child) { + (Progress::ToStart, _) => { + self.migrate_top(); + }, + (Progress::LastKey(_), Progress::LastKey(_)) => { // we're in the middle of doing work on a child tree. self.migrate_child(); }, - (Some(ref top_key), None) => { - // we have a top key and no child key. 3 possibilities exist: - // 1. we continue the top key migrations. - // 2. this is the root of a child key, and we start processing child keys (and - // should call `migrate_child`). + (Progress::LastKey(top_key), Progress::ToStart) => { // 3. this is the root of a child key, and we are finishing all child-keys (and // should call `migrate_top`). // NOTE: this block is written intentionally to verbosely for easy of // verification. - match ( - top_key.starts_with(DEFAULT_CHILD_STORAGE_KEY_PREFIX), - self.prev_tick_child, - ) { - (false, false) => { - // continue the top key migration - self.migrate_top(); - }, - (true, false) => { - self.last_child = Some(Default::default()); - self.migrate_child(); - self.prev_tick_child = true; - }, - (true, true) => { - // we're done with migrating a child-root. - self.prev_tick_child = false; - self.migrate_top(); - }, - (false, true) => { - // should never happen. - log!(error, "LOGIC ERROR: unreachable code [0]."); - Pallet::::halt(); - }, - }; + if !top_key.starts_with(DEFAULT_CHILD_STORAGE_KEY_PREFIX) { + // we continue the top key migrations. + // continue the top key migration + self.migrate_top(); + } else { + // this is the root of a child key, and we start processing child keys (and + // should call `migrate_child`). + self.migrate_child(); + } }, - (None, Some(_)) => { - log!(error, "LOGIC ERROR: unreachable code [1]."); - Pallet::::halt() + (Progress::LastKey(_), Progress::Complete) => { + // we're done with migrating a child-root. + self.migrate_top(); + self.progress_child = Progress::ToStart; }, - (None, None) => { + (Progress::Complete, _) => { // nada }, } @@ -324,19 +312,26 @@ pub mod pallet { /// It updates the dynamic counters. fn migrate_child(&mut self) { use sp_io::default_child_storage as child_io; - let (last_child, last_top) = match (&self.last_child, &self.last_top) { - (Some(last_child), Some(last_top)) => (last_child, last_top), + let (maybe_current_child, child_root) = match (&self.progress_child, &self.progress_top) + { + (Progress::LastKey(last_child), Progress::LastKey(last_top)) => { + let child_root = Pallet::::transform_child_key_or_halt(&last_top); + let maybe_current_child = child_io::next_key(child_root, &last_child); + (maybe_current_child, child_root) + }, + (Progress::ToStart, Progress::LastKey(last_top)) => { + let child_root = Pallet::::transform_child_key_or_halt(&last_top); + // Start with the empty key as first key. + (Some(Vec::new()), child_root) + }, _ => { - // defensive: this function is only called when both of these values exist. - // much that we can do otherwise.. + // defensive: there must be an ongoing top migration. frame_support::defensive!("cannot migrate child key."); return }, }; - let child_root = Pallet::::transform_child_key_or_halt(&last_top); - let maybe_current_child = child_io::next_key(child_root, &last_child); - if let Some(ref current_child) = maybe_current_child { + if let Some(current_child) = maybe_current_child.as_ref() { let added_size = if let Some(data) = child_io::get(child_root, ¤t_child) { child_io::set(child_root, current_child, &data); data.len() as u32 @@ -348,25 +343,28 @@ pub mod pallet { } log!(trace, "migrated a child key, next_child_key: {:?}", maybe_current_child); - self.last_child = maybe_current_child; + self.progress_child = match maybe_current_child { + Some(last_child) => Progress::LastKey(last_child), + None => Progress::Complete, + } } /// Migrate the current top key, setting it to its new value, if one exists. /// /// It updates the dynamic counters. fn migrate_top(&mut self) { - let last_top = match &self.last_top { - Some(last_top) => last_top, - None => { - // defensive: this function is only called when this value exist. - // much that we can do otherwise.. + let maybe_current_top = match &self.progress_top { + Progress::LastKey(last_top) => sp_io::storage::next_key(last_top), + // Start with the empty key as first key. + Progress::ToStart => Some(Vec::new()), + Progress::Complete => { + // defensive: there must be an ongoing top migration. frame_support::defensive!("cannot migrate top key."); return }, }; - let maybe_current_top = sp_io::storage::next_key(last_top); - if let Some(ref current_top) = maybe_current_top { + if let Some(current_top) = maybe_current_top.as_ref() { let added_size = if let Some(data) = sp_io::storage::get(¤t_top) { sp_io::storage::set(¤t_top, &data); data.len() as u32 @@ -378,7 +376,10 @@ pub mod pallet { } log!(trace, "migrated a top key, next_top_key = {:?}", maybe_current_top); - self.last_top = maybe_current_top; + self.progress_top = match maybe_current_top { + Some(last_top) => Progress::LastKey(last_top), + None => Progress::Complete, + } } } @@ -811,7 +812,7 @@ mod benchmarks { continue_migrate_wrong_witness { let null = MigrationLimits::default(); let caller = frame_benchmarking::whitelisted_caller(); - let bad_witness = MigrationTask { last_top: Some(vec![1u8]), ..Default::default() }; + let bad_witness = MigrationTask { progress_top: Progress::LastKey(vec![1u8]), ..Default::default() }; }: { assert!( StateTrieMigration::::continue_migrate( @@ -1141,15 +1142,12 @@ mod test { } #[test] - #[ignore] fn detects_value_in_empty_top_key() { let limit = MigrationLimits { item: 1, size: 1000 }; let initial_keys = Some(vec![(vec![], vec![66u8; 77])]); let mut ext = new_test_ext(StateVersion::V0, false, initial_keys.clone(), None); let root_upgraded = ext.execute_with(|| { - sp_io::storage::set(&[], &vec![66u8; 77]); - AutoLimits::::put(Some(limit)); let root = run_to_block(30).0; @@ -1168,9 +1166,7 @@ mod test { } #[test] - #[ignore] fn detects_value_in_first_child_key() { - use frame_support::storage::child; let limit = MigrationLimits { item: 1, size: 1000 }; let initial_child = Some(vec![(b"chk1".to_vec(), vec![], vec![66u8; 77])]); let mut ext = new_test_ext(StateVersion::V0, false, None, initial_child.clone()); @@ -1186,7 +1182,6 @@ mod test { let mut ext2 = new_test_ext(StateVersion::V1, false, None, initial_child); let root = ext2.execute_with(|| { - child::put(&child::ChildInfo::new_default(b"chk1"), &[], &vec![66u8; 77]); AutoLimits::::put(Some(limit)); run_to_block(30).0 }); @@ -1214,7 +1209,7 @@ mod test { // eventually everything is over. assert!(matches!( StateTrieMigration::migration_process(), - MigrationTask { last_child: None, last_top: None, .. } + MigrationTask { progress_top: Progress::Complete, .. } )); root }); @@ -1276,7 +1271,10 @@ mod test { Origin::signed(1), MigrationLimits { item: 5, size: 100 }, 100, - MigrationTask { last_top: Some(vec![1u8]), ..Default::default() } + MigrationTask { + progress_top: Progress::LastKey(vec![1u8]), + ..Default::default() + } ), Error::::BadWitness ); @@ -1451,7 +1449,8 @@ pub(crate) mod remote_tests { // set the version to 1, as if the upgrade happened. ext.state_version = sp_core::storage::StateVersion::V1; - let (top_left, child_left) = ext.as_backend().essence().check_migration_state().unwrap(); + let (top_left, child_left) = + substrate_state_trie_migration_rpc::migration_status(&ext.as_backend()).unwrap(); assert!( top_left > 0, "no node needs migrating, this probably means that state was initialized with `StateVersion::V1`", @@ -1509,7 +1508,8 @@ pub(crate) mod remote_tests { ) }); - let (top_left, child_left) = ext.as_backend().essence().check_migration_state().unwrap(); + let (top_left, child_left) = + substrate_state_trie_migration_rpc::migration_status(&ext.as_backend()).unwrap(); assert_eq!(top_left, 0); assert_eq!(child_left, 0); } diff --git a/primitives/state-machine/Cargo.toml b/primitives/state-machine/Cargo.toml index 2a6ab80133ee6..80651130575ea 100644 --- a/primitives/state-machine/Cargo.toml +++ b/primitives/state-machine/Cargo.toml @@ -18,7 +18,6 @@ log = { version = "0.4.11", optional = true } thiserror = { version = "1.0.30", optional = true } parking_lot = { version = "0.12.0", optional = true } hash-db = { version = "0.15.2", default-features = false } -trie-db = { version = "0.23.1", default-features = false } trie-root = { version = "0.17.0", default-features = false } sp-trie = { version = "6.0.0", path = "../trie", default-features = false } sp-core = { version = "6.0.0", path = "../core", default-features = false } @@ -47,7 +46,6 @@ std = [ "sp-externalities/std", "sp-std/std", "sp-trie/std", - "trie-db/std", "trie-root/std", "log", "thiserror", diff --git a/primitives/state-machine/src/trie_backend_essence.rs b/primitives/state-machine/src/trie_backend_essence.rs index b0eb543824379..8531e4907d6a7 100644 --- a/primitives/state-machine/src/trie_backend_essence.rs +++ b/primitives/state-machine/src/trie_backend_essence.rs @@ -23,7 +23,7 @@ use codec::Encode; use hash_db::{self, AsHashDB, HashDB, HashDBRef, Hasher, Prefix}; #[cfg(feature = "std")] use parking_lot::RwLock; -use sp_core::storage::{ChildInfo, ChildType, PrefixedStorageKey, StateVersion}; +use sp_core::storage::{ChildInfo, ChildType, StateVersion}; use sp_std::{boxed::Box, vec::Vec}; use sp_trie::{ child_delta_trie_root, delta_trie_root, empty_child_trie_root, read_child_trie_value, @@ -36,10 +36,6 @@ use sp_trie::{ use std::collections::HashMap; #[cfg(feature = "std")] use std::sync::Arc; -use trie_db::{ - node::{NodePlan, ValuePlan}, - TrieDBNodeIterator, -}; #[cfg(not(feature = "std"))] macro_rules! format { @@ -433,72 +429,6 @@ where ); } - /// Check remaining state item to migrate. Note this function should be remove when all state - /// migration did finished as it is only an utility. - // original author: @cheme - pub fn check_migration_state(&self) -> Result<(u64, u64)> { - let threshold: u32 = sp_core::storage::TRIE_VALUE_NODE_THRESHOLD; - let mut nb_to_migrate = 0; - let mut nb_to_migrate_child = 0; - - let trie = sp_trie::trie_types::TrieDB::new(self, &self.root) - .map_err(|e| format!("TrieDB creation error: {}", e))?; - let iter_node = TrieDBNodeIterator::new(&trie) - .map_err(|e| format!("TrieDB node iterator error: {}", e))?; - for node in iter_node { - let node = node.map_err(|e| format!("TrieDB node iterator error: {}", e))?; - match node.2.node_plan() { - NodePlan::Leaf { value, .. } | - NodePlan::NibbledBranch { value: Some(value), .. } => - if let ValuePlan::Inline(range) = value { - if (range.end - range.start) as u32 >= threshold { - nb_to_migrate += 1; - } - }, - _ => (), - } - } - - let mut child_roots: Vec<(ChildInfo, Vec)> = Vec::new(); - // get all child trie roots - for key_value in trie.iter().map_err(|e| format!("TrieDB node iterator error: {}", e))? { - let (key, value) = - key_value.map_err(|e| format!("TrieDB node iterator error: {}", e))?; - if key[..] - .starts_with(sp_core::storage::well_known_keys::DEFAULT_CHILD_STORAGE_KEY_PREFIX) - { - let prefixed_key = PrefixedStorageKey::new(key); - let (_type, unprefixed) = ChildType::from_prefixed_key(&prefixed_key).unwrap(); - child_roots.push((ChildInfo::new_default(unprefixed), value)); - } - } - for (child_info, root) in child_roots { - let mut child_root = H::Out::default(); - let storage = KeySpacedDB::new(self, child_info.keyspace()); - - child_root.as_mut()[..].copy_from_slice(&root[..]); - let trie = sp_trie::trie_types::TrieDB::new(&storage, &child_root) - .map_err(|e| format!("New child TrieDB error: {}", e))?; - let iter_node = TrieDBNodeIterator::new(&trie) - .map_err(|e| format!("TrieDB node iterator error: {}", e))?; - for node in iter_node { - let node = node.map_err(|e| format!("Child TrieDB node iterator error: {}", e))?; - match node.2.node_plan() { - NodePlan::Leaf { value, .. } | - NodePlan::NibbledBranch { value: Some(value), .. } => - if let ValuePlan::Inline(range) = value { - if (range.end - range.start) as u32 >= threshold { - nb_to_migrate_child += 1; - } - }, - _ => (), - } - } - } - - Ok((nb_to_migrate, nb_to_migrate_child)) - } - /// Returns all `(key, value)` pairs in the trie. pub fn pairs(&self) -> Vec<(StorageKey, StorageValue)> { let collect_all = || -> sp_std::result::Result<_, Box>> { diff --git a/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml b/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml new file mode 100644 index 0000000000000..deb641a89a466 --- /dev/null +++ b/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "substrate-state-trie-migration-rpc" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Node-specific RPC methods for interaction with state trie migration." +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +serde = { version = "1", features = ["derive"] } +log = { version = "0.4.14", default-features = false } + +sp-std = { path = "../../../../primitives/std" } +sp-io = { path = "../../../../primitives/io" } +sp-core = { path = "../../../../primitives/core" } +sp-state-machine = { path = "../../../../primitives/state-machine" } +sp-trie = { path = "../../../../primitives/trie" } +trie-db = { version = "0.23.1" } + +jsonrpc-core = "18.0.0" +jsonrpc-core-client = "18.0.0" +jsonrpc-derive = "18.0.0" + +# Substrate Dependencies +sc-client-api = { version = "4.0.0-dev", path = "../../../../client/api" } +sc-rpc-api = { version = "0.10.0-dev", path = "../../../../client/rpc-api" } +sp-runtime = { version = "6.0.0", path = "../../../../primitives/runtime" } + +[dev-dependencies] +serde_json = "1" diff --git a/utils/frame/rpc/state-trie-migration-rpc/README.md b/utils/frame/rpc/state-trie-migration-rpc/README.md new file mode 100644 index 0000000000000..03bbfdf1b5939 --- /dev/null +++ b/utils/frame/rpc/state-trie-migration-rpc/README.md @@ -0,0 +1,3 @@ +Node-specific RPC methods for interaction with trie migration. + +License: Apache-2.0 diff --git a/utils/frame/rpc/state-trie-migration-rpc/src/lib.rs b/utils/frame/rpc/state-trie-migration-rpc/src/lib.rs new file mode 100644 index 0000000000000..98a3cf964843c --- /dev/null +++ b/utils/frame/rpc/state-trie-migration-rpc/src/lib.rs @@ -0,0 +1,164 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Rpc for state migration. + +use jsonrpc_core::{Error, ErrorCode, Result}; +use jsonrpc_derive::rpc; +use sc_rpc_api::DenyUnsafe; +use serde::{Deserialize, Serialize}; +use sp_runtime::{generic::BlockId, traits::Block as BlockT}; +use std::sync::Arc; + +use sp_core::{ + storage::{ChildInfo, ChildType, PrefixedStorageKey}, + Hasher, +}; +use sp_state_machine::Backend; +use sp_trie::{trie_types::TrieDB, KeySpacedDB, Trie}; +use trie_db::{ + node::{NodePlan, ValuePlan}, + TrieDBNodeIterator, +}; + +fn count_migrate<'a, H: Hasher>( + storage: &'a dyn trie_db::HashDBRef>, + root: &'a H::Out, +) -> std::result::Result<(u64, TrieDB<'a, H>), String> { + let mut nb = 0u64; + let trie = TrieDB::new(storage, root).map_err(|e| format!("TrieDB creation error: {}", e))?; + let iter_node = + TrieDBNodeIterator::new(&trie).map_err(|e| format!("TrieDB node iterator error: {}", e))?; + for node in iter_node { + let node = node.map_err(|e| format!("TrieDB node iterator error: {}", e))?; + match node.2.node_plan() { + NodePlan::Leaf { value, .. } | NodePlan::NibbledBranch { value: Some(value), .. } => + if let ValuePlan::Inline(range) = value { + if (range.end - range.start) as u32 >= + sp_core::storage::TRIE_VALUE_NODE_THRESHOLD + { + nb += 1; + } + }, + _ => (), + } + } + Ok((nb, trie)) +} + +/// Check trie migration status. +pub fn migration_status(backend: &B) -> std::result::Result<(u64, u64), String> +where + H: Hasher, + H::Out: codec::Codec, + B: Backend, +{ + let trie_backend = if let Some(backend) = backend.as_trie_backend() { + backend + } else { + return Err("No access to trie from backend.".to_string()) + }; + let essence = trie_backend.essence(); + let (nb_to_migrate, trie) = count_migrate(essence, &essence.root())?; + + let mut nb_to_migrate_child = 0; + let mut child_roots: Vec<(ChildInfo, Vec)> = Vec::new(); + // get all child trie roots + for key_value in trie.iter().map_err(|e| format!("TrieDB node iterator error: {}", e))? { + let (key, value) = key_value.map_err(|e| format!("TrieDB node iterator error: {}", e))?; + if key[..].starts_with(sp_core::storage::well_known_keys::DEFAULT_CHILD_STORAGE_KEY_PREFIX) + { + let prefixed_key = PrefixedStorageKey::new(key); + let (_type, unprefixed) = ChildType::from_prefixed_key(&prefixed_key).unwrap(); + child_roots.push((ChildInfo::new_default(unprefixed), value)); + } + } + for (child_info, root) in child_roots { + let mut child_root = H::Out::default(); + let storage = KeySpacedDB::new(essence, child_info.keyspace()); + + child_root.as_mut()[..].copy_from_slice(&root[..]); + nb_to_migrate_child += count_migrate(&storage, &child_root)?.0; + } + + Ok((nb_to_migrate, nb_to_migrate_child)) +} + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] +pub struct MigrationStatusResult { + top_remaining_to_migrate: u64, + child_remaining_to_migrate: u64, +} + +/// Migration RPC methods. +#[rpc] +pub trait StateMigrationApi { + /// Check current migration state. + /// + /// This call is performed locally without submitting any transactions. Thus executing this + /// won't change any state. Nonetheless it is a VERY costy call that should be + /// only exposed to trusted peers. + #[rpc(name = "state_trieMigrationStatus")] + fn call(&self, at: Option) -> Result; +} + +/// An implementation of state migration specific RPC methods. +pub struct MigrationRpc { + client: Arc, + backend: Arc, + deny_unsafe: DenyUnsafe, + _marker: std::marker::PhantomData<(B, BA)>, +} + +impl MigrationRpc { + /// Create new state migration rpc for the given reference to the client. + pub fn new(client: Arc, backend: Arc, deny_unsafe: DenyUnsafe) -> Self { + MigrationRpc { client, backend, deny_unsafe, _marker: Default::default() } + } +} + +impl StateMigrationApi<::Hash> for MigrationRpc +where + B: BlockT, + C: Send + Sync + 'static + sc_client_api::HeaderBackend, + BA: 'static + sc_client_api::backend::Backend, +{ + fn call(&self, at: Option<::Hash>) -> Result { + if let Err(err) = self.deny_unsafe.check_if_safe() { + return Err(err.into()) + } + + let block_id = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash)); + let state = self.backend.state_at(block_id).map_err(error_into_rpc_err)?; + let (top, child) = migration_status(&state).map_err(error_into_rpc_err)?; + + Ok(MigrationStatusResult { + top_remaining_to_migrate: top, + child_remaining_to_migrate: child, + }) + } +} + +fn error_into_rpc_err(err: impl std::fmt::Display) -> Error { + Error { + code: ErrorCode::InternalError, + message: "Error while checking migration state".into(), + data: Some(err.to_string().into()), + } +} From d21c6f12b8a36043930ac4c888debba34727751b Mon Sep 17 00:00:00 2001 From: Falco Hirschenberger Date: Wed, 16 Mar 2022 17:05:34 +0100 Subject: [PATCH 024/484] Prevent possible rocksdb-corruption by running benchmark-storage (#11040) * Prevent possible rocksdb-corruption by running benchmark-storage Also adds a `sanitize_key` function to strip path-prefixes from the db-keys (for databases that use prefixed keys such as rocksdb) fixes #10998 * Fix @cheme 's annotations. * Update utils/frame/benchmarking-cli/src/storage/write.rs Co-authored-by: Oliver Tale-Yazdi * Make logic match the name of bool flag `invert_inserts` * Remove unused lifetime Co-authored-by: Oliver Tale-Yazdi --- client/db/src/lib.rs | 8 +-- client/db/src/parity_db.rs | 4 ++ primitives/database/src/lib.rs | 5 ++ .../benchmarking-cli/src/storage/write.rs | 64 ++++++++++--------- 4 files changed, 46 insertions(+), 35 deletions(-) diff --git a/client/db/src/lib.rs b/client/db/src/lib.rs index f451062de33ab..6a8a025f1f45f 100644 --- a/client/db/src/lib.rs +++ b/client/db/src/lib.rs @@ -107,7 +107,8 @@ pub type DbState = sp_state_machine::TrieBackend>>, HashFor>; /// Length of a [`DbHash`]. -pub const DB_HASH_LEN: usize = 32; +const DB_HASH_LEN: usize = 32; + /// Hash type that this backend uses for the database. pub type DbHash = sp_core::H256; @@ -1351,10 +1352,7 @@ impl Backend { let mut removal: u64 = 0; let mut bytes_removal: u64 = 0; for (mut key, (val, rc)) in operation.db_updates.drain() { - if !self.storage.prefix_keys { - // Strip prefix - key.drain(0..key.len() - DB_HASH_LEN); - }; + self.storage.db.sanitize_key(&mut key); if rc > 0 { ops += 1; bytes += key.len() as u64 + val.len() as u64; diff --git a/client/db/src/parity_db.rs b/client/db/src/parity_db.rs index c27d7b0d4b4db..c81a346bb023f 100644 --- a/client/db/src/parity_db.rs +++ b/client/db/src/parity_db.rs @@ -112,4 +112,8 @@ impl> Database for DbAdapter { fn supports_ref_counting(&self) -> bool { true } + + fn sanitize_key(&self, key: &mut Vec) { + let _prefix = key.drain(0..key.len() - crate::DB_HASH_LEN); + } } diff --git a/primitives/database/src/lib.rs b/primitives/database/src/lib.rs index 2d7c5fd6653e1..3ec62a2b78148 100644 --- a/primitives/database/src/lib.rs +++ b/primitives/database/src/lib.rs @@ -110,6 +110,11 @@ pub trait Database>: Send + Sync { fn supports_ref_counting(&self) -> bool { false } + + /// Remove a possible path-prefix from the key. + /// + /// Not all database implementations use a prefix for keys, so this function may be a noop. + fn sanitize_key(&self, _key: &mut Vec) {} } impl std::fmt::Debug for dyn Database { diff --git a/utils/frame/benchmarking-cli/src/storage/write.rs b/utils/frame/benchmarking-cli/src/storage/write.rs index eb9ba11f30696..94a0eea9728ff 100644 --- a/utils/frame/benchmarking-cli/src/storage/write.rs +++ b/utils/frame/benchmarking-cli/src/storage/write.rs @@ -17,7 +17,7 @@ use sc_cli::Result; use sc_client_api::UsageProvider; -use sc_client_db::{DbHash, DbState, DB_HASH_LEN}; +use sc_client_db::{DbHash, DbState}; use sp_api::StateBackend; use sp_blockchain::HeaderBackend; use sp_database::{ColumnId, Transaction}; @@ -27,7 +27,7 @@ use sp_runtime::{ }; use sp_trie::PrefixedMemoryDB; -use log::info; +use log::{info, trace}; use rand::prelude::*; use std::{fmt::Debug, sync::Arc, time::Instant}; @@ -50,7 +50,6 @@ impl StorageCmd { // Store the time that it took to write each value. let mut record = BenchRecord::default(); - let supports_rc = db.supports_ref_counting(); let block = BlockId::Number(client.usage_info().chain.best_number); let header = client.header(block)?.ok_or("Header not found")?; let original_root = *header.state_root(); @@ -62,15 +61,34 @@ impl StorageCmd { let mut rng = Self::setup_rng(); kvs.shuffle(&mut rng); + // Generate all random values first; Make sure there are no collisions with existing + // db entries, so we can rollback all additions without corrupting existing entries. + for (k, original_v) in kvs.iter_mut() { + 'retry: loop { + let mut new_v = vec![0; original_v.len()]; + // Create a random value to overwrite with. + // NOTE: We use a possibly higher entropy than the original value, + // could be improved but acts as an over-estimation which is fine for now. + rng.fill_bytes(&mut new_v[..]); + let new_kv = vec![(k.as_ref(), Some(new_v.as_ref()))]; + let (_, mut stx) = trie.storage_root(new_kv.iter().cloned(), self.state_version()); + for (mut k, (_, rc)) in stx.drain().into_iter() { + if rc > 0 { + db.sanitize_key(&mut k); + if db.get(state_col, &k).is_some() { + trace!("Benchmark-store key creation: Key collision detected, retry"); + continue 'retry + } + } + } + *original_v = new_v; + break + } + } + info!("Writing {} keys", kvs.len()); // Write each value in one commit. - for (k, original_v) in kvs.iter() { - // Create a random value to overwrite with. - // NOTE: We use a possibly higher entropy than the original value, - // could be improved but acts as an over-estimation which is fine for now. - let mut new_v = vec![0; original_v.len()]; - rng.fill_bytes(&mut new_v[..]); - + for (k, new_v) in kvs.iter() { // Interesting part here: let start = Instant::now(); // Create a TX that will modify the Trie in the DB and @@ -78,12 +96,12 @@ impl StorageCmd { let replace = vec![(k.as_ref(), Some(new_v.as_ref()))]; let (_, stx) = trie.storage_root(replace.iter().cloned(), self.state_version()); // Only the keep the insertions, since we do not want to benchmark pruning. - let tx = convert_tx::(stx.clone(), true, state_col, supports_rc); + let tx = convert_tx::(db.clone(), stx.clone(), false, state_col); db.commit(tx).map_err(|e| format!("Writing to the Database: {}", e))?; record.append(new_v.len(), start.elapsed())?; // Now undo the changes by removing what was added. - let tx = convert_tx::(stx.clone(), false, state_col, supports_rc); + let tx = convert_tx::(db.clone(), stx.clone(), true, state_col); db.commit(tx).map_err(|e| format!("Writing to the Database: {}", e))?; } Ok(record) @@ -93,35 +111,21 @@ impl StorageCmd { /// Converts a Trie transaction into a DB transaction. /// Removals are ignored and will not be included in the final tx. /// `invert_inserts` replaces all inserts with removals. -/// -/// The keys of Trie transactions are prefixed, this is treated differently by each DB. -/// ParityDB can use an optimization where only the last `DB_HASH_LEN` byte are needed. -/// The last `DB_HASH_LEN` byte are the hash of the actual stored data, everything -/// before that is the route in the Patricia Trie. -/// RocksDB cannot do this and needs the whole route, hence no key truncating for RocksDB. -/// -/// TODO: -/// This copies logic from [`sp_client_db::Backend::try_commit_operation`] and should be -/// refactored to use a canonical `sanitize_key` function from `sp_client_db` which -/// does not yet exist. fn convert_tx( + db: Arc>, mut tx: PrefixedMemoryDB>, invert_inserts: bool, col: ColumnId, - supports_rc: bool, ) -> Transaction { let mut ret = Transaction::::default(); for (mut k, (v, rc)) in tx.drain().into_iter() { - if supports_rc { - let _prefix = k.drain(0..k.len() - DB_HASH_LEN); - } - if rc > 0 { + db.sanitize_key(&mut k); if invert_inserts { - ret.set(col, k.as_ref(), &v); - } else { ret.remove(col, &k); + } else { + ret.set(col, &k, &v); } } // < 0 means removal - ignored. From c7b8e6d9fdcaea6017570e9074ce184e6f2d3c27 Mon Sep 17 00:00:00 2001 From: Georges Date: Wed, 16 Mar 2022 21:27:19 +0000 Subject: [PATCH 025/484] Moving `NposSolution` to frame (#11031) * Move `sp-npos-elections-solution-type` to `frame-election-provider-support` First stab at it, will need to amend some more stuff * Fixing tests * Fixing tests * Fixing cargo.toml for std configuration * fmt * Committing suggested changes renaming, and re exporting macro. * Removing unneeded imports * Move `NposSolution` to frame * Removing `npos_election` dependencies Implementing _fpes better * some feedback for moving NPoSSolution to frame * fmt * more formatting * Fixed some imports and fmt * Fixing docs Co-authored-by: kianenigma --- Cargo.lock | 4 +- bin/node/runtime/Cargo.toml | 2 - bin/node/runtime/src/lib.rs | 16 +-- .../src/benchmarking.rs | 2 +- .../election-provider-multi-phase/src/lib.rs | 5 +- .../election-provider-multi-phase/src/mock.rs | 4 +- .../src/signed.rs | 3 +- .../src/unsigned.rs | 12 +- .../solution-type/Cargo.toml | 2 +- .../solution-type/fuzzer/Cargo.toml | 1 + .../solution-type/src/codec.rs | 72 +++++----- .../solution-type/src/lib.rs | 14 +- .../solution-type/src/single_page.rs | 62 ++++---- frame/election-provider-support/src/lib.rs | 77 +++++++++- frame/election-provider-support/src/traits.rs | 129 +++++++++++++++++ frame/staking/src/pallet/mod.rs | 8 +- primitives/npos-elections/src/assignments.rs | 43 +----- primitives/npos-elections/src/lib.rs | 18 +-- primitives/npos-elections/src/traits.rs | 132 +----------------- 19 files changed, 313 insertions(+), 293 deletions(-) create mode 100644 frame/election-provider-support/src/traits.rs diff --git a/Cargo.lock b/Cargo.lock index e2321d7fa99d8..d3cc9ab0d4320 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2164,13 +2164,13 @@ dependencies = [ name = "frame-election-provider-solution-type" version = "4.0.0-dev" dependencies = [ + "frame-election-provider-support", "parity-scale-codec", "proc-macro-crate 1.1.0", "proc-macro2", "quote", "scale-info", "sp-arithmetic", - "sp-npos-elections", "syn", "trybuild", ] @@ -2198,6 +2198,7 @@ version = "2.0.0-alpha.5" dependencies = [ "clap 3.1.6", "frame-election-provider-solution-type", + "frame-election-provider-support", "honggfuzz", "parity-scale-codec", "rand 0.8.4", @@ -5065,7 +5066,6 @@ dependencies = [ "sp-core", "sp-inherents", "sp-io", - "sp-npos-elections", "sp-offchain", "sp-runtime", "sp-sandbox", diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 0572345fa0c89..686508b47dba1 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -38,7 +38,6 @@ sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../.. sp-session = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/session" } sp-transaction-pool = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/transaction-pool" } sp-version = { version = "5.0.0", default-features = false, path = "../../../primitives/version" } -sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/npos-elections" } sp-io = { version = "6.0.0", default-features = false, path = "../../../primitives/io" } sp-sandbox = { version = "0.10.0-dev", default-features = false, path = "../../../primitives/sandbox" } @@ -178,7 +177,6 @@ std = [ "pallet-vesting/std", "log/std", "frame-try-runtime/std", - "sp-npos-elections/std", "sp-io/std", "pallet-child-bounties/std", ] diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 20b718e2fa8f7..797e0c079028e 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -23,9 +23,11 @@ #![recursion_limit = "256"] use codec::{Decode, Encode, MaxEncodedLen}; -use frame_election_provider_support::onchain; +use frame_election_provider_support::{onchain, ExtendedBalance, VoteWeight}; use frame_support::{ - construct_runtime, parameter_types, + construct_runtime, + pallet_prelude::Get, + parameter_types, traits::{ AsEnsureOriginWithArg, ConstU128, ConstU16, ConstU32, Currency, EnsureOneOf, EqualPrivilegeOnly, Everything, Imbalance, InstanceFilter, KeyOwnerProofSystem, @@ -598,7 +600,7 @@ frame_election_provider_support::generate_solution_type!( ); parameter_types! { - pub MaxNominations: u32 = ::LIMIT as u32; + pub MaxNominations: u32 = ::LIMIT as u32; } /// The numbers configured here could always be more than the the maximum limits of staking pallet @@ -621,10 +623,8 @@ pub const MINER_MAX_ITERATIONS: u32 = 10; /// A source of random balance for NposSolver, which is meant to be run by the OCW election miner. pub struct OffchainRandomBalancing; -impl frame_support::pallet_prelude::Get> - for OffchainRandomBalancing -{ - fn get() -> Option<(usize, sp_npos_elections::ExtendedBalance)> { +impl Get> for OffchainRandomBalancing { + fn get() -> Option<(usize, ExtendedBalance)> { use sp_runtime::traits::TrailingZeroInput; let iters = match MINER_MAX_ITERATIONS { 0 => 0, @@ -693,7 +693,7 @@ impl pallet_bags_list::Config for Runtime { type ScoreProvider = Staking; type WeightInfo = pallet_bags_list::weights::SubstrateWeight; type BagThresholds = BagThresholds; - type Score = sp_npos_elections::VoteWeight; + type Score = VoteWeight; } parameter_types! { diff --git a/frame/election-provider-multi-phase/src/benchmarking.rs b/frame/election-provider-multi-phase/src/benchmarking.rs index 479afd9843386..b331516a4e797 100644 --- a/frame/election-provider-multi-phase/src/benchmarking.rs +++ b/frame/election-provider-multi-phase/src/benchmarking.rs @@ -28,7 +28,6 @@ use frame_support::{ use frame_system::RawOrigin; use rand::{prelude::SliceRandom, rngs::SmallRng, SeedableRng}; use sp_arithmetic::{per_things::Percent, traits::One}; -use sp_npos_elections::IndexAssignment; use sp_runtime::InnerOf; const SEED: u32 = 999; @@ -461,6 +460,7 @@ frame_benchmarking::benchmarks! { T::BenchmarkingConfig::DESIRED_TARGETS[1]; // Subtract this percentage from the actual encoded size let f in 0 .. 95; + use frame_election_provider_support::IndexAssignment; // Compute a random solution, then work backwards to get the lists of voters, targets, and // assignments diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 7e211c5ee9211..b366ceb4dafe3 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -231,7 +231,7 @@ use codec::{Decode, Encode}; use frame_election_provider_support::{ - ElectionDataProvider, ElectionProvider, InstantElectionProvider, + ElectionDataProvider, ElectionProvider, InstantElectionProvider, NposSolution, }; use frame_support::{ dispatch::DispatchResultWithPostInfo, @@ -246,8 +246,7 @@ use sp_arithmetic::{ UpperOf, }; use sp_npos_elections::{ - assignment_ratio_to_staked_normalized, ElectionScore, EvaluateSupport, NposSolution, Supports, - VoteWeight, + assignment_ratio_to_staked_normalized, ElectionScore, EvaluateSupport, Supports, VoteWeight, }; use sp_runtime::{ traits::Bounded, diff --git a/frame/election-provider-multi-phase/src/mock.rs b/frame/election-provider-multi-phase/src/mock.rs index 7c4ef5d8055c3..d09d45784cba4 100644 --- a/frame/election-provider-multi-phase/src/mock.rs +++ b/frame/election-provider-multi-phase/src/mock.rs @@ -18,7 +18,7 @@ use super::*; use crate as multi_phase; use frame_election_provider_support::{ - data_provider, onchain, ElectionDataProvider, SequentialPhragmen, + data_provider, onchain, ElectionDataProvider, NposSolution, SequentialPhragmen, }; pub use frame_support::{assert_noop, assert_ok}; use frame_support::{ @@ -38,7 +38,7 @@ use sp_core::{ }; use sp_npos_elections::{ assignment_ratio_to_staked_normalized, seq_phragmen, to_supports, ElectionResult, - EvaluateSupport, ExtendedBalance, NposSolution, + EvaluateSupport, ExtendedBalance, }; use sp_runtime::{ testing::Header, diff --git a/frame/election-provider-multi-phase/src/signed.rs b/frame/election-provider-multi-phase/src/signed.rs index a233346b4fd77..82b40e3276036 100644 --- a/frame/election-provider-multi-phase/src/signed.rs +++ b/frame/election-provider-multi-phase/src/signed.rs @@ -23,12 +23,13 @@ use crate::{ SolutionOrSnapshotSize, Weight, WeightInfo, }; use codec::{Decode, Encode, HasCompact}; +use frame_election_provider_support::NposSolution; use frame_support::{ storage::bounded_btree_map::BoundedBTreeMap, traits::{defensive_prelude::*, Currency, Get, OnUnbalanced, ReservableCurrency}, }; use sp_arithmetic::traits::SaturatedConversion; -use sp_npos_elections::{ElectionScore, NposSolution}; +use sp_npos_elections::ElectionScore; use sp_runtime::{ traits::{Saturating, Zero}, RuntimeDebug, diff --git a/frame/election-provider-multi-phase/src/unsigned.rs b/frame/election-provider-multi-phase/src/unsigned.rs index 81ea4453d0964..3380a61d4310e 100644 --- a/frame/election-provider-multi-phase/src/unsigned.rs +++ b/frame/election-provider-multi-phase/src/unsigned.rs @@ -23,12 +23,11 @@ use crate::{ WeightInfo, }; use codec::Encode; -use frame_election_provider_support::{NposSolver, PerThing128}; +use frame_election_provider_support::{NposSolution, NposSolver, PerThing128}; use frame_support::{dispatch::DispatchResult, ensure, traits::Get}; use frame_system::offchain::SubmitTransaction; use sp_npos_elections::{ assignment_ratio_to_staked_normalized, assignment_staked_to_ratio_normalized, ElectionResult, - NposSolution, }; use sp_runtime::{ offchain::storage::{MutateStorageError, StorageValueRef}, @@ -52,9 +51,9 @@ pub type VoterOf = frame_election_provider_support::VoterOf<::Da pub type Assignment = sp_npos_elections::Assignment<::AccountId, SolutionAccuracyOf>; -/// The [`IndexAssignment`][sp_npos_elections::IndexAssignment] type specialized for a particular -/// runtime `T`. -pub type IndexAssignmentOf = sp_npos_elections::IndexAssignmentOf>; +/// The [`IndexAssignment`][frame_election_provider_support::IndexAssignment] type specialized for a +/// particular runtime `T`. +pub type IndexAssignmentOf = frame_election_provider_support::IndexAssignmentOf>; /// Error type of the pallet's [`crate::Config::Solver`]. pub type SolverErrorOf = <::Solver as NposSolver>::Error; @@ -742,10 +741,11 @@ mod tests { }; use codec::Decode; use frame_benchmarking::Zero; + use frame_election_provider_support::IndexAssignment; use frame_support::{ assert_noop, assert_ok, bounded_vec, dispatch::Dispatchable, traits::OffchainWorker, }; - use sp_npos_elections::{ElectionScore, IndexAssignment}; + use sp_npos_elections::ElectionScore; use sp_runtime::{ offchain::storage_lock::{BlockAndTime, StorageLock}, traits::ValidateUnsigned, diff --git a/frame/election-provider-support/solution-type/Cargo.toml b/frame/election-provider-support/solution-type/Cargo.toml index d489c7b4c10de..09747eebe2dff 100644 --- a/frame/election-provider-support/solution-type/Cargo.toml +++ b/frame/election-provider-support/solution-type/Cargo.toml @@ -25,5 +25,5 @@ parity-scale-codec = "3.0.0" scale-info = "2.0.1" sp-arithmetic = { version = "5.0.0", path = "../../../primitives/arithmetic" } # used by generate_solution_type: -sp-npos-elections = { version = "4.0.0-dev", path = "../../../primitives/npos-elections" } +frame-election-provider-support = { version = "4.0.0-dev", path = ".." } trybuild = "1.0.53" diff --git a/frame/election-provider-support/solution-type/fuzzer/Cargo.toml b/frame/election-provider-support/solution-type/fuzzer/Cargo.toml index f52b7f7332620..f6c2f2fe491e8 100644 --- a/frame/election-provider-support/solution-type/fuzzer/Cargo.toml +++ b/frame/election-provider-support/solution-type/fuzzer/Cargo.toml @@ -20,6 +20,7 @@ rand = { version = "0.8", features = ["std", "small_rng"] } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } frame-election-provider-solution-type = { version = "4.0.0-dev", path = ".." } +frame-election-provider-support = { version = "4.0.0-dev", path = "../.." } sp-arithmetic = { version = "5.0.0", path = "../../../../primitives/arithmetic" } sp-runtime = { version = "6.0.0", path = "../../../../primitives/runtime" } # used by generate_solution_type: diff --git a/frame/election-provider-support/solution-type/src/codec.rs b/frame/election-provider-support/solution-type/src/codec.rs index 71faa141f0b69..05d753d3d8456 100644 --- a/frame/election-provider-support/solution-type/src/codec.rs +++ b/frame/election-provider-support/solution-type/src/codec.rs @@ -51,14 +51,14 @@ fn decode_impl( quote! { let #name = < - _npos::sp_std::prelude::Vec<(_npos::codec::Compact<#voter_type>, _npos::codec::Compact<#target_type>)> + _feps::sp_std::prelude::Vec<(_feps::codec::Compact<#voter_type>, _feps::codec::Compact<#target_type>)> as - _npos::codec::Decode + _feps::codec::Decode >::decode(value)?; let #name = #name .into_iter() .map(|(v, t)| (v.0, t.0)) - .collect::<_npos::sp_std::prelude::Vec<_>>(); + .collect::<_feps::sp_std::prelude::Vec<_>>(); } }; @@ -73,12 +73,12 @@ fn decode_impl( quote! { let #name = < - _npos::sp_std::prelude::Vec<( - _npos::codec::Compact<#voter_type>, - [(_npos::codec::Compact<#target_type>, _npos::codec::Compact<#weight_type>); #c-1], - _npos::codec::Compact<#target_type>, + _feps::sp_std::prelude::Vec<( + _feps::codec::Compact<#voter_type>, + [(_feps::codec::Compact<#target_type>, _feps::codec::Compact<#weight_type>); #c-1], + _feps::codec::Compact<#target_type>, )> - as _npos::codec::Decode + as _feps::codec::Decode >::decode(value)?; let #name = #name .into_iter() @@ -87,7 +87,7 @@ fn decode_impl( [ #inner_impl ], t_last.0, )) - .collect::<_npos::sp_std::prelude::Vec<_>>(); + .collect::<_feps::sp_std::prelude::Vec<_>>(); } }) .collect::(); @@ -100,8 +100,8 @@ fn decode_impl( .collect::(); quote!( - impl _npos::codec::Decode for #ident { - fn decode(value: &mut I) -> Result { + impl _feps::codec::Decode for #ident { + fn decode(value: &mut I) -> Result { #decode_impl_single #decode_impl_rest @@ -123,10 +123,10 @@ fn encode_impl(ident: &syn::Ident, count: usize) -> TokenStream2 { let #name = self.#name .iter() .map(|(v, t)| ( - _npos::codec::Compact(v.clone()), - _npos::codec::Compact(t.clone()), + _feps::codec::Compact(v.clone()), + _feps::codec::Compact(t.clone()), )) - .collect::<_npos::sp_std::prelude::Vec<_>>(); + .collect::<_feps::sp_std::prelude::Vec<_>>(); #name.encode_to(&mut r); } }; @@ -139,8 +139,8 @@ fn encode_impl(ident: &syn::Ident, count: usize) -> TokenStream2 { let inners_solution_array = (0..c - 1) .map(|i| { quote! {( - _npos::codec::Compact(inner[#i].0.clone()), - _npos::codec::Compact(inner[#i].1.clone()), + _feps::codec::Compact(inner[#i].0.clone()), + _feps::codec::Compact(inner[#i].1.clone()), ),} }) .collect::(); @@ -149,19 +149,19 @@ fn encode_impl(ident: &syn::Ident, count: usize) -> TokenStream2 { let #name = self.#name .iter() .map(|(v, inner, t_last)| ( - _npos::codec::Compact(v.clone()), + _feps::codec::Compact(v.clone()), [ #inners_solution_array ], - _npos::codec::Compact(t_last.clone()), + _feps::codec::Compact(t_last.clone()), )) - .collect::<_npos::sp_std::prelude::Vec<_>>(); + .collect::<_feps::sp_std::prelude::Vec<_>>(); #name.encode_to(&mut r); } }) .collect::(); quote!( - impl _npos::codec::Encode for #ident { - fn encode(&self) -> _npos::sp_std::prelude::Vec { + impl _feps::codec::Encode for #ident { + fn encode(&self) -> _feps::sp_std::prelude::Vec { let mut r = vec![]; #encode_impl_single #encode_impl_rest @@ -182,8 +182,8 @@ fn scale_info_impl( let name = format!("{}", vote_field(1)); quote! { .field(|f| - f.ty::<_npos::sp_std::prelude::Vec< - (_npos::codec::Compact<#voter_type>, _npos::codec::Compact<#target_type>) + f.ty::<_feps::sp_std::prelude::Vec< + (_feps::codec::Compact<#voter_type>, _feps::codec::Compact<#target_type>) >>() .name(#name) ) @@ -194,10 +194,10 @@ fn scale_info_impl( let name = format!("{}", vote_field(2)); quote! { .field(|f| - f.ty::<_npos::sp_std::prelude::Vec<( - _npos::codec::Compact<#voter_type>, - (_npos::codec::Compact<#target_type>, _npos::codec::Compact<#weight_type>), - _npos::codec::Compact<#target_type> + f.ty::<_feps::sp_std::prelude::Vec<( + _feps::codec::Compact<#voter_type>, + (_feps::codec::Compact<#target_type>, _feps::codec::Compact<#weight_type>), + _feps::codec::Compact<#target_type> )>>() .name(#name) ) @@ -209,13 +209,13 @@ fn scale_info_impl( let name = format!("{}", vote_field(c)); quote! { .field(|f| - f.ty::<_npos::sp_std::prelude::Vec<( - _npos::codec::Compact<#voter_type>, + f.ty::<_feps::sp_std::prelude::Vec<( + _feps::codec::Compact<#voter_type>, [ - (_npos::codec::Compact<#target_type>, _npos::codec::Compact<#weight_type>); + (_feps::codec::Compact<#target_type>, _feps::codec::Compact<#weight_type>); #c - 1 ], - _npos::codec::Compact<#target_type> + _feps::codec::Compact<#target_type> )>>() .name(#name) ) @@ -224,14 +224,14 @@ fn scale_info_impl( .collect::(); quote!( - impl _npos::scale_info::TypeInfo for #ident { + impl _feps::scale_info::TypeInfo for #ident { type Identity = Self; - fn type_info() -> _npos::scale_info::Type<_npos::scale_info::form::MetaForm> { - _npos::scale_info::Type::builder() - .path(_npos::scale_info::Path::new(stringify!(#ident), module_path!())) + fn type_info() -> _feps::scale_info::Type<_feps::scale_info::form::MetaForm> { + _feps::scale_info::Type::builder() + .path(_feps::scale_info::Path::new(stringify!(#ident), module_path!())) .composite( - _npos::scale_info::build::Fields::named() + _feps::scale_info::build::Fields::named() #scale_info_impl_single #scale_info_impl_double #scale_info_impl_rest diff --git a/frame/election-provider-support/solution-type/src/lib.rs b/frame/election-provider-support/solution-type/src/lib.rs index 3de923cdffd03..6e2788f06007f 100644 --- a/frame/election-provider-support/solution-type/src/lib.rs +++ b/frame/election-provider-support/solution-type/src/lib.rs @@ -88,7 +88,7 @@ pub(crate) fn syn_err(message: &'static str) -> syn::Error { /// ``` /// /// The given struct provides function to convert from/to `Assignment` as part of -/// `sp_npos_elections::Solution` trait: +/// `frame_election_provider_support::NposSolution` trait: /// /// - `fn from_assignment<..>(..)` /// - `fn into_assignment<..>(..)` @@ -101,7 +101,7 @@ pub(crate) fn syn_err(message: &'static str) -> syn::Error { /// /// ``` /// # use frame_election_provider_solution_type::generate_solution_type; -/// # use sp_npos_elections::NposSolution; +/// # use frame_election_provider_support::NposSolution; /// # use sp_arithmetic::per_things::Perbill; /// generate_solution_type!( /// #[compact] @@ -226,11 +226,11 @@ where } fn imports() -> Result { - match crate_name("sp-npos-elections") { - Ok(FoundCrate::Itself) => Ok(quote! { use crate as _npos; }), - Ok(FoundCrate::Name(sp_npos_elections)) => { - let ident = syn::Ident::new(&sp_npos_elections, Span::call_site()); - Ok(quote!( extern crate #ident as _npos; )) + match crate_name("frame-election-provider-support") { + Ok(FoundCrate::Itself) => Ok(quote! { use crate as _feps; }), + Ok(FoundCrate::Name(frame_election_provider_support)) => { + let ident = syn::Ident::new(&frame_election_provider_support, Span::call_site()); + Ok(quote!( extern crate #ident as _feps; )) }, Err(e) => Err(syn::Error::new(Span::call_site(), e)), } diff --git a/frame/election-provider-support/solution-type/src/single_page.rs b/frame/election-provider-support/solution-type/src/single_page.rs index 01a8a8eba5dcc..c1d897444da31 100644 --- a/frame/election-provider-support/solution-type/src/single_page.rs +++ b/frame/election-provider-support/solution-type/src/single_page.rs @@ -39,7 +39,7 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result { let name = vote_field(1); // NOTE: we use the visibility of the struct for the fields as well.. could be made better. quote!( - #vis #name: _npos::sp_std::prelude::Vec<(#voter_type, #target_type)>, + #vis #name: _feps::sp_std::prelude::Vec<(#voter_type, #target_type)>, ) }; @@ -48,7 +48,7 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result { let field_name = vote_field(c); let array_len = c - 1; quote!( - #vis #field_name: _npos::sp_std::prelude::Vec<( + #vis #field_name: _feps::sp_std::prelude::Vec<( #voter_type, [(#target_type, #weight_type); #array_len], #target_type @@ -83,9 +83,9 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result { Eq, Clone, Debug, - _npos::codec::Encode, - _npos::codec::Decode, - _npos::scale_info::TypeInfo, + _feps::codec::Encode, + _feps::codec::Decode, + _feps::scale_info::TypeInfo, )]) }; @@ -101,8 +101,8 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result { #derives_and_maybe_compact_encoding #vis struct #ident { #single #rest } - use _npos::__OrInvalidIndex; - impl _npos::NposSolution for #ident { + use _feps::__OrInvalidIndex; + impl _feps::NposSolution for #ident { const LIMIT: usize = #count; type VoterIndex = #voter_type; type TargetIndex = #target_type; @@ -114,34 +114,34 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result { } fn from_assignment( - assignments: &[_npos::Assignment], + assignments: &[_feps::Assignment], voter_index: FV, target_index: FT, - ) -> Result + ) -> Result where - A: _npos::IdentifierT, + A: _feps::IdentifierT, for<'r> FV: Fn(&'r A) -> Option, for<'r> FT: Fn(&'r A) -> Option, { let mut #struct_name: #ident = Default::default(); - for _npos::Assignment { who, distribution } in assignments { + for _feps::Assignment { who, distribution } in assignments { match distribution.len() { 0 => continue, #from_impl _ => { - return Err(_npos::Error::SolutionTargetOverflow); + return Err(_feps::Error::SolutionTargetOverflow); } } }; Ok(#struct_name) } - fn into_assignment( + fn into_assignment( self, voter_at: impl Fn(Self::VoterIndex) -> Option, target_at: impl Fn(Self::TargetIndex) -> Option, - ) -> Result<_npos::sp_std::prelude::Vec<_npos::Assignment>, _npos::Error> { - let mut #assignment_name: _npos::sp_std::prelude::Vec<_npos::Assignment> = Default::default(); + ) -> Result<_feps::sp_std::prelude::Vec<_feps::Assignment>, _feps::Error> { + let mut #assignment_name: _feps::sp_std::prelude::Vec<_feps::Assignment> = Default::default(); #into_impl Ok(#assignment_name) } @@ -158,10 +158,10 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result { all_edges } - fn unique_targets(&self) -> _npos::sp_std::prelude::Vec { + fn unique_targets(&self) -> _feps::sp_std::prelude::Vec { // NOTE: this implementation returns the targets sorted, but we don't use it yet per // se, nor is the API enforcing it. - use _npos::sp_std::collections::btree_set::BTreeSet; + use _feps::sp_std::collections::btree_set::BTreeSet; let mut all_targets: BTreeSet = BTreeSet::new(); let mut maybe_insert_target = |t: Self::TargetIndex| { all_targets.insert(t); @@ -173,22 +173,22 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result { } } - type __IndexAssignment = _npos::IndexAssignment< - <#ident as _npos::NposSolution>::VoterIndex, - <#ident as _npos::NposSolution>::TargetIndex, - <#ident as _npos::NposSolution>::Accuracy, + type __IndexAssignment = _feps::IndexAssignment< + <#ident as _feps::NposSolution>::VoterIndex, + <#ident as _feps::NposSolution>::TargetIndex, + <#ident as _feps::NposSolution>::Accuracy, >; - impl<'a> _npos::sp_std::convert::TryFrom<&'a [__IndexAssignment]> for #ident { - type Error = _npos::Error; + impl<'a> _feps::sp_std::convert::TryFrom<&'a [__IndexAssignment]> for #ident { + type Error = _feps::Error; fn try_from(index_assignments: &'a [__IndexAssignment]) -> Result { let mut #struct_name = #ident::default(); - for _npos::IndexAssignment { who, distribution } in index_assignments { + for _feps::IndexAssignment { who, distribution } in index_assignments { match distribution.len() { 0 => {} #from_index_impl _ => { - return Err(_npos::Error::SolutionTargetOverflow); + return Err(_feps::Error::SolutionTargetOverflow); } } }; @@ -310,7 +310,7 @@ pub(crate) fn into_impl( let name = vote_field(1); quote!( for (voter_index, target_index) in self.#name { - #assignments.push(_npos::Assignment { + #assignments.push(_feps::Assignment { who: voter_at(voter_index).or_invalid_index()?, distribution: vec![ (target_at(target_index).or_invalid_index()?, #per_thing::one()) @@ -329,25 +329,25 @@ pub(crate) fn into_impl( let mut inners_parsed = inners .iter() .map(|(ref t_idx, p)| { - sum = _npos::sp_arithmetic::traits::Saturating::saturating_add(sum, *p); + sum = _feps::sp_arithmetic::traits::Saturating::saturating_add(sum, *p); let target = target_at(*t_idx).or_invalid_index()?; Ok((target, *p)) }) - .collect::, _npos::Error>>()?; + .collect::, _feps::Error>>()?; if sum >= #per_thing::one() { - return Err(_npos::Error::SolutionWeightOverflow); + return Err(_feps::Error::SolutionWeightOverflow); } // defensive only. Since Percent doesn't have `Sub`. - let p_last = _npos::sp_arithmetic::traits::Saturating::saturating_sub( + let p_last = _feps::sp_arithmetic::traits::Saturating::saturating_sub( #per_thing::one(), sum, ); inners_parsed.push((target_at(t_last_idx).or_invalid_index()?, p_last)); - #assignments.push(_npos::Assignment { + #assignments.push(_feps::Assignment { who: voter_at(voter_index).or_invalid_index()?, distribution: inners_parsed, }); diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index 1bd10ba09346f..ca95eda6b5095 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -167,17 +167,86 @@ #![cfg_attr(not(feature = "std"), no_std)] pub mod onchain; -use frame_support::{traits::Get, BoundedVec}; +pub mod traits; +use codec::{Decode, Encode}; +use frame_support::{traits::Get, BoundedVec, RuntimeDebug}; use sp_runtime::traits::Bounded; use sp_std::{fmt::Debug, prelude::*}; -/// Re-export some type as they are used in the interface. +/// Re-export the solution generation macro. pub use frame_election_provider_solution_type::generate_solution_type; +/// Re-export some type as they are used in the interface. pub use sp_arithmetic::PerThing; pub use sp_npos_elections::{ - Assignment, ElectionResult, ExtendedBalance, IdentifierT, PerThing128, Support, Supports, - VoteWeight, + Assignment, ElectionResult, Error, ExtendedBalance, IdentifierT, PerThing128, Support, + Supports, VoteWeight, }; +pub use traits::NposSolution; + +// re-export for the solution macro, with the dependencies of the macro. +#[doc(hidden)] +pub use codec; +#[doc(hidden)] +pub use scale_info; +#[doc(hidden)] +pub use sp_arithmetic; +#[doc(hidden)] +pub use sp_std; +// Simple Extension trait to easily convert `None` from index closures to `Err`. +// +// This is only generated and re-exported for the solution code to use. +#[doc(hidden)] +pub trait __OrInvalidIndex { + fn or_invalid_index(self) -> Result; +} + +impl __OrInvalidIndex for Option { + fn or_invalid_index(self) -> Result { + self.ok_or(Error::SolutionInvalidIndex) + } +} + +/// The [`IndexAssignment`] type is an intermediate between the assignments list +/// ([`&[Assignment]`][Assignment]) and `SolutionOf`. +/// +/// The voter and target identifiers have already been replaced with appropriate indices, +/// making it fast to repeatedly encode into a `SolutionOf`. This property turns out +/// to be important when trimming for solution length. +#[derive(RuntimeDebug, Clone, Default)] +#[cfg_attr(feature = "std", derive(PartialEq, Eq, Encode, Decode))] +pub struct IndexAssignment { + /// Index of the voter among the voters list. + pub who: VoterIndex, + /// The distribution of the voter's stake among winning targets. + /// + /// Targets are identified by their index in the canonical list. + pub distribution: Vec<(TargetIndex, P)>, +} + +impl IndexAssignment { + pub fn new( + assignment: &Assignment, + voter_index: impl Fn(&AccountId) -> Option, + target_index: impl Fn(&AccountId) -> Option, + ) -> Result { + Ok(Self { + who: voter_index(&assignment.who).or_invalid_index()?, + distribution: assignment + .distribution + .iter() + .map(|(target, proportion)| Some((target_index(target)?, proportion.clone()))) + .collect::>>() + .or_invalid_index()?, + }) + } +} + +/// A type alias for [`IndexAssignment`] made from [`NposSolution`]. +pub type IndexAssignmentOf = IndexAssignment< + ::VoterIndex, + ::TargetIndex, + ::Accuracy, +>; /// Types that are used by the data provider trait. pub mod data_provider { diff --git a/frame/election-provider-support/src/traits.rs b/frame/election-provider-support/src/traits.rs new file mode 100644 index 0000000000000..e1fc0663e7d1e --- /dev/null +++ b/frame/election-provider-support/src/traits.rs @@ -0,0 +1,129 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Traits for the election operations. + +use crate::{Assignment, IdentifierT, IndexAssignmentOf, PerThing128, VoteWeight}; +use codec::Encode; +use scale_info::TypeInfo; +use sp_arithmetic::traits::{Bounded, UniqueSaturatedInto}; +use sp_npos_elections::{ElectionScore, Error, EvaluateSupport}; +use sp_std::{ + convert::{TryFrom, TryInto}, + fmt::Debug, + prelude::*, +}; + +/// An opaque index-based, NPoS solution type. +pub trait NposSolution +where + Self: Sized + for<'a> sp_std::convert::TryFrom<&'a [IndexAssignmentOf], Error = Error>, +{ + /// The maximum number of votes that are allowed. + const LIMIT: usize; + + /// The voter type. Needs to be an index (convert to usize). + type VoterIndex: UniqueSaturatedInto + + TryInto + + TryFrom + + Debug + + Copy + + Clone + + Bounded + + Encode + + TypeInfo; + + /// The target type. Needs to be an index (convert to usize). + type TargetIndex: UniqueSaturatedInto + + TryInto + + TryFrom + + Debug + + Copy + + Clone + + Bounded + + Encode + + TypeInfo; + + /// The weight/accuracy type of each vote. + type Accuracy: PerThing128; + + /// Get the length of all the voters that this type is encoding. + /// + /// This is basically the same as the number of assignments, or number of active voters. + fn voter_count(&self) -> usize; + + /// Get the total count of edges. + /// + /// This is effectively in the range of {[`Self::voter_count`], [`Self::voter_count`] * + /// [`Self::LIMIT`]}. + fn edge_count(&self) -> usize; + + /// Get the number of unique targets in the whole struct. + /// + /// Once presented with a list of winners, this set and the set of winners must be + /// equal. + fn unique_targets(&self) -> Vec; + + /// Get the average edge count. + fn average_edge_count(&self) -> usize { + self.edge_count().checked_div(self.voter_count()).unwrap_or(0) + } + + /// Compute the score of this solution type. + fn score( + self, + stake_of: FS, + voter_at: impl Fn(Self::VoterIndex) -> Option, + target_at: impl Fn(Self::TargetIndex) -> Option, + ) -> Result + where + for<'r> FS: Fn(&'r A) -> VoteWeight, + A: IdentifierT, + { + let ratio = self.into_assignment(voter_at, target_at)?; + let staked = + sp_npos_elections::helpers::assignment_ratio_to_staked_normalized(ratio, stake_of)?; + let supports = sp_npos_elections::to_supports(&staked); + Ok(supports.evaluate()) + } + + /// Remove a certain voter. + /// + /// This will only search until the first instance of `to_remove`, and return true. If + /// no instance is found (no-op), then it returns false. + /// + /// In other words, if this return true, exactly **one** element must have been removed self. + fn remove_voter(&mut self, to_remove: Self::VoterIndex) -> bool; + + /// Build self from a list of assignments. + fn from_assignment( + assignments: &[Assignment], + voter_index: FV, + target_index: FT, + ) -> Result + where + A: IdentifierT, + for<'r> FV: Fn(&'r A) -> Option, + for<'r> FT: Fn(&'r A) -> Option; + + /// Convert self into a `Vec>` + fn into_assignment( + self, + voter_at: impl Fn(Self::VoterIndex) -> Option, + target_at: impl Fn(Self::TargetIndex) -> Option, + ) -> Result>, Error>; +} diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index b50ce5ed4502c..39173bf61c833 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -40,9 +40,9 @@ mod impls; pub use impls::*; use crate::{ - log, slashing, weights::WeightInfo, ActiveEraInfo, BalanceOf, EraPayout, EraRewardPoints, - Exposure, Forcing, MaxUnlockingChunks, NegativeImbalanceOf, Nominations, PositiveImbalanceOf, - Releases, RewardDestination, SessionInterface, StakingLedger, UnappliedSlash, UnlockChunk, + slashing, weights::WeightInfo, ActiveEraInfo, BalanceOf, EraPayout, EraRewardPoints, Exposure, + Forcing, MaxUnlockingChunks, NegativeImbalanceOf, Nominations, PositiveImbalanceOf, Releases, + RewardDestination, SessionInterface, StakingLedger, UnappliedSlash, UnlockChunk, ValidatorPrefs, }; @@ -554,7 +554,7 @@ pub mod pallet { } for &(ref stash, ref controller, balance, ref status) in &self.stakers { - log!( + crate::log!( trace, "inserting genesis staker: {:?} => {:?} => {:?}", stash, diff --git a/primitives/npos-elections/src/assignments.rs b/primitives/npos-elections/src/assignments.rs index 5641a33447bad..9422ccdf65884 100644 --- a/primitives/npos-elections/src/assignments.rs +++ b/primitives/npos-elections/src/assignments.rs @@ -17,7 +17,7 @@ //! Structs and helpers for distributing a voter's stake among various winners. -use crate::{Error, ExtendedBalance, IdentifierT, PerThing128, __OrInvalidIndex}; +use crate::{ExtendedBalance, IdentifierT, PerThing128}; #[cfg(feature = "std")] use codec::{Decode, Encode}; use sp_arithmetic::{ @@ -166,44 +166,3 @@ impl StakedAssignment { self.distribution.iter().fold(Zero::zero(), |a, b| a.saturating_add(b.1)) } } -/// The [`IndexAssignment`] type is an intermediate between the assignments list -/// ([`&[Assignment]`][Assignment]) and `SolutionOf`. -/// -/// The voter and target identifiers have already been replaced with appropriate indices, -/// making it fast to repeatedly encode into a `SolutionOf`. This property turns out -/// to be important when trimming for solution length. -#[derive(RuntimeDebug, Clone, Default)] -#[cfg_attr(feature = "std", derive(PartialEq, Eq, Encode, Decode))] -pub struct IndexAssignment { - /// Index of the voter among the voters list. - pub who: VoterIndex, - /// The distribution of the voter's stake among winning targets. - /// - /// Targets are identified by their index in the canonical list. - pub distribution: Vec<(TargetIndex, P)>, -} - -impl IndexAssignment { - pub fn new( - assignment: &Assignment, - voter_index: impl Fn(&AccountId) -> Option, - target_index: impl Fn(&AccountId) -> Option, - ) -> Result { - Ok(Self { - who: voter_index(&assignment.who).or_invalid_index()?, - distribution: assignment - .distribution - .iter() - .map(|(target, proportion)| Some((target_index(target)?, proportion.clone()))) - .collect::>>() - .or_invalid_index()?, - }) - } -} - -/// A type alias for [`IndexAssignment`] made from [`crate::NposSolution`]. -pub type IndexAssignmentOf = IndexAssignment< - ::VoterIndex, - ::TargetIndex, - ::Accuracy, ->; diff --git a/primitives/npos-elections/src/lib.rs b/primitives/npos-elections/src/lib.rs index 673036c2d19d1..11d531fa56d87 100644 --- a/primitives/npos-elections/src/lib.rs +++ b/primitives/npos-elections/src/lib.rs @@ -100,26 +100,16 @@ pub mod pjr; pub mod reduce; pub mod traits; -pub use assignments::{Assignment, IndexAssignment, IndexAssignmentOf, StakedAssignment}; +pub use assignments::{Assignment, StakedAssignment}; pub use balancing::*; pub use helpers::*; pub use phragmen::*; pub use phragmms::*; pub use pjr::*; pub use reduce::reduce; -pub use traits::{IdentifierT, NposSolution, PerThing128, __OrInvalidIndex}; - -// re-export for the solution macro, with the dependencies of the macro. -#[doc(hidden)] -pub use codec; -#[doc(hidden)] -pub use scale_info; -#[doc(hidden)] -pub use sp_arithmetic; -#[doc(hidden)] -pub use sp_std; - -/// The errors that might occur in the this crate and solution-type. +pub use traits::{IdentifierT, PerThing128}; + +/// The errors that might occur in this crate and `frame-election-provider-solution-type`. #[derive(Eq, PartialEq, RuntimeDebug)] pub enum Error { /// While going from solution indices to ratio, the weight of all the edges has gone above the diff --git a/primitives/npos-elections/src/traits.rs b/primitives/npos-elections/src/traits.rs index 0723948b62269..91026f9de4b6b 100644 --- a/primitives/npos-elections/src/traits.rs +++ b/primitives/npos-elections/src/traits.rs @@ -17,22 +17,9 @@ //! Traits for the npos-election operations. -use crate::{ - Assignment, ElectionScore, Error, EvaluateSupport, ExtendedBalance, IndexAssignmentOf, - VoteWeight, -}; -use codec::Encode; -use scale_info::TypeInfo; -use sp_arithmetic::{ - traits::{Bounded, UniqueSaturatedInto}, - PerThing, -}; -use sp_std::{ - convert::{TryFrom, TryInto}, - fmt::Debug, - ops::Mul, - prelude::*, -}; +use crate::ExtendedBalance; +use sp_arithmetic::PerThing; +use sp_std::{fmt::Debug, ops::Mul, prelude::*}; /// an aggregator trait for a generic type of a voter/target identifier. This usually maps to /// substrate's account id. @@ -42,116 +29,3 @@ impl IdentifierT for T {} /// Aggregator trait for a PerThing that can be multiplied by u128 (ExtendedBalance). pub trait PerThing128: PerThing + Mul {} impl> PerThing128 for T {} - -/// Simple Extension trait to easily convert `None` from index closures to `Err`. -/// -/// This is only generated and re-exported for the solution code to use. -#[doc(hidden)] -pub trait __OrInvalidIndex { - fn or_invalid_index(self) -> Result; -} - -impl __OrInvalidIndex for Option { - fn or_invalid_index(self) -> Result { - self.ok_or(Error::SolutionInvalidIndex) - } -} - -/// An opaque index-based, NPoS solution type. -pub trait NposSolution -where - Self: Sized + for<'a> sp_std::convert::TryFrom<&'a [IndexAssignmentOf], Error = Error>, -{ - /// The maximum number of votes that are allowed. - const LIMIT: usize; - - /// The voter type. Needs to be an index (convert to usize). - type VoterIndex: UniqueSaturatedInto - + TryInto - + TryFrom - + Debug - + Copy - + Clone - + Bounded - + Encode - + TypeInfo; - - /// The target type. Needs to be an index (convert to usize). - type TargetIndex: UniqueSaturatedInto - + TryInto - + TryFrom - + Debug - + Copy - + Clone - + Bounded - + Encode - + TypeInfo; - - /// The weight/accuracy type of each vote. - type Accuracy: PerThing128; - - /// Get the length of all the voters that this type is encoding. - /// - /// This is basically the same as the number of assignments, or number of active voters. - fn voter_count(&self) -> usize; - - /// Get the total count of edges. - /// - /// This is effectively in the range of {[`Self::voter_count`], [`Self::voter_count`] * - /// [`Self::LIMIT`]}. - fn edge_count(&self) -> usize; - - /// Get the number of unique targets in the whole struct. - /// - /// Once presented with a list of winners, this set and the set of winners must be - /// equal. - fn unique_targets(&self) -> Vec; - - /// Get the average edge count. - fn average_edge_count(&self) -> usize { - self.edge_count().checked_div(self.voter_count()).unwrap_or(0) - } - - /// Compute the score of this solution type. - fn score( - self, - stake_of: FS, - voter_at: impl Fn(Self::VoterIndex) -> Option, - target_at: impl Fn(Self::TargetIndex) -> Option, - ) -> Result - where - for<'r> FS: Fn(&'r A) -> VoteWeight, - A: IdentifierT, - { - let ratio = self.into_assignment(voter_at, target_at)?; - let staked = crate::helpers::assignment_ratio_to_staked_normalized(ratio, stake_of)?; - let supports = crate::to_supports(&staked); - Ok(supports.evaluate()) - } - - /// Remove a certain voter. - /// - /// This will only search until the first instance of `to_remove`, and return true. If - /// no instance is found (no-op), then it returns false. - /// - /// In other words, if this return true, exactly **one** element must have been removed self. - fn remove_voter(&mut self, to_remove: Self::VoterIndex) -> bool; - - /// Build self from a list of assignments. - fn from_assignment( - assignments: &[Assignment], - voter_index: FV, - target_index: FT, - ) -> Result - where - A: IdentifierT, - for<'r> FV: Fn(&'r A) -> Option, - for<'r> FT: Fn(&'r A) -> Option; - - /// Convert self into a `Vec>` - fn into_assignment( - self, - voter_at: impl Fn(Self::VoterIndex) -> Option, - target_at: impl Fn(Self::TargetIndex) -> Option, - ) -> Result>, Error>; -} From df54bdc68b275be0edd2eaf3cf6b3dc4856d13c8 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 17 Mar 2022 11:40:31 +0100 Subject: [PATCH 026/484] Add execution overhead benchmarking (#10977) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add benchmark-block Signed-off-by: Oliver Tale-Yazdi * Remove first approach This reverts commit 9ce7f32cae12ce637bd3b85d255dd55ba7198f9d. * Add block and extrinsic benchmarks * Doc Signed-off-by: Oliver Tale-Yazdi * Fix template Signed-off-by: Oliver Tale-Yazdi * Beauty fixes Signed-off-by: Oliver Tale-Yazdi * Check for non-empty chain Signed-off-by: Oliver Tale-Yazdi * Add tests for Stats Signed-off-by: Oliver Tale-Yazdi * Review fixes Signed-off-by: Oliver Tale-Yazdi * Review fixes Signed-off-by: Oliver Tale-Yazdi * Apply suggestions from code review Co-authored-by: Shawn Tabrizi * Review fixes Signed-off-by: Oliver Tale-Yazdi * Review fixes Signed-off-by: Oliver Tale-Yazdi * Push first version again Signed-off-by: Oliver Tale-Yazdi * Push first version again Signed-off-by: Oliver Tale-Yazdi * Cleanup Signed-off-by: Oliver Tale-Yazdi * Cleanup Signed-off-by: Oliver Tale-Yazdi * Cleanup Signed-off-by: Oliver Tale-Yazdi * Beauty fixes Signed-off-by: Oliver Tale-Yazdi * Apply suggestions from code review Co-authored-by: Bastian Köcher * Update utils/frame/benchmarking-cli/src/overhead/template.rs Co-authored-by: Bastian Köcher * Review fixes Signed-off-by: Oliver Tale-Yazdi * Doc + Template fixes Signed-off-by: Oliver Tale-Yazdi * Review fixes Signed-off-by: Oliver Tale-Yazdi * Comment fix Signed-off-by: Oliver Tale-Yazdi * Add test Signed-off-by: Oliver Tale-Yazdi * Pust merge fixup Signed-off-by: Oliver Tale-Yazdi * Fixup Signed-off-by: Oliver Tale-Yazdi * Move code to better place Signed-off-by: Oliver Tale-Yazdi Co-authored-by: Shawn Tabrizi Co-authored-by: Bastian Köcher --- Cargo.lock | 2 + bin/node/cli/src/cli.rs | 7 + bin/node/cli/src/command.rs | 25 ++- bin/node/cli/src/command_helper.rs | 69 ++++++ bin/node/cli/src/lib.rs | 2 + .../cli/tests/benchmark_overhead_works.rs | 46 ++++ utils/frame/benchmarking-cli/Cargo.toml | 2 + utils/frame/benchmarking-cli/src/lib.rs | 3 + .../benchmarking-cli/src/overhead/bench.rs | 210 ++++++++++++++++++ .../benchmarking-cli/src/overhead/cmd.rs | 118 ++++++++++ .../benchmarking-cli/src/overhead/mod.rs | 22 ++ .../benchmarking-cli/src/overhead/template.rs | 110 +++++++++ .../benchmarking-cli/src/overhead/weights.hbs | 92 ++++++++ .../src/post_processing/mod.rs | 95 ++++++++ .../frame/benchmarking-cli/src/storage/cmd.rs | 27 +-- .../benchmarking-cli/src/storage/record.rs | 59 ++++- .../benchmarking-cli/src/storage/template.rs | 16 +- 17 files changed, 852 insertions(+), 53 deletions(-) create mode 100644 bin/node/cli/src/command_helper.rs create mode 100644 bin/node/cli/tests/benchmark_overhead_works.rs create mode 100644 utils/frame/benchmarking-cli/src/overhead/bench.rs create mode 100644 utils/frame/benchmarking-cli/src/overhead/cmd.rs create mode 100644 utils/frame/benchmarking-cli/src/overhead/mod.rs create mode 100644 utils/frame/benchmarking-cli/src/overhead/template.rs create mode 100644 utils/frame/benchmarking-cli/src/overhead/weights.hbs create mode 100644 utils/frame/benchmarking-cli/src/post_processing/mod.rs diff --git a/Cargo.lock b/Cargo.lock index d3cc9ab0d4320..fb9561dfabdcb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2139,6 +2139,7 @@ dependencies = [ "memory-db", "parity-scale-codec", "rand 0.8.4", + "sc-block-builder", "sc-cli", "sc-client-api", "sc-client-db", @@ -2152,6 +2153,7 @@ dependencies = [ "sp-core", "sp-database", "sp-externalities", + "sp-inherents", "sp-keystore", "sp-runtime", "sp-state-machine", diff --git a/bin/node/cli/src/cli.rs b/bin/node/cli/src/cli.rs index 386215854b963..a911cc26ef87c 100644 --- a/bin/node/cli/src/cli.rs +++ b/bin/node/cli/src/cli.rs @@ -42,6 +42,13 @@ pub enum Subcommand { #[clap(name = "benchmark", about = "Benchmark runtime pallets.")] Benchmark(frame_benchmarking_cli::BenchmarkCmd), + /// Sub command for benchmarking the per-block and per-extrinsic execution overhead. + #[clap( + name = "benchmark-overhead", + about = "Benchmark the per-block and per-extrinsic execution overhead." + )] + BenchmarkOverhead(frame_benchmarking_cli::OverheadCmd), + /// Sub command for benchmarking the storage speed. #[clap(name = "benchmark-storage", about = "Benchmark storage speed.")] BenchmarkStorage(frame_benchmarking_cli::StorageCmd), diff --git a/bin/node/cli/src/command.rs b/bin/node/cli/src/command.rs index cc6480bb90d55..e208e324ee2aa 100644 --- a/bin/node/cli/src/command.rs +++ b/bin/node/cli/src/command.rs @@ -18,10 +18,13 @@ use crate::{chain_spec, service, service::new_partial, Cli, Subcommand}; use node_executor::ExecutorDispatch; -use node_runtime::{Block, RuntimeApi}; +use node_primitives::Block; +use node_runtime::RuntimeApi; use sc_cli::{ChainSpec, Result, RuntimeVersion, SubstrateCli}; use sc_service::PartialComponents; +use std::sync::Arc; + impl SubstrateCli for Cli { fn impl_name() -> String { "Substrate Node".into() @@ -95,13 +98,21 @@ pub fn run() -> Result<()> { You can enable it with `--features runtime-benchmarks`." .into()) }, - Some(Subcommand::BenchmarkStorage(cmd)) => { - if !cfg!(feature = "runtime-benchmarks") { - return Err("Benchmarking wasn't enabled when building the node. \ - You can enable it with `--features runtime-benchmarks`." - .into()) - } + Some(Subcommand::BenchmarkOverhead(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.async_run(|mut config| { + use super::command_helper::{inherent_data, ExtrinsicBuilder}; + // We don't use the authority role since that would start producing blocks + // in the background which would mess with our benchmark. + config.role = sc_service::Role::Full; + let PartialComponents { client, task_manager, .. } = new_partial(&config)?; + let ext_builder = ExtrinsicBuilder::new(client.clone()); + + Ok((cmd.run(config, client, inherent_data()?, Arc::new(ext_builder)), task_manager)) + }) + }, + Some(Subcommand::BenchmarkStorage(cmd)) => { let runner = cli.create_runner(cmd)?; runner.async_run(|config| { let PartialComponents { client, task_manager, backend, .. } = new_partial(&config)?; diff --git a/bin/node/cli/src/command_helper.rs b/bin/node/cli/src/command_helper.rs new file mode 100644 index 0000000000000..51fe7a5c5a7bf --- /dev/null +++ b/bin/node/cli/src/command_helper.rs @@ -0,0 +1,69 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Contains code to setup the command invocations in [`super::command`] which would +//! otherwise bloat that module. + +use crate::service::{create_extrinsic, FullClient}; + +use node_runtime::SystemCall; +use sc_cli::Result; +use sp_inherents::{InherentData, InherentDataProvider}; +use sp_keyring::Sr25519Keyring; +use sp_runtime::OpaqueExtrinsic; + +use std::{sync::Arc, time::Duration}; + +/// Generates extrinsics for the `benchmark-overhead` command. +pub struct ExtrinsicBuilder { + client: Arc, +} + +impl ExtrinsicBuilder { + /// Creates a new [`Self`] from the given client. + pub fn new(client: Arc) -> Self { + Self { client } + } +} + +impl frame_benchmarking_cli::ExtrinsicBuilder for ExtrinsicBuilder { + fn remark(&self, nonce: u32) -> std::result::Result { + let acc = Sr25519Keyring::Bob.pair(); + let extrinsic: OpaqueExtrinsic = create_extrinsic( + self.client.as_ref(), + acc, + SystemCall::remark { remark: vec![] }, + Some(nonce), + ) + .into(); + + Ok(extrinsic) + } +} + +/// Generates inherent data for the `benchmark-overhead` command. +pub fn inherent_data() -> Result { + let mut inherent_data = InherentData::new(); + let d = Duration::from_millis(0); + let timestamp = sp_timestamp::InherentDataProvider::new(d.into()); + + timestamp + .provide_inherent_data(&mut inherent_data) + .map_err(|e| format!("creating inherent data: {:?}", e))?; + Ok(inherent_data) +} diff --git a/bin/node/cli/src/lib.rs b/bin/node/cli/src/lib.rs index 791140a25484d..06c0bcccbc296 100644 --- a/bin/node/cli/src/lib.rs +++ b/bin/node/cli/src/lib.rs @@ -38,6 +38,8 @@ pub mod service; mod cli; #[cfg(feature = "cli")] mod command; +#[cfg(feature = "cli")] +mod command_helper; #[cfg(feature = "cli")] pub use cli::*; diff --git a/bin/node/cli/tests/benchmark_overhead_works.rs b/bin/node/cli/tests/benchmark_overhead_works.rs new file mode 100644 index 0000000000000..550221ee2f70f --- /dev/null +++ b/bin/node/cli/tests/benchmark_overhead_works.rs @@ -0,0 +1,46 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use assert_cmd::cargo::cargo_bin; +use std::process::Command; +use tempfile::tempdir; + +/// Tests that the `benchmark-overhead` command works for the substrate dev runtime. +#[test] +fn benchmark_overhead_works() { + let tmp_dir = tempdir().expect("could not create a temp dir"); + let base_path = tmp_dir.path(); + + // Only put 10 extrinsics into the block otherwise it takes forever to build it + // especially for a non-release build. + let status = Command::new(cargo_bin("substrate")) + .args(&["benchmark-overhead", "--dev", "-d"]) + .arg(base_path) + .arg("--weight-path") + .arg(base_path) + .args(["--warmup", "10", "--repeat", "10"]) + .args(["--add", "100", "--mul", "1.2", "--metric", "p75"]) + .args(["--max-ext-per-block", "10"]) + .status() + .unwrap(); + assert!(status.success()); + + // Weight files have been created. + assert!(base_path.join("block_weights.rs").exists()); + assert!(base_path.join("extrinsic_weights.rs").exists()); +} diff --git a/utils/frame/benchmarking-cli/Cargo.toml b/utils/frame/benchmarking-cli/Cargo.toml index 81e7396db3e68..5575bb833ca77 100644 --- a/utils/frame/benchmarking-cli/Cargo.toml +++ b/utils/frame/benchmarking-cli/Cargo.toml @@ -16,6 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] frame-benchmarking = { version = "4.0.0-dev", path = "../../../frame/benchmarking" } frame-support = { version = "4.0.0-dev", path = "../../../frame/support" } sp-core = { version = "6.0.0", path = "../../../primitives/core" } +sc-block-builder = { version = "0.10.0-dev", path = "../../../client/block-builder" } sc-service = { version = "0.10.0-dev", default-features = false, path = "../../../client/service" } sc-client-api = { version = "4.0.0-dev", path = "../../../client/api" } sc-cli = { version = "0.10.0-dev", path = "../../../client/cli" } @@ -26,6 +27,7 @@ sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sp-externalities = { version = "0.12.0", path = "../../../primitives/externalities" } sp-database = { version = "4.0.0-dev", path = "../../../primitives/database" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } +sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } sp-keystore = { version = "0.12.0", path = "../../../primitives/keystore" } sp-storage = { version = "6.0.0", path = "../../../primitives/storage" } sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } diff --git a/utils/frame/benchmarking-cli/src/lib.rs b/utils/frame/benchmarking-cli/src/lib.rs index 9815fe88a7f02..e06d57963dad3 100644 --- a/utils/frame/benchmarking-cli/src/lib.rs +++ b/utils/frame/benchmarking-cli/src/lib.rs @@ -16,12 +16,15 @@ // limitations under the License. mod command; +pub mod overhead; +mod post_processing; mod storage; mod writer; use sc_cli::{ExecutionStrategy, WasmExecutionMethod, DEFAULT_WASM_EXECUTION_METHOD}; use std::{fmt::Debug, path::PathBuf}; +pub use overhead::{ExtrinsicBuilder, OverheadCmd}; pub use storage::StorageCmd; // Add a more relaxed parsing for pallet names by allowing pallet directory names with `-` to be diff --git a/utils/frame/benchmarking-cli/src/overhead/bench.rs b/utils/frame/benchmarking-cli/src/overhead/bench.rs new file mode 100644 index 0000000000000..3e18c6a86db24 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/overhead/bench.rs @@ -0,0 +1,210 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Contains the core benchmarking logic. + +use sc_block_builder::{BlockBuilderApi, BlockBuilderProvider}; +use sc_cli::{Error, Result}; +use sc_client_api::Backend as ClientBackend; +use sp_api::{ApiExt, BlockId, Core, ProvideRuntimeApi}; +use sp_blockchain::{ + ApplyExtrinsicFailed::Validity, + Error::{ApplyExtrinsicFailed, RuntimeApiError}, +}; +use sp_runtime::{ + traits::{Block as BlockT, Zero}, + transaction_validity::{InvalidTransaction, TransactionValidityError}, + OpaqueExtrinsic, +}; + +use clap::Args; +use log::info; +use serde::Serialize; +use std::{marker::PhantomData, sync::Arc, time::Instant}; + +use crate::{overhead::cmd::ExtrinsicBuilder, storage::record::Stats}; + +/// Parameters to configure an *overhead* benchmark. +#[derive(Debug, Default, Serialize, Clone, PartialEq, Args)] +pub struct BenchmarkParams { + /// Rounds of warmups before measuring. + #[clap(long, default_value = "100")] + pub warmup: u32, + + /// How many times the benchmark should be repeated. + #[clap(long, default_value = "1000")] + pub repeat: u32, + + /// Maximal number of extrinsics that should be put into a block. + /// + /// Only useful for debugging. + #[clap(long)] + pub max_ext_per_block: Option, +} + +/// The results of multiple runs in nano seconds. +pub(crate) type BenchRecord = Vec; + +/// Type of a benchmark. +#[derive(Serialize, Clone, PartialEq, Copy)] +pub(crate) enum BenchmarkType { + /// Measure the per-extrinsic execution overhead. + Extrinsic, + /// Measure the per-block execution overhead. + Block, +} + +/// Holds all objects needed to run the *overhead* benchmarks. +pub(crate) struct Benchmark { + client: Arc, + params: BenchmarkParams, + inherent_data: sp_inherents::InherentData, + ext_builder: Arc, + _p: PhantomData<(Block, BA)>, +} + +impl Benchmark +where + Block: BlockT, + BA: ClientBackend, + C: BlockBuilderProvider + ProvideRuntimeApi, + C::Api: ApiExt + BlockBuilderApi, +{ + /// Create a new [`Self`] from the arguments. + pub fn new( + client: Arc, + params: BenchmarkParams, + inherent_data: sp_inherents::InherentData, + ext_builder: Arc, + ) -> Self { + Self { client, params, inherent_data, ext_builder, _p: PhantomData } + } + + /// Run the specified benchmark. + pub fn bench(&self, bench_type: BenchmarkType) -> Result { + let (block, num_ext) = self.build_block(bench_type)?; + let record = self.measure_block(&block, num_ext, bench_type)?; + Stats::new(&record) + } + + /// Builds a block for the given benchmark type. + /// + /// Returns the block and the number of extrinsics in the block + /// that are not inherents. + fn build_block(&self, bench_type: BenchmarkType) -> Result<(Block, u64)> { + let mut builder = self.client.new_block(Default::default())?; + // Create and insert the inherents. + let inherents = builder.create_inherents(self.inherent_data.clone())?; + for inherent in inherents { + builder.push(inherent)?; + } + + // Return early if we just want a block with inherents and no additional extrinsics. + if bench_type == BenchmarkType::Block { + return Ok((builder.build()?.block, 0)) + } + + // Put as many extrinsics into the block as possible and count them. + info!("Building block, this takes some time..."); + let mut num_ext = 0; + for nonce in 0..self.max_ext_per_block() { + let ext = self.ext_builder.remark(nonce)?; + match builder.push(ext.clone()) { + Ok(()) => {}, + Err(ApplyExtrinsicFailed(Validity(TransactionValidityError::Invalid( + InvalidTransaction::ExhaustsResources, + )))) => break, // Block is full + Err(e) => return Err(Error::Client(e)), + } + num_ext += 1; + } + if num_ext == 0 { + return Err("A Block must hold at least one extrinsic".into()) + } + info!("Extrinsics per block: {}", num_ext); + let block = builder.build()?.block; + + Ok((block, num_ext)) + } + + /// Measures the time that it take to execute a block or an extrinsic. + fn measure_block( + &self, + block: &Block, + num_ext: u64, + bench_type: BenchmarkType, + ) -> Result { + let mut record = BenchRecord::new(); + if bench_type == BenchmarkType::Extrinsic && num_ext == 0 { + return Err("Cannot measure the extrinsic time of an empty block".into()) + } + let genesis = BlockId::Number(Zero::zero()); + + info!("Running {} warmups...", self.params.warmup); + for _ in 0..self.params.warmup { + self.client + .runtime_api() + .execute_block(&genesis, block.clone()) + .map_err(|e| Error::Client(RuntimeApiError(e)))?; + } + + info!("Executing block {} times", self.params.repeat); + // Interesting part here: + // Execute a block multiple times and record each execution time. + for _ in 0..self.params.repeat { + let block = block.clone(); + let runtime_api = self.client.runtime_api(); + let start = Instant::now(); + + runtime_api + .execute_block(&genesis, block) + .map_err(|e| Error::Client(RuntimeApiError(e)))?; + + let elapsed = start.elapsed().as_nanos(); + if bench_type == BenchmarkType::Extrinsic { + // Checked for non-zero div above. + record.push((elapsed as f64 / num_ext as f64).ceil() as u64); + } else { + record.push(elapsed as u64); + } + } + + Ok(record) + } + + fn max_ext_per_block(&self) -> u32 { + self.params.max_ext_per_block.unwrap_or(u32::MAX) + } +} + +impl BenchmarkType { + /// Short name of the benchmark type. + pub(crate) fn short_name(&self) -> &'static str { + match self { + Self::Extrinsic => "extrinsic", + Self::Block => "block", + } + } + + /// Long name of the benchmark type. + pub(crate) fn long_name(&self) -> &'static str { + match self { + Self::Extrinsic => "ExtrinsicBase", + Self::Block => "BlockExecution", + } + } +} diff --git a/utils/frame/benchmarking-cli/src/overhead/cmd.rs b/utils/frame/benchmarking-cli/src/overhead/cmd.rs new file mode 100644 index 0000000000000..8c75627fe2462 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/overhead/cmd.rs @@ -0,0 +1,118 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Contains the [`OverheadCmd`] as entry point for the CLI to execute +//! the *overhead* benchmarks. + +use sc_block_builder::{BlockBuilderApi, BlockBuilderProvider}; +use sc_cli::{CliConfiguration, Result, SharedParams}; +use sc_client_api::Backend as ClientBackend; +use sc_service::Configuration; +use sp_api::{ApiExt, ProvideRuntimeApi}; +use sp_runtime::{traits::Block as BlockT, OpaqueExtrinsic}; + +use clap::{Args, Parser}; +use log::info; +use serde::Serialize; +use std::{fmt::Debug, sync::Arc}; + +use crate::{ + overhead::{ + bench::{Benchmark, BenchmarkParams, BenchmarkType}, + template::TemplateData, + }, + post_processing::WeightParams, +}; + +/// Benchmarks the per-block and per-extrinsic execution overhead. +#[derive(Debug, Parser)] +pub struct OverheadCmd { + #[allow(missing_docs)] + #[clap(flatten)] + pub shared_params: SharedParams, + + #[allow(missing_docs)] + #[clap(flatten)] + pub params: OverheadParams, +} + +/// Configures the benchmark, the post-processing and weight generation. +#[derive(Debug, Default, Serialize, Clone, PartialEq, Args)] +pub struct OverheadParams { + #[allow(missing_docs)] + #[clap(flatten)] + pub weight: WeightParams, + + #[allow(missing_docs)] + #[clap(flatten)] + pub bench: BenchmarkParams, +} + +/// Used by the benchmark to build signed extrinsics. +/// +/// The built extrinsics only need to be valid in the first block +/// who's parent block is the genesis block. +pub trait ExtrinsicBuilder { + /// Build a `System::remark` extrinsic. + fn remark(&self, nonce: u32) -> std::result::Result; +} + +impl OverheadCmd { + /// Measures the per-block and per-extrinsic execution overhead. + /// + /// Writes the results to console and into two instances of the + /// `weights.hbs` template, one for each benchmark. + pub async fn run( + &self, + cfg: Configuration, + client: Arc, + inherent_data: sp_inherents::InherentData, + ext_builder: Arc, + ) -> Result<()> + where + Block: BlockT, + BA: ClientBackend, + C: BlockBuilderProvider + ProvideRuntimeApi, + C::Api: ApiExt + BlockBuilderApi, + { + let bench = Benchmark::new(client, self.params.bench.clone(), inherent_data, ext_builder); + + // per-block execution overhead + { + let stats = bench.bench(BenchmarkType::Block)?; + info!("Per-block execution overhead [ns]:\n{:?}", stats); + let template = TemplateData::new(BenchmarkType::Block, &cfg, &self.params, &stats)?; + template.write(&self.params.weight.weight_path)?; + } + // per-extrinsic execution overhead + { + let stats = bench.bench(BenchmarkType::Extrinsic)?; + info!("Per-extrinsic execution overhead [ns]:\n{:?}", stats); + let template = TemplateData::new(BenchmarkType::Extrinsic, &cfg, &self.params, &stats)?; + template.write(&self.params.weight.weight_path)?; + } + + Ok(()) + } +} + +// Boilerplate +impl CliConfiguration for OverheadCmd { + fn shared_params(&self) -> &SharedParams { + &self.shared_params + } +} diff --git a/utils/frame/benchmarking-cli/src/overhead/mod.rs b/utils/frame/benchmarking-cli/src/overhead/mod.rs new file mode 100644 index 0000000000000..abdeac22b7898 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/overhead/mod.rs @@ -0,0 +1,22 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod bench; +pub mod cmd; +mod template; + +pub use cmd::{ExtrinsicBuilder, OverheadCmd}; diff --git a/utils/frame/benchmarking-cli/src/overhead/template.rs b/utils/frame/benchmarking-cli/src/overhead/template.rs new file mode 100644 index 0000000000000..f6fb8ed9d929e --- /dev/null +++ b/utils/frame/benchmarking-cli/src/overhead/template.rs @@ -0,0 +1,110 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Converts a benchmark result into [`TemplateData`] and writes +//! it into the `weights.hbs` template. + +use sc_cli::Result; +use sc_service::Configuration; + +use handlebars::Handlebars; +use log::info; +use serde::Serialize; +use std::{env, fs, path::PathBuf}; + +use crate::{ + overhead::{bench::BenchmarkType, cmd::OverheadParams}, + storage::record::Stats, +}; + +static VERSION: &'static str = env!("CARGO_PKG_VERSION"); +static TEMPLATE: &str = include_str!("./weights.hbs"); + +/// Data consumed by Handlebar to fill out the `weights.hbs` template. +#[derive(Serialize, Debug, Clone)] +pub(crate) struct TemplateData { + /// Short name of the benchmark. Can be "block" or "extrinsic". + long_name: String, + /// Long name of the benchmark. Can be "BlockExecution" or "ExtrinsicBase". + short_name: String, + /// Name of the runtime. Taken from the chain spec. + runtime_name: String, + /// Version of the benchmarking CLI used. + version: String, + /// Date that the template was filled out. + date: String, + /// Command line arguments that were passed to the CLI. + args: Vec, + /// Params of the executed command. + params: OverheadParams, + /// Stats about the benchmark result. + stats: Stats, + /// The resulting weight in ns. + weight: u64, +} + +impl TemplateData { + /// Returns a new [`Self`] from the given params. + pub(crate) fn new( + t: BenchmarkType, + cfg: &Configuration, + params: &OverheadParams, + stats: &Stats, + ) -> Result { + let weight = params.weight.calc_weight(stats)?; + + Ok(TemplateData { + short_name: t.short_name().into(), + long_name: t.long_name().into(), + runtime_name: cfg.chain_spec.name().into(), + version: VERSION.into(), + date: chrono::Utc::now().format("%Y-%m-%d (Y/M/D)").to_string(), + args: env::args().collect::>(), + params: params.clone(), + stats: stats.clone(), + weight, + }) + } + + /// Fill out the `weights.hbs` HBS template with its own data. + /// Writes the result to `path` which can be a directory or a file. + pub fn write(&self, path: &Option) -> Result<()> { + let mut handlebars = Handlebars::new(); + // Format large integers with underscores. + handlebars.register_helper("underscore", Box::new(crate::writer::UnderscoreHelper)); + // Don't HTML escape any characters. + handlebars.register_escape_fn(|s| -> String { s.to_string() }); + + let out_path = self.build_path(path)?; + let mut fd = fs::File::create(&out_path)?; + info!("Writing weights to {:?}", fs::canonicalize(&out_path)?); + handlebars + .render_template_to_write(&TEMPLATE, &self, &mut fd) + .map_err(|e| format!("HBS template write: {:?}", e).into()) + } + + /// Build a path for the weight file. + fn build_path(&self, weight_out: &Option) -> Result { + let mut path = weight_out.clone().unwrap_or(PathBuf::from(".")); + + if !path.is_dir() { + return Err("Need directory as --weight-path".into()) + } + path.push(format!("{}_weights.rs", self.short_name)); + Ok(path) + } +} diff --git a/utils/frame/benchmarking-cli/src/overhead/weights.hbs b/utils/frame/benchmarking-cli/src/overhead/weights.hbs new file mode 100644 index 0000000000000..0f6b7f3e9119f --- /dev/null +++ b/utils/frame/benchmarking-cli/src/overhead/weights.hbs @@ -0,0 +1,92 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION {{version}} +//! DATE: {{date}} +//! +//! SHORT-NAME: `{{short_name}}`, LONG-NAME: `{{long_name}}`, RUNTIME: `{{runtime_name}}` +//! WARMUPS: `{{params.bench.warmup}}`, REPEAT: `{{params.bench.repeat}}` +//! WEIGHT-PATH: `{{params.weight.weight_path}}` +//! WEIGHT-METRIC: `{{params.weight.weight_metric}}`, WEIGHT-MUL: `{{params.weight.weight_mul}}`, WEIGHT-ADD: `{{params.weight.weight_add}}` + +// Executed Command: +{{#each args as |arg|}} +// {{arg}} +{{/each}} + +use frame_support::{ + parameter_types, + weights::{constants::WEIGHT_PER_NANOS, Weight}, +}; + +parameter_types! { + {{#if (eq short_name "block")}} + /// Time to execute an empty block. + {{else}} + /// Time to execute a NO-OP extrinsic eg. `System::remark`. + {{/if}} + /// Calculated by multiplying the *{{params.weight.weight_metric}}* with `{{params.weight.weight_mul}}` and adding `{{params.weight.weight_add}}`. + /// + /// Stats [ns]: + /// Min, Max: {{underscore stats.min}}, {{underscore stats.max}} + /// Average: {{underscore stats.avg}} + /// Median: {{underscore stats.median}} + /// StdDev: {{stats.stddev}} + /// + /// Percentiles [ns]: + /// 99th: {{underscore stats.p99}} + /// 95th: {{underscore stats.p95}} + /// 75th: {{underscore stats.p75}} + pub const {{long_name}}Weight: Weight = {{underscore weight}} * WEIGHT_PER_NANOS; +} + +#[cfg(test)] +mod test_weights { + use frame_support::weights::constants; + + /// Checks that the weight exists and is sane. + // NOTE: If this test fails but you are sure that the generated values are fine, + // you can delete it. + #[test] + fn sane() { + let w = super::{{long_name}}Weight::get(); + + {{#if (eq short_name "block")}} + // At least 100 µs. + assert!( + w >= 100 * constants::WEIGHT_PER_MICROS, + "Weight should be at least 100 µs." + ); + // At most 50 ms. + assert!( + w <= 50 * constants::WEIGHT_PER_MILLIS, + "Weight should be at most 50 ms." + ); + {{else}} + // At least 10 µs. + assert!( + w >= 10 * constants::WEIGHT_PER_MICROS, + "Weight should be at least 10 µs." + ); + // At most 1 ms. + assert!( + w <= constants::WEIGHT_PER_MILLIS, + "Weight should be at most 1 ms." + ); + {{/if}} + } +} diff --git a/utils/frame/benchmarking-cli/src/post_processing/mod.rs b/utils/frame/benchmarking-cli/src/post_processing/mod.rs new file mode 100644 index 0000000000000..fb20d9bd0c488 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/post_processing/mod.rs @@ -0,0 +1,95 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Calculates a weight from the statistics of a benchmark result. + +use sc_cli::Result; + +use clap::Args; +use serde::Serialize; +use std::path::PathBuf; + +use crate::storage::record::{StatSelect, Stats}; + +/// Configures the weight generation. +#[derive(Debug, Default, Serialize, Clone, PartialEq, Args)] +pub struct WeightParams { + /// File or directory to write the *weight* files to. + /// + /// For Substrate this should be `frame/support/src/weights`. + #[clap(long)] + pub weight_path: Option, + + /// Select a specific metric to calculate the final weight output. + #[clap(long = "metric", default_value = "average")] + pub weight_metric: StatSelect, + + /// Multiply the resulting weight with the given factor. Must be positive. + /// + /// Is applied before `weight_add`. + #[clap(long = "mul", default_value = "1")] + pub weight_mul: f64, + + /// Add the given offset to the resulting weight. + /// + /// Is applied after `weight_mul`. + #[clap(long = "add", default_value = "0")] + pub weight_add: u64, +} + +/// Calculates the final weight by multiplying the selected metric with +/// `weight_mul` and adding `weight_add`. +/// Does not use safe casts and can overflow. +impl WeightParams { + pub(crate) fn calc_weight(&self, stat: &Stats) -> Result { + if self.weight_mul.is_sign_negative() || !self.weight_mul.is_normal() { + return Err("invalid floating number for `weight_mul`".into()) + } + let s = stat.select(self.weight_metric) as f64; + let w = s.mul_add(self.weight_mul, self.weight_add as f64).ceil(); + Ok(w as u64) // No safe cast here since there is no `From` for `u64`. + } +} + +#[cfg(test)] +mod test_weight_params { + use super::WeightParams; + use crate::storage::record::{StatSelect, Stats}; + + #[test] + fn calc_weight_works() { + let stats = Stats { avg: 113, ..Default::default() }; + let params = WeightParams { + weight_metric: StatSelect::Average, + weight_mul: 0.75, + weight_add: 3, + ..Default::default() + }; + + let want = (113.0f64 * 0.75 + 3.0).ceil() as u64; // Ceil for overestimation. + let got = params.calc_weight(&stats).unwrap(); + assert_eq!(want, got); + } + + #[test] + fn calc_weight_detects_negative_mul() { + let stats = Stats::default(); + let params = WeightParams { weight_mul: -0.75, ..Default::default() }; + + assert!(params.calc_weight(&stats).is_err()); + } +} diff --git a/utils/frame/benchmarking-cli/src/storage/cmd.rs b/utils/frame/benchmarking-cli/src/storage/cmd.rs index ad7d13a2022e4..c38e6636e5a3e 100644 --- a/utils/frame/benchmarking-cli/src/storage/cmd.rs +++ b/utils/frame/benchmarking-cli/src/storage/cmd.rs @@ -33,8 +33,8 @@ use serde::Serialize; use sp_runtime::generic::BlockId; use std::{fmt::Debug, path::PathBuf, sync::Arc}; -use super::{record::StatSelect, template::TemplateData}; - +use super::template::TemplateData; +use crate::post_processing::WeightParams; /// Benchmark the storage of a Substrate node with a live chain snapshot. #[derive(Debug, Parser)] pub struct StorageCmd { @@ -58,24 +58,9 @@ pub struct StorageCmd { /// Parameters for modifying the benchmark behaviour and the post processing of the results. #[derive(Debug, Default, Serialize, Clone, PartialEq, Args)] pub struct StorageParams { - /// Path to write the *weight* file to. Can be a file or directory. - /// For substrate this should be `frame/support/src/weights`. - #[clap(long)] - pub weight_path: Option, - - /// Select a specific metric to calculate the final weight output. - #[clap(long = "metric", default_value = "average")] - pub weight_metric: StatSelect, - - /// Multiply the resulting weight with the given factor. Must be positive. - /// Is calculated before `weight_add`. - #[clap(long = "mul", default_value = "1")] - pub weight_mul: f64, - - /// Add the given offset to the resulting weight. - /// Is calculated after `weight_mul`. - #[clap(long = "add", default_value = "0")] - pub weight_add: u64, + #[allow(missing_docs)] + #[clap(flatten)] + pub weight_params: WeightParams, /// Skip the `read` benchmark. #[clap(long)] @@ -153,7 +138,7 @@ impl StorageCmd { template.set_stats(None, Some(stats))?; } - template.write(&self.params.weight_path, &self.params.template_path) + template.write(&self.params.weight_params.weight_path, &self.params.template_path) } /// Returns the specified state version. diff --git a/utils/frame/benchmarking-cli/src/storage/record.rs b/utils/frame/benchmarking-cli/src/storage/record.rs index 667274bef0dd5..530fa4cdfe965 100644 --- a/utils/frame/benchmarking-cli/src/storage/record.rs +++ b/utils/frame/benchmarking-cli/src/storage/record.rs @@ -36,25 +36,25 @@ pub(crate) struct BenchRecord { #[derive(Serialize, Default, Clone)] pub(crate) struct Stats { /// Sum of all values. - sum: u64, + pub(crate) sum: u64, /// Minimal observed value. - min: u64, + pub(crate) min: u64, /// Maximal observed value. - max: u64, + pub(crate) max: u64, /// Average of all values. - avg: u64, + pub(crate) avg: u64, /// Median of all values. - median: u64, + pub(crate) median: u64, /// Standard derivation of all values. - stddev: f64, + pub(crate) stddev: f64, /// 99th percentile. At least 99% of all values are below this threshold. - p99: u64, + pub(crate) p99: u64, /// 95th percentile. At least 95% of all values are below this threshold. - p95: u64, + pub(crate) p95: u64, /// 75th percentile. At least 75% of all values are below this threshold. - p75: u64, + pub(crate) p75: u64, } /// Selects a specific field from a [`Stats`] object. @@ -159,8 +159,8 @@ impl Stats { /// This is best effort since it ignores the interpolation case. fn percentile(mut xs: Vec, p: f64) -> u64 { xs.sort(); - let index = (xs.len() as f64 * p).ceil() as usize; - xs[index] + let index = (xs.len() as f64 * p).ceil() as usize - 1; + xs[index.clamp(0, xs.len() - 1)] } } @@ -195,3 +195,40 @@ impl FromStr for StatSelect { } } } + +#[cfg(test)] +mod test_stats { + use super::Stats; + use rand::{seq::SliceRandom, thread_rng}; + + #[test] + fn stats_correct() { + let mut data: Vec = (1..=100).collect(); + data.shuffle(&mut thread_rng()); + let stats = Stats::new(&data).unwrap(); + + assert_eq!(stats.sum, 5050); + assert_eq!(stats.min, 1); + assert_eq!(stats.max, 100); + + assert_eq!(stats.avg, 50); + assert_eq!(stats.median, 50); // 50.5 to be exact. + assert_eq!(stats.stddev, 28.87); // Rounded with 1/100 precision. + + assert_eq!(stats.p99, 99); + assert_eq!(stats.p95, 95); + assert_eq!(stats.p75, 75); + } + + #[test] + fn no_panic_short_lengths() { + // Empty input does error. + assert!(Stats::new(&vec![]).is_err()); + + // Different small input lengths are fine. + for l in 1..10 { + let data = (0..=l).collect(); + assert!(Stats::new(&data).is_ok()); + } + } +} diff --git a/utils/frame/benchmarking-cli/src/storage/template.rs b/utils/frame/benchmarking-cli/src/storage/template.rs index 13b825d891a51..10e6902b934bc 100644 --- a/utils/frame/benchmarking-cli/src/storage/template.rs +++ b/utils/frame/benchmarking-cli/src/storage/template.rs @@ -77,11 +77,11 @@ impl TemplateData { write: Option<(Stats, Stats)>, ) -> Result<()> { if let Some(read) = read { - self.read_weight = calc_weight(&read.0, &self.params)?; + self.read_weight = self.params.weight_params.calc_weight(&read.0)?; self.read = Some(read); } if let Some(write) = write { - self.write_weight = calc_weight(&write.0, &self.params)?; + self.write_weight = self.params.weight_params.calc_weight(&write.0)?; self.write = Some(write); } Ok(()) @@ -130,15 +130,3 @@ impl TemplateData { path } } - -/// Calculates the final weight by multiplying the selected metric with -/// `mul` and adding `add`. -/// Does not use safe casts and can overflow. -fn calc_weight(stat: &Stats, params: &StorageParams) -> Result { - if params.weight_mul.is_sign_negative() || !params.weight_mul.is_normal() { - return Err("invalid floating number for `weight_mul`".into()) - } - let s = stat.select(params.weight_metric) as f64; - let w = s.mul_add(params.weight_mul, params.weight_add as f64).ceil(); - Ok(w as u64) // No safe cast here since there is no `From` for `u64`. -} From b8f7a135967c07f399e10419e6676216839b9a79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Thu, 17 Mar 2022 12:16:56 +0100 Subject: [PATCH 027/484] sc-rpc-api: Remove unused error variants (#11012) --- client/rpc-api/src/author/error.rs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/client/rpc-api/src/author/error.rs b/client/rpc-api/src/author/error.rs index eee77edd5e208..fe12bd581f120 100644 --- a/client/rpc-api/src/author/error.rs +++ b/client/rpc-api/src/author/error.rs @@ -43,15 +43,9 @@ pub enum Error { /// Incorrect extrinsic format. #[error("Invalid extrinsic format: {}", .0)] BadFormat(#[from] codec::Error), - /// Incorrect seed phrase. - #[error("Invalid seed phrase/SURI")] - BadSeedPhrase, /// Key type ID has an unknown format. #[error("Invalid key type ID format (should be of length four)")] BadKeyType, - /// Key type ID has some unsupported crypto. - #[error("The crypto of key type ID is unknown")] - UnsupportedKeyType, /// Some random issue with the key store. Shouldn't happen. #[error("The key store is unavailable")] KeyStoreUnavailable, @@ -84,8 +78,6 @@ const POOL_TOO_LOW_PRIORITY: i64 = POOL_INVALID_TX + 4; const POOL_CYCLE_DETECTED: i64 = POOL_INVALID_TX + 5; /// The transaction was not included to the pool because of the limits. const POOL_IMMEDIATELY_DROPPED: i64 = POOL_INVALID_TX + 6; -/// The key type crypto is not known. -const UNSUPPORTED_KEY_TYPE: i64 = POOL_INVALID_TX + 7; /// The transaction was not included to the pool since it is unactionable, /// it is not propagable and the local node does not author blocks. const POOL_UNACTIONABLE: i64 = POOL_INVALID_TX + 8; @@ -156,14 +148,6 @@ impl From for rpc::Error { the local node does not author blocks".into(), ), }, - Error::UnsupportedKeyType => rpc::Error { - code: rpc::ErrorCode::ServerError(UNSUPPORTED_KEY_TYPE), - message: "Unknown key type crypto" .into(), - data: Some( - "The crypto for the given key type is unknown, please add the public key to the \ - request to insert the key successfully.".into() - ), - }, Error::UnsafeRpcCalled(e) => e.into(), e => errors::internal(e), } From cc12865352d7a25ff85596c9425b54e1d6ea3968 Mon Sep 17 00:00:00 2001 From: Georges Date: Thu, 17 Mar 2022 12:52:26 +0000 Subject: [PATCH 028/484] Allow `pallet-election-provider` to accept smaller solutions as well (#10905) * Allow `pallet-election-provider` to accept smaller solutions, issue #9478 * Fixing a typo * Adding some more tests Removing a seemingly outdated comment * making it a URL * Updating test name as per suggestion Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Updating documentation to be more explicit And to follow the general guidelines Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Fixing formatting Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- .../election-provider-multi-phase/src/lib.rs | 49 ++++++++++++++++--- .../src/unsigned.rs | 7 ++- frame/election-provider-support/src/lib.rs | 6 +++ 3 files changed, 54 insertions(+), 8 deletions(-) diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index b366ceb4dafe3..90c540b85d67d 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -1346,7 +1346,7 @@ impl Pallet { T::DataProvider::targets(Some(target_limit)).map_err(ElectionError::DataProvider)?; let voters = T::DataProvider::voters(Some(voter_limit)).map_err(ElectionError::DataProvider)?; - let desired_targets = + let mut desired_targets = T::DataProvider::desired_targets().map_err(ElectionError::DataProvider)?; // Defensive-only. @@ -1355,6 +1355,22 @@ impl Pallet { return Err(ElectionError::DataProvider("Snapshot too big for submission.")) } + // If `desired_targets` > `targets.len()`, cap `desired_targets` to that level and emit a + // warning + let max_len = targets + .len() + .try_into() + .map_err(|_| ElectionError::DataProvider("Failed to convert usize"))?; + if desired_targets > max_len { + log!( + warn, + "desired_targets: {} > targets.len(): {}, capping desired_targets", + desired_targets, + max_len + ); + desired_targets = max_len; + } + Ok((targets, voters, desired_targets)) } @@ -1413,9 +1429,6 @@ impl Pallet { let desired_targets = Self::desired_targets().ok_or(FeasibilityError::SnapshotUnavailable)?; - // NOTE: this is a bit of duplicate, but we keep it around for veracity. The unsigned path - // already checked this in `unsigned_per_dispatch_checks`. The signed path *could* check it - // upon arrival, thus we would then remove it here. Given overlay it is cheap anyhow ensure!(winners.len() as u32 == desired_targets, FeasibilityError::WrongWinnerCount); // Ensure that the solution's score can pass absolute min-score. @@ -1588,7 +1601,7 @@ mod feasibility_check { raw_solution, roll_to, EpochLength, ExtBuilder, MultiPhase, Runtime, SignedPhase, TargetIndex, UnsignedPhase, VoterIndex, }; - use frame_support::assert_noop; + use frame_support::{assert_noop, assert_ok}; const COMPUTE: ElectionCompute = ElectionCompute::OnChain; @@ -1625,7 +1638,7 @@ mod feasibility_check { } #[test] - fn desired_targets() { + fn desired_targets_gets_capped() { ExtBuilder::default().desired_targets(8).build_and_execute(|| { roll_to(::get() - ::get() - ::get()); assert!(MultiPhase::current_phase().is_signed()); @@ -1633,8 +1646,30 @@ mod feasibility_check { let raw = raw_solution(); assert_eq!(raw.solution.unique_targets().len(), 4); - assert_eq!(MultiPhase::desired_targets().unwrap(), 8); + // desired_targets is capped to the number of targets which is 4 + assert_eq!(MultiPhase::desired_targets().unwrap(), 4); + + // It should succeed + assert_ok!(MultiPhase::feasibility_check(raw, COMPUTE)); + }) + } + + #[test] + fn less_than_desired_targets_fails() { + ExtBuilder::default().desired_targets(8).build_and_execute(|| { + roll_to(::get() - ::get() - ::get()); + assert!(MultiPhase::current_phase().is_signed()); + + let mut raw = raw_solution(); + + assert_eq!(raw.solution.unique_targets().len(), 4); + // desired_targets is capped to the number of targets which is 4 + assert_eq!(MultiPhase::desired_targets().unwrap(), 4); + + // Force the number of winners to be bigger to fail + raw.solution.votes1[0].1 = 4; + // It should succeed assert_noop!( MultiPhase::feasibility_check(raw, COMPUTE), FeasibilityError::WrongWinnerCount, diff --git a/frame/election-provider-multi-phase/src/unsigned.rs b/frame/election-provider-multi-phase/src/unsigned.rs index 3380a61d4310e..c52a4da22cb87 100644 --- a/frame/election-provider-multi-phase/src/unsigned.rs +++ b/frame/election-provider-multi-phase/src/unsigned.rs @@ -1044,8 +1044,13 @@ mod tests { roll_to(25); assert!(MultiPhase::current_phase().is_unsigned()); + // Force the number of winners to be bigger to fail + let (mut solution, _) = + MultiPhase::mine_solution::<::Solver>().unwrap(); + solution.solution.votes1[0].1 = 4; + assert_eq!( - MultiPhase::mine_check_save_submit().unwrap_err(), + MultiPhase::basic_checks(&solution, "mined").unwrap_err(), MinerError::PreDispatchChecksFailed(DispatchError::Module(ModuleError { index: 2, error: 1, diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index ca95eda6b5095..e1975a2681e6a 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -289,6 +289,12 @@ pub trait ElectionDataProvider { /// /// This should be implemented as a self-weighing function. The implementor should register its /// appropriate weight at the end of execution with the system pallet directly. + /// + /// A sensible implementation should use the minimum between this value and + /// [`Self::targets().len()`], since desiring a winner set larger than candidates is not + /// feasible. + /// + /// This is documented further in issue: fn desired_targets() -> data_provider::Result; /// Provide a best effort prediction about when the next election is about to happen. From 62806e9fc507453ab960ddc5b58f63bda627a559 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Thu, 17 Mar 2022 15:47:42 +0000 Subject: [PATCH 029/484] fix sibmit benchmark in election-multi-phase pallet (#11057) --- frame/election-provider-multi-phase/src/benchmarking.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frame/election-provider-multi-phase/src/benchmarking.rs b/frame/election-provider-multi-phase/src/benchmarking.rs index b331516a4e797..2a9286369f394 100644 --- a/frame/election-provider-multi-phase/src/benchmarking.rs +++ b/frame/election-provider-multi-phase/src/benchmarking.rs @@ -309,7 +309,6 @@ frame_benchmarking::benchmarks! { } submit { - // the solution will be worse than all of them meaning the score need to be checked against // ~ log2(c) let solution = RawSolution { @@ -341,7 +340,11 @@ frame_benchmarking::benchmarks! { signed_submissions.put(); let caller = frame_benchmarking::whitelisted_caller(); - T::Currency::make_free_balance_be(&caller, T::Currency::minimum_balance() * 10u32.into()); + let deposit = MultiPhase::::deposit_for( + &solution, + MultiPhase::::snapshot_metadata().unwrap_or_default(), + ); + T::Currency::make_free_balance_be(&caller, T::Currency::minimum_balance() * 1000u32.into() + deposit); }: _(RawOrigin::Signed(caller), Box::new(solution)) verify { From d8b2e611e6ebd8a43c8dd331ae5379eceb2b4753 Mon Sep 17 00:00:00 2001 From: AurevoirXavier Date: Fri, 18 Mar 2022 20:35:38 +0800 Subject: [PATCH 030/484] Use Lowercase (#11059) --- utils/frame/try-runtime/cli/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/frame/try-runtime/cli/src/lib.rs b/utils/frame/try-runtime/cli/src/lib.rs index 2298fa5c042ee..ef8db7a1e5a0c 100644 --- a/utils/frame/try-runtime/cli/src/lib.rs +++ b/utils/frame/try-runtime/cli/src/lib.rs @@ -619,7 +619,7 @@ pub(crate) async fn ensure_matching_spec { // first, deal with spec name - if expected_spec_name == name { + if expected_spec_name.to_lowercase() == name { log::info!(target: LOG_TARGET, "found matching spec name: {:?}", name); } else { let msg = format!( From f7107984c78402b536f93ff6f10499f53e947575 Mon Sep 17 00:00:00 2001 From: AurevoirXavier Date: Fri, 18 Mar 2022 23:03:21 +0800 Subject: [PATCH 031/484] Expose `ToOwned` (#11060) --- primitives/std/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/primitives/std/src/lib.rs b/primitives/std/src/lib.rs index 03a079e3965f4..05dff0f3077b6 100644 --- a/primitives/std/src/lib.rs +++ b/primitives/std/src/lib.rs @@ -99,6 +99,7 @@ impl Writer { /// This should include only things which are in the normal std prelude. pub mod prelude { pub use crate::{ + borrow::ToOwned, boxed::Box, clone::Clone, cmp::{Eq, PartialEq, Reverse}, From 6486864f9b0bed3c5502cf0341ee7ef3e4cc15cd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 18 Mar 2022 15:25:16 +0000 Subject: [PATCH 032/484] Bump serde_json from 1.0.74 to 1.0.79 (#11020) Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.74 to 1.0.79. - [Release notes](https://github.com/serde-rs/json/releases) - [Commits](https://github.com/serde-rs/json/compare/v1.0.74...v1.0.79) --- updated-dependencies: - dependency-name: serde_json dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- bin/node/bench/Cargo.toml | 2 +- client/beefy/rpc/Cargo.toml | 2 +- client/chain-spec/Cargo.toml | 2 +- client/cli/Cargo.toml | 2 +- client/consensus/babe/rpc/Cargo.toml | 2 +- client/finality-grandpa/Cargo.toml | 2 +- client/keystore/Cargo.toml | 2 +- client/network/Cargo.toml | 2 +- client/peerset/Cargo.toml | 2 +- client/rpc-api/Cargo.toml | 2 +- client/rpc-servers/Cargo.toml | 2 +- client/rpc/Cargo.toml | 2 +- client/service/Cargo.toml | 2 +- client/sync-state-rpc/Cargo.toml | 2 +- client/telemetry/Cargo.toml | 2 +- frame/merkle-mountain-range/rpc/Cargo.toml | 2 +- frame/transaction-payment/Cargo.toml | 2 +- frame/transaction-payment/asset-tx-payment/Cargo.toml | 2 +- primitives/rpc/Cargo.toml | 2 +- primitives/runtime/Cargo.toml | 2 +- primitives/serializer/Cargo.toml | 2 +- test-utils/client/Cargo.toml | 2 +- utils/frame/benchmarking-cli/Cargo.toml | 2 +- 24 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fb9561dfabdcb..d3b0771c451c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9399,9 +9399,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.74" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee2bb9cd061c5865d345bb02ca49fcef1391741b672b54a0bf7b679badec3142" +checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" dependencies = [ "itoa 1.0.1", "ryu", diff --git a/bin/node/bench/Cargo.toml b/bin/node/bench/Cargo.toml index 5b728258dd03b..7bdf46e2b6600 100644 --- a/bin/node/bench/Cargo.toml +++ b/bin/node/bench/Cargo.toml @@ -18,7 +18,7 @@ sc-client-api = { version = "4.0.0-dev", path = "../../../client/api/" } sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } sp-state-machine = { version = "0.12.0", path = "../../../primitives/state-machine" } serde = "1.0.136" -serde_json = "1.0.74" +serde_json = "1.0.79" derive_more = "0.99.16" kvdb = "0.11.0" kvdb-rocksdb = "0.15.1" diff --git a/client/beefy/rpc/Cargo.toml b/client/beefy/rpc/Cargo.toml index 071b07b6b2294..ebbb9527f9367 100644 --- a/client/beefy/rpc/Cargo.toml +++ b/client/beefy/rpc/Cargo.toml @@ -31,7 +31,7 @@ beefy-gadget = { version = "4.0.0-dev", path = "../." } beefy-primitives = { version = "4.0.0-dev", path = "../../../primitives/beefy" } [dev-dependencies] -serde_json = "1.0.74" +serde_json = "1.0.79" sc-rpc = { version = "4.0.0-dev", path = "../../rpc", features = [ "test-helpers", diff --git a/client/chain-spec/Cargo.toml b/client/chain-spec/Cargo.toml index 68d591254d213..e18f9b72c75e4 100644 --- a/client/chain-spec/Cargo.toml +++ b/client/chain-spec/Cargo.toml @@ -18,7 +18,7 @@ impl-trait-for-tuples = "0.2.1" sc-network = { version = "0.10.0-dev", path = "../network" } sp-core = { version = "6.0.0", path = "../../primitives/core" } serde = { version = "1.0.136", features = ["derive"] } -serde_json = "1.0.74" +serde_json = "1.0.79" sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } sc-telemetry = { version = "4.0.0-dev", path = "../telemetry" } codec = { package = "parity-scale-codec", version = "3.0.0" } diff --git a/client/cli/Cargo.toml b/client/cli/Cargo.toml index c799fd6dee980..665bd6acfaf33 100644 --- a/client/cli/Cargo.toml +++ b/client/cli/Cargo.toml @@ -25,7 +25,7 @@ rand = "0.7.3" regex = "1.5.4" rpassword = "5.0.0" serde = "1.0.136" -serde_json = "1.0.74" +serde_json = "1.0.79" thiserror = "1.0.30" tiny-bip39 = "0.8.2" tokio = { version = "1.15", features = ["signal", "rt-multi-thread", "parking_lot"] } diff --git a/client/consensus/babe/rpc/Cargo.toml b/client/consensus/babe/rpc/Cargo.toml index 7db7edd2de133..ee6549480a6d2 100644 --- a/client/consensus/babe/rpc/Cargo.toml +++ b/client/consensus/babe/rpc/Cargo.toml @@ -33,7 +33,7 @@ sp-keystore = { version = "0.12.0", path = "../../../../primitives/keystore" } [dev-dependencies] sc-consensus = { version = "0.10.0-dev", path = "../../../consensus/common" } -serde_json = "1.0.74" +serde_json = "1.0.79" sp-keyring = { version = "6.0.0", path = "../../../../primitives/keyring" } sc-keystore = { version = "4.0.0-dev", path = "../../../keystore" } substrate-test-runtime-client = { version = "2.0.0", path = "../../../../test-utils/runtime/client" } diff --git a/client/finality-grandpa/Cargo.toml b/client/finality-grandpa/Cargo.toml index 9733f35bd0e93..86cd57fc7c092 100644 --- a/client/finality-grandpa/Cargo.toml +++ b/client/finality-grandpa/Cargo.toml @@ -37,7 +37,7 @@ sp-keystore = { version = "0.12.0", path = "../../primitives/keystore" } sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } sc-telemetry = { version = "4.0.0-dev", path = "../telemetry" } sc-keystore = { version = "4.0.0-dev", path = "../keystore" } -serde_json = "1.0.74" +serde_json = "1.0.79" sc-client-api = { version = "4.0.0-dev", path = "../api" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } sc-network = { version = "0.10.0-dev", path = "../network" } diff --git a/client/keystore/Cargo.toml b/client/keystore/Cargo.toml index 09fca2a843a13..844110f668869 100644 --- a/client/keystore/Cargo.toml +++ b/client/keystore/Cargo.toml @@ -22,7 +22,7 @@ sp-core = { version = "6.0.0", path = "../../primitives/core" } sp-keystore = { version = "0.12.0", path = "../../primitives/keystore" } hex = "0.4.0" parking_lot = "0.12.0" -serde_json = "1.0.74" +serde_json = "1.0.79" [dev-dependencies] tempfile = "3.1.0" diff --git a/client/network/Cargo.toml b/client/network/Cargo.toml index 0dbdd3ec50e95..671271fcb3b77 100644 --- a/client/network/Cargo.toml +++ b/client/network/Cargo.toml @@ -45,7 +45,7 @@ sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } sc-client-api = { version = "4.0.0-dev", path = "../api" } sc-peerset = { version = "4.0.0-dev", path = "../peerset" } serde = { version = "1.0.136", features = ["derive"] } -serde_json = "1.0.74" +serde_json = "1.0.79" smallvec = "1.8.0" sp-arithmetic = { version = "5.0.0", path = "../../primitives/arithmetic" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } diff --git a/client/peerset/Cargo.toml b/client/peerset/Cargo.toml index 1bbf8bd6e9cba..12991eac81bdc 100644 --- a/client/peerset/Cargo.toml +++ b/client/peerset/Cargo.toml @@ -18,7 +18,7 @@ futures = "0.3.19" libp2p = { version = "0.40.0", default-features = false } sc-utils = { version = "4.0.0-dev", path = "../utils"} log = "0.4.8" -serde_json = "1.0.74" +serde_json = "1.0.79" wasm-timer = "0.2" [dev-dependencies] diff --git a/client/rpc-api/Cargo.toml b/client/rpc-api/Cargo.toml index 510c59e5b8aac..38ac1fc443351 100644 --- a/client/rpc-api/Cargo.toml +++ b/client/rpc-api/Cargo.toml @@ -28,7 +28,7 @@ sp-version = { version = "5.0.0", path = "../../primitives/version" } sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } sc-chain-spec = { path = "../chain-spec", version = "4.0.0-dev" } serde = { version = "1.0.136", features = ["derive"] } -serde_json = "1.0.74" +serde_json = "1.0.79" sc-transaction-pool-api = { version = "4.0.0-dev", path = "../transaction-pool/api" } sp-rpc = { version = "6.0.0", path = "../../primitives/rpc" } sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } diff --git a/client/rpc-servers/Cargo.toml b/client/rpc-servers/Cargo.toml index 496152ecb7505..f0c3fd496aa70 100644 --- a/client/rpc-servers/Cargo.toml +++ b/client/rpc-servers/Cargo.toml @@ -18,7 +18,7 @@ jsonrpc-core = "18.0.0" pubsub = { package = "jsonrpc-pubsub", version = "18.0.0" } log = "0.4.8" prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus", version = "0.10.0-dev"} -serde_json = "1.0.74" +serde_json = "1.0.79" tokio = { version = "1.15", features = ["parking_lot"] } http = { package = "jsonrpc-http-server", version = "18.0.0" } ipc = { package = "jsonrpc-ipc-server", version = "18.0.0" } diff --git a/client/rpc/Cargo.toml b/client/rpc/Cargo.toml index 314c49d2f862c..eeeb67f362617 100644 --- a/client/rpc/Cargo.toml +++ b/client/rpc/Cargo.toml @@ -23,7 +23,7 @@ log = "0.4.8" sp-core = { version = "6.0.0", path = "../../primitives/core" } rpc = { package = "jsonrpc-core", version = "18.0.0" } sp-version = { version = "5.0.0", path = "../../primitives/version" } -serde_json = "1.0.74" +serde_json = "1.0.79" sp-session = { version = "4.0.0-dev", path = "../../primitives/session" } sp-offchain = { version = "4.0.0-dev", path = "../../primitives/offchain" } sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } diff --git a/client/service/Cargo.toml b/client/service/Cargo.toml index 84ac9e3de642b..737a29f1db0c0 100644 --- a/client/service/Cargo.toml +++ b/client/service/Cargo.toml @@ -34,7 +34,7 @@ exit-future = "0.2.0" pin-project = "1.0.10" hash-db = "0.15.2" serde = "1.0.136" -serde_json = "1.0.74" +serde_json = "1.0.79" sc-keystore = { version = "4.0.0-dev", path = "../keystore" } sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } sp-trie = { version = "6.0.0", path = "../../primitives/trie" } diff --git a/client/sync-state-rpc/Cargo.toml b/client/sync-state-rpc/Cargo.toml index e6072267f9638..e13631f210bc0 100644 --- a/client/sync-state-rpc/Cargo.toml +++ b/client/sync-state-rpc/Cargo.toml @@ -22,7 +22,7 @@ sc-client-api = { version = "4.0.0-dev", path = "../api" } sc-consensus-babe = { version = "0.10.0-dev", path = "../consensus/babe" } sc-consensus-epochs = { version = "0.10.0-dev", path = "../consensus/epochs" } sc-finality-grandpa = { version = "0.10.0-dev", path = "../finality-grandpa" } -serde_json = "1.0.74" +serde_json = "1.0.79" serde = { version = "1.0.136", features = ["derive"] } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } diff --git a/client/telemetry/Cargo.toml b/client/telemetry/Cargo.toml index 7bdd60a037e17..6daa75f453629 100644 --- a/client/telemetry/Cargo.toml +++ b/client/telemetry/Cargo.toml @@ -23,6 +23,6 @@ log = "0.4.8" pin-project = "1.0.10" rand = "0.7.2" serde = { version = "1.0.136", features = ["derive"] } -serde_json = "1.0.74" +serde_json = "1.0.79" chrono = "0.4.19" thiserror = "1.0.30" diff --git a/frame/merkle-mountain-range/rpc/Cargo.toml b/frame/merkle-mountain-range/rpc/Cargo.toml index 7124682c5689a..9ac26c2ed54b2 100644 --- a/frame/merkle-mountain-range/rpc/Cargo.toml +++ b/frame/merkle-mountain-range/rpc/Cargo.toml @@ -27,4 +27,4 @@ sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } pallet-mmr-primitives = { version = "4.0.0-dev", path = "../primitives" } [dev-dependencies] -serde_json = "1.0.74" +serde_json = "1.0.79" diff --git a/frame/transaction-payment/Cargo.toml b/frame/transaction-payment/Cargo.toml index ea894e7cada66..d37a98deecb2a 100644 --- a/frame/transaction-payment/Cargo.toml +++ b/frame/transaction-payment/Cargo.toml @@ -29,7 +29,7 @@ frame-support = { version = "4.0.0-dev", default-features = false, path = "../su frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } [dev-dependencies] -serde_json = "1.0.74" +serde_json = "1.0.79" pallet-balances = { version = "4.0.0-dev", path = "../balances" } [features] diff --git a/frame/transaction-payment/asset-tx-payment/Cargo.toml b/frame/transaction-payment/asset-tx-payment/Cargo.toml index ec07fc967c923..0c2bcb730aa75 100644 --- a/frame/transaction-payment/asset-tx-payment/Cargo.toml +++ b/frame/transaction-payment/asset-tx-payment/Cargo.toml @@ -30,7 +30,7 @@ serde = { version = "1.0.136", optional = true } [dev-dependencies] smallvec = "1.8.0" -serde_json = "1.0.74" +serde_json = "1.0.79" sp-storage = { version = "6.0.0", default-features = false, path = "../../../primitives/storage" } diff --git a/primitives/rpc/Cargo.toml b/primitives/rpc/Cargo.toml index f2d048ef713e7..dcfd48558de25 100644 --- a/primitives/rpc/Cargo.toml +++ b/primitives/rpc/Cargo.toml @@ -18,4 +18,4 @@ sp-core = { version = "6.0.0", path = "../core" } rustc-hash = "1.1.0" [dev-dependencies] -serde_json = "1.0.74" +serde_json = "1.0.79" diff --git a/primitives/runtime/Cargo.toml b/primitives/runtime/Cargo.toml index 5f7b45ba15a8c..a0acdaf27dc5b 100644 --- a/primitives/runtime/Cargo.toml +++ b/primitives/runtime/Cargo.toml @@ -32,7 +32,7 @@ hash256-std-hasher = { version = "0.15.2", default-features = false } either = { version = "1.5", default-features = false } [dev-dependencies] -serde_json = "1.0.74" +serde_json = "1.0.79" rand = "0.7.2" sp-state-machine = { version = "0.12.0", path = "../state-machine" } sp-api = { version = "4.0.0-dev", path = "../api" } diff --git a/primitives/serializer/Cargo.toml b/primitives/serializer/Cargo.toml index 6a9fb5d90ddb4..c81f1cd10a824 100644 --- a/primitives/serializer/Cargo.toml +++ b/primitives/serializer/Cargo.toml @@ -15,4 +15,4 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] serde = "1.0.136" -serde_json = "1.0.74" +serde_json = "1.0.79" diff --git a/test-utils/client/Cargo.toml b/test-utils/client/Cargo.toml index f3299d800a6f7..1f7115cf16cf8 100644 --- a/test-utils/client/Cargo.toml +++ b/test-utils/client/Cargo.toml @@ -16,7 +16,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.19" hex = "0.4" serde = "1.0.136" -serde_json = "1.0.74" +serde_json = "1.0.79" sc-client-api = { version = "4.0.0-dev", path = "../../client/api" } sc-client-db = { version = "0.10.0-dev", features = [ "test-helpers", diff --git a/utils/frame/benchmarking-cli/Cargo.toml b/utils/frame/benchmarking-cli/Cargo.toml index 5575bb833ca77..b28fe195a2f31 100644 --- a/utils/frame/benchmarking-cli/Cargo.toml +++ b/utils/frame/benchmarking-cli/Cargo.toml @@ -38,7 +38,7 @@ codec = { version = "3.0.0", package = "parity-scale-codec" } clap = { version = "3.1.6", features = ["derive"] } chrono = "0.4" serde = "1.0.136" -serde_json = "1.0.74" +serde_json = "1.0.79" handlebars = "4.1.6" Inflector = "0.11.4" linked-hash-map = "0.5.4" From c0f55e9fcd9368d85f910796cd6066f9dea2c422 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 18 Mar 2022 17:16:09 +0000 Subject: [PATCH 033/484] Bump proc-macro-crate from 1.1.0 to 1.1.3 (#11063) Bumps [proc-macro-crate](https://github.com/bkchr/proc-macro-crate) from 1.1.0 to 1.1.3. - [Release notes](https://github.com/bkchr/proc-macro-crate/releases) - [Commits](https://github.com/bkchr/proc-macro-crate/commits) --- updated-dependencies: - dependency-name: proc-macro-crate dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 28 +++++++++---------- client/chain-spec/derive/Cargo.toml | 2 +- client/tracing/proc-macro/Cargo.toml | 2 +- .../solution-type/Cargo.toml | 2 +- frame/staking/reward-curve/Cargo.toml | 2 +- frame/support/procedural/tools/Cargo.toml | 2 +- primitives/api/proc-macro/Cargo.toml | 2 +- .../runtime-interface/proc-macro/Cargo.toml | 2 +- test-utils/derive/Cargo.toml | 2 +- 9 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d3b0771c451c5..70a50a6f3e23b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2168,7 +2168,7 @@ version = "4.0.0-dev" dependencies = [ "frame-election-provider-support", "parity-scale-codec", - "proc-macro-crate 1.1.0", + "proc-macro-crate 1.1.3", "proc-macro2", "quote", "scale-info", @@ -2290,7 +2290,7 @@ name = "frame-support-procedural-tools" version = "4.0.0-dev" dependencies = [ "frame-support-procedural-tools-derive", - "proc-macro-crate 1.1.0", + "proc-macro-crate 1.1.3", "proc-macro2", "quote", "syn", @@ -3448,7 +3448,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4299ebf790ea9de1cb72e73ff2ae44c723ef264299e5e2d5ef46a371eb3ac3d8" dependencies = [ - "proc-macro-crate 1.1.0", + "proc-macro-crate 1.1.3", "proc-macro2", "quote", "syn", @@ -4663,7 +4663,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "424f6e86263cd5294cbd7f1e95746b95aca0e0d66bff31e5a40d6baa87b4aa99" dependencies = [ - "proc-macro-crate 1.1.0", + "proc-macro-crate 1.1.3", "proc-macro-error", "proc-macro2", "quote", @@ -6441,7 +6441,7 @@ dependencies = [ name = "pallet-staking-reward-curve" version = "4.0.0-dev" dependencies = [ - "proc-macro-crate 1.1.0", + "proc-macro-crate 1.1.3", "proc-macro2", "quote", "sp-runtime", @@ -6736,7 +6736,7 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c6e626dc84025ff56bf1476ed0e30d10c84d7f89a475ef46ebabee1095a8fba" dependencies = [ - "proc-macro-crate 1.1.0", + "proc-macro-crate 1.1.3", "proc-macro2", "quote", "syn", @@ -7230,9 +7230,9 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.1.0" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebace6889caf889b4d3f76becee12e90353f2b8c7d875534a71e5742f8f6f83" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" dependencies = [ "thiserror", "toml", @@ -8107,7 +8107,7 @@ dependencies = [ name = "sc-chain-spec-derive" version = "4.0.0-dev" dependencies = [ - "proc-macro-crate 1.1.0", + "proc-macro-crate 1.1.3", "proc-macro2", "quote", "syn", @@ -9092,7 +9092,7 @@ dependencies = [ name = "sc-tracing-proc-macro" version = "4.0.0-dev" dependencies = [ - "proc-macro-crate 1.1.0", + "proc-macro-crate 1.1.3", "proc-macro2", "quote", "syn", @@ -9177,7 +9177,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7805950c36512db9e3251c970bb7ac425f326716941862205d612ab3b5e46e2" dependencies = [ - "proc-macro-crate 1.1.0", + "proc-macro-crate 1.1.3", "proc-macro2", "quote", "syn", @@ -9670,7 +9670,7 @@ name = "sp-api-proc-macro" version = "4.0.0-dev" dependencies = [ "blake2 0.10.2", - "proc-macro-crate 1.1.0", + "proc-macro-crate 1.1.3", "proc-macro2", "quote", "syn", @@ -10193,7 +10193,7 @@ name = "sp-runtime-interface-proc-macro" version = "5.0.0" dependencies = [ "Inflector", - "proc-macro-crate 1.1.0", + "proc-macro-crate 1.1.3", "proc-macro2", "quote", "syn", @@ -10773,7 +10773,7 @@ dependencies = [ name = "substrate-test-utils-derive" version = "0.10.0-dev" dependencies = [ - "proc-macro-crate 1.1.0", + "proc-macro-crate 1.1.3", "proc-macro2", "quote", "syn", diff --git a/client/chain-spec/derive/Cargo.toml b/client/chain-spec/derive/Cargo.toml index 1ea9440572d47..9aa1d6c09405c 100644 --- a/client/chain-spec/derive/Cargo.toml +++ b/client/chain-spec/derive/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -proc-macro-crate = "1.1.0" +proc-macro-crate = "1.1.3" proc-macro2 = "1.0.36" quote = "1.0.10" syn = "1.0.82" diff --git a/client/tracing/proc-macro/Cargo.toml b/client/tracing/proc-macro/Cargo.toml index e939d0837b216..645a6ed93a16f 100644 --- a/client/tracing/proc-macro/Cargo.toml +++ b/client/tracing/proc-macro/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -proc-macro-crate = "1.1.0" +proc-macro-crate = "1.1.3" proc-macro2 = "1.0.36" quote = { version = "1.0.10", features = ["proc-macro"] } syn = { version = "1.0.82", features = ["proc-macro", "full", "extra-traits", "parsing"] } diff --git a/frame/election-provider-support/solution-type/Cargo.toml b/frame/election-provider-support/solution-type/Cargo.toml index 09747eebe2dff..e59bbcc8e7b38 100644 --- a/frame/election-provider-support/solution-type/Cargo.toml +++ b/frame/election-provider-support/solution-type/Cargo.toml @@ -18,7 +18,7 @@ proc-macro = true syn = { version = "1.0.82", features = ["full", "visit"] } quote = "1.0" proc-macro2 = "1.0.36" -proc-macro-crate = "1.1.0" +proc-macro-crate = "1.1.3" [dev-dependencies] parity-scale-codec = "3.0.0" diff --git a/frame/staking/reward-curve/Cargo.toml b/frame/staking/reward-curve/Cargo.toml index 82167bd8c8ec4..d53fb72b0e08a 100644 --- a/frame/staking/reward-curve/Cargo.toml +++ b/frame/staking/reward-curve/Cargo.toml @@ -18,7 +18,7 @@ proc-macro = true syn = { version = "1.0.82", features = ["full", "visit"] } quote = "1.0.10" proc-macro2 = "1.0.36" -proc-macro-crate = "1.1.0" +proc-macro-crate = "1.1.3" [dev-dependencies] sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } diff --git a/frame/support/procedural/tools/Cargo.toml b/frame/support/procedural/tools/Cargo.toml index ae2de2effd4ae..b38071dd31585 100644 --- a/frame/support/procedural/tools/Cargo.toml +++ b/frame/support/procedural/tools/Cargo.toml @@ -16,4 +16,4 @@ frame-support-procedural-tools-derive = { version = "3.0.0", path = "./derive" } proc-macro2 = "1.0.36" quote = "1.0.10" syn = { version = "1.0.82", features = ["full", "visit", "extra-traits"] } -proc-macro-crate = "1.1.0" +proc-macro-crate = "1.1.3" diff --git a/primitives/api/proc-macro/Cargo.toml b/primitives/api/proc-macro/Cargo.toml index 356e15a6fd115..dc5deb2efa668 100644 --- a/primitives/api/proc-macro/Cargo.toml +++ b/primitives/api/proc-macro/Cargo.toml @@ -20,7 +20,7 @@ quote = "1.0.10" syn = { version = "1.0.82", features = ["full", "fold", "extra-traits", "visit"] } proc-macro2 = "1.0.36" blake2 = { version = "0.10.2", default-features = false } -proc-macro-crate = "1.1.0" +proc-macro-crate = "1.1.3" # Required for the doc tests [features] diff --git a/primitives/runtime-interface/proc-macro/Cargo.toml b/primitives/runtime-interface/proc-macro/Cargo.toml index 57e5448348d6c..ef59e0119f9fe 100644 --- a/primitives/runtime-interface/proc-macro/Cargo.toml +++ b/primitives/runtime-interface/proc-macro/Cargo.toml @@ -20,4 +20,4 @@ syn = { version = "1.0.82", features = ["full", "visit", "fold", "extra-traits"] quote = "1.0.10" proc-macro2 = "1.0.36" Inflector = "0.11.4" -proc-macro-crate = "1.1.0" +proc-macro-crate = "1.1.3" diff --git a/test-utils/derive/Cargo.toml b/test-utils/derive/Cargo.toml index 166d9cc1eff17..16e0b9822f7bb 100644 --- a/test-utils/derive/Cargo.toml +++ b/test-utils/derive/Cargo.toml @@ -11,7 +11,7 @@ description = "Substrate test utilities macros" [dependencies] quote = "1.0.10" syn = { version = "1.0.82", features = ["full"] } -proc-macro-crate = "1.1.0" +proc-macro-crate = "1.1.3" proc-macro2 = "1.0.36" [lib] From dda72430f8a90deeadea92aa45cf46a1f1805010 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Sat, 19 Mar 2022 11:35:39 +0000 Subject: [PATCH 034/484] some additional utils for state-trie-migration (#11064) * some additional utils for state-trie-migration' * Update frame/state-trie-migration/src/lib.rs Co-authored-by: cheme Co-authored-by: cheme --- bin/node/runtime/src/lib.rs | 3 -- frame/state-trie-migration/src/lib.rs | 75 ++++++++++++++++++++++----- 2 files changed, 63 insertions(+), 15 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 797e0c079028e..d679af6f51c42 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1372,8 +1372,6 @@ impl pallet_whitelist::Config for Runtime { } parameter_types! { - pub const SignedMigrationMaxLimits: pallet_state_trie_migration::MigrationLimits = - pallet_state_trie_migration::MigrationLimits { size: 1024 * 1024 / 2, item: 512 }; pub const MigrationSignedDepositPerItem: Balance = 1 * CENTS; pub const MigrationSignedDepositBase: Balance = 20 * DOLLARS; } @@ -1384,7 +1382,6 @@ impl pallet_state_trie_migration::Config for Runtime { type Currency = Balances; type SignedDepositPerItem = MigrationSignedDepositPerItem; type SignedDepositBase = MigrationSignedDepositBase; - type SignedMigrationMaxLimits = SignedMigrationMaxLimits; // Warning: this is not advised, as it might allow the chain to be temporarily DOS-ed. // Preferably, if the chain's governance/maintenance team is planning on using a specific // account for the migration, put it here to make sure only that account can trigger the signed diff --git a/frame/state-trie-migration/src/lib.rs b/frame/state-trie-migration/src/lib.rs index a5a077c54e579..87be4099c89c8 100644 --- a/frame/state-trie-migration/src/lib.rs +++ b/frame/state-trie-migration/src/lib.rs @@ -123,10 +123,14 @@ pub mod pallet { } } + /// The progress of either the top or child keys. #[derive(Clone, Encode, Decode, scale_info::TypeInfo, PartialEq, Eq)] - pub(crate) enum Progress { + pub enum Progress { + /// Yet to begin. ToStart, + /// Ongoing, with the last key given. LastKey(Vec), + /// All done. Complete, } @@ -447,10 +451,6 @@ pub mod pallet { /// Final deposit is `items * SignedDepositPerItem + SignedDepositBase`. type SignedDepositBase: Get>; - /// The maximum limits that the signed migration could use. - #[pallet::constant] - type SignedMigrationMaxLimits: Get; - /// The weight information of this pallet. type WeightInfo: WeightInfo; } @@ -470,6 +470,13 @@ pub mod pallet { #[pallet::getter(fn auto_limits)] pub type AutoLimits = StorageValue<_, Option, ValueQuery>; + /// The maximum limits that the signed migration could use. + /// + /// If not set, no signed submission is allowed. + #[pallet::storage] + #[pallet::getter(fn signed_migration_max_limits)] + pub type SignedMigrationMaxLimits = StorageValue<_, MigrationLimits, OptionQuery>; + #[pallet::error] pub enum Error { /// max signed limits not respected. @@ -480,6 +487,8 @@ pub mod pallet { BadWitness, /// upper bound of size is exceeded, SizeUpperBoundExceeded, + /// Signed migration is not allowed because the maximum limit is not set yet. + SignedMigrationNotAllowed, } #[pallet::call] @@ -491,7 +500,7 @@ pub mod pallet { pub fn control_auto_migration( origin: OriginFor, maybe_config: Option, - ) -> DispatchResultWithPostInfo { + ) -> DispatchResult { T::ControlOrigin::ensure_origin(origin)?; AutoLimits::::put(maybe_config); Ok(().into()) @@ -532,7 +541,8 @@ pub mod pallet { ) -> DispatchResultWithPostInfo { let who = T::SignedFilter::ensure_origin(origin)?; - let max_limits = T::SignedMigrationMaxLimits::get(); + let max_limits = + Self::signed_migration_max_limits().ok_or(Error::::SignedMigrationNotAllowed)?; ensure!( limits.size <= max_limits.size && limits.item <= max_limits.item, Error::::MaxSignedLimits, @@ -701,6 +711,40 @@ pub mod pallet { }) } } + + /// Set the maximum limit of the signed migration. + #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))] + pub fn set_signed_max_limits( + origin: OriginFor, + limits: MigrationLimits, + ) -> DispatchResult { + let _ = T::ControlOrigin::ensure_origin(origin)?; + SignedMigrationMaxLimits::::put(limits); + Ok(()) + } + + /// Forcefully set the progress the running migration. + /// + /// This is only useful in one case: the next key to migrate is too big to be migrated with + /// a signed account, in a parachain context, and we simply want to skip it. A reasonable + /// example of this would be `:code:`, which is both very expensive to migrate, and commonly + /// used, so probably it is already migrated. + /// + /// In case you mess things up, you can also, in principle, use this to reset the migration + /// process. + #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))] + pub fn force_set_progress( + origin: OriginFor, + progress_top: Progress, + progress_child: Progress, + ) -> DispatchResult { + let _ = T::ControlOrigin::ensure_origin(origin)?; + MigrationProcess::::mutate(|task| { + task.progress_top = progress_top; + task.progress_child = progress_child; + }); + Ok(()) + } } #[pallet::hooks] @@ -1000,7 +1044,6 @@ mod mock { pub const OffchainRepeat: u32 = 1; pub const SignedDepositPerItem: u64 = 1; pub const SignedDepositBase: u64 = 5; - pub const SignedMigrationMaxLimits: MigrationLimits = MigrationLimits { size: 1024, item: 5 }; } impl pallet_balances::Config for Test { @@ -1021,7 +1064,6 @@ mod mock { type Currency = Balances; type SignedDepositPerItem = SignedDepositPerItem; type SignedDepositBase = SignedDepositBase; - type SignedMigrationMaxLimits = SignedMigrationMaxLimits; type SignedFilter = EnsureSigned; type WeightInfo = (); } @@ -1104,7 +1146,14 @@ mod mock { } sp_tracing::try_init_simple(); - (custom_storage, version).into() + let mut ext: sp_io::TestExternalities = (custom_storage, version).into(); + + // set some genesis values for this pallet as well. + ext.execute_with(|| { + SignedMigrationMaxLimits::::put(MigrationLimits { size: 1024, item: 5 }); + }); + + ext } pub(crate) fn run_to_block(n: u32) -> (H256, u64) { @@ -1283,11 +1332,13 @@ mod test { while !MigrationProcess::::get().finished() { // first we compute the task to get the accurate consumption. let mut task = StateTrieMigration::migration_process(); - task.migrate_until_exhaustion(SignedMigrationMaxLimits::get()); + task.migrate_until_exhaustion( + StateTrieMigration::signed_migration_max_limits().unwrap(), + ); frame_support::assert_ok!(StateTrieMigration::continue_migrate( Origin::signed(1), - SignedMigrationMaxLimits::get(), + StateTrieMigration::signed_migration_max_limits().unwrap(), task.dyn_size, MigrationProcess::::get() )); From 5a390393c8c7aea3a210d72164d76ea02e805d90 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Sat, 19 Mar 2022 12:24:41 +0000 Subject: [PATCH 035/484] Incorporate the new electing/electable naming into the code (#10956) * Incorporate the new electing/electable naming into the code * Update frame/election-provider-support/src/lib.rs Co-authored-by: Zeke Mostov * Update frame/election-provider-support/src/lib.rs Co-authored-by: Zeke Mostov * Some additional changes * fmt * update codec * revert lock file to master * fix doc test Co-authored-by: Zeke Mostov --- bin/node/runtime/src/lib.rs | 16 +++---- frame/bags-list/remote-tests/src/snapshot.rs | 3 +- .../src/benchmarking.rs | 4 +- .../election-provider-multi-phase/src/lib.rs | 37 +++++++-------- .../election-provider-multi-phase/src/mock.rs | 13 +++-- frame/election-provider-support/src/lib.rs | 17 ++++--- .../election-provider-support/src/onchain.rs | 11 +++-- frame/staking/src/pallet/impls.rs | 4 +- frame/staking/src/tests.rs | 47 ++++++++++--------- 9 files changed, 82 insertions(+), 70 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index d679af6f51c42..64bdcfc870205 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -46,11 +46,12 @@ use frame_system::{ pub use node_primitives::{AccountId, Signature}; use node_primitives::{AccountIndex, Balance, BlockNumber, Hash, Index, Moment}; use pallet_contracts::weights::WeightInfo; +use pallet_election_provider_multi_phase::SolutionAccuracyOf; use pallet_grandpa::{ fg_primitives, AuthorityId as GrandpaId, AuthorityList as GrandpaAuthorityList, }; use pallet_im_online::sr25519::AuthorityId as ImOnlineId; -use pallet_session::historical as pallet_session_historical; +use pallet_session::historical::{self as pallet_session_historical}; pub use pallet_transaction_payment::{CurrencyAdapter, Multiplier, TargetedFeeAdjustment}; use pallet_transaction_payment::{FeeDetails, RuntimeDispatchInfo}; use sp_api::impl_runtime_apis; @@ -668,20 +669,17 @@ impl pallet_election_provider_multi_phase::Config for Runtime { type DataProvider = Staking; type Solution = NposSolution16; type Fallback = pallet_election_provider_multi_phase::NoFallback; - type GovernanceFallback = - frame_election_provider_support::onchain::OnChainSequentialPhragmen; + type GovernanceFallback = onchain::OnChainSequentialPhragmen; type Solver = frame_election_provider_support::SequentialPhragmen< AccountId, - pallet_election_provider_multi_phase::SolutionAccuracyOf, + SolutionAccuracyOf, OffchainRandomBalancing, >; - type WeightInfo = pallet_election_provider_multi_phase::weights::SubstrateWeight; type ForceOrigin = EnsureRootOrHalfCouncil; + type MaxElectableTargets = ConstU16<{ u16::MAX }>; + type MaxElectingVoters = ConstU32<10_000>; type BenchmarkingConfig = ElectionProviderBenchmarkConfig; - // BagsList allows a practically unbounded count of nominators to participate in NPoS elections. - // To ensure we respect memory limits when using the BagsList this must be set to a number of - // voters we know can fit into a single vec allocation. - type VoterSnapshotPerBlock = ConstU32<10_000>; + type WeightInfo = pallet_election_provider_multi_phase::weights::SubstrateWeight; } parameter_types! { diff --git a/frame/bags-list/remote-tests/src/snapshot.rs b/frame/bags-list/remote-tests/src/snapshot.rs index 241b64b366117..2d996746a29cc 100644 --- a/frame/bags-list/remote-tests/src/snapshot.rs +++ b/frame/bags-list/remote-tests/src/snapshot.rs @@ -56,7 +56,8 @@ pub async fn execute ); let voters = - as ElectionDataProvider>::voters(voter_limit).unwrap(); + as ElectionDataProvider>::electing_voters(voter_limit) + .unwrap(); let mut voters_nominator_only = voters .iter() diff --git a/frame/election-provider-multi-phase/src/benchmarking.rs b/frame/election-provider-multi-phase/src/benchmarking.rs index 2a9286369f394..923e9e2d984cc 100644 --- a/frame/election-provider-multi-phase/src/benchmarking.rs +++ b/frame/election-provider-multi-phase/src/benchmarking.rs @@ -260,8 +260,8 @@ frame_benchmarking::benchmarks! { // we don't directly need the data-provider to be populated, but it is just easy to use it. set_up_data_provider::(v, t); - let targets = T::DataProvider::targets(None)?; - let voters = T::DataProvider::voters(None)?; + let targets = T::DataProvider::electable_targets(None)?; + let voters = T::DataProvider::electing_voters(None)?; let desired_targets = T::DataProvider::desired_targets()?; assert!(>::snapshot().is_none()); }: { diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 90c540b85d67d..b57d24d2d530c 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -69,7 +69,7 @@ //! Upon the end of the signed phase, the solutions are examined from best to worse (i.e. `pop()`ed //! until drained). Each solution undergoes an expensive `Pallet::feasibility_check`, which ensures //! the score claimed by this score was correct, and it is valid based on the election data (i.e. -//! votes and candidates). At each step, if the current best solution passes the feasibility check, +//! votes and targets). At each step, if the current best solution passes the feasibility check, //! it is considered to be the best one. The sender of the origin is rewarded, and the rest of the //! queued solutions get their deposit back and are discarded, without being checked. //! @@ -249,7 +249,6 @@ use sp_npos_elections::{ assignment_ratio_to_staked_normalized, ElectionScore, EvaluateSupport, Supports, VoteWeight, }; use sp_runtime::{ - traits::Bounded, transaction_validity::{ InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity, TransactionValidityError, ValidTransaction, @@ -643,14 +642,15 @@ pub mod pallet { #[pallet::constant] type SignedDepositWeight: Get>; - /// The maximum number of voters to put in the snapshot. At the moment, snapshots are only - /// over a single block, but once multi-block elections are introduced they will take place - /// over multiple blocks. - /// - /// Also, note the data type: If the voters are represented by a `u32` in `type - /// CompactSolution`, the same `u32` is used here to ensure bounds are respected. + /// The maximum number of electing voters to put in the snapshot. At the moment, snapshots + /// are only over a single block, but once multi-block elections are introduced they will + /// take place over multiple blocks. + #[pallet::constant] + type MaxElectingVoters: Get>; + + /// The maximum number of electable targets to put in the snapshot. #[pallet::constant] - type VoterSnapshotPerBlock: Get>; + type MaxElectableTargets: Get>; /// Handler for the slashed deposits. type SlashHandler: OnUnbalanced>; @@ -817,7 +817,7 @@ pub mod pallet { fn integrity_test() { use sp_std::mem::size_of; // The index type of both voters and targets need to be smaller than that of usize (very - // unlikely to be the case, but anyhow). + // unlikely to be the case, but anyhow).. assert!(size_of::>() <= size_of::()); assert!(size_of::>() <= size_of::()); @@ -1338,14 +1338,13 @@ impl Pallet { /// Extracted for easier weight calculation. fn create_snapshot_external( ) -> Result<(Vec, Vec>, u32), ElectionError> { - let target_limit = >::max_value().saturated_into::(); - // for now we have just a single block snapshot. - let voter_limit = T::VoterSnapshotPerBlock::get().saturated_into::(); - - let targets = - T::DataProvider::targets(Some(target_limit)).map_err(ElectionError::DataProvider)?; - let voters = - T::DataProvider::voters(Some(voter_limit)).map_err(ElectionError::DataProvider)?; + let target_limit = T::MaxElectableTargets::get().saturated_into::(); + let voter_limit = T::MaxElectingVoters::get().saturated_into::(); + + let targets = T::DataProvider::electable_targets(Some(target_limit)) + .map_err(ElectionError::DataProvider)?; + let voters = T::DataProvider::electing_voters(Some(voter_limit)) + .map_err(ElectionError::DataProvider)?; let mut desired_targets = T::DataProvider::desired_targets().map_err(ElectionError::DataProvider)?; @@ -2090,7 +2089,7 @@ mod tests { // we have 8 voters in total. assert_eq!(crate::mock::Voters::get().len(), 8); // but we want to take 2. - crate::mock::VoterSnapshotPerBlock::set(2); + crate::mock::MaxElectingVoters::set(2); // Signed phase opens just fine. roll_to(15); diff --git a/frame/election-provider-multi-phase/src/mock.rs b/frame/election-provider-multi-phase/src/mock.rs index d09d45784cba4..89b5b72565dcb 100644 --- a/frame/election-provider-multi-phase/src/mock.rs +++ b/frame/election-provider-multi-phase/src/mock.rs @@ -263,7 +263,8 @@ parameter_types! { pub static MinerMaxWeight: Weight = BlockWeights::get().max_block; pub static MinerMaxLength: u32 = 256; pub static MockWeightInfo: bool = false; - pub static VoterSnapshotPerBlock: VoterIndex = u32::max_value(); + pub static MaxElectingVoters: VoterIndex = u32::max_value(); + pub static MaxElectableTargets: TargetIndex = TargetIndex::max_value(); pub static EpochLength: u64 = 30; pub static OnChianFallback: bool = true; @@ -413,7 +414,8 @@ impl crate::Config for Runtime { type GovernanceFallback = NoFallback; type ForceOrigin = frame_system::EnsureRoot; type Solution = TestNposSolution; - type VoterSnapshotPerBlock = VoterSnapshotPerBlock; + type MaxElectingVoters = MaxElectingVoters; + type MaxElectableTargets = MaxElectableTargets; type Solver = SequentialPhragmen, Balancing>; } @@ -439,7 +441,8 @@ impl ElectionDataProvider for StakingMock { type AccountId = AccountId; type BlockNumber = u64; type MaxVotesPerVoter = MaxNominations; - fn targets(maybe_max_len: Option) -> data_provider::Result> { + + fn electable_targets(maybe_max_len: Option) -> data_provider::Result> { let targets = Targets::get(); if maybe_max_len.map_or(false, |max_len| targets.len() > max_len) { @@ -449,7 +452,9 @@ impl ElectionDataProvider for StakingMock { Ok(targets) } - fn voters(maybe_max_len: Option) -> data_provider::Result>> { + fn electing_voters( + maybe_max_len: Option, + ) -> data_provider::Result>> { let mut voters = Voters::get(); if let Some(max_len) = maybe_max_len { voters.truncate(max_len) diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index e1975a2681e6a..81fd841a6dc47 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -107,12 +107,12 @@ //! fn desired_targets() -> data_provider::Result { //! Ok(1) //! } -//! fn voters(maybe_max_len: Option) +//! fn electing_voters(maybe_max_len: Option) //! -> data_provider::Result>> //! { //! Ok(Default::default()) //! } -//! fn targets(maybe_max_len: Option) -> data_provider::Result> { +//! fn electable_targets(maybe_max_len: Option) -> data_provider::Result> { //! Ok(vec![10, 20, 30]) //! } //! fn next_election_prediction(now: BlockNumber) -> BlockNumber { @@ -138,7 +138,7 @@ //! type DataProvider = T::DataProvider; //! //! fn elect() -> Result, Self::Error> { -//! Self::DataProvider::targets(None) +//! Self::DataProvider::electable_targets(None) //! .map_err(|_| "failed to elect") //! .map(|t| vec![(t[0], Support::default())]) //! } @@ -265,16 +265,19 @@ pub trait ElectionDataProvider { /// Maximum number of votes per voter that this data provider is providing. type MaxVotesPerVoter: Get; - /// All possible targets for the election, i.e. the candidates. + /// All possible targets for the election, i.e. the targets that could become elected, thus + /// "electable". /// /// If `maybe_max_len` is `Some(v)` then the resulting vector MUST NOT be longer than `v` items /// long. /// /// This should be implemented as a self-weighing function. The implementor should register its /// appropriate weight at the end of execution with the system pallet directly. - fn targets(maybe_max_len: Option) -> data_provider::Result>; + fn electable_targets( + maybe_max_len: Option, + ) -> data_provider::Result>; - /// All possible voters for the election. + /// All the voters that participate in the election, thus "electing". /// /// Note that if a notion of self-vote exists, it should be represented here. /// @@ -283,7 +286,7 @@ pub trait ElectionDataProvider { /// /// This should be implemented as a self-weighing function. The implementor should register its /// appropriate weight at the end of execution with the system pallet directly. - fn voters(maybe_max_len: Option) -> data_provider::Result>>; + fn electing_voters(maybe_max_len: Option) -> data_provider::Result>>; /// The number of targets to elect. /// diff --git a/frame/election-provider-support/src/onchain.rs b/frame/election-provider-support/src/onchain.rs index 41245f67fb02c..7d845c2dc5ab3 100644 --- a/frame/election-provider-support/src/onchain.rs +++ b/frame/election-provider-support/src/onchain.rs @@ -79,10 +79,11 @@ impl OnChainSequentialPhragmen { maybe_max_voters: Option, maybe_max_targets: Option, ) -> Result, Error> { - let voters = ::DataProvider::voters(maybe_max_voters) - .map_err(Error::DataProvider)?; - let targets = ::DataProvider::targets(maybe_max_targets) + let voters = ::DataProvider::electing_voters(maybe_max_voters) .map_err(Error::DataProvider)?; + let targets = + ::DataProvider::electable_targets(maybe_max_targets) + .map_err(Error::DataProvider)?; let desired_targets = ::DataProvider::desired_targets() .map_err(Error::DataProvider)?; @@ -197,7 +198,7 @@ mod tests { type AccountId = AccountId; type BlockNumber = BlockNumber; type MaxVotesPerVoter = ConstU32<2>; - fn voters(_: Option) -> data_provider::Result>> { + fn electing_voters(_: Option) -> data_provider::Result>> { Ok(vec![ (1, 10, bounded_vec![10, 20]), (2, 20, bounded_vec![30, 20]), @@ -205,7 +206,7 @@ mod tests { ]) } - fn targets(_: Option) -> data_provider::Result> { + fn electable_targets(_: Option) -> data_provider::Result> { Ok(vec![10, 20, 30]) } diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index a4aadb16ab1b9..cb024ba2bc524 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -860,7 +860,7 @@ impl ElectionDataProvider for Pallet { Ok(Self::validator_count()) } - fn voters(maybe_max_len: Option) -> data_provider::Result>> { + fn electing_voters(maybe_max_len: Option) -> data_provider::Result>> { // This can never fail -- if `maybe_max_len` is `Some(_)` we handle it. let voters = Self::get_npos_voters(maybe_max_len); debug_assert!(maybe_max_len.map_or(true, |max| voters.len() <= max)); @@ -868,7 +868,7 @@ impl ElectionDataProvider for Pallet { Ok(voters) } - fn targets(maybe_max_len: Option) -> data_provider::Result> { + fn electable_targets(maybe_max_len: Option) -> data_provider::Result> { let target_count = Validators::::count(); // We can't handle this case yet -- return an error. diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 2b2a32e0edab7..2d4145242a45c 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4055,10 +4055,12 @@ mod election_data_provider { #[test] fn voters_include_self_vote() { ExtBuilder::default().nominate(false).build_and_execute(|| { - assert!(>::iter().map(|(x, _)| x).all(|v| Staking::voters(None) - .unwrap() - .into_iter() - .any(|(w, _, t)| { v == w && t[0] == w }))) + assert!(>::iter().map(|(x, _)| x).all(|v| Staking::electing_voters( + None + ) + .unwrap() + .into_iter() + .any(|(w, _, t)| { v == w && t[0] == w }))) }) } @@ -4067,7 +4069,7 @@ mod election_data_provider { ExtBuilder::default().build_and_execute(|| { assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); assert_eq!( - ::voters(None) + ::electing_voters(None) .unwrap() .iter() .find(|x| x.0 == 101) @@ -4082,7 +4084,7 @@ mod election_data_provider { // 11 is gone. start_active_era(2); assert_eq!( - ::voters(None) + ::electing_voters(None) .unwrap() .iter() .find(|x| x.0 == 101) @@ -4094,7 +4096,7 @@ mod election_data_provider { // resubmit and it is back assert_ok!(Staking::nominate(Origin::signed(100), vec![11, 21])); assert_eq!( - ::voters(None) + ::electing_voters(None) .unwrap() .iter() .find(|x| x.0 == 101) @@ -4118,20 +4120,23 @@ mod election_data_provider { ); // if limits is less.. - assert_eq!(Staking::voters(Some(1)).unwrap().len(), 1); + assert_eq!(Staking::electing_voters(Some(1)).unwrap().len(), 1); // if limit is equal.. - assert_eq!(Staking::voters(Some(5)).unwrap().len(), 5); + assert_eq!(Staking::electing_voters(Some(5)).unwrap().len(), 5); // if limit is more. - assert_eq!(Staking::voters(Some(55)).unwrap().len(), 5); + assert_eq!(Staking::electing_voters(Some(55)).unwrap().len(), 5); // if target limit is more.. - assert_eq!(Staking::targets(Some(6)).unwrap().len(), 4); - assert_eq!(Staking::targets(Some(4)).unwrap().len(), 4); + assert_eq!(Staking::electable_targets(Some(6)).unwrap().len(), 4); + assert_eq!(Staking::electable_targets(Some(4)).unwrap().len(), 4); // if target limit is less, then we return an error. - assert_eq!(Staking::targets(Some(1)).unwrap_err(), "Target snapshot too big"); + assert_eq!( + Staking::electable_targets(Some(1)).unwrap_err(), + "Target snapshot too big" + ); }); } @@ -4165,7 +4170,7 @@ mod election_data_provider { // we take 4 voters: 2 validators and 2 nominators (so nominators quota = 2) assert_eq!( - Staking::voters(Some(3)) + Staking::electing_voters(Some(3)) .unwrap() .iter() .map(|(stash, _, _)| stash) @@ -4204,7 +4209,7 @@ mod election_data_provider { // we take 5 voters assert_eq!( - Staking::voters(Some(5)) + Staking::electing_voters(Some(5)) .unwrap() .iter() .map(|(stash, _, _)| stash) @@ -4225,7 +4230,7 @@ mod election_data_provider { // we take 4 voters assert_eq!( - Staking::voters(Some(4)) + Staking::electing_voters(Some(4)) .unwrap() .iter() .map(|(stash, _, _)| stash) @@ -4666,7 +4671,7 @@ fn change_of_max_nominations() { vec![(70, 3), (101, 2), (60, 1)] ); // 3 validators and 3 nominators - assert_eq!(Staking::voters(None).unwrap().len(), 3 + 3); + assert_eq!(Staking::electing_voters(None).unwrap().len(), 3 + 3); // abrupt change from 16 to 4, everyone should be fine. MaxNominations::set(4); @@ -4677,7 +4682,7 @@ fn change_of_max_nominations() { .collect::>(), vec![(70, 3), (101, 2), (60, 1)] ); - assert_eq!(Staking::voters(None).unwrap().len(), 3 + 3); + assert_eq!(Staking::electing_voters(None).unwrap().len(), 3 + 3); // abrupt change from 4 to 3, everyone should be fine. MaxNominations::set(3); @@ -4688,7 +4693,7 @@ fn change_of_max_nominations() { .collect::>(), vec![(70, 3), (101, 2), (60, 1)] ); - assert_eq!(Staking::voters(None).unwrap().len(), 3 + 3); + assert_eq!(Staking::electing_voters(None).unwrap().len(), 3 + 3); // abrupt change from 3 to 2, this should cause some nominators to be non-decodable, and // thus non-existent unless if they update. @@ -4705,7 +4710,7 @@ fn change_of_max_nominations() { // but its value cannot be decoded and default is returned. assert!(Nominators::::get(70).is_none()); - assert_eq!(Staking::voters(None).unwrap().len(), 3 + 2); + assert_eq!(Staking::electing_voters(None).unwrap().len(), 3 + 2); assert!(Nominators::::contains_key(101)); // abrupt change from 2 to 1, this should cause some nominators to be non-decodable, and @@ -4722,7 +4727,7 @@ fn change_of_max_nominations() { assert!(Nominators::::contains_key(60)); assert!(Nominators::::get(70).is_none()); assert!(Nominators::::get(60).is_some()); - assert_eq!(Staking::voters(None).unwrap().len(), 3 + 1); + assert_eq!(Staking::electing_voters(None).unwrap().len(), 3 + 1); // now one of them can revive themselves by re-nominating to a proper value. assert_ok!(Staking::nominate(Origin::signed(71), vec![1])); From ade8ec329331414f8a223824ffd547bbae8dc85c Mon Sep 17 00:00:00 2001 From: Koute Date: Sun, 20 Mar 2022 04:11:02 +0900 Subject: [PATCH 036/484] Refactor WASM module instantiation (#10480) * Refactor WASM module instantiation; enable WASM instance pooling * Disable the `uffd` feature on `wasmtime` * Restore the original behavior regarding the initial WASM memory size * Adjust error message * Remove unnecessary import in the benchmarks * Preinstantiate the WASM runtime for a slight speedup * Delete the asserts in `convert_memory_import_into_export` * `return` -> `break` * Revert WASM instance pooling for now * Have `convert_memory_import_into_export` return an error instead of panic * Update the warning when an import is missing * Rustfmt and clippy fix * Fix executor benchmarks' compilation without `wasmtime` being enabled * rustfmt again * Align to review comments * Extend tests so that both imported and exported memories are tested * Increase the number of heap pages for exported memories too * Fix `decommit_works` test --- Cargo.lock | 2 + client/executor/Cargo.toml | 6 + client/executor/benches/bench.rs | 136 ++++++++++++ client/executor/benches/kusama_runtime.wasm | Bin 0 -> 5554150 bytes .../common/src/runtime_blob/runtime_blob.rs | 84 +++++++- client/executor/wasmtime/src/imports.rs | 162 +++----------- .../executor/wasmtime/src/instance_wrapper.rs | 91 ++++---- client/executor/wasmtime/src/runtime.rs | 106 ++++----- client/executor/wasmtime/src/tests.rs | 204 ++++++++++-------- 9 files changed, 468 insertions(+), 323 deletions(-) create mode 100644 client/executor/benches/bench.rs create mode 100755 client/executor/benches/kusama_runtime.wasm diff --git a/Cargo.lock b/Cargo.lock index 70a50a6f3e23b..2760ee0570d45 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8464,6 +8464,8 @@ dependencies = [ name = "sc-executor" version = "0.10.0-dev" dependencies = [ + "criterion", + "env_logger 0.9.0", "hex-literal", "lazy_static", "lru 0.6.6", diff --git a/client/executor/Cargo.toml b/client/executor/Cargo.toml index 96a9c0ba6e399..cba5892eace13 100644 --- a/client/executor/Cargo.toml +++ b/client/executor/Cargo.toml @@ -47,6 +47,12 @@ sc-tracing = { version = "4.0.0-dev", path = "../tracing" } tracing-subscriber = "0.2.19" paste = "1.0" regex = "1" +criterion = "0.3" +env_logger = "0.9" + +[[bench]] +name = "bench" +harness = false [features] default = ["std"] diff --git a/client/executor/benches/bench.rs b/client/executor/benches/bench.rs new file mode 100644 index 0000000000000..20632536571b2 --- /dev/null +++ b/client/executor/benches/bench.rs @@ -0,0 +1,136 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use criterion::{criterion_group, criterion_main, Criterion}; + +use sc_executor_common::{runtime_blob::RuntimeBlob, wasm_runtime::WasmModule}; +use sc_runtime_test::wasm_binary_unwrap as test_runtime; +use sp_wasm_interface::HostFunctions as _; +use std::sync::Arc; + +enum Method { + Interpreted, + #[cfg(feature = "wasmtime")] + Compiled { + fast_instance_reuse: bool, + }, +} + +// This is just a bog-standard Kusama runtime with the extra `test_empty_return` +// function copy-pasted from the test runtime. +fn kusama_runtime() -> &'static [u8] { + include_bytes!("kusama_runtime.wasm") +} + +fn initialize(runtime: &[u8], method: Method) -> Arc { + let blob = RuntimeBlob::uncompress_if_needed(runtime).unwrap(); + let host_functions = sp_io::SubstrateHostFunctions::host_functions(); + let heap_pages = 2048; + let allow_missing_func_imports = true; + + match method { + Method::Interpreted => sc_executor_wasmi::create_runtime( + blob, + heap_pages, + host_functions, + allow_missing_func_imports, + ) + .map(|runtime| -> Arc { Arc::new(runtime) }), + #[cfg(feature = "wasmtime")] + Method::Compiled { fast_instance_reuse } => + sc_executor_wasmtime::create_runtime::( + blob, + sc_executor_wasmtime::Config { + heap_pages, + max_memory_size: None, + allow_missing_func_imports, + cache_path: None, + semantics: sc_executor_wasmtime::Semantics { + fast_instance_reuse, + deterministic_stack_limit: None, + canonicalize_nans: false, + parallel_compilation: true, + }, + }, + ) + .map(|runtime| -> Arc { Arc::new(runtime) }), + } + .unwrap() +} + +fn bench_call_instance(c: &mut Criterion) { + let _ = env_logger::try_init(); + + #[cfg(feature = "wasmtime")] + { + let runtime = initialize(test_runtime(), Method::Compiled { fast_instance_reuse: true }); + c.bench_function("call_instance_test_runtime_with_fast_instance_reuse", |b| { + let mut instance = runtime.new_instance().unwrap(); + b.iter(|| instance.call_export("test_empty_return", &[0]).unwrap()) + }); + } + + #[cfg(feature = "wasmtime")] + { + let runtime = initialize(test_runtime(), Method::Compiled { fast_instance_reuse: false }); + c.bench_function("call_instance_test_runtime_without_fast_instance_reuse", |b| { + let mut instance = runtime.new_instance().unwrap(); + b.iter(|| instance.call_export("test_empty_return", &[0]).unwrap()); + }); + } + + #[cfg(feature = "wasmtime")] + { + let runtime = initialize(kusama_runtime(), Method::Compiled { fast_instance_reuse: true }); + c.bench_function("call_instance_kusama_runtime_with_fast_instance_reuse", |b| { + let mut instance = runtime.new_instance().unwrap(); + b.iter(|| instance.call_export("test_empty_return", &[0]).unwrap()) + }); + } + + #[cfg(feature = "wasmtime")] + { + let runtime = initialize(kusama_runtime(), Method::Compiled { fast_instance_reuse: false }); + c.bench_function("call_instance_kusama_runtime_without_fast_instance_reuse", |b| { + let mut instance = runtime.new_instance().unwrap(); + b.iter(|| instance.call_export("test_empty_return", &[0]).unwrap()); + }); + } + + { + let runtime = initialize(test_runtime(), Method::Interpreted); + c.bench_function("call_instance_test_runtime_interpreted", |b| { + let mut instance = runtime.new_instance().unwrap(); + b.iter(|| instance.call_export("test_empty_return", &[0]).unwrap()) + }); + } + + { + let runtime = initialize(kusama_runtime(), Method::Interpreted); + c.bench_function("call_instance_kusama_runtime_interpreted", |b| { + let mut instance = runtime.new_instance().unwrap(); + b.iter(|| instance.call_export("test_empty_return", &[0]).unwrap()) + }); + } +} + +criterion_group! { + name = benches; + config = Criterion::default(); + targets = bench_call_instance +} +criterion_main!(benches); diff --git a/client/executor/benches/kusama_runtime.wasm b/client/executor/benches/kusama_runtime.wasm new file mode 100755 index 0000000000000000000000000000000000000000..3470237fb5aeea2fc21c9efc5a214b0754f3afd6 GIT binary patch literal 5554150 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 R@EV##%=Od9YW3M5K^Vb=n#@W3T{1r zPRvX|14gk-zBdr~i0T8%LRFEF`eaIopSe&o?@2zYGTqR5h2^5@o|)YmvlYG%15uHx zIs_{et{3VSNL7JE+qWnPrQX23ig_0b*_3WBU=hg>o&1sj#ZjMlLM% zHd%>dGz>9}$Dw0PMXlj%=@?dF?o2v{6htjWM0osJ(lH$Aqji~djJi)N=op8SJYW?{ z9pi9|aNHOj183A&9iyob2g8Y4!jvdHYOg_QpK_m+bCA^bqtg;h)iz{LmM3_06^W&2 zg=YUfZY{y-VvzWkVWP!!bw`nk$l2^p#l7J^x~MBeNnuan%N(aGFhrR}_AQgnDkNzA z+b;`z1}N9eq?ib)ECUf$a~Bt;lkAF!WElv;hi?T5qt2*WXw{co70}08#r{f*+i0Yw z$B!m8AYjFi9x$nzT4sWb=%=2CR?$y~6VO70pv*ce{t-@6v&6L5QAw96%KKcVugjz>kTmtJb|B@_OlYi-jl7B_sWNt@ph0nNt zFh)19;Qz38U{&_bCq+7f`J~npm2eL7KoFH@;2P}WX!UhAtK5~lUxfV?y;GPEKcr*o z$0aGp_3U^$3{asg6xCcqLYs=>a9!KYQ{=!F((HXZAi7>K&gGPxs8#J1Iu7kpcl01j zH_r1RPWL*Cw`>aWmf^ux97bc}DTZ=3`Q9TozBRPrsY(oXcc|BgIVU2q0PNYo-5(at zt_=Y%LGnyMF{%r^(4=&9z(=i0jtfcJ2JXuZ6uBqU6DHdGG(G_<`)7j~5>Wb_wc9e6 zLMpWn3987drJk-LG0fPnPt4gW2Lr6BL)P^c(S6iUhSy&5wJv$_Y)y|?PitVvy5(m2 zqPzA6%yGF)%$2l9fb=Ymst*@ZNYWDkN_xtKc~GIFep8pLcA4q&VY?Is!5OjY4bJyN z)Bo@pB0V9LF>4MBE1Oh&Oc5Y&-<$36=a2Jh=9fw`;v{;lqRJ`RTKdlrg}Mjl@-oRK zD~EUB@liCJDV)^byF5%~dkS!QgKYT8RE`P(lD%Lsp4{>|c^w`%Y4#P+3Z@q*U` zY-(F-@$?}IzyZe!|LUZ?DfyTzaU|5cH9Q;UXHQf>Xk^aB9u$*c4CAZ~uw`yX_%9u#nsJkjrXuX--<2E~j&G?A zM}_47u%lx5$WI>nVN)RiKj`jAQXU(^LSQqMHMT|TiRV5%sZGmh zY*#Oo`cVi0>W#8d%=7R>%0L0|Aq032@0N*c@*x&StyqtG&-ta!$r*@=Yg#u_*D?!t z=<-;G@U!d?rLKhv$-ESp;SuYM!BkS$GC8J6T~mTuQ<`(T*%U74lRGHokfs&yJ(ZNVPFc<}cW;!0`2;b|O z``4SC7Gr-owL90J+P?bK3R}xN(AAc*ec5(0Qj1qfSC+erpw|oK#&7PD=fZC&fdYMl z8`sT+@CrD!xctSV|Ge;01OzGIlP}ut(X?Cn)a;?<#`4OcZ~b&70ex>Kl~VTft(kJm zU;phleZP5=a>hv`w;hatN<_;x#P0;%mGm6m(VoT~1i$-Rg~-Ldw!5*+E?^GFHwD4O zG`8ehe_aK%etnOI#jooby9#RQpBHc6uAJ5RtkpF9Wshqy(feoroNTYvjkE&_; zkrSVJ_oohh>AjBvBtb!wKGhs-Co!1+ucz^mrochVh3U42 zS@NN@v6v-^Fw*8cYtt&m+v71i&b50xUrFQtkxM{k1sDW?HGGCTugf*;itrm-Y=T%Y zPp;)NXjtz=BNu^Jb{9|dPU{2GKXm6fo2NyzBzs0hrB@quyZv1|hJRaU)x;c~D zYbh%mowFy4j>_?|I$F7)|0Wc7DX+b=q P9{jwFF+}f_6XOoFuw^xPAWY{(;`VMk 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 z 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�E@^ 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

nkyoJ7^W9c3~jN;vc3sOtk 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ψ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

7%jb0-+Ae% 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

V_ga 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

`Dr66(fQTy| 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

6{u>RgkeeoM-jy+Hb*aa*^iCl~@))V-T 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 Result<(), WasmError> { + let import_section = match self.raw_module.import_section_mut() { + Some(import_section) => import_section, + None => return Ok(()), + }; + + let import_entries = import_section.entries_mut(); + for index in 0..import_entries.len() { + let entry = &import_entries[index]; + let memory_ty = match entry.external() { + External::Memory(memory_ty) => *memory_ty, + _ => continue, + }; + + let memory_name = entry.field().to_owned(); + import_entries.remove(index); + + self.raw_module + .insert_section(Section::Memory(MemorySection::with_entries(vec![memory_ty]))) + .map_err(|error| { + WasmError::Other(format!( + "can't convert a memory import into an export: failed to insert a new memory section: {}", + error + )) + })?; + + if self.raw_module.export_section_mut().is_none() { + // A module without an export section is somewhat unrealistic, but let's do this + // just in case to cover all of our bases. + self.raw_module + .insert_section(Section::Export(Default::default())) + .expect("an export section can be always inserted if it doesn't exist; qed"); + } + self.raw_module + .export_section_mut() + .expect("export section already existed or we just added it above, so it always exists; qed") + .entries_mut() + .push(ExportEntry::new(memory_name, Internal::Memory(0))); + + break + } + + Ok(()) + } + + /// Increases the number of memory pages requested by the WASM blob by + /// the given amount of `extra_heap_pages`. + /// + /// Will return an error in case there is no memory section present, + /// or if the memory section is empty. + /// + /// Only modifies the initial size of the memory; the maximum is unmodified + /// unless it's smaller than the initial size, in which case it will be increased + /// so that it's at least as big as the initial size. + pub fn add_extra_heap_pages_to_memory_section( + &mut self, + extra_heap_pages: u32, + ) -> Result<(), WasmError> { + let memory_section = self + .raw_module + .memory_section_mut() + .ok_or_else(|| WasmError::Other("no memory section found".into()))?; + + if memory_section.entries().is_empty() { + return Err(WasmError::Other("memory section is empty".into())) + } + for memory_ty in memory_section.entries_mut() { + let min = memory_ty.limits().initial().saturating_add(extra_heap_pages); + let max = memory_ty.limits().maximum().map(|max| std::cmp::max(min, max)); + *memory_ty = MemoryType::new(min, max); + } + Ok(()) + } + /// Returns an iterator of all globals which were exported by [`expose_mutable_globals`]. pub(super) fn exported_internal_global_names<'module>( &'module self, diff --git a/client/executor/wasmtime/src/imports.rs b/client/executor/wasmtime/src/imports.rs index 636a5761c9475..4aad571029313 100644 --- a/client/executor/wasmtime/src/imports.rs +++ b/client/executor/wasmtime/src/imports.rs @@ -16,37 +16,24 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::{ - host::HostContext, - runtime::{Store, StoreData}, -}; +use crate::{host::HostContext, runtime::StoreData}; use sc_executor_common::error::WasmError; use sp_wasm_interface::{FunctionContext, HostFunctions}; -use std::{collections::HashMap, convert::TryInto}; -use wasmtime::{Extern, ExternType, Func, FuncType, ImportType, Memory, MemoryType, Module, Trap}; - -pub struct Imports { - /// Contains the index into `externs` where the memory import is stored if any. `None` if there - /// is none. - pub memory_import_index: Option, - pub externs: Vec, -} +use std::collections::HashMap; +use wasmtime::{ExternType, FuncType, ImportType, Linker, Module, Trap}; -/// Goes over all imports of a module and prepares a vector of `Extern`s that can be used for -/// instantiation of the module. Returns an error if there are imports that cannot be satisfied. -pub(crate) fn resolve_imports( - store: &mut Store, +/// Goes over all imports of a module and prepares the given linker for instantiation of the module. +/// Returns an error if there are imports that cannot be satisfied. +pub(crate) fn prepare_imports( + linker: &mut Linker, module: &Module, - heap_pages: u64, allow_missing_func_imports: bool, -) -> Result +) -> Result<(), WasmError> where H: HostFunctions, { - let mut externs = vec![]; - let mut memory_import_index = None; let mut pending_func_imports = HashMap::new(); - for (index, import_ty) in module.imports().enumerate() { + for import_ty in module.imports() { let name = import_name(&import_ty)?; if import_ty.module() != "env" { @@ -57,41 +44,36 @@ where ))) } - if name == "memory" { - memory_import_index = Some(index); - externs.push((index, resolve_memory_import(store, &import_ty, heap_pages)?)); - continue - } - match import_ty.ty() { ExternType::Func(func_ty) => { - pending_func_imports.insert(name.to_owned(), (index, import_ty, func_ty)); + pending_func_imports.insert(name.to_owned(), (import_ty, func_ty)); }, _ => return Err(WasmError::Other(format!( - "host doesn't provide any non function imports besides 'memory': {}:{}", + "host doesn't provide any non function imports: {}:{}", import_ty.module(), name, ))), }; } - let mut registry = Registry { store, externs, pending_func_imports }; - + let mut registry = Registry { linker, pending_func_imports }; H::register_static(&mut registry)?; - let mut externs = registry.externs; if !registry.pending_func_imports.is_empty() { if allow_missing_func_imports { - for (_, (index, import_ty, func_ty)) in registry.pending_func_imports { - externs.push(( - index, - MissingHostFuncHandler::new(&import_ty)?.into_extern(store, &func_ty), - )); + for (name, (import_ty, func_ty)) in registry.pending_func_imports { + let error = format!("call to a missing function {}:{}", import_ty.module(), name); + log::debug!("Missing import: '{}' {:?}", name, func_ty); + linker + .func_new("env", &name, func_ty.clone(), move |_, _, _| { + Err(Trap::new(error.clone())) + }) + .expect("adding a missing import stub can only fail when the item already exists, and it is missing here; qed"); } } else { let mut names = Vec::new(); - for (name, (_, import_ty, _)) in registry.pending_func_imports { + for (name, (import_ty, _)) in registry.pending_func_imports { names.push(format!("'{}:{}'", import_ty.module(), name)); } let names = names.join(", "); @@ -102,16 +84,12 @@ where } } - externs.sort_unstable_by_key(|&(index, _)| index); - let externs = externs.into_iter().map(|(_, ext)| ext).collect(); - - Ok(Imports { memory_import_index, externs }) + Ok(()) } struct Registry<'a, 'b> { - store: &'a mut Store, - externs: Vec<(usize, Extern)>, - pending_func_imports: HashMap, FuncType)>, + linker: &'a mut Linker, + pending_func_imports: HashMap, FuncType)>, } impl<'a, 'b> sp_wasm_interface::HostFunctionRegistry for Registry<'a, 'b> { @@ -131,9 +109,13 @@ impl<'a, 'b> sp_wasm_interface::HostFunctionRegistry for Registry<'a, 'b> { fn_name: &str, func: impl wasmtime::IntoFunc, ) -> Result<(), Self::Error> { - if let Some((index, _, _)) = self.pending_func_imports.remove(fn_name) { - let func = Func::wrap(&mut *self.store, func); - self.externs.push((index, Extern::Func(func))); + if self.pending_func_imports.remove(fn_name).is_some() { + self.linker.func_wrap("env", fn_name, func).map_err(|error| { + WasmError::Other(format!( + "failed to register host function '{}' with the WASM linker: {}", + fn_name, error + )) + })?; } Ok(()) @@ -149,85 +131,3 @@ fn import_name<'a, 'b: 'a>(import: &'a ImportType<'b>) -> Result<&'a str, WasmEr })?; Ok(name) } - -fn resolve_memory_import( - store: &mut Store, - import_ty: &ImportType, - heap_pages: u64, -) -> Result { - let requested_memory_ty = match import_ty.ty() { - ExternType::Memory(memory_ty) => memory_ty, - _ => - return Err(WasmError::Other(format!( - "this import must be of memory type: {}:{}", - import_ty.module(), - import_name(&import_ty)?, - ))), - }; - - // Increment the min (a.k.a initial) number of pages by `heap_pages` and check if it exceeds the - // maximum specified by the import. - let initial = requested_memory_ty.minimum().saturating_add(heap_pages); - if let Some(max) = requested_memory_ty.maximum() { - if initial > max { - return Err(WasmError::Other(format!( - "incremented number of pages by heap_pages (total={}) is more than maximum requested\ - by the runtime wasm module {}", - initial, - max, - ))) - } - } - - // Note that the return value of `maximum` and `minimum`, while a u64, - // will always fit into a u32 for 32-bit memories. - // 64-bit memories are part of the memory64 proposal for WebAssembly which is not standardized - // yet. - let minimum: u32 = initial.try_into().map_err(|_| { - WasmError::Other(format!( - "minimum number of memory pages ({}) doesn't fit into u32", - initial - )) - })?; - let maximum: Option = match requested_memory_ty.maximum() { - Some(max) => Some(max.try_into().map_err(|_| { - WasmError::Other(format!( - "maximum number of memory pages ({}) doesn't fit into u32", - max - )) - })?), - None => None, - }; - - let memory_ty = MemoryType::new(minimum, maximum); - let memory = Memory::new(store, memory_ty).map_err(|e| { - WasmError::Other(format!( - "failed to create a memory during resolving of memory import: {}", - e, - )) - })?; - Ok(Extern::Memory(memory)) -} - -/// A `Callable` handler for missing functions. -struct MissingHostFuncHandler { - module: String, - name: String, -} - -impl MissingHostFuncHandler { - fn new(import_ty: &ImportType) -> Result { - Ok(Self { - module: import_ty.module().to_string(), - name: import_name(import_ty)?.to_string(), - }) - } - - fn into_extern(self, store: &mut Store, func_ty: &FuncType) -> Extern { - let Self { module, name } = self; - let func = Func::new(store, func_ty.clone(), move |_, _, _| { - Err(Trap::new(format!("call to a missing function {}:{}", module, name))) - }); - Extern::Func(func) - } -} diff --git a/client/executor/wasmtime/src/instance_wrapper.rs b/client/executor/wasmtime/src/instance_wrapper.rs index 896b71cea21dd..6abcbca1bba6f 100644 --- a/client/executor/wasmtime/src/instance_wrapper.rs +++ b/client/executor/wasmtime/src/instance_wrapper.rs @@ -21,12 +21,13 @@ use crate::runtime::{Store, StoreData}; use sc_executor_common::{ - error::{Backtrace, Error, MessageWithBacktrace, Result}, + error::{Backtrace, Error, MessageWithBacktrace, Result, WasmError}, wasm_runtime::InvokeMethod, }; -use sp_wasm_interface::{HostFunctions, Pointer, Value, WordSize}; +use sp_wasm_interface::{Pointer, Value, WordSize}; use wasmtime::{ - AsContext, AsContextMut, Extern, Func, Global, Instance, Memory, Module, Table, Val, + AsContext, AsContextMut, Engine, Extern, Func, Global, Instance, InstancePre, Memory, Table, + Val, }; /// Invoked entrypoint format. @@ -162,62 +163,41 @@ fn extern_func(extern_: &Extern) -> Option<&Func> { } } +pub(crate) fn create_store(engine: &wasmtime::Engine, max_memory_size: Option) -> Store { + let limits = if let Some(max_memory_size) = max_memory_size { + wasmtime::StoreLimitsBuilder::new().memory_size(max_memory_size).build() + } else { + Default::default() + }; + + let mut store = + Store::new(engine, StoreData { limits, host_state: None, memory: None, table: None }); + if max_memory_size.is_some() { + store.limiter(|s| &mut s.limits); + } + store +} + impl InstanceWrapper { - /// Create a new instance wrapper from the given wasm module. - pub fn new( - module: &Module, - heap_pages: u64, - allow_missing_func_imports: bool, + pub(crate) fn new( + engine: &Engine, + instance_pre: &InstancePre, max_memory_size: Option, - ) -> Result - where - H: HostFunctions, - { - let limits = if let Some(max_memory_size) = max_memory_size { - wasmtime::StoreLimitsBuilder::new().memory_size(max_memory_size).build() - } else { - Default::default() - }; - - let mut store = Store::new( - module.engine(), - StoreData { limits, host_state: None, memory: None, table: None }, - ); - if max_memory_size.is_some() { - store.limiter(|s| &mut s.limits); - } - - // Scan all imports, find the matching host functions, and create stubs that adapt arguments - // and results. - let imports = crate::imports::resolve_imports::( - &mut store, - module, - heap_pages, - allow_missing_func_imports, - )?; - - let instance = Instance::new(&mut store, module, &imports.externs) - .map_err(|e| Error::from(format!("cannot instantiate: {}", e)))?; - - let memory = match imports.memory_import_index { - Some(memory_idx) => extern_memory(&imports.externs[memory_idx]) - .expect("only memory can be at the `memory_idx`; qed") - .clone(), - None => { - let memory = get_linear_memory(&instance, &mut store)?; - if !memory.grow(&mut store, heap_pages).is_ok() { - return Err("failed top increase the linear memory size".into()) - } - memory - }, - }; - + ) -> Result { + let mut store = create_store(engine, max_memory_size); + let instance = instance_pre.instantiate(&mut store).map_err(|error| { + WasmError::Other( + format!("failed to instantiate a new WASM module instance: {}", error,), + ) + })?; + + let memory = get_linear_memory(&instance, &mut store)?; let table = get_table(&instance, &mut store); store.data_mut().memory = Some(memory); store.data_mut().table = table; - Ok(Self { instance, memory, store }) + Ok(InstanceWrapper { instance, memory, store }) } /// Resolves a substrate entrypoint by the given name. @@ -435,8 +415,11 @@ impl InstanceWrapper { fn decommit_works() { let engine = wasmtime::Engine::default(); let code = wat::parse_str("(module (memory (export \"memory\") 1 4))").unwrap(); - let module = Module::new(&engine, code).unwrap(); - let mut wrapper = InstanceWrapper::new::<()>(&module, 2, true, None).unwrap(); + let module = wasmtime::Module::new(&engine, code).unwrap(); + let linker = wasmtime::Linker::new(&engine); + let mut store = create_store(&engine, None); + let instance_pre = linker.instantiate_pre(&mut store, &module).unwrap(); + let mut wrapper = InstanceWrapper::new(&engine, &instance_pre, None).unwrap(); unsafe { *wrapper.memory.data_ptr(&wrapper.store) = 42 }; assert_eq!(unsafe { *wrapper.memory.data_ptr(&wrapper.store) }, 42); wrapper.decommit(); diff --git a/client/executor/wasmtime/src/runtime.rs b/client/executor/wasmtime/src/runtime.rs index 6533aa194e4c4..acf54e04e07fd 100644 --- a/client/executor/wasmtime/src/runtime.rs +++ b/client/executor/wasmtime/src/runtime.rs @@ -23,7 +23,6 @@ use crate::{ instance_wrapper::{EntryPoint, InstanceWrapper}, util, }; -use core::marker::PhantomData; use sc_allocator::FreeingBumpHeapAllocator; use sc_executor_common::{ @@ -80,35 +79,25 @@ impl StoreData { pub(crate) type Store = wasmtime::Store; -enum Strategy { +enum Strategy { FastInstanceReuse { instance_wrapper: InstanceWrapper, globals_snapshot: GlobalsSnapshot, data_segments_snapshot: Arc, heap_base: u32, }, - RecreateInstance(InstanceCreator), + RecreateInstance(InstanceCreator), } -struct InstanceCreator { - module: Arc, - heap_pages: u64, - allow_missing_func_imports: bool, +struct InstanceCreator { + engine: wasmtime::Engine, + instance_pre: Arc>, max_memory_size: Option, - phantom: PhantomData, } -impl InstanceCreator -where - H: HostFunctions, -{ +impl InstanceCreator { fn instantiate(&mut self) -> Result { - InstanceWrapper::new::( - &*self.module, - self.heap_pages, - self.allow_missing_func_imports, - self.max_memory_size, - ) + InstanceWrapper::new(&self.engine, &self.instance_pre, self.max_memory_size) } } @@ -144,23 +133,19 @@ struct InstanceSnapshotData { /// A `WasmModule` implementation using wasmtime to compile the runtime module to machine code /// and execute the compiled code. -pub struct WasmtimeRuntime { - module: Arc, +pub struct WasmtimeRuntime { + engine: wasmtime::Engine, + instance_pre: Arc>, snapshot_data: Option, config: Config, - phantom: PhantomData, } -impl WasmModule for WasmtimeRuntime -where - H: HostFunctions, -{ +impl WasmModule for WasmtimeRuntime { fn new_instance(&self) -> Result> { let strategy = if let Some(ref snapshot_data) = self.snapshot_data { - let mut instance_wrapper = InstanceWrapper::new::( - &self.module, - self.config.heap_pages, - self.config.allow_missing_func_imports, + let mut instance_wrapper = InstanceWrapper::new( + &self.engine, + &self.instance_pre, self.config.max_memory_size, )?; let heap_base = instance_wrapper.extract_heap_base()?; @@ -174,19 +159,17 @@ where &mut InstanceGlobals { instance: &mut instance_wrapper }, ); - Strategy::::FastInstanceReuse { + Strategy::FastInstanceReuse { instance_wrapper, globals_snapshot, data_segments_snapshot: snapshot_data.data_segments_snapshot.clone(), heap_base, } } else { - Strategy::::RecreateInstance(InstanceCreator { - module: self.module.clone(), - heap_pages: self.config.heap_pages, - allow_missing_func_imports: self.config.allow_missing_func_imports, + Strategy::RecreateInstance(InstanceCreator { + engine: self.engine.clone(), + instance_pre: self.instance_pre.clone(), max_memory_size: self.config.max_memory_size, - phantom: PhantomData, }) }; @@ -196,14 +179,11 @@ where /// A `WasmInstance` implementation that reuses compiled module and spawns instances /// to execute the compiled code. -pub struct WasmtimeInstance { - strategy: Strategy, +pub struct WasmtimeInstance { + strategy: Strategy, } -impl WasmInstance for WasmtimeInstance -where - H: HostFunctions, -{ +impl WasmInstance for WasmtimeInstance { fn call(&mut self, method: InvokeMethod, data: &[u8]) -> Result> { match &mut self.strategy { Strategy::FastInstanceReuse { @@ -498,7 +478,7 @@ enum CodeSupplyMode<'a> { pub fn create_runtime( blob: RuntimeBlob, config: Config, -) -> std::result::Result, WasmError> +) -> std::result::Result where H: HostFunctions, { @@ -520,7 +500,7 @@ where pub unsafe fn create_runtime_from_artifact( compiled_artifact: &[u8], config: Config, -) -> std::result::Result, WasmError> +) -> std::result::Result where H: HostFunctions, { @@ -534,7 +514,7 @@ where unsafe fn do_create_runtime( code_supply_mode: CodeSupplyMode<'_>, config: Config, -) -> std::result::Result, WasmError> +) -> std::result::Result where H: HostFunctions, { @@ -550,27 +530,39 @@ where } let engine = Engine::new(&wasmtime_config) - .map_err(|e| WasmError::Other(format!("cannot create the engine for runtime: {}", e)))?; + .map_err(|e| WasmError::Other(format!("cannot create the wasmtime engine: {}", e)))?; let (module, snapshot_data) = match code_supply_mode { CodeSupplyMode::Verbatim { blob } => { - let blob = instrument(blob, &config.semantics)?; + let mut blob = instrument(blob, &config.semantics)?; + + // We don't actually need the memory to be imported so we can just convert any memory + // import into an export with impunity. This simplifies our code since `wasmtime` will + // now automatically take care of creating the memory for us, and it also allows us + // to potentially enable `wasmtime`'s instance pooling at a later date. (Imported + // memories are ineligible for pooling.) + blob.convert_memory_import_into_export()?; + blob.add_extra_heap_pages_to_memory_section( + config + .heap_pages + .try_into() + .map_err(|e| WasmError::Other(format!("invalid `heap_pages`: {}", e)))?, + )?; + + let serialized_blob = blob.clone().serialize(); + + let module = wasmtime::Module::new(&engine, &serialized_blob) + .map_err(|e| WasmError::Other(format!("cannot create module: {}", e)))?; if config.semantics.fast_instance_reuse { let data_segments_snapshot = DataSegmentsSnapshot::take(&blob).map_err(|e| { WasmError::Other(format!("cannot take data segments snapshot: {}", e)) })?; let data_segments_snapshot = Arc::new(data_segments_snapshot); - let mutable_globals = ExposedMutableGlobalsSet::collect(&blob); - let module = wasmtime::Module::new(&engine, &blob.serialize()) - .map_err(|e| WasmError::Other(format!("cannot create module: {}", e)))?; - (module, Some(InstanceSnapshotData { data_segments_snapshot, mutable_globals })) } else { - let module = wasmtime::Module::new(&engine, &blob.serialize()) - .map_err(|e| WasmError::Other(format!("cannot create module: {}", e)))?; (module, None) } }, @@ -584,7 +576,15 @@ where }, }; - Ok(WasmtimeRuntime { module: Arc::new(module), snapshot_data, config, phantom: PhantomData }) + let mut linker = wasmtime::Linker::new(&engine); + crate::imports::prepare_imports::(&mut linker, &module, config.allow_missing_func_imports)?; + + let mut store = crate::instance_wrapper::create_store(module.engine(), config.max_memory_size); + let instance_pre = linker + .instantiate_pre(&mut store, &module) + .map_err(|e| WasmError::Other(format!("cannot preinstantiate module: {}", e)))?; + + Ok(WasmtimeRuntime { engine, instance_pre: Arc::new(instance_pre), snapshot_data, config }) } fn instrument( diff --git a/client/executor/wasmtime/src/tests.rs b/client/executor/wasmtime/src/tests.rs index 664d05f5387fc..a4ca0959da869 100644 --- a/client/executor/wasmtime/src/tests.rs +++ b/client/executor/wasmtime/src/tests.rs @@ -24,7 +24,7 @@ use std::sync::Arc; type HostFunctions = sp_io::SubstrateHostFunctions; struct RuntimeBuilder { - code: Option<&'static str>, + code: Option, fast_instance_reuse: bool, canonicalize_nans: bool, deterministic_stack: bool, @@ -46,7 +46,7 @@ impl RuntimeBuilder { } } - fn use_wat(&mut self, code: &'static str) { + fn use_wat(&mut self, code: String) { self.code = Some(code); } @@ -152,7 +152,7 @@ fn test_stack_depth_reaching() { let runtime = { let mut builder = RuntimeBuilder::new_on_demand(); - builder.use_wat(TEST_GUARD_PAGE_SKIP); + builder.use_wat(TEST_GUARD_PAGE_SKIP.to_string()); builder.deterministic_stack(true); builder.build() }; @@ -168,10 +168,19 @@ fn test_stack_depth_reaching() { } #[test] -fn test_max_memory_pages() { +fn test_max_memory_pages_imported_memory() { + test_max_memory_pages(true); +} + +#[test] +fn test_max_memory_pages_exported_memory() { + test_max_memory_pages(false); +} + +fn test_max_memory_pages(import_memory: bool) { fn try_instantiate( max_memory_size: Option, - wat: &'static str, + wat: String, ) -> Result<(), Box> { let runtime = { let mut builder = RuntimeBuilder::new_on_demand(); @@ -184,31 +193,48 @@ fn test_max_memory_pages() { Ok(()) } + fn memory(initial: u32, maximum: Option, import: bool) -> String { + let memory = if let Some(maximum) = maximum { + format!("(memory $0 {} {})", initial, maximum) + } else { + format!("(memory $0 {})", initial) + }; + + if import { + format!("(import \"env\" \"memory\" {})", memory) + } else { + format!("{}\n(export \"memory\" (memory $0))", memory) + } + } + const WASM_PAGE_SIZE: usize = 65536; // check the old behavior if preserved. That is, if no limit is set we allow 4 GiB of memory. try_instantiate( None, - r#" - (module - ;; we want to allocate the maximum number of pages supported in wasm for this test. - ;; - ;; However, due to a bug in wasmtime (I think wasmi is also affected) it is only possible - ;; to allocate 65536 - 1 pages. - ;; - ;; Then, during creation of the Substrate Runtime instance, 1024 (heap_pages) pages are - ;; mounted. - ;; - ;; Thus 65535 = 64511 + 1024 - (import "env" "memory" (memory 64511)) - - (global (export "__heap_base") i32 (i32.const 0)) - (func (export "main") - (param i32 i32) (result i64) - (i64.const 0) + format!( + r#" + (module + {} + (global (export "__heap_base") i32 (i32.const 0)) + (func (export "main") + (param i32 i32) (result i64) + (i64.const 0) + ) ) - ) - "#, + "#, + /* + We want to allocate the maximum number of pages supported in wasm for this test. + However, due to a bug in wasmtime (I think wasmi is also affected) it is only possible + to allocate 65536 - 1 pages. + + Then, during creation of the Substrate Runtime instance, 1024 (heap_pages) pages are + mounted. + + Thus 65535 = 64511 + 1024 + */ + memory(64511, None, import_memory) + ), ) .unwrap(); @@ -217,94 +243,104 @@ fn test_max_memory_pages() { // max_memory_size = (1 (initial) + 1024 (heap_pages)) * WASM_PAGE_SIZE try_instantiate( Some((1 + 1024) * WASM_PAGE_SIZE), - r#" - (module - - (import "env" "memory" (memory 1)) ;; <- 1 initial, max is not specified - - (global (export "__heap_base") i32 (i32.const 0)) - (func (export "main") - (param i32 i32) (result i64) - (i64.const 0) + format!( + r#" + (module + {} + (global (export "__heap_base") i32 (i32.const 0)) + (func (export "main") + (param i32 i32) (result i64) + (i64.const 0) + ) ) - ) - "#, + "#, + // 1 initial, max is not specified. + memory(1, None, import_memory) + ), ) .unwrap(); // max is specified explicitly to 2048 pages. try_instantiate( Some((1 + 1024) * WASM_PAGE_SIZE), - r#" - (module - - (import "env" "memory" (memory 1 2048)) ;; <- max is 2048 - - (global (export "__heap_base") i32 (i32.const 0)) - (func (export "main") - (param i32 i32) (result i64) - (i64.const 0) + format!( + r#" + (module + {} + (global (export "__heap_base") i32 (i32.const 0)) + (func (export "main") + (param i32 i32) (result i64) + (i64.const 0) + ) ) - ) - "#, + "#, + // Max is 2048. + memory(1, Some(2048), import_memory) + ), ) .unwrap(); // memory grow should work as long as it doesn't exceed 1025 pages in total. try_instantiate( Some((0 + 1024 + 25) * WASM_PAGE_SIZE), - r#" - (module - (import "env" "memory" (memory 0)) ;; <- zero starting pages. - - (global (export "__heap_base") i32 (i32.const 0)) - (func (export "main") - (param i32 i32) (result i64) - - ;; assert(memory.grow returns != -1) - (if - (i32.eq - (memory.grow - (i32.const 25) + format!( + r#" + (module + {} + (global (export "__heap_base") i32 (i32.const 0)) + (func (export "main") + (param i32 i32) (result i64) + + ;; assert(memory.grow returns != -1) + (if + (i32.eq + (memory.grow + (i32.const 25) + ) + (i32.const -1) ) - (i32.const -1) + (unreachable) ) - (unreachable) - ) - (i64.const 0) + (i64.const 0) + ) ) - ) - "#, + "#, + // Zero starting pages. + memory(0, None, import_memory) + ), ) .unwrap(); // We start with 1025 pages and try to grow at least one. try_instantiate( Some((1 + 1024) * WASM_PAGE_SIZE), - r#" - (module - (import "env" "memory" (memory 1)) ;; <- initial=1, meaning after heap pages mount the - ;; total will be already 1025 - (global (export "__heap_base") i32 (i32.const 0)) - (func (export "main") - (param i32 i32) (result i64) - - ;; assert(memory.grow returns == -1) - (if - (i32.ne - (memory.grow - (i32.const 1) + format!( + r#" + (module + {} + (global (export "__heap_base") i32 (i32.const 0)) + (func (export "main") + (param i32 i32) (result i64) + + ;; assert(memory.grow returns == -1) + (if + (i32.ne + (memory.grow + (i32.const 1) + ) + (i32.const -1) ) - (i32.const -1) + (unreachable) ) - (unreachable) - ) - (i64.const 0) + (i64.const 0) + ) ) - ) - "#, + "#, + // Initial=1, meaning after heap pages mount the total will be already 1025. + memory(1, None, import_memory) + ), ) .unwrap(); } From b310d49a1081ee7901c7433043b3ff6cac5fc8ee Mon Sep 17 00:00:00 2001 From: Dan Shields <35669742+NukeManDan@users.noreply.github.com> Date: Sat, 19 Mar 2022 13:19:00 -0600 Subject: [PATCH 037/484] Add github issue form templates (#11061) * add issue form templates * Apply suggestions from code review Co-authored-by: Sacha Lansky Co-authored-by: Sacha Lansky --- .github/ISSUE_TEMPLATE/bug.yaml | 34 +++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 7 ++++ .github/ISSUE_TEMPLATE/feature.yaml | 52 +++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug.yaml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature.yaml diff --git a/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/bug.yaml new file mode 100644 index 0000000000000..ae40df08eca76 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yaml @@ -0,0 +1,34 @@ +name: Bug Report +description: Let us know about an issue you experienced with this software +# labels: ["some existing label","another one"] +body: + - type: checkboxes + attributes: + label: Is there an existing issue? + description: Please search to see if an issue already exists and leave a comment that you also experienced this issue or add your specifics that are related to an existing issue. + options: + - label: I have searched the existing issues + required: true + - type: checkboxes + attributes: + label: Experiencing problems? Have you tried our Stack Exchange first? + description: Please search to see if an post already exists, and ask if not. Please do not file support issues here. + options: + - label: This is not a support question. + required: true + - type: textarea + id: bug + attributes: + label: Description of bug + # description: What seems to be the problem? + # placeholder: Describe the problem. + validations: + required: true + - type: textarea + id: steps + attributes: + label: Steps to reproduce + description: Provide the steps that led to the discovery of the issue. + # placeholder: Describe what you were doing so we can reproduce the problem. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000000..e422e317411f3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,7 @@ +blank_issues_enabled: true +contact_links: + - name: Support & Troubleshooting with the Substrate Stack Exchange Community + url: https://substrate.stackexchange.com + about: | + For general problems with Substrate or related technologies, please search here first + for solutions, by keyword and tags. If you discover no solution, please then ask and questions in our community! We highly encourage everyone also share their understanding by answering questions for others. diff --git a/.github/ISSUE_TEMPLATE/feature.yaml b/.github/ISSUE_TEMPLATE/feature.yaml new file mode 100644 index 0000000000000..92b2fea3e88da --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.yaml @@ -0,0 +1,52 @@ +name: Feature Request +description: Submit your requests and suggestions to improve! +body: + - type: checkboxes + attributes: + label: Is there an existing issue? + description: Please search to see if an issue already exists and leave a comment that you also experienced this issue or add your specifics that are related to an existing issue. + options: + - label: I have searched the existing issues + required: true + - type: checkboxes + attributes: + label: Experiencing problems? Have you tried our Stack Exchange first? + description: Please search to see if an post already exists, and ask if not. Please do not file support issues here. + options: + - label: This is not a support question. + required: true + - type: textarea + id: content + attributes: + label: Motivation + description: Please give precedence as to what lead you to file this issue. + # placeholder: Describe ... + validations: + required: false + - type: textarea + id: content + attributes: + label: Request + description: Please describe what is needed. + # placeholder: Describe what you would like to see added or changed. + validations: + required: true + - type: textarea + id: content + attributes: + label: Solution + description: If possible, please describe what a solution could be. + # placeholder: Describe what you would like to see added or changed. + validations: + required: false + - type: dropdown + id: help + attributes: + label: Are you willing to help with this request? + multiple: true + options: + - Yes! + - No. + - Maybe (please elaborate above) + validations: + required: true From 651c6af25eb6c6b632de483a10a8c128daa620f2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 19 Mar 2022 21:07:18 +0000 Subject: [PATCH 038/484] Bump cargo_metadata from 0.14.1 to 0.14.2 (#11066) Bumps [cargo_metadata](https://github.com/oli-obk/cargo_metadata) from 0.14.1 to 0.14.2. - [Release notes](https://github.com/oli-obk/cargo_metadata/releases) - [Commits](https://github.com/oli-obk/cargo_metadata/compare/0.14.1...0.14.2) --- updated-dependencies: - dependency-name: cargo_metadata dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- utils/wasm-builder/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2760ee0570d45..e847dcd23d325 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -851,9 +851,9 @@ dependencies = [ [[package]] name = "cargo_metadata" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba2ae6de944143141f6155a473a6b02f66c7c3f9f47316f802f80204ebfe6e12" +checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" dependencies = [ "camino", "cargo-platform", diff --git a/utils/wasm-builder/Cargo.toml b/utils/wasm-builder/Cargo.toml index ce9687f9ae0ef..66c04432c5dec 100644 --- a/utils/wasm-builder/Cargo.toml +++ b/utils/wasm-builder/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] build-helper = "0.1.1" -cargo_metadata = "0.14.1" +cargo_metadata = "0.14.2" tempfile = "3.1.0" toml = "0.5.4" walkdir = "2.3.2" From 295eec02d0e634c5397f9c37b90bf854c309cfd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Sat, 19 Mar 2022 23:58:32 +0100 Subject: [PATCH 039/484] sc-consensus-aura: Remove obsolete dependency (#11074) --- Cargo.lock | 3 --- client/consensus/aura/Cargo.toml | 3 --- 2 files changed, 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e847dcd23d325..3de4fc3f7020a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2646,10 +2646,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" dependencies = [ "cfg-if 1.0.0", - "js-sys", "libc", "wasi 0.10.0+wasi-snapshot-preview1", - "wasm-bindgen", ] [[package]] @@ -8239,7 +8237,6 @@ version = "0.10.0-dev" dependencies = [ "async-trait", "futures 0.3.19", - "getrandom 0.2.3", "log 0.4.14", "parity-scale-codec", "parking_lot 0.12.0", diff --git a/client/consensus/aura/Cargo.toml b/client/consensus/aura/Cargo.toml index 6f5ff3c221fac..2ad61f2bc48d9 100644 --- a/client/consensus/aura/Cargo.toml +++ b/client/consensus/aura/Cargo.toml @@ -35,9 +35,6 @@ sp-keystore = { version = "0.12.0", path = "../../../primitives/keystore" } sc-telemetry = { version = "4.0.0-dev", path = "../../telemetry" } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus", version = "0.10.0-dev" } async-trait = "0.1.50" -# We enable it only for web-wasm check -# See https://docs.rs/getrandom/0.2.1/getrandom/#webassembly-support -getrandom = { version = "0.2", features = ["js"], optional = true } [dev-dependencies] sp-timestamp = { version = "4.0.0-dev", path = "../../../primitives/timestamp" } From c3654850515f6c6f40be6cbb4df69ff05b4d60cf Mon Sep 17 00:00:00 2001 From: Qinxuan Chen Date: Mon, 21 Mar 2022 16:37:59 +0800 Subject: [PATCH 040/484] pallet-whitelist: add sp-api/std to std feature to fix compile error (#11077) Signed-off-by: koushiro --- frame/whitelist/Cargo.toml | 11 ++++++----- frame/whitelist/src/benchmarking.rs | 7 ++++--- frame/whitelist/src/lib.rs | 17 +++++++---------- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/frame/whitelist/Cargo.toml b/frame/whitelist/Cargo.toml index 6bc43ff961781..5f414e5d32033 100644 --- a/frame/whitelist/Cargo.toml +++ b/frame/whitelist/Cargo.toml @@ -17,25 +17,26 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = scale-info = { version = "2.0", default-features = false, features = ["derive"] } sp-api = { version = "4.0.0-dev", default-features = false, path = "../../primitives/api" } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } + frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } [dev-dependencies] sp-core = { version = "6.0.0", path = "../../primitives/core" } -pallet-preimage = { version = "4.0.0-dev", path = "../preimage/" } -pallet-balances = { version = "4.0.0-dev", path = "../balances/" } +sp-io = { version = "6.0.0", path = "../../primitives/io" } +pallet-balances = { version = "4.0.0-dev", path = "../balances" } +pallet-preimage = { version = "4.0.0-dev", path = "../preimage" } [features] default = ["std"] std = [ "codec/std", "scale-info/std", - "sp-std/std", - "sp-io/std", + "sp-api/std", "sp-runtime/std", + "sp-std/std", "frame-support/std", "frame-system/std", ] diff --git a/frame/whitelist/src/benchmarking.rs b/frame/whitelist/src/benchmarking.rs index c51ea0a0a3246..50809ddef7ec1 100644 --- a/frame/whitelist/src/benchmarking.rs +++ b/frame/whitelist/src/benchmarking.rs @@ -20,10 +20,11 @@ #![cfg(feature = "runtime-benchmarks")] use super::*; -use core::convert::TryInto; use frame_benchmarking::benchmarks; -use frame_support::{ensure, traits::PreimageRecipient}; -use sp_runtime::traits::Hash; +use frame_support::{ + ensure, + traits::{EnsureOrigin, Get, PreimageRecipient}, +}; #[cfg(test)] use crate::Pallet as Whitelist; diff --git a/frame/whitelist/src/lib.rs b/frame/whitelist/src/lib.rs index 9f20386da85f5..c2de16964a494 100644 --- a/frame/whitelist/src/lib.rs +++ b/frame/whitelist/src/lib.rs @@ -39,21 +39,15 @@ mod mock; mod tests; pub mod weights; -use sp_runtime::traits::Dispatchable; -use sp_std::prelude::*; - use codec::{Decode, DecodeLimit, Encode, FullCodec, MaxEncodedLen}; use frame_support::{ ensure, traits::{PreimageProvider, PreimageRecipient}, - weights::{GetDispatchInfo, PostDispatchInfo}, + weights::{GetDispatchInfo, PostDispatchInfo, Weight}, }; use scale_info::TypeInfo; -use sp_api::HashT; -use weights::WeightInfo; - -use frame_support::pallet_prelude::*; -use frame_system::pallet_prelude::*; +use sp_runtime::traits::{Dispatchable, Hash}; +use sp_std::prelude::*; pub use pallet::*; @@ -66,6 +60,9 @@ pub struct Preimage { #[frame_support::pallet] pub mod pallet { use super::*; + use crate::weights::WeightInfo; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; #[pallet::config] pub trait Config: frame_system::Config { @@ -92,7 +89,7 @@ pub mod pallet { type PreimageProvider: PreimageProvider + PreimageRecipient; /// The weight information for this pallet. - type WeightInfo: weights::WeightInfo; + type WeightInfo: WeightInfo; } #[pallet::pallet] From 2469b067189ba1d3390e1436201c031880cb929a Mon Sep 17 00:00:00 2001 From: zqhxuyuan Date: Mon, 21 Mar 2022 16:41:37 +0800 Subject: [PATCH 041/484] add MaxEncodedLen trait to Time (#11078) --- frame/support/src/traits/misc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/support/src/traits/misc.rs b/frame/support/src/traits/misc.rs index 8c61874003bce..2a0d6f0523e71 100644 --- a/frame/support/src/traits/misc.rs +++ b/frame/support/src/traits/misc.rs @@ -510,7 +510,7 @@ pub trait HandleLifetime { impl HandleLifetime for () {} pub trait Time { - type Moment: sp_arithmetic::traits::AtLeast32Bit + Parameter + Default + Copy; + type Moment: sp_arithmetic::traits::AtLeast32Bit + Parameter + Default + Copy + MaxEncodedLen; fn now() -> Self::Moment; } From 77be9bb5804dac5cf09e6bb9c88ee1cea735448d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Mon, 21 Mar 2022 11:35:49 +0100 Subject: [PATCH 042/484] pallet-macro: Ensure that building with `missing_docs` works (#11075) * pallet-macro: Ensure that building with `missing_docs` works Before this pr it was failing when compiling with `missing_docs`, because the macro was generating functions etc without the appropriate docs. In the case of this pr it is mainly about hiding these functions in the docs as they are internal api anyway. * Fix UI test --- .../support/procedural/src/pallet/expand/call.rs | 5 ++++- .../procedural/src/pallet/expand/error.rs | 2 ++ .../src/pallet/expand/pallet_struct.rs | 2 ++ .../procedural/src/pallet/expand/storage.rs | 2 ++ frame/support/test/Cargo.toml | 2 +- frame/support/test/pallet/src/lib.rs | 16 ++++++++++++++++ .../call_argument_invalid_bound_2.stderr | 6 +++--- 7 files changed, 30 insertions(+), 5 deletions(-) diff --git a/frame/support/procedural/src/pallet/expand/call.rs b/frame/support/procedural/src/pallet/expand/call.rs index 355d4f87d3db9..2d7c550a44869 100644 --- a/frame/support/procedural/src/pallet/expand/call.rs +++ b/frame/support/procedural/src/pallet/expand/call.rs @@ -178,7 +178,10 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { #( #( #[doc = #fn_doc] )* #fn_name { - #( #args_compact_attr #args_name_stripped: #args_type ),* + #( + #[allow(missing_docs)] + #args_compact_attr #args_name_stripped: #args_type + ),* }, )* } diff --git a/frame/support/procedural/src/pallet/expand/error.rs b/frame/support/procedural/src/pallet/expand/error.rs index 9e2b801083e46..184fa86d3ae19 100644 --- a/frame/support/procedural/src/pallet/expand/error.rs +++ b/frame/support/procedural/src/pallet/expand/error.rs @@ -90,6 +90,7 @@ pub fn expand_error(def: &mut Def) -> proc_macro2::TokenStream { } impl<#type_impl_gen> #error_ident<#type_use_gen> #config_where_clause { + #[doc(hidden)] pub fn as_u8(&self) -> u8 { match &self { Self::__Ignore(_, _) => unreachable!("`__Ignore` can never be constructed"), @@ -97,6 +98,7 @@ pub fn expand_error(def: &mut Def) -> proc_macro2::TokenStream { } } + #[doc(hidden)] pub fn as_str(&self) -> &'static str { match &self { Self::__Ignore(_, _) => unreachable!("`__Ignore` can never be constructed"), diff --git a/frame/support/procedural/src/pallet/expand/pallet_struct.rs b/frame/support/procedural/src/pallet/expand/pallet_struct.rs index 61c6d001a6f94..52586a70a521a 100644 --- a/frame/support/procedural/src/pallet/expand/pallet_struct.rs +++ b/frame/support/procedural/src/pallet/expand/pallet_struct.rs @@ -81,6 +81,7 @@ pub fn expand_pallet_struct(def: &mut Def) -> proc_macro2::TokenStream { let error_ident = &error_def.error; quote::quote_spanned!(def.pallet_struct.attr_span => impl<#type_impl_gen> #pallet_ident<#type_use_gen> #config_where_clause { + #[doc(hidden)] pub fn error_metadata() -> Option<#frame_support::metadata::PalletErrorMetadata> { Some(#frame_support::metadata::PalletErrorMetadata { ty: #frame_support::scale_info::meta_type::<#error_ident<#type_use_gen>>() @@ -91,6 +92,7 @@ pub fn expand_pallet_struct(def: &mut Def) -> proc_macro2::TokenStream { } else { quote::quote_spanned!(def.pallet_struct.attr_span => impl<#type_impl_gen> #pallet_ident<#type_use_gen> #config_where_clause { + #[doc(hidden)] pub fn error_metadata() -> Option<#frame_support::metadata::PalletErrorMetadata> { None } diff --git a/frame/support/procedural/src/pallet/expand/storage.rs b/frame/support/procedural/src/pallet/expand/storage.rs index f45223c1cc842..d59dd46051392 100644 --- a/frame/support/procedural/src/pallet/expand/storage.rs +++ b/frame/support/procedural/src/pallet/expand/storage.rs @@ -406,6 +406,7 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream { quote::quote_spanned!(storage_def.attr_span => #(#cfg_attrs)* + #[doc(hidden)] #prefix_struct_vis struct #counter_prefix_struct_ident<#type_use_gen>( core::marker::PhantomData<(#type_use_gen,)> ); @@ -439,6 +440,7 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream { #maybe_counter #(#cfg_attrs)* + #[doc(hidden)] #prefix_struct_vis struct #prefix_struct_ident<#type_use_gen>( core::marker::PhantomData<(#type_use_gen,)> ); diff --git a/frame/support/test/Cargo.toml b/frame/support/test/Cargo.toml index d99889cf99bc4..383b7cf812b41 100644 --- a/frame/support/test/Cargo.toml +++ b/frame/support/test/Cargo.toml @@ -23,7 +23,7 @@ sp-runtime = { version = "6.0.0", default-features = false, path = "../../../pri sp-core = { version = "6.0.0", default-features = false, path = "../../../primitives/core" } sp-std = { version = "4.0.0", default-features = false, path = "../../../primitives/std" } sp-version = { version = "5.0.0", default-features = false, path = "../../../primitives/version" } -trybuild = "1.0.53" +trybuild = { version = "1.0.53", features = [ "diff" ] } pretty_assertions = "1.0.0" rustversion = "1.0.6" frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } diff --git a/frame/support/test/pallet/src/lib.rs b/frame/support/test/pallet/src/lib.rs index 25741313c2a03..37678e056f3e1 100644 --- a/frame/support/test/pallet/src/lib.rs +++ b/frame/support/test/pallet/src/lib.rs @@ -14,6 +14,12 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + +//! Testing pallet macro + +// Ensure docs are propagated properly by the macros. +#![warn(missing_docs)] + pub use pallet::*; #[frame_support::pallet] @@ -29,6 +35,10 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config {} + /// I'm the documentation + #[pallet::storage] + pub type Value = StorageValue; + #[pallet::genesis_config] #[cfg_attr(feature = "std", derive(Default))] pub struct GenesisConfig {} @@ -37,4 +47,10 @@ pub mod pallet { impl GenesisBuild for GenesisConfig { fn build(&self) {} } + + #[pallet::error] + pub enum Error { + /// Something failed + Test, + } } diff --git a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr index e9d267274a6ed..aff0661620874 100644 --- a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr +++ b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr @@ -56,10 +56,10 @@ note: required by a bound in `encode_to` = note: this error originates in the derive macro `frame_support::codec::Encode` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0277]: the trait bound `::Bar: WrapperTypeDecode` is not satisfied - --> tests/pallet_ui/call_argument_invalid_bound_2.rs:20:36 + --> tests/pallet_ui/call_argument_invalid_bound_2.rs:17:12 | -20 | pub fn foo(origin: OriginFor, bar: T::Bar) -> DispatchResultWithPostInfo { - | ^^^ the trait `WrapperTypeDecode` is not implemented for `::Bar` +17 | #[pallet::call] + | ^^^^ the trait `WrapperTypeDecode` is not implemented for `::Bar` | = note: required because of the requirements on the impl of `Decode` for `::Bar` note: required by a bound in `parity_scale_codec::Decode::decode` From 54024d3e59f0b2abc38d13de892b2dc3aaee327b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Mon, 21 Mar 2022 13:15:37 +0100 Subject: [PATCH 043/484] import-blocks: Do not read `stdin` to memory (#11072) * import-blocks: Do not read `stdin` to memory This fixes a bug with `import-blocks` reading the entire `stdin` before starting to import the blocks. However, for huge files that uses quite a lot of memory. We can just read from `stdin` step by step as we do it with a file. This ensures that we don't read the entire input at once into memory. * FMT * Fix warning --- client/cli/src/commands/import_blocks_cmd.rs | 8 ++------ client/service/src/chain_ops/import_blocks.rs | 15 ++++++++------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/client/cli/src/commands/import_blocks_cmd.rs b/client/cli/src/commands/import_blocks_cmd.rs index fece33ba23a2c..749824834bf7b 100644 --- a/client/cli/src/commands/import_blocks_cmd.rs +++ b/client/cli/src/commands/import_blocks_cmd.rs @@ -72,13 +72,9 @@ impl ImportBlocksCmd { B: BlockT + for<'de> serde::Deserialize<'de>, IQ: sc_service::ImportQueue + 'static, { - let file: Box = match &self.input { + let file: Box = match &self.input { Some(filename) => Box::new(fs::File::open(filename)?), - None => { - let mut buffer = Vec::new(); - io::stdin().read_to_end(&mut buffer)?; - Box::new(io::Cursor::new(buffer)) - }, + None => Box::new(io::stdin()), }; import_blocks(client, import_queue, file, false, self.binary) diff --git a/client/service/src/chain_ops/import_blocks.rs b/client/service/src/chain_ops/import_blocks.rs index 9d74fa1c276fa..dc03cdcedae06 100644 --- a/client/service/src/chain_ops/import_blocks.rs +++ b/client/service/src/chain_ops/import_blocks.rs @@ -36,7 +36,7 @@ use sp_runtime::{ }; use std::{ convert::{TryFrom, TryInto}, - io::{Read, Seek}, + io::Read, pin::Pin, task::Poll, time::{Duration, Instant}, @@ -63,7 +63,7 @@ pub fn build_spec(spec: &dyn ChainSpec, raw: bool) -> error::Result { /// SignedBlock and return it. enum BlockIter where - R: std::io::Read + std::io::Seek, + R: std::io::Read, { Binary { // Total number of blocks we are expecting to decode. @@ -83,7 +83,7 @@ where impl BlockIter where - R: Read + Seek + 'static, + R: Read + 'static, B: BlockT + MaybeSerializeDeserialize, { fn new(input: R, binary: bool) -> Result { @@ -119,7 +119,7 @@ where impl Iterator for BlockIter where - R: Read + Seek + 'static, + R: Read + 'static, B: BlockT + MaybeSerializeDeserialize, { type Item = Result, String>; @@ -267,10 +267,11 @@ impl Speedometer { /// Different State that the `import_blocks` future could be in. enum ImportState where - R: Read + Seek + 'static, + R: Read + 'static, B: BlockT + MaybeSerializeDeserialize, { - /// We are reading from the BlockIter structure, adding those blocks to the queue if possible. + /// We are reading from the [`BlockIter`] structure, adding those blocks to the queue if + /// possible. Reading { block_iter: BlockIter }, /// The queue is full (contains at least MAX_PENDING_BLOCKS blocks) and we are waiting for it /// to catch up. @@ -291,7 +292,7 @@ where pub fn import_blocks( client: Arc, mut import_queue: IQ, - input: impl Read + Seek + Send + 'static, + input: impl Read + Send + 'static, force: bool, binary: bool, ) -> Pin> + Send>> From fcbe6fd7facfcf49169765b71e8c9ce9f066ef65 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker <17734409+nathanwhit@users.noreply.github.com> Date: Mon, 21 Mar 2022 08:21:53 -0400 Subject: [PATCH 044/484] Don't return the same block twice in ancestor binary search (#11067) * Don't return the same block in ancestor search * Add regression test for ancestor search repeat --- client/network/src/protocol/sync.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/client/network/src/protocol/sync.rs b/client/network/src/protocol/sync.rs index f31afc828bd86..749366f6c1653 100644 --- a/client/network/src/protocol/sync.rs +++ b/client/network/src/protocol/sync.rs @@ -2320,7 +2320,11 @@ fn handle_ancestor_search_state( } assert!(right >= left); let middle = left + (right - left) / two; - Some((AncestorSearchState::BinarySearch(left, right), middle)) + if middle == curr_block_num { + None + } else { + Some((AncestorSearchState::BinarySearch(left, right), middle)) + } }, } } @@ -3238,4 +3242,9 @@ mod test { sync.on_block_data(&peer_id1, Some(request), response).unwrap(); assert_eq!(sync.best_queued_number, 4); } + #[test] + fn ancestor_search_repeat() { + let state = AncestorSearchState::::BinarySearch(1, 3); + assert!(handle_ancestor_search_state(&state, 2, true).is_none()); + } } From d2cc7f8ca71d118d2b7435194478aa422ac9d266 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker <17734409+nathanwhit@users.noreply.github.com> Date: Tue, 22 Mar 2022 03:47:05 -0400 Subject: [PATCH 045/484] Don't ban peers for small duplicate requests (#11084) * Don't ban peers for small duplicate requests * Address review comment --- client/network/src/block_request_handler.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/client/network/src/block_request_handler.rs b/client/network/src/block_request_handler.rs index 2f17cdac0744b..e1fe9ebf8d060 100644 --- a/client/network/src/block_request_handler.rs +++ b/client/network/src/block_request_handler.rs @@ -54,6 +54,10 @@ mod rep { /// Reputation change when a peer sent us the same request multiple times. pub const SAME_REQUEST: Rep = Rep::new_fatal("Same block request multiple times"); + + /// Reputation change when a peer sent us the same "small" request multiple times. + pub const SAME_SMALL_REQUEST: Rep = + Rep::new(-(1 << 10), "same small block request multiple times"); } /// Generates a [`ProtocolConfig`] for the block request protocol, refusing incoming requests. @@ -200,8 +204,16 @@ impl BlockRequestHandler { Some(SeenRequestsValue::Fulfilled(ref mut requests)) => { *requests = requests.saturating_add(1); + let small_request = attributes + .difference(BlockAttributes::HEADER | BlockAttributes::JUSTIFICATION) + .is_empty(); + if *requests > MAX_NUMBER_OF_SAME_REQUESTS_PER_PEER { - reputation_change = Some(rep::SAME_REQUEST); + reputation_change = Some(if small_request { + rep::SAME_SMALL_REQUEST + } else { + rep::SAME_REQUEST + }); } }, None => { From 0398397227408b452213725c9cc294e19f77cb15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Tue, 22 Mar 2022 09:23:47 +0100 Subject: [PATCH 046/484] Add `dev_getBlockStats` RPC (#10939) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add chain_getBlockStats rpc * Fix broken doc link * Apply suggestions from code review Co-authored-by: Niklas Adolfsson * Apply suggestions from code review Co-authored-by: Bastian Köcher * fmt * Fix compilation * Move Blockstats * Apply suggestions from code review Co-authored-by: David * fmt Co-authored-by: ascjones Co-authored-by: Niklas Adolfsson Co-authored-by: Bastian Köcher Co-authored-by: David --- Cargo.lock | 1 + bin/node/rpc/src/lib.rs | 7 +- client/rpc-api/Cargo.toml | 1 + client/rpc-api/src/dev/error.rs | 71 +++++++++++++++++++ client/rpc-api/src/dev/mod.rs | 64 +++++++++++++++++ client/rpc-api/src/lib.rs | 1 + client/rpc/src/dev/mod.rs | 118 ++++++++++++++++++++++++++++++++ client/rpc/src/dev/tests.rs | 58 ++++++++++++++++ client/rpc/src/lib.rs | 1 + 9 files changed, 319 insertions(+), 3 deletions(-) create mode 100644 client/rpc-api/src/dev/error.rs create mode 100644 client/rpc-api/src/dev/mod.rs create mode 100644 client/rpc/src/dev/mod.rs create mode 100644 client/rpc/src/dev/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 3de4fc3f7020a..3d9068f8ebd7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8864,6 +8864,7 @@ dependencies = [ "parking_lot 0.12.0", "sc-chain-spec", "sc-transaction-pool-api", + "scale-info", "serde", "serde_json", "sp-core", diff --git a/bin/node/rpc/src/lib.rs b/bin/node/rpc/src/lib.rs index 09f350ed3dcf1..b8349e26cd1da 100644 --- a/bin/node/rpc/src/lib.rs +++ b/bin/node/rpc/src/lib.rs @@ -103,6 +103,7 @@ pub fn create_full( ) -> Result, Box> where C: ProvideRuntimeApi + + sc_client_api::BlockBackend + HeaderBackend + AuxStore + HeaderMetadata @@ -123,6 +124,7 @@ where use pallet_contracts_rpc::{Contracts, ContractsApi}; use pallet_mmr_rpc::{Mmr, MmrApi}; use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApi}; + use sc_rpc::dev::{Dev, DevApi}; use substrate_frame_rpc_system::{FullSystem, SystemApi}; let mut io = jsonrpc_core::IoHandler::default(); @@ -159,19 +161,18 @@ where subscription_executor, finality_provider, ))); - io.extend_with(substrate_state_trie_migration_rpc::StateMigrationApi::to_delegate( substrate_state_trie_migration_rpc::MigrationRpc::new(client.clone(), backend, deny_unsafe), )); - io.extend_with(sc_sync_state_rpc::SyncStateRpcApi::to_delegate( sc_sync_state_rpc::SyncStateRpcHandler::new( chain_spec, - client, + client.clone(), shared_authority_set, shared_epoch_changes, )?, )); + io.extend_with(DevApi::to_delegate(Dev::new(client, deny_unsafe))); Ok(io) } diff --git a/client/rpc-api/Cargo.toml b/client/rpc-api/Cargo.toml index 38ac1fc443351..06deb0eef0ce3 100644 --- a/client/rpc-api/Cargo.toml +++ b/client/rpc-api/Cargo.toml @@ -23,6 +23,7 @@ log = "0.4.8" parking_lot = "0.12.0" thiserror = "1.0" +scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } sp-core = { version = "6.0.0", path = "../../primitives/core" } sp-version = { version = "5.0.0", path = "../../primitives/version" } sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } diff --git a/client/rpc-api/src/dev/error.rs b/client/rpc-api/src/dev/error.rs new file mode 100644 index 0000000000000..1a14b0d78994e --- /dev/null +++ b/client/rpc-api/src/dev/error.rs @@ -0,0 +1,71 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Error helpers for Dev RPC module. + +use crate::errors; +use jsonrpc_core as rpc; + +/// Dev RPC Result type. +pub type Result = std::result::Result; + +/// Dev RPC future Result type. +pub type FutureResult = jsonrpc_core::BoxFuture>; + +/// Dev RPC errors. +#[derive(Debug, thiserror::Error)] +pub enum Error { + /// Failed to query specified block or its parent: Probably an invalid hash. + #[error("Error while querying block: {0}")] + BlockQueryError(Box), + /// The re-execution of the specified block failed. + #[error("Failed to re-execute the specified block")] + BlockExecutionFailed, + /// The witness compaction failed. + #[error("Failed to create to compact the witness")] + WitnessCompactionFailed, + /// The method is marked as unsafe but unsafe flag wasn't supplied on the CLI. + #[error(transparent)] + UnsafeRpcCalled(#[from] crate::policy::UnsafeRpcError), +} + +/// Base error code for all dev errors. +const BASE_ERROR: i64 = 6000; + +impl From for rpc::Error { + fn from(e: Error) -> Self { + match e { + Error::BlockQueryError(_) => rpc::Error { + code: rpc::ErrorCode::ServerError(BASE_ERROR + 1), + message: e.to_string(), + data: None, + }, + Error::BlockExecutionFailed => rpc::Error { + code: rpc::ErrorCode::ServerError(BASE_ERROR + 3), + message: e.to_string(), + data: None, + }, + Error::WitnessCompactionFailed => rpc::Error { + code: rpc::ErrorCode::ServerError(BASE_ERROR + 4), + message: e.to_string(), + data: None, + }, + e => errors::internal(e), + } + } +} diff --git a/client/rpc-api/src/dev/mod.rs b/client/rpc-api/src/dev/mod.rs new file mode 100644 index 0000000000000..b1ae8934af8a1 --- /dev/null +++ b/client/rpc-api/src/dev/mod.rs @@ -0,0 +1,64 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Substrate dev API containing RPCs that are mainly meant for debugging and stats collection for +//! developers. The endpoints in this RPC module are not meant to be available to non-local users +//! and are all marked `unsafe`. + +pub mod error; + +use self::error::Result; +use codec::{Decode, Encode}; +use jsonrpc_derive::rpc; +use scale_info::TypeInfo; +use serde::{Deserialize, Serialize}; + +/// Statistics of a block returned by the `dev_getBlockStats` RPC. +#[derive(Eq, PartialEq, Clone, Copy, Encode, Decode, Debug, TypeInfo, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BlockStats { + /// The length in bytes of the storage proof produced by executing the block. + pub witness_len: u64, + /// The length in bytes of the storage proof after compaction. + pub witness_compact_len: u64, + /// Length of the block in bytes. + /// + /// This information can also be acquired by downloading the whole block. This merely + /// saves some complexity on the client side. + pub block_len: u64, + /// Number of extrinsics in the block. + /// + /// This information can also be acquired by downloading the whole block. This merely + /// saves some complexity on the client side. + pub num_extrinsics: u64, +} + +/// Substrate dev API. +/// +/// This API contains unstable and unsafe methods only meant for development nodes. They +/// are all flagged as unsafe for this reason. +#[rpc] +pub trait DevApi { + /// Reexecute the specified `block_hash` and gather statistics while doing so. + /// + /// This function requires the specified block and its parent to be available + /// at the queried node. If either the specified block or the parent is pruned, + /// this function will return `None`. + #[rpc(name = "dev_getBlockStats")] + fn block_stats(&self, block_hash: Hash) -> Result>; +} diff --git a/client/rpc-api/src/lib.rs b/client/rpc-api/src/lib.rs index 2b2e09e709aa9..e06f30bf9cd87 100644 --- a/client/rpc-api/src/lib.rs +++ b/client/rpc-api/src/lib.rs @@ -35,6 +35,7 @@ pub use policy::{DenyUnsafe, UnsafeRpcError}; pub mod author; pub mod chain; pub mod child_state; +pub mod dev; pub mod offchain; pub mod state; pub mod system; diff --git a/client/rpc/src/dev/mod.rs b/client/rpc/src/dev/mod.rs new file mode 100644 index 0000000000000..d782a03feae43 --- /dev/null +++ b/client/rpc/src/dev/mod.rs @@ -0,0 +1,118 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Implementation of the [`DevApi`] trait providing debug utilities for Substrate based +//! blockchains. + +#[cfg(test)] +mod tests; + +pub use sc_rpc_api::dev::{BlockStats, DevApi}; + +use sc_client_api::{BlockBackend, HeaderBackend}; +use sc_rpc_api::{ + dev::error::{Error, Result}, + DenyUnsafe, +}; +use sp_api::{ApiExt, Core, ProvideRuntimeApi}; +use sp_core::Encode; +use sp_runtime::{ + generic::{BlockId, DigestItem}, + traits::{Block as BlockT, Header}, +}; +use std::{ + marker::{PhantomData, Send, Sync}, + sync::Arc, +}; + +type HasherOf = <::Header as Header>::Hashing; + +/// The Dev API. All methods are unsafe. +pub struct Dev { + client: Arc, + deny_unsafe: DenyUnsafe, + _phantom: PhantomData, +} + +impl Dev { + /// Create a new Dev API. + pub fn new(client: Arc, deny_unsafe: DenyUnsafe) -> Self { + Self { client, deny_unsafe, _phantom: PhantomData::default() } + } +} + +impl DevApi for Dev +where + Block: BlockT + 'static, + Client: BlockBackend + + HeaderBackend + + ProvideRuntimeApi + + Send + + Sync + + 'static, + Client::Api: Core, +{ + fn block_stats(&self, hash: Block::Hash) -> Result> { + self.deny_unsafe.check_if_safe()?; + + let block = { + let block = self + .client + .block(&BlockId::Hash(hash)) + .map_err(|e| Error::BlockQueryError(Box::new(e)))?; + if let Some(block) = block { + let (mut header, body) = block.block.deconstruct(); + // Remove the `Seal` to ensure we have the number of digests as expected by the + // runtime. + header.digest_mut().logs.retain(|item| !matches!(item, DigestItem::Seal(_, _))); + Block::new(header, body) + } else { + return Ok(None) + } + }; + let parent_header = { + let parent_hash = *block.header().parent_hash(); + let parent_header = self + .client + .header(BlockId::Hash(parent_hash)) + .map_err(|e| Error::BlockQueryError(Box::new(e)))?; + if let Some(header) = parent_header { + header + } else { + return Ok(None) + } + }; + let block_len = block.encoded_size() as u64; + let num_extrinsics = block.extrinsics().len() as u64; + let pre_root = *parent_header.state_root(); + let mut runtime_api = self.client.runtime_api(); + runtime_api.record_proof(); + runtime_api + .execute_block(&BlockId::Hash(parent_header.hash()), block) + .map_err(|_| Error::BlockExecutionFailed)?; + let witness = runtime_api + .extract_proof() + .expect("We enabled proof recording. A proof must be available; qed"); + let witness_len = witness.encoded_size() as u64; + let witness_compact_len = witness + .into_compact_proof::>(pre_root) + .map_err(|_| Error::WitnessCompactionFailed)? + .encoded_size() as u64; + Ok(Some(BlockStats { witness_len, witness_compact_len, block_len, num_extrinsics })) + } +} diff --git a/client/rpc/src/dev/tests.rs b/client/rpc/src/dev/tests.rs new file mode 100644 index 0000000000000..1d31abe38b640 --- /dev/null +++ b/client/rpc/src/dev/tests.rs @@ -0,0 +1,58 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use super::*; +use assert_matches::assert_matches; +use futures::executor; +use sc_block_builder::BlockBuilderProvider; +use sp_blockchain::HeaderBackend; +use sp_consensus::BlockOrigin; +use substrate_test_runtime_client::{prelude::*, runtime::Block}; + +#[test] +fn block_stats_work() { + let mut client = Arc::new(substrate_test_runtime_client::new()); + let api = >::new(client.clone(), DenyUnsafe::No); + + let block = client.new_block(Default::default()).unwrap().build().unwrap().block; + executor::block_on(client.import(BlockOrigin::Own, block)).unwrap(); + + // Can't gather stats for a block without a parent. + assert_eq!(api.block_stats(client.genesis_hash()).unwrap(), None); + + assert_eq!( + api.block_stats(client.info().best_hash).unwrap(), + Some(BlockStats { + witness_len: 597, + witness_compact_len: 500, + block_len: 99, + num_extrinsics: 0, + }), + ); +} + +#[test] +fn deny_unsafe_works() { + let mut client = Arc::new(substrate_test_runtime_client::new()); + let api = >::new(client.clone(), DenyUnsafe::Yes); + + let block = client.new_block(Default::default()).unwrap().build().unwrap().block; + executor::block_on(client.import(BlockOrigin::Own, block)).unwrap(); + + assert_matches!(api.block_stats(client.info().best_hash), Err(Error::UnsafeRpcCalled(_))); +} diff --git a/client/rpc/src/lib.rs b/client/rpc/src/lib.rs index 3966baf13c756..59a1d542d365a 100644 --- a/client/rpc/src/lib.rs +++ b/client/rpc/src/lib.rs @@ -34,6 +34,7 @@ pub use sc_rpc_api::{DenyUnsafe, Metadata}; pub mod author; pub mod chain; +pub mod dev; pub mod offchain; pub mod state; pub mod system; From 3db771b4c216af0a6f0447801e51460e2dc8d0c0 Mon Sep 17 00:00:00 2001 From: Xiliang Chen Date: Tue, 22 Mar 2022 21:45:35 +1300 Subject: [PATCH 047/484] Add ProxyRemoved event (#11085) --- frame/proxy/src/lib.rs | 19 ++++++++++++++++++- frame/proxy/src/tests.rs | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/frame/proxy/src/lib.rs b/frame/proxy/src/lib.rs index 425af18367854..84f1a84badc90 100644 --- a/frame/proxy/src/lib.rs +++ b/frame/proxy/src/lib.rs @@ -570,6 +570,13 @@ pub mod pallet { proxy_type: T::ProxyType, delay: T::BlockNumber, }, + /// A proxy was removed. + ProxyRemoved { + delegator: T::AccountId, + delegatee: T::AccountId, + proxy_type: T::ProxyType, + delay: T::BlockNumber, + }, } /// Old name generated by `decl_event`. @@ -712,7 +719,11 @@ impl Pallet { ) -> DispatchResult { Proxies::::try_mutate_exists(delegator, |x| { let (mut proxies, old_deposit) = x.take().ok_or(Error::::NotFound)?; - let proxy_def = ProxyDefinition { delegate: delegatee, proxy_type, delay }; + let proxy_def = ProxyDefinition { + delegate: delegatee.clone(), + proxy_type: proxy_type.clone(), + delay, + }; let i = proxies.binary_search(&proxy_def).ok().ok_or(Error::::NotFound)?; proxies.remove(i); let new_deposit = Self::deposit(proxies.len() as u32); @@ -724,6 +735,12 @@ impl Pallet { if !proxies.is_empty() { *x = Some((proxies, new_deposit)) } + Self::deposit_event(Event::::ProxyRemoved { + delegator: delegator.clone(), + delegatee, + proxy_type, + delay, + }); Ok(()) }) } diff --git a/frame/proxy/src/tests.rs b/frame/proxy/src/tests.rs index b4f30ab4029ab..a0807f1d3d0b6 100644 --- a/frame/proxy/src/tests.rs +++ b/frame/proxy/src/tests.rs @@ -436,13 +436,49 @@ fn add_remove_proxies_works() { Error::::NotFound ); assert_ok!(Proxy::remove_proxy(Origin::signed(1), 4, ProxyType::JustUtility, 0)); + System::assert_last_event( + ProxyEvent::ProxyRemoved { + delegator: 1, + delegatee: 4, + proxy_type: ProxyType::JustUtility, + delay: 0, + } + .into(), + ); assert_eq!(Balances::reserved_balance(1), 4); assert_ok!(Proxy::remove_proxy(Origin::signed(1), 3, ProxyType::Any, 0)); assert_eq!(Balances::reserved_balance(1), 3); + System::assert_last_event( + ProxyEvent::ProxyRemoved { + delegator: 1, + delegatee: 3, + proxy_type: ProxyType::Any, + delay: 0, + } + .into(), + ); assert_ok!(Proxy::remove_proxy(Origin::signed(1), 2, ProxyType::Any, 0)); assert_eq!(Balances::reserved_balance(1), 2); + System::assert_last_event( + ProxyEvent::ProxyRemoved { + delegator: 1, + delegatee: 2, + proxy_type: ProxyType::Any, + delay: 0, + } + .into(), + ); assert_ok!(Proxy::remove_proxy(Origin::signed(1), 2, ProxyType::JustTransfer, 0)); assert_eq!(Balances::reserved_balance(1), 0); + System::assert_last_event( + ProxyEvent::ProxyRemoved { + delegator: 1, + delegatee: 2, + proxy_type: ProxyType::JustTransfer, + delay: 0, + } + .into(), + ); assert_noop!( Proxy::add_proxy(Origin::signed(1), 1, ProxyType::Any, 0), Error::::NoSelfProxy From dc32d918b79070af49500fca74fc5046d373526c Mon Sep 17 00:00:00 2001 From: cheme Date: Tue, 22 Mar 2022 10:53:49 +0100 Subject: [PATCH 048/484] Do not upgrade db_version on metadata reading failure. (#11081) --- Cargo.lock | 4 ++-- client/db/Cargo.toml | 2 +- client/db/src/parity_db.rs | 2 +- client/db/src/utils.rs | 1 + 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3d9068f8ebd7c..8e725af983a5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6697,9 +6697,9 @@ dependencies = [ [[package]] name = "parity-db" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "865edee5b792f537356d9e55cbc138e7f4718dc881a7ea45a18b37bf61c21e3d" +checksum = "3d121a9af17a43efd0a38c6afa508b927ba07785bd4709efb2ac03bf77efef8d" dependencies = [ "blake2-rfc", "crc32fast", diff --git a/client/db/Cargo.toml b/client/db/Cargo.toml index 7b1e4da20861e..105bc43c61bc9 100644 --- a/client/db/Cargo.toml +++ b/client/db/Cargo.toml @@ -33,7 +33,7 @@ sc-state-db = { version = "0.10.0-dev", path = "../state-db" } sp-trie = { version = "6.0.0", path = "../../primitives/trie" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } sp-database = { version = "4.0.0-dev", path = "../../primitives/database" } -parity-db = { version = "0.3.8", optional = true } +parity-db = { version = "0.3.9", optional = true } [dev-dependencies] sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } diff --git a/client/db/src/parity_db.rs b/client/db/src/parity_db.rs index c81a346bb023f..f88e6f2e91678 100644 --- a/client/db/src/parity_db.rs +++ b/client/db/src/parity_db.rs @@ -73,7 +73,7 @@ pub fn open>( if upgrade { log::info!("Upgrading database metadata."); if let Some(meta) = parity_db::Options::load_metadata(path)? { - config.write_metadata(path, &meta.salt)?; + config.write_metadata_with_version(path, &meta.salt, Some(meta.version))?; } } diff --git a/client/db/src/utils.rs b/client/db/src/utils.rs index 7dcb6676a1750..29aa8424221a6 100644 --- a/client/db/src/utils.rs +++ b/client/db/src/utils.rs @@ -275,6 +275,7 @@ fn open_parity_db(path: &Path, db_type: DatabaseType, create: boo match crate::parity_db::open(path, db_type, create, false) { Ok(db) => Ok(db), Err(parity_db::Error::InvalidConfiguration(_)) => { + log::warn!("Invalid parity db configuration, attempting database metadata update."); // Try to update the database with the new config Ok(crate::parity_db::open(path, db_type, create, true)?) }, From 6613b3207e071f941f1cd3221d3c53b7a7a18bad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Tue, 22 Mar 2022 13:25:34 +0100 Subject: [PATCH 049/484] warp-sync: Return an error when trying to enable it for archive nodes. (#11086) * warp-sync: Return an error when trying to enable it for archive nodes. * Fix checks * Ups * FMT --- client/network/src/config.rs | 14 +++++++++++++- client/service/src/builder.rs | 14 +++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/client/network/src/config.rs b/client/network/src/config.rs index a7e4e5cc87668..eedf3fc22b961 100644 --- a/client/network/src/config.rs +++ b/client/network/src/config.rs @@ -377,8 +377,8 @@ impl From for ParseErr { } } -#[derive(Clone, Debug, Eq, PartialEq)] /// Sync operation mode. +#[derive(Clone, Debug, Eq, PartialEq)] pub enum SyncMode { /// Full block download and verification. Full, @@ -393,6 +393,18 @@ pub enum SyncMode { Warp, } +impl SyncMode { + /// Returns if `self` is [`Self::Warp`]. + pub fn is_warp(&self) -> bool { + matches!(self, Self::Warp) + } + + /// Returns if `self` is [`Self::Fast`]. + pub fn is_fast(&self) -> bool { + matches!(self, Self::Fast { .. }) + } +} + impl Default for SyncMode { fn default() -> Self { Self::Full diff --git a/client/service/src/builder.rs b/client/service/src/builder.rs index e9c1691107c71..f4ff932435755 100644 --- a/client/service/src/builder.rs +++ b/client/service/src/builder.rs @@ -39,7 +39,7 @@ use sc_executor::RuntimeVersionOf; use sc_keystore::LocalKeystore; use sc_network::{ block_request_handler::{self, BlockRequestHandler}, - config::Role, + config::{Role, SyncMode}, light_client_requests::{self, handler::LightClientRequestHandler}, state_request_handler::{self, StateRequestHandler}, warp_request_handler::{self, RequestHandler as WarpSyncRequestHandler, WarpSyncProvider}, @@ -767,6 +767,18 @@ where warp_sync, } = params; + if warp_sync.is_none() && config.network.sync_mode.is_warp() { + return Err("Warp sync enabled, but no warp sync provider configured.".into()) + } + + if config.state_pruning.is_archive() { + match config.network.sync_mode { + SyncMode::Fast { .. } => return Err("Fast sync doesn't work for archive nodes".into()), + SyncMode::Warp => return Err("Warp sync doesn't work for archive nodes".into()), + SyncMode::Full => {}, + }; + } + let transaction_pool_adapter = Arc::new(TransactionPoolAdapter { imports_external_transactions: !matches!(config.role, Role::Light), pool: transaction_pool, From 3252c11a701392b120e0e5d68978d6fff354cf24 Mon Sep 17 00:00:00 2001 From: Georges Date: Wed, 23 Mar 2022 09:14:44 +0000 Subject: [PATCH 050/484] Implementing `MaxEncodedLen` for `generate_solution_type` (#11032) * Move `sp-npos-elections-solution-type` to `frame-election-provider-support` First stab at it, will need to amend some more stuff * Fixing tests * Fixing tests * Fixing cargo.toml for std configuration * Implementing `MaxEncodedLen` on `generate_solution_type` * Full implementation of `max_encoded_len` * Fixing implementation bug adding some comments and documentation * fmt * Committing suggested changes renaming, and re exporting macro. * Removing unneeded imports * Implementing `MaxEncodedLen` on `generate_solution_type` * Full implementation of `max_encoded_len` * Fixing implementation bug adding some comments and documentation * Move `NposSolution` to frame * Implementing `MaxEncodedLen` on `generate_solution_type` * Full implementation of `max_encoded_len` * Fixing implementation bug adding some comments and documentation * Fixing test * Removing unneeded dependencies * `VoterSnapshotPerBlock` -> `MaxElectingVoters` * rename `SizeBound` to `MaxVoters` * Removing TODO and change bound * renaming `size_bound` to `max_voters` * Enabling tests for `solution-type` These got dropped off after the crate was moved from `sp_npos_elections` * Adding tests for `MaxEncodedLen` of solution_type * Better rustdocs. Better indenting and comments. Removing unneeded imports in tests. --- Cargo.lock | 3 + bin/node/runtime/src/lib.rs | 4 +- .../election-provider-multi-phase/src/mock.rs | 7 +- frame/election-provider-support/Cargo.toml | 1 + .../solution-type/Cargo.toml | 1 + .../solution-type/fuzzer/Cargo.toml | 1 + .../solution-type/fuzzer/src/compact.rs | 1 + .../solution-type/src/lib.rs | 35 ++++++-- .../solution-type/src/single_page.rs | 22 +++++ .../tests/ui/fail/missing_accuracy.rs | 1 + .../tests/ui/fail/missing_size_bound.rs | 10 +++ .../tests/ui/fail/missing_size_bound.stderr | 5 ++ .../tests/ui/fail/missing_target.rs | 1 + .../tests/ui/fail/missing_voter.rs | 1 + .../tests/ui/fail/no_annotations.rs | 1 + .../tests/ui/fail/swap_voter_target.rs | 1 + .../tests/ui/fail/wrong_attribute.rs | 1 + frame/election-provider-support/src/lib.rs | 5 ++ .../{solution-type => }/src/mock.rs | 11 ++- .../{solution-type => }/src/tests.rs | 84 ++++++++++++++++++- 20 files changed, 184 insertions(+), 12 deletions(-) create mode 100644 frame/election-provider-support/solution-type/tests/ui/fail/missing_size_bound.rs create mode 100644 frame/election-provider-support/solution-type/tests/ui/fail/missing_size_bound.stderr rename frame/election-provider-support/{solution-type => }/src/mock.rs (96%) rename frame/election-provider-support/{solution-type => }/src/tests.rs (80%) diff --git a/Cargo.lock b/Cargo.lock index 8e725af983a5d..1b478fcc18a78 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2167,6 +2167,7 @@ name = "frame-election-provider-solution-type" version = "4.0.0-dev" dependencies = [ "frame-election-provider-support", + "frame-support", "parity-scale-codec", "proc-macro-crate 1.1.3", "proc-macro2", @@ -2185,6 +2186,7 @@ dependencies = [ "frame-support", "frame-system", "parity-scale-codec", + "rand 0.7.3", "scale-info", "sp-arithmetic", "sp-core", @@ -2201,6 +2203,7 @@ dependencies = [ "clap 3.1.6", "frame-election-provider-solution-type", "frame-election-provider-support", + "frame-support", "honggfuzz", "parity-scale-codec", "rand 0.8.4", diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 64bdcfc870205..b7137de48fb15 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -597,11 +597,13 @@ frame_election_provider_support::generate_solution_type!( VoterIndex = u32, TargetIndex = u16, Accuracy = sp_runtime::PerU16, + MaxVoters = MaxElectingVoters, >(16) ); parameter_types! { pub MaxNominations: u32 = ::LIMIT as u32; + pub MaxElectingVoters: u32 = 10_000; } /// The numbers configured here could always be more than the the maximum limits of staking pallet @@ -677,7 +679,7 @@ impl pallet_election_provider_multi_phase::Config for Runtime { >; type ForceOrigin = EnsureRootOrHalfCouncil; type MaxElectableTargets = ConstU16<{ u16::MAX }>; - type MaxElectingVoters = ConstU32<10_000>; + type MaxElectingVoters = MaxElectingVoters; type BenchmarkingConfig = ElectionProviderBenchmarkConfig; type WeightInfo = pallet_election_provider_multi_phase::weights::SubstrateWeight; } diff --git a/frame/election-provider-multi-phase/src/mock.rs b/frame/election-provider-multi-phase/src/mock.rs index 89b5b72565dcb..e2384d2f15761 100644 --- a/frame/election-provider-multi-phase/src/mock.rs +++ b/frame/election-provider-multi-phase/src/mock.rs @@ -70,7 +70,12 @@ pub(crate) type TargetIndex = u16; frame_election_provider_support::generate_solution_type!( #[compact] - pub struct TestNposSolution::(16) + pub struct TestNposSolution::< + VoterIndex = VoterIndex, + TargetIndex = TargetIndex, + Accuracy = PerU16, + MaxVoters = ConstU32::<20> + >(16) ); /// All events of this pallet. diff --git a/frame/election-provider-support/Cargo.toml b/frame/election-provider-support/Cargo.toml index 16b79dbb098d4..be0c05e46df32 100644 --- a/frame/election-provider-support/Cargo.toml +++ b/frame/election-provider-support/Cargo.toml @@ -24,6 +24,7 @@ frame-system = { version = "4.0.0-dev", default-features = false, path = "../sys frame-election-provider-solution-type = { version = "4.0.0-dev", path = "solution-type" } [dev-dependencies] +rand = "0.7.3" sp-npos-elections = { version = "4.0.0-dev", path = "../../primitives/npos-elections" } sp-core = { version = "6.0.0", path = "../../primitives/core" } sp-io = { version = "6.0.0", path = "../../primitives/io" } diff --git a/frame/election-provider-support/solution-type/Cargo.toml b/frame/election-provider-support/solution-type/Cargo.toml index e59bbcc8e7b38..ca3038c9145ce 100644 --- a/frame/election-provider-support/solution-type/Cargo.toml +++ b/frame/election-provider-support/solution-type/Cargo.toml @@ -26,4 +26,5 @@ scale-info = "2.0.1" sp-arithmetic = { version = "5.0.0", path = "../../../primitives/arithmetic" } # used by generate_solution_type: frame-election-provider-support = { version = "4.0.0-dev", path = ".." } +frame-support = { version = "4.0.0-dev", path = "../../support" } trybuild = "1.0.53" diff --git a/frame/election-provider-support/solution-type/fuzzer/Cargo.toml b/frame/election-provider-support/solution-type/fuzzer/Cargo.toml index f6c2f2fe491e8..e2bae3f72a4f7 100644 --- a/frame/election-provider-support/solution-type/fuzzer/Cargo.toml +++ b/frame/election-provider-support/solution-type/fuzzer/Cargo.toml @@ -25,6 +25,7 @@ sp-arithmetic = { version = "5.0.0", path = "../../../../primitives/arithmetic" sp-runtime = { version = "6.0.0", path = "../../../../primitives/runtime" } # used by generate_solution_type: sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../../../primitives/npos-elections" } +frame-support = { version = "4.0.0-dev", path = "../../../support" } [[bin]] name = "compact" diff --git a/frame/election-provider-support/solution-type/fuzzer/src/compact.rs b/frame/election-provider-support/solution-type/fuzzer/src/compact.rs index 501d241b2b80b..e7ef440ff2195 100644 --- a/frame/election-provider-support/solution-type/fuzzer/src/compact.rs +++ b/frame/election-provider-support/solution-type/fuzzer/src/compact.rs @@ -8,6 +8,7 @@ fn main() { VoterIndex = u32, TargetIndex = u32, Accuracy = Percent, + MaxVoters = frame_support::traits::ConstU32::<100_000>, >(16)); loop { fuzz!(|fuzzer_data: &[u8]| { diff --git a/frame/election-provider-support/solution-type/src/lib.rs b/frame/election-provider-support/solution-type/src/lib.rs index 6e2788f06007f..57d939377b62c 100644 --- a/frame/election-provider-support/solution-type/src/lib.rs +++ b/frame/election-provider-support/solution-type/src/lib.rs @@ -49,6 +49,12 @@ pub(crate) fn syn_err(message: &'static str) -> syn::Error { /// compact encoding. /// - The accuracy of the ratios. This must be one of the `PerThing` types defined in /// `sp-arithmetic`. +/// - The maximum number of voters. This must be of type `Get`. Check +/// for more details. This is used to bound the struct, by leveraging the fact that `votes1.len() +/// < votes2.len() < ... < votesn.len()` (the details of the struct is explained further below). +/// We know that `sum_i votes_i.len() <= MaxVoters`, and we know that the maximum size of the +/// struct would be achieved if all voters fall in the last bucket. One can also check the tests +/// and more specifically `max_encoded_len_exact` for a concrete example. /// /// Moreover, the maximum number of edges per voter (distribution per assignment) also need to be /// specified. Attempting to convert from/to an assignment with more distributions will fail. @@ -59,10 +65,12 @@ pub(crate) fn syn_err(message: &'static str) -> syn::Error { /// ``` /// # use frame_election_provider_solution_type::generate_solution_type; /// # use sp_arithmetic::per_things::Perbill; +/// # use frame_support::traits::ConstU32; /// generate_solution_type!(pub struct TestSolution::< /// VoterIndex = u16, /// TargetIndex = u8, /// Accuracy = Perbill, +/// MaxVoters = ConstU32::<10>, /// >(4)); /// ``` /// @@ -103,9 +111,15 @@ pub(crate) fn syn_err(message: &'static str) -> syn::Error { /// # use frame_election_provider_solution_type::generate_solution_type; /// # use frame_election_provider_support::NposSolution; /// # use sp_arithmetic::per_things::Perbill; +/// # use frame_support::traits::ConstU32; /// generate_solution_type!( /// #[compact] -/// pub struct TestSolutionCompact::(8) +/// pub struct TestSolutionCompact::< +/// VoterIndex = u16, +/// TargetIndex = u8, +/// Accuracy = Perbill, +/// MaxVoters = ConstU32::<10>, +/// >(8) /// ); /// ``` #[proc_macro] @@ -129,6 +143,7 @@ struct SolutionDef { voter_type: syn::Type, target_type: syn::Type, weight_type: syn::Type, + max_voters: syn::Type, count: usize, compact_encoding: bool, } @@ -167,11 +182,11 @@ impl Parse for SolutionDef { let _ = ::parse(input)?; let generics: syn::AngleBracketedGenericArguments = input.parse()?; - if generics.args.len() != 3 { - return Err(syn_err("Must provide 3 generic args.")) + if generics.args.len() != 4 { + return Err(syn_err("Must provide 4 generic args.")) } - let expected_types = ["VoterIndex", "TargetIndex", "Accuracy"]; + let expected_types = ["VoterIndex", "TargetIndex", "Accuracy", "MaxVoters"]; let mut types: Vec = generics .args @@ -197,6 +212,7 @@ impl Parse for SolutionDef { }) .collect::>()?; + let max_voters = types.pop().expect("Vector of length 4 can be popped; qed"); let weight_type = types.pop().expect("Vector of length 3 can be popped; qed"); let target_type = types.pop().expect("Vector of length 2 can be popped; qed"); let voter_type = types.pop().expect("Vector of length 1 can be popped; qed"); @@ -205,7 +221,16 @@ impl Parse for SolutionDef { let count_expr: syn::ExprParen = input.parse()?; let count = parse_parenthesized_number::(count_expr)?; - Ok(Self { vis, ident, voter_type, target_type, weight_type, count, compact_encoding }) + Ok(Self { + vis, + ident, + voter_type, + target_type, + weight_type, + max_voters, + count, + compact_encoding, + }) } } diff --git a/frame/election-provider-support/solution-type/src/single_page.rs b/frame/election-provider-support/solution-type/src/single_page.rs index c1d897444da31..5a3ddc22f61c8 100644 --- a/frame/election-provider-support/solution-type/src/single_page.rs +++ b/frame/election-provider-support/solution-type/src/single_page.rs @@ -28,6 +28,7 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result { voter_type, target_type, weight_type, + max_voters, compact_encoding, } = def; @@ -178,6 +179,27 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result { <#ident as _feps::NposSolution>::TargetIndex, <#ident as _feps::NposSolution>::Accuracy, >; + impl _feps::codec::MaxEncodedLen for #ident { + fn max_encoded_len() -> usize { + use frame_support::traits::Get; + use _feps::codec::Encode; + let s: u32 = #max_voters::get(); + let max_element_size = + // the first voter.. + #voter_type::max_encoded_len() + // #count - 1 tuples.. + .saturating_add( + (#count - 1).saturating_mul( + #target_type::max_encoded_len().saturating_add(#weight_type::max_encoded_len()))) + // and the last target. + .saturating_add(#target_type::max_encoded_len()); + // The assumption is that it contains #count-1 empty elements + // and then last element with full size + #count + .saturating_mul(_feps::codec::Compact(0u32).encoded_size()) + .saturating_add((s as usize).saturating_mul(max_element_size)) + } + } impl<'a> _feps::sp_std::convert::TryFrom<&'a [__IndexAssignment]> for #ident { type Error = _feps::Error; fn try_from(index_assignments: &'a [__IndexAssignment]) -> Result { diff --git a/frame/election-provider-support/solution-type/tests/ui/fail/missing_accuracy.rs b/frame/election-provider-support/solution-type/tests/ui/fail/missing_accuracy.rs index 22693cd875e17..52ae9623fd384 100644 --- a/frame/election-provider-support/solution-type/tests/ui/fail/missing_accuracy.rs +++ b/frame/election-provider-support/solution-type/tests/ui/fail/missing_accuracy.rs @@ -4,6 +4,7 @@ generate_solution_type!(pub struct TestSolution::< VoterIndex = u16, TargetIndex = u8, Perbill, + MaxVoters = ConstU32::<10>, >(8)); fn main() {} diff --git a/frame/election-provider-support/solution-type/tests/ui/fail/missing_size_bound.rs b/frame/election-provider-support/solution-type/tests/ui/fail/missing_size_bound.rs new file mode 100644 index 0000000000000..fe8ac04cc8d61 --- /dev/null +++ b/frame/election-provider-support/solution-type/tests/ui/fail/missing_size_bound.rs @@ -0,0 +1,10 @@ +use frame_election_provider_solution_type::generate_solution_type; + +generate_solution_type!(pub struct TestSolution::< + VoterIndex = u16, + TargetIndex = u8, + Accuracy = Perbill, + ConstU32::<10>, +>(8)); + +fn main() {} diff --git a/frame/election-provider-support/solution-type/tests/ui/fail/missing_size_bound.stderr b/frame/election-provider-support/solution-type/tests/ui/fail/missing_size_bound.stderr new file mode 100644 index 0000000000000..c685ab816d399 --- /dev/null +++ b/frame/election-provider-support/solution-type/tests/ui/fail/missing_size_bound.stderr @@ -0,0 +1,5 @@ +error: Expected binding: `MaxVoters = ...` + --> tests/ui/fail/missing_size_bound.rs:7:2 + | +7 | ConstU32::<10>, + | ^^^^^^^^^^^^^^ diff --git a/frame/election-provider-support/solution-type/tests/ui/fail/missing_target.rs b/frame/election-provider-support/solution-type/tests/ui/fail/missing_target.rs index 8d0ca927c5fb3..b457c4abada6c 100644 --- a/frame/election-provider-support/solution-type/tests/ui/fail/missing_target.rs +++ b/frame/election-provider-support/solution-type/tests/ui/fail/missing_target.rs @@ -4,6 +4,7 @@ generate_solution_type!(pub struct TestSolution::< VoterIndex = u16, u8, Accuracy = Perbill, + MaxVoters = ConstU32::<10>, >(8)); fn main() {} diff --git a/frame/election-provider-support/solution-type/tests/ui/fail/missing_voter.rs b/frame/election-provider-support/solution-type/tests/ui/fail/missing_voter.rs index ad4b7f5217794..3d12e3e6b5ec4 100644 --- a/frame/election-provider-support/solution-type/tests/ui/fail/missing_voter.rs +++ b/frame/election-provider-support/solution-type/tests/ui/fail/missing_voter.rs @@ -4,6 +4,7 @@ generate_solution_type!(pub struct TestSolution::< u16, TargetIndex = u8, Accuracy = Perbill, + MaxVoters = ConstU32::<10>, >(8)); fn main() {} diff --git a/frame/election-provider-support/solution-type/tests/ui/fail/no_annotations.rs b/frame/election-provider-support/solution-type/tests/ui/fail/no_annotations.rs index 87673a3823513..9aab15e7ec9a1 100644 --- a/frame/election-provider-support/solution-type/tests/ui/fail/no_annotations.rs +++ b/frame/election-provider-support/solution-type/tests/ui/fail/no_annotations.rs @@ -4,6 +4,7 @@ generate_solution_type!(pub struct TestSolution::< u16, u8, Perbill, + MaxVoters = ConstU32::<10>, >(8)); fn main() {} diff --git a/frame/election-provider-support/solution-type/tests/ui/fail/swap_voter_target.rs b/frame/election-provider-support/solution-type/tests/ui/fail/swap_voter_target.rs index f1d5d0e7bf99f..4275aae045a60 100644 --- a/frame/election-provider-support/solution-type/tests/ui/fail/swap_voter_target.rs +++ b/frame/election-provider-support/solution-type/tests/ui/fail/swap_voter_target.rs @@ -4,6 +4,7 @@ generate_solution_type!(pub struct TestSolution::< TargetIndex = u16, VoterIndex = u8, Accuracy = Perbill, + MaxVoters = ConstU32::<10>, >(8)); fn main() {} diff --git a/frame/election-provider-support/solution-type/tests/ui/fail/wrong_attribute.rs b/frame/election-provider-support/solution-type/tests/ui/fail/wrong_attribute.rs index d04cc4a7a966b..a51cc724ad158 100644 --- a/frame/election-provider-support/solution-type/tests/ui/fail/wrong_attribute.rs +++ b/frame/election-provider-support/solution-type/tests/ui/fail/wrong_attribute.rs @@ -5,6 +5,7 @@ generate_solution_type!( VoterIndex = u8, TargetIndex = u16, Accuracy = Perbill, + MaxVoters = ConstU32::<10>, >(8) ); diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index 81fd841a6dc47..2cc27472e8846 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -192,6 +192,11 @@ pub use scale_info; pub use sp_arithmetic; #[doc(hidden)] pub use sp_std; + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; // Simple Extension trait to easily convert `None` from index closures to `Err`. // // This is only generated and re-exported for the solution code to use. diff --git a/frame/election-provider-support/solution-type/src/mock.rs b/frame/election-provider-support/src/mock.rs similarity index 96% rename from frame/election-provider-support/solution-type/src/mock.rs rename to frame/election-provider-support/src/mock.rs index c3d032f2eb257..1ea8dddf7eb17 100644 --- a/frame/election-provider-support/solution-type/src/mock.rs +++ b/frame/election-provider-support/src/mock.rs @@ -19,10 +19,16 @@ #![cfg(test)] -use std::{collections::HashMap, convert::TryInto, hash::Hash, HashSet}; +use std::{ + collections::{HashMap, HashSet}, + convert::TryInto, + hash::Hash, +}; use rand::{seq::SliceRandom, Rng}; +pub type AccountId = u64; + /// The candidate mask allows easy disambiguation between voters and candidates: accounts /// for which this bit is set are candidates, and without it, are voters. pub const CANDIDATE_MASK: AccountId = 1 << ((std::mem::size_of::() * 8) - 1); @@ -34,13 +40,14 @@ pub fn p(p: u8) -> TestAccuracy { } pub type MockAssignment = crate::Assignment; -pub type Voter = (AccountId, VoteWeight, Vec); +pub type Voter = (AccountId, crate::VoteWeight, Vec); crate::generate_solution_type! { pub struct TestSolution::< VoterIndex = u32, TargetIndex = u16, Accuracy = TestAccuracy, + MaxVoters = frame_support::traits::ConstU32::<20>, >(16) } diff --git a/frame/election-provider-support/solution-type/src/tests.rs b/frame/election-provider-support/src/tests.rs similarity index 80% rename from frame/election-provider-support/solution-type/src/tests.rs rename to frame/election-provider-support/src/tests.rs index f173e425b5187..7b4e46d836176 100644 --- a/frame/election-provider-support/solution-type/src/tests.rs +++ b/frame/election-provider-support/src/tests.rs @@ -20,13 +20,15 @@ #![cfg(test)] use crate::{mock::*, IndexAssignment, NposSolution}; +use frame_support::traits::ConstU32; use rand::SeedableRng; use std::convert::TryInto; mod solution_type { use super::*; - use codec::{Decode, Encode}; - // these need to come from the same dev-dependency `sp-npos-elections`, not from the crate. + use codec::{Decode, Encode, MaxEncodedLen}; + // these need to come from the same dev-dependency `frame-election-provider-support`, not from + // the crate. use crate::{generate_solution_type, Assignment, Error as NposError, NposSolution}; use sp_std::{convert::TryInto, fmt::Debug}; @@ -37,7 +39,12 @@ mod solution_type { use crate::generate_solution_type; generate_solution_type!( #[compact] - struct InnerTestSolutionIsolated::(12) + struct InnerTestSolutionIsolated::< + VoterIndex = u32, + TargetIndex = u8, + Accuracy = sp_runtime::Percent, + MaxVoters = crate::tests::ConstU32::<20>, + >(12) ); } @@ -50,6 +57,7 @@ mod solution_type { VoterIndex = u32, TargetIndex = u32, Accuracy = TestAccuracy, + MaxVoters = ConstU32::<20>, >(16) ); let solution = InnerTestSolution { @@ -68,6 +76,7 @@ mod solution_type { VoterIndex = u32, TargetIndex = u32, Accuracy = TestAccuracy, + MaxVoters = ConstU32::<20>, >(16) ); let compact = InnerTestSolutionCompact { @@ -82,6 +91,75 @@ mod solution_type { assert!(with_compact < without_compact); } + #[test] + fn max_encoded_len_too_small() { + generate_solution_type!( + pub struct InnerTestSolution::< + VoterIndex = u32, + TargetIndex = u32, + Accuracy = TestAccuracy, + MaxVoters = ConstU32::<1>, + >(3) + ); + let solution = InnerTestSolution { + votes1: vec![(2, 20), (4, 40)], + votes2: vec![(1, [(10, p(80))], 11), (5, [(50, p(85))], 51)], + ..Default::default() + }; + + // We actually have 4 voters, but the bound is 1 voter, so the implemented bound is too + // small. + assert!(solution.encode().len() > InnerTestSolution::max_encoded_len()); + } + + #[test] + fn max_encoded_len_upper_bound() { + generate_solution_type!( + pub struct InnerTestSolution::< + VoterIndex = u32, + TargetIndex = u32, + Accuracy = TestAccuracy, + MaxVoters = ConstU32::<4>, + >(3) + ); + let solution = InnerTestSolution { + votes1: vec![(2, 20), (4, 40)], + votes2: vec![(1, [(10, p(80))], 11), (5, [(50, p(85))], 51)], + ..Default::default() + }; + + // We actually have 4 voters, and the bound is 4 voters, so the implemented bound should be + // larger than the encoded len. + assert!(solution.encode().len() < InnerTestSolution::max_encoded_len()); + } + + #[test] + fn max_encoded_len_exact() { + generate_solution_type!( + pub struct InnerTestSolution::< + VoterIndex = u32, + TargetIndex = u32, + Accuracy = TestAccuracy, + MaxVoters = ConstU32::<4>, + >(3) + ); + let solution = InnerTestSolution { + votes1: vec![], + votes2: vec![], + votes3: vec![ + (1, [(10, p(50)), (11, p(20))], 12), + (2, [(20, p(50)), (21, p(20))], 22), + (3, [(30, p(50)), (31, p(20))], 32), + (4, [(40, p(50)), (41, p(20))], 42), + ], + }; + + // We have 4 voters, the bound is 4 voters, and all the voters voted for 3 targets, which is + // the max number of targets. This should represent the upper bound that `max_encoded_len` + // represents. + assert_eq!(solution.encode().len(), InnerTestSolution::max_encoded_len()); + } + #[test] fn solution_struct_is_codec() { let solution = TestSolution { From 4bb0b52fcde12aa214ece7f3d63c9cc363dc97e1 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Wed, 23 Mar 2022 14:17:26 +0000 Subject: [PATCH 051/484] Store validator self-vote in bags-list, and allow them to be trimmed for election (#10821) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Implement the new validator-in-bags-list scenario + migration * Apply suggestions from code review Co-authored-by: Zeke Mostov * some review comments * guard the migration * some review comments * Fix tests 🤦‍♂️ * Fix build * fix weight_of_fn * reformat line width * make const * use weight of fn cached * SortedListProvider -> VoterList * Fix all build and docs * check post migration Co-authored-by: Zeke Mostov --- bin/node/runtime/src/lib.rs | 4 +- frame/babe/src/mock.rs | 2 +- frame/bags-list/remote-tests/src/lib.rs | 2 +- frame/bags-list/remote-tests/src/migration.rs | 11 +- frame/bags-list/remote-tests/src/snapshot.rs | 5 +- frame/grandpa/src/mock.rs | 2 +- frame/offences/benchmarking/src/mock.rs | 2 +- frame/session/benchmarking/src/mock.rs | 2 +- frame/session/src/migrations/v1.rs | 4 +- frame/staking/src/benchmarking.rs | 50 +++-- frame/staking/src/lib.rs | 3 +- frame/staking/src/migrations.rs | 83 ++++++++- frame/staking/src/mock.rs | 10 +- frame/staking/src/pallet/impls.rs | 176 ++++++++++-------- frame/staking/src/pallet/mod.rs | 35 ++-- frame/staking/src/testing_utils.rs | 4 +- frame/staking/src/tests.rs | 114 ++++++------ 17 files changed, 305 insertions(+), 204 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index b7137de48fb15..a6d97cb299b24 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -558,9 +558,7 @@ impl pallet_staking::Config for Runtime { type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type ElectionProvider = ElectionProviderMultiPhase; type GenesisElectionProvider = onchain::OnChainSequentialPhragmen; - // Alternatively, use pallet_staking::UseNominatorsMap to just use the nominators map. - // Note that the aforementioned does not scale to a very large number of nominators. - type SortedListProvider = BagsList; + type VoterList = BagsList; type MaxUnlockingChunks = ConstU32<32>; type WeightInfo = pallet_staking::weights::SubstrateWeight; type BenchmarkingConfig = StakingBenchmarkingConfig; diff --git a/frame/babe/src/mock.rs b/frame/babe/src/mock.rs index 152ec5ab206e7..e74288577c9ef 100644 --- a/frame/babe/src/mock.rs +++ b/frame/babe/src/mock.rs @@ -197,7 +197,7 @@ impl pallet_staking::Config for Test { type NextNewSession = Session; type ElectionProvider = onchain::OnChainSequentialPhragmen; type GenesisElectionProvider = Self::ElectionProvider; - type SortedListProvider = pallet_staking::UseNominatorsMap; + type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); diff --git a/frame/bags-list/remote-tests/src/lib.rs b/frame/bags-list/remote-tests/src/lib.rs index 83c322f93134e..caf7a2a547e09 100644 --- a/frame/bags-list/remote-tests/src/lib.rs +++ b/frame/bags-list/remote-tests/src/lib.rs @@ -48,7 +48,7 @@ pub fn display_and_check_bags(currency_unit: u64, currency_na let min_nominator_bond = >::get(); log::info!(target: LOG_TARGET, "min nominator bond is {:?}", min_nominator_bond); - let voter_list_count = ::SortedListProvider::count(); + let voter_list_count = ::VoterList::count(); // go through every bag to track the total number of voters within bags and log some info about // how voters are distributed within the bags. diff --git a/frame/bags-list/remote-tests/src/migration.rs b/frame/bags-list/remote-tests/src/migration.rs index 4d5169fcc6dfa..c4cd73c45d377 100644 --- a/frame/bags-list/remote-tests/src/migration.rs +++ b/frame/bags-list/remote-tests/src/migration.rs @@ -17,7 +17,6 @@ //! Test to check the migration of the voter bag. use crate::{RuntimeT, LOG_TARGET}; -use frame_election_provider_support::SortedListProvider; use frame_support::traits::PalletInfoAccess; use pallet_staking::Nominators; use remote_externalities::{Builder, Mode, OnlineConfig}; @@ -45,16 +44,16 @@ pub async fn execute( let pre_migrate_nominator_count = >::iter().count() as u32; log::info!(target: LOG_TARGET, "Nominator count: {}", pre_migrate_nominator_count); - // run the actual migration, - let moved = ::SortedListProvider::unsafe_regenerate( + use frame_election_provider_support::SortedListProvider; + // run the actual migration + let moved = ::VoterList::unsafe_regenerate( pallet_staking::Nominators::::iter().map(|(n, _)| n), pallet_staking::Pallet::::weight_of_fn(), ); log::info!(target: LOG_TARGET, "Moved {} nominators", moved); - let voter_list_len = - ::SortedListProvider::iter().count() as u32; - let voter_list_count = ::SortedListProvider::count(); + let voter_list_len = ::VoterList::iter().count() as u32; + let voter_list_count = ::VoterList::count(); // and confirm it is equal to the length of the `VoterList`. assert_eq!(pre_migrate_nominator_count, voter_list_len); assert_eq!(pre_migrate_nominator_count, voter_list_count); diff --git a/frame/bags-list/remote-tests/src/snapshot.rs b/frame/bags-list/remote-tests/src/snapshot.rs index 2d996746a29cc..408f5f2bd8aa2 100644 --- a/frame/bags-list/remote-tests/src/snapshot.rs +++ b/frame/bags-list/remote-tests/src/snapshot.rs @@ -16,6 +16,7 @@ //! Test to execute the snapshot using the voter bag. +use frame_election_provider_support::SortedListProvider; use frame_support::traits::PalletInfoAccess; use remote_externalities::{Builder, Mode, OnlineConfig}; use sp_runtime::{traits::Block as BlockT, DeserializeOwned}; @@ -48,11 +49,11 @@ pub async fn execute .unwrap(); ext.execute_with(|| { - use frame_election_provider_support::{ElectionDataProvider, SortedListProvider}; + use frame_election_provider_support::ElectionDataProvider; log::info!( target: crate::LOG_TARGET, "{} nodes in bags list.", - ::SortedListProvider::count(), + ::VoterList::count(), ); let voters = diff --git a/frame/grandpa/src/mock.rs b/frame/grandpa/src/mock.rs index 9dac33f979841..6490a2b6992bf 100644 --- a/frame/grandpa/src/mock.rs +++ b/frame/grandpa/src/mock.rs @@ -205,7 +205,7 @@ impl pallet_staking::Config for Test { type NextNewSession = Session; type ElectionProvider = onchain::OnChainSequentialPhragmen; type GenesisElectionProvider = Self::ElectionProvider; - type SortedListProvider = pallet_staking::UseNominatorsMap; + type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); diff --git a/frame/offences/benchmarking/src/mock.rs b/frame/offences/benchmarking/src/mock.rs index 22c9af0f4c3cd..4359b7745ddd6 100644 --- a/frame/offences/benchmarking/src/mock.rs +++ b/frame/offences/benchmarking/src/mock.rs @@ -175,7 +175,7 @@ impl pallet_staking::Config for Test { type OffendingValidatorsThreshold = (); type ElectionProvider = onchain::OnChainSequentialPhragmen; type GenesisElectionProvider = Self::ElectionProvider; - type SortedListProvider = pallet_staking::UseNominatorsMap; + type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); diff --git a/frame/session/benchmarking/src/mock.rs b/frame/session/benchmarking/src/mock.rs index a9328b6546c91..5ebc75245630c 100644 --- a/frame/session/benchmarking/src/mock.rs +++ b/frame/session/benchmarking/src/mock.rs @@ -182,7 +182,7 @@ impl pallet_staking::Config for Test { type ElectionProvider = onchain::OnChainSequentialPhragmen; type GenesisElectionProvider = Self::ElectionProvider; type MaxUnlockingChunks = ConstU32<32>; - type SortedListProvider = pallet_staking::UseNominatorsMap; + type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); } diff --git a/frame/session/src/migrations/v1.rs b/frame/session/src/migrations/v1.rs index 2a69cd6d6a550..3c687ea7d9d66 100644 --- a/frame/session/src/migrations/v1.rs +++ b/frame/session/src/migrations/v1.rs @@ -87,7 +87,7 @@ pub fn migrate ListScenario { /// - the destination bag has at least one node, which will need its next pointer updated. /// /// NOTE: while this scenario specifically targets a worst case for the bags-list, it should - /// also elicit a worst case for other known `SortedListProvider` implementations; although - /// this may not be true against unknown `SortedListProvider` implementations. + /// also elicit a worst case for other known `VoterList` implementations; although + /// this may not be true against unknown `VoterList` implementations. fn new(origin_weight: BalanceOf, is_increase: bool) -> Result { ensure!(!origin_weight.is_zero(), "origin weight must be greater than 0"); @@ -189,7 +189,7 @@ impl ListScenario { // find a destination weight that will trigger the worst case scenario let dest_weight_as_vote = - T::SortedListProvider::score_update_worst_case(&origin_stash1, is_increase); + T::VoterList::score_update_worst_case(&origin_stash1, is_increase); let total_issuance = T::Currency::total_issuance(); @@ -316,7 +316,7 @@ benchmarks! { let scenario = ListScenario::::new(origin_weight, true)?; let controller = scenario.origin_controller1.clone(); let stash = scenario.origin_stash1.clone(); - assert!(T::SortedListProvider::contains(&stash)); + assert!(T::VoterList::contains(&stash)); let ed = T::Currency::minimum_balance(); let mut ledger = Ledger::::get(&controller).unwrap(); @@ -328,28 +328,24 @@ benchmarks! { }: withdraw_unbonded(RawOrigin::Signed(controller.clone()), s) verify { assert!(!Ledger::::contains_key(controller)); - assert!(!T::SortedListProvider::contains(&stash)); + assert!(!T::VoterList::contains(&stash)); } validate { - // clean up any existing state. - clear_validators_and_nominators::(); - - let origin_weight = MinNominatorBond::::get().max(T::Currency::minimum_balance()); - - // setup a worst case scenario where the user calling validate was formerly a nominator so - // they must be removed from the list. - let scenario = ListScenario::::new(origin_weight, true)?; - let controller = scenario.origin_controller1.clone(); - let stash = scenario.origin_stash1.clone(); - assert!(T::SortedListProvider::contains(&stash)); + let (stash, controller) = create_stash_controller::( + T::MaxNominations::get() - 1, + 100, + Default::default(), + )?; + // because it is chilled. + assert!(!T::VoterList::contains(&stash)); let prefs = ValidatorPrefs::default(); whitelist_account!(controller); }: _(RawOrigin::Signed(controller), prefs) verify { assert!(Validators::::contains_key(&stash)); - assert!(!T::SortedListProvider::contains(&stash)); + assert!(T::VoterList::contains(&stash)); } kick { @@ -434,14 +430,14 @@ benchmarks! { ).unwrap(); assert!(!Nominators::::contains_key(&stash)); - assert!(!T::SortedListProvider::contains(&stash)); + assert!(!T::VoterList::contains(&stash)); let validators = create_validators::(n, 100).unwrap(); whitelist_account!(controller); }: _(RawOrigin::Signed(controller), validators) verify { assert!(Nominators::::contains_key(&stash)); - assert!(T::SortedListProvider::contains(&stash)) + assert!(T::VoterList::contains(&stash)) } chill { @@ -455,12 +451,12 @@ benchmarks! { let scenario = ListScenario::::new(origin_weight, true)?; let controller = scenario.origin_controller1.clone(); let stash = scenario.origin_stash1.clone(); - assert!(T::SortedListProvider::contains(&stash)); + assert!(T::VoterList::contains(&stash)); whitelist_account!(controller); }: _(RawOrigin::Signed(controller)) verify { - assert!(!T::SortedListProvider::contains(&stash)); + assert!(!T::VoterList::contains(&stash)); } set_payee { @@ -523,13 +519,13 @@ benchmarks! { let scenario = ListScenario::::new(origin_weight, true)?; let controller = scenario.origin_controller1.clone(); let stash = scenario.origin_stash1.clone(); - assert!(T::SortedListProvider::contains(&stash)); + assert!(T::VoterList::contains(&stash)); add_slashing_spans::(&stash, s); }: _(RawOrigin::Root, stash.clone(), s) verify { assert!(!Ledger::::contains_key(&controller)); - assert!(!T::SortedListProvider::contains(&stash)); + assert!(!T::VoterList::contains(&stash)); } cancel_deferred_slash { @@ -708,13 +704,13 @@ benchmarks! { Ledger::::insert(&controller, l); assert!(Bonded::::contains_key(&stash)); - assert!(T::SortedListProvider::contains(&stash)); + assert!(T::VoterList::contains(&stash)); whitelist_account!(controller); }: _(RawOrigin::Signed(controller), stash.clone(), s) verify { assert!(!Bonded::::contains_key(&stash)); - assert!(!T::SortedListProvider::contains(&stash)); + assert!(!T::VoterList::contains(&stash)); } new_era { @@ -899,7 +895,7 @@ benchmarks! { let scenario = ListScenario::::new(origin_weight, true)?; let controller = scenario.origin_controller1.clone(); let stash = scenario.origin_stash1.clone(); - assert!(T::SortedListProvider::contains(&stash)); + assert!(T::VoterList::contains(&stash)); Staking::::set_staking_configs( RawOrigin::Root.into(), @@ -914,7 +910,7 @@ benchmarks! { let caller = whitelisted_caller(); }: _(RawOrigin::Signed(caller), controller.clone()) verify { - assert!(!T::SortedListProvider::contains(&stash)); + assert!(!T::VoterList::contains(&stash)); } force_apply_min_commission { diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 4c1bb438457e5..2a0716721dd51 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -780,7 +780,8 @@ enum Releases { V5_0_0, // blockable validators. V6_0_0, // removal of all storage associated with offchain phragmen. V7_0_0, // keep track of number of nominators / validators in map - V8_0_0, // populate `SortedListProvider`. + V8_0_0, // populate `VoterList`. + V9_0_0, // inject validators into `VoterList` as well. } impl Default for Releases { diff --git a/frame/staking/src/migrations.rs b/frame/staking/src/migrations.rs index 3991c4a66076f..96c905f4e5942 100644 --- a/frame/staking/src/migrations.rs +++ b/frame/staking/src/migrations.rs @@ -17,13 +17,83 @@ //! Storage migrations for the Staking pallet. use super::*; +use frame_election_provider_support::SortedListProvider; +use frame_support::traits::OnRuntimeUpgrade; + +pub mod v9 { + use super::*; + + /// Migration implementation that injects all validators into sorted list. + /// + /// This is only useful for chains that started their `VoterList` just based on nominators. + pub struct InjectValidatorsIntoVoterList(sp_std::marker::PhantomData); + impl OnRuntimeUpgrade for InjectValidatorsIntoVoterList { + fn on_runtime_upgrade() -> Weight { + if StorageVersion::::get() == Releases::V8_0_0 { + let prev_count = T::VoterList::count(); + let weight_of_cached = Pallet::::weight_of_fn(); + for (v, _) in Validators::::iter() { + let weight = weight_of_cached(&v); + let _ = T::VoterList::on_insert(v.clone(), weight).map_err(|err| { + log!(warn, "failed to insert {:?} into VoterList: {:?}", v, err) + }); + } + + log!( + info, + "injected a total of {} new voters, prev count: {} next count: {}, updating to version 9", + Validators::::count(), + prev_count, + T::VoterList::count(), + ); + + StorageVersion::::put(crate::Releases::V9_0_0); + T::BlockWeights::get().max_block + } else { + log!( + warn, + "InjectValidatorsIntoVoterList being executed on the wrong storage \ + version, expected Releases::V8_0_0" + ); + T::DbWeight::get().reads(1) + } + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result<(), &'static str> { + use frame_support::traits::OnRuntimeUpgradeHelpersExt; + frame_support::ensure!( + StorageVersion::::get() == crate::Releases::V8_0_0, + "must upgrade linearly" + ); + + let prev_count = T::VoterList::count(); + Self::set_temp_storage(prev_count, "prev"); + Ok(()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade() -> Result<(), &'static str> { + use frame_support::traits::OnRuntimeUpgradeHelpersExt; + let post_count = T::VoterList::count(); + let prev_count = Self::get_temp_storage::("prev").unwrap(); + let validators = Validators::::count(); + assert!(post_count == prev_count + validators); + + frame_support::ensure!( + StorageVersion::::get() == crate::Releases::V9_0_0, + "must upgrade " + ); + Ok(()) + } + } +} pub mod v8 { + use crate::{Config, Nominators, Pallet, StorageVersion, Weight}; use frame_election_provider_support::SortedListProvider; use frame_support::traits::Get; - use crate::{Config, Nominators, Pallet, StorageVersion, Weight}; - #[cfg(feature = "try-runtime")] pub fn pre_migrate() -> Result<(), &'static str> { frame_support::ensure!( @@ -35,16 +105,16 @@ pub mod v8 { Ok(()) } - /// Migration to sorted [`SortedListProvider`]. + /// Migration to sorted `VoterList`. pub fn migrate() -> Weight { if StorageVersion::::get() == crate::Releases::V7_0_0 { crate::log!(info, "migrating staking to Releases::V8_0_0"); - let migrated = T::SortedListProvider::unsafe_regenerate( + let migrated = T::VoterList::unsafe_regenerate( Nominators::::iter().map(|(id, _)| id), Pallet::::weight_of_fn(), ); - debug_assert_eq!(T::SortedListProvider::sanity_check(), Ok(())); + debug_assert_eq!(T::VoterList::sanity_check(), Ok(())); StorageVersion::::put(crate::Releases::V8_0_0); crate::log!( @@ -61,8 +131,7 @@ pub mod v8 { #[cfg(feature = "try-runtime")] pub fn post_migrate() -> Result<(), &'static str> { - T::SortedListProvider::sanity_check() - .map_err(|_| "SortedListProvider is not in a sane state.")?; + T::VoterList::sanity_check().map_err(|_| "VoterList is not in a sane state.")?; crate::log!(info, "👜 staking bags-list migration passes POST migrate checks ✅",); Ok(()) } diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index 843791a46ade2..bb71232c34673 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -270,8 +270,8 @@ impl crate::pallet::pallet::Config for Test { type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type ElectionProvider = onchain::OnChainSequentialPhragmen; type GenesisElectionProvider = Self::ElectionProvider; - // NOTE: consider a macro and use `UseNominatorsMap` as well. - type SortedListProvider = BagsList; + // NOTE: consider a macro and use `UseNominatorsAndValidatorsMap` as well. + type VoterList = BagsList; type MaxUnlockingChunks = ConstU32<32>; type BenchmarkingConfig = TestBenchmarkingConfig; type WeightInfo = (); @@ -541,9 +541,9 @@ fn check_count() { assert_eq!(nominator_count, Nominators::::count()); assert_eq!(validator_count, Validators::::count()); - // the voters that the `SortedListProvider` list is storing for us. - let external_voters = ::SortedListProvider::count(); - assert_eq!(external_voters, nominator_count); + // the voters that the `VoterList` list is storing for us. + let external_voters = ::VoterList::count(); + assert_eq!(external_voters, nominator_count + validator_count); } fn check_ledgers() { diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index cb024ba2bc524..9d5a3ed484184 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -49,6 +49,14 @@ use crate::{ use super::{pallet::*, STAKING_ID}; +/// The maximum number of iterations that we do whilst iterating over `T::VoterList` in +/// `get_npos_voters`. +/// +/// In most cases, if we want n items, we iterate exactly n times. In rare cases, if a voter is +/// invalid (for any reason) the iteration continues. With this constant, we iterate at most 2 * n +/// times and then give up. +const NPOS_MAX_ITERATIONS_COEFFICIENT: u32 = 2; + impl Pallet { /// The total balance that can be slashed from a stash account as of right now. pub fn slashable_balance_of(stash: &T::AccountId) -> BalanceOf { @@ -649,90 +657,77 @@ impl Pallet { /// Get all of the voters that are eligible for the npos election. /// - /// `maybe_max_len` can imposes a cap on the number of voters returned; First all the validator - /// are included in no particular order, then remainder is taken from the nominators, as - /// returned by [`Config::SortedListProvider`]. - /// - /// This will use nominators, and all the validators will inject a self vote. + /// `maybe_max_len` can imposes a cap on the number of voters returned; /// /// This function is self-weighing as [`DispatchClass::Mandatory`]. /// /// ### Slashing /// - /// All nominations that have been submitted before the last non-zero slash of the validator are - /// auto-chilled, but still count towards the limit imposed by `maybe_max_len`. + /// All votes that have been submitted before the last non-zero slash of the corresponding + /// target are *auto-chilled*, but still count towards the limit imposed by `maybe_max_len`. pub fn get_npos_voters(maybe_max_len: Option) -> Vec> { let max_allowed_len = { - let nominator_count = Nominators::::count() as usize; - let validator_count = Validators::::count() as usize; - let all_voter_count = validator_count.saturating_add(nominator_count); + let all_voter_count = T::VoterList::count() as usize; maybe_max_len.unwrap_or(all_voter_count).min(all_voter_count) }; let mut all_voters = Vec::<_>::with_capacity(max_allowed_len); - // first, grab all validators in no particular order, capped by the maximum allowed length. - let mut validators_taken = 0u32; - for (validator, _) in >::iter().take(max_allowed_len) { - // Append self vote. - let self_vote = ( - validator.clone(), - Self::weight_of(&validator), - vec![validator.clone()] - .try_into() - .expect("`MaxVotesPerVoter` must be greater than or equal to 1"), - ); - all_voters.push(self_vote); - validators_taken.saturating_inc(); - } - - // .. and grab whatever we have left from nominators. - let nominators_quota = (max_allowed_len as u32).saturating_sub(validators_taken); + // cache a few things. + let weight_of = Self::weight_of_fn(); let slashing_spans = >::iter().collect::>(); - // track the count of nominators added to `all_voters + let mut voters_seen = 0u32; + let mut validators_taken = 0u32; let mut nominators_taken = 0u32; - // track every nominator iterated over, but not necessarily added to `all_voters` - let mut nominators_seen = 0u32; - // cache the total-issuance once in this function - let weight_of = Self::weight_of_fn(); - - let mut nominators_iter = T::SortedListProvider::iter(); - while nominators_taken < nominators_quota && nominators_seen < nominators_quota * 2 { - let nominator = match nominators_iter.next() { - Some(nominator) => { - nominators_seen.saturating_inc(); - nominator + let mut sorted_voters = T::VoterList::iter(); + while all_voters.len() < max_allowed_len && + voters_seen < (NPOS_MAX_ITERATIONS_COEFFICIENT * max_allowed_len as u32) + { + let voter = match sorted_voters.next() { + Some(voter) => { + voters_seen.saturating_inc(); + voter }, None => break, }; if let Some(Nominations { submitted_in, mut targets, suppressed: _ }) = - >::get(&nominator) + >::get(&voter) { - log!( - trace, - "fetched nominator {:?} with weight {:?}", - nominator, - weight_of(&nominator) - ); + // if this voter is a nominator: targets.retain(|stash| { slashing_spans .get(stash) .map_or(true, |spans| submitted_in >= spans.last_nonzero_slash()) }); if !targets.len().is_zero() { - all_voters.push((nominator.clone(), weight_of(&nominator), targets)); + all_voters.push((voter.clone(), weight_of(&voter), targets)); nominators_taken.saturating_inc(); } + } else if Validators::::contains_key(&voter) { + // if this voter is a validator: + let self_vote = ( + voter.clone(), + weight_of(&voter), + vec![voter.clone()] + .try_into() + .expect("`MaxVotesPerVoter` must be greater than or equal to 1"), + ); + all_voters.push(self_vote); + validators_taken.saturating_inc(); } else { - // this can only happen if: 1. there a pretty bad bug in the bags-list (or whatever - // is the sorted list) logic and the state of the two pallets is no longer - // compatible, or because the nominators is not decodable since they have more - // nomination than `T::MaxNominations`. This can rarely happen, and is not really an - // emergency or bug if it does. - log!(warn, "DEFENSIVE: invalid item in `SortedListProvider`: {:?}, this nominator probably has too many nominations now", nominator) + // this can only happen if: 1. there a bug in the bags-list (or whatever is the + // sorted list) logic and the state of the two pallets is no longer compatible, or + // because the nominators is not decodable since they have more nomination than + // `T::MaxNominations`. The latter can rarely happen, and is not really an emergency + // or bug if it does. + log!( + warn, + "DEFENSIVE: invalid item in `VoterList`: {:?}, this nominator probably has too many nominations now", + voter + ) } } @@ -752,6 +747,7 @@ impl Pallet { validators_taken, nominators_taken ); + all_voters } @@ -773,7 +769,7 @@ impl Pallet { } /// This function will add a nominator to the `Nominators` storage map, - /// and [`SortedListProvider`]. + /// and `VoterList`. /// /// If the nominator already exists, their nominations will be updated. /// @@ -782,18 +778,21 @@ impl Pallet { /// wrong. pub fn do_add_nominator(who: &T::AccountId, nominations: Nominations) { if !Nominators::::contains_key(who) { - // maybe update sorted list. Error checking is defensive-only - this should never fail. - let _ = T::SortedListProvider::on_insert(who.clone(), Self::weight_of(who)) + // maybe update sorted list. + let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who)) .defensive_unwrap_or_default(); - - debug_assert_eq!(T::SortedListProvider::sanity_check(), Ok(())); } - Nominators::::insert(who, nominations); + + debug_assert_eq!( + Nominators::::count() + Validators::::count(), + T::VoterList::count() + ); + debug_assert_eq!(T::VoterList::sanity_check(), Ok(())); } /// This function will remove a nominator from the `Nominators` storage map, - /// and [`SortedListProvider`]. + /// and `VoterList`. /// /// Returns true if `who` was removed from `Nominators`, otherwise false. /// @@ -801,15 +800,21 @@ impl Pallet { /// `Nominators` or `VoterList` outside of this function is almost certainly /// wrong. pub fn do_remove_nominator(who: &T::AccountId) -> bool { - if Nominators::::contains_key(who) { + let outcome = if Nominators::::contains_key(who) { Nominators::::remove(who); - T::SortedListProvider::on_remove(who); - debug_assert_eq!(T::SortedListProvider::sanity_check(), Ok(())); - debug_assert_eq!(Nominators::::count(), T::SortedListProvider::count()); + T::VoterList::on_remove(who); true } else { false - } + }; + + debug_assert_eq!(T::VoterList::sanity_check(), Ok(())); + debug_assert_eq!( + Nominators::::count() + Validators::::count(), + T::VoterList::count() + ); + + outcome } /// This function will add a validator to the `Validators` storage map. @@ -820,7 +825,18 @@ impl Pallet { /// `Validators` or `VoterList` outside of this function is almost certainly /// wrong. pub fn do_add_validator(who: &T::AccountId, prefs: ValidatorPrefs) { + if !Validators::::contains_key(who) { + // maybe update sorted list. + let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who)) + .defensive_unwrap_or_default(); + } Validators::::insert(who, prefs); + + debug_assert_eq!( + Nominators::::count() + Validators::::count(), + T::VoterList::count() + ); + debug_assert_eq!(T::VoterList::sanity_check(), Ok(())); } /// This function will remove a validator from the `Validators` storage map. @@ -831,12 +847,21 @@ impl Pallet { /// `Validators` or `VoterList` outside of this function is almost certainly /// wrong. pub fn do_remove_validator(who: &T::AccountId) -> bool { - if Validators::::contains_key(who) { + let outcome = if Validators::::contains_key(who) { Validators::::remove(who); + T::VoterList::on_remove(who); true } else { false - } + }; + + debug_assert_eq!(T::VoterList::sanity_check(), Ok(())); + debug_assert_eq!( + Nominators::::count() + Validators::::count(), + T::VoterList::count() + ); + + outcome } /// Register some amount of weight directly with the system pallet. @@ -963,7 +988,7 @@ impl ElectionDataProvider for Pallet { >::remove_all(); >::remove_all(); - T::SortedListProvider::unsafe_clear(); + T::VoterList::unsafe_clear(); } #[cfg(feature = "runtime-benchmarks")] @@ -1278,20 +1303,24 @@ impl ScoreProvider for Pallet { /// A simple voter list implementation that does not require any additional pallets. Note, this /// does not provided nominators in sorted ordered. If you desire nominators in a sorted order take /// a look at [`pallet-bags-list]. -pub struct UseNominatorsMap(sp_std::marker::PhantomData); -impl SortedListProvider for UseNominatorsMap { +pub struct UseNominatorsAndValidatorsMap(sp_std::marker::PhantomData); +impl SortedListProvider for UseNominatorsAndValidatorsMap { type Error = (); type Score = VoteWeight; /// Returns iterator over voter list, which can have `take` called on it. fn iter() -> Box> { - Box::new(Nominators::::iter().map(|(n, _)| n)) + Box::new( + Validators::::iter() + .map(|(v, _)| v) + .chain(Nominators::::iter().map(|(n, _)| n)), + ) } fn count() -> u32 { - Nominators::::count() + Nominators::::count().saturating_add(Validators::::count()) } fn contains(id: &T::AccountId) -> bool { - Nominators::::contains_key(id) + Nominators::::contains_key(id) || Validators::::contains_key(id) } fn on_insert(_: T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> { // nothing to do on insert. @@ -1318,5 +1347,6 @@ impl SortedListProvider for UseNominatorsMap { // NOTE: Caller must ensure this doesn't lead to too many storage accesses. This is a // condition of SortedListProvider::unsafe_clear. Nominators::::remove_all(); + Validators::::remove_all(); } } diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 39173bf61c833..306bd34390d82 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -17,7 +17,7 @@ //! Staking FRAME Pallet. -use frame_election_provider_support::SortedListProvider; +use frame_election_provider_support::{SortedListProvider, VoteWeight}; use frame_support::{ dispatch::Codec, pallet_prelude::*, @@ -163,13 +163,12 @@ pub mod pallet { /// After the threshold is reached a new era will be forced. type OffendingValidatorsThreshold: Get; - /// Something that can provide a sorted list of voters in a somewhat sorted way. The - /// original use case for this was designed with `pallet_bags_list::Pallet` in mind. If - /// the bags-list is not desired, [`impls::UseNominatorsMap`] is likely the desired option. - type SortedListProvider: SortedListProvider< - Self::AccountId, - Score = frame_election_provider_support::VoteWeight, - >; + /// Something that provides a best-effort sorted list of voters aka electing nominators, + /// used for NPoS election. + /// + /// The changes to nominators are reported to this. Moreover, each validator's self-vote is + /// also reported as one independent vote. + type VoterList: SortedListProvider; /// The maximum number of `unlocking` chunks a [`StakingLedger`] can have. Effectively /// determines how many unique eras a staker may be unbonding in. @@ -584,10 +583,10 @@ pub mod pallet { }); } - // all voters are reported to the `SortedListProvider`. + // all voters are reported to the `VoterList`. assert_eq!( - T::SortedListProvider::count(), - Nominators::::count(), + T::VoterList::count(), + Nominators::::count() + Validators::::count(), "not all genesis stakers were inserted into sorted list provider, something is wrong." ); } @@ -837,9 +836,9 @@ pub mod pallet { // NOTE: ledger must be updated prior to calling `Self::weight_of`. Self::update_ledger(&controller, &ledger); // update this staker in the sorted list, if they exist in it. - if T::SortedListProvider::contains(&stash) { - T::SortedListProvider::on_update(&stash, Self::weight_of(&ledger.stash)); - debug_assert_eq!(T::SortedListProvider::sanity_check(), Ok(())); + if T::VoterList::contains(&stash) { + T::VoterList::on_update(&stash, Self::weight_of(&ledger.stash)); + debug_assert_eq!(T::VoterList::sanity_check(), Ok(())); } Self::deposit_event(Event::::Bonded(stash.clone(), extra)); @@ -920,8 +919,8 @@ pub mod pallet { Self::update_ledger(&controller, &ledger); // update this staker in the sorted list, if they exist in it. - if T::SortedListProvider::contains(&ledger.stash) { - T::SortedListProvider::on_update(&ledger.stash, Self::weight_of(&ledger.stash)); + if T::VoterList::contains(&ledger.stash) { + T::VoterList::on_update(&ledger.stash, Self::weight_of(&ledger.stash)); } Self::deposit_event(Event::::Unbonded(ledger.stash, value)); @@ -1403,8 +1402,8 @@ pub mod pallet { // NOTE: ledger must be updated prior to calling `Self::weight_of`. Self::update_ledger(&controller, &ledger); - if T::SortedListProvider::contains(&ledger.stash) { - T::SortedListProvider::on_update(&ledger.stash, Self::weight_of(&ledger.stash)); + if T::VoterList::contains(&ledger.stash) { + T::VoterList::on_update(&ledger.stash, Self::weight_of(&ledger.stash)); } let removed_chunks = 1u32 // for the case where the last iterated chunk is not removed diff --git a/frame/staking/src/testing_utils.rs b/frame/staking/src/testing_utils.rs index 8e6bd88468930..5f9f378b10619 100644 --- a/frame/staking/src/testing_utils.rs +++ b/frame/staking/src/testing_utils.rs @@ -38,11 +38,11 @@ const SEED: u32 = 0; pub fn clear_validators_and_nominators() { Validators::::remove_all(); - // whenever we touch nominators counter we should update `T::SortedListProvider` as well. + // whenever we touch nominators counter we should update `T::VoterList` as well. Nominators::::remove_all(); // NOTE: safe to call outside block production - T::SortedListProvider::unsafe_clear(); + T::VoterList::unsafe_clear(); } /// Grab a funded user. diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 2d4145242a45c..11dfe3e4777f8 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4113,11 +4113,7 @@ mod election_data_provider { .set_status(41, StakerStatus::Validator) .build_and_execute(|| { // sum of all nominators who'd be voters (1), plus the self-votes (4). - assert_eq!( - ::SortedListProvider::count() + - >::iter().count() as u32, - 5 - ); + assert_eq!(::VoterList::count(), 5); // if limits is less.. assert_eq!(Staking::electing_voters(Some(1)).unwrap().len(), 1); @@ -4140,43 +4136,43 @@ mod election_data_provider { }); } + // Tests the criteria that in `ElectionDataProvider::voters` function, we try to get at most + // `maybe_max_len` voters, and if some of them end up being skipped, we iterate at most `2 * + // maybe_max_len`. #[test] - fn only_iterates_max_2_times_nominators_quota() { + fn only_iterates_max_2_times_max_allowed_len() { ExtBuilder::default() - .nominate(true) // add nominator 101, who nominates [11, 21] + .nominate(false) // the other nominators only nominate 21 .add_staker(61, 60, 2_000, StakerStatus::::Nominator(vec![21])) .add_staker(71, 70, 2_000, StakerStatus::::Nominator(vec![21])) .add_staker(81, 80, 2_000, StakerStatus::::Nominator(vec![21])) .build_and_execute(|| { - // given our nominators ordered by stake, - assert_eq!( - ::SortedListProvider::iter().collect::>(), - vec![61, 71, 81, 101] - ); - - // and total voters + // all voters ordered by stake, assert_eq!( - ::SortedListProvider::count() + - >::iter().count() as u32, - 7 + ::VoterList::iter().collect::>(), + vec![61, 71, 81, 11, 21, 31] ); - // roll to session 5 run_to_block(25); // slash 21, the only validator nominated by our first 3 nominators add_slash(&21); - // we take 4 voters: 2 validators and 2 nominators (so nominators quota = 2) + // we want 2 voters now, and in maximum we allow 4 iterations. This is what happens: + // 61 is pruned; + // 71 is pruned; + // 81 is pruned; + // 11 is taken; + // we finish since the 2x limit is reached. assert_eq!( - Staking::electing_voters(Some(3)) + Staking::electing_voters(Some(2)) .unwrap() .iter() .map(|(stash, _, _)| stash) .copied() .collect::>(), - vec![31, 11], // 2 validators, but no nominators because we hit the quota + vec![11], ); }); } @@ -4189,46 +4185,35 @@ mod election_data_provider { #[test] fn get_max_len_voters_even_if_some_nominators_are_slashed() { ExtBuilder::default() - .nominate(true) // add nominator 101, who nominates [11, 21] + .nominate(false) .add_staker(61, 60, 20, StakerStatus::::Nominator(vec![21])) - // 61 only nominates validator 21 ^^ .add_staker(71, 70, 10, StakerStatus::::Nominator(vec![11, 21])) + .add_staker(81, 80, 10, StakerStatus::::Nominator(vec![11, 21])) .build_and_execute(|| { - // given our nominators ordered by stake, + // given our voters ordered by stake, assert_eq!( - ::SortedListProvider::iter().collect::>(), - vec![101, 61, 71] + ::VoterList::iter().collect::>(), + vec![11, 21, 31, 61, 71, 81] ); - // and total voters - assert_eq!( - ::SortedListProvider::count() + - >::iter().count() as u32, - 6 - ); - - // we take 5 voters + // we take 4 voters assert_eq!( - Staking::electing_voters(Some(5)) + Staking::electing_voters(Some(4)) .unwrap() .iter() .map(|(stash, _, _)| stash) .copied() .collect::>(), - // then - vec![ - 31, 21, 11, // 3 nominators - 101, 61 // 2 validators, and 71 is excluded - ], + vec![11, 21, 31, 61], ); // roll to session 5 run_to_block(25); - // slash 21, the only validator nominated by 61 + // slash 21, the only validator nominated by 61. add_slash(&21); - // we take 4 voters + // we take 4 voters; 71 and 81 are replacing the ejected ones. assert_eq!( Staking::electing_voters(Some(4)) .unwrap() @@ -4236,10 +4221,7 @@ mod election_data_provider { .map(|(stash, _, _)| stash) .copied() .collect::>(), - vec![ - 31, 11, // 2 validators (21 was slashed) - 101, 71 // 2 nominators, excluding 61 - ], + vec![11, 31, 71, 81], ); }); } @@ -4755,19 +4737,45 @@ mod sorted_list_provider { fn re_nominate_does_not_change_counters_or_list() { ExtBuilder::default().nominate(true).build_and_execute(|| { // given - let pre_insert_nominator_count = Nominators::::iter().count() as u32; - assert_eq!(::SortedListProvider::count(), pre_insert_nominator_count); - assert!(Nominators::::contains_key(101)); - assert_eq!(::SortedListProvider::iter().collect::>(), vec![101]); + let pre_insert_voter_count = + (Nominators::::count() + Validators::::count()) as u32; + assert_eq!(::VoterList::count(), pre_insert_voter_count); + + assert_eq!( + ::VoterList::iter().collect::>(), + vec![11, 21, 31, 101] + ); // when account 101 renominates assert_ok!(Staking::nominate(Origin::signed(100), vec![41])); // then counts don't change - assert_eq!(::SortedListProvider::count(), pre_insert_nominator_count); - assert_eq!(Nominators::::iter().count() as u32, pre_insert_nominator_count); + assert_eq!(::VoterList::count(), pre_insert_voter_count); + // and the list is the same + assert_eq!( + ::VoterList::iter().collect::>(), + vec![11, 21, 31, 101] + ); + }); + } + + #[test] + fn re_validate_does_not_change_counters_or_list() { + ExtBuilder::default().nominate(false).build_and_execute(|| { + // given + let pre_insert_voter_count = + (Nominators::::count() + Validators::::count()) as u32; + assert_eq!(::VoterList::count(), pre_insert_voter_count); + + assert_eq!(::VoterList::iter().collect::>(), vec![11, 21, 31]); + + // when account 11 re-validates + assert_ok!(Staking::validate(Origin::signed(10), Default::default())); + + // then counts don't change + assert_eq!(::VoterList::count(), pre_insert_voter_count); // and the list is the same - assert_eq!(::SortedListProvider::iter().collect::>(), vec![101]); + assert_eq!(::VoterList::iter().collect::>(), vec![11, 21, 31]); }); } } From 1ca5a246d3cfcf633216dee12007497cd6ecf0a0 Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Wed, 23 Mar 2022 18:10:13 -0400 Subject: [PATCH 052/484] Fix `generate_storage_alias!` (#11102) * add test * fix issues * make order intuitive --- frame/support/src/lib.rs | 42 ++++++++++++------- .../support/src/storage/bounded_btree_map.rs | 4 +- .../support/src/storage/bounded_btree_set.rs | 4 +- frame/support/src/storage/bounded_vec.rs | 4 +- .../src/storage/generator/double_map.rs | 2 +- frame/support/src/storage/generator/map.rs | 2 +- frame/support/src/storage/generator/nmap.rs | 6 +-- frame/support/src/storage/mod.rs | 6 +-- frame/support/src/storage/types/nmap.rs | 2 +- frame/support/src/storage/weak_bounded_vec.rs | 4 +- 10 files changed, 45 insertions(+), 31 deletions(-) diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index 3988b5e9af219..5f62995c52272 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -154,8 +154,8 @@ macro_rules! bounded_vec { /// // to `Vec` /// generate_storage_alias!( /// OtherPrefix, OtherStorageName => DoubleMap< -/// (u32, Twox64Concat), -/// (u32, Twox64Concat), +/// (Twox64Concat, u32), +/// (Twox64Concat, u32), /// Vec /// > /// ); @@ -165,8 +165,8 @@ macro_rules! bounded_vec { /// generate_storage_alias!(Prefix, ValueName => Value); /// generate_storage_alias!( /// Prefix, SomeStorageName => DoubleMap< -/// (u32, Twox64Concat), -/// (u32, Twox64Concat), +/// (Twox64Concat, u32), +/// (Twox64Concat, u32), /// Vec, /// ValueQuery /// > @@ -175,14 +175,14 @@ macro_rules! bounded_vec { /// // generate a map from `Config::AccountId` (with hasher `Twox64Concat`) to `Vec` /// trait Config { type AccountId: codec::FullCodec; } /// generate_storage_alias!( -/// Prefix, GenericStorage => Map<(T::AccountId, Twox64Concat), Vec> +/// Prefix, GenericStorage => Map<(Twox64Concat, T::AccountId), Vec> /// ); /// # fn main() {} /// ``` #[macro_export] macro_rules! generate_storage_alias { // without generic for $name. - ($pallet:ident, $name:ident => Map<($key:ty, $hasher:ty), $value:ty $(, $querytype:ty)?>) => { + ($pallet:ident, $name:ident => Map<($hasher:ty, $key:ty), $value:ty $(, $querytype:ty)?>) => { $crate::paste::paste! { $crate::generate_storage_alias!(@GENERATE_INSTANCE_STRUCT $pallet, $name); type $name = $crate::storage::types::StorageMap< @@ -197,7 +197,7 @@ macro_rules! generate_storage_alias { ( $pallet:ident, $name:ident - => DoubleMap<($key1:ty, $hasher1:ty), ($key2:ty, $hasher2:ty), $value:ty $(, $querytype:ty)?> + => DoubleMap<($hasher1:ty, $key1:ty), ($hasher2:ty, $key2:ty), $value:ty $(, $querytype:ty)?> ) => { $crate::paste::paste! { $crate::generate_storage_alias!(@GENERATE_INSTANCE_STRUCT $pallet, $name); @@ -215,7 +215,7 @@ macro_rules! generate_storage_alias { ( $pallet:ident, $name:ident - => NMap, $value:ty $(, $querytype:ty)?> + => NMap, $value:ty $(, $querytype:ty)?> ) => { $crate::paste::paste! { $crate::generate_storage_alias!(@GENERATE_INSTANCE_STRUCT $pallet, $name); @@ -243,15 +243,15 @@ macro_rules! generate_storage_alias { ( $pallet:ident, $name:ident<$t:ident : $bounds:tt> - => Map<($key:ty, $hasher:ty), $value:ty $(, $querytype:ty)?> + => Map<($hasher:ty, $key:ty), $value:ty $(, $querytype:ty)?> ) => { $crate::paste::paste! { $crate::generate_storage_alias!(@GENERATE_INSTANCE_STRUCT $pallet, $name); #[allow(type_alias_bounds)] type $name<$t : $bounds> = $crate::storage::types::StorageMap< [<$name Instance>], - $key, $hasher, + $key, $value, $( $querytype )? >; @@ -260,17 +260,17 @@ macro_rules! generate_storage_alias { ( $pallet:ident, $name:ident<$t:ident : $bounds:tt> - => DoubleMap<($key1:ty, $hasher1:ty), ($key2:ty, $hasher2:ty), $value:ty $(, $querytype:ty)?> + => DoubleMap<($hasher1:ty, $key1:ty), ($hasher2:ty, $key2:ty), $value:ty $(, $querytype:ty)?> ) => { $crate::paste::paste! { $crate::generate_storage_alias!(@GENERATE_INSTANCE_STRUCT $pallet, $name); #[allow(type_alias_bounds)] type $name<$t : $bounds> = $crate::storage::types::StorageDoubleMap< [<$name Instance>], - $key1, $hasher1, - $key2, + $key1, $hasher2, + $key2, $value, $( $querytype )? >; @@ -279,7 +279,7 @@ macro_rules! generate_storage_alias { ( $pallet:ident, $name:ident<$t:ident : $bounds:tt> - => NMap<$(($key:ty, $hasher:ty),)+ $value:ty $(, $querytype:ty)?> + => NMap<$(($hasher:ty, $key:ty),)+ $value:ty $(, $querytype:ty)?> ) => { $crate::paste::paste! { $crate::generate_storage_alias!(@GENERATE_INSTANCE_STRUCT $pallet, $name); @@ -945,6 +945,20 @@ pub mod tests { } } + #[test] + fn generate_storage_alias_works() { + new_test_ext().execute_with(|| { + generate_storage_alias!( + Test, + GenericData2 => Map<(Blake2_128Concat, T::BlockNumber), T::BlockNumber> + ); + + assert_eq!(Module::::generic_data2(5), None); + GenericData2::::insert(5, 5); + assert_eq!(Module::::generic_data2(5), Some(5)); + }); + } + #[test] fn map_issue_3318() { new_test_ext().execute_with(|| { diff --git a/frame/support/src/storage/bounded_btree_map.rs b/frame/support/src/storage/bounded_btree_map.rs index ed132adac657e..6190552476a45 100644 --- a/frame/support/src/storage/bounded_btree_map.rs +++ b/frame/support/src/storage/bounded_btree_map.rs @@ -344,10 +344,10 @@ pub mod test { use sp_io::TestExternalities; crate::generate_storage_alias! { Prefix, Foo => Value>> } - crate::generate_storage_alias! { Prefix, FooMap => Map<(u32, Twox128), BoundedBTreeMap>> } + crate::generate_storage_alias! { Prefix, FooMap => Map<(Twox128, u32), BoundedBTreeMap>> } crate::generate_storage_alias! { Prefix, - FooDoubleMap => DoubleMap<(u32, Twox128), (u32, Twox128), BoundedBTreeMap>> + FooDoubleMap => DoubleMap<(Twox128, u32), (Twox128, u32), BoundedBTreeMap>> } fn map_from_keys(keys: &[K]) -> BTreeMap diff --git a/frame/support/src/storage/bounded_btree_set.rs b/frame/support/src/storage/bounded_btree_set.rs index 7d543549c6bae..543b997e94e34 100644 --- a/frame/support/src/storage/bounded_btree_set.rs +++ b/frame/support/src/storage/bounded_btree_set.rs @@ -327,10 +327,10 @@ pub mod test { use sp_std::convert::TryInto; crate::generate_storage_alias! { Prefix, Foo => Value>> } - crate::generate_storage_alias! { Prefix, FooMap => Map<(u32, Twox128), BoundedBTreeSet>> } + crate::generate_storage_alias! { Prefix, FooMap => Map<(Twox128, u32), BoundedBTreeSet>> } crate::generate_storage_alias! { Prefix, - FooDoubleMap => DoubleMap<(u32, Twox128), (u32, Twox128), BoundedBTreeSet>> + FooDoubleMap => DoubleMap<(Twox128, u32), (Twox128, u32), BoundedBTreeSet>> } fn set_from_keys(keys: &[T]) -> BTreeSet diff --git a/frame/support/src/storage/bounded_vec.rs b/frame/support/src/storage/bounded_vec.rs index 4e513258f9684..cf585af395587 100644 --- a/frame/support/src/storage/bounded_vec.rs +++ b/frame/support/src/storage/bounded_vec.rs @@ -589,10 +589,10 @@ pub mod test { use sp_io::TestExternalities; crate::generate_storage_alias! { Prefix, Foo => Value>> } - crate::generate_storage_alias! { Prefix, FooMap => Map<(u32, Twox128), BoundedVec>> } + crate::generate_storage_alias! { Prefix, FooMap => Map<(Twox128, u32), BoundedVec>> } crate::generate_storage_alias! { Prefix, - FooDoubleMap => DoubleMap<(u32, Twox128), (u32, Twox128), BoundedVec>> + FooDoubleMap => DoubleMap<(Twox128, u32), (Twox128, u32), BoundedVec>> } #[test] diff --git a/frame/support/src/storage/generator/double_map.rs b/frame/support/src/storage/generator/double_map.rs index d63dda7d5b320..12e1764bfb65d 100644 --- a/frame/support/src/storage/generator/double_map.rs +++ b/frame/support/src/storage/generator/double_map.rs @@ -527,7 +527,7 @@ mod test_iterators { use crate::hash::Identity; crate::generate_storage_alias!( MyModule, - MyDoubleMap => DoubleMap<(u64, Identity), (u64, Identity), u64> + MyDoubleMap => DoubleMap<(Identity, u64), (Identity, u64), u64> ); MyDoubleMap::insert(1, 10, 100); diff --git a/frame/support/src/storage/generator/map.rs b/frame/support/src/storage/generator/map.rs index 4157edefeac4b..da48952bcba87 100644 --- a/frame/support/src/storage/generator/map.rs +++ b/frame/support/src/storage/generator/map.rs @@ -384,7 +384,7 @@ mod test_iterators { fn map_iter_from() { sp_io::TestExternalities::default().execute_with(|| { use crate::hash::Identity; - crate::generate_storage_alias!(MyModule, MyMap => Map<(u64, Identity), u64>); + crate::generate_storage_alias!(MyModule, MyMap => Map<(Identity, u64), u64>); MyMap::insert(1, 10); MyMap::insert(2, 20); diff --git a/frame/support/src/storage/generator/nmap.rs b/frame/support/src/storage/generator/nmap.rs index 1ad308bb22c78..be085ca2d9db6 100755 --- a/frame/support/src/storage/generator/nmap.rs +++ b/frame/support/src/storage/generator/nmap.rs @@ -477,7 +477,7 @@ mod test_iterators { use crate::{hash::Identity, storage::Key as NMapKey}; crate::generate_storage_alias!( MyModule, - MyNMap => NMap, u64> + MyNMap => NMap, u64> ); MyNMap::insert((1, 1, 1), 11); @@ -519,8 +519,8 @@ mod test_iterators { { crate::generate_storage_alias!(Test, NMap => DoubleMap< - (u16, crate::Blake2_128Concat), - (u32, crate::Twox64Concat), + (crate::Blake2_128Concat, u16), + (crate::Twox64Concat, u32), u64 >); diff --git a/frame/support/src/storage/mod.rs b/frame/support/src/storage/mod.rs index 3d777fa3ace50..226682eecf10b 100644 --- a/frame/support/src/storage/mod.rs +++ b/frame/support/src/storage/mod.rs @@ -1618,7 +1618,7 @@ mod test { use crate::{hash::Identity, storage::generator::map::StorageMap}; crate::generate_storage_alias! { MyModule, - MyStorageMap => Map<(u64, Identity), u64> + MyStorageMap => Map<(Identity, u64), u64> } MyStorageMap::insert(1, 10); @@ -1735,10 +1735,10 @@ mod test { } crate::generate_storage_alias! { Prefix, Foo => Value>> } - crate::generate_storage_alias! { Prefix, FooMap => Map<(u32, Twox128), BoundedVec>> } + crate::generate_storage_alias! { Prefix, FooMap => Map<(Twox128, u32), BoundedVec>> } crate::generate_storage_alias! { Prefix, - FooDoubleMap => DoubleMap<(u32, Twox128), (u32, Twox128), BoundedVec>> + FooDoubleMap => DoubleMap<(Twox128, u32), (Twox128, u32), BoundedVec>> } #[test] diff --git a/frame/support/src/storage/types/nmap.rs b/frame/support/src/storage/types/nmap.rs index 5b51ed1ffdf49..561bf5298e183 100755 --- a/frame/support/src/storage/types/nmap.rs +++ b/frame/support/src/storage/types/nmap.rs @@ -569,7 +569,7 @@ mod test { { crate::generate_storage_alias!(test, Foo => NMap< - Key<(u16, Blake2_128Concat)>, + Key<(Blake2_128Concat, u16)>, u32 >); diff --git a/frame/support/src/storage/weak_bounded_vec.rs b/frame/support/src/storage/weak_bounded_vec.rs index db62d73035397..4b3d87f776b4a 100644 --- a/frame/support/src/storage/weak_bounded_vec.rs +++ b/frame/support/src/storage/weak_bounded_vec.rs @@ -323,10 +323,10 @@ pub mod test { use sp_std::convert::TryInto; crate::generate_storage_alias! { Prefix, Foo => Value>> } - crate::generate_storage_alias! { Prefix, FooMap => Map<(u32, Twox128), WeakBoundedVec>> } + crate::generate_storage_alias! { Prefix, FooMap => Map<(Twox128, u32), WeakBoundedVec>> } crate::generate_storage_alias! { Prefix, - FooDoubleMap => DoubleMap<(u32, Twox128), (u32, Twox128), WeakBoundedVec>> + FooDoubleMap => DoubleMap<(Twox128, u32), (Twox128, u32), WeakBoundedVec>> } #[test] From f2b0f2b3ffab27427aafd7613a0c98b23ef3ec66 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Thu, 24 Mar 2022 09:11:14 +0100 Subject: [PATCH 053/484] Allow pallet error enum variants to contain fields (#10242) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Allow pallet errors to contain at most one field * Update docs on pallet::error * Reword documentation * cargo fmt * Introduce CompactPalletError trait and require #[pallet::error] fields to implement them * cargo fmt * Do not assume tuple variants * Add CompactPalletError derive macro * Check for error type compactness in construct_runtime * cargo fmt * Derive CompactPalletError instead of implementing it directly during macro expansion * Implement CompactPalletError on OptionBool instead of Option * Check for type idents instead of variant ident * Add doc comments for ErrorCompactnessTest * Add an trait implementation of ErrorCompactnessTest for () * Convert the error field of DispatchError to a 4-element byte array * Add static check for pallet error size * Rename to MAX_PALLET_ERROR_ENCODED_SIZE * Remove ErrorCompactnessTest trait * Remove check_compactness * Return only the most significant byte when constructing a custom InvalidTransaction * Rename CompactPalletError to PalletError * Use counter to generate unique idents for assert macros * Make declarative pallet macros compile with pallet error size checks * Remove unused doc comment * Try and fix build errors * Fix build errors * Add macro_use for some test modules * Test fix * Fix compilation errors * Remove unneeded #[macro_use] * Resolve import ambiguity * Make path to pallet Error enum more specific * Fix test expectation * Disambiguate imports * Fix test expectations * Revert appending pallet module name to path * Rename bags_list::list::Error to BagError * Fixes * Fixes * Fixes * Fix test expectations * Fix test expectation * Add more implementations for PalletError * Lift the 1-field requirement for nested pallet errors * Fix UI test expectation * Remove PalletError impl for OptionBool * Use saturating operations * cargo fmt * Delete obsolete test * Fix test expectation * Try and use assert macro in const context * Pull out the pallet error size check macro * Fix UI test for const assertion * cargo fmt * Apply clippy suggestion * Fix doc comment * Docs for create_tt_return_macro * Ensure TryInto is imported in earlier Rust editions * Apply suggestions from code review Co-authored-by: Bastian Köcher * Fix up comments and names * Implement PalletError for Never * cargo fmt * Don't compile example code * Bump API version for block builder * Factor in codec attributes while derving PalletError * Rename module and fix unit test * Add missing attribute * Check API version and convert ApplyExtrinsicResult accordingly * Rename BagError to ListError Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Use codec crate re-exported from frame support * Add links to types mentioned in doc comments Co-authored-by: Bastian Köcher * cargo fmt * cargo fmt * Re-add attribute for hidden docs Co-authored-by: Bastian Köcher Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- client/block-builder/src/lib.rs | 30 ++- client/rpc/src/state/tests.rs | 2 +- frame/bags-list/src/lib.rs | 6 +- frame/bags-list/src/list/mod.rs | 6 +- frame/bags-list/src/list/tests.rs | 2 +- frame/bags-list/src/tests.rs | 2 +- .../election-provider-multi-phase/src/lib.rs | 2 +- .../src/unsigned.rs | 4 +- frame/scheduler/src/mock.rs | 4 +- frame/sudo/src/mock.rs | 1 - .../procedural/src/construct_runtime/mod.rs | 34 +++ frame/support/procedural/src/lib.rs | 19 +- .../procedural/src/pallet/expand/error.rs | 96 ++++++-- .../procedural/src/pallet/parse/error.rs | 25 +- frame/support/procedural/src/pallet_error.rs | 197 ++++++++++++++++ frame/support/procedural/src/tt_macro.rs | 110 +++++++++ frame/support/src/dispatch.rs | 4 + frame/support/src/error.rs | 54 +---- frame/support/src/lib.rs | 55 ++++- frame/support/src/traits.rs | 3 + frame/support/src/traits/error.rs | 95 ++++++++ frame/support/test/tests/construct_runtime.rs | 154 +++++-------- .../pallet_error_too_large.rs | 85 +++++++ .../pallet_error_too_large.stderr | 13 ++ frame/support/test/tests/origin.rs | 214 ++++++++++++++++++ frame/support/test/tests/pallet.rs | 10 +- frame/support/test/tests/pallet_instance.rs | 4 +- ... => error_does_not_derive_pallet_error.rs} | 14 +- .../error_does_not_derive_pallet_error.stderr | 12 + .../tests/pallet_ui/error_no_fieldless.stderr | 5 - .../pallet_ui/pass/error_nested_types.rs | 41 ++++ frame/utility/src/tests.rs | 1 - primitives/block-builder/src/lib.rs | 10 +- primitives/runtime/src/legacy.rs | 20 ++ .../runtime/src/legacy/byte_sized_error.rs | 100 ++++++++ primitives/runtime/src/lib.rs | 23 +- primitives/runtime/src/traits.rs | 6 + utils/frame/rpc/system/src/lib.rs | 41 +++- 38 files changed, 1263 insertions(+), 241 deletions(-) create mode 100644 frame/support/procedural/src/pallet_error.rs create mode 100644 frame/support/procedural/src/tt_macro.rs create mode 100644 frame/support/src/traits/error.rs create mode 100644 frame/support/test/tests/construct_runtime_ui/pallet_error_too_large.rs create mode 100644 frame/support/test/tests/construct_runtime_ui/pallet_error_too_large.stderr create mode 100644 frame/support/test/tests/origin.rs rename frame/support/test/tests/pallet_ui/{error_no_fieldless.rs => error_does_not_derive_pallet_error.rs} (50%) create mode 100644 frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.stderr delete mode 100644 frame/support/test/tests/pallet_ui/error_no_fieldless.stderr create mode 100644 frame/support/test/tests/pallet_ui/pass/error_nested_types.rs create mode 100644 primitives/runtime/src/legacy.rs create mode 100644 primitives/runtime/src/legacy/byte_sized_error.rs diff --git a/client/block-builder/src/lib.rs b/client/block-builder/src/lib.rs index a4c6f5aad2aeb..3f9fecbccbb9e 100644 --- a/client/block-builder/src/lib.rs +++ b/client/block-builder/src/lib.rs @@ -35,6 +35,7 @@ use sp_blockchain::{ApplyExtrinsicFailed, Error}; use sp_core::ExecutionContext; use sp_runtime::{ generic::BlockId, + legacy, traits::{Block as BlockT, Hash, HashFor, Header as HeaderT, NumberFor, One}, Digest, }; @@ -135,6 +136,7 @@ where pub struct BlockBuilder<'a, Block: BlockT, A: ProvideRuntimeApi, B> { extrinsics: Vec, api: ApiRef<'a, A::Api>, + version: u32, block_id: BlockId, parent_hash: Block::Hash, backend: &'a B, @@ -183,10 +185,15 @@ where api.initialize_block_with_context(&block_id, ExecutionContext::BlockConstruction, &header)?; + let version = api + .api_version::>(&block_id)? + .ok_or_else(|| Error::VersionInvalid("BlockBuilderApi".to_string()))?; + Ok(Self { parent_hash, extrinsics: Vec::new(), api, + version, block_id, backend, estimated_header_size, @@ -199,13 +206,26 @@ where pub fn push(&mut self, xt: ::Extrinsic) -> Result<(), Error> { let block_id = &self.block_id; let extrinsics = &mut self.extrinsics; + let version = self.version; self.api.execute_in_transaction(|api| { - match api.apply_extrinsic_with_context( - block_id, - ExecutionContext::BlockConstruction, - xt.clone(), - ) { + let res = if version < 6 { + #[allow(deprecated)] + api.apply_extrinsic_before_version_6_with_context( + block_id, + ExecutionContext::BlockConstruction, + xt.clone(), + ) + .map(legacy::byte_sized_error::convert_to_latest) + } else { + api.apply_extrinsic_with_context( + block_id, + ExecutionContext::BlockConstruction, + xt.clone(), + ) + }; + + match res { Ok(Ok(_)) => { extrinsics.push(xt); TransactionOutcome::Commit(Ok(())) diff --git a/client/rpc/src/state/tests.rs b/client/rpc/src/state/tests.rs index 9dbe02cdb7d64..287dfac8c6ba0 100644 --- a/client/rpc/src/state/tests.rs +++ b/client/rpc/src/state/tests.rs @@ -527,7 +527,7 @@ fn should_return_runtime_version() { let result = "{\"specName\":\"test\",\"implName\":\"parity-test\",\"authoringVersion\":1,\ \"specVersion\":2,\"implVersion\":2,\"apis\":[[\"0xdf6acb689907609b\",4],\ - [\"0x37e397fc7c91f5e4\",1],[\"0xd2bc9897eed08f15\",3],[\"0x40fe3ad401f8959a\",5],\ + [\"0x37e397fc7c91f5e4\",1],[\"0xd2bc9897eed08f15\",3],[\"0x40fe3ad401f8959a\",6],\ [\"0xc6e9a76309f39b09\",1],[\"0xdd718d5cc53262d4\",1],[\"0xcbca25e39f142387\",2],\ [\"0xf78b278be53f454c\",2],[\"0xab3c0572291feb8b\",1],[\"0xbc9d89904f5b923f\",1]],\ \"transactionVersion\":1,\"stateVersion\":1}"; diff --git a/frame/bags-list/src/lib.rs b/frame/bags-list/src/lib.rs index c502245409fdb..aa9f1c80dfdbc 100644 --- a/frame/bags-list/src/lib.rs +++ b/frame/bags-list/src/lib.rs @@ -70,7 +70,7 @@ pub mod mock; mod tests; pub mod weights; -pub use list::{notional_bag_for, Bag, Error, List, Node}; +pub use list::{notional_bag_for, Bag, List, ListError, Node}; pub use pallet::*; pub use weights::WeightInfo; @@ -270,7 +270,7 @@ impl, I: 'static> Pallet { } impl, I: 'static> SortedListProvider for Pallet { - type Error = Error; + type Error = ListError; type Score = T::Score; @@ -286,7 +286,7 @@ impl, I: 'static> SortedListProvider for Pallet List::::contains(id) } - fn on_insert(id: T::AccountId, score: T::Score) -> Result<(), Error> { + fn on_insert(id: T::AccountId, score: T::Score) -> Result<(), ListError> { List::::insert(id, score) } diff --git a/frame/bags-list/src/list/mod.rs b/frame/bags-list/src/list/mod.rs index 4921817c7e146..4e1287458bcb4 100644 --- a/frame/bags-list/src/list/mod.rs +++ b/frame/bags-list/src/list/mod.rs @@ -39,7 +39,7 @@ use sp_std::{ }; #[derive(Debug, PartialEq, Eq)] -pub enum Error { +pub enum ListError { /// A duplicate id has been detected. Duplicate, } @@ -266,9 +266,9 @@ impl, I: 'static> List { /// Insert a new id into the appropriate bag in the list. /// /// Returns an error if the list already contains `id`. - pub(crate) fn insert(id: T::AccountId, score: T::Score) -> Result<(), Error> { + pub(crate) fn insert(id: T::AccountId, score: T::Score) -> Result<(), ListError> { if Self::contains(&id) { - return Err(Error::Duplicate) + return Err(ListError::Duplicate) } let bag_score = notional_bag_for::(score); diff --git a/frame/bags-list/src/list/tests.rs b/frame/bags-list/src/list/tests.rs index 9b7a078b44284..c8e233f1e62cc 100644 --- a/frame/bags-list/src/list/tests.rs +++ b/frame/bags-list/src/list/tests.rs @@ -248,7 +248,7 @@ mod list { // then assert_storage_noop!(assert_eq!( List::::insert(3, 20).unwrap_err(), - Error::Duplicate + ListError::Duplicate )); }); } diff --git a/frame/bags-list/src/tests.rs b/frame/bags-list/src/tests.rs index 99396c9cbb3e3..0d6ba4721b9a2 100644 --- a/frame/bags-list/src/tests.rs +++ b/frame/bags-list/src/tests.rs @@ -518,7 +518,7 @@ mod sorted_list_provider { // then assert_storage_noop!(assert_eq!( BagsList::on_insert(3, 20).unwrap_err(), - Error::Duplicate + ListError::Duplicate )); }); } diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index b57d24d2d530c..ddc06ce0aecfd 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -1583,7 +1583,7 @@ impl ElectionProvider for Pallet { /// number. pub fn dispatch_error_to_invalid(error: DispatchError) -> InvalidTransaction { let error_number = match error { - DispatchError::Module(ModuleError { error, .. }) => error, + DispatchError::Module(ModuleError { error, .. }) => error[0], _ => 0, }; InvalidTransaction::Custom(error_number) diff --git a/frame/election-provider-multi-phase/src/unsigned.rs b/frame/election-provider-multi-phase/src/unsigned.rs index c52a4da22cb87..d210852bac19e 100644 --- a/frame/election-provider-multi-phase/src/unsigned.rs +++ b/frame/election-provider-multi-phase/src/unsigned.rs @@ -931,7 +931,7 @@ mod tests { #[test] #[should_panic(expected = "Invalid unsigned submission must produce invalid block and \ deprive validator from their authoring reward.: \ - Module(ModuleError { index: 2, error: 1, message: \ + Module(ModuleError { index: 2, error: [1, 0, 0, 0], message: \ Some(\"PreDispatchWrongWinnerCount\") })")] fn unfeasible_solution_panics() { ExtBuilder::default().build_and_execute(|| { @@ -1053,7 +1053,7 @@ mod tests { MultiPhase::basic_checks(&solution, "mined").unwrap_err(), MinerError::PreDispatchChecksFailed(DispatchError::Module(ModuleError { index: 2, - error: 1, + error: [1, 0, 0, 0], message: Some("PreDispatchWrongWinnerCount"), })), ); diff --git a/frame/scheduler/src/mock.rs b/frame/scheduler/src/mock.rs index 869425c2ff530..ecd04c3e48b52 100644 --- a/frame/scheduler/src/mock.rs +++ b/frame/scheduler/src/mock.rs @@ -38,7 +38,7 @@ use sp_runtime::{ // Logger module to track execution. #[frame_support::pallet] pub mod logger { - use super::*; + use super::{OriginCaller, OriginTrait}; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; use std::cell::RefCell; @@ -71,7 +71,7 @@ pub mod logger { #[pallet::call] impl Pallet where - ::Origin: OriginTrait, + ::Origin: OriginTrait, { #[pallet::weight(*weight)] pub fn log(origin: OriginFor, i: u32, weight: Weight) -> DispatchResult { diff --git a/frame/sudo/src/mock.rs b/frame/sudo/src/mock.rs index 410807789069b..2e2a4abafcd98 100644 --- a/frame/sudo/src/mock.rs +++ b/frame/sudo/src/mock.rs @@ -34,7 +34,6 @@ use sp_runtime::{ // Logger module to track execution. #[frame_support::pallet] pub mod logger { - use super::*; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; diff --git a/frame/support/procedural/src/construct_runtime/mod.rs b/frame/support/procedural/src/construct_runtime/mod.rs index 249eb072001c9..2a86869382c93 100644 --- a/frame/support/procedural/src/construct_runtime/mod.rs +++ b/frame/support/procedural/src/construct_runtime/mod.rs @@ -241,6 +241,7 @@ fn construct_runtime_final_expansion( expand::expand_outer_inherent(&name, &block, &unchecked_extrinsic, &pallets, &scrate); let validate_unsigned = expand::expand_outer_validate_unsigned(&name, &pallets, &scrate); let integrity_test = decl_integrity_test(&scrate); + let static_assertions = decl_static_assertions(&name, &pallets, &scrate); let res = quote!( #scrate_decl @@ -282,6 +283,8 @@ fn construct_runtime_final_expansion( #validate_unsigned #integrity_test + + #static_assertions ); Ok(res) @@ -471,3 +474,34 @@ fn decl_integrity_test(scrate: &TokenStream2) -> TokenStream2 { } ) } + +fn decl_static_assertions( + runtime: &Ident, + pallet_decls: &[Pallet], + scrate: &TokenStream2, +) -> TokenStream2 { + let error_encoded_size_check = pallet_decls.iter().map(|decl| { + let path = &decl.path; + let assert_message = format!( + "The maximum encoded size of the error type in the `{}` pallet exceeds \ + `MAX_MODULE_ERROR_ENCODED_SIZE`", + decl.name, + ); + + quote! { + #scrate::tt_call! { + macro = [{ #path::tt_error_token }] + frame_support = [{ #scrate }] + ~~> #scrate::assert_error_encoded_size! { + path = [{ #path }] + runtime = [{ #runtime }] + assert_message = [{ #assert_message }] + } + } + } + }); + + quote! { + #(#error_encoded_size_check)* + } +} diff --git a/frame/support/procedural/src/lib.rs b/frame/support/procedural/src/lib.rs index e2233fff72285..92564e94493c1 100644 --- a/frame/support/procedural/src/lib.rs +++ b/frame/support/procedural/src/lib.rs @@ -28,9 +28,11 @@ mod dummy_part_checker; mod key_prefix; mod match_and_insert; mod pallet; +mod pallet_error; mod partial_eq_no_bound; mod storage; mod transactional; +mod tt_macro; use proc_macro::TokenStream; use std::{cell::RefCell, str::FromStr}; @@ -41,9 +43,9 @@ thread_local! { static COUNTER: RefCell = RefCell::new(Counter(0)); } -/// Counter to generate a relatively unique identifier for macros querying for the existence of -/// pallet parts. This is necessary because declarative macros gets hoisted to the crate root, -/// which shares the namespace with other pallets containing the very same query macros. +/// Counter to generate a relatively unique identifier for macros. This is necessary because +/// declarative macros gets hoisted to the crate root, which shares the namespace with other pallets +/// containing the very same macros. struct Counter(u64); impl Counter { @@ -562,3 +564,14 @@ pub fn __generate_dummy_part_checker(input: TokenStream) -> TokenStream { pub fn match_and_insert(input: TokenStream) -> TokenStream { match_and_insert::match_and_insert(input) } + +#[proc_macro_derive(PalletError, attributes(codec))] +pub fn derive_pallet_error(input: TokenStream) -> TokenStream { + pallet_error::derive_pallet_error(input) +} + +/// Internal macro used by `frame_support` to create tt-call-compliant macros +#[proc_macro] +pub fn __create_tt_macro(input: TokenStream) -> TokenStream { + tt_macro::create_tt_return_macro(input) +} diff --git a/frame/support/procedural/src/pallet/expand/error.rs b/frame/support/procedural/src/pallet/expand/error.rs index 184fa86d3ae19..86b06d737decf 100644 --- a/frame/support/procedural/src/pallet/expand/error.rs +++ b/frame/support/procedural/src/pallet/expand/error.rs @@ -15,20 +15,48 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::pallet::Def; +use crate::{ + pallet::{parse::error::VariantField, Def}, + COUNTER, +}; use frame_support_procedural_tools::get_doc_literals; +use syn::spanned::Spanned; /// /// * impl various trait on Error pub fn expand_error(def: &mut Def) -> proc_macro2::TokenStream { - let error = if let Some(error) = &def.error { error } else { return Default::default() }; + let count = COUNTER.with(|counter| counter.borrow_mut().inc()); + let error_token_unique_id = + syn::Ident::new(&format!("__tt_error_token_{}", count), def.item.span()); - let error_ident = &error.error; let frame_support = &def.frame_support; let frame_system = &def.frame_system; + let config_where_clause = &def.config.where_clause; + + let error = if let Some(error) = &def.error { + error + } else { + return quote::quote! { + #[macro_export] + #[doc(hidden)] + macro_rules! #error_token_unique_id { + { + $caller:tt + frame_support = [{ $($frame_support:ident)::* }] + } => { + $($frame_support::)*tt_return! { + $caller + } + }; + } + + pub use #error_token_unique_id as tt_error_token; + } + }; + + let error_ident = &error.error; let type_impl_gen = &def.type_impl_generics(error.attr_span); let type_use_gen = &def.type_use_generics(error.attr_span); - let config_where_clause = &def.config.where_clause; let phantom_variant: syn::Variant = syn::parse_quote!( #[doc(hidden)] @@ -39,13 +67,19 @@ pub fn expand_error(def: &mut Def) -> proc_macro2::TokenStream { ) ); - let as_u8_matches = error.variants.iter().enumerate().map( - |(i, (variant, _))| quote::quote_spanned!(error.attr_span => Self::#variant => #i as u8,), - ); - - let as_str_matches = error.variants.iter().map(|(variant, _)| { - let variant_str = format!("{}", variant); - quote::quote_spanned!(error.attr_span => Self::#variant => #variant_str,) + let as_str_matches = error.variants.iter().map(|(variant, field_ty, _)| { + let variant_str = variant.to_string(); + match field_ty { + Some(VariantField { is_named: true }) => { + quote::quote_spanned!(error.attr_span => Self::#variant { .. } => #variant_str,) + }, + Some(VariantField { is_named: false }) => { + quote::quote_spanned!(error.attr_span => Self::#variant(..) => #variant_str,) + }, + None => { + quote::quote_spanned!(error.attr_span => Self::#variant => #variant_str,) + }, + } }); let error_item = { @@ -62,9 +96,14 @@ pub fn expand_error(def: &mut Def) -> proc_macro2::TokenStream { let capture_docs = if cfg!(feature = "no-metadata-docs") { "never" } else { "always" }; // derive TypeInfo for error metadata - error_item - .attrs - .push(syn::parse_quote!( #[derive(#frame_support::scale_info::TypeInfo)] )); + error_item.attrs.push(syn::parse_quote! { + #[derive( + #frame_support::codec::Encode, + #frame_support::codec::Decode, + #frame_support::scale_info::TypeInfo, + #frame_support::PalletError, + )] + }); error_item.attrs.push(syn::parse_quote!( #[scale_info(skip_type_params(#type_use_gen), capture_docs = #capture_docs)] )); @@ -90,14 +129,6 @@ pub fn expand_error(def: &mut Def) -> proc_macro2::TokenStream { } impl<#type_impl_gen> #error_ident<#type_use_gen> #config_where_clause { - #[doc(hidden)] - pub fn as_u8(&self) -> u8 { - match &self { - Self::__Ignore(_, _) => unreachable!("`__Ignore` can never be constructed"), - #( #as_u8_matches )* - } - } - #[doc(hidden)] pub fn as_str(&self) -> &'static str { match &self { @@ -120,18 +151,37 @@ pub fn expand_error(def: &mut Def) -> proc_macro2::TokenStream { #config_where_clause { fn from(err: #error_ident<#type_use_gen>) -> Self { + use #frame_support::codec::Encode; let index = < ::PalletInfo as #frame_support::traits::PalletInfo >::index::>() .expect("Every active module has an index in the runtime; qed") as u8; + let mut encoded = err.encode(); + encoded.resize(#frame_support::MAX_MODULE_ERROR_ENCODED_SIZE, 0); #frame_support::sp_runtime::DispatchError::Module(#frame_support::sp_runtime::ModuleError { index, - error: err.as_u8(), + error: core::convert::TryInto::try_into(encoded).expect("encoded error is resized to be equal to the maximum encoded error size; qed"), message: Some(err.as_str()), }) } } + + #[macro_export] + #[doc(hidden)] + macro_rules! #error_token_unique_id { + { + $caller:tt + frame_support = [{ $($frame_support:ident)::* }] + } => { + $($frame_support::)*tt_return! { + $caller + error = [{ #error_ident }] + } + }; + } + + pub use #error_token_unique_id as tt_error_token; ) } diff --git a/frame/support/procedural/src/pallet/parse/error.rs b/frame/support/procedural/src/pallet/parse/error.rs index 419770386bf69..0ec49aa0adb49 100644 --- a/frame/support/procedural/src/pallet/parse/error.rs +++ b/frame/support/procedural/src/pallet/parse/error.rs @@ -18,20 +18,26 @@ use super::helper; use frame_support_procedural_tools::get_doc_literals; use quote::ToTokens; -use syn::spanned::Spanned; +use syn::{spanned::Spanned, Fields}; /// List of additional token to be used for parsing. mod keyword { syn::custom_keyword!(Error); } +/// Records information about the error enum variants. +pub struct VariantField { + /// Whether or not the field is named, i.e. whether it is a tuple variant or struct variant. + pub is_named: bool, +} + /// This checks error declaration as a enum declaration with only variants without fields nor /// discriminant. pub struct ErrorDef { /// The index of error item in pallet module. pub index: usize, - /// Variants ident and doc literals (ordered as declaration order) - pub variants: Vec<(syn::Ident, Vec)>, + /// Variants ident, optional field and doc literals (ordered as declaration order) + pub variants: Vec<(syn::Ident, Option, Vec)>, /// A set of usage of instance, must be check for consistency with trait. pub instances: Vec, /// The keyword error used (contains span). @@ -70,18 +76,19 @@ impl ErrorDef { .variants .iter() .map(|variant| { - if !matches!(variant.fields, syn::Fields::Unit) { - let msg = "Invalid pallet::error, unexpected fields, must be `Unit`"; - return Err(syn::Error::new(variant.fields.span(), msg)) - } + let field_ty = match &variant.fields { + Fields::Unit => None, + Fields::Named(_) => Some(VariantField { is_named: true }), + Fields::Unnamed(_) => Some(VariantField { is_named: false }), + }; if variant.discriminant.is_some() { - let msg = "Invalid pallet::error, unexpected discriminant, discriminant \ + let msg = "Invalid pallet::error, unexpected discriminant, discriminants \ are not supported"; let span = variant.discriminant.as_ref().unwrap().0.span(); return Err(syn::Error::new(span, msg)) } - Ok((variant.ident.clone(), get_doc_literals(&variant.attrs))) + Ok((variant.ident.clone(), field_ty, get_doc_literals(&variant.attrs))) }) .collect::>()?; diff --git a/frame/support/procedural/src/pallet_error.rs b/frame/support/procedural/src/pallet_error.rs new file mode 100644 index 0000000000000..216168131e43d --- /dev/null +++ b/frame/support/procedural/src/pallet_error.rs @@ -0,0 +1,197 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use frame_support_procedural_tools::generate_crate_access_2018; +use quote::ToTokens; +use std::str::FromStr; + +// Derive `PalletError` +pub fn derive_pallet_error(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let syn::DeriveInput { ident: name, generics, data, .. } = match syn::parse(input) { + Ok(input) => input, + Err(e) => return e.to_compile_error().into(), + }; + + let frame_support = match generate_crate_access_2018("frame-support") { + Ok(c) => c, + Err(e) => return e.into_compile_error().into(), + }; + let frame_support = &frame_support; + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + let max_encoded_size = match data { + syn::Data::Struct(syn::DataStruct { fields, .. }) => match fields { + syn::Fields::Named(syn::FieldsNamed { named: fields, .. }) | + syn::Fields::Unnamed(syn::FieldsUnnamed { unnamed: fields, .. }) => { + let maybe_field_tys = fields + .iter() + .map(|f| generate_field_types(f, &frame_support)) + .collect::>>(); + let field_tys = match maybe_field_tys { + Ok(tys) => tys.into_iter().flatten(), + Err(e) => return e.into_compile_error().into(), + }; + quote::quote! { + 0_usize + #( + .saturating_add(< + #field_tys as #frame_support::traits::PalletError + >::MAX_ENCODED_SIZE) + )* + } + }, + syn::Fields::Unit => quote::quote!(0), + }, + syn::Data::Enum(syn::DataEnum { variants, .. }) => { + let field_tys = variants + .iter() + .map(|variant| generate_variant_field_types(variant, &frame_support)) + .collect::>>, syn::Error>>(); + + let field_tys = match field_tys { + Ok(tys) => tys.into_iter().flatten().collect::>(), + Err(e) => return e.to_compile_error().into(), + }; + + // We start with `1`, because the discriminant of an enum is stored as u8 + if field_tys.is_empty() { + quote::quote!(1) + } else { + let variant_sizes = field_tys.into_iter().map(|variant_field_tys| { + quote::quote! { + 1_usize + #(.saturating_add(< + #variant_field_tys as #frame_support::traits::PalletError + >::MAX_ENCODED_SIZE))* + } + }); + + quote::quote! {{ + let mut size = 1_usize; + let mut tmp = 0_usize; + #( + tmp = #variant_sizes; + size = if tmp > size { tmp } else { size }; + tmp = 0_usize; + )* + size + }} + } + }, + syn::Data::Union(syn::DataUnion { union_token, .. }) => { + let msg = "Cannot derive `PalletError` for union; please implement it directly"; + return syn::Error::new(union_token.span, msg).into_compile_error().into() + }, + }; + + quote::quote!( + const _: () = { + impl #impl_generics #frame_support::traits::PalletError + for #name #ty_generics #where_clause + { + const MAX_ENCODED_SIZE: usize = #max_encoded_size; + } + }; + ) + .into() +} + +fn generate_field_types( + field: &syn::Field, + scrate: &syn::Ident, +) -> syn::Result> { + let attrs = &field.attrs; + + for attr in attrs { + if attr.path.is_ident("codec") { + match attr.parse_meta()? { + syn::Meta::List(ref meta_list) if meta_list.nested.len() == 1 => { + match meta_list + .nested + .first() + .expect("Just checked that there is one item; qed") + { + syn::NestedMeta::Meta(syn::Meta::Path(path)) + if path.get_ident().map_or(false, |i| i == "skip") => + return Ok(None), + + syn::NestedMeta::Meta(syn::Meta::Path(path)) + if path.get_ident().map_or(false, |i| i == "compact") => + { + let field_ty = &field.ty; + return Ok(Some(quote::quote!(#scrate::codec::Compact<#field_ty>))) + }, + + syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue { + path, + lit: syn::Lit::Str(lit_str), + .. + })) if path.get_ident().map_or(false, |i| i == "encoded_as") => { + let ty = proc_macro2::TokenStream::from_str(&lit_str.value())?; + return Ok(Some(ty)) + }, + + _ => (), + } + }, + _ => (), + } + } + } + + Ok(Some(field.ty.to_token_stream())) +} + +fn generate_variant_field_types( + variant: &syn::Variant, + scrate: &syn::Ident, +) -> syn::Result>> { + let attrs = &variant.attrs; + + for attr in attrs { + if attr.path.is_ident("codec") { + match attr.parse_meta()? { + syn::Meta::List(ref meta_list) if meta_list.nested.len() == 1 => { + match meta_list + .nested + .first() + .expect("Just checked that there is one item; qed") + { + syn::NestedMeta::Meta(syn::Meta::Path(path)) + if path.get_ident().map_or(false, |i| i == "skip") => + return Ok(None), + + _ => (), + } + }, + _ => (), + } + } + } + + match &variant.fields { + syn::Fields::Named(syn::FieldsNamed { named: fields, .. }) | + syn::Fields::Unnamed(syn::FieldsUnnamed { unnamed: fields, .. }) => { + let field_tys = fields + .iter() + .map(|field| generate_field_types(field, scrate)) + .collect::>>()?; + Ok(Some(field_tys.into_iter().flatten().collect())) + }, + syn::Fields::Unit => Ok(None), + } +} diff --git a/frame/support/procedural/src/tt_macro.rs b/frame/support/procedural/src/tt_macro.rs new file mode 100644 index 0000000000000..0a270a7173cfc --- /dev/null +++ b/frame/support/procedural/src/tt_macro.rs @@ -0,0 +1,110 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Implementation of the `create_tt_return_macro` macro + +use crate::COUNTER; +use frame_support_procedural_tools::generate_crate_access_2018; +use proc_macro2::{Ident, TokenStream}; +use quote::format_ident; + +struct CreateTtReturnMacroDef { + name: Ident, + args: Vec<(Ident, TokenStream)>, +} + +impl syn::parse::Parse for CreateTtReturnMacroDef { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let name = input.parse()?; + let _ = input.parse::()?; + + let mut args = Vec::new(); + while !input.is_empty() { + let mut value; + let key: Ident = input.parse()?; + let _ = input.parse::()?; + let _: syn::token::Bracket = syn::bracketed!(value in input); + let _: syn::token::Brace = syn::braced!(value in value); + let value: TokenStream = value.parse()?; + + args.push((key, value)) + } + + Ok(Self { name, args }) + } +} + +/// A proc macro that accepts a name and any number of key-value pairs, to be used to create a +/// declarative macro that follows tt-call conventions and simply calls [`tt_call::tt_return`], +/// accepting an optional `frame-support` argument and returning the key-value pairs that were +/// supplied to the proc macro. +/// +/// # Example +/// ```ignore +/// __create_tt_macro! { +/// my_tt_macro, +/// foo = [{ bar }] +/// } +/// +/// // Creates the following declarative macro: +/// +/// macro_rules! my_tt_macro { +/// { +/// $caller:tt +/// $(frame_support = [{ $($frame_support:ident)::* }])? +/// } => { +/// frame_support::tt_return! { +/// $caller +/// foo = [{ bar }] +/// } +/// } +/// } +/// ``` +pub fn create_tt_return_macro(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let CreateTtReturnMacroDef { name, args } = + syn::parse_macro_input!(input as CreateTtReturnMacroDef); + + let frame_support = match generate_crate_access_2018("frame-support") { + Ok(i) => i, + Err(e) => return e.into_compile_error().into(), + }; + let (keys, values): (Vec<_>, Vec<_>) = args.into_iter().unzip(); + let count = COUNTER.with(|counter| counter.borrow_mut().inc()); + let unique_name = format_ident!("{}_{}", name, count); + + let decl_macro = quote::quote! { + #[macro_export] + #[doc(hidden)] + macro_rules! #unique_name { + { + $caller:tt + $(frame_support = [{ $($frame_support:ident)::* }])? + } => { + #frame_support::tt_return! { + $caller + #( + #keys = [{ #values }] + )* + } + } + } + + pub use #unique_name as #name; + }; + + decl_macro.into() +} diff --git a/frame/support/src/dispatch.rs b/frame/support/src/dispatch.rs index ece5173f2f4ca..7ccf796167297 100644 --- a/frame/support/src/dispatch.rs +++ b/frame/support/src/dispatch.rs @@ -2003,6 +2003,10 @@ macro_rules! decl_module { pub type Pallet<$trait_instance $(, $instance $( = $module_default_instance)?)?> = $mod_type<$trait_instance $(, $instance)?>; + $crate::__create_tt_macro! { + tt_error_token, + } + $crate::decl_module! { @impl_on_initialize { $system } diff --git a/frame/support/src/error.rs b/frame/support/src/error.rs index 4880bba5c5e97..764376a4e1dc2 100644 --- a/frame/support/src/error.rs +++ b/frame/support/src/error.rs @@ -85,7 +85,12 @@ macro_rules! decl_error { } ) => { $(#[$attr])* - #[derive($crate::scale_info::TypeInfo)] + #[derive( + $crate::codec::Encode, + $crate::codec::Decode, + $crate::scale_info::TypeInfo, + $crate::PalletError, + )] #[scale_info(skip_type_params($generic $(, $inst_generic)?), capture_docs = "always")] pub enum $error<$generic: $trait $(, $inst_generic: $instance)?> $( where $( $where_ty: $where_bound ),* )? @@ -114,17 +119,6 @@ macro_rules! decl_error { impl<$generic: $trait $(, $inst_generic: $instance)?> $error<$generic $(, $inst_generic)?> $( where $( $where_ty: $where_bound ),* )? { - fn as_u8(&self) -> u8 { - $crate::decl_error! { - @GENERATE_AS_U8 - self - $error - {} - 0, - $( $name ),* - } - } - fn as_str(&self) -> &'static str { match self { Self::__Ignore(_, _) => unreachable!("`__Ignore` can never be constructed"), @@ -149,47 +143,19 @@ macro_rules! decl_error { $( where $( $where_ty: $where_bound ),* )? { fn from(err: $error<$generic $(, $inst_generic)?>) -> Self { + use $crate::codec::Encode; let index = <$generic::PalletInfo as $crate::traits::PalletInfo> ::index::<$module<$generic $(, $inst_generic)?>>() .expect("Every active module has an index in the runtime; qed") as u8; + let mut error = err.encode(); + error.resize($crate::MAX_MODULE_ERROR_ENCODED_SIZE, 0); $crate::sp_runtime::DispatchError::Module($crate::sp_runtime::ModuleError { index, - error: err.as_u8(), + error: core::convert::TryInto::try_into(error).expect("encoded error is resized to be equal to the maximum encoded error size; qed"), message: Some(err.as_str()), }) } } }; - (@GENERATE_AS_U8 - $self:ident - $error:ident - { $( $generated:tt )* } - $index:expr, - $name:ident - $( , $rest:ident )* - ) => { - $crate::decl_error! { - @GENERATE_AS_U8 - $self - $error - { - $( $generated )* - $error::$name => $index, - } - $index + 1, - $( $rest ),* - } - }; - (@GENERATE_AS_U8 - $self:ident - $error:ident - { $( $generated:tt )* } - $index:expr, - ) => { - match $self { - $error::__Ignore(_, _) => unreachable!("`__Ignore` can never be constructed"), - $( $generated )* - } - } } diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index 5f62995c52272..70a79c07189dc 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -93,7 +93,9 @@ pub use self::{ StorageMap, StorageNMap, StoragePrefixedMap, StorageValue, }, }; -pub use sp_runtime::{self, print, traits::Printable, ConsensusEngineId}; +pub use sp_runtime::{ + self, print, traits::Printable, ConsensusEngineId, MAX_MODULE_ERROR_ENCODED_SIZE, +}; use codec::{Decode, Encode}; use scale_info::TypeInfo; @@ -103,7 +105,7 @@ use sp_runtime::TypeId; pub const LOG_TARGET: &'static str = "runtime::frame-support"; /// A type that cannot be instantiated. -#[derive(Debug, PartialEq, Eq, Clone, TypeInfo)] +#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] pub enum Never {} /// A pallet identifier. These are per pallet and should be stored in a registry somewhere. @@ -598,11 +600,12 @@ pub fn debug(data: &impl sp_std::fmt::Debug) { #[doc(inline)] pub use frame_support_procedural::{ - construct_runtime, decl_storage, match_and_insert, transactional, RuntimeDebugNoBound, + construct_runtime, decl_storage, match_and_insert, transactional, PalletError, + RuntimeDebugNoBound, }; #[doc(hidden)] -pub use frame_support_procedural::__generate_dummy_part_checker; +pub use frame_support_procedural::{__create_tt_macro, __generate_dummy_part_checker}; /// Derive [`Clone`] but do not bound any generic. /// @@ -847,6 +850,32 @@ macro_rules! assert_ok { }; } +/// Assert that the maximum encoding size does not exceed the value defined in +/// [`MAX_MODULE_ERROR_ENCODED_SIZE`] during compilation. +/// +/// This macro is intended to be used in conjunction with `tt_call!`. +#[macro_export] +macro_rules! assert_error_encoded_size { + { + path = [{ $($path:ident)::+ }] + runtime = [{ $runtime:ident }] + assert_message = [{ $assert_message:literal }] + error = [{ $error:ident }] + } => { + const _: () = assert!( + < + $($path::)+$error<$runtime> as $crate::traits::PalletError + >::MAX_ENCODED_SIZE <= $crate::MAX_MODULE_ERROR_ENCODED_SIZE, + $assert_message + ); + }; + { + path = [{ $($path:ident)::+ }] + runtime = [{ $runtime:ident }] + assert_message = [{ $assert_message:literal }] + } => {}; +} + #[cfg(feature = "std")] #[doc(hidden)] pub use serde::{Deserialize, Serialize}; @@ -1375,6 +1404,7 @@ pub mod pallet_prelude { TransactionTag, TransactionValidity, TransactionValidityError, UnknownTransaction, ValidTransaction, }, + MAX_MODULE_ERROR_ENCODED_SIZE, }; pub use sp_std::marker::PhantomData; } @@ -1652,10 +1682,25 @@ pub mod pallet_prelude { /// pub enum Error { /// /// $some_optional_doc /// $SomeFieldLessVariant, +/// /// $some_more_optional_doc +/// $SomeVariantWithOneField(FieldType), /// ... /// } /// ``` -/// I.e. a regular rust enum named `Error`, with generic `T` and fieldless variants. +/// I.e. a regular rust enum named `Error`, with generic `T` and fieldless or multiple-field +/// variants. +/// +/// Any field type in the enum variants must implement [`scale_info::TypeInfo`] in order to be +/// properly used in the metadata, and its encoded size should be as small as possible, +/// preferably 1 byte in size in order to reduce storage size. The error enum itself has an +/// absolute maximum encoded size specified by [`MAX_MODULE_ERROR_ENCODED_SIZE`]. +/// +/// Field types in enum variants must also implement [`PalletError`](traits::PalletError), +/// otherwise the pallet will fail to compile. Rust primitive types have already implemented +/// the [`PalletError`](traits::PalletError) trait along with some commonly used stdlib types +/// such as `Option` and `PhantomData`, and hence in most use cases, a manual implementation is +/// not necessary and is discouraged. +/// /// The generic `T` mustn't bound anything and where clause is not allowed. But bounds and /// where clause shouldn't be needed for any usecase. /// diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index a8ce78ae9dabc..40afc0d337f43 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -46,6 +46,9 @@ pub use validation::{ ValidatorSetWithIdentification, VerifySeal, }; +mod error; +pub use error::PalletError; + mod filter; pub use filter::{ClearFilterGuard, FilterStack, FilterStackGuard, InstanceFilter, IntegrityTest}; diff --git a/frame/support/src/traits/error.rs b/frame/support/src/traits/error.rs new file mode 100644 index 0000000000000..8e26891669e65 --- /dev/null +++ b/frame/support/src/traits/error.rs @@ -0,0 +1,95 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Traits for describing and constraining pallet error types. +use codec::{Compact, Decode, Encode}; +use sp_std::marker::PhantomData; + +/// Trait indicating that the implementing type is going to be included as a field in a variant of +/// the `#[pallet::error]` enum type. +/// +/// ## Notes +/// +/// The pallet error enum has a maximum encoded size as defined by +/// [`frame_support::MAX_MODULE_ERROR_ENCODED_SIZE`]. If the pallet error type exceeds this size +/// limit, a static assertion during compilation will fail. The compilation error will be in the +/// format of `error[E0080]: evaluation of constant value failed` due to the usage of +/// const assertions. +pub trait PalletError: Encode + Decode { + /// The maximum encoded size for the implementing type. + /// + /// This will be used to check whether the pallet error type is less than or equal to + /// [`frame_support::MAX_MODULE_ERROR_ENCODED_SIZE`], and if it is, a compilation error will be + /// thrown. + const MAX_ENCODED_SIZE: usize; +} + +macro_rules! impl_for_types { + (size: $size:expr, $($typ:ty),+) => { + $( + impl PalletError for $typ { + const MAX_ENCODED_SIZE: usize = $size; + } + )+ + }; +} + +impl_for_types!(size: 0, (), crate::Never); +impl_for_types!(size: 1, u8, i8, bool); +impl_for_types!(size: 2, u16, i16, Compact); +impl_for_types!(size: 4, u32, i32, Compact); +impl_for_types!(size: 5, Compact); +impl_for_types!(size: 8, u64, i64); +impl_for_types!(size: 9, Compact); +// Contains a u64 for secs and u32 for nanos, hence 12 bytes +impl_for_types!(size: 12, core::time::Duration); +impl_for_types!(size: 16, u128, i128); +impl_for_types!(size: 17, Compact); + +impl PalletError for PhantomData { + const MAX_ENCODED_SIZE: usize = 0; +} + +impl PalletError for core::ops::Range { + const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE.saturating_mul(2); +} + +impl PalletError for [T; N] { + const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE.saturating_mul(N); +} + +impl PalletError for Option { + const MAX_ENCODED_SIZE: usize = T::MAX_ENCODED_SIZE.saturating_add(1); +} + +impl PalletError for Result { + const MAX_ENCODED_SIZE: usize = if T::MAX_ENCODED_SIZE > E::MAX_ENCODED_SIZE { + T::MAX_ENCODED_SIZE + } else { + E::MAX_ENCODED_SIZE + } + .saturating_add(1); +} + +#[impl_trait_for_tuples::impl_for_tuples(1, 18)] +impl PalletError for Tuple { + const MAX_ENCODED_SIZE: usize = { + let mut size = 0_usize; + for_tuples!( #(size = size.saturating_add(Tuple::MAX_ENCODED_SIZE);)* ); + size + }; +} diff --git a/frame/support/test/tests/construct_runtime.rs b/frame/support/test/tests/construct_runtime.rs index b3f8feb8aa4b2..804deb08919a4 100644 --- a/frame/support/test/tests/construct_runtime.rs +++ b/frame/support/test/tests/construct_runtime.rs @@ -271,139 +271,95 @@ pub type Header = generic::Header; pub type Block = generic::Block; pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; -mod origin_test { - use super::{module3, nested, system, Block, UncheckedExtrinsic}; - use frame_support::traits::{Contains, OriginTrait}; - - impl nested::module3::Config for RuntimeOriginTest {} - impl module3::Config for RuntimeOriginTest {} - - pub struct BaseCallFilter; - impl Contains for BaseCallFilter { - fn contains(c: &Call) -> bool { - match c { - Call::NestedModule3(_) => true, - _ => false, - } - } - } - - impl system::Config for RuntimeOriginTest { - type BaseCallFilter = BaseCallFilter; - type Hash = super::H256; - type Origin = Origin; - type BlockNumber = super::BlockNumber; - type AccountId = u32; - type Event = Event; - type PalletInfo = PalletInfo; - type Call = Call; - type DbWeight = (); - } - - frame_support::construct_runtime!( - pub enum RuntimeOriginTest where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic - { - System: system::{Pallet, Event, Origin}, - NestedModule3: nested::module3::{Pallet, Origin, Call}, - Module3: module3::{Pallet, Origin, Call}, - } - ); - - #[test] - fn origin_default_filter() { - let accepted_call = nested::module3::Call::fail {}.into(); - let rejected_call = module3::Call::fail {}.into(); - - assert_eq!(Origin::root().filter_call(&accepted_call), true); - assert_eq!(Origin::root().filter_call(&rejected_call), true); - assert_eq!(Origin::none().filter_call(&accepted_call), true); - assert_eq!(Origin::none().filter_call(&rejected_call), false); - assert_eq!(Origin::signed(0).filter_call(&accepted_call), true); - assert_eq!(Origin::signed(0).filter_call(&rejected_call), false); - assert_eq!(Origin::from(Some(0)).filter_call(&accepted_call), true); - assert_eq!(Origin::from(Some(0)).filter_call(&rejected_call), false); - assert_eq!(Origin::from(None).filter_call(&accepted_call), true); - assert_eq!(Origin::from(None).filter_call(&rejected_call), false); - assert_eq!(Origin::from(super::nested::module3::Origin).filter_call(&accepted_call), true); - assert_eq!(Origin::from(super::nested::module3::Origin).filter_call(&rejected_call), false); - - let mut origin = Origin::from(Some(0)); - origin.add_filter(|c| matches!(c, Call::Module3(_))); - assert_eq!(origin.filter_call(&accepted_call), false); - assert_eq!(origin.filter_call(&rejected_call), false); - - // Now test for root origin and filters: - let mut origin = Origin::from(Some(0)); - origin.set_caller_from(Origin::root()); - assert!(matches!(origin.caller, OriginCaller::system(super::system::RawOrigin::Root))); - - // Root origin bypass all filter. - assert_eq!(origin.filter_call(&accepted_call), true); - assert_eq!(origin.filter_call(&rejected_call), true); - - origin.set_caller_from(Origin::from(Some(0))); - - // Back to another signed origin, the filtered are now effective again - assert_eq!(origin.filter_call(&accepted_call), true); - assert_eq!(origin.filter_call(&rejected_call), false); - - origin.set_caller_from(Origin::root()); - origin.reset_filter(); - - // Root origin bypass all filter, even when they are reset. - assert_eq!(origin.filter_call(&accepted_call), true); - assert_eq!(origin.filter_call(&rejected_call), true); - } -} - #[test] fn check_modules_error_type() { assert_eq!( Module1_1::fail(system::Origin::::Root.into()), - Err(DispatchError::Module(ModuleError { index: 31, error: 0, message: Some("Something") })), + Err(DispatchError::Module(ModuleError { + index: 31, + error: [0; 4], + message: Some("Something") + })), ); assert_eq!( Module2::fail(system::Origin::::Root.into()), - Err(DispatchError::Module(ModuleError { index: 32, error: 0, message: Some("Something") })), + Err(DispatchError::Module(ModuleError { + index: 32, + error: [0; 4], + message: Some("Something") + })), ); assert_eq!( Module1_2::fail(system::Origin::::Root.into()), - Err(DispatchError::Module(ModuleError { index: 33, error: 0, message: Some("Something") })), + Err(DispatchError::Module(ModuleError { + index: 33, + error: [0; 4], + message: Some("Something") + })), ); assert_eq!( NestedModule3::fail(system::Origin::::Root.into()), - Err(DispatchError::Module(ModuleError { index: 34, error: 0, message: Some("Something") })), + Err(DispatchError::Module(ModuleError { + index: 34, + error: [0; 4], + message: Some("Something") + })), ); assert_eq!( Module1_3::fail(system::Origin::::Root.into()), - Err(DispatchError::Module(ModuleError { index: 6, error: 0, message: Some("Something") })), + Err(DispatchError::Module(ModuleError { + index: 6, + error: [0; 4], + message: Some("Something") + })), ); assert_eq!( Module1_4::fail(system::Origin::::Root.into()), - Err(DispatchError::Module(ModuleError { index: 3, error: 0, message: Some("Something") })), + Err(DispatchError::Module(ModuleError { + index: 3, + error: [0; 4], + message: Some("Something") + })), ); assert_eq!( Module1_5::fail(system::Origin::::Root.into()), - Err(DispatchError::Module(ModuleError { index: 4, error: 0, message: Some("Something") })), + Err(DispatchError::Module(ModuleError { + index: 4, + error: [0; 4], + message: Some("Something") + })), ); assert_eq!( Module1_6::fail(system::Origin::::Root.into()), - Err(DispatchError::Module(ModuleError { index: 1, error: 0, message: Some("Something") })), + Err(DispatchError::Module(ModuleError { + index: 1, + error: [0; 4], + message: Some("Something") + })), ); assert_eq!( Module1_7::fail(system::Origin::::Root.into()), - Err(DispatchError::Module(ModuleError { index: 2, error: 0, message: Some("Something") })), + Err(DispatchError::Module(ModuleError { + index: 2, + error: [0; 4], + message: Some("Something") + })), ); assert_eq!( Module1_8::fail(system::Origin::::Root.into()), - Err(DispatchError::Module(ModuleError { index: 12, error: 0, message: Some("Something") })), + Err(DispatchError::Module(ModuleError { + index: 12, + error: [0; 4], + message: Some("Something") + })), ); assert_eq!( Module1_9::fail(system::Origin::::Root.into()), - Err(DispatchError::Module(ModuleError { index: 13, error: 0, message: Some("Something") })), + Err(DispatchError::Module(ModuleError { + index: 13, + error: [0; 4], + message: Some("Something") + })), ); } diff --git a/frame/support/test/tests/construct_runtime_ui/pallet_error_too_large.rs b/frame/support/test/tests/construct_runtime_ui/pallet_error_too_large.rs new file mode 100644 index 0000000000000..827d8a58af733 --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/pallet_error_too_large.rs @@ -0,0 +1,85 @@ +use frame_support::construct_runtime; +use sp_runtime::{generic, traits::BlakeTwo256}; +use sp_core::sr25519; + +#[frame_support::pallet] +mod pallet { + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::error] + pub enum Error { + MyError(crate::Nested1), + } +} + +#[derive(scale_info::TypeInfo, frame_support::PalletError, codec::Encode, codec::Decode)] +pub enum Nested1 { + Nested2(Nested2) +} + +#[derive(scale_info::TypeInfo, frame_support::PalletError, codec::Encode, codec::Decode)] +pub enum Nested2 { + Nested3(Nested3) +} + +#[derive(scale_info::TypeInfo, frame_support::PalletError, codec::Encode, codec::Decode)] +pub enum Nested3 { + Nested4(Nested4) +} + +#[derive(scale_info::TypeInfo, frame_support::PalletError, codec::Encode, codec::Decode)] +pub enum Nested4 { + Num(u8) +} + +pub type Signature = sr25519::Signature; +pub type BlockNumber = u32; +pub type Header = generic::Header; +pub type Block = generic::Block; +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; + +impl pallet::Config for Runtime {} + +impl frame_system::Config for Runtime { + type BaseCallFilter = frame_support::traits::Everything; + type Origin = Origin; + type Index = u64; + type BlockNumber = u32; + type Call = Call; + type Hash = sp_runtime::testing::H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = u64; + type Lookup = sp_runtime::traits::IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = frame_support::traits::ConstU32<250>; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +construct_runtime! { + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: frame_system::{Pallet, Call, Storage, Config, Event}, + Pallet: pallet::{Pallet}, + } +} + +fn main() {} diff --git a/frame/support/test/tests/construct_runtime_ui/pallet_error_too_large.stderr b/frame/support/test/tests/construct_runtime_ui/pallet_error_too_large.stderr new file mode 100644 index 0000000000000..161873866b6f3 --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/pallet_error_too_large.stderr @@ -0,0 +1,13 @@ +error[E0080]: evaluation of constant value failed + --> tests/construct_runtime_ui/pallet_error_too_large.rs:74:1 + | +74 | / construct_runtime! { +75 | | pub enum Runtime where +76 | | Block = Block, +77 | | NodeBlock = Block, +... | +82 | | } +83 | | } + | |_^ the evaluated program panicked at 'The maximum encoded size of the error type in the `Pallet` pallet exceeds `MAX_MODULE_ERROR_ENCODED_SIZE`', $DIR/tests/construct_runtime_ui/pallet_error_too_large.rs:74:1 + | + = note: this error originates in the macro `$crate::panic::panic_2021` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/frame/support/test/tests/origin.rs b/frame/support/test/tests/origin.rs new file mode 100644 index 0000000000000..1def44c15b48f --- /dev/null +++ b/frame/support/test/tests/origin.rs @@ -0,0 +1,214 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Origin tests for construct_runtime macro + +#![recursion_limit = "128"] + +use frame_support::traits::{Contains, OriginTrait}; +use scale_info::TypeInfo; +use sp_core::{sr25519, H256}; +use sp_runtime::{generic, traits::BlakeTwo256}; + +mod system; + +mod nested { + use super::*; + + pub mod module { + use super::*; + + pub trait Config: system::Config {} + + frame_support::decl_module! { + pub struct Module for enum Call + where origin: ::Origin, system=system + { + #[weight = 0] + pub fn fail(_origin) -> frame_support::dispatch::DispatchResult { + Err(Error::::Something.into()) + } + } + } + + #[derive(Clone, PartialEq, Eq, Debug, codec::Encode, codec::Decode, TypeInfo)] + pub struct Origin; + + frame_support::decl_event! { + pub enum Event { + A, + } + } + + frame_support::decl_error! { + pub enum Error for Module { + Something + } + } + + frame_support::decl_storage! { + trait Store for Module as Module {} + add_extra_genesis { + build(|_config| {}) + } + } + } +} + +pub mod module { + use super::*; + + pub trait Config: system::Config {} + + frame_support::decl_module! { + pub struct Module for enum Call + where origin: ::Origin, system=system + { + #[weight = 0] + pub fn fail(_origin) -> frame_support::dispatch::DispatchResult { + Err(Error::::Something.into()) + } + #[weight = 0] + pub fn aux_1(_origin, #[compact] _data: u32) -> frame_support::dispatch::DispatchResult { + unreachable!() + } + #[weight = 0] + pub fn aux_2(_origin, _data: i32, #[compact] _data2: u32) -> frame_support::dispatch::DispatchResult { + unreachable!() + } + #[weight = 0] + fn aux_3(_origin, _data: i32, _data2: String) -> frame_support::dispatch::DispatchResult { + unreachable!() + } + #[weight = 3] + fn aux_4(_origin) -> frame_support::dispatch::DispatchResult { unreachable!() } + #[weight = (5, frame_support::weights::DispatchClass::Operational)] + fn operational(_origin) { unreachable!() } + } + } + + #[derive(Clone, PartialEq, Eq, Debug, codec::Encode, codec::Decode, TypeInfo)] + pub struct Origin(pub core::marker::PhantomData); + + frame_support::decl_event! { + pub enum Event { + A, + } + } + + frame_support::decl_error! { + pub enum Error for Module { + Something + } + } + + frame_support::decl_storage! { + trait Store for Module as Module {} + add_extra_genesis { + build(|_config| {}) + } + } +} + +impl nested::module::Config for RuntimeOriginTest {} +impl module::Config for RuntimeOriginTest {} + +pub struct BaseCallFilter; +impl Contains for BaseCallFilter { + fn contains(c: &Call) -> bool { + match c { + Call::NestedModule(_) => true, + _ => false, + } + } +} + +impl system::Config for RuntimeOriginTest { + type BaseCallFilter = BaseCallFilter; + type Hash = H256; + type Origin = Origin; + type BlockNumber = BlockNumber; + type AccountId = u32; + type Event = Event; + type PalletInfo = PalletInfo; + type Call = Call; + type DbWeight = (); +} + +frame_support::construct_runtime!( + pub enum RuntimeOriginTest where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: system::{Pallet, Event, Origin}, + NestedModule: nested::module::{Pallet, Origin, Call}, + Module: module::{Pallet, Origin, Call}, + } +); + +pub type Signature = sr25519::Signature; +pub type BlockNumber = u64; +pub type Header = generic::Header; +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; +pub type Block = generic::Block; + +#[test] +fn origin_default_filter() { + let accepted_call = nested::module::Call::fail {}.into(); + let rejected_call = module::Call::fail {}.into(); + + assert_eq!(Origin::root().filter_call(&accepted_call), true); + assert_eq!(Origin::root().filter_call(&rejected_call), true); + assert_eq!(Origin::none().filter_call(&accepted_call), true); + assert_eq!(Origin::none().filter_call(&rejected_call), false); + assert_eq!(Origin::signed(0).filter_call(&accepted_call), true); + assert_eq!(Origin::signed(0).filter_call(&rejected_call), false); + assert_eq!(Origin::from(Some(0)).filter_call(&accepted_call), true); + assert_eq!(Origin::from(Some(0)).filter_call(&rejected_call), false); + assert_eq!(Origin::from(None).filter_call(&accepted_call), true); + assert_eq!(Origin::from(None).filter_call(&rejected_call), false); + assert_eq!(Origin::from(nested::module::Origin).filter_call(&accepted_call), true); + assert_eq!(Origin::from(nested::module::Origin).filter_call(&rejected_call), false); + + let mut origin = Origin::from(Some(0)); + origin.add_filter(|c| matches!(c, Call::Module(_))); + assert_eq!(origin.filter_call(&accepted_call), false); + assert_eq!(origin.filter_call(&rejected_call), false); + + // Now test for root origin and filters: + let mut origin = Origin::from(Some(0)); + origin.set_caller_from(Origin::root()); + assert!(matches!(origin.caller, OriginCaller::system(system::RawOrigin::Root))); + + // Root origin bypass all filter. + assert_eq!(origin.filter_call(&accepted_call), true); + assert_eq!(origin.filter_call(&rejected_call), true); + + origin.set_caller_from(Origin::from(Some(0))); + + // Back to another signed origin, the filtered are now effective again + assert_eq!(origin.filter_call(&accepted_call), true); + assert_eq!(origin.filter_call(&rejected_call), false); + + origin.set_caller_from(Origin::root()); + origin.reset_filter(); + + // Root origin bypass all filter, even when they are reset. + assert_eq!(origin.filter_call(&accepted_call), true); + assert_eq!(origin.filter_call(&rejected_call), true); +} diff --git a/frame/support/test/tests/pallet.rs b/frame/support/test/tests/pallet.rs index 451cb2e7b889e..83f6a722f93aa 100644 --- a/frame/support/test/tests/pallet.rs +++ b/frame/support/test/tests/pallet.rs @@ -20,7 +20,7 @@ use frame_support::{ storage::unhashed, traits::{ ConstU32, GetCallName, GetStorageVersion, OnFinalize, OnGenesis, OnInitialize, - OnRuntimeUpgrade, PalletInfoAccess, StorageVersion, + OnRuntimeUpgrade, PalletError, PalletInfoAccess, StorageVersion, }, weights::{DispatchClass, DispatchInfo, GetDispatchInfo, Pays, RuntimeDbWeight}, }; @@ -229,9 +229,14 @@ pub mod pallet { } #[pallet::error] + #[derive(PartialEq, Eq)] pub enum Error { /// doc comment put into metadata InsufficientProposersBalance, + Code(u8), + #[codec(skip)] + Skipped(u128), + CompactU8(#[codec(compact)] u8), } #[pallet::event] @@ -656,10 +661,11 @@ fn error_expand() { DispatchError::from(pallet::Error::::InsufficientProposersBalance), DispatchError::Module(ModuleError { index: 1, - error: 0, + error: [0, 0, 0, 0], message: Some("InsufficientProposersBalance") }), ); + assert_eq!( as PalletError>::MAX_ENCODED_SIZE, 3); } #[test] diff --git a/frame/support/test/tests/pallet_instance.rs b/frame/support/test/tests/pallet_instance.rs index 30b9bcda88d1e..118794e2fa20a 100644 --- a/frame/support/test/tests/pallet_instance.rs +++ b/frame/support/test/tests/pallet_instance.rs @@ -343,7 +343,7 @@ fn error_expand() { DispatchError::from(pallet::Error::::InsufficientProposersBalance), DispatchError::Module(ModuleError { index: 1, - error: 0, + error: [0; 4], message: Some("InsufficientProposersBalance") }), ); @@ -364,7 +364,7 @@ fn error_expand() { ), DispatchError::Module(ModuleError { index: 2, - error: 0, + error: [0; 4], message: Some("InsufficientProposersBalance") }), ); diff --git a/frame/support/test/tests/pallet_ui/error_no_fieldless.rs b/frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.rs similarity index 50% rename from frame/support/test/tests/pallet_ui/error_no_fieldless.rs rename to frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.rs index c9d444d6f90dd..254d65866774f 100644 --- a/frame/support/test/tests/pallet_ui/error_no_fieldless.rs +++ b/frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.rs @@ -1,25 +1,19 @@ #[frame_support::pallet] mod pallet { - use frame_support::pallet_prelude::Hooks; - use frame_system::pallet_prelude::BlockNumberFor; - #[pallet::config] pub trait Config: frame_system::Config {} #[pallet::pallet] pub struct Pallet(core::marker::PhantomData); - #[pallet::hooks] - impl Hooks> for Pallet {} - - #[pallet::call] - impl Pallet {} - #[pallet::error] pub enum Error { - U8(u8), + CustomError(crate::MyError), } } +#[derive(scale_info::TypeInfo, codec::Encode, codec::Decode)] +enum MyError {} + fn main() { } diff --git a/frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.stderr b/frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.stderr new file mode 100644 index 0000000000000..2a8149e309ac1 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.stderr @@ -0,0 +1,12 @@ +error[E0277]: the trait bound `MyError: PalletError` is not satisfied + --> tests/pallet_ui/error_does_not_derive_pallet_error.rs:1:1 + | +1 | #[frame_support::pallet] + | ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `PalletError` is not implemented for `MyError` + | +note: required by `MAX_ENCODED_SIZE` + --> $WORKSPACE/frame/support/src/traits/error.rs + | + | const MAX_ENCODED_SIZE: usize; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the derive macro `frame_support::PalletError` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/frame/support/test/tests/pallet_ui/error_no_fieldless.stderr b/frame/support/test/tests/pallet_ui/error_no_fieldless.stderr deleted file mode 100644 index 1d69fbeff9aac..0000000000000 --- a/frame/support/test/tests/pallet_ui/error_no_fieldless.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: Invalid pallet::error, unexpected fields, must be `Unit` - --> $DIR/error_no_fieldless.rs:20:5 - | -20 | U8(u8), - | ^^^^ diff --git a/frame/support/test/tests/pallet_ui/pass/error_nested_types.rs b/frame/support/test/tests/pallet_ui/pass/error_nested_types.rs new file mode 100644 index 0000000000000..1b6f584af23b9 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/pass/error_nested_types.rs @@ -0,0 +1,41 @@ +use codec::{Decode, Encode}; +use frame_support::PalletError; + +#[frame_support::pallet] +mod pallet { + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::error] + pub enum Error { + CustomError(crate::MyError), + } +} + +#[derive(Encode, Decode, PalletError, scale_info::TypeInfo)] +pub enum MyError { + Foo, + Bar, + Baz(NestedError), + Struct(MyStruct), + Wrapper(Wrapper), +} + +#[derive(Encode, Decode, PalletError, scale_info::TypeInfo)] +pub enum NestedError { + Quux +} + +#[derive(Encode, Decode, PalletError, scale_info::TypeInfo)] +pub struct MyStruct { + field: u8, +} + +#[derive(Encode, Decode, PalletError, scale_info::TypeInfo)] +pub struct Wrapper(bool); + +fn main() { +} diff --git a/frame/utility/src/tests.rs b/frame/utility/src/tests.rs index 9a1e11f54d6e3..44b07f70db14c 100644 --- a/frame/utility/src/tests.rs +++ b/frame/utility/src/tests.rs @@ -38,7 +38,6 @@ use sp_runtime::{ // example module to test behaviors. #[frame_support::pallet] pub mod example { - use super::*; use frame_support::{dispatch::WithPostDispatchInfo, pallet_prelude::*}; use frame_system::pallet_prelude::*; diff --git a/primitives/block-builder/src/lib.rs b/primitives/block-builder/src/lib.rs index 229f115c6667f..1b74c27b7ae43 100644 --- a/primitives/block-builder/src/lib.rs +++ b/primitives/block-builder/src/lib.rs @@ -20,11 +20,14 @@ #![cfg_attr(not(feature = "std"), no_std)] use sp_inherents::{CheckInherentsResult, InherentData}; -use sp_runtime::{traits::Block as BlockT, ApplyExtrinsicResult}; +use sp_runtime::{ + legacy::byte_sized_error::ApplyExtrinsicResult as ApplyExtrinsicResultBeforeV6, + traits::Block as BlockT, ApplyExtrinsicResult, +}; sp_api::decl_runtime_apis! { /// The `BlockBuilder` api trait that provides the required functionality for building a block. - #[api_version(5)] + #[api_version(6)] pub trait BlockBuilder { /// Apply the given extrinsic. /// @@ -32,6 +35,9 @@ sp_api::decl_runtime_apis! { /// this block or not. fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResult; + #[changed_in(6)] + fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResultBeforeV6; + /// Finish the current block. #[renamed("finalise_block", 3)] fn finalize_block() -> ::Header; diff --git a/primitives/runtime/src/legacy.rs b/primitives/runtime/src/legacy.rs new file mode 100644 index 0000000000000..7bc7c88a7e10d --- /dev/null +++ b/primitives/runtime/src/legacy.rs @@ -0,0 +1,20 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Runtime types that existed in old API versions. + +pub mod byte_sized_error; diff --git a/primitives/runtime/src/legacy/byte_sized_error.rs b/primitives/runtime/src/legacy/byte_sized_error.rs new file mode 100644 index 0000000000000..049abff69ff1a --- /dev/null +++ b/primitives/runtime/src/legacy/byte_sized_error.rs @@ -0,0 +1,100 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Runtime types that existed prior to BlockBuilder API version 6. + +use crate::{ArithmeticError, TokenError}; +use codec::{Decode, Encode}; +use scale_info::TypeInfo; +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; + +/// [`ModuleError`] type definition before BlockBuilder API version 6. +#[derive(Eq, Clone, Copy, Encode, Decode, Debug, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct ModuleError { + /// Module index, matching the metadata module index. + pub index: u8, + /// Module specific error value. + pub error: u8, + /// Optional error message. + #[codec(skip)] + #[cfg_attr(feature = "std", serde(skip_deserializing))] + pub message: Option<&'static str>, +} + +impl PartialEq for ModuleError { + fn eq(&self, other: &Self) -> bool { + (self.index == other.index) && (self.error == other.error) + } +} + +/// [`DispatchError`] type definition before BlockBuilder API version 6. +#[derive(Eq, Clone, Copy, Encode, Decode, Debug, TypeInfo, PartialEq)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub enum DispatchError { + /// Some error occurred. + Other( + #[codec(skip)] + #[cfg_attr(feature = "std", serde(skip_deserializing))] + &'static str, + ), + /// Failed to lookup some data. + CannotLookup, + /// A bad origin. + BadOrigin, + /// A custom error in a module. + Module(ModuleError), + /// At least one consumer is remaining so the account cannot be destroyed. + ConsumerRemaining, + /// There are no providers so the account cannot be created. + NoProviders, + /// There are too many consumers so the account cannot be created. + TooManyConsumers, + /// An error to do with tokens. + Token(TokenError), + /// An arithmetic error. + Arithmetic(ArithmeticError), +} + +/// [`DispatchOutcome`] type definition before BlockBuilder API version 6. +pub type DispatchOutcome = Result<(), DispatchError>; + +/// [`ApplyExtrinsicResult`] type definition before BlockBuilder API version 6. +pub type ApplyExtrinsicResult = + Result; + +/// Convert the legacy `ApplyExtrinsicResult` type to the latest version. +pub fn convert_to_latest(old: ApplyExtrinsicResult) -> crate::ApplyExtrinsicResult { + old.map(|outcome| { + outcome.map_err(|e| match e { + DispatchError::Other(s) => crate::DispatchError::Other(s), + DispatchError::CannotLookup => crate::DispatchError::CannotLookup, + DispatchError::BadOrigin => crate::DispatchError::BadOrigin, + DispatchError::Module(err) => crate::DispatchError::Module(crate::ModuleError { + index: err.index, + error: [err.error, 0, 0, 0], + message: err.message, + }), + DispatchError::ConsumerRemaining => crate::DispatchError::ConsumerRemaining, + DispatchError::NoProviders => crate::DispatchError::NoProviders, + DispatchError::TooManyConsumers => crate::DispatchError::TooManyConsumers, + DispatchError::Token(err) => crate::DispatchError::Token(err), + DispatchError::Arithmetic(err) => crate::DispatchError::Arithmetic(err), + }) + }) +} diff --git a/primitives/runtime/src/lib.rs b/primitives/runtime/src/lib.rs index a428da59f6a0d..337fac5812aed 100644 --- a/primitives/runtime/src/lib.rs +++ b/primitives/runtime/src/lib.rs @@ -57,6 +57,7 @@ use scale_info::TypeInfo; pub mod curve; pub mod generic; +pub mod legacy; mod multiaddress; pub mod offchain; pub mod runtime_logger; @@ -97,6 +98,10 @@ pub use sp_arithmetic::{ pub use either::Either; +/// The number of bytes of the module-specific `error` field defined in [`ModuleError`]. +/// In FRAME, this is the maximum encoded size of a pallet error type. +pub const MAX_MODULE_ERROR_ENCODED_SIZE: usize = 4; + /// An abstraction over justification for a block's validity under a consensus algorithm. /// /// Essentially a finality proof. The exact formulation will vary between consensus @@ -468,7 +473,7 @@ pub struct ModuleError { /// Module index, matching the metadata module index. pub index: u8, /// Module specific error value. - pub error: u8, + pub error: [u8; MAX_MODULE_ERROR_ENCODED_SIZE], /// Optional error message. #[codec(skip)] #[cfg_attr(feature = "std", serde(skip_deserializing))] @@ -922,15 +927,15 @@ mod tests { fn dispatch_error_encoding() { let error = DispatchError::Module(ModuleError { index: 1, - error: 2, + error: [2, 0, 0, 0], message: Some("error message"), }); let encoded = error.encode(); let decoded = DispatchError::decode(&mut &encoded[..]).unwrap(); - assert_eq!(encoded, vec![3, 1, 2]); + assert_eq!(encoded, vec![3, 1, 2, 0, 0, 0]); assert_eq!( decoded, - DispatchError::Module(ModuleError { index: 1, error: 2, message: None }) + DispatchError::Module(ModuleError { index: 1, error: [2, 0, 0, 0], message: None }) ); } @@ -943,9 +948,9 @@ mod tests { Other("bar"), CannotLookup, BadOrigin, - Module(ModuleError { index: 1, error: 1, message: None }), - Module(ModuleError { index: 1, error: 2, message: None }), - Module(ModuleError { index: 2, error: 1, message: None }), + Module(ModuleError { index: 1, error: [1, 0, 0, 0], message: None }), + Module(ModuleError { index: 1, error: [2, 0, 0, 0], message: None }), + Module(ModuleError { index: 2, error: [1, 0, 0, 0], message: None }), ConsumerRemaining, NoProviders, Token(TokenError::NoFunds), @@ -970,8 +975,8 @@ mod tests { // Ignores `message` field in `Module` variant. assert_eq!( - Module(ModuleError { index: 1, error: 1, message: Some("foo") }), - Module(ModuleError { index: 1, error: 1, message: None }), + Module(ModuleError { index: 1, error: [1, 0, 0, 0], message: Some("foo") }), + Module(ModuleError { index: 1, error: [1, 0, 0, 0], message: None }), ); } diff --git a/primitives/runtime/src/traits.rs b/primitives/runtime/src/traits.rs index 1cea7c4e805c1..ba4ca790a9198 100644 --- a/primitives/runtime/src/traits.rs +++ b/primitives/runtime/src/traits.rs @@ -1554,6 +1554,12 @@ impl Printable for &[u8] { } } +impl Printable for [u8; N] { + fn print(&self) { + sp_io::misc::print_hex(&self[..]); + } +} + impl Printable for &str { fn print(&self) { sp_io::misc::print_utf8(self.as_bytes()); diff --git a/utils/frame/rpc/system/src/lib.rs b/utils/frame/rpc/system/src/lib.rs index eb1b258c97ec6..0eae4d061afbb 100644 --- a/utils/frame/rpc/system/src/lib.rs +++ b/utils/frame/rpc/system/src/lib.rs @@ -25,10 +25,11 @@ use jsonrpc_core::{Error as RpcError, ErrorCode}; use jsonrpc_derive::rpc; use sc_rpc_api::DenyUnsafe; use sc_transaction_pool_api::{InPoolTransaction, TransactionPool}; +use sp_api::ApiExt; use sp_block_builder::BlockBuilder; use sp_blockchain::HeaderBackend; use sp_core::{hexdisplay::HexDisplay, Bytes}; -use sp_runtime::{generic::BlockId, traits}; +use sp_runtime::{generic::BlockId, legacy, traits}; pub use self::gen_client::Client as SystemClient; pub use frame_system_rpc_runtime_api::AccountNonceApi; @@ -135,14 +136,40 @@ where .map_err(|e| RpcError { code: ErrorCode::ServerError(Error::DecodeError.into()), message: "Unable to dry run extrinsic.".into(), - data: Some(format!("{:?}", e).into()), + data: Some(e.to_string().into()), })?; - let result = api.apply_extrinsic(&at, uxt).map_err(|e| RpcError { - code: ErrorCode::ServerError(Error::RuntimeError.into()), - message: "Unable to dry run extrinsic.".into(), - data: Some(e.to_string().into()), - })?; + let api_version = api + .api_version::>(&at) + .map_err(|e| RpcError { + code: ErrorCode::ServerError(Error::RuntimeError.into()), + message: "Unable to dry run extrinsic.".into(), + data: Some(e.to_string().into()), + })? + .ok_or_else(|| RpcError { + code: ErrorCode::ServerError(Error::RuntimeError.into()), + message: "Unable to dry run extrinsic.".into(), + data: Some( + format!("Could not find `BlockBuilder` api for block `{:?}`.", at).into(), + ), + })?; + + let result = if api_version < 6 { + #[allow(deprecated)] + api.apply_extrinsic_before_version_6(&at, uxt) + .map(legacy::byte_sized_error::convert_to_latest) + .map_err(|e| RpcError { + code: ErrorCode::ServerError(Error::RuntimeError.into()), + message: "Unable to dry run extrinsic.".into(), + data: Some(e.to_string().into()), + })? + } else { + api.apply_extrinsic(&at, uxt).map_err(|e| RpcError { + code: ErrorCode::ServerError(Error::RuntimeError.into()), + message: "Unable to dry run extrinsic.".into(), + data: Some(e.to_string().into()), + })? + }; Ok(Encode::encode(&result).into()) }; From a59ccd174949cf10ca41b1c9eef2c7a626b44b00 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 24 Mar 2022 09:51:55 +0100 Subject: [PATCH 054/484] BABE's revert procedure (#11022) * First rough draft for BABE revert * Proper babe revert test * Cleanup * Test trivial cleanup * Fix to make clippy happy * Check polkadot companion * Check cumulus companion * Remove babe's blocks weight on revert * Handle "empty" blockchain edge case * Run companions * Simplify the filter predicate * Saturating sub is not required * Run pipeline * Run pipeline again... --- bin/node-template/node/src/command.rs | 2 +- bin/node/cli/src/command.rs | 8 ++- client/cli/src/commands/revert_cmd.rs | 16 ++++- client/consensus/babe/src/lib.rs | 79 ++++++++++++++++++++- client/consensus/babe/src/tests.rs | 82 ++++++++++++++++++++++ client/consensus/epochs/src/lib.rs | 51 +++++++++++--- utils/fork-tree/src/lib.rs | 98 +++++++++++++++++++++++++-- 7 files changed, 314 insertions(+), 22 deletions(-) diff --git a/bin/node-template/node/src/command.rs b/bin/node-template/node/src/command.rs index 72c7a75b387bb..66ee9fe45a55c 100644 --- a/bin/node-template/node/src/command.rs +++ b/bin/node-template/node/src/command.rs @@ -95,7 +95,7 @@ pub fn run() -> sc_cli::Result<()> { runner.async_run(|config| { let PartialComponents { client, task_manager, backend, .. } = service::new_partial(&config)?; - Ok((cmd.run(client, backend), task_manager)) + Ok((cmd.run(client, backend, None), task_manager)) }) }, Some(Subcommand::Benchmark(cmd)) => diff --git a/bin/node/cli/src/command.rs b/bin/node/cli/src/command.rs index e208e324ee2aa..3c2039fde4757 100644 --- a/bin/node/cli/src/command.rs +++ b/bin/node/cli/src/command.rs @@ -168,7 +168,13 @@ pub fn run() -> Result<()> { let runner = cli.create_runner(cmd)?; runner.async_run(|config| { let PartialComponents { client, task_manager, backend, .. } = new_partial(&config)?; - Ok((cmd.run(client, backend), task_manager)) + let revert_aux = Box::new(|client, backend, blocks| { + sc_consensus_babe::revert(client, backend, blocks)?; + // TODO: grandpa revert + Ok(()) + }); + + Ok((cmd.run(client, backend, Some(revert_aux)), task_manager)) }) }, #[cfg(feature = "try-runtime")] diff --git a/client/cli/src/commands/revert_cmd.rs b/client/cli/src/commands/revert_cmd.rs index c207d198d5a27..f65e348b37b89 100644 --- a/client/cli/src/commands/revert_cmd.rs +++ b/client/cli/src/commands/revert_cmd.rs @@ -24,7 +24,7 @@ use crate::{ use clap::Parser; use sc_client_api::{Backend, UsageProvider}; use sc_service::chain_ops::revert_chain; -use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor}; use std::{fmt::Debug, str::FromStr, sync::Arc}; /// The `revert` command used revert the chain to a previous state. @@ -43,9 +43,18 @@ pub struct RevertCmd { pub pruning_params: PruningParams, } +/// Revert handler for auxiliary data (e.g. consensus). +type AuxRevertHandler = + Box, Arc, NumberFor) -> error::Result<()>>; + impl RevertCmd { /// Run the revert command - pub async fn run(&self, client: Arc, backend: Arc) -> error::Result<()> + pub async fn run( + &self, + client: Arc, + backend: Arc, + aux_revert: Option>, + ) -> error::Result<()> where B: BlockT, BA: Backend, @@ -53,6 +62,9 @@ impl RevertCmd { <<::Header as HeaderT>::Number as FromStr>::Err: Debug, { let blocks = self.num.parse()?; + if let Some(aux_revert) = aux_revert { + aux_revert(client.clone(), backend.clone(), blocks)?; + } revert_chain(client, backend, blocks)?; Ok(()) diff --git a/client/consensus/babe/src/lib.rs b/client/consensus/babe/src/lib.rs index 442dbab77e120..4f3139a013d4b 100644 --- a/client/consensus/babe/src/lib.rs +++ b/client/consensus/babe/src/lib.rs @@ -92,8 +92,8 @@ use retain_mut::RetainMut; use schnorrkel::SignatureError; use sc_client_api::{ - backend::AuxStore, AuxDataOperations, BlockchainEvents, FinalityNotification, PreCommitActions, - ProvideUncles, UsageProvider, + backend::AuxStore, AuxDataOperations, Backend as BackendT, BlockchainEvents, + FinalityNotification, PreCommitActions, ProvideUncles, UsageProvider, }; use sc_consensus::{ block_import::{ @@ -113,7 +113,9 @@ use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_DEBUG, CONSENSUS_TRACE} use sp_api::{ApiExt, ProvideRuntimeApi}; use sp_application_crypto::AppKey; use sp_block_builder::BlockBuilder as BlockBuilderApi; -use sp_blockchain::{Error as ClientError, HeaderBackend, HeaderMetadata, Result as ClientResult}; +use sp_blockchain::{ + Backend as _, Error as ClientError, HeaderBackend, HeaderMetadata, Result as ClientResult, +}; use sp_consensus::{ BlockOrigin, CacheKeyId, CanAuthorWith, Environment, Error as ConsensusError, Proposer, SelectChain, @@ -1830,3 +1832,74 @@ where Ok(BasicQueue::new(verifier, Box::new(block_import), justification_import, spawner, registry)) } + +/// Reverts aux data. +pub fn revert( + client: Arc, + backend: Arc, + blocks: NumberFor, +) -> ClientResult<()> +where + Block: BlockT, + Client: AuxStore + + HeaderMetadata + + HeaderBackend + + ProvideRuntimeApi + + UsageProvider, + Client::Api: BabeApi, + Backend: BackendT, +{ + let best_number = client.info().best_number; + let finalized = client.info().finalized_number; + let revertible = blocks.min(best_number - finalized); + + let number = best_number - revertible; + let hash = client + .block_hash_from_id(&BlockId::Number(number))? + .ok_or(ClientError::Backend(format!( + "Unexpected hash lookup failure for block number: {}", + number + )))?; + + // Revert epoch changes tree. + + let config = Config::get(&*client)?; + let epoch_changes = + aux_schema::load_epoch_changes::(&*client, config.genesis_config())?; + let mut epoch_changes = epoch_changes.shared_data(); + + if number == Zero::zero() { + // Special case, no epoch changes data were present on genesis. + *epoch_changes = EpochChangesFor::::default(); + } else { + epoch_changes.revert(descendent_query(&*client), hash, number); + } + + // Remove block weights added after the revert point. + + let mut weight_keys = HashSet::with_capacity(revertible.saturated_into()); + let leaves = backend.blockchain().leaves()?.into_iter().filter(|&leaf| { + sp_blockchain::tree_route(&*client, hash, leaf) + .map(|route| route.retracted().is_empty()) + .unwrap_or_default() + }); + for leaf in leaves { + let mut hash = leaf; + // Insert parent after parent until we don't hit an already processed + // branch or we reach a direct child of the rollback point. + while weight_keys.insert(aux_schema::block_weight_key(hash)) { + let meta = client.header_metadata(hash)?; + if meta.number <= number + One::one() { + // We've reached a child of the revert point, stop here. + break + } + hash = client.header_metadata(hash)?.parent; + } + } + let weight_keys: Vec<_> = weight_keys.iter().map(|val| val.as_slice()).collect(); + + // Write epoch changes and remove weights in one shot. + aux_schema::write_epoch_changes::(&epoch_changes, |values| { + client.insert_aux(values, weight_keys.iter()) + }) +} diff --git a/client/consensus/babe/src/tests.rs b/client/consensus/babe/src/tests.rs index d2de05bc91952..080387c88655c 100644 --- a/client/consensus/babe/src/tests.rs +++ b/client/consensus/babe/src/tests.rs @@ -735,6 +735,88 @@ fn importing_block_one_sets_genesis_epoch() { assert_eq!(epoch_for_second_block, genesis_epoch); } +#[test] +fn revert_prunes_epoch_changes_and_removes_weights() { + let mut net = BabeTestNet::new(1); + + let peer = net.peer(0); + let data = peer.data.as_ref().expect("babe link set up during initialization"); + + let client = peer.client().as_client(); + let backend = peer.client().as_backend(); + let mut block_import = data.block_import.lock().take().expect("import set up during init"); + let epoch_changes = data.link.epoch_changes.clone(); + + let mut proposer_factory = DummyFactory { + client: client.clone(), + config: data.link.config.clone(), + epoch_changes: data.link.epoch_changes.clone(), + mutator: Arc::new(|_, _| ()), + }; + + let mut propose_and_import_blocks_wrap = |parent_id, n| { + propose_and_import_blocks(&client, &mut proposer_factory, &mut block_import, parent_id, n) + }; + + // Test scenario. + // Information for epoch 19 is produced on three different forks at block #13. + // One branch starts before the revert point (epoch data should be maintained). + // One branch starts after the revert point (epoch data should be removed). + // + // *----------------- F(#13) --#18 < fork #2 + // / + // A(#1) ---- B(#7) ----#8----+-----#12----- C(#13) ---- D(#19) ------#21 < canon + // \ ^ \ + // \ revert *---- G(#13) ---- H(#19) ---#20 < fork #3 + // \ to #10 + // *-----E(#7)---#11 < fork #1 + let canon = propose_and_import_blocks_wrap(BlockId::Number(0), 21); + let fork1 = propose_and_import_blocks_wrap(BlockId::Hash(canon[0]), 10); + let fork2 = propose_and_import_blocks_wrap(BlockId::Hash(canon[7]), 10); + let fork3 = propose_and_import_blocks_wrap(BlockId::Hash(canon[11]), 8); + + // We should be tracking a total of 9 epochs in the fork tree + assert_eq!(epoch_changes.shared_data().tree().iter().count(), 8); + // And only one root + assert_eq!(epoch_changes.shared_data().tree().roots().count(), 1); + + // Revert canon chain to block #10 (best(21) - 11) + revert(client.clone(), backend, 11).expect("revert should work for baked test scenario"); + + // Load and check epoch changes. + + let actual_nodes = aux_schema::load_epoch_changes::( + &*client, + data.link.config.genesis_config(), + ) + .expect("load epoch changes") + .shared_data() + .tree() + .iter() + .map(|(h, _, _)| *h) + .collect::>(); + + let expected_nodes = vec![ + canon[0], // A + canon[6], // B + fork2[4], // F + fork1[5], // E + ]; + + assert_eq!(actual_nodes, expected_nodes); + + let weight_data_check = |hashes: &[Hash], expected: bool| { + hashes.iter().all(|hash| { + aux_schema::load_block_weight(&*client, hash).unwrap().is_some() == expected + }) + }; + assert!(weight_data_check(&canon[..10], true)); + assert!(weight_data_check(&canon[10..], false)); + assert!(weight_data_check(&fork1, true)); + assert!(weight_data_check(&fork2, true)); + assert!(weight_data_check(&fork3, false)); +} + #[test] fn importing_epoch_change_block_prunes_tree() { let mut net = BabeTestNet::new(1); diff --git a/client/consensus/epochs/src/lib.rs b/client/consensus/epochs/src/lib.rs index b380d8ed54904..90081bf9af442 100644 --- a/client/consensus/epochs/src/lib.rs +++ b/client/consensus/epochs/src/lib.rs @@ -21,7 +21,7 @@ pub mod migration; use codec::{Decode, Encode}; -use fork_tree::ForkTree; +use fork_tree::{FilterAction, ForkTree}; use sc_client_api::utils::is_descendent_of; use sp_blockchain::{Error as ClientError, HeaderBackend, HeaderMetadata}; use sp_runtime::traits::{Block as BlockT, NumberFor, One, Zero}; @@ -660,15 +660,6 @@ where parent_number: Number, slot: E::Slot, ) -> Result>, fork_tree::Error> { - // find_node_where will give you the node in the fork-tree which is an ancestor - // of the `parent_hash` by default. if the last epoch was signalled at the parent_hash, - // then it won't be returned. we need to create a new fake chain head hash which - // "descends" from our parent-hash. - let fake_head_hash = fake_head_hash(parent_hash); - - let is_descendent_of = - descendent_of_builder.build_is_descendent_of(Some((fake_head_hash, *parent_hash))); - if parent_number == Zero::zero() { // need to insert the genesis epoch. return Ok(Some(ViableEpochDescriptor::UnimportedGenesis(slot))) @@ -683,6 +674,15 @@ where } } + // find_node_where will give you the node in the fork-tree which is an ancestor + // of the `parent_hash` by default. if the last epoch was signalled at the parent_hash, + // then it won't be returned. we need to create a new fake chain head hash which + // "descends" from our parent-hash. + let fake_head_hash = fake_head_hash(parent_hash); + + let is_descendent_of = + descendent_of_builder.build_is_descendent_of(Some((fake_head_hash, *parent_hash))); + // We want to find the deepest node in the tree which is an ancestor // of our block and where the start slot of the epoch was before the // slot of our block. The genesis special-case doesn't need to look @@ -798,6 +798,37 @@ where }); self.epochs.insert((hash, number), persisted); } + + /// Revert to a specified block given its `hash` and `number`. + /// This removes all the epoch changes information that were announced by + /// all the given block descendents. + pub fn revert>( + &mut self, + descendent_of_builder: D, + hash: Hash, + number: Number, + ) { + let is_descendent_of = descendent_of_builder.build_is_descendent_of(None); + + let filter = |node_hash: &Hash, node_num: &Number, _: &PersistedEpochHeader| { + if number >= *node_num && + (is_descendent_of(node_hash, &hash).unwrap_or_default() || *node_hash == hash) + { + // Continue the search in this subtree. + FilterAction::KeepNode + } else if number < *node_num && is_descendent_of(&hash, node_hash).unwrap_or_default() { + // Found a node to be removed. + FilterAction::Remove + } else { + // Not a parent or child of the one we're looking for, stop processing this branch. + FilterAction::KeepTree + } + }; + + self.inner.drain_filter(filter).for_each(|(h, n, _)| { + self.epochs.remove(&(h, n)); + }); + } } /// Type alias to produce the epoch-changes tree from a block type. diff --git a/utils/fork-tree/src/lib.rs b/utils/fork-tree/src/lib.rs index a718ff26213e4..1d9b39f7dc04b 100644 --- a/utils/fork-tree/src/lib.rs +++ b/utils/fork-tree/src/lib.rs @@ -69,6 +69,17 @@ pub enum FinalizationResult { Unchanged, } +/// Filtering action. +#[derive(Debug, PartialEq)] +pub enum FilterAction { + /// Remove the node and its subtree. + Remove, + /// Maintain the node. + KeepNode, + /// Maintain the node and its subtree. + KeepTree, +} + /// A tree data structure that stores several nodes across multiple branches. /// Top-level branches are called roots. The tree has functionality for /// finalizing nodes, which means that that node is traversed, and all competing @@ -624,6 +635,29 @@ where (None, false) => Ok(FinalizationResult::Unchanged), } } + + /// Remove from the tree some nodes (and their subtrees) using a `filter` predicate. + /// The `filter` is called over tree nodes and returns a filter action: + /// - `Remove` if the node and its subtree should be removed; + /// - `KeepNode` if we should maintain the node and keep processing the tree. + /// - `KeepTree` if we should maintain the node and its entire subtree. + /// An iterator over all the pruned nodes is returned. + pub fn drain_filter(&mut self, mut filter: F) -> impl Iterator + where + F: FnMut(&H, &N, &V) -> FilterAction, + { + let mut removed = Vec::new(); + let mut i = 0; + while i < self.roots.len() { + if self.roots[i].drain_filter(&mut filter, &mut removed) { + removed.push(self.roots.remove(i)); + } else { + i += 1; + } + } + self.rebalance(); + RemovedIterator { stack: removed } + } } // Workaround for: https://github.com/rust-lang/rust/issues/34537 @@ -849,6 +883,34 @@ mod node_implementation { }, } } + + /// Calls a `filter` predicate for the given node. + /// The `filter` is called over tree nodes and returns a filter action: + /// - `Remove` if the node and its subtree should be removed; + /// - `KeepNode` if we should maintain the node and keep processing the tree; + /// - `KeepTree` if we should maintain the node and its entire subtree. + /// Pruned subtrees are added to the `removed` list. + /// Returns a booleans indicateing if this node (and its subtree) should be removed. + pub fn drain_filter(&mut self, filter: &mut F, removed: &mut Vec>) -> bool + where + F: FnMut(&H, &N, &V) -> FilterAction, + { + match filter(&self.hash, &self.number, &self.data) { + FilterAction::KeepNode => { + let mut i = 0; + while i < self.children.len() { + if self.children[i].drain_filter(filter, removed) { + removed.push(self.children.remove(i)); + } else { + i += 1; + } + } + false + }, + FilterAction::KeepTree => false, + FilterAction::Remove => true, + } + } } } @@ -895,6 +957,8 @@ impl Iterator for RemovedIterator { #[cfg(test)] mod test { + use crate::FilterAction; + use super::{Error, FinalizationResult, ForkTree}; #[derive(Debug, PartialEq)] @@ -919,11 +983,11 @@ mod test { // / - G // / / // A - F - H - I - // \ - // - L - M \ - // - O - // \ - // — J - K + // \ \ + // \ - L - M + // \ \ + // \ - O + // - J - K // // (where N is not a part of fork tree) // @@ -1458,4 +1522,28 @@ mod test { ["A", "F", "H", "L", "O", "P", "M", "I", "G", "B", "C", "D", "E", "J", "K"] ); } + + #[test] + fn tree_drain_filter() { + let (mut tree, _) = test_fork_tree(); + + let filter = |h: &&str, _: &u64, _: &()| match *h { + "A" | "B" | "F" | "G" => FilterAction::KeepNode, + "C" => FilterAction::KeepTree, + "H" | "J" => FilterAction::Remove, + _ => panic!("Unexpected filtering for node: {}", *h), + }; + + let removed = tree.drain_filter(filter); + + assert_eq!( + tree.iter().map(|(h, _, _)| *h).collect::>(), + ["A", "B", "C", "D", "E", "F", "G"] + ); + + assert_eq!( + removed.map(|(h, _, _)| h).collect::>(), + ["J", "K", "H", "L", "M", "O", "I"] + ); + } } From 1252e4d5c5ca46ff92526af78a331f2ace3767a5 Mon Sep 17 00:00:00 2001 From: Koute Date: Thu, 24 Mar 2022 22:19:17 +0900 Subject: [PATCH 055/484] Add extra WASM heap pages when precompiling the runtime blob (#11107) * Add extra WASM heap pages when precompiling the runtime blob * Fix compilation * Fix rustdoc * Fix rustdoc for real this time * Fix benches compilation * Improve the builder in `sc-executor-wasmtime`'s tests --- client/executor/benches/bench.rs | 2 +- client/executor/src/wasm_runtime.rs | 2 +- client/executor/wasmtime/src/runtime.rs | 47 ++++----- client/executor/wasmtime/src/tests.rs | 124 ++++++++++++++---------- 4 files changed, 100 insertions(+), 75 deletions(-) diff --git a/client/executor/benches/bench.rs b/client/executor/benches/bench.rs index 20632536571b2..49ea8be50624e 100644 --- a/client/executor/benches/bench.rs +++ b/client/executor/benches/bench.rs @@ -55,11 +55,11 @@ fn initialize(runtime: &[u8], method: Method) -> Arc { sc_executor_wasmtime::create_runtime::( blob, sc_executor_wasmtime::Config { - heap_pages, max_memory_size: None, allow_missing_func_imports, cache_path: None, semantics: sc_executor_wasmtime::Semantics { + extra_heap_pages: heap_pages, fast_instance_reuse, deterministic_stack_limit: None, canonicalize_nans: false, diff --git a/client/executor/src/wasm_runtime.rs b/client/executor/src/wasm_runtime.rs index d996d7b490e88..952130e980874 100644 --- a/client/executor/src/wasm_runtime.rs +++ b/client/executor/src/wasm_runtime.rs @@ -317,11 +317,11 @@ where WasmExecutionMethod::Compiled => sc_executor_wasmtime::create_runtime::( blob, sc_executor_wasmtime::Config { - heap_pages, max_memory_size: None, allow_missing_func_imports, cache_path: cache_path.map(ToOwned::to_owned), semantics: sc_executor_wasmtime::Semantics { + extra_heap_pages: heap_pages, fast_instance_reuse: true, deterministic_stack_limit: None, canonicalize_nans: false, diff --git a/client/executor/wasmtime/src/runtime.rs b/client/executor/wasmtime/src/runtime.rs index acf54e04e07fd..cbe1359d28a6c 100644 --- a/client/executor/wasmtime/src/runtime.rs +++ b/client/executor/wasmtime/src/runtime.rs @@ -414,20 +414,22 @@ pub struct Semantics { /// Configures wasmtime to use multiple threads for compiling. pub parallel_compilation: bool, + + /// The number of extra WASM pages which will be allocated + /// on top of what is requested by the WASM blob itself. + pub extra_heap_pages: u64, } pub struct Config { - /// The number of wasm pages to be mounted after instantiation. - pub heap_pages: u64, - /// The total amount of memory in bytes an instance can request. /// /// If specified, the runtime will be able to allocate only that much of wasm memory. - /// This is the total number and therefore the [`Config::heap_pages`] is accounted for. + /// This is the total number and therefore the [`Semantics::extra_heap_pages`] is accounted + /// for. /// /// That means that the initial number of pages of a linear memory plus the - /// [`Config::heap_pages`] multiplied by the wasm page size (64KiB) should be less than or - /// equal to `max_memory_size`, otherwise the instance won't be created. + /// [`Semantics::extra_heap_pages`] multiplied by the wasm page size (64KiB) should be less + /// than or equal to `max_memory_size`, otherwise the instance won't be created. /// /// Moreover, `memory.grow` will fail (return -1) if the sum of sizes of currently mounted /// and additional pages exceeds `max_memory_size`. @@ -534,21 +536,7 @@ where let (module, snapshot_data) = match code_supply_mode { CodeSupplyMode::Verbatim { blob } => { - let mut blob = instrument(blob, &config.semantics)?; - - // We don't actually need the memory to be imported so we can just convert any memory - // import into an export with impunity. This simplifies our code since `wasmtime` will - // now automatically take care of creating the memory for us, and it also allows us - // to potentially enable `wasmtime`'s instance pooling at a later date. (Imported - // memories are ineligible for pooling.) - blob.convert_memory_import_into_export()?; - blob.add_extra_heap_pages_to_memory_section( - config - .heap_pages - .try_into() - .map_err(|e| WasmError::Other(format!("invalid `heap_pages`: {}", e)))?, - )?; - + let blob = prepare_blob_for_compilation(blob, &config.semantics)?; let serialized_blob = blob.clone().serialize(); let module = wasmtime::Module::new(&engine, &serialized_blob) @@ -587,7 +575,7 @@ where Ok(WasmtimeRuntime { engine, instance_pre: Arc::new(instance_pre), snapshot_data, config }) } -fn instrument( +fn prepare_blob_for_compilation( mut blob: RuntimeBlob, semantics: &Semantics, ) -> std::result::Result { @@ -600,6 +588,19 @@ fn instrument( blob.expose_mutable_globals(); } + // We don't actually need the memory to be imported so we can just convert any memory + // import into an export with impunity. This simplifies our code since `wasmtime` will + // now automatically take care of creating the memory for us, and it also allows us + // to potentially enable `wasmtime`'s instance pooling at a later date. (Imported + // memories are ineligible for pooling.) + blob.convert_memory_import_into_export()?; + blob.add_extra_heap_pages_to_memory_section( + semantics + .extra_heap_pages + .try_into() + .map_err(|e| WasmError::Other(format!("invalid `extra_heap_pages`: {}", e)))?, + )?; + Ok(blob) } @@ -609,7 +610,7 @@ pub fn prepare_runtime_artifact( blob: RuntimeBlob, semantics: &Semantics, ) -> std::result::Result, WasmError> { - let blob = instrument(blob, semantics)?; + let blob = prepare_blob_for_compilation(blob, semantics)?; let engine = Engine::new(&common_config(semantics)?) .map_err(|e| WasmError::Other(format!("cannot create the engine: {}", e)))?; diff --git a/client/executor/wasmtime/src/tests.rs b/client/executor/wasmtime/src/tests.rs index a4ca0959da869..d5b92f2f24a76 100644 --- a/client/executor/wasmtime/src/tests.rs +++ b/client/executor/wasmtime/src/tests.rs @@ -28,8 +28,9 @@ struct RuntimeBuilder { fast_instance_reuse: bool, canonicalize_nans: bool, deterministic_stack: bool, - heap_pages: u64, + extra_heap_pages: u64, max_memory_size: Option, + precompile_runtime: bool, } impl RuntimeBuilder { @@ -41,34 +42,44 @@ impl RuntimeBuilder { fast_instance_reuse: false, canonicalize_nans: false, deterministic_stack: false, - heap_pages: 1024, + extra_heap_pages: 1024, max_memory_size: None, + precompile_runtime: false, } } - fn use_wat(&mut self, code: String) { + fn use_wat(&mut self, code: String) -> &mut Self { self.code = Some(code); + self } - fn canonicalize_nans(&mut self, canonicalize_nans: bool) { + fn canonicalize_nans(&mut self, canonicalize_nans: bool) -> &mut Self { self.canonicalize_nans = canonicalize_nans; + self } - fn deterministic_stack(&mut self, deterministic_stack: bool) { + fn deterministic_stack(&mut self, deterministic_stack: bool) -> &mut Self { self.deterministic_stack = deterministic_stack; + self } - fn max_memory_size(&mut self, max_memory_size: Option) { + fn precompile_runtime(&mut self, precompile_runtime: bool) -> &mut Self { + self.precompile_runtime = precompile_runtime; + self + } + + fn max_memory_size(&mut self, max_memory_size: Option) -> &mut Self { self.max_memory_size = max_memory_size; + self } - fn build(self) -> Arc { + fn build(&mut self) -> Arc { let blob = { let wasm: Vec; let wasm = match self.code { None => wasm_binary_unwrap(), - Some(wat) => { + Some(ref wat) => { wasm = wat::parse_str(wat).expect("wat parsing failed"); &wasm }, @@ -78,27 +89,31 @@ impl RuntimeBuilder { .expect("failed to create a runtime blob out of test runtime") }; - let rt = crate::create_runtime::( - blob, - crate::Config { - heap_pages: self.heap_pages, - max_memory_size: self.max_memory_size, - allow_missing_func_imports: true, - cache_path: None, - semantics: crate::Semantics { - fast_instance_reuse: self.fast_instance_reuse, - deterministic_stack_limit: match self.deterministic_stack { - true => Some(crate::DeterministicStackLimit { - logical_max: 65536, - native_stack_max: 256 * 1024 * 1024, - }), - false => None, - }, - canonicalize_nans: self.canonicalize_nans, - parallel_compilation: true, + let config = crate::Config { + max_memory_size: self.max_memory_size, + allow_missing_func_imports: true, + cache_path: None, + semantics: crate::Semantics { + fast_instance_reuse: self.fast_instance_reuse, + deterministic_stack_limit: match self.deterministic_stack { + true => Some(crate::DeterministicStackLimit { + logical_max: 65536, + native_stack_max: 256 * 1024 * 1024, + }), + false => None, }, + canonicalize_nans: self.canonicalize_nans, + parallel_compilation: true, + extra_heap_pages: self.extra_heap_pages, }, - ) + }; + + let rt = if self.precompile_runtime { + let artifact = crate::prepare_runtime_artifact(blob, &config.semantics).unwrap(); + unsafe { crate::create_runtime_from_artifact::(&artifact, config) } + } else { + crate::create_runtime::(blob, config) + } .expect("cannot create runtime"); Arc::new(rt) as Arc @@ -107,11 +122,7 @@ impl RuntimeBuilder { #[test] fn test_nan_canonicalization() { - let runtime = { - let mut builder = RuntimeBuilder::new_on_demand(); - builder.canonicalize_nans(true); - builder.build() - }; + let runtime = RuntimeBuilder::new_on_demand().canonicalize_nans(true).build(); let mut instance = runtime.new_instance().expect("failed to instantiate a runtime"); @@ -150,12 +161,10 @@ fn test_nan_canonicalization() { fn test_stack_depth_reaching() { const TEST_GUARD_PAGE_SKIP: &str = include_str!("test-guard-page-skip.wat"); - let runtime = { - let mut builder = RuntimeBuilder::new_on_demand(); - builder.use_wat(TEST_GUARD_PAGE_SKIP.to_string()); - builder.deterministic_stack(true); - builder.build() - }; + let runtime = RuntimeBuilder::new_on_demand() + .use_wat(TEST_GUARD_PAGE_SKIP.to_string()) + .deterministic_stack(true) + .build(); let mut instance = runtime.new_instance().expect("failed to instantiate a runtime"); match instance.call_export("test-many-locals", &[]).unwrap_err() { @@ -168,26 +177,36 @@ fn test_stack_depth_reaching() { } #[test] -fn test_max_memory_pages_imported_memory() { - test_max_memory_pages(true); +fn test_max_memory_pages_imported_memory_without_precompilation() { + test_max_memory_pages(true, false); +} + +#[test] +fn test_max_memory_pages_exported_memory_without_precompilation() { + test_max_memory_pages(false, false); } #[test] -fn test_max_memory_pages_exported_memory() { - test_max_memory_pages(false); +fn test_max_memory_pages_imported_memory_with_precompilation() { + test_max_memory_pages(true, true); } -fn test_max_memory_pages(import_memory: bool) { +#[test] +fn test_max_memory_pages_exported_memory_with_precompilation() { + test_max_memory_pages(false, true); +} + +fn test_max_memory_pages(import_memory: bool, precompile_runtime: bool) { fn try_instantiate( max_memory_size: Option, wat: String, + precompile_runtime: bool, ) -> Result<(), Box> { - let runtime = { - let mut builder = RuntimeBuilder::new_on_demand(); - builder.use_wat(wat); - builder.max_memory_size(max_memory_size); - builder.build() - }; + let runtime = RuntimeBuilder::new_on_demand() + .use_wat(wat) + .max_memory_size(max_memory_size) + .precompile_runtime(precompile_runtime) + .build(); let mut instance = runtime.new_instance()?; let _ = instance.call_export("main", &[])?; Ok(()) @@ -235,6 +254,7 @@ fn test_max_memory_pages(import_memory: bool) { */ memory(64511, None, import_memory) ), + precompile_runtime, ) .unwrap(); @@ -257,6 +277,7 @@ fn test_max_memory_pages(import_memory: bool) { // 1 initial, max is not specified. memory(1, None, import_memory) ), + precompile_runtime, ) .unwrap(); @@ -277,6 +298,7 @@ fn test_max_memory_pages(import_memory: bool) { // Max is 2048. memory(1, Some(2048), import_memory) ), + precompile_runtime, ) .unwrap(); @@ -309,6 +331,7 @@ fn test_max_memory_pages(import_memory: bool) { // Zero starting pages. memory(0, None, import_memory) ), + precompile_runtime, ) .unwrap(); @@ -341,6 +364,7 @@ fn test_max_memory_pages(import_memory: bool) { // Initial=1, meaning after heap pages mount the total will be already 1025. memory(1, None, import_memory) ), + precompile_runtime, ) .unwrap(); } @@ -353,7 +377,6 @@ fn test_instances_without_reuse_are_not_leaked() { let runtime = crate::create_runtime::( RuntimeBlob::uncompress_if_needed(wasm_binary_unwrap()).unwrap(), crate::Config { - heap_pages: 2048, max_memory_size: None, allow_missing_func_imports: true, cache_path: None, @@ -362,6 +385,7 @@ fn test_instances_without_reuse_are_not_leaked() { deterministic_stack_limit: None, canonicalize_nans: false, parallel_compilation: true, + extra_heap_pages: 2048, }, }, ) From 6709b9252fdf27a38a4389429491b212db98926d Mon Sep 17 00:00:00 2001 From: Qinxuan Chen Date: Fri, 25 Mar 2022 00:36:58 +0800 Subject: [PATCH 056/484] pallet-referenda: make the pallet instanceable (#11089) Signed-off-by: koushiro --- frame/referenda/src/branch.rs | 14 +- frame/referenda/src/lib.rs | 244 ++++++++++++++++++---------------- frame/referenda/src/types.rs | 43 +++--- 3 files changed, 160 insertions(+), 141 deletions(-) diff --git a/frame/referenda/src/branch.rs b/frame/referenda/src/branch.rs index 6a4efa31e15e2..f381f5fe5b709 100644 --- a/frame/referenda/src/branch.rs +++ b/frame/referenda/src/branch.rs @@ -59,7 +59,7 @@ impl From for ServiceBranch { impl ServiceBranch { /// Return the weight of the `nudge` function when it takes the branch denoted by `self`. - pub fn weight_of_nudge(self) -> frame_support::weights::Weight { + pub fn weight_of_nudge, I: 'static>(self) -> frame_support::weights::Weight { use ServiceBranch::*; match self { NoDeposit => T::WeightInfo::nudge_referendum_no_deposit(), @@ -81,7 +81,7 @@ impl ServiceBranch { } /// Return the maximum possible weight of the `nudge` function. - pub fn max_weight_of_nudge() -> frame_support::weights::Weight { + pub fn max_weight_of_nudge, I: 'static>() -> frame_support::weights::Weight { 0.max(T::WeightInfo::nudge_referendum_no_deposit()) .max(T::WeightInfo::nudge_referendum_preparing()) .max(T::WeightInfo::nudge_referendum_queued()) @@ -101,7 +101,9 @@ impl ServiceBranch { /// Return the weight of the `place_decision_deposit` function when it takes the branch denoted /// by `self`. - pub fn weight_of_deposit(self) -> Option { + pub fn weight_of_deposit, I: 'static>( + self, + ) -> Option { use ServiceBranch::*; Some(match self { Preparing => T::WeightInfo::place_decision_deposit_preparing(), @@ -124,7 +126,7 @@ impl ServiceBranch { } /// Return the maximum possible weight of the `place_decision_deposit` function. - pub fn max_weight_of_deposit() -> frame_support::weights::Weight { + pub fn max_weight_of_deposit, I: 'static>() -> frame_support::weights::Weight { 0.max(T::WeightInfo::place_decision_deposit_preparing()) .max(T::WeightInfo::place_decision_deposit_queued()) .max(T::WeightInfo::place_decision_deposit_not_queued()) @@ -154,7 +156,7 @@ impl From for OneFewerDecidingBranch { impl OneFewerDecidingBranch { /// Return the weight of the `one_fewer_deciding` function when it takes the branch denoted /// by `self`. - pub fn weight(self) -> frame_support::weights::Weight { + pub fn weight, I: 'static>(self) -> frame_support::weights::Weight { use OneFewerDecidingBranch::*; match self { QueueEmpty => T::WeightInfo::one_fewer_deciding_queue_empty(), @@ -164,7 +166,7 @@ impl OneFewerDecidingBranch { } /// Return the maximum possible weight of the `one_fewer_deciding` function. - pub fn max_weight() -> frame_support::weights::Weight { + pub fn max_weight, I: 'static>() -> frame_support::weights::Weight { 0.max(T::WeightInfo::one_fewer_deciding_queue_empty()) .max(T::WeightInfo::one_fewer_deciding_passing()) .max(T::WeightInfo::one_fewer_deciding_failing()) diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index fb19d2b9ed248..067775fd336d0 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -83,15 +83,18 @@ use sp_std::{fmt::Debug, prelude::*}; mod branch; mod types; pub mod weights; -use branch::{BeginDecidingBranch, OneFewerDecidingBranch, ServiceBranch}; -pub use pallet::*; -pub use types::{ - BalanceOf, CallOf, Curve, DecidingStatus, DecidingStatusOf, Deposit, InsertSorted, - NegativeImbalanceOf, PalletsOriginOf, ReferendumIndex, ReferendumInfo, ReferendumInfoOf, - ReferendumStatus, ReferendumStatusOf, ScheduleAddressOf, TallyOf, TrackIdOf, TrackInfo, - TrackInfoOf, TracksInfo, VotesOf, + +use self::branch::{BeginDecidingBranch, OneFewerDecidingBranch, ServiceBranch}; +pub use self::{ + pallet::*, + types::{ + BalanceOf, CallOf, Curve, DecidingStatus, DecidingStatusOf, Deposit, InsertSorted, + NegativeImbalanceOf, PalletsOriginOf, ReferendumIndex, ReferendumInfo, ReferendumInfoOf, + ReferendumStatus, ReferendumStatusOf, ScheduleAddressOf, TallyOf, TrackIdOf, TrackInfo, + TrackInfoOf, TracksInfo, VotesOf, + }, + weights::WeightInfo, }; -pub use weights::WeightInfo; #[cfg(test)] mod mock; @@ -106,25 +109,33 @@ const ASSEMBLY_ID: LockIdentifier = *b"assembly"; #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::{pallet_prelude::*, traits::EnsureOrigin, Parameter}; + use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; - use sp_runtime::DispatchResult; #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] #[pallet::without_storage_info] - pub struct Pallet(_); + pub struct Pallet(_); #[pallet::config] - pub trait Config: frame_system::Config + Sized { + pub trait Config: frame_system::Config + Sized { // System level stuff. - type Call: Parameter + Dispatchable + From>; - type Event: From> + IsType<::Event>; + type Call: Parameter + Dispatchable + From>; + type Event: From> + IsType<::Event>; /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; /// The Scheduler. - type Scheduler: ScheduleAnon, PalletsOriginOf, Hash = Self::Hash> - + ScheduleNamed, PalletsOriginOf, Hash = Self::Hash>; + type Scheduler: ScheduleAnon< + Self::BlockNumber, + CallOf, + PalletsOriginOf, + Hash = Self::Hash, + > + ScheduleNamed< + Self::BlockNumber, + CallOf, + PalletsOriginOf, + Hash = Self::Hash, + >; /// Currency type for this pallet. type Currency: ReservableCurrency; // Origins and unbalances. @@ -133,7 +144,7 @@ pub mod pallet { /// Origin from which any vote may be killed. type KillOrigin: EnsureOrigin; /// Handler for the unbalanced reduction when slashing a preimage deposit. - type Slash: OnUnbalanced>; + type Slash: OnUnbalanced>; /// The counting type for votes. Usually just balance. type Votes: AtLeast32BitUnsigned + Copy + Parameter + Member; /// The tallying type. @@ -142,7 +153,7 @@ pub mod pallet { // Constants /// The minimum amount to be used as a deposit for a public referendum proposal. #[pallet::constant] - type SubmissionDeposit: Get>; + type SubmissionDeposit: Get>; /// Maximum size of the referendum queue for a single track. #[pallet::constant] @@ -162,7 +173,7 @@ pub mod pallet { // The other stuff. /// Information concerning the different referendum tracks. type Tracks: TracksInfo< - BalanceOf, + BalanceOf, Self::BlockNumber, Origin = ::PalletsOrigin, >; @@ -170,39 +181,40 @@ pub mod pallet { /// The next free referendum index, aka the number of referenda started so far. #[pallet::storage] - pub type ReferendumCount = StorageValue<_, ReferendumIndex, ValueQuery>; + pub type ReferendumCount = StorageValue<_, ReferendumIndex, ValueQuery>; /// Information concerning any given referendum. #[pallet::storage] - pub type ReferendumInfoFor = - StorageMap<_, Blake2_128Concat, ReferendumIndex, ReferendumInfoOf>; + pub type ReferendumInfoFor, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, ReferendumIndex, ReferendumInfoOf>; /// The sorted list of referenda ready to be decided but not yet being decided, ordered by /// conviction-weighted approvals. /// /// This should be empty if `DecidingCount` is less than `TrackInfo::max_deciding`. #[pallet::storage] - pub type TrackQueue = StorageMap< + pub type TrackQueue, I: 'static = ()> = StorageMap< _, Twox64Concat, - TrackIdOf, + TrackIdOf, BoundedVec<(ReferendumIndex, T::Votes), T::MaxQueued>, ValueQuery, >; /// The number of referenda being decided currently. #[pallet::storage] - pub type DecidingCount = StorageMap<_, Twox64Concat, TrackIdOf, u32, ValueQuery>; + pub type DecidingCount, I: 'static = ()> = + StorageMap<_, Twox64Concat, TrackIdOf, u32, ValueQuery>; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { + pub enum Event, I: 'static = ()> { /// A referendum has being submitted. Submitted { /// Index of the referendum. index: ReferendumIndex, /// The track (and by extension proposal dispatch origin) of this referendum. - track: TrackIdOf, + track: TrackIdOf, /// The hash of the proposal up for referendum. proposal_hash: T::Hash, }, @@ -213,7 +225,7 @@ pub mod pallet { /// The account who placed the deposit. who: T::AccountId, /// The amount placed by the account. - amount: BalanceOf, + amount: BalanceOf, }, /// The decision deposit has been refunded. DecisionDepositRefunded { @@ -222,21 +234,21 @@ pub mod pallet { /// The account who placed the deposit. who: T::AccountId, /// The amount placed by the account. - amount: BalanceOf, + amount: BalanceOf, }, /// A deposit has been slashaed. DepositSlashed { /// The account who placed the deposit. who: T::AccountId, /// The amount placed by the account. - amount: BalanceOf, + amount: BalanceOf, }, /// A referendum has moved into the deciding phase. DecisionStarted { /// Index of the referendum. index: ReferendumIndex, /// The track (and by extension proposal dispatch origin) of this referendum. - track: TrackIdOf, + track: TrackIdOf, /// The hash of the proposal up for referendum. proposal_hash: T::Hash, /// The current tally of votes in this referendum. @@ -293,7 +305,7 @@ pub mod pallet { } #[pallet::error] - pub enum Error { + pub enum Error { /// Referendum is not ongoing. NotOngoing, /// Referendum's decision deposit is already paid. @@ -319,7 +331,7 @@ pub mod pallet { } #[pallet::call] - impl Pallet { + impl, I: 'static> Pallet { /// Propose a referendum on a privileged action. /// /// - `origin`: must be `Signed` and the account must have `SubmissionDeposit` funds @@ -338,9 +350,10 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; - let track = T::Tracks::track_for(&proposal_origin).map_err(|_| Error::::NoTrack)?; + let track = + T::Tracks::track_for(&proposal_origin).map_err(|_| Error::::NoTrack)?; let submission_deposit = Self::take_deposit(who, T::SubmissionDeposit::get())?; - let index = ReferendumCount::::mutate(|x| { + let index = ReferendumCount::::mutate(|x| { let r = *x; *x += 1; r @@ -360,9 +373,9 @@ pub mod pallet { in_queue: false, alarm: Self::set_alarm(nudge_call, now.saturating_add(T::UndecidingTimeout::get())), }; - ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); + ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); - Self::deposit_event(Event::::Submitted { index, track, proposal_hash }); + Self::deposit_event(Event::::Submitted { index, track, proposal_hash }); Ok(()) } @@ -374,24 +387,24 @@ pub mod pallet { /// posted. /// /// Emits `DecisionDepositPlaced`. - #[pallet::weight(ServiceBranch::max_weight_of_deposit::())] + #[pallet::weight(ServiceBranch::max_weight_of_deposit::())] pub fn place_decision_deposit( origin: OriginFor, index: ReferendumIndex, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; let mut status = Self::ensure_ongoing(index)?; - ensure!(status.decision_deposit.is_none(), Error::::HasDeposit); - let track = Self::track(status.track).ok_or(Error::::NoTrack)?; + ensure!(status.decision_deposit.is_none(), Error::::HasDeposit); + let track = Self::track(status.track).ok_or(Error::::NoTrack)?; status.decision_deposit = Some(Self::take_deposit(who.clone(), track.decision_deposit)?); let now = frame_system::Pallet::::block_number(); let (info, _, branch) = Self::service_referendum(now, index, status); - ReferendumInfoFor::::insert(index, info); + ReferendumInfoFor::::insert(index, info); let e = - Event::::DecisionDepositPlaced { index, who, amount: track.decision_deposit }; + Event::::DecisionDepositPlaced { index, who, amount: track.decision_deposit }; Self::deposit_event(e); - Ok(branch.weight_of_deposit::().into()) + Ok(branch.weight_of_deposit::().into()) } /// Refund the Decision Deposit for a closed referendum back to the depositor. @@ -407,14 +420,15 @@ pub mod pallet { index: ReferendumIndex, ) -> DispatchResult { ensure_signed_or_root(origin)?; - let mut info = ReferendumInfoFor::::get(index).ok_or(Error::::BadReferendum)?; + let mut info = + ReferendumInfoFor::::get(index).ok_or(Error::::BadReferendum)?; let deposit = info .take_decision_deposit() - .map_err(|_| Error::::Unfinished)? - .ok_or(Error::::NoDeposit)?; + .map_err(|_| Error::::Unfinished)? + .ok_or(Error::::NoDeposit)?; Self::refund_deposit(Some(deposit.clone())); - ReferendumInfoFor::::insert(index, info); - let e = Event::::DecisionDepositRefunded { + ReferendumInfoFor::::insert(index, info); + let e = Event::::DecisionDepositRefunded { index, who: deposit.who, amount: deposit.amount, @@ -437,13 +451,13 @@ pub mod pallet { let _ = T::Scheduler::cancel(last_alarm); } Self::note_one_fewer_deciding(status.track); - Self::deposit_event(Event::::Cancelled { index, tally: status.tally }); + Self::deposit_event(Event::::Cancelled { index, tally: status.tally }); let info = ReferendumInfo::Cancelled( frame_system::Pallet::::block_number(), status.submission_deposit, status.decision_deposit, ); - ReferendumInfoFor::::insert(index, info); + ReferendumInfoFor::::insert(index, info); Ok(()) } @@ -461,11 +475,11 @@ pub mod pallet { let _ = T::Scheduler::cancel(last_alarm); } Self::note_one_fewer_deciding(status.track); - Self::deposit_event(Event::::Killed { index, tally: status.tally }); + Self::deposit_event(Event::::Killed { index, tally: status.tally }); Self::slash_deposit(Some(status.submission_deposit.clone())); Self::slash_deposit(status.decision_deposit.clone()); let info = ReferendumInfo::Killed(frame_system::Pallet::::block_number()); - ReferendumInfoFor::::insert(index, info); + ReferendumInfoFor::::insert(index, info); Ok(()) } @@ -473,7 +487,7 @@ pub mod pallet { /// /// - `origin`: must be `Root`. /// - `index`: the referendum to be advanced. - #[pallet::weight(ServiceBranch::max_weight_of_nudge::())] + #[pallet::weight(ServiceBranch::max_weight_of_nudge::())] pub fn nudge_referendum( origin: OriginFor, index: ReferendumIndex, @@ -485,9 +499,9 @@ pub mod pallet { status.alarm = None; let (info, dirty, branch) = Self::service_referendum(now, index, status); if dirty { - ReferendumInfoFor::::insert(index, info); + ReferendumInfoFor::::insert(index, info); } - Ok(Some(branch.weight_of_nudge::()).into()) + Ok(Some(branch.weight_of_nudge::()).into()) } /// Advance a track onto its next logical state. Only used internally. @@ -499,14 +513,14 @@ pub mod pallet { /// `DecidingCount` is not yet updated. This means that we should either: /// - begin deciding another referendum (and leave `DecidingCount` alone); or /// - decrement `DecidingCount`. - #[pallet::weight(OneFewerDecidingBranch::max_weight::())] + #[pallet::weight(OneFewerDecidingBranch::max_weight::())] pub fn one_fewer_deciding( origin: OriginFor, - track: TrackIdOf, + track: TrackIdOf, ) -> DispatchResultWithPostInfo { ensure_root(origin)?; - let track_info = T::Tracks::info(track).ok_or(Error::::BadTrack)?; - let mut track_queue = TrackQueue::::get(track); + let track_info = T::Tracks::info(track).ok_or(Error::::BadTrack)?; + let mut track_queue = TrackQueue::::get(track); let branch = if let Some((index, mut status)) = Self::next_for_deciding(&mut track_queue) { let now = frame_system::Pallet::::block_number(); @@ -515,23 +529,23 @@ pub mod pallet { if let Some(set_alarm) = maybe_alarm { Self::ensure_alarm_at(&mut status, index, set_alarm); } - ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); - TrackQueue::::insert(track, track_queue); + ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); + TrackQueue::::insert(track, track_queue); branch.into() } else { - DecidingCount::::mutate(track, |x| x.saturating_dec()); + DecidingCount::::mutate(track, |x| x.saturating_dec()); OneFewerDecidingBranch::QueueEmpty }; - Ok(Some(branch.weight::()).into()) + Ok(Some(branch.weight::()).into()) } } } -impl Polling for Pallet { +impl, I: 'static> Polling for Pallet { type Index = ReferendumIndex; - type Votes = VotesOf; + type Votes = VotesOf; type Moment = T::BlockNumber; - type Class = TrackIdOf; + type Class = TrackIdOf; fn classes() -> Vec { T::Tracks::tracks().iter().map(|x| x.0).collect() @@ -539,14 +553,14 @@ impl Polling for Pallet { fn access_poll( index: Self::Index, - f: impl FnOnce(PollStatus<&mut T::Tally, T::BlockNumber, TrackIdOf>) -> R, + f: impl FnOnce(PollStatus<&mut T::Tally, T::BlockNumber, TrackIdOf>) -> R, ) -> R { - match ReferendumInfoFor::::get(index) { + match ReferendumInfoFor::::get(index) { Some(ReferendumInfo::Ongoing(mut status)) => { let result = f(PollStatus::Ongoing(&mut status.tally, status.track)); let now = frame_system::Pallet::::block_number(); Self::ensure_alarm_at(&mut status, index, now + One::one()); - ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); + ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); result }, Some(ReferendumInfo::Approved(end, ..)) => f(PollStatus::Completed(end, true)), @@ -558,15 +572,15 @@ impl Polling for Pallet { fn try_access_poll( index: Self::Index, f: impl FnOnce( - PollStatus<&mut T::Tally, T::BlockNumber, TrackIdOf>, + PollStatus<&mut T::Tally, T::BlockNumber, TrackIdOf>, ) -> Result, ) -> Result { - match ReferendumInfoFor::::get(index) { + match ReferendumInfoFor::::get(index) { Some(ReferendumInfo::Ongoing(mut status)) => { let result = f(PollStatus::Ongoing(&mut status.tally, status.track))?; let now = frame_system::Pallet::::block_number(); Self::ensure_alarm_at(&mut status, index, now + One::one()); - ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); + ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); Ok(result) }, Some(ReferendumInfo::Approved(end, ..)) => f(PollStatus::Completed(end, true)), @@ -575,13 +589,13 @@ impl Polling for Pallet { } } - fn as_ongoing(index: Self::Index) -> Option<(T::Tally, TrackIdOf)> { + fn as_ongoing(index: Self::Index) -> Option<(T::Tally, TrackIdOf)> { Self::ensure_ongoing(index).ok().map(|x| (x.tally, x.track)) } #[cfg(feature = "runtime-benchmarks")] fn create_ongoing(class: Self::Class) -> Result { - let index = ReferendumCount::::mutate(|x| { + let index = ReferendumCount::::mutate(|x| { let r = *x; *x += 1; r @@ -590,7 +604,7 @@ impl Polling for Pallet { let dummy_account_id = codec::Decode::decode(&mut sp_runtime::traits::TrailingZeroInput::new(&b"dummy"[..])) .expect("infinite length input; no invalid inputs for type; qed"); - let mut status = ReferendumStatusOf:: { + let mut status = ReferendumStatusOf:: { track: class, origin: frame_support::dispatch::RawOrigin::Root.into(), proposal_hash: ::hash_of(&index), @@ -604,7 +618,7 @@ impl Polling for Pallet { alarm: None, }; Self::ensure_alarm_at(&mut status, index, sp_runtime::traits::Bounded::max_value()); - ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); + ReferendumInfoFor::::insert(index, ReferendumInfo::Ongoing(status)); Ok(index) } @@ -619,7 +633,7 @@ impl Polling for Pallet { } else { ReferendumInfo::Rejected(now, status.submission_deposit, status.decision_deposit) }; - ReferendumInfoFor::::insert(index, info); + ReferendumInfoFor::::insert(index, info); Ok(()) } @@ -633,20 +647,22 @@ impl Polling for Pallet { } } -impl Pallet { +impl, I: 'static> Pallet { /// Check that referendum `index` is in the `Ongoing` state and return the `ReferendumStatus` /// value, or `Err` otherwise. - pub fn ensure_ongoing(index: ReferendumIndex) -> Result, DispatchError> { - match ReferendumInfoFor::::get(index) { + pub fn ensure_ongoing( + index: ReferendumIndex, + ) -> Result, DispatchError> { + match ReferendumInfoFor::::get(index) { Some(ReferendumInfo::Ongoing(status)) => Ok(status), - _ => Err(Error::::NotOngoing.into()), + _ => Err(Error::::NotOngoing.into()), } } // Enqueue a proposal from a referendum which has presumably passed. fn schedule_enactment( index: ReferendumIndex, - track: &TrackInfoOf, + track: &TrackInfoOf, desired: DispatchTime, origin: PalletsOriginOf, call_hash: T::Hash, @@ -668,9 +684,9 @@ impl Pallet { /// Set an alarm to dispatch `call` at block number `when`. fn set_alarm( - call: impl Into>, + call: impl Into>, when: T::BlockNumber, - ) -> Option<(T::BlockNumber, ScheduleAddressOf)> { + ) -> Option<(T::BlockNumber, ScheduleAddressOf)> { let alarm_interval = T::AlarmInterval::get().max(One::one()); let when = (when + alarm_interval - One::one()) / alarm_interval * alarm_interval; let maybe_result = T::Scheduler::schedule( @@ -698,10 +714,10 @@ impl Pallet { /// /// This will properly set up the `confirming` item. fn begin_deciding( - status: &mut ReferendumStatusOf, + status: &mut ReferendumStatusOf, index: ReferendumIndex, now: T::BlockNumber, - track: &TrackInfoOf, + track: &TrackInfoOf, ) -> (Option, BeginDecidingBranch) { let is_passing = Self::is_passing( &status.tally, @@ -711,14 +727,14 @@ impl Pallet { &track.min_approval, ); status.in_queue = false; - Self::deposit_event(Event::::DecisionStarted { + Self::deposit_event(Event::::DecisionStarted { index, tally: status.tally.clone(), proposal_hash: status.proposal_hash.clone(), track: status.track.clone(), }); let confirming = if is_passing { - Self::deposit_event(Event::::ConfirmStarted { index }); + Self::deposit_event(Event::::ConfirmStarted { index }); Some(now.saturating_add(track.confirm_period)) } else { None @@ -737,21 +753,21 @@ impl Pallet { /// If `None`, then it is queued and should be nudged automatically as the queue gets drained. fn ready_for_deciding( now: T::BlockNumber, - track: &TrackInfoOf, + track: &TrackInfoOf, index: ReferendumIndex, - status: &mut ReferendumStatusOf, + status: &mut ReferendumStatusOf, ) -> (Option, ServiceBranch) { - let deciding_count = DecidingCount::::get(status.track); + let deciding_count = DecidingCount::::get(status.track); if deciding_count < track.max_deciding { // Begin deciding. - DecidingCount::::insert(status.track, deciding_count.saturating_add(1)); + DecidingCount::::insert(status.track, deciding_count.saturating_add(1)); let r = Self::begin_deciding(status, index, now, track); (r.0, r.1.into()) } else { // Add to queue. let item = (index, status.tally.ayes()); status.in_queue = true; - TrackQueue::::mutate(status.track, |q| q.insert_sorted_by_key(item, |x| x.1)); + TrackQueue::::mutate(status.track, |q| q.insert_sorted_by_key(item, |x| x.1)); (None, ServiceBranch::Queued) } } @@ -759,8 +775,8 @@ impl Pallet { /// Grab the index and status for the referendum which is the highest priority of those for the /// given track which are ready for being decided. fn next_for_deciding( - track_queue: &mut BoundedVec<(u32, VotesOf), T::MaxQueued>, - ) -> Option<(ReferendumIndex, ReferendumStatusOf)> { + track_queue: &mut BoundedVec<(u32, VotesOf), T::MaxQueued>, + ) -> Option<(ReferendumIndex, ReferendumStatusOf)> { loop { let (index, _) = track_queue.pop()?; match Self::ensure_ongoing(index) { @@ -773,7 +789,7 @@ impl Pallet { /// Schedule a call to `one_fewer_deciding` function via the dispatchable /// `defer_one_fewer_deciding`. We could theoretically call it immediately (and it would be /// overall more efficient), however the weights become rather less easy to measure. - fn note_one_fewer_deciding(track: TrackIdOf) { + fn note_one_fewer_deciding(track: TrackIdOf) { // Set an alarm call for the next block to nudge the track along. let now = frame_system::Pallet::::block_number(); let next_block = now + One::one(); @@ -801,7 +817,7 @@ impl Pallet { /// /// Returns `false` if nothing changed. fn ensure_alarm_at( - status: &mut ReferendumStatusOf, + status: &mut ReferendumStatusOf, index: ReferendumIndex, alarm: T::BlockNumber, ) -> bool { @@ -839,8 +855,8 @@ impl Pallet { fn service_referendum( now: T::BlockNumber, index: ReferendumIndex, - mut status: ReferendumStatusOf, - ) -> (ReferendumInfoOf, bool, ServiceBranch) { + mut status: ReferendumStatusOf, + ) -> (ReferendumInfoOf, bool, ServiceBranch) { let mut dirty = false; // Should it begin being decided? let track = match Self::track(status.track) { @@ -857,7 +873,7 @@ impl Pallet { if status.in_queue { // Does our position in the queue need updating? let ayes = status.tally.ayes(); - let mut queue = TrackQueue::::get(status.track); + let mut queue = TrackQueue::::get(status.track); let maybe_old_pos = queue.iter().position(|(x, _)| *x == index); let new_pos = queue.binary_search_by_key(&ayes, |x| x.1).unwrap_or_else(|x| x); branch = if maybe_old_pos.is_none() && new_pos > 0 { @@ -872,7 +888,7 @@ impl Pallet { } else { ServiceBranch::NotQueued }; - TrackQueue::::insert(status.track, queue); + TrackQueue::::insert(status.track, queue); } else { // Are we ready for deciding? branch = if status.decision_deposit.is_some() { @@ -897,7 +913,7 @@ impl Pallet { if status.deciding.is_none() && now >= timeout { // Too long without being decided - end it. Self::ensure_no_alarm(&mut status); - Self::deposit_event(Event::::TimedOut { index, tally: status.tally }); + Self::deposit_event(Event::::TimedOut { index, tally: status.tally }); return ( ReferendumInfo::TimedOut( now, @@ -931,7 +947,7 @@ impl Pallet { status.origin, call_hash, ); - Self::deposit_event(Event::::Confirmed { + Self::deposit_event(Event::::Confirmed { index, tally: status.tally, }); @@ -950,7 +966,7 @@ impl Pallet { // Start confirming dirty = true; deciding.confirming = Some(now.saturating_add(track.confirm_period)); - Self::deposit_event(Event::::ConfirmStarted { index }); + Self::deposit_event(Event::::ConfirmStarted { index }); ServiceBranch::BeginConfirming }, } @@ -959,7 +975,7 @@ impl Pallet { // Failed! Self::ensure_no_alarm(&mut status); Self::note_one_fewer_deciding(status.track); - Self::deposit_event(Event::::Rejected { index, tally: status.tally }); + Self::deposit_event(Event::::Rejected { index, tally: status.tally }); return ( ReferendumInfo::Rejected( now, @@ -974,7 +990,7 @@ impl Pallet { // Stop confirming dirty = true; deciding.confirming = None; - Self::deposit_event(Event::::ConfirmAborted { index }); + Self::deposit_event(Event::::ConfirmAborted { index }); ServiceBranch::EndConfirming } else { ServiceBranch::ContinueNotConfirming @@ -993,7 +1009,7 @@ impl Pallet { fn decision_time( deciding: &DecidingStatusOf, tally: &T::Tally, - track: &TrackInfoOf, + track: &TrackInfoOf, ) -> T::BlockNumber { deciding.confirming.unwrap_or_else(|| { // Set alarm to the point where the current voting would make it pass. @@ -1007,7 +1023,7 @@ impl Pallet { } /// Cancel the alarm in `status`, if one exists. - fn ensure_no_alarm(status: &mut ReferendumStatusOf) { + fn ensure_no_alarm(status: &mut ReferendumStatusOf) { if let Some((_, last_alarm)) = status.alarm.take() { // Incorrect alarm - cancel it. let _ = T::Scheduler::cancel(last_alarm); @@ -1017,29 +1033,29 @@ impl Pallet { /// Reserve a deposit and return the `Deposit` instance. fn take_deposit( who: T::AccountId, - amount: BalanceOf, - ) -> Result>, DispatchError> { + amount: BalanceOf, + ) -> Result>, DispatchError> { T::Currency::reserve(&who, amount)?; Ok(Deposit { who, amount }) } /// Return a deposit, if `Some`. - fn refund_deposit(deposit: Option>>) { + fn refund_deposit(deposit: Option>>) { if let Some(Deposit { who, amount }) = deposit { T::Currency::unreserve(&who, amount); } } /// Slash a deposit, if `Some`. - fn slash_deposit(deposit: Option>>) { + fn slash_deposit(deposit: Option>>) { if let Some(Deposit { who, amount }) = deposit { T::Slash::on_unbalanced(T::Currency::slash_reserved(&who, amount).0); - Self::deposit_event(Event::::DepositSlashed { who, amount }); + Self::deposit_event(Event::::DepositSlashed { who, amount }); } } /// Get the track info value for the track `id`. - fn track(id: TrackIdOf) -> Option<&'static TrackInfoOf> { + fn track(id: TrackIdOf) -> Option<&'static TrackInfoOf> { let tracks = T::Tracks::tracks(); let index = tracks.binary_search_by_key(&id, |x| x.0).unwrap_or_else(|x| x); Some(&tracks[index].1) diff --git a/frame/referenda/src/types.rs b/frame/referenda/src/types.rs index 8ea9fc3faf3d0..622075100631b 100644 --- a/frame/referenda/src/types.rs +++ b/frame/referenda/src/types.rs @@ -24,44 +24,45 @@ use scale_info::TypeInfo; use sp_runtime::RuntimeDebug; use sp_std::fmt::Debug; -pub type BalanceOf = - <::Currency as Currency<::AccountId>>::Balance; -pub type NegativeImbalanceOf = <::Currency as Currency< +pub type BalanceOf = + <>::Currency as Currency<::AccountId>>::Balance; +pub type NegativeImbalanceOf = <>::Currency as Currency< ::AccountId, >>::NegativeImbalance; -pub type CallOf = ::Call; -pub type VotesOf = ::Votes; -pub type TallyOf = ::Tally; +pub type CallOf = >::Call; +pub type VotesOf = >::Votes; +pub type TallyOf = >::Tally; pub type PalletsOriginOf = <::Origin as OriginTrait>::PalletsOrigin; -pub type ReferendumInfoOf = ReferendumInfo< - TrackIdOf, +pub type ReferendumInfoOf = ReferendumInfo< + TrackIdOf, PalletsOriginOf, ::BlockNumber, ::Hash, - BalanceOf, - TallyOf, + BalanceOf, + TallyOf, ::AccountId, - ScheduleAddressOf, + ScheduleAddressOf, >; -pub type ReferendumStatusOf = ReferendumStatus< - TrackIdOf, +pub type ReferendumStatusOf = ReferendumStatus< + TrackIdOf, PalletsOriginOf, ::BlockNumber, ::Hash, - BalanceOf, - TallyOf, + BalanceOf, + TallyOf, ::AccountId, - ScheduleAddressOf, + ScheduleAddressOf, >; pub type DecidingStatusOf = DecidingStatus<::BlockNumber>; -pub type TrackInfoOf = TrackInfo, ::BlockNumber>; -pub type TrackIdOf = <::Tracks as TracksInfo< - BalanceOf, +pub type TrackInfoOf = + TrackInfo, ::BlockNumber>; +pub type TrackIdOf = <>::Tracks as TracksInfo< + BalanceOf, ::BlockNumber, >>::Id; -pub type ScheduleAddressOf = <::Scheduler as Anon< +pub type ScheduleAddressOf = <>::Scheduler as Anon< ::BlockNumber, - CallOf, + CallOf, PalletsOriginOf, >>::Address; From c3597e25b43fd7a501e32235d0b2d14f3d12e444 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 24 Mar 2022 22:12:48 +0100 Subject: [PATCH 057/484] Clear storage before running the bench (#11109) Signed-off-by: Oliver Tale-Yazdi Co-authored-by: Shawn Tabrizi --- frame/bags-list/src/benchmarks.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frame/bags-list/src/benchmarks.rs b/frame/bags-list/src/benchmarks.rs index b94a97093d001..bcfd1e3392b0e 100644 --- a/frame/bags-list/src/benchmarks.rs +++ b/frame/bags-list/src/benchmarks.rs @@ -144,6 +144,10 @@ frame_benchmarking::benchmarks! { // - both heavier's `prev` and `next` are nodes that will need to be read and written. // - `lighter` is the bag's `head`, so the bag will need to be read and written. + // clear any pre-existing storage. + // NOTE: safe to call outside block production + List::::unsafe_clear(); + let bag_thresh = T::BagThresholds::get()[0]; // insert the nodes in order From 8743fc1c7b229296ef26af7e5a328d96a86d9e43 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Fri, 25 Mar 2022 12:27:43 +0100 Subject: [PATCH 058/484] Add `benchmark-block` command (#11091) * Add benchmark-block command Signed-off-by: Oliver Tale-Yazdi * Apply suggestions from code review Co-authored-by: Shawn Tabrizi * Beauty fixes Signed-off-by: Oliver Tale-Yazdi * Beauty fixes Signed-off-by: Oliver Tale-Yazdi Co-authored-by: Shawn Tabrizi Co-authored-by: parity-processbot <> --- Cargo.lock | 2 + bin/node/cli/src/cli.rs | 7 + bin/node/cli/src/command.rs | 7 + utils/frame/benchmarking-cli/Cargo.toml | 2 + .../frame/benchmarking-cli/src/block/bench.rs | 176 ++++++++++++++++++ utils/frame/benchmarking-cli/src/block/cmd.rs | 101 ++++++++++ utils/frame/benchmarking-cli/src/block/mod.rs | 24 +++ utils/frame/benchmarking-cli/src/lib.rs | 2 + .../benchmarking-cli/src/overhead/cmd.rs | 10 +- 9 files changed, 330 insertions(+), 1 deletion(-) create mode 100644 utils/frame/benchmarking-cli/src/block/bench.rs create mode 100644 utils/frame/benchmarking-cli/src/block/cmd.rs create mode 100644 utils/frame/benchmarking-cli/src/block/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 1b478fcc18a78..1731ded2906cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2129,6 +2129,7 @@ dependencies = [ "clap 3.1.6", "frame-benchmarking", "frame-support", + "frame-system", "handlebars", "hash-db", "hex", @@ -2160,6 +2161,7 @@ dependencies = [ "sp-std", "sp-storage", "sp-trie", + "thousands", ] [[package]] diff --git a/bin/node/cli/src/cli.rs b/bin/node/cli/src/cli.rs index a911cc26ef87c..952c6311f8b38 100644 --- a/bin/node/cli/src/cli.rs +++ b/bin/node/cli/src/cli.rs @@ -42,6 +42,13 @@ pub enum Subcommand { #[clap(name = "benchmark", about = "Benchmark runtime pallets.")] Benchmark(frame_benchmarking_cli::BenchmarkCmd), + /// Benchmark the execution time of historic blocks and compare it to their consumed weight. + #[clap( + name = "benchmark-block", + about = "Benchmark the execution time of historic blocks and compare it to their consumed weight." + )] + BenchmarkBlock(frame_benchmarking_cli::BlockCmd), + /// Sub command for benchmarking the per-block and per-extrinsic execution overhead. #[clap( name = "benchmark-overhead", diff --git a/bin/node/cli/src/command.rs b/bin/node/cli/src/command.rs index 3c2039fde4757..bd324b20fb019 100644 --- a/bin/node/cli/src/command.rs +++ b/bin/node/cli/src/command.rs @@ -98,6 +98,13 @@ pub fn run() -> Result<()> { You can enable it with `--features runtime-benchmarks`." .into()) }, + Some(Subcommand::BenchmarkBlock(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.async_run(|config| { + let PartialComponents { client, task_manager, .. } = new_partial(&config)?; + Ok((cmd.run(client), task_manager)) + }) + }, Some(Subcommand::BenchmarkOverhead(cmd)) => { let runner = cli.create_runner(cmd)?; runner.async_run(|mut config| { diff --git a/utils/frame/benchmarking-cli/Cargo.toml b/utils/frame/benchmarking-cli/Cargo.toml index b28fe195a2f31..11bba2b37957f 100644 --- a/utils/frame/benchmarking-cli/Cargo.toml +++ b/utils/frame/benchmarking-cli/Cargo.toml @@ -15,6 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] frame-benchmarking = { version = "4.0.0-dev", path = "../../../frame/benchmarking" } frame-support = { version = "4.0.0-dev", path = "../../../frame/support" } +frame-system = { version = "4.0.0-dev", path = "../../../frame/system" } sp-core = { version = "6.0.0", path = "../../../primitives/core" } sc-block-builder = { version = "0.10.0-dev", path = "../../../client/block-builder" } sc-service = { version = "0.10.0-dev", default-features = false, path = "../../../client/service" } @@ -50,6 +51,7 @@ hash-db = "0.15.2" hex = "0.4.3" memory-db = "0.29.0" rand = { version = "0.8.4", features = ["small_rng"] } +thousands = "0.2.0" [features] default = ["db", "sc-client-db/runtime-benchmarks"] diff --git a/utils/frame/benchmarking-cli/src/block/bench.rs b/utils/frame/benchmarking-cli/src/block/bench.rs new file mode 100644 index 0000000000000..d3c1c97b04ab2 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/block/bench.rs @@ -0,0 +1,176 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Contains the core benchmarking logic. + +use codec::DecodeAll; +use frame_support::weights::constants::WEIGHT_PER_NANOS; +use frame_system::ConsumedWeight; +use sc_block_builder::{BlockBuilderApi, BlockBuilderProvider}; +use sc_cli::{Error, Result}; +use sc_client_api::{Backend as ClientBackend, BlockBackend, StorageProvider, UsageProvider}; +use sp_api::{ApiExt, Core, HeaderT, ProvideRuntimeApi}; +use sp_blockchain::Error::RuntimeApiError; +use sp_runtime::{generic::BlockId, traits::Block as BlockT, DigestItem, OpaqueExtrinsic}; +use sp_storage::StorageKey; + +use clap::Args; +use log::{info, warn}; +use serde::Serialize; +use std::{fmt::Debug, marker::PhantomData, sync::Arc, time::Instant}; +use thousands::Separable; + +use crate::storage::record::{StatSelect, Stats}; + +/// Log target for printing block weight info. +const LOG_TARGET: &'static str = "benchmark::block::weight"; + +/// Parameters for modifying the benchmark behaviour. +#[derive(Debug, Default, Serialize, Clone, PartialEq, Args)] +pub struct BenchmarkParams { + /// Number of the first block to consider. + #[clap(long)] + pub from: u32, + + /// Last block number to consider. + #[clap(long)] + pub to: u32, + + /// Number of times that the benchmark should be repeated for each block. + #[clap(long, default_value = "10")] + pub repeat: u32, +} + +/// Convenience closure for the [`Benchmark::run()`] function. +pub struct Benchmark { + client: Arc, + params: BenchmarkParams, + _p: PhantomData<(Block, BA, C)>, +} + +/// Helper for nano seconds. +type NanoSeconds = u64; + +impl Benchmark +where + Block: BlockT, + BA: ClientBackend, + C: BlockBuilderProvider + + ProvideRuntimeApi + + StorageProvider + + UsageProvider + + BlockBackend, + C::Api: ApiExt + BlockBuilderApi, +{ + /// Returns a new [`Self`] from the arguments. + pub fn new(client: Arc, params: BenchmarkParams) -> Self { + Self { client, params, _p: PhantomData } + } + + /// Benchmark the execution speed of historic blocks and log the results. + pub fn run(&self) -> Result<()> { + if self.params.from == 0 { + return Err("Cannot benchmark the genesis block".into()) + } + + for i in self.params.from..=self.params.to { + let block_num = BlockId::Number(i.into()); + let parent_num = BlockId::Number(((i - 1) as u32).into()); + let consumed = self.consumed_weight(&block_num)?; + + let block = + self.client.block(&block_num)?.ok_or(format!("Block {} not found", block_num))?; + let block = self.unsealed(block.block); + let took = self.measure_block(&block, &parent_num)?; + + self.log_weight(i, block.extrinsics().len(), consumed, took); + } + + Ok(()) + } + + /// Return the average *execution* aka. *import* time of the block. + fn measure_block(&self, block: &Block, parent_num: &BlockId) -> Result { + let mut record = Vec::::default(); + // Interesting part here: + // Execute the block multiple times and collect stats about its execution time. + for _ in 0..self.params.repeat { + let block = block.clone(); + let runtime_api = self.client.runtime_api(); + let start = Instant::now(); + + runtime_api + .execute_block(&parent_num, block) + .map_err(|e| Error::Client(RuntimeApiError(e)))?; + + record.push(start.elapsed().as_nanos() as NanoSeconds); + } + + let took = Stats::new(&record)?.select(StatSelect::Average); + Ok(took) + } + + /// Returns the total nanoseconds of a [`frame_system::ConsumedWeight`] for a block number. + /// + /// This is the post-dispatch corrected weight and is only available + /// after executing the block. + fn consumed_weight(&self, block: &BlockId) -> Result { + // Hard-coded key for System::BlockWeight. It could also be passed in as argument + // for the benchmark, but I think this should work as well. + let hash = hex::decode("26aa394eea5630e07c48ae0c9558cef734abf5cb34d6244378cddbf18e849d96")?; + let key = StorageKey(hash); + + let mut raw_weight = &self + .client + .storage(&block, &key)? + .ok_or(format!("Could not find System::BlockWeight for block: {}", block))? + .0[..]; + + let weight = ConsumedWeight::decode_all(&mut raw_weight)?; + // Should be divisible, but still use floats in case we ever change that. + Ok((weight.total() as f64 / WEIGHT_PER_NANOS as f64).floor() as NanoSeconds) + } + + /// Prints the weight info of a block to the console. + fn log_weight(&self, num: u32, num_ext: usize, consumed: NanoSeconds, took: NanoSeconds) { + // The ratio of weight that the block used vs what it consumed. + // This should in general not exceed 100% (minus outliers). + let percent = (took as f64 / consumed as f64) * 100.0; + + let msg = format!( + "Block {} with {: >5} tx used {: >6.2}% of its weight ({: >14} of {: >14} ns)", + num, + num_ext, + percent, + took.separate_with_commas(), + consumed.separate_with_commas() + ); + + if took <= consumed { + info!(target: LOG_TARGET, "{}", msg); + } else { + warn!(target: LOG_TARGET, "{} - OVER WEIGHT!", msg); + } + } + + /// Removes the consensus seal from the block. + fn unsealed(&self, block: Block) -> Block { + let (mut header, exts) = block.deconstruct(); + header.digest_mut().logs.retain(|item| !matches!(item, DigestItem::Seal(_, _))); + Block::new(header, exts) + } +} diff --git a/utils/frame/benchmarking-cli/src/block/cmd.rs b/utils/frame/benchmarking-cli/src/block/cmd.rs new file mode 100644 index 0000000000000..4618c1dd894e1 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/block/cmd.rs @@ -0,0 +1,101 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Contains the [`BlockCmd`] as entry point for the CLI to execute +//! the *block* benchmark. + +use sc_block_builder::{BlockBuilderApi, BlockBuilderProvider}; +use sc_cli::{CliConfiguration, ImportParams, Result, SharedParams}; +use sc_client_api::{Backend as ClientBackend, BlockBackend, StorageProvider, UsageProvider}; +use sp_api::{ApiExt, ProvideRuntimeApi}; +use sp_runtime::{traits::Block as BlockT, OpaqueExtrinsic}; + +use clap::Parser; +use std::{fmt::Debug, sync::Arc}; + +use super::bench::{Benchmark, BenchmarkParams}; + +/// Benchmark the execution time historic blocks. +/// +/// This can be used to verify that blocks do not use more weight than they consumed +/// in their `WeightInfo`. Example: +/// +/// Let's say you are on a Substrate chain and want to verify that the first 3 blocks +/// did not use more weight than declared which would otherwise be an issue. +/// To test this with a dev node, first create one with a temp directory: +/// +/// $ substrate --dev -d /tmp/my-dev --execution wasm --wasm-execution compiled +/// +/// And wait some time to let it produce 3 blocks. Then benchmark them with: +/// +/// $ substrate benchmark-block --from 1 --to 3 --dev -d /tmp/my-dev +/// --execution wasm --wasm-execution compiled --pruning archive +/// +/// The output will be similar to this: +/// +/// Block 1 with 1 tx used 77.34% of its weight ( 5,308,964 of 6,864,645 ns) +/// Block 2 with 1 tx used 77.99% of its weight ( 5,353,992 of 6,864,645 ns) +/// Block 3 with 1 tx used 75.91% of its weight ( 5,305,938 of 6,989,645 ns) +/// +/// The percent number is important and indicates how much weight +/// was used as compared to the consumed weight. +/// This number should be below 100% for reference hardware. +#[derive(Debug, Parser)] +pub struct BlockCmd { + #[allow(missing_docs)] + #[clap(flatten)] + pub shared_params: SharedParams, + + #[allow(missing_docs)] + #[clap(flatten)] + pub import_params: ImportParams, + + #[allow(missing_docs)] + #[clap(flatten)] + pub params: BenchmarkParams, +} + +impl BlockCmd { + /// Benchmark the execution time of historic blocks and compare it to their consumed weight. + /// + /// Output will be printed to console. + pub async fn run(&self, client: Arc) -> Result<()> + where + Block: BlockT, + BA: ClientBackend, + C: BlockBuilderProvider + + BlockBackend + + ProvideRuntimeApi + + StorageProvider + + UsageProvider, + C::Api: ApiExt + BlockBuilderApi, + { + // Put everything in the benchmark type to have the generic types handy. + Benchmark::new(client, self.params.clone()).run() + } +} + +// Boilerplate +impl CliConfiguration for BlockCmd { + fn shared_params(&self) -> &SharedParams { + &self.shared_params + } + + fn import_params(&self) -> Option<&ImportParams> { + Some(&self.import_params) + } +} diff --git a/utils/frame/benchmarking-cli/src/block/mod.rs b/utils/frame/benchmarking-cli/src/block/mod.rs new file mode 100644 index 0000000000000..97fdb6ad2c20c --- /dev/null +++ b/utils/frame/benchmarking-cli/src/block/mod.rs @@ -0,0 +1,24 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Crate to benchmark the execution time of historic blocks +//! and compare it to their consumed weight. + +mod bench; +mod cmd; + +pub use cmd::BlockCmd; diff --git a/utils/frame/benchmarking-cli/src/lib.rs b/utils/frame/benchmarking-cli/src/lib.rs index e06d57963dad3..288e6b4b86156 100644 --- a/utils/frame/benchmarking-cli/src/lib.rs +++ b/utils/frame/benchmarking-cli/src/lib.rs @@ -15,6 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +mod block; mod command; pub mod overhead; mod post_processing; @@ -24,6 +25,7 @@ mod writer; use sc_cli::{ExecutionStrategy, WasmExecutionMethod, DEFAULT_WASM_EXECUTION_METHOD}; use std::{fmt::Debug, path::PathBuf}; +pub use block::BlockCmd; pub use overhead::{ExtrinsicBuilder, OverheadCmd}; pub use storage::StorageCmd; diff --git a/utils/frame/benchmarking-cli/src/overhead/cmd.rs b/utils/frame/benchmarking-cli/src/overhead/cmd.rs index 8c75627fe2462..f74c32ba72a86 100644 --- a/utils/frame/benchmarking-cli/src/overhead/cmd.rs +++ b/utils/frame/benchmarking-cli/src/overhead/cmd.rs @@ -19,7 +19,7 @@ //! the *overhead* benchmarks. use sc_block_builder::{BlockBuilderApi, BlockBuilderProvider}; -use sc_cli::{CliConfiguration, Result, SharedParams}; +use sc_cli::{CliConfiguration, ImportParams, Result, SharedParams}; use sc_client_api::Backend as ClientBackend; use sc_service::Configuration; use sp_api::{ApiExt, ProvideRuntimeApi}; @@ -45,6 +45,10 @@ pub struct OverheadCmd { #[clap(flatten)] pub shared_params: SharedParams, + #[allow(missing_docs)] + #[clap(flatten)] + pub import_params: ImportParams, + #[allow(missing_docs)] #[clap(flatten)] pub params: OverheadParams, @@ -115,4 +119,8 @@ impl CliConfiguration for OverheadCmd { fn shared_params(&self) -> &SharedParams { &self.shared_params } + + fn import_params(&self) -> Option<&ImportParams> { + Some(&self.import_params) + } } From 855366a00e8e7a0086a4d582de5eaf04315c3746 Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Fri, 25 Mar 2022 09:42:54 -0400 Subject: [PATCH 059/484] Improve Bounties and Child Bounties Deposit Logic (#11014) * basic idea * make tests better * update bounties pallet to also have similar logic * new test verifies logic for bounty pallet * add test for new child logic * better name * make `node` compile with bounties changes * * formatting * use uniform notion of parent and child, no "master" or "general" entity * README updated to match comments * Revert "* formatting" This reverts commit 1ab729e7c23b5db24a8e229d487bbc2ed81d38c3. * update bounties logic to use bounds * fix child * bounties test for max * update tests * check min bound * update node * remove stale comment * Update frame/bounties/src/lib.rs Co-authored-by: Dan Shields --- bin/node/runtime/src/lib.rs | 34 +-- frame/bounties/src/lib.rs | 32 ++- frame/bounties/src/tests.rs | 134 +++++++++-- frame/child-bounties/src/lib.rs | 35 ++- frame/child-bounties/src/tests.rs | 358 +++++++++++++++++++++++------- 5 files changed, 460 insertions(+), 133 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index a6d97cb299b24..47bdedbab83a3 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -220,7 +220,7 @@ impl frame_system::Config for Runtime { type SystemWeightInfo = frame_system::weights::SubstrateWeight; type SS58Prefix = ConstU16<42>; type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; + type MaxConsumers = ConstU32<16>; } impl pallet_randomness_collective_flip::Config for Runtime {} @@ -827,7 +827,7 @@ impl pallet_democracy::Config for Runtime { type Slash = Treasury; type Scheduler = Scheduler; type PalletsOrigin = OriginCaller; - type MaxVotes = frame_support::traits::ConstU32<100>; + type MaxVotes = ConstU32<100>; type WeightInfo = pallet_democracy::weights::SubstrateWeight; type MaxProposals = MaxProposals; } @@ -929,17 +929,9 @@ parameter_types! { pub const TipFindersFee: Percent = Percent::from_percent(20); pub const TipReportDepositBase: Balance = 1 * DOLLARS; pub const DataDepositPerByte: Balance = 1 * CENTS; - pub const BountyDepositBase: Balance = 1 * DOLLARS; - pub const BountyDepositPayoutDelay: BlockNumber = 1 * DAYS; pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry"); - pub const BountyUpdatePeriod: BlockNumber = 14 * DAYS; pub const MaximumReasonLength: u32 = 300; - pub const BountyCuratorDeposit: Permill = Permill::from_percent(50); - pub const BountyValueMinimum: Balance = 5 * DOLLARS; pub const MaxApprovals: u32 = 100; - pub const MaxActiveChildBountyCount: u32 = 5; - pub const ChildBountyValueMinimum: Balance = 1 * DOLLARS; - pub const ChildBountyCuratorDepositBase: Permill = Permill::from_percent(10); } impl pallet_treasury::Config for Runtime { @@ -966,12 +958,25 @@ impl pallet_treasury::Config for Runtime { type MaxApprovals = MaxApprovals; } +parameter_types! { + pub const BountyCuratorDeposit: Permill = Permill::from_percent(50); + pub const BountyValueMinimum: Balance = 5 * DOLLARS; + pub const BountyDepositBase: Balance = 1 * DOLLARS; + pub const CuratorDepositMultiplier: Permill = Permill::from_percent(50); + pub const CuratorDepositMin: Balance = 1 * DOLLARS; + pub const CuratorDepositMax: Balance = 100 * DOLLARS; + pub const BountyDepositPayoutDelay: BlockNumber = 1 * DAYS; + pub const BountyUpdatePeriod: BlockNumber = 14 * DAYS; +} + impl pallet_bounties::Config for Runtime { type Event = Event; type BountyDepositBase = BountyDepositBase; type BountyDepositPayoutDelay = BountyDepositPayoutDelay; type BountyUpdatePeriod = BountyUpdatePeriod; - type BountyCuratorDeposit = BountyCuratorDeposit; + type CuratorDepositMultiplier = CuratorDepositMultiplier; + type CuratorDepositMin = CuratorDepositMin; + type CuratorDepositMax = CuratorDepositMax; type BountyValueMinimum = BountyValueMinimum; type DataDepositPerByte = DataDepositPerByte; type MaximumReasonLength = MaximumReasonLength; @@ -979,11 +984,14 @@ impl pallet_bounties::Config for Runtime { type ChildBountyManager = ChildBounties; } +parameter_types! { + pub const ChildBountyValueMinimum: Balance = 1 * DOLLARS; +} + impl pallet_child_bounties::Config for Runtime { type Event = Event; - type MaxActiveChildBountyCount = MaxActiveChildBountyCount; + type MaxActiveChildBountyCount = ConstU32<5>; type ChildBountyValueMinimum = ChildBountyValueMinimum; - type ChildBountyCuratorDepositBase = ChildBountyCuratorDepositBase; type WeightInfo = pallet_child_bounties::weights::SubstrateWeight; } diff --git a/frame/bounties/src/lib.rs b/frame/bounties/src/lib.rs index 988b15c58a13f..98f2da305a06d 100644 --- a/frame/bounties/src/lib.rs +++ b/frame/bounties/src/lib.rs @@ -196,10 +196,20 @@ pub mod pallet { #[pallet::constant] type BountyUpdatePeriod: Get; - /// Percentage of the curator fee that will be reserved upfront as deposit for bounty - /// curator. + /// The curator deposit is calculated as a percentage of the curator fee. + /// + /// This deposit has optional upper and lower bounds with `CuratorDepositMax` and + /// `CuratorDepositMin`. + #[pallet::constant] + type CuratorDepositMultiplier: Get; + + /// Maximum amount of funds that should be placed in a deposit for making a proposal. #[pallet::constant] - type BountyCuratorDeposit: Get; + type CuratorDepositMax: Get>>; + + /// Minimum amount of funds that should be placed in a deposit for making a proposal. + #[pallet::constant] + type CuratorDepositMin: Get>>; /// Minimum value for a bounty. #[pallet::constant] @@ -502,7 +512,7 @@ pub mod pallet { BountyStatus::CuratorProposed { ref curator } => { ensure!(signer == *curator, Error::::RequireCurator); - let deposit = T::BountyCuratorDeposit::get() * bounty.fee; + let deposit = Self::calculate_curator_deposit(&bounty.fee); T::Currency::reserve(curator, deposit)?; bounty.curator_deposit = deposit; @@ -762,7 +772,19 @@ pub mod pallet { } impl Pallet { - // Add public immutables and private mutables. + pub fn calculate_curator_deposit(fee: &BalanceOf) -> BalanceOf { + let mut deposit = T::CuratorDepositMultiplier::get() * *fee; + + if let Some(max_deposit) = T::CuratorDepositMax::get() { + deposit = deposit.min(max_deposit) + } + + if let Some(min_deposit) = T::CuratorDepositMin::get() { + deposit = deposit.max(min_deposit) + } + + deposit + } /// The account ID of the treasury pot. /// diff --git a/frame/bounties/src/tests.rs b/frame/bounties/src/tests.rs index 3206fce9912fa..9a84bd687abc1 100644 --- a/frame/bounties/src/tests.rs +++ b/frame/bounties/src/tests.rs @@ -60,6 +60,8 @@ parameter_types! { pub const AvailableBlockRatio: Perbill = Perbill::one(); } +type Balance = u64; + impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); @@ -91,7 +93,7 @@ impl pallet_balances::Config for Test { type MaxLocks = (); type MaxReserves = (); type ReserveIdentifier = [u8; 8]; - type Balance = u64; + type Balance = Balance; type Event = Event; type DustRemoval = (); type ExistentialDeposit = ConstU64<1>; @@ -125,15 +127,23 @@ impl pallet_treasury::Config for Test { type SpendFunds = Bounties; type MaxApprovals = ConstU32<100>; } + parameter_types! { - pub const BountyCuratorDeposit: Permill = Permill::from_percent(50); + // This will be 50% of the bounty fee. + pub const CuratorDepositMultiplier: Permill = Permill::from_percent(50); + pub const CuratorDepositMax: Balance = 1_000; + pub const CuratorDepositMin: Balance = 3; + } + impl Config for Test { type Event = Event; type BountyDepositBase = ConstU64<80>; type BountyDepositPayoutDelay = ConstU64<3>; type BountyUpdatePeriod = ConstU64<20>; - type BountyCuratorDeposit = BountyCuratorDeposit; + type CuratorDepositMultiplier = CuratorDepositMultiplier; + type CuratorDepositMax = CuratorDepositMax; + type CuratorDepositMin = CuratorDepositMin; type BountyValueMinimum = ConstU64<1>; type DataDepositPerByte = ConstU64<1>; type MaximumReasonLength = ConstU32<16384>; @@ -543,13 +553,14 @@ fn assign_curator_works() { Error::::InvalidFee ); - assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, 4)); + let fee = 4; + assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, fee)); assert_eq!( Bounties::bounties(0).unwrap(), Bounty { proposer: 0, - fee: 4, + fee, curator_deposit: 0, value: 50, bond: 85, @@ -567,20 +578,22 @@ fn assign_curator_works() { assert_ok!(Bounties::accept_curator(Origin::signed(4), 0)); + let expected_deposit = Bounties::calculate_curator_deposit(&fee); + assert_eq!( Bounties::bounties(0).unwrap(), Bounty { proposer: 0, - fee: 4, - curator_deposit: 2, + fee, + curator_deposit: expected_deposit, value: 50, bond: 85, status: BountyStatus::Active { curator: 4, update_due: 22 }, } ); - assert_eq!(Balances::free_balance(&4), 8); - assert_eq!(Balances::reserved_balance(&4), 2); + assert_eq!(Balances::free_balance(&4), 10 - expected_deposit); + assert_eq!(Balances::reserved_balance(&4), expected_deposit); }); } @@ -596,17 +609,17 @@ fn unassign_curator_works() { System::set_block_number(2); >::on_initialize(2); - assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, 4)); + let fee = 4; + assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, fee)); assert_noop!(Bounties::unassign_curator(Origin::signed(1), 0), BadOrigin); - assert_ok!(Bounties::unassign_curator(Origin::signed(4), 0)); assert_eq!( Bounties::bounties(0).unwrap(), Bounty { proposer: 0, - fee: 4, + fee, curator_deposit: 0, value: 50, bond: 85, @@ -614,19 +627,17 @@ fn unassign_curator_works() { } ); - assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, 4)); - + assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, fee)); Balances::make_free_balance_be(&4, 10); - assert_ok!(Bounties::accept_curator(Origin::signed(4), 0)); - + let expected_deposit = Bounties::calculate_curator_deposit(&fee); assert_ok!(Bounties::unassign_curator(Origin::root(), 0)); assert_eq!( Bounties::bounties(0).unwrap(), Bounty { proposer: 0, - fee: 4, + fee, curator_deposit: 0, value: 50, bond: 85, @@ -634,8 +645,8 @@ fn unassign_curator_works() { } ); - assert_eq!(Balances::free_balance(&4), 8); - assert_eq!(Balances::reserved_balance(&4), 0); // slashed 2 + assert_eq!(Balances::free_balance(&4), 10 - expected_deposit); + assert_eq!(Balances::reserved_balance(&4), 0); // slashed curator deposit }); } @@ -652,10 +663,12 @@ fn award_and_claim_bounty_works() { System::set_block_number(2); >::on_initialize(2); - assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, 4)); + let fee = 4; + assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, fee)); assert_ok!(Bounties::accept_curator(Origin::signed(4), 0)); - assert_eq!(Balances::free_balance(4), 8); // inital 10 - 2 deposit + let expected_deposit = Bounties::calculate_curator_deposit(&fee); + assert_eq!(Balances::free_balance(4), 10 - expected_deposit); assert_noop!( Bounties::award_bounty(Origin::signed(1), 0, 3), @@ -668,8 +681,8 @@ fn award_and_claim_bounty_works() { Bounties::bounties(0).unwrap(), Bounty { proposer: 0, - fee: 4, - curator_deposit: 2, + fee, + curator_deposit: expected_deposit, value: 50, bond: 85, status: BountyStatus::PendingPayout { curator: 4, beneficiary: 3, unlock_at: 5 }, @@ -1034,3 +1047,78 @@ fn unassign_curator_self() { assert_eq!(Balances::reserved_balance(1), 0); // not slashed }); } + +#[test] +fn accept_curator_handles_different_deposit_calculations() { + // This test will verify that a bounty with and without a fee results + // in a different curator deposit: one using the value, and one using the fee. + new_test_ext().execute_with(|| { + // Case 1: With a fee + let user = 1; + let bounty_index = 0; + let value = 88; + let fee = 42; + + System::set_block_number(1); + Balances::make_free_balance_be(&Treasury::account_id(), 101); + Balances::make_free_balance_be(&user, 100); + assert_ok!(Bounties::propose_bounty(Origin::signed(0), value, b"12345".to_vec())); + assert_ok!(Bounties::approve_bounty(Origin::root(), bounty_index)); + + System::set_block_number(2); + >::on_initialize(2); + + assert_ok!(Bounties::propose_curator(Origin::root(), bounty_index, user, fee)); + assert_ok!(Bounties::accept_curator(Origin::signed(user), bounty_index)); + + let expected_deposit = CuratorDepositMultiplier::get() * fee; + assert_eq!(Balances::free_balance(&user), 100 - expected_deposit); + assert_eq!(Balances::reserved_balance(&user), expected_deposit); + + // Case 2: Lower bound + let user = 2; + let bounty_index = 1; + let value = 35; + let fee = 0; + + Balances::make_free_balance_be(&Treasury::account_id(), 101); + Balances::make_free_balance_be(&user, 100); + + assert_ok!(Bounties::propose_bounty(Origin::signed(0), value, b"12345".to_vec())); + assert_ok!(Bounties::approve_bounty(Origin::root(), bounty_index)); + + System::set_block_number(3); + >::on_initialize(3); + + assert_ok!(Bounties::propose_curator(Origin::root(), bounty_index, user, fee)); + assert_ok!(Bounties::accept_curator(Origin::signed(user), bounty_index)); + + let expected_deposit = CuratorDepositMin::get(); + assert_eq!(Balances::free_balance(&user), 100 - expected_deposit); + assert_eq!(Balances::reserved_balance(&user), expected_deposit); + + // Case 3: Upper bound + let user = 3; + let bounty_index = 2; + let value = 1_000_000; + let fee = 50_000; + let starting_balance = fee * 2; + + Balances::make_free_balance_be(&Treasury::account_id(), value * 2); + Balances::make_free_balance_be(&user, starting_balance); + Balances::make_free_balance_be(&0, starting_balance); + + assert_ok!(Bounties::propose_bounty(Origin::signed(0), value, b"12345".to_vec())); + assert_ok!(Bounties::approve_bounty(Origin::root(), bounty_index)); + + System::set_block_number(3); + >::on_initialize(3); + + assert_ok!(Bounties::propose_curator(Origin::root(), bounty_index, user, fee)); + assert_ok!(Bounties::accept_curator(Origin::signed(user), bounty_index)); + + let expected_deposit = CuratorDepositMax::get(); + assert_eq!(Balances::free_balance(&user), starting_balance - expected_deposit); + assert_eq!(Balances::reserved_balance(&user), expected_deposit); + }); +} diff --git a/frame/child-bounties/src/lib.rs b/frame/child-bounties/src/lib.rs index 36acc7766bf61..2fea61c045cf6 100644 --- a/frame/child-bounties/src/lib.rs +++ b/frame/child-bounties/src/lib.rs @@ -66,7 +66,7 @@ use frame_support::traits::{ use sp_runtime::{ traits::{AccountIdConversion, BadOrigin, CheckedSub, Saturating, StaticLookup, Zero}, - DispatchResult, Permill, RuntimeDebug, + DispatchResult, RuntimeDebug, }; use frame_support::pallet_prelude::*; @@ -144,11 +144,6 @@ pub mod pallet { #[pallet::constant] type ChildBountyValueMinimum: Get>; - /// Percentage of child-bounty value to be reserved as curator deposit - /// when curator fee is zero. - #[pallet::constant] - type ChildBountyCuratorDepositBase: Get; - /// The overarching event type. type Event: From> + IsType<::Event>; @@ -392,7 +387,7 @@ pub mod pallet { ) -> DispatchResult { let signer = ensure_signed(origin)?; - let _ = Self::ensure_bounty_active(parent_bounty_id)?; + let (parent_curator, _) = Self::ensure_bounty_active(parent_bounty_id)?; // Mutate child-bounty. ChildBounties::::try_mutate_exists( parent_bounty_id, @@ -406,11 +401,13 @@ pub mod pallet { { ensure!(signer == *curator, BountiesError::::RequireCurator); - // Reserve child-bounty curator deposit. Curator deposit - // is reserved based on a percentage of child-bounty - // value instead of fee, to avoid no deposit in case the - // fee is set as zero. - let deposit = T::ChildBountyCuratorDepositBase::get() * child_bounty.value; + // Reserve child-bounty curator deposit. + let deposit = Self::calculate_curator_deposit( + &parent_curator, + curator, + &child_bounty.fee, + ); + T::Currency::reserve(curator, deposit)?; child_bounty.curator_deposit = deposit; @@ -770,6 +767,20 @@ pub mod pallet { } impl Pallet { + // This function will calculate the deposit of a curator. + fn calculate_curator_deposit( + parent_curator: &T::AccountId, + child_curator: &T::AccountId, + bounty_fee: &BalanceOf, + ) -> BalanceOf { + if parent_curator == child_curator { + return Zero::zero() + } + + // We just use the same logic from the parent bounties pallet. + pallet_bounties::Pallet::::calculate_curator_deposit(bounty_fee) + } + /// The account ID of a child-bounty account. pub fn child_bounty_account_id(id: BountyIndex) -> T::AccountId { // This function is taken from the parent (bounties) pallet, but the diff --git a/frame/child-bounties/src/tests.rs b/frame/child-bounties/src/tests.rs index a6748c47b73d8..61545561a26c3 100644 --- a/frame/child-bounties/src/tests.rs +++ b/frame/child-bounties/src/tests.rs @@ -65,6 +65,8 @@ parameter_types! { pub const AvailableBlockRatio: Perbill = Perbill::one(); } +type Balance = u64; + impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); @@ -96,7 +98,7 @@ impl pallet_balances::Config for Test { type MaxLocks = (); type MaxReserves = (); type ReserveIdentifier = [u8; 8]; - type Balance = u64; + type Balance = Balance; type Event = Event; type DustRemoval = (); type ExistentialDeposit = ConstU64<1>; @@ -130,28 +132,30 @@ impl pallet_treasury::Config for Test { type MaxApprovals = ConstU32<100>; } parameter_types! { - pub const BountyCuratorDeposit: Permill = Permill::from_percent(50); + // This will be 50% of the bounty fee. + pub const CuratorDepositMultiplier: Permill = Permill::from_percent(50); + pub const CuratorDepositMax: Balance = 1_000; + pub const CuratorDepositMin: Balance = 3; + } impl pallet_bounties::Config for Test { type Event = Event; type BountyDepositBase = ConstU64<80>; type BountyDepositPayoutDelay = ConstU64<3>; type BountyUpdatePeriod = ConstU64<10>; - type BountyCuratorDeposit = BountyCuratorDeposit; + type CuratorDepositMultiplier = CuratorDepositMultiplier; + type CuratorDepositMax = CuratorDepositMax; + type CuratorDepositMin = CuratorDepositMin; type BountyValueMinimum = ConstU64<5>; type DataDepositPerByte = ConstU64<1>; type MaximumReasonLength = ConstU32<300>; type WeightInfo = (); type ChildBountyManager = ChildBounties; } -parameter_types! { - pub const ChildBountyCuratorDepositBase: Permill = Permill::from_percent(10); -} impl pallet_child_bounties::Config for Test { type Event = Event; type MaxActiveChildBountyCount = ConstU32<2>; type ChildBountyValueMinimum = ConstU64<1>; - type ChildBountyCuratorDepositBase = ChildBountyCuratorDepositBase; type WeightInfo = (); } @@ -197,10 +201,10 @@ fn minting_works() { fn add_child_bounty() { new_test_ext().execute_with(|| { // TestProcedure. - // 1, Create bounty & move to active state with enough bounty fund & master-curator. - // 2, Master-curator adds child-bounty child-bounty-1, test for error like RequireCurator + // 1, Create bounty & move to active state with enough bounty fund & parent curator. + // 2, Parent curator adds child-bounty child-bounty-1, test for error like RequireCurator // ,InsufficientProposersBalance, InsufficientBountyBalance with invalid arguments. - // 3, Master-curator adds child-bounty child-bounty-1, moves to "Approved" state & + // 3, Parent curator adds child-bounty child-bounty-1, moves to "Approved" state & // test for the event Added. // 4, Test for DB state of `Bounties` & `ChildBounties`. // 5, Observe fund transaction moment between Bounty, Child-bounty, @@ -217,27 +221,30 @@ fn add_child_bounty() { System::set_block_number(2); >::on_initialize(2); - assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, 4)); + let fee = 8; + assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, fee)); Balances::make_free_balance_be(&4, 10); assert_ok!(Bounties::accept_curator(Origin::signed(4), 0)); - assert_eq!(Balances::free_balance(&4), 8); - assert_eq!(Balances::reserved_balance(&4), 2); + // This verifies that the accept curator logic took a deposit. + let expected_deposit = CuratorDepositMultiplier::get() * fee; + assert_eq!(Balances::reserved_balance(&4), expected_deposit); + assert_eq!(Balances::free_balance(&4), 10 - expected_deposit); // Add child-bounty. - // Acc-4 is the master curator. + // Acc-4 is the parent curator. // Call from invalid origin & check for error "RequireCurator". assert_noop!( ChildBounties::add_child_bounty(Origin::signed(0), 0, 10, b"12345-p1".to_vec()), BountiesError::RequireCurator, ); - // Update the master curator balance. + // Update the parent curator balance. Balances::make_free_balance_be(&4, 101); - // Master curator fee is reserved on parent bounty account. + // parent curator fee is reserved on parent bounty account. assert_eq!(Balances::free_balance(Bounties::bounty_account_id(0)), 50); assert_eq!(Balances::reserved_balance(Bounties::bounty_account_id(0)), 0); @@ -258,7 +265,7 @@ fn add_child_bounty() { assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 }); assert_eq!(Balances::free_balance(4), 101); - assert_eq!(Balances::reserved_balance(4), 2); + assert_eq!(Balances::reserved_balance(4), expected_deposit); // DB check. // Check the child-bounty status. @@ -285,8 +292,8 @@ fn add_child_bounty() { fn child_bounty_assign_curator() { new_test_ext().execute_with(|| { // TestProcedure - // 1, Create bounty & move to active state with enough bounty fund & master-curator. - // 2, Master-curator adds child-bounty child-bounty-1, moves to "Active" state. + // 1, Create bounty & move to active state with enough bounty fund & parent curator. + // 2, Parent curator adds child-bounty child-bounty-1, moves to "Active" state. // 3, Test for DB state of `ChildBounties`. // Make the parent bounty. @@ -302,21 +309,22 @@ fn child_bounty_assign_curator() { System::set_block_number(2); >::on_initialize(2); - assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, 4)); - + let fee = 4; + assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, fee)); assert_ok!(Bounties::accept_curator(Origin::signed(4), 0)); // Bounty account status before adding child-bounty. assert_eq!(Balances::free_balance(Bounties::bounty_account_id(0)), 50); assert_eq!(Balances::reserved_balance(Bounties::bounty_account_id(0)), 0); - // Check the balance of master curator. - // Curator deposit is reserved for master curator on parent bounty. - assert_eq!(Balances::free_balance(4), 99); - assert_eq!(Balances::reserved_balance(4), 2); + // Check the balance of parent curator. + // Curator deposit is reserved for parent curator on parent bounty. + let expected_deposit = Bounties::calculate_curator_deposit(&fee); + assert_eq!(Balances::free_balance(4), 101 - expected_deposit); + assert_eq!(Balances::reserved_balance(4), expected_deposit); // Add child-bounty. - // Acc-4 is the master curator & make sure enough deposit. + // Acc-4 is the parent curator & make sure enough deposit. assert_ok!(ChildBounties::add_child_bounty(Origin::signed(4), 0, 10, b"12345-p1".to_vec())); assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 }); @@ -329,22 +337,23 @@ fn child_bounty_assign_curator() { assert_eq!(Balances::free_balance(ChildBounties::child_bounty_account_id(0)), 10); assert_eq!(Balances::reserved_balance(ChildBounties::child_bounty_account_id(0)), 0); - assert_ok!(ChildBounties::propose_curator(Origin::signed(4), 0, 0, 8, 2)); + let fee = 6u64; + assert_ok!(ChildBounties::propose_curator(Origin::signed(4), 0, 0, 8, fee)); assert_eq!( ChildBounties::child_bounties(0, 0).unwrap(), ChildBounty { parent_bounty: 0, value: 10, - fee: 2, + fee, curator_deposit: 0, status: ChildBountyStatus::CuratorProposed { curator: 8 }, } ); - // Check the balance of master curator. - assert_eq!(Balances::free_balance(4), 99); - assert_eq!(Balances::reserved_balance(4), 2); + // Check the balance of parent curator. + assert_eq!(Balances::free_balance(4), 101 - expected_deposit); + assert_eq!(Balances::reserved_balance(4), expected_deposit); assert_noop!( ChildBounties::accept_curator(Origin::signed(3), 0, 0), @@ -353,20 +362,22 @@ fn child_bounty_assign_curator() { assert_ok!(ChildBounties::accept_curator(Origin::signed(8), 0, 0)); + let expected_child_deposit = CuratorDepositMultiplier::get() * fee; + assert_eq!( ChildBounties::child_bounties(0, 0).unwrap(), ChildBounty { parent_bounty: 0, value: 10, - fee: 2, - curator_deposit: 1, + fee, + curator_deposit: expected_child_deposit, status: ChildBountyStatus::Active { curator: 8 }, } ); - // Deposit for child-bounty curator is reserved. - assert_eq!(Balances::free_balance(8), 100); - assert_eq!(Balances::reserved_balance(8), 1); + // Deposit for child-bounty curator deposit is reserved. + assert_eq!(Balances::free_balance(8), 101 - expected_child_deposit); + assert_eq!(Balances::reserved_balance(8), expected_child_deposit); // Bounty account status at exit. assert_eq!(Balances::free_balance(Bounties::bounty_account_id(0)), 40); @@ -411,7 +422,8 @@ fn award_claim_child_bounty() { assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 }); // Propose and accept curator for child-bounty. - assert_ok!(ChildBounties::propose_curator(Origin::signed(4), 0, 0, 8, 2)); + let fee = 8; + assert_ok!(ChildBounties::propose_curator(Origin::signed(4), 0, 0, 8, fee)); assert_ok!(ChildBounties::accept_curator(Origin::signed(8), 0, 0)); // Award child-bounty. @@ -423,13 +435,14 @@ fn award_claim_child_bounty() { assert_ok!(ChildBounties::award_child_bounty(Origin::signed(8), 0, 0, 7)); + let expected_deposit = CuratorDepositMultiplier::get() * fee; assert_eq!( ChildBounties::child_bounties(0, 0).unwrap(), ChildBounty { parent_bounty: 0, value: 10, - fee: 2, - curator_deposit: 1, + fee, + curator_deposit: expected_deposit, status: ChildBountyStatus::PendingPayout { curator: 8, beneficiary: 7, @@ -450,11 +463,11 @@ fn award_claim_child_bounty() { assert_ok!(ChildBounties::claim_child_bounty(Origin::signed(7), 0, 0)); // Ensure child-bounty curator is paid with curator fee & deposit refund. - assert_eq!(Balances::free_balance(8), 103); + assert_eq!(Balances::free_balance(8), 101 + fee); assert_eq!(Balances::reserved_balance(8), 0); // Ensure executor is paid with beneficiary amount. - assert_eq!(Balances::free_balance(7), 8); + assert_eq!(Balances::free_balance(7), 10 - fee); assert_eq!(Balances::reserved_balance(7), 0); // Child-bounty account status. @@ -591,8 +604,8 @@ fn close_child_bounty_pending() { System::set_block_number(2); >::on_initialize(2); - assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, 6)); - + let parent_fee = 6; + assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, parent_fee)); assert_ok!(Bounties::accept_curator(Origin::signed(4), 0)); // Child-bounty. @@ -601,8 +614,10 @@ fn close_child_bounty_pending() { assert_eq!(last_event(), ChildBountiesEvent::Added { index: 0, child_index: 0 }); // Propose and accept curator for child-bounty. - assert_ok!(ChildBounties::propose_curator(Origin::signed(4), 0, 0, 8, 2)); + let child_fee = 4; + assert_ok!(ChildBounties::propose_curator(Origin::signed(4), 0, 0, 8, child_fee)); assert_ok!(ChildBounties::accept_curator(Origin::signed(8), 0, 0)); + let expected_child_deposit = CuratorDepositMin::get(); assert_ok!(ChildBounties::award_child_bounty(Origin::signed(8), 0, 0, 7)); @@ -616,8 +631,8 @@ fn close_child_bounty_pending() { assert_eq!(ChildBounties::parent_child_bounties(0), 1); // Ensure no changes in child-bounty curator balance. - assert_eq!(Balances::free_balance(8), 100); - assert_eq!(Balances::reserved_balance(8), 1); + assert_eq!(Balances::reserved_balance(8), expected_child_deposit); + assert_eq!(Balances::free_balance(8), 101 - expected_child_deposit); // Child-bounty account status. assert_eq!(Balances::free_balance(ChildBounties::child_bounty_account_id(0)), 10); @@ -755,7 +770,6 @@ fn child_bounty_active_unassign_curator() { >::on_initialize(2); assert_ok!(Bounties::propose_curator(Origin::root(), 0, 4, 6)); - assert_ok!(Bounties::accept_curator(Origin::signed(4), 0)); // Create Child-bounty. @@ -766,16 +780,18 @@ fn child_bounty_active_unassign_curator() { >::on_initialize(3); // Propose and accept curator for child-bounty. - assert_ok!(ChildBounties::propose_curator(Origin::signed(4), 0, 0, 8, 2)); + let fee = 6; + assert_ok!(ChildBounties::propose_curator(Origin::signed(4), 0, 0, 8, fee)); assert_ok!(ChildBounties::accept_curator(Origin::signed(8), 0, 0)); + let expected_child_deposit = CuratorDepositMultiplier::get() * fee; assert_eq!( ChildBounties::child_bounties(0, 0).unwrap(), ChildBounty { parent_bounty: 0, value: 10, - fee: 2, - curator_deposit: 1, + fee, + curator_deposit: expected_child_deposit, status: ChildBountyStatus::Active { curator: 8 }, } ); @@ -792,27 +808,29 @@ fn child_bounty_active_unassign_curator() { ChildBounty { parent_bounty: 0, value: 10, - fee: 2, + fee, curator_deposit: 0, status: ChildBountyStatus::Added, } ); // Ensure child-bounty curator was slashed. - assert_eq!(Balances::free_balance(8), 100); + assert_eq!(Balances::free_balance(8), 101 - expected_child_deposit); assert_eq!(Balances::reserved_balance(8), 0); // slashed // Propose and accept curator for child-bounty again. - assert_ok!(ChildBounties::propose_curator(Origin::signed(4), 0, 0, 7, 2)); + let fee = 2; + assert_ok!(ChildBounties::propose_curator(Origin::signed(4), 0, 0, 7, fee)); assert_ok!(ChildBounties::accept_curator(Origin::signed(7), 0, 0)); + let expected_child_deposit = CuratorDepositMin::get(); assert_eq!( ChildBounties::child_bounties(0, 0).unwrap(), ChildBounty { parent_bounty: 0, value: 10, - fee: 2, - curator_deposit: 1, + fee, + curator_deposit: expected_child_deposit, status: ChildBountyStatus::Active { curator: 7 }, } ); @@ -820,7 +838,7 @@ fn child_bounty_active_unassign_curator() { System::set_block_number(5); >::on_initialize(5); - // Unassign curator again - from master curator. + // Unassign curator again - from parent curator. assert_ok!(ChildBounties::unassign_curator(Origin::signed(4), 0, 0)); // Verify updated child-bounty status. @@ -836,7 +854,7 @@ fn child_bounty_active_unassign_curator() { ); // Ensure child-bounty curator was slashed. - assert_eq!(Balances::free_balance(7), 100); + assert_eq!(Balances::free_balance(7), 101 - expected_child_deposit); assert_eq!(Balances::reserved_balance(7), 0); // slashed // Propose and accept curator for child-bounty again. @@ -848,8 +866,8 @@ fn child_bounty_active_unassign_curator() { ChildBounty { parent_bounty: 0, value: 10, - fee: 2, - curator_deposit: 1, + fee, + curator_deposit: expected_child_deposit, status: ChildBountyStatus::Active { curator: 6 }, } ); @@ -877,16 +895,18 @@ fn child_bounty_active_unassign_curator() { assert_eq!(Balances::reserved_balance(6), 0); // Propose and accept curator for child-bounty one last time. - assert_ok!(ChildBounties::propose_curator(Origin::signed(4), 0, 0, 6, 2)); + let fee = 2; + assert_ok!(ChildBounties::propose_curator(Origin::signed(4), 0, 0, 6, fee)); assert_ok!(ChildBounties::accept_curator(Origin::signed(6), 0, 0)); + let expected_child_deposit = CuratorDepositMin::get(); assert_eq!( ChildBounties::child_bounties(0, 0).unwrap(), ChildBounty { parent_bounty: 0, value: 10, - fee: 2, - curator_deposit: 1, + fee, + curator_deposit: expected_child_deposit, status: ChildBountyStatus::Active { curator: 6 }, } ); @@ -920,7 +940,7 @@ fn child_bounty_active_unassign_curator() { ); // Ensure child-bounty curator was slashed. - assert_eq!(Balances::free_balance(6), 100); // slashed + assert_eq!(Balances::free_balance(6), 101 - expected_child_deposit); // slashed assert_eq!(Balances::reserved_balance(6), 0); }); } @@ -960,16 +980,18 @@ fn parent_bounty_inactive_unassign_curator_child_bounty() { >::on_initialize(3); // Propose and accept curator for child-bounty. - assert_ok!(ChildBounties::propose_curator(Origin::signed(4), 0, 0, 8, 2)); + let fee = 8; + assert_ok!(ChildBounties::propose_curator(Origin::signed(4), 0, 0, 8, fee)); assert_ok!(ChildBounties::accept_curator(Origin::signed(8), 0, 0)); + let expected_child_deposit = CuratorDepositMultiplier::get() * fee; assert_eq!( ChildBounties::child_bounties(0, 0).unwrap(), ChildBounty { parent_bounty: 0, value: 10, - fee: 2, - curator_deposit: 1, + fee, + curator_deposit: expected_child_deposit, status: ChildBountyStatus::Active { curator: 8 }, } ); @@ -999,14 +1021,14 @@ fn parent_bounty_inactive_unassign_curator_child_bounty() { ChildBounty { parent_bounty: 0, value: 10, - fee: 2, + fee, curator_deposit: 0, status: ChildBountyStatus::Added, } ); // Ensure child-bounty curator was slashed. - assert_eq!(Balances::free_balance(8), 100); + assert_eq!(Balances::free_balance(8), 101 - expected_child_deposit); assert_eq!(Balances::reserved_balance(8), 0); // slashed System::set_block_number(6); @@ -1020,16 +1042,18 @@ fn parent_bounty_inactive_unassign_curator_child_bounty() { >::on_initialize(7); // Propose and accept curator for child-bounty again. - assert_ok!(ChildBounties::propose_curator(Origin::signed(5), 0, 0, 7, 2)); + let fee = 2; + assert_ok!(ChildBounties::propose_curator(Origin::signed(5), 0, 0, 7, fee)); assert_ok!(ChildBounties::accept_curator(Origin::signed(7), 0, 0)); + let expected_deposit = CuratorDepositMin::get(); assert_eq!( ChildBounties::child_bounties(0, 0).unwrap(), ChildBounty { parent_bounty: 0, value: 10, - fee: 2, - curator_deposit: 1, + fee, + curator_deposit: expected_deposit, status: ChildBountyStatus::Active { curator: 7 }, } ); @@ -1048,7 +1072,7 @@ fn parent_bounty_inactive_unassign_curator_child_bounty() { System::set_block_number(9); >::on_initialize(9); - // Unassign curator again - from master curator. + // Unassign curator again - from parent curator. assert_ok!(ChildBounties::unassign_curator(Origin::signed(7), 0, 0)); // Verify updated child-bounty status. @@ -1157,25 +1181,26 @@ fn children_curator_fee_calculation_test() { System::set_block_number(4); >::on_initialize(4); - // Propose curator for child-bounty. - assert_ok!(ChildBounties::propose_curator(Origin::signed(4), 0, 0, 8, 2)); + let fee = 6; + // Propose curator for child-bounty. + assert_ok!(ChildBounties::propose_curator(Origin::signed(4), 0, 0, 8, fee)); // Check curator fee added to the sum. - assert_eq!(ChildBounties::children_curator_fees(0), 2); - + assert_eq!(ChildBounties::children_curator_fees(0), fee); // Accept curator for child-bounty. assert_ok!(ChildBounties::accept_curator(Origin::signed(8), 0, 0)); - // Award child-bounty. assert_ok!(ChildBounties::award_child_bounty(Origin::signed(8), 0, 0, 7)); + let expected_child_deposit = CuratorDepositMultiplier::get() * fee; + assert_eq!( ChildBounties::child_bounties(0, 0).unwrap(), ChildBounty { parent_bounty: 0, value: 10, - fee: 2, - curator_deposit: 1, + fee, + curator_deposit: expected_child_deposit, status: ChildBountyStatus::PendingPayout { curator: 8, beneficiary: 7, @@ -1201,7 +1226,7 @@ fn children_curator_fee_calculation_test() { assert_ok!(Bounties::claim_bounty(Origin::signed(9), 0)); // Ensure parent-bounty curator received correctly reduced fee. - assert_eq!(Balances::free_balance(4), 105); // 101 + 6 - 2 + assert_eq!(Balances::free_balance(4), 101 + 6 - fee); // 101 + 6 - 2 assert_eq!(Balances::reserved_balance(4), 0); // Verify parent-bounty beneficiary balance. @@ -1209,3 +1234,176 @@ fn children_curator_fee_calculation_test() { assert_eq!(Balances::reserved_balance(9), 0); }); } + +#[test] +fn accept_curator_handles_different_deposit_calculations() { + // This test will verify that a bounty with and without a fee results + // in a different curator deposit, and if the child curator matches the parent curator. + new_test_ext().execute_with(|| { + // Setup a parent bounty. + let parent_curator = 0; + let parent_index = 0; + let parent_value = 1_000_000; + let parent_fee = 10_000; + + System::set_block_number(1); + Balances::make_free_balance_be(&Treasury::account_id(), parent_value * 3); + Balances::make_free_balance_be(&parent_curator, parent_fee * 100); + assert_ok!(Bounties::propose_bounty( + Origin::signed(parent_curator), + parent_value, + b"12345".to_vec() + )); + assert_ok!(Bounties::approve_bounty(Origin::root(), parent_index)); + + System::set_block_number(2); + >::on_initialize(2); + + assert_ok!(Bounties::propose_curator( + Origin::root(), + parent_index, + parent_curator, + parent_fee + )); + assert_ok!(Bounties::accept_curator(Origin::signed(parent_curator), parent_index)); + + // Now we can start creating some child bounties. + // Case 1: Parent and child curator are not the same. + + let child_index = 0; + let child_curator = 1; + let child_value = 1_000; + let child_fee = 100; + let starting_balance = 100 * child_fee + child_value; + + Balances::make_free_balance_be(&child_curator, starting_balance); + assert_ok!(ChildBounties::add_child_bounty( + Origin::signed(parent_curator), + parent_index, + child_value, + b"12345-p1".to_vec() + )); + System::set_block_number(3); + >::on_initialize(3); + assert_ok!(ChildBounties::propose_curator( + Origin::signed(parent_curator), + parent_index, + child_index, + child_curator, + child_fee + )); + assert_ok!(ChildBounties::accept_curator( + Origin::signed(child_curator), + parent_index, + child_index + )); + + let expected_deposit = CuratorDepositMultiplier::get() * child_fee; + assert_eq!(Balances::free_balance(child_curator), starting_balance - expected_deposit); + assert_eq!(Balances::reserved_balance(child_curator), expected_deposit); + + // Case 2: Parent and child curator are the same. + + let child_index = 1; + let child_curator = parent_curator; // The same as parent bounty curator + let child_value = 1_000; + let child_fee = 10; + + let free_before = Balances::free_balance(&parent_curator); + let reserved_before = Balances::reserved_balance(&parent_curator); + + assert_ok!(ChildBounties::add_child_bounty( + Origin::signed(parent_curator), + parent_index, + child_value, + b"12345-p1".to_vec() + )); + System::set_block_number(4); + >::on_initialize(4); + assert_ok!(ChildBounties::propose_curator( + Origin::signed(parent_curator), + parent_index, + child_index, + child_curator, + child_fee + )); + assert_ok!(ChildBounties::accept_curator( + Origin::signed(child_curator), + parent_index, + child_index + )); + + // No expected deposit + assert_eq!(Balances::free_balance(child_curator), free_before); + assert_eq!(Balances::reserved_balance(child_curator), reserved_before); + + // Case 3: Upper Limit + + let child_index = 2; + let child_curator = 2; + let child_value = 10_000; + let child_fee = 5_000; + + Balances::make_free_balance_be(&child_curator, starting_balance); + assert_ok!(ChildBounties::add_child_bounty( + Origin::signed(parent_curator), + parent_index, + child_value, + b"12345-p1".to_vec() + )); + System::set_block_number(5); + >::on_initialize(5); + assert_ok!(ChildBounties::propose_curator( + Origin::signed(parent_curator), + parent_index, + child_index, + child_curator, + child_fee + )); + assert_ok!(ChildBounties::accept_curator( + Origin::signed(child_curator), + parent_index, + child_index + )); + + let expected_deposit = CuratorDepositMax::get(); + assert_eq!(Balances::free_balance(child_curator), starting_balance - expected_deposit); + assert_eq!(Balances::reserved_balance(child_curator), expected_deposit); + + // There is a max number of child bounties at a time. + assert_ok!(ChildBounties::impl_close_child_bounty(parent_index, child_index)); + + // Case 4: Lower Limit + + let child_index = 3; + let child_curator = 3; + let child_value = 10_000; + let child_fee = 0; + + Balances::make_free_balance_be(&child_curator, starting_balance); + assert_ok!(ChildBounties::add_child_bounty( + Origin::signed(parent_curator), + parent_index, + child_value, + b"12345-p1".to_vec() + )); + System::set_block_number(5); + >::on_initialize(5); + assert_ok!(ChildBounties::propose_curator( + Origin::signed(parent_curator), + parent_index, + child_index, + child_curator, + child_fee + )); + assert_ok!(ChildBounties::accept_curator( + Origin::signed(child_curator), + parent_index, + child_index + )); + + let expected_deposit = CuratorDepositMin::get(); + assert_eq!(Balances::free_balance(child_curator), starting_balance - expected_deposit); + assert_eq!(Balances::reserved_balance(child_curator), expected_deposit); + }); +} From 8326d4d468bdd82f8e550385ab91474f87280e03 Mon Sep 17 00:00:00 2001 From: Qinxuan Chen Date: Fri, 25 Mar 2022 21:45:01 +0800 Subject: [PATCH 060/484] pallet-conviction-voting: make the pallet instantiable (#11088) * pallet-conviction-voting: make the pallet instanceable Signed-off-by: koushiro * Add default type param for some type alias Signed-off-by: koushiro --- frame/conviction-voting/src/conviction.rs | 4 +- frame/conviction-voting/src/lib.rs | 185 ++++++++++++---------- frame/conviction-voting/src/tests.rs | 5 +- frame/conviction-voting/src/types.rs | 5 +- 4 files changed, 109 insertions(+), 90 deletions(-) diff --git a/frame/conviction-voting/src/conviction.rs b/frame/conviction-voting/src/conviction.rs index 129f2771124b5..1feff35b19fcc 100644 --- a/frame/conviction-voting/src/conviction.rs +++ b/frame/conviction-voting/src/conviction.rs @@ -17,14 +17,14 @@ //! The conviction datatype. -use crate::types::Delegations; use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_runtime::{ traits::{Bounded, CheckedDiv, CheckedMul, Zero}, RuntimeDebug, }; -use sp_std::{convert::TryFrom, result::Result}; + +use crate::types::Delegations; /// A value denoting the strength of conviction of a vote. #[derive( diff --git a/frame/conviction-voting/src/lib.rs b/frame/conviction-voting/src/lib.rs index 8e7e0d91b1cf4..af91e99fb3796 100644 --- a/frame/conviction-voting/src/lib.rs +++ b/frame/conviction-voting/src/lib.rs @@ -28,6 +28,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use frame_support::{ + dispatch::{DispatchError, DispatchResult}, ensure, traits::{ fungible, Currency, Get, LockIdentifier, LockableCurrency, PollStatus, Polling, @@ -36,7 +37,7 @@ use frame_support::{ }; use sp_runtime::{ traits::{AtLeast32BitUnsigned, Saturating, Zero}, - ArithmeticError, DispatchError, DispatchResult, Perbill, + ArithmeticError, Perbill, }; use sp_std::prelude::*; @@ -44,11 +45,14 @@ mod conviction; mod types; mod vote; pub mod weights; -pub use conviction::Conviction; -pub use pallet::*; -pub use types::{Delegations, Tally, UnvoteScope}; -pub use vote::{AccountVote, Casting, Delegating, Vote, Voting}; -pub use weights::WeightInfo; + +pub use self::{ + conviction::Conviction, + pallet::*, + types::{Delegations, Tally, UnvoteScope}, + vote::{AccountVote, Casting, Delegating, Vote, Voting}, + weights::WeightInfo, +}; #[cfg(test)] mod tests; @@ -58,44 +62,43 @@ pub mod benchmarking; const CONVICTION_VOTING_ID: LockIdentifier = *b"pyconvot"; -type BalanceOf = - <::Currency as Currency<::AccountId>>::Balance; -type VotingOf = Voting< - BalanceOf, +type BalanceOf = + <>::Currency as Currency<::AccountId>>::Balance; +type VotingOf = Voting< + BalanceOf, ::AccountId, ::BlockNumber, - PollIndexOf, - ::MaxVotes, + PollIndexOf, + >::MaxVotes, >; #[allow(dead_code)] -type DelegatingOf = Delegating< - BalanceOf, +type DelegatingOf = Delegating< + BalanceOf, ::AccountId, ::BlockNumber, >; -pub type TallyOf = Tally, ::MaxTurnout>; -pub type VotesOf = BalanceOf; -type PollIndexOf = <::Polls as Polling>>::Index; +pub type TallyOf = Tally, >::MaxTurnout>; +pub type VotesOf = BalanceOf; +type PollIndexOf = <>::Polls as Polling>>::Index; #[cfg(feature = "runtime-benchmarks")] -type IndexOf = <::Polls as Polling>>::Index; -type ClassOf = <::Polls as Polling>>::Class; +type IndexOf = <>::Polls as Polling>>::Index; +type ClassOf = <>::Polls as Polling>>::Class; #[frame_support::pallet] pub mod pallet { use super::*; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; - use sp_runtime::DispatchResult; #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] #[pallet::without_storage_info] - pub struct Pallet(_); + pub struct Pallet(_); #[pallet::config] - pub trait Config: frame_system::Config + Sized { + pub trait Config: frame_system::Config + Sized { // System level stuff. - type Event: From> + IsType<::Event>; + type Event: From> + IsType<::Event>; /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; /// Currency type with which voting happens. @@ -104,12 +107,16 @@ pub mod pallet { + fungible::Inspect; /// The implementation of the logic which conducts polls. - type Polls: Polling, Votes = BalanceOf, Moment = Self::BlockNumber>; + type Polls: Polling< + TallyOf, + Votes = BalanceOf, + Moment = Self::BlockNumber, + >; /// The maximum amount of tokens which may be used for voting. May just be /// `Currency::total_issuance`, but you might want to reduce this in order to account for /// funds in the system which are unable to vote (e.g. parachain auction deposits). - type MaxTurnout: Get>; + type MaxTurnout: Get>; /// The maximum number of concurrent votes an account may have. /// @@ -129,13 +136,13 @@ pub mod pallet { /// All voting for a particular voter in a particular voting class. We store the balance for the /// number of votes that we have recorded. #[pallet::storage] - pub type VotingFor = StorageDoubleMap< + pub type VotingFor, I: 'static = ()> = StorageDoubleMap< _, Twox64Concat, T::AccountId, Twox64Concat, - ClassOf, - VotingOf, + ClassOf, + VotingOf, ValueQuery, >; @@ -143,12 +150,17 @@ pub mod pallet { /// require. The actual amount locked on behalf of this pallet should always be the maximum of /// this list. #[pallet::storage] - pub type ClassLocksFor = - StorageMap<_, Twox64Concat, T::AccountId, Vec<(ClassOf, BalanceOf)>, ValueQuery>; + pub type ClassLocksFor, I: 'static = ()> = StorageMap< + _, + Twox64Concat, + T::AccountId, + Vec<(ClassOf, BalanceOf)>, + ValueQuery, + >; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { + pub enum Event, I: 'static = ()> { /// An account has delegated their vote to another account. \[who, target\] Delegated(T::AccountId, T::AccountId), /// An \[account\] has cancelled a previous delegation operation. @@ -156,7 +168,7 @@ pub mod pallet { } #[pallet::error] - pub enum Error { + pub enum Error { /// Poll is not ongoing. NotOngoing, /// The given account did not vote on the poll. @@ -185,7 +197,7 @@ pub mod pallet { } #[pallet::call] - impl Pallet { + impl, I: 'static> Pallet { /// Vote in a poll. If `vote.is_aye()`, the vote is to enact the proposal; /// otherwise it is a vote to keep the status quo. /// @@ -198,8 +210,8 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::vote_new().max(T::WeightInfo::vote_existing()))] pub fn vote( origin: OriginFor, - #[pallet::compact] poll_index: PollIndexOf, - vote: AccountVote>, + #[pallet::compact] poll_index: PollIndexOf, + vote: AccountVote>, ) -> DispatchResult { let who = ensure_signed(origin)?; Self::try_vote(&who, poll_index, vote) @@ -233,10 +245,10 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::delegate(T::MaxVotes::get()))] pub fn delegate( origin: OriginFor, - class: ClassOf, + class: ClassOf, to: T::AccountId, conviction: Conviction, - balance: BalanceOf, + balance: BalanceOf, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; let votes = Self::try_delegate(who, class, to, conviction, balance)?; @@ -261,7 +273,10 @@ pub mod pallet { // NOTE: weight must cover an incorrect voting of origin with max votes, this is ensure // because a valid delegation cover decoding a direct voting with max votes. #[pallet::weight(T::WeightInfo::undelegate(T::MaxVotes::get().into()))] - pub fn undelegate(origin: OriginFor, class: ClassOf) -> DispatchResultWithPostInfo { + pub fn undelegate( + origin: OriginFor, + class: ClassOf, + ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; let votes = Self::try_undelegate(who, class)?; Ok(Some(T::WeightInfo::undelegate(votes)).into()) @@ -279,7 +294,7 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::unlock())] pub fn unlock( origin: OriginFor, - class: ClassOf, + class: ClassOf, target: T::AccountId, ) -> DispatchResult { ensure_signed(origin)?; @@ -319,8 +334,8 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::remove_vote())] pub fn remove_vote( origin: OriginFor, - class: Option>, - index: PollIndexOf, + class: Option>, + index: PollIndexOf, ) -> DispatchResult { let who = ensure_signed(origin)?; Self::try_remove_vote(&who, index, class, UnvoteScope::Any) @@ -346,8 +361,8 @@ pub mod pallet { pub fn remove_other_vote( origin: OriginFor, target: T::AccountId, - class: ClassOf, - index: PollIndexOf, + class: ClassOf, + index: PollIndexOf, ) -> DispatchResult { let who = ensure_signed(origin)?; let scope = if target == who { UnvoteScope::Any } else { UnvoteScope::OnlyExpired }; @@ -357,17 +372,17 @@ pub mod pallet { } } -impl Pallet { +impl, I: 'static> Pallet { /// Actually enact a vote, if legit. fn try_vote( who: &T::AccountId, - poll_index: PollIndexOf, - vote: AccountVote>, + poll_index: PollIndexOf, + vote: AccountVote>, ) -> DispatchResult { - ensure!(vote.balance() <= T::Currency::free_balance(who), Error::::InsufficientFunds); + ensure!(vote.balance() <= T::Currency::free_balance(who), Error::::InsufficientFunds); T::Polls::try_access_poll(poll_index, |poll_status| { - let (tally, class) = poll_status.ensure_ongoing().ok_or(Error::::NotOngoing)?; - VotingFor::::try_mutate(who, &class, |voting| { + let (tally, class) = poll_status.ensure_ongoing().ok_or(Error::::NotOngoing)?; + VotingFor::::try_mutate(who, &class, |voting| { if let Voting::Casting(Casting { ref mut votes, delegations, .. }) = voting { match votes.binary_search_by_key(&poll_index, |i| i.0) { Ok(i) => { @@ -381,7 +396,7 @@ impl Pallet { Err(i) => { votes .try_insert(i, (poll_index, vote)) - .map_err(|()| Error::::MaxVotesReached)?; + .map_err(|()| Error::::MaxVotesReached)?; }, } // Shouldn't be possible to fail, but we handle it gracefully. @@ -390,7 +405,7 @@ impl Pallet { tally.increase(approve, *delegations); } } else { - return Err(Error::::AlreadyDelegating.into()) + return Err(Error::::AlreadyDelegating.into()) } // Extend the lock to `balance` (rather than setting it) since we don't know what // other votes are in place. @@ -408,23 +423,23 @@ impl Pallet { /// This will generally be combined with a call to `unlock`. fn try_remove_vote( who: &T::AccountId, - poll_index: PollIndexOf, - class_hint: Option>, + poll_index: PollIndexOf, + class_hint: Option>, scope: UnvoteScope, ) -> DispatchResult { let class = class_hint .or_else(|| Some(T::Polls::as_ongoing(poll_index)?.1)) - .ok_or(Error::::ClassNeeded)?; - VotingFor::::try_mutate(who, class, |voting| { + .ok_or(Error::::ClassNeeded)?; + VotingFor::::try_mutate(who, class, |voting| { if let Voting::Casting(Casting { ref mut votes, delegations, ref mut prior }) = voting { let i = votes .binary_search_by_key(&poll_index, |i| i.0) - .map_err(|_| Error::::NotVoter)?; + .map_err(|_| Error::::NotVoter)?; let v = votes.remove(i); T::Polls::try_access_poll(poll_index, |poll_status| match poll_status { PollStatus::Ongoing(tally, _) => { - ensure!(matches!(scope, UnvoteScope::Any), Error::::NoPermission); + ensure!(matches!(scope, UnvoteScope::Any), Error::::NoPermission); // Shouldn't be possible to fail, but we handle it gracefully. tally.remove(v.1).ok_or(ArithmeticError::Underflow)?; if let Some(approve) = v.1.as_standard() { @@ -441,7 +456,7 @@ impl Pallet { if now < unlock_at { ensure!( matches!(scope, UnvoteScope::Any), - Error::::NoPermissionYet + Error::::NoPermissionYet ); prior.accumulate(unlock_at, balance) } @@ -459,10 +474,10 @@ impl Pallet { /// Return the number of votes for `who` fn increase_upstream_delegation( who: &T::AccountId, - class: &ClassOf, - amount: Delegations>, + class: &ClassOf, + amount: Delegations>, ) -> u32 { - VotingFor::::mutate(who, class, |voting| match voting { + VotingFor::::mutate(who, class, |voting| match voting { Voting::Delegating(Delegating { delegations, .. }) => { // We don't support second level delegating, so we don't need to do anything more. *delegations = delegations.saturating_add(amount); @@ -487,10 +502,10 @@ impl Pallet { /// Return the number of votes for `who` fn reduce_upstream_delegation( who: &T::AccountId, - class: &ClassOf, - amount: Delegations>, + class: &ClassOf, + amount: Delegations>, ) -> u32 { - VotingFor::::mutate(who, class, |voting| match voting { + VotingFor::::mutate(who, class, |voting| match voting { Voting::Delegating(Delegating { delegations, .. }) => { // We don't support second level delegating, so we don't need to do anything more. *delegations = delegations.saturating_sub(amount); @@ -517,16 +532,16 @@ impl Pallet { /// Return the upstream number of votes. fn try_delegate( who: T::AccountId, - class: ClassOf, + class: ClassOf, target: T::AccountId, conviction: Conviction, - balance: BalanceOf, + balance: BalanceOf, ) -> Result { - ensure!(who != target, Error::::Nonsense); - T::Polls::classes().binary_search(&class).map_err(|_| Error::::BadClass)?; - ensure!(balance <= T::Currency::free_balance(&who), Error::::InsufficientFunds); + ensure!(who != target, Error::::Nonsense); + T::Polls::classes().binary_search(&class).map_err(|_| Error::::BadClass)?; + ensure!(balance <= T::Currency::free_balance(&who), Error::::InsufficientFunds); let votes = - VotingFor::::try_mutate(&who, &class, |voting| -> Result { + VotingFor::::try_mutate(&who, &class, |voting| -> Result { let old = sp_std::mem::replace( voting, Voting::Delegating(Delegating { @@ -538,10 +553,10 @@ impl Pallet { }), ); match old { - Voting::Delegating(Delegating { .. }) => Err(Error::::AlreadyDelegating)?, + Voting::Delegating(Delegating { .. }) => Err(Error::::AlreadyDelegating)?, Voting::Casting(Casting { votes, delegations, prior }) => { // here we just ensure that we're currently idling with no votes recorded. - ensure!(votes.is_empty(), Error::::AlreadyVoting); + ensure!(votes.is_empty(), Error::::AlreadyVoting); voting.set_common(delegations, prior); }, } @@ -553,16 +568,16 @@ impl Pallet { Self::extend_lock(&who, &class, balance); Ok(votes) })?; - Self::deposit_event(Event::::Delegated(who, target)); + Self::deposit_event(Event::::Delegated(who, target)); Ok(votes) } /// Attempt to end the current delegation. /// /// Return the number of votes of upstream. - fn try_undelegate(who: T::AccountId, class: ClassOf) -> Result { + fn try_undelegate(who: T::AccountId, class: ClassOf) -> Result { let votes = - VotingFor::::try_mutate(&who, &class, |voting| -> Result { + VotingFor::::try_mutate(&who, &class, |voting| -> Result { match sp_std::mem::replace(voting, Voting::default()) { Voting::Delegating(Delegating { balance, @@ -589,29 +604,31 @@ impl Pallet { Ok(votes) }, - Voting::Casting(_) => Err(Error::::NotDelegating.into()), + Voting::Casting(_) => Err(Error::::NotDelegating.into()), } })?; - Self::deposit_event(Event::::Undelegated(who)); + Self::deposit_event(Event::::Undelegated(who)); Ok(votes) } - fn extend_lock(who: &T::AccountId, class: &ClassOf, amount: BalanceOf) { - ClassLocksFor::::mutate(who, |locks| match locks.iter().position(|x| &x.0 == class) { - Some(i) => locks[i].1 = locks[i].1.max(amount), - None => locks.push((class.clone(), amount)), + fn extend_lock(who: &T::AccountId, class: &ClassOf, amount: BalanceOf) { + ClassLocksFor::::mutate(who, |locks| { + match locks.iter().position(|x| &x.0 == class) { + Some(i) => locks[i].1 = locks[i].1.max(amount), + None => locks.push((class.clone(), amount)), + } }); T::Currency::extend_lock(CONVICTION_VOTING_ID, who, amount, WithdrawReasons::TRANSFER); } /// Rejig the lock on an account. It will never get more stringent (since that would indicate /// a security hole) but may be reduced from what they are currently. - fn update_lock(class: &ClassOf, who: &T::AccountId) { - let class_lock_needed = VotingFor::::mutate(who, class, |voting| { + fn update_lock(class: &ClassOf, who: &T::AccountId) { + let class_lock_needed = VotingFor::::mutate(who, class, |voting| { voting.rejig(frame_system::Pallet::::block_number()); voting.locked_balance() }); - let lock_needed = ClassLocksFor::::mutate(who, |locks| { + let lock_needed = ClassLocksFor::::mutate(who, |locks| { locks.retain(|x| &x.0 != class); if !class_lock_needed.is_zero() { locks.push((class.clone(), class_lock_needed)); diff --git a/frame/conviction-voting/src/tests.rs b/frame/conviction-voting/src/tests.rs index cedb23b02a8db..6a8bad5d8944e 100644 --- a/frame/conviction-voting/src/tests.rs +++ b/frame/conviction-voting/src/tests.rs @@ -19,8 +19,6 @@ use std::collections::BTreeMap; -use super::*; -use crate as pallet_conviction_voting; use frame_support::{ assert_noop, assert_ok, parameter_types, traits::{ConstU32, ConstU64, Contains, Polling}, @@ -31,6 +29,9 @@ use sp_runtime::{ traits::{BlakeTwo256, IdentityLookup}, }; +use super::*; +use crate as pallet_conviction_voting; + type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; diff --git a/frame/conviction-voting/src/types.rs b/frame/conviction-voting/src/types.rs index 2ad1a164dd143..e2b5844ddd5df 100644 --- a/frame/conviction-voting/src/types.rs +++ b/frame/conviction-voting/src/types.rs @@ -19,8 +19,6 @@ use sp_std::marker::PhantomData; -use super::*; -use crate::{AccountVote, Conviction, Vote}; use codec::{Codec, Decode, Encode, MaxEncodedLen}; use frame_support::{ traits::VoteTally, CloneNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound, @@ -32,6 +30,9 @@ use sp_runtime::{ RuntimeDebug, }; +use super::*; +use crate::{AccountVote, Conviction, Vote}; + /// Info regarding an ongoing referendum. #[derive( CloneNoBound, From 259cd9e3d4dee5d93cb02f7cb518d04dbc7fa7fd Mon Sep 17 00:00:00 2001 From: Adrian Catangiu Date: Fri, 25 Mar 2022 17:31:42 +0200 Subject: [PATCH 061/484] Implement Lean BEEFY (#10882) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Simplified BEEFY worker logic based on the invariant that GRANDPA will always finalize 1st block of each new session, meaning BEEFY worker is guaranteed to receive finality notification for the BEEFY mandatory blocks. Under these conditions the current design is as follows: - session changes are detected based on BEEFY Digest present in BEEFY mandatory blocks, - on each new session new `Rounds` of voting is created, with old rounds being dropped (for gossip rounds, last 3 are still alive so votes are still being gossiped), - after processing finality for a block, the worker votes if a new voting target has become available as a result of said block finality processing, - incoming votes as well as self-created votes are processed and signed commitments are created for completed BEEFY voting rounds, - the worker votes if a new voting target becomes available once a round successfully completes. On worker startup, the current validator set is retrieved from the BEEFY pallet. If it is the genesis validator set, worker starts voting right away considering Block #1 as session start. Otherwise (not genesis), the worker will vote starting with mandatory block of the next session. Later on when we add the BEEFY initial-sync (catch-up) logic, the worker will sync all past mandatory blocks Signed Commitments and will be able to start voting right away. BEEFY mandatory block is the block with header containing the BEEFY `AuthoritiesChange` Digest, this block is guaranteed to be finalized by GRANDPA. This session-boundary block is signed by the ending-session's validator set. Next blocks will be signed by the new session's validator set. This behavior is consistent with what GRANDPA does as well. Also drop the limit N on active gossip rounds. In an adversarial network, a bad actor could create and gossip N invalid votes with round numbers larger than the current correct round number. This would lead to votes for correct rounds to no longer be gossiped. Add unit-tests for all components, including full voter consensus tests. Signed-off-by: Adrian Catangiu Co-authored-by: Tomasz Drwięga Co-authored-by: David Salami --- Cargo.lock | 10 + client/beefy/Cargo.toml | 14 +- client/beefy/src/gossip.rs | 306 ++++++----- client/beefy/src/lib.rs | 73 +-- client/beefy/src/metrics.rs | 13 +- client/beefy/src/round.rs | 358 +++++++++---- client/beefy/src/tests.rs | 590 ++++++++++++++++++++ client/beefy/src/worker.rs | 978 ++++++++++++++++++++++++---------- frame/beefy/src/lib.rs | 28 +- primitives/beefy/Cargo.toml | 5 + test-utils/runtime/Cargo.toml | 2 + test-utils/runtime/src/lib.rs | 6 + 12 files changed, 1815 insertions(+), 568 deletions(-) create mode 100644 client/beefy/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 1731ded2906cf..81a53ed858859 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -484,12 +484,15 @@ dependencies = [ "beefy-primitives", "fnv", "futures 0.3.19", + "futures-timer", "hex", "log 0.4.14", "parity-scale-codec", "parking_lot 0.12.0", "sc-chain-spec", "sc-client-api", + "sc-consensus", + "sc-finality-grandpa", "sc-keystore", "sc-network", "sc-network-gossip", @@ -500,13 +503,19 @@ dependencies = [ "sp-application-crypto", "sp-arithmetic", "sp-blockchain", + "sp-consensus", "sp-core", + "sp-finality-grandpa", + "sp-keyring", "sp-keystore", "sp-runtime", "sp-tracing", "strum", "substrate-prometheus-endpoint", + "substrate-test-runtime-client", + "tempfile", "thiserror", + "tokio", "wasm-timer", ] @@ -10688,6 +10697,7 @@ dependencies = [ name = "substrate-test-runtime" version = "2.0.0" dependencies = [ + "beefy-primitives", "cfg-if 1.0.0", "frame-support", "frame-system", diff --git a/client/beefy/Cargo.toml b/client/beefy/Cargo.toml index 1cd0f1fd50d80..02be645b3fc08 100644 --- a/client/beefy/Cargo.toml +++ b/client/beefy/Cargo.toml @@ -10,6 +10,7 @@ description = "BEEFY Client gadget for substrate" [dependencies] fnv = "1.0.6" futures = "0.3" +futures-timer = "3.0.1" hex = "0.4.2" log = "0.4" parking_lot = "0.12.0" @@ -23,22 +24,31 @@ sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } sp-application-crypto = { version = "6.0.0", path = "../../primitives/application-crypto" } sp-arithmetic = { version = "5.0.0", path = "../../primitives/arithmetic" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } +sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } sp-core = { version = "6.0.0", path = "../../primitives/core" } sp-keystore = { version = "0.12.0", path = "../../primitives/keystore" } sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } sc-chain-spec = { version = "4.0.0-dev", path = "../../client/chain-spec" } -sc-utils = { version = "4.0.0-dev", path = "../utils" } sc-client-api = { version = "4.0.0-dev", path = "../api" } +sc-finality-grandpa = { version = "0.10.0-dev", path = "../../client/finality-grandpa" } sc-keystore = { version = "4.0.0-dev", path = "../keystore" } sc-network = { version = "0.10.0-dev", path = "../network" } sc-network-gossip = { version = "0.10.0-dev", path = "../network-gossip" } +sc-utils = { version = "4.0.0-dev", path = "../utils" } beefy-primitives = { version = "4.0.0-dev", path = "../../primitives/beefy" } [dev-dependencies] -sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } +sc-consensus = { version = "0.10.0-dev", path = "../consensus/common" } sc-network-test = { version = "0.8.0", path = "../network/test" } +sp-finality-grandpa = { version = "4.0.0-dev", path = "../../primitives/finality-grandpa" } +sp-keyring = { version = "6.0.0", path = "../../primitives/keyring" } +sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } +substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } + serde = "1.0.136" strum = { version = "0.23", features = ["derive"] } +tokio = "1.15" +tempfile = "3.1.0" diff --git a/client/beefy/src/gossip.rs b/client/beefy/src/gossip.rs index 37358441ef88a..54d283fede32e 100644 --- a/client/beefy/src/gossip.rs +++ b/client/beefy/src/gossip.rs @@ -35,9 +35,6 @@ use beefy_primitives::{ use crate::keystore::BeefyKeystore; -// Limit BEEFY gossip by keeping only a bound number of voting rounds alive. -const MAX_LIVE_GOSSIP_ROUNDS: usize = 3; - // Timeout for rebroadcasting messages. const REBROADCAST_AFTER: Duration = Duration::from_secs(60 * 5); @@ -52,13 +49,50 @@ where /// A type that represents hash of the message. pub type MessageHash = [u8; 8]; -type KnownVotes = BTreeMap, fnv::FnvHashSet>; +struct KnownVotes { + last_done: Option>, + live: BTreeMap, fnv::FnvHashSet>, +} + +impl KnownVotes { + pub fn new() -> Self { + Self { last_done: None, live: BTreeMap::new() } + } + + /// Create new round votes set if not already present. + fn insert(&mut self, round: NumberFor) { + self.live.entry(round).or_default(); + } + + /// Remove `round` and older from live set, update `last_done` accordingly. + fn conclude(&mut self, round: NumberFor) { + self.live.retain(|&number, _| number > round); + self.last_done = self.last_done.max(Some(round)); + } + + /// Return true if `round` is newer than previously concluded rounds. + /// + /// Latest concluded round is still considered alive to allow proper gossiping for it. + fn is_live(&self, round: &NumberFor) -> bool { + Some(*round) >= self.last_done + } + + /// Add new _known_ `hash` to the round's known votes. + fn add_known(&mut self, round: &NumberFor, hash: MessageHash) { + self.live.get_mut(round).map(|known| known.insert(hash)); + } + + /// Check if `hash` is already part of round's known votes. + fn is_known(&self, round: &NumberFor, hash: &MessageHash) -> bool { + self.live.get(round).map(|known| known.contains(hash)).unwrap_or(false) + } +} /// BEEFY gossip validator /// /// Validate BEEFY gossip messages and limit the number of live BEEFY voting rounds. /// -/// Allows messages from last [`MAX_LIVE_GOSSIP_ROUNDS`] to flow, everything else gets +/// Allows messages for 'rounds >= last concluded' to flow, everything else gets /// rejected/expired. /// ///All messaging is handled in a single BEEFY global topic. @@ -78,57 +112,25 @@ where pub fn new() -> GossipValidator { GossipValidator { topic: topic::(), - known_votes: RwLock::new(BTreeMap::new()), + known_votes: RwLock::new(KnownVotes::new()), next_rebroadcast: Mutex::new(Instant::now() + REBROADCAST_AFTER), } } /// Note a voting round. /// - /// Noting `round` will keep `round` live. - /// - /// We retain the [`MAX_LIVE_GOSSIP_ROUNDS`] most **recent** voting rounds as live. - /// As long as a voting round is live, it will be gossiped to peer nodes. + /// Noting round will start a live `round`. pub(crate) fn note_round(&self, round: NumberFor) { - debug!(target: "beefy", "🥩 About to note round #{}", round); - - let mut live = self.known_votes.write(); - - if !live.contains_key(&round) { - live.insert(round, Default::default()); - } - - if live.len() > MAX_LIVE_GOSSIP_ROUNDS { - let to_remove = live.iter().next().map(|x| x.0).copied(); - if let Some(first) = to_remove { - live.remove(&first); - } - } - } - - fn add_known(known_votes: &mut KnownVotes, round: &NumberFor, hash: MessageHash) { - known_votes.get_mut(round).map(|known| known.insert(hash)); - } - - // Note that we will always keep the most recent unseen round alive. - // - // This is a preliminary fix and the detailed description why we are - // doing this can be found as part of the issue below - // - // https://github.com/paritytech/grandpa-bridge-gadget/issues/237 - // - fn is_live(known_votes: &KnownVotes, round: &NumberFor) -> bool { - let unseen_round = if let Some(max_known_round) = known_votes.keys().last() { - round > max_known_round - } else { - known_votes.is_empty() - }; - - known_votes.contains_key(round) || unseen_round + debug!(target: "beefy", "🥩 About to note gossip round #{}", round); + self.known_votes.write().insert(round); } - fn is_known(known_votes: &KnownVotes, round: &NumberFor, hash: &MessageHash) -> bool { - known_votes.get(round).map(|known| known.contains(hash)).unwrap_or(false) + /// Conclude a voting round. + /// + /// This can be called once round is complete so we stop gossiping for it. + pub(crate) fn conclude_round(&self, round: NumberFor) { + debug!(target: "beefy", "🥩 About to drop gossip round #{}", round); + self.known_votes.write().conclude(round); } } @@ -152,17 +154,17 @@ where { let known_votes = self.known_votes.read(); - if !GossipValidator::::is_live(&known_votes, &round) { + if !known_votes.is_live(&round) { return ValidationResult::Discard } - if GossipValidator::::is_known(&known_votes, &round, &msg_hash) { + if known_votes.is_known(&round, &msg_hash) { return ValidationResult::ProcessAndKeep(self.topic) } } if BeefyKeystore::verify(&msg.id, &msg.signature, &msg.commitment.encode()) { - GossipValidator::::add_known(&mut *self.known_votes.write(), &round, msg_hash); + self.known_votes.write().add_known(&round, msg_hash); return ValidationResult::ProcessAndKeep(self.topic) } else { // TODO: report peer @@ -182,7 +184,7 @@ where }; let round = msg.commitment.block_number; - let expired = !GossipValidator::::is_live(&known_votes, &round); + let expired = !known_votes.is_live(&round); trace!(target: "beefy", "🥩 Message for round #{} expired: {}", round, expired); @@ -212,11 +214,11 @@ where let msg = match VoteMessage::, Public, Signature>::decode(&mut data) { Ok(vote) => vote, - Err(_) => return true, + Err(_) => return false, }; let round = msg.commitment.block_number; - let allowed = GossipValidator::::is_live(&known_votes, &round); + let allowed = known_votes.is_live(&round); debug!(target: "beefy", "🥩 Message for round #{} allowed: {}", round, allowed); @@ -240,60 +242,58 @@ mod tests { use super::*; #[test] - fn note_round_works() { - let gv = GossipValidator::::new(); - - gv.note_round(1u64); - - let live = gv.known_votes.read(); - assert!(GossipValidator::::is_live(&live, &1u64)); - - drop(live); - - gv.note_round(3u64); - gv.note_round(7u64); - gv.note_round(10u64); - - let live = gv.known_votes.read(); - - assert_eq!(live.len(), MAX_LIVE_GOSSIP_ROUNDS); - - assert!(!GossipValidator::::is_live(&live, &1u64)); - assert!(GossipValidator::::is_live(&live, &3u64)); - assert!(GossipValidator::::is_live(&live, &7u64)); - assert!(GossipValidator::::is_live(&live, &10u64)); + fn known_votes_insert_remove() { + let mut kv = KnownVotes::::new(); + + kv.insert(1); + kv.insert(1); + kv.insert(2); + assert_eq!(kv.live.len(), 2); + + let mut kv = KnownVotes::::new(); + kv.insert(1); + kv.insert(2); + kv.insert(3); + + assert!(kv.last_done.is_none()); + kv.conclude(2); + assert_eq!(kv.live.len(), 1); + assert!(!kv.live.contains_key(&2)); + assert_eq!(kv.last_done, Some(2)); + + kv.conclude(1); + assert_eq!(kv.last_done, Some(2)); + + kv.conclude(3); + assert_eq!(kv.last_done, Some(3)); + assert!(kv.live.is_empty()); } #[test] - fn keeps_most_recent_max_rounds() { + fn note_and_drop_round_works() { let gv = GossipValidator::::new(); - gv.note_round(3u64); - gv.note_round(7u64); - gv.note_round(10u64); gv.note_round(1u64); - let live = gv.known_votes.read(); - - assert_eq!(live.len(), MAX_LIVE_GOSSIP_ROUNDS); + assert!(gv.known_votes.read().is_live(&1u64)); - assert!(GossipValidator::::is_live(&live, &3u64)); - assert!(!GossipValidator::::is_live(&live, &1u64)); - - drop(live); + gv.note_round(3u64); + gv.note_round(7u64); + gv.note_round(10u64); - gv.note_round(23u64); - gv.note_round(15u64); - gv.note_round(20u64); - gv.note_round(2u64); + assert_eq!(gv.known_votes.read().live.len(), 4); - let live = gv.known_votes.read(); + gv.conclude_round(7u64); - assert_eq!(live.len(), MAX_LIVE_GOSSIP_ROUNDS); + let votes = gv.known_votes.read(); - assert!(GossipValidator::::is_live(&live, &15u64)); - assert!(GossipValidator::::is_live(&live, &20u64)); - assert!(GossipValidator::::is_live(&live, &23u64)); + // rounds 1 and 3 are outdated, don't gossip anymore + assert!(!votes.is_live(&1u64)); + assert!(!votes.is_live(&3u64)); + // latest concluded round is still gossiped + assert!(votes.is_live(&7u64)); + // round 10 is alive and in-progress + assert!(votes.is_live(&10u64)); } #[test] @@ -304,22 +304,18 @@ mod tests { gv.note_round(7u64); gv.note_round(10u64); - let live = gv.known_votes.read(); - - assert_eq!(live.len(), MAX_LIVE_GOSSIP_ROUNDS); - - drop(live); + assert_eq!(gv.known_votes.read().live.len(), 3); // note round #7 again -> should not change anything gv.note_round(7u64); - let live = gv.known_votes.read(); + let votes = gv.known_votes.read(); - assert_eq!(live.len(), MAX_LIVE_GOSSIP_ROUNDS); + assert_eq!(votes.live.len(), 3); - assert!(GossipValidator::::is_live(&live, &3u64)); - assert!(GossipValidator::::is_live(&live, &7u64)); - assert!(GossipValidator::::is_live(&live, &10u64)); + assert!(votes.is_live(&3u64)); + assert!(votes.is_live(&7u64)); + assert!(votes.is_live(&10u64)); } struct TestContext; @@ -349,29 +345,32 @@ mod tests { beefy_keystore.sign(&who.public(), &commitment.encode()).unwrap() } + fn dummy_vote(block_number: u64) -> VoteMessage { + let payload = Payload::new(known_payload_ids::MMR_ROOT_ID, MmrRootHash::default().encode()); + let commitment = Commitment { payload, block_number, validator_set_id: 0 }; + let signature = sign_commitment(&Keyring::Alice, &commitment); + + VoteMessage { commitment, id: Keyring::Alice.public(), signature } + } + #[test] fn should_avoid_verifying_signatures_twice() { let gv = GossipValidator::::new(); let sender = sc_network::PeerId::random(); let mut context = TestContext; - let payload = Payload::new(known_payload_ids::MMR_ROOT_ID, MmrRootHash::default().encode()); - let commitment = Commitment { payload, block_number: 3_u64, validator_set_id: 0 }; - - let signature = sign_commitment(&Keyring::Alice, &commitment); - - let vote = VoteMessage { commitment, id: Keyring::Alice.public(), signature }; + let vote = dummy_vote(3); gv.note_round(3u64); gv.note_round(7u64); gv.note_round(10u64); - // first time the cache should be populated. + // first time the cache should be populated let res = gv.validate(&mut context, &sender, &vote.encode()); assert!(matches!(res, ValidationResult::ProcessAndKeep(_))); assert_eq!( - gv.known_votes.read().get(&vote.commitment.block_number).map(|x| x.len()), + gv.known_votes.read().live.get(&vote.commitment.block_number).map(|x| x.len()), Some(1) ); @@ -380,17 +379,84 @@ mod tests { assert!(matches!(res, ValidationResult::ProcessAndKeep(_))); - // next we should quickly reject if the round is not live. - gv.note_round(11_u64); - gv.note_round(12_u64); + // next we should quickly reject if the round is not live + gv.conclude_round(7_u64); - assert!(!GossipValidator::::is_live( - &*gv.known_votes.read(), - &vote.commitment.block_number - )); + assert!(!gv.known_votes.read().is_live(&vote.commitment.block_number)); let res = gv.validate(&mut context, &sender, &vote.encode()); assert!(matches!(res, ValidationResult::Discard)); } + + #[test] + fn messages_allowed_and_expired() { + let gv = GossipValidator::::new(); + let sender = sc_network::PeerId::random(); + let topic = Default::default(); + let intent = MessageIntent::Broadcast; + + // note round 2 and 3, then conclude 2 + gv.note_round(2u64); + gv.note_round(3u64); + gv.conclude_round(2u64); + let mut allowed = gv.message_allowed(); + let mut expired = gv.message_expired(); + + // check bad vote format + assert!(!allowed(&sender, intent, &topic, &mut [0u8; 16])); + assert!(expired(topic, &mut [0u8; 16])); + + // inactive round 1 -> expired + let vote = dummy_vote(1); + let mut encoded_vote = vote.encode(); + assert!(!allowed(&sender, intent, &topic, &mut encoded_vote)); + assert!(expired(topic, &mut encoded_vote)); + + // active round 2 -> !expired - concluded but still gossiped + let vote = dummy_vote(2); + let mut encoded_vote = vote.encode(); + assert!(allowed(&sender, intent, &topic, &mut encoded_vote)); + assert!(!expired(topic, &mut encoded_vote)); + + // in progress round 3 -> !expired + let vote = dummy_vote(3); + let mut encoded_vote = vote.encode(); + assert!(allowed(&sender, intent, &topic, &mut encoded_vote)); + assert!(!expired(topic, &mut encoded_vote)); + + // unseen round 4 -> !expired + let vote = dummy_vote(3); + let mut encoded_vote = vote.encode(); + assert!(allowed(&sender, intent, &topic, &mut encoded_vote)); + assert!(!expired(topic, &mut encoded_vote)); + } + + #[test] + fn messages_rebroadcast() { + let gv = GossipValidator::::new(); + let sender = sc_network::PeerId::random(); + let topic = Default::default(); + + let vote = dummy_vote(1); + let mut encoded_vote = vote.encode(); + + // re-broadcasting only allowed at `REBROADCAST_AFTER` intervals + let intent = MessageIntent::PeriodicRebroadcast; + let mut allowed = gv.message_allowed(); + + // rebroadcast not allowed so soon after GossipValidator creation + assert!(!allowed(&sender, intent, &topic, &mut encoded_vote)); + + // hack the inner deadline to be `now` + *gv.next_rebroadcast.lock() = Instant::now(); + + // still not allowed on old `allowed` closure result + assert!(!allowed(&sender, intent, &topic, &mut encoded_vote)); + + // renew closure result + let mut allowed = gv.message_allowed(); + // rebroadcast should be allowed now + assert!(allowed(&sender, intent, &topic, &mut encoded_vote)); + } } diff --git a/client/beefy/src/lib.rs b/client/beefy/src/lib.rs index 29d74c15dd599..8a6e175f58321 100644 --- a/client/beefy/src/lib.rs +++ b/client/beefy/src/lib.rs @@ -18,14 +18,14 @@ use std::sync::Arc; -use log::debug; use prometheus::Registry; use sc_client_api::{Backend, BlockchainEvents, Finalizer}; -use sc_network_gossip::{GossipEngine, Network as GossipNetwork}; +use sc_network_gossip::Network as GossipNetwork; use sp_api::ProvideRuntimeApi; use sp_blockchain::HeaderBackend; +use sp_consensus::SyncOracle; use sp_keystore::SyncCryptoStorePtr; use sp_runtime::traits::Block; @@ -41,6 +41,10 @@ mod round; mod worker; pub mod notification; + +#[cfg(test)] +mod tests; + pub use beefy_protocol_name::standard_name as protocol_standard_name; pub(crate) mod beefy_protocol_name { @@ -112,7 +116,7 @@ where BE: Backend, C: Client, C::Api: BeefyApi, - N: GossipNetwork + Clone + Send + 'static, + N: GossipNetwork + Clone + SyncOracle + Send + Sync + 'static, { /// BEEFY client pub client: Arc, @@ -134,6 +138,7 @@ where pub protocol_name: std::borrow::Cow<'static, str>, } +#[cfg(not(test))] /// Start the BEEFY gadget. /// /// This is a thin shim around running and awaiting a BEEFY worker. @@ -143,7 +148,7 @@ where BE: Backend, C: Client, C::Api: BeefyApi, - N: GossipNetwork + Clone + Send + 'static, + N: GossipNetwork + Clone + SyncOracle + Send + Sync + 'static, { let BeefyParams { client, @@ -157,18 +162,24 @@ where protocol_name, } = beefy_params; + let sync_oracle = network.clone(); let gossip_validator = Arc::new(gossip::GossipValidator::new()); - let gossip_engine = GossipEngine::new(network, protocol_name, gossip_validator.clone(), None); + let gossip_engine = sc_network_gossip::GossipEngine::new( + network, + protocol_name, + gossip_validator.clone(), + None, + ); let metrics = prometheus_registry.as_ref().map(metrics::Metrics::register).and_then( |result| match result { Ok(metrics) => { - debug!(target: "beefy", "🥩 Registered metrics"); + log::debug!(target: "beefy", "🥩 Registered metrics"); Some(metrics) }, Err(err) => { - debug!(target: "beefy", "🥩 Failed to register metrics: {:?}", err); + log::debug!(target: "beefy", "🥩 Failed to register metrics: {:?}", err); None }, }, @@ -184,54 +195,10 @@ where gossip_validator, min_block_delta, metrics, + sync_oracle, }; - let worker = worker::BeefyWorker::<_, _, _>::new(worker_params); + let worker = worker::BeefyWorker::<_, _, _, _>::new(worker_params); worker.run().await } - -#[cfg(test)] -mod tests { - use super::*; - use sc_chain_spec::{ChainSpec, GenericChainSpec}; - use serde::{Deserialize, Serialize}; - use sp_core::H256; - use sp_runtime::{BuildStorage, Storage}; - - #[derive(Debug, Serialize, Deserialize)] - struct Genesis(std::collections::BTreeMap); - impl BuildStorage for Genesis { - fn assimilate_storage(&self, storage: &mut Storage) -> Result<(), String> { - storage.top.extend( - self.0.iter().map(|(a, b)| (a.clone().into_bytes(), b.clone().into_bytes())), - ); - Ok(()) - } - } - - #[test] - fn beefy_protocol_name() { - let chain_spec = GenericChainSpec::::from_json_file(std::path::PathBuf::from( - "../chain-spec/res/chain_spec.json", - )) - .unwrap() - .cloned_box(); - - // Create protocol name using random genesis hash. - let genesis_hash = H256::random(); - let expected = format!("/{}/beefy/1", hex::encode(genesis_hash)); - let proto_name = beefy_protocol_name::standard_name(&genesis_hash, &chain_spec); - assert_eq!(proto_name.to_string(), expected); - - // Create protocol name using hardcoded genesis hash. Verify exact representation. - let genesis_hash = [ - 50, 4, 60, 123, 58, 106, 216, 246, 194, 188, 139, 193, 33, 212, 202, 171, 9, 55, 123, - 94, 8, 43, 12, 251, 187, 57, 173, 19, 188, 74, 205, 147, - ]; - let expected = - "/32043c7b3a6ad8f6c2bc8bc121d4caab09377b5e082b0cfbbb39ad13bc4acd93/beefy/1".to_string(); - let proto_name = beefy_protocol_name::standard_name(&genesis_hash, &chain_spec); - assert_eq!(proto_name.to_string(), expected); - } -} diff --git a/client/beefy/src/metrics.rs b/client/beefy/src/metrics.rs index 4b2a5c8dfd5c9..20fa98e52fdd5 100644 --- a/client/beefy/src/metrics.rs +++ b/client/beefy/src/metrics.rs @@ -18,7 +18,9 @@ //! BEEFY Prometheus metrics definition -use prometheus::{register, Counter, Gauge, PrometheusError, Registry, U64}; +#[cfg(not(test))] +use prometheus::{register, PrometheusError, Registry}; +use prometheus::{Counter, Gauge, U64}; /// BEEFY metrics exposed through Prometheus pub(crate) struct Metrics { @@ -37,6 +39,7 @@ pub(crate) struct Metrics { } impl Metrics { + #[cfg(not(test))] pub(crate) fn register(registry: &Registry) -> Result { Ok(Self { beefy_validator_set_id: register( @@ -97,3 +100,11 @@ macro_rules! metric_inc { } }}; } + +#[cfg(test)] +#[macro_export] +macro_rules! metric_get { + ($self:ident, $m:ident) => {{ + $self.metrics.as_ref().map(|metrics| metrics.$m.clone()) + }}; +} diff --git a/client/beefy/src/round.rs b/client/beefy/src/round.rs index e5404cfa6d216..eba769b2356f0 100644 --- a/client/beefy/src/round.rs +++ b/client/beefy/src/round.rs @@ -16,7 +16,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use std::{collections::BTreeMap, hash::Hash}; +use std::{ + collections::{BTreeMap, HashMap}, + hash::Hash, +}; use log::{debug, trace}; @@ -24,25 +27,33 @@ use beefy_primitives::{ crypto::{Public, Signature}, ValidatorSet, ValidatorSetId, }; -use sp_arithmetic::traits::AtLeast32BitUnsigned; -use sp_runtime::traits::MaybeDisplay; +use sp_runtime::traits::{Block, NumberFor}; +/// Tracks for each round which validators have voted/signed and +/// whether the local `self` validator has voted/signed. +/// +/// Does not do any validation on votes or signatures, layers above need to handle that (gossip). #[derive(Default)] struct RoundTracker { - votes: Vec<(Public, Signature)>, + self_vote: bool, + votes: HashMap, } impl RoundTracker { - fn add_vote(&mut self, vote: (Public, Signature)) -> bool { - // this needs to handle equivocations in the future - if self.votes.contains(&vote) { + fn add_vote(&mut self, vote: (Public, Signature), self_vote: bool) -> bool { + if self.votes.contains_key(&vote.0) { return false } - self.votes.push(vote); + self.self_vote = self.self_vote || self_vote; + self.votes.insert(vote.0, vote.1); true } + fn has_self_vote(&self) -> bool { + self.self_vote + } + fn is_done(&self, threshold: usize) -> bool { self.votes.len() >= threshold } @@ -53,74 +64,125 @@ fn threshold(authorities: usize) -> usize { authorities - faulty } -pub(crate) struct Rounds { - rounds: BTreeMap<(Payload, Number), RoundTracker>, +/// Keeps track of all voting rounds (block numbers) within a session. +/// Only round numbers > `best_done` are of interest, all others are considered stale. +/// +/// Does not do any validation on votes or signatures, layers above need to handle that (gossip). +pub(crate) struct Rounds { + rounds: BTreeMap<(Payload, NumberFor), RoundTracker>, + best_done: Option>, + session_start: NumberFor, validator_set: ValidatorSet, + prev_validator_set: ValidatorSet, } -impl Rounds +impl Rounds where - P: Ord + Hash, - N: Ord + AtLeast32BitUnsigned + MaybeDisplay, + P: Ord + Hash + Clone, + B: Block, { - pub(crate) fn new(validator_set: ValidatorSet) -> Self { - Rounds { rounds: BTreeMap::new(), validator_set } + pub(crate) fn new( + session_start: NumberFor, + validator_set: ValidatorSet, + prev_validator_set: ValidatorSet, + ) -> Self { + Rounds { + rounds: BTreeMap::new(), + best_done: None, + session_start, + validator_set, + prev_validator_set, + } } } -impl Rounds +impl Rounds where - H: Ord + Hash + Clone, - N: Ord + AtLeast32BitUnsigned + MaybeDisplay + Clone, + P: Ord + Hash + Clone, + B: Block, { - pub(crate) fn validator_set_id(&self) -> ValidatorSetId { - self.validator_set.id() + pub(crate) fn validator_set_id_for(&self, block_number: NumberFor) -> ValidatorSetId { + if block_number > self.session_start { + self.validator_set.id() + } else { + self.prev_validator_set.id() + } } - pub(crate) fn validators(&self) -> &[Public] { - self.validator_set.validators() + pub(crate) fn validators_for(&self, block_number: NumberFor) -> &[Public] { + if block_number > self.session_start { + self.validator_set.validators() + } else { + self.prev_validator_set.validators() + } } - pub(crate) fn add_vote(&mut self, round: &(H, N), vote: (Public, Signature)) -> bool { - if self.validator_set.validators().iter().any(|id| vote.0 == *id) { - self.rounds.entry(round.clone()).or_default().add_vote(vote) - } else { + pub(crate) fn validator_set(&self) -> &ValidatorSet { + &self.validator_set + } + + pub(crate) fn session_start(&self) -> &NumberFor { + &self.session_start + } + + pub(crate) fn should_self_vote(&self, round: &(P, NumberFor)) -> bool { + Some(round.1.clone()) > self.best_done && + self.rounds.get(round).map(|tracker| !tracker.has_self_vote()).unwrap_or(true) + } + + pub(crate) fn add_vote( + &mut self, + round: &(P, NumberFor), + vote: (Public, Signature), + self_vote: bool, + ) -> bool { + if Some(round.1.clone()) <= self.best_done { + debug!( + target: "beefy", + "🥩 received vote for old stale round {:?}, ignoring", + round.1 + ); + false + } else if !self.validator_set.validators().iter().any(|id| vote.0 == *id) { + debug!( + target: "beefy", + "🥩 received vote {:?} from validator that is not in the validator set, ignoring", + vote + ); false + } else { + self.rounds.entry(round.clone()).or_default().add_vote(vote, self_vote) } } - pub(crate) fn is_done(&self, round: &(H, N)) -> bool { + pub(crate) fn try_conclude( + &mut self, + round: &(P, NumberFor), + ) -> Option>> { let done = self .rounds .get(round) .map(|tracker| tracker.is_done(threshold(self.validator_set.len()))) .unwrap_or(false); - - debug!(target: "beefy", "🥩 Round #{} done: {}", round.1, done); - - done - } - - pub(crate) fn drop(&mut self, round: &(H, N)) -> Option>> { - trace!(target: "beefy", "🥩 About to drop round #{}", round.1); - - let signatures = self.rounds.remove(round)?.votes; - - Some( - self.validator_set - .validators() - .iter() - .map(|authority_id| { - signatures.iter().find_map(|(id, sig)| { - if id == authority_id { - Some(sig.clone()) - } else { - None - } - }) - }) - .collect(), - ) + trace!(target: "beefy", "🥩 Round #{} done: {}", round.1, done); + + if done { + // remove this and older (now stale) rounds + let signatures = self.rounds.remove(round)?.votes; + self.rounds.retain(|&(_, number), _| number > round.1); + self.best_done = self.best_done.clone().max(Some(round.1.clone())); + trace!(target: "beefy", "🥩 Concluded round #{}", round.1); + + Some( + self.validator_set + .validators() + .iter() + .map(|authority_id| signatures.get(authority_id).cloned()) + .collect(), + ) + } else { + None + } } } @@ -128,13 +190,52 @@ where mod tests { use sc_network_test::Block; use sp_core::H256; - use sp_runtime::traits::NumberFor; use beefy_primitives::{crypto::Public, ValidatorSet}; - use super::Rounds; + use super::{threshold, RoundTracker, Rounds}; use crate::keystore::tests::Keyring; + #[test] + fn round_tracker() { + let mut rt = RoundTracker::default(); + let bob_vote = (Keyring::Bob.public(), Keyring::Bob.sign(b"I am committed")); + let threshold = 2; + + // self vote not added yet + assert!(!rt.has_self_vote()); + + // adding new vote allowed + assert!(rt.add_vote(bob_vote.clone(), false)); + // adding existing vote not allowed + assert!(!rt.add_vote(bob_vote, false)); + + // self vote still not added yet + assert!(!rt.has_self_vote()); + + // vote is not done + assert!(!rt.is_done(threshold)); + + let alice_vote = (Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed")); + // adding new vote (self vote this time) allowed + assert!(rt.add_vote(alice_vote, true)); + + // self vote registered + assert!(rt.has_self_vote()); + // vote is now done + assert!(rt.is_done(threshold)); + } + + #[test] + fn vote_threshold() { + assert_eq!(threshold(1), 1); + assert_eq!(threshold(2), 2); + assert_eq!(threshold(3), 3); + assert_eq!(threshold(4), 3); + assert_eq!(threshold(100), 67); + assert_eq!(threshold(300), 201); + } + #[test] fn new_rounds() { sp_tracing::try_init_simple(); @@ -145,116 +246,175 @@ mod tests { ) .unwrap(); - let rounds = Rounds::>::new(validators); - - assert_eq!(42, rounds.validator_set_id()); + let session_start = 1u64.into(); + let rounds = Rounds::::new(session_start, validators.clone(), validators); + assert_eq!(42, rounds.validator_set_id_for(session_start)); + assert_eq!(1, *rounds.session_start()); assert_eq!( &vec![Keyring::Alice.public(), Keyring::Bob.public(), Keyring::Charlie.public()], - rounds.validators() + rounds.validators_for(session_start) ); } #[test] - fn add_vote() { + fn add_and_conclude_votes() { sp_tracing::try_init_simple(); let validators = ValidatorSet::::new( - vec![Keyring::Alice.public(), Keyring::Bob.public(), Keyring::Charlie.public()], + vec![ + Keyring::Alice.public(), + Keyring::Bob.public(), + Keyring::Charlie.public(), + Keyring::Eve.public(), + ], Default::default(), ) .unwrap(); + let round = (H256::from_low_u64_le(1), 1); - let mut rounds = Rounds::>::new(validators); + let session_start = 1u64.into(); + let mut rounds = Rounds::::new(session_start, validators.clone(), validators); + // no self vote yet, should self vote + assert!(rounds.should_self_vote(&round)); + + // add 1st good vote assert!(rounds.add_vote( - &(H256::from_low_u64_le(1), 1), - (Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed")) + &round, + (Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed")), + true )); + // round not concluded + assert!(rounds.try_conclude(&round).is_none()); + // self vote already present, should not self vote + assert!(!rounds.should_self_vote(&round)); - assert!(!rounds.is_done(&(H256::from_low_u64_le(1), 1))); - - // invalid vote + // double voting not allowed assert!(!rounds.add_vote( - &(H256::from_low_u64_le(1), 1), - (Keyring::Dave.public(), Keyring::Dave.sign(b"I am committed")) + &round, + (Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed")), + true )); - assert!(!rounds.is_done(&(H256::from_low_u64_le(1), 1))); + // invalid vote (Dave is not a validator) + assert!(!rounds.add_vote( + &round, + (Keyring::Dave.public(), Keyring::Dave.sign(b"I am committed")), + false + )); + assert!(rounds.try_conclude(&round).is_none()); + // add 2nd good vote assert!(rounds.add_vote( - &(H256::from_low_u64_le(1), 1), - (Keyring::Bob.public(), Keyring::Bob.sign(b"I am committed")) + &round, + (Keyring::Bob.public(), Keyring::Bob.sign(b"I am committed")), + false )); + // round not concluded + assert!(rounds.try_conclude(&round).is_none()); - assert!(!rounds.is_done(&(H256::from_low_u64_le(1), 1))); - + // add 3rd good vote assert!(rounds.add_vote( - &(H256::from_low_u64_le(1), 1), - (Keyring::Charlie.public(), Keyring::Charlie.sign(b"I am committed")) + &round, + (Keyring::Charlie.public(), Keyring::Charlie.sign(b"I am committed")), + false )); + // round concluded + assert!(rounds.try_conclude(&round).is_some()); - assert!(rounds.is_done(&(H256::from_low_u64_le(1), 1))); + // Eve is a validator, but round was concluded, adding vote disallowed + assert!(!rounds.add_vote( + &round, + (Keyring::Eve.public(), Keyring::Eve.sign(b"I am committed")), + false + )); } #[test] - fn drop() { + fn multiple_rounds() { sp_tracing::try_init_simple(); let validators = ValidatorSet::::new( - vec![Keyring::Alice.public(), Keyring::Bob.public(), Keyring::Charlie.public()], + vec![ + Keyring::Alice.public(), + Keyring::Bob.public(), + Keyring::Charlie.public(), + Keyring::Dave.public(), + ], Default::default(), ) .unwrap(); - let mut rounds = Rounds::>::new(validators); + let session_start = 1u64.into(); + let mut rounds = Rounds::::new(session_start, validators.clone(), validators); // round 1 - rounds.add_vote( + assert!(rounds.add_vote( &(H256::from_low_u64_le(1), 1), (Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed")), - ); - rounds.add_vote( + true, + )); + assert!(rounds.add_vote( &(H256::from_low_u64_le(1), 1), (Keyring::Bob.public(), Keyring::Bob.sign(b"I am committed")), - ); + false, + )); + assert!(rounds.add_vote( + &(H256::from_low_u64_le(1), 1), + (Keyring::Charlie.public(), Keyring::Charlie.sign(b"I am committed")), + false, + )); // round 2 - rounds.add_vote( + assert!(rounds.add_vote( &(H256::from_low_u64_le(2), 2), (Keyring::Alice.public(), Keyring::Alice.sign(b"I am again committed")), - ); - rounds.add_vote( + true, + )); + assert!(rounds.add_vote( &(H256::from_low_u64_le(2), 2), (Keyring::Bob.public(), Keyring::Bob.sign(b"I am again committed")), - ); + false, + )); + assert!(rounds.add_vote( + &(H256::from_low_u64_le(2), 2), + (Keyring::Charlie.public(), Keyring::Charlie.sign(b"I am again committed")), + false, + )); // round 3 - rounds.add_vote( + assert!(rounds.add_vote( &(H256::from_low_u64_le(3), 3), (Keyring::Alice.public(), Keyring::Alice.sign(b"I am still committed")), - ); - rounds.add_vote( + true, + )); + assert!(rounds.add_vote( &(H256::from_low_u64_le(3), 3), (Keyring::Bob.public(), Keyring::Bob.sign(b"I am still committed")), - ); - + false, + )); + assert!(rounds.add_vote( + &(H256::from_low_u64_le(3), 3), + (Keyring::Charlie.public(), Keyring::Charlie.sign(b"I am still committed")), + false, + )); assert_eq!(3, rounds.rounds.len()); - // drop unknown round - assert!(rounds.drop(&(H256::from_low_u64_le(5), 5)).is_none()); + // conclude unknown round + assert!(rounds.try_conclude(&(H256::from_low_u64_le(5), 5)).is_none()); assert_eq!(3, rounds.rounds.len()); - // drop round 2 - let signatures = rounds.drop(&(H256::from_low_u64_le(2), 2)).unwrap(); - - assert_eq!(2, rounds.rounds.len()); + // conclude round 2 + let signatures = rounds.try_conclude(&(H256::from_low_u64_le(2), 2)).unwrap(); + assert_eq!(1, rounds.rounds.len()); assert_eq!( signatures, vec![ Some(Keyring::Alice.sign(b"I am again committed")), Some(Keyring::Bob.sign(b"I am again committed")), + Some(Keyring::Charlie.sign(b"I am again committed")), None ] ); diff --git a/client/beefy/src/tests.rs b/client/beefy/src/tests.rs new file mode 100644 index 0000000000000..92b5ad91c11e1 --- /dev/null +++ b/client/beefy/src/tests.rs @@ -0,0 +1,590 @@ +// This file is part of Substrate. + +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Tests and test helpers for BEEFY. + +use futures::{future, stream::FuturesUnordered, Future, StreamExt}; +use parking_lot::Mutex; +use serde::{Deserialize, Serialize}; +use std::{sync::Arc, task::Poll}; +use tokio::{runtime::Runtime, time::Duration}; + +use sc_chain_spec::{ChainSpec, GenericChainSpec}; +use sc_client_api::HeaderBackend; +use sc_consensus::BoxJustificationImport; +use sc_keystore::LocalKeystore; +use sc_network::{config::ProtocolConfig, NetworkService}; +use sc_network_gossip::GossipEngine; +use sc_network_test::{ + Block, BlockImportAdapter, FullPeerConfig, PassThroughVerifier, Peer, PeersClient, + PeersFullClient, TestNetFactory, +}; +use sc_utils::notification::NotificationReceiver; + +use beefy_primitives::{ + crypto::AuthorityId, ConsensusLog, MmrRootHash, ValidatorSet, BEEFY_ENGINE_ID, + KEY_TYPE as BeefyKeyType, +}; +use sp_consensus::BlockOrigin; +use sp_core::H256; +use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; +use sp_runtime::{ + codec::Encode, generic::BlockId, traits::Header as HeaderT, BuildStorage, DigestItem, Storage, +}; + +use substrate_test_runtime_client::{runtime::Header, Backend, ClientExt}; + +use crate::{ + beefy_protocol_name, + keystore::tests::Keyring as BeefyKeyring, + notification::*, + worker::{tests::TestModifiers, BeefyWorker}, +}; + +const BEEFY_PROTOCOL_NAME: &'static str = "/beefy/1"; + +type BeefyValidatorSet = ValidatorSet; +type BeefyPeer = Peer; + +#[derive(Debug, Serialize, Deserialize)] +struct Genesis(std::collections::BTreeMap); +impl BuildStorage for Genesis { + fn assimilate_storage(&self, storage: &mut Storage) -> Result<(), String> { + storage + .top + .extend(self.0.iter().map(|(a, b)| (a.clone().into_bytes(), b.clone().into_bytes()))); + Ok(()) + } +} + +#[test] +fn beefy_protocol_name() { + let chain_spec = GenericChainSpec::::from_json_file(std::path::PathBuf::from( + "../chain-spec/res/chain_spec.json", + )) + .unwrap() + .cloned_box(); + + // Create protocol name using random genesis hash. + let genesis_hash = H256::random(); + let expected = format!("/{}/beefy/1", hex::encode(genesis_hash)); + let proto_name = beefy_protocol_name::standard_name(&genesis_hash, &chain_spec); + assert_eq!(proto_name.to_string(), expected); + + // Create protocol name using hardcoded genesis hash. Verify exact representation. + let genesis_hash = [ + 50, 4, 60, 123, 58, 106, 216, 246, 194, 188, 139, 193, 33, 212, 202, 171, 9, 55, 123, 94, + 8, 43, 12, 251, 187, 57, 173, 19, 188, 74, 205, 147, + ]; + let expected = + "/32043c7b3a6ad8f6c2bc8bc121d4caab09377b5e082b0cfbbb39ad13bc4acd93/beefy/1".to_string(); + let proto_name = beefy_protocol_name::standard_name(&genesis_hash, &chain_spec); + assert_eq!(proto_name.to_string(), expected); +} + +// TODO: compiler warns us about unused `signed_commitment_stream`, will use in later tests +#[allow(dead_code)] +#[derive(Clone)] +pub(crate) struct BeefyLinkHalf { + signed_commitment_stream: BeefySignedCommitmentStream, + beefy_best_block_stream: BeefyBestBlockStream, +} + +#[derive(Default)] +pub(crate) struct PeerData { + pub(crate) beefy_link_half: Mutex>, + pub(crate) test_modifiers: Option, +} + +impl PeerData { + pub(crate) fn use_validator_set(&mut self, validator_set: &ValidatorSet) { + if let Some(tm) = self.test_modifiers.as_mut() { + tm.active_validators = validator_set.clone(); + } else { + self.test_modifiers = Some(TestModifiers { + active_validators: validator_set.clone(), + corrupt_mmr_roots: false, + }); + } + } +} + +pub(crate) struct BeefyTestNet { + peers: Vec, +} + +impl BeefyTestNet { + pub(crate) fn new(n_authority: usize, n_full: usize) -> Self { + let mut net = BeefyTestNet { peers: Vec::with_capacity(n_authority + n_full) }; + for _ in 0..n_authority { + net.add_authority_peer(); + } + for _ in 0..n_full { + net.add_full_peer(); + } + net + } + + pub(crate) fn add_authority_peer(&mut self) { + self.add_full_peer_with_config(FullPeerConfig { + notifications_protocols: vec![BEEFY_PROTOCOL_NAME.into()], + is_authority: true, + ..Default::default() + }) + } + + pub(crate) fn generate_blocks( + &mut self, + count: usize, + session_length: u64, + validator_set: &BeefyValidatorSet, + ) { + self.peer(0).generate_blocks(count, BlockOrigin::File, |builder| { + let mut block = builder.build().unwrap().block; + + let block_num = *block.header.number(); + let num_byte = block_num.to_le_bytes().into_iter().next().unwrap(); + let mmr_root = MmrRootHash::repeat_byte(num_byte); + + add_mmr_digest(&mut block.header, mmr_root); + + if block_num % session_length == 0 { + add_auth_change_digest(&mut block.header, validator_set.clone()); + } + + block + }); + } +} + +impl TestNetFactory for BeefyTestNet { + type Verifier = PassThroughVerifier; + type BlockImport = PeersClient; + type PeerData = PeerData; + + /// Create new test network with peers and given config. + fn from_config(_config: &ProtocolConfig) -> Self { + BeefyTestNet { peers: Vec::new() } + } + + fn make_verifier( + &self, + _client: PeersClient, + _cfg: &ProtocolConfig, + _: &PeerData, + ) -> Self::Verifier { + PassThroughVerifier::new(false) // use non-instant finality. + } + + fn make_block_import( + &self, + client: PeersClient, + ) -> ( + BlockImportAdapter, + Option>, + Self::PeerData, + ) { + (client.as_block_import(), None, PeerData::default()) + } + + fn peer(&mut self, i: usize) -> &mut BeefyPeer { + &mut self.peers[i] + } + + fn peers(&self) -> &Vec { + &self.peers + } + + fn mut_peers)>(&mut self, closure: F) { + closure(&mut self.peers); + } + + fn add_full_peer(&mut self) { + self.add_full_peer_with_config(FullPeerConfig { + notifications_protocols: vec![BEEFY_PROTOCOL_NAME.into()], + is_authority: false, + ..Default::default() + }) + } +} + +fn add_mmr_digest(header: &mut Header, mmr_hash: MmrRootHash) { + header.digest_mut().push(DigestItem::Consensus( + BEEFY_ENGINE_ID, + ConsensusLog::::MmrRoot(mmr_hash).encode(), + )); +} + +fn add_auth_change_digest(header: &mut Header, new_auth_set: BeefyValidatorSet) { + header.digest_mut().push(DigestItem::Consensus( + BEEFY_ENGINE_ID, + ConsensusLog::::AuthoritiesChange(new_auth_set).encode(), + )); +} + +pub(crate) fn make_beefy_ids(keys: &[BeefyKeyring]) -> Vec { + keys.iter().map(|key| key.clone().public().into()).collect() +} + +pub(crate) fn create_beefy_keystore(authority: BeefyKeyring) -> SyncCryptoStorePtr { + let keystore = Arc::new(LocalKeystore::in_memory()); + SyncCryptoStore::ecdsa_generate_new(&*keystore, BeefyKeyType, Some(&authority.to_seed())) + .expect("Creates authority key"); + keystore +} + +pub(crate) fn create_beefy_worker( + peer: &BeefyPeer, + key: &BeefyKeyring, + min_block_delta: u32, +) -> BeefyWorker>> { + let keystore = create_beefy_keystore(*key); + + let (signed_commitment_sender, signed_commitment_stream) = + BeefySignedCommitmentStream::::channel(); + let (beefy_best_block_sender, beefy_best_block_stream) = + BeefyBestBlockStream::::channel(); + + let beefy_link_half = BeefyLinkHalf { signed_commitment_stream, beefy_best_block_stream }; + *peer.data.beefy_link_half.lock() = Some(beefy_link_half); + let test_modifiers = peer.data.test_modifiers.clone().unwrap(); + + let network = peer.network_service().clone(); + let sync_oracle = network.clone(); + let gossip_validator = Arc::new(crate::gossip::GossipValidator::new()); + let gossip_engine = + GossipEngine::new(network, BEEFY_PROTOCOL_NAME, gossip_validator.clone(), None); + let worker_params = crate::worker::WorkerParams { + client: peer.client().as_client(), + backend: peer.client().as_backend(), + key_store: Some(keystore).into(), + signed_commitment_sender, + beefy_best_block_sender, + gossip_engine, + gossip_validator, + min_block_delta, + metrics: None, + sync_oracle, + }; + + BeefyWorker::<_, _, _, _>::new(worker_params, test_modifiers) +} + +// Spawns beefy voters. Returns a future to spawn on the runtime. +fn initialize_beefy( + net: &mut BeefyTestNet, + peers: &[BeefyKeyring], + min_block_delta: u32, +) -> impl Future { + let voters = FuturesUnordered::new(); + + for (peer_id, key) in peers.iter().enumerate() { + let worker = create_beefy_worker(&net.peers[peer_id], key, min_block_delta); + let gadget = worker.run(); + + fn assert_send(_: &T) {} + assert_send(&gadget); + voters.push(gadget); + } + + voters.for_each(|_| async move {}) +} + +fn block_until(future: impl Future + Unpin, net: &Arc>, runtime: &mut Runtime) { + let drive_to_completion = futures::future::poll_fn(|cx| { + net.lock().poll(cx); + Poll::<()>::Pending + }); + runtime.block_on(future::select(future, drive_to_completion)); +} + +fn run_for(duration: Duration, net: &Arc>, runtime: &mut Runtime) { + let sleep = runtime.spawn(async move { tokio::time::sleep(duration).await }); + block_until(sleep, net, runtime); +} + +pub(crate) fn get_beefy_streams( + net: &mut BeefyTestNet, + peers: &[BeefyKeyring], +) -> (Vec>, Vec>>) { + let mut best_block_streams = Vec::new(); + let mut signed_commitment_streams = Vec::new(); + for peer_id in 0..peers.len() { + let beefy_link_half = + net.peer(peer_id).data.beefy_link_half.lock().as_ref().unwrap().clone(); + let BeefyLinkHalf { signed_commitment_stream, beefy_best_block_stream } = beefy_link_half; + best_block_streams.push(beefy_best_block_stream.subscribe()); + signed_commitment_streams.push(signed_commitment_stream.subscribe()); + } + (best_block_streams, signed_commitment_streams) +} + +fn wait_for_best_beefy_blocks( + streams: Vec>, + net: &Arc>, + runtime: &mut Runtime, + expected_beefy_blocks: &[u64], +) { + let mut wait_for = Vec::new(); + let len = expected_beefy_blocks.len(); + streams.into_iter().enumerate().for_each(|(i, stream)| { + let mut expected = expected_beefy_blocks.iter(); + wait_for.push(Box::pin(stream.take(len).for_each(move |best_beefy_hash| { + let expected = expected.next(); + async move { + let block_id = BlockId::hash(best_beefy_hash); + let header = + net.lock().peer(i).client().as_client().expect_header(block_id).unwrap(); + let best_beefy = *header.number(); + + assert_eq!(expected, Some(best_beefy).as_ref()); + } + }))); + }); + let wait_for = futures::future::join_all(wait_for); + block_until(wait_for, net, runtime); +} + +fn wait_for_beefy_signed_commitments( + streams: Vec>>, + net: &Arc>, + runtime: &mut Runtime, + expected_commitment_block_nums: &[u64], +) { + let mut wait_for = Vec::new(); + let len = expected_commitment_block_nums.len(); + streams.into_iter().for_each(|stream| { + let mut expected = expected_commitment_block_nums.iter(); + wait_for.push(Box::pin(stream.take(len).for_each(move |signed_commitment| { + let expected = expected.next(); + async move { + let commitment_block_num = signed_commitment.commitment.block_number; + assert_eq!(expected, Some(commitment_block_num).as_ref()); + // TODO: also verify commitment payload, validator set id, and signatures. + } + }))); + }); + let wait_for = futures::future::join_all(wait_for); + block_until(wait_for, net, runtime); +} + +fn streams_empty_after_timeout( + streams: Vec>, + net: &Arc>, + runtime: &mut Runtime, + timeout: Option, +) where + T: std::fmt::Debug, + T: std::cmp::PartialEq, +{ + if let Some(timeout) = timeout { + run_for(timeout, net, runtime); + } + streams.into_iter().for_each(|mut stream| { + runtime.block_on(future::poll_fn(move |cx| { + assert_eq!(stream.poll_next_unpin(cx), Poll::Pending); + Poll::Ready(()) + })); + }); +} + +fn finalize_block_and_wait_for_beefy( + net: &Arc>, + peers: &[BeefyKeyring], + runtime: &mut Runtime, + finalize_targets: &[u64], + expected_beefy: &[u64], +) { + let (best_blocks, signed_commitments) = get_beefy_streams(&mut *net.lock(), peers); + + for block in finalize_targets { + let finalize = BlockId::number(*block); + for i in 0..peers.len() { + net.lock().peer(i).client().as_client().finalize_block(finalize, None).unwrap(); + } + } + + if expected_beefy.is_empty() { + // run for 1 second then verify no new best beefy block available + let timeout = Some(Duration::from_millis(500)); + streams_empty_after_timeout(best_blocks, &net, runtime, timeout); + streams_empty_after_timeout(signed_commitments, &net, runtime, None); + } else { + // run until expected beefy blocks are received + wait_for_best_beefy_blocks(best_blocks, &net, runtime, expected_beefy); + wait_for_beefy_signed_commitments(signed_commitments, &net, runtime, expected_beefy); + } +} + +#[test] +fn beefy_finalizing_blocks() { + sp_tracing::try_init_simple(); + + let mut runtime = Runtime::new().unwrap(); + let peers = &[BeefyKeyring::Alice, BeefyKeyring::Bob]; + let validator_set = ValidatorSet::new(make_beefy_ids(peers), 0).unwrap(); + let session_len = 10; + let min_block_delta = 4; + + let mut net = BeefyTestNet::new(2, 0); + + for i in 0..peers.len() { + net.peer(i).data.use_validator_set(&validator_set); + } + runtime.spawn(initialize_beefy(&mut net, peers, min_block_delta)); + + // push 42 blocks including `AuthorityChange` digests every 10 blocks. + net.generate_blocks(42, session_len, &validator_set); + net.block_until_sync(); + + let net = Arc::new(Mutex::new(net)); + + // Minimum BEEFY block delta is 4. + + // finalize block #5 -> BEEFY should finalize #1 (mandatory) and #5 from diff-power-of-two rule. + finalize_block_and_wait_for_beefy(&net, peers, &mut runtime, &[5], &[1, 5]); + + // GRANDPA finalize #10 -> BEEFY finalize #10 (mandatory) + finalize_block_and_wait_for_beefy(&net, peers, &mut runtime, &[10], &[10]); + + // GRANDPA finalize #18 -> BEEFY finalize #14, then #18 (diff-power-of-two rule) + finalize_block_and_wait_for_beefy(&net, peers, &mut runtime, &[18], &[14, 18]); + + // GRANDPA finalize #20 -> BEEFY finalize #20 (mandatory) + finalize_block_and_wait_for_beefy(&net, peers, &mut runtime, &[20], &[20]); + + // GRANDPA finalize #21 -> BEEFY finalize nothing (yet) because min delta is 4 + finalize_block_and_wait_for_beefy(&net, peers, &mut runtime, &[21], &[]); +} + +#[test] +fn lagging_validators() { + sp_tracing::try_init_simple(); + + let mut runtime = Runtime::new().unwrap(); + let peers = &[BeefyKeyring::Charlie, BeefyKeyring::Dave]; + let validator_set = ValidatorSet::new(make_beefy_ids(peers), 0).unwrap(); + let session_len = 30; + let min_block_delta = 1; + + let mut net = BeefyTestNet::new(2, 0); + for i in 0..peers.len() { + net.peer(i).data.use_validator_set(&validator_set); + } + runtime.spawn(initialize_beefy(&mut net, peers, min_block_delta)); + + // push 42 blocks including `AuthorityChange` digests every 30 blocks. + net.generate_blocks(42, session_len, &validator_set); + net.block_until_sync(); + + let net = Arc::new(Mutex::new(net)); + + // finalize block #15 -> BEEFY should finalize #1 (mandatory) and #9, #13, #14, #15 from + // diff-power-of-two rule. + finalize_block_and_wait_for_beefy(&net, peers, &mut runtime, &[15], &[1, 9, 13, 14, 15]); + + // Charlie finalizes #25, Dave lags behind + let finalize = BlockId::number(25); + let (best_blocks, signed_commitments) = get_beefy_streams(&mut *net.lock(), peers); + net.lock().peer(0).client().as_client().finalize_block(finalize, None).unwrap(); + // verify nothing gets finalized by BEEFY + let timeout = Some(Duration::from_millis(500)); + streams_empty_after_timeout(best_blocks, &net, &mut runtime, timeout); + streams_empty_after_timeout(signed_commitments, &net, &mut runtime, None); + + // Dave catches up and also finalizes #25 + let (best_blocks, signed_commitments) = get_beefy_streams(&mut *net.lock(), peers); + net.lock().peer(1).client().as_client().finalize_block(finalize, None).unwrap(); + // expected beefy finalizes block #17 from diff-power-of-two + wait_for_best_beefy_blocks(best_blocks, &net, &mut runtime, &[23, 24, 25]); + wait_for_beefy_signed_commitments(signed_commitments, &net, &mut runtime, &[23, 24, 25]); + + // Both finalize #30 (mandatory session) and #32 -> BEEFY finalize #30 (mandatory), #31, #32 + finalize_block_and_wait_for_beefy(&net, peers, &mut runtime, &[30, 32], &[30, 31, 32]); +} + +#[test] +fn correct_beefy_payload() { + sp_tracing::try_init_simple(); + + let mut runtime = Runtime::new().unwrap(); + let peers = + &[BeefyKeyring::Alice, BeefyKeyring::Bob, BeefyKeyring::Charlie, BeefyKeyring::Dave]; + let validator_set = ValidatorSet::new(make_beefy_ids(peers), 0).unwrap(); + let session_len = 20; + let min_block_delta = 2; + + let mut net = BeefyTestNet::new(4, 0); + for i in 0..peers.len() { + net.peer(i).data.use_validator_set(&validator_set); + } + + // Dave will vote on bad mmr roots + net.peer(3).data.test_modifiers.as_mut().map(|tm| tm.corrupt_mmr_roots = true); + runtime.spawn(initialize_beefy(&mut net, peers, min_block_delta)); + + // push 10 blocks + net.generate_blocks(12, session_len, &validator_set); + net.block_until_sync(); + + let net = Arc::new(Mutex::new(net)); + // with 3 good voters and 1 bad one, consensus should happen and best blocks produced. + finalize_block_and_wait_for_beefy(&net, peers, &mut runtime, &[10], &[1, 9]); + + let (best_blocks, signed_commitments) = + get_beefy_streams(&mut *net.lock(), &[BeefyKeyring::Alice]); + + // now 2 good validators and 1 bad one are voting + net.lock() + .peer(0) + .client() + .as_client() + .finalize_block(BlockId::number(11), None) + .unwrap(); + net.lock() + .peer(1) + .client() + .as_client() + .finalize_block(BlockId::number(11), None) + .unwrap(); + net.lock() + .peer(3) + .client() + .as_client() + .finalize_block(BlockId::number(11), None) + .unwrap(); + + // verify consensus is _not_ reached + let timeout = Some(Duration::from_millis(500)); + streams_empty_after_timeout(best_blocks, &net, &mut runtime, timeout); + streams_empty_after_timeout(signed_commitments, &net, &mut runtime, None); + + // 3rd good validator catches up and votes as well + let (best_blocks, signed_commitments) = + get_beefy_streams(&mut *net.lock(), &[BeefyKeyring::Alice]); + net.lock() + .peer(2) + .client() + .as_client() + .finalize_block(BlockId::number(11), None) + .unwrap(); + + // verify consensus is reached + wait_for_best_beefy_blocks(best_blocks, &net, &mut runtime, &[11]); + wait_for_beefy_signed_commitments(signed_commitments, &net, &mut runtime, &[11]); +} diff --git a/client/beefy/src/worker.rs b/client/beefy/src/worker.rs index 3f23638758eca..85674c09a278b 100644 --- a/client/beefy/src/worker.rs +++ b/client/beefy/src/worker.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use std::{collections::BTreeSet, fmt::Debug, marker::PhantomData, sync::Arc}; +use std::{collections::BTreeSet, fmt::Debug, marker::PhantomData, sync::Arc, time::Duration}; use codec::{Codec, Decode, Encode}; use futures::{future, FutureExt, StreamExt}; @@ -28,6 +28,7 @@ use sc_network_gossip::GossipEngine; use sp_api::BlockId; use sp_arithmetic::traits::AtLeast32Bit; +use sp_consensus::SyncOracle; use sp_runtime::{ generic::OpaqueDigestItemId, traits::{Block, Header, NumberFor}, @@ -35,7 +36,7 @@ use sp_runtime::{ }; use beefy_primitives::{ - crypto::{AuthorityId, Public, Signature}, + crypto::{AuthorityId, Signature}, known_payload_ids, BeefyApi, Commitment, ConsensusLog, MmrRootHash, Payload, SignedCommitment, ValidatorSet, VersionedFinalityProof, VoteMessage, BEEFY_ENGINE_ID, GENESIS_AUTHORITY_SET_ID, }; @@ -47,10 +48,11 @@ use crate::{ metric_inc, metric_set, metrics::Metrics, notification::{BeefyBestBlockSender, BeefySignedCommitmentSender}, - round, Client, + round::Rounds, + Client, }; -pub(crate) struct WorkerParams +pub(crate) struct WorkerParams where B: Block, { @@ -63,14 +65,16 @@ where pub gossip_validator: Arc>, pub min_block_delta: u32, pub metrics: Option, + pub sync_oracle: SO, } /// A BEEFY worker plays the BEEFY protocol -pub(crate) struct BeefyWorker +pub(crate) struct BeefyWorker where B: Block, BE: Backend, C: Client, + SO: SyncOracle + Send + Sync + Clone + 'static, { client: Arc, backend: Arc, @@ -81,26 +85,32 @@ where /// Min delta in block numbers between two blocks, BEEFY should vote on min_block_delta: u32, metrics: Option, - rounds: Option>>, + rounds: Option>, finality_notifications: FinalityNotifications, /// Best block we received a GRANDPA notification for - best_grandpa_block: NumberFor, + best_grandpa_block_header: ::Header, /// Best block a BEEFY voting round has been concluded for best_beefy_block: Option>, /// Used to keep RPC worker up to date on latest/best beefy beefy_best_block_sender: BeefyBestBlockSender, /// Validator set id for the last signed commitment last_signed_id: u64, + /// Handle to the sync oracle + sync_oracle: SO, // keep rustc happy _backend: PhantomData, + #[cfg(test)] + // behavior modifiers used in tests + test_res: tests::TestModifiers, } -impl BeefyWorker +impl BeefyWorker where B: Block + Codec, BE: Backend, C: Client, C::Api: BeefyApi, + SO: SyncOracle + Send + Sync + Clone + 'static, { /// Return a new BEEFY worker instance. /// @@ -108,7 +118,12 @@ where /// BEEFY pallet has been deployed on-chain. /// /// The BEEFY pallet is needed in order to keep track of the BEEFY authority set. - pub(crate) fn new(worker_params: WorkerParams) -> Self { + pub(crate) fn new( + worker_params: WorkerParams, + #[cfg(test)] + // behavior modifiers used in tests + test_res: tests::TestModifiers, + ) -> Self { let WorkerParams { client, backend, @@ -119,8 +134,13 @@ where gossip_validator, min_block_delta, metrics, + sync_oracle, } = worker_params; + let last_finalized_header = client + .expect_header(BlockId::number(client.info().finalized_number)) + .expect("latest block always has header available; qed."); + BeefyWorker { client: client.clone(), backend, @@ -128,200 +148,176 @@ where signed_commitment_sender, gossip_engine: Arc::new(Mutex::new(gossip_engine)), gossip_validator, - min_block_delta, + // always target at least one block better than current best beefy + min_block_delta: min_block_delta.max(1), metrics, rounds: None, finality_notifications: client.finality_notification_stream(), - best_grandpa_block: client.info().finalized_number, + best_grandpa_block_header: last_finalized_header, best_beefy_block: None, last_signed_id: 0, beefy_best_block_sender, + sync_oracle, _backend: PhantomData, + #[cfg(test)] + test_res, } } } -impl BeefyWorker +impl BeefyWorker where B: Block, BE: Backend, C: Client, C::Api: BeefyApi, + SO: SyncOracle + Send + Sync + Clone + 'static, { - /// Return `true`, if we should vote on block `number` - fn should_vote_on(&self, number: NumberFor) -> bool { - let best_beefy_block = if let Some(block) = self.best_beefy_block { - block - } else { - debug!(target: "beefy", "🥩 Missing best BEEFY block - won't vote for: {:?}", number); - return false - }; - - let target = vote_target(self.best_grandpa_block, best_beefy_block, self.min_block_delta); - - trace!(target: "beefy", "🥩 should_vote_on: #{:?}, next_block_to_vote_on: #{:?}", number, target); - - metric_set!(self, beefy_should_vote_on, target); - - number == target - } - - /// Return the current active validator set at header `header`. - /// - /// Note that the validator set could be `None`. This is the case if we don't find - /// a BEEFY authority set change and we can't fetch the authority set from the - /// BEEFY on-chain state. - /// - /// Such a failure is usually an indication that the BEEFY pallet has not been deployed (yet). - fn validator_set(&self, header: &B::Header) -> Option> { - let new = if let Some(new) = find_authorities_change::(header) { - Some(new) + /// Return `Some(number)` if we should be voting on block `number` now, + /// return `None` if there is no block we should vote on now. + fn current_vote_target(&self) -> Option> { + let rounds = if let Some(r) = &self.rounds { + r } else { - let at = BlockId::hash(header.hash()); - self.client.runtime_api().validator_set(&at).ok().flatten() + debug!(target: "beefy", "🥩 No voting round started"); + return None }; - trace!(target: "beefy", "🥩 active validator set: {:?}", new); - - new + let best_finalized = *self.best_grandpa_block_header.number(); + // `target` is guaranteed > `best_beefy` since `min_block_delta` is at least `1`. + let target = vote_target( + best_finalized, + self.best_beefy_block, + *rounds.session_start(), + self.min_block_delta, + ); + trace!( + target: "beefy", + "🥩 best beefy: #{:?}, best finalized: #{:?}, current_vote_target: {:?}", + self.best_beefy_block, + best_finalized, + target + ); + if let Some(target) = &target { + metric_set!(self, beefy_should_vote_on, target); + } + target } /// Verify `active` validator set for `block` against the key store /// - /// The critical case is, if we do have a public key in the key store which is not - /// part of the active validator set. + /// We want to make sure that we have _at least one_ key in our keystore that + /// is part of the validator set, that's because if there are no local keys + /// then we can't perform our job as a validator. /// /// Note that for a non-authority node there will be no keystore, and we will /// return an error and don't check. The error can usually be ignored. fn verify_validator_set( &self, block: &NumberFor, - active: &ValidatorSet, + active: &ValidatorSet, ) -> Result<(), error::Error> { - let active: BTreeSet<&Public> = active.validators().iter().collect(); + let active: BTreeSet<&AuthorityId> = active.validators().iter().collect(); let public_keys = self.key_store.public_keys()?; - let store: BTreeSet<&Public> = public_keys.iter().collect(); + let store: BTreeSet<&AuthorityId> = public_keys.iter().collect(); - let missing: Vec<_> = store.difference(&active).cloned().collect(); - - if !missing.is_empty() { - debug!(target: "beefy", "🥩 for block {:?} public key missing in validator set: {:?}", block, missing); + if store.intersection(&active).count() == 0 { + let msg = "no authority public key found in store".to_string(); + debug!(target: "beefy", "🥩 for block {:?} {}", block, msg); + Err(error::Error::Keystore(msg)) + } else { + Ok(()) } - - Ok(()) } - fn handle_finality_notification(&mut self, notification: FinalityNotification) { - trace!(target: "beefy", "🥩 Finality notification: {:?}", notification); - - // update best GRANDPA finalized block we have seen - self.best_grandpa_block = *notification.header.number(); - - if let Some(active) = self.validator_set(¬ification.header) { - // Authority set change or genesis set id triggers new voting rounds - // - // TODO: (grandpa-bridge-gadget#366) Enacting a new authority set will also - // implicitly 'conclude' the currently active BEEFY voting round by starting a - // new one. This should be replaced by proper round life-cycle handling. - if self.rounds.is_none() || - active.id() != self.rounds.as_ref().unwrap().validator_set_id() || - (active.id() == GENESIS_AUTHORITY_SET_ID && self.best_beefy_block.is_none()) - { - debug!(target: "beefy", "🥩 New active validator set id: {:?}", active); - metric_set!(self, beefy_validator_set_id, active.id()); - - // BEEFY should produce a signed commitment for each session - if active.id() != self.last_signed_id + 1 && active.id() != GENESIS_AUTHORITY_SET_ID - { - metric_inc!(self, beefy_skipped_sessions); - } - - if log_enabled!(target: "beefy", log::Level::Debug) { - // verify the new validator set - only do it if we're also logging the warning - let _ = self.verify_validator_set(notification.header.number(), &active); - } - - let id = active.id(); - self.rounds = Some(round::Rounds::new(active)); - - debug!(target: "beefy", "🥩 New Rounds for id: {:?}", id); - - self.best_beefy_block = Some(*notification.header.number()); + /// Set best BEEFY block to `block_num`. + /// + /// Also sends/updates the best BEEFY block hash to the RPC worker. + fn set_best_beefy_block(&mut self, block_num: NumberFor) { + if Some(block_num) > self.best_beefy_block { + // Try to get block hash ourselves. + let block_hash = match self.client.hash(block_num) { + Ok(h) => h, + Err(e) => { + error!(target: "beefy", "🥩 Failed to get hash for block number {}: {}", + block_num, e); + None + }, + }; + // Update RPC worker with new best BEEFY block hash. + block_hash.map(|hash| { self.beefy_best_block_sender - .notify(|| Ok::<_, ()>(notification.hash.clone())) - .expect("forwards closure result; the closure always returns Ok; qed."); - - // this metric is kind of 'fake'. Best BEEFY block should only be updated once we - // have a signed commitment for the block. Remove once the above TODO is done. - metric_set!(self, beefy_best_block, *notification.header.number()); - } + .notify(|| Ok::<_, ()>(hash)) + .expect("forwards closure result; the closure always returns Ok; qed.") + }); + // Set new best BEEFY block number. + self.best_beefy_block = Some(block_num); + metric_set!(self, beefy_best_block, block_num); + } else { + debug!(target: "beefy", "🥩 Can't set best beefy to older: {}", block_num); } + } - if self.should_vote_on(*notification.header.number()) { - let (validators, validator_set_id) = if let Some(rounds) = &self.rounds { - (rounds.validators(), rounds.validator_set_id()) - } else { - debug!(target: "beefy", "🥩 Missing validator set - can't vote for: {:?}", notification.header.hash()); - return - }; - let authority_id = if let Some(id) = self.key_store.authority_id(validators) { - debug!(target: "beefy", "🥩 Local authority id: {:?}", id); - id - } else { - debug!(target: "beefy", "🥩 Missing validator id - can't vote for: {:?}", notification.header.hash()); - return - }; - - let mmr_root = - if let Some(hash) = find_mmr_root_digest::(¬ification.header) { - hash - } else { - warn!(target: "beefy", "🥩 No MMR root digest found for: {:?}", notification.header.hash()); - return - }; - - let payload = Payload::new(known_payload_ids::MMR_ROOT_ID, mmr_root.encode()); - let commitment = Commitment { - payload, - block_number: notification.header.number(), - validator_set_id, - }; - let encoded_commitment = commitment.encode(); + /// Handle session changes by starting new voting round for mandatory blocks. + fn init_session_at(&mut self, active: ValidatorSet, session_start: NumberFor) { + debug!(target: "beefy", "🥩 New active validator set: {:?}", active); + metric_set!(self, beefy_validator_set_id, active.id()); + // BEEFY should produce a signed commitment for each session + if active.id() != self.last_signed_id + 1 && active.id() != GENESIS_AUTHORITY_SET_ID { + metric_inc!(self, beefy_skipped_sessions); + } - let signature = match self.key_store.sign(&authority_id, &*encoded_commitment) { - Ok(sig) => sig, - Err(err) => { - warn!(target: "beefy", "🥩 Error signing commitment: {:?}", err); - return - }, - }; + if log_enabled!(target: "beefy", log::Level::Debug) { + // verify the new validator set - only do it if we're also logging the warning + let _ = self.verify_validator_set(&session_start, &active); + } - trace!( - target: "beefy", - "🥩 Produced signature using {:?}, is_valid: {:?}", - authority_id, - BeefyKeystore::verify(&authority_id, &signature, &*encoded_commitment) - ); + let prev_validator_set = if let Some(r) = &self.rounds { + r.validator_set().clone() + } else { + // no previous rounds present use new validator set instead (genesis case) + active.clone() + }; + let id = active.id(); + self.rounds = Some(Rounds::new(session_start, active, prev_validator_set)); + info!(target: "beefy", "🥩 New Rounds for validator set id: {:?} with session_start {:?}", id, session_start); + } - let message = VoteMessage { commitment, id: authority_id, signature }; + fn handle_finality_notification(&mut self, notification: &FinalityNotification) { + trace!(target: "beefy", "🥩 Finality notification: {:?}", notification); + let number = *notification.header.number(); - let encoded_message = message.encode(); + // On start-up ignore old finality notifications that we're not interested in. + if number <= *self.best_grandpa_block_header.number() { + debug!(target: "beefy", "🥩 Got unexpected finality for old block #{:?}", number); + return + } - metric_inc!(self, beefy_votes_sent); + // update best GRANDPA finalized block we have seen + self.best_grandpa_block_header = notification.header.clone(); - debug!(target: "beefy", "🥩 Sent vote message: {:?}", message); + self.handle_finality(¬ification.header); + } - self.handle_vote( - (message.commitment.payload, *message.commitment.block_number), - (message.id, message.signature), - ); + fn handle_finality(&mut self, header: &B::Header) { + // Check for and handle potential new session. + if let Some(new_validator_set) = find_authorities_change::(header) { + self.init_session_at(new_validator_set, *header.number()); + } - self.gossip_engine.lock().gossip_message(topic::(), encoded_message, false); + // Vote if there's now a new vote target. + if let Some(target_number) = self.current_vote_target() { + self.do_vote(target_number); } } - fn handle_vote(&mut self, round: (Payload, NumberFor), vote: (Public, Signature)) { + fn handle_vote( + &mut self, + round: (Payload, NumberFor), + vote: (AuthorityId, Signature), + self_vote: bool, + ) { self.gossip_validator.note_round(round.1); let rounds = if let Some(rounds) = self.rounds.as_mut() { @@ -331,12 +327,12 @@ where return }; - let vote_added = rounds.add_vote(&round, vote); + if rounds.add_vote(&round, vote, self_vote) { + if let Some(signatures) = rounds.try_conclude(&round) { + self.gossip_validator.conclude_round(round.1); - if vote_added && rounds.is_done(&round) { - if let Some(signatures) = rounds.drop(&round) { // id is stored for skipped session metric calculation - self.last_signed_id = rounds.validator_set_id(); + self.last_signed_id = rounds.validator_set_id_for(round.1); let block_num = round.1; let commitment = Commitment { @@ -351,48 +347,167 @@ where info!(target: "beefy", "🥩 Round #{} concluded, committed: {:?}.", round.1, signed_commitment); - if self - .backend - .append_justification( - BlockId::Number(block_num), - ( - BEEFY_ENGINE_ID, - VersionedFinalityProof::V1(signed_commitment.clone()).encode(), - ), - ) - .is_err() - { - // just a trace, because until the round lifecycle is improved, we will - // conclude certain rounds multiple times. - trace!(target: "beefy", "🥩 Failed to append justification: {:?}", signed_commitment); + if let Err(e) = self.backend.append_justification( + BlockId::Number(block_num), + ( + BEEFY_ENGINE_ID, + VersionedFinalityProof::V1(signed_commitment.clone()).encode(), + ), + ) { + trace!(target: "beefy", "🥩 Error {:?} on appending justification: {:?}", e, signed_commitment); } self.signed_commitment_sender .notify(|| Ok::<_, ()>(signed_commitment)) .expect("forwards closure result; the closure always returns Ok; qed."); - self.best_beefy_block = Some(block_num); - if let Err(err) = self.client.hash(block_num).map(|h| { - if let Some(hash) = h { - self.beefy_best_block_sender - .notify(|| Ok::<_, ()>(hash)) - .expect("forwards closure result; the closure always returns Ok; qed."); - } - }) { - error!(target: "beefy", "🥩 Failed to get hash for block number {}: {}", - block_num, err); - } + self.set_best_beefy_block(block_num); - metric_set!(self, beefy_best_block, block_num); + // Vote if there's now a new vote target. + if let Some(target_number) = self.current_vote_target() { + self.do_vote(target_number); + } } } } + /// Create and gossip Signed Commitment for block number `target_number`. + /// + /// Also handle this self vote by calling `self.handle_vote()` for it. + fn do_vote(&mut self, target_number: NumberFor) { + trace!(target: "beefy", "🥩 Try voting on {}", target_number); + + // Most of the time we get here, `target` is actually `best_grandpa`, + // avoid asking `client` for header in that case. + let target_header = if target_number == *self.best_grandpa_block_header.number() { + self.best_grandpa_block_header.clone() + } else { + match self.client.expect_header(BlockId::Number(target_number)) { + Ok(h) => h, + Err(err) => { + debug!( + target: "beefy", + "🥩 Could not get header for block #{:?} (error: {:?}), skipping vote..", + target_number, + err + ); + return + }, + } + }; + let target_hash = target_header.hash(); + + let mmr_root = if let Some(hash) = self.extract_mmr_root_digest(&target_header) { + hash + } else { + warn!(target: "beefy", "🥩 No MMR root digest found for: {:?}", target_hash); + return + }; + let payload = Payload::new(known_payload_ids::MMR_ROOT_ID, mmr_root.encode()); + + let (validators, validator_set_id) = if let Some(rounds) = &self.rounds { + if !rounds.should_self_vote(&(payload.clone(), target_number)) { + debug!(target: "beefy", "🥩 Don't double vote for block number: {:?}", target_number); + return + } + (rounds.validators_for(target_number), rounds.validator_set_id_for(target_number)) + } else { + debug!(target: "beefy", "🥩 Missing validator set - can't vote for: {:?}", target_hash); + return + }; + let authority_id = if let Some(id) = self.key_store.authority_id(validators) { + debug!(target: "beefy", "🥩 Local authority id: {:?}", id); + id + } else { + debug!(target: "beefy", "🥩 Missing validator id - can't vote for: {:?}", target_hash); + return + }; + + let commitment = Commitment { payload, block_number: target_number, validator_set_id }; + let encoded_commitment = commitment.encode(); + + let signature = match self.key_store.sign(&authority_id, &*encoded_commitment) { + Ok(sig) => sig, + Err(err) => { + warn!(target: "beefy", "🥩 Error signing commitment: {:?}", err); + return + }, + }; + + trace!( + target: "beefy", + "🥩 Produced signature using {:?}, is_valid: {:?}", + authority_id, + BeefyKeystore::verify(&authority_id, &signature, &*encoded_commitment) + ); + + let message = VoteMessage { commitment, id: authority_id, signature }; + + let encoded_message = message.encode(); + + metric_inc!(self, beefy_votes_sent); + + debug!(target: "beefy", "🥩 Sent vote message: {:?}", message); + + self.handle_vote( + (message.commitment.payload, message.commitment.block_number), + (message.id, message.signature), + true, + ); + + self.gossip_engine.lock().gossip_message(topic::(), encoded_message, false); + } + + /// Wait for BEEFY runtime pallet to be available. + #[cfg(not(test))] + async fn wait_for_runtime_pallet(&mut self) { + self.client + .finality_notification_stream() + .take_while(|notif| { + let at = BlockId::hash(notif.header.hash()); + if let Some(active) = self.client.runtime_api().validator_set(&at).ok().flatten() { + if active.id() == GENESIS_AUTHORITY_SET_ID { + // When starting from genesis, there is no session boundary digest. + // Just initialize `rounds` to Block #1 as BEEFY mandatory block. + self.init_session_at(active, 1u32.into()); + } + // In all other cases, we just go without `rounds` initialized, meaning the + // worker won't vote until it witnesses a session change. + // Once we'll implement 'initial sync' (catch-up), the worker will be able to + // start voting right away. + self.handle_finality_notification(notif); + future::ready(false) + } else { + trace!(target: "beefy", "🥩 Finality notification: {:?}", notif); + trace!(target: "beefy", "🥩 Waiting for BEEFY pallet to become available..."); + future::ready(true) + } + }) + .for_each(|_| future::ready(())) + .await; + // get a new stream that provides _new_ notifications (from here on out) + self.finality_notifications = self.client.finality_notification_stream(); + } + + /// For tests don't use runtime pallet. Start rounds from block #1. + #[cfg(test)] + async fn wait_for_runtime_pallet(&mut self) { + let active = self.test_res.active_validators.clone(); + self.init_session_at(active, 1u32.into()); + } + + /// Main loop for BEEFY worker. + /// + /// Wait for BEEFY runtime pallet to be available, then start the main async loop + /// which is driven by finality notifications and gossiped votes. pub(crate) async fn run(mut self) { + info!(target: "beefy", "🥩 run BEEFY worker, best grandpa: #{:?}.", self.best_grandpa_block_header.number()); + self.wait_for_runtime_pallet().await; + let mut votes = Box::pin(self.gossip_engine.lock().messages_for(topic::()).filter_map( |notification| async move { debug!(target: "beefy", "🥩 Got vote message: {:?}", notification); - VoteMessage::, Public, Signature>::decode( + VoteMessage::, AuthorityId, Signature>::decode( &mut ¬ification.message[..], ) .ok() @@ -400,13 +515,18 @@ where )); loop { + while self.sync_oracle.is_major_syncing() { + debug!(target: "beefy", "Waiting for major sync to complete..."); + futures_timer::Delay::new(Duration::from_secs(5)).await; + } + let engine = self.gossip_engine.clone(); let gossip_engine = future::poll_fn(|cx| engine.lock().poll_unpin(cx)); futures::select! { notification = self.finality_notifications.next().fuse() => { if let Some(notification) = notification { - self.handle_finality_notification(notification); + self.handle_finality_notification(¬ification); } else { return; } @@ -416,6 +536,7 @@ where self.handle_vote( (vote.commitment.payload, vote.commitment.block_number), (vote.id, vote.signature), + false ); } else { return; @@ -428,20 +549,36 @@ where } } } + + /// Simple wrapper over mmr root extraction. + #[cfg(not(test))] + fn extract_mmr_root_digest(&self, header: &B::Header) -> Option { + find_mmr_root_digest::(header) + } + + /// For tests, have the option to modify mmr root. + #[cfg(test)] + fn extract_mmr_root_digest(&self, header: &B::Header) -> Option { + let mut mmr_root = find_mmr_root_digest::(header); + if self.test_res.corrupt_mmr_roots { + mmr_root.as_mut().map(|hash| *hash ^= MmrRootHash::random()); + } + mmr_root + } } /// Extract the MMR root hash from a digest in the given header, if it exists. -fn find_mmr_root_digest(header: &B::Header) -> Option +fn find_mmr_root_digest(header: &B::Header) -> Option where B: Block, - Id: Codec, { - header.digest().logs().iter().find_map(|log| { - match log.try_to::>(OpaqueDigestItemId::Consensus(&BEEFY_ENGINE_ID)) { - Some(ConsensusLog::MmrRoot(root)) => Some(root), - _ => None, - } - }) + let id = OpaqueDigestItemId::Consensus(&BEEFY_ENGINE_ID); + + let filter = |log: ConsensusLog| match log { + ConsensusLog::MmrRoot(root) => Some(root), + _ => None, + }; + header.digest().convert_first(|l| l.try_to(id).and_then(filter)) } /// Scan the `header` digest log for a BEEFY validator set change. Return either the new @@ -456,119 +593,402 @@ where ConsensusLog::AuthoritiesChange(validator_set) => Some(validator_set), _ => None, }; - header.digest().convert_first(|l| l.try_to(id).and_then(filter)) } -/// Calculate next block number to vote on -fn vote_target(best_grandpa: N, best_beefy: N, min_delta: u32) -> N +/// Calculate next block number to vote on. +/// +/// Return `None` if there is no voteable target yet. +fn vote_target( + best_grandpa: N, + best_beefy: Option, + session_start: N, + min_delta: u32, +) -> Option where N: AtLeast32Bit + Copy + Debug, { - let diff = best_grandpa.saturating_sub(best_beefy); - let diff = diff.saturated_into::(); - let target = best_beefy + min_delta.max(diff.next_power_of_two()).into(); - - trace!( - target: "beefy", - "🥩 vote target - diff: {:?}, next_power_of_two: {:?}, target block: #{:?}", - diff, - diff.next_power_of_two(), - target, - ); - - target + // if the mandatory block (session_start) does not have a beefy justification yet, + // we vote on it + let target = match best_beefy { + None => { + trace!( + target: "beefy", + "🥩 vote target - mandatory block: #{:?}", + session_start, + ); + session_start + }, + Some(bbb) if bbb < session_start => { + trace!( + target: "beefy", + "🥩 vote target - mandatory block: #{:?}", + session_start, + ); + session_start + }, + Some(bbb) => { + let diff = best_grandpa.saturating_sub(bbb) + 1u32.into(); + let diff = diff.saturated_into::() / 2; + let target = bbb + min_delta.max(diff.next_power_of_two()).into(); + + trace!( + target: "beefy", + "🥩 vote target - diff: {:?}, next_power_of_two: {:?}, target block: #{:?}", + diff, + diff.next_power_of_two(), + target, + ); + + target + }, + }; + + // Don't vote for targets until they've been finalized + // (`target` can be > `best_grandpa` when `min_delta` is big enough). + if target > best_grandpa { + None + } else { + Some(target) + } } #[cfg(test)] -mod tests { - use super::vote_target; +pub(crate) mod tests { + use super::*; + use crate::{ + keystore::tests::Keyring, + tests::{create_beefy_worker, get_beefy_streams, make_beefy_ids, BeefyTestNet}, + }; + + use futures::{executor::block_on, future::poll_fn, task::Poll}; + + use sc_client_api::HeaderBackend; + use sc_network::NetworkService; + use sc_network_test::{PeersFullClient, TestNetFactory}; + use sp_api::HeaderT; + use substrate_test_runtime_client::{ + runtime::{Block, Digest, DigestItem, Header, H256}, + Backend, + }; + + #[derive(Clone)] + pub struct TestModifiers { + pub active_validators: ValidatorSet, + pub corrupt_mmr_roots: bool, + } #[test] fn vote_on_min_block_delta() { - let t = vote_target(1u32, 0, 4); - assert_eq!(4, t); - let t = vote_target(2u32, 0, 4); - assert_eq!(4, t); - let t = vote_target(3u32, 0, 4); - assert_eq!(4, t); - let t = vote_target(4u32, 0, 4); - assert_eq!(4, t); - - let t = vote_target(4u32, 4, 4); - assert_eq!(8, t); - - let t = vote_target(10u32, 10, 4); - assert_eq!(14, t); - let t = vote_target(11u32, 10, 4); - assert_eq!(14, t); - let t = vote_target(12u32, 10, 4); - assert_eq!(14, t); - let t = vote_target(13u32, 10, 4); - assert_eq!(14, t); - - let t = vote_target(10u32, 10, 8); - assert_eq!(18, t); - let t = vote_target(11u32, 10, 8); - assert_eq!(18, t); - let t = vote_target(12u32, 10, 8); - assert_eq!(18, t); - let t = vote_target(13u32, 10, 8); - assert_eq!(18, t); + let t = vote_target(1u32, Some(1), 1, 4); + assert_eq!(None, t); + let t = vote_target(2u32, Some(1), 1, 4); + assert_eq!(None, t); + let t = vote_target(4u32, Some(2), 1, 4); + assert_eq!(None, t); + let t = vote_target(6u32, Some(2), 1, 4); + assert_eq!(Some(6), t); + + let t = vote_target(9u32, Some(4), 1, 4); + assert_eq!(Some(8), t); + + let t = vote_target(10u32, Some(10), 1, 8); + assert_eq!(None, t); + let t = vote_target(12u32, Some(10), 1, 8); + assert_eq!(None, t); + let t = vote_target(18u32, Some(10), 1, 8); + assert_eq!(Some(18), t); } #[test] fn vote_on_power_of_two() { - let t = vote_target(1008u32, 1000, 4); - assert_eq!(1008, t); + let t = vote_target(1008u32, Some(1000), 1, 4); + assert_eq!(Some(1004), t); - let t = vote_target(1016u32, 1000, 4); - assert_eq!(1016, t); + let t = vote_target(1016u32, Some(1000), 1, 4); + assert_eq!(Some(1008), t); - let t = vote_target(1032u32, 1000, 4); - assert_eq!(1032, t); + let t = vote_target(1032u32, Some(1000), 1, 4); + assert_eq!(Some(1016), t); - let t = vote_target(1064u32, 1000, 4); - assert_eq!(1064, t); + let t = vote_target(1064u32, Some(1000), 1, 4); + assert_eq!(Some(1032), t); - let t = vote_target(1128u32, 1000, 4); - assert_eq!(1128, t); + let t = vote_target(1128u32, Some(1000), 1, 4); + assert_eq!(Some(1064), t); - let t = vote_target(1256u32, 1000, 4); - assert_eq!(1256, t); + let t = vote_target(1256u32, Some(1000), 1, 4); + assert_eq!(Some(1128), t); - let t = vote_target(1512u32, 1000, 4); - assert_eq!(1512, t); + let t = vote_target(1512u32, Some(1000), 1, 4); + assert_eq!(Some(1256), t); - let t = vote_target(1024u32, 0, 4); - assert_eq!(1024, t); + let t = vote_target(1024u32, Some(1), 1, 4); + assert_eq!(Some(513), t); } #[test] fn vote_on_target_block() { - let t = vote_target(1008u32, 1002, 4); - assert_eq!(1010, t); - let t = vote_target(1010u32, 1002, 4); - assert_eq!(1010, t); - - let t = vote_target(1016u32, 1006, 4); - assert_eq!(1022, t); - let t = vote_target(1022u32, 1006, 4); - assert_eq!(1022, t); - - let t = vote_target(1032u32, 1012, 4); - assert_eq!(1044, t); - let t = vote_target(1044u32, 1012, 4); - assert_eq!(1044, t); - - let t = vote_target(1064u32, 1014, 4); - assert_eq!(1078, t); - let t = vote_target(1078u32, 1014, 4); - assert_eq!(1078, t); - - let t = vote_target(1128u32, 1008, 4); - assert_eq!(1136, t); - let t = vote_target(1136u32, 1008, 4); - assert_eq!(1136, t); + let t = vote_target(1008u32, Some(1002), 1, 4); + assert_eq!(Some(1006), t); + let t = vote_target(1010u32, Some(1002), 1, 4); + assert_eq!(Some(1006), t); + + let t = vote_target(1016u32, Some(1006), 1, 4); + assert_eq!(Some(1014), t); + let t = vote_target(1022u32, Some(1006), 1, 4); + assert_eq!(Some(1014), t); + + let t = vote_target(1032u32, Some(1012), 1, 4); + assert_eq!(Some(1028), t); + let t = vote_target(1044u32, Some(1012), 1, 4); + assert_eq!(Some(1028), t); + + let t = vote_target(1064u32, Some(1014), 1, 4); + assert_eq!(Some(1046), t); + let t = vote_target(1078u32, Some(1014), 1, 4); + assert_eq!(Some(1046), t); + + let t = vote_target(1128u32, Some(1008), 1, 4); + assert_eq!(Some(1072), t); + let t = vote_target(1136u32, Some(1008), 1, 4); + assert_eq!(Some(1072), t); + } + + #[test] + fn vote_on_mandatory_block() { + let t = vote_target(1008u32, Some(1002), 1004, 4); + assert_eq!(Some(1004), t); + let t = vote_target(1016u32, Some(1006), 1007, 4); + assert_eq!(Some(1007), t); + let t = vote_target(1064u32, Some(1014), 1063, 4); + assert_eq!(Some(1063), t); + let t = vote_target(1320u32, Some(1012), 1234, 4); + assert_eq!(Some(1234), t); + + let t = vote_target(1128u32, Some(1008), 1008, 4); + assert_eq!(Some(1072), t); + } + + #[test] + fn extract_authorities_change_digest() { + let mut header = Header::new( + 1u32.into(), + Default::default(), + Default::default(), + Default::default(), + Digest::default(), + ); + + // verify empty digest shows nothing + assert!(find_authorities_change::(&header).is_none()); + + let peers = &[Keyring::One, Keyring::Two]; + let id = 42; + let validator_set = ValidatorSet::new(make_beefy_ids(peers), id).unwrap(); + header.digest_mut().push(DigestItem::Consensus( + BEEFY_ENGINE_ID, + ConsensusLog::::AuthoritiesChange(validator_set.clone()).encode(), + )); + + // verify validator set is correctly extracted from digest + let extracted = find_authorities_change::(&header); + assert_eq!(extracted, Some(validator_set)); + } + + #[test] + fn extract_mmr_root_digest() { + let mut header = Header::new( + 1u32.into(), + Default::default(), + Default::default(), + Default::default(), + Digest::default(), + ); + + // verify empty digest shows nothing + assert!(find_mmr_root_digest::(&header).is_none()); + + let mmr_root_hash = H256::random(); + header.digest_mut().push(DigestItem::Consensus( + BEEFY_ENGINE_ID, + ConsensusLog::::MmrRoot(mmr_root_hash.clone()).encode(), + )); + + // verify validator set is correctly extracted from digest + let extracted = find_mmr_root_digest::(&header); + assert_eq!(extracted, Some(mmr_root_hash)); + } + + #[test] + fn should_vote_target() { + let keys = &[Keyring::Alice]; + let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); + let mut net = BeefyTestNet::new(1, 0); + net.peer(0).data.use_validator_set(&validator_set); + let mut worker = create_beefy_worker(&net.peer(0), &keys[0], 1); + + // rounds not initialized -> should vote: `None` + assert_eq!(worker.current_vote_target(), None); + + let set_up = |worker: &mut BeefyWorker< + Block, + PeersFullClient, + Backend, + Arc>, + >, + best_grandpa: u64, + best_beefy: Option, + session_start: u64, + min_delta: u32| { + let grandpa_header = Header::new( + best_grandpa, + Default::default(), + Default::default(), + Default::default(), + Default::default(), + ); + worker.best_grandpa_block_header = grandpa_header; + worker.best_beefy_block = best_beefy; + worker.min_block_delta = min_delta; + worker.rounds = + Some(Rounds::new(session_start, validator_set.clone(), validator_set.clone())); + }; + + // under min delta + set_up(&mut worker, 1, Some(1), 1, 4); + assert_eq!(worker.current_vote_target(), None); + set_up(&mut worker, 5, Some(2), 1, 4); + assert_eq!(worker.current_vote_target(), None); + + // vote on min delta + set_up(&mut worker, 9, Some(4), 1, 4); + assert_eq!(worker.current_vote_target(), Some(8)); + set_up(&mut worker, 18, Some(10), 1, 8); + assert_eq!(worker.current_vote_target(), Some(18)); + + // vote on power of two + set_up(&mut worker, 1008, Some(1000), 1, 1); + assert_eq!(worker.current_vote_target(), Some(1004)); + set_up(&mut worker, 1016, Some(1000), 1, 2); + assert_eq!(worker.current_vote_target(), Some(1008)); + + // nothing new to vote on + set_up(&mut worker, 1000, Some(1000), 1, 1); + assert_eq!(worker.current_vote_target(), None); + + // vote on mandatory + set_up(&mut worker, 1008, None, 1000, 8); + assert_eq!(worker.current_vote_target(), Some(1000)); + set_up(&mut worker, 1008, Some(1000), 1001, 8); + assert_eq!(worker.current_vote_target(), Some(1001)); + } + + #[test] + fn keystore_vs_validator_set() { + let keys = &[Keyring::Alice]; + let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); + let mut net = BeefyTestNet::new(1, 0); + net.peer(0).data.use_validator_set(&validator_set); + let mut worker = create_beefy_worker(&net.peer(0), &keys[0], 1); + + // keystore doesn't contain other keys than validators' + assert_eq!(worker.verify_validator_set(&1, &validator_set), Ok(())); + + // unknown `Bob` key + let keys = &[Keyring::Bob]; + let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); + let err_msg = "no authority public key found in store".to_string(); + let expected = Err(error::Error::Keystore(err_msg)); + assert_eq!(worker.verify_validator_set(&1, &validator_set), expected); + + // worker has no keystore + worker.key_store = None.into(); + let expected_err = Err(error::Error::Keystore("no Keystore".into())); + assert_eq!(worker.verify_validator_set(&1, &validator_set), expected_err); + } + + #[test] + fn setting_best_beefy_block() { + let keys = &[Keyring::Alice]; + let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); + let mut net = BeefyTestNet::new(1, 0); + net.peer(0).data.use_validator_set(&validator_set); + let mut worker = create_beefy_worker(&net.peer(0), &keys[0], 1); + + let (mut best_block_streams, _) = get_beefy_streams(&mut net, keys); + let mut best_block_stream = best_block_streams.drain(..).next().unwrap(); + + // no 'best beefy block' + assert_eq!(worker.best_beefy_block, None); + block_on(poll_fn(move |cx| { + assert_eq!(best_block_stream.poll_next_unpin(cx), Poll::Pending); + Poll::Ready(()) + })); + + // unknown hash for block #1 + let (mut best_block_streams, _) = get_beefy_streams(&mut net, keys); + let mut best_block_stream = best_block_streams.drain(..).next().unwrap(); + worker.set_best_beefy_block(1); + assert_eq!(worker.best_beefy_block, Some(1)); + block_on(poll_fn(move |cx| { + assert_eq!(best_block_stream.poll_next_unpin(cx), Poll::Pending); + Poll::Ready(()) + })); + + // generate 2 blocks, try again expect success + let (mut best_block_streams, _) = get_beefy_streams(&mut net, keys); + let mut best_block_stream = best_block_streams.drain(..).next().unwrap(); + net.generate_blocks(2, 10, &validator_set); + + worker.set_best_beefy_block(2); + assert_eq!(worker.best_beefy_block, Some(2)); + block_on(poll_fn(move |cx| { + match best_block_stream.poll_next_unpin(cx) { + // expect Some(hash-of-block-2) + Poll::Ready(Some(hash)) => { + let block_num = net.peer(0).client().as_client().number(hash).unwrap(); + assert_eq!(block_num, Some(2)); + }, + v => panic!("unexpected value: {:?}", v), + } + Poll::Ready(()) + })); + } + + #[test] + fn setting_initial_session() { + let keys = &[Keyring::Alice]; + let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); + let mut net = BeefyTestNet::new(1, 0); + net.peer(0).data.use_validator_set(&validator_set); + let mut worker = create_beefy_worker(&net.peer(0), &keys[0], 1); + + assert!(worker.rounds.is_none()); + + // verify setting the correct validator sets and boundary for genesis session + worker.init_session_at(validator_set.clone(), 1); + + let worker_rounds = worker.rounds.as_ref().unwrap(); + assert_eq!(worker_rounds.validator_set(), &validator_set); + assert_eq!(worker_rounds.session_start(), &1); + // in genesis case both current and prev validator sets are the same + assert_eq!(worker_rounds.validator_set_id_for(1), validator_set.id()); + assert_eq!(worker_rounds.validator_set_id_for(2), validator_set.id()); + + // new validator set + let keys = &[Keyring::Bob]; + let new_validator_set = ValidatorSet::new(make_beefy_ids(keys), 1).unwrap(); + + // verify setting the correct validator sets and boundary for non-genesis session + worker.init_session_at(new_validator_set.clone(), 11); + + let worker_rounds = worker.rounds.as_ref().unwrap(); + assert_eq!(worker_rounds.validator_set(), &new_validator_set); + assert_eq!(worker_rounds.session_start(), &11); + // mandatory block gets prev set, further blocks get new set + assert_eq!(worker_rounds.validator_set_id_for(11), validator_set.id()); + assert_eq!(worker_rounds.validator_set_id_for(12), new_validator_set.id()); + assert_eq!(worker_rounds.validator_set_id_for(13), new_validator_set.id()); } } diff --git a/frame/beefy/src/lib.rs b/frame/beefy/src/lib.rs index 4aa1d1337cd0a..744a06561e8c2 100644 --- a/frame/beefy/src/lib.rs +++ b/frame/beefy/src/lib.rs @@ -105,20 +105,20 @@ impl Pallet { } fn change_authorities(new: Vec, queued: Vec) { - // As in GRANDPA, we trigger a validator set change only if the the validator - // set has actually changed. - if new != Self::authorities() { - >::put(&new); - - let next_id = Self::validator_set_id() + 1u64; - >::put(next_id); - if let Some(validator_set) = ValidatorSet::::new(new, next_id) { - let log = DigestItem::Consensus( - BEEFY_ENGINE_ID, - ConsensusLog::AuthoritiesChange(validator_set).encode(), - ); - >::deposit_log(log); - } + // Always issue a change if `session` says that the validators have changed. + // Even if their session keys are the same as before, the underlying economic + // identities have changed. Furthermore, the digest below is used to signal + // BEEFY mandatory blocks. + >::put(&new); + + let next_id = Self::validator_set_id() + 1u64; + >::put(next_id); + if let Some(validator_set) = ValidatorSet::::new(new, next_id) { + let log = DigestItem::Consensus( + BEEFY_ENGINE_ID, + ConsensusLog::AuthoritiesChange(validator_set).encode(), + ); + >::deposit_log(log); } >::put(&queued); diff --git a/primitives/beefy/Cargo.toml b/primitives/beefy/Cargo.toml index 4aa53aff2c3cb..cf901f4a34fc6 100644 --- a/primitives/beefy/Cargo.toml +++ b/primitives/beefy/Cargo.toml @@ -4,8 +4,13 @@ version = "4.0.0-dev" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate" description = "Primitives for BEEFY protocol." +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { version = "3.0.0", package = "parity-scale-codec", default-features = false, features = ["derive"] } diff --git a/test-utils/runtime/Cargo.toml b/test-utils/runtime/Cargo.toml index 8c61cbbf8adbe..ad7dca5e08fb1 100644 --- a/test-utils/runtime/Cargo.toml +++ b/test-utils/runtime/Cargo.toml @@ -13,6 +13,7 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] +beefy-primitives = { version = "4.0.0-dev", default-features = false, path = "../../primitives/beefy" } sp-application-crypto = { version = "6.0.0", default-features = false, path = "../../primitives/application-crypto" } sp-consensus-aura = { version = "0.10.0-dev", default-features = false, path = "../../primitives/consensus/aura" } sp-consensus-babe = { version = "0.10.0-dev", default-features = false, path = "../../primitives/consensus/babe" } @@ -65,6 +66,7 @@ default = [ "std", ] std = [ + "beefy-primitives/std", "sp-application-crypto/std", "sp-consensus-aura/std", "sp-consensus-babe/std", diff --git a/test-utils/runtime/src/lib.rs b/test-utils/runtime/src/lib.rs index 861d95efb3087..743652a0ee899 100644 --- a/test-utils/runtime/src/lib.rs +++ b/test-utils/runtime/src/lib.rs @@ -926,6 +926,12 @@ cfg_if! { } } + impl beefy_primitives::BeefyApi for RuntimeApi { + fn validator_set() -> Option> { + None + } + } + impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { fn account_nonce(_account: AccountId) -> Index { 0 From 581f1ea485f1b93520a9ce6724b8ab02f9bc9d2a Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Fri, 25 Mar 2022 19:38:39 +0000 Subject: [PATCH 062/484] Remove unneeded code (#11117) * Remove unneeded code * Remove unused imports Signed-off-by: Oliver Tale-Yazdi Co-authored-by: Oliver Tale-Yazdi --- frame/whitelist/src/lib.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/frame/whitelist/src/lib.rs b/frame/whitelist/src/lib.rs index c2de16964a494..239f0fd280160 100644 --- a/frame/whitelist/src/lib.rs +++ b/frame/whitelist/src/lib.rs @@ -39,7 +39,7 @@ mod mock; mod tests; pub mod weights; -use codec::{Decode, DecodeLimit, Encode, FullCodec, MaxEncodedLen}; +use codec::{DecodeLimit, Encode, FullCodec}; use frame_support::{ ensure, traits::{PreimageProvider, PreimageRecipient}, @@ -51,12 +51,6 @@ use sp_std::prelude::*; pub use pallet::*; -#[derive(Clone, Encode, Decode, TypeInfo, MaxEncodedLen)] -pub struct Preimage { - preimage: BoundedVec, - deposit: Option<(AccountId, Balance)>, -} - #[frame_support::pallet] pub mod pallet { use super::*; From ede7b623b3b3d9e87dc21b9710e320ec231e3883 Mon Sep 17 00:00:00 2001 From: Georges Date: Fri, 25 Mar 2022 20:15:50 +0000 Subject: [PATCH 063/484] Add a bounded fallback on failed elections (#10988) * Allow `pallet-election-provider` to accept smaller solutions, issue #9478 * Fixing a typo * Adding some more tests Removing a seemingly outdated comment * making it a URL * Updating test name as per suggestion Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Updating documentation to be more explicit And to follow the general guidelines Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Fixing formatting * `Fallback` now of type `InstantElectionProvider` Some cleanups * Allow `pallet-election-provider` to accept smaller solutions, issue #9478 * Fixing a typo * Adding some more tests Removing a seemingly outdated comment * making it a URL * Updating test name as per suggestion Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Updating documentation to be more explicit And to follow the general guidelines Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Fixing formatting * `Fallback` now of type `InstantElectionProvider` Some cleanups * Merging types into one type with generics * Removing `ConstUSize` and use `ConstU32` * cleaning up the code * deprecating `OnChainSequentialPhragmen` Renaming it to `UnboundedSequentialPhragmen` which should only be used at genesis and for testing. Use preferrably `BoundedOnChainSequentialPhragmen` * Amending docs * Adding some explicit imports * Implementing generic `BoundedOnchainExecution` Removing the deprecated `OnChainSequentialPhragmen` * Use the right Balancing strategy * Refactoring `onchain::Config` Creating `onchain::ExecutionConfig` * Merge master * fmt * Name cleanups after review suggestions * cosmetics * renaming `instant_elect` to `elect_with_bounds` Other corresponding changes as per @kianenigma feedback * `BoundedOnchainExecution` -> `BoundedExecution` And `UnboundedOnchainExecution` -> `UnboundedExecution` * feedback from kian * fmt + unneeded import Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Co-authored-by: kianenigma --- bin/node/runtime/src/lib.rs | 34 ++-- frame/babe/src/mock.rs | 10 +- .../election-provider-multi-phase/src/lib.rs | 26 ++- .../election-provider-multi-phase/src/mock.rs | 30 ++- frame/election-provider-support/src/lib.rs | 20 +- .../election-provider-support/src/onchain.rs | 189 +++++++++++------- frame/grandpa/src/mock.rs | 10 +- .../merkle-mountain-range/src/mmr/storage.rs | 7 +- frame/offences/benchmarking/src/mock.rs | 10 +- frame/session/benchmarking/src/mock.rs | 10 +- frame/staking/src/mock.rs | 12 +- 11 files changed, 223 insertions(+), 135 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 47bdedbab83a3..565f151ce2a08 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -23,7 +23,7 @@ #![recursion_limit = "256"] use codec::{Decode, Encode, MaxEncodedLen}; -use frame_election_provider_support::{onchain, ExtendedBalance, VoteWeight}; +use frame_election_provider_support::{onchain, ExtendedBalance, SequentialPhragmen, VoteWeight}; use frame_support::{ construct_runtime, pallet_prelude::Get, @@ -557,7 +557,7 @@ impl pallet_staking::Config for Runtime { type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type ElectionProvider = ElectionProviderMultiPhase; - type GenesisElectionProvider = onchain::OnChainSequentialPhragmen; + type GenesisElectionProvider = onchain::UnboundedExecution; type VoterList = BagsList; type MaxUnlockingChunks = ConstU32<32>; type WeightInfo = pallet_staking::weights::SubstrateWeight; @@ -642,9 +642,19 @@ impl Get> for OffchainRandomBalancing { } } -impl onchain::Config for Runtime { - type Accuracy = Perbill; - type DataProvider = ::DataProvider; +pub struct OnChainSeqPhragmen; +impl onchain::ExecutionConfig for OnChainSeqPhragmen { + type System = Runtime; + type Solver = SequentialPhragmen< + AccountId, + pallet_election_provider_multi_phase::SolutionAccuracyOf, + >; + type DataProvider = ::DataProvider; +} + +impl onchain::BoundedExecutionConfig for OnChainSeqPhragmen { + type VotersBound = ConstU32<20_000>; + type TargetsBound = ConstU32<2_000>; } impl pallet_election_provider_multi_phase::Config for Runtime { @@ -668,13 +678,9 @@ impl pallet_election_provider_multi_phase::Config for Runtime { type RewardHandler = (); // nothing to do upon rewards type DataProvider = Staking; type Solution = NposSolution16; - type Fallback = pallet_election_provider_multi_phase::NoFallback; - type GovernanceFallback = onchain::OnChainSequentialPhragmen; - type Solver = frame_election_provider_support::SequentialPhragmen< - AccountId, - SolutionAccuracyOf, - OffchainRandomBalancing, - >; + type Fallback = onchain::BoundedExecution; + type GovernanceFallback = onchain::BoundedExecution; + type Solver = SequentialPhragmen, OffchainRandomBalancing>; type ForceOrigin = EnsureRootOrHalfCouncil; type MaxElectableTargets = ConstU16<{ u16::MAX }>; type MaxElectingVoters = MaxElectingVoters; @@ -1899,6 +1905,7 @@ impl_runtime_apis! { #[cfg(test)] mod tests { use super::*; + use frame_election_provider_support::NposSolution; use frame_system::offchain::CreateSignedTransaction; use sp_runtime::UpperOf; @@ -1915,7 +1922,8 @@ mod tests { #[test] fn perbill_as_onchain_accuracy() { - type OnChainAccuracy = ::Accuracy; + type OnChainAccuracy = + <::Solution as NposSolution>::Accuracy; let maximum_chain_accuracy: Vec> = (0..MaxNominations::get()) .map(|_| >::from(OnChainAccuracy::one().deconstruct())) .collect(); diff --git a/frame/babe/src/mock.rs b/frame/babe/src/mock.rs index e74288577c9ef..37d8e9e37a5f4 100644 --- a/frame/babe/src/mock.rs +++ b/frame/babe/src/mock.rs @@ -19,7 +19,7 @@ use crate::{self as pallet_babe, Config, CurrentSlot}; use codec::Encode; -use frame_election_provider_support::onchain; +use frame_election_provider_support::{onchain, SequentialPhragmen}; use frame_support::{ parameter_types, traits::{ConstU128, ConstU32, ConstU64, GenesisBuild, KeyOwnerProofSystem, OnInitialize}, @@ -172,8 +172,10 @@ parameter_types! { pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(16); } -impl onchain::Config for Test { - type Accuracy = Perbill; +pub struct OnChainSeqPhragmen; +impl onchain::ExecutionConfig for OnChainSeqPhragmen { + type System = Test; + type Solver = SequentialPhragmen; type DataProvider = Staking; } @@ -195,7 +197,7 @@ impl pallet_staking::Config for Test { type MaxNominatorRewardedPerValidator = ConstU32<64>; type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type NextNewSession = Session; - type ElectionProvider = onchain::OnChainSequentialPhragmen; + type ElectionProvider = onchain::UnboundedExecution; type GenesisElectionProvider = Self::ElectionProvider; type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index ddc06ce0aecfd..e67a5cab8d643 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -242,7 +242,7 @@ use frame_support::{ use frame_system::{ensure_none, offchain::SendTransactionTypes}; use scale_info::TypeInfo; use sp_arithmetic::{ - traits::{CheckedAdd, Saturating, Zero}, + traits::{Bounded, CheckedAdd, Saturating, Zero}, UpperOf, }; use sp_npos_elections::{ @@ -323,10 +323,7 @@ impl ElectionProvider for NoFallback { } impl InstantElectionProvider for NoFallback { - fn instant_elect( - _: Option, - _: Option, - ) -> Result, Self::Error> { + fn elect_with_bounds(_: usize, _: usize) -> Result, Self::Error> { Err("NoFallback.") } } @@ -683,7 +680,7 @@ pub mod pallet { + TypeInfo; /// Configuration for the fallback. - type Fallback: ElectionProvider< + type Fallback: InstantElectionProvider< AccountId = Self::AccountId, BlockNumber = Self::BlockNumber, DataProvider = Self::DataProvider, @@ -692,7 +689,7 @@ pub mod pallet { /// Configuration of the governance-only fallback. /// /// As a side-note, it is recommend for test-nets to use `type ElectionProvider = - /// OnChainSeqPhragmen<_>` if the test-net is not expected to have thousands of nominators. + /// BoundedExecution<_>` if the test-net is not expected to have thousands of nominators. type GovernanceFallback: InstantElectionProvider< AccountId = Self::AccountId, BlockNumber = Self::BlockNumber, @@ -1040,13 +1037,14 @@ pub mod pallet { let maybe_max_voters = maybe_max_voters.map(|x| x as usize); let maybe_max_targets = maybe_max_targets.map(|x| x as usize); - let supports = - T::GovernanceFallback::instant_elect(maybe_max_voters, maybe_max_targets).map_err( - |e| { - log!(error, "GovernanceFallback failed: {:?}", e); - Error::::FallbackFailed - }, - )?; + let supports = T::GovernanceFallback::elect_with_bounds( + maybe_max_voters.unwrap_or(Bounded::max_value()), + maybe_max_targets.unwrap_or(Bounded::max_value()), + ) + .map_err(|e| { + log!(error, "GovernanceFallback failed: {:?}", e); + Error::::FallbackFailed + })?; let solution = ReadySolution { supports, diff --git a/frame/election-provider-multi-phase/src/mock.rs b/frame/election-provider-multi-phase/src/mock.rs index e2384d2f15761..1b3c4d9306246 100644 --- a/frame/election-provider-multi-phase/src/mock.rs +++ b/frame/election-provider-multi-phase/src/mock.rs @@ -272,11 +272,13 @@ parameter_types! { pub static MaxElectableTargets: TargetIndex = TargetIndex::max_value(); pub static EpochLength: u64 = 30; - pub static OnChianFallback: bool = true; + pub static OnChainFallback: bool = true; } -impl onchain::Config for Runtime { - type Accuracy = sp_runtime::Perbill; +pub struct OnChainSeqPhragmen; +impl onchain::ExecutionConfig for OnChainSeqPhragmen { + type System = Runtime; + type Solver = SequentialPhragmen, Balancing>; type DataProvider = StakingMock; } @@ -288,11 +290,23 @@ impl ElectionProvider for MockFallback { type DataProvider = StakingMock; fn elect() -> Result, Self::Error> { - if OnChianFallback::get() { - onchain::OnChainSequentialPhragmen::::elect() - .map_err(|_| "OnChainSequentialPhragmen failed") + Self::elect_with_bounds(Bounded::max_value(), Bounded::max_value()) + } +} + +impl InstantElectionProvider for MockFallback { + fn elect_with_bounds( + max_voters: usize, + max_targets: usize, + ) -> Result, Self::Error> { + if OnChainFallback::get() { + onchain::UnboundedExecution::::elect_with_bounds( + max_voters, + max_targets, + ) + .map_err(|_| "UnboundedExecution failed") } else { - super::NoFallback::::elect() + super::NoFallback::::elect_with_bounds(max_voters, max_targets) } } } @@ -532,7 +546,7 @@ impl ExtBuilder { self } pub fn onchain_fallback(self, onchain: bool) -> Self { - ::set(onchain); + ::set(onchain); self } pub fn miner_weight(self, weight: Weight) -> Self { diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index 2cc27472e8846..d79b5289dffe3 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -168,6 +168,7 @@ pub mod onchain; pub mod traits; +#[cfg(feature = "std")] use codec::{Decode, Encode}; use frame_support::{traits::Get, BoundedVec, RuntimeDebug}; use sp_runtime::traits::Bounded; @@ -368,9 +369,10 @@ pub trait ElectionProvider { BlockNumber = Self::BlockNumber, >; - /// Elect a new set of winners. + /// Elect a new set of winners, without specifying any bounds on the amount of data fetched from + /// [`Self::DataProvider`]. An implementation could nonetheless impose its own custom limits. /// - /// The result is returned in a target major format, namely as vector of supports. + /// The result is returned in a target major format, namely as *vector of supports*. /// /// This should be implemented as a self-weighing function. The implementor should register its /// appropriate weight at the end of execution with the system pallet directly. @@ -385,11 +387,17 @@ pub trait ElectionProvider { /// Consequently, allows for control over the amount of data that is being fetched from the /// [`ElectionProvider::DataProvider`]. pub trait InstantElectionProvider: ElectionProvider { - /// Elect a new set of winners, instantly, with the given given limits set on the + /// Elect a new set of winners, but unlike [`ElectionProvider::elect`] which cannot enforce + /// bounds, this trait method can enforce bounds on the amount of data provided by the /// `DataProvider`. - fn instant_elect( - maybe_max_voters: Option, - maybe_max_targets: Option, + /// + /// An implementing type, if itself bounded, should choose the minimum of the two bounds to + /// choose the final value of `max_voters` and `max_targets`. In other words, an implementation + /// should guarantee that `max_voter` and `max_targets` provided to this method are absolutely + /// respected. + fn elect_with_bounds( + max_voters: usize, + max_targets: usize, ) -> Result, Self::Error>; } diff --git a/frame/election-provider-support/src/onchain.rs b/frame/election-provider-support/src/onchain.rs index 7d845c2dc5ab3..57fd931a467d1 100644 --- a/frame/election-provider-support/src/onchain.rs +++ b/frame/election-provider-support/src/onchain.rs @@ -15,9 +15,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! An implementation of [`ElectionProvider`] that does an on-chain sequential phragmen. +//! An implementation of [`ElectionProvider`] that uses an `NposSolver` to do the election. -use crate::{ElectionDataProvider, ElectionProvider, InstantElectionProvider}; +use crate::{ElectionDataProvider, ElectionProvider, InstantElectionProvider, NposSolver}; use frame_support::{traits::Get, weights::DispatchClass}; use sp_npos_elections::*; use sp_std::{collections::btree_map::BTreeMap, marker::PhantomData, prelude::*}; @@ -41,92 +41,141 @@ impl From for Error { /// /// This will accept voting data on the fly and produce the results immediately. /// -/// ### Warning -/// -/// This can be very expensive to run frequently on-chain. Use with care. Moreover, this -/// implementation ignores the additional data of the election data provider and gives no insight on -/// how much weight was consumed. -/// /// Finally, the [`ElectionProvider`] implementation of this type does not impose any limits on the /// number of voters and targets that are fetched. This could potentially make this unsuitable for -/// execution onchain. On the other hand, the [`InstantElectionProvider`] implementation does limit -/// these inputs. +/// execution onchain. One could, however, impose bounds on it by using for example +/// `BoundedExecution` which will the bounds provided in the configuration. +/// +/// On the other hand, the [`InstantElectionProvider`] implementation does limit these inputs, +/// either via using `BoundedExecution` and imposing the bounds there, or dynamically via calling +/// `elect_with_bounds` providing these bounds. If you use `elect_with_bounds` along with +/// `InstantElectionProvider`, the bound that would be used is the minimum of the 2 bounds. /// /// It is advisable to use the former ([`ElectionProvider::elect`]) only at genesis, or for testing, -/// the latter [`InstantElectionProvider::instant_elect`] for onchain operations, with thoughtful -/// bounds. -pub struct OnChainSequentialPhragmen(PhantomData); +/// the latter [`InstantElectionProvider::elect_with_bounds`] for onchain operations, with +/// thoughtful bounds. +/// +/// Please use `BoundedExecution` at all times except at genesis or for testing, with thoughtful +/// bounds in order to bound the potential execution time. Limit the use `UnboundedExecution` at +/// genesis or for testing, as it does not bound the inputs. However, this can be used with +/// `[InstantElectionProvider::elect_with_bounds`] that dynamically imposes limits. +pub struct BoundedExecution(PhantomData); -/// Configuration trait of [`OnChainSequentialPhragmen`]. +/// An unbounded variant of [`BoundedExecution`]. /// -/// Note that this is similar to a pallet traits, but [`OnChainSequentialPhragmen`] is not a pallet. +/// ### Warning /// -/// WARNING: the user of this pallet must ensure that the `Accuracy` type will work nicely with the -/// normalization operation done inside `seq_phragmen`. See -/// [`sp_npos_elections::Assignment::try_normalize`] for more info. -pub trait Config: frame_system::Config { - /// The accuracy used to compute the election: - type Accuracy: PerThing128; +/// This can be very expensive to run frequently on-chain. Use with care. Moreover, this +/// implementation ignores the additional data of the election data provider and gives no insight on +/// how much weight was consumed. +pub struct UnboundedExecution(PhantomData); + +/// Configuration trait of [`UnboundedExecution`]. +pub trait ExecutionConfig { + /// Something that implements the system pallet configs. This is to enable to register extra + /// weight. + type System: frame_system::Config; + /// `NposSolver` that should be used, an example would be `PhragMMS`. + type Solver: NposSolver< + AccountId = ::AccountId, + Error = sp_npos_elections::Error, + >; /// Something that provides the data for election. type DataProvider: ElectionDataProvider< - AccountId = Self::AccountId, - BlockNumber = Self::BlockNumber, + AccountId = ::AccountId, + BlockNumber = ::BlockNumber, >; } -impl OnChainSequentialPhragmen { - fn elect_with( - maybe_max_voters: Option, - maybe_max_targets: Option, - ) -> Result, Error> { - let voters = ::DataProvider::electing_voters(maybe_max_voters) - .map_err(Error::DataProvider)?; - let targets = - ::DataProvider::electable_targets(maybe_max_targets) - .map_err(Error::DataProvider)?; - let desired_targets = ::DataProvider::desired_targets() - .map_err(Error::DataProvider)?; - - let stake_map: BTreeMap = voters - .iter() - .map(|(validator, vote_weight, _)| (validator.clone(), *vote_weight)) - .collect(); - - let stake_of = - |w: &T::AccountId| -> VoteWeight { stake_map.get(w).cloned().unwrap_or_default() }; - - let ElectionResult::<_, T::Accuracy> { winners: _, assignments } = - seq_phragmen(desired_targets as usize, targets, voters, None).map_err(Error::from)?; - - let staked = assignment_ratio_to_staked_normalized(assignments, &stake_of)?; - - let weight = T::BlockWeights::get().max_block; - frame_system::Pallet::::register_extra_weight_unchecked( - weight, - DispatchClass::Mandatory, - ); - - Ok(to_supports(&staked)) +/// Configuration trait of [`BoundedExecution`]. +pub trait BoundedExecutionConfig: ExecutionConfig { + /// Bounds the number of voters. + type VotersBound: Get; + /// Bounds the number of targets. + type TargetsBound: Get; +} + +fn elect_with( + maybe_max_voters: Option, + maybe_max_targets: Option, +) -> Result::AccountId>, Error> { + let voters = T::DataProvider::electing_voters(maybe_max_voters).map_err(Error::DataProvider)?; + let targets = + T::DataProvider::electable_targets(maybe_max_targets).map_err(Error::DataProvider)?; + let desired_targets = T::DataProvider::desired_targets().map_err(Error::DataProvider)?; + + let stake_map: BTreeMap<_, _> = voters + .iter() + .map(|(validator, vote_weight, _)| (validator.clone(), *vote_weight)) + .collect(); + + let stake_of = |w: &::AccountId| -> VoteWeight { + stake_map.get(w).cloned().unwrap_or_default() + }; + + let ElectionResult { winners: _, assignments } = + T::Solver::solve(desired_targets as usize, targets, voters).map_err(Error::from)?; + + let staked = assignment_ratio_to_staked_normalized(assignments, &stake_of)?; + + let weight = ::BlockWeights::get().max_block; + frame_system::Pallet::::register_extra_weight_unchecked( + weight, + DispatchClass::Mandatory, + ); + + Ok(to_supports(&staked)) +} + +impl ElectionProvider for UnboundedExecution { + type AccountId = ::AccountId; + type BlockNumber = ::BlockNumber; + type Error = Error; + type DataProvider = T::DataProvider; + + fn elect() -> Result::AccountId>, Self::Error> { + // This should not be called if not in `std` mode (and therefore neither in genesis nor in + // testing) + if cfg!(not(feature = "std")) { + frame_support::log::error!( + "Please use `InstantElectionProvider` instead to provide bounds on election if not in \ + genesis or testing mode" + ); + } + + elect_with::(None, None) } } -impl ElectionProvider for OnChainSequentialPhragmen { - type AccountId = T::AccountId; - type BlockNumber = T::BlockNumber; +impl InstantElectionProvider for UnboundedExecution { + fn elect_with_bounds( + max_voters: usize, + max_targets: usize, + ) -> Result, Self::Error> { + elect_with::(Some(max_voters), Some(max_targets)) + } +} + +impl ElectionProvider for BoundedExecution { + type AccountId = ::AccountId; + type BlockNumber = ::BlockNumber; type Error = Error; type DataProvider = T::DataProvider; - fn elect() -> Result, Self::Error> { - Self::elect_with(None, None) + fn elect() -> Result::AccountId>, Self::Error> { + elect_with::(Some(T::VotersBound::get() as usize), Some(T::TargetsBound::get() as usize)) } } -impl InstantElectionProvider for OnChainSequentialPhragmen { - fn instant_elect( - maybe_max_voters: Option, - maybe_max_targets: Option, +impl InstantElectionProvider for BoundedExecution { + fn elect_with_bounds( + max_voters: usize, + max_targets: usize, ) -> Result, Self::Error> { - Self::elect_with(maybe_max_voters, maybe_max_targets) + elect_with::( + Some(max_voters.min(T::VotersBound::get() as usize)), + Some(max_targets.min(T::TargetsBound::get() as usize)), + ) } } @@ -135,7 +184,6 @@ mod tests { use super::*; use sp_npos_elections::Support; use sp_runtime::Perbill; - type AccountId = u64; type BlockNumber = u64; @@ -180,12 +228,13 @@ mod tests { type MaxConsumers = frame_support::traits::ConstU32<16>; } - impl Config for Runtime { - type Accuracy = Perbill; + impl ExecutionConfig for Runtime { + type System = Self; + type Solver = crate::SequentialPhragmen; type DataProvider = mock_data_provider::DataProvider; } - type OnChainPhragmen = OnChainSequentialPhragmen; + type OnChainPhragmen = UnboundedExecution; mod mock_data_provider { use frame_support::{bounded_vec, traits::ConstU32}; diff --git a/frame/grandpa/src/mock.rs b/frame/grandpa/src/mock.rs index 6490a2b6992bf..0296cd2e28d88 100644 --- a/frame/grandpa/src/mock.rs +++ b/frame/grandpa/src/mock.rs @@ -22,7 +22,7 @@ use crate::{self as pallet_grandpa, AuthorityId, AuthorityList, Config, ConsensusLog}; use ::grandpa as finality_grandpa; use codec::Encode; -use frame_election_provider_support::onchain; +use frame_election_provider_support::{onchain, SequentialPhragmen}; use frame_support::{ parameter_types, traits::{ @@ -180,8 +180,10 @@ parameter_types! { pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(17); } -impl onchain::Config for Test { - type Accuracy = Perbill; +pub struct OnChainSeqPhragmen; +impl onchain::ExecutionConfig for OnChainSeqPhragmen { + type System = Test; + type Solver = SequentialPhragmen<::AccountId, Perbill>; type DataProvider = Staking; } @@ -203,7 +205,7 @@ impl pallet_staking::Config for Test { type MaxNominatorRewardedPerValidator = ConstU32<64>; type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type NextNewSession = Session; - type ElectionProvider = onchain::OnChainSequentialPhragmen; + type ElectionProvider = onchain::UnboundedExecution; type GenesisElectionProvider = Self::ElectionProvider; type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; diff --git a/frame/merkle-mountain-range/src/mmr/storage.rs b/frame/merkle-mountain-range/src/mmr/storage.rs index a48f60183d679..535057ca80da7 100644 --- a/frame/merkle-mountain-range/src/mmr/storage.rs +++ b/frame/merkle-mountain-range/src/mmr/storage.rs @@ -18,7 +18,6 @@ //! A MMR storage implementations. use codec::Encode; -use frame_support::log; use mmr_lib::helper; use sp_io::offchain_index; use sp_std::iter::Peekable; @@ -93,7 +92,7 @@ where } sp_std::if_std! { - log::trace!("elems: {:?}", elems.iter().map(|elem| elem.hash()).collect::>()); + frame_support::log::trace!("elems: {:?}", elems.iter().map(|elem| elem.hash()).collect::>()); } let leaves = NumberOfLeaves::::get(); @@ -152,8 +151,8 @@ fn peaks_to_prune_and_store( let peaks_before = if old_size == 0 { vec![] } else { helper::get_peaks(old_size) }; let peaks_after = helper::get_peaks(new_size); sp_std::if_std! { - log::trace!("peaks_before: {:?}", peaks_before); - log::trace!("peaks_after: {:?}", peaks_after); + frame_support::log::trace!("peaks_before: {:?}", peaks_before); + frame_support::log::trace!("peaks_after: {:?}", peaks_after); } let mut peaks_before = peaks_before.into_iter().peekable(); let mut peaks_after = peaks_after.into_iter().peekable(); diff --git a/frame/offences/benchmarking/src/mock.rs b/frame/offences/benchmarking/src/mock.rs index 4359b7745ddd6..1a4414de0b0b0 100644 --- a/frame/offences/benchmarking/src/mock.rs +++ b/frame/offences/benchmarking/src/mock.rs @@ -20,7 +20,7 @@ #![cfg(test)] use super::*; -use frame_election_provider_support::onchain; +use frame_election_provider_support::{onchain, SequentialPhragmen}; use frame_support::{ parameter_types, traits::{ConstU32, ConstU64}, @@ -150,8 +150,10 @@ parameter_types! { pub type Extrinsic = sp_runtime::testing::TestXt; -impl onchain::Config for Test { - type Accuracy = Perbill; +pub struct OnChainSeqPhragmen; +impl onchain::ExecutionConfig for OnChainSeqPhragmen { + type System = Test; + type Solver = SequentialPhragmen; type DataProvider = Staking; } @@ -173,7 +175,7 @@ impl pallet_staking::Config for Test { type NextNewSession = Session; type MaxNominatorRewardedPerValidator = ConstU32<64>; type OffendingValidatorsThreshold = (); - type ElectionProvider = onchain::OnChainSequentialPhragmen; + type ElectionProvider = onchain::UnboundedExecution; type GenesisElectionProvider = Self::ElectionProvider; type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; diff --git a/frame/session/benchmarking/src/mock.rs b/frame/session/benchmarking/src/mock.rs index 5ebc75245630c..24b42b3e9f4b5 100644 --- a/frame/session/benchmarking/src/mock.rs +++ b/frame/session/benchmarking/src/mock.rs @@ -19,7 +19,7 @@ #![cfg(test)] -use frame_election_provider_support::onchain; +use frame_election_provider_support::{onchain, SequentialPhragmen}; use frame_support::{ parameter_types, traits::{ConstU32, ConstU64}, @@ -156,8 +156,10 @@ where type Extrinsic = Extrinsic; } -impl onchain::Config for Test { - type Accuracy = sp_runtime::Perbill; +pub struct OnChainSeqPhragmen; +impl onchain::ExecutionConfig for OnChainSeqPhragmen { + type System = Test; + type Solver = SequentialPhragmen; type DataProvider = Staking; } @@ -179,7 +181,7 @@ impl pallet_staking::Config for Test { type NextNewSession = Session; type MaxNominatorRewardedPerValidator = ConstU32<64>; type OffendingValidatorsThreshold = (); - type ElectionProvider = onchain::OnChainSequentialPhragmen; + type ElectionProvider = onchain::UnboundedExecution; type GenesisElectionProvider = Self::ElectionProvider; type MaxUnlockingChunks = ConstU32<32>; type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index bb71232c34673..bb90aded852ee 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -18,7 +18,9 @@ //! Test utilities use crate::{self as pallet_staking, *}; -use frame_election_provider_support::{onchain, SortedListProvider, VoteWeight}; +use frame_election_provider_support::{ + onchain, SequentialPhragmen, SortedListProvider, VoteWeight, +}; use frame_support::{ assert_ok, parameter_types, traits::{ @@ -245,8 +247,10 @@ impl pallet_bags_list::Config for Test { type Score = VoteWeight; } -impl onchain::Config for Test { - type Accuracy = Perbill; +pub struct OnChainSeqPhragmen; +impl onchain::ExecutionConfig for OnChainSeqPhragmen { + type System = Test; + type Solver = SequentialPhragmen; type DataProvider = Staking; } @@ -268,7 +272,7 @@ impl crate::pallet::pallet::Config for Test { type NextNewSession = Session; type MaxNominatorRewardedPerValidator = ConstU32<64>; type OffendingValidatorsThreshold = OffendingValidatorsThreshold; - type ElectionProvider = onchain::OnChainSequentialPhragmen; + type ElectionProvider = onchain::UnboundedExecution; type GenesisElectionProvider = Self::ElectionProvider; // NOTE: consider a macro and use `UseNominatorsAndValidatorsMap` as well. type VoterList = BagsList; From 12d72aa644e64ebb0f692672c8eeecdb251933c3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 26 Mar 2022 08:19:07 +0100 Subject: [PATCH 064/484] Bump handlebars from 4.1.6 to 4.2.2 (#11073) Bumps [handlebars](https://github.com/sunng87/handlebars-rust) from 4.1.6 to 4.2.2. - [Release notes](https://github.com/sunng87/handlebars-rust/releases) - [Changelog](https://github.com/sunng87/handlebars-rust/blob/master/CHANGELOG.md) - [Commits](https://github.com/sunng87/handlebars-rust/compare/v4.1.6...v4.2.2) --- updated-dependencies: - dependency-name: handlebars dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- utils/frame/benchmarking-cli/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 81a53ed858859..2a761b9a761ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2779,9 +2779,9 @@ checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3" [[package]] name = "handlebars" -version = "4.1.6" +version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167fa173496c9eadd8749cca6f8339ac88e248f3ad2442791d0b743318a94fc0" +checksum = "99d6a30320f094710245150395bc763ad23128d6a1ebbad7594dc4164b62c56b" dependencies = [ "log 0.4.14", "pest", diff --git a/utils/frame/benchmarking-cli/Cargo.toml b/utils/frame/benchmarking-cli/Cargo.toml index 11bba2b37957f..5cb81232085a4 100644 --- a/utils/frame/benchmarking-cli/Cargo.toml +++ b/utils/frame/benchmarking-cli/Cargo.toml @@ -40,7 +40,7 @@ clap = { version = "3.1.6", features = ["derive"] } chrono = "0.4" serde = "1.0.136" serde_json = "1.0.79" -handlebars = "4.1.6" +handlebars = "4.2.2" Inflector = "0.11.4" linked-hash-map = "0.5.4" log = "0.4.8" From a020e50273c198f5d82a49f3d75f9031da5ddcc5 Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Sun, 27 Mar 2022 02:43:47 -0400 Subject: [PATCH 065/484] remove misleading sentence (#11125) --- frame/support/src/storage/mod.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/frame/support/src/storage/mod.rs b/frame/support/src/storage/mod.rs index 226682eecf10b..4a0eebf567993 100644 --- a/frame/support/src/storage/mod.rs +++ b/frame/support/src/storage/mod.rs @@ -517,9 +517,6 @@ pub trait IterableStorageNMap: StorageN /// An implementation of a map with a two keys. /// -/// It provides an important ability to efficiently remove all entries -/// that have a common first key. -/// /// Details on implementation can be found at [`generator::StorageDoubleMap`]. pub trait StorageDoubleMap { /// The type that get/take returns. From d2911def8182a66515438561a9876468ce5c9c0d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 27 Mar 2022 21:19:55 +0200 Subject: [PATCH 066/484] Bump zeroize from 1.4.3 to 1.5.4 (#11123) Bumps [zeroize](https://github.com/RustCrypto/utils) from 1.4.3 to 1.5.4. - [Release notes](https://github.com/RustCrypto/utils/releases) - [Commits](https://github.com/RustCrypto/utils/compare/zeroize-v1.4.3...zeroize-v1.5.4) --- updated-dependencies: - dependency-name: zeroize dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- client/network/Cargo.toml | 2 +- primitives/core/Cargo.toml | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2a761b9a761ed..ac30433c5ea28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12474,18 +12474,18 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.4.3" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619" +checksum = "7eb5728b8afd3f280a869ce1d4c554ffaed35f45c231fc41bfbd0381bef50317" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.2.0" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdff2024a851a322b08f179173ae2ba620445aef1e838f0c196820eade4ae0c7" +checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" dependencies = [ "proc-macro2", "quote", diff --git a/client/network/Cargo.toml b/client/network/Cargo.toml index 671271fcb3b77..d6bc90016ab82 100644 --- a/client/network/Cargo.toml +++ b/client/network/Cargo.toml @@ -61,7 +61,7 @@ unsigned-varint = { version = "0.6.0", features = [ "asynchronous_codec", ] } void = "1.0.2" -zeroize = "1.4.3" +zeroize = "1.5.4" libp2p = "0.40.0" [dev-dependencies] diff --git a/primitives/core/Cargo.toml b/primitives/core/Cargo.toml index 55d00362033d0..413a11c9dc0a4 100644 --- a/primitives/core/Cargo.toml +++ b/primitives/core/Cargo.toml @@ -32,7 +32,7 @@ substrate-bip39 = { version = "0.4.4", optional = true } tiny-bip39 = { version = "0.8.2", optional = true } regex = { version = "1.5.4", optional = true } num-traits = { version = "0.2.8", default-features = false } -zeroize = { version = "1.4.3", default-features = false } +zeroize = { version = "1.5.4", default-features = false } secrecy = { version = "0.8.0", default-features = false } lazy_static = { version = "1.4.0", default-features = false, optional = true } parking_lot = { version = "0.12.0", optional = true } From d1c971fbd3542597974dfdaac5a24c0383caf918 Mon Sep 17 00:00:00 2001 From: Alexander Popiak Date: Sun, 27 Mar 2022 23:35:52 +0200 Subject: [PATCH 067/484] add notes and warnings to ProvideInherent docs (#9730) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add notes and warnings to ProvideInherent docs * rephrase ProvideInherent doc comments * more comment refinement * remove multiple inherents note * remove repetition Co-authored-by: Bastian Köcher * replace inherent example in docs * add note about who checks is_inherent_required * Apply suggestions from code review Co-authored-by: Bastian Köcher Co-authored-by: Bastian Köcher --- frame/support/src/inherent.rs | 35 ++++++++++++++++++++++++--------- primitives/inherents/src/lib.rs | 12 +++++------ 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/frame/support/src/inherent.rs b/frame/support/src/inherent.rs index 59d55b1df3f3c..0aa6b9d3f75a9 100644 --- a/frame/support/src/inherent.rs +++ b/frame/support/src/inherent.rs @@ -24,9 +24,12 @@ pub use sp_inherents::{ CheckInherentsResult, InherentData, InherentIdentifier, IsFatalError, MakeFatalError, }; -/// A pallet that provides or verifies an inherent extrinsic. +/// A pallet that provides or verifies an inherent extrinsic will implement this trait. /// -/// The pallet may provide the inherent, verify an inherent, or both provide and verify. +/// The pallet may provide an inherent, verify an inherent, or both provide and verify. +/// +/// Briefly, inherent extrinsics ("inherents") are extrinsics that are added to a block by the block +/// producer. See [`sp_inherents`] for more documentation on inherents. pub trait ProvideInherent { /// The call type of the pallet. type Call; @@ -36,6 +39,12 @@ pub trait ProvideInherent { const INHERENT_IDENTIFIER: self::InherentIdentifier; /// Create an inherent out of the given `InherentData`. + /// + /// NOTE: All checks necessary to ensure that the inherent is correct and that can be done in + /// the runtime should happen in the returned `Call`. + /// E.g. if this provides the timestamp, the call will check that the given timestamp is + /// increasing the old timestamp by more than a minimum and it will also check that the + /// timestamp hasn't already been set in the current block. fn create_inherent(data: &InherentData) -> Option; /// Determines whether this inherent is required in this block. @@ -44,15 +53,17 @@ pub trait ProvideInherent { /// implementation returns this. /// /// - `Ok(Some(e))` indicates that this inherent is required in this block. `construct_runtime!` - /// will call this function from in its implementation of `fn check_extrinsics`. + /// will call this function in its implementation of `fn check_extrinsics`. /// If the inherent is not present, it will return `e`. /// /// - `Err(_)` indicates that this function failed and further operations should be aborted. /// - /// NOTE: If inherent is required then the runtime asserts that the block contains at least + /// NOTE: If the inherent is required then the runtime asserts that the block contains at least /// one inherent for which: /// * type is [`Self::Call`], /// * [`Self::is_inherent`] returns true. + /// + /// NOTE: This is currently only checked by block producers, not all full nodes. fn is_inherent_required(_: &InherentData) -> Result, Self::Error> { Ok(None) } @@ -64,21 +75,27 @@ pub trait ProvideInherent { /// included in the block by its author. Whereas the second parameter represents the inherent /// data that the verifying node calculates. /// - /// NOTE: A block can contains multiple inherent. + /// This is intended to allow for checks that cannot be done within the runtime such as, e.g., + /// the timestamp. + /// + /// # Warning + /// + /// This check is not guaranteed to be run by all full nodes and cannot be relied upon for + /// ensuring that the block is correct. fn check_inherent(_: &Self::Call, _: &InherentData) -> Result<(), Self::Error> { Ok(()) } /// Return whether the call is an inherent call. /// - /// NOTE: Signed extrinsics are not inherent, but signed extrinsic with the given call variant - /// can be dispatched. + /// NOTE: Signed extrinsics are not inherents, but a signed extrinsic with the given call + /// variant can be dispatched. /// /// # Warning /// - /// In FRAME, inherent are enforced to be before other extrinsics, for this reason, + /// In FRAME, inherents are enforced to be executed before other extrinsics. For this reason, /// pallets with unsigned transactions **must ensure** that no unsigned transaction call /// is an inherent call, when implementing `ValidateUnsigned::validate_unsigned`. - /// Otherwise block producer can produce invalid blocks by including them after non inherent. + /// Otherwise block producers can produce invalid blocks by including them after non inherents. fn is_inherent(call: &Self::Call) -> bool; } diff --git a/primitives/inherents/src/lib.rs b/primitives/inherents/src/lib.rs index cd04a1bc3b3dd..a3ef963c47b39 100644 --- a/primitives/inherents/src/lib.rs +++ b/primitives/inherents/src/lib.rs @@ -15,14 +15,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Substrate inherent extrinsics +//! Substrate Inherent Extrinsics //! //! Inherent extrinsics are extrinsics that are inherently added to each block. However, it is up to -//! runtime implementation to require an inherent for each block or to make it optional. Inherents -//! are mainly used to pass data from the block producer to the runtime. So, inherents require some -//! part that is running on the client side and some part that is running on the runtime side. Any -//! data that is required by an inherent is passed as [`InherentData`] from the client to the -//! runtime when the inherents are constructed. +//! the runtime implementation to require an inherent for each block or to make it optional. +//! Inherents are mainly used to pass data from the block producer to the runtime. So, inherents +//! require some part that is running on the client side and some part that is running on the +//! runtime side. Any data that is required by an inherent is passed as [`InherentData`] from the +//! client to the runtime when the inherents are constructed. //! //! The process of constructing and applying inherents is the following: //! From 2720ef54d90ea56489a7a4211c72c24083f780f4 Mon Sep 17 00:00:00 2001 From: Koute Date: Tue, 29 Mar 2022 06:42:01 +0900 Subject: [PATCH 068/484] Bump `tokio` to 1.17.0 (#10894) * Bump `tokio` to 0.17.0 * Revert version changes to scale-info Co-authored-by: Keith Yeung --- Cargo.lock | 23 +++++++++++---------- bin/node/cli/Cargo.toml | 2 +- client/cli/Cargo.toml | 2 +- client/consensus/manual-seal/Cargo.toml | 2 +- client/finality-grandpa/Cargo.toml | 2 +- client/offchain/Cargo.toml | 2 +- client/rpc-servers/Cargo.toml | 2 +- client/service/Cargo.toml | 2 +- client/service/test/Cargo.toml | 2 +- test-utils/Cargo.toml | 2 +- test-utils/test-crate/Cargo.toml | 2 +- utils/frame/remote-externalities/Cargo.toml | 2 +- utils/frame/rpc/support/Cargo.toml | 2 +- utils/prometheus/Cargo.toml | 4 ++-- 14 files changed, 26 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ac30433c5ea28..873da8abaca39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3010,7 +3010,7 @@ dependencies = [ "httpdate", "itoa 0.4.8", "pin-project-lite 0.2.6", - "socket2 0.4.0", + "socket2 0.4.4", "tokio", "tower-service", "tracing", @@ -3914,7 +3914,7 @@ dependencies = [ "log 0.4.14", "rand 0.8.4", "smallvec 1.8.0", - "socket2 0.4.0", + "socket2 0.4.4", "void", ] @@ -4122,7 +4122,7 @@ dependencies = [ "libc", "libp2p-core", "log 0.4.14", - "socket2 0.4.0", + "socket2 0.4.4", ] [[package]] @@ -4559,9 +4559,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.7.13" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c2bdb6314ec10835cd3293dd268473a835c02b7b352e788be788b3c6ca6bb16" +checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2" dependencies = [ "libc", "log 0.4.14", @@ -9623,9 +9623,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.4.0" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e3dfc207c526015c632472a77be09cf1b6e46866581aecae5cc38fb4235dea2" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" dependencies = [ "libc", "winapi 0.3.9", @@ -11024,19 +11024,20 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.15.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbbf1c778ec206785635ce8ad57fe52b3009ae9e0c9f574a728f3049d3e55838" +checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" dependencies = [ "bytes 1.1.0", "libc", "memchr", - "mio 0.7.13", + "mio 0.8.0", "num_cpus", "once_cell", - "parking_lot 0.11.2", + "parking_lot 0.12.0", "pin-project-lite 0.2.6", "signal-hook-registry", + "socket2 0.4.4", "tokio-macros", "winapi 0.3.9", ] diff --git a/bin/node/cli/Cargo.toml b/bin/node/cli/Cargo.toml index 24e069d21f694..0e8cdfd7c3fe5 100644 --- a/bin/node/cli/Cargo.toml +++ b/bin/node/cli/Cargo.toml @@ -126,7 +126,7 @@ platforms = "2.0" async-std = { version = "1.10.0", features = ["attributes"] } soketto = "0.4.2" criterion = { version = "0.3.5", features = ["async_tokio"] } -tokio = { version = "1.15", features = ["macros", "time", "parking_lot"] } +tokio = { version = "1.17.0", features = ["macros", "time", "parking_lot"] } jsonrpsee-ws-client = "0.4.1" wait-timeout = "0.2" remote-externalities = { path = "../../../utils/frame/remote-externalities" } diff --git a/client/cli/Cargo.toml b/client/cli/Cargo.toml index 665bd6acfaf33..0fbb06e8aa4a9 100644 --- a/client/cli/Cargo.toml +++ b/client/cli/Cargo.toml @@ -28,7 +28,7 @@ serde = "1.0.136" serde_json = "1.0.79" thiserror = "1.0.30" tiny-bip39 = "0.8.2" -tokio = { version = "1.15", features = ["signal", "rt-multi-thread", "parking_lot"] } +tokio = { version = "1.17.0", features = ["signal", "rt-multi-thread", "parking_lot"] } parity-scale-codec = "3.0.0" sc-client-api = { version = "4.0.0-dev", path = "../api" } diff --git a/client/consensus/manual-seal/Cargo.toml b/client/consensus/manual-seal/Cargo.toml index 7aaad863af557..62650d50ce4ac 100644 --- a/client/consensus/manual-seal/Cargo.toml +++ b/client/consensus/manual-seal/Cargo.toml @@ -47,7 +47,7 @@ sp-timestamp = { path = "../../../primitives/timestamp", version = "4.0.0-dev" } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus", version = "0.10.0-dev" } [dev-dependencies] -tokio = { version = "1.15.0", features = ["rt-multi-thread", "macros"] } +tokio = { version = "1.17.0", features = ["rt-multi-thread", "macros"] } sc-basic-authorship = { path = "../../basic-authorship", version = "0.10.0-dev" } substrate-test-runtime-client = { path = "../../../test-utils/runtime/client", version = "2.0.0" } substrate-test-runtime-transaction-pool = { path = "../../../test-utils/runtime/transaction-pool", version = "2.0.0" } diff --git a/client/finality-grandpa/Cargo.toml b/client/finality-grandpa/Cargo.toml index 86cd57fc7c092..b8b41ba703655 100644 --- a/client/finality-grandpa/Cargo.toml +++ b/client/finality-grandpa/Cargo.toml @@ -61,5 +61,5 @@ substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/ru sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } serde = "1.0.136" -tokio = "1.15" +tokio = "1.17.0" tempfile = "3.1.0" diff --git a/client/offchain/Cargo.toml b/client/offchain/Cargo.toml index e542759b48096..3edba1f45704a 100644 --- a/client/offchain/Cargo.toml +++ b/client/offchain/Cargo.toml @@ -43,7 +43,7 @@ sc-transaction-pool-api = { version = "4.0.0-dev", path = "../transaction-pool/a sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } -tokio = "1.15" +tokio = "1.17.0" lazy_static = "1.4.0" [features] diff --git a/client/rpc-servers/Cargo.toml b/client/rpc-servers/Cargo.toml index f0c3fd496aa70..6b2c9f5aa3738 100644 --- a/client/rpc-servers/Cargo.toml +++ b/client/rpc-servers/Cargo.toml @@ -19,7 +19,7 @@ pubsub = { package = "jsonrpc-pubsub", version = "18.0.0" } log = "0.4.8" prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus", version = "0.10.0-dev"} serde_json = "1.0.79" -tokio = { version = "1.15", features = ["parking_lot"] } +tokio = { version = "1.17.0", features = ["parking_lot"] } http = { package = "jsonrpc-http-server", version = "18.0.0" } ipc = { package = "jsonrpc-ipc-server", version = "18.0.0" } ws = { package = "jsonrpc-ws-server", version = "18.0.0" } diff --git a/client/service/Cargo.toml b/client/service/Cargo.toml index 737a29f1db0c0..2cb23fed40f22 100644 --- a/client/service/Cargo.toml +++ b/client/service/Cargo.toml @@ -78,7 +78,7 @@ parity-util-mem = { version = "0.11.0", default-features = false, features = [ "primitive-types", ] } async-trait = "0.1.50" -tokio = { version = "1.15", features = ["time", "rt-multi-thread", "parking_lot"] } +tokio = { version = "1.17.0", features = ["time", "rt-multi-thread", "parking_lot"] } tempfile = "3.1.0" directories = "4.0.1" diff --git a/client/service/test/Cargo.toml b/client/service/test/Cargo.toml index 0b99b9c5aeb9c..fa3c49a2340ad 100644 --- a/client/service/test/Cargo.toml +++ b/client/service/test/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] hex = "0.4" hex-literal = "0.3.4" tempfile = "3.1.0" -tokio = { version = "1.15.0", features = ["time"] } +tokio = { version = "1.17.0", features = ["time"] } log = "0.4.8" fdlimit = "0.2.1" parking_lot = "0.12.0" diff --git a/test-utils/Cargo.toml b/test-utils/Cargo.toml index 756de58032803..9f66fa812bb4b 100644 --- a/test-utils/Cargo.toml +++ b/test-utils/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] futures = "0.3.16" substrate-test-utils-derive = { version = "0.10.0-dev", path = "./derive" } -tokio = { version = "1.10", features = ["macros", "time"] } +tokio = { version = "1.17.0", features = ["macros", "time"] } [dev-dependencies] sc-service = { version = "0.10.0-dev", path = "../client/service" } diff --git a/test-utils/test-crate/Cargo.toml b/test-utils/test-crate/Cargo.toml index 4e07f92e86ea4..f6fea8407eaa7 100644 --- a/test-utils/test-crate/Cargo.toml +++ b/test-utils/test-crate/Cargo.toml @@ -12,6 +12,6 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dev-dependencies] -tokio = { version = "1.15", features = ["macros"] } +tokio = { version = "1.17.0", features = ["macros"] } test-utils = { version = "4.0.0-dev", path = "..", package = "substrate-test-utils" } sc-service = { version = "0.10.0-dev", path = "../../client/service" } diff --git a/utils/frame/remote-externalities/Cargo.toml b/utils/frame/remote-externalities/Cargo.toml index d5042444119dc..fc44c63b8acd4 100644 --- a/utils/frame/remote-externalities/Cargo.toml +++ b/utils/frame/remote-externalities/Cargo.toml @@ -28,7 +28,7 @@ sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } sp-version = { version = "5.0.0", path = "../../../primitives/version" } [dev-dependencies] -tokio = { version = "1.15", features = ["macros", "rt-multi-thread"] } +tokio = { version = "1.17.0", features = ["macros", "rt-multi-thread"] } pallet-elections-phragmen = { path = "../../../frame/elections-phragmen", version = "5.0.0-dev" } frame-support = { path = "../../../frame/support", version = "4.0.0-dev" } diff --git a/utils/frame/rpc/support/Cargo.toml b/utils/frame/rpc/support/Cargo.toml index 0055ef47f8f4e..91ec47e34bd37 100644 --- a/utils/frame/rpc/support/Cargo.toml +++ b/utils/frame/rpc/support/Cargo.toml @@ -26,4 +26,4 @@ sc-rpc-api = { version = "0.10.0-dev", path = "../../../../client/rpc-api" } [dev-dependencies] frame-system = { version = "4.0.0-dev", path = "../../../../frame/system" } scale-info = "2.0.1" -tokio = "1.15" +tokio = "1.17.0" diff --git a/utils/prometheus/Cargo.toml b/utils/prometheus/Cargo.toml index fb998d2809fae..4940712f2f4d9 100644 --- a/utils/prometheus/Cargo.toml +++ b/utils/prometheus/Cargo.toml @@ -17,9 +17,9 @@ log = "0.4.8" prometheus = { version = "0.13.0", default-features = false } futures-util = { version = "0.3.19", default-features = false, features = ["io"] } thiserror = "1.0" -tokio = { version = "1.15", features = ["parking_lot"] } +tokio = { version = "1.17.0", features = ["parking_lot"] } hyper = { version = "0.14.16", default-features = false, features = ["http1", "server", "tcp"] } [dev-dependencies] hyper = { version = "0.14.16", features = ["client"] } -tokio = { version = "1.15", features = ["rt-multi-thread"] } +tokio = { version = "1.17.0", features = ["rt-multi-thread"] } From 2a6e3004380a62b31ed3b3dcd5a6b0dc1055a009 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Mar 2022 23:43:30 +0200 Subject: [PATCH 069/484] Bump libc from 0.2.119 to 0.2.121 (#11127) Bumps [libc](https://github.com/rust-lang/libc) from 0.2.119 to 0.2.121. - [Release notes](https://github.com/rust-lang/libc/releases) - [Commits](https://github.com/rust-lang/libc/compare/0.2.119...0.2.121) --- updated-dependencies: - dependency-name: libc dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- client/executor/wasmtime/Cargo.toml | 2 +- client/tracing/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 873da8abaca39..2fe01c905d7cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3658,9 +3658,9 @@ checksum = "3576a87f2ba00f6f106fdfcd16db1d698d648a26ad8e0573cad8537c3c362d2a" [[package]] name = "libc" -version = "0.2.119" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4" +checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" [[package]] name = "libgit2-sys" diff --git a/client/executor/wasmtime/Cargo.toml b/client/executor/wasmtime/Cargo.toml index 13bf196c4d44e..f0155204f5442 100644 --- a/client/executor/wasmtime/Cargo.toml +++ b/client/executor/wasmtime/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -libc = "0.2.119" +libc = "0.2.121" cfg-if = "1.0" log = "0.4.8" parity-wasm = "0.42.0" diff --git a/client/tracing/Cargo.toml b/client/tracing/Cargo.toml index 3c4401e2d342d..76cdcb4e4b021 100644 --- a/client/tracing/Cargo.toml +++ b/client/tracing/Cargo.toml @@ -17,7 +17,7 @@ ansi_term = "0.12.1" atty = "0.2.13" chrono = "0.4.19" lazy_static = "1.4.0" -libc = "0.2.119" +libc = "0.2.121" log = { version = "0.4.8" } once_cell = "1.8.0" parking_lot = "0.12.0" From 146233c376a009599ac16bd49c0ea9fb8e085756 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 29 Mar 2022 14:07:51 +0200 Subject: [PATCH 070/484] Spellcheck HBS templates and add test (#11119) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Spellcheck HBS templates and fix vars Signed-off-by: Oliver Tale-Yazdi * Add test for benchmark-storage Signed-off-by: Oliver Tale-Yazdi * Fmt templates Signed-off-by: Oliver Tale-Yazdi * fmt Signed-off-by: Oliver Tale-Yazdi * Review fixes Co-authored-by: Bastian Köcher Co-authored-by: Bastian Köcher --- bin/node/cli/tests/benchmark_storage_works.rs | 52 +++++++++++++++++++ .../benchmarking-cli/src/overhead/weights.hbs | 28 +++------- .../benchmarking-cli/src/storage/weights.hbs | 31 ++++++----- 3 files changed, 77 insertions(+), 34 deletions(-) create mode 100644 bin/node/cli/tests/benchmark_storage_works.rs diff --git a/bin/node/cli/tests/benchmark_storage_works.rs b/bin/node/cli/tests/benchmark_storage_works.rs new file mode 100644 index 0000000000000..1628f9a7e97ba --- /dev/null +++ b/bin/node/cli/tests/benchmark_storage_works.rs @@ -0,0 +1,52 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use assert_cmd::cargo::cargo_bin; +use std::{ + path::Path, + process::{Command, ExitStatus}, +}; +use tempfile::tempdir; + +/// Tests that the `benchmark-storage` command works for the dev runtime. +#[test] +fn benchmark_storage_works() { + let tmp_dir = tempdir().expect("could not create a temp dir"); + let base_path = tmp_dir.path(); + + // Benchmarking the storage works and creates the correct weight file. + assert!(benchmark_storage("rocksdb", base_path).success()); + assert!(base_path.join("rocksdb_weights.rs").exists()); + + assert!(benchmark_storage("paritydb", base_path).success()); + assert!(base_path.join("paritydb_weights.rs").exists()); +} + +fn benchmark_storage(db: &str, base_path: &Path) -> ExitStatus { + Command::new(cargo_bin("substrate")) + .args(&["benchmark-storage", "--dev"]) + .arg("--db") + .arg(db) + .arg("--weight-path") + .arg(base_path) + .args(["--state-version", "1"]) + .args(["--warmups", "0"]) + .args(["--add", "100", "--mul", "1.2", "--metric", "p75"]) + .status() + .unwrap() +} diff --git a/utils/frame/benchmarking-cli/src/overhead/weights.hbs b/utils/frame/benchmarking-cli/src/overhead/weights.hbs index 0f6b7f3e9119f..ad33f55a9f363 100644 --- a/utils/frame/benchmarking-cli/src/overhead/weights.hbs +++ b/utils/frame/benchmarking-cli/src/overhead/weights.hbs @@ -37,17 +37,17 @@ parameter_types! { {{#if (eq short_name "block")}} /// Time to execute an empty block. {{else}} - /// Time to execute a NO-OP extrinsic eg. `System::remark`. + /// Time to execute a NO-OP extrinsic, for example `System::remark`. {{/if}} /// Calculated by multiplying the *{{params.weight.weight_metric}}* with `{{params.weight.weight_mul}}` and adding `{{params.weight.weight_add}}`. /// - /// Stats [ns]: + /// Stats [NS]: /// Min, Max: {{underscore stats.min}}, {{underscore stats.max}} /// Average: {{underscore stats.avg}} /// Median: {{underscore stats.median}} - /// StdDev: {{stats.stddev}} + /// Std-Dev: {{stats.stddev}} /// - /// Percentiles [ns]: + /// Percentiles [NS]: /// 99th: {{underscore stats.p99}} /// 95th: {{underscore stats.p95}} /// 75th: {{underscore stats.p75}} @@ -67,26 +67,14 @@ mod test_weights { {{#if (eq short_name "block")}} // At least 100 µs. - assert!( - w >= 100 * constants::WEIGHT_PER_MICROS, - "Weight should be at least 100 µs." - ); + assert!(w >= 100 * constants::WEIGHT_PER_MICROS, "Weight should be at least 100 µs."); // At most 50 ms. - assert!( - w <= 50 * constants::WEIGHT_PER_MILLIS, - "Weight should be at most 50 ms." - ); + assert!(w <= 50 * constants::WEIGHT_PER_MILLIS, "Weight should be at most 50 ms."); {{else}} // At least 10 µs. - assert!( - w >= 10 * constants::WEIGHT_PER_MICROS, - "Weight should be at least 10 µs." - ); + assert!(w >= 10 * constants::WEIGHT_PER_MICROS, "Weight should be at least 10 µs."); // At most 1 ms. - assert!( - w <= constants::WEIGHT_PER_MILLIS, - "Weight should be at most 1 ms." - ); + assert!(w <= constants::WEIGHT_PER_MILLIS, "Weight should be at most 1 ms."); {{/if}} } } diff --git a/utils/frame/benchmarking-cli/src/storage/weights.hbs b/utils/frame/benchmarking-cli/src/storage/weights.hbs index bfb832cb847f9..63f896e1104b8 100644 --- a/utils/frame/benchmarking-cli/src/storage/weights.hbs +++ b/utils/frame/benchmarking-cli/src/storage/weights.hbs @@ -22,52 +22,55 @@ //! BLOCK-NUM: `{{block_number}}` //! SKIP-WRITE: `{{params.skip_write}}`, SKIP-READ: `{{params.skip_read}}`, WARMUPS: `{{params.warmups}}` //! STATE-VERSION: `V{{params.state_version}}`, STATE-CACHE-SIZE: `{{params.state_cache_size}}` -//! WEIGHT-PATH: `{{params.weight_path}}` -//! METRIC: `{{params.weight_metric}}`, WEIGHT-MUL: `{{params.weight_mul}}`, WEIGHT-ADD: `{{params.weight_add}}` +//! WEIGHT-PATH: `{{params.weight_params.weight_path}}` +//! METRIC: `{{params.weight_params.weight_metric}}`, WEIGHT-MUL: `{{params.weight_params.weight_mul}}`, WEIGHT-ADD: `{{params.weight_params.weight_add}}` // Executed Command: {{#each args as |arg|}} // {{arg}} {{/each}} -/// Storage DB weights for the {{runtime_name}} runtime and {{db_name}}. +/// Storage DB weights for the `{{runtime_name}}` runtime and `{{db_name}}`. pub mod constants { - use frame_support::{parameter_types, weights::{RuntimeDbWeight, constants}}; + use frame_support::{ + parameter_types, + weights::{constants, RuntimeDbWeight}, + }; parameter_types! { {{#if (eq db_name "ParityDb")}} - /// ParityDB can be enabled with a feature flag, but is still experimental. These weights + /// `ParityDB` can be enabled with a feature flag, but is still experimental. These weights /// are available for brave runtime engineers who may want to try this out as default. {{else}} - /// By default, Substrate uses RocksDB, so this will be the weight used throughout + /// By default, Substrate uses `RocksDB`, so this will be the weight used throughout /// the runtime. {{/if}} pub const {{db_name}}Weight: RuntimeDbWeight = RuntimeDbWeight { /// Time to read one storage item. - /// Calculated by multiplying the *{{params.weight_metric}}* of all values with `{{params.weight_mul}}` and adding `{{params.weight_add}}`. + /// Calculated by multiplying the *{{params.weight_params.weight_metric}}* of all values with `{{params.weight_params.weight_mul}}` and adding `{{params.weight_params.weight_add}}`. /// - /// Stats [ns]: + /// Stats [NS]: /// Min, Max: {{underscore read.0.min}}, {{underscore read.0.max}} /// Average: {{underscore read.0.avg}} /// Median: {{underscore read.0.median}} - /// StdDev: {{read.0.stddev}} + /// Std-Dev: {{read.0.stddev}} /// - /// Percentiles [ns]: + /// Percentiles [NS]: /// 99th: {{underscore read.0.p99}} /// 95th: {{underscore read.0.p95}} /// 75th: {{underscore read.0.p75}} read: {{underscore read_weight}} * constants::WEIGHT_PER_NANOS, /// Time to write one storage item. - /// Calculated by multiplying the *{{params.weight_metric}}* of all values with `{{params.weight_mul}}` and adding `{{params.weight_add}}`. + /// Calculated by multiplying the *{{params.weight_params.weight_metric}}* of all values with `{{params.weight_params.weight_mul}}` and adding `{{params.weight_params.weight_add}}`. /// - /// Stats [ns]: + /// Stats [NS]: /// Min, Max: {{underscore write.0.min}}, {{underscore write.0.max}} /// Average: {{underscore write.0.avg}} /// Median: {{underscore write.0.median}} - /// StdDev: {{write.0.stddev}} + /// Std-Dev: {{write.0.stddev}} /// - /// Percentiles [ns]: + /// Percentiles [NS]: /// 99th: {{underscore write.0.p99}} /// 95th: {{underscore write.0.p95}} /// 75th: {{underscore write.0.p75}} From 14fd898dddc48dbebd4c901aded2b732ee407c2a Mon Sep 17 00:00:00 2001 From: Koute Date: Tue, 29 Mar 2022 23:19:19 +0900 Subject: [PATCH 071/484] Update `lru`, `regex` and `thread_local` (#11135) * Update `lru` to 0.7.3 * Update `regex` to 1.5.5 * Update `thread_local` to 1.1.4 --- Cargo.lock | 22 +++++++++++----------- bin/node/cli/Cargo.toml | 2 +- client/cli/Cargo.toml | 2 +- client/executor/Cargo.toml | 4 ++-- client/network-gossip/Cargo.toml | 2 +- client/network/Cargo.toml | 2 +- client/tracing/Cargo.toml | 2 +- primitives/blockchain/Cargo.toml | 2 +- primitives/panic-handler/Cargo.toml | 2 +- 9 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2fe01c905d7cb..03a04cedcbf27 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4075,7 +4075,7 @@ dependencies = [ "libp2p-core", "libp2p-swarm", "log 0.4.14", - "lru 0.7.0", + "lru 0.7.3", "rand 0.7.3", "smallvec 1.8.0", "unsigned-varint 0.7.0", @@ -4354,9 +4354,9 @@ dependencies = [ [[package]] name = "lru" -version = "0.7.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c748cfe47cb8da225c37595b3108bea1c198c84aaae8ea0ba76d01dda9fc803" +checksum = "fcb87f3080f6d1d69e8c564c0fcfde1d7aa8cc451ce40cae89479111f03bc0eb" dependencies = [ "hashbrown 0.11.2", ] @@ -7739,9 +7739,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.5.4" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" dependencies = [ "aho-corasick", "memchr", @@ -8479,7 +8479,7 @@ dependencies = [ "env_logger 0.9.0", "hex-literal", "lazy_static", - "lru 0.6.6", + "lru 0.7.3", "parity-scale-codec", "parking_lot 0.12.0", "paste 1.0.6", @@ -8690,7 +8690,7 @@ dependencies = [ "linked-hash-map", "linked_hash_set", "log 0.4.14", - "lru 0.7.0", + "lru 0.7.3", "parity-scale-codec", "parking_lot 0.12.0", "pin-project 1.0.10", @@ -8734,7 +8734,7 @@ dependencies = [ "futures-timer", "libp2p", "log 0.4.14", - "lru 0.7.0", + "lru 0.7.3", "quickcheck", "sc-network", "sp-runtime", @@ -9802,7 +9802,7 @@ version = "4.0.0-dev" dependencies = [ "futures 0.3.19", "log 0.4.14", - "lru 0.7.0", + "lru 0.7.3", "parity-scale-codec", "parking_lot 0.12.0", "sp-api", @@ -10931,9 +10931,9 @@ checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" [[package]] name = "thread_local" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" dependencies = [ "once_cell", ] diff --git a/bin/node/cli/Cargo.toml b/bin/node/cli/Cargo.toml index 0e8cdfd7c3fe5..a1aa695bf3a15 100644 --- a/bin/node/cli/Cargo.toml +++ b/bin/node/cli/Cargo.toml @@ -121,7 +121,7 @@ tempfile = "3.1.0" assert_cmd = "2.0.2" nix = "0.23" serde_json = "1.0" -regex = "1" +regex = "1.5.5" platforms = "2.0" async-std = { version = "1.10.0", features = ["attributes"] } soketto = "0.4.2" diff --git a/client/cli/Cargo.toml b/client/cli/Cargo.toml index 0fbb06e8aa4a9..7725e25c0b815 100644 --- a/client/cli/Cargo.toml +++ b/client/cli/Cargo.toml @@ -22,7 +22,7 @@ libp2p = "0.40.0" log = "0.4.11" names = { version = "0.13.0", default-features = false } rand = "0.7.3" -regex = "1.5.4" +regex = "1.5.5" rpassword = "5.0.0" serde = "1.0.136" serde_json = "1.0.79" diff --git a/client/executor/Cargo.toml b/client/executor/Cargo.toml index cba5892eace13..34416b1e0d460 100644 --- a/client/executor/Cargo.toml +++ b/client/executor/Cargo.toml @@ -32,7 +32,7 @@ sc-executor-wasmi = { version = "0.10.0-dev", path = "wasmi" } sc-executor-wasmtime = { version = "0.10.0-dev", path = "wasmtime", optional = true } parking_lot = "0.12.0" sp-core-hashing-proc-macro = { version = "5.0.0", path = "../../primitives/core/hashing/proc-macro" } -lru = "0.6.6" +lru = "0.7.3" tracing = "0.1.29" [dev-dependencies] @@ -46,7 +46,7 @@ sp-maybe-compressed-blob = { version = "4.1.0-dev", path = "../../primitives/may sc-tracing = { version = "4.0.0-dev", path = "../tracing" } tracing-subscriber = "0.2.19" paste = "1.0" -regex = "1" +regex = "1.5.5" criterion = "0.3" env_logger = "0.9" diff --git a/client/network-gossip/Cargo.toml b/client/network-gossip/Cargo.toml index ade44dc94aa81..c8410d6d18783 100644 --- a/client/network-gossip/Cargo.toml +++ b/client/network-gossip/Cargo.toml @@ -19,7 +19,7 @@ futures = "0.3.19" futures-timer = "3.0.1" libp2p = { version = "0.40.0", default-features = false } log = "0.4.8" -lru = "0.7.0" +lru = "0.7.3" ahash = "0.7.6" prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } sc-network = { version = "0.10.0-dev", path = "../network" } diff --git a/client/network/Cargo.toml b/client/network/Cargo.toml index d6bc90016ab82..cd33830c1efe0 100644 --- a/client/network/Cargo.toml +++ b/client/network/Cargo.toml @@ -34,7 +34,7 @@ hex = "0.4.0" ip_network = "0.4.1" linked-hash-map = "0.5.4" linked_hash_set = "0.1.3" -lru = "0.7.0" +lru = "0.7.3" log = "0.4.8" parking_lot = "0.12.0" pin-project = "1.0.10" diff --git a/client/tracing/Cargo.toml b/client/tracing/Cargo.toml index 76cdcb4e4b021..599e310a85157 100644 --- a/client/tracing/Cargo.toml +++ b/client/tracing/Cargo.toml @@ -21,7 +21,7 @@ libc = "0.2.121" log = { version = "0.4.8" } once_cell = "1.8.0" parking_lot = "0.12.0" -regex = "1.5.4" +regex = "1.5.5" rustc-hash = "1.1.0" serde = "1.0.136" thiserror = "1.0.30" diff --git a/primitives/blockchain/Cargo.toml b/primitives/blockchain/Cargo.toml index bdb326bdb2e9d..4c9946b7a3127 100644 --- a/primitives/blockchain/Cargo.toml +++ b/primitives/blockchain/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] log = "0.4.11" -lru = "0.7.0" +lru = "0.7.3" parking_lot = "0.12.0" thiserror = "1.0.30" futures = "0.3.19" diff --git a/primitives/panic-handler/Cargo.toml b/primitives/panic-handler/Cargo.toml index abab34d29519b..0155b6532876c 100644 --- a/primitives/panic-handler/Cargo.toml +++ b/primitives/panic-handler/Cargo.toml @@ -15,5 +15,5 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] backtrace = "0.3.63" -regex = "1.5.4" +regex = "1.5.5" lazy_static = "1.4.0" From bc5186205294e94b2792a868ac074138217ee6f1 Mon Sep 17 00:00:00 2001 From: Alexander Gryaznov Date: Tue, 29 Mar 2022 17:14:49 +0200 Subject: [PATCH 072/484] contracts: add `seal_code_hash` and `seal_own_code_hash` to API (#10933) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * `seal_origin` + tests added * `seal_origin` benchmark added * `seal_code_hash` + tests added * `seal_code_hash` benchmark added * `seal_own_code_hash` + tests added * `seal_own_code_hash` benchmark added * fmt lil fix * akward accident bug fix * Apply suggestions from code review Co-authored-by: Alexander Theißen * Apply suggestions from code review Co-authored-by: Alexander Theißen * benchmark fix * `WasmModule::getter()` to take `module_name` arg * test enhanced * fixes based on review feedback * Apply suggestions from code review Co-authored-by: Alexander Theißen * Hash left as const to return a ref to it from mock * HASH test val to local const in mock * Apply suggestions from code review Co-authored-by: Alexander Theißen * fixes to benchmarks according to review feedback * cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_contracts --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/contracts/src/weights.rs --template=./.maintain/frame-weight-template.hbs * removed `seal_origin` from API Co-authored-by: Alexander Theißen Co-authored-by: Parity Bot --- frame/contracts/src/benchmarking/code.rs | 4 +- frame/contracts/src/benchmarking/mod.rs | 71 +- frame/contracts/src/exec.rs | 72 ++ frame/contracts/src/schedule.rs | 8 + frame/contracts/src/wasm/mod.rs | 120 ++- frame/contracts/src/wasm/runtime.rs | 48 + frame/contracts/src/weights.rs | 1242 +++++++++++----------- 7 files changed, 951 insertions(+), 614 deletions(-) diff --git a/frame/contracts/src/benchmarking/code.rs b/frame/contracts/src/benchmarking/code.rs index f9d71fde65885..2544fd6b7f922 100644 --- a/frame/contracts/src/benchmarking/code.rs +++ b/frame/contracts/src/benchmarking/code.rs @@ -339,12 +339,12 @@ where /// Creates a wasm module that calls the imported function named `getter_name` `repeat` /// times. The imported function is expected to have the "getter signature" of /// (out_ptr: u32, len_ptr: u32) -> (). - pub fn getter(getter_name: &'static str, repeat: u32) -> Self { + pub fn getter(module_name: &'static str, getter_name: &'static str, repeat: u32) -> Self { let pages = max_pages::(); ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "seal0", + module: module_name, name: getter_name, params: vec![ValueType::I32, ValueType::I32], return_type: None, diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs index 8539978bd6b39..de83f51a01528 100644 --- a/frame/contracts/src/benchmarking/mod.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -394,7 +394,7 @@ benchmarks! { seal_caller { let r in 0 .. API_BENCHMARK_BATCHES; let instance = Contract::::new(WasmModule::getter( - "seal_caller", r * API_BENCHMARK_BATCH_SIZE + "seal0", "seal_caller", r * API_BENCHMARK_BATCH_SIZE ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) @@ -436,6 +436,59 @@ benchmarks! { let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + seal_code_hash { + let r in 0 .. API_BENCHMARK_BATCHES; + let accounts = (0 .. r * API_BENCHMARK_BATCH_SIZE) + .map(|n| account::("account", n, 0)) + .collect::>(); + let account_len = accounts.get(0).map(|i| i.encode().len()).unwrap_or(0); + let accounts_bytes = accounts.iter().map(|a| a.encode()).flatten().collect::>(); + let accounts_len = accounts_bytes.len(); + let pages = code::max_pages::(); + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + module: "__unstable__", + name: "seal_code_hash", + params: vec![ValueType::I32, ValueType::I32, ValueType::I32], + return_type: Some(ValueType::I32), + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: 32u32.to_le_bytes().to_vec(), // output length + }, + DataSegment { + offset: 36, + value: accounts_bytes, + }, + ], + call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + Counter(36, account_len as u32), // address_ptr + Regular(Instruction::I32Const(4)), // ptr to output data + Regular(Instruction::I32Const(0)), // ptr to output length + Regular(Instruction::Call(0)), + Regular(Instruction::Drop), + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![])?; + let info = instance.info()?; + // every account would be a contract (worst case) + for acc in accounts.iter() { + >::insert(acc, info.clone()); + } + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + + seal_own_code_hash { + let r in 0 .. API_BENCHMARK_BATCHES; + let instance = Contract::::new(WasmModule::getter( + "__unstable__", "seal_own_code_hash", r * API_BENCHMARK_BATCH_SIZE + ), vec![])?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + seal_caller_is_origin { let r in 0 .. API_BENCHMARK_BATCHES; let code = WasmModule::::from(ModuleDefinition { @@ -459,7 +512,7 @@ benchmarks! { seal_address { let r in 0 .. API_BENCHMARK_BATCHES; let instance = Contract::::new(WasmModule::getter( - "seal_address", r * API_BENCHMARK_BATCH_SIZE + "seal0", "seal_address", r * API_BENCHMARK_BATCH_SIZE ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) @@ -467,7 +520,7 @@ benchmarks! { seal_gas_left { let r in 0 .. API_BENCHMARK_BATCHES; let instance = Contract::::new(WasmModule::getter( - "seal_gas_left", r * API_BENCHMARK_BATCH_SIZE + "seal0", "seal_gas_left", r * API_BENCHMARK_BATCH_SIZE ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) @@ -475,7 +528,7 @@ benchmarks! { seal_balance { let r in 0 .. API_BENCHMARK_BATCHES; let instance = Contract::::new(WasmModule::getter( - "seal_balance", r * API_BENCHMARK_BATCH_SIZE + "seal0", "seal_balance", r * API_BENCHMARK_BATCH_SIZE ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) @@ -483,7 +536,7 @@ benchmarks! { seal_value_transferred { let r in 0 .. API_BENCHMARK_BATCHES; let instance = Contract::::new(WasmModule::getter( - "seal_value_transferred", r * API_BENCHMARK_BATCH_SIZE + "seal0", "seal_value_transferred", r * API_BENCHMARK_BATCH_SIZE ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) @@ -491,7 +544,7 @@ benchmarks! { seal_minimum_balance { let r in 0 .. API_BENCHMARK_BATCHES; let instance = Contract::::new(WasmModule::getter( - "seal_minimum_balance", r * API_BENCHMARK_BATCH_SIZE + "seal0", "seal_minimum_balance", r * API_BENCHMARK_BATCH_SIZE ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) @@ -499,7 +552,7 @@ benchmarks! { seal_block_number { let r in 0 .. API_BENCHMARK_BATCHES; let instance = Contract::::new(WasmModule::getter( - "seal_block_number", r * API_BENCHMARK_BATCH_SIZE + "seal0", "seal_block_number", r * API_BENCHMARK_BATCH_SIZE ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) @@ -507,7 +560,7 @@ benchmarks! { seal_now { let r in 0 .. API_BENCHMARK_BATCHES; let instance = Contract::::new(WasmModule::getter( - "seal_now", r * API_BENCHMARK_BATCH_SIZE + "seal0", "seal_now", r * API_BENCHMARK_BATCH_SIZE ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) @@ -2341,7 +2394,7 @@ benchmarks! { } // w_memory_grow = w_bench - 2 * w_param - // We can only allow allocate as much memory as it is allowed in a a contract. + // We can only allow allocate as much memory as it is allowed in a contract. // Therefore the repeat count is limited by the maximum memory any contract can have. // Using a contract with more memory will skew the benchmark because the runtime of grow // depends on how much memory is already allocated. diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index 455665687d973..e73b29e54378b 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -162,6 +162,14 @@ pub trait Ext: sealing::Sealed { /// Check if a contract lives at the specified `address`. fn is_contract(&self, address: &AccountIdOf) -> bool; + /// Returns the code hash of the contract for the given `address`. + /// + /// Returns `None` if the `address` does not belong to a contract. + fn code_hash(&self, address: &AccountIdOf) -> Option>; + + /// Returns the code hash of the contract being executed. + fn own_code_hash(&mut self) -> &CodeHash; + /// Check if the caller of the current contract is the origin of the whole call stack. /// /// This can be checked with `is_contract(self.caller())` as well. @@ -1103,6 +1111,14 @@ where ContractInfoOf::::contains_key(&address) } + fn code_hash(&self, address: &T::AccountId) -> Option> { + >::get(&address).map(|contract| contract.code_hash) + } + + fn own_code_hash(&mut self) -> &CodeHash { + &self.top_frame_mut().contract_info().code_hash + } + fn caller_is_origin(&self) -> bool { self.caller() == &self.origin } @@ -1753,6 +1769,62 @@ mod tests { }); } + #[test] + fn code_hash_returns_proper_values() { + let code_bob = MockLoader::insert(Call, |ctx, _| { + // ALICE is not a contract and hence she does not have a code_hash + assert!(ctx.ext.code_hash(&ALICE).is_none()); + // BOB is a contract and hence he has a code_hash + assert!(ctx.ext.code_hash(&BOB).is_some()); + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + let schedule = ::Schedule::get(); + place_contract(&BOB, code_bob); + let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); + // ALICE (not contract) -> BOB (contract) + let result = MockStack::run_call( + ALICE, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + &schedule, + 0, + vec![0], + None, + ); + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn own_code_hash_returns_proper_values() { + let bob_ch = MockLoader::insert(Call, |ctx, _| { + let code_hash = ctx.ext.code_hash(&BOB).unwrap(); + assert_eq!(*ctx.ext.own_code_hash(), code_hash); + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + let schedule = ::Schedule::get(); + place_contract(&BOB, bob_ch); + let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); + // ALICE (not contract) -> BOB (contract) + let result = MockStack::run_call( + ALICE, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + &schedule, + 0, + vec![0], + None, + ); + assert_matches!(result, Ok(_)); + }); + } + #[test] fn caller_is_origin_returns_proper_values() { let code_charlie = MockLoader::insert(Call, |ctx, _| { diff --git a/frame/contracts/src/schedule.rs b/frame/contracts/src/schedule.rs index 8535166a6ac5c..b0c58d721d578 100644 --- a/frame/contracts/src/schedule.rs +++ b/frame/contracts/src/schedule.rs @@ -265,6 +265,12 @@ pub struct HostFnWeights { /// Weight of calling `seal_is_contract`. pub is_contract: Weight, + /// Weight of calling `seal_code_hash`. + pub code_hash: Weight, + + /// Weight of calling `seal_own_code_hash`. + pub own_code_hash: Weight, + /// Weight of calling `seal_caller_is_origin`. pub caller_is_origin: Weight, @@ -584,6 +590,8 @@ impl Default for HostFnWeights { Self { caller: cost_batched!(seal_caller), is_contract: cost_batched!(seal_is_contract), + code_hash: cost_batched!(seal_code_hash), + own_code_hash: cost_batched!(seal_own_code_hash), caller_is_origin: cost_batched!(seal_caller_is_origin), address: cost_batched!(seal_address), gas_left: cost_batched!(seal_gas_left), diff --git a/frame/contracts/src/wasm/mod.rs b/frame/contracts/src/wasm/mod.rs index 3912a936684c2..c38613cb68102 100644 --- a/frame/contracts/src/wasm/mod.rs +++ b/frame/contracts/src/wasm/mod.rs @@ -438,6 +438,13 @@ mod tests { fn is_contract(&self, _address: &AccountIdOf) -> bool { true } + fn code_hash(&self, _address: &AccountIdOf) -> Option> { + Some(H256::from_slice(&[0x11; 32])) + } + fn own_code_hash(&mut self) -> &CodeHash { + const HASH: H256 = H256::repeat_byte(0x10); + &HASH + } fn caller_is_origin(&self) -> bool { false } @@ -1155,7 +1162,7 @@ mod tests { ); } - /// calls `seal_caller` and compares the result with the constant 42. + /// calls `seal_caller` and compares the result with the constant (ALICE's address part). const CODE_CALLER: &str = r#" (module (import "seal0" "seal_caller" (func $seal_caller (param i32 i32))) @@ -1185,7 +1192,7 @@ mod tests { ) ) - ;; assert that the first 64 byte are the beginning of "ALICE" + ;; assert that the first 8 bytes are the beginning of "ALICE" (call $assert (i64.eq (i64.load (i32.const 0)) @@ -1203,7 +1210,7 @@ mod tests { assert_ok!(execute(CODE_CALLER, vec![], MockExt::default())); } - /// calls `seal_address` and compares the result with the constant 69. + /// calls `seal_address` and compares the result with the constant (BOB's address part). const CODE_ADDRESS: &str = r#" (module (import "seal0" "seal_address" (func $seal_address (param i32 i32))) @@ -1233,7 +1240,7 @@ mod tests { ) ) - ;; assert that the first 64 byte are the beginning of "BOB" + ;; assert that the first 8 bytes are the beginning of "BOB" (call $assert (i64.eq (i64.load (i32.const 0)) @@ -2361,6 +2368,111 @@ mod tests { } #[test] + #[cfg(feature = "unstable-interface")] + fn code_hash_works() { + /// calls `seal_code_hash` and compares the result with the constant. + const CODE_CODE_HASH: &str = r#" +(module + (import "__unstable__" "seal_code_hash" (func $seal_code_hash (param i32 i32 i32) (result i32))) + (import "env" "memory" (memory 1 1)) + + ;; size of our buffer is 32 bytes + (data (i32.const 32) "\20") + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func (export "call") + ;; fill the buffer with the code hash. + (call $seal_code_hash + (i32.const 0) ;; input: address_ptr (before call) + (i32.const 0) ;; output: code_hash_ptr (after call) + (i32.const 32) ;; same 32 bytes length for input and output + ) + + ;; assert size == 32 + (call $assert + (i32.eq + (i32.load (i32.const 32)) + (i32.const 32) + ) + ) + + ;; assert that the first 8 bytes are "1111111111111111" + (call $assert + (i64.eq + (i64.load (i32.const 0)) + (i64.const 0x1111111111111111) + ) + ) + drop + ) + + (func (export "deploy")) +) +"#; + assert_ok!(execute(CODE_CODE_HASH, vec![], MockExt::default())); + } + + #[test] + #[cfg(feature = "unstable-interface")] + fn own_code_hash_works() { + /// calls `seal_own_code_hash` and compares the result with the constant. + const CODE_OWN_CODE_HASH: &str = r#" +(module + (import "__unstable__" "seal_own_code_hash" (func $seal_own_code_hash (param i32 i32))) + (import "env" "memory" (memory 1 1)) + + ;; size of our buffer is 32 bytes + (data (i32.const 32) "\20") + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func (export "call") + ;; fill the buffer with the code hash + (call $seal_own_code_hash + (i32.const 0) ;; output: code_hash_ptr + (i32.const 32) ;; 32 bytes length of code_hash output + ) + + ;; assert size == 32 + (call $assert + (i32.eq + (i32.load (i32.const 32)) + (i32.const 32) + ) + ) + + ;; assert that the first 8 bytes are "1010101010101010" + (call $assert + (i64.eq + (i64.load (i32.const 0)) + (i64.const 0x1010101010101010) + ) + ) + ) + + (func (export "deploy")) +) +"#; + assert_ok!(execute(CODE_OWN_CODE_HASH, vec![], MockExt::default())); + } + + #[test] + #[cfg(feature = "unstable-interface")] fn caller_is_origin_works() { const CODE_CALLER_IS_ORIGIN: &str = r#" ;; This runs `caller_is_origin` check on zero account address diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index 043b45e6a76ed..975cfcdd12db8 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -144,6 +144,12 @@ pub enum RuntimeCosts { Caller, /// Weight of calling `seal_is_contract`. IsContract, + /// Weight of calling `seal_code_hash`. + #[cfg(feature = "unstable-interface")] + CodeHash, + /// Weight of calling `seal_own_code_hash`. + #[cfg(feature = "unstable-interface")] + OwnCodeHash, /// Weight of calling `seal_caller_is_origin`. CallerIsOrigin, /// Weight of calling `seal_address`. @@ -234,6 +240,10 @@ impl RuntimeCosts { CopyToContract(len) => s.input_per_byte.saturating_mul(len.into()), Caller => s.caller, IsContract => s.is_contract, + #[cfg(feature = "unstable-interface")] + CodeHash => s.code_hash, + #[cfg(feature = "unstable-interface")] + OwnCodeHash => s.own_code_hash, CallerIsOrigin => s.caller_is_origin, Address => s.address, GasLeft => s.gas_left, @@ -1371,6 +1381,44 @@ define_env!(Env, , Ok(ctx.ext.is_contract(&address) as u32) }, + // Retrieve the code hash for a specified contract address. + // + // # Parameters + // + // - `account_ptr`: a pointer to the address in question. + // Should be decodable as an `T::AccountId`. Traps otherwise. + // - `out_ptr`: pointer to the linear memory where the returning value is written to. + // - `out_len_ptr`: in-out pointer into linear memory where the buffer length + // is read from and the value length is written to. + // + // # Errors + // + // `ReturnCode::KeyNotFound` + [__unstable__] seal_code_hash(ctx, account_ptr: u32, out_ptr: u32, out_len_ptr: u32) -> ReturnCode => { + ctx.charge_gas(RuntimeCosts::CodeHash)?; + let address: <::T as frame_system::Config>::AccountId = + ctx.read_sandbox_memory_as(account_ptr)?; + if let Some(value) = ctx.ext.code_hash(&address) { + ctx.write_sandbox_output(out_ptr, out_len_ptr, &value.encode(), false, already_charged)?; + Ok(ReturnCode::Success) + } else { + Ok(ReturnCode::KeyNotFound) + } + }, + + // Retrieve the code hash of the currently executing contract. + // + // # Parameters + // + // - `out_ptr`: pointer to the linear memory where the returning value is written to. + // - `out_len_ptr`: in-out pointer into linear memory where the buffer length + // is read from and the value length is written to. + [__unstable__] seal_own_code_hash(ctx, out_ptr: u32, out_len_ptr: u32) => { + ctx.charge_gas(RuntimeCosts::OwnCodeHash)?; + let code_hash_encoded = &ctx.ext.own_code_hash().encode(); + Ok(ctx.write_sandbox_output(out_ptr, out_len_ptr, code_hash_encoded, false, already_charged)?) + }, + // Checks whether the caller of the current contract is the origin of the whole call stack. // // Prefer this over `seal_is_contract` when checking whether your contract is being called by a contract diff --git a/frame/contracts/src/weights.rs b/frame/contracts/src/weights.rs index b438ad51cbfca..43f00196ab3b2 100644 --- a/frame/contracts/src/weights.rs +++ b/frame/contracts/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_contracts //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-02-18, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-03-21, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -56,6 +56,8 @@ pub trait WeightInfo { fn remove_code() -> Weight; fn seal_caller(r: u32, ) -> Weight; fn seal_is_contract(r: u32, ) -> Weight; + fn seal_code_hash(r: u32, ) -> Weight; + fn seal_own_code_hash(r: u32, ) -> Weight; fn seal_caller_is_origin(r: u32, ) -> Weight; fn seal_address(r: u32, ) -> Weight; fn seal_gas_left(r: u32, ) -> Weight; @@ -160,14 +162,14 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: Contracts DeletionQueue (r:1 w:0) fn on_initialize() -> Weight { - (1_512_000 as Weight) + (1_569_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn on_initialize_per_trie_key(k: u32, ) -> Weight { - (8_089_000 as Weight) + (9_620_000 as Weight) // Standard Error: 0 - .saturating_add((741_000 as Weight).saturating_mul(k as Weight)) + .saturating_add((748_000 as Weight).saturating_mul(k as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(k as Weight))) @@ -175,17 +177,17 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts DeletionQueue (r:1 w:0) fn on_initialize_per_queue_item(q: u32, ) -> Weight { (0 as Weight) - // Standard Error: 5_000 - .saturating_add((2_287_000 as Weight).saturating_mul(q as Weight)) + // Standard Error: 4_000 + .saturating_add((1_795_000 as Weight).saturating_mul(q as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Contracts PristineCode (r:1 w:0) // Storage: Contracts CodeStorage (r:0 w:1) fn reinstrument(c: u32, ) -> Weight { - (15_212_000 as Weight) + (12_256_000 as Weight) // Standard Error: 0 - .saturating_add((51_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((49_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -194,9 +196,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Timestamp Now (r:1 w:0) // Storage: System Account (r:1 w:1) fn call_with_code_per_byte(c: u32, ) -> Weight { - (218_406_000 as Weight) + (213_494_000 as Weight) // Standard Error: 0 - .saturating_add((55_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((53_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -208,9 +210,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts PristineCode (r:0 w:1) // Storage: Contracts OwnerInfoOf (r:0 w:1) fn instantiate_with_code(c: u32, s: u32, ) -> Weight { - (265_773_000 as Weight) + (231_180_000 as Weight) // Standard Error: 0 - .saturating_add((127_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((125_000 as Weight).saturating_mul(c as Weight)) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) @@ -223,7 +225,7 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:1 w:1) fn instantiate(s: u32, ) -> Weight { - (173_852_000 as Weight) + (172_238_000 as Weight) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(6 as Weight)) @@ -234,7 +236,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Timestamp Now (r:1 w:0) // Storage: System Account (r:1 w:1) fn call() -> Weight { - (140_088_000 as Weight) + (140_912_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -242,9 +244,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts PristineCode (r:0 w:1) // Storage: Contracts OwnerInfoOf (r:0 w:1) fn upload_code(c: u32, ) -> Weight { - (44_290_000 as Weight) + (42_493_000 as Weight) // Standard Error: 0 - .saturating_add((51_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((49_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -252,7 +254,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:0 w:1) // Storage: Contracts PristineCode (r:0 w:1) fn remove_code() -> Weight { - (24_364_000 as Weight) + (24_533_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -261,9 +263,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_caller(r: u32, ) -> Weight { - (219_617_000 as Weight) - // Standard Error: 119_000 - .saturating_add((50_409_000 as Weight).saturating_mul(r as Weight)) + (220_009_000 as Weight) + // Standard Error: 80_000 + .saturating_add((47_887_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -272,9 +274,21 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_is_contract(r: u32, ) -> Weight { - (102_073_000 as Weight) - // Standard Error: 843_000 - .saturating_add((369_025_000 as Weight).saturating_mul(r as Weight)) + (71_779_000 as Weight) + // Standard Error: 900_000 + .saturating_add((371_278_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: System Account (r:1 w:0) + // Storage: Contracts ContractInfoOf (r:1 w:1) + // Storage: Contracts CodeStorage (r:1 w:0) + // Storage: Timestamp Now (r:1 w:0) + fn seal_code_hash(r: u32, ) -> Weight { + (0 as Weight) + // Standard Error: 2_329_000 + .saturating_add((451_731_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -283,10 +297,21 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + fn seal_own_code_hash(r: u32, ) -> Weight { + (227_824_000 as Weight) + // Standard Error: 128_000 + .saturating_add((52_843_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: System Account (r:1 w:0) + // Storage: Contracts ContractInfoOf (r:1 w:1) + // Storage: Contracts CodeStorage (r:1 w:0) + // Storage: Timestamp Now (r:1 w:0) fn seal_caller_is_origin(r: u32, ) -> Weight { - (213_550_000 as Weight) - // Standard Error: 63_000 - .saturating_add((21_519_000 as Weight).saturating_mul(r as Weight)) + (213_057_000 as Weight) + // Standard Error: 43_000 + .saturating_add((21_023_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -295,9 +320,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_address(r: u32, ) -> Weight { - (220_649_000 as Weight) - // Standard Error: 95_000 - .saturating_add((50_197_000 as Weight).saturating_mul(r as Weight)) + (219_066_000 as Weight) + // Standard Error: 117_000 + .saturating_add((48_056_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -306,9 +331,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_gas_left(r: u32, ) -> Weight { - (218_190_000 as Weight) - // Standard Error: 99_000 - .saturating_add((49_817_000 as Weight).saturating_mul(r as Weight)) + (218_844_000 as Weight) + // Standard Error: 101_000 + .saturating_add((47_325_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -317,9 +342,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_balance(r: u32, ) -> Weight { - (223_133_000 as Weight) - // Standard Error: 188_000 - .saturating_add((142_288_000 as Weight).saturating_mul(r as Weight)) + (219_234_000 as Weight) + // Standard Error: 171_000 + .saturating_add((142_534_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -328,9 +353,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_value_transferred(r: u32, ) -> Weight { - (216_612_000 as Weight) - // Standard Error: 103_000 - .saturating_add((49_956_000 as Weight).saturating_mul(r as Weight)) + (215_128_000 as Weight) + // Standard Error: 119_000 + .saturating_add((48_392_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -339,9 +364,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_minimum_balance(r: u32, ) -> Weight { - (218_349_000 as Weight) - // Standard Error: 93_000 - .saturating_add((49_656_000 as Weight).saturating_mul(r as Weight)) + (214_603_000 as Weight) + // Standard Error: 115_000 + .saturating_add((48_041_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -350,9 +375,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_block_number(r: u32, ) -> Weight { - (213_151_000 as Weight) - // Standard Error: 110_000 - .saturating_add((50_099_000 as Weight).saturating_mul(r as Weight)) + (214_091_000 as Weight) + // Standard Error: 126_000 + .saturating_add((48_067_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -361,9 +386,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_now(r: u32, ) -> Weight { - (216_816_000 as Weight) - // Standard Error: 95_000 - .saturating_add((49_724_000 as Weight).saturating_mul(r as Weight)) + (214_418_000 as Weight) + // Standard Error: 100_000 + .saturating_add((47_791_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -373,9 +398,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Timestamp Now (r:1 w:0) // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) fn seal_weight_to_fee(r: u32, ) -> Weight { - (223_053_000 as Weight) - // Standard Error: 148_000 - .saturating_add((124_240_000 as Weight).saturating_mul(r as Weight)) + (229_261_000 as Weight) + // Standard Error: 150_000 + .saturating_add((121_988_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -384,9 +409,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_gas(r: u32, ) -> Weight { - (127_253_000 as Weight) - // Standard Error: 27_000 - .saturating_add((25_608_000 as Weight).saturating_mul(r as Weight)) + (127_983_000 as Weight) + // Standard Error: 56_000 + .saturating_add((24_016_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -395,9 +420,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_input(r: u32, ) -> Weight { - (218_057_000 as Weight) - // Standard Error: 98_000 - .saturating_add((49_061_000 as Weight).saturating_mul(r as Weight)) + (216_634_000 as Weight) + // Standard Error: 114_000 + .saturating_add((46_864_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -406,9 +431,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_input_per_kb(n: u32, ) -> Weight { - (293_563_000 as Weight) - // Standard Error: 3_000 - .saturating_add((11_877_000 as Weight).saturating_mul(n as Weight)) + (285_180_000 as Weight) + // Standard Error: 4_000 + .saturating_add((11_899_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -416,10 +441,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - fn seal_return(r: u32, ) -> Weight { - (211_511_000 as Weight) - // Standard Error: 70_000 - .saturating_add((2_085_000 as Weight).saturating_mul(r as Weight)) + fn seal_return(_r: u32, ) -> Weight { + (215_379_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -428,9 +451,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_return_per_kb(n: u32, ) -> Weight { - (213_876_000 as Weight) + (213_957_000 as Weight) // Standard Error: 0 - .saturating_add((193_000 as Weight).saturating_mul(n as Weight)) + .saturating_add((201_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -441,9 +464,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts DeletionQueue (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:1 w:1) fn seal_terminate(r: u32, ) -> Weight { - (214_736_000 as Weight) - // Standard Error: 206_000 - .saturating_add((53_637_000 as Weight).saturating_mul(r as Weight)) + (215_782_000 as Weight) + // Standard Error: 149_000 + .saturating_add((52_421_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().reads((4 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -455,9 +478,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Timestamp Now (r:1 w:0) // Storage: RandomnessCollectiveFlip RandomMaterial (r:1 w:0) fn seal_random(r: u32, ) -> Weight { - (222_037_000 as Weight) - // Standard Error: 191_000 - .saturating_add((160_114_000 as Weight).saturating_mul(r as Weight)) + (217_910_000 as Weight) + // Standard Error: 149_000 + .saturating_add((157_525_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -466,9 +489,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_deposit_event(r: u32, ) -> Weight { - (219_211_000 as Weight) - // Standard Error: 239_000 - .saturating_add((296_722_000 as Weight).saturating_mul(r as Weight)) + (230_787_000 as Weight) + // Standard Error: 210_000 + .saturating_add((296_973_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -478,11 +501,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Timestamp Now (r:1 w:0) // Storage: System EventTopics (r:100 w:100) fn seal_deposit_event_per_topic_and_kb(t: u32, n: u32, ) -> Weight { - (519_643_000 as Weight) - // Standard Error: 1_842_000 - .saturating_add((300_853_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 363_000 - .saturating_add((82_577_000 as Weight).saturating_mul(n as Weight)) + (539_238_000 as Weight) + // Standard Error: 1_701_000 + .saturating_add((294_348_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 335_000 + .saturating_add((82_116_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(t as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -493,17 +516,17 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_debug_message(r: u32, ) -> Weight { - (132_710_000 as Weight) - // Standard Error: 77_000 - .saturating_add((41_623_000 as Weight).saturating_mul(r as Weight)) + (135_081_000 as Weight) + // Standard Error: 94_000 + .saturating_add((39_247_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_set_storage(r: u32, ) -> Weight { - (40_644_000 as Weight) - // Standard Error: 1_072_000 - .saturating_add((412_308_000 as Weight).saturating_mul(r as Weight)) + (41_752_000 as Weight) + // Standard Error: 1_107_000 + .saturating_add((403_473_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -511,25 +534,25 @@ impl WeightInfo for SubstrateWeight { } // Storage: Skipped Metadata (r:0 w:0) fn seal_set_storage_per_new_kb(n: u32, ) -> Weight { - (609_052_000 as Weight) - // Standard Error: 258_000 - .saturating_add((28_633_000 as Weight).saturating_mul(n as Weight)) + (602_028_000 as Weight) + // Standard Error: 255_000 + .saturating_add((28_303_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(105 as Weight)) .saturating_add(T::DbWeight::get().writes(103 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_set_storage_per_old_kb(n: u32, ) -> Weight { - (629_665_000 as Weight) - // Standard Error: 300_000 - .saturating_add((10_947_000 as Weight).saturating_mul(n as Weight)) + (620_964_000 as Weight) + // Standard Error: 308_000 + .saturating_add((11_338_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(105 as Weight)) .saturating_add(T::DbWeight::get().writes(103 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_clear_storage(r: u32, ) -> Weight { - (91_519_000 as Weight) - // Standard Error: 889_000 - .saturating_add((386_498_000 as Weight).saturating_mul(r as Weight)) + (88_113_000 as Weight) + // Standard Error: 851_000 + .saturating_add((381_671_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(3 as Weight)) @@ -537,51 +560,51 @@ impl WeightInfo for SubstrateWeight { } // Storage: Skipped Metadata (r:0 w:0) fn seal_clear_storage_per_kb(n: u32, ) -> Weight { - (612_224_000 as Weight) - // Standard Error: 269_000 - .saturating_add((10_709_000 as Weight).saturating_mul(n as Weight)) + (603_193_000 as Weight) + // Standard Error: 262_000 + .saturating_add((10_286_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(105 as Weight)) .saturating_add(T::DbWeight::get().writes(103 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_get_storage(r: u32, ) -> Weight { - (112_236_000 as Weight) - // Standard Error: 624_000 - .saturating_add((327_655_000 as Weight).saturating_mul(r as Weight)) + (112_477_000 as Weight) + // Standard Error: 666_000 + .saturating_add((324_824_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_get_storage_per_kb(n: u32, ) -> Weight { - (567_711_000 as Weight) - // Standard Error: 387_000 - .saturating_add((63_984_000 as Weight).saturating_mul(n as Weight)) + (564_781_000 as Weight) + // Standard Error: 403_000 + .saturating_add((63_824_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(104 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_contains_storage(r: u32, ) -> Weight { - (109_996_000 as Weight) - // Standard Error: 681_000 - .saturating_add((298_317_000 as Weight).saturating_mul(r as Weight)) + (115_207_000 as Weight) + // Standard Error: 672_000 + .saturating_add((290_919_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_contains_storage_per_kb(n: u32, ) -> Weight { - (518_342_000 as Weight) - // Standard Error: 251_000 - .saturating_add((9_666_000 as Weight).saturating_mul(n as Weight)) + (511_026_000 as Weight) + // Standard Error: 224_000 + .saturating_add((10_138_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(104 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_take_storage(r: u32, ) -> Weight { - (75_974_000 as Weight) - // Standard Error: 1_000_000 - .saturating_add((417_954_000 as Weight).saturating_mul(r as Weight)) + (79_113_000 as Weight) + // Standard Error: 904_000 + .saturating_add((417_022_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(3 as Weight)) @@ -589,9 +612,9 @@ impl WeightInfo for SubstrateWeight { } // Storage: Skipped Metadata (r:0 w:0) fn seal_take_storage_per_kb(n: u32, ) -> Weight { - (653_188_000 as Weight) - // Standard Error: 333_000 - .saturating_add((64_810_000 as Weight).saturating_mul(n as Weight)) + (651_769_000 as Weight) + // Standard Error: 338_000 + .saturating_add((65_576_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(105 as Weight)) .saturating_add(T::DbWeight::get().writes(103 as Weight)) } @@ -600,9 +623,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_transfer(r: u32, ) -> Weight { - (127_056_000 as Weight) - // Standard Error: 1_106_000 - .saturating_add((1_784_183_000 as Weight).saturating_mul(r as Weight)) + (93_588_000 as Weight) + // Standard Error: 1_444_000 + .saturating_add((1_803_217_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(2 as Weight)) @@ -614,8 +637,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Timestamp Now (r:1 w:0) fn seal_call(r: u32, ) -> Weight { (0 as Weight) - // Standard Error: 2_621_000 - .saturating_add((19_757_765_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 3_050_000 + .saturating_add((19_925_209_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -627,8 +650,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Timestamp Now (r:1 w:0) fn seal_delegate_call(r: u32, ) -> Weight { (0 as Weight) - // Standard Error: 6_286_000 - .saturating_add((19_798_229_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 7_377_000 + .saturating_add((19_978_301_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads((99 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -637,11 +660,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:2 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_call_per_transfer_clone_kb(t: u32, c: u32, ) -> Weight { - (10_922_130_000 as Weight) - // Standard Error: 15_556_000 - .saturating_add((1_672_276_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 6_000 - .saturating_add((11_984_000 as Weight).saturating_mul(c as Weight)) + (11_124_804_000 as Weight) + // Standard Error: 21_475_000 + .saturating_add((1_635_442_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 9_000 + .saturating_add((11_981_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(105 as Weight)) .saturating_add(T::DbWeight::get().reads((101 as Weight).saturating_mul(t as Weight))) .saturating_add(T::DbWeight::get().writes(101 as Weight)) @@ -655,8 +678,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts OwnerInfoOf (r:100 w:100) fn seal_instantiate(r: u32, ) -> Weight { (0 as Weight) - // Standard Error: 46_147_000 - .saturating_add((27_589_519_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 47_682_000 + .saturating_add((27_883_754_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(6 as Weight)) .saturating_add(T::DbWeight::get().reads((400 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(3 as Weight)) @@ -669,11 +692,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts Nonce (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:1 w:1) fn seal_instantiate_per_transfer_salt_kb(t: u32, s: u32, ) -> Weight { - (14_790_752_000 as Weight) - // Standard Error: 37_838_000 - .saturating_add((714_016_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 17_000 - .saturating_add((155_605_000 as Weight).saturating_mul(s as Weight)) + (14_824_308_000 as Weight) + // Standard Error: 39_823_000 + .saturating_add((880_630_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 18_000 + .saturating_add((156_232_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(207 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(t as Weight))) .saturating_add(T::DbWeight::get().writes(205 as Weight)) @@ -684,9 +707,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_sha2_256(r: u32, ) -> Weight { - (216_547_000 as Weight) - // Standard Error: 126_000 - .saturating_add((81_132_000 as Weight).saturating_mul(r as Weight)) + (218_378_000 as Weight) + // Standard Error: 131_000 + .saturating_add((78_260_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -695,9 +718,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_sha2_256_per_kb(n: u32, ) -> Weight { - (459_912_000 as Weight) - // Standard Error: 27_000 - .saturating_add((464_750_000 as Weight).saturating_mul(n as Weight)) + (202_849_000 as Weight) + // Standard Error: 61_000 + .saturating_add((466_532_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -706,9 +729,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_keccak_256(r: u32, ) -> Weight { - (212_653_000 as Weight) + (220_258_000 as Weight) // Standard Error: 147_000 - .saturating_add((93_380_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((90_363_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -717,9 +740,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_keccak_256_per_kb(n: u32, ) -> Weight { - (324_536_000 as Weight) - // Standard Error: 20_000 - .saturating_add((306_160_000 as Weight).saturating_mul(n as Weight)) + (232_371_000 as Weight) + // Standard Error: 23_000 + .saturating_add((307_036_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -728,9 +751,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_blake2_256(r: u32, ) -> Weight { - (218_574_000 as Weight) - // Standard Error: 123_000 - .saturating_add((65_035_000 as Weight).saturating_mul(r as Weight)) + (217_991_000 as Weight) + // Standard Error: 124_000 + .saturating_add((62_273_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -739,9 +762,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_blake2_256_per_kb(n: u32, ) -> Weight { - (345_804_000 as Weight) - // Standard Error: 14_000 - .saturating_add((118_896_000 as Weight).saturating_mul(n as Weight)) + (396_282_000 as Weight) + // Standard Error: 13_000 + .saturating_add((119_575_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -750,9 +773,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_blake2_128(r: u32, ) -> Weight { - (215_898_000 as Weight) - // Standard Error: 108_000 - .saturating_add((64_332_000 as Weight).saturating_mul(r as Weight)) + (217_578_000 as Weight) + // Standard Error: 104_000 + .saturating_add((62_189_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -761,9 +784,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_blake2_128_per_kb(n: u32, ) -> Weight { - (351_569_000 as Weight) - // Standard Error: 18_000 - .saturating_add((118_896_000 as Weight).saturating_mul(n as Weight)) + (358_167_000 as Weight) + // Standard Error: 15_000 + .saturating_add((119_692_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -772,9 +795,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_ecdsa_recover(r: u32, ) -> Weight { - (272_893_000 as Weight) - // Standard Error: 1_438_000 - .saturating_add((15_412_877_000 as Weight).saturating_mul(r as Weight)) + (292_884_000 as Weight) + // Standard Error: 683_000 + .saturating_add((3_824_902_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -785,265 +808,265 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts OwnerInfoOf (r:36 w:36) fn seal_set_code_hash(r: u32, ) -> Weight { (0 as Weight) - // Standard Error: 2_132_000 - .saturating_add((937_623_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 2_302_000 + .saturating_add((922_467_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads((99 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes((99 as Weight).saturating_mul(r as Weight))) } fn instr_i64const(r: u32, ) -> Weight { - (74_268_000 as Weight) + (74_516_000 as Weight) // Standard Error: 1_000 - .saturating_add((595_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((592_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64load(r: u32, ) -> Weight { - (74_515_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_300_000 as Weight).saturating_mul(r as Weight)) + (74_430_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_320_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64store(r: u32, ) -> Weight { - (74_217_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_411_000 as Weight).saturating_mul(r as Weight)) + (74_440_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_428_000 as Weight).saturating_mul(r as Weight)) } fn instr_select(r: u32, ) -> Weight { - (73_689_000 as Weight) - // Standard Error: 4_000 - .saturating_add((1_792_000 as Weight).saturating_mul(r as Weight)) + (74_151_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_782_000 as Weight).saturating_mul(r as Weight)) } fn instr_if(r: u32, ) -> Weight { - (73_755_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_899_000 as Weight).saturating_mul(r as Weight)) + (74_225_000 as Weight) + // Standard Error: 3_000 + .saturating_add((1_887_000 as Weight).saturating_mul(r as Weight)) } fn instr_br(r: u32, ) -> Weight { - (73_735_000 as Weight) - // Standard Error: 0 - .saturating_add((903_000 as Weight).saturating_mul(r as Weight)) + (73_987_000 as Weight) + // Standard Error: 1_000 + .saturating_add((898_000 as Weight).saturating_mul(r as Weight)) } fn instr_br_if(r: u32, ) -> Weight { - (73_595_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_448_000 as Weight).saturating_mul(r as Weight)) + (73_305_000 as Weight) + // Standard Error: 4_000 + .saturating_add((1_465_000 as Weight).saturating_mul(r as Weight)) } fn instr_br_table(r: u32, ) -> Weight { - (73_524_000 as Weight) + (73_037_000 as Weight) // Standard Error: 3_000 - .saturating_add((1_572_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_605_000 as Weight).saturating_mul(r as Weight)) } fn instr_br_table_per_entry(e: u32, ) -> Weight { - (76_361_000 as Weight) + (76_434_000 as Weight) // Standard Error: 0 .saturating_add((4_000 as Weight).saturating_mul(e as Weight)) } fn instr_call(r: u32, ) -> Weight { - (76_131_000 as Weight) - // Standard Error: 7_000 - .saturating_add((7_271_000 as Weight).saturating_mul(r as Weight)) + (75_461_000 as Weight) + // Standard Error: 10_000 + .saturating_add((7_446_000 as Weight).saturating_mul(r as Weight)) } fn instr_call_indirect(r: u32, ) -> Weight { - (87_948_000 as Weight) - // Standard Error: 14_000 - .saturating_add((9_429_000 as Weight).saturating_mul(r as Weight)) + (87_222_000 as Weight) + // Standard Error: 15_000 + .saturating_add((9_406_000 as Weight).saturating_mul(r as Weight)) } fn instr_call_indirect_per_param(p: u32, ) -> Weight { - (98_091_000 as Weight) + (97_204_000 as Weight) // Standard Error: 1_000 - .saturating_add((481_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((472_000 as Weight).saturating_mul(p as Weight)) } fn instr_local_get(r: u32, ) -> Weight { - (74_311_000 as Weight) + (75_299_000 as Weight) // Standard Error: 1_000 - .saturating_add((627_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((601_000 as Weight).saturating_mul(r as Weight)) } fn instr_local_set(r: u32, ) -> Weight { - (74_701_000 as Weight) - // Standard Error: 1_000 - .saturating_add((677_000 as Weight).saturating_mul(r as Weight)) + (74_827_000 as Weight) + // Standard Error: 3_000 + .saturating_add((686_000 as Weight).saturating_mul(r as Weight)) } fn instr_local_tee(r: u32, ) -> Weight { - (74_645_000 as Weight) - // Standard Error: 1_000 - .saturating_add((890_000 as Weight).saturating_mul(r as Weight)) + (74_624_000 as Weight) + // Standard Error: 2_000 + .saturating_add((895_000 as Weight).saturating_mul(r as Weight)) } fn instr_global_get(r: u32, ) -> Weight { - (77_130_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_156_000 as Weight).saturating_mul(r as Weight)) + (77_435_000 as Weight) + // Standard Error: 3_000 + .saturating_add((1_201_000 as Weight).saturating_mul(r as Weight)) } fn instr_global_set(r: u32, ) -> Weight { - (77_199_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_366_000 as Weight).saturating_mul(r as Weight)) + (76_693_000 as Weight) + // Standard Error: 3_000 + .saturating_add((1_410_000 as Weight).saturating_mul(r as Weight)) } fn instr_memory_current(r: u32, ) -> Weight { - (74_024_000 as Weight) - // Standard Error: 2_000 - .saturating_add((675_000 as Weight).saturating_mul(r as Weight)) + (74_244_000 as Weight) + // Standard Error: 1_000 + .saturating_add((660_000 as Weight).saturating_mul(r as Weight)) } fn instr_memory_grow(r: u32, ) -> Weight { - (75_226_000 as Weight) - // Standard Error: 170_000 - .saturating_add((186_225_000 as Weight).saturating_mul(r as Weight)) + (73_527_000 as Weight) + // Standard Error: 931_000 + .saturating_add((184_946_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64clz(r: u32, ) -> Weight { - (74_307_000 as Weight) - // Standard Error: 2_000 - .saturating_add((896_000 as Weight).saturating_mul(r as Weight)) + (74_181_000 as Weight) + // Standard Error: 6_000 + .saturating_add((906_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64ctz(r: u32, ) -> Weight { - (74_408_000 as Weight) - // Standard Error: 3_000 - .saturating_add((895_000 as Weight).saturating_mul(r as Weight)) + (74_339_000 as Weight) + // Standard Error: 1_000 + .saturating_add((896_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64popcnt(r: u32, ) -> Weight { - (74_418_000 as Weight) - // Standard Error: 1_000 - .saturating_add((885_000 as Weight).saturating_mul(r as Weight)) + (74_444_000 as Weight) + // Standard Error: 3_000 + .saturating_add((889_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64eqz(r: u32, ) -> Weight { - (74_130_000 as Weight) - // Standard Error: 2_000 - .saturating_add((920_000 as Weight).saturating_mul(r as Weight)) + (74_572_000 as Weight) + // Standard Error: 1_000 + .saturating_add((908_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64extendsi32(r: u32, ) -> Weight { - (74_318_000 as Weight) + (74_349_000 as Weight) // Standard Error: 2_000 - .saturating_add((876_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((881_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64extendui32(r: u32, ) -> Weight { - (74_496_000 as Weight) + (74_426_000 as Weight) // Standard Error: 1_000 - .saturating_add((871_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((875_000 as Weight).saturating_mul(r as Weight)) } fn instr_i32wrapi64(r: u32, ) -> Weight { - (73_938_000 as Weight) - // Standard Error: 0 - .saturating_add((897_000 as Weight).saturating_mul(r as Weight)) + (74_172_000 as Weight) + // Standard Error: 2_000 + .saturating_add((906_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64eq(r: u32, ) -> Weight { - (73_943_000 as Weight) + (74_169_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_367_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_362_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64ne(r: u32, ) -> Weight { - (74_305_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_353_000 as Weight).saturating_mul(r as Weight)) + (74_205_000 as Weight) + // Standard Error: 4_000 + .saturating_add((1_360_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64lts(r: u32, ) -> Weight { - (73_948_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_361_000 as Weight).saturating_mul(r as Weight)) + (74_237_000 as Weight) + // Standard Error: 3_000 + .saturating_add((1_356_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64ltu(r: u32, ) -> Weight { - (74_188_000 as Weight) - // Standard Error: 4_000 - .saturating_add((1_354_000 as Weight).saturating_mul(r as Weight)) + (74_181_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_357_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64gts(r: u32, ) -> Weight { - (74_156_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_353_000 as Weight).saturating_mul(r as Weight)) + (74_038_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_360_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64gtu(r: u32, ) -> Weight { - (73_972_000 as Weight) - // Standard Error: 0 - .saturating_add((1_365_000 as Weight).saturating_mul(r as Weight)) + (73_881_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_372_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64les(r: u32, ) -> Weight { - (74_082_000 as Weight) - // Standard Error: 5_000 - .saturating_add((1_360_000 as Weight).saturating_mul(r as Weight)) + (73_969_000 as Weight) + // Standard Error: 0 + .saturating_add((1_361_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64leu(r: u32, ) -> Weight { - (74_190_000 as Weight) - // Standard Error: 1_000 + (74_497_000 as Weight) + // Standard Error: 3_000 .saturating_add((1_359_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64ges(r: u32, ) -> Weight { - (73_803_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_370_000 as Weight).saturating_mul(r as Weight)) + (74_275_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_354_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64geu(r: u32, ) -> Weight { - (74_063_000 as Weight) - // Standard Error: 1_000 + (74_349_000 as Weight) + // Standard Error: 3_000 .saturating_add((1_357_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64add(r: u32, ) -> Weight { - (73_750_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_345_000 as Weight).saturating_mul(r as Weight)) + (74_192_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_333_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64sub(r: u32, ) -> Weight { - (73_979_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_341_000 as Weight).saturating_mul(r as Weight)) + (74_271_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_340_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64mul(r: u32, ) -> Weight { - (74_197_000 as Weight) - // Standard Error: 4_000 - .saturating_add((1_332_000 as Weight).saturating_mul(r as Weight)) + (73_971_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_340_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64divs(r: u32, ) -> Weight { - (73_624_000 as Weight) - // Standard Error: 5_000 - .saturating_add((2_020_000 as Weight).saturating_mul(r as Weight)) + (74_546_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_995_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64divu(r: u32, ) -> Weight { - (74_074_000 as Weight) + (74_194_000 as Weight) // Standard Error: 2_000 .saturating_add((2_050_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64rems(r: u32, ) -> Weight { - (73_766_000 as Weight) - // Standard Error: 5_000 - .saturating_add((2_016_000 as Weight).saturating_mul(r as Weight)) + (74_106_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_997_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64remu(r: u32, ) -> Weight { - (73_978_000 as Weight) - // Standard Error: 3_000 - .saturating_add((2_064_000 as Weight).saturating_mul(r as Weight)) + (74_219_000 as Weight) + // Standard Error: 5_000 + .saturating_add((2_061_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64and(r: u32, ) -> Weight { - (73_996_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_336_000 as Weight).saturating_mul(r as Weight)) + (74_157_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_338_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64or(r: u32, ) -> Weight { - (74_058_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_335_000 as Weight).saturating_mul(r as Weight)) + (74_135_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_336_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64xor(r: u32, ) -> Weight { - (73_983_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_337_000 as Weight).saturating_mul(r as Weight)) + (74_038_000 as Weight) + // Standard Error: 3_000 + .saturating_add((1_345_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64shl(r: u32, ) -> Weight { - (74_061_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_351_000 as Weight).saturating_mul(r as Weight)) + (74_011_000 as Weight) + // Standard Error: 3_000 + .saturating_add((1_362_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64shrs(r: u32, ) -> Weight { - (73_940_000 as Weight) - // Standard Error: 5_000 - .saturating_add((1_360_000 as Weight).saturating_mul(r as Weight)) + (74_054_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_356_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64shru(r: u32, ) -> Weight { - (73_954_000 as Weight) + (73_900_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_359_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_358_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64rotl(r: u32, ) -> Weight { - (74_026_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_354_000 as Weight).saturating_mul(r as Weight)) + (73_948_000 as Weight) + // Standard Error: 0 + .saturating_add((1_357_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64rotr(r: u32, ) -> Weight { - (74_149_000 as Weight) + (73_972_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_352_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_358_000 as Weight).saturating_mul(r as Weight)) } } @@ -1051,14 +1074,14 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { // Storage: Contracts DeletionQueue (r:1 w:0) fn on_initialize() -> Weight { - (1_512_000 as Weight) + (1_569_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn on_initialize_per_trie_key(k: u32, ) -> Weight { - (8_089_000 as Weight) + (9_620_000 as Weight) // Standard Error: 0 - .saturating_add((741_000 as Weight).saturating_mul(k as Weight)) + .saturating_add((748_000 as Weight).saturating_mul(k as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(k as Weight))) @@ -1066,17 +1089,17 @@ impl WeightInfo for () { // Storage: Contracts DeletionQueue (r:1 w:0) fn on_initialize_per_queue_item(q: u32, ) -> Weight { (0 as Weight) - // Standard Error: 5_000 - .saturating_add((2_287_000 as Weight).saturating_mul(q as Weight)) + // Standard Error: 4_000 + .saturating_add((1_795_000 as Weight).saturating_mul(q as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Contracts PristineCode (r:1 w:0) // Storage: Contracts CodeStorage (r:0 w:1) fn reinstrument(c: u32, ) -> Weight { - (15_212_000 as Weight) + (12_256_000 as Weight) // Standard Error: 0 - .saturating_add((51_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((49_000 as Weight).saturating_mul(c as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1085,9 +1108,9 @@ impl WeightInfo for () { // Storage: Timestamp Now (r:1 w:0) // Storage: System Account (r:1 w:1) fn call_with_code_per_byte(c: u32, ) -> Weight { - (218_406_000 as Weight) + (213_494_000 as Weight) // Standard Error: 0 - .saturating_add((55_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((53_000 as Weight).saturating_mul(c as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -1099,9 +1122,9 @@ impl WeightInfo for () { // Storage: Contracts PristineCode (r:0 w:1) // Storage: Contracts OwnerInfoOf (r:0 w:1) fn instantiate_with_code(c: u32, s: u32, ) -> Weight { - (265_773_000 as Weight) + (231_180_000 as Weight) // Standard Error: 0 - .saturating_add((127_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((125_000 as Weight).saturating_mul(c as Weight)) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) @@ -1114,7 +1137,7 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:1 w:1) fn instantiate(s: u32, ) -> Weight { - (173_852_000 as Weight) + (172_238_000 as Weight) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(6 as Weight)) @@ -1125,7 +1148,7 @@ impl WeightInfo for () { // Storage: Timestamp Now (r:1 w:0) // Storage: System Account (r:1 w:1) fn call() -> Weight { - (140_088_000 as Weight) + (140_912_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -1133,9 +1156,9 @@ impl WeightInfo for () { // Storage: Contracts PristineCode (r:0 w:1) // Storage: Contracts OwnerInfoOf (r:0 w:1) fn upload_code(c: u32, ) -> Weight { - (44_290_000 as Weight) + (42_493_000 as Weight) // Standard Error: 0 - .saturating_add((51_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((49_000 as Weight).saturating_mul(c as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -1143,7 +1166,7 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:0 w:1) // Storage: Contracts PristineCode (r:0 w:1) fn remove_code() -> Weight { - (24_364_000 as Weight) + (24_533_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -1152,9 +1175,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_caller(r: u32, ) -> Weight { - (219_617_000 as Weight) - // Standard Error: 119_000 - .saturating_add((50_409_000 as Weight).saturating_mul(r as Weight)) + (220_009_000 as Weight) + // Standard Error: 80_000 + .saturating_add((47_887_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1163,9 +1186,21 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_is_contract(r: u32, ) -> Weight { - (102_073_000 as Weight) - // Standard Error: 843_000 - .saturating_add((369_025_000 as Weight).saturating_mul(r as Weight)) + (71_779_000 as Weight) + // Standard Error: 900_000 + .saturating_add((371_278_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(RocksDbWeight::get().reads(4 as Weight)) + .saturating_add(RocksDbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: System Account (r:1 w:0) + // Storage: Contracts ContractInfoOf (r:1 w:1) + // Storage: Contracts CodeStorage (r:1 w:0) + // Storage: Timestamp Now (r:1 w:0) + fn seal_code_hash(r: u32, ) -> Weight { + (0 as Weight) + // Standard Error: 2_329_000 + .saturating_add((451_731_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -1174,10 +1209,21 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + fn seal_own_code_hash(r: u32, ) -> Weight { + (227_824_000 as Weight) + // Standard Error: 128_000 + .saturating_add((52_843_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(RocksDbWeight::get().reads(4 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: System Account (r:1 w:0) + // Storage: Contracts ContractInfoOf (r:1 w:1) + // Storage: Contracts CodeStorage (r:1 w:0) + // Storage: Timestamp Now (r:1 w:0) fn seal_caller_is_origin(r: u32, ) -> Weight { - (213_550_000 as Weight) - // Standard Error: 63_000 - .saturating_add((21_519_000 as Weight).saturating_mul(r as Weight)) + (213_057_000 as Weight) + // Standard Error: 43_000 + .saturating_add((21_023_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1186,9 +1232,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_address(r: u32, ) -> Weight { - (220_649_000 as Weight) - // Standard Error: 95_000 - .saturating_add((50_197_000 as Weight).saturating_mul(r as Weight)) + (219_066_000 as Weight) + // Standard Error: 117_000 + .saturating_add((48_056_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1197,9 +1243,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_gas_left(r: u32, ) -> Weight { - (218_190_000 as Weight) - // Standard Error: 99_000 - .saturating_add((49_817_000 as Weight).saturating_mul(r as Weight)) + (218_844_000 as Weight) + // Standard Error: 101_000 + .saturating_add((47_325_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1208,9 +1254,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_balance(r: u32, ) -> Weight { - (223_133_000 as Weight) - // Standard Error: 188_000 - .saturating_add((142_288_000 as Weight).saturating_mul(r as Weight)) + (219_234_000 as Weight) + // Standard Error: 171_000 + .saturating_add((142_534_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1219,9 +1265,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_value_transferred(r: u32, ) -> Weight { - (216_612_000 as Weight) - // Standard Error: 103_000 - .saturating_add((49_956_000 as Weight).saturating_mul(r as Weight)) + (215_128_000 as Weight) + // Standard Error: 119_000 + .saturating_add((48_392_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1230,9 +1276,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_minimum_balance(r: u32, ) -> Weight { - (218_349_000 as Weight) - // Standard Error: 93_000 - .saturating_add((49_656_000 as Weight).saturating_mul(r as Weight)) + (214_603_000 as Weight) + // Standard Error: 115_000 + .saturating_add((48_041_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1241,9 +1287,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_block_number(r: u32, ) -> Weight { - (213_151_000 as Weight) - // Standard Error: 110_000 - .saturating_add((50_099_000 as Weight).saturating_mul(r as Weight)) + (214_091_000 as Weight) + // Standard Error: 126_000 + .saturating_add((48_067_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1252,9 +1298,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_now(r: u32, ) -> Weight { - (216_816_000 as Weight) - // Standard Error: 95_000 - .saturating_add((49_724_000 as Weight).saturating_mul(r as Weight)) + (214_418_000 as Weight) + // Standard Error: 100_000 + .saturating_add((47_791_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1264,9 +1310,9 @@ impl WeightInfo for () { // Storage: Timestamp Now (r:1 w:0) // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) fn seal_weight_to_fee(r: u32, ) -> Weight { - (223_053_000 as Weight) - // Standard Error: 148_000 - .saturating_add((124_240_000 as Weight).saturating_mul(r as Weight)) + (229_261_000 as Weight) + // Standard Error: 150_000 + .saturating_add((121_988_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1275,9 +1321,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_gas(r: u32, ) -> Weight { - (127_253_000 as Weight) - // Standard Error: 27_000 - .saturating_add((25_608_000 as Weight).saturating_mul(r as Weight)) + (127_983_000 as Weight) + // Standard Error: 56_000 + .saturating_add((24_016_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1286,9 +1332,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_input(r: u32, ) -> Weight { - (218_057_000 as Weight) - // Standard Error: 98_000 - .saturating_add((49_061_000 as Weight).saturating_mul(r as Weight)) + (216_634_000 as Weight) + // Standard Error: 114_000 + .saturating_add((46_864_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1297,9 +1343,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_input_per_kb(n: u32, ) -> Weight { - (293_563_000 as Weight) - // Standard Error: 3_000 - .saturating_add((11_877_000 as Weight).saturating_mul(n as Weight)) + (285_180_000 as Weight) + // Standard Error: 4_000 + .saturating_add((11_899_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1307,10 +1353,8 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - fn seal_return(r: u32, ) -> Weight { - (211_511_000 as Weight) - // Standard Error: 70_000 - .saturating_add((2_085_000 as Weight).saturating_mul(r as Weight)) + fn seal_return(_r: u32, ) -> Weight { + (215_379_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1319,9 +1363,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_return_per_kb(n: u32, ) -> Weight { - (213_876_000 as Weight) + (213_957_000 as Weight) // Standard Error: 0 - .saturating_add((193_000 as Weight).saturating_mul(n as Weight)) + .saturating_add((201_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1332,9 +1376,9 @@ impl WeightInfo for () { // Storage: Contracts DeletionQueue (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:1 w:1) fn seal_terminate(r: u32, ) -> Weight { - (214_736_000 as Weight) - // Standard Error: 206_000 - .saturating_add((53_637_000 as Weight).saturating_mul(r as Weight)) + (215_782_000 as Weight) + // Standard Error: 149_000 + .saturating_add((52_421_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().reads((4 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -1346,9 +1390,9 @@ impl WeightInfo for () { // Storage: Timestamp Now (r:1 w:0) // Storage: RandomnessCollectiveFlip RandomMaterial (r:1 w:0) fn seal_random(r: u32, ) -> Weight { - (222_037_000 as Weight) - // Standard Error: 191_000 - .saturating_add((160_114_000 as Weight).saturating_mul(r as Weight)) + (217_910_000 as Weight) + // Standard Error: 149_000 + .saturating_add((157_525_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1357,9 +1401,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_deposit_event(r: u32, ) -> Weight { - (219_211_000 as Weight) - // Standard Error: 239_000 - .saturating_add((296_722_000 as Weight).saturating_mul(r as Weight)) + (230_787_000 as Weight) + // Standard Error: 210_000 + .saturating_add((296_973_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1369,11 +1413,11 @@ impl WeightInfo for () { // Storage: Timestamp Now (r:1 w:0) // Storage: System EventTopics (r:100 w:100) fn seal_deposit_event_per_topic_and_kb(t: u32, n: u32, ) -> Weight { - (519_643_000 as Weight) - // Standard Error: 1_842_000 - .saturating_add((300_853_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 363_000 - .saturating_add((82_577_000 as Weight).saturating_mul(n as Weight)) + (539_238_000 as Weight) + // Standard Error: 1_701_000 + .saturating_add((294_348_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 335_000 + .saturating_add((82_116_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().reads((100 as Weight).saturating_mul(t as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -1384,17 +1428,17 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_debug_message(r: u32, ) -> Weight { - (132_710_000 as Weight) - // Standard Error: 77_000 - .saturating_add((41_623_000 as Weight).saturating_mul(r as Weight)) + (135_081_000 as Weight) + // Standard Error: 94_000 + .saturating_add((39_247_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_set_storage(r: u32, ) -> Weight { - (40_644_000 as Weight) - // Standard Error: 1_072_000 - .saturating_add((412_308_000 as Weight).saturating_mul(r as Weight)) + (41_752_000 as Weight) + // Standard Error: 1_107_000 + .saturating_add((403_473_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -1402,25 +1446,25 @@ impl WeightInfo for () { } // Storage: Skipped Metadata (r:0 w:0) fn seal_set_storage_per_new_kb(n: u32, ) -> Weight { - (609_052_000 as Weight) - // Standard Error: 258_000 - .saturating_add((28_633_000 as Weight).saturating_mul(n as Weight)) + (602_028_000 as Weight) + // Standard Error: 255_000 + .saturating_add((28_303_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(105 as Weight)) .saturating_add(RocksDbWeight::get().writes(103 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_set_storage_per_old_kb(n: u32, ) -> Weight { - (629_665_000 as Weight) - // Standard Error: 300_000 - .saturating_add((10_947_000 as Weight).saturating_mul(n as Weight)) + (620_964_000 as Weight) + // Standard Error: 308_000 + .saturating_add((11_338_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(105 as Weight)) .saturating_add(RocksDbWeight::get().writes(103 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_clear_storage(r: u32, ) -> Weight { - (91_519_000 as Weight) - // Standard Error: 889_000 - .saturating_add((386_498_000 as Weight).saturating_mul(r as Weight)) + (88_113_000 as Weight) + // Standard Error: 851_000 + .saturating_add((381_671_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) @@ -1428,51 +1472,51 @@ impl WeightInfo for () { } // Storage: Skipped Metadata (r:0 w:0) fn seal_clear_storage_per_kb(n: u32, ) -> Weight { - (612_224_000 as Weight) - // Standard Error: 269_000 - .saturating_add((10_709_000 as Weight).saturating_mul(n as Weight)) + (603_193_000 as Weight) + // Standard Error: 262_000 + .saturating_add((10_286_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(105 as Weight)) .saturating_add(RocksDbWeight::get().writes(103 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_get_storage(r: u32, ) -> Weight { - (112_236_000 as Weight) - // Standard Error: 624_000 - .saturating_add((327_655_000 as Weight).saturating_mul(r as Weight)) + (112_477_000 as Weight) + // Standard Error: 666_000 + .saturating_add((324_824_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_get_storage_per_kb(n: u32, ) -> Weight { - (567_711_000 as Weight) - // Standard Error: 387_000 - .saturating_add((63_984_000 as Weight).saturating_mul(n as Weight)) + (564_781_000 as Weight) + // Standard Error: 403_000 + .saturating_add((63_824_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(104 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_contains_storage(r: u32, ) -> Weight { - (109_996_000 as Weight) - // Standard Error: 681_000 - .saturating_add((298_317_000 as Weight).saturating_mul(r as Weight)) + (115_207_000 as Weight) + // Standard Error: 672_000 + .saturating_add((290_919_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_contains_storage_per_kb(n: u32, ) -> Weight { - (518_342_000 as Weight) - // Standard Error: 251_000 - .saturating_add((9_666_000 as Weight).saturating_mul(n as Weight)) + (511_026_000 as Weight) + // Standard Error: 224_000 + .saturating_add((10_138_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(104 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_take_storage(r: u32, ) -> Weight { - (75_974_000 as Weight) - // Standard Error: 1_000_000 - .saturating_add((417_954_000 as Weight).saturating_mul(r as Weight)) + (79_113_000 as Weight) + // Standard Error: 904_000 + .saturating_add((417_022_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) @@ -1480,9 +1524,9 @@ impl WeightInfo for () { } // Storage: Skipped Metadata (r:0 w:0) fn seal_take_storage_per_kb(n: u32, ) -> Weight { - (653_188_000 as Weight) - // Standard Error: 333_000 - .saturating_add((64_810_000 as Weight).saturating_mul(n as Weight)) + (651_769_000 as Weight) + // Standard Error: 338_000 + .saturating_add((65_576_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(105 as Weight)) .saturating_add(RocksDbWeight::get().writes(103 as Weight)) } @@ -1491,9 +1535,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_transfer(r: u32, ) -> Weight { - (127_056_000 as Weight) - // Standard Error: 1_106_000 - .saturating_add((1_784_183_000 as Weight).saturating_mul(r as Weight)) + (93_588_000 as Weight) + // Standard Error: 1_444_000 + .saturating_add((1_803_217_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) @@ -1505,8 +1549,8 @@ impl WeightInfo for () { // Storage: Timestamp Now (r:1 w:0) fn seal_call(r: u32, ) -> Weight { (0 as Weight) - // Standard Error: 2_621_000 - .saturating_add((19_757_765_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 3_050_000 + .saturating_add((19_925_209_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -1518,8 +1562,8 @@ impl WeightInfo for () { // Storage: Timestamp Now (r:1 w:0) fn seal_delegate_call(r: u32, ) -> Weight { (0 as Weight) - // Standard Error: 6_286_000 - .saturating_add((19_798_229_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 7_377_000 + .saturating_add((19_978_301_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads((99 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1528,11 +1572,11 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:2 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_call_per_transfer_clone_kb(t: u32, c: u32, ) -> Weight { - (10_922_130_000 as Weight) - // Standard Error: 15_556_000 - .saturating_add((1_672_276_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 6_000 - .saturating_add((11_984_000 as Weight).saturating_mul(c as Weight)) + (11_124_804_000 as Weight) + // Standard Error: 21_475_000 + .saturating_add((1_635_442_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 9_000 + .saturating_add((11_981_000 as Weight).saturating_mul(c as Weight)) .saturating_add(RocksDbWeight::get().reads(105 as Weight)) .saturating_add(RocksDbWeight::get().reads((101 as Weight).saturating_mul(t as Weight))) .saturating_add(RocksDbWeight::get().writes(101 as Weight)) @@ -1546,8 +1590,8 @@ impl WeightInfo for () { // Storage: Contracts OwnerInfoOf (r:100 w:100) fn seal_instantiate(r: u32, ) -> Weight { (0 as Weight) - // Standard Error: 46_147_000 - .saturating_add((27_589_519_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 47_682_000 + .saturating_add((27_883_754_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(6 as Weight)) .saturating_add(RocksDbWeight::get().reads((400 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) @@ -1560,11 +1604,11 @@ impl WeightInfo for () { // Storage: Contracts Nonce (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:1 w:1) fn seal_instantiate_per_transfer_salt_kb(t: u32, s: u32, ) -> Weight { - (14_790_752_000 as Weight) - // Standard Error: 37_838_000 - .saturating_add((714_016_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 17_000 - .saturating_add((155_605_000 as Weight).saturating_mul(s as Weight)) + (14_824_308_000 as Weight) + // Standard Error: 39_823_000 + .saturating_add((880_630_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 18_000 + .saturating_add((156_232_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(207 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(t as Weight))) .saturating_add(RocksDbWeight::get().writes(205 as Weight)) @@ -1575,9 +1619,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_sha2_256(r: u32, ) -> Weight { - (216_547_000 as Weight) - // Standard Error: 126_000 - .saturating_add((81_132_000 as Weight).saturating_mul(r as Weight)) + (218_378_000 as Weight) + // Standard Error: 131_000 + .saturating_add((78_260_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1586,9 +1630,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_sha2_256_per_kb(n: u32, ) -> Weight { - (459_912_000 as Weight) - // Standard Error: 27_000 - .saturating_add((464_750_000 as Weight).saturating_mul(n as Weight)) + (202_849_000 as Weight) + // Standard Error: 61_000 + .saturating_add((466_532_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1597,9 +1641,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_keccak_256(r: u32, ) -> Weight { - (212_653_000 as Weight) + (220_258_000 as Weight) // Standard Error: 147_000 - .saturating_add((93_380_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((90_363_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1608,9 +1652,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_keccak_256_per_kb(n: u32, ) -> Weight { - (324_536_000 as Weight) - // Standard Error: 20_000 - .saturating_add((306_160_000 as Weight).saturating_mul(n as Weight)) + (232_371_000 as Weight) + // Standard Error: 23_000 + .saturating_add((307_036_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1619,9 +1663,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_blake2_256(r: u32, ) -> Weight { - (218_574_000 as Weight) - // Standard Error: 123_000 - .saturating_add((65_035_000 as Weight).saturating_mul(r as Weight)) + (217_991_000 as Weight) + // Standard Error: 124_000 + .saturating_add((62_273_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1630,9 +1674,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_blake2_256_per_kb(n: u32, ) -> Weight { - (345_804_000 as Weight) - // Standard Error: 14_000 - .saturating_add((118_896_000 as Weight).saturating_mul(n as Weight)) + (396_282_000 as Weight) + // Standard Error: 13_000 + .saturating_add((119_575_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1641,9 +1685,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_blake2_128(r: u32, ) -> Weight { - (215_898_000 as Weight) - // Standard Error: 108_000 - .saturating_add((64_332_000 as Weight).saturating_mul(r as Weight)) + (217_578_000 as Weight) + // Standard Error: 104_000 + .saturating_add((62_189_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1652,9 +1696,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_blake2_128_per_kb(n: u32, ) -> Weight { - (351_569_000 as Weight) - // Standard Error: 18_000 - .saturating_add((118_896_000 as Weight).saturating_mul(n as Weight)) + (358_167_000 as Weight) + // Standard Error: 15_000 + .saturating_add((119_692_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1663,9 +1707,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_ecdsa_recover(r: u32, ) -> Weight { - (272_893_000 as Weight) - // Standard Error: 1_438_000 - .saturating_add((15_412_877_000 as Weight).saturating_mul(r as Weight)) + (292_884_000 as Weight) + // Standard Error: 683_000 + .saturating_add((3_824_902_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1676,264 +1720,264 @@ impl WeightInfo for () { // Storage: Contracts OwnerInfoOf (r:36 w:36) fn seal_set_code_hash(r: u32, ) -> Weight { (0 as Weight) - // Standard Error: 2_132_000 - .saturating_add((937_623_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 2_302_000 + .saturating_add((922_467_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads((99 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes((99 as Weight).saturating_mul(r as Weight))) } fn instr_i64const(r: u32, ) -> Weight { - (74_268_000 as Weight) + (74_516_000 as Weight) // Standard Error: 1_000 - .saturating_add((595_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((592_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64load(r: u32, ) -> Weight { - (74_515_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_300_000 as Weight).saturating_mul(r as Weight)) + (74_430_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_320_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64store(r: u32, ) -> Weight { - (74_217_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_411_000 as Weight).saturating_mul(r as Weight)) + (74_440_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_428_000 as Weight).saturating_mul(r as Weight)) } fn instr_select(r: u32, ) -> Weight { - (73_689_000 as Weight) - // Standard Error: 4_000 - .saturating_add((1_792_000 as Weight).saturating_mul(r as Weight)) + (74_151_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_782_000 as Weight).saturating_mul(r as Weight)) } fn instr_if(r: u32, ) -> Weight { - (73_755_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_899_000 as Weight).saturating_mul(r as Weight)) + (74_225_000 as Weight) + // Standard Error: 3_000 + .saturating_add((1_887_000 as Weight).saturating_mul(r as Weight)) } fn instr_br(r: u32, ) -> Weight { - (73_735_000 as Weight) - // Standard Error: 0 - .saturating_add((903_000 as Weight).saturating_mul(r as Weight)) + (73_987_000 as Weight) + // Standard Error: 1_000 + .saturating_add((898_000 as Weight).saturating_mul(r as Weight)) } fn instr_br_if(r: u32, ) -> Weight { - (73_595_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_448_000 as Weight).saturating_mul(r as Weight)) + (73_305_000 as Weight) + // Standard Error: 4_000 + .saturating_add((1_465_000 as Weight).saturating_mul(r as Weight)) } fn instr_br_table(r: u32, ) -> Weight { - (73_524_000 as Weight) + (73_037_000 as Weight) // Standard Error: 3_000 - .saturating_add((1_572_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_605_000 as Weight).saturating_mul(r as Weight)) } fn instr_br_table_per_entry(e: u32, ) -> Weight { - (76_361_000 as Weight) + (76_434_000 as Weight) // Standard Error: 0 .saturating_add((4_000 as Weight).saturating_mul(e as Weight)) } fn instr_call(r: u32, ) -> Weight { - (76_131_000 as Weight) - // Standard Error: 7_000 - .saturating_add((7_271_000 as Weight).saturating_mul(r as Weight)) + (75_461_000 as Weight) + // Standard Error: 10_000 + .saturating_add((7_446_000 as Weight).saturating_mul(r as Weight)) } fn instr_call_indirect(r: u32, ) -> Weight { - (87_948_000 as Weight) - // Standard Error: 14_000 - .saturating_add((9_429_000 as Weight).saturating_mul(r as Weight)) + (87_222_000 as Weight) + // Standard Error: 15_000 + .saturating_add((9_406_000 as Weight).saturating_mul(r as Weight)) } fn instr_call_indirect_per_param(p: u32, ) -> Weight { - (98_091_000 as Weight) + (97_204_000 as Weight) // Standard Error: 1_000 - .saturating_add((481_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((472_000 as Weight).saturating_mul(p as Weight)) } fn instr_local_get(r: u32, ) -> Weight { - (74_311_000 as Weight) + (75_299_000 as Weight) // Standard Error: 1_000 - .saturating_add((627_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((601_000 as Weight).saturating_mul(r as Weight)) } fn instr_local_set(r: u32, ) -> Weight { - (74_701_000 as Weight) - // Standard Error: 1_000 - .saturating_add((677_000 as Weight).saturating_mul(r as Weight)) + (74_827_000 as Weight) + // Standard Error: 3_000 + .saturating_add((686_000 as Weight).saturating_mul(r as Weight)) } fn instr_local_tee(r: u32, ) -> Weight { - (74_645_000 as Weight) - // Standard Error: 1_000 - .saturating_add((890_000 as Weight).saturating_mul(r as Weight)) + (74_624_000 as Weight) + // Standard Error: 2_000 + .saturating_add((895_000 as Weight).saturating_mul(r as Weight)) } fn instr_global_get(r: u32, ) -> Weight { - (77_130_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_156_000 as Weight).saturating_mul(r as Weight)) + (77_435_000 as Weight) + // Standard Error: 3_000 + .saturating_add((1_201_000 as Weight).saturating_mul(r as Weight)) } fn instr_global_set(r: u32, ) -> Weight { - (77_199_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_366_000 as Weight).saturating_mul(r as Weight)) + (76_693_000 as Weight) + // Standard Error: 3_000 + .saturating_add((1_410_000 as Weight).saturating_mul(r as Weight)) } fn instr_memory_current(r: u32, ) -> Weight { - (74_024_000 as Weight) - // Standard Error: 2_000 - .saturating_add((675_000 as Weight).saturating_mul(r as Weight)) + (74_244_000 as Weight) + // Standard Error: 1_000 + .saturating_add((660_000 as Weight).saturating_mul(r as Weight)) } fn instr_memory_grow(r: u32, ) -> Weight { - (75_226_000 as Weight) - // Standard Error: 170_000 - .saturating_add((186_225_000 as Weight).saturating_mul(r as Weight)) + (73_527_000 as Weight) + // Standard Error: 931_000 + .saturating_add((184_946_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64clz(r: u32, ) -> Weight { - (74_307_000 as Weight) - // Standard Error: 2_000 - .saturating_add((896_000 as Weight).saturating_mul(r as Weight)) + (74_181_000 as Weight) + // Standard Error: 6_000 + .saturating_add((906_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64ctz(r: u32, ) -> Weight { - (74_408_000 as Weight) - // Standard Error: 3_000 - .saturating_add((895_000 as Weight).saturating_mul(r as Weight)) + (74_339_000 as Weight) + // Standard Error: 1_000 + .saturating_add((896_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64popcnt(r: u32, ) -> Weight { - (74_418_000 as Weight) - // Standard Error: 1_000 - .saturating_add((885_000 as Weight).saturating_mul(r as Weight)) + (74_444_000 as Weight) + // Standard Error: 3_000 + .saturating_add((889_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64eqz(r: u32, ) -> Weight { - (74_130_000 as Weight) - // Standard Error: 2_000 - .saturating_add((920_000 as Weight).saturating_mul(r as Weight)) + (74_572_000 as Weight) + // Standard Error: 1_000 + .saturating_add((908_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64extendsi32(r: u32, ) -> Weight { - (74_318_000 as Weight) + (74_349_000 as Weight) // Standard Error: 2_000 - .saturating_add((876_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((881_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64extendui32(r: u32, ) -> Weight { - (74_496_000 as Weight) + (74_426_000 as Weight) // Standard Error: 1_000 - .saturating_add((871_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((875_000 as Weight).saturating_mul(r as Weight)) } fn instr_i32wrapi64(r: u32, ) -> Weight { - (73_938_000 as Weight) - // Standard Error: 0 - .saturating_add((897_000 as Weight).saturating_mul(r as Weight)) + (74_172_000 as Weight) + // Standard Error: 2_000 + .saturating_add((906_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64eq(r: u32, ) -> Weight { - (73_943_000 as Weight) + (74_169_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_367_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_362_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64ne(r: u32, ) -> Weight { - (74_305_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_353_000 as Weight).saturating_mul(r as Weight)) + (74_205_000 as Weight) + // Standard Error: 4_000 + .saturating_add((1_360_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64lts(r: u32, ) -> Weight { - (73_948_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_361_000 as Weight).saturating_mul(r as Weight)) + (74_237_000 as Weight) + // Standard Error: 3_000 + .saturating_add((1_356_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64ltu(r: u32, ) -> Weight { - (74_188_000 as Weight) - // Standard Error: 4_000 - .saturating_add((1_354_000 as Weight).saturating_mul(r as Weight)) + (74_181_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_357_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64gts(r: u32, ) -> Weight { - (74_156_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_353_000 as Weight).saturating_mul(r as Weight)) + (74_038_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_360_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64gtu(r: u32, ) -> Weight { - (73_972_000 as Weight) - // Standard Error: 0 - .saturating_add((1_365_000 as Weight).saturating_mul(r as Weight)) + (73_881_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_372_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64les(r: u32, ) -> Weight { - (74_082_000 as Weight) - // Standard Error: 5_000 - .saturating_add((1_360_000 as Weight).saturating_mul(r as Weight)) + (73_969_000 as Weight) + // Standard Error: 0 + .saturating_add((1_361_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64leu(r: u32, ) -> Weight { - (74_190_000 as Weight) - // Standard Error: 1_000 + (74_497_000 as Weight) + // Standard Error: 3_000 .saturating_add((1_359_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64ges(r: u32, ) -> Weight { - (73_803_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_370_000 as Weight).saturating_mul(r as Weight)) + (74_275_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_354_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64geu(r: u32, ) -> Weight { - (74_063_000 as Weight) - // Standard Error: 1_000 + (74_349_000 as Weight) + // Standard Error: 3_000 .saturating_add((1_357_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64add(r: u32, ) -> Weight { - (73_750_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_345_000 as Weight).saturating_mul(r as Weight)) + (74_192_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_333_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64sub(r: u32, ) -> Weight { - (73_979_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_341_000 as Weight).saturating_mul(r as Weight)) + (74_271_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_340_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64mul(r: u32, ) -> Weight { - (74_197_000 as Weight) - // Standard Error: 4_000 - .saturating_add((1_332_000 as Weight).saturating_mul(r as Weight)) + (73_971_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_340_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64divs(r: u32, ) -> Weight { - (73_624_000 as Weight) - // Standard Error: 5_000 - .saturating_add((2_020_000 as Weight).saturating_mul(r as Weight)) + (74_546_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_995_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64divu(r: u32, ) -> Weight { - (74_074_000 as Weight) + (74_194_000 as Weight) // Standard Error: 2_000 .saturating_add((2_050_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64rems(r: u32, ) -> Weight { - (73_766_000 as Weight) - // Standard Error: 5_000 - .saturating_add((2_016_000 as Weight).saturating_mul(r as Weight)) + (74_106_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_997_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64remu(r: u32, ) -> Weight { - (73_978_000 as Weight) - // Standard Error: 3_000 - .saturating_add((2_064_000 as Weight).saturating_mul(r as Weight)) + (74_219_000 as Weight) + // Standard Error: 5_000 + .saturating_add((2_061_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64and(r: u32, ) -> Weight { - (73_996_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_336_000 as Weight).saturating_mul(r as Weight)) + (74_157_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_338_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64or(r: u32, ) -> Weight { - (74_058_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_335_000 as Weight).saturating_mul(r as Weight)) + (74_135_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_336_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64xor(r: u32, ) -> Weight { - (73_983_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_337_000 as Weight).saturating_mul(r as Weight)) + (74_038_000 as Weight) + // Standard Error: 3_000 + .saturating_add((1_345_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64shl(r: u32, ) -> Weight { - (74_061_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_351_000 as Weight).saturating_mul(r as Weight)) + (74_011_000 as Weight) + // Standard Error: 3_000 + .saturating_add((1_362_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64shrs(r: u32, ) -> Weight { - (73_940_000 as Weight) - // Standard Error: 5_000 - .saturating_add((1_360_000 as Weight).saturating_mul(r as Weight)) + (74_054_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_356_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64shru(r: u32, ) -> Weight { - (73_954_000 as Weight) + (73_900_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_359_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_358_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64rotl(r: u32, ) -> Weight { - (74_026_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_354_000 as Weight).saturating_mul(r as Weight)) + (73_948_000 as Weight) + // Standard Error: 0 + .saturating_add((1_357_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64rotr(r: u32, ) -> Weight { - (74_149_000 as Weight) + (73_972_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_352_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_358_000 as Weight).saturating_mul(r as Weight)) } } From 873573633ecb30c03fddd6beb9e93325d7a258b0 Mon Sep 17 00:00:00 2001 From: wigy <1888808+wigy-opensource-developer@users.noreply.github.com> Date: Tue, 29 Mar 2022 20:33:20 +0000 Subject: [PATCH 073/484] Unignore test (#11097) --- client/network/src/service/tests.rs | 53 ++++++++++++++--------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/client/network/src/service/tests.rs b/client/network/src/service/tests.rs index 3dfd7392cd4dd..03d647eade173 100644 --- a/client/network/src/service/tests.rs +++ b/client/network/src/service/tests.rs @@ -186,7 +186,6 @@ fn build_nodes_one_proto() -> ( (node1, events_stream1, node2, events_stream2) } -#[ignore] #[test] fn notifications_state_consistent() { // Runs two nodes and ensures that events are propagated out of the API in a consistent @@ -272,38 +271,38 @@ fn notifications_state_consistent() { match next_event { future::Either::Left(Event::NotificationStreamOpened { remote, protocol, .. - }) => { - something_happened = true; - assert!(!node1_to_node2_open); - node1_to_node2_open = true; - assert_eq!(remote, *node2.local_peer_id()); - assert_eq!(protocol, PROTOCOL_NAME); - }, + }) => + if protocol == PROTOCOL_NAME { + something_happened = true; + assert!(!node1_to_node2_open); + node1_to_node2_open = true; + assert_eq!(remote, *node2.local_peer_id()); + }, future::Either::Right(Event::NotificationStreamOpened { remote, protocol, .. - }) => { - something_happened = true; - assert!(!node2_to_node1_open); - node2_to_node1_open = true; - assert_eq!(remote, *node1.local_peer_id()); - assert_eq!(protocol, PROTOCOL_NAME); - }, + }) => + if protocol == PROTOCOL_NAME { + something_happened = true; + assert!(!node2_to_node1_open); + node2_to_node1_open = true; + assert_eq!(remote, *node1.local_peer_id()); + }, future::Either::Left(Event::NotificationStreamClosed { remote, protocol, .. - }) => { - assert!(node1_to_node2_open); - node1_to_node2_open = false; - assert_eq!(remote, *node2.local_peer_id()); - assert_eq!(protocol, PROTOCOL_NAME); - }, + }) => + if protocol == PROTOCOL_NAME { + assert!(node1_to_node2_open); + node1_to_node2_open = false; + assert_eq!(remote, *node2.local_peer_id()); + }, future::Either::Right(Event::NotificationStreamClosed { remote, protocol, .. - }) => { - assert!(node2_to_node1_open); - node2_to_node1_open = false; - assert_eq!(remote, *node1.local_peer_id()); - assert_eq!(protocol, PROTOCOL_NAME); - }, + }) => + if protocol == PROTOCOL_NAME { + assert!(node2_to_node1_open); + node2_to_node1_open = false; + assert_eq!(remote, *node1.local_peer_id()); + }, future::Either::Left(Event::NotificationsReceived { remote, .. }) => { assert!(node1_to_node2_open); assert_eq!(remote, *node2.local_peer_id()); From 1b8c0a149efc000174ba1fefeaeeab02f164103b Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Wed, 30 Mar 2022 20:16:19 +0200 Subject: [PATCH 074/484] Fix Phragmen benchmark (#11137) * Fix phragmen benchmark Signed-off-by: Oliver Tale-Yazdi * cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_elections_phragmen --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/elections-phragmen/src/weights.rs --template=./.maintain/frame-weight-template.hbs Co-authored-by: Parity Bot --- frame/elections-phragmen/src/benchmarking.rs | 29 ++--- frame/elections-phragmen/src/weights.rs | 112 +++++++++---------- 2 files changed, 71 insertions(+), 70 deletions(-) diff --git a/frame/elections-phragmen/src/benchmarking.rs b/frame/elections-phragmen/src/benchmarking.rs index ae98de4be7e4e..05e9df60c7fbe 100644 --- a/frame/elections-phragmen/src/benchmarking.rs +++ b/frame/elections-phragmen/src/benchmarking.rs @@ -39,7 +39,9 @@ type Lookup = <::Lookup as StaticLookup>::Source; /// grab new account with infinite balance. fn endowed_account(name: &'static str, index: u32) -> T::AccountId { let account: T::AccountId = account(name, index, 0); - let amount = default_stake::(BALANCE_FACTOR); + // Fund each account with at-least his stake but still a sane amount as to not mess up + // the vote calculation. + let amount = default_stake::(MAX_VOTERS) * BalanceOf::::from(BALANCE_FACTOR); let _ = T::Currency::make_free_balance_be(&account, amount); // important to increase the total issuance since T::CurrencyToVote will need it to be sane for // phragmen to work. @@ -54,9 +56,9 @@ fn as_lookup(account: T::AccountId) -> Lookup { } /// Get a reasonable amount of stake based on the execution trait's configuration -fn default_stake(factor: u32) -> BalanceOf { - let factor = BalanceOf::::from(factor); - T::Currency::minimum_balance() * factor +fn default_stake(num_votes: u32) -> BalanceOf { + let min = T::Currency::minimum_balance(); + Elections::::deposit_of(num_votes as usize).max(min) } /// Get the current number of candidates. @@ -88,7 +90,7 @@ fn submit_candidates_with_self_vote( prefix: &'static str, ) -> Result, &'static str> { let candidates = submit_candidates::(c, prefix)?; - let stake = default_stake::(BALANCE_FACTOR); + let stake = default_stake::(c); let _ = candidates .iter() .map(|c| submit_voter::(c.clone(), vec![c.clone()], stake).map(|_| ())) @@ -112,7 +114,7 @@ fn distribute_voters( num_voters: u32, votes: usize, ) -> Result<(), &'static str> { - let stake = default_stake::(BALANCE_FACTOR); + let stake = default_stake::(num_voters); for i in 0..num_voters { // to ensure that votes are different all_candidates.rotate_left(1); @@ -160,7 +162,7 @@ benchmarks! { let all_candidates = submit_candidates::(v, "candidates")?; let caller = endowed_account::("caller", 0); - let stake = default_stake::(BALANCE_FACTOR); + let stake = default_stake::(v); // original votes. let mut votes = all_candidates; @@ -173,14 +175,15 @@ benchmarks! { }: vote(RawOrigin::Signed(caller), votes, stake) vote_more { - let v in 2 .. (MAXIMUM_VOTE as u32); + let v in 2 .. (MAXIMUM_VOTE as u32); clean::(); // create a bunch of candidates. let all_candidates = submit_candidates::(v, "candidates")?; let caller = endowed_account::("caller", 0); - let stake = default_stake::(BALANCE_FACTOR); + // Multiply the stake with 10 since we want to be able to divide it by 10 again. + let stake = default_stake::(v) * BalanceOf::::from(10u32); // original votes. let mut votes = all_candidates.iter().skip(1).cloned().collect::>(); @@ -194,14 +197,14 @@ benchmarks! { }: vote(RawOrigin::Signed(caller), votes, stake / >::from(10u32)) vote_less { - let v in 2 .. (MAXIMUM_VOTE as u32); + let v in 2 .. (MAXIMUM_VOTE as u32); clean::(); // create a bunch of candidates. let all_candidates = submit_candidates::(v, "candidates")?; let caller = endowed_account::("caller", 0); - let stake = default_stake::(BALANCE_FACTOR); + let stake = default_stake::(v); // original votes. let mut votes = all_candidates; @@ -224,7 +227,7 @@ benchmarks! { let caller = endowed_account::("caller", 0); - let stake = default_stake::(BALANCE_FACTOR); + let stake = default_stake::(v); submit_voter::(caller.clone(), all_candidates, stake)?; whitelist!(caller); @@ -238,7 +241,7 @@ benchmarks! { let m = T::DesiredMembers::get() + T::DesiredRunnersUp::get(); clean::(); - let stake = default_stake::(BALANCE_FACTOR); + let stake = default_stake::(c); // create m members and runners combined. let _ = fill_seats_up_to::(m)?; diff --git a/frame/elections-phragmen/src/weights.rs b/frame/elections-phragmen/src/weights.rs index 22c1c1cbeb406..e973334b833cc 100644 --- a/frame/elections-phragmen/src/weights.rs +++ b/frame/elections-phragmen/src/weights.rs @@ -18,11 +18,11 @@ //! Autogenerated weights for pallet_elections_phragmen //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-01-30, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-03-29, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// ./target/production/substrate +// target/production/substrate // benchmark // --chain=dev // --steps=50 @@ -33,9 +33,7 @@ // --wasm-execution=compiled // --heap-pages=4096 // --output=./frame/elections-phragmen/src/weights.rs -// --template=.maintain/frame-weight-template.hbs -// --header=HEADER-APACHE2 -// --raw +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -70,9 +68,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vote_equal(v: u32, ) -> Weight { - (23_406_000 as Weight) + (22_981_000 as Weight) // Standard Error: 6_000 - .saturating_add((270_000 as Weight).saturating_mul(v as Weight)) + .saturating_add((232_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -82,9 +80,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vote_more(v: u32, ) -> Weight { - (35_660_000 as Weight) - // Standard Error: 6_000 - .saturating_add((316_000 as Weight).saturating_mul(v as Weight)) + (36_170_000 as Weight) + // Standard Error: 8_000 + .saturating_add((219_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -94,16 +92,16 @@ impl WeightInfo for SubstrateWeight { // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vote_less(v: u32, ) -> Weight { - (35_999_000 as Weight) - // Standard Error: 6_000 - .saturating_add((299_000 as Weight).saturating_mul(v as Weight)) + (35_798_000 as Weight) + // Standard Error: 8_000 + .saturating_add((241_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn remove_voter() -> Weight { - (33_692_000 as Weight) + (33_060_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -111,17 +109,17 @@ impl WeightInfo for SubstrateWeight { // Storage: Elections Members (r:1 w:0) // Storage: Elections RunnersUp (r:1 w:0) fn submit_candidacy(c: u32, ) -> Weight { - (35_506_000 as Weight) - // Standard Error: 1_000 - .saturating_add((192_000 as Weight).saturating_mul(c as Weight)) + (35_384_000 as Weight) + // Standard Error: 0 + .saturating_add((124_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Elections Candidates (r:1 w:1) fn renounce_candidacy_candidate(c: u32, ) -> Weight { - (31_402_000 as Weight) + (31_555_000 as Weight) // Standard Error: 1_000 - .saturating_add((113_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((78_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -131,13 +129,13 @@ impl WeightInfo for SubstrateWeight { // Storage: Council Proposals (r:1 w:0) // Storage: Council Members (r:0 w:1) fn renounce_candidacy_members() -> Weight { - (42_727_000 as Weight) + (41_531_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } // Storage: Elections RunnersUp (r:1 w:1) fn renounce_candidacy_runners_up() -> Weight { - (30_638_000 as Weight) + (30_762_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -152,13 +150,13 @@ impl WeightInfo for SubstrateWeight { // Storage: Council Proposals (r:1 w:0) // Storage: Council Members (r:0 w:1) fn remove_member_with_replacement() -> Weight { - (49_317_000 as Weight) + (48_287_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } // Storage: Elections RunnersUp (r:1 w:0) fn remove_member_wrong_refund() -> Weight { - (4_688_000 as Weight) + (4_747_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) } // Storage: Elections Voting (r:251 w:250) @@ -169,8 +167,8 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:250 w:250) fn clean_defunct_voters(v: u32, _d: u32, ) -> Weight { (0 as Weight) - // Standard Error: 36_000 - .saturating_add((51_016_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 37_000 + .saturating_add((49_564_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(v as Weight))) .saturating_add(T::DbWeight::get().writes((3 as Weight).saturating_mul(v as Weight))) @@ -186,12 +184,12 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:2 w:2) fn election_phragmen(c: u32, v: u32, e: u32, ) -> Weight { (0 as Weight) - // Standard Error: 1_760_000 - .saturating_add((29_569_000 as Weight).saturating_mul(c as Weight)) - // Standard Error: 732_000 - .saturating_add((51_842_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 49_000 - .saturating_add((3_546_000 as Weight).saturating_mul(e as Weight)) + // Standard Error: 1_656_000 + .saturating_add((29_011_000 as Weight).saturating_mul(c as Weight)) + // Standard Error: 689_000 + .saturating_add((49_204_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 47_000 + .saturating_add((3_352_000 as Weight).saturating_mul(e as Weight)) .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(c as Weight))) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(v as Weight))) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(c as Weight))) @@ -206,9 +204,9 @@ impl WeightInfo for () { // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vote_equal(v: u32, ) -> Weight { - (23_406_000 as Weight) + (22_981_000 as Weight) // Standard Error: 6_000 - .saturating_add((270_000 as Weight).saturating_mul(v as Weight)) + .saturating_add((232_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -218,9 +216,9 @@ impl WeightInfo for () { // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vote_more(v: u32, ) -> Weight { - (35_660_000 as Weight) - // Standard Error: 6_000 - .saturating_add((316_000 as Weight).saturating_mul(v as Weight)) + (36_170_000 as Weight) + // Standard Error: 8_000 + .saturating_add((219_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -230,16 +228,16 @@ impl WeightInfo for () { // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vote_less(v: u32, ) -> Weight { - (35_999_000 as Weight) - // Standard Error: 6_000 - .saturating_add((299_000 as Weight).saturating_mul(v as Weight)) + (35_798_000 as Weight) + // Standard Error: 8_000 + .saturating_add((241_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn remove_voter() -> Weight { - (33_692_000 as Weight) + (33_060_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -247,17 +245,17 @@ impl WeightInfo for () { // Storage: Elections Members (r:1 w:0) // Storage: Elections RunnersUp (r:1 w:0) fn submit_candidacy(c: u32, ) -> Weight { - (35_506_000 as Weight) - // Standard Error: 1_000 - .saturating_add((192_000 as Weight).saturating_mul(c as Weight)) + (35_384_000 as Weight) + // Standard Error: 0 + .saturating_add((124_000 as Weight).saturating_mul(c as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Elections Candidates (r:1 w:1) fn renounce_candidacy_candidate(c: u32, ) -> Weight { - (31_402_000 as Weight) + (31_555_000 as Weight) // Standard Error: 1_000 - .saturating_add((113_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((78_000 as Weight).saturating_mul(c as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -267,13 +265,13 @@ impl WeightInfo for () { // Storage: Council Proposals (r:1 w:0) // Storage: Council Members (r:0 w:1) fn renounce_candidacy_members() -> Weight { - (42_727_000 as Weight) + (41_531_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } // Storage: Elections RunnersUp (r:1 w:1) fn renounce_candidacy_runners_up() -> Weight { - (30_638_000 as Weight) + (30_762_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -288,13 +286,13 @@ impl WeightInfo for () { // Storage: Council Proposals (r:1 w:0) // Storage: Council Members (r:0 w:1) fn remove_member_with_replacement() -> Weight { - (49_317_000 as Weight) + (48_287_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } // Storage: Elections RunnersUp (r:1 w:0) fn remove_member_wrong_refund() -> Weight { - (4_688_000 as Weight) + (4_747_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) } // Storage: Elections Voting (r:251 w:250) @@ -305,8 +303,8 @@ impl WeightInfo for () { // Storage: System Account (r:250 w:250) fn clean_defunct_voters(v: u32, _d: u32, ) -> Weight { (0 as Weight) - // Standard Error: 36_000 - .saturating_add((51_016_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 37_000 + .saturating_add((49_564_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(v as Weight))) .saturating_add(RocksDbWeight::get().writes((3 as Weight).saturating_mul(v as Weight))) @@ -322,12 +320,12 @@ impl WeightInfo for () { // Storage: System Account (r:2 w:2) fn election_phragmen(c: u32, v: u32, e: u32, ) -> Weight { (0 as Weight) - // Standard Error: 1_760_000 - .saturating_add((29_569_000 as Weight).saturating_mul(c as Weight)) - // Standard Error: 732_000 - .saturating_add((51_842_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 49_000 - .saturating_add((3_546_000 as Weight).saturating_mul(e as Weight)) + // Standard Error: 1_656_000 + .saturating_add((29_011_000 as Weight).saturating_mul(c as Weight)) + // Standard Error: 689_000 + .saturating_add((49_204_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 47_000 + .saturating_add((3_352_000 as Weight).saturating_mul(e as Weight)) .saturating_add(RocksDbWeight::get().reads((2 as Weight).saturating_mul(c as Weight))) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(v as Weight))) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(c as Weight))) From 336e1c9c3836fe33bdd8c1c9bffdfee0b401ad61 Mon Sep 17 00:00:00 2001 From: Georges Date: Thu, 31 Mar 2022 13:54:44 +0100 Subject: [PATCH 075/484] Enforce `MaxEncodedLen` impl for `NposSolution` (#11103) * Fail if `MaxVoters` too small * Fixing benchmarking test, better naming of error * reverting accidental change * use fully qualified syntax no need to interate to calculate len * Fail directly if too many voters --- .../election-provider-multi-phase/src/mock.rs | 2 +- .../solution-type/src/single_page.rs | 6 +++++ frame/election-provider-support/src/lib.rs | 3 ++- frame/election-provider-support/src/mock.rs | 2 +- frame/election-provider-support/src/tests.rs | 27 +++++++++++++++++++ primitives/npos-elections/src/lib.rs | 4 ++- 6 files changed, 40 insertions(+), 4 deletions(-) diff --git a/frame/election-provider-multi-phase/src/mock.rs b/frame/election-provider-multi-phase/src/mock.rs index 1b3c4d9306246..d6f040363dba0 100644 --- a/frame/election-provider-multi-phase/src/mock.rs +++ b/frame/election-provider-multi-phase/src/mock.rs @@ -74,7 +74,7 @@ frame_election_provider_support::generate_solution_type!( VoterIndex = VoterIndex, TargetIndex = TargetIndex, Accuracy = PerU16, - MaxVoters = ConstU32::<20> + MaxVoters = ConstU32::<2_000> >(16) ); diff --git a/frame/election-provider-support/solution-type/src/single_page.rs b/frame/election-provider-support/solution-type/src/single_page.rs index 5a3ddc22f61c8..a20f0542984dc 100644 --- a/frame/election-provider-support/solution-type/src/single_page.rs +++ b/frame/election-provider-support/solution-type/src/single_page.rs @@ -124,6 +124,11 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result { for<'r> FV: Fn(&'r A) -> Option, for<'r> FT: Fn(&'r A) -> Option, { + // Make sure that the voter bound is binding. + // `assignments.len()` actually represents the number of voters + if assignments.len() as u32 > <#max_voters as _feps::Get>::get() { + return Err(_feps::Error::TooManyVoters); + } let mut #struct_name: #ident = Default::default(); for _feps::Assignment { who, distribution } in assignments { match distribution.len() { @@ -134,6 +139,7 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result { } } }; + Ok(#struct_name) } diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index d79b5289dffe3..453cef8956fe5 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -170,12 +170,13 @@ pub mod onchain; pub mod traits; #[cfg(feature = "std")] use codec::{Decode, Encode}; -use frame_support::{traits::Get, BoundedVec, RuntimeDebug}; +use frame_support::{BoundedVec, RuntimeDebug}; use sp_runtime::traits::Bounded; use sp_std::{fmt::Debug, prelude::*}; /// Re-export the solution generation macro. pub use frame_election_provider_solution_type::generate_solution_type; +pub use frame_support::traits::Get; /// Re-export some type as they are used in the interface. pub use sp_arithmetic::PerThing; pub use sp_npos_elections::{ diff --git a/frame/election-provider-support/src/mock.rs b/frame/election-provider-support/src/mock.rs index 1ea8dddf7eb17..d10b1724c95d8 100644 --- a/frame/election-provider-support/src/mock.rs +++ b/frame/election-provider-support/src/mock.rs @@ -47,7 +47,7 @@ crate::generate_solution_type! { VoterIndex = u32, TargetIndex = u16, Accuracy = TestAccuracy, - MaxVoters = frame_support::traits::ConstU32::<20>, + MaxVoters = frame_support::traits::ConstU32::<2_500>, >(16) } diff --git a/frame/election-provider-support/src/tests.rs b/frame/election-provider-support/src/tests.rs index 7b4e46d836176..f88f3653c681c 100644 --- a/frame/election-provider-support/src/tests.rs +++ b/frame/election-provider-support/src/tests.rs @@ -91,6 +91,33 @@ mod solution_type { assert!(with_compact < without_compact); } + #[test] + fn from_assignment_fail_too_many_voters() { + let rng = rand::rngs::SmallRng::seed_from_u64(0); + + // This will produce 24 voters.. + let (voters, assignments, candidates) = generate_random_votes(10, 25, rng); + let voter_index = make_voter_fn(&voters); + let target_index = make_target_fn(&candidates); + + // Limit the voters to 20.. + generate_solution_type!( + pub struct InnerTestSolution::< + VoterIndex = u32, + TargetIndex = u16, + Accuracy = TestAccuracy, + MaxVoters = frame_support::traits::ConstU32::<20>, + >(16) + ); + + // 24 > 20, so this should fail. + assert_eq!( + InnerTestSolution::from_assignment(&assignments, &voter_index, &target_index) + .unwrap_err(), + NposError::TooManyVoters, + ); + } + #[test] fn max_encoded_len_too_small() { generate_solution_type!( diff --git a/primitives/npos-elections/src/lib.rs b/primitives/npos-elections/src/lib.rs index 11d531fa56d87..93fb24eb4a3ca 100644 --- a/primitives/npos-elections/src/lib.rs +++ b/primitives/npos-elections/src/lib.rs @@ -119,12 +119,14 @@ pub enum Error { SolutionTargetOverflow, /// One of the index functions returned none. SolutionInvalidIndex, - /// One of the page indices was invalid + /// One of the page indices was invalid. SolutionInvalidPageIndex, /// An error occurred in some arithmetic operation. ArithmeticError(&'static str), /// The data provided to create support map was invalid. InvalidSupportEdge, + /// The number of voters is bigger than the `MaxVoters` bound. + TooManyVoters, } /// A type which is used in the API of this crate as a numeric weight of a vote, most often the From 3a65f1f5c262561f8067ffb6954ea645b3942148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Thu, 31 Mar 2022 15:13:34 +0200 Subject: [PATCH 076/484] frame-support: Rename tests to express what they are doing (#11147) Fixes: https://github.com/paritytech/substrate/issues/11145 --- frame/support/src/storage/bounded_vec.rs | 2 +- frame/support/src/storage/weak_bounded_vec.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/support/src/storage/bounded_vec.rs b/frame/support/src/storage/bounded_vec.rs index cf585af395587..137015098cfa6 100644 --- a/frame/support/src/storage/bounded_vec.rs +++ b/frame/support/src/storage/bounded_vec.rs @@ -693,7 +693,7 @@ pub mod test { } #[test] - fn try_append_is_correct() { + fn bound_returns_correct_value() { assert_eq!(BoundedVec::>::bound(), 7); } diff --git a/frame/support/src/storage/weak_bounded_vec.rs b/frame/support/src/storage/weak_bounded_vec.rs index 4b3d87f776b4a..aa6dc88eaa4f4 100644 --- a/frame/support/src/storage/weak_bounded_vec.rs +++ b/frame/support/src/storage/weak_bounded_vec.rs @@ -330,7 +330,7 @@ pub mod test { } #[test] - fn try_append_is_correct() { + fn bound_returns_correct_value() { assert_eq!(WeakBoundedVec::>::bound(), 7); } From 3942530f21c08ce018851fb14b593ec14df7f39f Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Thu, 31 Mar 2022 15:16:34 +0100 Subject: [PATCH 077/484] add iter-from for bags-list (#11104) * add iter-from for bags-list * Fix * Apply suggestions from code review Co-authored-by: Oliver Tale-Yazdi * Fix Co-authored-by: Oliver Tale-Yazdi --- frame/bags-list/src/lib.rs | 8 +++++- frame/bags-list/src/list/mod.rs | 29 ++++++++++++++++++++++ frame/bags-list/src/tests.rs | 21 ++++++++++++++++ frame/election-provider-support/src/lib.rs | 5 ++++ frame/staking/src/pallet/impls.rs | 18 +++++++++++++- 5 files changed, 79 insertions(+), 2 deletions(-) diff --git a/frame/bags-list/src/lib.rs b/frame/bags-list/src/lib.rs index aa9f1c80dfdbc..94553433e230d 100644 --- a/frame/bags-list/src/lib.rs +++ b/frame/bags-list/src/lib.rs @@ -271,13 +271,19 @@ impl, I: 'static> Pallet { impl, I: 'static> SortedListProvider for Pallet { type Error = ListError; - type Score = T::Score; fn iter() -> Box> { Box::new(List::::iter().map(|n| n.id().clone())) } + fn iter_from( + start: &T::AccountId, + ) -> Result>, Self::Error> { + let iter = List::::iter_from(start)?; + Ok(Box::new(iter.map(|n| n.id().clone()))) + } + fn count() -> u32 { ListNodes::::count() } diff --git a/frame/bags-list/src/list/mod.rs b/frame/bags-list/src/list/mod.rs index 4e1287458bcb4..db8c06a38d674 100644 --- a/frame/bags-list/src/list/mod.rs +++ b/frame/bags-list/src/list/mod.rs @@ -42,6 +42,8 @@ use sp_std::{ pub enum ListError { /// A duplicate id has been detected. Duplicate, + /// Given node id was not found. + NodeNotFound, } #[cfg(test)] @@ -244,6 +246,33 @@ impl, I: 'static> List { iter.filter_map(Bag::get).flat_map(|bag| bag.iter()) } + /// Same as `iter`, but we start from a specific node. + /// + /// All items after this node are returned, excluding `start` itself. + pub(crate) fn iter_from( + start: &T::AccountId, + ) -> Result>, ListError> { + // We chain two iterators: + // 1. from the given `start` till the end of the bag + // 2. all the bags that come after `start`'s bag. + + let start_node = Node::::get(start).ok_or(ListError::NodeNotFound)?; + let start_node_upper = start_node.bag_upper; + let start_bag = sp_std::iter::successors(start_node.next(), |prev| prev.next()); + + let thresholds = T::BagThresholds::get(); + let idx = thresholds.partition_point(|&threshold| start_node_upper > threshold); + let leftover_bags = thresholds + .into_iter() + .take(idx) + .copied() + .rev() + .filter_map(Bag::get) + .flat_map(|bag| bag.iter()); + + Ok(start_bag.chain(leftover_bags)) + } + /// Insert several ids into the appropriate bags in the list. Continues with insertions /// if duplicates are detected. /// diff --git a/frame/bags-list/src/tests.rs b/frame/bags-list/src/tests.rs index 0d6ba4721b9a2..941623229dc27 100644 --- a/frame/bags-list/src/tests.rs +++ b/frame/bags-list/src/tests.rs @@ -458,6 +458,27 @@ mod sorted_list_provider { }); } + #[test] + fn iter_from_works() { + ExtBuilder::default().add_ids(vec![(5, 5), (6, 15)]).build_and_execute(|| { + // given + assert_eq!( + List::::get_bags(), + vec![(10, vec![1, 5]), (20, vec![6]), (1000, vec![2, 3, 4])] + ); + + assert_eq!(BagsList::iter_from(&2).unwrap().collect::>(), vec![3, 4, 6, 1, 5]); + assert_eq!(BagsList::iter_from(&3).unwrap().collect::>(), vec![4, 6, 1, 5]); + assert_eq!(BagsList::iter_from(&4).unwrap().collect::>(), vec![6, 1, 5]); + assert_eq!(BagsList::iter_from(&6).unwrap().collect::>(), vec![1, 5]); + assert_eq!(BagsList::iter_from(&1).unwrap().collect::>(), vec![5]); + assert!(BagsList::iter_from(&5).unwrap().collect::>().is_empty()); + assert!(BagsList::iter_from(&7).is_err()); + + assert_storage_noop!(assert!(BagsList::iter_from(&8).is_err())); + }); + } + #[test] fn count_works() { ExtBuilder::default().build_and_execute(|| { diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index 453cef8956fe5..19735cf6035ac 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -441,6 +441,11 @@ pub trait SortedListProvider { /// An iterator over the list, which can have `take` called on it. fn iter() -> Box>; + /// Returns an iterator over the list, starting right after from the given voter. + /// + /// May return an error if `start` is invalid. + fn iter_from(start: &AccountId) -> Result>, Self::Error>; + /// The current count of ids in the list. fn count() -> u32; diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 9d5a3ed484184..90f19c6badd8f 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -1308,7 +1308,6 @@ impl SortedListProvider for UseNominatorsAndValidatorsM type Error = (); type Score = VoteWeight; - /// Returns iterator over voter list, which can have `take` called on it. fn iter() -> Box> { Box::new( Validators::::iter() @@ -1316,6 +1315,23 @@ impl SortedListProvider for UseNominatorsAndValidatorsM .chain(Nominators::::iter().map(|(n, _)| n)), ) } + fn iter_from( + start: &T::AccountId, + ) -> Result>, Self::Error> { + if Validators::::contains_key(start) { + let start_key = Validators::::hashed_key_for(start); + Ok(Box::new( + Validators::::iter_from(start_key) + .map(|(n, _)| n) + .chain(Nominators::::iter().map(|(x, _)| x)), + )) + } else if Nominators::::contains_key(start) { + let start_key = Nominators::::hashed_key_for(start); + Ok(Box::new(Nominators::::iter_from(start_key).map(|(n, _)| n))) + } else { + Err(()) + } + } fn count() -> u32 { Nominators::::count().saturating_add(Validators::::count()) } From b2637f3c540a9f3c08f90c040bbe558cf45a4bbf Mon Sep 17 00:00:00 2001 From: David Salami <31099392+Wizdave97@users.noreply.github.com> Date: Fri, 1 Apr 2022 09:50:11 +0100 Subject: [PATCH 078/484] Refactor Beefy MMR and remove parachain specific implementations (#10664) * refactor beefy mmr * use plain vector of bytes for leaf extra * update comment * update comments * remove unused vars * Use sp_std::vec::Vec Co-authored-by: Adrian Catangiu * make extra data generic * fix tests * refactor beefy-mmr * Update frame/beefy-mmr/src/lib.rs * minor fix * fmt * Update frame/beefy-mmr/src/lib.rs Co-authored-by: Adrian Catangiu --- frame/beefy-mmr/src/lib.rs | 55 +++++++----------------------------- frame/beefy-mmr/src/mock.rs | 23 +++++++++++---- frame/beefy-mmr/src/tests.rs | 18 +++++------- primitives/beefy/src/mmr.rs | 21 ++++++++++++-- 4 files changed, 52 insertions(+), 65 deletions(-) diff --git a/frame/beefy-mmr/src/lib.rs b/frame/beefy-mmr/src/lib.rs index 476589717e06c..9ee7bd770f64c 100644 --- a/frame/beefy-mmr/src/lib.rs +++ b/frame/beefy-mmr/src/lib.rs @@ -29,17 +29,16 @@ //! The MMR leaf contains: //! 1. Block number and parent block hash. //! 2. Merkle Tree Root Hash of next BEEFY validator set. -//! 3. Merkle Tree Root Hash of current parachain heads state. +//! 3. Arbitrary extra leaf data to be used by downstream pallets to include custom data. //! //! and thanks to versioning can be easily updated in the future. -use sp_runtime::traits::{Convert, Hash}; +use sp_runtime::traits::{Convert, Hash, Member}; use sp_std::prelude::*; -use beefy_primitives::mmr::{BeefyNextAuthoritySet, MmrLeaf, MmrLeafVersion}; +use beefy_primitives::mmr::{BeefyDataProvider, BeefyNextAuthoritySet, MmrLeaf, MmrLeafVersion}; use pallet_mmr::primitives::LeafDataProvider; -use codec::Encode; use frame_support::traits::Get; pub use pallet::*; @@ -90,23 +89,6 @@ impl Convert> for BeefyEcdsaToEth } type MerkleRootOf = ::Hash; -type ParaId = u32; -type ParaHead = Vec; - -/// A type that is able to return current list of parachain heads that end up in the MMR leaf. -pub trait ParachainHeadsProvider { - /// Return a list of tuples containing a `ParaId` and Parachain Header data (ParaHead). - /// - /// The returned data does not have to be sorted. - fn parachain_heads() -> Vec<(ParaId, ParaHead)>; -} - -/// A default implementation for runtimes without parachains. -impl ParachainHeadsProvider for () { - fn parachain_heads() -> Vec<(ParaId, ParaHead)> { - Default::default() - } -} #[frame_support::pallet] pub mod pallet { @@ -138,12 +120,11 @@ pub mod pallet { /// efficiency reasons. type BeefyAuthorityToMerkleLeaf: Convert<::BeefyId, Vec>; - /// Retrieve a list of current parachain heads. - /// - /// The trait is implemented for `paras` module, but since not all chains might have - /// parachains, and we want to keep the MMR leaf structure uniform, it's possible to use - /// `()` as well to simply put dummy data to the leaf. - type ParachainHeads: ParachainHeadsProvider; + /// The type expected for the leaf extra data + type LeafExtra: Member + codec::FullCodec; + + /// Retrieve arbitrary data that should be added to the mmr leaf + type BeefyDataProvider: BeefyDataProvider; } /// Details of next BEEFY authority set. @@ -163,13 +144,14 @@ where ::BlockNumber, ::Hash, MerkleRootOf, + T::LeafExtra, >; fn leaf_data() -> Self::LeafData { MmrLeaf { version: T::LeafVersion::get(), parent_number_and_hash: frame_system::Pallet::::leaf_data(), - parachain_heads: Pallet::::parachain_heads_merkle_root(), + leaf_extra: T::BeefyDataProvider::extra_data(), beefy_next_authority_set: Pallet::::update_beefy_next_authority_set(), } } @@ -188,23 +170,6 @@ impl Pallet where MerkleRootOf: From + Into, { - /// Returns latest root hash of a merkle tree constructed from all active parachain headers. - /// - /// The leafs are sorted by `ParaId` to allow more efficient lookups and non-existence proofs. - /// - /// NOTE this does not include parathreads - only parachains are part of the merkle tree. - /// - /// NOTE This is an initial and inefficient implementation, which re-constructs - /// the merkle tree every block. Instead we should update the merkle root in - /// [Self::on_initialize] call of this pallet and update the merkle tree efficiently (use - /// on-chain storage to persist inner nodes). - fn parachain_heads_merkle_root() -> MerkleRootOf { - let mut para_heads = T::ParachainHeads::parachain_heads(); - para_heads.sort(); - let para_heads = para_heads.into_iter().map(|pair| pair.encode()); - beefy_merkle_tree::merkle_root::(para_heads).into() - } - /// Returns details of the next BEEFY authority set. /// /// Details contain authority set id, authority set length and a merkle root, diff --git a/frame/beefy-mmr/src/mock.rs b/frame/beefy-mmr/src/mock.rs index dcee901ec14ce..f6a35f68a4a1f 100644 --- a/frame/beefy-mmr/src/mock.rs +++ b/frame/beefy-mmr/src/mock.rs @@ -18,6 +18,7 @@ use std::vec; use beefy_primitives::mmr::MmrLeafVersion; +use codec::Encode; use frame_support::{ construct_runtime, parameter_types, sp_io::TestExternalities, @@ -34,7 +35,9 @@ use sp_runtime::{ use crate as pallet_beefy_mmr; -pub use beefy_primitives::{crypto::AuthorityId as BeefyId, ConsensusLog, BEEFY_ENGINE_ID}; +pub use beefy_primitives::{ + crypto::AuthorityId as BeefyId, mmr::BeefyDataProvider, ConsensusLog, BEEFY_ENGINE_ID, +}; impl_opaque_keys! { pub struct MockSessionKeys { @@ -102,6 +105,7 @@ pub type MmrLeaf = beefy_primitives::mmr::MmrLeaf< ::BlockNumber, ::Hash, ::Hash, + Vec, >; impl pallet_mmr::Config for Test { @@ -131,13 +135,20 @@ impl pallet_beefy_mmr::Config for Test { type BeefyAuthorityToMerkleLeaf = pallet_beefy_mmr::BeefyEcdsaToEthereum; - type ParachainHeads = DummyParaHeads; + type LeafExtra = Vec; + + type BeefyDataProvider = DummyDataProvider; } -pub struct DummyParaHeads; -impl pallet_beefy_mmr::ParachainHeadsProvider for DummyParaHeads { - fn parachain_heads() -> Vec<(pallet_beefy_mmr::ParaId, pallet_beefy_mmr::ParaHead)> { - vec![(15, vec![1, 2, 3]), (5, vec![4, 5, 6])] +pub struct DummyDataProvider; +impl BeefyDataProvider> for DummyDataProvider { + fn extra_data() -> Vec { + let mut col = vec![(15, vec![1, 2, 3]), (5, vec![4, 5, 6])]; + col.sort(); + beefy_merkle_tree::merkle_root::, _, _>( + col.into_iter().map(|pair| pair.encode()), + ) + .to_vec() } } diff --git a/frame/beefy-mmr/src/tests.rs b/frame/beefy-mmr/src/tests.rs index 452b8736a7916..fd383adb1d4a2 100644 --- a/frame/beefy-mmr/src/tests.rs +++ b/frame/beefy-mmr/src/tests.rs @@ -71,7 +71,7 @@ fn should_contain_mmr_digest() { assert_eq!( System::digest().logs, vec![beefy_log(ConsensusLog::MmrRoot( - hex!("969d516e5279540ef38e4a710fb0645cab4c3b01e528be7285b85ec9c5fb55c8").into() + hex!("fa0275b19b2565089f7e2377ee73b9050e8d53bce108ef722a3251fd9d371d4b").into() ))] ); @@ -82,13 +82,13 @@ fn should_contain_mmr_digest() { System::digest().logs, vec![ beefy_log(ConsensusLog::MmrRoot( - hex!("969d516e5279540ef38e4a710fb0645cab4c3b01e528be7285b85ec9c5fb55c8").into() + hex!("fa0275b19b2565089f7e2377ee73b9050e8d53bce108ef722a3251fd9d371d4b").into() )), beefy_log(ConsensusLog::AuthoritiesChange( ValidatorSet::new(vec![mock_beefy_id(3), mock_beefy_id(4),], 1,).unwrap() )), beefy_log(ConsensusLog::MmrRoot( - hex!("8c42b7b040d262f7f2e26abeb61ab0c3c448f60c7f2f19e6ca0035d9bb3ae7e2").into() + hex!("85554fa7d4e863cce3cdce668c1ae82c0174ad37f8d1399284018bec9f9971c3").into() )), ] ); @@ -114,10 +114,8 @@ fn should_contain_valid_leaf_data() { root: hex!("176e73f1bf656478b728e28dd1a7733c98621b8acf830bff585949763dca7a96") .into(), }, - parachain_heads: hex!( - "ed893c8f8cc87195a5d4d2805b011506322036bcace79642aa3e94ab431e442e" - ) - .into(), + leaf_extra: hex!("55b8e9e1cc9f0db7776fac0ca66318ef8acfb8ec26db11e373120583e07ee648") + .to_vec(), } ); @@ -138,10 +136,8 @@ fn should_contain_valid_leaf_data() { root: hex!("9c6b2c1b0d0b25a008e6c882cc7b415f309965c72ad2b944ac0931048ca31cd5") .into(), }, - parachain_heads: hex!( - "ed893c8f8cc87195a5d4d2805b011506322036bcace79642aa3e94ab431e442e" - ) - .into(), + leaf_extra: hex!("55b8e9e1cc9f0db7776fac0ca66318ef8acfb8ec26db11e373120583e07ee648") + .to_vec() } ); } diff --git a/primitives/beefy/src/mmr.rs b/primitives/beefy/src/mmr.rs index 29f513629d012..426a1ba5ff80b 100644 --- a/primitives/beefy/src/mmr.rs +++ b/primitives/beefy/src/mmr.rs @@ -26,12 +26,26 @@ //! but we imagine they will be useful for other chains that either want to bridge with Polkadot //! or are completely standalone, but heavily inspired by Polkadot. +use crate::Vec; use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; +/// A provider for extra data that gets added to the Mmr leaf +pub trait BeefyDataProvider { + /// Return a vector of bytes, ideally should be a merkle root hash + fn extra_data() -> ExtraData; +} + +/// A default implementation for runtimes. +impl BeefyDataProvider> for () { + fn extra_data() -> Vec { + Vec::new() + } +} + /// A standard leaf that gets added every block to the MMR constructed by Substrate's `pallet_mmr`. #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] -pub struct MmrLeaf { +pub struct MmrLeaf { /// Version of the leaf format. /// /// Can be used to enable future format migrations and compatibility. @@ -41,8 +55,9 @@ pub struct MmrLeaf { pub parent_number_and_hash: (BlockNumber, Hash), /// A merkle root of the next BEEFY authority set. pub beefy_next_authority_set: BeefyNextAuthoritySet, - /// A merkle root of all registered parachain heads. - pub parachain_heads: MerkleRoot, + /// Arbitrary extra leaf data to be used by downstream pallets to include custom data in the + /// [`MmrLeaf`] + pub leaf_extra: ExtraData, } /// A MMR leaf versioning scheme. From 77d1671d18e2c5c22525e260f75b48c3468ab1f6 Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Sat, 2 Apr 2022 13:12:54 -0400 Subject: [PATCH 079/484] Add Limit to Tranasctional Layers (#10808) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * introduce hard limit to transactional * add single layer transactional * remove single_transactional * Update mod.rs * add tests * maybe fix contracts cc @athei * fmt * fix contract logic * Update frame/contracts/src/exec.rs Co-authored-by: Alexander Theißen * Update exec.rs * add unchecked and custom errors * Update lib.rs * Apply suggestions from code review Co-authored-by: Alexander Theißen * Replace storage access by atomics Co-authored-by: Alexander Theißen --- frame/contracts/src/exec.rs | 29 ++- frame/support/procedural/src/transactional.rs | 4 +- frame/support/src/storage/mod.rs | 208 +++++++++++++----- .../support/test/tests/storage_transaction.rs | 108 +++++---- primitives/runtime/src/lib.rs | 33 +++ 5 files changed, 273 insertions(+), 109 deletions(-) diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index e73b29e54378b..7aa5c0b731fad 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -774,14 +774,27 @@ where // All changes performed by the contract are executed under a storage transaction. // This allows for roll back on error. Changes to the cached contract_info are - // comitted or rolled back when popping the frame. - let (success, output) = with_transaction(|| { - let output = do_transaction(); - match &output { - Ok(result) if !result.did_revert() => TransactionOutcome::Commit((true, output)), - _ => TransactionOutcome::Rollback((false, output)), - } - }); + // committed or rolled back when popping the frame. + // + // `with_transactional` may return an error caused by a limit in the + // transactional storage depth. + let transaction_outcome = + with_transaction(|| -> TransactionOutcome> { + let output = do_transaction(); + match &output { + Ok(result) if !result.did_revert() => + TransactionOutcome::Commit(Ok((true, output))), + _ => TransactionOutcome::Rollback(Ok((false, output))), + } + }); + + let (success, output) = match transaction_outcome { + // `with_transactional` executed successfully, and we have the expected output. + Ok((success, output)) => (success, output), + // `with_transactional` returned an error, and we propagate that error and note no state + // has changed. + Err(error) => (false, Err(error.into())), + }; self.pop_frame(success); output } diff --git a/frame/support/procedural/src/transactional.rs b/frame/support/procedural/src/transactional.rs index 66a8d083fb562..ba75fbc9737aa 100644 --- a/frame/support/procedural/src/transactional.rs +++ b/frame/support/procedural/src/transactional.rs @@ -49,7 +49,9 @@ pub fn require_transactional(_attr: TokenStream, input: TokenStream) -> Result = RefCell::new(0); + pub fn get_transaction_level() -> Layer { + NUM_LEVELS.load(Ordering::SeqCst) } - pub fn require_transaction() { - let level = TRANSACTION_LEVEL.with(|v| *v.borrow()); - if level == 0 { - panic!("Require transaction not called within with_transaction"); - } + /// Increments the transaction level. Returns an error if levels go past the limit. + /// + /// Returns a guard that when dropped decrements the transaction level automatically. + pub fn inc_transaction_level() -> Result { + NUM_LEVELS + .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |existing_levels| { + if existing_levels >= TRANSACTIONAL_LIMIT { + return None + } + // Cannot overflow because of check above. + Some(existing_levels + 1) + }) + .map_err(|_| ())?; + Ok(StorageLayerGuard) } - pub struct TransactionLevelGuard; + fn dec_transaction_level() { + NUM_LEVELS + .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |existing_levels| { + if existing_levels == 0 { + log::warn!( + "We are underflowing with calculating transactional levels. Not great, but let's not panic...", + ); + None + } else { + // Cannot underflow because of checks above. + Some(existing_levels - 1) + } + }) + .ok(); + } - impl Drop for TransactionLevelGuard { - fn drop(&mut self) { - TRANSACTION_LEVEL.with(|v| *v.borrow_mut() -= 1); - } + pub fn is_transactional() -> bool { + get_transaction_level() > 0 } - /// Increments the transaction level. - /// - /// Returns a guard that when dropped decrements the transaction level automatically. - pub fn inc_transaction_level() -> TransactionLevelGuard { - TRANSACTION_LEVEL.with(|v| { - let mut val = v.borrow_mut(); - *val += 1; - if *val > 10 { - log::warn!( - "Detected with_transaction with nest level {}. Nested usage of with_transaction is not recommended.", - *val - ); - } - }); + pub struct StorageLayerGuard; - TransactionLevelGuard + impl Drop for StorageLayerGuard { + fn drop(&mut self) { + dec_transaction_level() + } } } -/// Assert this method is called within a storage transaction. -/// This will **panic** if is not called within a storage transaction. -/// -/// This assertion is enabled for native execution and when `debug_assertions` are enabled. -pub fn require_transaction() { - #[cfg(all(feature = "std", any(test, debug_assertions)))] - debug_helper::require_transaction(); +/// Check if the current call is within a transactional layer. +pub fn is_transactional() -> bool { + transaction_level_tracker::is_transactional() } /// Execute the supplied function in a new storage transaction. @@ -100,15 +113,55 @@ pub fn require_transaction() { /// All changes to storage performed by the supplied function are discarded if the returned /// outcome is `TransactionOutcome::Rollback`. /// -/// Transactions can be nested to any depth. Commits happen to the parent transaction. -pub fn with_transaction(f: impl FnOnce() -> TransactionOutcome) -> R { +/// Transactions can be nested up to `TRANSACTIONAL_LIMIT` times; more than that will result in an +/// error. +/// +/// Commits happen to the parent transaction. +pub fn with_transaction(f: impl FnOnce() -> TransactionOutcome>) -> Result +where + E: From, +{ use sp_io::storage::{commit_transaction, rollback_transaction, start_transaction}; use TransactionOutcome::*; + let _guard = transaction_level_tracker::inc_transaction_level() + .map_err(|()| TransactionalError::LimitReached.into())?; + start_transaction(); - #[cfg(all(feature = "std", any(test, debug_assertions)))] - let _guard = debug_helper::inc_transaction_level(); + match f() { + Commit(res) => { + commit_transaction(); + res + }, + Rollback(res) => { + rollback_transaction(); + res + }, + } +} + +/// Same as [`with_transaction`] but without a limit check on nested transactional layers. +/// +/// This is mostly for backwards compatibility before there was a transactional layer limit. +/// It is recommended to only use [`with_transaction`] to avoid users from generating too many +/// transactional layers. +pub fn with_transaction_unchecked(f: impl FnOnce() -> TransactionOutcome) -> R { + use sp_io::storage::{commit_transaction, rollback_transaction, start_transaction}; + use TransactionOutcome::*; + + let maybe_guard = transaction_level_tracker::inc_transaction_level(); + + if maybe_guard.is_err() { + log::warn!( + "The transactional layer limit has been reached, and new transactional layers are being + spawned with `with_transaction_unchecked`. This could be caused by someone trying to + attack your chain, and you should investigate usage of `with_transaction_unchecked` and + potentially migrate to `with_transaction`, which enforces a transactional limit.", + ); + } + + start_transaction(); match f() { Commit(res) => { @@ -1418,12 +1471,13 @@ pub fn storage_prefix(pallet_name: &[u8], storage_name: &[u8]) -> [u8; 32] { #[cfg(test)] mod test { use super::*; - use crate::{assert_ok, hash::Identity, Twox128}; + use crate::{assert_noop, assert_ok, hash::Identity, Twox128}; use bounded_vec::BoundedVec; use frame_support::traits::ConstU32; use generator::StorageValue as _; use sp_core::hashing::twox_128; use sp_io::TestExternalities; + use sp_runtime::DispatchResult; use weak_bounded_vec::WeakBoundedVec; #[test] @@ -1535,25 +1589,67 @@ mod test { } #[test] - #[should_panic(expected = "Require transaction not called within with_transaction")] - fn require_transaction_should_panic() { + fn is_transactional_should_return_false() { TestExternalities::default().execute_with(|| { - require_transaction(); + assert!(!is_transactional()); }); } #[test] - fn require_transaction_should_not_panic_in_with_transaction() { + fn is_transactional_should_not_error_in_with_transaction() { TestExternalities::default().execute_with(|| { - with_transaction(|| { - require_transaction(); - TransactionOutcome::Commit(()) - }); - - with_transaction(|| { - require_transaction(); - TransactionOutcome::Rollback(()) - }); + assert_ok!(with_transaction(|| -> TransactionOutcome { + assert!(is_transactional()); + TransactionOutcome::Commit(Ok(())) + })); + + assert_noop!( + with_transaction(|| -> TransactionOutcome { + assert!(is_transactional()); + TransactionOutcome::Rollback(Err("revert".into())) + }), + "revert" + ); + }); + } + + fn recursive_transactional(num: u32) -> DispatchResult { + if num == 0 { + return Ok(()) + } + + with_transaction(|| -> TransactionOutcome { + let res = recursive_transactional(num - 1); + TransactionOutcome::Commit(res) + }) + } + + #[test] + fn transaction_limit_should_work() { + TestExternalities::default().execute_with(|| { + assert_eq!(transaction_level_tracker::get_transaction_level(), 0); + + assert_ok!(with_transaction(|| -> TransactionOutcome { + assert_eq!(transaction_level_tracker::get_transaction_level(), 1); + TransactionOutcome::Commit(Ok(())) + })); + + assert_ok!(with_transaction(|| -> TransactionOutcome { + assert_eq!(transaction_level_tracker::get_transaction_level(), 1); + let res = with_transaction(|| -> TransactionOutcome { + assert_eq!(transaction_level_tracker::get_transaction_level(), 2); + TransactionOutcome::Commit(Ok(())) + }); + TransactionOutcome::Commit(res) + })); + + assert_ok!(recursive_transactional(255)); + assert_noop!( + recursive_transactional(256), + sp_runtime::TransactionalError::LimitReached + ); + + assert_eq!(transaction_level_tracker::get_transaction_level(), 0); }); } diff --git a/frame/support/test/tests/storage_transaction.rs b/frame/support/test/tests/storage_transaction.rs index 0f1c3a2e0c536..848a91a7f5a86 100644 --- a/frame/support/test/tests/storage_transaction.rs +++ b/frame/support/test/tests/storage_transaction.rs @@ -16,12 +16,13 @@ // limitations under the License. use frame_support::{ - assert_noop, assert_ok, + assert_noop, assert_ok, assert_storage_noop, dispatch::{DispatchError, DispatchResult}, storage::{with_transaction, TransactionOutcome::*}, transactional, StorageMap, StorageValue, }; use sp_io::TestExternalities; +use sp_runtime::TransactionOutcome; use sp_std::result; pub trait Config: frame_support_test::Config {} @@ -67,13 +68,13 @@ fn storage_transaction_basic_commit() { assert_eq!(Value::get(), 0); assert!(!Map::contains_key("val0")); - with_transaction(|| { + assert_ok!(with_transaction(|| -> TransactionOutcome { Value::set(99); Map::insert("val0", 99); assert_eq!(Value::get(), 99); assert_eq!(Map::get("val0"), 99); - Commit(()) - }); + Commit(Ok(())) + })); assert_eq!(Value::get(), 99); assert_eq!(Map::get("val0"), 99); @@ -86,13 +87,26 @@ fn storage_transaction_basic_rollback() { assert_eq!(Value::get(), 0); assert_eq!(Map::get("val0"), 0); - with_transaction(|| { - Value::set(99); - Map::insert("val0", 99); - assert_eq!(Value::get(), 99); - assert_eq!(Map::get("val0"), 99); - Rollback(()) - }); + assert_noop!( + with_transaction(|| -> TransactionOutcome { + Value::set(99); + Map::insert("val0", 99); + assert_eq!(Value::get(), 99); + assert_eq!(Map::get("val0"), 99); + Rollback(Err("revert".into())) + }), + "revert" + ); + + assert_storage_noop!(assert_ok!(with_transaction( + || -> TransactionOutcome { + Value::set(99); + Map::insert("val0", 99); + assert_eq!(Value::get(), 99); + assert_eq!(Map::get("val0"), 99); + Rollback(Ok(())) + } + ))); assert_eq!(Value::get(), 0); assert_eq!(Map::get("val0"), 0); @@ -105,32 +119,35 @@ fn storage_transaction_rollback_then_commit() { Value::set(1); Map::insert("val1", 1); - with_transaction(|| { + assert_ok!(with_transaction(|| -> TransactionOutcome { Value::set(2); Map::insert("val1", 2); Map::insert("val2", 2); - with_transaction(|| { - Value::set(3); - Map::insert("val1", 3); - Map::insert("val2", 3); - Map::insert("val3", 3); + assert_noop!( + with_transaction(|| -> TransactionOutcome { + Value::set(3); + Map::insert("val1", 3); + Map::insert("val2", 3); + Map::insert("val3", 3); - assert_eq!(Value::get(), 3); - assert_eq!(Map::get("val1"), 3); - assert_eq!(Map::get("val2"), 3); - assert_eq!(Map::get("val3"), 3); + assert_eq!(Value::get(), 3); + assert_eq!(Map::get("val1"), 3); + assert_eq!(Map::get("val2"), 3); + assert_eq!(Map::get("val3"), 3); - Rollback(()) - }); + Rollback(Err("revert".into())) + }), + "revert" + ); assert_eq!(Value::get(), 2); assert_eq!(Map::get("val1"), 2); assert_eq!(Map::get("val2"), 2); assert_eq!(Map::get("val3"), 0); - Commit(()) - }); + Commit(Ok(())) + })); assert_eq!(Value::get(), 2); assert_eq!(Map::get("val1"), 2); @@ -145,32 +162,35 @@ fn storage_transaction_commit_then_rollback() { Value::set(1); Map::insert("val1", 1); - with_transaction(|| { - Value::set(2); - Map::insert("val1", 2); - Map::insert("val2", 2); + assert_noop!( + with_transaction(|| -> TransactionOutcome { + Value::set(2); + Map::insert("val1", 2); + Map::insert("val2", 2); + + assert_ok!(with_transaction(|| -> TransactionOutcome { + Value::set(3); + Map::insert("val1", 3); + Map::insert("val2", 3); + Map::insert("val3", 3); - with_transaction(|| { - Value::set(3); - Map::insert("val1", 3); - Map::insert("val2", 3); - Map::insert("val3", 3); + assert_eq!(Value::get(), 3); + assert_eq!(Map::get("val1"), 3); + assert_eq!(Map::get("val2"), 3); + assert_eq!(Map::get("val3"), 3); + + Commit(Ok(())) + })); assert_eq!(Value::get(), 3); assert_eq!(Map::get("val1"), 3); assert_eq!(Map::get("val2"), 3); assert_eq!(Map::get("val3"), 3); - Commit(()) - }); - - assert_eq!(Value::get(), 3); - assert_eq!(Map::get("val1"), 3); - assert_eq!(Map::get("val2"), 3); - assert_eq!(Map::get("val3"), 3); - - Rollback(()) - }); + Rollback(Err("revert".into())) + }), + "revert" + ); assert_eq!(Value::get(), 1); assert_eq!(Map::get("val1"), 1); diff --git a/primitives/runtime/src/lib.rs b/primitives/runtime/src/lib.rs index 337fac5812aed..c09db5124cc1f 100644 --- a/primitives/runtime/src/lib.rs +++ b/primitives/runtime/src/lib.rs @@ -486,6 +486,31 @@ impl PartialEq for ModuleError { } } +/// Errors related to transactional storage layers. +#[derive(Eq, PartialEq, Clone, Copy, Encode, Decode, Debug, TypeInfo)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub enum TransactionalError { + /// Too many transactional layers have been spawned. + LimitReached, + /// A transactional layer was expected, but does not exist. + NoLayer, +} + +impl From for &'static str { + fn from(e: TransactionalError) -> &'static str { + match e { + TransactionalError::LimitReached => "Too many transactional layers have been spawned", + TransactionalError::NoLayer => "A transactional layer was expected, but does not exist", + } + } +} + +impl From for DispatchError { + fn from(e: TransactionalError) -> DispatchError { + Self::Transactional(e) + } +} + /// Reason why a dispatch call failed. #[derive(Eq, Clone, Copy, Encode, Decode, Debug, TypeInfo, PartialEq)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] @@ -512,6 +537,9 @@ pub enum DispatchError { Token(TokenError), /// An arithmetic error. Arithmetic(ArithmeticError), + /// The number of transactional layers has been reached, or we are not in a transactional + /// layer. + Transactional(TransactionalError), } /// Result of a `Dispatchable` which contains the `DispatchResult` and additional information about @@ -647,6 +675,7 @@ impl From for &'static str { DispatchError::TooManyConsumers => "Too many consumers", DispatchError::Token(e) => e.into(), DispatchError::Arithmetic(e) => e.into(), + DispatchError::Transactional(e) => e.into(), } } } @@ -685,6 +714,10 @@ impl traits::Printable for DispatchError { "Arithmetic error: ".print(); <&'static str>::from(*e).print(); }, + Self::Transactional(e) => { + "Transactional error: ".print(); + <&'static str>::from(*e).print(); + }, } } } From 753d73832ecbe6500c067441676b4247bcbb7cde Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 3 Apr 2022 19:34:13 +0000 Subject: [PATCH 080/484] Bump futures from 0.3.19 to 0.3.21 (#11133) Bumps [futures](https://github.com/rust-lang/futures-rs) from 0.3.19 to 0.3.21. - [Release notes](https://github.com/rust-lang/futures-rs/releases) - [Changelog](https://github.com/rust-lang/futures-rs/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-lang/futures-rs/compare/0.3.19...0.3.21) --- updated-dependencies: - dependency-name: futures dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 220 +++++++++--------- bin/node/bench/Cargo.toml | 2 +- bin/node/cli/Cargo.toml | 4 +- bin/node/executor/Cargo.toml | 2 +- bin/node/testing/Cargo.toml | 2 +- client/api/Cargo.toml | 2 +- client/authority-discovery/Cargo.toml | 2 +- client/basic-authorship/Cargo.toml | 2 +- client/beefy/rpc/Cargo.toml | 2 +- client/cli/Cargo.toml | 2 +- client/consensus/aura/Cargo.toml | 2 +- client/consensus/babe/Cargo.toml | 2 +- client/consensus/babe/rpc/Cargo.toml | 2 +- client/consensus/common/Cargo.toml | 2 +- client/consensus/manual-seal/Cargo.toml | 2 +- client/consensus/pow/Cargo.toml | 2 +- client/consensus/slots/Cargo.toml | 2 +- client/finality-grandpa/Cargo.toml | 2 +- client/informant/Cargo.toml | 2 +- client/network-gossip/Cargo.toml | 2 +- client/network/Cargo.toml | 2 +- client/network/test/Cargo.toml | 2 +- client/offchain/Cargo.toml | 2 +- client/peerset/Cargo.toml | 2 +- client/rpc-api/Cargo.toml | 2 +- client/rpc-servers/Cargo.toml | 2 +- client/rpc/Cargo.toml | 2 +- client/service/Cargo.toml | 2 +- client/service/test/Cargo.toml | 2 +- client/telemetry/Cargo.toml | 2 +- client/transaction-pool/Cargo.toml | 2 +- client/transaction-pool/api/Cargo.toml | 2 +- client/utils/Cargo.toml | 2 +- primitives/api/test/Cargo.toml | 2 +- primitives/blockchain/Cargo.toml | 2 +- primitives/consensus/common/Cargo.toml | 4 +- primitives/core/Cargo.toml | 2 +- primitives/inherents/Cargo.toml | 2 +- primitives/io/Cargo.toml | 2 +- primitives/keystore/Cargo.toml | 2 +- test-utils/client/Cargo.toml | 2 +- test-utils/runtime/Cargo.toml | 2 +- test-utils/runtime/client/Cargo.toml | 2 +- .../runtime/transaction-pool/Cargo.toml | 2 +- utils/frame/rpc/support/Cargo.toml | 2 +- utils/frame/rpc/system/Cargo.toml | 2 +- 46 files changed, 157 insertions(+), 157 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 03a04cedcbf27..6fb6f5814565a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -483,7 +483,7 @@ version = "4.0.0-dev" dependencies = [ "beefy-primitives", "fnv", - "futures 0.3.19", + "futures 0.3.21", "futures-timer", "hex", "log 0.4.14", @@ -525,7 +525,7 @@ version = "4.0.0-dev" dependencies = [ "beefy-gadget", "beefy-primitives", - "futures 0.3.19", + "futures 0.3.21", "jsonrpc-core", "jsonrpc-core-client", "jsonrpc-derive", @@ -1312,7 +1312,7 @@ dependencies = [ "clap 2.34.0", "criterion-plot", "csv", - "futures 0.3.19", + "futures 0.3.21", "itertools", "lazy_static", "num-traits", @@ -1967,7 +1967,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e43f2f1833d64e33f15592464d6fdd70f349dda7b1a53088eb83cd94014008c5" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", ] [[package]] @@ -2027,7 +2027,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9def033d8505edf199f6a5d07aa7e6d2d6185b164293b77f0efd108f4f3e11d" dependencies = [ "either", - "futures 0.3.19", + "futures 0.3.21", "futures-timer", "log 0.4.14", "num-traits", @@ -2480,9 +2480,9 @@ checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" [[package]] name = "futures" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28560757fe2bb34e79f907794bb6b22ae8b0e5c669b638a1132f2592b19035b4" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" dependencies = [ "futures-channel", "futures-core", @@ -2495,9 +2495,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3dda0b6588335f360afc675d0564c17a77a2bda81ca178a4b6081bd86c7f0b" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" dependencies = [ "futures-core", "futures-sink", @@ -2505,15 +2505,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" [[package]] name = "futures-executor" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29d6d2ff5bb10fb95c85b8ce46538a2e5f5e7fdc755623a7d4529ab8a4ed9d2a" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" dependencies = [ "futures-core", "futures-task", @@ -2523,9 +2523,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f9d34af5a1aac6fb380f735fe510746c38067c5bf16c7fd250280503c971b2" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" [[package]] name = "futures-lite" @@ -2544,9 +2544,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbd947adfffb0efc70599b3ddcf7b5597bb5fa9e245eb99f62b3a5f7bb8bd3c" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" dependencies = [ "proc-macro2", "quote", @@ -2566,15 +2566,15 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3055baccb68d74ff6480350f8d6eb8fcfa3aa11bdc1a1ae3afdd0514617d508" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" [[package]] name = "futures-task" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" [[package]] name = "futures-timer" @@ -2584,9 +2584,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b5cf40b47a271f77a8b1bec03ca09044d99d2372c0de244e66430761127164" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" dependencies = [ "futures 0.1.31", "futures-channel", @@ -3103,7 +3103,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a6d52908d4ea4ab2bc22474ba149bf1011c8e2c3ebc1ff593ae28ac44f494b6" dependencies = [ "async-io", - "futures 0.3.19", + "futures 0.3.21", "futures-lite", "if-addrs", "ipnet", @@ -3258,7 +3258,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2b99d4207e2a04fb4581746903c2bb7eb376f88de9c699d0f3e10feeac0cd3a" dependencies = [ "derive_more", - "futures 0.3.19", + "futures 0.3.21", "hyper 0.14.16", "hyper-tls", "jsonrpc-core", @@ -3277,7 +3277,7 @@ version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14f7f76aef2d054868398427f6c54943cf3d1caa9a7ec7d0c38d69df97a965eb" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "futures-executor", "futures-util", "log 0.4.14", @@ -3292,7 +3292,7 @@ version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b51da17abecbdab3e3d4f26b01c5ec075e88d3abe3ab3b05dc9aa69392764ec0" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "jsonrpc-client-transports", ] @@ -3314,7 +3314,7 @@ version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1dea6e07251d9ce6a552abfb5d7ad6bc290a4596c8dcc3d795fae2bbdc1f3ff" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "hyper 0.14.16", "jsonrpc-core", "jsonrpc-server-utils", @@ -3330,7 +3330,7 @@ version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "382bb0206323ca7cda3dcd7e245cea86d37d02457a02a975e3378fb149a48845" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "jsonrpc-core", "jsonrpc-server-utils", "log 0.4.14", @@ -3345,7 +3345,7 @@ version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240f87695e6c6f62fb37f05c02c04953cf68d6408b8c1c89de85c7a0125b1011" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "jsonrpc-core", "lazy_static", "log 0.4.14", @@ -3361,7 +3361,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4fdea130485b572c39a460d50888beb00afb3e35de23ccd7fad8ff19f0e0d4" dependencies = [ "bytes 1.1.0", - "futures 0.3.19", + "futures 0.3.21", "globset", "jsonrpc-core", "lazy_static", @@ -3378,7 +3378,7 @@ version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f892c7d766369475ab7b0669f417906302d7c0fb521285c0a0c92e52e7c8e946" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "jsonrpc-core", "jsonrpc-server-utils", "log 0.4.14", @@ -3416,7 +3416,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3303cdf246e6ab76e2866fb3d9acb6c76a068b1b28bd923a1b7a8122257ad7b5" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "http", "jsonrpsee-core", "jsonrpsee-types 0.8.0", @@ -3519,7 +3519,7 @@ dependencies = [ "arrayvec 0.7.1", "async-trait", "fnv", - "futures 0.3.19", + "futures 0.3.21", "http", "jsonrpsee-types 0.4.1", "log 0.4.14", @@ -3718,7 +3718,7 @@ checksum = "3bec54343492ba5940a6c555e512c6721139835d28c59bc22febece72dfd0d9d" dependencies = [ "atomic", "bytes 1.1.0", - "futures 0.3.19", + "futures 0.3.21", "lazy_static", "libp2p-core", "libp2p-deflate", @@ -3762,7 +3762,7 @@ dependencies = [ "ed25519-dalek", "either", "fnv", - "futures 0.3.19", + "futures 0.3.21", "futures-timer", "lazy_static", "libsecp256k1", @@ -3792,7 +3792,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51a800adb195f33de63f4b17b63fe64cfc23bf2c6a0d3d0d5321328664e65197" dependencies = [ "flate2", - "futures 0.3.19", + "futures 0.3.21", "libp2p-core", ] @@ -3803,7 +3803,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb8f89d15cb6e3c5bc22afff7513b11bab7856f2872d3cfba86f7f63a06bc498" dependencies = [ "async-std-resolver", - "futures 0.3.19", + "futures 0.3.21", "libp2p-core", "log 0.4.14", "smallvec 1.8.0", @@ -3818,7 +3818,7 @@ checksum = "aab3d7210901ea51b7bae2b581aa34521797af8c4ec738c980bda4a06434067f" dependencies = [ "cuckoofilter", "fnv", - "futures 0.3.19", + "futures 0.3.21", "libp2p-core", "libp2p-swarm", "log 0.4.14", @@ -3839,7 +3839,7 @@ dependencies = [ "byteorder", "bytes 1.1.0", "fnv", - "futures 0.3.19", + "futures 0.3.21", "hex_fmt", "libp2p-core", "libp2p-swarm", @@ -3860,7 +3860,7 @@ version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cca1275574183f288ff8b72d535d5ffa5ea9292ef7829af8b47dcb197c7b0dcd" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "libp2p-core", "libp2p-swarm", "log 0.4.14", @@ -3882,7 +3882,7 @@ dependencies = [ "bytes 1.1.0", "either", "fnv", - "futures 0.3.19", + "futures 0.3.21", "libp2p-core", "libp2p-swarm", "log 0.4.14", @@ -3906,7 +3906,7 @@ dependencies = [ "async-io", "data-encoding", "dns-parser", - "futures 0.3.19", + "futures 0.3.21", "if-watch", "lazy_static", "libp2p-core", @@ -3940,7 +3940,7 @@ checksum = "7f2cd64ef597f40e14bfce0497f50ecb63dd6d201c61796daeb4227078834fbf" dependencies = [ "asynchronous-codec 0.6.0", "bytes 1.1.0", - "futures 0.3.19", + "futures 0.3.21", "libp2p-core", "log 0.4.14", "nohash-hasher", @@ -3958,7 +3958,7 @@ checksum = "a8772c7a99088221bb7ca9c5c0574bf55046a7ab4c319f3619b275f28c8fb87a" dependencies = [ "bytes 1.1.0", "curve25519-dalek 3.0.2", - "futures 0.3.19", + "futures 0.3.21", "lazy_static", "libp2p-core", "log 0.4.14", @@ -3978,7 +3978,7 @@ version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80ef7b0ec5cf06530d9eb6cf59ae49d46a2c45663bde31c25a12f682664adbcf" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "libp2p-core", "libp2p-swarm", "log 0.4.14", @@ -3995,7 +3995,7 @@ checksum = "5fba1a6ff33e4a274c89a3b1d78b9f34f32af13265cc5c46c16938262d4e945a" dependencies = [ "asynchronous-codec 0.6.0", "bytes 1.1.0", - "futures 0.3.19", + "futures 0.3.21", "libp2p-core", "log 0.4.14", "prost", @@ -4010,7 +4010,7 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f1a458bbda880107b5b36fcb9b5a1ef0c329685da0e203ed692a8ebe64cc92c" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "log 0.4.14", "pin-project 1.0.10", "rand 0.7.3", @@ -4026,7 +4026,7 @@ checksum = "2852b61c90fa8ce3c8fcc2aba76e6cefc20d648f9df29157d6b3a916278ef3e3" dependencies = [ "asynchronous-codec 0.6.0", "bytes 1.1.0", - "futures 0.3.19", + "futures 0.3.21", "futures-timer", "libp2p-core", "libp2p-swarm", @@ -4049,7 +4049,7 @@ checksum = "14a6d2b9e7677eff61dc3d2854876aaf3976d84a01ef6664b610c77a0c9407c5" dependencies = [ "asynchronous-codec 0.6.0", "bimap", - "futures 0.3.19", + "futures 0.3.21", "libp2p-core", "libp2p-swarm", "log 0.4.14", @@ -4071,7 +4071,7 @@ checksum = "a877a4ced6d46bf84677e1974e8cf61fb434af73b2e96fb48d6cb6223a4634d8" dependencies = [ "async-trait", "bytes 1.1.0", - "futures 0.3.19", + "futures 0.3.21", "libp2p-core", "libp2p-swarm", "log 0.4.14", @@ -4089,7 +4089,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f5184a508f223bc100a12665517773fb8730e9f36fc09eefb670bf01b107ae9" dependencies = [ "either", - "futures 0.3.19", + "futures 0.3.21", "libp2p-core", "log 0.4.14", "rand 0.7.3", @@ -4115,7 +4115,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7399c5b6361ef525d41c11fcf51635724f832baf5819b30d3d873eabb4fbae4b" dependencies = [ "async-io", - "futures 0.3.19", + "futures 0.3.21", "futures-timer", "if-watch", "ipnet", @@ -4132,7 +4132,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8b7563e46218165dfd60f64b96f7ce84590d75f53ecbdc74a7dd01450dc5973" dependencies = [ "async-std", - "futures 0.3.19", + "futures 0.3.21", "libp2p-core", "log 0.4.14", ] @@ -4143,7 +4143,7 @@ version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1008a302b73c5020251f9708c653f5ed08368e530e247cc9cd2f109ff30042cf" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "js-sys", "libp2p-core", "parity-send-wrapper", @@ -4158,7 +4158,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22e12df82d1ed64969371a9e65ea92b91064658604cc2576c2757f18ead9a1cf" dependencies = [ "either", - "futures 0.3.19", + "futures 0.3.21", "futures-rustls", "libp2p-core", "log 0.4.14", @@ -4175,7 +4175,7 @@ version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e7362abb8867d7187e7e93df17f460d554c997fc5c8ac57dc1259057f6889af" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "libp2p-core", "parking_lot 0.11.2", "thiserror", @@ -4696,7 +4696,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d91ec0a2440aaff5f78ec35631a7027d50386c6163aa975f7caa0d5da4b6ff8" dependencies = [ "bytes 1.1.0", - "futures 0.3.19", + "futures 0.3.21", "log 0.4.14", "pin-project 1.0.10", "smallvec 1.8.0", @@ -4800,7 +4800,7 @@ dependencies = [ "clap 3.1.6", "derive_more", "fs_extra", - "futures 0.3.19", + "futures 0.3.21", "hash-db", "hex", "kvdb", @@ -4842,7 +4842,7 @@ dependencies = [ "frame-benchmarking-cli", "frame-system", "frame-system-rpc-runtime-api", - "futures 0.3.19", + "futures 0.3.21", "hex-literal", "jsonrpsee-ws-client 0.4.1", "log 0.4.14", @@ -4921,7 +4921,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "futures 0.3.19", + "futures 0.3.21", "node-primitives", "node-runtime", "node-testing", @@ -5182,7 +5182,7 @@ version = "3.0.0-dev" dependencies = [ "frame-system", "fs_extra", - "futures 0.3.19", + "futures 0.3.21", "log 0.4.14", "node-executor", "node-primitives", @@ -6766,7 +6766,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9981e32fb75e004cc148f5fb70342f393830e0a4aa62e3cc93b50976218d42b6" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "libc", "log 0.4.14", "rand 0.7.3", @@ -7975,7 +7975,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4da5fcb054c46f5a5dff833b129285a93d3f0179531735e6c866e8cc307d2020" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "pin-project 0.4.27", "static_assertions", ] @@ -8034,7 +8034,7 @@ name = "sc-authority-discovery" version = "0.10.0-dev" dependencies = [ "async-trait", - "futures 0.3.19", + "futures 0.3.21", "futures-timer", "ip_network", "libp2p", @@ -8062,7 +8062,7 @@ dependencies = [ name = "sc-basic-authorship" version = "0.10.0-dev" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "futures-timer", "log 0.4.14", "parity-scale-codec", @@ -8132,7 +8132,7 @@ dependencies = [ "chrono", "clap 3.1.6", "fdlimit", - "futures 0.3.19", + "futures 0.3.21", "hex", "libp2p", "log 0.4.14", @@ -8168,7 +8168,7 @@ name = "sc-client-api" version = "4.0.0-dev" dependencies = [ "fnv", - "futures 0.3.19", + "futures 0.3.21", "hash-db", "log 0.4.14", "parity-scale-codec", @@ -8226,7 +8226,7 @@ name = "sc-consensus" version = "0.10.0-dev" dependencies = [ "async-trait", - "futures 0.3.19", + "futures 0.3.21", "futures-timer", "libp2p", "log 0.4.14", @@ -8250,7 +8250,7 @@ name = "sc-consensus-aura" version = "0.10.0-dev" dependencies = [ "async-trait", - "futures 0.3.19", + "futures 0.3.21", "log 0.4.14", "parity-scale-codec", "parking_lot 0.12.0", @@ -8288,7 +8288,7 @@ version = "0.10.0-dev" dependencies = [ "async-trait", "fork-tree", - "futures 0.3.19", + "futures 0.3.21", "log 0.4.14", "merlin", "num-bigint", @@ -8336,7 +8336,7 @@ dependencies = [ name = "sc-consensus-babe-rpc" version = "0.10.0-dev" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "jsonrpc-core", "jsonrpc-core-client", "jsonrpc-derive", @@ -8379,7 +8379,7 @@ version = "0.10.0-dev" dependencies = [ "assert_matches", "async-trait", - "futures 0.3.19", + "futures 0.3.21", "jsonrpc-core", "jsonrpc-core-client", "jsonrpc-derive", @@ -8417,7 +8417,7 @@ name = "sc-consensus-pow" version = "0.10.0-dev" dependencies = [ "async-trait", - "futures 0.3.19", + "futures 0.3.21", "futures-timer", "log 0.4.14", "parity-scale-codec", @@ -8441,7 +8441,7 @@ name = "sc-consensus-slots" version = "0.10.0-dev" dependencies = [ "async-trait", - "futures 0.3.19", + "futures 0.3.21", "futures-timer", "log 0.4.14", "parity-scale-codec", @@ -8573,7 +8573,7 @@ dependencies = [ "dyn-clone", "finality-grandpa", "fork-tree", - "futures 0.3.19", + "futures 0.3.21", "futures-timer", "hex", "log 0.4.14", @@ -8615,7 +8615,7 @@ name = "sc-finality-grandpa-rpc" version = "0.10.0-dev" dependencies = [ "finality-grandpa", - "futures 0.3.19", + "futures 0.3.21", "jsonrpc-core", "jsonrpc-core-client", "jsonrpc-derive", @@ -8642,7 +8642,7 @@ name = "sc-informant" version = "0.10.0-dev" dependencies = [ "ansi_term", - "futures 0.3.19", + "futures 0.3.21", "futures-timer", "log 0.4.14", "parity-util-mem", @@ -8682,7 +8682,7 @@ dependencies = [ "either", "fnv", "fork-tree", - "futures 0.3.19", + "futures 0.3.21", "futures-timer", "hex", "ip_network", @@ -8730,7 +8730,7 @@ version = "0.10.0-dev" dependencies = [ "ahash", "async-std", - "futures 0.3.19", + "futures 0.3.21", "futures-timer", "libp2p", "log 0.4.14", @@ -8749,7 +8749,7 @@ version = "0.8.0" dependencies = [ "async-std", "async-trait", - "futures 0.3.19", + "futures 0.3.21", "futures-timer", "libp2p", "log 0.4.14", @@ -8776,7 +8776,7 @@ version = "4.0.0-dev" dependencies = [ "bytes 1.1.0", "fnv", - "futures 0.3.19", + "futures 0.3.21", "futures-timer", "hex", "hyper 0.14.16", @@ -8810,7 +8810,7 @@ dependencies = [ name = "sc-peerset" version = "4.0.0-dev" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "libp2p", "log 0.4.14", "rand 0.7.3", @@ -8832,7 +8832,7 @@ name = "sc-rpc" version = "4.0.0-dev" dependencies = [ "assert_matches", - "futures 0.3.19", + "futures 0.3.21", "hash-db", "jsonrpc-core", "jsonrpc-pubsub", @@ -8868,7 +8868,7 @@ dependencies = [ name = "sc-rpc-api" version = "0.10.0-dev" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "jsonrpc-core", "jsonrpc-core-client", "jsonrpc-derive", @@ -8893,7 +8893,7 @@ dependencies = [ name = "sc-rpc-server" version = "4.0.0-dev" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "jsonrpc-core", "jsonrpc-http-server", "jsonrpc-ipc-server", @@ -8927,7 +8927,7 @@ dependencies = [ "async-trait", "directories", "exit-future", - "futures 0.3.19", + "futures 0.3.21", "futures-timer", "hash-db", "jsonrpc-core", @@ -8990,7 +8990,7 @@ name = "sc-service-test" version = "2.0.0" dependencies = [ "fdlimit", - "futures 0.3.19", + "futures 0.3.21", "hex", "hex-literal", "log 0.4.14", @@ -9059,7 +9059,7 @@ name = "sc-telemetry" version = "4.0.0-dev" dependencies = [ "chrono", - "futures 0.3.19", + "futures 0.3.21", "libp2p", "log 0.4.14", "parking_lot 0.12.0", @@ -9118,7 +9118,7 @@ version = "4.0.0-dev" dependencies = [ "assert_matches", "criterion", - "futures 0.3.19", + "futures 0.3.21", "futures-timer", "hex", "linked-hash-map", @@ -9150,7 +9150,7 @@ dependencies = [ name = "sc-transaction-pool-api" version = "4.0.0-dev" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "log 0.4.14", "serde", "sp-blockchain", @@ -9162,7 +9162,7 @@ dependencies = [ name = "sc-utils" version = "4.0.0-dev" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "futures-timer", "lazy_static", "log 0.4.14", @@ -9639,7 +9639,7 @@ checksum = "b5c71ed3d54db0a699f4948e1bb3e45b450fa31fe602621dee6680361d569c88" dependencies = [ "base64 0.12.3", "bytes 0.5.6", - "futures 0.3.19", + "futures 0.3.21", "httparse", "log 0.4.14", "rand 0.7.3", @@ -9655,7 +9655,7 @@ dependencies = [ "base64 0.13.0", "bytes 1.1.0", "flate2", - "futures 0.3.19", + "futures 0.3.21", "httparse", "log 0.4.14", "rand 0.8.4", @@ -9695,7 +9695,7 @@ name = "sp-api-test" version = "2.0.1" dependencies = [ "criterion", - "futures 0.3.19", + "futures 0.3.21", "log 0.4.14", "parity-scale-codec", "rustversion", @@ -9800,7 +9800,7 @@ dependencies = [ name = "sp-blockchain" version = "4.0.0-dev" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "log 0.4.14", "lru 0.7.3", "parity-scale-codec", @@ -9818,7 +9818,7 @@ name = "sp-consensus" version = "0.10.0-dev" dependencies = [ "async-trait", - "futures 0.3.19", + "futures 0.3.21", "futures-timer", "log 0.4.14", "parity-scale-codec", @@ -9917,7 +9917,7 @@ dependencies = [ "criterion", "dyn-clonable", "ed25519-dalek", - "futures 0.3.19", + "futures 0.3.21", "hash-db", "hash256-std-hasher", "hex", @@ -10028,7 +10028,7 @@ name = "sp-inherents" version = "4.0.0-dev" dependencies = [ "async-trait", - "futures 0.3.19", + "futures 0.3.21", "impl-trait-for-tuples", "parity-scale-codec", "sp-core", @@ -10041,7 +10041,7 @@ dependencies = [ name = "sp-io" version = "6.0.0" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "hash-db", "libsecp256k1", "log 0.4.14", @@ -10076,7 +10076,7 @@ name = "sp-keystore" version = "0.12.0" dependencies = [ "async-trait", - "futures 0.3.19", + "futures 0.3.21", "merlin", "parity-scale-codec", "parking_lot 0.12.0", @@ -10599,7 +10599,7 @@ version = "3.0.0" dependencies = [ "frame-support", "frame-system", - "futures 0.3.19", + "futures 0.3.21", "jsonrpc-client-transports", "parity-scale-codec", "sc-rpc-api", @@ -10614,7 +10614,7 @@ name = "substrate-frame-rpc-system" version = "4.0.0-dev" dependencies = [ "frame-system-rpc-runtime-api", - "futures 0.3.19", + "futures 0.3.21", "jsonrpc-core", "jsonrpc-core-client", "jsonrpc-derive", @@ -10673,7 +10673,7 @@ name = "substrate-test-client" version = "2.0.1" dependencies = [ "async-trait", - "futures 0.3.19", + "futures 0.3.21", "hex", "parity-scale-codec", "sc-client-api", @@ -10702,7 +10702,7 @@ dependencies = [ "frame-support", "frame-system", "frame-system-rpc-runtime-api", - "futures 0.3.19", + "futures 0.3.21", "log 0.4.14", "memory-db", "pallet-babe", @@ -10744,7 +10744,7 @@ dependencies = [ name = "substrate-test-runtime-client" version = "2.0.0" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "parity-scale-codec", "sc-block-builder", "sc-client-api", @@ -10762,7 +10762,7 @@ dependencies = [ name = "substrate-test-runtime-transaction-pool" version = "2.0.0" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "parity-scale-codec", "parking_lot 0.12.0", "sc-transaction-pool", @@ -10777,7 +10777,7 @@ dependencies = [ name = "substrate-test-utils" version = "4.0.0-dev" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "sc-service", "substrate-test-utils-derive", "tokio", @@ -11809,7 +11809,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "js-sys", "parking_lot 0.11.2", "pin-utils", @@ -12465,7 +12465,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7d9028f208dd5e63c614be69f115c1b53cacc1111437d4c765185856666c107" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "log 0.4.14", "nohash-hasher", "parking_lot 0.11.2", diff --git a/bin/node/bench/Cargo.toml b/bin/node/bench/Cargo.toml index 7bdf46e2b6600..45a959ac16b14 100644 --- a/bin/node/bench/Cargo.toml +++ b/bin/node/bench/Cargo.toml @@ -39,4 +39,4 @@ parity-util-mem = { version = "0.11.0", default-features = false, features = ["p parity-db = { version = "0.3" } sc-transaction-pool = { version = "4.0.0-dev", path = "../../../client/transaction-pool" } sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../../client/transaction-pool/api" } -futures = { version = "0.3.19", features = ["thread-pool"] } +futures = { version = "0.3.21", features = ["thread-pool"] } diff --git a/bin/node/cli/Cargo.toml b/bin/node/cli/Cargo.toml index a1aa695bf3a15..d25c1b1f0bf81 100644 --- a/bin/node/cli/Cargo.toml +++ b/bin/node/cli/Cargo.toml @@ -37,7 +37,7 @@ crate-type = ["cdylib", "rlib"] clap = { version = "3.1.6", features = ["derive"], optional = true } codec = { package = "parity-scale-codec", version = "3.0.0" } serde = { version = "1.0.136", features = ["derive"] } -futures = "0.3.19" +futures = "0.3.21" hex-literal = "0.3.4" log = "0.4.8" rand = "0.8" @@ -116,7 +116,7 @@ sc-service-test = { version = "2.0.0", path = "../../../client/service/test" } sc-block-builder = { version = "0.10.0-dev", path = "../../../client/block-builder" } sp-tracing = { version = "5.0.0", path = "../../../primitives/tracing" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -futures = "0.3.19" +futures = "0.3.21" tempfile = "3.1.0" assert_cmd = "2.0.2" nix = "0.23" diff --git a/bin/node/executor/Cargo.toml b/bin/node/executor/Cargo.toml index 21862ecf81b5d..3425a0df65d9b 100644 --- a/bin/node/executor/Cargo.toml +++ b/bin/node/executor/Cargo.toml @@ -40,7 +40,7 @@ sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } sp-externalities = { version = "0.12.0", path = "../../../primitives/externalities" } sp-keyring = { version = "6.0.0", path = "../../../primitives/keyring" } wat = "1.0" -futures = "0.3.19" +futures = "0.3.21" [features] wasmtime = ["sc-executor/wasmtime"] diff --git a/bin/node/testing/Cargo.toml b/bin/node/testing/Cargo.toml index 36a25ab7d4a7a..02d13f0c64718 100644 --- a/bin/node/testing/Cargo.toml +++ b/bin/node/testing/Cargo.toml @@ -48,4 +48,4 @@ sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" log = "0.4.8" tempfile = "3.1.0" fs_extra = "1" -futures = "0.3.19" +futures = "0.3.21" diff --git a/client/api/Cargo.toml b/client/api/Cargo.toml index 3d1802c379f04..d209a311f45b9 100644 --- a/client/api/Cargo.toml +++ b/client/api/Cargo.toml @@ -21,7 +21,7 @@ sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/comm sc-executor = { version = "0.10.0-dev", path = "../executor" } sp-externalities = { version = "0.12.0", path = "../../primitives/externalities" } fnv = "1.0.6" -futures = "0.3.19" +futures = "0.3.21" hash-db = { version = "0.15.2", default-features = false } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } log = "0.4.8" diff --git a/client/authority-discovery/Cargo.toml b/client/authority-discovery/Cargo.toml index d866bafb29dcf..ba432d073698a 100644 --- a/client/authority-discovery/Cargo.toml +++ b/client/authority-discovery/Cargo.toml @@ -20,7 +20,7 @@ prost-build = "0.9" async-trait = "0.1" codec = { package = "parity-scale-codec", default-features = false, version = "3.0.0" } thiserror = "1.0" -futures = "0.3.19" +futures = "0.3.21" futures-timer = "3.0.1" ip_network = "0.4.1" libp2p = { version = "0.40.0", default-features = false, features = ["kad"] } diff --git a/client/basic-authorship/Cargo.toml b/client/basic-authorship/Cargo.toml index 4fd140835525e..20560d4c834e7 100644 --- a/client/basic-authorship/Cargo.toml +++ b/client/basic-authorship/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } -futures = "0.3.19" +futures = "0.3.21" futures-timer = "3.0.1" log = "0.4.8" prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus", version = "0.10.0-dev"} diff --git a/client/beefy/rpc/Cargo.toml b/client/beefy/rpc/Cargo.toml index ebbb9527f9367..47f2558c5ee71 100644 --- a/client/beefy/rpc/Cargo.toml +++ b/client/beefy/rpc/Cargo.toml @@ -8,7 +8,7 @@ repository = "https://github.com/paritytech/substrate" description = "RPC for the BEEFY Client gadget for substrate" [dependencies] -futures = "0.3.19" +futures = "0.3.21" log = "0.4" parking_lot = "0.12.0" thiserror = "1.0" diff --git a/client/cli/Cargo.toml b/client/cli/Cargo.toml index 7725e25c0b815..1fabbca46968e 100644 --- a/client/cli/Cargo.toml +++ b/client/cli/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] chrono = "0.4.10" clap = { version = "3.1.6", features = ["derive"] } fdlimit = "0.2.1" -futures = "0.3.19" +futures = "0.3.21" hex = "0.4.2" libp2p = "0.40.0" log = "0.4.11" diff --git a/client/consensus/aura/Cargo.toml b/client/consensus/aura/Cargo.toml index 2ad61f2bc48d9..ac90d35dbce3c 100644 --- a/client/consensus/aura/Cargo.toml +++ b/client/consensus/aura/Cargo.toml @@ -23,7 +23,7 @@ sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/c sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } sp-consensus-slots = { version = "0.10.0-dev", path = "../../../primitives/consensus/slots" } thiserror = "1.0" -futures = "0.3.19" +futures = "0.3.21" sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } log = "0.4.8" sp-core = { version = "6.0.0", path = "../../../primitives/core" } diff --git a/client/consensus/babe/Cargo.toml b/client/consensus/babe/Cargo.toml index a135807d707e0..20cc359849f52 100644 --- a/client/consensus/babe/Cargo.toml +++ b/client/consensus/babe/Cargo.toml @@ -43,7 +43,7 @@ sc-consensus-slots = { version = "0.10.0-dev", path = "../slots" } sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } fork-tree = { version = "3.0.0", path = "../../../utils/fork-tree" } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus", version = "0.10.0-dev" } -futures = "0.3.19" +futures = "0.3.21" parking_lot = "0.12.0" log = "0.4.8" schnorrkel = { version = "0.9.1", features = ["preaudit_deprecated"] } diff --git a/client/consensus/babe/rpc/Cargo.toml b/client/consensus/babe/rpc/Cargo.toml index ee6549480a6d2..cce4544f09705 100644 --- a/client/consensus/babe/rpc/Cargo.toml +++ b/client/consensus/babe/rpc/Cargo.toml @@ -23,7 +23,7 @@ serde = { version = "1.0.136", features = ["derive"] } sp-blockchain = { version = "4.0.0-dev", path = "../../../../primitives/blockchain" } sp-runtime = { version = "6.0.0", path = "../../../../primitives/runtime" } sc-consensus-epochs = { version = "0.10.0-dev", path = "../../epochs" } -futures = "0.3.19" +futures = "0.3.21" thiserror = "1.0" sp-api = { version = "4.0.0-dev", path = "../../../../primitives/api" } sp-consensus = { version = "0.10.0-dev", path = "../../../../primitives/consensus/common" } diff --git a/client/consensus/common/Cargo.toml b/client/consensus/common/Cargo.toml index 3c640a2363b6a..4bd5cff457f5c 100644 --- a/client/consensus/common/Cargo.toml +++ b/client/consensus/common/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] thiserror = "1.0.30" libp2p = { version = "0.40.0", default-features = false } log = "0.4.8" -futures = { version = "0.3.19", features = ["thread-pool"] } +futures = { version = "0.3.21", features = ["thread-pool"] } futures-timer = "3.0.1" sc-client-api = { version = "4.0.0-dev", path = "../../api" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } diff --git a/client/consensus/manual-seal/Cargo.toml b/client/consensus/manual-seal/Cargo.toml index 62650d50ce4ac..360cfe862f875 100644 --- a/client/consensus/manual-seal/Cargo.toml +++ b/client/consensus/manual-seal/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] thiserror = "1.0" -futures = "0.3.19" +futures = "0.3.21" jsonrpc-core = "18.0.0" jsonrpc-core-client = "18.0.0" jsonrpc-derive = "18.0.0" diff --git a/client/consensus/pow/Cargo.toml b/client/consensus/pow/Cargo.toml index 553ef60e8145c..4ddbf0c0ead7f 100644 --- a/client/consensus/pow/Cargo.toml +++ b/client/consensus/pow/Cargo.toml @@ -25,7 +25,7 @@ sp-consensus-pow = { version = "0.10.0-dev", path = "../../../primitives/consens sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } log = "0.4.8" -futures = "0.3.19" +futures = "0.3.21" futures-timer = "3.0.1" parking_lot = "0.12.0" thiserror = "1.0" diff --git a/client/consensus/slots/Cargo.toml b/client/consensus/slots/Cargo.toml index 07e0c291f4a37..2884f8443e30e 100644 --- a/client/consensus/slots/Cargo.toml +++ b/client/consensus/slots/Cargo.toml @@ -27,7 +27,7 @@ sc-telemetry = { version = "4.0.0-dev", path = "../../telemetry" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } sp-timestamp = { version = "4.0.0-dev", path = "../../../primitives/timestamp" } -futures = "0.3.19" +futures = "0.3.21" futures-timer = "3.0.1" log = "0.4.11" thiserror = "1.0.30" diff --git a/client/finality-grandpa/Cargo.toml b/client/finality-grandpa/Cargo.toml index b8b41ba703655..58dc22192c0c1 100644 --- a/client/finality-grandpa/Cargo.toml +++ b/client/finality-grandpa/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] thiserror = "1.0" dyn-clone = "1.0" fork-tree = { version = "3.0.0", path = "../../utils/fork-tree" } -futures = "0.3.19" +futures = "0.3.21" futures-timer = "3.0.1" hex = "0.4.2" log = "0.4.8" diff --git a/client/informant/Cargo.toml b/client/informant/Cargo.toml index dba0555c68969..e58763a4ebcc9 100644 --- a/client/informant/Cargo.toml +++ b/client/informant/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] ansi_term = "0.12.1" -futures = "0.3.19" +futures = "0.3.21" futures-timer = "3.0.1" log = "0.4.8" parity-util-mem = { version = "0.11.0", default-features = false, features = ["primitive-types"] } diff --git a/client/network-gossip/Cargo.toml b/client/network-gossip/Cargo.toml index c8410d6d18783..51f5b658402ef 100644 --- a/client/network-gossip/Cargo.toml +++ b/client/network-gossip/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] -futures = "0.3.19" +futures = "0.3.21" futures-timer = "3.0.1" libp2p = { version = "0.40.0", default-features = false } log = "0.4.8" diff --git a/client/network/Cargo.toml b/client/network/Cargo.toml index cd33830c1efe0..be69663b9a544 100644 --- a/client/network/Cargo.toml +++ b/client/network/Cargo.toml @@ -27,7 +27,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", features = [ either = "1.5.3" fnv = "1.0.6" fork-tree = { version = "3.0.0", path = "../../utils/fork-tree" } -futures = "0.3.19" +futures = "0.3.21" futures-timer = "3.0.2" asynchronous-codec = "0.5" hex = "0.4.0" diff --git a/client/network/test/Cargo.toml b/client/network/test/Cargo.toml index be15cbc807e22..b9505f97d14c0 100644 --- a/client/network/test/Cargo.toml +++ b/client/network/test/Cargo.toml @@ -17,7 +17,7 @@ async-std = "1.10.0" sc-network = { version = "0.10.0-dev", path = "../" } log = "0.4.8" parking_lot = "0.12.0" -futures = "0.3.19" +futures = "0.3.21" futures-timer = "3.0.1" rand = "0.7.2" libp2p = { version = "0.40.0", default-features = false } diff --git a/client/offchain/Cargo.toml b/client/offchain/Cargo.toml index 3edba1f45704a..6cb1f83e360be 100644 --- a/client/offchain/Cargo.toml +++ b/client/offchain/Cargo.toml @@ -17,7 +17,7 @@ bytes = "1.1" codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } hex = "0.4" fnv = "1.0.6" -futures = "0.3.19" +futures = "0.3.21" futures-timer = "3.0.2" num_cpus = "1.13" parking_lot = "0.12.0" diff --git a/client/peerset/Cargo.toml b/client/peerset/Cargo.toml index 12991eac81bdc..b292f300ba353 100644 --- a/client/peerset/Cargo.toml +++ b/client/peerset/Cargo.toml @@ -14,7 +14,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -futures = "0.3.19" +futures = "0.3.21" libp2p = { version = "0.40.0", default-features = false } sc-utils = { version = "4.0.0-dev", path = "../utils"} log = "0.4.8" diff --git a/client/rpc-api/Cargo.toml b/client/rpc-api/Cargo.toml index 06deb0eef0ce3..20a18ec6c61cc 100644 --- a/client/rpc-api/Cargo.toml +++ b/client/rpc-api/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } -futures = "0.3.19" +futures = "0.3.21" jsonrpc-core = "18.0.0" jsonrpc-core-client = "18.0.0" jsonrpc-derive = "18.0.0" diff --git a/client/rpc-servers/Cargo.toml b/client/rpc-servers/Cargo.toml index 6b2c9f5aa3738..c5af4ba200fe0 100644 --- a/client/rpc-servers/Cargo.toml +++ b/client/rpc-servers/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -futures = "0.3.19" +futures = "0.3.21" jsonrpc-core = "18.0.0" pubsub = { package = "jsonrpc-pubsub", version = "18.0.0" } log = "0.4.8" diff --git a/client/rpc/Cargo.toml b/client/rpc/Cargo.toml index eeeb67f362617..4fea6501f693d 100644 --- a/client/rpc/Cargo.toml +++ b/client/rpc/Cargo.toml @@ -17,7 +17,7 @@ sc-rpc-api = { version = "0.10.0-dev", path = "../rpc-api" } sc-client-api = { version = "4.0.0-dev", path = "../api" } sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } codec = { package = "parity-scale-codec", version = "3.0.0" } -futures = "0.3.19" +futures = "0.3.21" jsonrpc-pubsub = "18.0.0" log = "0.4.8" sp-core = { version = "6.0.0", path = "../../primitives/core" } diff --git a/client/service/Cargo.toml b/client/service/Cargo.toml index 2cb23fed40f22..47474217ebd2a 100644 --- a/client/service/Cargo.toml +++ b/client/service/Cargo.toml @@ -23,7 +23,7 @@ test-helpers = [] [dependencies] thiserror = "1.0.30" -futures = "0.3.19" +futures = "0.3.21" jsonrpc-pubsub = "18.0" jsonrpc-core = "18.0" rand = "0.7.3" diff --git a/client/service/test/Cargo.toml b/client/service/test/Cargo.toml index fa3c49a2340ad..ab92de4e84f8b 100644 --- a/client/service/test/Cargo.toml +++ b/client/service/test/Cargo.toml @@ -26,7 +26,7 @@ sp-externalities = { version = "0.12.0", path = "../../../primitives/externaliti sp-trie = { version = "6.0.0", path = "../../../primitives/trie" } sp-storage = { version = "6.0.0", path = "../../../primitives/storage" } sc-client-db = { version = "0.10.0-dev", default-features = false, path = "../../db" } -futures = "0.3.19" +futures = "0.3.21" sc-service = { version = "0.10.0-dev", features = ["test-helpers"], path = "../../service" } sc-network = { version = "0.10.0-dev", path = "../../network" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } diff --git a/client/telemetry/Cargo.toml b/client/telemetry/Cargo.toml index 6daa75f453629..67583e325f34c 100644 --- a/client/telemetry/Cargo.toml +++ b/client/telemetry/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] parking_lot = "0.12.0" -futures = "0.3.19" +futures = "0.3.21" wasm-timer = "0.2.5" libp2p = { version = "0.40.0", default-features = false, features = ["dns-async-std", "tcp-async-io", "wasm-ext", "websocket"] } log = "0.4.8" diff --git a/client/transaction-pool/Cargo.toml b/client/transaction-pool/Cargo.toml index 4c9a341db6a22..df309760b847b 100644 --- a/client/transaction-pool/Cargo.toml +++ b/client/transaction-pool/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } thiserror = "1.0.30" -futures = "0.3.19" +futures = "0.3.21" futures-timer = "3.0.2" log = "0.4.8" parity-util-mem = { version = "0.11.0", default-features = false, features = ["primitive-types"] } diff --git a/client/transaction-pool/api/Cargo.toml b/client/transaction-pool/api/Cargo.toml index b444be6c6e788..8dd5e7253f965 100644 --- a/client/transaction-pool/api/Cargo.toml +++ b/client/transaction-pool/api/Cargo.toml @@ -9,7 +9,7 @@ repository = "https://github.com/paritytech/substrate/" description = "Transaction pool client facing API." [dependencies] -futures = "0.3.19" +futures = "0.3.21" log = "0.4.8" serde = { version = "1.0.136", features = ["derive"] } thiserror = "1.0.30" diff --git a/client/utils/Cargo.toml b/client/utils/Cargo.toml index a107aab1dbd6a..2f1338577e375 100644 --- a/client/utils/Cargo.toml +++ b/client/utils/Cargo.toml @@ -10,7 +10,7 @@ description = "I/O for Substrate runtimes" readme = "README.md" [dependencies] -futures = "0.3.19" +futures = "0.3.21" lazy_static = "1.4.0" parking_lot = "0.12.0" prometheus = { version = "0.13.0", default-features = false } diff --git a/primitives/api/test/Cargo.toml b/primitives/api/test/Cargo.toml index 786639f393e92..9f9f399234db4 100644 --- a/primitives/api/test/Cargo.toml +++ b/primitives/api/test/Cargo.toml @@ -26,7 +26,7 @@ rustversion = "1.0.6" [dev-dependencies] criterion = "0.3.0" -futures = "0.3.19" +futures = "0.3.21" log = "0.4.14" sp-core = { version = "6.0.0", path = "../../core" } diff --git a/primitives/blockchain/Cargo.toml b/primitives/blockchain/Cargo.toml index 4c9946b7a3127..a79bc9c5591cf 100644 --- a/primitives/blockchain/Cargo.toml +++ b/primitives/blockchain/Cargo.toml @@ -18,7 +18,7 @@ log = "0.4.11" lru = "0.7.3" parking_lot = "0.12.0" thiserror = "1.0.30" -futures = "0.3.19" +futures = "0.3.21" codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } sp-consensus = { version = "0.10.0-dev", path = "../consensus/common" } sp-runtime = { version = "6.0.0", path = "../runtime" } diff --git a/primitives/consensus/common/Cargo.toml b/primitives/consensus/common/Cargo.toml index c712eff28500e..35bd7f33fbf47 100644 --- a/primitives/consensus/common/Cargo.toml +++ b/primitives/consensus/common/Cargo.toml @@ -18,7 +18,7 @@ async-trait = "0.1.42" codec = { package = "parity-scale-codec", version = "3.0.0", features = [ "derive", ] } -futures = { version = "0.3.19", features = ["thread-pool"] } +futures = { version = "0.3.21", features = ["thread-pool"] } log = "0.4.8" sp-core = { path = "../../core", version = "6.0.0"} sp-inherents = { version = "4.0.0-dev", path = "../../inherents" } @@ -30,7 +30,7 @@ sp-runtime = { version = "6.0.0", path = "../../runtime" } thiserror = "1.0.30" [dev-dependencies] -futures = "0.3.19" +futures = "0.3.21" sp-test-primitives = { version = "2.0.0", path = "../../test-primitives" } [features] diff --git a/primitives/core/Cargo.toml b/primitives/core/Cargo.toml index 413a11c9dc0a4..74a159b2e7d39 100644 --- a/primitives/core/Cargo.toml +++ b/primitives/core/Cargo.toml @@ -41,7 +41,7 @@ sp-debug-derive = { version = "4.0.0", default-features = false, path = "../debu sp-storage = { version = "6.0.0", default-features = false, path = "../storage" } sp-externalities = { version = "0.12.0", optional = true, path = "../externalities" } parity-util-mem = { version = "0.11.0", default-features = false, features = ["primitive-types"] } -futures = { version = "0.3.19", optional = true } +futures = { version = "0.3.21", optional = true } dyn-clonable = { version = "0.9.0", optional = true } thiserror = { version = "1.0.30", optional = true } bitflags = "1.3" diff --git a/primitives/inherents/Cargo.toml b/primitives/inherents/Cargo.toml index a90fb67e2385a..f7beb7bdfcf54 100644 --- a/primitives/inherents/Cargo.toml +++ b/primitives/inherents/Cargo.toml @@ -24,7 +24,7 @@ impl-trait-for-tuples = "0.2.0" async-trait = { version = "0.1.50", optional = true } [dev-dependencies] -futures = "0.3.19" +futures = "0.3.21" [features] default = [ "std" ] diff --git a/primitives/io/Cargo.toml b/primitives/io/Cargo.toml index c51e34b861b9b..bdae6174a87cb 100644 --- a/primitives/io/Cargo.toml +++ b/primitives/io/Cargo.toml @@ -28,7 +28,7 @@ sp-trie = { version = "6.0.0", optional = true, path = "../trie" } sp-externalities = { version = "0.12.0", optional = true, path = "../externalities" } sp-tracing = { version = "5.0.0", default-features = false, path = "../tracing" } log = { version = "0.4.8", optional = true } -futures = { version = "0.3.19", features = ["thread-pool"], optional = true } +futures = { version = "0.3.21", features = ["thread-pool"], optional = true } parking_lot = { version = "0.12.0", optional = true } secp256k1 = { version = "0.21.2", features = ["recovery", "global-context"], optional = true } tracing = { version = "0.1.29", default-features = false } diff --git a/primitives/keystore/Cargo.toml b/primitives/keystore/Cargo.toml index 7b1d1dd4ebc4b..f201cb8518bc6 100644 --- a/primitives/keystore/Cargo.toml +++ b/primitives/keystore/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] async-trait = "0.1.50" -futures = "0.3.19" +futures = "0.3.21" parking_lot = { version = "0.12.0", default-features = false } serde = { version = "1.0", optional = true } thiserror = "1.0" diff --git a/test-utils/client/Cargo.toml b/test-utils/client/Cargo.toml index 1f7115cf16cf8..f99300f27eacb 100644 --- a/test-utils/client/Cargo.toml +++ b/test-utils/client/Cargo.toml @@ -13,7 +13,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } -futures = "0.3.19" +futures = "0.3.21" hex = "0.4" serde = "1.0.136" serde_json = "1.0.79" diff --git a/test-utils/runtime/Cargo.toml b/test-utils/runtime/Cargo.toml index ad7dca5e08fb1..cbefed27ca9bd 100644 --- a/test-utils/runtime/Cargo.toml +++ b/test-utils/runtime/Cargo.toml @@ -56,7 +56,7 @@ sc-block-builder = { version = "0.10.0-dev", path = "../../client/block-builder" sc-executor = { version = "0.10.0-dev", path = "../../client/executor" } sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } substrate-test-runtime-client = { version = "2.0.0", path = "./client" } -futures = "0.3.19" +futures = "0.3.21" [build-dependencies] substrate-wasm-builder = { version = "5.0.0-dev", path = "../../utils/wasm-builder" } diff --git a/test-utils/runtime/client/Cargo.toml b/test-utils/runtime/client/Cargo.toml index 642d754868f7d..a22d9f302a9e9 100644 --- a/test-utils/runtime/client/Cargo.toml +++ b/test-utils/runtime/client/Cargo.toml @@ -23,4 +23,4 @@ sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } codec = { package = "parity-scale-codec", version = "3.0.0" } sc-client-api = { version = "4.0.0-dev", path = "../../../client/api" } -futures = "0.3.19" +futures = "0.3.21" diff --git a/test-utils/runtime/transaction-pool/Cargo.toml b/test-utils/runtime/transaction-pool/Cargo.toml index 8bc75c9d4c053..59d576acdcb8f 100644 --- a/test-utils/runtime/transaction-pool/Cargo.toml +++ b/test-utils/runtime/transaction-pool/Cargo.toml @@ -19,5 +19,5 @@ sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } sc-transaction-pool = { version = "4.0.0-dev", path = "../../../client/transaction-pool" } sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../../client/transaction-pool/api" } -futures = "0.3.19" +futures = "0.3.21" thiserror = "1.0" diff --git a/utils/frame/rpc/support/Cargo.toml b/utils/frame/rpc/support/Cargo.toml index 91ec47e34bd37..fe304370727f0 100644 --- a/utils/frame/rpc/support/Cargo.toml +++ b/utils/frame/rpc/support/Cargo.toml @@ -15,7 +15,7 @@ description = "Substrate RPC for FRAME's support" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -futures = "0.3.19" +futures = "0.3.21" jsonrpc-client-transports = { version = "18.0.0", features = ["http"] } codec = { package = "parity-scale-codec", version = "3.0.0" } serde = "1" diff --git a/utils/frame/rpc/system/Cargo.toml b/utils/frame/rpc/system/Cargo.toml index 1015760efee6f..31a6f93e847b1 100644 --- a/utils/frame/rpc/system/Cargo.toml +++ b/utils/frame/rpc/system/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] sc-client-api = { version = "4.0.0-dev", path = "../../../../client/api" } codec = { package = "parity-scale-codec", version = "3.0.0" } -futures = "0.3.19" +futures = "0.3.21" jsonrpc-core = "18.0.0" jsonrpc-core-client = "18.0.0" jsonrpc-derive = "18.0.0" From 38887857a44cae9fdf8689df8e770cfac357a3db Mon Sep 17 00:00:00 2001 From: Qinxuan Chen Date: Mon, 4 Apr 2022 03:44:29 +0800 Subject: [PATCH 081/484] Remove useless imports (#11136) Because `TryInto`/`TryFrom` are in prelude by default from edition 2021 Signed-off-by: koushiro --- bin/node/cli/src/service.rs | 2 +- bin/node/cli/tests/common.rs | 1 - bin/node/cli/tests/running_the_node_and_interrupt.rs | 5 +---- bin/node/cli/tests/telemetry.rs | 2 +- bin/node/cli/tests/temp_base_path_works.rs | 1 - client/allocator/src/freeing_bump.rs | 1 - client/api/src/client.rs | 2 +- client/authority-discovery/src/worker.rs | 10 +++------- client/beefy/src/keystore.rs | 2 -- client/cli/src/commands/insert_key.rs | 2 +- client/cli/src/commands/utils.rs | 2 +- client/cli/src/commands/verify.rs | 2 +- client/cli/src/error.rs | 6 +++--- client/cli/src/params/mod.rs | 2 +- client/consensus/aura/src/lib.rs | 11 ++--------- client/consensus/babe/src/lib.rs | 3 +-- client/consensus/manual-seal/src/error.rs | 2 +- client/consensus/pow/src/lib.rs | 4 ++-- client/db/src/utils.rs | 2 +- client/executor/common/src/sandbox/wasmer_backend.rs | 2 +- client/finality-grandpa/rpc/src/lib.rs | 2 +- client/informant/src/display.rs | 6 +----- client/network-gossip/src/bridge.rs | 1 - client/network/src/config.rs | 1 - client/network/src/protocol.rs | 1 - .../protocol/notifications/upgrade/notifications.rs | 2 +- client/network/src/request_responses.rs | 1 - client/network/src/service.rs | 1 - client/network/src/service/out_events.rs | 1 - client/offchain/src/api.rs | 7 ++----- client/offchain/src/api/http.rs | 1 - client/offchain/src/api/timestamp.rs | 5 +---- client/rpc/src/author/mod.rs | 2 +- client/service/src/chain_ops/import_blocks.rs | 1 - client/service/src/config.rs | 2 +- client/service/src/metrics.rs | 2 +- client/transaction-pool/src/lib.rs | 1 - client/transaction-pool/tests/pool.rs | 4 +--- frame/assets/src/lib.rs | 2 +- frame/bags-list/fuzzer/src/main.rs | 1 - frame/bags-list/remote-tests/src/sanity_check.rs | 1 - frame/contracts/src/tests.rs | 1 - frame/conviction-voting/src/vote.rs | 2 +- frame/election-provider-support/src/mock.rs | 1 - frame/election-provider-support/src/tests.rs | 3 +-- frame/election-provider-support/src/traits.rs | 8 ++------ frame/im-online/src/lib.rs | 2 +- frame/preimage/src/lib.rs | 2 +- frame/proxy/src/lib.rs | 2 +- frame/randomness-collective-flip/src/lib.rs | 1 - frame/staking/reward-curve/src/lib.rs | 1 - frame/staking/src/lib.rs | 2 +- frame/staking/src/pallet/mod.rs | 2 +- .../procedural/src/construct_runtime/expand/event.rs | 2 +- .../procedural/src/construct_runtime/expand/origin.rs | 4 ++-- frame/support/procedural/src/pallet/expand/error.rs | 2 +- frame/support/src/error.rs | 2 +- frame/support/src/lib.rs | 2 -- frame/support/src/storage/bounded_btree_map.rs | 5 +---- frame/support/src/storage/bounded_btree_set.rs | 6 +----- frame/support/src/storage/weak_bounded_vec.rs | 3 +-- frame/uniques/src/benchmarking.rs | 2 +- frame/vesting/src/lib.rs | 2 +- primitives/application-crypto/src/lib.rs | 8 ++++---- primitives/arithmetic/fuzzer/src/biguint.rs | 1 - primitives/arithmetic/fuzzer/src/normalize.rs | 1 - primitives/arithmetic/src/biguint.rs | 3 +-- primitives/arithmetic/src/fixed_point.rs | 1 - primitives/arithmetic/src/helpers_128bit.rs | 1 - primitives/arithmetic/src/lib.rs | 2 +- primitives/arithmetic/src/per_things.rs | 1 - primitives/arithmetic/src/traits.rs | 8 ++------ primitives/consensus/common/src/error.rs | 4 ++-- primitives/consensus/vrf/src/schnorrkel.rs | 1 - primitives/core/src/crypto.rs | 4 ++-- primitives/core/src/ecdsa.rs | 4 ++-- primitives/core/src/ed25519.rs | 6 ++---- primitives/core/src/offchain/mod.rs | 5 +---- primitives/core/src/sr25519.rs | 6 ++---- primitives/rpc/src/number.rs | 5 +---- .../runtime-interface/proc-macro/src/pass_by/enum_.rs | 2 +- primitives/runtime-interface/src/pass_by.rs | 4 ++-- primitives/runtime-interface/test-wasm/src/lib.rs | 2 +- primitives/runtime/src/curve.rs | 4 ---- primitives/runtime/src/generic/header.rs | 2 +- primitives/runtime/src/lib.rs | 2 +- primitives/runtime/src/traits.rs | 8 +------- primitives/std/src/lib.rs | 1 - primitives/storage/src/lib.rs | 2 +- primitives/wasm-interface/src/lib.rs | 2 +- utils/prometheus/src/lib.rs | 1 - 91 files changed, 81 insertions(+), 176 deletions(-) diff --git a/bin/node/cli/src/service.rs b/bin/node/cli/src/service.rs index 038c14029cf31..03e7311fb3c01 100644 --- a/bin/node/cli/src/service.rs +++ b/bin/node/cli/src/service.rs @@ -562,7 +562,7 @@ mod tests { RuntimeAppPublic, }; use sp_timestamp; - use std::{borrow::Cow, convert::TryInto, sync::Arc}; + use std::{borrow::Cow, sync::Arc}; type AccountPublic = ::Signer; diff --git a/bin/node/cli/tests/common.rs b/bin/node/cli/tests/common.rs index 8391cff355969..c17cabfa1d38a 100644 --- a/bin/node/cli/tests/common.rs +++ b/bin/node/cli/tests/common.rs @@ -26,7 +26,6 @@ use nix::{ use node_primitives::Block; use remote_externalities::rpc_api; use std::{ - convert::TryInto, ops::{Deref, DerefMut}, path::Path, process::{Child, Command, ExitStatus}, diff --git a/bin/node/cli/tests/running_the_node_and_interrupt.rs b/bin/node/cli/tests/running_the_node_and_interrupt.rs index edce2bbc6e4c5..703123faf0e62 100644 --- a/bin/node/cli/tests/running_the_node_and_interrupt.rs +++ b/bin/node/cli/tests/running_the_node_and_interrupt.rs @@ -25,10 +25,7 @@ use nix::{ }, unistd::Pid, }; -use std::{ - convert::TryInto, - process::{Child, Command}, -}; +use std::process::{Child, Command}; use tempfile::tempdir; pub mod common; diff --git a/bin/node/cli/tests/telemetry.rs b/bin/node/cli/tests/telemetry.rs index 212fec7a02cf6..64da4bd4b68f1 100644 --- a/bin/node/cli/tests/telemetry.rs +++ b/bin/node/cli/tests/telemetry.rs @@ -21,7 +21,7 @@ use nix::{ sys::signal::{kill, Signal::SIGINT}, unistd::Pid, }; -use std::{convert::TryInto, process}; +use std::process; pub mod common; pub mod websocket_server; diff --git a/bin/node/cli/tests/temp_base_path_works.rs b/bin/node/cli/tests/temp_base_path_works.rs index a5b9b7499fc49..306c490c2f760 100644 --- a/bin/node/cli/tests/temp_base_path_works.rs +++ b/bin/node/cli/tests/temp_base_path_works.rs @@ -25,7 +25,6 @@ use nix::{ }; use regex::Regex; use std::{ - convert::TryInto, io::Read, path::PathBuf, process::{Command, Stdio}, diff --git a/client/allocator/src/freeing_bump.rs b/client/allocator/src/freeing_bump.rs index d7152f5a209ea..7eeda45370b87 100644 --- a/client/allocator/src/freeing_bump.rs +++ b/client/allocator/src/freeing_bump.rs @@ -71,7 +71,6 @@ use crate::Error; pub use sp_core::MAX_POSSIBLE_ALLOCATION; use sp_wasm_interface::{Pointer, WordSize}; use std::{ - convert::{TryFrom, TryInto}, mem, ops::{Index, IndexMut, Range}, }; diff --git a/client/api/src/client.rs b/client/api/src/client.rs index 11195e1def28c..9c55be3238130 100644 --- a/client/api/src/client.rs +++ b/client/api/src/client.rs @@ -25,7 +25,7 @@ use sp_runtime::{ traits::{Block as BlockT, NumberFor}, Justifications, }; -use std::{collections::HashSet, convert::TryFrom, fmt, sync::Arc}; +use std::{collections::HashSet, fmt, sync::Arc}; use crate::{blockchain::Info, notifications::StorageEventStream, FinalizeSummary, ImportSummary}; diff --git a/client/authority-discovery/src/worker.rs b/client/authority-discovery/src/worker.rs index 019abaac3cfcb..726c032281cd8 100644 --- a/client/authority-discovery/src/worker.rs +++ b/client/authority-discovery/src/worker.rs @@ -24,7 +24,6 @@ use crate::{ use std::{ collections::{HashMap, HashSet}, - convert::TryInto, marker::PhantomData, sync::Arc, time::Duration, @@ -581,14 +580,11 @@ where .authorities(&id) .map_err(|e| Error::CallingRuntime(e.into()))? .into_iter() - .map(std::convert::Into::into) + .map(Into::into) .collect::>(); - let intersection = local_pub_keys - .intersection(&authorities) - .cloned() - .map(std::convert::Into::into) - .collect(); + let intersection = + local_pub_keys.intersection(&authorities).cloned().map(Into::into).collect(); Ok(intersection) } diff --git a/client/beefy/src/keystore.rs b/client/beefy/src/keystore.rs index 32478a11434fa..b0259a42075ea 100644 --- a/client/beefy/src/keystore.rs +++ b/client/beefy/src/keystore.rs @@ -16,8 +16,6 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use std::convert::{From, TryInto}; - use sp_application_crypto::RuntimeAppPublic; use sp_core::keccak_256; use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; diff --git a/client/cli/src/commands/insert_key.rs b/client/cli/src/commands/insert_key.rs index 852b4e50376ff..68201d7b4bffc 100644 --- a/client/cli/src/commands/insert_key.rs +++ b/client/cli/src/commands/insert_key.rs @@ -25,7 +25,7 @@ use sc_keystore::LocalKeystore; use sc_service::config::{BasePath, KeystoreConfig}; use sp_core::crypto::{KeyTypeId, SecretString}; use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; -use std::{convert::TryFrom, sync::Arc}; +use std::sync::Arc; /// The `insert` command #[derive(Debug, Clone, Parser)] diff --git a/client/cli/src/commands/utils.rs b/client/cli/src/commands/utils.rs index d20df01e5a90e..fa776c25a2eda 100644 --- a/client/cli/src/commands/utils.rs +++ b/client/cli/src/commands/utils.rs @@ -31,7 +31,7 @@ use sp_core::{ Pair, }; use sp_runtime::{traits::IdentifyAccount, MultiSigner}; -use std::{convert::TryFrom, io::Read, path::PathBuf}; +use std::{io::Read, path::PathBuf}; /// Public key type for Runtime pub type PublicFor

=

::Public; diff --git a/client/cli/src/commands/verify.rs b/client/cli/src/commands/verify.rs index a8879f42caafc..b004a948a7a48 100644 --- a/client/cli/src/commands/verify.rs +++ b/client/cli/src/commands/verify.rs @@ -66,7 +66,7 @@ impl VerifyCmd { fn verify(sig_data: Vec, message: Vec, uri: &str) -> error::Result<()> where Pair: sp_core::Pair, - Pair::Signature: for<'a> std::convert::TryFrom<&'a [u8]>, + Pair::Signature: for<'a> TryFrom<&'a [u8]>, { let signature = Pair::Signature::try_from(&sig_data).map_err(|_| error::Error::SignatureFormatInvalid)?; diff --git a/client/cli/src/error.rs b/client/cli/src/error.rs index 69b2eeaaf6186..f38a95e0115f1 100644 --- a/client/cli/src/error.rs +++ b/client/cli/src/error.rs @@ -80,19 +80,19 @@ pub enum Error { GlobalLoggerError(#[from] sc_tracing::logging::Error), } -impl std::convert::From<&str> for Error { +impl From<&str> for Error { fn from(s: &str) -> Error { Error::Input(s.to_string()) } } -impl std::convert::From for Error { +impl From for Error { fn from(s: String) -> Error { Error::Input(s) } } -impl std::convert::From for Error { +impl From for Error { fn from(e: crypto::PublicError) -> Error { Error::InvalidUri(e) } diff --git a/client/cli/src/params/mod.rs b/client/cli/src/params/mod.rs index e0571aa4bbbf8..33e714ec138b0 100644 --- a/client/cli/src/params/mod.rs +++ b/client/cli/src/params/mod.rs @@ -32,7 +32,7 @@ use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, NumberFor}, }; -use std::{convert::TryFrom, fmt::Debug, str::FromStr}; +use std::{fmt::Debug, str::FromStr}; pub use crate::params::{ database_params::*, import_params::*, keystore_params::*, network_params::*, diff --git a/client/consensus/aura/src/lib.rs b/client/consensus/aura/src/lib.rs index 4d91eaaae92ab..f6ea2e08e1e88 100644 --- a/client/consensus/aura/src/lib.rs +++ b/client/consensus/aura/src/lib.rs @@ -30,14 +30,7 @@ //! //! NOTE: Aura itself is designed to be generic over the crypto used. #![forbid(missing_docs, unsafe_code)] -use std::{ - convert::{TryFrom, TryInto}, - fmt::Debug, - hash::Hash, - marker::PhantomData, - pin::Pin, - sync::Arc, -}; +use std::{fmt::Debug, hash::Hash, marker::PhantomData, pin::Pin, sync::Arc}; use futures::prelude::*; use log::{debug, trace}; @@ -517,7 +510,7 @@ pub enum Error { Inherent(sp_inherents::Error), } -impl std::convert::From> for String { +impl From> for String { fn from(error: Error) -> String { error.to_string() } diff --git a/client/consensus/babe/src/lib.rs b/client/consensus/babe/src/lib.rs index 4f3139a013d4b..818384a5c3d7e 100644 --- a/client/consensus/babe/src/lib.rs +++ b/client/consensus/babe/src/lib.rs @@ -69,7 +69,6 @@ use std::{ borrow::Cow, collections::{HashMap, HashSet}, - convert::TryInto, future::Future, pin::Pin, sync::Arc, @@ -320,7 +319,7 @@ pub enum Error { ForkTree(Box>), } -impl std::convert::From> for String { +impl From> for String { fn from(error: Error) -> String { error.to_string() } diff --git a/client/consensus/manual-seal/src/error.rs b/client/consensus/manual-seal/src/error.rs index 2f946f3de3ccc..7c3211203bf54 100644 --- a/client/consensus/manual-seal/src/error.rs +++ b/client/consensus/manual-seal/src/error.rs @@ -102,7 +102,7 @@ impl Error { } } -impl std::convert::From for jsonrpc_core::Error { +impl From for jsonrpc_core::Error { fn from(error: Error) -> Self { jsonrpc_core::Error { code: jsonrpc_core::ErrorCode::ServerError(error.to_code()), diff --git a/client/consensus/pow/src/lib.rs b/client/consensus/pow/src/lib.rs index 6d0bc3fc5a192..8885099ceb514 100644 --- a/client/consensus/pow/src/lib.rs +++ b/client/consensus/pow/src/lib.rs @@ -119,13 +119,13 @@ pub enum Error { Other(String), } -impl std::convert::From> for String { +impl From> for String { fn from(error: Error) -> String { error.to_string() } } -impl std::convert::From> for ConsensusError { +impl From> for ConsensusError { fn from(error: Error) -> ConsensusError { ConsensusError::ClientImport(error.to_string()) } diff --git a/client/db/src/utils.rs b/client/db/src/utils.rs index 29aa8424221a6..1798838ecc155 100644 --- a/client/db/src/utils.rs +++ b/client/db/src/utils.rs @@ -19,7 +19,7 @@ //! Db-based backend utility structures and functions, used by both //! full and light storages. -use std::{convert::TryInto, fmt, fs, io, path::Path, sync::Arc}; +use std::{fmt, fs, io, path::Path, sync::Arc}; use log::{debug, info}; diff --git a/client/executor/common/src/sandbox/wasmer_backend.rs b/client/executor/common/src/sandbox/wasmer_backend.rs index dfb26c4a8dedb..904afc9470400 100644 --- a/client/executor/common/src/sandbox/wasmer_backend.rs +++ b/client/executor/common/src/sandbox/wasmer_backend.rs @@ -26,7 +26,7 @@ use crate::{ use codec::{Decode, Encode}; use sp_core::sandbox::HostError; use sp_wasm_interface::{FunctionContext, Pointer, ReturnValue, Value, WordSize}; -use std::{cell::RefCell, collections::HashMap, convert::TryInto, rc::Rc}; +use std::{cell::RefCell, collections::HashMap, rc::Rc}; use wasmer::RuntimeError; use crate::sandbox::{ diff --git a/client/finality-grandpa/rpc/src/lib.rs b/client/finality-grandpa/rpc/src/lib.rs index bde2e5612b2e6..9c51bc3d226a7 100644 --- a/client/finality-grandpa/rpc/src/lib.rs +++ b/client/finality-grandpa/rpc/src/lib.rs @@ -171,7 +171,7 @@ where mod tests { use super::*; use jsonrpc_core::{types::Params, Notification, Output}; - use std::{collections::HashSet, convert::TryInto, sync::Arc}; + use std::{collections::HashSet, sync::Arc}; use parity_scale_codec::{Decode, Encode}; use sc_block_builder::{BlockBuilder, RecordProof}; diff --git a/client/informant/src/display.rs b/client/informant/src/display.rs index 8d76939fddd6f..77b28dec55651 100644 --- a/client/informant/src/display.rs +++ b/client/informant/src/display.rs @@ -22,11 +22,7 @@ use log::info; use sc_client_api::ClientInfo; use sc_network::{NetworkStatus, SyncState, WarpSyncPhase, WarpSyncProgress}; use sp_runtime::traits::{Block as BlockT, CheckedDiv, NumberFor, Saturating, Zero}; -use std::{ - convert::{TryFrom, TryInto}, - fmt, - time::Instant, -}; +use std::{fmt, time::Instant}; /// State of the informant display system. /// diff --git a/client/network-gossip/src/bridge.rs b/client/network-gossip/src/bridge.rs index f72cd15616515..2e09e7cc614a4 100644 --- a/client/network-gossip/src/bridge.rs +++ b/client/network-gossip/src/bridge.rs @@ -303,7 +303,6 @@ mod tests { use sp_runtime::{testing::H256, traits::Block as BlockT}; use std::{ borrow::Cow, - convert::TryInto, sync::{Arc, Mutex}, }; use substrate_test_runtime_client::runtime::Block; diff --git a/client/network/src/config.rs b/client/network/src/config.rs index eedf3fc22b961..7c2dfef7fe89c 100644 --- a/client/network/src/config.rs +++ b/client/network/src/config.rs @@ -50,7 +50,6 @@ use sp_runtime::traits::Block as BlockT; use std::{ borrow::Cow, collections::HashMap, - convert::TryFrom, error::Error, fs, future::Future, diff --git a/client/network/src/protocol.rs b/client/network/src/protocol.rs index c00d4302f827c..80694210e77dc 100644 --- a/client/network/src/protocol.rs +++ b/client/network/src/protocol.rs @@ -60,7 +60,6 @@ use sp_runtime::{ use std::{ borrow::Cow, collections::{HashMap, HashSet, VecDeque}, - convert::TryFrom as _, io, iter, num::NonZeroUsize, pin::Pin, diff --git a/client/network/src/protocol/notifications/upgrade/notifications.rs b/client/network/src/protocol/notifications/upgrade/notifications.rs index 53270975a5b0d..3fbb59d399a0e 100644 --- a/client/network/src/protocol/notifications/upgrade/notifications.rs +++ b/client/network/src/protocol/notifications/upgrade/notifications.rs @@ -41,7 +41,7 @@ use libp2p::core::{upgrade, InboundUpgrade, OutboundUpgrade, UpgradeInfo}; use log::{error, warn}; use std::{ borrow::Cow, - convert::{Infallible, TryFrom as _}, + convert::Infallible, io, mem, pin::Pin, task::{Context, Poll}, diff --git a/client/network/src/request_responses.rs b/client/network/src/request_responses.rs index 58007c7bc5ce1..4613a15af9366 100644 --- a/client/network/src/request_responses.rs +++ b/client/network/src/request_responses.rs @@ -56,7 +56,6 @@ use libp2p::{ use std::{ borrow::Cow, collections::{hash_map::Entry, HashMap}, - convert::TryFrom as _, io, iter, pin::Pin, task::{Context, Poll}, diff --git a/client/network/src/service.rs b/client/network/src/service.rs index a02aa982318a9..e89be325fa486 100644 --- a/client/network/src/service.rs +++ b/client/network/src/service.rs @@ -73,7 +73,6 @@ use std::{ borrow::Cow, cmp, collections::{HashMap, HashSet}, - convert::TryFrom as _, fs, iter, marker::PhantomData, num::NonZeroUsize, diff --git a/client/network/src/service/out_events.rs b/client/network/src/service/out_events.rs index 1fe13bc30faff..3bff5a16fd0c2 100644 --- a/client/network/src/service/out_events.rs +++ b/client/network/src/service/out_events.rs @@ -38,7 +38,6 @@ use parking_lot::Mutex; use prometheus_endpoint::{register, CounterVec, GaugeVec, Opts, PrometheusError, Registry, U64}; use std::{ cell::RefCell, - convert::TryFrom as _, fmt, pin::Pin, sync::Arc, diff --git a/client/offchain/src/api.rs b/client/offchain/src/api.rs index 30c7969279ee2..c80b511c84d17 100644 --- a/client/offchain/src/api.rs +++ b/client/offchain/src/api.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use std::{collections::HashSet, convert::TryFrom, str::FromStr, sync::Arc, thread::sleep}; +use std::{collections::HashSet, str::FromStr, sync::Arc, thread::sleep}; use crate::NetworkProvider; use codec::{Decode, Encode}; @@ -327,10 +327,7 @@ mod tests { use sc_client_db::offchain::LocalStorage; use sc_network::{NetworkStateInfo, PeerId}; use sp_core::offchain::{DbExternalities, Externalities}; - use std::{ - convert::{TryFrom, TryInto}, - time::SystemTime, - }; + use std::time::SystemTime; pub(super) struct TestNetwork(); diff --git a/client/offchain/src/api/http.rs b/client/offchain/src/api/http.rs index 2a7514116cb5d..012de78c5f645 100644 --- a/client/offchain/src/api/http.rs +++ b/client/offchain/src/api/http.rs @@ -37,7 +37,6 @@ use once_cell::sync::Lazy; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; use sp_core::offchain::{HttpError, HttpRequestId, HttpRequestStatus, Timestamp}; use std::{ - convert::TryFrom, fmt, io::Read as _, pin::Pin, diff --git a/client/offchain/src/api/timestamp.rs b/client/offchain/src/api/timestamp.rs index 6622331f570df..4b3f5efddf275 100644 --- a/client/offchain/src/api/timestamp.rs +++ b/client/offchain/src/api/timestamp.rs @@ -19,10 +19,7 @@ //! Helper methods dedicated to timestamps. use sp_core::offchain::Timestamp; -use std::{ - convert::TryInto, - time::{Duration, SystemTime}, -}; +use std::time::{Duration, SystemTime}; /// Returns the current time as a `Timestamp`. pub fn now() -> Timestamp { diff --git a/client/rpc/src/author/mod.rs b/client/rpc/src/author/mod.rs index 2b604d2897c58..5064e61342101 100644 --- a/client/rpc/src/author/mod.rs +++ b/client/rpc/src/author/mod.rs @@ -21,7 +21,7 @@ #[cfg(test)] mod tests; -use std::{convert::TryInto, sync::Arc}; +use std::sync::Arc; use sp_blockchain::HeaderBackend; diff --git a/client/service/src/chain_ops/import_blocks.rs b/client/service/src/chain_ops/import_blocks.rs index dc03cdcedae06..c0612124dd0c2 100644 --- a/client/service/src/chain_ops/import_blocks.rs +++ b/client/service/src/chain_ops/import_blocks.rs @@ -35,7 +35,6 @@ use sp_runtime::{ }, }; use std::{ - convert::{TryFrom, TryInto}, io::Read, pin::Pin, task::Poll, diff --git a/client/service/src/config.rs b/client/service/src/config.rs index fd32aebdebdd8..7c7bb480aa5c3 100644 --- a/client/service/src/config.rs +++ b/client/service/src/config.rs @@ -294,7 +294,7 @@ impl BasePath { } } -impl std::convert::From for BasePath { +impl From for BasePath { fn from(path: PathBuf) -> Self { BasePath::new(path) } diff --git a/client/service/src/metrics.rs b/client/service/src/metrics.rs index 02e3ab95f1187..9fbf2c3ea3fca 100644 --- a/client/service/src/metrics.rs +++ b/client/service/src/metrics.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use std::{convert::TryFrom, time::SystemTime}; +use std::time::SystemTime; use crate::config::Configuration; use futures_timer::Delay; diff --git a/client/transaction-pool/src/lib.rs b/client/transaction-pool/src/lib.rs index ec93d1f7c51fe..9fd2ef1deef43 100644 --- a/client/transaction-pool/src/lib.rs +++ b/client/transaction-pool/src/lib.rs @@ -40,7 +40,6 @@ pub use graph::{base_pool::Limit as PoolLimit, ChainApi, Options, Pool, Transact use parking_lot::Mutex; use std::{ collections::{HashMap, HashSet}, - convert::TryInto, pin::Pin, sync::Arc, }; diff --git a/client/transaction-pool/tests/pool.rs b/client/transaction-pool/tests/pool.rs index 08ecdbc86cca4..2243f140cb22d 100644 --- a/client/transaction-pool/tests/pool.rs +++ b/client/transaction-pool/tests/pool.rs @@ -36,7 +36,7 @@ use sp_runtime::{ traits::Block as _, transaction_validity::{InvalidTransaction, TransactionSource, ValidTransaction}, }; -use std::{collections::BTreeSet, convert::TryInto, sync::Arc}; +use std::{collections::BTreeSet, sync::Arc}; use substrate_test_runtime_client::{ runtime::{Block, Extrinsic, Hash, Header, Index, Transfer}, AccountKeyring::*, @@ -864,8 +864,6 @@ fn ready_set_should_eventually_resolve_when_block_update_arrives() { #[test] fn should_not_accept_old_signatures() { - use std::convert::TryFrom; - let client = Arc::new(substrate_test_runtime_client::new()); let pool = Arc::new( diff --git a/frame/assets/src/lib.rs b/frame/assets/src/lib.rs index 71ba7b4d24910..9b0f23a0a1783 100644 --- a/frame/assets/src/lib.rs +++ b/frame/assets/src/lib.rs @@ -147,7 +147,7 @@ use sp_runtime::{ }, ArithmeticError, TokenError, }; -use sp_std::{borrow::Borrow, convert::TryInto, prelude::*}; +use sp_std::{borrow::Borrow, prelude::*}; use frame_support::{ dispatch::{DispatchError, DispatchResult}, diff --git a/frame/bags-list/fuzzer/src/main.rs b/frame/bags-list/fuzzer/src/main.rs index 3ff59b450befa..6f538eb28e7e4 100644 --- a/frame/bags-list/fuzzer/src/main.rs +++ b/frame/bags-list/fuzzer/src/main.rs @@ -30,7 +30,6 @@ use frame_election_provider_support::{SortedListProvider, VoteWeight}; use honggfuzz::fuzz; use pallet_bags_list::mock::{AccountId, BagsList, ExtBuilder}; -use std::convert::From; const ID_RANGE: AccountId = 25_000; diff --git a/frame/bags-list/remote-tests/src/sanity_check.rs b/frame/bags-list/remote-tests/src/sanity_check.rs index f2b6881edea7f..1027efb8539ee 100644 --- a/frame/bags-list/remote-tests/src/sanity_check.rs +++ b/frame/bags-list/remote-tests/src/sanity_check.rs @@ -23,7 +23,6 @@ use frame_support::{ }; use remote_externalities::{Builder, Mode, OnlineConfig}; use sp_runtime::{traits::Block as BlockT, DeserializeOwned}; -use sp_std::prelude::*; /// Execute the sanity check of the bags-list. pub async fn execute( diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index 2b01cbe3c7429..ce59f5bb858ac 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -96,7 +96,6 @@ pub mod test_utils { } macro_rules! assert_return_code { ( $x:expr , $y:expr $(,)? ) => {{ - use sp_std::convert::TryInto; assert_eq!(u32::from_le_bytes($x.data[..].try_into().unwrap()), $y as u32); }}; } diff --git a/frame/conviction-voting/src/vote.rs b/frame/conviction-voting/src/vote.rs index d7ca931de35a1..e608a6dcfd01b 100644 --- a/frame/conviction-voting/src/vote.rs +++ b/frame/conviction-voting/src/vote.rs @@ -25,7 +25,7 @@ use sp_runtime::{ traits::{Saturating, Zero}, RuntimeDebug, }; -use sp_std::{convert::TryFrom, prelude::*, result::Result}; +use sp_std::prelude::*; /// A number of lock periods, plus a vote, one way or the other. #[derive(Copy, Clone, Eq, PartialEq, Default, RuntimeDebug, MaxEncodedLen)] diff --git a/frame/election-provider-support/src/mock.rs b/frame/election-provider-support/src/mock.rs index d10b1724c95d8..7c834f06f3cdf 100644 --- a/frame/election-provider-support/src/mock.rs +++ b/frame/election-provider-support/src/mock.rs @@ -21,7 +21,6 @@ use std::{ collections::{HashMap, HashSet}, - convert::TryInto, hash::Hash, }; diff --git a/frame/election-provider-support/src/tests.rs b/frame/election-provider-support/src/tests.rs index f88f3653c681c..1ccff79f3efd4 100644 --- a/frame/election-provider-support/src/tests.rs +++ b/frame/election-provider-support/src/tests.rs @@ -22,7 +22,6 @@ use crate::{mock::*, IndexAssignment, NposSolution}; use frame_support::traits::ConstU32; use rand::SeedableRng; -use std::convert::TryInto; mod solution_type { use super::*; @@ -30,7 +29,7 @@ mod solution_type { // these need to come from the same dev-dependency `frame-election-provider-support`, not from // the crate. use crate::{generate_solution_type, Assignment, Error as NposError, NposSolution}; - use sp_std::{convert::TryInto, fmt::Debug}; + use sp_std::fmt::Debug; #[allow(dead_code)] mod __private { diff --git a/frame/election-provider-support/src/traits.rs b/frame/election-provider-support/src/traits.rs index e1fc0663e7d1e..ed812e2e0f2c4 100644 --- a/frame/election-provider-support/src/traits.rs +++ b/frame/election-provider-support/src/traits.rs @@ -22,16 +22,12 @@ use codec::Encode; use scale_info::TypeInfo; use sp_arithmetic::traits::{Bounded, UniqueSaturatedInto}; use sp_npos_elections::{ElectionScore, Error, EvaluateSupport}; -use sp_std::{ - convert::{TryFrom, TryInto}, - fmt::Debug, - prelude::*, -}; +use sp_std::{fmt::Debug, prelude::*}; /// An opaque index-based, NPoS solution type. pub trait NposSolution where - Self: Sized + for<'a> sp_std::convert::TryFrom<&'a [IndexAssignmentOf], Error = Error>, + Self: Sized + for<'a> TryFrom<&'a [IndexAssignmentOf], Error = Error>, { /// The maximum number of votes that are allowed. const LIMIT: usize; diff --git a/frame/im-online/src/lib.rs b/frame/im-online/src/lib.rs index e2213ef4169b1..5ef515db0b236 100644 --- a/frame/im-online/src/lib.rs +++ b/frame/im-online/src/lib.rs @@ -104,7 +104,7 @@ use sp_staking::{ offence::{Kind, Offence, ReportOffence}, SessionIndex, }; -use sp_std::{convert::TryInto, prelude::*}; +use sp_std::prelude::*; pub use weights::WeightInfo; pub mod sr25519 { diff --git a/frame/preimage/src/lib.rs b/frame/preimage/src/lib.rs index c34057a1034d3..a5d8ee28b5952 100644 --- a/frame/preimage/src/lib.rs +++ b/frame/preimage/src/lib.rs @@ -37,7 +37,7 @@ mod tests; pub mod weights; use sp_runtime::traits::{BadOrigin, Hash, Saturating}; -use sp_std::{convert::TryFrom, prelude::*}; +use sp_std::prelude::*; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ diff --git a/frame/proxy/src/lib.rs b/frame/proxy/src/lib.rs index 84f1a84badc90..891315c92aa39 100644 --- a/frame/proxy/src/lib.rs +++ b/frame/proxy/src/lib.rs @@ -48,7 +48,7 @@ use sp_runtime::{ traits::{Dispatchable, Hash, Saturating, TrailingZeroInput, Zero}, DispatchResult, }; -use sp_std::{convert::TryInto, prelude::*}; +use sp_std::prelude::*; pub use weights::WeightInfo; pub use pallet::*; diff --git a/frame/randomness-collective-flip/src/lib.rs b/frame/randomness-collective-flip/src/lib.rs index b72aa665550cf..f709578f6941a 100644 --- a/frame/randomness-collective-flip/src/lib.rs +++ b/frame/randomness-collective-flip/src/lib.rs @@ -71,7 +71,6 @@ use safe_mix::TripletMix; use codec::Encode; use frame_support::traits::Randomness; use sp_runtime::traits::{Hash, Saturating}; -use sp_std::prelude::*; const RANDOM_MATERIAL_LEN: u32 = 81; diff --git a/frame/staking/reward-curve/src/lib.rs b/frame/staking/reward-curve/src/lib.rs index 9b2f3100b487e..e66f6fde37599 100644 --- a/frame/staking/reward-curve/src/lib.rs +++ b/frame/staking/reward-curve/src/lib.rs @@ -24,7 +24,6 @@ use proc_macro::TokenStream; use proc_macro2::{Span, TokenStream as TokenStream2}; use proc_macro_crate::{crate_name, FoundCrate}; use quote::{quote, ToTokens}; -use std::convert::TryInto; use syn::parse::{Parse, ParseStream}; /// Accepts a number of expressions to create a instance of PiecewiseLinear which represents the diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 2a0716721dd51..872ed2e1af404 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -316,7 +316,7 @@ use sp_staking::{ offence::{Offence, OffenceError, ReportOffence}, EraIndex, SessionIndex, }; -use sp_std::{collections::btree_map::BTreeMap, convert::From, prelude::*}; +use sp_std::{collections::btree_map::BTreeMap, prelude::*}; pub use weights::WeightInfo; pub use pallet::{pallet::*, *}; diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 306bd34390d82..fa8c453c2b0fe 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -33,7 +33,7 @@ use sp_runtime::{ DispatchError, Perbill, Percent, }; use sp_staking::{EraIndex, SessionIndex}; -use sp_std::{cmp::max, convert::From, prelude::*}; +use sp_std::{cmp::max, prelude::*}; mod impls; diff --git a/frame/support/procedural/src/construct_runtime/expand/event.rs b/frame/support/procedural/src/construct_runtime/expand/event.rs index ef071a9fc7ef5..b242f9641562c 100644 --- a/frame/support/procedural/src/construct_runtime/expand/event.rs +++ b/frame/support/procedural/src/construct_runtime/expand/event.rs @@ -127,7 +127,7 @@ fn expand_event_conversion( Event::#variant_name(x) } } - impl #scrate::sp_std::convert::TryInto<#pallet_event> for Event { + impl TryInto<#pallet_event> for Event { type Error = (); fn try_into(self) -> #scrate::sp_std::result::Result<#pallet_event, Self::Error> { diff --git a/frame/support/procedural/src/construct_runtime/expand/origin.rs b/frame/support/procedural/src/construct_runtime/expand/origin.rs index 077bef3744a5a..342a002b52b9d 100644 --- a/frame/support/procedural/src/construct_runtime/expand/origin.rs +++ b/frame/support/procedural/src/construct_runtime/expand/origin.rs @@ -230,7 +230,7 @@ pub fn expand_outer_origin( } } - impl #scrate::sp_std::convert::TryFrom for #system_path::Origin<#runtime> { + impl TryFrom for #system_path::Origin<#runtime> { type Error = OriginCaller; fn try_from(x: OriginCaller) -> #scrate::sp_std::result::Result<#system_path::Origin<#runtime>, OriginCaller> @@ -359,7 +359,7 @@ fn expand_origin_pallet_conversions( } } - impl #scrate::sp_std::convert::TryFrom for #pallet_origin { + impl TryFrom for #pallet_origin { type Error = OriginCaller; fn try_from( x: OriginCaller, diff --git a/frame/support/procedural/src/pallet/expand/error.rs b/frame/support/procedural/src/pallet/expand/error.rs index 86b06d737decf..124e8b312ce39 100644 --- a/frame/support/procedural/src/pallet/expand/error.rs +++ b/frame/support/procedural/src/pallet/expand/error.rs @@ -162,7 +162,7 @@ pub fn expand_error(def: &mut Def) -> proc_macro2::TokenStream { #frame_support::sp_runtime::DispatchError::Module(#frame_support::sp_runtime::ModuleError { index, - error: core::convert::TryInto::try_into(encoded).expect("encoded error is resized to be equal to the maximum encoded error size; qed"), + error: TryInto::try_into(encoded).expect("encoded error is resized to be equal to the maximum encoded error size; qed"), message: Some(err.as_str()), }) } diff --git a/frame/support/src/error.rs b/frame/support/src/error.rs index 764376a4e1dc2..0ffe4334e2e30 100644 --- a/frame/support/src/error.rs +++ b/frame/support/src/error.rs @@ -152,7 +152,7 @@ macro_rules! decl_error { $crate::sp_runtime::DispatchError::Module($crate::sp_runtime::ModuleError { index, - error: core::convert::TryInto::try_into(error).expect("encoded error is resized to be equal to the maximum encoded error size; qed"), + error: TryInto::try_into(error).expect("encoded error is resized to be equal to the maximum encoded error size; qed"), message: Some(err.as_str()), }) } diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index 70a79c07189dc..714449eec7847 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -127,13 +127,11 @@ impl TypeId for PalletId { macro_rules! bounded_vec { ($ ($values:expr),* $(,)?) => { { - use $crate::sp_std::convert::TryInto as _; $crate::sp_std::vec![$($values),*].try_into().unwrap() } }; ( $value:expr ; $repetition:expr ) => { { - use $crate::sp_std::convert::TryInto as _; $crate::sp_std::vec![$value ; $repetition].try_into().unwrap() } } diff --git a/frame/support/src/storage/bounded_btree_map.rs b/frame/support/src/storage/bounded_btree_map.rs index 6190552476a45..eca4b17821c77 100644 --- a/frame/support/src/storage/bounded_btree_map.rs +++ b/frame/support/src/storage/bounded_btree_map.rs @@ -22,10 +22,7 @@ use crate::{ traits::{Get, TryCollect}, }; use codec::{Decode, Encode, MaxEncodedLen}; -use sp_std::{ - borrow::Borrow, collections::btree_map::BTreeMap, convert::TryFrom, marker::PhantomData, - ops::Deref, -}; +use sp_std::{borrow::Borrow, collections::btree_map::BTreeMap, marker::PhantomData, ops::Deref}; /// A bounded map based on a B-Tree. /// diff --git a/frame/support/src/storage/bounded_btree_set.rs b/frame/support/src/storage/bounded_btree_set.rs index 543b997e94e34..f38952bf545d9 100644 --- a/frame/support/src/storage/bounded_btree_set.rs +++ b/frame/support/src/storage/bounded_btree_set.rs @@ -22,10 +22,7 @@ use crate::{ traits::{Get, TryCollect}, }; use codec::{Decode, Encode, MaxEncodedLen}; -use sp_std::{ - borrow::Borrow, collections::btree_set::BTreeSet, convert::TryFrom, marker::PhantomData, - ops::Deref, -}; +use sp_std::{borrow::Borrow, collections::btree_set::BTreeSet, marker::PhantomData, ops::Deref}; /// A bounded set based on a B-Tree. /// @@ -324,7 +321,6 @@ pub mod test { use crate::Twox128; use frame_support::traits::ConstU32; use sp_io::TestExternalities; - use sp_std::convert::TryInto; crate::generate_storage_alias! { Prefix, Foo => Value>> } crate::generate_storage_alias! { Prefix, FooMap => Map<(Twox128, u32), BoundedBTreeSet>> } diff --git a/frame/support/src/storage/weak_bounded_vec.rs b/frame/support/src/storage/weak_bounded_vec.rs index aa6dc88eaa4f4..21cc487b49082 100644 --- a/frame/support/src/storage/weak_bounded_vec.rs +++ b/frame/support/src/storage/weak_bounded_vec.rs @@ -27,7 +27,7 @@ use core::{ ops::{Deref, Index, IndexMut}, slice::SliceIndex, }; -use sp_std::{convert::TryFrom, marker::PhantomData, prelude::*}; +use sp_std::{marker::PhantomData, prelude::*}; /// A weakly bounded vector. /// @@ -320,7 +320,6 @@ pub mod test { use crate::Twox128; use frame_support::traits::ConstU32; use sp_io::TestExternalities; - use sp_std::convert::TryInto; crate::generate_storage_alias! { Prefix, Foo => Value>> } crate::generate_storage_alias! { Prefix, FooMap => Map<(Twox128, u32), WeakBoundedVec>> } diff --git a/frame/uniques/src/benchmarking.rs b/frame/uniques/src/benchmarking.rs index d6223ec88f81b..19d3dfac6e5ac 100644 --- a/frame/uniques/src/benchmarking.rs +++ b/frame/uniques/src/benchmarking.rs @@ -30,7 +30,7 @@ use frame_support::{ }; use frame_system::RawOrigin as SystemOrigin; use sp_runtime::traits::Bounded; -use sp_std::{convert::TryInto, prelude::*}; +use sp_std::prelude::*; use crate::Pallet as Uniques; diff --git a/frame/vesting/src/lib.rs b/frame/vesting/src/lib.rs index 775902f223ba0..13841a0443ceb 100644 --- a/frame/vesting/src/lib.rs +++ b/frame/vesting/src/lib.rs @@ -74,7 +74,7 @@ use sp_runtime::{ }, RuntimeDebug, }; -use sp_std::{convert::TryInto, fmt::Debug, prelude::*}; +use sp_std::{fmt::Debug, prelude::*}; pub use vesting_info::*; pub use weights::WeightInfo; diff --git a/primitives/application-crypto/src/lib.rs b/primitives/application-crypto/src/lib.rs index b12fe72b271a6..05f89c40ef99f 100644 --- a/primitives/application-crypto/src/lib.rs +++ b/primitives/application-crypto/src/lib.rs @@ -42,7 +42,7 @@ pub use scale_info; #[cfg(feature = "std")] pub use serde; #[doc(hidden)] -pub use sp_std::{convert::TryFrom, ops::Deref, vec::Vec}; +pub use sp_std::{ops::Deref, vec::Vec}; pub mod ecdsa; pub mod ed25519; @@ -363,7 +363,7 @@ macro_rules! app_crypto_public_common { } } - impl<'a> $crate::TryFrom<&'a [u8]> for Public { + impl<'a> TryFrom<&'a [u8]> for Public { type Error = (); fn try_from(data: &'a [u8]) -> Result { @@ -518,7 +518,7 @@ macro_rules! app_crypto_signature_common { type Generic = $sig; } - impl<'a> $crate::TryFrom<&'a [u8]> for Signature { + impl<'a> TryFrom<&'a [u8]> for Signature { type Error = (); fn try_from(data: &'a [u8]) -> Result { @@ -526,7 +526,7 @@ macro_rules! app_crypto_signature_common { } } - impl $crate::TryFrom<$crate::Vec> for Signature { + impl TryFrom<$crate::Vec> for Signature { type Error = (); fn try_from(data: $crate::Vec) -> Result { diff --git a/primitives/arithmetic/fuzzer/src/biguint.rs b/primitives/arithmetic/fuzzer/src/biguint.rs index e4c088a2e8ab3..f49743a4b8a69 100644 --- a/primitives/arithmetic/fuzzer/src/biguint.rs +++ b/primitives/arithmetic/fuzzer/src/biguint.rs @@ -29,7 +29,6 @@ use honggfuzz::fuzz; use sp_arithmetic::biguint::{BigUint, Single}; -use std::convert::TryFrom; fn main() { loop { diff --git a/primitives/arithmetic/fuzzer/src/normalize.rs b/primitives/arithmetic/fuzzer/src/normalize.rs index e4f90dbc1c415..dd717115a5c9c 100644 --- a/primitives/arithmetic/fuzzer/src/normalize.rs +++ b/primitives/arithmetic/fuzzer/src/normalize.rs @@ -25,7 +25,6 @@ use honggfuzz::fuzz; use sp_arithmetic::Normalizable; -use std::convert::TryInto; type Ty = u64; diff --git a/primitives/arithmetic/src/biguint.rs b/primitives/arithmetic/src/biguint.rs index b26ac4294d111..33f0960ee378c 100644 --- a/primitives/arithmetic/src/biguint.rs +++ b/primitives/arithmetic/src/biguint.rs @@ -19,7 +19,7 @@ use codec::{Decode, Encode}; use num_traits::{One, Zero}; -use sp_std::{cell::RefCell, cmp::Ordering, convert::TryFrom, ops, prelude::*, vec}; +use sp_std::{cell::RefCell, cmp::Ordering, ops, prelude::*, vec}; // A sensible value for this would be half of the dword size of the host machine. Since the // runtime is compiled to 32bit webassembly, using 32 and 64 for single and double respectively @@ -664,7 +664,6 @@ pub mod tests { #[test] fn can_try_build_numbers_from_types() { - use sp_std::convert::TryFrom; assert_eq!(u64::try_from(with_limbs(1)).unwrap(), 1); assert_eq!(u64::try_from(with_limbs(2)).unwrap(), u32::MAX as u64 + 2); assert_eq!(u64::try_from(with_limbs(3)).unwrap_err(), "cannot fit a number into u64"); diff --git a/primitives/arithmetic/src/fixed_point.rs b/primitives/arithmetic/src/fixed_point.rs index 1c61d6c3806ae..3ecfb60ee0f52 100644 --- a/primitives/arithmetic/src/fixed_point.rs +++ b/primitives/arithmetic/src/fixed_point.rs @@ -27,7 +27,6 @@ use crate::{ }; use codec::{CompactAs, Decode, Encode}; use sp_std::{ - convert::{TryFrom, TryInto}, fmt::Debug, ops::{self, Add, Div, Mul, Sub}, prelude::*, diff --git a/primitives/arithmetic/src/helpers_128bit.rs b/primitives/arithmetic/src/helpers_128bit.rs index af9729c9702c3..735b11287cbe4 100644 --- a/primitives/arithmetic/src/helpers_128bit.rs +++ b/primitives/arithmetic/src/helpers_128bit.rs @@ -24,7 +24,6 @@ use crate::biguint; use num_traits::Zero; use sp_std::{ cmp::{max, min}, - convert::TryInto, mem, }; diff --git a/primitives/arithmetic/src/lib.rs b/primitives/arithmetic/src/lib.rs index 273608a3d1711..729da123757c7 100644 --- a/primitives/arithmetic/src/lib.rs +++ b/primitives/arithmetic/src/lib.rs @@ -44,7 +44,7 @@ pub use fixed_point::{FixedI128, FixedI64, FixedPointNumber, FixedPointOperand, pub use per_things::{InnerOf, PerThing, PerU16, Perbill, Percent, Permill, Perquintill, UpperOf}; pub use rational::{Rational128, RationalInfinite}; -use sp_std::{cmp::Ordering, convert::TryInto, fmt::Debug, prelude::*}; +use sp_std::{cmp::Ordering, fmt::Debug, prelude::*}; use traits::{BaseArithmetic, One, SaturatedConversion, Unsigned, Zero}; /// Trait for comparing two numbers with an threshold. diff --git a/primitives/arithmetic/src/per_things.rs b/primitives/arithmetic/src/per_things.rs index c3ccca56ca33f..1b9e6d91a2cd3 100644 --- a/primitives/arithmetic/src/per_things.rs +++ b/primitives/arithmetic/src/per_things.rs @@ -25,7 +25,6 @@ use crate::traits::{ use codec::{CompactAs, Encode}; use num_traits::{Pow, SaturatingAdd, SaturatingSub}; use sp_std::{ - convert::{TryFrom, TryInto}, fmt, ops, ops::{Add, Sub}, prelude::*, diff --git a/primitives/arithmetic/src/traits.rs b/primitives/arithmetic/src/traits.rs index 447f8cef51f9a..748aaed2a7cf5 100644 --- a/primitives/arithmetic/src/traits.rs +++ b/primitives/arithmetic/src/traits.rs @@ -23,12 +23,8 @@ pub use num_traits::{ checked_pow, Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedRem, CheckedShl, CheckedShr, CheckedSub, One, Signed, Unsigned, Zero, }; -use sp_std::{ - self, - convert::{TryFrom, TryInto}, - ops::{ - Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, RemAssign, Shl, Shr, Sub, SubAssign, - }, +use sp_std::ops::{ + Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, RemAssign, Shl, Shr, Sub, SubAssign, }; /// A meta trait for arithmetic type operations, regardless of any limitation on size. diff --git a/primitives/consensus/common/src/error.rs b/primitives/consensus/common/src/error.rs index 280ff2322898d..0656b5761fb38 100644 --- a/primitives/consensus/common/src/error.rs +++ b/primitives/consensus/common/src/error.rs @@ -86,13 +86,13 @@ pub enum Error { CannotSign(Vec, String), } -impl core::convert::From for Error { +impl From for Error { fn from(p: Public) -> Self { Self::InvalidAuthority(p) } } -impl core::convert::From for Error { +impl From for Error { fn from(s: String) -> Self { Self::StateUnavailable(s) } diff --git a/primitives/consensus/vrf/src/schnorrkel.rs b/primitives/consensus/vrf/src/schnorrkel.rs index 1ef23427e0ed0..094a398893ff6 100644 --- a/primitives/consensus/vrf/src/schnorrkel.rs +++ b/primitives/consensus/vrf/src/schnorrkel.rs @@ -21,7 +21,6 @@ use codec::{Decode, Encode, EncodeLike}; use schnorrkel::errors::MultiSignatureStage; use sp_core::U512; use sp_std::{ - convert::TryFrom, ops::{Deref, DerefMut}, prelude::*, }; diff --git a/primitives/core/src/crypto.rs b/primitives/core/src/crypto.rs index 12f8397eee4a4..f994da1515350 100644 --- a/primitives/core/src/crypto.rs +++ b/primitives/core/src/crypto.rs @@ -38,7 +38,7 @@ pub use secrecy::SecretString; use sp_runtime_interface::pass_by::PassByInner; #[doc(hidden)] pub use sp_std::ops::Deref; -use sp_std::{convert::TryFrom, hash::Hash, str, vec::Vec}; +use sp_std::{hash::Hash, str, vec::Vec}; /// Trait to zeroize a memory buffer. pub use zeroize::Zeroize; @@ -535,7 +535,7 @@ impl From<[u8; 32]> for AccountId32 { } } -impl<'a> sp_std::convert::TryFrom<&'a [u8]> for AccountId32 { +impl<'a> TryFrom<&'a [u8]> for AccountId32 { type Error = (); fn try_from(x: &'a [u8]) -> Result { if x.len() == 32 { diff --git a/primitives/core/src/ecdsa.rs b/primitives/core/src/ecdsa.rs index 7a4e4399913dc..6343e3f4dfd0d 100644 --- a/primitives/core/src/ecdsa.rs +++ b/primitives/core/src/ecdsa.rs @@ -137,7 +137,7 @@ impl AsMut<[u8]> for Public { } } -impl sp_std::convert::TryFrom<&[u8]> for Public { +impl TryFrom<&[u8]> for Public { type Error = (); fn try_from(data: &[u8]) -> Result { @@ -209,7 +209,7 @@ impl<'de> Deserialize<'de> for Public { #[derive(Encode, Decode, MaxEncodedLen, PassByInner, TypeInfo, PartialEq, Eq)] pub struct Signature(pub [u8; 65]); -impl sp_std::convert::TryFrom<&[u8]> for Signature { +impl TryFrom<&[u8]> for Signature { type Error = (); fn try_from(data: &[u8]) -> Result { diff --git a/primitives/core/src/ed25519.rs b/primitives/core/src/ed25519.rs index 555c23c6b60cc..0bde9e2e5303a 100644 --- a/primitives/core/src/ed25519.rs +++ b/primitives/core/src/ed25519.rs @@ -39,8 +39,6 @@ use crate::crypto::{DeriveJunction, Pair as TraitPair, SecretStringError}; #[cfg(feature = "std")] use bip39::{Language, Mnemonic, MnemonicType}; #[cfg(feature = "full_crypto")] -use core::convert::TryFrom; -#[cfg(feature = "full_crypto")] use ed25519_dalek::{Signer as _, Verifier as _}; #[cfg(feature = "std")] use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; @@ -116,7 +114,7 @@ impl Deref for Public { } } -impl sp_std::convert::TryFrom<&[u8]> for Public { +impl TryFrom<&[u8]> for Public { type Error = (); fn try_from(data: &[u8]) -> Result { @@ -215,7 +213,7 @@ impl<'de> Deserialize<'de> for Public { #[derive(Encode, Decode, MaxEncodedLen, PassByInner, TypeInfo, PartialEq, Eq)] pub struct Signature(pub [u8; 64]); -impl sp_std::convert::TryFrom<&[u8]> for Signature { +impl TryFrom<&[u8]> for Signature { type Error = (); fn try_from(data: &[u8]) -> Result { diff --git a/primitives/core/src/offchain/mod.rs b/primitives/core/src/offchain/mod.rs index b9e310dc1fb10..4ffadc3e4031d 100644 --- a/primitives/core/src/offchain/mod.rs +++ b/primitives/core/src/offchain/mod.rs @@ -21,10 +21,7 @@ use crate::{OpaquePeerId, RuntimeDebug}; use codec::{Decode, Encode}; use scale_info::TypeInfo; use sp_runtime_interface::pass_by::{PassByCodec, PassByEnum, PassByInner}; -use sp_std::{ - convert::TryFrom, - prelude::{Box, Vec}, -}; +use sp_std::prelude::{Box, Vec}; pub use crate::crypto::KeyTypeId; diff --git a/primitives/core/src/sr25519.rs b/primitives/core/src/sr25519.rs index 2f298fa2a2666..ef033c2099b5f 100644 --- a/primitives/core/src/sr25519.rs +++ b/primitives/core/src/sr25519.rs @@ -35,8 +35,6 @@ use schnorrkel::{ #[cfg(feature = "full_crypto")] use sp_std::vec::Vec; #[cfg(feature = "std")] -use std::convert::TryFrom; -#[cfg(feature = "std")] use substrate_bip39::mini_secret_from_entropy; use crate::{ @@ -142,7 +140,7 @@ impl std::str::FromStr for Public { } } -impl sp_std::convert::TryFrom<&[u8]> for Public { +impl TryFrom<&[u8]> for Public { type Error = (); fn try_from(data: &[u8]) -> Result { @@ -215,7 +213,7 @@ impl<'de> Deserialize<'de> for Public { #[derive(Encode, Decode, MaxEncodedLen, PassByInner, TypeInfo, PartialEq, Eq)] pub struct Signature(pub [u8; 64]); -impl sp_std::convert::TryFrom<&[u8]> for Signature { +impl TryFrom<&[u8]> for Signature { type Error = (); fn try_from(data: &[u8]) -> Result { diff --git a/primitives/rpc/src/number.rs b/primitives/rpc/src/number.rs index 5a433a9598e01..81084a09d4ac8 100644 --- a/primitives/rpc/src/number.rs +++ b/primitives/rpc/src/number.rs @@ -20,10 +20,7 @@ use serde::{Deserialize, Serialize}; use sp_core::U256; -use std::{ - convert::{TryFrom, TryInto}, - fmt::Debug, -}; +use std::fmt::Debug; /// A number type that can be serialized both as a number or a string that encodes a number in a /// string. diff --git a/primitives/runtime-interface/proc-macro/src/pass_by/enum_.rs b/primitives/runtime-interface/proc-macro/src/pass_by/enum_.rs index 1fd22c6a25383..7c3f066f6c83b 100644 --- a/primitives/runtime-interface/proc-macro/src/pass_by/enum_.rs +++ b/primitives/runtime-interface/proc-macro/src/pass_by/enum_.rs @@ -51,7 +51,7 @@ pub fn derive_impl(input: DeriveInput) -> Result { type PassBy = #crate_::pass_by::Enum<#ident>; } - impl #crate_::sp_std::convert::TryFrom for #ident { + impl TryFrom for #ident { type Error = (); fn try_from(inner: u8) -> #crate_::sp_std::result::Result { diff --git a/primitives/runtime-interface/src/pass_by.rs b/primitives/runtime-interface/src/pass_by.rs index fb2d6b818d174..5d895ff5b3f82 100644 --- a/primitives/runtime-interface/src/pass_by.rs +++ b/primitives/runtime-interface/src/pass_by.rs @@ -33,7 +33,7 @@ use crate::wasm::*; #[cfg(feature = "std")] use sp_wasm_interface::{FunctionContext, Pointer, Result}; -use sp_std::{convert::TryFrom, marker::PhantomData}; +use sp_std::marker::PhantomData; #[cfg(not(feature = "std"))] use sp_std::vec::Vec; @@ -382,7 +382,7 @@ impl, I: RIType> RIType for Inner { /// } /// } /// -/// impl std::convert::TryFrom for Test { +/// impl TryFrom for Test { /// type Error = (); /// /// fn try_from(val: u8) -> Result { diff --git a/primitives/runtime-interface/test-wasm/src/lib.rs b/primitives/runtime-interface/test-wasm/src/lib.rs index 0c8a9c04ab1e9..f518a2e17498c 100644 --- a/primitives/runtime-interface/test-wasm/src/lib.rs +++ b/primitives/runtime-interface/test-wasm/src/lib.rs @@ -22,7 +22,7 @@ use sp_runtime_interface::runtime_interface; #[cfg(not(feature = "std"))] -use sp_std::{convert::TryFrom, mem, prelude::*}; +use sp_std::{mem, prelude::*}; use sp_core::{sr25519::Public, wasm_export_functions}; diff --git a/primitives/runtime/src/curve.rs b/primitives/runtime/src/curve.rs index b5532c3d8cef7..c6bfa66017870 100644 --- a/primitives/runtime/src/curve.rs +++ b/primitives/runtime/src/curve.rs @@ -110,8 +110,6 @@ where #[test] fn test_multiply_by_rational_saturating() { - use std::convert::TryInto; - let div = 100u32; for value in 0..=div { for p in 0..=div { @@ -132,8 +130,6 @@ fn test_multiply_by_rational_saturating() { #[test] fn test_calculate_for_fraction_times_denominator() { - use std::convert::TryInto; - let curve = PiecewiseLinear { points: &[ (Perbill::from_parts(0_000_000_000), Perbill::from_parts(0_500_000_000)), diff --git a/primitives/runtime/src/generic/header.rs b/primitives/runtime/src/generic/header.rs index 3e1a673d257a1..a7b43608f2b78 100644 --- a/primitives/runtime/src/generic/header.rs +++ b/primitives/runtime/src/generic/header.rs @@ -29,7 +29,7 @@ use crate::{ #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; use sp_core::U256; -use sp_std::{convert::TryFrom, fmt::Debug}; +use sp_std::fmt::Debug; /// Abstraction over a block header for a substrate chain. #[derive(Encode, Decode, PartialEq, Eq, Clone, sp_core::RuntimeDebug, TypeInfo)] diff --git a/primitives/runtime/src/lib.rs b/primitives/runtime/src/lib.rs index c09db5124cc1f..39e606eb9b5f4 100644 --- a/primitives/runtime/src/lib.rs +++ b/primitives/runtime/src/lib.rs @@ -50,7 +50,7 @@ use sp_core::{ hash::{H256, H512}, sr25519, }; -use sp_std::{convert::TryFrom, prelude::*}; +use sp_std::prelude::*; use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; diff --git a/primitives/runtime/src/traits.rs b/primitives/runtime/src/traits.rs index ba4ca790a9198..9c71b2023d3f8 100644 --- a/primitives/runtime/src/traits.rs +++ b/primitives/runtime/src/traits.rs @@ -37,13 +37,7 @@ pub use sp_arithmetic::traits::{ UniqueSaturatedFrom, UniqueSaturatedInto, Zero, }; use sp_core::{self, storage::StateVersion, Hasher, RuntimeDebug, TypeId}; -use sp_std::{ - self, - convert::{TryFrom, TryInto}, - fmt::Debug, - marker::PhantomData, - prelude::*, -}; +use sp_std::{self, fmt::Debug, marker::PhantomData, prelude::*}; #[cfg(feature = "std")] use std::fmt::Display; #[cfg(feature = "std")] diff --git a/primitives/std/src/lib.rs b/primitives/std/src/lib.rs index 05dff0f3077b6..6653c3d7eadec 100644 --- a/primitives/std/src/lib.rs +++ b/primitives/std/src/lib.rs @@ -103,7 +103,6 @@ pub mod prelude { boxed::Box, clone::Clone, cmp::{Eq, PartialEq, Reverse}, - convert::{TryFrom, TryInto}, iter::IntoIterator, vec::Vec, }; diff --git a/primitives/storage/src/lib.rs b/primitives/storage/src/lib.rs index d377ea931df2d..fecd2b24dbb00 100644 --- a/primitives/storage/src/lib.rs +++ b/primitives/storage/src/lib.rs @@ -423,7 +423,7 @@ impl From for u8 { } } -impl sp_std::convert::TryFrom for StateVersion { +impl TryFrom for StateVersion { type Error = (); fn try_from(val: u8) -> sp_std::result::Result { match val { diff --git a/primitives/wasm-interface/src/lib.rs b/primitives/wasm-interface/src/lib.rs index d57666f126899..6dfc3116ddc48 100644 --- a/primitives/wasm-interface/src/lib.rs +++ b/primitives/wasm-interface/src/lib.rs @@ -73,7 +73,7 @@ impl From for u8 { } } -impl sp_std::convert::TryFrom for ValueType { +impl TryFrom for ValueType { type Error = (); fn try_from(val: u8) -> sp_std::result::Result { diff --git a/utils/prometheus/src/lib.rs b/utils/prometheus/src/lib.rs index 1892741eff5da..3ea9d45d48b11 100644 --- a/utils/prometheus/src/lib.rs +++ b/utils/prometheus/src/lib.rs @@ -122,7 +122,6 @@ async fn init_prometheus_with_listener( mod tests { use super::*; use hyper::{Client, Uri}; - use std::convert::TryFrom; #[test] fn prometheus_works() { From ca77a5767c924e063edb16b4c2bbef8cb9394be8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Apr 2022 11:21:45 +0200 Subject: [PATCH 082/484] Bump backtrace from 0.3.63 to 0.3.64 (#11157) Bumps [backtrace](https://github.com/rust-lang/backtrace-rs) from 0.3.63 to 0.3.64. - [Release notes](https://github.com/rust-lang/backtrace-rs/releases) - [Commits](https://github.com/rust-lang/backtrace-rs/compare/0.3.63...0.3.64) --- updated-dependencies: - dependency-name: backtrace dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- primitives/panic-handler/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6fb6f5814565a..74ad907f55d3f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -400,9 +400,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "backtrace" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "321629d8ba6513061f26707241fa9bc89524ff1cd7a915a97ef0c62c666ce1b6" +checksum = "5e121dee8023ce33ab248d9ce1493df03c3b38a659b240096fcbd7048ff9c31f" dependencies = [ "addr2line", "cc", diff --git a/primitives/panic-handler/Cargo.toml b/primitives/panic-handler/Cargo.toml index 0155b6532876c..7b2023eff0bfc 100644 --- a/primitives/panic-handler/Cargo.toml +++ b/primitives/panic-handler/Cargo.toml @@ -14,6 +14,6 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -backtrace = "0.3.63" +backtrace = "0.3.64" regex = "1.5.5" lazy_static = "1.4.0" From 346850538cfddbe9f9bc57a7c36efc56707281e8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Apr 2022 11:22:18 +0200 Subject: [PATCH 083/484] Bump dyn-clone from 1.0.4 to 1.0.5 (#11048) Bumps [dyn-clone](https://github.com/dtolnay/dyn-clone) from 1.0.4 to 1.0.5. - [Release notes](https://github.com/dtolnay/dyn-clone/releases) - [Commits](https://github.com/dtolnay/dyn-clone/compare/1.0.4...1.0.5) --- updated-dependencies: - dependency-name: dyn-clone dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 74ad907f55d3f..690661dacaee8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1751,9 +1751,9 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf" +checksum = "21e50f3adc76d6a43f5ed73b698a87d0760ca74617f60f7c3b879003536fdd28" [[package]] name = "dynasm" From caf3d8406de8ffe5cb974d97e9bab5f7f4513db5 Mon Sep 17 00:00:00 2001 From: moh-eulith <101080211+moh-eulith@users.noreply.github.com> Date: Mon, 4 Apr 2022 05:23:17 -0400 Subject: [PATCH 084/484] Update kvdb-rocksdb to 0.15.2 (#11144) --- Cargo.lock | 4 ++-- client/db/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 690661dacaee8..23f11860ca6f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3616,9 +3616,9 @@ dependencies = [ [[package]] name = "kvdb-rocksdb" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e72a631a32527fafe22d0751c002e67d28173c49dcaecf79d1aaa323c520e9" +checksum = "ca7fbdfd71cd663dceb0faf3367a99f8cf724514933e9867cec4995b6027cbc1" dependencies = [ "fs-swap", "kvdb", diff --git a/client/db/Cargo.toml b/client/db/Cargo.toml index 105bc43c61bc9..12bb29958cfec 100644 --- a/client/db/Cargo.toml +++ b/client/db/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] parking_lot = "0.12.0" log = "0.4.8" kvdb = "0.11.0" -kvdb-rocksdb = { version = "0.15.1", optional = true } +kvdb-rocksdb = { version = "0.15.2", optional = true } kvdb-memorydb = "0.11.0" linked-hash-map = "0.5.4" hash-db = "0.15.2" From bd242e882e2c1cb73b803c59e1fc1927715af192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Mon, 4 Apr 2022 13:19:46 +0200 Subject: [PATCH 085/484] Revert "Replace storage access by atomics" (#11156) This reverts commit a69b8eb4a28f365a4a4b2fc295a693ec4d3d3cd5. --- frame/support/src/storage/mod.rs | 54 +++++++++++++++++--------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/frame/support/src/storage/mod.rs b/frame/support/src/storage/mod.rs index c9814e28a7ae4..106d3e3f459aa 100644 --- a/frame/support/src/storage/mod.rs +++ b/frame/support/src/storage/mod.rs @@ -48,46 +48,48 @@ pub mod unhashed; pub mod weak_bounded_vec; mod transaction_level_tracker { - use core::sync::atomic::{AtomicU32, Ordering}; - type Layer = u32; - static NUM_LEVELS: AtomicU32 = AtomicU32::new(0); + const TRANSACTION_LEVEL_KEY: &'static [u8] = b":transaction_level:"; const TRANSACTIONAL_LIMIT: Layer = 255; pub fn get_transaction_level() -> Layer { - NUM_LEVELS.load(Ordering::SeqCst) + crate::storage::unhashed::get_or_default::(TRANSACTION_LEVEL_KEY) + } + + fn set_transaction_level(level: &Layer) { + crate::storage::unhashed::put::(TRANSACTION_LEVEL_KEY, level); + } + + fn kill_transaction_level() { + crate::storage::unhashed::kill(TRANSACTION_LEVEL_KEY); } /// Increments the transaction level. Returns an error if levels go past the limit. /// /// Returns a guard that when dropped decrements the transaction level automatically. pub fn inc_transaction_level() -> Result { - NUM_LEVELS - .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |existing_levels| { - if existing_levels >= TRANSACTIONAL_LIMIT { - return None - } - // Cannot overflow because of check above. - Some(existing_levels + 1) - }) - .map_err(|_| ())?; + let existing_levels = get_transaction_level(); + if existing_levels >= TRANSACTIONAL_LIMIT { + return Err(()) + } + // Cannot overflow because of check above. + set_transaction_level(&(existing_levels + 1)); Ok(StorageLayerGuard) } fn dec_transaction_level() { - NUM_LEVELS - .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |existing_levels| { - if existing_levels == 0 { - log::warn!( - "We are underflowing with calculating transactional levels. Not great, but let's not panic...", - ); - None - } else { - // Cannot underflow because of checks above. - Some(existing_levels - 1) - } - }) - .ok(); + let existing_levels = get_transaction_level(); + if existing_levels == 0 { + log::warn!( + "We are underflowing with calculating transactional levels. Not great, but let's not panic...", + ); + } else if existing_levels == 1 { + // Don't leave any trace of this storage item. + kill_transaction_level(); + } else { + // Cannot underflow because of checks above. + set_transaction_level(&(existing_levels - 1)); + } } pub fn is_transactional() -> bool { From 1b798831f2a09f153b13c070fd445413ee0aba92 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Mon, 4 Apr 2022 16:29:29 +0200 Subject: [PATCH 086/484] Remove not required DigestItem conversion (#11165) --- frame/aura/src/lib.rs | 4 ++-- frame/babe/src/lib.rs | 2 +- frame/grandpa/src/lib.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frame/aura/src/lib.rs b/frame/aura/src/lib.rs index 657965c60a3f1..0f474770017d5 100644 --- a/frame/aura/src/lib.rs +++ b/frame/aura/src/lib.rs @@ -153,7 +153,7 @@ impl Pallet { AURA_ENGINE_ID, ConsensusLog::AuthoritiesChange(new.into_inner()).encode(), ); - >::deposit_log(log.into()); + >::deposit_log(log); } fn initialize_authorities(authorities: &[T::AuthorityId]) { @@ -225,7 +225,7 @@ impl OneSessionHandler for Pallet { ConsensusLog::::OnDisabled(i as AuthorityIndex).encode(), ); - >::deposit_log(log.into()); + >::deposit_log(log); } } diff --git a/frame/babe/src/lib.rs b/frame/babe/src/lib.rs index f673c8b43bee0..87ae762707ccd 100644 --- a/frame/babe/src/lib.rs +++ b/frame/babe/src/lib.rs @@ -627,7 +627,7 @@ impl Pallet { fn deposit_consensus(new: U) { let log = DigestItem::Consensus(BABE_ENGINE_ID, new.encode()); - >::deposit_log(log.into()) + >::deposit_log(log) } fn deposit_randomness(randomness: &schnorrkel::Randomness) { diff --git a/frame/grandpa/src/lib.rs b/frame/grandpa/src/lib.rs index 68d4cf26a2e23..e30d65acbc6a6 100644 --- a/frame/grandpa/src/lib.rs +++ b/frame/grandpa/src/lib.rs @@ -498,7 +498,7 @@ impl Pallet { /// Deposit one of this module's logs. fn deposit_log(log: ConsensusLog) { let log = DigestItem::Consensus(GRANDPA_ENGINE_ID, log.encode()); - >::deposit_log(log.into()); + >::deposit_log(log); } // Perform module initialization, abstracted so that it can be called either through genesis From c00d67d7385f22b3db21029d715078911afc68a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Mon, 4 Apr 2022 22:28:57 +0200 Subject: [PATCH 087/484] transactional: Fix some nitpicks (#11163) * transactional: Fix some nitpicks This fixes some nitpicks related to the transactional storage stuff from me. As everything was merged too fast, here are some nitpicks from me. First, the entire functionality is moved into its own file to have a clear separation. Secondly I changed the `set_transactional_level` to not take `Layer` by reference. Besides that I have added some docs etc. * Add some comment * Move tests * :facepalm: --- frame/support/procedural/src/transactional.rs | 2 +- frame/support/src/storage/mod.rs | 212 +--------------- frame/support/src/storage/transactional.rs | 232 ++++++++++++++++++ 3 files changed, 242 insertions(+), 204 deletions(-) create mode 100644 frame/support/src/storage/transactional.rs diff --git a/frame/support/procedural/src/transactional.rs b/frame/support/procedural/src/transactional.rs index ba75fbc9737aa..eb77a320a509f 100644 --- a/frame/support/procedural/src/transactional.rs +++ b/frame/support/procedural/src/transactional.rs @@ -49,7 +49,7 @@ pub fn require_transactional(_attr: TokenStream, input: TokenStream) -> Result Layer { - crate::storage::unhashed::get_or_default::(TRANSACTION_LEVEL_KEY) - } - - fn set_transaction_level(level: &Layer) { - crate::storage::unhashed::put::(TRANSACTION_LEVEL_KEY, level); - } - - fn kill_transaction_level() { - crate::storage::unhashed::kill(TRANSACTION_LEVEL_KEY); - } - - /// Increments the transaction level. Returns an error if levels go past the limit. - /// - /// Returns a guard that when dropped decrements the transaction level automatically. - pub fn inc_transaction_level() -> Result { - let existing_levels = get_transaction_level(); - if existing_levels >= TRANSACTIONAL_LIMIT { - return Err(()) - } - // Cannot overflow because of check above. - set_transaction_level(&(existing_levels + 1)); - Ok(StorageLayerGuard) - } - - fn dec_transaction_level() { - let existing_levels = get_transaction_level(); - if existing_levels == 0 { - log::warn!( - "We are underflowing with calculating transactional levels. Not great, but let's not panic...", - ); - } else if existing_levels == 1 { - // Don't leave any trace of this storage item. - kill_transaction_level(); - } else { - // Cannot underflow because of checks above. - set_transaction_level(&(existing_levels - 1)); - } - } - - pub fn is_transactional() -> bool { - get_transaction_level() > 0 - } - - pub struct StorageLayerGuard; - - impl Drop for StorageLayerGuard { - fn drop(&mut self) { - dec_transaction_level() - } - } -} - -/// Check if the current call is within a transactional layer. -pub fn is_transactional() -> bool { - transaction_level_tracker::is_transactional() -} - -/// Execute the supplied function in a new storage transaction. -/// -/// All changes to storage performed by the supplied function are discarded if the returned -/// outcome is `TransactionOutcome::Rollback`. -/// -/// Transactions can be nested up to `TRANSACTIONAL_LIMIT` times; more than that will result in an -/// error. -/// -/// Commits happen to the parent transaction. -pub fn with_transaction(f: impl FnOnce() -> TransactionOutcome>) -> Result -where - E: From, -{ - use sp_io::storage::{commit_transaction, rollback_transaction, start_transaction}; - use TransactionOutcome::*; - - let _guard = transaction_level_tracker::inc_transaction_level() - .map_err(|()| TransactionalError::LimitReached.into())?; - - start_transaction(); - - match f() { - Commit(res) => { - commit_transaction(); - res - }, - Rollback(res) => { - rollback_transaction(); - res - }, - } -} - -/// Same as [`with_transaction`] but without a limit check on nested transactional layers. -/// -/// This is mostly for backwards compatibility before there was a transactional layer limit. -/// It is recommended to only use [`with_transaction`] to avoid users from generating too many -/// transactional layers. -pub fn with_transaction_unchecked(f: impl FnOnce() -> TransactionOutcome) -> R { - use sp_io::storage::{commit_transaction, rollback_transaction, start_transaction}; - use TransactionOutcome::*; - - let maybe_guard = transaction_level_tracker::inc_transaction_level(); - - if maybe_guard.is_err() { - log::warn!( - "The transactional layer limit has been reached, and new transactional layers are being - spawned with `with_transaction_unchecked`. This could be caused by someone trying to - attack your chain, and you should investigate usage of `with_transaction_unchecked` and - potentially migrate to `with_transaction`, which enforces a transactional limit.", - ); - } - - start_transaction(); - - match f() { - Commit(res) => { - commit_transaction(); - res - }, - Rollback(res) => { - rollback_transaction(); - res - }, - } -} - /// A trait for working with macro-generated storage values under the substrate storage API. /// /// Details on implementation can be found at [`generator::StorageValue`]. @@ -1473,13 +1345,12 @@ pub fn storage_prefix(pallet_name: &[u8], storage_name: &[u8]) -> [u8; 32] { #[cfg(test)] mod test { use super::*; - use crate::{assert_noop, assert_ok, hash::Identity, Twox128}; + use crate::{assert_ok, hash::Identity, Twox128}; use bounded_vec::BoundedVec; use frame_support::traits::ConstU32; use generator::StorageValue as _; use sp_core::hashing::twox_128; use sp_io::TestExternalities; - use sp_runtime::DispatchResult; use weak_bounded_vec::WeakBoundedVec; #[test] @@ -1590,71 +1461,6 @@ mod test { }); } - #[test] - fn is_transactional_should_return_false() { - TestExternalities::default().execute_with(|| { - assert!(!is_transactional()); - }); - } - - #[test] - fn is_transactional_should_not_error_in_with_transaction() { - TestExternalities::default().execute_with(|| { - assert_ok!(with_transaction(|| -> TransactionOutcome { - assert!(is_transactional()); - TransactionOutcome::Commit(Ok(())) - })); - - assert_noop!( - with_transaction(|| -> TransactionOutcome { - assert!(is_transactional()); - TransactionOutcome::Rollback(Err("revert".into())) - }), - "revert" - ); - }); - } - - fn recursive_transactional(num: u32) -> DispatchResult { - if num == 0 { - return Ok(()) - } - - with_transaction(|| -> TransactionOutcome { - let res = recursive_transactional(num - 1); - TransactionOutcome::Commit(res) - }) - } - - #[test] - fn transaction_limit_should_work() { - TestExternalities::default().execute_with(|| { - assert_eq!(transaction_level_tracker::get_transaction_level(), 0); - - assert_ok!(with_transaction(|| -> TransactionOutcome { - assert_eq!(transaction_level_tracker::get_transaction_level(), 1); - TransactionOutcome::Commit(Ok(())) - })); - - assert_ok!(with_transaction(|| -> TransactionOutcome { - assert_eq!(transaction_level_tracker::get_transaction_level(), 1); - let res = with_transaction(|| -> TransactionOutcome { - assert_eq!(transaction_level_tracker::get_transaction_level(), 2); - TransactionOutcome::Commit(Ok(())) - }); - TransactionOutcome::Commit(res) - })); - - assert_ok!(recursive_transactional(255)); - assert_noop!( - recursive_transactional(256), - sp_runtime::TransactionalError::LimitReached - ); - - assert_eq!(transaction_level_tracker::get_transaction_level(), 0); - }); - } - #[test] fn key_prefix_iterator_works() { TestExternalities::default().execute_with(|| { diff --git a/frame/support/src/storage/transactional.rs b/frame/support/src/storage/transactional.rs new file mode 100644 index 0000000000000..d1c59d44e2581 --- /dev/null +++ b/frame/support/src/storage/transactional.rs @@ -0,0 +1,232 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Provides functionality around the transaction storage. +//! +//! Transactional storage provides functionality to run an entire code block +//! in a storage transaction. This means that either the entire changes to the +//! storage are committed or everything is thrown away. This simplifies the +//! writing of functionality that may bail at any point of operation. Otherwise +//! you would need to first verify all storage accesses and then do the storage +//! modifications. +//! +//! [`with_transaction`] provides a way to run a given closure in a transactional context. + +use sp_io::storage::{commit_transaction, rollback_transaction, start_transaction}; +use sp_runtime::{DispatchError, TransactionOutcome, TransactionalError}; + +/// The type that is being used to store the current number of active layers. +type Layer = u32; +/// The key that is holds the current number of active layers. +const TRANSACTION_LEVEL_KEY: &[u8] = b":transaction_level:"; +/// The maximum number of nested layers. +const TRANSACTIONAL_LIMIT: Layer = 255; + +/// Returns the current number of nested transactional layers. +fn get_transaction_level() -> Layer { + crate::storage::unhashed::get_or_default::(TRANSACTION_LEVEL_KEY) +} + +/// Set the current number of nested transactional layers. +fn set_transaction_level(level: Layer) { + crate::storage::unhashed::put::(TRANSACTION_LEVEL_KEY, &level); +} + +/// Kill the transactional layers storage. +fn kill_transaction_level() { + crate::storage::unhashed::kill(TRANSACTION_LEVEL_KEY); +} + +/// Increments the transaction level. Returns an error if levels go past the limit. +/// +/// Returns a guard that when dropped decrements the transaction level automatically. +fn inc_transaction_level() -> Result { + let existing_levels = get_transaction_level(); + if existing_levels >= TRANSACTIONAL_LIMIT { + return Err(()) + } + // Cannot overflow because of check above. + set_transaction_level(existing_levels + 1); + Ok(StorageLayerGuard) +} + +fn dec_transaction_level() { + let existing_levels = get_transaction_level(); + if existing_levels == 0 { + log::warn!( + "We are underflowing with calculating transactional levels. Not great, but let's not panic...", + ); + } else if existing_levels == 1 { + // Don't leave any trace of this storage item. + kill_transaction_level(); + } else { + // Cannot underflow because of checks above. + set_transaction_level(existing_levels - 1); + } +} + +struct StorageLayerGuard; + +impl Drop for StorageLayerGuard { + fn drop(&mut self) { + dec_transaction_level() + } +} + +/// Check if the current call is within a transactional layer. +pub fn is_transactional() -> bool { + get_transaction_level() > 0 +} + +/// Execute the supplied function in a new storage transaction. +/// +/// All changes to storage performed by the supplied function are discarded if the returned +/// outcome is `TransactionOutcome::Rollback`. +/// +/// Transactions can be nested up to `TRANSACTIONAL_LIMIT` times; more than that will result in an +/// error. +/// +/// Commits happen to the parent transaction. +pub fn with_transaction(f: impl FnOnce() -> TransactionOutcome>) -> Result +where + E: From, +{ + // This needs to happen before `start_transaction` below. + // Otherwise we may rollback the increase, then decrease as the guard goes out of scope + // and then end in some bad state. + let _guard = inc_transaction_level().map_err(|()| TransactionalError::LimitReached.into())?; + + start_transaction(); + + match f() { + TransactionOutcome::Commit(res) => { + commit_transaction(); + res + }, + TransactionOutcome::Rollback(res) => { + rollback_transaction(); + res + }, + } +} + +/// Same as [`with_transaction`] but without a limit check on nested transactional layers. +/// +/// This is mostly for backwards compatibility before there was a transactional layer limit. +/// It is recommended to only use [`with_transaction`] to avoid users from generating too many +/// transactional layers. +pub fn with_transaction_unchecked(f: impl FnOnce() -> TransactionOutcome) -> R { + // This needs to happen before `start_transaction` below. + // Otherwise we may rollback the increase, then decrease as the guard goes out of scope + // and then end in some bad state. + let maybe_guard = inc_transaction_level(); + + if maybe_guard.is_err() { + log::warn!( + "The transactional layer limit has been reached, and new transactional layers are being + spawned with `with_transaction_unchecked`. This could be caused by someone trying to + attack your chain, and you should investigate usage of `with_transaction_unchecked` and + potentially migrate to `with_transaction`, which enforces a transactional limit.", + ); + } + + start_transaction(); + + match f() { + TransactionOutcome::Commit(res) => { + commit_transaction(); + res + }, + TransactionOutcome::Rollback(res) => { + rollback_transaction(); + res + }, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{assert_noop, assert_ok}; + use sp_io::TestExternalities; + use sp_runtime::DispatchResult; + + #[test] + fn is_transactional_should_return_false() { + TestExternalities::default().execute_with(|| { + assert!(!is_transactional()); + }); + } + + #[test] + fn is_transactional_should_not_error_in_with_transaction() { + TestExternalities::default().execute_with(|| { + assert_ok!(with_transaction(|| -> TransactionOutcome { + assert!(is_transactional()); + TransactionOutcome::Commit(Ok(())) + })); + + assert_noop!( + with_transaction(|| -> TransactionOutcome { + assert!(is_transactional()); + TransactionOutcome::Rollback(Err("revert".into())) + }), + "revert" + ); + }); + } + + fn recursive_transactional(num: u32) -> DispatchResult { + if num == 0 { + return Ok(()) + } + + with_transaction(|| -> TransactionOutcome { + let res = recursive_transactional(num - 1); + TransactionOutcome::Commit(res) + }) + } + + #[test] + fn transaction_limit_should_work() { + TestExternalities::default().execute_with(|| { + assert_eq!(get_transaction_level(), 0); + + assert_ok!(with_transaction(|| -> TransactionOutcome { + assert_eq!(get_transaction_level(), 1); + TransactionOutcome::Commit(Ok(())) + })); + + assert_ok!(with_transaction(|| -> TransactionOutcome { + assert_eq!(get_transaction_level(), 1); + let res = with_transaction(|| -> TransactionOutcome { + assert_eq!(get_transaction_level(), 2); + TransactionOutcome::Commit(Ok(())) + }); + TransactionOutcome::Commit(res) + })); + + assert_ok!(recursive_transactional(255)); + assert_noop!( + recursive_transactional(256), + sp_runtime::TransactionalError::LimitReached + ); + + assert_eq!(get_transaction_level(), 0); + }); + } +} From 472f35292990515e7073b27b3ada0bcd43e526a4 Mon Sep 17 00:00:00 2001 From: Zhenghao Lu <54395432+EmisonLu@users.noreply.github.com> Date: Tue, 5 Apr 2022 16:39:46 +0800 Subject: [PATCH 088/484] Correct a spelling mistake (#11167) --- client/executor/wasmtime/src/runtime.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/executor/wasmtime/src/runtime.rs b/client/executor/wasmtime/src/runtime.rs index cbe1359d28a6c..fa3b567cc0abc 100644 --- a/client/executor/wasmtime/src/runtime.rs +++ b/client/executor/wasmtime/src/runtime.rs @@ -386,7 +386,7 @@ pub struct Semantics { // I.e. if [`CodeSupplyMode::Verbatim`] is used. pub fast_instance_reuse: bool, - /// Specifiying `Some` will enable deterministic stack height. That is, all executor + /// Specifying `Some` will enable deterministic stack height. That is, all executor /// invocations will reach stack overflow at the exactly same point across different wasmtime /// versions and architectures. /// From 511a9640ab1fa3ea0c3a11d35b53255dfd6f5593 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Tue, 5 Apr 2022 19:01:45 +0200 Subject: [PATCH 089/484] Upgrade to wasmer 2.2 (#11168) --- Cargo.lock | 389 ++++++++++++------ client/executor/common/Cargo.toml | 5 +- client/executor/common/src/sandbox.rs | 2 + .../common/src/sandbox/wasmer_backend.rs | 6 +- .../src/communication/gossip.rs | 2 +- client/finality-grandpa/src/justification.rs | 2 +- 6 files changed, 270 insertions(+), 136 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 23f11860ca6f8..17931984c41e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -795,6 +795,27 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" +[[package]] +name = "bytecheck" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "314889ea31cda264cb7c3d6e6e5c9415a987ecb0e72c17c00d36fbb881d34abe" +dependencies = [ + "bytecheck_derive", + "ptr_meta", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a2b3b92c135dae665a6f760205b89187638e83bed17ef3e44e83c712cf30600" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "byteorder" version = "1.3.4" @@ -1140,11 +1161,11 @@ checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" [[package]] name = "cranelift-bforest" -version = "0.68.0" +version = "0.76.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9221545c0507dc08a62b2d8b5ffe8e17ac580b0a74d1813b496b8d70b070fbd0" +checksum = "7e6bea67967505247f54fa2c85cf4f6e0e31c4e5692c9b70e4ae58e339067333" dependencies = [ - "cranelift-entity 0.68.0", + "cranelift-entity 0.76.0", ] [[package]] @@ -1158,21 +1179,19 @@ dependencies = [ [[package]] name = "cranelift-codegen" -version = "0.68.0" +version = "0.76.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e9936ea608b6cd176f107037f6adbb4deac933466fc7231154f96598b2d3ab1" +checksum = "48194035d2752bdd5bdae429e3ab88676e95f52a2b1355a5d4e809f9e39b1d74" dependencies = [ - "byteorder", - "cranelift-bforest 0.68.0", - "cranelift-codegen-meta 0.68.0", - "cranelift-codegen-shared 0.68.0", - "cranelift-entity 0.68.0", - "gimli 0.22.0", + "cranelift-bforest 0.76.0", + "cranelift-codegen-meta 0.76.0", + "cranelift-codegen-shared 0.76.0", + "cranelift-entity 0.76.0", + "gimli 0.25.0", "log 0.4.14", "regalloc 0.0.31", "smallvec 1.8.0", - "target-lexicon 0.11.2", - "thiserror", + "target-lexicon", ] [[package]] @@ -1189,17 +1208,17 @@ dependencies = [ "log 0.4.14", "regalloc 0.0.33", "smallvec 1.8.0", - "target-lexicon 0.12.0", + "target-lexicon", ] [[package]] name = "cranelift-codegen-meta" -version = "0.68.0" +version = "0.76.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ef2b2768568306540f4c8db3acce9105534d34c4a1e440529c1e702d7f8c8d7" +checksum = "976efb22fcab4f2cd6bd4e9913764616a54d895c1a23530128d04e03633c555f" dependencies = [ - "cranelift-codegen-shared 0.68.0", - "cranelift-entity 0.68.0", + "cranelift-codegen-shared 0.76.0", + "cranelift-entity 0.76.0", ] [[package]] @@ -1213,9 +1232,9 @@ dependencies = [ [[package]] name = "cranelift-codegen-shared" -version = "0.68.0" +version = "0.76.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6759012d6d19c4caec95793f052613e9d4113e925e7f14154defbac0f1d4c938" +checksum = "9dabb5fe66e04d4652e434195b45ae65b5c8172d520247b8f66d8df42b2b45dc" [[package]] name = "cranelift-codegen-shared" @@ -1225,12 +1244,9 @@ checksum = "981da52d8f746af1feb96290c83977ff8d41071a7499e991d8abae0d4869f564" [[package]] name = "cranelift-entity" -version = "0.68.0" +version = "0.76.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86badbce14e15f52a45b666b38abe47b204969dd7f8fb7488cb55dd46b361fa6" -dependencies = [ - "serde", -] +checksum = "3329733e4d4b8e91c809efcaa4faee80bf66f20164e3dd16d707346bd3494799" [[package]] name = "cranelift-entity" @@ -1243,14 +1259,14 @@ dependencies = [ [[package]] name = "cranelift-frontend" -version = "0.68.0" +version = "0.76.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b608bb7656c554d0a4cf8f50c7a10b857e80306f6ff829ad6d468a7e2323c8d8" +checksum = "279afcc0d3e651b773f94837c3d581177b348c8d69e928104b2e9fccb226f921" dependencies = [ - "cranelift-codegen 0.68.0", + "cranelift-codegen 0.76.0", "log 0.4.14", "smallvec 1.8.0", - "target-lexicon 0.11.2", + "target-lexicon", ] [[package]] @@ -1262,7 +1278,7 @@ dependencies = [ "cranelift-codegen 0.80.0", "log 0.4.14", "smallvec 1.8.0", - "target-lexicon 0.12.0", + "target-lexicon", ] [[package]] @@ -1273,7 +1289,7 @@ checksum = "166028ca0343a6ee7bddac0e70084e142b23f99c701bd6f6ea9123afac1a7a46" dependencies = [ "cranelift-codegen 0.80.0", "libc", - "target-lexicon 0.12.0", + "target-lexicon", ] [[package]] @@ -1757,9 +1773,9 @@ checksum = "21e50f3adc76d6a43f5ed73b698a87d0760ca74617f60f7c3b879003536fdd28" [[package]] name = "dynasm" -version = "1.1.0" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdc2d9a5e44da60059bd38db2d05cbb478619541b8c79890547861ec1e3194f0" +checksum = "add9a102807b524ec050363f09e06f1504214b0e1c7797f64261c891022dce8b" dependencies = [ "bitflags", "byteorder", @@ -1772,13 +1788,13 @@ dependencies = [ [[package]] name = "dynasmrt" -version = "1.1.0" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42276e3f205fe63887cca255aa9a65a63fb72764c30b9a6252a7c7e46994f689" +checksum = "64fba5a42bd76a17cad4bfa00de168ee1cbfa06a5e8ce992ae880218c05641a9" dependencies = [ "byteorder", "dynasm", - "memmap2 0.2.1", + "memmap2 0.5.0", ] [[package]] @@ -1851,6 +1867,26 @@ dependencies = [ "syn", ] +[[package]] +name = "enum-iterator" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eeac5c5edb79e4e39fe8439ef35207780a11f69c52cbe424ce3dfad4cb78de6" +dependencies = [ + "enum-iterator-derive", +] + +[[package]] +name = "enum-iterator-derive" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "enumflags2" version = "0.6.4" @@ -2676,9 +2712,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.22.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724" +checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7" dependencies = [ "fallible-iterator", "indexmap", @@ -3684,16 +3720,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "libloading" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "351a32417a12d5f7e82c368a66781e307834dae04c6ce0cd4456d52989229883" -dependencies = [ - "cfg-if 1.0.0", - "winapi 0.3.9", -] - [[package]] name = "libloading" version = "0.7.0" @@ -4343,6 +4369,27 @@ dependencies = [ "value-bag", ] +[[package]] +name = "loupe" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6a72dfa44fe15b5e76b94307eeb2ff995a8c5b283b55008940c02e0c5b634d" +dependencies = [ + "indexmap", + "loupe-derive", + "rustversion", +] + +[[package]] +name = "loupe-derive" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fbfc88337168279f2e9ae06e157cfed4efd3316e14dc96ed074d4f2e6c5952" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "lru" version = "0.6.6" @@ -5327,21 +5374,23 @@ dependencies = [ [[package]] name = "object" -version = "0.22.0" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397" +checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" dependencies = [ "crc32fast", "indexmap", + "memchr", ] [[package]] name = "object" -version = "0.27.1" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" +checksum = "40bec70ba014595f99f7aa110b84331ffe1ee9aece7fe6f387cc7e3ecda4d456" dependencies = [ "crc32fast", + "hashbrown 0.11.2", "indexmap", "memchr", ] @@ -7365,6 +7414,26 @@ dependencies = [ "cc", ] +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -7776,6 +7845,18 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "region" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76e189c2369884dce920945e2ddf79b3dff49e071a167dd1817fa9c4c00d512e" +dependencies = [ + "bitflags", + "libc", + "mach", + "winapi 0.3.9", +] + [[package]] name = "remote-externalities" version = "0.10.0-dev" @@ -7804,6 +7885,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "rend" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79af64b4b6362ffba04eef3a4e10829718a4896dac19daa741851c86781edf95" +dependencies = [ + "bytecheck", +] + [[package]] name = "resolv-conf" version = "0.7.0" @@ -7835,6 +7925,31 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "rkyv" +version = "0.7.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f08c8062c1fe1253064043b8fc07bfea1b9702b71b4a86c11ea3588183b12e1" +dependencies = [ + "bytecheck", + "hashbrown 0.12.0", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e289706df51226e84814bf6ba1a9e1013112ae29bc7a9878f73fce360520c403" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "rocksdb" version = "0.18.0" @@ -8524,7 +8639,6 @@ dependencies = [ "thiserror", "wasm-instrument", "wasmer", - "wasmer-compiler-singlepass", "wasmi", ] @@ -9257,6 +9371,12 @@ dependencies = [ "untrusted", ] +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + [[package]] name = "sec1" version = "0.2.1" @@ -10855,15 +10975,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "target-lexicon" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "422045212ea98508ae3d28025bc5aaa2bd4a9cdaecd442a08da2ee620ee9ea95" - -[[package]] -name = "target-lexicon" -version = "0.12.0" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ae3b39281e4b14b8123bdbaddd472b7dfe215e444181f2f9d2443c2444f834" +checksum = "d7fa7e55043acb85fca6b3c01485a2eeb6b69c5d21002e273c79e465f43b7ac1" [[package]] name = "tempfile" @@ -11232,6 +11346,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" dependencies = [ "cfg-if 1.0.0", + "log 0.4.14", "pin-project-lite 0.2.6", "tracing-attributes", "tracing-core", @@ -11471,9 +11586,9 @@ version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ee73e6e4924fe940354b8d4d98cad5231175d615cd855b758adc658c0aac6a0" dependencies = [ - "cfg-if 1.0.0", + "cfg-if 0.1.10", "digest 0.10.3", - "rand 0.8.4", + "rand 0.6.5", "static_assertions", ] @@ -11820,21 +11935,25 @@ dependencies = [ [[package]] name = "wasmer" -version = "1.0.2" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a70cfae554988d904d64ca17ab0e7cd652ee5c8a0807094819c1ea93eb9d6866" +checksum = "f727a39e7161f7438ddb8eafe571b67c576a8c2fb459f666d9053b5bba4afdea" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", "indexmap", + "js-sys", + "loupe", "more-asserts", - "target-lexicon 0.11.2", + "target-lexicon", "thiserror", + "wasm-bindgen", "wasmer-compiler", "wasmer-compiler-cranelift", + "wasmer-compiler-singlepass", "wasmer-derive", "wasmer-engine", - "wasmer-engine-jit", - "wasmer-engine-native", + "wasmer-engine-dylib", + "wasmer-engine-universal", "wasmer-types", "wasmer-vm", "wat", @@ -11843,34 +11962,38 @@ dependencies = [ [[package]] name = "wasmer-compiler" -version = "1.0.2" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b7732a9cab472bd921d5a0c422f45b3d03f62fa2c40a89e0770cef6d47e383e" +checksum = "4e9951599222eb12bd13d4d91bcded0a880e4c22c2dfdabdf5dc7e5e803b7bf3" dependencies = [ "enumset", + "loupe", + "rkyv", "serde", "serde_bytes", "smallvec 1.8.0", - "target-lexicon 0.11.2", + "target-lexicon", "thiserror", "wasmer-types", "wasmer-vm", - "wasmparser 0.65.0", + "wasmparser 0.78.2", ] [[package]] name = "wasmer-compiler-cranelift" -version = "1.0.2" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb9395f094e1d81534f4c5e330ed4cdb424e8df870d29ad585620284f5fddb" +checksum = "44c83273bce44e668f3a2b9ccb7f1193db918b1d6806f64acc5ff71f6ece5f20" dependencies = [ - "cranelift-codegen 0.68.0", - "cranelift-frontend 0.68.0", - "gimli 0.22.0", + "cranelift-codegen 0.76.0", + "cranelift-entity 0.76.0", + "cranelift-frontend 0.76.0", + "gimli 0.25.0", + "loupe", "more-asserts", "rayon", - "serde", "smallvec 1.8.0", + "target-lexicon", "tracing", "wasmer-compiler", "wasmer-types", @@ -11879,17 +12002,17 @@ dependencies = [ [[package]] name = "wasmer-compiler-singlepass" -version = "1.0.2" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "426ae6ef0f606ca815510f3e2ef6f520e217514bfb7a664defe180b9a9e75d07" +checksum = "5432e993840cdb8e6875ddc8c9eea64e7a129579b4706bd91b8eb474d9c4a860" dependencies = [ "byteorder", "dynasm", "dynasmrt", "lazy_static", + "loupe", "more-asserts", "rayon", - "serde", "smallvec 1.8.0", "wasmer-compiler", "wasmer-types", @@ -11898,9 +12021,9 @@ dependencies = [ [[package]] name = "wasmer-derive" -version = "1.0.2" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b86dcd2c3efdb8390728a2b56f762db07789aaa5aa872a9dc776ba3a7912ed" +checksum = "458dbd9718a837e6dbc52003aef84487d79eedef5fa28c7d28b6784be98ac08e" dependencies = [ "proc-macro-error", "proc-macro2", @@ -11910,19 +12033,20 @@ dependencies = [ [[package]] name = "wasmer-engine" -version = "1.0.2" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efe4667d6bd888f26ae8062a63a9379fa697415b4b4e380f33832e8418fd71b5" +checksum = "6ed603a6d037ebbb14014d7f739ae996a78455a4b86c41cfa4e81c590a1253b9" dependencies = [ "backtrace", - "bincode", + "enumset", "lazy_static", - "memmap2 0.2.1", + "loupe", + "memmap2 0.5.0", "more-asserts", "rustc-demangle", "serde", "serde_bytes", - "target-lexicon 0.11.2", + "target-lexicon", "thiserror", "wasmer-compiler", "wasmer-types", @@ -11930,51 +12054,57 @@ dependencies = [ ] [[package]] -name = "wasmer-engine-jit" -version = "1.0.2" +name = "wasmer-engine-dylib" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26770be802888011b4a3072f2a282fc2faa68aa48c71b3db6252a3937a85f3da" +checksum = "ccd7fdc60e252a795c849b3f78a81a134783051407e7e279c10b7019139ef8dc" dependencies = [ - "bincode", - "cfg-if 0.1.10", - "region", + "cfg-if 1.0.0", + "enum-iterator", + "enumset", + "leb128", + "libloading 0.7.0", + "loupe", + "object 0.28.3", + "rkyv", "serde", - "serde_bytes", + "tempfile", + "tracing", "wasmer-compiler", "wasmer-engine", + "wasmer-object", "wasmer-types", "wasmer-vm", - "winapi 0.3.9", + "which", ] [[package]] -name = "wasmer-engine-native" -version = "1.0.2" +name = "wasmer-engine-universal" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bb4083a6c69f2cd4b000b82a80717f37c6cc2e536aee3a8ffe9af3edc276a8b" +checksum = "dcff0cd2c01a8de6009fd863b14ea883132a468a24f2d2ee59dc34453d3a31b5" dependencies = [ - "bincode", - "cfg-if 0.1.10", + "cfg-if 1.0.0", + "enum-iterator", + "enumset", "leb128", - "libloading 0.6.7", - "serde", - "tempfile", - "tracing", + "loupe", + "region 3.0.0", + "rkyv", "wasmer-compiler", "wasmer-engine", - "wasmer-object", "wasmer-types", "wasmer-vm", - "which", + "winapi 0.3.9", ] [[package]] name = "wasmer-object" -version = "1.0.2" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abf8e0c12b82ff81ebecd30d7e118be5fec871d6de885a90eeb105df0a769a7b" +checksum = "24ce18ac2877050e59580d27ee1a88f3192d7a31e77fbba0852abc7888d6e0b5" dependencies = [ - "object 0.22.0", + "object 0.28.3", "thiserror", "wasmer-compiler", "wasmer-types", @@ -11982,29 +12112,34 @@ dependencies = [ [[package]] name = "wasmer-types" -version = "1.0.2" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f4ac28c2951cd792c18332f03da523ed06b170f5cf6bb5b1bdd7e36c2a8218" +checksum = "659fa3dd6c76f62630deff4ac8c7657b07f0b1e4d7e0f8243a552b9d9b448e24" dependencies = [ - "cranelift-entity 0.68.0", + "indexmap", + "loupe", + "rkyv", "serde", "thiserror", ] [[package]] name = "wasmer-vm" -version = "1.0.2" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7635ba0b6d2fd325f588d69a950ad9fa04dddbf6ad08b6b2a183146319bf6ae" +checksum = "afdc46158517c2769f9938bc222a7d41b3bb330824196279d8aa2d667cd40641" dependencies = [ "backtrace", "cc", - "cfg-if 0.1.10", + "cfg-if 1.0.0", + "enum-iterator", "indexmap", "libc", + "loupe", "memoffset", "more-asserts", - "region", + "region 3.0.0", + "rkyv", "serde", "thiserror", "wasmer-types", @@ -12039,9 +12174,9 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.65.0" +version = "0.78.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc2fe6350834b4e528ba0901e7aa405d78b89dc1fa3145359eb4de0e323fcf" +checksum = "52144d4c78e5cf8b055ceab8e5fa22814ce4315d6002ad32cfd914f37c12fd65" [[package]] name = "wasmparser" @@ -12068,10 +12203,10 @@ dependencies = [ "paste 1.0.6", "psm", "rayon", - "region", + "region 2.2.0", "rustc-demangle", "serde", - "target-lexicon 0.12.0", + "target-lexicon", "wasmparser 0.81.0", "wasmtime-cache", "wasmtime-cranelift", @@ -12117,7 +12252,7 @@ dependencies = [ "log 0.4.14", "more-asserts", "object 0.27.1", - "target-lexicon 0.12.0", + "target-lexicon", "thiserror", "wasmparser 0.81.0", "wasmtime-environ", @@ -12137,7 +12272,7 @@ dependencies = [ "more-asserts", "object 0.27.1", "serde", - "target-lexicon 0.12.0", + "target-lexicon", "thiserror", "wasmparser 0.81.0", "wasmtime-types", @@ -12155,10 +12290,10 @@ dependencies = [ "cfg-if 1.0.0", "gimli 0.26.1", "object 0.27.1", - "region", + "region 2.2.0", "rustix", "serde", - "target-lexicon 0.12.0", + "target-lexicon", "thiserror", "wasmtime-environ", "wasmtime-runtime", @@ -12183,7 +12318,7 @@ dependencies = [ "memoffset", "more-asserts", "rand 0.8.4", - "region", + "region 2.2.0", "rustix", "thiserror", "wasmtime-environ", diff --git a/client/executor/common/Cargo.toml b/client/executor/common/Cargo.toml index 9282fe3b03dd6..149d9fdc236cd 100644 --- a/client/executor/common/Cargo.toml +++ b/client/executor/common/Cargo.toml @@ -24,13 +24,10 @@ sp-maybe-compressed-blob = { version = "4.1.0-dev", path = "../../../primitives/ sp-serializer = { version = "4.0.0-dev", path = "../../../primitives/serializer" } thiserror = "1.0.30" environmental = "1.1.3" - -wasmer = { version = "1.0", optional = true } -wasmer-compiler-singlepass = { version = "1.0", optional = true } +wasmer = { version = "2.2", optional = true, features = ["singlepass"] } [features] default = [] wasmer-sandbox = [ "wasmer", - "wasmer-compiler-singlepass", ] diff --git a/client/executor/common/src/sandbox.rs b/client/executor/common/src/sandbox.rs index 3f46ec53bdfe4..a2c1f602b1c9a 100644 --- a/client/executor/common/src/sandbox.rs +++ b/client/executor/common/src/sandbox.rs @@ -251,6 +251,8 @@ pub enum InstantiationError { /// Module is well-formed, instantiated and linked, but while executing the start function /// a trap was generated. StartTrapped, + /// The code was compiled with a CPU feature not available on the host. + CpuFeature, } fn decode_environment_definition( diff --git a/client/executor/common/src/sandbox/wasmer_backend.rs b/client/executor/common/src/sandbox/wasmer_backend.rs index 904afc9470400..44b43757148de 100644 --- a/client/executor/common/src/sandbox/wasmer_backend.rs +++ b/client/executor/common/src/sandbox/wasmer_backend.rs @@ -43,9 +43,8 @@ pub struct Backend { impl Backend { pub fn new() -> Self { - let compiler = wasmer_compiler_singlepass::Singlepass::default(); - - Backend { store: wasmer::Store::new(&wasmer::JIT::new(compiler).engine()) } + let compiler = wasmer::Singlepass::default(); + Backend { store: wasmer::Store::new(&wasmer::Universal::new(compiler).engine()) } } } @@ -191,6 +190,7 @@ pub fn instantiate( wasmer::InstantiationError::Start(_) => InstantiationError::StartTrapped, wasmer::InstantiationError::HostEnvInitialization(_) => InstantiationError::EnvironmentDefinitionCorrupted, + wasmer::InstantiationError::CpuFeature(_) => InstantiationError::CpuFeature, }) })?; diff --git a/client/finality-grandpa/src/communication/gossip.rs b/client/finality-grandpa/src/communication/gossip.rs index 7ac4066780d0f..c39e2e82a621e 100644 --- a/client/finality-grandpa/src/communication/gossip.rs +++ b/client/finality-grandpa/src/communication/gossip.rs @@ -799,7 +799,7 @@ impl Inner { Some(ref mut v) => if v.set_id == set_id { let diff_authorities = self.authorities.iter().collect::>() != - authorities.iter().collect(); + authorities.iter().collect::>(); if diff_authorities { debug!(target: "afg", diff --git a/client/finality-grandpa/src/justification.rs b/client/finality-grandpa/src/justification.rs index 5ee5f278ed8b6..39f24cb8ea57d 100644 --- a/client/finality-grandpa/src/justification.rs +++ b/client/finality-grandpa/src/justification.rs @@ -185,7 +185,7 @@ impl GrandpaJustification { } } - let ancestry_hashes = + let ancestry_hashes: HashSet<_> = self.votes_ancestries.iter().map(|h: &Block::Header| h.hash()).collect(); if visited_hashes != ancestry_hashes { From c20cad163bd349f0e1d72263831f616dcdfe94ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Apr 2022 21:14:49 +0000 Subject: [PATCH 090/484] Bump enumflags2 from 0.6.4 to 0.7.4 (#11108) * Bump enumflags2 from 0.6.4 to 0.7.4 Bumps [enumflags2](https://github.com/NieDzejkob/enumflags2) from 0.6.4 to 0.7.4. - [Release notes](https://github.com/NieDzejkob/enumflags2/releases) - [Commits](https://github.com/NieDzejkob/enumflags2/compare/v0.6.4...v0.7.4) --- updated-dependencies: - dependency-name: enumflags2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * use `#[bitflags]` attribute macro Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Shawn Tabrizi --- Cargo.lock | 12 ++++++------ frame/identity/Cargo.toml | 2 +- frame/identity/src/types.rs | 5 +++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 17931984c41e4..bd6db80272113 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1889,18 +1889,18 @@ dependencies = [ [[package]] name = "enumflags2" -version = "0.6.4" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c8d82922337cd23a15f88b70d8e4ef5f11da38dd7cdb55e84dd5de99695da0" +checksum = "1b3ab37dc79652c9d85f1f7b6070d77d321d2467f5fe7b00d6b7a86c57b092ae" dependencies = [ "enumflags2_derive", ] [[package]] name = "enumflags2_derive" -version = "0.6.4" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "946ee94e3dbf58fdd324f9ce245c7b238d46a66f00e86a020b71996349e46cce" +checksum = "f58dc3c5e468259f19f2d46304a6b28f1c3d034442e14b322d2b850e36f6d5ae" dependencies = [ "proc-macro2", "quote", @@ -11586,9 +11586,9 @@ version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ee73e6e4924fe940354b8d4d98cad5231175d615cd855b758adc658c0aac6a0" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", "digest 0.10.3", - "rand 0.6.5", + "rand 0.8.4", "static_assertions", ] diff --git a/frame/identity/Cargo.toml b/frame/identity/Cargo.toml index 7ebfda7cf176f..5dff7acc73e26 100644 --- a/frame/identity/Cargo.toml +++ b/frame/identity/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -enumflags2 = { version = "0.6.2" } +enumflags2 = { version = "0.7.4" } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } diff --git a/frame/identity/src/types.rs b/frame/identity/src/types.rs index cb79ace98d81c..18fc54c941cbd 100644 --- a/frame/identity/src/types.rs +++ b/frame/identity/src/types.rs @@ -17,7 +17,7 @@ use super::*; use codec::{Decode, Encode, MaxEncodedLen}; -use enumflags2::BitFlags; +use enumflags2::{bitflags, BitFlags}; use frame_support::{ traits::{ConstU32, Get}, BoundedVec, CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound, @@ -230,8 +230,9 @@ impl Date: Wed, 6 Apr 2022 09:55:55 +0200 Subject: [PATCH 091/484] Change default execution strategies to `Wasm` (#11170) This pr changes all default execution strategies to `Wasm`. This is basically a deprecation of the native runtime. While the native runtime isn't removed and can still be used, it will not be used anymore by default. This will also improve the usage for people who want to run random commits, which most often forget to run with `--execution wasm`. Otherwise they often run into storage root mismatches because of using the native runtime. --- client/cli/src/arg_enums.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/cli/src/arg_enums.rs b/client/cli/src/arg_enums.rs index df4b68ff5c325..a09b7f824c7ba 100644 --- a/client/cli/src/arg_enums.rs +++ b/client/cli/src/arg_enums.rs @@ -281,14 +281,14 @@ impl Into for SyncMode { } /// Default value for the `--execution-syncing` parameter. -pub const DEFAULT_EXECUTION_SYNCING: ExecutionStrategy = ExecutionStrategy::NativeElseWasm; +pub const DEFAULT_EXECUTION_SYNCING: ExecutionStrategy = ExecutionStrategy::Wasm; /// Default value for the `--execution-import-block` parameter. -pub const DEFAULT_EXECUTION_IMPORT_BLOCK: ExecutionStrategy = ExecutionStrategy::NativeElseWasm; +pub const DEFAULT_EXECUTION_IMPORT_BLOCK: ExecutionStrategy = ExecutionStrategy::Wasm; /// Default value for the `--execution-import-block` parameter when the node is a validator. pub const DEFAULT_EXECUTION_IMPORT_BLOCK_VALIDATOR: ExecutionStrategy = ExecutionStrategy::Wasm; /// Default value for the `--execution-block-construction` parameter. pub const DEFAULT_EXECUTION_BLOCK_CONSTRUCTION: ExecutionStrategy = ExecutionStrategy::Wasm; /// Default value for the `--execution-offchain-worker` parameter. -pub const DEFAULT_EXECUTION_OFFCHAIN_WORKER: ExecutionStrategy = ExecutionStrategy::Native; +pub const DEFAULT_EXECUTION_OFFCHAIN_WORKER: ExecutionStrategy = ExecutionStrategy::Wasm; /// Default value for the `--execution-other` parameter. -pub const DEFAULT_EXECUTION_OTHER: ExecutionStrategy = ExecutionStrategy::Native; +pub const DEFAULT_EXECUTION_OTHER: ExecutionStrategy = ExecutionStrategy::Wasm; From 0a5e0238bd9fa90bd49a3f62b1ec7da1ada97f6a Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Wed, 6 Apr 2022 10:19:14 +0200 Subject: [PATCH 092/484] Fix fork-tree descendent check (#11150) * Fix fork-tree descendent check * Add test assertions for the fix * Improve documentation * Nitpicks --- utils/fork-tree/src/lib.rs | 76 +++++++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 30 deletions(-) diff --git a/utils/fork-tree/src/lib.rs b/utils/fork-tree/src/lib.rs index 1d9b39f7dc04b..c23a4f55f44a1 100644 --- a/utils/fork-tree/src/lib.rs +++ b/utils/fork-tree/src/lib.rs @@ -509,14 +509,14 @@ where } /// Checks if any node in the tree is finalized by either finalizing the - /// node itself or a child node that's not in the tree, guaranteeing that - /// the node being finalized isn't a descendent of any of the node's - /// children. Returns `Some(true)` if the node being finalized is a root, - /// `Some(false)` if the node being finalized is not a root, and `None` if - /// no node in the tree is finalized. The given `predicate` is checked on - /// the prospective finalized root and must pass for finalization to occur. - /// The given function `is_descendent_of` should return `true` if the second - /// hash (target) is a descendent of the first hash (base). + /// node itself or a node's descendent that's not in the tree, guaranteeing + /// that the node being finalized isn't a descendent of (or equal to) any of + /// the node's children. Returns `Some(true)` if the node being finalized is + /// a root, `Some(false)` if the node being finalized is not a root, and + /// `None` if no node in the tree is finalized. The given `predicate` is + /// checked on the prospective finalized root and must pass for finalization + /// to occur. The given function `is_descendent_of` should return `true` if + /// the second hash (target) is a descendent of the first hash (base). pub fn finalizes_any_with_descendent_if( &self, hash: &H, @@ -541,8 +541,10 @@ where for node in self.node_iter() { if predicate(&node.data) { if node.hash == *hash || is_descendent_of(&node.hash, hash)? { - for node in node.children.iter() { - if node.number <= number && is_descendent_of(&node.hash, &hash)? { + for child in node.children.iter() { + if child.number <= number && + (child.hash == *hash || is_descendent_of(&child.hash, hash)?) + { return Err(Error::UnfinalizedAncestor) } } @@ -556,12 +558,12 @@ where } /// Finalize a root in the tree by either finalizing the node itself or a - /// child node that's not in the tree, guaranteeing that the node being - /// finalized isn't a descendent of any of the root's children. The given - /// `predicate` is checked on the prospective finalized root and must pass for - /// finalization to occur. The given function `is_descendent_of` should - /// return `true` if the second hash (target) is a descendent of the first - /// hash (base). + /// node's descendent that's not in the tree, guaranteeing that the node + /// being finalized isn't a descendent of (or equal to) any of the root's + /// children. The given `predicate` is checked on the prospective finalized + /// root and must pass for finalization to occur. The given function + /// `is_descendent_of` should return `true` if the second hash (target) is a + /// descendent of the first hash (base). pub fn finalize_with_descendent_if( &mut self, hash: &H, @@ -587,8 +589,10 @@ where for (i, root) in self.roots.iter().enumerate() { if predicate(&root.data) { if root.hash == *hash || is_descendent_of(&root.hash, hash)? { - for node in root.children.iter() { - if node.number <= number && is_descendent_of(&node.hash, &hash)? { + for child in root.children.iter() { + if child.number <= number && + (child.hash == *hash || is_descendent_of(&child.hash, hash)?) + { return Err(Error::UnfinalizedAncestor) } } @@ -606,12 +610,11 @@ where node.data }); - // if the block being finalized is earlier than a given root, then it - // must be its ancestor, otherwise we can prune the root. if there's a - // root at the same height then the hashes must match. otherwise the - // node being finalized is higher than the root so it must be its - // descendent (in this case the node wasn't finalized earlier presumably - // because the predicate didn't pass). + // Retain only roots that are descendents of the finalized block (this + // happens if the node has been properly finalized) or that are + // ancestors (or equal) to the finalized block (in this case the node + // wasn't finalized earlier presumably because the predicate didn't + // pass). let mut changed = false; let roots = std::mem::take(&mut self.roots); @@ -1275,18 +1278,31 @@ mod test { Ok(None), ); + // finalizing "D" is not allowed since it is not a root. + assert_eq!( + tree.finalize_with_descendent_if(&"D", 10, &is_descendent_of, |c| c.effective <= 10), + Err(Error::UnfinalizedAncestor) + ); + // finalizing "D" will finalize a block from the tree, but it can't be applied yet - // since it is not a root change + // since it is not a root change. assert_eq!( tree.finalizes_any_with_descendent_if(&"D", 10, &is_descendent_of, |c| c.effective == - 10,), + 10), Ok(Some(false)), ); + // finalizing "E" is not allowed since there are not finalized anchestors. + assert_eq!( + tree.finalizes_any_with_descendent_if(&"E", 15, &is_descendent_of, |c| c.effective == + 10), + Err(Error::UnfinalizedAncestor) + ); + // finalizing "B" doesn't finalize "A0" since the predicate doesn't pass, // although it will clear out "A1" from the tree assert_eq!( - tree.finalize_with_descendent_if(&"B", 2, &is_descendent_of, |c| c.effective <= 2,), + tree.finalize_with_descendent_if(&"B", 2, &is_descendent_of, |c| c.effective <= 2), Ok(FinalizationResult::Changed(None)), ); @@ -1307,7 +1323,7 @@ mod test { ); assert_eq!( - tree.finalize_with_descendent_if(&"C", 5, &is_descendent_of, |c| c.effective <= 5,), + tree.finalize_with_descendent_if(&"C", 5, &is_descendent_of, |c| c.effective <= 5), Ok(FinalizationResult::Changed(Some(Change { effective: 5 }))), ); @@ -1326,12 +1342,12 @@ mod test { // it will work with "G" though since it is not in the same branch as "E" assert_eq!( tree.finalizes_any_with_descendent_if(&"G", 100, &is_descendent_of, |c| c.effective <= - 100,), + 100), Ok(Some(true)), ); assert_eq!( - tree.finalize_with_descendent_if(&"G", 100, &is_descendent_of, |c| c.effective <= 100,), + tree.finalize_with_descendent_if(&"G", 100, &is_descendent_of, |c| c.effective <= 100), Ok(FinalizationResult::Changed(Some(Change { effective: 10 }))), ); From b085494acce5fba0c32b5b94a8020e57432f1396 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Wed, 6 Apr 2022 16:23:57 +0200 Subject: [PATCH 093/484] Grandpa revert procedure (#11162) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Grandpa revert procedure * Trigger ci pipeline * Test rename * Update client/finality-grandpa/src/authorities.rs Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com> --- bin/node-template/node/src/command.rs | 6 +- bin/node/cli/src/command.rs | 15 ++- client/consensus/babe/src/lib.rs | 4 +- client/finality-grandpa/src/authorities.rs | 33 +++++- client/finality-grandpa/src/lib.rs | 49 +++++++- client/finality-grandpa/src/tests.rs | 127 ++++++++++++++++++++- client/network/test/src/lib.rs | 2 +- 7 files changed, 224 insertions(+), 12 deletions(-) diff --git a/bin/node-template/node/src/command.rs b/bin/node-template/node/src/command.rs index 66ee9fe45a55c..f033e779543a0 100644 --- a/bin/node-template/node/src/command.rs +++ b/bin/node-template/node/src/command.rs @@ -95,7 +95,11 @@ pub fn run() -> sc_cli::Result<()> { runner.async_run(|config| { let PartialComponents { client, task_manager, backend, .. } = service::new_partial(&config)?; - Ok((cmd.run(client, backend, None), task_manager)) + let aux_revert = Box::new(move |client, _, blocks| { + sc_finality_grandpa::revert(client, blocks)?; + Ok(()) + }); + Ok((cmd.run(client, backend, Some(aux_revert)), task_manager)) }) }, Some(Subcommand::Benchmark(cmd)) => diff --git a/bin/node/cli/src/command.rs b/bin/node/cli/src/command.rs index bd324b20fb019..db243ff6f597b 100644 --- a/bin/node/cli/src/command.rs +++ b/bin/node/cli/src/command.rs @@ -16,7 +16,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::{chain_spec, service, service::new_partial, Cli, Subcommand}; +use crate::{ + chain_spec, service, + service::{new_partial, FullClient}, + Cli, Subcommand, +}; use node_executor::ExecutorDispatch; use node_primitives::Block; use node_runtime::RuntimeApi; @@ -175,13 +179,12 @@ pub fn run() -> Result<()> { let runner = cli.create_runner(cmd)?; runner.async_run(|config| { let PartialComponents { client, task_manager, backend, .. } = new_partial(&config)?; - let revert_aux = Box::new(|client, backend, blocks| { - sc_consensus_babe::revert(client, backend, blocks)?; - // TODO: grandpa revert + let aux_revert = Box::new(move |client: Arc, backend, blocks| { + sc_consensus_babe::revert(client.clone(), backend, blocks)?; + grandpa::revert(client, blocks)?; Ok(()) }); - - Ok((cmd.run(client, backend, Some(revert_aux)), task_manager)) + Ok((cmd.run(client, backend, Some(aux_revert)), task_manager)) }) }, #[cfg(feature = "try-runtime")] diff --git a/client/consensus/babe/src/lib.rs b/client/consensus/babe/src/lib.rs index 818384a5c3d7e..3d3a7f24df816 100644 --- a/client/consensus/babe/src/lib.rs +++ b/client/consensus/babe/src/lib.rs @@ -1832,7 +1832,9 @@ where Ok(BasicQueue::new(verifier, Box::new(block_import), justification_import, spawner, registry)) } -/// Reverts aux data. +/// Reverts protocol aux data to at most the last finalized block. +/// In particular, epoch-changes and block weights announced after the revert +/// point are removed. pub fn revert( client: Arc, backend: Arc, diff --git a/client/finality-grandpa/src/authorities.rs b/client/finality-grandpa/src/authorities.rs index 033a1c4bbb239..668fe5f269051 100644 --- a/client/finality-grandpa/src/authorities.rs +++ b/client/finality-grandpa/src/authorities.rs @@ -21,7 +21,7 @@ use std::{cmp::Ord, fmt::Debug, ops::Add}; use finality_grandpa::voter_set::VoterSet; -use fork_tree::ForkTree; +use fork_tree::{FilterAction, ForkTree}; use log::debug; use parity_scale_codec::{Decode, Encode}; use parking_lot::MappedMutexGuard; @@ -220,6 +220,37 @@ where pub(crate) fn current(&self) -> (u64, &[(AuthorityId, u64)]) { (self.set_id, &self.current_authorities[..]) } + + /// Revert to a specified block given its `hash` and `number`. + /// This removes all the authority set changes that were announced after + /// the revert point. + /// Revert point is identified by `number` and `hash`. + pub(crate) fn revert(&mut self, hash: H, number: N, is_descendent_of: &F) + where + F: Fn(&H, &H) -> Result, + { + let mut filter = |node_hash: &H, node_num: &N, _: &PendingChange| { + if number >= *node_num && + (is_descendent_of(node_hash, &hash).unwrap_or_default() || *node_hash == hash) + { + // Continue the search in this subtree. + FilterAction::KeepNode + } else if number < *node_num && is_descendent_of(&hash, node_hash).unwrap_or_default() { + // Found a node to be removed. + FilterAction::Remove + } else { + // Not a parent or child of the one we're looking for, stop processing this branch. + FilterAction::KeepTree + } + }; + + // Remove standard changes. + let _ = self.pending_standard_changes.drain_filter(&mut filter); + + // Remove forced changes. + self.pending_forced_changes + .retain(|change| !is_descendent_of(&hash, &change.canon_hash).unwrap_or_default()); + } } impl AuthoritySet diff --git a/client/finality-grandpa/src/lib.rs b/client/finality-grandpa/src/lib.rs index fef16286381ea..34d5b6bb1f70c 100644 --- a/client/finality-grandpa/src/lib.rs +++ b/client/finality-grandpa/src/lib.rs @@ -63,6 +63,7 @@ use parking_lot::RwLock; use prometheus_endpoint::{PrometheusError, Registry}; use sc_client_api::{ backend::{AuxStore, Backend}, + utils::is_descendent_of, BlockchainEvents, CallExecutor, ExecutionStrategy, ExecutorProvider, Finalizer, LockImportRun, StorageProvider, TransactionFor, }; @@ -71,7 +72,7 @@ use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_DEBUG, CONSENSUS_INFO}; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver}; use sp_api::ProvideRuntimeApi; use sp_application_crypto::AppKey; -use sp_blockchain::{Error as ClientError, HeaderBackend, HeaderMetadata}; +use sp_blockchain::{Error as ClientError, HeaderBackend, HeaderMetadata, Result as ClientResult}; use sp_consensus::SelectChain; use sp_core::crypto::ByteArray; use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; @@ -1162,3 +1163,49 @@ fn local_authority_id( .map(|(p, _)| p.clone()) }) } + +/// Reverts protocol aux data to at most the last finalized block. +/// In particular, standard and forced authority set changes announced after the +/// revert point are removed. +pub fn revert(client: Arc, blocks: NumberFor) -> ClientResult<()> +where + Block: BlockT, + Client: AuxStore + + HeaderMetadata + + HeaderBackend + + ProvideRuntimeApi, +{ + let best_number = client.info().best_number; + let finalized = client.info().finalized_number; + let revertible = blocks.min(best_number - finalized); + + let number = best_number - revertible; + let hash = client + .block_hash_from_id(&BlockId::Number(number))? + .ok_or(ClientError::Backend(format!( + "Unexpected hash lookup failure for block number: {}", + number + )))?; + + let info = client.info(); + let persistent_data: PersistentData = + aux_schema::load_persistent(&*client, info.genesis_hash, Zero::zero(), || unreachable!())?; + + let shared_authority_set = persistent_data.authority_set; + let mut authority_set = shared_authority_set.inner(); + + let is_descendent_of = is_descendent_of(&*client, None); + authority_set.revert(hash, number, &is_descendent_of); + + // The following has the side effect to properly reset the current voter state. + let (set_id, set_ref) = authority_set.current(); + let new_set = Some(NewAuthoritySet { + canon_hash: info.finalized_hash, + canon_number: info.finalized_number, + set_id, + authorities: set_ref.to_vec(), + }); + aux_schema::update_authority_set::(&authority_set, new_set.as_ref(), |values| { + client.insert_aux(values, None) + }) +} diff --git a/client/finality-grandpa/src/tests.rs b/client/finality-grandpa/src/tests.rs index 2e545b6e88ebf..5083cbfc21d1d 100644 --- a/client/finality-grandpa/src/tests.rs +++ b/client/finality-grandpa/src/tests.rs @@ -57,7 +57,7 @@ use tokio::runtime::{Handle, Runtime}; use authorities::AuthoritySet; use communication::grandpa_protocol_name; -use sc_block_builder::BlockBuilderProvider; +use sc_block_builder::{BlockBuilder, BlockBuilderProvider}; use sc_consensus::LongestChain; use sc_keystore::LocalKeystore; use sp_application_crypto::key_types::GRANDPA; @@ -1685,3 +1685,128 @@ fn grandpa_environment_doesnt_send_equivocation_reports_for_itself() { let equivocation_proof = sp_finality_grandpa::Equivocation::Prevote(equivocation); assert!(environment.report_equivocation(equivocation_proof).is_ok()); } + +#[test] +fn revert_prunes_authority_changes() { + sp_tracing::try_init_simple(); + let runtime = Runtime::new().unwrap(); + + let peers = &[Ed25519Keyring::Alice, Ed25519Keyring::Bob, Ed25519Keyring::Charlie]; + + type TestBlockBuilder<'a> = + BlockBuilder<'a, Block, PeersFullClient, substrate_test_runtime_client::Backend>; + let edit_block = |builder: TestBlockBuilder| { + let mut block = builder.build().unwrap().block; + add_scheduled_change( + &mut block, + ScheduledChange { next_authorities: make_ids(peers), delay: 0 }, + ); + block + }; + + let api = TestApi::new(make_ids(peers)); + let mut net = GrandpaTestNet::new(api, 3, 0); + runtime.spawn(initialize_grandpa(&mut net, peers)); + + let peer = net.peer(0); + let client = peer.client().as_client(); + + // Test scenario: (X) = auth-change, 24 = revert-point + // + // +---------(27) + // / + // 0---(21)---23---24---25---(28)---30 + // ^ \ + // revert-point +------(29) + + // Construct canonical chain + + // add 20 blocks + peer.push_blocks(20, false); + // at block 21 we add an authority transition + peer.generate_blocks(1, BlockOrigin::File, edit_block); + // add more blocks on top of it (until we have 24) + peer.push_blocks(3, false); + // add more blocks on top of it (until we have 27) + peer.push_blocks(3, false); + // at block 28 we add an authority transition + peer.generate_blocks(1, BlockOrigin::File, edit_block); + // add more blocks on top of it (until we have 30) + peer.push_blocks(2, false); + + // Fork before revert point + + // add more blocks on top of block 23 (until we have 26) + let hash = peer.generate_blocks_at( + BlockId::Number(23), + 3, + BlockOrigin::File, + |builder| { + let mut block = builder.build().unwrap().block; + block.header.digest_mut().push(DigestItem::Other(vec![1])); + block + }, + false, + false, + true, + ForkChoiceStrategy::LongestChain, + ); + // at block 27 of the fork add an authority transition + peer.generate_blocks_at( + BlockId::Hash(hash), + 1, + BlockOrigin::File, + edit_block, + false, + false, + true, + ForkChoiceStrategy::LongestChain, + ); + + // Fork after revert point + + // add more block on top of block 25 (until we have 28) + let hash = peer.generate_blocks_at( + BlockId::Number(25), + 3, + BlockOrigin::File, + |builder| { + let mut block = builder.build().unwrap().block; + block.header.digest_mut().push(DigestItem::Other(vec![2])); + block + }, + false, + false, + true, + ForkChoiceStrategy::LongestChain, + ); + // at block 29 of the fork add an authority transition + peer.generate_blocks_at( + BlockId::Hash(hash), + 1, + BlockOrigin::File, + edit_block, + false, + false, + true, + ForkChoiceStrategy::LongestChain, + ); + + revert(client.clone(), 6).unwrap(); + + let persistent_data: PersistentData = aux_schema::load_persistent( + &*client, + client.info().genesis_hash, + Zero::zero(), + || unreachable!(), + ) + .unwrap(); + let changes_num: Vec<_> = persistent_data + .authority_set + .inner() + .pending_standard_changes + .iter() + .map(|(_, n, _)| *n) + .collect(); + assert_eq!(changes_num, [21, 27]); +} diff --git a/client/network/test/src/lib.rs b/client/network/test/src/lib.rs index 3986ac47f3616..552879f35d934 100644 --- a/client/network/test/src/lib.rs +++ b/client/network/test/src/lib.rs @@ -334,7 +334,7 @@ where /// Add blocks to the peer -- edit the block before adding. The chain will /// start at the given block iD. - fn generate_blocks_at( + pub fn generate_blocks_at( &mut self, at: BlockId, count: usize, From 27387ac5a17e5d97d2c9d08df784600817db2de9 Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Wed, 6 Apr 2022 18:20:15 +0200 Subject: [PATCH 094/484] [ci] Remove vault (#11179) * [ci] Remove vault * remove anchors --- .gitlab-ci.yml | 47 ----------------------------------------------- 1 file changed, 47 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b6f9ff9486069..d196ade85a6fb 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -39,9 +39,6 @@ variables: &default-vars DOCKER_OS: "debian:stretch" ARCH: "x86_64" CI_IMAGE: "paritytech/ci-linux:production" - VAULT_SERVER_URL: "https://vault.parity-mgmt-vault.parity.io" - VAULT_AUTH_PATH: "gitlab-parity-io-jwt" - VAULT_AUTH_ROLE: "cicd_gitlab_parity_${CI_PROJECT_NAME}" default: cache: {} @@ -188,46 +185,6 @@ default: - cp -r ./scripts/ci/docker/substrate.Dockerfile ./artifacts/substrate/ - sccache -s -#### Vault secrets -.vault-secrets: &vault-secrets - secrets: - DOCKER_HUB_USER: - vault: cicd/gitlab/parity/DOCKER_HUB_USER@kv - file: false - DOCKER_HUB_PASS: - vault: cicd/gitlab/parity/DOCKER_HUB_PASS@kv - file: false - GITHUB_PR_TOKEN: - vault: cicd/gitlab/parity/GITHUB_PR_TOKEN@kv - file: false - GITHUB_TOKEN: - vault: cicd/gitlab/parity/GITHUB_TOKEN@kv - file: false - AWS_ACCESS_KEY_ID: - vault: cicd/gitlab/$CI_PROJECT_PATH/AWS_ACCESS_KEY_ID@kv - file: false - AWS_SECRET_ACCESS_KEY: - vault: cicd/gitlab/$CI_PROJECT_PATH/AWS_SECRET_ACCESS_KEY@kv - file: false - GITHUB_EMAIL: - vault: cicd/gitlab/$CI_PROJECT_PATH/GITHUB_EMAIL@kv - file: false - GITHUB_RELEASE_TOKEN: - vault: cicd/gitlab/$CI_PROJECT_PATH/GITHUB_RELEASE_TOKEN@kv - file: false - GITHUB_SSH_PRIV_KEY: - vault: cicd/gitlab/$CI_PROJECT_PATH/GITHUB_SSH_PRIV_KEY@kv - file: false - GITHUB_USER: - vault: cicd/gitlab/$CI_PROJECT_PATH/GITHUB_USER@kv - file: false - MATRIX_ACCESS_TOKEN: - vault: cicd/gitlab/$CI_PROJECT_PATH/MATRIX_ACCESS_TOKEN@kv - file: false - MATRIX_ROOM_ID: - vault: cicd/gitlab/$CI_PROJECT_PATH/MATRIX_ROOM_ID@kv - file: false - #### stage: .pre @@ -638,7 +595,6 @@ build-rustdoc: .build-push-docker-image: &build-push-docker-image <<: *build-refs <<: *kubernetes-env - <<: *vault-secrets image: quay.io/buildah/stable variables: &docker-build-vars <<: *default-vars @@ -697,7 +653,6 @@ publish-s3-release: stage: publish <<: *build-refs <<: *kubernetes-env - <<: *vault-secrets needs: - job: build-linux-substrate artifacts: true @@ -719,7 +674,6 @@ publish-s3-release: publish-rustdoc: stage: publish <<: *kubernetes-env - <<: *vault-secrets image: node:16 variables: GIT_DEPTH: 100 @@ -778,7 +732,6 @@ publish-rustdoc: publish-draft-release: stage: publish - <<: *vault-secrets image: paritytech/tools:latest rules: - if: $CI_COMMIT_REF_NAME =~ /^ci-release-.*$/ From 1e904c49636c04f3caf0022f907367994a78e2a3 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Thu, 7 Apr 2022 08:47:36 +0200 Subject: [PATCH 095/484] chore: update jsonrpsee to `v0.10.1` (#11173) * fix(bin/node): remove unsed dep jsonrpsee * chore(remote ext): update jsonrpsee v0.10.1 * chore(try runtime): update jsonrpsee v0.10.1 * Update utils/frame/try-runtime/cli/src/commands/follow_chain.rs * cargo fmt --- Cargo.lock | 125 +++++------------- bin/node/cli/Cargo.toml | 1 - client/informant/src/display.rs | 2 +- client/network/src/config.rs | 2 +- client/network/src/lib.rs | 4 +- client/peerset/src/lib.rs | 4 +- client/peerset/src/peersstate.rs | 8 +- utils/frame/remote-externalities/Cargo.toml | 2 +- utils/frame/try-runtime/cli/Cargo.toml | 2 +- .../cli/src/commands/follow_chain.rs | 12 +- 10 files changed, 54 insertions(+), 108 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bd6db80272113..0a1a1e0b17bec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2803,7 +2803,7 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util", + "tokio-util 0.6.7", "tracing", ] @@ -3404,7 +3404,7 @@ dependencies = [ "log 0.4.14", "tokio", "tokio-stream", - "tokio-util", + "tokio-util 0.6.7", "unicase 2.6.0", ] @@ -3425,53 +3425,42 @@ dependencies = [ [[package]] name = "jsonrpsee" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373a33d987866ccfe1af4bc11b089dce941764313f9fd8b7cf13fcb51b72dc5" -dependencies = [ - "jsonrpsee-types 0.4.1", - "jsonrpsee-utils", - "jsonrpsee-ws-client 0.4.1", -] - -[[package]] -name = "jsonrpsee" -version = "0.8.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05fd8cd6c6b1bbd06881d2cf88f1fc83cc36c98f2219090f839115fb4a956cb9" +checksum = "91dc760c341fa81173f9a434931aaf32baad5552b0230cc6c93e8fb7eaad4c19" dependencies = [ "jsonrpsee-core", "jsonrpsee-proc-macros", - "jsonrpsee-types 0.8.0", - "jsonrpsee-ws-client 0.8.0", + "jsonrpsee-types", + "jsonrpsee-ws-client", ] [[package]] name = "jsonrpsee-client-transport" -version = "0.8.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3303cdf246e6ab76e2866fb3d9acb6c76a068b1b28bd923a1b7a8122257ad7b5" +checksum = "765f7a36d5087f74e3b3b47805c2188fef8eb54afcb587b078d9f8ebfe9c7220" dependencies = [ "futures 0.3.21", "http", "jsonrpsee-core", - "jsonrpsee-types 0.8.0", + "jsonrpsee-types", "pin-project 1.0.10", "rustls-native-certs 0.6.1", "soketto 0.7.1", "thiserror", "tokio", "tokio-rustls 0.23.2", - "tokio-util", + "tokio-util 0.7.1", "tracing", "webpki-roots 0.22.2", ] [[package]] name = "jsonrpsee-core" -version = "0.8.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f220b5a238dc7992b90f1144fbf6eaa585872c9376afe6fe6863ffead6191bf3" +checksum = "82ef77ecd20c2254d54f5da8c0738eacca61e6b6511268a8f2753e3148c6c706" dependencies = [ "anyhow", "arrayvec 0.7.1", @@ -3480,7 +3469,7 @@ dependencies = [ "futures-channel", "futures-util", "hyper 0.14.16", - "jsonrpsee-types 0.8.0", + "jsonrpsee-types", "rustc-hash", "serde", "serde_json", @@ -3492,9 +3481,9 @@ dependencies = [ [[package]] name = "jsonrpsee-proc-macros" -version = "0.8.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4299ebf790ea9de1cb72e73ff2ae44c723ef264299e5e2d5ef46a371eb3ac3d8" +checksum = "b7291c72805bc7d413b457e50d8ef3e87aa554da65ecbbc278abb7dfc283e7f0" dependencies = [ "proc-macro-crate 1.1.3", "proc-macro2", @@ -3504,28 +3493,9 @@ dependencies = [ [[package]] name = "jsonrpsee-types" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f778cf245158fbd8f5d50823a2e9e4c708a40be164766bd35e9fb1d86715b2" -dependencies = [ - "anyhow", - "async-trait", - "beef", - "futures-channel", - "futures-util", - "hyper 0.14.16", - "log 0.4.14", - "serde", - "serde_json", - "soketto 0.7.1", - "thiserror", -] - -[[package]] -name = "jsonrpsee-types" -version = "0.8.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1b3f601bbbe45cd63f5407b6f7d7950e08a7d4f82aa699ff41a4a5e9e54df58" +checksum = "38b6aa52f322cbf20c762407629b8300f39bcc0cf0619840d9252a2f65fd2dd9" dependencies = [ "anyhow", "beef", @@ -3535,50 +3505,15 @@ dependencies = [ "tracing", ] -[[package]] -name = "jsonrpsee-utils" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0109c4f972058f3b1925b73a17210aff7b63b65967264d0045d15ee88fe84f0c" -dependencies = [ - "arrayvec 0.7.1", - "beef", - "jsonrpsee-types 0.4.1", -] - -[[package]] -name = "jsonrpsee-ws-client" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "559aa56fc402af206c00fc913dc2be1d9d788dcde045d14df141a535245d35ef" -dependencies = [ - "arrayvec 0.7.1", - "async-trait", - "fnv", - "futures 0.3.21", - "http", - "jsonrpsee-types 0.4.1", - "log 0.4.14", - "pin-project 1.0.10", - "rustls-native-certs 0.5.0", - "serde", - "serde_json", - "soketto 0.7.1", - "thiserror", - "tokio", - "tokio-rustls 0.22.0", - "tokio-util", -] - [[package]] name = "jsonrpsee-ws-client" -version = "0.8.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aff425cee7c779e33920913bc695447416078ee6d119f443f3060feffa4e86b5" +checksum = "dd66d18bab78d956df24dd0d2e41e4c00afbb818fda94a98264bdd12ce8506ac" dependencies = [ "jsonrpsee-client-transport", "jsonrpsee-core", - "jsonrpsee-types 0.8.0", + "jsonrpsee-types", ] [[package]] @@ -4891,7 +4826,6 @@ dependencies = [ "frame-system-rpc-runtime-api", "futures 0.3.21", "hex-literal", - "jsonrpsee-ws-client 0.4.1", "log 0.4.14", "nix", "node-executor", @@ -7863,7 +7797,7 @@ version = "0.10.0-dev" dependencies = [ "env_logger 0.9.0", "frame-support", - "jsonrpsee 0.8.0", + "jsonrpsee", "log 0.4.14", "pallet-elections-phragmen", "parity-scale-codec", @@ -11317,13 +11251,26 @@ checksum = "1caa0b0c8d94a049db56b5acf8cba99dc0623aab1b26d5b5f5e2d945846b3592" dependencies = [ "bytes 1.1.0", "futures-core", - "futures-io", "futures-sink", "log 0.4.14", "pin-project-lite 0.2.6", "tokio", ] +[[package]] +name = "tokio-util" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0edfdeb067411dba2044da6d1cb2df793dd35add7888d73c16e3381ded401764" +dependencies = [ + "bytes 1.1.0", + "futures-core", + "futures-io", + "futures-sink", + "pin-project-lite 0.2.6", + "tokio", +] + [[package]] name = "toml" version = "0.5.8" @@ -11540,7 +11487,7 @@ name = "try-runtime-cli" version = "0.10.0-dev" dependencies = [ "clap 3.1.6", - "jsonrpsee 0.4.1", + "jsonrpsee", "log 0.4.14", "parity-scale-codec", "remote-externalities", diff --git a/bin/node/cli/Cargo.toml b/bin/node/cli/Cargo.toml index d25c1b1f0bf81..4eb5983b6488e 100644 --- a/bin/node/cli/Cargo.toml +++ b/bin/node/cli/Cargo.toml @@ -127,7 +127,6 @@ async-std = { version = "1.10.0", features = ["attributes"] } soketto = "0.4.2" criterion = { version = "0.3.5", features = ["async_tokio"] } tokio = { version = "1.17.0", features = ["macros", "time", "parking_lot"] } -jsonrpsee-ws-client = "0.4.1" wait-timeout = "0.2" remote-externalities = { path = "../../../utils/frame/remote-externalities" } pallet-timestamp = { version = "4.0.0-dev", path = "../../../frame/timestamp" } diff --git a/client/informant/src/display.rs b/client/informant/src/display.rs index 77b28dec55651..446ddf47b4cab 100644 --- a/client/informant/src/display.rs +++ b/client/informant/src/display.rs @@ -30,7 +30,7 @@ use std::{fmt, time::Instant}; /// like: /// /// > Syncing 5.4 bps, target=#531028 (4 peers), best: #90683 (0x4ca8…51b8), -/// > finalized #360 (0x6f24…a38b), ⬇ 5.5kiB/s ⬆ 0.9kiB/s +/// > finalized #360 (0x6f24…a38b), ⬇ 5.5kiB/s ⬆ 0.9kiB/s /// /// # Usage /// diff --git a/client/network/src/config.rs b/client/network/src/config.rs index 7c2dfef7fe89c..40aefe9a3ec2d 100644 --- a/client/network/src/config.rs +++ b/client/network/src/config.rs @@ -579,7 +579,7 @@ pub struct NonDefaultSetConfig { /// considered established once this protocol is open. /// /// > **Note**: This field isn't present for the default set, as this is handled internally - /// > by the networking code. + /// > by the networking code. pub notifications_protocol: Cow<'static, str>, /// If the remote reports that it doesn't support the protocol indicated in the /// `notifications_protocol` field, then each of these fallback names will be tried one by diff --git a/client/network/src/lib.rs b/client/network/src/lib.rs index 27e9a7547b44f..d9f5b3de1bb10 100644 --- a/client/network/src/lib.rs +++ b/client/network/src/lib.rs @@ -103,8 +103,8 @@ //! protocol ID. //! //! > **Note**: It is possible for the same connection to be used for multiple chains. For example, -//! > one can use both the `/dot/sync/2` and `/sub/sync/2` protocols on the same -//! > connection, provided that the remote supports them. +//! > one can use both the `/dot/sync/2` and `/sub/sync/2` protocols on the same +//! > connection, provided that the remote supports them. //! //! Substrate uses the following standard libp2p protocols: //! diff --git a/client/peerset/src/lib.rs b/client/peerset/src/lib.rs index c777cfec19d25..859319fab1320 100644 --- a/client/peerset/src/lib.rs +++ b/client/peerset/src/lib.rs @@ -129,7 +129,7 @@ impl PeersetHandle { /// Has no effect if the node was already a reserved peer. /// /// > **Note**: Keep in mind that the networking has to know an address for this node, - /// > otherwise it will not be able to connect to it. + /// > otherwise it will not be able to connect to it. pub fn add_reserved_peer(&self, set_id: SetId, peer_id: PeerId) { let _ = self.tx.unbounded_send(Action::AddReservedPeer(set_id, peer_id)); } @@ -232,7 +232,7 @@ pub struct SetConfig { /// List of bootstrap nodes to initialize the set with. /// /// > **Note**: Keep in mind that the networking has to know an address for these nodes, - /// > otherwise it will not be able to connect to them. + /// > otherwise it will not be able to connect to them. pub bootnodes: Vec, /// Lists of nodes we should always be connected to. diff --git a/client/peerset/src/peersstate.rs b/client/peerset/src/peersstate.rs index 272b903c1da2b..ca22cac324088 100644 --- a/client/peerset/src/peersstate.rs +++ b/client/peerset/src/peersstate.rs @@ -25,8 +25,8 @@ //! slots. //! //! > Note: This module is purely dedicated to managing slots and reputations. Features such as -//! > for example connecting to some nodes in priority should be added outside of this -//! > module, rather than inside. +//! > for example connecting to some nodes in priority should be added outside of this +//! > module, rather than inside. use libp2p::PeerId; use log::error; @@ -50,8 +50,8 @@ pub struct PeersState { /// List of nodes that we know about. /// /// > **Note**: This list should really be ordered by decreasing reputation, so that we can - /// easily select the best node to connect to. As a first draft, however, we don't - /// sort, to make the logic easier. + /// > easily select the best node to connect to. As a first draft, however, we don't sort, to + /// > make the logic easier. nodes: HashMap, /// Configuration of each set. The size of this `Vec` is never modified. diff --git a/utils/frame/remote-externalities/Cargo.toml b/utils/frame/remote-externalities/Cargo.toml index fc44c63b8acd4..343dda6892759 100644 --- a/utils/frame/remote-externalities/Cargo.toml +++ b/utils/frame/remote-externalities/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { version = "0.8", features = ["ws-client", "macros"] } +jsonrpsee = { version = "0.10.1", features = ["ws-client", "macros"] } env_logger = "0.9" frame-support = { path = "../../../frame/support", optional = true, version = "4.0.0-dev" } diff --git a/utils/frame/try-runtime/cli/Cargo.toml b/utils/frame/try-runtime/cli/Cargo.toml index 6f72bd3b9d7f2..3f0ee257d9756 100644 --- a/utils/frame/try-runtime/cli/Cargo.toml +++ b/utils/frame/try-runtime/cli/Cargo.toml @@ -32,4 +32,4 @@ sp-externalities = { version = "0.12.0", path = "../../../../primitives/external sp-version = { version = "5.0.0", path = "../../../../primitives/version" } remote-externalities = { version = "0.10.0-dev", path = "../../remote-externalities" } -jsonrpsee = { version = "0.4.1", default-features = false, features = ["ws-client"] } +jsonrpsee = { version = "0.10.1", default-features = false, features = ["ws-client"] } diff --git a/utils/frame/try-runtime/cli/src/commands/follow_chain.rs b/utils/frame/try-runtime/cli/src/commands/follow_chain.rs index 7400813b9175e..db305fa590760 100644 --- a/utils/frame/try-runtime/cli/src/commands/follow_chain.rs +++ b/utils/frame/try-runtime/cli/src/commands/follow_chain.rs @@ -20,7 +20,7 @@ use crate::{ state_machine_call_with_proof, SharedParams, LOG_TARGET, }; use jsonrpsee::{ - types::{traits::SubscriptionClient, Subscription}, + core::client::{Subscription, SubscriptionClientT}, ws_client::WsClientBuilder, }; use parity_scale_codec::Decode; @@ -76,13 +76,13 @@ where loop { let header = match subscription.next().await { - Ok(Some(header)) => header, - Ok(None) => { - log::warn!("subscription returned `None`. Probably decoding has failed."); + Some(Ok(header)) => header, + None => { + log::warn!("subscription closed"); break }, - Err(why) => { - log::warn!("subscription returned error: {:?}.", why); + Some(Err(why)) => { + log::warn!("subscription returned error: {:?}. Probably decoding has failed.", why); continue }, }; From 4412fce7f37c072abf4f4ed824874ce2bcd0d8f6 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Thu, 7 Apr 2022 09:46:51 +0200 Subject: [PATCH 096/484] relax input conditions of fixed::checked_from_integer (#11159) * relax input conditions of fixed::checked_from_integer * Fix build --- frame/transaction-payment/src/lib.rs | 2 +- primitives/arithmetic/src/fixed_point.rs | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/frame/transaction-payment/src/lib.rs b/frame/transaction-payment/src/lib.rs index 37fecf20cc528..12edb6f2c390b 100644 --- a/frame/transaction-payment/src/lib.rs +++ b/frame/transaction-payment/src/lib.rs @@ -348,7 +348,7 @@ pub mod pallet { // loss. assert!( ::max_value() >= - Multiplier::checked_from_integer( + Multiplier::checked_from_integer::( T::BlockWeights::get().max_block.try_into().unwrap() ) .unwrap(), diff --git a/primitives/arithmetic/src/fixed_point.rs b/primitives/arithmetic/src/fixed_point.rs index 3ecfb60ee0f52..7ce17bb72611f 100644 --- a/primitives/arithmetic/src/fixed_point.rs +++ b/primitives/arithmetic/src/fixed_point.rs @@ -121,7 +121,8 @@ pub trait FixedPointNumber: /// Creates `self` from an integer number `int`. /// /// Returns `None` if `int` exceeds accuracy. - fn checked_from_integer(int: Self::Inner) -> Option { + fn checked_from_integer>(int: N) -> Option { + let int: Self::Inner = int.into(); int.checked_mul(&Self::DIV).map(Self::from_inner) } @@ -898,31 +899,32 @@ macro_rules! implement_fixed { let accuracy = $name::accuracy(); // Case where integer fits. - let a = $name::checked_from_integer(42).expect("42 * accuracy <= inner_max; qed"); + let a = $name::checked_from_integer::<$inner_type>(42) + .expect("42 * accuracy <= inner_max; qed"); assert_eq!(a.into_inner(), 42 * accuracy); // Max integer that fit. - let a = $name::checked_from_integer(inner_max / accuracy) + let a = $name::checked_from_integer::<$inner_type>(inner_max / accuracy) .expect("(inner_max / accuracy) * accuracy <= inner_max; qed"); assert_eq!(a.into_inner(), (inner_max / accuracy) * accuracy); // Case where integer doesn't fit, so it returns `None`. - let a = $name::checked_from_integer(inner_max / accuracy + 1); + let a = $name::checked_from_integer::<$inner_type>(inner_max / accuracy + 1); assert_eq!(a, None); if $name::SIGNED { // Case where integer fits. - let a = $name::checked_from_integer(0.saturating_sub(42)) + let a = $name::checked_from_integer::<$inner_type>(0.saturating_sub(42)) .expect("-42 * accuracy >= inner_min; qed"); assert_eq!(a.into_inner(), 0 - 42 * accuracy); // Min integer that fit. - let a = $name::checked_from_integer(inner_min / accuracy) + let a = $name::checked_from_integer::<$inner_type>(inner_min / accuracy) .expect("(inner_min / accuracy) * accuracy <= inner_min; qed"); assert_eq!(a.into_inner(), (inner_min / accuracy) * accuracy); // Case where integer doesn't fit, so it returns `None`. - let a = $name::checked_from_integer(inner_min / accuracy - 1); + let a = $name::checked_from_integer::<$inner_type>(inner_min / accuracy - 1); assert_eq!(a, None); } } From 0433c3dd80cbfb65edcad27661c27e0e5baa1607 Mon Sep 17 00:00:00 2001 From: Stephen Shelton Date: Thu, 7 Apr 2022 11:24:59 +0000 Subject: [PATCH 097/484] Apply `WeightToFeePolynomial`s to `pallet_transaction_payment`'s length fee (#10785) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Apply WeightToFeePolynomials to length fee * Remove TransactionByteFee * Add test cases for ConstantModifierFee * Restore import * Use pallet::constant_name * Remove irrelevant TODO comment * Update frame/support/src/weights.rs * Update frame/transaction-payment/src/lib.rs * Update frame/transaction-payment/src/lib.rs * Update frame/transaction-payment/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * s/ConstantModifierFee/ConstantMultiplier/ * Impl LengthToFee for test configs * fmt * Remove unused import * Impl WeightToFeePolynomial for byte fee in ExtBuilder * Remove unused import * fix doc Co-authored-by: Bastian Köcher Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Co-authored-by: Shawn Tabrizi --- bin/node-template/runtime/src/lib.rs | 6 +-- bin/node/runtime/src/lib.rs | 4 +- frame/balances/src/tests_composite.rs | 2 +- frame/balances/src/tests_local.rs | 2 +- frame/balances/src/tests_reentrancy.rs | 2 +- frame/executive/src/lib.rs | 6 ++- frame/support/src/weights.rs | 42 +++++++++++++++- .../asset-tx-payment/src/tests.rs | 2 +- frame/transaction-payment/src/lib.rs | 49 ++++++++++++------- frame/transaction-payment/src/payment.rs | 3 +- 10 files changed, 85 insertions(+), 33 deletions(-) diff --git a/bin/node-template/runtime/src/lib.rs b/bin/node-template/runtime/src/lib.rs index 93cf234fa52b8..40adbb0388111 100644 --- a/bin/node-template/runtime/src/lib.rs +++ b/bin/node-template/runtime/src/lib.rs @@ -249,15 +249,11 @@ impl pallet_balances::Config for Runtime { type WeightInfo = pallet_balances::weights::SubstrateWeight; } -parameter_types! { - pub const TransactionByteFee: Balance = 1; -} - impl pallet_transaction_payment::Config for Runtime { type OnChargeTransaction = CurrencyAdapter; - type TransactionByteFee = TransactionByteFee; type OperationalFeeMultiplier = ConstU8<5>; type WeightToFee = IdentityFee; + type LengthToFee = IdentityFee; type FeeMultiplierUpdate = (); } diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 565f151ce2a08..57584bed39b2c 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -35,7 +35,7 @@ use frame_support::{ }, weights::{ constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND}, - DispatchClass, IdentityFee, Weight, + ConstantMultiplier, DispatchClass, IdentityFee, Weight, }, PalletId, RuntimeDebug, }; @@ -444,9 +444,9 @@ parameter_types! { impl pallet_transaction_payment::Config for Runtime { type OnChargeTransaction = CurrencyAdapter; - type TransactionByteFee = TransactionByteFee; type OperationalFeeMultiplier = OperationalFeeMultiplier; type WeightToFee = IdentityFee; + type LengthToFee = ConstantMultiplier; type FeeMultiplierUpdate = TargetedFeeAdjustment; } diff --git a/frame/balances/src/tests_composite.rs b/frame/balances/src/tests_composite.rs index a24be44927375..4a2cc1d91936d 100644 --- a/frame/balances/src/tests_composite.rs +++ b/frame/balances/src/tests_composite.rs @@ -78,9 +78,9 @@ impl frame_system::Config for Test { impl pallet_transaction_payment::Config for Test { type OnChargeTransaction = CurrencyAdapter, ()>; - type TransactionByteFee = ConstU64<1>; type OperationalFeeMultiplier = ConstU8<5>; type WeightToFee = IdentityFee; + type LengthToFee = IdentityFee; type FeeMultiplierUpdate = (); } diff --git a/frame/balances/src/tests_local.rs b/frame/balances/src/tests_local.rs index ae56f3b1f0f2c..cfc7f84ab3a38 100644 --- a/frame/balances/src/tests_local.rs +++ b/frame/balances/src/tests_local.rs @@ -79,9 +79,9 @@ impl frame_system::Config for Test { impl pallet_transaction_payment::Config for Test { type OnChargeTransaction = CurrencyAdapter, ()>; - type TransactionByteFee = ConstU64<1>; type OperationalFeeMultiplier = ConstU8<5>; type WeightToFee = IdentityFee; + type LengthToFee = IdentityFee; type FeeMultiplierUpdate = (); } diff --git a/frame/balances/src/tests_reentrancy.rs b/frame/balances/src/tests_reentrancy.rs index 4303efc2322c5..7037e9615afd8 100644 --- a/frame/balances/src/tests_reentrancy.rs +++ b/frame/balances/src/tests_reentrancy.rs @@ -85,9 +85,9 @@ impl frame_system::Config for Test { impl pallet_transaction_payment::Config for Test { type OnChargeTransaction = CurrencyAdapter, ()>; - type TransactionByteFee = ConstU64<1>; type OperationalFeeMultiplier = ConstU8<5>; type WeightToFee = IdentityFee; + type LengthToFee = IdentityFee; type FeeMultiplierUpdate = (); } diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index d19ea8127bad3..de1cc3b916404 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -576,7 +576,9 @@ mod tests { ConstU32, ConstU64, ConstU8, Currency, LockIdentifier, LockableCurrency, WithdrawReasons, }, - weights::{IdentityFee, RuntimeDbWeight, Weight, WeightToFeePolynomial}, + weights::{ + ConstantMultiplier, IdentityFee, RuntimeDbWeight, Weight, WeightToFeePolynomial, + }, }; use frame_system::{Call as SystemCall, ChainContext, LastRuntimeUpgradeInfo}; use pallet_balances::Call as BalancesCall; @@ -787,9 +789,9 @@ mod tests { } impl pallet_transaction_payment::Config for Runtime { type OnChargeTransaction = CurrencyAdapter; - type TransactionByteFee = TransactionByteFee; type OperationalFeeMultiplier = ConstU8<5>; type WeightToFee = IdentityFee; + type LengthToFee = ConstantMultiplier; type FeeMultiplierUpdate = (); } impl custom::Config for Runtime {} diff --git a/frame/support/src/weights.rs b/frame/support/src/weights.rs index b3ed42bb45fc4..4c3fcaa0fd423 100644 --- a/frame/support/src/weights.rs +++ b/frame/support/src/weights.rs @@ -132,7 +132,10 @@ mod extrinsic_weights; mod paritydb_weights; mod rocksdb_weights; -use crate::dispatch::{DispatchError, DispatchErrorWithPostInfo, DispatchResultWithPostInfo}; +use crate::{ + dispatch::{DispatchError, DispatchErrorWithPostInfo, DispatchResultWithPostInfo}, + traits::Get, +}; use codec::{Decode, Encode}; use scale_info::TypeInfo; #[cfg(feature = "std")] @@ -709,6 +712,34 @@ where } } +/// Implementor of [`WeightToFeePolynomial`] that uses a constant multiplier. +/// # Example +/// +/// ``` +/// # use frame_support::traits::ConstU128; +/// # use frame_support::weights::ConstantMultiplier; +/// // Results in a multiplier of 10 for each unit of weight (or length) +/// type LengthToFee = ConstantMultiplier::>; +/// ``` +pub struct ConstantMultiplier(sp_std::marker::PhantomData<(T, M)>); + +impl WeightToFeePolynomial for ConstantMultiplier +where + T: BaseArithmetic + From + Copy + Unsigned, + M: Get, +{ + type Balance = T; + + fn polynomial() -> WeightToFeeCoefficients { + smallvec!(WeightToFeeCoefficient { + coeff_integer: M::get(), + coeff_frac: Perbill::zero(), + negative: false, + degree: 1, + }) + } +} + /// A struct holding value for each `DispatchClass`. #[derive(Clone, Eq, PartialEq, Default, RuntimeDebug, Encode, Decode, TypeInfo)] pub struct PerDispatchClass { @@ -983,4 +1014,13 @@ mod tests { assert_eq!(IdentityFee::::calc(&50), 50); assert_eq!(IdentityFee::::calc(&Weight::max_value()), Balance::max_value()); } + + #[test] + fn constant_fee_works() { + use crate::traits::ConstU128; + assert_eq!(ConstantMultiplier::>::calc(&0), 0); + assert_eq!(ConstantMultiplier::>::calc(&50), 500); + assert_eq!(ConstantMultiplier::>::calc(&16), 16384); + assert_eq!(ConstantMultiplier::>::calc(&2), u128::MAX); + } } diff --git a/frame/transaction-payment/asset-tx-payment/src/tests.rs b/frame/transaction-payment/asset-tx-payment/src/tests.rs index f2a1ad1406575..d72a288ac7a33 100644 --- a/frame/transaction-payment/asset-tx-payment/src/tests.rs +++ b/frame/transaction-payment/asset-tx-payment/src/tests.rs @@ -145,8 +145,8 @@ impl WeightToFeePolynomial for WeightToFee { impl pallet_transaction_payment::Config for Runtime { type OnChargeTransaction = CurrencyAdapter; - type TransactionByteFee = TransactionByteFee; type WeightToFee = WeightToFee; + type LengthToFee = WeightToFee; type FeeMultiplierUpdate = (); type OperationalFeeMultiplier = ConstU8<5>; } diff --git a/frame/transaction-payment/src/lib.rs b/frame/transaction-payment/src/lib.rs index 12edb6f2c390b..1462faaa07062 100644 --- a/frame/transaction-payment/src/lib.rs +++ b/frame/transaction-payment/src/lib.rs @@ -260,10 +260,6 @@ pub mod pallet { /// might be refunded. In the end the fees can be deposited. type OnChargeTransaction: OnChargeTransaction; - /// The fee to be paid for making a transaction; the per-byte portion. - #[pallet::constant] - type TransactionByteFee: Get>; - /// A fee mulitplier for `Operational` extrinsics to compute "virtual tip" to boost their /// `priority` /// @@ -291,6 +287,9 @@ pub mod pallet { /// Convert a weight value into a deductible fee based on the currency type. type WeightToFee: WeightToFeePolynomial>; + /// Convert a length value into a deductible fee based on the currency type. + type LengthToFee: WeightToFeePolynomial>; + /// Update the multiplier of the next block, based on the previous block's weight. type FeeMultiplierUpdate: MultiplierUpdate; } @@ -302,6 +301,12 @@ pub mod pallet { fn weight_to_fee_polynomial() -> Vec>> { T::WeightToFee::polynomial().to_vec() } + + /// The polynomial that is applied in order to derive fee from length. + #[pallet::constant_name(LengthToFee)] + fn length_to_fee_polynomial() -> Vec>> { + T::LengthToFee::polynomial().to_vec() + } } #[pallet::type_value] @@ -510,25 +515,18 @@ where class: DispatchClass, ) -> FeeDetails> { if pays_fee == Pays::Yes { - let len = >::from(len); - let per_byte = T::TransactionByteFee::get(); - - // length fee. this is not adjusted. - let fixed_len_fee = per_byte.saturating_mul(len); - // the adjustable part of the fee. let unadjusted_weight_fee = Self::weight_to_fee(weight); let multiplier = Self::next_fee_multiplier(); // final adjusted weight fee. let adjusted_weight_fee = multiplier.saturating_mul_int(unadjusted_weight_fee); + // length fee. this is adjusted via `LengthToFee`. + let len_fee = Self::length_to_fee(len); + let base_fee = Self::weight_to_fee(T::BlockWeights::get().get(class).base_extrinsic); FeeDetails { - inclusion_fee: Some(InclusionFee { - base_fee, - len_fee: fixed_len_fee, - adjusted_weight_fee, - }), + inclusion_fee: Some(InclusionFee { base_fee, len_fee, adjusted_weight_fee }), tip, } } else { @@ -536,6 +534,10 @@ where } } + fn length_to_fee(length: u32) -> BalanceOf { + T::LengthToFee::calc(&(length as Weight)) + } + fn weight_to_fee(weight: Weight) -> BalanceOf { // cap the weight to the maximum defined in runtime, otherwise it will be the // `Bounded` maximum of its data type, which is not desired. @@ -835,8 +837,8 @@ mod tests { } parameter_types! { - pub static TransactionByteFee: u64 = 1; pub static WeightToFee: u64 = 1; + pub static TransactionByteFee: u64 = 1; pub static OperationalFeeMultiplier: u8 = 5; } @@ -892,6 +894,19 @@ mod tests { } } + impl WeightToFeePolynomial for TransactionByteFee { + type Balance = u64; + + fn polynomial() -> WeightToFeeCoefficients { + smallvec![WeightToFeeCoefficient { + degree: 1, + coeff_frac: Perbill::zero(), + coeff_integer: TRANSACTION_BYTE_FEE.with(|v| *v.borrow()), + negative: false, + }] + } + } + thread_local! { static TIP_UNBALANCED_AMOUNT: RefCell = RefCell::new(0); static FEE_UNBALANCED_AMOUNT: RefCell = RefCell::new(0); @@ -913,9 +928,9 @@ mod tests { impl Config for Runtime { type OnChargeTransaction = CurrencyAdapter; - type TransactionByteFee = TransactionByteFee; type OperationalFeeMultiplier = OperationalFeeMultiplier; type WeightToFee = WeightToFee; + type LengthToFee = TransactionByteFee; type FeeMultiplierUpdate = (); } diff --git a/frame/transaction-payment/src/payment.rs b/frame/transaction-payment/src/payment.rs index 58e6ef63109a3..5b4a613102792 100644 --- a/frame/transaction-payment/src/payment.rs +++ b/frame/transaction-payment/src/payment.rs @@ -12,7 +12,7 @@ use sp_runtime::{ use sp_std::{fmt::Debug, marker::PhantomData}; use frame_support::{ - traits::{Currency, ExistenceRequirement, Get, Imbalance, OnUnbalanced, WithdrawReasons}, + traits::{Currency, ExistenceRequirement, Imbalance, OnUnbalanced, WithdrawReasons}, unsigned::TransactionValidityError, }; @@ -73,7 +73,6 @@ pub struct CurrencyAdapter(PhantomData<(C, OU)>); impl OnChargeTransaction for CurrencyAdapter where T: Config, - T::TransactionByteFee: Get<::AccountId>>::Balance>, C: Currency<::AccountId>, C::PositiveImbalance: Imbalance< ::AccountId>>::Balance, From b8dadf531da752ccd20b4b24b9b78e48bdd21602 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Thu, 7 Apr 2022 15:00:30 +0200 Subject: [PATCH 098/484] Improve docs of `remove_all` and `remove_prefix` (#11182) These docs didn't mention how the removal works internally. This is important for the user to know that calling such a method multiple times in the same block leads always to the same result. --- frame/support/src/storage/mod.rs | 34 +++++++++++++++++-- frame/support/src/storage/types/double_map.rs | 21 ++++++++++-- frame/support/src/storage/types/map.rs | 6 ++++ frame/support/src/storage/types/nmap.rs | 25 ++++++++++++-- primitives/io/src/lib.rs | 10 +++--- 5 files changed, 84 insertions(+), 12 deletions(-) diff --git a/frame/support/src/storage/mod.rs b/frame/support/src/storage/mod.rs index f3eb94498ed92..066422ad456aa 100644 --- a/frame/support/src/storage/mod.rs +++ b/frame/support/src/storage/mod.rs @@ -502,7 +502,18 @@ pub trait StorageDoubleMap { KArg1: EncodeLike, KArg2: EncodeLike; - /// Remove all values under the first key. + /// Remove all values under the first key `k1` in the overlay and up to `limit` in the + /// backend. + /// + /// All values in the client overlay will be deleted, if there is some `limit` then up to + /// `limit` values are deleted from the client backend, if `limit` is none then all values in + /// the client backend are deleted. + /// + /// # Note + /// + /// Calling this multiple times per block with a `limit` set leads always to the same keys being + /// removed and the same result being returned. This happens because the keys to delete in the + /// overlay are not taken into account when deleting keys in the backend. fn remove_prefix(k1: KArg1, limit: Option) -> sp_io::KillStorageResult where KArg1: ?Sized + EncodeLike; @@ -632,7 +643,18 @@ pub trait StorageNMap { /// Remove the value under a key. fn remove + TupleToEncodedIter>(key: KArg); - /// Remove all values under the partial prefix key. + /// Remove all values starting with `partial_key` in the overlay and up to `limit` in the + /// backend. + /// + /// All values in the client overlay will be deleted, if there is some `limit` then up to + /// `limit` values are deleted from the client backend, if `limit` is none then all values in + /// the client backend are deleted. + /// + /// # Note + /// + /// Calling this multiple times per block with a `limit` set leads always to the same keys being + /// removed and the same result being returned. This happens because the keys to delete in the + /// overlay are not taken into account when deleting keys in the backend. fn remove_prefix(partial_key: KP, limit: Option) -> sp_io::KillStorageResult where K: HasKeyPrefix; @@ -1076,11 +1098,17 @@ pub trait StoragePrefixedMap { crate::storage::storage_prefix(Self::module_prefix(), Self::storage_prefix()) } - /// Remove all values of the storage in the overlay and up to `limit` in the backend. + /// Remove all values in the overlay and up to `limit` in the backend. /// /// All values in the client overlay will be deleted, if there is some `limit` then up to /// `limit` values are deleted from the client backend, if `limit` is none then all values in /// the client backend are deleted. + /// + /// # Note + /// + /// Calling this multiple times per block with a `limit` set leads always to the same keys being + /// removed and the same result being returned. This happens because the keys to delete in the + /// overlay are not taken into account when deleting keys in the backend. fn remove_all(limit: Option) -> sp_io::KillStorageResult { sp_io::storage::clear_prefix(&Self::final_prefix(), limit) } diff --git a/frame/support/src/storage/types/double_map.rs b/frame/support/src/storage/types/double_map.rs index e864920e488f0..42b903128934d 100644 --- a/frame/support/src/storage/types/double_map.rs +++ b/frame/support/src/storage/types/double_map.rs @@ -217,7 +217,18 @@ where >::remove(k1, k2) } - /// Remove all values under the first key. + /// Remove all values under `k1` in the overlay and up to `limit` in the + /// backend. + /// + /// All values in the client overlay will be deleted, if there is some `limit` then up to + /// `limit` values are deleted from the client backend, if `limit` is none then all values in + /// the client backend are deleted. + /// + /// # Note + /// + /// Calling this multiple times per block with a `limit` set leads always to the same keys being + /// removed and the same result being returned. This happens because the keys to delete in the + /// overlay are not taken into account when deleting keys in the backend. pub fn remove_prefix(k1: KArg1, limit: Option) -> sp_io::KillStorageResult where KArg1: ?Sized + EncodeLike, @@ -337,11 +348,17 @@ where >(key1, key2) } - /// Remove all values of the storage in the overlay and up to `limit` in the backend. + /// Remove all values in the overlay and up to `limit` in the backend. /// /// All values in the client overlay will be deleted, if there is some `limit` then up to /// `limit` values are deleted from the client backend, if `limit` is none then all values in /// the client backend are deleted. + /// + /// # Note + /// + /// Calling this multiple times per block with a `limit` set leads always to the same keys being + /// removed and the same result being returned. This happens because the keys to delete in the + /// overlay are not taken into account when deleting keys in the backend. pub fn remove_all(limit: Option) -> sp_io::KillStorageResult { >::remove_all(limit) } diff --git a/frame/support/src/storage/types/map.rs b/frame/support/src/storage/types/map.rs index 01aa2f44abe5e..bc32c58da8db6 100644 --- a/frame/support/src/storage/types/map.rs +++ b/frame/support/src/storage/types/map.rs @@ -241,6 +241,12 @@ where /// All values in the client overlay will be deleted, if there is some `limit` then up to /// `limit` values are deleted from the client backend, if `limit` is none then all values in /// the client backend are deleted. + /// + /// # Note + /// + /// Calling this multiple times per block with a `limit` set leads always to the same keys being + /// removed and the same result being returned. This happens because the keys to delete in the + /// overlay are not taken into account when deleting keys in the backend. pub fn remove_all(limit: Option) -> sp_io::KillStorageResult { >::remove_all(limit) } diff --git a/frame/support/src/storage/types/nmap.rs b/frame/support/src/storage/types/nmap.rs index 561bf5298e183..aff32f2110009 100755 --- a/frame/support/src/storage/types/nmap.rs +++ b/frame/support/src/storage/types/nmap.rs @@ -173,7 +173,18 @@ where >::remove(key) } - /// Remove all values under the first key. + /// Remove all values starting with `partial_key` in the overlay and up to `limit` in the + /// backend. + /// + /// All values in the client overlay will be deleted, if there is some `limit` then up to + /// `limit` values are deleted from the client backend, if `limit` is none then all values in + /// the client backend are deleted. + /// + /// # Note + /// + /// Calling this multiple times per block with a `limit` set leads always to the same keys being + /// removed and the same result being returned. This happens because the keys to delete in the + /// overlay are not taken into account when deleting keys in the backend. pub fn remove_prefix(partial_key: KP, limit: Option) -> sp_io::KillStorageResult where Key: HasKeyPrefix, @@ -277,7 +288,17 @@ where >::migrate_keys::<_>(key, hash_fns) } - /// Remove all value of the storage. + /// Remove all values in the overlay and up to `limit` in the backend. + /// + /// All values in the client overlay will be deleted, if there is some `limit` then up to + /// `limit` values are deleted from the client backend, if `limit` is none then all values in + /// the client backend are deleted. + /// + /// # Note + /// + /// Calling this multiple times per block with a `limit` set leads always to the same keys being + /// removed and the same result being returned. This happens because the keys to delete in the + /// overlay are not taken into account when deleting keys in the backend. pub fn remove_all(limit: Option) -> sp_io::KillStorageResult { >::remove_all(limit) } diff --git a/primitives/io/src/lib.rs b/primitives/io/src/lib.rs index 9f49a098b6add..2ff49b5048810 100644 --- a/primitives/io/src/lib.rs +++ b/primitives/io/src/lib.rs @@ -158,9 +158,7 @@ pub trait Storage { /// The limit can be used to partially delete a prefix storage in case it is too large /// to delete in one go (block). /// - /// It returns a boolean false iff some keys are remaining in - /// the prefix after the functions returns. Also returns a `u32` with - /// the number of keys removed from the process. + /// Returns [`KillStorageResult`] to inform about the result. /// /// # Note /// @@ -171,8 +169,10 @@ pub trait Storage { /// /// Calling this function multiple times per block for the same `prefix` does /// not make much sense because it is not cumulative when called inside the same block. - /// Use this function to distribute the deletion of a single child trie across multiple - /// blocks. + /// The deletion would always start from `prefix` resulting in the same keys being deleted + /// every time this function is called with the exact same arguments per block. This happens + /// because the keys in the overlay are not taken into account when deleting keys in the + /// backend. #[version(2)] fn clear_prefix(&mut self, prefix: &[u8], limit: Option) -> KillStorageResult { let (all_removed, num_removed) = Externalities::clear_prefix(*self, prefix, limit); From 00f728dbee22750e3e3d0283640be0ee27cbd963 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulo=20Silva=20de=20Souza?= <77391175+joao-paulo-parity@users.noreply.github.com> Date: Thu, 7 Apr 2022 11:23:33 -0300 Subject: [PATCH 099/484] update pr-custom-review.yml (#11169) syntax in accordance to https://github.com/paritytech/pr-custom-review/tree/5814820aa0e5d35412f31dc02f9d130a8b138cae#configuration-syntax rules in accordance to https://github.com/paritytech/pr-custom-review/blob/da1d81b9fd39705cc8b37f59235283801c818708/rules.md --- .github/pr-custom-review.yml | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/.github/pr-custom-review.yml b/.github/pr-custom-review.yml index 615203c11c15b..7ce8146571cd3 100644 --- a/.github/pr-custom-review.yml +++ b/.github/pr-custom-review.yml @@ -1,18 +1,16 @@ +# 🔒 PROTECTED: Changes to locks-review-team should be approved by the current locks-review-team +locks-review-team: locks-review +team-leads-team: polkadot-review +action-review-team: ci + rules: - - name: Changed runtime files - condition: .*/runtime/.*lib.rs + - name: Core developers check_type: changed_files + condition: .* min_approvals: 2 teams: - - substrateteamleads + - core-devs - - name: Substrate developers - check_type: changed_files - condition: .* - any: - - min_approvals: 2 - teams: - - core-devs - - min_approvals: 1 - teams: - - substrateteamleads \ No newline at end of file +prevent_review_request: + teams: + - core-devs From 98e8ae6915543c36d79de47b47f4028aaf09cbfa Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 7 Apr 2022 21:33:11 +0200 Subject: [PATCH 100/484] Sub-commands for `benchmark` (#11164) * Restructure benchmark commands Signed-off-by: Oliver Tale-Yazdi * Add benchmark block test Signed-off-by: Oliver Tale-Yazdi * Fixup imports Signed-off-by: Oliver Tale-Yazdi * CI Signed-off-by: Oliver Tale-Yazdi * Review fixes Signed-off-by: Oliver Tale-Yazdi * Extend error message Signed-off-by: Oliver Tale-Yazdi * Apply suggestions from code review Co-authored-by: Zeke Mostov * Review fixes Signed-off-by: Oliver Tale-Yazdi * Add commands to node-template Signed-off-by: Oliver Tale-Yazdi Co-authored-by: Zeke Mostov --- Cargo.lock | 4 + bin/node-template/node/Cargo.toml | 4 + bin/node-template/node/src/cli.rs | 4 +- bin/node-template/node/src/command.rs | 46 +++- bin/node-template/node/src/command_helper.rs | 131 ++++++++++++ bin/node-template/node/src/main.rs | 1 + bin/node-template/node/src/service.rs | 2 +- bin/node-template/runtime/src/lib.rs | 3 + bin/node/cli/src/cli.rs | 23 +- bin/node/cli/src/command.rs | 70 +++--- bin/node/cli/src/command_helper.rs | 12 +- bin/node/cli/tests/benchmark_block_works.rs | 48 +++++ .../cli/tests/benchmark_overhead_works.rs | 4 +- bin/node/cli/tests/benchmark_storage_works.rs | 4 +- .../frame/benchmarking-cli/src/block/bench.rs | 2 +- utils/frame/benchmarking-cli/src/block/cmd.rs | 4 +- utils/frame/benchmarking-cli/src/lib.rs | 201 +++++++----------- .../benchmarking-cli/src/overhead/bench.rs | 3 +- .../benchmarking-cli/src/overhead/cmd.rs | 8 +- .../benchmarking-cli/src/overhead/template.rs | 4 +- .../src/{ => pallet}/command.rs | 22 +- .../frame/benchmarking-cli/src/pallet/mod.rs | 150 +++++++++++++ .../src/{ => pallet}/template.hbs | 0 .../src/{ => pallet}/writer.rs | 44 +--- .../frame/benchmarking-cli/src/shared/mod.rs | 65 ++++++ .../benchmarking-cli/src/shared/record.rs | 72 +++++++ .../{storage/record.rs => shared/stats.rs} | 74 ++----- .../mod.rs => shared/weight_params.rs} | 8 +- .../frame/benchmarking-cli/src/storage/cmd.rs | 7 +- .../frame/benchmarking-cli/src/storage/mod.rs | 1 - .../benchmarking-cli/src/storage/read.rs | 3 +- .../benchmarking-cli/src/storage/template.rs | 5 +- .../benchmarking-cli/src/storage/write.rs | 3 +- 33 files changed, 690 insertions(+), 342 deletions(-) create mode 100644 bin/node-template/node/src/command_helper.rs create mode 100644 bin/node/cli/tests/benchmark_block_works.rs rename utils/frame/benchmarking-cli/src/{ => pallet}/command.rs (95%) create mode 100644 utils/frame/benchmarking-cli/src/pallet/mod.rs rename utils/frame/benchmarking-cli/src/{ => pallet}/template.hbs (100%) rename utils/frame/benchmarking-cli/src/{ => pallet}/writer.rs (93%) create mode 100644 utils/frame/benchmarking-cli/src/shared/mod.rs create mode 100644 utils/frame/benchmarking-cli/src/shared/record.rs rename utils/frame/benchmarking-cli/src/{storage/record.rs => shared/stats.rs} (70%) rename utils/frame/benchmarking-cli/src/{post_processing/mod.rs => shared/weight_params.rs} (92%) diff --git a/Cargo.lock b/Cargo.lock index 0a1a1e0b17bec..1ecb25b7b4b1e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5089,8 +5089,10 @@ dependencies = [ "clap 3.1.6", "frame-benchmarking", "frame-benchmarking-cli", + "frame-system", "jsonrpc-core", "node-template-runtime", + "pallet-transaction-payment", "pallet-transaction-payment-rpc", "sc-basic-authorship", "sc-cli", @@ -5113,6 +5115,8 @@ dependencies = [ "sp-consensus-aura", "sp-core", "sp-finality-grandpa", + "sp-inherents", + "sp-keyring", "sp-runtime", "sp-timestamp", "substrate-build-script-utils", diff --git a/bin/node-template/node/Cargo.toml b/bin/node-template/node/Cargo.toml index 4549a5b613da2..e642ce3c0411e 100644 --- a/bin/node-template/node/Cargo.toml +++ b/bin/node-template/node/Cargo.toml @@ -36,6 +36,10 @@ sp-finality-grandpa = { version = "4.0.0-dev", path = "../../../primitives/final sc-client-api = { version = "4.0.0-dev", path = "../../../client/api" } sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } sp-timestamp = { version = "4.0.0-dev", path = "../../../primitives/timestamp" } +sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } +sp-keyring = { version = "6.0.0", path = "../../../primitives/keyring" } +frame-system = { version = "4.0.0-dev", path = "../../../frame/system" } +pallet-transaction-payment = { version = "4.0.0-dev", default-features = false, path = "../../../frame/transaction-payment" } # These dependencies are used for the node template's RPCs jsonrpc-core = "18.0.0" diff --git a/bin/node-template/node/src/cli.rs b/bin/node-template/node/src/cli.rs index c4d27b71e4994..710c7f3f9e145 100644 --- a/bin/node-template/node/src/cli.rs +++ b/bin/node-template/node/src/cli.rs @@ -36,8 +36,8 @@ pub enum Subcommand { /// Revert the chain to a previous state. Revert(sc_cli::RevertCmd), - /// The custom benchmark subcommand benchmarking runtime pallets. - #[clap(name = "benchmark", about = "Benchmark runtime pallets.")] + /// Sub-commands concerned with benchmarking. + #[clap(subcommand)] Benchmark(frame_benchmarking_cli::BenchmarkCmd), /// Try some command against runtime state. diff --git a/bin/node-template/node/src/command.rs b/bin/node-template/node/src/command.rs index f033e779543a0..ede969b3572c8 100644 --- a/bin/node-template/node/src/command.rs +++ b/bin/node-template/node/src/command.rs @@ -1,11 +1,14 @@ use crate::{ chain_spec, cli::{Cli, Subcommand}, + command_helper::{inherent_benchmark_data, BenchmarkExtrinsicBuilder}, service, }; +use frame_benchmarking_cli::BenchmarkCmd; use node_template_runtime::Block; use sc_cli::{ChainSpec, RuntimeVersion, SubstrateCli}; use sc_service::PartialComponents; +use std::sync::Arc; impl SubstrateCli for Cli { fn impl_name() -> String { @@ -102,16 +105,41 @@ pub fn run() -> sc_cli::Result<()> { Ok((cmd.run(client, backend, Some(aux_revert)), task_manager)) }) }, - Some(Subcommand::Benchmark(cmd)) => - if cfg!(feature = "runtime-benchmarks") { - let runner = cli.create_runner(cmd)?; + Some(Subcommand::Benchmark(cmd)) => { + let runner = cli.create_runner(cmd)?; + + runner.sync_run(|config| { + let PartialComponents { client, backend, .. } = service::new_partial(&config)?; + + // This switch needs to be in the client, since the client decides + // which sub-commands it wants to support. + match cmd { + BenchmarkCmd::Pallet(cmd) => { + if !cfg!(feature = "runtime-benchmarks") { + return Err( + "Runtime benchmarking wasn't enabled when building the node. \ + You can enable it with `--features runtime-benchmarks`." + .into(), + ) + } + + cmd.run::(config) + }, + BenchmarkCmd::Block(cmd) => cmd.run(client), + BenchmarkCmd::Storage(cmd) => { + let db = backend.expose_db(); + let storage = backend.expose_storage(); - runner.sync_run(|config| cmd.run::(config)) - } else { - Err("Benchmarking wasn't enabled when building the node. You can enable it with \ - `--features runtime-benchmarks`." - .into()) - }, + cmd.run(config, client, db, storage) + }, + BenchmarkCmd::Overhead(cmd) => { + let ext_builder = BenchmarkExtrinsicBuilder::new(client.clone()); + + cmd.run(config, client, inherent_benchmark_data()?, Arc::new(ext_builder)) + }, + } + }) + }, #[cfg(feature = "try-runtime")] Some(Subcommand::TryRuntime(cmd)) => { let runner = cli.create_runner(cmd)?; diff --git a/bin/node-template/node/src/command_helper.rs b/bin/node-template/node/src/command_helper.rs new file mode 100644 index 0000000000000..287e81b1e96bd --- /dev/null +++ b/bin/node-template/node/src/command_helper.rs @@ -0,0 +1,131 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Contains code to setup the command invocations in [`super::command`] which would +//! otherwise bloat that module. + +use crate::service::FullClient; + +use node_template_runtime as runtime; +use runtime::SystemCall; +use sc_cli::Result; +use sc_client_api::BlockBackend; +use sp_core::{Encode, Pair}; +use sp_inherents::{InherentData, InherentDataProvider}; +use sp_keyring::Sr25519Keyring; +use sp_runtime::{OpaqueExtrinsic, SaturatedConversion}; + +use std::{sync::Arc, time::Duration}; + +/// Generates extrinsics for the `benchmark overhead` command. +/// +/// Note: Should only be used for benchmarking. +pub struct BenchmarkExtrinsicBuilder { + client: Arc, +} + +impl BenchmarkExtrinsicBuilder { + /// Creates a new [`Self`] from the given client. + pub fn new(client: Arc) -> Self { + Self { client } + } +} + +impl frame_benchmarking_cli::ExtrinsicBuilder for BenchmarkExtrinsicBuilder { + fn remark(&self, nonce: u32) -> std::result::Result { + let acc = Sr25519Keyring::Bob.pair(); + let extrinsic: OpaqueExtrinsic = create_benchmark_extrinsic( + self.client.as_ref(), + acc, + SystemCall::remark { remark: vec![] }.into(), + nonce, + ) + .into(); + + Ok(extrinsic) + } +} + +/// Create a transaction using the given `call`. +/// +/// Note: Should only be used for benchmarking. +pub fn create_benchmark_extrinsic( + client: &FullClient, + sender: sp_core::sr25519::Pair, + call: runtime::Call, + nonce: u32, +) -> runtime::UncheckedExtrinsic { + let genesis_hash = client.block_hash(0).ok().flatten().expect("Genesis block exists; qed"); + let best_hash = client.chain_info().best_hash; + let best_block = client.chain_info().best_number; + + let period = runtime::BlockHashCount::get() + .checked_next_power_of_two() + .map(|c| c / 2) + .unwrap_or(2) as u64; + let extra: runtime::SignedExtra = ( + frame_system::CheckNonZeroSender::::new(), + frame_system::CheckSpecVersion::::new(), + frame_system::CheckTxVersion::::new(), + frame_system::CheckGenesis::::new(), + frame_system::CheckEra::::from(sp_runtime::generic::Era::mortal( + period, + best_block.saturated_into(), + )), + frame_system::CheckNonce::::from(nonce), + frame_system::CheckWeight::::new(), + pallet_transaction_payment::ChargeTransactionPayment::::from(0), + ); + + let raw_payload = runtime::SignedPayload::from_raw( + call.clone(), + extra.clone(), + ( + (), + runtime::VERSION.spec_version, + runtime::VERSION.transaction_version, + genesis_hash, + best_hash, + (), + (), + (), + ), + ); + let signature = raw_payload.using_encoded(|e| sender.sign(e)); + + runtime::UncheckedExtrinsic::new_signed( + call.clone(), + sp_runtime::AccountId32::from(sender.public()).into(), + runtime::Signature::Sr25519(signature.clone()), + extra.clone(), + ) +} + +/// Generates inherent data for the `benchmark overhead` command. +/// +/// Note: Should only be used for benchmarking. +pub fn inherent_benchmark_data() -> Result { + let mut inherent_data = InherentData::new(); + let d = Duration::from_millis(0); + let timestamp = sp_timestamp::InherentDataProvider::new(d.into()); + + timestamp + .provide_inherent_data(&mut inherent_data) + .map_err(|e| format!("creating inherent data: {:?}", e))?; + Ok(inherent_data) +} diff --git a/bin/node-template/node/src/main.rs b/bin/node-template/node/src/main.rs index 4449d28b9fa41..0f2fbd5a909c6 100644 --- a/bin/node-template/node/src/main.rs +++ b/bin/node-template/node/src/main.rs @@ -6,6 +6,7 @@ mod chain_spec; mod service; mod cli; mod command; +mod command_helper; mod rpc; fn main() -> sc_cli::Result<()> { diff --git a/bin/node-template/node/src/service.rs b/bin/node-template/node/src/service.rs index fc7dc9b978df3..e2a8cb4ed834b 100644 --- a/bin/node-template/node/src/service.rs +++ b/bin/node-template/node/src/service.rs @@ -31,7 +31,7 @@ impl sc_executor::NativeExecutionDispatch for ExecutorDispatch { } } -type FullClient = +pub(crate) type FullClient = sc_service::TFullClient>; type FullBackend = sc_service::TFullBackend; type FullSelectChain = sc_consensus::LongestChain; diff --git a/bin/node-template/runtime/src/lib.rs b/bin/node-template/runtime/src/lib.rs index 40adbb0388111..780a84572d582 100644 --- a/bin/node-template/runtime/src/lib.rs +++ b/bin/node-template/runtime/src/lib.rs @@ -33,6 +33,7 @@ pub use frame_support::{ }, StorageValue, }; +pub use frame_system::Call as SystemCall; pub use pallet_balances::Call as BalancesCall; pub use pallet_timestamp::Call as TimestampCall; use pallet_transaction_payment::CurrencyAdapter; @@ -306,6 +307,8 @@ pub type SignedExtra = ( ); /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; +/// The payload being signed in transactions. +pub type SignedPayload = generic::SignedPayload; /// Executive: handles dispatch to the various modules. pub type Executive = frame_executive::Executive< Runtime, diff --git a/bin/node/cli/src/cli.rs b/bin/node/cli/src/cli.rs index 952c6311f8b38..7430cc46f4cc7 100644 --- a/bin/node/cli/src/cli.rs +++ b/bin/node/cli/src/cli.rs @@ -38,28 +38,11 @@ pub enum Subcommand { )] Inspect(node_inspect::cli::InspectCmd), - /// The custom benchmark subcommmand benchmarking runtime pallets. - #[clap(name = "benchmark", about = "Benchmark runtime pallets.")] + /// Sub-commands concerned with benchmarking. + /// The pallet benchmarking moved to the `pallet` sub-command. + #[clap(subcommand)] Benchmark(frame_benchmarking_cli::BenchmarkCmd), - /// Benchmark the execution time of historic blocks and compare it to their consumed weight. - #[clap( - name = "benchmark-block", - about = "Benchmark the execution time of historic blocks and compare it to their consumed weight." - )] - BenchmarkBlock(frame_benchmarking_cli::BlockCmd), - - /// Sub command for benchmarking the per-block and per-extrinsic execution overhead. - #[clap( - name = "benchmark-overhead", - about = "Benchmark the per-block and per-extrinsic execution overhead." - )] - BenchmarkOverhead(frame_benchmarking_cli::OverheadCmd), - - /// Sub command for benchmarking the storage speed. - #[clap(name = "benchmark-storage", about = "Benchmark storage speed.")] - BenchmarkStorage(frame_benchmarking_cli::StorageCmd), - /// Try some command against runtime state. #[cfg(feature = "try-runtime")] TryRuntime(try_runtime_cli::TryRuntimeCmd), diff --git a/bin/node/cli/src/command.rs b/bin/node/cli/src/command.rs index db243ff6f597b..c752b1c30ae26 100644 --- a/bin/node/cli/src/command.rs +++ b/bin/node/cli/src/command.rs @@ -16,11 +16,13 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +use super::command_helper::{inherent_benchmark_data, BenchmarkExtrinsicBuilder}; use crate::{ chain_spec, service, service::{new_partial, FullClient}, Cli, Subcommand, }; +use frame_benchmarking_cli::*; use node_executor::ExecutorDispatch; use node_primitives::Block; use node_runtime::RuntimeApi; @@ -92,45 +94,39 @@ pub fn run() -> Result<()> { runner.sync_run(|config| cmd.run::(config)) }, - Some(Subcommand::Benchmark(cmd)) => - if cfg!(feature = "runtime-benchmarks") { - let runner = cli.create_runner(cmd)?; - - runner.sync_run(|config| cmd.run::(config)) - } else { - Err("Benchmarking wasn't enabled when building the node. \ - You can enable it with `--features runtime-benchmarks`." - .into()) - }, - Some(Subcommand::BenchmarkBlock(cmd)) => { + Some(Subcommand::Benchmark(cmd)) => { let runner = cli.create_runner(cmd)?; - runner.async_run(|config| { - let PartialComponents { client, task_manager, .. } = new_partial(&config)?; - Ok((cmd.run(client), task_manager)) - }) - }, - Some(Subcommand::BenchmarkOverhead(cmd)) => { - let runner = cli.create_runner(cmd)?; - runner.async_run(|mut config| { - use super::command_helper::{inherent_data, ExtrinsicBuilder}; - // We don't use the authority role since that would start producing blocks - // in the background which would mess with our benchmark. - config.role = sc_service::Role::Full; - - let PartialComponents { client, task_manager, .. } = new_partial(&config)?; - let ext_builder = ExtrinsicBuilder::new(client.clone()); - - Ok((cmd.run(config, client, inherent_data()?, Arc::new(ext_builder)), task_manager)) - }) - }, - Some(Subcommand::BenchmarkStorage(cmd)) => { - let runner = cli.create_runner(cmd)?; - runner.async_run(|config| { - let PartialComponents { client, task_manager, backend, .. } = new_partial(&config)?; - let db = backend.expose_db(); - let storage = backend.expose_storage(); - Ok((cmd.run(config, client, db, storage), task_manager)) + runner.sync_run(|config| { + let PartialComponents { client, backend, .. } = new_partial(&config)?; + + // This switch needs to be in the client, since the client decides + // which sub-commands it wants to support. + match cmd { + BenchmarkCmd::Pallet(cmd) => { + if !cfg!(feature = "runtime-benchmarks") { + return Err( + "Runtime benchmarking wasn't enabled when building the node. \ + You can enable it with `--features runtime-benchmarks`." + .into(), + ) + } + + cmd.run::(config) + }, + BenchmarkCmd::Block(cmd) => cmd.run(client), + BenchmarkCmd::Storage(cmd) => { + let db = backend.expose_db(); + let storage = backend.expose_storage(); + + cmd.run(config, client, db, storage) + }, + BenchmarkCmd::Overhead(cmd) => { + let ext_builder = BenchmarkExtrinsicBuilder::new(client.clone()); + + cmd.run(config, client, inherent_benchmark_data()?, Arc::new(ext_builder)) + }, + } }) }, Some(Subcommand::Key(cmd)) => cmd.run(&cli), diff --git a/bin/node/cli/src/command_helper.rs b/bin/node/cli/src/command_helper.rs index 51fe7a5c5a7bf..84d85ee367cab 100644 --- a/bin/node/cli/src/command_helper.rs +++ b/bin/node/cli/src/command_helper.rs @@ -29,19 +29,19 @@ use sp_runtime::OpaqueExtrinsic; use std::{sync::Arc, time::Duration}; -/// Generates extrinsics for the `benchmark-overhead` command. -pub struct ExtrinsicBuilder { +/// Generates extrinsics for the `benchmark overhead` command. +pub struct BenchmarkExtrinsicBuilder { client: Arc, } -impl ExtrinsicBuilder { +impl BenchmarkExtrinsicBuilder { /// Creates a new [`Self`] from the given client. pub fn new(client: Arc) -> Self { Self { client } } } -impl frame_benchmarking_cli::ExtrinsicBuilder for ExtrinsicBuilder { +impl frame_benchmarking_cli::ExtrinsicBuilder for BenchmarkExtrinsicBuilder { fn remark(&self, nonce: u32) -> std::result::Result { let acc = Sr25519Keyring::Bob.pair(); let extrinsic: OpaqueExtrinsic = create_extrinsic( @@ -56,8 +56,8 @@ impl frame_benchmarking_cli::ExtrinsicBuilder for ExtrinsicBuilder { } } -/// Generates inherent data for the `benchmark-overhead` command. -pub fn inherent_data() -> Result { +/// Generates inherent data for the `benchmark overhead` command. +pub fn inherent_benchmark_data() -> Result { let mut inherent_data = InherentData::new(); let d = Duration::from_millis(0); let timestamp = sp_timestamp::InherentDataProvider::new(d.into()); diff --git a/bin/node/cli/tests/benchmark_block_works.rs b/bin/node/cli/tests/benchmark_block_works.rs new file mode 100644 index 0000000000000..37a4db25f363b --- /dev/null +++ b/bin/node/cli/tests/benchmark_block_works.rs @@ -0,0 +1,48 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +// Unix only since it uses signals from [`common::run_node_for_a_while`]. +#![cfg(unix)] + +use assert_cmd::cargo::cargo_bin; +use std::process::Command; +use tempfile::tempdir; + +pub mod common; + +/// `benchmark block` works for the dev runtime using the wasm executor. +#[tokio::test] +async fn benchmark_block_works() { + let base_dir = tempdir().expect("could not create a temp dir"); + + common::run_node_for_a_while(base_dir.path(), &["--dev"]).await; + + // Invoke `benchmark block` with all options to make sure that they are valid. + let status = Command::new(cargo_bin("substrate")) + .args(["benchmark", "block", "--dev"]) + .arg("-d") + .arg(base_dir.path()) + .args(["--pruning", "archive"]) + .args(["--from", "1", "--to", "1"]) + .args(["--repeat", "1"]) + .args(["--execution", "wasm", "--wasm-execution", "compiled"]) + .status() + .unwrap(); + + assert!(status.success()) +} diff --git a/bin/node/cli/tests/benchmark_overhead_works.rs b/bin/node/cli/tests/benchmark_overhead_works.rs index 550221ee2f70f..44dcebfbc0c35 100644 --- a/bin/node/cli/tests/benchmark_overhead_works.rs +++ b/bin/node/cli/tests/benchmark_overhead_works.rs @@ -20,7 +20,7 @@ use assert_cmd::cargo::cargo_bin; use std::process::Command; use tempfile::tempdir; -/// Tests that the `benchmark-overhead` command works for the substrate dev runtime. +/// Tests that the `benchmark overhead` command works for the substrate dev runtime. #[test] fn benchmark_overhead_works() { let tmp_dir = tempdir().expect("could not create a temp dir"); @@ -29,7 +29,7 @@ fn benchmark_overhead_works() { // Only put 10 extrinsics into the block otherwise it takes forever to build it // especially for a non-release build. let status = Command::new(cargo_bin("substrate")) - .args(&["benchmark-overhead", "--dev", "-d"]) + .args(&["benchmark", "overhead", "--dev", "-d"]) .arg(base_path) .arg("--weight-path") .arg(base_path) diff --git a/bin/node/cli/tests/benchmark_storage_works.rs b/bin/node/cli/tests/benchmark_storage_works.rs index 1628f9a7e97ba..30f860e48459f 100644 --- a/bin/node/cli/tests/benchmark_storage_works.rs +++ b/bin/node/cli/tests/benchmark_storage_works.rs @@ -23,7 +23,7 @@ use std::{ }; use tempfile::tempdir; -/// Tests that the `benchmark-storage` command works for the dev runtime. +/// Tests that the `benchmark storage` command works for the dev runtime. #[test] fn benchmark_storage_works() { let tmp_dir = tempdir().expect("could not create a temp dir"); @@ -39,7 +39,7 @@ fn benchmark_storage_works() { fn benchmark_storage(db: &str, base_path: &Path) -> ExitStatus { Command::new(cargo_bin("substrate")) - .args(&["benchmark-storage", "--dev"]) + .args(&["benchmark", "storage", "--dev"]) .arg("--db") .arg(db) .arg("--weight-path") diff --git a/utils/frame/benchmarking-cli/src/block/bench.rs b/utils/frame/benchmarking-cli/src/block/bench.rs index d3c1c97b04ab2..e48a7e8b3c6f5 100644 --- a/utils/frame/benchmarking-cli/src/block/bench.rs +++ b/utils/frame/benchmarking-cli/src/block/bench.rs @@ -34,7 +34,7 @@ use serde::Serialize; use std::{fmt::Debug, marker::PhantomData, sync::Arc, time::Instant}; use thousands::Separable; -use crate::storage::record::{StatSelect, Stats}; +use crate::shared::{StatSelect, Stats}; /// Log target for printing block weight info. const LOG_TARGET: &'static str = "benchmark::block::weight"; diff --git a/utils/frame/benchmarking-cli/src/block/cmd.rs b/utils/frame/benchmarking-cli/src/block/cmd.rs index 4618c1dd894e1..e4e1716b1c5ac 100644 --- a/utils/frame/benchmarking-cli/src/block/cmd.rs +++ b/utils/frame/benchmarking-cli/src/block/cmd.rs @@ -29,7 +29,7 @@ use std::{fmt::Debug, sync::Arc}; use super::bench::{Benchmark, BenchmarkParams}; -/// Benchmark the execution time historic blocks. +/// Benchmark the execution time of historic blocks. /// /// This can be used to verify that blocks do not use more weight than they consumed /// in their `WeightInfo`. Example: @@ -73,7 +73,7 @@ impl BlockCmd { /// Benchmark the execution time of historic blocks and compare it to their consumed weight. /// /// Output will be printed to console. - pub async fn run(&self, client: Arc) -> Result<()> + pub fn run(&self, client: Arc) -> Result<()> where Block: BlockT, BA: ClientBackend, diff --git a/utils/frame/benchmarking-cli/src/lib.rs b/utils/frame/benchmarking-cli/src/lib.rs index 288e6b4b86156..2543a63b2f159 100644 --- a/utils/frame/benchmarking-cli/src/lib.rs +++ b/utils/frame/benchmarking-cli/src/lib.rs @@ -15,144 +15,85 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! Contains the root [`BenchmarkCmd`] command and exports its sub-commands. + mod block; -mod command; -pub mod overhead; -mod post_processing; +mod overhead; +mod pallet; +mod shared; mod storage; -mod writer; - -use sc_cli::{ExecutionStrategy, WasmExecutionMethod, DEFAULT_WASM_EXECUTION_METHOD}; -use std::{fmt::Debug, path::PathBuf}; pub use block::BlockCmd; pub use overhead::{ExtrinsicBuilder, OverheadCmd}; +pub use pallet::PalletCmd; pub use storage::StorageCmd; -// Add a more relaxed parsing for pallet names by allowing pallet directory names with `-` to be -// used like crate names with `_` -fn parse_pallet_name(pallet: &str) -> String { - pallet.replace("-", "_") +use sc_cli::{CliConfiguration, DatabaseParams, ImportParams, PruningParams, Result, SharedParams}; + +/// The root `benchmarking` command. +/// +/// Has no effect itself besides printing a help menu of the sub-commands. +#[derive(Debug, clap::Subcommand)] +pub enum BenchmarkCmd { + Pallet(PalletCmd), + Storage(StorageCmd), + Overhead(OverheadCmd), + Block(BlockCmd), } -/// The `benchmark` command used to benchmark FRAME Pallets. -#[derive(Debug, clap::Parser)] -pub struct BenchmarkCmd { - /// Select a FRAME Pallet to benchmark, or `*` for all (in which case `extrinsic` must be `*`). - #[clap(short, long, parse(from_str = parse_pallet_name), required_unless_present = "list")] - pub pallet: Option, - - /// Select an extrinsic inside the pallet to benchmark, or `*` for all. - #[clap(short, long, required_unless_present = "list")] - pub extrinsic: Option, - - /// Select how many samples we should take across the variable components. - #[clap(short, long, default_value = "1")] - pub steps: u32, - - /// Indicates lowest values for each of the component ranges. - #[clap(long = "low", use_value_delimiter = true)] - pub lowest_range_values: Vec, - - /// Indicates highest values for each of the component ranges. - #[clap(long = "high", use_value_delimiter = true)] - pub highest_range_values: Vec, - - /// Select how many repetitions of this benchmark should run from within the wasm. - #[clap(short, long, default_value = "1")] - pub repeat: u32, - - /// Select how many repetitions of this benchmark should run from the client. - /// - /// NOTE: Using this alone may give slower results, but will afford you maximum Wasm memory. - #[clap(long, default_value = "1")] - pub external_repeat: u32, - - /// Print the raw results in JSON format. - #[clap(long = "json")] - pub json_output: bool, - - /// Write the raw results in JSON format into the given file. - #[clap(long, conflicts_with = "json-output")] - pub json_file: Option, - - /// Don't print the median-slopes linear regression analysis. - #[clap(long)] - pub no_median_slopes: bool, - - /// Don't print the min-squares linear regression analysis. - #[clap(long)] - pub no_min_squares: bool, - - /// Output the benchmarks to a Rust file at the given path. - #[clap(long)] - pub output: Option, - - /// Add a header file to your outputted benchmarks - #[clap(long)] - pub header: Option, - - /// Path to Handlebars template file used for outputting benchmark results. (Optional) - #[clap(long)] - pub template: Option, - - /// Which analysis function to use when outputting benchmarks: - /// * min-squares (default) - /// * median-slopes - /// * max (max of min squares and median slopes for each value) - #[clap(long)] - pub output_analysis: Option, - - /// Set the heap pages while running benchmarks. If not set, the default value from the client - /// is used. - #[clap(long)] - pub heap_pages: Option, - - /// Disable verification logic when running benchmarks. - #[clap(long)] - pub no_verify: bool, - - /// Display and run extra benchmarks that would otherwise not be needed for weight - /// construction. - #[clap(long)] - pub extra: bool, - - /// Estimate PoV size. - #[clap(long)] - pub record_proof: bool, - - #[allow(missing_docs)] - #[clap(flatten)] - pub shared_params: sc_cli::SharedParams, - - /// The execution strategy that should be used for benchmarks - #[clap(long, value_name = "STRATEGY", arg_enum, ignore_case = true)] - pub execution: Option, - - /// Method for executing Wasm runtime code. - #[clap( - long = "wasm-execution", - value_name = "METHOD", - possible_values = WasmExecutionMethod::variants(), - ignore_case = true, - default_value = DEFAULT_WASM_EXECUTION_METHOD, - )] - pub wasm_method: WasmExecutionMethod, - - /// Limit the memory the database cache can use. - #[clap(long = "db-cache", value_name = "MiB", default_value = "1024")] - pub database_cache_size: u32, - - /// List the benchmarks that match your query rather than running them. - /// - /// When nothing is provided, we list all benchmarks. - #[clap(long)] - pub list: bool, +/// Unwraps a [`BenchmarkCmd`] into its concrete sub-command. +macro_rules! unwrap_cmd { + { + $self:expr, + $cmd:ident, + $code:expr + } => { + match $self { + BenchmarkCmd::Pallet($cmd) => $code, + BenchmarkCmd::Storage($cmd) => $code, + BenchmarkCmd::Overhead($cmd) => $code, + BenchmarkCmd::Block($cmd) => $code, + } + } +} - /// If enabled, the storage info is not displayed in the output next to the analysis. - /// - /// This is independent of the storage info appearing in the *output file*. Use a Handlebar - /// template for that purpose. - #[clap(long)] - pub no_storage_info: bool, +/// Forward the [`CliConfiguration`] trait implementation. +/// +/// Each time a sub-command exposes a new config option, it must be added here. +impl CliConfiguration for BenchmarkCmd { + fn shared_params(&self) -> &SharedParams { + unwrap_cmd! { + self, cmd, cmd.shared_params() + } + } + + fn import_params(&self) -> Option<&ImportParams> { + unwrap_cmd! { + self, cmd, cmd.import_params() + } + } + + fn database_params(&self) -> Option<&DatabaseParams> { + unwrap_cmd! { + self, cmd, cmd.database_params() + } + } + + fn pruning_params(&self) -> Option<&PruningParams> { + unwrap_cmd! { + self, cmd, cmd.pruning_params() + } + } + + fn state_cache_size(&self) -> Result { + unwrap_cmd! { + self, cmd, cmd.state_cache_size() + } + } + + fn chain_id(&self, is_dev: bool) -> Result { + unwrap_cmd! { + self, cmd, cmd.chain_id(is_dev) + } + } } diff --git a/utils/frame/benchmarking-cli/src/overhead/bench.rs b/utils/frame/benchmarking-cli/src/overhead/bench.rs index 3e18c6a86db24..68f3f6597b466 100644 --- a/utils/frame/benchmarking-cli/src/overhead/bench.rs +++ b/utils/frame/benchmarking-cli/src/overhead/bench.rs @@ -36,7 +36,8 @@ use log::info; use serde::Serialize; use std::{marker::PhantomData, sync::Arc, time::Instant}; -use crate::{overhead::cmd::ExtrinsicBuilder, storage::record::Stats}; +use super::cmd::ExtrinsicBuilder; +use crate::shared::Stats; /// Parameters to configure an *overhead* benchmark. #[derive(Debug, Default, Serialize, Clone, PartialEq, Args)] diff --git a/utils/frame/benchmarking-cli/src/overhead/cmd.rs b/utils/frame/benchmarking-cli/src/overhead/cmd.rs index f74c32ba72a86..3cf281986861f 100644 --- a/utils/frame/benchmarking-cli/src/overhead/cmd.rs +++ b/utils/frame/benchmarking-cli/src/overhead/cmd.rs @@ -35,10 +35,10 @@ use crate::{ bench::{Benchmark, BenchmarkParams, BenchmarkType}, template::TemplateData, }, - post_processing::WeightParams, + shared::WeightParams, }; -/// Benchmarks the per-block and per-extrinsic execution overhead. +/// Benchmark the execution overhead per-block and per-extrinsic. #[derive(Debug, Parser)] pub struct OverheadCmd { #[allow(missing_docs)] @@ -76,11 +76,11 @@ pub trait ExtrinsicBuilder { } impl OverheadCmd { - /// Measures the per-block and per-extrinsic execution overhead. + /// Measure the per-block and per-extrinsic execution overhead. /// /// Writes the results to console and into two instances of the /// `weights.hbs` template, one for each benchmark. - pub async fn run( + pub fn run( &self, cfg: Configuration, client: Arc, diff --git a/utils/frame/benchmarking-cli/src/overhead/template.rs b/utils/frame/benchmarking-cli/src/overhead/template.rs index f6fb8ed9d929e..d5f90d4873866 100644 --- a/utils/frame/benchmarking-cli/src/overhead/template.rs +++ b/utils/frame/benchmarking-cli/src/overhead/template.rs @@ -28,7 +28,7 @@ use std::{env, fs, path::PathBuf}; use crate::{ overhead::{bench::BenchmarkType, cmd::OverheadParams}, - storage::record::Stats, + shared::{Stats, UnderscoreHelper}, }; static VERSION: &'static str = env!("CARGO_PKG_VERSION"); @@ -85,7 +85,7 @@ impl TemplateData { pub fn write(&self, path: &Option) -> Result<()> { let mut handlebars = Handlebars::new(); // Format large integers with underscores. - handlebars.register_helper("underscore", Box::new(crate::writer::UnderscoreHelper)); + handlebars.register_helper("underscore", Box::new(UnderscoreHelper)); // Don't HTML escape any characters. handlebars.register_escape_fn(|s| -> String { s.to_string() }); diff --git a/utils/frame/benchmarking-cli/src/command.rs b/utils/frame/benchmarking-cli/src/pallet/command.rs similarity index 95% rename from utils/frame/benchmarking-cli/src/command.rs rename to utils/frame/benchmarking-cli/src/pallet/command.rs index 0ced8b28ce016..89b8d018bcb5c 100644 --- a/utils/frame/benchmarking-cli/src/command.rs +++ b/utils/frame/benchmarking-cli/src/pallet/command.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::BenchmarkCmd; +use super::{writer, PalletCmd}; use codec::{Decode, Encode}; use frame_benchmarking::{ Analysis, BenchmarkBatch, BenchmarkBatchSplitResults, BenchmarkList, BenchmarkParameter, @@ -87,7 +87,13 @@ fn combine_batches( .collect::>() } -impl BenchmarkCmd { +/// Explains possible reasons why the metadata for the benchmarking could not be found. +const ERROR_METADATA_NOT_FOUND: &'static str = "Did not find the benchmarking metadata. \ +This could mean that you either did not build the node correctly with the \ +`--features runtime-benchmarks` flag, or the chain spec that you are using was \ +not created by a node that was compiled with the flag"; + +impl PalletCmd { /// Runs the command and benchmarks the chain. pub fn run(&self, config: Configuration) -> Result<()> where @@ -165,7 +171,7 @@ impl BenchmarkCmd { sp_core::testing::TaskExecutor::new(), ) .execute(strategy.into()) - .map_err(|e| format!("Error getting benchmark list: {}", e))?; + .map_err(|e| format!("{}: {}", ERROR_METADATA_NOT_FOUND, e))?; let (list, storage_info) = <(Vec, Vec) as Decode>::decode(&mut &result[..]) @@ -359,7 +365,7 @@ impl BenchmarkCmd { // Create the weights.rs file. if let Some(output_path) = &self.output { - crate::writer::write_results(&batches, &storage_info, output_path, self)?; + writer::write_results(&batches, &storage_info, output_path, self)?; } // Jsonify the result and write it to a file or stdout if desired. @@ -414,11 +420,7 @@ impl BenchmarkCmd { if !self.no_storage_info { let mut comments: Vec = Default::default(); - crate::writer::add_storage_comments( - &mut comments, - &batch.db_results, - &storage_info, - ); + writer::add_storage_comments(&mut comments, &batch.db_results, &storage_info); println!("Raw Storage Info\n========"); for comment in comments { println!("{}", comment); @@ -469,7 +471,7 @@ impl BenchmarkCmd { } } -impl CliConfiguration for BenchmarkCmd { +impl CliConfiguration for PalletCmd { fn shared_params(&self) -> &SharedParams { &self.shared_params } diff --git a/utils/frame/benchmarking-cli/src/pallet/mod.rs b/utils/frame/benchmarking-cli/src/pallet/mod.rs new file mode 100644 index 0000000000000..48ddcc7ce8eec --- /dev/null +++ b/utils/frame/benchmarking-cli/src/pallet/mod.rs @@ -0,0 +1,150 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +mod command; +mod writer; + +use sc_cli::{ExecutionStrategy, WasmExecutionMethod, DEFAULT_WASM_EXECUTION_METHOD}; +use std::{fmt::Debug, path::PathBuf}; + +// Add a more relaxed parsing for pallet names by allowing pallet directory names with `-` to be +// used like crate names with `_` +fn parse_pallet_name(pallet: &str) -> String { + pallet.replace("-", "_") +} + +/// Benchmark the extrinsic weight of FRAME Pallets. +#[derive(Debug, clap::Parser)] +pub struct PalletCmd { + /// Select a FRAME Pallet to benchmark, or `*` for all (in which case `extrinsic` must be `*`). + #[clap(short, long, parse(from_str = parse_pallet_name), required_unless_present = "list")] + pub pallet: Option, + + /// Select an extrinsic inside the pallet to benchmark, or `*` for all. + #[clap(short, long, required_unless_present = "list")] + pub extrinsic: Option, + + /// Select how many samples we should take across the variable components. + #[clap(short, long, default_value = "1")] + pub steps: u32, + + /// Indicates lowest values for each of the component ranges. + #[clap(long = "low", use_value_delimiter = true)] + pub lowest_range_values: Vec, + + /// Indicates highest values for each of the component ranges. + #[clap(long = "high", use_value_delimiter = true)] + pub highest_range_values: Vec, + + /// Select how many repetitions of this benchmark should run from within the wasm. + #[clap(short, long, default_value = "1")] + pub repeat: u32, + + /// Select how many repetitions of this benchmark should run from the client. + /// + /// NOTE: Using this alone may give slower results, but will afford you maximum Wasm memory. + #[clap(long, default_value = "1")] + pub external_repeat: u32, + + /// Print the raw results in JSON format. + #[clap(long = "json")] + pub json_output: bool, + + /// Write the raw results in JSON format into the given file. + #[clap(long, conflicts_with = "json-output")] + pub json_file: Option, + + /// Don't print the median-slopes linear regression analysis. + #[clap(long)] + pub no_median_slopes: bool, + + /// Don't print the min-squares linear regression analysis. + #[clap(long)] + pub no_min_squares: bool, + + /// Output the benchmarks to a Rust file at the given path. + #[clap(long)] + pub output: Option, + + /// Add a header file to your outputted benchmarks. + #[clap(long)] + pub header: Option, + + /// Path to Handlebars template file used for outputting benchmark results. (Optional) + #[clap(long)] + pub template: Option, + + /// Which analysis function to use when outputting benchmarks: + /// * min-squares (default) + /// * median-slopes + /// * max (max of min squares and median slopes for each value) + #[clap(long)] + pub output_analysis: Option, + + /// Set the heap pages while running benchmarks. If not set, the default value from the client + /// is used. + #[clap(long)] + pub heap_pages: Option, + + /// Disable verification logic when running benchmarks. + #[clap(long)] + pub no_verify: bool, + + /// Display and run extra benchmarks that would otherwise not be needed for weight + /// construction. + #[clap(long)] + pub extra: bool, + + /// Estimate PoV size. + #[clap(long)] + pub record_proof: bool, + + #[allow(missing_docs)] + #[clap(flatten)] + pub shared_params: sc_cli::SharedParams, + + /// The execution strategy that should be used for benchmarks. + #[clap(long, value_name = "STRATEGY", arg_enum, ignore_case = true)] + pub execution: Option, + + /// Method for executing Wasm runtime code. + #[clap( + long = "wasm-execution", + value_name = "METHOD", + possible_values = WasmExecutionMethod::variants(), + ignore_case = true, + default_value = DEFAULT_WASM_EXECUTION_METHOD, + )] + pub wasm_method: WasmExecutionMethod, + + /// Limit the memory the database cache can use. + #[clap(long = "db-cache", value_name = "MiB", default_value = "1024")] + pub database_cache_size: u32, + + /// List the benchmarks that match your query rather than running them. + /// + /// When nothing is provided, we list all benchmarks. + #[clap(long)] + pub list: bool, + + /// If enabled, the storage info is not displayed in the output next to the analysis. + /// + /// This is independent of the storage info appearing in the *output file*. Use a Handlebar + /// template for that purpose. + #[clap(long)] + pub no_storage_info: bool, +} diff --git a/utils/frame/benchmarking-cli/src/template.hbs b/utils/frame/benchmarking-cli/src/pallet/template.hbs similarity index 100% rename from utils/frame/benchmarking-cli/src/template.hbs rename to utils/frame/benchmarking-cli/src/pallet/template.hbs diff --git a/utils/frame/benchmarking-cli/src/writer.rs b/utils/frame/benchmarking-cli/src/pallet/writer.rs similarity index 93% rename from utils/frame/benchmarking-cli/src/writer.rs rename to utils/frame/benchmarking-cli/src/pallet/writer.rs index 17f1221e46d8b..cd97b3efbd9db 100644 --- a/utils/frame/benchmarking-cli/src/writer.rs +++ b/utils/frame/benchmarking-cli/src/pallet/writer.rs @@ -26,7 +26,7 @@ use std::{ use inflector::Inflector; use serde::Serialize; -use crate::BenchmarkCmd; +use crate::{shared::UnderscoreHelper, PalletCmd}; use frame_benchmarking::{ Analysis, AnalysisChoice, BenchmarkBatchSplitResults, BenchmarkResult, BenchmarkSelector, RegressionModel, @@ -68,7 +68,7 @@ struct BenchmarkData { comments: Vec, } -// This forwards some specific metadata from the `BenchmarkCmd` +// This forwards some specific metadata from the `PalletCmd` #[derive(Serialize, Default, Debug, Clone)] struct CmdData { steps: u32, @@ -255,7 +255,7 @@ pub fn write_results( batches: &[BenchmarkBatchSplitResults], storage_info: &[StorageInfo], path: &PathBuf, - cmd: &BenchmarkCmd, + cmd: &PalletCmd, ) -> Result<(), std::io::Error> { // Use custom template if provided. let template: String = match &cmd.template { @@ -416,44 +416,6 @@ pub(crate) fn add_storage_comments( } } -// Add an underscore after every 3rd character, i.e. a separator for large numbers. -fn underscore(i: Number) -> String -where - Number: std::string::ToString, -{ - let mut s = String::new(); - let i_str = i.to_string(); - let a = i_str.chars().rev().enumerate(); - for (idx, val) in a { - if idx != 0 && idx % 3 == 0 { - s.insert(0, '_'); - } - s.insert(0, val); - } - s -} - -// A Handlebars helper to add an underscore after every 3rd character, -// i.e. a separator for large numbers. -#[derive(Clone, Copy)] -pub(crate) struct UnderscoreHelper; -impl handlebars::HelperDef for UnderscoreHelper { - fn call<'reg: 'rc, 'rc>( - &self, - h: &handlebars::Helper, - _: &handlebars::Handlebars, - _: &handlebars::Context, - _rc: &mut handlebars::RenderContext, - out: &mut dyn handlebars::Output, - ) -> handlebars::HelperResult { - use handlebars::JsonRender; - let param = h.param(0).unwrap(); - let underscore_param = underscore(param.value().render()); - out.write(&underscore_param)?; - Ok(()) - } -} - // A helper to join a string of vectors. #[derive(Clone, Copy)] struct JoinHelper; diff --git a/utils/frame/benchmarking-cli/src/shared/mod.rs b/utils/frame/benchmarking-cli/src/shared/mod.rs new file mode 100644 index 0000000000000..f08d79b9aafca --- /dev/null +++ b/utils/frame/benchmarking-cli/src/shared/mod.rs @@ -0,0 +1,65 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Code that is shared among all benchmarking sub-commands. + +pub mod record; +pub mod stats; +pub mod weight_params; + +pub use record::BenchRecord; +pub use stats::{StatSelect, Stats}; +pub use weight_params::WeightParams; + +/// A Handlebars helper to add an underscore after every 3rd character, +/// i.e. a separator for large numbers. +#[derive(Clone, Copy)] +pub struct UnderscoreHelper; + +impl handlebars::HelperDef for UnderscoreHelper { + fn call<'reg: 'rc, 'rc>( + &self, + h: &handlebars::Helper, + _: &handlebars::Handlebars, + _: &handlebars::Context, + _rc: &mut handlebars::RenderContext, + out: &mut dyn handlebars::Output, + ) -> handlebars::HelperResult { + use handlebars::JsonRender; + let param = h.param(0).unwrap(); + let underscore_param = underscore(param.value().render()); + out.write(&underscore_param)?; + Ok(()) + } +} + +/// Add an underscore after every 3rd character, i.e. a separator for large numbers. +fn underscore(i: Number) -> String +where + Number: std::string::ToString, +{ + let mut s = String::new(); + let i_str = i.to_string(); + let a = i_str.chars().rev().enumerate(); + for (idx, val) in a { + if idx != 0 && idx % 3 == 0 { + s.insert(0, '_'); + } + s.insert(0, val); + } + s +} diff --git a/utils/frame/benchmarking-cli/src/shared/record.rs b/utils/frame/benchmarking-cli/src/shared/record.rs new file mode 100644 index 0000000000000..79ab37f651528 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/shared/record.rs @@ -0,0 +1,72 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Defines the [`BenchRecord`] and its facilities for computing [`super::Stats`]. + +use sc_cli::Result; +use sc_service::Configuration; + +use log::info; +use serde::Serialize; +use std::{fs, path::PathBuf, time::Duration}; + +use super::Stats; + +/// Raw output of a Storage benchmark. +#[derive(Debug, Default, Clone, Serialize)] +pub struct BenchRecord { + /// Multi-Map of value sizes and the time that it took to access them. + ns_per_size: Vec<(u64, u64)>, +} + +impl BenchRecord { + /// Appends a new record. Uses safe casts. + pub fn append(&mut self, size: usize, d: Duration) -> Result<()> { + let size: u64 = size.try_into().map_err(|e| format!("Size overflow u64: {}", e))?; + let ns: u64 = d + .as_nanos() + .try_into() + .map_err(|e| format!("Nanoseconds overflow u64: {}", e))?; + self.ns_per_size.push((size, ns)); + Ok(()) + } + + /// Returns the statistics for *time* and *value size*. + pub fn calculate_stats(self) -> Result<(Stats, Stats)> { + let (size, time): (Vec<_>, Vec<_>) = self.ns_per_size.into_iter().unzip(); + let size = Stats::new(&size)?; + let time = Stats::new(&time)?; + Ok((time, size)) // The swap of time/size here is intentional. + } + + /// Unless a path is specified, saves the raw results in a json file in the current directory. + /// Prefixes it with the DB name and suffixed with `path_suffix`. + pub fn save_json(&self, cfg: &Configuration, out_path: &PathBuf, suffix: &str) -> Result<()> { + let mut path = PathBuf::from(out_path); + if path.is_dir() || path.as_os_str().is_empty() { + path.push(&format!("{}_{}", cfg.database, suffix).to_lowercase()); + path.set_extension("json"); + } + + let json = serde_json::to_string_pretty(&self) + .map_err(|e| format!("Serializing as JSON: {:?}", e))?; + + fs::write(&path, json)?; + info!("Raw data written to {:?}", fs::canonicalize(&path)?); + Ok(()) + } +} diff --git a/utils/frame/benchmarking-cli/src/storage/record.rs b/utils/frame/benchmarking-cli/src/shared/stats.rs similarity index 70% rename from utils/frame/benchmarking-cli/src/storage/record.rs rename to utils/frame/benchmarking-cli/src/shared/stats.rs index 530fa4cdfe965..7785965fed4a7 100644 --- a/utils/frame/benchmarking-cli/src/storage/record.rs +++ b/utils/frame/benchmarking-cli/src/shared/stats.rs @@ -15,46 +15,38 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Calculates statistics and fills out the `weight.hbs` template. +//! Handles statistics that were generated from benchmarking results and +//! that can be used to fill out weight templates. use sc_cli::Result; -use sc_service::Configuration; -use log::info; use serde::Serialize; -use std::{fmt, fs, path::PathBuf, result, str::FromStr, time::Duration}; - -/// Raw output of a Storage benchmark. -#[derive(Debug, Default, Clone, Serialize)] -pub(crate) struct BenchRecord { - /// Multi-Map of value sizes and the time that it took to access them. - ns_per_size: Vec<(u64, u64)>, -} +use std::{fmt, result, str::FromStr}; /// Various statistics that help to gauge the quality of the produced weights. /// Will be written to the weight file and printed to console. #[derive(Serialize, Default, Clone)] -pub(crate) struct Stats { +pub struct Stats { /// Sum of all values. - pub(crate) sum: u64, + pub sum: u64, /// Minimal observed value. - pub(crate) min: u64, + pub min: u64, /// Maximal observed value. - pub(crate) max: u64, + pub max: u64, /// Average of all values. - pub(crate) avg: u64, + pub avg: u64, /// Median of all values. - pub(crate) median: u64, + pub median: u64, /// Standard derivation of all values. - pub(crate) stddev: f64, + pub stddev: f64, /// 99th percentile. At least 99% of all values are below this threshold. - pub(crate) p99: u64, + pub p99: u64, /// 95th percentile. At least 95% of all values are below this threshold. - pub(crate) p95: u64, + pub p95: u64, /// 75th percentile. At least 75% of all values are below this threshold. - pub(crate) p75: u64, + pub p75: u64, } /// Selects a specific field from a [`Stats`] object. @@ -75,44 +67,6 @@ pub enum StatSelect { P75Percentile, } -impl BenchRecord { - /// Appends a new record. Uses safe casts. - pub fn append(&mut self, size: usize, d: Duration) -> Result<()> { - let size: u64 = size.try_into().map_err(|e| format!("Size overflow u64: {}", e))?; - let ns: u64 = d - .as_nanos() - .try_into() - .map_err(|e| format!("Nanoseconds overflow u64: {}", e))?; - self.ns_per_size.push((size, ns)); - Ok(()) - } - - /// Returns the statistics for *time* and *value size*. - pub(crate) fn calculate_stats(self) -> Result<(Stats, Stats)> { - let (size, time): (Vec<_>, Vec<_>) = self.ns_per_size.into_iter().unzip(); - let size = Stats::new(&size)?; - let time = Stats::new(&time)?; - Ok((time, size)) // The swap of time/size here is intentional. - } - - /// Unless a path is specified, saves the raw results in a json file in the current directory. - /// Prefixes it with the DB name and suffixed with `path_suffix`. - pub fn save_json(&self, cfg: &Configuration, out_path: &PathBuf, suffix: &str) -> Result<()> { - let mut path = PathBuf::from(out_path); - if path.is_dir() || path.as_os_str().is_empty() { - path.push(&format!("{}_{}", cfg.database, suffix).to_lowercase()); - path.set_extension("json"); - } - - let json = serde_json::to_string_pretty(&self) - .map_err(|e| format!("Serializing as JSON: {:?}", e))?; - - fs::write(&path, json)?; - info!("Raw data written to {:?}", fs::canonicalize(&path)?); - Ok(()) - } -} - impl Stats { /// Calculates statistics and returns them. pub fn new(xs: &Vec) -> Result { @@ -137,7 +91,7 @@ impl Stats { } /// Returns the selected stat. - pub(crate) fn select(&self, s: StatSelect) -> u64 { + pub fn select(&self, s: StatSelect) -> u64 { match s { StatSelect::Maximum => self.max, StatSelect::Average => self.avg, diff --git a/utils/frame/benchmarking-cli/src/post_processing/mod.rs b/utils/frame/benchmarking-cli/src/shared/weight_params.rs similarity index 92% rename from utils/frame/benchmarking-cli/src/post_processing/mod.rs rename to utils/frame/benchmarking-cli/src/shared/weight_params.rs index fb20d9bd0c488..4dd80cd41ff3d 100644 --- a/utils/frame/benchmarking-cli/src/post_processing/mod.rs +++ b/utils/frame/benchmarking-cli/src/shared/weight_params.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Calculates a weight from the statistics of a benchmark result. +//! Calculates a weight from the [`super::Stats`] of a benchmark result. use sc_cli::Result; @@ -23,7 +23,7 @@ use clap::Args; use serde::Serialize; use std::path::PathBuf; -use crate::storage::record::{StatSelect, Stats}; +use super::{StatSelect, Stats}; /// Configures the weight generation. #[derive(Debug, Default, Serialize, Clone, PartialEq, Args)] @@ -55,7 +55,7 @@ pub struct WeightParams { /// `weight_mul` and adding `weight_add`. /// Does not use safe casts and can overflow. impl WeightParams { - pub(crate) fn calc_weight(&self, stat: &Stats) -> Result { + pub fn calc_weight(&self, stat: &Stats) -> Result { if self.weight_mul.is_sign_negative() || !self.weight_mul.is_normal() { return Err("invalid floating number for `weight_mul`".into()) } @@ -68,7 +68,7 @@ impl WeightParams { #[cfg(test)] mod test_weight_params { use super::WeightParams; - use crate::storage::record::{StatSelect, Stats}; + use crate::shared::{StatSelect, Stats}; #[test] fn calc_weight_works() { diff --git a/utils/frame/benchmarking-cli/src/storage/cmd.rs b/utils/frame/benchmarking-cli/src/storage/cmd.rs index c38e6636e5a3e..c2cc219ef1528 100644 --- a/utils/frame/benchmarking-cli/src/storage/cmd.rs +++ b/utils/frame/benchmarking-cli/src/storage/cmd.rs @@ -34,8 +34,9 @@ use sp_runtime::generic::BlockId; use std::{fmt::Debug, path::PathBuf, sync::Arc}; use super::template::TemplateData; -use crate::post_processing::WeightParams; -/// Benchmark the storage of a Substrate node with a live chain snapshot. +use crate::shared::WeightParams; + +/// Benchmark the storage speed of a chain snapshot. #[derive(Debug, Parser)] pub struct StorageCmd { #[allow(missing_docs)] @@ -99,7 +100,7 @@ pub struct StorageParams { impl StorageCmd { /// Calls into the Read and Write benchmarking functions. /// Processes the output and writes it into files and stdout. - pub async fn run( + pub fn run( &self, cfg: Configuration, client: Arc, diff --git a/utils/frame/benchmarking-cli/src/storage/mod.rs b/utils/frame/benchmarking-cli/src/storage/mod.rs index 9849cbcb6097b..0c722fdd47029 100644 --- a/utils/frame/benchmarking-cli/src/storage/mod.rs +++ b/utils/frame/benchmarking-cli/src/storage/mod.rs @@ -17,7 +17,6 @@ pub mod cmd; pub mod read; -pub mod record; pub mod template; pub mod write; diff --git a/utils/frame/benchmarking-cli/src/storage/read.rs b/utils/frame/benchmarking-cli/src/storage/read.rs index ca506202e1067..f58f3c3de0c19 100644 --- a/utils/frame/benchmarking-cli/src/storage/read.rs +++ b/utils/frame/benchmarking-cli/src/storage/read.rs @@ -27,7 +27,8 @@ use log::info; use rand::prelude::*; use std::{fmt::Debug, sync::Arc, time::Instant}; -use super::{cmd::StorageCmd, record::BenchRecord}; +use super::cmd::StorageCmd; +use crate::shared::BenchRecord; impl StorageCmd { /// Benchmarks the time it takes to read a single Storage item. diff --git a/utils/frame/benchmarking-cli/src/storage/template.rs b/utils/frame/benchmarking-cli/src/storage/template.rs index 10e6902b934bc..26aa8a962301b 100644 --- a/utils/frame/benchmarking-cli/src/storage/template.rs +++ b/utils/frame/benchmarking-cli/src/storage/template.rs @@ -22,7 +22,8 @@ use log::info; use serde::Serialize; use std::{env, fs, path::PathBuf}; -use super::{cmd::StorageParams, record::Stats}; +use super::cmd::StorageParams; +use crate::shared::{Stats, UnderscoreHelper}; static VERSION: &'static str = env!("CARGO_PKG_VERSION"); static TEMPLATE: &str = include_str!("./weights.hbs"); @@ -97,7 +98,7 @@ impl TemplateData { pub fn write(&self, path: &Option, hbs_template: &Option) -> Result<()> { let mut handlebars = handlebars::Handlebars::new(); // Format large integers with underscore. - handlebars.register_helper("underscore", Box::new(crate::writer::UnderscoreHelper)); + handlebars.register_helper("underscore", Box::new(UnderscoreHelper)); // Don't HTML escape any characters. handlebars.register_escape_fn(|s| -> String { s.to_string() }); // Use custom template if provided. diff --git a/utils/frame/benchmarking-cli/src/storage/write.rs b/utils/frame/benchmarking-cli/src/storage/write.rs index 94a0eea9728ff..d5d5bc2fffa5b 100644 --- a/utils/frame/benchmarking-cli/src/storage/write.rs +++ b/utils/frame/benchmarking-cli/src/storage/write.rs @@ -31,7 +31,8 @@ use log::{info, trace}; use rand::prelude::*; use std::{fmt::Debug, sync::Arc, time::Instant}; -use super::{cmd::StorageCmd, record::BenchRecord}; +use super::cmd::StorageCmd; +use crate::shared::BenchRecord; impl StorageCmd { /// Benchmarks the time it takes to write a single Storage item. From 4cc812be9618f5780b1538b1ac770cf54ae43a45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulo=20Silva=20de=20Souza?= <77391175+joao-paulo-parity@users.noreply.github.com> Date: Fri, 8 Apr 2022 04:38:51 -0300 Subject: [PATCH 101/484] fix .github/pr-custom-review.yml (#11187) --- .github/pr-custom-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/pr-custom-review.yml b/.github/pr-custom-review.yml index 7ce8146571cd3..518faf15a6113 100644 --- a/.github/pr-custom-review.yml +++ b/.github/pr-custom-review.yml @@ -11,6 +11,6 @@ rules: teams: - core-devs -prevent_review_request: +prevent-review-request: teams: - core-devs From e0cae4616425f52496bf338edf43dfabd4349281 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Apr 2022 10:41:21 +0200 Subject: [PATCH 102/484] Bump impl-trait-for-tuples from 0.2.1 to 0.2.2 (#11171) Bumps [impl-trait-for-tuples](https://github.com/bkchr/impl-trait-for-tuples) from 0.2.1 to 0.2.2. - [Release notes](https://github.com/bkchr/impl-trait-for-tuples/releases) - [Commits](https://github.com/bkchr/impl-trait-for-tuples/compare/v0.2.1...v0.2.2) --- updated-dependencies: - dependency-name: impl-trait-for-tuples dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- client/chain-spec/Cargo.toml | 2 +- frame/authorship/Cargo.toml | 2 +- frame/session/Cargo.toml | 2 +- frame/support/Cargo.toml | 2 +- frame/treasury/Cargo.toml | 2 +- primitives/inherents/Cargo.toml | 2 +- primitives/runtime-interface/Cargo.toml | 2 +- primitives/runtime/Cargo.toml | 2 +- primitives/wasm-interface/Cargo.toml | 2 +- 10 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1ecb25b7b4b1e..8c9227ea80ef4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3168,9 +3168,9 @@ dependencies = [ [[package]] name = "impl-trait-for-tuples" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5dacb10c5b3bb92d46ba347505a9041e676bb20ad220101326bffb0c93031ee" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" dependencies = [ "proc-macro2", "quote", diff --git a/client/chain-spec/Cargo.toml b/client/chain-spec/Cargo.toml index e18f9b72c75e4..bd19fd59d699e 100644 --- a/client/chain-spec/Cargo.toml +++ b/client/chain-spec/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] sc-chain-spec-derive = { version = "4.0.0-dev", path = "./derive" } -impl-trait-for-tuples = "0.2.1" +impl-trait-for-tuples = "0.2.2" sc-network = { version = "0.10.0-dev", path = "../network" } sp-core = { version = "6.0.0", path = "../../primitives/core" } serde = { version = "1.0.136", features = ["derive"] } diff --git a/frame/authorship/Cargo.toml b/frame/authorship/Cargo.toml index 77a44f5b5dd72..4557ee65f1a6b 100644 --- a/frame/authorship/Cargo.toml +++ b/frame/authorship/Cargo.toml @@ -22,7 +22,7 @@ sp-std = { version = "4.0.0", default-features = false, path = "../../primitives sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -impl-trait-for-tuples = "0.2.1" +impl-trait-for-tuples = "0.2.2" [dev-dependencies] sp-core = { version = "6.0.0", path = "../../primitives/core" } diff --git a/frame/session/Cargo.toml b/frame/session/Cargo.toml index 647b3482b2025..28f2df47027bc 100644 --- a/frame/session/Cargo.toml +++ b/frame/session/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] log = { version = "0.4.0", default-features = false } -impl-trait-for-tuples = "0.2.1" +impl-trait-for-tuples = "0.2.2" codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } diff --git a/frame/support/Cargo.toml b/frame/support/Cargo.toml index 3161e4f4db69b..ec596494c601b 100644 --- a/frame/support/Cargo.toml +++ b/frame/support/Cargo.toml @@ -31,7 +31,7 @@ paste = "1.0" once_cell = { version = "1", default-features = false, optional = true } sp-state-machine = { version = "0.12.0", optional = true, path = "../../primitives/state-machine" } bitflags = "1.3" -impl-trait-for-tuples = "0.2.1" +impl-trait-for-tuples = "0.2.2" smallvec = "1.8.0" log = { version = "0.4.14", default-features = false } sp-core-hashing-proc-macro = { version = "5.0.0", path = "../../primitives/core/hashing/proc-macro" } diff --git a/frame/treasury/Cargo.toml b/frame/treasury/Cargo.toml index f848f48acd254..85745c6c99fd4 100644 --- a/frame/treasury/Cargo.toml +++ b/frame/treasury/Cargo.toml @@ -19,7 +19,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = ] } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", features = ["derive"], optional = true } -impl-trait-for-tuples = "0.2.1" +impl-trait-for-tuples = "0.2.2" sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } diff --git a/primitives/inherents/Cargo.toml b/primitives/inherents/Cargo.toml index f7beb7bdfcf54..0e701a397d7d9 100644 --- a/primitives/inherents/Cargo.toml +++ b/primitives/inherents/Cargo.toml @@ -20,7 +20,7 @@ sp-core = { version = "6.0.0", default-features = false, path = "../core" } sp-runtime = { version = "6.0.0", path = "../runtime", optional = true } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } thiserror = { version = "1.0.30", optional = true } -impl-trait-for-tuples = "0.2.0" +impl-trait-for-tuples = "0.2.2" async-trait = { version = "0.1.50", optional = true } [dev-dependencies] diff --git a/primitives/runtime-interface/Cargo.toml b/primitives/runtime-interface/Cargo.toml index 883ea28e5e887..102592288af14 100644 --- a/primitives/runtime-interface/Cargo.toml +++ b/primitives/runtime-interface/Cargo.toml @@ -23,7 +23,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = static_assertions = "1.0.0" primitive-types = { version = "0.11.1", default-features = false } sp-storage = { version = "6.0.0", default-features = false, path = "../storage" } -impl-trait-for-tuples = "0.2.1" +impl-trait-for-tuples = "0.2.2" [dev-dependencies] sp-runtime-interface-test-wasm = { version = "2.0.0", path = "test-wasm" } diff --git a/primitives/runtime/Cargo.toml b/primitives/runtime/Cargo.toml index a0acdaf27dc5b..22d2ea62ae45f 100644 --- a/primitives/runtime/Cargo.toml +++ b/primitives/runtime/Cargo.toml @@ -26,7 +26,7 @@ sp-io = { version = "6.0.0", default-features = false, path = "../io" } log = { version = "0.4.14", default-features = false } paste = "1.0" rand = { version = "0.7.2", optional = true } -impl-trait-for-tuples = "0.2.1" +impl-trait-for-tuples = "0.2.2" parity-util-mem = { version = "0.11.0", default-features = false, features = ["primitive-types"] } hash256-std-hasher = { version = "0.15.2", default-features = false } either = { version = "1.5", default-features = false } diff --git a/primitives/wasm-interface/Cargo.toml b/primitives/wasm-interface/Cargo.toml index fa6c5ede18546..5fa0627490a22 100644 --- a/primitives/wasm-interface/Cargo.toml +++ b/primitives/wasm-interface/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] wasmi = { version = "0.9.1", optional = true } wasmtime = { version = "0.33.0", optional = true, default-features = false } log = { version = "0.4.14", optional = true } -impl-trait-for-tuples = "0.2.1" +impl-trait-for-tuples = "0.2.2" sp-std = { version = "4.0.0", path = "../std", default-features = false } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } From 7b24cd2bab4ec1cd2a57bb1e46fd0493c3364242 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 Apr 2022 10:18:25 +0000 Subject: [PATCH 103/484] Bump lru from 0.7.3 to 0.7.5 (#11190) Bumps [lru](https://github.com/jeromefroe/lru-rs) from 0.7.3 to 0.7.5. - [Release notes](https://github.com/jeromefroe/lru-rs/releases) - [Changelog](https://github.com/jeromefroe/lru-rs/blob/master/CHANGELOG.md) - [Commits](https://github.com/jeromefroe/lru-rs/compare/0.7.3...0.7.5) --- updated-dependencies: - dependency-name: lru dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 14 +++++++------- client/executor/Cargo.toml | 2 +- client/network-gossip/Cargo.toml | 2 +- client/network/Cargo.toml | 2 +- primitives/blockchain/Cargo.toml | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8c9227ea80ef4..c64800e002339 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4036,7 +4036,7 @@ dependencies = [ "libp2p-core", "libp2p-swarm", "log 0.4.14", - "lru 0.7.3", + "lru 0.7.5", "rand 0.7.3", "smallvec 1.8.0", "unsigned-varint 0.7.0", @@ -4336,9 +4336,9 @@ dependencies = [ [[package]] name = "lru" -version = "0.7.3" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb87f3080f6d1d69e8c564c0fcfde1d7aa8cc451ce40cae89479111f03bc0eb" +checksum = "32613e41de4c47ab04970c348ca7ae7382cf116625755af070b008a15516a889" dependencies = [ "hashbrown 0.11.2", ] @@ -8532,7 +8532,7 @@ dependencies = [ "env_logger 0.9.0", "hex-literal", "lazy_static", - "lru 0.7.3", + "lru 0.7.5", "parity-scale-codec", "parking_lot 0.12.0", "paste 1.0.6", @@ -8742,7 +8742,7 @@ dependencies = [ "linked-hash-map", "linked_hash_set", "log 0.4.14", - "lru 0.7.3", + "lru 0.7.5", "parity-scale-codec", "parking_lot 0.12.0", "pin-project 1.0.10", @@ -8786,7 +8786,7 @@ dependencies = [ "futures-timer", "libp2p", "log 0.4.14", - "lru 0.7.3", + "lru 0.7.5", "quickcheck", "sc-network", "sp-runtime", @@ -9860,7 +9860,7 @@ version = "4.0.0-dev" dependencies = [ "futures 0.3.21", "log 0.4.14", - "lru 0.7.3", + "lru 0.7.5", "parity-scale-codec", "parking_lot 0.12.0", "sp-api", diff --git a/client/executor/Cargo.toml b/client/executor/Cargo.toml index 34416b1e0d460..8e10de1b04a95 100644 --- a/client/executor/Cargo.toml +++ b/client/executor/Cargo.toml @@ -32,7 +32,7 @@ sc-executor-wasmi = { version = "0.10.0-dev", path = "wasmi" } sc-executor-wasmtime = { version = "0.10.0-dev", path = "wasmtime", optional = true } parking_lot = "0.12.0" sp-core-hashing-proc-macro = { version = "5.0.0", path = "../../primitives/core/hashing/proc-macro" } -lru = "0.7.3" +lru = "0.7.5" tracing = "0.1.29" [dev-dependencies] diff --git a/client/network-gossip/Cargo.toml b/client/network-gossip/Cargo.toml index 51f5b658402ef..9854575ab6b02 100644 --- a/client/network-gossip/Cargo.toml +++ b/client/network-gossip/Cargo.toml @@ -19,7 +19,7 @@ futures = "0.3.21" futures-timer = "3.0.1" libp2p = { version = "0.40.0", default-features = false } log = "0.4.8" -lru = "0.7.3" +lru = "0.7.5" ahash = "0.7.6" prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } sc-network = { version = "0.10.0-dev", path = "../network" } diff --git a/client/network/Cargo.toml b/client/network/Cargo.toml index be69663b9a544..6d70d41964de4 100644 --- a/client/network/Cargo.toml +++ b/client/network/Cargo.toml @@ -34,7 +34,7 @@ hex = "0.4.0" ip_network = "0.4.1" linked-hash-map = "0.5.4" linked_hash_set = "0.1.3" -lru = "0.7.3" +lru = "0.7.5" log = "0.4.8" parking_lot = "0.12.0" pin-project = "1.0.10" diff --git a/primitives/blockchain/Cargo.toml b/primitives/blockchain/Cargo.toml index a79bc9c5591cf..c6fcdd3cb1c02 100644 --- a/primitives/blockchain/Cargo.toml +++ b/primitives/blockchain/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] log = "0.4.11" -lru = "0.7.3" +lru = "0.7.5" parking_lot = "0.12.0" thiserror = "1.0.30" futures = "0.3.21" From 371e52c99184ae44b63f86844660e8b16a9ad962 Mon Sep 17 00:00:00 2001 From: Koute Date: Fri, 8 Apr 2022 20:26:47 +0900 Subject: [PATCH 104/484] Catch panics on the FFI boundary between the runtime and the host for `wasmtime` (#11189) * Catch panics on the FFI boundary between the runtime and the host for `wasmtime` * Use an already existing test runtime function * Merge the tests together --- client/executor/runtime-test/src/lib.rs | 4 ++- client/executor/src/integration_tests/mod.rs | 19 +++++++++++--- .../host_function_interface.rs | 26 +++++++++++++++---- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/client/executor/runtime-test/src/lib.rs b/client/executor/runtime-test/src/lib.rs index bf9f76edd945e..0c61d6fcd38a2 100644 --- a/client/executor/runtime-test/src/lib.rs +++ b/client/executor/runtime-test/src/lib.rs @@ -113,7 +113,9 @@ sp_core::wasm_export_functions! { } } - fn test_exhaust_heap() -> Vec { Vec::with_capacity(16777216) } + fn test_allocate_vec(size: u32) -> Vec { + Vec::with_capacity(size as usize) + } fn test_fp_f32add(a: [u8; 4], b: [u8; 4]) -> [u8; 4] { let a = f32::from_le_bytes(a); diff --git a/client/executor/src/integration_tests/mod.rs b/client/executor/src/integration_tests/mod.rs index 462a8ba1b8766..75b458a399e3f 100644 --- a/client/executor/src/integration_tests/mod.rs +++ b/client/executor/src/integration_tests/mod.rs @@ -466,13 +466,24 @@ fn should_trap_when_heap_exhausted(wasm_method: WasmExecutionMethod) { RuntimeBlob::uncompress_if_needed(wasm_binary_unwrap()).unwrap(), &mut ext.ext(), true, - "test_exhaust_heap", - &[0], + "test_allocate_vec", + &16777216_u32.encode(), ) - .map_err(|e| e.to_string()) .unwrap_err(); - assert!(err.contains("Allocator ran out of space")); + match err { + #[cfg(feature = "wasmtime")] + Error::AbortedDueToTrap(error) if wasm_method == WasmExecutionMethod::Compiled => { + assert_eq!( + error.message, + r#"host code panicked while being called by the runtime: Failed to allocate memory: "Allocator ran out of space""# + ); + }, + Error::RuntimePanicked(error) if wasm_method == WasmExecutionMethod::Interpreted => { + assert_eq!(error, r#"Failed to allocate memory: "Allocator ran out of space""#); + }, + error => panic!("unexpected error: {:?}", error), + } } fn mk_test_runtime(wasm_method: WasmExecutionMethod, pages: u64) -> Arc { 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 1566bbf302c3b..03da0bed59815 100644 --- a/primitives/runtime-interface/proc-macro/src/runtime_interface/host_function_interface.rs +++ b/primitives/runtime-interface/proc-macro/src/runtime_interface/host_function_interface.rs @@ -374,11 +374,27 @@ fn generate_host_function_implementation( -> std::result::Result<#ffi_return_ty, #crate_::sp_wasm_interface::wasmtime::Trap> { T::with_function_context(caller, move |__function_context__| { - #struct_name::call( - __function_context__, - #(#ffi_names,)* - ) - }).map_err(#crate_::sp_wasm_interface::wasmtime::Trap::new) + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + #struct_name::call( + __function_context__, + #(#ffi_names,)* + ).map_err(#crate_::sp_wasm_interface::wasmtime::Trap::new) + })); + match result { + Ok(result) => result, + Err(panic) => { + let message = + if let Some(message) = panic.downcast_ref::() { + format!("host code panicked while being called by the runtime: {}", message) + } else if let Some(message) = panic.downcast_ref::<&'static str>() { + format!("host code panicked while being called by the runtime: {}", message) + } else { + "host code panicked while being called by the runtime".to_owned() + }; + return Err(#crate_::sp_wasm_interface::wasmtime::Trap::new(message)); + } + } + }) } )?; }; From c7f5eeca509ae54d4a249a853b432744aedf9376 Mon Sep 17 00:00:00 2001 From: Sergejs Kostjucenko <85877331+sergejparity@users.noreply.github.com> Date: Fri, 8 Apr 2022 18:39:10 +0300 Subject: [PATCH 105/484] Add GHA for custom PR review (#10951) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add GHA for custom PR review * Change FILES rule settings * Update rules according to feedback * Update .github/pr-custom-review.yml Co-authored-by: Denis Pisarev <17856421+TriplEight@users.noreply.github.com> * CI: PRCR new 🔒 team is locks-review * CI: rename a confusing step * Update .github/workflows/pr-custom-review.yml Co-authored-by: João Paulo Silva de Souza <77391175+joao-paulo-parity@users.noreply.github.com> * CI: use a proper new team for as per discussion with @drahnr it was decided to create a dedicated team for reviewing runtime files * Update pr-custom-review.yml * Update .github/workflows/pr-custom-review.yml Co-authored-by: João Paulo Silva de Souza <77391175+joao-paulo-parity@users.noreply.github.com> * Update .github/workflows/pr-custom-review.yml Co-authored-by: João Paulo Silva de Souza <77391175+joao-paulo-parity@users.noreply.github.com> Co-authored-by: Denis Pisarev <17856421+TriplEight@users.noreply.github.com> Co-authored-by: TriplEight Co-authored-by: Denis Pisarev Co-authored-by: João Paulo Silva de Souza <77391175+joao-paulo-parity@users.noreply.github.com> --- .github/workflows/pr-custom-review.yml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 .github/workflows/pr-custom-review.yml diff --git a/.github/workflows/pr-custom-review.yml b/.github/workflows/pr-custom-review.yml new file mode 100644 index 0000000000000..d113d39067846 --- /dev/null +++ b/.github/workflows/pr-custom-review.yml @@ -0,0 +1,23 @@ +name: Check reviews + +on: + pull_request: + branches: + - master + - main + types: + - opened + - reopened + - synchronize + - review_requested + - review_request_removed + pull_request_review: + +jobs: + pr-custom-review: + runs-on: ubuntu-latest + steps: + - name: pr-custom-review + uses: paritytech/pr-custom-review@v2 + with: + token: ${{ secrets.PRCR_TOKEN }} From 26068400bf800dd0494a66a1fcbefc10a96f3ca0 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Sat, 9 Apr 2022 14:08:46 +0200 Subject: [PATCH 106/484] Remove useless trait bound and `move` instructions (#11193) * Remove useless trait bound and `move` instructions * cargo fmt --- bin/node-template/node/src/command.rs | 2 +- bin/node/cli/src/command.rs | 2 +- client/finality-grandpa/src/lib.rs | 5 +---- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/bin/node-template/node/src/command.rs b/bin/node-template/node/src/command.rs index ede969b3572c8..0c850b322a7d2 100644 --- a/bin/node-template/node/src/command.rs +++ b/bin/node-template/node/src/command.rs @@ -98,7 +98,7 @@ pub fn run() -> sc_cli::Result<()> { runner.async_run(|config| { let PartialComponents { client, task_manager, backend, .. } = service::new_partial(&config)?; - let aux_revert = Box::new(move |client, _, blocks| { + let aux_revert = Box::new(|client, _, blocks| { sc_finality_grandpa::revert(client, blocks)?; Ok(()) }); diff --git a/bin/node/cli/src/command.rs b/bin/node/cli/src/command.rs index c752b1c30ae26..ea91bb90b2f2c 100644 --- a/bin/node/cli/src/command.rs +++ b/bin/node/cli/src/command.rs @@ -175,7 +175,7 @@ pub fn run() -> Result<()> { let runner = cli.create_runner(cmd)?; runner.async_run(|config| { let PartialComponents { client, task_manager, backend, .. } = new_partial(&config)?; - let aux_revert = Box::new(move |client: Arc, backend, blocks| { + let aux_revert = Box::new(|client: Arc, backend, blocks| { sc_consensus_babe::revert(client.clone(), backend, blocks)?; grandpa::revert(client, blocks)?; Ok(()) diff --git a/client/finality-grandpa/src/lib.rs b/client/finality-grandpa/src/lib.rs index 34d5b6bb1f70c..17e04affa0f98 100644 --- a/client/finality-grandpa/src/lib.rs +++ b/client/finality-grandpa/src/lib.rs @@ -1170,10 +1170,7 @@ fn local_authority_id( pub fn revert(client: Arc, blocks: NumberFor) -> ClientResult<()> where Block: BlockT, - Client: AuxStore - + HeaderMetadata - + HeaderBackend - + ProvideRuntimeApi, + Client: AuxStore + HeaderMetadata + HeaderBackend, { let best_number = client.info().best_number; let finalized = client.info().finalized_number; From 513636382406eb902099f937b6667bc99f309d5b Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Mon, 11 Apr 2022 09:45:45 +0200 Subject: [PATCH 107/484] Normalization of CLI options format (CamelCase => kebab-case) (#11194) * Convert cli options from 'CamelCase' to 'kebab-case' * Remove not required 'as_str' for 'ExecutionStrategy' option --- client/cli/src/arg_enums.rs | 30 ++++++------------- client/cli/src/commands/run_cmd.rs | 10 +++---- client/cli/src/params/mod.rs | 4 +-- client/cli/src/params/network_params.rs | 18 +++++++---- client/cli/src/params/node_key_params.rs | 2 +- .../cli/src/params/offchain_worker_params.rs | 2 +- client/cli/src/params/shared_params.rs | 2 +- utils/frame/try-runtime/cli/src/lib.rs | 2 +- 8 files changed, 32 insertions(+), 38 deletions(-) diff --git a/client/cli/src/arg_enums.rs b/client/cli/src/arg_enums.rs index a09b7f824c7ba..ac50413803278 100644 --- a/client/cli/src/arg_enums.rs +++ b/client/cli/src/arg_enums.rs @@ -88,7 +88,7 @@ impl Into for WasmExecutionMethod { /// The default [`WasmExecutionMethod`]. #[cfg(feature = "wasmtime")] -pub const DEFAULT_WASM_EXECUTION_METHOD: &str = "Compiled"; +pub const DEFAULT_WASM_EXECUTION_METHOD: &str = "compiled"; /// The default [`WasmExecutionMethod`]. #[cfg(not(feature = "wasmtime"))] @@ -96,7 +96,7 @@ pub const DEFAULT_WASM_EXECUTION_METHOD: &str = "interpreted-i-know-what-i-do"; #[allow(missing_docs)] #[derive(Debug, Copy, Clone, PartialEq, Eq, ArgEnum)] -#[clap(rename_all = "PascalCase")] +#[clap(rename_all = "kebab-case")] pub enum TracingReceiver { /// Output the tracing records using the log. Log, @@ -112,7 +112,7 @@ impl Into for TracingReceiver { /// The type of the node key. #[derive(Debug, Copy, Clone, PartialEq, Eq, ArgEnum)] -#[clap(rename_all = "PascalCase")] +#[clap(rename_all = "kebab-case")] pub enum NodeKeyType { /// Use ed25519. Ed25519, @@ -120,7 +120,7 @@ pub enum NodeKeyType { /// The crypto scheme to use. #[derive(Debug, Copy, Clone, PartialEq, Eq, ArgEnum)] -#[clap(rename_all = "PascalCase")] +#[clap(rename_all = "kebab-case")] pub enum CryptoScheme { /// Use ed25519. Ed25519, @@ -132,7 +132,7 @@ pub enum CryptoScheme { /// The type of the output format. #[derive(Debug, Copy, Clone, PartialEq, Eq, ArgEnum)] -#[clap(rename_all = "PascalCase")] +#[clap(rename_all = "kebab-case")] pub enum OutputType { /// Output as json. Json, @@ -142,7 +142,7 @@ pub enum OutputType { /// How to execute blocks #[derive(Debug, Copy, Clone, PartialEq, Eq, ArgEnum)] -#[clap(rename_all = "PascalCase")] +#[clap(rename_all = "kebab-case")] pub enum ExecutionStrategy { /// Execute with native build (if available, WebAssembly otherwise). Native, @@ -165,22 +165,10 @@ impl Into for ExecutionStrategy { } } -impl ExecutionStrategy { - /// Returns the variant as `'&static str`. - pub fn as_str(&self) -> &'static str { - match self { - Self::Native => "Native", - Self::Wasm => "Wasm", - Self::Both => "Both", - Self::NativeElseWasm => "NativeElseWasm", - } - } -} - /// Available RPC methods. #[allow(missing_docs)] #[derive(Debug, Copy, Clone, PartialEq, ArgEnum)] -#[clap(rename_all = "PascalCase")] +#[clap(rename_all = "kebab-case")] pub enum RpcMethods { /// Expose every RPC method only when RPC is listening on `localhost`, /// otherwise serve only safe RPC methods. @@ -243,7 +231,7 @@ impl Database { /// Whether off-chain workers are enabled. #[allow(missing_docs)] #[derive(Debug, Clone, ArgEnum)] -#[clap(rename_all = "PascalCase")] +#[clap(rename_all = "kebab-case")] pub enum OffchainWorkerEnabled { /// Always have offchain worker enabled. Always, @@ -255,7 +243,7 @@ pub enum OffchainWorkerEnabled { /// Syncing mode. #[derive(Debug, Clone, Copy, ArgEnum, PartialEq)] -#[clap(rename_all = "PascalCase")] +#[clap(rename_all = "kebab-case")] pub enum SyncMode { /// Full sync. Download end verify all blocks. Full, diff --git a/client/cli/src/commands/run_cmd.rs b/client/cli/src/commands/run_cmd.rs index b9318813b0480..dea1e2a6dae5b 100644 --- a/client/cli/src/commands/run_cmd.rs +++ b/client/cli/src/commands/run_cmd.rs @@ -71,16 +71,16 @@ pub struct RunCmd { /// RPC methods to expose. /// - /// - `Unsafe`: Exposes every RPC method. - /// - `Safe`: Exposes only a safe subset of RPC methods, denying unsafe RPC methods. - /// - `Auto`: Acts as `Safe` if RPC is served externally, e.g. when `--{rpc,ws}-external` is - /// passed, otherwise acts as `Unsafe`. + /// - `unsafe`: Exposes every RPC method. + /// - `safe`: Exposes only a safe subset of RPC methods, denying unsafe RPC methods. + /// - `auto`: Acts as `safe` if RPC is served externally, e.g. when `--{rpc,ws}-external` is + /// passed, otherwise acts as `unsafe`. #[clap( long, value_name = "METHOD SET", arg_enum, ignore_case = true, - default_value = "Auto", + default_value = "auto", verbatim_doc_comment )] pub rpc_methods: RpcMethods, diff --git a/client/cli/src/params/mod.rs b/client/cli/src/params/mod.rs index 33e714ec138b0..9fccce606b4e4 100644 --- a/client/cli/src/params/mod.rs +++ b/client/cli/src/params/mod.rs @@ -118,7 +118,7 @@ impl BlockNumberOrHash { #[derive(Debug, Clone, Args)] pub struct CryptoSchemeFlag { /// cryptography scheme - #[clap(long, value_name = "SCHEME", arg_enum, ignore_case = true, default_value = "Sr25519")] + #[clap(long, value_name = "SCHEME", arg_enum, ignore_case = true, default_value = "sr25519")] pub scheme: CryptoScheme, } @@ -126,7 +126,7 @@ pub struct CryptoSchemeFlag { #[derive(Debug, Clone, Args)] pub struct OutputTypeFlag { /// output format - #[clap(long, value_name = "FORMAT", arg_enum, ignore_case = true, default_value = "Text")] + #[clap(long, value_name = "FORMAT", arg_enum, ignore_case = true, default_value = "text")] pub output_type: OutputType, } diff --git a/client/cli/src/params/network_params.rs b/client/cli/src/params/network_params.rs index 7a265c5e27609..ac5039fb89754 100644 --- a/client/cli/src/params/network_params.rs +++ b/client/cli/src/params/network_params.rs @@ -132,12 +132,18 @@ pub struct NetworkParams { /// Blockchain syncing mode. /// - /// - `Full`: Download and validate full blockchain history. - /// - /// - `Fast`: Download blocks and the latest state only. - /// - /// - `FastUnsafe`: Same as `Fast`, but skip downloading state proofs. - #[clap(long, arg_enum, value_name = "SYNC_MODE", default_value = "Full", ignore_case(true))] + /// - `full`: Download and validate full blockchain history. + /// - `fast`: Download blocks and the latest state only. + /// - `fast-unsafe`: Same as `fast`, but skip downloading state proofs. + /// - `warp`: Download the latest state and proof. + #[clap( + long, + arg_enum, + value_name = "SYNC_MODE", + default_value = "full", + ignore_case = true, + verbatim_doc_comment + )] pub sync: SyncMode, } diff --git a/client/cli/src/params/node_key_params.rs b/client/cli/src/params/node_key_params.rs index f31fd854cbb56..699d0c4ece8dc 100644 --- a/client/cli/src/params/node_key_params.rs +++ b/client/cli/src/params/node_key_params.rs @@ -66,7 +66,7 @@ pub struct NodeKeyParams { /// /// The node's secret key determines the corresponding public key and hence the /// node's peer ID in the context of libp2p. - #[clap(long, value_name = "TYPE", arg_enum, ignore_case = true, default_value = "Ed25519")] + #[clap(long, value_name = "TYPE", arg_enum, ignore_case = true, default_value = "ed25519")] pub node_key_type: NodeKeyType, /// The file from which to read the node's secret key to use for libp2p networking. diff --git a/client/cli/src/params/offchain_worker_params.rs b/client/cli/src/params/offchain_worker_params.rs index 3ab507f108598..f01c75a5aa70d 100644 --- a/client/cli/src/params/offchain_worker_params.rs +++ b/client/cli/src/params/offchain_worker_params.rs @@ -40,7 +40,7 @@ pub struct OffchainWorkerParams { value_name = "ENABLED", arg_enum, ignore_case = true, - default_value = "WhenValidating" + default_value = "when-validating" )] pub enabled: OffchainWorkerEnabled, diff --git a/client/cli/src/params/shared_params.rs b/client/cli/src/params/shared_params.rs index 99eddf4e7c4b7..01df2cf9abba4 100644 --- a/client/cli/src/params/shared_params.rs +++ b/client/cli/src/params/shared_params.rs @@ -76,7 +76,7 @@ pub struct SharedParams { pub tracing_targets: Option, /// Receiver to process tracing messages. - #[clap(long, value_name = "RECEIVER", arg_enum, ignore_case = true, default_value = "Log")] + #[clap(long, value_name = "RECEIVER", arg_enum, ignore_case = true, default_value = "log")] pub tracing_receiver: TracingReceiver, } diff --git a/utils/frame/try-runtime/cli/src/lib.rs b/utils/frame/try-runtime/cli/src/lib.rs index ef8db7a1e5a0c..a9047259838cf 100644 --- a/utils/frame/try-runtime/cli/src/lib.rs +++ b/utils/frame/try-runtime/cli/src/lib.rs @@ -387,7 +387,7 @@ pub struct SharedParams { pub shared_params: sc_cli::SharedParams, /// The execution strategy that should be used. - #[clap(long, value_name = "STRATEGY", arg_enum, ignore_case = true, default_value = "Wasm")] + #[clap(long, value_name = "STRATEGY", arg_enum, ignore_case = true, default_value = "wasm")] pub execution: ExecutionStrategy, /// Type of wasm execution used. From bbaf5f34408ff7fe87cdac2aa9845020c3755c4b Mon Sep 17 00:00:00 2001 From: Xiliang Chen Date: Mon, 11 Apr 2022 20:35:25 +1200 Subject: [PATCH 108/484] add sort & try_append to bounded_vec (#11196) --- frame/support/src/storage/bounded_vec.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/frame/support/src/storage/bounded_vec.rs b/frame/support/src/storage/bounded_vec.rs index 137015098cfa6..92ca167f98436 100644 --- a/frame/support/src/storage/bounded_vec.rs +++ b/frame/support/src/storage/bounded_vec.rs @@ -129,6 +129,16 @@ impl BoundedVec { self.0.sort_by(compare) } + /// Exactly the same semantics as [`slice::sort`]. + /// + /// This is safe since sorting cannot change the number of elements in the vector. + pub fn sort(&mut self) + where + T: sp_std::cmp::Ord, + { + self.0.sort() + } + /// Exactly the same semantics as `Vec::remove`. /// /// # Panics @@ -374,6 +384,17 @@ impl> BoundedVec { } } + /// Exactly the same semantics as [`Vec::append`], but returns an error and does nothing if the + /// length of the outcome is larger than the bound. + pub fn try_append(&mut self, other: &mut Vec) -> Result<(), ()> { + if other.len().saturating_add(self.len()) <= Self::bound() { + self.0.append(other); + Ok(()) + } else { + Err(()) + } + } + /// Consumes self and mutates self via the given `mutate` function. /// /// If the outcome of mutation is within bounds, `Some(Self)` is returned. Else, `None` is From 6349f5e302ed884253bdfb3d03725ca70cc988ca Mon Sep 17 00:00:00 2001 From: Dominique Date: Mon, 11 Apr 2022 10:36:13 +0200 Subject: [PATCH 109/484] Updated docs for method `set_payee` & `set_controller` (#11192) * Updated docs for method 'set_payee' * Updated docs for method 'set_controller' --- frame/staking/src/pallet/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index fa8c453c2b0fe..498d861660961 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -1109,7 +1109,7 @@ pub mod pallet { /// (Re-)set the payment target for a controller. /// - /// Effects will be felt at the beginning of the next era. + /// Effects will be felt instantly (as soon as this function is completed successfully). /// /// The dispatch origin for this call must be _Signed_ by the controller, not the stash. /// @@ -1137,7 +1137,7 @@ pub mod pallet { /// (Re-)set the controller of a stash. /// - /// Effects will be felt at the beginning of the next era. + /// Effects will be felt instantly (as soon as this function is completed successfully). /// /// The dispatch origin for this call must be _Signed_ by the stash, not the controller. /// From 28f1b3116e9c950d346860008cc41ddca5582897 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Mon, 11 Apr 2022 11:21:54 +0200 Subject: [PATCH 110/484] Prepare for rust stable 1.60 (#11138) * Prepare for rust stable 1.59 Besides preparing the UI tests this also adds a new script update-rust-stable.sh script for simplifying the update of a rust stable version. This script will run all UI tests for the new rust stable version and updating the expected output. * Ensure we run the UI tests in CI * use staging ci image * More test updates * Unignore test (#11097) * empty commit for pipeline rerun * empty commit for pipeline rerun * Try to make clippy happy * More clippy fixes * FMT * ci image production Co-authored-by: alvicsam Co-authored-by: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> --- .gitlab-ci.yml | 2 + .maintain/update-rust-stable.sh | 42 ++++++++ bin/node/cli/benches/transaction_pool.rs | 6 +- client/network/src/service.rs | 2 +- client/offchain/src/lib.rs | 4 +- client/service/src/client/call_executor.rs | 42 ++++---- client/service/src/client/wasm_override.rs | 2 +- docs/CONTRIBUTING.adoc | 8 ++ .../solution-type/src/lib.rs | 5 + .../test/tests/construct_runtime_ui.rs | 5 + .../no_std_genesis_config.stderr | 31 +++--- .../undefined_genesis_config_part.stderr | 31 +++--- frame/support/test/tests/decl_module_ui.rs | 5 + frame/support/test/tests/decl_storage_ui.rs | 5 + .../support/test/tests/derive_no_bound_ui.rs | 5 + .../tests/derive_no_bound_ui/clone.stderr | 14 +-- .../tests/derive_no_bound_ui/default.stderr | 14 +-- frame/support/test/tests/pallet_ui.rs | 5 + .../call_argument_invalid_bound.stderr | 14 +-- .../call_argument_invalid_bound_2.stderr | 68 +++++-------- .../error_does_not_derive_pallet_error.stderr | 17 ++-- .../pallet_ui/event_field_not_member.stderr | 14 +-- ...age_ensure_span_are_ok_on_wrong_gen.stderr | 99 ++++++------------- ...re_span_are_ok_on_wrong_gen_unnamed.stderr | 99 ++++++------------- .../pallet_ui/storage_info_unsatisfied.stderr | 17 ++-- .../storage_info_unsatisfied_nmap.stderr | 5 - primitives/api/test/tests/trybuild.rs | 5 + .../ui/changed_in_no_default_method.stderr | 4 +- .../ui/impl_incorrect_method_signature.stderr | 32 +++--- .../tests/ui/mock_only_self_reference.stderr | 42 ++++---- ...reference_in_impl_runtime_apis_call.stderr | 32 +++--- primitives/runtime-interface/tests/ui.rs | 5 + .../state-machine/src/in_memory_backend.rs | 2 +- .../state-machine/src/trie_backend_essence.rs | 9 +- primitives/version/src/embed.rs | 3 +- utils/frame/remote-externalities/src/lib.rs | 2 +- 36 files changed, 329 insertions(+), 368 deletions(-) create mode 100755 .maintain/update-rust-stable.sh diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d196ade85a6fb..029c486365088 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -381,6 +381,8 @@ test-linux-stable: &test-linux RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" RUST_BACKTRACE: 1 WASM_BUILD_NO_COLOR: 1 + # Ensure we run the UI tests. + RUN_UI_TESTS: 1 script: # this job runs all tests in former runtime-benchmarks, frame-staking and wasmtime tests - time cargo test --workspace --locked --release --verbose --features runtime-benchmarks --manifest-path ./bin/node/cli/Cargo.toml diff --git a/.maintain/update-rust-stable.sh b/.maintain/update-rust-stable.sh new file mode 100755 index 0000000000000..b253bb4105313 --- /dev/null +++ b/.maintain/update-rust-stable.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +# +# Script for updating the UI tests for a new rust stable version. +# +# It needs to be called like this: +# +# update-rust-stable.sh 1.61 +# +# This will run all UI tests with the rust stable 1.61. The script +# requires that rustup is installed. +set -e + +if [ "$#" -ne 1 ]; then + echo "Please specify the rust version to use. E.g. update-rust-stable.sh 1.61" + exit +fi + +RUST_VERSION=$1 + +if ! command -v rustup &> /dev/null +then + echo "rustup needs to be installed" + exit +fi + +rustup install $RUST_VERSION +rustup component add rust-src --toolchain $RUST_VERSION + +# Ensure we run the ui tests +export RUN_UI_TESTS=1 +# We don't need any wasm files for ui tests +export SKIP_WASM_BUILD=1 +# Let trybuild overwrite the .stderr files +export TRYBUILD=overwrite + +# Run all the relevant UI tests +# +# Any new UI tests in different crates need to be added here as well. +rustup run $RUST_VERSION cargo test -p sp-runtime-interface ui +rustup run $RUST_VERSION cargo test -p sp-api-test ui +rustup run $RUST_VERSION cargo test -p frame-election-provider-solution-type ui +rustup run $RUST_VERSION cargo test -p frame-support-test ui diff --git a/bin/node/cli/benches/transaction_pool.rs b/bin/node/cli/benches/transaction_pool.rs index e89527f2333a2..9a602e286bbae 100644 --- a/bin/node/cli/benches/transaction_pool.rs +++ b/bin/node/cli/benches/transaction_pool.rs @@ -126,7 +126,7 @@ fn create_account_extrinsics( accounts .iter() .enumerate() - .map(|(i, a)| { + .flat_map(|(i, a)| { vec![ // Reset the nonce by removing any funds create_extrinsic( @@ -162,7 +162,6 @@ fn create_account_extrinsics( ), ] }) - .flatten() .map(OpaqueExtrinsic::from) .collect() } @@ -174,7 +173,7 @@ fn create_benchmark_extrinsics( ) -> Vec { accounts .iter() - .map(|account| { + .flat_map(|account| { (0..extrinsics_per_account).map(move |nonce| { create_extrinsic( client, @@ -187,7 +186,6 @@ fn create_benchmark_extrinsics( ) }) }) - .flatten() .map(OpaqueExtrinsic::from) .collect() } diff --git a/client/network/src/service.rs b/client/network/src/service.rs index e89be325fa486..7239c9f6f9e6a 100644 --- a/client/network/src/service.rs +++ b/client/network/src/service.rs @@ -572,7 +572,7 @@ impl NetworkWorker { .collect(); let endpoint = if let Some(e) = - swarm.behaviour_mut().node(peer_id).map(|i| i.endpoint()).flatten() + swarm.behaviour_mut().node(peer_id).and_then(|i| i.endpoint()) { e.clone().into() } else { diff --git a/client/offchain/src/lib.rs b/client/offchain/src/lib.rs index 8d016e945453b..d54d491b04c43 100644 --- a/client/offchain/src/lib.rs +++ b/client/offchain/src/lib.rs @@ -74,11 +74,11 @@ where H: ExHashT, { fn set_authorized_peers(&self, peers: HashSet) { - self.set_authorized_peers(peers) + NetworkService::set_authorized_peers(self, peers) } fn set_authorized_only(&self, reserved_only: bool) { - self.set_authorized_only(reserved_only) + NetworkService::set_authorized_only(self, reserved_only) } } diff --git a/client/service/src/client/call_executor.rs b/client/service/src/client/call_executor.rs index f271b35a69ced..0f13b08d0ce8e 100644 --- a/client/service/src/client/call_executor.rs +++ b/client/service/src/client/call_executor.rs @@ -91,28 +91,26 @@ where B: backend::Backend, { let spec = CallExecutor::runtime_version(self, id)?; - let code = if let Some(d) = self - .wasm_override - .as_ref() - .as_ref() - .map(|o| o.get(&spec.spec_version, onchain_code.heap_pages, &spec.spec_name)) - .flatten() - { - log::debug!(target: "wasm_overrides", "using WASM override for block {}", id); - d - } else if let Some(s) = - self.wasm_substitutes.get(spec.spec_version, onchain_code.heap_pages, id) - { - log::debug!(target: "wasm_substitutes", "Using WASM substitute for block {:?}", id); - s - } else { - log::debug!( - target: "wasm_overrides", - "No WASM override available for block {}, using onchain code", - id - ); - onchain_code - }; + let code = + if let Some(d) = + self.wasm_override.as_ref().as_ref().and_then(|o| { + o.get(&spec.spec_version, onchain_code.heap_pages, &spec.spec_name) + }) { + log::debug!(target: "wasm_overrides", "using WASM override for block {}", id); + d + } else if let Some(s) = + self.wasm_substitutes.get(spec.spec_version, onchain_code.heap_pages, id) + { + log::debug!(target: "wasm_substitutes", "Using WASM substitute for block {:?}", id); + s + } else { + log::debug!( + target: "wasm_overrides", + "No WASM override available for block {}, using onchain code", + id + ); + onchain_code + }; Ok(code) } diff --git a/client/service/src/client/wasm_override.rs b/client/service/src/client/wasm_override.rs index 267aea0709871..fa35be9fcac87 100644 --- a/client/service/src/client/wasm_override.rs +++ b/client/service/src/client/wasm_override.rs @@ -186,7 +186,7 @@ impl WasmOverride { for entry in fs::read_dir(dir).map_err(handle_err)? { let entry = entry.map_err(handle_err)?; let path = entry.path(); - match path.extension().map(|e| e.to_str()).flatten() { + match path.extension().and_then(|e| e.to_str()) { Some("wasm") => { let code = fs::read(&path).map_err(handle_err)?; let code_hash = make_hash(&code); diff --git a/docs/CONTRIBUTING.adoc b/docs/CONTRIBUTING.adoc index 5b1920e775bbd..d39b8a6d729ff 100644 --- a/docs/CONTRIBUTING.adoc +++ b/docs/CONTRIBUTING.adoc @@ -111,6 +111,14 @@ Please label issues with the following labels: Declaring formal releases remains the prerogative of the project maintainer(s). +== UI tests + +UI tests are used for macros to ensure that the output of a macro doesn't change and is in the expected format. These UI tests are sensible to any changes +in the macro generated code or to switching the rust stable version. The tests are only run when the `RUN_UI_TESTS` environment variable is set. So, when +the CI is for example complaining about failing UI tests and it is expected that they fail these tests need to be executed locally. To simplify the updating +of the UI test ouput there is the `.maintain/update-rust-stable.sh` script. This can be run with `.maintain/update-rust-stable.sh CURRENT_STABLE_VERSION` +and then it will run all UI tests to update the expected output. + == Changes to this arrangement This is an experiment and feedback is welcome! This document may also be subject to pull-requests or changes by contributors where you believe you have something valuable to add or change. diff --git a/frame/election-provider-support/solution-type/src/lib.rs b/frame/election-provider-support/solution-type/src/lib.rs index 57d939377b62c..7c5cb960d2430 100644 --- a/frame/election-provider-support/solution-type/src/lib.rs +++ b/frame/election-provider-support/solution-type/src/lib.rs @@ -265,6 +265,11 @@ fn imports() -> Result { mod tests { #[test] fn ui_fail() { + // Only run the ui tests when `RUN_UI_TESTS` is set. + if std::env::var("RUN_UI_TESTS").is_err() { + return + } + let cases = trybuild::TestCases::new(); cases.compile_fail("tests/ui/fail/*.rs"); } diff --git a/frame/support/test/tests/construct_runtime_ui.rs b/frame/support/test/tests/construct_runtime_ui.rs index 66636416c1f51..38aa780766835 100644 --- a/frame/support/test/tests/construct_runtime_ui.rs +++ b/frame/support/test/tests/construct_runtime_ui.rs @@ -21,6 +21,11 @@ use std::env; #[cfg(not(feature = "disable-ui-tests"))] #[test] fn ui() { + // Only run the ui tests when `RUN_UI_TESTS` is set. + if env::var("RUN_UI_TESTS").is_err() { + return + } + // As trybuild is using `cargo check`, we don't need the real WASM binaries. env::set_var("SKIP_WASM_BUILD", "1"); diff --git a/frame/support/test/tests/construct_runtime_ui/no_std_genesis_config.stderr b/frame/support/test/tests/construct_runtime_ui/no_std_genesis_config.stderr index 6d5a48bf0909a..404c0c3627b7b 100644 --- a/frame/support/test/tests/construct_runtime_ui/no_std_genesis_config.stderr +++ b/frame/support/test/tests/construct_runtime_ui/no_std_genesis_config.stderr @@ -31,21 +31,16 @@ help: consider importing this struct | error[E0283]: type annotations needed - --> tests/construct_runtime_ui/no_std_genesis_config.rs:40:1 - | -40 | / construct_runtime! { -41 | | pub enum Runtime where -42 | | Block = Block, -43 | | NodeBlock = Block, -... | -48 | | } -49 | | } - | |_^ cannot infer type - | - = note: cannot satisfy `_: std::default::Default` -note: required by `std::default::Default::default` - --> $RUST/core/src/default.rs - | - | fn default() -> Self; - | ^^^^^^^^^^^^^^^^^^^^^ - = note: this error originates in the derive macro `Default` (in Nightly builds, run with -Z macro-backtrace for more info) + --> tests/construct_runtime_ui/no_std_genesis_config.rs:40:1 + | +40 | / construct_runtime! { +41 | | pub enum Runtime where +42 | | Block = Block, +43 | | NodeBlock = Block, +... | +48 | | } +49 | | } + | |_^ cannot infer type + | + = note: cannot satisfy `_: std::default::Default` + = note: this error originates in the derive macro `Default` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.stderr b/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.stderr index e8532aa9a064f..fa1cee1ac7e2f 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.stderr +++ b/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.stderr @@ -34,21 +34,16 @@ help: consider importing this struct | error[E0283]: type annotations needed - --> tests/construct_runtime_ui/undefined_genesis_config_part.rs:49:1 - | -49 | / construct_runtime! { -50 | | pub enum Runtime where -51 | | Block = Block, -52 | | NodeBlock = Block, -... | -57 | | } -58 | | } - | |_^ cannot infer type - | - = note: cannot satisfy `_: std::default::Default` -note: required by `std::default::Default::default` - --> $RUST/core/src/default.rs - | - | fn default() -> Self; - | ^^^^^^^^^^^^^^^^^^^^^ - = note: this error originates in the derive macro `Default` (in Nightly builds, run with -Z macro-backtrace for more info) + --> tests/construct_runtime_ui/undefined_genesis_config_part.rs:49:1 + | +49 | / construct_runtime! { +50 | | pub enum Runtime where +51 | | Block = Block, +52 | | NodeBlock = Block, +... | +57 | | } +58 | | } + | |_^ cannot infer type + | + = note: cannot satisfy `_: std::default::Default` + = note: this error originates in the derive macro `Default` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/frame/support/test/tests/decl_module_ui.rs b/frame/support/test/tests/decl_module_ui.rs index 829850ec2d476..292451335e7ea 100644 --- a/frame/support/test/tests/decl_module_ui.rs +++ b/frame/support/test/tests/decl_module_ui.rs @@ -19,6 +19,11 @@ #[cfg(not(feature = "disable-ui-tests"))] #[test] fn decl_module_ui() { + // Only run the ui tests when `RUN_UI_TESTS` is set. + if std::env::var("RUN_UI_TESTS").is_err() { + return + } + // As trybuild is using `cargo check`, we don't need the real WASM binaries. std::env::set_var("SKIP_WASM_BUILD", "1"); diff --git a/frame/support/test/tests/decl_storage_ui.rs b/frame/support/test/tests/decl_storage_ui.rs index d4db02ad19a0e..34dfea8601ab9 100644 --- a/frame/support/test/tests/decl_storage_ui.rs +++ b/frame/support/test/tests/decl_storage_ui.rs @@ -19,6 +19,11 @@ #[cfg(not(feature = "disable-ui-tests"))] #[test] fn decl_storage_ui() { + // Only run the ui tests when `RUN_UI_TESTS` is set. + if std::env::var("RUN_UI_TESTS").is_err() { + return + } + // As trybuild is using `cargo check`, we don't need the real WASM binaries. std::env::set_var("SKIP_WASM_BUILD", "1"); diff --git a/frame/support/test/tests/derive_no_bound_ui.rs b/frame/support/test/tests/derive_no_bound_ui.rs index 91e530d1d8da7..d714e1113625a 100644 --- a/frame/support/test/tests/derive_no_bound_ui.rs +++ b/frame/support/test/tests/derive_no_bound_ui.rs @@ -19,6 +19,11 @@ #[cfg(not(feature = "disable-ui-tests"))] #[test] fn derive_no_bound_ui() { + // Only run the ui tests when `RUN_UI_TESTS` is set. + if std::env::var("RUN_UI_TESTS").is_err() { + return + } + // As trybuild is using `cargo check`, we don't need the real WASM binaries. std::env::set_var("SKIP_WASM_BUILD", "1"); diff --git a/frame/support/test/tests/derive_no_bound_ui/clone.stderr b/frame/support/test/tests/derive_no_bound_ui/clone.stderr index 45428a8728c21..7744586e56bf4 100644 --- a/frame/support/test/tests/derive_no_bound_ui/clone.stderr +++ b/frame/support/test/tests/derive_no_bound_ui/clone.stderr @@ -1,11 +1,5 @@ error[E0277]: the trait bound `::C: Clone` is not satisfied - --> tests/derive_no_bound_ui/clone.rs:7:2 - | -7 | c: T::C, - | ^ the trait `Clone` is not implemented for `::C` - | -note: required by `clone` - --> $RUST/core/src/clone.rs - | - | fn clone(&self) -> Self; - | ^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/derive_no_bound_ui/clone.rs:7:2 + | +7 | c: T::C, + | ^ the trait `Clone` is not implemented for `::C` diff --git a/frame/support/test/tests/derive_no_bound_ui/default.stderr b/frame/support/test/tests/derive_no_bound_ui/default.stderr index 7608f877a3b56..d56dd438f2a7f 100644 --- a/frame/support/test/tests/derive_no_bound_ui/default.stderr +++ b/frame/support/test/tests/derive_no_bound_ui/default.stderr @@ -1,11 +1,5 @@ error[E0277]: the trait bound `::C: std::default::Default` is not satisfied - --> $DIR/default.rs:7:2 - | -7 | c: T::C, - | ^ the trait `std::default::Default` is not implemented for `::C` - | -note: required by `std::default::Default::default` - --> $DIR/default.rs:116:5 - | -116 | fn default() -> Self; - | ^^^^^^^^^^^^^^^^^^^^^ + --> tests/derive_no_bound_ui/default.rs:7:2 + | +7 | c: T::C, + | ^ the trait `std::default::Default` is not implemented for `::C` diff --git a/frame/support/test/tests/pallet_ui.rs b/frame/support/test/tests/pallet_ui.rs index a77d76deff8dc..2db1d3cb0543a 100644 --- a/frame/support/test/tests/pallet_ui.rs +++ b/frame/support/test/tests/pallet_ui.rs @@ -19,6 +19,11 @@ #[cfg(not(feature = "disable-ui-tests"))] #[test] fn pallet_ui() { + // Only run the ui tests when `RUN_UI_TESTS` is set. + if std::env::var("RUN_UI_TESTS").is_err() { + return + } + // As trybuild is using `cargo check`, we don't need the real WASM binaries. std::env::set_var("SKIP_WASM_BUILD", "1"); diff --git a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.stderr b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.stderr index 9701b1bdd06f3..3a636d9f659c7 100644 --- a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.stderr +++ b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.stderr @@ -9,16 +9,10 @@ error[E0277]: `::Bar` doesn't implement `std::fmt::Debug` = note: required for the cast to the object type `dyn std::fmt::Debug` error[E0277]: the trait bound `::Bar: Clone` is not satisfied - --> tests/pallet_ui/call_argument_invalid_bound.rs:20:36 - | -20 | pub fn foo(origin: OriginFor, bar: T::Bar) -> DispatchResultWithPostInfo { - | ^^^ the trait `Clone` is not implemented for `::Bar` - | -note: required by `clone` - --> $RUST/core/src/clone.rs - | - | fn clone(&self) -> Self; - | ^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/pallet_ui/call_argument_invalid_bound.rs:20:36 + | +20 | pub fn foo(origin: OriginFor, bar: T::Bar) -> DispatchResultWithPostInfo { + | ^^^ the trait `Clone` is not implemented for `::Bar` error[E0369]: binary operation `==` cannot be applied to type `&::Bar` --> tests/pallet_ui/call_argument_invalid_bound.rs:20:36 diff --git a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr index aff0661620874..f182382d18f11 100644 --- a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr +++ b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr @@ -9,16 +9,10 @@ error[E0277]: `::Bar` doesn't implement `std::fmt::Debug` = note: required for the cast to the object type `dyn std::fmt::Debug` error[E0277]: the trait bound `::Bar: Clone` is not satisfied - --> tests/pallet_ui/call_argument_invalid_bound_2.rs:20:36 - | -20 | pub fn foo(origin: OriginFor, bar: T::Bar) -> DispatchResultWithPostInfo { - | ^^^ the trait `Clone` is not implemented for `::Bar` - | -note: required by `clone` - --> $RUST/core/src/clone.rs - | - | fn clone(&self) -> Self; - | ^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/pallet_ui/call_argument_invalid_bound_2.rs:20:36 + | +20 | pub fn foo(origin: OriginFor, bar: T::Bar) -> DispatchResultWithPostInfo { + | ^^^ the trait `Clone` is not implemented for `::Bar` error[E0369]: binary operation `==` cannot be applied to type `&::Bar` --> tests/pallet_ui/call_argument_invalid_bound_2.rs:20:36 @@ -32,38 +26,26 @@ help: consider further restricting this bound | +++++++++++++++++++++ error[E0277]: the trait bound `::Bar: WrapperTypeEncode` is not satisfied - --> tests/pallet_ui/call_argument_invalid_bound_2.rs:1:1 - | -1 | #[frame_support::pallet] - | ^----------------------- - | | - | _in this procedural macro expansion - | | -2 | | mod pallet { -3 | | use frame_support::pallet_prelude::{Hooks, DispatchResultWithPostInfo}; -4 | | use frame_system::pallet_prelude::{BlockNumberFor, OriginFor}; -... | -16 | | -17 | | #[pallet::call] - | |__________________^ the trait `WrapperTypeEncode` is not implemented for `::Bar` - | - = note: required because of the requirements on the impl of `Encode` for `::Bar` -note: required by a bound in `encode_to` - --> $CARGO/parity-scale-codec-3.0.0/src/codec.rs - | - | fn encode_to(&self, dest: &mut T) { - | ^^^^^^ required by this bound in `encode_to` - = note: this error originates in the derive macro `frame_support::codec::Encode` (in Nightly builds, run with -Z macro-backtrace for more info) + --> tests/pallet_ui/call_argument_invalid_bound_2.rs:20:36 + | +1 | / #[frame_support::pallet] +2 | | mod pallet { +3 | | use frame_support::pallet_prelude::{Hooks, DispatchResultWithPostInfo}; +4 | | use frame_system::pallet_prelude::{BlockNumberFor, OriginFor}; +... | +16 | | +17 | | #[pallet::call] + | |__________________- required by a bound introduced by this call +... +20 | pub fn foo(origin: OriginFor, bar: T::Bar) -> DispatchResultWithPostInfo { + | ^^^ the trait `WrapperTypeEncode` is not implemented for `::Bar` + | + = note: required because of the requirements on the impl of `Encode` for `::Bar` error[E0277]: the trait bound `::Bar: WrapperTypeDecode` is not satisfied - --> tests/pallet_ui/call_argument_invalid_bound_2.rs:17:12 - | -17 | #[pallet::call] - | ^^^^ the trait `WrapperTypeDecode` is not implemented for `::Bar` - | - = note: required because of the requirements on the impl of `Decode` for `::Bar` -note: required by a bound in `parity_scale_codec::Decode::decode` - --> $CARGO/parity-scale-codec-3.0.0/src/codec.rs - | - | fn decode(input: &mut I) -> Result; - | ^^^^^ required by this bound in `parity_scale_codec::Decode::decode` + --> tests/pallet_ui/call_argument_invalid_bound_2.rs:17:12 + | +17 | #[pallet::call] + | ^^^^ the trait `WrapperTypeDecode` is not implemented for `::Bar` + | + = note: required because of the requirements on the impl of `Decode` for `::Bar` diff --git a/frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.stderr b/frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.stderr index 2a8149e309ac1..0f2ea7e161c4e 100644 --- a/frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.stderr +++ b/frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.stderr @@ -1,12 +1,7 @@ error[E0277]: the trait bound `MyError: PalletError` is not satisfied - --> tests/pallet_ui/error_does_not_derive_pallet_error.rs:1:1 - | -1 | #[frame_support::pallet] - | ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `PalletError` is not implemented for `MyError` - | -note: required by `MAX_ENCODED_SIZE` - --> $WORKSPACE/frame/support/src/traits/error.rs - | - | const MAX_ENCODED_SIZE: usize; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - = note: this error originates in the derive macro `frame_support::PalletError` (in Nightly builds, run with -Z macro-backtrace for more info) + --> tests/pallet_ui/error_does_not_derive_pallet_error.rs:1:1 + | +1 | #[frame_support::pallet] + | ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `PalletError` is not implemented for `MyError` + | + = note: this error originates in the derive macro `frame_support::PalletError` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/frame/support/test/tests/pallet_ui/event_field_not_member.stderr b/frame/support/test/tests/pallet_ui/event_field_not_member.stderr index ff184db988e3c..3db258a819fcb 100644 --- a/frame/support/test/tests/pallet_ui/event_field_not_member.stderr +++ b/frame/support/test/tests/pallet_ui/event_field_not_member.stderr @@ -1,14 +1,8 @@ error[E0277]: the trait bound `::Bar: Clone` is not satisfied - --> tests/pallet_ui/event_field_not_member.rs:23:7 - | -23 | B { b: T::Bar }, - | ^ the trait `Clone` is not implemented for `::Bar` - | -note: required by `clone` - --> $RUST/core/src/clone.rs - | - | fn clone(&self) -> Self; - | ^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/pallet_ui/event_field_not_member.rs:23:7 + | +23 | B { b: T::Bar }, + | ^ the trait `Clone` is not implemented for `::Bar` error[E0369]: binary operation `==` cannot be applied to type `&::Bar` --> tests/pallet_ui/event_field_not_member.rs:23:7 diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr index 35f8bbdbd5248..87528751a0a7a 100644 --- a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr @@ -7,11 +7,6 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied = note: required because of the requirements on the impl of `Decode` for `Bar` = note: required because of the requirements on the impl of `FullCodec` for `Bar` = note: required because of the requirements on the impl of `PartialStorageInfoTrait` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` -note: required by `partial_storage_info` - --> $WORKSPACE/frame/support/src/traits/storage.rs - | - | fn partial_storage_info() -> Vec; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs:10:12 @@ -22,11 +17,6 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied = note: required because of the requirements on the impl of `FullEncode` for `Bar` = note: required because of the requirements on the impl of `FullCodec` for `Bar` = note: required because of the requirements on the impl of `PartialStorageInfoTrait` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` -note: required by `partial_storage_info` - --> $WORKSPACE/frame/support/src/traits/storage.rs - | - | fn partial_storage_info() -> Vec; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs:10:12 @@ -38,68 +28,43 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied = note: required because of the requirements on the impl of `FullEncode` for `Bar` = note: required because of the requirements on the impl of `FullCodec` for `Bar` = note: required because of the requirements on the impl of `PartialStorageInfoTrait` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` -note: required by `partial_storage_info` - --> $WORKSPACE/frame/support/src/traits/storage.rs - | - | fn partial_storage_info() -> Vec; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied - --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs:21:12 - | -21 | #[pallet::storage] - | ^^^^^^^ the trait `TypeInfo` is not implemented for `Bar` - | - = note: required because of the requirements on the impl of `StaticTypeInfo` for `Bar` - = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` -note: required by `build_metadata` - --> $WORKSPACE/frame/support/src/storage/types/mod.rs - | - | fn build_metadata(doc: Vec<&'static str>, entries: &mut Vec); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs:21:12 + | +21 | #[pallet::storage] + | ^^^^^^^ the trait `TypeInfo` is not implemented for `Bar` + | + = note: required because of the requirements on the impl of `StaticTypeInfo` for `Bar` + = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied - --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs:21:12 - | -21 | #[pallet::storage] - | ^^^^^^^ the trait `WrapperTypeDecode` is not implemented for `Bar` - | - = note: required because of the requirements on the impl of `Decode` for `Bar` - = note: required because of the requirements on the impl of `FullCodec` for `Bar` - = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` -note: required by `build_metadata` - --> $WORKSPACE/frame/support/src/storage/types/mod.rs - | - | fn build_metadata(doc: Vec<&'static str>, entries: &mut Vec); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs:21:12 + | +21 | #[pallet::storage] + | ^^^^^^^ the trait `WrapperTypeDecode` is not implemented for `Bar` + | + = note: required because of the requirements on the impl of `Decode` for `Bar` + = note: required because of the requirements on the impl of `FullCodec` for `Bar` + = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied - --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs:21:12 - | -21 | #[pallet::storage] - | ^^^^^^^ the trait `EncodeLike` is not implemented for `Bar` - | - = note: required because of the requirements on the impl of `FullEncode` for `Bar` - = note: required because of the requirements on the impl of `FullCodec` for `Bar` - = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` -note: required by `build_metadata` - --> $WORKSPACE/frame/support/src/storage/types/mod.rs - | - | fn build_metadata(doc: Vec<&'static str>, entries: &mut Vec); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs:21:12 + | +21 | #[pallet::storage] + | ^^^^^^^ the trait `EncodeLike` is not implemented for `Bar` + | + = note: required because of the requirements on the impl of `FullEncode` for `Bar` + = note: required because of the requirements on the impl of `FullCodec` for `Bar` + = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied - --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs:21:12 - | -21 | #[pallet::storage] - | ^^^^^^^ the trait `WrapperTypeEncode` is not implemented for `Bar` - | - = note: required because of the requirements on the impl of `Encode` for `Bar` - = note: required because of the requirements on the impl of `FullEncode` for `Bar` - = note: required because of the requirements on the impl of `FullCodec` for `Bar` - = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` -note: required by `build_metadata` - --> $WORKSPACE/frame/support/src/storage/types/mod.rs - | - | fn build_metadata(doc: Vec<&'static str>, entries: &mut Vec); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.rs:21:12 + | +21 | #[pallet::storage] + | ^^^^^^^ the trait `WrapperTypeEncode` is not implemented for `Bar` + | + = note: required because of the requirements on the impl of `Encode` for `Bar` + = note: required because of the requirements on the impl of `FullEncode` for `Bar` + = note: required because of the requirements on the impl of `FullCodec` for `Bar` + = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr index b5f250bb89718..6c3f6dc662fbc 100644 --- a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr @@ -7,11 +7,6 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied = note: required because of the requirements on the impl of `Decode` for `Bar` = note: required because of the requirements on the impl of `FullCodec` for `Bar` = note: required because of the requirements on the impl of `PartialStorageInfoTrait` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` -note: required by `partial_storage_info` - --> $WORKSPACE/frame/support/src/traits/storage.rs - | - | fn partial_storage_info() -> Vec; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:10:12 @@ -22,11 +17,6 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied = note: required because of the requirements on the impl of `FullEncode` for `Bar` = note: required because of the requirements on the impl of `FullCodec` for `Bar` = note: required because of the requirements on the impl of `PartialStorageInfoTrait` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` -note: required by `partial_storage_info` - --> $WORKSPACE/frame/support/src/traits/storage.rs - | - | fn partial_storage_info() -> Vec; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:10:12 @@ -38,68 +28,43 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied = note: required because of the requirements on the impl of `FullEncode` for `Bar` = note: required because of the requirements on the impl of `FullCodec` for `Bar` = note: required because of the requirements on the impl of `PartialStorageInfoTrait` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` -note: required by `partial_storage_info` - --> $WORKSPACE/frame/support/src/traits/storage.rs - | - | fn partial_storage_info() -> Vec; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied - --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:21:12 - | -21 | #[pallet::storage] - | ^^^^^^^ the trait `TypeInfo` is not implemented for `Bar` - | - = note: required because of the requirements on the impl of `StaticTypeInfo` for `Bar` - = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` -note: required by `build_metadata` - --> $WORKSPACE/frame/support/src/storage/types/mod.rs - | - | fn build_metadata(doc: Vec<&'static str>, entries: &mut Vec); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:21:12 + | +21 | #[pallet::storage] + | ^^^^^^^ the trait `TypeInfo` is not implemented for `Bar` + | + = note: required because of the requirements on the impl of `StaticTypeInfo` for `Bar` + = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied - --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:21:12 - | -21 | #[pallet::storage] - | ^^^^^^^ the trait `WrapperTypeDecode` is not implemented for `Bar` - | - = note: required because of the requirements on the impl of `Decode` for `Bar` - = note: required because of the requirements on the impl of `FullCodec` for `Bar` - = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` -note: required by `build_metadata` - --> $WORKSPACE/frame/support/src/storage/types/mod.rs - | - | fn build_metadata(doc: Vec<&'static str>, entries: &mut Vec); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:21:12 + | +21 | #[pallet::storage] + | ^^^^^^^ the trait `WrapperTypeDecode` is not implemented for `Bar` + | + = note: required because of the requirements on the impl of `Decode` for `Bar` + = note: required because of the requirements on the impl of `FullCodec` for `Bar` + = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied - --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:21:12 - | -21 | #[pallet::storage] - | ^^^^^^^ the trait `EncodeLike` is not implemented for `Bar` - | - = note: required because of the requirements on the impl of `FullEncode` for `Bar` - = note: required because of the requirements on the impl of `FullCodec` for `Bar` - = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` -note: required by `build_metadata` - --> $WORKSPACE/frame/support/src/storage/types/mod.rs - | - | fn build_metadata(doc: Vec<&'static str>, entries: &mut Vec); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:21:12 + | +21 | #[pallet::storage] + | ^^^^^^^ the trait `EncodeLike` is not implemented for `Bar` + | + = note: required because of the requirements on the impl of `FullEncode` for `Bar` + = note: required because of the requirements on the impl of `FullCodec` for `Bar` + = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied - --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:21:12 - | -21 | #[pallet::storage] - | ^^^^^^^ the trait `WrapperTypeEncode` is not implemented for `Bar` - | - = note: required because of the requirements on the impl of `Encode` for `Bar` - = note: required because of the requirements on the impl of `FullEncode` for `Bar` - = note: required because of the requirements on the impl of `FullCodec` for `Bar` - = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` -note: required by `build_metadata` - --> $WORKSPACE/frame/support/src/storage/types/mod.rs - | - | fn build_metadata(doc: Vec<&'static str>, entries: &mut Vec); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.rs:21:12 + | +21 | #[pallet::storage] + | ^^^^^^^ the trait `WrapperTypeEncode` is not implemented for `Bar` + | + = note: required because of the requirements on the impl of `Encode` for `Bar` + = note: required because of the requirements on the impl of `FullEncode` for `Bar` + = note: required because of the requirements on the impl of `FullCodec` for `Bar` + = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` diff --git a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr index 35537cfbc9e07..68856f122c7ac 100644 --- a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr +++ b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr @@ -1,12 +1,7 @@ error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied - --> tests/pallet_ui/storage_info_unsatisfied.rs:9:12 - | -9 | #[pallet::pallet] - | ^^^^^^ the trait `MaxEncodedLen` is not implemented for `Bar` - | - = note: required because of the requirements on the impl of `StorageInfoTrait` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` -note: required by `storage_info` - --> $WORKSPACE/frame/support/src/traits/storage.rs - | - | fn storage_info() -> Vec; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + --> tests/pallet_ui/storage_info_unsatisfied.rs:9:12 + | +9 | #[pallet::pallet] + | ^^^^^^ the trait `MaxEncodedLen` is not implemented for `Bar` + | + = note: required because of the requirements on the impl of `StorageInfoTrait` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` diff --git a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr index fb6580bb5a3e7..226cb40f1d48b 100644 --- a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr +++ b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr @@ -6,8 +6,3 @@ error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied | = note: required because of the requirements on the impl of `KeyGeneratorMaxEncodedLen` for `Key` = note: required because of the requirements on the impl of `StorageInfoTrait` for `frame_support::pallet_prelude::StorageNMap<_GeneratedPrefixForStorageFoo, Key, u32>` -note: required by `storage_info` - --> $WORKSPACE/frame/support/src/traits/storage.rs - | - | fn storage_info() -> Vec; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/primitives/api/test/tests/trybuild.rs b/primitives/api/test/tests/trybuild.rs index 9e3af145dc566..f3d6aa59a0336 100644 --- a/primitives/api/test/tests/trybuild.rs +++ b/primitives/api/test/tests/trybuild.rs @@ -20,6 +20,11 @@ use std::env; #[rustversion::attr(not(stable), ignore)] #[test] fn ui() { + // Only run the ui tests when `RUN_UI_TESTS` is set. + if env::var("RUN_UI_TESTS").is_err() { + return + } + // As trybuild is using `cargo check`, we don't need the real WASM binaries. env::set_var("SKIP_WASM_BUILD", "1"); diff --git a/primitives/api/test/tests/ui/changed_in_no_default_method.stderr b/primitives/api/test/tests/ui/changed_in_no_default_method.stderr index ed4c0f9088573..096b1091e6f41 100644 --- a/primitives/api/test/tests/ui/changed_in_no_default_method.stderr +++ b/primitives/api/test/tests/ui/changed_in_no_default_method.stderr @@ -1,6 +1,6 @@ error: There is no 'default' method with this name (without `changed_in` attribute). -The 'default' method is used to call into the latest implementation. - --> $DIR/changed_in_no_default_method.rs:15:6 + The 'default' method is used to call into the latest implementation. + --> tests/ui/changed_in_no_default_method.rs:15:6 | 15 | fn test(data: u64); | ^^^^ diff --git a/primitives/api/test/tests/ui/impl_incorrect_method_signature.stderr b/primitives/api/test/tests/ui/impl_incorrect_method_signature.stderr index 2fb06c3565ea2..b1478e2f53344 100644 --- a/primitives/api/test/tests/ui/impl_incorrect_method_signature.stderr +++ b/primitives/api/test/tests/ui/impl_incorrect_method_signature.stderr @@ -1,28 +1,23 @@ error[E0053]: method `test` has an incompatible type for trait - --> $DIR/impl_incorrect_method_signature.rs:19:17 + --> tests/ui/impl_incorrect_method_signature.rs:19:17 | -13 | fn test(data: u64); - | --- type in trait -... 19 | fn test(data: String) {} | ^^^^^^ | | | expected `u64`, found struct `std::string::String` | help: change the parameter type to match the trait: `u64` | +note: type in trait + --> tests/ui/impl_incorrect_method_signature.rs:13:17 + | +13 | fn test(data: u64); + | ^^^ = note: expected fn pointer `fn(u64)` found fn pointer `fn(std::string::String)` error[E0053]: method `Api_test_runtime_api_impl` has an incompatible type for trait - --> $DIR/impl_incorrect_method_signature.rs:17:1 + --> tests/ui/impl_incorrect_method_signature.rs:17:1 | -11 | / sp_api::decl_runtime_apis! { -12 | | pub trait Api { -13 | | fn test(data: u64); -14 | | } -15 | | } - | |_- type in trait -16 | 17 | sp_api::impl_runtime_apis! { | -^^^^^^^^^^^^^^^^^^^^^^^^^ | | @@ -36,12 +31,21 @@ error[E0053]: method `Api_test_runtime_api_impl` has an incompatible type for tr 33 | | } | |_- help: change the parameter type to match the trait: `std::option::Option` | +note: type in trait + --> tests/ui/impl_incorrect_method_signature.rs:11:1 + | +11 | / sp_api::decl_runtime_apis! { +12 | | pub trait Api { +13 | | fn test(data: u64); +14 | | } +15 | | } + | |_^ = note: expected fn pointer `fn(&RuntimeApiImpl<__SR_API_BLOCK__, RuntimeApiImplCall>, &BlockId<__SR_API_BLOCK__>, ExecutionContext, std::option::Option, Vec<_>) -> Result<_, _>` found fn pointer `fn(&RuntimeApiImpl<__SR_API_BLOCK__, RuntimeApiImplCall>, &BlockId<__SR_API_BLOCK__>, ExecutionContext, std::option::Option, Vec<_>) -> Result<_, _>` = note: this error originates in the macro `sp_api::impl_runtime_apis` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0308]: mismatched types - --> $DIR/impl_incorrect_method_signature.rs:17:1 + --> tests/ui/impl_incorrect_method_signature.rs:17:1 | 17 | / sp_api::impl_runtime_apis! { 18 | | impl self::Api for Runtime { @@ -55,7 +59,7 @@ error[E0308]: mismatched types = note: this error originates in the macro `sp_api::impl_runtime_apis` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0308]: mismatched types - --> $DIR/impl_incorrect_method_signature.rs:19:11 + --> tests/ui/impl_incorrect_method_signature.rs:19:11 | 19 | fn test(data: String) {} | ^^^^ expected `u64`, found struct `std::string::String` diff --git a/primitives/api/test/tests/ui/mock_only_self_reference.stderr b/primitives/api/test/tests/ui/mock_only_self_reference.stderr index 1b1d2553940a5..c67de70b9c140 100644 --- a/primitives/api/test/tests/ui/mock_only_self_reference.stderr +++ b/primitives/api/test/tests/ui/mock_only_self_reference.stderr @@ -1,26 +1,18 @@ error: Only `&self` is supported! - --> $DIR/mock_only_self_reference.rs:14:11 + --> tests/ui/mock_only_self_reference.rs:14:11 | 14 | fn test(self, data: u64) {} | ^^^^ error: Only `&self` is supported! - --> $DIR/mock_only_self_reference.rs:16:12 + --> tests/ui/mock_only_self_reference.rs:16:12 | 16 | fn test2(&mut self, data: u64) {} | ^ error[E0053]: method `Api_test_runtime_api_impl` has an incompatible type for trait - --> $DIR/mock_only_self_reference.rs:12:1 + --> tests/ui/mock_only_self_reference.rs:12:1 | -3 | / sp_api::decl_runtime_apis! { -4 | | pub trait Api { -5 | | fn test(data: u64); -6 | | fn test2(data: u64); -7 | | } -8 | | } - | |_- type in trait -... 12 | sp_api::mock_impl_runtime_apis! { | -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | | @@ -34,12 +26,8 @@ error[E0053]: method `Api_test_runtime_api_impl` has an incompatible type for tr 18 | | } | |_- help: change the parameter type to match the trait: `Option` | - = note: expected fn pointer `fn(&MockApi, &BlockId, Extrinsic>>, ExecutionContext, Option, Vec<_>) -> Result<_, _>` - found fn pointer `fn(&MockApi, &BlockId, Extrinsic>>, ExecutionContext, Option<()>, Vec<_>) -> Result<_, _>` - = note: this error originates in the macro `sp_api::mock_impl_runtime_apis` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0053]: method `Api_test2_runtime_api_impl` has an incompatible type for trait - --> $DIR/mock_only_self_reference.rs:12:1 +note: type in trait + --> tests/ui/mock_only_self_reference.rs:3:1 | 3 | / sp_api::decl_runtime_apis! { 4 | | pub trait Api { @@ -47,8 +35,14 @@ error[E0053]: method `Api_test2_runtime_api_impl` has an incompatible type for t 6 | | fn test2(data: u64); 7 | | } 8 | | } - | |_- type in trait -... + | |_^ + = note: expected fn pointer `fn(&MockApi, &BlockId, Extrinsic>>, ExecutionContext, Option, Vec<_>) -> Result<_, _>` + found fn pointer `fn(&MockApi, &BlockId, Extrinsic>>, ExecutionContext, Option<()>, Vec<_>) -> Result<_, _>` + = note: this error originates in the macro `sp_api::mock_impl_runtime_apis` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0053]: method `Api_test2_runtime_api_impl` has an incompatible type for trait + --> tests/ui/mock_only_self_reference.rs:12:1 + | 12 | sp_api::mock_impl_runtime_apis! { | -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | | @@ -62,6 +56,16 @@ error[E0053]: method `Api_test2_runtime_api_impl` has an incompatible type for t 18 | | } | |_- help: change the parameter type to match the trait: `Option` | +note: type in trait + --> tests/ui/mock_only_self_reference.rs:3:1 + | +3 | / sp_api::decl_runtime_apis! { +4 | | pub trait Api { +5 | | fn test(data: u64); +6 | | fn test2(data: u64); +7 | | } +8 | | } + | |_^ = note: expected fn pointer `fn(&MockApi, &BlockId, Extrinsic>>, ExecutionContext, Option, Vec<_>) -> Result<_, _>` found fn pointer `fn(&MockApi, &BlockId, Extrinsic>>, ExecutionContext, Option<()>, Vec<_>) -> Result<_, _>` = note: this error originates in the macro `sp_api::mock_impl_runtime_apis` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.stderr b/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.stderr index d11aebbf149bb..dbc0f6def3aa5 100644 --- a/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.stderr +++ b/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.stderr @@ -1,28 +1,23 @@ error[E0053]: method `test` has an incompatible type for trait - --> $DIR/type_reference_in_impl_runtime_apis_call.rs:19:17 + --> tests/ui/type_reference_in_impl_runtime_apis_call.rs:19:17 | -13 | fn test(data: u64); - | --- type in trait -... 19 | fn test(data: &u64) { | ^^^^ | | | expected `u64`, found `&u64` | help: change the parameter type to match the trait: `u64` | +note: type in trait + --> tests/ui/type_reference_in_impl_runtime_apis_call.rs:13:17 + | +13 | fn test(data: u64); + | ^^^ = note: expected fn pointer `fn(u64)` found fn pointer `fn(&u64)` error[E0053]: method `Api_test_runtime_api_impl` has an incompatible type for trait - --> $DIR/type_reference_in_impl_runtime_apis_call.rs:17:1 + --> tests/ui/type_reference_in_impl_runtime_apis_call.rs:17:1 | -11 | / sp_api::decl_runtime_apis! { -12 | | pub trait Api { -13 | | fn test(data: u64); -14 | | } -15 | | } - | |_- type in trait -16 | 17 | sp_api::impl_runtime_apis! { | -^^^^^^^^^^^^^^^^^^^^^^^^^ | | @@ -36,12 +31,21 @@ error[E0053]: method `Api_test_runtime_api_impl` has an incompatible type for tr 35 | | } | |_- help: change the parameter type to match the trait: `std::option::Option` | +note: type in trait + --> tests/ui/type_reference_in_impl_runtime_apis_call.rs:11:1 + | +11 | / sp_api::decl_runtime_apis! { +12 | | pub trait Api { +13 | | fn test(data: u64); +14 | | } +15 | | } + | |_^ = note: expected fn pointer `fn(&RuntimeApiImpl<__SR_API_BLOCK__, RuntimeApiImplCall>, &BlockId<__SR_API_BLOCK__>, ExecutionContext, std::option::Option, Vec<_>) -> Result<_, _>` found fn pointer `fn(&RuntimeApiImpl<__SR_API_BLOCK__, RuntimeApiImplCall>, &BlockId<__SR_API_BLOCK__>, ExecutionContext, std::option::Option<&u64>, Vec<_>) -> Result<_, _>` = note: this error originates in the macro `sp_api::impl_runtime_apis` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0308]: mismatched types - --> $DIR/type_reference_in_impl_runtime_apis_call.rs:17:1 + --> tests/ui/type_reference_in_impl_runtime_apis_call.rs:17:1 | 17 | / sp_api::impl_runtime_apis! { 18 | | impl self::Api for Runtime { @@ -55,7 +59,7 @@ error[E0308]: mismatched types = note: this error originates in the macro `sp_api::impl_runtime_apis` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0308]: mismatched types - --> $DIR/type_reference_in_impl_runtime_apis_call.rs:19:11 + --> tests/ui/type_reference_in_impl_runtime_apis_call.rs:19:11 | 19 | fn test(data: &u64) { | ^^^^^^^ expected `u64`, found `&u64` diff --git a/primitives/runtime-interface/tests/ui.rs b/primitives/runtime-interface/tests/ui.rs index 9e3af145dc566..f3d6aa59a0336 100644 --- a/primitives/runtime-interface/tests/ui.rs +++ b/primitives/runtime-interface/tests/ui.rs @@ -20,6 +20,11 @@ use std::env; #[rustversion::attr(not(stable), ignore)] #[test] fn ui() { + // Only run the ui tests when `RUN_UI_TESTS` is set. + if env::var("RUN_UI_TESTS").is_err() { + return + } + // As trybuild is using `cargo check`, we don't need the real WASM binaries. env::set_var("SKIP_WASM_BUILD", "1"); diff --git a/primitives/state-machine/src/in_memory_backend.rs b/primitives/state-machine/src/in_memory_backend.rs index 4605d07b2ab63..2d8173452abfe 100644 --- a/primitives/state-machine/src/in_memory_backend.rs +++ b/primitives/state-machine/src/in_memory_backend.rs @@ -59,7 +59,7 @@ where ) { let (top, child) = changes.into_iter().partition::, _>(|v| v.0.is_none()); let (root, transaction) = self.full_storage_root( - top.iter().map(|(_, v)| v).flatten().map(|(k, v)| (&k[..], v.as_deref())), + top.iter().flat_map(|(_, v)| v).map(|(k, v)| (&k[..], v.as_deref())), child.iter().filter_map(|v| { v.0.as_ref().map(|c| (c, v.1.iter().map(|(k, v)| (&k[..], v.as_deref())))) }), diff --git a/primitives/state-machine/src/trie_backend_essence.rs b/primitives/state-machine/src/trie_backend_essence.rs index 8531e4907d6a7..418e6f3d2dce1 100644 --- a/primitives/state-machine/src/trie_backend_essence.rs +++ b/primitives/state-machine/src/trie_backend_essence.rs @@ -580,16 +580,15 @@ impl<'a, S: 'a + TrieBackendStorage, H: Hasher> hash_db::HashDB for Ephemeral<'a, S, H> { fn get(&self, key: &H::Out, prefix: Prefix) -> Option { - if let Some(val) = HashDB::get(self.overlay, key, prefix) { - Some(val) - } else { - match self.storage.get(&key, prefix) { + match HashDB::get(self.overlay, key, prefix) { + Some(val) => Some(val), + None => match self.storage.get(&key, prefix) { Ok(x) => x, Err(e) => { warn!(target: "trie", "Failed to read from DB: {}", e); None }, - } + }, } } diff --git a/primitives/version/src/embed.rs b/primitives/version/src/embed.rs index e6b468e2e58cc..c71849238fe33 100644 --- a/primitives/version/src/embed.rs +++ b/primitives/version/src/embed.rs @@ -44,8 +44,7 @@ pub fn embed_runtime_version( .apis .iter() .map(Encode::encode) - .map(|v| v.into_iter()) - .flatten() + .flat_map(|v| v.into_iter()) .collect::>(); module.set_custom_section("runtime_apis", apis); diff --git a/utils/frame/remote-externalities/src/lib.rs b/utils/frame/remote-externalities/src/lib.rs index 533d65c49c2e1..018ad2f4e0ad8 100644 --- a/utils/frame/remote-externalities/src/lib.rs +++ b/utils/frame/remote-externalities/src/lib.rs @@ -848,7 +848,7 @@ impl Builder { info!( target: LOG_TARGET, "injecting a total of {} child keys", - child_kv.iter().map(|(_, kv)| kv).flatten().count() + child_kv.iter().flat_map(|(_, kv)| kv).count(), ); for (info, key_values) in child_kv { From 186d67cd6ac364ec40270f68f2786408176f6c95 Mon Sep 17 00:00:00 2001 From: Koute Date: Mon, 11 Apr 2022 18:46:53 +0900 Subject: [PATCH 111/484] Add new hardware and software metrics (#11062) * Add new hardware and software metrics * Move sysinfo tests into `mod tests` * Correct a typo in a comment * Remove unnecessary `nix` dependency * Fix the version tests * Add a `--disable-hardware-benchmarks` CLI argument * Disable hardware benchmarks in the integration tests * Remove unused import * Fix benchmarks compilation * Move code to a new `sc-sysinfo` crate * Correct `impl_version` comment * Move `--disable-hardware-benchmarks` to the chain-specific bin crate * Move printing out of hardware bench results to `sc-sysinfo` * Move hardware benchmarks to a separate messages; trigger them manually * Rename some of the fields in the `HwBench` struct * Revert changes to the telemetry crate; manually send hwbench messages * Move sysinfo logs into the sysinfo crate * Move the `TARGET_OS_*` constants into the sysinfo crate * Minor cleanups * Move the `HwBench` struct to the sysinfo crate * Derive `Clone` for `HwBench` * Fix broken telemetry connection notification stream * Prevent the telemetry connection notifiers from leaking if they're disconnected * Turn the telemetry notification failure log into a debug log * Rename `--disable-hardware-benchmarks` to `--no-hardware-benchmarks` --- Cargo.lock | 30 +- Cargo.toml | 1 + bin/node/cli/Cargo.toml | 1 + bin/node/cli/benches/block_production.rs | 3 +- bin/node/cli/benches/transaction_pool.rs | 2 +- bin/node/cli/src/chain_spec.rs | 2 +- bin/node/cli/src/cli.rs | 10 + bin/node/cli/src/command.rs | 3 +- bin/node/cli/src/service.rs | 34 +- bin/node/cli/tests/check_block_works.rs | 2 +- bin/node/cli/tests/export_import_flow.rs | 2 +- bin/node/cli/tests/inspect_works.rs | 2 +- bin/node/cli/tests/purge_chain_works.rs | 2 +- .../tests/running_the_node_and_interrupt.rs | 3 +- bin/node/cli/tests/telemetry.rs | 1 + bin/node/cli/tests/temp_base_path_works.rs | 2 +- bin/node/cli/tests/version.rs | 24 +- client/cli/src/lib.rs | 2 +- client/service/Cargo.toml | 1 + client/service/src/builder.rs | 12 +- client/sysinfo/Cargo.toml | 26 ++ client/sysinfo/README.md | 4 + client/sysinfo/build.rs | 31 ++ client/sysinfo/src/lib.rs | 111 +++++ client/sysinfo/src/sysinfo.rs | 393 ++++++++++++++++++ client/sysinfo/src/sysinfo_linux.rs | 101 +++++ client/telemetry/src/lib.rs | 32 ++ client/telemetry/src/node.rs | 16 +- utils/build-script-utils/src/version.rs | 18 +- 29 files changed, 808 insertions(+), 63 deletions(-) create mode 100644 client/sysinfo/Cargo.toml create mode 100644 client/sysinfo/README.md create mode 100644 client/sysinfo/build.rs create mode 100644 client/sysinfo/src/lib.rs create mode 100644 client/sysinfo/src/sysinfo.rs create mode 100644 client/sysinfo/src/sysinfo_linux.rs diff --git a/Cargo.lock b/Cargo.lock index c64800e002339..609ccb0baeef9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2842,12 +2842,6 @@ dependencies = [ "crunchy", ] -[[package]] -name = "hashbrown" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" - [[package]] name = "hashbrown" version = "0.11.2" @@ -3179,12 +3173,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.6.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3" +checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" dependencies = [ "autocfg 1.0.1", - "hashbrown 0.9.1", + "hashbrown 0.11.2", "serde", ] @@ -4863,6 +4857,7 @@ dependencies = [ "sc-service", "sc-service-test", "sc-sync-state-rpc", + "sc-sysinfo", "sc-telemetry", "sc-transaction-pool", "sc-transaction-pool-api", @@ -9002,6 +8997,7 @@ dependencies = [ "sc-offchain", "sc-rpc", "sc-rpc-server", + "sc-sysinfo", "sc-telemetry", "sc-tracing", "sc-transaction-pool", @@ -9106,6 +9102,22 @@ dependencies = [ "thiserror", ] +[[package]] +name = "sc-sysinfo" +version = "6.0.0-dev" +dependencies = [ + "futures 0.3.19", + "libc", + "log 0.4.14", + "rand 0.7.3", + "rand_pcg 0.2.1", + "regex", + "sc-telemetry", + "serde", + "serde_json", + "sp-core", +] + [[package]] name = "sc-telemetry" version = "4.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index 13657dd1234a5..576bae6b574b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,6 +54,7 @@ members = [ "client/service", "client/service/test", "client/state-db", + "client/sysinfo", "client/sync-state-rpc", "client/telemetry", "client/tracing", diff --git a/bin/node/cli/Cargo.toml b/bin/node/cli/Cargo.toml index 4eb5983b6488e..dda6b57d0244b 100644 --- a/bin/node/cli/Cargo.toml +++ b/bin/node/cli/Cargo.toml @@ -76,6 +76,7 @@ sc-telemetry = { version = "4.0.0-dev", path = "../../../client/telemetry" } sc-executor = { version = "0.10.0-dev", path = "../../../client/executor" } sc-authority-discovery = { version = "0.10.0-dev", path = "../../../client/authority-discovery" } sc-sync-state-rpc = { version = "0.10.0-dev", path = "../../../client/sync-state-rpc" } +sc-sysinfo = { version = "6.0.0-dev", path = "../../../client/sysinfo" } # frame dependencies frame-system = { version = "4.0.0-dev", path = "../../../frame/system" } diff --git a/bin/node/cli/benches/block_production.rs b/bin/node/cli/benches/block_production.rs index 77b51fa28dd1a..ebb89c07da221 100644 --- a/bin/node/cli/benches/block_production.rs +++ b/bin/node/cli/benches/block_production.rs @@ -110,7 +110,8 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase { wasm_runtime_overrides: None, }; - node_cli::service::new_full_base(config, |_, _| ()).expect("creating a full node doesn't fail") + node_cli::service::new_full_base(config, false, |_, _| ()) + .expect("creating a full node doesn't fail") } fn extrinsic_set_time(now: u64) -> OpaqueExtrinsic { diff --git a/bin/node/cli/benches/transaction_pool.rs b/bin/node/cli/benches/transaction_pool.rs index 9a602e286bbae..a889399eda83a 100644 --- a/bin/node/cli/benches/transaction_pool.rs +++ b/bin/node/cli/benches/transaction_pool.rs @@ -102,7 +102,7 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase { wasm_runtime_overrides: None, }; - node_cli::service::new_full_base(config, |_, _| ()).expect("Creates node") + node_cli::service::new_full_base(config, false, |_, _| ()).expect("Creates node") } fn create_accounts(num: usize) -> Vec { diff --git a/bin/node/cli/src/chain_spec.rs b/bin/node/cli/src/chain_spec.rs index 11516f964903a..1212d3b3d07ed 100644 --- a/bin/node/cli/src/chain_spec.rs +++ b/bin/node/cli/src/chain_spec.rs @@ -471,7 +471,7 @@ pub(crate) mod tests { sc_service_test::connectivity(integration_test_config_with_two_authorities(), |config| { let NewFullBase { task_manager, client, network, transaction_pool, .. } = - new_full_base(config, |_, _| ())?; + new_full_base(config, false, |_, _| ())?; Ok(sc_service_test::TestNetComponents::new( task_manager, client, diff --git a/bin/node/cli/src/cli.rs b/bin/node/cli/src/cli.rs index 7430cc46f4cc7..5b5db62199bbd 100644 --- a/bin/node/cli/src/cli.rs +++ b/bin/node/cli/src/cli.rs @@ -26,6 +26,16 @@ pub struct Cli { #[allow(missing_docs)] #[clap(flatten)] pub run: sc_cli::RunCmd, + + /// Disable automatic hardware benchmarks. + /// + /// By default these benchmarks are automatically ran at startup and measure + /// the CPU speed, the memory bandwidth and the disk speed. + /// + /// The results are then printed out in the logs, and also sent as part of + /// telemetry, if telemetry is enabled. + #[clap(long)] + pub no_hardware_benchmarks: bool, } /// Possible subcommands of the main binary. diff --git a/bin/node/cli/src/command.rs b/bin/node/cli/src/command.rs index ea91bb90b2f2c..880b7d7b7ecb7 100644 --- a/bin/node/cli/src/command.rs +++ b/bin/node/cli/src/command.rs @@ -86,7 +86,8 @@ pub fn run() -> Result<()> { None => { let runner = cli.create_runner(&cli.run)?; runner.run_node_until_exit(|config| async move { - service::new_full(config).map_err(sc_cli::Error::Service) + service::new_full(config, cli.no_hardware_benchmarks) + .map_err(sc_cli::Error::Service) }) }, Some(Subcommand::Inspect(cmd)) => { diff --git a/bin/node/cli/src/service.rs b/bin/node/cli/src/service.rs index 03e7311fb3c01..2742e7b113d6d 100644 --- a/bin/node/cli/src/service.rs +++ b/bin/node/cli/src/service.rs @@ -309,11 +309,21 @@ pub struct NewFullBase { /// Creates a full service from the configuration. pub fn new_full_base( mut config: Configuration, + disable_hardware_benchmarks: bool, with_startup_data: impl FnOnce( &sc_consensus_babe::BabeBlockImport, &sc_consensus_babe::BabeLink, ), ) -> Result { + let hwbench = if !disable_hardware_benchmarks { + config.database.path().map(|database_path| { + let _ = std::fs::create_dir_all(&database_path); + sc_sysinfo::gather_hwbench(Some(database_path)) + }) + } else { + None + }; + let sc_service::PartialComponents { client, backend, @@ -383,6 +393,19 @@ pub fn new_full_base( telemetry: telemetry.as_mut(), })?; + if let Some(hwbench) = hwbench { + sc_sysinfo::print_hwbench(&hwbench); + + if let Some(ref mut telemetry) = telemetry { + let telemetry_handle = telemetry.handle(); + task_manager.spawn_handle().spawn( + "telemetry_hwbench", + None, + sc_sysinfo::initialize_hwbench_telemetry(telemetry_handle, hwbench), + ); + } + } + let (block_import, grandpa_link, babe_link) = import_setup; (with_startup_data)(&block_import, &babe_link); @@ -530,8 +553,12 @@ pub fn new_full_base( } /// Builds a new service for a full client. -pub fn new_full(config: Configuration) -> Result { - new_full_base(config, |_, _| ()).map(|NewFullBase { task_manager, .. }| task_manager) +pub fn new_full( + config: Configuration, + disable_hardware_benchmarks: bool, +) -> Result { + new_full_base(config, disable_hardware_benchmarks, |_, _| ()) + .map(|NewFullBase { task_manager, .. }| task_manager) } #[cfg(test)] @@ -598,6 +625,7 @@ mod tests { let NewFullBase { task_manager, client, network, transaction_pool, .. } = new_full_base( config, + false, |block_import: &sc_consensus_babe::BabeBlockImport, babe_link: &sc_consensus_babe::BabeLink| { setup_handles = Some((block_import.clone(), babe_link.clone())); @@ -775,7 +803,7 @@ mod tests { crate::chain_spec::tests::integration_test_config_with_two_authorities(), |config| { let NewFullBase { task_manager, client, network, transaction_pool, .. } = - new_full_base(config, |_, _| ())?; + new_full_base(config, false, |_, _| ())?; Ok(sc_service_test::TestNetComponents::new( task_manager, client, diff --git a/bin/node/cli/tests/check_block_works.rs b/bin/node/cli/tests/check_block_works.rs index ac853b201b8c6..c5447fd2311c6 100644 --- a/bin/node/cli/tests/check_block_works.rs +++ b/bin/node/cli/tests/check_block_works.rs @@ -28,7 +28,7 @@ pub mod common; async fn check_block_works() { let base_path = tempdir().expect("could not create a temp dir"); - common::run_node_for_a_while(base_path.path(), &["--dev"]).await; + common::run_node_for_a_while(base_path.path(), &["--dev", "--no-hardware-benchmarks"]).await; let status = Command::new(cargo_bin("substrate")) .args(&["check-block", "--dev", "--pruning", "archive", "-d"]) diff --git a/bin/node/cli/tests/export_import_flow.rs b/bin/node/cli/tests/export_import_flow.rs index 2a2133bbfe4fe..48fccc8ca0293 100644 --- a/bin/node/cli/tests/export_import_flow.rs +++ b/bin/node/cli/tests/export_import_flow.rs @@ -188,7 +188,7 @@ async fn export_import_revert() { let exported_blocks_file = base_path.path().join("exported_blocks"); let db_path = base_path.path().join("db"); - common::run_node_for_a_while(base_path.path(), &["--dev"]).await; + common::run_node_for_a_while(base_path.path(), &["--dev", "--no-hardware-benchmarks"]).await; let mut executor = ExportImportRevertExecutor::new(&base_path, &exported_blocks_file, &db_path); diff --git a/bin/node/cli/tests/inspect_works.rs b/bin/node/cli/tests/inspect_works.rs index 28ad88dd501d1..6f73cc69582a9 100644 --- a/bin/node/cli/tests/inspect_works.rs +++ b/bin/node/cli/tests/inspect_works.rs @@ -28,7 +28,7 @@ pub mod common; async fn inspect_works() { let base_path = tempdir().expect("could not create a temp dir"); - common::run_node_for_a_while(base_path.path(), &["--dev"]).await; + common::run_node_for_a_while(base_path.path(), &["--dev", "--no-hardware-benchmarks"]).await; let status = Command::new(cargo_bin("substrate")) .args(&["inspect", "--dev", "--pruning", "archive", "-d"]) diff --git a/bin/node/cli/tests/purge_chain_works.rs b/bin/node/cli/tests/purge_chain_works.rs index 1a62aec287447..811762a714a9d 100644 --- a/bin/node/cli/tests/purge_chain_works.rs +++ b/bin/node/cli/tests/purge_chain_works.rs @@ -27,7 +27,7 @@ pub mod common; async fn purge_chain_works() { let base_path = tempdir().expect("could not create a temp dir"); - common::run_node_for_a_while(base_path.path(), &["--dev"]).await; + common::run_node_for_a_while(base_path.path(), &["--dev", "--no-hardware-benchmarks"]).await; let status = Command::new(cargo_bin("substrate")) .args(&["purge-chain", "--dev", "-d"]) diff --git a/bin/node/cli/tests/running_the_node_and_interrupt.rs b/bin/node/cli/tests/running_the_node_and_interrupt.rs index 703123faf0e62..ecdb4d7671b01 100644 --- a/bin/node/cli/tests/running_the_node_and_interrupt.rs +++ b/bin/node/cli/tests/running_the_node_and_interrupt.rs @@ -38,6 +38,7 @@ async fn running_the_node_works_and_can_be_interrupted() { Command::new(cargo_bin("substrate")) .args(&["--dev", "-d"]) .arg(base_path.path()) + .arg("--no-hardware-benchmarks") .spawn() .unwrap(), ); @@ -61,7 +62,7 @@ async fn running_the_node_works_and_can_be_interrupted() { async fn running_two_nodes_with_the_same_ws_port_should_work() { fn start_node() -> Child { Command::new(cargo_bin("substrate")) - .args(&["--dev", "--tmp", "--ws-port=45789"]) + .args(&["--dev", "--tmp", "--ws-port=45789", "--no-hardware-benchmarks"]) .spawn() .unwrap() } diff --git a/bin/node/cli/tests/telemetry.rs b/bin/node/cli/tests/telemetry.rs index 64da4bd4b68f1..bef4e4ea03048 100644 --- a/bin/node/cli/tests/telemetry.rs +++ b/bin/node/cli/tests/telemetry.rs @@ -71,6 +71,7 @@ async fn telemetry_works() { let mut substrate = substrate .args(&["--dev", "--tmp", "--telemetry-url"]) .arg(format!("ws://{} 10", addr)) + .arg("--no-hardware-benchmarks") .stdout(process::Stdio::piped()) .stderr(process::Stdio::piped()) .stdin(process::Stdio::null()) diff --git a/bin/node/cli/tests/temp_base_path_works.rs b/bin/node/cli/tests/temp_base_path_works.rs index 306c490c2f760..df293161e3234 100644 --- a/bin/node/cli/tests/temp_base_path_works.rs +++ b/bin/node/cli/tests/temp_base_path_works.rs @@ -36,7 +36,7 @@ pub mod common; async fn temp_base_path_works() { let mut cmd = Command::new(cargo_bin("substrate")); let mut child = common::KillChildOnDrop( - cmd.args(&["--dev", "--tmp"]) + cmd.args(&["--dev", "--tmp", "--no-hardware-benchmarks"]) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() diff --git a/bin/node/cli/tests/version.rs b/bin/node/cli/tests/version.rs index 133eb65f4acef..f5a6f3a53d598 100644 --- a/bin/node/cli/tests/version.rs +++ b/bin/node/cli/tests/version.rs @@ -17,13 +17,11 @@ // along with this program. If not, see . use assert_cmd::cargo::cargo_bin; -use platforms::*; use regex::Regex; use std::process::Command; fn expected_regex() -> Regex { - Regex::new(r"^substrate (\d+\.\d+\.\d+(?:-.+?)?)-([a-f\d]+|unknown)-(.+?)-(.+?)(?:-(.+))?$") - .unwrap() + Regex::new(r"^substrate (.+)-([a-f\d]+)$").unwrap() } #[test] @@ -37,33 +35,17 @@ fn version_is_full() { let captures = expected.captures(output.as_str()).expect("could not parse version in output"); assert_eq!(&captures[1], env!("CARGO_PKG_VERSION")); - assert_eq!(&captures[3], TARGET_ARCH.as_str()); - assert_eq!(&captures[4], TARGET_OS.as_str()); - assert_eq!(captures.get(5).map(|x| x.as_str()), TARGET_ENV.map(|x| x.as_str())); } #[test] fn test_regex_matches_properly() { let expected = expected_regex(); - let captures = expected.captures("substrate 2.0.0-da487d19d-x86_64-linux-gnu").unwrap(); + let captures = expected.captures("substrate 2.0.0-da487d19d").unwrap(); assert_eq!(&captures[1], "2.0.0"); assert_eq!(&captures[2], "da487d19d"); - assert_eq!(&captures[3], "x86_64"); - assert_eq!(&captures[4], "linux"); - assert_eq!(captures.get(5).map(|x| x.as_str()), Some("gnu")); - let captures = expected.captures("substrate 2.0.0-alpha.5-da487d19d-x86_64-linux-gnu").unwrap(); + let captures = expected.captures("substrate 2.0.0-alpha.5-da487d19d").unwrap(); assert_eq!(&captures[1], "2.0.0-alpha.5"); assert_eq!(&captures[2], "da487d19d"); - assert_eq!(&captures[3], "x86_64"); - assert_eq!(&captures[4], "linux"); - assert_eq!(captures.get(5).map(|x| x.as_str()), Some("gnu")); - - let captures = expected.captures("substrate 2.0.0-alpha.5-da487d19d-x86_64-linux").unwrap(); - assert_eq!(&captures[1], "2.0.0-alpha.5"); - assert_eq!(&captures[2], "da487d19d"); - assert_eq!(&captures[3], "x86_64"); - assert_eq!(&captures[4], "linux"); - assert_eq!(captures.get(5).map(|x| x.as_str()), None); } diff --git a/client/cli/src/lib.rs b/client/cli/src/lib.rs index c920c6cd52cff..849a2d2cceb1f 100644 --- a/client/cli/src/lib.rs +++ b/client/cli/src/lib.rs @@ -56,7 +56,7 @@ pub trait SubstrateCli: Sized { /// /// By default this will look like this: /// - /// `2.0.0-b950f731c-x86_64-linux-gnu` + /// `2.0.0-b950f731c` /// /// Where the hash is the short commit hash of the commit of in the Git repository. fn impl_version() -> String; diff --git a/client/service/Cargo.toml b/client/service/Cargo.toml index 47474217ebd2a..c40559bdb5059 100644 --- a/client/service/Cargo.toml +++ b/client/service/Cargo.toml @@ -72,6 +72,7 @@ sc-offchain = { version = "4.0.0-dev", path = "../offchain" } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus", version = "0.10.0-dev" } sc-tracing = { version = "4.0.0-dev", path = "../tracing" } sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } +sc-sysinfo = { version = "6.0.0-dev", path = "../sysinfo" } tracing = "0.1.29" tracing-futures = { version = "0.2.4" } parity-util-mem = { version = "0.11.0", default-features = false, features = [ diff --git a/client/service/src/builder.rs b/client/service/src/builder.rs index f4ff932435755..882d7666dbe63 100644 --- a/client/service/src/builder.rs +++ b/client/service/src/builder.rs @@ -487,8 +487,13 @@ where ) .map_err(|e| Error::Application(Box::new(e)))?; + let sysinfo = sc_sysinfo::gather_sysinfo(); + sc_sysinfo::print_sysinfo(&sysinfo); + let telemetry = telemetry - .map(|telemetry| init_telemetry(&mut config, network.clone(), client.clone(), telemetry)) + .map(|telemetry| { + init_telemetry(&mut config, network.clone(), client.clone(), telemetry, Some(sysinfo)) + }) .transpose()?; info!("📦 Highest known block at #{}", chain_info.best_number); @@ -609,12 +614,16 @@ fn init_telemetry>( network: Arc::Hash>>, client: Arc, telemetry: &mut Telemetry, + sysinfo: Option, ) -> sc_telemetry::Result { let genesis_hash = client.block_hash(Zero::zero()).ok().flatten().unwrap_or_default(); let connection_message = ConnectionMessage { name: config.network.node_name.to_owned(), implementation: config.impl_name.to_owned(), version: config.impl_version.to_owned(), + target_os: sc_sysinfo::TARGET_OS.into(), + target_arch: sc_sysinfo::TARGET_ARCH.into(), + target_env: sc_sysinfo::TARGET_ENV.into(), config: String::new(), chain: config.chain_spec.name().to_owned(), genesis_hash: format!("{:?}", genesis_hash), @@ -625,6 +634,7 @@ fn init_telemetry>( .unwrap_or(0) .to_string(), network_id: network.local_peer_id().to_base58(), + sysinfo, }; telemetry.start_telemetry(connection_message)?; diff --git a/client/sysinfo/Cargo.toml b/client/sysinfo/Cargo.toml new file mode 100644 index 0000000000000..8efe583fb9335 --- /dev/null +++ b/client/sysinfo/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "sc-sysinfo" +version = "6.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "A crate that provides basic hardware and software telemetry information." +documentation = "https://docs.rs/sc-sysinfo" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +futures = "0.3.19" +log = "0.4.11" +rand = "0.7.3" +rand_pcg = "0.2.1" +regex = "1" +libc = "0.2" +serde = { version = "1.0.136", features = ["derive"] } +serde_json = "1.0.79" +sp-core = { version = "6.0.0", path = "../../primitives/core" } +sc-telemetry = { version = "4.0.0-dev", path = "../telemetry" } diff --git a/client/sysinfo/README.md b/client/sysinfo/README.md new file mode 100644 index 0000000000000..4a2189c5ed8db --- /dev/null +++ b/client/sysinfo/README.md @@ -0,0 +1,4 @@ +This crate contains the code necessary to gather basic hardware +and software telemetry information about the node on which we're running. + +License: GPL-3.0-or-later WITH Classpath-exception-2.0 diff --git a/client/sysinfo/build.rs b/client/sysinfo/build.rs new file mode 100644 index 0000000000000..6d288107445aa --- /dev/null +++ b/client/sysinfo/build.rs @@ -0,0 +1,31 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +fn main() { + let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR is always set in build scripts; qed"); + let out_dir = std::path::PathBuf::from(out_dir); + let target_os = std::env::var("CARGO_CFG_TARGET_OS") + .expect("CARGO_CFG_TARGET_OS is always set in build scripts; qed"); + let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH") + .expect("CARGO_CFG_TARGET_ARCH is always set in build scripts; qed"); + let target_env = std::env::var("CARGO_CFG_TARGET_ENV") + .expect("CARGO_CFG_TARGET_ENV is always set in build scripts; qed"); + std::fs::write(out_dir.join("target_os.txt"), target_os).unwrap(); + std::fs::write(out_dir.join("target_arch.txt"), target_arch).unwrap(); + std::fs::write(out_dir.join("target_env.txt"), target_env).unwrap(); +} diff --git a/client/sysinfo/src/lib.rs b/client/sysinfo/src/lib.rs new file mode 100644 index 0000000000000..be13efb82c66f --- /dev/null +++ b/client/sysinfo/src/lib.rs @@ -0,0 +1,111 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! This crate contains the code necessary to gather basic hardware +//! and software telemetry information about the node on which we're running. + +use futures::prelude::*; + +mod sysinfo; +#[cfg(target_os = "linux")] +mod sysinfo_linux; + +pub use sysinfo::{gather_hwbench, gather_sysinfo}; + +/// The operating system part of the current target triplet. +pub const TARGET_OS: &str = include_str!(concat!(env!("OUT_DIR"), "/target_os.txt")); + +/// The CPU ISA architecture part of the current target triplet. +pub const TARGET_ARCH: &str = include_str!(concat!(env!("OUT_DIR"), "/target_arch.txt")); + +/// The environment part of the current target triplet. +pub const TARGET_ENV: &str = include_str!(concat!(env!("OUT_DIR"), "/target_env.txt")); + +/// Hardware benchmark results for the node. +#[derive(Clone, Debug, serde::Serialize)] +pub struct HwBench { + /// The CPU speed, as measured in how many MB/s it can hash using the BLAKE2b-256 hash. + pub cpu_hashrate_score: u64, + /// Memory bandwidth in MB/s, calculated by measuring the throughput of `memcpy`. + pub memory_memcpy_score: u64, + /// Sequential disk write speed in MB/s. + pub disk_sequential_write_score: Option, + /// Random disk write speed in MB/s. + pub disk_random_write_score: Option, +} + +/// Prints out the system software/hardware information in the logs. +pub fn print_sysinfo(sysinfo: &sc_telemetry::SysInfo) { + log::info!("💻 Operating system: {}", TARGET_OS); + log::info!("💻 CPU architecture: {}", TARGET_ARCH); + if !TARGET_ENV.is_empty() { + log::info!("💻 Target environment: {}", TARGET_ENV); + } + + if let Some(ref cpu) = sysinfo.cpu { + log::info!("💻 CPU: {}", cpu); + } + if let Some(core_count) = sysinfo.core_count { + log::info!("💻 CPU cores: {}", core_count); + } + if let Some(memory) = sysinfo.memory { + log::info!("💻 Memory: {}MB", memory / (1024 * 1024)); + } + if let Some(ref linux_kernel) = sysinfo.linux_kernel { + log::info!("💻 Kernel: {}", linux_kernel); + } + if let Some(ref linux_distro) = sysinfo.linux_distro { + log::info!("💻 Linux distribution: {}", linux_distro); + } + if let Some(is_virtual_machine) = sysinfo.is_virtual_machine { + log::info!("💻 Virtual machine: {}", if is_virtual_machine { "yes" } else { "no" }); + } +} + +/// Prints out the results of the hardware benchmarks in the logs. +pub fn print_hwbench(hwbench: &HwBench) { + log::info!("🏁 CPU score: {}MB/s", hwbench.cpu_hashrate_score); + log::info!("🏁 Memory score: {}MB/s", hwbench.memory_memcpy_score); + + if let Some(score) = hwbench.disk_sequential_write_score { + log::info!("🏁 Disk score (seq. writes): {}MB/s", score); + } + if let Some(score) = hwbench.disk_random_write_score { + log::info!("🏁 Disk score (rand. writes): {}MB/s", score); + } +} + +/// Initializes the hardware benchmarks telemetry. +pub fn initialize_hwbench_telemetry( + telemetry_handle: sc_telemetry::TelemetryHandle, + hwbench: HwBench, +) -> impl std::future::Future { + let mut connect_stream = telemetry_handle.on_connect_stream(); + async move { + let payload = serde_json::to_value(&hwbench) + .expect("the `HwBench` can always be serialized into a JSON object; qed"); + let mut payload = match payload { + serde_json::Value::Object(map) => map, + _ => unreachable!("the `HwBench` always serializes into a JSON object; qed"), + }; + payload.insert("msg".into(), "sysinfo.hwbench".into()); + while connect_stream.next().await.is_some() { + telemetry_handle.send_telemetry(sc_telemetry::SUBSTRATE_INFO, payload.clone()); + } + } +} diff --git a/client/sysinfo/src/sysinfo.rs b/client/sysinfo/src/sysinfo.rs new file mode 100644 index 0000000000000..ceb28447002cf --- /dev/null +++ b/client/sysinfo/src/sysinfo.rs @@ -0,0 +1,393 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use crate::HwBench; +use rand::{seq::SliceRandom, Rng}; +use sc_telemetry::SysInfo; +use std::{ + fs::File, + io::{Seek, SeekFrom, Write}, + ops::{Deref, DerefMut}, + path::{Path, PathBuf}, + time::{Duration, Instant}, +}; + +#[inline(always)] +pub(crate) fn benchmark( + name: &str, + size: usize, + max_iterations: usize, + max_duration: Duration, + mut run: impl FnMut() -> Result<(), E>, +) -> Result { + // Run the benchmark once as a warmup to get the code into the L1 cache. + run()?; + + // Then run it multiple times and average the result. + let timestamp = Instant::now(); + let mut elapsed = Duration::default(); + let mut count = 0; + for _ in 0..max_iterations { + run()?; + + count += 1; + elapsed = timestamp.elapsed(); + + if elapsed >= max_duration { + break + } + } + + let score = (((size * count) as f64 / elapsed.as_secs_f64()) / (1024.0 * 1024.0)) as u64; + log::trace!( + "Calculated {} of {}MB/s in {} iterations in {}ms", + name, + score, + count, + elapsed.as_millis() + ); + Ok(score) +} + +/// Gathers information about node's hardware and software. +pub fn gather_sysinfo() -> SysInfo { + #[allow(unused_mut)] + let mut sysinfo = SysInfo { + cpu: None, + memory: None, + core_count: None, + linux_kernel: None, + linux_distro: None, + is_virtual_machine: None, + }; + + #[cfg(target_os = "linux")] + crate::sysinfo_linux::gather_linux_sysinfo(&mut sysinfo); + + sysinfo +} + +#[inline(never)] +fn clobber(slice: &mut [u8]) { + assert!(!slice.is_empty()); + + // Discourage the compiler from optimizing out our benchmarks. + // + // Volatile reads and writes are guaranteed to not be elided nor reordered, + // so we can use them to effectively clobber a piece of memory and prevent + // the compiler from optimizing out our technically unnecessary code. + // + // This is not totally bulletproof in theory, but should work in practice. + // + // SAFETY: We've checked that the slice is not empty, so reading and writing + // its first element is always safe. + unsafe { + let value = std::ptr::read_volatile(slice.as_ptr()); + std::ptr::write_volatile(slice.as_mut_ptr(), value); + } +} + +// This benchmarks the CPU speed as measured by calculating BLAKE2b-256 hashes, in MB/s. +fn benchmark_cpu() -> u64 { + // In general the results of this benchmark are somewhat sensitive to how much + // data we hash at the time. The smaller this is the *less* MB/s we can hash, + // the bigger this is the *more* MB/s we can hash, up until a certain point + // where we can achieve roughly ~100% of what the hasher can do. If we'd plot + // this on a graph with the number of bytes we want to hash on the X axis + // and the speed in MB/s on the Y axis then we'd essentially see it grow + // logarithmically. + // + // In practice however we might not always have enough data to hit the maximum + // possible speed that the hasher can achieve, so the size set here should be + // picked in such a way as to still measure how fast the hasher is at hashing, + // but without hitting its theoretical maximum speed. + const SIZE: usize = 32 * 1024; + const MAX_ITERATIONS: usize = 4 * 1024; + const MAX_DURATION: Duration = Duration::from_millis(100); + + let mut buffer = Vec::new(); + buffer.resize(SIZE, 0x66); + let mut hash = Default::default(); + + let run = || -> Result<(), ()> { + clobber(&mut buffer); + hash = sp_core::hashing::blake2_256(&buffer); + clobber(&mut hash); + + Ok(()) + }; + + benchmark("CPU score", SIZE, MAX_ITERATIONS, MAX_DURATION, run) + .expect("benchmark cannot fail; qed") +} + +// This benchmarks the effective `memcpy` memory bandwidth available in MB/s. +// +// It doesn't technically measure the absolute maximum memory bandwidth available, +// but that's fine, because real code most of the time isn't optimized to take +// advantage of the full memory bandwidth either. +fn benchmark_memory() -> u64 { + // Ideally this should be at least as big as the CPU's L3 cache, + // and it should be big enough so that the `memcpy` takes enough + // time to be actually measurable. + // + // As long as it's big enough increasing it further won't change + // the benchmark's results. + const SIZE: usize = 64 * 1024 * 1024; + const MAX_ITERATIONS: usize = 32; + const MAX_DURATION: Duration = Duration::from_millis(100); + + let mut src = Vec::new(); + let mut dst = Vec::new(); + + // Prefault the pages; we want to measure the memory bandwidth, + // not how fast the kernel can supply us with fresh memory pages. + src.resize(SIZE, 0x66); + dst.resize(SIZE, 0x77); + + let run = || -> Result<(), ()> { + clobber(&mut src); + clobber(&mut dst); + + // SAFETY: Both vectors are of the same type and of the same size, + // so copying data between them is safe. + unsafe { + // We use `memcpy` directly here since `copy_from_slice` isn't actually + // guaranteed to be turned into a `memcpy`. + libc::memcpy(dst.as_mut_ptr().cast(), src.as_ptr().cast(), SIZE); + } + + clobber(&mut dst); + clobber(&mut src); + + Ok(()) + }; + + benchmark("memory score", SIZE, MAX_ITERATIONS, MAX_DURATION, run) + .expect("benchmark cannot fail; qed") +} + +struct TemporaryFile { + fp: Option, + path: PathBuf, +} + +impl Drop for TemporaryFile { + fn drop(&mut self) { + let _ = self.fp.take(); + + // Remove the file. + // + // This has to be done *after* the benchmark, + // otherwise it changes the results as the data + // doesn't actually get properly flushed to the disk, + // since the file's not there anymore. + if let Err(error) = std::fs::remove_file(&self.path) { + log::warn!("Failed to remove the file used for the disk benchmark: {}", error); + } + } +} + +impl Deref for TemporaryFile { + type Target = File; + fn deref(&self) -> &Self::Target { + self.fp.as_ref().expect("`fp` is None only during `drop`") + } +} + +impl DerefMut for TemporaryFile { + fn deref_mut(&mut self) -> &mut Self::Target { + self.fp.as_mut().expect("`fp` is None only during `drop`") + } +} + +fn rng() -> rand_pcg::Pcg64 { + rand_pcg::Pcg64::new(0xcafef00dd15ea5e5, 0xa02bdbf7bb3c0a7ac28fa16a64abf96) +} + +fn random_data(size: usize) -> Vec { + let mut buffer = Vec::new(); + buffer.resize(size, 0); + rng().fill(&mut buffer[..]); + buffer +} + +pub fn benchmark_disk_sequential_writes(directory: &Path) -> Result { + const SIZE: usize = 64 * 1024 * 1024; + const MAX_ITERATIONS: usize = 32; + const MAX_DURATION: Duration = Duration::from_millis(300); + + let buffer = random_data(SIZE); + let path = directory.join(".disk_bench_seq_wr.tmp"); + + let fp = + File::create(&path).map_err(|error| format!("failed to create a test file: {}", error))?; + + let mut fp = TemporaryFile { fp: Some(fp), path }; + + fp.sync_all() + .map_err(|error| format!("failed to fsync the test file: {}", error))?; + + let run = || { + // Just dump everything to the disk in one go. + fp.write_all(&buffer) + .map_err(|error| format!("failed to write to the test file: {}", error))?; + + // And then make sure it was actually written to disk. + fp.sync_all() + .map_err(|error| format!("failed to fsync the test file: {}", error))?; + + // Rewind to the beginning for the next iteration of the benchmark. + fp.seek(SeekFrom::Start(0)) + .map_err(|error| format!("failed to seek to the start of the test file: {}", error))?; + + Ok(()) + }; + + benchmark("disk sequential write score", SIZE, MAX_ITERATIONS, MAX_DURATION, run) +} + +pub fn benchmark_disk_random_writes(directory: &Path) -> Result { + const SIZE: usize = 64 * 1024 * 1024; + const MAX_ITERATIONS: usize = 32; + const MAX_DURATION: Duration = Duration::from_millis(300); + + let buffer = random_data(SIZE); + let path = directory.join(".disk_bench_rand_wr.tmp"); + + let fp = + File::create(&path).map_err(|error| format!("failed to create a test file: {}", error))?; + + let mut fp = TemporaryFile { fp: Some(fp), path }; + + // Since we want to test random writes we need an existing file + // through which we can seek, so here we just populate it with some data. + fp.write_all(&buffer) + .map_err(|error| format!("failed to write to the test file: {}", error))?; + + fp.sync_all() + .map_err(|error| format!("failed to fsync the test file: {}", error))?; + + // Generate a list of random positions at which we'll issue writes. + let mut positions = Vec::with_capacity(SIZE / 4096); + { + let mut position = 0; + while position < SIZE { + positions.push(position); + position += 4096; + } + } + + positions.shuffle(&mut rng()); + + let run = || { + for &position in &positions { + fp.seek(SeekFrom::Start(position as u64)) + .map_err(|error| format!("failed to seek in the test file: {}", error))?; + + // Here we deliberately only write half of the chunk since we don't + // want the OS' disk scheduler to coalesce our writes into one single + // sequential write. + // + // Also the chunk's size is deliberately exactly half of a modern disk's + // sector size to trigger an RMW cycle. + let chunk = &buffer[position..position + 2048]; + fp.write_all(&chunk) + .map_err(|error| format!("failed to write to the test file: {}", error))?; + } + + fp.sync_all() + .map_err(|error| format!("failed to fsync the test file: {}", error))?; + + Ok(()) + }; + + // We only wrote half of the bytes hence `SIZE / 2`. + benchmark("disk random write score", SIZE / 2, MAX_ITERATIONS, MAX_DURATION, run) +} + +/// Benchmarks the hardware and returns the results of those benchmarks. +/// +/// Optionally accepts a path to a `scratch_directory` to use to benchmark the disk. +pub fn gather_hwbench(scratch_directory: Option<&Path>) -> HwBench { + #[allow(unused_mut)] + let mut hwbench = HwBench { + cpu_hashrate_score: benchmark_cpu(), + memory_memcpy_score: benchmark_memory(), + disk_sequential_write_score: None, + disk_random_write_score: None, + }; + + if let Some(scratch_directory) = scratch_directory { + hwbench.disk_sequential_write_score = + match benchmark_disk_sequential_writes(scratch_directory) { + Ok(score) => Some(score), + Err(error) => { + log::warn!("Failed to run the sequential write disk benchmark: {}", error); + None + }, + }; + + hwbench.disk_random_write_score = match benchmark_disk_random_writes(scratch_directory) { + Ok(score) => Some(score), + Err(error) => { + log::warn!("Failed to run the random write disk benchmark: {}", error); + None + }, + }; + } + + hwbench +} + +#[cfg(test)] +mod tests { + use super::*; + + #[cfg(target_os = "linux")] + #[test] + fn test_gather_sysinfo_linux() { + let sysinfo = gather_sysinfo(); + assert!(sysinfo.cpu.unwrap().len() > 0); + assert!(sysinfo.core_count.unwrap() > 0); + assert!(sysinfo.memory.unwrap() > 0); + assert_ne!(sysinfo.is_virtual_machine, None); + assert_ne!(sysinfo.linux_kernel, None); + assert_ne!(sysinfo.linux_distro, None); + } + + #[test] + fn test_benchmark_cpu() { + assert_ne!(benchmark_cpu(), 0); + } + + #[test] + fn test_benchmark_memory() { + assert_ne!(benchmark_memory(), 0); + } + + #[test] + fn test_benchmark_disk_sequential_writes() { + assert!(benchmark_disk_sequential_writes("./".as_ref()).unwrap() > 0); + } + + #[test] + fn test_benchmark_disk_random_writes() { + assert!(benchmark_disk_random_writes("./".as_ref()).unwrap() > 0); + } +} diff --git a/client/sysinfo/src/sysinfo_linux.rs b/client/sysinfo/src/sysinfo_linux.rs new file mode 100644 index 0000000000000..41ab6014cbef0 --- /dev/null +++ b/client/sysinfo/src/sysinfo_linux.rs @@ -0,0 +1,101 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use regex::Regex; +use sc_telemetry::SysInfo; +use std::collections::HashSet; + +fn read_file(path: &str) -> Option { + match std::fs::read_to_string(path) { + Ok(data) => Some(data), + Err(error) => { + log::warn!("Failed to read '{}': {}", path, error); + None + }, + } +} + +fn extract(data: &str, regex: &str) -> Option +where + T: std::str::FromStr, +{ + Regex::new(regex) + .expect("regex is correct; qed") + .captures(&data)? + .get(1)? + .as_str() + .parse() + .ok() +} + +const LINUX_REGEX_CPU: &str = r#"(?m)^model name\s*:\s*([^\n]+)"#; +const LINUX_REGEX_PHYSICAL_ID: &str = r#"(?m)^physical id\s*:\s*(\d+)"#; +const LINUX_REGEX_CORE_ID: &str = r#"(?m)^core id\s*:\s*(\d+)"#; +const LINUX_REGEX_HYPERVISOR: &str = r#"(?m)^flags\s*:.+?\bhypervisor\b"#; +const LINUX_REGEX_MEMORY: &str = r#"(?m)^MemTotal:\s*(\d+) kB"#; +const LINUX_REGEX_DISTRO: &str = r#"(?m)^PRETTY_NAME\s*=\s*"?(.+?)"?$"#; + +pub fn gather_linux_sysinfo(sysinfo: &mut SysInfo) { + if let Some(data) = read_file("/proc/cpuinfo") { + sysinfo.cpu = extract(&data, LINUX_REGEX_CPU); + sysinfo.is_virtual_machine = + Some(Regex::new(LINUX_REGEX_HYPERVISOR).unwrap().is_match(&data)); + + // The /proc/cpuinfo returns a list of all of the hardware threads. + // + // Here we extract all of the unique {CPU ID, core ID} pairs to get + // the total number of cores. + let mut set: HashSet<(u32, u32)> = HashSet::new(); + for chunk in data.split("\n\n") { + let pid = extract(chunk, LINUX_REGEX_PHYSICAL_ID); + let cid = extract(chunk, LINUX_REGEX_CORE_ID); + if let (Some(pid), Some(cid)) = (pid, cid) { + set.insert((pid, cid)); + } + } + + if !set.is_empty() { + sysinfo.core_count = Some(set.len() as u32); + } + } + + if let Some(data) = read_file("/proc/meminfo") { + sysinfo.memory = extract(&data, LINUX_REGEX_MEMORY).map(|memory: u64| memory * 1024); + } + + if let Some(data) = read_file("/etc/os-release") { + sysinfo.linux_distro = extract(&data, LINUX_REGEX_DISTRO); + } + + // NOTE: We don't use the `nix` crate to call this since it doesn't + // currently check for errors. + unsafe { + // SAFETY: The `utsname` is full of byte arrays, so this is safe. + let mut uname: libc::utsname = std::mem::zeroed(); + if libc::uname(&mut uname) < 0 { + log::warn!("uname failed: {}", std::io::Error::last_os_error()); + } else { + let length = + uname.release.iter().position(|&byte| byte == 0).unwrap_or(uname.release.len()); + let release = std::slice::from_raw_parts(uname.release.as_ptr().cast(), length); + if let Ok(release) = std::str::from_utf8(release) { + sysinfo.linux_kernel = Some(release.into()); + } + } + } +} diff --git a/client/telemetry/src/lib.rs b/client/telemetry/src/lib.rs index 68128e0207085..d570701a3d9ec 100644 --- a/client/telemetry/src/lib.rs +++ b/client/telemetry/src/lib.rs @@ -101,6 +101,38 @@ pub struct ConnectionMessage { pub startup_time: String, /// Node's network ID. pub network_id: String, + + /// Node's OS. + pub target_os: String, + + /// Node's ISA. + pub target_arch: String, + + /// Node's target platform ABI or libc. + pub target_env: String, + + /// Node's software and hardware information. + pub sysinfo: Option, +} + +/// Hardware and software information for the node. +/// +/// Gathering most of this information is highly OS-specific, +/// so most of the fields here are optional. +#[derive(Debug, Serialize)] +pub struct SysInfo { + /// The exact CPU model. + pub cpu: Option, + /// The total amount of memory, in bytes. + pub memory: Option, + /// The number of physical CPU cores. + pub core_count: Option, + /// The Linux kernel version. + pub linux_kernel: Option, + /// The exact Linux distribution used. + pub linux_distro: Option, + /// Whether the node's running under a virtual machine. + pub is_virtual_machine: Option, } /// Telemetry worker. diff --git a/client/telemetry/src/node.rs b/client/telemetry/src/node.rs index ec857ede70fdb..f3d746abb2b18 100644 --- a/client/telemetry/src/node.rs +++ b/client/telemetry/src/node.rs @@ -179,8 +179,20 @@ where Poll::Ready(Ok(sink)) => { log::debug!(target: "telemetry", "✅ Connected to {}", self.addr); - for sender in self.telemetry_connection_notifier.iter_mut() { - let _ = sender.send(()); + { + let mut index = 0; + while index < self.telemetry_connection_notifier.len() { + let sender = &mut self.telemetry_connection_notifier[index]; + if let Err(error) = sender.try_send(()) { + if !error.is_disconnected() { + log::debug!(target: "telemetry", "Failed to send a telemetry connection notification: {}", error); + } else { + self.telemetry_connection_notifier.swap_remove(index); + continue + } + } + index += 1; + } } let buf = self diff --git a/utils/build-script-utils/src/version.rs b/utils/build-script-utils/src/version.rs index 773949e30d8d4..9432f23e9b8ee 100644 --- a/utils/build-script-utils/src/version.rs +++ b/utils/build-script-utils/src/version.rs @@ -15,7 +15,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use platforms::*; use std::{borrow::Cow, process::Command}; /// Generate the `cargo:` key output @@ -42,26 +41,13 @@ pub fn generate_cargo_keys() { println!("cargo:rustc-env=SUBSTRATE_CLI_IMPL_VERSION={}", get_version(&commit)) } -fn get_platform() -> String { - let env_dash = if TARGET_ENV.is_some() { "-" } else { "" }; - - format!( - "{}-{}{}{}", - TARGET_ARCH.as_str(), - TARGET_OS.as_str(), - env_dash, - TARGET_ENV.map(|x| x.as_str()).unwrap_or(""), - ) -} - fn get_version(impl_commit: &str) -> String { let commit_dash = if impl_commit.is_empty() { "" } else { "-" }; format!( - "{}{}{}-{}", + "{}{}{}", std::env::var("CARGO_PKG_VERSION").unwrap_or_default(), commit_dash, - impl_commit, - get_platform(), + impl_commit ) } From fb4a4b249ccb121e0c5ab50eb5045a963ef5cbbe Mon Sep 17 00:00:00 2001 From: Xiliang Chen Date: Tue, 12 Apr 2022 17:26:34 +1200 Subject: [PATCH 112/484] add has_identity (#11197) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add has_identity * Update frame/identity/src/lib.rs Co-authored-by: Bastian Köcher * update * update Co-authored-by: Bastian Köcher --- Cargo.lock | 2 +- frame/identity/src/lib.rs | 6 ++++++ frame/identity/src/tests.rs | 17 +++++++++++++++++ frame/identity/src/types.rs | 37 +++++++++++++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 609ccb0baeef9..29e4c2c1ad320 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9106,7 +9106,7 @@ dependencies = [ name = "sc-sysinfo" version = "6.0.0-dev" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "libc", "log 0.4.14", "rand 0.7.3", diff --git a/frame/identity/src/lib.rs b/frame/identity/src/lib.rs index 51e63541a89b1..904b71654b416 100644 --- a/frame/identity/src/lib.rs +++ b/frame/identity/src/lib.rs @@ -978,4 +978,10 @@ impl Pallet { .filter_map(|a| SuperOf::::get(&a).map(|x| (a, x.1))) .collect() } + + /// Check if the account has corresponding identity information by the identity field. + pub fn has_identity(who: &T::AccountId, fields: u64) -> bool { + IdentityOf::::get(who) + .map_or(false, |registration| (registration.info.fields().0.bits() & fields) == fields) + } } diff --git a/frame/identity/src/tests.rs b/frame/identity/src/tests.rs index bf41b451cbaa3..8cb0563ebeaa1 100644 --- a/frame/identity/src/tests.rs +++ b/frame/identity/src/tests.rs @@ -500,3 +500,20 @@ fn setting_account_id_should_work() { assert_ok!(Identity::set_account_id(Origin::signed(4), 0, 3)); }); } + +#[test] +fn test_has_identity() { + new_test_ext().execute_with(|| { + assert_ok!(Identity::set_identity(Origin::signed(10), Box::new(ten()))); + assert!(Identity::has_identity(&10, IdentityField::Display as u64)); + assert!(Identity::has_identity(&10, IdentityField::Legal as u64)); + assert!(Identity::has_identity( + &10, + IdentityField::Display as u64 | IdentityField::Legal as u64 + )); + assert!(!Identity::has_identity( + &10, + IdentityField::Display as u64 | IdentityField::Legal as u64 | IdentityField::Web as u64 + )); + }); +} diff --git a/frame/identity/src/types.rs b/frame/identity/src/types.rs index 18fc54c941cbd..cb8091fe18747 100644 --- a/frame/identity/src/types.rs +++ b/frame/identity/src/types.rs @@ -53,6 +53,12 @@ pub enum Data { ShaThree256([u8; 32]), } +impl Data { + pub fn is_none(&self) -> bool { + self == &Data::None + } +} + impl Decode for Data { fn decode(input: &mut I) -> sp_std::result::Result { let b = input.read_byte()?; @@ -333,6 +339,37 @@ pub struct IdentityInfo> { pub twitter: Data, } +impl> IdentityInfo { + pub(crate) fn fields(&self) -> IdentityFields { + let mut res = >::empty(); + if !self.display.is_none() { + res.insert(IdentityField::Display); + } + if !self.legal.is_none() { + res.insert(IdentityField::Legal); + } + if !self.web.is_none() { + res.insert(IdentityField::Web); + } + if !self.riot.is_none() { + res.insert(IdentityField::Riot); + } + if !self.email.is_none() { + res.insert(IdentityField::Email); + } + if self.pgp_fingerprint.is_some() { + res.insert(IdentityField::PgpFingerprint); + } + if !self.image.is_none() { + res.insert(IdentityField::Image); + } + if !self.twitter.is_none() { + res.insert(IdentityField::Twitter); + } + IdentityFields(res) + } +} + /// Information concerning the identity of the controller of an account. /// /// NOTE: This is stored separately primarily to facilitate the addition of extra fields in a From 1cbe2934a658249a2378b9feeb5a1ed51cbf6f31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Tue, 12 Apr 2022 13:12:53 +0200 Subject: [PATCH 113/484] Finality notification: Optimize calculation of stale heads (#11200) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Finality notification: Optimize calculation of stale heads While looking into some problem on Versi where a collator seemed to be stuck. I found out that it was not stuck but there was a huge gap between last finalized and best block. This lead to a lot leaves and it was basically trapped inside some loop of reading block headers from the db to find the stale heads. While looking into this I found out that `leaves` already supports the feature to give us the stale heads relative easily. However, the semantics change a little bit. Instead of returning all stale heads of blocks that are not reachable anymore after finalizing a block, we currently only return heads with a number lower than the finalized block. This should be no problem, because these other leaves that are stale will be returned later when a block gets finalized which number is bigger than the block number of these leaves. While doing that, I also changed `tree_route` of the `FinalityNotification` to include the `old_finalized`. Based on the comment I assumed that this was already part of it. However, if wanted, I can revert this change. * FMT * Update client/service/src/client/client.rs Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com> * Do not include the last finalized block * Rename function * FMT * Fix tests * Update figure Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com> --- client/api/src/client.rs | 2 + client/api/src/in_mem.rs | 14 +++++++ client/api/src/leaves.rs | 20 +++++++++- client/consensus/babe/src/tests.rs | 4 +- client/db/src/lib.rs | 13 +++++++ client/service/src/client/client.rs | 41 +++++++------------- client/service/test/src/client/mod.rs | 30 +++++++++----- primitives/blockchain/src/backend.rs | 8 ++++ primitives/blockchain/src/header_metadata.rs | 5 +++ 9 files changed, 99 insertions(+), 38 deletions(-) diff --git a/client/api/src/client.rs b/client/api/src/client.rs index 9c55be3238130..183ce610e7b7c 100644 --- a/client/api/src/client.rs +++ b/client/api/src/client.rs @@ -307,6 +307,8 @@ pub struct FinalityNotification { /// Finalized block header. pub header: Block::Header, /// Path from the old finalized to new finalized parent (implicitly finalized blocks). + /// + /// This maps to the range `(old_finalized, new_finalized]`. pub tree_route: Arc<[Block::Hash]>, /// Stale branches heads. pub stale_heads: Arc<[Block::Hash]>, diff --git a/client/api/src/in_mem.rs b/client/api/src/in_mem.rs index d989004ee2178..51370f47e7d14 100644 --- a/client/api/src/in_mem.rs +++ b/client/api/src/in_mem.rs @@ -444,6 +444,20 @@ impl blockchain::Backend for Blockchain { Ok(self.storage.read().leaves.hashes()) } + fn displaced_leaves_after_finalizing( + &self, + block_number: NumberFor, + ) -> sp_blockchain::Result> { + Ok(self + .storage + .read() + .leaves + .displaced_by_finalize_height(block_number) + .leaves() + .cloned() + .collect::>()) + } + fn children(&self, _parent_hash: Block::Hash) -> sp_blockchain::Result> { unimplemented!() } diff --git a/client/api/src/leaves.rs b/client/api/src/leaves.rs index 2a3b95188e68e..5859290777433 100644 --- a/client/api/src/leaves.rs +++ b/client/api/src/leaves.rs @@ -56,7 +56,7 @@ impl FinalizationDisplaced { } /// Iterate over all displaced leaves. - pub fn leaves(&self) -> impl IntoIterator { + pub fn leaves(&self) -> impl Iterator { self.leaves.values().flatten() } } @@ -145,6 +145,24 @@ where FinalizationDisplaced { leaves: below_boundary } } + /// The same as [`Self::finalize_height`], but it only simulates the operation. + /// + /// This means that no changes are done. + /// + /// Returns the leaves that would be displaced by finalizing the given block. + pub fn displaced_by_finalize_height(&self, number: N) -> FinalizationDisplaced { + let boundary = if number == N::zero() { + return FinalizationDisplaced { leaves: BTreeMap::new() } + } else { + number - N::one() + }; + + let below_boundary = self.storage.range(&Reverse(boundary)..); + FinalizationDisplaced { + leaves: below_boundary.map(|(k, v)| (k.clone(), v.clone())).collect(), + } + } + /// Undo all pending operations. /// /// This returns an `Undo` struct, where any diff --git a/client/consensus/babe/src/tests.rs b/client/consensus/babe/src/tests.rs index 080387c88655c..aa2d824b8ccb9 100644 --- a/client/consensus/babe/src/tests.rs +++ b/client/consensus/babe/src/tests.rs @@ -1018,12 +1018,12 @@ fn obsolete_blocks_aux_data_cleanup() { // Create the following test scenario: // - // /-----B3 --- B4 ( < fork2 ) + // /--- --B3 --- B4 ( < fork2 ) // G --- A1 --- A2 --- A3 --- A4 ( < fork1 ) // \-----C4 --- C5 ( < fork3 ) let fork1_hashes = propose_and_import_blocks_wrap(BlockId::Number(0), 4); - let fork2_hashes = propose_and_import_blocks_wrap(BlockId::Number(2), 2); + let fork2_hashes = propose_and_import_blocks_wrap(BlockId::Number(0), 2); let fork3_hashes = propose_and_import_blocks_wrap(BlockId::Number(3), 2); // Check that aux data is present for all but the genesis block. diff --git a/client/db/src/lib.rs b/client/db/src/lib.rs index 6a8a025f1f45f..e753ab9acba2b 100644 --- a/client/db/src/lib.rs +++ b/client/db/src/lib.rs @@ -627,6 +627,19 @@ impl sc_client_api::blockchain::Backend for BlockchainDb, + ) -> ClientResult> { + Ok(self + .leaves + .read() + .displaced_by_finalize_height(block_number) + .leaves() + .cloned() + .collect::>()) + } + fn children(&self, parent_hash: Block::Hash) -> ClientResult> { children::read_children(&*self.db, columns::META, meta_keys::CHILDREN_PREFIX, parent_hash) } diff --git a/client/service/src/client/client.rs b/client/service/src/client/client.rs index 004d4b711e091..dfa392bc96bde 100644 --- a/client/service/src/client/client.rs +++ b/client/service/src/client/client.rs @@ -899,36 +899,25 @@ where let finalized = route_from_finalized.enacted().iter().map(|elem| elem.hash).collect::>(); - let last_finalized_number = self - .backend - .blockchain() - .number(last_finalized)? - .expect("Previous finalized block expected to be onchain; qed"); - let mut stale_heads = Vec::new(); - for head in self.backend.blockchain().leaves()? { - let route_from_finalized = - sp_blockchain::tree_route(self.backend.blockchain(), block, head)?; - let retracted = route_from_finalized.retracted(); - let pivot = route_from_finalized.common_block(); - // It is not guaranteed that `backend.blockchain().leaves()` doesn't return - // heads that were in a stale state before this finalization and thus already - // included in previous notifications. We want to skip such heads. - // Given the "route" from the currently finalized block to the head under - // analysis, the condition for it to be added to the new stale heads list is: - // `!retracted.is_empty() && last_finalized_number <= pivot.number` - // 1. "route" has some "retractions". - // 2. previously finalized block number is not greater than the "route" pivot: - // - if `last_finalized_number <= pivot.number` then this is a new stale head; - // - else the stale head was already included by some previous finalization. - if !retracted.is_empty() && last_finalized_number <= pivot.number { - stale_heads.push(head); - } - } + let block_number = route_from_finalized + .last() + .expect( + "The block to finalize is always the latest \ + block in the route to the finalized block; qed", + ) + .number; + + // The stale heads are the leaves that will be displaced after the + // block is finalized. + let stale_heads = + self.backend.blockchain().displaced_leaves_after_finalizing(block_number)?; + let header = self .backend .blockchain() .header(BlockId::Hash(block))? - .expect("Finalized block expected to be onchain; qed"); + .expect("Block to finalize expected to be onchain; qed"); + operation.notify_finalized = Some(FinalizeSummary { header, finalized, stale_heads }); } diff --git a/client/service/test/src/client/mod.rs b/client/service/test/src/client/mod.rs index c86b23347d417..6aa047c6393da 100644 --- a/client/service/test/src/client/mod.rs +++ b/client/service/test/src/client/mod.rs @@ -1011,12 +1011,16 @@ fn finalizing_diverged_block_should_trigger_reorg() { assert_eq!(client.chain_info().best_hash, b3.hash()); - finality_notification_check(&mut finality_notifications, &[b1.hash()], &[a2.hash()]); + ClientExt::finalize_block(&client, BlockId::Hash(b3.hash()), None).unwrap(); + + finality_notification_check(&mut finality_notifications, &[b1.hash()], &[]); + finality_notification_check(&mut finality_notifications, &[b2.hash(), b3.hash()], &[a2.hash()]); assert!(finality_notifications.try_next().is_err()); } #[test] fn finality_notifications_content() { + sp_tracing::try_init_simple(); let (mut client, _select_chain) = TestClientBuilder::new().build_with_longest_chain(); // -> D3 -> D4 @@ -1110,12 +1114,8 @@ fn finality_notifications_content() { // Import and finalize D4 block_on(client.import_as_final(BlockOrigin::Own, d4.clone())).unwrap(); - finality_notification_check( - &mut finality_notifications, - &[a1.hash(), a2.hash()], - &[c1.hash(), b2.hash()], - ); - finality_notification_check(&mut finality_notifications, &[d3.hash(), d4.hash()], &[a3.hash()]); + finality_notification_check(&mut finality_notifications, &[a1.hash(), a2.hash()], &[c1.hash()]); + finality_notification_check(&mut finality_notifications, &[d3.hash(), d4.hash()], &[b2.hash()]); assert!(finality_notifications.try_next().is_err()); } @@ -1214,7 +1214,7 @@ fn doesnt_import_blocks_that_revert_finality() { // -> C1 // / - // G -> A1 -> A2 + // G -> A1 -> A2 -> A3 // \ // -> B1 -> B2 -> B3 @@ -1294,7 +1294,19 @@ fn doesnt_import_blocks_that_revert_finality() { assert_eq!(import_err.to_string(), expected_err.to_string()); - finality_notification_check(&mut finality_notifications, &[a1.hash(), a2.hash()], &[b2.hash()]); + let a3 = client + .new_block_at(&BlockId::Hash(a2.hash()), Default::default(), false) + .unwrap() + .build() + .unwrap() + .block; + block_on(client.import(BlockOrigin::Own, a3.clone())).unwrap(); + ClientExt::finalize_block(&client, BlockId::Hash(a3.hash()), None).unwrap(); + + finality_notification_check(&mut finality_notifications, &[a1.hash(), a2.hash()], &[]); + + finality_notification_check(&mut finality_notifications, &[a3.hash()], &[b2.hash()]); + assert!(finality_notifications.try_next().is_err()); } diff --git a/primitives/blockchain/src/backend.rs b/primitives/blockchain/src/backend.rs index 3c6419648388e..84b34105e59b8 100644 --- a/primitives/blockchain/src/backend.rs +++ b/primitives/blockchain/src/backend.rs @@ -100,6 +100,14 @@ pub trait Backend: /// Results must be ordered best (longest, highest) chain first. fn leaves(&self) -> Result>; + /// Returns displaced leaves after the given block would be finalized. + /// + /// The returned leaves do not contain the leaves from the same height as `block_number`. + fn displaced_leaves_after_finalizing( + &self, + block_number: NumberFor, + ) -> Result>; + /// Return hashes of all blocks that are children of the block with `parent_hash`. fn children(&self, parent_hash: Block::Hash) -> Result>; diff --git a/primitives/blockchain/src/header_metadata.rs b/primitives/blockchain/src/header_metadata.rs index c21c82b9fbc23..858dcf6c92e80 100644 --- a/primitives/blockchain/src/header_metadata.rs +++ b/primitives/blockchain/src/header_metadata.rs @@ -201,6 +201,11 @@ impl TreeRoute { pub fn enacted(&self) -> &[HashAndNumber] { &self.route[self.pivot + 1..] } + + /// Returns the last block. + pub fn last(&self) -> Option<&HashAndNumber> { + self.route.last() + } } /// Handles header metadata: hash, number, parent hash, etc. From d30394403ad012e35153d121ce3f16a0e56834fb Mon Sep 17 00:00:00 2001 From: Denis Pisarev Date: Tue, 12 Apr 2022 15:53:03 +0200 Subject: [PATCH 114/484] CI: rename ambiguous jobs (#11207) --- .github/workflows/pr-custom-review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-custom-review.yml b/.github/workflows/pr-custom-review.yml index d113d39067846..322403da03b47 100644 --- a/.github/workflows/pr-custom-review.yml +++ b/.github/workflows/pr-custom-review.yml @@ -1,4 +1,4 @@ -name: Check reviews +name: Assign reviewers on: pull_request: From 2d6326e85ef21055ce7ad79f3997f498036e9f6c Mon Sep 17 00:00:00 2001 From: Qinxuan Chen Date: Wed, 13 Apr 2022 04:43:10 +0800 Subject: [PATCH 115/484] Bump soketto to v0.7.1 (#11203) Signed-off-by: koushiro --- Cargo.lock | 35 +++----------------------- bin/node/cli/Cargo.toml | 2 +- bin/node/cli/tests/websocket_server.rs | 4 +-- 3 files changed, 7 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 29e4c2c1ad320..3435608ebc7b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -450,12 +450,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "base64" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" - [[package]] name = "base64" version = "0.13.0" @@ -832,12 +826,6 @@ dependencies = [ "iovec", ] -[[package]] -name = "bytes" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" - [[package]] name = "bytes" version = "1.1.0" @@ -3441,7 +3429,7 @@ dependencies = [ "jsonrpsee-types", "pin-project 1.0.10", "rustls-native-certs 0.6.1", - "soketto 0.7.1", + "soketto", "thiserror", "tokio", "tokio-rustls 0.23.2", @@ -3467,7 +3455,7 @@ dependencies = [ "rustc-hash", "serde", "serde_json", - "soketto 0.7.1", + "soketto", "thiserror", "tokio", "tracing", @@ -4119,7 +4107,7 @@ dependencies = [ "log 0.4.14", "quicksink", "rw-stream-sink", - "soketto 0.7.1", + "soketto", "url 2.2.1", "webpki-roots 0.21.0", ] @@ -4863,7 +4851,7 @@ dependencies = [ "sc-transaction-pool-api", "serde", "serde_json", - "soketto 0.4.2", + "soketto", "sp-api", "sp-authority-discovery", "sp-authorship", @@ -9701,21 +9689,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "soketto" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5c71ed3d54db0a699f4948e1bb3e45b450fa31fe602621dee6680361d569c88" -dependencies = [ - "base64 0.12.3", - "bytes 0.5.6", - "futures 0.3.21", - "httparse", - "log 0.4.14", - "rand 0.7.3", - "sha-1 0.9.4", -] - [[package]] name = "soketto" version = "0.7.1" diff --git a/bin/node/cli/Cargo.toml b/bin/node/cli/Cargo.toml index dda6b57d0244b..bb4ec763a3051 100644 --- a/bin/node/cli/Cargo.toml +++ b/bin/node/cli/Cargo.toml @@ -125,7 +125,7 @@ serde_json = "1.0" regex = "1.5.5" platforms = "2.0" async-std = { version = "1.10.0", features = ["attributes"] } -soketto = "0.4.2" +soketto = "0.7.1" criterion = { version = "0.3.5", features = ["async_tokio"] } tokio = { version = "1.17.0", features = ["macros", "time", "parking_lot"] } wait-timeout = "0.2" diff --git a/bin/node/cli/tests/websocket_server.rs b/bin/node/cli/tests/websocket_server.rs index 6eecfaf6de53f..513497c6cddb5 100644 --- a/bin/node/cli/tests/websocket_server.rs +++ b/bin/node/cli/tests/websocket_server.rs @@ -123,12 +123,12 @@ impl WsServer { let mut server = Server::new(pending_incoming); let websocket_key = match server.receive_request().await { - Ok(req) => req.into_key(), + Ok(req) => req.key(), Err(err) => return Err(Box::new(err) as Box<_>), }; match server - .send_response(&{ Response::Accept { key: &websocket_key, protocol: None } }) + .send_response(&{ Response::Accept { key: websocket_key, protocol: None } }) .await { Ok(()) => {}, From f22310756c759b4d7b3019e7ae22e3c2c4fce361 Mon Sep 17 00:00:00 2001 From: Adrian Catangiu Date: Wed, 13 Apr 2022 13:13:06 +0300 Subject: [PATCH 116/484] Expose MMR root through runtime API - use it in BEEFY client (#11183) * beefy-gadget: allow custom runtime api provider * beefy-gadget: use mock runtime api in tests * pallet-mmr: expose mmr root from state through runtime API * beefy-gadget: get mmr root from runtime state * pallet-beefy-mmr: remove MmrRoot from header digests * frame/mmr: move mmr primitives out of frame * frame/mmr: completely move primitives out of frame * address review comments * beefy-mmr: bring back mmr root from header digest * clippy fixes for rustc 1.60 * address review comments --- Cargo.lock | 37 ++- Cargo.toml | 2 +- bin/node/runtime/src/lib.rs | 6 +- client/beefy/Cargo.toml | 1 + client/beefy/src/lib.rs | 22 +- client/beefy/src/metrics.rs | 5 +- client/beefy/src/tests.rs | 250 +++++++++++------- client/beefy/src/worker.rs | 135 +++++----- client/db/src/lib.rs | 1 + frame/beefy-mmr/Cargo.toml | 2 - frame/beefy-mmr/src/lib.rs | 4 +- frame/beefy-mmr/src/tests.rs | 2 +- frame/merkle-mountain-range/Cargo.toml | 5 +- frame/merkle-mountain-range/rpc/Cargo.toml | 3 +- frame/merkle-mountain-range/rpc/src/lib.rs | 6 +- frame/merkle-mountain-range/src/lib.rs | 36 ++- frame/merkle-mountain-range/src/mmr/mod.rs | 4 +- frame/merkle-mountain-range/src/mock.rs | 4 +- frame/merkle-mountain-range/src/tests.rs | 2 +- .../merkle-mountain-range}/Cargo.toml | 19 +- .../merkle-mountain-range}/src/lib.rs | 192 +++++++------- 21 files changed, 403 insertions(+), 335 deletions(-) rename {frame/merkle-mountain-range/primitives => primitives/merkle-mountain-range}/Cargo.toml (68%) rename {frame/merkle-mountain-range/primitives => primitives/merkle-mountain-range}/src/lib.rs (95%) diff --git a/Cargo.lock b/Cargo.lock index 3435608ebc7b8..5731874d8ca81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -502,6 +502,7 @@ dependencies = [ "sp-finality-grandpa", "sp-keyring", "sp-keystore", + "sp-mmr-primitives", "sp-runtime", "sp-tracing", "strum", @@ -5652,7 +5653,6 @@ dependencies = [ "log 0.4.14", "pallet-beefy", "pallet-mmr", - "pallet-mmr-primitives", "pallet-session", "parity-scale-codec", "scale-info", @@ -6081,27 +6081,11 @@ dependencies = [ "frame-support", "frame-system", "hex-literal", - "pallet-mmr-primitives", "parity-scale-codec", "scale-info", "sp-core", "sp-io", - "sp-runtime", - "sp-std", -] - -[[package]] -name = "pallet-mmr-primitives" -version = "4.0.0-dev" -dependencies = [ - "frame-support", - "frame-system", - "hex-literal", - "log 0.4.14", - "parity-scale-codec", - "serde", - "sp-api", - "sp-core", + "sp-mmr-primitives", "sp-runtime", "sp-std", ] @@ -6113,13 +6097,13 @@ dependencies = [ "jsonrpc-core", "jsonrpc-core-client", "jsonrpc-derive", - "pallet-mmr-primitives", "parity-scale-codec", "serde", "serde_json", "sp-api", "sp-blockchain", "sp-core", + "sp-mmr-primitives", "sp-runtime", ] @@ -10140,6 +10124,21 @@ dependencies = [ "zstd", ] +[[package]] +name = "sp-mmr-primitives" +version = "4.0.0-dev" +dependencies = [ + "hex-literal", + "log 0.4.14", + "parity-scale-codec", + "serde", + "sp-api", + "sp-core", + "sp-debug-derive", + "sp-runtime", + "sp-std", +] + [[package]] name = "sp-npos-elections" version = "4.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index 576bae6b574b2..45df7258ef253 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -101,7 +101,6 @@ members = [ "frame/lottery", "frame/membership", "frame/merkle-mountain-range", - "frame/merkle-mountain-range/primitives", "frame/merkle-mountain-range/rpc", "frame/multisig", "frame/nicks", @@ -172,6 +171,7 @@ members = [ "primitives/keyring", "primitives/keystore", "primitives/maybe-compressed-blob", + "primitives/merkle-mountain-range", "primitives/npos-elections", "primitives/npos-elections/fuzzer", "primitives/offchain", diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 57584bed39b2c..f422d1ffc45b8 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1261,7 +1261,7 @@ impl pallet_mmr::Config for Runtime { const INDEXING_PREFIX: &'static [u8] = b"mmr"; type Hashing = ::Hashing; type Hash = ::Hash; - type LeafData = frame_system::Pallet; + type LeafData = pallet_mmr::ParentNumberAndHash; type OnNewRoot = (); type WeightInfo = (); } @@ -1804,6 +1804,10 @@ impl_runtime_apis! { let node = mmr::DataOrHash::Data(leaf.into_opaque_leaf()); pallet_mmr::verify_leaf_proof::(root, node, proof) } + + fn mmr_root() -> Result { + Ok(Mmr::mmr_root()) + } } impl sp_session::SessionKeys for Runtime { diff --git a/client/beefy/Cargo.toml b/client/beefy/Cargo.toml index 02be645b3fc08..96248249200af 100644 --- a/client/beefy/Cargo.toml +++ b/client/beefy/Cargo.toml @@ -27,6 +27,7 @@ sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } sp-core = { version = "6.0.0", path = "../../primitives/core" } sp-keystore = { version = "0.12.0", path = "../../primitives/keystore" } +sp-mmr-primitives = { version = "4.0.0-dev", path = "../../primitives/merkle-mountain-range" } sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } sc-chain-spec = { version = "4.0.0-dev", path = "../../client/chain-spec" } diff --git a/client/beefy/src/lib.rs b/client/beefy/src/lib.rs index 8a6e175f58321..5d8aa5c866c4a 100644 --- a/client/beefy/src/lib.rs +++ b/client/beefy/src/lib.rs @@ -27,9 +27,10 @@ use sp_api::ProvideRuntimeApi; use sp_blockchain::HeaderBackend; use sp_consensus::SyncOracle; use sp_keystore::SyncCryptoStorePtr; +use sp_mmr_primitives::MmrApi; use sp_runtime::traits::Block; -use beefy_primitives::BeefyApi; +use beefy_primitives::{BeefyApi, MmrRootHash}; use crate::notification::{BeefyBestBlockSender, BeefySignedCommitmentSender}; @@ -87,7 +88,7 @@ pub fn beefy_peers_set_config( /// of today, Rust does not allow a type alias to be used as a trait bound. Tracking /// issue is . pub trait Client: - BlockchainEvents + HeaderBackend + Finalizer + ProvideRuntimeApi + Send + Sync + BlockchainEvents + HeaderBackend + Finalizer + Send + Sync where B: Block, BE: Backend, @@ -110,18 +111,21 @@ where } /// BEEFY gadget initialization parameters. -pub struct BeefyParams +pub struct BeefyParams where B: Block, BE: Backend, C: Client, - C::Api: BeefyApi, + R: ProvideRuntimeApi, + R::Api: BeefyApi + MmrApi, N: GossipNetwork + Clone + SyncOracle + Send + Sync + 'static, { /// BEEFY client pub client: Arc, /// Client Backend pub backend: Arc, + /// Runtime Api Provider + pub runtime: Arc, /// Local key store pub key_store: Option, /// Gossip network @@ -138,21 +142,22 @@ where pub protocol_name: std::borrow::Cow<'static, str>, } -#[cfg(not(test))] /// Start the BEEFY gadget. /// /// This is a thin shim around running and awaiting a BEEFY worker. -pub async fn start_beefy_gadget(beefy_params: BeefyParams) +pub async fn start_beefy_gadget(beefy_params: BeefyParams) where B: Block, BE: Backend, C: Client, - C::Api: BeefyApi, + R: ProvideRuntimeApi, + R::Api: BeefyApi + MmrApi, N: GossipNetwork + Clone + SyncOracle + Send + Sync + 'static, { let BeefyParams { client, backend, + runtime, key_store, network, signed_commitment_sender, @@ -188,6 +193,7 @@ where let worker_params = worker::WorkerParams { client, backend, + runtime, key_store: key_store.into(), signed_commitment_sender, beefy_best_block_sender, @@ -198,7 +204,7 @@ where sync_oracle, }; - let worker = worker::BeefyWorker::<_, _, _, _>::new(worker_params); + let worker = worker::BeefyWorker::<_, _, _, _, _>::new(worker_params); worker.run().await } diff --git a/client/beefy/src/metrics.rs b/client/beefy/src/metrics.rs index 20fa98e52fdd5..a6d29dbf88abb 100644 --- a/client/beefy/src/metrics.rs +++ b/client/beefy/src/metrics.rs @@ -18,9 +18,7 @@ //! BEEFY Prometheus metrics definition -#[cfg(not(test))] -use prometheus::{register, PrometheusError, Registry}; -use prometheus::{Counter, Gauge, U64}; +use prometheus::{register, Counter, Gauge, PrometheusError, Registry, U64}; /// BEEFY metrics exposed through Prometheus pub(crate) struct Metrics { @@ -39,7 +37,6 @@ pub(crate) struct Metrics { } impl Metrics { - #[cfg(not(test))] pub(crate) fn register(registry: &Registry) -> Result { Ok(Self { beefy_validator_set_id: register( diff --git a/client/beefy/src/tests.rs b/client/beefy/src/tests.rs index 92b5ad91c11e1..e568daba8e112 100644 --- a/client/beefy/src/tests.rs +++ b/client/beefy/src/tests.rs @@ -28,18 +28,20 @@ use sc_chain_spec::{ChainSpec, GenericChainSpec}; use sc_client_api::HeaderBackend; use sc_consensus::BoxJustificationImport; use sc_keystore::LocalKeystore; -use sc_network::{config::ProtocolConfig, NetworkService}; -use sc_network_gossip::GossipEngine; +use sc_network::config::ProtocolConfig; use sc_network_test::{ Block, BlockImportAdapter, FullPeerConfig, PassThroughVerifier, Peer, PeersClient, - PeersFullClient, TestNetFactory, + TestNetFactory, }; use sc_utils::notification::NotificationReceiver; use beefy_primitives::{ - crypto::AuthorityId, ConsensusLog, MmrRootHash, ValidatorSet, BEEFY_ENGINE_ID, + crypto::AuthorityId, BeefyApi, ConsensusLog, MmrRootHash, ValidatorSet, BEEFY_ENGINE_ID, KEY_TYPE as BeefyKeyType, }; +use sp_mmr_primitives::{EncodableOpaqueLeaf, Error as MmrError, LeafIndex, MmrApi, Proof}; + +use sp_api::{ApiRef, ProvideRuntimeApi}; use sp_consensus::BlockOrigin; use sp_core::H256; use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; @@ -47,19 +49,16 @@ use sp_runtime::{ codec::Encode, generic::BlockId, traits::Header as HeaderT, BuildStorage, DigestItem, Storage, }; -use substrate_test_runtime_client::{runtime::Header, Backend, ClientExt}; +use substrate_test_runtime_client::{runtime::Header, ClientExt}; -use crate::{ - beefy_protocol_name, - keystore::tests::Keyring as BeefyKeyring, - notification::*, - worker::{tests::TestModifiers, BeefyWorker}, -}; +use crate::{beefy_protocol_name, keystore::tests::Keyring as BeefyKeyring, notification::*}; -const BEEFY_PROTOCOL_NAME: &'static str = "/beefy/1"; +pub(crate) const BEEFY_PROTOCOL_NAME: &'static str = "/beefy/1"; +const GOOD_MMR_ROOT: MmrRootHash = MmrRootHash::repeat_byte(0xbf); +const BAD_MMR_ROOT: MmrRootHash = MmrRootHash::repeat_byte(0x42); -type BeefyValidatorSet = ValidatorSet; -type BeefyPeer = Peer; +pub(crate) type BeefyValidatorSet = ValidatorSet; +pub(crate) type BeefyPeer = Peer; #[derive(Debug, Serialize, Deserialize)] struct Genesis(std::collections::BTreeMap); @@ -101,27 +100,13 @@ fn beefy_protocol_name() { #[allow(dead_code)] #[derive(Clone)] pub(crate) struct BeefyLinkHalf { - signed_commitment_stream: BeefySignedCommitmentStream, - beefy_best_block_stream: BeefyBestBlockStream, + pub signed_commitment_stream: BeefySignedCommitmentStream, + pub beefy_best_block_stream: BeefyBestBlockStream, } #[derive(Default)] pub(crate) struct PeerData { pub(crate) beefy_link_half: Mutex>, - pub(crate) test_modifiers: Option, -} - -impl PeerData { - pub(crate) fn use_validator_set(&mut self, validator_set: &ValidatorSet) { - if let Some(tm) = self.test_modifiers.as_mut() { - tm.active_validators = validator_set.clone(); - } else { - self.test_modifiers = Some(TestModifiers { - active_validators: validator_set.clone(), - corrupt_mmr_roots: false, - }); - } - } } pub(crate) struct BeefyTestNet { @@ -153,17 +138,19 @@ impl BeefyTestNet { count: usize, session_length: u64, validator_set: &BeefyValidatorSet, + include_mmr_digest: bool, ) { self.peer(0).generate_blocks(count, BlockOrigin::File, |builder| { let mut block = builder.build().unwrap().block; - let block_num = *block.header.number(); - let num_byte = block_num.to_le_bytes().into_iter().next().unwrap(); - let mmr_root = MmrRootHash::repeat_byte(num_byte); - - add_mmr_digest(&mut block.header, mmr_root); + if include_mmr_digest { + let block_num = *block.header.number(); + let num_byte = block_num.to_le_bytes().into_iter().next().unwrap(); + let mmr_root = MmrRootHash::repeat_byte(num_byte); + add_mmr_digest(&mut block.header, mmr_root); + } - if block_num % session_length == 0 { + if *block.header.number() % session_length == 0 { add_auth_change_digest(&mut block.header, validator_set.clone()); } @@ -223,6 +210,79 @@ impl TestNetFactory for BeefyTestNet { } } +macro_rules! create_test_api { + ( $api_name:ident, mmr_root: $mmr_root:expr, $($inits:expr),+ ) => { + pub(crate) mod $api_name { + use super::*; + + #[derive(Clone, Default)] + pub(crate) struct TestApi {} + + // compiler gets confused and warns us about unused inner + #[allow(dead_code)] + pub(crate) struct RuntimeApi { + inner: TestApi, + } + + impl ProvideRuntimeApi for TestApi { + type Api = RuntimeApi; + fn runtime_api<'a>(&'a self) -> ApiRef<'a, Self::Api> { + RuntimeApi { inner: self.clone() }.into() + } + } + sp_api::mock_impl_runtime_apis! { + impl BeefyApi for RuntimeApi { + fn validator_set() -> Option { + BeefyValidatorSet::new(make_beefy_ids(&[$($inits),+]), 0) + } + } + + impl MmrApi for RuntimeApi { + fn generate_proof(_leaf_index: LeafIndex) + -> Result<(EncodableOpaqueLeaf, Proof), MmrError> { + unimplemented!() + } + + fn verify_proof(_leaf: EncodableOpaqueLeaf, _proof: Proof) + -> Result<(), MmrError> { + unimplemented!() + } + + fn verify_proof_stateless( + _root: MmrRootHash, + _leaf: EncodableOpaqueLeaf, + _proof: Proof + ) -> Result<(), MmrError> { + unimplemented!() + } + + fn mmr_root() -> Result { + Ok($mmr_root) + } + } + } + } + }; +} + +create_test_api!(two_validators, mmr_root: GOOD_MMR_ROOT, BeefyKeyring::Alice, BeefyKeyring::Bob); +create_test_api!( + four_validators, + mmr_root: GOOD_MMR_ROOT, + BeefyKeyring::Alice, + BeefyKeyring::Bob, + BeefyKeyring::Charlie, + BeefyKeyring::Dave +); +create_test_api!( + bad_four_validators, + mmr_root: BAD_MMR_ROOT, + BeefyKeyring::Alice, + BeefyKeyring::Bob, + BeefyKeyring::Charlie, + BeefyKeyring::Dave +); + fn add_mmr_digest(header: &mut Header, mmr_hash: MmrRootHash) { header.digest_mut().push(DigestItem::Consensus( BEEFY_ENGINE_ID, @@ -248,54 +308,43 @@ pub(crate) fn create_beefy_keystore(authority: BeefyKeyring) -> SyncCryptoStoreP keystore } -pub(crate) fn create_beefy_worker( - peer: &BeefyPeer, - key: &BeefyKeyring, - min_block_delta: u32, -) -> BeefyWorker>> { - let keystore = create_beefy_keystore(*key); - - let (signed_commitment_sender, signed_commitment_stream) = - BeefySignedCommitmentStream::::channel(); - let (beefy_best_block_sender, beefy_best_block_stream) = - BeefyBestBlockStream::::channel(); - - let beefy_link_half = BeefyLinkHalf { signed_commitment_stream, beefy_best_block_stream }; - *peer.data.beefy_link_half.lock() = Some(beefy_link_half); - let test_modifiers = peer.data.test_modifiers.clone().unwrap(); - - let network = peer.network_service().clone(); - let sync_oracle = network.clone(); - let gossip_validator = Arc::new(crate::gossip::GossipValidator::new()); - let gossip_engine = - GossipEngine::new(network, BEEFY_PROTOCOL_NAME, gossip_validator.clone(), None); - let worker_params = crate::worker::WorkerParams { - client: peer.client().as_client(), - backend: peer.client().as_backend(), - key_store: Some(keystore).into(), - signed_commitment_sender, - beefy_best_block_sender, - gossip_engine, - gossip_validator, - min_block_delta, - metrics: None, - sync_oracle, - }; - - BeefyWorker::<_, _, _, _>::new(worker_params, test_modifiers) -} - // Spawns beefy voters. Returns a future to spawn on the runtime. -fn initialize_beefy( +fn initialize_beefy( net: &mut BeefyTestNet, - peers: &[BeefyKeyring], + peers: Vec<(usize, &BeefyKeyring, Arc)>, min_block_delta: u32, -) -> impl Future { +) -> impl Future +where + API: ProvideRuntimeApi + Default + Sync + Send, + API::Api: BeefyApi + MmrApi, +{ let voters = FuturesUnordered::new(); - for (peer_id, key) in peers.iter().enumerate() { - let worker = create_beefy_worker(&net.peers[peer_id], key, min_block_delta); - let gadget = worker.run(); + for (peer_id, key, api) in peers.into_iter() { + let peer = &net.peers[peer_id]; + + let keystore = create_beefy_keystore(*key); + + let (signed_commitment_sender, signed_commitment_stream) = + BeefySignedCommitmentStream::::channel(); + let (beefy_best_block_sender, beefy_best_block_stream) = + BeefyBestBlockStream::::channel(); + let beefy_link_half = BeefyLinkHalf { signed_commitment_stream, beefy_best_block_stream }; + *peer.data.beefy_link_half.lock() = Some(beefy_link_half); + + let beefy_params = crate::BeefyParams { + client: peer.client().as_client(), + backend: peer.client().as_backend(), + runtime: api.clone(), + key_store: Some(keystore), + network: peer.network_service().clone(), + signed_commitment_sender, + beefy_best_block_sender, + min_block_delta, + prometheus_registry: None, + protocol_name: BEEFY_PROTOCOL_NAME.into(), + }; + let gadget = crate::start_beefy_gadget::<_, _, _, _, _>(beefy_params); fn assert_send(_: &T) {} assert_send(&gadget); @@ -443,13 +492,12 @@ fn beefy_finalizing_blocks() { let mut net = BeefyTestNet::new(2, 0); - for i in 0..peers.len() { - net.peer(i).data.use_validator_set(&validator_set); - } - runtime.spawn(initialize_beefy(&mut net, peers, min_block_delta)); + let api = Arc::new(two_validators::TestApi {}); + let beefy_peers = peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect(); + runtime.spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta)); // push 42 blocks including `AuthorityChange` digests every 10 blocks. - net.generate_blocks(42, session_len, &validator_set); + net.generate_blocks(42, session_len, &validator_set, true); net.block_until_sync(); let net = Arc::new(Mutex::new(net)); @@ -477,19 +525,18 @@ fn lagging_validators() { sp_tracing::try_init_simple(); let mut runtime = Runtime::new().unwrap(); - let peers = &[BeefyKeyring::Charlie, BeefyKeyring::Dave]; + let peers = &[BeefyKeyring::Alice, BeefyKeyring::Bob]; let validator_set = ValidatorSet::new(make_beefy_ids(peers), 0).unwrap(); let session_len = 30; let min_block_delta = 1; let mut net = BeefyTestNet::new(2, 0); - for i in 0..peers.len() { - net.peer(i).data.use_validator_set(&validator_set); - } - runtime.spawn(initialize_beefy(&mut net, peers, min_block_delta)); + let api = Arc::new(two_validators::TestApi {}); + let beefy_peers = peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect(); + runtime.spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta)); // push 42 blocks including `AuthorityChange` digests every 30 blocks. - net.generate_blocks(42, session_len, &validator_set); + net.generate_blocks(42, session_len, &validator_set, true); net.block_until_sync(); let net = Arc::new(Mutex::new(net)); @@ -498,7 +545,7 @@ fn lagging_validators() { // diff-power-of-two rule. finalize_block_and_wait_for_beefy(&net, peers, &mut runtime, &[15], &[1, 9, 13, 14, 15]); - // Charlie finalizes #25, Dave lags behind + // Alice finalizes #25, Bob lags behind let finalize = BlockId::number(25); let (best_blocks, signed_commitments) = get_beefy_streams(&mut *net.lock(), peers); net.lock().peer(0).client().as_client().finalize_block(finalize, None).unwrap(); @@ -507,7 +554,7 @@ fn lagging_validators() { streams_empty_after_timeout(best_blocks, &net, &mut runtime, timeout); streams_empty_after_timeout(signed_commitments, &net, &mut runtime, None); - // Dave catches up and also finalizes #25 + // Bob catches up and also finalizes #25 let (best_blocks, signed_commitments) = get_beefy_streams(&mut *net.lock(), peers); net.lock().peer(1).client().as_client().finalize_block(finalize, None).unwrap(); // expected beefy finalizes block #17 from diff-power-of-two @@ -530,16 +577,23 @@ fn correct_beefy_payload() { let min_block_delta = 2; let mut net = BeefyTestNet::new(4, 0); - for i in 0..peers.len() { - net.peer(i).data.use_validator_set(&validator_set); - } + + // Alice, Bob, Charlie will vote on good payloads + let good_api = Arc::new(four_validators::TestApi {}); + let good_peers = [BeefyKeyring::Alice, BeefyKeyring::Bob, BeefyKeyring::Charlie] + .iter() + .enumerate() + .map(|(id, key)| (id, key, good_api.clone())) + .collect(); + runtime.spawn(initialize_beefy(&mut net, good_peers, min_block_delta)); // Dave will vote on bad mmr roots - net.peer(3).data.test_modifiers.as_mut().map(|tm| tm.corrupt_mmr_roots = true); - runtime.spawn(initialize_beefy(&mut net, peers, min_block_delta)); + let bad_api = Arc::new(bad_four_validators::TestApi {}); + let bad_peers = vec![(3, &BeefyKeyring::Dave, bad_api)]; + runtime.spawn(initialize_beefy(&mut net, bad_peers, min_block_delta)); // push 10 blocks - net.generate_blocks(12, session_len, &validator_set); + net.generate_blocks(12, session_len, &validator_set, false); net.block_until_sync(); let net = Arc::new(Mutex::new(net)); diff --git a/client/beefy/src/worker.rs b/client/beefy/src/worker.rs index 85674c09a278b..128850a5f172f 100644 --- a/client/beefy/src/worker.rs +++ b/client/beefy/src/worker.rs @@ -26,9 +26,10 @@ use parking_lot::Mutex; use sc_client_api::{Backend, FinalityNotification, FinalityNotifications}; use sc_network_gossip::GossipEngine; -use sp_api::BlockId; +use sp_api::{BlockId, ProvideRuntimeApi}; use sp_arithmetic::traits::AtLeast32Bit; use sp_consensus::SyncOracle; +use sp_mmr_primitives::MmrApi; use sp_runtime::{ generic::OpaqueDigestItemId, traits::{Block, Header, NumberFor}, @@ -52,12 +53,10 @@ use crate::{ Client, }; -pub(crate) struct WorkerParams -where - B: Block, -{ +pub(crate) struct WorkerParams { pub client: Arc, pub backend: Arc, + pub runtime: Arc, pub key_store: BeefyKeystore, pub signed_commitment_sender: BeefySignedCommitmentSender, pub beefy_best_block_sender: BeefyBestBlockSender, @@ -69,15 +68,10 @@ where } /// A BEEFY worker plays the BEEFY protocol -pub(crate) struct BeefyWorker -where - B: Block, - BE: Backend, - C: Client, - SO: SyncOracle + Send + Sync + Clone + 'static, -{ +pub(crate) struct BeefyWorker { client: Arc, backend: Arc, + runtime: Arc, key_store: BeefyKeystore, signed_commitment_sender: BeefySignedCommitmentSender, gossip_engine: Arc>>, @@ -99,17 +93,15 @@ where sync_oracle: SO, // keep rustc happy _backend: PhantomData, - #[cfg(test)] - // behavior modifiers used in tests - test_res: tests::TestModifiers, } -impl BeefyWorker +impl BeefyWorker where B: Block + Codec, BE: Backend, C: Client, - C::Api: BeefyApi, + R: ProvideRuntimeApi, + R::Api: BeefyApi + MmrApi, SO: SyncOracle + Send + Sync + Clone + 'static, { /// Return a new BEEFY worker instance. @@ -118,15 +110,11 @@ where /// BEEFY pallet has been deployed on-chain. /// /// The BEEFY pallet is needed in order to keep track of the BEEFY authority set. - pub(crate) fn new( - worker_params: WorkerParams, - #[cfg(test)] - // behavior modifiers used in tests - test_res: tests::TestModifiers, - ) -> Self { + pub(crate) fn new(worker_params: WorkerParams) -> Self { let WorkerParams { client, backend, + runtime, key_store, signed_commitment_sender, beefy_best_block_sender, @@ -144,6 +132,7 @@ where BeefyWorker { client: client.clone(), backend, + runtime, key_store, signed_commitment_sender, gossip_engine: Arc::new(Mutex::new(gossip_engine)), @@ -159,20 +148,9 @@ where beefy_best_block_sender, sync_oracle, _backend: PhantomData, - #[cfg(test)] - test_res, } } -} -impl BeefyWorker -where - B: Block, - BE: Backend, - C: Client, - C::Api: BeefyApi, - SO: SyncOracle + Send + Sync + Clone + 'static, -{ /// Return `Some(number)` if we should be voting on block `number` now, /// return `None` if there is no block we should vote on now. fn current_vote_target(&self) -> Option> { @@ -396,7 +374,7 @@ where }; let target_hash = target_header.hash(); - let mmr_root = if let Some(hash) = self.extract_mmr_root_digest(&target_header) { + let mmr_root = if let Some(hash) = self.get_mmr_root_digest(&target_header) { hash } else { warn!(target: "beefy", "🥩 No MMR root digest found for: {:?}", target_hash); @@ -458,13 +436,12 @@ where } /// Wait for BEEFY runtime pallet to be available. - #[cfg(not(test))] async fn wait_for_runtime_pallet(&mut self) { self.client .finality_notification_stream() .take_while(|notif| { let at = BlockId::hash(notif.header.hash()); - if let Some(active) = self.client.runtime_api().validator_set(&at).ok().flatten() { + if let Some(active) = self.runtime.runtime_api().validator_set(&at).ok().flatten() { if active.id() == GENESIS_AUTHORITY_SET_ID { // When starting from genesis, there is no session boundary digest. // Just initialize `rounds` to Block #1 as BEEFY mandatory block. @@ -488,13 +465,6 @@ where self.finality_notifications = self.client.finality_notification_stream(); } - /// For tests don't use runtime pallet. Start rounds from block #1. - #[cfg(test)] - async fn wait_for_runtime_pallet(&mut self) { - let active = self.test_res.active_validators.clone(); - self.init_session_at(active, 1u32.into()); - } - /// Main loop for BEEFY worker. /// /// Wait for BEEFY runtime pallet to be available, then start the main async loop @@ -550,20 +520,15 @@ where } } - /// Simple wrapper over mmr root extraction. - #[cfg(not(test))] - fn extract_mmr_root_digest(&self, header: &B::Header) -> Option { - find_mmr_root_digest::(header) - } - - /// For tests, have the option to modify mmr root. - #[cfg(test)] - fn extract_mmr_root_digest(&self, header: &B::Header) -> Option { - let mut mmr_root = find_mmr_root_digest::(header); - if self.test_res.corrupt_mmr_roots { - mmr_root.as_mut().map(|hash| *hash ^= MmrRootHash::random()); - } - mmr_root + /// Simple wrapper that gets MMR root from header digests or from client state. + fn get_mmr_root_digest(&self, header: &B::Header) -> Option { + find_mmr_root_digest::(header).or_else(|| { + self.runtime + .runtime_api() + .mmr_root(&BlockId::hash(header.hash())) + .ok() + .and_then(|r| r.ok()) + }) } } @@ -658,11 +623,16 @@ pub(crate) mod tests { use super::*; use crate::{ keystore::tests::Keyring, - tests::{create_beefy_worker, get_beefy_streams, make_beefy_ids, BeefyTestNet}, + notification::{BeefyBestBlockStream, BeefySignedCommitmentStream}, + tests::{ + create_beefy_keystore, get_beefy_streams, make_beefy_ids, two_validators::TestApi, + BeefyPeer, BeefyTestNet, BEEFY_PROTOCOL_NAME, + }, }; use futures::{executor::block_on, future::poll_fn, task::Poll}; + use crate::tests::BeefyLinkHalf; use sc_client_api::HeaderBackend; use sc_network::NetworkService; use sc_network_test::{PeersFullClient, TestNetFactory}; @@ -672,10 +642,40 @@ pub(crate) mod tests { Backend, }; - #[derive(Clone)] - pub struct TestModifiers { - pub active_validators: ValidatorSet, - pub corrupt_mmr_roots: bool, + fn create_beefy_worker( + peer: &BeefyPeer, + key: &Keyring, + min_block_delta: u32, + ) -> BeefyWorker>> { + let keystore = create_beefy_keystore(*key); + + let (signed_commitment_sender, signed_commitment_stream) = + BeefySignedCommitmentStream::::channel(); + let (beefy_best_block_sender, beefy_best_block_stream) = + BeefyBestBlockStream::::channel(); + let beefy_link_half = BeefyLinkHalf { signed_commitment_stream, beefy_best_block_stream }; + *peer.data.beefy_link_half.lock() = Some(beefy_link_half); + + let api = Arc::new(TestApi {}); + let network = peer.network_service().clone(); + let sync_oracle = network.clone(); + let gossip_validator = Arc::new(crate::gossip::GossipValidator::new()); + let gossip_engine = + GossipEngine::new(network, BEEFY_PROTOCOL_NAME, gossip_validator.clone(), None); + let worker_params = crate::worker::WorkerParams { + client: peer.client().as_client(), + backend: peer.client().as_backend(), + runtime: api, + key_store: Some(keystore).into(), + signed_commitment_sender, + beefy_best_block_sender, + gossip_engine, + gossip_validator, + min_block_delta, + metrics: None, + sync_oracle, + }; + BeefyWorker::<_, _, _, _, _>::new(worker_params) } #[test] @@ -825,7 +825,6 @@ pub(crate) mod tests { let keys = &[Keyring::Alice]; let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); let mut net = BeefyTestNet::new(1, 0); - net.peer(0).data.use_validator_set(&validator_set); let mut worker = create_beefy_worker(&net.peer(0), &keys[0], 1); // rounds not initialized -> should vote: `None` @@ -833,8 +832,9 @@ pub(crate) mod tests { let set_up = |worker: &mut BeefyWorker< Block, - PeersFullClient, Backend, + PeersFullClient, + TestApi, Arc>, >, best_grandpa: u64, @@ -889,7 +889,6 @@ pub(crate) mod tests { let keys = &[Keyring::Alice]; let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); let mut net = BeefyTestNet::new(1, 0); - net.peer(0).data.use_validator_set(&validator_set); let mut worker = create_beefy_worker(&net.peer(0), &keys[0], 1); // keystore doesn't contain other keys than validators' @@ -913,7 +912,6 @@ pub(crate) mod tests { let keys = &[Keyring::Alice]; let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); let mut net = BeefyTestNet::new(1, 0); - net.peer(0).data.use_validator_set(&validator_set); let mut worker = create_beefy_worker(&net.peer(0), &keys[0], 1); let (mut best_block_streams, _) = get_beefy_streams(&mut net, keys); @@ -939,7 +937,7 @@ pub(crate) mod tests { // generate 2 blocks, try again expect success let (mut best_block_streams, _) = get_beefy_streams(&mut net, keys); let mut best_block_stream = best_block_streams.drain(..).next().unwrap(); - net.generate_blocks(2, 10, &validator_set); + net.generate_blocks(2, 10, &validator_set, false); worker.set_best_beefy_block(2); assert_eq!(worker.best_beefy_block, Some(2)); @@ -961,7 +959,6 @@ pub(crate) mod tests { let keys = &[Keyring::Alice]; let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); let mut net = BeefyTestNet::new(1, 0); - net.peer(0).data.use_validator_set(&validator_set); let mut worker = create_beefy_worker(&net.peer(0), &keys[0], 1); assert!(worker.rounds.is_none()); diff --git a/client/db/src/lib.rs b/client/db/src/lib.rs index e753ab9acba2b..735058897340e 100644 --- a/client/db/src/lib.rs +++ b/client/db/src/lib.rs @@ -106,6 +106,7 @@ const DEFAULT_CHILD_RATIO: (usize, usize) = (1, 10); pub type DbState = sp_state_machine::TrieBackend>>, HashFor>; +#[cfg(feature = "with-parity-db")] /// Length of a [`DbHash`]. const DB_HASH_LEN: usize = 32; diff --git a/frame/beefy-mmr/Cargo.toml b/frame/beefy-mmr/Cargo.toml index 19703fa79863a..a32b1dc6dbd64 100644 --- a/frame/beefy-mmr/Cargo.toml +++ b/frame/beefy-mmr/Cargo.toml @@ -18,7 +18,6 @@ serde = { version = "1.0.136", optional = true } frame-support = { version = "4.0.0-dev", path = "../support", default-features = false } frame-system = { version = "4.0.0-dev", path = "../system", default-features = false } pallet-mmr = { version = "4.0.0-dev", path = "../merkle-mountain-range", default-features = false } -pallet-mmr-primitives = { version = "4.0.0-dev", path = "../merkle-mountain-range/primitives", default-features = false } pallet-session = { version = "4.0.0-dev", path = "../session", default-features = false } sp-core = { version = "6.0.0", path = "../../primitives/core", default-features = false } @@ -46,7 +45,6 @@ std = [ "k256/std", "log/std", "pallet-beefy/std", - "pallet-mmr-primitives/std", "pallet-mmr/std", "pallet-session/std", "serde", diff --git a/frame/beefy-mmr/src/lib.rs b/frame/beefy-mmr/src/lib.rs index 9ee7bd770f64c..640ebeb7d49cc 100644 --- a/frame/beefy-mmr/src/lib.rs +++ b/frame/beefy-mmr/src/lib.rs @@ -37,7 +37,7 @@ use sp_runtime::traits::{Convert, Hash, Member}; use sp_std::prelude::*; use beefy_primitives::mmr::{BeefyDataProvider, BeefyNextAuthoritySet, MmrLeaf, MmrLeafVersion}; -use pallet_mmr::primitives::LeafDataProvider; +use pallet_mmr::{LeafDataProvider, ParentNumberAndHash}; use frame_support::traits::Get; @@ -150,7 +150,7 @@ where fn leaf_data() -> Self::LeafData { MmrLeaf { version: T::LeafVersion::get(), - parent_number_and_hash: frame_system::Pallet::::leaf_data(), + parent_number_and_hash: ParentNumberAndHash::::leaf_data(), leaf_extra: T::BeefyDataProvider::extra_data(), beefy_next_authority_set: Pallet::::update_beefy_next_authority_set(), } diff --git a/frame/beefy-mmr/src/tests.rs b/frame/beefy-mmr/src/tests.rs index fd383adb1d4a2..37f571cd842ee 100644 --- a/frame/beefy-mmr/src/tests.rs +++ b/frame/beefy-mmr/src/tests.rs @@ -49,7 +49,7 @@ fn offchain_key(pos: usize) -> Vec { } fn read_mmr_leaf(ext: &mut TestExternalities, index: usize) -> MmrLeaf { - type Node = pallet_mmr_primitives::DataOrHash; + type Node = pallet_mmr::primitives::DataOrHash; ext.persist_offchain_overlay(); let offchain_db = ext.offchain_db(); offchain_db diff --git a/frame/merkle-mountain-range/Cargo.toml b/frame/merkle-mountain-range/Cargo.toml index 796ab98dc2c32..8b67174e8385d 100644 --- a/frame/merkle-mountain-range/Cargo.toml +++ b/frame/merkle-mountain-range/Cargo.toml @@ -18,6 +18,7 @@ mmr-lib = { package = "ckb-merkle-mountain-range", default-features = false, ver sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } +sp-mmr-primitives = { version = "4.0.0-dev", default-features = false, path = "../../primitives/merkle-mountain-range" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } @@ -25,8 +26,6 @@ frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = " frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -pallet-mmr-primitives = { version = "4.0.0-dev", default-features = false, path = "./primitives" } - [dev-dependencies] env_logger = "0.9" hex-literal = "0.3" @@ -39,12 +38,12 @@ std = [ "mmr-lib/std", "sp-core/std", "sp-io/std", + "sp-mmr-primitives/std", "sp-runtime/std", "sp-std/std", "frame-benchmarking/std", "frame-support/std", "frame-system/std", - "pallet-mmr-primitives/std", ] runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/merkle-mountain-range/rpc/Cargo.toml b/frame/merkle-mountain-range/rpc/Cargo.toml index 9ac26c2ed54b2..94c895ea91517 100644 --- a/frame/merkle-mountain-range/rpc/Cargo.toml +++ b/frame/merkle-mountain-range/rpc/Cargo.toml @@ -22,9 +22,8 @@ serde = { version = "1.0.136", features = ["derive"] } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } sp-core = { version = "6.0.0", path = "../../../primitives/core" } +sp-mmr-primitives = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/merkle-mountain-range" } sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } -pallet-mmr-primitives = { version = "4.0.0-dev", path = "../primitives" } - [dev-dependencies] serde_json = "1.0.79" diff --git a/frame/merkle-mountain-range/rpc/src/lib.rs b/frame/merkle-mountain-range/rpc/src/lib.rs index bf3eb3b694e39..99359bfea8eb6 100644 --- a/frame/merkle-mountain-range/rpc/src/lib.rs +++ b/frame/merkle-mountain-range/rpc/src/lib.rs @@ -26,13 +26,13 @@ use jsonrpc_core::{Error, ErrorCode, Result}; use jsonrpc_derive::rpc; use serde::{Deserialize, Serialize}; -use pallet_mmr_primitives::{Error as MmrError, Proof}; use sp_api::ProvideRuntimeApi; use sp_blockchain::HeaderBackend; use sp_core::Bytes; +use sp_mmr_primitives::{Error as MmrError, LeafIndex, Proof}; use sp_runtime::{generic::BlockId, traits::Block as BlockT}; -pub use pallet_mmr_primitives::{LeafIndex, MmrApi as MmrRuntimeApi}; +pub use sp_mmr_primitives::MmrApi as MmrRuntimeApi; /// Retrieved MMR leaf and its proof. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] @@ -42,7 +42,7 @@ pub struct LeafProof { pub block_hash: BlockHash, /// SCALE-encoded leaf data. pub leaf: Bytes, - /// SCALE-encoded proof data. See [pallet_mmr_primitives::Proof]. + /// SCALE-encoded proof data. See [sp_mmr_primitives::Proof]. pub proof: Bytes, } diff --git a/frame/merkle-mountain-range/src/lib.rs b/frame/merkle-mountain-range/src/lib.rs index f904428e02048..855eb0a7436dc 100644 --- a/frame/merkle-mountain-range/src/lib.rs +++ b/frame/merkle-mountain-range/src/lib.rs @@ -58,7 +58,7 @@ use codec::Encode; use frame_support::weights::Weight; -use sp_runtime::traits; +use sp_runtime::traits::{self, One, Saturating}; #[cfg(any(feature = "runtime-benchmarks", test))] mod benchmarking; @@ -70,7 +70,30 @@ mod mock; mod tests; pub use pallet::*; -pub use pallet_mmr_primitives::{self as primitives, NodeIndex}; +pub use sp_mmr_primitives::{self as primitives, Error, LeafDataProvider, LeafIndex, NodeIndex}; + +/// The most common use case for MMRs is to store historical block hashes, +/// so that any point in time in the future we can receive a proof about some past +/// blocks without using excessive on-chain storage. +/// +/// Hence we implement the [LeafDataProvider] for [ParentNumberAndHash] which is a +/// crate-local wrapper over [frame_system::Pallet]. Since the current block hash +/// is not available (since the block is not finished yet), +/// we use the `parent_hash` here along with parent block number. +pub struct ParentNumberAndHash { + _phanthom: sp_std::marker::PhantomData, +} + +impl LeafDataProvider for ParentNumberAndHash { + type LeafData = (::BlockNumber, ::Hash); + + fn leaf_data() -> Self::LeafData { + ( + frame_system::Pallet::::block_number().saturating_sub(One::one()), + frame_system::Pallet::::parent_hash(), + ) + } +} pub trait WeightInfo { fn on_initialize(peaks: NodeIndex) -> Weight; @@ -161,7 +184,7 @@ pub mod pallet { /// Current size of the MMR (number of leaves). #[pallet::storage] #[pallet::getter(fn mmr_leaves)] - pub type NumberOfLeaves = StorageValue<_, NodeIndex, ValueQuery>; + pub type NumberOfLeaves = StorageValue<_, LeafIndex, ValueQuery>; /// Hashes of the nodes in the MMR. /// @@ -240,7 +263,7 @@ impl, I: 'static> Pallet { /// all the leaves to be present. /// It may return an error or panic if used incorrectly. pub fn generate_proof( - leaf_index: NodeIndex, + leaf_index: LeafIndex, ) -> Result<(LeafOf, primitives::Proof<>::Hash>), primitives::Error> { let mmr: ModuleMmr = mmr::Mmr::new(Self::mmr_leaves()); mmr.generate_proof(leaf_index) @@ -272,4 +295,9 @@ impl, I: 'static> Pallet { Err(primitives::Error::Verify.log_debug("The proof is incorrect.")) } } + + /// Return the on-chain MMR root hash. + pub fn mmr_root() -> >::Hash { + Self::mmr_root_hash() + } } diff --git a/frame/merkle-mountain-range/src/mmr/mod.rs b/frame/merkle-mountain-range/src/mmr/mod.rs index 1a729b08b966f..1cb4e8535b991 100644 --- a/frame/merkle-mountain-range/src/mmr/mod.rs +++ b/frame/merkle-mountain-range/src/mmr/mod.rs @@ -19,7 +19,7 @@ mod mmr; pub mod storage; pub mod utils; -use crate::primitives::FullLeaf; +use sp_mmr_primitives::{DataOrHash, FullLeaf}; use sp_runtime::traits; pub use self::mmr::{verify_leaf_proof, Mmr}; @@ -28,7 +28,7 @@ pub use self::mmr::{verify_leaf_proof, Mmr}; pub type NodeOf = Node<>::Hashing, L>; /// A node stored in the MMR. -pub type Node = crate::primitives::DataOrHash; +pub type Node = DataOrHash; /// Default Merging & Hashing behavior for MMR. pub struct Hasher(sp_std::marker::PhantomData<(H, L)>); diff --git a/frame/merkle-mountain-range/src/mock.rs b/frame/merkle-mountain-range/src/mock.rs index 56d3c9c0d77d8..b2b6821fcd054 100644 --- a/frame/merkle-mountain-range/src/mock.rs +++ b/frame/merkle-mountain-range/src/mock.rs @@ -20,8 +20,8 @@ use crate::*; use codec::{Decode, Encode}; use frame_support::traits::{ConstU32, ConstU64}; -use pallet_mmr_primitives::{Compact, LeafDataProvider}; use sp_core::H256; +use sp_mmr_primitives::{Compact, LeafDataProvider}; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup, Keccak256}, @@ -74,7 +74,7 @@ impl Config for Test { type Hashing = Keccak256; type Hash = H256; - type LeafData = Compact, LeafData)>; + type LeafData = Compact, LeafData)>; type OnNewRoot = (); type WeightInfo = (); } diff --git a/frame/merkle-mountain-range/src/tests.rs b/frame/merkle-mountain-range/src/tests.rs index 576a7ace8f1c0..70d1395aa94d5 100644 --- a/frame/merkle-mountain-range/src/tests.rs +++ b/frame/merkle-mountain-range/src/tests.rs @@ -19,11 +19,11 @@ use crate::{mmr::utils, mock::*, *}; use frame_support::traits::OnInitialize; use mmr_lib::helper; -use pallet_mmr_primitives::{Compact, Proof}; use sp_core::{ offchain::{testing::TestOffchainExt, OffchainDbExt, OffchainWorkerExt}, H256, }; +use sp_mmr_primitives::{Compact, Proof}; pub(crate) fn new_test_ext() -> sp_io::TestExternalities { frame_system::GenesisConfig::default().build_storage::().unwrap().into() diff --git a/frame/merkle-mountain-range/primitives/Cargo.toml b/primitives/merkle-mountain-range/Cargo.toml similarity index 68% rename from frame/merkle-mountain-range/primitives/Cargo.toml rename to primitives/merkle-mountain-range/Cargo.toml index 3ce2caa7762c0..ddb820f9e6cc4 100644 --- a/frame/merkle-mountain-range/primitives/Cargo.toml +++ b/primitives/merkle-mountain-range/Cargo.toml @@ -1,12 +1,12 @@ [package] -name = "pallet-mmr-primitives" +name = "sp-mmr-primitives" version = "4.0.0-dev" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" -description = "FRAME Merkle Mountain Range primitives." +description = "Merkle Mountain Range primitives." [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -16,13 +16,11 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = log = { version = "0.4.14", default-features = false } serde = { version = "1.0.136", optional = true, features = ["derive"] } -sp-api = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/api" } -sp-core = { version = "6.0.0", default-features = false, path = "../../../primitives/core" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../../primitives/runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../../primitives/std" } - -frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } -frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } +sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } +sp-core = { version = "6.0.0", default-features = false, path = "../core" } +sp-debug-derive = { version = "4.0.0", default-features = false, path = "../debug-derive" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../std" } [dev-dependencies] hex-literal = "0.3" @@ -35,8 +33,7 @@ std = [ "serde", "sp-api/std", "sp-core/std", + "sp-debug-derive/std", "sp-runtime/std", "sp-std/std", - "frame-support/std", - "frame-system/std", ] diff --git a/frame/merkle-mountain-range/primitives/src/lib.rs b/primitives/merkle-mountain-range/src/lib.rs similarity index 95% rename from frame/merkle-mountain-range/primitives/src/lib.rs rename to primitives/merkle-mountain-range/src/lib.rs index cc78dfefefe60..a27536b8d35b7 100644 --- a/frame/merkle-mountain-range/primitives/src/lib.rs +++ b/primitives/merkle-mountain-range/src/lib.rs @@ -20,8 +20,8 @@ #![cfg_attr(not(feature = "std"), no_std)] #![warn(missing_docs)] -use frame_support::RuntimeDebug; -use sp_runtime::traits::{self, One, Saturating}; +use sp_debug_derive::RuntimeDebug; +use sp_runtime::traits; use sp_std::fmt; #[cfg(not(feature = "std"))] use sp_std::prelude::Vec; @@ -57,21 +57,6 @@ impl LeafDataProvider for () { } } -/// The most common use case for MMRs is to store historical block hashes, -/// so that any point in time in the future we can receive a proof about some past -/// blocks without using excessive on-chain storage. -/// -/// Hence we implement the [LeafDataProvider] for [frame_system::Pallet]. Since the -/// current block hash is not available (since the block is not finished yet), -/// we use the `parent_hash` here along with parent block number. -impl LeafDataProvider for frame_system::Pallet { - type LeafData = (::BlockNumber, ::Hash); - - fn leaf_data() -> Self::LeafData { - (Self::block_number().saturating_sub(One::one()), Self::parent_hash()) - } -} - /// New MMR root notification hook. pub trait OnNewRoot { /// Function called by the pallet in case new MMR root has been computed. @@ -83,6 +68,17 @@ impl OnNewRoot for () { fn on_new_root(_root: &Hash) {} } +/// A MMR proof data for one of the leaves. +#[derive(codec::Encode, codec::Decode, RuntimeDebug, Clone, PartialEq, Eq)] +pub struct Proof { + /// The index of the leaf the proof is for. + pub leaf_index: LeafIndex, + /// Number of leaves in MMR, when the proof was generated. + pub leaf_count: NodeIndex, + /// Proof elements (hashes of siblings of inner nodes on the path to the leaf). + pub items: Vec, +} + /// A full leaf content stored in the offchain-db. pub trait FullLeaf: Clone + PartialEq + fmt::Debug { /// Encode the leaf either in it's full or compact form. @@ -97,6 +93,80 @@ impl FullLeaf } } +/// A helper type to allow using arbitrary SCALE-encoded leaf data in the RuntimeApi. +/// +/// The point is to be able to verify MMR proofs from external MMRs, where we don't +/// know the exact leaf type, but it's enough for us to have it SCALE-encoded. +/// +/// Note the leaf type should be encoded in its compact form when passed through this type. +/// See [FullLeaf] documentation for details. +/// +/// This type does not implement SCALE encoding/decoding on purpose to avoid confusion, +/// it would have to be SCALE-compatible with the concrete leaf type, but due to SCALE limitations +/// it's not possible to know how many bytes the encoding of concrete leaf type uses. +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +#[derive(RuntimeDebug, Clone, PartialEq)] +pub struct OpaqueLeaf( + /// Raw bytes of the leaf type encoded in its compact form. + /// + /// NOTE it DOES NOT include length prefix (like `Vec` encoding would). + #[cfg_attr(feature = "std", serde(with = "sp_core::bytes"))] + pub Vec, +); + +impl OpaqueLeaf { + /// Convert a concrete MMR leaf into an opaque type. + pub fn from_leaf(leaf: &T) -> Self { + let encoded_leaf = leaf.using_encoded(|d| d.to_vec(), true); + OpaqueLeaf::from_encoded_leaf(encoded_leaf) + } + + /// Create a `OpaqueLeaf` given raw bytes of compact-encoded leaf. + pub fn from_encoded_leaf(encoded_leaf: Vec) -> Self { + OpaqueLeaf(encoded_leaf) + } + + /// Attempt to decode the leaf into expected concrete type. + pub fn try_decode(&self) -> Option { + codec::Decode::decode(&mut &*self.0).ok() + } +} + +impl FullLeaf for OpaqueLeaf { + fn using_encoded R>(&self, f: F, _compact: bool) -> R { + f(&self.0) + } +} + +/// A type-safe wrapper for the concrete leaf type. +/// +/// This structure serves merely to avoid passing raw `Vec` around. +/// It must be `Vec`-encoding compatible. +/// +/// It is different from [`OpaqueLeaf`], because it does implement `Codec` +/// and the encoding has to match raw `Vec` encoding. +#[derive(codec::Encode, codec::Decode, RuntimeDebug, PartialEq, Eq)] +pub struct EncodableOpaqueLeaf(pub Vec); + +impl EncodableOpaqueLeaf { + /// Convert a concrete leaf into encodable opaque version. + pub fn from_leaf(leaf: &T) -> Self { + let opaque = OpaqueLeaf::from_leaf(leaf); + Self::from_opaque_leaf(opaque) + } + + /// Given an opaque leaf, make it encodable. + pub fn from_opaque_leaf(opaque: OpaqueLeaf) -> Self { + Self(opaque.0) + } + + /// Try to convert into a [OpaqueLeaf]. + pub fn into_opaque_leaf(self) -> OpaqueLeaf { + // wrap into `OpaqueLeaf` type + OpaqueLeaf::from_encoded_leaf(self.0) + } +} + /// An element representing either full data or it's hash. /// /// See [Compact] to see how it may be used in practice to reduce the size @@ -281,17 +351,6 @@ impl_leaf_data_for_tuple!(A:0, B:1, C:2); impl_leaf_data_for_tuple!(A:0, B:1, C:2, D:3); impl_leaf_data_for_tuple!(A:0, B:1, C:2, D:3, E:4); -/// A MMR proof data for one of the leaves. -#[derive(codec::Encode, codec::Decode, RuntimeDebug, Clone, PartialEq, Eq)] -pub struct Proof { - /// The index of the leaf the proof is for. - pub leaf_index: LeafIndex, - /// Number of leaves in MMR, when the proof was generated. - pub leaf_count: NodeIndex, - /// Proof elements (hashes of siblings of inner nodes on the path to the leaf). - pub items: Vec, -} - /// Merkle Mountain Range operation error. #[derive(RuntimeDebug, codec::Encode, codec::Decode, PartialEq, Eq)] pub enum Error { @@ -334,80 +393,6 @@ impl Error { } } -/// A helper type to allow using arbitrary SCALE-encoded leaf data in the RuntimeApi. -/// -/// The point is to be able to verify MMR proofs from external MMRs, where we don't -/// know the exact leaf type, but it's enough for us to have it SCALE-encoded. -/// -/// Note the leaf type should be encoded in its compact form when passed through this type. -/// See [FullLeaf] documentation for details. -/// -/// This type does not implement SCALE encoding/decoding on purpose to avoid confusion, -/// it would have to be SCALE-compatible with the concrete leaf type, but due to SCALE limitations -/// it's not possible to know how many bytes the encoding of concrete leaf type uses. -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] -#[derive(RuntimeDebug, Clone, PartialEq)] -pub struct OpaqueLeaf( - /// Raw bytes of the leaf type encoded in its compact form. - /// - /// NOTE it DOES NOT include length prefix (like `Vec` encoding would). - #[cfg_attr(feature = "std", serde(with = "sp_core::bytes"))] - pub Vec, -); - -impl OpaqueLeaf { - /// Convert a concrete MMR leaf into an opaque type. - pub fn from_leaf(leaf: &T) -> Self { - let encoded_leaf = leaf.using_encoded(|d| d.to_vec(), true); - OpaqueLeaf::from_encoded_leaf(encoded_leaf) - } - - /// Create a `OpaqueLeaf` given raw bytes of compact-encoded leaf. - pub fn from_encoded_leaf(encoded_leaf: Vec) -> Self { - OpaqueLeaf(encoded_leaf) - } - - /// Attempt to decode the leaf into expected concrete type. - pub fn try_decode(&self) -> Option { - codec::Decode::decode(&mut &*self.0).ok() - } -} - -impl FullLeaf for OpaqueLeaf { - fn using_encoded R>(&self, f: F, _compact: bool) -> R { - f(&self.0) - } -} - -/// A type-safe wrapper for the concrete leaf type. -/// -/// This structure serves merely to avoid passing raw `Vec` around. -/// It must be `Vec`-encoding compatible. -/// -/// It is different from [`OpaqueLeaf`], because it does implement `Codec` -/// and the encoding has to match raw `Vec` encoding. -#[derive(codec::Encode, codec::Decode, RuntimeDebug, PartialEq, Eq)] -pub struct EncodableOpaqueLeaf(pub Vec); - -impl EncodableOpaqueLeaf { - /// Convert a concrete leaf into encodable opaque version. - pub fn from_leaf(leaf: &T) -> Self { - let opaque = OpaqueLeaf::from_leaf(leaf); - Self::from_opaque_leaf(opaque) - } - - /// Given an opaque leaf, make it encodable. - pub fn from_opaque_leaf(opaque: OpaqueLeaf) -> Self { - Self(opaque.0) - } - - /// Try to convert into a [OpaqueLeaf]. - pub fn into_opaque_leaf(self) -> OpaqueLeaf { - // wrap into `OpaqueLeaf` type - OpaqueLeaf::from_encoded_leaf(self.0) - } -} - sp_api::decl_runtime_apis! { /// API to interact with MMR pallet. pub trait MmrApi { @@ -429,6 +414,9 @@ sp_api::decl_runtime_apis! { /// The leaf data is expected to be encoded in it's compact form. fn verify_proof_stateless(root: Hash, leaf: EncodableOpaqueLeaf, proof: Proof) -> Result<(), Error>; + + /// Return the on-chain MMR root hash. + fn mmr_root() -> Result; } } From e3d7aa95837c4e0f6710ba161bc4e5145371830f Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Wed, 13 Apr 2022 13:55:14 +0200 Subject: [PATCH 117/484] Fix WASM block producer panic (#11206) * Box events Signed-off-by: Oliver Tale-Yazdi * Fix tests Signed-off-by: Oliver Tale-Yazdi * Revert "Box events" This reverts commit 9fb1887cd23eb272844d63640b0b2d9ba3e549a1. * Revert "Fix tests" This reverts commit 981c50f23a7c514c9527299734bc6bc5b77a817f. * Use simpler approach Signed-off-by: Oliver Tale-Yazdi * Update doc Signed-off-by: Oliver Tale-Yazdi --- frame/system/src/lib.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/frame/system/src/lib.rs b/frame/system/src/lib.rs index 9b30e7b452276..0eefc23a6b5d6 100644 --- a/frame/system/src/lib.rs +++ b/frame/system/src/lib.rs @@ -591,11 +591,14 @@ pub mod pallet { /// Events deposited for the current block. /// - /// NOTE: This storage item is explicitly unbounded since it is never intended to be read - /// from within the runtime. + /// NOTE: The item is unbound and should therefore never be read on chain. + /// It could otherwise inflate the PoV size of a block. + /// + /// Events have a large in-memory size. Box the events to not go out-of-memory + /// just in case someone still reads them from within the runtime. #[pallet::storage] pub(super) type Events = - StorageValue<_, Vec>, ValueQuery>; + StorageValue<_, Vec>>, ValueQuery>; /// The number of events in the `Events` list. #[pallet::storage] @@ -1213,7 +1216,7 @@ impl Pallet { old_event_count }; - Events::::append(&event); + Events::::append(event); for topic in topics { >::append(topic, &(block_number, event_idx)); @@ -1380,14 +1383,16 @@ impl Pallet { /// items for any behavior like this. #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] pub fn events() -> Vec> { - Self::read_events_no_consensus() + // Dereferencing the events here is fine since we are not in the + // memory-restricted runtime. + Self::read_events_no_consensus().into_iter().map(|e| *e).collect() } /// Get the current events deposited by the runtime. /// /// Should only be called if you know what you are doing and outside of the runtime block /// execution else it can have a large impact on the PoV size of a block. - pub fn read_events_no_consensus() -> Vec> { + pub fn read_events_no_consensus() -> Vec>> { Events::::get() } From 0a8272549e7b3ca60f998a3ad28cb2d8b917df92 Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Wed, 13 Apr 2022 14:22:25 +0200 Subject: [PATCH 118/484] [ci] fix publish-docker-substrate job (#11218) --- .gitlab-ci.yml | 48 ++++++++++++++++++------------------------------ 1 file changed, 18 insertions(+), 30 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 029c486365088..62684ffebd987 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -41,6 +41,13 @@ variables: &default-vars CI_IMAGE: "paritytech/ci-linux:production" default: + retry: + max: 2 + when: + - runner_system_failure + - unknown_failure + - api_failure + interruptible: true cache: {} .collect-artifacts: &collect-artifacts @@ -60,13 +67,6 @@ default: - artifacts/ .kubernetes-env: &kubernetes-env - retry: - max: 2 - when: - - runner_system_failure - - unknown_failure - - api_failure - interruptible: true tags: - kubernetes-parity-build @@ -81,13 +81,6 @@ default: image: "${CI_IMAGE}" before_script: - *rust-info-script - retry: - max: 2 - when: - - runner_system_failure - - unknown_failure - - api_failure - interruptible: true tags: - linux-docker @@ -170,21 +163,6 @@ default: | tee ./artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA/::trie::read::small.json' - sccache -s -.build-linux-substrate-script: &build-linux-substrate-script - - WASM_BUILD_NO_COLOR=1 time cargo build --release --verbose - - mv ./target/release/substrate ./artifacts/substrate/. - - echo -n "Substrate version = " - - if [ "${CI_COMMIT_TAG}" ]; then - echo "${CI_COMMIT_TAG}" | tee ./artifacts/substrate/VERSION; - else - ./artifacts/substrate/substrate --version | - sed -n -E 's/^substrate ([0-9.]+.*-[0-9a-f]{7,13})-.*$/\1/p' | - tee ./artifacts/substrate/VERSION; - fi - - sha256sum ./artifacts/substrate/substrate | tee ./artifacts/substrate/substrate.sha256 - - cp -r ./scripts/ci/docker/substrate.Dockerfile ./artifacts/substrate/ - - sccache -s - #### stage: .pre @@ -525,7 +503,17 @@ build-linux-substrate: before_script: - mkdir -p ./artifacts/substrate/ script: - - *build-linux-substrate-script + - WASM_BUILD_NO_COLOR=1 time cargo build --release --verbose + - mv ./target/release/substrate ./artifacts/substrate/. + - echo -n "Substrate version = " + - if [ "${CI_COMMIT_TAG}" ]; then + echo "${CI_COMMIT_TAG}" | tee ./artifacts/substrate/VERSION; + else + ./artifacts/substrate/substrate --version | + cut -d ' ' -f 2 | tee ./artifacts/substrate/VERSION; + fi + - sha256sum ./artifacts/substrate/substrate | tee ./artifacts/substrate/substrate.sha256 + - cp -r ./scripts/ci/docker/substrate.Dockerfile ./artifacts/substrate/ - printf '\n# building node-template\n\n' - ./scripts/ci/node-template-release.sh ./artifacts/substrate/substrate-node-template.tar.gz From 938070c4ddc954e820f6edbff3d2c557ca85d9e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Wed, 13 Apr 2022 16:53:35 +0200 Subject: [PATCH 119/484] Document the chain spec format (#11208) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Document the chain spec format * Update client/chain-spec/src/lib.rs Co-authored-by: Pierre Krieger * Update client/chain-spec/src/lib.rs Co-authored-by: Pierre Krieger * Apply suggestions from code review Co-authored-by: Sacha Lansky * Update client/chain-spec/src/lib.rs Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com> * Update client/chain-spec/src/lib.rs Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com> * Mention extensions Co-authored-by: Pierre Krieger Co-authored-by: Sacha Lansky Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com> --- client/chain-spec/src/lib.rs | 90 +++++++++++++++++++++++++++++++----- 1 file changed, 79 insertions(+), 11 deletions(-) diff --git a/client/chain-spec/src/lib.rs b/client/chain-spec/src/lib.rs index eb72592b54a99..73d3e1af15492 100644 --- a/client/chain-spec/src/lib.rs +++ b/client/chain-spec/src/lib.rs @@ -53,19 +53,19 @@ //! //! #[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ChainSpecGroup)] //! pub struct ClientParams { -//! max_block_size: usize, -//! max_extrinsic_size: usize, +//! max_block_size: usize, +//! max_extrinsic_size: usize, //! } //! //! #[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ChainSpecGroup)] //! pub struct PoolParams { -//! max_transaction_size: usize, +//! max_transaction_size: usize, //! } //! //! #[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ChainSpecGroup, ChainSpecExtension)] //! pub struct Extension { -//! pub client: ClientParams, -//! pub pool: PoolParams, +//! pub client: ClientParams, +//! pub pool: PoolParams, //! } //! //! pub type BlockNumber = u64; @@ -88,24 +88,92 @@ //! //! #[derive(Clone, Debug, Serialize, Deserialize, ChainSpecGroup)] //! pub struct ClientParams { -//! max_block_size: usize, -//! max_extrinsic_size: usize, +//! max_block_size: usize, +//! max_extrinsic_size: usize, //! } //! //! #[derive(Clone, Debug, Serialize, Deserialize, ChainSpecGroup)] //! pub struct PoolParams { -//! max_transaction_size: usize, +//! max_transaction_size: usize, //! } //! //! #[derive(Clone, Debug, Serialize, Deserialize, ChainSpecExtension)] //! pub struct Extension { -//! pub client: ClientParams, -//! #[forks] -//! pub pool: Forks, +//! pub client: ClientParams, +//! #[forks] +//! pub pool: Forks, //! } //! //! pub type MyChainSpec = GenericChainSpec; //! ``` +//! +//! # Substrate chain specification format +//! +//! The Substrate chain specification is a `json` file that describes the basics of a chain. Most +//! importantly it lays out the genesis storage which leads to the genesis hash. The default +//! Substrate chain specification format is the following: +//! +//! ```json +//! // The human readable name of the chain. +//! "name": "Flaming Fir", +//! // The id of the chain. +//! "id": "flamingfir9", +//! // The chain type of this chain. +//! // Possible values are `Live`, `Development`, `Local`. +//! "chainType": "Live", +//! // A list of multi addresses that belong to boot nodes of the chain. +//! "bootNodes": [ +//! "/dns/0.flamingfir.paritytech.net/tcp/30333/p2p/12D3KooWLK2gMLhWsYJzjW3q35zAs9FDDVqfqVfVuskiGZGRSMvR", +//! ], +//! // Optional list of "multi address, verbosity" of telemetry endpoints. +//! // The verbosity goes from `0` to `9`. With `0` being the mode with the lowest verbosity. +//! "telemetryEndpoints": [ +//! [ +//! "/dns/telemetry.polkadot.io/tcp/443/x-parity-wss/%2Fsubmit%2F", +//! 0 +//! ] +//! ], +//! // Optional networking protocol id that identifies the chain. +//! "protocolId": "fir9", +//! // Optional fork id. Should most likely be left empty. +//! // Can be used to signal a fork on the network level when two chains have the +//! // same genesis hash. +//! "forkId": "random_fork", +//! // Custom properties. +//! "properties": { +//! "tokenDecimals": 15, +//! "tokenSymbol": "FIR" +//! }, +//! // Deprecated field. Should be ignored. +//! "consensusEngine": null, +//! // The genesis declaration of the chain. +//! // +//! // `runtime`, `raw`, `stateRootHash` denote the type of the genesis declaration. +//! // +//! // These declarations are in the following formats: +//! // - `runtime` is a `json` object that can be parsed by a compatible `GenesisConfig`. This +//! // `GenesisConfig` is declared by a runtime and opaque to the node. +//! // - `raw` is a `json` object with two fields `top` and `children_default`. Each of these +//! // fields is a map of `key => value`. These key/value pairs represent the genesis storage. +//! // - `stateRootHash` is a single hex encoded hash that represents the genesis hash. The hash +//! // type depends on the hash used by the chain. +//! // +//! "genesis": { "runtime": {} }, +//! /// Optional map of `block_number` to `wasm_code`. +//! /// +//! /// The given `wasm_code` will be used to substitute the on-chain wasm code starting with the +//! /// given block number until the `spec_version` on-chain changes. The given `wasm_code` should +//! /// be as close as possible to the on-chain wasm code. A substitute should be used to fix a bug +//! /// that can not be fixed with a runtime upgrade, if for example the runtime is constantly +//! /// panicking. Introducing new runtime apis isn't supported, because the node +//! /// will read the runtime version from the on-chain wasm code. Use this functionality only when +//! /// there is no other way around it and only patch the problematic bug, the rest should be done +//! /// with a on-chain runtime upgrade. +//! "codeSubstitutes": [], +//! ``` +//! +//! The chain spec can be extended with other fields that are opaque to the default chain spec. +//! Specific node implementations will need to be able to deserialize these extensions. mod chain_spec; mod extension; From fb53020da4553ced550481a0cfbb11451311c634 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Wed, 13 Apr 2022 17:01:36 +0200 Subject: [PATCH 120/484] authority-discovery: Fix flaky test (#11219) It can happen that `+ 1` overflows `p.signature[1]` ;D (I have seen this in the CI). --- client/authority-discovery/src/worker/tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/authority-discovery/src/worker/tests.rs b/client/authority-discovery/src/worker/tests.rs index 904c674d269bc..a1a699bc30dd2 100644 --- a/client/authority-discovery/src/worker/tests.rs +++ b/client/authority-discovery/src/worker/tests.rs @@ -625,7 +625,7 @@ fn reject_address_with_invalid_peer_signature() { )); // tamper with the signature let mut record = schema::SignedAuthorityRecord::decode(kv_pairs[0].1.as_slice()).unwrap(); - record.peer_signature.as_mut().map(|p| p.signature[1] += 1); + record.peer_signature.as_mut().map(|p| p.signature[1] = !p.signature[1]); record.encode(&mut kv_pairs[0].1).unwrap(); let cached_remote_addresses = tester.process_value_found(false, kv_pairs); From 57c51157ca85872d04e709efdc93c18c284b7f8e Mon Sep 17 00:00:00 2001 From: Liu-Cheng Xu Date: Thu, 14 Apr 2022 17:18:54 +0800 Subject: [PATCH 121/484] Make the generic of CliConfiguration explicit (#11223) Othewise the compiler will complain if someone passes something that implements `CliConfiguration` but `DVC` is not the default `()`. --- client/cli/src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/cli/src/lib.rs b/client/cli/src/lib.rs index 849a2d2cceb1f..244f6e167f9d5 100644 --- a/client/cli/src/lib.rs +++ b/client/cli/src/lib.rs @@ -196,7 +196,10 @@ pub trait SubstrateCli: Sized { /// Create a runner for the command provided in argument. This will create a Configuration and /// a tokio runtime - fn create_runner(&self, command: &T) -> error::Result> { + fn create_runner, DVC: DefaultConfigurationValues>( + &self, + command: &T, + ) -> error::Result> { let tokio_runtime = build_runtime()?; let config = command.create_configuration(self, tokio_runtime.handle().clone())?; From cb0a998eea7d352a436803bd625910a4f52dfa61 Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Thu, 14 Apr 2022 16:07:36 +0300 Subject: [PATCH 122/484] Update wasmtime to 0.35.3 (#11058) * Update wasmtime to 0.35.2 and default `zstd` features in all crates * Update wasmtime to 0.35.3 --- Cargo.lock | 163 +++++++++++--------- client/executor/wasmtime/Cargo.toml | 2 +- frame/state-trie-migration/Cargo.toml | 2 +- primitives/maybe-compressed-blob/Cargo.toml | 2 +- primitives/runtime/Cargo.toml | 2 +- primitives/wasm-interface/Cargo.toml | 2 +- utils/frame/try-runtime/cli/Cargo.toml | 2 +- 7 files changed, 93 insertions(+), 82 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5731874d8ca81..440d549af1a35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1159,11 +1159,11 @@ dependencies = [ [[package]] name = "cranelift-bforest" -version = "0.80.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9516ba6b2ba47b4cbf63b713f75b432fafa0a0e0464ec8381ec76e6efe931ab3" +checksum = "38faa2a16616c8e78a18d37b4726b98bfd2de192f2fdc8a39ddf568a408a0f75" dependencies = [ - "cranelift-entity 0.80.0", + "cranelift-entity 0.82.3", ] [[package]] @@ -1185,17 +1185,17 @@ dependencies = [ [[package]] name = "cranelift-codegen" -version = "0.80.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "489e5d0081f7edff6be12d71282a8bf387b5df64d5592454b75d662397f2d642" +checksum = "26f192472a3ba23860afd07d2b0217dc628f21fcc72617aa1336d98e1671f33b" dependencies = [ - "cranelift-bforest 0.80.0", - "cranelift-codegen-meta 0.80.0", - "cranelift-codegen-shared 0.80.0", - "cranelift-entity 0.80.0", + "cranelift-bforest 0.82.3", + "cranelift-codegen-meta 0.82.3", + "cranelift-codegen-shared 0.82.3", + "cranelift-entity 0.82.3", "gimli 0.26.1", "log 0.4.14", - "regalloc 0.0.33", + "regalloc 0.0.34", "smallvec 1.8.0", "target-lexicon", ] @@ -1212,11 +1212,11 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.80.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d36ee1140371bb0f69100e734b30400157a4adf7b86148dee8b0a438763ead48" +checksum = "0f32ddb89e9b89d3d9b36a5b7d7ea3261c98235a76ac95ba46826b8ec40b1a24" dependencies = [ - "cranelift-codegen-shared 0.80.0", + "cranelift-codegen-shared 0.82.3", ] [[package]] @@ -1227,9 +1227,9 @@ checksum = "9dabb5fe66e04d4652e434195b45ae65b5c8172d520247b8f66d8df42b2b45dc" [[package]] name = "cranelift-codegen-shared" -version = "0.80.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "981da52d8f746af1feb96290c83977ff8d41071a7499e991d8abae0d4869f564" +checksum = "01fd0d9f288cc1b42d9333b7a776b17e278fc888c28e6a0f09b5573d45a150bc" [[package]] name = "cranelift-entity" @@ -1239,9 +1239,9 @@ checksum = "3329733e4d4b8e91c809efcaa4faee80bf66f20164e3dd16d707346bd3494799" [[package]] name = "cranelift-entity" -version = "0.80.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2906740053dd3bcf95ce53df0fd9b5649c68ae4bd9adada92b406f059eae461" +checksum = "9e3bfe172b83167604601faf9dc60453e0d0a93415b57a9c4d1a7ae6849185cf" dependencies = [ "serde", ] @@ -1260,11 +1260,11 @@ dependencies = [ [[package]] name = "cranelift-frontend" -version = "0.80.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7cb156de1097f567d46bf57a0cd720a72c3e15e1a2bd8b1041ba2fc894471b7" +checksum = "a006e3e32d80ce0e4ba7f1f9ddf66066d052a8c884a110b91d05404d6ce26dce" dependencies = [ - "cranelift-codegen 0.80.0", + "cranelift-codegen 0.82.3", "log 0.4.14", "smallvec 1.8.0", "target-lexicon", @@ -1272,28 +1272,28 @@ dependencies = [ [[package]] name = "cranelift-native" -version = "0.80.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "166028ca0343a6ee7bddac0e70084e142b23f99c701bd6f6ea9123afac1a7a46" +checksum = "501241b0cdf903412ec9075385ac9f2b1eb18a89044d1538e97fab603231f70c" dependencies = [ - "cranelift-codegen 0.80.0", + "cranelift-codegen 0.82.3", "libc", "target-lexicon", ] [[package]] name = "cranelift-wasm" -version = "0.80.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5012a1cde0c8b3898770b711490d803018ae9bec2d60674ba0e5b2058a874f80" +checksum = "16d9e4211bbc3268042a96dd4de5bd979cda22434991d035f5f8eacba987fad2" dependencies = [ - "cranelift-codegen 0.80.0", - "cranelift-entity 0.80.0", - "cranelift-frontend 0.80.0", + "cranelift-codegen 0.82.3", + "cranelift-entity 0.82.3", + "cranelift-frontend 0.82.3", "itertools", "log 0.4.14", "smallvec 1.8.0", - "wasmparser 0.81.0", + "wasmparser 0.83.0", "wasmtime-types", ] @@ -3191,12 +3191,9 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "0.4.4" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ef6787e7f0faedc040f95716bdd0e62bcfcf4ba93da053b62dea2691c13864" -dependencies = [ - "winapi 0.3.9", -] +checksum = "ec58677acfea8a15352d42fc87d11d63596ade9239e0a7c9352914417515dbe6" [[package]] name = "iovec" @@ -4228,9 +4225,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.0.36" +version = "0.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a261afc61b7a5e323933b402ca6a1765183687c614789b1e4db7762ed4230bca" +checksum = "5284f00d480e1c39af34e72f8ad60b94f47007e3481cd3b731c1d67190ddc7b7" [[package]] name = "lite-json" @@ -5319,9 +5316,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" +checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" [[package]] name = "oorandom" @@ -7702,9 +7699,9 @@ dependencies = [ [[package]] name = "regalloc" -version = "0.0.33" +version = "0.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d808cff91dfca7b239d40b972ba628add94892b1d9e19a842aedc5cfae8ab1a" +checksum = "62446b1d3ebf980bdc68837700af1d77b37bc430e524bf95319c6eada2a4cc02" dependencies = [ "log 0.4.14", "rustc-hash", @@ -7913,9 +7910,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.31.3" +version = "0.33.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2dcfc2778a90e38f56a708bfc90572422e11d6c7ee233d053d1f782cf9df6d2" +checksum = "03627528abcc4a365554d32a9f3bbf67f7694c102cfeda792dc86a2d6057cc85" dependencies = [ "bitflags", "errno", @@ -12115,34 +12112,33 @@ checksum = "52144d4c78e5cf8b055ceab8e5fa22814ce4315d6002ad32cfd914f37c12fd65" [[package]] name = "wasmparser" -version = "0.81.0" +version = "0.83.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98930446519f63d00a836efdc22f67766ceae8dbcc1571379f2bcabc6b2b9abc" +checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" [[package]] name = "wasmtime" -version = "0.33.0" +version = "0.35.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414be1bc5ca12e755ffd3ff7acc3a6d1979922f8237fc34068b2156cebcc3270" +checksum = "21ffb4705016d5ca91e18a72ed6822dab50e6d5ddd7045461b17ef19071cdef1" dependencies = [ "anyhow", "backtrace", "bincode", "cfg-if 1.0.0", - "cpp_demangle", "indexmap", "lazy_static", "libc", "log 0.4.14", "object 0.27.1", + "once_cell", "paste 1.0.6", "psm", "rayon", "region 2.2.0", - "rustc-demangle", "serde", "target-lexicon", - "wasmparser 0.81.0", + "wasmparser 0.83.0", "wasmtime-cache", "wasmtime-cranelift", "wasmtime-environ", @@ -12153,9 +12149,9 @@ dependencies = [ [[package]] name = "wasmtime-cache" -version = "0.33.0" +version = "0.35.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9b4cd1949206fda9241faf8c460a7d797aa1692594d3dd6bc1cbfa57ee20d0" +checksum = "85c6ab24291fa7cb3a181f5669f6c72599b7ef781669759b45c7828c5999d0c0" dependencies = [ "anyhow", "base64 0.13.0", @@ -12173,14 +12169,14 @@ dependencies = [ [[package]] name = "wasmtime-cranelift" -version = "0.33.0" +version = "0.35.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4693d33725773615a4c9957e4aa731af57b27dca579702d1d8ed5750760f1a9" +checksum = "f04c810078a491b7bc4866ebe045f714d2b95e6b539e1f64009a4a7606be11de" dependencies = [ "anyhow", - "cranelift-codegen 0.80.0", - "cranelift-entity 0.80.0", - "cranelift-frontend 0.80.0", + "cranelift-codegen 0.82.3", + "cranelift-entity 0.82.3", + "cranelift-frontend 0.82.3", "cranelift-native", "cranelift-wasm", "gimli 0.26.1", @@ -12189,18 +12185,18 @@ dependencies = [ "object 0.27.1", "target-lexicon", "thiserror", - "wasmparser 0.81.0", + "wasmparser 0.83.0", "wasmtime-environ", ] [[package]] name = "wasmtime-environ" -version = "0.33.0" +version = "0.35.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b17e47116a078b9770e6fb86cff8b9a660826623cebcfff251b047c8d8993ef" +checksum = "61448266ea164b1ac406363cdcfac81c7c44db4d94c7a81c8620ac6c5c6cdf59" dependencies = [ "anyhow", - "cranelift-entity 0.80.0", + "cranelift-entity 0.82.3", "gimli 0.26.1", "indexmap", "log 0.4.14", @@ -12209,44 +12205,58 @@ dependencies = [ "serde", "target-lexicon", "thiserror", - "wasmparser 0.81.0", + "wasmparser 0.83.0", "wasmtime-types", ] [[package]] name = "wasmtime-jit" -version = "0.33.0" +version = "0.35.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60ea5b380bdf92e32911400375aeefb900ac9d3f8e350bb6ba555a39315f2ee7" +checksum = "156b4623c6b0d4b8c24afb846c20525922f538ef464cc024abab7ea8de2109a2" dependencies = [ "addr2line", "anyhow", "bincode", "cfg-if 1.0.0", + "cpp_demangle", "gimli 0.26.1", + "log 0.4.14", "object 0.27.1", "region 2.2.0", + "rustc-demangle", "rustix", "serde", "target-lexicon", "thiserror", "wasmtime-environ", + "wasmtime-jit-debug", "wasmtime-runtime", "winapi 0.3.9", ] +[[package]] +name = "wasmtime-jit-debug" +version = "0.35.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5dc31f811760a6c76b2672c404866fd19b75e5fb3b0075a3e377a6846490654" +dependencies = [ + "lazy_static", + "object 0.27.1", + "rustix", +] + [[package]] name = "wasmtime-runtime" -version = "0.33.0" +version = "0.35.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abc7cd79937edd6e238b337608ebbcaf9c086a8457f01dfd598324f7fa56d81a" +checksum = "f907beaff69d4d920fa4688411ee4cc75c0f01859e424677f9e426e2ef749864" dependencies = [ "anyhow", "backtrace", "cc", "cfg-if 1.0.0", "indexmap", - "lazy_static", "libc", "log 0.4.14", "mach", @@ -12257,19 +12267,20 @@ dependencies = [ "rustix", "thiserror", "wasmtime-environ", + "wasmtime-jit-debug", "winapi 0.3.9", ] [[package]] name = "wasmtime-types" -version = "0.33.0" +version = "0.35.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9e5e51a461a2cf2b69e1fc48f325b17d78a8582816e18479e8ead58844b23f8" +checksum = "514ef0e5fd197b9609dc9eb74beba0c84d5a12b2417cbae55534633329ba4852" dependencies = [ - "cranelift-entity 0.80.0", + "cranelift-entity 0.82.3", "serde", "thiserror", - "wasmparser 0.81.0", + "wasmparser 0.83.0", ] [[package]] @@ -12566,18 +12577,18 @@ dependencies = [ [[package]] name = "zstd" -version = "0.9.0+zstd.1.5.0" +version = "0.10.0+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07749a5dc2cb6b36661290245e350f15ec3bbb304e493db54a1d354480522ccd" +checksum = "3b1365becbe415f3f0fcd024e2f7b45bacfb5bdd055f0dc113571394114e7bdd" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "4.1.1+zstd.1.5.0" +version = "4.1.4+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91c90f2c593b003603e5e0493c837088df4469da25aafff8bce42ba48caf079" +checksum = "2f7cd17c9af1a4d6c24beb1cc54b17e2ef7b593dc92f19e9d9acad8b182bbaee" dependencies = [ "libc", "zstd-sys", @@ -12585,9 +12596,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "1.6.1+zstd.1.5.0" +version = "1.6.3+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "615120c7a2431d16cf1cf979e7fc31ba7a5b5e5707b29c8a99e5dbf8a8392a33" +checksum = "fc49afa5c8d634e75761feda8c592051e7eeb4683ba827211eb0d731d3402ea8" dependencies = [ "cc", "libc", diff --git a/client/executor/wasmtime/Cargo.toml b/client/executor/wasmtime/Cargo.toml index f0155204f5442..233c2e7d8f098 100644 --- a/client/executor/wasmtime/Cargo.toml +++ b/client/executor/wasmtime/Cargo.toml @@ -23,7 +23,7 @@ sp-wasm-interface = { version = "6.0.0", path = "../../../primitives/wasm-interf sp-runtime-interface = { version = "6.0.0", path = "../../../primitives/runtime-interface" } sp-core = { version = "6.0.0", path = "../../../primitives/core" } sc-allocator = { version = "4.1.0-dev", path = "../../allocator" } -wasmtime = { version = "0.33.0", default-features = false, features = [ +wasmtime = { version = "0.35.3", default-features = false, features = [ "cache", "cranelift", "jitdump", diff --git a/frame/state-trie-migration/Cargo.toml b/frame/state-trie-migration/Cargo.toml index 762fd85f13b62..0124ba5b83283 100644 --- a/frame/state-trie-migration/Cargo.toml +++ b/frame/state-trie-migration/Cargo.toml @@ -30,7 +30,7 @@ frame-benchmarking = { default-features = false, path = "../benchmarking", optio serde = { version = "1.0.133", optional = true } thousands = { version = "0.2.0", optional = true } remote-externalities = { path = "../../utils/frame/remote-externalities", optional = true } -zstd = { version = "0.9.0", optional = true } +zstd = { version = "0.10.0", default-features = false, optional = true } [dev-dependencies] pallet-balances = { path = "../balances" } diff --git a/primitives/maybe-compressed-blob/Cargo.toml b/primitives/maybe-compressed-blob/Cargo.toml index d8814356df4bb..3baecaf8e79a8 100644 --- a/primitives/maybe-compressed-blob/Cargo.toml +++ b/primitives/maybe-compressed-blob/Cargo.toml @@ -12,4 +12,4 @@ readme = "README.md" [dependencies] thiserror = "1.0" -zstd = { version = "0.9.0", default-features = false } +zstd = { version = "0.10.0", default-features = false } diff --git a/primitives/runtime/Cargo.toml b/primitives/runtime/Cargo.toml index 22d2ea62ae45f..9e9b01ebea3d2 100644 --- a/primitives/runtime/Cargo.toml +++ b/primitives/runtime/Cargo.toml @@ -38,7 +38,7 @@ sp-state-machine = { version = "0.12.0", path = "../state-machine" } sp-api = { version = "4.0.0-dev", path = "../api" } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } -zstd = "0.9" +zstd = { version = "0.10.0", default-features = false } [features] bench = [] diff --git a/primitives/wasm-interface/Cargo.toml b/primitives/wasm-interface/Cargo.toml index 5fa0627490a22..775ab1c5b7060 100644 --- a/primitives/wasm-interface/Cargo.toml +++ b/primitives/wasm-interface/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] wasmi = { version = "0.9.1", optional = true } -wasmtime = { version = "0.33.0", optional = true, default-features = false } +wasmtime = { version = "0.35.3", optional = true, default-features = false } log = { version = "0.4.14", optional = true } impl-trait-for-tuples = "0.2.2" sp-std = { version = "4.0.0", path = "../std", default-features = false } diff --git a/utils/frame/try-runtime/cli/Cargo.toml b/utils/frame/try-runtime/cli/Cargo.toml index 3f0ee257d9756..abbbe882c2fac 100644 --- a/utils/frame/try-runtime/cli/Cargo.toml +++ b/utils/frame/try-runtime/cli/Cargo.toml @@ -17,7 +17,7 @@ clap = { version = "3.1.6", features = ["derive"] } log = "0.4.8" parity-scale-codec = "3.0.0" serde = "1.0.136" -zstd = "0.9.0" +zstd = { version = "0.10.0", default-features = false } sc-service = { version = "0.10.0-dev", default-features = false, path = "../../../../client/service" } sc-cli = { version = "0.10.0-dev", path = "../../../../client/cli" } From 3e3a1874cc273c8e0ecbbc3a8cb149f1169d5f0b Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Thu, 14 Apr 2022 17:25:54 +0100 Subject: [PATCH 123/484] Rb staking pallet validator commission change event (#10827) * commission-changed-event-and-deposit * deposit_event fix * commission_changed_event_works * fmt * CommissionChanged -> ValidatorPrefsUpdated * event ValidatorPrefs * updated commet * fmt * Update frame/staking/src/pallet/mod.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Update frame/staking/src/pallet/mod.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * Update frame/staking/src/tests.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> --- frame/staking/src/pallet/mod.rs | 6 +++++- frame/staking/src/tests.rs | 10 ++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 498d861660961..1a8fd59d23987 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -628,6 +628,8 @@ pub mod pallet { Chilled(T::AccountId), /// The stakers' rewards are getting paid. \[era_index, validator_stash\] PayoutStarted(EraIndex, T::AccountId), + /// A validator has set their preferences. + ValidatorPrefsSet(T::AccountId, ValidatorPrefs), } #[pallet::error] @@ -1016,7 +1018,9 @@ pub mod pallet { } Self::do_remove_nominator(stash); - Self::do_add_validator(stash, prefs); + Self::do_add_validator(stash, prefs.clone()); + Self::deposit_event(Event::::ValidatorPrefsSet(ledger.stash, prefs)); + Ok(()) } diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 11dfe3e4777f8..7161418afc76e 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4598,11 +4598,21 @@ fn capped_stakers_works() { #[test] fn min_commission_works() { ExtBuilder::default().build_and_execute(|| { + // account 10 controls the stash from account 11 assert_ok!(Staking::validate( Origin::signed(10), ValidatorPrefs { commission: Perbill::from_percent(5), blocked: false } )); + // event emitted should be correct + assert_eq!( + *staking_events().last().unwrap(), + Event::ValidatorPrefsSet( + 11, + ValidatorPrefs { commission: Perbill::from_percent(5), blocked: false } + ) + ); + assert_ok!(Staking::set_staking_configs( Origin::root(), ConfigOp::Remove, From 9da2189b04d72b0619ab0b59f6132d0b7e9ddea6 Mon Sep 17 00:00:00 2001 From: stanly-johnson Date: Thu, 14 Apr 2022 23:41:42 +0400 Subject: [PATCH 124/484] pallet_assets : Remove event for no_op (#11023) * remove no_op event * ensure no transferred event emitted Co-authored-by: Oliver Tale-Yazdi --- frame/assets/src/functions.rs | 8 +------- frame/assets/src/tests.rs | 8 ++------ 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/frame/assets/src/functions.rs b/frame/assets/src/functions.rs index 48a86ca3cfa04..0be79619e0967 100644 --- a/frame/assets/src/functions.rs +++ b/frame/assets/src/functions.rs @@ -529,14 +529,8 @@ impl, I: 'static> Pallet { maybe_need_admin: Option, f: TransferFlags, ) -> Result<(T::Balance, Option), DispatchError> { - // Early exist if no-op. + // Early exit if no-op. if amount.is_zero() { - Self::deposit_event(Event::Transferred { - asset_id: id, - from: source.clone(), - to: dest.clone(), - amount, - }); return Ok((amount, None)) } diff --git a/frame/assets/src/tests.rs b/frame/assets/src/tests.rs index 7430b742e7d2a..db0d6a5f212f9 100644 --- a/frame/assets/src/tests.rs +++ b/frame/assets/src/tests.rs @@ -620,12 +620,8 @@ fn transferring_less_than_one_unit_is_fine() { assert_ok!(Assets::mint(Origin::signed(1), 0, 1, 100)); assert_eq!(Assets::balance(0, 1), 100); assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, 0)); - System::assert_last_event(mock::Event::Assets(crate::Event::Transferred { - asset_id: 0, - from: 1, - to: 2, - amount: 0, - })); + // `ForceCreated` and `Issued` but no `Transferred` event. + assert_eq!(System::events().len(), 2); }); } From ae30c1dc1230fe1a51a2d0f84f9f3700ca40eab3 Mon Sep 17 00:00:00 2001 From: Georges Date: Fri, 15 Apr 2022 11:15:01 +0100 Subject: [PATCH 125/484] Adding benchmarking for new `frame_election_provider_support` (#11149) * First stab at adding benchmarking for `election-provider-support` onchain * Adding `BoundedPhragMMS` and fixing stuff * Fixing node runtime * Fixing tests * Finalising all benchmarking stuff * better comments * Better benchmarking config * Better `WeightInfo` and benchmarking * Fixing tests * Adding some documentation * Fixing some typos * Incorporating review feedback * cleanup of rustdocs * rustdoc changes * changes after code review * Fixing some errors. * Fixing dependencies post merge * Bringing back `UnboundedExecution` * Better rustdoc and naming * Cargo.toml formatting --- Cargo.lock | 14 +++ Cargo.toml | 1 + bin/node/runtime/Cargo.toml | 2 + bin/node/runtime/src/lib.rs | 11 +- frame/babe/src/mock.rs | 3 +- .../election-provider-multi-phase/Cargo.toml | 10 +- .../election-provider-multi-phase/src/mock.rs | 5 +- .../benchmarking/Cargo.toml | 38 ++++++ .../benchmarking/src/lib.rs | 91 ++++++++++++++ frame/election-provider-support/src/lib.rs | 19 ++- .../election-provider-support/src/onchain.rs | 115 ++++++++++++------ .../election-provider-support/src/weights.rs | 94 ++++++++++++++ frame/grandpa/src/mock.rs | 5 +- frame/offences/benchmarking/src/mock.rs | 3 +- frame/session/benchmarking/src/mock.rs | 3 +- frame/staking/src/mock.rs | 3 +- primitives/npos-elections/src/balancing.rs | 2 +- primitives/npos-elections/src/phragmms.rs | 2 +- 18 files changed, 366 insertions(+), 55 deletions(-) create mode 100644 frame/election-provider-support/benchmarking/Cargo.toml create mode 100644 frame/election-provider-support/benchmarking/src/lib.rs create mode 100644 frame/election-provider-support/src/weights.rs diff --git a/Cargo.lock b/Cargo.lock index 440d549af1a35..2fa738118247e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4998,6 +4998,7 @@ dependencies = [ "pallet-conviction-voting", "pallet-democracy", "pallet-election-provider-multi-phase", + "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", "pallet-gilt", "pallet-grandpa", @@ -5851,6 +5852,7 @@ dependencies = [ "frame-system", "log 0.4.14", "pallet-balances", + "pallet-election-provider-support-benchmarking", "parity-scale-codec", "parking_lot 0.12.0", "rand 0.7.3", @@ -5866,6 +5868,18 @@ dependencies = [ "strum", ] +[[package]] +name = "pallet-election-provider-support-benchmarking" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-election-provider-support", + "frame-system", + "parity-scale-codec", + "sp-npos-elections", + "sp-runtime", +] + [[package]] name = "pallet-elections-phragmen" version = "5.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index 45df7258ef253..c281913cd55ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -87,6 +87,7 @@ members = [ "frame/try-runtime", "frame/election-provider-multi-phase", "frame/election-provider-support", + "frame/election-provider-support/benchmarking", "frame/election-provider-support/solution-type", "frame/election-provider-support/solution-type/fuzzer", "frame/examples/basic", diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 686508b47dba1..41b2402d33a53 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -65,6 +65,7 @@ pallet-contracts-rpc-runtime-api = { version = "4.0.0-dev", default-features = f pallet-conviction-voting = { version = "4.0.0-dev", default-features = false, path = "../../../frame/conviction-voting" } pallet-democracy = { version = "4.0.0-dev", default-features = false, path = "../../../frame/democracy" } pallet-election-provider-multi-phase = { version = "4.0.0-dev", default-features = false, path = "../../../frame/election-provider-multi-phase" } +pallet-election-provider-support-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../../frame/election-provider-support/benchmarking", optional = true } pallet-elections-phragmen = { version = "5.0.0-dev", default-features = false, path = "../../../frame/elections-phragmen" } pallet-gilt = { version = "4.0.0-dev", default-features = false, path = "../../../frame/gilt" } pallet-grandpa = { version = "4.0.0-dev", default-features = false, path = "../../../frame/grandpa" } @@ -196,6 +197,7 @@ runtime-benchmarks = [ "pallet-conviction-voting/runtime-benchmarks", "pallet-democracy/runtime-benchmarks", "pallet-election-provider-multi-phase/runtime-benchmarks", + "pallet-election-provider-support-benchmarking/runtime-benchmarks", "pallet-elections-phragmen/runtime-benchmarks", "pallet-gilt/runtime-benchmarks", "pallet-grandpa/runtime-benchmarks", diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index f422d1ffc45b8..f37345014f3a1 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -643,17 +643,18 @@ impl Get> for OffchainRandomBalancing { } pub struct OnChainSeqPhragmen; -impl onchain::ExecutionConfig for OnChainSeqPhragmen { +impl onchain::Config for OnChainSeqPhragmen { type System = Runtime; type Solver = SequentialPhragmen< AccountId, pallet_election_provider_multi_phase::SolutionAccuracyOf, >; type DataProvider = ::DataProvider; + type WeightInfo = frame_election_provider_support::weights::SubstrateWeight; } -impl onchain::BoundedExecutionConfig for OnChainSeqPhragmen { - type VotersBound = ConstU32<20_000>; +impl onchain::BoundedConfig for OnChainSeqPhragmen { + type VotersBound = MaxElectingVoters; type TargetsBound = ConstU32<2_000>; } @@ -1531,6 +1532,7 @@ mod benches { [pallet_contracts, Contracts] [pallet_democracy, Democracy] [pallet_election_provider_multi_phase, ElectionProviderMultiPhase] + [pallet_election_provider_support_benchmarking, EPSBench::] [pallet_elections_phragmen, Elections] [pallet_gilt, Gilt] [pallet_grandpa, Grandpa] @@ -1851,6 +1853,7 @@ impl_runtime_apis! { // which is why we need these two lines below. use pallet_session_benchmarking::Pallet as SessionBench; use pallet_offences_benchmarking::Pallet as OffencesBench; + use pallet_election_provider_support_benchmarking::Pallet as EPSBench; use frame_system_benchmarking::Pallet as SystemBench; use baseline::Pallet as BaselineBench; @@ -1872,11 +1875,13 @@ impl_runtime_apis! { // which is why we need these two lines below. use pallet_session_benchmarking::Pallet as SessionBench; use pallet_offences_benchmarking::Pallet as OffencesBench; + use pallet_election_provider_support_benchmarking::Pallet as EPSBench; use frame_system_benchmarking::Pallet as SystemBench; use baseline::Pallet as BaselineBench; impl pallet_session_benchmarking::Config for Runtime {} impl pallet_offences_benchmarking::Config for Runtime {} + impl pallet_election_provider_support_benchmarking::Config for Runtime {} impl frame_system_benchmarking::Config for Runtime {} impl baseline::Config for Runtime {} diff --git a/frame/babe/src/mock.rs b/frame/babe/src/mock.rs index 37d8e9e37a5f4..15f53e7da0823 100644 --- a/frame/babe/src/mock.rs +++ b/frame/babe/src/mock.rs @@ -173,10 +173,11 @@ parameter_types! { } pub struct OnChainSeqPhragmen; -impl onchain::ExecutionConfig for OnChainSeqPhragmen { +impl onchain::Config for OnChainSeqPhragmen { type System = Test; type Solver = SequentialPhragmen; type DataProvider = Staking; + type WeightInfo = (); } impl pallet_staking::Config for Test { diff --git a/frame/election-provider-multi-phase/Cargo.toml b/frame/election-provider-multi-phase/Cargo.toml index 25f98d965d86b..48134966a9253 100644 --- a/frame/election-provider-multi-phase/Cargo.toml +++ b/frame/election-provider-multi-phase/Cargo.toml @@ -17,7 +17,9 @@ static_assertions = "1.1.0" codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.0.1", default-features = false, features = [ + "derive", +] } log = { version = "0.4.14", default-features = false } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } @@ -33,11 +35,14 @@ frame-election-provider-support = { version = "4.0.0-dev", default-features = fa # Optional imports for benchmarking frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +pallet-election-provider-support-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../election-provider-support/benchmarking", optional = true } rand = { version = "0.7.3", default-features = false, optional = true, features = [ "alloc", "small_rng", ] } -strum = { optional = true, default-features = false, version = "0.23.0", features = ["derive"] } +strum = { optional = true, default-features = false, version = "0.23.0", features = [ + "derive", +] } [dev-dependencies] parking_lot = "0.12.0" @@ -46,7 +51,6 @@ sp-core = { version = "6.0.0", default-features = false, path = "../../primitive sp-io = { version = "6.0.0", path = "../../primitives/io" } sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../primitives/npos-elections" } sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } -frame-election-provider-support = { version = "4.0.0-dev", path = "../election-provider-support" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } frame-benchmarking = { version = "4.0.0-dev", path = "../benchmarking" } diff --git a/frame/election-provider-multi-phase/src/mock.rs b/frame/election-provider-multi-phase/src/mock.rs index d6f040363dba0..7c06ff6bee546 100644 --- a/frame/election-provider-multi-phase/src/mock.rs +++ b/frame/election-provider-multi-phase/src/mock.rs @@ -276,10 +276,11 @@ parameter_types! { } pub struct OnChainSeqPhragmen; -impl onchain::ExecutionConfig for OnChainSeqPhragmen { +impl onchain::Config for OnChainSeqPhragmen { type System = Runtime; type Solver = SequentialPhragmen, Balancing>; type DataProvider = StakingMock; + type WeightInfo = (); } pub struct MockFallback; @@ -304,7 +305,7 @@ impl InstantElectionProvider for MockFallback { max_voters, max_targets, ) - .map_err(|_| "UnboundedExecution failed") + .map_err(|_| "onchain::UnboundedExecution failed.") } else { super::NoFallback::::elect_with_bounds(max_voters, max_targets) } diff --git a/frame/election-provider-support/benchmarking/Cargo.toml b/frame/election-provider-support/benchmarking/Cargo.toml new file mode 100644 index 0000000000000..e9719be139a6d --- /dev/null +++ b/frame/election-provider-support/benchmarking/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "pallet-election-provider-support-benchmarking" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Benchmarking for election provider support onchain config trait" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ + "derive", +] } +sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/npos-elections" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../../primitives/runtime" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } +frame-election-provider-support = { version = "4.0.0-dev", default-features = false, path = ".." } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../benchmarking", optional = true } + + +[features] +default = ["std"] +std = [ + "codec/std", + "sp-npos-elections/std", + "sp-runtime/std", + "frame-benchmarking/std", + "frame-system/std", +] + +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-election-provider-support/runtime-benchmarks", +] diff --git a/frame/election-provider-support/benchmarking/src/lib.rs b/frame/election-provider-support/benchmarking/src/lib.rs new file mode 100644 index 0000000000000..547e35bed36e8 --- /dev/null +++ b/frame/election-provider-support/benchmarking/src/lib.rs @@ -0,0 +1,91 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Election provider support pallet benchmarking. +//! This is separated into its own crate to avoid bloating the size of the runtime. + +#![cfg(feature = "runtime-benchmarks")] +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::Decode; +use frame_benchmarking::{benchmarks, Vec}; +use frame_election_provider_support::{NposSolver, PhragMMS, SequentialPhragmen}; + +pub struct Pallet(frame_system::Pallet); +pub trait Config: frame_system::Config {} + +const VOTERS: [u32; 2] = [1_000, 2_000]; +const TARGETS: [u32; 2] = [500, 1_000]; +const VOTES_PER_VOTER: [u32; 2] = [5, 16]; + +const SEED: u32 = 999; +fn set_up_voters_targets( + voters_len: u32, + targets_len: u32, + degree: usize, +) -> (Vec<(AccountId, u64, impl IntoIterator)>, Vec) { + // fill targets. + let mut targets = (0..targets_len) + .map(|i| frame_benchmarking::account::("Target", i, SEED)) + .collect::>(); + assert!(targets.len() > degree, "we should always have enough voters to fill"); + targets.truncate(degree); + + // fill voters. + let voters = (0..voters_len) + .map(|i| { + let voter = frame_benchmarking::account::("Voter", i, SEED); + (voter, 1_000, targets.clone()) + }) + .collect::>(); + + (voters, targets) +} + +benchmarks! { + phragmen { + // number of votes in snapshot. + let v in (VOTERS[0]) .. VOTERS[1]; + // number of targets in snapshot. + let t in (TARGETS[0]) .. TARGETS[1]; + // number of votes per voter (ie the degree). + let d in (VOTES_PER_VOTER[0]) .. VOTES_PER_VOTER[1]; + + let (voters, targets) = set_up_voters_targets::(v, t, d as usize); + }: { + assert!( + SequentialPhragmen:: + ::solve(d as usize, targets, voters).is_ok() + ); + } + + phragmms { + // number of votes in snapshot. + let v in (VOTERS[0]) .. VOTERS[1]; + // number of targets in snapshot. + let t in (TARGETS[0]) .. TARGETS[1]; + // number of votes per voter (ie the degree). + let d in (VOTES_PER_VOTER[0]) .. VOTES_PER_VOTER[1]; + + let (voters, targets) = set_up_voters_targets::(v, t, d as usize); + }: { + assert!( + PhragMMS:: + ::solve(d as usize, targets, voters).is_ok() + ); + } +} diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index 19735cf6035ac..37ea2fcc59091 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -170,7 +170,7 @@ pub mod onchain; pub mod traits; #[cfg(feature = "std")] use codec::{Decode, Encode}; -use frame_support::{BoundedVec, RuntimeDebug}; +use frame_support::{weights::Weight, BoundedVec, RuntimeDebug}; use sp_runtime::traits::Bounded; use sp_std::{fmt::Debug, prelude::*}; @@ -195,6 +195,9 @@ pub use sp_arithmetic; #[doc(hidden)] pub use sp_std; +pub mod weights; +pub use weights::WeightInfo; + #[cfg(test)] mod mock; #[cfg(test)] @@ -523,6 +526,12 @@ pub trait NposSolver { targets: Vec, voters: Vec<(Self::AccountId, VoteWeight, impl IntoIterator)>, ) -> Result, Self::Error>; + + /// Measure the weight used in the calculation of the solver. + /// - `voters` is the number of voters. + /// - `targets` is the number of targets. + /// - `vote_degree` is the degree ie the maximum numbers of votes per voter. + fn weight(voters: u32, targets: u32, vote_degree: u32) -> Weight; } /// A wrapper for [`sp_npos_elections::seq_phragmen`] that implements [`NposSolver`]. See the @@ -547,6 +556,10 @@ impl< ) -> Result, Self::Error> { sp_npos_elections::seq_phragmen(winners, targets, voters, Balancing::get()) } + + fn weight(voters: u32, targets: u32, vote_degree: u32) -> Weight { + T::phragmen(voters, targets, vote_degree) + } } /// A wrapper for [`sp_npos_elections::phragmms()`] that implements [`NposSolver`]. See the @@ -571,6 +584,10 @@ impl< ) -> Result, Self::Error> { sp_npos_elections::phragmms(winners, targets, voters, Balancing::get()) } + + fn weight(voters: u32, targets: u32, vote_degree: u32) -> Weight { + T::phragmms(voters, targets, vote_degree) + } } /// A voter, at the level of abstraction of this crate. diff --git a/frame/election-provider-support/src/onchain.rs b/frame/election-provider-support/src/onchain.rs index 57fd931a467d1..62e76c3888822 100644 --- a/frame/election-provider-support/src/onchain.rs +++ b/frame/election-provider-support/src/onchain.rs @@ -15,9 +15,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! An implementation of [`ElectionProvider`] that uses an `NposSolver` to do the election. +//! An implementation of [`ElectionProvider`] that uses an `NposSolver` to do the election. As the +//! name suggests, this is meant to be used onchain. Given how heavy the calculations are, please be +//! careful when using it onchain. -use crate::{ElectionDataProvider, ElectionProvider, InstantElectionProvider, NposSolver}; +use crate::{ + Debug, ElectionDataProvider, ElectionProvider, InstantElectionProvider, NposSolver, WeightInfo, +}; use frame_support::{traits::Get, weights::DispatchClass}; use sp_npos_elections::*; use sp_std::{collections::btree_map::BTreeMap, marker::PhantomData, prelude::*}; @@ -41,39 +45,32 @@ impl From for Error { /// /// This will accept voting data on the fly and produce the results immediately. /// -/// Finally, the [`ElectionProvider`] implementation of this type does not impose any limits on the +/// The [`ElectionProvider`] implementation of this type does not impose any dynamic limits on the /// number of voters and targets that are fetched. This could potentially make this unsuitable for -/// execution onchain. One could, however, impose bounds on it by using for example -/// `BoundedExecution` which will the bounds provided in the configuration. +/// execution onchain. One could, however, impose bounds on it by using `BoundedExecution` using the +/// `MaxVoters` and `MaxTargets` bonds in the `BoundedConfig` trait. /// -/// On the other hand, the [`InstantElectionProvider`] implementation does limit these inputs, -/// either via using `BoundedExecution` and imposing the bounds there, or dynamically via calling -/// `elect_with_bounds` providing these bounds. If you use `elect_with_bounds` along with -/// `InstantElectionProvider`, the bound that would be used is the minimum of the 2 bounds. -/// -/// It is advisable to use the former ([`ElectionProvider::elect`]) only at genesis, or for testing, -/// the latter [`InstantElectionProvider::elect_with_bounds`] for onchain operations, with -/// thoughtful bounds. +/// On the other hand, the [`InstantElectionProvider`] implementation does limit these inputs +/// dynamically. If you use `elect_with_bounds` along with `InstantElectionProvider`, the bound that +/// would be used is the minimum of the dynamic bounds given as arguments to `elect_with_bounds` and +/// the trait bounds (`MaxVoters` and `MaxTargets`). /// /// Please use `BoundedExecution` at all times except at genesis or for testing, with thoughtful /// bounds in order to bound the potential execution time. Limit the use `UnboundedExecution` at /// genesis or for testing, as it does not bound the inputs. However, this can be used with /// `[InstantElectionProvider::elect_with_bounds`] that dynamically imposes limits. -pub struct BoundedExecution(PhantomData); +pub struct BoundedExecution(PhantomData); /// An unbounded variant of [`BoundedExecution`]. /// /// ### Warning /// -/// This can be very expensive to run frequently on-chain. Use with care. Moreover, this -/// implementation ignores the additional data of the election data provider and gives no insight on -/// how much weight was consumed. -pub struct UnboundedExecution(PhantomData); - -/// Configuration trait of [`UnboundedExecution`]. -pub trait ExecutionConfig { - /// Something that implements the system pallet configs. This is to enable to register extra - /// weight. +/// This can be very expensive to run frequently on-chain. Use with care. +pub struct UnboundedExecution(PhantomData); + +/// Configuration trait for an onchain election execution. +pub trait Config { + /// Needed for weight registration. type System: frame_system::Config; /// `NposSolver` that should be used, an example would be `PhragMMS`. type Solver: NposSolver< @@ -85,17 +82,18 @@ pub trait ExecutionConfig { AccountId = ::AccountId, BlockNumber = ::BlockNumber, >; + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; } -/// Configuration trait of [`BoundedExecution`]. -pub trait BoundedExecutionConfig: ExecutionConfig { +pub trait BoundedConfig: Config { /// Bounds the number of voters. type VotersBound: Get; /// Bounds the number of targets. type TargetsBound: Get; } -fn elect_with( +fn elect_with( maybe_max_voters: Option, maybe_max_targets: Option, ) -> Result::AccountId>, Error> { @@ -104,6 +102,9 @@ fn elect_with( T::DataProvider::electable_targets(maybe_max_targets).map_err(Error::DataProvider)?; let desired_targets = T::DataProvider::desired_targets().map_err(Error::DataProvider)?; + let voters_len = voters.len() as u32; + let targets_len = targets.len() as u32; + let stake_map: BTreeMap<_, _> = voters .iter() .map(|(validator, vote_weight, _)| (validator.clone(), *vote_weight)) @@ -118,7 +119,11 @@ fn elect_with( let staked = assignment_ratio_to_staked_normalized(assignments, &stake_of)?; - let weight = ::BlockWeights::get().max_block; + let weight = T::Solver::weight::( + voters_len, + targets_len, + ::MaxVotesPerVoter::get(), + ); frame_system::Pallet::::register_extra_weight_unchecked( weight, DispatchClass::Mandatory, @@ -127,13 +132,13 @@ fn elect_with( Ok(to_supports(&staked)) } -impl ElectionProvider for UnboundedExecution { +impl ElectionProvider for UnboundedExecution { type AccountId = ::AccountId; type BlockNumber = ::BlockNumber; type Error = Error; type DataProvider = T::DataProvider; - fn elect() -> Result::AccountId>, Self::Error> { + fn elect() -> Result, Self::Error> { // This should not be called if not in `std` mode (and therefore neither in genesis nor in // testing) if cfg!(not(feature = "std")) { @@ -147,7 +152,7 @@ impl ElectionProvider for UnboundedExecution { } } -impl InstantElectionProvider for UnboundedExecution { +impl InstantElectionProvider for UnboundedExecution { fn elect_with_bounds( max_voters: usize, max_targets: usize, @@ -156,18 +161,18 @@ impl InstantElectionProvider for UnboundedExecution { } } -impl ElectionProvider for BoundedExecution { +impl ElectionProvider for BoundedExecution { type AccountId = ::AccountId; type BlockNumber = ::BlockNumber; type Error = Error; type DataProvider = T::DataProvider; - fn elect() -> Result::AccountId>, Self::Error> { + fn elect() -> Result, Self::Error> { elect_with::(Some(T::VotersBound::get() as usize), Some(T::TargetsBound::get() as usize)) } } -impl InstantElectionProvider for BoundedExecution { +impl InstantElectionProvider for BoundedExecution { fn elect_with_bounds( max_voters: usize, max_targets: usize, @@ -182,6 +187,8 @@ impl InstantElectionProvider for BoundedExecution #[cfg(test)] mod tests { use super::*; + use crate::{PhragMMS, SequentialPhragmen}; + use frame_support::traits::ConstU32; use sp_npos_elections::Support; use sp_runtime::Perbill; type AccountId = u64; @@ -228,13 +235,32 @@ mod tests { type MaxConsumers = frame_support::traits::ConstU32<16>; } - impl ExecutionConfig for Runtime { - type System = Self; - type Solver = crate::SequentialPhragmen; + struct PhragmenParams; + struct PhragMMSParams; + + impl Config for PhragmenParams { + type System = Runtime; + type Solver = SequentialPhragmen; type DataProvider = mock_data_provider::DataProvider; + type WeightInfo = (); } - type OnChainPhragmen = UnboundedExecution; + impl BoundedConfig for PhragmenParams { + type VotersBound = ConstU32<600>; + type TargetsBound = ConstU32<400>; + } + + impl Config for PhragMMSParams { + type System = Runtime; + type Solver = PhragMMS; + type DataProvider = mock_data_provider::DataProvider; + type WeightInfo = (); + } + + impl BoundedConfig for PhragMMSParams { + type VotersBound = ConstU32<600>; + type TargetsBound = ConstU32<400>; + } mod mock_data_provider { use frame_support::{bounded_vec, traits::ConstU32}; @@ -273,7 +299,20 @@ mod tests { fn onchain_seq_phragmen_works() { sp_io::TestExternalities::new_empty().execute_with(|| { assert_eq!( - OnChainPhragmen::elect().unwrap(), + BoundedExecution::::elect().unwrap(), + vec![ + (10, Support { total: 25, voters: vec![(1, 10), (3, 15)] }), + (30, Support { total: 35, voters: vec![(2, 20), (3, 15)] }) + ] + ); + }) + } + + #[test] + fn onchain_phragmms_works() { + sp_io::TestExternalities::new_empty().execute_with(|| { + assert_eq!( + BoundedExecution::::elect().unwrap(), vec![ (10, Support { total: 25, voters: vec![(1, 10), (3, 15)] }), (30, Support { total: 35, voters: vec![(2, 20), (3, 15)] }) diff --git a/frame/election-provider-support/src/weights.rs b/frame/election-provider-support/src/weights.rs new file mode 100644 index 0000000000000..f288ae63bb5da --- /dev/null +++ b/frame/election-provider-support/src/weights.rs @@ -0,0 +1,94 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for pallet_election_provider_support_onchain_benchmarking +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-04-04, STEPS: `1`, REPEAT: 1, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// target/release/substrate +// benchmark +// --chain=dev +// --steps=1 +// --repeat=1 +// --pallet=pallet_election_provider_support_benchmarking +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=frame/election-provider-support/src/weights.rs +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_election_provider_support_benchmarking. +pub trait WeightInfo { + fn phragmen(v: u32, t: u32, d: u32, ) -> Weight; + fn phragmms(v: u32, t: u32, d: u32, ) -> Weight; +} + +/// Weights for pallet_election_provider_support_benchmarking using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + fn phragmen(v: u32, t: u32, d: u32, ) -> Weight { + (0 as Weight) + // Standard Error: 667_000 + .saturating_add((32_973_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 1_334_000 + .saturating_add((1_334_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 60_644_000 + .saturating_add((2_636_364_000 as Weight).saturating_mul(d as Weight)) + } + fn phragmms(v: u32, t: u32, d: u32, ) -> Weight { + (0 as Weight) + // Standard Error: 73_000 + .saturating_add((21_073_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 146_000 + .saturating_add((65_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 6_649_000 + .saturating_add((1_711_424_000 as Weight).saturating_mul(d as Weight)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn phragmen(v: u32, t: u32, d: u32, ) -> Weight { + (0 as Weight) + // Standard Error: 667_000 + .saturating_add((32_973_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 1_334_000 + .saturating_add((1_334_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 60_644_000 + .saturating_add((2_636_364_000 as Weight).saturating_mul(d as Weight)) + } + fn phragmms(v: u32, t: u32, d: u32, ) -> Weight { + (0 as Weight) + // Standard Error: 73_000 + .saturating_add((21_073_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 146_000 + .saturating_add((65_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 6_649_000 + .saturating_add((1_711_424_000 as Weight).saturating_mul(d as Weight)) + } +} diff --git a/frame/grandpa/src/mock.rs b/frame/grandpa/src/mock.rs index 0296cd2e28d88..67d5a3d7fd373 100644 --- a/frame/grandpa/src/mock.rs +++ b/frame/grandpa/src/mock.rs @@ -181,10 +181,11 @@ parameter_types! { } pub struct OnChainSeqPhragmen; -impl onchain::ExecutionConfig for OnChainSeqPhragmen { +impl onchain::Config for OnChainSeqPhragmen { type System = Test; - type Solver = SequentialPhragmen<::AccountId, Perbill>; + type Solver = SequentialPhragmen; type DataProvider = Staking; + type WeightInfo = (); } impl pallet_staking::Config for Test { diff --git a/frame/offences/benchmarking/src/mock.rs b/frame/offences/benchmarking/src/mock.rs index 1a4414de0b0b0..74cc29586920d 100644 --- a/frame/offences/benchmarking/src/mock.rs +++ b/frame/offences/benchmarking/src/mock.rs @@ -151,10 +151,11 @@ parameter_types! { pub type Extrinsic = sp_runtime::testing::TestXt; pub struct OnChainSeqPhragmen; -impl onchain::ExecutionConfig for OnChainSeqPhragmen { +impl onchain::Config for OnChainSeqPhragmen { type System = Test; type Solver = SequentialPhragmen; type DataProvider = Staking; + type WeightInfo = (); } impl pallet_staking::Config for Test { diff --git a/frame/session/benchmarking/src/mock.rs b/frame/session/benchmarking/src/mock.rs index 24b42b3e9f4b5..5acc484f9ba62 100644 --- a/frame/session/benchmarking/src/mock.rs +++ b/frame/session/benchmarking/src/mock.rs @@ -157,10 +157,11 @@ where } pub struct OnChainSeqPhragmen; -impl onchain::ExecutionConfig for OnChainSeqPhragmen { +impl onchain::Config for OnChainSeqPhragmen { type System = Test; type Solver = SequentialPhragmen; type DataProvider = Staking; + type WeightInfo = (); } impl pallet_staking::Config for Test { diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index bb90aded852ee..09809483ec155 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -248,10 +248,11 @@ impl pallet_bags_list::Config for Test { } pub struct OnChainSeqPhragmen; -impl onchain::ExecutionConfig for OnChainSeqPhragmen { +impl onchain::Config for OnChainSeqPhragmen { type System = Test; type Solver = SequentialPhragmen; type DataProvider = Staking; + type WeightInfo = (); } impl crate::pallet::pallet::Config for Test { diff --git a/primitives/npos-elections/src/balancing.rs b/primitives/npos-elections/src/balancing.rs index 98f193e9e6116..54b8ee4bf243e 100644 --- a/primitives/npos-elections/src/balancing.rs +++ b/primitives/npos-elections/src/balancing.rs @@ -37,7 +37,7 @@ use sp_std::prelude::*; /// /// In almost all cases, a balanced solution will have a better score than an unbalanced solution, /// yet this is not 100% guaranteed because the first element of a [`crate::ElectionScore`] does not -/// directly related to balancing. +/// directly relate to balancing. /// /// Note that some reference implementation adopt an approach in which voters are balanced randomly /// per round. To advocate determinism, we don't do this. In each round, all voters are exactly diff --git a/primitives/npos-elections/src/phragmms.rs b/primitives/npos-elections/src/phragmms.rs index 6220cacd157b2..aa4c558bea1da 100644 --- a/primitives/npos-elections/src/phragmms.rs +++ b/primitives/npos-elections/src/phragmms.rs @@ -37,7 +37,7 @@ use sp_std::{prelude::*, rc::Rc}; /// - The algorithm is a _best-effort_ to elect `to_elect`. If less candidates are provided, less /// winners are returned, without an error. /// -/// This can only fail of the normalization fails. This can happen if for any of the resulting +/// This can only fail if the normalization fails. This can happen if for any of the resulting /// assignments, `assignment.distribution.map(|p| p.deconstruct()).sum()` fails to fit inside /// `UpperOf

`. A user of this crate may statically assert that this can never happen and safely /// `expect` this to return `Ok`. From 134fdf080af145c6c8630598d6079b80e9734231 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Fri, 15 Apr 2022 14:09:16 +0200 Subject: [PATCH 126/484] Add `benchmark machine` placeholder (#11198) * Move new_rng to shared code Signed-off-by: Oliver Tale-Yazdi * Add bechmark machine command Signed-off-by: Oliver Tale-Yazdi * Use sc-sysinfo Signed-off-by: Oliver Tale-Yazdi * Add --no-hardware-benchmarks Signed-off-by: Oliver Tale-Yazdi * Lockfile Signed-off-by: Oliver Tale-Yazdi * Do not create components if not needed Signed-off-by: Oliver Tale-Yazdi * Fix tests Signed-off-by: Oliver Tale-Yazdi * Revert "Add --no-hardware-benchmarks" This reverts commit d4ee98222bf1a5ea62ac60dd7d5c62070e2d7f70. * Fix tests Signed-off-by: Oliver Tale-Yazdi * Update Cargo deps Signed-off-by: Oliver Tale-Yazdi * Move sr255119::verify bench to sc-sysinfo Signed-off-by: Oliver Tale-Yazdi * Move sr255119::verify bench to sc-sysinfo Signed-off-by: Oliver Tale-Yazdi * Switch benchmarks to return f64 Signed-off-by: Oliver Tale-Yazdi * Review fixes Signed-off-by: Oliver Tale-Yazdi * fmt Signed-off-by: Oliver Tale-Yazdi * Hide command until completed Signed-off-by: Oliver Tale-Yazdi * Use concrete rand implementation Signed-off-by: Oliver Tale-Yazdi * Put clobber into a function Signed-off-by: Oliver Tale-Yazdi * Add test Signed-off-by: Oliver Tale-Yazdi * Add comment Signed-off-by: Oliver Tale-Yazdi * Update cargo to match polkadot Signed-off-by: Oliver Tale-Yazdi * Remove doc that does not format in the console Signed-off-by: Oliver Tale-Yazdi * Limit benchmark by time Signed-off-by: Oliver Tale-Yazdi * Add ExecutionLimit and make function infallible Signed-off-by: Oliver Tale-Yazdi * CI Signed-off-by: Oliver Tale-Yazdi * Add doc Signed-off-by: Oliver Tale-Yazdi --- Cargo.lock | 86 ++++++++++++++++-- bin/node-template/node/src/command.rs | 11 ++- bin/node/cli/src/command.rs | 10 +- bin/node/cli/tests/benchmark_block_works.rs | 2 +- bin/node/cli/tests/benchmark_machine_works.rs | 32 +++++++ client/sysinfo/Cargo.toml | 2 + client/sysinfo/src/lib.rs | 38 +++++++- client/sysinfo/src/sysinfo.rs | 91 +++++++++++++++---- utils/frame/benchmarking-cli/Cargo.toml | 5 +- utils/frame/benchmarking-cli/src/lib.rs | 5 + .../frame/benchmarking-cli/src/machine/mod.rs | 86 ++++++++++++++++++ .../frame/benchmarking-cli/src/shared/mod.rs | 10 ++ .../frame/benchmarking-cli/src/storage/cmd.rs | 11 +-- .../benchmarking-cli/src/storage/read.rs | 4 +- .../benchmarking-cli/src/storage/write.rs | 4 +- 15 files changed, 351 insertions(+), 46 deletions(-) create mode 100644 bin/node/cli/tests/benchmark_machine_works.rs create mode 100644 utils/frame/benchmarking-cli/src/machine/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 2fa738118247e..52c6daad6b422 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1677,6 +1677,17 @@ dependencies = [ "dirs-sys-next", ] +[[package]] +name = "dirs" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" +dependencies = [ + "libc", + "redox_users 0.3.5", + "winapi 0.3.9", +] + [[package]] name = "dirs-sys" version = "0.3.6" @@ -1684,7 +1695,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" dependencies = [ "libc", - "redox_users", + "redox_users 0.4.0", "winapi 0.3.9", ] @@ -1695,7 +1706,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ "libc", - "redox_users", + "redox_users 0.4.0", "winapi 0.3.9", ] @@ -1844,6 +1855,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "enum-as-inner" version = "0.3.3" @@ -2009,9 +2026,9 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fastrand" -version = "1.4.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca5faf057445ce5c9d4329e382b2ce7ca38550ef3b73a5348362d5f24e0c7fe3" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" dependencies = [ "instant", ] @@ -2173,13 +2190,16 @@ dependencies = [ "log 0.4.14", "memory-db", "parity-scale-codec", + "prettytable-rs", "rand 0.8.4", + "rand_pcg 0.3.1", "sc-block-builder", "sc-cli", "sc-client-api", "sc-client-db", "sc-executor", "sc-service", + "sc-sysinfo", "serde", "serde_json", "serde_nanos", @@ -2192,9 +2212,9 @@ dependencies = [ "sp-keystore", "sp-runtime", "sp-state-machine", - "sp-std", "sp-storage", "sp-trie", + "tempfile", "thousands", ] @@ -7183,6 +7203,20 @@ dependencies = [ "output_vt100", ] +[[package]] +name = "prettytable-rs" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fd04b170004fa2daccf418a7f8253aaf033c27760b5f225889024cf66d7ac2e" +dependencies = [ + "atty", + "csv", + "encode_unicode", + "lazy_static", + "term", + "unicode-width", +] + [[package]] name = "primitive-types" version = "0.11.1" @@ -7670,6 +7704,17 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_users" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" +dependencies = [ + "getrandom 0.1.16", + "redox_syscall 0.1.57", + "rust-argon2", +] + [[package]] name = "redox_users" version = "0.4.0" @@ -7886,6 +7931,18 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "rust-argon2" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb" +dependencies = [ + "base64 0.13.0", + "blake2b_simd", + "constant_time_eq", + "crossbeam-utils 0.8.5", +] + [[package]] name = "rustc-demangle" version = "0.1.18" @@ -9099,6 +9156,8 @@ dependencies = [ "serde", "serde_json", "sp-core", + "sp-io", + "sp-std", ] [[package]] @@ -10914,18 +10973,29 @@ checksum = "d7fa7e55043acb85fca6b3c01485a2eeb6b69c5d21002e273c79e465f43b7ac1" [[package]] name = "tempfile" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" dependencies = [ "cfg-if 1.0.0", + "fastrand", "libc", - "rand 0.8.4", "redox_syscall 0.2.10", "remove_dir_all", "winapi 0.3.9", ] +[[package]] +name = "term" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd106a334b7657c10b7c540a0106114feadeb4dc314513e97df481d5d966f42" +dependencies = [ + "byteorder", + "dirs", + "winapi 0.3.9", +] + [[package]] name = "termcolor" version = "1.1.2" diff --git a/bin/node-template/node/src/command.rs b/bin/node-template/node/src/command.rs index 0c850b322a7d2..afa4612f1ee4a 100644 --- a/bin/node-template/node/src/command.rs +++ b/bin/node-template/node/src/command.rs @@ -109,8 +109,6 @@ pub fn run() -> sc_cli::Result<()> { let runner = cli.create_runner(cmd)?; runner.sync_run(|config| { - let PartialComponents { client, backend, .. } = service::new_partial(&config)?; - // This switch needs to be in the client, since the client decides // which sub-commands it wants to support. match cmd { @@ -125,18 +123,25 @@ pub fn run() -> sc_cli::Result<()> { cmd.run::(config) }, - BenchmarkCmd::Block(cmd) => cmd.run(client), + BenchmarkCmd::Block(cmd) => { + let PartialComponents { client, .. } = service::new_partial(&config)?; + cmd.run(client) + }, BenchmarkCmd::Storage(cmd) => { + let PartialComponents { client, backend, .. } = + service::new_partial(&config)?; let db = backend.expose_db(); let storage = backend.expose_storage(); cmd.run(config, client, db, storage) }, BenchmarkCmd::Overhead(cmd) => { + let PartialComponents { client, .. } = service::new_partial(&config)?; let ext_builder = BenchmarkExtrinsicBuilder::new(client.clone()); cmd.run(config, client, inherent_benchmark_data()?, Arc::new(ext_builder)) }, + BenchmarkCmd::Machine(cmd) => cmd.run(&config), } }) }, diff --git a/bin/node/cli/src/command.rs b/bin/node/cli/src/command.rs index 880b7d7b7ecb7..e2c772e809200 100644 --- a/bin/node/cli/src/command.rs +++ b/bin/node/cli/src/command.rs @@ -99,8 +99,6 @@ pub fn run() -> Result<()> { let runner = cli.create_runner(cmd)?; runner.sync_run(|config| { - let PartialComponents { client, backend, .. } = new_partial(&config)?; - // This switch needs to be in the client, since the client decides // which sub-commands it wants to support. match cmd { @@ -115,18 +113,24 @@ pub fn run() -> Result<()> { cmd.run::(config) }, - BenchmarkCmd::Block(cmd) => cmd.run(client), + BenchmarkCmd::Block(cmd) => { + let PartialComponents { client, .. } = new_partial(&config)?; + cmd.run(client) + }, BenchmarkCmd::Storage(cmd) => { + let PartialComponents { client, backend, .. } = new_partial(&config)?; let db = backend.expose_db(); let storage = backend.expose_storage(); cmd.run(config, client, db, storage) }, BenchmarkCmd::Overhead(cmd) => { + let PartialComponents { client, .. } = new_partial(&config)?; let ext_builder = BenchmarkExtrinsicBuilder::new(client.clone()); cmd.run(config, client, inherent_benchmark_data()?, Arc::new(ext_builder)) }, + BenchmarkCmd::Machine(cmd) => cmd.run(&config), } }) }, diff --git a/bin/node/cli/tests/benchmark_block_works.rs b/bin/node/cli/tests/benchmark_block_works.rs index 37a4db25f363b..359abf3e4265f 100644 --- a/bin/node/cli/tests/benchmark_block_works.rs +++ b/bin/node/cli/tests/benchmark_block_works.rs @@ -30,7 +30,7 @@ pub mod common; async fn benchmark_block_works() { let base_dir = tempdir().expect("could not create a temp dir"); - common::run_node_for_a_while(base_dir.path(), &["--dev"]).await; + common::run_node_for_a_while(base_dir.path(), &["--dev", "--no-hardware-benchmarks"]).await; // Invoke `benchmark block` with all options to make sure that they are valid. let status = Command::new(cargo_bin("substrate")) diff --git a/bin/node/cli/tests/benchmark_machine_works.rs b/bin/node/cli/tests/benchmark_machine_works.rs new file mode 100644 index 0000000000000..df407e988f636 --- /dev/null +++ b/bin/node/cli/tests/benchmark_machine_works.rs @@ -0,0 +1,32 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use assert_cmd::cargo::cargo_bin; +use std::process::Command; + +/// Tests that the `benchmark machine` command works for the substrate dev runtime. +#[test] +fn benchmark_machine_works() { + let status = Command::new(cargo_bin("substrate")) + .args(["benchmark", "machine", "--dev"]) + .args(["--verify-duration", "0.1"]) + .status() + .unwrap(); + + assert!(status.success()); +} diff --git a/client/sysinfo/Cargo.toml b/client/sysinfo/Cargo.toml index 8efe583fb9335..540918cb37c01 100644 --- a/client/sysinfo/Cargo.toml +++ b/client/sysinfo/Cargo.toml @@ -23,4 +23,6 @@ libc = "0.2" serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0.79" sp-core = { version = "6.0.0", path = "../../primitives/core" } +sp-io = { version = "6.0.0", path = "../../primitives/io" } +sp-std = { version = "4.0.0", path = "../../primitives/std" } sc-telemetry = { version = "4.0.0-dev", path = "../telemetry" } diff --git a/client/sysinfo/src/lib.rs b/client/sysinfo/src/lib.rs index be13efb82c66f..911e725dcdd4e 100644 --- a/client/sysinfo/src/lib.rs +++ b/client/sysinfo/src/lib.rs @@ -20,12 +20,16 @@ //! and software telemetry information about the node on which we're running. use futures::prelude::*; +use std::time::Duration; mod sysinfo; #[cfg(target_os = "linux")] mod sysinfo_linux; -pub use sysinfo::{gather_hwbench, gather_sysinfo}; +pub use sysinfo::{ + benchmark_cpu, benchmark_disk_random_writes, benchmark_disk_sequential_writes, + benchmark_memory, benchmark_sr25519_verify, gather_hwbench, gather_sysinfo, +}; /// The operating system part of the current target triplet. pub const TARGET_OS: &str = include_str!(concat!(env!("OUT_DIR"), "/target_os.txt")); @@ -49,6 +53,38 @@ pub struct HwBench { pub disk_random_write_score: Option, } +/// Limit the execution time of a benchmark. +pub enum ExecutionLimit { + /// Limit by the maximal duration. + MaxDuration(Duration), + + /// Limit by the maximal number of iterations. + MaxIterations(usize), + + /// Limit by the maximal duration and maximal number of iterations. + Both { max_iterations: usize, max_duration: Duration }, +} + +impl ExecutionLimit { + /// Returns the duration limit or `MAX` if none is present. + pub fn max_duration(&self) -> Duration { + match self { + Self::MaxDuration(d) => *d, + Self::Both { max_duration, .. } => *max_duration, + _ => Duration::from_secs(u64::MAX), + } + } + + /// Returns the iterations limit or `MAX` if none is present. + pub fn max_iterations(&self) -> usize { + match self { + Self::MaxIterations(d) => *d, + Self::Both { max_iterations, .. } => *max_iterations, + _ => usize::MAX, + } + } +} + /// Prints out the system software/hardware information in the logs. pub fn print_sysinfo(sysinfo: &sc_telemetry::SysInfo) { log::info!("💻 Operating system: {}", TARGET_OS); diff --git a/client/sysinfo/src/sysinfo.rs b/client/sysinfo/src/sysinfo.rs index ceb28447002cf..65d7a9e41b406 100644 --- a/client/sysinfo/src/sysinfo.rs +++ b/client/sysinfo/src/sysinfo.rs @@ -16,9 +16,14 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::HwBench; -use rand::{seq::SliceRandom, Rng}; +use crate::{ExecutionLimit, HwBench}; + use sc_telemetry::SysInfo; +use sp_core::{sr25519, Pair}; +use sp_io::crypto::sr25519_verify; +use sp_std::prelude::*; + +use rand::{seq::SliceRandom, Rng, RngCore}; use std::{ fs::File, io::{Seek, SeekFrom, Write}, @@ -34,7 +39,7 @@ pub(crate) fn benchmark( max_iterations: usize, max_duration: Duration, mut run: impl FnMut() -> Result<(), E>, -) -> Result { +) -> Result { // Run the benchmark once as a warmup to get the code into the L1 cache. run()?; @@ -53,11 +58,11 @@ pub(crate) fn benchmark( } } - let score = (((size * count) as f64 / elapsed.as_secs_f64()) / (1024.0 * 1024.0)) as u64; + let score = ((size * count) as f64 / elapsed.as_secs_f64()) / (1024.0 * 1024.0); log::trace!( "Calculated {} of {}MB/s in {} iterations in {}ms", name, - score, + score as u64, count, elapsed.as_millis() ); @@ -83,7 +88,7 @@ pub fn gather_sysinfo() -> SysInfo { } #[inline(never)] -fn clobber(slice: &mut [u8]) { +fn clobber_slice(slice: &mut [T]) { assert!(!slice.is_empty()); // Discourage the compiler from optimizing out our benchmarks. @@ -102,8 +107,17 @@ fn clobber(slice: &mut [u8]) { } } +#[inline(never)] +fn clobber_value(input: &mut T) { + // Look into `clobber_slice` for a comment. + unsafe { + let value = std::ptr::read_volatile(input); + std::ptr::write_volatile(input, value); + } +} + // This benchmarks the CPU speed as measured by calculating BLAKE2b-256 hashes, in MB/s. -fn benchmark_cpu() -> u64 { +pub fn benchmark_cpu() -> u64 { // In general the results of this benchmark are somewhat sensitive to how much // data we hash at the time. The smaller this is the *less* MB/s we can hash, // the bigger this is the *more* MB/s we can hash, up until a certain point @@ -125,15 +139,15 @@ fn benchmark_cpu() -> u64 { let mut hash = Default::default(); let run = || -> Result<(), ()> { - clobber(&mut buffer); + clobber_slice(&mut buffer); hash = sp_core::hashing::blake2_256(&buffer); - clobber(&mut hash); + clobber_slice(&mut hash); Ok(()) }; benchmark("CPU score", SIZE, MAX_ITERATIONS, MAX_DURATION, run) - .expect("benchmark cannot fail; qed") + .expect("benchmark cannot fail; qed") as u64 } // This benchmarks the effective `memcpy` memory bandwidth available in MB/s. @@ -141,7 +155,7 @@ fn benchmark_cpu() -> u64 { // It doesn't technically measure the absolute maximum memory bandwidth available, // but that's fine, because real code most of the time isn't optimized to take // advantage of the full memory bandwidth either. -fn benchmark_memory() -> u64 { +pub fn benchmark_memory() -> u64 { // Ideally this should be at least as big as the CPU's L3 cache, // and it should be big enough so that the `memcpy` takes enough // time to be actually measurable. @@ -161,8 +175,8 @@ fn benchmark_memory() -> u64 { dst.resize(SIZE, 0x77); let run = || -> Result<(), ()> { - clobber(&mut src); - clobber(&mut dst); + clobber_slice(&mut src); + clobber_slice(&mut dst); // SAFETY: Both vectors are of the same type and of the same size, // so copying data between them is safe. @@ -172,14 +186,14 @@ fn benchmark_memory() -> u64 { libc::memcpy(dst.as_mut_ptr().cast(), src.as_ptr().cast(), SIZE); } - clobber(&mut dst); - clobber(&mut src); + clobber_slice(&mut dst); + clobber_slice(&mut src); Ok(()) }; benchmark("memory score", SIZE, MAX_ITERATIONS, MAX_DURATION, run) - .expect("benchmark cannot fail; qed") + .expect("benchmark cannot fail; qed") as u64 } struct TemporaryFile { @@ -260,6 +274,7 @@ pub fn benchmark_disk_sequential_writes(directory: &Path) -> Result }; benchmark("disk sequential write score", SIZE, MAX_ITERATIONS, MAX_DURATION, run) + .map(|s| s as u64) } pub fn benchmark_disk_random_writes(directory: &Path) -> Result { @@ -319,6 +334,45 @@ pub fn benchmark_disk_random_writes(directory: &Path) -> Result { // We only wrote half of the bytes hence `SIZE / 2`. benchmark("disk random write score", SIZE / 2, MAX_ITERATIONS, MAX_DURATION, run) + .map(|s| s as u64) +} + +/// Benchmarks the verification speed of sr25519 signatures. +/// +/// Returns the throughput in MB/s by convention. +/// The values are rather small (0.4-0.8) so it is advised to convert them into KB/s. +pub fn benchmark_sr25519_verify(limit: ExecutionLimit) -> f64 { + const INPUT_SIZE: usize = 32; + const ITERATION_SIZE: usize = 2048; + let pair = sr25519::Pair::from_string("//Alice", None).unwrap(); + + let mut rng = rng(); + let mut msgs = Vec::new(); + let mut sigs = Vec::new(); + + for _ in 0..ITERATION_SIZE { + let mut msg = vec![0u8; INPUT_SIZE]; + rng.fill_bytes(&mut msg[..]); + + sigs.push(pair.sign(&msg)); + msgs.push(msg); + } + + let run = || -> Result<(), String> { + for (sig, msg) in sigs.iter().zip(msgs.iter()) { + let mut ok = sr25519_verify(&sig, &msg[..], &pair.public()); + clobber_value(&mut ok); + } + Ok(()) + }; + benchmark( + "sr25519 verification score", + INPUT_SIZE * ITERATION_SIZE, + limit.max_iterations(), + limit.max_duration(), + run, + ) + .expect("sr25519 verification cannot fail; qed") } /// Benchmarks the hardware and returns the results of those benchmarks. @@ -390,4 +444,9 @@ mod tests { fn test_benchmark_disk_random_writes() { assert!(benchmark_disk_random_writes("./".as_ref()).unwrap() > 0); } + + #[test] + fn test_benchmark_sr25519_verify() { + assert!(benchmark_sr25519_verify(ExecutionLimit::MaxIterations(1)) > 0.0); + } } diff --git a/utils/frame/benchmarking-cli/Cargo.toml b/utils/frame/benchmarking-cli/Cargo.toml index 5cb81232085a4..858d16558e821 100644 --- a/utils/frame/benchmarking-cli/Cargo.toml +++ b/utils/frame/benchmarking-cli/Cargo.toml @@ -23,6 +23,7 @@ sc-client-api = { version = "4.0.0-dev", path = "../../../client/api" } sc-cli = { version = "0.10.0-dev", path = "../../../client/cli" } sc-client-db = { version = "0.10.0-dev", path = "../../../client/db" } sc-executor = { version = "0.10.0-dev", path = "../../../client/executor" } +sc-sysinfo = { version = "6.0.0-dev", path = "../../../client/sysinfo" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sp-externalities = { version = "0.12.0", path = "../../../primitives/externalities" } @@ -32,7 +33,6 @@ sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } sp-keystore = { version = "0.12.0", path = "../../../primitives/keystore" } sp-storage = { version = "6.0.0", path = "../../../primitives/storage" } sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../../primitives/std" } sp-state-machine = { version = "0.12.0", path = "../../../primitives/state-machine" } sp-trie = { version = "6.0.0", path = "../../../primitives/trie" } codec = { version = "3.0.0", package = "parity-scale-codec" } @@ -52,6 +52,9 @@ hex = "0.4.3" memory-db = "0.29.0" rand = { version = "0.8.4", features = ["small_rng"] } thousands = "0.2.0" +prettytable-rs = "0.8.0" +tempfile = "3.2.0" +rand_pcg = "0.3.1" [features] default = ["db", "sc-client-db/runtime-benchmarks"] diff --git a/utils/frame/benchmarking-cli/src/lib.rs b/utils/frame/benchmarking-cli/src/lib.rs index 2543a63b2f159..75e2edc042a26 100644 --- a/utils/frame/benchmarking-cli/src/lib.rs +++ b/utils/frame/benchmarking-cli/src/lib.rs @@ -18,12 +18,14 @@ //! Contains the root [`BenchmarkCmd`] command and exports its sub-commands. mod block; +mod machine; mod overhead; mod pallet; mod shared; mod storage; pub use block::BlockCmd; +pub use machine::MachineCmd; pub use overhead::{ExtrinsicBuilder, OverheadCmd}; pub use pallet::PalletCmd; pub use storage::StorageCmd; @@ -39,6 +41,8 @@ pub enum BenchmarkCmd { Storage(StorageCmd), Overhead(OverheadCmd), Block(BlockCmd), + #[clap(hide = true)] // Hidden until fully completed. + Machine(MachineCmd), } /// Unwraps a [`BenchmarkCmd`] into its concrete sub-command. @@ -53,6 +57,7 @@ macro_rules! unwrap_cmd { BenchmarkCmd::Storage($cmd) => $code, BenchmarkCmd::Overhead($cmd) => $code, BenchmarkCmd::Block($cmd) => $code, + BenchmarkCmd::Machine($cmd) => $code, } } } diff --git a/utils/frame/benchmarking-cli/src/machine/mod.rs b/utils/frame/benchmarking-cli/src/machine/mod.rs new file mode 100644 index 0000000000000..ee6bf765d01c4 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/machine/mod.rs @@ -0,0 +1,86 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Contains the [`MachineCmd`] as entry point for the node +//! and the core benchmarking logic. + +use sc_cli::{CliConfiguration, Result, SharedParams}; +use sc_service::Configuration; +use sc_sysinfo::{ + benchmark_cpu, benchmark_disk_random_writes, benchmark_disk_sequential_writes, + benchmark_memory, benchmark_sr25519_verify, ExecutionLimit, +}; + +use clap::Parser; +use log::info; +use prettytable::{cell, row, table}; +use std::{fmt::Debug, fs, time::Duration}; + +/// Command to benchmark the hardware. +/// +/// Runs multiple benchmarks and prints their output to console. +/// Can be used to gauge if the hardware is fast enough to keep up with a chain's requirements. +/// This command must be integrated by the client since the client can set compiler flags +/// which influence the results. +/// +/// You can use the `--base-path` flag to set a location for the disk benchmarks. +#[derive(Debug, Parser)] +pub struct MachineCmd { + #[allow(missing_docs)] + #[clap(flatten)] + pub shared_params: SharedParams, + + /// Time limit for the verification benchmark. + #[clap(long, default_value = "2.0", value_name = "SECONDS")] + pub verify_duration: f32, +} + +impl MachineCmd { + /// Execute the benchmark and print the results. + pub fn run(&self, cfg: &Configuration) -> Result<()> { + // Ensure that the dir exists since the node is not started to take care of it. + let dir = cfg.database.path().ok_or("No DB directory provided")?; + fs::create_dir_all(dir)?; + + info!("Running machine benchmarks..."); + let write = benchmark_disk_sequential_writes(dir)?; + let read = benchmark_disk_random_writes(dir)?; + let verify_limit = + ExecutionLimit::MaxDuration(Duration::from_secs_f32(self.verify_duration)); + let verify = benchmark_sr25519_verify(verify_limit) * 1024.0; + + // Use a table for nicer console output. + let table = table!( + ["Category", "Function", "Score", "Unit"], + ["CPU", "BLAKE2-256", benchmark_cpu(), "MB/s"], + ["CPU", "SR25519 Verify", format!("{:.1}", verify), "KB/s"], + ["Memory", "Copy", benchmark_memory(), "MB/s"], + ["Disk", "Seq Write", write, "MB/s"], + ["Disk", "Rnd Write", read, "MB/s"] + ); + + info!("\n{}", table); + Ok(()) + } +} + +// Boilerplate +impl CliConfiguration for MachineCmd { + fn shared_params(&self) -> &SharedParams { + &self.shared_params + } +} diff --git a/utils/frame/benchmarking-cli/src/shared/mod.rs b/utils/frame/benchmarking-cli/src/shared/mod.rs index f08d79b9aafca..853fbdef8e87f 100644 --- a/utils/frame/benchmarking-cli/src/shared/mod.rs +++ b/utils/frame/benchmarking-cli/src/shared/mod.rs @@ -25,6 +25,8 @@ pub use record::BenchRecord; pub use stats::{StatSelect, Stats}; pub use weight_params::WeightParams; +use rand::prelude::*; + /// A Handlebars helper to add an underscore after every 3rd character, /// i.e. a separator for large numbers. #[derive(Clone, Copy)] @@ -63,3 +65,11 @@ where } s } + +/// Returns an rng and the seed that was used to create it. +/// +/// Uses a random seed if none is provided. +pub fn new_rng(seed: Option) -> (impl rand::Rng, u64) { + let seed = seed.unwrap_or(rand::thread_rng().gen::()); + (rand_pcg::Pcg64::seed_from_u64(seed), seed) +} diff --git a/utils/frame/benchmarking-cli/src/storage/cmd.rs b/utils/frame/benchmarking-cli/src/storage/cmd.rs index c2cc219ef1528..23222dbd120ab 100644 --- a/utils/frame/benchmarking-cli/src/storage/cmd.rs +++ b/utils/frame/benchmarking-cli/src/storage/cmd.rs @@ -34,7 +34,7 @@ use sp_runtime::generic::BlockId; use std::{fmt::Debug, path::PathBuf, sync::Arc}; use super::template::TemplateData; -use crate::shared::WeightParams; +use crate::shared::{new_rng, WeightParams}; /// Benchmark the storage speed of a chain snapshot. #[derive(Debug, Parser)] @@ -151,13 +151,6 @@ impl StorageCmd { } } - /// Creates an rng from a random seed. - pub(crate) fn setup_rng() -> impl rand::Rng { - let seed = rand::thread_rng().gen::(); - info!("Using seed {}", seed); - StdRng::seed_from_u64(seed) - } - /// Run some rounds of the (read) benchmark as warmup. /// See `frame_benchmarking_cli::storage::read::bench_read` for detailed comments. fn bench_warmup(&self, client: &Arc) -> Result<()> @@ -169,7 +162,7 @@ impl StorageCmd { let block = BlockId::Number(client.usage_info().chain.best_number); let empty_prefix = StorageKey(Vec::new()); let mut keys = client.storage_keys(&block, &empty_prefix)?; - let mut rng = Self::setup_rng(); + let (mut rng, _) = new_rng(None); keys.shuffle(&mut rng); for i in 0..self.params.warmups { diff --git a/utils/frame/benchmarking-cli/src/storage/read.rs b/utils/frame/benchmarking-cli/src/storage/read.rs index f58f3c3de0c19..c1dc6daba0953 100644 --- a/utils/frame/benchmarking-cli/src/storage/read.rs +++ b/utils/frame/benchmarking-cli/src/storage/read.rs @@ -28,7 +28,7 @@ use rand::prelude::*; use std::{fmt::Debug, sync::Arc, time::Instant}; use super::cmd::StorageCmd; -use crate::shared::BenchRecord; +use crate::shared::{new_rng, BenchRecord}; impl StorageCmd { /// Benchmarks the time it takes to read a single Storage item. @@ -47,7 +47,7 @@ impl StorageCmd { // Load all keys and randomly shuffle them. let empty_prefix = StorageKey(Vec::new()); let mut keys = client.storage_keys(&block, &empty_prefix)?; - let mut rng = Self::setup_rng(); + let (mut rng, _) = new_rng(None); keys.shuffle(&mut rng); // Interesting part here: diff --git a/utils/frame/benchmarking-cli/src/storage/write.rs b/utils/frame/benchmarking-cli/src/storage/write.rs index d5d5bc2fffa5b..ab25109a35d49 100644 --- a/utils/frame/benchmarking-cli/src/storage/write.rs +++ b/utils/frame/benchmarking-cli/src/storage/write.rs @@ -32,7 +32,7 @@ use rand::prelude::*; use std::{fmt::Debug, sync::Arc, time::Instant}; use super::cmd::StorageCmd; -use crate::shared::BenchRecord; +use crate::shared::{new_rng, BenchRecord}; impl StorageCmd { /// Benchmarks the time it takes to write a single Storage item. @@ -59,7 +59,7 @@ impl StorageCmd { info!("Preparing keys from block {}", block); // Load all KV pairs and randomly shuffle them. let mut kvs = trie.pairs(); - let mut rng = Self::setup_rng(); + let (mut rng, _) = new_rng(None); kvs.shuffle(&mut rng); // Generate all random values first; Make sure there are no collisions with existing From d0aed6be1593e88045940dc49544d124ee5b68d4 Mon Sep 17 00:00:00 2001 From: Dan Shields <35669742+NukeManDan@users.noreply.github.com> Date: Sat, 16 Apr 2022 02:55:37 -0700 Subject: [PATCH 127/484] Child bounties comments (#11053) * * formatting * use uniform notion of parent and child, no "master" or "general" entity * README updated to match comments * `parent_index` used over simply `index` * rm `parent_*` change * parent_bounty_id * parent_index rm * fmt * Apply suggestions from code review --- frame/bounties/README.md | 46 ++++++++++++++++++----------- frame/bounties/src/lib.rs | 24 ++++++++++----- frame/child-bounties/README.md | 30 ++++++++++++------- frame/child-bounties/src/lib.rs | 52 ++++++++++++++++----------------- 4 files changed, 90 insertions(+), 62 deletions(-) diff --git a/frame/bounties/README.md b/frame/bounties/README.md index bf63fca5f34b2..232334cb1edd6 100644 --- a/frame/bounties/README.md +++ b/frame/bounties/README.md @@ -2,28 +2,38 @@ ## Bounty -**Note :: This pallet is tightly coupled with pallet-treasury** +> NOTE: This pallet is tightly coupled with pallet-treasury. -A Bounty Spending is a reward for a specified body of work - or specified set of objectives - that -needs to be executed for a predefined Treasury amount to be paid out. A curator is assigned after -the bounty is approved and funded by Council, to be delegated with the responsibility of assigning a -payout address once the specified set of objectives is completed. +A Bounty Spending is a reward for a specified body of work - or specified set of objectives - +that needs to be executed for a predefined Treasury amount to be paid out. A curator is assigned +after the bounty is approved and funded by Council, to be delegated with the responsibility of +assigning a payout address once the specified set of objectives is completed. -After the Council has activated a bounty, it delegates the work that requires expertise to a curator -in exchange of a deposit. Once the curator accepts the bounty, they get to close the active bounty. -Closing the active bounty enacts a delayed payout to the payout address, the curator fee and the -return of the curator deposit. The delay allows for intervention through regular democracy. The -Council gets to unassign the curator, resulting in a new curator election. The Council also gets to -cancel the bounty if deemed necessary before assigning a curator or once the bounty is active or -payout is pending, resulting in the slash of the curator's deposit. +After the Council has activated a bounty, it delegates the work that requires expertise to a +curator in exchange of a deposit. Once the curator accepts the bounty, they get to close the +active bounty. Closing the active bounty enacts a delayed payout to the payout address, the +curator fee and the return of the curator deposit. The delay allows for intervention through +regular democracy. The Council gets to unassign the curator, resulting in a new curator +election. The Council also gets to cancel the bounty if deemed necessary before assigning a +curator or once the bounty is active or payout is pending, resulting in the slash of the +curator's deposit. + +This pallet may opt into using a [`ChildBountyManager`] that enables bounties to be split into +sub-bounties, as children of anh established bounty (called the parent in the context of it's +children). + +> NOTE: The parent bounty cannot be closed if it has a non-zero number of it has active child +> bounties associated with it. ### Terminology -- **Bounty spending proposal:** A proposal to reward a predefined body of work upon completion by - the Treasury. +Bounty: + +- **Bounty spending proposal:** A proposal to reward a predefined body of work upon completion + by the Treasury. - **Proposer:** An account proposing a bounty spending. -- **Curator:** An account managing the bounty and assigning a payout address receiving the reward - for the completion of work. +- **Curator:** An account managing the bounty and assigning a payout address receiving the + reward for the completion of work. - **Deposit:** The amount held on deposit for placing a bounty proposal plus the amount held on deposit per byte within the bounty description. - **Curator deposit:** The payment from a candidate willing to curate an approved bounty. The @@ -31,7 +41,8 @@ payout is pending, resulting in the slash of the curator's deposit. - **Bounty value:** The total amount that should be paid to the Payout Address if the bounty is rewarded. - **Payout address:** The account to which the total or part of the bounty is assigned to. -- **Payout Delay:** The delay period for which a bounty beneficiary needs to wait before claiming. +- **Payout Delay:** The delay period for which a bounty beneficiary needs to wait before + claiming. - **Curator fee:** The reserved upfront payment for a curator for work related to the bounty. ## Interface @@ -39,6 +50,7 @@ payout is pending, resulting in the slash of the curator's deposit. ### Dispatchable Functions Bounty protocol: + - `propose_bounty` - Propose a specific treasury amount to be earmarked for a predefined set of tasks and stake the required deposit. - `approve_bounty` - Accept a specific treasury amount to be earmarked for a predefined body of diff --git a/frame/bounties/src/lib.rs b/frame/bounties/src/lib.rs index 98f2da305a06d..886c758bbe42e 100644 --- a/frame/bounties/src/lib.rs +++ b/frame/bounties/src/lib.rs @@ -35,10 +35,17 @@ //! curator or once the bounty is active or payout is pending, resulting in the slash of the //! curator's deposit. //! +//! This pallet may opt into using a [`ChildBountyManager`] that enables bounties to be split into +//! sub-bounties, as children of anh established bounty (called the parent in the context of it's +//! children). +//! +//! > NOTE: The parent bounty cannot be closed if it has a non-zero number of it has active child +//! > bounties associated with it. //! //! ### Terminology //! //! Bounty: +//! //! - **Bounty spending proposal:** A proposal to reward a predefined body of work upon completion //! by the Treasury. //! - **Proposer:** An account proposing a bounty spending. @@ -60,6 +67,7 @@ //! ### Dispatchable Functions //! //! Bounty protocol: +//! //! - `propose_bounty` - Propose a specific treasury amount to be earmarked for a predefined set of //! tasks and stake the required deposit. //! - `approve_bounty` - Accept a specific treasury amount to be earmarked for a predefined body of @@ -165,9 +173,9 @@ pub enum BountyStatus { }, } -/// The child-bounty manager. +/// The child bounty manager. pub trait ChildBountyManager { - /// Get the active child-bounties for a parent bounty. + /// Get the active child bounties for a parent bounty. fn child_bounties_count(bounty_id: BountyIndex) -> BountyIndex; /// Get total curator fees of children-bounty curators. @@ -231,7 +239,7 @@ pub mod pallet { /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; - /// The child-bounty manager. + /// The child bounty manager. type ChildBountyManager: ChildBountyManager>; } @@ -256,7 +264,7 @@ pub mod pallet { PendingPayout, /// The bounties cannot be claimed/closed because it's still in the countdown period. Premature, - /// The bounty cannot be closed because it has active child-bounties. + /// The bounty cannot be closed because it has active child bounties. HasActiveChildBounty, /// Too many approvals are already queued. TooManyQueued, @@ -552,7 +560,7 @@ pub mod pallet { Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { let mut bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; - // Ensure no active child-bounties before processing the call. + // Ensure no active child bounties before processing the call. ensure!( T::ChildBountyManager::child_bounties_count(bounty_id) == 0, Error::::HasActiveChildBounty @@ -610,8 +618,8 @@ pub mod pallet { let err_amount = T::Currency::unreserve(&curator, bounty.curator_deposit); debug_assert!(err_amount.is_zero()); - // Get total child-bounties curator fees, and subtract it from master curator - // fee. + // Get total child bounties curator fees, and subtract it from the parent + // curator fee (the fee in present referenced bounty, `self`). let children_fee = T::ChildBountyManager::children_curator_fees(bounty_id); debug_assert!(children_fee <= fee); @@ -663,7 +671,7 @@ pub mod pallet { |maybe_bounty| -> DispatchResultWithPostInfo { let bounty = maybe_bounty.as_ref().ok_or(Error::::InvalidIndex)?; - // Ensure no active child-bounties before processing the call. + // Ensure no active child bounties before processing the call. ensure!( T::ChildBountyManager::child_bounties_count(bounty_id) == 0, Error::::HasActiveChildBounty diff --git a/frame/child-bounties/README.md b/frame/child-bounties/README.md index e07996d54957a..695b6616b1751 100644 --- a/frame/child-bounties/README.md +++ b/frame/child-bounties/README.md @@ -1,21 +1,29 @@ -# Child Bounties Pallet (pallet-child-bounties) +# Child Bounties Pallet ( `pallet-child-bounties` ) ## Child Bounty -> NOTE: This pallet is tightly coupled with pallet-treasury and pallet-bounties. +> NOTE: This pallet is tightly coupled with `pallet-treasury` and `pallet-bounties`. -With child bounties, a large bounty proposal can be divided into smaller chunks, for parallel execution, and for efficient governance and tracking of spent funds. - -A child-bounty is a smaller piece of work, extracted from a parent bounty. A curator is assigned after the child-bounty is created by the parent bounty curator, to be delegated with the responsibility of assigning a payout address once the specified set of tasks is completed. +With child bounties, a large bounty proposal can be divided into smaller chunks, +for parallel execution, and for efficient governance and tracking of spent funds. +A child bounty is a smaller piece of work, extracted from a parent bounty. +A curator is assigned after the child bounty is created by the parent bounty curator, +to be delegated with the responsibility of assigning a payout address once +the specified set of tasks is completed. ## Interface ### Dispatchable Functions -- `add_child_bounty` - Add a child-bounty for a parent-bounty to for dividing the work in smaller tasks. -- `propose_curator` - Assign an account to a child-bounty as candidate curator. -- `accept_curator` - Accept a child-bounty assignment from the parent-bounty curator, setting a curator deposit. +Child Bounty protocol: + +- `add_child_bounty` - Add a child bounty for a parent bounty to for dividing the work in + smaller tasks. +- `propose_curator` - Assign an account to a child bounty as candidate curator. +- `accept_curator` - Accept a child bounty assignment from the parent bounty curator, + setting a curator deposit. - `award_child_bounty` - Close and pay out the specified amount for the completed work. -- `claim_child_bounty` - Claim a specific child-bounty amount from the payout address. -- `unassign_curator` - Unassign an accepted curator from a specific child-bounty. -- `close_child_bounty` - Cancel the child-bounty for a specific treasury amount and close the bounty. +- `claim_child_bounty` - Claim a specific child bounty amount from the payout address. +- `unassign_curator` - Unassign an accepted curator from a specific child bounty. +- `close_child_bounty` - Cancel the child bounty for a specific treasury amount + and close the bounty. diff --git a/frame/child-bounties/src/lib.rs b/frame/child-bounties/src/lib.rs index 2fea61c045cf6..0bbd4512443d7 100644 --- a/frame/child-bounties/src/lib.rs +++ b/frame/child-bounties/src/lib.rs @@ -15,16 +15,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! # Child Bounties Pallet ( pallet-child-bounties ) +//! # Child Bounties Pallet ( `pallet-child-bounties` ) //! //! ## Child Bounty //! -//! > NOTE: This pallet is tightly coupled with pallet-treasury and pallet-bounties. +//! > NOTE: This pallet is tightly coupled with `pallet-treasury` and `pallet-bounties`. //! //! With child bounties, a large bounty proposal can be divided into smaller chunks, //! for parallel execution, and for efficient governance and tracking of spent funds. -//! A child-bounty is a smaller piece of work, extracted from a parent bounty. -//! A curator is assigned after the child-bounty is created by the parent bounty curator, +//! A child bounty is a smaller piece of work, extracted from a parent bounty. +//! A curator is assigned after the child bounty is created by the parent bounty curator, //! to be delegated with the responsibility of assigning a payout address once the specified //! set of tasks is completed. //! @@ -33,22 +33,22 @@ //! ### Dispatchable Functions //! //! Child Bounty protocol: -//! - `add_child_bounty` - Add a child-bounty for a parent-bounty to for dividing the work in +//! - `add_child_bounty` - Add a child bounty for a parent bounty to for dividing the work in //! smaller tasks. -//! - `propose_curator` - Assign an account to a child-bounty as candidate curator. -//! - `accept_curator` - Accept a child-bounty assignment from the parent-bounty curator, setting a +//! - `propose_curator` - Assign an account to a child bounty as candidate curator. +//! - `accept_curator` - Accept a child bounty assignment from the parent bounty curator, setting a //! curator deposit. //! - `award_child_bounty` - Close and pay out the specified amount for the completed work. -//! - `claim_child_bounty` - Claim a specific child-bounty amount from the payout address. -//! - `unassign_curator` - Unassign an accepted curator from a specific child-bounty. -//! - `close_child_bounty` - Cancel the child-bounty for a specific treasury amount and close the +//! - `claim_child_bounty` - Claim a specific child bounty amount from the payout address. +//! - `unassign_curator` - Unassign an accepted curator from a specific child bounty. +//! - `close_child_bounty` - Cancel the child bounty for a specific treasury amount and close the //! bounty. // Most of the business logic in this pallet has been // originally contributed by "https://github.com/shamb0", // as part of the PR - https://github.com/paritytech/substrate/pull/7965. // The code has been moved here and then refactored in order to -// extract child-bounties as a separate pallet. +// extract child bounties as a separate pallet. #![cfg_attr(not(feature = "std"), no_std)] @@ -101,7 +101,7 @@ pub struct ChildBounty { pub enum ChildBountyStatus { /// The child-bounty is added and waiting for curator assignment. Added, - /// A curator has been proposed by the parent-bounty curator. Waiting for + /// A curator has been proposed by the parent bounty curator. Waiting for /// acceptance from the child-bounty curator. CuratorProposed { /// The assigned child-bounty curator of this bounty. @@ -136,7 +136,7 @@ pub mod pallet { pub trait Config: frame_system::Config + pallet_treasury::Config + pallet_bounties::Config { - /// Maximum number of child-bounties that can be added to a parent bounty. + /// Maximum number of child bounties that can be added to a parent bounty. #[pallet::constant] type MaxActiveChildBountyCount: Get; @@ -157,7 +157,7 @@ pub mod pallet { ParentBountyNotActive, /// The bounty balance is not enough to add new child-bounty. InsufficientBountyBalance, - /// Number of child-bounties exceeds limit `MaxActiveChildBountyCount`. + /// Number of child bounties exceeds limit `MaxActiveChildBountyCount`. TooManyChildBounties, } @@ -184,14 +184,14 @@ pub mod pallet { #[pallet::getter(fn child_bounty_count)] pub type ChildBountyCount = StorageValue<_, BountyIndex, ValueQuery>; - /// Number of child-bounties per parent bounty. + /// Number of child bounties per parent bounty. /// Map of parent bounty index to number of child bounties. #[pallet::storage] #[pallet::getter(fn parent_child_bounties)] pub type ParentChildBounties = StorageMap<_, Twox64Concat, BountyIndex, u32, ValueQuery>; - /// Child-bounties that have been added. + /// Child bounties that have been added. #[pallet::storage] #[pallet::getter(fn child_bounties)] pub type ChildBounties = StorageDoubleMap< @@ -226,7 +226,7 @@ pub mod pallet { /// parent bounty to child-bounty account, if parent bounty has enough /// funds, else the call fails. /// - /// Upper bound to maximum number of active child-bounties that can be + /// Upper bound to maximum number of active child bounties that can be /// added are managed via runtime trait config /// [`Config::MaxActiveChildBountyCount`]. /// @@ -427,12 +427,12 @@ pub mod pallet { /// the curator of the parent bounty, or any signed origin. /// /// For the origin other than T::RejectOrigin and the child-bounty - /// curator, parent-bounty must be in active state, for this call to + /// curator, parent bounty must be in active state, for this call to /// work. We allow child-bounty curator and T::RejectOrigin to execute - /// this call irrespective of the parent-bounty state. + /// this call irrespective of the parent bounty state. /// /// If this function is called by the `RejectOrigin` or the - /// parent-bounty curator, we assume that the child-bounty curator is + /// parent bounty curator, we assume that the child-bounty curator is /// malicious or inactive. As a result, child-bounty curator deposit is /// slashed. /// @@ -486,7 +486,7 @@ pub mod pallet { }, ChildBountyStatus::CuratorProposed { ref curator } => { // A child-bounty curator has been proposed, but not accepted yet. - // Either `RejectOrigin`, parent-bounty curator or the proposed + // Either `RejectOrigin`, parent bounty curator or the proposed // child-bounty curator can unassign the child-bounty curator. ensure!( maybe_sender.map_or(true, |sender| { @@ -524,7 +524,7 @@ pub mod pallet { update_due < frame_system::Pallet::::block_number() { // Slash the child-bounty curator if - // + the call is made by the parent-bounty curator. + // + the call is made by the parent bounty curator. // + or the curator is inactive. slash_curator(curator, &mut child_bounty.curator_deposit); // Continue to change bounty status below. @@ -556,7 +556,7 @@ pub mod pallet { /// /// The beneficiary will be able to claim the funds after a delay. /// - /// The dispatch origin for this call must be the master curator or + /// The dispatch origin for this call must be the parent curator or /// curator of this child-bounty. /// /// Parent bounty must be in active state, for this child-bounty call to @@ -835,7 +835,7 @@ impl Pallet { // Nothing extra to do besides the removal of the child-bounty below. }, ChildBountyStatus::Active { curator } => { - // Cancelled by master curator or RejectOrigin, + // Cancelled by parent curator or RejectOrigin, // refund deposit of the working child-bounty curator. let _ = T::Currency::unreserve(curator, child_bounty.curator_deposit); // Then execute removal of the child-bounty below. @@ -850,7 +850,7 @@ impl Pallet { }, } - // Revert the curator fee back to parent-bounty curator & + // Revert the curator fee back to parent bounty curator & // reduce the active child-bounty count. ChildrenCuratorFees::::mutate(parent_bounty_id, |value| { *value = value.saturating_sub(child_bounty.fee) @@ -888,7 +888,7 @@ impl Pallet { } // Implement ChildBountyManager to connect with the bounties pallet. This is -// where we pass the active child-bounties and child curator fees to the parent +// where we pass the active child bounties and child curator fees to the parent // bounty. impl pallet_bounties::ChildBountyManager> for Pallet { fn child_bounties_count( From dcf0e79c183460ce4427cbbe46c5e7be33f20487 Mon Sep 17 00:00:00 2001 From: Alexander Gryaznov Date: Sat, 16 Apr 2022 15:51:40 +0300 Subject: [PATCH 128/484] Add `frame_support::crypto::ecdsa::Public.to_eth_address()` (`k256`-based) and use it in pallets (#11087) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * `ecdsa::Public::to_eth_address` + test, beefy-mmr `convert()` to use it, contracts Ext interface * `seal_ecdsa_to_eth_address` all but benchmark done * `seal_ecdsa_to_eth_address` + wasm test * `seal_ecdsa_to_eth_address` + benchmark * fixed dependencies * Apply suggestions from code review Co-authored-by: Alexander Theißen * fixes from review #1 * ecdsa::Public(*pk).to_eth_address() moved to frame_support and contracts to use it * beefy-mmr to use newly added frame_support function for convertion * a doc fix * import fix * benchmark fix-1 (still fails) * benchmark fixed * Apply suggestions from code review Co-authored-by: Alexander Theißen * fixes on Alex T feedback * to_eth_address() put into extension trait for sp-core::ecdsa::Public * Update frame/support/src/crypto/ecdsa.rs Co-authored-by: Alexander Theißen * Update frame/contracts/src/wasm/mod.rs Co-authored-by: Alexander Theißen * fixes on issues pointed out in review * benchmark errors fixed * fmt fix * EcdsaRecoverFailed err docs updated * Apply suggestions from code review Co-authored-by: Bastian Köcher * make applied suggestions compile * get rid of unwrap() in runtime * Remove expect Co-authored-by: Alexander Theißen Co-authored-by: Bastian Köcher Co-authored-by: Bastian Köcher --- Cargo.lock | 43 +++++++--------------- frame/beefy-mmr/Cargo.toml | 2 - frame/beefy-mmr/src/lib.rs | 19 ++++------ frame/contracts/src/benchmarking/mod.rs | 38 +++++++++++++++++++ frame/contracts/src/exec.rs | 43 +++++++++++++++++++++- frame/contracts/src/schedule.rs | 4 ++ frame/contracts/src/wasm/mod.rs | 39 ++++++++++++++++++++ frame/contracts/src/wasm/runtime.rs | 48 +++++++++++++++++++++--- frame/contracts/src/weights.rs | 23 ++++++++++++ frame/support/Cargo.toml | 1 + frame/support/src/crypto.rs | 21 +++++++++++ frame/support/src/crypto/ecdsa.rs | 49 +++++++++++++++++++++++++ frame/support/src/lib.rs | 1 + 13 files changed, 283 insertions(+), 48 deletions(-) create mode 100644 frame/support/src/crypto.rs create mode 100644 frame/support/src/crypto/ecdsa.rs diff --git a/Cargo.lock b/Cargo.lock index 52c6daad6b422..3e37bcfd167e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -456,12 +456,6 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" -[[package]] -name = "base64ct" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874f8444adcb4952a8bc51305c8be95c8ec8237bb0d2e78d2e039f771f8828a0" - [[package]] name = "beef" version = "0.5.1" @@ -1805,6 +1799,7 @@ checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" dependencies = [ "der", "elliptic-curve", + "rfc6979", "signature", ] @@ -2311,6 +2306,7 @@ dependencies = [ "frame-support-procedural", "frame-system", "impl-trait-for-tuples", + "k256", "log 0.4.14", "once_cell", "parity-scale-codec", @@ -5667,7 +5663,6 @@ dependencies = [ "frame-system", "hex", "hex-literal", - "k256", "log 0.4.14", "pallet-beefy", "pallet-mmr", @@ -7071,17 +7066,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "pkcs8" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" -dependencies = [ - "der", - "spki", - "zeroize", -] - [[package]] name = "pkg-config" version = "0.3.19" @@ -7871,6 +7855,17 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "448296241d034b96c11173591deaa1302f2c17b56092106c1f92c1bc0183a8c9" +[[package]] +name = "rfc6979" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" +dependencies = [ + "crypto-bigint", + "hmac 0.11.0", + "zeroize", +] + [[package]] name = "ring" version = "0.16.20" @@ -9377,7 +9372,6 @@ checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" dependencies = [ "der", "generic-array 0.14.4", - "pkcs8", "subtle", "zeroize", ] @@ -9662,6 +9656,7 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" dependencies = [ + "digest 0.9.0", "rand_core 0.6.2", ] @@ -10588,16 +10583,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" -[[package]] -name = "spki" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" -dependencies = [ - "base64ct", - "der", -] - [[package]] name = "ss58-registry" version = "1.11.0" diff --git a/frame/beefy-mmr/Cargo.toml b/frame/beefy-mmr/Cargo.toml index a32b1dc6dbd64..9da8198b405f7 100644 --- a/frame/beefy-mmr/Cargo.toml +++ b/frame/beefy-mmr/Cargo.toml @@ -10,7 +10,6 @@ repository = "https://github.com/paritytech/substrate" [dependencies] hex = { version = "0.4", optional = true } codec = { version = "3.0.0", package = "parity-scale-codec", default-features = false, features = ["derive"] } -k256 = { version = "0.10.2", default-features = false, features = ["arithmetic"] } log = { version = "0.4.13", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", optional = true } @@ -42,7 +41,6 @@ std = [ "frame-support/std", "frame-system/std", "hex", - "k256/std", "log/std", "pallet-beefy/std", "pallet-mmr/std", diff --git a/frame/beefy-mmr/src/lib.rs b/frame/beefy-mmr/src/lib.rs index 640ebeb7d49cc..8b904d6aefd5f 100644 --- a/frame/beefy-mmr/src/lib.rs +++ b/frame/beefy-mmr/src/lib.rs @@ -39,7 +39,7 @@ use sp_std::prelude::*; use beefy_primitives::mmr::{BeefyDataProvider, BeefyNextAuthoritySet, MmrLeaf, MmrLeafVersion}; use pallet_mmr::{LeafDataProvider, ParentNumberAndHash}; -use frame_support::traits::Get; +use frame_support::{crypto::ecdsa::ECDSAExt, traits::Get}; pub use pallet::*; @@ -71,19 +71,16 @@ where pub struct BeefyEcdsaToEthereum; impl Convert> for BeefyEcdsaToEthereum { fn convert(a: beefy_primitives::crypto::AuthorityId) -> Vec { - use k256::{elliptic_curve::sec1::ToEncodedPoint, PublicKey}; - use sp_core::crypto::ByteArray; - - PublicKey::from_sec1_bytes(a.as_slice()) - .map(|pub_key| { - // uncompress the key - let uncompressed = pub_key.to_encoded_point(false); - // convert to ETH address - sp_io::hashing::keccak_256(&uncompressed.as_bytes()[1..])[12..].to_vec() - }) + sp_core::ecdsa::Public::try_from(a.as_ref()) .map_err(|_| { log::error!(target: "runtime::beefy", "Invalid BEEFY PublicKey format!"); }) + .unwrap_or(sp_core::ecdsa::Public::from_raw([0u8; 33])) + .to_eth_address() + .map(|v| v.to_vec()) + .map_err(|_| { + log::error!(target: "runtime::beefy", "Failed to convert BEEFY PublicKey to ETH address!"); + }) .unwrap_or_default() } } diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs index de83f51a01528..e67436fbc9d37 100644 --- a/frame/contracts/src/benchmarking/mod.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -1963,6 +1963,44 @@ benchmarks! { let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + // Only calling the function itself for the list of + // generated different ECDSA keys. + seal_ecdsa_to_eth_address { + let r in 0 .. API_BENCHMARK_BATCHES; + let key_type = sp_core::crypto::KeyTypeId(*b"code"); + let pub_keys_bytes = (0..r * API_BENCHMARK_BATCH_SIZE) + .map(|_| { + sp_io::crypto::ecdsa_generate(key_type, None).0 + }) + .flatten() + .collect::>(); + let pub_keys_bytes_len = pub_keys_bytes.len() as i32; + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + module: "__unstable__", + name: "seal_ecdsa_to_eth_address", + params: vec![ValueType::I32, ValueType::I32], + return_type: Some(ValueType::I32), + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: pub_keys_bytes, + }, + ], + call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + Counter(0, 33), // pub_key_ptr + Regular(Instruction::I32Const(pub_keys_bytes_len)), // out_ptr + Regular(Instruction::Call(0)), + Regular(Instruction::Drop), + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![])?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + seal_set_code_hash { let r in 0 .. API_BENCHMARK_BATCHES; let code_hashes = (0..r * API_BENCHMARK_BATCH_SIZE) diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index 7aa5c0b731fad..54a5223b53d21 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -22,6 +22,7 @@ use crate::{ Pallet as Contracts, Schedule, }; use frame_support::{ + crypto::ecdsa::ECDSAExt, dispatch::{DispatchError, DispatchResult, DispatchResultWithPostInfo, Dispatchable}, storage::{with_transaction, TransactionOutcome}, traits::{Contains, Currency, ExistenceRequirement, OriginTrait, Randomness, Time}, @@ -30,7 +31,7 @@ use frame_support::{ use frame_system::RawOrigin; use pallet_contracts_primitives::ExecReturnValue; use smallvec::{Array, SmallVec}; -use sp_core::crypto::UncheckedFrom; +use sp_core::{crypto::UncheckedFrom, ecdsa::Public as ECDSAPublic}; use sp_io::crypto::secp256k1_ecdsa_recover_compressed; use sp_runtime::traits::Convert; use sp_std::{marker::PhantomData, mem, prelude::*}; @@ -232,6 +233,9 @@ pub trait Ext: sealing::Sealed { /// Recovers ECDSA compressed public key based on signature and message hash. fn ecdsa_recover(&self, signature: &[u8; 65], message_hash: &[u8; 32]) -> Result<[u8; 33], ()>; + /// Returns Ethereum address from the ECDSA compressed public key. + fn ecdsa_to_eth_address(&self, pk: &[u8; 33]) -> Result<[u8; 20], ()>; + /// Tests sometimes need to modify and inspect the contract info directly. #[cfg(test)] fn contract_info(&mut self) -> &mut ContractInfo; @@ -1204,6 +1208,10 @@ where secp256k1_ecdsa_recover_compressed(&signature, &message_hash).map_err(|_| ()) } + fn ecdsa_to_eth_address(&self, pk: &[u8; 33]) -> Result<[u8; 20], ()> { + ECDSAPublic(*pk).to_eth_address() + } + #[cfg(test)] fn contract_info(&mut self) -> &mut ContractInfo { self.top_frame_mut().contract_info() @@ -1267,6 +1275,7 @@ mod tests { use codec::{Decode, Encode}; use frame_support::{assert_err, assert_ok}; use frame_system::{EventRecord, Phase}; + use hex_literal::hex; use pallet_contracts_primitives::ReturnFlags; use pretty_assertions::assert_eq; use sp_core::Bytes; @@ -2718,4 +2727,36 @@ mod tests { )); }); } + #[test] + fn ecdsa_to_eth_address_returns_proper_value() { + let bob_ch = MockLoader::insert(Call, |ctx, _| { + let pubkey_compressed: [u8; 33] = + hex!("028db55b05db86c0b1786ca49f095d76344c9e6056b2f02701a7e7f3c20aabfd91")[..] + .try_into() + .unwrap(); + assert_eq!( + ctx.ext.ecdsa_to_eth_address(&pubkey_compressed).unwrap(), + hex!("09231da7b19A016f9e576d23B16277062F4d46A8")[..] + ); + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + let schedule = ::Schedule::get(); + place_contract(&BOB, bob_ch); + + let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); + let result = MockStack::run_call( + ALICE, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + &schedule, + 0, + vec![], + None, + ); + assert_matches!(result, Ok(_)); + }); + } } diff --git a/frame/contracts/src/schedule.rs b/frame/contracts/src/schedule.rs index b0c58d721d578..bbbeb72f2cdf6 100644 --- a/frame/contracts/src/schedule.rs +++ b/frame/contracts/src/schedule.rs @@ -418,6 +418,9 @@ pub struct HostFnWeights { /// Weight of calling `seal_ecdsa_recover`. pub ecdsa_recover: Weight, + /// Weight of calling `seal_ecdsa_to_eth_address`. + pub ecdsa_to_eth_address: Weight, + /// The type parameter is used in the default implementation. #[codec(skip)] pub _phantom: PhantomData, @@ -653,6 +656,7 @@ impl Default for HostFnWeights { hash_blake2_128: cost_batched!(seal_hash_blake2_128), hash_blake2_128_per_byte: cost_byte_batched!(seal_hash_blake2_128_per_kb), ecdsa_recover: cost_batched!(seal_ecdsa_recover), + ecdsa_to_eth_address: cost_batched!(seal_ecdsa_to_eth_address), _phantom: PhantomData, } } diff --git a/frame/contracts/src/wasm/mod.rs b/frame/contracts/src/wasm/mod.rs index c38613cb68102..f12df06b938c8 100644 --- a/frame/contracts/src/wasm/mod.rs +++ b/frame/contracts/src/wasm/mod.rs @@ -503,6 +503,9 @@ mod tests { fn contract_info(&mut self) -> &mut crate::ContractInfo { unimplemented!() } + fn ecdsa_to_eth_address(&self, _pk: &[u8; 33]) -> Result<[u8; 20], ()> { + Ok([2u8; 20]) + } } fn execute>(wat: &str, input_data: Vec, mut ext: E) -> ExecResult { @@ -1085,6 +1088,42 @@ mod tests { assert_eq!(mock_ext.ecdsa_recover.into_inner(), [([1; 65], [1; 32])]); } + #[test] + #[cfg(feature = "unstable-interface")] + fn contract_ecdsa_to_eth_address() { + /// calls `seal_ecdsa_to_eth_address` for the contstant and ensures the result equals the + /// expected one. + const CODE_ECDSA_TO_ETH_ADDRESS: &str = r#" +(module + (import "__unstable__" "seal_ecdsa_to_eth_address" (func $seal_ecdsa_to_eth_address (param i32 i32) (result i32))) + (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) + (import "env" "memory" (memory 1 1)) + + (func (export "call") + ;; fill the buffer with the eth address. + (call $seal_ecdsa_to_eth_address (i32.const 0) (i32.const 0)) + + ;; Return the contents of the buffer + (call $seal_return + (i32.const 0) + (i32.const 0) + (i32.const 20) + ) + + ;; seal_return doesn't return, so this is effectively unreachable. + (unreachable) + ) + (func (export "deploy")) +) +"#; + + let output = execute(CODE_ECDSA_TO_ETH_ADDRESS, vec![], MockExt::default()).unwrap(); + assert_eq!( + output, + ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes([0x02; 20].to_vec()) } + ); + } + const CODE_GET_STORAGE: &str = r#" (module (import "seal0" "seal_get_storage" (func $seal_get_storage (param i32 i32 i32) (result i32))) diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index 975cfcdd12db8..876bd8848b0b1 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -71,7 +71,9 @@ pub enum ReturnCode { /// The call dispatched by `seal_call_runtime` was executed but returned an error. #[cfg(feature = "unstable-interface")] CallRuntimeReturnedError = 10, - /// ECDSA pubkey recovery failed. Most probably wrong recovery id or signature. + /// ECDSA pubkey recovery failed (most probably wrong recovery id or signature), or + /// ECDSA compressed pubkey conversion into Ethereum address failed (most probably + /// wrong pubkey provided). #[cfg(feature = "unstable-interface")] EcdsaRecoverFailed = 11, } @@ -225,6 +227,9 @@ pub enum RuntimeCosts { /// Weight of calling `seal_set_code_hash` #[cfg(feature = "unstable-interface")] SetCodeHash, + /// Weight of calling `ecdsa_to_eth_address` + #[cfg(feature = "unstable-interface")] + EcdsaToEthAddress, } impl RuntimeCosts { @@ -309,6 +314,8 @@ impl RuntimeCosts { CallRuntime(weight) => weight, #[cfg(feature = "unstable-interface")] SetCodeHash => s.set_code_hash, + #[cfg(feature = "unstable-interface")] + EcdsaToEthAddress => s.ecdsa_to_eth_address, }; RuntimeToken { #[cfg(test)] @@ -1984,12 +1991,12 @@ define_env!(Env, , // # Parameters // // - `signature_ptr`: the pointer into the linear memory where the signature - // is placed. Should be decodable as a 65 bytes. Traps otherwise. + // is placed. Should be decodable as a 65 bytes. Traps otherwise. // - `message_hash_ptr`: the pointer into the linear memory where the message // hash is placed. Should be decodable as a 32 bytes. Traps otherwise. // - `output_ptr`: the pointer into the linear memory where the output - // data is placed. The buffer should be 33 bytes. Traps otherwise. - // The function will write the result directly into this buffer. + // data is placed. The buffer should be 33 bytes. The function + // will write the result directly into this buffer. // // # Errors // @@ -2036,7 +2043,7 @@ define_env!(Env, , // // # Parameters // - // - code_hash_ptr: A pointer to the buffer that contains the new code hash. + // - `code_hash_ptr`: A pointer to the buffer that contains the new code hash. // // # Errors // @@ -2052,4 +2059,35 @@ define_env!(Env, , Ok(()) => Ok(ReturnCode::Success) } }, + + // Calculates Ethereum address from the ECDSA compressed public key and stores + // it into the supplied buffer. + // + // # Parameters + // + // - `key_ptr`: a pointer to the ECDSA compressed public key. Should be decodable as a 33 bytes value. + // Traps otherwise. + // - `out_ptr`: the pointer into the linear memory where the output + // data is placed. The function will write the result + // directly into this buffer. + // + // The value is stored to linear memory at the address pointed to by `out_ptr`. + // If the available space at `out_ptr` is less than the size of the value a trap is triggered. + // + // # Errors + // + // `ReturnCode::EcdsaRecoverFailed` + [__unstable__] seal_ecdsa_to_eth_address(ctx, key_ptr: u32, out_ptr: u32) -> ReturnCode => { + ctx.charge_gas(RuntimeCosts::EcdsaToEthAddress)?; + let mut compressed_key: [u8; 33] = [0;33]; + ctx.read_sandbox_memory_into_buf(key_ptr, &mut compressed_key)?; + let result = ctx.ext.ecdsa_to_eth_address(&compressed_key); + match result { + Ok(eth_address) => { + ctx.write_sandbox_memory(out_ptr, eth_address.as_ref())?; + Ok(ReturnCode::Success) + }, + Err(_) => Ok(ReturnCode::EcdsaRecoverFailed), + } + }, ); diff --git a/frame/contracts/src/weights.rs b/frame/contracts/src/weights.rs index 43f00196ab3b2..85ff2548ca698 100644 --- a/frame/contracts/src/weights.rs +++ b/frame/contracts/src/weights.rs @@ -103,6 +103,7 @@ pub trait WeightInfo { fn seal_hash_blake2_128(r: u32, ) -> Weight; fn seal_hash_blake2_128_per_kb(n: u32, ) -> Weight; fn seal_ecdsa_recover(r: u32, ) -> Weight; + fn seal_ecdsa_to_eth_address(r: u32, ) -> Weight; fn seal_set_code_hash(r: u32, ) -> Weight; fn instr_i64const(r: u32, ) -> Weight; fn instr_i64load(r: u32, ) -> Weight; @@ -805,6 +806,17 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + fn seal_ecdsa_to_eth_address(r: u32, ) -> Weight { + (272_893_000 as Weight) + // Standard Error: 1_438_000 + .saturating_add((15_412_877_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: System Account (r:1 w:0) + // Storage: Contracts ContractInfoOf (r:1 w:1) + // Storage: Contracts CodeStorage (r:1 w:0) + // Storage: Timestamp Now (r:1 w:0) // Storage: Contracts OwnerInfoOf (r:36 w:36) fn seal_set_code_hash(r: u32, ) -> Weight { (0 as Weight) @@ -1717,6 +1729,17 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + fn seal_ecdsa_to_eth_address(r: u32, ) -> Weight { + (272_893_000 as Weight) + // Standard Error: 1_438_000 + .saturating_add((15_412_877_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(RocksDbWeight::get().reads(4 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: System Account (r:1 w:0) + // Storage: Contracts ContractInfoOf (r:1 w:1) + // Storage: Contracts CodeStorage (r:1 w:0) + // Storage: Timestamp Now (r:1 w:0) // Storage: Contracts OwnerInfoOf (r:36 w:36) fn seal_set_code_hash(r: u32, ) -> Weight { (0 as Weight) diff --git a/frame/support/Cargo.toml b/frame/support/Cargo.toml index ec596494c601b..ce04fddb8521f 100644 --- a/frame/support/Cargo.toml +++ b/frame/support/Cargo.toml @@ -35,6 +35,7 @@ impl-trait-for-tuples = "0.2.2" smallvec = "1.8.0" log = { version = "0.4.14", default-features = false } sp-core-hashing-proc-macro = { version = "5.0.0", path = "../../primitives/core/hashing/proc-macro" } +k256 = { version = "0.10.2", default-features = false, features = ["ecdsa"] } [dev-dependencies] assert_matches = "1.3.0" diff --git a/frame/support/src/crypto.rs b/frame/support/src/crypto.rs new file mode 100644 index 0000000000000..182a784649d02 --- /dev/null +++ b/frame/support/src/crypto.rs @@ -0,0 +1,21 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Utilities for dealing with crypto primitives. Sometimes we need to use these from inside WASM +//! contracts, where crypto calculations have weak performance. + +pub mod ecdsa; diff --git a/frame/support/src/crypto/ecdsa.rs b/frame/support/src/crypto/ecdsa.rs new file mode 100644 index 0000000000000..a4a04acabe1df --- /dev/null +++ b/frame/support/src/crypto/ecdsa.rs @@ -0,0 +1,49 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Simple ECDSA secp256k1 API. +//! +//! Provides an extension trait for [`sp_core::ecdsa::Public`] to do certain operations. + +use sp_core::{crypto::ByteArray, ecdsa::Public}; + +/// Extension trait for [`Public`] to be used from inside the runtime. +/// +/// # Note +/// +/// This is needed because host functions cannot be called from within +/// `sp_core` due to cyclic dependencies on `sp_io`. +pub trait ECDSAExt { + /// Returns Ethereum address calculated from this ECDSA public key. + fn to_eth_address(&self) -> Result<[u8; 20], ()>; +} + +impl ECDSAExt for Public { + fn to_eth_address(&self) -> Result<[u8; 20], ()> { + use k256::{elliptic_curve::sec1::ToEncodedPoint, PublicKey}; + + PublicKey::from_sec1_bytes(self.as_slice()).map_err(drop).and_then(|pub_key| { + // uncompress the key + let uncompressed = pub_key.to_encoded_point(false); + // convert to ETH address + <[u8; 20]>::try_from( + sp_io::hashing::keccak_256(&uncompressed.as_bytes()[1..])[12..].as_ref(), + ) + .map_err(drop) + }) + } +} diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index 714449eec7847..4484ff6b6b295 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -64,6 +64,7 @@ pub mod event; pub mod inherent; #[macro_use] pub mod error; +pub mod crypto; pub mod instances; pub mod migrations; pub mod traits; From 6d28bc01b75eba5a48756e1a07dd1d0e71c91208 Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Mon, 18 Apr 2022 12:39:32 +0200 Subject: [PATCH 129/484] Remark storage (#10698) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remark storage * Fixed benches * Update frame/remark/src/lib.rs Co-authored-by: Bastian Köcher * Fixed build * Fixed build Co-authored-by: Bastian Köcher --- Cargo.lock | 17 ++++++ Cargo.toml | 1 + bin/node/runtime/Cargo.toml | 3 + bin/node/runtime/src/lib.rs | 7 +++ frame/remark/Cargo.toml | 42 ++++++++++++++ frame/remark/README.md | 6 ++ frame/remark/src/benchmarking.rs | 47 +++++++++++++++ frame/remark/src/lib.rs | 85 ++++++++++++++++++++++++++++ frame/remark/src/mock.rs | 79 ++++++++++++++++++++++++++ frame/remark/src/tests.rs | 55 ++++++++++++++++++ frame/remark/src/weights.rs | 71 +++++++++++++++++++++++ frame/transaction-storage/src/lib.rs | 2 +- 12 files changed, 414 insertions(+), 1 deletion(-) create mode 100644 frame/remark/Cargo.toml create mode 100644 frame/remark/README.md create mode 100644 frame/remark/src/benchmarking.rs create mode 100644 frame/remark/src/lib.rs create mode 100644 frame/remark/src/mock.rs create mode 100644 frame/remark/src/tests.rs create mode 100644 frame/remark/src/weights.rs diff --git a/Cargo.lock b/Cargo.lock index 3e37bcfd167e4..84872ce141da4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5032,6 +5032,7 @@ dependencies = [ "pallet-randomness-collective-flip", "pallet-recovery", "pallet-referenda", + "pallet-remark", "pallet-scheduler", "pallet-session", "pallet-session-benchmarking", @@ -6306,6 +6307,22 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-remark" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-scheduler" version = "4.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index c281913cd55ed..5cc90ec6f183b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -112,6 +112,7 @@ members = [ "frame/randomness-collective-flip", "frame/recovery", "frame/referenda", + "frame/remark", "frame/scheduler", "frame/scored-pool", "frame/session", diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 41b2402d33a53..75f860eb240e8 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -83,6 +83,7 @@ pallet-proxy = { version = "4.0.0-dev", default-features = false, path = "../../ pallet-randomness-collective-flip = { version = "4.0.0-dev", default-features = false, path = "../../../frame/randomness-collective-flip" } pallet-recovery = { version = "4.0.0-dev", default-features = false, path = "../../../frame/recovery" } pallet-referenda = { version = "4.0.0-dev", default-features = false, path = "../../../frame/referenda" } +pallet-remark = { version = "4.0.0-dev", default-features = false, path = "../../../frame/remark" } pallet-session = { version = "4.0.0-dev", features = [ "historical" ], path = "../../../frame/session", default-features = false } pallet-session-benchmarking = { version = "4.0.0-dev", path = "../../../frame/session/benchmarking", default-features = false, optional = true } pallet-staking = { version = "4.0.0-dev", default-features = false, path = "../../../frame/staking" } @@ -173,6 +174,7 @@ std = [ "sp-version/std", "pallet-society/std", "pallet-referenda/std", + "pallet-remark/std", "pallet-recovery/std", "pallet-uniques/std", "pallet-vesting/std", @@ -213,6 +215,7 @@ runtime-benchmarks = [ "pallet-proxy/runtime-benchmarks", "pallet-scheduler/runtime-benchmarks", "pallet-referenda/runtime-benchmarks", + "pallet-remark/runtime-benchmarks", "pallet-session-benchmarking", "pallet-society/runtime-benchmarks", "pallet-staking/runtime-benchmarks", diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index f37345014f3a1..dca386b303610 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -778,6 +778,11 @@ impl pallet_referenda::Config for Runtime { type Tracks = TracksInfo; } +impl pallet_remark::Config for Runtime { + type WeightInfo = pallet_remark::weights::SubstrateWeight; + type Event = Event; +} + parameter_types! { pub const LaunchPeriod: BlockNumber = 28 * 24 * 60 * MINUTES; pub const VotingPeriod: BlockNumber = 28 * 24 * 60 * MINUTES; @@ -1457,6 +1462,7 @@ construct_runtime!( StateTrieMigration: pallet_state_trie_migration, ChildBounties: pallet_child_bounties, Referenda: pallet_referenda, + Remark: pallet_remark, ConvictionVoting: pallet_conviction_voting, Whitelist: pallet_whitelist, } @@ -1547,6 +1553,7 @@ mod benches { [pallet_preimage, Preimage] [pallet_proxy, Proxy] [pallet_referenda, Referenda] + [pallet_remark, Remark] [pallet_scheduler, Scheduler] [pallet_session, SessionBench::] [pallet_staking, Staking] diff --git a/frame/remark/Cargo.toml b/frame/remark/Cargo.toml new file mode 100644 index 0000000000000..82c76d679cc10 --- /dev/null +++ b/frame/remark/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "pallet-remark" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Remark storage pallet" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +serde = { version = "1.0.136", optional = true } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } +sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } + +[dev-dependencies] +sp-core = { version = "6.0.0", path = "../../primitives/core", default-features = false } + +[features] +default = ["std"] +runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] +std = [ + "serde", + "codec/std", + "scale-info/std", + "sp-runtime/std", + "frame-support/std", + "frame-system/std", + "sp-io/std", + "sp-std/std", +] diff --git a/frame/remark/README.md b/frame/remark/README.md new file mode 100644 index 0000000000000..f2341d6a0eaec --- /dev/null +++ b/frame/remark/README.md @@ -0,0 +1,6 @@ +# Remark Storage Pallet + +Allows storing arbitrary data off chain. + + +License: Apache-2.0 diff --git a/frame/remark/src/benchmarking.rs b/frame/remark/src/benchmarking.rs new file mode 100644 index 0000000000000..d30a8aa5df07d --- /dev/null +++ b/frame/remark/src/benchmarking.rs @@ -0,0 +1,47 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Benchmarks for remarks pallet + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; +use frame_benchmarking::{benchmarks, whitelisted_caller}; +use frame_system::{EventRecord, Pallet as System, RawOrigin}; +use sp_std::*; + +#[cfg(test)] +use crate::Pallet as Remark; + +fn assert_last_event(generic_event: ::Event) { + let events = System::::events(); + let system_event: ::Event = generic_event.into(); + let EventRecord { event, .. } = &events[events.len() - 1]; + assert_eq!(event, &system_event); +} + +benchmarks! { + store { + let l in 1 .. 1024*1024; + let caller: T::AccountId = whitelisted_caller(); + }: _(RawOrigin::Signed(caller.clone()), vec![0u8; l as usize]) + verify { + assert_last_event::(Event::Stored { sender: caller, content_hash: sp_io::hashing::blake2_256(&vec![0u8; l as usize]).into() }.into()); + } + + impl_benchmark_test_suite!(Remark, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/frame/remark/src/lib.rs b/frame/remark/src/lib.rs new file mode 100644 index 0000000000000..6803b2a60085f --- /dev/null +++ b/frame/remark/src/lib.rs @@ -0,0 +1,85 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Remark storage pallet. Indexes remarks and stores them off chain. + +// Ensure we're `no_std` when compiling for Wasm. +#![cfg_attr(not(feature = "std"), no_std)] + +mod benchmarking; +pub mod weights; + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +use sp_std::prelude::*; + +// Re-export pallet items so that they can be accessed from the crate namespace. +pub use pallet::*; +pub use weights::WeightInfo; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type Event: From> + IsType<::Event>; + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + } + + #[pallet::error] + pub enum Error { + /// Attempting to store empty data. + Empty, + /// Attempted to call `store` outside of block execution. + BadContext, + } + + #[pallet::pallet] + #[pallet::without_storage_info] + pub struct Pallet(_); + + #[pallet::call] + impl Pallet { + /// Index and store data off chain. + #[pallet::weight(T::WeightInfo::store(remark.len() as u32))] + pub fn store(origin: OriginFor, remark: Vec) -> DispatchResultWithPostInfo { + ensure!(!remark.is_empty(), Error::::Empty); + let sender = ensure_signed(origin)?; + let content_hash = sp_io::hashing::blake2_256(&remark); + let extrinsic_index = >::extrinsic_index() + .ok_or_else(|| Error::::BadContext)?; + sp_io::transaction_index::index(extrinsic_index, remark.len() as u32, content_hash); + Self::deposit_event(Event::Stored { sender, content_hash: content_hash.into() }); + Ok(().into()) + } + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Stored data off chain. + Stored { sender: T::AccountId, content_hash: sp_core::H256 }, + } +} diff --git a/frame/remark/src/mock.rs b/frame/remark/src/mock.rs new file mode 100644 index 0000000000000..67a0399e9c386 --- /dev/null +++ b/frame/remark/src/mock.rs @@ -0,0 +1,79 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Test environment for remarks pallet. + +use crate as pallet_remark; +use frame_support::traits::{ConstU16, ConstU32, ConstU64}; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, +}; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +pub type Block = frame_system::mocking::MockBlock; + +// Configure a mock runtime to test the pallet. +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Remark: pallet_remark::{ Pallet, Call, Event }, + } +); + +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type Origin = Origin; + type Call = Call; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = ConstU64<250>; + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = ConstU16<42>; + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +impl pallet_remark::Config for Test { + type Event = Event; + type WeightInfo = (); +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = GenesisConfig { system: Default::default() }.build_storage().unwrap(); + t.into() +} diff --git a/frame/remark/src/tests.rs b/frame/remark/src/tests.rs new file mode 100644 index 0000000000000..60a376c5afca5 --- /dev/null +++ b/frame/remark/src/tests.rs @@ -0,0 +1,55 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Tests for remarks pallet. + +use super::{Error, Event, Pallet as Remark}; +use crate::mock::*; +use frame_support::{assert_noop, assert_ok}; +use frame_system::RawOrigin; + +#[test] +fn generates_event() { + new_test_ext().execute_with(|| { + let caller = 1; + let data = vec![0u8; 100]; + System::set_block_number(System::block_number() + 1); //otherwise event won't be registered. + assert_ok!(Remark::::store(RawOrigin::Signed(caller.clone()).into(), data.clone(),)); + let events = System::events(); + let system_event: ::Event = Event::Stored { + content_hash: sp_io::hashing::blake2_256(&data).into(), + sender: caller, + } + .into(); + let frame_system::EventRecord { event, .. } = &events[events.len() - 1]; + assert_eq!(event, &system_event); + }); +} + +#[test] +fn does_not_store_empty() { + new_test_ext().execute_with(|| { + let caller = 1; + let data = vec![]; + System::set_block_number(System::block_number() + 1); //otherwise event won't be registered. + assert_noop!( + Remark::::store(RawOrigin::Signed(caller.clone()).into(), data.clone(),), + Error::::Empty + ); + assert!(System::events().is_empty()); + }); +} diff --git a/frame/remark/src/weights.rs b/frame/remark/src/weights.rs new file mode 100644 index 0000000000000..50b0fc3ebfc19 --- /dev/null +++ b/frame/remark/src/weights.rs @@ -0,0 +1,71 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for pallet_remark +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-03-11, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// target/release/substrate +// benchmark +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_remark +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./frame/remark/src/weights.rs +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_remark. +pub trait WeightInfo { + fn store(l: u32, ) -> Weight; +} + +/// Weights for pallet_remark using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) + fn store(l: u32, ) -> Weight { + (18_328_000 as Weight) + // Standard Error: 0 + .saturating_add((1_000 as Weight).saturating_mul(l as Weight)) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) + fn store(l: u32, ) -> Weight { + (18_328_000 as Weight) + // Standard Error: 0 + .saturating_add((1_000 as Weight).saturating_mul(l as Weight)) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + } +} diff --git a/frame/transaction-storage/src/lib.rs b/frame/transaction-storage/src/lib.rs index d95a60b495121..e9aa786766dac 100644 --- a/frame/transaction-storage/src/lib.rs +++ b/frame/transaction-storage/src/lib.rs @@ -170,7 +170,7 @@ pub mod pallet { #[pallet::call] impl Pallet { - /// Index and store data on chain. Minimum data size is 1 bytes, maximum is + /// Index and store data off chain. Minimum data size is 1 bytes, maximum is /// `MaxTransactionSize`. Data will be removed after `STORAGE_PERIOD` blocks, unless `renew` /// is called. # /// - n*log(n) of data size, as all data is pushed to an in-memory trie. From cf9478bdedfe198d0efc84c997f0e8d4d221a779 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Apr 2022 12:03:46 +0000 Subject: [PATCH 130/484] Bump async-std from 1.10.0 to 1.11.0 (#11160) Bumps [async-std](https://github.com/async-rs/async-std) from 1.10.0 to 1.11.0. - [Release notes](https://github.com/async-rs/async-std/releases) - [Changelog](https://github.com/async-rs/async-std/blob/master/CHANGELOG.md) - [Commits](https://github.com/async-rs/async-std/compare/v1.10.0...v1.11.0) --- updated-dependencies: - dependency-name: async-std dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- bin/node/cli/Cargo.toml | 2 +- client/network-gossip/Cargo.toml | 2 +- client/network/Cargo.toml | 2 +- client/network/test/Cargo.toml | 2 +- client/service/Cargo.toml | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 84872ce141da4..c7b3997b1d055 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -256,9 +256,9 @@ dependencies = [ [[package]] name = "async-std" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8056f1455169ab86dd47b47391e4ab0cbd25410a70e9fe675544f49bafaf952" +checksum = "52580991739c5cdb36cde8b2a516371c0a3b70dda36d916cc08b82372916808c" dependencies = [ "async-attributes", "async-channel", diff --git a/bin/node/cli/Cargo.toml b/bin/node/cli/Cargo.toml index bb4ec763a3051..1c6acce2c01df 100644 --- a/bin/node/cli/Cargo.toml +++ b/bin/node/cli/Cargo.toml @@ -124,7 +124,7 @@ nix = "0.23" serde_json = "1.0" regex = "1.5.5" platforms = "2.0" -async-std = { version = "1.10.0", features = ["attributes"] } +async-std = { version = "1.11.0", features = ["attributes"] } soketto = "0.7.1" criterion = { version = "0.3.5", features = ["async_tokio"] } tokio = { version = "1.17.0", features = ["macros", "time", "parking_lot"] } diff --git a/client/network-gossip/Cargo.toml b/client/network-gossip/Cargo.toml index 9854575ab6b02..6e566a100bae5 100644 --- a/client/network-gossip/Cargo.toml +++ b/client/network-gossip/Cargo.toml @@ -27,6 +27,6 @@ sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } tracing = "0.1.29" [dev-dependencies] -async-std = "1.10.0" +async-std = "1.11.0" quickcheck = "1.0.3" substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } diff --git a/client/network/Cargo.toml b/client/network/Cargo.toml index 6d70d41964de4..f0a6a949d1641 100644 --- a/client/network/Cargo.toml +++ b/client/network/Cargo.toml @@ -74,7 +74,7 @@ sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } substrate-test-runtime = { version = "2.0.0", path = "../../test-utils/runtime" } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } tempfile = "3.1.0" -async-std = "1.10.0" +async-std = "1.11.0" [features] default = [] diff --git a/client/network/test/Cargo.toml b/client/network/test/Cargo.toml index b9505f97d14c0..3c72bd404492e 100644 --- a/client/network/test/Cargo.toml +++ b/client/network/test/Cargo.toml @@ -13,7 +13,7 @@ repository = "https://github.com/paritytech/substrate/" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -async-std = "1.10.0" +async-std = "1.11.0" sc-network = { version = "0.10.0-dev", path = "../" } log = "0.4.8" parking_lot = "0.12.0" diff --git a/client/service/Cargo.toml b/client/service/Cargo.toml index c40559bdb5059..490ef1cbb16b0 100644 --- a/client/service/Cargo.toml +++ b/client/service/Cargo.toml @@ -86,4 +86,4 @@ directories = "4.0.1" [dev-dependencies] substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } substrate-test-runtime = { version = "2.0.0", path = "../../test-utils/runtime/" } -async-std = { version = "1.10.0", default-features = false } +async-std = { version = "1.11.0", default-features = false } From 3814712f304efdcd110f714125d1e6327b76baa6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Apr 2022 14:32:20 +0000 Subject: [PATCH 131/484] Bump sha2 from 0.10.1 to 0.10.2 (#11161) Bumps [sha2](https://github.com/RustCrypto/hashes) from 0.10.1 to 0.10.2. - [Release notes](https://github.com/RustCrypto/hashes/releases) - [Commits](https://github.com/RustCrypto/hashes/compare/sha2-v0.10.1...sha2-v0.10.2) --- updated-dependencies: - dependency-name: sha2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 6 +++--- primitives/core/hashing/Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c7b3997b1d055..ab768fad79889 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9612,9 +9612,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99c3bd8169c58782adad9290a9af5939994036b76187f7b4f0e6de91dbbfc0ec" +checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" dependencies = [ "cfg-if 1.0.0", "cpufeatures 0.2.1", @@ -10072,7 +10072,7 @@ dependencies = [ "blake2 0.10.2", "byteorder", "digest 0.10.3", - "sha2 0.10.1", + "sha2 0.10.2", "sha3 0.10.0", "sp-std", "twox-hash", diff --git a/primitives/core/hashing/Cargo.toml b/primitives/core/hashing/Cargo.toml index 978cb8906d5d9..0aee960f9e13d 100644 --- a/primitives/core/hashing/Cargo.toml +++ b/primitives/core/hashing/Cargo.toml @@ -18,7 +18,7 @@ byteorder = { version = "1.3.2", default-features = false } digest = { version = "0.10.3", default-features = false } blake2 = { version = "0.10.2", default-features = false } -sha2 = { version = "0.10.1", default-features = false } +sha2 = { version = "0.10.2", default-features = false } sha3 = { version = "0.10.0", default-features = false } twox-hash = { version = "1.6.2", default-features = false, features = ["digest_0_10"] } From 993d22a4622a3b67c67805909d93ee0508562c27 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Apr 2022 16:45:38 +0200 Subject: [PATCH 132/484] Bump ss58-registry from 1.11.0 to 1.15.0 (#11224) Bumps [ss58-registry](https://github.com/paritytech/ss58-registry) from 1.11.0 to 1.15.0. - [Release notes](https://github.com/paritytech/ss58-registry/releases) - [Changelog](https://github.com/paritytech/ss58-registry/blob/main/CHANGELOG.md) - [Commits](https://github.com/paritytech/ss58-registry/compare/v1.11.0...v1.15.0) --- updated-dependencies: - dependency-name: ss58-registry dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- primitives/core/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ab768fad79889..796a3e3d271ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10602,9 +10602,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "ss58-registry" -version = "1.11.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1230685dc82f8699110640244d361a7099c602f08bddc5c90765a5153b4881dc" +checksum = "2f9799e6d412271cb2414597581128b03f3285f260ea49f5363d07df6a332b3e" dependencies = [ "Inflector", "proc-macro2", diff --git a/primitives/core/Cargo.toml b/primitives/core/Cargo.toml index 74a159b2e7d39..402e3ebf07df1 100644 --- a/primitives/core/Cargo.toml +++ b/primitives/core/Cargo.toml @@ -57,7 +57,7 @@ hex = { version = "0.4", default-features = false, optional = true } libsecp256k1 = { version = "0.7", default-features = false, features = ["static-context"], optional = true } merlin = { version = "2.0", default-features = false, optional = true } secp256k1 = { version = "0.21.2", default-features = false, features = ["recovery", "alloc"], optional = true } -ss58-registry = { version = "1.11.0", default-features = false } +ss58-registry = { version = "1.15.0", default-features = false } sp-core-hashing = { version = "4.0.0", path = "./hashing", default-features = false, optional = true } sp-runtime-interface = { version = "6.0.0", default-features = false, path = "../runtime-interface" } From 9f8a24c59037eb8b635c0c21c3a8e13080ee79ad Mon Sep 17 00:00:00 2001 From: Georges Date: Wed, 20 Apr 2022 13:15:18 +0100 Subject: [PATCH 133/484] Split `SolutionImprovementThresholds` into two types (#11221) * Splitting `SolutionImprovementThreshold` in 2 One for Signed phase and one for Unsigned phase. * Adding some tests * Fixes after code review. - Removing `GetDefault`. - Shorter naming. - More explicit test. --- bin/node/runtime/src/lib.rs | 5 +- .../election-provider-multi-phase/src/lib.rs | 13 +++-- .../election-provider-multi-phase/src/mock.rs | 16 ++++-- .../src/signed.rs | 52 ++++++++++++++++++- .../src/unsigned.rs | 4 +- 5 files changed, 75 insertions(+), 15 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index dca386b303610..1da670b389192 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -574,7 +574,7 @@ parameter_types! { pub const SignedDepositBase: Balance = 1 * DOLLARS; pub const SignedDepositByte: Balance = 1 * CENTS; - pub SolutionImprovementThreshold: Perbill = Perbill::from_rational(1u32, 10_000); + pub BetterUnsignedThreshold: Perbill = Perbill::from_rational(1u32, 10_000); // miner configs pub const MultiPhaseUnsignedPriority: TransactionPriority = StakingUnsignedPriority::get() - 1u64; @@ -664,7 +664,8 @@ impl pallet_election_provider_multi_phase::Config for Runtime { type EstimateCallFee = TransactionPayment; type SignedPhase = SignedPhase; type UnsignedPhase = UnsignedPhase; - type SolutionImprovementThreshold = SolutionImprovementThreshold; + type BetterUnsignedThreshold = BetterUnsignedThreshold; + type BetterSignedThreshold = (); type OffchainRepeat = OffchainRepeat; type MinerMaxWeight = MinerMaxWeight; type MinerMaxLength = MinerMaxLength; diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index e67a5cab8d643..5dc44d38fc365 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -102,8 +102,8 @@ //! valid if propagated, and it acts similar to an inherent. //! //! Validators will only submit solutions if the one that they have computed is sufficiently better -//! than the best queued one (see [`pallet::Config::SolutionImprovementThreshold`]) and will limit -//! the weight of the solution to [`pallet::Config::MinerMaxWeight`]. +//! than the best queued one (see [`pallet::Config::BetterUnsignedThreshold`]) and will limit the +//! weight of the solution to [`pallet::Config::MinerMaxWeight`]. //! //! The unsigned phase can be made passive depending on how the previous signed phase went, by //! setting the first inner value of [`Phase`] to `false`. For now, the signed phase is always @@ -585,9 +585,14 @@ pub mod pallet { type SignedPhase: Get; /// The minimum amount of improvement to the solution score that defines a solution as - /// "better" (in any phase). + /// "better" in the Signed phase. #[pallet::constant] - type SolutionImprovementThreshold: Get; + type BetterSignedThreshold: Get; + + /// The minimum amount of improvement to the solution score that defines a solution as + /// "better" in the Unsigned phase. + #[pallet::constant] + type BetterUnsignedThreshold: Get; /// The repeat threshold of the offchain worker. /// diff --git a/frame/election-provider-multi-phase/src/mock.rs b/frame/election-provider-multi-phase/src/mock.rs index 7c06ff6bee546..2c9d7bb34dba5 100644 --- a/frame/election-provider-multi-phase/src/mock.rs +++ b/frame/election-provider-multi-phase/src/mock.rs @@ -20,7 +20,7 @@ use crate as multi_phase; use frame_election_provider_support::{ data_provider, onchain, ElectionDataProvider, NposSolution, SequentialPhragmen, }; -pub use frame_support::{assert_noop, assert_ok}; +pub use frame_support::{assert_noop, assert_ok, pallet_prelude::GetDefault}; use frame_support::{ bounded_vec, parameter_types, traits::{ConstU32, Hooks}, @@ -263,7 +263,8 @@ parameter_types! { pub static SignedRewardBase: Balance = 7; pub static SignedMaxWeight: Weight = BlockWeights::get().max_block; pub static MinerTxPriority: u64 = 100; - pub static SolutionImprovementThreshold: Perbill = Perbill::zero(); + pub static BetterSignedThreshold: Perbill = Perbill::zero(); + pub static BetterUnsignedThreshold: Perbill = Perbill::zero(); pub static OffchainRepeat: BlockNumber = 5; pub static MinerMaxWeight: Weight = BlockWeights::get().max_block; pub static MinerMaxLength: u32 = 256; @@ -414,7 +415,8 @@ impl crate::Config for Runtime { type EstimateCallFee = frame_support::traits::ConstU32<8>; type SignedPhase = SignedPhase; type UnsignedPhase = UnsignedPhase; - type SolutionImprovementThreshold = SolutionImprovementThreshold; + type BetterUnsignedThreshold = BetterUnsignedThreshold; + type BetterSignedThreshold = BetterSignedThreshold; type OffchainRepeat = OffchainRepeat; type MinerMaxWeight = MinerMaxWeight; type MinerMaxLength = MinerMaxLength; @@ -537,8 +539,12 @@ impl ExtBuilder { ::set(p); self } - pub fn solution_improvement_threshold(self, p: Perbill) -> Self { - ::set(p); + pub fn better_signed_threshold(self, p: Perbill) -> Self { + ::set(p); + self + } + pub fn better_unsigned_threshold(self, p: Perbill) -> Self { + ::set(p); self } pub fn phases(self, signed: BlockNumber, unsigned: BlockNumber) -> Self { diff --git a/frame/election-provider-multi-phase/src/signed.rs b/frame/election-provider-multi-phase/src/signed.rs index 82b40e3276036..465528068e71a 100644 --- a/frame/election-provider-multi-phase/src/signed.rs +++ b/frame/election-provider-multi-phase/src/signed.rs @@ -291,7 +291,7 @@ impl SignedSubmissions { None => return InsertResult::NotInserted, Some((score, _)) => *score, }; - let threshold = T::SolutionImprovementThreshold::get(); + let threshold = T::BetterSignedThreshold::get(); // if we haven't improved on the weakest score, don't change anything. if !insert_score.strict_threshold_better(weakest_score, threshold) { @@ -499,7 +499,7 @@ mod tests { balances, raw_solution, roll_to, ExtBuilder, MultiPhase, Origin, Runtime, SignedMaxSubmissions, SignedMaxWeight, }, - Error, Phase, + Error, Perbill, Phase, }; use frame_support::{assert_noop, assert_ok, assert_storage_noop}; @@ -632,6 +632,54 @@ mod tests { }) } + #[test] + fn cannot_submit_worse_with_full_queue_depends_on_threshold() { + ExtBuilder::default() + .signed_max_submission(1) + .better_signed_threshold(Perbill::from_percent(20)) + .build_and_execute(|| { + roll_to(15); + assert!(MultiPhase::current_phase().is_signed()); + + let mut solution = RawSolution { + score: ElectionScore { + minimal_stake: 5u128, + sum_stake: 0u128, + sum_stake_squared: 10u128, + }, + ..Default::default() + }; + assert_ok!(MultiPhase::submit(Origin::signed(99), Box::new(solution))); + + // This is 10% better, so does not meet the 20% threshold and is therefore rejected. + solution = RawSolution { + score: ElectionScore { + minimal_stake: 5u128, + sum_stake: 0u128, + sum_stake_squared: 9u128, + }, + ..Default::default() + }; + + assert_noop!( + MultiPhase::submit(Origin::signed(99), Box::new(solution)), + Error::::SignedQueueFull, + ); + + // This is however 30% better and should therefore be accepted. + solution = RawSolution { + score: ElectionScore { + minimal_stake: 5u128, + sum_stake: 0u128, + sum_stake_squared: 7u128, + }, + ..Default::default() + }; + + assert_ok!(MultiPhase::submit(Origin::signed(99), Box::new(solution))); + }) + } + #[test] fn weakest_is_removed_if_better_provided() { ExtBuilder::default().build_and_execute(|| { diff --git a/frame/election-provider-multi-phase/src/unsigned.rs b/frame/election-provider-multi-phase/src/unsigned.rs index d210852bac19e..d184c3acfe91b 100644 --- a/frame/election-provider-multi-phase/src/unsigned.rs +++ b/frame/election-provider-multi-phase/src/unsigned.rs @@ -624,7 +624,7 @@ impl Pallet { ensure!( Self::queued_solution().map_or(true, |q: ReadySolution<_>| raw_solution .score - .strict_threshold_better(q.score, T::SolutionImprovementThreshold::get())), + .strict_threshold_better(q.score, T::BetterUnsignedThreshold::get())), Error::::PreDispatchWeakSubmission, ); @@ -1066,7 +1066,7 @@ mod tests { .desired_targets(1) .add_voter(7, 2, bounded_vec![10]) .add_voter(8, 5, bounded_vec![10]) - .solution_improvement_threshold(Perbill::from_percent(50)) + .better_unsigned_threshold(Perbill::from_percent(50)) .build_and_execute(|| { roll_to(25); assert!(MultiPhase::current_phase().is_unsigned()); From 183d99e4af0fffd0650ed9e8c5ba91c56b21882b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Apr 2022 15:39:28 +0200 Subject: [PATCH 134/484] Bump log from 0.4.14 to 0.4.16 (#11236) Bumps [log](https://github.com/rust-lang/log) from 0.4.14 to 0.4.16. - [Release notes](https://github.com/rust-lang/log/releases) - [Changelog](https://github.com/rust-lang/log/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-lang/log/commits) --- updated-dependencies: - dependency-name: log dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 355 +++++++++--------- bin/node/bench/Cargo.toml | 2 +- bin/node/cli/Cargo.toml | 2 +- bin/node/runtime/Cargo.toml | 2 +- bin/node/testing/Cargo.toml | 2 +- client/allocator/Cargo.toml | 2 +- client/api/Cargo.toml | 2 +- client/authority-discovery/Cargo.toml | 2 +- client/basic-authorship/Cargo.toml | 2 +- client/cli/Cargo.toml | 2 +- client/consensus/aura/Cargo.toml | 2 +- client/consensus/babe/Cargo.toml | 2 +- client/consensus/common/Cargo.toml | 2 +- client/consensus/manual-seal/Cargo.toml | 2 +- client/consensus/pow/Cargo.toml | 2 +- client/consensus/slots/Cargo.toml | 2 +- client/db/Cargo.toml | 2 +- client/executor/wasmi/Cargo.toml | 2 +- client/executor/wasmtime/Cargo.toml | 2 +- client/finality-grandpa/Cargo.toml | 2 +- client/informant/Cargo.toml | 2 +- client/network-gossip/Cargo.toml | 2 +- client/network/Cargo.toml | 2 +- client/network/test/Cargo.toml | 2 +- client/peerset/Cargo.toml | 2 +- client/proposer-metrics/Cargo.toml | 2 +- client/rpc-api/Cargo.toml | 2 +- client/rpc-servers/Cargo.toml | 2 +- client/rpc/Cargo.toml | 2 +- client/service/Cargo.toml | 2 +- client/service/test/Cargo.toml | 2 +- client/state-db/Cargo.toml | 2 +- client/sysinfo/Cargo.toml | 2 +- client/telemetry/Cargo.toml | 2 +- client/tracing/Cargo.toml | 2 +- client/transaction-pool/Cargo.toml | 2 +- client/transaction-pool/api/Cargo.toml | 2 +- frame/babe/Cargo.toml | 2 +- frame/bags-list/Cargo.toml | 2 +- frame/bags-list/remote-tests/Cargo.toml | 2 +- frame/balances/Cargo.toml | 2 +- frame/beefy-mmr/Cargo.toml | 2 +- frame/benchmarking/Cargo.toml | 2 +- frame/bounties/Cargo.toml | 2 +- frame/child-bounties/Cargo.toml | 2 +- frame/collective/Cargo.toml | 2 +- .../election-provider-multi-phase/Cargo.toml | 2 +- frame/examples/basic/Cargo.toml | 2 +- frame/examples/offchain-worker/Cargo.toml | 2 +- frame/grandpa/Cargo.toml | 2 +- frame/im-online/Cargo.toml | 2 +- frame/membership/Cargo.toml | 2 +- frame/node-authorization/Cargo.toml | 2 +- frame/offences/Cargo.toml | 2 +- frame/scheduler/Cargo.toml | 2 +- frame/session/Cargo.toml | 2 +- frame/staking/Cargo.toml | 2 +- frame/staking/reward-fn/Cargo.toml | 2 +- frame/state-trie-migration/Cargo.toml | 2 +- frame/support/Cargo.toml | 2 +- frame/system/Cargo.toml | 2 +- frame/timestamp/Cargo.toml | 2 +- frame/tips/Cargo.toml | 2 +- frame/uniques/Cargo.toml | 2 +- frame/vesting/Cargo.toml | 2 +- primitives/api/Cargo.toml | 2 +- primitives/api/test/Cargo.toml | 2 +- primitives/blockchain/Cargo.toml | 2 +- primitives/consensus/common/Cargo.toml | 2 +- primitives/core/Cargo.toml | 2 +- primitives/finality-grandpa/Cargo.toml | 2 +- primitives/io/Cargo.toml | 2 +- primitives/merkle-mountain-range/Cargo.toml | 2 +- primitives/runtime/Cargo.toml | 2 +- primitives/state-machine/Cargo.toml | 2 +- primitives/tasks/Cargo.toml | 2 +- primitives/timestamp/Cargo.toml | 2 +- .../transaction-storage-proof/Cargo.toml | 2 +- primitives/wasm-interface/Cargo.toml | 2 +- test-utils/runtime/Cargo.toml | 2 +- utils/frame/benchmarking-cli/Cargo.toml | 2 +- utils/frame/remote-externalities/Cargo.toml | 2 +- .../rpc/state-trie-migration-rpc/Cargo.toml | 2 +- utils/frame/rpc/system/Cargo.toml | 2 +- utils/frame/try-runtime/cli/Cargo.toml | 2 +- utils/prometheus/Cargo.toml | 2 +- 86 files changed, 263 insertions(+), 262 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 796a3e3d271ce..b7bb085b0962f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -226,7 +226,7 @@ dependencies = [ "fastrand", "futures-lite", "libc", - "log 0.4.14", + "log 0.4.16", "nb-connect", "once_cell", "parking", @@ -272,7 +272,7 @@ dependencies = [ "futures-lite", "gloo-timers", "kv-log-macro", - "log 0.4.14", + "log 0.4.16", "memchr", "num_cpus", "once_cell", @@ -474,7 +474,7 @@ dependencies = [ "futures 0.3.21", "futures-timer", "hex", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "parking_lot 0.12.0", "sc-chain-spec", @@ -519,7 +519,7 @@ dependencies = [ "jsonrpc-core-client", "jsonrpc-derive", "jsonrpc-pubsub", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "parking_lot 0.12.0", "sc-rpc", @@ -539,7 +539,7 @@ dependencies = [ "env_logger 0.9.0", "hex", "hex-literal", - "log 0.4.14", + "log 0.4.16", "tiny-keccak", ] @@ -1171,7 +1171,7 @@ dependencies = [ "cranelift-codegen-shared 0.76.0", "cranelift-entity 0.76.0", "gimli 0.25.0", - "log 0.4.14", + "log 0.4.16", "regalloc 0.0.31", "smallvec 1.8.0", "target-lexicon", @@ -1188,7 +1188,7 @@ dependencies = [ "cranelift-codegen-shared 0.82.3", "cranelift-entity 0.82.3", "gimli 0.26.1", - "log 0.4.14", + "log 0.4.16", "regalloc 0.0.34", "smallvec 1.8.0", "target-lexicon", @@ -1247,7 +1247,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "279afcc0d3e651b773f94837c3d581177b348c8d69e928104b2e9fccb226f921" dependencies = [ "cranelift-codegen 0.76.0", - "log 0.4.14", + "log 0.4.16", "smallvec 1.8.0", "target-lexicon", ] @@ -1259,7 +1259,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a006e3e32d80ce0e4ba7f1f9ddf66066d052a8c884a110b91d05404d6ce26dce" dependencies = [ "cranelift-codegen 0.82.3", - "log 0.4.14", + "log 0.4.16", "smallvec 1.8.0", "target-lexicon", ] @@ -1285,7 +1285,7 @@ dependencies = [ "cranelift-entity 0.82.3", "cranelift-frontend 0.82.3", "itertools", - "log 0.4.14", + "log 0.4.16", "smallvec 1.8.0", "wasmparser 0.83.0", "wasmtime-types", @@ -1937,7 +1937,7 @@ checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" dependencies = [ "atty", "humantime 1.3.0", - "log 0.4.14", + "log 0.4.16", "regex", "termcolor", ] @@ -1948,7 +1948,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" dependencies = [ - "log 0.4.14", + "log 0.4.16", "regex", ] @@ -1960,7 +1960,7 @@ checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" dependencies = [ "atty", "humantime 2.1.0", - "log 0.4.14", + "log 0.4.16", "regex", "termcolor", ] @@ -2054,7 +2054,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fdbe0d94371f9ce939b555dd342d0686cc4c0cadbcd4b61d70af5ff97eb4126" dependencies = [ "env_logger 0.7.1", - "log 0.4.14", + "log 0.4.16", ] [[package]] @@ -2066,7 +2066,7 @@ dependencies = [ "either", "futures 0.3.21", "futures-timer", - "log 0.4.14", + "log 0.4.16", "num-traits", "parity-scale-codec", "parking_lot 0.11.2", @@ -2151,7 +2151,7 @@ dependencies = [ "frame-system", "hex-literal", "linregress", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "paste 1.0.6", "scale-info", @@ -2182,7 +2182,7 @@ dependencies = [ "itertools", "kvdb", "linked-hash-map", - "log 0.4.14", + "log 0.4.16", "memory-db", "parity-scale-codec", "prettytable-rs", @@ -2307,7 +2307,7 @@ dependencies = [ "frame-system", "impl-trait-for-tuples", "k256", - "log 0.4.14", + "log 0.4.16", "once_cell", "parity-scale-codec", "parity-util-mem", @@ -2411,7 +2411,7 @@ version = "4.0.0-dev" dependencies = [ "criterion", "frame-support", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "scale-info", "serde", @@ -2746,7 +2746,7 @@ dependencies = [ "bitflags", "libc", "libgit2-sys", - "log 0.4.14", + "log 0.4.16", "url 2.2.1", ] @@ -2765,7 +2765,7 @@ dependencies = [ "aho-corasick", "bstr", "fnv", - "log 0.4.14", + "log 0.4.16", "regex", ] @@ -2824,7 +2824,7 @@ version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d6a30320f094710245150395bc763ad23128d6a1ebbad7594dc4164b62c56b" dependencies = [ - "log 0.4.14", + "log 0.4.16", "pest", "pest_derive", "quick-error 2.0.0", @@ -3061,7 +3061,7 @@ dependencies = [ "ct-logs", "futures-util", "hyper 0.14.16", - "log 0.4.14", + "log 0.4.16", "rustls 0.19.1", "rustls-native-certs 0.5.0", "tokio", @@ -3143,7 +3143,7 @@ dependencies = [ "if-addrs", "ipnet", "libc", - "log 0.4.14", + "log 0.4.16", "winapi 0.3.9", ] @@ -3295,7 +3295,7 @@ dependencies = [ "hyper-tls", "jsonrpc-core", "jsonrpc-pubsub", - "log 0.4.14", + "log 0.4.16", "serde", "serde_json", "tokio", @@ -3312,7 +3312,7 @@ dependencies = [ "futures 0.3.21", "futures-executor", "futures-util", - "log 0.4.14", + "log 0.4.16", "serde", "serde_derive", "serde_json", @@ -3350,7 +3350,7 @@ dependencies = [ "hyper 0.14.16", "jsonrpc-core", "jsonrpc-server-utils", - "log 0.4.14", + "log 0.4.16", "net2", "parking_lot 0.11.2", "unicase 2.6.0", @@ -3365,7 +3365,7 @@ dependencies = [ "futures 0.3.21", "jsonrpc-core", "jsonrpc-server-utils", - "log 0.4.14", + "log 0.4.16", "parity-tokio-ipc", "parking_lot 0.11.2", "tower-service", @@ -3380,7 +3380,7 @@ dependencies = [ "futures 0.3.21", "jsonrpc-core", "lazy_static", - "log 0.4.14", + "log 0.4.16", "parking_lot 0.11.2", "rand 0.7.3", "serde", @@ -3397,7 +3397,7 @@ dependencies = [ "globset", "jsonrpc-core", "lazy_static", - "log 0.4.14", + "log 0.4.16", "tokio", "tokio-stream", "tokio-util 0.6.7", @@ -3413,7 +3413,7 @@ dependencies = [ "futures 0.3.21", "jsonrpc-core", "jsonrpc-server-utils", - "log 0.4.14", + "log 0.4.16", "parity-ws", "parking_lot 0.11.2", "slab", @@ -3557,7 +3557,7 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" dependencies = [ - "log 0.4.14", + "log 0.4.16", ] [[package]] @@ -3589,7 +3589,7 @@ checksum = "ca7fbdfd71cd663dceb0faf3367a99f8cf724514933e9867cec4995b6027cbc1" dependencies = [ "fs-swap", "kvdb", - "log 0.4.14", + "log 0.4.16", "num_cpus", "owning_ref", "parity-util-mem", @@ -3723,7 +3723,7 @@ dependencies = [ "futures-timer", "lazy_static", "libsecp256k1", - "log 0.4.14", + "log 0.4.16", "multiaddr", "multihash 0.14.0", "multistream-select", @@ -3762,7 +3762,7 @@ dependencies = [ "async-std-resolver", "futures 0.3.21", "libp2p-core", - "log 0.4.14", + "log 0.4.16", "smallvec 1.8.0", "trust-dns-resolver", ] @@ -3778,7 +3778,7 @@ dependencies = [ "futures 0.3.21", "libp2p-core", "libp2p-swarm", - "log 0.4.14", + "log 0.4.16", "prost", "prost-build", "rand 0.7.3", @@ -3800,7 +3800,7 @@ dependencies = [ "hex_fmt", "libp2p-core", "libp2p-swarm", - "log 0.4.14", + "log 0.4.16", "prost", "prost-build", "rand 0.7.3", @@ -3820,7 +3820,7 @@ dependencies = [ "futures 0.3.21", "libp2p-core", "libp2p-swarm", - "log 0.4.14", + "log 0.4.16", "lru 0.6.6", "prost", "prost-build", @@ -3842,7 +3842,7 @@ dependencies = [ "futures 0.3.21", "libp2p-core", "libp2p-swarm", - "log 0.4.14", + "log 0.4.16", "prost", "prost-build", "rand 0.7.3", @@ -3868,7 +3868,7 @@ dependencies = [ "lazy_static", "libp2p-core", "libp2p-swarm", - "log 0.4.14", + "log 0.4.16", "rand 0.8.4", "smallvec 1.8.0", "socket2 0.4.4", @@ -3899,7 +3899,7 @@ dependencies = [ "bytes 1.1.0", "futures 0.3.21", "libp2p-core", - "log 0.4.14", + "log 0.4.16", "nohash-hasher", "parking_lot 0.11.2", "rand 0.7.3", @@ -3918,7 +3918,7 @@ dependencies = [ "futures 0.3.21", "lazy_static", "libp2p-core", - "log 0.4.14", + "log 0.4.16", "prost", "prost-build", "rand 0.8.4", @@ -3938,7 +3938,7 @@ dependencies = [ "futures 0.3.21", "libp2p-core", "libp2p-swarm", - "log 0.4.14", + "log 0.4.16", "rand 0.7.3", "void", "wasm-timer", @@ -3954,7 +3954,7 @@ dependencies = [ "bytes 1.1.0", "futures 0.3.21", "libp2p-core", - "log 0.4.14", + "log 0.4.16", "prost", "prost-build", "unsigned-varint 0.7.0", @@ -3968,7 +3968,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f1a458bbda880107b5b36fcb9b5a1ef0c329685da0e203ed692a8ebe64cc92c" dependencies = [ "futures 0.3.21", - "log 0.4.14", + "log 0.4.16", "pin-project 1.0.10", "rand 0.7.3", "salsa20", @@ -3987,7 +3987,7 @@ dependencies = [ "futures-timer", "libp2p-core", "libp2p-swarm", - "log 0.4.14", + "log 0.4.16", "pin-project 1.0.10", "prost", "prost-build", @@ -4009,7 +4009,7 @@ dependencies = [ "futures 0.3.21", "libp2p-core", "libp2p-swarm", - "log 0.4.14", + "log 0.4.16", "prost", "prost-build", "rand 0.8.4", @@ -4031,7 +4031,7 @@ dependencies = [ "futures 0.3.21", "libp2p-core", "libp2p-swarm", - "log 0.4.14", + "log 0.4.16", "lru 0.7.5", "rand 0.7.3", "smallvec 1.8.0", @@ -4048,7 +4048,7 @@ dependencies = [ "either", "futures 0.3.21", "libp2p-core", - "log 0.4.14", + "log 0.4.16", "rand 0.7.3", "smallvec 1.8.0", "void", @@ -4078,7 +4078,7 @@ dependencies = [ "ipnet", "libc", "libp2p-core", - "log 0.4.14", + "log 0.4.16", "socket2 0.4.4", ] @@ -4091,7 +4091,7 @@ dependencies = [ "async-std", "futures 0.3.21", "libp2p-core", - "log 0.4.14", + "log 0.4.16", ] [[package]] @@ -4118,7 +4118,7 @@ dependencies = [ "futures 0.3.21", "futures-rustls", "libp2p-core", - "log 0.4.14", + "log 0.4.16", "quicksink", "rw-stream-sink", "soketto", @@ -4287,14 +4287,14 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" dependencies = [ - "log 0.4.14", + "log 0.4.16", ] [[package]] name = "log" -version = "0.4.14" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" dependencies = [ "cfg-if 1.0.0", "value-bag", @@ -4528,7 +4528,7 @@ dependencies = [ "iovec", "kernel32-sys", "libc", - "log 0.4.14", + "log 0.4.16", "miow 0.2.2", "net2", "slab", @@ -4542,7 +4542,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2" dependencies = [ "libc", - "log 0.4.14", + "log 0.4.16", "miow 0.3.6", "ntapi", "winapi 0.3.9", @@ -4555,7 +4555,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" dependencies = [ "lazycell", - "log 0.4.14", + "log 0.4.16", "mio 0.6.23", "slab", ] @@ -4675,7 +4675,7 @@ checksum = "7d91ec0a2440aaff5f78ec35631a7027d50386c6163aa975f7caa0d5da4b6ff8" dependencies = [ "bytes 1.1.0", "futures 0.3.21", - "log 0.4.14", + "log 0.4.16", "pin-project 1.0.10", "smallvec 1.8.0", "unsigned-varint 0.7.0", @@ -4727,7 +4727,7 @@ checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d" dependencies = [ "lazy_static", "libc", - "log 0.4.14", + "log 0.4.16", "openssl", "openssl-probe", "openssl-sys", @@ -4784,7 +4784,7 @@ dependencies = [ "kvdb", "kvdb-rocksdb", "lazy_static", - "log 0.4.14", + "log 0.4.16", "node-primitives", "node-runtime", "node-testing", @@ -4822,7 +4822,7 @@ dependencies = [ "frame-system-rpc-runtime-api", "futures 0.3.21", "hex-literal", - "log 0.4.14", + "log 0.4.16", "nix", "node-executor", "node-inspect", @@ -4996,7 +4996,7 @@ dependencies = [ "frame-system-rpc-runtime-api", "frame-try-runtime", "hex-literal", - "log 0.4.14", + "log 0.4.16", "node-primitives", "pallet-asset-tx-payment", "pallet-assets", @@ -5167,7 +5167,7 @@ dependencies = [ "frame-system", "fs_extra", "futures 0.3.21", - "log 0.4.14", + "log 0.4.16", "node-executor", "node-primitives", "node-runtime", @@ -5550,7 +5550,7 @@ dependencies = [ "frame-election-provider-support", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.16", "pallet-authorship", "pallet-balances", "pallet-offences", @@ -5579,7 +5579,7 @@ dependencies = [ "frame-election-provider-support", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.16", "pallet-balances", "parity-scale-codec", "scale-info", @@ -5607,7 +5607,7 @@ dependencies = [ "frame-election-provider-support", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.16", "pallet-bags-list", "pallet-staking", "remote-externalities", @@ -5626,7 +5626,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.16", "pallet-transaction-payment", "parity-scale-codec", "scale-info", @@ -5664,7 +5664,7 @@ dependencies = [ "frame-system", "hex", "hex-literal", - "log 0.4.14", + "log 0.4.16", "pallet-beefy", "pallet-mmr", "pallet-session", @@ -5685,7 +5685,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.16", "pallet-balances", "pallet-treasury", "parity-scale-codec", @@ -5703,7 +5703,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.16", "pallet-balances", "pallet-bounties", "pallet-treasury", @@ -5722,7 +5722,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "scale-info", "sp-core", @@ -5742,7 +5742,7 @@ dependencies = [ "frame-support", "frame-system", "hex-literal", - "log 0.4.14", + "log 0.4.16", "pallet-balances", "pallet-contracts-primitives", "pallet-contracts-proc-macro", @@ -5866,7 +5866,7 @@ dependencies = [ "frame-election-provider-support", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.16", "pallet-balances", "pallet-election-provider-support-benchmarking", "parity-scale-codec", @@ -5903,7 +5903,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.16", "pallet-balances", "parity-scale-codec", "scale-info", @@ -5922,7 +5922,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.16", "pallet-balances", "parity-scale-codec", "scale-info", @@ -5939,7 +5939,7 @@ dependencies = [ "frame-support", "frame-system", "lite-json", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "scale-info", "sp-core", @@ -5990,7 +5990,7 @@ dependencies = [ "frame-election-provider-support", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.16", "pallet-authorship", "pallet-balances", "pallet-offences", @@ -6035,7 +6035,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.16", "pallet-authorship", "pallet-session", "parity-scale-codec", @@ -6089,7 +6089,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "scale-info", "sp-core", @@ -6171,7 +6171,7 @@ version = "4.0.0-dev" dependencies = [ "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "scale-info", "sp-core", @@ -6186,7 +6186,7 @@ version = "4.0.0-dev" dependencies = [ "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.16", "pallet-balances", "parity-scale-codec", "scale-info", @@ -6330,7 +6330,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.16", "pallet-preimage", "parity-scale-codec", "scale-info", @@ -6363,7 +6363,7 @@ dependencies = [ "frame-support", "frame-system", "impl-trait-for-tuples", - "log 0.4.14", + "log 0.4.16", "pallet-timestamp", "parity-scale-codec", "scale-info", @@ -6424,7 +6424,7 @@ dependencies = [ "frame-election-provider-support", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.16", "pallet-authorship", "pallet-bags-list", "pallet-balances", @@ -6461,7 +6461,7 @@ dependencies = [ name = "pallet-staking-reward-fn" version = "4.0.0-dev" dependencies = [ - "log 0.4.14", + "log 0.4.16", "sp-arithmetic", ] @@ -6472,7 +6472,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.16", "pallet-balances", "parity-scale-codec", "parking_lot 0.12.0", @@ -6525,7 +6525,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "scale-info", "sp-core", @@ -6543,7 +6543,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.16", "pallet-balances", "pallet-treasury", "parity-scale-codec", @@ -6645,7 +6645,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.16", "pallet-balances", "parity-scale-codec", "scale-info", @@ -6678,7 +6678,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.14", + "log 0.4.16", "pallet-balances", "parity-scale-codec", "scale-info", @@ -6717,7 +6717,7 @@ dependencies = [ "fs2", "hex", "libc", - "log 0.4.14", + "log 0.4.16", "lz4", "memmap2 0.2.1", "parking_lot 0.11.2", @@ -6765,7 +6765,7 @@ checksum = "9981e32fb75e004cc148f5fb70342f393830e0a4aa62e3cc93b50976218d42b6" dependencies = [ "futures 0.3.21", "libc", - "log 0.4.14", + "log 0.4.16", "rand 0.7.3", "tokio", "winapi 0.3.9", @@ -6822,7 +6822,7 @@ dependencies = [ "byteorder", "bytes 0.4.12", "httparse", - "log 0.4.14", + "log 0.4.16", "mio 0.6.23", "mio-extras", "rand 0.7.3", @@ -7131,7 +7131,7 @@ checksum = "a2a7bc6b2a29e632e45451c941832803a18cce6781db04de8a04696cdca8bde4" dependencies = [ "cfg-if 0.1.10", "libc", - "log 0.4.14", + "log 0.4.16", "wepoll-sys", "winapi 0.3.9", ] @@ -7323,7 +7323,7 @@ dependencies = [ "heck 0.3.2", "itertools", "lazy_static", - "log 0.4.14", + "log 0.4.16", "multimap", "petgraph", "prost", @@ -7404,7 +7404,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" dependencies = [ "env_logger 0.8.4", - "log 0.4.14", + "log 0.4.16", "rand 0.8.4", ] @@ -7752,7 +7752,7 @@ version = "0.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "571f7f397d61c4755285cd37853fe8e03271c243424a907415909379659381c5" dependencies = [ - "log 0.4.14", + "log 0.4.16", "rustc-hash", "smallvec 1.8.0", ] @@ -7763,7 +7763,7 @@ version = "0.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62446b1d3ebf980bdc68837700af1d77b37bc430e524bf95319c6eada2a4cc02" dependencies = [ - "log 0.4.14", + "log 0.4.16", "rustc-hash", "smallvec 1.8.0", ] @@ -7826,7 +7826,7 @@ dependencies = [ "env_logger 0.9.0", "frame-support", "jsonrpsee", - "log 0.4.14", + "log 0.4.16", "pallet-elections-phragmen", "parity-scale-codec", "serde", @@ -8012,7 +8012,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" dependencies = [ "base64 0.13.0", - "log 0.4.14", + "log 0.4.16", "ring", "sct 0.6.0", "webpki 0.21.4", @@ -8024,7 +8024,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d37e5e2290f3e040b594b1a9e04377c2c671f1a1cfd9bfdef82106ac1c113f84" dependencies = [ - "log 0.4.14", + "log 0.4.16", "ring", "sct 0.7.0", "webpki 0.22.0", @@ -8123,7 +8123,7 @@ dependencies = [ name = "sc-allocator" version = "4.1.0-dev" dependencies = [ - "log 0.4.14", + "log 0.4.16", "sp-core", "sp-wasm-interface", "thiserror", @@ -8138,7 +8138,7 @@ dependencies = [ "futures-timer", "ip_network", "libp2p", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "prost", "prost-build", @@ -8164,7 +8164,7 @@ version = "0.10.0-dev" dependencies = [ "futures 0.3.21", "futures-timer", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "parking_lot 0.12.0", "sc-block-builder", @@ -8235,7 +8235,7 @@ dependencies = [ "futures 0.3.21", "hex", "libp2p", - "log 0.4.14", + "log 0.4.16", "names", "parity-scale-codec", "rand 0.7.3", @@ -8270,7 +8270,7 @@ dependencies = [ "fnv", "futures 0.3.21", "hash-db", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "parking_lot 0.12.0", "sc-executor", @@ -8302,7 +8302,7 @@ dependencies = [ "kvdb-memorydb", "kvdb-rocksdb", "linked-hash-map", - "log 0.4.14", + "log 0.4.16", "parity-db", "parity-scale-codec", "parking_lot 0.12.0", @@ -8329,7 +8329,7 @@ dependencies = [ "futures 0.3.21", "futures-timer", "libp2p", - "log 0.4.14", + "log 0.4.16", "parking_lot 0.12.0", "sc-client-api", "sc-utils", @@ -8351,7 +8351,7 @@ version = "0.10.0-dev" dependencies = [ "async-trait", "futures 0.3.21", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "parking_lot 0.12.0", "sc-block-builder", @@ -8389,7 +8389,7 @@ dependencies = [ "async-trait", "fork-tree", "futures 0.3.21", - "log 0.4.14", + "log 0.4.16", "merlin", "num-bigint", "num-rational 0.2.4", @@ -8483,7 +8483,7 @@ dependencies = [ "jsonrpc-core", "jsonrpc-core-client", "jsonrpc-derive", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "sc-basic-authorship", "sc-client-api", @@ -8519,7 +8519,7 @@ dependencies = [ "async-trait", "futures 0.3.21", "futures-timer", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "parking_lot 0.12.0", "sc-client-api", @@ -8543,7 +8543,7 @@ dependencies = [ "async-trait", "futures 0.3.21", "futures-timer", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "sc-client-api", "sc-consensus", @@ -8631,7 +8631,7 @@ dependencies = [ name = "sc-executor-wasmi" version = "0.10.0-dev" dependencies = [ - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "sc-allocator", "sc-executor-common", @@ -8648,7 +8648,7 @@ version = "0.10.0-dev" dependencies = [ "cfg-if 1.0.0", "libc", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "parity-wasm 0.42.2", "sc-allocator", @@ -8675,7 +8675,7 @@ dependencies = [ "futures 0.3.21", "futures-timer", "hex", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "parking_lot 0.12.0", "rand 0.8.4", @@ -8719,7 +8719,7 @@ dependencies = [ "jsonrpc-core-client", "jsonrpc-derive", "jsonrpc-pubsub", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "sc-block-builder", "sc-client-api", @@ -8743,7 +8743,7 @@ dependencies = [ "ansi_term", "futures 0.3.21", "futures-timer", - "log 0.4.14", + "log 0.4.16", "parity-util-mem", "sc-client-api", "sc-network", @@ -8788,7 +8788,7 @@ dependencies = [ "libp2p", "linked-hash-map", "linked_hash_set", - "log 0.4.14", + "log 0.4.16", "lru 0.7.5", "parity-scale-codec", "parking_lot 0.12.0", @@ -8832,7 +8832,7 @@ dependencies = [ "futures 0.3.21", "futures-timer", "libp2p", - "log 0.4.14", + "log 0.4.16", "lru 0.7.5", "quickcheck", "sc-network", @@ -8851,7 +8851,7 @@ dependencies = [ "futures 0.3.21", "futures-timer", "libp2p", - "log 0.4.14", + "log 0.4.16", "parking_lot 0.12.0", "rand 0.7.3", "sc-block-builder", @@ -8911,7 +8911,7 @@ version = "4.0.0-dev" dependencies = [ "futures 0.3.21", "libp2p", - "log 0.4.14", + "log 0.4.16", "rand 0.7.3", "sc-utils", "serde_json", @@ -8922,7 +8922,7 @@ dependencies = [ name = "sc-proposer-metrics" version = "0.10.0-dev" dependencies = [ - "log 0.4.14", + "log 0.4.16", "substrate-prometheus-endpoint", ] @@ -8936,7 +8936,7 @@ dependencies = [ "jsonrpc-core", "jsonrpc-pubsub", "lazy_static", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "parking_lot 0.12.0", "sc-block-builder", @@ -8972,7 +8972,7 @@ dependencies = [ "jsonrpc-core-client", "jsonrpc-derive", "jsonrpc-pubsub", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "parking_lot 0.12.0", "sc-chain-spec", @@ -8998,7 +8998,7 @@ dependencies = [ "jsonrpc-ipc-server", "jsonrpc-pubsub", "jsonrpc-ws-server", - "log 0.4.14", + "log 0.4.16", "serde_json", "substrate-prometheus-endpoint", "tokio", @@ -9031,7 +9031,7 @@ dependencies = [ "hash-db", "jsonrpc-core", "jsonrpc-pubsub", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "parity-util-mem", "parking_lot 0.12.0", @@ -9093,7 +9093,7 @@ dependencies = [ "futures 0.3.21", "hex", "hex-literal", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "parking_lot 0.12.0", "sc-block-builder", @@ -9125,7 +9125,7 @@ dependencies = [ name = "sc-state-db" version = "0.10.0-dev" dependencies = [ - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "parity-util-mem", "parity-util-mem-derive", @@ -9160,7 +9160,7 @@ version = "6.0.0-dev" dependencies = [ "futures 0.3.21", "libc", - "log 0.4.14", + "log 0.4.16", "rand 0.7.3", "rand_pcg 0.2.1", "regex", @@ -9179,7 +9179,7 @@ dependencies = [ "chrono", "futures 0.3.21", "libp2p", - "log 0.4.14", + "log 0.4.16", "parking_lot 0.12.0", "pin-project 1.0.10", "rand 0.7.3", @@ -9199,7 +9199,7 @@ dependencies = [ "criterion", "lazy_static", "libc", - "log 0.4.14", + "log 0.4.16", "once_cell", "parking_lot 0.12.0", "regex", @@ -9240,7 +9240,7 @@ dependencies = [ "futures-timer", "hex", "linked-hash-map", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "parity-util-mem", "parking_lot 0.12.0", @@ -9269,7 +9269,7 @@ name = "sc-transaction-pool-api" version = "4.0.0-dev" dependencies = [ "futures 0.3.21", - "log 0.4.14", + "log 0.4.16", "serde", "sp-blockchain", "sp-runtime", @@ -9283,7 +9283,7 @@ dependencies = [ "futures 0.3.21", "futures-timer", "lazy_static", - "log 0.4.14", + "log 0.4.16", "parking_lot 0.12.0", "prometheus", "tokio-test", @@ -9766,7 +9766,7 @@ dependencies = [ "flate2", "futures 0.3.21", "httparse", - "log 0.4.14", + "log 0.4.16", "rand 0.8.4", "sha-1 0.9.4", ] @@ -9776,7 +9776,7 @@ name = "sp-api" version = "4.0.0-dev" dependencies = [ "hash-db", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "sp-api-proc-macro", "sp-core", @@ -9805,7 +9805,7 @@ version = "2.0.1" dependencies = [ "criterion", "futures 0.3.21", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "rustversion", "sc-block-builder", @@ -9910,7 +9910,7 @@ name = "sp-blockchain" version = "4.0.0-dev" dependencies = [ "futures 0.3.21", - "log 0.4.14", + "log 0.4.16", "lru 0.7.5", "parity-scale-codec", "parking_lot 0.12.0", @@ -9929,7 +9929,7 @@ dependencies = [ "async-trait", "futures 0.3.21", "futures-timer", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "sp-core", "sp-inherents", @@ -10034,7 +10034,7 @@ dependencies = [ "impl-serde", "lazy_static", "libsecp256k1", - "log 0.4.14", + "log 0.4.16", "merlin", "num-traits", "parity-scale-codec", @@ -10120,7 +10120,7 @@ name = "sp-finality-grandpa" version = "4.0.0-dev" dependencies = [ "finality-grandpa", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "scale-info", "serde", @@ -10153,7 +10153,7 @@ dependencies = [ "futures 0.3.21", "hash-db", "libsecp256k1", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "parking_lot 0.12.0", "secp256k1", @@ -10211,7 +10211,7 @@ name = "sp-mmr-primitives" version = "4.0.0-dev" dependencies = [ "hex-literal", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "serde", "sp-api", @@ -10284,7 +10284,7 @@ dependencies = [ "either", "hash256-std-hasher", "impl-trait-for-tuples", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "parity-util-mem", "paste 1.0.6", @@ -10380,7 +10380,7 @@ name = "sp-sandbox" version = "0.10.0-dev" dependencies = [ "assert_matches", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "sp-core", "sp-io", @@ -10427,7 +10427,7 @@ version = "0.12.0" dependencies = [ "hash-db", "hex-literal", - "log 0.4.14", + "log 0.4.16", "num-traits", "parity-scale-codec", "parking_lot 0.12.0", @@ -10465,7 +10465,7 @@ dependencies = [ name = "sp-tasks" version = "4.0.0-dev" dependencies = [ - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "sp-core", "sp-externalities", @@ -10492,7 +10492,7 @@ version = "4.0.0-dev" dependencies = [ "async-trait", "futures-timer", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "sp-api", "sp-inherents", @@ -10525,7 +10525,7 @@ name = "sp-transaction-storage-proof" version = "4.0.0-dev" dependencies = [ "async-trait", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "scale-info", "sp-core", @@ -10587,7 +10587,7 @@ name = "sp-wasm-interface" version = "6.0.0" dependencies = [ "impl-trait-for-tuples", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "sp-std", "wasmi", @@ -10732,7 +10732,7 @@ dependencies = [ "jsonrpc-core", "jsonrpc-core-client", "jsonrpc-derive", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "sc-client-api", "sc-rpc-api", @@ -10753,7 +10753,7 @@ version = "0.10.0-dev" dependencies = [ "futures-util", "hyper 0.14.16", - "log 0.4.14", + "log 0.4.16", "prometheus", "thiserror", "tokio", @@ -10766,7 +10766,7 @@ dependencies = [ "jsonrpc-core", "jsonrpc-core-client", "jsonrpc-derive", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "sc-client-api", "sc-rpc-api", @@ -10817,7 +10817,7 @@ dependencies = [ "frame-system", "frame-system-rpc-runtime-api", "futures 0.3.21", - "log 0.4.14", + "log 0.4.16", "memory-db", "pallet-babe", "pallet-timestamp", @@ -11190,7 +11190,7 @@ checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674" dependencies = [ "bytes 0.4.12", "futures 0.1.31", - "log 0.4.14", + "log 0.4.16", ] [[package]] @@ -11223,7 +11223,7 @@ dependencies = [ "crossbeam-utils 0.7.2", "futures 0.1.31", "lazy_static", - "log 0.4.14", + "log 0.4.16", "mio 0.6.23", "num_cpus", "parking_lot 0.9.0", @@ -11323,7 +11323,7 @@ dependencies = [ "bytes 1.1.0", "futures-core", "futures-sink", - "log 0.4.14", + "log 0.4.16", "pin-project-lite 0.2.6", "tokio", ] @@ -11364,7 +11364,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" dependencies = [ "cfg-if 1.0.0", - "log 0.4.14", + "log 0.4.16", "pin-project-lite 0.2.6", "tracing-attributes", "tracing-core", @@ -11407,7 +11407,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3" dependencies = [ "lazy_static", - "log 0.4.14", + "log 0.4.16", "tracing-core", ] @@ -11480,7 +11480,7 @@ checksum = "d32d034c0d3db64b43c31de38e945f15b40cd4ca6d2dcfc26d4798ce8de4ab83" dependencies = [ "hash-db", "hashbrown 0.12.0", - "log 0.4.14", + "log 0.4.16", "rustc-hex", "smallvec 1.8.0", ] @@ -11520,7 +11520,7 @@ dependencies = [ "idna 0.2.2", "ipnet", "lazy_static", - "log 0.4.14", + "log 0.4.16", "rand 0.8.4", "smallvec 1.8.0", "thiserror", @@ -11538,7 +11538,7 @@ dependencies = [ "futures-util", "ipconfig", "lazy_static", - "log 0.4.14", + "log 0.4.16", "lru-cache", "parking_lot 0.11.2", "resolv-conf", @@ -11559,7 +11559,7 @@ version = "0.10.0-dev" dependencies = [ "clap 3.1.6", "jsonrpsee", - "log 0.4.14", + "log 0.4.16", "parity-scale-codec", "remote-externalities", "sc-chain-spec", @@ -11765,11 +11765,12 @@ dependencies = [ [[package]] name = "value-bag" -version = "1.0.0-alpha.6" +version = "1.0.0-alpha.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b676010e055c99033117c2343b33a40a30b91fecd6c49055ac9cd2d6c305ab1" +checksum = "79923f7731dc61ebfba3633098bf3ac533bbd35ccd8c57e7088d9a5eebe0263f" dependencies = [ "ctor", + "version_check 0.9.2", ] [[package]] @@ -11834,7 +11835,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" dependencies = [ - "log 0.4.14", + "log 0.4.16", "try-lock", ] @@ -11868,7 +11869,7 @@ checksum = "f34c405b4f0658583dba0c1c7c9b694f3cac32655db463b56c254a1c75269523" dependencies = [ "bumpalo", "lazy_static", - "log 0.4.14", + "log 0.4.16", "proc-macro2", "quote", "syn", @@ -11922,7 +11923,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0c32691b6c7e6c14e7f8fd55361a9088b507aa49620fcd06c09b3a1082186b9" dependencies = [ - "log 0.4.14", + "log 0.4.16", "parity-wasm 0.32.0", "rustc-demangle", ] @@ -12215,7 +12216,7 @@ dependencies = [ "indexmap", "lazy_static", "libc", - "log 0.4.14", + "log 0.4.16", "object 0.27.1", "once_cell", "paste 1.0.6", @@ -12244,7 +12245,7 @@ dependencies = [ "bincode", "directories-next", "file-per-thread-logger", - "log 0.4.14", + "log 0.4.16", "rustix", "serde", "sha2 0.9.8", @@ -12266,7 +12267,7 @@ dependencies = [ "cranelift-native", "cranelift-wasm", "gimli 0.26.1", - "log 0.4.14", + "log 0.4.16", "more-asserts", "object 0.27.1", "target-lexicon", @@ -12285,7 +12286,7 @@ dependencies = [ "cranelift-entity 0.82.3", "gimli 0.26.1", "indexmap", - "log 0.4.14", + "log 0.4.16", "more-asserts", "object 0.27.1", "serde", @@ -12307,7 +12308,7 @@ dependencies = [ "cfg-if 1.0.0", "cpp_demangle", "gimli 0.26.1", - "log 0.4.14", + "log 0.4.16", "object 0.27.1", "region 2.2.0", "rustc-demangle", @@ -12344,7 +12345,7 @@ dependencies = [ "cfg-if 1.0.0", "indexmap", "libc", - "log 0.4.14", + "log 0.4.16", "mach", "memoffset", "more-asserts", @@ -12633,7 +12634,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7d9028f208dd5e63c614be69f115c1b53cacc1111437d4c765185856666c107" dependencies = [ "futures 0.3.21", - "log 0.4.14", + "log 0.4.16", "nohash-hasher", "parking_lot 0.11.2", "rand 0.8.4", diff --git a/bin/node/bench/Cargo.toml b/bin/node/bench/Cargo.toml index 45a959ac16b14..2675c12d0d3b4 100644 --- a/bin/node/bench/Cargo.toml +++ b/bin/node/bench/Cargo.toml @@ -10,7 +10,7 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0" [dependencies] clap = { version = "3.1.6", features = ["derive"] } -log = "0.4.8" +log = "0.4.16" node-primitives = { version = "2.0.0", path = "../primitives" } node-testing = { version = "3.0.0-dev", path = "../testing" } node-runtime = { version = "3.0.0-dev", path = "../runtime" } diff --git a/bin/node/cli/Cargo.toml b/bin/node/cli/Cargo.toml index 1c6acce2c01df..5562efa1846e1 100644 --- a/bin/node/cli/Cargo.toml +++ b/bin/node/cli/Cargo.toml @@ -39,7 +39,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0" } serde = { version = "1.0.136", features = ["derive"] } futures = "0.3.21" hex-literal = "0.3.4" -log = "0.4.8" +log = "0.4.16" rand = "0.8" # primitives diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 75f860eb240e8..19d911a16552d 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -21,7 +21,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } static_assertions = "1.1.0" hex-literal = { version = "0.3.4", optional = true } -log = { version = "0.4.14", default-features = false } +log = { version = "0.4.16", default-features = false } # primitives sp-authority-discovery = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/authority-discovery" } diff --git a/bin/node/testing/Cargo.toml b/bin/node/testing/Cargo.toml index 02d13f0c64718..fc072312debaa 100644 --- a/bin/node/testing/Cargo.toml +++ b/bin/node/testing/Cargo.toml @@ -45,7 +45,7 @@ sp-block-builder = { version = "4.0.0-dev", path = "../../../primitives/block-bu sc-block-builder = { version = "0.10.0-dev", path = "../../../client/block-builder" } sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -log = "0.4.8" +log = "0.4.16" tempfile = "3.1.0" fs_extra = "1" futures = "0.3.21" diff --git a/client/allocator/Cargo.toml b/client/allocator/Cargo.toml index 8375c39ba4a14..190a150b57fff 100644 --- a/client/allocator/Cargo.toml +++ b/client/allocator/Cargo.toml @@ -16,5 +16,5 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] sp-core = { version = "6.0.0", path = "../../primitives/core" } sp-wasm-interface = { version = "6.0.0", path = "../../primitives/wasm-interface" } -log = "0.4.11" +log = "0.4.16" thiserror = "1.0.30" diff --git a/client/api/Cargo.toml b/client/api/Cargo.toml index d209a311f45b9..8230f0baf3e72 100644 --- a/client/api/Cargo.toml +++ b/client/api/Cargo.toml @@ -24,7 +24,7 @@ fnv = "1.0.6" futures = "0.3.21" hash-db = { version = "0.15.2", default-features = false } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } -log = "0.4.8" +log = "0.4.16" parking_lot = "0.12.0" sp-database = { version = "4.0.0-dev", path = "../../primitives/database" } sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } diff --git a/client/authority-discovery/Cargo.toml b/client/authority-discovery/Cargo.toml index ba432d073698a..effc8cd715e1d 100644 --- a/client/authority-discovery/Cargo.toml +++ b/client/authority-discovery/Cargo.toml @@ -24,7 +24,7 @@ futures = "0.3.21" futures-timer = "3.0.1" ip_network = "0.4.1" libp2p = { version = "0.40.0", default-features = false, features = ["kad"] } -log = "0.4.8" +log = "0.4.16" prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus", version = "0.10.0-dev" } prost = "0.9" rand = "0.7.2" diff --git a/client/basic-authorship/Cargo.toml b/client/basic-authorship/Cargo.toml index 20560d4c834e7..6e0c15e1af2cc 100644 --- a/client/basic-authorship/Cargo.toml +++ b/client/basic-authorship/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.21" futures-timer = "3.0.1" -log = "0.4.8" +log = "0.4.16" prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus", version = "0.10.0-dev"} sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } diff --git a/client/cli/Cargo.toml b/client/cli/Cargo.toml index 1fabbca46968e..6a4d4b74f963e 100644 --- a/client/cli/Cargo.toml +++ b/client/cli/Cargo.toml @@ -19,7 +19,7 @@ fdlimit = "0.2.1" futures = "0.3.21" hex = "0.4.2" libp2p = "0.40.0" -log = "0.4.11" +log = "0.4.16" names = { version = "0.13.0", default-features = false } rand = "0.7.3" regex = "1.5.5" diff --git a/client/consensus/aura/Cargo.toml b/client/consensus/aura/Cargo.toml index ac90d35dbce3c..beaa29d364672 100644 --- a/client/consensus/aura/Cargo.toml +++ b/client/consensus/aura/Cargo.toml @@ -25,7 +25,7 @@ sp-consensus-slots = { version = "0.10.0-dev", path = "../../../primitives/conse thiserror = "1.0" futures = "0.3.21" sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } -log = "0.4.8" +log = "0.4.16" sp-core = { version = "6.0.0", path = "../../../primitives/core" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } sc-consensus-slots = { version = "0.10.0-dev", path = "../slots" } diff --git a/client/consensus/babe/Cargo.toml b/client/consensus/babe/Cargo.toml index 20cc359849f52..48313cd0437e0 100644 --- a/client/consensus/babe/Cargo.toml +++ b/client/consensus/babe/Cargo.toml @@ -45,7 +45,7 @@ fork-tree = { version = "3.0.0", path = "../../../utils/fork-tree" } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus", version = "0.10.0-dev" } futures = "0.3.21" parking_lot = "0.12.0" -log = "0.4.8" +log = "0.4.16" schnorrkel = { version = "0.9.1", features = ["preaudit_deprecated"] } rand = "0.7.2" merlin = "2.0" diff --git a/client/consensus/common/Cargo.toml b/client/consensus/common/Cargo.toml index 4bd5cff457f5c..f4f7626ab6b7e 100644 --- a/client/consensus/common/Cargo.toml +++ b/client/consensus/common/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] thiserror = "1.0.30" libp2p = { version = "0.40.0", default-features = false } -log = "0.4.8" +log = "0.4.16" futures = { version = "0.3.21", features = ["thread-pool"] } futures-timer = "3.0.1" sc-client-api = { version = "4.0.0-dev", path = "../../api" } diff --git a/client/consensus/manual-seal/Cargo.toml b/client/consensus/manual-seal/Cargo.toml index 360cfe862f875..c1568ea488337 100644 --- a/client/consensus/manual-seal/Cargo.toml +++ b/client/consensus/manual-seal/Cargo.toml @@ -18,7 +18,7 @@ futures = "0.3.21" jsonrpc-core = "18.0.0" jsonrpc-core-client = "18.0.0" jsonrpc-derive = "18.0.0" -log = "0.4.8" +log = "0.4.16" codec = { package = "parity-scale-codec", version = "3.0.0" } serde = { version = "1.0", features = ["derive"] } assert_matches = "1.3.0" diff --git a/client/consensus/pow/Cargo.toml b/client/consensus/pow/Cargo.toml index 4ddbf0c0ead7f..fc0151d293bbd 100644 --- a/client/consensus/pow/Cargo.toml +++ b/client/consensus/pow/Cargo.toml @@ -24,7 +24,7 @@ sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } sp-consensus-pow = { version = "0.10.0-dev", path = "../../../primitives/consensus/pow" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } -log = "0.4.8" +log = "0.4.16" futures = "0.3.21" futures-timer = "3.0.1" parking_lot = "0.12.0" diff --git a/client/consensus/slots/Cargo.toml b/client/consensus/slots/Cargo.toml index 2884f8443e30e..8689818de8c17 100644 --- a/client/consensus/slots/Cargo.toml +++ b/client/consensus/slots/Cargo.toml @@ -29,7 +29,7 @@ sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } sp-timestamp = { version = "4.0.0-dev", path = "../../../primitives/timestamp" } futures = "0.3.21" futures-timer = "3.0.1" -log = "0.4.11" +log = "0.4.16" thiserror = "1.0.30" async-trait = "0.1.50" diff --git a/client/db/Cargo.toml b/client/db/Cargo.toml index 12bb29958cfec..071e7aa78ddac 100644 --- a/client/db/Cargo.toml +++ b/client/db/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] parking_lot = "0.12.0" -log = "0.4.8" +log = "0.4.16" kvdb = "0.11.0" kvdb-rocksdb = { version = "0.15.2", optional = true } kvdb-memorydb = "0.11.0" diff --git a/client/executor/wasmi/Cargo.toml b/client/executor/wasmi/Cargo.toml index cab254f1c71f6..99b8010c07502 100644 --- a/client/executor/wasmi/Cargo.toml +++ b/client/executor/wasmi/Cargo.toml @@ -14,7 +14,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = "0.4.8" +log = "0.4.16" wasmi = "0.9.1" codec = { package = "parity-scale-codec", version = "3.0.0" } sc-executor-common = { version = "0.10.0-dev", path = "../common" } diff --git a/client/executor/wasmtime/Cargo.toml b/client/executor/wasmtime/Cargo.toml index 233c2e7d8f098..15f3a40cd46b1 100644 --- a/client/executor/wasmtime/Cargo.toml +++ b/client/executor/wasmtime/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] libc = "0.2.121" cfg-if = "1.0" -log = "0.4.8" +log = "0.4.16" parity-wasm = "0.42.0" codec = { package = "parity-scale-codec", version = "3.0.0" } sc-executor-common = { version = "0.10.0-dev", path = "../common" } diff --git a/client/finality-grandpa/Cargo.toml b/client/finality-grandpa/Cargo.toml index 58dc22192c0c1..2d011fb96be59 100644 --- a/client/finality-grandpa/Cargo.toml +++ b/client/finality-grandpa/Cargo.toml @@ -20,7 +20,7 @@ fork-tree = { version = "3.0.0", path = "../../utils/fork-tree" } futures = "0.3.21" futures-timer = "3.0.1" hex = "0.4.2" -log = "0.4.8" +log = "0.4.16" parking_lot = "0.12.0" rand = "0.8.4" ahash = "0.7.6" diff --git a/client/informant/Cargo.toml b/client/informant/Cargo.toml index e58763a4ebcc9..9ba994b5a66c7 100644 --- a/client/informant/Cargo.toml +++ b/client/informant/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] ansi_term = "0.12.1" futures = "0.3.21" futures-timer = "3.0.1" -log = "0.4.8" +log = "0.4.16" parity-util-mem = { version = "0.11.0", default-features = false, features = ["primitive-types"] } sc-client-api = { version = "4.0.0-dev", path = "../api" } sc-network = { version = "0.10.0-dev", path = "../network" } diff --git a/client/network-gossip/Cargo.toml b/client/network-gossip/Cargo.toml index 6e566a100bae5..2989ff5b51412 100644 --- a/client/network-gossip/Cargo.toml +++ b/client/network-gossip/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] futures = "0.3.21" futures-timer = "3.0.1" libp2p = { version = "0.40.0", default-features = false } -log = "0.4.8" +log = "0.4.16" lru = "0.7.5" ahash = "0.7.6" prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } diff --git a/client/network/Cargo.toml b/client/network/Cargo.toml index f0a6a949d1641..6fe804ce52413 100644 --- a/client/network/Cargo.toml +++ b/client/network/Cargo.toml @@ -35,7 +35,7 @@ ip_network = "0.4.1" linked-hash-map = "0.5.4" linked_hash_set = "0.1.3" lru = "0.7.5" -log = "0.4.8" +log = "0.4.16" parking_lot = "0.12.0" pin-project = "1.0.10" prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } diff --git a/client/network/test/Cargo.toml b/client/network/test/Cargo.toml index 3c72bd404492e..642654bcb2de5 100644 --- a/client/network/test/Cargo.toml +++ b/client/network/test/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] async-std = "1.11.0" sc-network = { version = "0.10.0-dev", path = "../" } -log = "0.4.8" +log = "0.4.16" parking_lot = "0.12.0" futures = "0.3.21" futures-timer = "3.0.1" diff --git a/client/peerset/Cargo.toml b/client/peerset/Cargo.toml index b292f300ba353..5cda0913da1a1 100644 --- a/client/peerset/Cargo.toml +++ b/client/peerset/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] futures = "0.3.21" libp2p = { version = "0.40.0", default-features = false } sc-utils = { version = "4.0.0-dev", path = "../utils"} -log = "0.4.8" +log = "0.4.16" serde_json = "1.0.79" wasm-timer = "0.2" diff --git a/client/proposer-metrics/Cargo.toml b/client/proposer-metrics/Cargo.toml index 1a4b1fd4ce2c9..fc80d5b14fd8f 100644 --- a/client/proposer-metrics/Cargo.toml +++ b/client/proposer-metrics/Cargo.toml @@ -13,5 +13,5 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = "0.4.8" +log = "0.4.16" prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus", version = "0.10.0-dev"} diff --git a/client/rpc-api/Cargo.toml b/client/rpc-api/Cargo.toml index 20a18ec6c61cc..21354737ce2cb 100644 --- a/client/rpc-api/Cargo.toml +++ b/client/rpc-api/Cargo.toml @@ -19,7 +19,7 @@ jsonrpc-core = "18.0.0" jsonrpc-core-client = "18.0.0" jsonrpc-derive = "18.0.0" jsonrpc-pubsub = "18.0.0" -log = "0.4.8" +log = "0.4.16" parking_lot = "0.12.0" thiserror = "1.0" diff --git a/client/rpc-servers/Cargo.toml b/client/rpc-servers/Cargo.toml index c5af4ba200fe0..12f5c23493ad8 100644 --- a/client/rpc-servers/Cargo.toml +++ b/client/rpc-servers/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] futures = "0.3.21" jsonrpc-core = "18.0.0" pubsub = { package = "jsonrpc-pubsub", version = "18.0.0" } -log = "0.4.8" +log = "0.4.16" prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus", version = "0.10.0-dev"} serde_json = "1.0.79" tokio = { version = "1.17.0", features = ["parking_lot"] } diff --git a/client/rpc/Cargo.toml b/client/rpc/Cargo.toml index 4fea6501f693d..0035ddaa94502 100644 --- a/client/rpc/Cargo.toml +++ b/client/rpc/Cargo.toml @@ -19,7 +19,7 @@ sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.21" jsonrpc-pubsub = "18.0.0" -log = "0.4.8" +log = "0.4.16" sp-core = { version = "6.0.0", path = "../../primitives/core" } rpc = { package = "jsonrpc-core", version = "18.0.0" } sp-version = { version = "5.0.0", path = "../../primitives/version" } diff --git a/client/service/Cargo.toml b/client/service/Cargo.toml index 490ef1cbb16b0..575305a597798 100644 --- a/client/service/Cargo.toml +++ b/client/service/Cargo.toml @@ -28,7 +28,7 @@ jsonrpc-pubsub = "18.0" jsonrpc-core = "18.0" rand = "0.7.3" parking_lot = "0.12.0" -log = "0.4.11" +log = "0.4.16" futures-timer = "3.0.1" exit-future = "0.2.0" pin-project = "1.0.10" diff --git a/client/service/test/Cargo.toml b/client/service/test/Cargo.toml index ab92de4e84f8b..854e8f5b74659 100644 --- a/client/service/test/Cargo.toml +++ b/client/service/test/Cargo.toml @@ -16,7 +16,7 @@ hex = "0.4" hex-literal = "0.3.4" tempfile = "3.1.0" tokio = { version = "1.17.0", features = ["time"] } -log = "0.4.8" +log = "0.4.16" fdlimit = "0.2.1" parking_lot = "0.12.0" sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } diff --git a/client/state-db/Cargo.toml b/client/state-db/Cargo.toml index 42ef4338b4066..3ca5a9065045f 100644 --- a/client/state-db/Cargo.toml +++ b/client/state-db/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] parking_lot = "0.12.0" -log = "0.4.11" +log = "0.4.16" sc-client-api = { version = "4.0.0-dev", path = "../api" } sp-core = { version = "6.0.0", path = "../../primitives/core" } codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } diff --git a/client/sysinfo/Cargo.toml b/client/sysinfo/Cargo.toml index 540918cb37c01..15e9545094cbf 100644 --- a/client/sysinfo/Cargo.toml +++ b/client/sysinfo/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] futures = "0.3.19" -log = "0.4.11" +log = "0.4.16" rand = "0.7.3" rand_pcg = "0.2.1" regex = "1" diff --git a/client/telemetry/Cargo.toml b/client/telemetry/Cargo.toml index 67583e325f34c..bb517290cef51 100644 --- a/client/telemetry/Cargo.toml +++ b/client/telemetry/Cargo.toml @@ -19,7 +19,7 @@ parking_lot = "0.12.0" futures = "0.3.21" wasm-timer = "0.2.5" libp2p = { version = "0.40.0", default-features = false, features = ["dns-async-std", "tcp-async-io", "wasm-ext", "websocket"] } -log = "0.4.8" +log = "0.4.16" pin-project = "1.0.10" rand = "0.7.2" serde = { version = "1.0.136", features = ["derive"] } diff --git a/client/tracing/Cargo.toml b/client/tracing/Cargo.toml index 599e310a85157..121d55ca930b8 100644 --- a/client/tracing/Cargo.toml +++ b/client/tracing/Cargo.toml @@ -18,7 +18,7 @@ atty = "0.2.13" chrono = "0.4.19" lazy_static = "1.4.0" libc = "0.2.121" -log = { version = "0.4.8" } +log = { version = "0.4.16" } once_cell = "1.8.0" parking_lot = "0.12.0" regex = "1.5.5" diff --git a/client/transaction-pool/Cargo.toml b/client/transaction-pool/Cargo.toml index df309760b847b..f651b2022032a 100644 --- a/client/transaction-pool/Cargo.toml +++ b/client/transaction-pool/Cargo.toml @@ -17,7 +17,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0" } thiserror = "1.0.30" futures = "0.3.21" futures-timer = "3.0.2" -log = "0.4.8" +log = "0.4.16" parity-util-mem = { version = "0.11.0", default-features = false, features = ["primitive-types"] } parking_lot = "0.12.0" prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus", version = "0.10.0-dev"} diff --git a/client/transaction-pool/api/Cargo.toml b/client/transaction-pool/api/Cargo.toml index 8dd5e7253f965..84395aaf2eace 100644 --- a/client/transaction-pool/api/Cargo.toml +++ b/client/transaction-pool/api/Cargo.toml @@ -10,7 +10,7 @@ description = "Transaction pool client facing API." [dependencies] futures = "0.3.21" -log = "0.4.8" +log = "0.4.16" serde = { version = "1.0.136", features = ["derive"] } thiserror = "1.0.30" diff --git a/frame/babe/Cargo.toml b/frame/babe/Cargo.toml index a13a2d71f25d3..2c3af69b3b4a1 100644 --- a/frame/babe/Cargo.toml +++ b/frame/babe/Cargo.toml @@ -29,7 +29,7 @@ sp-runtime = { version = "6.0.0", default-features = false, path = "../../primit sp-session = { version = "4.0.0-dev", default-features = false, path = "../../primitives/session" } sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -log = { version = "0.4.14", default-features = false } +log = { version = "0.4.16", default-features = false } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } diff --git a/frame/bags-list/Cargo.toml b/frame/bags-list/Cargo.toml index 87b8d7939e125..8a47fd7d9e7d2 100644 --- a/frame/bags-list/Cargo.toml +++ b/frame/bags-list/Cargo.toml @@ -27,7 +27,7 @@ frame-system = { version = "4.0.0-dev", default-features = false, path = "../sys frame-election-provider-support = { version = "4.0.0-dev", default-features = false, path = "../election-provider-support" } # third party -log = { version = "0.4.14", default-features = false } +log = { version = "0.4.16", default-features = false } # Optional imports for benchmarking frame-benchmarking = { version = "4.0.0-dev", path = "../benchmarking", optional = true, default-features = false } diff --git a/frame/bags-list/remote-tests/Cargo.toml b/frame/bags-list/remote-tests/Cargo.toml index e81c4f1a8d4e8..05675741ae51b 100644 --- a/frame/bags-list/remote-tests/Cargo.toml +++ b/frame/bags-list/remote-tests/Cargo.toml @@ -31,5 +31,5 @@ sp-std = { path = "../../../primitives/std", version = "4.0.0" } remote-externalities = { path = "../../../utils/frame/remote-externalities", version = "0.10.0-dev" } # others -log = "0.4.14" +log = "0.4.16" tokio = { version = "1", features = ["macros"] } diff --git a/frame/balances/Cargo.toml b/frame/balances/Cargo.toml index a0de11638e664..46a15f9c6d7ef 100644 --- a/frame/balances/Cargo.toml +++ b/frame/balances/Cargo.toml @@ -20,7 +20,7 @@ sp-runtime = { version = "6.0.0", default-features = false, path = "../../primit frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -log = { version = "0.4.14", default-features = false } +log = { version = "0.4.16", default-features = false } [dev-dependencies] sp-io = { version = "6.0.0", path = "../../primitives/io" } diff --git a/frame/beefy-mmr/Cargo.toml b/frame/beefy-mmr/Cargo.toml index 9da8198b405f7..40e16ad780a61 100644 --- a/frame/beefy-mmr/Cargo.toml +++ b/frame/beefy-mmr/Cargo.toml @@ -10,7 +10,7 @@ repository = "https://github.com/paritytech/substrate" [dependencies] hex = { version = "0.4", optional = true } codec = { version = "3.0.0", package = "parity-scale-codec", default-features = false, features = ["derive"] } -log = { version = "0.4.13", default-features = false } +log = { version = "0.4.16", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", optional = true } diff --git a/frame/benchmarking/Cargo.toml b/frame/benchmarking/Cargo.toml index ce301ff9034a2..aa551095d1d7e 100644 --- a/frame/benchmarking/Cargo.toml +++ b/frame/benchmarking/Cargo.toml @@ -26,7 +26,7 @@ sp-application-crypto = { version = "6.0.0", path = "../../primitives/applicatio sp-storage = { version = "6.0.0", path = "../../primitives/storage", default-features = false } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -log = { version = "0.4.14", default-features = false } +log = { version = "0.4.16", default-features = false } serde = { version = "1.0.136", optional = true } [dev-dependencies] diff --git a/frame/bounties/Cargo.toml b/frame/bounties/Cargo.toml index a158895a252be..2eb8f486d3556 100644 --- a/frame/bounties/Cargo.toml +++ b/frame/bounties/Cargo.toml @@ -25,7 +25,7 @@ pallet-treasury = { version = "4.0.0-dev", default-features = false, path = "../ sp-io = { version = "6.0.0", path = "../../primitives/io", default-features = false } sp-core = { version = "6.0.0", path = "../../primitives/core", default-features = false } frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } -log = { version = "0.4.14", default-features = false } +log = { version = "0.4.16", default-features = false } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } diff --git a/frame/child-bounties/Cargo.toml b/frame/child-bounties/Cargo.toml index 9d2e1031604ec..b6a38fb22548d 100644 --- a/frame/child-bounties/Cargo.toml +++ b/frame/child-bounties/Cargo.toml @@ -26,7 +26,7 @@ pallet-bounties = { version = "4.0.0-dev", default-features = false, path = "../ sp-io = { version = "6.0.0", path = "../../primitives/io", default-features = false } sp-core = { version = "6.0.0", path = "../../primitives/core", default-features = false } frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } -log = { version = "0.4.14", default-features = false } +log = { version = "0.4.16", default-features = false } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } diff --git a/frame/collective/Cargo.toml b/frame/collective/Cargo.toml index c9793e155c2a1..7e1186143270c 100644 --- a/frame/collective/Cargo.toml +++ b/frame/collective/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -log = { version = "0.4.14", default-features = false } +log = { version = "0.4.16", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } diff --git a/frame/election-provider-multi-phase/Cargo.toml b/frame/election-provider-multi-phase/Cargo.toml index 48134966a9253..50afd274c3f7d 100644 --- a/frame/election-provider-multi-phase/Cargo.toml +++ b/frame/election-provider-multi-phase/Cargo.toml @@ -20,7 +20,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = scale-info = { version = "2.0.1", default-features = false, features = [ "derive", ] } -log = { version = "0.4.14", default-features = false } +log = { version = "0.4.16", default-features = false } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } diff --git a/frame/examples/basic/Cargo.toml b/frame/examples/basic/Cargo.toml index a54301944a017..d9051dff8f2cb 100644 --- a/frame/examples/basic/Cargo.toml +++ b/frame/examples/basic/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } -log = { version = "0.4.14", default-features = false } +log = { version = "0.4.16", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../benchmarking", optional = true } diff --git a/frame/examples/offchain-worker/Cargo.toml b/frame/examples/offchain-worker/Cargo.toml index 8daf0686d816c..ede25c2c939df 100644 --- a/frame/examples/offchain-worker/Cargo.toml +++ b/frame/examples/offchain-worker/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } lite-json = { version = "0.1", default-features = false } -log = { version = "0.4.14", default-features = false } +log = { version = "0.4.16", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } diff --git a/frame/grandpa/Cargo.toml b/frame/grandpa/Cargo.toml index cad238a4e365f..7312ae7da4cf8 100644 --- a/frame/grandpa/Cargo.toml +++ b/frame/grandpa/Cargo.toml @@ -28,7 +28,7 @@ frame-support = { version = "4.0.0-dev", default-features = false, path = "../su frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } pallet-authorship = { version = "4.0.0-dev", default-features = false, path = "../authorship" } pallet-session = { version = "4.0.0-dev", default-features = false, path = "../session" } -log = { version = "0.4.14", default-features = false } +log = { version = "0.4.16", default-features = false } [dev-dependencies] frame-benchmarking = { version = "4.0.0-dev", path = "../benchmarking" } diff --git a/frame/im-online/Cargo.toml b/frame/im-online/Cargo.toml index 277d4d2fb2de3..8ac2da0fbb053 100644 --- a/frame/im-online/Cargo.toml +++ b/frame/im-online/Cargo.toml @@ -24,7 +24,7 @@ sp-runtime = { version = "6.0.0", default-features = false, path = "../../primit sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -log = { version = "0.4.14", default-features = false } +log = { version = "0.4.16", default-features = false } frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } diff --git a/frame/membership/Cargo.toml b/frame/membership/Cargo.toml index 40ad619e904cc..a6ea127b79cba 100644 --- a/frame/membership/Cargo.toml +++ b/frame/membership/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -log = { version = "0.4.0", default-features = false } +log = { version = "0.4.16", default-features = false } sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } diff --git a/frame/node-authorization/Cargo.toml b/frame/node-authorization/Cargo.toml index 6ae1f21336b6c..cfd1939d36ed7 100644 --- a/frame/node-authorization/Cargo.toml +++ b/frame/node-authorization/Cargo.toml @@ -20,7 +20,7 @@ sp-core = { version = "6.0.0", default-features = false, path = "../../primitive sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -log = { version = "0.4.14", default-features = false } +log = { version = "0.4.16", default-features = false } [features] default = ["std"] diff --git a/frame/offences/Cargo.toml b/frame/offences/Cargo.toml index 01409d78053c5..bb728bd05f2a1 100644 --- a/frame/offences/Cargo.toml +++ b/frame/offences/Cargo.toml @@ -22,7 +22,7 @@ sp-runtime = { version = "6.0.0", default-features = false, path = "../../primit sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -log = { version = "0.4.14", default-features = false } +log = { version = "0.4.16", default-features = false } [dev-dependencies] sp-io = { version = "6.0.0", path = "../../primitives/io" } diff --git a/frame/scheduler/Cargo.toml b/frame/scheduler/Cargo.toml index 9000d1f693cca..577deb84e559a 100644 --- a/frame/scheduler/Cargo.toml +++ b/frame/scheduler/Cargo.toml @@ -12,7 +12,7 @@ readme = "README.md" [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -log = { version = "0.4.14", default-features = false } +log = { version = "0.4.16", default-features = false } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } diff --git a/frame/session/Cargo.toml b/frame/session/Cargo.toml index 28f2df47027bc..d1407f1509307 100644 --- a/frame/session/Cargo.toml +++ b/frame/session/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = { version = "0.4.0", default-features = false } +log = { version = "0.4.16", default-features = false } impl-trait-for-tuples = "0.2.2" codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } diff --git a/frame/staking/Cargo.toml b/frame/staking/Cargo.toml index 073d959e2d1dd..66ac5e0ff1b36 100644 --- a/frame/staking/Cargo.toml +++ b/frame/staking/Cargo.toml @@ -30,7 +30,7 @@ pallet-session = { version = "4.0.0-dev", default-features = false, features = [ pallet-authorship = { version = "4.0.0-dev", default-features = false, path = "../authorship" } sp-application-crypto = { version = "6.0.0", default-features = false, path = "../../primitives/application-crypto" } frame-election-provider-support = { version = "4.0.0-dev", default-features = false, path = "../election-provider-support" } -log = { version = "0.4.14", default-features = false } +log = { version = "0.4.16", default-features = false } # Optional imports for benchmarking frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } diff --git a/frame/staking/reward-fn/Cargo.toml b/frame/staking/reward-fn/Cargo.toml index 9a768becae830..f396030a1ae9a 100644 --- a/frame/staking/reward-fn/Cargo.toml +++ b/frame/staking/reward-fn/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] sp-arithmetic = { version = "5.0.0", default-features = false, path = "../../../primitives/arithmetic" } -log = { version = "0.4.14", default-features = false } +log = { version = "0.4.16", default-features = false } [features] default = ["std"] diff --git a/frame/state-trie-migration/Cargo.toml b/frame/state-trie-migration/Cargo.toml index 0124ba5b83283..0c6ad240281e1 100644 --- a/frame/state-trie-migration/Cargo.toml +++ b/frame/state-trie-migration/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } -log = { version = "0.4.14", default-features = false } +log = { version = "0.4.16", default-features = false } sp-std = { default-features = false, path = "../../primitives/std" } sp-io = { default-features = false, path = "../../primitives/io" } diff --git a/frame/support/Cargo.toml b/frame/support/Cargo.toml index ce04fddb8521f..f0d663f7a26fa 100644 --- a/frame/support/Cargo.toml +++ b/frame/support/Cargo.toml @@ -33,7 +33,7 @@ sp-state-machine = { version = "0.12.0", optional = true, path = "../../primitiv bitflags = "1.3" impl-trait-for-tuples = "0.2.2" smallvec = "1.8.0" -log = { version = "0.4.14", default-features = false } +log = { version = "0.4.16", default-features = false } sp-core-hashing-proc-macro = { version = "5.0.0", path = "../../primitives/core/hashing/proc-macro" } k256 = { version = "0.10.2", default-features = false, features = ["ecdsa"] } diff --git a/frame/system/Cargo.toml b/frame/system/Cargo.toml index e9196c4eb94f4..935038837d955 100644 --- a/frame/system/Cargo.toml +++ b/frame/system/Cargo.toml @@ -22,7 +22,7 @@ sp-io = { version = "6.0.0", path = "../../primitives/io", default-features = fa sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } sp-version = { version = "5.0.0", default-features = false, path = "../../primitives/version" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } -log = { version = "0.4.14", default-features = false } +log = { version = "0.4.16", default-features = false } [dev-dependencies] criterion = "0.3.3" diff --git a/frame/timestamp/Cargo.toml b/frame/timestamp/Cargo.toml index 72d632faf8583..83eb8a38eca77 100644 --- a/frame/timestamp/Cargo.toml +++ b/frame/timestamp/Cargo.toml @@ -25,7 +25,7 @@ frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = " frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } sp-timestamp = { version = "4.0.0-dev", default-features = false, path = "../../primitives/timestamp" } -log = { version = "0.4.14", default-features = false } +log = { version = "0.4.16", default-features = false } [dev-dependencies] sp-io ={ version = "6.0.0", path = "../../primitives/io" } diff --git a/frame/tips/Cargo.toml b/frame/tips/Cargo.toml index dc65d5c28d1c8..6cfdd2d1e7062 100644 --- a/frame/tips/Cargo.toml +++ b/frame/tips/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -log = { version = "0.4.0", default-features = false } +log = { version = "0.4.16", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", features = ["derive"], optional = true } diff --git a/frame/uniques/Cargo.toml b/frame/uniques/Cargo.toml index b0abb814eb5af..e8b95b8cb80c7 100644 --- a/frame/uniques/Cargo.toml +++ b/frame/uniques/Cargo.toml @@ -20,7 +20,7 @@ sp-runtime = { version = "6.0.0", default-features = false, path = "../../primit frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } -log = { version = "0.4.14", default-features = false } +log = { version = "0.4.16", default-features = false } [dev-dependencies] sp-std = { version = "4.0.0", path = "../../primitives/std" } diff --git a/frame/vesting/Cargo.toml b/frame/vesting/Cargo.toml index 83f897847f79a..01ce92c6eaa4f 100644 --- a/frame/vesting/Cargo.toml +++ b/frame/vesting/Cargo.toml @@ -22,7 +22,7 @@ sp-runtime = { version = "6.0.0", default-features = false, path = "../../primit frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } -log = { version = "0.4.0", default-features = false } +log = { version = "0.4.16", default-features = false } [dev-dependencies] sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } diff --git a/primitives/api/Cargo.toml b/primitives/api/Cargo.toml index a0dd8eeb31e7a..3a94cd8e0bd88 100644 --- a/primitives/api/Cargo.toml +++ b/primitives/api/Cargo.toml @@ -23,7 +23,7 @@ sp-state-machine = { version = "0.12.0", optional = true, path = "../state-machi hash-db = { version = "0.15.2", optional = true } thiserror = { version = "1.0.30", optional = true } -log = { version = "0.4.14", default-features = false } +log = { version = "0.4.16", default-features = false } [dev-dependencies] sp-test-primitives = { version = "2.0.0", path = "../test-primitives" } diff --git a/primitives/api/test/Cargo.toml b/primitives/api/test/Cargo.toml index 9f9f399234db4..2acf09d46c886 100644 --- a/primitives/api/test/Cargo.toml +++ b/primitives/api/test/Cargo.toml @@ -27,7 +27,7 @@ rustversion = "1.0.6" [dev-dependencies] criterion = "0.3.0" futures = "0.3.21" -log = "0.4.14" +log = "0.4.16" sp-core = { version = "6.0.0", path = "../../core" } [[bench]] diff --git a/primitives/blockchain/Cargo.toml b/primitives/blockchain/Cargo.toml index c6fcdd3cb1c02..f242132798efa 100644 --- a/primitives/blockchain/Cargo.toml +++ b/primitives/blockchain/Cargo.toml @@ -14,7 +14,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = "0.4.11" +log = "0.4.16" lru = "0.7.5" parking_lot = "0.12.0" thiserror = "1.0.30" diff --git a/primitives/consensus/common/Cargo.toml b/primitives/consensus/common/Cargo.toml index 35bd7f33fbf47..afbe437a16fa4 100644 --- a/primitives/consensus/common/Cargo.toml +++ b/primitives/consensus/common/Cargo.toml @@ -19,7 +19,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", features = [ "derive", ] } futures = { version = "0.3.21", features = ["thread-pool"] } -log = "0.4.8" +log = "0.4.16" sp-core = { path = "../../core", version = "6.0.0"} sp-inherents = { version = "4.0.0-dev", path = "../../inherents" } sp-state-machine = { version = "0.12.0", path = "../../state-machine" } diff --git a/primitives/core/Cargo.toml b/primitives/core/Cargo.toml index 402e3ebf07df1..9c79cb4843401 100644 --- a/primitives/core/Cargo.toml +++ b/primitives/core/Cargo.toml @@ -18,7 +18,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = "max-encoded-len", ] } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -log = { version = "0.4.11", default-features = false } +log = { version = "0.4.16", default-features = false } serde = { version = "1.0.136", optional = true, features = ["derive"] } byteorder = { version = "1.3.2", default-features = false } primitive-types = { version = "0.11.1", default-features = false, features = ["codec", "scale-info"] } diff --git a/primitives/finality-grandpa/Cargo.toml b/primitives/finality-grandpa/Cargo.toml index ebc067e630c15..e6464207e67f2 100644 --- a/primitives/finality-grandpa/Cargo.toml +++ b/primitives/finality-grandpa/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } grandpa = { package = "finality-grandpa", version = "0.15.0", default-features = false, features = ["derive-codec"] } -log = { version = "0.4.8", optional = true } +log = { version = "0.4.16", optional = true } serde = { version = "1.0.136", optional = true, features = ["derive"] } sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } sp-application-crypto = { version = "6.0.0", default-features = false, path = "../application-crypto" } diff --git a/primitives/io/Cargo.toml b/primitives/io/Cargo.toml index bdae6174a87cb..9964fe2eea318 100644 --- a/primitives/io/Cargo.toml +++ b/primitives/io/Cargo.toml @@ -27,7 +27,7 @@ sp-runtime-interface = { version = "6.0.0", default-features = false, path = ".. sp-trie = { version = "6.0.0", optional = true, path = "../trie" } sp-externalities = { version = "0.12.0", optional = true, path = "../externalities" } sp-tracing = { version = "5.0.0", default-features = false, path = "../tracing" } -log = { version = "0.4.8", optional = true } +log = { version = "0.4.16", optional = true } futures = { version = "0.3.21", features = ["thread-pool"], optional = true } parking_lot = { version = "0.12.0", optional = true } secp256k1 = { version = "0.21.2", features = ["recovery", "global-context"], optional = true } diff --git a/primitives/merkle-mountain-range/Cargo.toml b/primitives/merkle-mountain-range/Cargo.toml index ddb820f9e6cc4..d6e244d50c495 100644 --- a/primitives/merkle-mountain-range/Cargo.toml +++ b/primitives/merkle-mountain-range/Cargo.toml @@ -13,7 +13,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } -log = { version = "0.4.14", default-features = false } +log = { version = "0.4.16", default-features = false } serde = { version = "1.0.136", optional = true, features = ["derive"] } sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } diff --git a/primitives/runtime/Cargo.toml b/primitives/runtime/Cargo.toml index 9e9b01ebea3d2..2d6080f7af849 100644 --- a/primitives/runtime/Cargo.toml +++ b/primitives/runtime/Cargo.toml @@ -23,7 +23,7 @@ sp-application-crypto = { version = "6.0.0", default-features = false, path = ". sp-arithmetic = { version = "5.0.0", default-features = false, path = "../arithmetic" } sp-std = { version = "4.0.0", default-features = false, path = "../std" } sp-io = { version = "6.0.0", default-features = false, path = "../io" } -log = { version = "0.4.14", default-features = false } +log = { version = "0.4.16", default-features = false } paste = "1.0" rand = { version = "0.7.2", optional = true } impl-trait-for-tuples = "0.2.2" diff --git a/primitives/state-machine/Cargo.toml b/primitives/state-machine/Cargo.toml index 80651130575ea..0080c72b1f0d0 100644 --- a/primitives/state-machine/Cargo.toml +++ b/primitives/state-machine/Cargo.toml @@ -14,7 +14,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = { version = "0.4.11", optional = true } +log = { version = "0.4.16", optional = true } thiserror = { version = "1.0.30", optional = true } parking_lot = { version = "0.12.0", optional = true } hash-db = { version = "0.15.2", default-features = false } diff --git a/primitives/tasks/Cargo.toml b/primitives/tasks/Cargo.toml index 7f05afeaf0cec..bf97212d08914 100644 --- a/primitives/tasks/Cargo.toml +++ b/primitives/tasks/Cargo.toml @@ -14,7 +14,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = { version = "0.4.8", optional = true } +log = { version = "0.4.16", optional = true } sp-core = { version = "6.0.0", default-features = false, path = "../core" } sp-externalities = { version = "0.12.0", optional = true, path = "../externalities" } sp-io = { version = "6.0.0", default-features = false, path = "../io" } diff --git a/primitives/timestamp/Cargo.toml b/primitives/timestamp/Cargo.toml index 176a4db5cb45b..db753b0b708d9 100644 --- a/primitives/timestamp/Cargo.toml +++ b/primitives/timestamp/Cargo.toml @@ -19,7 +19,7 @@ sp-runtime = { version = "6.0.0", default-features = false, path = "../runtime" codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../inherents" } thiserror = { version = "1.0.30", optional = true } -log = { version = "0.4.8", optional = true } +log = { version = "0.4.16", optional = true } futures-timer = { version = "3.0.2", optional = true } async-trait = { version = "0.1.50", optional = true } diff --git a/primitives/transaction-storage-proof/Cargo.toml b/primitives/transaction-storage-proof/Cargo.toml index 966142a7e14fa..7e8949d3da4b4 100644 --- a/primitives/transaction-storage-proof/Cargo.toml +++ b/primitives/transaction-storage-proof/Cargo.toml @@ -20,7 +20,7 @@ sp-trie = { version = "6.0.0", optional = true, path = "../trie" } sp-core = { version = "6.0.0", path = "../core", optional = true } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -log = { version = "0.4.8", optional = true } +log = { version = "0.4.16", optional = true } async-trait = { version = "0.1.50", optional = true } [features] diff --git a/primitives/wasm-interface/Cargo.toml b/primitives/wasm-interface/Cargo.toml index 775ab1c5b7060..3473370179b83 100644 --- a/primitives/wasm-interface/Cargo.toml +++ b/primitives/wasm-interface/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] wasmi = { version = "0.9.1", optional = true } wasmtime = { version = "0.35.3", optional = true, default-features = false } -log = { version = "0.4.14", optional = true } +log = { version = "0.4.16", optional = true } impl-trait-for-tuples = "0.2.2" sp-std = { version = "4.0.0", path = "../std", default-features = false } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } diff --git a/test-utils/runtime/Cargo.toml b/test-utils/runtime/Cargo.toml index cbefed27ca9bd..64767877548a9 100644 --- a/test-utils/runtime/Cargo.toml +++ b/test-utils/runtime/Cargo.toml @@ -48,7 +48,7 @@ sp-externalities = { version = "0.12.0", default-features = false, path = "../.. # 3rd party cfg-if = "1.0" -log = { version = "0.4.14", default-features = false } +log = { version = "0.4.16", default-features = false } serde = { version = "1.0.136", optional = true, features = ["derive"] } [dev-dependencies] diff --git a/utils/frame/benchmarking-cli/Cargo.toml b/utils/frame/benchmarking-cli/Cargo.toml index 858d16558e821..8a3773fb10022 100644 --- a/utils/frame/benchmarking-cli/Cargo.toml +++ b/utils/frame/benchmarking-cli/Cargo.toml @@ -43,7 +43,7 @@ serde_json = "1.0.79" handlebars = "4.2.2" Inflector = "0.11.4" linked-hash-map = "0.5.4" -log = "0.4.8" +log = "0.4.16" itertools = "0.10.3" serde_nanos = "0.1.2" kvdb = "0.11.0" diff --git a/utils/frame/remote-externalities/Cargo.toml b/utils/frame/remote-externalities/Cargo.toml index 343dda6892759..7ca5b2f255e28 100644 --- a/utils/frame/remote-externalities/Cargo.toml +++ b/utils/frame/remote-externalities/Cargo.toml @@ -17,7 +17,7 @@ jsonrpsee = { version = "0.10.1", features = ["ws-client", "macros"] } env_logger = "0.9" frame-support = { path = "../../../frame/support", optional = true, version = "4.0.0-dev" } -log = "0.4.11" +log = "0.4.16" codec = { package = "parity-scale-codec", version = "3.0.0" } serde_json = "1.0" serde = "1.0.136" diff --git a/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml b/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml index deb641a89a466..3913bae425757 100644 --- a/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml +++ b/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } serde = { version = "1", features = ["derive"] } -log = { version = "0.4.14", default-features = false } +log = { version = "0.4.16", default-features = false } sp-std = { path = "../../../../primitives/std" } sp-io = { path = "../../../../primitives/io" } diff --git a/utils/frame/rpc/system/Cargo.toml b/utils/frame/rpc/system/Cargo.toml index 31a6f93e847b1..9f3f2c6f266c5 100644 --- a/utils/frame/rpc/system/Cargo.toml +++ b/utils/frame/rpc/system/Cargo.toml @@ -19,7 +19,7 @@ futures = "0.3.21" jsonrpc-core = "18.0.0" jsonrpc-core-client = "18.0.0" jsonrpc-derive = "18.0.0" -log = "0.4.8" +log = "0.4.16" sp-runtime = { version = "6.0.0", path = "../../../../primitives/runtime" } sp-api = { version = "4.0.0-dev", path = "../../../../primitives/api" } frame-system-rpc-runtime-api = { version = "4.0.0-dev", path = "../../../../frame/system/rpc/runtime-api" } diff --git a/utils/frame/try-runtime/cli/Cargo.toml b/utils/frame/try-runtime/cli/Cargo.toml index abbbe882c2fac..70c890a220a3a 100644 --- a/utils/frame/try-runtime/cli/Cargo.toml +++ b/utils/frame/try-runtime/cli/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] clap = { version = "3.1.6", features = ["derive"] } -log = "0.4.8" +log = "0.4.16" parity-scale-codec = "3.0.0" serde = "1.0.136" zstd = { version = "0.10.0", default-features = false } diff --git a/utils/prometheus/Cargo.toml b/utils/prometheus/Cargo.toml index 4940712f2f4d9..a9c3fd03e1688 100644 --- a/utils/prometheus/Cargo.toml +++ b/utils/prometheus/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = "0.4.8" +log = "0.4.16" prometheus = { version = "0.13.0", default-features = false } futures-util = { version = "0.3.19", default-features = false, features = ["io"] } thiserror = "1.0" From 3b73f11631d42a55c8ce648cc7149e9338531757 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 20 Apr 2022 22:46:34 +0200 Subject: [PATCH 135/484] Bump trybuild from 1.0.53 to 1.0.60 (#11237) Bumps [trybuild](https://github.com/dtolnay/trybuild) from 1.0.53 to 1.0.60. - [Release notes](https://github.com/dtolnay/trybuild/releases) - [Commits](https://github.com/dtolnay/trybuild/compare/1.0.53...1.0.60) --- updated-dependencies: - dependency-name: trybuild dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 7 ++++--- frame/election-provider-support/solution-type/Cargo.toml | 2 +- frame/support/test/Cargo.toml | 2 +- primitives/api/test/Cargo.toml | 2 +- primitives/runtime-interface/Cargo.toml | 2 +- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b7bb085b0962f..3d46093c38c4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11579,14 +11579,15 @@ dependencies = [ [[package]] name = "trybuild" -version = "1.0.53" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d664de8ea7e531ad4c0f5a834f20b8cb2b8e6dfe88d05796ee7887518ed67b9" +checksum = "0da18123d1316f5a65fc9b94e30a0fcf58afb1daff1b8e18f41dc30f5bfc38c8" dependencies = [ "dissimilar", "glob", - "lazy_static", + "once_cell", "serde", + "serde_derive", "serde_json", "termcolor", "toml", diff --git a/frame/election-provider-support/solution-type/Cargo.toml b/frame/election-provider-support/solution-type/Cargo.toml index ca3038c9145ce..5f4454eda48df 100644 --- a/frame/election-provider-support/solution-type/Cargo.toml +++ b/frame/election-provider-support/solution-type/Cargo.toml @@ -27,4 +27,4 @@ sp-arithmetic = { version = "5.0.0", path = "../../../primitives/arithmetic" } # used by generate_solution_type: frame-election-provider-support = { version = "4.0.0-dev", path = ".." } frame-support = { version = "4.0.0-dev", path = "../../support" } -trybuild = "1.0.53" +trybuild = "1.0.60" diff --git a/frame/support/test/Cargo.toml b/frame/support/test/Cargo.toml index 383b7cf812b41..f5f690a0468af 100644 --- a/frame/support/test/Cargo.toml +++ b/frame/support/test/Cargo.toml @@ -23,7 +23,7 @@ sp-runtime = { version = "6.0.0", default-features = false, path = "../../../pri sp-core = { version = "6.0.0", default-features = false, path = "../../../primitives/core" } sp-std = { version = "4.0.0", default-features = false, path = "../../../primitives/std" } sp-version = { version = "5.0.0", default-features = false, path = "../../../primitives/version" } -trybuild = { version = "1.0.53", features = [ "diff" ] } +trybuild = { version = "1.0.60", features = [ "diff" ] } pretty_assertions = "1.0.0" rustversion = "1.0.6" frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } diff --git a/primitives/api/test/Cargo.toml b/primitives/api/test/Cargo.toml index 2acf09d46c886..a36e68ecf9fe0 100644 --- a/primitives/api/test/Cargo.toml +++ b/primitives/api/test/Cargo.toml @@ -21,7 +21,7 @@ sp-consensus = { version = "0.10.0-dev", path = "../../consensus/common" } sc-block-builder = { version = "0.10.0-dev", path = "../../../client/block-builder" } codec = { package = "parity-scale-codec", version = "3.0.0" } sp-state-machine = { version = "0.12.0", path = "../../state-machine" } -trybuild = "1.0.53" +trybuild = "1.0.60" rustversion = "1.0.6" [dev-dependencies] diff --git a/primitives/runtime-interface/Cargo.toml b/primitives/runtime-interface/Cargo.toml index 102592288af14..c9419f73722c6 100644 --- a/primitives/runtime-interface/Cargo.toml +++ b/primitives/runtime-interface/Cargo.toml @@ -31,7 +31,7 @@ sp-state-machine = { version = "0.12.0", path = "../state-machine" } sp-core = { version = "6.0.0", path = "../core" } sp-io = { version = "6.0.0", path = "../io" } rustversion = "1.0.6" -trybuild = "1.0.53" +trybuild = "1.0.60" [features] default = [ "std" ] From d15379d519aeae53212a170ea7f340ebc8045f0a Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Wed, 20 Apr 2022 13:56:07 -0700 Subject: [PATCH 136/484] Configurable call fee refund for signed submissions (#11002) * Refund call fee for all non-invalid signed submissions * Clean up * Fix benchmarks * Remove reward from struct * WIP SignedMaxRefunds * Apply suggestions from code review * Add test for ejected call_fee refunds * Add test for number of calls refunded * Account for read op in mutate * Apply suggestions from code review * Add to node runtime * Don't refund ejected solutions * Update frame/election-provider-multi-phase/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Inegrity test SignedMaxRefunds * Use reward handle to refund call fee * Fix node runtime build * Drain in order of submission * Update frame/election-provider-multi-phase/src/signed.rs * save * Update frame/election-provider-multi-phase/src/signed.rs Co-authored-by: Niklas Adolfsson * Update frame/election-provider-multi-phase/src/signed.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Co-authored-by: Niklas Adolfsson Co-authored-by: Shawn Tabrizi --- bin/node/runtime/src/lib.rs | 1 + .../src/benchmarking.rs | 18 ++- .../election-provider-multi-phase/src/lib.rs | 24 +++- .../election-provider-multi-phase/src/mock.rs | 2 + .../src/signed.rs | 123 ++++++++++++++---- 5 files changed, 133 insertions(+), 35 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 1da670b389192..c523ccc86fc7a 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -674,6 +674,7 @@ impl pallet_election_provider_multi_phase::Config for Runtime { type SignedRewardBase = SignedRewardBase; type SignedDepositBase = SignedDepositBase; type SignedDepositByte = SignedDepositByte; + type SignedMaxRefunds = ConstU32<3>; type SignedDepositWeight = (); type SignedMaxWeight = MinerMaxWeight; type SlashHandler = (); // burn slashes diff --git a/frame/election-provider-multi-phase/src/benchmarking.rs b/frame/election-provider-multi-phase/src/benchmarking.rs index 923e9e2d984cc..6dfb2ac794810 100644 --- a/frame/election-provider-multi-phase/src/benchmarking.rs +++ b/frame/election-provider-multi-phase/src/benchmarking.rs @@ -225,14 +225,24 @@ frame_benchmarking::benchmarks! { compute: Default::default() }; let deposit: BalanceOf = 10u32.into(); - let reward: BalanceOf = 20u32.into(); + + let reward: BalanceOf = T::SignedRewardBase::get(); + let call_fee: BalanceOf = 30u32.into(); assert_ok!(T::Currency::reserve(&receiver, deposit)); assert_eq!(T::Currency::free_balance(&receiver), initial_balance - 10u32.into()); }: { - >::finalize_signed_phase_accept_solution(ready, &receiver, deposit, reward) + >::finalize_signed_phase_accept_solution( + ready, + &receiver, + deposit, + call_fee + ) } verify { - assert_eq!(T::Currency::free_balance(&receiver), initial_balance + 20u32.into()); + assert_eq!( + T::Currency::free_balance(&receiver), + initial_balance + reward + call_fee + ); assert_eq!(T::Currency::reserved_balance(&receiver), 0u32.into()); } @@ -333,7 +343,7 @@ frame_benchmarking::benchmarks! { raw_solution, who: account("submitters", i, SEED), deposit: Default::default(), - reward: Default::default(), + call_fee: Default::default(), }; signed_submissions.insert(signed_submission); } diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 5dc44d38fc365..a9e341bad600f 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -242,7 +242,7 @@ use frame_support::{ use frame_system::{ensure_none, offchain::SendTransactionTypes}; use scale_info::TypeInfo; use sp_arithmetic::{ - traits::{Bounded, CheckedAdd, Saturating, Zero}, + traits::{Bounded, CheckedAdd, Zero}, UpperOf, }; use sp_npos_elections::{ @@ -628,6 +628,10 @@ pub mod pallet { #[pallet::constant] type SignedMaxWeight: Get; + /// The maximum amount of unchecked solutions to refund the call fee for. + #[pallet::constant] + type SignedMaxRefunds: Get; + /// Base reward for a signed solution #[pallet::constant] type SignedRewardBase: Get>; @@ -848,6 +852,11 @@ pub mod pallet { ::MaxVotesPerVoter::get(), as NposSolution>::LIMIT as u32, ); + + // While it won't cause any failures, setting `SignedMaxRefunds` gt + // `SignedMaxSubmissions` is a red flag that the developer does not understand how to + // configure this pallet. + assert!(T::SignedMaxSubmissions::get() >= T::SignedMaxRefunds::get()); } } @@ -988,14 +997,17 @@ pub mod pallet { // create the submission let deposit = Self::deposit_for(&raw_solution, size); - let reward = { + let call_fee = { let call = Call::submit { raw_solution: raw_solution.clone() }; - let call_fee = T::EstimateCallFee::estimate_call_fee(&call, None.into()); - T::SignedRewardBase::get().saturating_add(call_fee) + T::EstimateCallFee::estimate_call_fee(&call, None.into()) }; - let submission = - SignedSubmission { who: who.clone(), deposit, raw_solution: *raw_solution, reward }; + let submission = SignedSubmission { + who: who.clone(), + deposit, + raw_solution: *raw_solution, + call_fee, + }; // insert the submission if the queue has space or it's better than the weakest // eject the weakest if the queue was full diff --git a/frame/election-provider-multi-phase/src/mock.rs b/frame/election-provider-multi-phase/src/mock.rs index 2c9d7bb34dba5..38d9c8dfc1b7e 100644 --- a/frame/election-provider-multi-phase/src/mock.rs +++ b/frame/election-provider-multi-phase/src/mock.rs @@ -257,6 +257,7 @@ parameter_types! { pub static SignedPhase: BlockNumber = 10; pub static UnsignedPhase: BlockNumber = 5; pub static SignedMaxSubmissions: u32 = 5; + pub static SignedMaxRefunds: u32 = 1; pub static SignedDepositBase: Balance = 5; pub static SignedDepositByte: Balance = 0; pub static SignedDepositWeight: Balance = 0; @@ -427,6 +428,7 @@ impl crate::Config for Runtime { type SignedDepositWeight = (); type SignedMaxWeight = SignedMaxWeight; type SignedMaxSubmissions = SignedMaxSubmissions; + type SignedMaxRefunds = SignedMaxRefunds; type SlashHandler = (); type RewardHandler = (); type DataProvider = StakingMock; diff --git a/frame/election-provider-multi-phase/src/signed.rs b/frame/election-provider-multi-phase/src/signed.rs index 465528068e71a..5f61eb7575da4 100644 --- a/frame/election-provider-multi-phase/src/signed.rs +++ b/frame/election-provider-multi-phase/src/signed.rs @@ -38,6 +38,7 @@ use sp_std::{ cmp::Ordering, collections::{btree_map::BTreeMap, btree_set::BTreeSet}, ops::Deref, + vec::Vec, }; /// A raw, unchecked signed submission. @@ -51,8 +52,8 @@ pub struct SignedSubmission { pub deposit: Balance, /// The raw solution itself. pub raw_solution: RawSolution, - /// The reward that should potentially be paid for this solution, if accepted. - pub reward: Balance, + // The estimated fee `who` paid to submit the solution. + pub call_fee: Balance, } impl Ord for SignedSubmission @@ -235,20 +236,33 @@ impl SignedSubmissions { } /// Empty the set of signed submissions, returning an iterator of signed submissions in - /// arbitrary order. + /// order of submission. /// /// Note that if the iterator is dropped without consuming all elements, not all may be removed /// from the underlying `SignedSubmissionsMap`, putting the storages into an invalid state. /// /// Note that, like `put`, this function consumes `Self` and modifies storage. - fn drain(mut self) -> impl Iterator> { + fn drain_submitted_order(mut self) -> impl Iterator> { + let mut keys = SignedSubmissionsMap::::iter_keys() + .filter(|k| { + if self.deletion_overlay.contains(k) { + // Remove submissions that should be deleted. + SignedSubmissionsMap::::remove(k); + false + } else { + true + } + }) + .chain(self.insertion_overlay.keys().copied()) + .collect::>(); + keys.sort(); + SignedSubmissionIndices::::kill(); SignedSubmissionNextIndex::::kill(); - let insertion_overlay = sp_std::mem::take(&mut self.insertion_overlay); - SignedSubmissionsMap::::drain() - .filter(move |(k, _v)| !self.deletion_overlay.contains(k)) - .map(|(_k, v)| v) - .chain(insertion_overlay.into_iter().map(|(_k, v)| v)) + + keys.into_iter().filter_map(move |index| { + SignedSubmissionsMap::::take(index).or_else(|| self.insertion_overlay.remove(&index)) + }) } /// Decode the length of the signed submissions without actually reading the entire struct into @@ -362,7 +376,7 @@ impl Pallet { Self::snapshot_metadata().unwrap_or_default(); while let Some(best) = all_submissions.pop_last() { - let SignedSubmission { raw_solution, who, deposit, reward } = best; + let SignedSubmission { raw_solution, who, deposit, call_fee } = best; let active_voters = raw_solution.solution.voter_count() as u32; let feasibility_weight = { // defensive only: at the end of signed phase, snapshot will exits. @@ -377,7 +391,7 @@ impl Pallet { ready_solution, &who, deposit, - reward, + call_fee, ); found_solution = true; @@ -396,10 +410,23 @@ impl Pallet { // Any unprocessed solution is pointless to even consider. Feasible or malicious, // they didn't end up being used. Unreserve the bonds. let discarded = all_submissions.len(); - for SignedSubmission { who, deposit, .. } in all_submissions.drain() { + let mut refund_count = 0; + let max_refunds = T::SignedMaxRefunds::get(); + + for SignedSubmission { who, deposit, call_fee, .. } in + all_submissions.drain_submitted_order() + { + if refund_count < max_refunds { + // Refund fee + let positive_imbalance = T::Currency::deposit_creating(&who, call_fee); + T::RewardHandler::on_unbalanced(positive_imbalance); + refund_count += 1; + } + + // Unreserve deposit let _remaining = T::Currency::unreserve(&who, deposit); - weight = weight.saturating_add(T::DbWeight::get().writes(1)); debug_assert!(_remaining.is_zero()); + weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 2)); } debug_assert!(!SignedSubmissionIndices::::exists()); @@ -424,20 +451,22 @@ impl Pallet { ready_solution: ReadySolution, who: &T::AccountId, deposit: BalanceOf, - reward: BalanceOf, + call_fee: BalanceOf, ) { // write this ready solution. >::put(ready_solution); + let reward = T::SignedRewardBase::get(); // emit reward event Self::deposit_event(crate::Event::Rewarded { account: who.clone(), value: reward }); - // unreserve deposit. + // Unreserve deposit. let _remaining = T::Currency::unreserve(who, deposit); debug_assert!(_remaining.is_zero()); - // Reward. - let positive_imbalance = T::Currency::deposit_creating(who, reward); + // Reward and refund the call fee. + let positive_imbalance = + T::Currency::deposit_creating(who, reward.saturating_add(call_fee)); T::RewardHandler::on_unbalanced(positive_imbalance); } @@ -496,8 +525,8 @@ mod tests { use super::*; use crate::{ mock::{ - balances, raw_solution, roll_to, ExtBuilder, MultiPhase, Origin, Runtime, - SignedMaxSubmissions, SignedMaxWeight, + balances, raw_solution, roll_to, Balances, ExtBuilder, MultiPhase, Origin, Runtime, + SignedMaxRefunds, SignedMaxSubmissions, SignedMaxWeight, }, Error, Perbill, Phase, }; @@ -599,8 +628,8 @@ mod tests { // 99 is rewarded. assert_eq!(balances(&99), (100 + 7 + 8, 0)); - // 999 gets everything back. - assert_eq!(balances(&999), (100, 0)); + // 999 gets everything back, including the call fee. + assert_eq!(balances(&999), (100 + 8, 0)); }) } @@ -632,6 +661,44 @@ mod tests { }) } + #[test] + fn call_fee_refund_is_limited_by_signed_max_refunds() { + ExtBuilder::default().build_and_execute(|| { + roll_to(15); + assert!(MultiPhase::current_phase().is_signed()); + assert_eq!(SignedMaxRefunds::get(), 1); + assert!(SignedMaxSubmissions::get() > 2); + + for s in 0..SignedMaxSubmissions::get() { + let account = 99 + s as u64; + Balances::make_free_balance_be(&account, 100); + // score is always decreasing + let mut solution = raw_solution(); + solution.score.minimal_stake -= s as u128; + + assert_ok!(MultiPhase::submit(Origin::signed(account), Box::new(solution))); + assert_eq!(balances(&account), (95, 5)); + } + + assert!(MultiPhase::finalize_signed_phase()); + + for s in 0..SignedMaxSubmissions::get() { + let account = 99 + s as u64; + // lower accounts have higher scores + if s == 0 { + // winning solution always gets call fee + reward + assert_eq!(balances(&account), (100 + 8 + 7, 0)) + } else if s == 1 { + // 1 runner up gets their call fee refunded + assert_eq!(balances(&account), (100 + 8, 0)) + } else { + // all other solutions don't get a call fee refund + assert_eq!(balances(&account), (100, 0)); + } + } + }); + } + #[test] fn cannot_submit_worse_with_full_queue_depends_on_threshold() { ExtBuilder::default() @@ -687,12 +754,15 @@ mod tests { assert!(MultiPhase::current_phase().is_signed()); for s in 0..SignedMaxSubmissions::get() { + let account = 99 + s as u64; + Balances::make_free_balance_be(&account, 100); // score is always getting better let solution = RawSolution { score: ElectionScore { minimal_stake: (5 + s).into(), ..Default::default() }, ..Default::default() }; - assert_ok!(MultiPhase::submit(Origin::signed(99), Box::new(solution))); + assert_ok!(MultiPhase::submit(Origin::signed(account), Box::new(solution))); + assert_eq!(balances(&account), (95, 5)); } assert_eq!( @@ -708,7 +778,7 @@ mod tests { score: ElectionScore { minimal_stake: 20, ..Default::default() }, ..Default::default() }; - assert_ok!(MultiPhase::submit(Origin::signed(99), Box::new(solution))); + assert_ok!(MultiPhase::submit(Origin::signed(999), Box::new(solution))); // the one with score 5 was rejected, the new one inserted. assert_eq!( @@ -718,6 +788,9 @@ mod tests { .collect::>(), vec![6, 7, 8, 9, 20] ); + + // the submitter of the ejected solution does *not* get a call fee refund + assert_eq!(balances(&(99 + 0)), (100, 0)); }) } @@ -873,8 +946,8 @@ mod tests { assert_eq!(balances(&99), (100 + 7 + 8, 0)); // 999 is slashed. assert_eq!(balances(&999), (95, 0)); - // 9999 gets everything back. - assert_eq!(balances(&9999), (100, 0)); + // 9999 gets everything back, including the call fee. + assert_eq!(balances(&9999), (100 + 8, 0)); }) } From 5fdb8548523ed7500d9d3e5bc5c2ba20286c9e5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Wed, 20 Apr 2022 23:03:34 +0200 Subject: [PATCH 137/484] Aura: Expose `SimpleSlotWorker` (#11247) Instead of just returning `SlotWorker` from `build_aura_worker` we now return `SimpleSlotWorker`. This is required for some future changes in Cumulus. --- client/consensus/aura/src/lib.rs | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/client/consensus/aura/src/lib.rs b/client/consensus/aura/src/lib.rs index f6ea2e08e1e88..d803aa4ae97f8 100644 --- a/client/consensus/aura/src/lib.rs +++ b/client/consensus/aura/src/lib.rs @@ -40,7 +40,8 @@ use codec::{Codec, Decode, Encode}; use sc_client_api::{backend::AuxStore, BlockOf, UsageProvider}; use sc_consensus::{BlockImport, BlockImportParams, ForkChoiceStrategy, StateAction}; use sc_consensus_slots::{ - BackoffAuthoringBlocksStrategy, InherentDataProviderExt, SlotInfo, StorageChanges, + BackoffAuthoringBlocksStrategy, InherentDataProviderExt, SimpleSlotWorkerToSlotWorker, + SlotInfo, StorageChanges, }; use sc_telemetry::TelemetryHandle; use sp_api::ProvideRuntimeApi; @@ -201,7 +202,7 @@ where Ok(sc_consensus_slots::start_slot_worker( slot_duration, select_chain, - worker, + SimpleSlotWorkerToSlotWorker(worker), sync_oracle, create_inherent_data_providers, can_author_with, @@ -256,7 +257,15 @@ pub fn build_aura_worker( telemetry, force_authoring, }: BuildAuraWorkerParams, -) -> impl sc_consensus_slots::SlotWorker>::Proof> +) -> impl sc_consensus_slots::SimpleSlotWorker< + B, + Proposer = PF::Proposer, + BlockImport = I, + SyncOracle = SO, + JustificationSyncLink = L, + Claim = P::Public, + EpochData = Vec>, +> where B: BlockT, C: ProvideRuntimeApi + BlockOf + AuxStore + HeaderBackend + Send + Sync, @@ -272,7 +281,7 @@ where L: sc_consensus::JustificationSyncLink, BS: BackoffAuthoringBlocksStrategy> + Send + Sync + 'static, { - sc_consensus_slots::SimpleSlotWorkerToSlotWorker(AuraWorker { + AuraWorker { client, block_import, env: proposer_factory, @@ -285,7 +294,7 @@ where block_proposal_slot_portion, max_block_proposal_slot_portion, _key_type: PhantomData::

, - }) + } } struct AuraWorker { @@ -448,11 +457,10 @@ where } fn proposer(&mut self, block: &B::Header) -> Self::CreateProposer { - Box::pin( - self.env - .init(block) - .map_err(|e| sp_consensus::Error::ClientImport(format!("{:?}", e)).into()), - ) + self.env + .init(block) + .map_err(|e| sp_consensus::Error::ClientImport(format!("{:?}", e)).into()) + .boxed() } fn telemetry(&self) -> Option { From 8e880c9728ce423581119b547fcacfe2753dfe71 Mon Sep 17 00:00:00 2001 From: Dmitry Kashitsyn Date: Thu, 21 Apr 2022 15:06:04 +0600 Subject: [PATCH 138/484] Split `SandboxInstance::get_global_val` implementation per backend (#11234) --- client/executor/common/src/sandbox.rs | 30 +++++-------------- .../common/src/sandbox/wasmer_backend.rs | 14 +++++++++ .../common/src/sandbox/wasmi_backend.rs | 5 ++++ 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/client/executor/common/src/sandbox.rs b/client/executor/common/src/sandbox.rs index a2c1f602b1c9a..98eafaf32b8f9 100644 --- a/client/executor/common/src/sandbox.rs +++ b/client/executor/common/src/sandbox.rs @@ -36,13 +36,14 @@ use std::{collections::HashMap, rc::Rc}; #[cfg(feature = "wasmer-sandbox")] use wasmer_backend::{ - instantiate as wasmer_instantiate, invoke as wasmer_invoke, new_memory as wasmer_new_memory, - Backend as WasmerBackend, MemoryWrapper as WasmerMemoryWrapper, + get_global as wasmer_get_global, instantiate as wasmer_instantiate, invoke as wasmer_invoke, + new_memory as wasmer_new_memory, Backend as WasmerBackend, + MemoryWrapper as WasmerMemoryWrapper, }; use wasmi_backend::{ - instantiate as wasmi_instantiate, invoke as wasmi_invoke, new_memory as wasmi_new_memory, - MemoryWrapper as WasmiMemoryWrapper, + get_global as wasmi_get_global, instantiate as wasmi_instantiate, invoke as wasmi_invoke, + new_memory as wasmi_new_memory, MemoryWrapper as WasmiMemoryWrapper, }; /// Index of a function inside the supervisor. @@ -213,27 +214,10 @@ impl SandboxInstance { /// Returns `Some(_)` if the global could be found. pub fn get_global_val(&self, name: &str) -> Option { match &self.backend_instance { - BackendInstance::Wasmi(wasmi_instance) => { - let wasmi_global = wasmi_instance.export_by_name(name)?.as_global()?.get(); - - Some(wasmi_global.into()) - }, + BackendInstance::Wasmi(wasmi_instance) => wasmi_get_global(wasmi_instance, name), #[cfg(feature = "wasmer-sandbox")] - BackendInstance::Wasmer(wasmer_instance) => { - use sp_wasm_interface::Value; - - let global = wasmer_instance.exports.get_global(name).ok()?; - let wasmtime_value = match global.get() { - wasmer::Val::I32(val) => Value::I32(val), - wasmer::Val::I64(val) => Value::I64(val), - wasmer::Val::F32(val) => Value::F32(f32::to_bits(val)), - wasmer::Val::F64(val) => Value::F64(f64::to_bits(val)), - _ => None?, - }; - - Some(wasmtime_value) - }, + BackendInstance::Wasmer(wasmer_instance) => wasmer_get_global(wasmer_instance, name), } } } diff --git a/client/executor/common/src/sandbox/wasmer_backend.rs b/client/executor/common/src/sandbox/wasmer_backend.rs index 44b43757148de..fd2f317457f88 100644 --- a/client/executor/common/src/sandbox/wasmer_backend.rs +++ b/client/executor/common/src/sandbox/wasmer_backend.rs @@ -432,3 +432,17 @@ impl MemoryTransfer for MemoryWrapper { } } } + +/// Get global value by name +pub fn get_global(instance: &wasmer::Instance, name: &str) -> Option { + let global = instance.exports.get_global(name).ok()?; + let wasmtime_value = match global.get() { + wasmer::Val::I32(val) => Value::I32(val), + wasmer::Val::I64(val) => Value::I64(val), + wasmer::Val::F32(val) => Value::F32(f32::to_bits(val)), + wasmer::Val::F64(val) => Value::F64(f64::to_bits(val)), + _ => None?, + }; + + Some(wasmtime_value) +} diff --git a/client/executor/common/src/sandbox/wasmi_backend.rs b/client/executor/common/src/sandbox/wasmi_backend.rs index 92bb0e1e398e0..0954287f52f6b 100644 --- a/client/executor/common/src/sandbox/wasmi_backend.rs +++ b/client/executor/common/src/sandbox/wasmi_backend.rs @@ -321,3 +321,8 @@ pub fn invoke( }) }) } + +/// Get global value by name +pub fn get_global(instance: &wasmi::ModuleRef, name: &str) -> Option { + Some(instance.export_by_name(name)?.as_global()?.get().into()) +} From d6ab699ce2363ad100bc95e026fc0a900f15e3a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Apr 2022 09:37:41 +0000 Subject: [PATCH 139/484] Bump proc-macro2 from 1.0.36 to 1.0.37 (#11191) Bumps [proc-macro2](https://github.com/dtolnay/proc-macro2) from 1.0.36 to 1.0.37. - [Release notes](https://github.com/dtolnay/proc-macro2/releases) - [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.36...1.0.37) --- updated-dependencies: - dependency-name: proc-macro2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- client/chain-spec/derive/Cargo.toml | 2 +- client/tracing/proc-macro/Cargo.toml | 2 +- frame/election-provider-support/solution-type/Cargo.toml | 2 +- frame/staking/reward-curve/Cargo.toml | 2 +- frame/support/procedural/Cargo.toml | 2 +- frame/support/procedural/tools/Cargo.toml | 2 +- frame/support/procedural/tools/derive/Cargo.toml | 2 +- primitives/api/proc-macro/Cargo.toml | 2 +- primitives/core/hashing/proc-macro/Cargo.toml | 2 +- primitives/runtime-interface/proc-macro/Cargo.toml | 2 +- primitives/version/proc-macro/Cargo.toml | 2 +- test-utils/derive/Cargo.toml | 2 +- 13 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3d46093c38c4a..a1a23117235e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7282,9 +7282,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro2" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1" dependencies = [ "unicode-xid", ] @@ -11691,9 +11691,9 @@ checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" [[package]] name = "unicode-xid" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" [[package]] name = "universal-hash" diff --git a/client/chain-spec/derive/Cargo.toml b/client/chain-spec/derive/Cargo.toml index 9aa1d6c09405c..8cb5db5e42d91 100644 --- a/client/chain-spec/derive/Cargo.toml +++ b/client/chain-spec/derive/Cargo.toml @@ -16,7 +16,7 @@ proc-macro = true [dependencies] proc-macro-crate = "1.1.3" -proc-macro2 = "1.0.36" +proc-macro2 = "1.0.37" quote = "1.0.10" syn = "1.0.82" diff --git a/client/tracing/proc-macro/Cargo.toml b/client/tracing/proc-macro/Cargo.toml index 645a6ed93a16f..b55c3482be3f4 100644 --- a/client/tracing/proc-macro/Cargo.toml +++ b/client/tracing/proc-macro/Cargo.toml @@ -16,6 +16,6 @@ proc-macro = true [dependencies] proc-macro-crate = "1.1.3" -proc-macro2 = "1.0.36" +proc-macro2 = "1.0.37" quote = { version = "1.0.10", features = ["proc-macro"] } syn = { version = "1.0.82", features = ["proc-macro", "full", "extra-traits", "parsing"] } diff --git a/frame/election-provider-support/solution-type/Cargo.toml b/frame/election-provider-support/solution-type/Cargo.toml index 5f4454eda48df..a4e7256127115 100644 --- a/frame/election-provider-support/solution-type/Cargo.toml +++ b/frame/election-provider-support/solution-type/Cargo.toml @@ -17,7 +17,7 @@ proc-macro = true [dependencies] syn = { version = "1.0.82", features = ["full", "visit"] } quote = "1.0" -proc-macro2 = "1.0.36" +proc-macro2 = "1.0.37" proc-macro-crate = "1.1.3" [dev-dependencies] diff --git a/frame/staking/reward-curve/Cargo.toml b/frame/staking/reward-curve/Cargo.toml index d53fb72b0e08a..dd8361ece6386 100644 --- a/frame/staking/reward-curve/Cargo.toml +++ b/frame/staking/reward-curve/Cargo.toml @@ -17,7 +17,7 @@ proc-macro = true [dependencies] syn = { version = "1.0.82", features = ["full", "visit"] } quote = "1.0.10" -proc-macro2 = "1.0.36" +proc-macro2 = "1.0.37" proc-macro-crate = "1.1.3" [dev-dependencies] diff --git a/frame/support/procedural/Cargo.toml b/frame/support/procedural/Cargo.toml index 4e9618b5bc167..8f7438710dfdb 100644 --- a/frame/support/procedural/Cargo.toml +++ b/frame/support/procedural/Cargo.toml @@ -16,7 +16,7 @@ proc-macro = true [dependencies] frame-support-procedural-tools = { version = "4.0.0-dev", path = "./tools" } -proc-macro2 = "1.0.36" +proc-macro2 = "1.0.37" quote = "1.0.10" Inflector = "0.11.4" syn = { version = "1.0.82", features = ["full"] } diff --git a/frame/support/procedural/tools/Cargo.toml b/frame/support/procedural/tools/Cargo.toml index b38071dd31585..00c453b1f1928 100644 --- a/frame/support/procedural/tools/Cargo.toml +++ b/frame/support/procedural/tools/Cargo.toml @@ -13,7 +13,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] frame-support-procedural-tools-derive = { version = "3.0.0", path = "./derive" } -proc-macro2 = "1.0.36" +proc-macro2 = "1.0.37" quote = "1.0.10" syn = { version = "1.0.82", features = ["full", "visit", "extra-traits"] } proc-macro-crate = "1.1.3" diff --git a/frame/support/procedural/tools/derive/Cargo.toml b/frame/support/procedural/tools/derive/Cargo.toml index 5ed1b506dfb97..8d536be1cb725 100644 --- a/frame/support/procedural/tools/derive/Cargo.toml +++ b/frame/support/procedural/tools/derive/Cargo.toml @@ -15,6 +15,6 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -proc-macro2 = "1.0.36" +proc-macro2 = "1.0.37" quote = { version = "1.0.10", features = ["proc-macro"] } syn = { version = "1.0.82", features = ["proc-macro" ,"full", "extra-traits", "parsing"] } diff --git a/primitives/api/proc-macro/Cargo.toml b/primitives/api/proc-macro/Cargo.toml index dc5deb2efa668..24ce1360c80f7 100644 --- a/primitives/api/proc-macro/Cargo.toml +++ b/primitives/api/proc-macro/Cargo.toml @@ -18,7 +18,7 @@ proc-macro = true [dependencies] quote = "1.0.10" syn = { version = "1.0.82", features = ["full", "fold", "extra-traits", "visit"] } -proc-macro2 = "1.0.36" +proc-macro2 = "1.0.37" blake2 = { version = "0.10.2", default-features = false } proc-macro-crate = "1.1.3" diff --git a/primitives/core/hashing/proc-macro/Cargo.toml b/primitives/core/hashing/proc-macro/Cargo.toml index b3dc155cd8bf3..9a5f991d04362 100644 --- a/primitives/core/hashing/proc-macro/Cargo.toml +++ b/primitives/core/hashing/proc-macro/Cargo.toml @@ -18,5 +18,5 @@ proc-macro = true [dependencies] syn = { version = "1.0.82", features = ["full", "parsing"] } quote = "1.0.6" -proc-macro2 = "1.0.36" +proc-macro2 = "1.0.37" sp-core-hashing = { version = "4.0.0", path = "../", default-features = false } diff --git a/primitives/runtime-interface/proc-macro/Cargo.toml b/primitives/runtime-interface/proc-macro/Cargo.toml index ef59e0119f9fe..3c66f371b7a0c 100644 --- a/primitives/runtime-interface/proc-macro/Cargo.toml +++ b/primitives/runtime-interface/proc-macro/Cargo.toml @@ -18,6 +18,6 @@ proc-macro = true [dependencies] syn = { version = "1.0.82", features = ["full", "visit", "fold", "extra-traits"] } quote = "1.0.10" -proc-macro2 = "1.0.36" +proc-macro2 = "1.0.37" Inflector = "0.11.4" proc-macro-crate = "1.1.3" diff --git a/primitives/version/proc-macro/Cargo.toml b/primitives/version/proc-macro/Cargo.toml index e54012a516f2a..b9ab7cee0ad2c 100644 --- a/primitives/version/proc-macro/Cargo.toml +++ b/primitives/version/proc-macro/Cargo.toml @@ -18,7 +18,7 @@ proc-macro = true [dependencies] quote = "1.0.10" syn = { version = "1.0.82", features = ["full", "fold", "extra-traits", "visit"] } -proc-macro2 = "1.0.36" +proc-macro2 = "1.0.37" codec = { package = "parity-scale-codec", version = "3.0.0", features = [ "derive" ] } [dev-dependencies] diff --git a/test-utils/derive/Cargo.toml b/test-utils/derive/Cargo.toml index 16e0b9822f7bb..afa729c5163e9 100644 --- a/test-utils/derive/Cargo.toml +++ b/test-utils/derive/Cargo.toml @@ -12,7 +12,7 @@ description = "Substrate test utilities macros" quote = "1.0.10" syn = { version = "1.0.82", features = ["full"] } proc-macro-crate = "1.1.3" -proc-macro2 = "1.0.36" +proc-macro2 = "1.0.37" [lib] proc-macro = true From 77d36dd3b5909783bf79a75923381e0b33b642b5 Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Thu, 21 Apr 2022 06:50:17 -0700 Subject: [PATCH 140/484] staking: Fix `Reward` usage (#10887) * staking: Fix `Reward` usage * Some small fixes * Check on_unbalanced was called * Improve tests * Add not for Reward; FMT * :facepalm: Co-authored-by: parity-processbot <> --- frame/staking/src/mock.rs | 10 +++++++- frame/staking/src/pallet/impls.rs | 4 ++++ frame/staking/src/pallet/mod.rs | 2 ++ frame/staking/src/tests.rs | 40 +++++++++++++++++++++++++++---- 4 files changed, 50 insertions(+), 6 deletions(-) diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index 09809483ec155..324a367f48e00 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -237,6 +237,7 @@ const THRESHOLDS: [sp_npos_elections::VoteWeight; 9] = parameter_types! { pub static BagThresholds: &'static [sp_npos_elections::VoteWeight] = &THRESHOLDS; pub static MaxNominations: u32 = 16; + pub static RewardOnUnbalanceWasCalled: bool = false; } impl pallet_bags_list::Config for Test { @@ -255,6 +256,13 @@ impl onchain::Config for OnChainSeqPhragmen { type WeightInfo = (); } +pub struct MockReward {} +impl OnUnbalanced> for MockReward { + fn on_unbalanced(_: PositiveImbalanceOf) { + RewardOnUnbalanceWasCalled::set(true); + } +} + impl crate::pallet::pallet::Config for Test { type MaxNominations = MaxNominations; type Currency = Balances; @@ -263,7 +271,7 @@ impl crate::pallet::pallet::Config for Test { type RewardRemainder = RewardRemainderMock; type Event = Event; type Slash = (); - type Reward = (); + type Reward = MockReward; type SessionsPerEra = SessionsPerEra; type SlashDeferDuration = SlashDeferDuration; type SlashCancelOrigin = frame_system::EnsureRoot; diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 90f19c6badd8f..96af7009bed0c 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -174,11 +174,13 @@ impl Pallet { Self::deposit_event(Event::::PayoutStarted(era, ledger.stash.clone())); + let mut total_imbalance = PositiveImbalanceOf::::zero(); // We can now make total validator payout: if let Some(imbalance) = Self::make_payout(&ledger.stash, validator_staking_payout + validator_commission_payout) { Self::deposit_event(Event::::Rewarded(ledger.stash, imbalance.peek())); + total_imbalance.subsume(imbalance); } // Track the number of payout ops to nominators. Note: @@ -199,9 +201,11 @@ impl Pallet { nominator_payout_count += 1; let e = Event::::Rewarded(nominator.who.clone(), imbalance.peek()); Self::deposit_event(e); + total_imbalance.subsume(imbalance); } } + T::Reward::on_unbalanced(total_imbalance); debug_assert!(nominator_payout_count <= T::MaxNominatorRewardedPerValidator::get()); Ok(Some(T::WeightInfo::payout_stakers_alive_staked(nominator_payout_count)).into()) } diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 1a8fd59d23987..d24509033e024 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -121,6 +121,8 @@ pub mod pallet { type Slash: OnUnbalanced>; /// Handler for the unbalanced increment when rewarding a staker. + /// NOTE: in most cases, the implementation of `OnUnbalanced` should modify the total + /// issuance. type Reward: OnUnbalanced>; /// Number of sessions per era. diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 7161418afc76e..f0de26b588577 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -3370,26 +3370,47 @@ fn set_history_depth_works() { #[test] fn test_payout_stakers() { - // Here we will test validator can set `max_nominators_payout` and it works. - // We also test that `payout_extra_nominators` works. + // Test that payout_stakers work in general, including that only the top + // `T::MaxNominatorRewardedPerValidator` nominators are rewarded. ExtBuilder::default().has_stakers(false).build_and_execute(|| { let balance = 1000; + // Track the exposure of the validator and all nominators. + let mut total_exposure = balance; + // Track the exposure of the validator and the nominators that will get paid out. + let mut payout_exposure = balance; // Create a validator: bond_validator(11, 10, balance); // Default(64) + assert_eq!(Validators::::count(), 1); // Create nominators, targeting stash of validators for i in 0..100 { - bond_nominator(1000 + i, 100 + i, balance + i as Balance, vec![11]); + let bond_amount = balance + i as Balance; + bond_nominator(1000 + i, 100 + i, bond_amount, vec![11]); + total_exposure += bond_amount; + if i >= 36 { + payout_exposure += bond_amount; + }; } + let payout_exposure_part = Perbill::from_rational(payout_exposure, total_exposure); mock::start_active_era(1); Staking::reward_by_ids(vec![(11, 1)]); // compute and ensure the reward amount is greater than zero. - let _ = current_total_payout_for_duration(reward_time_per_era()); + let payout = current_total_payout_for_duration(reward_time_per_era()); + let actual_paid_out = payout_exposure_part * payout; mock::start_active_era(2); + + let pre_payout_total_issuance = Balances::total_issuance(); + RewardOnUnbalanceWasCalled::set(false); assert_ok!(Staking::payout_stakers(Origin::signed(1337), 11, 1)); + assert_eq_error_rate!( + Balances::total_issuance(), + pre_payout_total_issuance + actual_paid_out, + 1 + ); + assert!(RewardOnUnbalanceWasCalled::get()); // Top 64 nominators of validator 11 automatically paid out, including the validator // Validator payout goes to controller. @@ -3418,10 +3439,19 @@ fn test_payout_stakers() { Staking::reward_by_ids(vec![(11, 1)]); // compute and ensure the reward amount is greater than zero. - let _ = current_total_payout_for_duration(reward_time_per_era()); + let payout = current_total_payout_for_duration(reward_time_per_era()); + let actual_paid_out = payout_exposure_part * payout; + let pre_payout_total_issuance = Balances::total_issuance(); mock::start_active_era(i); + RewardOnUnbalanceWasCalled::set(false); assert_ok!(Staking::payout_stakers(Origin::signed(1337), 11, i - 1)); + assert_eq_error_rate!( + Balances::total_issuance(), + pre_payout_total_issuance + actual_paid_out, + 1 + ); + assert!(RewardOnUnbalanceWasCalled::get()); } // We track rewards in `claimed_rewards` vec From 8766706ac2dc6e2cccc5368dbfa968b58be80a5c Mon Sep 17 00:00:00 2001 From: Adrian Catangiu Date: Thu, 21 Apr 2022 20:58:03 +0300 Subject: [PATCH 141/484] adjust BEEFY client logging (#11261) Move gossip filter logging to level=trace because it's very spammy. Move some debug-relevant logs from trace to debug. Signed-off-by: acatangiu --- client/beefy/src/gossip.rs | 2 +- client/beefy/src/round.rs | 2 +- client/beefy/src/worker.rs | 16 ++++++++-------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/client/beefy/src/gossip.rs b/client/beefy/src/gossip.rs index 54d283fede32e..02d5efe9e0e58 100644 --- a/client/beefy/src/gossip.rs +++ b/client/beefy/src/gossip.rs @@ -220,7 +220,7 @@ where let round = msg.commitment.block_number; let allowed = known_votes.is_live(&round); - debug!(target: "beefy", "🥩 Message for round #{} allowed: {}", round, allowed); + trace!(target: "beefy", "🥩 Message for round #{} allowed: {}", round, allowed); allowed }) diff --git a/client/beefy/src/round.rs b/client/beefy/src/round.rs index eba769b2356f0..a5a15bac5f8f9 100644 --- a/client/beefy/src/round.rs +++ b/client/beefy/src/round.rs @@ -171,7 +171,7 @@ where let signatures = self.rounds.remove(round)?.votes; self.rounds.retain(|&(_, number), _| number > round.1); self.best_done = self.best_done.clone().max(Some(round.1.clone())); - trace!(target: "beefy", "🥩 Concluded round #{}", round.1); + debug!(target: "beefy", "🥩 Concluded round #{}", round.1); Some( self.validator_set diff --git a/client/beefy/src/worker.rs b/client/beefy/src/worker.rs index 128850a5f172f..696bee9d7ee76 100644 --- a/client/beefy/src/worker.rs +++ b/client/beefy/src/worker.rs @@ -263,7 +263,7 @@ where } fn handle_finality_notification(&mut self, notification: &FinalityNotification) { - trace!(target: "beefy", "🥩 Finality notification: {:?}", notification); + debug!(target: "beefy", "🥩 Finality notification: {:?}", notification); let number = *notification.header.number(); // On start-up ignore old finality notifications that we're not interested in. @@ -332,7 +332,7 @@ where VersionedFinalityProof::V1(signed_commitment.clone()).encode(), ), ) { - trace!(target: "beefy", "🥩 Error {:?} on appending justification: {:?}", e, signed_commitment); + debug!(target: "beefy", "🥩 Error {:?} on appending justification: {:?}", e, signed_commitment); } self.signed_commitment_sender .notify(|| Ok::<_, ()>(signed_commitment)) @@ -352,7 +352,7 @@ where /// /// Also handle this self vote by calling `self.handle_vote()` for it. fn do_vote(&mut self, target_number: NumberFor) { - trace!(target: "beefy", "🥩 Try voting on {}", target_number); + debug!(target: "beefy", "🥩 Try voting on {}", target_number); // Most of the time we get here, `target` is actually `best_grandpa`, // avoid asking `client` for header in that case. @@ -455,7 +455,7 @@ where future::ready(false) } else { trace!(target: "beefy", "🥩 Finality notification: {:?}", notif); - trace!(target: "beefy", "🥩 Waiting for BEEFY pallet to become available..."); + debug!(target: "beefy", "🥩 Waiting for BEEFY pallet to become available..."); future::ready(true) } }) @@ -475,7 +475,7 @@ where let mut votes = Box::pin(self.gossip_engine.lock().messages_for(topic::()).filter_map( |notification| async move { - debug!(target: "beefy", "🥩 Got vote message: {:?}", notification); + trace!(target: "beefy", "🥩 Got vote message: {:?}", notification); VoteMessage::, AuthorityId, Signature>::decode( &mut ¬ification.message[..], @@ -577,7 +577,7 @@ where // we vote on it let target = match best_beefy { None => { - trace!( + debug!( target: "beefy", "🥩 vote target - mandatory block: #{:?}", session_start, @@ -585,7 +585,7 @@ where session_start }, Some(bbb) if bbb < session_start => { - trace!( + debug!( target: "beefy", "🥩 vote target - mandatory block: #{:?}", session_start, @@ -597,7 +597,7 @@ where let diff = diff.saturated_into::() / 2; let target = bbb + min_delta.max(diff.next_power_of_two()).into(); - trace!( + debug!( target: "beefy", "🥩 vote target - diff: {:?}, next_power_of_two: {:?}, target block: #{:?}", diff, From 6487d8aca2444377d17c633c6670b6edb42998c6 Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Thu, 21 Apr 2022 15:53:54 -0700 Subject: [PATCH 142/484] staking: Proportional ledger slashing (#10982) * staking: Proportional ledger slashing * Some comment cleanup * Update frame/staking/src/pallet/mod.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Fix benchmarks * FMT * Try fill in all staking configs * round of feedback and imp from kian * demonstrate per_thing usage * Update some tests * FMT * Test that era offset works correctly * Update mocks * Remove unnescary docs * Remove unlock_era * Update frame/staking/src/lib.rs * Adjust tests to account for only remove when < ED * Remove stale TODOs * Remove dupe test Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Co-authored-by: kianenigma --- bin/node/runtime/src/lib.rs | 2 + frame/babe/src/mock.rs | 2 + frame/grandpa/src/mock.rs | 2 + frame/offences/benchmarking/src/mock.rs | 2 + frame/session/benchmarking/src/mock.rs | 2 + frame/staking/src/benchmarking.rs | 3 +- frame/staking/src/lib.rs | 161 +++++++++++------- frame/staking/src/mock.rs | 14 ++ frame/staking/src/pallet/impls.rs | 9 +- frame/staking/src/pallet/mod.rs | 25 ++- frame/staking/src/slashing.rs | 17 +- frame/staking/src/tests.rs | 215 +++++++++++++++++++++++- primitives/staking/src/lib.rs | 25 +++ 13 files changed, 402 insertions(+), 77 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index c523ccc86fc7a..c88dc8d731d03 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -537,6 +537,7 @@ impl pallet_staking::BenchmarkingConfig for StakingBenchmarkingConfig { impl pallet_staking::Config for Runtime { type MaxNominations = MaxNominations; type Currency = Balances; + type CurrencyBalance = Balance; type UnixTime = Timestamp; type CurrencyToVote = U128CurrencyToVote; type RewardRemainder = Treasury; @@ -560,6 +561,7 @@ impl pallet_staking::Config for Runtime { type GenesisElectionProvider = onchain::UnboundedExecution; type VoterList = BagsList; type MaxUnlockingChunks = ConstU32<32>; + type OnStakerSlash = (); type WeightInfo = pallet_staking::weights::SubstrateWeight; type BenchmarkingConfig = StakingBenchmarkingConfig; } diff --git a/frame/babe/src/mock.rs b/frame/babe/src/mock.rs index 15f53e7da0823..5677eb7e28e49 100644 --- a/frame/babe/src/mock.rs +++ b/frame/babe/src/mock.rs @@ -186,6 +186,7 @@ impl pallet_staking::Config for Test { type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; type Event = Event; type Currency = Balances; + type CurrencyBalance = ::Balance; type Slash = (); type Reward = (); type SessionsPerEra = SessionsPerEra; @@ -202,6 +203,7 @@ impl pallet_staking::Config for Test { type GenesisElectionProvider = Self::ElectionProvider; type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; + type OnStakerSlash = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); } diff --git a/frame/grandpa/src/mock.rs b/frame/grandpa/src/mock.rs index 67d5a3d7fd373..5700fbe3aa36e 100644 --- a/frame/grandpa/src/mock.rs +++ b/frame/grandpa/src/mock.rs @@ -194,6 +194,7 @@ impl pallet_staking::Config for Test { type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; type Event = Event; type Currency = Balances; + type CurrencyBalance = ::Balance; type Slash = (); type Reward = (); type SessionsPerEra = SessionsPerEra; @@ -210,6 +211,7 @@ impl pallet_staking::Config for Test { type GenesisElectionProvider = Self::ElectionProvider; type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; + type OnStakerSlash = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); } diff --git a/frame/offences/benchmarking/src/mock.rs b/frame/offences/benchmarking/src/mock.rs index 74cc29586920d..fba8c567059c2 100644 --- a/frame/offences/benchmarking/src/mock.rs +++ b/frame/offences/benchmarking/src/mock.rs @@ -161,6 +161,7 @@ impl onchain::Config for OnChainSeqPhragmen { impl pallet_staking::Config for Test { type MaxNominations = ConstU32<16>; type Currency = Balances; + type CurrencyBalance = ::Balance; type UnixTime = pallet_timestamp::Pallet; type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; type RewardRemainder = (); @@ -180,6 +181,7 @@ impl pallet_staking::Config for Test { type GenesisElectionProvider = Self::ElectionProvider; type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; + type OnStakerSlash = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); } diff --git a/frame/session/benchmarking/src/mock.rs b/frame/session/benchmarking/src/mock.rs index 5acc484f9ba62..6835736aa8e00 100644 --- a/frame/session/benchmarking/src/mock.rs +++ b/frame/session/benchmarking/src/mock.rs @@ -167,6 +167,7 @@ impl onchain::Config for OnChainSeqPhragmen { impl pallet_staking::Config for Test { type MaxNominations = ConstU32<16>; type Currency = Balances; + type CurrencyBalance = ::Balance; type UnixTime = pallet_timestamp::Pallet; type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; type RewardRemainder = (); @@ -186,6 +187,7 @@ impl pallet_staking::Config for Test { type GenesisElectionProvider = Self::ElectionProvider; type MaxUnlockingChunks = ConstU32<32>; type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; + type OnStakerSlash = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); } diff --git a/frame/staking/src/benchmarking.rs b/frame/staking/src/benchmarking.rs index 983f1bd54deb7..6c1589417f15a 100644 --- a/frame/staking/src/benchmarking.rs +++ b/frame/staking/src/benchmarking.rs @@ -802,7 +802,8 @@ benchmarks! { &stash, slash_amount, &mut BalanceOf::::zero(), - &mut NegativeImbalanceOf::::zero() + &mut NegativeImbalanceOf::::zero(), + EraIndex::zero() ); } verify { let balance_after = T::Currency::free_balance(&stash); diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 872ed2e1af404..8003b9814c8e6 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -302,7 +302,7 @@ mod pallet; use codec::{Decode, Encode, HasCompact}; use frame_support::{ parameter_types, - traits::{Currency, Get}, + traits::{Currency, Defensive, Get}, weights::Weight, BoundedVec, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, }; @@ -310,7 +310,7 @@ use scale_info::TypeInfo; use sp_runtime::{ curve::PiecewiseLinear, traits::{AtLeast32BitUnsigned, Convert, Saturating, Zero}, - Perbill, RuntimeDebug, + Perbill, Perquintill, RuntimeDebug, }; use sp_staking::{ offence::{Offence, OffenceError, ReportOffence}, @@ -338,8 +338,7 @@ macro_rules! log { pub type RewardPoint = u32; /// The balance type of this pallet. -pub type BalanceOf = - <::Currency as Currency<::AccountId>>::Balance; +pub type BalanceOf = ::CurrencyBalance; type PositiveImbalanceOf = <::Currency as Currency< ::AccountId, @@ -440,31 +439,30 @@ pub struct UnlockChunk { /// The ledger of a (bonded) stash. #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct StakingLedger { +#[scale_info(skip_type_params(T))] +pub struct StakingLedger { /// The stash account whose balance is actually locked and at stake. - pub stash: AccountId, + pub stash: T::AccountId, /// The total amount of the stash's balance that we are currently accounting for. /// It's just `active` plus all the `unlocking` balances. #[codec(compact)] - pub total: Balance, + pub total: BalanceOf, /// The total amount of the stash's balance that will be at stake in any forthcoming /// rounds. #[codec(compact)] - pub active: Balance, + pub active: BalanceOf, /// Any balance that is becoming free, which may eventually be transferred out of the stash /// (assuming it doesn't get slashed first). It is assumed that this will be treated as a first /// in, first out queue where the new (higher value) eras get pushed on the back. - pub unlocking: BoundedVec, MaxUnlockingChunks>, + pub unlocking: BoundedVec>, MaxUnlockingChunks>, /// List of eras for which the stakers behind a validator have claimed rewards. Only updated /// for validators. pub claimed_rewards: Vec, } -impl - StakingLedger -{ +impl StakingLedger { /// Initializes the default object using the given `validator`. - pub fn default_from(stash: AccountId) -> Self { + pub fn default_from(stash: T::AccountId) -> Self { Self { stash, total: Zero::zero(), @@ -507,8 +505,8 @@ impl (Self, Balance) { - let mut unlocking_balance: Balance = Zero::zero(); + fn rebond(mut self, value: BalanceOf) -> (Self, BalanceOf) { + let mut unlocking_balance = BalanceOf::::zero(); while let Some(last) = self.unlocking.last_mut() { if unlocking_balance + last.value <= value { @@ -530,57 +528,96 @@ impl StakingLedger -where - Balance: AtLeast32BitUnsigned + Saturating + Copy, -{ - /// Slash the validator for a given amount of balance. This can grow the value - /// of the slash in the case that the validator has less than `minimum_balance` - /// active funds. Returns the amount of funds actually slashed. + /// Slash the staker for a given amount of balance. This can grow the value of the slash in the + /// case that either the active bonded or some unlocking chunks become dust after slashing. + /// Returns the amount of funds actually slashed. /// - /// Slashes from `active` funds first, and then `unlocking`, starting with the - /// chunks that are closest to unlocking. - fn slash(&mut self, mut value: Balance, minimum_balance: Balance) -> Balance { - let pre_total = self.total; - let total = &mut self.total; - let active = &mut self.active; - - let slash_out_of = - |total_remaining: &mut Balance, target: &mut Balance, value: &mut Balance| { - let mut slash_from_target = (*value).min(*target); - - if !slash_from_target.is_zero() { - *target -= slash_from_target; - - // Don't leave a dust balance in the staking system. - if *target <= minimum_balance { - slash_from_target += *target; - *value += sp_std::mem::replace(target, Zero::zero()); - } - - *total_remaining = total_remaining.saturating_sub(slash_from_target); - *value -= slash_from_target; - } - }; - - slash_out_of(total, active, &mut value); - - let i = self - .unlocking - .iter_mut() - .map(|chunk| { - slash_out_of(total, &mut chunk.value, &mut value); - chunk.value - }) - .take_while(|value| value.is_zero()) // Take all fully-consumed chunks out. - .count(); + /// # Note + /// + /// This calls `Config::OnStakerSlash::on_slash` with information as to how the slash + /// was applied. + fn slash( + &mut self, + slash_amount: BalanceOf, + minimum_balance: BalanceOf, + slash_era: EraIndex, + ) -> BalanceOf { + use sp_staking::OnStakerSlash as _; + + if slash_amount.is_zero() { + return Zero::zero() + } - // Kill all drained chunks. - let _ = self.unlocking.drain(..i); + let mut remaining_slash = slash_amount; + let pre_slash_total = self.total; + + let era_after_slash = slash_era + 1; + let chunk_unlock_era_after_slash = era_after_slash + T::BondingDuration::get(); + + // Calculate the total balance of active funds and unlocking funds in the affected range. + let (affected_balance, slash_chunks_priority): (_, Box>) = { + if let Some(start_index) = + self.unlocking.iter().position(|c| c.era >= chunk_unlock_era_after_slash) + { + // The indices of the first chunk after the slash up through the most recent chunk. + // (The most recent chunk is at greatest from this era) + let affected_indices = start_index..self.unlocking.len(); + let unbonding_affected_balance = + affected_indices.clone().fold(BalanceOf::::zero(), |sum, i| { + if let Some(chunk) = self.unlocking.get_mut(i).defensive() { + sum.saturating_add(chunk.value) + } else { + sum + } + }); + ( + self.active.saturating_add(unbonding_affected_balance), + Box::new(affected_indices.chain((0..start_index).rev())), + ) + } else { + (self.active, Box::new((0..self.unlocking.len()).rev())) + } + }; + + // Helper to update `target` and the ledgers total after accounting for slashing `target`. + let ratio = Perquintill::from_rational(slash_amount, affected_balance); + let mut slash_out_of = |target: &mut BalanceOf, slash_remaining: &mut BalanceOf| { + let mut slash_from_target = + if slash_amount < affected_balance { ratio * (*target) } else { *slash_remaining } + .min(*target); + + // slash out from *target exactly `slash_from_target`. + *target = *target - slash_from_target; + if *target < minimum_balance { + // Slash the rest of the target if its dust + slash_from_target = + sp_std::mem::replace(target, Zero::zero()).saturating_add(slash_from_target) + } - pre_total.saturating_sub(*total) + self.total = self.total.saturating_sub(slash_from_target); + *slash_remaining = slash_remaining.saturating_sub(slash_from_target); + }; + + // If this is *not* a proportional slash, the active will always wiped to 0. + slash_out_of(&mut self.active, &mut remaining_slash); + + let mut slashed_unlocking = BTreeMap::<_, _>::new(); + for i in slash_chunks_priority { + if let Some(chunk) = self.unlocking.get_mut(i).defensive() { + slash_out_of(&mut chunk.value, &mut remaining_slash); + // write the new slashed value of this chunk to the map. + slashed_unlocking.insert(chunk.era, chunk.value); + if remaining_slash.is_zero() { + break + } + } else { + break + } + } + self.unlocking.retain(|c| !c.value.is_zero()); + T::OnStakerSlash::on_slash(&self.stash, self.active, &slashed_unlocking); + pre_slash_total.saturating_sub(self.total) } } diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index 324a367f48e00..24572855264c4 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -238,6 +238,7 @@ parameter_types! { pub static BagThresholds: &'static [sp_npos_elections::VoteWeight] = &THRESHOLDS; pub static MaxNominations: u32 = 16; pub static RewardOnUnbalanceWasCalled: bool = false; + pub static LedgerSlashPerEra: (BalanceOf, BTreeMap>) = (Zero::zero(), BTreeMap::new()); } impl pallet_bags_list::Config for Test { @@ -263,9 +264,21 @@ impl OnUnbalanced> for MockReward { } } +pub struct OnStakerSlashMock(core::marker::PhantomData); +impl sp_staking::OnStakerSlash for OnStakerSlashMock { + fn on_slash( + _pool_account: &AccountId, + slashed_bonded: Balance, + slashed_chunks: &BTreeMap, + ) { + LedgerSlashPerEra::set((slashed_bonded, slashed_chunks.clone())); + } +} + impl crate::pallet::pallet::Config for Test { type MaxNominations = MaxNominations; type Currency = Balances; + type CurrencyBalance = ::Balance; type UnixTime = Timestamp; type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; type RewardRemainder = RewardRemainderMock; @@ -286,6 +299,7 @@ impl crate::pallet::pallet::Config for Test { // NOTE: consider a macro and use `UseNominatorsAndValidatorsMap` as well. type VoterList = BagsList; type MaxUnlockingChunks = ConstU32<32>; + type OnStakerSlash = OnStakerSlashMock; type BenchmarkingConfig = TestBenchmarkingConfig; type WeightInfo = (); } diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 96af7009bed0c..ea4235f274da3 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -213,10 +213,7 @@ impl Pallet { /// Update the ledger for a controller. /// /// This will also update the stash lock. - pub(crate) fn update_ledger( - controller: &T::AccountId, - ledger: &StakingLedger>, - ) { + pub(crate) fn update_ledger(controller: &T::AccountId, ledger: &StakingLedger) { T::Currency::set_lock(STAKING_ID, &ledger.stash, ledger.total, WithdrawReasons::all()); >::insert(controller, ledger); } @@ -606,7 +603,7 @@ impl Pallet { for era in (*earliest)..keep_from { let era_slashes = ::UnappliedSlashes::take(&era); for slash in era_slashes { - slashing::apply_slash::(slash); + slashing::apply_slash::(slash, era); } } @@ -1248,7 +1245,7 @@ where unapplied.reporters = details.reporters.clone(); if slash_defer_duration == 0 { // Apply right away. - slashing::apply_slash::(unapplied); + slashing::apply_slash::(unapplied, slash_era); { let slash_cost = (6, 5); let reward_cost = (2, 2); diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index d24509033e024..f57d5bd7c7a0a 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -75,8 +75,22 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config + SendTransactionTypes> { /// The staking balance. - type Currency: LockableCurrency; - + type Currency: LockableCurrency< + Self::AccountId, + Moment = Self::BlockNumber, + Balance = Self::CurrencyBalance, + >; + /// Just the `Currency::Balance` type; we have this item to allow us to constrain it to + /// `From`. + type CurrencyBalance: sp_runtime::traits::AtLeast32BitUnsigned + + codec::FullCodec + + Copy + + MaybeSerializeDeserialize + + sp_std::fmt::Debug + + Default + + From + + TypeInfo + + MaxEncodedLen; /// Time used for computing era duration. /// /// It is guaranteed to start being called from the first `on_finalize`. Thus value at @@ -177,6 +191,10 @@ pub mod pallet { #[pallet::constant] type MaxUnlockingChunks: Get; + /// A hook called when any staker is slashed. Mostly likely this can be a no-op unless + /// other pallets exist that are affected by slashing per-staker. + type OnStakerSlash: sp_staking::OnStakerSlash>; + /// Some parameters of the benchmarking. type BenchmarkingConfig: BenchmarkingConfig; @@ -239,8 +257,7 @@ pub mod pallet { /// Map from all (unlocked) "controller" accounts to the info regarding the staking. #[pallet::storage] #[pallet::getter(fn ledger)] - pub type Ledger = - StorageMap<_, Blake2_128Concat, T::AccountId, StakingLedger>>; + pub type Ledger = StorageMap<_, Blake2_128Concat, T::AccountId, StakingLedger>; /// Where the reward payment should be made. Keyed by stash. #[pallet::storage] diff --git a/frame/staking/src/slashing.rs b/frame/staking/src/slashing.rs index 2f381ad631fe5..9fc50eaf538f6 100644 --- a/frame/staking/src/slashing.rs +++ b/frame/staking/src/slashing.rs @@ -598,6 +598,7 @@ pub fn do_slash( value: BalanceOf, reward_payout: &mut BalanceOf, slashed_imbalance: &mut NegativeImbalanceOf, + slash_era: EraIndex, ) { let controller = match >::bonded(stash) { None => return, // defensive: should always exist. @@ -609,7 +610,7 @@ pub fn do_slash( None => return, // nothing to do. }; - let value = ledger.slash(value, T::Currency::minimum_balance()); + let value = ledger.slash(value, T::Currency::minimum_balance(), slash_era); if !value.is_zero() { let (imbalance, missing) = T::Currency::slash(stash, value); @@ -628,7 +629,10 @@ pub fn do_slash( } /// Apply a previously-unapplied slash. -pub(crate) fn apply_slash(unapplied_slash: UnappliedSlash>) { +pub(crate) fn apply_slash( + unapplied_slash: UnappliedSlash>, + slash_era: EraIndex, +) { let mut slashed_imbalance = NegativeImbalanceOf::::zero(); let mut reward_payout = unapplied_slash.payout; @@ -637,10 +641,17 @@ pub(crate) fn apply_slash(unapplied_slash: UnappliedSlash(&nominator, nominator_slash, &mut reward_payout, &mut slashed_imbalance); + do_slash::( + &nominator, + nominator_slash, + &mut reward_payout, + &mut slashed_imbalance, + slash_era, + ); } pay_reporters::(reward_payout, slashed_imbalance, &unapplied_slash.reporters); diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index f0de26b588577..21d4714985c6b 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -2104,7 +2104,7 @@ fn reward_validator_slashing_validator_does_not_overflow() { &[Perbill::from_percent(100)], ); - assert_eq!(Balances::total_balance(&11), stake - 1); + assert_eq!(Balances::total_balance(&11), stake); assert_eq!(Balances::total_balance(&2), 1); }) } @@ -4854,3 +4854,216 @@ fn force_apply_min_commission_works() { ); }); } + +#[test] +fn ledger_slash_works() { + let c = |era, value| UnlockChunk:: { era, value }; + // Given + let mut ledger = StakingLedger:: { + stash: 123, + total: 10, + active: 10, + unlocking: bounded_vec![], + claimed_rewards: vec![], + }; + + assert_eq!(BondingDuration::get(), 3); + + // When we slash a ledger with no unlocking chunks + assert_eq!(ledger.slash(5, 1, 0), 5); + // Then + assert_eq!(ledger.total, 5); + assert_eq!(ledger.active, 5); + assert_eq!(LedgerSlashPerEra::get().0, 5); + assert_eq!(LedgerSlashPerEra::get().1, Default::default()); + + // When we slash a ledger with no unlocking chunks and the slash amount is greater then the + // total + assert_eq!(ledger.slash(11, 1, 0), 5); + // Then + assert_eq!(ledger.total, 0); + assert_eq!(ledger.active, 0); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!(LedgerSlashPerEra::get().1, Default::default()); + + // Given + ledger.unlocking = bounded_vec![c(4, 10), c(5, 10)]; + ledger.total = 2 * 10; + ledger.active = 0; + // When all the chunks overlap with the slash eras + assert_eq!(ledger.slash(20, 0, 0), 20); + // Then + assert_eq!(ledger.unlocking, vec![]); + assert_eq!(ledger.total, 0); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(4, 0), (5, 0)])); + + // Given + ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)]; + ledger.total = 4 * 100; + ledger.active = 0; + // When the first 2 chunks don't overlap with the affected range of unlock eras. + assert_eq!(ledger.slash(140, 0, 2), 140); + // Then + assert_eq!(ledger.unlocking, vec![c(4, 100), c(5, 100), c(6, 30), c(7, 30)]); + assert_eq!(ledger.total, 4 * 100 - 140); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(6, 30), (7, 30)])); + + // Given + ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; + ledger.active = 500; + // 900 + ledger.total = 40 + 10 + 100 + 250 + 500; + // When we have a partial slash that touches all chunks + assert_eq!(ledger.slash(900 / 2, 0, 0), 450); + // Then + assert_eq!(ledger.active, 500 / 2); + assert_eq!(ledger.unlocking, vec![c(4, 40 / 2), c(5, 100 / 2), c(6, 10 / 2), c(7, 250 / 2)]); + assert_eq!(ledger.total, 900 / 2); + assert_eq!(LedgerSlashPerEra::get().0, 500 / 2); + assert_eq!( + LedgerSlashPerEra::get().1, + BTreeMap::from([(4, 40 / 2), (5, 100 / 2), (6, 10 / 2), (7, 250 / 2)]) + ); + + // slash 1/4th with not chunk. + ledger.unlocking = bounded_vec![]; + ledger.active = 500; + ledger.total = 500; + // When we have a partial slash that touches all chunks + assert_eq!(ledger.slash(500 / 4, 0, 0), 500 / 4); + // Then + assert_eq!(ledger.active, 3 * 500 / 4); + assert_eq!(ledger.unlocking, vec![]); + assert_eq!(ledger.total, ledger.active); + assert_eq!(LedgerSlashPerEra::get().0, 3 * 500 / 4); + assert_eq!(LedgerSlashPerEra::get().1, Default::default()); + + // Given we have the same as above, + ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; + ledger.active = 500; + ledger.total = 40 + 10 + 100 + 250 + 500; // 900 + assert_eq!(ledger.total, 900); + // When we have a higher min balance + assert_eq!( + ledger.slash( + 900 / 2, + 25, /* min balance - chunks with era 0 & 2 will be slashed to <=25, causing it to + * get swept */ + 0 + ), + 475 + ); + let dust = (10 / 2) + (40 / 2); + assert_eq!(ledger.active, 500 / 2); + assert_eq!(ledger.unlocking, vec![c(5, 100 / 2), c(7, 250 / 2)]); + assert_eq!(ledger.total, 900 / 2 - dust); + assert_eq!(LedgerSlashPerEra::get().0, 500 / 2); + assert_eq!( + LedgerSlashPerEra::get().1, + BTreeMap::from([(4, 0), (5, 100 / 2), (6, 0), (7, 250 / 2)]) + ); + + // Given + // slash order --------------------NA--------2----------0----------1---- + ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)]; + ledger.active = 500; + ledger.total = 40 + 10 + 100 + 250 + 500; // 900 + assert_eq!( + ledger.slash( + 500 + 10 + 250 + 100 / 2, // active + era 6 + era 7 + era 5 / 2 + 0, + 2 /* slash era 2+4 first, so the affected parts are era 2+4, era 3+4 and + * ledge.active. This will cause the affected to go to zero, and then we will + * start slashing older chunks */ + ), + 500 + 250 + 10 + 100 / 2 + ); + // Then + assert_eq!(ledger.active, 0); + assert_eq!(ledger.unlocking, vec![c(4, 40), c(5, 100 / 2)]); + assert_eq!(ledger.total, 90); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(5, 100 / 2), (6, 0), (7, 0)])); + + // Given + // iteration order------------------NA---------2----------0----------1---- + ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)]; + ledger.active = 100; + ledger.total = 5 * 100; + // When + assert_eq!( + ledger.slash( + 351, // active + era 6 + era 7 + era 5 / 2 + 1 + 50, // min balance - everything slashed below 50 will get dusted + 2 /* slash era 2+4 first, so the affected parts are era 2+4, era 3+4 and + * ledge.active. This will cause the affected to go to zero, and then we will + * start slashing older chunks */ + ), + 400 + ); + // Then + assert_eq!(ledger.active, 0); + assert_eq!(ledger.unlocking, vec![c(4, 100)]); + assert_eq!(ledger.total, 100); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(5, 0), (6, 0), (7, 0)])); + + // Tests for saturating arithmetic + + // Given + let slash = u64::MAX as Balance * 2; + let value = slash + - (9 * 4) // The value of the other parts of ledger that will get slashed + + 1; + + ledger.active = 10; + ledger.unlocking = bounded_vec![c(4, 10), c(5, 10), c(6, 10), c(7, value)]; + ledger.total = value + 40; + // When + let slash_amount = ledger.slash(slash, 0, 0); + assert_eq_error_rate!(slash_amount, slash, 5); + // Then + assert_eq!(ledger.active, 0); // slash of 9 + assert_eq!(ledger.unlocking, vec![]); + assert_eq!(ledger.total, 0); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(4, 0), (5, 0), (6, 0), (7, 0)])); + + // Given + let slash = u64::MAX as Balance * 2; + let value = u64::MAX as Balance * 2; + let unit = 100; + // slash * value that will saturate + assert!(slash.checked_mul(value).is_none()); + // but slash * unit won't. + assert!(slash.checked_mul(unit).is_some()); + ledger.unlocking = bounded_vec![c(4, unit), c(5, value), c(6, unit), c(7, unit)]; + //--------------------------------------note value^^^ + ledger.active = unit; + ledger.total = unit * 4 + value; + // When + assert_eq!(ledger.slash(slash, 0, 0), slash - 43); + // Then + // The amount slashed out of `unit` + let affected_balance = value + unit * 4; + let ratio = Perquintill::from_rational(slash, affected_balance); + // `unit` after the slash is applied + let unit_slashed = { + let unit_slash = ratio * unit; + unit - unit_slash + }; + let value_slashed = { + let value_slash = ratio * value; + value - value_slash + }; + assert_eq!(ledger.active, unit_slashed); + assert_eq!(ledger.unlocking, vec![c(5, value_slashed)]); + assert_eq!(ledger.total, value_slashed); + assert_eq!(LedgerSlashPerEra::get().0, 0); + assert_eq!( + LedgerSlashPerEra::get().1, + BTreeMap::from([(4, 0), (5, value_slashed), (6, 0), (7, 0)]) + ); +} diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 15208df62cc66..57d858be1e0ef 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -19,6 +19,7 @@ //! A crate which contains primitives that are useful for implementation that uses staking //! approaches in general. Definitions related to sessions, slashing, etc go here. +use sp_std::collections::btree_map::BTreeMap; pub mod offence; @@ -27,3 +28,27 @@ pub type SessionIndex = u32; /// Counter for the number of eras that have passed. pub type EraIndex = u32; + +/// Trait describing something that implements a hook for any operations to perform when a staker is +/// slashed. +pub trait OnStakerSlash { + /// A hook for any operations to perform when a staker is slashed. + /// + /// # Arguments + /// + /// * `stash` - The stash of the staker whom the slash was applied to. + /// * `slashed_active` - The new bonded balance of the staker after the slash was applied. + /// * `slashed_unlocking` - a map of slashed eras, and the balance of that unlocking chunk after + /// the slash is applied. Any era not present in the map is not affected at all. + fn on_slash( + stash: &AccountId, + slashed_active: Balance, + slashed_unlocking: &BTreeMap, + ); +} + +impl OnStakerSlash for () { + fn on_slash(_: &AccountId, _: Balance, _: &BTreeMap) { + // Nothing to do here + } +} From 2e7b21cb55083e24cc8bf8c48d603b16c9b7a94f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Apr 2022 10:39:33 +0200 Subject: [PATCH 143/484] Bump tracing-core from 0.1.21 to 0.1.26 (#11258) Bumps [tracing-core](https://github.com/tokio-rs/tracing) from 0.1.21 to 0.1.26. - [Release notes](https://github.com/tokio-rs/tracing/releases) - [Commits](https://github.com/tokio-rs/tracing/compare/tracing-core-0.1.21...tracing-core-0.1.26) --- updated-dependencies: - dependency-name: tracing-core dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 11 +++++++++-- primitives/io/Cargo.toml | 2 +- primitives/runtime-interface/test/Cargo.toml | 2 +- primitives/tracing/Cargo.toml | 2 +- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a1a23117235e0..486cb083bca99 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11383,11 +11383,12 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.21" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4" +checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" dependencies = [ "lazy_static", + "valuable", ] [[package]] @@ -11764,6 +11765,12 @@ dependencies = [ "percent-encoding 2.1.0", ] +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "value-bag" version = "1.0.0-alpha.8" diff --git a/primitives/io/Cargo.toml b/primitives/io/Cargo.toml index 9964fe2eea318..6d0ac398e473d 100644 --- a/primitives/io/Cargo.toml +++ b/primitives/io/Cargo.toml @@ -32,7 +32,7 @@ futures = { version = "0.3.21", features = ["thread-pool"], optional = true } parking_lot = { version = "0.12.0", optional = true } secp256k1 = { version = "0.21.2", features = ["recovery", "global-context"], optional = true } tracing = { version = "0.1.29", default-features = false } -tracing-core = { version = "0.1.17", default-features = false} +tracing-core = { version = "0.1.26", default-features = false} [features] default = ["std"] diff --git a/primitives/runtime-interface/test/Cargo.toml b/primitives/runtime-interface/test/Cargo.toml index 3b51088ea3779..60962bb16caf5 100644 --- a/primitives/runtime-interface/test/Cargo.toml +++ b/primitives/runtime-interface/test/Cargo.toml @@ -21,4 +21,4 @@ sp-state-machine = { version = "0.12.0", path = "../../state-machine" } sp-runtime = { version = "6.0.0", path = "../../runtime" } sp-io = { version = "6.0.0", path = "../../io" } tracing = "0.1.29" -tracing-core = "0.1.17" +tracing-core = "0.1.26" diff --git a/primitives/tracing/Cargo.toml b/primitives/tracing/Cargo.toml index 3f53cc2e6c5c6..d305b756e2d68 100644 --- a/primitives/tracing/Cargo.toml +++ b/primitives/tracing/Cargo.toml @@ -23,7 +23,7 @@ codec = { version = "3.0.0", package = "parity-scale-codec", default-features = "derive", ] } tracing = { version = "0.1.29", default-features = false } -tracing-core = { version = "0.1.21", default-features = false } +tracing-core = { version = "0.1.26", default-features = false } tracing-subscriber = { version = "0.2.25", optional = true, features = [ "tracing-log", ] } From 5483d7f4c6bfd8df9f32b8ba5874a3fadacd2db6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 22 Apr 2022 16:58:53 +0200 Subject: [PATCH 144/484] BABE: Fix aux data cleaning (#11263) With the latest optimizations of the `FinalityNotification` generation, the aux data pruning started to print a warning. The problem here was that we printed a warning and stopped the adding of blocks to prune when we hit the `heigh_limit`. This is now wrong, as we could for example have two 512 long forks and then we start finalizing one of them. The second fork head would be part of the stale heads at some point (in the current implementation when we finalize second fork head number + 1), but then we would actually need to go back into the past than `heigh_limit` (which was actually last_finalized - 1). We now go back until we reach the canonical chain. Also fixed some wrong comment that was added by be about the content of the `finalized` blocks in the `FinalityNotification`. --- client/api/src/client.rs | 2 +- client/consensus/babe/src/lib.rs | 59 +++++++++++++++++++----------- client/consensus/babe/src/tests.rs | 9 +++++ 3 files changed, 48 insertions(+), 22 deletions(-) diff --git a/client/api/src/client.rs b/client/api/src/client.rs index 183ce610e7b7c..c4b01fbd0abbd 100644 --- a/client/api/src/client.rs +++ b/client/api/src/client.rs @@ -308,7 +308,7 @@ pub struct FinalityNotification { pub header: Block::Header, /// Path from the old finalized to new finalized parent (implicitly finalized blocks). /// - /// This maps to the range `(old_finalized, new_finalized]`. + /// This maps to the range `(old_finalized, new_finalized)`. pub tree_route: Arc<[Block::Hash]>, /// Stale branches heads. pub stale_heads: Arc<[Block::Hash]>, diff --git a/client/consensus/babe/src/lib.rs b/client/consensus/babe/src/lib.rs index 3d3a7f24df816..be5c2809bd796 100644 --- a/client/consensus/babe/src/lib.rs +++ b/client/consensus/babe/src/lib.rs @@ -541,49 +541,66 @@ where // Remove obsolete block's weight data by leveraging finality notifications. // This includes data for all finalized blocks (excluding the most recent one) // and all stale branches. -fn aux_storage_cleanup, Block: BlockT>( +fn aux_storage_cleanup + HeaderBackend, Block: BlockT>( client: &C, notification: &FinalityNotification, ) -> AuxDataOperations { let mut aux_keys = HashSet::new(); - // Cleans data for finalized block's ancestors down to, and including, the previously - // finalized one. - - let first_new_finalized = notification.tree_route.get(0).unwrap_or(¬ification.hash); - match client.header_metadata(*first_new_finalized) { + let first = notification.tree_route.first().unwrap_or(¬ification.hash); + match client.header_metadata(*first) { Ok(meta) => { aux_keys.insert(aux_schema::block_weight_key(meta.parent)); }, - Err(err) => { - warn!(target: "babe", "header lookup fail while cleaning data for block {}: {}", first_new_finalized.to_string(), err.to_string()); - }, + Err(err) => warn!( + target: "babe", + "Failed to lookup metadata for block `{:?}`: {}", + first, + err, + ), } - aux_keys.extend(notification.tree_route.iter().map(aux_schema::block_weight_key)); + // Cleans data for finalized block's ancestors + aux_keys.extend( + notification + .tree_route + .iter() + // Ensure we don't prune latest finalized block. + // This should not happen, but better be safe than sorry! + .filter(|h| **h != notification.hash) + .map(aux_schema::block_weight_key), + ); // Cleans data for stale branches. - // A safenet in case of malformed notification. - let height_limit = notification.header.number().saturating_sub( - notification.tree_route.len().saturated_into::>() + One::one(), - ); for head in notification.stale_heads.iter() { let mut hash = *head; - // Insert stale blocks hashes until canonical chain is not reached. - // Soon or late we should hit an element already present within the `aux_keys` set. + // Insert stale blocks hashes until canonical chain is reached. + // If we reach a block that is already part of the `aux_keys` we can stop the processing the + // head. while aux_keys.insert(aux_schema::block_weight_key(hash)) { match client.header_metadata(hash) { Ok(meta) => { - // This should never happen and must be considered a bug. - if meta.number <= height_limit { - warn!(target: "babe", "unexpected canonical chain state or malformed finality notification"); + hash = meta.parent; + + // If the parent is part of the canonical chain or there doesn't exist a block + // hash for the parent number (bug?!), we can abort adding blocks. + if client + .hash(meta.number.saturating_sub(1u32.into())) + .ok() + .flatten() + .map_or(true, |h| h == hash) + { break } - hash = meta.parent; }, Err(err) => { - warn!(target: "babe", "header lookup fail while cleaning data for block {}: {}", head.to_string(), err.to_string()); + warn!( + target: "babe", + "Header lookup fail while cleaning data for block {:?}: {}", + hash, + err, + ); break }, } diff --git a/client/consensus/babe/src/tests.rs b/client/consensus/babe/src/tests.rs index aa2d824b8ccb9..db19deda06fd8 100644 --- a/client/consensus/babe/src/tests.rs +++ b/client/consensus/babe/src/tests.rs @@ -1043,4 +1043,13 @@ fn obsolete_blocks_aux_data_cleanup() { assert!(aux_data_check(&fork2_hashes, false)); // Present C4, C5 assert!(aux_data_check(&fork3_hashes, true)); + + client.finalize_block(BlockId::Number(4), None, true).unwrap(); + + // Wiped: A3 + assert!(aux_data_check(&fork1_hashes[2..3], false)); + // Present: A4 + assert!(aux_data_check(&fork1_hashes[3..], true)); + // Present C4, C5 + assert!(aux_data_check(&fork3_hashes, true)); } From b9f80dfe0b90fa7a4773a18e86da398a5f6188ce Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Mon, 25 Apr 2022 07:16:38 +0300 Subject: [PATCH 145/484] More efficient identity and multiplier weight to fee (#11226) --- frame/support/src/weights.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/frame/support/src/weights.rs b/frame/support/src/weights.rs index 4c3fcaa0fd423..5b4a13e7f9457 100644 --- a/frame/support/src/weights.rs +++ b/frame/support/src/weights.rs @@ -710,6 +710,10 @@ where degree: 1, }) } + + fn calc(weight: &Weight) -> Self::Balance { + Self::Balance::saturated_from(*weight) + } } /// Implementor of [`WeightToFeePolynomial`] that uses a constant multiplier. @@ -738,6 +742,10 @@ where degree: 1, }) } + + fn calc(weight: &Weight) -> Self::Balance { + Self::Balance::saturated_from(*weight).saturating_mul(M::get()) + } } /// A struct holding value for each `DispatchClass`. From 0c08c727ffe2a72e015b2a3b541ea1c3dcf16051 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Mon, 25 Apr 2022 17:07:09 +0200 Subject: [PATCH 146/484] cumulus-companion: Fix CI when there is no Polkadot companion (#11280) This tries to fix the CI if there is no polkadot companion. Currently we don't update Polkadot master in Cumulus, which means we may use some old commit that isn't compiling with the latest Substrate master anymore. This can happen if there was a pr that had a companion in Polkadot, but no companion was required for Cumulus. Then Cumulus will still point to some old Polkadot commit that isn't compiling anymore with the latest Substrate commit. So, we need to tell the script to use the latest master of Polkadot. If there is a companion for Polkadot, it would simply override the extra dependency patch later on. --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 62684ffebd987..1c3e81ca71f3d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -474,6 +474,7 @@ cargo-check-macos: "$DEPENDENT_REPO" "$GITHUB_PR_TOKEN" "$CARGO_UPDATE_CRATES" + "$EXTRA_DEPENDENCIES" # Individual jobs are set up for each dependent project so that they can be ran in parallel. # Arguably we could generate a job for each companion in the PR's description using Gitlab's @@ -490,6 +491,7 @@ check-dependent-cumulus: variables: DEPENDENT_REPO: cumulus CARGO_UPDATE_CRATES: "sp-io polkadot-runtime-common" + EXTRA_DEPENDENCIES: polkadot build-linux-substrate: From bafb4dbf9759b200b481f80fff94b74909c9fc2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Mon, 25 Apr 2022 20:12:06 +0200 Subject: [PATCH 147/484] pallet-asset: Fix transfer of a large amount of an asset (#11241) * pallet-asset: Fix transfer of a large amount of an asset Before this pr transferring a large amount of an asset would check that transferring the asset would not overflow the supply of the asset. However, it doesn't make sense to check for asset supply overflow when we just transfer from one account to another account and don't increase the supply in any way. It also required to extend the `can_deposit` method of `fungible` and `fungibles` with a `mint` parameter. If this parameter is set to `true`, it means we want to mint the amount of an asset before transferring it into an account. For `can_withdraw` we don't need to add an extra parameter, because withdrawing should never be able to underflow the supply. If that would happen, it would mean that somewhere the supply wasn't increased while increasing the balance of an account. * Update frame/assets/src/functions.rs * Update frame/assets/src/functions.rs * Update frame/assets/src/functions.rs Co-authored-by: Shawn Tabrizi * FMT Co-authored-by: Shawn Tabrizi --- frame/assets/src/functions.rs | 14 +++++++++++--- frame/assets/src/impl_fungibles.rs | 3 ++- frame/assets/src/tests.rs | 10 ++++++++++ frame/balances/src/lib.rs | 9 +++++---- frame/support/src/traits/tokens/fungible.rs | 14 ++++++++++---- frame/support/src/traits/tokens/fungibles.rs | 10 +++++++++- 6 files changed, 47 insertions(+), 13 deletions(-) diff --git a/frame/assets/src/functions.rs b/frame/assets/src/functions.rs index 0be79619e0967..a6abfd9e0409c 100644 --- a/frame/assets/src/functions.rs +++ b/frame/assets/src/functions.rs @@ -103,16 +103,24 @@ impl, I: 'static> Pallet { Remove } + /// Returns `true` when the balance of `account` can be increased by `amount`. + /// + /// - `id`: The id of the asset that should be increased. + /// - `who`: The account of which the balance should be increased. + /// - `amount`: The amount by which the balance should be increased. + /// - `increase_supply`: Will the supply of the asset be increased by `amount` at the same time + /// as crediting the `account`. pub(super) fn can_increase( id: T::AssetId, who: &T::AccountId, amount: T::Balance, + increase_supply: bool, ) -> DepositConsequence { let details = match Asset::::get(id) { Some(details) => details, None => return DepositConsequence::UnknownAsset, }; - if details.supply.checked_add(&amount).is_none() { + if increase_supply && details.supply.checked_add(&amount).is_none() { return DepositConsequence::Overflow } if let Some(balance) = Self::maybe_balance(id, who) { @@ -283,7 +291,7 @@ impl, I: 'static> Pallet { (true, Some(dust)) => (amount, Some(dust)), _ => (debit, None), }; - Self::can_increase(id, &dest, credit).into_result()?; + Self::can_increase(id, &dest, credit, false).into_result()?; Ok((credit, maybe_burn)) } @@ -379,7 +387,7 @@ impl, I: 'static> Pallet { return Ok(()) } - Self::can_increase(id, beneficiary, amount).into_result()?; + Self::can_increase(id, beneficiary, amount, true).into_result()?; Asset::::try_mutate(id, |maybe_details| -> DispatchResult { let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; diff --git a/frame/assets/src/impl_fungibles.rs b/frame/assets/src/impl_fungibles.rs index 49caac83f4c4a..6b263bc0c7bef 100644 --- a/frame/assets/src/impl_fungibles.rs +++ b/frame/assets/src/impl_fungibles.rs @@ -47,8 +47,9 @@ impl, I: 'static> fungibles::Inspect<::AccountId asset: Self::AssetId, who: &::AccountId, amount: Self::Balance, + mint: bool, ) -> DepositConsequence { - Pallet::::can_increase(asset, who, amount) + Pallet::::can_increase(asset, who, amount, mint) } fn can_withdraw( diff --git a/frame/assets/src/tests.rs b/frame/assets/src/tests.rs index db0d6a5f212f9..50ab04111edff 100644 --- a/frame/assets/src/tests.rs +++ b/frame/assets/src/tests.rs @@ -967,3 +967,13 @@ fn querying_allowance_should_work() { assert_eq!(Assets::allowance(0, &1, &2), 0); }); } + +#[test] +fn transfer_large_asset() { + new_test_ext().execute_with(|| { + let amount = u64::pow(2, 63) + 2; + assert_ok!(Assets::force_create(Origin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(Origin::signed(1), 0, 1, amount)); + assert_ok!(Assets::transfer(Origin::signed(1), 0, 2, amount - 1)); + }) +} diff --git a/frame/balances/src/lib.rs b/frame/balances/src/lib.rs index 6bf37dfda037b..0eab933f7757d 100644 --- a/frame/balances/src/lib.rs +++ b/frame/balances/src/lib.rs @@ -810,12 +810,13 @@ impl, I: 'static> Pallet { _who: &T::AccountId, amount: T::Balance, account: &AccountData, + mint: bool, ) -> DepositConsequence { if amount.is_zero() { return DepositConsequence::Success } - if TotalIssuance::::get().checked_add(&amount).is_none() { + if mint && TotalIssuance::::get().checked_add(&amount).is_none() { return DepositConsequence::Overflow } @@ -1093,8 +1094,8 @@ impl, I: 'static> fungible::Inspect for Pallet liquid.saturating_sub(must_remain_to_exist) } } - fn can_deposit(who: &T::AccountId, amount: Self::Balance) -> DepositConsequence { - Self::deposit_consequence(who, amount, &Self::account(who)) + fn can_deposit(who: &T::AccountId, amount: Self::Balance, mint: bool) -> DepositConsequence { + Self::deposit_consequence(who, amount, &Self::account(who), mint) } fn can_withdraw( who: &T::AccountId, @@ -1110,7 +1111,7 @@ impl, I: 'static> fungible::Mutate for Pallet { return Ok(()) } Self::try_mutate_account(who, |account, _is_new| -> DispatchResult { - Self::deposit_consequence(who, amount, &account).into_result()?; + Self::deposit_consequence(who, amount, &account, true).into_result()?; account.free += amount; Ok(()) })?; diff --git a/frame/support/src/traits/tokens/fungible.rs b/frame/support/src/traits/tokens/fungible.rs index 712103a1e8837..7422a9d651874 100644 --- a/frame/support/src/traits/tokens/fungible.rs +++ b/frame/support/src/traits/tokens/fungible.rs @@ -50,7 +50,11 @@ pub trait Inspect { fn reducible_balance(who: &AccountId, keep_alive: bool) -> Self::Balance; /// Returns `true` if the balance of `who` may be increased by `amount`. - fn can_deposit(who: &AccountId, amount: Self::Balance) -> DepositConsequence; + /// + /// - `who`: The account of which the balance should be increased by `amount`. + /// - `amount`: How much should the balance be increased? + /// - `mint`: Will `amount` be minted to deposit it into `account`? + fn can_deposit(who: &AccountId, amount: Self::Balance, mint: bool) -> DepositConsequence; /// Returns `Failed` if the balance of `who` may not be decreased by `amount`, otherwise /// the consequence. @@ -86,7 +90,9 @@ pub trait Mutate: Inspect { amount: Self::Balance, ) -> Result { let extra = Self::can_withdraw(&source, amount).into_result()?; - Self::can_deposit(&dest, amount.saturating_add(extra)).into_result()?; + // As we first burn and then mint, we don't need to check if `mint` fits into the supply. + // If we can withdraw/burn it, we can also mint it again. + Self::can_deposit(&dest, amount.saturating_add(extra), false).into_result()?; let actual = Self::burn_from(source, amount)?; debug_assert!( actual == amount.saturating_add(extra), @@ -216,8 +222,8 @@ impl< fn reducible_balance(who: &AccountId, keep_alive: bool) -> Self::Balance { >::reducible_balance(A::get(), who, keep_alive) } - fn can_deposit(who: &AccountId, amount: Self::Balance) -> DepositConsequence { - >::can_deposit(A::get(), who, amount) + fn can_deposit(who: &AccountId, amount: Self::Balance, mint: bool) -> DepositConsequence { + >::can_deposit(A::get(), who, amount, mint) } fn can_withdraw(who: &AccountId, amount: Self::Balance) -> WithdrawConsequence { >::can_withdraw(A::get(), who, amount) diff --git a/frame/support/src/traits/tokens/fungibles.rs b/frame/support/src/traits/tokens/fungibles.rs index 8e68b36d60c7a..2abadf037687d 100644 --- a/frame/support/src/traits/tokens/fungibles.rs +++ b/frame/support/src/traits/tokens/fungibles.rs @@ -53,10 +53,16 @@ pub trait Inspect { fn reducible_balance(asset: Self::AssetId, who: &AccountId, keep_alive: bool) -> Self::Balance; /// Returns `true` if the `asset` balance of `who` may be increased by `amount`. + /// + /// - `asset`: The asset that should be deposited. + /// - `who`: The account of which the balance should be increased by `amount`. + /// - `amount`: How much should the balance be increased? + /// - `mint`: Will `amount` be minted to deposit it into `account`? fn can_deposit( asset: Self::AssetId, who: &AccountId, amount: Self::Balance, + mint: bool, ) -> DepositConsequence; /// Returns `Failed` if the `asset` balance of `who` may not be decreased by `amount`, otherwise @@ -137,7 +143,9 @@ pub trait Mutate: Inspect { amount: Self::Balance, ) -> Result { let extra = Self::can_withdraw(asset, &source, amount).into_result()?; - Self::can_deposit(asset, &dest, amount.saturating_add(extra)).into_result()?; + // As we first burn and then mint, we don't need to check if `mint` fits into the supply. + // If we can withdraw/burn it, we can also mint it again. + Self::can_deposit(asset, &dest, amount.saturating_add(extra), false).into_result()?; let actual = Self::burn_from(asset, source, amount)?; debug_assert!( actual == amount.saturating_add(extra), From b8c467836bc92ff4ac38738c5eb066fc3df64203 Mon Sep 17 00:00:00 2001 From: tgmichel Date: Mon, 25 Apr 2022 21:07:26 +0200 Subject: [PATCH 148/484] Add `chain-info` Subcommand (#11250) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add `blockchain-info` Subcommand * Update comment * Cleanup * Cleanup * Use `sync_run` * Use `sc_client_db` utility fns instead service backend * Use service `Backend` builder * Impl `From` * Rename to `chain-info` * fmt * Copyright year Co-authored-by: Bastian Köcher * Expose `DatabaseParams` Co-authored-by: Bastian Köcher --- Cargo.lock | 1 + bin/node-template/node/src/cli.rs | 3 + bin/node-template/node/src/command.rs | 4 + bin/node/cli/src/cli.rs | 3 + bin/node/cli/src/command.rs | 4 + client/cli/Cargo.toml | 1 + client/cli/src/commands/chain_info_cmd.rs | 103 ++++++++++++++++++++++ client/cli/src/commands/mod.rs | 3 +- 8 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 client/cli/src/commands/chain_info_cmd.rs diff --git a/Cargo.lock b/Cargo.lock index 486cb083bca99..48fe7cc122607 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8242,6 +8242,7 @@ dependencies = [ "regex", "rpassword", "sc-client-api", + "sc-client-db", "sc-keystore", "sc-network", "sc-service", diff --git a/bin/node-template/node/src/cli.rs b/bin/node-template/node/src/cli.rs index 710c7f3f9e145..4ab4d34210c98 100644 --- a/bin/node-template/node/src/cli.rs +++ b/bin/node-template/node/src/cli.rs @@ -47,4 +47,7 @@ pub enum Subcommand { /// Try some command against runtime state. Note: `try-runtime` feature must be enabled. #[cfg(not(feature = "try-runtime"))] TryRuntime, + + /// Db meta columns information. + ChainInfo(sc_cli::ChainInfoCmd), } diff --git a/bin/node-template/node/src/command.rs b/bin/node-template/node/src/command.rs index afa4612f1ee4a..809257f79007c 100644 --- a/bin/node-template/node/src/command.rs +++ b/bin/node-template/node/src/command.rs @@ -162,6 +162,10 @@ pub fn run() -> sc_cli::Result<()> { Some(Subcommand::TryRuntime) => Err("TryRuntime wasn't enabled when building the node. \ You can enable it with `--features try-runtime`." .into()), + Some(Subcommand::ChainInfo(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.sync_run(|config| cmd.run::(&config)) + }, None => { let runner = cli.create_runner(&cli.run)?; runner.run_node_until_exit(|config| async move { diff --git a/bin/node/cli/src/cli.rs b/bin/node/cli/src/cli.rs index 5b5db62199bbd..5b2977599bab0 100644 --- a/bin/node/cli/src/cli.rs +++ b/bin/node/cli/src/cli.rs @@ -94,4 +94,7 @@ pub enum Subcommand { /// Revert the chain to a previous state. Revert(sc_cli::RevertCmd), + + /// Db meta columns information. + ChainInfo(sc_cli::ChainInfoCmd), } diff --git a/bin/node/cli/src/command.rs b/bin/node/cli/src/command.rs index e2c772e809200..b98a38d2dbf5f 100644 --- a/bin/node/cli/src/command.rs +++ b/bin/node/cli/src/command.rs @@ -206,5 +206,9 @@ pub fn run() -> Result<()> { Some(Subcommand::TryRuntime) => Err("TryRuntime wasn't enabled when building the node. \ You can enable it with `--features try-runtime`." .into()), + Some(Subcommand::ChainInfo(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.sync_run(|config| cmd.run::(&config)) + }, } } diff --git a/client/cli/Cargo.toml b/client/cli/Cargo.toml index 6a4d4b74f963e..adf88b8c88a2c 100644 --- a/client/cli/Cargo.toml +++ b/client/cli/Cargo.toml @@ -32,6 +32,7 @@ tokio = { version = "1.17.0", features = ["signal", "rt-multi-thread", "parking_ parity-scale-codec = "3.0.0" sc-client-api = { version = "4.0.0-dev", path = "../api" } +sc-client-db = { version = "0.10.0-dev", path = "../db" } sc-keystore = { version = "4.0.0-dev", path = "../keystore" } sc-network = { version = "0.10.0-dev", path = "../network" } sc-service = { version = "0.10.0-dev", default-features = false, path = "../service" } diff --git a/client/cli/src/commands/chain_info_cmd.rs b/client/cli/src/commands/chain_info_cmd.rs new file mode 100644 index 0000000000000..0e57d1677efbb --- /dev/null +++ b/client/cli/src/commands/chain_info_cmd.rs @@ -0,0 +1,103 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use crate::{CliConfiguration, DatabaseParams, PruningParams, Result as CliResult, SharedParams}; +use parity_scale_codec::{Decode, Encode}; +use sc_client_api::{backend::Backend as BackendT, blockchain::HeaderBackend}; +use sp_blockchain::Info; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; +use std::{fmt::Debug, io}; + +/// The `chain-info` subcommand used to output db meta columns information. +#[derive(Debug, Clone, clap::Parser)] +pub struct ChainInfoCmd { + #[allow(missing_docs)] + #[clap(flatten)] + pub pruning_params: PruningParams, + + #[allow(missing_docs)] + #[clap(flatten)] + pub shared_params: SharedParams, + + #[allow(missing_docs)] + #[clap(flatten)] + pub database_params: DatabaseParams, +} + +/// Serializable `chain-info` subcommand output. +#[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, serde::Serialize)] +struct ChainInfo { + /// Best block hash. + best_hash: B::Hash, + /// Best block number. + best_number: <::Header as HeaderT>::Number, + /// Genesis block hash. + genesis_hash: B::Hash, + /// The head of the finalized chain. + finalized_hash: B::Hash, + /// Last finalized block number. + finalized_number: <::Header as HeaderT>::Number, +} + +impl From> for ChainInfo { + fn from(info: Info) -> Self { + ChainInfo:: { + best_hash: info.best_hash, + best_number: info.best_number, + genesis_hash: info.genesis_hash, + finalized_hash: info.finalized_hash, + finalized_number: info.finalized_number, + } + } +} + +impl ChainInfoCmd { + /// Run the `chain-info` subcommand + pub fn run(&self, config: &sc_service::Configuration) -> CliResult<()> + where + B: BlockT, + { + let db_config = sc_client_db::DatabaseSettings { + state_cache_size: config.state_cache_size, + state_cache_child_ratio: config.state_cache_child_ratio.map(|v| (v, 100)), + state_pruning: config.state_pruning.clone(), + source: config.database.clone(), + keep_blocks: config.keep_blocks.clone(), + }; + let backend = sc_service::new_db_backend::(db_config)?; + let info: ChainInfo = backend.blockchain().info().into(); + let mut out = io::stdout(); + serde_json::to_writer_pretty(&mut out, &info) + .map_err(|e| format!("Error writing JSON: {}", e))?; + Ok(()) + } +} + +impl CliConfiguration for ChainInfoCmd { + fn shared_params(&self) -> &SharedParams { + &self.shared_params + } + + fn pruning_params(&self) -> Option<&PruningParams> { + Some(&self.pruning_params) + } + + fn database_params(&self) -> Option<&DatabaseParams> { + Some(&self.database_params) + } +} diff --git a/client/cli/src/commands/mod.rs b/client/cli/src/commands/mod.rs index 2b46e1d99caa2..8e84afa34e24a 100644 --- a/client/cli/src/commands/mod.rs +++ b/client/cli/src/commands/mod.rs @@ -16,6 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . mod build_spec_cmd; +mod chain_info_cmd; mod check_block_cmd; mod export_blocks_cmd; mod export_state_cmd; @@ -35,7 +36,7 @@ mod vanity; mod verify; pub use self::{ - build_spec_cmd::BuildSpecCmd, check_block_cmd::CheckBlockCmd, + build_spec_cmd::BuildSpecCmd, chain_info_cmd::ChainInfoCmd, check_block_cmd::CheckBlockCmd, export_blocks_cmd::ExportBlocksCmd, export_state_cmd::ExportStateCmd, generate::GenerateCmd, generate_node_key::GenerateNodeKeyCmd, import_blocks_cmd::ImportBlocksCmd, insert_key::InsertKeyCmd, inspect_key::InspectKeyCmd, inspect_node_key::InspectNodeKeyCmd, From 0ec3c114a9dd58c95c0260c5c021e6b8b6423888 Mon Sep 17 00:00:00 2001 From: Koute Date: Tue, 26 Apr 2022 17:05:51 +0900 Subject: [PATCH 149/484] Turn on logger's interest cache (#11264) --- Cargo.lock | 6 ++++-- client/tracing/Cargo.toml | 2 +- client/tracing/src/logging/mod.rs | 5 ++++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 48fe7cc122607..58cb4ed0e99ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11404,12 +11404,14 @@ dependencies = [ [[package]] name = "tracing-log" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" dependencies = [ + "ahash", "lazy_static", "log 0.4.16", + "lru 0.7.5", "tracing-core", ] diff --git a/client/tracing/Cargo.toml b/client/tracing/Cargo.toml index 121d55ca930b8..4178e26fe7bf6 100644 --- a/client/tracing/Cargo.toml +++ b/client/tracing/Cargo.toml @@ -26,7 +26,7 @@ rustc-hash = "1.1.0" serde = "1.0.136" thiserror = "1.0.30" tracing = "0.1.29" -tracing-log = "0.1.2" +tracing-log = { version = "0.1.3", features = ["interest-cache"] } tracing-subscriber = { version = "0.2.25", features = ["parking_lot"] } sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } sp-rpc = { version = "6.0.0", path = "../../primitives/rpc" } diff --git a/client/tracing/src/logging/mod.rs b/client/tracing/src/logging/mod.rs index c325a3f73c413..f5cdda4d35442 100644 --- a/client/tracing/src/logging/mod.rs +++ b/client/tracing/src/logging/mod.rs @@ -155,7 +155,10 @@ where let max_level_hint = Layer::::max_level_hint(&env_filter); let max_level = to_log_level_filter(max_level_hint); - tracing_log::LogTracer::builder().with_max_level(max_level).init()?; + tracing_log::LogTracer::builder() + .with_max_level(max_level) + .with_interest_cache(tracing_log::InterestCacheConfig::default()) + .init()?; // If we're only logging `INFO` entries then we'll use a simplified logging format. let detailed_output = match max_level_hint { From 0f08e6c7765cdefdad00df224b090f991b4bd01e Mon Sep 17 00:00:00 2001 From: Qinxuan Chen Date: Tue, 26 Apr 2022 17:25:41 +0800 Subject: [PATCH 150/484] sp-sandbox: move the sandbox module of `sp-core` into `sp-sandbox` (#11027) * sp-sandbox: move the sandbox module of sp-core into sp-sandbox Signed-off-by: koushiro * Fix Signed-off-by: koushiro * Fix Signed-off-by: koushiro --- Cargo.lock | 6 +-- client/executor/Cargo.toml | 29 ++++++------ client/executor/common/Cargo.toml | 13 +++--- client/executor/common/src/sandbox.rs | 24 +++++----- .../common/src/sandbox/wasmer_backend.rs | 23 +++++----- .../common/src/sandbox/wasmi_backend.rs | 6 +-- client/executor/runtime-test/Cargo.toml | 7 +-- client/executor/wasmi/Cargo.toml | 9 ++-- client/executor/wasmi/src/lib.rs | 44 ++++++++++--------- client/executor/wasmtime/Cargo.toml | 21 ++++----- client/executor/wasmtime/src/host.rs | 34 +++++++------- primitives/core/src/lib.rs | 1 - primitives/sandbox/Cargo.toml | 15 ++++--- primitives/sandbox/src/embedded_executor.rs | 34 +++++++++----- .../src/sandbox.rs => sandbox/src/env.rs} | 10 +++-- primitives/sandbox/src/host_executor.rs | 44 +++++++++---------- primitives/sandbox/src/lib.rs | 25 ++++++----- 17 files changed, 183 insertions(+), 162 deletions(-) mode change 100755 => 100644 primitives/sandbox/src/embedded_executor.rs rename primitives/{core/src/sandbox.rs => sandbox/src/env.rs} (92%) mode change 100755 => 100644 primitives/sandbox/src/host_executor.rs mode change 100755 => 100644 primitives/sandbox/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 58cb4ed0e99ad..d58c66096848c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8618,8 +8618,8 @@ dependencies = [ "environmental", "parity-scale-codec", "sc-allocator", - "sp-core", "sp-maybe-compressed-blob", + "sp-sandbox", "sp-serializer", "sp-wasm-interface", "thiserror", @@ -8637,8 +8637,8 @@ dependencies = [ "sc-allocator", "sc-executor-common", "scoped-tls", - "sp-core", "sp-runtime-interface", + "sp-sandbox", "sp-wasm-interface", "wasmi", ] @@ -8655,9 +8655,9 @@ dependencies = [ "sc-allocator", "sc-executor-common", "sc-runtime-test", - "sp-core", "sp-io", "sp-runtime-interface", + "sp-sandbox", "sp-wasm-interface", "wasmtime", "wat", diff --git a/client/executor/Cargo.toml b/client/executor/Cargo.toml index 8e10de1b04a95..ad2288b9272d3 100644 --- a/client/executor/Cargo.toml +++ b/client/executor/Cargo.toml @@ -14,26 +14,27 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +lazy_static = "1.4.0" +lru = "0.7.5" +parking_lot = "0.12.0" +tracing = "0.1.29" +wasmi = "0.9.1" + codec = { package = "parity-scale-codec", version = "3.0.0" } -sp-io = { version = "6.0.0", path = "../../primitives/io" } +sc-executor-common = { version = "0.10.0-dev", path = "common" } +sc-executor-wasmi = { version = "0.10.0-dev", path = "wasmi" } +sc-executor-wasmtime = { version = "0.10.0-dev", path = "wasmtime", optional = true } +sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } sp-core = { version = "6.0.0", path = "../../primitives/core" } +sp-core-hashing-proc-macro = { version = "5.0.0", path = "../../primitives/core/hashing/proc-macro" } +sp-externalities = { version = "0.12.0", path = "../../primitives/externalities" } +sp-io = { version = "6.0.0", path = "../../primitives/io" } +sp-panic-handler = { version = "4.0.0", path = "../../primitives/panic-handler" } +sp-runtime-interface = { version = "6.0.0", path = "../../primitives/runtime-interface" } sp-tasks = { version = "4.0.0-dev", path = "../../primitives/tasks" } sp-trie = { version = "6.0.0", path = "../../primitives/trie" } sp-version = { version = "5.0.0", path = "../../primitives/version" } -sp-panic-handler = { version = "4.0.0", path = "../../primitives/panic-handler" } -wasmi = "0.9.1" -lazy_static = "1.4.0" -sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } sp-wasm-interface = { version = "6.0.0", path = "../../primitives/wasm-interface" } -sp-runtime-interface = { version = "6.0.0", path = "../../primitives/runtime-interface" } -sp-externalities = { version = "0.12.0", path = "../../primitives/externalities" } -sc-executor-common = { version = "0.10.0-dev", path = "common" } -sc-executor-wasmi = { version = "0.10.0-dev", path = "wasmi" } -sc-executor-wasmtime = { version = "0.10.0-dev", path = "wasmtime", optional = true } -parking_lot = "0.12.0" -sp-core-hashing-proc-macro = { version = "5.0.0", path = "../../primitives/core/hashing/proc-macro" } -lru = "0.7.5" -tracing = "0.1.29" [dev-dependencies] wat = "1.0" diff --git a/client/executor/common/Cargo.toml b/client/executor/common/Cargo.toml index 149d9fdc236cd..677148f709e6b 100644 --- a/client/executor/common/Cargo.toml +++ b/client/executor/common/Cargo.toml @@ -14,17 +14,18 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +environmental = "1.1.3" +thiserror = "1.0.30" wasm-instrument = "0.1" -codec = { package = "parity-scale-codec", version = "3.0.0" } wasmi = "0.9.1" -sp-core = { version = "6.0.0", path = "../../../primitives/core" } +wasmer = { version = "2.2", features = ["singlepass"], optional = true } + +codec = { package = "parity-scale-codec", version = "3.0.0" } sc-allocator = { version = "4.1.0-dev", path = "../../allocator" } -sp-wasm-interface = { version = "6.0.0", path = "../../../primitives/wasm-interface" } sp-maybe-compressed-blob = { version = "4.1.0-dev", path = "../../../primitives/maybe-compressed-blob" } +sp-sandbox = { version = "0.10.0-dev", path = "../../../primitives/sandbox" } sp-serializer = { version = "4.0.0-dev", path = "../../../primitives/serializer" } -thiserror = "1.0.30" -environmental = "1.1.3" -wasmer = { version = "2.2", optional = true, features = ["singlepass"] } +sp-wasm-interface = { version = "6.0.0", path = "../../../primitives/wasm-interface" } [features] default = [] diff --git a/client/executor/common/src/sandbox.rs b/client/executor/common/src/sandbox.rs index 98eafaf32b8f9..fe14c0865cfde 100644 --- a/client/executor/common/src/sandbox.rs +++ b/client/executor/common/src/sandbox.rs @@ -22,26 +22,26 @@ #[cfg(feature = "wasmer-sandbox")] mod wasmer_backend; - mod wasmi_backend; +use std::{collections::HashMap, rc::Rc}; + +use codec::Decode; +use sp_sandbox::env as sandbox_env; +use sp_wasm_interface::{FunctionContext, Pointer, WordSize}; + use crate::{ error::{self, Result}, util, }; -use codec::Decode; -use sp_core::sandbox as sandbox_primitives; -use sp_wasm_interface::{FunctionContext, Pointer, WordSize}; -use std::{collections::HashMap, rc::Rc}; #[cfg(feature = "wasmer-sandbox")] -use wasmer_backend::{ +use self::wasmer_backend::{ get_global as wasmer_get_global, instantiate as wasmer_instantiate, invoke as wasmer_invoke, new_memory as wasmer_new_memory, Backend as WasmerBackend, MemoryWrapper as WasmerMemoryWrapper, }; - -use wasmi_backend::{ +use self::wasmi_backend::{ get_global as wasmi_get_global, instantiate as wasmi_instantiate, invoke as wasmi_invoke, new_memory as wasmi_new_memory, MemoryWrapper as WasmiMemoryWrapper, }; @@ -243,7 +243,7 @@ fn decode_environment_definition( mut raw_env_def: &[u8], memories: &[Option], ) -> std::result::Result<(Imports, GuestToSupervisorFunctionMapping), InstantiationError> { - let env_def = sandbox_primitives::EnvironmentDefinition::decode(&mut raw_env_def) + let env_def = sandbox_env::EnvironmentDefinition::decode(&mut raw_env_def) .map_err(|_| InstantiationError::EnvironmentDefinitionCorrupted)?; let mut func_map = HashMap::new(); @@ -255,12 +255,12 @@ fn decode_environment_definition( let field = entry.field_name.clone(); match entry.entity { - sandbox_primitives::ExternEntity::Function(func_idx) => { + sandbox_env::ExternEntity::Function(func_idx) => { let externals_idx = guest_to_supervisor_mapping.define(SupervisorFuncIndex(func_idx as usize)); func_map.insert((module, field), externals_idx); }, - sandbox_primitives::ExternEntity::Memory(memory_idx) => { + sandbox_env::ExternEntity::Memory(memory_idx) => { let memory_ref = memories .get(memory_idx as usize) .cloned() @@ -446,7 +446,7 @@ impl Store

{ let backend_context = &self.backend_context; let maximum = match maximum { - sandbox_primitives::MEM_UNLIMITED => None, + sandbox_env::MEM_UNLIMITED => None, specified_limit => Some(specified_limit), }; diff --git a/client/executor/common/src/sandbox/wasmer_backend.rs b/client/executor/common/src/sandbox/wasmer_backend.rs index fd2f317457f88..ab585c7d15431 100644 --- a/client/executor/common/src/sandbox/wasmer_backend.rs +++ b/client/executor/common/src/sandbox/wasmer_backend.rs @@ -18,20 +18,21 @@ //! Wasmer specific impls for sandbox -use crate::{ - error::{Error, Result}, - sandbox::Memory, - util::{checked_range, MemoryTransfer}, -}; -use codec::{Decode, Encode}; -use sp_core::sandbox::HostError; -use sp_wasm_interface::{FunctionContext, Pointer, ReturnValue, Value, WordSize}; use std::{cell::RefCell, collections::HashMap, rc::Rc}; + use wasmer::RuntimeError; -use crate::sandbox::{ - BackendInstance, GuestEnvironment, InstantiationError, SandboxContext, SandboxInstance, - SupervisorFuncIndex, +use codec::{Decode, Encode}; +use sp_sandbox::HostError; +use sp_wasm_interface::{FunctionContext, Pointer, ReturnValue, Value, WordSize}; + +use crate::{ + error::{Error, Result}, + sandbox::{ + BackendInstance, GuestEnvironment, InstantiationError, Memory, SandboxContext, + SandboxInstance, SupervisorFuncIndex, + }, + util::{checked_range, MemoryTransfer}, }; environmental::environmental!(SandboxContextStore: trait SandboxContext); diff --git a/client/executor/common/src/sandbox/wasmi_backend.rs b/client/executor/common/src/sandbox/wasmi_backend.rs index 0954287f52f6b..9c7c154b5b135 100644 --- a/client/executor/common/src/sandbox/wasmi_backend.rs +++ b/client/executor/common/src/sandbox/wasmi_backend.rs @@ -18,11 +18,11 @@ //! Wasmi specific impls for sandbox -use codec::{Decode, Encode}; -use sp_core::sandbox::HostError; -use sp_wasm_interface::{FunctionContext, Pointer, ReturnValue, Value, WordSize}; use std::rc::Rc; +use codec::{Decode, Encode}; +use sp_sandbox::HostError; +use sp_wasm_interface::{FunctionContext, Pointer, ReturnValue, Value, WordSize}; use wasmi::{ memory_units::Pages, ImportResolver, MemoryInstance, Module, ModuleInstance, RuntimeArgs, RuntimeValue, Trap, TrapKind, diff --git a/client/executor/runtime-test/Cargo.toml b/client/executor/runtime-test/Cargo.toml index 352ffdf7a65c2..507b35114d630 100644 --- a/client/executor/runtime-test/Cargo.toml +++ b/client/executor/runtime-test/Cargo.toml @@ -13,24 +13,25 @@ repository = "https://github.com/paritytech/substrate/" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +paste = "1.0.6" + sp-core = { version = "6.0.0", default-features = false, path = "../../../primitives/core" } sp-io = { version = "6.0.0", default-features = false, path = "../../../primitives/io", features = ["improved_panic_error_reporting"] } sp-runtime = { version = "6.0.0", default-features = false, path = "../../../primitives/runtime" } sp-sandbox = { version = "0.10.0-dev", default-features = false, path = "../../../primitives/sandbox" } sp-std = { version = "4.0.0", default-features = false, path = "../../../primitives/std" } sp-tasks = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/tasks" } -paste = "1.0.6" [build-dependencies] substrate-wasm-builder = { version = "5.0.0-dev", path = "../../../utils/wasm-builder" } [features] -default = [ "std" ] +default = ["std"] std = [ "sp-core/std", "sp-io/std", "sp-runtime/std", "sp-sandbox/std", "sp-std/std", - "sp-tasks/std", + "sp-tasks/std", ] diff --git a/client/executor/wasmi/Cargo.toml b/client/executor/wasmi/Cargo.toml index 99b8010c07502..ed653dca0aaab 100644 --- a/client/executor/wasmi/Cargo.toml +++ b/client/executor/wasmi/Cargo.toml @@ -15,11 +15,12 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] log = "0.4.16" +scoped-tls = "1.0" wasmi = "0.9.1" + codec = { package = "parity-scale-codec", version = "3.0.0" } -sc-executor-common = { version = "0.10.0-dev", path = "../common" } sc-allocator = { version = "4.1.0-dev", path = "../../allocator" } -sp-wasm-interface = { version = "6.0.0", path = "../../../primitives/wasm-interface" } +sc-executor-common = { version = "0.10.0-dev", path = "../common" } +sp-sandbox = { version = "0.10.0-dev", path = "../../../primitives/sandbox" } sp-runtime-interface = { version = "6.0.0", path = "../../../primitives/runtime-interface" } -sp-core = { version = "6.0.0", path = "../../../primitives/core" } -scoped-tls = "1.0" +sp-wasm-interface = { version = "6.0.0", path = "../../../primitives/wasm-interface" } diff --git a/client/executor/wasmi/src/lib.rs b/client/executor/wasmi/src/lib.rs index 97c73c3454a4b..ec98dc0df3954 100644 --- a/client/executor/wasmi/src/lib.rs +++ b/client/executor/wasmi/src/lib.rs @@ -18,8 +18,17 @@ //! This crate provides an implementation of `WasmModule` that is baked by wasmi. -use codec::{Decode, Encode}; +use std::{cell::RefCell, rc::Rc, str, sync::Arc}; + use log::{debug, error, trace}; +use wasmi::{ + memory_units::Pages, + FuncInstance, ImportsBuilder, MemoryInstance, MemoryRef, Module, ModuleInstance, ModuleRef, + RuntimeValue::{self, I32, I64}, + TableRef, +}; + +use codec::{Decode, Encode}; use sc_executor_common::{ error::{Error, MessageWithBacktrace, WasmError}, runtime_blob::{DataSegmentsSnapshot, RuntimeBlob}, @@ -27,18 +36,11 @@ use sc_executor_common::{ util::MemoryTransfer, wasm_runtime::{InvokeMethod, WasmInstance, WasmModule}, }; -use sp_core::sandbox as sandbox_primitives; use sp_runtime_interface::unpack_ptr_and_len; +use sp_sandbox::env as sandbox_env; use sp_wasm_interface::{ Function, FunctionContext, MemoryId, Pointer, Result as WResult, Sandbox, WordSize, }; -use std::{cell::RefCell, rc::Rc, str, sync::Arc}; -use wasmi::{ - memory_units::Pages, - FuncInstance, ImportsBuilder, MemoryInstance, MemoryRef, Module, ModuleInstance, ModuleRef, - RuntimeValue::{self, I32, I64}, - TableRef, -}; struct FunctionExecutor { sandbox_store: Rc>>, @@ -155,15 +157,15 @@ impl Sandbox for FunctionExecutor { let len = buf_len as usize; let buffer = match sandboxed_memory.read(Pointer::new(offset as u32), len) { - Err(_) => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS), + Err(_) => return Ok(sandbox_env::ERR_OUT_OF_BOUNDS), Ok(buffer) => buffer, }; if let Err(_) = self.memory.set(buf_ptr.into(), &buffer) { - return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS) + return Ok(sandbox_env::ERR_OUT_OF_BOUNDS) } - Ok(sandbox_primitives::ERR_OK) + Ok(sandbox_env::ERR_OK) } fn memory_set( @@ -179,15 +181,15 @@ impl Sandbox for FunctionExecutor { let len = val_len as usize; let buffer = match self.memory.get(val_ptr.into(), len) { - Err(_) => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS), + Err(_) => return Ok(sandbox_env::ERR_OUT_OF_BOUNDS), Ok(buffer) => buffer, }; if let Err(_) = sandboxed_memory.write_from(Pointer::new(offset as u32), &buffer) { - return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS) + return Ok(sandbox_env::ERR_OUT_OF_BOUNDS) } - Ok(sandbox_primitives::ERR_OK) + Ok(sandbox_env::ERR_OK) } fn memory_teardown(&mut self, memory_id: MemoryId) -> WResult<()> { @@ -236,7 +238,7 @@ impl Sandbox for FunctionExecutor { state, &mut SandboxContext { dispatch_thunk, executor: self }, ) { - Ok(None) => Ok(sandbox_primitives::ERR_OK), + Ok(None) => Ok(sandbox_env::ERR_OK), Ok(Some(val)) => { // Serialize return value and write it back into the memory. sp_wasm_interface::ReturnValue::Value(val.into()).using_encoded(|val| { @@ -244,10 +246,10 @@ impl Sandbox for FunctionExecutor { Err("Return value buffer is too small")?; } self.write_memory(return_val, val).map_err(|_| "Return value buffer is OOB")?; - Ok(sandbox_primitives::ERR_OK) + Ok(sandbox_env::ERR_OK) }) }, - Err(_) => Ok(sandbox_primitives::ERR_EXECUTION), + Err(_) => Ok(sandbox_env::ERR_EXECUTION), } } @@ -280,7 +282,7 @@ impl Sandbox for FunctionExecutor { let guest_env = match sandbox::GuestEnvironment::decode(&*self.sandbox_store.borrow(), raw_env_def) { Ok(guest_env) => guest_env, - Err(_) => return Ok(sandbox_primitives::ERR_MODULE as u32), + Err(_) => return Ok(sandbox_env::ERR_MODULE as u32), }; let store = self.sandbox_store.clone(); @@ -294,8 +296,8 @@ impl Sandbox for FunctionExecutor { let instance_idx_or_err_code = match result.map(|i| i.register(&mut store.borrow_mut(), dispatch_thunk)) { Ok(instance_idx) => instance_idx, - Err(sandbox::InstantiationError::StartTrapped) => sandbox_primitives::ERR_EXECUTION, - Err(_) => sandbox_primitives::ERR_MODULE, + Err(sandbox::InstantiationError::StartTrapped) => sandbox_env::ERR_EXECUTION, + Err(_) => sandbox_env::ERR_MODULE, }; Ok(instance_idx_or_err_code) diff --git a/client/executor/wasmtime/Cargo.toml b/client/executor/wasmtime/Cargo.toml index 15f3a40cd46b1..5871629f47c0a 100644 --- a/client/executor/wasmtime/Cargo.toml +++ b/client/executor/wasmtime/Cargo.toml @@ -13,22 +13,23 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -libc = "0.2.121" cfg-if = "1.0" +libc = "0.2.121" log = "0.4.16" parity-wasm = "0.42.0" +wasmtime = { version = "0.35.3", default-features = false, features = [ + "cache", + "cranelift", + "jitdump", + "parallel-compilation", +] } + codec = { package = "parity-scale-codec", version = "3.0.0" } +sc-allocator = { version = "4.1.0-dev", path = "../../allocator" } sc-executor-common = { version = "0.10.0-dev", path = "../common" } -sp-wasm-interface = { version = "6.0.0", path = "../../../primitives/wasm-interface", features = ["wasmtime"] } sp-runtime-interface = { version = "6.0.0", path = "../../../primitives/runtime-interface" } -sp-core = { version = "6.0.0", path = "../../../primitives/core" } -sc-allocator = { version = "4.1.0-dev", path = "../../allocator" } -wasmtime = { version = "0.35.3", default-features = false, features = [ - "cache", - "cranelift", - "jitdump", - "parallel-compilation", -] } +sp-sandbox = { version = "0.10.0-dev", path = "../../../primitives/sandbox" } +sp-wasm-interface = { version = "6.0.0", path = "../../../primitives/wasm-interface", features = ["wasmtime"] } [dev-dependencies] sc-runtime-test = { version = "2.0.0", path = "../runtime-test" } diff --git a/client/executor/wasmtime/src/host.rs b/client/executor/wasmtime/src/host.rs index 23deacbf93623..376eba86829a7 100644 --- a/client/executor/wasmtime/src/host.rs +++ b/client/executor/wasmtime/src/host.rs @@ -19,18 +19,20 @@ //! This module defines `HostState` and `HostContext` structs which provide logic and state //! required for execution of host. -use crate::{runtime::StoreData, util}; -use codec::{Decode, Encode}; use log::trace; +use wasmtime::{Caller, Func, Val}; + +use codec::{Decode, Encode}; use sc_allocator::FreeingBumpHeapAllocator; use sc_executor_common::{ error::Result, sandbox::{self, SupervisorFuncIndex}, util::MemoryTransfer, }; -use sp_core::sandbox as sandbox_primitives; +use sp_sandbox::env as sandbox_env; use sp_wasm_interface::{FunctionContext, MemoryId, Pointer, Sandbox, WordSize}; -use wasmtime::{Caller, Func, Val}; + +use crate::{runtime::StoreData, util}; // The sandbox store is inside of a Option>> so that we can temporarily borrow it. struct SandboxStore(Option>>); @@ -164,15 +166,15 @@ impl<'a> Sandbox for HostContext<'a> { let len = buf_len as usize; let buffer = match sandboxed_memory.read(Pointer::new(offset as u32), len) { - Err(_) => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS), + Err(_) => return Ok(sandbox_env::ERR_OUT_OF_BOUNDS), Ok(buffer) => buffer, }; if util::write_memory_from(&mut self.caller, buf_ptr, &buffer).is_err() { - return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS) + return Ok(sandbox_env::ERR_OUT_OF_BOUNDS) } - Ok(sandbox_primitives::ERR_OK) + Ok(sandbox_env::ERR_OK) } fn memory_set( @@ -187,15 +189,15 @@ impl<'a> Sandbox for HostContext<'a> { let len = val_len as usize; let buffer = match util::read_memory(&self.caller, val_ptr, len) { - Err(_) => return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS), + Err(_) => return Ok(sandbox_env::ERR_OUT_OF_BOUNDS), Ok(buffer) => buffer, }; if sandboxed_memory.write_from(Pointer::new(offset as u32), &buffer).is_err() { - return Ok(sandbox_primitives::ERR_OUT_OF_BOUNDS) + return Ok(sandbox_env::ERR_OUT_OF_BOUNDS) } - Ok(sandbox_primitives::ERR_OK) + Ok(sandbox_env::ERR_OK) } fn memory_teardown(&mut self, memory_id: MemoryId) -> sp_wasm_interface::Result<()> { @@ -236,7 +238,7 @@ impl<'a> Sandbox for HostContext<'a> { ); match result { - Ok(None) => Ok(sandbox_primitives::ERR_OK), + Ok(None) => Ok(sandbox_env::ERR_OK), Ok(Some(val)) => { // Serialize return value and write it back into the memory. sp_wasm_interface::ReturnValue::Value(val.into()).using_encoded(|val| { @@ -245,10 +247,10 @@ impl<'a> Sandbox for HostContext<'a> { } ::write_memory(self, return_val, val) .map_err(|_| "can't write return value")?; - Ok(sandbox_primitives::ERR_OK) + Ok(sandbox_env::ERR_OK) }) }, - Err(_) => Ok(sandbox_primitives::ERR_EXECUTION), + Err(_) => Ok(sandbox_env::ERR_EXECUTION), } } @@ -285,7 +287,7 @@ impl<'a> Sandbox for HostContext<'a> { let guest_env = match sandbox::GuestEnvironment::decode(&self.sandbox_store(), raw_env_def) { Ok(guest_env) => guest_env, - Err(_) => return Ok(sandbox_primitives::ERR_MODULE as u32), + Err(_) => return Ok(sandbox_env::ERR_MODULE as u32), }; let mut store = self @@ -315,8 +317,8 @@ impl<'a> Sandbox for HostContext<'a> { let instance_idx_or_err_code = match result { Ok(instance) => instance.register(&mut self.sandbox_store_mut(), dispatch_thunk), - Err(sandbox::InstantiationError::StartTrapped) => sandbox_primitives::ERR_EXECUTION, - Err(_) => sandbox_primitives::ERR_MODULE, + Err(sandbox::InstantiationError::StartTrapped) => sandbox_env::ERR_EXECUTION, + Err(_) => sandbox_env::ERR_MODULE, }; Ok(instance_idx_or_err_code as u32) diff --git a/primitives/core/src/lib.rs b/primitives/core/src/lib.rs index b7c8b69e8a0ab..0709b615cf0d4 100644 --- a/primitives/core/src/lib.rs +++ b/primitives/core/src/lib.rs @@ -62,7 +62,6 @@ pub mod hash; #[cfg(feature = "std")] mod hasher; pub mod offchain; -pub mod sandbox; pub mod sr25519; pub mod testing; #[cfg(feature = "std")] diff --git a/primitives/sandbox/Cargo.toml b/primitives/sandbox/Cargo.toml index 22d295f313703..db9301a53029b 100644 --- a/primitives/sandbox/Cargo.toml +++ b/primitives/sandbox/Cargo.toml @@ -19,28 +19,29 @@ wasmi = { version = "0.9.1", default-features = false, features = ["core"] } wasmi = "0.9.0" [dependencies] +log = { version = "0.4", default-features = false } wasmi = { version = "0.9.0", optional = true } + +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } sp-core = { version = "6.0.0", default-features = false, path = "../core" } -sp-std = { version = "4.0.0", default-features = false, path = "../std" } sp-io = { version = "6.0.0", default-features = false, path = "../io" } +sp-std = { version = "4.0.0", default-features = false, path = "../std" } sp-wasm-interface = { version = "6.0.0", default-features = false, path = "../wasm-interface" } -codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } -log = { version = "0.4", default-features = false } [dev-dependencies] -wat = "1.0" assert_matches = "1.3.0" +wat = "1.0" [features] default = ["std"] std = [ + "log/std", "wasmi", - "sp-core/std", - "sp-std/std", "codec/std", + "sp-core/std", "sp-io/std", + "sp-std/std", "sp-wasm-interface/std", - "log/std", ] strict = [] wasmer-sandbox = [] diff --git a/primitives/sandbox/src/embedded_executor.rs b/primitives/sandbox/src/embedded_executor.rs old mode 100755 new mode 100644 index 43967a0a38987..8a20cc1b39b54 --- a/primitives/sandbox/src/embedded_executor.rs +++ b/primitives/sandbox/src/embedded_executor.rs @@ -17,18 +17,20 @@ //! An embedded WASM executor utilizing `wasmi`. -use super::{Error, HostError, HostFuncType, ReturnValue, Value, TARGET}; use alloc::string::String; -use log::debug; -use sp_std::{ - borrow::ToOwned, collections::btree_map::BTreeMap, fmt, marker::PhantomData, prelude::*, -}; + use wasmi::{ memory_units::Pages, Externals, FuncInstance, FuncRef, GlobalDescriptor, GlobalRef, ImportResolver, MemoryDescriptor, MemoryInstance, MemoryRef, Module, ModuleInstance, ModuleRef, RuntimeArgs, RuntimeValue, Signature, TableDescriptor, TableRef, Trap, TrapKind, }; +use sp_std::{ + borrow::ToOwned, collections::btree_map::BTreeMap, fmt, marker::PhantomData, prelude::*, +}; + +use crate::{Error, HostError, HostFuncType, ReturnValue, Value, TARGET}; + /// The linear memory used by the sandbox. #[derive(Clone)] pub struct Memory { @@ -162,13 +164,18 @@ impl ImportResolver for EnvironmentDefinitionBuilder { ) -> Result { let key = (module_name.as_bytes().to_owned(), field_name.as_bytes().to_owned()); let externval = self.map.get(&key).ok_or_else(|| { - debug!(target: TARGET, "Export {}:{} not found", module_name, field_name); + log::debug!(target: TARGET, "Export {}:{} not found", module_name, field_name); wasmi::Error::Instantiation(String::new()) })?; let host_func_idx = match *externval { ExternVal::HostFunc(ref idx) => idx, _ => { - debug!(target: TARGET, "Export {}:{} is not a host func", module_name, field_name); + log::debug!( + target: TARGET, + "Export {}:{} is not a host func", + module_name, + field_name, + ); return Err(wasmi::Error::Instantiation(String::new())) }, }; @@ -181,7 +188,7 @@ impl ImportResolver for EnvironmentDefinitionBuilder { _field_name: &str, _global_type: &GlobalDescriptor, ) -> Result { - debug!(target: TARGET, "Importing globals is not supported yet"); + log::debug!(target: TARGET, "Importing globals is not supported yet"); Err(wasmi::Error::Instantiation(String::new())) } @@ -193,13 +200,18 @@ impl ImportResolver for EnvironmentDefinitionBuilder { ) -> Result { let key = (module_name.as_bytes().to_owned(), field_name.as_bytes().to_owned()); let externval = self.map.get(&key).ok_or_else(|| { - debug!(target: TARGET, "Export {}:{} not found", module_name, field_name); + log::debug!(target: TARGET, "Export {}:{} not found", module_name, field_name); wasmi::Error::Instantiation(String::new()) })?; let memory = match *externval { ExternVal::Memory(ref m) => m, _ => { - debug!(target: TARGET, "Export {}:{} is not a memory", module_name, field_name); + log::debug!( + target: TARGET, + "Export {}:{} is not a memory", + module_name, + field_name, + ); return Err(wasmi::Error::Instantiation(String::new())) }, }; @@ -212,7 +224,7 @@ impl ImportResolver for EnvironmentDefinitionBuilder { _field_name: &str, _table_type: &TableDescriptor, ) -> Result { - debug!("Importing tables is not supported yet"); + log::debug!("Importing tables is not supported yet"); Err(wasmi::Error::Instantiation(String::new())) } } diff --git a/primitives/core/src/sandbox.rs b/primitives/sandbox/src/env.rs similarity index 92% rename from primitives/core/src/sandbox.rs rename to primitives/sandbox/src/env.rs index 1f408a3b8cc05..94b1c5e467a9c 100644 --- a/primitives/core/src/sandbox.rs +++ b/primitives/sandbox/src/env.rs @@ -18,14 +18,16 @@ //! Definition of a sandbox environment. use codec::{Decode, Encode}; + +use sp_core::RuntimeDebug; use sp_std::vec::Vec; /// Error error that can be returned from host function. -#[derive(Encode, Decode, crate::RuntimeDebug)] +#[derive(Encode, Decode, RuntimeDebug)] pub struct HostError; /// Describes an entity to define or import into the environment. -#[derive(Clone, PartialEq, Eq, Encode, Decode, crate::RuntimeDebug)] +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)] pub enum ExternEntity { /// Function that is specified by an index in a default table of /// a module that creates the sandbox. @@ -42,7 +44,7 @@ pub enum ExternEntity { /// /// Each entry has a two-level name and description of an entity /// being defined. -#[derive(Clone, PartialEq, Eq, Encode, Decode, crate::RuntimeDebug)] +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)] pub struct Entry { /// Module name of which corresponding entity being defined. pub module_name: Vec, @@ -53,7 +55,7 @@ pub struct Entry { } /// Definition of runtime that could be used by sandboxed code. -#[derive(Clone, PartialEq, Eq, Encode, Decode, crate::RuntimeDebug)] +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)] pub struct EnvironmentDefinition { /// Vector of all entries in the environment definition. pub entries: Vec, diff --git a/primitives/sandbox/src/host_executor.rs b/primitives/sandbox/src/host_executor.rs old mode 100755 new mode 100644 index 83721f40e3d93..e62c051262ca8 --- a/primitives/sandbox/src/host_executor.rs +++ b/primitives/sandbox/src/host_executor.rs @@ -17,12 +17,13 @@ //! A WASM executor utilizing the sandbox runtime interface of the host. -use super::{Error, HostFuncType, ReturnValue, Value}; use codec::{Decode, Encode}; -use sp_core::sandbox as sandbox_primitives; + use sp_io::sandbox; use sp_std::{marker, mem, prelude::*, rc::Rc, slice, vec}; +use crate::{env, Error, HostFuncType, ReturnValue, Value}; + mod ffi { use super::HostFuncType; use sp_std::mem; @@ -68,11 +69,10 @@ pub struct Memory { impl super::SandboxMemory for Memory { fn new(initial: u32, maximum: Option) -> Result { - let maximum = - if let Some(maximum) = maximum { maximum } else { sandbox_primitives::MEM_UNLIMITED }; + let maximum = if let Some(maximum) = maximum { maximum } else { env::MEM_UNLIMITED }; match sandbox::memory_new(initial, maximum) { - sandbox_primitives::ERR_MODULE => Err(Error::Module), + env::ERR_MODULE => Err(Error::Module), memory_idx => Ok(Memory { handle: Rc::new(MemoryHandle { memory_idx }) }), } } @@ -81,8 +81,8 @@ impl super::SandboxMemory for Memory { let result = sandbox::memory_get(self.handle.memory_idx, offset, buf.as_mut_ptr(), buf.len() as u32); match result { - sandbox_primitives::ERR_OK => Ok(()), - sandbox_primitives::ERR_OUT_OF_BOUNDS => Err(Error::OutOfBounds), + env::ERR_OK => Ok(()), + env::ERR_OUT_OF_BOUNDS => Err(Error::OutOfBounds), _ => unreachable!(), } } @@ -95,8 +95,8 @@ impl super::SandboxMemory for Memory { val.len() as u32, ); match result { - sandbox_primitives::ERR_OK => Ok(()), - sandbox_primitives::ERR_OUT_OF_BOUNDS => Err(Error::OutOfBounds), + env::ERR_OK => Ok(()), + env::ERR_OUT_OF_BOUNDS => Err(Error::OutOfBounds), _ => unreachable!(), } } @@ -104,22 +104,18 @@ impl super::SandboxMemory for Memory { /// A builder for the environment of the sandboxed WASM module. pub struct EnvironmentDefinitionBuilder { - env_def: sandbox_primitives::EnvironmentDefinition, + env_def: env::EnvironmentDefinition, retained_memories: Vec, _marker: marker::PhantomData, } impl EnvironmentDefinitionBuilder { - fn add_entry( - &mut self, - module: N1, - field: N2, - extern_entity: sandbox_primitives::ExternEntity, - ) where + fn add_entry(&mut self, module: N1, field: N2, extern_entity: env::ExternEntity) + where N1: Into>, N2: Into>, { - let entry = sandbox_primitives::Entry { + let entry = env::Entry { module_name: module.into(), field_name: field.into(), entity: extern_entity, @@ -131,7 +127,7 @@ impl EnvironmentDefinitionBuilder { impl super::SandboxEnvironmentBuilder for EnvironmentDefinitionBuilder { fn new() -> EnvironmentDefinitionBuilder { EnvironmentDefinitionBuilder { - env_def: sandbox_primitives::EnvironmentDefinition { entries: Vec::new() }, + env_def: env::EnvironmentDefinition { entries: Vec::new() }, retained_memories: Vec::new(), _marker: marker::PhantomData::, } @@ -142,7 +138,7 @@ impl super::SandboxEnvironmentBuilder for EnvironmentDefinitionBui N1: Into>, N2: Into>, { - let f = sandbox_primitives::ExternEntity::Function(f as u32); + let f = env::ExternEntity::Function(f as u32); self.add_entry(module, field, f); } @@ -154,7 +150,7 @@ impl super::SandboxEnvironmentBuilder for EnvironmentDefinitionBui // We need to retain memory to keep it alive while the EnvironmentDefinitionBuilder alive. self.retained_memories.push(mem.clone()); - let mem = sandbox_primitives::ExternEntity::Memory(mem.handle.memory_idx as u32); + let mem = env::ExternEntity::Memory(mem.handle.memory_idx as u32); self.add_entry(module, field, mem); } } @@ -228,8 +224,8 @@ impl super::SandboxInstance for Instance { ); let instance_idx = match result { - sandbox_primitives::ERR_MODULE => return Err(Error::Module), - sandbox_primitives::ERR_EXECUTION => return Err(Error::Execution), + env::ERR_MODULE => return Err(Error::Module), + env::ERR_EXECUTION => return Err(Error::Execution), instance_idx => instance_idx, }; @@ -256,12 +252,12 @@ impl super::SandboxInstance for Instance { ); match result { - sandbox_primitives::ERR_OK => { + env::ERR_OK => { let return_val = ReturnValue::decode(&mut &return_val[..]).map_err(|_| Error::Execution)?; Ok(return_val) }, - sandbox_primitives::ERR_EXECUTION => Err(Error::Execution), + env::ERR_EXECUTION => Err(Error::Execution), _ => unreachable!(), } } diff --git a/primitives/sandbox/src/lib.rs b/primitives/sandbox/src/lib.rs old mode 100755 new mode 100644 index 537c7cbb31b6d..b6b4a5a97da8c --- a/primitives/sandbox/src/lib.rs +++ b/primitives/sandbox/src/lib.rs @@ -40,26 +40,27 @@ extern crate alloc; -use sp_std::prelude::*; - -pub use sp_core::sandbox::HostError; -pub use sp_wasm_interface::{ReturnValue, Value}; - -/// The target used for logging. -const TARGET: &str = "runtime::sandbox"; - pub mod embedded_executor; +pub mod env; #[cfg(not(feature = "std"))] pub mod host_executor; -#[cfg(all(feature = "wasmer-sandbox", not(feature = "std")))] -pub use host_executor as default_executor; +use sp_core::RuntimeDebug; +use sp_std::prelude::*; + +pub use sp_wasm_interface::{ReturnValue, Value}; #[cfg(not(all(feature = "wasmer-sandbox", not(feature = "std"))))] -pub use embedded_executor as default_executor; +pub use self::embedded_executor as default_executor; +pub use self::env::HostError; +#[cfg(all(feature = "wasmer-sandbox", not(feature = "std")))] +pub use self::host_executor as default_executor; + +/// The target used for logging. +const TARGET: &str = "runtime::sandbox"; /// Error that can occur while using this crate. -#[derive(sp_core::RuntimeDebug)] +#[derive(RuntimeDebug)] pub enum Error { /// Module is not valid, couldn't be instantiated. Module, From be3bca09d1b3ff7ce7f343af5eadc7fed8dd38c1 Mon Sep 17 00:00:00 2001 From: Sergejs Kostjucenko <85877331+sergejparity@users.noreply.github.com> Date: Tue, 26 Apr 2022 12:28:18 +0300 Subject: [PATCH 151/484] Change ci vars to use group one's (#11281) --- .gitlab-ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1c3e81ca71f3d..c05d528b2b091 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -599,7 +599,7 @@ build-rustdoc: - echo "${PRODUCT} version = ${VERSION}" - test -z "${VERSION}" && exit 1 script: - - test "$DOCKER_HUB_USER" -a "$DOCKER_HUB_PASS" || + - test "$Docker_Hub_User_Parity" -a "$Docker_Hub_Pass_Parity" || ( echo "no docker credentials provided"; exit 1 ) - buildah bud --format=docker @@ -608,8 +608,8 @@ build-rustdoc: --tag "$IMAGE_NAME:$VERSION" --tag "$IMAGE_NAME:latest" --file "$DOCKERFILE" . - - echo "$DOCKER_HUB_PASS" | - buildah login --username "$DOCKER_HUB_USER" --password-stdin docker.io + - echo "$Docker_Hub_Pass_Parity" | + buildah login --username "$Docker_Hub_User_Parity" --password-stdin docker.io - buildah info - buildah push --format=v2s2 "$IMAGE_NAME:$VERSION" - buildah push --format=v2s2 "$IMAGE_NAME:latest" From bcef0cff58550868c9b12cd277064feb8a77d42d Mon Sep 17 00:00:00 2001 From: Ayevbeosa Iyamu Date: Tue, 26 Apr 2022 12:57:55 +0100 Subject: [PATCH 152/484] Recovery Pallet benchmarking (#11176) * Created benchmarks * Added recovery benchmarks * benchmark for `create_recovery` * benchmark for `initiate_recovery` * benchmark for `vouch_recovery` * benchmark for `claim_recovery` * benchmark for `close_recovery` * benchmark for `remove_recovery` * benchmark for `cancel_recovered` * benchmark for `as_recovered` * Some refactoring * Some refactoring * Fix create_recovery benchmark * fix close_recovery benchmark test * fixed issues with failing tests * Update frame/recovery/src/benchmarking.rs Co-authored-by: Oliver Tale-Yazdi * removed repetitive code * create weights file * Used weights to annotate extrinsics * Added WeightInfo implementation to mock * Beauty fixes Signed-off-by: Oliver Tale-Yazdi * Update frame/recovery/src/benchmarking.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Update frame/recovery/src/benchmarking.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Set vec to be mut * set delay_period to use non-zero amount * set delay default * Add weights Signed-off-by: Oliver Tale-Yazdi Co-authored-by: Oliver Tale-Yazdi Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- Cargo.lock | 1 + bin/node/runtime/Cargo.toml | 1 + bin/node/runtime/src/lib.rs | 2 + frame/recovery/Cargo.toml | 8 + frame/recovery/src/benchmarking.rs | 371 +++++++++++++++++++++++++++++ frame/recovery/src/lib.rs | 114 ++------- frame/recovery/src/mock.rs | 5 +- frame/recovery/src/tests.rs | 21 +- frame/recovery/src/weights.rs | 203 ++++++++++++++++ 9 files changed, 626 insertions(+), 100 deletions(-) create mode 100644 frame/recovery/src/benchmarking.rs create mode 100644 frame/recovery/src/weights.rs diff --git a/Cargo.lock b/Cargo.lock index d58c66096848c..397f1f038c5d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6276,6 +6276,7 @@ dependencies = [ name = "pallet-recovery" version = "4.0.0-dev" dependencies = [ + "frame-benchmarking", "frame-support", "frame-system", "pallet-balances", diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 19d911a16552d..847530e5c61bc 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -215,6 +215,7 @@ runtime-benchmarks = [ "pallet-proxy/runtime-benchmarks", "pallet-scheduler/runtime-benchmarks", "pallet-referenda/runtime-benchmarks", + "pallet-recovery/runtime-benchmarks", "pallet-remark/runtime-benchmarks", "pallet-session-benchmarking", "pallet-society/runtime-benchmarks", diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index c88dc8d731d03..f073482d887bd 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1213,6 +1213,7 @@ parameter_types! { impl pallet_recovery::Config for Runtime { type Event = Event; + type WeightInfo = pallet_recovery::weights::SubstrateWeight; type Call = Call; type Currency = Balances; type ConfigDepositBase = ConfigDepositBase; @@ -1557,6 +1558,7 @@ mod benches { [pallet_preimage, Preimage] [pallet_proxy, Proxy] [pallet_referenda, Referenda] + [pallet_recovery, Recovery] [pallet_remark, Remark] [pallet_scheduler, Scheduler] [pallet_session, SessionBench::] diff --git a/frame/recovery/Cargo.toml b/frame/recovery/Cargo.toml index 0a173fe2c9dab..98dbfc0eb0d20 100644 --- a/frame/recovery/Cargo.toml +++ b/frame/recovery/Cargo.toml @@ -18,6 +18,7 @@ scale-info = { version = "2.0.1", default-features = false, features = ["derive" sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } @@ -27,6 +28,12 @@ pallet-balances = { version = "4.0.0-dev", path = "../balances" } [features] default = ["std"] +runtime-benchmarks = [ + 'frame-benchmarking', + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] std = [ "codec/std", "scale-info/std", @@ -35,5 +42,6 @@ std = [ "sp-runtime/std", "frame-support/std", "frame-system/std", + "frame-benchmarking/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/recovery/src/benchmarking.rs b/frame/recovery/src/benchmarking.rs new file mode 100644 index 0000000000000..5354de6d10b51 --- /dev/null +++ b/frame/recovery/src/benchmarking.rs @@ -0,0 +1,371 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; + +use crate::Pallet; +use frame_benchmarking::{account, benchmarks, whitelisted_caller}; +use frame_support::traits::{Currency, Get}; +use frame_system::RawOrigin; +use sp_runtime::traits::Bounded; + +const SEED: u32 = 0; +const DEFAULT_DELAY: u32 = 0; + +fn assert_last_event(generic_event: ::Event) { + frame_system::Pallet::::assert_last_event(generic_event.into()); +} + +fn get_total_deposit( + bounded_friends: &FriendsOf, +) -> Option<<::Currency as Currency<::AccountId>>::Balance> +{ + let friend_deposit = T::FriendDepositFactor::get() + .checked_mul(&bounded_friends.len().saturated_into()) + .unwrap(); + + T::ConfigDepositBase::get().checked_add(&friend_deposit) +} + +fn generate_friends(num: u32) -> Vec<::AccountId> { + // Create friends + let mut friends = (0..num).map(|x| account("friend", x, SEED)).collect::>(); + // Sort + friends.sort(); + + for friend in 0..friends.len() { + // Top up accounts of friends + T::Currency::make_free_balance_be( + &friends.get(friend).unwrap(), + BalanceOf::::max_value(), + ); + } + + friends +} + +fn add_caller_and_generate_friends( + caller: T::AccountId, + num: u32, +) -> Vec<::AccountId> { + // Create friends + let mut friends = generate_friends::(num - 1); + + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + + friends.push(caller); + + // Sort + friends.sort(); + + friends +} + +fn insert_recovery_account(caller: &T::AccountId, account: &T::AccountId) { + T::Currency::make_free_balance_be(&account, BalanceOf::::max_value()); + + let n = T::MaxFriends::get(); + + let friends = generate_friends::(n); + + let bounded_friends: FriendsOf = friends.try_into().unwrap(); + + // Get deposit for recovery + let total_deposit = get_total_deposit::(&bounded_friends).unwrap(); + + let recovery_config = RecoveryConfig { + delay_period: DEFAULT_DELAY.into(), + deposit: total_deposit, + friends: bounded_friends, + threshold: n as u16, + }; + + // Reserve deposit for recovery + T::Currency::reserve(&caller, total_deposit).unwrap(); + + >::insert(&account, recovery_config); +} + +benchmarks! { + as_recovered { + let caller: T::AccountId = whitelisted_caller(); + let recovered_account: T::AccountId = account("recovered_account", 0, SEED); + let call: ::Call = frame_system::Call::::remark { remark: vec![] }.into(); + + Proxy::::insert(&caller, &recovered_account); + }: _( + RawOrigin::Signed(caller), + recovered_account, + Box::new(call) + ) + + set_recovered { + let lost: T::AccountId = whitelisted_caller(); + let rescuer: T::AccountId = whitelisted_caller(); + }: _( + RawOrigin::Root, + lost.clone(), + rescuer.clone() + ) verify { + assert_last_event::( + Event::AccountRecovered { + lost_account: lost, + rescuer_account: rescuer, + }.into() + ); + } + + create_recovery { + let n in 1 .. T::MaxFriends::get(); + + let caller: T::AccountId = whitelisted_caller(); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + + // Create friends + let friends = generate_friends::(n); + }: _( + RawOrigin::Signed(caller.clone()), + friends, + n as u16, + DEFAULT_DELAY.into() + ) verify { + assert_last_event::(Event::RecoveryCreated { account: caller }.into()); + } + + initiate_recovery { + let caller: T::AccountId = whitelisted_caller(); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + + let lost_account: T::AccountId = account("lost_account", 0, SEED); + + insert_recovery_account::(&caller, &lost_account); + }: _( + RawOrigin::Signed(caller.clone()), + lost_account.clone() + ) verify { + assert_last_event::( + Event::RecoveryInitiated { + lost_account: lost_account, + rescuer_account: caller, + }.into() + ); + } + + vouch_recovery { + let n in 1 .. T::MaxFriends::get(); + + let caller: T::AccountId = whitelisted_caller(); + let lost_account: T::AccountId = account("lost_account", 0, SEED); + let rescuer_account: T::AccountId = account("rescuer_account", 0, SEED); + + // Create friends + let friends = add_caller_and_generate_friends::(caller.clone(), n); + let bounded_friends: FriendsOf = friends.try_into().unwrap(); + + // Get deposit for recovery + let total_deposit = get_total_deposit::(&bounded_friends).unwrap(); + + let recovery_config = RecoveryConfig { + delay_period: DEFAULT_DELAY.into(), + deposit: total_deposit.clone(), + friends: bounded_friends.clone(), + threshold: n as u16, + }; + + // Create the recovery config storage item + >::insert(&lost_account, recovery_config.clone()); + + // Reserve deposit for recovery + T::Currency::reserve(&caller, total_deposit).unwrap(); + + // Create an active recovery status + let recovery_status = ActiveRecovery { + created: DEFAULT_DELAY.into(), + deposit: total_deposit, + friends: generate_friends::(n - 1).try_into().unwrap(), + }; + + // Create the active recovery storage item + >::insert(&lost_account, &rescuer_account, recovery_status); + + }: _( + RawOrigin::Signed(caller.clone()), + lost_account.clone(), + rescuer_account.clone() + ) verify { + assert_last_event::( + Event::RecoveryVouched { + lost_account: lost_account, + rescuer_account: rescuer_account, + sender: caller, + }.into() + ); + } + + claim_recovery { + let n in 1 .. T::MaxFriends::get(); + + let caller: T::AccountId = whitelisted_caller(); + let lost_account: T::AccountId = account("lost_account", 0, SEED); + + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + + // Create friends + let friends = generate_friends::(n); + let bounded_friends: FriendsOf = friends.try_into().unwrap(); + + // Get deposit for recovery + let total_deposit = get_total_deposit::(&bounded_friends).unwrap(); + + let recovery_config = RecoveryConfig { + delay_period: 0u32.into(), + deposit: total_deposit.clone(), + friends: bounded_friends.clone(), + threshold: n as u16, + }; + + // Create the recovery config storage item + >::insert(&lost_account, recovery_config.clone()); + + // Reserve deposit for recovery + T::Currency::reserve(&caller, total_deposit).unwrap(); + + // Create an active recovery status + let recovery_status = ActiveRecovery { + created: 0u32.into(), + deposit: total_deposit, + friends: bounded_friends.clone(), + }; + + // Create the active recovery storage item + >::insert(&lost_account, &caller, recovery_status); + }: _( + RawOrigin::Signed(caller.clone()), + lost_account.clone() + ) verify { + assert_last_event::( + Event::AccountRecovered { + lost_account: lost_account, + rescuer_account: caller, + }.into() + ); + } + + close_recovery { + let caller: T::AccountId = whitelisted_caller(); + let rescuer_account: T::AccountId = account("rescuer_account", 0, SEED); + + let n in 1 .. T::MaxFriends::get(); + + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + T::Currency::make_free_balance_be(&rescuer_account, BalanceOf::::max_value()); + + // Create friends + let friends = generate_friends::(n); + let bounded_friends: FriendsOf = friends.try_into().unwrap(); + + // Get deposit for recovery + let total_deposit = get_total_deposit::(&bounded_friends).unwrap(); + + let recovery_config = RecoveryConfig { + delay_period: DEFAULT_DELAY.into(), + deposit: total_deposit.clone(), + friends: bounded_friends.clone(), + threshold: n as u16, + }; + + // Create the recovery config storage item + >::insert(&caller, recovery_config.clone()); + + // Reserve deposit for recovery + T::Currency::reserve(&caller, total_deposit).unwrap(); + + // Create an active recovery status + let recovery_status = ActiveRecovery { + created: DEFAULT_DELAY.into(), + deposit: total_deposit, + friends: bounded_friends.clone(), + }; + + // Create the active recovery storage item + >::insert(&caller, &rescuer_account, recovery_status); + }: _( + RawOrigin::Signed(caller.clone()), + rescuer_account.clone() + ) verify { + assert_last_event::( + Event::RecoveryClosed { + lost_account: caller, + rescuer_account: rescuer_account, + }.into() + ); + } + + remove_recovery { + let n in 1 .. T::MaxFriends::get(); + + let caller: T::AccountId = whitelisted_caller(); + + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + + // Create friends + let friends = generate_friends::(n); + let bounded_friends: FriendsOf = friends.try_into().unwrap(); + + // Get deposit for recovery + let total_deposit = get_total_deposit::(&bounded_friends).unwrap(); + + let recovery_config = RecoveryConfig { + delay_period: DEFAULT_DELAY.into(), + deposit: total_deposit.clone(), + friends: bounded_friends.clone(), + threshold: n as u16, + }; + + // Create the recovery config storage item + >::insert(&caller, recovery_config); + + // Reserve deposit for recovery + T::Currency::reserve(&caller, total_deposit).unwrap(); + }: _( + RawOrigin::Signed(caller.clone()) + ) verify { + assert_last_event::( + Event::RecoveryRemoved { + lost_account: caller + }.into() + ); + } + + cancel_recovered { + let caller: T::AccountId = whitelisted_caller(); + let account: T::AccountId = account("account", 0, SEED); + + frame_system::Pallet::::inc_providers(&caller); + + frame_system::Pallet::::inc_consumers(&caller)?; + + Proxy::::insert(&caller, &account); + }: _( + RawOrigin::Signed(caller), + account + ) + + impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/frame/recovery/src/lib.rs b/frame/recovery/src/lib.rs index adc5c0b895c5f..e75c9025760b0 100644 --- a/frame/recovery/src/lib.rs +++ b/frame/recovery/src/lib.rs @@ -167,11 +167,16 @@ use frame_support::{ }; pub use pallet::*; +pub use weights::WeightInfo; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; #[cfg(test)] mod mock; #[cfg(test)] mod tests; +pub mod weights; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; @@ -222,10 +227,14 @@ pub mod pallet { /// The overarching event type. type Event: From> + IsType<::Event>; + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + /// The overarching call type. type Call: Parameter + Dispatchable - + GetDispatchInfo; + + GetDispatchInfo + + From>; /// The currency mechanism. type Currency: ReservableCurrency; @@ -365,21 +374,12 @@ pub mod pallet { /// Parameters: /// - `account`: The recovered account you want to make a call on-behalf-of. /// - `call`: The call you want to make with the recovered account. - /// - /// # - /// - The weight of the `call` + 10,000. - /// - One storage lookup to check account is recovered by `who`. O(1) - /// # #[pallet::weight({ let dispatch_info = call.get_dispatch_info(); ( - dispatch_info.weight - .saturating_add(10_000) - // AccountData for inner call origin accountdata. - .saturating_add(T::DbWeight::get().reads_writes(1, 1)), + T::WeightInfo::as_recovered().saturating_add(dispatch_info.weight), dispatch_info.class, - ) - })] + )})] pub fn as_recovered( origin: OriginFor, account: T::AccountId, @@ -402,12 +402,7 @@ pub mod pallet { /// Parameters: /// - `lost`: The "lost account" to be recovered. /// - `rescuer`: The "rescuer account" which can call as the lost account. - /// - /// # - /// - One storage write O(1) - /// - One event - /// # - #[pallet::weight(30_000_000)] + #[pallet::weight(T::WeightInfo::set_recovered())] pub fn set_recovered( origin: OriginFor, lost: T::AccountId, @@ -439,18 +434,7 @@ pub mod pallet { /// friends. /// - `delay_period`: The number of blocks after a recovery attempt is initialized that /// needs to pass before the account can be recovered. - /// - /// # - /// - Key: F (len of friends) - /// - One storage read to check that account is not already recoverable. O(1). - /// - A check that the friends list is sorted and unique. O(F) - /// - One currency reserve operation. O(X) - /// - One storage write. O(1). Codec O(F). - /// - One event. - /// - /// Total Complexity: O(F + X) - /// # - #[pallet::weight(100_000_000)] + #[pallet::weight(T::WeightInfo::create_recovery(friends.len() as u32))] pub fn create_recovery( origin: OriginFor, friends: Vec, @@ -501,18 +485,7 @@ pub mod pallet { /// Parameters: /// - `account`: The lost account that you want to recover. This account needs to be /// recoverable (i.e. have a recovery configuration). - /// - /// # - /// - One storage read to check that account is recoverable. O(F) - /// - One storage read to check that this recovery process hasn't already started. O(1) - /// - One currency reserve operation. O(X) - /// - One storage read to get the current block number. O(1) - /// - One storage write. O(1). - /// - One event. - /// - /// Total Complexity: O(F + X) - /// # - #[pallet::weight(100_000_000)] + #[pallet::weight(T::WeightInfo::initiate_recovery())] pub fn initiate_recovery(origin: OriginFor, account: T::AccountId) -> DispatchResult { let who = ensure_signed(origin)?; // Check that the account is recoverable @@ -552,19 +525,7 @@ pub mod pallet { /// /// The combination of these two parameters must point to an active recovery /// process. - /// - /// # - /// Key: F (len of friends in config), V (len of vouching friends) - /// - One storage read to get the recovery configuration. O(1), Codec O(F) - /// - One storage read to get the active recovery process. O(1), Codec O(V) - /// - One binary search to confirm caller is a friend. O(logF) - /// - One binary search to confirm caller has not already vouched. O(logV) - /// - One storage write. O(1), Codec O(V). - /// - One event. - /// - /// Total Complexity: O(F + logF + V + logV) - /// # - #[pallet::weight(100_000_000)] + #[pallet::weight(T::WeightInfo::vouch_recovery(T::MaxFriends::get()))] pub fn vouch_recovery( origin: OriginFor, lost: T::AccountId, @@ -605,18 +566,7 @@ pub mod pallet { /// Parameters: /// - `account`: The lost account that you want to claim has been successfully recovered by /// you. - /// - /// # - /// Key: F (len of friends in config), V (len of vouching friends) - /// - One storage read to get the recovery configuration. O(1), Codec O(F) - /// - One storage read to get the active recovery process. O(1), Codec O(V) - /// - One storage read to get the current block number. O(1) - /// - One storage write. O(1), Codec O(V). - /// - One event. - /// - /// Total Complexity: O(F + V) - /// # - #[pallet::weight(100_000_000)] + #[pallet::weight(T::WeightInfo::claim_recovery(T::MaxFriends::get()))] pub fn claim_recovery(origin: OriginFor, account: T::AccountId) -> DispatchResult { let who = ensure_signed(origin)?; // Get the recovery configuration for the lost account @@ -659,16 +609,7 @@ pub mod pallet { /// /// Parameters: /// - `rescuer`: The account trying to rescue this recoverable account. - /// - /// # - /// Key: V (len of vouching friends) - /// - One storage read/remove to get the active recovery process. O(1), Codec O(V) - /// - One balance call to repatriate reserved. O(X) - /// - One event. - /// - /// Total Complexity: O(V + X) - /// # - #[pallet::weight(30_000_000)] + #[pallet::weight(T::WeightInfo::close_recovery(T::MaxFriends::get()))] pub fn close_recovery(origin: OriginFor, rescuer: T::AccountId) -> DispatchResult { let who = ensure_signed(origin)?; // Take the active recovery process started by the rescuer for this account. @@ -701,17 +642,7 @@ pub mod pallet { /// /// The dispatch origin for this call must be _Signed_ and must be a /// recoverable account (i.e. has a recovery configuration). - /// - /// # - /// Key: F (len of friends) - /// - One storage read to get the prefix iterator for active recoveries. O(1) - /// - One storage read/remove to get the recovery configuration. O(1), Codec O(F) - /// - One balance call to unreserved. O(X) - /// - One event. - /// - /// Total Complexity: O(F + X) - /// # - #[pallet::weight(30_000_000)] + #[pallet::weight(T::WeightInfo::remove_recovery(T::MaxFriends::get()))] pub fn remove_recovery(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; // Check there are no active recoveries @@ -733,16 +664,13 @@ pub mod pallet { /// /// Parameters: /// - `account`: The recovered account you are able to call on-behalf-of. - /// - /// # - /// - One storage mutation to check account is recovered by `who`. O(1) - /// # - #[pallet::weight(30_000_000)] + #[pallet::weight(T::WeightInfo::cancel_recovered())] pub fn cancel_recovered(origin: OriginFor, account: T::AccountId) -> DispatchResult { let who = ensure_signed(origin)?; // Check `who` is allowed to make a call on behalf of `account` ensure!(Self::proxy(&who) == Some(account), Error::::NotAllowed); Proxy::::remove(&who); + frame_system::Pallet::::dec_consumers(&who); Ok(()) } diff --git a/frame/recovery/src/mock.rs b/frame/recovery/src/mock.rs index 2088f9eb0937e..44fc4d72a4a5f 100644 --- a/frame/recovery/src/mock.rs +++ b/frame/recovery/src/mock.rs @@ -97,15 +97,18 @@ parameter_types! { pub const ConfigDepositBase: u64 = 10; pub const FriendDepositFactor: u64 = 1; pub const RecoveryDeposit: u64 = 10; + // Large number of friends for benchmarking. + pub const MaxFriends: u32 = 128; } impl Config for Test { type Event = Event; + type WeightInfo = (); type Call = Call; type Currency = Balances; type ConfigDepositBase = ConfigDepositBase; type FriendDepositFactor = FriendDepositFactor; - type MaxFriends = ConstU32<3>; + type MaxFriends = MaxFriends; type RecoveryDeposit = RecoveryDeposit; } diff --git a/frame/recovery/src/tests.rs b/frame/recovery/src/tests.rs index 16fc678d357bb..a900a5b6bfa2a 100644 --- a/frame/recovery/src/tests.rs +++ b/frame/recovery/src/tests.rs @@ -20,7 +20,8 @@ use super::*; use frame_support::{assert_noop, assert_ok, bounded_vec, traits::Currency}; use mock::{ - new_test_ext, run_to_block, Balances, BalancesCall, Call, Origin, Recovery, RecoveryCall, Test, + new_test_ext, run_to_block, Balances, BalancesCall, Call, MaxFriends, Origin, Recovery, + RecoveryCall, Test, }; use sp_runtime::traits::BadOrigin; @@ -112,10 +113,13 @@ fn malicious_recovery_fails() { // Using account 1, the malicious user begins the recovery process on account 5 assert_ok!(Recovery::initiate_recovery(Origin::signed(1), 5)); // Off chain, the user **tricks** their friends and asks them to vouch for the recovery - assert_ok!(Recovery::vouch_recovery(Origin::signed(2), 5, 1)); // shame on you - assert_ok!(Recovery::vouch_recovery(Origin::signed(3), 5, 1)); // shame on you - assert_ok!(Recovery::vouch_recovery(Origin::signed(4), 5, 1)); // shame on you - // We met the threshold, lets try to recover the account...? + assert_ok!(Recovery::vouch_recovery(Origin::signed(2), 5, 1)); + // shame on you + assert_ok!(Recovery::vouch_recovery(Origin::signed(3), 5, 1)); + // shame on you + assert_ok!(Recovery::vouch_recovery(Origin::signed(4), 5, 1)); + // shame on you + // We met the threshold, lets try to recover the account...? assert_noop!(Recovery::claim_recovery(Origin::signed(1), 5), Error::::DelayPeriod); // Account 1 needs to wait... run_to_block(19); @@ -162,7 +166,12 @@ fn create_recovery_handles_basic_errors() { ); // Too many friends assert_noop!( - Recovery::create_recovery(Origin::signed(5), vec![1, 2, 3, 4], 4, 0), + Recovery::create_recovery( + Origin::signed(5), + vec![1; (MaxFriends::get() + 1) as usize], + 1, + 0 + ), Error::::MaxFriends ); // Unsorted friends diff --git a/frame/recovery/src/weights.rs b/frame/recovery/src/weights.rs new file mode 100644 index 0000000000000..4e5fb91ae69e1 --- /dev/null +++ b/frame/recovery/src/weights.rs @@ -0,0 +1,203 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for pallet_recovery +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-04-26, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// target/production/substrate +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet-recovery +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./frame/recovery/src/weights.rs +// --template=.maintain/frame-weight-template.hbs +// --header=HEADER-APACHE2 + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_recovery. +pub trait WeightInfo { + fn as_recovered() -> Weight; + fn set_recovered() -> Weight; + fn create_recovery(n: u32, ) -> Weight; + fn initiate_recovery() -> Weight; + fn vouch_recovery(n: u32, ) -> Weight; + fn claim_recovery(n: u32, ) -> Weight; + fn close_recovery(n: u32, ) -> Weight; + fn remove_recovery(n: u32, ) -> Weight; + fn cancel_recovered() -> Weight; +} + +/// Weights for pallet_recovery using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: Recovery Proxy (r:1 w:0) + fn as_recovered() -> Weight { + (3_667_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + } + // Storage: Recovery Proxy (r:0 w:1) + fn set_recovered() -> Weight { + (10_149_000 as Weight) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Recovery Recoverable (r:1 w:1) + fn create_recovery(n: u32, ) -> Weight { + (23_154_000 as Weight) + // Standard Error: 16_000 + .saturating_add((235_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Recovery Recoverable (r:1 w:0) + // Storage: Recovery ActiveRecoveries (r:1 w:1) + fn initiate_recovery() -> Weight { + (28_857_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Recovery Recoverable (r:1 w:0) + // Storage: Recovery ActiveRecoveries (r:1 w:1) + fn vouch_recovery(n: u32, ) -> Weight { + (17_841_000 as Weight) + // Standard Error: 10_000 + .saturating_add((349_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Recovery Recoverable (r:1 w:0) + // Storage: Recovery ActiveRecoveries (r:1 w:0) + // Storage: Recovery Proxy (r:1 w:1) + fn claim_recovery(n: u32, ) -> Weight { + (24_927_000 as Weight) + // Standard Error: 14_000 + .saturating_add((222_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Recovery ActiveRecoveries (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn close_recovery(n: u32, ) -> Weight { + (28_201_000 as Weight) + // Standard Error: 17_000 + .saturating_add((272_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: Recovery ActiveRecoveries (r:1 w:0) + // Storage: Recovery Recoverable (r:1 w:1) + fn remove_recovery(n: u32, ) -> Weight { + (27_531_000 as Weight) + // Standard Error: 10_000 + .saturating_add((218_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Recovery Proxy (r:1 w:1) + fn cancel_recovered() -> Weight { + (9_015_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: Recovery Proxy (r:1 w:0) + fn as_recovered() -> Weight { + (3_667_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + } + // Storage: Recovery Proxy (r:0 w:1) + fn set_recovered() -> Weight { + (10_149_000 as Weight) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Recovery Recoverable (r:1 w:1) + fn create_recovery(n: u32, ) -> Weight { + (23_154_000 as Weight) + // Standard Error: 16_000 + .saturating_add((235_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Recovery Recoverable (r:1 w:0) + // Storage: Recovery ActiveRecoveries (r:1 w:1) + fn initiate_recovery() -> Weight { + (28_857_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Recovery Recoverable (r:1 w:0) + // Storage: Recovery ActiveRecoveries (r:1 w:1) + fn vouch_recovery(n: u32, ) -> Weight { + (17_841_000 as Weight) + // Standard Error: 10_000 + .saturating_add((349_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Recovery Recoverable (r:1 w:0) + // Storage: Recovery ActiveRecoveries (r:1 w:0) + // Storage: Recovery Proxy (r:1 w:1) + fn claim_recovery(n: u32, ) -> Weight { + (24_927_000 as Weight) + // Standard Error: 14_000 + .saturating_add((222_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Recovery ActiveRecoveries (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn close_recovery(n: u32, ) -> Weight { + (28_201_000 as Weight) + // Standard Error: 17_000 + .saturating_add((272_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + // Storage: Recovery ActiveRecoveries (r:1 w:0) + // Storage: Recovery Recoverable (r:1 w:1) + fn remove_recovery(n: u32, ) -> Weight { + (27_531_000 as Weight) + // Standard Error: 10_000 + .saturating_add((218_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Recovery Proxy (r:1 w:1) + fn cancel_recovered() -> Weight { + (9_015_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } +} From 9448e36892091c7dce2892c8c3d88116ed818989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulo=20Silva=20de=20Souza?= <77391175+joao-paulo-parity@users.noreply.github.com> Date: Tue, 26 Apr 2022 09:14:21 -0300 Subject: [PATCH 153/484] Skip pr-custom-review if pull request is in Draft (#11248) * skip pr-custom-review if pull request is in Draft * ready_for_review * document steps[*].if * fix * converted_to_draft * fix Co-authored-by: Giles Cope --- .github/workflows/pr-custom-review.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/pr-custom-review.yml b/.github/workflows/pr-custom-review.yml index 322403da03b47..6cb16d931d6f4 100644 --- a/.github/workflows/pr-custom-review.yml +++ b/.github/workflows/pr-custom-review.yml @@ -11,12 +11,31 @@ on: - synchronize - review_requested - review_request_removed + - ready_for_review + - converted_to_draft pull_request_review: jobs: pr-custom-review: runs-on: ubuntu-latest steps: + - name: Skip if pull request is in Draft + # `if: github.event.pull_request.draft == true` should be kept here, at + # the step level, rather than at the job level. The latter is not + # recommended because when the PR is moved from "Draft" to "Ready to + # review" the workflow will immediately be passing (since it was skipped), + # even though it hasn't actually ran, since it takes a few seconds for + # the workflow to start. This is also disclosed in: + # https://github.community/t/dont-run-actions-on-draft-pull-requests/16817/17 + # That scenario would open an opportunity for the check to be bypassed: + # 1. Get your PR approved + # 2. Move it to Draft + # 3. Push whatever commits you want + # 4. Move it to "Ready for review"; now the workflow is passing (it was + # skipped) and "Check reviews" is also passing (it won't be updated + # until the workflow is finished) + if: github.event.pull_request.draft == true + run: exit 1 - name: pr-custom-review uses: paritytech/pr-custom-review@v2 with: From c887ecbe28f5397faaf3c1ae994dce703434c0f1 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 26 Apr 2022 16:31:26 +0200 Subject: [PATCH 154/484] Follow ups for `benchmark machine` (#11270) * Follow ups for the MachineCmd Signed-off-by: Oliver Tale-Yazdi * Fix CI Signed-off-by: Oliver Tale-Yazdi * Review fixes Signed-off-by: Oliver Tale-Yazdi * Add to node-template Signed-off-by: Oliver Tale-Yazdi * Fix test with feature flag Signed-off-by: Oliver Tale-Yazdi * Review fixes Signed-off-by: Oliver Tale-Yazdi * Lower disk requirements Signed-off-by: Oliver Tale-Yazdi * Add ExecutionLimit to the disk benchmarks Signed-off-by: Oliver Tale-Yazdi * fmt Signed-off-by: Oliver Tale-Yazdi * Add doc Signed-off-by: Oliver Tale-Yazdi * Review fixes Signed-off-by: Oliver Tale-Yazdi * Rename DISK_WRITE_LIMIT -> DEFAULT_DISK_EXECUTION_LIMIT Signed-off-by: Oliver Tale-Yazdi * Rename POLKADOT_REFERENCE_HARDWARE -> SUBSTRATE_REFERENCE_HARDWARE Signed-off-by: Oliver Tale-Yazdi * Fix build profile + add license Signed-off-by: Oliver Tale-Yazdi * Remove deps Signed-off-by: Oliver Tale-Yazdi * Set tolerance to 10% Signed-off-by: Oliver Tale-Yazdi * Fix tests Signed-off-by: Oliver Tale-Yazdi * Ignore test I cannot reproduce the CI error, even with the full command: cargo test --workspace --locked --release --verbose --features runtime-benchmarks --manifest-path ./bin/node/cli/Cargo.toml I will put an 'ignore' on that test for now, since it works for me and is worth having. Signed-off-by: Oliver Tale-Yazdi * Remove test Still cannot reproduce the error and it fails in the CI. Removing it now. Signed-off-by: Oliver Tale-Yazdi Co-authored-by: Shawn Tabrizi --- Cargo.lock | 2 + bin/node-template/node/src/command.rs | 5 +- bin/node/cli/Cargo.toml | 1 + bin/node/cli/src/command.rs | 3 +- bin/node/cli/tests/benchmark_machine_works.rs | 23 ++- client/sysinfo/src/lib.rs | 5 + client/sysinfo/src/sysinfo.rs | 66 ++++-- utils/frame/benchmarking-cli/Cargo.toml | 2 + utils/frame/benchmarking-cli/build.rs | 31 +++ utils/frame/benchmarking-cli/src/lib.rs | 2 +- .../benchmarking-cli/src/machine/hardware.rs | 191 ++++++++++++++++++ .../frame/benchmarking-cli/src/machine/mod.rs | 173 ++++++++++++++-- .../src/machine/reference_hardware.json | 32 +++ .../frame/benchmarking-cli/src/shared/mod.rs | 16 ++ 14 files changed, 511 insertions(+), 41 deletions(-) create mode 100644 utils/frame/benchmarking-cli/build.rs create mode 100644 utils/frame/benchmarking-cli/src/machine/hardware.rs create mode 100644 utils/frame/benchmarking-cli/src/machine/reference_hardware.json diff --git a/Cargo.lock b/Cargo.lock index 397f1f038c5d3..26954cddbd5dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2181,6 +2181,7 @@ dependencies = [ "hex", "itertools", "kvdb", + "lazy_static", "linked-hash-map", "log 0.4.16", "memory-db", @@ -2210,6 +2211,7 @@ dependencies = [ "sp-storage", "sp-trie", "tempfile", + "thiserror", "thousands", ] diff --git a/bin/node-template/node/src/command.rs b/bin/node-template/node/src/command.rs index 809257f79007c..e3e10007929e6 100644 --- a/bin/node-template/node/src/command.rs +++ b/bin/node-template/node/src/command.rs @@ -4,7 +4,7 @@ use crate::{ command_helper::{inherent_benchmark_data, BenchmarkExtrinsicBuilder}, service, }; -use frame_benchmarking_cli::BenchmarkCmd; +use frame_benchmarking_cli::{BenchmarkCmd, SUBSTRATE_REFERENCE_HARDWARE}; use node_template_runtime::Block; use sc_cli::{ChainSpec, RuntimeVersion, SubstrateCli}; use sc_service::PartialComponents; @@ -141,7 +141,8 @@ pub fn run() -> sc_cli::Result<()> { cmd.run(config, client, inherent_benchmark_data()?, Arc::new(ext_builder)) }, - BenchmarkCmd::Machine(cmd) => cmd.run(&config), + BenchmarkCmd::Machine(cmd) => + cmd.run(&config, SUBSTRATE_REFERENCE_HARDWARE.clone()), } }) }, diff --git a/bin/node/cli/Cargo.toml b/bin/node/cli/Cargo.toml index 5562efa1846e1..6bb36b9f9ab94 100644 --- a/bin/node/cli/Cargo.toml +++ b/bin/node/cli/Cargo.toml @@ -96,6 +96,7 @@ sc-cli = { version = "0.10.0-dev", optional = true, path = "../../../client/cli" frame-benchmarking-cli = { version = "4.0.0-dev", optional = true, path = "../../../utils/frame/benchmarking-cli" } node-inspect = { version = "0.9.0-dev", optional = true, path = "../inspect" } try-runtime-cli = { version = "0.10.0-dev", optional = true, path = "../../../utils/frame/try-runtime/cli" } +serde_json = "1.0.79" [target.'cfg(any(target_arch="x86_64", target_arch="aarch64"))'.dependencies] node-executor = { version = "3.0.0-dev", path = "../executor", features = ["wasmtime"] } diff --git a/bin/node/cli/src/command.rs b/bin/node/cli/src/command.rs index b98a38d2dbf5f..b17a26fa02935 100644 --- a/bin/node/cli/src/command.rs +++ b/bin/node/cli/src/command.rs @@ -130,7 +130,8 @@ pub fn run() -> Result<()> { cmd.run(config, client, inherent_benchmark_data()?, Arc::new(ext_builder)) }, - BenchmarkCmd::Machine(cmd) => cmd.run(&config), + BenchmarkCmd::Machine(cmd) => + cmd.run(&config, SUBSTRATE_REFERENCE_HARDWARE.clone()), } }) }, diff --git a/bin/node/cli/tests/benchmark_machine_works.rs b/bin/node/cli/tests/benchmark_machine_works.rs index df407e988f636..bf4a2b7b85e65 100644 --- a/bin/node/cli/tests/benchmark_machine_works.rs +++ b/bin/node/cli/tests/benchmark_machine_works.rs @@ -24,9 +24,30 @@ use std::process::Command; fn benchmark_machine_works() { let status = Command::new(cargo_bin("substrate")) .args(["benchmark", "machine", "--dev"]) - .args(["--verify-duration", "0.1"]) + .args(["--verify-duration", "0.1", "--disk-duration", "0.1"]) + // Make it succeed. + .args(["--allow-fail"]) .status() .unwrap(); assert!(status.success()); } + +/// Test that the hardware does not meet the requirements. +/// +/// This is most likely to succeed since it uses a test profile. +#[test] +#[cfg(debug_assertions)] +fn benchmark_machine_fails_with_slow_hardware() { + let output = Command::new(cargo_bin("substrate")) + .args(["benchmark", "machine", "--dev"]) + .args(["--verify-duration", "0.1", "--disk-duration", "2", "--tolerance", "0"]) + .output() + .unwrap(); + + // Command should have failed. + assert!(!output.status.success()); + // An `UnmetRequirement` error should have been printed. + let log = String::from_utf8_lossy(&output.stderr).to_string(); + assert!(log.contains("UnmetRequirement")); +} diff --git a/client/sysinfo/src/lib.rs b/client/sysinfo/src/lib.rs index 911e725dcdd4e..be63fefe9ecd1 100644 --- a/client/sysinfo/src/lib.rs +++ b/client/sysinfo/src/lib.rs @@ -66,6 +66,11 @@ pub enum ExecutionLimit { } impl ExecutionLimit { + /// Creates a new execution limit with the passed seconds as duration limit. + pub fn from_secs_f32(secs: f32) -> Self { + Self::MaxDuration(Duration::from_secs_f32(secs)) + } + /// Returns the duration limit or `MAX` if none is present. pub fn max_duration(&self) -> Duration { match self { diff --git a/client/sysinfo/src/sysinfo.rs b/client/sysinfo/src/sysinfo.rs index 65d7a9e41b406..cd6adcf623e66 100644 --- a/client/sysinfo/src/sysinfo.rs +++ b/client/sysinfo/src/sysinfo.rs @@ -241,10 +241,16 @@ fn random_data(size: usize) -> Vec { buffer } -pub fn benchmark_disk_sequential_writes(directory: &Path) -> Result { +/// A default [`ExecutionLimit`] that can be used to call [`benchmark_disk_sequential_writes`] +/// and [`benchmark_disk_random_writes`]. +pub const DEFAULT_DISK_EXECUTION_LIMIT: ExecutionLimit = + ExecutionLimit::Both { max_iterations: 32, max_duration: Duration::from_millis(300) }; + +pub fn benchmark_disk_sequential_writes( + limit: ExecutionLimit, + directory: &Path, +) -> Result { const SIZE: usize = 64 * 1024 * 1024; - const MAX_ITERATIONS: usize = 32; - const MAX_DURATION: Duration = Duration::from_millis(300); let buffer = random_data(SIZE); let path = directory.join(".disk_bench_seq_wr.tmp"); @@ -273,14 +279,21 @@ pub fn benchmark_disk_sequential_writes(directory: &Path) -> Result Ok(()) }; - benchmark("disk sequential write score", SIZE, MAX_ITERATIONS, MAX_DURATION, run) - .map(|s| s as u64) + benchmark( + "disk sequential write score", + SIZE, + limit.max_iterations(), + limit.max_duration(), + run, + ) + .map(|s| s as u64) } -pub fn benchmark_disk_random_writes(directory: &Path) -> Result { +pub fn benchmark_disk_random_writes( + limit: ExecutionLimit, + directory: &Path, +) -> Result { const SIZE: usize = 64 * 1024 * 1024; - const MAX_ITERATIONS: usize = 32; - const MAX_DURATION: Duration = Duration::from_millis(300); let buffer = random_data(SIZE); let path = directory.join(".disk_bench_rand_wr.tmp"); @@ -333,8 +346,14 @@ pub fn benchmark_disk_random_writes(directory: &Path) -> Result { }; // We only wrote half of the bytes hence `SIZE / 2`. - benchmark("disk random write score", SIZE / 2, MAX_ITERATIONS, MAX_DURATION, run) - .map(|s| s as u64) + benchmark( + "disk random write score", + SIZE / 2, + limit.max_iterations(), + limit.max_duration(), + run, + ) + .map(|s| s as u64) } /// Benchmarks the verification speed of sr25519 signatures. @@ -389,7 +408,8 @@ pub fn gather_hwbench(scratch_directory: Option<&Path>) -> HwBench { if let Some(scratch_directory) = scratch_directory { hwbench.disk_sequential_write_score = - match benchmark_disk_sequential_writes(scratch_directory) { + match benchmark_disk_sequential_writes(DEFAULT_DISK_EXECUTION_LIMIT, scratch_directory) + { Ok(score) => Some(score), Err(error) => { log::warn!("Failed to run the sequential write disk benchmark: {}", error); @@ -397,13 +417,14 @@ pub fn gather_hwbench(scratch_directory: Option<&Path>) -> HwBench { }, }; - hwbench.disk_random_write_score = match benchmark_disk_random_writes(scratch_directory) { - Ok(score) => Some(score), - Err(error) => { - log::warn!("Failed to run the random write disk benchmark: {}", error); - None - }, - }; + hwbench.disk_random_write_score = + match benchmark_disk_random_writes(DEFAULT_DISK_EXECUTION_LIMIT, scratch_directory) { + Ok(score) => Some(score), + Err(error) => { + log::warn!("Failed to run the random write disk benchmark: {}", error); + None + }, + }; } hwbench @@ -437,12 +458,17 @@ mod tests { #[test] fn test_benchmark_disk_sequential_writes() { - assert!(benchmark_disk_sequential_writes("./".as_ref()).unwrap() > 0); + assert!( + benchmark_disk_sequential_writes(DEFAULT_DISK_EXECUTION_LIMIT, "./".as_ref()).unwrap() > + 0 + ); } #[test] fn test_benchmark_disk_random_writes() { - assert!(benchmark_disk_random_writes("./".as_ref()).unwrap() > 0); + assert!( + benchmark_disk_random_writes(DEFAULT_DISK_EXECUTION_LIMIT, "./".as_ref()).unwrap() > 0 + ); } #[test] diff --git a/utils/frame/benchmarking-cli/Cargo.toml b/utils/frame/benchmarking-cli/Cargo.toml index 8a3773fb10022..208099162c52b 100644 --- a/utils/frame/benchmarking-cli/Cargo.toml +++ b/utils/frame/benchmarking-cli/Cargo.toml @@ -55,6 +55,8 @@ thousands = "0.2.0" prettytable-rs = "0.8.0" tempfile = "3.2.0" rand_pcg = "0.3.1" +lazy_static = "1.4.0" +thiserror = "1.0.30" [features] default = ["db", "sc-client-db/runtime-benchmarks"] diff --git a/utils/frame/benchmarking-cli/build.rs b/utils/frame/benchmarking-cli/build.rs new file mode 100644 index 0000000000000..4347804156815 --- /dev/null +++ b/utils/frame/benchmarking-cli/build.rs @@ -0,0 +1,31 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::env; + +/// Exposes build environment variables to the rust code. +/// +/// - The build profile as `build_profile` +/// - The optimization level as `build_opt_level` +pub fn main() { + if let Ok(opt_level) = env::var("OPT_LEVEL") { + println!("cargo:rustc-cfg=build_opt_level={:?}", opt_level); + } + if let Ok(profile) = env::var("PROFILE") { + println!("cargo:rustc-cfg=build_profile={:?}", profile); + } +} diff --git a/utils/frame/benchmarking-cli/src/lib.rs b/utils/frame/benchmarking-cli/src/lib.rs index 75e2edc042a26..d0eee3d2939fc 100644 --- a/utils/frame/benchmarking-cli/src/lib.rs +++ b/utils/frame/benchmarking-cli/src/lib.rs @@ -25,7 +25,7 @@ mod shared; mod storage; pub use block::BlockCmd; -pub use machine::MachineCmd; +pub use machine::{MachineCmd, Requirements, SUBSTRATE_REFERENCE_HARDWARE}; pub use overhead::{ExtrinsicBuilder, OverheadCmd}; pub use pallet::PalletCmd; pub use storage::StorageCmd; diff --git a/utils/frame/benchmarking-cli/src/machine/hardware.rs b/utils/frame/benchmarking-cli/src/machine/hardware.rs new file mode 100644 index 0000000000000..5c62660cc7cf4 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/machine/hardware.rs @@ -0,0 +1,191 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Contains types to define hardware requirements. + +use lazy_static::lazy_static; +use serde::{Deserialize, Serialize}; +use std::fmt; + +lazy_static! { + /// The hardware requirements as measured on reference hardware. + /// + /// These values are provided by Parity, however it is possible + /// to use your own requirements if you are running a custom chain. + /// + /// The reference hardware is describe here: + /// + pub static ref SUBSTRATE_REFERENCE_HARDWARE: Requirements = { + let raw = include_bytes!("reference_hardware.json").as_slice(); + serde_json::from_slice(raw).expect("Hardcoded data is known good; qed") + }; +} + +/// Multiple requirements for the hardware. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct Requirements(pub Vec); + +/// A single requirement for the hardware. +#[derive(Deserialize, Serialize, Debug, Clone, Copy, PartialEq)] +pub struct Requirement { + /// The metric to measure. + pub metric: Metric, + /// The minimal throughput that needs to be archived for this requirement. + pub minimum: Throughput, +} + +/// A single hardware metric. +/// +/// The implementation of these is in `sc-sysinfo`. +#[derive(Deserialize, Serialize, Debug, Clone, Copy, PartialEq)] +pub enum Metric { + /// SR25519 signature verification. + Sr25519Verify, + /// Blake2-256 hashing algorithm. + Blake2256, + /// Copying data in RAM. + MemCopy, + /// Disk sequential write. + DiskSeqWrite, + /// Disk random write. + DiskRndWrite, +} + +/// Throughput as measured in bytes per second. +#[derive(Deserialize, Serialize, Debug, Clone, Copy, PartialEq)] +pub enum Throughput { + /// KiB/s + KiBs(f64), + /// MiB/s + MiBs(f64), + /// GiB/s + GiBs(f64), +} + +impl Metric { + /// The category of the metric. + pub fn category(&self) -> &'static str { + match self { + Self::Sr25519Verify | Self::Blake2256 => "CPU", + Self::MemCopy => "Memory", + Self::DiskSeqWrite | Self::DiskRndWrite => "Disk", + } + } + + /// The name of the metric. It is always prefixed by the [`self::category()`]. + pub fn name(&self) -> &'static str { + match self { + Self::Sr25519Verify => "SR25519-Verify", + Self::Blake2256 => "BLAKE2-256", + Self::MemCopy => "Copy", + Self::DiskSeqWrite => "Seq Write", + Self::DiskRndWrite => "Rnd Write", + } + } +} + +const KIBIBYTE: f64 = 1024.0; + +impl Throughput { + /// The unit of the metric. + pub fn unit(&self) -> &'static str { + match self { + Self::KiBs(_) => "KiB/s", + Self::MiBs(_) => "MiB/s", + Self::GiBs(_) => "GiB/s", + } + } + + /// [`Self`] as number of byte/s. + pub fn to_bs(&self) -> f64 { + self.to_kibs() * KIBIBYTE + } + + /// [`Self`] as number of kibibyte/s. + pub fn to_kibs(&self) -> f64 { + self.to_mibs() * KIBIBYTE + } + + /// [`Self`] as number of mebibyte/s. + pub fn to_mibs(&self) -> f64 { + self.to_gibs() * KIBIBYTE + } + + /// [`Self`] as number of gibibyte/s. + pub fn to_gibs(&self) -> f64 { + match self { + Self::KiBs(k) => *k / (KIBIBYTE * KIBIBYTE), + Self::MiBs(m) => *m / KIBIBYTE, + Self::GiBs(g) => *g, + } + } + + /// Normalizes [`Self`] to use the larges unit possible. + pub fn normalize(&self) -> Self { + let bs = self.to_bs(); + + if bs >= KIBIBYTE * KIBIBYTE * KIBIBYTE { + Self::GiBs(self.to_gibs()) + } else if bs >= KIBIBYTE * KIBIBYTE { + Self::MiBs(self.to_mibs()) + } else { + Self::KiBs(self.to_kibs()) + } + } +} + +impl fmt::Display for Throughput { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let normalized = self.normalize(); + match normalized { + Self::KiBs(s) | Self::MiBs(s) | Self::GiBs(s) => + write!(f, "{:.2?} {}", s, normalized.unit()), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use sp_runtime::assert_eq_error_rate; + + /// `SUBSTRATE_REFERENCE_HARDWARE` can be en- and decoded. + #[test] + fn json_static_data() { + let raw = serde_json::to_string(&*SUBSTRATE_REFERENCE_HARDWARE).unwrap(); + let decoded: Requirements = serde_json::from_str(&raw).unwrap(); + + assert_eq!(decoded, SUBSTRATE_REFERENCE_HARDWARE.clone()); + } + + /// Test the [`Throughput`]. + #[test] + fn throughput_works() { + /// Float precision. + const EPS: f64 = 0.1; + let gib = Throughput::GiBs(14.324); + + assert_eq_error_rate!(14.324, gib.to_gibs(), EPS); + assert_eq_error_rate!(14667.776, gib.to_mibs(), EPS); + assert_eq_error_rate!(14667.776 * 1024.0, gib.to_kibs(), EPS); + assert_eq!("14.32 GiB/s", gib.to_string()); + assert_eq!("14.32 GiB/s", gib.normalize().to_string()); + + let mib = Throughput::MiBs(1029.0); + assert_eq!("1.00 GiB/s", mib.to_string()); + } +} diff --git a/utils/frame/benchmarking-cli/src/machine/mod.rs b/utils/frame/benchmarking-cli/src/machine/mod.rs index ee6bf765d01c4..9e25e58921d71 100644 --- a/utils/frame/benchmarking-cli/src/machine/mod.rs +++ b/utils/frame/benchmarking-cli/src/machine/mod.rs @@ -18,6 +18,8 @@ //! Contains the [`MachineCmd`] as entry point for the node //! and the core benchmarking logic. +pub mod hardware; + use sc_cli::{CliConfiguration, Result, SharedParams}; use sc_service::Configuration; use sc_sysinfo::{ @@ -26,9 +28,12 @@ use sc_sysinfo::{ }; use clap::Parser; -use log::info; +use log::{error, info, warn}; use prettytable::{cell, row, table}; -use std::{fmt::Debug, fs, time::Duration}; +use std::{boxed::Box, fmt::Debug, fs, path::Path}; + +use crate::shared::check_build_profile; +pub use hardware::{Metric, Requirement, Requirements, Throughput, SUBSTRATE_REFERENCE_HARDWARE}; /// Command to benchmark the hardware. /// @@ -44,40 +49,176 @@ pub struct MachineCmd { #[clap(flatten)] pub shared_params: SharedParams, + /// Do not return an error if any check fails. + /// + /// Should only be used for debugging. + #[clap(long)] + pub allow_fail: bool, + + /// Set a fault tolerance for passing a requirement. + /// + /// 10% means that the test would pass even when only 90% score was archived. + /// Can be used to mitigate outliers of the benchmarks. + #[clap(long, default_value = "10.0", value_name = "PERCENT")] + pub tolerance: f64, + /// Time limit for the verification benchmark. #[clap(long, default_value = "2.0", value_name = "SECONDS")] pub verify_duration: f32, + + /// Time limit for each disk benchmark. + #[clap(long, default_value = "5.0", value_name = "SECONDS")] + pub disk_duration: f32, +} + +/// Helper for the result of a concrete benchmark. +struct BenchResult { + /// Did the hardware pass the benchmark? + passed: bool, + + /// The absolute score that was archived. + score: Throughput, + + /// The score relative to the minimal required score. + /// + /// Is in range [0, 1]. + rel_score: f64, +} + +/// Errors that can be returned by the this command. +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum Error { + #[error("One of the benchmarks had a score that was lower than its requirement")] + UnmetRequirement, + + #[error("The build profile is unfit for benchmarking: {0}")] + BadBuildProfile(String), + + #[error("Benchmark results are off by at least factor 100")] + BadResults, } impl MachineCmd { /// Execute the benchmark and print the results. - pub fn run(&self, cfg: &Configuration) -> Result<()> { + pub fn run(&self, cfg: &Configuration, requirements: Requirements) -> Result<()> { + self.validate_args()?; // Ensure that the dir exists since the node is not started to take care of it. let dir = cfg.database.path().ok_or("No DB directory provided")?; fs::create_dir_all(dir)?; info!("Running machine benchmarks..."); - let write = benchmark_disk_sequential_writes(dir)?; - let read = benchmark_disk_random_writes(dir)?; - let verify_limit = - ExecutionLimit::MaxDuration(Duration::from_secs_f32(self.verify_duration)); - let verify = benchmark_sr25519_verify(verify_limit) * 1024.0; + let mut results = Vec::new(); + for requirement in &requirements.0 { + let result = self.run_benchmark(requirement, &dir)?; + results.push(result); + } + self.print_summary(requirements, results) + } + /// Benchmarks a specific metric of the hardware and judges the resulting score. + fn run_benchmark(&self, requirement: &Requirement, dir: &Path) -> Result { + // Dispatch the concrete function from `sc-sysinfo`. + let score = self.measure(&requirement.metric, dir)?; + let rel_score = score.to_bs() / requirement.minimum.to_bs(); + + // Sanity check if the result is off by factor >100x. + if rel_score >= 100.0 || rel_score <= 0.01 { + self.check_failed(Error::BadResults)?; + } + let passed = rel_score >= (1.0 - (self.tolerance / 100.0)); + Ok(BenchResult { passed, score, rel_score }) + } + + /// Measures a metric of the hardware. + fn measure(&self, metric: &Metric, dir: &Path) -> Result { + let verify_limit = ExecutionLimit::from_secs_f32(self.verify_duration); + let disk_limit = ExecutionLimit::from_secs_f32(self.disk_duration); + + let score = match metric { + Metric::Blake2256 => Throughput::MiBs(benchmark_cpu() as f64), + Metric::Sr25519Verify => Throughput::MiBs(benchmark_sr25519_verify(verify_limit)), + Metric::MemCopy => Throughput::MiBs(benchmark_memory() as f64), + Metric::DiskSeqWrite => + Throughput::MiBs(benchmark_disk_sequential_writes(disk_limit, dir)? as f64), + Metric::DiskRndWrite => + Throughput::MiBs(benchmark_disk_random_writes(disk_limit, dir)? as f64), + }; + Ok(score) + } + + /// Prints a human-readable summary. + fn print_summary(&self, requirements: Requirements, results: Vec) -> Result<()> { // Use a table for nicer console output. - let table = table!( - ["Category", "Function", "Score", "Unit"], - ["CPU", "BLAKE2-256", benchmark_cpu(), "MB/s"], - ["CPU", "SR25519 Verify", format!("{:.1}", verify), "KB/s"], - ["Memory", "Copy", benchmark_memory(), "MB/s"], - ["Disk", "Seq Write", write, "MB/s"], - ["Disk", "Rnd Write", read, "MB/s"] + let mut table = table!(["Category", "Function", "Score", "Minimum", "Result"]); + // Count how many passed and how many failed. + let (mut passed, mut failed) = (0, 0); + for (requirement, result) in requirements.0.iter().zip(results.iter()) { + if result.passed { + passed += 1 + } else { + failed += 1 + } + + table.add_row(result.to_row(requirement)); + } + // Print the table and a summary. + info!( + "\n{}\nFrom {} benchmarks in total, {} passed and {} failed ({:.0?}% fault tolerance).", + table, + passed + failed, + passed, + failed, + self.tolerance ); + // Print the final result. + if failed != 0 { + info!("The hardware fails to meet the requirements"); + self.check_failed(Error::UnmetRequirement)?; + } else { + info!("The hardware meets the requirements "); + } + // Check that the results were not created by a bad build profile. + if let Err(err) = check_build_profile() { + self.check_failed(Error::BadBuildProfile(err))?; + } + Ok(()) + } + + /// Returns `Ok` if [`self.allow_fail`] is set and otherwise the error argument. + fn check_failed(&self, e: Error) -> Result<()> { + if !self.allow_fail { + error!("Failing since --allow-fail is not set"); + Err(sc_cli::Error::Application(Box::new(e))) + } else { + warn!("Ignoring error since --allow-fail is set: {:?}", e); + Ok(()) + } + } - info!("\n{}", table); + /// Validates the CLI arguments. + fn validate_args(&self) -> Result<()> { + if self.tolerance > 100.0 || self.tolerance < 0.0 { + return Err("The --tolerance argument is out of range".into()) + } Ok(()) } } +impl BenchResult { + /// Format [`Self`] as row that can be printed in a table. + fn to_row(&self, req: &Requirement) -> prettytable::Row { + let passed = if self.passed { "✅ Pass" } else { "❌ Fail" }; + row![ + req.metric.category(), + req.metric.name(), + format!("{}", self.score), + format!("{}", req.minimum), + format!("{} ({: >5.1?} %)", passed, self.rel_score * 100.0) + ] + } +} + // Boilerplate impl CliConfiguration for MachineCmd { fn shared_params(&self) -> &SharedParams { diff --git a/utils/frame/benchmarking-cli/src/machine/reference_hardware.json b/utils/frame/benchmarking-cli/src/machine/reference_hardware.json new file mode 100644 index 0000000000000..12645df8391e7 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/machine/reference_hardware.json @@ -0,0 +1,32 @@ +[ + { + "metric": "Blake2256", + "minimum": { + "MiBs": 1029.0 + } + }, + { + "metric": "Sr25519Verify", + "minimum": { + "KiBs": 666.0 + } + }, + { + "metric": "MemCopy", + "minimum": { + "GiBs": 14.323 + } + }, + { + "metric": "DiskSeqWrite", + "minimum": { + "MiBs": 450.0 + } + }, + { + "metric": "DiskRndWrite", + "minimum": { + "MiBs": 200.0 + } + } +] diff --git a/utils/frame/benchmarking-cli/src/shared/mod.rs b/utils/frame/benchmarking-cli/src/shared/mod.rs index 853fbdef8e87f..f959c285a346e 100644 --- a/utils/frame/benchmarking-cli/src/shared/mod.rs +++ b/utils/frame/benchmarking-cli/src/shared/mod.rs @@ -73,3 +73,19 @@ pub fn new_rng(seed: Option) -> (impl rand::Rng, u64) { let seed = seed.unwrap_or(rand::thread_rng().gen::()); (rand_pcg::Pcg64::seed_from_u64(seed), seed) } + +/// Returns an error if a debug profile is detected. +/// +/// The rust compiler only exposes the binary information whether +/// or not we are in a `debug` build. +/// This means that `release` and `production` cannot be told apart. +/// This function additionally checks for OPT-LEVEL = 3. +pub fn check_build_profile() -> Result<(), String> { + if cfg!(build_profile = "debug") { + Err("Detected a `debug` profile".into()) + } else if !cfg!(build_opt_level = "3") { + Err("The optimization level is not set to 3".into()) + } else { + Ok(()) + } +} From 21de010f45f006b690d83e6b376554263a652421 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulo=20Silva=20de=20Souza?= <77391175+joao-paulo-parity@users.noreply.github.com> Date: Tue, 26 Apr 2022 11:59:13 -0300 Subject: [PATCH 155/484] use options for check_dependent_project (#11284) Co-authored-by: TriplEight --- .gitlab-ci.yml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c05d528b2b091..0c06f3eb9c8e0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -468,13 +468,10 @@ cargo-check-macos: "--branch=$PIPELINE_SCRIPTS_TAG" https://github.com/paritytech/pipeline-scripts - ./pipeline-scripts/check_dependent_project.sh - paritytech - substrate - --substrate - "$DEPENDENT_REPO" - "$GITHUB_PR_TOKEN" - "$CARGO_UPDATE_CRATES" - "$EXTRA_DEPENDENCIES" + --org paritytech + --dependent-repo "$DEPENDENT_REPO" + --github-api-token "$GITHUB_PR_TOKEN" + --extra-dependencies "$EXTRA_DEPENDENCIES" # Individual jobs are set up for each dependent project so that they can be ran in parallel. # Arguably we could generate a job for each companion in the PR's description using Gitlab's @@ -484,13 +481,11 @@ check-dependent-polkadot: <<: *check-dependent-project variables: DEPENDENT_REPO: polkadot - CARGO_UPDATE_CRATES: "sp-io" check-dependent-cumulus: <<: *check-dependent-project variables: DEPENDENT_REPO: cumulus - CARGO_UPDATE_CRATES: "sp-io polkadot-runtime-common" EXTRA_DEPENDENCIES: polkadot From a097e86cb9f7c7e36d9892efdd08d3f8980a8425 Mon Sep 17 00:00:00 2001 From: yjh Date: Wed, 27 Apr 2022 02:21:30 +0800 Subject: [PATCH 156/484] chore: remove unused scoped-tls (#11286) * chore: remove unused scoped-tls * rm irrelevant files * rm sp-core --- Cargo.lock | 7 ------- client/executor/wasmi/Cargo.toml | 1 - 2 files changed, 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 26954cddbd5dc..eeca5975a6f2d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8639,7 +8639,6 @@ dependencies = [ "parity-scale-codec", "sc-allocator", "sc-executor-common", - "scoped-tls", "sp-runtime-interface", "sp-sandbox", "sp-wasm-interface", @@ -9347,12 +9346,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "scoped-tls" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" - [[package]] name = "scopeguard" version = "1.1.0" diff --git a/client/executor/wasmi/Cargo.toml b/client/executor/wasmi/Cargo.toml index ed653dca0aaab..9d913d1e9cc3e 100644 --- a/client/executor/wasmi/Cargo.toml +++ b/client/executor/wasmi/Cargo.toml @@ -15,7 +15,6 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] log = "0.4.16" -scoped-tls = "1.0" wasmi = "0.9.1" codec = { package = "parity-scale-codec", version = "3.0.0" } From 2fc8a111a50d90707559d5d792f1e6e17b90f763 Mon Sep 17 00:00:00 2001 From: joe petrowski <25483142+joepetrowski@users.noreply.github.com> Date: Tue, 26 Apr 2022 21:15:00 +0200 Subject: [PATCH 157/484] typo in `from_rational` rounding example (#11301) --- primitives/arithmetic/src/per_things.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/primitives/arithmetic/src/per_things.rs b/primitives/arithmetic/src/per_things.rs index 1b9e6d91a2cd3..3851270b8d4db 100644 --- a/primitives/arithmetic/src/per_things.rs +++ b/primitives/arithmetic/src/per_things.rs @@ -273,7 +273,7 @@ pub trait PerThing: /// ```rust /// # use sp_arithmetic::{Percent, PerThing}; /// # fn main () { - /// // 989/100 is technically closer to 99%. + /// // 989/1000 is technically closer to 99%. /// assert_eq!( /// Percent::from_rational(989u64, 1000), /// Percent::from_parts(98), From fa46945e48fdf405e01f9cc76e97c189b7c5f987 Mon Sep 17 00:00:00 2001 From: Koute Date: Wed, 27 Apr 2022 14:35:52 +0900 Subject: [PATCH 158/484] Make wildcard storage change subscriptions RPC-unsafe (#11259) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * When an RPC is rejected because it's RPC-unsafe say why in the error message * Make wildcard storage change subscriptions RPC-unsafe * Apply suggestions from code review Co-authored-by: Bastian Köcher * Fix typo * Fix tests Co-authored-by: Bastian Köcher --- client/consensus/babe/rpc/src/lib.rs | 2 +- client/rpc-api/src/policy.rs | 4 ++-- client/rpc-api/src/state/mod.rs | 13 +++++++++- client/rpc/src/state/mod.rs | 10 +++++++- client/rpc/src/state/tests.rs | 36 ++++++++++++++++++++++++++++ utils/frame/rpc/system/src/lib.rs | 2 +- 6 files changed, 61 insertions(+), 6 deletions(-) diff --git a/client/consensus/babe/rpc/src/lib.rs b/client/consensus/babe/rpc/src/lib.rs index 9dd6424a43a98..1fbe23e54f50d 100644 --- a/client/consensus/babe/rpc/src/lib.rs +++ b/client/consensus/babe/rpc/src/lib.rs @@ -290,6 +290,6 @@ mod tests { let mut response: serde_json::Value = serde_json::from_str(&response).unwrap(); let error: RpcError = serde_json::from_value(response["error"].take()).unwrap(); - assert_eq!(error, RpcError::method_not_found()) + assert_eq!(error, sc_rpc_api::UnsafeRpcError.into()) } } diff --git a/client/rpc-api/src/policy.rs b/client/rpc-api/src/policy.rs index 4d1e1d7c4ce7c..dc0753c1b9139 100644 --- a/client/rpc-api/src/policy.rs +++ b/client/rpc-api/src/policy.rs @@ -56,7 +56,7 @@ impl std::fmt::Display for UnsafeRpcError { impl std::error::Error for UnsafeRpcError {} impl From for rpc::Error { - fn from(_: UnsafeRpcError) -> rpc::Error { - rpc::Error::method_not_found() + fn from(error: UnsafeRpcError) -> rpc::Error { + rpc::Error { code: rpc::ErrorCode::MethodNotFound, message: error.to_string(), data: None } } } diff --git a/client/rpc-api/src/state/mod.rs b/client/rpc-api/src/state/mod.rs index 453e954ce5952..42e927580960c 100644 --- a/client/rpc-api/src/state/mod.rs +++ b/client/rpc-api/src/state/mod.rs @@ -144,7 +144,18 @@ pub trait StateApi { id: SubscriptionId, ) -> RpcResult; - /// New storage subscription + /// Subscribe to the changes in the storage. + /// + /// This RPC endpoint has two modes of operation: + /// 1) When `keys` is not `None` you'll only be informed about the changes + /// done to the specified keys; this is RPC-safe. + /// 2) When `keys` is `None` you'll be informed of *all* of the changes; + /// **this is RPC-unsafe**. + /// + /// When subscribed to all of the changes this API will emit every storage + /// change for every block that is imported. These changes will only be sent + /// after a block is imported. If you require a consistent view across all changes + /// of every block, you need to take this into account. #[pubsub(subscription = "state_storage", subscribe, name = "state_subscribeStorage")] fn subscribe_storage( &self, diff --git a/client/rpc/src/state/mod.rs b/client/rpc/src/state/mod.rs index 071db5324c836..c9806a30b4549 100644 --- a/client/rpc/src/state/mod.rs +++ b/client/rpc/src/state/mod.rs @@ -332,7 +332,15 @@ where subscriber: Subscriber>, keys: Option>, ) { - self.backend.subscribe_storage(meta, subscriber, keys); + if keys.is_none() { + if let Err(err) = self.deny_unsafe.check_if_safe() { + subscriber.reject(err.into()) + .expect("subscription rejection can only fail if it's been already rejected, and we're rejecting it for the first time; qed"); + return + } + } + + self.backend.subscribe_storage(meta, subscriber, keys) } fn unsubscribe_storage( diff --git a/client/rpc/src/state/tests.rs b/client/rpc/src/state/tests.rs index 287dfac8c6ba0..19e474ee6459a 100644 --- a/client/rpc/src/state/tests.rs +++ b/client/rpc/src/state/tests.rs @@ -571,3 +571,39 @@ fn should_deserialize_storage_key() { assert_eq!(k.0.len(), 32); } + +#[test] +fn wildcard_storage_subscriptions_are_rpc_unsafe() { + let (subscriber, id, _) = Subscriber::new_test("test"); + + let client = Arc::new(substrate_test_runtime_client::new()); + let (api, _child) = new_full( + client.clone(), + SubscriptionManager::new(Arc::new(TaskExecutor)), + DenyUnsafe::Yes, + None, + ); + + api.subscribe_storage(Default::default(), subscriber, None.into()); + + let error = executor::block_on(id).unwrap().unwrap_err(); + assert_eq!(error.to_string(), "Method not found: RPC call is unsafe to be called externally"); +} + +#[test] +fn concrete_storage_subscriptions_are_rpc_safe() { + let (subscriber, id, _) = Subscriber::new_test("test"); + + let client = Arc::new(substrate_test_runtime_client::new()); + let (api, _child) = new_full( + client.clone(), + SubscriptionManager::new(Arc::new(TaskExecutor)), + DenyUnsafe::Yes, + None, + ); + + let key = StorageKey(STORAGE_KEY.to_vec()); + api.subscribe_storage(Default::default(), subscriber, Some(vec![key])); + + assert!(matches!(executor::block_on(id), Ok(Ok(SubscriptionId::String(_))))); +} diff --git a/utils/frame/rpc/system/src/lib.rs b/utils/frame/rpc/system/src/lib.rs index 0eae4d061afbb..b7da7730f0920 100644 --- a/utils/frame/rpc/system/src/lib.rs +++ b/utils/frame/rpc/system/src/lib.rs @@ -279,7 +279,7 @@ mod tests { let res = accounts.dry_run(vec![].into(), None); // then - assert_eq!(block_on(res), Err(RpcError::method_not_found())); + assert_eq!(block_on(res), Err(sc_rpc_api::UnsafeRpcError.into())); } #[test] From 91ad79344af9f58617ffa7d4e0213db02d5cebe4 Mon Sep 17 00:00:00 2001 From: Chigozie Joshua <36326251+guzzit@users.noreply.github.com> Date: Wed, 27 Apr 2022 10:23:46 +0100 Subject: [PATCH 159/484] Adds remove_approval feature to treasury pallet (#11243) * added remove_approval feature to treasury pallet * Update frame/treasury/src/lib.rs Co-authored-by: Xiliang Chen * treasury pallet refactoring: removed proposalId check in remove_approval call and trailing new line in tests file * fmt * cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_treasury --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/treasury/src/weights.rs --template=./.maintain/frame-weight-template.hbs * Update frame/treasury/src/lib.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * remove_approval extrinsic in treasury pallet:added extra checks in test and extra documentation * refactored error logging on remove_approval extrinsic in treasury pallet Co-authored-by: Xiliang Chen Co-authored-by: Shawn Tabrizi Co-authored-by: Parity Bot Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> --- frame/treasury/src/benchmarking.rs | 11 ++++++ frame/treasury/src/lib.rs | 37 ++++++++++++++++++++ frame/treasury/src/tests.rs | 18 ++++++++++ frame/treasury/src/weights.rs | 54 ++++++++++++++++++------------ 4 files changed, 99 insertions(+), 21 deletions(-) diff --git a/frame/treasury/src/benchmarking.rs b/frame/treasury/src/benchmarking.rs index a0dd58ee3d42a..47bc1b9ea99de 100644 --- a/frame/treasury/src/benchmarking.rs +++ b/frame/treasury/src/benchmarking.rs @@ -87,6 +87,17 @@ benchmarks_instance_pallet! { let proposal_id = Treasury::::proposal_count() - 1; }: _(RawOrigin::Root, proposal_id) + remove_approval { + let (caller, value, beneficiary_lookup) = setup_proposal::(SEED); + Treasury::::propose_spend( + RawOrigin::Signed(caller).into(), + value, + beneficiary_lookup + )?; + let proposal_id = Treasury::::proposal_count() - 1; + Treasury::::approve_proposal(RawOrigin::Root.into(), proposal_id)?; + }: _(RawOrigin::Root, proposal_id) + on_initialize_proposals { let p in 0 .. T::MaxApprovals::get(); setup_pot_account::(); diff --git a/frame/treasury/src/lib.rs b/frame/treasury/src/lib.rs index 81fca5243afa3..3f76ddb639c5b 100644 --- a/frame/treasury/src/lib.rs +++ b/frame/treasury/src/lib.rs @@ -50,6 +50,7 @@ //! - `propose_spend` - Make a spending proposal and stake the required deposit. //! - `reject_proposal` - Reject a proposal, slashing the deposit. //! - `approve_proposal` - Accept the proposal, returning the deposit. +//! - `remove_approval` - Remove an approval, the deposit will no longer be returned. //! //! ## GenesisConfig //! @@ -289,6 +290,8 @@ pub mod pallet { InvalidIndex, /// Too many approvals in the queue. TooManyApprovals, + /// Proposal has not been approved. + ProposalNotApproved, } #[pallet::hooks] @@ -393,6 +396,40 @@ pub mod pallet { .map_err(|_| Error::::TooManyApprovals)?; Ok(()) } + + /// Force a previously approved proposal to be removed from the approval queue. + /// The original deposit will no longer be returned. + /// + /// May only be called from `T::RejectOrigin`. + /// - `proposal_id`: The index of a proposal + /// + /// # + /// - Complexity: O(A) where `A` is the number of approvals + /// - Db reads and writes: `Approvals` + /// # + /// + /// Errors: + /// - `ProposalNotApproved`: The `proposal_id` supplied was not found in the approval queue, + /// i.e., the proposal has not been approved. This could also mean the proposal does not + /// exist altogether, thus there is no way it would have been approved in the first place. + #[pallet::weight((T::WeightInfo::remove_approval(), DispatchClass::Operational))] + pub fn remove_approval( + origin: OriginFor, + #[pallet::compact] proposal_id: ProposalIndex, + ) -> DispatchResult { + T::RejectOrigin::ensure_origin(origin)?; + + Approvals::::try_mutate(|v| -> DispatchResult { + if let Some(index) = v.iter().position(|x| x == &proposal_id) { + v.remove(index); + Ok(()) + } else { + Err(Error::::ProposalNotApproved.into()) + } + })?; + + Ok(()) + } } } diff --git a/frame/treasury/src/tests.rs b/frame/treasury/src/tests.rs index 26189f5201498..a67bbe9b1d1e9 100644 --- a/frame/treasury/src/tests.rs +++ b/frame/treasury/src/tests.rs @@ -388,3 +388,21 @@ fn max_approvals_limited() { ); }); } + +#[test] +fn remove_already_removed_approval_fails() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&Treasury::account_id(), 101); + + assert_ok!(Treasury::propose_spend(Origin::signed(0), 100, 3)); + assert_ok!(Treasury::approve_proposal(Origin::root(), 0)); + assert_eq!(Treasury::approvals(), vec![0]); + assert_ok!(Treasury::remove_approval(Origin::root(), 0)); + assert_eq!(Treasury::approvals(), vec![]); + + assert_noop!( + Treasury::remove_approval(Origin::root(), 0), + Error::::ProposalNotApproved + ); + }); +} diff --git a/frame/treasury/src/weights.rs b/frame/treasury/src/weights.rs index dcbf5983fa65c..c846a7f6885e9 100644 --- a/frame/treasury/src/weights.rs +++ b/frame/treasury/src/weights.rs @@ -18,12 +18,13 @@ //! Autogenerated weights for pallet_treasury //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-01-31, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-04-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// ./target/production/substrate +// target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -33,9 +34,7 @@ // --wasm-execution=compiled // --heap-pages=4096 // --output=./frame/treasury/src/weights.rs -// --template=.maintain/frame-weight-template.hbs -// --header=HEADER-APACHE2 -// --raw +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -49,6 +48,7 @@ pub trait WeightInfo { fn propose_spend() -> Weight; fn reject_proposal() -> Weight; fn approve_proposal(p: u32, ) -> Weight; + fn remove_approval() -> Weight; fn on_initialize_proposals(p: u32, ) -> Weight; } @@ -58,34 +58,40 @@ impl WeightInfo for SubstrateWeight { // Storage: Treasury ProposalCount (r:1 w:1) // Storage: Treasury Proposals (r:0 w:1) fn propose_spend() -> Weight { - (21_673_000 as Weight) + (22_063_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Treasury Proposals (r:1 w:1) // Storage: System Account (r:1 w:1) fn reject_proposal() -> Weight { - (25_353_000 as Weight) + (25_965_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Treasury Proposals (r:1 w:0) // Storage: Treasury Approvals (r:1 w:1) fn approve_proposal(p: u32, ) -> Weight { - (8_164_000 as Weight) - // Standard Error: 1_000 - .saturating_add((57_000 as Weight).saturating_mul(p as Weight)) + (7_299_000 as Weight) + // Standard Error: 0 + .saturating_add((103_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Treasury Approvals (r:1 w:1) + fn remove_approval() -> Weight { + (3_867_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Treasury Approvals (r:1 w:1) // Storage: Bounties BountyApprovals (r:1 w:1) // Storage: Treasury Proposals (r:2 w:2) // Storage: System Account (r:4 w:4) fn on_initialize_proposals(p: u32, ) -> Weight { - (20_762_000 as Weight) - // Standard Error: 21_000 - .saturating_add((26_835_000 as Weight).saturating_mul(p as Weight)) + (24_020_000 as Weight) + // Standard Error: 22_000 + .saturating_add((28_198_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(p as Weight))) .saturating_add(T::DbWeight::get().writes(2 as Weight)) @@ -98,34 +104,40 @@ impl WeightInfo for () { // Storage: Treasury ProposalCount (r:1 w:1) // Storage: Treasury Proposals (r:0 w:1) fn propose_spend() -> Weight { - (21_673_000 as Weight) + (22_063_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Treasury Proposals (r:1 w:1) // Storage: System Account (r:1 w:1) fn reject_proposal() -> Weight { - (25_353_000 as Weight) + (25_965_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Treasury Proposals (r:1 w:0) // Storage: Treasury Approvals (r:1 w:1) fn approve_proposal(p: u32, ) -> Weight { - (8_164_000 as Weight) - // Standard Error: 1_000 - .saturating_add((57_000 as Weight).saturating_mul(p as Weight)) + (7_299_000 as Weight) + // Standard Error: 0 + .saturating_add((103_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Treasury Approvals (r:1 w:1) + fn remove_approval() -> Weight { + (3_867_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Treasury Approvals (r:1 w:1) // Storage: Bounties BountyApprovals (r:1 w:1) // Storage: Treasury Proposals (r:2 w:2) // Storage: System Account (r:4 w:4) fn on_initialize_proposals(p: u32, ) -> Weight { - (20_762_000 as Weight) - // Standard Error: 21_000 - .saturating_add((26_835_000 as Weight).saturating_mul(p as Weight)) + (24_020_000 as Weight) + // Standard Error: 22_000 + .saturating_add((28_198_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(p as Weight))) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) From 571e0d7b7adbd5e8ffa9f74bf5f0e7d8296efe64 Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Wed, 27 Apr 2022 03:46:47 -0700 Subject: [PATCH 160/484] Nomination Pools (#10694) * Update frame/nomination-pools/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Add admin roles and make some calls permissionless * Destroy pool in withdraw unbonded * Add docs on pool admin * Fixup tests * Test unbond_other permissionless scenarios * Test withdraw unbonded permissionless * Test only can join open pools * Move unsafe set state to mock * Test: nominate_works * Add bounds: MinJoinBond, MinCreateBond, MaxPools * Test MinCreateBond, MinJoinBond, MaxPools * Add post checks to tests * Remove some TODOs * Setup weight infrastructure * Benchmark claim_payout * Benchmark create * Benchmark nominate * Benchmark join * Benchmark unbond_other * Refactor join benchmark to use scenario setup * Clean up and address warnings * Basic withdraw unbonded benchmarks * Refactor nominate benchmark * Refactor claim payout * Add feature sp-staking/runtime-benchmarks * Get node runtime to compile * Get node to run * Make claim_payout bench work with node * Make pool_withdraw_unbonded bench work with node * Make withdraw_unbonded_other work with node runtime' * Make create benchmark work with node * Make nominate benchmark work with node runtime * WiP new benchmark crate * Implement initial mock for benchmarks * Establish benchmark setup logic * Get claim payout and nominate benchmarks working * Remove pool bench utils; make struct fields pub insteaad * Get more benchmarks to work; trim interface trait * Some more top level docs * Finish tranistion benchmarks to crate * Hook up benchmark pallet to node runtime * Get benches to work with node runtime * cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark --chain=dev --steps=50 --repeat=20 --pallet=pallet_nomination_pools --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/nomination-pools/src/weights.rs --template=./.maintain/frame-weight-template.hbs * Benchmark withdraw_unbonded_other_kill * Delete old benchmarking files * Refunds for withdraw_unbonded * Remove some TODOs * 'Don't return an option for the current_era' * Streamline extrinsic docs * small docs tweaks * Refactor ledger::slash * Add on_slash impl for nomination pools * slash refactor wip * WIP slash working * DRY Ledger::stash * Fix slash saturation * Remove unused param from slash * Docs and warnings * Test ledger::slash * save progress * Introduce counter for delegators * Add tests for max delegator errors * Reproducible account ids * Adapt tests to new account id format * Simplify create_accounts api * Fix staking tests * Save PerBill slash impl before removing * Rever ledger slash test * Get node runtime to work * Organize sub pools by unbond era, not curren era * staking: Proportional ledger slashing * Some comment cleanup * Add more test post checks * Update frame/staking/src/pallet/mod.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Tests: account for storing unbond era * Improve docs for staking interface * Add events Created, Destroyed * withdraw_unbonded: Remove useless withdraw dust check * Test: withdraw_unbonded_other_handles_faulty_sub_pool_accounting * Add extrinsics: set_state_other, set_metadata * Test: set_state_other_works * Test: set_metadata_works * Add benchmarks for set_state_other, set_metadata * Fix benchmarks * Add weight info for new extrinsics * Some feedback * duo feedback * Incorporate some more feedback * integrate more kian feedback * integrate more kian feedback * More improvements * Add destroying_mul * Make do_reward_payout take refs * Remove some TODOs * Add test for saturating * feedback * Fix join test * use `inner` for nested types in nomination pools (#11030) * Use nested inner type for pool * make tests and benchmarks work * remove feat * all tests work now * fix node-runtime * nomination-pools: update benches for new account format (#11033) * Update benches to new account format * More sensible seeds * bring back rward account sanity check * Comment * Add extrinsic set_configs (#11038) * Better sanity checks for nomination pools (#11042) * new sanity checks, few other changes * Update frame/nomination-pools/src/lib.rs Co-authored-by: Zeke Mostov * nomination-pools: Fix state event regression + benches (#11045) * new sanity checks, few other changes * Fix benches, improve sanity check * Remove useless clear storage in benchmarking * Set state * Save * Doc * Update frame/nomination-pools/src/lib.rs Co-authored-by: kianenigma * FMT * Try fill in all staking configs * Fix build * More changes to nomination pools (#11050) * new sanity checks, few other changes * some last touches as a whole * Apply suggestions from code review * Remove redundant event * Improve unbond_other error handling * Remove comment Co-authored-by: Zeke Mostov Co-authored-by: emostov <32168567+emostov@users.noreply.github.com> * Remove sanity module and some TODOs * round of feedback and imp from kian * Add TODO for ED QoL at reward pool creation * Make sure reward pool never gets dusted * Improve error type * demonstrate per_thing usage * Update sanity check & fix create_works * Improve test ext pool creation & fix some more tests * Try revert * Revert "Try revert" This reverts commit a6d1c9b920ad995fa8cdcbf6561b8db81c7a7783. * Revert "Improve test ext pool creation & fix some more tests" This reverts commit b410010ec4c9bc05410b62ebbba2a39d68d18c99. * Revert "Update sanity check & fix create_works" This reverts commit cdc2a32d22e6c00609bf496a5854b8f99f031156. Roll back reward account funding * Revert "Improve error type" This reverts commit f48cd379e608f98c70bd64a83fbc2e4530b115e1. * Revert "Make sure reward pool never gets dusted" This reverts commit 850cb3e80aa6462830bec7b545a434d0528ae774. revert * Update some tests * FMT * Test that era offset works correctly * Update mocks * Remove unnescary docs * Doc updates * Update calculate_delegator_payout_works_with_a_pool_of_1 * Fix test: claim_payout_works * do_reward_payout_correctly_sets_pool_state_to_destroying * Remove test do_reward_payout_errors_correctly * Fix test: do_reward_payout_works * Fix test: create_errors_correctly * Fix test: create works * Fix test: unbond_other_of_3_works * Ensure that ED is transferred into reward pool upon creation * WIP pool lifecycle test * Fix benchmarks * Add sanity check for ED + reward pools * `bond_extra` for nomination pools (#11100) * bond_extra for nomination pools * Update frame/nomination-pools/src/lib.rs * Update frame/nomination-pools/src/lib.rs * Update frame/nomination-pools/src/lib.rs Co-authored-by: Zeke Mostov * Update frame/nomination-pools/src/lib.rs Co-authored-by: Zeke Mostov * add benchmarks * remove the min logic of bond_extra Co-authored-by: Zeke Mostov * FMT * Update frame/nomination-pools/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Update frame/nomination-pools/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Update frame/nomination-pools/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Update frame/nomination-pools/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * make it compile end to end * Update some type viz * Update kick terminology * Update frame/nomination-pools/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Update frame/nomination-pools/src/lib.rs * Cache bonded account when creating pool * Add bond extra weight stuff * Update frame/nomination-pools/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Update docs for pool withdraw unbonded * Update docs for unbond * Improve Doc * Update frame/nomination-pools/src/lib.rs Co-authored-by: Oliver Tale-Yazdi * Update frame/nomination-pools/Cargo.toml Co-authored-by: Oliver Tale-Yazdi * Update frame/nomination-pools/src/lib.rs Co-authored-by: Oliver Tale-Yazdi * Update frame/nomination-pools/src/lib.rs Co-authored-by: Oliver Tale-Yazdi * Update frame/nomination-pools/src/lib.rs Co-authored-by: Oliver Tale-Yazdi * Improve Docs * Some docs improvements * fmt * Remove unlock_era * Fix accidental frame-support regression * Fix issue with transactions in tests * Fix doc links * Make sure result in test is used * Update frame/nomination-pools/src/lib.rs Co-authored-by: Oliver Tale-Yazdi * Fix can toggle state * Account for new_funds in ok to be open * Update docs: ok_to_withdraw_unbonded_other_with * Update frame/nomination-pools/src/lib.rs Co-authored-by: Oliver Tale-Yazdi * Update frame/nomination-pools/src/lib.rs Co-authored-by: Oliver Tale-Yazdi * Remove some staking comments * Rename SubPoolsWithEra to UnbondingPoolsWithEra * Use validators length for benchmarks * Use metadata length for benchmarks * Remove debug assert eq * docs * Fix test: withdraw_unbonded_other_errors_correctly * Fix check for having enough balance to create the pool * Bond event for pool creation * Ok to be open * FMT * Remove _other postfix * Update frame/staking/src/lib.rs * Adjust tests to account for only remove when < ED * Remove stale TODOs * Remove dupe test * Fix build * Make sure to convert to u256 so we don't saturate * Refund depositor with reward pool fee * FMT * Remove reachable defensive * Use compact encoding for relevant extrinsics * Remove unnescary make_free_be for cleaning reward account * Add not to maintainers for reward account accounting * Remove note to maintainers from public doc * Make sure all configs have currency balance * Avoid saturation in balance_to_unbond * Partial Unbonding for Nomination Pools (#11212) * first draft of partial unbonding for pools * remove option * Add some more tests and fix issues * Fix all tests * simplify some tests * Update frame/nomination-pools/src/mock.rs * remove clone * rename to delegator_unbonding_eras * Update frame/nomination-pools/src/tests.rs Co-authored-by: Zeke Mostov * Update frame/nomination-pools/src/tests.rs Co-authored-by: Zeke Mostov * Update frame/nomination-pools/src/tests.rs Co-authored-by: Zeke Mostov * remove pub * Update frame/nomination-pools/src/lib.rs Co-authored-by: Zeke Mostov * Update frame/nomination-pools/src/lib.rs Co-authored-by: Zeke Mostov * undo * Update frame/nomination-pools/src/lib.rs Co-authored-by: Zeke Mostov * Update frame/nomination-pools/src/lib.rs Co-authored-by: Zeke Mostov * leftovers * fix invariant * Fix the depositor assumption * round of self-review * little bit more cleanup * Update frame/nomination-pools/src/mock.rs * Apply suggestions from code review * Update frame/nomination-pools/src/lib.rs Co-authored-by: Zeke Mostov * Fix interpretation of MinCreateBond * controvesial refactor * rename * make everything build * add TODO about killing the reward account * Update frame/nomination-pools/src/lib.rs Co-authored-by: Zeke Mostov * Update frame/nomination-pools/src/lib.rs * last self-review Co-authored-by: Zeke Mostov * Update Cargo.lock * Rename Delegator to PoolMember * fmt * Get runtime to build with runtime-benchmarks feature * Update Cargo.lock * Fix asserts to work in more scenarios * gte not gt * cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_nomination_pools --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/nomination-pools/src/weights.rs --template=./.maintain/frame-weight-template.hbs * Update frame/staking/src/mock.rs * Update frame/nomination-pools/src/lib.rs * Update frame/staking/src/slashing.rs * Apply suggestions from code review * fmt * Fix some tests Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Co-authored-by: Parity Bot Co-authored-by: kianenigma Co-authored-by: Oliver Tale-Yazdi Co-authored-by: Shawn Tabrizi --- Cargo.lock | 42 + Cargo.toml | 2 + bin/node/cli/src/chain_spec.rs | 1 + bin/node/runtime/Cargo.toml | 4 + bin/node/runtime/src/lib.rs | 39 +- bin/node/testing/src/genesis.rs | 1 + frame/bags-list/src/benchmarks.rs | 12 +- frame/nomination-pools/Cargo.toml | 44 + .../nomination-pools/benchmarking/Cargo.toml | 55 + frame/nomination-pools/benchmarking/README.md | 0 .../nomination-pools/benchmarking/src/lib.rs | 639 ++++ .../nomination-pools/benchmarking/src/mock.rs | 200 ++ frame/nomination-pools/src/lib.rs | 2217 +++++++++++++ frame/nomination-pools/src/mock.rs | 309 ++ frame/nomination-pools/src/tests.rs | 2929 +++++++++++++++++ frame/nomination-pools/src/weights.rs | 482 +++ frame/staking/Cargo.toml | 1 + frame/staking/src/benchmarking.rs | 2 +- frame/staking/src/lib.rs | 12 + frame/staking/src/mock.rs | 2 +- frame/staking/src/pallet/impls.rs | 71 +- frame/staking/src/pallet/mod.rs | 2 +- frame/support/src/lib.rs | 21 + .../support/src/storage/bounded_btree_map.rs | 8 + frame/support/src/traits/misc.rs | 13 +- primitives/staking/Cargo.toml | 1 + primitives/staking/src/lib.rs | 80 +- 27 files changed, 7174 insertions(+), 15 deletions(-) create mode 100644 frame/nomination-pools/Cargo.toml create mode 100644 frame/nomination-pools/benchmarking/Cargo.toml create mode 100644 frame/nomination-pools/benchmarking/README.md create mode 100644 frame/nomination-pools/benchmarking/src/lib.rs create mode 100644 frame/nomination-pools/benchmarking/src/mock.rs create mode 100644 frame/nomination-pools/src/lib.rs create mode 100644 frame/nomination-pools/src/mock.rs create mode 100644 frame/nomination-pools/src/tests.rs create mode 100644 frame/nomination-pools/src/weights.rs diff --git a/Cargo.lock b/Cargo.lock index eeca5975a6f2d..596b737e22d09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5027,6 +5027,8 @@ dependencies = [ "pallet-membership", "pallet-mmr", "pallet-multisig", + "pallet-nomination-pools", + "pallet-nomination-pools-benchmarking", "pallet-offences", "pallet-offences-benchmarking", "pallet-preimage", @@ -6182,6 +6184,46 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-nomination-pools" +version = "1.0.0" +dependencies = [ + "frame-support", + "frame-system", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-staking", + "sp-std", + "sp-tracing", +] + +[[package]] +name = "pallet-nomination-pools-benchmarking" +version = "1.0.0" +dependencies = [ + "frame-benchmarking", + "frame-election-provider-support", + "frame-support", + "frame-system", + "pallet-bags-list", + "pallet-balances", + "pallet-nomination-pools", + "pallet-staking", + "pallet-staking-reward-curve", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-staking", + "sp-std", +] + [[package]] name = "pallet-offences" version = "4.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index 5cc90ec6f183b..f91e6226ccfd0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -109,6 +109,8 @@ members = [ "frame/offences", "frame/preimage", "frame/proxy", + "frame/nomination-pools", + "frame/nomination-pools/benchmarking", "frame/randomness-collective-flip", "frame/recovery", "frame/referenda", diff --git a/bin/node/cli/src/chain_spec.rs b/bin/node/cli/src/chain_spec.rs index 1212d3b3d07ed..221c229876275 100644 --- a/bin/node/cli/src/chain_spec.rs +++ b/bin/node/cli/src/chain_spec.rs @@ -363,6 +363,7 @@ pub fn testnet_genesis( gilt: Default::default(), transaction_storage: Default::default(), transaction_payment: Default::default(), + nomination_pools: Default::default(), } } diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 847530e5c61bc..09f7534e7f521 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -76,6 +76,8 @@ pallet-lottery = { version = "4.0.0-dev", default-features = false, path = "../. pallet-membership = { version = "4.0.0-dev", default-features = false, path = "../../../frame/membership" } pallet-mmr = { version = "4.0.0-dev", default-features = false, path = "../../../frame/merkle-mountain-range" } pallet-multisig = { version = "4.0.0-dev", default-features = false, path = "../../../frame/multisig" } +pallet-nomination-pools = { version = "1.0.0", default-features = false, path = "../../../frame/nomination-pools"} +pallet-nomination-pools-benchmarking = { version = "1.0.0", default-features = false, optional = true, path = "../../../frame/nomination-pools/benchmarking" } pallet-offences = { version = "4.0.0-dev", default-features = false, path = "../../../frame/offences" } pallet-offences-benchmarking = { version = "4.0.0-dev", path = "../../../frame/offences/benchmarking", default-features = false, optional = true } pallet-preimage = { version = "4.0.0-dev", default-features = false, path = "../../../frame/preimage" } @@ -140,6 +142,7 @@ std = [ "pallet-membership/std", "pallet-mmr/std", "pallet-multisig/std", + "pallet-nomination-pools/std", "pallet-identity/std", "pallet-scheduler/std", "node-primitives/std", @@ -210,6 +213,7 @@ runtime-benchmarks = [ "pallet-membership/runtime-benchmarks", "pallet-mmr/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", + "pallet-nomination-pools-benchmarking", "pallet-offences-benchmarking", "pallet-preimage/runtime-benchmarks", "pallet-proxy/runtime-benchmarks", diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index f073482d887bd..54101cc222f84 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -561,7 +561,7 @@ impl pallet_staking::Config for Runtime { type GenesisElectionProvider = onchain::UnboundedExecution; type VoterList = BagsList; type MaxUnlockingChunks = ConstU32<32>; - type OnStakerSlash = (); + type OnStakerSlash = NominationPools; type WeightInfo = pallet_staking::weights::SubstrateWeight; type BenchmarkingConfig = StakingBenchmarkingConfig; } @@ -705,6 +705,38 @@ impl pallet_bags_list::Config for Runtime { type Score = VoteWeight; } +parameter_types! { + pub const PostUnbondPoolsWindow: u32 = 4; + pub const NominationPoolsPalletId: PalletId = PalletId(*b"py/npols"); +} + +use sp_runtime::traits::Convert; +pub struct BalanceToU256; +impl Convert for BalanceToU256 { + fn convert(balance: Balance) -> sp_core::U256 { + sp_core::U256::from(balance) + } +} +pub struct U256ToBalance; +impl Convert for U256ToBalance { + fn convert(n: sp_core::U256) -> Balance { + n.try_into().unwrap_or(Balance::max_value()) + } +} + +impl pallet_nomination_pools::Config for Runtime { + type WeightInfo = (); + type Event = Event; + type Currency = Balances; + type BalanceToU256 = BalanceToU256; + type U256ToBalance = U256ToBalance; + type StakingInterface = pallet_staking::Pallet; + type PostUnbondingPoolsWindow = PostUnbondPoolsWindow; + type MaxMetadataLen = ConstU32<256>; + type MaxUnbonding = ConstU32<8>; + type PalletId = NominationPoolsPalletId; +} + parameter_types! { pub const VoteLockingPeriod: BlockNumber = 30 * DAYS; } @@ -1470,6 +1502,7 @@ construct_runtime!( Remark: pallet_remark, ConvictionVoting: pallet_conviction_voting, Whitelist: pallet_whitelist, + NominationPools: pallet_nomination_pools, } ); @@ -1554,6 +1587,7 @@ mod benches { [pallet_membership, TechnicalMembership] [pallet_mmr, Mmr] [pallet_multisig, Multisig] + [pallet_nomination_pools, NominationPoolsBench::] [pallet_offences, OffencesBench::] [pallet_preimage, Preimage] [pallet_proxy, Proxy] @@ -1869,6 +1903,7 @@ impl_runtime_apis! { use pallet_election_provider_support_benchmarking::Pallet as EPSBench; use frame_system_benchmarking::Pallet as SystemBench; use baseline::Pallet as BaselineBench; + use pallet_nomination_pools_benchmarking::Pallet as NominationPoolsBench; let mut list = Vec::::new(); list_benchmarks!(list, extra); @@ -1891,12 +1926,14 @@ impl_runtime_apis! { use pallet_election_provider_support_benchmarking::Pallet as EPSBench; use frame_system_benchmarking::Pallet as SystemBench; use baseline::Pallet as BaselineBench; + use pallet_nomination_pools_benchmarking::Pallet as NominationPoolsBench; impl pallet_session_benchmarking::Config for Runtime {} impl pallet_offences_benchmarking::Config for Runtime {} impl pallet_election_provider_support_benchmarking::Config for Runtime {} impl frame_system_benchmarking::Config for Runtime {} impl baseline::Config for Runtime {} + impl pallet_nomination_pools_benchmarking::Config for Runtime {} let whitelist: Vec = vec![ // Block Number diff --git a/bin/node/testing/src/genesis.rs b/bin/node/testing/src/genesis.rs index 8d2b53b0b7210..73e7ea50261d3 100644 --- a/bin/node/testing/src/genesis.rs +++ b/bin/node/testing/src/genesis.rs @@ -92,5 +92,6 @@ pub fn config_endowed(code: Option<&[u8]>, extra_endowed: Vec) -> Gen gilt: Default::default(), transaction_storage: Default::default(), transaction_payment: Default::default(), + nomination_pools: Default::default(), } } diff --git a/frame/bags-list/src/benchmarks.rs b/frame/bags-list/src/benchmarks.rs index bcfd1e3392b0e..1a901c4d9d5a1 100644 --- a/frame/bags-list/src/benchmarks.rs +++ b/frame/bags-list/src/benchmarks.rs @@ -179,10 +179,10 @@ frame_benchmarking::benchmarks! { vec![heavier, lighter, heavier_prev, heavier_next] ) } -} -frame_benchmarking::impl_benchmark_test_suite!( - Pallet, - crate::mock::ExtBuilder::default().skip_genesis_ids().build(), - crate::mock::Runtime -); + impl_benchmark_test_suite!( + Pallet, + crate::mock::ExtBuilder::default().skip_genesis_ids().build(), + crate::mock::Runtime + ); +} diff --git a/frame/nomination-pools/Cargo.toml b/frame/nomination-pools/Cargo.toml new file mode 100644 index 0000000000000..882a6f363a14f --- /dev/null +++ b/frame/nomination-pools/Cargo.toml @@ -0,0 +1,44 @@ +[package] +name = "pallet-nomination-pools" +version = "1.0.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME nomination pools pallet" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +# parity +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } + +# FRAME +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } +sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } + +[dev-dependencies] +pallet-balances = { version = "4.0.0-dev", path = "../balances" } +sp-io = { version = "6.0.0", path = "../../primitives/io" } +sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } + +[features] +runtime-benchmarks = [] +default = ["std"] +std = [ + "codec/std", + "scale-info/std", + "frame-support/std", + "frame-system/std", + "sp-runtime/std", + "sp-std/std", + "sp-staking/std", + "sp-core/std", +] diff --git a/frame/nomination-pools/benchmarking/Cargo.toml b/frame/nomination-pools/benchmarking/Cargo.toml new file mode 100644 index 0000000000000..58158c20cf195 --- /dev/null +++ b/frame/nomination-pools/benchmarking/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = "pallet-nomination-pools-benchmarking" +version = "1.0.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME nomination pools pallet benchmarking" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +# parity +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } + +# FRAME +frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../benchmarking" } +frame-election-provider-support = { version = "4.0.0-dev", default-features = false, path = "../../election-provider-support" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } +pallet-bags-list = { version = "4.0.0-dev", default-features = false, features = ["runtime-benchmarks"], path = "../../bags-list" } +pallet-staking = { version = "4.0.0-dev", default-features = false, features = ["runtime-benchmarks"], path = "../../staking" } +pallet-nomination-pools = { version = "1.0.0", default-features = false, path = "../", features = ["runtime-benchmarks"] } + +# Substrate Primitives +sp-runtime = { version = "6.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/staking" } +sp-std = { version = "4.0.0", default-features = false, path = "../../../primitives/std" } + +[dev-dependencies] +pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../../balances" } +pallet-timestamp = { version = "4.0.0-dev", path = "../../timestamp" } +pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../../staking/reward-curve" } +sp-core = { version = "6.0.0", path = "../../../primitives/core" } +sp-io = { version = "6.0.0", path = "../../../primitives/io" } + +[features] +default = ["std"] +std = [ + "frame-benchmarking/std", + "frame-election-provider-support/std", + "frame-support/std", + "frame-system/std", + "pallet-bags-list/std", + "pallet-staking/std", + "pallet-nomination-pools/std", + "sp-runtime/std", + "sp-staking/std", + "sp-std/std", + "pallet-balances/std", +] diff --git a/frame/nomination-pools/benchmarking/README.md b/frame/nomination-pools/benchmarking/README.md new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs new file mode 100644 index 0000000000000..7ae6338e6304a --- /dev/null +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -0,0 +1,639 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Benchmarks for the nomination pools coupled with the staking and bags list pallets. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(test)] +mod mock; + +use frame_benchmarking::{account, frame_support::traits::Currency, vec, whitelist_account, Vec}; +use frame_election_provider_support::SortedListProvider; +use frame_support::{ensure, traits::Get}; +use frame_system::RawOrigin as Origin; +use pallet_nomination_pools::{ + BalanceOf, BondExtra, BondedPoolInner, BondedPools, ConfigOp, MaxPoolMembers, + MaxPoolMembersPerPool, MaxPools, Metadata, MinCreateBond, MinJoinBond, Pallet as Pools, + PoolMembers, PoolRoles, PoolState, RewardPools, SubPoolsStorage, +}; +use sp_runtime::traits::{Bounded, Zero}; +use sp_staking::{EraIndex, StakingInterface}; +// `frame_benchmarking::benchmarks!` macro needs this +use pallet_nomination_pools::Call; + +type CurrencyOf = ::Currency; + +const USER_SEED: u32 = 0; +const MAX_SPANS: u32 = 100; + +pub trait Config: + pallet_nomination_pools::Config + pallet_staking::Config + pallet_bags_list::Config +{ +} + +pub struct Pallet(Pools); + +fn create_funded_user_with_balance( + string: &'static str, + n: u32, + balance: BalanceOf, +) -> T::AccountId { + let user = account(string, n, USER_SEED); + T::Currency::make_free_balance_be(&user, balance); + user +} + +// Create a bonded pool account, bonding `balance` and giving the account `balance * 2` free +// balance. +fn create_pool_account( + n: u32, + balance: BalanceOf, +) -> (T::AccountId, T::AccountId) { + let ed = CurrencyOf::::minimum_balance(); + let pool_creator: T::AccountId = + create_funded_user_with_balance::("pool_creator", n, ed + balance * 2u32.into()); + + Pools::::create( + Origin::Signed(pool_creator.clone()).into(), + balance, + pool_creator.clone(), + pool_creator.clone(), + pool_creator.clone(), + ) + .unwrap(); + + let pool_account = pallet_nomination_pools::BondedPools::::iter() + .find(|(_, bonded_pool)| bonded_pool.roles.depositor == pool_creator) + .map(|(pool_id, _)| Pools::::create_bonded_account(pool_id)) + .expect("pool_creator created a pool above"); + + (pool_creator, pool_account) +} + +fn vote_to_balance( + vote: u64, +) -> Result, &'static str> { + vote.try_into().map_err(|_| "could not convert u64 to Balance") +} + +#[allow(unused)] +struct ListScenario { + /// Stash/Controller that is expected to be moved. + origin1: T::AccountId, + creator1: T::AccountId, + dest_weight: BalanceOf, + origin1_member: Option, +} + +impl ListScenario { + /// An expensive scenario for bags-list implementation: + /// + /// - the node to be updated (r) is the head of a bag that has at least one other node. The bag + /// itself will need to be read and written to update its head. The node pointed to by r.next + /// will need to be read and written as it will need to have its prev pointer updated. Note + /// that there are two other worst case scenarios for bag removal: 1) the node is a tail and + /// 2) the node is a middle node with prev and next; all scenarios end up with the same number + /// of storage reads and writes. + /// + /// - the destination bag has at least one node, which will need its next pointer updated. + pub(crate) fn new( + origin_weight: BalanceOf, + is_increase: bool, + ) -> Result { + ensure!(!origin_weight.is_zero(), "origin weight must be greater than 0"); + + ensure!( + pallet_nomination_pools::MaxPools::::get().unwrap_or(0) >= 3, + "must allow at least three pools for benchmarks" + ); + + // Burn the entire issuance. + let i = CurrencyOf::::burn(CurrencyOf::::total_issuance()); + sp_std::mem::forget(i); + + // Create accounts with the origin weight + let (pool_creator1, pool_origin1) = create_pool_account::(USER_SEED + 1, origin_weight); + T::StakingInterface::nominate( + pool_origin1.clone(), + // NOTE: these don't really need to be validators. + vec![account("random_validator", 0, USER_SEED)], + )?; + + let (_, pool_origin2) = create_pool_account::(USER_SEED + 2, origin_weight); + T::StakingInterface::nominate( + pool_origin2.clone(), + vec![account("random_validator", 0, USER_SEED)].clone(), + )?; + + // Find a destination weight that will trigger the worst case scenario + let dest_weight_as_vote = ::VoterList::score_update_worst_case( + &pool_origin1, + is_increase, + ); + + let dest_weight: BalanceOf = + dest_weight_as_vote.try_into().map_err(|_| "could not convert u64 to Balance")?; + + // Create an account with the worst case destination weight + let (_, pool_dest1) = create_pool_account::(USER_SEED + 3, dest_weight); + T::StakingInterface::nominate( + pool_dest1.clone(), + vec![account("random_validator", 0, USER_SEED)], + )?; + + let weight_of = pallet_staking::Pallet::::weight_of_fn(); + assert_eq!(vote_to_balance::(weight_of(&pool_origin1)).unwrap(), origin_weight); + assert_eq!(vote_to_balance::(weight_of(&pool_origin2)).unwrap(), origin_weight); + assert_eq!(vote_to_balance::(weight_of(&pool_dest1)).unwrap(), dest_weight); + + Ok(ListScenario { + origin1: pool_origin1, + creator1: pool_creator1, + dest_weight, + origin1_member: None, + }) + } + + fn add_joiner(mut self, amount: BalanceOf) -> Self { + let amount = MinJoinBond::::get() + .max(CurrencyOf::::minimum_balance()) + // Max `amount` with minimum thresholds for account balance and joining a pool + // to ensure 1) the user can be created and 2) can join the pool + .max(amount); + + let joiner: T::AccountId = account("joiner", USER_SEED, 0); + self.origin1_member = Some(joiner.clone()); + CurrencyOf::::make_free_balance_be(&joiner, amount * 2u32.into()); + + let original_bonded = T::StakingInterface::active_stake(&self.origin1).unwrap(); + + // Unbond `amount` from the underlying pool account so when the member joins + // we will maintain `current_bonded`. + T::StakingInterface::unbond(self.origin1.clone(), amount) + .expect("the pool was created in `Self::new`."); + + // Account pool points for the unbonded balance. + BondedPools::::mutate(&1, |maybe_pool| { + maybe_pool.as_mut().map(|pool| pool.points -= amount) + }); + + Pools::::join(Origin::Signed(joiner.clone()).into(), amount, 1).unwrap(); + + // Sanity check that the vote weight is still the same as the original bonded + let weight_of = pallet_staking::Pallet::::weight_of_fn(); + assert_eq!(vote_to_balance::(weight_of(&self.origin1)).unwrap(), original_bonded); + + // Sanity check the member was added correctly + let member = PoolMembers::::get(&joiner).unwrap(); + assert_eq!(member.points, amount); + assert_eq!(member.pool_id, 1); + + self + } +} + +frame_benchmarking::benchmarks! { + join { + let origin_weight = pallet_nomination_pools::MinCreateBond::::get() + .max(CurrencyOf::::minimum_balance()) + * 2u32.into(); + + // setup the worst case list scenario. + let scenario = ListScenario::::new(origin_weight, true)?; + assert_eq!( + T::StakingInterface::active_stake(&scenario.origin1).unwrap(), + origin_weight + ); + + let max_additional = scenario.dest_weight.clone() - origin_weight; + let joiner_free = CurrencyOf::::minimum_balance() + max_additional; + + let joiner: T::AccountId + = create_funded_user_with_balance::("joiner", 0, joiner_free); + + whitelist_account!(joiner); + }: _(Origin::Signed(joiner.clone()), max_additional, 1) + verify { + assert_eq!(CurrencyOf::::free_balance(&joiner), joiner_free - max_additional); + assert_eq!( + T::StakingInterface::active_stake(&scenario.origin1).unwrap(), + scenario.dest_weight + ); + } + + bond_extra_transfer { + let origin_weight = pallet_nomination_pools::MinCreateBond::::get() + .max(CurrencyOf::::minimum_balance()) + * 2u32.into(); + let scenario = ListScenario::::new(origin_weight, true)?; + let extra = scenario.dest_weight.clone() - origin_weight; + + // creator of the src pool will bond-extra, bumping itself to dest bag. + + }: bond_extra(Origin::Signed(scenario.creator1.clone()), BondExtra::FreeBalance(extra)) + verify { + assert!( + T::StakingInterface::active_stake(&scenario.origin1).unwrap() >= + scenario.dest_weight + ); + } + + bond_extra_reward { + let origin_weight = pallet_nomination_pools::MinCreateBond::::get() + .max(CurrencyOf::::minimum_balance()) + * 2u32.into(); + let scenario = ListScenario::::new(origin_weight, true)?; + let extra = (scenario.dest_weight.clone() - origin_weight).max(CurrencyOf::::minimum_balance()); + + // transfer exactly `extra` to the depositor of the src pool (1), + let reward_account1 = Pools::::create_reward_account(1); + assert!(extra >= CurrencyOf::::minimum_balance()); + CurrencyOf::::deposit_creating(&reward_account1, extra); + + }: bond_extra(Origin::Signed(scenario.creator1.clone()), BondExtra::Rewards) + verify { + assert!( + T::StakingInterface::active_stake(&scenario.origin1).unwrap() >= + scenario.dest_weight + ); + } + + claim_payout { + let origin_weight = pallet_nomination_pools::MinCreateBond::::get().max(CurrencyOf::::minimum_balance()) * 2u32.into(); + let ed = CurrencyOf::::minimum_balance(); + let (depositor, pool_account) = create_pool_account::(0, origin_weight); + let reward_account = Pools::::create_reward_account(1); + + // Send funds to the reward account of the pool + CurrencyOf::::make_free_balance_be(&reward_account, ed + origin_weight); + + // Sanity check + assert_eq!( + CurrencyOf::::free_balance(&depositor), + origin_weight + ); + + whitelist_account!(depositor); + }:_(Origin::Signed(depositor.clone())) + verify { + assert_eq!( + CurrencyOf::::free_balance(&depositor), + origin_weight * 2u32.into() + ); + assert_eq!( + CurrencyOf::::free_balance(&reward_account), + ed + Zero::zero() + ); + } + + unbond { + // The weight the nominator will start at. The value used here is expected to be + // significantly higher than the first position in a list (e.g. the first bag threshold). + let origin_weight = BalanceOf::::try_from(952_994_955_240_703u128) + .map_err(|_| "balance expected to be a u128") + .unwrap(); + let scenario = ListScenario::::new(origin_weight, false)?; + let amount = origin_weight - scenario.dest_weight.clone(); + + let scenario = scenario.add_joiner(amount); + let member_id = scenario.origin1_member.unwrap().clone(); + let all_points = PoolMembers::::get(&member_id).unwrap().points; + whitelist_account!(member_id); + }: _(Origin::Signed(member_id.clone()), member_id.clone(), all_points) + verify { + let bonded_after = T::StakingInterface::active_stake(&scenario.origin1).unwrap(); + // We at least went down to the destination bag + assert!(bonded_after <= scenario.dest_weight.clone()); + let member = PoolMembers::::get( + &member_id + ) + .unwrap(); + assert_eq!( + member.unbonding_eras.keys().cloned().collect::>(), + vec![0 + T::StakingInterface::bonding_duration()] + ); + assert_eq!( + member.unbonding_eras.values().cloned().collect::>(), + vec![all_points] + ); + } + + pool_withdraw_unbonded { + let s in 0 .. MAX_SPANS; + + let min_create_bond = MinCreateBond::::get() + .max(T::StakingInterface::minimum_bond()) + .max(CurrencyOf::::minimum_balance()); + let (depositor, pool_account) = create_pool_account::(0, min_create_bond); + + // Add a new member + let min_join_bond = MinJoinBond::::get().max(CurrencyOf::::minimum_balance()); + let joiner = create_funded_user_with_balance::("joiner", 0, min_join_bond * 2u32.into()); + Pools::::join(Origin::Signed(joiner.clone()).into(), min_join_bond, 1) + .unwrap(); + + // Sanity check join worked + assert_eq!( + T::StakingInterface::active_stake(&pool_account).unwrap(), + min_create_bond + min_join_bond + ); + assert_eq!(CurrencyOf::::free_balance(&joiner), min_join_bond); + + // Unbond the new member + Pools::::fully_unbond(Origin::Signed(joiner.clone()).into(), joiner.clone()).unwrap(); + + // Sanity check that unbond worked + assert_eq!( + T::StakingInterface::active_stake(&pool_account).unwrap(), + min_create_bond + ); + assert_eq!(pallet_staking::Ledger::::get(&pool_account).unwrap().unlocking.len(), 1); + // Set the current era + pallet_staking::CurrentEra::::put(EraIndex::max_value()); + + // Add `s` count of slashing spans to storage. + pallet_staking::benchmarking::add_slashing_spans::(&pool_account, s); + whitelist_account!(pool_account); + }: _(Origin::Signed(pool_account.clone()), 1, s) + verify { + // The joiners funds didn't change + assert_eq!(CurrencyOf::::free_balance(&joiner), min_join_bond); + // The unlocking chunk was removed + assert_eq!(pallet_staking::Ledger::::get(pool_account).unwrap().unlocking.len(), 0); + } + + withdraw_unbonded_update { + let s in 0 .. MAX_SPANS; + + let min_create_bond = MinCreateBond::::get() + .max(T::StakingInterface::minimum_bond()) + .max(CurrencyOf::::minimum_balance()); + let (depositor, pool_account) = create_pool_account::(0, min_create_bond); + + // Add a new member + let min_join_bond = MinJoinBond::::get().max(CurrencyOf::::minimum_balance()); + let joiner = create_funded_user_with_balance::("joiner", 0, min_join_bond * 2u32.into()); + Pools::::join(Origin::Signed(joiner.clone()).into(), min_join_bond, 1) + .unwrap(); + + // Sanity check join worked + assert_eq!( + T::StakingInterface::active_stake(&pool_account).unwrap(), + min_create_bond + min_join_bond + ); + assert_eq!(CurrencyOf::::free_balance(&joiner), min_join_bond); + + // Unbond the new member + pallet_staking::CurrentEra::::put(0); + Pools::::fully_unbond(Origin::Signed(joiner.clone()).into(), joiner.clone()).unwrap(); + + // Sanity check that unbond worked + assert_eq!( + T::StakingInterface::active_stake(&pool_account).unwrap(), + min_create_bond + ); + assert_eq!(pallet_staking::Ledger::::get(&pool_account).unwrap().unlocking.len(), 1); + + // Set the current era to ensure we can withdraw unbonded funds + pallet_staking::CurrentEra::::put(EraIndex::max_value()); + + pallet_staking::benchmarking::add_slashing_spans::(&pool_account, s); + whitelist_account!(joiner); + }: withdraw_unbonded(Origin::Signed(joiner.clone()), joiner.clone(), s) + verify { + assert_eq!( + CurrencyOf::::free_balance(&joiner), + min_join_bond * 2u32.into() + ); + // The unlocking chunk was removed + assert_eq!(pallet_staking::Ledger::::get(&pool_account).unwrap().unlocking.len(), 0); + } + + withdraw_unbonded_kill { + let s in 0 .. MAX_SPANS; + + let min_create_bond = MinCreateBond::::get() + .max(T::StakingInterface::minimum_bond()) + .max(CurrencyOf::::minimum_balance()); + + let (depositor, pool_account) = create_pool_account::(0, min_create_bond); + + // We set the pool to the destroying state so the depositor can leave + BondedPools::::try_mutate(&1, |maybe_bonded_pool| { + maybe_bonded_pool.as_mut().ok_or(()).map(|bonded_pool| { + bonded_pool.state = PoolState::Destroying; + }) + }) + .unwrap(); + + // Unbond the creator + pallet_staking::CurrentEra::::put(0); + // Simulate some rewards so we can check if the rewards storage is cleaned up. We check this + // here to ensure the complete flow for destroying a pool works - the reward pool account + // should never exist by time the depositor withdraws so we test that it gets cleaned + // up when unbonding. + let reward_account = Pools::::create_reward_account(1); + assert!(frame_system::Account::::contains_key(&reward_account)); + Pools::::fully_unbond(Origin::Signed(depositor.clone()).into(), depositor.clone()).unwrap(); + + // Sanity check that unbond worked + assert_eq!( + T::StakingInterface::active_stake(&pool_account).unwrap(), + Zero::zero() + ); + assert_eq!( + CurrencyOf::::free_balance(&pool_account), + min_create_bond + ); + assert_eq!(pallet_staking::Ledger::::get(&pool_account).unwrap().unlocking.len(), 1); + + // Set the current era to ensure we can withdraw unbonded funds + pallet_staking::CurrentEra::::put(EraIndex::max_value()); + + // Some last checks that storage items we expect to get cleaned up are present + assert!(pallet_staking::Ledger::::contains_key(&pool_account)); + assert!(BondedPools::::contains_key(&1)); + assert!(SubPoolsStorage::::contains_key(&1)); + assert!(RewardPools::::contains_key(&1)); + assert!(PoolMembers::::contains_key(&depositor)); + assert!(frame_system::Account::::contains_key(&reward_account)); + + whitelist_account!(depositor); + }: withdraw_unbonded(Origin::Signed(depositor.clone()), depositor.clone(), s) + verify { + // Pool removal worked + assert!(!pallet_staking::Ledger::::contains_key(&pool_account)); + assert!(!BondedPools::::contains_key(&1)); + assert!(!SubPoolsStorage::::contains_key(&1)); + assert!(!RewardPools::::contains_key(&1)); + assert!(!PoolMembers::::contains_key(&depositor)); + assert!(!frame_system::Account::::contains_key(&pool_account)); + assert!(!frame_system::Account::::contains_key(&reward_account)); + + // Funds where transferred back correctly + assert_eq!( + CurrencyOf::::free_balance(&depositor), + // gets bond back + rewards collecting when unbonding + min_create_bond * 2u32.into() + CurrencyOf::::minimum_balance() + ); + } + + create { + let min_create_bond = MinCreateBond::::get() + .max(T::StakingInterface::minimum_bond()) + .max(CurrencyOf::::minimum_balance()); + let depositor: T::AccountId = account("depositor", USER_SEED, 0); + + // Give the depositor some balance to bond + CurrencyOf::::make_free_balance_be(&depositor, min_create_bond * 2u32.into()); + + // Make sure no pools exist as a pre-condition for our verify checks + assert_eq!(RewardPools::::count(), 0); + assert_eq!(BondedPools::::count(), 0); + + whitelist_account!(depositor); + }: _( + Origin::Signed(depositor.clone()), + min_create_bond, + depositor.clone(), + depositor.clone(), + depositor.clone() + ) + verify { + assert_eq!(RewardPools::::count(), 1); + assert_eq!(BondedPools::::count(), 1); + let (_, new_pool) = BondedPools::::iter().next().unwrap(); + assert_eq!( + new_pool, + BondedPoolInner { + points: min_create_bond, + state: PoolState::Open, + member_counter: 1, + roles: PoolRoles { + depositor: depositor.clone(), + root: depositor.clone(), + nominator: depositor.clone(), + state_toggler: depositor.clone(), + }, + } + ); + assert_eq!( + T::StakingInterface::active_stake(&Pools::::create_bonded_account(1)), + Some(min_create_bond) + ); + } + + nominate { + let n in 1 .. T::MaxNominations::get(); + + // Create a pool + let min_create_bond = MinCreateBond::::get() + .max(T::StakingInterface::minimum_bond()) + .max(CurrencyOf::::minimum_balance()); + let (depositor, pool_account) = create_pool_account::(0, min_create_bond); + + // Create some accounts to nominate. For the sake of benchmarking they don't need to be + // actual validators + let validators: Vec<_> = (0..n) + .map(|i| account("stash", USER_SEED, i)) + .collect(); + + whitelist_account!(depositor); + }:_(Origin::Signed(depositor.clone()), 1, validators) + verify { + assert_eq!(RewardPools::::count(), 1); + assert_eq!(BondedPools::::count(), 1); + let (_, new_pool) = BondedPools::::iter().next().unwrap(); + assert_eq!( + new_pool, + BondedPoolInner { + points: min_create_bond, + state: PoolState::Open, + member_counter: 1, + roles: PoolRoles { + depositor: depositor.clone(), + root: depositor.clone(), + nominator: depositor.clone(), + state_toggler: depositor.clone(), + } + } + ); + assert_eq!( + T::StakingInterface::active_stake(&Pools::::create_bonded_account(1)), + Some(min_create_bond) + ); + } + + set_state { + // Create a pool + let min_create_bond = MinCreateBond::::get() + .max(T::StakingInterface::minimum_bond()) + .max(CurrencyOf::::minimum_balance()); + let (depositor, pool_account) = create_pool_account::(0, min_create_bond); + BondedPools::::mutate(&1, |maybe_pool| { + // Force the pool into an invalid state + maybe_pool.as_mut().map(|mut pool| pool.points = min_create_bond * 10u32.into()); + }); + + let caller = account("caller", 0, USER_SEED); + whitelist_account!(caller); + }:_(Origin::Signed(caller), 1, PoolState::Destroying) + verify { + assert_eq!(BondedPools::::get(1).unwrap().state, PoolState::Destroying); + } + + set_metadata { + let n in 1 .. ::MaxMetadataLen::get(); + + // Create a pool + let min_create_bond = MinCreateBond::::get() + .max(T::StakingInterface::minimum_bond()) + .max(CurrencyOf::::minimum_balance()); + let (depositor, pool_account) = create_pool_account::(0, min_create_bond); + + // Create metadata of the max possible size + let metadata: Vec = (0..n).map(|_| 42).collect(); + + whitelist_account!(depositor); + }:_(Origin::Signed(depositor), 1, metadata.clone()) + verify { + assert_eq!(Metadata::::get(&1), metadata); + } + + set_configs { + }:_( + Origin::Root, + ConfigOp::Set(BalanceOf::::max_value()), + ConfigOp::Set(BalanceOf::::max_value()), + ConfigOp::Set(u32::MAX), + ConfigOp::Set(u32::MAX), + ConfigOp::Set(u32::MAX) + ) verify { + assert_eq!(MinJoinBond::::get(), BalanceOf::::max_value()); + assert_eq!(MinCreateBond::::get(), BalanceOf::::max_value()); + assert_eq!(MaxPools::::get(), Some(u32::MAX)); + assert_eq!(MaxPoolMembers::::get(), Some(u32::MAX)); + assert_eq!(MaxPoolMembersPerPool::::get(), Some(u32::MAX)); + } + + impl_benchmark_test_suite!( + Pallet, + crate::mock::new_test_ext(), + crate::mock::Runtime + ); +} diff --git a/frame/nomination-pools/benchmarking/src/mock.rs b/frame/nomination-pools/benchmarking/src/mock.rs new file mode 100644 index 0000000000000..d98bcb542f514 --- /dev/null +++ b/frame/nomination-pools/benchmarking/src/mock.rs @@ -0,0 +1,200 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use frame_election_provider_support::VoteWeight; +use frame_support::{pallet_prelude::*, parameter_types, traits::ConstU64, PalletId}; +use sp_runtime::traits::{Convert, IdentityLookup}; + +type AccountId = u128; +type AccountIndex = u32; +type BlockNumber = u64; +type Balance = u128; + +impl frame_system::Config for Runtime { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type 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 = IdentityLookup; + type Header = sp_runtime::testing::Header; + type Event = Event; + type BlockHashCount = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +impl pallet_timestamp::Config for Runtime { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = ConstU64<5>; + type WeightInfo = (); +} + +parameter_types! { + pub const ExistentialDeposit: Balance = 10; +} +impl pallet_balances::Config for Runtime { + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type Balance = Balance; + type Event = Event; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); +} + +pallet_staking_reward_curve::build! { + const I_NPOS: sp_runtime::curve::PiecewiseLinear<'static> = curve!( + min_inflation: 0_025_000, + max_inflation: 0_100_000, + ideal_stake: 0_500_000, + falloff: 0_050_000, + max_piece_count: 40, + test_precision: 0_005_000, + ); +} +parameter_types! { + pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS; +} +impl pallet_staking::Config for Runtime { + type MaxNominations = ConstU32<16>; + type Currency = Balances; + type CurrencyBalance = Balance; + type UnixTime = pallet_timestamp::Pallet; + type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; + type RewardRemainder = (); + type Event = Event; + type Slash = (); + type Reward = (); + type SessionsPerEra = (); + type SlashDeferDuration = (); + type SlashCancelOrigin = frame_system::EnsureRoot; + type BondingDuration = ConstU32<3>; + type SessionInterface = (); + type EraPayout = pallet_staking::ConvertCurve; + type NextNewSession = (); + type MaxNominatorRewardedPerValidator = ConstU32<64>; + type OffendingValidatorsThreshold = (); + type ElectionProvider = + frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking)>; + type GenesisElectionProvider = Self::ElectionProvider; + type VoterList = pallet_bags_list::Pallet; + type MaxUnlockingChunks = ConstU32<32>; + type OnStakerSlash = Pools; + type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; + type WeightInfo = (); +} + +parameter_types! { + pub static BagThresholds: &'static [VoteWeight] = &[10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000]; +} + +impl pallet_bags_list::Config for Runtime { + type Event = Event; + type WeightInfo = (); + type BagThresholds = BagThresholds; + type ScoreProvider = Staking; + type Score = VoteWeight; +} + +pub struct BalanceToU256; +impl Convert for BalanceToU256 { + fn convert(n: Balance) -> sp_core::U256 { + n.into() + } +} + +pub struct U256ToBalance; +impl Convert for U256ToBalance { + fn convert(n: sp_core::U256) -> Balance { + n.try_into().unwrap() + } +} + +parameter_types! { + pub static PostUnbondingPoolsWindow: u32 = 10; + pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls"); +} + +impl pallet_nomination_pools::Config for Runtime { + type Event = Event; + type WeightInfo = (); + type Currency = Balances; + type BalanceToU256 = BalanceToU256; + type U256ToBalance = U256ToBalance; + type StakingInterface = Staking; + type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow; + type MaxMetadataLen = ConstU32<256>; + type MaxUnbonding = ConstU32<8>; + type PalletId = PoolsPalletId; +} + +impl crate::Config for Runtime {} + +impl frame_system::offchain::SendTransactionTypes for Runtime +where + Call: From, +{ + type OverarchingCall = Call; + type Extrinsic = UncheckedExtrinsic; +} + +type Block = frame_system::mocking::MockBlock; +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +frame_support::construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: frame_system::{Pallet, Call, Event}, + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Staking: pallet_staking::{Pallet, Call, Config, Storage, Event}, + BagsList: pallet_bags_list::{Pallet, Call, Storage, Event}, + Pools: pallet_nomination_pools::{Pallet, Call, Storage, Event}, + } +); + +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let _ = pallet_nomination_pools::GenesisConfig:: { + min_join_bond: 2, + min_create_bond: 2, + max_pools: Some(3), + max_members_per_pool: Some(3), + max_members: Some(3 * 3), + } + .assimilate_storage(&mut storage); + sp_io::TestExternalities::from(storage) +} diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs new file mode 100644 index 0000000000000..bafed9fc2f5b4 --- /dev/null +++ b/frame/nomination-pools/src/lib.rs @@ -0,0 +1,2217 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Nomination Pools for Staking Delegation +//! +//! A pallet that allows members to delegate their stake to nominating pools. A nomination pool +//! acts as nominator and nominates validators on the members behalf. +//! +//! # Index +//! +//! * [Key terms](#key-terms) +//! * [Usage](#usage) +//! * [Design](#design) +//! +//! ## Key terms +//! +//! * bonded pool: Tracks the distribution of actively staked funds. See [`BondedPool`] and +//! [`BondedPoolInner`]. Bonded pools are identified via the pools bonded account. +//! * reward pool: Tracks rewards earned by actively staked funds. See [`RewardPool`] and +//! [`RewardPools`]. Reward pools are identified via the pools bonded account. +//! * unbonding sub pools: Collection of pools at different phases of the unbonding lifecycle. See +//! [`SubPools`] and [`SubPoolsStorage`]. Sub pools are identified via the pools bonded account. +//! * members: Accounts that are members of pools. See [`PoolMember`] and [`PoolMembers`]. Pool +//! members are identified via their account. +//! * point: A unit of measure for a members portion of a pool's funds. +//! * kick: The act of a pool administrator forcibly ejecting a member. +//! +//! ## Usage +//! +//! ### Join +//! +//! A account can stake funds with a nomination pool by calling [`Call::join`]. +//! +//! ### Claim rewards +//! +//! After joining a pool, a member can claim rewards by calling [`Call::claim_payout`]. +//! +//! For design docs see the [reward pool](#reward-pool) section. +//! +//! ### Leave +//! +//! In order to leave, a member must take two steps. +//! +//! First, they must call [`Call::unbond`]. The unbond other extrinsic will start the +//! unbonding process by unbonding all of the members funds. +//! +//! Second, once [`sp_staking::StakingInterface::bonding_duration`] eras have passed, the member +//! can call [`Call::withdraw_unbonded`] to withdraw all their funds. +//! +//! For design docs see the [bonded pool](#bonded-pool) and [unbonding sub +//! pools](#unbonding-sub-pools) sections. +//! +//! ### Slashes +//! +//! Slashes are distributed evenly across the bonded pool and the unbonding pools from slash era+1 +//! through the slash apply era. Thus, any member who either a) unbonded or b) was actively +//! bonded in the aforementioned range of eras will be affected by the slash. A member is slashed +//! pro-rata based on its stake relative to the total slash amount. +//! +//! For design docs see the [slashing](#slashing) section. +//! +//! ### Administration +//! +//! A pool can be created with the [`Call::create`] call. Once created, the pools nominator or root +//! user must call [`Call::nominate`] to start nominating. [`Call::nominate`] can be called at +//! anytime to update validator selection. +//! +//! To help facilitate pool administration the pool has one of three states (see [`PoolState`]): +//! +//! * Open: Anyone can join the pool and no members can be permissionlessly removed. +//! * Blocked: No members can join and some admin roles can kick members. +//! * Destroying: No members can join and all members can be permissionlessly removed with +//! [`Call::unbond`] and [`Call::withdraw_unbonded`]. Once a pool is in destroying state, it +//! cannot be reverted to another state. +//! +//! A pool has 3 administrative roles (see [`PoolRoles`]): +//! +//! * Depositor: creates the pool and is the initial member. They can only leave the pool once all +//! other members have left. Once they fully leave the pool is destroyed. +//! * Nominator: can select which validators the pool nominates. +//! * State-Toggler: can change the pools state and kick members if the pool is blocked. +//! * Root: can change the nominator, state-toggler, or itself and can perform any of the actions +//! the nominator or state-toggler can. +//! +//! ## Design +//! +//! _Notes_: this section uses pseudo code to explain general design and does not necessarily +//! reflect the exact implementation. Additionally, a working knowledge of `pallet-staking`'s api is +//! assumed. +//! +//! ### Goals +//! +//! * Maintain network security by upholding integrity of slashing events, sufficiently penalizing +//! members that where in the pool while it was backing a validator that got slashed. +//! * Maximize scalability in terms of member count. +//! +//! In order to maintain scalability, all operations are independent of the number of members. To +//! do this, delegation specific information is stored local to the member while the pool data +//! structures have bounded datum. +//! +//! ### Bonded pool +//! +//! A bonded pool nominates with its total balance, excluding that which has been withdrawn for +//! unbonding. The total points of a bonded pool are always equal to the sum of points of the +//! delegation members. A bonded pool tracks its points and reads its bonded balance. +//! +//! When a member joins a pool, `amount_transferred` is transferred from the members account +//! to the bonded pools account. Then the pool calls `staking::bond_extra(amount_transferred)` and +//! issues new points which are tracked by the member and added to the bonded pool's points. +//! +//! When the pool already has some balance, we want the value of a point before the transfer to +//! equal the value of a point after the transfer. So, when a member joins a bonded pool with a +//! given `amount_transferred`, we maintain the ratio of bonded balance to points such that: +//! +//! ```text +//! balance_after_transfer / points_after_transfer == balance_before_transfer / points_before_transfer; +//! ``` +//! +//! To achieve this, we issue points based on the following: +//! +//! ```text +//! points_issued = (points_before_transfer / balance_before_transfer) * amount_transferred; +//! ``` +//! +//! For new bonded pools we can set the points issued per balance arbitrarily. In this +//! implementation we use a 1 points to 1 balance ratio for pool creation (see +//! [`POINTS_TO_BALANCE_INIT_RATIO`]). +//! +//! **Relevant extrinsics:** +//! +//! * [`Call::create`] +//! * [`Call::join`] +//! +//! ### Reward pool +//! +//! When a pool is first bonded it sets up an deterministic, inaccessible account as its reward +//! destination. To track staking rewards we track how the balance of this reward account changes. +//! +//! The reward pool needs to store: +//! +//! * The pool balance at the time of the last payout: `reward_pool.balance` +//! * The total earnings ever at the time of the last payout: `reward_pool.total_earnings` +//! * The total points in the pool at the time of the last payout: `reward_pool.points` +//! +//! And the member needs to store: +//! +//! * The total payouts at the time of the last payout by that member: +//! `member.reward_pool_total_earnings` +//! +//! Before the first reward claim is initiated for a pool, all the above variables are set to zero. +//! +//! When a member initiates a claim, the following happens: +//! +//! 1) Compute the reward pool's total points and the member's virtual points in the reward pool +//! * First `current_total_earnings` is computed (`current_balance` is the free balance of the +//! reward pool at the beginning of these operations.) +//! ```text +//! current_total_earnings = +//! current_balance - reward_pool.balance + pool.total_earnings; +//! ``` +//! * Then the `current_points` is computed. Every balance unit that was added to the reward +//! pool since last time recorded means that the `pool.points` is increased by +//! `bonding_pool.total_points`. In other words, for every unit of balance that has been +//! earned by the reward pool, the reward pool points are inflated by `bonded_pool.points`. In +//! effect this allows each, single unit of balance (e.g. planck) to be divvied up pro-rata +//! among members based on points. +//! ```text +//! new_earnings = current_total_earnings - reward_pool.total_earnings; +//! current_points = reward_pool.points + bonding_pool.points * new_earnings; +//! ``` +//! * Finally, the`member_virtual_points` are computed: the product of the member's points in +//! the bonding pool and the total inflow of balance units since the last time the member +//! claimed rewards +//! ```text +//! new_earnings_since_last_claim = current_total_earnings - member.reward_pool_total_earnings; +//! member_virtual_points = member.points * new_earnings_since_last_claim; +//! ``` +//! 2) Compute the `member_payout`: +//! ```text +//! member_pool_point_ratio = member_virtual_points / current_points; +//! member_payout = current_balance * member_pool_point_ratio; +//! ``` +//! 3) Transfer `member_payout` to the member +//! 4) For the member set: +//! ```text +//! member.reward_pool_total_earnings = current_total_earnings; +//! ``` +//! 5) For the pool set: +//! ```text +//! reward_pool.points = current_points - member_virtual_points; +//! reward_pool.balance = current_balance - member_payout; +//! reward_pool.total_earnings = current_total_earnings; +//! ``` +//! +//! _Note_: One short coming of this design is that new joiners can claim rewards for the era after +//! they join even though their funds did not contribute to the pools vote weight. When a +//! member joins, it's `reward_pool_total_earnings` field is set equal to the `total_earnings` +//! of the reward pool at that point in time. At best the reward pool has the rewards up through the +//! previous era. If a member joins prior to the election snapshot it will benefit from the +//! rewards for the active era despite not contributing to the pool's vote weight. If it joins +//! after the election snapshot is taken it will benefit from the rewards of the next _2_ eras +//! because it's vote weight will not be counted until the election snapshot in active era + 1. +//! Related: +// _Note to maintainers_: In order to ensure the reward account never falls below the existential +// deposit, at creation the reward account must be endowed with the existential deposit. All logic +// for calculating rewards then does not see that existential deposit as part of the free balance. +// See `RewardPool::current_balance`. +//! +//! **Relevant extrinsics:** +//! +//! * [`Call::claim_payout`] +//! +//! ### Unbonding sub pools +//! +//! When a member unbonds, it's balance is unbonded in the bonded pool's account and tracked in +//! an unbonding pool associated with the active era. If no such pool exists, one is created. To +//! track which unbonding sub pool a member belongs too, a member tracks it's +//! `unbonding_era`. +//! +//! When a member initiates unbonding it's claim on the bonded pool +//! (`balance_to_unbond`) is computed as: +//! +//! ```text +//! balance_to_unbond = (bonded_pool.balance / bonded_pool.points) * member.points; +//! ``` +//! +//! If this is the first transfer into an unbonding pool arbitrary amount of points can be issued +//! per balance. In this implementation unbonding pools are initialized with a 1 point to 1 balance +//! ratio (see [`POINTS_TO_BALANCE_INIT_RATIO`]). Otherwise, the unbonding pools hold the same +//! points to balance ratio properties as the bonded pool, so member points in the +//! unbonding pool are issued based on +//! +//! ```text +//! new_points_issued = (points_before_transfer / balance_before_transfer) * balance_to_unbond; +//! ``` +//! +//! For scalability, a bound is maintained on the number of unbonding sub pools (see +//! [`TotalUnbondingPools`]). An unbonding pool is removed once its older than `current_era - +//! TotalUnbondingPools`. An unbonding pool is merged into the unbonded pool with +//! +//! ```text +//! unbounded_pool.balance = unbounded_pool.balance + unbonding_pool.balance; +//! unbounded_pool.points = unbounded_pool.points + unbonding_pool.points; +//! ``` +//! +//! This scheme "averages" out the points value in the unbonded pool. +//! +//! Once a members `unbonding_era` is older than `current_era - +//! [sp_staking::StakingInterface::bonding_duration]`, it can can cash it's points out of the +//! corresponding unbonding pool. If it's `unbonding_era` is older than `current_era - +//! TotalUnbondingPools`, it can cash it's points from the unbonded pool. +//! +//! **Relevant extrinsics:** +//! +//! * [`Call::unbond`] +//! * [`Call::withdraw_unbonded`] +//! +//! ### Slashing +//! +//! This section assumes that the slash computation is executed by +//! `pallet_staking::StakingLedger::slash`, which passes the information to this pallet via +//! [`sp_staking::OnStakerSlash::on_slash`]. +//! +//! Unbonding pools need to be slashed to ensure all nominators whom where in the bonded pool +//! while it was backing a validator that equivocated are punished. Without these measures a +//! member could unbond right after a validator equivocated with no consequences. +//! +//! This strategy is unfair to members who joined after the slash, because they get slashed as +//! well, but spares members who unbond. The latter is much more important for security: if a +//! pool's validators are attacking the network, their members need to unbond fast! Avoiding +//! slashes gives them an incentive to do that if validators get repeatedly slashed. +//! +//! To be fair to joiners, this implementation also need joining pools, which are actively staking, +//! in addition to the unbonding pools. For maintenance simplicity these are not implemented. +//! Related: +//! +//! **Relevant methods:** +//! +//! * [`Pallet::on_slash`] +//! +//! ### Limitations +//! +//! * PoolMembers cannot vote with their staked funds because they are transferred into the pools +//! account. In the future this can be overcome by allowing the members to vote with their bonded +//! funds via vote splitting. +//! * PoolMembers cannot quickly transfer to another pool if they do no like nominations, instead +//! they must wait for the unbonding duration. +//! +//! # Runtime builder warnings +//! +//! * Watch out for overflow of [`RewardPoints`] and [`BalanceOf`] types. Consider things like the +//! chains total issuance, staking reward rate, and burn rate. + +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::Codec; +use frame_support::{ + defensive, ensure, + pallet_prelude::{MaxEncodedLen, *}, + storage::bounded_btree_map::BoundedBTreeMap, + traits::{ + Currency, Defensive, DefensiveOption, DefensiveResult, DefensiveSaturating, + ExistenceRequirement, Get, + }, + CloneNoBound, DefaultNoBound, PartialEqNoBound, RuntimeDebugNoBound, +}; +use scale_info::TypeInfo; +use sp_core::U256; +use sp_runtime::traits::{AccountIdConversion, Bounded, CheckedSub, Convert, Saturating, Zero}; +use sp_staking::{EraIndex, OnStakerSlash, StakingInterface}; +use sp_std::{collections::btree_map::BTreeMap, fmt::Debug, ops::Div, vec::Vec}; + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +pub mod weights; + +pub use pallet::*; +pub use weights::WeightInfo; + +/// The balance type used by the currency system. +pub type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; +/// Type used to track the points of a reward pool. +pub type RewardPoints = U256; +/// Type used for unique identifier of each pool. +pub type PoolId = u32; + +type UnbondingPoolsWithEra = BoundedBTreeMap, TotalUnbondingPools>; + +pub const POINTS_TO_BALANCE_INIT_RATIO: u32 = 1; + +/// Possible operations on the configuration values of this pallet. +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound, PartialEq, Clone)] +pub enum ConfigOp { + /// Don't change. + Noop, + /// Set the given value. + Set(T), + /// Remove from storage. + Remove, +} + +/// The type of bonding that can happen to a pool. +enum BondType { + /// Someone is bonding into the pool upon creation. + Create, + /// Someone is adding more funds later to this pool. + Later, +} + +/// How to increase the bond of a member. +#[derive(Encode, Decode, Clone, Copy, Debug, PartialEq, Eq, TypeInfo)] +pub enum BondExtra { + /// Take from the free balance. + FreeBalance(Balance), + /// Take the entire amount from the accumulated rewards. + Rewards, +} + +/// The type of account being created. +#[derive(Encode, Decode)] +enum AccountType { + Bonded, + Reward, +} + +/// A member in a pool. +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound, CloneNoBound)] +#[cfg_attr(feature = "std", derive(PartialEqNoBound, DefaultNoBound))] +#[codec(mel_bound(T: Config))] +#[scale_info(skip_type_params(T))] +pub struct PoolMember { + /// The identifier of the pool to which `who` belongs. + pub pool_id: PoolId, + /// The quantity of points this member has in the bonded pool or in a sub pool if + /// `Self::unbonding_era` is some. + pub points: BalanceOf, + /// The reward pools total earnings _ever_ the last time this member claimed a payout. + /// Assuming no massive burning events, we expect this value to always be below total issuance. + /// This value lines up with the [`RewardPool::total_earnings`] after a member claims a + /// payout. + pub reward_pool_total_earnings: BalanceOf, + /// The eras in which this member is unbonding, mapped from era index to the number of + /// points scheduled to unbond in the given era. + pub unbonding_eras: BoundedBTreeMap, T::MaxUnbonding>, +} + +impl PoolMember { + #[cfg(any(test, debug_assertions))] + fn total_points(&self) -> BalanceOf { + self.active_points().saturating_add(self.unbonding_points()) + } + + /// Active balance of the member. + /// + /// This is derived from the ratio of points in the pool to which the member belongs to. + /// Might return different values based on the pool state for the same member and points. + pub(crate) fn active_balance(&self) -> BalanceOf { + if let Some(pool) = BondedPool::::get(self.pool_id).defensive() { + pool.points_to_balance(self.points) + } else { + Zero::zero() + } + } + + /// Active points of the member. + pub(crate) fn active_points(&self) -> BalanceOf { + self.points + } + + /// Inactive points of the member, waiting to be withdrawn. + pub(crate) fn unbonding_points(&self) -> BalanceOf { + self.unbonding_eras + .as_ref() + .iter() + .fold(BalanceOf::::zero(), |acc, (_, v)| acc.saturating_add(*v)) + } + + /// Try and unbond `points` from self, with the given target unbonding era. + /// + /// Returns `Ok(())` and updates `unbonding_eras` and `points` if success, `Err(_)` otherwise. + fn try_unbond( + &mut self, + points: BalanceOf, + unbonding_era: EraIndex, + ) -> Result<(), Error> { + if let Some(new_points) = self.points.checked_sub(&points) { + match self.unbonding_eras.get_mut(&unbonding_era) { + Some(already_unbonding_points) => + *already_unbonding_points = already_unbonding_points.saturating_add(points), + None => self + .unbonding_eras + .try_insert(unbonding_era, points) + .map(|old| { + if old.is_some() { + defensive!("value checked to not exist in the map; qed"); + } + }) + .map_err(|_| Error::::MaxUnbondingLimit)?, + } + self.points = new_points; + Ok(()) + } else { + Err(Error::::NotEnoughPointsToUnbond) + } + } + + /// Withdraw any funds in [`Self::unbonding_eras`] who's deadline in reached and is fully + /// unlocked. + /// + /// Returns a a subset of [`Self::unbonding_eras`] that got withdrawn. + /// + /// Infallible, noop if no unbonding eras exist. + fn withdraw_unlocked( + &mut self, + current_era: EraIndex, + ) -> BoundedBTreeMap, T::MaxUnbonding> { + // NOTE: if only drain-filter was stable.. + let mut removed_points = + BoundedBTreeMap::, T::MaxUnbonding>::default(); + self.unbonding_eras.retain(|e, p| { + if *e > current_era { + true + } else { + removed_points + .try_insert(*e, p.clone()) + .expect("source map is bounded, this is a subset, will be bounded; qed"); + false + } + }); + removed_points + } +} + +/// A pool's possible states. +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, PartialEq, RuntimeDebugNoBound, Clone, Copy)] +pub enum PoolState { + /// The pool is open to be joined, and is working normally. + Open, + /// The pool is blocked. No one else can join. + Blocked, + /// The pool is in the process of being destroyed. + /// + /// All members can now be permissionlessly unbonded, and the pool can never go back to any + /// other state other than being dissolved. + Destroying, +} + +/// Pool adminstration roles. +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, Debug, PartialEq, Clone)] +pub struct PoolRoles { + /// Creates the pool and is the initial member. They can only leave the pool once all + /// other members have left. Once they fully leave, the pool is destroyed. + pub depositor: AccountId, + /// Can change the nominator, state-toggler, or itself and can perform any of the actions + /// the nominator or state-toggler can. + pub root: AccountId, + /// Can select which validators the pool nominates. + pub nominator: AccountId, + /// Can change the pools state and kick members if the pool is blocked. + pub state_toggler: AccountId, +} + +/// Pool permissions and state +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, DebugNoBound, PartialEq, Clone)] +#[codec(mel_bound(T: Config))] +#[scale_info(skip_type_params(T))] +pub struct BondedPoolInner { + /// Total points of all the members in the pool who are actively bonded. + pub points: BalanceOf, + /// The current state of the pool. + pub state: PoolState, + /// Count of members that belong to the pool. + pub member_counter: u32, + /// See [`PoolRoles`]. + pub roles: PoolRoles, +} + +/// A wrapper for bonded pools, with utility functions. +/// +/// The main purpose of this is to wrap a [`BondedPoolInner`], with the account + id of the pool, +/// for easier access. +#[derive(RuntimeDebugNoBound)] +#[cfg_attr(feature = "std", derive(Clone, PartialEq))] +pub struct BondedPool { + /// The identifier of the pool. + id: PoolId, + /// The inner fields. + inner: BondedPoolInner, +} + +impl sp_std::ops::Deref for BondedPool { + type Target = BondedPoolInner; + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl sp_std::ops::DerefMut for BondedPool { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +impl BondedPool { + /// Create a new bonded pool with the given roles and identifier. + fn new(id: PoolId, roles: PoolRoles) -> Self { + Self { + id, + inner: BondedPoolInner { + roles, + state: PoolState::Open, + points: Zero::zero(), + member_counter: Zero::zero(), + }, + } + } + + /// Get [`Self`] from storage. Returns `None` if no entry for `pool_account` exists. + fn get(id: PoolId) -> Option { + BondedPools::::try_get(id).ok().map(|inner| Self { id, inner }) + } + + /// Get the bonded account id of this pool. + fn bonded_account(&self) -> T::AccountId { + Pallet::::create_bonded_account(self.id) + } + + /// Get the reward account id of this pool. + fn reward_account(&self) -> T::AccountId { + Pallet::::create_reward_account(self.id) + } + + /// Consume self and put into storage. + fn put(self) { + BondedPools::::insert(self.id, BondedPoolInner { ..self.inner }); + } + + /// Consume self and remove from storage. + fn remove(self) { + BondedPools::::remove(self.id); + } + + /// Convert the given amount of balance to points given the current pool state. + /// + /// This is often used for bonding and issuing new funds into the pool. + fn balance_to_point(&self, new_funds: BalanceOf) -> BalanceOf { + let bonded_balance = + T::StakingInterface::active_stake(&self.bonded_account()).unwrap_or(Zero::zero()); + Pallet::::balance_to_point(bonded_balance, self.points, new_funds) + } + + /// Convert the given number of points to balance given the current pool state. + /// + /// This is often used for unbonding. + fn points_to_balance(&self, points: BalanceOf) -> BalanceOf { + let bonded_balance = + T::StakingInterface::active_stake(&self.bonded_account()).unwrap_or(Zero::zero()); + Pallet::::point_to_balance(bonded_balance, self.points, points) + } + + /// Issue points to [`Self`] for `new_funds`. + fn issue(&mut self, new_funds: BalanceOf) -> BalanceOf { + let points_to_issue = self.balance_to_point(new_funds); + self.points = self.points.saturating_add(points_to_issue); + points_to_issue + } + + /// Dissolve some points from the pool i.e. unbond the given amount of points from this pool. + /// This is the opposite of issuing some funds into the pool. + /// + /// Mutates self in place, but does not write anything to storage. + /// + /// Returns the equivalent balance amount that actually needs to get unbonded. + fn dissolve(&mut self, points: BalanceOf) -> BalanceOf { + // NOTE: do not optimize by removing `balance`. it must be computed before mutating + // `self.point`. + let balance = self.points_to_balance(points); + self.points = self.points.saturating_sub(points); + balance + } + + /// Increment the member counter. Ensures that the pool and system member limits are + /// respected. + fn try_inc_members(&mut self) -> Result<(), DispatchError> { + ensure!( + MaxPoolMembersPerPool::::get() + .map_or(true, |max_per_pool| self.member_counter < max_per_pool), + Error::::MaxPoolMembers + ); + ensure!( + MaxPoolMembers::::get().map_or(true, |max| PoolMembers::::count() < max), + Error::::MaxPoolMembers + ); + self.member_counter = self.member_counter.defensive_saturating_add(1); + Ok(()) + } + + /// Decrement the member counter. + fn dec_members(mut self) -> Self { + self.member_counter = self.member_counter.defensive_saturating_sub(1); + self + } + + /// The pools balance that is transferrable. + fn transferrable_balance(&self) -> BalanceOf { + let account = self.bonded_account(); + T::Currency::free_balance(&account) + .saturating_sub(T::StakingInterface::active_stake(&account).unwrap_or_default()) + } + + fn can_nominate(&self, who: &T::AccountId) -> bool { + *who == self.roles.root || *who == self.roles.nominator + } + + fn can_kick(&self, who: &T::AccountId) -> bool { + (*who == self.roles.root || *who == self.roles.state_toggler) && + self.state == PoolState::Blocked + } + + fn can_toggle_state(&self, who: &T::AccountId) -> bool { + (*who == self.roles.root || *who == self.roles.state_toggler) && !self.is_destroying() + } + + fn can_set_metadata(&self, who: &T::AccountId) -> bool { + *who == self.roles.root || *who == self.roles.state_toggler + } + + fn is_destroying(&self) -> bool { + matches!(self.state, PoolState::Destroying) + } + + fn is_destroying_and_only_depositor(&self, alleged_depositor_points: BalanceOf) -> bool { + // NOTE: if we add `&& self.member_counter == 1`, then this becomes even more strict and + // ensures that there are no unbonding members hanging around either. + self.is_destroying() && self.points == alleged_depositor_points + } + + /// Whether or not the pool is ok to be in `PoolSate::Open`. If this returns an `Err`, then the + /// pool is unrecoverable and should be in the destroying state. + fn ok_to_be_open(&self, new_funds: BalanceOf) -> Result<(), DispatchError> { + ensure!(!self.is_destroying(), Error::::CanNotChangeState); + + let bonded_balance = + T::StakingInterface::active_stake(&self.bonded_account()).unwrap_or(Zero::zero()); + ensure!(!bonded_balance.is_zero(), Error::::OverflowRisk); + + let points_to_balance_ratio_floor = self + .points + // We checked for zero above + .div(bonded_balance); + + // Pool points can inflate relative to balance, but only if the pool is slashed. + // If we cap the ratio of points:balance so one cannot join a pool that has been slashed + // 90%, + ensure!(points_to_balance_ratio_floor < 10u32.into(), Error::::OverflowRisk); + // while restricting the balance to 1/10th of max total issuance, + let next_bonded_balance = bonded_balance.saturating_add(new_funds); + ensure!( + next_bonded_balance < BalanceOf::::max_value().div(10u32.into()), + Error::::OverflowRisk + ); + // then we can be decently confident the bonding pool points will not overflow + // `BalanceOf`. Note that these are just heuristics. + + Ok(()) + } + + /// Check that the pool can accept a member with `new_funds`. + fn ok_to_join(&self, new_funds: BalanceOf) -> Result<(), DispatchError> { + ensure!(self.state == PoolState::Open, Error::::NotOpen); + self.ok_to_be_open(new_funds)?; + Ok(()) + } + + fn ok_to_unbond_with( + &self, + caller: &T::AccountId, + target_account: &T::AccountId, + target_member: &PoolMember, + unbonding_points: BalanceOf, + ) -> Result<(), DispatchError> { + let is_permissioned = caller == target_account; + let is_depositor = *target_account == self.roles.depositor; + match (is_permissioned, is_depositor) { + // If the pool is blocked, then an admin with kicking permissions can remove a + // member. If the pool is being destroyed, anyone can remove a member + (false, false) => { + ensure!( + self.can_kick(caller) || self.is_destroying(), + Error::::NotKickerOrDestroying + ) + }, + // Any member who is not the depositor can always unbond themselves + (true, false) => (), + (_, true) => { + if self.is_destroying_and_only_depositor(target_member.active_points()) { + // if the pool is about to be destroyed, anyone can unbond the depositor, and + // they can fully unbond. + } else { + // only the depositor can partially unbond, and they can only unbond up to the + // threshold. + ensure!(is_permissioned, Error::::DoesNotHavePermission); + let balance_after_unbond = { + let new_depositor_points = + target_member.active_points().saturating_sub(unbonding_points); + let mut depositor_after_unbond = (*target_member).clone(); + depositor_after_unbond.points = new_depositor_points; + depositor_after_unbond.active_balance() + }; + ensure!( + balance_after_unbond >= MinCreateBond::::get(), + Error::::NotOnlyPoolMember + ); + } + }, + }; + Ok(()) + } + + /// # Returns + /// + /// * Ok(()) if [`Call::withdraw_unbonded`] can be called, `Err(DispatchError)` otherwise. + fn ok_to_withdraw_unbonded_with( + &self, + caller: &T::AccountId, + target_account: &T::AccountId, + target_member: &PoolMember, + sub_pools: &SubPools, + ) -> Result<(), DispatchError> { + if *target_account == self.roles.depositor { + ensure!( + sub_pools.sum_unbonding_points() == target_member.unbonding_points(), + Error::::NotOnlyPoolMember + ); + debug_assert_eq!(self.member_counter, 1, "only member must exist at this point"); + Ok(()) + } else { + // This isn't a depositor + let is_permissioned = caller == target_account; + ensure!( + is_permissioned || self.can_kick(caller) || self.is_destroying(), + Error::::NotKickerOrDestroying + ); + Ok(()) + } + } + + /// Bond exactly `amount` from `who`'s funds into this pool. + /// + /// If the bond type is `Create`, `StakingInterface::bond` is called, and `who` + /// is allowed to be killed. Otherwise, `StakingInterface::bond_extra` is called and `who` + /// cannot be killed. + /// + /// Returns `Ok(points_issues)`, `Err` otherwise. + fn try_bond_funds( + &mut self, + who: &T::AccountId, + amount: BalanceOf, + ty: BondType, + ) -> Result, DispatchError> { + // Cache the value + let bonded_account = self.bonded_account(); + T::Currency::transfer( + &who, + &bonded_account, + amount, + match ty { + BondType::Create => ExistenceRequirement::AllowDeath, + BondType::Later => ExistenceRequirement::KeepAlive, + }, + )?; + // We must calculate the points issued *before* we bond who's funds, else points:balance + // ratio will be wrong. + let points_issued = self.issue(amount); + + match ty { + BondType::Create => T::StakingInterface::bond( + bonded_account.clone(), + bonded_account, + amount, + self.reward_account(), + )?, + // The pool should always be created in such a way its in a state to bond extra, but if + // the active balance is slashed below the minimum bonded or the account cannot be + // found, we exit early. + BondType::Later => T::StakingInterface::bond_extra(bonded_account, amount)?, + } + + Ok(points_issued) + } + + /// If `n` saturates at it's upper bound, mark the pool as destroying. This is useful when a + /// number saturating indicates the pool can no longer correctly keep track of state. + fn bound_check(&mut self, n: U256) -> U256 { + if n == U256::max_value() { + self.set_state(PoolState::Destroying) + } + + n + } + + // Set the state of `self`, and deposit an event if the state changed. State should never be set + // directly in in order to ensure a state change event is always correctly deposited. + fn set_state(&mut self, state: PoolState) { + if self.state != state { + self.state = state; + Pallet::::deposit_event(Event::::StateChanged { + pool_id: self.id, + new_state: state, + }); + }; + } +} + +/// A reward pool. +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound)] +#[cfg_attr(feature = "std", derive(Clone, PartialEq))] +#[codec(mel_bound(T: Config))] +#[scale_info(skip_type_params(T))] +pub struct RewardPool { + /// The balance of this reward pool after the last claimed payout. + pub balance: BalanceOf, + /// The total earnings _ever_ of this reward pool after the last claimed payout. I.E. the sum + /// of all incoming balance through the pools life. + /// + /// NOTE: We assume this will always be less than total issuance and thus can use the runtimes + /// `Balance` type. However in a chain with a burn rate higher than the rate this increases, + /// this type should be bigger than `Balance`. + pub total_earnings: BalanceOf, + /// The total points of this reward pool after the last claimed payout. + pub points: RewardPoints, +} + +impl RewardPool { + /// Mutate the reward pool by updating the total earnings and current free balance. + fn update_total_earnings_and_balance(&mut self, id: PoolId) { + let current_balance = Self::current_balance(id); + // The earnings since the last time it was updated + let new_earnings = current_balance.saturating_sub(self.balance); + // The lifetime earnings of the of the reward pool + self.total_earnings = new_earnings.saturating_add(self.total_earnings); + self.balance = current_balance; + } + + /// Get a reward pool and update its total earnings and balance + fn get_and_update(id: PoolId) -> Option { + RewardPools::::get(id).map(|mut r| { + r.update_total_earnings_and_balance(id); + r + }) + } + + /// The current balance of the reward pool. Never access the reward pools free balance directly. + /// The existential deposit was not received as a reward, so the reward pool can not use it. + fn current_balance(id: PoolId) -> BalanceOf { + T::Currency::free_balance(&Pallet::::create_reward_account(id)) + .saturating_sub(T::Currency::minimum_balance()) + } +} + +/// An unbonding pool. This is always mapped with an era. +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, DefaultNoBound, RuntimeDebugNoBound)] +#[cfg_attr(feature = "std", derive(Clone, PartialEq, Eq))] +#[codec(mel_bound(T: Config))] +#[scale_info(skip_type_params(T))] +pub struct UnbondPool { + /// The points in this pool. + points: BalanceOf, + /// The funds in the pool. + balance: BalanceOf, +} + +impl UnbondPool { + fn balance_to_point(&self, new_funds: BalanceOf) -> BalanceOf { + Pallet::::balance_to_point(self.balance, self.points, new_funds) + } + + fn point_to_balance(&self, points: BalanceOf) -> BalanceOf { + Pallet::::point_to_balance(self.balance, self.points, points) + } + + /// Issue points and update the balance given `new_balance`. + fn issue(&mut self, new_funds: BalanceOf) { + self.points = self.points.saturating_add(self.balance_to_point(new_funds)); + self.balance = self.balance.saturating_add(new_funds); + } + + /// Dissolve some points from the unbonding pool, reducing the balance of the pool + /// proportionally. + /// + /// This is the opposite of `issue`. + /// + /// Returns the actual amount of `Balance` that was removed from the pool. + fn dissolve(&mut self, points: BalanceOf) -> BalanceOf { + let balance_to_unbond = self.point_to_balance(points); + self.points = self.points.saturating_sub(points); + self.balance = self.balance.saturating_sub(balance_to_unbond); + + balance_to_unbond + } +} + +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, DefaultNoBound, RuntimeDebugNoBound)] +#[cfg_attr(feature = "std", derive(Clone, PartialEq))] +#[codec(mel_bound(T: Config))] +#[scale_info(skip_type_params(T))] +pub struct SubPools { + /// A general, era agnostic pool of funds that have fully unbonded. The pools + /// of `Self::with_era` will lazily be merged into into this pool if they are + /// older then `current_era - TotalUnbondingPools`. + no_era: UnbondPool, + /// Map of era in which a pool becomes unbonded in => unbond pools. + with_era: UnbondingPoolsWithEra, +} + +impl SubPools { + /// Merge the oldest `with_era` unbond pools into the `no_era` unbond pool. + /// + /// This is often used whilst getting the sub-pool from storage, thus it consumes and returns + /// `Self` for ergonomic purposes. + fn maybe_merge_pools(mut self, unbond_era: EraIndex) -> Self { + // Ex: if `TotalUnbondingPools` is 5 and current era is 10, we only want to retain pools + // 6..=10. Note that in the first few eras where `checked_sub` is `None`, we don't remove + // anything. + if let Some(newest_era_to_remove) = unbond_era.checked_sub(TotalUnbondingPools::::get()) + { + self.with_era.retain(|k, v| { + if *k > newest_era_to_remove { + // keep + true + } else { + // merge into the no-era pool + self.no_era.points = self.no_era.points.saturating_add(v.points); + self.no_era.balance = self.no_era.balance.saturating_add(v.balance); + false + } + }); + } + + self + } + + /// The sum of all unbonding points, regardless of whether they are actually unlocked or not. + fn sum_unbonding_points(&self) -> BalanceOf { + self.no_era.points.saturating_add( + self.with_era + .values() + .fold(BalanceOf::::zero(), |acc, pool| acc.saturating_add(pool.points)), + ) + } + + /// The sum of all unbonding balance, regardless of whether they are actually unlocked or not. + #[cfg(any(test, debug_assertions))] + fn sum_unbonding_balance(&self) -> BalanceOf { + self.no_era.balance.saturating_add( + self.with_era + .values() + .fold(BalanceOf::::zero(), |acc, pool| acc.saturating_add(pool.balance)), + ) + } +} + +/// The maximum amount of eras an unbonding pool can exist prior to being merged with the +/// `no_era` pool. This is guaranteed to at least be equal to the staking `UnbondingDuration`. For +/// improved UX [`Config::PostUnbondingPoolsWindow`] should be configured to a non-zero value. +pub struct TotalUnbondingPools(PhantomData); +impl Get for TotalUnbondingPools { + fn get() -> u32 { + // NOTE: this may be dangerous in the scenario bonding_duration gets decreased because + // we would no longer be able to decode `UnbondingPoolsWithEra`, which uses + // `TotalUnbondingPools` as the bound + T::StakingInterface::bonding_duration() + T::PostUnbondingPoolsWindow::get() + } +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::transactional; + use frame_system::{ensure_signed, pallet_prelude::*}; + + #[pallet::pallet] + #[pallet::generate_store(pub(crate) trait Store)] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type Event: From> + IsType<::Event>; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: weights::WeightInfo; + + /// The nominating balance. + type Currency: Currency; + + /// The nomination pool's pallet id. + #[pallet::constant] + type PalletId: Get; + + /// Infallible method for converting `Currency::Balance` to `U256`. + type BalanceToU256: Convert, U256>; + + /// Infallible method for converting `U256` to `Currency::Balance`. + type U256ToBalance: Convert>; + + /// The interface for nominating. + type StakingInterface: StakingInterface< + Balance = BalanceOf, + AccountId = Self::AccountId, + >; + + /// The amount of eras a `SubPools::with_era` pool can exist before it gets merged into the + /// `SubPools::no_era` pool. In other words, this is the amount of eras a member will be + /// able to withdraw from an unbonding pool which is guaranteed to have the correct ratio of + /// points to balance; once the `with_era` pool is merged into the `no_era` pool, the ratio + /// can become skewed due to some slashed ratio getting merged in at some point. + type PostUnbondingPoolsWindow: Get; + + /// The maximum length, in bytes, that a pools metadata maybe. + type MaxMetadataLen: Get; + + /// The maximum number of simultaneous unbonding chunks that can exist per member. + type MaxUnbonding: Get; + } + + /// Minimum amount to bond to join a pool. + #[pallet::storage] + pub type MinJoinBond = StorageValue<_, BalanceOf, ValueQuery>; + + /// Minimum bond required to create a pool. + /// + /// This is the amount that the depositor must put as their initial stake in the pool, as an + /// indication of "skin in the game". + #[pallet::storage] + pub type MinCreateBond = StorageValue<_, BalanceOf, ValueQuery>; + + /// Maximum number of nomination pools that can exist. If `None`, then an unbounded number of + /// pools can exist. + #[pallet::storage] + pub type MaxPools = StorageValue<_, u32, OptionQuery>; + + /// Maximum number of members that can exist in the system. If `None`, then the count + /// members are not bound on a system wide basis. + #[pallet::storage] + pub type MaxPoolMembers = StorageValue<_, u32, OptionQuery>; + + /// Maximum number of members that may belong to pool. If `None`, then the count of + /// members is not bound on a per pool basis. + #[pallet::storage] + pub type MaxPoolMembersPerPool = StorageValue<_, u32, OptionQuery>; + + /// Active members. + #[pallet::storage] + pub type PoolMembers = + CountedStorageMap<_, Twox64Concat, T::AccountId, PoolMember>; + + /// Storage for bonded pools. + // To get or insert a pool see [`BondedPool::get`] and [`BondedPool::put`] + #[pallet::storage] + pub type BondedPools = + CountedStorageMap<_, Twox64Concat, PoolId, BondedPoolInner>; + + /// Reward pools. This is where there rewards for each pool accumulate. When a members payout + /// is claimed, the balance comes out fo the reward pool. Keyed by the bonded pools account. + #[pallet::storage] + pub type RewardPools = CountedStorageMap<_, Twox64Concat, PoolId, RewardPool>; + + /// Groups of unbonding pools. Each group of unbonding pools belongs to a bonded pool, + /// hence the name sub-pools. Keyed by the bonded pools account. + #[pallet::storage] + pub type SubPoolsStorage = CountedStorageMap<_, Twox64Concat, PoolId, SubPools>; + + /// Metadata for the pool. + #[pallet::storage] + pub type Metadata = + CountedStorageMap<_, Twox64Concat, PoolId, BoundedVec, ValueQuery>; + + #[pallet::storage] + pub type LastPoolId = StorageValue<_, u32, ValueQuery>; + + #[pallet::storage] + pub type ReversePoolIdLookup = + CountedStorageMap<_, Twox64Concat, T::AccountId, PoolId, OptionQuery>; + + #[pallet::genesis_config] + pub struct GenesisConfig { + pub min_join_bond: BalanceOf, + pub min_create_bond: BalanceOf, + pub max_pools: Option, + pub max_members_per_pool: Option, + pub max_members: Option, + } + + #[cfg(feature = "std")] + impl Default for GenesisConfig { + fn default() -> Self { + Self { + min_join_bond: Zero::zero(), + min_create_bond: Zero::zero(), + max_pools: Some(16), + max_members_per_pool: Some(32), + max_members: Some(16 * 32), + } + } + } + + #[pallet::genesis_build] + impl GenesisBuild for GenesisConfig { + fn build(&self) { + MinJoinBond::::put(self.min_join_bond); + MinCreateBond::::put(self.min_create_bond); + if let Some(max_pools) = self.max_pools { + MaxPools::::put(max_pools); + } + if let Some(max_members_per_pool) = self.max_members_per_pool { + MaxPoolMembersPerPool::::put(max_members_per_pool); + } + if let Some(max_members) = self.max_members { + MaxPoolMembers::::put(max_members); + } + } + } + + /// Events of this pallet. + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event { + /// A pool has been created. + Created { depositor: T::AccountId, pool_id: PoolId }, + /// A member has became bonded in a pool. + Bonded { member: T::AccountId, pool_id: PoolId, bonded: BalanceOf, joined: bool }, + /// A payout has been made to a member. + PaidOut { member: T::AccountId, pool_id: PoolId, payout: BalanceOf }, + /// A member has unbonded from their pool. + Unbonded { member: T::AccountId, pool_id: PoolId, amount: BalanceOf }, + /// A member has withdrawn from their pool. + Withdrawn { member: T::AccountId, pool_id: PoolId, amount: BalanceOf }, + /// A pool has been destroyed. + Destroyed { pool_id: PoolId }, + /// The state of a pool has changed + StateChanged { pool_id: PoolId, new_state: PoolState }, + } + + #[pallet::error] + #[cfg_attr(test, derive(PartialEq))] + pub enum Error { + /// A (bonded) pool id does not exist. + PoolNotFound, + /// An account is not a member. + PoolMemberNotFound, + /// A reward pool does not exist. In all cases this is a system logic error. + RewardPoolNotFound, + /// A sub pool does not exist. + SubPoolsNotFound, + /// An account is already delegating in another pool. An account may only belong to one + /// pool at a time. + AccountBelongsToOtherPool, + /// The pool has insufficient balance to bond as a nominator. + InsufficientBond, + /// The member is already unbonding in this era. + AlreadyUnbonding, + /// The member is fully unbonded (and thus cannot access the bonded and reward pool + /// anymore to, for example, collect rewards). + FullyUnbonding, + /// The member cannot unbond further chunks due to reaching the limit. + MaxUnbondingLimit, + /// None of the funds can be withdrawn yet because the bonding duration has not passed. + CannotWithdrawAny, + /// The amount does not meet the minimum bond to either join or create a pool. + MinimumBondNotMet, + /// The transaction could not be executed due to overflow risk for the pool. + OverflowRisk, + /// A pool must be in [`PoolState::Destroying`] in order for the depositor to unbond or for + /// other members to be permissionlessly unbonded. + NotDestroying, + /// The depositor must be the only member in the bonded pool in order to unbond. And the + /// depositor must be the only member in the sub pools in order to withdraw unbonded. + NotOnlyPoolMember, + /// The caller does not have nominating permissions for the pool. + NotNominator, + /// Either a) the caller cannot make a valid kick or b) the pool is not destroying. + NotKickerOrDestroying, + /// The pool is not open to join + NotOpen, + /// The system is maxed out on pools. + MaxPools, + /// Too many members in the pool or system. + MaxPoolMembers, + /// The pools state cannot be changed. + CanNotChangeState, + /// The caller does not have adequate permissions. + DoesNotHavePermission, + /// Metadata exceeds [`Config::MaxMetadataLen`] + MetadataExceedsMaxLen, + /// Some error occurred that should never happen. This should be reported to the + /// maintainers. + DefensiveError, + /// Not enough points. Ty unbonding less. + NotEnoughPointsToUnbond, + } + + #[pallet::call] + impl Pallet { + /// Stake funds with a pool. The amount to bond is transferred from the member to the + /// pools account and immediately increases the pools bond. + /// + /// # Note + /// + /// * An account can only be a member of a single pool. + /// * An account cannot join the same pool multiple times. + /// * This call will *not* dust the member account, so the member must have at least + /// `existential deposit + amount` in their account. + /// * Only a pool with [`PoolState::Open`] can be joined + #[pallet::weight(T::WeightInfo::join())] + #[transactional] + pub fn join( + origin: OriginFor, + #[pallet::compact] amount: BalanceOf, + pool_id: PoolId, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + ensure!(amount >= MinJoinBond::::get(), Error::::MinimumBondNotMet); + // If a member already exists that means they already belong to a pool + ensure!(!PoolMembers::::contains_key(&who), Error::::AccountBelongsToOtherPool); + + let mut bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; + bonded_pool.ok_to_join(amount)?; + + // We just need its total earnings at this point in time, but we don't need to write it + // because we are not adjusting its points (all other values can calculated virtual). + let reward_pool = RewardPool::::get_and_update(pool_id) + .defensive_ok_or_else(|| Error::::RewardPoolNotFound)?; + + bonded_pool.try_inc_members()?; + let points_issued = bonded_pool.try_bond_funds(&who, amount, BondType::Later)?; + + PoolMembers::insert( + who.clone(), + PoolMember:: { + pool_id, + points: points_issued, + // At best the reward pool has the rewards up through the previous era. If the + // member joins prior to the snapshot they will benefit from the rewards of + // the active era despite not contributing to the pool's vote weight. If they + // join after the snapshot is taken they will benefit from the rewards of the + // next 2 eras because their vote weight will not be counted until the + // snapshot in active era + 1. + reward_pool_total_earnings: reward_pool.total_earnings, + unbonding_eras: Default::default(), + }, + ); + + Self::deposit_event(Event::::Bonded { + member: who, + pool_id, + bonded: amount, + joined: true, + }); + bonded_pool.put(); + + Ok(()) + } + + /// Bond `extra` more funds from `origin` into the pool to which they already belong. + /// + /// Additional funds can come from either the free balance of the account, of from the + /// accumulated rewards, see [`BondExtra`]. + // NOTE: this transaction is implemented with the sole purpose of readability and + // correctness, not optimization. We read/write several storage items multiple times instead + // of just once, in the spirit reusing code. + #[pallet::weight( + T::WeightInfo::bond_extra_transfer() + .max(T::WeightInfo::bond_extra_reward()) + )] + #[transactional] + pub fn bond_extra(origin: OriginFor, extra: BondExtra>) -> DispatchResult { + let who = ensure_signed(origin)?; + let (mut member, mut bonded_pool, mut reward_pool) = Self::get_member_with_pools(&who)?; + + let (points_issued, bonded) = match extra { + BondExtra::FreeBalance(amount) => + (bonded_pool.try_bond_funds(&who, amount, BondType::Later)?, amount), + BondExtra::Rewards => { + let claimed = Self::do_reward_payout( + &who, + &mut member, + &mut bonded_pool, + &mut reward_pool, + )?; + (bonded_pool.try_bond_funds(&who, claimed, BondType::Later)?, claimed) + }, + }; + bonded_pool.ok_to_be_open(bonded)?; + member.points = member.points.saturating_add(points_issued); + + Self::deposit_event(Event::::Bonded { + member: who.clone(), + pool_id: member.pool_id, + bonded, + joined: false, + }); + Self::put_member_with_pools(&who, member, bonded_pool, reward_pool); + + Ok(()) + } + + /// A bonded member can use this to claim their payout based on the rewards that the pool + /// has accumulated since their last claimed payout (OR since joining if this is there first + /// time claiming rewards). The payout will be transferred to the member's account. + /// + /// The member will earn rewards pro rata based on the members stake vs the sum of the + /// members in the pools stake. Rewards do not "expire". + #[pallet::weight(T::WeightInfo::claim_payout())] + #[transactional] + pub fn claim_payout(origin: OriginFor) -> DispatchResult { + let who = ensure_signed(origin)?; + let (mut member, mut bonded_pool, mut reward_pool) = Self::get_member_with_pools(&who)?; + + let _ = Self::do_reward_payout(&who, &mut member, &mut bonded_pool, &mut reward_pool)?; + + Self::put_member_with_pools(&who, member, bonded_pool, reward_pool); + Ok(()) + } + + /// Unbond up to `unbonding_points` of the `member_account`'s funds from the pool. It + /// implicitly collects the rewards one last time, since not doing so would mean some + /// rewards would go forfeited. + /// + /// Under certain conditions, this call can be dispatched permissionlessly (i.e. by any + /// account). + /// + /// # Conditions for a permissionless dispatch. + /// + /// * The pool is blocked and the caller is either the root or state-toggler. This is + /// refereed to as a kick. + /// * The pool is destroying and the member is not the depositor. + /// * The pool is destroying, the member is the depositor and no other members are in the + /// pool. + /// + /// ## Conditions for permissioned dispatch (i.e. the caller is also the + /// `member_account`): + /// + /// * The caller is not the depositor. + /// * The caller is the depositor, the pool is destroying and no other members are in the + /// pool. + /// + /// # Note + /// + /// If there are too many unlocking chunks to unbond with the pool account, + /// [`Call::pool_withdraw_unbonded`] can be called to try and minimize unlocking chunks. If + /// there are too many unlocking chunks, the result of this call will likely be the + /// `NoMoreChunks` error from the staking system. + #[pallet::weight(T::WeightInfo::unbond())] + #[transactional] + pub fn unbond( + origin: OriginFor, + member_account: T::AccountId, + #[pallet::compact] unbonding_points: BalanceOf, + ) -> DispatchResult { + let caller = ensure_signed(origin)?; + let (mut member, mut bonded_pool, mut reward_pool) = + Self::get_member_with_pools(&member_account)?; + + bonded_pool.ok_to_unbond_with(&caller, &member_account, &member, unbonding_points)?; + + // Claim the the payout prior to unbonding. Once the user is unbonding their points + // no longer exist in the bonded pool and thus they can no longer claim their payouts. + // It is not strictly necessary to claim the rewards, but we do it here for UX. + Self::do_reward_payout( + &member_account, + &mut member, + &mut bonded_pool, + &mut reward_pool, + )?; + + let current_era = T::StakingInterface::current_era(); + let unbond_era = T::StakingInterface::bonding_duration().saturating_add(current_era); + + // Try and unbond in the member map. + member.try_unbond(unbonding_points, unbond_era)?; + + // Unbond in the actual underlying nominator. + let unbonding_balance = bonded_pool.dissolve(unbonding_points); + T::StakingInterface::unbond(bonded_pool.bonded_account(), unbonding_balance)?; + + // Note that we lazily create the unbonding pools here if they don't already exist + let mut sub_pools = SubPoolsStorage::::get(member.pool_id) + .unwrap_or_default() + .maybe_merge_pools(unbond_era); + + // Update the unbond pool associated with the current era with the unbonded funds. Note + // that we lazily create the unbond pool if it does not yet exist. + if !sub_pools.with_era.contains_key(&unbond_era) { + sub_pools + .with_era + .try_insert(unbond_era, UnbondPool::default()) + // The above call to `maybe_merge_pools` should ensure there is + // always enough space to insert. + .defensive_map_err(|_| Error::::DefensiveError)?; + } + + sub_pools + .with_era + .get_mut(&unbond_era) + // The above check ensures the pool exists. + .defensive_ok_or_else(|| Error::::DefensiveError)? + .issue(unbonding_balance); + + Self::deposit_event(Event::::Unbonded { + member: member_account.clone(), + pool_id: member.pool_id, + amount: unbonding_balance, + }); + + // Now that we know everything has worked write the items to storage. + SubPoolsStorage::insert(&member.pool_id, sub_pools); + Self::put_member_with_pools(&member_account, member, bonded_pool, reward_pool); + + Ok(()) + } + + /// Call `withdraw_unbonded` for the pools account. This call can be made by any account. + /// + /// This is useful if their are too many unlocking chunks to call `unbond`, and some + /// can be cleared by withdrawing. In the case there are too many unlocking chunks, the user + /// would probably see an error like `NoMoreChunks` emitted from the staking system when + /// they attempt to unbond. + #[pallet::weight(T::WeightInfo::pool_withdraw_unbonded(*num_slashing_spans))] + #[transactional] + pub fn pool_withdraw_unbonded( + origin: OriginFor, + pool_id: PoolId, + num_slashing_spans: u32, + ) -> DispatchResult { + let _ = ensure_signed(origin)?; + let pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; + // For now we only allow a pool to withdraw unbonded if its not destroying. If the pool + // is destroying then `withdraw_unbonded` can be used. + ensure!(pool.state != PoolState::Destroying, Error::::NotDestroying); + T::StakingInterface::withdraw_unbonded(pool.bonded_account(), num_slashing_spans)?; + Ok(()) + } + + /// Withdraw unbonded funds from `member_account`. If no bonded funds can be unbonded, an + /// error is returned. + /// + /// Under certain conditions, this call can be dispatched permissionlessly (i.e. by any + /// account). + /// + /// # Conditions for a permissionless dispatch + /// + /// * The pool is in destroy mode and the target is not the depositor. + /// * The target is the depositor and they are the only member in the sub pools. + /// * The pool is blocked and the caller is either the root or state-toggler. + /// + /// # Conditions for permissioned dispatch + /// + /// * The caller is the target and they are not the depositor. + /// + /// # Note + /// + /// If the target is the depositor, the pool will be destroyed. + #[pallet::weight( + T::WeightInfo::withdraw_unbonded_kill(*num_slashing_spans) + )] + #[transactional] + pub fn withdraw_unbonded( + origin: OriginFor, + member_account: T::AccountId, + num_slashing_spans: u32, + ) -> DispatchResultWithPostInfo { + let caller = ensure_signed(origin)?; + let mut member = + PoolMembers::::get(&member_account).ok_or(Error::::PoolMemberNotFound)?; + let current_era = T::StakingInterface::current_era(); + + let bonded_pool = BondedPool::::get(member.pool_id) + .defensive_ok_or_else(|| Error::::PoolNotFound)?; + let mut sub_pools = SubPoolsStorage::::get(member.pool_id) + .defensive_ok_or_else(|| Error::::SubPoolsNotFound)?; + + bonded_pool.ok_to_withdraw_unbonded_with( + &caller, + &member_account, + &member, + &sub_pools, + )?; + + // NOTE: must do this after we have done the `ok_to_withdraw_unbonded_other_with` check. + let withdrawn_points = member.withdraw_unlocked(current_era); + ensure!(!withdrawn_points.is_empty(), Error::::CannotWithdrawAny); + + // Before calculate the `balance_to_unbond`, with call withdraw unbonded to ensure the + // `transferrable_balance` is correct. + T::StakingInterface::withdraw_unbonded( + bonded_pool.bonded_account(), + num_slashing_spans, + )?; + + let balance_to_unbond = withdrawn_points + .iter() + .fold(BalanceOf::::zero(), |accumulator, (era, unlocked_points)| { + if let Some(era_pool) = sub_pools.with_era.get_mut(&era) { + let balance_to_unbond = era_pool.dissolve(*unlocked_points); + if era_pool.points.is_zero() { + sub_pools.with_era.remove(&era); + } + accumulator.saturating_add(balance_to_unbond) + } else { + // A pool does not belong to this era, so it must have been merged to the + // era-less pool. + accumulator.saturating_add(sub_pools.no_era.dissolve(*unlocked_points)) + } + }) + // A call to this function may cause the pool's stash to get dusted. If this happens + // before the last member has withdrawn, then all subsequent withdraws will be 0. + // However the unbond pools do no get updated to reflect this. In the aforementioned + // scenario, this check ensures we don't try to withdraw funds that don't exist. + // This check is also defensive in cases where the unbond pool does not update its + // balance (e.g. a bug in the slashing hook.) We gracefully proceed in order to + // ensure members can leave the pool and it can be destroyed. + .min(bonded_pool.transferrable_balance()); + + T::Currency::transfer( + &bonded_pool.bonded_account(), + &member_account, + balance_to_unbond, + ExistenceRequirement::AllowDeath, + ) + .defensive()?; + + Self::deposit_event(Event::::Withdrawn { + member: member_account.clone(), + pool_id: member.pool_id, + amount: balance_to_unbond, + }); + + let post_info_weight = if member.active_points().is_zero() { + // member being reaped. + PoolMembers::::remove(&member_account); + + if member_account == bonded_pool.roles.depositor { + Pallet::::dissolve_pool(bonded_pool); + None + } else { + bonded_pool.dec_members().put(); + SubPoolsStorage::::insert(&member.pool_id, sub_pools); + Some(T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)) + } + } else { + // we certainly don't need to delete any pools, because no one is being removed. + SubPoolsStorage::::insert(&member.pool_id, sub_pools); + PoolMembers::::insert(&member_account, member); + Some(T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)) + }; + + Ok(post_info_weight.into()) + } + + /// Create a new delegation pool. + /// + /// # Arguments + /// + /// * `amount` - The amount of funds to delegate to the pool. This also acts of a sort of + /// deposit since the pools creator cannot fully unbond funds until the pool is being + /// destroyed. + /// * `index` - A disambiguation index for creating the account. Likely only useful when + /// creating multiple pools in the same extrinsic. + /// * `root` - The account to set as [`PoolRoles::root`]. + /// * `nominator` - The account to set as the [`PoolRoles::nominator`]. + /// * `state_toggler` - The account to set as the [`PoolRoles::state_toggler`]. + /// + /// # Note + /// + /// In addition to `amount`, the caller will transfer the existential deposit; so the caller + /// needs at have at least `amount + existential_deposit` transferrable. + #[pallet::weight(T::WeightInfo::create())] + #[transactional] + pub fn create( + origin: OriginFor, + #[pallet::compact] amount: BalanceOf, + root: T::AccountId, + nominator: T::AccountId, + state_toggler: T::AccountId, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + ensure!( + amount >= + T::StakingInterface::minimum_bond() + .max(MinCreateBond::::get()) + .max(MinJoinBond::::get()), + Error::::MinimumBondNotMet + ); + ensure!( + MaxPools::::get() + .map_or(true, |max_pools| BondedPools::::count() < max_pools), + Error::::MaxPools + ); + ensure!(!PoolMembers::::contains_key(&who), Error::::AccountBelongsToOtherPool); + + let pool_id = LastPoolId::::mutate(|id| { + *id += 1; + *id + }); + let mut bonded_pool = BondedPool::::new( + pool_id, + PoolRoles { root, nominator, state_toggler, depositor: who.clone() }, + ); + + bonded_pool.try_inc_members()?; + let points = bonded_pool.try_bond_funds(&who, amount, BondType::Create)?; + + T::Currency::transfer( + &who, + &bonded_pool.reward_account(), + T::Currency::minimum_balance(), + ExistenceRequirement::AllowDeath, + )?; + + PoolMembers::::insert( + who.clone(), + PoolMember:: { + pool_id, + points, + reward_pool_total_earnings: Zero::zero(), + unbonding_eras: Default::default(), + }, + ); + RewardPools::::insert( + pool_id, + RewardPool:: { + balance: Zero::zero(), + points: U256::zero(), + total_earnings: Zero::zero(), + }, + ); + ReversePoolIdLookup::::insert(bonded_pool.bonded_account(), pool_id); + Self::deposit_event(Event::::Created { + depositor: who.clone(), + pool_id: pool_id.clone(), + }); + Self::deposit_event(Event::::Bonded { + member: who, + pool_id, + bonded: amount, + joined: true, + }); + bonded_pool.put(); + + Ok(()) + } + + #[pallet::weight(T::WeightInfo::nominate(validators.len() as u32))] + pub fn nominate( + origin: OriginFor, + pool_id: PoolId, + validators: Vec, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; + ensure!(bonded_pool.can_nominate(&who), Error::::NotNominator); + T::StakingInterface::nominate(bonded_pool.bonded_account(), validators)?; + Ok(()) + } + + #[pallet::weight(T::WeightInfo::set_state())] + pub fn set_state( + origin: OriginFor, + pool_id: PoolId, + state: PoolState, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let mut bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; + ensure!(bonded_pool.state != PoolState::Destroying, Error::::CanNotChangeState); + + if bonded_pool.can_toggle_state(&who) { + bonded_pool.set_state(state); + } else if bonded_pool.ok_to_be_open(Zero::zero()).is_err() && + state == PoolState::Destroying + { + // If the pool has bad properties, then anyone can set it as destroying + bonded_pool.set_state(PoolState::Destroying); + } else { + Err(Error::::CanNotChangeState)?; + } + + bonded_pool.put(); + + Ok(()) + } + + #[pallet::weight(T::WeightInfo::set_metadata(metadata.len() as u32))] + pub fn set_metadata( + origin: OriginFor, + pool_id: PoolId, + metadata: Vec, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let metadata: BoundedVec<_, _> = + metadata.try_into().map_err(|_| Error::::MetadataExceedsMaxLen)?; + ensure!( + BondedPool::::get(pool_id) + .ok_or(Error::::PoolNotFound)? + .can_set_metadata(&who), + Error::::DoesNotHavePermission + ); + + Metadata::::mutate(pool_id, |pool_meta| *pool_meta = metadata); + + Ok(()) + } + + /// Update configurations for the nomination pools. The origin must for this call must be + /// Root. + /// + /// # Arguments + /// + /// * `min_join_bond` - Set [`MinJoinBond`]. + /// * `min_create_bond` - Set [`MinCreateBond`]. + /// * `max_pools` - Set [`MaxPools`]. + /// * `max_members` - Set [`MaxPoolMembers`]. + /// * `max_members_per_pool` - Set [`MaxPoolMembersPerPool`]. + #[pallet::weight(T::WeightInfo::set_configs())] + pub fn set_configs( + origin: OriginFor, + min_join_bond: ConfigOp>, + min_create_bond: ConfigOp>, + max_pools: ConfigOp, + max_members: ConfigOp, + max_members_per_pool: ConfigOp, + ) -> DispatchResult { + ensure_root(origin)?; + + macro_rules! config_op_exp { + ($storage:ty, $op:ident) => { + match $op { + ConfigOp::Noop => (), + ConfigOp::Set(v) => <$storage>::put(v), + ConfigOp::Remove => <$storage>::kill(), + } + }; + } + + config_op_exp!(MinJoinBond::, min_join_bond); + config_op_exp!(MinCreateBond::, min_create_bond); + config_op_exp!(MaxPools::, max_pools); + config_op_exp!(MaxPoolMembers::, max_members); + config_op_exp!(MaxPoolMembersPerPool::, max_members_per_pool); + + Ok(()) + } + } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn integrity_test() { + assert!( + sp_std::mem::size_of::() >= + 2 * sp_std::mem::size_of::>(), + "bit-length of the reward points must be at least twice as much as balance" + ); + + assert!( + T::StakingInterface::bonding_duration() < TotalUnbondingPools::::get(), + "There must be more unbonding pools then the bonding duration / + so a slash can be applied to relevant unboding pools. (We assume / + the bonding duration > slash deffer duration.", + ); + } + } +} + +impl Pallet { + /// Remove everything related to the given bonded pool. + /// + /// All sub-pools are also deleted. All accounts are dusted and the leftover of the reward + /// account is returned to the depositor. + pub fn dissolve_pool(bonded_pool: BondedPool) { + let reward_account = bonded_pool.reward_account(); + let bonded_account = bonded_pool.bonded_account(); + + ReversePoolIdLookup::::remove(&bonded_account); + RewardPools::::remove(bonded_pool.id); + SubPoolsStorage::::remove(bonded_pool.id); + + // Kill accounts from storage by making their balance go below ED. We assume that the + // accounts have no references that would prevent destruction once we get to this point. We + // don't work with the system pallet directly, but + // 1. we drain the reward account and kill it. This account should never have any extra + // consumers anyway. + // 2. the bonded account should become a 'killed stash' in the staking system, and all of + // its consumers removed. + debug_assert_eq!(frame_system::Pallet::::consumers(&reward_account), 0); + debug_assert_eq!(frame_system::Pallet::::consumers(&bonded_account), 0); + debug_assert_eq!( + T::StakingInterface::total_stake(&bonded_account).unwrap_or_default(), + Zero::zero() + ); + + // This shouldn't fail, but if it does we don't really care + let reward_pool_remaining = T::Currency::free_balance(&reward_account); + let _ = T::Currency::transfer( + &reward_account, + &bonded_pool.roles.depositor, + reward_pool_remaining, + ExistenceRequirement::AllowDeath, + ); + + // TODO: this is purely defensive. + T::Currency::make_free_balance_be(&reward_account, Zero::zero()); + T::Currency::make_free_balance_be(&bonded_pool.bonded_account(), Zero::zero()); + + Self::deposit_event(Event::::Destroyed { pool_id: bonded_pool.id }); + bonded_pool.remove(); + } + + /// Create the main, bonded account of a pool with the given id. + pub fn create_bonded_account(id: PoolId) -> T::AccountId { + T::PalletId::get().into_sub_account((AccountType::Bonded, id)) + } + + /// Create the reward account of a pool with the given id. + pub fn create_reward_account(id: PoolId) -> T::AccountId { + // NOTE: in order to have a distinction in the test account id type (u128), we put + // account_type first so it does not get truncated out. + T::PalletId::get().into_sub_account((AccountType::Reward, id)) + } + + /// Get the member with their associated bonded and reward pool. + fn get_member_with_pools( + who: &T::AccountId, + ) -> Result<(PoolMember, BondedPool, RewardPool), Error> { + let member = PoolMembers::::get(&who).ok_or(Error::::PoolMemberNotFound)?; + let bonded_pool = + BondedPool::::get(member.pool_id).defensive_ok_or(Error::::PoolNotFound)?; + let reward_pool = + RewardPools::::get(member.pool_id).defensive_ok_or(Error::::PoolNotFound)?; + Ok((member, bonded_pool, reward_pool)) + } + + /// Persist the member with their associated bonded and reward pool into storage, consuming + /// all of them. + fn put_member_with_pools( + member_account: &T::AccountId, + member: PoolMember, + bonded_pool: BondedPool, + reward_pool: RewardPool, + ) { + bonded_pool.put(); + RewardPools::insert(member.pool_id, reward_pool); + PoolMembers::::insert(member_account, member); + } + + /// Calculate the equivalent point of `new_funds` in a pool with `current_balance` and + /// `current_points`. + fn balance_to_point( + current_balance: BalanceOf, + current_points: BalanceOf, + new_funds: BalanceOf, + ) -> BalanceOf { + let u256 = |x| T::BalanceToU256::convert(x); + let balance = |x| T::U256ToBalance::convert(x); + match (current_balance.is_zero(), current_points.is_zero()) { + (_, true) => new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()), + (true, false) => { + // The pool was totally slashed. + // This is the equivalent of `(current_points / 1) * new_funds`. + new_funds.saturating_mul(current_points) + }, + (false, false) => { + // Equivalent to (current_points / current_balance) * new_funds + balance( + u256(current_points) + .saturating_mul(u256(new_funds)) + // We check for zero above + .div(u256(current_balance)), + ) + }, + } + } + + /// Calculate the equivalent balance of `points` in a pool with `current_balance` and + /// `current_points`. + fn point_to_balance( + current_balance: BalanceOf, + current_points: BalanceOf, + points: BalanceOf, + ) -> BalanceOf { + let u256 = |x| T::BalanceToU256::convert(x); + let balance = |x| T::U256ToBalance::convert(x); + if current_balance.is_zero() || current_points.is_zero() || points.is_zero() { + // There is nothing to unbond + return Zero::zero() + } + + // Equivalent of (current_balance / current_points) * points + balance(u256(current_balance).saturating_mul(u256(points))) + // We check for zero above + .div(current_points) + } + + /// Calculate the rewards for `member`. + /// + /// Returns the payout amount. + fn calculate_member_payout( + member: &mut PoolMember, + bonded_pool: &mut BondedPool, + reward_pool: &mut RewardPool, + ) -> Result, DispatchError> { + let u256 = |x| T::BalanceToU256::convert(x); + let balance = |x| T::U256ToBalance::convert(x); + + let last_total_earnings = reward_pool.total_earnings; + reward_pool.update_total_earnings_and_balance(bonded_pool.id); + + // Notice there is an edge case where total_earnings have not increased and this is zero + let new_earnings = u256(reward_pool.total_earnings.saturating_sub(last_total_earnings)); + + // The new points that will be added to the pool. For every unit of balance that has been + // earned by the reward pool, we inflate the reward pool points by `bonded_pool.points`. In + // effect this allows each, single unit of balance (e.g. plank) to be divvied up pro rata + // among members based on points. + let new_points = u256(bonded_pool.points).saturating_mul(new_earnings); + + // The points of the reward pool after taking into account the new earnings. Notice that + // this only stays even or increases over time except for when we subtract member virtual + // shares. + let current_points = bonded_pool.bound_check(reward_pool.points.saturating_add(new_points)); + + // The rewards pool's earnings since the last time this member claimed a payout. + let new_earnings_since_last_claim = + reward_pool.total_earnings.saturating_sub(member.reward_pool_total_earnings); + + // The points of the reward pool that belong to the member. + let member_virtual_points = + // The members portion of the reward pool + u256(member.active_points()) + // times the amount the pool has earned since the member last claimed. + .saturating_mul(u256(new_earnings_since_last_claim)); + + let member_payout = if member_virtual_points.is_zero() || + current_points.is_zero() || + reward_pool.balance.is_zero() + { + Zero::zero() + } else { + // Equivalent to `(member_virtual_points / current_points) * reward_pool.balance` + let numerator = { + let numerator = member_virtual_points.saturating_mul(u256(reward_pool.balance)); + bonded_pool.bound_check(numerator) + }; + balance( + numerator + // We check for zero above + .div(current_points), + ) + }; + + // Record updates + if reward_pool.total_earnings == BalanceOf::::max_value() { + bonded_pool.set_state(PoolState::Destroying); + }; + member.reward_pool_total_earnings = reward_pool.total_earnings; + reward_pool.points = current_points.saturating_sub(member_virtual_points); + reward_pool.balance = reward_pool.balance.saturating_sub(member_payout); + + Ok(member_payout) + } + + /// If the member has some rewards, transfer a payout from the reward pool to the member. + // Emits events and potentially modifies pool state if any arithmetic saturates, but does + // not persist any of the mutable inputs to storage. + fn do_reward_payout( + member_account: &T::AccountId, + member: &mut PoolMember, + bonded_pool: &mut BondedPool, + reward_pool: &mut RewardPool, + ) -> Result, DispatchError> { + debug_assert_eq!(member.pool_id, bonded_pool.id); + // a member who has no skin in the game anymore cannot claim any rewards. + ensure!(!member.active_points().is_zero(), Error::::FullyUnbonding); + let was_destroying = bonded_pool.is_destroying(); + + let member_payout = Self::calculate_member_payout(member, bonded_pool, reward_pool)?; + + // Transfer payout to the member. + T::Currency::transfer( + &bonded_pool.reward_account(), + &member_account, + member_payout, + ExistenceRequirement::AllowDeath, + )?; + + Self::deposit_event(Event::::PaidOut { + member: member_account.clone(), + pool_id: member.pool_id, + payout: member_payout, + }); + + if bonded_pool.is_destroying() && !was_destroying { + Self::deposit_event(Event::::StateChanged { + pool_id: member.pool_id, + new_state: PoolState::Destroying, + }); + } + + Ok(member_payout) + } + + /// Ensure the correctness of the state of this pallet. + /// + /// This should be valid before or after each state transition of this pallet. + /// + /// ## Invariants: + /// + /// First, let's consider pools: + /// + /// * `BondedPools` and `RewardPools` must all have the EXACT SAME key-set. + /// * `SubPoolsStorage` must be a subset of the above superset. + /// * `Metadata` keys must be a subset of the above superset. + /// * the count of the above set must be less than `MaxPools`. + /// + /// Then, considering members as well: + /// + /// * each `BondedPool.member_counter` must be: + /// - correct (compared to actual count of member who have `.pool_id` this pool) + /// - less than `MaxPoolMembersPerPool`. + /// * each `member.pool_id` must correspond to an existing `BondedPool.id` (which implies the + /// existence of the reward pool as well). + /// * count of all members must be less than `MaxPoolMembers`. + /// + /// Then, considering unbonding members: + /// + /// for each pool: + /// * sum of the balance that's tracked in all unbonding pools must be the same as the + /// unbonded balance of the main account, as reported by the staking interface. + /// * sum of the balance that's tracked in all unbonding pools, plus the bonded balance of the + /// main account should be less than or qual to the total balance of the main account. + /// + /// ## Sanity check level + /// + /// To cater for tests that want to escape parts of these checks, this function is split into + /// multiple `level`s, where the higher the level, the more checks we performs. So, + /// `sanity_check(255)` is the strongest sanity check, and `0` performs no checks. + #[cfg(any(test, debug_assertions))] + pub fn sanity_checks(level: u8) -> Result<(), &'static str> { + if level.is_zero() { + return Ok(()) + } + // note: while a bit wacky, since they have the same key, even collecting to vec should + // result in the same set of keys, in the same order. + let bonded_pools = BondedPools::::iter_keys().collect::>(); + let reward_pools = RewardPools::::iter_keys().collect::>(); + assert_eq!(bonded_pools, reward_pools); + + assert!(Metadata::::iter_keys().all(|k| bonded_pools.contains(&k))); + assert!(SubPoolsStorage::::iter_keys().all(|k| bonded_pools.contains(&k))); + + assert!(MaxPools::::get().map_or(true, |max| bonded_pools.len() <= (max as usize))); + + for id in reward_pools { + let account = Self::create_reward_account(id); + assert!(T::Currency::free_balance(&account) >= T::Currency::minimum_balance()); + } + + let mut pools_members = BTreeMap::::new(); + let mut all_members = 0u32; + PoolMembers::::iter().for_each(|(_, d)| { + assert!(BondedPools::::contains_key(d.pool_id)); + assert!(!d.total_points().is_zero(), "no member should have zero points: {:?}", d); + *pools_members.entry(d.pool_id).or_default() += 1; + all_members += 1; + }); + + BondedPools::::iter().for_each(|(id, inner)| { + let bonded_pool = BondedPool { id, inner }; + assert_eq!( + pools_members.get(&id).map(|x| *x).unwrap_or_default(), + bonded_pool.member_counter + ); + assert!(MaxPoolMembersPerPool::::get() + .map_or(true, |max| bonded_pool.member_counter <= max)); + + let depositor = PoolMembers::::get(&bonded_pool.roles.depositor).unwrap(); + assert!( + bonded_pool.is_destroying_and_only_depositor(depositor.active_points()) || + depositor.active_points() >= MinCreateBond::::get(), + "depositor must always have MinCreateBond stake in the pool, except for when the \ + pool is being destroyed and the depositor is the last member", + ); + }); + assert!(MaxPoolMembers::::get().map_or(true, |max| all_members <= max)); + + if level <= 1 { + return Ok(()) + } + + for (pool_id, _pool) in BondedPools::::iter() { + let pool_account = Pallet::::create_bonded_account(pool_id); + let subs = SubPoolsStorage::::get(pool_id).unwrap_or_default(); + + let sum_unbonding_balance = subs.sum_unbonding_balance(); + let bonded_balance = + T::StakingInterface::active_stake(&pool_account).unwrap_or_default(); + let total_balance = T::Currency::total_balance(&pool_account); + + assert!( + total_balance >= bonded_balance + sum_unbonding_balance, + "faulty pool: {:?} / {:?}, total_balance {:?} >= bonded_balance {:?} + sum_unbonding_balance {:?}", + pool_id, + _pool, + total_balance, + bonded_balance, + sum_unbonding_balance + ); + } + + Ok(()) + } + + /// Fully unbond the shares of `member`, when executed from `origin`. + /// + /// This is useful for backwards compatibility with the majority of tests that only deal with + /// full unbonding, not partial unbonding. + #[cfg(any(feature = "runtime-benchmarks", test))] + pub fn fully_unbond( + origin: frame_system::pallet_prelude::OriginFor, + member: T::AccountId, + ) -> DispatchResult { + let points = PoolMembers::::get(&member).map(|d| d.active_points()).unwrap_or_default(); + Self::unbond(origin, member, points) + } +} + +impl OnStakerSlash> for Pallet { + fn on_slash( + pool_account: &T::AccountId, + // Bonded balance is always read directly from staking, therefore we need not update + // anything here. + _slashed_bonded: BalanceOf, + slashed_unlocking: &BTreeMap>, + ) { + if let Some(pool_id) = ReversePoolIdLookup::::get(pool_account) { + let mut sub_pools = match SubPoolsStorage::::get(pool_id).defensive() { + Some(sub_pools) => sub_pools, + None => return, + }; + for (era, slashed_balance) in slashed_unlocking.iter() { + if let Some(pool) = sub_pools.with_era.get_mut(era) { + pool.balance = *slashed_balance + } + } + SubPoolsStorage::::insert(pool_id, sub_pools); + } + } +} diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs new file mode 100644 index 0000000000000..5498496965adb --- /dev/null +++ b/frame/nomination-pools/src/mock.rs @@ -0,0 +1,309 @@ +use super::*; +use crate::{self as pools}; +use frame_support::{assert_ok, parameter_types, PalletId}; +use frame_system::RawOrigin; +use std::collections::HashMap; + +pub type AccountId = u128; +pub type Balance = u128; + +// Ext builder creates a pool with id 1. +pub fn default_bonded_account() -> AccountId { + Pools::create_bonded_account(1) +} + +// Ext builder creates a pool with id 1. +pub fn default_reward_account() -> AccountId { + Pools::create_reward_account(1) +} + +parameter_types! { + pub static CurrentEra: EraIndex = 0; + pub static BondingDuration: EraIndex = 3; + static BondedBalanceMap: HashMap = Default::default(); + static UnbondingBalanceMap: HashMap = Default::default(); + #[derive(Clone, PartialEq)] + pub static MaxUnbonding: u32 = 8; + pub static Nominations: Vec = vec![]; +} + +pub struct StakingMock; +impl StakingMock { + pub(crate) fn set_bonded_balance(who: AccountId, bonded: Balance) { + BONDED_BALANCE_MAP.with(|m| m.borrow_mut().insert(who, bonded)); + } +} + +impl sp_staking::StakingInterface for StakingMock { + type Balance = Balance; + type AccountId = AccountId; + + fn minimum_bond() -> Self::Balance { + 10 + } + + fn current_era() -> EraIndex { + CurrentEra::get() + } + + fn bonding_duration() -> EraIndex { + BondingDuration::get() + } + + fn active_stake(who: &Self::AccountId) -> Option { + BondedBalanceMap::get().get(who).map(|v| *v) + } + + fn total_stake(who: &Self::AccountId) -> Option { + match ( + UnbondingBalanceMap::get().get(who).map(|v| *v), + BondedBalanceMap::get().get(who).map(|v| *v), + ) { + (None, None) => None, + (Some(v), None) | (None, Some(v)) => Some(v), + (Some(a), Some(b)) => Some(a + b), + } + } + + fn bond_extra(who: Self::AccountId, extra: Self::Balance) -> DispatchResult { + BONDED_BALANCE_MAP.with(|m| *m.borrow_mut().get_mut(&who).unwrap() += extra); + Ok(()) + } + + fn unbond(who: Self::AccountId, amount: Self::Balance) -> DispatchResult { + BONDED_BALANCE_MAP.with(|m| *m.borrow_mut().get_mut(&who).unwrap() -= amount); + UNBONDING_BALANCE_MAP + .with(|m| *m.borrow_mut().entry(who).or_insert(Self::Balance::zero()) += amount); + Ok(()) + } + + fn withdraw_unbonded(who: Self::AccountId, _: u32) -> Result { + // Simulates removing unlocking chunks and only having the bonded balance locked + let _maybe_new_free = UNBONDING_BALANCE_MAP.with(|m| m.borrow_mut().remove(&who)); + + Ok(100) + } + + fn bond( + stash: Self::AccountId, + _: Self::AccountId, + value: Self::Balance, + _: Self::AccountId, + ) -> DispatchResult { + StakingMock::set_bonded_balance(stash, value); + Ok(()) + } + + fn nominate(_: Self::AccountId, nominations: Vec) -> DispatchResult { + Nominations::set(nominations); + Ok(()) + } +} + +impl frame_system::Config for Runtime { + type SS58Prefix = (); + type BaseCallFilter = frame_support::traits::Everything; + type Origin = Origin; + type Index = u64; + type BlockNumber = u64; + type Call = Call; + type Hash = sp_core::H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = sp_runtime::traits::IdentityLookup; + type Header = sp_runtime::testing::Header; + type Event = Event; + type BlockHashCount = (); + type DbWeight = (); + type BlockLength = (); + type BlockWeights = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +parameter_types! { + pub static ExistentialDeposit: Balance = 5; +} + +impl pallet_balances::Config for Runtime { + type MaxLocks = frame_support::traits::ConstU32<1024>; + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type Balance = Balance; + type Event = Event; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); +} + +pub struct BalanceToU256; +impl Convert for BalanceToU256 { + fn convert(n: Balance) -> U256 { + n.into() + } +} + +pub struct U256ToBalance; +impl Convert for U256ToBalance { + fn convert(n: U256) -> Balance { + n.try_into().unwrap() + } +} + +parameter_types! { + pub static PostUnbondingPoolsWindow: u32 = 2; + pub static MaxMetadataLen: u32 = 2; + pub static CheckLevel: u8 = 255; + pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls"); +} +impl pools::Config for Runtime { + type Event = Event; + type WeightInfo = (); + type Currency = Balances; + type BalanceToU256 = BalanceToU256; + type U256ToBalance = U256ToBalance; + type StakingInterface = StakingMock; + type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow; + type PalletId = PoolsPalletId; + type MaxMetadataLen = MaxMetadataLen; + type MaxUnbonding = MaxUnbonding; +} + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; +frame_support::construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Storage, Event, Config}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Pools: pools::{Pallet, Call, Storage, Event}, + } +); + +#[derive(Default)] +pub struct ExtBuilder { + members: Vec<(AccountId, Balance)>, +} + +impl ExtBuilder { + // Add members to pool 0. + pub(crate) fn add_members(mut self, members: Vec<(AccountId, Balance)>) -> Self { + self.members = members; + self + } + + pub(crate) fn ed(self, ed: Balance) -> Self { + ExistentialDeposit::set(ed); + self + } + + pub(crate) fn with_check(self, level: u8) -> Self { + CheckLevel::set(level); + self + } + + pub(crate) fn build(self) -> sp_io::TestExternalities { + let mut storage = + frame_system::GenesisConfig::default().build_storage::().unwrap(); + + let _ = crate::GenesisConfig:: { + min_join_bond: 2, + min_create_bond: 2, + max_pools: Some(2), + max_members_per_pool: Some(3), + max_members: Some(4), + } + .assimilate_storage(&mut storage); + + let mut ext = sp_io::TestExternalities::from(storage); + + ext.execute_with(|| { + // for events to be deposited. + frame_system::Pallet::::set_block_number(1); + + // make a pool + let amount_to_bond = ::StakingInterface::minimum_bond(); + Balances::make_free_balance_be(&10, amount_to_bond * 2); + assert_ok!(Pools::create(RawOrigin::Signed(10).into(), amount_to_bond, 900, 901, 902)); + + let last_pool = LastPoolId::::get(); + for (account_id, bonded) in self.members { + Balances::make_free_balance_be(&account_id, bonded * 2); + assert_ok!(Pools::join(RawOrigin::Signed(account_id).into(), bonded, last_pool)); + } + }); + + ext + } + + pub fn build_and_execute(self, test: impl FnOnce() -> ()) { + self.build().execute_with(|| { + test(); + Pools::sanity_checks(CheckLevel::get()).unwrap(); + }) + } +} + +pub(crate) fn unsafe_set_state(pool_id: PoolId, state: PoolState) -> Result<(), ()> { + BondedPools::::try_mutate(pool_id, |maybe_bonded_pool| { + maybe_bonded_pool.as_mut().ok_or(()).map(|bonded_pool| { + bonded_pool.state = state; + }) + }) +} + +parameter_types! { + static ObservedEvents: usize = 0; +} + +/// All events of this pallet. +pub(crate) fn pool_events_since_last_call() -> Vec> { + let events = System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let Event::Pools(inner) = e { Some(inner) } else { None }) + .collect::>(); + let already_seen = ObservedEvents::get(); + ObservedEvents::set(events.len()); + events.into_iter().skip(already_seen).collect() +} + +/// Same as `fully_unbond`, in permissioned setting. +pub fn fully_unbond_permissioned(member: AccountId) -> DispatchResult { + let points = PoolMembers::::get(&member) + .map(|d| d.active_points()) + .unwrap_or_default(); + Pools::unbond(Origin::signed(member), member, points) +} + +#[cfg(test)] +mod test { + use super::*; + #[test] + fn u256_to_balance_convert_works() { + assert_eq!(U256ToBalance::convert(0u32.into()), Zero::zero()); + assert_eq!(U256ToBalance::convert(Balance::max_value().into()), Balance::max_value()) + } + + #[test] + #[should_panic] + fn u256_to_balance_convert_panics_correctly() { + U256ToBalance::convert(U256::from(Balance::max_value()).saturating_add(1u32.into())); + } + + #[test] + fn balance_to_u256_convert_works() { + assert_eq!(BalanceToU256::convert(0u32.into()), U256::zero()); + assert_eq!(BalanceToU256::convert(Balance::max_value()), Balance::max_value().into()) + } +} diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs new file mode 100644 index 0000000000000..7df922280873b --- /dev/null +++ b/frame/nomination-pools/src/tests.rs @@ -0,0 +1,2929 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::*; +use crate::{mock::*, Event}; +use frame_support::{ + assert_noop, assert_ok, assert_storage_noop, bounded_btree_map, + storage::{with_transaction, TransactionOutcome}, +}; + +macro_rules! unbonding_pools_with_era { + ($($k:expr => $v:expr),* $(,)?) => {{ + use sp_std::iter::{Iterator, IntoIterator}; + let not_bounded: BTreeMap<_, _> = Iterator::collect(IntoIterator::into_iter([$(($k, $v),)*])); + UnbondingPoolsWithEra::try_from(not_bounded).unwrap() + }}; +} + +macro_rules! member_unbonding_eras { + ($( $any:tt )*) => {{ + let x: BoundedBTreeMap = bounded_btree_map!($( $any )*); + x + }}; +} + +pub const DEFAULT_ROLES: PoolRoles = + PoolRoles { depositor: 10, root: 900, nominator: 901, state_toggler: 902 }; + +#[test] +fn test_setup_works() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(BondedPools::::count(), 1); + assert_eq!(RewardPools::::count(), 1); + assert_eq!(SubPoolsStorage::::count(), 0); + assert_eq!(PoolMembers::::count(), 1); + assert_eq!(StakingMock::bonding_duration(), 3); + + let last_pool = LastPoolId::::get(); + assert_eq!( + BondedPool::::get(last_pool).unwrap(), + BondedPool:: { + id: last_pool, + inner: BondedPoolInner { + state: PoolState::Open, + points: 10, + member_counter: 1, + roles: DEFAULT_ROLES + }, + } + ); + assert_eq!( + RewardPools::::get(last_pool).unwrap(), + RewardPool:: { balance: 0, points: 0.into(), total_earnings: 0 } + ); + assert_eq!( + PoolMembers::::get(10).unwrap(), + PoolMember:: { pool_id: last_pool, points: 10, ..Default::default() } + ) + }) +} + +mod bonded_pool { + use super::*; + #[test] + fn points_to_issue_works() { + let mut bonded_pool = BondedPool:: { + id: 123123, + inner: BondedPoolInner { + state: PoolState::Open, + points: 100, + member_counter: 1, + roles: DEFAULT_ROLES, + }, + }; + + // 1 points : 1 balance ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); + assert_eq!(bonded_pool.balance_to_point(10), 10); + assert_eq!(bonded_pool.balance_to_point(0), 0); + + // 2 points : 1 balance ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 50); + assert_eq!(bonded_pool.balance_to_point(10), 20); + + // 1 points : 2 balance ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); + bonded_pool.points = 50; + assert_eq!(bonded_pool.balance_to_point(10), 5); + + // 100 points : 0 balance ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 0); + bonded_pool.points = 100; + assert_eq!(bonded_pool.balance_to_point(10), 100 * 10); + + // 0 points : 100 balance + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); + bonded_pool.points = 100; + assert_eq!(bonded_pool.balance_to_point(10), 10); + + // 10 points : 3 balance ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 30); + assert_eq!(bonded_pool.balance_to_point(10), 33); + + // 2 points : 3 balance ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 300); + bonded_pool.points = 200; + assert_eq!(bonded_pool.balance_to_point(10), 6); + + // 4 points : 9 balance ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 900); + bonded_pool.points = 400; + assert_eq!(bonded_pool.balance_to_point(90), 40); + } + + #[test] + fn balance_to_unbond_works() { + // 1 balance : 1 points ratio + let mut bonded_pool = BondedPool:: { + id: 123123, + inner: BondedPoolInner { + state: PoolState::Open, + points: 100, + member_counter: 1, + roles: DEFAULT_ROLES, + }, + }; + + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); + assert_eq!(bonded_pool.points_to_balance(10), 10); + assert_eq!(bonded_pool.points_to_balance(0), 0); + + // 2 balance : 1 points ratio + bonded_pool.points = 50; + assert_eq!(bonded_pool.points_to_balance(10), 20); + + // 100 balance : 0 points ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 0); + bonded_pool.points = 0; + assert_eq!(bonded_pool.points_to_balance(10), 0); + + // 0 balance : 100 points ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 0); + bonded_pool.points = 100; + assert_eq!(bonded_pool.points_to_balance(10), 0); + + // 10 balance : 3 points ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); + bonded_pool.points = 30; + assert_eq!(bonded_pool.points_to_balance(10), 33); + + // 2 balance : 3 points ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 200); + bonded_pool.points = 300; + assert_eq!(bonded_pool.points_to_balance(10), 6); + + // 4 balance : 9 points ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 400); + bonded_pool.points = 900; + assert_eq!(bonded_pool.points_to_balance(90), 40); + } + + #[test] + fn ok_to_join_with_works() { + ExtBuilder::default().build_and_execute(|| { + let pool = BondedPool:: { + id: 123, + inner: BondedPoolInner { + state: PoolState::Open, + points: 100, + member_counter: 1, + roles: DEFAULT_ROLES, + }, + }; + + // Simulate a 100% slashed pool + StakingMock::set_bonded_balance(pool.bonded_account(), 0); + assert_noop!(pool.ok_to_join(0), Error::::OverflowRisk); + + // Simulate a 89% + StakingMock::set_bonded_balance(pool.bonded_account(), 11); + assert_ok!(pool.ok_to_join(0)); + + // Simulate a 90% slashed pool + StakingMock::set_bonded_balance(pool.bonded_account(), 10); + assert_noop!(pool.ok_to_join(0), Error::::OverflowRisk); + + StakingMock::set_bonded_balance(pool.bonded_account(), Balance::MAX / 10); + // New bonded balance would be over 1/10th of Balance type + assert_noop!(pool.ok_to_join(0), Error::::OverflowRisk); + // and a sanity check + StakingMock::set_bonded_balance(pool.bonded_account(), Balance::MAX / 10 - 1); + assert_ok!(pool.ok_to_join(0)); + }); + } +} + +mod reward_pool { + #[test] + fn current_balance_only_counts_balance_over_existential_deposit() { + use super::*; + + ExtBuilder::default().build_and_execute(|| { + let reward_account = Pools::create_reward_account(2); + + // Given + assert_eq!(Balances::free_balance(&reward_account), 0); + + // Then + assert_eq!(RewardPool::::current_balance(2), 0); + + // Given + Balances::make_free_balance_be(&reward_account, Balances::minimum_balance()); + + // Then + assert_eq!(RewardPool::::current_balance(2), 0); + + // Given + Balances::make_free_balance_be(&reward_account, Balances::minimum_balance() + 1); + + // Then + assert_eq!(RewardPool::::current_balance(2), 1); + }); + } +} + +mod unbond_pool { + use super::*; + + #[test] + fn points_to_issue_works() { + // 1 points : 1 balance ratio + let unbond_pool = UnbondPool:: { points: 100, balance: 100 }; + assert_eq!(unbond_pool.balance_to_point(10), 10); + assert_eq!(unbond_pool.balance_to_point(0), 0); + + // 2 points : 1 balance ratio + let unbond_pool = UnbondPool:: { points: 100, balance: 50 }; + assert_eq!(unbond_pool.balance_to_point(10), 20); + + // 1 points : 2 balance ratio + let unbond_pool = UnbondPool:: { points: 50, balance: 100 }; + assert_eq!(unbond_pool.balance_to_point(10), 5); + + // 100 points : 0 balance ratio + let unbond_pool = UnbondPool:: { points: 100, balance: 0 }; + assert_eq!(unbond_pool.balance_to_point(10), 100 * 10); + + // 0 points : 100 balance + let unbond_pool = UnbondPool:: { points: 0, balance: 100 }; + assert_eq!(unbond_pool.balance_to_point(10), 10); + + // 10 points : 3 balance ratio + let unbond_pool = UnbondPool:: { points: 100, balance: 30 }; + assert_eq!(unbond_pool.balance_to_point(10), 33); + + // 2 points : 3 balance ratio + let unbond_pool = UnbondPool:: { points: 200, balance: 300 }; + assert_eq!(unbond_pool.balance_to_point(10), 6); + + // 4 points : 9 balance ratio + let unbond_pool = UnbondPool:: { points: 400, balance: 900 }; + assert_eq!(unbond_pool.balance_to_point(90), 40); + } + + #[test] + fn balance_to_unbond_works() { + // 1 balance : 1 points ratio + let unbond_pool = UnbondPool:: { points: 100, balance: 100 }; + assert_eq!(unbond_pool.point_to_balance(10), 10); + assert_eq!(unbond_pool.point_to_balance(0), 0); + + // 1 balance : 2 points ratio + let unbond_pool = UnbondPool:: { points: 100, balance: 50 }; + assert_eq!(unbond_pool.point_to_balance(10), 5); + + // 2 balance : 1 points ratio + let unbond_pool = UnbondPool:: { points: 50, balance: 100 }; + assert_eq!(unbond_pool.point_to_balance(10), 20); + + // 100 balance : 0 points ratio + let unbond_pool = UnbondPool:: { points: 0, balance: 100 }; + assert_eq!(unbond_pool.point_to_balance(10), 0); + + // 0 balance : 100 points ratio + let unbond_pool = UnbondPool:: { points: 100, balance: 0 }; + assert_eq!(unbond_pool.point_to_balance(10), 0); + + // 10 balance : 3 points ratio + let unbond_pool = UnbondPool:: { points: 30, balance: 100 }; + assert_eq!(unbond_pool.point_to_balance(10), 33); + + // 2 balance : 3 points ratio + let unbond_pool = UnbondPool:: { points: 300, balance: 200 }; + assert_eq!(unbond_pool.point_to_balance(10), 6); + + // 4 balance : 9 points ratio + let unbond_pool = UnbondPool:: { points: 900, balance: 400 }; + assert_eq!(unbond_pool.point_to_balance(90), 40); + } +} + +mod sub_pools { + use super::*; + + #[test] + fn maybe_merge_pools_works() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(TotalUnbondingPools::::get(), 5); + + // Given + let mut sub_pool_0 = SubPools:: { + no_era: UnbondPool::::default(), + with_era: unbonding_pools_with_era! { + 0 => UnbondPool:: { points: 10, balance: 10 }, + 1 => UnbondPool:: { points: 10, balance: 10 }, + 2 => UnbondPool:: { points: 20, balance: 20 }, + 3 => UnbondPool:: { points: 30, balance: 30 }, + 4 => UnbondPool:: { points: 40, balance: 40 }, + }, + }; + + // When `current_era < TotalUnbondingPools`, + let sub_pool_1 = sub_pool_0.clone().maybe_merge_pools(3); + + // Then it exits early without modifications + assert_eq!(sub_pool_1, sub_pool_0); + + // When `current_era == TotalUnbondingPools`, + let sub_pool_1 = sub_pool_1.maybe_merge_pools(4); + + // Then it exits early without modifications + assert_eq!(sub_pool_1, sub_pool_0); + + // When `current_era - TotalUnbondingPools == 0`, + let mut sub_pool_1 = sub_pool_1.maybe_merge_pools(5); + + // Then era 0 is merged into the `no_era` pool + sub_pool_0.no_era = sub_pool_0.with_era.remove(&0).unwrap(); + assert_eq!(sub_pool_1, sub_pool_0); + + // Given we have entries for era 1..=5 + sub_pool_1 + .with_era + .try_insert(5, UnbondPool:: { points: 50, balance: 50 }) + .unwrap(); + sub_pool_0 + .with_era + .try_insert(5, UnbondPool:: { points: 50, balance: 50 }) + .unwrap(); + + // When `current_era - TotalUnbondingPools == 1` + let sub_pool_2 = sub_pool_1.maybe_merge_pools(6); + let era_1_pool = sub_pool_0.with_era.remove(&1).unwrap(); + + // Then era 1 is merged into the `no_era` pool + sub_pool_0.no_era.points += era_1_pool.points; + sub_pool_0.no_era.balance += era_1_pool.balance; + assert_eq!(sub_pool_2, sub_pool_0); + + // When `current_era - TotalUnbondingPools == 5`, so all pools with era <= 4 are removed + let sub_pool_3 = sub_pool_2.maybe_merge_pools(10); + + // Then all eras <= 5 are merged into the `no_era` pool + for era in 2..=5 { + let to_merge = sub_pool_0.with_era.remove(&era).unwrap(); + sub_pool_0.no_era.points += to_merge.points; + sub_pool_0.no_era.balance += to_merge.balance; + } + assert_eq!(sub_pool_3, sub_pool_0); + }); + } +} + +mod join { + use super::*; + + #[test] + fn join_works() { + let bonded = |points, member_counter| BondedPool:: { + id: 1, + inner: BondedPoolInner { + state: PoolState::Open, + points, + member_counter, + roles: DEFAULT_ROLES, + }, + }; + ExtBuilder::default().build_and_execute(|| { + // Given + Balances::make_free_balance_be(&11, ExistentialDeposit::get() + 2); + assert!(!PoolMembers::::contains_key(&11)); + + // When + assert_ok!(Pools::join(Origin::signed(11), 2, 1)); + + // then + assert_eq!( + PoolMembers::::get(&11).unwrap(), + PoolMember:: { pool_id: 1, points: 2, ..Default::default() } + ); + assert_eq!(BondedPool::::get(1).unwrap(), bonded(12, 2)); + + // Given + // The bonded balance is slashed in half + StakingMock::set_bonded_balance(Pools::create_bonded_account(1), 6); + + // And + Balances::make_free_balance_be(&12, ExistentialDeposit::get() + 12); + assert!(!PoolMembers::::contains_key(&12)); + + // When + assert_ok!(Pools::join(Origin::signed(12), 12, 1)); + + // Then + assert_eq!( + PoolMembers::::get(&12).unwrap(), + PoolMember:: { pool_id: 1, points: 24, ..Default::default() } + ); + assert_eq!(BondedPool::::get(1).unwrap(), bonded(12 + 24, 3)); + }); + } + + #[test] + fn join_errors_correctly() { + ExtBuilder::default().with_check(0).build_and_execute(|| { + // 10 is already part of the default pool created. + assert_eq!(PoolMembers::::get(&10).unwrap().pool_id, 1); + + assert_noop!( + Pools::join(Origin::signed(10), 420, 123), + Error::::AccountBelongsToOtherPool + ); + + assert_noop!(Pools::join(Origin::signed(11), 420, 123), Error::::PoolNotFound); + + // Force the pools bonded balance to 0, simulating a 100% slash + StakingMock::set_bonded_balance(Pools::create_bonded_account(1), 0); + assert_noop!(Pools::join(Origin::signed(11), 420, 1), Error::::OverflowRisk); + + // Given a mocked bonded pool + BondedPool:: { + id: 123, + inner: BondedPoolInner { + member_counter: 1, + state: PoolState::Open, + points: 100, + roles: DEFAULT_ROLES, + }, + } + .put(); + + // and reward pool + RewardPools::::insert( + 123, + RewardPool:: { + balance: Zero::zero(), + total_earnings: Zero::zero(), + points: U256::from(0u32), + }, + ); + + // Force the points:balance ratio to 100/10 + StakingMock::set_bonded_balance(Pools::create_bonded_account(123), 10); + assert_noop!(Pools::join(Origin::signed(11), 420, 123), Error::::OverflowRisk); + + StakingMock::set_bonded_balance(Pools::create_bonded_account(123), Balance::MAX / 10); + // Balance is gt 1/10 of Balance::MAX + assert_noop!(Pools::join(Origin::signed(11), 5, 123), Error::::OverflowRisk); + + StakingMock::set_bonded_balance(Pools::create_bonded_account(1), 10); + + // Cannot join a pool that isn't open + unsafe_set_state(123, PoolState::Blocked).unwrap(); + assert_noop!(Pools::join(Origin::signed(11), 10, 123), Error::::NotOpen); + + unsafe_set_state(123, PoolState::Destroying).unwrap(); + assert_noop!(Pools::join(Origin::signed(11), 10, 123), Error::::NotOpen); + + // Given + MinJoinBond::::put(100); + + // Then + assert_noop!( + Pools::join(Origin::signed(11), 99, 123), + Error::::MinimumBondNotMet + ); + }); + } + + #[test] + #[cfg_attr(debug_assertions, should_panic(expected = "Defensive failure has been triggered!"))] + #[cfg_attr(not(debug_assertions), should_panic)] + fn join_panics_when_reward_pool_not_found() { + ExtBuilder::default().build_and_execute(|| { + StakingMock::set_bonded_balance(Pools::create_bonded_account(123), 100); + BondedPool:: { + id: 123, + inner: BondedPoolInner { + state: PoolState::Open, + points: 100, + member_counter: 1, + roles: DEFAULT_ROLES, + }, + } + .put(); + let _ = Pools::join(Origin::signed(11), 420, 123); + }); + } + + #[test] + fn join_max_member_limits_are_respected() { + ExtBuilder::default().build_and_execute(|| { + // Given + assert_eq!(MaxPoolMembersPerPool::::get(), Some(3)); + for i in 1..3 { + let account = i + 100; + Balances::make_free_balance_be(&account, 100 + Balances::minimum_balance()); + + assert_ok!(Pools::join(Origin::signed(account), 100, 1)); + } + + Balances::make_free_balance_be(&103, 100 + Balances::minimum_balance()); + + // Then + assert_noop!( + Pools::join(Origin::signed(103), 100, 1), + Error::::MaxPoolMembers + ); + + // Given + assert_eq!(PoolMembers::::count(), 3); + assert_eq!(MaxPoolMembers::::get(), Some(4)); + + Balances::make_free_balance_be(&104, 100 + Balances::minimum_balance()); + assert_ok!(Pools::create(Origin::signed(104), 100, 104, 104, 104)); + + let pool_account = BondedPools::::iter() + .find(|(_, bonded_pool)| bonded_pool.roles.depositor == 104) + .map(|(pool_account, _)| pool_account) + .unwrap(); + + // Then + assert_noop!( + Pools::join(Origin::signed(103), 100, pool_account), + Error::::MaxPoolMembers + ); + }); + } +} + +mod claim_payout { + use super::*; + + fn del(points: Balance, reward_pool_total_earnings: Balance) -> PoolMember { + PoolMember { + pool_id: 1, + points, + reward_pool_total_earnings, + unbonding_eras: Default::default(), + } + } + + fn rew(balance: Balance, points: u32, total_earnings: Balance) -> RewardPool { + RewardPool { balance, points: points.into(), total_earnings } + } + + #[test] + fn claim_payout_works() { + ExtBuilder::default() + .add_members(vec![(40, 40), (50, 50)]) + .build_and_execute(|| { + // Given each member currently has a free balance of + Balances::make_free_balance_be(&10, 0); + Balances::make_free_balance_be(&40, 0); + Balances::make_free_balance_be(&50, 0); + let ed = Balances::minimum_balance(); + // and the reward pool has earned 100 in rewards + Balances::make_free_balance_be(&default_reward_account(), ed + 100); + + // When + assert_ok!(Pools::claim_payout(Origin::signed(10))); + + // Then + // Expect a payout of 10: (10 del virtual points / 100 pool points) * 100 pool + // balance + assert_eq!(PoolMembers::::get(10).unwrap(), del(10, 100)); + assert_eq!( + RewardPools::::get(&1).unwrap(), + rew(90, 100 * 100 - 100 * 10, 100) + ); + assert_eq!(Balances::free_balance(&10), 10); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 90); + + // When + assert_ok!(Pools::claim_payout(Origin::signed(40))); + + // Then + // Expect payout 40: (400 del virtual points / 900 pool points) * 90 pool balance + assert_eq!(PoolMembers::::get(40).unwrap(), del(40, 100)); + assert_eq!( + RewardPools::::get(&1).unwrap(), + rew(50, 9_000 - 100 * 40, 100) + ); + assert_eq!(Balances::free_balance(&40), 40); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 50); + + // When + assert_ok!(Pools::claim_payout(Origin::signed(50))); + + // Then + // Expect payout 50: (50 del virtual points / 50 pool points) * 50 pool balance + assert_eq!(PoolMembers::::get(50).unwrap(), del(50, 100)); + assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 100)); + assert_eq!(Balances::free_balance(&50), 50); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 0); + + // Given the reward pool has some new rewards + Balances::make_free_balance_be(&default_reward_account(), ed + 50); + + // When + assert_ok!(Pools::claim_payout(Origin::signed(10))); + + // Then + // Expect payout 5: (500 del virtual points / 5,000 pool points) * 50 pool balance + assert_eq!(PoolMembers::::get(10).unwrap(), del(10, 150)); + assert_eq!(RewardPools::::get(&1).unwrap(), rew(45, 5_000 - 50 * 10, 150)); + assert_eq!(Balances::free_balance(&10), 10 + 5); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 45); + + // When + assert_ok!(Pools::claim_payout(Origin::signed(40))); + + // Then + // Expect payout 20: (2,000 del virtual points / 4,500 pool points) * 45 pool + // balance + assert_eq!(PoolMembers::::get(40).unwrap(), del(40, 150)); + assert_eq!(RewardPools::::get(&1).unwrap(), rew(25, 4_500 - 50 * 40, 150)); + assert_eq!(Balances::free_balance(&40), 40 + 20); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 25); + + // Given del 50 hasn't claimed and the reward pools has just earned 50 + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 50)); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 75); + + // When + assert_ok!(Pools::claim_payout(Origin::signed(50))); + + // Then + // We expect a payout of 50: (5,000 del virtual points / 7,5000 pool points) * 75 + // pool balance + assert_eq!(PoolMembers::::get(50).unwrap(), del(50, 200)); + assert_eq!( + RewardPools::::get(&1).unwrap(), + rew( + 25, + // old pool points + points from new earnings - del points. + // + // points from new earnings = new earnings(50) * bonded_pool.points(100) + // del points = member.points(50) * new_earnings_since_last_claim (100) + (2_500 + 50 * 100) - 50 * 100, + 200, + ) + ); + assert_eq!(Balances::free_balance(&50), 50 + 50); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 25); + + // When + assert_ok!(Pools::claim_payout(Origin::signed(10))); + + // Then + // We expect a payout of 5 + assert_eq!(PoolMembers::::get(10).unwrap(), del(10, 200)); + assert_eq!(RewardPools::::get(&1).unwrap(), rew(20, 2_500 - 10 * 50, 200)); + assert_eq!(Balances::free_balance(&10), 15 + 5); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 20); + + // Given del 40 hasn't claimed and the reward pool has just earned 400 + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 400)); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 420); + + // When + assert_ok!(Pools::claim_payout(Origin::signed(10))); + + // Then + // We expect a payout of 40 + assert_eq!(PoolMembers::::get(10).unwrap(), del(10, 600)); + assert_eq!( + RewardPools::::get(&1).unwrap(), + rew( + 380, + // old pool points + points from new earnings - del points + // + // points from new earnings = new earnings(400) * bonded_pool.points(100) + // del points = member.points(10) * new_earnings_since_last_claim(400) + (2_000 + 400 * 100) - 10 * 400, + 600 + ) + ); + assert_eq!(Balances::free_balance(&10), 20 + 40); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 380); + + // Given del 40 + del 50 haven't claimed and the reward pool has earned 20 + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 20)); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 400); + + // When + assert_ok!(Pools::claim_payout(Origin::signed(10))); + + // Then + // Expect a payout of 2: (200 del virtual points / 38,000 pool points) * 400 pool + // balance + assert_eq!(PoolMembers::::get(10).unwrap(), del(10, 620)); + assert_eq!( + RewardPools::::get(&1).unwrap(), + rew(398, (38_000 + 20 * 100) - 10 * 20, 620) + ); + assert_eq!(Balances::free_balance(&10), 60 + 2); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 398); + + // When + assert_ok!(Pools::claim_payout(Origin::signed(40))); + + // Then + // Expect a payout of 188: (18,800 del virtual points / 39,800 pool points) * 399 + // pool balance + assert_eq!(PoolMembers::::get(40).unwrap(), del(40, 620)); + assert_eq!( + RewardPools::::get(&1).unwrap(), + rew(210, 39_800 - 40 * 470, 620) + ); + assert_eq!(Balances::free_balance(&40), 60 + 188); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 210); + + // When + assert_ok!(Pools::claim_payout(Origin::signed(50))); + + // Then + // Expect payout of 210: (21,000 / 21,000) * 210 + assert_eq!(PoolMembers::::get(50).unwrap(), del(50, 620)); + assert_eq!( + RewardPools::::get(&1).unwrap(), + rew(0, 21_000 - 50 * 420, 620) + ); + assert_eq!(Balances::free_balance(&50), 100 + 210); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 0); + }); + } + + #[test] + fn do_reward_payout_correctly_sets_pool_state_to_destroying() { + ExtBuilder::default().build_and_execute(|| { + let _ = with_transaction(|| -> TransactionOutcome { + let mut bonded_pool = BondedPool::::get(1).unwrap(); + let mut reward_pool = RewardPools::::get(1).unwrap(); + let mut member = PoolMembers::::get(10).unwrap(); + + // -- reward_pool.total_earnings saturates + + // Given + Balances::make_free_balance_be(&default_reward_account(), Balance::MAX); + + // When + assert_ok!(Pools::do_reward_payout( + &10, + &mut member, + &mut bonded_pool, + &mut reward_pool + )); + + // Then + assert!(bonded_pool.is_destroying()); + + storage::TransactionOutcome::Rollback(Ok(())) + }); + + // -- current_points saturates (reward_pool.points + new_earnings * bonded_pool.points) + let _ = with_transaction(|| -> TransactionOutcome { + // Given + let mut bonded_pool = BondedPool::::get(1).unwrap(); + let mut reward_pool = RewardPools::::get(1).unwrap(); + let mut member = PoolMembers::::get(10).unwrap(); + // Force new_earnings * bonded_pool.points == 100 + Balances::make_free_balance_be(&default_reward_account(), 5 + 10); + assert_eq!(bonded_pool.points, 10); + // Force reward_pool.points == U256::MAX - new_earnings * bonded_pool.points + reward_pool.points = U256::MAX - U256::from(100u32); + RewardPools::::insert(1, reward_pool.clone()); + + // When + assert_ok!(Pools::do_reward_payout( + &10, + &mut member, + &mut bonded_pool, + &mut reward_pool + )); + + // Then + assert!(bonded_pool.is_destroying()); + + storage::TransactionOutcome::Rollback(Ok(())) + }); + }); + } + + #[test] + fn reward_payout_errors_if_a_member_is_fully_unbonding() { + ExtBuilder::default().add_members(vec![(11, 11)]).build_and_execute(|| { + // fully unbond the member. + assert_ok!(Pools::fully_unbond(Origin::signed(11), 11)); + + let mut bonded_pool = BondedPool::::get(1).unwrap(); + let mut reward_pool = RewardPools::::get(1).unwrap(); + let mut member = PoolMembers::::get(11).unwrap(); + + assert_noop!( + Pools::do_reward_payout(&11, &mut member, &mut bonded_pool, &mut reward_pool,), + Error::::FullyUnbonding + ); + }); + } + + #[test] + fn calculate_member_payout_works_with_a_pool_of_1() { + let del = |reward_pool_total_earnings| del(10, reward_pool_total_earnings); + + ExtBuilder::default().build_and_execute(|| { + let mut bonded_pool = BondedPool::::get(1).unwrap(); + let mut reward_pool = RewardPools::::get(1).unwrap(); + let mut member = PoolMembers::::get(10).unwrap(); + let ed = Balances::minimum_balance(); + + // Given no rewards have been earned + // When + let payout = + Pools::calculate_member_payout(&mut member, &mut bonded_pool, &mut reward_pool) + .unwrap(); + + // Then + assert_eq!(payout, 0); + assert_eq!(member, del(0)); + assert_eq!(reward_pool, rew(0, 0, 0)); + + // Given the pool has earned some rewards for the first time + Balances::make_free_balance_be(&default_reward_account(), ed + 5); + + // When + let payout = + Pools::calculate_member_payout(&mut member, &mut bonded_pool, &mut reward_pool) + .unwrap(); + + // Then + assert_eq!(payout, 5); // (10 * 5 del virtual points / 10 * 5 pool points) * 5 pool balance + assert_eq!(reward_pool, rew(0, 0, 5)); + assert_eq!(member, del(5)); + + // Given the pool has earned rewards again + Balances::make_free_balance_be(&default_reward_account(), ed + 10); + + // When + let payout = + Pools::calculate_member_payout(&mut member, &mut bonded_pool, &mut reward_pool) + .unwrap(); + + // Then + assert_eq!(payout, 10); // (10 * 10 del virtual points / 10 pool points) * 5 pool balance + assert_eq!(reward_pool, rew(0, 0, 15)); + assert_eq!(member, del(15)); + + // Given the pool has earned no new rewards + Balances::make_free_balance_be(&default_reward_account(), ed + 0); + + // When + let payout = + Pools::calculate_member_payout(&mut member, &mut bonded_pool, &mut reward_pool) + .unwrap(); + + // Then + assert_eq!(payout, 0); + assert_eq!(reward_pool, rew(0, 0, 15)); + assert_eq!(member, del(15)); + }); + } + + #[test] + fn calculate_member_payout_works_with_a_pool_of_3() { + ExtBuilder::default() + .add_members(vec![(40, 40), (50, 50)]) + .build_and_execute(|| { + let mut bonded_pool = BondedPool::::get(1).unwrap(); + let mut reward_pool = RewardPools::::get(1).unwrap(); + let ed = Balances::minimum_balance(); + // PoolMember with 10 points + let mut del_10 = PoolMembers::::get(10).unwrap(); + // PoolMember with 40 points + let mut del_40 = PoolMembers::::get(40).unwrap(); + // PoolMember with 50 points + let mut del_50 = PoolMembers::::get(50).unwrap(); + + // Given we have a total of 100 points split among the members + assert_eq!(del_50.points + del_40.points + del_10.points, 100); + assert_eq!(bonded_pool.points, 100); + // and the reward pool has earned 100 in rewards + Balances::make_free_balance_be(&default_reward_account(), ed + 100); + + // When + let payout = + Pools::calculate_member_payout(&mut del_10, &mut bonded_pool, &mut reward_pool) + .unwrap(); + + // Then + assert_eq!(payout, 10); // (10 del virtual points / 100 pool points) * 100 pool balance + assert_eq!(del_10, del(10, 100)); + assert_eq!(reward_pool, rew(90, 100 * 100 - 100 * 10, 100)); + // Mock the reward pool transferring the payout to del_10 + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 10)); + + // When + let payout = + Pools::calculate_member_payout(&mut del_40, &mut bonded_pool, &mut reward_pool) + .unwrap(); + + // Then + assert_eq!(payout, 40); // (400 del virtual points / 900 pool points) * 90 pool balance + assert_eq!(del_40, del(40, 100)); + assert_eq!( + reward_pool, + rew( + 50, + // old pool points - member virtual points + 9_000 - 100 * 40, + 100 + ) + ); + // Mock the reward pool transferring the payout to del_40 + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 40)); + + // When + let payout = + Pools::calculate_member_payout(&mut del_50, &mut bonded_pool, &mut reward_pool) + .unwrap(); + + // Then + assert_eq!(payout, 50); // (50 del virtual points / 50 pool points) * 50 pool balance + assert_eq!(del_50, del(50, 100)); + assert_eq!(reward_pool, rew(0, 0, 100)); + // Mock the reward pool transferring the payout to del_50 + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 50)); + + // Given the reward pool has some new rewards + Balances::make_free_balance_be(&default_reward_account(), ed + 50); + + // When + let payout = + Pools::calculate_member_payout(&mut del_10, &mut bonded_pool, &mut reward_pool) + .unwrap(); + + // Then + assert_eq!(payout, 5); // (500 del virtual points / 5,000 pool points) * 50 pool balance + assert_eq!(del_10, del(10, 150)); + assert_eq!(reward_pool, rew(45, 5_000 - 50 * 10, 150)); + // Mock the reward pool transferring the payout to del_10 + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 5)); + + // When + let payout = + Pools::calculate_member_payout(&mut del_40, &mut bonded_pool, &mut reward_pool) + .unwrap(); + + // Then + assert_eq!(payout, 20); // (2,000 del virtual points / 4,500 pool points) * 45 pool balance + assert_eq!(del_40, del(40, 150)); + assert_eq!(reward_pool, rew(25, 4_500 - 50 * 40, 150)); + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 20)); + + // Given del_50 hasn't claimed and the reward pools has just earned 50 + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 50)); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 75); + + // When + let payout = + Pools::calculate_member_payout(&mut del_50, &mut bonded_pool, &mut reward_pool) + .unwrap(); + + // Then + assert_eq!(payout, 50); // (5,000 del virtual points / 7,5000 pool points) * 75 pool balance + assert_eq!(del_50, del(50, 200)); + assert_eq!( + reward_pool, + rew( + 25, + // old pool points + points from new earnings - del points. + // + // points from new earnings = new earnings(50) * bonded_pool.points(100) + // del points = member.points(50) * new_earnings_since_last_claim (100) + (2_500 + 50 * 100) - 50 * 100, + 200, + ) + ); + // Mock the reward pool transferring the payout to del_50 + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 50)); + + // When + let payout = + Pools::calculate_member_payout(&mut del_10, &mut bonded_pool, &mut reward_pool) + .unwrap(); + + // Then + assert_eq!(payout, 5); + assert_eq!(del_10, del(10, 200)); + assert_eq!(reward_pool, rew(20, 2_500 - 10 * 50, 200)); + // Mock the reward pool transferring the payout to del_10 + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 5)); + + // Given del_40 hasn't claimed and the reward pool has just earned 400 + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 400)); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 420); + + // When + let payout = + Pools::calculate_member_payout(&mut del_10, &mut bonded_pool, &mut reward_pool) + .unwrap(); + + // Then + assert_eq!(payout, 40); + assert_eq!(del_10, del(10, 600)); + assert_eq!( + reward_pool, + rew( + 380, + // old pool points + points from new earnings - del points + // + // points from new earnings = new earnings(400) * bonded_pool.points(100) + // del points = member.points(10) * new_earnings_since_last_claim(400) + (2_000 + 400 * 100) - 10 * 400, + 600 + ) + ); + // Mock the reward pool transferring the payout to del_10 + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 40)); + + // Given del_40 + del_50 haven't claimed and the reward pool has earned 20 + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 20)); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 400); + + // When + let payout = + Pools::calculate_member_payout(&mut del_10, &mut bonded_pool, &mut reward_pool) + .unwrap(); + + // Then + assert_eq!(payout, 2); // (200 del virtual points / 38,000 pool points) * 400 pool balance + assert_eq!(del_10, del(10, 620)); + assert_eq!(reward_pool, rew(398, (38_000 + 20 * 100) - 10 * 20, 620)); + // Mock the reward pool transferring the payout to del_10 + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 2)); + + // When + let payout = + Pools::calculate_member_payout(&mut del_40, &mut bonded_pool, &mut reward_pool) + .unwrap(); + + // Then + assert_eq!(payout, 188); // (18,800 del virtual points / 39,800 pool points) * 399 pool balance + assert_eq!(del_40, del(40, 620)); + assert_eq!(reward_pool, rew(210, 39_800 - 40 * 470, 620)); + // Mock the reward pool transferring the payout to del_10 + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 188)); + + // When + let payout = + Pools::calculate_member_payout(&mut del_50, &mut bonded_pool, &mut reward_pool) + .unwrap(); + + // Then + assert_eq!(payout, 210); // (21,000 / 21,000) * 210 + assert_eq!(del_50, del(50, 620)); + assert_eq!(reward_pool, rew(0, 21_000 - 50 * 420, 620)); + }); + } + + #[test] + fn do_reward_payout_works() { + ExtBuilder::default() + .add_members(vec![(40, 40), (50, 50)]) + .build_and_execute(|| { + let mut bonded_pool = BondedPool::::get(1).unwrap(); + let mut reward_pool = RewardPools::::get(1).unwrap(); + let ed = Balances::minimum_balance(); + + // Given the bonded pool has 100 points + assert_eq!(bonded_pool.points, 100); + // Each member currently has a free balance of + Balances::make_free_balance_be(&10, 0); + Balances::make_free_balance_be(&40, 0); + Balances::make_free_balance_be(&50, 0); + // and the reward pool has earned 100 in rewards + Balances::make_free_balance_be(&default_reward_account(), ed + 100); + + let mut del_10 = PoolMembers::get(10).unwrap(); + let mut del_40 = PoolMembers::get(40).unwrap(); + let mut del_50 = PoolMembers::get(50).unwrap(); + + // When + assert_ok!(Pools::do_reward_payout( + &10, + &mut del_10, + &mut bonded_pool, + &mut reward_pool + )); + + // Then + // Expect a payout of 10: (10 del virtual points / 100 pool points) * 100 pool + // balance + assert_eq!(del_10, del(10, 100)); + assert_eq!(reward_pool, rew(90, 100 * 100 - 100 * 10, 100)); + assert_eq!(Balances::free_balance(&10), 10); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 90); + + // When + assert_ok!(Pools::do_reward_payout( + &40, + &mut del_40, + &mut bonded_pool, + &mut reward_pool + )); + + // Then + // Expect payout 40: (400 del virtual points / 900 pool points) * 90 pool balance + assert_eq!(del_40, del(40, 100)); + assert_eq!(reward_pool, rew(50, 9_000 - 100 * 40, 100)); + assert_eq!(Balances::free_balance(&40), 40); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 50); + + // When + assert_ok!(Pools::do_reward_payout( + &50, + &mut del_50, + &mut bonded_pool, + &mut reward_pool + )); + + // Then + // Expect payout 50: (50 del virtual points / 50 pool points) * 50 pool balance + assert_eq!(del_50, del(50, 100)); + assert_eq!(reward_pool, rew(0, 0, 100)); + assert_eq!(Balances::free_balance(&50), 50); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 0); + + // Given the reward pool has some new rewards + Balances::make_free_balance_be(&default_reward_account(), ed + 50); + + // When + assert_ok!(Pools::do_reward_payout( + &10, + &mut del_10, + &mut bonded_pool, + &mut reward_pool + )); + + // Then + // Expect payout 5: (500 del virtual points / 5,000 pool points) * 50 pool balance + assert_eq!(del_10, del(10, 150)); + assert_eq!(reward_pool, rew(45, 5_000 - 50 * 10, 150)); + assert_eq!(Balances::free_balance(&10), 10 + 5); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 45); + + // When + assert_ok!(Pools::do_reward_payout( + &40, + &mut del_40, + &mut bonded_pool, + &mut reward_pool + )); + + // Then + // Expect payout 20: (2,000 del virtual points / 4,500 pool points) * 45 pool + // balance + assert_eq!(del_40, del(40, 150)); + assert_eq!(reward_pool, rew(25, 4_500 - 50 * 40, 150)); + assert_eq!(Balances::free_balance(&40), 40 + 20); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 25); + + // Given del 50 hasn't claimed and the reward pools has just earned 50 + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 50)); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 75); + + // When + assert_ok!(Pools::do_reward_payout( + &50, + &mut del_50, + &mut bonded_pool, + &mut reward_pool + )); + + // Then + // We expect a payout of 50: (5,000 del virtual points / 7,5000 pool points) * 75 + // pool balance + assert_eq!(del_50, del(50, 200)); + assert_eq!( + reward_pool, + rew( + 25, + // old pool points + points from new earnings - del points. + // + // points from new earnings = new earnings(50) * bonded_pool.points(100) + // del points = member.points(50) * new_earnings_since_last_claim (100) + (2_500 + 50 * 100) - 50 * 100, + 200, + ) + ); + assert_eq!(Balances::free_balance(&50), 50 + 50); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 25); + + // When + assert_ok!(Pools::do_reward_payout( + &10, + &mut del_10, + &mut bonded_pool, + &mut reward_pool + )); + + // Then + // We expect a payout of 5 + assert_eq!(del_10, del(10, 200)); + assert_eq!(reward_pool, rew(20, 2_500 - 10 * 50, 200)); + assert_eq!(Balances::free_balance(&10), 15 + 5); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 20); + + // Given del 40 hasn't claimed and the reward pool has just earned 400 + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 400)); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 420); + + // When + assert_ok!(Pools::do_reward_payout( + &10, + &mut del_10, + &mut bonded_pool, + &mut reward_pool + )); + + // Then + // We expect a payout of 40 + assert_eq!(del_10, del(10, 600)); + assert_eq!( + reward_pool, + rew( + 380, + // old pool points + points from new earnings - del points + // + // points from new earnings = new earnings(400) * bonded_pool.points(100) + // del points = member.points(10) * new_earnings_since_last_claim(400) + (2_000 + 400 * 100) - 10 * 400, + 600 + ) + ); + assert_eq!(Balances::free_balance(&10), 20 + 40); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 380); + + // Given del 40 + del 50 haven't claimed and the reward pool has earned 20 + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 20)); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 400); + + // When + assert_ok!(Pools::do_reward_payout( + &10, + &mut del_10, + &mut bonded_pool, + &mut reward_pool + )); + + // Then + // Expect a payout of 2: (200 del virtual points / 38,000 pool points) * 400 pool + // balance + assert_eq!(del_10, del(10, 620)); + assert_eq!(reward_pool, rew(398, (38_000 + 20 * 100) - 10 * 20, 620)); + assert_eq!(Balances::free_balance(&10), 60 + 2); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 398); + + // When + assert_ok!(Pools::do_reward_payout( + &40, + &mut del_40, + &mut bonded_pool, + &mut reward_pool + )); + + // Then + // Expect a payout of 188: (18,800 del virtual points / 39,800 pool points) * 399 + // pool balance + assert_eq!(del_40, del(40, 620)); + assert_eq!(reward_pool, rew(210, 39_800 - 40 * 470, 620)); + assert_eq!(Balances::free_balance(&40), 60 + 188); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 210); + + // When + assert_ok!(Pools::do_reward_payout( + &50, + &mut del_50, + &mut bonded_pool, + &mut reward_pool + )); + + // Then + // Expect payout of 210: (21,000 / 21,000) * 210 + assert_eq!(del_50, del(50, 620)); + assert_eq!(reward_pool, rew(0, 21_000 - 50 * 420, 620)); + assert_eq!(Balances::free_balance(&50), 100 + 210); + assert_eq!(Balances::free_balance(&default_reward_account()), ed + 0); + }); + } +} + +mod unbond { + use super::*; + + #[test] + fn unbond_of_1_works() { + ExtBuilder::default().build_and_execute(|| { + unsafe_set_state(1, PoolState::Destroying).unwrap(); + assert_ok!(fully_unbond_permissioned(10)); + + assert_eq!( + SubPoolsStorage::::get(1).unwrap().with_era, + unbonding_pools_with_era! { 0 + 3 => UnbondPool:: { points: 10, balance: 10 }} + ); + + assert_eq!( + BondedPool::::get(1).unwrap(), + BondedPool { + id: 1, + inner: BondedPoolInner { + state: PoolState::Destroying, + points: 0, + member_counter: 1, + roles: DEFAULT_ROLES, + } + } + ); + + assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 0); + }); + } + + #[test] + fn unbond_of_3_works() { + ExtBuilder::default() + .add_members(vec![(40, 40), (550, 550)]) + .build_and_execute(|| { + let ed = Balances::minimum_balance(); + // Given a slash from 600 -> 100 + StakingMock::set_bonded_balance(default_bonded_account(), 100); + // and unclaimed rewards of 600. + Balances::make_free_balance_be(&default_reward_account(), ed + 600); + + // When + assert_ok!(fully_unbond_permissioned(40)); + + // Then + assert_eq!( + SubPoolsStorage::::get(1).unwrap().with_era, + unbonding_pools_with_era! { 0 + 3 => UnbondPool { points: 6, balance: 6 }} + ); + assert_eq!( + BondedPool::::get(1).unwrap(), + BondedPool { + id: 1, + inner: BondedPoolInner { + state: PoolState::Open, + points: 560, + member_counter: 3, + roles: DEFAULT_ROLES, + } + } + ); + + assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 94); + assert_eq!( + PoolMembers::::get(40).unwrap().unbonding_eras, + member_unbonding_eras!(0 + 3 => 40) + ); + assert_eq!(Balances::free_balance(&40), 40 + 40); // We claim rewards when unbonding + + // When + unsafe_set_state(1, PoolState::Destroying).unwrap(); + assert_ok!(fully_unbond_permissioned(550)); + + // Then + assert_eq!( + SubPoolsStorage::::get(&1).unwrap().with_era, + unbonding_pools_with_era! { 0 + 3 => UnbondPool { points: 98, balance: 98 }} + ); + assert_eq!( + BondedPool::::get(1).unwrap(), + BondedPool { + id: 1, + inner: BondedPoolInner { + state: PoolState::Destroying, + points: 10, + member_counter: 3, + roles: DEFAULT_ROLES + } + } + ); + assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 2); + assert_eq!( + PoolMembers::::get(550).unwrap().unbonding_eras, + member_unbonding_eras!(0 + 3 => 550) + ); + assert_eq!(Balances::free_balance(&550), 550 + 550); + + // When + assert_ok!(fully_unbond_permissioned(10)); + + // Then + assert_eq!( + SubPoolsStorage::::get(1).unwrap().with_era, + unbonding_pools_with_era! { 0 + 3 => UnbondPool { points: 100, balance: 100 }} + ); + assert_eq!( + BondedPool::::get(1).unwrap(), + BondedPool { + id: 1, + inner: BondedPoolInner { + state: PoolState::Destroying, + points: 0, + member_counter: 3, + roles: DEFAULT_ROLES + } + } + ); + assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 0); + assert_eq!( + PoolMembers::::get(550).unwrap().unbonding_eras, + member_unbonding_eras!(0 + 3 => 550) + ); + assert_eq!(Balances::free_balance(&550), 550 + 550); + }); + } + + #[test] + fn unbond_merges_older_pools() { + ExtBuilder::default().with_check(1).build_and_execute(|| { + // Given + assert_eq!(StakingMock::bonding_duration(), 3); + SubPoolsStorage::::insert( + 1, + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + 0 + 3 => UnbondPool { balance: 10, points: 100 }, + 1 + 3 => UnbondPool { balance: 20, points: 20 }, + 2 + 3 => UnbondPool { balance: 101, points: 101} + }, + }, + ); + unsafe_set_state(1, PoolState::Destroying).unwrap(); + + // When + let current_era = 1 + TotalUnbondingPools::::get(); + CurrentEra::set(current_era); + + assert_ok!(fully_unbond_permissioned(10)); + + // Then + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: UnbondPool { balance: 10 + 20, points: 100 + 20 }, + with_era: unbonding_pools_with_era! { + 2 + 3 => UnbondPool { balance: 101, points: 101}, + current_era + 3 => UnbondPool { balance: 10, points: 10 }, + }, + }, + ) + }); + } + + #[test] + fn unbond_kick_works() { + // Kick: the pool is blocked and the caller is either the root or state-toggler. + ExtBuilder::default() + .add_members(vec![(100, 100), (200, 200)]) + .build_and_execute(|| { + // Given + unsafe_set_state(1, PoolState::Blocked).unwrap(); + let bonded_pool = BondedPool::::get(1).unwrap(); + assert_eq!(bonded_pool.roles.root, 900); + assert_eq!(bonded_pool.roles.nominator, 901); + assert_eq!(bonded_pool.roles.state_toggler, 902); + + // When the nominator trys to kick, then its a noop + assert_noop!( + Pools::fully_unbond(Origin::signed(901), 100), + Error::::NotKickerOrDestroying + ); + + // When the root kicks then its ok + assert_ok!(Pools::fully_unbond(Origin::signed(900), 100)); + + // When the state toggler kicks then its ok + assert_ok!(Pools::fully_unbond(Origin::signed(902), 200)); + + assert_eq!( + BondedPool::::get(1).unwrap(), + BondedPool { + id: 1, + inner: BondedPoolInner { + roles: DEFAULT_ROLES, + state: PoolState::Blocked, + points: 10, // Only 10 points because 200 + 100 was unbonded + member_counter: 3, + } + } + ); + assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 10); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + 0 + 3 => UnbondPool { points: 100 + 200, balance: 100 + 200 } + }, + } + ); + assert_eq!( + UNBONDING_BALANCE_MAP + .with(|m| *m.borrow_mut().get(&default_bonded_account()).unwrap()), + 100 + 200 + ); + }); + } + + #[test] + fn unbond_permissionless_works() { + // Scenarios where non-admin accounts can unbond others + ExtBuilder::default().add_members(vec![(100, 100)]).build_and_execute(|| { + // Given the pool is blocked + unsafe_set_state(1, PoolState::Blocked).unwrap(); + + // A permissionless unbond attempt errors + assert_noop!( + Pools::fully_unbond(Origin::signed(420), 100), + Error::::NotKickerOrDestroying + ); + + // Given the pool is destroying + unsafe_set_state(1, PoolState::Destroying).unwrap(); + + // The depositor cannot be fully unbonded until they are the last member + assert_noop!( + Pools::fully_unbond(Origin::signed(10), 10), + Error::::NotOnlyPoolMember + ); + + // Any account can unbond a member that is not the depositor + assert_ok!(Pools::fully_unbond(Origin::signed(420), 100)); + + // Given the pool is blocked + unsafe_set_state(1, PoolState::Blocked).unwrap(); + + // The depositor cannot be unbonded + assert_noop!( + Pools::fully_unbond(Origin::signed(420), 10), + Error::::DoesNotHavePermission + ); + + // Given the pools is destroying + unsafe_set_state(1, PoolState::Destroying).unwrap(); + + // The depositor can be unbonded by anyone. + assert_ok!(Pools::fully_unbond(Origin::signed(420), 10)); + + assert_eq!(BondedPools::::get(1).unwrap().points, 0); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + 0 + 3 => UnbondPool { points: 110, balance: 110 } + } + } + ); + assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 0); + assert_eq!( + UNBONDING_BALANCE_MAP + .with(|m| *m.borrow_mut().get(&default_bonded_account()).unwrap()), + 110 + ); + }); + } + + #[test] + #[cfg_attr(debug_assertions, should_panic(expected = "Defensive failure has been triggered!"))] + #[cfg_attr(not(debug_assertions), should_panic)] + fn unbond_errors_correctly() { + ExtBuilder::default().build_and_execute(|| { + assert_noop!( + Pools::fully_unbond(Origin::signed(11), 11), + Error::::PoolMemberNotFound + ); + + // Add the member + let member = PoolMember { pool_id: 2, points: 10, ..Default::default() }; + PoolMembers::::insert(11, member); + + let _ = Pools::fully_unbond(Origin::signed(11), 11); + }); + } + + #[test] + #[cfg_attr(debug_assertions, should_panic(expected = "Defensive failure has been triggered!"))] + #[cfg_attr(not(debug_assertions), should_panic)] + fn unbond_panics_when_reward_pool_not_found() { + ExtBuilder::default().build_and_execute(|| { + let member = PoolMember { pool_id: 2, points: 10, ..Default::default() }; + PoolMembers::::insert(11, member); + BondedPool:: { + id: 1, + inner: BondedPoolInner { + state: PoolState::Open, + points: 10, + member_counter: 1, + roles: DEFAULT_ROLES, + }, + } + .put(); + + let _ = Pools::fully_unbond(Origin::signed(11), 11); + }); + } + + #[test] + fn partial_unbond_era_tracking() { + ExtBuilder::default().build_and_execute(|| { + // given + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 10); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 0); + assert_eq!(PoolMembers::::get(10).unwrap().pool_id, 1); + assert_eq!( + PoolMembers::::get(10).unwrap().unbonding_eras, + member_unbonding_eras!() + ); + assert_eq!(BondedPool::::get(1).unwrap().points, 10); + assert!(SubPoolsStorage::::get(1).is_none()); + assert_eq!(CurrentEra::get(), 0); + assert_eq!(BondingDuration::get(), 3); + + // so the depositor can leave, just keeps the test simpler. + unsafe_set_state(1, PoolState::Destroying).unwrap(); + + // when: casual unbond + assert_ok!(Pools::unbond(Origin::signed(10), 10, 1)); + + // then + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 9); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 1); + assert_eq!(BondedPool::::get(1).unwrap().points, 9); + assert_eq!( + PoolMembers::::get(10).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 1) + ); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + 3 => UnbondPool { points: 1, balance: 1 } + } + } + ); + + // when: casual further unbond, same era. + assert_ok!(Pools::unbond(Origin::signed(10), 10, 5)); + + // then + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 4); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 6); + assert_eq!(BondedPool::::get(1).unwrap().points, 4); + assert_eq!( + PoolMembers::::get(10).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 6) + ); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + 3 => UnbondPool { points: 6, balance: 6 } + } + } + ); + + // when: casual further unbond, next era. + CurrentEra::set(1); + assert_ok!(Pools::unbond(Origin::signed(10), 10, 1)); + + // then + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 3); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 7); + assert_eq!(BondedPool::::get(1).unwrap().points, 3); + assert_eq!( + PoolMembers::::get(10).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 6, 4 => 1) + ); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + 3 => UnbondPool { points: 6, balance: 6 }, + 4 => UnbondPool { points: 1, balance: 1 } + } + } + ); + + // when: unbonding more than our active: error + assert_noop!( + Pools::unbond(Origin::signed(10), 10, 5), + Error::::NotEnoughPointsToUnbond + ); + // instead: + assert_ok!(Pools::unbond(Origin::signed(10), 10, 3)); + + // then + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 0); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 10); + assert_eq!(BondedPool::::get(1).unwrap().points, 0); + assert_eq!( + PoolMembers::::get(10).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 6, 4 => 4) + ); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + 3 => UnbondPool { points: 6, balance: 6 }, + 4 => UnbondPool { points: 4, balance: 4 } + } + } + ); + }); + } + + #[test] + fn partial_unbond_max_chunks() { + ExtBuilder::default().ed(1).build_and_execute(|| { + // so the depositor can leave, just keeps the test simpler. + unsafe_set_state(1, PoolState::Destroying).unwrap(); + MaxUnbonding::set(2); + + // given + assert_ok!(Pools::unbond(Origin::signed(10), 10, 2)); + CurrentEra::set(1); + assert_ok!(Pools::unbond(Origin::signed(10), 10, 3)); + assert_eq!( + PoolMembers::::get(10).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 2, 4 => 3) + ); + + // when + CurrentEra::set(2); + assert_noop!( + Pools::unbond(Origin::signed(10), 10, 4), + Error::::MaxUnbondingLimit + ); + + // when + MaxUnbonding::set(3); + assert_ok!(Pools::unbond(Origin::signed(10), 10, 1)); + assert_eq!( + PoolMembers::::get(10).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 2, 4 => 3, 5 => 1) + ); + }) + } + + // depositor can unbond inly up to `MinCreateBond`. + #[test] + fn depositor_permissioned_partial_unbond() { + ExtBuilder::default().ed(1).add_members(vec![(100, 100)]).build_and_execute(|| { + // given + assert_eq!(MinCreateBond::::get(), 2); + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 10); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 0); + + // can unbond a bit.. + assert_ok!(Pools::unbond(Origin::signed(10), 10, 3)); + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 7); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 3); + + // but not less than 2 + assert_noop!( + Pools::unbond(Origin::signed(10), 10, 6), + Error::::NotOnlyPoolMember + ); + }); + } + + // same as above, but the pool is slashed and therefore the depositor cannot partially unbond. + #[test] + fn depositor_permissioned_partial_unbond_slashed() { + ExtBuilder::default().ed(1).add_members(vec![(100, 100)]).build_and_execute(|| { + // given + assert_eq!(MinCreateBond::::get(), 2); + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 10); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 0); + + // slash the default pool + StakingMock::set_bonded_balance(Pools::create_bonded_account(1), 5); + + // cannot unbond even 7, because the value of shares is now less. + assert_noop!( + Pools::unbond(Origin::signed(10), 10, 7), + Error::::NotOnlyPoolMember + ); + }); + } +} + +mod pool_withdraw_unbonded { + use super::*; + + #[test] + fn pool_withdraw_unbonded_works() { + ExtBuilder::default().build_and_execute(|| { + // Given 10 unbond'ed directly against the pool account + assert_ok!(StakingMock::unbond(default_bonded_account(), 5)); + // and the pool account only has 10 balance + assert_eq!(StakingMock::active_stake(&default_bonded_account()), Some(5)); + assert_eq!(StakingMock::total_stake(&default_bonded_account()), Some(10)); + assert_eq!(Balances::free_balance(&default_bonded_account()), 10); + + // When + assert_ok!(Pools::pool_withdraw_unbonded(Origin::signed(10), 1, 0)); + + // Then there unbonding balance is no longer locked + assert_eq!(StakingMock::active_stake(&default_bonded_account()), Some(5)); + assert_eq!(StakingMock::total_stake(&default_bonded_account()), Some(5)); + assert_eq!(Balances::free_balance(&default_bonded_account()), 10); + }); + } +} + +mod withdraw_unbonded { + use super::*; + use frame_support::bounded_btree_map; + + #[test] + fn withdraw_unbonded_works_against_slashed_no_era_sub_pool() { + ExtBuilder::default() + .add_members(vec![(40, 40), (550, 550)]) + .build_and_execute(|| { + // Given + assert_eq!(StakingMock::bonding_duration(), 3); + assert_ok!(Pools::fully_unbond(Origin::signed(550), 550)); + assert_ok!(Pools::fully_unbond(Origin::signed(40), 40)); + assert_eq!(Balances::free_balance(&default_bonded_account()), 600); + + let mut current_era = 1; + CurrentEra::set(current_era); + // In a new era, unbond the depositor + unsafe_set_state(1, PoolState::Destroying).unwrap(); + assert_ok!(Pools::fully_unbond(Origin::signed(10), 10)); + + let mut sub_pools = SubPoolsStorage::::get(1).unwrap(); + let unbond_pool = sub_pools.with_era.get_mut(&(current_era + 3)).unwrap(); + // Sanity check + assert_eq!(*unbond_pool, UnbondPool { points: 10, balance: 10 }); + + // Simulate a slash to the pool with_era(current_era), decreasing the balance by + // half + unbond_pool.balance = 5; + SubPoolsStorage::::insert(1, sub_pools); + // Update the equivalent of the unbonding chunks for the `StakingMock` + UNBONDING_BALANCE_MAP + .with(|m| *m.borrow_mut().get_mut(&default_bonded_account()).unwrap() -= 5); + Balances::make_free_balance_be(&default_bonded_account(), 595); + + // Advance the current_era to ensure all `with_era` pools will be merged into + // `no_era` pool + current_era += TotalUnbondingPools::::get(); + CurrentEra::set(current_era); + + // Simulate some other call to unbond that would merge `with_era` pools into + // `no_era` + let sub_pools = + SubPoolsStorage::::get(1).unwrap().maybe_merge_pools(current_era + 3); + SubPoolsStorage::::insert(1, sub_pools); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: UnbondPool { points: 550 + 40 + 10, balance: 550 + 40 + 5 }, + with_era: Default::default() + } + ); + + // When + assert_ok!(Pools::withdraw_unbonded(Origin::signed(550), 550, 0)); + + // Then + assert_eq!( + SubPoolsStorage::::get(1).unwrap().no_era, + UnbondPool { points: 40 + 10, balance: 40 + 5 + 5 } + ); + assert_eq!(Balances::free_balance(&550), 550 + 545); + assert_eq!(Balances::free_balance(&default_bonded_account()), 50); + assert!(!PoolMembers::::contains_key(550)); + + // When + assert_ok!(Pools::withdraw_unbonded(Origin::signed(40), 40, 0)); + + // Then + assert_eq!( + SubPoolsStorage::::get(1).unwrap().no_era, + UnbondPool { points: 10, balance: 10 } + ); + assert_eq!(Balances::free_balance(&40), 40 + 40); + assert_eq!(Balances::free_balance(&default_bonded_account()), 50 - 40); + assert!(!PoolMembers::::contains_key(40)); + + // When + assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 0)); + + // Then + assert_eq!(Balances::free_balance(&10), 10 + 10); + assert_eq!(Balances::free_balance(&default_bonded_account()), 0); + assert!(!PoolMembers::::contains_key(10)); + // Pools are removed from storage because the depositor left + assert!(!SubPoolsStorage::::contains_key(1),); + assert!(!RewardPools::::contains_key(1),); + assert!(!BondedPools::::contains_key(1),); + }); + } + + // This test also documents the case when the pools free balance goes below ED before all + // members have unbonded. + #[test] + fn withdraw_unbonded_works_against_slashed_with_era_sub_pools() { + ExtBuilder::default() + .add_members(vec![(40, 40), (550, 550)]) + .build_and_execute(|| { + // Given + StakingMock::set_bonded_balance(default_bonded_account(), 100); // slash bonded balance + Balances::make_free_balance_be(&default_bonded_account(), 100); + assert_eq!(StakingMock::total_stake(&default_bonded_account()), Some(100)); + + assert_ok!(Pools::fully_unbond(Origin::signed(40), 40)); + assert_ok!(Pools::fully_unbond(Origin::signed(550), 550)); + unsafe_set_state(1, PoolState::Destroying).unwrap(); + assert_ok!(Pools::fully_unbond(Origin::signed(10), 10)); + + SubPoolsStorage::::insert( + 1, + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { 0 + 3 => UnbondPool { points: 600, balance: 100 }}, + }, + ); + CurrentEra::set(StakingMock::bonding_duration()); + + // When + assert_ok!(Pools::withdraw_unbonded(Origin::signed(40), 40, 0)); + + // Then + assert_eq!( + SubPoolsStorage::::get(&1).unwrap().with_era, + unbonding_pools_with_era! { 0 + 3 => UnbondPool { points: 560, balance: 94 }} + ); + assert_eq!(Balances::free_balance(&40), 40 + 6); + assert_eq!(Balances::free_balance(&default_bonded_account()), 94); + assert!(!PoolMembers::::contains_key(40)); + + // When + assert_ok!(Pools::withdraw_unbonded(Origin::signed(550), 550, 0)); + + // Then + assert_eq!( + SubPoolsStorage::::get(&1).unwrap().with_era, + unbonding_pools_with_era! { 0 + 3 => UnbondPool { points: 10, balance: 2 }} + ); + assert_eq!(Balances::free_balance(&550), 550 + 92); + // The account was dusted because it went below ED(5) + assert_eq!(Balances::free_balance(&default_bonded_account()), 0); + assert!(!PoolMembers::::contains_key(550)); + + // When + assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 0)); + + // Then + assert_eq!(Balances::free_balance(&10), 10 + 0); + assert_eq!(Balances::free_balance(&default_bonded_account()), 0); + assert!(!PoolMembers::::contains_key(10)); + // Pools are removed from storage because the depositor left + assert!(!SubPoolsStorage::::contains_key(1),); + assert!(!RewardPools::::contains_key(1),); + assert!(!BondedPools::::contains_key(1),); + }); + } + + #[test] + fn withdraw_unbonded_handles_faulty_sub_pool_accounting() { + ExtBuilder::default().build_and_execute(|| { + // Given + assert_eq!(Balances::minimum_balance(), 5); + assert_eq!(Balances::free_balance(&10), 5); + assert_eq!(Balances::free_balance(&default_bonded_account()), 10); + unsafe_set_state(1, PoolState::Destroying).unwrap(); + assert_ok!(Pools::fully_unbond(Origin::signed(10), 10)); + + // Simulate a slash that is not accounted for in the sub pools. + Balances::make_free_balance_be(&default_bonded_account(), 5); + assert_eq!( + SubPoolsStorage::::get(1).unwrap().with_era, + //------------------------------balance decrease is not account for + unbonding_pools_with_era! { 0 + 3 => UnbondPool { points: 10, balance: 10 } } + ); + + CurrentEra::set(0 + 3); + + // When + assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 0)); + + // Then + assert_eq!(Balances::free_balance(10), 10 + 5); + assert_eq!(Balances::free_balance(&default_bonded_account()), 0); + }); + } + + #[test] + fn withdraw_unbonded_errors_correctly() { + ExtBuilder::default().with_check(0).build_and_execute(|| { + // Insert the sub-pool + let sub_pools = SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { 0 + 3 => UnbondPool { points: 10, balance: 10 }}, + }; + SubPoolsStorage::::insert(1, sub_pools.clone()); + + assert_noop!( + Pools::withdraw_unbonded(Origin::signed(11), 11, 0), + Error::::PoolMemberNotFound + ); + + let mut member = PoolMember { pool_id: 1, points: 10, ..Default::default() }; + PoolMembers::::insert(11, member.clone()); + + // Simulate calling `unbond` + member.unbonding_eras = member_unbonding_eras!(3 + 0 => 10); + PoolMembers::::insert(11, member.clone()); + + // We are still in the bonding duration + assert_noop!( + Pools::withdraw_unbonded(Origin::signed(11), 11, 0), + Error::::CannotWithdrawAny + ); + + // If we error the member does not get removed + assert_eq!(PoolMembers::::get(&11), Some(member)); + // and the sub pools do not get updated. + assert_eq!(SubPoolsStorage::::get(1).unwrap(), sub_pools) + }); + } + + #[test] + fn withdraw_unbonded_kick() { + ExtBuilder::default() + .add_members(vec![(100, 100), (200, 200)]) + .build_and_execute(|| { + // Given + assert_ok!(Pools::fully_unbond(Origin::signed(100), 100)); + assert_ok!(Pools::fully_unbond(Origin::signed(200), 200)); + assert_eq!( + BondedPool::::get(1).unwrap(), + BondedPool { + id: 1, + inner: BondedPoolInner { + points: 10, + state: PoolState::Open, + member_counter: 3, + roles: DEFAULT_ROLES + } + } + ); + CurrentEra::set(StakingMock::bonding_duration()); + + // Cannot kick when pool is open + assert_noop!( + Pools::withdraw_unbonded(Origin::signed(902), 100, 0), + Error::::NotKickerOrDestroying + ); + + // Given + unsafe_set_state(1, PoolState::Blocked).unwrap(); + + // Cannot kick as a nominator + assert_noop!( + Pools::withdraw_unbonded(Origin::signed(901), 100, 0), + Error::::NotKickerOrDestroying + ); + + // Can kick as root + assert_ok!(Pools::withdraw_unbonded(Origin::signed(900), 100, 0)); + + // Can kick as state toggler + assert_ok!(Pools::withdraw_unbonded(Origin::signed(900), 200, 0)); + + assert_eq!(Balances::free_balance(100), 100 + 100); + assert_eq!(Balances::free_balance(200), 200 + 200); + assert!(!PoolMembers::::contains_key(100)); + assert!(!PoolMembers::::contains_key(200)); + assert_eq!(SubPoolsStorage::::get(1).unwrap(), Default::default()); + }); + } + + #[test] + fn withdraw_unbonded_destroying_permissionless() { + ExtBuilder::default().add_members(vec![(100, 100)]).build_and_execute(|| { + // Given + assert_ok!(Pools::fully_unbond(Origin::signed(100), 100)); + assert_eq!( + BondedPool::::get(1).unwrap(), + BondedPool { + id: 1, + inner: BondedPoolInner { + points: 10, + state: PoolState::Open, + member_counter: 2, + roles: DEFAULT_ROLES, + } + } + ); + CurrentEra::set(StakingMock::bonding_duration()); + assert_eq!(Balances::free_balance(100), 100); + + // Cannot permissionlessly withdraw + assert_noop!( + Pools::fully_unbond(Origin::signed(420), 100), + Error::::NotKickerOrDestroying + ); + + // Given + unsafe_set_state(1, PoolState::Destroying).unwrap(); + + // Can permissionlesly withdraw a member that is not the depositor + assert_ok!(Pools::withdraw_unbonded(Origin::signed(420), 100, 0)); + + assert_eq!(SubPoolsStorage::::get(1).unwrap(), Default::default(),); + assert_eq!(Balances::free_balance(100), 100 + 100); + assert!(!PoolMembers::::contains_key(100)); + }); + } + + #[test] + fn withdraw_unbonded_depositor_with_era_pool() { + ExtBuilder::default() + .add_members(vec![(100, 100), (200, 200)]) + .build_and_execute(|| { + // Given + assert_ok!(Pools::fully_unbond(Origin::signed(100), 100)); + + let mut current_era = 1; + CurrentEra::set(current_era); + + assert_ok!(Pools::fully_unbond(Origin::signed(200), 200)); + unsafe_set_state(1, PoolState::Destroying).unwrap(); + assert_ok!(Pools::fully_unbond(Origin::signed(10), 10)); + + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + 0 + 3 => UnbondPool { points: 100, balance: 100}, + 1 + 3 => UnbondPool { points: 200 + 10, balance: 200 + 10 } + } + } + ); + + // Skip ahead eras to where its valid for the members to withdraw + current_era += StakingMock::bonding_duration(); + CurrentEra::set(current_era); + + // Cannot withdraw the depositor if their is a member in another `with_era` pool. + assert_noop!( + Pools::withdraw_unbonded(Origin::signed(420), 10, 0), + Error::::NotOnlyPoolMember + ); + + // Given + assert_ok!(Pools::withdraw_unbonded(Origin::signed(420), 100, 0)); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + // Note that era 0+3 unbond pool is destroyed because points went to 0 + 1 + 3 => UnbondPool { points: 200 + 10, balance: 200 + 10 } + } + } + ); + + // Cannot withdraw the depositor if their is a member in another `with_era` pool. + assert_noop!( + Pools::withdraw_unbonded(Origin::signed(420), 10, 0), + Error::::NotOnlyPoolMember + ); + + // Given + assert_ok!(Pools::withdraw_unbonded(Origin::signed(420), 200, 0)); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + 1 + 3 => UnbondPool { points: 10, balance: 10 } + } + } + ); + + // The depositor can withdraw + assert_ok!(Pools::withdraw_unbonded(Origin::signed(420), 10, 0)); + assert!(!PoolMembers::::contains_key(10)); + assert_eq!(Balances::free_balance(10), 10 + 10); + // Pools are removed from storage because the depositor left + assert!(!SubPoolsStorage::::contains_key(1)); + assert!(!RewardPools::::contains_key(1)); + assert!(!BondedPools::::contains_key(1)); + }); + } + + #[test] + fn withdraw_unbonded_depositor_no_era_pool() { + ExtBuilder::default().add_members(vec![(100, 100)]).build_and_execute(|| { + // Given + assert_ok!(Pools::fully_unbond(Origin::signed(100), 100)); + unsafe_set_state(1, PoolState::Destroying).unwrap(); + assert_ok!(Pools::fully_unbond(Origin::signed(10), 10)); + // Skip ahead to an era where the `with_era` pools can get merged into the `no_era` + // pool. + let current_era = TotalUnbondingPools::::get(); + CurrentEra::set(current_era); + + // Simulate some other withdraw that caused the pool to merge + let sub_pools = + SubPoolsStorage::::get(1).unwrap().maybe_merge_pools(current_era + 3); + SubPoolsStorage::::insert(1, sub_pools); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: UnbondPool { points: 100 + 10, balance: 100 + 10 }, + with_era: Default::default(), + } + ); + + // Cannot withdraw depositor with another member in the `no_era` pool + assert_noop!( + Pools::withdraw_unbonded(Origin::signed(420), 10, 0), + Error::::NotOnlyPoolMember + ); + + // Given + assert_ok!(Pools::withdraw_unbonded(Origin::signed(420), 100, 0)); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: UnbondPool { points: 10, balance: 10 }, + with_era: Default::default(), + } + ); + + // The depositor can withdraw + assert_ok!(Pools::withdraw_unbonded(Origin::signed(420), 10, 0)); + assert!(!PoolMembers::::contains_key(10)); + assert_eq!(Balances::free_balance(10), 10 + 10); + // Pools are removed from storage because the depositor left + assert!(!SubPoolsStorage::::contains_key(1)); + assert!(!RewardPools::::contains_key(1)); + assert!(!BondedPools::::contains_key(1)); + }); + } + + #[test] + fn partial_withdraw_unbonded_depositor() { + ExtBuilder::default().ed(1).build_and_execute(|| { + // so the depositor can leave, just keeps the test simpler. + unsafe_set_state(1, PoolState::Destroying).unwrap(); + + // given + assert_ok!(Pools::unbond(Origin::signed(10), 10, 6)); + CurrentEra::set(1); + assert_ok!(Pools::unbond(Origin::signed(10), 10, 1)); + assert_eq!( + PoolMembers::::get(10).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 6, 4 => 1) + ); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + 3 => UnbondPool { points: 6, balance: 6 }, + 4 => UnbondPool { points: 1, balance: 1 } + } + } + ); + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 3); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 7); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::PaidOut { member: 10, pool_id: 1, payout: 0 }, + Event::Unbonded { member: 10, pool_id: 1, amount: 6 }, + Event::PaidOut { member: 10, pool_id: 1, payout: 0 }, + Event::Unbonded { member: 10, pool_id: 1, amount: 1 } + ] + ); + + // when + CurrentEra::set(2); + assert_noop!( + Pools::withdraw_unbonded(Origin::signed(10), 10, 0), + Error::::CannotWithdrawAny + ); + + // when + CurrentEra::set(3); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 0)); + + // then + assert_eq!( + PoolMembers::::get(10).unwrap().unbonding_eras, + member_unbonding_eras!(4 => 1) + ); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + 4 => UnbondPool { points: 1, balance: 1 } + } + } + ); + assert_eq!( + pool_events_since_last_call(), + vec![Event::Withdrawn { member: 10, pool_id: 1, amount: 6 }] + ); + + // when + CurrentEra::set(4); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 0)); + + // then + assert_eq!( + PoolMembers::::get(10).unwrap().unbonding_eras, + member_unbonding_eras!() + ); + assert_eq!(SubPoolsStorage::::get(1).unwrap(), Default::default()); + assert_eq!( + pool_events_since_last_call(), + vec![Event::Withdrawn { member: 10, pool_id: 1, amount: 1 },] + ); + + // when repeating: + assert_noop!( + Pools::withdraw_unbonded(Origin::signed(10), 10, 0), + Error::::CannotWithdrawAny + ); + }); + } + + #[test] + fn partial_withdraw_unbonded_non_depositor() { + ExtBuilder::default().add_members(vec![(11, 10)]).build_and_execute(|| { + // given + assert_ok!(Pools::unbond(Origin::signed(11), 11, 6)); + CurrentEra::set(1); + assert_ok!(Pools::unbond(Origin::signed(11), 11, 1)); + assert_eq!( + PoolMembers::::get(11).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 6, 4 => 1) + ); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + 3 => UnbondPool { points: 6, balance: 6 }, + 4 => UnbondPool { points: 1, balance: 1 } + } + } + ); + assert_eq!(PoolMembers::::get(11).unwrap().active_points(), 3); + assert_eq!(PoolMembers::::get(11).unwrap().unbonding_points(), 7); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 11, pool_id: 1, bonded: 10, joined: true }, + Event::PaidOut { member: 11, pool_id: 1, payout: 0 }, + Event::Unbonded { member: 11, pool_id: 1, amount: 6 }, + Event::PaidOut { member: 11, pool_id: 1, payout: 0 }, + Event::Unbonded { member: 11, pool_id: 1, amount: 1 } + ] + ); + + // when + CurrentEra::set(2); + assert_noop!( + Pools::withdraw_unbonded(Origin::signed(11), 11, 0), + Error::::CannotWithdrawAny + ); + + // when + CurrentEra::set(3); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(11), 11, 0)); + + // then + assert_eq!( + PoolMembers::::get(11).unwrap().unbonding_eras, + member_unbonding_eras!(4 => 1) + ); + assert_eq!( + SubPoolsStorage::::get(1).unwrap(), + SubPools { + no_era: Default::default(), + with_era: unbonding_pools_with_era! { + 4 => UnbondPool { points: 1, balance: 1 } + } + } + ); + assert_eq!( + pool_events_since_last_call(), + vec![Event::Withdrawn { member: 11, pool_id: 1, amount: 6 }] + ); + + // when + CurrentEra::set(4); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(11), 11, 0)); + + // then + assert_eq!( + PoolMembers::::get(11).unwrap().unbonding_eras, + member_unbonding_eras!() + ); + assert_eq!(SubPoolsStorage::::get(1).unwrap(), Default::default()); + assert_eq!( + pool_events_since_last_call(), + vec![Event::Withdrawn { member: 11, pool_id: 1, amount: 1 }] + ); + + // when repeating: + assert_noop!( + Pools::withdraw_unbonded(Origin::signed(11), 11, 0), + Error::::CannotWithdrawAny + ); + }); + } +} + +mod create { + use super::*; + + #[test] + fn create_works() { + ExtBuilder::default().build_and_execute(|| { + // next pool id is 2. + let next_pool_stash = Pools::create_bonded_account(2); + let ed = Balances::minimum_balance(); + + assert!(!BondedPools::::contains_key(2)); + assert!(!RewardPools::::contains_key(2)); + assert!(!PoolMembers::::contains_key(11)); + assert_eq!(StakingMock::active_stake(&next_pool_stash), None); + + Balances::make_free_balance_be(&11, StakingMock::minimum_bond() + ed); + assert_ok!(Pools::create( + Origin::signed(11), + StakingMock::minimum_bond(), + 123, + 456, + 789 + )); + + assert_eq!(Balances::free_balance(&11), 0); + assert_eq!( + PoolMembers::::get(11).unwrap(), + PoolMember { + pool_id: 2, + points: StakingMock::minimum_bond(), + ..Default::default() + } + ); + assert_eq!( + BondedPool::::get(2).unwrap(), + BondedPool { + id: 2, + inner: BondedPoolInner { + points: StakingMock::minimum_bond(), + member_counter: 1, + state: PoolState::Open, + roles: PoolRoles { + depositor: 11, + root: 123, + nominator: 456, + state_toggler: 789 + } + } + } + ); + assert_eq!( + StakingMock::active_stake(&next_pool_stash).unwrap(), + StakingMock::minimum_bond() + ); + assert_eq!( + RewardPools::::get(2).unwrap(), + RewardPool { + balance: Zero::zero(), + points: U256::zero(), + total_earnings: Zero::zero(), + } + ); + }); + } + + #[test] + fn create_errors_correctly() { + ExtBuilder::default().with_check(0).build_and_execute(|| { + assert_noop!( + Pools::create(Origin::signed(10), 420, 123, 456, 789), + Error::::AccountBelongsToOtherPool + ); + + // Given + assert_eq!(MinCreateBond::::get(), 2); + assert_eq!(StakingMock::minimum_bond(), 10); + + // Then + assert_noop!( + Pools::create(Origin::signed(11), 9, 123, 456, 789), + Error::::MinimumBondNotMet + ); + + // Given + MinCreateBond::::put(20); + + // Then + assert_noop!( + Pools::create(Origin::signed(11), 19, 123, 456, 789), + Error::::MinimumBondNotMet + ); + + // Given + BondedPool:: { + id: 2, + inner: BondedPoolInner { + state: PoolState::Open, + points: 10, + member_counter: 1, + roles: DEFAULT_ROLES, + }, + } + .put(); + assert_eq!(MaxPools::::get(), Some(2)); + assert_eq!(BondedPools::::count(), 2); + + // Then + assert_noop!( + Pools::create(Origin::signed(11), 20, 123, 456, 789), + Error::::MaxPools + ); + + // Given + assert_eq!(PoolMembers::::count(), 1); + MaxPools::::put(3); + MaxPoolMembers::::put(1); + Balances::make_free_balance_be(&11, 5 + 20); + + // Then + assert_noop!( + Pools::create(Origin::signed(11), 20, 11, 11, 11), + Error::::MaxPoolMembers + ); + }); + } +} + +mod nominate { + use super::*; + + #[test] + fn nominate_works() { + ExtBuilder::default().build_and_execute(|| { + // Depositor can't nominate + assert_noop!( + Pools::nominate(Origin::signed(10), 1, vec![21]), + Error::::NotNominator + ); + + // State toggler can't nominate + assert_noop!( + Pools::nominate(Origin::signed(902), 1, vec![21]), + Error::::NotNominator + ); + + // Root can nominate + assert_ok!(Pools::nominate(Origin::signed(900), 1, vec![21])); + assert_eq!(Nominations::get(), vec![21]); + + // Nominator can nominate + assert_ok!(Pools::nominate(Origin::signed(901), 1, vec![31])); + assert_eq!(Nominations::get(), vec![31]); + + // Can't nominate for a pool that doesn't exist + assert_noop!( + Pools::nominate(Origin::signed(902), 123, vec![21]), + Error::::PoolNotFound + ); + }); + } +} + +mod set_state { + use super::*; + + #[test] + fn set_state_works() { + ExtBuilder::default().build_and_execute(|| { + // Given + assert_ok!(BondedPool::::get(1).unwrap().ok_to_be_open(0)); + + // Only the root and state toggler can change the state when the pool is ok to be open. + assert_noop!( + Pools::set_state(Origin::signed(10), 1, PoolState::Blocked), + Error::::CanNotChangeState + ); + assert_noop!( + Pools::set_state(Origin::signed(901), 1, PoolState::Blocked), + Error::::CanNotChangeState + ); + + // Root can change state + assert_ok!(Pools::set_state(Origin::signed(900), 1, PoolState::Blocked)); + assert_eq!(BondedPool::::get(1).unwrap().state, PoolState::Blocked); + + // State toggler can change state + assert_ok!(Pools::set_state(Origin::signed(902), 1, PoolState::Destroying)); + assert_eq!(BondedPool::::get(1).unwrap().state, PoolState::Destroying); + + // If the pool is destroying, then no one can set state + assert_noop!( + Pools::set_state(Origin::signed(900), 1, PoolState::Blocked), + Error::::CanNotChangeState + ); + assert_noop!( + Pools::set_state(Origin::signed(902), 1, PoolState::Blocked), + Error::::CanNotChangeState + ); + + // If the pool is not ok to be open, then anyone can set it to destroying + + // Given + unsafe_set_state(1, PoolState::Open).unwrap(); + let mut bonded_pool = BondedPool::::get(1).unwrap(); + bonded_pool.points = 100; + bonded_pool.put(); + // When + assert_ok!(Pools::set_state(Origin::signed(11), 1, PoolState::Destroying)); + // Then + assert_eq!(BondedPool::::get(1).unwrap().state, PoolState::Destroying); + + // Given + Balances::make_free_balance_be(&default_bonded_account(), Balance::max_value() / 10); + unsafe_set_state(1, PoolState::Open).unwrap(); + // When + assert_ok!(Pools::set_state(Origin::signed(11), 1, PoolState::Destroying)); + // Then + assert_eq!(BondedPool::::get(1).unwrap().state, PoolState::Destroying); + + // If the pool is not ok to be open, it cannot be permissionleslly set to a state that + // isn't destroying + unsafe_set_state(1, PoolState::Open).unwrap(); + assert_noop!( + Pools::set_state(Origin::signed(11), 1, PoolState::Blocked), + Error::::CanNotChangeState + ); + }); + } +} + +mod set_metadata { + use super::*; + + #[test] + fn set_metadata_works() { + ExtBuilder::default().build_and_execute(|| { + // Root can set metadata + assert_ok!(Pools::set_metadata(Origin::signed(900), 1, vec![1, 1])); + assert_eq!(Metadata::::get(1), vec![1, 1]); + + // State toggler can set metadata + assert_ok!(Pools::set_metadata(Origin::signed(902), 1, vec![2, 2])); + assert_eq!(Metadata::::get(1), vec![2, 2]); + + // Depositor can't set metadata + assert_noop!( + Pools::set_metadata(Origin::signed(10), 1, vec![3, 3]), + Error::::DoesNotHavePermission + ); + + // Nominator can't set metadata + assert_noop!( + Pools::set_metadata(Origin::signed(901), 1, vec![3, 3]), + Error::::DoesNotHavePermission + ); + + // Metadata cannot be longer than `MaxMetadataLen` + assert_noop!( + Pools::set_metadata(Origin::signed(900), 1, vec![1, 1, 1]), + Error::::MetadataExceedsMaxLen + ); + }); + } +} + +mod set_configs { + use super::*; + + #[test] + fn set_configs_works() { + ExtBuilder::default().build_and_execute(|| { + // Setting works + assert_ok!(Pools::set_configs( + Origin::root(), + ConfigOp::Set(1 as Balance), + ConfigOp::Set(2 as Balance), + ConfigOp::Set(3u32), + ConfigOp::Set(4u32), + ConfigOp::Set(5u32), + )); + assert_eq!(MinJoinBond::::get(), 1); + assert_eq!(MinCreateBond::::get(), 2); + assert_eq!(MaxPools::::get(), Some(3)); + assert_eq!(MaxPoolMembers::::get(), Some(4)); + assert_eq!(MaxPoolMembersPerPool::::get(), Some(5)); + + // Noop does nothing + assert_storage_noop!(assert_ok!(Pools::set_configs( + Origin::root(), + ConfigOp::Noop, + ConfigOp::Noop, + ConfigOp::Noop, + ConfigOp::Noop, + ConfigOp::Noop, + ))); + + // Removing works + assert_ok!(Pools::set_configs( + Origin::root(), + ConfigOp::Remove, + ConfigOp::Remove, + ConfigOp::Remove, + ConfigOp::Remove, + ConfigOp::Remove + )); + assert_eq!(MinJoinBond::::get(), 0); + assert_eq!(MinCreateBond::::get(), 0); + assert_eq!(MaxPools::::get(), None); + assert_eq!(MaxPoolMembers::::get(), None); + assert_eq!(MaxPoolMembersPerPool::::get(), None); + }); + } +} + +mod bond_extra { + use super::*; + use crate::Event; + + #[test] + fn bond_extra_from_free_balance_creator() { + ExtBuilder::default().build_and_execute(|| { + // 10 is the owner and a member in pool 1, give them some more funds. + Balances::make_free_balance_be(&10, 100); + + // given + assert_eq!(PoolMembers::::get(10).unwrap().points, 10); + assert_eq!(BondedPools::::get(1).unwrap().points, 10); + assert_eq!(Balances::free_balance(10), 100); + + // when + assert_ok!(Pools::bond_extra(Origin::signed(10), BondExtra::FreeBalance(10))); + + // then + assert_eq!(Balances::free_balance(10), 90); + assert_eq!(PoolMembers::::get(10).unwrap().points, 20); + assert_eq!(BondedPools::::get(1).unwrap().points, 20); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: false } + ] + ); + + // when + assert_ok!(Pools::bond_extra(Origin::signed(10), BondExtra::FreeBalance(20))); + + // then + assert_eq!(Balances::free_balance(10), 70); + assert_eq!(PoolMembers::::get(10).unwrap().points, 40); + assert_eq!(BondedPools::::get(1).unwrap().points, 40); + + assert_eq!( + pool_events_since_last_call(), + vec![Event::Bonded { member: 10, pool_id: 1, bonded: 20, joined: false }] + ); + }) + } + + #[test] + fn bond_extra_from_rewards_creator() { + ExtBuilder::default().build_and_execute(|| { + // put some money in the reward account, all of which will belong to 10 as the only + // member of the pool. + Balances::make_free_balance_be(&default_reward_account(), 7); + // ... if which only 2 is claimable to make sure the reward account does not die. + let claimable_reward = 7 - ExistentialDeposit::get(); + + // given + assert_eq!(PoolMembers::::get(10).unwrap().points, 10); + assert_eq!(BondedPools::::get(1).unwrap().points, 10); + assert_eq!(Balances::free_balance(10), 5); + + // when + assert_ok!(Pools::bond_extra(Origin::signed(10), BondExtra::Rewards)); + + // then + assert_eq!(Balances::free_balance(10), 5); + assert_eq!(PoolMembers::::get(10).unwrap().points, 10 + claimable_reward); + assert_eq!(BondedPools::::get(1).unwrap().points, 10 + claimable_reward); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::PaidOut { member: 10, pool_id: 1, payout: claimable_reward }, + Event::Bonded { + member: 10, + pool_id: 1, + bonded: claimable_reward, + joined: false + } + ] + ); + }) + } + + #[test] + fn bond_extra_from_rewards_joiner() { + ExtBuilder::default().add_members(vec![(20, 20)]).build_and_execute(|| { + // put some money in the reward account, all of which will belong to 10 as the only + // member of the pool. + Balances::make_free_balance_be(&default_reward_account(), 8); + // ... if which only 3 is claimable to make sure the reward account does not die. + let claimable_reward = 8 - ExistentialDeposit::get(); + // NOTE: easier to read of we use 3, so let's use the number instead of variable. + assert_eq!(claimable_reward, 3, "test is correct if rewards are divisible by 3"); + + // given + assert_eq!(PoolMembers::::get(10).unwrap().points, 10); + assert_eq!(PoolMembers::::get(20).unwrap().points, 20); + assert_eq!(BondedPools::::get(1).unwrap().points, 30); + assert_eq!(Balances::free_balance(10), 5); + assert_eq!(Balances::free_balance(20), 20); + + // when + assert_ok!(Pools::bond_extra(Origin::signed(10), BondExtra::Rewards)); + + // then + assert_eq!(Balances::free_balance(10), 5); + // 10's share of the reward is 1/3, since they gave 10/30 of the total shares. + assert_eq!(PoolMembers::::get(10).unwrap().points, 10 + 1); + assert_eq!(BondedPools::::get(1).unwrap().points, 30 + 1); + + // when + assert_ok!(Pools::bond_extra(Origin::signed(20), BondExtra::Rewards)); + + // then + assert_eq!(Balances::free_balance(20), 20); + // 20's share of the rewards is the other 2/3 of the rewards, since they have 20/30 of + // the shares + assert_eq!(PoolMembers::::get(20).unwrap().points, 20 + 2); + assert_eq!(BondedPools::::get(1).unwrap().points, 30 + 3); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, + Event::PaidOut { member: 10, pool_id: 1, payout: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 1, joined: false }, + Event::PaidOut { member: 20, pool_id: 1, payout: 2 }, + Event::Bonded { member: 20, pool_id: 1, bonded: 2, joined: false } + ] + ); + }) + } +} diff --git a/frame/nomination-pools/src/weights.rs b/frame/nomination-pools/src/weights.rs new file mode 100644 index 0000000000000..4bd291f5276f8 --- /dev/null +++ b/frame/nomination-pools/src/weights.rs @@ -0,0 +1,482 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for pallet_nomination_pools +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-04-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// target/production/substrate +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_nomination_pools +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./frame/nomination-pools/src/weights.rs +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_nomination_pools. +pub trait WeightInfo { + fn join() -> Weight; + fn bond_extra_transfer() -> Weight; + fn bond_extra_reward() -> Weight; + fn claim_payout() -> Weight; + fn unbond() -> Weight; + fn pool_withdraw_unbonded(s: u32, ) -> Weight; + fn withdraw_unbonded_update(s: u32, ) -> Weight; + fn withdraw_unbonded_kill(s: u32, ) -> Weight; + fn create() -> Weight; + fn nominate(n: u32, ) -> Weight; + fn set_state() -> Weight; + fn set_metadata(n: u32, ) -> Weight; + fn set_configs() -> Weight; +} + +/// Weights for pallet_nomination_pools using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: NominationPools MinJoinBond (r:1 w:0) + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: Staking Ledger (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:0) + // Storage: System Account (r:2 w:1) + // Storage: NominationPools MaxPoolMembersPerPool (r:1 w:0) + // Storage: NominationPools MaxPoolMembers (r:1 w:0) + // Storage: NominationPools CounterForPoolMembers (r:1 w:1) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Balances Locks (r:1 w:1) + // Storage: BagsList ListNodes (r:3 w:3) + // Storage: BagsList ListBags (r:2 w:2) + fn join() -> Weight { + (117_870_000 as Weight) + .saturating_add(T::DbWeight::get().reads(18 as Weight)) + .saturating_add(T::DbWeight::get().writes(12 as Weight)) + } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: System Account (r:2 w:2) + // Storage: Staking Ledger (r:1 w:1) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Balances Locks (r:1 w:1) + // Storage: BagsList ListNodes (r:3 w:3) + // Storage: BagsList ListBags (r:2 w:2) + fn bond_extra_transfer() -> Weight { + (110_176_000 as Weight) + .saturating_add(T::DbWeight::get().reads(14 as Weight)) + .saturating_add(T::DbWeight::get().writes(13 as Weight)) + } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: System Account (r:3 w:3) + // Storage: Staking Ledger (r:1 w:1) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Balances Locks (r:1 w:1) + // Storage: BagsList ListNodes (r:2 w:2) + // Storage: BagsList ListBags (r:2 w:2) + fn bond_extra_reward() -> Weight { + (122_829_000 as Weight) + .saturating_add(T::DbWeight::get().reads(14 as Weight)) + .saturating_add(T::DbWeight::get().writes(13 as Weight)) + } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn claim_payout() -> Weight { + (50_094_000 as Weight) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().writes(5 as Weight)) + } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: System Account (r:2 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Staking Ledger (r:1 w:1) + // Storage: Staking Nominators (r:1 w:0) + // Storage: Staking MinNominatorBond (r:1 w:0) + // Storage: Balances Locks (r:1 w:1) + // Storage: BagsList ListNodes (r:3 w:3) + // Storage: Staking Bonded (r:1 w:0) + // Storage: BagsList ListBags (r:2 w:2) + // Storage: NominationPools SubPoolsStorage (r:1 w:1) + // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) + fn unbond() -> Weight { + (119_288_000 as Weight) + .saturating_add(T::DbWeight::get().reads(19 as Weight)) + .saturating_add(T::DbWeight::get().writes(14 as Weight)) + } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:0) + // Storage: Staking Ledger (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Balances Locks (r:1 w:1) + fn pool_withdraw_unbonded(s: u32, ) -> Weight { + (39_986_000 as Weight) + // Standard Error: 0 + .saturating_add((50_000 as Weight).saturating_mul(s as Weight)) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) + } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: NominationPools SubPoolsStorage (r:1 w:1) + // Storage: Staking Ledger (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + // Storage: System Account (r:1 w:1) + // Storage: NominationPools CounterForPoolMembers (r:1 w:1) + fn withdraw_unbonded_update(s: u32, ) -> Weight { + (76_897_000 as Weight) + // Standard Error: 0 + .saturating_add((48_000 as Weight).saturating_mul(s as Weight)) + .saturating_add(T::DbWeight::get().reads(9 as Weight)) + .saturating_add(T::DbWeight::get().writes(8 as Weight)) + } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: NominationPools SubPoolsStorage (r:1 w:1) + // Storage: Staking Ledger (r:1 w:1) + // Storage: Staking Bonded (r:1 w:1) + // Storage: Staking SlashingSpans (r:1 w:0) + // Storage: Staking Validators (r:1 w:0) + // Storage: Staking Nominators (r:1 w:0) + // Storage: System Account (r:2 w:2) + // Storage: Balances Locks (r:1 w:1) + // Storage: NominationPools CounterForPoolMembers (r:1 w:1) + // Storage: NominationPools ReversePoolIdLookup (r:1 w:1) + // Storage: NominationPools CounterForReversePoolIdLookup (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: NominationPools CounterForRewardPools (r:1 w:1) + // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) + // Storage: NominationPools CounterForBondedPools (r:1 w:1) + // Storage: Staking Payee (r:0 w:1) + fn withdraw_unbonded_kill(_s: u32, ) -> Weight { + (135_837_000 as Weight) + .saturating_add(T::DbWeight::get().reads(20 as Weight)) + .saturating_add(T::DbWeight::get().writes(17 as Weight)) + } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: Staking MinNominatorBond (r:1 w:0) + // Storage: NominationPools MinCreateBond (r:1 w:0) + // Storage: NominationPools MinJoinBond (r:1 w:0) + // Storage: NominationPools MaxPools (r:1 w:0) + // Storage: NominationPools CounterForBondedPools (r:1 w:1) + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: NominationPools LastPoolId (r:1 w:1) + // Storage: NominationPools MaxPoolMembersPerPool (r:1 w:0) + // Storage: NominationPools MaxPoolMembers (r:1 w:0) + // Storage: NominationPools CounterForPoolMembers (r:1 w:1) + // Storage: System Account (r:2 w:2) + // Storage: Staking Ledger (r:1 w:1) + // Storage: Staking Bonded (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Staking HistoryDepth (r:1 w:0) + // Storage: Balances Locks (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: NominationPools CounterForRewardPools (r:1 w:1) + // Storage: NominationPools ReversePoolIdLookup (r:1 w:1) + // Storage: NominationPools CounterForReversePoolIdLookup (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: Staking Payee (r:0 w:1) + fn create() -> Weight { + (129_265_000 as Weight) + .saturating_add(T::DbWeight::get().reads(23 as Weight)) + .saturating_add(T::DbWeight::get().writes(16 as Weight)) + } + // Storage: NominationPools BondedPools (r:1 w:0) + // Storage: Staking Ledger (r:1 w:0) + // Storage: Staking MinNominatorBond (r:1 w:0) + // Storage: Staking Nominators (r:1 w:1) + // Storage: Staking MaxNominatorsCount (r:1 w:0) + // Storage: Staking Validators (r:2 w:0) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Staking Bonded (r:1 w:0) + // Storage: BagsList ListNodes (r:1 w:1) + // Storage: BagsList ListBags (r:1 w:1) + // Storage: BagsList CounterForListNodes (r:1 w:1) + // Storage: Staking CounterForNominators (r:1 w:1) + fn nominate(n: u32, ) -> Weight { + (45_546_000 as Weight) + // Standard Error: 11_000 + .saturating_add((2_075_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(12 as Weight)) + .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) + .saturating_add(T::DbWeight::get().writes(5 as Weight)) + } + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: Staking Ledger (r:1 w:0) + fn set_state() -> Weight { + (23_256_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: NominationPools BondedPools (r:1 w:0) + // Storage: NominationPools Metadata (r:1 w:1) + // Storage: NominationPools CounterForMetadata (r:1 w:1) + fn set_metadata(n: u32, ) -> Weight { + (10_893_000 as Weight) + // Standard Error: 0 + .saturating_add((1_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: NominationPools MinJoinBond (r:0 w:1) + // Storage: NominationPools MaxPoolMembers (r:0 w:1) + // Storage: NominationPools MaxPoolMembersPerPool (r:0 w:1) + // Storage: NominationPools MinCreateBond (r:0 w:1) + // Storage: NominationPools MaxPools (r:0 w:1) + fn set_configs() -> Weight { + (2_793_000 as Weight) + .saturating_add(T::DbWeight::get().writes(5 as Weight)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: NominationPools MinJoinBond (r:1 w:0) + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: Staking Ledger (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:0) + // Storage: System Account (r:2 w:1) + // Storage: NominationPools MaxPoolMembersPerPool (r:1 w:0) + // Storage: NominationPools MaxPoolMembers (r:1 w:0) + // Storage: NominationPools CounterForPoolMembers (r:1 w:1) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Balances Locks (r:1 w:1) + // Storage: BagsList ListNodes (r:3 w:3) + // Storage: BagsList ListBags (r:2 w:2) + fn join() -> Weight { + (117_870_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(18 as Weight)) + .saturating_add(RocksDbWeight::get().writes(12 as Weight)) + } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: System Account (r:2 w:2) + // Storage: Staking Ledger (r:1 w:1) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Balances Locks (r:1 w:1) + // Storage: BagsList ListNodes (r:3 w:3) + // Storage: BagsList ListBags (r:2 w:2) + fn bond_extra_transfer() -> Weight { + (110_176_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(14 as Weight)) + .saturating_add(RocksDbWeight::get().writes(13 as Weight)) + } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: System Account (r:3 w:3) + // Storage: Staking Ledger (r:1 w:1) + // Storage: Staking Bonded (r:1 w:0) + // Storage: Balances Locks (r:1 w:1) + // Storage: BagsList ListNodes (r:2 w:2) + // Storage: BagsList ListBags (r:2 w:2) + fn bond_extra_reward() -> Weight { + (122_829_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(14 as Weight)) + .saturating_add(RocksDbWeight::get().writes(13 as Weight)) + } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn claim_payout() -> Weight { + (50_094_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(5 as Weight)) + .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: System Account (r:2 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Staking Ledger (r:1 w:1) + // Storage: Staking Nominators (r:1 w:0) + // Storage: Staking MinNominatorBond (r:1 w:0) + // Storage: Balances Locks (r:1 w:1) + // Storage: BagsList ListNodes (r:3 w:3) + // Storage: Staking Bonded (r:1 w:0) + // Storage: BagsList ListBags (r:2 w:2) + // Storage: NominationPools SubPoolsStorage (r:1 w:1) + // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) + fn unbond() -> Weight { + (119_288_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(19 as Weight)) + .saturating_add(RocksDbWeight::get().writes(14 as Weight)) + } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:0) + // Storage: Staking Ledger (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Balances Locks (r:1 w:1) + fn pool_withdraw_unbonded(s: u32, ) -> Weight { + (39_986_000 as Weight) + // Standard Error: 0 + .saturating_add((50_000 as Weight).saturating_mul(s as Weight)) + .saturating_add(RocksDbWeight::get().reads(5 as Weight)) + .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: NominationPools SubPoolsStorage (r:1 w:1) + // Storage: Staking Ledger (r:1 w:1) + // Storage: Balances Locks (r:1 w:1) + // Storage: System Account (r:1 w:1) + // Storage: NominationPools CounterForPoolMembers (r:1 w:1) + fn withdraw_unbonded_update(s: u32, ) -> Weight { + (76_897_000 as Weight) + // Standard Error: 0 + .saturating_add((48_000 as Weight).saturating_mul(s as Weight)) + .saturating_add(RocksDbWeight::get().reads(9 as Weight)) + .saturating_add(RocksDbWeight::get().writes(8 as Weight)) + } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: NominationPools SubPoolsStorage (r:1 w:1) + // Storage: Staking Ledger (r:1 w:1) + // Storage: Staking Bonded (r:1 w:1) + // Storage: Staking SlashingSpans (r:1 w:0) + // Storage: Staking Validators (r:1 w:0) + // Storage: Staking Nominators (r:1 w:0) + // Storage: System Account (r:2 w:2) + // Storage: Balances Locks (r:1 w:1) + // Storage: NominationPools CounterForPoolMembers (r:1 w:1) + // Storage: NominationPools ReversePoolIdLookup (r:1 w:1) + // Storage: NominationPools CounterForReversePoolIdLookup (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: NominationPools CounterForRewardPools (r:1 w:1) + // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) + // Storage: NominationPools CounterForBondedPools (r:1 w:1) + // Storage: Staking Payee (r:0 w:1) + fn withdraw_unbonded_kill(_s: u32, ) -> Weight { + (135_837_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(20 as Weight)) + .saturating_add(RocksDbWeight::get().writes(17 as Weight)) + } + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + // Storage: Staking MinNominatorBond (r:1 w:0) + // Storage: NominationPools MinCreateBond (r:1 w:0) + // Storage: NominationPools MinJoinBond (r:1 w:0) + // Storage: NominationPools MaxPools (r:1 w:0) + // Storage: NominationPools CounterForBondedPools (r:1 w:1) + // Storage: NominationPools PoolMembers (r:1 w:1) + // Storage: NominationPools LastPoolId (r:1 w:1) + // Storage: NominationPools MaxPoolMembersPerPool (r:1 w:0) + // Storage: NominationPools MaxPoolMembers (r:1 w:0) + // Storage: NominationPools CounterForPoolMembers (r:1 w:1) + // Storage: System Account (r:2 w:2) + // Storage: Staking Ledger (r:1 w:1) + // Storage: Staking Bonded (r:1 w:1) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Staking HistoryDepth (r:1 w:0) + // Storage: Balances Locks (r:1 w:1) + // Storage: NominationPools RewardPools (r:1 w:1) + // Storage: NominationPools CounterForRewardPools (r:1 w:1) + // Storage: NominationPools ReversePoolIdLookup (r:1 w:1) + // Storage: NominationPools CounterForReversePoolIdLookup (r:1 w:1) + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: Staking Payee (r:0 w:1) + fn create() -> Weight { + (129_265_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(23 as Weight)) + .saturating_add(RocksDbWeight::get().writes(16 as Weight)) + } + // Storage: NominationPools BondedPools (r:1 w:0) + // Storage: Staking Ledger (r:1 w:0) + // Storage: Staking MinNominatorBond (r:1 w:0) + // Storage: Staking Nominators (r:1 w:1) + // Storage: Staking MaxNominatorsCount (r:1 w:0) + // Storage: Staking Validators (r:2 w:0) + // Storage: Staking CurrentEra (r:1 w:0) + // Storage: Staking Bonded (r:1 w:0) + // Storage: BagsList ListNodes (r:1 w:1) + // Storage: BagsList ListBags (r:1 w:1) + // Storage: BagsList CounterForListNodes (r:1 w:1) + // Storage: Staking CounterForNominators (r:1 w:1) + fn nominate(n: u32, ) -> Weight { + (45_546_000 as Weight) + // Standard Error: 11_000 + .saturating_add((2_075_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(RocksDbWeight::get().reads(12 as Weight)) + .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) + .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + } + // Storage: NominationPools BondedPools (r:1 w:1) + // Storage: Staking Ledger (r:1 w:0) + fn set_state() -> Weight { + (23_256_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: NominationPools BondedPools (r:1 w:0) + // Storage: NominationPools Metadata (r:1 w:1) + // Storage: NominationPools CounterForMetadata (r:1 w:1) + fn set_metadata(n: u32, ) -> Weight { + (10_893_000 as Weight) + // Standard Error: 0 + .saturating_add((1_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + // Storage: NominationPools MinJoinBond (r:0 w:1) + // Storage: NominationPools MaxPoolMembers (r:0 w:1) + // Storage: NominationPools MaxPoolMembersPerPool (r:0 w:1) + // Storage: NominationPools MinCreateBond (r:0 w:1) + // Storage: NominationPools MaxPools (r:0 w:1) + fn set_configs() -> Weight { + (2_793_000 as Weight) + .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + } +} diff --git a/frame/staking/Cargo.toml b/frame/staking/Cargo.toml index 66ac5e0ff1b36..88080df0b5912 100644 --- a/frame/staking/Cargo.toml +++ b/frame/staking/Cargo.toml @@ -71,5 +71,6 @@ runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-election-provider-support/runtime-benchmarks", "rand_chacha", + "sp-staking/runtime-benchmarks" ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/staking/src/benchmarking.rs b/frame/staking/src/benchmarking.rs index 6c1589417f15a..7321740cfa79f 100644 --- a/frame/staking/src/benchmarking.rs +++ b/frame/staking/src/benchmarking.rs @@ -49,7 +49,7 @@ type MaxNominators = <::BenchmarkingConfig as BenchmarkingConfig // Add slashing spans to a user account. Not relevant for actual use, only to benchmark // read and write operations. -fn add_slashing_spans(who: &T::AccountId, spans: u32) { +pub fn add_slashing_spans(who: &T::AccountId, spans: u32) { if spans == 0 { return } diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 8003b9814c8e6..037dcc86ab2a8 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -737,6 +737,18 @@ where } } +impl SessionInterface for () { + fn disable_validator(_: u32) -> bool { + true + } + fn validators() -> Vec { + Vec::new() + } + fn prune_historical_up_to(_: SessionIndex) { + () + } +} + /// Handler for determining how much of a balance should be paid out on the current era. pub trait EraPayout { /// Determine the payout for this era. diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index 24572855264c4..bd2d8cdc32ce9 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -323,7 +323,7 @@ pub struct ExtBuilder { invulnerables: Vec, has_stakers: bool, initialize_first_session: bool, - min_nominator_bond: Balance, + pub min_nominator_bond: Balance, min_validator_bond: Balance, balance_factor: Balance, status: BTreeMap>, diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index ea4235f274da3..4665d956fdddb 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -29,15 +29,15 @@ use frame_support::{ }, weights::{Weight, WithPostDispatchInfo}, }; -use frame_system::pallet_prelude::BlockNumberFor; +use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; use pallet_session::historical; use sp_runtime::{ - traits::{Bounded, Convert, SaturatedConversion, Saturating, Zero}, + traits::{Bounded, Convert, SaturatedConversion, Saturating, StaticLookup, Zero}, Perbill, }; use sp_staking::{ offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}, - EraIndex, SessionIndex, + EraIndex, SessionIndex, StakingInterface, }; use sp_std::{collections::btree_map::BTreeMap, prelude::*}; @@ -1367,3 +1367,68 @@ impl SortedListProvider for UseNominatorsAndValidatorsM Validators::::remove_all(); } } + +impl StakingInterface for Pallet { + type AccountId = T::AccountId; + type Balance = BalanceOf; + + fn minimum_bond() -> Self::Balance { + MinNominatorBond::::get() + } + + fn bonding_duration() -> EraIndex { + T::BondingDuration::get() + } + + fn current_era() -> EraIndex { + Self::current_era().unwrap_or(Zero::zero()) + } + + fn active_stake(controller: &Self::AccountId) -> Option { + Self::ledger(controller).map(|l| l.active) + } + + fn total_stake(controller: &Self::AccountId) -> Option { + Self::ledger(controller).map(|l| l.total) + } + + fn bond_extra(stash: Self::AccountId, extra: Self::Balance) -> DispatchResult { + Self::bond_extra(RawOrigin::Signed(stash).into(), extra) + } + + fn unbond(controller: Self::AccountId, value: Self::Balance) -> DispatchResult { + Self::unbond(RawOrigin::Signed(controller).into(), value) + } + + fn withdraw_unbonded( + controller: Self::AccountId, + num_slashing_spans: u32, + ) -> Result { + Self::withdraw_unbonded(RawOrigin::Signed(controller).into(), num_slashing_spans) + .map(|post_info| { + post_info + .actual_weight + .unwrap_or(T::WeightInfo::withdraw_unbonded_kill(num_slashing_spans)) + }) + .map_err(|err_with_post_info| err_with_post_info.error) + } + + fn bond( + stash: Self::AccountId, + controller: Self::AccountId, + value: Self::Balance, + payee: Self::AccountId, + ) -> DispatchResult { + Self::bond( + RawOrigin::Signed(stash).into(), + T::Lookup::unlookup(controller), + value, + RewardDestination::Account(payee), + ) + } + + fn nominate(controller: Self::AccountId, targets: Vec) -> DispatchResult { + let targets = targets.into_iter().map(T::Lookup::unlookup).collect::>(); + Self::nominate(RawOrigin::Signed(controller).into(), targets) + } +} diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index f57d5bd7c7a0a..bf13786d64ee6 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -30,7 +30,7 @@ use frame_support::{ use frame_system::{ensure_root, ensure_signed, offchain::SendTransactionTypes, pallet_prelude::*}; use sp_runtime::{ traits::{CheckedSub, SaturatedConversion, StaticLookup, Zero}, - DispatchError, Perbill, Percent, + Perbill, Percent, }; use sp_staking::{EraIndex, SessionIndex}; use sp_std::{cmp::max, prelude::*}; diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index 4484ff6b6b295..2d2944cc9a44f 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -87,6 +87,8 @@ pub use self::{ StorageHasher, Twox128, Twox256, Twox64Concat, }, storage::{ + bounded_btree_map::BoundedBTreeMap, + bounded_btree_set::BoundedBTreeSet, bounded_vec::{BoundedSlice, BoundedVec}, migration, weak_bounded_vec::WeakBoundedVec, @@ -138,6 +140,25 @@ macro_rules! bounded_vec { } } +/// Build a bounded btree-map from the given literals. +/// +/// The type of the outcome must be known. +/// +/// Will not handle any errors and just panic if the given literals cannot fit in the corresponding +/// bounded vec type. Thus, this is only suitable for testing and non-consensus code. +#[macro_export] +#[cfg(feature = "std")] +macro_rules! bounded_btree_map { + ($ ( $key:expr => $value:expr ),* $(,)?) => { + { + $crate::traits::TryCollect::<$crate::BoundedBTreeMap<_, _, _>>::try_collect( + $crate::sp_std::vec![$(($key, $value)),*].into_iter() + ).unwrap() + } + }; + +} + /// Generate a new type alias for [`storage::types::StorageValue`], /// [`storage::types::StorageMap`], [`storage::types::StorageDoubleMap`] /// and [`storage::types::StorageNMap`]. diff --git a/frame/support/src/storage/bounded_btree_map.rs b/frame/support/src/storage/bounded_btree_map.rs index eca4b17821c77..0d589994bcc2c 100644 --- a/frame/support/src/storage/bounded_btree_map.rs +++ b/frame/support/src/storage/bounded_btree_map.rs @@ -74,6 +74,14 @@ where Self(t, Default::default()) } + /// Exactly the same semantics as `BTreeMap::retain`. + /// + /// The is a safe `&mut self` borrow because `retain` can only ever decrease the length of the + /// inner map. + pub fn retain bool>(&mut self, f: F) { + self.0.retain(f) + } + /// Create a new `BoundedBTreeMap`. /// /// Does not allocate. diff --git a/frame/support/src/traits/misc.rs b/frame/support/src/traits/misc.rs index 2a0d6f0523e71..7575aa8c19dc6 100644 --- a/frame/support/src/traits/misc.rs +++ b/frame/support/src/traits/misc.rs @@ -129,9 +129,13 @@ pub trait DefensiveOption { /// if `None`, which should never happen. fn defensive_map_or_else U, F: FnOnce(T) -> U>(self, default: D, f: F) -> U; - /// Defensively transform this option to a result. + /// Defensively transform this option to a result, mapping `None` to the return value of an + /// error closure. fn defensive_ok_or_else E>(self, err: F) -> Result; + /// Defensively transform this option to a result, mapping `None` to a default value. + fn defensive_ok_or(self, err: E) -> Result; + /// Exactly the same as `map`, but it prints the appropriate warnings if the value being mapped /// is `None`. fn defensive_map U>(self, f: F) -> Option; @@ -284,6 +288,13 @@ impl DefensiveOption for Option { }) } + fn defensive_ok_or(self, err: E) -> Result { + self.ok_or_else(|| { + defensive!(); + err + }) + } + fn defensive_map U>(self, f: F) -> Option { match self { Some(inner) => Some(f(inner)), diff --git a/primitives/staking/Cargo.toml b/primitives/staking/Cargo.toml index 7761519dabefb..ac6c9d0e6428c 100644 --- a/primitives/staking/Cargo.toml +++ b/primitives/staking/Cargo.toml @@ -26,3 +26,4 @@ std = [ "sp-runtime/std", "sp-std/std", ] +runtime-benchmarks = [] diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 57d858be1e0ef..7ff0808af0a63 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -19,6 +19,7 @@ //! A crate which contains primitives that are useful for implementation that uses staking //! approaches in general. Definitions related to sessions, slashing, etc go here. +use sp_runtime::{DispatchError, DispatchResult}; use sp_std::collections::btree_map::BTreeMap; pub mod offence; @@ -38,7 +39,7 @@ pub trait OnStakerSlash { /// /// * `stash` - The stash of the staker whom the slash was applied to. /// * `slashed_active` - The new bonded balance of the staker after the slash was applied. - /// * `slashed_unlocking` - a map of slashed eras, and the balance of that unlocking chunk after + /// * `slashed_unlocking` - A map of slashed eras, and the balance of that unlocking chunk after /// the slash is applied. Any era not present in the map is not affected at all. fn on_slash( stash: &AccountId, @@ -52,3 +53,80 @@ impl OnStakerSlash for () { // Nothing to do here } } + +/// Trait for communication with the staking pallet. +pub trait StakingInterface { + /// Balance type used by the staking system. + type Balance; + + /// AccountId type used by the staking system + type AccountId; + + /// The minimum amount required to bond in order to be a nominator. This does not necessarily + /// mean the nomination will be counted in an election, but instead just enough to be stored as + /// a nominator. In other words, this is the minimum amount to register the intention to + /// nominate. + fn minimum_bond() -> Self::Balance; + + /// Number of eras that staked funds must remain bonded for. + /// + /// # Note + /// + /// This must be strictly greater than the staking systems slash deffer duration. + fn bonding_duration() -> EraIndex; + + /// The current era index. + /// + /// This should be the latest planned era that the staking system knows about. + fn current_era() -> EraIndex; + + /// The amount of active stake that `controller` has in the staking system. + fn active_stake(controller: &Self::AccountId) -> Option; + + /// The total stake that `controller` has in the staking system. This includes the + /// [`Self::active_stake`], and any funds currently in the process of unbonding via + /// [`Self::unbond`]. + /// + /// # Note + /// + /// This is only guaranteed to reflect the amount locked by the staking system. If there are + /// non-staking locks on the bonded pair's balance this may not be accurate. + fn total_stake(controller: &Self::AccountId) -> Option; + + /// Bond (lock) `value` of `stash`'s balance. `controller` will be set as the account + /// controlling `stash`. This creates what is referred to as "bonded pair". + fn bond( + stash: Self::AccountId, + controller: Self::AccountId, + value: Self::Balance, + payee: Self::AccountId, + ) -> DispatchResult; + + /// Have `controller` nominate `validators`. + fn nominate( + controller: Self::AccountId, + validators: sp_std::vec::Vec, + ) -> DispatchResult; + + /// Bond some extra amount in the _Stash_'s free balance against the active bonded balance of + /// the account. The amount extra actually bonded will never be more than the _Stash_'s free + /// balance. + fn bond_extra(controller: Self::AccountId, extra: Self::Balance) -> DispatchResult; + + /// Schedule a portion of the active bonded balance to be unlocked at era + /// [Self::current_era] + [`Self::bonding_duration`]. + /// + /// Once the unlock era has been reached, [`Self::withdraw_unbonded`] can be called to unlock + /// the funds. + /// + /// The amount of times this can be successfully called is limited based on how many distinct + /// eras funds are schedule to unlock in. Calling [`Self::withdraw_unbonded`] after some unlock + /// schedules have reached their unlocking era should allow more calls to this function. + fn unbond(controller: Self::AccountId, value: Self::Balance) -> DispatchResult; + + /// Unlock any funds schedule to unlock before or at the current era. + fn withdraw_unbonded( + controller: Self::AccountId, + num_slashing_spans: u32, + ) -> Result; +} From e35ad2c60b44d5ed158b41d1593f05514ebc2ea4 Mon Sep 17 00:00:00 2001 From: Squirrel Date: Wed, 27 Apr 2022 18:19:07 +0100 Subject: [PATCH 161/484] Updating shell.nix nightly (#11266) * The 2022-02-10 nightly can't build some deps There's a sig that returns `impl IntoIterator` and an Option is being retured but it's incorrectly not allowing a `?` in the method. * removing duplicate test * [ci] add job cargo-check-nixos * add dummy variables nix check * fix check-dependent jobs * fix check-dependent-project template Co-authored-by: alvicsam --- .gitlab-ci.yml | 14 ++++++++++++++ shell.nix | 2 +- utils/frame/remote-externalities/src/lib.rs | 19 +------------------ 3 files changed, 16 insertions(+), 19 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0c06f3eb9c8e0..bd75cb0bac63c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -263,6 +263,20 @@ cargo-clippy: script: - SKIP_WASM_BUILD=1 env -u RUSTFLAGS cargo +nightly clippy --all-targets +cargo-check-nixos: + stage: test + <<: *docker-env + <<: *test-refs + before_script: [] + variables: + CI_IMAGE: "nixos/nix" + SNAP: "DUMMY" + WS_API: "DUMMY" + script: + - nix-channel --update + - nix-shell shell.nix + - nix-shell --run "cargo check --workspace --all-targets --all-features" + cargo-check-benches: stage: test <<: *docker-env diff --git a/shell.nix b/shell.nix index 023946ce16de4..c318995605a38 100644 --- a/shell.nix +++ b/shell.nix @@ -5,7 +5,7 @@ let rev = "15b7a05f20aab51c4ffbefddb1b448e862dccb7d"; }); nixpkgs = import { overlays = [ mozillaOverlay ]; }; - rust-nightly = with nixpkgs; ((rustChannelOf { date = "2022-02-10"; channel = "nightly"; }).rust.override { + rust-nightly = with nixpkgs; ((rustChannelOf { date = "2022-04-20"; channel = "nightly"; }).rust.override { extensions = [ "rust-src" ]; targets = [ "wasm32-unknown-unknown" ]; }); diff --git a/utils/frame/remote-externalities/src/lib.rs b/utils/frame/remote-externalities/src/lib.rs index 018ad2f4e0ad8..a717a93f072bc 100644 --- a/utils/frame/remote-externalities/src/lib.rs +++ b/utils/frame/remote-externalities/src/lib.rs @@ -1098,21 +1098,6 @@ mod remote_tests { } } - #[tokio::test] - async fn can_build_child_tree() { - init_logger(); - Builder::::new() - .mode(Mode::Online(OnlineConfig { - transport: "wss://rpc.polkadot.io:443".to_owned().into(), - pallets: vec!["Crowdloan".to_owned()], - ..Default::default() - })) - .build() - .await - .expect(REMOTE_INACCESSIBLE) - .execute_with(|| {}); - } - #[tokio::test] async fn can_create_child_snapshot() { init_logger(); @@ -1196,9 +1181,7 @@ mod remote_tests { init_logger(); Builder::::new() .mode(Mode::Online(OnlineConfig { - // transport: "wss://kusama-rpc.polkadot.io".to_owned().into(), - transport: "ws://kianenigma-archive:9924".to_owned().into(), - // transport: "ws://localhost:9999".to_owned().into(), + transport: "wss://rpc.polkadot.io:443".to_owned().into(), pallets: vec!["Crowdloan".to_owned()], ..Default::default() })) From 071c1ea72096862ff47277565b9052489ce4efcc Mon Sep 17 00:00:00 2001 From: Joshua W <64296537+HashWarlock@users.noreply.github.com> Date: Thu, 28 Apr 2022 06:05:18 -0500 Subject: [PATCH 162/484] Dependency Injection Trait Locker for Uniques Pallet (#11025) * Create a dependency injection trait named Locker that can be implemented downstream to enable locking of an asset. Use case defined in RMRK substrate pallet PR76 * Formatting * Change impl Locker function name to is_locked * Remove unused import * Add docstring header * Remove impl_locker file and add Locker trait to frame_support::traits * Expose Locker from frame_support::traits::misc * Formatting * Move to tokens folder * Move to tokens folder * Format and remove Locker from misc traits * Punctuation * Update frame/support/src/traits/tokens/misc.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Co-authored-by: Shawn Tabrizi Co-authored-by: Giles Cope --- bin/node/runtime/src/lib.rs | 1 + frame/support/src/traits.rs | 2 +- frame/support/src/traits/tokens.rs | 2 +- frame/support/src/traits/tokens/misc.rs | 16 ++++++++++++++++ frame/uniques/src/functions.rs | 1 + frame/uniques/src/lib.rs | 7 ++++++- frame/uniques/src/mock.rs | 1 + 7 files changed, 27 insertions(+), 3 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 54101cc222f84..18ce64c29f8e8 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1407,6 +1407,7 @@ impl pallet_uniques::Config for Runtime { #[cfg(feature = "runtime-benchmarks")] type Helper = (); type CreateOrigin = AsEnsureOriginWithArg>; + type Locker = (); } impl pallet_transaction_storage::Config for Runtime { diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index 40afc0d337f43..edeb7fead7c84 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -27,7 +27,7 @@ pub use tokens::{ }, fungible, fungibles, imbalance::{Imbalance, OnUnbalanced, SignedImbalance}, - BalanceStatus, ExistenceRequirement, WithdrawReasons, + BalanceStatus, ExistenceRequirement, Locker, WithdrawReasons, }; mod members; diff --git a/frame/support/src/traits/tokens.rs b/frame/support/src/traits/tokens.rs index 92f8ce12d9128..77eb83adfbfb0 100644 --- a/frame/support/src/traits/tokens.rs +++ b/frame/support/src/traits/tokens.rs @@ -27,5 +27,5 @@ pub mod nonfungibles; pub use imbalance::Imbalance; pub use misc::{ AssetId, Balance, BalanceConversion, BalanceStatus, DepositConsequence, ExistenceRequirement, - WithdrawConsequence, WithdrawReasons, + Locker, WithdrawConsequence, WithdrawReasons, }; diff --git a/frame/support/src/traits/tokens/misc.rs b/frame/support/src/traits/tokens/misc.rs index f30fd02bfe831..86304b14a33f6 100644 --- a/frame/support/src/traits/tokens/misc.rs +++ b/frame/support/src/traits/tokens/misc.rs @@ -179,3 +179,19 @@ pub trait BalanceConversion { type Error; fn to_asset_balance(balance: InBalance, asset_id: AssetId) -> Result; } + +/// Trait to handle asset locking mechanism to ensure interactions with the asset can be implemented +/// downstream to extend logic of Uniques current functionality. +pub trait Locker { + /// Check if the asset should be locked and prevent interactions with the asset from executing. + fn is_locked(class: ClassId, instance: InstanceId) -> bool; +} + +impl Locker for () { + // Default will be false if not implemented downstream. + // Note: The logic check in this function must be constant time and consistent for benchmarks + // to work. + fn is_locked(_class: ClassId, _instance: InstanceId) -> bool { + false + } +} diff --git a/frame/uniques/src/functions.rs b/frame/uniques/src/functions.rs index 40c436bd56b47..8b874997f2ff0 100644 --- a/frame/uniques/src/functions.rs +++ b/frame/uniques/src/functions.rs @@ -33,6 +33,7 @@ impl, I: 'static> Pallet { ) -> DispatchResult { let class_details = Class::::get(&class).ok_or(Error::::UnknownClass)?; ensure!(!class_details.is_frozen, Error::::Frozen); + ensure!(!T::Locker::is_locked(class, instance), Error::::Locked); let mut details = Asset::::get(&class, &instance).ok_or(Error::::UnknownClass)?; diff --git a/frame/uniques/src/lib.rs b/frame/uniques/src/lib.rs index 1e14825454193..d999acc4c9403 100644 --- a/frame/uniques/src/lib.rs +++ b/frame/uniques/src/lib.rs @@ -43,7 +43,7 @@ pub mod weights; use codec::{Decode, Encode}; use frame_support::traits::{ - BalanceStatus::Reserved, Currency, EnsureOriginWithArg, ReservableCurrency, + tokens::Locker, BalanceStatus::Reserved, Currency, EnsureOriginWithArg, ReservableCurrency, }; use frame_system::Config as SystemConfig; use sp_runtime::{ @@ -108,6 +108,9 @@ pub mod pallet { Self::ClassId, >; + /// Locker trait to enable Locking mechanism downstream. + type Locker: Locker; + /// The basic amount of funds that must be reserved for an asset class. #[pallet::constant] type ClassDeposit: Get>; @@ -352,6 +355,8 @@ pub mod pallet { Unapproved, /// The named owner has not signed ownership of the class is acceptable. Unaccepted, + /// The asset instance is locked. + Locked, } impl, I: 'static> Pallet { diff --git a/frame/uniques/src/mock.rs b/frame/uniques/src/mock.rs index 265142443ef48..f32540f6ef7ba 100644 --- a/frame/uniques/src/mock.rs +++ b/frame/uniques/src/mock.rs @@ -91,6 +91,7 @@ impl Config for Test { type Currency = Balances; type CreateOrigin = AsEnsureOriginWithArg>; type ForceOrigin = frame_system::EnsureRoot; + type Locker = (); type ClassDeposit = ConstU64<2>; type InstanceDeposit = ConstU64<1>; type MetadataDepositBase = ConstU64<1>; From 84e612d6be76fe52683ef930ff2834f04ceb0354 Mon Sep 17 00:00:00 2001 From: Adrian Catangiu Date: Thu, 28 Apr 2022 14:19:32 +0300 Subject: [PATCH 163/484] pallet-beefy: ensure mandatory block once per session (#11269) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * pallet-beefy: ensure mandatory block once per session Signed-off-by: acatangiu * pallet-beefy: fix tests with auth changes every session Signed-off-by: acatangiu * Apply suggestions from code review Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com> * beefy: fix incorrect skip session metric on node restart Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com> --- client/beefy/src/worker.rs | 5 ++++- frame/beefy-mmr/src/tests.rs | 26 +++++++++++++++++--------- frame/beefy/src/lib.rs | 16 ++++++---------- frame/beefy/src/mock.rs | 3 +-- frame/beefy/src/tests.rs | 30 ++++++++++++++++++------------ 5 files changed, 46 insertions(+), 34 deletions(-) diff --git a/client/beefy/src/worker.rs b/client/beefy/src/worker.rs index 696bee9d7ee76..8ab18c58f9dd3 100644 --- a/client/beefy/src/worker.rs +++ b/client/beefy/src/worker.rs @@ -242,7 +242,10 @@ where debug!(target: "beefy", "🥩 New active validator set: {:?}", active); metric_set!(self, beefy_validator_set_id, active.id()); // BEEFY should produce a signed commitment for each session - if active.id() != self.last_signed_id + 1 && active.id() != GENESIS_AUTHORITY_SET_ID { + if active.id() != self.last_signed_id + 1 && + active.id() != GENESIS_AUTHORITY_SET_ID && + self.last_signed_id != 0 + { metric_inc!(self, beefy_skipped_sessions); } diff --git a/frame/beefy-mmr/src/tests.rs b/frame/beefy-mmr/src/tests.rs index 37f571cd842ee..fd3ecd5067155 100644 --- a/frame/beefy-mmr/src/tests.rs +++ b/frame/beefy-mmr/src/tests.rs @@ -70,9 +70,14 @@ fn should_contain_mmr_digest() { assert_eq!( System::digest().logs, - vec![beefy_log(ConsensusLog::MmrRoot( - hex!("fa0275b19b2565089f7e2377ee73b9050e8d53bce108ef722a3251fd9d371d4b").into() - ))] + vec![ + beefy_log(ConsensusLog::AuthoritiesChange( + ValidatorSet::new(vec![mock_beefy_id(1), mock_beefy_id(2)], 1).unwrap() + )), + beefy_log(ConsensusLog::MmrRoot( + hex!("95803defe6ea9f41e7ec6afa497064f21bfded027d8812efacbdf984e630cbdc").into() + )) + ] ); // unique every time @@ -81,14 +86,17 @@ fn should_contain_mmr_digest() { assert_eq!( System::digest().logs, vec![ + beefy_log(ConsensusLog::AuthoritiesChange( + ValidatorSet::new(vec![mock_beefy_id(1), mock_beefy_id(2)], 1).unwrap() + )), beefy_log(ConsensusLog::MmrRoot( - hex!("fa0275b19b2565089f7e2377ee73b9050e8d53bce108ef722a3251fd9d371d4b").into() + hex!("95803defe6ea9f41e7ec6afa497064f21bfded027d8812efacbdf984e630cbdc").into() )), beefy_log(ConsensusLog::AuthoritiesChange( - ValidatorSet::new(vec![mock_beefy_id(3), mock_beefy_id(4),], 1,).unwrap() + ValidatorSet::new(vec![mock_beefy_id(3), mock_beefy_id(4)], 2).unwrap() )), beefy_log(ConsensusLog::MmrRoot( - hex!("85554fa7d4e863cce3cdce668c1ae82c0174ad37f8d1399284018bec9f9971c3").into() + hex!("a73271a0974f1e67d6e9b8dd58e506177a2e556519a330796721e98279a753e2").into() )), ] ); @@ -109,9 +117,9 @@ fn should_contain_valid_leaf_data() { version: MmrLeafVersion::new(1, 5), parent_number_and_hash: (0_u64, H256::repeat_byte(0x45)), beefy_next_authority_set: BeefyNextAuthoritySet { - id: 1, + id: 2, len: 2, - root: hex!("176e73f1bf656478b728e28dd1a7733c98621b8acf830bff585949763dca7a96") + root: hex!("9c6b2c1b0d0b25a008e6c882cc7b415f309965c72ad2b944ac0931048ca31cd5") .into(), }, leaf_extra: hex!("55b8e9e1cc9f0db7776fac0ca66318ef8acfb8ec26db11e373120583e07ee648") @@ -131,7 +139,7 @@ fn should_contain_valid_leaf_data() { version: MmrLeafVersion::new(1, 5), parent_number_and_hash: (1_u64, H256::repeat_byte(0x45)), beefy_next_authority_set: BeefyNextAuthoritySet { - id: 2, + id: 3, len: 2, root: hex!("9c6b2c1b0d0b25a008e6c882cc7b415f309965c72ad2b944ac0931048ca31cd5") .into(), diff --git a/frame/beefy/src/lib.rs b/frame/beefy/src/lib.rs index 744a06561e8c2..14e7ac26cdb6e 100644 --- a/frame/beefy/src/lib.rs +++ b/frame/beefy/src/lib.rs @@ -105,10 +105,6 @@ impl Pallet { } fn change_authorities(new: Vec, queued: Vec) { - // Always issue a change if `session` says that the validators have changed. - // Even if their session keys are the same as before, the underlying economic - // identities have changed. Furthermore, the digest below is used to signal - // BEEFY mandatory blocks. >::put(&new); let next_id = Self::validator_set_id() + 1u64; @@ -153,16 +149,16 @@ impl OneSessionHandler for Pallet { Self::initialize_authorities(&authorities); } - fn on_new_session<'a, I: 'a>(changed: bool, validators: I, queued_validators: I) + fn on_new_session<'a, I: 'a>(_changed: bool, validators: I, queued_validators: I) where I: Iterator, { - if changed { - let next_authorities = validators.map(|(_, k)| k).collect::>(); - let next_queued_authorities = queued_validators.map(|(_, k)| k).collect::>(); + let next_authorities = validators.map(|(_, k)| k).collect::>(); + let next_queued_authorities = queued_validators.map(|(_, k)| k).collect::>(); - Self::change_authorities(next_authorities, next_queued_authorities); - } + // Always issue a change on each `session`, even if validator set hasn't changed. + // We want to have at least one BEEFY mandatory block per session. + Self::change_authorities(next_authorities, next_queued_authorities); } fn on_disabled(i: u32) { diff --git a/frame/beefy/src/mock.rs b/frame/beefy/src/mock.rs index 5fc04f7cbd1d2..7a8f15cd51d29 100644 --- a/frame/beefy/src/mock.rs +++ b/frame/beefy/src/mock.rs @@ -145,8 +145,7 @@ pub fn new_test_ext_raw_authorities(authorities: Vec<(u64, BeefyId)>) -> TestExt let session_keys: Vec<_> = authorities .iter() - .enumerate() - .map(|(_, id)| (id.0 as u64, id.0 as u64, MockSessionKeys { dummy: id.1.clone() })) + .map(|id| (id.0 as u64, id.0 as u64, MockSessionKeys { dummy: id.1.clone() })) .collect(); BasicExternalities::execute_with_storage(&mut t, || { diff --git a/frame/beefy/src/tests.rs b/frame/beefy/src/tests.rs index 7acb40f200dfd..4136b0c1f1ecf 100644 --- a/frame/beefy/src/tests.rs +++ b/frame/beefy/src/tests.rs @@ -59,23 +59,28 @@ fn genesis_session_initializes_authorities() { #[test] fn session_change_updates_authorities() { new_test_ext(vec![1, 2, 3, 4]).execute_with(|| { - init_block(1); - assert!(0 == Beefy::validator_set_id()); - // no change - no log - assert!(System::digest().logs.is_empty()); - - init_block(2); + init_block(1); assert!(1 == Beefy::validator_set_id()); let want = beefy_log(ConsensusLog::AuthoritiesChange( - ValidatorSet::new(vec![mock_beefy_id(3), mock_beefy_id(4)], 1).unwrap(), + ValidatorSet::new(vec![mock_beefy_id(1), mock_beefy_id(2)], 1).unwrap(), )); let log = System::digest().logs[0].clone(); + assert_eq!(want, log); + + init_block(2); + + assert!(2 == Beefy::validator_set_id()); + + let want = beefy_log(ConsensusLog::AuthoritiesChange( + ValidatorSet::new(vec![mock_beefy_id(3), mock_beefy_id(4)], 2).unwrap(), + )); + let log = System::digest().logs[1].clone(); assert_eq!(want, log); }); } @@ -85,15 +90,13 @@ fn session_change_updates_next_authorities() { let want = vec![mock_beefy_id(1), mock_beefy_id(2), mock_beefy_id(3), mock_beefy_id(4)]; new_test_ext(vec![1, 2, 3, 4]).execute_with(|| { - init_block(1); - let next_authorities = Beefy::next_authorities(); assert!(next_authorities.len() == 2); assert_eq!(want[0], next_authorities[0]); assert_eq!(want[1], next_authorities[1]); - init_block(2); + init_block(1); let next_authorities = Beefy::next_authorities(); @@ -121,11 +124,14 @@ fn validator_set_updates_work() { let want = vec![mock_beefy_id(1), mock_beefy_id(2), mock_beefy_id(3), mock_beefy_id(4)]; new_test_ext(vec![1, 2, 3, 4]).execute_with(|| { + let vs = Beefy::validator_set().unwrap(); + assert_eq!(vs.id(), 0u64); + init_block(1); let vs = Beefy::validator_set().unwrap(); - assert_eq!(vs.id(), 0u64); + assert_eq!(vs.id(), 1u64); assert_eq!(want[0], vs.validators()[0]); assert_eq!(want[1], vs.validators()[1]); @@ -133,7 +139,7 @@ fn validator_set_updates_work() { let vs = Beefy::validator_set().unwrap(); - assert_eq!(vs.id(), 1u64); + assert_eq!(vs.id(), 2u64); assert_eq!(want[2], vs.validators()[0]); assert_eq!(want[3], vs.validators()[1]); }); From 9338c29e5564cbf3aff98cefaff8ec752a2344b1 Mon Sep 17 00:00:00 2001 From: yjh Date: Thu, 28 Apr 2022 22:12:56 +0800 Subject: [PATCH 164/484] make sp-wasm-interface some wasmi impls works in no_std (#11310) --- primitives/wasm-interface/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/primitives/wasm-interface/src/lib.rs b/primitives/wasm-interface/src/lib.rs index 6dfc3116ddc48..246c1abaeae3b 100644 --- a/primitives/wasm-interface/src/lib.rs +++ b/primitives/wasm-interface/src/lib.rs @@ -21,7 +21,7 @@ use sp_std::{borrow::Cow, iter::Iterator, marker::PhantomData, mem, result, vec, vec::Vec}; -#[cfg(feature = "std")] +#[cfg(feature = "wasmi")] mod wasmi_impl; #[cfg(not(all(feature = "std", feature = "wasmtime")))] From 6f9e65341712710a4e35f2584640c3bd7b329c4b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Apr 2022 17:57:02 +0200 Subject: [PATCH 165/484] Bump ss58-registry from 1.15.0 to 1.17.0 (#11265) Bumps [ss58-registry](https://github.com/paritytech/ss58-registry) from 1.15.0 to 1.17.0. - [Release notes](https://github.com/paritytech/ss58-registry/releases) - [Changelog](https://github.com/paritytech/ss58-registry/blob/main/CHANGELOG.md) - [Commits](https://github.com/paritytech/ss58-registry/compare/v1.15.0...v1.17.0) --- updated-dependencies: - dependency-name: ss58-registry dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Giles Cope --- Cargo.lock | 9 +++++---- primitives/core/Cargo.toml | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 596b737e22d09..16e048f774230 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7466,9 +7466,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.10" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" dependencies = [ "proc-macro2", ] @@ -10641,11 +10641,12 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "ss58-registry" -version = "1.15.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f9799e6d412271cb2414597581128b03f3285f260ea49f5363d07df6a332b3e" +checksum = "7b84a70894df7a73666e0694f44b41a9571625e9546fb58a0818a565d2c7e084" dependencies = [ "Inflector", + "num-format", "proc-macro2", "quote", "serde", diff --git a/primitives/core/Cargo.toml b/primitives/core/Cargo.toml index 9c79cb4843401..d23579c8ca2c0 100644 --- a/primitives/core/Cargo.toml +++ b/primitives/core/Cargo.toml @@ -57,7 +57,7 @@ hex = { version = "0.4", default-features = false, optional = true } libsecp256k1 = { version = "0.7", default-features = false, features = ["static-context"], optional = true } merlin = { version = "2.0", default-features = false, optional = true } secp256k1 = { version = "0.21.2", default-features = false, features = ["recovery", "alloc"], optional = true } -ss58-registry = { version = "1.15.0", default-features = false } +ss58-registry = { version = "1.17.0", default-features = false } sp-core-hashing = { version = "4.0.0", path = "./hashing", default-features = false, optional = true } sp-runtime-interface = { version = "6.0.0", default-features = false, path = "../runtime-interface" } From 1fdee915eb2e9eb69c1362b85d34007506445ad7 Mon Sep 17 00:00:00 2001 From: Bisola Olasehinde Date: Fri, 29 Apr 2022 03:25:50 +0100 Subject: [PATCH 166/484] Replace simple parameter_types (#11254) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * update bin/node-template/runtime/src/lib.rs * update frame/contracts/src/tests.rs * update frame/executive/src/lib.rs * update frame/grandpa/src/mock.rs * update frame/im-online/src/mock.rs * update frame/offences/benchmarking/src/mock.rs * update frame/recovery/src/mock.rs * update frame/referenda/src/mock.rs * update frame/session/benchmarking/src/mock.rs * update frame/staking/src/mock.rs * update frame/state-trie-migration/src/lib.rs * update frame/support/test/compile_pass/src/lib.rs * frame/treasury/src/tests.rs * update frame/whitelist/src/mock.rs * update frame/vesting/src/mock.rs * update test-utils/runtime/src/lib.rs * update bin/node-template/runtime/src/lib.rs * Update frame/grandpa/src/mock.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * resolve failed checks 1518659 & 1518669 * resolve format check * backtrack to resolve compile error * check --all --tests ✅ * cargo +nightly fmt ✅ Co-authored-by: Shawn Tabrizi Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Co-authored-by: Keith Yeung --- bin/node-template/runtime/src/lib.rs | 12 +++++----- frame/contracts/src/tests.rs | 6 +---- frame/executive/src/lib.rs | 5 +---- frame/grandpa/src/mock.rs | 6 +---- frame/im-online/src/mock.rs | 6 +---- frame/offences/benchmarking/src/mock.rs | 6 ++--- frame/referenda/src/mock.rs | 26 ++++++---------------- frame/session/benchmarking/src/mock.rs | 6 ++--- frame/state-trie-migration/src/lib.rs | 14 ++++++------ frame/support/test/compile_pass/src/lib.rs | 5 ++--- frame/treasury/src/tests.rs | 5 +---- frame/whitelist/src/mock.rs | 13 ++++------- test-utils/runtime/src/lib.rs | 3 +-- 13 files changed, 35 insertions(+), 78 deletions(-) diff --git a/bin/node-template/runtime/src/lib.rs b/bin/node-template/runtime/src/lib.rs index 780a84572d582..a7a162bf74327 100644 --- a/bin/node-template/runtime/src/lib.rs +++ b/bin/node-template/runtime/src/lib.rs @@ -26,7 +26,9 @@ use sp_version::RuntimeVersion; // A few exports that help ease life for downstream crates. pub use frame_support::{ construct_runtime, parameter_types, - traits::{ConstU128, ConstU32, ConstU8, KeyOwnerProofSystem, Randomness, StorageInfo}, + traits::{ + ConstU128, ConstU32, ConstU64, ConstU8, KeyOwnerProofSystem, Randomness, StorageInfo, + }, weights::{ constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND}, IdentityFee, Weight, @@ -132,8 +134,8 @@ pub fn native_version() -> NativeVersion { const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); parameter_types! { - pub const Version: RuntimeVersion = VERSION; pub const BlockHashCount: BlockNumber = 2400; + pub const Version: RuntimeVersion = VERSION; /// We allow for 2 seconds of compute with a 6 second average block time. pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights ::with_sensible_defaults(2 * WEIGHT_PER_SECOND, NORMAL_DISPATCH_RATIO); @@ -224,15 +226,11 @@ impl pallet_grandpa::Config for Runtime { type MaxAuthorities = ConstU32<32>; } -parameter_types! { - pub const MinimumPeriod: u64 = SLOT_DURATION / 2; -} - impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; type OnTimestampSet = Aura; - type MinimumPeriod = MinimumPeriod; + type MinimumPeriod = ConstU64<{ SLOT_DURATION / 2 }>; type WeightInfo = (); } diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index ce59f5bb858ac..9d949eea9b948 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -234,9 +234,6 @@ impl pallet_utility::Config for Test { type WeightInfo = (); } parameter_types! { - pub const MaxValueSize: u32 = 16_384; - pub const DeletionWeightLimit: Weight = 500_000_000_000; - pub const MaxCodeSize: u32 = 2 * 1024; pub MySchedule: Schedule = { let mut schedule = >::default(); // We want stack height to be always enabled for tests so that this @@ -244,7 +241,6 @@ parameter_types! { schedule.limits.stack_height = Some(512); schedule }; - pub const TransactionByteFee: u64 = 0; pub static DepositPerByte: BalanceOf = 1; pub const DepositPerItem: BalanceOf = 2; } @@ -286,7 +282,7 @@ impl Config for Test { type WeightInfo = (); type ChainExtension = TestExtension; type DeletionQueueDepth = ConstU32<1024>; - type DeletionWeightLimit = DeletionWeightLimit; + type DeletionWeightLimit = ConstU64<500_000_000_000>; type Schedule = MySchedule; type DepositPerByte = DepositPerByte; type DepositPerItem = DepositPerItem; diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index de1cc3b916404..68b5e910ed1fc 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -769,14 +769,11 @@ mod tests { } type Balance = u64; - parameter_types! { - pub const ExistentialDeposit: Balance = 1; - } impl pallet_balances::Config for Runtime { type Balance = Balance; type Event = Event; type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; + type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type MaxLocks = (); type MaxReserves = (); diff --git a/frame/grandpa/src/mock.rs b/frame/grandpa/src/mock.rs index 5700fbe3aa36e..5e6c955c441c5 100644 --- a/frame/grandpa/src/mock.rs +++ b/frame/grandpa/src/mock.rs @@ -172,11 +172,7 @@ pallet_staking_reward_curve::build! { parameter_types! { pub const SessionsPerEra: SessionIndex = 3; pub const BondingDuration: EraIndex = 3; - pub const SlashDeferDuration: EraIndex = 0; - pub const AttestationPeriod: u64 = 100; pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; - pub const ElectionLookahead: u64 = 0; - pub const StakingUnsignedPriority: u64 = u64::MAX / 2; pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(17); } @@ -199,7 +195,7 @@ impl pallet_staking::Config for Test { type Reward = (); type SessionsPerEra = SessionsPerEra; type BondingDuration = BondingDuration; - type SlashDeferDuration = SlashDeferDuration; + type SlashDeferDuration = (); type SlashCancelOrigin = frame_system::EnsureRoot; type SessionInterface = Self; type UnixTime = pallet_timestamp::Pallet; diff --git a/frame/im-online/src/mock.rs b/frame/im-online/src/mock.rs index 86904b38d834b..2459f7e748941 100644 --- a/frame/im-online/src/mock.rs +++ b/frame/im-online/src/mock.rs @@ -218,17 +218,13 @@ impl frame_support::traits::EstimateNextSessionRotation for TestNextSession } } -parameter_types! { - pub const UnsignedPriority: u64 = 1 << 20; -} - impl Config for Runtime { type AuthorityId = UintAuthorityId; type Event = Event; type ValidatorSet = Historical; type NextSessionRotation = TestNextSessionRotation; type ReportUnresponsiveness = OffenceHandler; - type UnsignedPriority = UnsignedPriority; + type UnsignedPriority = ConstU64<{ 1 << 20 }>; type WeightInfo = (); type MaxKeys = ConstU32<10_000>; type MaxPeerInHeartbeats = ConstU32<10_000>; diff --git a/frame/offences/benchmarking/src/mock.rs b/frame/offences/benchmarking/src/mock.rs index fba8c567059c2..d51a81b1212c0 100644 --- a/frame/offences/benchmarking/src/mock.rs +++ b/frame/offences/benchmarking/src/mock.rs @@ -69,9 +69,7 @@ impl frame_system::Config for Test { type OnSetCode = (); type MaxConsumers = frame_support::traits::ConstU32<16>; } -parameter_types! { - pub const ExistentialDeposit: Balance = 10; -} + impl pallet_balances::Config for Test { type MaxLocks = (); type MaxReserves = (); @@ -79,7 +77,7 @@ impl pallet_balances::Config for Test { type Balance = Balance; type Event = Event; type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; + type ExistentialDeposit = ConstU64<10>; type AccountStore = System; type WeightInfo = (); } diff --git a/frame/referenda/src/mock.rs b/frame/referenda/src/mock.rs index fdd14fdadf04d..1b0bbba24bbe6 100644 --- a/frame/referenda/src/mock.rs +++ b/frame/referenda/src/mock.rs @@ -26,7 +26,6 @@ use frame_support::{ ConstU32, ConstU64, Contains, EqualPrivilegeOnly, OnInitialize, OriginTrait, Polling, PreimageRecipient, SortedMembers, }, - weights::Weight, }; use frame_system::{EnsureRoot, EnsureSignedBy}; use sp_core::H256; @@ -62,7 +61,6 @@ impl Contains for BaseFilter { } parameter_types! { - pub const BlockHashCount: u64 = 250; pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::simple_max(1_000_000); } @@ -81,7 +79,7 @@ impl frame_system::Config for Test { type Lookup = IdentityLookup; type Header = Header; type Event = Event; - type BlockHashCount = BlockHashCount; + type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; type AccountData = pallet_balances::AccountData; @@ -101,15 +99,12 @@ impl pallet_preimage::Config for Test { type BaseDeposit = (); type ByteDeposit = (); } -parameter_types! { - pub MaximumSchedulerWeight: Weight = 2_000_000_000_000; -} impl pallet_scheduler::Config for Test { type Event = Event; type Origin = Origin; type PalletsOrigin = OriginCaller; type Call = Call; - type MaximumWeight = MaximumSchedulerWeight; + type MaximumWeight = ConstU64<2_000_000_000_000>; type ScheduleOrigin = EnsureRoot; type MaxScheduledPerBlock = ConstU32<100>; type WeightInfo = (); @@ -117,26 +112,19 @@ impl pallet_scheduler::Config for Test { type PreimageProvider = Preimage; type NoPreimagePostponement = ConstU64<10>; } -parameter_types! { - pub const ExistentialDeposit: u64 = 1; - pub const MaxLocks: u32 = 10; -} impl pallet_balances::Config for Test { type MaxReserves = (); type ReserveIdentifier = [u8; 8]; - type MaxLocks = MaxLocks; + type MaxLocks = ConstU32<10>; type Balance = u64; type Event = Event; type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; + type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); } parameter_types! { pub static AlarmInterval: u64 = 1; - pub const SubmissionDeposit: u64 = 2; - pub const MaxQueued: u32 = 3; - pub const UndecidingTimeout: u64 = 20; } ord_parameter_types! { pub const One: u64 = 1; @@ -228,9 +216,9 @@ impl Config for Test { type Slash = (); type Votes = u32; type Tally = Tally; - type SubmissionDeposit = SubmissionDeposit; - type MaxQueued = MaxQueued; - type UndecidingTimeout = UndecidingTimeout; + type SubmissionDeposit = ConstU64<2>; + type MaxQueued = ConstU32<3>; + type UndecidingTimeout = ConstU64<20>; type AlarmInterval = AlarmInterval; type Tracks = TestTracksInfo; } diff --git a/frame/session/benchmarking/src/mock.rs b/frame/session/benchmarking/src/mock.rs index 6835736aa8e00..c777f2c56de3a 100644 --- a/frame/session/benchmarking/src/mock.rs +++ b/frame/session/benchmarking/src/mock.rs @@ -73,9 +73,7 @@ impl frame_system::Config for Test { type OnSetCode = (); type MaxConsumers = ConstU32<16>; } -parameter_types! { - pub const ExistentialDeposit: Balance = 10; -} + impl pallet_balances::Config for Test { type MaxLocks = (); type MaxReserves = (); @@ -83,7 +81,7 @@ impl pallet_balances::Config for Test { type Balance = Balance; type Event = Event; type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; + type ExistentialDeposit = ConstU64<10>; type AccountStore = System; type WeightInfo = (); } diff --git a/frame/state-trie-migration/src/lib.rs b/frame/state-trie-migration/src/lib.rs index 87be4099c89c8..3d66c6a66b8cf 100644 --- a/frame/state-trie-migration/src/lib.rs +++ b/frame/state-trie-migration/src/lib.rs @@ -980,7 +980,10 @@ mod benchmarks { mod mock { use super::*; use crate as pallet_state_trie_migration; - use frame_support::{parameter_types, traits::Hooks}; + use frame_support::{ + parameter_types, + traits::{ConstU32, ConstU64, Hooks}, + }; use frame_system::{EnsureRoot, EnsureSigned}; use sp_core::{ storage::{ChildInfo, StateVersion}, @@ -1008,7 +1011,6 @@ mod mock { ); parameter_types! { - pub const BlockHashCount: u32 = 250; pub const SS58Prefix: u8 = 42; } @@ -1026,7 +1028,7 @@ mod mock { type Lookup = IdentityLookup; type Header = sp_runtime::generic::Header; type Event = Event; - type BlockHashCount = BlockHashCount; + type BlockHashCount = ConstU32<250>; type DbWeight = (); type Version = (); type PalletInfo = PalletInfo; @@ -1036,12 +1038,10 @@ mod mock { type SystemWeightInfo = (); type SS58Prefix = SS58Prefix; type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; + type MaxConsumers = ConstU32<16>; } parameter_types! { - pub const ExistentialDeposit: u64 = 1; - pub const OffchainRepeat: u32 = 1; pub const SignedDepositPerItem: u64 = 1; pub const SignedDepositBase: u64 = 5; } @@ -1050,7 +1050,7 @@ mod mock { type Balance = u64; type Event = Event; type DustRemoval = (); - type ExistentialDeposit = ExistentialDeposit; + type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type MaxLocks = (); type MaxReserves = (); diff --git a/frame/support/test/compile_pass/src/lib.rs b/frame/support/test/compile_pass/src/lib.rs index 0c955c749613c..7850726048546 100644 --- a/frame/support/test/compile_pass/src/lib.rs +++ b/frame/support/test/compile_pass/src/lib.rs @@ -24,7 +24,7 @@ use frame_support::{ construct_runtime, parameter_types, - traits::{ConstU16, ConstU32}, + traits::{ConstU16, ConstU32, ConstU64}, }; use sp_core::{sr25519, H256}; use sp_runtime::{ @@ -50,7 +50,6 @@ pub type BlockNumber = u64; pub type Index = u64; parameter_types! { - pub const BlockHashCount: BlockNumber = 2400; pub const Version: RuntimeVersion = VERSION; } @@ -63,7 +62,7 @@ impl frame_system::Config for Runtime { type Hashing = BlakeTwo256; type Header = Header; type Lookup = IdentityLookup; - type BlockHashCount = BlockHashCount; + type BlockHashCount = ConstU64<2400>; type Version = Version; type AccountData = (); type Origin = Origin; diff --git a/frame/treasury/src/tests.rs b/frame/treasury/src/tests.rs index a67bbe9b1d1e9..b755db29682aa 100644 --- a/frame/treasury/src/tests.rs +++ b/frame/treasury/src/tests.rs @@ -101,9 +101,6 @@ parameter_types! { pub const ProposalBond: Permill = Permill::from_percent(5); pub const Burn: Permill = Permill::from_percent(50); pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry"); - pub const BountyUpdatePeriod: u32 = 20; - pub const BountyCuratorDeposit: Permill = Permill::from_percent(50); - pub const BountyValueMinimum: u64 = 1; pub const MaxApprovals: u32 = 100; } impl Config for Test { @@ -121,7 +118,7 @@ impl Config for Test { type BurnDestination = (); // Just gets burned. type WeightInfo = (); type SpendFunds = (); - type MaxApprovals = MaxApprovals; + type MaxApprovals = ConstU32<100>; } pub fn new_test_ext() -> sp_io::TestExternalities { diff --git a/frame/whitelist/src/mock.rs b/frame/whitelist/src/mock.rs index 3009a6f6b5d58..634db53a09a4e 100644 --- a/frame/whitelist/src/mock.rs +++ b/frame/whitelist/src/mock.rs @@ -22,7 +22,7 @@ use crate as pallet_whitelist; use frame_support::{ - parameter_types, + construct_runtime, parameter_types, traits::{ConstU32, ConstU64, Nothing}, }; use frame_system::EnsureRoot; @@ -36,7 +36,7 @@ use sp_runtime::{ type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; -frame_support::construct_runtime!( +construct_runtime!( pub enum Test where Block = Block, NodeBlock = Block, @@ -49,7 +49,7 @@ frame_support::construct_runtime!( } ); -frame_support::parameter_types! { +parameter_types! { pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::simple_max(1024); } @@ -92,16 +92,11 @@ impl pallet_balances::Config for Test { type WeightInfo = (); } -parameter_types! { - // Taken from Polkadot as reference. - pub const PreimageMaxSize: u32 = 4096 * 1024; -} - impl pallet_preimage::Config for Test { type Event = Event; type Currency = Balances; type ManagerOrigin = EnsureRoot; - type MaxSize = PreimageMaxSize; + type MaxSize = ConstU32<{ 4096 * 1024 }>; // PreimageMaxSize Taken from Polkadot as reference. type BaseDeposit = ConstU64<1>; type ByteDeposit = ConstU64<1>; type WeightInfo = (); diff --git a/test-utils/runtime/src/lib.rs b/test-utils/runtime/src/lib.rs index 743652a0ee899..94325ae9c1ab1 100644 --- a/test-utils/runtime/src/lib.rs +++ b/test-utils/runtime/src/lib.rs @@ -553,7 +553,6 @@ impl frame_support::traits::PalletInfo for Runtime { } parameter_types! { - pub const BlockHashCount: BlockNumber = 2400; pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight { read: 100, write: 1000, @@ -578,7 +577,7 @@ impl frame_system::Config for Runtime { type Lookup = IdentityLookup; type Header = Header; type Event = Event; - type BlockHashCount = BlockHashCount; + type BlockHashCount = ConstU64<2400>; type DbWeight = (); type Version = (); type PalletInfo = Self; From bb8152d239565d95af63695ab78c74b2e651b534 Mon Sep 17 00:00:00 2001 From: Qinxuan Chen Date: Fri, 29 Apr 2022 16:28:17 +0800 Subject: [PATCH 167/484] Remove deprecated RawEvent types from some pallets (#11316) Signed-off-by: koushiro --- frame/atomic-swap/src/lib.rs | 4 ---- frame/balances/src/lib.rs | 4 ---- frame/collective/src/lib.rs | 4 ---- frame/elections-phragmen/src/lib.rs | 3 --- frame/grandpa/src/lib.rs | 3 --- frame/indices/src/lib.rs | 4 ---- frame/membership/src/lib.rs | 4 ---- frame/proxy/src/lib.rs | 4 ---- frame/session/src/lib.rs | 4 ---- frame/society/src/lib.rs | 4 ---- frame/system/src/lib.rs | 4 ---- frame/tips/src/lib.rs | 4 ---- frame/treasury/src/lib.rs | 4 ---- 13 files changed, 50 deletions(-) diff --git a/frame/atomic-swap/src/lib.rs b/frame/atomic-swap/src/lib.rs index e57496954a574..40429c226e649 100644 --- a/frame/atomic-swap/src/lib.rs +++ b/frame/atomic-swap/src/lib.rs @@ -232,10 +232,6 @@ pub mod pallet { SwapCancelled { account: T::AccountId, proof: HashedProof }, } - /// Old name generated by `decl_event`. - #[deprecated(note = "use `Event` instead")] - pub type RawEvent = Event; - #[pallet::call] impl Pallet { /// Register a new atomic swap, declaring an intention to send funds from origin to target diff --git a/frame/balances/src/lib.rs b/frame/balances/src/lib.rs index 0eab933f7757d..5ba6c4dfa1f69 100644 --- a/frame/balances/src/lib.rs +++ b/frame/balances/src/lib.rs @@ -466,10 +466,6 @@ pub mod pallet { Slashed { who: T::AccountId, amount: T::Balance }, } - /// Old name generated by `decl_event`. - #[deprecated(note = "use `Event` instead")] - pub type RawEvent = Event; - #[pallet::error] pub enum Error { /// Vesting balance too high to send value diff --git a/frame/collective/src/lib.rs b/frame/collective/src/lib.rs index e876343ec33da..817e4e176bd4c 100644 --- a/frame/collective/src/lib.rs +++ b/frame/collective/src/lib.rs @@ -306,10 +306,6 @@ pub mod pallet { Closed { proposal_hash: T::Hash, yes: MemberCount, no: MemberCount }, } - /// Old name generated by `decl_event`. - #[deprecated(note = "use `Event` instead")] - pub type RawEvent = Event; - #[pallet::error] pub enum Error { /// Account is not a member diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 4758c793cfefd..a59da9c20f809 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -569,9 +569,6 @@ pub mod pallet { }, } - #[deprecated(note = "use `Event` instead")] - pub type RawEvent = Event; - #[pallet::error] pub enum Error { /// Cannot vote when no candidates or members exist. diff --git a/frame/grandpa/src/lib.rs b/frame/grandpa/src/lib.rs index e30d65acbc6a6..318f2f40ea4d9 100644 --- a/frame/grandpa/src/lib.rs +++ b/frame/grandpa/src/lib.rs @@ -262,9 +262,6 @@ pub mod pallet { Resumed, } - #[deprecated(note = "use `Event` instead")] - pub type RawEvent = Event; - #[pallet::error] pub enum Error { /// Attempt to signal GRANDPA pause when the authority set isn't live diff --git a/frame/indices/src/lib.rs b/frame/indices/src/lib.rs index 9c9e3580f2c04..db983a802cadf 100644 --- a/frame/indices/src/lib.rs +++ b/frame/indices/src/lib.rs @@ -270,10 +270,6 @@ pub mod pallet { IndexFrozen { index: T::AccountIndex, who: T::AccountId }, } - /// Old name generated by `decl_event`. - #[deprecated(note = "use `Event` instead")] - pub type RawEvent = Event; - #[pallet::error] pub enum Error { /// The index was not already assigned. diff --git a/frame/membership/src/lib.rs b/frame/membership/src/lib.rs index e8256fab83af2..5a9c0a461cdac 100644 --- a/frame/membership/src/lib.rs +++ b/frame/membership/src/lib.rs @@ -147,10 +147,6 @@ pub mod pallet { Dummy { _phantom_data: PhantomData<(T::AccountId, >::Event)> }, } - /// Old name generated by `decl_event`. - #[deprecated(note = "use `Event` instead")] - pub type RawEvent = Event; - #[pallet::error] pub enum Error { /// Already a member. diff --git a/frame/proxy/src/lib.rs b/frame/proxy/src/lib.rs index 891315c92aa39..1bab3626fb5a9 100644 --- a/frame/proxy/src/lib.rs +++ b/frame/proxy/src/lib.rs @@ -579,10 +579,6 @@ pub mod pallet { }, } - /// Old name generated by `decl_event`. - #[deprecated(note = "use `Event` instead")] - pub type RawEvent = Event; - #[pallet::error] pub enum Error { /// There are too many proxies registered or too many announcements pending. diff --git a/frame/session/src/lib.rs b/frame/session/src/lib.rs index 4cf793a9b4739..d6ecd17a8f2ae 100644 --- a/frame/session/src/lib.rs +++ b/frame/session/src/lib.rs @@ -547,10 +547,6 @@ pub mod pallet { NewSession { session_index: SessionIndex }, } - /// Old name generated by `decl_event`. - #[deprecated(note = "use `Event` instead")] - pub type RawEvent = Event; - /// Error for the session pallet. #[pallet::error] pub enum Error { diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index a9f83094fd49c..6ad63d2102c53 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -513,10 +513,6 @@ pub mod pallet { Deposit { value: BalanceOf }, } - /// Old name generated by `decl_event`. - #[deprecated(note = "use `Event` instead")] - pub type RawEvent = Event; - /// The first member. #[pallet::storage] #[pallet::getter(fn founder)] diff --git a/frame/system/src/lib.rs b/frame/system/src/lib.rs index 0eefc23a6b5d6..14e67cfcd8156 100644 --- a/frame/system/src/lib.rs +++ b/frame/system/src/lib.rs @@ -509,10 +509,6 @@ pub mod pallet { Remarked { sender: T::AccountId, hash: T::Hash }, } - /// Old name generated by `decl_event`. - #[deprecated(note = "use `Event` instead")] - pub type RawEvent = Event; - /// Error for the System pallet #[pallet::error] pub enum Error { diff --git a/frame/tips/src/lib.rs b/frame/tips/src/lib.rs index ae320629dfc34..4b7d78b90be38 100644 --- a/frame/tips/src/lib.rs +++ b/frame/tips/src/lib.rs @@ -195,10 +195,6 @@ pub mod pallet { TipSlashed { tip_hash: T::Hash, finder: T::AccountId, deposit: BalanceOf }, } - /// Old name generated by `decl_event`. - #[deprecated(note = "use `Event` instead")] - pub type RawEvent = Event; - #[pallet::error] pub enum Error { /// The reason given is just too big. diff --git a/frame/treasury/src/lib.rs b/frame/treasury/src/lib.rs index 3f76ddb639c5b..d080d346ba3de 100644 --- a/frame/treasury/src/lib.rs +++ b/frame/treasury/src/lib.rs @@ -277,10 +277,6 @@ pub mod pallet { Deposit { value: BalanceOf }, } - /// Old name generated by `decl_event`. - #[deprecated(note = "use `Event` instead")] - pub type RawEvent = Event; - /// Error for the treasury pallet. #[pallet::error] pub enum Error { From 5c87d3db7a467ed6a2dc8502fea3beeea5834237 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Apr 2022 12:49:05 +0400 Subject: [PATCH 168/484] Upgrade to libp2p 0.44.0 (#11009) * Update libp2p to 0.43.0, lru to 0.7.3 * Fix websoket Incoming::Data * Rename ProtocolsHandler -> ConnectionHandler, remove inject_dis/connected, minor fixes * Fix args for inject_connection* callbacks * Fix DialPeer/DialAddress * Fix debug fmt * Add Endpoint to NetworkState * Fix Kad::get_record by key * Fix Sha2_256::digest * Fix IntoConnectionHandler * Fix borrowchk * Fix DialError::WrongPeerId * Remove NodeHandlerWrapperError * Fix KademliaEvent variants * Fix impl Add for String * Fix tabs in network_state * Apply cargo fmt * Fix a typo in req/resp * Fix tests * Fix peer_info:entry.info_expire * Fix PeerInfoBehaviour inject_address_change and inject_connection_closed * Patch libp2p to 0.44.0#6cc3b4e * Fix inject_connection_closed kad, req/resp * Apply cargo fmt * Use libp2p from crates.io * Fix review notes --- Cargo.lock | 711 ++++++++++++------ client/authority-discovery/Cargo.toml | 2 +- client/authority-discovery/src/worker.rs | 4 +- client/cli/Cargo.toml | 2 +- client/consensus/common/Cargo.toml | 2 +- client/network-gossip/Cargo.toml | 2 +- client/network/Cargo.toml | 6 +- client/network/src/behaviour.rs | 8 +- client/network/src/bitswap.rs | 10 +- client/network/src/discovery.rs | 81 +- client/network/src/network_state.rs | 29 +- client/network/src/peer_info.rs | 120 +-- client/network/src/protocol.rs | 49 +- .../src/protocol/notifications/behaviour.rs | 40 +- .../src/protocol/notifications/handler.rs | 42 +- .../src/protocol/notifications/tests.rs | 38 +- client/network/src/request_responses.rs | 86 +-- client/network/src/service.rs | 48 +- client/network/test/Cargo.toml | 2 +- client/peerset/Cargo.toml | 2 +- client/telemetry/Cargo.toml | 2 +- client/telemetry/src/transport.rs | 9 +- primitives/core/Cargo.toml | 2 +- .../benchmarking-cli/src/pallet/writer.rs | 2 +- 24 files changed, 758 insertions(+), 541 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 16e048f774230..7a5ef8e09ae7b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -218,20 +218,19 @@ dependencies = [ [[package]] name = "async-io" -version = "1.3.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9315f8f07556761c3e48fec2e6b276004acf426e6dc068b2c2251854d65ee0fd" +checksum = "a811e6a479f2439f0c04038796b5cfb3d2ad56c230e0f2d3f7b04d68cfee607b" dependencies = [ "concurrent-queue", - "fastrand", "futures-lite", "libc", "log 0.4.16", - "nb-connect", "once_cell", "parking", "polling", - "vec-arena", + "slab", + "socket2 0.4.4", "waker-fn", "winapi 0.3.9", ] @@ -254,6 +253,23 @@ dependencies = [ "event-listener", ] +[[package]] +name = "async-process" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83137067e3a2a6a06d67168e49e68a0957d215410473a740cea95a2425c0b7c6" +dependencies = [ + "async-io", + "blocking", + "cfg-if 1.0.0", + "event-listener", + "futures-lite", + "libc", + "once_cell", + "signal-hook", + "winapi 0.3.9", +] + [[package]] name = "async-std" version = "1.11.0" @@ -265,6 +281,7 @@ dependencies = [ "async-global-executor", "async-io", "async-lock", + "async-process", "crossbeam-utils 0.8.5", "futures-channel", "futures-core", @@ -284,15 +301,16 @@ dependencies = [ [[package]] name = "async-std-resolver" -version = "0.20.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665c56111e244fe38e7708ee10948a4356ad6a548997c21f5a63a0f4e0edc4d" +checksum = "0f2f8a4a203be3325981310ab243a28e6e4ea55b6519bffce05d41ab60e09ad8" dependencies = [ "async-std", "async-trait", "futures-io", "futures-util", "pin-utils", + "socket2 0.4.4", "trust-dns-resolver", ] @@ -612,17 +630,6 @@ dependencies = [ "wyz", ] -[[package]] -name = "blake2" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a5720225ef5daecf08657f23791354e1685a8c91a4c60c7f3d3b2892f978f4" -dependencies = [ - "crypto-mac 0.8.0", - "digest 0.9.0", - "opaque-debug 0.3.0", -] - [[package]] name = "blake2" version = "0.10.2" @@ -916,21 +923,21 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chacha20" -version = "0.7.1" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fee7ad89dc1128635074c268ee661f90c3f7e83d9fd12910608c36b47d6c3412" +checksum = "01b72a433d0cf2aef113ba70f62634c56fddb0f244e6377185c56a7cadbd8f91" dependencies = [ "cfg-if 1.0.0", "cipher", - "cpufeatures 0.1.5", + "cpufeatures 0.2.1", "zeroize", ] [[package]] name = "chacha20poly1305" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1580317203210c517b6d44794abfbe600698276db18127e37ad3e69bf5e848e5" +checksum = "3b84ed6d1d5f7aa9bdde921a5090e0ca4d934d250ea3b402a5fab3a994e28a2a" dependencies = [ "aead", "chacha20", @@ -1094,9 +1101,9 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" [[package]] name = "core-foundation" -version = "0.9.1" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" dependencies = [ "core-foundation-sys", "libc", @@ -1104,9 +1111,18 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "core2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +dependencies = [ + "memchr", +] [[package]] name = "cpp_demangle" @@ -1528,6 +1544,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "curve25519-dalek" +version = "4.0.0-pre.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4033478fbf70d6acf2655ac70da91ee65852d69daf7a67bf7a2f518fb47aafcf" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.6.2", + "subtle", + "zeroize", +] + [[package]] name = "darling" version = "0.13.0" @@ -1734,9 +1763,9 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] name = "dtoa" -version = "0.4.8" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" +checksum = "5caaa75cbd2b960ff1e5392d2cfb1f44717fffe12fc1f32b7b5d1267f99732a6" [[package]] name = "dyn-clonable" @@ -1858,11 +1887,11 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" [[package]] name = "enum-as-inner" -version = "0.3.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5f0096a91d210159eceb2ff5e1c4da18388a170e1e3ce948aac9c8fdbbf595" +checksum = "21cdad81446a7f7dc43f6a77409efeb9733d2fa65553efef6018ef257c959b73" dependencies = [ - "heck 0.3.2", + "heck 0.4.0", "proc-macro2", "quote", "syn", @@ -2598,13 +2627,13 @@ dependencies = [ [[package]] name = "futures-rustls" -version = "0.21.1" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1387e07917c711fb4ee4f48ea0adb04a3c9739e53ef85bf43ae1edc2937a8b" +checksum = "e01fe9932a224b72b45336d96040aa86386d674a31d0af27d800ea7bc8ca97fe" dependencies = [ "futures-io", - "rustls 0.19.1", - "webpki 0.21.4", + "rustls 0.20.2", + "webpki 0.22.0", ] [[package]] @@ -3103,9 +3132,9 @@ dependencies = [ [[package]] name = "idna" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89829a5d69c23d348314a7ac337fe39173b61149a9864deabd260983aed48c21" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" dependencies = [ "matches", "unicode-bidi", @@ -3114,39 +3143,30 @@ dependencies = [ [[package]] name = "if-addrs" -version = "0.6.5" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28538916eb3f3976311f5dfbe67b5362d0add1293d0a9cad17debf86f8e3aa48" +checksum = "cbc0fa01ffc752e9dbc72818cdb072cd028b86be5e09dd04c5a643704fe101a9" dependencies = [ - "if-addrs-sys", "libc", "winapi 0.3.9", ] -[[package]] -name = "if-addrs-sys" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de74b9dd780476e837e5eb5ab7c88b49ed304126e412030a0adba99c8efe79ea" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "if-watch" -version = "0.2.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6d52908d4ea4ab2bc22474ba149bf1011c8e2c3ebc1ff593ae28ac44f494b6" +checksum = "ae8f4a3c3d4c89351ca83e120c1c00b27df945d38e05695668c9d4b4f7bc52f3" dependencies = [ "async-io", + "core-foundation", + "fnv", "futures 0.3.21", - "futures-lite", "if-addrs", "ipnet", - "libc", "log 0.4.16", - "winapi 0.3.9", + "rtnetlink", + "system-configuration", + "windows", ] [[package]] @@ -3191,9 +3211,9 @@ dependencies = [ [[package]] name = "instant" -version = "0.1.9" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if 1.0.0", ] @@ -3230,11 +3250,11 @@ checksum = "aa2f047c0a98b2f299aa5d6d7088443570faae494e9ae1305e48be000c9e0eb1" [[package]] name = "ipconfig" -version = "0.2.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7" +checksum = "723519edce41262b05d4143ceb95050e4c614f483e78e9fd9e39a8275a84ad98" dependencies = [ - "socket2 0.3.19", + "socket2 0.4.4", "widestring", "winapi 0.3.9", "winreg", @@ -3242,9 +3262,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.3.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135" +checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" [[package]] name = "itertools" @@ -3451,7 +3471,7 @@ dependencies = [ "tokio-rustls 0.23.2", "tokio-util 0.7.1", "tracing", - "webpki-roots 0.22.2", + "webpki-roots", ] [[package]] @@ -3671,14 +3691,18 @@ checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" [[package]] name = "libp2p" -version = "0.40.0" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bec54343492ba5940a6c555e512c6721139835d28c59bc22febece72dfd0d9d" +checksum = "475ce2ac4a9727e53a519f6ee05b38abfcba8f0d39c4d24f103d184e36fd5b0f" dependencies = [ "atomic", "bytes 1.1.0", "futures 0.3.21", + "futures-timer", + "getrandom 0.2.3", + "instant", "lazy_static", + "libp2p-autonat", "libp2p-core", "libp2p-deflate", "libp2p-dns", @@ -3704,17 +3728,36 @@ dependencies = [ "libp2p-websocket", "libp2p-yamux", "multiaddr", - "parking_lot 0.11.2", + "parking_lot 0.12.0", "pin-project 1.0.10", + "rand 0.7.3", "smallvec 1.8.0", - "wasm-timer", +] + +[[package]] +name = "libp2p-autonat" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13b690e65046af6a09c0b27bd9508fa1cab0efce889de74b0b643b9d2a98f9a" +dependencies = [ + "async-trait", + "futures 0.3.21", + "futures-timer", + "instant", + "libp2p-core", + "libp2p-request-response", + "libp2p-swarm", + "log 0.4.16", + "prost", + "prost-build", + "rand 0.8.4", ] [[package]] name = "libp2p-core" -version = "0.30.0" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef22d9bba1e8bcb7ec300073e6802943fe8abb8190431842262b5f1c30abba1" +checksum = "db5b02602099fb75cb2d16f9ea860a320d6eb82ce41e95ab680912c454805cd5" dependencies = [ "asn1_der", "bs58", @@ -3723,32 +3766,33 @@ dependencies = [ "fnv", "futures 0.3.21", "futures-timer", + "instant", "lazy_static", "libsecp256k1", "log 0.4.16", "multiaddr", - "multihash 0.14.0", + "multihash 0.16.2", "multistream-select", - "parking_lot 0.11.2", + "parking_lot 0.12.0", "pin-project 1.0.10", "prost", "prost-build", "rand 0.8.4", "ring", "rw-stream-sink", - "sha2 0.9.8", + "sha2 0.10.2", "smallvec 1.8.0", "thiserror", - "unsigned-varint 0.7.0", + "unsigned-varint 0.7.1", "void", "zeroize", ] [[package]] name = "libp2p-deflate" -version = "0.30.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51a800adb195f33de63f4b17b63fe64cfc23bf2c6a0d3d0d5321328664e65197" +checksum = "6b1d37f042f748e224f04785d0e987ae09a2aa518d6401d82d412dad83e360ed" dependencies = [ "flate2", "futures 0.3.21", @@ -3757,9 +3801,9 @@ dependencies = [ [[package]] name = "libp2p-dns" -version = "0.30.0" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb8f89d15cb6e3c5bc22afff7513b11bab7856f2872d3cfba86f7f63a06bc498" +checksum = "066e33e854e10b5c93fc650458bf2179c7e0d143db260b0963e44a94859817f1" dependencies = [ "async-std-resolver", "futures 0.3.21", @@ -3771,9 +3815,9 @@ dependencies = [ [[package]] name = "libp2p-floodsub" -version = "0.31.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aab3d7210901ea51b7bae2b581aa34521797af8c4ec738c980bda4a06434067f" +checksum = "733d3ea6ebe7a7a85df2bc86678b93f24b015fae5fe3b3acc4c400e795a55d2d" dependencies = [ "cuckoofilter", "fnv", @@ -3789,9 +3833,9 @@ dependencies = [ [[package]] name = "libp2p-gossipsub" -version = "0.33.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfeead619eb5dac46e65acc78c535a60aaec803d1428cca6407c3a4fc74d698d" +checksum = "a90c989a7c0969c2ab63e898da9bc735e3be53fb4f376e9c045ce516bcc9f928" dependencies = [ "asynchronous-codec 0.6.0", "base64 0.13.0", @@ -3800,41 +3844,43 @@ dependencies = [ "fnv", "futures 0.3.21", "hex_fmt", + "instant", "libp2p-core", "libp2p-swarm", "log 0.4.16", + "prometheus-client", "prost", "prost-build", "rand 0.7.3", "regex", - "sha2 0.9.8", + "sha2 0.10.2", "smallvec 1.8.0", - "unsigned-varint 0.7.0", + "unsigned-varint 0.7.1", "wasm-timer", ] [[package]] name = "libp2p-identify" -version = "0.31.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cca1275574183f288ff8b72d535d5ffa5ea9292ef7829af8b47dcb197c7b0dcd" +checksum = "c5ef5a5b57904c7c33d6713ef918d239dc6b7553458f3475d87f8a18e9c651c8" dependencies = [ "futures 0.3.21", + "futures-timer", "libp2p-core", "libp2p-swarm", "log 0.4.16", - "lru 0.6.6", + "lru", "prost", "prost-build", "smallvec 1.8.0", - "wasm-timer", ] [[package]] name = "libp2p-kad" -version = "0.32.0" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2297dc0ca285f3a09d1368bde02449e539b46f94d32d53233f53f6625bcd3ba" +checksum = "564e6bd64d177446399ed835b9451a8825b07929d6daa6a94e6405592974725e" dependencies = [ "arrayvec 0.5.2", "asynchronous-codec 0.6.0", @@ -3842,25 +3888,27 @@ dependencies = [ "either", "fnv", "futures 0.3.21", + "futures-timer", + "instant", "libp2p-core", "libp2p-swarm", "log 0.4.16", "prost", "prost-build", "rand 0.7.3", - "sha2 0.9.8", + "sha2 0.10.2", "smallvec 1.8.0", + "thiserror", "uint", - "unsigned-varint 0.7.0", + "unsigned-varint 0.7.1", "void", - "wasm-timer", ] [[package]] name = "libp2p-mdns" -version = "0.32.0" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c864b64bdc8a84ff3910a0df88e6535f256191a450870f1e7e10cbf8e64d45" +checksum = "611ae873c8e280ccfab0d57c7a13cac5644f364529e233114ff07863946058b0" dependencies = [ "async-io", "data-encoding", @@ -3879,23 +3927,25 @@ dependencies = [ [[package]] name = "libp2p-metrics" -version = "0.1.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4af432fcdd2f8ba4579b846489f8f0812cfd738ced2c0af39df9b1c48bbb6ab2" +checksum = "985be799bb3796e0c136c768208c3c06604a38430571906a13dcfeda225a3b9d" dependencies = [ "libp2p-core", + "libp2p-gossipsub", "libp2p-identify", "libp2p-kad", "libp2p-ping", + "libp2p-relay", "libp2p-swarm", - "open-metrics-client", + "prometheus-client", ] [[package]] name = "libp2p-mplex" -version = "0.30.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2cd64ef597f40e14bfce0497f50ecb63dd6d201c61796daeb4227078834fbf" +checksum = "442eb0c9fff0bf22a34f015724b4143ce01877e079ed0963c722d94c07c72160" dependencies = [ "asynchronous-codec 0.6.0", "bytes 1.1.0", @@ -3903,17 +3953,17 @@ dependencies = [ "libp2p-core", "log 0.4.16", "nohash-hasher", - "parking_lot 0.11.2", + "parking_lot 0.12.0", "rand 0.7.3", "smallvec 1.8.0", - "unsigned-varint 0.7.0", + "unsigned-varint 0.7.1", ] [[package]] name = "libp2p-noise" -version = "0.33.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8772c7a99088221bb7ca9c5c0574bf55046a7ab4c319f3619b275f28c8fb87a" +checksum = "9dd7e0c94051cda67123be68cf6b65211ba3dde7277be9068412de3e7ffd63ef" dependencies = [ "bytes 1.1.0", "curve25519-dalek 3.0.2", @@ -3924,7 +3974,7 @@ dependencies = [ "prost", "prost-build", "rand 0.8.4", - "sha2 0.9.8", + "sha2 0.10.2", "snow", "static_assertions", "x25519-dalek", @@ -3933,24 +3983,25 @@ dependencies = [ [[package]] name = "libp2p-ping" -version = "0.31.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80ef7b0ec5cf06530d9eb6cf59ae49d46a2c45663bde31c25a12f682664adbcf" +checksum = "bf57a3c2e821331dda9fe612d4654d676ab6e33d18d9434a18cced72630df6ad" dependencies = [ "futures 0.3.21", + "futures-timer", + "instant", "libp2p-core", "libp2p-swarm", "log 0.4.16", "rand 0.7.3", "void", - "wasm-timer", ] [[package]] name = "libp2p-plaintext" -version = "0.30.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fba1a6ff33e4a274c89a3b1d78b9f34f32af13265cc5c46c16938262d4e945a" +checksum = "962c0fb0e7212fb96a69b87f2d09bcefd317935239bdc79cda900e7a8897a3fe" dependencies = [ "asynchronous-codec 0.6.0", "bytes 1.1.0", @@ -3959,7 +4010,7 @@ dependencies = [ "log 0.4.16", "prost", "prost-build", - "unsigned-varint 0.7.0", + "unsigned-varint 0.7.1", "void", ] @@ -3979,89 +4030,96 @@ dependencies = [ [[package]] name = "libp2p-relay" -version = "0.4.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2852b61c90fa8ce3c8fcc2aba76e6cefc20d648f9df29157d6b3a916278ef3e3" +checksum = "3aa754cb7bccef51ebc3c458c6bbcef89d83b578a9925438389be841527d408f" dependencies = [ "asynchronous-codec 0.6.0", "bytes 1.1.0", + "either", "futures 0.3.21", "futures-timer", + "instant", "libp2p-core", "libp2p-swarm", "log 0.4.16", "pin-project 1.0.10", "prost", "prost-build", - "rand 0.7.3", + "rand 0.8.4", "smallvec 1.8.0", - "unsigned-varint 0.7.0", + "static_assertions", + "thiserror", + "unsigned-varint 0.7.1", "void", - "wasm-timer", ] [[package]] name = "libp2p-rendezvous" -version = "0.1.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14a6d2b9e7677eff61dc3d2854876aaf3976d84a01ef6664b610c77a0c9407c5" +checksum = "bbd0baab894c5b84da510b915d53264d566c3c35889f09931fe9edbd2a773bee" dependencies = [ "asynchronous-codec 0.6.0", "bimap", "futures 0.3.21", + "futures-timer", + "instant", "libp2p-core", "libp2p-swarm", "log 0.4.16", "prost", "prost-build", "rand 0.8.4", - "sha2 0.9.8", + "sha2 0.10.2", "thiserror", - "unsigned-varint 0.7.0", + "unsigned-varint 0.7.1", "void", - "wasm-timer", ] [[package]] name = "libp2p-request-response" -version = "0.13.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a877a4ced6d46bf84677e1974e8cf61fb434af73b2e96fb48d6cb6223a4634d8" +checksum = "b5e6a6fc6c9ad95661f46989473b34bd2993d14a4de497ff3b2668a910d4b869" dependencies = [ "async-trait", "bytes 1.1.0", "futures 0.3.21", + "instant", "libp2p-core", "libp2p-swarm", "log 0.4.16", - "lru 0.7.5", "rand 0.7.3", "smallvec 1.8.0", - "unsigned-varint 0.7.0", - "wasm-timer", + "unsigned-varint 0.7.1", ] [[package]] name = "libp2p-swarm" -version = "0.31.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f5184a508f223bc100a12665517773fb8730e9f36fc09eefb670bf01b107ae9" +checksum = "8f0c69ad9e8f7c5fc50ad5ad9c7c8b57f33716532a2b623197f69f93e374d14c" dependencies = [ "either", + "fnv", "futures 0.3.21", + "futures-timer", + "instant", "libp2p-core", "log 0.4.16", + "pin-project 1.0.10", "rand 0.7.3", "smallvec 1.8.0", + "thiserror", "void", - "wasm-timer", ] [[package]] name = "libp2p-swarm-derive" -version = "0.25.0" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "072c290f727d39bdc4e9d6d1c847978693d25a673bd757813681e33e5f6c00c2" +checksum = "daf2fe8c80b43561355f4d51875273b5b6dfbac37952e8f64b1270769305c9d7" dependencies = [ "quote", "syn", @@ -4069,9 +4127,9 @@ dependencies = [ [[package]] name = "libp2p-tcp" -version = "0.30.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7399c5b6361ef525d41c11fcf51635724f832baf5819b30d3d873eabb4fbae4b" +checksum = "193447aa729c85aac2376828df76d171c1a589c9e6b58fcc7f9d9a020734122c" dependencies = [ "async-io", "futures 0.3.21", @@ -4086,9 +4144,9 @@ dependencies = [ [[package]] name = "libp2p-uds" -version = "0.30.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8b7563e46218165dfd60f64b96f7ce84590d75f53ecbdc74a7dd01450dc5973" +checksum = "24bdab114f7f2701757d6541266e1131b429bbae382008f207f2114ee4222dcb" dependencies = [ "async-std", "futures 0.3.21", @@ -4098,9 +4156,9 @@ dependencies = [ [[package]] name = "libp2p-wasm-ext" -version = "0.30.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1008a302b73c5020251f9708c653f5ed08368e530e247cc9cd2f109ff30042cf" +checksum = "4f6ea0f84a967ef59a16083f222c18115ae2e91db69809dce275df62e101b279" dependencies = [ "futures 0.3.21", "js-sys", @@ -4112,9 +4170,9 @@ dependencies = [ [[package]] name = "libp2p-websocket" -version = "0.31.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e12df82d1ed64969371a9e65ea92b91064658604cc2576c2757f18ead9a1cf" +checksum = "c932834c3754501c368d1bf3d0fb458487a642b90fc25df082a3a2f3d3b32e37" dependencies = [ "either", "futures 0.3.21", @@ -4125,18 +4183,18 @@ dependencies = [ "rw-stream-sink", "soketto", "url 2.2.1", - "webpki-roots 0.21.0", + "webpki-roots", ] [[package]] name = "libp2p-yamux" -version = "0.34.0" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7362abb8867d7187e7e93df17f460d554c997fc5c8ac57dc1259057f6889af" +checksum = "be902ebd89193cd020e89e89107726a38cfc0d16d18f613f4a37d046e92c7517" dependencies = [ "futures 0.3.21", "libp2p-core", - "parking_lot 0.11.2", + "parking_lot 0.12.0", "thiserror", "yamux", ] @@ -4323,15 +4381,6 @@ dependencies = [ "syn", ] -[[package]] -name = "lru" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ea2d928b485416e8908cff2d97d621db22b27f7b3b6729e438bcf42c671ba91" -dependencies = [ - "hashbrown 0.11.2", -] - [[package]] name = "lru" version = "0.7.5" @@ -4592,19 +4641,19 @@ checksum = "0debeb9fcf88823ea64d64e4a815ab1643f33127d995978e099942ce38f25238" [[package]] name = "multiaddr" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48ee4ea82141951ac6379f964f71b20876d43712bea8faf6dd1a375e08a46499" +checksum = "3c580bfdd8803cce319b047d239559a22f809094aaea4ac13902a1fdcfcd4261" dependencies = [ "arrayref", "bs58", "byteorder", "data-encoding", - "multihash 0.14.0", + "multihash 0.16.2", "percent-encoding 2.1.0", "serde", "static_assertions", - "unsigned-varint 0.7.0", + "unsigned-varint 0.7.1", "url 2.2.1", ] @@ -4630,7 +4679,7 @@ dependencies = [ "blake3", "digest 0.9.0", "generic-array 0.14.4", - "multihash-derive", + "multihash-derive 0.7.2", "sha2 0.9.8", "sha3 0.9.1", "unsigned-varint 0.5.1", @@ -4638,15 +4687,15 @@ dependencies = [ [[package]] name = "multihash" -version = "0.14.0" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "752a61cd890ff691b4411423d23816d5866dd5621e4d1c5687a53b94b5a979d8" +checksum = "e3db354f401db558759dfc1e568d010a5d4146f4d3f637be1275ec4a3cf09689" dependencies = [ - "digest 0.9.0", - "generic-array 0.14.4", - "multihash-derive", - "sha2 0.9.8", - "unsigned-varint 0.7.0", + "core2", + "digest 0.10.3", + "multihash-derive 0.8.0", + "sha2 0.10.2", + "unsigned-varint 0.7.1", ] [[package]] @@ -4663,6 +4712,20 @@ dependencies = [ "synstructure", ] +[[package]] +name = "multihash-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc076939022111618a5026d3be019fd8b366e76314538ff9a1b59ffbcbf98bcd" +dependencies = [ + "proc-macro-crate 1.1.3", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", + "synstructure", +] + [[package]] name = "multimap" version = "0.8.2" @@ -4671,16 +4734,16 @@ checksum = "1255076139a83bb467426e7f8d0134968a8118844faa755985e077cf31850333" [[package]] name = "multistream-select" -version = "0.10.2" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d91ec0a2440aaff5f78ec35631a7027d50386c6163aa975f7caa0d5da4b6ff8" +checksum = "363a84be6453a70e63513660f4894ef815daf88e3356bffcda9ca27d810ce83b" dependencies = [ "bytes 1.1.0", "futures 0.3.21", "log 0.4.16", "pin-project 1.0.10", "smallvec 1.8.0", - "unsigned-varint 0.7.0", + "unsigned-varint 0.7.1", ] [[package]] @@ -4740,24 +4803,92 @@ dependencies = [ ] [[package]] -name = "nb-connect" -version = "1.0.3" +name = "net2" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670361df1bc2399ee1ff50406a0d422587dd3bb0da596e1978fe8e05dabddf4f" +checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" dependencies = [ + "cfg-if 0.1.10", "libc", - "socket2 0.3.19", + "winapi 0.3.9", ] [[package]] -name = "net2" -version = "0.2.37" +name = "netlink-packet-core" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" +checksum = "345b8ab5bd4e71a2986663e88c56856699d060e78e152e6e9d7966fcd5491297" dependencies = [ - "cfg-if 0.1.10", + "anyhow", + "byteorder", "libc", - "winapi 0.3.9", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-route" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "733ea73609acfd7fa7ddadfb7bf709b0471668c456ad9513685af543a06342b2" +dependencies = [ + "anyhow", + "bitflags", + "byteorder", + "libc", + "netlink-packet-core", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-utils" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25af9cf0dc55498b7bd94a1508af7a78706aa0ab715a73c5169273e03c84845e" +dependencies = [ + "anyhow", + "byteorder", + "paste 1.0.6", + "thiserror", +] + +[[package]] +name = "netlink-proto" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8785b8141e8432aa45fceb922a7e876d7da3fad37fa7e7ec702ace3aa0826b" +dependencies = [ + "bytes 1.1.0", + "futures 0.3.21", + "log 0.4.16", + "netlink-packet-core", + "netlink-sys", + "tokio", +] + +[[package]] +name = "netlink-sys" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e4c9f9547a08241bee7b6558b9b98e1f290d187de8b7cfca2bbb4937bcaa8f8" +dependencies = [ + "async-io", + "bytes 1.1.0", + "futures 0.3.21", + "libc", + "log 0.4.16", +] + +[[package]] +name = "nix" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf" +dependencies = [ + "bitflags", + "cc", + "cfg-if 1.0.0", + "libc", + "memoffset", ] [[package]] @@ -4825,7 +4956,7 @@ dependencies = [ "futures 0.3.21", "hex-literal", "log 0.4.16", - "nix", + "nix 0.23.1", "node-executor", "node-inspect", "node-primitives", @@ -5360,29 +5491,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" -[[package]] -name = "open-metrics-client" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7337d80c23c2d8b1349563981bc4fb531220733743ba8115454a67b181173f0d" -dependencies = [ - "dtoa", - "itoa 0.4.8", - "open-metrics-client-derive-text-encode", - "owning_ref", -] - -[[package]] -name = "open-metrics-client-derive-text-encode" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15c83b586f00268c619c1cb3340ec1a6f59dd9ba1d9833a273a68e6d5cd8ffc" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "openssl" version = "0.10.35" @@ -7348,6 +7456,29 @@ dependencies = [ "thiserror", ] +[[package]] +name = "prometheus-client" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9a896938cc6018c64f279888b8c7559d3725210d5db9a3a1ee6bc7188d51d34" +dependencies = [ + "dtoa", + "itoa 1.0.1", + "owning_ref", + "prometheus-client-derive-text-encode", +] + +[[package]] +name = "prometheus-client-derive-text-encode" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8e12d01b9d66ad9eb4529c57666b6263fc1993cb30261d83ead658fdd932652" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "prost" version = "0.9.0" @@ -7988,6 +8119,21 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "rtnetlink" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f54290e54521dac3de4149d83ddf9f62a359b3cc93bcb494a794a41e6f4744b" +dependencies = [ + "async-global-executor", + "futures 0.3.21", + "log 0.4.16", + "netlink-packet-route", + "netlink-proto", + "nix 0.22.3", + "thiserror", +] + [[package]] name = "rust-argon2" version = "0.8.3" @@ -8036,6 +8182,15 @@ dependencies = [ "semver 0.11.0", ] +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver 1.0.4", +] + [[package]] name = "rustix" version = "0.33.5" @@ -8625,7 +8780,7 @@ dependencies = [ "env_logger 0.9.0", "hex-literal", "lazy_static", - "lru 0.7.5", + "lru", "parity-scale-codec", "parking_lot 0.12.0", "paste 1.0.6", @@ -8834,7 +8989,7 @@ dependencies = [ "linked-hash-map", "linked_hash_set", "log 0.4.16", - "lru 0.7.5", + "lru", "parity-scale-codec", "parking_lot 0.12.0", "pin-project 1.0.10", @@ -8878,7 +9033,7 @@ dependencies = [ "futures-timer", "libp2p", "log 0.4.16", - "lru 0.7.5", + "lru", "quickcheck", "sc-network", "sp-runtime", @@ -9697,6 +9852,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42a568c8f2cd051a4d283bd6eb0343ac214c1b0f1ac19f93e1175b2dee38c73d" +[[package]] +name = "signal-hook" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef33d6d0cd06e0840fba9985aab098c147e67e05cee14d412d3345ed14ff30ac" +dependencies = [ + "libc", + "signal-hook-registry", +] + [[package]] name = "signal-hook-registry" version = "1.3.0" @@ -9757,20 +9922,19 @@ checksum = "45456094d1983e2ee2a18fdfebce3189fa451699d0502cb8e3b49dba5ba41451" [[package]] name = "snow" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6142f7c25e94f6fd25a32c3348ec230df9109b463f59c8c7acc4bd34936babb7" +checksum = "774d05a3edae07ce6d68ea6984f3c05e9bba8927e3dd591e3b479e5b03213d0d" dependencies = [ "aes-gcm", - "blake2 0.9.1", + "blake2", "chacha20poly1305", - "rand 0.8.4", + "curve25519-dalek 4.0.0-pre.1", "rand_core 0.6.2", "ring", - "rustc_version 0.3.3", - "sha2 0.9.8", + "rustc_version 0.4.0", + "sha2 0.10.2", "subtle", - "x25519-dalek", ] [[package]] @@ -9831,7 +9995,7 @@ dependencies = [ name = "sp-api-proc-macro" version = "4.0.0-dev" dependencies = [ - "blake2 0.10.2", + "blake2", "proc-macro-crate 1.1.3", "proc-macro2", "quote", @@ -9950,7 +10114,7 @@ version = "4.0.0-dev" dependencies = [ "futures 0.3.21", "log 0.4.16", - "lru 0.7.5", + "lru", "parity-scale-codec", "parking_lot 0.12.0", "sp-api", @@ -10108,7 +10272,7 @@ dependencies = [ name = "sp-core-hashing" version = "4.0.0" dependencies = [ - "blake2 0.10.2", + "blake2", "byteorder", "digest 0.10.3", "sha2 0.10.2", @@ -11001,6 +11165,27 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "system-configuration" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75182f12f490e953596550b65ee31bda7c8e043d9386174b353bda50838c3fd" +dependencies = [ + "bitflags", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tap" version = "1.0.1" @@ -11450,7 +11635,7 @@ dependencies = [ "ahash", "lazy_static", "log 0.4.16", - "lru 0.7.5", + "lru", "tracing-core", ] @@ -11549,9 +11734,9 @@ dependencies = [ [[package]] name = "trust-dns-proto" -version = "0.20.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d57e219ba600dd96c2f6d82eb79645068e14edbc5c7e27514af40436b88150c" +checksum = "9c31f240f59877c3d4bb3b3ea0ec5a6a0cff07323580ff8c7a605cd7d08b255d" dependencies = [ "async-trait", "cfg-if 1.0.0", @@ -11560,7 +11745,7 @@ dependencies = [ "futures-channel", "futures-io", "futures-util", - "idna 0.2.2", + "idna 0.2.3", "ipnet", "lazy_static", "log 0.4.16", @@ -11573,9 +11758,9 @@ dependencies = [ [[package]] name = "trust-dns-resolver" -version = "0.20.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0437eea3a6da51acc1e946545ff53d5b8fb2611ff1c3bed58522dde100536ae" +checksum = "e4ba72c2ea84515690c9fcef4c6c660bb9df3036ed1051686de84605b74fd558" dependencies = [ "cfg-if 1.0.0", "futures-util", @@ -11583,7 +11768,7 @@ dependencies = [ "lazy_static", "log 0.4.16", "lru-cache", - "parking_lot 0.11.2", + "parking_lot 0.12.0", "resolv-conf", "smallvec 1.8.0", "thiserror", @@ -11768,9 +11953,9 @@ dependencies = [ [[package]] name = "unsigned-varint" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f8d425fafb8cd76bc3f22aace4af471d3156301d7508f2107e98fbeae10bc7f" +checksum = "d86a8dc7f45e4c1b0d30e43038c38f274e77af056aa5f74b93c2cf9eb3c1c836" dependencies = [ "asynchronous-codec 0.6.0", "bytes 1.1.0", @@ -11802,7 +11987,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ccd964113622c8e9322cfac19eb1004a07e636c545f325da085d5cdde6f1f8b" dependencies = [ "form_urlencoded", - "idna 0.2.2", + "idna 0.2.3", "matches", "percent-encoding 2.1.0", ] @@ -12468,15 +12653,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "webpki-roots" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82015b7e0b8bad8185994674a13a93306bea76cf5a16c5a181382fd3a5ec2376" -dependencies = [ - "webpki 0.21.4", -] - [[package]] name = "webpki-roots" version = "0.22.2" @@ -12548,9 +12724,9 @@ dependencies = [ [[package]] name = "widestring" -version = "0.4.3" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c" +checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983" [[package]] name = "winapi" @@ -12595,43 +12771,86 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac7fef12f4b59cd0a29339406cc9203ab44e440ddff6b3f5a41455349fa9cf3" +dependencies = [ + "windows_aarch64_msvc 0.29.0", + "windows_i686_gnu 0.29.0", + "windows_i686_msvc 0.29.0", + "windows_x86_64_gnu 0.29.0", + "windows_x86_64_msvc 0.29.0", +] + [[package]] name = "windows-sys" version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6" dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", + "windows_aarch64_msvc 0.32.0", + "windows_i686_gnu 0.32.0", + "windows_i686_msvc 0.32.0", + "windows_x86_64_gnu 0.32.0", + "windows_x86_64_msvc 0.32.0", ] +[[package]] +name = "windows_aarch64_msvc" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d027175d00b01e0cbeb97d6ab6ebe03b12330a35786cbaca5252b1c4bf5d9b" + [[package]] name = "windows_aarch64_msvc" version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" +[[package]] +name = "windows_i686_gnu" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8793f59f7b8e8b01eda1a652b2697d87b93097198ae85f823b969ca5b89bba58" + [[package]] name = "windows_i686_gnu" version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" +[[package]] +name = "windows_i686_msvc" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8602f6c418b67024be2996c512f5f995de3ba417f4c75af68401ab8756796ae4" + [[package]] name = "windows_i686_msvc" version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" +[[package]] +name = "windows_x86_64_gnu" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d615f419543e0bd7d2b3323af0d86ff19cbc4f816e6453f36a2c2ce889c354" + [[package]] name = "windows_x86_64_gnu" version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" +[[package]] +name = "windows_x86_64_msvc" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d95421d9ed3672c280884da53201a5c46b7b2765ca6faf34b0d71cf34a3561" + [[package]] name = "windows_x86_64_msvc" version = "0.32.0" @@ -12640,9 +12859,9 @@ checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" [[package]] name = "winreg" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" +checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" dependencies = [ "winapi 0.3.9", ] @@ -12679,23 +12898,23 @@ dependencies = [ [[package]] name = "yamux" -version = "0.9.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7d9028f208dd5e63c614be69f115c1b53cacc1111437d4c765185856666c107" +checksum = "0c0608f53c1dc0bad505d03a34bbd49fbf2ad7b51eb036123e896365532745a1" dependencies = [ "futures 0.3.21", "log 0.4.16", "nohash-hasher", - "parking_lot 0.11.2", + "parking_lot 0.12.0", "rand 0.8.4", "static_assertions", ] [[package]] name = "zeroize" -version = "1.5.4" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb5728b8afd3f280a869ce1d4c554ffaed35f45c231fc41bfbd0381bef50317" +checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619" dependencies = [ "zeroize_derive", ] diff --git a/client/authority-discovery/Cargo.toml b/client/authority-discovery/Cargo.toml index effc8cd715e1d..0e107b19ce6ab 100644 --- a/client/authority-discovery/Cargo.toml +++ b/client/authority-discovery/Cargo.toml @@ -23,7 +23,7 @@ thiserror = "1.0" futures = "0.3.21" futures-timer = "3.0.1" ip_network = "0.4.1" -libp2p = { version = "0.40.0", default-features = false, features = ["kad"] } +libp2p = { version = "0.44.0", default-features = false, features = ["kad"] } log = "0.4.16" prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus", version = "0.10.0-dev" } prost = "0.9" diff --git a/client/authority-discovery/src/worker.rs b/client/authority-discovery/src/worker.rs index 726c032281cd8..0912359501385 100644 --- a/client/authority-discovery/src/worker.rs +++ b/client/authority-discovery/src/worker.rs @@ -37,7 +37,7 @@ use codec::Decode; use ip_network::IpNetwork; use libp2p::{ core::multiaddr, - multihash::{Hasher, Multihash}, + multihash::{Multihash, MultihashDigest}, }; use log::{debug, error, log_enabled}; use prometheus_endpoint::{register, Counter, CounterVec, Gauge, Opts, U64}; @@ -638,7 +638,7 @@ where } fn hash_authority_id(id: &[u8]) -> sc_network::KademliaKey { - sc_network::KademliaKey::new(&libp2p::multihash::Sha2_256::digest(id)) + sc_network::KademliaKey::new(&libp2p::multihash::Code::Sha2_256.digest(id).digest()) } // Makes sure all values are the same and returns it diff --git a/client/cli/Cargo.toml b/client/cli/Cargo.toml index adf88b8c88a2c..0495f522c1ade 100644 --- a/client/cli/Cargo.toml +++ b/client/cli/Cargo.toml @@ -18,7 +18,7 @@ clap = { version = "3.1.6", features = ["derive"] } fdlimit = "0.2.1" futures = "0.3.21" hex = "0.4.2" -libp2p = "0.40.0" +libp2p = "0.44.0" log = "0.4.16" names = { version = "0.13.0", default-features = false } rand = "0.7.3" diff --git a/client/consensus/common/Cargo.toml b/client/consensus/common/Cargo.toml index f4f7626ab6b7e..2846a3e7898c5 100644 --- a/client/consensus/common/Cargo.toml +++ b/client/consensus/common/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] thiserror = "1.0.30" -libp2p = { version = "0.40.0", default-features = false } +libp2p = { version = "0.44.0", default-features = false } log = "0.4.16" futures = { version = "0.3.21", features = ["thread-pool"] } futures-timer = "3.0.1" diff --git a/client/network-gossip/Cargo.toml b/client/network-gossip/Cargo.toml index 2989ff5b51412..79e07b597c65d 100644 --- a/client/network-gossip/Cargo.toml +++ b/client/network-gossip/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] futures = "0.3.21" futures-timer = "3.0.1" -libp2p = { version = "0.40.0", default-features = false } +libp2p = { version = "0.44.0", default-features = false } log = "0.4.16" lru = "0.7.5" ahash = "0.7.6" diff --git a/client/network/Cargo.toml b/client/network/Cargo.toml index 6fe804ce52413..93b389d4361c1 100644 --- a/client/network/Cargo.toml +++ b/client/network/Cargo.toml @@ -61,12 +61,12 @@ unsigned-varint = { version = "0.6.0", features = [ "asynchronous_codec", ] } void = "1.0.2" -zeroize = "1.5.4" -libp2p = "0.40.0" +zeroize = "1.4.3" +libp2p = "0.44.0" [dev-dependencies] assert_matches = "1.3" -libp2p = { version = "0.40.0", default-features = false } +libp2p = { version = "0.44.0", default-features = false } quickcheck = "1.0.3" rand = "0.7.2" sp-test-primitives = { version = "2.0.0", path = "../../primitives/test-primitives" } diff --git a/client/network/src/behaviour.rs b/client/network/src/behaviour.rs index d0de50ef61897..f6a7e9eb87018 100644 --- a/client/network/src/behaviour.rs +++ b/client/network/src/behaviour.rs @@ -33,8 +33,8 @@ use libp2p::{ identify::IdentifyInfo, kad::record, swarm::{ - toggle::Toggle, NetworkBehaviour, NetworkBehaviourAction, NetworkBehaviourEventProcess, - PollParameters, + behaviour::toggle::Toggle, NetworkBehaviour, NetworkBehaviourAction, + NetworkBehaviourEventProcess, PollParameters, }, NetworkBehaviour, }; @@ -304,7 +304,7 @@ impl Behaviour { /// Start querying a record from the DHT. Will later produce either a `ValueFound` or a /// `ValueNotFound` event. - pub fn get_value(&mut self, key: &record::Key) { + pub fn get_value(&mut self, key: record::Key) { self.discovery.get_value(key); } @@ -519,7 +519,7 @@ impl Behaviour { &mut self, _cx: &mut Context, _: &mut impl PollParameters, - ) -> Poll, ::ProtocolsHandler>> + ) -> Poll, ::ConnectionHandler>> { if let Some(event) = self.events.pop_front() { return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)) diff --git a/client/network/src/bitswap.rs b/client/network/src/bitswap.rs index e7c37968b5f99..da80ab8cb3d33 100644 --- a/client/network/src/bitswap.rs +++ b/client/network/src/bitswap.rs @@ -194,10 +194,10 @@ impl Bitswap { } impl NetworkBehaviour for Bitswap { - type ProtocolsHandler = OneShotHandler; + type ConnectionHandler = OneShotHandler; type OutEvent = void::Void; - fn new_handler(&mut self) -> Self::ProtocolsHandler { + fn new_handler(&mut self) -> Self::ConnectionHandler { Default::default() } @@ -205,10 +205,6 @@ impl NetworkBehaviour for Bitswap { Vec::new() } - fn inject_connected(&mut self, _peer: &PeerId) {} - - fn inject_disconnected(&mut self, _peer: &PeerId) {} - fn inject_event(&mut self, peer: PeerId, _connection: ConnectionId, message: HandlerEvent) { let request = match message { HandlerEvent::ResponseSent => return, @@ -300,7 +296,7 @@ impl NetworkBehaviour for Bitswap { &mut self, _ctx: &mut Context, _: &mut impl PollParameters, - ) -> Poll> { + ) -> Poll> { if let Some((peer_id, message)) = self.ready_blocks.pop_front() { return Poll::Ready(NetworkBehaviourAction::NotifyHandler { peer_id, diff --git a/client/network/src/discovery.rs b/client/network/src/discovery.rs index cf75e2dcb4c65..a916ffda0794d 100644 --- a/client/network/src/discovery.rs +++ b/client/network/src/discovery.rs @@ -67,8 +67,8 @@ use libp2p::{ mdns::{Mdns, MdnsConfig, MdnsEvent}, multiaddr::Protocol, swarm::{ - protocols_handler::multi::IntoMultiHandler, DialError, IntoProtocolsHandler, - NetworkBehaviour, NetworkBehaviourAction, PollParameters, ProtocolsHandler, + handler::multi::IntoMultiHandler, ConnectionHandler, DialError, IntoConnectionHandler, + NetworkBehaviour, NetworkBehaviourAction, PollParameters, }, }; use log::{debug, error, info, trace, warn}; @@ -355,9 +355,9 @@ impl DiscoveryBehaviour { /// Start fetching a record from the DHT. /// /// A corresponding `ValueFound` or `ValueNotFound` event will later be generated. - pub fn get_value(&mut self, key: &record::Key) { + pub fn get_value(&mut self, key: record::Key) { for k in self.kademlias.values_mut() { - k.get_record(key, Quorum::One); + k.get_record(key.clone(), Quorum::One); } } @@ -433,7 +433,7 @@ impl DiscoveryBehaviour { &mut self, pid: ProtocolId, handler: KademliaHandlerProto, - ) -> ::ProtocolsHandler { + ) -> ::ConnectionHandler { let mut handlers: HashMap<_, _> = self .kademlias .iter_mut() @@ -498,10 +498,10 @@ pub enum DiscoveryOut { } impl NetworkBehaviour for DiscoveryBehaviour { - type ProtocolsHandler = IntoMultiHandler>; + type ConnectionHandler = IntoMultiHandler>; type OutEvent = DiscoveryOut; - fn new_handler(&mut self) -> Self::ProtocolsHandler { + fn new_handler(&mut self) -> Self::ConnectionHandler { let iter = self .kademlias .iter_mut() @@ -568,6 +568,7 @@ impl NetworkBehaviour for DiscoveryBehaviour { conn: &ConnectionId, endpoint: &ConnectedPoint, failed_addresses: Option<&Vec>, + other_established: usize, ) { self.num_connections += 1; for k in self.kademlias.values_mut() { @@ -577,37 +578,37 @@ impl NetworkBehaviour for DiscoveryBehaviour { conn, endpoint, failed_addresses, + other_established, ) } } - fn inject_connected(&mut self, peer_id: &PeerId) { - for k in self.kademlias.values_mut() { - NetworkBehaviour::inject_connected(k, peer_id) - } - } - fn inject_connection_closed( &mut self, - _peer_id: &PeerId, - _conn: &ConnectionId, - _endpoint: &ConnectedPoint, - _handler: ::Handler, + peer_id: &PeerId, + conn: &ConnectionId, + endpoint: &ConnectedPoint, + handler: ::Handler, + remaining_established: usize, ) { self.num_connections -= 1; - // NetworkBehaviour::inject_connection_closed on Kademlia does nothing. - } - - fn inject_disconnected(&mut self, peer_id: &PeerId) { - for k in self.kademlias.values_mut() { - NetworkBehaviour::inject_disconnected(k, peer_id) + for (pid, event) in handler.into_iter() { + if let Some(kad) = self.kademlias.get_mut(&pid) { + kad.inject_connection_closed(peer_id, conn, endpoint, event, remaining_established) + } else { + error!( + target: "sub-libp2p", + "inject_connection_closed: no kademlia instance registered for protocol {:?}", + pid, + ) + } } } fn inject_dial_failure( &mut self, peer_id: Option, - _: Self::ProtocolsHandler, + _: Self::ConnectionHandler, error: &DialError, ) { if let Some(peer_id) = peer_id { @@ -630,7 +631,7 @@ impl NetworkBehaviour for DiscoveryBehaviour { &mut self, peer_id: PeerId, connection: ConnectionId, - (pid, event): <::Handler as ProtocolsHandler>::OutEvent, + (pid, event): <::Handler as ConnectionHandler>::OutEvent, ) { if let Some(kad) = self.kademlias.get_mut(&pid) { return kad.inject_event(peer_id, connection, event) @@ -689,7 +690,7 @@ impl NetworkBehaviour for DiscoveryBehaviour { } } - fn inject_listen_failure(&mut self, _: &Multiaddr, _: &Multiaddr, _: Self::ProtocolsHandler) { + fn inject_listen_failure(&mut self, _: &Multiaddr, _: &Multiaddr, _: Self::ConnectionHandler) { // NetworkBehaviour::inject_listen_failure on Kademlia does nothing. } @@ -709,7 +710,7 @@ impl NetworkBehaviour for DiscoveryBehaviour { &mut self, cx: &mut Context, params: &mut impl PollParameters, - ) -> Poll> { + ) -> Poll> { // Immediately process the content of `discovered`. if let Some(ev) = self.pending_events.pop_front() { return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) @@ -770,12 +771,8 @@ impl NetworkBehaviour for DiscoveryBehaviour { let ev = DiscoveryOut::Discovered(peer); return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) }, - KademliaEvent::InboundPutRecordRequest { .. } | - KademliaEvent::InboundAddProviderRequest { .. } => { - debug_assert!(false, "We don't use kad filtering at the moment"); - }, KademliaEvent::PendingRoutablePeer { .. } | - KademliaEvent::InboundRequestServed { .. } => { + KademliaEvent::InboundRequest { .. } => { // We are not interested in this event at the moment. }, KademliaEvent::OutboundQueryCompleted { @@ -890,19 +887,10 @@ impl NetworkBehaviour for DiscoveryBehaviour { warn!(target: "sub-libp2p", "Libp2p => Unhandled Kademlia event: {:?}", e) }, }, - NetworkBehaviourAction::DialAddress { address, handler } => { - let pid = pid.clone(); - let handler = self.new_handler_with_replacement(pid, handler); - return Poll::Ready(NetworkBehaviourAction::DialAddress { address, handler }) - }, - NetworkBehaviourAction::DialPeer { peer_id, condition, handler } => { + NetworkBehaviourAction::Dial { opts, handler } => { let pid = pid.clone(); let handler = self.new_handler_with_replacement(pid, handler); - return Poll::Ready(NetworkBehaviourAction::DialPeer { - peer_id, - condition, - handler, - }) + return Poll::Ready(NetworkBehaviourAction::Dial { opts, handler }) }, NetworkBehaviourAction::NotifyHandler { peer_id, handler, event } => return Poll::Ready(NetworkBehaviourAction::NotifyHandler { @@ -941,10 +929,7 @@ impl NetworkBehaviour for DiscoveryBehaviour { }, MdnsEvent::Expired(_) => {}, }, - NetworkBehaviourAction::DialAddress { .. } => { - unreachable!("mDNS never dials!"); - }, - NetworkBehaviourAction::DialPeer { .. } => { + NetworkBehaviourAction::Dial { .. } => { unreachable!("mDNS never dials!"); }, NetworkBehaviourAction::NotifyHandler { event, .. } => match event {}, /* `event` is an enum with no variant */ @@ -995,7 +980,7 @@ impl MdnsWrapper { &mut self, cx: &mut Context<'_>, params: &mut impl PollParameters, - ) -> Poll::ProtocolsHandler>> { + ) -> Poll::ConnectionHandler>> { loop { match self { Self::Instantiating(fut) => diff --git a/client/network/src/network_state.rs b/client/network/src/network_state.rs index a5e2fbef421db..57073c57afa69 100644 --- a/client/network/src/network_state.rs +++ b/client/network/src/network_state.rs @@ -20,7 +20,10 @@ //! //! **Warning**: These APIs are not stable. -use libp2p::{core::ConnectedPoint, Multiaddr}; +use libp2p::{ + core::{ConnectedPoint, Endpoint as CoreEndpoint}, + Multiaddr, +}; use serde::{Deserialize, Serialize}; use std::{ collections::{HashMap, HashSet}, @@ -80,7 +83,7 @@ pub struct NotConnectedPeer { #[serde(rename_all = "camelCase")] pub enum PeerEndpoint { /// We are dialing the given address. - Dialing(Multiaddr), + Dialing(Multiaddr, Endpoint), /// We are listening. Listening { /// Local address of the connection. @@ -90,12 +93,32 @@ pub enum PeerEndpoint { }, } +/// Part of the `NetworkState` struct. Unstable. +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum Endpoint { + /// The socket comes from a dialer. + Dialer, + /// The socket comes from a listener. + Listener, +} + impl From for PeerEndpoint { fn from(endpoint: ConnectedPoint) -> Self { match endpoint { - ConnectedPoint::Dialer { address } => Self::Dialing(address), + ConnectedPoint::Dialer { address, role_override } => + Self::Dialing(address, role_override.into()), ConnectedPoint::Listener { local_addr, send_back_addr } => Self::Listening { local_addr, send_back_addr }, } } } + +impl From for Endpoint { + fn from(endpoint: CoreEndpoint) -> Self { + match endpoint { + CoreEndpoint::Dialer => Self::Dialer, + CoreEndpoint::Listener => Self::Listener, + } + } +} diff --git a/client/network/src/peer_info.rs b/client/network/src/peer_info.rs index 378c258820ffb..c3e79437bdd06 100644 --- a/client/network/src/peer_info.rs +++ b/client/network/src/peer_info.rs @@ -28,8 +28,8 @@ use libp2p::{ identify::{Identify, IdentifyConfig, IdentifyEvent, IdentifyInfo}, ping::{Ping, PingConfig, PingEvent, PingSuccess}, swarm::{ - IntoProtocolsHandler, IntoProtocolsHandlerSelect, NetworkBehaviour, NetworkBehaviourAction, - PollParameters, ProtocolsHandler, + ConnectionHandler, IntoConnectionHandler, IntoConnectionHandlerSelect, NetworkBehaviour, + NetworkBehaviourAction, PollParameters, }, Multiaddr, }; @@ -170,14 +170,14 @@ pub enum PeerInfoEvent { } impl NetworkBehaviour for PeerInfoBehaviour { - type ProtocolsHandler = IntoProtocolsHandlerSelect< - ::ProtocolsHandler, - ::ProtocolsHandler, + type ConnectionHandler = IntoConnectionHandlerSelect< + ::ConnectionHandler, + ::ConnectionHandler, >; type OutEvent = PeerInfoEvent; - fn new_handler(&mut self) -> Self::ProtocolsHandler { - IntoProtocolsHandler::select(self.ping.new_handler(), self.identify.new_handler()) + fn new_handler(&mut self) -> Self::ConnectionHandler { + IntoConnectionHandler::select(self.ping.new_handler(), self.identify.new_handler()) } fn addresses_of_peer(&mut self, peer_id: &PeerId) -> Vec { @@ -195,11 +195,18 @@ impl NetworkBehaviour for PeerInfoBehaviour { ) { self.ping.inject_address_change(peer_id, conn, old, new); self.identify.inject_address_change(peer_id, conn, old, new); - } - fn inject_connected(&mut self, peer_id: &PeerId) { - self.ping.inject_connected(peer_id); - self.identify.inject_connected(peer_id); + if let Some(entry) = self.nodes_info.get_mut(peer_id) { + if let Some(endpoint) = entry.endpoints.iter_mut().find(|e| e == &old) { + *endpoint = new.clone(); + } else { + error!(target: "sub-libp2p", + "Unknown address change for peer {:?} from {:?} to {:?}", peer_id, old, new); + } + } else { + error!(target: "sub-libp2p", + "Unknown peer {:?} to change address from {:?} to {:?}", peer_id, old, new); + } } fn inject_connection_established( @@ -208,11 +215,22 @@ impl NetworkBehaviour for PeerInfoBehaviour { conn: &ConnectionId, endpoint: &ConnectedPoint, failed_addresses: Option<&Vec>, + other_established: usize, ) { - self.ping - .inject_connection_established(peer_id, conn, endpoint, failed_addresses); - self.identify - .inject_connection_established(peer_id, conn, endpoint, failed_addresses); + self.ping.inject_connection_established( + peer_id, + conn, + endpoint, + failed_addresses, + other_established, + ); + self.identify.inject_connection_established( + peer_id, + conn, + endpoint, + failed_addresses, + other_established, + ); match self.nodes_info.entry(*peer_id) { Entry::Vacant(e) => { e.insert(NodeInfo::new(endpoint.clone())); @@ -234,14 +252,29 @@ impl NetworkBehaviour for PeerInfoBehaviour { peer_id: &PeerId, conn: &ConnectionId, endpoint: &ConnectedPoint, - handler: ::Handler, + handler: ::Handler, + remaining_established: usize, ) { let (ping_handler, identity_handler) = handler.into_inner(); - self.identify - .inject_connection_closed(peer_id, conn, endpoint, identity_handler); - self.ping.inject_connection_closed(peer_id, conn, endpoint, ping_handler); + self.identify.inject_connection_closed( + peer_id, + conn, + endpoint, + identity_handler, + remaining_established, + ); + self.ping.inject_connection_closed( + peer_id, + conn, + endpoint, + ping_handler, + remaining_established, + ); if let Some(entry) = self.nodes_info.get_mut(peer_id) { + if remaining_established == 0 { + entry.info_expire = Some(Instant::now() + CACHE_EXPIRE); + } entry.endpoints.retain(|ep| ep != endpoint) } else { error!(target: "sub-libp2p", @@ -249,23 +282,11 @@ impl NetworkBehaviour for PeerInfoBehaviour { } } - fn inject_disconnected(&mut self, peer_id: &PeerId) { - self.ping.inject_disconnected(peer_id); - self.identify.inject_disconnected(peer_id); - - if let Some(entry) = self.nodes_info.get_mut(peer_id) { - entry.info_expire = Some(Instant::now() + CACHE_EXPIRE); - } else { - error!(target: "sub-libp2p", - "Disconnected from node we were not connected to {:?}", peer_id); - } - } - fn inject_event( &mut self, peer_id: PeerId, connection: ConnectionId, - event: <::Handler as ProtocolsHandler>::OutEvent, + event: <::Handler as ConnectionHandler>::OutEvent, ) { match event { EitherOutput::First(event) => self.ping.inject_event(peer_id, connection, event), @@ -276,7 +297,7 @@ impl NetworkBehaviour for PeerInfoBehaviour { fn inject_dial_failure( &mut self, peer_id: Option, - handler: Self::ProtocolsHandler, + handler: Self::ConnectionHandler, error: &libp2p::swarm::DialError, ) { let (ping_handler, identity_handler) = handler.into_inner(); @@ -313,7 +334,7 @@ impl NetworkBehaviour for PeerInfoBehaviour { &mut self, local_addr: &Multiaddr, send_back_addr: &Multiaddr, - handler: Self::ProtocolsHandler, + handler: Self::ConnectionHandler, ) { let (ping_handler, identity_handler) = handler.into_inner(); self.identify @@ -335,7 +356,7 @@ impl NetworkBehaviour for PeerInfoBehaviour { &mut self, cx: &mut Context, params: &mut impl PollParameters, - ) -> Poll> { + ) -> Poll> { loop { match self.ping.poll(cx, params) { Poll::Pending => break, @@ -344,19 +365,10 @@ impl NetworkBehaviour for PeerInfoBehaviour { self.handle_ping_report(&peer, rtt) } }, - Poll::Ready(NetworkBehaviourAction::DialAddress { address, handler }) => { + Poll::Ready(NetworkBehaviourAction::Dial { opts, handler }) => { let handler = - IntoProtocolsHandler::select(handler, self.identify.new_handler()); - return Poll::Ready(NetworkBehaviourAction::DialAddress { address, handler }) - }, - Poll::Ready(NetworkBehaviourAction::DialPeer { peer_id, condition, handler }) => { - let handler = - IntoProtocolsHandler::select(handler, self.identify.new_handler()); - return Poll::Ready(NetworkBehaviourAction::DialPeer { - peer_id, - condition, - handler, - }) + IntoConnectionHandler::select(handler, self.identify.new_handler()); + return Poll::Ready(NetworkBehaviourAction::Dial { opts, handler }) }, Poll::Ready(NetworkBehaviourAction::NotifyHandler { peer_id, handler, event }) => return Poll::Ready(NetworkBehaviourAction::NotifyHandler { @@ -392,17 +404,9 @@ impl NetworkBehaviour for PeerInfoBehaviour { IdentifyEvent::Pushed { .. } => {}, IdentifyEvent::Sent { .. } => {}, }, - Poll::Ready(NetworkBehaviourAction::DialAddress { address, handler }) => { - let handler = IntoProtocolsHandler::select(self.ping.new_handler(), handler); - return Poll::Ready(NetworkBehaviourAction::DialAddress { address, handler }) - }, - Poll::Ready(NetworkBehaviourAction::DialPeer { peer_id, condition, handler }) => { - let handler = IntoProtocolsHandler::select(self.ping.new_handler(), handler); - return Poll::Ready(NetworkBehaviourAction::DialPeer { - peer_id, - condition, - handler, - }) + Poll::Ready(NetworkBehaviourAction::Dial { opts, handler }) => { + let handler = IntoConnectionHandler::select(self.ping.new_handler(), handler); + return Poll::Ready(NetworkBehaviourAction::Dial { opts, handler }) }, Poll::Ready(NetworkBehaviourAction::NotifyHandler { peer_id, handler, event }) => return Poll::Ready(NetworkBehaviourAction::NotifyHandler { diff --git a/client/network/src/protocol.rs b/client/network/src/protocol.rs index 80694210e77dc..8497dfd940de0 100644 --- a/client/network/src/protocol.rs +++ b/client/network/src/protocol.rs @@ -36,8 +36,8 @@ use libp2p::{ }, request_response::OutboundFailure, swarm::{ - IntoProtocolsHandler, NetworkBehaviour, NetworkBehaviourAction, PollParameters, - ProtocolsHandler, + ConnectionHandler, IntoConnectionHandler, NetworkBehaviour, NetworkBehaviourAction, + PollParameters, }, Multiaddr, PeerId, }; @@ -1367,10 +1367,10 @@ pub enum CustomMessageOutcome { } impl NetworkBehaviour for Protocol { - type ProtocolsHandler = ::ProtocolsHandler; + type ConnectionHandler = ::ConnectionHandler; type OutEvent = CustomMessageOutcome; - fn new_handler(&mut self) -> Self::ProtocolsHandler { + fn new_handler(&mut self) -> Self::ConnectionHandler { self.behaviour.new_handler() } @@ -1384,9 +1384,15 @@ impl NetworkBehaviour for Protocol { conn: &ConnectionId, endpoint: &ConnectedPoint, failed_addresses: Option<&Vec>, + other_established: usize, ) { - self.behaviour - .inject_connection_established(peer_id, conn, endpoint, failed_addresses) + self.behaviour.inject_connection_established( + peer_id, + conn, + endpoint, + failed_addresses, + other_established, + ) } fn inject_connection_closed( @@ -1394,24 +1400,23 @@ impl NetworkBehaviour for Protocol { peer_id: &PeerId, conn: &ConnectionId, endpoint: &ConnectedPoint, - handler: ::Handler, + handler: ::Handler, + remaining_established: usize, ) { - self.behaviour.inject_connection_closed(peer_id, conn, endpoint, handler) - } - - fn inject_connected(&mut self, peer_id: &PeerId) { - self.behaviour.inject_connected(peer_id) - } - - fn inject_disconnected(&mut self, peer_id: &PeerId) { - self.behaviour.inject_disconnected(peer_id) + self.behaviour.inject_connection_closed( + peer_id, + conn, + endpoint, + handler, + remaining_established, + ) } fn inject_event( &mut self, peer_id: PeerId, connection: ConnectionId, - event: <::Handler as ProtocolsHandler>::OutEvent, + event: <::Handler as ConnectionHandler>::OutEvent, ) { self.behaviour.inject_event(peer_id, connection, event) } @@ -1420,7 +1425,7 @@ impl NetworkBehaviour for Protocol { &mut self, cx: &mut std::task::Context, params: &mut impl PollParameters, - ) -> Poll> { + ) -> Poll> { if let Some(message) = self.pending_messages.pop_front() { return Poll::Ready(NetworkBehaviourAction::GenerateEvent(message)) } @@ -1581,10 +1586,8 @@ impl NetworkBehaviour for Protocol { let event = match self.behaviour.poll(cx, params) { Poll::Pending => return Poll::Pending, Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) => ev, - Poll::Ready(NetworkBehaviourAction::DialAddress { address, handler }) => - return Poll::Ready(NetworkBehaviourAction::DialAddress { address, handler }), - Poll::Ready(NetworkBehaviourAction::DialPeer { peer_id, condition, handler }) => - return Poll::Ready(NetworkBehaviourAction::DialPeer { peer_id, condition, handler }), + Poll::Ready(NetworkBehaviourAction::Dial { opts, handler }) => + return Poll::Ready(NetworkBehaviourAction::Dial { opts, handler }), Poll::Ready(NetworkBehaviourAction::NotifyHandler { peer_id, handler, event }) => return Poll::Ready(NetworkBehaviourAction::NotifyHandler { peer_id, @@ -1800,7 +1803,7 @@ impl NetworkBehaviour for Protocol { fn inject_dial_failure( &mut self, peer_id: Option, - handler: Self::ProtocolsHandler, + handler: Self::ConnectionHandler, error: &libp2p::swarm::DialError, ) { self.behaviour.inject_dial_failure(peer_id, handler, error); diff --git a/client/network/src/protocol/notifications/behaviour.rs b/client/network/src/protocol/notifications/behaviour.rs index b47216473970e..61f7db78c98b1 100644 --- a/client/network/src/protocol/notifications/behaviour.rs +++ b/client/network/src/protocol/notifications/behaviour.rs @@ -26,8 +26,8 @@ use futures::prelude::*; use libp2p::{ core::{connection::ConnectionId, ConnectedPoint, Multiaddr, PeerId}, swarm::{ - DialError, DialPeerCondition, IntoProtocolsHandler, NetworkBehaviour, - NetworkBehaviourAction, NotifyHandler, PollParameters, + DialError, IntoConnectionHandler, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, + PollParameters, }, }; use log::{error, trace, warn}; @@ -620,10 +620,8 @@ impl Notifications { set_id, ); trace!(target: "sub-libp2p", "Libp2p <= Dial {}", entry.key().0); - // The `DialPeerCondition` ensures that dial attempts are de-duplicated - self.events.push_back(NetworkBehaviourAction::DialPeer { - peer_id: entry.key().0.clone(), - condition: DialPeerCondition::Disconnected, + self.events.push_back(NetworkBehaviourAction::Dial { + opts: entry.key().0.clone().into(), handler, }); entry.insert(PeerState::Requested); @@ -657,10 +655,8 @@ impl Notifications { set_id, ); trace!(target: "sub-libp2p", "Libp2p <= Dial {:?}", occ_entry.key()); - // The `DialPeerCondition` ensures that dial attempts are de-duplicated - self.events.push_back(NetworkBehaviourAction::DialPeer { - peer_id: occ_entry.key().0.clone(), - condition: DialPeerCondition::Disconnected, + self.events.push_back(NetworkBehaviourAction::Dial { + opts: occ_entry.key().0.clone().into(), handler, }); *occ_entry.into_mut() = PeerState::Requested; @@ -1059,10 +1055,10 @@ impl Notifications { } impl NetworkBehaviour for Notifications { - type ProtocolsHandler = NotifsHandlerProto; + type ConnectionHandler = NotifsHandlerProto; type OutEvent = NotificationsOut; - fn new_handler(&mut self) -> Self::ProtocolsHandler { + fn new_handler(&mut self) -> Self::ConnectionHandler { NotifsHandlerProto::new(self.notif_protocols.clone()) } @@ -1070,14 +1066,13 @@ impl NetworkBehaviour for Notifications { Vec::new() } - fn inject_connected(&mut self, _: &PeerId) {} - fn inject_connection_established( &mut self, peer_id: &PeerId, conn: &ConnectionId, endpoint: &ConnectedPoint, _failed_addresses: Option<&Vec>, + _other_established: usize, ) { for set_id in (0..self.notif_protocols.len()).map(sc_peerset::SetId::from) { match self.peers.entry((*peer_id, set_id)).or_insert(PeerState::Poisoned) { @@ -1136,7 +1131,8 @@ impl NetworkBehaviour for Notifications { peer_id: &PeerId, conn: &ConnectionId, _endpoint: &ConnectedPoint, - _handler: ::Handler, + _handler: ::Handler, + _remaining_established: usize, ) { for set_id in (0..self.notif_protocols.len()).map(sc_peerset::SetId::from) { let mut entry = if let Entry::Occupied(entry) = self.peers.entry((*peer_id, set_id)) { @@ -1394,12 +1390,10 @@ impl NetworkBehaviour for Notifications { } } - fn inject_disconnected(&mut self, _peer_id: &PeerId) {} - fn inject_dial_failure( &mut self, peer_id: Option, - _: Self::ProtocolsHandler, + _: Self::ConnectionHandler, error: &DialError, ) { if let DialError::Transport(errors) = error { @@ -1989,7 +1983,7 @@ impl NetworkBehaviour for Notifications { &mut self, cx: &mut Context, _params: &mut impl PollParameters, - ) -> Poll> { + ) -> Poll> { if let Some(event) = self.events.pop_front() { return Poll::Ready(event) } @@ -2038,12 +2032,8 @@ impl NetworkBehaviour for Notifications { PeerState::PendingRequest { timer, .. } if *timer == delay_id => { trace!(target: "sub-libp2p", "Libp2p <= Dial {:?} now that ban has expired", peer_id); - // The `DialPeerCondition` ensures that dial attempts are de-duplicated - self.events.push_back(NetworkBehaviourAction::DialPeer { - peer_id, - condition: DialPeerCondition::Disconnected, - handler, - }); + self.events + .push_back(NetworkBehaviourAction::Dial { opts: peer_id.into(), handler }); *peer_state = PeerState::Requested; }, diff --git a/client/network/src/protocol/notifications/handler.rs b/client/network/src/protocol/notifications/handler.rs index 91225f54203af..510f72d4b0123 100644 --- a/client/network/src/protocol/notifications/handler.rs +++ b/client/network/src/protocol/notifications/handler.rs @@ -16,10 +16,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! Implementations of the `IntoProtocolsHandler` and `ProtocolsHandler` traits for both incoming +//! Implementations of the `IntoConnectionHandler` and `ConnectionHandler` traits for both incoming //! and outgoing substreams for all gossiping protocols. //! -//! This is the main implementation of `ProtocolsHandler` in this crate, that handles all the +//! This is the main implementation of `ConnectionHandler` in this crate, that handles all the //! gossiping protocols that are Substrate-related and outside of the scope of libp2p. //! //! # Usage @@ -74,8 +74,8 @@ use libp2p::{ ConnectedPoint, PeerId, }, swarm::{ - IntoProtocolsHandler, KeepAlive, NegotiatedSubstream, ProtocolsHandler, - ProtocolsHandlerEvent, ProtocolsHandlerUpgrErr, SubstreamProtocol, + ConnectionHandler, ConnectionHandlerEvent, ConnectionHandlerUpgrErr, IntoConnectionHandler, + KeepAlive, NegotiatedSubstream, SubstreamProtocol, }, }; use log::error; @@ -107,7 +107,7 @@ const OPEN_TIMEOUT: Duration = Duration::from_secs(10); /// open substreams. const INITIAL_KEEPALIVE_TIME: Duration = Duration::from_secs(5); -/// Implements the `IntoProtocolsHandler` trait of libp2p. +/// Implements the `IntoConnectionHandler` trait of libp2p. /// /// Every time a connection with a remote starts, an instance of this struct is created and /// sent to a background task dedicated to this connection. Once the connection is established, @@ -138,7 +138,7 @@ pub struct NotifsHandler { /// Events to return in priority from `poll`. events_queue: VecDeque< - ProtocolsHandlerEvent, + ConnectionHandlerEvent, >, } @@ -225,7 +225,7 @@ enum State { }, } -impl IntoProtocolsHandler for NotifsHandlerProto { +impl IntoConnectionHandler for NotifsHandlerProto { type Handler = NotifsHandler; fn inbound_protocol(&self) -> UpgradeCollec { @@ -475,7 +475,7 @@ impl NotifsHandlerProto { } } -impl ProtocolsHandler for NotifsHandler { +impl ConnectionHandler for NotifsHandler { type InEvent = NotifsHandlerIn; type OutEvent = NotifsHandlerOut; type Error = NotifsHandlerError; @@ -505,7 +505,7 @@ impl ProtocolsHandler for NotifsHandler { let mut protocol_info = &mut self.protocols[protocol_index]; match protocol_info.state { State::Closed { pending_opening } => { - self.events_queue.push_back(ProtocolsHandlerEvent::Custom( + self.events_queue.push_back(ConnectionHandlerEvent::Custom( NotifsHandlerOut::OpenDesiredByRemote { protocol_index }, )); @@ -573,7 +573,7 @@ impl ProtocolsHandler for NotifsHandler { in_substream: in_substream.take(), }; - self.events_queue.push_back(ProtocolsHandlerEvent::Custom( + self.events_queue.push_back(ConnectionHandlerEvent::Custom( NotifsHandlerOut::OpenResultOk { protocol_index, negotiated_fallback: new_open.negotiated_fallback, @@ -601,7 +601,7 @@ impl ProtocolsHandler for NotifsHandler { ); self.events_queue.push_back( - ProtocolsHandlerEvent::OutboundSubstreamRequest { + ConnectionHandlerEvent::OutboundSubstreamRequest { protocol: SubstreamProtocol::new(proto, protocol_index) .with_timeout(OPEN_TIMEOUT), }, @@ -622,7 +622,7 @@ impl ProtocolsHandler for NotifsHandler { ); self.events_queue.push_back( - ProtocolsHandlerEvent::OutboundSubstreamRequest { + ConnectionHandlerEvent::OutboundSubstreamRequest { protocol: SubstreamProtocol::new(proto, protocol_index) .with_timeout(OPEN_TIMEOUT), }, @@ -660,7 +660,7 @@ impl ProtocolsHandler for NotifsHandler { self.protocols[protocol_index].state = State::Closed { pending_opening: true }; - self.events_queue.push_back(ProtocolsHandlerEvent::Custom( + self.events_queue.push_back(ConnectionHandlerEvent::Custom( NotifsHandlerOut::OpenResultErr { protocol_index }, )); }, @@ -670,7 +670,7 @@ impl ProtocolsHandler for NotifsHandler { State::Closed { .. } => {}, } - self.events_queue.push_back(ProtocolsHandlerEvent::Custom( + self.events_queue.push_back(ConnectionHandlerEvent::Custom( NotifsHandlerOut::CloseResult { protocol_index }, )); }, @@ -680,7 +680,7 @@ impl ProtocolsHandler for NotifsHandler { fn inject_dial_upgrade_error( &mut self, num: usize, - _: ProtocolsHandlerUpgrErr, + _: ConnectionHandlerUpgrErr, ) { match self.protocols[num].state { State::Closed { ref mut pending_opening } | @@ -692,7 +692,7 @@ impl ProtocolsHandler for NotifsHandler { State::Opening { .. } => { self.protocols[num].state = State::Closed { pending_opening: false }; - self.events_queue.push_back(ProtocolsHandlerEvent::Custom( + self.events_queue.push_back(ConnectionHandlerEvent::Custom( NotifsHandlerOut::OpenResultErr { protocol_index: num }, )); }, @@ -717,7 +717,7 @@ impl ProtocolsHandler for NotifsHandler { &mut self, cx: &mut Context, ) -> Poll< - ProtocolsHandlerEvent< + ConnectionHandlerEvent< Self::OutboundProtocol, Self::OutboundOpenInfo, Self::OutEvent, @@ -741,7 +741,7 @@ impl ProtocolsHandler for NotifsHandler { // a substream is ready to send if there isn't actually something to send. match Pin::new(&mut *notifications_sink_rx).as_mut().poll_peek(cx) { Poll::Ready(Some(&NotificationsSinkMessage::ForceClose)) => - return Poll::Ready(ProtocolsHandlerEvent::Close( + return Poll::Ready(ConnectionHandlerEvent::Close( NotifsHandlerError::SyncNotificationsClogged, )), Poll::Ready(Some(&NotificationsSinkMessage::Notification { .. })) => {}, @@ -789,7 +789,7 @@ impl ProtocolsHandler for NotifsHandler { Poll::Ready(Err(_)) => { *out_substream = None; let event = NotifsHandlerOut::CloseDesired { protocol_index }; - return Poll::Ready(ProtocolsHandlerEvent::Custom(event)) + return Poll::Ready(ConnectionHandlerEvent::Custom(event)) }, }; }, @@ -815,7 +815,7 @@ impl ProtocolsHandler for NotifsHandler { Poll::Pending => {}, Poll::Ready(Some(Ok(message))) => { let event = NotifsHandlerOut::Notification { protocol_index, message }; - return Poll::Ready(ProtocolsHandlerEvent::Custom(event)) + return Poll::Ready(ConnectionHandlerEvent::Custom(event)) }, Poll::Ready(None) | Poll::Ready(Some(Err(_))) => *in_substream = None, }, @@ -827,7 +827,7 @@ impl ProtocolsHandler for NotifsHandler { Poll::Ready(Err(_)) => { self.protocols[protocol_index].state = State::Closed { pending_opening: *pending_opening }; - return Poll::Ready(ProtocolsHandlerEvent::Custom( + return Poll::Ready(ConnectionHandlerEvent::Custom( NotifsHandlerOut::CloseDesired { protocol_index }, )) }, diff --git a/client/network/src/protocol/notifications/tests.rs b/client/network/src/protocol/notifications/tests.rs index 73058598a1e3b..da12dbf216c4c 100644 --- a/client/network/src/protocol/notifications/tests.rs +++ b/client/network/src/protocol/notifications/tests.rs @@ -29,8 +29,8 @@ use libp2p::{ }, identity, noise, swarm::{ - DialError, IntoProtocolsHandler, NetworkBehaviour, NetworkBehaviourAction, PollParameters, - ProtocolsHandler, Swarm, SwarmEvent, + ConnectionHandler, DialError, IntoConnectionHandler, NetworkBehaviour, + NetworkBehaviourAction, PollParameters, Swarm, SwarmEvent, }, yamux, Multiaddr, PeerId, Transport, }; @@ -133,10 +133,10 @@ impl std::ops::DerefMut for CustomProtoWithAddr { } impl NetworkBehaviour for CustomProtoWithAddr { - type ProtocolsHandler = ::ProtocolsHandler; + type ConnectionHandler = ::ConnectionHandler; type OutEvent = ::OutEvent; - fn new_handler(&mut self) -> Self::ProtocolsHandler { + fn new_handler(&mut self) -> Self::ConnectionHandler { self.inner.new_handler() } @@ -150,23 +150,21 @@ impl NetworkBehaviour for CustomProtoWithAddr { list } - fn inject_connected(&mut self, peer_id: &PeerId) { - self.inner.inject_connected(peer_id) - } - - fn inject_disconnected(&mut self, peer_id: &PeerId) { - self.inner.inject_disconnected(peer_id) - } - fn inject_connection_established( &mut self, peer_id: &PeerId, conn: &ConnectionId, endpoint: &ConnectedPoint, failed_addresses: Option<&Vec>, + other_established: usize, ) { - self.inner - .inject_connection_established(peer_id, conn, endpoint, failed_addresses) + self.inner.inject_connection_established( + peer_id, + conn, + endpoint, + failed_addresses, + other_established, + ) } fn inject_connection_closed( @@ -174,16 +172,18 @@ impl NetworkBehaviour for CustomProtoWithAddr { peer_id: &PeerId, conn: &ConnectionId, endpoint: &ConnectedPoint, - handler: ::Handler, + handler: ::Handler, + remaining_established: usize, ) { - self.inner.inject_connection_closed(peer_id, conn, endpoint, handler) + self.inner + .inject_connection_closed(peer_id, conn, endpoint, handler, remaining_established) } fn inject_event( &mut self, peer_id: PeerId, connection: ConnectionId, - event: <::Handler as ProtocolsHandler>::OutEvent, + event: <::Handler as ConnectionHandler>::OutEvent, ) { self.inner.inject_event(peer_id, connection, event) } @@ -192,14 +192,14 @@ impl NetworkBehaviour for CustomProtoWithAddr { &mut self, cx: &mut Context, params: &mut impl PollParameters, - ) -> Poll> { + ) -> Poll> { self.inner.poll(cx, params) } fn inject_dial_failure( &mut self, peer_id: Option, - handler: Self::ProtocolsHandler, + handler: Self::ConnectionHandler, error: &DialError, ) { self.inner.inject_dial_failure(peer_id, handler, error) diff --git a/client/network/src/request_responses.rs b/client/network/src/request_responses.rs index 4613a15af9366..87d070bc469a3 100644 --- a/client/network/src/request_responses.rs +++ b/client/network/src/request_responses.rs @@ -49,8 +49,8 @@ use libp2p::{ RequestResponseConfig, RequestResponseEvent, RequestResponseMessage, ResponseChannel, }, swarm::{ - protocols_handler::multi::MultiHandler, IntoProtocolsHandler, NetworkBehaviour, - NetworkBehaviourAction, PollParameters, ProtocolsHandler, + handler::multi::MultiHandler, ConnectionHandler, IntoConnectionHandler, NetworkBehaviour, + NetworkBehaviourAction, PollParameters, }, }; use std::{ @@ -381,7 +381,7 @@ impl RequestResponsesBehaviour { &mut self, protocol: String, handler: RequestResponseHandler, - ) -> ::ProtocolsHandler { + ) -> ::ConnectionHandler { let mut handlers: HashMap<_, _> = self .protocols .iter_mut() @@ -400,11 +400,13 @@ impl RequestResponsesBehaviour { } impl NetworkBehaviour for RequestResponsesBehaviour { - type ProtocolsHandler = - MultiHandler as NetworkBehaviour>::ProtocolsHandler>; + type ConnectionHandler = MultiHandler< + String, + as NetworkBehaviour>::ConnectionHandler, + >; type OutEvent = Event; - fn new_handler(&mut self) -> Self::ProtocolsHandler { + fn new_handler(&mut self) -> Self::ConnectionHandler { let iter = self .protocols .iter_mut() @@ -426,6 +428,7 @@ impl NetworkBehaviour for RequestResponsesBehaviour { conn: &ConnectionId, endpoint: &ConnectedPoint, failed_addresses: Option<&Vec>, + other_established: usize, ) { for (p, _) in self.protocols.values_mut() { NetworkBehaviour::inject_connection_established( @@ -434,32 +437,35 @@ impl NetworkBehaviour for RequestResponsesBehaviour { conn, endpoint, failed_addresses, + other_established, ) } } - fn inject_connected(&mut self, peer_id: &PeerId) { - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::inject_connected(p, peer_id) - } - } - fn inject_connection_closed( &mut self, peer_id: &PeerId, conn: &ConnectionId, endpoint: &ConnectedPoint, - _handler: ::Handler, + handler: ::Handler, + remaining_established: usize, ) { - for (p, _) in self.protocols.values_mut() { - let handler = p.new_handler(); - NetworkBehaviour::inject_connection_closed(p, peer_id, conn, endpoint, handler); - } - } - - fn inject_disconnected(&mut self, peer_id: &PeerId) { - for (p, _) in self.protocols.values_mut() { - NetworkBehaviour::inject_disconnected(p, peer_id) + for (p_name, event) in handler.into_iter() { + if let Some((proto, _)) = self.protocols.get_mut(p_name.as_str()) { + proto.inject_connection_closed( + peer_id, + conn, + endpoint, + event, + remaining_established, + ) + } else { + log::error!( + target: "sub-libp2p", + "inject_connection_closed: no request-response instance registered for protocol {:?}", + p_name, + ) + } } } @@ -467,7 +473,7 @@ impl NetworkBehaviour for RequestResponsesBehaviour { &mut self, peer_id: PeerId, connection: ConnectionId, - (p_name, event): ::OutEvent, + (p_name, event): ::OutEvent, ) { if let Some((proto, _)) = self.protocols.get_mut(&*p_name) { return proto.inject_event(peer_id, connection, event) @@ -499,7 +505,7 @@ impl NetworkBehaviour for RequestResponsesBehaviour { fn inject_dial_failure( &mut self, peer_id: Option, - _: Self::ProtocolsHandler, + _: Self::ConnectionHandler, error: &libp2p::swarm::DialError, ) { for (p, _) in self.protocols.values_mut() { @@ -536,7 +542,7 @@ impl NetworkBehaviour for RequestResponsesBehaviour { &mut self, cx: &mut Context, params: &mut impl PollParameters, - ) -> Poll> { + ) -> Poll> { 'poll_all: loop { if let Some(message_request) = self.message_request.take() { // Now we can can poll `MessageRequest` until we get the reputation @@ -677,25 +683,15 @@ impl NetworkBehaviour for RequestResponsesBehaviour { // Other events generated by the underlying behaviour are transparently // passed through. - NetworkBehaviourAction::DialAddress { address, handler } => { - log::error!( - "The request-response isn't supposed to start dialing peers" - ); - let protocol = protocol.to_string(); - let handler = self.new_handler_with_replacement(protocol, handler); - return Poll::Ready(NetworkBehaviourAction::DialAddress { - address, - handler, - }) - }, - NetworkBehaviourAction::DialPeer { peer_id, condition, handler } => { + NetworkBehaviourAction::Dial { opts, handler } => { + if opts.get_peer_id().is_none() { + log::error!( + "The request-response isn't supposed to start dialing addresses" + ); + } let protocol = protocol.to_string(); let handler = self.new_handler_with_replacement(protocol, handler); - return Poll::Ready(NetworkBehaviourAction::DialPeer { - peer_id, - condition, - handler, - }) + return Poll::Ready(NetworkBehaviourAction::Dial { opts, handler }) }, NetworkBehaviourAction::NotifyHandler { peer_id, handler, event } => return Poll::Ready(NetworkBehaviourAction::NotifyHandler { @@ -1146,7 +1142,7 @@ mod tests { // this test, so they wouldn't connect to each other. { let dial_addr = swarms[1].1.clone(); - Swarm::dial_addr(&mut swarms[0].0, dial_addr).unwrap(); + Swarm::dial(&mut swarms[0].0, dial_addr).unwrap(); } let (mut swarm, _, peerset) = swarms.remove(0); @@ -1246,7 +1242,7 @@ mod tests { // this test, so they wouldn't connect to each other. { let dial_addr = swarms[1].1.clone(); - Swarm::dial_addr(&mut swarms[0].0, dial_addr).unwrap(); + Swarm::dial(&mut swarms[0].0, dial_addr).unwrap(); } // Running `swarm[0]` in the background until a `InboundRequest` event happens, @@ -1375,7 +1371,7 @@ mod tests { // Ask swarm 1 to dial swarm 2. There isn't any discovery mechanism in place in this test, // so they wouldn't connect to each other. - swarm_1.dial_addr(listen_add_2).unwrap(); + swarm_1.dial(listen_add_2).unwrap(); // Run swarm 2 in the background, receiving two requests. pool.spawner() diff --git a/client/network/src/service.rs b/client/network/src/service.rs index 7239c9f6f9e6a..e88e7924954b3 100644 --- a/client/network/src/service.rs +++ b/client/network/src/service.rs @@ -49,16 +49,12 @@ use crate::{ use codec::Encode as _; use futures::{channel::oneshot, prelude::*}; use libp2p::{ - core::{ - connection::{ConnectionError, ConnectionLimits, PendingConnectionError}, - either::EitherError, - upgrade, ConnectedPoint, Executor, - }, + core::{either::EitherError, upgrade, ConnectedPoint, Executor}, multiaddr, ping::Failure as PingFailure, swarm::{ - protocols_handler::NodeHandlerWrapperError, AddressScore, DialError, NetworkBehaviour, - SwarmBuilder, SwarmEvent, + AddressScore, ConnectionError, ConnectionLimits, DialError, NetworkBehaviour, + PendingConnectionError, SwarmBuilder, SwarmEvent, }, Multiaddr, PeerId, }; @@ -1531,7 +1527,7 @@ impl Future for NetworkWorker { ServiceToWorkerMsg::PropagateTransactions => this.tx_handler_controller.propagate_transactions(), ServiceToWorkerMsg::GetValue(key) => - this.network_service.behaviour_mut().get_value(&key), + this.network_service.behaviour_mut().get_value(key), ServiceToWorkerMsg::PutValue(key, value) => this.network_service.behaviour_mut().put_value(key, value), ServiceToWorkerMsg::SetReservedOnly(reserved_only) => this @@ -1897,21 +1893,18 @@ impl Future for NetworkWorker { }; let reason = match cause { Some(ConnectionError::IO(_)) => "transport-error", - Some(ConnectionError::Handler(NodeHandlerWrapperError::Handler( - EitherError::A(EitherError::A(EitherError::A(EitherError::B( - EitherError::A(PingFailure::Timeout), - )))), - ))) => "ping-timeout", - Some(ConnectionError::Handler(NodeHandlerWrapperError::Handler( - EitherError::A(EitherError::A(EitherError::A(EitherError::A( + Some(ConnectionError::Handler(EitherError::A(EitherError::A( + EitherError::A(EitherError::B(EitherError::A( + PingFailure::Timeout, + ))), + )))) => "ping-timeout", + Some(ConnectionError::Handler(EitherError::A(EitherError::A( + EitherError::A(EitherError::A( NotifsHandlerError::SyncNotificationsClogged, - )))), - ))) => "sync-notifications-clogged", - Some(ConnectionError::Handler(NodeHandlerWrapperError::Handler(_))) => - "protocol-error", - Some(ConnectionError::Handler( - NodeHandlerWrapperError::KeepAliveTimeout, - )) => "keep-alive-timeout", + )), + )))) => "sync-notifications-clogged", + Some(ConnectionError::Handler(_)) => "protocol-error", + Some(ConnectionError::KeepAliveTimeout) => "keep-alive-timeout", None => "actively-closed", }; metrics @@ -1946,10 +1939,12 @@ impl Future for NetworkWorker { ); if this.boot_node_ids.contains(&peer_id) { - if let DialError::InvalidPeerId = error { + if let DialError::WrongPeerId { obtained, endpoint } = &error { error!( - "💔 The bootnode you want to connect provided a different peer ID than the one you expect: `{}`.", + "💔 The bootnode you want to connect provided a different peer ID than the one you expect: `{}` with `{}`:`{:?}`.", peer_id, + obtained, + endpoint, ); } } @@ -1958,13 +1953,14 @@ impl Future for NetworkWorker { if let Some(metrics) = this.metrics.as_ref() { let reason = match error { DialError::ConnectionLimit(_) => Some("limit-reached"), - DialError::InvalidPeerId => Some("invalid-peer-id"), + DialError::InvalidPeerId(_) => Some("invalid-peer-id"), DialError::Transport(_) | DialError::ConnectionIo(_) => Some("transport-error"), DialError::Banned | DialError::LocalPeerId | DialError::NoAddresses | DialError::DialPeerConditionFalse(_) | + DialError::WrongPeerId { .. } | DialError::Aborted => None, // ignore them }; if let Some(reason) = reason { @@ -1998,7 +1994,7 @@ impl Future for NetworkWorker { if let Some(metrics) = this.metrics.as_ref() { let reason = match error { PendingConnectionError::ConnectionLimit(_) => Some("limit-reached"), - PendingConnectionError::InvalidPeerId => Some("invalid-peer-id"), + PendingConnectionError::WrongPeerId { .. } => Some("invalid-peer-id"), PendingConnectionError::Transport(_) | PendingConnectionError::IO(_) => Some("transport-error"), PendingConnectionError::Aborted => None, // ignore it diff --git a/client/network/test/Cargo.toml b/client/network/test/Cargo.toml index 642654bcb2de5..39297dd3ea295 100644 --- a/client/network/test/Cargo.toml +++ b/client/network/test/Cargo.toml @@ -20,7 +20,7 @@ parking_lot = "0.12.0" futures = "0.3.21" futures-timer = "3.0.1" rand = "0.7.2" -libp2p = { version = "0.40.0", default-features = false } +libp2p = { version = "0.44.0", default-features = false } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } sc-consensus = { version = "0.10.0-dev", path = "../../consensus/common" } sc-client-api = { version = "4.0.0-dev", path = "../../api" } diff --git a/client/peerset/Cargo.toml b/client/peerset/Cargo.toml index 5cda0913da1a1..ee83d186b5417 100644 --- a/client/peerset/Cargo.toml +++ b/client/peerset/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] futures = "0.3.21" -libp2p = { version = "0.40.0", default-features = false } +libp2p = { version = "0.44.0", default-features = false } sc-utils = { version = "4.0.0-dev", path = "../utils"} log = "0.4.16" serde_json = "1.0.79" diff --git a/client/telemetry/Cargo.toml b/client/telemetry/Cargo.toml index bb517290cef51..4dfefd06ac737 100644 --- a/client/telemetry/Cargo.toml +++ b/client/telemetry/Cargo.toml @@ -18,7 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"] parking_lot = "0.12.0" futures = "0.3.21" wasm-timer = "0.2.5" -libp2p = { version = "0.40.0", default-features = false, features = ["dns-async-std", "tcp-async-io", "wasm-ext", "websocket"] } +libp2p = { version = "0.44.0", default-features = false, features = ["dns-async-std", "tcp-async-io", "wasm-ext", "websocket"] } log = "0.4.16" pin-project = "1.0.10" rand = "0.7.2" diff --git a/client/telemetry/src/transport.rs b/client/telemetry/src/transport.rs index 23725b44a64dd..e21a2380be255 100644 --- a/client/telemetry/src/transport.rs +++ b/client/telemetry/src/transport.rs @@ -38,8 +38,13 @@ pub(crate) fn initialize_transport() -> Result { let item = libp2p::websocket::framed::OutgoingData::Binary(item); future::ready(Ok::<_, io::Error>(item)) }) - .try_filter(|item| future::ready(item.is_data())) - .map_ok(|data| data.into_bytes()); + .try_filter_map(|item| async move { + if let libp2p::websocket::framed::Incoming::Data(data) = item { + Ok(Some(data.into_bytes())) + } else { + Ok(None) + } + }); future::ready(Ok::<_, io::Error>(connec)) }) }; diff --git a/primitives/core/Cargo.toml b/primitives/core/Cargo.toml index d23579c8ca2c0..9fbdeb15a0366 100644 --- a/primitives/core/Cargo.toml +++ b/primitives/core/Cargo.toml @@ -32,7 +32,7 @@ substrate-bip39 = { version = "0.4.4", optional = true } tiny-bip39 = { version = "0.8.2", optional = true } regex = { version = "1.5.4", optional = true } num-traits = { version = "0.2.8", default-features = false } -zeroize = { version = "1.5.4", default-features = false } +zeroize = { version = "1.4.3", default-features = false } secrecy = { version = "0.8.0", default-features = false } lazy_static = { version = "1.4.0", default-features = false, optional = true } parking_lot = { version = "0.12.0", optional = true } diff --git a/utils/frame/benchmarking-cli/src/pallet/writer.rs b/utils/frame/benchmarking-cli/src/pallet/writer.rs index cd97b3efbd9db..81b20ad445cd0 100644 --- a/utils/frame/benchmarking-cli/src/pallet/writer.rs +++ b/utils/frame/benchmarking-cli/src/pallet/writer.rs @@ -311,7 +311,7 @@ pub fn write_results( // Check if there might be multiple instances benchmarked. if all_results.keys().any(|(p, i)| p == pallet && i != instance) { // Create new file: "path/to/pallet_name_instance_name.rs". - file_path.push(pallet.clone() + "_" + &instance.to_snake_case()); + file_path.push(pallet.clone() + "_" + instance.to_snake_case().as_str()); } else { // Create new file: "path/to/pallet_name.rs". file_path.push(pallet.clone()); From 9fea5bb21cfe485e03af6254054f40d3ec29da1a Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Fri, 29 Apr 2022 17:02:03 +0300 Subject: [PATCH 169/484] Network sync refactoring (part 1) (#11303) * Remove unnecessary imports, move one internal re-export into where it is actually used, make one import explicit * Move a few data structures down into modules * Use generic parameters in `sc-network` instead of `chain::Client` trait * Remove unnecessary bound --- client/network/src/behaviour.rs | 112 ++++++++++++++--- client/network/src/bitswap.rs | 28 +++-- client/network/src/block_request_handler.rs | 15 ++- client/network/src/chain.rs | 48 ------- client/network/src/config.rs | 9 +- client/network/src/lib.rs | 1 - .../src/light_client_requests/handler.rs | 20 +-- client/network/src/protocol.rs | 37 ++++-- client/network/src/protocol/sync.rs | 68 ++++------ client/network/src/protocol/sync/state.rs | 32 +++-- client/network/src/protocol/sync/warp.rs | 56 ++++++--- client/network/src/service.rs | 117 ++++++++++++++---- client/network/src/state_request_handler.rs | 14 ++- client/network/test/src/lib.rs | 4 +- client/service/src/lib.rs | 21 +++- 15 files changed, 378 insertions(+), 204 deletions(-) delete mode 100644 client/network/src/chain.rs diff --git a/client/network/src/behaviour.rs b/client/network/src/behaviour.rs index f6a7e9eb87018..5ff3ba1ad44f4 100644 --- a/client/network/src/behaviour.rs +++ b/client/network/src/behaviour.rs @@ -40,8 +40,10 @@ use libp2p::{ }; use log::debug; use prost::Message; +use sc_client_api::{BlockBackend, ProofProvider}; use sc_consensus::import_queue::{IncomingBlock, Origin}; use sc_peerset::PeersetHandle; +use sp_blockchain::{HeaderBackend, HeaderMetadata}; use sp_consensus::BlockOrigin; use sp_runtime::{ traits::{Block as BlockT, NumberFor}, @@ -62,17 +64,27 @@ pub use crate::request_responses::{ /// General behaviour of the network. Combines all protocols together. #[derive(NetworkBehaviour)] #[behaviour(out_event = "BehaviourOut", poll_method = "poll", event_process = true)] -pub struct Behaviour { +pub struct Behaviour +where + B: BlockT, + Client: HeaderBackend + + BlockBackend + + HeaderMetadata + + ProofProvider + + Send + + Sync + + 'static, +{ /// All the substrate-specific protocols. - substrate: Protocol, + substrate: Protocol, /// Periodically pings and identifies the nodes we are connected to, and store information in a /// cache. peer_info: peer_info::PeerInfoBehaviour, /// Discovers nodes of the network. discovery: DiscoveryBehaviour, /// Bitswap server for blockchain data. - bitswap: Toggle>, - /// Generic request-reponse protocols. + bitswap: Toggle>, + /// Generic request-response protocols. request_responses: request_responses::RequestResponsesBehaviour, /// Queue of events to produce for the outside. @@ -191,17 +203,27 @@ pub enum BehaviourOut { Dht(DhtEvent, Duration), } -impl Behaviour { +impl Behaviour +where + B: BlockT, + Client: HeaderBackend + + BlockBackend + + HeaderMetadata + + ProofProvider + + Send + + Sync + + 'static, +{ /// Builds a new `Behaviour`. pub fn new( - substrate: Protocol, + substrate: Protocol, user_agent: String, local_public_key: PublicKey, disco_config: DiscoveryConfig, block_request_protocol_config: request_responses::ProtocolConfig, state_request_protocol_config: request_responses::ProtocolConfig, warp_sync_protocol_config: Option, - bitswap: Option>, + bitswap: Option>, light_client_request_protocol_config: request_responses::ProtocolConfig, // All remaining request protocol configs. mut request_response_protocols: Vec, @@ -293,12 +315,12 @@ impl Behaviour { } /// Returns a shared reference to the user protocol. - pub fn user_protocol(&self) -> &Protocol { + pub fn user_protocol(&self) -> &Protocol { &self.substrate } /// Returns a mutable reference to the user protocol. - pub fn user_protocol_mut(&mut self) -> &mut Protocol { + pub fn user_protocol_mut(&mut self) -> &mut Protocol { &mut self.substrate } @@ -325,13 +347,33 @@ fn reported_roles_to_observed_role(roles: Roles) -> ObservedRole { } } -impl NetworkBehaviourEventProcess for Behaviour { +impl NetworkBehaviourEventProcess for Behaviour +where + B: BlockT, + Client: HeaderBackend + + BlockBackend + + HeaderMetadata + + ProofProvider + + Send + + Sync + + 'static, +{ fn inject_event(&mut self, event: void::Void) { void::unreachable(event) } } -impl NetworkBehaviourEventProcess> for Behaviour { +impl NetworkBehaviourEventProcess> for Behaviour +where + B: BlockT, + Client: HeaderBackend + + BlockBackend + + HeaderMetadata + + ProofProvider + + Send + + Sync + + 'static, +{ fn inject_event(&mut self, event: CustomMessageOutcome) { match event { CustomMessageOutcome::BlockImport(origin, blocks) => @@ -435,7 +477,17 @@ impl NetworkBehaviourEventProcess> for Behavi } } -impl NetworkBehaviourEventProcess for Behaviour { +impl NetworkBehaviourEventProcess for Behaviour +where + B: BlockT, + Client: HeaderBackend + + BlockBackend + + HeaderMetadata + + ProofProvider + + Send + + Sync + + 'static, +{ fn inject_event(&mut self, event: request_responses::Event) { match event { request_responses::Event::InboundRequest { peer, protocol, result } => { @@ -457,7 +509,17 @@ impl NetworkBehaviourEventProcess for Behav } } -impl NetworkBehaviourEventProcess for Behaviour { +impl NetworkBehaviourEventProcess for Behaviour +where + B: BlockT, + Client: HeaderBackend + + BlockBackend + + HeaderMetadata + + ProofProvider + + Send + + Sync + + 'static, +{ fn inject_event(&mut self, event: peer_info::PeerInfoEvent) { let peer_info::PeerInfoEvent::Identified { peer_id, @@ -480,7 +542,17 @@ impl NetworkBehaviourEventProcess for Behav } } -impl NetworkBehaviourEventProcess for Behaviour { +impl NetworkBehaviourEventProcess for Behaviour +where + B: BlockT, + Client: HeaderBackend + + BlockBackend + + HeaderMetadata + + ProofProvider + + Send + + Sync + + 'static, +{ fn inject_event(&mut self, out: DiscoveryOut) { match out { DiscoveryOut::UnroutablePeer(_peer_id) => { @@ -514,7 +586,17 @@ impl NetworkBehaviourEventProcess for Behaviour { } } -impl Behaviour { +impl Behaviour +where + B: BlockT, + Client: HeaderBackend + + BlockBackend + + HeaderMetadata + + ProofProvider + + Send + + Sync + + 'static, +{ fn poll( &mut self, _cx: &mut Context, diff --git a/client/network/src/bitswap.rs b/client/network/src/bitswap.rs index da80ab8cb3d33..c46990997ca05 100644 --- a/client/network/src/bitswap.rs +++ b/client/network/src/bitswap.rs @@ -20,12 +20,9 @@ //! Only supports bitswap 1.2.0. //! CID is expected to reference 256-bit Blake2b transaction hash. -use crate::{ - chain::Client, - schema::bitswap::{ - message::{wantlist::WantType, Block as MessageBlock, BlockPresence, BlockPresenceType}, - Message as BitswapMessage, - }, +use crate::schema::bitswap::{ + message::{wantlist::WantType, Block as MessageBlock, BlockPresence, BlockPresenceType}, + Message as BitswapMessage, }; use cid::Version; use core::pin::Pin; @@ -44,10 +41,12 @@ use libp2p::{ }; use log::{debug, error, trace}; use prost::Message; +use sc_client_api::BlockBackend; use sp_runtime::traits::Block as BlockT; use std::{ collections::VecDeque, io, + marker::PhantomData, sync::Arc, task::{Context, Poll}, }; @@ -181,19 +180,24 @@ impl Prefix { } /// Network behaviour that handles sending and receiving IPFS blocks. -pub struct Bitswap { - client: Arc>, +pub struct Bitswap { + client: Arc, ready_blocks: VecDeque<(PeerId, BitswapMessage)>, + _block: PhantomData, } -impl Bitswap { +impl Bitswap { /// Create a new instance of the bitswap protocol handler. - pub fn new(client: Arc>) -> Self { - Self { client, ready_blocks: Default::default() } + pub fn new(client: Arc) -> Self { + Self { client, ready_blocks: Default::default(), _block: PhantomData::default() } } } -impl NetworkBehaviour for Bitswap { +impl NetworkBehaviour for Bitswap +where + B: BlockT, + Client: BlockBackend + Send + Sync + 'static, +{ type ConnectionHandler = OneShotHandler; type OutEvent = void::Void; diff --git a/client/network/src/block_request_handler.rs b/client/network/src/block_request_handler.rs index e1fe9ebf8d060..2e238c01636d5 100644 --- a/client/network/src/block_request_handler.rs +++ b/client/network/src/block_request_handler.rs @@ -18,7 +18,6 @@ //! `crate::request_responses::RequestResponsesBehaviour`. use crate::{ - chain::Client, config::ProtocolId, protocol::message::BlockAttributes, request_responses::{IncomingRequest, OutgoingResponse, ProtocolConfig}, @@ -33,6 +32,8 @@ use futures::{ use log::debug; use lru::LruCache; use prost::Message; +use sc_client_api::BlockBackend; +use sp_blockchain::HeaderBackend; use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, Header, One, Zero}, @@ -113,8 +114,8 @@ enum SeenRequestsValue { } /// Handler for incoming block requests from a remote peer. -pub struct BlockRequestHandler { - client: Arc>, +pub struct BlockRequestHandler { + client: Arc, request_receiver: mpsc::Receiver, /// Maps from request to number of times we have seen this request. /// @@ -122,11 +123,15 @@ pub struct BlockRequestHandler { seen_requests: LruCache, SeenRequestsValue>, } -impl BlockRequestHandler { +impl BlockRequestHandler +where + B: BlockT, + Client: HeaderBackend + BlockBackend + Send + Sync + 'static, +{ /// Create a new [`BlockRequestHandler`]. pub fn new( protocol_id: &ProtocolId, - client: Arc>, + client: Arc, num_peer_hint: usize, ) -> (Self, ProtocolConfig) { // Reserve enough request slots for one request per peer when we are at the maximum diff --git a/client/network/src/chain.rs b/client/network/src/chain.rs deleted file mode 100644 index c66cc2ce1daf5..0000000000000 --- a/client/network/src/chain.rs +++ /dev/null @@ -1,48 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -//! Blockchain access trait - -use sc_client_api::{BlockBackend, ProofProvider}; -pub use sc_client_api::{StorageData, StorageKey}; -pub use sc_consensus::ImportedState; -use sp_blockchain::{Error, HeaderBackend, HeaderMetadata}; -use sp_runtime::traits::{Block as BlockT, BlockIdTo}; - -/// Local client abstraction for the network. -pub trait Client: - HeaderBackend - + ProofProvider - + BlockIdTo - + BlockBackend - + HeaderMetadata - + Send - + Sync -{ -} - -impl Client for T where - T: HeaderBackend - + ProofProvider - + BlockIdTo - + BlockBackend - + HeaderMetadata - + Send - + Sync -{ -} diff --git a/client/network/src/config.rs b/client/network/src/config.rs index 40aefe9a3ec2d..2b448ed14eab0 100644 --- a/client/network/src/config.rs +++ b/client/network/src/config.rs @@ -22,7 +22,6 @@ //! See the documentation of [`Params`]. pub use crate::{ - chain::Client, request_responses::{ IncomingRequest, OutgoingResponse, ProtocolConfig as RequestResponseConfig, }, @@ -64,7 +63,11 @@ use std::{ use zeroize::Zeroize; /// Network initialization parameters. -pub struct Params { +pub struct Params +where + B: BlockT + 'static, + H: ExHashT, +{ /// Assigned role for our node (full, light, ...). pub role: Role, @@ -79,7 +82,7 @@ pub struct Params { pub network_config: NetworkConfiguration, /// Client that contains the blockchain. - pub chain: Arc>, + pub chain: Arc, /// Pool of transactions. /// diff --git a/client/network/src/lib.rs b/client/network/src/lib.rs index d9f5b3de1bb10..973e0b15b7509 100644 --- a/client/network/src/lib.rs +++ b/client/network/src/lib.rs @@ -245,7 +245,6 @@ //! More precise usage details are still being worked on and will likely change in the future. mod behaviour; -mod chain; mod discovery; mod peer_info; mod protocol; diff --git a/client/network/src/light_client_requests/handler.rs b/client/network/src/light_client_requests/handler.rs index fb258304f2e8f..cb9bd960767ff 100644 --- a/client/network/src/light_client_requests/handler.rs +++ b/client/network/src/light_client_requests/handler.rs @@ -23,7 +23,6 @@ //! [`LightClientRequestHandler`](handler::LightClientRequestHandler). use crate::{ - chain::Client, config::ProtocolId, request_responses::{IncomingRequest, OutgoingResponse, ProtocolConfig}, schema, PeerId, @@ -32,27 +31,32 @@ use codec::{self, Decode, Encode}; use futures::{channel::mpsc, prelude::*}; use log::{debug, trace}; use prost::Message; -use sc_client_api::StorageProof; +use sc_client_api::{ProofProvider, StorageProof}; use sc_peerset::ReputationChange; use sp_core::{ hexdisplay::HexDisplay, storage::{ChildInfo, ChildType, PrefixedStorageKey}, }; use sp_runtime::{generic::BlockId, traits::Block}; -use std::sync::Arc; +use std::{marker::PhantomData, sync::Arc}; const LOG_TARGET: &str = "light-client-request-handler"; /// Handler for incoming light client requests from a remote peer. -pub struct LightClientRequestHandler { +pub struct LightClientRequestHandler { request_receiver: mpsc::Receiver, /// Blockchain client. - client: Arc>, + client: Arc, + _block: PhantomData, } -impl LightClientRequestHandler { +impl LightClientRequestHandler +where + B: Block, + Client: ProofProvider + Send + Sync + 'static, +{ /// Create a new [`crate::block_request_handler::BlockRequestHandler`]. - pub fn new(protocol_id: &ProtocolId, client: Arc>) -> (Self, ProtocolConfig) { + pub fn new(protocol_id: &ProtocolId, client: Arc) -> (Self, ProtocolConfig) { // For now due to lack of data on light client request handling in production systems, this // value is chosen to match the block request limit. let (tx, request_receiver) = mpsc::channel(20); @@ -60,7 +64,7 @@ impl LightClientRequestHandler { let mut protocol_config = super::generate_protocol_config(protocol_id); protocol_config.inbound_queue = Some(tx); - (Self { client, request_receiver }, protocol_config) + (Self { client, request_receiver, _block: PhantomData::default() }, protocol_config) } /// Run [`LightClientRequestHandler`]. diff --git a/client/network/src/protocol.rs b/client/network/src/protocol.rs index 8497dfd940de0..9999d278a249c 100644 --- a/client/network/src/protocol.rs +++ b/client/network/src/protocol.rs @@ -17,7 +17,6 @@ // along with this program. If not, see . use crate::{ - chain::Client, config::{self, ProtocolId, WarpSyncProvider}, error, request_responses::RequestFailure, @@ -49,6 +48,7 @@ use message::{ use notifications::{Notifications, NotificationsOut}; use prometheus_endpoint::{register, Gauge, GaugeVec, Opts, PrometheusError, Registry, U64}; use prost::Message as _; +use sc_client_api::{BlockBackend, HeaderBackend, ProofProvider}; use sc_consensus::import_queue::{BlockImportError, BlockImportStatus, IncomingBlock, Origin}; use sp_arithmetic::traits::SaturatedConversion; use sp_consensus::{block_validation::BlockAnnounceValidator, BlockOrigin}; @@ -76,6 +76,7 @@ pub mod message; pub mod sync; pub use notifications::{NotificationsSink, NotifsHandlerError, Ready}; +use sp_blockchain::HeaderMetadata; /// Interval at which we perform time based maintenance const TICK_TIMEOUT: time::Duration = time::Duration::from_millis(1100); @@ -158,7 +159,7 @@ impl Metrics { } // Lock must always be taken in order declared here. -pub struct Protocol { +pub struct Protocol { /// Interval at which we call `tick`. tick_timeout: Pin + Send>>, /// Pending list of messages to return from `poll` as a priority. @@ -167,10 +168,10 @@ pub struct Protocol { genesis_hash: B::Hash, /// State machine that handles the list of in-progress requests. Only full node peers are /// registered. - sync: ChainSync, + sync: ChainSync, // All connected peers. Contains both full and light node peers. peers: HashMap>, - chain: Arc>, + chain: Arc, /// List of nodes for which we perform additional logging because they are important for the /// user. important_peers: HashSet, @@ -283,18 +284,28 @@ impl BlockAnnouncesHandshake { } } -impl Protocol { +impl Protocol +where + B: BlockT, + Client: HeaderBackend + + BlockBackend + + HeaderMetadata + + ProofProvider + + Send + + Sync + + 'static, +{ /// Create a new instance. pub fn new( config: ProtocolConfig, - chain: Arc>, + chain: Arc, protocol_id: ProtocolId, network_config: &config::NetworkConfiguration, notifications_protocols_handshakes: Vec>, block_announce_validator: Box + Send>, metrics_registry: Option<&Registry>, warp_sync_provider: Option>>, - ) -> error::Result<(Protocol, sc_peerset::PeersetHandle, Vec<(PeerId, Multiaddr)>)> { + ) -> error::Result<(Protocol, sc_peerset::PeersetHandle, Vec<(PeerId, Multiaddr)>)> { let info = chain.info(); let sync = ChainSync::new( config.sync_mode(), @@ -1366,7 +1377,17 @@ pub enum CustomMessageOutcome { None, } -impl NetworkBehaviour for Protocol { +impl NetworkBehaviour for Protocol +where + B: BlockT, + Client: HeaderBackend + + BlockBackend + + HeaderMetadata + + ProofProvider + + Send + + Sync + + 'static, +{ type ConnectionHandler = ::ConnectionHandler; type OutEvent = CustomMessageOutcome; diff --git a/client/network/src/protocol/sync.rs b/client/network/src/protocol/sync.rs index 749366f6c1653..7e6a2a3c78508 100644 --- a/client/network/src/protocol/sync.rs +++ b/client/network/src/protocol/sync.rs @@ -39,9 +39,10 @@ use extra_requests::ExtraRequests; use futures::{stream::FuturesUnordered, task::Poll, Future, FutureExt, StreamExt}; use libp2p::PeerId; use log::{debug, error, info, trace, warn}; +use sc_client_api::{BlockBackend, ProofProvider}; use sc_consensus::{BlockImportError, BlockImportStatus, IncomingBlock}; use sp_arithmetic::traits::Saturating; -use sp_blockchain::{Error as ClientError, HeaderMetadata}; +use sp_blockchain::{Error as ClientError, HeaderBackend, HeaderMetadata}; use sp_consensus::{ block_validation::{BlockAnnounceValidator, Validation}, BlockOrigin, BlockStatus, @@ -54,6 +55,7 @@ use sp_runtime::{ }, EncodedJustification, Justifications, }; +pub use state::StateDownloadProgress; use state::StateSync; use std::{ collections::{hash_map::Entry, HashMap, HashSet}, @@ -63,6 +65,7 @@ use std::{ sync::Arc, }; use warp::{WarpProofRequest, WarpSync, WarpSyncProvider}; +pub use warp::{WarpSyncPhase, WarpSyncProgress}; mod blocks; mod extra_requests; @@ -194,9 +197,9 @@ struct GapSync { /// The main data structure which contains all the state for a chains /// active syncing strategy. -pub struct ChainSync { +pub struct ChainSync { /// Chain client. - client: Arc>, + client: Arc, /// The active peers that we are using to sync and their PeerSync status peers: HashMap>, /// A `BlockCollection` of blocks that are being downloaded from peers @@ -228,9 +231,9 @@ pub struct ChainSync { /// Stats per peer about the number of concurrent block announce validations. block_announce_validation_per_peer_stats: HashMap, /// State sync in progress, if any. - state_sync: Option>, + state_sync: Option>, /// Warp sync in progress, if any. - warp_sync: Option>, + warp_sync: Option>, /// Warp sync provider. warp_sync_provider: Option>>, /// Enable importing existing blocks. This is used used after the state download to @@ -329,30 +332,6 @@ pub enum SyncState { Downloading, } -/// Reported state download progress. -#[derive(Clone, Eq, PartialEq, Debug)] -pub struct StateDownloadProgress { - /// Estimated download percentage. - pub percentage: u32, - /// Total state size in bytes downloaded so far. - pub size: u64, -} - -/// Reported warp sync phase. -#[derive(Clone, Eq, PartialEq, Debug)] -pub enum WarpSyncPhase { - /// Waiting for peers to connect. - AwaitingPeers, - /// Downloading and verifying grandpa warp proofs. - DownloadingWarpProofs, - /// Downloading state data. - DownloadingState, - /// Importing state. - ImportingState, - /// Downloading block history. - DownloadingBlocks(NumberFor), -} - impl fmt::Display for WarpSyncPhase { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { @@ -365,15 +344,6 @@ impl fmt::Display for WarpSyncPhase { } } -/// Reported warp sync progress. -#[derive(Clone, Eq, PartialEq, Debug)] -pub struct WarpSyncProgress { - /// Estimated download percentage. - pub phase: WarpSyncPhase, - /// Total bytes downloaded so far. - pub total_bytes: u64, -} - /// Syncing status and statistics. #[derive(Clone)] pub struct Status { @@ -534,11 +504,21 @@ enum HasSlotForBlockAnnounceValidation { MaximumPeerSlotsReached, } -impl ChainSync { +impl ChainSync +where + B: BlockT, + Client: HeaderBackend + + BlockBackend + + HeaderMetadata + + ProofProvider + + Send + + Sync + + 'static, +{ /// Create a new instance. pub fn new( mode: SyncMode, - client: Arc>, + client: Arc, block_announce_validator: Box + Send>, max_parallel_downloads: u32, warp_sync_provider: Option>>, @@ -2741,7 +2721,11 @@ mod test { } /// Send a block annoucnement for the given `header`. - fn send_block_announce(header: Header, peer_id: &PeerId, sync: &mut ChainSync) { + fn send_block_announce( + header: Header, + peer_id: &PeerId, + sync: &mut ChainSync, + ) { let block_annnounce = BlockAnnounce { header: header.clone(), state: Some(BlockState::Best), @@ -2780,7 +2764,7 @@ mod test { /// Get a block request from `sync` and check that is matches the expected request. fn get_block_request( - sync: &mut ChainSync, + sync: &mut ChainSync, from: FromBlock, max: u32, peer: &PeerId, diff --git a/client/network/src/protocol/sync/state.rs b/client/network/src/protocol/sync/state.rs index 0df862a48333f..4eddc4c60867e 100644 --- a/client/network/src/protocol/sync/state.rs +++ b/client/network/src/protocol/sync/state.rs @@ -16,14 +16,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use super::StateDownloadProgress; -use crate::{ - chain::{Client, ImportedState}, - schema::v1::{StateEntry, StateRequest, StateResponse}, -}; +use crate::schema::v1::{StateEntry, StateRequest, StateResponse}; use codec::{Decode, Encode}; use log::debug; -use sc_client_api::CompactProof; +use sc_client_api::{CompactProof, ProofProvider}; +use sc_consensus::ImportedState; use smallvec::SmallVec; use sp_core::storage::well_known_keys; use sp_runtime::traits::{Block as BlockT, Header, NumberFor}; @@ -33,18 +30,27 @@ use std::{collections::HashMap, sync::Arc}; /// State sync state machine. Accumulates partial state data until it /// is ready to be imported. -pub struct StateSync { +pub struct StateSync { target_block: B::Hash, target_header: B::Header, target_root: B::Hash, last_key: SmallVec<[Vec; 2]>, state: HashMap, (Vec<(Vec, Vec)>, Vec>)>, complete: bool, - client: Arc>, + client: Arc, imported_bytes: u64, skip_proof: bool, } +/// Reported state download progress. +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct StateDownloadProgress { + /// Estimated download percentage. + pub percentage: u32, + /// Total state size in bytes downloaded so far. + pub size: u64, +} + /// Import state chunk result. pub enum ImportResult { /// State is complete and ready for import. @@ -55,9 +61,13 @@ pub enum ImportResult { BadResponse, } -impl StateSync { +impl StateSync +where + B: BlockT, + Client: ProofProvider + Send + Sync + 'static, +{ /// Create a new instance. - pub fn new(client: Arc>, target: B::Header, skip_proof: bool) -> Self { + pub fn new(client: Arc, target: B::Header, skip_proof: bool) -> Self { Self { client, target_block: target.hash(), @@ -71,7 +81,7 @@ impl StateSync { } } - /// Validate and import a state reponse. + /// Validate and import a state response. pub fn import(&mut self, response: StateResponse) -> ImportResult { if response.entries.is_empty() && response.proof.is_empty() { debug!(target: "sync", "Bad state response"); diff --git a/client/network/src/protocol/sync/warp.rs b/client/network/src/protocol/sync/warp.rs index f12deb2dbb432..fa2c23a0b33c1 100644 --- a/client/network/src/protocol/sync/warp.rs +++ b/client/network/src/protocol/sync/warp.rs @@ -17,23 +17,44 @@ // along with this program. If not, see . ///! Warp sync support. -pub use super::state::ImportResult; -use super::state::StateSync; +use super::state::{ImportResult, StateSync}; +use crate::schema::v1::{StateRequest, StateResponse}; pub use crate::warp_request_handler::{ EncodedProof, Request as WarpProofRequest, VerificationResult, WarpSyncProvider, }; -use crate::{ - chain::Client, - schema::v1::{StateRequest, StateResponse}, - WarpSyncPhase, WarpSyncProgress, -}; +use sc_client_api::ProofProvider; +use sp_blockchain::HeaderBackend; use sp_finality_grandpa::{AuthorityList, SetId}; use sp_runtime::traits::{Block as BlockT, NumberFor, Zero}; use std::sync::Arc; -enum Phase { +enum Phase { WarpProof { set_id: SetId, authorities: AuthorityList, last_hash: B::Hash }, - State(StateSync), + State(StateSync), +} + +/// Reported warp sync phase. +#[derive(Clone, Eq, PartialEq, Debug)] +pub enum WarpSyncPhase { + /// Waiting for peers to connect. + AwaitingPeers, + /// Downloading and verifying grandpa warp proofs. + DownloadingWarpProofs, + /// Downloading state data. + DownloadingState, + /// Importing state. + ImportingState, + /// Downloading block history. + DownloadingBlocks(NumberFor), +} + +/// Reported warp sync progress. +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct WarpSyncProgress { + /// Estimated download percentage. + pub phase: WarpSyncPhase, + /// Total bytes downloaded so far. + pub total_bytes: u64, } /// Import warp proof result. @@ -45,19 +66,20 @@ pub enum WarpProofImportResult { } /// Warp sync state machine. Accumulates warp proofs and state. -pub struct WarpSync { - phase: Phase, - client: Arc>, +pub struct WarpSync { + phase: Phase, + client: Arc, warp_sync_provider: Arc>, total_proof_bytes: u64, } -impl WarpSync { +impl WarpSync +where + B: BlockT, + Client: HeaderBackend + ProofProvider + 'static, +{ /// Create a new instance. - pub fn new( - client: Arc>, - warp_sync_provider: Arc>, - ) -> Self { + pub fn new(client: Arc, warp_sync_provider: Arc>) -> Self { let last_hash = client.hash(Zero::zero()).unwrap().expect("Genesis header always exists"); let phase = Phase::WarpProof { set_id: 0, diff --git a/client/network/src/service.rs b/client/network/src/service.rs index e88e7924954b3..6ffc0ec49bd67 100644 --- a/client/network/src/service.rs +++ b/client/network/src/service.rs @@ -54,16 +54,18 @@ use libp2p::{ ping::Failure as PingFailure, swarm::{ AddressScore, ConnectionError, ConnectionLimits, DialError, NetworkBehaviour, - PendingConnectionError, SwarmBuilder, SwarmEvent, + PendingConnectionError, Swarm, SwarmBuilder, SwarmEvent, }, Multiaddr, PeerId, }; use log::{debug, error, info, trace, warn}; use metrics::{Histogram, HistogramVec, MetricSources, Metrics}; use parking_lot::Mutex; +use sc_client_api::{BlockBackend, ProofProvider}; use sc_consensus::{BlockImportError, BlockImportStatus, ImportQueue, Link}; use sc_peerset::PeersetHandle; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; +use sp_blockchain::{HeaderBackend, HeaderMetadata}; use sp_runtime::traits::{Block as BlockT, NumberFor}; use std::{ borrow::Cow, @@ -98,7 +100,7 @@ pub use libp2p::{ }, kad::record::Key as KademliaKey, }; -pub use signature::*; +pub use signature::Signature; /// Substrate network service. Handles network IO and manages connectivity. pub struct NetworkService { @@ -130,13 +132,24 @@ pub struct NetworkService { _marker: PhantomData, } -impl NetworkWorker { +impl NetworkWorker +where + B: BlockT + 'static, + H: ExHashT, + Client: HeaderBackend + + BlockBackend + + HeaderMetadata + + ProofProvider + + Send + + Sync + + 'static, +{ /// Creates the network service. /// /// Returns a `NetworkWorker` that implements `Future` and must be regularly polled in order /// for the network processing to advance. From it, you can extract a `NetworkService` using /// `worker.service()`. The `NetworkService` can be shared through the codebase. - pub fn new(mut params: Params) -> Result { + pub fn new(mut params: Params) -> Result { // Ensure the listen addresses are consistent with the transport. ensure_addresses_consistent_with_transport( params.network_config.listen_addresses.iter(), @@ -247,7 +260,7 @@ impl NetworkWorker { // Build the swarm. let client = params.chain.clone(); - let (mut swarm, bandwidth): (Swarm, _) = { + let (mut swarm, bandwidth): (Swarm>, _) = { let user_agent = format!( "{} ({})", params.network_config.client_version, params.network_config.node_name @@ -392,14 +405,18 @@ impl NetworkWorker { // Listen on multiaddresses. for addr in ¶ms.network_config.listen_addresses { - if let Err(err) = Swarm::::listen_on(&mut swarm, addr.clone()) { + if let Err(err) = Swarm::>::listen_on(&mut swarm, addr.clone()) { warn!(target: "sub-libp2p", "Can't listen on {} because: {:?}", addr, err) } } // Add external addresses. for addr in ¶ms.network_config.public_addresses { - Swarm::::add_external_address(&mut swarm, addr.clone(), AddressScore::Infinite); + Swarm::>::add_external_address( + &mut swarm, + addr.clone(), + AddressScore::Infinite, + ); } let external_addresses = Arc::new(Mutex::new(Vec::new())); @@ -540,14 +557,14 @@ impl NetworkWorker { /// Returns the local `PeerId`. pub fn local_peer_id(&self) -> &PeerId { - Swarm::::local_peer_id(&self.network_service) + Swarm::>::local_peer_id(&self.network_service) } /// Returns the list of addresses we are listening on. /// /// Does **NOT** include a trailing `/p2p/` with our `PeerId`. pub fn listen_addresses(&self) -> impl Iterator { - Swarm::::listeners(&self.network_service) + Swarm::>::listeners(&self.network_service) } /// Get network state. @@ -627,7 +644,7 @@ impl NetworkWorker { .collect() }; - let peer_id = Swarm::::local_peer_id(&swarm).to_base58(); + let peer_id = Swarm::>::local_peer_id(&swarm).to_base58(); let listened_addresses = swarm.listeners().cloned().collect(); let external_addresses = swarm.external_addresses().map(|r| &r.addr).cloned().collect(); @@ -1445,7 +1462,18 @@ enum ServiceToWorkerMsg { /// /// You are encouraged to poll this in a separate background thread or task. #[must_use = "The NetworkWorker must be polled in order for the network to advance"] -pub struct NetworkWorker { +pub struct NetworkWorker +where + B: BlockT + 'static, + H: ExHashT, + Client: HeaderBackend + + BlockBackend + + HeaderMetadata + + ProofProvider + + Send + + Sync + + 'static, +{ /// Updated by the `NetworkWorker` and loaded by the `NetworkService`. external_addresses: Arc>>, /// Updated by the `NetworkWorker` and loaded by the `NetworkService`. @@ -1455,7 +1483,7 @@ pub struct NetworkWorker { /// The network service that can be extracted and shared through the codebase. service: Arc>, /// The *actual* network. - network_service: Swarm, + network_service: Swarm>, /// The import queue that was passed at initialization. import_queue: Box>, /// Messages from the [`NetworkService`] that must be processed. @@ -1473,7 +1501,18 @@ pub struct NetworkWorker { tx_handler_controller: transactions::TransactionsHandlerController, } -impl Future for NetworkWorker { +impl Future for NetworkWorker +where + B: BlockT + 'static, + H: ExHashT, + Client: HeaderBackend + + BlockBackend + + HeaderMetadata + + ProofProvider + + Send + + Sync + + 'static, +{ type Output = (); fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context) -> Poll { @@ -2055,10 +2094,11 @@ impl Future for NetworkWorker { // Update the variables shared with the `NetworkService`. this.num_connected.store(num_connected_peers, Ordering::Relaxed); { - let external_addresses = Swarm::::external_addresses(&this.network_service) - .map(|r| &r.addr) - .cloned() - .collect(); + let external_addresses = + Swarm::>::external_addresses(&this.network_service) + .map(|r| &r.addr) + .cloned() + .collect(); *this.external_addresses.lock() = external_addresses; } @@ -2113,17 +2153,46 @@ impl Future for NetworkWorker { } } -impl Unpin for NetworkWorker {} - -/// The libp2p swarm, customized for our needs. -type Swarm = libp2p::swarm::Swarm>; +impl Unpin for NetworkWorker +where + B: BlockT + 'static, + H: ExHashT, + Client: HeaderBackend + + BlockBackend + + HeaderMetadata + + ProofProvider + + Send + + Sync + + 'static, +{ +} // Implementation of `import_queue::Link` trait using the available local variables. -struct NetworkLink<'a, B: BlockT> { - protocol: &'a mut Swarm, +struct NetworkLink<'a, B, Client> +where + B: BlockT, + Client: HeaderBackend + + BlockBackend + + HeaderMetadata + + ProofProvider + + Send + + Sync + + 'static, +{ + protocol: &'a mut Swarm>, } -impl<'a, B: BlockT> Link for NetworkLink<'a, B> { +impl<'a, B, Client> Link for NetworkLink<'a, B, Client> +where + B: BlockT, + Client: HeaderBackend + + BlockBackend + + HeaderMetadata + + ProofProvider + + Send + + Sync + + 'static, +{ fn blocks_processed( &mut self, imported: usize, diff --git a/client/network/src/state_request_handler.rs b/client/network/src/state_request_handler.rs index 10a77061a031d..3e208e22e3d93 100644 --- a/client/network/src/state_request_handler.rs +++ b/client/network/src/state_request_handler.rs @@ -18,7 +18,6 @@ //! `crate::request_responses::RequestResponsesBehaviour`. use crate::{ - chain::Client, config::ProtocolId, request_responses::{IncomingRequest, OutgoingResponse, ProtocolConfig}, schema::v1::{KeyValueStateEntry, StateEntry, StateRequest, StateResponse}, @@ -32,6 +31,7 @@ use futures::{ use log::{debug, trace}; use lru::LruCache; use prost::Message; +use sc_client_api::ProofProvider; use sp_runtime::{generic::BlockId, traits::Block as BlockT}; use std::{ hash::{Hash, Hasher}, @@ -96,8 +96,8 @@ enum SeenRequestsValue { } /// Handler for incoming block requests from a remote peer. -pub struct StateRequestHandler { - client: Arc>, +pub struct StateRequestHandler { + client: Arc, request_receiver: mpsc::Receiver, /// Maps from request to number of times we have seen this request. /// @@ -105,11 +105,15 @@ pub struct StateRequestHandler { seen_requests: LruCache, SeenRequestsValue>, } -impl StateRequestHandler { +impl StateRequestHandler +where + B: BlockT, + Client: ProofProvider + Send + Sync + 'static, +{ /// Create a new [`StateRequestHandler`]. pub fn new( protocol_id: &ProtocolId, - client: Arc>, + client: Arc, num_peer_hint: usize, ) -> (Self, ProtocolConfig) { // Reserve enough request slots for one request per peer when we are at the maximum diff --git a/client/network/test/src/lib.rs b/client/network/test/src/lib.rs index 552879f35d934..1760c08759761 100644 --- a/client/network/test/src/lib.rs +++ b/client/network/test/src/lib.rs @@ -223,7 +223,7 @@ pub struct Peer { block_import: BlockImportAdapter, select_chain: Option>, backend: Option>, - network: NetworkWorker::Hash>, + network: NetworkWorker::Hash, PeersFullClient>, imported_blocks_stream: Pin> + Send>>, finality_notification_stream: Pin> + Send>>, listen_addr: Multiaddr, @@ -498,7 +498,7 @@ where } /// Get a reference to the network worker. - pub fn network(&self) -> &NetworkWorker::Hash> { + pub fn network(&self) -> &NetworkWorker::Hash, PeersFullClient> { &self.network } diff --git a/client/service/src/lib.rs b/client/service/src/lib.rs index 6d9d99994288c..a48e6168c52cc 100644 --- a/client/service/src/lib.rs +++ b/client/service/src/lib.rs @@ -39,8 +39,10 @@ use std::{collections::HashMap, io, net::SocketAddr, pin::Pin}; use codec::{Decode, Encode}; use futures::{Future, FutureExt, StreamExt}; use log::{debug, error, warn}; +use sc_client_api::{BlockBackend, ProofProvider}; use sc_network::PeerId; use sc_utils::mpsc::TracingUnboundedReceiver; +use sp_blockchain::HeaderMetadata; use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, Header as HeaderT}, @@ -135,11 +137,18 @@ pub struct PartialComponents + HeaderBackend, + C: BlockchainEvents + + HeaderBackend + + BlockBackend + + HeaderMetadata + + ProofProvider + + Send + + Sync + + 'static, H: sc_network::ExHashT, >( role: Role, - mut network: sc_network::NetworkWorker, + mut network: sc_network::NetworkWorker, client: Arc, mut rpc_rx: TracingUnboundedReceiver>, should_have_peers: bool, @@ -461,7 +470,13 @@ where impl sc_network::config::TransactionPool for TransactionPoolAdapter where - C: sc_network::config::Client + Send + Sync, + C: HeaderBackend + + BlockBackend + + HeaderMetadata + + ProofProvider + + Send + + Sync + + 'static, Pool: 'static + TransactionPool, B: BlockT, H: std::hash::Hash + Eq + sp_runtime::traits::Member + sp_runtime::traits::MaybeSerialize, From 2ffb32010a4db508ba858ee3209e6677687f6eb8 Mon Sep 17 00:00:00 2001 From: yjh Date: Sat, 30 Apr 2022 14:11:15 +0800 Subject: [PATCH 170/484] import Vec when run on no_std for wasmi (#11319) * import Vec when run on no_std for wasmi * cargo fmt --- primitives/wasm-interface/src/wasmi_impl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/primitives/wasm-interface/src/wasmi_impl.rs b/primitives/wasm-interface/src/wasmi_impl.rs index 39afce4df4eb2..2239eb5f38658 100644 --- a/primitives/wasm-interface/src/wasmi_impl.rs +++ b/primitives/wasm-interface/src/wasmi_impl.rs @@ -16,8 +16,8 @@ // limitations under the License. //! Implementation of conversions between Substrate and wasmi types. - use crate::{Signature, Value, ValueType}; +use sp_std::vec::Vec; impl From for wasmi::RuntimeValue { fn from(value: Value) -> Self { From d6c02f6c6e9b303c3a609bd28a033be4d30364f6 Mon Sep 17 00:00:00 2001 From: Falco Hirschenberger Date: Sat, 30 Apr 2022 23:28:27 +0200 Subject: [PATCH 171/484] Apply some clippy lints (#11154) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Apply some clippy hints * Revert clippy ci changes * Update client/cli/src/commands/generate.rs Co-authored-by: Bastian Köcher * Update client/cli/src/commands/inspect_key.rs Co-authored-by: Bastian Köcher * Update client/db/src/bench.rs Co-authored-by: Bastian Köcher * Update client/db/src/bench.rs Co-authored-by: Bastian Köcher * Update client/service/src/client/block_rules.rs Co-authored-by: Bastian Köcher * Update client/service/src/client/block_rules.rs Co-authored-by: Bastian Köcher * Update client/network/src/transactions.rs Co-authored-by: Bastian Köcher * Update client/network/src/protocol.rs Co-authored-by: Bastian Köcher * Revert due to missing `or_default` function. * Fix compilation and simplify code * Undo change that corrupts benchmark. * fix clippy * Update client/service/test/src/lib.rs Co-authored-by: Bastian Köcher * Update client/state-db/src/noncanonical.rs Co-authored-by: Bastian Köcher * Update client/state-db/src/noncanonical.rs remove leftovers! * Update client/tracing/src/logging/directives.rs Co-authored-by: Bastian Köcher * Update utils/fork-tree/src/lib.rs Co-authored-by: Bastian Köcher * added needed ref * Update frame/referenda/src/benchmarking.rs * Simplify byte-vec creation * let's just not overlap the ranges * Correction * cargo fmt * Update utils/frame/benchmarking-cli/src/shared/stats.rs Co-authored-by: Bastian Köcher * Update utils/frame/benchmarking-cli/src/pallet/command.rs Co-authored-by: Bastian Köcher * Update utils/frame/benchmarking-cli/src/pallet/command.rs Co-authored-by: Bastian Köcher Co-authored-by: Bastian Köcher Co-authored-by: Giles Cope --- bin/node-template/node/src/rpc.rs | 2 +- bin/node-template/node/src/service.rs | 4 +- bin/node-template/pallets/template/src/lib.rs | 2 +- bin/node-template/runtime/src/lib.rs | 2 +- bin/node/bench/src/core.rs | 2 +- bin/node/bench/src/main.rs | 29 +- bin/node/bench/src/state_sizes.rs | 2 +- bin/node/bench/src/trie.rs | 2 +- bin/node/cli/src/service.rs | 6 +- bin/node/runtime/src/lib.rs | 6 +- bin/node/testing/src/bench.rs | 19 +- bin/utils/chain-spec-builder/src/main.rs | 2 +- client/allocator/src/freeing_bump.rs | 2 +- client/api/src/backend.rs | 2 +- client/api/src/execution_extensions.rs | 2 +- client/api/src/in_mem.rs | 34 +-- client/api/src/lib.rs | 6 +- client/api/src/notifications.rs | 7 +- client/api/src/notifications/registry.rs | 14 +- client/authority-discovery/src/worker.rs | 6 +- .../src/worker/addr_cache.rs | 5 +- .../basic-authorship/src/basic_authorship.rs | 2 +- client/beefy/rpc/src/lib.rs | 2 +- client/beefy/src/lib.rs | 4 +- client/block-builder/src/lib.rs | 2 +- client/cli/src/commands/generate.rs | 9 +- client/cli/src/commands/inspect_key.rs | 10 +- client/cli/src/commands/purge_chain_cmd.rs | 2 +- client/cli/src/commands/utils.rs | 4 +- client/cli/src/commands/vanity.rs | 20 +- client/cli/src/config.rs | 4 +- client/cli/src/lib.rs | 4 +- client/cli/src/params/keystore_params.rs | 2 +- client/cli/src/params/node_key_params.rs | 2 +- client/cli/src/params/shared_params.rs | 2 +- client/consensus/aura/src/import_queue.rs | 8 +- client/consensus/aura/src/lib.rs | 8 +- client/consensus/babe/rpc/src/lib.rs | 4 +- client/consensus/babe/src/authorship.rs | 6 +- client/consensus/babe/src/lib.rs | 18 +- client/consensus/babe/src/verification.rs | 12 +- client/consensus/common/src/block_import.rs | 3 +- client/consensus/common/src/import_queue.rs | 12 +- .../common/src/import_queue/basic_queue.rs | 4 +- .../common/src/import_queue/buffered_link.rs | 4 +- client/consensus/common/src/longest_chain.rs | 8 +- client/consensus/epochs/src/lib.rs | 8 +- .../manual-seal/src/consensus/babe.rs | 18 +- client/consensus/pow/src/lib.rs | 34 +-- client/consensus/pow/src/worker.rs | 2 +- client/consensus/slots/src/aux_schema.rs | 2 +- client/consensus/slots/src/lib.rs | 2 +- client/db/src/bench.rs | 43 ++- client/db/src/lib.rs | 102 +++---- client/db/src/offchain.rs | 4 +- client/db/src/storage_cache.rs | 14 +- client/db/src/upgrade.rs | 2 +- client/db/src/utils.rs | 30 +- .../runtime_blob/data_segments_snapshot.rs | 2 +- .../common/src/runtime_blob/runtime_blob.rs | 4 +- client/executor/common/src/sandbox.rs | 14 +- .../common/src/sandbox/wasmer_backend.rs | 6 +- .../common/src/sandbox/wasmi_backend.rs | 4 +- client/executor/src/native_executor.rs | 5 +- client/executor/src/wasm_runtime.rs | 4 +- client/executor/wasmi/src/lib.rs | 26 +- client/executor/wasmtime/src/host.rs | 19 +- client/finality-grandpa/rpc/src/report.rs | 4 +- client/finality-grandpa/src/authorities.rs | 3 +- client/finality-grandpa/src/aux_schema.rs | 2 +- .../src/communication/gossip.rs | 37 +-- .../finality-grandpa/src/communication/mod.rs | 6 +- client/finality-grandpa/src/environment.rs | 10 +- client/finality-grandpa/src/import.rs | 7 +- client/finality-grandpa/src/observer.rs | 6 +- client/finality-grandpa/src/voting_rule.rs | 2 +- client/finality-grandpa/src/warp_proof.rs | 2 +- client/informant/src/lib.rs | 6 +- client/keystore/src/local.rs | 10 +- client/network-gossip/src/state_machine.rs | 45 ++- client/network/src/behaviour.rs | 3 +- client/network/src/bitswap.rs | 6 +- client/network/src/block_request_handler.rs | 2 +- client/network/src/discovery.rs | 16 +- client/network/src/light_client_requests.rs | 6 +- client/network/src/protocol.rs | 23 +- client/network/src/protocol/message.rs | 2 +- .../src/protocol/notifications/behaviour.rs | 37 +-- .../src/protocol/notifications/handler.rs | 4 +- client/network/src/protocol/sync.rs | 123 ++++---- .../src/protocol/sync/extra_requests.rs | 2 +- client/network/src/protocol/sync/state.rs | 26 +- client/network/src/protocol/sync/warp.rs | 6 +- client/network/src/request_responses.rs | 47 ++- client/network/src/service.rs | 48 ++- client/network/src/service/metrics.rs | 4 +- client/network/src/service/out_events.rs | 18 +- client/network/src/state_request_handler.rs | 9 +- client/network/src/transactions.rs | 10 +- client/network/src/transport.rs | 6 +- client/network/src/warp_request_handler.rs | 6 +- client/network/test/src/lib.rs | 20 +- client/offchain/src/api/http.rs | 2 +- client/peerset/src/lib.rs | 13 +- client/peerset/src/peersstate.rs | 33 +- client/peerset/tests/fuzz.rs | 18 +- client/rpc-api/src/author/error.rs | 4 +- client/rpc-servers/src/middleware.rs | 5 +- client/rpc/src/author/mod.rs | 6 +- client/rpc/src/chain/mod.rs | 4 +- client/rpc/src/state/state_full.rs | 6 +- client/service/src/builder.rs | 12 +- client/service/src/client/block_rules.rs | 6 +- client/service/src/client/call_executor.rs | 4 +- client/service/src/client/client.rs | 38 ++- client/service/src/client/wasm_override.rs | 48 ++- client/service/src/client/wasm_substitutes.rs | 4 +- client/service/src/lib.rs | 12 +- client/service/src/metrics.rs | 4 +- client/service/src/task_manager/mod.rs | 4 +- client/service/test/src/lib.rs | 18 +- client/state-db/src/lib.rs | 10 +- client/state-db/src/noncanonical.rs | 27 +- client/state-db/src/pruning.rs | 6 +- client/sync-state-rpc/src/lib.rs | 2 +- client/telemetry/src/lib.rs | 8 +- client/telemetry/src/node.rs | 2 +- client/tracing/src/block/mod.rs | 20 +- client/tracing/src/lib.rs | 5 +- client/tracing/src/logging/directives.rs | 2 +- client/tracing/src/logging/mod.rs | 46 ++- client/transaction-pool/api/src/lib.rs | 2 +- client/transaction-pool/src/api.rs | 12 +- client/transaction-pool/src/graph/pool.rs | 2 +- client/transaction-pool/src/graph/ready.rs | 6 +- .../src/graph/validated_pool.rs | 18 +- client/transaction-pool/src/lib.rs | 17 +- client/transaction-pool/src/revalidation.rs | 8 +- client/utils/src/pubsub.rs | 8 +- frame/assets/src/benchmarking.rs | 8 +- frame/assets/src/functions.rs | 31 +- frame/assets/src/impl_stored_map.rs | 2 +- frame/assets/src/lib.rs | 16 +- frame/assets/src/types.rs | 4 +- frame/atomic-swap/src/lib.rs | 6 +- frame/aura/src/lib.rs | 2 +- frame/authorship/src/lib.rs | 10 +- frame/babe/src/lib.rs | 6 +- frame/bags-list/fuzzer/src/main.rs | 2 +- frame/bags-list/remote-tests/src/lib.rs | 2 +- frame/bags-list/src/benchmarks.rs | 2 +- frame/bags-list/src/lib.rs | 4 +- frame/bags-list/src/list/mod.rs | 18 +- frame/bags-list/src/mock.rs | 2 +- frame/balances/src/lib.rs | 52 ++-- frame/benchmarking/src/analysis.rs | 6 +- frame/bounties/src/benchmarking.rs | 2 +- frame/bounties/src/lib.rs | 4 +- frame/child-bounties/src/benchmarking.rs | 8 +- frame/child-bounties/src/lib.rs | 6 +- frame/collective/src/benchmarking.rs | 32 +- frame/contracts/src/benchmarking/code.rs | 19 +- frame/contracts/src/benchmarking/mod.rs | 26 +- frame/contracts/src/exec.rs | 29 +- frame/contracts/src/gas.rs | 2 +- frame/contracts/src/schedule.rs | 2 +- frame/contracts/src/storage.rs | 8 +- frame/contracts/src/storage/meter.rs | 4 +- frame/contracts/src/wasm/code_cache.rs | 5 +- frame/contracts/src/wasm/prepare.rs | 34 +-- frame/contracts/src/wasm/runtime.rs | 38 +-- frame/conviction-voting/src/benchmarking.rs | 20 +- frame/conviction-voting/src/lib.rs | 3 +- frame/democracy/src/benchmarking.rs | 34 +-- frame/democracy/src/lib.rs | 27 +- .../src/benchmarking.rs | 2 +- .../election-provider-multi-phase/src/lib.rs | 47 ++- .../src/unsigned.rs | 19 +- .../solution-type/src/lib.rs | 4 +- .../solution-type/src/single_page.rs | 2 +- frame/election-provider-support/src/lib.rs | 2 +- frame/elections-phragmen/src/benchmarking.rs | 3 +- frame/elections-phragmen/src/lib.rs | 288 +++++++++--------- frame/elections-phragmen/src/migrations/v5.rs | 6 +- frame/examples/basic/src/benchmarking.rs | 2 +- frame/examples/basic/src/lib.rs | 4 +- frame/examples/offchain-worker/src/lib.rs | 4 +- frame/executive/src/lib.rs | 10 +- frame/gilt/src/lib.rs | 2 +- frame/grandpa/src/lib.rs | 23 +- frame/identity/src/benchmarking.rs | 24 +- frame/identity/src/lib.rs | 36 +-- frame/identity/src/types.rs | 10 +- frame/im-online/src/lib.rs | 6 +- frame/indices/src/lib.rs | 6 +- frame/lottery/src/lib.rs | 8 +- frame/membership/src/lib.rs | 2 +- frame/multisig/src/benchmarking.rs | 26 +- frame/multisig/src/lib.rs | 3 +- frame/nicks/src/lib.rs | 6 +- frame/offences/benchmarking/src/lib.rs | 32 +- frame/offences/src/lib.rs | 5 +- frame/offences/src/migration.rs | 4 +- frame/preimage/src/benchmarking.rs | 24 +- frame/preimage/src/lib.rs | 18 +- frame/proxy/src/benchmarking.rs | 24 +- frame/proxy/src/lib.rs | 40 +-- frame/recovery/src/lib.rs | 4 +- frame/referenda/src/benchmarking.rs | 11 +- frame/referenda/src/lib.rs | 14 +- frame/referenda/src/types.rs | 6 +- frame/scheduler/src/lib.rs | 6 +- frame/scored-pool/src/lib.rs | 4 +- frame/session/src/historical/shared.rs | 11 +- frame/session/src/lib.rs | 11 +- frame/society/src/lib.rs | 36 ++- frame/staking/reward-fn/src/lib.rs | 28 +- frame/staking/src/benchmarking.rs | 36 +-- frame/staking/src/lib.rs | 4 +- frame/staking/src/pallet/impls.rs | 14 +- frame/staking/src/pallet/mod.rs | 16 +- frame/staking/src/slashing.rs | 4 +- frame/staking/src/testing_utils.rs | 4 +- frame/state-trie-migration/src/lib.rs | 24 +- .../src/construct_runtime/expand/config.rs | 8 +- .../procedural/src/construct_runtime/mod.rs | 10 +- .../procedural/src/pallet/expand/call.rs | 2 +- .../src/pallet/expand/genesis_config.rs | 2 +- .../procedural/src/pallet/expand/storage.rs | 10 +- .../procedural/src/pallet/parse/call.rs | 7 +- .../procedural/src/pallet/parse/config.rs | 7 +- .../procedural/src/pallet/parse/error.rs | 4 +- .../src/pallet/parse/extra_constants.rs | 7 +- .../src/pallet/parse/genesis_build.rs | 3 +- .../src/pallet/parse/genesis_config.rs | 2 +- .../procedural/src/pallet/parse/hooks.rs | 7 +- .../procedural/src/pallet/parse/inherent.rs | 7 +- .../procedural/src/pallet/parse/origin.rs | 2 +- .../src/pallet/parse/pallet_struct.rs | 4 +- .../procedural/src/pallet/parse/storage.rs | 14 +- .../src/pallet/parse/validate_unsigned.rs | 7 +- .../src/storage/genesis_config/builder_def.rs | 4 +- .../procedural/src/storage/instance_trait.rs | 2 +- frame/support/procedural/src/storage/mod.rs | 2 +- frame/support/src/hash.rs | 4 +- frame/support/src/lib.rs | 2 +- frame/support/src/storage/child.rs | 4 +- .../src/storage/generator/double_map.rs | 2 +- frame/support/src/storage/hashed.rs | 26 +- frame/support/src/storage/mod.rs | 2 +- .../support/src/storage/types/counted_map.rs | 5 +- frame/support/src/storage/types/nmap.rs | 2 +- frame/support/src/storage/unhashed.rs | 4 +- frame/support/src/traits/dispatch.rs | 2 +- frame/support/src/traits/members.rs | 2 +- frame/support/src/traits/metadata.rs | 2 +- frame/support/src/traits/misc.rs | 4 +- frame/support/src/traits/stored_map.rs | 2 +- frame/support/src/traits/tokens/currency.rs | 2 +- frame/support/src/traits/tokens/fungible.rs | 2 +- .../src/traits/tokens/fungible/balanced.rs | 4 +- frame/support/src/traits/tokens/fungibles.rs | 2 +- .../src/traits/tokens/fungibles/balanced.rs | 4 +- frame/support/src/weights.rs | 10 +- .../src/extensions/check_non_zero_sender.rs | 2 +- frame/system/src/extensions/check_weight.rs | 6 +- frame/system/src/lib.rs | 15 +- frame/system/src/limits.rs | 4 +- frame/system/src/migrations/mod.rs | 6 +- frame/system/src/offchain.rs | 4 +- frame/timestamp/src/lib.rs | 4 +- frame/tips/src/benchmarking.rs | 4 +- frame/tips/src/lib.rs | 4 +- .../asset-tx-payment/src/payment.rs | 11 +- frame/transaction-payment/src/lib.rs | 6 +- frame/transaction-payment/src/payment.rs | 2 +- frame/transaction-storage/src/lib.rs | 9 +- frame/uniques/src/benchmarking.rs | 12 +- frame/uniques/src/functions.rs | 6 +- frame/uniques/src/impl_nonfungibles.rs | 12 +- frame/uniques/src/lib.rs | 12 +- frame/utility/src/benchmarking.rs | 2 +- frame/vesting/src/benchmarking.rs | 14 +- frame/vesting/src/lib.rs | 10 +- frame/whitelist/src/benchmarking.rs | 2 +- .../api/proc-macro/src/decl_runtime_apis.rs | 16 +- .../api/proc-macro/src/impl_runtime_apis.rs | 8 +- .../proc-macro/src/mock_impl_runtime_apis.rs | 4 +- primitives/api/proc-macro/src/utils.rs | 14 +- primitives/api/src/lib.rs | 7 +- primitives/application-crypto/src/ecdsa.rs | 2 +- primitives/application-crypto/src/ed25519.rs | 2 +- primitives/application-crypto/src/sr25519.rs | 2 +- primitives/arithmetic/benches/bench.rs | 6 +- primitives/arithmetic/fuzzer/src/normalize.rs | 18 +- primitives/arithmetic/src/lib.rs | 9 +- primitives/arithmetic/src/rational.rs | 9 +- primitives/blockchain/src/backend.rs | 4 +- primitives/blockchain/src/header_metadata.rs | 10 +- primitives/consensus/common/src/evaluation.rs | 2 +- primitives/consensus/common/src/lib.rs | 2 +- primitives/core/benches/bench.rs | 12 +- .../core/hashing/proc-macro/src/impls.rs | 2 +- primitives/core/src/crypto.rs | 2 +- primitives/core/src/ecdsa.rs | 6 +- primitives/core/src/ed25519.rs | 2 +- primitives/core/src/hexdisplay.rs | 4 +- primitives/debug-derive/src/impls.rs | 7 +- primitives/externalities/src/extensions.rs | 4 +- primitives/finality-grandpa/src/lib.rs | 2 +- primitives/io/src/lib.rs | 23 +- primitives/keyring/src/ed25519.rs | 2 +- primitives/keyring/src/sr25519.rs | 2 +- primitives/keystore/src/testing.rs | 2 +- primitives/merkle-mountain-range/src/lib.rs | 2 +- .../npos-elections/fuzzer/src/common.rs | 2 +- .../fuzzer/src/phragmen_balancing.rs | 16 +- .../fuzzer/src/phragmms_balancing.rs | 11 +- .../npos-elections/fuzzer/src/reduce.rs | 2 +- primitives/npos-elections/src/helpers.rs | 12 +- primitives/npos-elections/src/lib.rs | 4 +- primitives/npos-elections/src/phragmen.rs | 5 +- primitives/npos-elections/src/phragmms.rs | 13 +- primitives/npos-elections/src/pjr.rs | 4 +- primitives/npos-elections/src/reduce.rs | 18 +- primitives/panic-handler/src/lib.rs | 10 +- .../proc-macro/src/pass_by/enum_.rs | 2 +- .../runtime-interface/proc-macro/src/utils.rs | 38 ++- primitives/runtime/src/generic/digest.rs | 8 +- primitives/runtime/src/offchain/http.rs | 2 +- .../runtime/src/offchain/storage_lock.rs | 2 +- primitives/runtime/src/runtime_string.rs | 4 +- primitives/runtime/src/traits.rs | 4 +- primitives/sandbox/src/embedded_executor.rs | 2 +- primitives/state-machine/src/backend.rs | 4 +- primitives/state-machine/src/basic.rs | 2 +- primitives/state-machine/src/ext.rs | 16 +- .../state-machine/src/in_memory_backend.rs | 2 +- primitives/state-machine/src/lib.rs | 109 +++---- .../src/overlayed_changes/mod.rs | 4 +- .../src/overlayed_changes/offchain.rs | 2 +- .../state-machine/src/proving_backend.rs | 24 +- .../state-machine/src/trie_backend_essence.rs | 20 +- primitives/storage/src/lib.rs | 12 +- primitives/tracing/src/types.rs | 4 +- .../transaction-storage-proof/src/lib.rs | 6 +- primitives/trie/src/lib.rs | 8 +- primitives/trie/src/trie_codec.rs | 2 +- primitives/trie/src/trie_stream.rs | 2 +- .../proc-macro/src/decl_runtime_version.rs | 2 +- primitives/wasm-interface/src/wasmi_impl.rs | 2 +- test-utils/client/src/lib.rs | 2 +- test-utils/runtime/src/genesismap.rs | 5 +- test-utils/runtime/src/system.rs | 2 +- utils/fork-tree/src/lib.rs | 84 ++--- .../benchmarking-cli/src/overhead/template.rs | 6 +- .../benchmarking-cli/src/pallet/command.rs | 18 +- .../benchmarking-cli/src/pallet/writer.rs | 10 +- .../benchmarking-cli/src/shared/stats.rs | 10 +- .../benchmarking-cli/src/storage/template.rs | 2 +- .../frame-utilities-cli/src/pallet_id.rs | 4 +- utils/frame/remote-externalities/src/lib.rs | 19 +- .../rpc/state-trie-migration-rpc/src/lib.rs | 2 +- .../cli/src/commands/execute_block.rs | 6 +- .../cli/src/commands/follow_chain.rs | 10 +- .../cli/src/commands/offchain_worker.rs | 6 +- utils/frame/try-runtime/cli/src/lib.rs | 8 +- utils/wasm-builder/src/wasm_project.rs | 15 +- 368 files changed, 1938 insertions(+), 2247 deletions(-) diff --git a/bin/node-template/node/src/rpc.rs b/bin/node-template/node/src/rpc.rs index d23b23178ec2a..7f3701b5ab74f 100644 --- a/bin/node-template/node/src/rpc.rs +++ b/bin/node-template/node/src/rpc.rs @@ -43,7 +43,7 @@ where io.extend_with(SystemApi::to_delegate(FullSystem::new(client.clone(), pool, deny_unsafe))); - io.extend_with(TransactionPaymentApi::to_delegate(TransactionPayment::new(client.clone()))); + io.extend_with(TransactionPaymentApi::to_delegate(TransactionPayment::new(client))); // Extend this RPC with a custom API by using the following syntax. // `YourRpcStruct` should have a reference to a client, which is needed diff --git a/bin/node-template/node/src/service.rs b/bin/node-template/node/src/service.rs index e2a8cb4ed834b..5f46a16a9668f 100644 --- a/bin/node-template/node/src/service.rs +++ b/bin/node-template/node/src/service.rs @@ -82,7 +82,7 @@ pub fn new_partial( let (client, backend, keystore_container, task_manager) = sc_service::new_full_parts::( - &config, + config, telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()), executor, )?; @@ -263,7 +263,7 @@ pub fn new_full(mut config: Configuration) -> Result let aura = sc_consensus_aura::start_aura::( StartAuraParams { slot_duration, - client: client.clone(), + client, select_chain, block_import, proposer_factory, diff --git a/bin/node-template/pallets/template/src/lib.rs b/bin/node-template/pallets/template/src/lib.rs index f5ce8c5a0f7fd..067c7ce2575a0 100644 --- a/bin/node-template/pallets/template/src/lib.rs +++ b/bin/node-template/pallets/template/src/lib.rs @@ -88,7 +88,7 @@ pub mod pallet { // Read a value from storage. match >::get() { // Return an error if the value has not been set. - None => Err(Error::::NoneValue)?, + None => return Err(Error::::NoneValue.into()), Some(old) => { // Increment the value read from storage; will error in the event of overflow. let new = old.checked_add(1).ok_or(Error::::StorageOverflow)?; diff --git a/bin/node-template/runtime/src/lib.rs b/bin/node-template/runtime/src/lib.rs index a7a162bf74327..0145cacef8f7d 100644 --- a/bin/node-template/runtime/src/lib.rs +++ b/bin/node-template/runtime/src/lib.rs @@ -478,7 +478,7 @@ impl_runtime_apis! { let storage_info = AllPalletsWithSystem::storage_info(); - return (list, storage_info) + (list, storage_info) } fn dispatch_benchmark( diff --git a/bin/node/bench/src/core.rs b/bin/node/bench/src/core.rs index b6ad3ecd80068..3b3060a888349 100644 --- a/bin/node/bench/src/core.rs +++ b/bin/node/bench/src/core.rs @@ -132,7 +132,7 @@ pub fn run_benchmark(benchmark: Box, mode: Mode) -> Be durations.push(duration.as_nanos()); } - durations.sort(); + durations.sort_unstable(); let raw_average = (durations.iter().sum::() / (durations.len() as u128)) as u64; let average = (durations.iter().skip(10).take(30).sum::() / 30) as u64; diff --git a/bin/node/bench/src/main.rs b/bin/node/bench/src/main.rs index 0e50447d464fb..d97c7af26535b 100644 --- a/bin/node/bench/src/main.rs +++ b/bin/node/bench/src/main.rs @@ -85,7 +85,7 @@ fn main() { let mut import_benchmarks = Vec::new(); - for profile in [Profile::Wasm, Profile::Native].iter() { + for profile in [Profile::Wasm, Profile::Native] { for size in [ SizeType::Empty, SizeType::Small, @@ -93,25 +93,14 @@ fn main() { SizeType::Large, SizeType::Full, SizeType::Custom(opt.transactions.unwrap_or(0)), - ] - .iter() - { + ] { for block_type in [ BlockType::RandomTransfersKeepAlive, BlockType::RandomTransfersReaping, BlockType::Noop, - ] - .iter() - { - for database_type in - [BenchDataBaseType::RocksDb, BenchDataBaseType::ParityDb].iter() - { - import_benchmarks.push(( - profile, - size.clone(), - block_type.clone(), - database_type, - )); + ] { + for database_type in [BenchDataBaseType::RocksDb, BenchDataBaseType::ParityDb] { + import_benchmarks.push((profile, size, block_type, database_type)); } } } @@ -120,11 +109,11 @@ fn main() { let benchmarks = matrix!( (profile, size, block_type, database_type) in import_benchmarks.into_iter() => ImportBenchmarkDescription { - profile: *profile, + profile, key_types: KeyTypes::Sr25519, - size: size, - block_type: block_type, - database_type: *database_type, + size, + block_type, + database_type, }, (size, db_type) in [ diff --git a/bin/node/bench/src/state_sizes.rs b/bin/node/bench/src/state_sizes.rs index f97645423edcd..5387850666b6e 100644 --- a/bin/node/bench/src/state_sizes.rs +++ b/bin/node/bench/src/state_sizes.rs @@ -17,7 +17,7 @@ // along with this program. If not, see . /// Kusama value size distribution -pub const KUSAMA_STATE_DISTRIBUTION: &'static [(u32, u32)] = &[ +pub const KUSAMA_STATE_DISTRIBUTION: &[(u32, u32)] = &[ (32, 35), (33, 20035), (34, 5369), diff --git a/bin/node/bench/src/trie.rs b/bin/node/bench/src/trie.rs index 1b4534cbd0f79..0539c9ce11462 100644 --- a/bin/node/bench/src/trie.rs +++ b/bin/node/bench/src/trie.rs @@ -282,7 +282,7 @@ impl core::Benchmark for TrieWriteBenchmark { let mut db = self.database.clone(); let kvdb = db.open(self.database_type); - let mut new_root = self.root.clone(); + let mut new_root = self.root; let mut overlay = HashMap::new(); let mut trie = SimpleTrie { db: kvdb.clone(), overlay: &mut overlay }; diff --git a/bin/node/cli/src/service.rs b/bin/node/cli/src/service.rs index 2742e7b113d6d..01c7eb9abe1b7 100644 --- a/bin/node/cli/src/service.rs +++ b/bin/node/cli/src/service.rs @@ -113,10 +113,10 @@ pub fn create_extrinsic( let signature = raw_payload.using_encoded(|e| sender.sign(e)); node_runtime::UncheckedExtrinsic::new_signed( - function.clone(), + function, sp_runtime::AccountId32::from(sender.public()).into(), - node_runtime::Signature::Sr25519(signature.clone()), - extra.clone(), + node_runtime::Signature::Sr25519(signature), + extra, ) } diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 18ce64c29f8e8..8c999bb76fc14 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -631,7 +631,7 @@ impl Get> for OffchainRandomBalancing { use sp_runtime::traits::TrailingZeroInput; let iters = match MINER_MAX_ITERATIONS { 0 => 0, - max @ _ => { + max => { let seed = sp_io::offchain::random_seed(); let random = ::decode(&mut TrailingZeroInput::new(&seed)) .expect("input is padded with zeroes; qed") % @@ -1148,7 +1148,7 @@ where let signature = raw_payload.using_encoded(|payload| C::sign(payload, public))?; let address = Indices::unlookup(account); let (call, extra, _) = raw_payload.deconstruct(); - Some((call, (address, signature.into(), extra))) + Some((call, (address, signature, extra))) } } @@ -1911,7 +1911,7 @@ impl_runtime_apis! { let storage_info = AllPalletsWithSystem::storage_info(); - return (list, storage_info) + (list, storage_info) } fn dispatch_benchmark( diff --git a/bin/node/testing/src/bench.rs b/bin/node/testing/src/bench.rs index 8227582d88a4b..7e13c0a0ac5e0 100644 --- a/bin/node/testing/src/bench.rs +++ b/bin/node/testing/src/bench.rs @@ -280,8 +280,7 @@ impl<'a> BlockContentIterator<'a> { let genesis_hash = client .block_hash(Zero::zero()) .expect("Database error?") - .expect("Genesis block always exists; qed") - .into(); + .expect("Genesis block always exists; qed"); BlockContentIterator { iteration: 0, content, keyring, runtime_version, genesis_hash } } @@ -569,15 +568,13 @@ impl BenchKeyring { genesis_hash, ); let key = self.accounts.get(&signed).expect("Account id not found in keyring"); - let signature = payload - .using_encoded(|b| { - if b.len() > 256 { - key.sign(&sp_io::hashing::blake2_256(b)) - } else { - key.sign(b) - } - }) - .into(); + let signature = payload.using_encoded(|b| { + if b.len() > 256 { + key.sign(&sp_io::hashing::blake2_256(b)) + } else { + key.sign(b) + } + }); UncheckedExtrinsic { signature: Some((sp_runtime::MultiAddress::Id(signed), signature, extra)), function: payload.0, diff --git a/bin/utils/chain-spec-builder/src/main.rs b/bin/utils/chain-spec-builder/src/main.rs index 3e8b1f4ea7523..e972e130fcf78 100644 --- a/bin/utils/chain-spec-builder/src/main.rs +++ b/bin/utils/chain-spec-builder/src/main.rs @@ -163,7 +163,7 @@ fn generate_chain_spec( } fn generate_authority_keys_and_store(seeds: &[String], keystore_path: &Path) -> Result<(), String> { - for (n, seed) in seeds.into_iter().enumerate() { + for (n, seed) in seeds.iter().enumerate() { let keystore: SyncCryptoStorePtr = Arc::new( LocalKeystore::open(keystore_path.join(format!("auth-{}", n)), None) .map_err(|err| err.to_string())?, diff --git a/client/allocator/src/freeing_bump.rs b/client/allocator/src/freeing_bump.rs index 7eeda45370b87..f14c31c79c483 100644 --- a/client/allocator/src/freeing_bump.rs +++ b/client/allocator/src/freeing_bump.rs @@ -90,7 +90,7 @@ fn error(msg: &'static str) -> Error { Error::Other(msg) } -const LOG_TARGET: &'static str = "wasm-heap"; +const LOG_TARGET: &str = "wasm-heap"; // The minimum possible allocation size is chosen to be 8 bytes because in that case we would have // easier time to provide the guaranteed alignment of 8. diff --git a/client/api/src/backend.rs b/client/api/src/backend.rs index e96616d5416e6..394fcd420fda1 100644 --- a/client/api/src/backend.rs +++ b/client/api/src/backend.rs @@ -499,7 +499,7 @@ pub trait Backend: AuxStore + Send + Sync { /// Returns true if state for given block is available. fn have_state_at(&self, hash: &Block::Hash, _number: NumberFor) -> bool { - self.state_at(BlockId::Hash(hash.clone())).is_ok() + self.state_at(BlockId::Hash(*hash)).is_ok() } /// Returns state backend with post-state of given block. diff --git a/client/api/src/execution_extensions.rs b/client/api/src/execution_extensions.rs index 92efafe91a174..574687312c82b 100644 --- a/client/api/src/execution_extensions.rs +++ b/client/api/src/execution_extensions.rs @@ -151,7 +151,7 @@ impl ExecutionExtensions { where T: OffchainSubmitTransaction + 'static, { - *self.transaction_pool.write() = Some(Arc::downgrade(&pool) as _); + *self.transaction_pool.write() = Some(Arc::downgrade(pool) as _); } /// Based on the execution context and capabilities it produces diff --git a/client/api/src/in_mem.rs b/client/api/src/in_mem.rs index 51370f47e7d14..8b8473287a7ca 100644 --- a/client/api/src/in_mem.rs +++ b/client/api/src/in_mem.rs @@ -166,23 +166,19 @@ impl Blockchain { body: Option::Extrinsic>>, new_state: NewBlockState, ) -> sp_blockchain::Result<()> { - let number = header.number().clone(); + let number = *header.number(); if new_state.is_best() { self.apply_head(&header)?; } { let mut storage = self.storage.write(); - storage - .leaves - .import(hash.clone(), number.clone(), header.parent_hash().clone()); - storage - .blocks - .insert(hash.clone(), StoredBlock::new(header, body, justifications)); + storage.leaves.import(hash, number, header.parent_hash().clone()); + storage.blocks.insert(hash, StoredBlock::new(header, body, justifications)); if let NewBlockState::Final = new_state { storage.finalized_hash = hash; - storage.finalized_number = number.clone(); + storage.finalized_number = number; } if number == Zero::zero() { @@ -266,9 +262,9 @@ impl Blockchain { } } - storage.best_hash = hash.clone(); - storage.best_number = number.clone(); - storage.hashes.insert(number.clone(), hash.clone()); + storage.best_hash = hash; + storage.best_number = *number; + storage.hashes.insert(*number, hash); Ok(()) } @@ -362,7 +358,7 @@ impl HeaderBackend for Blockchain { finalized_hash: storage.finalized_hash, finalized_number: storage.finalized_number, finalized_state: if storage.finalized_hash != Default::default() { - Some((storage.finalized_hash.clone(), storage.finalized_number)) + Some((storage.finalized_hash, storage.finalized_number)) } else { None }, @@ -428,16 +424,12 @@ impl blockchain::Backend for Blockchain { fn justifications(&self, id: BlockId) -> sp_blockchain::Result> { Ok(self.id(id).and_then(|hash| { - self.storage - .read() - .blocks - .get(&hash) - .and_then(|b| b.justifications().map(|x| x.clone())) + self.storage.read().blocks.get(&hash).and_then(|b| b.justifications().cloned()) })) } fn last_finalized(&self) -> sp_blockchain::Result { - Ok(self.storage.read().finalized_hash.clone()) + Ok(self.storage.read().finalized_hash) } fn leaves(&self) -> sp_blockchain::Result> { @@ -810,15 +802,15 @@ impl backend::LocalBackend for Backend where Block: /// Check that genesis storage is valid. pub fn check_genesis_storage(storage: &Storage) -> sp_blockchain::Result<()> { if storage.top.iter().any(|(k, _)| well_known_keys::is_child_storage_key(k)) { - return Err(sp_blockchain::Error::InvalidState.into()) + return Err(sp_blockchain::Error::InvalidState) } if storage .children_default .keys() - .any(|child_key| !well_known_keys::is_child_storage_key(&child_key)) + .any(|child_key| !well_known_keys::is_child_storage_key(child_key)) { - return Err(sp_blockchain::Error::InvalidState.into()) + return Err(sp_blockchain::Error::InvalidState) } Ok(()) diff --git a/client/api/src/lib.rs b/client/api/src/lib.rs index aab2fabd5e25e..3d21f12f6940b 100644 --- a/client/api/src/lib.rs +++ b/client/api/src/lib.rs @@ -57,10 +57,10 @@ pub mod utils { /// represent the current block `hash` and its `parent hash`, if given the /// function that's returned will assume that `hash` isn't part of the local DB /// yet, and all searches in the DB will instead reference the parent. - pub fn is_descendent_of<'a, Block: BlockT, T>( - client: &'a T, + pub fn is_descendent_of( + client: &T, current: Option<(Block::Hash, Block::Hash)>, - ) -> impl Fn(&Block::Hash, &Block::Hash) -> Result + 'a + ) -> impl Fn(&Block::Hash, &Block::Hash) -> Result + '_ where T: HeaderBackend + HeaderMetadata, { diff --git a/client/api/src/notifications.rs b/client/api/src/notifications.rs index 36798abc5bde5..9fcc381f9697e 100644 --- a/client/api/src/notifications.rs +++ b/client/api/src/notifications.rs @@ -71,10 +71,9 @@ type ChildKeys = Option>>>; impl StorageChangeSet { /// Convert the change set into iterator over storage items. - pub fn iter<'a>( - &'a self, - ) -> impl Iterator, &'a StorageKey, Option<&'a StorageData>)> + 'a - { + pub fn iter( + &self, + ) -> impl Iterator, &StorageKey, Option<&StorageData>)> + '_ { let top = self .changes .iter() diff --git a/client/api/src/notifications/registry.rs b/client/api/src/notifications/registry.rs index b34d5a6b6711e..882d6ed40be67 100644 --- a/client/api/src/notifications/registry.rs +++ b/client/api/src/notifications/registry.rs @@ -134,7 +134,7 @@ impl<'a> Subscribe> for Registry { }); if let Some(m) = self.metrics.as_ref() { - m.with_label_values(&[&"added"]).inc(); + m.with_label_values(&["added"]).inc(); } if self @@ -195,7 +195,7 @@ impl Registry { let k = StorageKey(k); let listeners = self.listeners.get(&k); - if let Some(ref listeners) = listeners { + if let Some(listeners) = listeners { subscribers.extend(listeners.iter()); } @@ -211,7 +211,7 @@ impl Registry { let k = StorageKey(k); let listeners = cl.get(&k); - if let Some(ref listeners) = listeners { + if let Some(listeners) = listeners { subscribers.extend(listeners.iter()); } @@ -268,22 +268,22 @@ impl Registry { ); if let Some(child_filters) = &sink.child_keys { for (c_key, filters) in child_filters { - if let Some((listeners, wildcards)) = self.child_listeners.get_mut(&c_key) { + if let Some((listeners, wildcards)) = self.child_listeners.get_mut(c_key) { Self::remove_subscriber_from( subscriber, - &filters, + filters, &mut *listeners, &mut *wildcards, ); if listeners.is_empty() && wildcards.is_empty() { - self.child_listeners.remove(&c_key); + self.child_listeners.remove(c_key); } } } } if let Some(m) = self.metrics.as_ref() { - m.with_label_values(&[&"removed"]).inc(); + m.with_label_values(&["removed"]).inc(); } Some((sink.keys.clone(), sink.child_keys.clone())) diff --git a/client/authority-discovery/src/worker.rs b/client/authority-discovery/src/worker.rs index 0912359501385..87cc72ba7a69c 100644 --- a/client/authority-discovery/src/worker.rs +++ b/client/authority-discovery/src/worker.rs @@ -64,7 +64,7 @@ mod schema { #[cfg(test)] pub mod tests; -const LOG_TARGET: &'static str = "sub-authority-discovery"; +const LOG_TARGET: &str = "sub-authority-discovery"; /// Maximum number of addresses cached per authority. Additional addresses are discarded. const MAX_ADDRESSES_PER_AUTHORITY: usize = 10; @@ -510,7 +510,7 @@ where // Ignore [`Multiaddr`]s without [`PeerId`] or with own addresses. let addresses: Vec = addresses .into_iter() - .filter(|a| get_peer_id(&a).filter(|p| *p != local_peer_id).is_some()) + .filter(|a| get_peer_id(a).filter(|p| *p != local_peer_id).is_some()) .collect(); let remote_peer_id = single(addresses.iter().map(get_peer_id)) @@ -525,7 +525,7 @@ where if let Some(peer_signature) = peer_signature { let public_key = sc_network::PublicKey::from_protobuf_encoding(&peer_signature.public_key) - .map_err(|e| Error::ParsingLibp2pIdentity(e))?; + .map_err(Error::ParsingLibp2pIdentity)?; let signature = sc_network::Signature { public_key, bytes: peer_signature.signature }; diff --git a/client/authority-discovery/src/worker/addr_cache.rs b/client/authority-discovery/src/worker/addr_cache.rs index 3cac5a6bf0348..f768b9c4e66a7 100644 --- a/client/authority-discovery/src/worker/addr_cache.rs +++ b/client/authority-discovery/src/worker/addr_cache.rs @@ -165,10 +165,7 @@ fn peer_id_from_multiaddr(addr: &Multiaddr) -> Option { } fn addresses_to_peer_ids(addresses: &HashSet) -> HashSet { - addresses - .iter() - .filter_map(|a| peer_id_from_multiaddr(a)) - .collect::>() + addresses.iter().filter_map(peer_id_from_multiaddr).collect::>() } #[cfg(test)] diff --git a/client/basic-authorship/src/basic_authorship.rs b/client/basic-authorship/src/basic_authorship.rs index 23725e5138697..5a020ee81050e 100644 --- a/client/basic-authorship/src/basic_authorship.rs +++ b/client/basic-authorship/src/basic_authorship.rs @@ -371,7 +371,7 @@ where error!( "❌️ Mandatory inherent extrinsic returned error. Block cannot be produced." ); - Err(ApplyExtrinsicFailed(Validity(e)))? + return Err(ApplyExtrinsicFailed(Validity(e))) }, Err(e) => { warn!("❗️ Inherent extrinsic returned unexpected error: {}. Dropping.", e); diff --git a/client/beefy/rpc/src/lib.rs b/client/beefy/rpc/src/lib.rs index 4c1bc03e222e7..e49af3352ae4e 100644 --- a/client/beefy/rpc/src/lib.rs +++ b/client/beefy/rpc/src/lib.rs @@ -192,7 +192,7 @@ where .read() .as_ref() .cloned() - .ok_or(Error::EndpointNotReady.into()); + .ok_or_else(|| Error::EndpointNotReady.into()); let future = async move { result }.boxed(); future.map_err(jsonrpc_core::Error::from).boxed() } diff --git a/client/beefy/src/lib.rs b/client/beefy/src/lib.rs index 5d8aa5c866c4a..c025ec5686ad2 100644 --- a/client/beefy/src/lib.rs +++ b/client/beefy/src/lib.rs @@ -51,9 +51,9 @@ pub use beefy_protocol_name::standard_name as protocol_standard_name; pub(crate) mod beefy_protocol_name { use sc_chain_spec::ChainSpec; - const NAME: &'static str = "/beefy/1"; + const NAME: &str = "/beefy/1"; /// Old names for the notifications protocol, used for backward compatibility. - pub(crate) const LEGACY_NAMES: [&'static str; 1] = ["/paritytech/beefy/1"]; + pub(crate) const LEGACY_NAMES: [&str; 1] = ["/paritytech/beefy/1"]; /// Name of the notifications protocol used by BEEFY. /// diff --git a/client/block-builder/src/lib.rs b/client/block-builder/src/lib.rs index 3f9fecbccbb9e..803e9c1e8bf26 100644 --- a/client/block-builder/src/lib.rs +++ b/client/block-builder/src/lib.rs @@ -264,7 +264,7 @@ where let storage_changes = self .api .into_storage_changes(&state, parent_hash) - .map_err(|e| sp_blockchain::Error::StorageChanges(e))?; + .map_err(sp_blockchain::Error::StorageChanges)?; Ok(BuiltBlock { block: ::new(header, self.extrinsics), diff --git a/client/cli/src/commands/generate.rs b/client/cli/src/commands/generate.rs index 9c1e5b689584b..5b1b708f8669c 100644 --- a/client/cli/src/commands/generate.rs +++ b/client/cli/src/commands/generate.rs @@ -61,16 +61,11 @@ impl GenerateCmd { }; let mnemonic = Mnemonic::new(words, Language::English); let password = self.keystore_params.read_password()?; - let output = self.output_scheme.output_type.clone(); + let output = self.output_scheme.output_type; with_crypto_scheme!( self.crypto_scheme.scheme, - print_from_uri( - mnemonic.phrase(), - password, - self.network_scheme.network.clone(), - output, - ) + print_from_uri(mnemonic.phrase(), password, self.network_scheme.network, output) ); Ok(()) } diff --git a/client/cli/src/commands/inspect_key.rs b/client/cli/src/commands/inspect_key.rs index 61fa8d2157e3b..14bb059503df9 100644 --- a/client/cli/src/commands/inspect_key.rs +++ b/client/cli/src/commands/inspect_key.rs @@ -87,15 +87,15 @@ impl InspectKeyCmd { self.crypto_scheme.scheme, print_from_public( &uri, - self.network_scheme.network.clone(), - self.output_scheme.output_type.clone(), + self.network_scheme.network, + self.output_scheme.output_type, ) )?; } else { if let Some(ref expect_public) = self.expect_public { with_crypto_scheme!( self.crypto_scheme.scheme, - expect_public_from_phrase(&&expect_public, &uri, password.as_ref(),) + expect_public_from_phrase(expect_public, &uri, password.as_ref()) )?; } @@ -104,8 +104,8 @@ impl InspectKeyCmd { print_from_uri( &uri, password, - self.network_scheme.network.clone(), - self.output_scheme.output_type.clone(), + self.network_scheme.network, + self.output_scheme.output_type, ) ); } diff --git a/client/cli/src/commands/purge_chain_cmd.rs b/client/cli/src/commands/purge_chain_cmd.rs index 7dd7c1f5a5a5a..b89487a18f779 100644 --- a/client/cli/src/commands/purge_chain_cmd.rs +++ b/client/cli/src/commands/purge_chain_cmd.rs @@ -60,7 +60,7 @@ impl PurgeChainCmd { io::stdin().read_line(&mut input)?; let input = input.trim(); - match input.chars().nth(0) { + match input.chars().next() { Some('y') | Some('Y') => {}, _ => { println!("Aborted"); diff --git a/client/cli/src/commands/utils.rs b/client/cli/src/commands/utils.rs index fa776c25a2eda..32556f0ea728d 100644 --- a/client/cli/src/commands/utils.rs +++ b/client/cli/src/commands/utils.rs @@ -74,7 +74,7 @@ pub fn print_from_uri( { let password = password.as_ref().map(|s| s.expose_secret().as_str()); let network_id = String::from(unwrap_or_default_ss58_version(network_override)); - if let Ok((pair, seed)) = Pair::from_phrase(uri, password.clone()) { + if let Ok((pair, seed)) = Pair::from_phrase(uri, password) { let public_key = pair.public(); let network_override = unwrap_or_default_ss58_version(network_override); @@ -113,7 +113,7 @@ pub fn print_from_uri( ); }, } - } else if let Ok((pair, seed)) = Pair::from_string_with_seed(uri, password.clone()) { + } else if let Ok((pair, seed)) = Pair::from_string_with_seed(uri, password) { let public_key = pair.public(); let network_override = unwrap_or_default_ss58_version(network_override); diff --git a/client/cli/src/commands/vanity.rs b/client/cli/src/commands/vanity.rs index 834b220df6388..6a1bf77f6c8b0 100644 --- a/client/cli/src/commands/vanity.rs +++ b/client/cli/src/commands/vanity.rs @@ -64,8 +64,8 @@ impl VanityCmd { print_from_uri( &formated_seed, None, - self.network_scheme.network.clone(), - self.output_scheme.output_type.clone(), + self.network_scheme.network, + self.output_scheme.output_type, ), ); Ok(()) @@ -98,7 +98,7 @@ where let p = Pair::from_seed(&seed); let ss58 = p.public().into_account().to_ss58check_with_version(network_override); - let score = calculate_score(&desired, &ss58); + let score = calculate_score(desired, &ss58); if score > best || desired.len() < 2 { best = score; if best >= top { @@ -117,20 +117,20 @@ where fn good_waypoint(done: u64) -> u64 { match done { 0..=1_000_000 => 100_000, - 0..=10_000_000 => 1_000_000, - 0..=100_000_000 => 10_000_000, - _ => 100_000_000, + 1_000_001..=10_000_000 => 1_000_000, + 10_000_001..=100_000_000 => 10_000_000, + 100_000_001.. => 100_000_000, } } fn next_seed(seed: &mut [u8]) { - for i in 0..seed.len() { - match seed[i] { + for s in seed { + match s { 255 => { - seed[i] = 0; + *s = 0; }, _ => { - seed[i] += 1; + *s += 1; break }, } diff --git a/client/cli/src/config.rs b/client/cli/src/config.rs index d0f10dc9f6f3a..5c44a05ab68dd 100644 --- a/client/cli/src/config.rs +++ b/client/cli/src/config.rs @@ -40,7 +40,7 @@ use std::{net::SocketAddr, path::PathBuf}; pub(crate) const NODE_NAME_MAX_LENGTH: usize = 64; /// Default sub directory to store network config. -pub(crate) const DEFAULT_NETWORK_CONFIG_PATH: &'static str = "network"; +pub(crate) const DEFAULT_NETWORK_CONFIG_PATH: &str = "network"; /// The recommended open file descriptor limit to be configured for the process. const RECOMMENDED_OPEN_FILE_DESCRIPTOR_LIMIT: u64 = 10_000; @@ -629,7 +629,7 @@ pub trait CliConfiguration: Sized { } // Call hook for custom profiling setup. - logger_hook(&mut logger, &config); + logger_hook(&mut logger, config); logger.init()?; diff --git a/client/cli/src/lib.rs b/client/cli/src/lib.rs index 244f6e167f9d5..e01befbef41a2 100644 --- a/client/cli/src/lib.rs +++ b/client/cli/src/lib.rs @@ -122,7 +122,7 @@ pub trait SubstrateCli: Sized { let app = ::command(); let mut full_version = Self::impl_version(); - full_version.push_str("\n"); + full_version.push('\n'); let name = Self::executable_name(); let author = Self::author(); @@ -164,7 +164,7 @@ pub trait SubstrateCli: Sized { let app = ::command(); let mut full_version = Self::impl_version(); - full_version.push_str("\n"); + full_version.push('\n'); let name = Self::executable_name(); let author = Self::author(); diff --git a/client/cli/src/params/keystore_params.rs b/client/cli/src/params/keystore_params.rs index 72b09134f57a4..46403f95fbc4b 100644 --- a/client/cli/src/params/keystore_params.rs +++ b/client/cli/src/params/keystore_params.rs @@ -26,7 +26,7 @@ use std::{ }; /// default sub directory for the key store -const DEFAULT_KEYSTORE_CONFIG_PATH: &'static str = "keystore"; +const DEFAULT_KEYSTORE_CONFIG_PATH: &str = "keystore"; /// Parameters of the keystore #[derive(Debug, Clone, Args)] diff --git a/client/cli/src/params/node_key_params.rs b/client/cli/src/params/node_key_params.rs index 699d0c4ece8dc..d51b6143ed393 100644 --- a/client/cli/src/params/node_key_params.rs +++ b/client/cli/src/params/node_key_params.rs @@ -112,7 +112,7 @@ fn invalid_node_key(e: impl std::fmt::Display) -> error::Error { /// Parse a Ed25519 secret key from a hex string into a `sc_network::Secret`. fn parse_ed25519_secret(hex: &str) -> error::Result { - H256::from_str(&hex).map_err(invalid_node_key).and_then(|bytes| { + H256::from_str(hex).map_err(invalid_node_key).and_then(|bytes| { ed25519::SecretKey::from_bytes(bytes) .map(sc_network::config::Secret::Input) .map_err(invalid_node_key) diff --git a/client/cli/src/params/shared_params.rs b/client/cli/src/params/shared_params.rs index 01df2cf9abba4..67b18aa8b09e2 100644 --- a/client/cli/src/params/shared_params.rs +++ b/client/cli/src/params/shared_params.rs @@ -126,7 +126,7 @@ impl SharedParams { /// Receiver to process tracing messages. pub fn tracing_receiver(&self) -> sc_service::TracingReceiver { - self.tracing_receiver.clone().into() + self.tracing_receiver.into() } /// Comma separated list of targets for tracing. diff --git a/client/consensus/aura/src/import_queue.rs b/client/consensus/aura/src/import_queue.rs index 56eb45c621a1b..30554006732c0 100644 --- a/client/consensus/aura/src/import_queue.rs +++ b/client/consensus/aura/src/import_queue.rs @@ -68,7 +68,7 @@ where C: sc_client_api::backend::AuxStore, P::Public: Encode + Decode + PartialEq + Clone, { - let seal = header.digest_mut().pop().ok_or_else(|| Error::HeaderUnsealed(hash))?; + let seal = header.digest_mut().pop().ok_or(Error::HeaderUnsealed(hash))?; let sig = seal.as_aura_seal().ok_or_else(|| aura_err(Error::HeaderBadSeal(hash)))?; @@ -81,7 +81,7 @@ where // check the signature is valid under the expected authority and // chain state. let expected_author = - slot_author::

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

(slot, authorities).ok_or(Error::SlotAuthorNotFound)?; let pre_hash = header.hash(); @@ -360,7 +360,7 @@ pub struct ImportQueueParams<'a, Block, I, C, S, CAW, CIDP> { } /// Start an import queue for the Aura consensus algorithm. -pub fn import_queue<'a, P, Block, I, C, S, CAW, CIDP>( +pub fn import_queue( ImportQueueParams { block_import, justification_import, @@ -371,7 +371,7 @@ pub fn import_queue<'a, P, Block, I, C, S, CAW, CIDP>( can_author_with, check_for_equivocation, telemetry, - }: ImportQueueParams<'a, Block, I, C, S, CAW, CIDP>, + }: ImportQueueParams, ) -> Result, sp_consensus::Error> where Block: BlockT, diff --git a/client/consensus/aura/src/lib.rs b/client/consensus/aura/src/lib.rs index d803aa4ae97f8..ac3b89f2ff9a2 100644 --- a/client/consensus/aura/src/lib.rs +++ b/client/consensus/aura/src/lib.rs @@ -186,7 +186,7 @@ where Error: std::error::Error + Send + From + 'static, { let worker = build_aura_worker::(BuildAuraWorkerParams { - client: client.clone(), + client, block_import, proposer_factory, keystore, @@ -459,7 +459,7 @@ where fn proposer(&mut self, block: &B::Header) -> Self::CreateProposer { self.env .init(block) - .map_err(|e| sp_consensus::Error::ClientImport(format!("{:?}", e)).into()) + .map_err(|e| sp_consensus::Error::ClientImport(format!("{:?}", e))) .boxed() } @@ -534,7 +534,7 @@ pub fn find_pre_digest(header: &B::Header) -> Resul for log in header.digest().logs() { trace!(target: "aura", "Checking log {:?}", log); match (CompatibleDigestItem::::as_aura_pre_digest(log), pre_digest.is_some()) { - (Some(_), true) => Err(aura_err(Error::MultipleHeaders))?, + (Some(_), true) => return Err(aura_err(Error::MultipleHeaders)), (None, _) => trace!(target: "aura", "Ignoring digest not meant for us"), (s, false) => pre_digest = s, } @@ -553,7 +553,7 @@ where .runtime_api() .authorities(at) .ok() - .ok_or_else(|| sp_consensus::Error::InvalidAuthoritiesSet.into()) + .ok_or(sp_consensus::Error::InvalidAuthoritiesSet) } #[cfg(test)] diff --git a/client/consensus/babe/rpc/src/lib.rs b/client/consensus/babe/rpc/src/lib.rs index 1fbe23e54f50d..2d0c81afc7775 100644 --- a/client/consensus/babe/rpc/src/lib.rs +++ b/client/consensus/babe/rpc/src/lib.rs @@ -142,7 +142,7 @@ where claims.entry(key).or_default().secondary.push(slot); }, PreDigest::SecondaryVRF { .. } => { - claims.entry(key).or_default().secondary_vrf.push(slot.into()); + claims.entry(key).or_default().secondary_vrf.push(slot); }, }; } @@ -205,7 +205,7 @@ where .epoch_data_for_child_of( descendent_query(&**client), &parent.hash(), - parent.number().clone(), + *parent.number(), slot.into(), |slot| Epoch::genesis(babe_config.genesis_config(), slot), ) diff --git a/client/consensus/babe/src/authorship.rs b/client/consensus/babe/src/authorship.rs index 1f74afb0e78b4..7a9b09495cd6f 100644 --- a/client/consensus/babe/src/authorship.rs +++ b/client/consensus/babe/src/authorship.rs @@ -202,15 +202,15 @@ pub fn claim_slot_using_keys( keystore: &SyncCryptoStorePtr, keys: &[(AuthorityId, usize)], ) -> Option<(PreDigest, AuthorityId)> { - claim_primary_slot(slot, epoch, epoch.config.c, keystore, &keys).or_else(|| { + claim_primary_slot(slot, epoch, epoch.config.c, keystore, keys).or_else(|| { if epoch.config.allowed_slots.is_secondary_plain_slots_allowed() || epoch.config.allowed_slots.is_secondary_vrf_slots_allowed() { claim_secondary_slot( slot, - &epoch, + epoch, keys, - &keystore, + keystore, epoch.config.allowed_slots.is_secondary_vrf_slots_allowed(), ) } else { diff --git a/client/consensus/babe/src/lib.rs b/client/consensus/babe/src/lib.rs index be5c2809bd796..490fdfb174311 100644 --- a/client/consensus/babe/src/lib.rs +++ b/client/consensus/babe/src/lib.rs @@ -528,7 +528,7 @@ where let (worker_tx, worker_rx) = channel(HANDLE_BUFFER_SIZE); let answer_requests = - answer_requests(worker_rx, babe_link.config, client, babe_link.epoch_changes.clone()); + answer_requests(worker_rx, babe_link.config, client, babe_link.epoch_changes); let inner = future::select(Box::pin(slot_worker), Box::pin(answer_requests)); Ok(BabeWorker { @@ -638,13 +638,13 @@ async fn answer_requests( slot_number, ) .map_err(|e| Error::::ForkTree(Box::new(e)))? - .ok_or_else(|| Error::::FetchEpoch(parent_hash))?; + .ok_or(Error::::FetchEpoch(parent_hash))?; let viable_epoch = epoch_changes .viable_epoch(&epoch_descriptor, |slot| { Epoch::genesis(&config.genesis_config, slot) }) - .ok_or_else(|| Error::::FetchEpoch(parent_hash))?; + .ok_or(Error::::FetchEpoch(parent_hash))?; Ok(sp_consensus_babe::Epoch { epoch_index: viable_epoch.as_ref().epoch_index, @@ -788,7 +788,7 @@ where .epoch_descriptor_for_child_of( descendent_query(&*self.client), &parent.hash(), - parent.number().clone(), + *parent.number(), slot, ) .map_err(|e| ConsensusError::ChainLookup(e.to_string()))? @@ -798,7 +798,7 @@ where fn authorities_len(&self, epoch_descriptor: &Self::EpochData) -> Option { self.epoch_changes .shared_data() - .viable_epoch(&epoch_descriptor, |slot| { + .viable_epoch(epoch_descriptor, |slot| { Epoch::genesis(&self.config.genesis_config, slot) }) .map(|epoch| epoch.as_ref().authorities.len()) @@ -815,7 +815,7 @@ where slot, self.epoch_changes .shared_data() - .viable_epoch(&epoch_descriptor, |slot| { + .viable_epoch(epoch_descriptor, |slot| { Epoch::genesis(&self.config.genesis_config, slot) })? .as_ref(), @@ -886,7 +886,7 @@ where .clone() .try_into() .map_err(|_| sp_consensus::Error::InvalidSignature(signature, public))?; - let digest_item = ::babe_seal(signature.into()); + let digest_item = ::babe_seal(signature); let mut import_block = BlockImportParams::new(BlockOrigin::Own, header); import_block.post_digests.push(digest_item); @@ -1245,12 +1245,12 @@ where pre_digest.slot(), ) .map_err(|e| Error::::ForkTree(Box::new(e)))? - .ok_or_else(|| Error::::FetchEpoch(parent_hash))?; + .ok_or(Error::::FetchEpoch(parent_hash))?; let viable_epoch = epoch_changes .viable_epoch(&epoch_descriptor, |slot| { Epoch::genesis(&self.config.genesis_config, slot) }) - .ok_or_else(|| Error::::FetchEpoch(parent_hash))?; + .ok_or(Error::::FetchEpoch(parent_hash))?; // We add one to the current slot to allow for some small drift. // FIXME #1019 in the future, alter this queue to allow deferring of headers diff --git a/client/consensus/babe/src/verification.rs b/client/consensus/babe/src/verification.rs index 41d1e1bfa5d36..53ec3002e6a85 100644 --- a/client/consensus/babe/src/verification.rs +++ b/client/consensus/babe/src/verification.rs @@ -99,7 +99,7 @@ pub(super) fn check_header( primary.slot, ); - check_primary_header::(pre_hash, primary, sig, &epoch, epoch.config.c)?; + check_primary_header::(pre_hash, primary, sig, epoch, epoch.config.c)?; }, PreDigest::SecondaryPlain(secondary) if epoch.config.allowed_slots.is_secondary_plain_slots_allowed() => @@ -110,7 +110,7 @@ pub(super) fn check_header( secondary.slot, ); - check_secondary_plain_header::(pre_hash, secondary, sig, &epoch)?; + check_secondary_plain_header::(pre_hash, secondary, sig, epoch)?; }, PreDigest::SecondaryVRF(secondary) if epoch.config.allowed_slots.is_secondary_vrf_slots_allowed() => @@ -121,7 +121,7 @@ pub(super) fn check_header( secondary.slot, ); - check_secondary_vrf_header::(pre_hash, secondary, sig, &epoch)?; + check_secondary_vrf_header::(pre_hash, secondary, sig, epoch)?; }, _ => return Err(babe_err(Error::SecondarySlotAssignmentsDisabled)), } @@ -153,7 +153,7 @@ fn check_primary_header( ) -> Result<(), Error> { let author = &epoch.authorities[pre_digest.authority_index as usize].0; - if AuthorityPair::verify(&signature, pre_hash, &author) { + if AuthorityPair::verify(&signature, pre_hash, author) { let (inout, _) = { let transcript = make_transcript(&epoch.randomness, pre_digest.slot, epoch.epoch_index); @@ -191,7 +191,7 @@ fn check_secondary_plain_header( // chain state. let expected_author = secondary_slot_author(pre_digest.slot, &epoch.authorities, epoch.randomness) - .ok_or_else(|| Error::NoSecondaryAuthorExpected)?; + .ok_or(Error::NoSecondaryAuthorExpected)?; let author = &epoch.authorities[pre_digest.authority_index as usize].0; @@ -217,7 +217,7 @@ fn check_secondary_vrf_header( // chain state. let expected_author = secondary_slot_author(pre_digest.slot, &epoch.authorities, epoch.randomness) - .ok_or_else(|| Error::NoSecondaryAuthorExpected)?; + .ok_or(Error::NoSecondaryAuthorExpected)?; let author = &epoch.authorities[pre_digest.authority_index as usize].0; diff --git a/client/consensus/common/src/block_import.rs b/client/consensus/common/src/block_import.rs index 24fec9b974a4c..f81c8eb7e8dee 100644 --- a/client/consensus/common/src/block_import.rs +++ b/client/consensus/common/src/block_import.rs @@ -62,8 +62,7 @@ impl ImportResult { /// `clear_justification_requests`, `needs_justification`, /// `bad_justification` set to false. pub fn imported(is_new_best: bool) -> ImportResult { - let mut aux = ImportedAux::default(); - aux.is_new_best = is_new_best; + let aux = ImportedAux { is_new_best, ..Default::default() }; ImportResult::Imported(aux) } diff --git a/client/consensus/common/src/import_queue.rs b/client/consensus/common/src/import_queue.rs index 8b560d0447411..a7b456191b000 100644 --- a/client/consensus/common/src/import_queue.rs +++ b/client/consensus/common/src/import_queue.rs @@ -232,17 +232,17 @@ pub(crate) async fn import_single_block_metered< trace!(target: "sync", "Header {} has {:?} logs", block.hash, header.digest().logs().len()); - let number = header.number().clone(); + let number = *header.number(); let hash = block.hash; - let parent_hash = header.parent_hash().clone(); + let parent_hash = *header.parent_hash(); let import_handler = |import| match import { Ok(ImportResult::AlreadyInChain) => { trace!(target: "sync", "Block already in chain {}: {:?}", number, hash); - Ok(BlockImportStatus::ImportedKnown(number, peer.clone())) + Ok(BlockImportStatus::ImportedKnown(number, peer)) }, Ok(ImportResult::Imported(aux)) => - Ok(BlockImportStatus::ImportedUnknown(number, aux, peer.clone())), + Ok(BlockImportStatus::ImportedUnknown(number, aux, peer)), Ok(ImportResult::MissingState) => { debug!(target: "sync", "Parent state is missing for {}: {:?}, parent: {:?}", number, hash, parent_hash); @@ -255,7 +255,7 @@ pub(crate) async fn import_single_block_metered< }, Ok(ImportResult::KnownBad) => { debug!(target: "sync", "Peer gave us a bad block {}: {:?}", number, hash); - Err(BlockImportError::BadBlock(peer.clone())) + Err(BlockImportError::BadBlock(peer)) }, Err(e) => { debug!(target: "sync", "Error importing block {}: {:?}: {}", number, hash, e); @@ -306,7 +306,7 @@ pub(crate) async fn import_single_block_metered< if let Some(metrics) = metrics.as_ref() { metrics.report_verification(false, started.elapsed()); } - BlockImportError::VerificationFailed(peer.clone(), msg) + BlockImportError::VerificationFailed(peer, msg) })?; if let Some(metrics) = metrics.as_ref() { diff --git a/client/consensus/common/src/import_queue/basic_queue.rs b/client/consensus/common/src/import_queue/basic_queue.rs index 5134dc041c26b..9fe293142050b 100644 --- a/client/consensus/common/src/import_queue/basic_queue.rs +++ b/client/consensus/common/src/import_queue/basic_queue.rs @@ -374,7 +374,7 @@ async fn import_many_blocks, Transaction: Send + 'stat }, }; - let block_number = block.header.as_ref().map(|h| h.number().clone()); + let block_number = block.header.as_ref().map(|h| *h.number()); let block_hash = block.hash; let import_result = if has_error { Err(BlockImportError::Cancelled) @@ -382,7 +382,7 @@ async fn import_many_blocks, Transaction: Send + 'stat // The actual import. import_single_block_metered( import_handle, - blocks_origin.clone(), + blocks_origin, block, verifier, metrics.clone(), diff --git a/client/consensus/common/src/import_queue/buffered_link.rs b/client/consensus/common/src/import_queue/buffered_link.rs index 8fb5689075ab2..d3d91f5bd31c5 100644 --- a/client/consensus/common/src/import_queue/buffered_link.rs +++ b/client/consensus/common/src/import_queue/buffered_link.rs @@ -105,14 +105,14 @@ impl Link for BufferedLinkSender { number: NumberFor, success: bool, ) { - let msg = BlockImportWorkerMsg::JustificationImported(who, hash.clone(), number, success); + let msg = BlockImportWorkerMsg::JustificationImported(who, *hash, number, success); let _ = self.tx.unbounded_send(msg); } fn request_justification(&mut self, hash: &B::Hash, number: NumberFor) { let _ = self .tx - .unbounded_send(BlockImportWorkerMsg::RequestJustification(hash.clone(), number)); + .unbounded_send(BlockImportWorkerMsg::RequestJustification(*hash, number)); } } diff --git a/client/consensus/common/src/longest_chain.rs b/client/consensus/common/src/longest_chain.rs index b38183b8ac11f..941cd4b944766 100644 --- a/client/consensus/common/src/longest_chain.rs +++ b/client/consensus/common/src/longest_chain.rs @@ -79,12 +79,12 @@ where Block: BlockT, { async fn leaves(&self) -> Result::Hash>, ConsensusError> { - LongestChain::leaves(self).map_err(|e| ConsensusError::ChainLookup(e.to_string()).into()) + LongestChain::leaves(self).map_err(|e| ConsensusError::ChainLookup(e.to_string())) } async fn best_chain(&self) -> Result<::Header, ConsensusError> { - LongestChain::best_block_header(&self) - .map_err(|e| ConsensusError::ChainLookup(e.to_string()).into()) + LongestChain::best_block_header(self) + .map_err(|e| ConsensusError::ChainLookup(e.to_string())) } async fn finality_target( @@ -97,6 +97,6 @@ where .blockchain() .best_containing(target_hash, maybe_max_number, import_lock) .map(|maybe_hash| maybe_hash.unwrap_or(target_hash)) - .map_err(|e| ConsensusError::ChainLookup(e.to_string()).into()) + .map_err(|e| ConsensusError::ChainLookup(e.to_string())) } } diff --git a/client/consensus/epochs/src/lib.rs b/client/consensus/epochs/src/lib.rs index 90081bf9af442..3a943e4851a4d 100644 --- a/client/consensus/epochs/src/lib.rs +++ b/client/consensus/epochs/src/lib.rs @@ -566,7 +566,7 @@ where ViableEpochDescriptor::UnimportedGenesis(slot) => Some(ViableEpoch::UnimportedGenesis(make_genesis(*slot))), ViableEpochDescriptor::Signaled(identifier, _) => - self.epoch(&identifier).map(ViableEpoch::Signaled), + self.epoch(identifier).map(ViableEpoch::Signaled), } } @@ -599,7 +599,7 @@ where ViableEpochDescriptor::UnimportedGenesis(slot) => Some(ViableEpoch::UnimportedGenesis(make_genesis(*slot))), ViableEpochDescriptor::Signaled(identifier, _) => - self.epoch_mut(&identifier).map(ViableEpoch::Signaled), + self.epoch_mut(identifier).map(ViableEpoch::Signaled), } } @@ -618,7 +618,7 @@ where { match descriptor { ViableEpochDescriptor::UnimportedGenesis(slot) => Some(make_genesis(*slot)), - ViableEpochDescriptor::Signaled(identifier, _) => self.epoch(&identifier).cloned(), + ViableEpochDescriptor::Signaled(identifier, _) => self.epoch(identifier).cloned(), } } @@ -750,7 +750,7 @@ where if let Some(gap) = &mut self.gap { if let PersistedEpoch::Regular(e) = epoch { - epoch = match gap.import(slot, hash.clone(), number.clone(), e) { + epoch = match gap.import(slot, hash, number, e) { Ok(()) => return Ok(()), Err(e) => PersistedEpoch::Regular(e), } diff --git a/client/consensus/manual-seal/src/consensus/babe.rs b/client/consensus/manual-seal/src/consensus/babe.rs index 53cc58df30a36..3e7770cd982d2 100644 --- a/client/consensus/manual-seal/src/consensus/babe.rs +++ b/client/consensus/manual-seal/src/consensus/babe.rs @@ -114,7 +114,7 @@ where .epoch_descriptor_for_child_of( descendent_query(&*self.client), &parent.hash(), - parent.number().clone(), + *parent.number(), pre_digest.slot(), ) .map_err(|e| format!("failed to fetch epoch_descriptor: {}", e))? @@ -162,11 +162,11 @@ where .epoch_descriptor_for_child_of( descendent_query(&*self.client), &parent.hash(), - parent.number().clone(), + *parent.number(), slot, ) .map_err(|e| Error::StringError(format!("failed to fetch epoch_descriptor: {}", e)))? - .ok_or_else(|| sp_consensus::Error::InvalidAuthoritiesSet)?; + .ok_or(sp_consensus::Error::InvalidAuthoritiesSet)?; let epoch = epoch_changes .viable_epoch(&epoch_descriptor, |slot| { @@ -216,19 +216,19 @@ where .epoch_descriptor_for_child_of( descendent_query(&*self.client), &parent.hash(), - parent.number().clone(), + *parent.number(), slot, ) .map_err(|e| { Error::StringError(format!("failed to fetch epoch_descriptor: {}", e)) })? - .ok_or_else(|| sp_consensus::Error::InvalidAuthoritiesSet)?; + .ok_or(sp_consensus::Error::InvalidAuthoritiesSet)?; match epoch_descriptor { ViableEpochDescriptor::Signaled(identifier, _epoch_header) => { let epoch_mut = epoch_changes .epoch_mut(&identifier) - .ok_or_else(|| sp_consensus::Error::InvalidAuthoritiesSet)?; + .ok_or(sp_consensus::Error::InvalidAuthoritiesSet)?; // mutate the current epoch epoch_mut.authorities = self.authorities.clone(); @@ -236,7 +236,7 @@ where let next_epoch = ConsensusLog::NextEpochData(NextEpochDescriptor { authorities: self.authorities.clone(), // copy the old randomness - randomness: epoch_mut.randomness.clone(), + randomness: epoch_mut.randomness, }); vec![ @@ -268,11 +268,11 @@ where .epoch_descriptor_for_child_of( descendent_query(&*self.client), &parent.hash(), - parent.number().clone(), + *parent.number(), slot, ) .map_err(|e| Error::StringError(format!("failed to fetch epoch_descriptor: {}", e)))? - .ok_or_else(|| sp_consensus::Error::InvalidAuthoritiesSet)?; + .ok_or(sp_consensus::Error::InvalidAuthoritiesSet)?; // drop the lock drop(epoch_changes); // a quick check to see if we're in the authorities diff --git a/client/consensus/pow/src/lib.rs b/client/consensus/pow/src/lib.rs index 8885099ceb514..6f9ee6f864ad8 100644 --- a/client/consensus/pow/src/lib.rs +++ b/client/consensus/pow/src/lib.rs @@ -234,7 +234,7 @@ impl Clone select_chain: self.select_chain.clone(), client: self.client.clone(), create_inherent_data_providers: self.create_inherent_data_providers.clone(), - check_inherents_after: self.check_inherents_after.clone(), + check_inherents_after: self.check_inherents_after, can_author_with: self.can_author_with.clone(), } } @@ -652,21 +652,19 @@ where }, }; - let proposal = match proposer - .propose(inherent_data, inherent_digest, build_time.clone(), None) - .await - { - Ok(x) => x, - Err(err) => { - warn!( - target: "pow", - "Unable to propose new block for authoring. \ - Creating proposal failed: {}", - err, - ); - continue - }, - }; + let proposal = + match proposer.propose(inherent_data, inherent_digest, build_time, None).await { + Ok(x) => x, + Err(err) => { + warn!( + target: "pow", + "Unable to propose new block for authoring. \ + Creating proposal failed: {}", + err, + ); + continue + }, + }; let build = MiningBuild:: { metadata: MiningMetadata { @@ -710,8 +708,8 @@ fn fetch_seal(digest: Option<&DigestItem>, hash: B::Hash) -> Result::WrongEngine(*id).into()) + Err(Error::::WrongEngine(*id)) }, - _ => return Err(Error::::HeaderUnsealed(hash).into()), + _ => Err(Error::::HeaderUnsealed(hash)), } } diff --git a/client/consensus/pow/src/worker.rs b/client/consensus/pow/src/worker.rs index 42f82fb43ef7b..750e78cd9a038 100644 --- a/client/consensus/pow/src/worker.rs +++ b/client/consensus/pow/src/worker.rs @@ -295,7 +295,7 @@ impl Stream for UntilImportedOrTimeout { } } - let timeout = self.timeout.clone(); + let timeout = self.timeout; let inner_delay = self.inner_delay.get_or_insert_with(|| Delay::new(timeout)); match Future::poll(Pin::new(inner_delay), cx) { diff --git a/client/consensus/slots/src/aux_schema.rs b/client/consensus/slots/src/aux_schema.rs index 275b12ff48f80..eeaec68d369d2 100644 --- a/client/consensus/slots/src/aux_schema.rs +++ b/client/consensus/slots/src/aux_schema.rs @@ -63,7 +63,7 @@ where P: Clone + Encode + Decode + PartialEq, { // We don't check equivocations for old headers out of our capacity. - if slot_now.saturating_sub(*slot) > Slot::from(MAX_SLOT_CAPACITY) { + if slot_now.saturating_sub(*slot) > MAX_SLOT_CAPACITY { return Ok(None) } diff --git a/client/consensus/slots/src/lib.rs b/client/consensus/slots/src/lib.rs index a97469fbcc300..a6fbc4bebc796 100644 --- a/client/consensus/slots/src/lib.rs +++ b/client/consensus/slots/src/lib.rs @@ -606,7 +606,7 @@ pub fn proposing_remaining_duration( // if we defined a maximum portion of the slot for proposal then we must make sure the // lenience doesn't go over it let lenient_proposing_duration = - if let Some(ref max_block_proposal_slot_portion) = max_block_proposal_slot_portion { + if let Some(max_block_proposal_slot_portion) = max_block_proposal_slot_portion { std::cmp::min( lenient_proposing_duration, slot_info.duration.mul_f32(max_block_proposal_slot_portion.get()), diff --git a/client/db/src/bench.rs b/client/db/src/bench.rs index fe31d31dfef9e..8bc4a0f4893c7 100644 --- a/client/db/src/bench.rs +++ b/client/db/src/bench.rs @@ -57,14 +57,14 @@ impl sp_state_machine::Storage> for StorageDb Result, String> { let prefixed_key = prefixed_key::>(key, prefix); if let Some(recorder) = &self.proof_recorder { - if let Some(v) = recorder.get(&key) { - return Ok(v.clone()) + if let Some(v) = recorder.get(key) { + return Ok(v) } let backend_value = self .db .get(0, &prefixed_key) .map_err(|e| format!("Database backend error: {:?}", e))?; - recorder.record(key.clone(), backend_value.clone()); + recorder.record(*key, backend_value.clone()); Ok(backend_value) } else { self.db @@ -114,7 +114,7 @@ impl BenchmarkingState { let mut state = BenchmarkingState { state: RefCell::new(None), db: Cell::new(None), - root: Cell::new(root.clone()), + root: Cell::new(root), genesis: Default::default(), genesis_root: Default::default(), record: Default::default(), @@ -123,7 +123,7 @@ impl BenchmarkingState { child_key_tracker: Default::default(), whitelist: Default::default(), proof_recorder: record_proof.then(Default::default), - proof_recorder_root: Cell::new(root.clone()), + proof_recorder_root: Cell::new(root), enable_tracking, }; @@ -143,7 +143,7 @@ impl BenchmarkingState { state_version, ); state.genesis = transaction.clone().drain(); - state.genesis_root = root.clone(); + state.genesis_root = root; state.commit(root, transaction, Vec::new(), Vec::new())?; state.record.take(); Ok(state) @@ -201,9 +201,7 @@ impl BenchmarkingState { let mut main_key_tracker = self.main_key_tracker.borrow_mut(); let key_tracker = if let Some(childtrie) = childtrie { - child_key_tracker - .entry(childtrie.to_vec()) - .or_insert_with(|| LinkedHashMap::new()) + child_key_tracker.entry(childtrie.to_vec()).or_insert_with(LinkedHashMap::new) } else { &mut main_key_tracker }; @@ -244,9 +242,7 @@ impl BenchmarkingState { let mut main_key_tracker = self.main_key_tracker.borrow_mut(); let key_tracker = if let Some(childtrie) = childtrie { - child_key_tracker - .entry(childtrie.to_vec()) - .or_insert_with(|| LinkedHashMap::new()) + child_key_tracker.entry(childtrie.to_vec()).or_insert_with(LinkedHashMap::new) } else { &mut main_key_tracker }; @@ -517,7 +513,7 @@ impl StateBackend> for BenchmarkingState { self.db.set(Some(db)); } - self.root.set(self.genesis_root.clone()); + self.root.set(self.genesis_root); self.reopen()?; self.wipe_tracker(); Ok(()) @@ -612,18 +608,17 @@ impl StateBackend> for BenchmarkingState { if proof_recorder_root == Default::default() || proof_size == 1 { // empty trie proof_size + } else if let Some(size) = proof.encoded_compact_size::>(proof_recorder_root) + { + size as u32 } else { - if let Some(size) = proof.encoded_compact_size::>(proof_recorder_root) { - size as u32 - } else { - panic!( - "proof rec root {:?}, root {:?}, genesis {:?}, rec_len {:?}", - self.proof_recorder_root.get(), - self.root.get(), - self.genesis_root, - proof_size, - ); - } + panic!( + "proof rec root {:?}, root {:?}, genesis {:?}, rec_len {:?}", + self.proof_recorder_root.get(), + self.root.get(), + self.genesis_root, + proof_size, + ); } }) } diff --git a/client/db/src/lib.rs b/client/db/src/lib.rs index 735058897340e..72422eb82d6dd 100644 --- a/client/db/src/lib.rs +++ b/client/db/src/lib.rs @@ -352,8 +352,8 @@ impl DatabaseSource { // // IIUC this is needed for polkadot to create its own dbs, so until it can use parity db // I would think rocksdb, but later parity-db. - DatabaseSource::Auto { paritydb_path, .. } => Some(&paritydb_path), - DatabaseSource::RocksDb { path, .. } | DatabaseSource::ParityDb { path } => Some(&path), + DatabaseSource::Auto { paritydb_path, .. } => Some(paritydb_path), + DatabaseSource::RocksDb { path, .. } | DatabaseSource::ParityDb { path } => Some(path), DatabaseSource::Custom(..) => None, } } @@ -478,7 +478,7 @@ impl BlockchainDb { if is_finalized { if with_state { - meta.finalized_state = Some((hash.clone(), number)); + meta.finalized_state = Some((hash, number)); } meta.finalized_number = number; meta.finalized_hash = hash; @@ -501,7 +501,7 @@ impl sc_client_api::blockchain::HeaderBackend for Blockcha } let header = utils::read_header(&*self.db, columns::KEY_LOOKUP, columns::HEADER, id)?; - cache_header(&mut cache, h.clone(), header.clone()); + cache_header(&mut cache, *h, header.clone()); Ok(header) }, BlockId::Number(_) => @@ -517,7 +517,7 @@ impl sc_client_api::blockchain::HeaderBackend for Blockcha genesis_hash: meta.genesis_hash, finalized_hash: meta.finalized_hash, finalized_number: meta.finalized_number, - finalized_state: meta.finalized_state.clone(), + finalized_state: meta.finalized_state, number_leaves: self.leaves.read().count(), block_gap: meta.block_gap, } @@ -540,10 +540,7 @@ impl sc_client_api::blockchain::HeaderBackend for Blockcha fn hash(&self, number: NumberFor) -> ClientResult> { self.header(BlockId::Number(number)) - .and_then(|maybe_header| match maybe_header { - Some(header) => Ok(Some(header.hash().clone())), - None => Ok(None), - }) + .map(|maybe_header| maybe_header.map(|header| header.hash())) } } @@ -621,7 +618,7 @@ impl sc_client_api::blockchain::Backend for BlockchainDb ClientResult { - Ok(self.meta.read().finalized_hash.clone()) + Ok(self.meta.read().finalized_hash) } fn leaves(&self) -> ClientResult> { @@ -765,8 +762,8 @@ impl BlockImportOperation { storage: Storage, state_version: StateVersion, ) -> ClientResult { - if storage.top.keys().any(|k| well_known_keys::is_child_storage_key(&k)) { - return Err(sp_blockchain::Error::InvalidState.into()) + if storage.top.keys().any(|k| well_known_keys::is_child_storage_key(k)) { + return Err(sp_blockchain::Error::InvalidState) } let child_delta = storage.children_default.iter().map(|(_storage_key, child_content)| { @@ -1063,7 +1060,7 @@ impl Backend { ) -> ClientResult { let is_archive_pruning = config.state_pruning.is_archive(); let blockchain = BlockchainDb::new(db.clone())?; - let map_e = |e: sc_state_db::Error| sp_blockchain::Error::from_state_db(e); + let map_e = sp_blockchain::Error::from_state_db; let state_db: StateDb<_, _> = StateDb::new( config.state_pruning.clone(), !db.supports_ref_counting(), @@ -1087,7 +1084,7 @@ impl Backend { is_archive: is_archive_pruning, io_stats: FrozenForDuration::new(std::time::Duration::from_secs(1)), state_usage: Arc::new(StateUsageStats::new()), - keep_blocks: config.keep_blocks.clone(), + keep_blocks: config.keep_blocks, genesis_state: RwLock::new(None), }; @@ -1135,7 +1132,7 @@ impl Backend { (meta.best_number - best_number).saturated_into::() > self.canonicalization_delay { - return Err(sp_blockchain::Error::SetHeadTooOld.into()) + return Err(sp_blockchain::Error::SetHeadTooOld) } let parent_exists = @@ -1154,16 +1151,16 @@ impl Backend { (&r.number, &r.hash) ); - return Err(::sp_blockchain::Error::NotInFinalizedChain.into()) + return Err(::sp_blockchain::Error::NotInFinalizedChain) } - retracted.push(r.hash.clone()); + retracted.push(r.hash); utils::remove_number_to_key_mapping(transaction, columns::KEY_LOOKUP, r.number)?; } // canonicalize: set the number lookup to map to this block's hash. for e in tree_route.enacted() { - enacted.push(e.hash.clone()); + enacted.push(e.hash); utils::insert_number_to_key_mapping( transaction, columns::KEY_LOOKUP, @@ -1199,8 +1196,7 @@ impl Backend { "Last finalized {:?} not parent of {:?}", last_finalized, header.hash() - )) - .into()) + ))) } Ok(()) } @@ -1217,7 +1213,7 @@ impl Backend { // TODO: ensure best chain contains this block. let number = *header.number(); self.ensure_sequential_finalization(header, last_finalized)?; - let with_state = sc_client_api::Backend::have_state_at(self, &hash, number); + let with_state = sc_client_api::Backend::have_state_at(self, hash, number); self.note_finalized(transaction, header, *hash, finalization_displaced, with_state)?; @@ -1264,9 +1260,10 @@ impl Backend { } trace!(target: "db", "Canonicalize block #{} ({:?})", new_canonical, hash); - let commit = self.storage.state_db.canonicalize_block(&hash).map_err( - |e: sc_state_db::Error| sp_blockchain::Error::from_state_db(e), - )?; + let commit = + self.storage.state_db.canonicalize_block(&hash).map_err( + sp_blockchain::Error::from_state_db::>, + )?; apply_state_commit(transaction, commit); } Ok(()) @@ -1282,7 +1279,7 @@ impl Backend { let mut meta_updates = Vec::with_capacity(operation.finalized_blocks.len()); let (best_num, mut last_finalized_hash, mut last_finalized_num, mut block_gap) = { let meta = self.blockchain.meta.read(); - (meta.best_number, meta.finalized_hash, meta.finalized_number, meta.block_gap.clone()) + (meta.best_number, meta.finalized_hash, meta.finalized_number, meta.block_gap) }; for (block, justification) in operation.finalized_blocks { @@ -1297,14 +1294,14 @@ impl Backend { &mut finalization_displaced_leaves, )?); last_finalized_hash = block_hash; - last_finalized_num = block_header.number().clone(); + last_finalized_num = *block_header.number(); } let imported = if let Some(pending_block) = operation.pending_block { let hash = pending_block.header.hash(); let parent_hash = *pending_block.header.parent_hash(); - let number = pending_block.header.number().clone(); + let number = *pending_block.header.number(); let existing_header = number <= best_num && self.blockchain.header(BlockId::hash(hash))?.is_some(); @@ -1352,7 +1349,7 @@ impl Backend { // memory to bootstrap consensus. It is queried for an initial list of // authorities, etc. *self.genesis_state.write() = Some(Arc::new(DbGenesisStorage::new( - pending_block.header.state_root().clone(), + *pending_block.header.state_root(), operation.db_updates.clone(), ))); } @@ -1411,7 +1408,7 @@ impl Backend { let commit = self .storage .state_db - .insert_block(&hash, number_u64, &pending_block.header.parent_hash(), changeset) + .insert_block(&hash, number_u64, pending_block.header.parent_hash(), changeset) .map_err(|e: sc_state_db::Error| { sp_blockchain::Error::from_state_db(e) })?; @@ -1419,7 +1416,7 @@ impl Backend { if number <= last_finalized_num { // Canonicalize in the db when re-importing existing blocks with state. let commit = self.storage.state_db.canonicalize_block(&hash).map_err( - |e: sc_state_db::Error| sp_blockchain::Error::from_state_db(e), + sp_blockchain::Error::from_state_db::>, )?; apply_state_commit(&mut transaction, commit); meta_updates.push(MetaUpdate { @@ -1549,11 +1546,8 @@ impl Backend { let number = header.number(); let hash = header.hash(); - let (enacted, retracted) = self.set_head_with_transaction( - &mut transaction, - hash.clone(), - (number.clone(), hash.clone()), - )?; + let (enacted, retracted) = + self.set_head_with_transaction(&mut transaction, hash, (*number, hash))?; meta_updates.push(MetaUpdate { hash, number: *number, @@ -1616,9 +1610,9 @@ impl Backend { displaced: &mut Option>>, with_state: bool, ) -> ClientResult<()> { - let f_num = f_header.number().clone(); + let f_num = *f_header.number(); - let lookup_key = utils::number_and_hash_to_lookup_key(f_num, f_hash.clone())?; + let lookup_key = utils::number_and_hash_to_lookup_key(f_num, f_hash)?; if with_state { transaction.set_from_vec(columns::META, meta_keys::FINALIZED_STATE, lookup_key.clone()); } @@ -1631,9 +1625,10 @@ impl Backend { .map(|c| f_num.saturated_into::() > c) .unwrap_or(true) { - let commit = self.storage.state_db.canonicalize_block(&f_hash).map_err( - |e: sc_state_db::Error| sp_blockchain::Error::from_state_db(e), - )?; + let commit = + self.storage.state_db.canonicalize_block(&f_hash).map_err( + sp_blockchain::Error::from_state_db::>, + )?; apply_state_commit(transaction, commit); } @@ -1664,18 +1659,18 @@ impl Backend { // Also discard all blocks from displaced branches for h in displaced.leaves() { let mut number = finalized; - let mut hash = h.clone(); + let mut hash = *h; // Follow displaced chains back until we reach a finalized block. // Since leaves are discarded due to finality, they can't have parents // that are canonical, but not yet finalized. So we stop deleting as soon as // we reach canonical chain. - while self.blockchain.hash(number)? != Some(hash.clone()) { - let id = BlockId::::hash(hash.clone()); + while self.blockchain.hash(number)? != Some(hash) { + let id = BlockId::::hash(hash); match self.blockchain.header(id)? { Some(header) => { self.prune_block(transaction, id)?; number = header.number().saturating_sub(One::one()); - hash = header.parent_hash().clone(); + hash = *header.parent_hash(); }, None => break, } @@ -1780,7 +1775,7 @@ fn apply_index_ops( // Bump ref counter let extrinsic = extrinsic.encode(); transaction.reference(columns::TRANSACTION, DbHash::from_slice(hash.as_ref())); - DbExtrinsic::Indexed { hash: hash.clone(), header: extrinsic } + DbExtrinsic::Indexed { hash: *hash, header: extrinsic } } else { match index_map.get(&(index as u32)) { Some((hash, size)) => { @@ -2063,8 +2058,7 @@ impl sc_client_api::backend::Backend for Backend { let update_finalized = best_number < finalized; - let key = - utils::number_and_hash_to_lookup_key(best_number.clone(), &best_hash)?; + let key = utils::number_and_hash_to_lookup_key(best_number, &best_hash)?; if update_finalized { transaction.set_from_vec( columns::META, @@ -2143,8 +2137,8 @@ impl sc_client_api::backend::Backend for Backend { return Err(sp_blockchain::Error::Backend(format!("Can't remove best block {:?}", hash))) } - let hdr = self.blockchain.header_metadata(hash.clone())?; - if !self.have_state_at(&hash, hdr.number) { + let hdr = self.blockchain.header_metadata(*hash)?; + if !self.have_state_at(hash, hdr.number) { return Err(sp_blockchain::Error::UnknownBlock(format!( "State already discarded for {:?}", hash @@ -2164,7 +2158,7 @@ impl sc_client_api::backend::Backend for Backend { apply_state_commit(&mut transaction, commit); } transaction.remove(columns::KEY_LOOKUP, hash.as_ref()); - leaves.revert(hash.clone(), hdr.number); + leaves.revert(*hash, hdr.number); leaves.prepare_transaction(&mut transaction, columns::META, meta_keys::LEAF_PREFIX); self.storage.db.commit(transaction)?; self.blockchain().remove_header_metadata(*hash); @@ -2185,8 +2179,7 @@ impl sc_client_api::backend::Backend for Backend { }; if is_genesis { if let Some(genesis_state) = &*self.genesis_state.read() { - let root = genesis_state.root.clone(); - let db_state = DbState::::new(genesis_state.clone(), root); + let db_state = DbState::::new(genesis_state.clone(), genesis_state.root); let state = RefTrackingState::new(db_state, self.storage.clone(), None); let caching_state = CachingState::new(state, self.shared_cache.clone(), None); let mut state = SyncingCachingState::new( @@ -2218,8 +2211,7 @@ impl sc_client_api::backend::Backend for Backend { if let Ok(()) = self.storage.state_db.pin(&hash) { let root = hdr.state_root; let db_state = DbState::::new(self.storage.clone(), root); - let state = - RefTrackingState::new(db_state, self.storage.clone(), Some(hash.clone())); + let state = RefTrackingState::new(db_state, self.storage.clone(), Some(hash)); let caching_state = CachingState::new(state, self.shared_cache.clone(), Some(hash)); Ok(SyncingCachingState::new( @@ -2241,7 +2233,7 @@ impl sc_client_api::backend::Backend for Backend { fn have_state_at(&self, hash: &Block::Hash, number: NumberFor) -> bool { if self.is_archive { - match self.blockchain.header_metadata(hash.clone()) { + match self.blockchain.header_metadata(*hash) { Ok(header) => sp_state_machine::Storage::get( self.storage.as_ref(), &header.state_root, diff --git a/client/db/src/offchain.rs b/client/db/src/offchain.rs index 4f0a77ce57566..030a410981b26 100644 --- a/client/db/src/offchain.rs +++ b/client/db/src/offchain.rs @@ -104,7 +104,7 @@ impl sp_core::offchain::OffchainStorage for LocalStorage { { drop(key_lock); let key_lock = locks.get_mut(&key); - if let Some(_) = key_lock.and_then(Arc::get_mut) { + if key_lock.and_then(Arc::get_mut).is_some() { locks.remove(&key); } } @@ -114,7 +114,7 @@ impl sp_core::offchain::OffchainStorage for LocalStorage { /// Concatenate the prefix and key to create an offchain key in the db. pub(crate) fn concatenate_prefix_and_key(prefix: &[u8], key: &[u8]) -> Vec { - prefix.iter().chain(key.into_iter()).cloned().collect() + prefix.iter().chain(key.iter()).cloned().collect() } #[cfg(test)] diff --git a/client/db/src/storage_cache.rs b/client/db/src/storage_cache.rs index 5047087376375..9dada92b066ea 100644 --- a/client/db/src/storage_cache.rs +++ b/client/db/src/storage_cache.rs @@ -360,9 +360,9 @@ impl CacheChanges { // Same block comitted twice with different state changes. // Treat it as reenacted/retracted. if is_best { - enacted.push(commit_hash.clone()); + enacted.push(*commit_hash); } else { - retracted.to_mut().push(commit_hash.clone()); + retracted.to_mut().push(*commit_hash); } } } @@ -371,7 +371,7 @@ impl CacheChanges { // Propagate cache only if committing on top of the latest canonical state // blocks are ordered by number and only one block with a given number is marked as // canonical (contributed to canonical state cache) - if let Some(_) = self.parent_hash { + if self.parent_hash.is_some() { let mut local_cache = self.local_cache.write(); if is_best { trace!( @@ -423,9 +423,9 @@ impl CacheChanges { storage: modifications, child_storage: child_modifications, number: *number, - hash: hash.clone(), + hash: *hash, is_canon: is_best, - parent: parent.clone(), + parent: *parent, }; let insert_at = cache .modifications @@ -564,7 +564,7 @@ impl>, B: BlockT> StateBackend> for Cachin let cache = self.cache.shared_cache.upgradable_read(); if Self::is_allowed(Some(key), None, &self.cache.parent_hash, &cache.modifications) { let mut cache = RwLockUpgradableReadGuard::upgrade(cache); - if let Some(entry) = cache.lru_hashes.get(key).map(|a| a.0.clone()) { + if let Some(entry) = cache.lru_hashes.get(key).map(|a| a.0) { trace!("Found hash in shared cache: {:?}", HexDisplay::from(&key)); return Ok(entry) } @@ -934,7 +934,7 @@ impl Drop for SyncingCachingState { let _lock = self.lock.read(); self.state_usage.merge_sm(caching_state.usage.take()); - if let Some(hash) = caching_state.cache.parent_hash.clone() { + if let Some(hash) = caching_state.cache.parent_hash { let is_best = self.meta.read().best_hash == hash; caching_state.cache.sync_cache(&[], &[], vec![], vec![], None, None, is_best); } diff --git a/client/db/src/upgrade.rs b/client/db/src/upgrade.rs index ec91a753ed870..cd18554fb0d06 100644 --- a/client/db/src/upgrade.rs +++ b/client/db/src/upgrade.rs @@ -30,7 +30,7 @@ use kvdb_rocksdb::{Database, DatabaseConfig}; use sp_runtime::traits::Block as BlockT; /// Version file name. -const VERSION_FILE_NAME: &'static str = "db_version"; +const VERSION_FILE_NAME: &str = "db_version"; /// Current db version. const CURRENT_VERSION: u32 = 4; diff --git a/client/db/src/utils.rs b/client/db/src/utils.rs index 1798838ecc155..d3cb9a994fdd3 100644 --- a/client/db/src/utils.rs +++ b/client/db/src/utils.rs @@ -201,16 +201,16 @@ fn open_database_at( db_type: DatabaseType, ) -> sp_blockchain::Result>> { let db: Arc> = match &source { - DatabaseSource::ParityDb { path } => open_parity_db::(&path, db_type, true)?, + DatabaseSource::ParityDb { path } => open_parity_db::(path, db_type, true)?, DatabaseSource::RocksDb { path, cache_size } => - open_kvdb_rocksdb::(&path, db_type, true, *cache_size)?, + open_kvdb_rocksdb::(path, db_type, true, *cache_size)?, DatabaseSource::Custom(db) => db.clone(), DatabaseSource::Auto { paritydb_path, rocksdb_path, cache_size } => { // check if rocksdb exists first, if not, open paritydb - match open_kvdb_rocksdb::(&rocksdb_path, db_type, false, *cache_size) { + match open_kvdb_rocksdb::(rocksdb_path, db_type, false, *cache_size) { Ok(db) => db, Err(OpenDbError::NotEnabled(_)) | Err(OpenDbError::DoesNotExist) => - open_parity_db::(&paritydb_path, db_type, true)?, + open_parity_db::(paritydb_path, db_type, true)?, Err(_) => return Err(backend_err("cannot open rocksdb. corrupted database")), } }, @@ -234,7 +234,7 @@ type OpenDbResult = Result>, OpenDbError>; impl fmt::Display for OpenDbError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - OpenDbError::Internal(e) => write!(f, "{}", e.to_string()), + OpenDbError::Internal(e) => write!(f, "{}", e), OpenDbError::DoesNotExist => write!(f, "Database does not exist at given location"), OpenDbError::NotEnabled(feat) => { write!(f, "`{}` feature not enabled, database can not be opened", feat) @@ -300,7 +300,7 @@ fn open_kvdb_rocksdb( cache_size: usize, ) -> OpenDbResult { // first upgrade database to required version - match crate::upgrade::upgrade_db::(&path, db_type) { + match crate::upgrade::upgrade_db::(path, db_type) { // in case of missing version file, assume that database simply does not exist at given // location Ok(_) | Err(crate::upgrade::UpgradeError::MissingDatabaseVersionFile) => (), @@ -363,8 +363,7 @@ pub fn check_database_type( return Err(sp_blockchain::Error::Backend(format!( "Unexpected database type. Expected: {}", db_type.as_str() - )) - .into()) + ))) }, None => { let mut transaction = Transaction::new(); @@ -425,9 +424,9 @@ pub fn read_db( where Block: BlockT, { - block_id_to_lookup_key(db, col_index, id).and_then(|key| match key { - Some(key) => Ok(db.get(col, key.as_ref())), - None => Ok(None), + block_id_to_lookup_key(db, col_index, id).map(|key| match key { + Some(key) => db.get(col, key.as_ref()), + None => None, }) } @@ -442,9 +441,10 @@ pub fn remove_from_db( where Block: BlockT, { - block_id_to_lookup_key(db, col_index, id).and_then(|key| match key { - Some(key) => Ok(transaction.remove(col, key.as_ref())), - None => Ok(()), + block_id_to_lookup_key(db, col_index, id).map(|key| { + if let Some(key) = key { + transaction.remove(col, key.as_ref()); + } }) } @@ -458,7 +458,7 @@ pub fn read_header( match read_db(db, col_index, col, id)? { Some(header) => match Block::Header::decode(&mut &header[..]) { Ok(header) => Ok(Some(header)), - Err(_) => return Err(sp_blockchain::Error::Backend("Error decoding header".into())), + Err(_) => Err(sp_blockchain::Error::Backend("Error decoding header".into())), }, None => Ok(None), } diff --git a/client/executor/common/src/runtime_blob/data_segments_snapshot.rs b/client/executor/common/src/runtime_blob/data_segments_snapshot.rs index b44370e681b13..e65fc32f637a6 100644 --- a/client/executor/common/src/runtime_blob/data_segments_snapshot.rs +++ b/client/executor/common/src/runtime_blob/data_segments_snapshot.rs @@ -39,7 +39,7 @@ impl DataSegmentsSnapshot { .map(|mut segment| { // Just replace contents of the segment since the segments will be discarded later // anyway. - let contents = mem::replace(segment.value_mut(), vec![]); + let contents = mem::take(segment.value_mut()); let init_expr = match segment.offset() { Some(offset) => offset.code(), diff --git a/client/executor/common/src/runtime_blob/runtime_blob.rs b/client/executor/common/src/runtime_blob/runtime_blob.rs index 649ff51f287e1..08df4b32d59eb 100644 --- a/client/executor/common/src/runtime_blob/runtime_blob.rs +++ b/client/executor/common/src/runtime_blob/runtime_blob.rs @@ -187,9 +187,7 @@ impl RuntimeBlob { } /// Returns an iterator of all globals which were exported by [`expose_mutable_globals`]. - pub(super) fn exported_internal_global_names<'module>( - &'module self, - ) -> impl Iterator { + pub(super) fn exported_internal_global_names(&self) -> impl Iterator { let exports = self.raw_module.export_section().map(|es| es.entries()).unwrap_or(&[]); exports.iter().filter_map(|export| match export.internal() { Internal::Global(_) if export.field().starts_with("exported_internal_global") => diff --git a/client/executor/common/src/sandbox.rs b/client/executor/common/src/sandbox.rs index fe14c0865cfde..1e925bd5a7835 100644 --- a/client/executor/common/src/sandbox.rs +++ b/client/executor/common/src/sandbox.rs @@ -264,8 +264,8 @@ fn decode_environment_definition( let memory_ref = memories .get(memory_idx as usize) .cloned() - .ok_or_else(|| InstantiationError::EnvironmentDefinitionCorrupted)? - .ok_or_else(|| InstantiationError::EnvironmentDefinitionCorrupted)?; + .ok_or(InstantiationError::EnvironmentDefinitionCorrupted)? + .ok_or(InstantiationError::EnvironmentDefinitionCorrupted)?; memories_map.insert((module, field), memory_ref); }, } @@ -458,7 +458,7 @@ impl Store

{ }; let mem_idx = memories.len(); - memories.push(Some(memory.clone())); + memories.push(Some(memory)); Ok(mem_idx as u32) } @@ -472,7 +472,7 @@ impl Store
{ pub fn instance(&self, instance_idx: u32) -> Result> { self.instances .get(instance_idx as usize) - .ok_or_else(|| "Trying to access a non-existent instance")? + .ok_or("Trying to access a non-existent instance")? .as_ref() .map(|v| v.0.clone()) .ok_or_else(|| "Trying to access a torndown instance".into()) @@ -488,7 +488,7 @@ impl Store
{ self.instances .get(instance_idx as usize) .as_ref() - .ok_or_else(|| "Trying to access a non-existent instance")? + .ok_or("Trying to access a non-existent instance")? .as_ref() .map(|v| v.1.clone()) .ok_or_else(|| "Trying to access a torndown instance".into()) @@ -504,7 +504,7 @@ impl Store
{ self.memories .get(memory_idx as usize) .cloned() - .ok_or_else(|| "Trying to access a non-existent sandboxed memory")? + .ok_or("Trying to access a non-existent sandboxed memory")? .ok_or_else(|| "Trying to access a torndown sandboxed memory".into()) } @@ -564,7 +564,7 @@ impl Store
{ #[cfg(feature = "wasmer-sandbox")] BackendContext::Wasmer(ref context) => - wasmer_instantiate(&context, wasm, guest_env, state, sandbox_context)?, + wasmer_instantiate(context, wasm, guest_env, state, sandbox_context)?, }; Ok(UnregisteredInstance { sandbox_instance }) diff --git a/client/executor/common/src/sandbox/wasmer_backend.rs b/client/executor/common/src/sandbox/wasmer_backend.rs index ab585c7d15431..29926141ed8b8 100644 --- a/client/executor/common/src/sandbox/wasmer_backend.rs +++ b/client/executor/common/src/sandbox/wasmer_backend.rs @@ -113,7 +113,7 @@ pub fn instantiate( type Exports = HashMap; let mut exports_map = Exports::new(); - for import in module.imports().into_iter() { + for import in module.imports() { match import.ty() { // Nothing to do here wasmer::ExternType::Global(_) | wasmer::ExternType::Table(_) => (), @@ -121,7 +121,7 @@ pub fn instantiate( wasmer::ExternType::Memory(_) => { let exports = exports_map .entry(import.module().to_string()) - .or_insert(wasmer::Exports::new()); + .or_insert_with(wasmer::Exports::new); let memory = guest_env .imports @@ -173,7 +173,7 @@ pub fn instantiate( let exports = exports_map .entry(import.module().to_string()) - .or_insert(wasmer::Exports::new()); + .or_insert_with(wasmer::Exports::new); exports.insert(import.name(), wasmer::Extern::Function(function)); }, diff --git a/client/executor/common/src/sandbox/wasmi_backend.rs b/client/executor/common/src/sandbox/wasmi_backend.rs index 9c7c154b5b135..03fa5dc06dea8 100644 --- a/client/executor/common/src/sandbox/wasmi_backend.rs +++ b/client/executor/common/src/sandbox/wasmi_backend.rs @@ -78,7 +78,7 @@ impl ImportResolver for Imports { // Here we use inner memory reference only to resolve the imports // without accessing the memory contents. All subsequent memory accesses // should happen through the wrapper, that enforces the memory access protocol. - let mem = wrapper.0.clone(); + let mem = wrapper.0; Ok(mem) } @@ -247,7 +247,7 @@ impl<'a> wasmi::Externals for GuestExternals<'a> { serialized_result_val_ptr, "Can't deallocate memory for dispatch thunk's result", ) - .and_then(|_| serialized_result_val) + .and(serialized_result_val) .and_then(|serialized_result_val| { let result_val = std::result::Result::::decode(&mut serialized_result_val.as_slice()) .map_err(|_| trap("Decoding Result failed!"))?; diff --git a/client/executor/src/native_executor.rs b/client/executor/src/native_executor.rs index 669780f2a4b6a..ea060a89c13df 100644 --- a/client/executor/src/native_executor.rs +++ b/client/executor/src/native_executor.rs @@ -253,7 +253,7 @@ where wasm_code: &[u8], ext: &mut dyn Externalities, ) -> std::result::Result, String> { - let runtime_blob = RuntimeBlob::uncompress_if_needed(&wasm_code) + let runtime_blob = RuntimeBlob::uncompress_if_needed(wasm_code) .map_err(|e| format!("Failed to create runtime blob: {:?}", e))?; if let Some(version) = crate::wasm_runtime::read_embedded_version(&runtime_blob) @@ -493,8 +493,7 @@ impl RuntimeSpawn for RuntimeInstanceSpawn { fn join(&self, handle: u64) -> Vec { let receiver = self.tasks.lock().remove(&handle).expect("No task for the handle"); - let output = receiver.recv().expect("Spawned task panicked for the handle"); - output + receiver.recv().expect("Spawned task panicked for the handle") } } diff --git a/client/executor/src/wasm_runtime.rs b/client/executor/src/wasm_runtime.rs index 952130e980874..9c07dd7abf1f2 100644 --- a/client/executor/src/wasm_runtime.rs +++ b/client/executor/src/wasm_runtime.rs @@ -368,7 +368,7 @@ pub fn read_embedded_version(blob: &RuntimeBlob) -> Result sandbox::SandboxContext for SandboxContext<'a> { match result { Ok(Some(RuntimeValue::I64(val))) => Ok(val), - Ok(_) => return Err("Supervisor function returned unexpected result!".into()), + Ok(_) => Err("Supervisor function returned unexpected result!".into()), Err(err) => Err(Error::Sandbox(err.to_string())), } } @@ -161,7 +161,7 @@ impl Sandbox for FunctionExecutor { Ok(buffer) => buffer, }; - if let Err(_) = self.memory.set(buf_ptr.into(), &buffer) { + if self.memory.set(buf_ptr.into(), &buffer).is_err() { return Ok(sandbox_env::ERR_OUT_OF_BOUNDS) } @@ -185,7 +185,7 @@ impl Sandbox for FunctionExecutor { Ok(buffer) => buffer, }; - if let Err(_) = sandboxed_memory.write_from(Pointer::new(offset as u32), &buffer) { + if sandboxed_memory.write_from(Pointer::new(offset as u32), &buffer).is_err() { return Ok(sandbox_env::ERR_OUT_OF_BOUNDS) } @@ -241,9 +241,9 @@ impl Sandbox for FunctionExecutor { Ok(None) => Ok(sandbox_env::ERR_OK), Ok(Some(val)) => { // Serialize return value and write it back into the memory. - sp_wasm_interface::ReturnValue::Value(val.into()).using_encoded(|val| { + sp_wasm_interface::ReturnValue::Value(val).using_encoded(|val| { if val.len() > return_val_len as usize { - Err("Return value buffer is too small")?; + return Err("Return value buffer is too small".into()) } self.write_memory(return_val, val).map_err(|_| "Return value buffer is OOB")?; Ok(sandbox_env::ERR_OK) @@ -272,11 +272,11 @@ impl Sandbox for FunctionExecutor { let table = self .table .as_ref() - .ok_or_else(|| "Runtime doesn't have a table; sandbox is unavailable")?; + .ok_or("Runtime doesn't have a table; sandbox is unavailable")?; table .get(dispatch_thunk_id) .map_err(|_| "dispatch_thunk_idx is out of the table bounds")? - .ok_or_else(|| "dispatch_thunk_idx points on an empty table entry")? + .ok_or("dispatch_thunk_idx points on an empty table entry")? }; let guest_env = @@ -458,9 +458,9 @@ impl wasmi::Externals for FunctionExecutor { fn get_mem_instance(module: &ModuleRef) -> Result { Ok(module .export_by_name("memory") - .ok_or_else(|| Error::InvalidMemoryReference)? + .ok_or(Error::InvalidMemoryReference)? .as_memory() - .ok_or_else(|| Error::InvalidMemoryReference)? + .ok_or(Error::InvalidMemoryReference)? .clone()) } @@ -469,9 +469,9 @@ fn get_mem_instance(module: &ModuleRef) -> Result { fn get_heap_base(module: &ModuleRef) -> Result { let heap_base_val = module .export_by_name("__heap_base") - .ok_or_else(|| Error::HeapBaseNotFoundOrInvalid)? + .ok_or(Error::HeapBaseNotFoundOrInvalid)? .as_global() - .ok_or_else(|| Error::HeapBaseNotFoundOrInvalid)? + .ok_or(Error::HeapBaseNotFoundOrInvalid)? .get(); match heap_base_val { @@ -564,7 +564,7 @@ fn call_in_wasm_module( match result { Ok(Some(I64(r))) => { let (ptr, length) = unpack_ptr_and_len(r as u64); - memory.get(ptr.into(), length as usize).map_err(|_| Error::Runtime) + memory.get(ptr, length as usize).map_err(|_| Error::Runtime) }, Err(e) => { trace!( @@ -572,7 +572,7 @@ fn call_in_wasm_module( "Failed to execute code with {} pages", memory.current_size().0, ); - Err(e.into()) + Err(e) }, _ => Err(Error::InvalidReturn), } diff --git a/client/executor/wasmtime/src/host.rs b/client/executor/wasmtime/src/host.rs index 376eba86829a7..0cb64820fa8a3 100644 --- a/client/executor/wasmtime/src/host.rs +++ b/client/executor/wasmtime/src/host.rs @@ -243,7 +243,7 @@ impl<'a> Sandbox for HostContext<'a> { // Serialize return value and write it back into the memory. sp_wasm_interface::ReturnValue::Value(val.into()).using_encoded(|val| { if val.len() > return_val_len as usize { - Err("Return value buffer is too small")?; + return Err("Return value buffer is too small".into()) } ::write_memory(self, return_val, val) .map_err(|_| "can't write return value")?; @@ -273,19 +273,18 @@ impl<'a> Sandbox for HostContext<'a> { .caller .data() .table() - .ok_or_else(|| "Runtime doesn't have a table; sandbox is unavailable")?; + .ok_or("Runtime doesn't have a table; sandbox is unavailable")?; let table_item = table.get(&mut self.caller, dispatch_thunk_id); table_item - .ok_or_else(|| "dispatch_thunk_id is out of bounds")? + .ok_or("dispatch_thunk_id is out of bounds")? .funcref() - .ok_or_else(|| "dispatch_thunk_idx should be a funcref")? - .ok_or_else(|| "dispatch_thunk_idx should point to actual func")? + .ok_or("dispatch_thunk_idx should be a funcref")? + .ok_or("dispatch_thunk_idx should point to actual func")? .clone() }; - let guest_env = match sandbox::GuestEnvironment::decode(&self.sandbox_store(), raw_env_def) - { + let guest_env = match sandbox::GuestEnvironment::decode(self.sandbox_store(), raw_env_def) { Ok(guest_env) => guest_env, Err(_) => return Ok(sandbox_env::ERR_MODULE as u32), }; @@ -304,7 +303,7 @@ impl<'a> Sandbox for HostContext<'a> { wasm, guest_env, state, - &mut SandboxContext { host_context: self, dispatch_thunk: dispatch_thunk.clone() }, + &mut SandboxContext { host_context: self, dispatch_thunk }, ) })); @@ -316,7 +315,7 @@ impl<'a> Sandbox for HostContext<'a> { }; let instance_idx_or_err_code = match result { - Ok(instance) => instance.register(&mut self.sandbox_store_mut(), dispatch_thunk), + Ok(instance) => instance.register(self.sandbox_store_mut(), dispatch_thunk), Err(sandbox::InstantiationError::StartTrapped) => sandbox_env::ERR_EXECUTION, Err(_) => sandbox_env::ERR_MODULE, }; @@ -366,7 +365,7 @@ impl<'a, 'b> sandbox::SandboxContext for SandboxContext<'a, 'b> { if let Some(ret_val) = ret_vals[0].i64() { Ok(ret_val) } else { - return Err("Supervisor function returned unexpected result!".into()) + Err("Supervisor function returned unexpected result!".into()) }, Err(err) => Err(err.to_string().into()), } diff --git a/client/finality-grandpa/rpc/src/report.rs b/client/finality-grandpa/rpc/src/report.rs index 24d0b5ab0d1d6..8c04ca28ef870 100644 --- a/client/finality-grandpa/rpc/src/report.rs +++ b/client/finality-grandpa/rpc/src/report.rs @@ -88,10 +88,10 @@ impl RoundState { voters: &HashSet, ) -> Result { let prevotes = &round_state.prevote_ids; - let missing_prevotes = voters.difference(&prevotes).cloned().collect(); + let missing_prevotes = voters.difference(prevotes).cloned().collect(); let precommits = &round_state.precommit_ids; - let missing_precommits = voters.difference(&precommits).cloned().collect(); + let missing_precommits = voters.difference(precommits).cloned().collect(); Ok(Self { round: round.try_into()?, diff --git a/client/finality-grandpa/src/authorities.rs b/client/finality-grandpa/src/authorities.rs index 668fe5f269051..c2d44371986ca 100644 --- a/client/finality-grandpa/src/authorities.rs +++ b/client/finality-grandpa/src/authorities.rs @@ -557,8 +557,7 @@ where fork_tree::FinalizationResult::Changed(change) => { status.changed = true; - let pending_forced_changes = - std::mem::replace(&mut self.pending_forced_changes, Vec::new()); + let pending_forced_changes = std::mem::take(&mut self.pending_forced_changes); // we will keep all forced changes for any later blocks and that are a // descendent of the finalized block (i.e. they are part of this branch). diff --git a/client/finality-grandpa/src/aux_schema.rs b/client/finality-grandpa/src/aux_schema.rs index 0ac9ba9e64bd2..25ed4a3f490e0 100644 --- a/client/finality-grandpa/src/aux_schema.rs +++ b/client/finality-grandpa/src/aux_schema.rs @@ -430,7 +430,7 @@ where // reset. let set_state = VoterSetState::::live( new_set.set_id, - &set, + set, (new_set.canon_hash, new_set.canon_number), ); let encoded = set_state.encode(); diff --git a/client/finality-grandpa/src/communication/gossip.rs b/client/finality-grandpa/src/communication/gossip.rs index c39e2e82a621e..250e640cbf037 100644 --- a/client/finality-grandpa/src/communication/gossip.rs +++ b/client/finality-grandpa/src/communication/gossip.rs @@ -504,13 +504,13 @@ impl Peers { fn new_peer(&mut self, who: PeerId, role: ObservedRole) { match role { ObservedRole::Authority if self.first_stage_peers.len() < LUCKY_PEERS => { - self.first_stage_peers.insert(who.clone()); + self.first_stage_peers.insert(who); }, ObservedRole::Authority if self.second_stage_peers.len() < LUCKY_PEERS => { - self.second_stage_peers.insert(who.clone()); + self.second_stage_peers.insert(who); }, ObservedRole::Light if self.lucky_light_peers.len() < LUCKY_PEERS => { - self.lucky_light_peers.insert(who.clone()); + self.lucky_light_peers.insert(who); }, _ => {}, } @@ -590,11 +590,8 @@ impl Peers { // - third set: LUCKY_PEERS random light client peers let shuffled_peers = { - let mut peers = self - .inner - .iter() - .map(|(peer_id, info)| (*peer_id, info.clone())) - .collect::>(); + let mut peers = + self.inner.iter().map(|(peer_id, info)| (*peer_id, info)).collect::>(); peers.shuffle(&mut rand::thread_rng()); peers @@ -1103,7 +1100,7 @@ impl Inner { // won't be able to reply since they don't follow the full GRANDPA // protocol and therefore might not have the vote data available. if let (Some(peer), Some(local_view)) = (self.peers.peer(who), &self.local_view) { - if self.catch_up_config.request_allowed(&peer) && + if self.catch_up_config.request_allowed(peer) && peer.view.set_id == local_view.set_id && peer.view.round.0.saturating_sub(CATCH_UP_THRESHOLD) > local_view.round.0 { @@ -1195,7 +1192,7 @@ impl Inner { return (false, None) } else { // report peer for timeout - Some((peer.clone(), cost::CATCH_UP_REQUEST_TIMEOUT)) + Some((*peer, cost::CATCH_UP_REQUEST_TIMEOUT)) } }, PendingCatchUp::Processing { instant, .. } => { @@ -1209,7 +1206,7 @@ impl Inner { }; self.pending_catch_up = PendingCatchUp::Requesting { - who: who.clone(), + who: *who, request: catch_up_request.clone(), instant: Instant::now(), }; @@ -1488,7 +1485,7 @@ impl sc_network_gossip::Validator for GossipValidator sc_network_gossip::Validator for GossipValidator { - self.report(who.clone(), cb); + self.report(*who, cb); context.broadcast_message(topic, data.to_vec(), false); sc_network_gossip::ValidationResult::ProcessAndKeep(topic) }, Action::ProcessAndDiscard(topic, cb) => { - self.report(who.clone(), cb); + self.report(*who, cb); sc_network_gossip::ValidationResult::ProcessAndDiscard(topic) }, Action::Discard(cb) => { - self.report(who.clone(), cb); + self.report(*who, cb); sc_network_gossip::ValidationResult::Discard }, } @@ -1572,7 +1569,7 @@ impl sc_network_gossip::Validator for GossipValidator return false, Some(x) => x, }; @@ -1583,11 +1580,9 @@ impl sc_network_gossip::Validator for GossipValidator( let mut total_weight = 0; for id in votes { - if let Some(weight) = voters.get(&id).map(|info| info.weight()) { + if let Some(weight) = voters.get(id).map(|info| info.weight()) { total_weight += weight.get(); if total_weight > full_threshold { return Err(cost::MALFORMED_CATCH_UP) diff --git a/client/finality-grandpa/src/environment.rs b/client/finality-grandpa/src/environment.rs index 6ffcdc719a166..63c8697053842 100644 --- a/client/finality-grandpa/src/environment.rs +++ b/client/finality-grandpa/src/environment.rs @@ -451,7 +451,7 @@ impl, SC, VR> Environment) -> Result>, Error>, { self.voter_set_state.with(|voter_set_state| { - if let Some(set_state) = f(&voter_set_state)? { + if let Some(set_state) = f(voter_set_state)? { *voter_set_state = set_state; if let Some(metrics) = self.metrics.as_ref() { @@ -987,11 +987,9 @@ where let mut current_rounds = current_rounds.clone(); current_rounds.remove(&round); - // NOTE: this condition should always hold as GRANDPA rounds are always + // NOTE: this entry should always exist as GRANDPA rounds are always // started in increasing order, still it's better to play it safe. - if !current_rounds.contains_key(&(round + 1)) { - current_rounds.insert(round + 1, HasVoted::No); - } + current_rounds.entry(round + 1).or_insert(HasVoted::No); let set_state = VoterSetState::::Live { completed_rounds, current_rounds }; @@ -1046,7 +1044,7 @@ where .votes .extend(historical_votes.seen().iter().skip(n_existing_votes).cloned()); already_completed.state = state; - crate::aux_schema::write_concluded_round(&*self.client, &already_completed)?; + crate::aux_schema::write_concluded_round(&*self.client, already_completed)?; } let set_state = VoterSetState::::Live { diff --git a/client/finality-grandpa/src/import.rs b/client/finality-grandpa/src/import.rs index ae5839d0c24e6..eefb3d3f0aee4 100644 --- a/client/finality-grandpa/src/import.rs +++ b/client/finality-grandpa/src/import.rs @@ -439,8 +439,7 @@ where // This code may be removed once warp sync to an old runtime is no longer needed. for prefix in ["GrandpaFinality", "Grandpa"] { let k = [twox_128(prefix.as_bytes()), twox_128(b"CurrentSetId")].concat(); - if let Ok(Some(id)) = - self.inner.storage(&id, &sc_client_api::StorageKey(k.to_vec())) + if let Ok(Some(id)) = self.inner.storage(id, &sc_client_api::StorageKey(k.to_vec())) { if let Ok(id) = SetId::decode(&mut id.0.as_ref()) { return Ok(id) @@ -451,7 +450,7 @@ where } else { self.inner .runtime_api() - .current_set_id(&id) + .current_set_id(id) .map_err(|e| ConsensusError::ClientImport(e.to_string())) } } @@ -732,7 +731,7 @@ impl GrandpaBlockImport Result, GrandpaError> { - environment::ancestry(&self.client, base, block) + environment::ancestry(self.client, base, block) } } @@ -193,13 +193,13 @@ where ); let observer_work = ObserverWork::new( - client.clone(), + client, network, persistent_data, config.keystore, voter_commands_rx, Some(justification_sender), - telemetry.clone(), + telemetry, ); let observer_work = observer_work.map_ok(|_| ()).map_err(|e| { diff --git a/client/finality-grandpa/src/voting_rule.rs b/client/finality-grandpa/src/voting_rule.rs index 749c504a051c1..209b0f1b33ced 100644 --- a/client/finality-grandpa/src/voting_rule.rs +++ b/client/finality-grandpa/src/voting_rule.rs @@ -125,7 +125,7 @@ where let current_target = current_target.clone(); // find the block at the given target height - Box::pin(std::future::ready(find_target(&*backend, target_number.clone(), ¤t_target))) + Box::pin(std::future::ready(find_target(&*backend, target_number, ¤t_target))) } } diff --git a/client/finality-grandpa/src/warp_proof.rs b/client/finality-grandpa/src/warp_proof.rs index bdb8e36373de3..90f6828a1105d 100644 --- a/client/finality-grandpa/src/warp_proof.rs +++ b/client/finality-grandpa/src/warp_proof.rs @@ -205,7 +205,7 @@ impl WarpSyncProof { let hash = proof.header.hash(); let number = *proof.header.number(); - if let Some((set_id, list)) = hard_forks.get(&(hash.clone(), number)) { + if let Some((set_id, list)) = hard_forks.get(&(hash, number)) { current_set_id = *set_id; current_authorities = list.clone(); } else { diff --git a/client/informant/src/lib.rs b/client/informant/src/lib.rs index 88a500a3a98fa..5dca77f1a7433 100644 --- a/client/informant/src/lib.rs +++ b/client/informant/src/lib.rs @@ -116,7 +116,7 @@ where if let Some((ref last_num, ref last_hash)) = last_best { if n.header.parent_hash() != last_hash && n.is_new_best { let maybe_ancestor = - sp_blockchain::lowest_common_ancestor(&*client, last_hash.clone(), n.hash); + sp_blockchain::lowest_common_ancestor(&*client, *last_hash, n.hash); match maybe_ancestor { Ok(ref ancestor) if ancestor.hash != *last_hash => info!( @@ -135,13 +135,13 @@ where } if n.is_new_best { - last_best = Some((n.header.number().clone(), n.hash.clone())); + last_best = Some((*n.header.number(), n.hash)); } // If we already printed a message for a given block recently, // we should not print it again. if !last_blocks.contains(&n.hash) { - last_blocks.push_back(n.hash.clone()); + last_blocks.push_back(n.hash); if last_blocks.len() > max_blocks_to_track { last_blocks.pop_front(); diff --git a/client/keystore/src/local.rs b/client/keystore/src/local.rs index 9f6f18d0c2930..19be6715ffe3f 100644 --- a/client/keystore/src/local.rs +++ b/client/keystore/src/local.rs @@ -198,7 +198,7 @@ impl SyncCryptoStore for LocalKeystore { .0 .read() .key_pair_by_type::(&pub_key, id) - .map_err(|e| TraitError::from(e))?; + .map_err(TraitError::from)?; key_pair.map(|k| k.sign(msg).encode()).map(Ok).transpose() }, sr25519::CRYPTO_ID => { @@ -209,7 +209,7 @@ impl SyncCryptoStore for LocalKeystore { .0 .read() .key_pair_by_type::(&pub_key, id) - .map_err(|e| TraitError::from(e))?; + .map_err(TraitError::from)?; key_pair.map(|k| k.sign(msg).encode()).map(Ok).transpose() }, ecdsa::CRYPTO_ID => { @@ -220,7 +220,7 @@ impl SyncCryptoStore for LocalKeystore { .0 .read() .key_pair_by_type::(&pub_key, id) - .map_err(|e| TraitError::from(e))?; + .map_err(TraitError::from)?; key_pair.map(|k| k.sign(msg).encode()).map(Ok).transpose() }, _ => Err(TraitError::KeyNotSupported(id)), @@ -320,7 +320,7 @@ impl SyncCryptoStore for LocalKeystore { fn has_keys(&self, public_keys: &[(Vec, KeyTypeId)]) -> bool { public_keys .iter() - .all(|(p, t)| self.0.read().key_phrase_by_type(&p, *t).ok().flatten().is_some()) + .all(|(p, t)| self.0.read().key_phrase_by_type(p, *t).ok().flatten().is_some()) } fn sr25519_vrf_sign( @@ -536,7 +536,7 @@ impl KeystoreInner { 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] != &id.0 { + if hex[0..4] != id.0 { continue } let public = hex[4..].to_vec(); diff --git a/client/network-gossip/src/state_machine.rs b/client/network-gossip/src/state_machine.rs index 4f06819df64d1..8a016cbaab3da 100644 --- a/client/network-gossip/src/state_machine.rs +++ b/client/network-gossip/src/state_machine.rs @@ -88,8 +88,7 @@ impl<'g, 'p, B: BlockT> ValidatorContext for NetworkContext<'g, 'p, B> { /// Send addressed message to a peer. fn send_message(&mut self, who: &PeerId, message: Vec) { - self.network - .write_notification(who.clone(), self.gossip.protocol.clone(), message); + self.network.write_notification(*who, self.gossip.protocol.clone(), message); } /// Send all messages with given topic to a peer. @@ -116,13 +115,13 @@ where for (message_hash, topic, message) in messages.clone() { let intent = match intent { MessageIntent::Broadcast { .. } => - if peer.known_messages.contains(&message_hash) { + if peer.known_messages.contains(message_hash) { continue } else { MessageIntent::Broadcast }, MessageIntent::PeriodicRebroadcast => { - if peer.known_messages.contains(&message_hash) { + if peer.known_messages.contains(message_hash) { MessageIntent::PeriodicRebroadcast } else { // peer doesn't know message, so the logic should treat it as an @@ -133,11 +132,11 @@ where other => other, }; - if !message_allowed(id, intent, &topic, &message) { + if !message_allowed(id, intent, topic, message) { continue } - peer.known_messages.insert(message_hash.clone()); + peer.known_messages.insert(*message_hash); tracing::trace!( target: "gossip", @@ -146,7 +145,7 @@ where ?message, "Propagating message", ); - network.write_notification(id.clone(), protocol.clone(), message.clone()); + network.write_notification(*id, protocol.clone(), message.clone()); } } } @@ -198,8 +197,7 @@ impl ConsensusGossip { ?role, "Registering peer", ); - self.peers - .insert(who.clone(), PeerConsensus { known_messages: Default::default() }); + self.peers.insert(who, PeerConsensus { known_messages: Default::default() }); let validator = self.validator.clone(); let mut context = NetworkContext { gossip: self, network }; @@ -213,7 +211,7 @@ impl ConsensusGossip { message: Vec, sender: Option, ) { - if self.known_messages.put(message_hash.clone(), ()).is_none() { + if self.known_messages.put(message_hash, ()).is_none() { self.messages.push(MessageEntry { message_hash, topic, message, sender }); if let Some(ref metrics) = self.metrics { @@ -319,10 +317,7 @@ impl ConsensusGossip { self.messages .iter() .filter(move |e| e.topic == topic) - .map(|entry| TopicNotification { - message: entry.message.clone(), - sender: entry.sender.clone(), - }) + .map(|entry| TopicNotification { message: entry.message.clone(), sender: entry.sender }) } /// Register incoming messages and return the ones that are new and valid (according to a gossip @@ -355,7 +350,7 @@ impl ConsensusGossip { protocol = %self.protocol, "Ignored already known message", ); - network.report_peer(who.clone(), rep::DUPLICATE_GOSSIP); + network.report_peer(who, rep::DUPLICATE_GOSSIP); continue } @@ -393,15 +388,13 @@ impl ConsensusGossip { }, }; - network.report_peer(who.clone(), rep::GOSSIP_SUCCESS); + network.report_peer(who, rep::GOSSIP_SUCCESS); peer.known_messages.insert(message_hash); - to_forward.push(( - topic, - TopicNotification { message: message.clone(), sender: Some(who.clone()) }, - )); + to_forward + .push((topic, TopicNotification { message: message.clone(), sender: Some(who) })); if keep { - self.register_message_hashed(message_hash, topic, message, Some(who.clone())); + self.register_message_hashed(message_hash, topic, message, Some(who)); } } @@ -431,7 +424,7 @@ impl ConsensusGossip { continue } - peer.known_messages.insert(entry.message_hash.clone()); + peer.known_messages.insert(entry.message_hash); tracing::trace!( target: "gossip", @@ -440,11 +433,7 @@ impl ConsensusGossip { ?entry.message, "Sending topic message", ); - network.write_notification( - who.clone(), - self.protocol.clone(), - entry.message.clone(), - ); + network.write_notification(*who, self.protocol.clone(), entry.message.clone()); } } } @@ -489,7 +478,7 @@ impl ConsensusGossip { ); peer.known_messages.insert(message_hash); - network.write_notification(who.clone(), self.protocol.clone(), message); + network.write_notification(*who, self.protocol.clone(), message); } } diff --git a/client/network/src/behaviour.rs b/client/network/src/behaviour.rs index 5ff3ba1ad44f4..b0bf3d6a1c135 100644 --- a/client/network/src/behaviour.rs +++ b/client/network/src/behaviour.rs @@ -434,7 +434,6 @@ where "Trying to send warp sync request when no protocol is configured {:?}", request, ); - return }, }, CustomMessageOutcome::NotificationStreamOpened { @@ -449,7 +448,7 @@ where protocol, negotiated_fallback, role: reported_roles_to_observed_role(roles), - notifications_sink: notifications_sink.clone(), + notifications_sink, }); }, CustomMessageOutcome::NotificationStreamReplaced { diff --git a/client/network/src/bitswap.rs b/client/network/src/bitswap.rs index c46990997ca05..d5039faaca113 100644 --- a/client/network/src/bitswap.rs +++ b/client/network/src/bitswap.rs @@ -65,7 +65,7 @@ const MAX_RESPONSE_QUEUE: usize = 20; // Max number of blocks per wantlist const MAX_WANTED_BLOCKS: usize = 16; -const PROTOCOL_NAME: &'static [u8] = b"/ipfs/bitswap/1.2.0"; +const PROTOCOL_NAME: &[u8] = b"/ipfs/bitswap/1.2.0"; type FutureResult = Pin> + Send>>; @@ -167,10 +167,10 @@ impl Prefix { let version = varint_encode::u64(self.version.into(), &mut buf); res.extend_from_slice(version); let mut buf = varint_encode::u64_buffer(); - let codec = varint_encode::u64(self.codec.into(), &mut buf); + let codec = varint_encode::u64(self.codec, &mut buf); res.extend_from_slice(codec); let mut buf = varint_encode::u64_buffer(); - let mh_type = varint_encode::u64(self.mh_type.into(), &mut buf); + let mh_type = varint_encode::u64(self.mh_type, &mut buf); res.extend_from_slice(mh_type); let mut buf = varint_encode::u64_buffer(); let mh_len = varint_encode::u64(self.mh_len as u64, &mut buf); diff --git a/client/network/src/block_request_handler.rs b/client/network/src/block_request_handler.rs index 2e238c01636d5..7458f421af397 100644 --- a/client/network/src/block_request_handler.rs +++ b/client/network/src/block_request_handler.rs @@ -197,7 +197,7 @@ where peer: *peer, max_blocks, direction, - from: from_block_id.clone(), + from: from_block_id, attributes, support_multiple_justifications, }; diff --git a/client/network/src/discovery.rs b/client/network/src/discovery.rs index a916ffda0794d..ae65d4f23cec7 100644 --- a/client/network/src/discovery.rs +++ b/client/network/src/discovery.rs @@ -287,7 +287,7 @@ impl DiscoveryBehaviour { for b in k.kbuckets() { for e in b.iter() { if !peers.contains(e.node.key.preimage()) { - peers.insert(e.node.key.preimage().clone()); + peers.insert(*e.node.key.preimage()); } } } @@ -307,7 +307,7 @@ impl DiscoveryBehaviour { k.add_address(&peer_id, addr.clone()); } - self.pending_events.push_back(DiscoveryOut::Discovered(peer_id.clone())); + self.pending_events.push_back(DiscoveryOut::Discovered(peer_id)); addrs_list.push(addr); } } @@ -718,7 +718,7 @@ impl NetworkBehaviour for DiscoveryBehaviour { // Poll the stream that fires when we need to start a random Kademlia query. if let Some(next_kad_random_query) = self.next_kad_random_query.as_mut() { - while let Poll::Ready(_) = next_kad_random_query.poll_unpin(cx) { + while next_kad_random_query.poll_unpin(cx).is_ready() { let actually_started = if self.num_connections < self.discovery_only_if_under_num { let random_peer_id = PeerId::random(); debug!( @@ -815,7 +815,7 @@ impl NetworkBehaviour for DiscoveryBehaviour { DiscoveryOut::ValueFound( results, - stats.duration().unwrap_or_else(Default::default), + stats.duration().unwrap_or_default(), ) }, Err(e @ libp2p::kad::GetRecordError::NotFound { .. }) => { @@ -826,7 +826,7 @@ impl NetworkBehaviour for DiscoveryBehaviour { ); DiscoveryOut::ValueNotFound( e.into_key(), - stats.duration().unwrap_or_else(Default::default), + stats.duration().unwrap_or_default(), ) }, Err(e) => { @@ -837,7 +837,7 @@ impl NetworkBehaviour for DiscoveryBehaviour { ); DiscoveryOut::ValueNotFound( e.into_key(), - stats.duration().unwrap_or_else(Default::default), + stats.duration().unwrap_or_default(), ) }, }; @@ -851,7 +851,7 @@ impl NetworkBehaviour for DiscoveryBehaviour { let ev = match res { Ok(ok) => DiscoveryOut::ValuePut( ok.key, - stats.duration().unwrap_or_else(Default::default), + stats.duration().unwrap_or_default(), ), Err(e) => { debug!( @@ -861,7 +861,7 @@ impl NetworkBehaviour for DiscoveryBehaviour { ); DiscoveryOut::ValuePutFailed( e.into_key(), - stats.duration().unwrap_or_else(Default::default), + stats.duration().unwrap_or_default(), ) }, }; diff --git a/client/network/src/light_client_requests.rs b/client/network/src/light_client_requests.rs index c77416003f821..d36158b2a373a 100644 --- a/client/network/src/light_client_requests.rs +++ b/client/network/src/light_client_requests.rs @@ -27,11 +27,7 @@ use std::time::Duration; /// Generate the light client protocol name from chain specific protocol identifier. fn generate_protocol_name(protocol_id: &ProtocolId) -> String { - let mut s = String::new(); - s.push_str("/"); - s.push_str(protocol_id.as_ref()); - s.push_str("/light/2"); - s + format!("/{}/light/2", protocol_id.as_ref()) } /// Generates a [`ProtocolConfig`] for the light client request protocol, refusing incoming diff --git a/client/network/src/protocol.rs b/client/network/src/protocol.rs index 9999d278a249c..7214e60172aaa 100644 --- a/client/network/src/protocol.rs +++ b/client/network/src/protocol.rs @@ -391,13 +391,8 @@ where sc_peerset::Peerset::from_config(sc_peerset::PeersetConfig { sets }) }; - let block_announces_protocol: Cow<'static, str> = Cow::from({ - let mut proto = String::new(); - proto.push_str("/"); - proto.push_str(protocol_id.as_ref()); - proto.push_str("/block-announces/1"); - proto - }); + let block_announces_protocol: Cow<'static, str> = + format!("/{}/block-announces/1", protocol_id.as_ref()).into(); let behaviour = { let best_number = info.best_number; @@ -952,7 +947,7 @@ where }, }; - peer.known_blocks.insert(hash.clone()); + peer.known_blocks.insert(hash); let is_best = match announce.state.unwrap_or(message::BlockState::Best) { message::BlockState::Best => true, @@ -1062,7 +1057,7 @@ where /// Uses `protocol` to queue a new justification request and tries to dispatch all pending /// requests. pub fn request_justification(&mut self, hash: &B::Hash, number: NumberFor) { - self.sync.request_justification(&hash, number) + self.sync.request_justification(hash, number) } /// Clear all pending justification requests. @@ -1479,7 +1474,7 @@ where }, }; - finished_block_requests.push((id.clone(), req, protobuf_response)); + finished_block_requests.push((*id, req, protobuf_response)); }, PeerRequest::State => { let protobuf_response = @@ -1576,7 +1571,7 @@ where } for (id, request) in self.sync.block_requests() { - let event = prepare_block_request(&mut self.peers, id.clone(), request); + let event = prepare_block_request(&mut self.peers, *id, request); self.pending_messages.push_back(event); } if let Some((id, request)) = self.sync.state_request() { @@ -1727,9 +1722,9 @@ where } }, NotificationsOut::CustomProtocolReplaced { peer_id, notifications_sink, set_id } => - if set_id == HARDCODED_PEERSETS_SYNC { - CustomMessageOutcome::None - } else if self.bad_handshake_substreams.contains(&(peer_id, set_id)) { + if set_id == HARDCODED_PEERSETS_SYNC || + self.bad_handshake_substreams.contains(&(peer_id, set_id)) + { CustomMessageOutcome::None } else { CustomMessageOutcome::NotificationStreamReplaced { diff --git a/client/network/src/protocol/message.rs b/client/network/src/protocol/message.rs index 3fb57b1c824a9..f173fff8503a5 100644 --- a/client/network/src/protocol/message.rs +++ b/client/network/src/protocol/message.rs @@ -154,7 +154,7 @@ impl generic::BlockAnnounce { AnnouncementSummary { block_hash: self.header.hash(), number: *self.header.number(), - parent_hash: self.header.parent_hash().clone(), + parent_hash: *self.header.parent_hash(), state: self.state, } } diff --git a/client/network/src/protocol/notifications/behaviour.rs b/client/network/src/protocol/notifications/behaviour.rs index 61f7db78c98b1..1f872ec857e79 100644 --- a/client/network/src/protocol/notifications/behaviour.rs +++ b/client/network/src/protocol/notifications/behaviour.rs @@ -402,7 +402,7 @@ impl Notifications { } /// Returns the list of all the peers we have an open channel to. - pub fn open_peers<'a>(&'a self) -> impl Iterator + 'a { + pub fn open_peers(&self) -> impl Iterator { self.peers.iter().filter(|(_, state)| state.is_open()).map(|((id, _), _)| id) } @@ -551,10 +551,7 @@ impl Notifications { } /// Returns the list of reserved peers. - pub fn reserved_peers<'a>( - &'a self, - set_id: sc_peerset::SetId, - ) -> impl Iterator + 'a { + pub fn reserved_peers(&self, set_id: sc_peerset::SetId) -> impl Iterator { self.peerset.reserved_peers(set_id) } @@ -621,7 +618,7 @@ impl Notifications { ); trace!(target: "sub-libp2p", "Libp2p <= Dial {}", entry.key().0); self.events.push_back(NetworkBehaviourAction::Dial { - opts: entry.key().0.clone().into(), + opts: entry.key().0.into(), handler, }); entry.insert(PeerState::Requested); @@ -634,7 +631,7 @@ impl Notifications { match mem::replace(occ_entry.get_mut(), PeerState::Poisoned) { // Backoff (not expired) => PendingRequest PeerState::Backoff { ref timer, ref timer_deadline } if *timer_deadline > now => { - let peer_id = occ_entry.key().0.clone(); + let peer_id = occ_entry.key().0; trace!( target: "sub-libp2p", "PSM => Connect({}, {:?}): Will start to connect at until {:?}", @@ -656,7 +653,7 @@ impl Notifications { ); trace!(target: "sub-libp2p", "Libp2p <= Dial {:?}", occ_entry.key()); self.events.push_back(NetworkBehaviourAction::Dial { - opts: occ_entry.key().0.clone().into(), + opts: occ_entry.key().0.into(), handler, }); *occ_entry.into_mut() = PeerState::Requested; @@ -666,7 +663,7 @@ impl Notifications { PeerState::Disabled { connections, backoff_until: Some(ref backoff) } if *backoff > now => { - let peer_id = occ_entry.key().0.clone(); + let peer_id = occ_entry.key().0; trace!( target: "sub-libp2p", "PSM => Connect({}, {:?}): But peer is backed-off until {:?}", @@ -781,7 +778,7 @@ impl Notifications { trace!(target: "sub-libp2p", "Handler({:?}, {:?}) <= Open({:?})", occ_entry.key(), *connec_id, set_id); self.events.push_back(NetworkBehaviourAction::NotifyHandler { - peer_id: occ_entry.key().0.clone(), + peer_id: occ_entry.key().0, handler: NotifyHandler::One(*connec_id), event: NotifsHandlerIn::Open { protocol_index: set_id.into() }, }); @@ -861,10 +858,8 @@ impl Notifications { if connections.iter().any(|(_, s)| matches!(s, ConnectionState::Open(_))) { trace!(target: "sub-libp2p", "External API <= Closed({}, {:?})", entry.key().0, set_id); - let event = NotificationsOut::CustomProtocolClosed { - peer_id: entry.key().0.clone(), - set_id, - }; + let event = + NotificationsOut::CustomProtocolClosed { peer_id: entry.key().0, set_id }; self.events.push_back(NetworkBehaviourAction::GenerateEvent(event)); } @@ -874,7 +869,7 @@ impl Notifications { trace!(target: "sub-libp2p", "Handler({:?}, {:?}) <= Close({:?})", entry.key(), *connec_id, set_id); self.events.push_back(NetworkBehaviourAction::NotifyHandler { - peer_id: entry.key().0.clone(), + peer_id: entry.key().0, handler: NotifyHandler::One(*connec_id), event: NotifsHandlerIn::Close { protocol_index: set_id.into() }, }); @@ -887,7 +882,7 @@ impl Notifications { trace!(target: "sub-libp2p", "Handler({:?}, {:?}) <= Close({:?})", entry.key(), *connec_id, set_id); self.events.push_back(NetworkBehaviourAction::NotifyHandler { - peer_id: entry.key().0.clone(), + peer_id: entry.key().0, handler: NotifyHandler::One(*connec_id), event: NotifsHandlerIn::Close { protocol_index: set_id.into() }, }); @@ -1406,7 +1401,7 @@ impl NetworkBehaviour for Notifications { trace!(target: "sub-libp2p", "Libp2p => Dial failure for {:?}", peer_id); for set_id in (0..self.notif_protocols.len()).map(sc_peerset::SetId::from) { - if let Entry::Occupied(mut entry) = self.peers.entry((peer_id.clone(), set_id)) { + if let Entry::Occupied(mut entry) = self.peers.entry((peer_id, set_id)) { match mem::replace(entry.get_mut(), PeerState::Poisoned) { // The peer is not in our list. st @ PeerState::Backoff { .. } => { @@ -1646,7 +1641,6 @@ impl NetworkBehaviour for Notifications { "OpenDesiredByRemote: Unexpected state in the custom protos handler: {:?}", state); debug_assert!(false); - return }, }; }, @@ -1742,13 +1736,11 @@ impl NetworkBehaviour for Notifications { state @ PeerState::Disabled { .. } | state @ PeerState::DisabledPendingEnable { .. } => { *entry.into_mut() = state; - return }, state => { error!(target: "sub-libp2p", "Unexpected state in the custom protos handler: {:?}", state); - return }, } }, @@ -1853,7 +1845,6 @@ impl NetworkBehaviour for Notifications { "OpenResultOk: Unexpected state in the custom protos handler: {:?}", state); debug_assert!(false); - return }, } }, @@ -2052,9 +2043,7 @@ impl NetworkBehaviour for Notifications { event: NotifsHandlerIn::Open { protocol_index: set_id.into() }, }); *connec_state = ConnectionState::Opening; - *peer_state = PeerState::Enabled { - connections: mem::replace(connections, Default::default()), - }; + *peer_state = PeerState::Enabled { connections: mem::take(connections) }; } else { *timer_deadline = Instant::now() + Duration::from_secs(5); let delay = futures_timer::Delay::new(Duration::from_secs(5)); diff --git a/client/network/src/protocol/notifications/handler.rs b/client/network/src/protocol/notifications/handler.rs index 510f72d4b0123..c1602319d0ac4 100644 --- a/client/network/src/protocol/notifications/handler.rs +++ b/client/network/src/protocol/notifications/handler.rs @@ -400,7 +400,7 @@ impl NotificationsSink { /// error to send a notification using an unknown protocol. /// /// This method will be removed in a future version. - pub fn send_sync_notification<'a>(&'a self, message: impl Into>) { + pub fn send_sync_notification(&self, message: impl Into>) { let mut lock = self.inner.sync_channel.lock(); if let Some(tx) = lock.as_mut() { @@ -425,7 +425,7 @@ impl NotificationsSink { /// /// The protocol name is expected to be checked ahead of calling this method. It is a logic /// error to send a notification using an unknown protocol. - pub async fn reserve_notification<'a>(&'a self) -> Result, ()> { + pub async fn reserve_notification(&self) -> Result, ()> { let mut lock = self.inner.async_channel.lock().await; let poll_ready = future::poll_fn(|cx| lock.poll_ready(cx)).await; diff --git a/client/network/src/protocol/sync.rs b/client/network/src/protocol/sync.rs index 7e6a2a3c78508..fb89e12a44fe8 100644 --- a/client/network/src/protocol/sync.rs +++ b/client/network/src/protocol/sync.rs @@ -672,7 +672,7 @@ where self.best_queued_number ); self.peers.insert( - who.clone(), + who, PeerSync { peer_id: who, common_number: self.best_queued_number, @@ -796,7 +796,7 @@ where .iter() // Only request blocks from peers who are ahead or on a par. .filter(|(_, peer)| peer.best_number >= number) - .map(|(id, _)| id.clone()) + .map(|(id, _)| *id) .collect(); debug!( @@ -809,7 +809,7 @@ where debug!(target: "sync", "Explicit sync request for block {:?} with {:?}", hash, peers); } - if self.is_known(&hash) { + if self.is_known(hash) { debug!(target: "sync", "Refusing to sync known hash {:?}", hash); return } @@ -843,7 +843,7 @@ where let peers = &mut self.peers; let mut matcher = self.extra_justifications.matcher(); std::iter::from_fn(move || { - if let Some((peer, request)) = matcher.next(&peers) { + if let Some((peer, request)) = matcher.next(peers) { peers .get_mut(&peer) .expect( @@ -1087,7 +1087,7 @@ where if let Some(start_block) = validate_blocks::(&blocks, who, Some(request))? { - self.blocks.insert(start_block, blocks, who.clone()); + self.blocks.insert(start_block, blocks, *who); } self.drain_blocks() }, @@ -1098,7 +1098,7 @@ where if let Some(start_block) = validate_blocks::(&blocks, who, Some(request))? { - gap_sync.blocks.insert(start_block, blocks, who.clone()); + gap_sync.blocks.insert(start_block, blocks, *who); } gap = true; let blocks: Vec<_> = gap_sync @@ -1106,11 +1106,12 @@ where .drain(gap_sync.best_queued_number + One::one()) .into_iter() .map(|block_data| { - let justifications = block_data.block.justifications.or( - legacy_justification_mapping( - block_data.block.justification, - ), - ); + let justifications = + block_data.block.justifications.or_else(|| { + legacy_justification_mapping( + block_data.block.justification, + ) + }); IncomingBlock { hash: block_data.block.hash, header: block_data.block.header, @@ -1129,7 +1130,7 @@ where blocks } else { debug!(target: "sync", "Unexpected gap block response from {}", who); - return Err(BadPeer(who.clone(), rep::NO_BLOCK)) + return Err(BadPeer(*who, rep::NO_BLOCK)) } }, PeerSyncState::DownloadingStale(_) => { @@ -1144,7 +1145,7 @@ where .map(|b| { let justifications = b .justifications - .or(legacy_justification_mapping(b.justification)); + .or_else(|| legacy_justification_mapping(b.justification)); IncomingBlock { hash: b.hash, header: b.header, @@ -1261,8 +1262,9 @@ where blocks .into_iter() .map(|b| { - let justifications = - b.justifications.or(legacy_justification_mapping(b.justification)); + let justifications = b + .justifications + .or_else(|| legacy_justification_mapping(b.justification)); IncomingBlock { hash: b.hash, header: b.header, @@ -1294,7 +1296,7 @@ where who: &PeerId, response: StateResponse, ) -> Result, BadPeer> { - if let Some(peer) = self.peers.get_mut(&who) { + if let Some(peer) = self.peers.get_mut(who) { if let PeerSyncState::DownloadingState = peer.state { peer.state = PeerSyncState::Available; self.allowed_requests.set_all(); @@ -1357,7 +1359,7 @@ where who: &PeerId, response: warp::EncodedProof, ) -> Result<(), BadPeer> { - if let Some(peer) = self.peers.get_mut(&who) { + if let Some(peer) = self.peers.get_mut(who) { if let PeerSyncState::DownloadingWarpProof = peer.state { peer.state = PeerSyncState::Available; self.allowed_requests.set_all(); @@ -1458,7 +1460,9 @@ where return Err(BadPeer(who, rep::BAD_JUSTIFICATION)) } - block.justifications.or(legacy_justification_mapping(block.justification)) + block + .justifications + .or_else(|| legacy_justification_mapping(block.justification)) } else { // we might have asked the peer for a justification on a block that we assumed it // had but didn't (regardless of whether it had a justification for it or not). @@ -1488,19 +1492,19 @@ where /// queue, with or without errors. /// /// `peer_info` is passed in case of a restart. - pub fn on_blocks_processed<'a>( - &'a mut self, + pub fn on_blocks_processed( + &mut self, imported: usize, count: usize, results: Vec<(Result>, BlockImportError>, B::Hash)>, - ) -> impl Iterator), BadPeer>> + 'a { + ) -> impl Iterator), BadPeer>> { trace!(target: "sync", "Imported {} of {}", imported, count); let mut output = Vec::new(); let mut has_error = false; for (_, hash) in &results { - self.queue_blocks.remove(&hash); + self.queue_blocks.remove(hash); } for (result, hash) in results { if has_error { @@ -1659,7 +1663,7 @@ where heads.sort(); let median = heads[heads.len() / 2]; if number + STATE_SYNC_FINALITY_THRESHOLD.saturated_into() >= median { - if let Ok(Some(header)) = self.client.header(BlockId::hash(hash.clone())) { + if let Ok(Some(header)) = self.client.header(BlockId::hash(*hash)) { log::debug!( target: "sync", "Starting state sync for #{} ({})", @@ -1688,7 +1692,7 @@ where /// Updates our internal state for best queued block and then goes /// through all peers to update our view of their state as well. fn on_block_queued(&mut self, hash: &B::Hash, number: NumberFor) { - if self.fork_targets.remove(&hash).is_some() { + if self.fork_targets.remove(hash).is_some() { trace!(target: "sync", "Completed fork sync {:?}", hash); } if let Some(gap_sync) = &mut self.gap_sync { @@ -1741,7 +1745,7 @@ where return HasSlotForBlockAnnounceValidation::TotalMaximumSlotsReached } - match self.block_announce_validation_per_peer_stats.entry(peer.clone()) { + match self.block_announce_validation_per_peer_stats.entry(*peer) { Entry::Vacant(entry) => { entry.insert(1); HasSlotForBlockAnnounceValidation::Yes @@ -1830,8 +1834,7 @@ where // Let external validator check the block announcement. let assoc_data = announce.data.as_ref().map_or(&[][..], |v| v.as_slice()); - let future = self.block_announce_validator.validate(&header, assoc_data); - let hash = hash.clone(); + let future = self.block_announce_validator.validate(header, assoc_data); self.block_announce_validation.push( async move { @@ -1900,7 +1903,7 @@ where PreValidateBlockAnnounce::Skip => return, }; - match self.block_announce_validation_per_peer_stats.entry(peer.clone()) { + match self.block_announce_validation_per_peer_stats.entry(*peer) { Entry::Vacant(_) => { error!( target: "sync", @@ -1994,7 +1997,7 @@ where if known || self.is_already_downloading(&hash) { trace!(target: "sync", "Known block announce from {}: {}", who, hash); if let Some(target) = self.fork_targets.get_mut(&hash) { - target.peers.insert(who.clone()); + target.peers.insert(who); } return PollBlockAnnounceValidation::Nothing { is_best, who, announce } } @@ -2070,9 +2073,7 @@ where /// Restart the sync process. This will reset all pending block requests and return an iterator /// of new block requests to make to peers. Peers that were downloading finality data (i.e. /// their state was `DownloadingJustification`) are unaffected and will stay in the same state. - fn restart<'a>( - &'a mut self, - ) -> impl Iterator), BadPeer>> + 'a { + fn restart(&mut self) -> impl Iterator), BadPeer>> + '_ { self.blocks.clear(); if let Err(e) = self.reset_sync_start_point() { warn!(target: "sync", "💔 Unable to restart sync: {}", e); @@ -2084,18 +2085,15 @@ where old_peers.into_iter().filter_map(move |(id, mut p)| { // peers that were downloading justifications // should be kept in that state. - match p.state { - PeerSyncState::DownloadingJustification(_) => { - // We make sure our commmon number is at least something we have. - p.common_number = self.best_queued_number; - self.peers.insert(id, p); - return None - }, - _ => {}, + if let PeerSyncState::DownloadingJustification(_) = p.state { + // We make sure our commmon number is at least something we have. + p.common_number = self.best_queued_number; + self.peers.insert(id, p); + return None } // handle peers that were in other states. - match self.new_peer(id.clone(), p.best_hash, p.best_number) { + match self.new_peer(id, p.best_hash, p.best_number) { Ok(None) => None, Ok(Some(x)) => Some(Ok((id, x))), Err(e) => Some(Err(e)), @@ -2124,23 +2122,24 @@ where self.import_existing = false; self.best_queued_hash = info.best_hash; self.best_queued_number = info.best_number; - if self.mode == SyncMode::Full { - if self.client.block_status(&BlockId::hash(info.best_hash))? != + + if self.mode == SyncMode::Full && + self.client.block_status(&BlockId::hash(info.best_hash))? != BlockStatus::InChainWithState - { - self.import_existing = true; - // Latest state is missing, start with the last finalized state or genesis instead. - if let Some((hash, number)) = info.finalized_state { - debug!(target: "sync", "Starting from finalized state #{}", number); - self.best_queued_hash = hash; - self.best_queued_number = number; - } else { - debug!(target: "sync", "Restarting from genesis"); - self.best_queued_hash = Default::default(); - self.best_queued_number = Zero::zero(); - } + { + self.import_existing = true; + // Latest state is missing, start with the last finalized state or genesis instead. + if let Some((hash, number)) = info.finalized_state { + debug!(target: "sync", "Starting from finalized state #{}", number); + self.best_queued_hash = hash; + self.best_queued_number = number; + } else { + debug!(target: "sync", "Restarting from genesis"); + self.best_queued_hash = Default::default(); + self.best_queued_number = Zero::zero(); } } + if let Some((start, end)) = info.block_gap { debug!(target: "sync", "Starting gap sync #{} - #{}", start, end); self.gap_sync = Some(GapSync { @@ -2192,7 +2191,7 @@ where let justifications = block_data .block .justifications - .or(legacy_justification_mapping(block_data.block.justification)); + .or_else(|| legacy_justification_mapping(block_data.block.justification)); IncomingBlock { hash: block_data.block.hash, header: block_data.block.header, @@ -2349,7 +2348,7 @@ fn peer_block_request( let request = message::generic::BlockRequest { id: 0, - fields: attrs.clone(), + fields: attrs, from, to: None, direction: message::Direction::Descending, @@ -2369,7 +2368,7 @@ fn peer_gap_block_request( common_number: NumberFor, ) -> Option<(Range>, BlockRequest)> { let range = blocks.needed_blocks( - id.clone(), + *id, MAX_BLOCKS_TO_REQUEST, std::cmp::min(peer.best_number, target), common_number, @@ -2383,7 +2382,7 @@ fn peer_gap_block_request( let request = message::generic::BlockRequest { id: 0, - fields: attrs.clone(), + fields: attrs, from, to: None, direction: message::Direction::Descending, @@ -2430,11 +2429,11 @@ fn fork_sync_request( }; trace!(target: "sync", "Downloading requested fork {:?} from {}, {} blocks", hash, id, count); return Some(( - hash.clone(), + *hash, message::generic::BlockRequest { id: 0, - fields: attributes.clone(), - from: message::FromBlock::Hash(hash.clone()), + fields: attributes, + from: message::FromBlock::Hash(*hash), to: None, direction: message::Direction::Descending, max: Some(count), diff --git a/client/network/src/protocol/sync/extra_requests.rs b/client/network/src/protocol/sync/extra_requests.rs index d0bfebab66010..43122631d3c22 100644 --- a/client/network/src/protocol/sync/extra_requests.rs +++ b/client/network/src/protocol/sync/extra_requests.rs @@ -327,7 +327,7 @@ impl<'a, B: BlockT> Matcher<'a, B> { { continue } - self.extras.active_requests.insert(peer.clone(), request); + self.extras.active_requests.insert(*peer, request); trace!(target: "sync", "Sending {} request to {:?} for {:?}", diff --git a/client/network/src/protocol/sync/state.rs b/client/network/src/protocol/sync/state.rs index 4eddc4c60867e..6208b2bcdd18a 100644 --- a/client/network/src/protocol/sync/state.rs +++ b/client/network/src/protocol/sync/state.rs @@ -71,7 +71,7 @@ where Self { client, target_block: target.hash(), - target_root: target.state_root().clone(), + target_root: *target.state_root(), target_header: target, last_key: SmallVec::default(), state: HashMap::default(), @@ -149,18 +149,16 @@ where if entry.0.len() > 0 && entry.1.len() > 1 { // Already imported child_trie with same root. // Warning this will not work with parallel download. - } else { - if entry.0.is_empty() { - for (key, _value) in key_values.iter() { - self.imported_bytes += key.len() as u64; - } + } else if entry.0.is_empty() { + for (key, _value) in key_values.iter() { + self.imported_bytes += key.len() as u64; + } - entry.0 = key_values; - } else { - for (key, value) in key_values { - self.imported_bytes += key.len() as u64; - entry.0.push((key, value)) - } + entry.0 = key_values; + } else { + for (key, value) in key_values { + self.imported_bytes += key.len() as u64; + entry.0.push((key, value)) } } } @@ -172,7 +170,7 @@ where // the parent cursor stays valid. // Empty parent trie content only happens when all the response content // is part of a single child trie. - if self.last_key.len() == 2 && response.entries[0].entries.len() == 0 { + if self.last_key.len() == 2 && response.entries[0].entries.is_empty() { // Do not remove the parent trie position. self.last_key.pop(); } else { @@ -220,7 +218,7 @@ where self.target_block, self.target_header.clone(), ImportedState { - block: self.target_block.clone(), + block: self.target_block, state: std::mem::take(&mut self.state).into(), }, ) diff --git a/client/network/src/protocol/sync/warp.rs b/client/network/src/protocol/sync/warp.rs index fa2c23a0b33c1..6845d6d1dc862 100644 --- a/client/network/src/protocol/sync/warp.rs +++ b/client/network/src/protocol/sync/warp.rs @@ -94,7 +94,7 @@ where match &mut self.phase { Phase::WarpProof { .. } => { log::debug!(target: "sync", "Unexpected state response"); - return ImportResult::BadResponse + ImportResult::BadResponse }, Phase::State(sync) => sync.import(response), } @@ -111,13 +111,13 @@ where match self.warp_sync_provider.verify(&response, *set_id, authorities.clone()) { Err(e) => { log::debug!(target: "sync", "Bad warp proof response: {}", e); - return WarpProofImportResult::BadResponse + WarpProofImportResult::BadResponse }, Ok(VerificationResult::Partial(new_set_id, new_authorities, new_last_hash)) => { log::debug!(target: "sync", "Verified partial proof, set_id={:?}", new_set_id); *set_id = new_set_id; *authorities = new_authorities; - *last_hash = new_last_hash.clone(); + *last_hash = new_last_hash; self.total_proof_bytes += response.0.len() as u64; WarpProofImportResult::Success }, diff --git a/client/network/src/request_responses.rs b/client/network/src/request_responses.rs index 87d070bc469a3..04d6ccb54339f 100644 --- a/client/network/src/request_responses.rs +++ b/client/network/src/request_responses.rs @@ -355,25 +355,21 @@ impl RequestResponsesBehaviour { (Instant::now(), pending_response), ); debug_assert!(prev_req_id.is_none(), "Expect request id to be unique."); - } else { - if pending_response.send(Err(RequestFailure::NotConnected)).is_err() { - log::debug!( - target: "sub-libp2p", - "Not connected to peer {:?}. At the same time local \ - node is no longer interested in the result.", - target, - ); - }; - } - } else { - if pending_response.send(Err(RequestFailure::UnknownProtocol)).is_err() { + } else if pending_response.send(Err(RequestFailure::NotConnected)).is_err() { log::debug!( target: "sub-libp2p", - "Unknown protocol {:?}. At the same time local \ + "Not connected to peer {:?}. At the same time local \ node is no longer interested in the result.", - protocol_name, + target, ); - }; + } + } else if pending_response.send(Err(RequestFailure::UnknownProtocol)).is_err() { + log::debug!( + target: "sub-libp2p", + "Unknown protocol {:?}. At the same time local \ + node is no longer interested in the result.", + protocol_name, + ); } } @@ -599,7 +595,7 @@ impl NetworkBehaviour for RequestResponsesBehaviour { // will be reported by the corresponding `RequestResponse` through // an `InboundFailure::Omission` event. let _ = resp_builder.try_send(IncomingRequest { - peer: peer.clone(), + peer, payload: request, pending_response: tx, }); @@ -648,7 +644,7 @@ impl NetworkBehaviour for RequestResponsesBehaviour { if let Ok(payload) = result { if let Some((protocol, _)) = self.protocols.get_mut(&*protocol_name) { - if let Err(_) = protocol.send_response(inner_channel, Ok(payload)) { + if protocol.send_response(inner_channel, Ok(payload)).is_err() { // Note: Failure is handled further below when receiving // `InboundFailure` event from `RequestResponse` behaviour. log::debug!( @@ -658,11 +654,9 @@ impl NetworkBehaviour for RequestResponsesBehaviour { Dropping response", request_id, protocol_name, ); - } else { - if let Some(sent_feedback) = sent_feedback { - self.send_feedback - .insert((protocol_name, request_id).into(), sent_feedback); - } + } else if let Some(sent_feedback) = sent_feedback { + self.send_feedback + .insert((protocol_name, request_id).into(), sent_feedback); } } } @@ -718,13 +712,10 @@ impl NetworkBehaviour for RequestResponsesBehaviour { message: RequestResponseMessage::Request { request_id, request, channel, .. }, } => { - self.pending_responses_arrival_time.insert( - (protocol.clone(), request_id.clone()).into(), - Instant::now(), - ); + self.pending_responses_arrival_time + .insert((protocol.clone(), request_id).into(), Instant::now()); - let get_peer_reputation = - self.peerset.clone().peer_reputation(peer.clone()); + let get_peer_reputation = self.peerset.clone().peer_reputation(peer); let get_peer_reputation = Box::pin(get_peer_reputation); // Save the Future-like state with params to poll `get_peer_reputation` diff --git a/client/network/src/service.rs b/client/network/src/service.rs index 6ffc0ec49bd67..91517e14915bc 100644 --- a/client/network/src/service.rs +++ b/client/network/src/service.rs @@ -195,7 +195,7 @@ where // Private and public keys configuration. let local_identity = params.network_config.node_key.clone().into_keypair()?; let local_public = local_identity.public(); - let local_peer_id = local_public.clone().to_peer_id(); + let local_peer_id = local_public.to_peer_id(); info!( target: "sub-libp2p", "🏷 Local node identity is: {}", @@ -248,7 +248,7 @@ where Err(Error::DuplicateBootnode { address: addr.clone(), first_id: *peer_id, - second_id: other.0.clone(), + second_id: other.0, }) } else { Ok(()) @@ -644,7 +644,7 @@ where .collect() }; - let peer_id = Swarm::>::local_peer_id(&swarm).to_base58(); + let peer_id = Swarm::>::local_peer_id(swarm).to_base58(); let listened_addresses = swarm.listeners().cloned().collect(); let external_addresses = swarm.external_addresses().map(|r| &r.addr).cloned().collect(); @@ -664,7 +664,7 @@ where .behaviour_mut() .user_protocol_mut() .peers_info() - .map(|(id, info)| (id.clone(), info.clone())) + .map(|(id, info)| (*id, info.clone())) .collect() } @@ -753,7 +753,7 @@ impl NetworkService { // `peers_notifications_sinks` mutex as soon as possible. let sink = { let peers_notifications_sinks = self.peers_notifications_sinks.lock(); - if let Some(sink) = peers_notifications_sinks.get(&(target.clone(), protocol.clone())) { + if let Some(sink) = peers_notifications_sinks.get(&(target, protocol.clone())) { sink.clone() } else { // Notification silently discarded, as documented. @@ -1093,7 +1093,7 @@ impl NetworkService { let _ = self .to_worker - .unbounded_send(ServiceToWorkerMsg::AddKnownAddress(peer_id.clone(), addr)); + .unbounded_send(ServiceToWorkerMsg::AddKnownAddress(peer_id, addr)); let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::AddReserved(peer_id)); Ok(()) } @@ -1173,7 +1173,7 @@ impl NetworkService { if !addr.is_empty() { let _ = self .to_worker - .unbounded_send(ServiceToWorkerMsg::AddKnownAddress(peer_id.clone(), addr)); + .unbounded_send(ServiceToWorkerMsg::AddKnownAddress(peer_id, addr)); } let _ = self .to_worker @@ -1227,7 +1227,7 @@ impl NetworkService { if !addr.is_empty() { let _ = self .to_worker - .unbounded_send(ServiceToWorkerMsg::AddKnownAddress(peer_id.clone(), addr)); + .unbounded_send(ServiceToWorkerMsg::AddKnownAddress(peer_id, addr)); } let _ = self .to_worker @@ -1351,9 +1351,7 @@ pub struct NotificationSender { impl NotificationSender { /// Returns a future that resolves when the `NotificationSender` is ready to send a /// notification. - pub async fn ready<'a>( - &'a self, - ) -> Result, NotificationSenderError> { + pub async fn ready(&self) -> Result, NotificationSenderError> { Ok(NotificationSenderReady { ready: match self.sink.reserve_notification().await { Ok(r) => r, @@ -1771,7 +1769,7 @@ where if let Some(metrics) = this.metrics.as_ref() { metrics .kademlia_random_queries_total - .with_label_values(&[&protocol.as_ref()]) + .with_label_values(&[protocol.as_ref()]) .inc(); }, Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::NotificationStreamOpened { @@ -1790,7 +1788,7 @@ where { let mut peers_notifications_sinks = this.peers_notifications_sinks.lock(); let _previous_value = peers_notifications_sinks - .insert((remote.clone(), protocol.clone()), notifications_sink); + .insert((remote, protocol.clone()), notifications_sink); debug_assert!(_previous_value.is_none()); } this.event_streams.send(Event::NotificationStreamOpened { @@ -1848,13 +1846,12 @@ where .inc(); } this.event_streams.send(Event::NotificationStreamClosed { - remote: remote.clone(), + remote, protocol: protocol.clone(), }); { let mut peers_notifications_sinks = this.peers_notifications_sinks.lock(); - let _previous_value = - peers_notifications_sinks.remove(&(remote.clone(), protocol)); + let _previous_value = peers_notifications_sinks.remove(&(remote, protocol)); debug_assert!(_previous_value.is_some()); } }, @@ -2117,10 +2114,7 @@ where for (lower_ilog2_bucket_bound, num_entries) in buckets { metrics .kbuckets_num_nodes - .with_label_values(&[ - &proto.as_ref(), - &lower_ilog2_bucket_bound.to_string(), - ]) + .with_label_values(&[proto.as_ref(), &lower_ilog2_bucket_bound.to_string()]) .set(num_entries as u64); } } @@ -2128,7 +2122,7 @@ where { metrics .kademlia_records_count - .with_label_values(&[&proto.as_ref()]) + .with_label_values(&[proto.as_ref()]) .set(num_entries as u64); } for (proto, num_entries) in @@ -2136,7 +2130,7 @@ where { metrics .kademlia_records_sizes_total - .with_label_values(&[&proto.as_ref()]) + .with_label_values(&[proto.as_ref()]) .set(num_entries as u64); } metrics @@ -2211,12 +2205,10 @@ where number: NumberFor, success: bool, ) { - self.protocol.behaviour_mut().user_protocol_mut().justification_import_result( - who, - hash.clone(), - number, - success, - ); + self.protocol + .behaviour_mut() + .user_protocol_mut() + .justification_import_result(who, *hash, number, success); } fn request_justification(&mut self, hash: &B::Hash, number: NumberFor) { self.protocol diff --git a/client/network/src/service/metrics.rs b/client/network/src/service/metrics.rs index ad30b1b093ff9..4b63df00b8d66 100644 --- a/client/network/src/service/metrics.rs +++ b/client/network/src/service/metrics.rs @@ -280,8 +280,8 @@ impl MetricSource for BandwidthCounters { type N = u64; fn collect(&self, mut set: impl FnMut(&[&str], Self::N)) { - set(&[&"in"], self.0.total_inbound()); - set(&[&"out"], self.0.total_outbound()); + set(&["in"], self.0.total_inbound()); + set(&["out"], self.0.total_outbound()); } } diff --git a/client/network/src/service/out_events.rs b/client/network/src/service/out_events.rs index 3bff5a16fd0c2..c95b46af4cefa 100644 --- a/client/network/src/service/out_events.rs +++ b/client/network/src/service/out_events.rs @@ -197,9 +197,9 @@ fn format_label(prefix: &str, protocol: &str, callback: impl FnOnce(&str)) { label_buffer.clear(); label_buffer.reserve(prefix.len() + protocol.len() + 2); label_buffer.push_str(prefix); - label_buffer.push_str("\""); + label_buffer.push('"'); label_buffer.push_str(protocol); - label_buffer.push_str("\""); + label_buffer.push('"'); callback(&label_buffer); }); } @@ -249,14 +249,14 @@ impl Metrics { .inc_by(num); }, Event::NotificationStreamOpened { protocol, .. } => { - format_label("notif-open-", &protocol, |protocol_label| { + format_label("notif-open-", protocol, |protocol_label| { self.events_total .with_label_values(&[protocol_label, "sent", name]) .inc_by(num); }); }, Event::NotificationStreamClosed { protocol, .. } => { - format_label("notif-closed-", &protocol, |protocol_label| { + format_label("notif-closed-", protocol, |protocol_label| { self.events_total .with_label_values(&[protocol_label, "sent", name]) .inc_by(num); @@ -264,7 +264,7 @@ impl Metrics { }, Event::NotificationsReceived { messages, .. } => for (protocol, message) in messages { - format_label("notif-", &protocol, |protocol_label| { + format_label("notif-", protocol, |protocol_label| { self.events_total .with_label_values(&[protocol_label, "sent", name]) .inc_by(num); @@ -290,24 +290,24 @@ impl Metrics { .inc(); }, Event::NotificationStreamOpened { protocol, .. } => { - format_label("notif-open-", &protocol, |protocol_label| { + format_label("notif-open-", protocol, |protocol_label| { self.events_total.with_label_values(&[protocol_label, "received", name]).inc(); }); }, Event::NotificationStreamClosed { protocol, .. } => { - format_label("notif-closed-", &protocol, |protocol_label| { + format_label("notif-closed-", protocol, |protocol_label| { self.events_total.with_label_values(&[protocol_label, "received", name]).inc(); }); }, Event::NotificationsReceived { messages, .. } => for (protocol, message) in messages { - format_label("notif-", &protocol, |protocol_label| { + format_label("notif-", protocol, |protocol_label| { self.events_total .with_label_values(&[protocol_label, "received", name]) .inc(); }); self.notifications_sizes - .with_label_values(&[&protocol, "received", name]) + .with_label_values(&[protocol, "received", name]) .inc_by(u64::try_from(message.len()).unwrap_or(u64::MAX)); }, } diff --git a/client/network/src/state_request_handler.rs b/client/network/src/state_request_handler.rs index 3e208e22e3d93..7ac1a17e8e759 100644 --- a/client/network/src/state_request_handler.rs +++ b/client/network/src/state_request_handler.rs @@ -63,11 +63,7 @@ pub fn generate_protocol_config(protocol_id: &ProtocolId) -> ProtocolConfig { /// Generate the state protocol name from chain specific protocol identifier. fn generate_protocol_name(protocol_id: &ProtocolId) -> String { - let mut s = String::new(); - s.push_str("/"); - s.push_str(protocol_id.as_ref()); - s.push_str("/state/2"); - s + format!("/{}/state/2", protocol_id.as_ref()) } /// The key of [`BlockRequestHandler::seen_requests`]. @@ -152,8 +148,7 @@ where let request = StateRequest::decode(&payload[..])?; let block: B::Hash = Decode::decode(&mut request.block.as_ref())?; - let key = - SeenRequestsKey { peer: *peer, block: block.clone(), start: request.start.clone() }; + let key = SeenRequestsKey { peer: *peer, block, start: request.start.clone() }; let mut reputation_changes = Vec::new(); diff --git a/client/network/src/transactions.rs b/client/network/src/transactions.rs index c09c6b88dab8b..64e208d718889 100644 --- a/client/network/src/transactions.rs +++ b/client/network/src/transactions.rs @@ -133,15 +133,7 @@ pub struct TransactionsHandlerPrototype { impl TransactionsHandlerPrototype { /// Create a new instance. pub fn new(protocol_id: ProtocolId) -> Self { - Self { - protocol_name: Cow::from({ - let mut proto = String::new(); - proto.push_str("/"); - proto.push_str(protocol_id.as_ref()); - proto.push_str("/transactions/1"); - proto - }), - } + Self { protocol_name: format!("/{}/transactions/1", protocol_id.as_ref()).into() } } /// Returns the configuration of the set to put in the network configuration. diff --git a/client/network/src/transport.rs b/client/network/src/transport.rs index 9a8e080234f32..64b199e9d769e 100644 --- a/client/network/src/transport.rs +++ b/client/network/src/transport.rs @@ -82,11 +82,11 @@ pub fn build_transport( rare panic here is basically zero"); // Legacy noise configurations for backward compatibility. - let mut noise_legacy = noise::LegacyConfig::default(); - noise_legacy.recv_legacy_handshake = true; + let noise_legacy = + noise::LegacyConfig { recv_legacy_handshake: true, ..Default::default() }; let mut xx_config = noise::NoiseConfig::xx(noise_keypair); - xx_config.set_legacy_config(noise_legacy.clone()); + xx_config.set_legacy_config(noise_legacy); xx_config.into_authenticated() }; diff --git a/client/network/src/warp_request_handler.rs b/client/network/src/warp_request_handler.rs index 4c839825ff5ec..d5bee5833a12a 100644 --- a/client/network/src/warp_request_handler.rs +++ b/client/network/src/warp_request_handler.rs @@ -82,11 +82,7 @@ pub fn generate_request_response_config(protocol_id: ProtocolId) -> RequestRespo /// Generate the grandpa warp sync protocol name from chain specific protocol identifier. fn generate_protocol_name(protocol_id: ProtocolId) -> String { - let mut s = String::new(); - s.push_str("/"); - s.push_str(protocol_id.as_ref()); - s.push_str("/sync/warp"); - s + format!("/{}/sync/warp", protocol_id.as_ref()) } /// Handler for incoming grandpa warp sync requests from a remote peer. diff --git a/client/network/test/src/lib.rs b/client/network/test/src/lib.rs index 1760c08759761..20f1a3014867b 100644 --- a/client/network/test/src/lib.rs +++ b/client/network/test/src/lib.rs @@ -236,7 +236,7 @@ where { /// Get this peer ID. pub fn id(&self) -> PeerId { - self.network.service().local_peer_id().clone() + *self.network.service().local_peer_id() } /// Returns true if we're major syncing. @@ -387,7 +387,7 @@ where if inform_sync_about_new_best_block { self.network.new_best_block_imported( at, - full_client.header(&BlockId::Hash(at)).ok().flatten().unwrap().number().clone(), + *full_client.header(&BlockId::Hash(at)).ok().flatten().unwrap().number(), ); } at @@ -458,7 +458,7 @@ where nonce, }; builder.push(transfer.into_signed_tx()).unwrap(); - nonce = nonce + 1; + nonce += 1; builder.build().unwrap().block }, headers_only, @@ -494,7 +494,7 @@ where /// Get a reference to the network service. pub fn network_service(&self) -> &Arc::Hash>> { - &self.network.service() + self.network.service() } /// Get a reference to the network worker. @@ -801,7 +801,7 @@ where let addrs = connect_to .iter() .map(|v| { - let peer_id = self.peer(*v).network_service().local_peer_id().clone(); + let peer_id = *self.peer(*v).network_service().local_peer_id(); let multiaddr = self.peer(*v).listen_addr.clone(); MultiaddrWithPeerId { peer_id, multiaddr } }) @@ -868,10 +868,8 @@ where self.mut_peers(move |peers| { for peer in peers.iter_mut() { - peer.network.add_known_address( - network.service().local_peer_id().clone(), - listen_addr.clone(), - ); + peer.network + .add_known_address(*network.service().local_peer_id(), listen_addr.clone()); } let imported_blocks_stream = Box::pin(client.import_notification_stream().fuse()); @@ -986,7 +984,7 @@ where /// Polls the testnet. Processes all the pending actions. fn poll(&mut self, cx: &mut FutureContext) { self.mut_peers(|peers| { - for (i, peer) in peers.into_iter().enumerate() { + for (i, peer) in peers.iter_mut().enumerate() { trace!(target: "sync", "-- Polling {}: {}", i, peer.id()); if let Poll::Ready(()) = peer.network.poll_unpin(cx) { panic!("NetworkWorker has terminated unexpectedly.") @@ -1076,7 +1074,7 @@ impl JustificationImport for ForceFinalized { ) -> Result<(), Self::Error> { self.0 .finalize_block(BlockId::Hash(hash), Some(justification), true) - .map_err(|_| ConsensusError::InvalidJustification.into()) + .map_err(|_| ConsensusError::InvalidJustification) } } diff --git a/client/offchain/src/api/http.rs b/client/offchain/src/api/http.rs index 012de78c5f645..f4fa7e0800b2d 100644 --- a/client/offchain/src/api/http.rs +++ b/client/offchain/src/api/http.rs @@ -199,7 +199,7 @@ impl HttpApi { ) -> Result<(), HttpError> { // Extract the request from the list. // Don't forget to add it back if necessary when returning. - let mut request = self.requests.remove(&request_id).ok_or_else(|| HttpError::Invalid)?; + let mut request = self.requests.remove(&request_id).ok_or(HttpError::Invalid)?; let mut deadline = timestamp::deadline_to_future(deadline); // Closure that writes data to a sender, taking the deadline into account. Can return `Ok` diff --git a/client/peerset/src/lib.rs b/client/peerset/src/lib.rs index 859319fab1320..ec09835c4898e 100644 --- a/client/peerset/src/lib.rs +++ b/client/peerset/src/lib.rs @@ -619,11 +619,9 @@ impl Peerset { self.update_time(); - if self.reserved_nodes[set_id.0].1 { - if !self.reserved_nodes[set_id.0].0.contains(&peer_id) { - self.message_queue.push_back(Message::Reject(index)); - return - } + if self.reserved_nodes[set_id.0].1 && !self.reserved_nodes[set_id.0].0.contains(&peer_id) { + self.message_queue.push_back(Message::Reject(index)); + return } let not_connected = match self.data.peer(set_id.0, &peer_id) { @@ -730,8 +728,7 @@ impl Stream for Peerset { return Poll::Ready(Some(message)) } - if let Poll::Ready(_) = Future::poll(Pin::new(&mut self.next_periodic_alloc_slots), cx) - { + if Future::poll(Pin::new(&mut self.next_periodic_alloc_slots), cx).is_ready() { self.next_periodic_alloc_slots = Delay::new(Duration::new(1, 0)); for set_index in 0..self.data.num_sets() { @@ -798,7 +795,7 @@ mod tests { fn next_message(mut peerset: Peerset) -> Result<(Message, Peerset), ()> { let next = futures::executor::block_on_stream(&mut peerset).next(); - let message = next.ok_or_else(|| ())?; + let message = next.ok_or(())?; Ok((message, peerset)) } diff --git a/client/peerset/src/peersstate.rs b/client/peerset/src/peersstate.rs index ca22cac324088..c9af5b8e2ccd0 100644 --- a/client/peerset/src/peersstate.rs +++ b/client/peerset/src/peersstate.rs @@ -169,9 +169,7 @@ impl PeersState { /// Returns an object that grants access to the reputation value of a peer. pub fn peer_reputation(&mut self, peer_id: PeerId) -> Reputation { - if !self.nodes.contains_key(&peer_id) { - self.nodes.insert(peer_id, Node::new(self.sets.len())); - } + self.nodes.entry(peer_id).or_insert_with(|| Node::new(self.sets.len())); let entry = match self.nodes.entry(peer_id) { Entry::Vacant(_) => unreachable!("guaranteed to be inserted above; qed"), @@ -652,7 +650,7 @@ mod tests { let id1 = PeerId::random(); let id2 = PeerId::random(); - peers_state.add_no_slot_node(0, id1.clone()); + peers_state.add_no_slot_node(0, id1); if let Peer::Unknown(p) = peers_state.peer(0, &id1) { assert!(p.discover().try_accept_incoming().is_ok()); } else { @@ -705,43 +703,28 @@ mod tests { assert!(peers_state.highest_not_connected_peer(0).is_none()); peers_state.peer(0, &id1).into_unknown().unwrap().discover().set_reputation(50); peers_state.peer(0, &id2).into_unknown().unwrap().discover().set_reputation(25); - assert_eq!( - peers_state.highest_not_connected_peer(0).map(|p| p.into_peer_id()), - Some(id1.clone()) - ); + assert_eq!(peers_state.highest_not_connected_peer(0).map(|p| p.into_peer_id()), Some(id1)); peers_state.peer(0, &id2).into_not_connected().unwrap().set_reputation(75); - assert_eq!( - peers_state.highest_not_connected_peer(0).map(|p| p.into_peer_id()), - Some(id2.clone()) - ); + assert_eq!(peers_state.highest_not_connected_peer(0).map(|p| p.into_peer_id()), Some(id2)); peers_state .peer(0, &id2) .into_not_connected() .unwrap() .try_accept_incoming() .unwrap(); - assert_eq!( - peers_state.highest_not_connected_peer(0).map(|p| p.into_peer_id()), - Some(id1.clone()) - ); + assert_eq!(peers_state.highest_not_connected_peer(0).map(|p| p.into_peer_id()), Some(id1)); peers_state.peer(0, &id1).into_not_connected().unwrap().set_reputation(100); peers_state.peer(0, &id2).into_connected().unwrap().disconnect(); - assert_eq!( - peers_state.highest_not_connected_peer(0).map(|p| p.into_peer_id()), - Some(id1.clone()) - ); + assert_eq!(peers_state.highest_not_connected_peer(0).map(|p| p.into_peer_id()), Some(id1)); peers_state.peer(0, &id1).into_not_connected().unwrap().set_reputation(-100); - assert_eq!( - peers_state.highest_not_connected_peer(0).map(|p| p.into_peer_id()), - Some(id2.clone()) - ); + assert_eq!(peers_state.highest_not_connected_peer(0).map(|p| p.into_peer_id()), Some(id2)); } #[test] fn disconnect_no_slot_doesnt_panic() { let mut peers_state = PeersState::new(iter::once(SetConfig { in_peers: 1, out_peers: 1 })); let id = PeerId::random(); - peers_state.add_no_slot_node(0, id.clone()); + peers_state.add_no_slot_node(0, id); let peer = peers_state .peer(0, &id) .into_unknown() diff --git a/client/peerset/tests/fuzz.rs b/client/peerset/tests/fuzz.rs index af4838d724cbf..48c5cb341c35a 100644 --- a/client/peerset/tests/fuzz.rs +++ b/client/peerset/tests/fuzz.rs @@ -52,7 +52,7 @@ fn test_once() { bootnodes: (0..Uniform::new_inclusive(0, 4).sample(&mut rng)) .map(|_| { let id = PeerId::random(); - known_nodes.insert(id.clone()); + known_nodes.insert(id); id }) .collect(), @@ -60,8 +60,8 @@ fn test_once() { (0..Uniform::new_inclusive(0, 2).sample(&mut rng)) .map(|_| { let id = PeerId::random(); - known_nodes.insert(id.clone()); - reserved_nodes.insert(id.clone()); + known_nodes.insert(id); + reserved_nodes.insert(id); id }) .collect() @@ -114,7 +114,7 @@ fn test_once() { // If we generate 1, discover a new node. 1 => { let new_id = PeerId::random(); - known_nodes.insert(new_id.clone()); + known_nodes.insert(new_id); peerset.add_to_peers_set(SetId::from(0), new_id); }, @@ -122,7 +122,7 @@ fn test_once() { 2 => if let Some(id) = known_nodes.iter().choose(&mut rng) { let val = Uniform::new_inclusive(i32::MIN, i32::MAX).sample(&mut rng); - peerset_handle.report_peer(id.clone(), ReputationChange::new(val, "")); + peerset_handle.report_peer(*id, ReputationChange::new(val, "")); }, // If we generate 3, disconnect from a random node. @@ -142,8 +142,8 @@ fn test_once() { }) .choose(&mut rng) { - peerset.incoming(SetId::from(0), id.clone(), next_incoming_id.clone()); - incoming_nodes.insert(next_incoming_id.clone(), id.clone()); + peerset.incoming(SetId::from(0), *id, next_incoming_id); + incoming_nodes.insert(next_incoming_id, *id); next_incoming_id.0 += 1; } }, @@ -157,8 +157,8 @@ fn test_once() { if let Some(id) = known_nodes.iter().filter(|n| !reserved_nodes.contains(*n)).choose(&mut rng) { - peerset_handle.add_reserved_peer(SetId::from(0), id.clone()); - reserved_nodes.insert(id.clone()); + peerset_handle.add_reserved_peer(SetId::from(0), *id); + reserved_nodes.insert(*id); } }, 8 => diff --git a/client/rpc-api/src/author/error.rs b/client/rpc-api/src/author/error.rs index fe12bd581f120..1f8c65c471398 100644 --- a/client/rpc-api/src/author/error.rs +++ b/client/rpc-api/src/author/error.rs @@ -89,12 +89,12 @@ impl From for rpc::Error { match e { Error::BadFormat(e) => rpc::Error { code: rpc::ErrorCode::ServerError(BAD_FORMAT), - message: format!("Extrinsic has invalid format: {}", e).into(), + message: format!("Extrinsic has invalid format: {}", e), data: None, }, Error::Verification(e) => rpc::Error { code: rpc::ErrorCode::ServerError(VERIFICATION_ERROR), - message: format!("Verification Error: {}", e).into(), + message: format!("Verification Error: {}", e), data: Some(e.to_string().into()), }, Error::Pool(PoolError::InvalidTransaction(InvalidTransaction::Custom(e))) => rpc::Error { diff --git a/client/rpc-servers/src/middleware.rs b/client/rpc-servers/src/middleware.rs index 4adc878660090..d4ac787ce9f0c 100644 --- a/client/rpc-servers/src/middleware.rs +++ b/client/rpc-servers/src/middleware.rs @@ -237,8 +237,5 @@ fn call_name<'a>(call: &'a jsonrpc_core::Call, known_methods: &HashSet) } fn is_success(output: &Option) -> bool { - match output { - Some(jsonrpc_core::Output::Success(..)) => true, - _ => false, - } + matches!(output, Some(jsonrpc_core::Output::Success(..))) } diff --git a/client/rpc/src/author/mod.rs b/client/rpc/src/author/mod.rs index 5064e61342101..2821eea2cc09d 100644 --- a/client/rpc/src/author/mod.rs +++ b/client/rpc/src/author/mod.rs @@ -119,7 +119,7 @@ where .runtime_api() .decode_session_keys(&generic::BlockId::Hash(best_block_hash), session_keys.to_vec()) .map_err(|e| Error::Client(Box::new(e)))? - .ok_or_else(|| Error::InvalidSessionKeys)?; + .ok_or(Error::InvalidSessionKeys)?; Ok(SyncCryptoStore::has_keys(&*self.keystore, &keys)) } @@ -143,7 +143,7 @@ where .map_err(|e| { e.into_pool_error() .map(Into::into) - .unwrap_or_else(|e| error::Error::Verification(Box::new(e)).into()) + .unwrap_or_else(|e| error::Error::Verification(Box::new(e))) }) .boxed() } @@ -201,7 +201,7 @@ where .map_err(|e| { e.into_pool_error() .map(error::Error::from) - .unwrap_or_else(|e| error::Error::Verification(Box::new(e)).into()) + .unwrap_or_else(|e| error::Error::Verification(Box::new(e))) }); let subscriptions = self.subscriptions.clone(); diff --git a/client/rpc/src/chain/mod.rs b/client/rpc/src/chain/mod.rs index c20e5a188ad3d..64231dd78c83c 100644 --- a/client/rpc/src/chain/mod.rs +++ b/client/rpc/src/chain/mod.rs @@ -60,7 +60,7 @@ where /// Tries to unwrap passed block hash, or uses best block hash otherwise. fn unwrap_or_best(&self, hash: Option) -> Block::Hash { - match hash.into() { + match hash { None => self.client().info().best_hash, Some(hash) => hash, } @@ -311,7 +311,7 @@ fn subscribe_headers( // send further subscriptions let stream = stream() .inspect_err(|e| warn!("Block notification stream error: {:?}", e)) - .map(|res| Ok(res)); + .map(Ok); stream::iter(vec![Ok(header)]) .chain(stream) diff --git a/client/rpc/src/state/state_full.rs b/client/rpc/src/state/state_full.rs index 1a35760bd67df..38f9b078d87a7 100644 --- a/client/rpc/src/state/state_full.rs +++ b/client/rpc/src/state/state_full.rs @@ -151,10 +151,8 @@ where changes: &mut Vec>, ) -> Result<()> { for block_hash in &range.hashes { - let block_hash = block_hash.clone(); - let mut block_changes = - StorageChangeSet { block: block_hash.clone(), changes: Vec::new() }; - let id = BlockId::hash(block_hash); + let mut block_changes = StorageChangeSet { block: *block_hash, changes: Vec::new() }; + let id = BlockId::hash(*block_hash); for key in keys { let (has_changed, data) = { let curr_data = self.client.storage(&id, key).map_err(client_err)?; diff --git a/client/service/src/builder.rs b/client/service/src/builder.rs index 882d7666dbe63..35ea67d8fbf85 100644 --- a/client/service/src/builder.rs +++ b/client/service/src/builder.rs @@ -263,7 +263,7 @@ where state_cache_child_ratio: config.state_cache_child_ratio.map(|v| (v, 100)), state_pruning: config.state_pruning.clone(), source: config.database.clone(), - keep_blocks: config.keep_blocks.clone(), + keep_blocks: config.keep_blocks, }; let backend = new_db_backend(db_config)?; @@ -421,10 +421,10 @@ where Some("offchain-worker"), sc_offchain::notification_future( config.role.is_authority(), - client.clone(), + client, offchain, Clone::clone(&spawn_handle), - network.clone(), + network, ), ); } @@ -517,7 +517,7 @@ where let metrics_service = if let Some(PrometheusConfig { port, registry }) = config.prometheus_config.clone() { // Set static metrics. - let metrics = MetricsService::with_prometheus(telemetry.clone(), ®istry, &config)?; + let metrics = MetricsService::with_prometheus(telemetry, ®istry, &config)?; spawn_handle.spawn( "prometheus-endpoint", None, @@ -526,7 +526,7 @@ where metrics } else { - MetricsService::new(telemetry.clone()) + MetricsService::new(telemetry) }; // Periodically updated metrics and telemetry updates. @@ -572,7 +572,7 @@ where None, sc_informant::build( client.clone(), - network.clone(), + network, transaction_pool.clone(), config.informant_output_format, ), diff --git a/client/service/src/client/block_rules.rs b/client/service/src/client/block_rules.rs index 519d5a13f5168..2ed27b8fe1b63 100644 --- a/client/service/src/client/block_rules.rs +++ b/client/service/src/client/block_rules.rs @@ -47,8 +47,8 @@ impl BlockRules { /// New block rules with provided black and white lists. pub fn new(fork_blocks: ForkBlocks, bad_blocks: BadBlocks) -> Self { Self { - bad: bad_blocks.unwrap_or_else(|| HashSet::new()), - forks: fork_blocks.unwrap_or_else(|| vec![]).into_iter().collect(), + bad: bad_blocks.unwrap_or_default(), + forks: fork_blocks.unwrap_or_default().into_iter().collect(), } } @@ -61,7 +61,7 @@ impl BlockRules { pub fn lookup(&self, number: NumberFor, hash: &B::Hash) -> LookupResult { if let Some(hash_for_height) = self.forks.get(&number) { if hash_for_height != hash { - return LookupResult::Expected(hash_for_height.clone()) + return LookupResult::Expected(*hash_for_height) } } diff --git a/client/service/src/client/call_executor.rs b/client/service/src/client/call_executor.rs index 0f13b08d0ce8e..1e8114df13339 100644 --- a/client/service/src/client/call_executor.rs +++ b/client/service/src/client/call_executor.rs @@ -283,7 +283,7 @@ where state_runtime_code.runtime_code().map_err(sp_blockchain::Error::RuntimeCode)?; self.executor .runtime_version(&mut ext, &runtime_code) - .map_err(|e| sp_blockchain::Error::VersionInvalid(e.to_string()).into()) + .map_err(|e| sp_blockchain::Error::VersionInvalid(e.to_string())) } fn prove_execution( @@ -305,7 +305,7 @@ where let runtime_code = self.check_override(runtime_code, at)?; sp_state_machine::prove_execution_on_trie_backend( - &trie_backend, + trie_backend, &mut Default::default(), &self.executor, self.spawn_handle.clone(), diff --git a/client/service/src/client/client.rs b/client/service/src/client/client.rs index dfa392bc96bde..48e4d0141c4e7 100644 --- a/client/service/src/client/client.rs +++ b/client/service/src/client/client.rs @@ -367,7 +367,7 @@ where let mut op = backend.begin_operation()?; let state_root = op.set_genesis_state(genesis_storage, !config.no_genesis, genesis_state_version)?; - let genesis_block = genesis::construct_genesis_block::(state_root.into()); + let genesis_block = genesis::construct_genesis_block::(state_root); info!( "🔨 Initializing Genesis block/state (state: {}, header-hash: {})", genesis_block.header().state_root(), @@ -551,7 +551,7 @@ where >::Api: CoreApi + ApiExt, { - let parent_hash = import_headers.post().parent_hash().clone(); + let parent_hash = *import_headers.post().parent_hash(); let status = self.backend.blockchain().status(BlockId::Hash(hash))?; let parent_exists = self.backend.blockchain().status(BlockId::Hash(parent_hash))? == blockchain::BlockStatus::InChain; @@ -609,7 +609,7 @@ where sc_consensus::StorageChanges::Import(changes) => { let mut storage = sp_storage::Storage::default(); for state in changes.state.0.into_iter() { - if state.parent_storage_keys.len() == 0 && state.state_root.len() == 0 { + if state.parent_storage_keys.is_empty() && state.state_root.is_empty() { for (key, value) in state.key_values.into_iter() { storage.top.insert(key, value); } @@ -617,7 +617,7 @@ where for parent_storage in state.parent_storage_keys { let storage_key = PrefixedStorageKey::new_ref(&parent_storage); let storage_key = - match ChildType::from_prefixed_key(&storage_key) { + match ChildType::from_prefixed_key(storage_key) { Some((ChildType::ParentKeyId, storage_key)) => storage_key, None => @@ -1033,7 +1033,7 @@ where return Ok(BlockStatus::Queued) } } - let hash_and_number = match id.clone() { + let hash_and_number = match *id { BlockId::Hash(hash) => self.backend.blockchain().number(hash)?.map(|n| (hash, n)), BlockId::Number(n) => self.backend.blockchain().hash(n)?.map(|hash| (hash, n)), }; @@ -1212,8 +1212,8 @@ where } let state = self.state_at(id)?; let child_info = |storage_key: &Vec| -> sp_blockchain::Result { - let storage_key = PrefixedStorageKey::new_ref(&storage_key); - match ChildType::from_prefixed_key(&storage_key) { + let storage_key = PrefixedStorageKey::new_ref(storage_key); + match ChildType::from_prefixed_key(storage_key) { Some((ChildType::ParentKeyId, storage_key)) => Ok(ChildInfo::new_default(storage_key)), None => Err(Error::Backend("Invalid child storage key.".to_string())), @@ -1222,7 +1222,7 @@ where let mut current_child = if start_key.len() == 2 { let start_key = start_key.get(0).expect("checked len"); if let Some(child_root) = state - .storage(&start_key) + .storage(start_key) .map_err(|e| sp_blockchain::Error::from_state(Box::new(e)))? { Some((child_info(start_key)?, child_root)) @@ -1232,7 +1232,7 @@ where } else { None }; - let mut current_key = start_key.last().map(Clone::clone).unwrap_or(Vec::new()); + let mut current_key = start_key.last().map(Clone::clone).unwrap_or_default(); let mut total_size = 0; let mut result = vec![( KeyValueStorageLevel { @@ -1276,14 +1276,13 @@ where total_size += size; if current_child.is_none() && - sp_core::storage::well_known_keys::is_child_storage_key(next_key.as_slice()) + sp_core::storage::well_known_keys::is_child_storage_key(next_key.as_slice()) && + !child_roots.contains(value.as_slice()) { - if !child_roots.contains(value.as_slice()) { - child_roots.insert(value.clone()); - switch_child_key = Some((next_key.clone(), value.clone())); - entries.push((next_key.clone(), value)); - break - } + child_roots.insert(value.clone()); + switch_child_key = Some((next_key.clone(), value.clone())); + entries.push((next_key.clone(), value)); + break } entries.push((next_key.clone(), value)); current_key = next_key; @@ -1647,7 +1646,7 @@ where { type Api = >::RuntimeApi; - fn runtime_api<'a>(&'a self) -> ApiRef<'a, Self::Api> { + fn runtime_api(&self) -> ApiRef { RA::construct_runtime_api(self) } } @@ -1661,12 +1660,11 @@ where type StateBackend = B::State; fn call_api_at< - 'a, R: Encode + Decode + PartialEq, NC: FnOnce() -> result::Result + UnwindSafe, >( &self, - params: CallApiAtParams<'a, Block, NC, B::State>, + params: CallApiAtParams, ) -> Result, sp_api::ApiError> { let at = params.at; @@ -1742,7 +1740,7 @@ where }) .map_err(|e| { warn!("Block import error: {}", e); - ConsensusError::ClientImport(e.to_string()).into() + ConsensusError::ClientImport(e.to_string()) }) } diff --git a/client/service/src/client/wasm_override.rs b/client/service/src/client/wasm_override.rs index fa35be9fcac87..5fc748f3e88b9 100644 --- a/client/service/src/client/wasm_override.rs +++ b/client/service/src/client/wasm_override.rs @@ -87,7 +87,7 @@ fn make_hash(val: &K) -> Vec { } impl FetchRuntimeCode for WasmBlob { - fn fetch_runtime_code<'a>(&'a self) -> Option> { + fn fetch_runtime_code(&self) -> Option> { Some(self.code.as_slice().into()) } } @@ -186,34 +186,30 @@ impl WasmOverride { for entry in fs::read_dir(dir).map_err(handle_err)? { let entry = entry.map_err(handle_err)?; let path = entry.path(); - match path.extension().and_then(|e| e.to_str()) { - Some("wasm") => { - let code = fs::read(&path).map_err(handle_err)?; - let code_hash = make_hash(&code); - let version = Self::runtime_version(executor, &code, &code_hash, Some(128))?; - + if let Some("wasm") = path.extension().and_then(|e| e.to_str()) { + let code = fs::read(&path).map_err(handle_err)?; + let code_hash = make_hash(&code); + let version = Self::runtime_version(executor, &code, &code_hash, Some(128))?; + tracing::info!( + target: "wasm_overrides", + version = %version, + file = %path.display(), + "Found wasm override.", + ); + + let wasm = + WasmBlob::new(code, code_hash, path.clone(), version.spec_name.to_string()); + + if let Some(other) = overrides.insert(version.spec_version, wasm) { tracing::info!( target: "wasm_overrides", - version = %version, - file = %path.display(), - "Found wasm override.", + first = %other.path.display(), + second = %path.display(), + %version, + "Found duplicate spec version for runtime.", ); - - let wasm = - WasmBlob::new(code, code_hash, path.clone(), version.spec_name.to_string()); - - if let Some(other) = overrides.insert(version.spec_version, wasm) { - tracing::info!( - target: "wasm_overrides", - first = %other.path.display(), - second = %path.display(), - %version, - "Found duplicate spec version for runtime.", - ); - duplicates.push(path.display().to_string()); - } - }, - _ => (), + duplicates.push(path.display().to_string()); + } } } diff --git a/client/service/src/client/wasm_substitutes.rs b/client/service/src/client/wasm_substitutes.rs index 3690672512675..f826fa3613c84 100644 --- a/client/service/src/client/wasm_substitutes.rs +++ b/client/service/src/client/wasm_substitutes.rs @@ -56,7 +56,7 @@ impl WasmSubstitute { /// Returns `true` when the substitute matches for the given `block_id`. fn matches(&self, block_id: &BlockId, backend: &impl backend::Backend) -> bool { let requested_block_number = - backend.blockchain().block_number_from_id(&block_id).ok().flatten(); + backend.blockchain().block_number_from_id(block_id).ok().flatten(); Some(self.block_number) <= requested_block_number } @@ -70,7 +70,7 @@ fn make_hash(val: &K) -> Vec { } impl FetchRuntimeCode for WasmSubstitute { - fn fetch_runtime_code<'a>(&'a self) -> Option> { + fn fetch_runtime_code(&self) -> Option> { Some(self.code.as_slice().into()) } } diff --git a/client/service/src/lib.rs b/client/service/src/lib.rs index a48e6168c52cc..0d2461376d961 100644 --- a/client/service/src/lib.rs +++ b/client/service/src/lib.rs @@ -180,7 +180,7 @@ async fn build_network_future< if notification.is_new_best { network.service().new_best_block_imported( notification.hash, - notification.header.number().clone(), + *notification.header.number(), ); } } @@ -204,7 +204,7 @@ async fn build_network_future< let _ = sender.send(network.local_peer_id().to_base58()); }, sc_rpc::system::Request::LocalListenAddresses(sender) => { - let peer_id = network.local_peer_id().clone().into(); + let peer_id = (*network.local_peer_id()).into(); let p2p_proto_suffix = sc_network::multiaddr::Protocol::P2p(peer_id); let addresses = network.listen_addresses() .map(|addr| addr.clone().with(p2p_proto_suffix.clone()).to_string()) @@ -222,7 +222,7 @@ async fn build_network_future< ).collect()); } sc_rpc::system::Request::NetworkState(sender) => { - if let Some(network_state) = serde_json::to_value(&network.network_state()).ok() { + if let Ok(network_state) = serde_json::to_value(&network.network_state()) { let _ = sender.send(network_state); } } @@ -265,7 +265,7 @@ async fn build_network_future< use sc_rpc::system::SyncState; let _ = sender.send(SyncState { - starting_block: starting_block, + starting_block, current_block: client.info().best_number, highest_block: network.best_seen_block(), }); @@ -385,7 +385,7 @@ fn start_rpc_servers< address, config.rpc_cors.as_ref(), gen_handler( - deny_unsafe(&address, &config.rpc_methods), + deny_unsafe(address, &config.rpc_methods), sc_rpc_server::RpcMiddleware::new( rpc_metrics.clone(), rpc_method_names.clone(), @@ -404,7 +404,7 @@ fn start_rpc_servers< config.rpc_ws_max_connections, config.rpc_cors.as_ref(), gen_handler( - deny_unsafe(&address, &config.rpc_methods), + deny_unsafe(address, &config.rpc_methods), sc_rpc_server::RpcMiddleware::new( rpc_metrics.clone(), rpc_method_names.clone(), diff --git a/client/service/src/metrics.rs b/client/service/src/metrics.rs index 9fbf2c3ea3fca..555023f894488 100644 --- a/client/service/src/metrics.rs +++ b/client/service/src/metrics.rs @@ -61,13 +61,13 @@ impl PrometheusMetrics { .const_label("name", name) .const_label("version", version), )?, - ®istry, + registry, )? .set(1); register( Gauge::::new("substrate_node_roles", "The roles the node is running as")?, - ®istry, + registry, )? .set(roles); diff --git a/client/service/src/task_manager/mod.rs b/client/service/src/task_manager/mod.rs index 2f9cd257c360b..49189dc21ce8d 100644 --- a/client/service/src/task_manager/mod.rs +++ b/client/service/src/task_manager/mod.rs @@ -38,12 +38,12 @@ mod prometheus_future; mod tests; /// Default task group name. -pub const DEFAULT_GROUP_NAME: &'static str = "default"; +pub const DEFAULT_GROUP_NAME: &str = "default"; /// The name of a group a task belongs to. /// /// This name is passed belong-side the task name to the prometheus metrics and can be used -/// to group tasks. +/// to group tasks. pub enum GroupName { /// Sets the group name to `default`. Default, diff --git a/client/service/test/src/lib.rs b/client/service/test/src/lib.rs index c492ed30f7d91..ef04e16a65d26 100644 --- a/client/service/test/src/lib.rs +++ b/client/service/test/src/lib.rs @@ -308,15 +308,15 @@ where handle.clone(), Some(key), self.base_port, - &temp, + temp, ); - let addr = node_config.network.listen_addresses.iter().next().unwrap().clone(); + let addr = node_config.network.listen_addresses.first().unwrap().clone(); let (service, user_data) = authority(node_config).expect("Error creating test node service"); handle.spawn(service.clone().map_err(|_| ())); - let addr = addr - .with(multiaddr::Protocol::P2p(service.network().local_peer_id().clone().into())); + let addr = + addr.with(multiaddr::Protocol::P2p((*service.network().local_peer_id()).into())); self.authority_nodes.push((self.nodes, service, user_data, addr)); self.nodes += 1; } @@ -329,14 +329,14 @@ where handle.clone(), None, self.base_port, - &temp, + temp, ); - let addr = node_config.network.listen_addresses.iter().next().unwrap().clone(); + let addr = node_config.network.listen_addresses.first().unwrap().clone(); let (service, user_data) = full(node_config).expect("Error creating test node service"); handle.spawn(service.clone().map_err(|_| ())); - let addr = addr - .with(multiaddr::Protocol::P2p(service.network().local_peer_id().clone().into())); + let addr = + addr.with(multiaddr::Protocol::P2p((*service.network().local_peer_id()).into())); self.full_nodes.push((self.nodes, service, user_data, addr)); self.nodes += 1; } @@ -461,7 +461,7 @@ pub fn sync( info!("Generating #{}", i + 1); } - make_block_and_import(&first_service, first_user_data); + make_block_and_import(first_service, first_user_data); } let info = network.full_nodes[0].1.client().info(); network.full_nodes[0] diff --git a/client/state-db/src/lib.rs b/client/state-db/src/lib.rs index 74f218e88f861..8fe2cdc9d9a85 100644 --- a/client/state-db/src/lib.rs +++ b/client/state-db/src/lib.rs @@ -268,7 +268,7 @@ impl StateDbSync Ok(()), @@ -314,7 +314,7 @@ impl StateDbSync if self.mode == PruningMode::ArchiveCanonical { commit.data.deleted.clear(); @@ -322,14 +322,14 @@ impl StateDbSync return Err(e), }; if let Some(ref mut pruning) = self.pruning { - pruning.note_canonical(&hash, &mut commit); + pruning.note_canonical(hash, &mut commit); } self.prune(&mut commit); Ok(commit) } fn best_canonical(&self) -> Option { - return self.non_canonical.last_canonicalized_block_number() + self.non_canonical.last_canonicalized_block_number() } fn is_pruned(&self, hash: &BlockHash, number: u64) -> bool { @@ -438,7 +438,7 @@ impl StateDbSync(values: &mut HashMap, inserted fn discard_descendants( levels: &mut (&mut [OverlayLevel], &mut [OverlayLevel]), - mut values: &mut HashMap, + values: &mut HashMap, parents: &mut HashMap, pinned: &HashMap, pinned_insertions: &mut HashMap, u32)>, @@ -135,12 +135,10 @@ fn discard_descendants( ) -> u32 { let (first, mut remainder) = if let Some((first, rest)) = levels.0.split_first_mut() { (Some(first), (rest, &mut *levels.1)) + } else if let Some((first, rest)) = levels.1.split_first_mut() { + (Some(first), (&mut *levels.0, rest)) } else { - if let Some((first, rest)) = levels.1.split_first_mut() { - (Some(first), (&mut *levels.0, rest)) - } else { - (None, (&mut *levels.0, &mut *levels.1)) - } + (None, (&mut *levels.0, &mut *levels.1)) }; let mut pinned_children = 0; if let Some(level) = first { @@ -169,7 +167,7 @@ fn discard_descendants( } else { // discard immediately. parents.remove(&overlay.hash); - discard_values(&mut values, overlay.inserted); + discard_values(values, overlay.inserted); } } } @@ -180,7 +178,7 @@ impl NonCanonicalOverlay { /// Creates a new instance. Does not expect any metadata to be present in the DB. pub fn new(db: &D) -> Result, Error> { let last_canonicalized = - db.get_meta(&to_meta_key(LAST_CANONICAL, &())).map_err(|e| Error::Db(e))?; + db.get_meta(&to_meta_key(LAST_CANONICAL, &())).map_err(Error::Db)?; let last_canonicalized = last_canonicalized .map(|buffer| <(BlockHash, u64)>::decode(&mut buffer.as_slice())) .transpose()?; @@ -196,7 +194,7 @@ impl NonCanonicalOverlay { let mut level = OverlayLevel::new(); for index in 0..MAX_BLOCKS_PER_LEVEL { let journal_key = to_journal_key(block, index); - if let Some(record) = db.get_meta(&journal_key).map_err(|e| Error::Db(e))? { + if let Some(record) = db.get_meta(&journal_key).map_err(Error::Db)? { let record: JournalRecord = Decode::decode(&mut record.as_slice())?; let inserted = record.inserted.iter().map(|(k, _)| k.clone()).collect(); @@ -280,7 +278,7 @@ impl NonCanonicalOverlay { { return Err(Error::InvalidParent) } - } else if !self.parents.contains_key(&parent_hash) { + } else if !self.parents.contains_key(parent_hash) { return Err(Error::InvalidParent) } } @@ -391,12 +389,12 @@ impl NonCanonicalOverlay { let level = self .levels .get(self.pending_canonicalizations.len()) - .ok_or_else(|| Error::InvalidBlock)?; + .ok_or(Error::InvalidBlock)?; let index = level .blocks .iter() .position(|overlay| overlay.hash == *hash) - .ok_or_else(|| Error::InvalidBlock)?; + .ok_or(Error::InvalidBlock)?; let mut discarded_journals = Vec::new(); let mut discarded_blocks = Vec::new(); @@ -493,10 +491,7 @@ impl NonCanonicalOverlay { Key: std::borrow::Borrow, Q: std::hash::Hash + Eq, { - if let Some((_, value)) = self.values.get(&key) { - return Some(value.clone()) - } - None + self.values.get(key).map(|v| v.1.clone()) } /// Check if the block is in the canonicalization queue. diff --git a/client/state-db/src/pruning.rs b/client/state-db/src/pruning.rs index 2631405cdffa7..0fdcb8e822b6f 100644 --- a/client/state-db/src/pruning.rs +++ b/client/state-db/src/pruning.rs @@ -76,7 +76,7 @@ impl RefWindow { db: &D, count_insertions: bool, ) -> Result, Error> { - let last_pruned = db.get_meta(&to_meta_key(LAST_PRUNED, &())).map_err(|e| Error::Db(e))?; + let last_pruned = db.get_meta(&to_meta_key(LAST_PRUNED, &())).map_err(Error::Db)?; let pending_number: u64 = match last_pruned { Some(buffer) => u64::decode(&mut buffer.as_slice())? + 1, None => 0, @@ -94,7 +94,7 @@ impl RefWindow { trace!(target: "state-db", "Reading pruning journal. Pending #{}", pending_number); loop { let journal_key = to_journal_key(block); - match db.get_meta(&journal_key).map_err(|e| Error::Db(e))? { + match db.get_meta(&journal_key).map_err(Error::Db)? { Some(record) => { let record: JournalRecord = Decode::decode(&mut record.as_slice())?; @@ -208,7 +208,7 @@ impl RefWindow { trace!(target: "state-db", "Applying pruning {:?} ({} deleted)", pruned.hash, pruned.deleted.len()); if self.count_insertions { for k in pruned.deleted.iter() { - self.death_index.remove(&k); + self.death_index.remove(k); } } self.pending_number += 1; diff --git a/client/sync-state-rpc/src/lib.rs b/client/sync-state-rpc/src/lib.rs index 6fc0d17800fed..d7696c662e856 100644 --- a/client/sync-state-rpc/src/lib.rs +++ b/client/sync-state-rpc/src/lib.rs @@ -166,7 +166,7 @@ where let finalized_block_weight = sc_consensus_babe::aux_schema::load_block_weight(&*self.client, finalized_hash)? - .ok_or_else(|| Error::LoadingBlockWeightFailed(finalized_hash))?; + .ok_or(Error::LoadingBlockWeightFailed(finalized_hash))?; Ok(LightSyncState { finalized_block_header: finalized_header, diff --git a/client/telemetry/src/lib.rs b/client/telemetry/src/lib.rs index d570701a3d9ec..fc40f999a6779 100644 --- a/client/telemetry/src/lib.rs +++ b/client/telemetry/src/lib.rs @@ -246,7 +246,7 @@ impl TelemetryWorker { "Initializing telemetry for: {:?}", addr, ); - node_map.entry(id.clone()).or_default().push((verbosity, addr.clone())); + node_map.entry(id).or_default().push((verbosity, addr.clone())); let node = node_pool.entry(addr.clone()).or_insert_with(|| { Node::new(transport.clone(), addr.clone(), Vec::new(), Vec::new()) @@ -288,7 +288,7 @@ impl TelemetryWorker { ) { let (id, verbosity, payload) = input.expect("the stream is never closed; qed"); - let ts = chrono::Local::now().to_rfc3339().to_string(); + let ts = chrono::Local::now().to_rfc3339(); let mut message = serde_json::Map::new(); message.insert("id".into(), id.into()); message.insert("ts".into(), ts.into()); @@ -318,7 +318,7 @@ impl TelemetryWorker { continue } - if let Some(node) = node_pool.get_mut(&addr) { + if let Some(node) = node_pool.get_mut(addr) { let _ = node.send(message.clone()).await; } else { log::debug!( @@ -386,7 +386,7 @@ impl Telemetry { /// The `connection_message` argument is a JSON object that is sent every time the connection /// (re-)establishes. pub fn start_telemetry(&mut self, connection_message: ConnectionMessage) -> Result<()> { - let endpoints = self.endpoints.take().ok_or_else(|| Error::TelemetryAlreadyInitialized)?; + let endpoints = self.endpoints.take().ok_or(Error::TelemetryAlreadyInitialized)?; self.register_sender .unbounded_send(Register::Telemetry { id: self.id, endpoints, connection_message }) diff --git a/client/telemetry/src/node.rs b/client/telemetry/src/node.rs index f3d746abb2b18..aa0f5a3843d33 100644 --- a/client/telemetry/src/node.rs +++ b/client/telemetry/src/node.rs @@ -239,7 +239,7 @@ where }, }, NodeSocket::WaitingReconnect(mut s) => { - if let Poll::Ready(_) = Future::poll(Pin::new(&mut s), cx) { + if Future::poll(Pin::new(&mut s), cx).is_ready() { socket = NodeSocket::ReconnectNow; } else { break NodeSocket::WaitingReconnect(s) diff --git a/client/tracing/src/block/mod.rs b/client/tracing/src/block/mod.rs index 259827e4b47d9..6de3a0220e15e 100644 --- a/client/tracing/src/block/mod.rs +++ b/client/tracing/src/block/mod.rs @@ -108,7 +108,7 @@ impl BlockSubscriber { impl Subscriber for BlockSubscriber { fn enabled(&self, metadata: &tracing::Metadata<'_>) -> bool { - if !metadata.is_span() && !metadata.fields().field(REQUIRED_EVENT_FIELD).is_some() { + if !metadata.is_span() && metadata.fields().field(REQUIRED_EVENT_FIELD).is_none() { return false } for (target, level) in &self.targets { @@ -221,12 +221,12 @@ where let mut header = self .client .header(id) - .map_err(|e| Error::InvalidBlockId(e))? + .map_err(Error::InvalidBlockId)? .ok_or_else(|| Error::MissingBlockComponent("Header not found".to_string()))?; let extrinsics = self .client .block_body(&id) - .map_err(|e| Error::InvalidBlockId(e))? + .map_err(Error::InvalidBlockId)? .ok_or_else(|| Error::MissingBlockComponent("Extrinsics not found".to_string()))?; tracing::debug!(target: "state_tracing", "Found {} extrinsics", extrinsics.len()); let parent_hash = *header.parent_hash(); @@ -252,16 +252,18 @@ where let _enter = span.enter(); self.client.runtime_api().execute_block(&parent_id, block) }) { - return Err(Error::Dispatch( - format!("Failed to collect traces and execute block: {}", e).to_string(), - )) + return Err(Error::Dispatch(format!( + "Failed to collect traces and execute block: {}", + e + ))) } } - let block_subscriber = - dispatch.downcast_ref::().ok_or(Error::Dispatch( + let block_subscriber = dispatch.downcast_ref::().ok_or_else(|| { + Error::Dispatch( "Cannot downcast Dispatch to BlockSubscriber after tracing block".to_string(), - ))?; + ) + })?; let spans: Vec<_> = block_subscriber .spans .lock() diff --git a/client/tracing/src/lib.rs b/client/tracing/src/lib.rs index ff3723e0d1a9a..1ae695a725f3f 100644 --- a/client/tracing/src/lib.rs +++ b/client/tracing/src/lib.rs @@ -166,8 +166,7 @@ impl Visit for Values { } fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) { - self.string_values - .insert(field.name().to_string(), format!("{:?}", value).to_owned()); + self.string_values.insert(field.name().to_string(), format!("{:?}", value)); } } @@ -239,7 +238,7 @@ impl ProfilingLayer { /// or without: "pallet" in which case the level defaults to `trace`. /// wasm_tracing indicates whether to enable wasm traces pub fn new_with_handler(trace_handler: Box, targets: &str) -> Self { - let targets: Vec<_> = targets.split(',').map(|s| parse_target(s)).collect(); + let targets: Vec<_> = targets.split(',').map(parse_target).collect(); Self { targets, trace_handlers: vec![trace_handler] } } diff --git a/client/tracing/src/logging/directives.rs b/client/tracing/src/logging/directives.rs index fe7d6a780dbf0..b5fb373674ace 100644 --- a/client/tracing/src/logging/directives.rs +++ b/client/tracing/src/logging/directives.rs @@ -84,7 +84,7 @@ pub fn reload_filter() -> Result<(), String> { log::debug!(target: "tracing", "Reloading log filter with: {}", env_filter); FILTER_RELOAD_HANDLE .get() - .ok_or("No reload handle present".to_string())? + .ok_or("No reload handle present")? .reload(env_filter) .map_err(|e| format!("{}", e)) } diff --git a/client/tracing/src/logging/mod.rs b/client/tracing/src/logging/mod.rs index f5cdda4d35442..33c83dd87189e 100644 --- a/client/tracing/src/logging/mod.rs +++ b/client/tracing/src/logging/mod.rs @@ -110,7 +110,7 @@ where // Accept all valid directives and print invalid ones fn parse_user_directives(mut env_filter: EnvFilter, dirs: &str) -> Result { for dir in dirs.split(',') { - env_filter = env_filter.add_directive(parse_default_directive(&dir)?); + env_filter = env_filter.add_directive(parse_default_directive(dir)?); } Ok(env_filter) } @@ -299,32 +299,30 @@ impl LoggerBuilder { Ok(()) } + } else if self.log_reloading { + let subscriber = prepare_subscriber( + &self.directives, + None, + self.force_colors, + self.detailed_output, + |builder| enable_log_reloading!(builder), + )?; + + tracing::subscriber::set_global_default(subscriber)?; + + Ok(()) } else { - if self.log_reloading { - let subscriber = prepare_subscriber( - &self.directives, - None, - self.force_colors, - self.detailed_output, - |builder| enable_log_reloading!(builder), - )?; + let subscriber = prepare_subscriber( + &self.directives, + None, + self.force_colors, + self.detailed_output, + |builder| builder, + )?; - tracing::subscriber::set_global_default(subscriber)?; + tracing::subscriber::set_global_default(subscriber)?; - Ok(()) - } else { - let subscriber = prepare_subscriber( - &self.directives, - None, - self.force_colors, - self.detailed_output, - |builder| builder, - )?; - - tracing::subscriber::set_global_default(subscriber)?; - - Ok(()) - } + Ok(()) } } } diff --git a/client/transaction-pool/api/src/lib.rs b/client/transaction-pool/api/src/lib.rs index d7b3ddc996362..448578b327b03 100644 --- a/client/transaction-pool/api/src/lib.rs +++ b/client/transaction-pool/api/src/lib.rs @@ -350,7 +350,7 @@ impl OffchainSubmitTransaction for TP extrinsic ); - let result = self.submit_local(&at, extrinsic); + let result = self.submit_local(at, extrinsic); result.map(|_| ()).map_err(|e| { log::warn!( diff --git a/client/transaction-pool/src/api.rs b/client/transaction-pool/src/api.rs index 12909f313d100..4710c96b003cd 100644 --- a/client/transaction-pool/src/api.rs +++ b/client/transaction-pool/src/api.rs @@ -123,7 +123,7 @@ where type BodyFuture = Ready::Extrinsic>>>>; fn block_body(&self, id: &BlockId) -> Self::BodyFuture { - ready(self.client.block_body(&id).map_err(|e| error::Error::from(e))) + ready(self.client.block_body(id).map_err(error::Error::from)) } fn validate_transaction( @@ -134,7 +134,7 @@ where ) -> Self::ValidationFuture { let (tx, rx) = oneshot::channel(); let client = self.client.clone(); - let at = at.clone(); + let at = *at; let validation_pool = self.validation_pool.clone(); let metrics = self.metrics.clone(); @@ -212,7 +212,7 @@ where let runtime_api = client.runtime_api(); let api_version = sp_tracing::within_span! { sp_tracing::Level::TRACE, "check_version"; runtime_api - .api_version::>(&at) + .api_version::>(at) .map_err(|e| Error::RuntimeApi(e.to_string()))? .ok_or_else(|| Error::RuntimeApi( format!("Could not find `TaggedTransactionQueue` api for block `{:?}`.", at) @@ -229,7 +229,7 @@ where sp_tracing::Level::TRACE, "runtime::validate_transaction"; { if api_version >= 3 { - runtime_api.validate_transaction(&at, source, uxt, block_hash) + runtime_api.validate_transaction(at, source, uxt, block_hash) .map_err(|e| Error::RuntimeApi(e.to_string())) } else { let block_number = client.to_number(at) @@ -249,11 +249,11 @@ where if api_version == 2 { #[allow(deprecated)] // old validate_transaction - runtime_api.validate_transaction_before_version_3(&at, source, uxt) + runtime_api.validate_transaction_before_version_3(at, source, uxt) .map_err(|e| Error::RuntimeApi(e.to_string())) } else { #[allow(deprecated)] // old validate_transaction - runtime_api.validate_transaction_before_version_2(&at, uxt) + runtime_api.validate_transaction_before_version_2(at, uxt) .map_err(|e| Error::RuntimeApi(e.to_string())) } } diff --git a/client/transaction-pool/src/graph/pool.rs b/client/transaction-pool/src/graph/pool.rs index 39be43f82c8b9..618ba8ccf24d5 100644 --- a/client/transaction-pool/src/graph/pool.rs +++ b/client/transaction-pool/src/graph/pool.rs @@ -335,7 +335,7 @@ impl Pool { // And finally - submit reverified transactions back to the pool self.validated_pool.resubmit_pruned( - &at, + at, known_imported_hashes, pruned_hashes, reverified_transactions.into_iter().map(|(_, xt)| xt).collect(), diff --git a/client/transaction-pool/src/graph/ready.rs b/client/transaction-pool/src/graph/ready.rs index ebaa73f149240..220e69b13e7eb 100644 --- a/client/transaction-pool/src/graph/ready.rs +++ b/client/transaction-pool/src/graph/ready.rs @@ -300,7 +300,7 @@ impl ReadyTransactions { for tag in &tx.transaction.transaction.requires { if let Some(hash) = self.provided_tags.get(tag) { if let Some(tx) = ready.get_mut(hash) { - remove_item(&mut tx.unlocks, &hash); + remove_item(&mut tx.unlocks, hash); } } } @@ -351,7 +351,7 @@ impl ReadyTransactions { let mut ready = self.ready.write(); let mut find_previous = |tag| -> Option> { let prev_hash = self.provided_tags.get(tag)?; - let tx2 = ready.get_mut(&prev_hash)?; + let tx2 = ready.get_mut(prev_hash)?; remove_item(&mut tx2.unlocks, hash); // We eagerly prune previous transactions as well. // But it might not always be good. @@ -551,7 +551,7 @@ impl Iterator for BestIterator { continue } - let ready = match self.all.get(&hash).cloned() { + let ready = match self.all.get(hash).cloned() { Some(ready) => ready, // The transaction is not in all, maybe it was removed in the meantime? None => continue, diff --git a/client/transaction-pool/src/graph/validated_pool.rs b/client/transaction-pool/src/graph/validated_pool.rs index 4ddaf8de5c2bc..084b04842ee90 100644 --- a/client/transaction-pool/src/graph/validated_pool.rs +++ b/client/transaction-pool/src/graph/validated_pool.rs @@ -442,7 +442,7 @@ impl ValidatedPool { pub fn extrinsics_tags(&self, hashes: &[ExtrinsicHash]) -> Vec>> { self.pool .read() - .by_hashes(&hashes) + .by_hashes(hashes) .into_iter() .map(|existing_in_pool| { existing_in_pool.map(|transaction| transaction.provides.to_vec()) @@ -546,7 +546,7 @@ impl ValidatedPool { let now = Instant::now(); let to_remove = { self.ready() - .filter(|tx| self.rotator.ban_if_stale(&now, block_number, &tx)) + .filter(|tx| self.rotator.ban_if_stale(&now, block_number, tx)) .map(|tx| tx.hash) .collect::>() }; @@ -554,7 +554,7 @@ impl ValidatedPool { let p = self.pool.read(); let mut hashes = Vec::new(); for tx in p.futures() { - if self.rotator.ban_if_stale(&now, block_number, &tx) { + if self.rotator.ban_if_stale(&now, block_number, tx) { hashes.push(tx.hash); } } @@ -630,11 +630,7 @@ impl ValidatedPool { /// Returns a Vec of hashes and extrinsics in the future pool. pub fn futures(&self) -> Vec<(ExtrinsicHash, ExtrinsicFor)> { - self.pool - .read() - .futures() - .map(|tx| (tx.hash.clone(), tx.data.clone())) - .collect() + self.pool.read().futures().map(|tx| (tx.hash, tx.data.clone())).collect() } /// Returns pool status. @@ -663,9 +659,9 @@ where match *imported { base::Imported::Ready { ref promoted, ref failed, ref removed, ref hash } => { listener.ready(hash, None); - failed.into_iter().for_each(|f| listener.invalid(f)); - removed.into_iter().for_each(|r| listener.dropped(&r.hash, Some(hash))); - promoted.into_iter().for_each(|p| listener.ready(p, None)); + failed.iter().for_each(|f| listener.invalid(f)); + removed.iter().for_each(|r| listener.dropped(&r.hash, Some(hash))); + promoted.iter().for_each(|p| listener.ready(p, None)); }, base::Imported::Future { ref hash } => listener.future(hash), } diff --git a/client/transaction-pool/src/lib.rs b/client/transaction-pool/src/lib.rs index 9fd2ef1deef43..eb36d46538c1a 100644 --- a/client/transaction-pool/src/lib.rs +++ b/client/transaction-pool/src/lib.rs @@ -430,7 +430,7 @@ where let validated = ValidatedTransaction::valid_at( block_number.saturated_into::(), - hash.clone(), + hash, TransactionSource::Local, xt, bytes, @@ -538,7 +538,7 @@ async fn prune_known_txs_for_block>(); + let hashes = extrinsics.iter().map(|tx| pool.hash_of(tx)).collect::>(); log::trace!(target: "txpool", "Pruning transactions: {:?}", hashes); @@ -609,11 +609,11 @@ where if let Some(ref tree_route) = tree_route { for retracted in tree_route.retracted() { // notify txs awaiting finality that it has been retracted - pool.validated_pool().on_block_retracted(retracted.hash.clone()); + pool.validated_pool().on_block_retracted(retracted.hash); } future::join_all(tree_route.enacted().iter().map(|h| { - prune_known_txs_for_block(BlockId::Hash(h.hash.clone()), &*api, &*pool) + prune_known_txs_for_block(BlockId::Hash(h.hash), &*api, &*pool) })) .await .into_iter() @@ -622,7 +622,7 @@ where }) } - pruned_log.extend(prune_known_txs_for_block(id.clone(), &*api, &*pool).await); + pruned_log.extend(prune_known_txs_for_block(id, &*api, &*pool).await); metrics.report(|metrics| { metrics.block_transactions_pruned.inc_by(pruned_log.len() as u64) @@ -632,7 +632,7 @@ where let mut resubmit_transactions = Vec::new(); for retracted in tree_route.retracted() { - let hash = retracted.hash.clone(); + let hash = retracted.hash; let block_transactions = api .block_body(&BlockId::hash(hash)) @@ -649,7 +649,7 @@ where resubmit_transactions.extend(block_transactions.into_iter().filter( |tx| { - let tx_hash = pool.hash_of(&tx); + let tx_hash = pool.hash_of(tx); let contains = pruned_log.contains(&tx_hash); // need to count all transactions, not just filtered, here @@ -699,8 +699,7 @@ where }); if next_action.revalidate { - let hashes = - pool.validated_pool().ready().map(|tx| tx.hash.clone()).collect(); + let hashes = pool.validated_pool().ready().map(|tx| tx.hash).collect(); revalidation_queue.revalidate_later(block_number, hashes).await; revalidation_strategy.lock().clear(); diff --git a/client/transaction-pool/src/revalidation.rs b/client/transaction-pool/src/revalidation.rs index e3641008a7061..b4b4299240a32 100644 --- a/client/transaction-pool/src/revalidation.rs +++ b/client/transaction-pool/src/revalidation.rs @@ -92,7 +92,7 @@ async fn batch_revalidate( }, Ok(Ok(validity)) => { revalidated.insert( - ext_hash.clone(), + ext_hash, ValidatedTransaction::valid_at( at.saturated_into::(), ext_hash, @@ -194,14 +194,14 @@ impl RevalidationWorker { self.block_ordered .entry(block_number) .and_modify(|value| { - value.insert(ext_hash.clone()); + value.insert(ext_hash); }) .or_insert_with(|| { let mut bt = HashSet::new(); - bt.insert(ext_hash.clone()); + bt.insert(ext_hash); bt }); - self.members.insert(ext_hash.clone(), block_number); + self.members.insert(ext_hash, block_number); } } diff --git a/client/utils/src/pubsub.rs b/client/utils/src/pubsub.rs index c8e51e3494b97..ba6e9ddc6ca2a 100644 --- a/client/utils/src/pubsub.rs +++ b/client/utils/src/pubsub.rs @@ -195,8 +195,8 @@ impl Hub { let mut shared_borrowed = shared_locked.borrow_mut(); let (registry, sinks) = shared_borrowed.get_mut(); - let dispatch_result = registry.dispatch(trigger, |subs_id, item| { - if let Some(tx) = sinks.get_mut(&subs_id) { + registry.dispatch(trigger, |subs_id, item| { + if let Some(tx) = sinks.get_mut(subs_id) { if let Err(send_err) = tx.unbounded_send(item) { log::warn!("Sink with SubsID = {} failed to perform unbounded_send: {} ({} as Dispatch<{}, Item = {}>::dispatch(...))", subs_id, send_err, std::any::type_name::(), std::any::type_name::(), @@ -211,9 +211,7 @@ impl Hub { std::any::type_name::(), ); } - }); - - dispatch_result + }) } } diff --git a/frame/assets/src/benchmarking.rs b/frame/assets/src/benchmarking.rs index 33de190a8e36a..ca88899edf842 100644 --- a/frame/assets/src/benchmarking.rs +++ b/frame/assets/src/benchmarking.rs @@ -281,7 +281,7 @@ benchmarks_instance_pallet! { let target0 = T::Lookup::unlookup(account("target", 0, SEED)); let target1 = T::Lookup::unlookup(account("target", 1, SEED)); let target2 = T::Lookup::unlookup(account("target", 2, SEED)); - }: _(SystemOrigin::Signed(caller), Default::default(), target0.clone(), target1.clone(), target2.clone()) + }: _(SystemOrigin::Signed(caller), Default::default(), target0, target1, target2) verify { assert_last_event::(Event::TeamChanged { asset_id: Default::default(), @@ -346,7 +346,7 @@ benchmarks_instance_pallet! { let (caller, _) = create_default_asset::(true); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); let dummy = vec![0u8; T::StringLimit::get() as usize]; - let origin = SystemOrigin::Signed(caller.clone()).into(); + let origin = SystemOrigin::Signed(caller).into(); Assets::::set_metadata(origin, Default::default(), dummy.clone(), dummy, 12)?; let origin = T::ForceOrigin::successful_origin(); @@ -365,7 +365,7 @@ benchmarks_instance_pallet! { owner: caller_lookup.clone(), issuer: caller_lookup.clone(), admin: caller_lookup.clone(), - freezer: caller_lookup.clone(), + freezer: caller_lookup, min_balance: 100u32.into(), is_sufficient: true, is_frozen: false, @@ -398,7 +398,7 @@ benchmarks_instance_pallet! { let delegate_lookup = T::Lookup::unlookup(delegate.clone()); let amount = 100u32.into(); let origin = SystemOrigin::Signed(owner.clone()).into(); - Assets::::approve_transfer(origin, id, delegate_lookup.clone(), amount)?; + Assets::::approve_transfer(origin, id, delegate_lookup, amount)?; let dest: T::AccountId = account("dest", 0, SEED); let dest_lookup = T::Lookup::unlookup(dest.clone()); diff --git a/frame/assets/src/functions.rs b/frame/assets/src/functions.rs index a6abfd9e0409c..0f8e7096e80c1 100644 --- a/frame/assets/src/functions.rs +++ b/frame/assets/src/functions.rs @@ -204,7 +204,7 @@ impl, I: 'static> Pallet { who: &T::AccountId, keep_alive: bool, ) -> Result { - let details = Asset::::get(id).ok_or_else(|| Error::::Unknown)?; + let details = Asset::::get(id).ok_or(Error::::Unknown)?; ensure!(!details.is_frozen, Error::::Frozen); let account = Account::::get(id, who).ok_or(Error::::NoAccount)?; @@ -258,7 +258,7 @@ impl, I: 'static> Pallet { Ok(dust) => actual.saturating_add(dust), //< guaranteed by reducible_balance Err(e) => { debug_assert!(false, "passed from reducible_balance; qed"); - return Err(e.into()) + return Err(e) }, }; @@ -291,7 +291,7 @@ impl, I: 'static> Pallet { (true, Some(dust)) => (amount, Some(dust)), _ => (debit, None), }; - Self::can_increase(id, &dest, credit, false).into_result()?; + Self::can_increase(id, dest, credit, false).into_result()?; Ok((credit, maybe_burn)) } @@ -352,7 +352,7 @@ impl, I: 'static> Pallet { ) -> DispatchResult { Self::increase_balance(id, beneficiary, amount, |details| -> DispatchResult { if let Some(check_issuer) = maybe_check_issuer { - ensure!(&check_issuer == &details.issuer, Error::::NoPermission); + ensure!(check_issuer == details.issuer, Error::::NoPermission); } debug_assert!( T::Balance::max_value() - details.supply >= amount, @@ -433,7 +433,7 @@ impl, I: 'static> Pallet { let actual = Self::decrease_balance(id, target, amount, f, |actual, details| { // Check admin rights. if let Some(check_admin) = maybe_check_admin { - ensure!(&check_admin == &details.admin, Error::::NoPermission); + ensure!(check_admin == details.admin, Error::::NoPermission); } debug_assert!(details.supply >= actual, "checked in prep; qed"); @@ -471,7 +471,7 @@ impl, I: 'static> Pallet { let mut target_died: Option = None; Asset::::try_mutate(id, |maybe_details| -> DispatchResult { - let mut details = maybe_details.as_mut().ok_or(Error::::Unknown)?; + let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; check(actual, details)?; @@ -483,8 +483,7 @@ impl, I: 'static> Pallet { account.balance = account.balance.saturating_sub(actual); if account.balance < details.min_balance { debug_assert!(account.balance.is_zero(), "checked in prep; qed"); - target_died = - Some(Self::dead_account(target, &mut details, &account.reason, false)); + target_died = Some(Self::dead_account(target, details, &account.reason, false)); if let Some(Remove) = target_died { return Ok(()) } @@ -543,8 +542,8 @@ impl, I: 'static> Pallet { } // Figure out the debit and credit, together with side-effects. - let debit = Self::prep_debit(id, &source, amount, f.into())?; - let (credit, maybe_burn) = Self::prep_credit(id, &dest, amount, debit, f.burn_dust)?; + let debit = Self::prep_debit(id, source, amount, f.into())?; + let (credit, maybe_burn) = Self::prep_credit(id, dest, amount, debit, f.burn_dust)?; let mut source_account = Account::::get(id, &source).ok_or(Error::::NoAccount)?; @@ -555,7 +554,7 @@ impl, I: 'static> Pallet { // Check admin rights. if let Some(need_admin) = maybe_need_admin { - ensure!(&need_admin == &details.admin, Error::::NoPermission); + ensure!(need_admin == details.admin, Error::::NoPermission); } // Skip if source == dest @@ -590,7 +589,7 @@ impl, I: 'static> Pallet { *maybe_account = Some(AssetAccountOf:: { balance: credit, is_frozen: false, - reason: Self::new_account(&dest, details, None)?, + reason: Self::new_account(dest, details, None)?, extra: T::Extra::default(), }); }, @@ -602,7 +601,7 @@ impl, I: 'static> Pallet { if source_account.balance < details.min_balance { debug_assert!(source_account.balance.is_zero(), "checked in prep; qed"); source_died = - Some(Self::dead_account(&source, details, &source_account.reason, false)); + Some(Self::dead_account(source, details, &source_account.reason, false)); if let Some(Remove) = source_died { Account::::remove(id, &source); return Ok(()) @@ -746,7 +745,7 @@ impl, I: 'static> Pallet { }; let deposit_required = T::ApprovalDeposit::get(); if approved.deposit < deposit_required { - T::Currency::reserve(&owner, deposit_required - approved.deposit)?; + T::Currency::reserve(owner, deposit_required - approved.deposit)?; approved.deposit = deposit_required; } approved.amount = approved.amount.saturating_add(amount); @@ -789,10 +788,10 @@ impl, I: 'static> Pallet { approved.amount.checked_sub(&amount).ok_or(Error::::Unapproved)?; let f = TransferFlags { keep_alive: false, best_effort: false, burn_dust: false }; - owner_died = Self::transfer_and_die(id, &owner, &destination, amount, None, f)?.1; + owner_died = Self::transfer_and_die(id, owner, destination, amount, None, f)?.1; if remaining.is_zero() { - T::Currency::unreserve(&owner, approved.deposit); + T::Currency::unreserve(owner, approved.deposit); Asset::::mutate(id, |maybe_details| { if let Some(details) = maybe_details { details.approvals.saturating_dec(); diff --git a/frame/assets/src/impl_stored_map.rs b/frame/assets/src/impl_stored_map.rs index dfdcff37d1d69..a4669c776ed41 100644 --- a/frame/assets/src/impl_stored_map.rs +++ b/frame/assets/src/impl_stored_map.rs @@ -42,7 +42,7 @@ impl, I: 'static> StoredMap<(T::AssetId, T::AccountId), T::Extra> f if let Some(ref mut account) = maybe_account { account.extra = extra; } else { - Err(DispatchError::NoProviders)?; + return Err(DispatchError::NoProviders.into()) } } else { // They want to delete it. Let this pass if the item never existed anyway. diff --git a/frame/assets/src/lib.rs b/frame/assets/src/lib.rs index 9b0f23a0a1783..eb412de58fda6 100644 --- a/frame/assets/src/lib.rs +++ b/frame/assets/src/lib.rs @@ -788,7 +788,7 @@ pub mod pallet { let origin = ensure_signed(origin)?; let d = Asset::::get(id).ok_or(Error::::Unknown)?; - ensure!(&origin == &d.freezer, Error::::NoPermission); + ensure!(origin == d.freezer, Error::::NoPermission); let who = T::Lookup::lookup(who)?; Account::::try_mutate(id, &who, |maybe_account| -> DispatchResult { @@ -819,7 +819,7 @@ pub mod pallet { let origin = ensure_signed(origin)?; let details = Asset::::get(id).ok_or(Error::::Unknown)?; - ensure!(&origin == &details.admin, Error::::NoPermission); + ensure!(origin == details.admin, Error::::NoPermission); let who = T::Lookup::lookup(who)?; Account::::try_mutate(id, &who, |maybe_account| -> DispatchResult { @@ -849,7 +849,7 @@ pub mod pallet { Asset::::try_mutate(id, |maybe_details| { let d = maybe_details.as_mut().ok_or(Error::::Unknown)?; - ensure!(&origin == &d.freezer, Error::::NoPermission); + ensure!(origin == d.freezer, Error::::NoPermission); d.is_frozen = true; @@ -876,7 +876,7 @@ pub mod pallet { Asset::::try_mutate(id, |maybe_details| { let d = maybe_details.as_mut().ok_or(Error::::Unknown)?; - ensure!(&origin == &d.admin, Error::::NoPermission); + ensure!(origin == d.admin, Error::::NoPermission); d.is_frozen = false; @@ -906,7 +906,7 @@ pub mod pallet { Asset::::try_mutate(id, |maybe_details| { let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; - ensure!(&origin == &details.owner, Error::::NoPermission); + ensure!(origin == details.owner, Error::::NoPermission); if details.owner == owner { return Ok(()) } @@ -951,7 +951,7 @@ pub mod pallet { Asset::::try_mutate(id, |maybe_details| { let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; - ensure!(&origin == &details.owner, Error::::NoPermission); + ensure!(origin == details.owner, Error::::NoPermission); details.issuer = issuer.clone(); details.admin = admin.clone(); @@ -1009,7 +1009,7 @@ pub mod pallet { let origin = ensure_signed(origin)?; let d = Asset::::get(id).ok_or(Error::::Unknown)?; - ensure!(&origin == &d.owner, Error::::NoPermission); + ensure!(origin == d.owner, Error::::NoPermission); Metadata::::try_mutate_exists(id, |metadata| { let deposit = metadata.take().ok_or(Error::::Unknown)?.deposit; @@ -1241,7 +1241,7 @@ pub mod pallet { .map(|_| ()) .or_else(|origin| -> DispatchResult { let origin = ensure_signed(origin)?; - ensure!(&origin == &d.admin, Error::::NoPermission); + ensure!(origin == d.admin, Error::::NoPermission); Ok(()) })?; diff --git a/frame/assets/src/types.rs b/frame/assets/src/types.rs index 56034e59086b9..2e8a1f911fb0f 100644 --- a/frame/assets/src/types.rs +++ b/frame/assets/src/types.rs @@ -104,9 +104,9 @@ impl ExistenceReason { if let ExistenceReason::DepositHeld(deposit) = sp_std::mem::replace(self, ExistenceReason::DepositRefunded) { - return Some(deposit) + Some(deposit) } else { - return None + None } } } diff --git a/frame/atomic-swap/src/lib.rs b/frame/atomic-swap/src/lib.rs index 40429c226e649..b999aefaaa907 100644 --- a/frame/atomic-swap/src/lib.rs +++ b/frame/atomic-swap/src/lib.rs @@ -138,7 +138,7 @@ where C: ReservableCurrency, { fn reserve(&self, source: &AccountId) -> DispatchResult { - C::reserve(&source, self.value) + C::reserve(source, self.value) } fn claim(&self, source: &AccountId, target: &AccountId) -> bool { @@ -267,7 +267,7 @@ pub mod pallet { action, end_block: frame_system::Pallet::::block_number() + duration, }; - PendingSwaps::::insert(target.clone(), hashed_proof.clone(), swap.clone()); + PendingSwaps::::insert(target.clone(), hashed_proof, swap.clone()); Self::deposit_event(Event::NewSwap { account: target, proof: hashed_proof, swap }); @@ -303,7 +303,7 @@ pub mod pallet { let succeeded = swap.action.claim(&swap.source, &target); - PendingSwaps::::remove(target.clone(), hashed_proof.clone()); + PendingSwaps::::remove(target.clone(), hashed_proof); Self::deposit_event(Event::SwapClaimed { account: target, diff --git a/frame/aura/src/lib.rs b/frame/aura/src/lib.rs index 0f474770017d5..222360dc3a7d3 100644 --- a/frame/aura/src/lib.rs +++ b/frame/aura/src/lib.rs @@ -261,7 +261,7 @@ impl> FindAuthor let i = Inner::find_author(digests)?; let validators = >::authorities(); - validators.get(i as usize).map(|k| k.clone()) + validators.get(i as usize).cloned() } } diff --git a/frame/authorship/src/lib.rs b/frame/authorship/src/lib.rs index 6b72d6ac5d28b..561db20849c2f 100644 --- a/frame/authorship/src/lib.rs +++ b/frame/authorship/src/lib.rs @@ -106,7 +106,7 @@ where let number = header.number(); if let Some(ref author) = author { - if !acc.insert((number.clone(), author.clone())) { + if !acc.insert((*number, author.clone())) { return Err("more than one uncle per number per author included") } } @@ -225,7 +225,7 @@ pub mod pallet { ensure!(new_uncles.len() <= MAX_UNCLES, Error::::TooManyUncles); if >::get() { - Err(Error::::UnclesAlreadySet)? + return Err(Error::::UnclesAlreadySet.into()) } >::put(true); @@ -334,7 +334,7 @@ impl Pallet { let hash = uncle.hash(); if let Some(author) = maybe_author.clone() { - T::EventHandler::note_uncle(author, now - uncle.number().clone()); + T::EventHandler::note_uncle(author, now - *uncle.number()); } uncles.push(UncleEntryItem::Uncle(hash, maybe_author)); } @@ -368,7 +368,7 @@ impl Pallet { } { - let parent_number = uncle.number().clone() - One::one(); + let parent_number = *uncle.number() - One::one(); let parent_hash = >::block_hash(&parent_number); if &parent_hash != uncle.parent_hash() { return Err(Error::::InvalidUncleParent.into()) @@ -387,7 +387,7 @@ impl Pallet { } // check uncle validity. - T::FilterUncle::filter_uncle(&uncle, accumulator).map_err(|e| Into::into(e)) + T::FilterUncle::filter_uncle(uncle, accumulator).map_err(Into::into) } fn prune_old_uncles(minimum_height: T::BlockNumber) { diff --git a/frame/babe/src/lib.rs b/frame/babe/src/lib.rs index 87ae762707ccd..2709316d87150 100644 --- a/frame/babe/src/lib.rs +++ b/frame/babe/src/lib.rs @@ -438,7 +438,7 @@ impl FindAuthor for Pallet { } } - return None + None } } @@ -641,7 +641,7 @@ impl Pallet { let segment_idx = segment_idx + 1; let bounded_randomness = BoundedVec::<_, ConstU32>::try_from(vec![ - randomness.clone(), + *randomness, ]) .expect("UNDER_CONSTRUCTION_SEGMENT_LENGTH >= 1"); UnderConstruction::::insert(&segment_idx, bounded_randomness); @@ -726,7 +726,7 @@ impl Pallet { vrf_output.0.attach_input_hash(&pubkey, transcript).ok() }) - .map(|inout| inout.make_bytes(&sp_consensus_babe::BABE_VRF_INOUT_CONTEXT)) + .map(|inout| inout.make_bytes(sp_consensus_babe::BABE_VRF_INOUT_CONTEXT)) }) }); diff --git a/frame/bags-list/fuzzer/src/main.rs b/frame/bags-list/fuzzer/src/main.rs index 6f538eb28e7e4..387c266d32256 100644 --- a/frame/bags-list/fuzzer/src/main.rs +++ b/frame/bags-list/fuzzer/src/main.rs @@ -62,7 +62,7 @@ fn main() { match action { Action::Insert => { - if BagsList::on_insert(id.clone(), vote_weight).is_err() { + if BagsList::on_insert(id, vote_weight).is_err() { // this was a duplicate id, which is ok. We can just update it. BagsList::on_update(&id, vote_weight); } diff --git a/frame/bags-list/remote-tests/src/lib.rs b/frame/bags-list/remote-tests/src/lib.rs index caf7a2a547e09..458064cf79f57 100644 --- a/frame/bags-list/remote-tests/src/lib.rs +++ b/frame/bags-list/remote-tests/src/lib.rs @@ -21,7 +21,7 @@ use frame_election_provider_support::ScoreProvider; use sp_std::prelude::*; /// A common log target to use. -pub const LOG_TARGET: &'static str = "runtime::bags-list::remote-tests"; +pub const LOG_TARGET: &str = "runtime::bags-list::remote-tests"; pub mod migration; pub mod sanity_check; diff --git a/frame/bags-list/src/benchmarks.rs b/frame/bags-list/src/benchmarks.rs index 1a901c4d9d5a1..dba0c9ee1e623 100644 --- a/frame/bags-list/src/benchmarks.rs +++ b/frame/bags-list/src/benchmarks.rs @@ -133,7 +133,7 @@ frame_benchmarking::benchmarks! { List::::get_bags(), vec![ (origin_bag_thresh, vec![origin_head.clone()]), - (dest_bag_thresh, vec![dest_head.clone(), origin_tail.clone()]) + (dest_bag_thresh, vec![dest_head.clone(), origin_tail]) ] ); } diff --git a/frame/bags-list/src/lib.rs b/frame/bags-list/src/lib.rs index 94553433e230d..53ccd5e4c19f1 100644 --- a/frame/bags-list/src/lib.rs +++ b/frame/bags-list/src/lib.rs @@ -74,7 +74,7 @@ pub use list::{notional_bag_for, Bag, List, ListError, Node}; pub use pallet::*; pub use weights::WeightInfo; -pub(crate) const LOG_TARGET: &'static str = "runtime::bags_list"; +pub(crate) const LOG_TARGET: &str = "runtime::bags_list"; // syntactic sugar for logging. #[macro_export] @@ -254,7 +254,7 @@ impl, I: 'static> Pallet { pub fn do_rebag(account: &T::AccountId, new_weight: T::Score) -> Option<(T::Score, T::Score)> { // if no voter at that node, don't do anything. // the caller just wasted the fee to call this. - let maybe_movement = list::Node::::get(&account) + let maybe_movement = list::Node::::get(account) .and_then(|node| List::update_position_for(node, new_weight)); if let Some((from, to)) = maybe_movement { Self::deposit_event(Event::::Rebagged { who: account.clone(), from, to }); diff --git a/frame/bags-list/src/list/mod.rs b/frame/bags-list/src/list/mod.rs index db8c06a38d674..4de70569253da 100644 --- a/frame/bags-list/src/list/mod.rs +++ b/frame/bags-list/src/list/mod.rs @@ -59,7 +59,7 @@ mod tests; pub fn notional_bag_for, I: 'static>(score: T::Score) -> T::Score { let thresholds = T::BagThresholds::get(); let idx = thresholds.partition_point(|&threshold| score > threshold); - thresholds.get(idx).copied().unwrap_or(T::Score::max_value()) + thresholds.get(idx).copied().unwrap_or_else(T::Score::max_value) } /// The **ONLY** entry point of this module. All operations to the bags-list should happen through @@ -163,7 +163,7 @@ impl, I: 'static> List { let affected_bag = { // this recreates `notional_bag_for` logic, but with the old thresholds. let idx = old_thresholds.partition_point(|&threshold| inserted_bag > threshold); - old_thresholds.get(idx).copied().unwrap_or(T::Score::max_value()) + old_thresholds.get(idx).copied().unwrap_or_else(T::Score::max_value) }; if !affected_old_bags.insert(affected_bag) { // If the previous threshold list was [10, 20], and we insert [3, 5], then there's @@ -420,24 +420,24 @@ impl, I: 'static> List { use crate::pallet; use frame_support::ensure; - let lighter_node = Node::::get(&lighter_id).ok_or(pallet::Error::IdNotFound)?; - let heavier_node = Node::::get(&heavier_id).ok_or(pallet::Error::IdNotFound)?; + let lighter_node = Node::::get(lighter_id).ok_or(pallet::Error::IdNotFound)?; + let heavier_node = Node::::get(heavier_id).ok_or(pallet::Error::IdNotFound)?; ensure!(lighter_node.bag_upper == heavier_node.bag_upper, pallet::Error::NotInSameBag); // this is the most expensive check, so we do it last. ensure!( - T::ScoreProvider::score(&heavier_id) > T::ScoreProvider::score(&lighter_id), + T::ScoreProvider::score(heavier_id) > T::ScoreProvider::score(lighter_id), pallet::Error::NotHeavier ); // remove the heavier node from this list. Note that this removes the node from storage and // decrements the node counter. - Self::remove(&heavier_id); + Self::remove(heavier_id); // re-fetch `lighter_node` from storage since it may have been updated when `heavier_node` // was removed. - let lighter_node = Node::::get(&lighter_id).ok_or_else(|| { + let lighter_node = Node::::get(lighter_id).ok_or_else(|| { debug_assert!(false, "id that should exist cannot be found"); crate::log!(warn, "id that should exist cannot be found"); pallet::Error::IdNotFound @@ -527,7 +527,7 @@ impl, I: 'static> List { thresholds.into_iter().filter_map(|t| Bag::::get(t)) }; - let _ = active_bags.clone().map(|b| b.sanity_check()).collect::>()?; + let _ = active_bags.clone().try_for_each(|b| b.sanity_check())?; let nodes_in_bags_count = active_bags.clone().fold(0u32, |acc, cur| acc + cur.iter().count() as u32); @@ -708,7 +708,7 @@ impl, I: 'static> Bag { // the first insertion into the bag. In this case, both head and tail should point to the // same node. if self.head.is_none() { - self.head = Some(id.clone()); + self.head = Some(id); debug_assert!(self.iter().count() == 1); } } diff --git a/frame/bags-list/src/mock.rs b/frame/bags-list/src/mock.rs index fce1431054174..961bf2b83552f 100644 --- a/frame/bags-list/src/mock.rs +++ b/frame/bags-list/src/mock.rs @@ -42,7 +42,7 @@ impl frame_election_provider_support::ScoreProvider for StakingMock { #[cfg(any(feature = "runtime-benchmarks", test))] fn set_score_of(id: &AccountId, weight: Self::Score) { - NEXT_VOTE_WEIGHT_MAP.with(|m| m.borrow_mut().insert(id.clone(), weight)); + NEXT_VOTE_WEIGHT_MAP.with(|m| m.borrow_mut().insert(*id, weight)); } } diff --git a/frame/balances/src/lib.rs b/frame/balances/src/lib.rs index 5ba6c4dfa1f69..7060486f8584d 100644 --- a/frame/balances/src/lib.rs +++ b/frame/balances/src/lib.rs @@ -775,7 +775,7 @@ impl, I: 'static> Pallet { /// Get both the free and reserved balances of an account. fn account(who: &T::AccountId) -> AccountData { - T::AccountStore::get(&who) + T::AccountStore::get(who) } /// Handles any steps needed after mutating an account. @@ -988,17 +988,15 @@ impl, I: 'static> Pallet { } } else { Locks::::insert(who, bounded_locks); - if !existed { - if system::Pallet::::inc_consumers_without_limit(who).is_err() { - // No providers for the locks. This is impossible under normal circumstances - // since the funds that are under the lock will themselves be stored in the - // account and therefore will need a reference. - log::warn!( - target: "runtime::balances", - "Warning: Attempt to introduce lock consumer reference, yet no providers. \ - This is unexpected but should be safe." - ); - } + if !existed && system::Pallet::::inc_consumers_without_limit(who).is_err() { + // No providers for the locks. This is impossible under normal circumstances + // since the funds that are under the lock will themselves be stored in the + // account and therefore will need a reference. + log::warn!( + target: "runtime::balances", + "Warning: Attempt to introduce lock consumer reference, yet no providers. \ + This is unexpected but should be safe." + ); } } } @@ -1107,7 +1105,7 @@ impl, I: 'static> fungible::Mutate for Pallet { return Ok(()) } Self::try_mutate_account(who, |account, _is_new| -> DispatchResult { - Self::deposit_consequence(who, amount, &account, true).into_result()?; + Self::deposit_consequence(who, amount, account, true).into_result()?; account.free += amount; Ok(()) })?; @@ -1126,7 +1124,7 @@ impl, I: 'static> fungible::Mutate for Pallet { let actual = Self::try_mutate_account( who, |account, _is_new| -> Result { - let extra = Self::withdraw_consequence(who, amount, &account).into_result()?; + let extra = Self::withdraw_consequence(who, amount, account).into_result()?; let actual = amount + extra; account.free -= actual; Ok(actual) @@ -1214,7 +1212,7 @@ impl, I: 'static> fungible::MutateHold for Pallet::InsufficientBalance); // ^^^ Guaranteed to be <= amount and <= a.reserved a.free = new_free; - a.reserved = a.reserved.saturating_sub(actual.clone()); + a.reserved = a.reserved.saturating_sub(actual); Ok(actual) }) } @@ -1318,7 +1316,7 @@ mod imbalances { } } fn peek(&self) -> T::Balance { - self.0.clone() + self.0 } } @@ -1377,7 +1375,7 @@ mod imbalances { } } fn peek(&self) -> T::Balance { - self.0.clone() + self.0 } } @@ -1560,7 +1558,7 @@ where if value.is_zero() { return (NegativeImbalance::zero(), Zero::zero()) } - if Self::total_balance(&who).is_zero() { + if Self::total_balance(who).is_zero() { return (NegativeImbalance::zero(), value) } @@ -1656,7 +1654,7 @@ where return Self::PositiveImbalance::zero() } - let r = Self::try_mutate_account( + Self::try_mutate_account( who, |account, is_new| -> Result { let ed = T::ExistentialDeposit::get(); @@ -1673,9 +1671,7 @@ where Ok(PositiveImbalance::new(value)) }, ) - .unwrap_or_else(|_| Self::PositiveImbalance::zero()); - - r + .unwrap_or_else(|_| Self::PositiveImbalance::zero()) } /// Withdraw some free balance from an account, respecting existence requirements. @@ -1785,7 +1781,7 @@ where account.free.checked_sub(&value).ok_or(Error::::InsufficientBalance)?; account.reserved = account.reserved.checked_add(&value).ok_or(ArithmeticError::Overflow)?; - Self::ensure_can_withdraw(&who, value.clone(), WithdrawReasons::RESERVE, account.free) + Self::ensure_can_withdraw(&who, value, WithdrawReasons::RESERVE, account.free) })?; Self::deposit_event(Event::Reserved { who: who.clone(), amount: value }); @@ -1799,7 +1795,7 @@ where if value.is_zero() { return Zero::zero() } - if Self::total_balance(&who).is_zero() { + if Self::total_balance(who).is_zero() { return value } @@ -1820,7 +1816,7 @@ where }, }; - Self::deposit_event(Event::Unreserved { who: who.clone(), amount: actual.clone() }); + Self::deposit_event(Event::Unreserved { who: who.clone(), amount: actual }); value - actual } @@ -1835,7 +1831,7 @@ where if value.is_zero() { return (NegativeImbalance::zero(), Zero::zero()) } - if Self::total_balance(&who).is_zero() { + if Self::total_balance(who).is_zero() { return (NegativeImbalance::zero(), value) } @@ -1925,7 +1921,7 @@ where }, Err(index) => { reserves - .try_insert(index, ReserveData { id: id.clone(), amount: value }) + .try_insert(index, ReserveData { id: *id, amount: value }) .map_err(|_| Error::::TooManyReserves)?; }, }; @@ -2086,7 +2082,7 @@ where reserves .try_insert( index, - ReserveData { id: id.clone(), amount: actual }, + ReserveData { id: *id, amount: actual }, ) .map_err(|_| Error::::TooManyReserves)?; diff --git a/frame/benchmarking/src/analysis.rs b/frame/benchmarking/src/analysis.rs index 52baec80e62ed..c19af781234f8 100644 --- a/frame/benchmarking/src/analysis.rs +++ b/frame/benchmarking/src/analysis.rs @@ -91,7 +91,7 @@ impl Analysis { }) .collect(); - values.sort(); + values.sort_unstable(); let mid = values.len() / 2; Some(Self { @@ -216,7 +216,7 @@ impl Analysis { } for (_, rs) in results.iter_mut() { - rs.sort(); + rs.sort_unstable(); let ql = rs.len() / 4; *rs = rs[ql..rs.len() - ql].to_vec(); } @@ -255,7 +255,7 @@ impl Analysis { .iter() .map(|(p, vs)| { // Avoid divide by zero - if vs.len() == 0 { + if vs.is_empty() { return (p.clone(), 0, 0) } let total = vs.iter().fold(0u128, |acc, v| acc + *v); diff --git a/frame/bounties/src/benchmarking.rs b/frame/bounties/src/benchmarking.rs index 04adacf6e4aec..912e461501be5 100644 --- a/frame/bounties/src/benchmarking.rs +++ b/frame/bounties/src/benchmarking.rs @@ -99,7 +99,7 @@ benchmarks! { propose_curator { setup_pot_account::(); let (caller, curator, fee, value, reason) = setup_bounty::(0, T::MaximumReasonLength::get()); - let curator_lookup = T::Lookup::unlookup(curator.clone()); + let curator_lookup = T::Lookup::unlookup(curator); Bounties::::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?; let bounty_id = BountyCount::::get() - 1; Bounties::::approve_bounty(RawOrigin::Root.into(), bounty_id)?; diff --git a/frame/bounties/src/lib.rs b/frame/bounties/src/lib.rs index 886c758bbe42e..dfeef36a1fae0 100644 --- a/frame/bounties/src/lib.rs +++ b/frame/bounties/src/lib.rs @@ -474,7 +474,7 @@ pub mod pallet { // Else this is the curator, willingly giving up their role. // Give back their deposit. let err_amount = - T::Currency::unreserve(&curator, bounty.curator_deposit); + T::Currency::unreserve(curator, bounty.curator_deposit); debug_assert!(err_amount.is_zero()); bounty.curator_deposit = Zero::zero(); // Continue to change bounty status below... @@ -706,7 +706,7 @@ pub mod pallet { BountyStatus::Active { curator, .. } => { // Cancelled by council, refund deposit of the working curator. let err_amount = - T::Currency::unreserve(&curator, bounty.curator_deposit); + T::Currency::unreserve(curator, bounty.curator_deposit); debug_assert!(err_amount.is_zero()); // Then execute removal of the bounty below. }, diff --git a/frame/child-bounties/src/benchmarking.rs b/frame/child-bounties/src/benchmarking.rs index d9edf14bbc9d7..dcb54361fac89 100644 --- a/frame/child-bounties/src/benchmarking.rs +++ b/frame/child-bounties/src/benchmarking.rs @@ -110,7 +110,7 @@ fn activate_bounty( Bounties::::propose_curator( RawOrigin::Root.into(), child_bounty_setup.bounty_id, - curator_lookup.clone(), + curator_lookup, child_bounty_setup.fee, )?; Bounties::::accept_curator( @@ -141,7 +141,7 @@ fn activate_child_bounty( RawOrigin::Signed(bounty_setup.curator.clone()).into(), bounty_setup.bounty_id, bounty_setup.child_bounty_id, - child_curator_lookup.clone(), + child_curator_lookup, bounty_setup.child_bounty_fee, )?; @@ -211,7 +211,7 @@ benchmarks! { RawOrigin::Signed(bounty_setup.curator.clone()).into(), bounty_setup.bounty_id, bounty_setup.child_bounty_id, - child_curator_lookup.clone(), + child_curator_lookup, bounty_setup.child_bounty_fee, )?; }: _(RawOrigin::Signed(bounty_setup.child_curator), bounty_setup.bounty_id, @@ -246,7 +246,7 @@ benchmarks! { setup_pot_account::(); let bounty_setup = activate_child_bounty::(0, T::MaximumReasonLength::get())?; let beneficiary_account: T::AccountId = account("beneficiary", 0, SEED); - let beneficiary = T::Lookup::unlookup(beneficiary_account.clone()); + let beneficiary = T::Lookup::unlookup(beneficiary_account); ChildBounties::::award_child_bounty( RawOrigin::Signed(bounty_setup.child_curator.clone()).into(), diff --git a/frame/child-bounties/src/lib.rs b/frame/child-bounties/src/lib.rs index 0bbd4512443d7..a8496d4e7af91 100644 --- a/frame/child-bounties/src/lib.rs +++ b/frame/child-bounties/src/lib.rs @@ -512,7 +512,7 @@ pub mod pallet { Some(sender) if sender == *curator => { // This is the child-bounty curator, willingly giving up their // role. Give back their deposit. - T::Currency::unreserve(&curator, child_bounty.curator_deposit); + T::Currency::unreserve(curator, child_bounty.curator_deposit); // Reset curator deposit. child_bounty.curator_deposit = Zero::zero(); // Continue to change bounty status below. @@ -673,13 +673,13 @@ pub mod pallet { // Unreserve the curator deposit. Should not fail // because the deposit is always reserved when curator is // assigned. - let _ = T::Currency::unreserve(&curator, child_bounty.curator_deposit); + let _ = T::Currency::unreserve(curator, child_bounty.curator_deposit); // Make payout to child-bounty curator. // Should not fail because curator fee is always less than bounty value. let fee_transfer_result = T::Currency::transfer( &child_bounty_account, - &curator, + curator, curator_fee, AllowDeath, ); diff --git a/frame/collective/src/benchmarking.rs b/frame/collective/src/benchmarking.rs index d5d0fc5f263e2..076afcd203030 100644 --- a/frame/collective/src/benchmarking.rs +++ b/frame/collective/src/benchmarking.rs @@ -250,7 +250,7 @@ benchmarks_instance_pallet! { let approve = true; Collective::::vote( SystemOrigin::Signed(voter.clone()).into(), - last_hash.clone(), + last_hash, index, approve, )?; @@ -259,7 +259,7 @@ benchmarks_instance_pallet! { let approve = true; Collective::::vote( SystemOrigin::Signed(voter.clone()).into(), - last_hash.clone(), + last_hash, index, approve, )?; @@ -272,7 +272,7 @@ benchmarks_instance_pallet! { // Whitelist voter account from further DB operations. let voter_key = frame_system::Account::::hashed_key_for(&voter); frame_benchmarking::benchmarking::add_to_whitelist(voter_key.into()); - }: _(SystemOrigin::Signed(voter), last_hash.clone(), index, approve) + }: _(SystemOrigin::Signed(voter), last_hash, index, approve) verify { // All proposals exist and the last proposal has just been updated. assert_eq!(Collective::::proposals().len(), p as usize); @@ -327,7 +327,7 @@ benchmarks_instance_pallet! { let approve = true; Collective::::vote( SystemOrigin::Signed(voter.clone()).into(), - last_hash.clone(), + last_hash, index, approve, )?; @@ -336,7 +336,7 @@ benchmarks_instance_pallet! { let approve = true; Collective::::vote( SystemOrigin::Signed(voter.clone()).into(), - last_hash.clone(), + last_hash, index, approve, )?; @@ -347,7 +347,7 @@ benchmarks_instance_pallet! { let approve = false; Collective::::vote( SystemOrigin::Signed(voter.clone()).into(), - last_hash.clone(), + last_hash, index, approve, )?; @@ -355,7 +355,7 @@ benchmarks_instance_pallet! { // Whitelist voter account from further DB operations. let voter_key = frame_system::Account::::hashed_key_for(&voter); frame_benchmarking::benchmarking::add_to_whitelist(voter_key.into()); - }: close(SystemOrigin::Signed(voter), last_hash.clone(), index, Weight::max_value(), bytes_in_storage) + }: close(SystemOrigin::Signed(voter), last_hash, index, Weight::max_value(), bytes_in_storage) verify { // The last proposal is removed. assert_eq!(Collective::::proposals().len(), (p - 1) as usize); @@ -400,7 +400,7 @@ benchmarks_instance_pallet! { // Caller switches vote to nay on their own proposal, allowing them to be the deciding approval vote Collective::::vote( SystemOrigin::Signed(caller.clone()).into(), - last_hash.clone(), + last_hash, p - 1, false, )?; @@ -411,7 +411,7 @@ benchmarks_instance_pallet! { let approve = false; Collective::::vote( SystemOrigin::Signed(voter.clone()).into(), - last_hash.clone(), + last_hash, p - 1, approve, )?; @@ -420,7 +420,7 @@ benchmarks_instance_pallet! { // Member zero is the first aye Collective::::vote( SystemOrigin::Signed(members[0].clone()).into(), - last_hash.clone(), + last_hash, p - 1, true, )?; @@ -432,11 +432,11 @@ benchmarks_instance_pallet! { let approve = true; Collective::::vote( SystemOrigin::Signed(caller.clone()).into(), - last_hash.clone(), + last_hash, index, approve, )?; - }: close(SystemOrigin::Signed(caller), last_hash.clone(), index, Weight::max_value(), bytes_in_storage) + }: close(SystemOrigin::Signed(caller), last_hash, index, Weight::max_value(), bytes_in_storage) verify { // The last proposal is removed. assert_eq!(Collective::::proposals().len(), (p - 1) as usize); @@ -493,7 +493,7 @@ benchmarks_instance_pallet! { let approve = true; Collective::::vote( SystemOrigin::Signed(voter.clone()).into(), - last_hash.clone(), + last_hash, index, approve, )?; @@ -502,7 +502,7 @@ benchmarks_instance_pallet! { // caller is prime, prime votes nay Collective::::vote( SystemOrigin::Signed(caller.clone()).into(), - last_hash.clone(), + last_hash, index, false, )?; @@ -560,7 +560,7 @@ benchmarks_instance_pallet! { // The prime member votes aye, so abstentions default to aye. Collective::::vote( SystemOrigin::Signed(caller.clone()).into(), - last_hash.clone(), + last_hash, p - 1, true // Vote aye. )?; @@ -572,7 +572,7 @@ benchmarks_instance_pallet! { let approve = false; Collective::::vote( SystemOrigin::Signed(voter.clone()).into(), - last_hash.clone(), + last_hash, p - 1, approve )?; diff --git a/frame/contracts/src/benchmarking/code.rs b/frame/contracts/src/benchmarking/code.rs index 2544fd6b7f922..5f9b43d3e3b7a 100644 --- a/frame/contracts/src/benchmarking/code.rs +++ b/frame/contracts/src/benchmarking/code.rs @@ -277,15 +277,14 @@ where } module }; - let limits = module + let limits = *module .import_section() .unwrap() .entries() .iter() .find_map(|e| if let External::Memory(mem) = e.external() { Some(mem) } else { None }) .unwrap() - .limits() - .clone(); + .limits(); let code = module.to_bytes().unwrap(); let hash = T::Hashing::hash(&code); let memory = @@ -512,16 +511,10 @@ pub mod body { DynInstr::RandomI32(low, high) => { vec![Instruction::I32Const(rng.gen_range(*low..*high))] }, - DynInstr::RandomI32Repeated(num) => (&mut rng) - .sample_iter(Standard) - .take(*num) - .map(|val| Instruction::I32Const(val)) - .collect(), - DynInstr::RandomI64Repeated(num) => (&mut rng) - .sample_iter(Standard) - .take(*num) - .map(|val| Instruction::I64Const(val)) - .collect(), + DynInstr::RandomI32Repeated(num) => + (&mut rng).sample_iter(Standard).take(*num).map(Instruction::I32Const).collect(), + DynInstr::RandomI64Repeated(num) => + (&mut rng).sample_iter(Standard).take(*num).map(Instruction::I64Const).collect(), DynInstr::RandomGetLocal(low, high) => { vec![Instruction::GetLocal(rng.gen_range(*low..*high))] }, diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs index e67436fbc9d37..84f4cb6083e5f 100644 --- a/frame/contracts/src/benchmarking/mod.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -138,7 +138,7 @@ where Storage::::write(&info.trie_id, &item.0, Some(item.1.clone()), None, false) .map_err(|_| "Failed to write storage to restoration dest")?; } - >::insert(&self.account_id, info.clone()); + >::insert(&self.account_id, info); Ok(()) } @@ -253,7 +253,7 @@ benchmarks! { )?; let value = T::Currency::minimum_balance(); let origin = RawOrigin::Signed(instance.caller.clone()); - let callee = instance.addr.clone(); + let callee = instance.addr; }: call(origin, callee, value, Weight::MAX, None, vec![]) // This constructs a contract that is maximal expensive to instrument. @@ -1067,7 +1067,7 @@ benchmarks! { ) .map_err(|_| "Failed to write to storage during setup.")?; } - >::insert(&instance.account_id, info.clone()); + >::insert(&instance.account_id, info); let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) @@ -1164,7 +1164,7 @@ benchmarks! { ) .map_err(|_| "Failed to write to storage during setup.")?; } - >::insert(&instance.account_id, info.clone()); + >::insert(&instance.account_id, info); let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) @@ -1216,7 +1216,7 @@ benchmarks! { ) .map_err(|_| "Failed to write to storage during setup.")?; } - >::insert(&instance.account_id, info.clone()); + >::insert(&instance.account_id, info); let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) @@ -1263,7 +1263,7 @@ benchmarks! { ) .map_err(|_| "Failed to write to storage during setup.")?; } - >::insert(&instance.account_id, info.clone()); + >::insert(&instance.account_id, info); let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) @@ -1308,7 +1308,7 @@ benchmarks! { ) .map_err(|_| "Failed to write to storage during setup.")?; } - >::insert(&instance.account_id, info.clone()); + >::insert(&instance.account_id, info); let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) @@ -1360,7 +1360,7 @@ benchmarks! { ) .map_err(|_| "Failed to write to storage during setup.")?; } - >::insert(&instance.account_id, info.clone()); + >::insert(&instance.account_id, info); let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) @@ -1412,7 +1412,7 @@ benchmarks! { ) .map_err(|_| "Failed to write to storage during setup.")?; } - >::insert(&instance.account_id, info.clone()); + >::insert(&instance.account_id, info); let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) @@ -1577,7 +1577,7 @@ benchmarks! { }); let instance = Contract::::new(code, vec![])?; let callee = instance.addr.clone(); - let origin = RawOrigin::Signed(instance.caller.clone()); + let origin = RawOrigin::Signed(instance.caller); }: call(origin, callee, 0u32.into(), Weight::MAX, None, vec![]) seal_call_per_transfer_clone_kb { @@ -1739,7 +1739,7 @@ benchmarks! { .collect::>(); for addr in &addresses { - if let Some(_) = ContractInfoOf::::get(&addr) { + if ContractInfoOf::::get(&addr).is_some() { return Err("Expected that contract does not exist at this point.".into()); } } @@ -1747,7 +1747,7 @@ benchmarks! { verify { for addr in &addresses { ContractInfoOf::::get(&addr) - .ok_or_else(|| "Contract should have been instantiated")?; + .ok_or("Contract should have been instantiated")?; } } @@ -1755,7 +1755,7 @@ benchmarks! { let t in 0 .. 1; let s in 0 .. (code::max_pages::() - 1) * 64; let callee_code = WasmModule::::dummy(); - let hash = callee_code.hash.clone(); + let hash = callee_code.hash; let hash_bytes = callee_code.hash.encode(); let hash_len = hash_bytes.len(); Contracts::::store_code_raw(callee_code.code, whitelisted_caller())?; diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index 54a5223b53d21..e3ff4788791a5 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -610,7 +610,7 @@ where debug_message: Option<&'a mut Vec>, ) -> Result<(Self, E), ExecError> { let (first_frame, executable, nonce) = - Self::new_frame(args, value, gas_meter, storage_meter, 0, &schedule)?; + Self::new_frame(args, value, gas_meter, storage_meter, 0, schedule)?; let stack = Self { origin, schedule, @@ -660,13 +660,10 @@ where }, FrameArgs::Instantiate { sender, nonce, executable, salt } => { let account_id = - >::contract_address(&sender, executable.code_hash(), &salt); + >::contract_address(&sender, executable.code_hash(), salt); let trie_id = Storage::::generate_trie_id(&account_id, nonce); - let contract = Storage::::new_contract( - &account_id, - trie_id, - executable.code_hash().clone(), - )?; + let contract = + Storage::::new_contract(&account_id, trie_id, *executable.code_hash())?; ( account_id, contract, @@ -742,7 +739,7 @@ where top_frame.nested_storage.charge_instantiate( &self.origin, &top_frame.account_id, - &mut top_frame.contract_info.get(&top_frame.account_id), + top_frame.contract_info.get(&top_frame.account_id), )?; } @@ -1020,11 +1017,11 @@ where code_hash: CodeHash, input_data: Vec, ) -> Result { - let executable = E::from_storage(code_hash, &self.schedule, self.gas_meter())?; + let executable = E::from_storage(code_hash, self.schedule, self.gas_meter())?; let top_frame = self.top_frame_mut(); let contract_info = top_frame.contract_info().clone(); let account_id = top_frame.account_id.clone(); - let value = top_frame.value_transferred.clone(); + let value = top_frame.value_transferred; let executable = self.push_frame( FrameArgs::Call { dest: account_id, @@ -1045,7 +1042,7 @@ where input_data: Vec, salt: &[u8], ) -> Result<(AccountIdOf, ExecReturnValue), ExecError> { - let executable = E::from_storage(code_hash, &self.schedule, self.gas_meter())?; + let executable = E::from_storage(code_hash, self.schedule, self.gas_meter())?; let nonce = self.next_nonce(); let executable = self.push_frame( FrameArgs::Instantiate { @@ -1118,7 +1115,7 @@ where fn caller(&self) -> &T::AccountId { if let Some(caller) = &self.top_frame().delegate_caller { - &caller + caller } else { self.frames().nth(1).map(|f| &f.account_id).unwrap_or(&self.origin) } @@ -1180,7 +1177,7 @@ where } fn schedule(&self) -> &Schedule { - &self.schedule + self.schedule } fn gas_meter(&mut self) -> &mut GasMeter { @@ -1205,7 +1202,7 @@ where } fn ecdsa_recover(&self, signature: &[u8; 65], message_hash: &[u8; 32]) -> Result<[u8; 33], ()> { - secp256k1_ecdsa_recover_compressed(&signature, &message_hash).map_err(|_| ()) + secp256k1_ecdsa_recover_compressed(signature, message_hash).map_err(|_| ()) } fn ecdsa_to_eth_address(&self, pk: &[u8; 33]) -> Result<[u8; 20], ()> { @@ -1220,8 +1217,8 @@ where fn set_code_hash(&mut self, hash: CodeHash) -> Result<(), DispatchError> { E::add_user(hash)?; let top_frame = self.top_frame_mut(); - let prev_hash = top_frame.contract_info().code_hash.clone(); - E::remove_user(prev_hash.clone()); + let prev_hash = top_frame.contract_info().code_hash; + E::remove_user(prev_hash); top_frame.contract_info().code_hash = hash; Contracts::::deposit_event(Event::ContractCodeUpdated { contract: top_frame.account_id.clone(), diff --git a/frame/contracts/src/gas.rs b/frame/contracts/src/gas.rs index cdf0c1407c6bd..41df125da0170 100644 --- a/frame/contracts/src/gas.rs +++ b/frame/contracts/src/gas.rs @@ -114,7 +114,7 @@ where if self.gas_left < amount { Err(>::OutOfGas.into()) } else { - self.gas_left = self.gas_left - amount; + self.gas_left -= amount; Ok(GasMeter::new(amount)) } } diff --git a/frame/contracts/src/schedule.rs b/frame/contracts/src/schedule.rs index bbbeb72f2cdf6..9e9f213fabb76 100644 --- a/frame/contracts/src/schedule.rs +++ b/frame/contracts/src/schedule.rs @@ -670,7 +670,7 @@ struct ScheduleRules<'a, T: Config> { impl Schedule { pub(crate) fn rules(&self, module: &elements::Module) -> impl gas_metering::Rules + '_ { ScheduleRules { - schedule: &self, + schedule: self, params: module .type_section() .iter() diff --git a/frame/contracts/src/storage.rs b/frame/contracts/src/storage.rs index 17022e9427664..2bdacc15cb11f 100644 --- a/frame/contracts/src/storage.rs +++ b/frame/contracts/src/storage.rs @@ -154,10 +154,10 @@ where let hashed_key = blake2_256(key); let child_trie_info = &child_trie_info(trie_id); let (old_len, old_value) = if take { - let val = child::get_raw(&child_trie_info, &hashed_key); + let val = child::get_raw(child_trie_info, &hashed_key); (val.as_ref().map(|v| v.len() as u32), val) } else { - (child::len(&child_trie_info, &hashed_key), None) + (child::len(child_trie_info, &hashed_key), None) }; if let Some(storage_meter) = storage_meter { @@ -183,8 +183,8 @@ where } match &new_value { - Some(new_value) => child::put_raw(&child_trie_info, &hashed_key, new_value), - None => child::kill(&child_trie_info, &hashed_key), + Some(new_value) => child::put_raw(child_trie_info, &hashed_key, new_value), + None => child::kill(child_trie_info, &hashed_key), } Ok(match (old_len, old_value) { diff --git a/frame/contracts/src/storage/meter.rs b/frame/contracts/src/storage/meter.rs index 28dc3356f7143..af51a5edc9472 100644 --- a/frame/contracts/src/storage/meter.rs +++ b/frame/contracts/src/storage/meter.rs @@ -230,7 +230,7 @@ where self.total_deposit = self.total_deposit.saturating_add(&absorbed.total_deposit); if !absorbed.own_deposit.is_zero() { - E::charge(origin, &contract, &absorbed.own_deposit, absorbed.terminated); + E::charge(origin, contract, &absorbed.own_deposit, absorbed.terminated); } } @@ -255,7 +255,7 @@ where limit: Option>, min_leftover: BalanceOf, ) -> Result { - let limit = E::check_limit(&origin, limit, min_leftover)?; + let limit = E::check_limit(origin, limit, min_leftover)?; Ok(Self { limit, ..Default::default() }) } diff --git a/frame/contracts/src/wasm/code_cache.rs b/frame/contracts/src/wasm/code_cache.rs index ee5cdd5307214..ca91fa3131474 100644 --- a/frame/contracts/src/wasm/code_cache.rs +++ b/frame/contracts/src/wasm/code_cache.rs @@ -165,8 +165,7 @@ where { let charged = gas_meter.charge(CodeToken::Load(schedule.limits.code_len))?; - let mut prefab_module = - >::get(code_hash).ok_or_else(|| Error::::CodeNotFound)?; + let mut prefab_module = >::get(code_hash).ok_or(Error::::CodeNotFound)?; gas_meter.adjust_gas(charged, CodeToken::Load(prefab_module.code.len() as u32)); prefab_module.code_hash = code_hash; @@ -189,7 +188,7 @@ pub fn reinstrument( schedule: &Schedule, ) -> Result { let original_code = - >::get(&prefab_module.code_hash).ok_or_else(|| Error::::CodeNotFound)?; + >::get(&prefab_module.code_hash).ok_or(Error::::CodeNotFound)?; let original_code_len = original_code.len(); prefab_module.code = prepare::reinstrument_contract::(original_code, schedule)?; prefab_module.instruction_weights_version = schedule.instruction_weights.version; diff --git a/frame/contracts/src/wasm/prepare.rs b/frame/contracts/src/wasm/prepare.rs index 4571d752a80c6..6e9babe1264ed 100644 --- a/frame/contracts/src/wasm/prepare.rs +++ b/frame/contracts/src/wasm/prepare.rs @@ -225,10 +225,7 @@ impl<'a, T: Config> ContractModule<'a, T> { .map(|is| is.entries()) .unwrap_or(&[]) .iter() - .filter(|entry| match *entry.external() { - External::Function(_) => true, - _ => false, - }) + .filter(|entry| matches!(*entry.external(), External::Function(_))) .count(); for export in export_entries { @@ -259,11 +256,10 @@ impl<'a, T: Config> ContractModule<'a, T> { // We still support () -> (i32) for backwards compatibility. let func_ty_idx = func_entries .get(fn_idx as usize) - .ok_or_else(|| "export refers to non-existent function")? + .ok_or("export refers to non-existent function")? .type_ref(); - let Type::Function(ref func_ty) = types - .get(func_ty_idx as usize) - .ok_or_else(|| "function has a non-existent type")?; + let Type::Function(ref func_ty) = + types.get(func_ty_idx as usize).ok_or("function has a non-existent type")?; if !(func_ty.params().is_empty() && (func_ty.results().is_empty() || func_ty.results() == [ValueType::I32])) { @@ -300,11 +296,11 @@ impl<'a, T: Config> ContractModule<'a, T> { let mut imported_mem_type = None; for import in import_entries { - let type_idx = match import.external() { - &External::Table(_) => return Err("Cannot import tables"), - &External::Global(_) => return Err("Cannot import globals"), - &External::Function(ref type_idx) => type_idx, - &External::Memory(ref memory_type) => { + let type_idx = match *import.external() { + External::Table(_) => return Err("Cannot import tables"), + External::Global(_) => return Err("Cannot import globals"), + External::Function(ref type_idx) => type_idx, + External::Memory(ref memory_type) => { if import.module() != IMPORT_MODULE_MEMORY { return Err("Invalid module for imported memory") } @@ -321,7 +317,7 @@ impl<'a, T: Config> ContractModule<'a, T> { let Type::Function(ref func_ty) = types .get(*type_idx as usize) - .ok_or_else(|| "validation: import entry points to a non-existent type")?; + .ok_or("validation: import entry points to a non-existent type")?; if !T::ChainExtension::enabled() && import.field().as_bytes() == b"seal_call_chain_extension" @@ -352,17 +348,15 @@ fn get_memory_limits( let limits = memory_type.limits(); match (limits.initial(), limits.maximum()) { (initial, Some(maximum)) if initial > maximum => - return Err( - "Requested initial number of pages should not exceed the requested maximum", - ), + Err("Requested initial number of pages should not exceed the requested maximum"), (_, Some(maximum)) if maximum > schedule.limits.memory_pages => - return Err("Maximum number of pages should not exceed the configured maximum."), + Err("Maximum number of pages should not exceed the configured maximum."), (initial, Some(maximum)) => Ok((initial, maximum)), (_, None) => { // Maximum number of pages should be always declared. // This isn't a hard requirement and can be treated as a maximum set // to configured maximum. - return Err("Maximum number of pages should be always declared.") + Err("Maximum number of pages should be always declared.") }, } } else { @@ -377,7 +371,7 @@ fn check_and_instrument( schedule: &Schedule, ) -> Result<(Vec, (u32, u32)), &'static str> { let result = (|| { - let contract_module = ContractModule::new(&original_code, schedule)?; + let contract_module = ContractModule::new(original_code, schedule)?; contract_module.scan_exports()?; contract_module.ensure_no_internal_memory()?; contract_module.ensure_table_size_limit(schedule.limits.table_size)?; diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index 876bd8848b0b1..7719d512b8aba 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -460,13 +460,13 @@ where return match trap_reason { // The trap was the result of the execution `return` host function. TrapReason::Return(ReturnData { flags, data }) => { - let flags = ReturnFlags::from_bits(flags) - .ok_or_else(|| Error::::InvalidCallFlags)?; + let flags = + ReturnFlags::from_bits(flags).ok_or(Error::::InvalidCallFlags)?; Ok(ExecReturnValue { flags, data: Bytes(data) }) }, TrapReason::Termination => Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes(Vec::new()) }), - TrapReason::SupervisorError(error) => Err(error)?, + TrapReason::SupervisorError(error) => return Err(error.into()), } } @@ -480,10 +480,10 @@ where // // Because panics are really undesirable in the runtime code, we treat this as // a trap for now. Eventually, we might want to revisit this. - Err(sp_sandbox::Error::Module) => Err("validation error")?, + Err(sp_sandbox::Error::Module) => return Err("validation error".into()), // Any other kind of a trap should result in a failure. Err(sp_sandbox::Error::Execution) | Err(sp_sandbox::Error::OutOfBounds) => - Err(Error::::ContractTrapped)?, + return Err(Error::::ContractTrapped.into()), } } @@ -620,7 +620,7 @@ where let len: u32 = self.read_sandbox_memory_as(out_len_ptr)?; if len < buf_len { - Err(Error::::OutputBufferTooSmall)? + return Err(Error::::OutputBufferTooSmall.into()) } if let Some(costs) = create_token(buf_len) { @@ -717,7 +717,7 @@ where let charged = self .charge_gas(RuntimeCosts::SetStorage { new_bytes: value_len, old_bytes: max_size })?; if value_len > max_size { - Err(Error::::ValueTooLarge)?; + return Err(Error::::ValueTooLarge.into()) } let mut key: StorageKey = [0; 32]; self.read_sandbox_memory_into_buf(key_ptr, &mut key)?; @@ -750,11 +750,11 @@ where ) -> Result { self.charge_gas(call_type.cost())?; let input_data = if flags.contains(CallFlags::CLONE_INPUT) { - let input = self.input_data.as_ref().ok_or_else(|| Error::::InputForwarded)?; + let input = self.input_data.as_ref().ok_or(Error::::InputForwarded)?; charge_gas!(self, RuntimeCosts::CallInputCloned(input.len() as u32))?; input.clone() } else if flags.contains(CallFlags::FORWARD_INPUT) { - self.input_data.take().ok_or_else(|| Error::::InputForwarded)? + self.input_data.take().ok_or(Error::::InputForwarded)? } else { self.charge_gas(RuntimeCosts::CopyFromContract(input_data_len))?; self.read_sandbox_memory(input_data_ptr, input_data_len)? @@ -1108,7 +1108,7 @@ define_env!(Env, , output_len_ptr: u32 ) -> ReturnCode => { ctx.call( - CallFlags::from_bits(flags).ok_or_else(|| Error::::InvalidCallFlags)?, + CallFlags::from_bits(flags).ok_or(Error::::InvalidCallFlags)?, CallType::Call{callee_ptr, value_ptr, gas}, input_data_ptr, input_data_len, @@ -1151,7 +1151,7 @@ define_env!(Env, , output_len_ptr: u32 ) -> ReturnCode => { ctx.call( - CallFlags::from_bits(flags).ok_or_else(|| Error::::InvalidCallFlags)?, + CallFlags::from_bits(flags).ok_or(Error::::InvalidCallFlags)?, CallType::DelegateCall{code_hash_ptr}, input_data_ptr, input_data_len, @@ -1486,7 +1486,7 @@ define_env!(Env, , ctx.charge_gas(RuntimeCosts::GasLeft)?; let gas_left = &ctx.ext.gas_meter().gas_left().encode(); Ok(ctx.write_sandbox_output( - out_ptr, out_len_ptr, &gas_left, false, already_charged, + out_ptr, out_len_ptr, gas_left, false, already_charged, )?) }, @@ -1535,7 +1535,7 @@ define_env!(Env, , [seal0] seal_random(ctx, subject_ptr: u32, subject_len: u32, out_ptr: u32, out_len_ptr: u32) => { ctx.charge_gas(RuntimeCosts::Random)?; if subject_len > ctx.ext.schedule().limits.subject_len { - Err(Error::::RandomSubjectTooLong)?; + return Err(Error::::RandomSubjectTooLong.into()); } let subject_buf = ctx.read_sandbox_memory(subject_ptr, subject_len)?; Ok(ctx.write_sandbox_output( @@ -1567,7 +1567,7 @@ define_env!(Env, , [seal1] seal_random(ctx, subject_ptr: u32, subject_len: u32, out_ptr: u32, out_len_ptr: u32) => { ctx.charge_gas(RuntimeCosts::Random)?; if subject_len > ctx.ext.schedule().limits.subject_len { - Err(Error::::RandomSubjectTooLong)?; + return Err(Error::::RandomSubjectTooLong.into()); } let subject_buf = ctx.read_sandbox_memory(subject_ptr, subject_len)?; Ok(ctx.write_sandbox_output( @@ -1685,13 +1685,13 @@ define_env!(Env, , let num_topic = topics_len .checked_div(sp_std::mem::size_of::>() as u32) - .ok_or_else(|| "Zero sized topics are not allowed")?; + .ok_or("Zero sized topics are not allowed")?; ctx.charge_gas(RuntimeCosts::DepositEvent { num_topic, len: data_len, })?; if data_len > ctx.ext.max_value_size() { - Err(Error::::ValueTooLarge)?; + return Err(Error::::ValueTooLarge.into()); } let mut topics: Vec::::T>> = match topics_len { @@ -1701,14 +1701,14 @@ define_env!(Env, , // If there are more than `event_topics`, then trap. if topics.len() > ctx.ext.schedule().limits.event_topics as usize { - Err(Error::::TooManyTopics)?; + return Err(Error::::TooManyTopics.into()); } // Check for duplicate topics. If there are any, then trap. // Complexity O(n * log(n)) and no additional allocations. // This also sorts the topics. if has_duplicates(&mut topics) { - Err(Error::::DuplicateTopics)?; + return Err(Error::::DuplicateTopics.into()); } let event_data = ctx.read_sandbox_memory(data_ptr, data_len)?; @@ -1888,7 +1888,7 @@ define_env!(Env, , ) -> u32 => { use crate::chain_extension::{ChainExtension, Environment, RetVal}; if !::ChainExtension::enabled() { - Err(Error::::NoChainExtension)?; + return Err(Error::::NoChainExtension.into()); } let env = Environment::new(ctx, input_ptr, input_len, output_ptr, output_len_ptr); match ::ChainExtension::call(func_id, env)? { diff --git a/frame/conviction-voting/src/benchmarking.rs b/frame/conviction-voting/src/benchmarking.rs index 2beee4f3b49d2..53ac7a07302f9 100644 --- a/frame/conviction-voting/src/benchmarking.rs +++ b/frame/conviction-voting/src/benchmarking.rs @@ -44,7 +44,7 @@ fn fill_voting() -> (ClassOf, BTreeMap, Vec> } } } - let c = r.iter().max_by_key(|(_, ref v)| v.len()).unwrap().0.clone(); + let c = r.iter().max_by_key(|(_, v)| v.len()).unwrap().0.clone(); (c, r) } @@ -73,7 +73,7 @@ benchmarks! { let r = polls.len() - 1; // We need to create existing votes for i in polls.iter().skip(1) { - ConvictionVoting::::vote(RawOrigin::Signed(caller.clone()).into(), *i, account_vote.clone())?; + ConvictionVoting::::vote(RawOrigin::Signed(caller.clone()).into(), *i, account_vote)?; } let votes = match VotingFor::::get(&caller, &class) { Voting::Casting(Casting { votes, .. }) => votes, @@ -100,7 +100,7 @@ benchmarks! { let r = polls.len(); // We need to create existing votes for i in polls.iter() { - ConvictionVoting::::vote(RawOrigin::Signed(caller.clone()).into(), *i, old_account_vote.clone())?; + ConvictionVoting::::vote(RawOrigin::Signed(caller.clone()).into(), *i, old_account_vote)?; } let votes = match VotingFor::::get(&caller, &class) { Voting::Casting(Casting { votes, .. }) => votes, @@ -128,7 +128,7 @@ benchmarks! { let r = polls.len(); // We need to create existing votes for i in polls.iter() { - ConvictionVoting::::vote(RawOrigin::Signed(caller.clone()).into(), *i, old_account_vote.clone())?; + ConvictionVoting::::vote(RawOrigin::Signed(caller.clone()).into(), *i, old_account_vote)?; } let votes = match VotingFor::::get(&caller, &class) { Voting::Casting(Casting { votes, .. }) => votes, @@ -156,7 +156,7 @@ benchmarks! { let r = polls.len(); // We need to create existing votes for i in polls.iter() { - ConvictionVoting::::vote(RawOrigin::Signed(voter.clone()).into(), *i, old_account_vote.clone())?; + ConvictionVoting::::vote(RawOrigin::Signed(voter.clone()).into(), *i, old_account_vote)?; } let votes = match VotingFor::::get(&caller, &class) { Voting::Casting(Casting { votes, .. }) => votes, @@ -189,14 +189,14 @@ benchmarks! { // We need to create existing delegations for i in polls.iter().take(r as usize) { - ConvictionVoting::::vote(RawOrigin::Signed(voter.clone()).into(), *i, delegate_vote.clone())?; + ConvictionVoting::::vote(RawOrigin::Signed(voter.clone()).into(), *i, delegate_vote)?; } assert_matches!( VotingFor::::get(&voter, &class), Voting::Casting(Casting { votes, .. }) if votes.len() == r as usize ); - }: _(RawOrigin::Signed(caller.clone()), class.clone(), voter.clone(), Conviction::Locked1x, delegated_balance) + }: _(RawOrigin::Signed(caller.clone()), class.clone(), voter, Conviction::Locked1x, delegated_balance) verify { assert_matches!(VotingFor::::get(&caller, &class), Voting::Delegating(_)); } @@ -224,7 +224,7 @@ benchmarks! { // We need to create delegations for i in polls.iter().take(r as usize) { - ConvictionVoting::::vote(RawOrigin::Signed(voter.clone()).into(), *i, delegate_vote.clone())?; + ConvictionVoting::::vote(RawOrigin::Signed(voter.clone()).into(), *i, delegate_vote)?; } assert_matches!( VotingFor::::get(&voter, &class), @@ -248,7 +248,7 @@ benchmarks! { for (class, polls) in all_polls.iter() { assert!(polls.len() > 0); for i in polls.iter() { - ConvictionVoting::::vote(RawOrigin::Signed(caller.clone()).into(), *i, normal_account_vote.clone())?; + ConvictionVoting::::vote(RawOrigin::Signed(caller.clone()).into(), *i, normal_account_vote)?; } } @@ -257,7 +257,7 @@ benchmarks! { // Vote big on the class with the most ongoing votes of them to bump the lock and make it // hard to recompute when removed. - ConvictionVoting::::vote(RawOrigin::Signed(caller.clone()).into(), polls[0], big_account_vote.clone())?; + ConvictionVoting::::vote(RawOrigin::Signed(caller.clone()).into(), polls[0], big_account_vote)?; let now_usable = >::reducible_balance(&caller, false); assert_eq!(orig_usable - now_usable, 100u32.into()); diff --git a/frame/conviction-voting/src/lib.rs b/frame/conviction-voting/src/lib.rs index af91e99fb3796..58ac76dac3a3e 100644 --- a/frame/conviction-voting/src/lib.rs +++ b/frame/conviction-voting/src/lib.rs @@ -553,7 +553,8 @@ impl, I: 'static> Pallet { }), ); match old { - Voting::Delegating(Delegating { .. }) => Err(Error::::AlreadyDelegating)?, + Voting::Delegating(Delegating { .. }) => + return Err(Error::::AlreadyDelegating.into()), Voting::Casting(Casting { votes, delegations, prior }) => { // here we just ensure that we're currently idling with no votes recorded. ensure!(votes.is_empty(), Error::::AlreadyVoting); diff --git a/frame/democracy/src/benchmarking.rs b/frame/democracy/src/benchmarking.rs index bce830b385a9f..c51fde8a3de8b 100644 --- a/frame/democracy/src/benchmarking.rs +++ b/frame/democracy/src/benchmarking.rs @@ -52,7 +52,7 @@ fn add_proposal(n: u32) -> Result { 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())?; + Democracy::::propose(RawOrigin::Signed(other).into(), proposal_hash, value)?; Ok(proposal_hash) } @@ -98,7 +98,7 @@ benchmarks! { let proposal_hash: T::Hash = T::Hashing::hash_of(&0); let value = T::MinimumDeposit::get(); whitelist_account!(caller); - }: _(RawOrigin::Signed(caller), proposal_hash, value.into()) + }: _(RawOrigin::Signed(caller), proposal_hash, value) verify { assert_eq!(Democracy::::public_props().len(), p as usize, "Proposals not created."); } @@ -133,7 +133,7 @@ benchmarks! { // We need to create existing direct votes for i in 0 .. r { let ref_idx = add_referendum::(i)?; - Democracy::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote.clone())?; + Democracy::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote)?; } let votes = match VotingOf::::get(&caller) { Voting::Direct { votes, .. } => votes, @@ -161,7 +161,7 @@ benchmarks! { // We need to create existing direct votes for i in 0 ..=r { let ref_idx = add_referendum::(i)?; - Democracy::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote.clone())?; + Democracy::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote)?; } let votes = match VotingOf::::get(&caller) { Voting::Direct { votes, .. } => votes, @@ -217,7 +217,7 @@ benchmarks! { // Place our proposal in the external queue, too. let hash = T::Hashing::hash_of(&0); assert_ok!( - Democracy::::external_propose(T::ExternalOrigin::successful_origin(), hash.clone()) + Democracy::::external_propose(T::ExternalOrigin::successful_origin(), hash) ); let origin = T::BlacklistOrigin::successful_origin(); // Add a referendum of our proposal. @@ -275,13 +275,13 @@ benchmarks! { fast_track { let origin_propose = T::ExternalDefaultOrigin::successful_origin(); let proposal_hash: T::Hash = T::Hashing::hash_of(&0); - Democracy::::external_propose_default(origin_propose, proposal_hash.clone())?; + Democracy::::external_propose_default(origin_propose, proposal_hash)?; // NOTE: Instant origin may invoke a little bit more logic, but may not always succeed. let origin_fast_track = T::FastTrackOrigin::successful_origin(); let voting_period = T::FastTrackVotingPeriod::get(); let delay = 0u32; - }: _(origin_fast_track, proposal_hash, voting_period.into(), delay.into()) + }: _(origin_fast_track, proposal_hash, voting_period, delay.into()) verify { assert_eq!(Democracy::::referendum_count(), 1, "referendum not created") } @@ -293,7 +293,7 @@ benchmarks! { 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())?; + Democracy::::external_propose_default(origin_propose, proposal_hash)?; let mut vetoers: Vec = Vec::new(); for i in 0 .. v { @@ -499,7 +499,7 @@ benchmarks! { // We need to create existing direct votes for the `new_delegate` for i in 0..r { let ref_idx = add_referendum::(i)?; - Democracy::::vote(RawOrigin::Signed(new_delegate.clone()).into(), ref_idx, account_vote.clone())?; + Democracy::::vote(RawOrigin::Signed(new_delegate.clone()).into(), ref_idx, account_vote)?; } let votes = match VotingOf::::get(&new_delegate) { Voting::Direct { votes, .. } => votes, @@ -550,7 +550,7 @@ benchmarks! { Democracy::::vote( RawOrigin::Signed(the_delegate.clone()).into(), ref_idx, - account_vote.clone() + account_vote )?; } let votes = match VotingOf::::get(&the_delegate) { @@ -619,17 +619,17 @@ benchmarks! { let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); let submitter = funded_account::("submitter", b); - Democracy::::note_preimage(RawOrigin::Signed(submitter.clone()).into(), encoded_proposal.clone())?; + Democracy::::note_preimage(RawOrigin::Signed(submitter).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()); + System::::set_block_number(block_number); assert!(Preimages::::contains_key(proposal_hash)); let caller = funded_account::("caller", 0); whitelist_account!(caller); - }: _(RawOrigin::Signed(caller), proposal_hash.clone(), u32::MAX) + }: _(RawOrigin::Signed(caller), proposal_hash, u32::MAX) verify { let proposal_hash = T::Hashing::hash(&encoded_proposal[..]); assert!(!Preimages::::contains_key(proposal_hash)); @@ -646,7 +646,7 @@ benchmarks! { // Vote and immediately unvote for i in 0 .. r { let ref_idx = add_referendum::(i)?; - Democracy::::vote(RawOrigin::Signed(locker.clone()).into(), ref_idx, small_vote.clone())?; + Democracy::::vote(RawOrigin::Signed(locker.clone()).into(), ref_idx, small_vote)?; Democracy::::remove_vote(RawOrigin::Signed(locker.clone()).into(), ref_idx)?; } @@ -669,7 +669,7 @@ benchmarks! { let small_vote = account_vote::(base_balance); for i in 0 .. r { let ref_idx = add_referendum::(i)?; - Democracy::::vote(RawOrigin::Signed(locker.clone()).into(), ref_idx, small_vote.clone())?; + Democracy::::vote(RawOrigin::Signed(locker.clone()).into(), ref_idx, small_vote)?; } // Create a big vote so lock increases @@ -711,7 +711,7 @@ benchmarks! { for i in 0 .. r { let ref_idx = add_referendum::(i)?; - Democracy::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote.clone())?; + Democracy::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote)?; } let votes = match VotingOf::::get(&caller) { @@ -740,7 +740,7 @@ benchmarks! { for i in 0 .. r { let ref_idx = add_referendum::(i)?; - Democracy::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote.clone())?; + Democracy::::vote(RawOrigin::Signed(caller.clone()).into(), ref_idx, account_vote)?; } let votes = match VotingOf::::get(&caller) { diff --git a/frame/democracy/src/lib.rs b/frame/democracy/src/lib.rs index 8588f1876d7e3..91362a0a3edf7 100644 --- a/frame/democracy/src/lib.rs +++ b/frame/democracy/src/lib.rs @@ -670,8 +670,7 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; - let seconds = - Self::len_of_deposit_of(proposal).ok_or_else(|| Error::::ProposalMissing)?; + let seconds = Self::len_of_deposit_of(proposal).ok_or(Error::::ProposalMissing)?; ensure!(seconds <= seconds_upper_bound, Error::::WrongUpperBound); let mut deposit = Self::deposit_of(proposal).ok_or(Error::::ProposalMissing)?; T::Currency::reserve(&who, deposit.1)?; @@ -820,12 +819,10 @@ pub mod pallet { // - `InstantAllowed` is `true` and `origin` is `InstantOrigin`. let maybe_ensure_instant = if voting_period < T::FastTrackVotingPeriod::get() { Some(origin) + } else if let Err(origin) = T::FastTrackOrigin::try_origin(origin) { + Some(origin) } else { - if let Err(origin) = T::FastTrackOrigin::try_origin(origin) { - Some(origin) - } else { - None - } + None }; if let Some(ensure_instant) = maybe_ensure_instant { T::InstantOrigin::ensure_origin(ensure_instant)?; @@ -867,7 +864,7 @@ pub mod pallet { if let Some((e_proposal_hash, _)) = >::get() { ensure!(proposal_hash == e_proposal_hash, Error::::ProposalMissing); } else { - Err(Error::::NoProposal)?; + return Err(Error::::NoProposal.into()) } let mut existing_vetoers = @@ -966,7 +963,7 @@ pub mod pallet { /// voted on. Weight is charged as if maximum votes. // NOTE: weight must cover an incorrect voting of origin with max votes, this is ensure // because a valid delegation cover decoding a direct voting with max votes. - #[pallet::weight(T::WeightInfo::undelegate(T::MaxVotes::get().into()))] + #[pallet::weight(T::WeightInfo::undelegate(T::MaxVotes::get()))] pub fn undelegate(origin: OriginFor) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; let votes = Self::try_undelegate(who)?; @@ -1634,7 +1631,7 @@ impl Pallet { ); Ok(()) } else { - Err(Error::::NoneWaiting)? + return Err(Error::::NoneWaiting.into()) } } @@ -1667,7 +1664,7 @@ impl Pallet { } Ok(()) } else { - Err(Error::::NoneWaiting)? + return Err(Error::::NoneWaiting.into()) } } @@ -1822,8 +1819,7 @@ impl Pallet { // To decode the enum variant we only need the first byte. let mut buf = [0u8; 1]; let key = >::hashed_key_for(proposal_hash); - let bytes = - sp_io::storage::read(&key, &mut buf, 0).ok_or_else(|| Error::::NotImminent)?; + let bytes = sp_io::storage::read(&key, &mut buf, 0).ok_or(Error::::NotImminent)?; // The value may be smaller that 1 byte. let mut input = &buf[0..buf.len().min(bytes as usize)]; @@ -1851,8 +1847,7 @@ impl Pallet { // * at most 5 bytes to decode a `Compact` let mut buf = [0u8; 6]; let key = >::hashed_key_for(proposal_hash); - let bytes = - sp_io::storage::read(&key, &mut buf, 0).ok_or_else(|| Error::::PreimageMissing)?; + let bytes = sp_io::storage::read(&key, &mut buf, 0).ok_or(Error::::PreimageMissing)?; // The value may be smaller that 6 bytes. let mut input = &buf[0..buf.len().min(bytes as usize)]; @@ -1931,7 +1926,7 @@ impl Pallet { fn decode_compact_u32_at(key: &[u8]) -> Option { // `Compact` takes at most 5 bytes. let mut buf = [0u8; 5]; - let bytes = sp_io::storage::read(&key, &mut buf, 0)?; + let bytes = sp_io::storage::read(key, &mut buf, 0)?; // The value may be smaller than 5 bytes. let mut input = &buf[0..buf.len().min(bytes as usize)]; match codec::Compact::::decode(&mut input) { diff --git a/frame/election-provider-multi-phase/src/benchmarking.rs b/frame/election-provider-multi-phase/src/benchmarking.rs index 6dfb2ac794810..a1c643754158b 100644 --- a/frame/election-provider-multi-phase/src/benchmarking.rs +++ b/frame/election-provider-multi-phase/src/benchmarking.rs @@ -491,7 +491,7 @@ frame_benchmarking::benchmarks! { // sort assignments by decreasing voter stake assignments.sort_by_key(|crate::unsigned::Assignment:: { who, .. }| { - let stake = cache.get(&who).map(|idx| { + let stake = cache.get(who).map(|idx| { let (_, stake, _) = voters[*idx]; stake }).unwrap_or_default(); diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index a9e341bad600f..4aea5b9da4794 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -264,7 +264,7 @@ mod mock; #[macro_use] pub mod helpers; -const LOG_TARGET: &'static str = "runtime::election-provider"; +const LOG_TARGET: &str = "runtime::election-provider"; pub mod signed; pub mod unsigned; @@ -1446,7 +1446,7 @@ impl Pallet { ensure!(winners.len() as u32 == desired_targets, FeasibilityError::WrongWinnerCount); // Ensure that the solution's score can pass absolute min-score. - let submitted_score = raw_solution.score.clone(); + let submitted_score = raw_solution.score; ensure!( Self::minimum_untrusted_score().map_or(true, |min_score| { submitted_score.strict_threshold_better(min_score, Perbill::zero()) @@ -1471,29 +1471,26 @@ impl Pallet { .map_err::(Into::into)?; // Ensure that assignments is correct. - let _ = assignments - .iter() - .map(|ref assignment| { - // Check that assignment.who is actually a voter (defensive-only). - // NOTE: while using the index map from `voter_index` is better than a blind linear - // search, this *still* has room for optimization. Note that we had the index when - // we did `solution -> assignment` and we lost it. Ideal is to keep the index - // around. - - // Defensive-only: must exist in the snapshot. - let snapshot_index = - voter_index(&assignment.who).ok_or(FeasibilityError::InvalidVoter)?; - // Defensive-only: index comes from the snapshot, must exist. - let (_voter, _stake, targets) = - snapshot_voters.get(snapshot_index).ok_or(FeasibilityError::InvalidVoter)?; - - // Check that all of the targets are valid based on the snapshot. - if assignment.distribution.iter().any(|(d, _)| !targets.contains(d)) { - return Err(FeasibilityError::InvalidVote) - } - Ok(()) - }) - .collect::>()?; + let _ = assignments.iter().try_for_each(|assignment| { + // Check that assignment.who is actually a voter (defensive-only). + // NOTE: while using the index map from `voter_index` is better than a blind linear + // search, this *still* has room for optimization. Note that we had the index when + // we did `solution -> assignment` and we lost it. Ideal is to keep the index + // around. + + // Defensive-only: must exist in the snapshot. + let snapshot_index = + voter_index(&assignment.who).ok_or(FeasibilityError::InvalidVoter)?; + // Defensive-only: index comes from the snapshot, must exist. + let (_voter, _stake, targets) = + snapshot_voters.get(snapshot_index).ok_or(FeasibilityError::InvalidVoter)?; + + // Check that all of the targets are valid based on the snapshot. + if assignment.distribution.iter().any(|(d, _)| !targets.contains(d)) { + return Err(FeasibilityError::InvalidVote) + } + Ok(()) + })?; // ----- Start building support. First, we need one more closure. let stake_of = helpers::stake_of_fn::(&snapshot_voters, &cache); diff --git a/frame/election-provider-multi-phase/src/unsigned.rs b/frame/election-provider-multi-phase/src/unsigned.rs index d184c3acfe91b..126dcb10416cc 100644 --- a/frame/election-provider-multi-phase/src/unsigned.rs +++ b/frame/election-provider-multi-phase/src/unsigned.rs @@ -99,7 +99,7 @@ impl From for MinerError { /// Save a given call into OCW storage. fn save_solution(call: &Call) -> Result<(), MinerError> { log!(debug, "saving a call to the offchain storage."); - let storage = StorageValueRef::persistent(&OFFCHAIN_CACHED_CALL); + let storage = StorageValueRef::persistent(OFFCHAIN_CACHED_CALL); match storage.mutate::<_, (), _>(|_| Ok(call.clone())) { Ok(_) => Ok(()), Err(MutateStorageError::ConcurrentModification(_)) => @@ -116,7 +116,7 @@ fn save_solution(call: &Call) -> Result<(), MinerError> { /// Get a saved solution from OCW storage if it exists. fn restore_solution() -> Result, MinerError> { - StorageValueRef::persistent(&OFFCHAIN_CACHED_CALL) + StorageValueRef::persistent(OFFCHAIN_CACHED_CALL) .get() .ok() .flatten() @@ -126,7 +126,7 @@ fn restore_solution() -> Result, MinerError> { /// Clear a saved solution from OCW storage. pub(super) fn kill_ocw_solution() { log!(debug, "clearing offchain call cache storage."); - let mut storage = StorageValueRef::persistent(&OFFCHAIN_CACHED_CALL); + let mut storage = StorageValueRef::persistent(OFFCHAIN_CACHED_CALL); storage.clear(); } @@ -135,14 +135,14 @@ pub(super) fn kill_ocw_solution() { /// After calling this, the next offchain worker is guaranteed to work, with respect to the /// frequency repeat. fn clear_offchain_repeat_frequency() { - let mut last_block = StorageValueRef::persistent(&OFFCHAIN_LAST_BLOCK); + let mut last_block = StorageValueRef::persistent(OFFCHAIN_LAST_BLOCK); last_block.clear(); } /// `true` when OCW storage contains a solution #[cfg(test)] fn ocw_solution_exists() -> bool { - matches!(StorageValueRef::persistent(&OFFCHAIN_CACHED_CALL).get::>(), Ok(Some(_))) + matches!(StorageValueRef::persistent(OFFCHAIN_CACHED_CALL).get::>(), Ok(Some(_))) } impl Pallet { @@ -206,9 +206,8 @@ impl Pallet { // get the solution, with a load of checks to ensure if submitted, IT IS ABSOLUTELY VALID. let (raw_solution, witness) = Self::mine_and_check()?; - let score = raw_solution.score.clone(); - let call: Call = - Call::submit_unsigned { raw_solution: Box::new(raw_solution), witness }.into(); + let score = raw_solution.score; + let call: Call = Call::submit_unsigned { raw_solution: Box::new(raw_solution), witness }; log!( debug, @@ -532,7 +531,7 @@ impl Pallet { // we found the right value - early exit the function. Ok(next) => return next, } - step = step / 2; + step /= 2; current_weight = weight_with(voters); } @@ -566,7 +565,7 @@ impl Pallet { /// is returned, `now` is written in storage and will be used in further calls as the baseline. pub fn ensure_offchain_repeat_frequency(now: T::BlockNumber) -> Result<(), MinerError> { let threshold = T::OffchainRepeat::get(); - let last_block = StorageValueRef::persistent(&OFFCHAIN_LAST_BLOCK); + let last_block = StorageValueRef::persistent(OFFCHAIN_LAST_BLOCK); let mutate_stat = last_block.mutate::<_, &'static str, _>( |maybe_head: Result, _>| { diff --git a/frame/election-provider-support/solution-type/src/lib.rs b/frame/election-provider-support/solution-type/src/lib.rs index 7c5cb960d2430..0a5c11e76dedb 100644 --- a/frame/election-provider-support/solution-type/src/lib.rs +++ b/frame/election-provider-support/solution-type/src/lib.rs @@ -153,7 +153,7 @@ fn check_attributes(input: ParseStream) -> syn::Result { if attrs.len() > 1 { let extra_attr = attrs.pop().expect("attributes vec with len > 1 can be popped"); return Err(syn::Error::new_spanned( - extra_attr.clone(), + extra_attr, "compact solution can accept only #[compact]", )) } @@ -164,7 +164,7 @@ fn check_attributes(input: ParseStream) -> syn::Result { if attr.path.is_ident("compact") { Ok(true) } else { - Err(syn::Error::new_spanned(attr.clone(), "compact solution can accept only #[compact]")) + Err(syn::Error::new_spanned(attr, "compact solution can accept only #[compact]")) } } diff --git a/frame/election-provider-support/solution-type/src/single_page.rs b/frame/election-provider-support/solution-type/src/single_page.rs index a20f0542984dc..a7ccf5085d2b1 100644 --- a/frame/election-provider-support/solution-type/src/single_page.rs +++ b/frame/election-provider-support/solution-type/src/single_page.rs @@ -33,7 +33,7 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result { } = def; if count <= 2 { - Err(syn_err("cannot build solution struct with capacity less than 3."))? + return Err(syn_err("cannot build solution struct with capacity less than 3.")) } let single = { diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index 37ea2fcc59091..69bd3025fa768 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -244,7 +244,7 @@ impl IndexAssignment>>() .or_invalid_index()?, }) diff --git a/frame/elections-phragmen/src/benchmarking.rs b/frame/elections-phragmen/src/benchmarking.rs index 05e9df60c7fbe..493f20924203c 100644 --- a/frame/elections-phragmen/src/benchmarking.rs +++ b/frame/elections-phragmen/src/benchmarking.rs @@ -93,8 +93,7 @@ fn submit_candidates_with_self_vote( let stake = default_stake::(c); let _ = candidates .iter() - .map(|c| submit_voter::(c.clone(), vec![c.clone()], stake).map(|_| ())) - .collect::>()?; + .try_for_each(|c| submit_voter::(c.clone(), vec![c.clone()], stake).map(|_| ()))?; Ok(candidates) } diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index a59da9c20f809..270d3853e2ca5 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -503,7 +503,7 @@ pub mod pallet { let had_replacement = Self::remove_and_replace_member(&who, true)?; debug_assert_eq!(has_replacement, had_replacement); - Self::deposit_event(Event::MemberKicked { member: who.clone() }); + Self::deposit_event(Event::MemberKicked { member: who }); if !had_replacement { Self::do_phragmen(); @@ -782,7 +782,7 @@ impl Pallet { })?; let remaining_member_ids_sorted = - Self::members().into_iter().map(|x| x.who.clone()).collect::>(); + Self::members().into_iter().map(|x| x.who).collect::>(); let outgoing = &[who.clone()]; let maybe_current_prime = T::ChangeMembers::get_prime(); let return_value = match maybe_replacement { @@ -861,7 +861,7 @@ impl Pallet { /// Reads Members, RunnersUp, Candidates and Voting(who) from database. fn is_defunct_voter(votes: &[T::AccountId]) -> bool { votes.iter().all(|v| { - !Self::is_member(v) && !Self::is_runner_up(v) && !Self::is_candidate(v).is_ok() + !Self::is_member(v) && !Self::is_runner_up(v) && Self::is_candidate(v).is_err() }) } @@ -923,148 +923,156 @@ impl Pallet { let weight_candidates = candidates_and_deposit.len() as u32; let weight_voters = voters_and_votes.len() as u32; let weight_edges = num_edges; - let _ = sp_npos_elections::seq_phragmen( - num_to_elect, - candidate_ids, - voters_and_votes.clone(), - None, - ) - .map(|ElectionResult:: { winners, assignments: _ }| { - // this is already sorted by id. - let old_members_ids_sorted = - >::take().into_iter().map(|m| m.who).collect::>(); - // this one needs a sort by id. - let mut old_runners_up_ids_sorted = - >::take().into_iter().map(|r| r.who).collect::>(); - old_runners_up_ids_sorted.sort(); - - // filter out those who end up with no backing stake. - let mut new_set_with_stake = winners - .into_iter() - .filter_map(|(m, b)| if b.is_zero() { None } else { Some((m, to_balance(b))) }) - .collect::)>>(); - - // OPTIMIZATION NOTE: we could bail out here if `new_set.len() == 0`. There isn't - // much left to do. Yet, re-arranging the code would require duplicating the - // slashing of exposed candidates, cleaning any previous members, and so on. For - // now, in favor of readability and veracity, we keep it simple. - - // split new set into winners and runners up. - let split_point = desired_seats.min(new_set_with_stake.len()); - let mut new_members_sorted_by_id = - new_set_with_stake.drain(..split_point).collect::>(); - new_members_sorted_by_id.sort_by(|i, j| i.0.cmp(&j.0)); - - // all the rest will be runners-up - new_set_with_stake.reverse(); - let new_runners_up_sorted_by_rank = new_set_with_stake; - let mut new_runners_up_ids_sorted = - new_runners_up_sorted_by_rank.iter().map(|(r, _)| r.clone()).collect::>(); - new_runners_up_ids_sorted.sort(); - - // Now we select a prime member using a [Borda - // count](https://en.wikipedia.org/wiki/Borda_count). We weigh everyone's vote for - // that new member by a multiplier based on the order of the votes. i.e. the first - // person a voter votes for gets a 16x multiplier, the next person gets a 15x - // multiplier, an so on... (assuming `MAXIMUM_VOTE` = 16) - let mut prime_votes = new_members_sorted_by_id - .iter() - .map(|c| (&c.0, BalanceOf::::zero())) - .collect::>(); - for (_, stake, votes) in voters_and_stakes.into_iter() { - for (vote_multiplier, who) in votes - .iter() - .enumerate() - .map(|(vote_position, who)| ((MAXIMUM_VOTE - vote_position) as u32, who)) - { - if let Ok(i) = prime_votes.binary_search_by_key(&who, |k| k.0) { - prime_votes[i].1 = prime_votes[i] - .1 - .saturating_add(stake.saturating_mul(vote_multiplier.into())); + let _ = + sp_npos_elections::seq_phragmen(num_to_elect, candidate_ids, voters_and_votes, None) + .map(|ElectionResult:: { winners, assignments: _ }| { + // this is already sorted by id. + let old_members_ids_sorted = >::take() + .into_iter() + .map(|m| m.who) + .collect::>(); + // this one needs a sort by id. + let mut old_runners_up_ids_sorted = >::take() + .into_iter() + .map(|r| r.who) + .collect::>(); + old_runners_up_ids_sorted.sort(); + + // filter out those who end up with no backing stake. + let mut new_set_with_stake = winners + .into_iter() + .filter_map( + |(m, b)| if b.is_zero() { None } else { Some((m, to_balance(b))) }, + ) + .collect::)>>(); + + // OPTIMIZATION NOTE: we could bail out here if `new_set.len() == 0`. There + // isn't much left to do. Yet, re-arranging the code would require duplicating + // the slashing of exposed candidates, cleaning any previous members, and so on. + // For now, in favor of readability and veracity, we keep it simple. + + // split new set into winners and runners up. + let split_point = desired_seats.min(new_set_with_stake.len()); + let mut new_members_sorted_by_id = + new_set_with_stake.drain(..split_point).collect::>(); + new_members_sorted_by_id.sort_by(|i, j| i.0.cmp(&j.0)); + + // all the rest will be runners-up + new_set_with_stake.reverse(); + let new_runners_up_sorted_by_rank = new_set_with_stake; + let mut new_runners_up_ids_sorted = new_runners_up_sorted_by_rank + .iter() + .map(|(r, _)| r.clone()) + .collect::>(); + new_runners_up_ids_sorted.sort(); + + // Now we select a prime member using a [Borda + // count](https://en.wikipedia.org/wiki/Borda_count). We weigh everyone's vote for + // that new member by a multiplier based on the order of the votes. i.e. the + // first person a voter votes for gets a 16x multiplier, the next person gets a + // 15x multiplier, an so on... (assuming `MAXIMUM_VOTE` = 16) + let mut prime_votes = new_members_sorted_by_id + .iter() + .map(|c| (&c.0, BalanceOf::::zero())) + .collect::>(); + for (_, stake, votes) in voters_and_stakes.into_iter() { + for (vote_multiplier, who) in + votes.iter().enumerate().map(|(vote_position, who)| { + ((MAXIMUM_VOTE - vote_position) as u32, who) + }) { + if let Ok(i) = prime_votes.binary_search_by_key(&who, |k| k.0) { + prime_votes[i].1 = prime_votes[i] + .1 + .saturating_add(stake.saturating_mul(vote_multiplier.into())); + } + } } - } - } - // We then select the new member with the highest weighted stake. In the case of a tie, - // the last person in the list with the tied score is selected. This is the person with - // the "highest" account id based on the sort above. - let prime = prime_votes.into_iter().max_by_key(|x| x.1).map(|x| x.0.clone()); - - // new_members_sorted_by_id is sorted by account id. - let new_members_ids_sorted = new_members_sorted_by_id - .iter() - .map(|(m, _)| m.clone()) - .collect::>(); - - // report member changes. We compute diff because we need the outgoing list. - let (incoming, outgoing) = T::ChangeMembers::compute_members_diff_sorted( - &new_members_ids_sorted, - &old_members_ids_sorted, - ); - T::ChangeMembers::change_members_sorted(&incoming, &outgoing, &new_members_ids_sorted); - T::ChangeMembers::set_prime(prime); - - // All candidates/members/runners-up who are no longer retaining a position as a - // seat holder will lose their bond. - candidates_and_deposit.iter().for_each(|(c, d)| { - if new_members_ids_sorted.binary_search(c).is_err() && - new_runners_up_ids_sorted.binary_search(c).is_err() - { - let (imbalance, _) = T::Currency::slash_reserved(c, *d); - T::LoserCandidate::on_unbalanced(imbalance); - Self::deposit_event(Event::CandidateSlashed { - candidate: c.clone(), - amount: *d, + // We then select the new member with the highest weighted stake. In the case of + // a tie, the last person in the list with the tied score is selected. This is + // the person with the "highest" account id based on the sort above. + let prime = prime_votes.into_iter().max_by_key(|x| x.1).map(|x| x.0.clone()); + + // new_members_sorted_by_id is sorted by account id. + let new_members_ids_sorted = new_members_sorted_by_id + .iter() + .map(|(m, _)| m.clone()) + .collect::>(); + + // report member changes. We compute diff because we need the outgoing list. + let (incoming, outgoing) = T::ChangeMembers::compute_members_diff_sorted( + &new_members_ids_sorted, + &old_members_ids_sorted, + ); + T::ChangeMembers::change_members_sorted( + &incoming, + &outgoing, + &new_members_ids_sorted, + ); + T::ChangeMembers::set_prime(prime); + + // All candidates/members/runners-up who are no longer retaining a position as a + // seat holder will lose their bond. + candidates_and_deposit.iter().for_each(|(c, d)| { + if new_members_ids_sorted.binary_search(c).is_err() && + new_runners_up_ids_sorted.binary_search(c).is_err() + { + let (imbalance, _) = T::Currency::slash_reserved(c, *d); + T::LoserCandidate::on_unbalanced(imbalance); + Self::deposit_event(Event::CandidateSlashed { + candidate: c.clone(), + amount: *d, + }); + } }); - } - }); - // write final values to storage. - let deposit_of_candidate = |x: &T::AccountId| -> BalanceOf { - // defensive-only. This closure is used against the new members and new runners-up, - // both of which are phragmen winners and thus must have deposit. - candidates_and_deposit - .iter() - .find_map(|(c, d)| if c == x { Some(*d) } else { None }) - .defensive_unwrap_or_default() - }; - // fetch deposits from the one recorded one. This will make sure that a candidate who - // submitted candidacy before a change to candidacy deposit will have the correct amount - // recorded. - >::put( - new_members_sorted_by_id - .iter() - .map(|(who, stake)| SeatHolder { - deposit: deposit_of_candidate(&who), - who: who.clone(), - stake: stake.clone(), - }) - .collect::>(), - ); - >::put( - new_runners_up_sorted_by_rank - .into_iter() - .map(|(who, stake)| SeatHolder { - deposit: deposit_of_candidate(&who), - who, - stake, - }) - .collect::>(), - ); + // write final values to storage. + let deposit_of_candidate = |x: &T::AccountId| -> BalanceOf { + // defensive-only. This closure is used against the new members and new + // runners-up, both of which are phragmen winners and thus must have + // deposit. + candidates_and_deposit + .iter() + .find_map(|(c, d)| if c == x { Some(*d) } else { None }) + .defensive_unwrap_or_default() + }; + // fetch deposits from the one recorded one. This will make sure that a + // candidate who submitted candidacy before a change to candidacy deposit will + // have the correct amount recorded. + >::put( + new_members_sorted_by_id + .iter() + .map(|(who, stake)| SeatHolder { + deposit: deposit_of_candidate(who), + who: who.clone(), + stake: *stake, + }) + .collect::>(), + ); + >::put( + new_runners_up_sorted_by_rank + .into_iter() + .map(|(who, stake)| SeatHolder { + deposit: deposit_of_candidate(&who), + who, + stake, + }) + .collect::>(), + ); - // clean candidates. - >::kill(); + // clean candidates. + >::kill(); - Self::deposit_event(Event::NewTerm { new_members: new_members_sorted_by_id }); - >::mutate(|v| *v += 1); - }) - .map_err(|e| { - log::error!( - target: "runtime::elections-phragmen", - "Failed to run election [{:?}].", - e, - ); - Self::deposit_event(Event::ElectionError); - }); + Self::deposit_event(Event::NewTerm { new_members: new_members_sorted_by_id }); + >::mutate(|v| *v += 1); + }) + .map_err(|e| { + log::error!( + target: "runtime::elections-phragmen", + "Failed to run election [{:?}].", + e, + ); + Self::deposit_event(Event::ElectionError); + }); T::WeightInfo::election_phragmen(weight_candidates, weight_voters, weight_edges) } diff --git a/frame/elections-phragmen/src/migrations/v5.rs b/frame/elections-phragmen/src/migrations/v5.rs index 1898668cd07b3..a9fb018ba0219 100644 --- a/frame/elections-phragmen/src/migrations/v5.rs +++ b/frame/elections-phragmen/src/migrations/v5.rs @@ -12,7 +12,7 @@ pub fn migrate(to_migrate: Vec) -> Weight { for who in to_migrate.iter() { if let Ok(mut voter) = Voting::::try_get(who) { - let free_balance = T::Currency::free_balance(&who); + let free_balance = T::Currency::free_balance(who); weight = weight.saturating_add(T::DbWeight::get().reads(2)); @@ -21,7 +21,7 @@ pub fn migrate(to_migrate: Vec) -> Weight { Voting::::insert(&who, voter); let pallet_id = T::PalletId::get(); - T::Currency::set_lock(pallet_id, &who, free_balance, WithdrawReasons::all()); + T::Currency::set_lock(pallet_id, who, free_balance, WithdrawReasons::all()); weight = weight.saturating_add(T::DbWeight::get().writes(2)); } @@ -38,7 +38,7 @@ pub fn pre_migrate_fn(to_migrate: Vec) -> Box::try_get(who) { - let free_balance = T::Currency::free_balance(&who); + let free_balance = T::Currency::free_balance(who); if voter.stake > free_balance { // all good diff --git a/frame/examples/basic/src/benchmarking.rs b/frame/examples/basic/src/benchmarking.rs index b823ccd072969..d7b933577ead5 100644 --- a/frame/examples/basic/src/benchmarking.rs +++ b/frame/examples/basic/src/benchmarking.rs @@ -63,7 +63,7 @@ benchmarks! { } }: { // The benchmark execution phase could also be a closure with custom code - m.sort(); + m.sort_unstable(); } // This line generates test cases for benchmarking, and could be run by: diff --git a/frame/examples/basic/src/lib.rs b/frame/examples/basic/src/lib.rs index 56e8db6936249..f8acc1962388f 100644 --- a/frame/examples/basic/src/lib.rs +++ b/frame/examples/basic/src/lib.rs @@ -758,8 +758,8 @@ where Some(Call::set_dummy { .. }) => { sp_runtime::print("set_dummy was received."); - let mut valid_tx = ValidTransaction::default(); - valid_tx.priority = Bounded::max_value(); + let valid_tx = + ValidTransaction { priority: Bounded::max_value(), ..Default::default() }; Ok(valid_tx) }, _ => Ok(Default::default()), diff --git a/frame/examples/offchain-worker/src/lib.rs b/frame/examples/offchain-worker/src/lib.rs index 3cf718217b062..b40311051594b 100644 --- a/frame/examples/offchain-worker/src/lib.rs +++ b/frame/examples/offchain-worker/src/lib.rs @@ -446,7 +446,7 @@ impl Pallet { if !signer.can_sign() { return Err( "No local accounts available. Consider adding one via `author_insertKey` RPC.", - )? + ) } // Make an external HTTP request to fetch the current price. // Note this call will block until response is received. @@ -640,7 +640,7 @@ impl Pallet { _ => return None, }; - let exp = price.fraction_length.checked_sub(2).unwrap_or(0); + let exp = price.fraction_length.saturating_sub(2); Some(price.integer as u32 * 100 + (price.fraction / 10_u64.pow(exp)) as u32) } diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index 68b5e910ed1fc..f1e7485d7440c 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -245,7 +245,7 @@ where let new_header = >::finalize(); let items_zip = header.digest().logs().iter().zip(new_header.digest().logs().iter()); for (header_item, computed_item) in items_zip { - header_item.check_equal(&computed_item); + header_item.check_equal(computed_item); assert!(header_item == computed_item, "Digest item must match that calculated."); } @@ -275,7 +275,7 @@ where pub fn initialize_block(header: &System::Header) { sp_io::init_tracing(); sp_tracing::enter_span!(sp_tracing::Level::TRACE, "init_block"); - let digests = Self::extract_pre_digest(&header); + let digests = Self::extract_pre_digest(header); Self::initialize_block_impl(header.number(), header.parent_hash(), &digests); } @@ -338,7 +338,7 @@ where let header = block.header(); // Check that `parent_hash` is correct. - let n = header.number().clone(); + let n = *header.number(); assert!( n > System::BlockNumber::zero() && >::block_hash(n - System::BlockNumber::one()) == @@ -478,13 +478,13 @@ where ); let items_zip = header.digest().logs().iter().zip(new_header.digest().logs().iter()); for (header_item, computed_item) in items_zip { - header_item.check_equal(&computed_item); + header_item.check_equal(computed_item); assert!(header_item == computed_item, "Digest item must match that calculated."); } // check storage root. let storage_root = new_header.state_root(); - header.state_root().check_equal(&storage_root); + header.state_root().check_equal(storage_root); assert!(header.state_root() == storage_root, "Storage root must match that calculated."); assert!( diff --git a/frame/gilt/src/lib.rs b/frame/gilt/src/lib.rs index 8956e04857f2c..59522f9a106f2 100644 --- a/frame/gilt/src/lib.rs +++ b/frame/gilt/src/lib.rs @@ -399,7 +399,7 @@ pub mod pallet { qs[queue_index].0 += net.0; qs[queue_index].1 = qs[queue_index].1.saturating_add(net.1); }); - Self::deposit_event(Event::BidPlaced { who: who.clone(), amount, duration }); + Self::deposit_event(Event::BidPlaced { who, amount, duration }); Ok(().into()) } diff --git a/frame/grandpa/src/lib.rs b/frame/grandpa/src/lib.rs index 318f2f40ea4d9..75244c72cfd16 100644 --- a/frame/grandpa/src/lib.rs +++ b/frame/grandpa/src/lib.rs @@ -77,7 +77,7 @@ const STORAGE_VERSION: StorageVersion = StorageVersion::new(4); #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::pallet_prelude::*; + use frame_support::{dispatch::DispatchResult, pallet_prelude::*}; use frame_system::pallet_prelude::*; #[pallet::pallet] @@ -244,10 +244,11 @@ pub mod pallet { origin: OriginFor, delay: T::BlockNumber, best_finalized_block_number: T::BlockNumber, - ) -> DispatchResultWithPostInfo { + ) -> DispatchResult { ensure_root(origin)?; - Ok(Self::on_stalled(delay, best_finalized_block_number).into()) + Self::on_stalled(delay, best_finalized_block_number); + Ok(()) } } @@ -423,7 +424,7 @@ impl Pallet { Ok(()) } else { - Err(Error::::PauseFailed)? + Err(Error::::PauseFailed.into()) } } @@ -435,7 +436,7 @@ impl Pallet { Ok(()) } else { - Err(Error::::ResumeFailed)? + Err(Error::::ResumeFailed.into()) } } @@ -461,9 +462,9 @@ impl Pallet { if !>::exists() { let scheduled_at = >::block_number(); - if let Some(_) = forced { + if forced.is_some() { if Self::next_forced().map_or(false, |next| next > scheduled_at) { - Err(Error::::TooSoon)? + return Err(Error::::TooSoon.into()) } // only allow the next forced change when twice the window has passed since @@ -488,7 +489,7 @@ impl Pallet { Ok(()) } else { - Err(Error::::ChangePending)? + Err(Error::::ChangePending.into()) } } @@ -544,14 +545,14 @@ impl Pallet { let previous_set_id_session_index = if set_id == 0 { None } else { - let session_index = Self::session_for_set(set_id - 1) - .ok_or_else(|| Error::::InvalidEquivocationProof)?; + let session_index = + Self::session_for_set(set_id - 1).ok_or(Error::::InvalidEquivocationProof)?; Some(session_index) }; let set_id_session_index = - Self::session_for_set(set_id).ok_or_else(|| Error::::InvalidEquivocationProof)?; + Self::session_for_set(set_id).ok_or(Error::::InvalidEquivocationProof)?; // check that the session id for the membership proof is within the // bounds of the set id reported in the equivocation. diff --git a/frame/identity/src/benchmarking.rs b/frame/identity/src/benchmarking.rs index 2145779ecf541..b225db4edfa91 100644 --- a/frame/identity/src/benchmarking.rs +++ b/frame/identity/src/benchmarking.rs @@ -39,11 +39,7 @@ fn add_registrars(r: u32) -> Result<(), &'static str> { let registrar: T::AccountId = account("registrar", i, SEED); let _ = T::Currency::make_free_balance_be(®istrar, BalanceOf::::max_value()); Identity::::add_registrar(RawOrigin::Root.into(), registrar.clone())?; - Identity::::set_fee( - RawOrigin::Signed(registrar.clone()).into(), - i.into(), - 10u32.into(), - )?; + Identity::::set_fee(RawOrigin::Signed(registrar.clone()).into(), i, 10u32.into())?; let fields = IdentityFields( IdentityField::Display | @@ -52,7 +48,7 @@ fn add_registrars(r: u32) -> Result<(), &'static str> { IdentityField::PgpFingerprint | IdentityField::Image | IdentityField::Twitter, ); - Identity::::set_fields(RawOrigin::Signed(registrar.clone()).into(), i.into(), fields)?; + Identity::::set_fields(RawOrigin::Signed(registrar.clone()).into(), i, fields)?; } assert_eq!(Registrars::::get().len(), r as usize); @@ -75,9 +71,9 @@ fn create_sub_accounts( } // Set identity so `set_subs` does not fail. - let _ = T::Currency::make_free_balance_be(&who, BalanceOf::::max_value() / 2u32.into()); + let _ = T::Currency::make_free_balance_be(who, BalanceOf::::max_value() / 2u32.into()); let info = create_identity_info::(1); - Identity::::set_identity(who_origin.clone().into(), Box::new(info))?; + Identity::::set_identity(who_origin.into(), Box::new(info))?; Ok(subs) } @@ -101,7 +97,7 @@ fn add_sub_accounts( fn create_identity_info(num_fields: u32) -> IdentityInfo { let data = Data::Raw(vec![0; 32].try_into().unwrap()); - let info = IdentityInfo { + IdentityInfo { additional: vec![(data.clone(), data.clone()); num_fields as usize].try_into().unwrap(), display: data.clone(), legal: data.clone(), @@ -110,10 +106,8 @@ fn create_identity_info(num_fields: u32) -> IdentityInfo::add_registrar(RawOrigin::Root.into(), caller.clone())?; let registrars = Registrars::::get(); - ensure!(registrars[r as usize].as_ref().unwrap().account == caller.clone(), "id not set."); + ensure!(registrars[r as usize].as_ref().unwrap().account == caller, "id not set."); }: _(RawOrigin::Signed(caller), r, account("new", 0, SEED)) verify { let registrars = Registrars::::get(); @@ -325,7 +319,7 @@ benchmarks! { }; Identity::::add_registrar(RawOrigin::Root.into(), caller.clone())?; - Identity::::request_judgement(user_origin.clone(), r, 10u32.into())?; + Identity::::request_judgement(user_origin, r, 10u32.into())?; }: _(RawOrigin::Signed(caller), r, user_lookup, Judgement::Reasonable) verify { assert_last_event::(Event::::JudgementGiven { target: user, registrar_index: r }.into()) diff --git a/frame/identity/src/lib.rs b/frame/identity/src/lib.rs index 904b71654b416..46f847606903d 100644 --- a/frame/identity/src/lib.rs +++ b/frame/identity/src/lib.rs @@ -324,8 +324,8 @@ pub mod pallet { /// - One event. /// # #[pallet::weight( T::WeightInfo::set_identity( - T::MaxRegistrars::get().into(), // R - T::MaxAdditionalFields::get().into(), // X + T::MaxRegistrars::get(), // R + T::MaxAdditionalFields::get(), // X ))] pub fn set_identity( origin: OriginFor, @@ -416,7 +416,7 @@ pub mod pallet { let new_deposit = T::SubAccountDeposit::get() * >::from(subs.len() as u32); let not_other_sub = - subs.iter().filter_map(|i| SuperOf::::get(&i.0)).all(|i| &i.0 == &sender); + subs.iter().filter_map(|i| SuperOf::::get(&i.0)).all(|i| i.0 == sender); ensure!(not_other_sub, Error::::AlreadyClaimed); if old_deposit < new_deposit { @@ -470,9 +470,9 @@ pub mod pallet { /// - One event. /// # #[pallet::weight(T::WeightInfo::clear_identity( - T::MaxRegistrars::get().into(), // R - T::MaxSubAccounts::get().into(), // S - T::MaxAdditionalFields::get().into(), // X + T::MaxRegistrars::get(), // R + T::MaxSubAccounts::get(), // S + T::MaxAdditionalFields::get(), // X ))] pub fn clear_identity(origin: OriginFor) -> DispatchResultWithPostInfo { let sender = ensure_signed(origin)?; @@ -484,7 +484,7 @@ pub mod pallet { >::remove(sub); } - let err_amount = T::Currency::unreserve(&sender, deposit.clone()); + let err_amount = T::Currency::unreserve(&sender, deposit); debug_assert!(err_amount.is_zero()); Self::deposit_event(Event::IdentityCleared { who: sender, deposit }); @@ -521,8 +521,8 @@ pub mod pallet { /// - One event. /// # #[pallet::weight(T::WeightInfo::request_judgement( - T::MaxRegistrars::get().into(), // R - T::MaxAdditionalFields::get().into(), // X + T::MaxRegistrars::get(), // R + T::MaxAdditionalFields::get(), // X ))] pub fn request_judgement( origin: OriginFor, @@ -542,7 +542,7 @@ pub mod pallet { match id.judgements.binary_search_by_key(®_index, |x| x.0) { Ok(i) => if id.judgements[i].1.is_sticky() { - Err(Error::::StickyJudgement)? + return Err(Error::::StickyJudgement.into()) } else { id.judgements[i] = item }, @@ -583,8 +583,8 @@ pub mod pallet { /// - One event /// # #[pallet::weight(T::WeightInfo::cancel_request( - T::MaxRegistrars::get().into(), // R - T::MaxAdditionalFields::get().into(), // X + T::MaxRegistrars::get(), // R + T::MaxAdditionalFields::get(), // X ))] pub fn cancel_request( origin: OriginFor, @@ -600,7 +600,7 @@ pub mod pallet { let fee = if let Judgement::FeePaid(fee) = id.judgements.remove(pos).1 { fee } else { - Err(Error::::JudgementGiven)? + return Err(Error::::JudgementGiven.into()) }; let err_amount = T::Currency::unreserve(&sender, fee); @@ -754,8 +754,8 @@ pub mod pallet { /// - One event. /// # #[pallet::weight(T::WeightInfo::provide_judgement( - T::MaxRegistrars::get().into(), // R - T::MaxAdditionalFields::get().into(), // X + T::MaxRegistrars::get(), // R + T::MaxAdditionalFields::get(), // X ))] pub fn provide_judgement( origin: OriginFor, @@ -821,9 +821,9 @@ pub mod pallet { /// - One event. /// # #[pallet::weight(T::WeightInfo::kill_identity( - T::MaxRegistrars::get().into(), // R - T::MaxSubAccounts::get().into(), // S - T::MaxAdditionalFields::get().into(), // X + T::MaxRegistrars::get(), // R + T::MaxSubAccounts::get(), // S + T::MaxAdditionalFields::get(), // X ))] pub fn kill_identity( origin: OriginFor, diff --git a/frame/identity/src/types.rs b/frame/identity/src/types.rs index cb8091fe18747..b1f15da3b1117 100644 --- a/frame/identity/src/types.rs +++ b/frame/identity/src/types.rs @@ -217,20 +217,14 @@ impl bool { - match self { - Judgement::FeePaid(_) => true, - _ => false, - } + matches!(self, Judgement::FeePaid(_)) } /// Returns `true` if this judgement is one that should not be generally be replaced outside /// of specialized handlers. Examples include "malicious" judgements and deposit-holding /// judgements. pub(crate) fn is_sticky(&self) -> bool { - match self { - Judgement::FeePaid(_) | Judgement::Erroneous => true, - _ => false, - } + matches!(self, Judgement::FeePaid(_) | Judgement::Erroneous) } } diff --git a/frame/im-online/src/lib.rs b/frame/im-online/src/lib.rs index 5ef515db0b236..05b7618b286a6 100644 --- a/frame/im-online/src/lib.rs +++ b/frame/im-online/src/lib.rs @@ -508,9 +508,9 @@ pub mod pallet { Ok(()) } else if exists { - Err(Error::::DuplicatedHeartbeat)? + Err(Error::::DuplicatedHeartbeat.into()) } else { - Err(Error::::InvalidKey)? + Err(Error::::InvalidKey.into()) } } } @@ -573,7 +573,7 @@ pub mod pallet { // check signature (this is expensive so we do it last). let signature_valid = heartbeat.using_encoded(|encoded_heartbeat| { - authority_id.verify(&encoded_heartbeat, &signature) + authority_id.verify(&encoded_heartbeat, signature) }); if !signature_valid { diff --git a/frame/indices/src/lib.rs b/frame/indices/src/lib.rs index db983a802cadf..ddc03c94b1233 100644 --- a/frame/indices/src/lib.rs +++ b/frame/indices/src/lib.rs @@ -142,7 +142,7 @@ pub mod pallet { Accounts::::try_mutate(index, |maybe_value| -> DispatchResult { let (account, amount, perm) = maybe_value.take().ok_or(Error::::NotAssigned)?; ensure!(!perm, Error::::Permanent); - ensure!(&account == &who, Error::::NotOwner); + ensure!(account == who, Error::::NotOwner); let lost = T::Currency::repatriate_reserved(&who, &new, amount, Reserved)?; *maybe_value = Some((new.clone(), amount.saturating_sub(lost), false)); Ok(()) @@ -176,7 +176,7 @@ pub mod pallet { Accounts::::try_mutate(index, |maybe_value| -> DispatchResult { let (account, amount, perm) = maybe_value.take().ok_or(Error::::NotAssigned)?; ensure!(!perm, Error::::Permanent); - ensure!(&account == &who, Error::::NotOwner); + ensure!(account == who, Error::::NotOwner); T::Currency::unreserve(&who, amount); Ok(()) })?; @@ -249,7 +249,7 @@ pub mod pallet { Accounts::::try_mutate(index, |maybe_value| -> DispatchResult { let (account, amount, perm) = maybe_value.take().ok_or(Error::::NotAssigned)?; ensure!(!perm, Error::::Permanent); - ensure!(&account == &who, Error::::NotOwner); + ensure!(account == who, Error::::NotOwner); T::Currency::slash_reserved(&who, amount); *maybe_value = Some((account, Zero::zero(), true)); Ok(()) diff --git a/frame/lottery/src/lib.rs b/frame/lottery/src/lib.rs index c9a508372ca85..bc96a029a4210 100644 --- a/frame/lottery/src/lib.rs +++ b/frame/lottery/src/lib.rs @@ -108,7 +108,7 @@ impl ValidateCall for () { impl ValidateCall for Pallet { fn validate_call(call: &::Call) -> bool { let valid_calls = CallIndices::::get(); - let call_index = match Self::call_to_index(&call) { + let call_index = match Self::call_to_index(call) { Ok(call_index) => call_index, Err(_) => return false, }; @@ -278,7 +278,7 @@ pub mod pallet { // but is not used if it is not relevant. } } - return T::DbWeight::get().reads(1) + T::DbWeight::get().reads(1) }) } } @@ -418,9 +418,9 @@ impl Pallet { fn call_to_index(call: &::Call) -> Result { let encoded_call = call.encode(); if encoded_call.len() < 2 { - Err(Error::::EncodingFailed)? + return Err(Error::::EncodingFailed.into()) } - return Ok((encoded_call[0], encoded_call[1])) + Ok((encoded_call[0], encoded_call[1])) } /// Logic for buying a ticket. diff --git a/frame/membership/src/lib.rs b/frame/membership/src/lib.rs index 5a9c0a461cdac..ad6273e78f0f2 100644 --- a/frame/membership/src/lib.rs +++ b/frame/membership/src/lib.rs @@ -377,7 +377,7 @@ mod benchmark { let m in 1 .. T::MaxMembers::get(); let members = (0..m).map(|i| account("member", i, SEED)).collect::>(); - set_members::(members.clone(), None); + set_members::(members, None); let new_member = account::("add", m, SEED); }: { assert_ok!(>::add_member(T::AddOrigin::successful_origin(), new_member.clone())); diff --git a/frame/multisig/src/benchmarking.rs b/frame/multisig/src/benchmarking.rs index 43abb349b7951..8201426f5330f 100644 --- a/frame/multisig/src/benchmarking.rs +++ b/frame/multisig/src/benchmarking.rs @@ -45,7 +45,7 @@ fn setup_multi( let call: ::Call = frame_system::Call::::remark { remark: vec![0; z as usize] }.into(); let call_data = OpaqueCall::::from_encoded(call.encode()); - return Ok((signatories, call_data)) + Ok((signatories, call_data)) } benchmarks! { @@ -74,7 +74,7 @@ benchmarks! { // Transaction Length let z in 0 .. 10_000; let (mut signatories, call) = setup_multi::(s, z)?; - let call_hash = blake2_256(&call.encoded()); + let call_hash = blake2_256(call.encoded()); let multi_account_id = Multisig::::multi_account_id(&signatories, s.try_into().unwrap()); let caller = signatories.pop().ok_or("signatories should have len 2 or more")?; // Whitelist caller account from further DB operations. @@ -92,7 +92,7 @@ benchmarks! { // Transaction Length let z in 0 .. 10_000; let (mut signatories, call) = setup_multi::(s, z)?; - let call_hash = blake2_256(&call.encoded()); + let call_hash = blake2_256(call.encoded()); let multi_account_id = Multisig::::multi_account_id(&signatories, s.try_into().unwrap()); let caller = signatories.pop().ok_or("signatories should have len 2 or more")?; T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); @@ -111,7 +111,7 @@ benchmarks! { // Transaction Length let z in 0 .. 10_000; let (mut signatories, call) = setup_multi::(s, z)?; - let call_hash = blake2_256(&call.encoded()); + let call_hash = blake2_256(call.encoded()); let multi_account_id = Multisig::::multi_account_id(&signatories, s.try_into().unwrap()); let mut signatories2 = signatories.clone(); let caller = signatories.pop().ok_or("signatories should have len 2 or more")?; @@ -136,7 +136,7 @@ benchmarks! { // Transaction Length let z in 0 .. 10_000; let (mut signatories, call) = setup_multi::(s, z)?; - let call_hash = blake2_256(&call.encoded()); + let call_hash = blake2_256(call.encoded()); let multi_account_id = Multisig::::multi_account_id(&signatories, s.try_into().unwrap()); let mut signatories2 = signatories.clone(); let caller = signatories.pop().ok_or("signatories should have len 2 or more")?; @@ -162,7 +162,7 @@ benchmarks! { // Transaction Length let z in 0 .. 10_000; let (mut signatories, call) = setup_multi::(s, z)?; - let call_hash = blake2_256(&call.encoded()); + let call_hash = blake2_256(call.encoded()); let multi_account_id = Multisig::::multi_account_id(&signatories, s.try_into().unwrap()); let mut signatories2 = signatories.clone(); let caller = signatories.pop().ok_or("signatories should have len 2 or more")?; @@ -195,7 +195,7 @@ benchmarks! { let (mut signatories, call) = setup_multi::(s, z)?; let multi_account_id = Multisig::::multi_account_id(&signatories, s.try_into().unwrap()); let caller = signatories.pop().ok_or("signatories should have len 2 or more")?; - let call_hash = blake2_256(&call.encoded()); + let call_hash = blake2_256(call.encoded()); // Whitelist caller account from further DB operations. let caller_key = frame_system::Account::::hashed_key_for(&caller); frame_benchmarking::benchmarking::add_to_whitelist(caller_key.into()); @@ -214,16 +214,16 @@ benchmarks! { let mut signatories2 = signatories.clone(); let multi_account_id = Multisig::::multi_account_id(&signatories, s.try_into().unwrap()); let caller = signatories.pop().ok_or("signatories should have len 2 or more")?; - let call_hash = blake2_256(&call.encoded()); + let call_hash = blake2_256(call.encoded()); // before the call, get the timepoint let timepoint = Multisig::::timepoint(); // Create the multi Multisig::::as_multi( - RawOrigin::Signed(caller.clone()).into(), + RawOrigin::Signed(caller).into(), s as u16, signatories, None, - call.clone(), + call, false, 0 )?; @@ -247,7 +247,7 @@ benchmarks! { let mut signatories2 = signatories.clone(); let caller = signatories.pop().ok_or("signatories should have len 2 or more")?; T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); - let call_hash = blake2_256(&call.encoded()); + let call_hash = blake2_256(call.encoded()); // before the call, get the timepoint let timepoint = Multisig::::timepoint(); // Create the multi @@ -284,11 +284,11 @@ benchmarks! { let (mut signatories, call) = setup_multi::(s, z)?; let multi_account_id = Multisig::::multi_account_id(&signatories, s.try_into().unwrap()); let caller = signatories.pop().ok_or("signatories should have len 2 or more")?; - let call_hash = blake2_256(&call.encoded()); + let call_hash = blake2_256(call.encoded()); let timepoint = Multisig::::timepoint(); // Create the multi let o = RawOrigin::Signed(caller.clone()).into(); - Multisig::::as_multi(o, s as u16, signatories.clone(), None, call.clone(), true, 0)?; + Multisig::::as_multi(o, s as u16, signatories.clone(), None, call, true, 0)?; assert!(Multisigs::::contains_key(&multi_account_id, call_hash)); assert!(Calls::::contains_key(call_hash)); // Whitelist caller account from further DB operations. diff --git a/frame/multisig/src/lib.rs b/frame/multisig/src/lib.rs index cd59ea881739d..d4ea041e5820e 100644 --- a/frame/multisig/src/lib.rs +++ b/frame/multisig/src/lib.rs @@ -295,8 +295,7 @@ pub mod pallet { let weight_used = T::WeightInfo::as_multi_threshold_1(call_len as u32) .saturating_add(actual_weight); let post_info = Some(weight_used).into(); - let error = err.error.into(); - DispatchErrorWithPostInfo { post_info, error } + DispatchErrorWithPostInfo { post_info, error: err.error } }, None => err, }) diff --git a/frame/nicks/src/lib.rs b/frame/nicks/src/lib.rs index 632ff7b0a8083..5da06a24df3e5 100644 --- a/frame/nicks/src/lib.rs +++ b/frame/nicks/src/lib.rs @@ -147,7 +147,7 @@ pub mod pallet { deposit } else { let deposit = T::ReservationFee::get(); - T::Currency::reserve(&sender, deposit.clone())?; + T::Currency::reserve(&sender, deposit)?; Self::deposit_event(Event::::NameSet { who: sender.clone() }); deposit }; @@ -172,7 +172,7 @@ pub mod pallet { let deposit = >::take(&sender).ok_or(Error::::Unnamed)?.1; - let err_amount = T::Currency::unreserve(&sender, deposit.clone()); + let err_amount = T::Currency::unreserve(&sender, deposit); debug_assert!(err_amount.is_zero()); Self::deposit_event(Event::::NameCleared { who: sender, deposit }); @@ -204,7 +204,7 @@ pub mod pallet { // Grab their deposit (and check that they have one). let deposit = >::take(&target).ok_or(Error::::Unnamed)?.1; // Slash their deposit from them. - T::Slashed::on_unbalanced(T::Currency::slash_reserved(&target, deposit.clone()).0); + T::Slashed::on_unbalanced(T::Currency::slash_reserved(&target, deposit).0); Self::deposit_event(Event::::NameKilled { target, deposit }); Ok(()) diff --git a/frame/offences/benchmarking/src/lib.rs b/frame/offences/benchmarking/src/lib.rs index 8688090206c36..98c6390964d82 100644 --- a/frame/offences/benchmarking/src/lib.rs +++ b/frame/offences/benchmarking/src/lib.rs @@ -101,15 +101,14 @@ fn create_offender(n: u32, nominators: u32) -> Result, &' let controller: T::AccountId = account("controller", n, SEED); let controller_lookup: LookupSourceOf = T::Lookup::unlookup(controller.clone()); let reward_destination = RewardDestination::Staked; - let raw_amount = bond_amount::(); + let amount = bond_amount::(); // add twice as much balance to prevent the account from being killed. - let free_amount = raw_amount.saturating_mul(2u32.into()); + let free_amount = amount.saturating_mul(2u32.into()); T::Currency::make_free_balance_be(&stash, free_amount); - let amount: BalanceOf = raw_amount.into(); Staking::::bond( RawOrigin::Signed(stash.clone()).into(), controller_lookup.clone(), - amount.clone(), + amount, reward_destination.clone(), )?; @@ -127,12 +126,12 @@ fn create_offender(n: u32, nominators: u32) -> Result, &' account("nominator controller", n * MAX_NOMINATORS + i, SEED); let nominator_controller_lookup: LookupSourceOf = T::Lookup::unlookup(nominator_controller.clone()); - T::Currency::make_free_balance_be(&nominator_stash, free_amount.into()); + T::Currency::make_free_balance_be(&nominator_stash, free_amount); Staking::::bond( RawOrigin::Signed(nominator_stash.clone()).into(), nominator_controller_lookup.clone(), - amount.clone(), + amount, reward_destination.clone(), )?; @@ -143,14 +142,13 @@ fn create_offender(n: u32, nominators: u32) -> Result, &' )?; individual_exposures - .push(IndividualExposure { who: nominator_stash.clone(), value: amount.clone() }); + .push(IndividualExposure { who: nominator_stash.clone(), value: amount }); nominator_stashes.push(nominator_stash.clone()); } - let exposure = - Exposure { total: amount.clone() * n.into(), own: amount, others: individual_exposures }; + let exposure = Exposure { total: amount * n.into(), own: amount, others: individual_exposures }; let current_era = 0u32; - Staking::::add_era_stakers(current_era.into(), stash.clone().into(), exposure); + Staking::::add_era_stakers(current_era, stash.clone(), exposure); Ok(Offender { controller, stash, nominator_stashes }) } @@ -327,13 +325,13 @@ benchmarks! { .flat_map(|offender| { let nom_slashes = offender.nominator_stashes.into_iter().flat_map(|nom| { balance_slash(nom.clone()).map(Into::into) - .chain(slash(nom.clone()).map(Into::into)) - }).collect::>(); + .chain(slash(nom).map(Into::into)) + }); let mut events = chill(offender.stash.clone()).map(Into::into) .chain(balance_slash(offender.stash.clone()).map(Into::into)) - .chain(slash(offender.stash.clone()).map(Into::into)) - .chain(nom_slashes.into_iter()) + .chain(slash(offender.stash).map(Into::into)) + .chain(nom_slashes) .collect::>(); // the first deposit creates endowed events, see `endowed_reward_events` @@ -341,10 +339,10 @@ benchmarks! { first = false; let mut reward_events = reporters.clone().into_iter() .flat_map(|reporter| vec![ - balance_deposit(reporter.clone(), reward.into()).into(), + balance_deposit(reporter.clone(), reward).into(), frame_system::Event::::NewAccount { account: reporter.clone() }.into(), ::Event::from( - pallet_balances::Event::::Endowed{account: reporter.clone(), free_balance: reward.into()} + pallet_balances::Event::::Endowed{account: reporter, free_balance: reward.into()} ).into(), ]) .collect::>(); @@ -352,7 +350,7 @@ benchmarks! { events.into_iter() } else { let mut reward_events = reporters.clone().into_iter() - .map(|reporter| balance_deposit(reporter, reward.into()).into()) + .map(|reporter| balance_deposit(reporter, reward).into()) .collect::>(); events.append(&mut reward_events); events.into_iter() diff --git a/frame/offences/src/lib.rs b/frame/offences/src/lib.rs index 6119fce7769e9..a6d69e2a5e268 100644 --- a/frame/offences/src/lib.rs +++ b/frame/offences/src/lib.rs @@ -136,8 +136,7 @@ where // The amount new offenders are slashed let new_fraction = O::slash_fraction(offenders_count, validator_set_count); - let slash_perbill: Vec<_> = - (0..concurrent_offenders.len()).map(|_| new_fraction.clone()).collect(); + let slash_perbill: Vec<_> = (0..concurrent_offenders.len()).map(|_| new_fraction).collect(); T::OnOffenceHandler::on_offence( &concurrent_offenders, @@ -202,7 +201,7 @@ impl Pallet { let concurrent_offenders = storage .concurrent_reports .iter() - .filter_map(|report_id| >::get(report_id)) + .filter_map(>::get) .collect::>(); storage.save(); diff --git a/frame/offences/src/migration.rs b/frame/offences/src/migration.rs index 72c24ef7c3e5b..0d6c98b564cb1 100644 --- a/frame/offences/src/migration.rs +++ b/frame/offences/src/migration.rs @@ -42,8 +42,8 @@ pub fn remove_deferred_storage() -> Weight { log::info!(target: "runtime::offences", "have {} deferred offences, applying.", deferred.len()); for (offences, perbill, session) in deferred.iter() { let consumed = T::OnOffenceHandler::on_offence( - &offences, - &perbill, + offences, + perbill, *session, DisableStrategy::WhenSlashed, ); diff --git a/frame/preimage/src/benchmarking.rs b/frame/preimage/src/benchmarking.rs index 64326108420e8..e0d7e9614abbc 100644 --- a/frame/preimage/src/benchmarking.rs +++ b/frame/preimage/src/benchmarking.rs @@ -62,7 +62,7 @@ benchmarks! { let caller = funded_account::("caller", 0); whitelist_account!(caller); let (preimage, hash) = sized_preimage_and_hash::(s); - assert_ok!(Preimage::::request_preimage(T::ManagerOrigin::successful_origin(), hash.clone())); + assert_ok!(Preimage::::request_preimage(T::ManagerOrigin::successful_origin(), hash)); }: note_preimage(RawOrigin::Signed(caller), preimage) verify { assert!(Preimage::::have_preimage(&hash)); @@ -71,7 +71,7 @@ benchmarks! { note_no_deposit_preimage { let s in 0 .. T::MaxSize::get(); let (preimage, hash) = sized_preimage_and_hash::(s); - assert_ok!(Preimage::::request_preimage(T::ManagerOrigin::successful_origin(), hash.clone())); + assert_ok!(Preimage::::request_preimage(T::ManagerOrigin::successful_origin(), hash)); }: note_preimage(T::ManagerOrigin::successful_origin(), preimage) verify { assert!(Preimage::::have_preimage(&hash)); @@ -83,7 +83,7 @@ benchmarks! { whitelist_account!(caller); let (preimage, hash) = preimage_and_hash::(); assert_ok!(Preimage::::note_preimage(RawOrigin::Signed(caller.clone()).into(), preimage)); - }: _(RawOrigin::Signed(caller), hash.clone()) + }: _(RawOrigin::Signed(caller), hash) verify { assert!(!Preimage::::have_preimage(&hash)); } @@ -91,7 +91,7 @@ benchmarks! { unnote_no_deposit_preimage { let (preimage, hash) = preimage_and_hash::(); assert_ok!(Preimage::::note_preimage(T::ManagerOrigin::successful_origin(), preimage)); - }: unnote_preimage(T::ManagerOrigin::successful_origin(), hash.clone()) + }: unnote_preimage(T::ManagerOrigin::successful_origin(), hash) verify { assert!(!Preimage::::have_preimage(&hash)); } @@ -124,7 +124,7 @@ benchmarks! { // Cheap request - the preimage is already requested, so just a counter bump. request_requested_preimage { let (_, hash) = preimage_and_hash::(); - assert_ok!(Preimage::::request_preimage(T::ManagerOrigin::successful_origin(), hash.clone())); + assert_ok!(Preimage::::request_preimage(T::ManagerOrigin::successful_origin(), hash)); }: request_preimage(T::ManagerOrigin::successful_origin(), hash) verify { assert_eq!(StatusFor::::get(&hash), Some(RequestStatus::Requested(2))); @@ -133,26 +133,26 @@ benchmarks! { // Expensive unrequest - last reference and it's noted, so will destroy the preimage. unrequest_preimage { let (preimage, hash) = preimage_and_hash::(); - assert_ok!(Preimage::::request_preimage(T::ManagerOrigin::successful_origin(), hash.clone())); + assert_ok!(Preimage::::request_preimage(T::ManagerOrigin::successful_origin(), hash)); assert_ok!(Preimage::::note_preimage(T::ManagerOrigin::successful_origin(), preimage)); - }: _(T::ManagerOrigin::successful_origin(), hash.clone()) + }: _(T::ManagerOrigin::successful_origin(), hash) verify { assert_eq!(StatusFor::::get(&hash), None); } // Cheap unrequest - last reference, but it's not noted. unrequest_unnoted_preimage { let (_, hash) = preimage_and_hash::(); - assert_ok!(Preimage::::request_preimage(T::ManagerOrigin::successful_origin(), hash.clone())); - }: unrequest_preimage(T::ManagerOrigin::successful_origin(), hash.clone()) + assert_ok!(Preimage::::request_preimage(T::ManagerOrigin::successful_origin(), hash)); + }: unrequest_preimage(T::ManagerOrigin::successful_origin(), hash) verify { assert_eq!(StatusFor::::get(&hash), None); } // Cheap unrequest - not the last reference. unrequest_multi_referenced_preimage { let (_, hash) = preimage_and_hash::(); - assert_ok!(Preimage::::request_preimage(T::ManagerOrigin::successful_origin(), hash.clone())); - assert_ok!(Preimage::::request_preimage(T::ManagerOrigin::successful_origin(), hash.clone())); - }: unrequest_preimage(T::ManagerOrigin::successful_origin(), hash.clone()) + assert_ok!(Preimage::::request_preimage(T::ManagerOrigin::successful_origin(), hash)); + assert_ok!(Preimage::::request_preimage(T::ManagerOrigin::successful_origin(), hash)); + }: unrequest_preimage(T::ManagerOrigin::successful_origin(), hash) verify { assert_eq!(StatusFor::::get(&hash), Some(RequestStatus::Requested(1))); } diff --git a/frame/preimage/src/lib.rs b/frame/preimage/src/lib.rs index a5d8ee28b5952..09f6ecd52f9ad 100644 --- a/frame/preimage/src/lib.rs +++ b/frame/preimage/src/lib.rs @@ -215,7 +215,8 @@ impl Pallet { // previously requested. This also allows the tx to pay no fee. let was_requested = match (StatusFor::::get(hash), maybe_depositor) { (Some(RequestStatus::Requested(..)), _) => true, - (Some(RequestStatus::Unrequested(..)), _) => Err(Error::::AlreadyNoted)?, + (Some(RequestStatus::Unrequested(..)), _) => + return Err(Error::::AlreadyNoted.into()), (None, None) => { StatusFor::::insert(hash, RequestStatus::Unrequested(None)); false @@ -256,7 +257,7 @@ impl Pallet { }); StatusFor::::insert(hash, RequestStatus::Requested(count)); if count == 1 { - Self::deposit_event(Event::Requested { hash: hash.clone() }); + Self::deposit_event(Event::Requested { hash: *hash }); } } @@ -270,20 +271,17 @@ impl Pallet { ) -> DispatchResult { match StatusFor::::get(hash).ok_or(Error::::NotNoted)? { RequestStatus::Unrequested(Some((owner, deposit))) => { - ensure!( - maybe_check_owner.map_or(true, |c| &c == &owner), - Error::::NotAuthorized - ); + ensure!(maybe_check_owner.map_or(true, |c| c == owner), Error::::NotAuthorized); T::Currency::unreserve(&owner, deposit); }, RequestStatus::Unrequested(None) => { ensure!(maybe_check_owner.is_none(), Error::::NotAuthorized); }, - RequestStatus::Requested(_) => Err(Error::::Requested)?, + RequestStatus::Requested(_) => return Err(Error::::Requested.into()), } StatusFor::::remove(hash); PreimageFor::::remove(hash); - Self::deposit_event(Event::Cleared { hash: hash.clone() }); + Self::deposit_event(Event::Cleared { hash: *hash }); Ok(()) } @@ -298,9 +296,9 @@ impl Pallet { debug_assert!(count == 1, "preimage request counter at zero?"); PreimageFor::::remove(hash); StatusFor::::remove(hash); - Self::deposit_event(Event::Cleared { hash: hash.clone() }); + Self::deposit_event(Event::Cleared { hash: *hash }); }, - RequestStatus::Unrequested(_) => Err(Error::::NotRequested)?, + RequestStatus::Unrequested(_) => return Err(Error::::NotRequested.into()), } Ok(()) } diff --git a/frame/proxy/src/benchmarking.rs b/frame/proxy/src/benchmarking.rs index f3098c6ad1274..87017290a3ab9 100644 --- a/frame/proxy/src/benchmarking.rs +++ b/frame/proxy/src/benchmarking.rs @@ -32,7 +32,7 @@ fn assert_last_event(generic_event: ::Event) { } fn add_proxies(n: u32, maybe_who: Option) -> Result<(), &'static str> { - let caller = maybe_who.unwrap_or_else(|| whitelisted_caller()); + let caller = maybe_who.unwrap_or_else(whitelisted_caller); T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value() / 2u32.into()); for i in 0..n { Proxy::::add_proxy( @@ -77,7 +77,7 @@ fn add_announcements( benchmarks! { proxy { - let p in 1 .. (T::MaxProxies::get() - 1).into() => add_proxies::(p, None)?; + let p in 1 .. (T::MaxProxies::get() - 1) => add_proxies::(p, None)?; // In this case the caller is the "target" proxy let caller: T::AccountId = account("target", p - 1, SEED); T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value() / 2u32.into()); @@ -91,7 +91,7 @@ benchmarks! { proxy_announced { let a in 0 .. T::MaxPending::get() - 1; - let p in 1 .. (T::MaxProxies::get() - 1).into() => add_proxies::(p, None)?; + let p in 1 .. (T::MaxProxies::get() - 1) => add_proxies::(p, None)?; // In this case the caller is the "target" proxy let caller: T::AccountId = account("anonymous", 0, SEED); let delegate: T::AccountId = account("target", p - 1, SEED); @@ -112,7 +112,7 @@ benchmarks! { remove_announcement { let a in 0 .. T::MaxPending::get() - 1; - let p in 1 .. (T::MaxProxies::get() - 1).into() => add_proxies::(p, None)?; + let p in 1 .. (T::MaxProxies::get() - 1) => add_proxies::(p, None)?; // In this case the caller is the "target" proxy let caller: T::AccountId = account("target", p - 1, SEED); T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value() / 2u32.into()); @@ -133,7 +133,7 @@ benchmarks! { reject_announcement { let a in 0 .. T::MaxPending::get() - 1; - let p in 1 .. (T::MaxProxies::get() - 1).into() => add_proxies::(p, None)?; + let p in 1 .. (T::MaxProxies::get() - 1) => add_proxies::(p, None)?; // In this case the caller is the "target" proxy let caller: T::AccountId = account("target", p - 1, SEED); T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value() / 2u32.into()); @@ -154,7 +154,7 @@ benchmarks! { announce { let a in 0 .. T::MaxPending::get() - 1; - let p in 1 .. (T::MaxProxies::get() - 1).into() => add_proxies::(p, None)?; + let p in 1 .. (T::MaxProxies::get() - 1) => add_proxies::(p, None)?; // In this case the caller is the "target" proxy let caller: T::AccountId = account("target", p - 1, SEED); T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value() / 2u32.into()); @@ -169,11 +169,11 @@ benchmarks! { } add_proxy { - let p in 1 .. (T::MaxProxies::get() - 1).into() => add_proxies::(p, None)?; + let p in 1 .. (T::MaxProxies::get() - 1) => add_proxies::(p, None)?; let caller: T::AccountId = whitelisted_caller(); }: _( RawOrigin::Signed(caller.clone()), - account("target", T::MaxProxies::get().into(), SEED), + account("target", T::MaxProxies::get(), SEED), T::ProxyType::default(), T::BlockNumber::zero() ) @@ -183,7 +183,7 @@ benchmarks! { } remove_proxy { - let p in 1 .. (T::MaxProxies::get() - 1).into() => add_proxies::(p, None)?; + let p in 1 .. (T::MaxProxies::get() - 1) => add_proxies::(p, None)?; let caller: T::AccountId = whitelisted_caller(); }: _( RawOrigin::Signed(caller.clone()), @@ -197,7 +197,7 @@ benchmarks! { } remove_proxies { - let p in 1 .. (T::MaxProxies::get() - 1).into() => add_proxies::(p, None)?; + let p in 1 .. (T::MaxProxies::get() - 1) => add_proxies::(p, None)?; let caller: T::AccountId = whitelisted_caller(); }: _(RawOrigin::Signed(caller.clone())) verify { @@ -206,7 +206,7 @@ benchmarks! { } anonymous { - let p in 1 .. (T::MaxProxies::get() - 1).into() => add_proxies::(p, None)?; + let p in 1 .. (T::MaxProxies::get() - 1) => add_proxies::(p, None)?; let caller: T::AccountId = whitelisted_caller(); }: _( RawOrigin::Signed(caller.clone()), @@ -225,7 +225,7 @@ benchmarks! { } kill_anonymous { - let p in 0 .. (T::MaxProxies::get() - 2).into(); + let p in 0 .. (T::MaxProxies::get() - 2); let caller: T::AccountId = whitelisted_caller(); T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); diff --git a/frame/proxy/src/lib.rs b/frame/proxy/src/lib.rs index 1bab3626fb5a9..9945626efbeb1 100644 --- a/frame/proxy/src/lib.rs +++ b/frame/proxy/src/lib.rs @@ -196,7 +196,7 @@ pub mod pallet { /// # #[pallet::weight({ let di = call.get_dispatch_info(); - (T::WeightInfo::proxy(T::MaxProxies::get().into()) + (T::WeightInfo::proxy(T::MaxProxies::get()) .saturating_add(di.weight) // AccountData for inner call origin accountdata. .saturating_add(T::DbWeight::get().reads_writes(1, 1)), @@ -230,7 +230,7 @@ pub mod pallet { /// # /// Weight is a function of the number of proxies the user has (P). /// # - #[pallet::weight(T::WeightInfo::add_proxy(T::MaxProxies::get().into()))] + #[pallet::weight(T::WeightInfo::add_proxy(T::MaxProxies::get()))] pub fn add_proxy( origin: OriginFor, delegate: T::AccountId, @@ -252,7 +252,7 @@ pub mod pallet { /// # /// Weight is a function of the number of proxies the user has (P). /// # - #[pallet::weight(T::WeightInfo::remove_proxy(T::MaxProxies::get().into()))] + #[pallet::weight(T::WeightInfo::remove_proxy(T::MaxProxies::get()))] pub fn remove_proxy( origin: OriginFor, delegate: T::AccountId, @@ -273,7 +273,7 @@ pub mod pallet { /// # /// Weight is a function of the number of proxies the user has (P). /// # - #[pallet::weight(T::WeightInfo::remove_proxies(T::MaxProxies::get().into()))] + #[pallet::weight(T::WeightInfo::remove_proxies(T::MaxProxies::get()))] pub fn remove_proxies(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; let (_, old_deposit) = Proxies::::take(&who); @@ -305,7 +305,7 @@ pub mod pallet { /// Weight is a function of the number of proxies the user has (P). /// # /// TODO: Might be over counting 1 read - #[pallet::weight(T::WeightInfo::anonymous(T::MaxProxies::get().into()))] + #[pallet::weight(T::WeightInfo::anonymous(T::MaxProxies::get()))] pub fn anonymous( origin: OriginFor, proxy_type: T::ProxyType, @@ -356,7 +356,7 @@ pub mod pallet { /// # /// Weight is a function of the number of proxies the user has (P). /// # - #[pallet::weight(T::WeightInfo::kill_anonymous(T::MaxProxies::get().into()))] + #[pallet::weight(T::WeightInfo::kill_anonymous(T::MaxProxies::get()))] pub fn kill_anonymous( origin: OriginFor, spawner: T::AccountId, @@ -398,7 +398,7 @@ pub mod pallet { /// - A: the number of announcements made. /// - P: the number of proxies the user has. /// # - #[pallet::weight(T::WeightInfo::announce(T::MaxPending::get(), T::MaxProxies::get().into()))] + #[pallet::weight(T::WeightInfo::announce(T::MaxPending::get(), T::MaxProxies::get()))] pub fn announce( origin: OriginFor, real: T::AccountId, @@ -408,12 +408,12 @@ pub mod pallet { Proxies::::get(&real) .0 .into_iter() - .find(|x| &x.delegate == &who) + .find(|x| x.delegate == who) .ok_or(Error::::NotProxy)?; let announcement = Announcement { real: real.clone(), - call_hash: call_hash.clone(), + call_hash, height: system::Pallet::::block_number(), }; @@ -452,9 +452,10 @@ pub mod pallet { /// - A: the number of announcements made. /// - P: the number of proxies the user has. /// # - #[pallet::weight( - T::WeightInfo::remove_announcement(T::MaxPending::get(), T::MaxProxies::get().into()) - )] + #[pallet::weight(T::WeightInfo::remove_announcement( + T::MaxPending::get(), + T::MaxProxies::get() + ))] pub fn remove_announcement( origin: OriginFor, real: T::AccountId, @@ -482,9 +483,10 @@ pub mod pallet { /// - A: the number of announcements made. /// - P: the number of proxies the user has. /// # - #[pallet::weight( - T::WeightInfo::reject_announcement(T::MaxPending::get(), T::MaxProxies::get().into()) - )] + #[pallet::weight(T::WeightInfo::reject_announcement( + T::MaxPending::get(), + T::MaxProxies::get() + ))] pub fn reject_announcement( origin: OriginFor, delegate: T::AccountId, @@ -517,7 +519,7 @@ pub mod pallet { /// # #[pallet::weight({ let di = call.get_dispatch_info(); - (T::WeightInfo::proxy_announced(T::MaxPending::get(), T::MaxProxies::get().into()) + (T::WeightInfo::proxy_announced(T::MaxPending::get(), T::MaxProxies::get()) .saturating_add(di.weight) // AccountData for inner call origin accountdata. .saturating_add(T::DbWeight::get().reads_writes(1, 1)), @@ -759,9 +761,9 @@ impl Pallet { let new_deposit = if len == 0 { BalanceOf::::zero() } else { base + factor * (len as u32).into() }; if new_deposit > old_deposit { - T::Currency::reserve(&who, new_deposit - old_deposit)?; + T::Currency::reserve(who, new_deposit - old_deposit)?; } else if new_deposit < old_deposit { - T::Currency::unreserve(&who, old_deposit - new_deposit); + T::Currency::unreserve(who, old_deposit - new_deposit); } Ok(if len == 0 { None } else { Some(new_deposit) }) } @@ -816,7 +818,7 @@ impl Pallet { // has. Some(Call::add_proxy { ref proxy_type, .. }) | Some(Call::remove_proxy { ref proxy_type, .. }) - if !def.proxy_type.is_superset(&proxy_type) => + if !def.proxy_type.is_superset(proxy_type) => false, // Proxy call cannot remove all proxies or kill anonymous proxies unless it has full // permissions. diff --git a/frame/recovery/src/lib.rs b/frame/recovery/src/lib.rs index e75c9025760b0..b839d25e32b47 100644 --- a/frame/recovery/src/lib.rs +++ b/frame/recovery/src/lib.rs @@ -388,7 +388,7 @@ pub mod pallet { let who = ensure_signed(origin)?; // Check `who` is allowed to make a call on behalf of `account` let target = Self::proxy(&who).ok_or(Error::::NotAllowed)?; - ensure!(&target == &account, Error::::NotAllowed); + ensure!(target == account, Error::::NotAllowed); call.dispatch(frame_system::RawOrigin::Signed(account).into()) .map(|_| ()) .map_err(|e| e.error) @@ -541,7 +541,7 @@ pub mod pallet { ensure!(Self::is_friend(&recovery_config.friends, &who), Error::::NotFriend); // Either insert the vouch, or return an error that the user already vouched. match active_recovery.friends.binary_search(&who) { - Ok(_pos) => Err(Error::::AlreadyVouched)?, + Ok(_pos) => return Err(Error::::AlreadyVouched.into()), Err(pos) => active_recovery .friends .try_insert(pos, who.clone()) diff --git a/frame/referenda/src/benchmarking.rs b/frame/referenda/src/benchmarking.rs index 08612e0614aeb..87b13868db9d9 100644 --- a/frame/referenda/src/benchmarking.rs +++ b/frame/referenda/src/benchmarking.rs @@ -57,10 +57,7 @@ fn create_referendum() -> (T::AccountId, ReferendumIndex) { fn place_deposit(index: ReferendumIndex) { let caller = funded_account::("caller", 0); whitelist_account!(caller); - assert_ok!(Referenda::::place_decision_deposit( - RawOrigin::Signed(caller.clone()).into(), - index, - )); + assert_ok!(Referenda::::place_decision_deposit(RawOrigin::Signed(caller).into(), index)); } fn nudge(index: ReferendumIndex) { @@ -265,7 +262,7 @@ benchmarks! { let track = Referenda::::ensure_ongoing(index).unwrap().track; assert_ok!(Referenda::::cancel(T::CancelOrigin::successful_origin(), index)); assert_eq!(DecidingCount::::get(&track), 1); - }: one_fewer_deciding(RawOrigin::Root, track.clone()) + }: one_fewer_deciding(RawOrigin::Root, track) verify { assert_eq!(DecidingCount::::get(&track), 0); } @@ -278,7 +275,7 @@ benchmarks! { assert_ok!(Referenda::::cancel(T::CancelOrigin::successful_origin(), queued[0])); assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get()); let deciding_count = DecidingCount::::get(&track); - }: one_fewer_deciding(RawOrigin::Root, track.clone()) + }: one_fewer_deciding(RawOrigin::Root, track) verify { assert_eq!(DecidingCount::::get(&track), deciding_count); assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get() - 1); @@ -297,7 +294,7 @@ benchmarks! { assert_ok!(Referenda::::cancel(T::CancelOrigin::successful_origin(), queued[0])); assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get()); let deciding_count = DecidingCount::::get(&track); - }: one_fewer_deciding(RawOrigin::Root, track.clone()) + }: one_fewer_deciding(RawOrigin::Root, track) verify { assert_eq!(DecidingCount::::get(&track), deciding_count); assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get() - 1); diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index 067775fd336d0..b53be191d3525 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -363,7 +363,7 @@ pub mod pallet { let status = ReferendumStatus { track, origin: proposal_origin, - proposal_hash: proposal_hash.clone(), + proposal_hash, enactment: enactment_moment, submitted: now, submission_deposit, @@ -643,7 +643,7 @@ impl, I: 'static> Polling for Pallet { .iter() .max_by_key(|(_, info)| info.max_deciding) .expect("Always one class"); - (r.0.clone(), r.1.max_deciding) + (r.0, r.1.max_deciding) } } @@ -730,8 +730,8 @@ impl, I: 'static> Pallet { Self::deposit_event(Event::::DecisionStarted { index, tally: status.tally.clone(), - proposal_hash: status.proposal_hash.clone(), - track: status.track.clone(), + proposal_hash: status.proposal_hash, + track: status.track, }); let confirming = if is_passing { Self::deposit_event(Event::::ConfirmStarted { index }); @@ -895,7 +895,7 @@ impl, I: 'static> Pallet { let prepare_end = status.submitted.saturating_add(track.prepare_period); if now >= prepare_end { let (maybe_alarm, branch) = - Self::ready_for_deciding(now, &track, index, &mut status); + Self::ready_for_deciding(now, track, index, &mut status); if let Some(set_alarm) = maybe_alarm { alarm = alarm.min(set_alarm); } @@ -934,7 +934,7 @@ impl, I: 'static> Pallet { &track.min_approval, ); branch = if is_passing { - match deciding.confirming.clone() { + match deciding.confirming { Some(t) if now >= t => { // Passed! Self::ensure_no_alarm(&mut status); @@ -996,7 +996,7 @@ impl, I: 'static> Pallet { ServiceBranch::ContinueNotConfirming } }; - alarm = Self::decision_time(&deciding, &status.tally, track); + alarm = Self::decision_time(deciding, &status.tally, track); }, } diff --git a/frame/referenda/src/types.rs b/frame/referenda/src/types.rs index 622075100631b..5e0361c8fe160 100644 --- a/frame/referenda/src/types.rs +++ b/frame/referenda/src/types.rs @@ -182,7 +182,7 @@ pub trait TracksInfo { /// Return the track info for track `id`, by default this just looks it up in `Self::tracks()`. fn info(id: Self::Id) -> Option<&'static TrackInfo> { - Self::tracks().iter().find(|x| &x.0 == &id).map(|x| &x.1) + Self::tracks().iter().find(|x| x.0 == id).map(|x| &x.1) } } @@ -309,9 +309,9 @@ impl Curve { match self { Self::LinearDecreasing { begin, delta } => if delta.is_zero() { - return *delta + *delta } else { - return (*begin - y.min(*begin)).min(*delta) / *delta + (*begin - y.min(*begin)).min(*delta) / *delta }, } } diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index ec60cedc280b6..9b0fc87587e3a 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -727,7 +727,7 @@ impl Pallet { Self::deposit_event(Event::Canceled { when, index }); Ok(()) } else { - Err(Error::::NotFound)? + return Err(Error::::NotFound.into()) } } @@ -765,7 +765,7 @@ impl Pallet { ) -> Result, DispatchError> { // ensure id it is unique if Lookup::::contains_key(&id) { - return Err(Error::::FailedToSchedule)? + return Err(Error::::FailedToSchedule.into()) } let when = Self::resolve_time(when)?; @@ -817,7 +817,7 @@ impl Pallet { Self::deposit_event(Event::Canceled { when, index }); Ok(()) } else { - Err(Error::::NotFound)? + return Err(Error::::NotFound.into()) } }) } diff --git a/frame/scored-pool/src/lib.rs b/frame/scored-pool/src/lib.rs index 3f0674d720efd..abdb9b2acc9b5 100644 --- a/frame/scored-pool/src/lib.rs +++ b/frame/scored-pool/src/lib.rs @@ -256,7 +256,7 @@ pub mod pallet { // reserve balance for each candidate in the pool. // panicking here is ok, since this just happens one time, pre-genesis. pool.iter().for_each(|(who, _)| { - T::Currency::reserve(&who, T::CandidateDeposit::get()) + T::Currency::reserve(who, T::CandidateDeposit::get()) .expect("balance too low to create candidacy"); >::insert(who, true); }); @@ -387,7 +387,7 @@ pub mod pallet { // if there is already an element with `score`, we insert // right before that. if not, the search returns a location // where we can insert while maintaining order. - let item = (who, Some(score.clone())); + let item = (who, Some(score)); let location = pool .binary_search_by_key(&Reverse(score), |(_, maybe_score)| { Reverse(maybe_score.unwrap_or_default()) diff --git a/frame/session/src/historical/shared.rs b/frame/session/src/historical/shared.rs index 9e19b9df6d78d..faa91f7919860 100644 --- a/frame/session/src/historical/shared.rs +++ b/frame/session/src/historical/shared.rs @@ -27,13 +27,10 @@ pub(super) const LAST_PRUNE: &[u8] = b"session_historical_last_prune"; /// Derive the key used to store the list of validators pub(super) fn derive_key>(prefix: P, session_index: SessionIndex) -> Vec { - let prefix: &[u8] = prefix.as_ref(); session_index.using_encoded(|encoded_session_index| { - prefix - .into_iter() - .chain(b"/".into_iter()) - .chain(encoded_session_index.into_iter()) - .copied() - .collect::>() + let mut key = prefix.as_ref().to_owned(); + key.push(b'/'); + key.extend_from_slice(encoded_session_index); + key }) } diff --git a/frame/session/src/lib.rs b/frame/session/src/lib.rs index d6ecd17a8f2ae..a460c650ec602 100644 --- a/frame/session/src/lib.rs +++ b/frame/session/src/lib.rs @@ -184,7 +184,7 @@ impl< // (0% is never returned). let progress = if now >= offset { let current = (now - offset) % period.clone() + One::one(); - Some(Permill::from_rational(current.clone(), period.clone())) + Some(Permill::from_rational(current, period)) } else { Some(Permill::from_rational(now + One::one(), offset)) }; @@ -689,7 +689,6 @@ impl Pallet { if let Some(&(_, ref old_keys)) = now_session_keys.next() { if old_keys != keys { changed = true; - return } } }; @@ -803,10 +802,10 @@ impl Pallet { let who = T::ValidatorIdOf::convert(account.clone()) .ok_or(Error::::NoAssociatedValidatorId)?; - ensure!(frame_system::Pallet::::can_inc_consumer(&account), Error::::NoAccount); + ensure!(frame_system::Pallet::::can_inc_consumer(account), Error::::NoAccount); let old_keys = Self::inner_set_keys(&who, keys)?; if old_keys.is_none() { - let assertion = frame_system::Pallet::::inc_consumers(&account).is_ok(); + let assertion = frame_system::Pallet::::inc_consumers(account).is_ok(); debug_assert!(assertion, "can_inc_consumer() returned true; no change since; qed"); } @@ -866,7 +865,7 @@ impl Pallet { let key_data = old_keys.get_raw(*id); Self::clear_key_owner(*id, key_data); } - frame_system::Pallet::::dec_consumers(&account); + frame_system::Pallet::::dec_consumers(account); Ok(()) } @@ -949,6 +948,6 @@ impl> FindAuthor let i = Inner::find_author(digests)?; let validators = >::validators(); - validators.get(i as usize).map(|k| k.clone()) + validators.get(i as usize).cloned() } } diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index 6ad63d2102c53..645a0b8ba1715 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -357,10 +357,10 @@ impl BidKind { if a == v { Ok(()) } else { - Err("incorrect identity")? + Err("incorrect identity".into()) } } else { - Err("not vouched")? + Err("not vouched".into()) } } } @@ -724,7 +724,7 @@ pub mod pallet { let deposit = T::CandidateDeposit::get(); T::Currency::reserve(&who, deposit)?; - Self::put_bid(bids, &who, value.clone(), BidKind::Deposit(deposit)); + Self::put_bid(bids, &who, value, BidKind::Deposit(deposit)); Self::deposit_event(Event::::Bid { candidate_id: who, offer: value }); Ok(()) } @@ -770,7 +770,7 @@ pub mod pallet { Self::deposit_event(Event::::Unbid { candidate: who }); Ok(()) } else { - Err(Error::::BadPosition)? + Err(Error::::BadPosition.into()) } }) } @@ -844,7 +844,7 @@ pub mod pallet { ensure!(!>::contains_key(&voucher), Error::::AlreadyVouching); >::insert(&voucher, VouchingStatus::Vouching); - Self::put_bid(bids, &who, value.clone(), BidKind::Vouch(voucher.clone(), tip)); + Self::put_bid(bids, &who, value, BidKind::Vouch(voucher.clone(), tip)); Self::deposit_event(Event::::Vouch { candidate_id: who, offer: value, @@ -887,7 +887,7 @@ pub mod pallet { Self::deposit_event(Event::::Unvouch { candidate: who }); Ok(()) } else { - Err(Error::::BadPosition)? + Err(Error::::BadPosition.into()) } }) } @@ -1001,7 +1001,7 @@ pub mod pallet { return Ok(()) } } - Err(Error::::NoPayout)? + Err(Error::::NoPayout.into()) } /// Found the society. @@ -1229,7 +1229,7 @@ pub mod pallet { // Remove suspended candidate >::remove(who); } else { - Err(Error::::NotSuspended)? + return Err(Error::::NotSuspended.into()) } Ok(()) } @@ -1392,8 +1392,8 @@ impl, I: 'static> Pallet { ensure!(Self::founder() != Some(m.clone()), Error::::Founder); let mut members = >::get(); - match members.binary_search(&m) { - Err(_) => Err(Error::::NotMember)?, + match members.binary_search(m) { + Err(_) => Err(Error::::NotMember.into()), Ok(i) => { members.remove(i); T::MembershipChanged::change_members_sorted(&[], &[m.clone()], &members[..]); @@ -1572,7 +1572,7 @@ impl, I: 'static> Pallet { >::put(&members[..]); >::put(&primary); - T::MembershipChanged::change_members_sorted(&accounts, &[], &members); + T::MembershipChanged::change_members_sorted(&accounts, &[], members); Self::deposit_event(Event::::Inducted { primary, candidates: accounts }); } @@ -1605,7 +1605,7 @@ impl, I: 'static> Pallet { if !payouts.is_empty() { let mut dropped = 0; for (_, amount) in payouts.iter_mut() { - if let Some(new_rest) = rest.checked_sub(&amount) { + if let Some(new_rest) = rest.checked_sub(amount) { // not yet totally slashed after this one; drop it completely. rest = new_rest; dropped += 1; @@ -1635,7 +1635,7 @@ impl, I: 'static> Pallet { /// Suspend a user, removing them from the member list. fn suspend_member(who: &T::AccountId) { - if Self::remove_member(&who).is_ok() { + if Self::remove_member(who).is_ok() { >::insert(who, true); >::remove(who); Self::deposit_event(Event::::MemberSuspended { member: who.clone() }); @@ -1683,12 +1683,10 @@ impl, I: 'static> Pallet { let mut approval_count = 0; let mut rejection_count = 0; // Tallies total number of approve and reject votes for the defender. - members.iter().filter_map(|m| >::take(m)).for_each( - |v| match v { - Vote::Approve => approval_count += 1, - _ => rejection_count += 1, - }, - ); + members.iter().filter_map(>::take).for_each(|v| match v { + Vote::Approve => approval_count += 1, + _ => rejection_count += 1, + }); if approval_count <= rejection_count { // User has failed the challenge diff --git a/frame/staking/reward-fn/src/lib.rs b/frame/staking/reward-fn/src/lib.rs index cb0b660bf544b..cc9919c28cce3 100644 --- a/frame/staking/reward-fn/src/lib.rs +++ b/frame/staking/reward-fn/src/lib.rs @@ -137,20 +137,18 @@ fn compute_taylor_serie_part(p: &INPoSParam) -> BigUint { if taylor_sum_positive == last_taylor_term_positive { taylor_sum = taylor_sum.add(&last_taylor_term); + } else if taylor_sum >= last_taylor_term { + taylor_sum = taylor_sum + .sub(&last_taylor_term) + // NOTE: Should never happen as checked above + .unwrap_or_else(|e| e); } else { - if taylor_sum >= last_taylor_term { - taylor_sum = taylor_sum - .sub(&last_taylor_term) - // NOTE: Should never happen as checked above - .unwrap_or_else(|e| e); - } else { - taylor_sum_positive = !taylor_sum_positive; - taylor_sum = last_taylor_term - .clone() - .sub(&taylor_sum) - // NOTE: Should never happen as checked above - .unwrap_or_else(|e| e); - } + taylor_sum_positive = !taylor_sum_positive; + taylor_sum = last_taylor_term + .clone() + .sub(&taylor_sum) + // NOTE: Should never happen as checked above + .unwrap_or_else(|e| e); } } @@ -217,10 +215,10 @@ fn div_by_stripped(mut a: BigUint, b: &BigUint) -> BigUint { return new_a .div(b, false) .map(|res| res.0) - .unwrap_or_else(|| BigUint::zero()) + .unwrap_or_else(BigUint::zero) .div_unit(100_000) .div_unit(100_000) } - a.div(b, false).map(|res| res.0).unwrap_or_else(|| BigUint::zero()) + a.div(b, false).map(|res| res.0).unwrap_or_else(BigUint::zero) } diff --git a/frame/staking/src/benchmarking.rs b/frame/staking/src/benchmarking.rs index 7321740cfa79f..eb9129ac4b436 100644 --- a/frame/staking/src/benchmarking.rs +++ b/frame/staking/src/benchmarking.rs @@ -183,8 +183,8 @@ impl ListScenario { Default::default(), )?; Staking::::nominate( - RawOrigin::Signed(origin_controller2.clone()).into(), - vec![T::Lookup::unlookup(account("random_validator", 0, SEED))].clone(), + RawOrigin::Signed(origin_controller2).into(), + vec![T::Lookup::unlookup(account("random_validator", 0, SEED))], )?; // find a destination weight that will trigger the worst case scenario @@ -239,10 +239,10 @@ benchmarks! { // the weight the nominator will start at. let scenario = ListScenario::::new(origin_weight, true)?; - let max_additional = scenario.dest_weight.clone() - origin_weight; + let max_additional = scenario.dest_weight - origin_weight; let stash = scenario.origin_stash1.clone(); - let controller = scenario.origin_controller1.clone(); + let controller = scenario.origin_controller1; let original_bonded: BalanceOf = Ledger::::get(&controller).map(|l| l.active).ok_or("ledger not created after")?; @@ -271,7 +271,7 @@ benchmarks! { let stash = scenario.origin_stash1.clone(); let controller = scenario.origin_controller1.clone(); - let amount = origin_weight - scenario.dest_weight.clone(); + let amount = origin_weight - scenario.dest_weight; let ledger = Ledger::::get(&controller).ok_or("ledger not created before")?; let original_bonded: BalanceOf = ledger.active; @@ -315,7 +315,7 @@ benchmarks! { // destination position because we are doing a removal from the list but no insert. let scenario = ListScenario::::new(origin_weight, true)?; let controller = scenario.origin_controller1.clone(); - let stash = scenario.origin_stash1.clone(); + let stash = scenario.origin_stash1; assert!(T::VoterList::contains(&stash)); let ed = T::Currency::minimum_balance(); @@ -450,7 +450,7 @@ benchmarks! { // destination position because we are doing a removal from the list but no insert. let scenario = ListScenario::::new(origin_weight, true)?; let controller = scenario.origin_controller1.clone(); - let stash = scenario.origin_stash1.clone(); + let stash = scenario.origin_stash1; assert!(T::VoterList::contains(&stash)); whitelist_account!(controller); @@ -518,7 +518,7 @@ benchmarks! { // destination position because we are doing a removal from the list but no insert. let scenario = ListScenario::::new(origin_weight, true)?; let controller = scenario.origin_controller1.clone(); - let stash = scenario.origin_stash1.clone(); + let stash = scenario.origin_stash1; assert!(T::VoterList::contains(&stash)); add_slashing_spans::(&stash, s); @@ -561,10 +561,10 @@ benchmarks! { let validator_controller = >::get(&validator).unwrap(); let balance_before = T::Currency::free_balance(&validator_controller); for (_, controller) in &nominators { - let balance = T::Currency::free_balance(&controller); + let balance = T::Currency::free_balance(controller); ensure!(balance.is_zero(), "Controller has balance, but should be dead."); } - }: payout_stakers(RawOrigin::Signed(caller), validator.clone(), current_era) + }: payout_stakers(RawOrigin::Signed(caller), validator, current_era) verify { let balance_after = T::Currency::free_balance(&validator_controller); ensure!( @@ -572,7 +572,7 @@ benchmarks! { "Balance of validator controller should have increased after payout.", ); for (_, controller) in &nominators { - let balance = T::Currency::free_balance(&controller); + let balance = T::Currency::free_balance(controller); ensure!(!balance.is_zero(), "Payout not given to controller."); } } @@ -594,7 +594,7 @@ benchmarks! { let balance_before = T::Currency::free_balance(&validator); let mut nominator_balances_before = Vec::new(); for (stash, _) in &nominators { - let balance = T::Currency::free_balance(&stash); + let balance = T::Currency::free_balance(stash); nominator_balances_before.push(balance); } }: payout_stakers(RawOrigin::Signed(caller), validator.clone(), current_era) @@ -605,7 +605,7 @@ benchmarks! { "Balance of validator stash should have increased after payout.", ); for ((stash, _), balance_before) in nominators.iter().zip(nominator_balances_before.iter()) { - let balance_after = T::Currency::free_balance(&stash); + let balance_after = T::Currency::free_balance(stash); ensure!( balance_before < &balance_after, "Balance of nominator stash should have increased after payout.", @@ -626,7 +626,7 @@ benchmarks! { // setup a worst case list scenario. let scenario = ListScenario::::new(origin_weight, true)?; - let dest_weight = scenario.dest_weight.clone(); + let dest_weight = scenario.dest_weight; // rebond an amount that will give the user dest_weight let rebond_amount = dest_weight - origin_weight; @@ -644,7 +644,7 @@ benchmarks! { }; let stash = scenario.origin_stash1.clone(); - let controller = scenario.origin_controller1.clone(); + let controller = scenario.origin_controller1; let mut staking_ledger = Ledger::::get(controller.clone()).unwrap(); for _ in 0 .. l { @@ -691,7 +691,7 @@ benchmarks! { // destination position because we are doing a removal from the list but no insert. let scenario = ListScenario::::new(origin_weight, true)?; let controller = scenario.origin_controller1.clone(); - let stash = scenario.origin_stash1.clone(); + let stash = scenario.origin_stash1; add_slashing_spans::(&stash, s); let l = StakingLedger { @@ -895,7 +895,7 @@ benchmarks! { // destination position because we are doing a removal from the list but no insert. let scenario = ListScenario::::new(origin_weight, true)?; let controller = scenario.origin_controller1.clone(); - let stash = scenario.origin_stash1.clone(); + let stash = scenario.origin_stash1; assert!(T::VoterList::contains(&stash)); Staking::::set_staking_configs( @@ -909,7 +909,7 @@ benchmarks! { )?; let caller = whitelisted_caller(); - }: _(RawOrigin::Signed(caller), controller.clone()) + }: _(RawOrigin::Signed(caller), controller) verify { assert!(!T::VoterList::contains(&stash)); } diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 037dcc86ab2a8..2d4c1ea9a3488 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -321,7 +321,7 @@ pub use weights::WeightInfo; pub use pallet::{pallet::*, *}; -pub(crate) const LOG_TARGET: &'static str = "runtime::staking"; +pub(crate) const LOG_TARGET: &str = "runtime::staking"; // syntactic sugar for logging. #[macro_export] @@ -784,7 +784,7 @@ impl (Balance, Balance) { let (validator_payout, max_payout) = inflation::compute_total_payout( - &T::get(), + T::get(), total_staked, total_issuance, // Duration of era; more than u64::MAX is rewarded as u64::MAX. diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 4665d956fdddb..0ac61e21fb323 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -123,8 +123,9 @@ impl Pallet { .claimed_rewards .retain(|&x| x >= current_era.saturating_sub(history_depth)); match ledger.claimed_rewards.binary_search(&era) { - Ok(_) => Err(Error::::AlreadyClaimed - .with_weight(T::WeightInfo::payout_stakers_alive_staked(0)))?, + Ok(_) => + return Err(Error::::AlreadyClaimed + .with_weight(T::WeightInfo::payout_stakers_alive_staked(0))), Err(pos) => ledger.claimed_rewards.insert(pos, era), } @@ -146,8 +147,8 @@ impl Pallet { let validator_reward_points = era_reward_points .individual .get(&ledger.stash) - .map(|points| *points) - .unwrap_or_else(|| Zero::zero()); + .copied() + .unwrap_or_else(Zero::zero); // Nothing to do if they have no reward points. if validator_reward_points.is_zero() { @@ -260,8 +261,7 @@ impl Pallet { 0 }); - let era_length = - session_index.checked_sub(current_era_start_session_index).unwrap_or(0); // Must never happen. + let era_length = session_index.saturating_sub(current_era_start_session_index); // Must never happen. match ForceEra::::get() { // Will be set to `NotForcing` again if a new era has been triggered. @@ -1036,7 +1036,7 @@ impl ElectionDataProvider for Pallet { ); Self::do_add_nominator( &v, - Nominations { targets: t.try_into().unwrap(), submitted_in: 0, suppressed: false }, + Nominations { targets: t, submitted_in: 0, suppressed: false }, ); }); } diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index bf13786d64ee6..117bc6e7c15bb 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -580,7 +580,7 @@ pub mod pallet { status ); assert!( - T::Currency::free_balance(&stash) >= balance, + T::Currency::free_balance(stash) >= balance, "Stash does not have enough balance to bond." ); frame_support::assert_ok!(>::bond( @@ -779,18 +779,18 @@ pub mod pallet { let stash = ensure_signed(origin)?; if >::contains_key(&stash) { - Err(Error::::AlreadyBonded)? + return Err(Error::::AlreadyBonded.into()) } let controller = T::Lookup::lookup(controller)?; if >::contains_key(&controller) { - Err(Error::::AlreadyPaired)? + return Err(Error::::AlreadyPaired.into()) } // Reject a bond which is considered to be _dust_. if value < T::Currency::minimum_balance() { - Err(Error::::InsufficientBond)? + return Err(Error::::InsufficientBond.into()) } frame_system::Pallet::::inc_consumers(&stash).map_err(|_| Error::::BadState)?; @@ -862,7 +862,7 @@ pub mod pallet { debug_assert_eq!(T::VoterList::sanity_check(), Ok(())); } - Self::deposit_event(Event::::Bonded(stash.clone(), extra)); + Self::deposit_event(Event::::Bonded(stash, extra)); } Ok(()) } @@ -1183,7 +1183,7 @@ pub mod pallet { let old_controller = Self::bonded(&stash).ok_or(Error::::NotStash)?; let controller = T::Lookup::lookup(controller)?; if >::contains_key(&controller) { - Err(Error::::AlreadyPaired)? + return Err(Error::::AlreadyPaired.into()) } if controller != old_controller { >::insert(&stash, &controller); @@ -1466,8 +1466,8 @@ pub mod pallet { ensure_root(origin)?; if let Some(current_era) = Self::current_era() { HistoryDepth::::mutate(|history_depth| { - let last_kept = current_era.checked_sub(*history_depth).unwrap_or(0); - let new_last_kept = current_era.checked_sub(new_history_depth).unwrap_or(0); + let last_kept = current_era.saturating_sub(*history_depth); + let new_last_kept = current_era.saturating_sub(new_history_depth); for era_index in last_kept..new_last_kept { Self::clear_era_information(era_index); } diff --git a/frame/staking/src/slashing.rs b/frame/staking/src/slashing.rs index 9fc50eaf538f6..0d55eb7fe0ddb 100644 --- a/frame/staking/src/slashing.rs +++ b/frame/staking/src/slashing.rs @@ -389,7 +389,7 @@ fn slash_nominators( let mut era_slash = as Store>::NominatorSlashInEra::get(¶ms.slash_era, stash) - .unwrap_or_else(|| Zero::zero()); + .unwrap_or_else(Zero::zero); era_slash += own_slash_difference; @@ -646,7 +646,7 @@ pub(crate) fn apply_slash( for &(ref nominator, nominator_slash) in &unapplied_slash.others { do_slash::( - &nominator, + nominator, nominator_slash, &mut reward_payout, &mut slashed_imbalance, diff --git a/frame/staking/src/testing_utils.rs b/frame/staking/src/testing_utils.rs index 5f9f378b10619..f30020597db45 100644 --- a/frame/staking/src/testing_utils.rs +++ b/frame/staking/src/testing_utils.rs @@ -85,7 +85,7 @@ pub fn create_stash_controller( amount, destination, )?; - return Ok((stash, controller)) + Ok((stash, controller)) } /// Create a stash and controller pair with fixed balance. @@ -127,7 +127,7 @@ pub fn create_stash_and_dead_controller( amount, destination, )?; - return Ok((stash, controller)) + Ok((stash, controller)) } /// create `max` validators. diff --git a/frame/state-trie-migration/src/lib.rs b/frame/state-trie-migration/src/lib.rs index 3d66c6a66b8cf..d78fd7d9ca932 100644 --- a/frame/state-trie-migration/src/lib.rs +++ b/frame/state-trie-migration/src/lib.rs @@ -57,7 +57,7 @@ pub use pallet::*; -const LOG_TARGET: &'static str = "runtime::state-trie-migration"; +const LOG_TARGET: &str = "runtime::state-trie-migration"; #[macro_export] macro_rules! log { @@ -319,12 +319,12 @@ pub mod pallet { let (maybe_current_child, child_root) = match (&self.progress_child, &self.progress_top) { (Progress::LastKey(last_child), Progress::LastKey(last_top)) => { - let child_root = Pallet::::transform_child_key_or_halt(&last_top); - let maybe_current_child = child_io::next_key(child_root, &last_child); + let child_root = Pallet::::transform_child_key_or_halt(last_top); + let maybe_current_child = child_io::next_key(child_root, last_child); (maybe_current_child, child_root) }, (Progress::ToStart, Progress::LastKey(last_top)) => { - let child_root = Pallet::::transform_child_key_or_halt(&last_top); + let child_root = Pallet::::transform_child_key_or_halt(last_top); // Start with the empty key as first key. (Some(Vec::new()), child_root) }, @@ -336,7 +336,7 @@ pub mod pallet { }; if let Some(current_child) = maybe_current_child.as_ref() { - let added_size = if let Some(data) = child_io::get(child_root, ¤t_child) { + let added_size = if let Some(data) = child_io::get(child_root, current_child) { child_io::set(child_root, current_child, &data); data.len() as u32 } else { @@ -369,8 +369,8 @@ pub mod pallet { }; if let Some(current_top) = maybe_current_top.as_ref() { - let added_size = if let Some(data) = sp_io::storage::get(¤t_top) { - sp_io::storage::set(¤t_top, &data); + let added_size = if let Some(data) = sp_io::storage::get(current_top) { + sp_io::storage::set(current_top, &data); data.len() as u32 } else { Zero::zero() @@ -503,7 +503,7 @@ pub mod pallet { ) -> DispatchResult { T::ControlOrigin::ensure_origin(origin)?; AutoLimits::::put(maybe_config); - Ok(().into()) + Ok(()) } /// Continue the migration for the given `limits`. @@ -616,7 +616,7 @@ pub mod pallet { let mut dyn_size = 0u32; for key in &keys { - if let Some(data) = sp_io::storage::get(&key) { + if let Some(data) = sp_io::storage::get(key) { dyn_size = dyn_size.saturating_add(data.len() as u32); sp_io::storage::set(key, &data); } @@ -678,9 +678,9 @@ pub mod pallet { let mut dyn_size = 0u32; let transformed_child_key = Self::transform_child_key(&root).ok_or("bad child key")?; for child_key in &child_keys { - if let Some(data) = child_io::get(transformed_child_key, &child_key) { + if let Some(data) = child_io::get(transformed_child_key, child_key) { dyn_size = dyn_size.saturating_add(data.len() as u32); - child_io::set(transformed_child_key, &child_key, &data); + child_io::set(transformed_child_key, child_key, &data); } } @@ -839,7 +839,7 @@ mod benchmarks { // The size of the key seemingly makes no difference in the read/write time, so we make it // constant. - const KEY: &'static [u8] = b"key"; + const KEY: &[u8] = b"key"; frame_benchmarking::benchmarks! { continue_migrate { diff --git a/frame/support/procedural/src/construct_runtime/expand/config.rs b/frame/support/procedural/src/construct_runtime/expand/config.rs index 79176fa7385ec..a3d70f18529c7 100644 --- a/frame/support/procedural/src/construct_runtime/expand/config.rs +++ b/frame/support/procedural/src/construct_runtime/expand/config.rs @@ -43,12 +43,8 @@ pub fn expand_outer_config( types.extend(expand_config_types(runtime, decl, &config, part_is_generic)); fields.extend(quote!(pub #field_name: #config,)); - build_storage_calls.extend(expand_config_build_storage_call( - scrate, - runtime, - decl, - &field_name, - )); + build_storage_calls + .extend(expand_config_build_storage_call(scrate, runtime, decl, field_name)); query_genesis_config_part_macros.push(quote! { #path::__substrate_genesis_config_check::is_genesis_config_defined!(#pallet_name); #[cfg(feature = "std")] diff --git a/frame/support/procedural/src/construct_runtime/mod.rs b/frame/support/procedural/src/construct_runtime/mod.rs index 2a86869382c93..7b4156a94db58 100644 --- a/frame/support/procedural/src/construct_runtime/mod.rs +++ b/frame/support/procedural/src/construct_runtime/mod.rs @@ -201,7 +201,7 @@ fn construct_runtime_intermediary_expansion( ); } - Ok(expansion.into()) + Ok(expansion) } /// All pallets have explicit definition of parts, this will expand to the runtime declaration. @@ -225,16 +225,16 @@ fn construct_runtime_final_expansion( })?; let hidden_crate_name = "construct_runtime"; - let scrate = generate_crate_access(&hidden_crate_name, "frame-support"); - let scrate_decl = generate_hidden_includes(&hidden_crate_name, "frame-support"); + let scrate = generate_crate_access(hidden_crate_name, "frame-support"); + let scrate_decl = generate_hidden_includes(hidden_crate_name, "frame-support"); let outer_event = expand::expand_outer_event(&name, &pallets, &scrate)?; - let outer_origin = expand::expand_outer_origin(&name, &system_pallet, &pallets, &scrate)?; + let outer_origin = expand::expand_outer_origin(&name, system_pallet, &pallets, &scrate)?; let all_pallets = decl_all_pallets(&name, pallets.iter()); let pallet_to_index = decl_pallet_runtime_setup(&name, &pallets, &scrate); - let dispatch = expand::expand_outer_dispatch(&name, &system_pallet, &pallets, &scrate); + let dispatch = expand::expand_outer_dispatch(&name, system_pallet, &pallets, &scrate); let metadata = expand::expand_runtime_metadata(&name, &pallets, &scrate, &unchecked_extrinsic); let outer_config = expand::expand_outer_config(&name, &pallets, &scrate); let inherent = diff --git a/frame/support/procedural/src/pallet/expand/call.rs b/frame/support/procedural/src/pallet/expand/call.rs index 2d7c550a44869..60546e1a96779 100644 --- a/frame/support/procedural/src/pallet/expand/call.rs +++ b/frame/support/procedural/src/pallet/expand/call.rs @@ -68,7 +68,7 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { .args .iter() .map(|(_, name, _)| { - syn::Ident::new(&name.to_string().trim_start_matches('_'), name.span()) + syn::Ident::new(name.to_string().trim_start_matches('_'), name.span()) }) .collect::>() }) diff --git a/frame/support/procedural/src/pallet/expand/genesis_config.rs b/frame/support/procedural/src/pallet/expand/genesis_config.rs index 18fa87a262533..739e85e0d1ced 100644 --- a/frame/support/procedural/src/pallet/expand/genesis_config.rs +++ b/frame/support/procedural/src/pallet/expand/genesis_config.rs @@ -85,7 +85,7 @@ pub fn expand_genesis_config(def: &mut Def) -> proc_macro2::TokenStream { syn::Item::Enum(syn::ItemEnum { attrs, .. }) | syn::Item::Struct(syn::ItemStruct { attrs, .. }) | syn::Item::Type(syn::ItemType { attrs, .. }) => { - if get_doc_literals(&attrs).is_empty() { + if get_doc_literals(attrs).is_empty() { attrs.push(syn::parse_quote!( #[doc = r" Can be used to configure the diff --git a/frame/support/procedural/src/pallet/expand/storage.rs b/frame/support/procedural/src/pallet/expand/storage.rs index d59dd46051392..657968e17a80c 100644 --- a/frame/support/procedural/src/pallet/expand/storage.rs +++ b/frame/support/procedural/src/pallet/expand/storage.rs @@ -74,9 +74,7 @@ fn check_prefix_duplicates( ), ); - if let Some(other_dup_err) = - used_prefixes.insert(counter_prefix.clone(), counter_dup_err.clone()) - { + if let Some(other_dup_err) = used_prefixes.insert(counter_prefix, counter_dup_err.clone()) { let mut err = counter_dup_err; err.combine(other_dup_err); return Err(err) @@ -113,7 +111,7 @@ pub fn process_generics(def: &mut Def) -> syn::Result<()> { _ => unreachable!("Checked by def"), }; - let prefix_ident = prefix_ident(&storage_def); + let prefix_ident = prefix_ident(storage_def); let type_use_gen = if def.config.has_instance { quote::quote_spanned!(storage_def.attr_span => T, I) } else { @@ -215,7 +213,7 @@ pub fn process_generics(def: &mut Def) -> syn::Result<()> { /// * generate metadatas pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream { if let Err(e) = process_generics(def) { - return e.into_compile_error().into() + return e.into_compile_error() } // Check for duplicate prefixes @@ -393,7 +391,7 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream { let prefix_structs = def.storages.iter().map(|storage_def| { let type_impl_gen = &def.type_impl_generics(storage_def.attr_span); let type_use_gen = &def.type_use_generics(storage_def.attr_span); - let prefix_struct_ident = prefix_ident(&storage_def); + let prefix_struct_ident = prefix_ident(storage_def); let prefix_struct_vis = &storage_def.vis; let prefix_struct_const = storage_def.prefix(); let config_where_clause = &def.config.where_clause; diff --git a/frame/support/procedural/src/pallet/parse/call.rs b/frame/support/procedural/src/pallet/parse/call.rs index 5468b1352197e..f532d339f1205 100644 --- a/frame/support/procedural/src/pallet/parse/call.rs +++ b/frame/support/procedural/src/pallet/parse/call.rs @@ -133,9 +133,10 @@ impl CallDef { return Err(syn::Error::new(item.span(), "Invalid pallet::call, expected item impl")) }; - let mut instances = vec![]; - instances.push(helper::check_impl_gen(&item.generics, item.impl_token.span())?); - instances.push(helper::check_pallet_struct_usage(&item.self_ty)?); + let instances = vec![ + helper::check_impl_gen(&item.generics, item.impl_token.span())?, + helper::check_pallet_struct_usage(&item.self_ty)?, + ]; if let Some((_, _, for_)) = item.trait_ { let msg = "Invalid pallet::call, expected no trait ident as in \ diff --git a/frame/support/procedural/src/pallet/parse/config.rs b/frame/support/procedural/src/pallet/parse/config.rs index 526c7eda2fd57..60888fc5dd357 100644 --- a/frame/support/procedural/src/pallet/parse/config.rs +++ b/frame/support/procedural/src/pallet/parse/config.rs @@ -373,9 +373,10 @@ impl ConfigDef { let found = if item.supertraits.is_empty() { "none".to_string() } else { - let mut found = item.supertraits.iter().fold(String::new(), |acc, s| { - format!("{}`{}`, ", acc, quote::quote!(#s).to_string()) - }); + let mut found = item + .supertraits + .iter() + .fold(String::new(), |acc, s| format!("{}`{}`, ", acc, quote::quote!(#s))); found.pop(); found.pop(); found diff --git a/frame/support/procedural/src/pallet/parse/error.rs b/frame/support/procedural/src/pallet/parse/error.rs index 0ec49aa0adb49..c6ce9b37c75a2 100644 --- a/frame/support/procedural/src/pallet/parse/error.rs +++ b/frame/support/procedural/src/pallet/parse/error.rs @@ -62,8 +62,8 @@ impl ErrorDef { return Err(syn::Error::new(item.span(), msg)) } - let mut instances = vec![]; - instances.push(helper::check_type_def_gen_no_bounds(&item.generics, item.ident.span())?); + let instances = + vec![helper::check_type_def_gen_no_bounds(&item.generics, item.ident.span())?]; if item.generics.where_clause.is_some() { let msg = "Invalid pallet::error, where clause is not allowed on pallet error item"; diff --git a/frame/support/procedural/src/pallet/parse/extra_constants.rs b/frame/support/procedural/src/pallet/parse/extra_constants.rs index 7163b4b632089..d8622da08461b 100644 --- a/frame/support/procedural/src/pallet/parse/extra_constants.rs +++ b/frame/support/procedural/src/pallet/parse/extra_constants.rs @@ -87,9 +87,10 @@ impl ExtraConstantsDef { )) }; - let mut instances = vec![]; - instances.push(helper::check_impl_gen(&item.generics, item.impl_token.span())?); - instances.push(helper::check_pallet_struct_usage(&item.self_ty)?); + let instances = vec![ + helper::check_impl_gen(&item.generics, item.impl_token.span())?, + helper::check_pallet_struct_usage(&item.self_ty)?, + ]; if let Some((_, _, for_)) = item.trait_ { let msg = "Invalid pallet::call, expected no trait ident as in \ diff --git a/frame/support/procedural/src/pallet/parse/genesis_build.rs b/frame/support/procedural/src/pallet/parse/genesis_build.rs index 79ee083069821..9815b8d2203c4 100644 --- a/frame/support/procedural/src/pallet/parse/genesis_build.rs +++ b/frame/support/procedural/src/pallet/parse/genesis_build.rs @@ -53,8 +53,7 @@ impl GenesisBuildDef { })? .1; - let mut instances = vec![]; - instances.push(helper::check_genesis_builder_usage(&item_trait)?); + let instances = vec![helper::check_genesis_builder_usage(item_trait)?]; Ok(Self { attr_span, index, instances, where_clause: item.generics.where_clause.clone() }) } diff --git a/frame/support/procedural/src/pallet/parse/genesis_config.rs b/frame/support/procedural/src/pallet/parse/genesis_config.rs index 875d15bdc061d..45e765c018aae 100644 --- a/frame/support/procedural/src/pallet/parse/genesis_config.rs +++ b/frame/support/procedural/src/pallet/parse/genesis_config.rs @@ -49,7 +49,7 @@ impl GenesisConfigDef { let mut instances = vec![]; // NOTE: GenesisConfig is not allowed to be only generic on I because it is not supported // by construct_runtime. - if let Some(u) = helper::check_type_def_optional_gen(&generics, ident.span())? { + if let Some(u) = helper::check_type_def_optional_gen(generics, ident.span())? { instances.push(u); } diff --git a/frame/support/procedural/src/pallet/parse/hooks.rs b/frame/support/procedural/src/pallet/parse/hooks.rs index cacc149e48668..2dc8f4da47c5f 100644 --- a/frame/support/procedural/src/pallet/parse/hooks.rs +++ b/frame/support/procedural/src/pallet/parse/hooks.rs @@ -45,9 +45,10 @@ impl HooksDef { return Err(syn::Error::new(item.span(), msg)) }; - let mut instances = vec![]; - instances.push(helper::check_pallet_struct_usage(&item.self_ty)?); - instances.push(helper::check_impl_gen(&item.generics, item.impl_token.span())?); + let instances = vec![ + helper::check_pallet_struct_usage(&item.self_ty)?, + helper::check_impl_gen(&item.generics, item.impl_token.span())?, + ]; let item_trait = &item .trait_ diff --git a/frame/support/procedural/src/pallet/parse/inherent.rs b/frame/support/procedural/src/pallet/parse/inherent.rs index 2833b3ef5c724..a485eed4c40d9 100644 --- a/frame/support/procedural/src/pallet/parse/inherent.rs +++ b/frame/support/procedural/src/pallet/parse/inherent.rs @@ -50,9 +50,10 @@ impl InherentDef { return Err(syn::Error::new(item.span(), msg)) } - let mut instances = vec![]; - instances.push(helper::check_pallet_struct_usage(&item.self_ty)?); - instances.push(helper::check_impl_gen(&item.generics, item.impl_token.span())?); + let instances = vec![ + helper::check_pallet_struct_usage(&item.self_ty)?, + helper::check_impl_gen(&item.generics, item.impl_token.span())?, + ]; Ok(InherentDef { index, instances }) } diff --git a/frame/support/procedural/src/pallet/parse/origin.rs b/frame/support/procedural/src/pallet/parse/origin.rs index 2d729376f5de4..89929e3e8dbfc 100644 --- a/frame/support/procedural/src/pallet/parse/origin.rs +++ b/frame/support/procedural/src/pallet/parse/origin.rs @@ -50,7 +50,7 @@ impl OriginDef { let is_generic = !generics.params.is_empty(); let mut instances = vec![]; - if let Some(u) = helper::check_type_def_optional_gen(&generics, item.span())? { + if let Some(u) = helper::check_type_def_optional_gen(generics, item.span())? { instances.push(u); } else { // construct_runtime only allow generic event for instantiable pallet. diff --git a/frame/support/procedural/src/pallet/parse/pallet_struct.rs b/frame/support/procedural/src/pallet/parse/pallet_struct.rs index d98862be8f783..a96c310b6f1ca 100644 --- a/frame/support/procedural/src/pallet/parse/pallet_struct.rs +++ b/frame/support/procedural/src/pallet/parse/pallet_struct.rs @@ -155,8 +155,8 @@ impl PalletStructDef { return Err(syn::Error::new(item.generics.where_clause.span(), msg)) } - let mut instances = vec![]; - instances.push(helper::check_type_def_gen_no_bounds(&item.generics, item.ident.span())?); + let instances = + vec![helper::check_type_def_gen_no_bounds(&item.generics, item.ident.span())?]; Ok(Self { index, diff --git a/frame/support/procedural/src/pallet/parse/storage.rs b/frame/support/procedural/src/pallet/parse/storage.rs index effe0ce6c55d8..1f1bb5b2f26ad 100644 --- a/frame/support/procedural/src/pallet/parse/storage.rs +++ b/frame/support/procedural/src/pallet/parse/storage.rs @@ -272,7 +272,7 @@ fn check_generics( ); e.pop(); e.pop(); - e.push_str("."); + e.push('.'); e }; @@ -550,7 +550,7 @@ fn process_generics( let args_span = segment.arguments.span(); let args = match &segment.arguments { - syn::PathArguments::AngleBracketed(args) if args.args.len() != 0 => args, + syn::PathArguments::AngleBracketed(args) if !args.args.is_empty() => args, _ => { let msg = "Invalid pallet::storage, invalid number of generic generic arguments, \ expect more that 0 generic arguments."; @@ -646,13 +646,16 @@ impl StorageDef { self.rename_as .as_ref() .map(syn::LitStr::value) - .unwrap_or(self.ident.to_string()) + .unwrap_or_else(|| self.ident.to_string()) } /// Return either the span of the ident or the span of the literal in the /// #[storage_prefix] attribute pub fn prefix_span(&self) -> proc_macro2::Span { - self.rename_as.as_ref().map(syn::LitStr::span).unwrap_or(self.ident.span()) + self.rename_as + .as_ref() + .map(syn::LitStr::span) + .unwrap_or_else(|| self.ident.span()) } pub fn try_from( @@ -672,8 +675,7 @@ impl StorageDef { let cfg_attrs = helper::get_item_cfg_attrs(&item.attrs); - let mut instances = vec![]; - instances.push(helper::check_type_def_gen(&item.generics, item.ident.span())?); + let instances = vec![helper::check_type_def_gen(&item.generics, item.ident.span())?]; let where_clause = item.generics.where_clause.clone(); let docs = get_doc_literals(&item.attrs); diff --git a/frame/support/procedural/src/pallet/parse/validate_unsigned.rs b/frame/support/procedural/src/pallet/parse/validate_unsigned.rs index a58671d9762de..18d5a2dc4443f 100644 --- a/frame/support/procedural/src/pallet/parse/validate_unsigned.rs +++ b/frame/support/procedural/src/pallet/parse/validate_unsigned.rs @@ -52,9 +52,10 @@ impl ValidateUnsignedDef { return Err(syn::Error::new(item.span(), msg)) } - let mut instances = vec![]; - instances.push(helper::check_pallet_struct_usage(&item.self_ty)?); - instances.push(helper::check_impl_gen(&item.generics, item.impl_token.span())?); + let instances = vec![ + helper::check_pallet_struct_usage(&item.self_ty)?, + helper::check_impl_gen(&item.generics, item.impl_token.span())?, + ]; Ok(ValidateUnsignedDef { index, instances }) } diff --git a/frame/support/procedural/src/storage/genesis_config/builder_def.rs b/frame/support/procedural/src/storage/genesis_config/builder_def.rs index 975791881da23..29e2560bc16e4 100644 --- a/frame/support/procedural/src/storage/genesis_config/builder_def.rs +++ b/frame/support/procedural/src/storage/genesis_config/builder_def.rs @@ -49,7 +49,7 @@ impl BuilderDef { let mut data = None; if let Some(builder) = &line.build { - is_generic |= ext::expr_contains_ident(&builder, &def.module_runtime_generic); + is_generic |= ext::expr_contains_ident(builder, &def.module_runtime_generic); is_generic |= line.is_generic; data = Some(match &line.storage_type { @@ -138,7 +138,7 @@ impl BuilderDef { } if let Some(builder) = def.extra_genesis_build.as_ref() { - is_generic |= ext::expr_contains_ident(&builder, &def.module_runtime_generic); + is_generic |= ext::expr_contains_ident(builder, &def.module_runtime_generic); blocks.push(quote_spanned! { builder.span() => let extra_genesis_builder: fn(&Self) = #builder; diff --git a/frame/support/procedural/src/storage/instance_trait.rs b/frame/support/procedural/src/storage/instance_trait.rs index 14e968112029e..e9df02e3dc132 100644 --- a/frame/support/procedural/src/storage/instance_trait.rs +++ b/frame/support/procedural/src/storage/instance_trait.rs @@ -116,7 +116,7 @@ fn create_and_impl_instance_struct( let instance_trait = quote!( #scrate::traits::Instance ); let instance_struct = &instance_def.instance_struct; - let prefix = format!("{}{}", instance_def.prefix, def.crate_name.to_string()); + let prefix = format!("{}{}", instance_def.prefix, def.crate_name); let doc = &instance_def.doc; let index = instance_def.index; diff --git a/frame/support/procedural/src/storage/mod.rs b/frame/support/procedural/src/storage/mod.rs index b89e756334986..756653f7ba85d 100644 --- a/frame/support/procedural/src/storage/mod.rs +++ b/frame/support/procedural/src/storage/mod.rs @@ -259,7 +259,7 @@ impl StorageLineDefExt { ) -> Self { let is_generic = match &storage_def.storage_type { StorageLineTypeDef::Simple(value) => - ext::type_contains_ident(&value, &def.module_runtime_generic), + ext::type_contains_ident(value, &def.module_runtime_generic), StorageLineTypeDef::Map(map) => ext::type_contains_ident(&map.key, &def.module_runtime_generic) || ext::type_contains_ident(&map.value, &def.module_runtime_generic), diff --git a/frame/support/src/hash.rs b/frame/support/src/hash.rs index 9ce0968351a42..9bdad6d9d59de 100644 --- a/frame/support/src/hash.rs +++ b/frame/support/src/hash.rs @@ -101,7 +101,7 @@ impl StorageHasher for Twox64Concat { const METADATA: metadata::StorageHasher = metadata::StorageHasher::Twox64Concat; type Output = Vec; fn hash(x: &[u8]) -> Vec { - twox_64(x).iter().chain(x.into_iter()).cloned().collect::>() + twox_64(x).iter().chain(x.iter()).cloned().collect::>() } fn max_len() -> usize { K::max_encoded_len().saturating_add(8) @@ -123,7 +123,7 @@ impl StorageHasher for Blake2_128Concat { const METADATA: metadata::StorageHasher = metadata::StorageHasher::Blake2_128Concat; type Output = Vec; fn hash(x: &[u8]) -> Vec { - blake2_128(x).iter().chain(x.into_iter()).cloned().collect::>() + blake2_128(x).iter().chain(x.iter()).cloned().collect::>() } fn max_len() -> usize { K::max_encoded_len().saturating_add(16) diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index 2d2944cc9a44f..3e84c009b5ca6 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -105,7 +105,7 @@ use scale_info::TypeInfo; use sp_runtime::TypeId; /// A unified log target for support operations. -pub const LOG_TARGET: &'static str = "runtime::frame-support"; +pub const LOG_TARGET: &str = "runtime::frame-support"; /// A type that cannot be instantiated. #[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)] diff --git a/frame/support/src/storage/child.rs b/frame/support/src/storage/child.rs index 949df84e7e768..e20d4fe0cd4d3 100644 --- a/frame/support/src/storage/child.rs +++ b/frame/support/src/storage/child.rs @@ -50,7 +50,7 @@ pub fn get(child_info: &ChildInfo, key: &[u8]) -> Option { /// Return the value of the item in storage under `key`, or the type's default if there is no /// explicit entry. pub fn get_or_default(child_info: &ChildInfo, key: &[u8]) -> T { - get(child_info, key).unwrap_or_else(Default::default) + get(child_info, key).unwrap_or_default() } /// Return the value of the item in storage under `key`, or `default_value` if there is no @@ -90,7 +90,7 @@ pub fn take(child_info: &ChildInfo, key: &[u8]) -> Option /// Remove `key` from storage, returning its value, or, if there was no explicit entry in storage, /// the default for its type. pub fn take_or_default(child_info: &ChildInfo, key: &[u8]) -> T { - take(child_info, key).unwrap_or_else(Default::default) + take(child_info, key).unwrap_or_default() } /// Return the value of the item in storage under `key`, or `default_value` if there is no diff --git a/frame/support/src/storage/generator/double_map.rs b/frame/support/src/storage/generator/double_map.rs index 12e1764bfb65d..16fcaf940c62a 100644 --- a/frame/support/src/storage/generator/double_map.rs +++ b/frame/support/src/storage/generator/double_map.rs @@ -461,7 +461,7 @@ where }, }; - let mut key2_material = G::Hasher2::reverse(&key_material); + let mut key2_material = G::Hasher2::reverse(key_material); let key2 = match K2::decode(&mut key2_material) { Ok(key2) => key2, Err(_) => { diff --git a/frame/support/src/storage/hashed.rs b/frame/support/src/storage/hashed.rs index a07db73c947d5..19c9d73be868d 100644 --- a/frame/support/src/storage/hashed.rs +++ b/frame/support/src/storage/hashed.rs @@ -28,7 +28,7 @@ where HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>, { - unhashed::get(&hash(key).as_ref()) + unhashed::get(hash(key).as_ref()) } /// Return the value of the item in storage under `key`, or the type's default if there is no @@ -39,7 +39,7 @@ where HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>, { - unhashed::get_or_default(&hash(key).as_ref()) + unhashed::get_or_default(hash(key).as_ref()) } /// Return the value of the item in storage under `key`, or `default_value` if there is no @@ -50,7 +50,7 @@ where HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>, { - unhashed::get_or(&hash(key).as_ref(), default_value) + unhashed::get_or(hash(key).as_ref(), default_value) } /// Return the value of the item in storage under `key`, or `default_value()` if there is no @@ -62,7 +62,7 @@ where HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>, { - unhashed::get_or_else(&hash(key).as_ref(), default_value) + unhashed::get_or_else(hash(key).as_ref(), default_value) } /// Put `value` in storage under `key`. @@ -72,7 +72,7 @@ where HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>, { - unhashed::put(&hash(key).as_ref(), value) + unhashed::put(hash(key).as_ref(), value) } /// Remove `key` from storage, returning its value if it had an explicit entry or `None` otherwise. @@ -82,7 +82,7 @@ where HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>, { - unhashed::take(&hash(key).as_ref()) + unhashed::take(hash(key).as_ref()) } /// Remove `key` from storage, returning its value, or, if there was no explicit entry in storage, @@ -93,7 +93,7 @@ where HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>, { - unhashed::take_or_default(&hash(key).as_ref()) + unhashed::take_or_default(hash(key).as_ref()) } /// Return the value of the item in storage under `key`, or `default_value` if there is no @@ -104,7 +104,7 @@ where HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>, { - unhashed::take_or(&hash(key).as_ref(), default_value) + unhashed::take_or(hash(key).as_ref(), default_value) } /// Return the value of the item in storage under `key`, or `default_value()` if there is no @@ -116,7 +116,7 @@ where HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>, { - unhashed::take_or_else(&hash(key).as_ref(), default_value) + unhashed::take_or_else(hash(key).as_ref(), default_value) } /// Check to see if `key` has an explicit entry in storage. @@ -125,7 +125,7 @@ where HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>, { - unhashed::exists(&hash(key).as_ref()) + unhashed::exists(hash(key).as_ref()) } /// Ensure `key` has no explicit entry in storage. @@ -134,7 +134,7 @@ where HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>, { - unhashed::kill(&hash(key).as_ref()) + unhashed::kill(hash(key).as_ref()) } /// Get a Vec of bytes from storage. @@ -143,7 +143,7 @@ where HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>, { - unhashed::get_raw(&hash(key).as_ref()) + unhashed::get_raw(hash(key).as_ref()) } /// Put a raw byte slice into storage. @@ -152,5 +152,5 @@ where HashFn: Fn(&[u8]) -> R, R: AsRef<[u8]>, { - unhashed::put_raw(&hash(key).as_ref(), value) + unhashed::put_raw(hash(key).as_ref(), value) } diff --git a/frame/support/src/storage/mod.rs b/frame/support/src/storage/mod.rs index 066422ad456aa..115f179d803a7 100644 --- a/frame/support/src/storage/mod.rs +++ b/frame/support/src/storage/mod.rs @@ -1038,7 +1038,7 @@ impl Iterator for ChildTriePrefixIterator { Some(self.previous_key.clone()) } else { sp_io::default_child_storage::next_key( - &self.child_info.storage_key(), + self.child_info.storage_key(), &self.previous_key, ) .filter(|n| n.starts_with(&self.prefix)) diff --git a/frame/support/src/storage/types/counted_map.rs b/frame/support/src/storage/types/counted_map.rs index 9c48267af86e0..a2160ede52fe9 100644 --- a/frame/support/src/storage/types/counted_map.rs +++ b/frame/support/src/storage/types/counted_map.rs @@ -217,8 +217,7 @@ where /// Take the value under a key. pub fn take + Clone>(key: KeyArg) -> QueryKind::Query { - let removed_value = - ::Map::mutate_exists(key, |value| core::mem::replace(value, None)); + let removed_value = ::Map::mutate_exists(key, |value| value.take()); if removed_value.is_some() { CounterFor::::mutate(|value| value.saturating_dec()); } @@ -429,7 +428,7 @@ where if cfg!(feature = "no-metadata-docs") { vec![] } else { - vec![&"Counter for the related counted storage map"] + vec!["Counter for the related counted storage map"] }, entries, ); diff --git a/frame/support/src/storage/types/nmap.rs b/frame/support/src/storage/types/nmap.rs index aff32f2110009..16dc30ea03903 100755 --- a/frame/support/src/storage/types/nmap.rs +++ b/frame/support/src/storage/types/nmap.rs @@ -481,7 +481,7 @@ where modifier: QueryKind::METADATA, ty: StorageEntryType::Map { key: scale_info::meta_type::(), - hashers: Key::HASHER_METADATA.iter().cloned().collect(), + hashers: Key::HASHER_METADATA.to_vec(), value: scale_info::meta_type::(), }, default: OnEmpty::get().encode(), diff --git a/frame/support/src/storage/unhashed.rs b/frame/support/src/storage/unhashed.rs index 96bccc6ae0feb..adabc9c14a3d5 100644 --- a/frame/support/src/storage/unhashed.rs +++ b/frame/support/src/storage/unhashed.rs @@ -38,7 +38,7 @@ pub fn get(key: &[u8]) -> Option { /// Return the value of the item in storage under `key`, or the type's default if there is no /// explicit entry. pub fn get_or_default(key: &[u8]) -> T { - get(key).unwrap_or_else(Default::default) + get(key).unwrap_or_default() } /// Return the value of the item in storage under `key`, or `default_value` if there is no @@ -70,7 +70,7 @@ pub fn take(key: &[u8]) -> Option { /// Remove `key` from storage, returning its value, or, if there was no explicit entry in storage, /// the default for its type. pub fn take_or_default(key: &[u8]) -> T { - take(key).unwrap_or_else(Default::default) + take(key).unwrap_or_default() } /// Return the value of the item in storage under `key`, or `default_value` if there is no diff --git a/frame/support/src/traits/dispatch.rs b/frame/support/src/traits/dispatch.rs index 250a31ebfb17a..443941bd8d771 100644 --- a/frame/support/src/traits/dispatch.rs +++ b/frame/support/src/traits/dispatch.rs @@ -157,7 +157,7 @@ impl, R: EnsureOrigin> type Success = Either; fn try_origin(o: OuterOrigin) -> Result { L::try_origin(o) - .map_or_else(|o| R::try_origin(o).map(|o| Either::Right(o)), |o| Ok(Either::Left(o))) + .map_or_else(|o| R::try_origin(o).map(Either::Right), |o| Ok(Either::Left(o))) } #[cfg(feature = "runtime-benchmarks")] diff --git a/frame/support/src/traits/members.rs b/frame/support/src/traits/members.rs index f3c586b64af04..8c69a2aaccb33 100644 --- a/frame/support/src/traits/members.rs +++ b/frame/support/src/traits/members.rs @@ -299,7 +299,7 @@ pub trait ChangeMembers { /// This resets any previous value of prime. fn set_members_sorted(new_members: &[AccountId], old_members: &[AccountId]) { let (incoming, outgoing) = Self::compute_members_diff_sorted(new_members, old_members); - Self::change_members_sorted(&incoming[..], &outgoing[..], &new_members); + Self::change_members_sorted(&incoming[..], &outgoing[..], new_members); } /// Compute diff between new and old members; they **must already be sorted**. diff --git a/frame/support/src/traits/metadata.rs b/frame/support/src/traits/metadata.rs index c76c53dbe8b2d..d3dc57e1ee52d 100644 --- a/frame/support/src/traits/metadata.rs +++ b/frame/support/src/traits/metadata.rs @@ -161,7 +161,7 @@ impl sp_std::cmp::Ord for CrateVersion { impl sp_std::cmp::PartialOrd for CrateVersion { fn partial_cmp(&self, other: &Self) -> Option { - Some(::cmp(&self, other)) + Some(::cmp(self, other)) } } diff --git a/frame/support/src/traits/misc.rs b/frame/support/src/traits/misc.rs index 7575aa8c19dc6..b67eac2fbe7bc 100644 --- a/frame/support/src/traits/misc.rs +++ b/frame/support/src/traits/misc.rs @@ -25,9 +25,9 @@ use sp_runtime::{traits::Block as BlockT, DispatchError}; use sp_std::{cmp::Ordering, prelude::*}; #[doc(hidden)] -pub const DEFENSIVE_OP_PUBLIC_ERROR: &'static str = "a defensive failure has been triggered; please report the block number at https://github.com/paritytech/substrate/issues"; +pub const DEFENSIVE_OP_PUBLIC_ERROR: &str = "a defensive failure has been triggered; please report the block number at https://github.com/paritytech/substrate/issues"; #[doc(hidden)] -pub const DEFENSIVE_OP_INTERNAL_ERROR: &'static str = "Defensive failure has been triggered!"; +pub const DEFENSIVE_OP_INTERNAL_ERROR: &str = "Defensive failure has been triggered!"; /// Generic function to mark an execution path as ONLY defensive. /// diff --git a/frame/support/src/traits/stored_map.rs b/frame/support/src/traits/stored_map.rs index 3c3ff2eb0ed98..2aae88096ec74 100644 --- a/frame/support/src/traits/stored_map.rs +++ b/frame/support/src/traits/stored_map.rs @@ -101,7 +101,7 @@ impl< } fn remove(k: &K) -> Result<(), DispatchError> { if S::contains_key(&k) { - L::killed(&k)?; + L::killed(k)?; S::remove(k); } Ok(()) diff --git a/frame/support/src/traits/tokens/currency.rs b/frame/support/src/traits/tokens/currency.rs index d4b5c0c184f85..9a1634fd89313 100644 --- a/frame/support/src/traits/tokens/currency.rs +++ b/frame/support/src/traits/tokens/currency.rs @@ -83,7 +83,7 @@ pub trait Currency { /// This is just the same as burning and issuing the same amount and has no effect on the /// total issuance. fn pair(amount: Self::Balance) -> (Self::PositiveImbalance, Self::NegativeImbalance) { - (Self::burn(amount.clone()), Self::issue(amount)) + (Self::burn(amount), Self::issue(amount)) } /// The 'free' balance of a given account. diff --git a/frame/support/src/traits/tokens/fungible.rs b/frame/support/src/traits/tokens/fungible.rs index 7422a9d651874..90aadb6d8daa6 100644 --- a/frame/support/src/traits/tokens/fungible.rs +++ b/frame/support/src/traits/tokens/fungible.rs @@ -92,7 +92,7 @@ pub trait Mutate: Inspect { let extra = Self::can_withdraw(&source, amount).into_result()?; // As we first burn and then mint, we don't need to check if `mint` fits into the supply. // If we can withdraw/burn it, we can also mint it again. - Self::can_deposit(&dest, amount.saturating_add(extra), false).into_result()?; + Self::can_deposit(dest, amount.saturating_add(extra), false).into_result()?; let actual = Self::burn_from(source, amount)?; debug_assert!( actual == amount.saturating_add(extra), diff --git a/frame/support/src/traits/tokens/fungible/balanced.rs b/frame/support/src/traits/tokens/fungible/balanced.rs index 196f3a35754af..ed9c3a1afa480 100644 --- a/frame/support/src/traits/tokens/fungible/balanced.rs +++ b/frame/support/src/traits/tokens/fungible/balanced.rs @@ -165,7 +165,7 @@ pub trait Unbalanced: Inspect { ) -> Result { let old_balance = Self::balance(who); let (mut new_balance, mut amount) = if old_balance < amount { - Err(TokenError::NoFunds)? + return Err(TokenError::NoFunds.into()) } else { (old_balance - amount, amount) }; @@ -225,7 +225,7 @@ pub trait Unbalanced: Inspect { let old_balance = Self::balance(who); let new_balance = old_balance.checked_add(&amount).ok_or(ArithmeticError::Overflow)?; if new_balance < Self::minimum_balance() { - Err(TokenError::BelowMinimum)? + return Err(TokenError::BelowMinimum.into()) } if old_balance != new_balance { Self::set_balance(who, new_balance)?; diff --git a/frame/support/src/traits/tokens/fungibles.rs b/frame/support/src/traits/tokens/fungibles.rs index 2abadf037687d..dab50d56962f6 100644 --- a/frame/support/src/traits/tokens/fungibles.rs +++ b/frame/support/src/traits/tokens/fungibles.rs @@ -145,7 +145,7 @@ pub trait Mutate: Inspect { let extra = Self::can_withdraw(asset, &source, amount).into_result()?; // As we first burn and then mint, we don't need to check if `mint` fits into the supply. // If we can withdraw/burn it, we can also mint it again. - Self::can_deposit(asset, &dest, amount.saturating_add(extra), false).into_result()?; + Self::can_deposit(asset, dest, amount.saturating_add(extra), false).into_result()?; let actual = Self::burn_from(asset, source, amount)?; debug_assert!( actual == amount.saturating_add(extra), diff --git a/frame/support/src/traits/tokens/fungibles/balanced.rs b/frame/support/src/traits/tokens/fungibles/balanced.rs index e07d45cc47170..a75832e4c440f 100644 --- a/frame/support/src/traits/tokens/fungibles/balanced.rs +++ b/frame/support/src/traits/tokens/fungibles/balanced.rs @@ -186,7 +186,7 @@ pub trait Unbalanced: Inspect { ) -> Result { let old_balance = Self::balance(asset, who); let (mut new_balance, mut amount) = if old_balance < amount { - Err(TokenError::NoFunds)? + return Err(TokenError::NoFunds.into()) } else { (old_balance - amount, amount) }; @@ -251,7 +251,7 @@ pub trait Unbalanced: Inspect { let old_balance = Self::balance(asset, who); let new_balance = old_balance.checked_add(&amount).ok_or(ArithmeticError::Overflow)?; if new_balance < Self::minimum_balance(asset) { - Err(TokenError::BelowMinimum)? + return Err(TokenError::BelowMinimum.into()) } if old_balance != new_balance { Self::set_balance(asset, who, new_balance)?; diff --git a/frame/support/src/weights.rs b/frame/support/src/weights.rs index 5b4a13e7f9457..292c7d3e91d06 100644 --- a/frame/support/src/weights.rs +++ b/frame/support/src/weights.rs @@ -355,7 +355,7 @@ impl PostDispatchInfo { /// Extract the actual weight from a dispatch result if any or fall back to the default weight. pub fn extract_actual_weight(result: &DispatchResultWithPostInfo, info: &DispatchInfo) -> Weight { match result { - Ok(post_info) => &post_info, + Ok(post_info) => post_info, Err(err) => &err.post_info, } .calc_actual_weight(info) @@ -432,7 +432,7 @@ where impl WeighData for Weight { fn weigh_data(&self, _: T) -> Weight { - return *self + *self } } @@ -450,7 +450,7 @@ impl PaysFee for Weight { impl WeighData for (Weight, DispatchClass, Pays) { fn weigh_data(&self, _: T) -> Weight { - return self.0 + self.0 } } @@ -468,7 +468,7 @@ impl PaysFee for (Weight, DispatchClass, Pays) { impl WeighData for (Weight, DispatchClass) { fn weigh_data(&self, _: T) -> Weight { - return self.0 + self.0 } } @@ -486,7 +486,7 @@ impl PaysFee for (Weight, DispatchClass) { impl WeighData for (Weight, Pays) { fn weigh_data(&self, _: T) -> Weight { - return self.0 + self.0 } } diff --git a/frame/system/src/extensions/check_non_zero_sender.rs b/frame/system/src/extensions/check_non_zero_sender.rs index f517201fbebc2..9a6c4007b3779 100644 --- a/frame/system/src/extensions/check_non_zero_sender.rs +++ b/frame/system/src/extensions/check_non_zero_sender.rs @@ -82,7 +82,7 @@ where _info: &DispatchInfoOf, _len: usize, ) -> TransactionValidity { - if who.using_encoded(|d| d.into_iter().all(|x| *x == 0)) { + if who.using_encoded(|d| d.iter().all(|x| *x == 0)) { return Err(TransactionValidityError::Invalid(InvalidTransaction::BadSigner)) } Ok(ValidTransaction::default()) diff --git a/frame/system/src/extensions/check_weight.rs b/frame/system/src/extensions/check_weight.rs index 774139054d08c..b59c36ecb53b5 100644 --- a/frame/system/src/extensions/check_weight.rs +++ b/frame/system/src/extensions/check_weight.rs @@ -188,7 +188,7 @@ where len: usize, ) -> Result<(), TransactionValidityError> { if info.class == DispatchClass::Mandatory { - Err(InvalidTransaction::MandatoryDispatch)? + return Err(InvalidTransaction::MandatoryDispatch.into()) } Self::do_pre_dispatch(info, len) } @@ -201,7 +201,7 @@ where len: usize, ) -> TransactionValidity { if info.class == DispatchClass::Mandatory { - Err(InvalidTransaction::MandatoryDispatch)? + return Err(InvalidTransaction::MandatoryDispatch.into()) } Self::do_validate(info, len) } @@ -234,7 +234,7 @@ where // extrinsics that result in error. if let (DispatchClass::Mandatory, Err(e)) = (info.class, result) { log::error!(target: "runtime::system", "Bad mandatory: {:?}", e); - Err(InvalidTransaction::BadMandatory)? + return Err(InvalidTransaction::BadMandatory.into()) } let unspent = post_info.calc_unspent(info); diff --git a/frame/system/src/lib.rs b/frame/system/src/lib.rs index 14e67cfcd8156..ba494dfbd9f8c 100644 --- a/frame/system/src/lib.rs +++ b/frame/system/src/lib.rs @@ -456,7 +456,7 @@ pub mod pallet { pub fn kill_storage(origin: OriginFor, keys: Vec) -> DispatchResultWithPostInfo { ensure_root(origin)?; for key in &keys { - storage::unhashed::kill(&key); + storage::unhashed::kill(key); } Ok(().into()) } @@ -833,7 +833,7 @@ impl< Some(account) => account.clone(), None => zero_account_id, }; - O::from(RawOrigin::Signed(first_member.clone())) + O::from(RawOrigin::Signed(first_member)) } } @@ -1196,8 +1196,7 @@ impl Pallet { } let phase = ExecutionPhase::::get().unwrap_or_default(); - let event = - EventRecord { phase, event, topics: topics.iter().cloned().collect::>() }; + let event = EventRecord { phase, event, topics: topics.to_vec() }; // Index of the to be added event. let event_idx = { @@ -1522,16 +1521,16 @@ impl Pallet { /// of the old and new runtime has the same spec name and that the spec version is increasing. pub fn can_set_code(code: &[u8]) -> Result<(), sp_runtime::DispatchError> { let current_version = T::Version::get(); - let new_version = sp_io::misc::runtime_version(&code) + let new_version = sp_io::misc::runtime_version(code) .and_then(|v| RuntimeVersion::decode(&mut &v[..]).ok()) - .ok_or_else(|| Error::::FailedToExtractRuntimeVersion)?; + .ok_or(Error::::FailedToExtractRuntimeVersion)?; if new_version.spec_name != current_version.spec_name { - Err(Error::::InvalidSpecName)? + return Err(Error::::InvalidSpecName.into()) } if new_version.spec_version <= current_version.spec_version { - Err(Error::::SpecVersionNeedsToIncrease)? + return Err(Error::::SpecVersionNeedsToIncrease.into()) } Ok(()) diff --git a/frame/system/src/limits.rs b/frame/system/src/limits.rs index 4942a5dace7d4..d3c108afb6f32 100644 --- a/frame/system/src/limits.rs +++ b/frame/system/src/limits.rs @@ -217,7 +217,7 @@ impl BlockWeights { /// Verifies correctness of this `BlockWeights` object. pub fn validate(self) -> ValidationResult { fn or_max(w: Option) -> Weight { - w.unwrap_or_else(|| Weight::max_value()) + w.unwrap_or_else(Weight::max_value) } let mut error = ValidationErrors::default(); @@ -246,7 +246,7 @@ impl BlockWeights { ); // Max extrinsic should not be 0 error_assert!( - weights.max_extrinsic.unwrap_or_else(|| Weight::max_value()) > 0, + weights.max_extrinsic.unwrap_or_else(Weight::max_value) > 0, &mut error, "[{:?}] {:?} (max_extrinsic) must not be 0. Check base cost and average initialization cost.", class, weights.max_extrinsic, diff --git a/frame/system/src/migrations/mod.rs b/frame/system/src/migrations/mod.rs index 358ba55b7c819..872cf389d246c 100644 --- a/frame/system/src/migrations/mod.rs +++ b/frame/system/src/migrations/mod.rs @@ -89,7 +89,7 @@ frame_support::generate_storage_alias!( pub fn migrate_from_single_u8_to_triple_ref_count() -> Weight { let mut translated: usize = 0; >::translate::<(T::Index, u8, T::AccountData), _>(|_key, (nonce, rc, data)| { - translated = translated + 1; + translated += 1; Some(AccountInfo { nonce, consumers: rc as RefCount, providers: 1, sufficients: 0, data }) }); log::info!( @@ -107,7 +107,7 @@ pub fn migrate_from_single_to_triple_ref_count() -> Weight { let mut translated: usize = 0; >::translate::<(T::Index, RefCount, T::AccountData), _>( |_key, (nonce, consumers, data)| { - translated = translated + 1; + translated += 1; Some(AccountInfo { nonce, consumers, providers: 1, sufficients: 0, data }) }, ); @@ -125,7 +125,7 @@ pub fn migrate_from_dual_to_triple_ref_count() -> Weight { let mut translated: usize = 0; >::translate::<(T::Index, RefCount, RefCount, T::AccountData), _>( |_key, (nonce, consumers, providers, data)| { - translated = translated + 1; + translated += 1; Some(AccountInfo { nonce, consumers, providers, sufficients: 0, data }) }, ); diff --git a/frame/system/src/offchain.rs b/frame/system/src/offchain.rs index bf52ab8e3791d..86440188a765c 100644 --- a/frame/system/src/offchain.rs +++ b/frame/system/src/offchain.rs @@ -88,7 +88,7 @@ where call: >::OverarchingCall, signature: Option<::SignaturePayload>, ) -> Result<(), ()> { - let xt = T::Extrinsic::new(call.into(), signature).ok_or(())?; + let xt = T::Extrinsic::new(call, signature).ok_or(())?; sp_io::offchain::submit_transaction(xt.encode()) } @@ -163,7 +163,7 @@ impl, X> Signer keystore_accounts.map(|account| account.public).collect(); Box::new( - keys.into_iter() + keys.iter() .enumerate() .map(|(index, key)| { let account_id = key.clone().into_account(); diff --git a/frame/timestamp/src/lib.rs b/frame/timestamp/src/lib.rs index d8bfb405e5c7c..c75a0ac9765b9 100644 --- a/frame/timestamp/src/lib.rs +++ b/frame/timestamp/src/lib.rs @@ -229,7 +229,7 @@ pub mod pallet { let data = (*inherent_data).saturated_into::(); let next_time = cmp::max(data, Self::now() + T::MinimumPeriod::get()); - Some(Call::set { now: next_time.into() }) + Some(Call::set { now: next_time }) } fn check_inherent( @@ -240,7 +240,7 @@ pub mod pallet { sp_timestamp::Timestamp::new(30 * 1000); let t: u64 = match call { - Call::set { ref now } => now.clone().saturated_into::(), + Call::set { ref now } => (*now).saturated_into::(), _ => return Ok(()), }; diff --git a/frame/tips/src/benchmarking.rs b/frame/tips/src/benchmarking.rs index 7abee6192f2e0..190ef60f3b810 100644 --- a/frame/tips/src/benchmarking.rs +++ b/frame/tips/src/benchmarking.rs @@ -131,7 +131,7 @@ benchmarks! { 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)?; + create_tips::(t - 1, hash, value)?; let caller = account("member", t - 1, SEED); // Whitelist caller account from further DB operations. let caller_key = frame_system::Account::::hashed_key_for(&caller); @@ -159,7 +159,7 @@ benchmarks! { let hash = T::Hashing::hash_of(&(&reason_hash, &beneficiary)); ensure!(Tips::::contains_key(hash), "tip does not exist"); - create_tips::(t, hash.clone(), value)?; + create_tips::(t, hash, value)?; let caller = account("caller", t, SEED); // Whitelist caller account from further DB operations. diff --git a/frame/tips/src/lib.rs b/frame/tips/src/lib.rs index 4b7d78b90be38..b9868dac43011 100644 --- a/frame/tips/src/lib.rs +++ b/frame/tips/src/lib.rs @@ -340,7 +340,7 @@ pub mod pallet { let hash = T::Hashing::hash_of(&(&reason_hash, &who)); Reasons::::insert(&reason_hash, &reason); - Self::deposit_event(Event::NewTip { tip_hash: hash.clone() }); + Self::deposit_event(Event::NewTip { tip_hash: hash }); let tips = vec![(tipper.clone(), tip_value)]; let tip = OpenTip { reason: reason_hash, @@ -390,7 +390,7 @@ pub mod pallet { let mut tip = Tips::::get(hash).ok_or(Error::::UnknownTip)?; if Self::insert_tip_and_check_closing(&mut tip, tipper, tip_value) { - Self::deposit_event(Event::TipClosing { tip_hash: hash.clone() }); + Self::deposit_event(Event::TipClosing { tip_hash: hash }); } Tips::::insert(&hash, tip); Ok(()) diff --git a/frame/transaction-payment/asset-tx-payment/src/payment.rs b/frame/transaction-payment/asset-tx-payment/src/payment.rs index 9eafc43fc2569..394696cc18929 100644 --- a/frame/transaction-payment/asset-tx-payment/src/payment.rs +++ b/frame/transaction-payment/asset-tx-payment/src/payment.rs @@ -127,15 +127,12 @@ where let converted_fee = CON::to_asset_balance(fee, asset_id) .map_err(|_| TransactionValidityError::from(InvalidTransaction::Payment))? .max(min_converted_fee); - let can_withdraw = >::can_withdraw( - asset_id.into(), - who, - converted_fee, - ); + let can_withdraw = + >::can_withdraw(asset_id, who, converted_fee); if !matches!(can_withdraw, WithdrawConsequence::Success) { return Err(InvalidTransaction::Payment.into()) } - >::withdraw(asset_id.into(), who, converted_fee) + >::withdraw(asset_id, who, converted_fee) .map_err(|_| TransactionValidityError::from(InvalidTransaction::Payment)) } @@ -153,7 +150,7 @@ where ) -> Result<(), TransactionValidityError> { let min_converted_fee = if corrected_fee.is_zero() { Zero::zero() } else { One::one() }; // Convert the corrected fee into the asset used for payment. - let converted_fee = CON::to_asset_balance(corrected_fee, paid.asset().into()) + let converted_fee = CON::to_asset_balance(corrected_fee, paid.asset()) .map_err(|_| -> TransactionValidityError { InvalidTransaction::Payment.into() })? .max(min_converted_fee); // Calculate how much refund we should return. diff --git a/frame/transaction-payment/src/lib.rs b/frame/transaction-payment/src/lib.rs index 1462faaa07062..45c8b8f479c9f 100644 --- a/frame/transaction-payment/src/lib.rs +++ b/frame/transaction-payment/src/lib.rs @@ -186,10 +186,8 @@ where let weights = T::BlockWeights::get(); // the computed ratio is only among the normal class. - let normal_max_weight = weights - .get(DispatchClass::Normal) - .max_total - .unwrap_or_else(|| weights.max_block); + let normal_max_weight = + weights.get(DispatchClass::Normal).max_total.unwrap_or(weights.max_block); let current_block_weight = >::block_weight(); let normal_block_weight = *current_block_weight.get(DispatchClass::Normal).min(&normal_max_weight); diff --git a/frame/transaction-payment/src/payment.rs b/frame/transaction-payment/src/payment.rs index 5b4a613102792..3a5fad0d66a52 100644 --- a/frame/transaction-payment/src/payment.rs +++ b/frame/transaction-payment/src/payment.rs @@ -132,7 +132,7 @@ where // refund to the the account that paid the fees. If this fails, the // account might have dropped below the existential balance. In // that case we don't refund anything. - let refund_imbalance = C::deposit_into_existing(&who, refund_amount) + let refund_imbalance = C::deposit_into_existing(who, refund_amount) .unwrap_or_else(|_| C::PositiveImbalance::zero()); // merge the imbalance caused by paying the fees and refunding parts of it again. let adjusted_paid = paid diff --git a/frame/transaction-storage/src/lib.rs b/frame/transaction-storage/src/lib.rs index e9aa786766dac..a63b31f2f1aac 100644 --- a/frame/transaction-storage/src/lib.rs +++ b/frame/transaction-storage/src/lib.rs @@ -192,8 +192,8 @@ pub mod pallet { let root = sp_io::trie::blake2_256_ordered_root(chunks, sp_runtime::StateVersion::V1); let content_hash = sp_io::hashing::blake2_256(&data); - let extrinsic_index = >::extrinsic_index() - .ok_or_else(|| Error::::BadContext)?; + let extrinsic_index = + >::extrinsic_index().ok_or(Error::::BadContext)?; sp_io::transaction_index::index(extrinsic_index, data.len() as u32, content_hash); let mut index = 0; @@ -287,13 +287,12 @@ pub mod pallet { Ok(index) => index, Err(index) => index, }; - let info = - infos.get(index).ok_or_else(|| Error::::MissingStateData)?.clone(); + let info = infos.get(index).ok_or(Error::::MissingStateData)?.clone(); let chunks = num_chunks(info.size); let prev_chunks = info.block_chunks - chunks; (info, selected_chunk_index - prev_chunks) }, - None => Err(Error::::MissingStateData)?, + None => return Err(Error::::MissingStateData.into()), }; ensure!( sp_io::trie::blake2_256_verify_proof( diff --git a/frame/uniques/src/benchmarking.rs b/frame/uniques/src/benchmarking.rs index 19d3dfac6e5ac..b743ca12913a3 100644 --- a/frame/uniques/src/benchmarking.rs +++ b/frame/uniques/src/benchmarking.rs @@ -176,7 +176,7 @@ benchmarks_instance_pallet! { let witness = Class::::get(class).unwrap().destroy_witness(); }: _(SystemOrigin::Signed(caller), class, witness) verify { - assert_last_event::(Event::Destroyed { class: class }.into()); + assert_last_event::(Event::Destroyed { class }.into()); } mint { @@ -216,7 +216,7 @@ benchmarks_instance_pallet! { caller_lookup.clone(), caller_lookup.clone(), caller_lookup.clone(), - caller_lookup.clone(), + caller_lookup, true, false, )?; @@ -268,7 +268,7 @@ benchmarks_instance_pallet! { let target_lookup = T::Lookup::unlookup(target.clone()); T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance()); let origin = SystemOrigin::Signed(target.clone()).into(); - Uniques::::set_accept_ownership(origin, Some(class.clone()))?; + Uniques::::set_accept_ownership(origin, Some(class))?; }: _(SystemOrigin::Signed(caller), class, target_lookup) verify { assert_last_event::(Event::OwnerChanged { class, new_owner: target }.into()); @@ -279,7 +279,7 @@ benchmarks_instance_pallet! { let target0 = T::Lookup::unlookup(account("target", 0, SEED)); let target1 = T::Lookup::unlookup(account("target", 1, SEED)); let target2 = T::Lookup::unlookup(account("target", 2, SEED)); - }: _(SystemOrigin::Signed(caller), class, target0.clone(), target1.clone(), target2.clone()) + }: _(SystemOrigin::Signed(caller), class, target0, target1, target2) verify { assert_last_event::(Event::TeamChanged{ class, @@ -297,7 +297,7 @@ benchmarks_instance_pallet! { owner: caller_lookup.clone(), issuer: caller_lookup.clone(), admin: caller_lookup.clone(), - freezer: caller_lookup.clone(), + freezer: caller_lookup, free_holding: true, is_frozen: false, }; @@ -390,7 +390,7 @@ benchmarks_instance_pallet! { let caller: T::AccountId = whitelisted_caller(); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); let class = T::Helper::class(0); - }: _(SystemOrigin::Signed(caller.clone()), Some(class.clone())) + }: _(SystemOrigin::Signed(caller.clone()), Some(class)) verify { assert_last_event::(Event::OwnershipAcceptanceChanged { who: caller, diff --git a/frame/uniques/src/functions.rs b/frame/uniques/src/functions.rs index 8b874997f2ff0..ea9eecd767dcf 100644 --- a/frame/uniques/src/functions.rs +++ b/frame/uniques/src/functions.rs @@ -73,7 +73,7 @@ impl, I: 'static> Pallet { owner: owner.clone(), issuer: admin.clone(), admin: admin.clone(), - freezer: admin.clone(), + freezer: admin, total_deposit: deposit, free_holding, instances: 0, @@ -135,7 +135,7 @@ impl, I: 'static> Pallet { Class::::try_mutate(&class, |maybe_class_details| -> DispatchResult { let class_details = maybe_class_details.as_mut().ok_or(Error::::UnknownClass)?; - with_details(&class_details)?; + with_details(class_details)?; let instances = class_details.instances.checked_add(1).ok_or(ArithmeticError::Overflow)?; @@ -171,7 +171,7 @@ impl, I: 'static> Pallet { maybe_class_details.as_mut().ok_or(Error::::UnknownClass)?; let details = Asset::::get(&class, &instance).ok_or(Error::::UnknownClass)?; - with_details(&class_details, &details)?; + with_details(class_details, &details)?; // Return the deposit. T::Currency::unreserve(&class_details.owner, details.deposit); diff --git a/frame/uniques/src/impl_nonfungibles.rs b/frame/uniques/src/impl_nonfungibles.rs index 89b95fb770489..492befc46967c 100644 --- a/frame/uniques/src/impl_nonfungibles.rs +++ b/frame/uniques/src/impl_nonfungibles.rs @@ -93,12 +93,12 @@ impl, I: 'static> Create<::AccountId> for Pallet admin: &T::AccountId, ) -> DispatchResult { Self::do_create_class( - class.clone(), + *class, who.clone(), admin.clone(), T::ClassDeposit::get(), false, - Event::Created { class: class.clone(), creator: who.clone(), owner: admin.clone() }, + Event::Created { class: *class, creator: who.clone(), owner: admin.clone() }, ) } } @@ -125,7 +125,7 @@ impl, I: 'static> Mutate<::AccountId> for Pallet instance: &Self::InstanceId, who: &T::AccountId, ) -> DispatchResult { - Self::do_mint(class.clone(), instance.clone(), who.clone(), |_| Ok(())) + Self::do_mint(*class, *instance, who.clone(), |_| Ok(())) } fn burn( @@ -133,10 +133,10 @@ impl, I: 'static> Mutate<::AccountId> for Pallet instance: &Self::InstanceId, maybe_check_owner: Option<&T::AccountId>, ) -> DispatchResult { - Self::do_burn(class.clone(), instance.clone(), |_, d| { + Self::do_burn(*class, *instance, |_, d| { if let Some(check_owner) = maybe_check_owner { if &d.owner != check_owner { - Err(Error::::NoPermission)?; + return Err(Error::::NoPermission.into()) } } Ok(()) @@ -150,7 +150,7 @@ impl, I: 'static> Transfer for Pallet { instance: &Self::InstanceId, destination: &T::AccountId, ) -> DispatchResult { - Self::do_transfer(class.clone(), instance.clone(), destination.clone(), |_, _| Ok(())) + Self::do_transfer(*class, *instance, destination.clone(), |_, _| Ok(())) } } diff --git a/frame/uniques/src/lib.rs b/frame/uniques/src/lib.rs index d999acc4c9403..a360a02ebc702 100644 --- a/frame/uniques/src/lib.rs +++ b/frame/uniques/src/lib.rs @@ -720,7 +720,7 @@ pub mod pallet { Class::::try_mutate(class, |maybe_details| { let details = maybe_details.as_mut().ok_or(Error::::UnknownClass)?; - ensure!(&origin == &details.freezer, Error::::NoPermission); + ensure!(origin == details.freezer, Error::::NoPermission); details.is_frozen = true; @@ -744,7 +744,7 @@ pub mod pallet { Class::::try_mutate(class, |maybe_details| { let details = maybe_details.as_mut().ok_or(Error::::UnknownClass)?; - ensure!(&origin == &details.admin, Error::::NoPermission); + ensure!(origin == details.admin, Error::::NoPermission); details.is_frozen = false; @@ -778,7 +778,7 @@ pub mod pallet { Class::::try_mutate(class, |maybe_details| { let details = maybe_details.as_mut().ok_or(Error::::UnknownClass)?; - ensure!(&origin == &details.owner, Error::::NoPermission); + ensure!(origin == details.owner, Error::::NoPermission); if details.owner == owner { return Ok(()) } @@ -827,7 +827,7 @@ pub mod pallet { Class::::try_mutate(class, |maybe_details| { let details = maybe_details.as_mut().ok_or(Error::::UnknownClass)?; - ensure!(&origin == &details.owner, Error::::NoPermission); + ensure!(origin == details.owner, Error::::NoPermission); details.issuer = issuer.clone(); details.admin = admin.clone(); @@ -867,7 +867,7 @@ pub mod pallet { Asset::::get(&class, &instance).ok_or(Error::::UnknownClass)?; if let Some(check) = maybe_check { - let permitted = &check == &class_details.admin || &check == &details.owner; + let permitted = check == class_details.admin || check == details.owner; ensure!(permitted, Error::::NoPermission); } @@ -916,7 +916,7 @@ pub mod pallet { let mut details = Asset::::get(&class, &instance).ok_or(Error::::UnknownClass)?; if let Some(check) = maybe_check { - let permitted = &check == &class_details.admin || &check == &details.owner; + let permitted = check == class_details.admin || check == details.owner; ensure!(permitted, Error::::NoPermission); } let maybe_check_delegate = maybe_check_delegate.map(T::Lookup::lookup).transpose()?; diff --git a/frame/utility/src/benchmarking.rs b/frame/utility/src/benchmarking.rs index 402128d005808..27f346f13f52f 100644 --- a/frame/utility/src/benchmarking.rs +++ b/frame/utility/src/benchmarking.rs @@ -70,7 +70,7 @@ benchmarks! { let call = Box::new(frame_system::Call::remark { remark: vec![] }.into()); let origin: T::Origin = RawOrigin::Signed(caller).into(); let pallets_origin: ::PalletsOrigin = origin.caller().clone(); - let pallets_origin = Into::::into(pallets_origin.clone()); + let pallets_origin = Into::::into(pallets_origin); }: _(RawOrigin::Root, Box::new(pallets_origin), call) impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::tests::Test); diff --git a/frame/vesting/src/benchmarking.rs b/frame/vesting/src/benchmarking.rs index 1693fdd3f1cbe..2b8150e995240 100644 --- a/frame/vesting/src/benchmarking.rs +++ b/frame/vesting/src/benchmarking.rs @@ -72,7 +72,7 @@ fn add_vesting_schedules( T::Currency::make_free_balance_be(&source, BalanceOf::::max_value()); } - Ok(total_locked.into()) + Ok(total_locked) } benchmarks! { @@ -91,7 +91,7 @@ benchmarks! { assert_eq!(System::::block_number(), T::BlockNumber::zero()); assert_eq!( Vesting::::vesting_balance(&caller), - Some(expected_balance.into()), + Some(expected_balance), "Vesting schedule not added", ); }: vest(RawOrigin::Signed(caller.clone())) @@ -99,7 +99,7 @@ benchmarks! { // Nothing happened since everything is still vested. assert_eq!( Vesting::::vesting_balance(&caller), - Some(expected_balance.into()), + Some(expected_balance), "Vesting schedule was removed", ); } @@ -156,7 +156,7 @@ benchmarks! { // Nothing happened since everything is still vested. assert_eq!( Vesting::::vesting_balance(&other), - Some(expected_balance.into()), + Some(expected_balance), "Vesting schedule was removed", ); } @@ -260,7 +260,7 @@ benchmarks! { ); assert_eq!( Vesting::::vesting_balance(&target), - Some(expected_balance.into()), + Some(expected_balance), "Lock not correctly updated", ); } @@ -274,7 +274,7 @@ benchmarks! { // Give target existing locks. add_locks::(&caller, l as u8); // Add max vesting schedules. - let expected_balance = add_vesting_schedules::(caller_lookup.clone(), s)?; + let expected_balance = add_vesting_schedules::(caller_lookup, s)?; // Schedules are not vesting at block 0. assert_eq!(System::::block_number(), T::BlockNumber::zero()); @@ -324,7 +324,7 @@ benchmarks! { // Give target other locks. add_locks::(&caller, l as u8); // Add max vesting schedules. - let total_transferred = add_vesting_schedules::(caller_lookup.clone(), s)?; + let total_transferred = add_vesting_schedules::(caller_lookup, s)?; // Go to about half way through all the schedules duration. (They all start at 1, and have a duration of 20 or 21). System::::set_block_number(11u32.into()); diff --git a/frame/vesting/src/lib.rs b/frame/vesting/src/lib.rs index 13841a0443ceb..23b7d231343b4 100644 --- a/frame/vesting/src/lib.rs +++ b/frame/vesting/src/lib.rs @@ -121,10 +121,10 @@ impl VestingAction { } /// Pick the schedules that this action dictates should continue vesting undisturbed. - fn pick_schedules<'a, T: Config>( - &'a self, + fn pick_schedules( + &self, schedules: Vec, T::BlockNumber>>, - ) -> impl Iterator, T::BlockNumber>> + 'a { + ) -> impl Iterator, T::BlockNumber>> + '_ { schedules.into_iter().enumerate().filter_map(move |(index, schedule)| { if self.should_remove(index) { None @@ -710,7 +710,7 @@ where let (schedules, locked_now) = Self::exec_action(schedules.to_vec(), VestingAction::Passive)?; - Self::write_vesting(&who, schedules)?; + Self::write_vesting(who, schedules)?; Self::write_lock(who, locked_now); Ok(()) @@ -744,7 +744,7 @@ where let (schedules, locked_now) = Self::exec_action(schedules.to_vec(), remove_action)?; - Self::write_vesting(&who, schedules)?; + Self::write_vesting(who, schedules)?; Self::write_lock(who, locked_now); Ok(()) } diff --git a/frame/whitelist/src/benchmarking.rs b/frame/whitelist/src/benchmarking.rs index 50809ddef7ec1..cafd1668819dd 100644 --- a/frame/whitelist/src/benchmarking.rs +++ b/frame/whitelist/src/benchmarking.rs @@ -69,7 +69,7 @@ benchmarks! { let origin = T::DispatchWhitelistedOrigin::successful_origin(); // NOTE: we remove `10` because we need some bytes to encode the variants and vec length let remark_len = >::MaxSize::get() - 10; - let remark = sp_std::vec![1_8; remark_len as usize]; + let remark = sp_std::vec![1u8; remark_len as usize]; let call: ::Call = frame_system::Call::remark { remark }.into(); let call_weight = call.get_dispatch_info().weight; diff --git a/primitives/api/proc-macro/src/decl_runtime_apis.rs b/primitives/api/proc-macro/src/decl_runtime_apis.rs index 2301f531590ee..b031c0f8bb1cc 100644 --- a/primitives/api/proc-macro/src/decl_runtime_apis.rs +++ b/primitives/api/proc-macro/src/decl_runtime_apis.rs @@ -188,7 +188,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_, AllowSelfRefInParameters::No)?; + let params = extract_parameter_names_types_and_borrows(fn_, AllowSelfRefInParameters::No)?; let trait_fn_name = &fn_.ident; let function_name_str = fn_.ident.to_string(); let fn_name = generate_native_call_generator_fn_name(&fn_.ident); @@ -336,7 +336,7 @@ fn generate_call_api_at_calls(decl: &ItemTrait) -> Result { // Generate a native call generator for each function of the given trait. for (attrs, fn_) in fns { let trait_name = &decl.ident; - let trait_fn_name = prefix_function_with_trait(&trait_name, &fn_.ident); + let trait_fn_name = prefix_function_with_trait(trait_name, &fn_.ident); let fn_name = generate_call_api_at_fn_name(&fn_.ident); let attrs = remove_supported_attributes(&mut attrs.clone()); @@ -360,7 +360,7 @@ fn generate_call_api_at_calls(decl: &ItemTrait) -> Result { let mut renames = Vec::new(); if let Some((_, a)) = attrs.iter().find(|a| a.0 == &RENAMED_ATTRIBUTE) { let (old_name, version) = parse_renamed_attribute(a)?; - renames.push((version, prefix_function_with_trait(&trait_name, &old_name))); + renames.push((version, prefix_function_with_trait(trait_name, &old_name))); } renames.sort_by(|l, r| r.cmp(l)); @@ -582,7 +582,7 @@ impl<'a> ToClientSideDecl<'a> { Vec::new() }, }; - let name = generate_method_runtime_api_impl_name(&self.trait_, &method.sig.ident); + let name = generate_method_runtime_api_impl_name(self.trait_, &method.sig.ident); let block_id = self.block_id; let crate_ = self.crate_; @@ -619,9 +619,9 @@ impl<'a> ToClientSideDecl<'a> { let params2 = params.clone(); let ret_type = return_type_extract_type(&method.sig.output); - fold_fn_decl_for_client_side(&mut method.sig, &self.block_id, &self.crate_); + fold_fn_decl_for_client_side(&mut method.sig, self.block_id, self.crate_); - let name_impl = generate_method_runtime_api_impl_name(&self.trait_, &method.sig.ident); + let name_impl = generate_method_runtime_api_impl_name(self.trait_, &method.sig.ident); let crate_ = self.crate_; let found_attributes = remove_supported_attributes(&mut method.attrs); @@ -630,7 +630,7 @@ impl<'a> ToClientSideDecl<'a> { let (native_handling, param_tuple) = match get_changed_in(&found_attributes) { Ok(Some(version)) => { // Make sure that the `changed_in` version is at least the current `api_version`. - if get_api_version(&self.found_attributes).ok() < Some(version) { + if get_api_version(self.found_attributes).ok() < Some(version) { self.errors.push( Error::new( method.span(), @@ -972,7 +972,7 @@ pub fn decl_runtime_apis_impl(input: proc_macro::TokenStream) -> proc_macro::Tok } fn decl_runtime_apis_impl_inner(api_decls: &[ItemTrait]) -> Result { - check_trait_decls(&api_decls)?; + check_trait_decls(api_decls)?; let hidden_includes = generate_hidden_includes(HIDDEN_INCLUDES_ID); let runtime_decls = generate_runtime_decls(api_decls)?; diff --git a/primitives/api/proc-macro/src/impl_runtime_apis.rs b/primitives/api/proc-macro/src/impl_runtime_apis.rs index f594a743fcf94..0ac3cfbe1244e 100644 --- a/primitives/api/proc-macro/src/impl_runtime_apis.rs +++ b/primitives/api/proc-macro/src/impl_runtime_apis.rs @@ -491,7 +491,7 @@ impl<'a> Fold for ApiRuntimeImplToApiRuntimeApiImpl<'a> { }; input.sig.ident = - generate_method_runtime_api_impl_name(&self.impl_trait, &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. @@ -593,7 +593,7 @@ fn generate_api_impl_for_runtime_api(impls: &[ItemImpl]) -> Result let mut result = Vec::with_capacity(impls.len()); for impl_ in impls { - let impl_trait_path = extract_impl_trait(&impl_, RequireQualifiedTraitPath::Yes)?; + let impl_trait_path = extract_impl_trait(impl_, RequireQualifiedTraitPath::Yes)?; let impl_trait = &impl_trait_path .segments .last() @@ -634,7 +634,7 @@ fn generate_runtime_api_versions(impls: &[ItemImpl]) -> Result { for impl_ in impls { let mut path = extend_with_runtime_decl_path( - extract_impl_trait(&impl_, RequireQualifiedTraitPath::Yes)?.clone(), + extract_impl_trait(impl_, RequireQualifiedTraitPath::Yes)?.clone(), ); // Remove the trait let trait_ = path @@ -723,7 +723,7 @@ fn impl_runtime_apis_impl_inner(api_impls: &[ItemImpl]) -> Result { // Filters all attributes except the cfg ones. fn filter_cfg_attrs(attrs: &[Attribute]) -> Vec { - attrs.into_iter().filter(|a| a.path.is_ident("cfg")).cloned().collect() + attrs.iter().filter(|a| a.path.is_ident("cfg")).cloned().collect() } #[cfg(test)] diff --git a/primitives/api/proc-macro/src/mock_impl_runtime_apis.rs b/primitives/api/proc-macro/src/mock_impl_runtime_apis.rs index ffc158ac94d29..6098f8d6bd741 100644 --- a/primitives/api/proc-macro/src/mock_impl_runtime_apis.rs +++ b/primitives/api/proc-macro/src/mock_impl_runtime_apis.rs @@ -283,7 +283,7 @@ impl<'a> Fold for FoldRuntimeApiImpl<'a> { }; input.sig.ident = - generate_method_runtime_api_impl_name(&self.impl_trait, &input.sig.ident); + generate_method_runtime_api_impl_name(self.impl_trait, &input.sig.ident); // When using advanced, the user needs to declare the correct return type on its own, // otherwise do it for the user. @@ -350,7 +350,7 @@ fn generate_runtime_api_impls(impls: &[ItemImpl]) -> Result> = None; for impl_ in impls { - let impl_trait_path = extract_impl_trait(&impl_, RequireQualifiedTraitPath::No)?; + let impl_trait_path = extract_impl_trait(impl_, RequireQualifiedTraitPath::No)?; let impl_trait = &impl_trait_path .segments .last() diff --git a/primitives/api/proc-macro/src/utils.rs b/primitives/api/proc-macro/src/utils.rs index 2aa6a657aa9c0..97b456b62dfa6 100644 --- a/primitives/api/proc-macro/src/utils.rs +++ b/primitives/api/proc-macro/src/utils.rs @@ -61,12 +61,11 @@ pub fn generate_crate_access(unique_id: &'static str) -> TokenStream { let mod_name = generate_hidden_includes_mod_name(unique_id); quote!( self::#mod_name::sp_api ) } - .into() } /// Generates the name of the module that contains the trait declaration for the runtime. pub fn generate_runtime_mod_name_for_trait(trait_: &Ident) -> Ident { - Ident::new(&format!("runtime_decl_for_{}", trait_.to_string()), Span::call_site()) + Ident::new(&format!("runtime_decl_for_{}", trait_), Span::call_site()) } /// Generates a name for a method that needs to be implemented in the runtime for the client side. @@ -169,17 +168,17 @@ pub fn extract_parameter_names_types_and_borrows( /// Generates the name for the native call generator function. pub fn generate_native_call_generator_fn_name(fn_name: &Ident) -> Ident { - Ident::new(&format!("{}_native_call_generator", fn_name.to_string()), Span::call_site()) + Ident::new(&format!("{}_native_call_generator", fn_name), Span::call_site()) } /// Generates the name for the call api at function. pub fn generate_call_api_at_fn_name(fn_name: &Ident) -> Ident { - Ident::new(&format!("{}_call_api_at", fn_name.to_string()), Span::call_site()) + Ident::new(&format!("{}_call_api_at", fn_name), Span::call_site()) } /// Prefix the given function with the trait name. pub fn prefix_function_with_trait(trait_: &Ident, function: &F) -> String { - format!("{}_{}", trait_.to_string(), function.to_string()) + format!("{}_{}", trait_, function.to_string()) } /// Extract all types that appear in signatures in the given `ImplItem`'s. @@ -250,10 +249,7 @@ pub enum RequireQualifiedTraitPath { } /// Extract the trait that is implemented by the given `ItemImpl`. -pub fn extract_impl_trait<'a>( - impl_: &'a ItemImpl, - require: RequireQualifiedTraitPath, -) -> Result<&'a Path> { +pub fn extract_impl_trait(impl_: &ItemImpl, require: RequireQualifiedTraitPath) -> Result<&Path> { impl_ .trait_ .as_ref() diff --git a/primitives/api/src/lib.rs b/primitives/api/src/lib.rs index 964ef15ce5f5a..2635c81948ff3 100644 --- a/primitives/api/src/lib.rs +++ b/primitives/api/src/lib.rs @@ -422,7 +422,7 @@ pub trait ConstructRuntimeApi> { type RuntimeApi: ApiExt; /// Construct an instance of the runtime api. - fn construct_runtime_api<'a>(call: &'a C) -> ApiRef<'a, Self::RuntimeApi>; + fn construct_runtime_api(call: &C) -> ApiRef; } /// Init the [`RuntimeLogger`](sp_runtime::runtime_logger::RuntimeLogger). @@ -554,12 +554,11 @@ pub trait CallApiAt { /// Calls the given api function with the given encoded arguments at the given block and returns /// the encoded result. fn call_api_at< - 'a, R: Encode + Decode + PartialEq, NC: FnOnce() -> result::Result + UnwindSafe, >( &self, - params: CallApiAtParams<'a, Block, NC, Self::StateBackend>, + params: CallApiAtParams, ) -> Result, ApiError>; /// Returns the runtime version at the given block. @@ -604,7 +603,7 @@ pub trait ProvideRuntimeApi { /// call to an api function, will `commit` its changes to an internal buffer. Otherwise, /// the modifications will be `discarded`. The modifications will not be applied to the /// storage, even on a `commit`. - fn runtime_api<'a>(&'a self) -> ApiRef<'a, Self::Api>; + fn runtime_api(&self) -> ApiRef; } /// Something that provides information about a runtime api. diff --git a/primitives/application-crypto/src/ecdsa.rs b/primitives/application-crypto/src/ecdsa.rs index 6a0eb7ab2f84d..6356f54a0b085 100644 --- a/primitives/application-crypto/src/ecdsa.rs +++ b/primitives/application-crypto/src/ecdsa.rs @@ -53,7 +53,7 @@ impl RuntimePublic for Public { } fn verify>(&self, msg: &M, signature: &Self::Signature) -> bool { - sp_io::crypto::ecdsa_verify(&signature, msg.as_ref(), self) + sp_io::crypto::ecdsa_verify(signature, msg.as_ref(), self) } fn to_raw_vec(&self) -> Vec { diff --git a/primitives/application-crypto/src/ed25519.rs b/primitives/application-crypto/src/ed25519.rs index f5ec40233ca93..199e55383b881 100644 --- a/primitives/application-crypto/src/ed25519.rs +++ b/primitives/application-crypto/src/ed25519.rs @@ -53,7 +53,7 @@ impl RuntimePublic for Public { } fn verify>(&self, msg: &M, signature: &Self::Signature) -> bool { - sp_io::crypto::ed25519_verify(&signature, msg.as_ref(), self) + sp_io::crypto::ed25519_verify(signature, msg.as_ref(), self) } fn to_raw_vec(&self) -> Vec { diff --git a/primitives/application-crypto/src/sr25519.rs b/primitives/application-crypto/src/sr25519.rs index 81c5320efd71b..c96e7382eb191 100644 --- a/primitives/application-crypto/src/sr25519.rs +++ b/primitives/application-crypto/src/sr25519.rs @@ -53,7 +53,7 @@ impl RuntimePublic for Public { } fn verify>(&self, msg: &M, signature: &Self::Signature) -> bool { - sp_io::crypto::sr25519_verify(&signature, msg.as_ref(), self) + sp_io::crypto::sr25519_verify(signature, msg.as_ref(), self) } fn to_raw_vec(&self) -> Vec { diff --git a/primitives/arithmetic/benches/bench.rs b/primitives/arithmetic/benches/bench.rs index 3e4fafe2a4a9a..57556135f6431 100644 --- a/primitives/arithmetic/benches/bench.rs +++ b/primitives/arithmetic/benches/bench.rs @@ -41,19 +41,19 @@ fn bench_op(c: &mut Criterion, name: &str, op: F) { fn bench_addition(c: &mut Criterion) { bench_op(c, "addition", |a, b| { - let _ = a.clone().add(&b); + let _ = a.clone().add(b); }); } fn bench_subtraction(c: &mut Criterion) { bench_op(c, "subtraction", |a, b| { - let _ = a.clone().sub(&b); + let _ = a.clone().sub(b); }); } fn bench_multiplication(c: &mut Criterion) { bench_op(c, "multiplication", |a, b| { - let _ = a.clone().mul(&b); + let _ = a.clone().mul(b); }); } diff --git a/primitives/arithmetic/fuzzer/src/normalize.rs b/primitives/arithmetic/fuzzer/src/normalize.rs index dd717115a5c9c..3d2d6eb9acfcc 100644 --- a/primitives/arithmetic/fuzzer/src/normalize.rs +++ b/primitives/arithmetic/fuzzer/src/normalize.rs @@ -35,7 +35,7 @@ fn main() { loop { fuzz!(|data: (Vec, Ty)| { let (data, norm) = data; - if data.len() == 0 { + if data.is_empty() { return } let pre_sum: u128 = data.iter().map(|x| *x as u128).sum(); @@ -44,16 +44,14 @@ fn main() { // error cases. if pre_sum > sum_limit || data.len() > len_limit { assert!(normalized.is_err()) + } else if let Ok(normalized) = normalized { + // if sum goes beyond u128, panic. + let sum: u128 = normalized.iter().map(|x| *x as u128).sum(); + + // if this function returns Ok(), then it will ALWAYS be accurate. + assert_eq!(sum, norm as u128, "sums don't match {:?}, {}", normalized, norm); } else { - if let Ok(normalized) = normalized { - // if sum goes beyond u128, panic. - let sum: u128 = normalized.iter().map(|x| *x as u128).sum(); - - // if this function returns Ok(), then it will ALWAYS be accurate. - assert_eq!(sum, norm as u128, "sums don't match {:?}, {}", normalized, norm); - } else { - panic!("Should have returned Ok for input = {:?}, target = {:?}", data, norm); - } + panic!("Should have returned Ok for input = {:?}, target = {:?}", data, norm); } }) } diff --git a/primitives/arithmetic/src/lib.rs b/primitives/arithmetic/src/lib.rs index 729da123757c7..e43e42763575d 100644 --- a/primitives/arithmetic/src/lib.rs +++ b/primitives/arithmetic/src/lib.rs @@ -65,7 +65,7 @@ where fn tcmp(&self, other: &T, threshold: T) -> Ordering { // early exit. if threshold.is_zero() { - return self.cmp(&other) + return self.cmp(other) } let upper_bound = other.saturating_add(threshold); @@ -73,7 +73,7 @@ where if upper_bound <= lower_bound { // defensive only. Can never happen. - self.cmp(&other) + self.cmp(other) } else { // upper_bound is guaranteed now to be bigger than lower. match (self.cmp(&lower_bound), self.cmp(&upper_bound)) { @@ -113,10 +113,7 @@ impl_normalize_for_numeric!(u8, u16, u32, u64, u128); impl Normalizable

for Vec

{ fn normalize(&self, targeted_sum: P) -> Result, &'static str> { - let uppers = self - .iter() - .map(|p| >::from(p.clone().deconstruct())) - .collect::>(); + let uppers = self.iter().map(|p| >::from(p.deconstruct())).collect::>(); let normalized = normalize(uppers.as_ref(), >::from(targeted_sum.deconstruct()))?; diff --git a/primitives/arithmetic/src/rational.rs b/primitives/arithmetic/src/rational.rs index 63ae6e65bc9ee..1beafbe811614 100644 --- a/primitives/arithmetic/src/rational.rs +++ b/primitives/arithmetic/src/rational.rs @@ -63,14 +63,14 @@ impl Ord for RationalInfinite { fn cmp(&self, other: &Self) -> Ordering { // handle some edge cases. if self.d() == other.d() { - self.n().cmp(&other.n()) + self.n().cmp(other.n()) } else if self.d().is_zero() { Ordering::Greater } else if other.d().is_zero() { Ordering::Less } else { // (a/b) cmp (c/d) => (a*d) cmp (c*b) - self.n().clone().mul(&other.d()).cmp(&other.n().clone().mul(&self.d())) + self.n().clone().mul(other.d()).cmp(&other.n().clone().mul(self.d())) } } } @@ -272,6 +272,7 @@ impl PartialEq for Rational128 { #[cfg(test)] mod tests { use super::{helpers_128bit::*, *}; + use static_assertions::const_assert; const MAX128: u128 = u128::MAX; const MAX64: u128 = u64::MAX as u128; @@ -349,8 +350,8 @@ mod tests { r(1_000_000_000, MAX64).lcm(&r(7_000_000_000, MAX64 - 1)), Ok(340282366920938463408034375210639556610), ); - assert!(340282366920938463408034375210639556610 < MAX128); - assert!(340282366920938463408034375210639556610 == MAX64 * (MAX64 - 1)); + const_assert!(340282366920938463408034375210639556610 < MAX128); + const_assert!(340282366920938463408034375210639556610 == MAX64 * (MAX64 - 1)); } #[test] diff --git a/primitives/blockchain/src/backend.rs b/primitives/blockchain/src/backend.rs index 84b34105e59b8..f80c6d0269116 100644 --- a/primitives/blockchain/src/backend.rs +++ b/primitives/blockchain/src/backend.rs @@ -184,7 +184,7 @@ pub trait Backend: if let Some(max_number) = maybe_max_number { loop { let current_header = self - .header(BlockId::Hash(current_hash.clone()))? + .header(BlockId::Hash(current_hash))? .ok_or_else(|| Error::MissingHeader(current_hash.to_string()))?; if current_header.number() <= &max_number { @@ -204,7 +204,7 @@ pub trait Backend: } let current_header = self - .header(BlockId::Hash(current_hash.clone()))? + .header(BlockId::Hash(current_hash))? .ok_or_else(|| Error::MissingHeader(current_hash.to_string()))?; // stop search in this chain once we go below the target's block number diff --git a/primitives/blockchain/src/header_metadata.rs b/primitives/blockchain/src/header_metadata.rs index 858dcf6c92e80..46477a75b8a1f 100644 --- a/primitives/blockchain/src/header_metadata.rs +++ b/primitives/blockchain/src/header_metadata.rs @@ -275,11 +275,11 @@ pub struct CachedHeaderMetadata { impl From<&Block::Header> for CachedHeaderMetadata { fn from(header: &Block::Header) -> Self { CachedHeaderMetadata { - hash: header.hash().clone(), - number: header.number().clone(), - parent: header.parent_hash().clone(), - state_root: header.state_root().clone(), - ancestor: header.parent_hash().clone(), + hash: header.hash(), + number: *header.number(), + parent: *header.parent_hash(), + state_root: *header.state_root(), + ancestor: *header.parent_hash(), } } } diff --git a/primitives/consensus/common/src/evaluation.rs b/primitives/consensus/common/src/evaluation.rs index d0ddbb6fab813..6a6ca835f3153 100644 --- a/primitives/consensus/common/src/evaluation.rs +++ b/primitives/consensus/common/src/evaluation.rs @@ -49,7 +49,7 @@ pub fn evaluate_initial( parent_number: <::Header as HeaderT>::Number, ) -> Result<()> { let encoded = Encode::encode(proposal); - let proposal = Block::decode(&mut &encoded[..]).map_err(|e| Error::BadProposalFormat(e))?; + let proposal = Block::decode(&mut &encoded[..]).map_err(Error::BadProposalFormat)?; if *parent_hash != *proposal.header().parent_hash() { return Err(Error::WrongParentHash { diff --git a/primitives/consensus/common/src/lib.rs b/primitives/consensus/common/src/lib.rs index 59bbf7618dfc3..2743f434c209b 100644 --- a/primitives/consensus/common/src/lib.rs +++ b/primitives/consensus/common/src/lib.rs @@ -169,7 +169,7 @@ impl ProofRecording for EnableProofRecording { const ENABLED: bool = true; fn into_proof(proof: Option) -> Result { - proof.ok_or_else(|| NoProofRecorded) + proof.ok_or(NoProofRecorded) } } diff --git a/primitives/core/benches/bench.rs b/primitives/core/benches/bench.rs index 44bcd657ba3f0..53421278dca26 100644 --- a/primitives/core/benches/bench.rs +++ b/primitives/core/benches/bench.rs @@ -79,7 +79,7 @@ fn bench_hash_128_dyn_size(c: &mut Criterion) { fn bench_ed25519(c: &mut Criterion) { let mut group = c.benchmark_group("ed25519"); - for msg_size in vec![32, 1024, 1024 * 1024] { + for &msg_size in &[32, 1024, 1024 * 1024] { let msg = (0..msg_size).map(|_| rand::random::()).collect::>(); let key = sp_core::ed25519::Pair::generate().0; group.bench_function(BenchmarkId::new("signing", format!("{}", msg_size)), |b| { @@ -87,7 +87,7 @@ fn bench_ed25519(c: &mut Criterion) { }); } - for msg_size in vec![32, 1024, 1024 * 1024] { + for &msg_size in &[32, 1024, 1024 * 1024] { let msg = (0..msg_size).map(|_| rand::random::()).collect::>(); let key = sp_core::ed25519::Pair::generate().0; let sig = key.sign(&msg); @@ -103,7 +103,7 @@ fn bench_ed25519(c: &mut Criterion) { fn bench_sr25519(c: &mut Criterion) { let mut group = c.benchmark_group("sr25519"); - for msg_size in vec![32, 1024, 1024 * 1024] { + for &msg_size in &[32, 1024, 1024 * 1024] { let msg = (0..msg_size).map(|_| rand::random::()).collect::>(); let key = sp_core::sr25519::Pair::generate().0; group.bench_function(BenchmarkId::new("signing", format!("{}", msg_size)), |b| { @@ -111,7 +111,7 @@ fn bench_sr25519(c: &mut Criterion) { }); } - for msg_size in vec![32, 1024, 1024 * 1024] { + for &msg_size in &[32, 1024, 1024 * 1024] { let msg = (0..msg_size).map(|_| rand::random::()).collect::>(); let key = sp_core::sr25519::Pair::generate().0; let sig = key.sign(&msg); @@ -127,7 +127,7 @@ fn bench_sr25519(c: &mut Criterion) { fn bench_ecdsa(c: &mut Criterion) { let mut group = c.benchmark_group("ecdsa"); - for msg_size in vec![32, 1024, 1024 * 1024] { + for &msg_size in &[32, 1024, 1024 * 1024] { let msg = (0..msg_size).map(|_| rand::random::()).collect::>(); let key = sp_core::ecdsa::Pair::generate().0; group.bench_function(BenchmarkId::new("signing", format!("{}", msg_size)), |b| { @@ -135,7 +135,7 @@ fn bench_ecdsa(c: &mut Criterion) { }); } - for msg_size in vec![32, 1024, 1024 * 1024] { + for &msg_size in &[32, 1024, 1024 * 1024] { let msg = (0..msg_size).map(|_| rand::random::()).collect::>(); let key = sp_core::ecdsa::Pair::generate().0; let sig = key.sign(&msg); diff --git a/primitives/core/hashing/proc-macro/src/impls.rs b/primitives/core/hashing/proc-macro/src/impls.rs index ff9593ea18441..3058cf019b143 100644 --- a/primitives/core/hashing/proc-macro/src/impls.rs +++ b/primitives/core/hashing/proc-macro/src/impls.rs @@ -26,7 +26,7 @@ pub(super) struct MultipleInputBytes(pub Vec>); impl MultipleInputBytes { pub(super) fn concatenated(mut self) -> Vec { - if self.0.len() == 0 { + if self.0.is_empty() { Vec::new() } else { let mut result = core::mem::take(&mut self.0[0]); diff --git a/primitives/core/src/crypto.rs b/primitives/core/src/crypto.rs index f994da1515350..80b44449dbac1 100644 --- a/primitives/core/src/crypto.rs +++ b/primitives/core/src/crypto.rs @@ -1172,7 +1172,7 @@ mod tests { impl ByteArray for TestPublic { const LEN: usize = 0; fn from_slice(bytes: &[u8]) -> Result { - if bytes.len() == 0 { + if bytes.is_empty() { Ok(Self) } else { Err(()) diff --git a/primitives/core/src/ecdsa.rs b/primitives/core/src/ecdsa.rs index 6343e3f4dfd0d..485e39d3a71db 100644 --- a/primitives/core/src/ecdsa.rs +++ b/primitives/core/src/ecdsa.rs @@ -364,7 +364,7 @@ impl From for Signature { /// Derive a single hard junction. #[cfg(feature = "full_crypto")] fn derive_hard_junction(secret_seed: &Seed, cc: &[u8; 32]) -> Seed { - ("Secp256k1HDKD", secret_seed, cc).using_encoded(|data| sp_core_hashing::blake2_256(data)) + ("Secp256k1HDKD", secret_seed, cc).using_encoded(sp_core_hashing::blake2_256) } /// An error when deriving a key. @@ -763,12 +763,12 @@ mod test { set_default_ss58_version(Ss58AddressFormat::custom(200)); // custom addr encoded by version 200 let addr = "4pbsSkWcBaYoFHrKJZp5fDVUKbqSYD9dhZZGvpp3vQ5ysVs5ybV"; - Public::from_ss58check(&addr).unwrap(); + Public::from_ss58check(addr).unwrap(); set_default_ss58_version(default_format); // set current ss58 version to default version let addr = "KWAfgC2aRG5UVD6CpbPQXCx4YZZUhvWqqAJE6qcYc9Rtr6g5C"; - Public::from_ss58check(&addr).unwrap(); + Public::from_ss58check(addr).unwrap(); println!("CUSTOM_FORMAT_SUCCESSFUL"); } else { diff --git a/primitives/core/src/ed25519.rs b/primitives/core/src/ed25519.rs index 0bde9e2e5303a..177af0651c0ef 100644 --- a/primitives/core/src/ed25519.rs +++ b/primitives/core/src/ed25519.rs @@ -397,7 +397,7 @@ impl From<&Public> for CryptoTypePublicPair { /// Derive a single hard junction. #[cfg(feature = "full_crypto")] fn derive_hard_junction(secret_seed: &Seed, cc: &[u8; 32]) -> Seed { - ("Ed25519HDKD", secret_seed, cc).using_encoded(|data| sp_core_hashing::blake2_256(data)) + ("Ed25519HDKD", secret_seed, cc).using_encoded(sp_core_hashing::blake2_256) } /// An error when deriving a key. diff --git a/primitives/core/src/hexdisplay.rs b/primitives/core/src/hexdisplay.rs index e5262ba8f657b..26c04c433b49f 100644 --- a/primitives/core/src/hexdisplay.rs +++ b/primitives/core/src/hexdisplay.rs @@ -69,13 +69,13 @@ impl AsBytesRef for &[u8] { impl AsBytesRef for [u8] { fn as_bytes_ref(&self) -> &[u8] { - &self + self } } impl AsBytesRef for sp_std::vec::Vec { fn as_bytes_ref(&self) -> &[u8] { - &self + self } } diff --git a/primitives/debug-derive/src/impls.rs b/primitives/debug-derive/src/impls.rs index 060997fe97821..51a4d876c79b6 100644 --- a/primitives/debug-derive/src/impls.rs +++ b/primitives/debug-derive/src/impls.rs @@ -67,10 +67,9 @@ mod implementation { /// Derive the inner implementation of `Debug::fmt` function. pub fn derive(name_str: &str, data: &Data) -> TokenStream { match *data { - Data::Struct(ref s) => derive_struct(&name_str, &s.fields), - Data::Union(ref u) => - derive_fields(&name_str, Fields::new(u.fields.named.iter(), None)), - Data::Enum(ref e) => derive_enum(&name_str, &e), + Data::Struct(ref s) => derive_struct(name_str, &s.fields), + Data::Union(ref u) => derive_fields(name_str, Fields::new(u.fields.named.iter(), None)), + Data::Enum(ref e) => derive_enum(name_str, e), } } diff --git a/primitives/externalities/src/extensions.rs b/primitives/externalities/src/extensions.rs index d58edb749e9e6..5db40f12c21aa 100644 --- a/primitives/externalities/src/extensions.rs +++ b/primitives/externalities/src/extensions.rs @@ -174,9 +174,7 @@ impl Extensions { } /// Returns a mutable iterator over all extensions. - pub fn iter_mut<'a>( - &'a mut self, - ) -> impl Iterator)> { + pub fn iter_mut(&mut self) -> impl Iterator)> { self.extensions.iter_mut() } } diff --git a/primitives/finality-grandpa/src/lib.rs b/primitives/finality-grandpa/src/lib.rs index dd5cef85a2ba5..4be42c3d19b6c 100644 --- a/primitives/finality-grandpa/src/lib.rs +++ b/primitives/finality-grandpa/src/lib.rs @@ -59,7 +59,7 @@ pub const GRANDPA_ENGINE_ID: ConsensusEngineId = *b"FRNK"; /// The storage key for the current set of weighted Grandpa authorities. /// The value stored is an encoded VersionedAuthorityList. -pub const GRANDPA_AUTHORITIES_KEY: &'static [u8] = b":grandpa_authorities"; +pub const GRANDPA_AUTHORITIES_KEY: &[u8] = b":grandpa_authorities"; /// The weight of an authority. pub type AuthorityWeight = u64; diff --git a/primitives/io/src/lib.rs b/primitives/io/src/lib.rs index 2ff49b5048810..8f62b03b62ea9 100644 --- a/primitives/io/src/lib.rs +++ b/primitives/io/src/lib.rs @@ -220,7 +220,7 @@ pub trait Storage { /// Get the next key in storage after the given one in lexicographic order. fn next_key(&mut self, key: &[u8]) -> Option> { - self.next_storage_key(&key) + self.next_storage_key(key) } /// Start a new nested transaction. @@ -629,7 +629,7 @@ pub trait Crypto { /// /// Returns the public key. fn ed25519_generate(&mut self, id: KeyTypeId, seed: Option>) -> ed25519::Public { - let seed = seed.as_ref().map(|s| std::str::from_utf8(&s).expect("Seed is valid utf8!")); + let seed = seed.as_ref().map(|s| std::str::from_utf8(s).expect("Seed is valid utf8!")); let keystore = &***self .extension::() .expect("No `keystore` associated for the current context!"); @@ -678,7 +678,7 @@ pub trait Crypto { pub_key: &ed25519::Public, ) -> bool { self.extension::() - .map(|extension| extension.push_ed25519(sig.clone(), pub_key.clone(), msg.to_vec())) + .map(|extension| extension.push_ed25519(sig.clone(), *pub_key, msg.to_vec())) .unwrap_or_else(|| ed25519_verify(sig, msg, pub_key)) } @@ -705,7 +705,7 @@ pub trait Crypto { pub_key: &sr25519::Public, ) -> bool { self.extension::() - .map(|extension| extension.push_sr25519(sig.clone(), pub_key.clone(), msg.to_vec())) + .map(|extension| extension.push_sr25519(sig.clone(), *pub_key, msg.to_vec())) .unwrap_or_else(|| sr25519_verify(sig, msg, pub_key)) } @@ -753,7 +753,7 @@ pub trait Crypto { /// /// Returns the public key. fn sr25519_generate(&mut self, id: KeyTypeId, seed: Option>) -> sr25519::Public { - let seed = seed.as_ref().map(|s| std::str::from_utf8(&s).expect("Seed is valid utf8!")); + let seed = seed.as_ref().map(|s| std::str::from_utf8(s).expect("Seed is valid utf8!")); let keystore = &***self .extension::() .expect("No `keystore` associated for the current context!"); @@ -803,7 +803,7 @@ pub trait Crypto { /// /// Returns the public key. fn ecdsa_generate(&mut self, id: KeyTypeId, seed: Option>) -> ecdsa::Public { - let seed = seed.as_ref().map(|s| std::str::from_utf8(&s).expect("Seed is valid utf8!")); + let seed = seed.as_ref().map(|s| std::str::from_utf8(s).expect("Seed is valid utf8!")); let keystore = &***self .extension::() .expect("No `keystore` associated for the current context!"); @@ -888,7 +888,7 @@ pub trait Crypto { pub_key: &ecdsa::Public, ) -> bool { self.extension::() - .map(|extension| extension.push_ecdsa(sig.clone(), pub_key.clone(), msg.to_vec())) + .map(|extension| extension.push_ecdsa(sig.clone(), *pub_key, msg.to_vec())) .unwrap_or_else(|| ecdsa_verify(sig, msg, pub_key)) } @@ -1504,14 +1504,7 @@ pub trait Sandbox { state_ptr: Pointer, ) -> u32 { self.sandbox() - .invoke( - instance_idx, - &function, - &args, - return_val_ptr, - return_val_len, - state_ptr.into(), - ) + .invoke(instance_idx, function, args, return_val_ptr, return_val_len, state_ptr.into()) .expect("Failed to invoke function with sandbox") } diff --git a/primitives/keyring/src/ed25519.rs b/primitives/keyring/src/ed25519.rs index 6d56062d3473c..404e4121e71a3 100644 --- a/primitives/keyring/src/ed25519.rs +++ b/primitives/keyring/src/ed25519.rs @@ -125,7 +125,7 @@ lazy_static! { impl From for Public { fn from(k: Keyring) -> Self { - (*PUBLIC_KEYS).get(&k).unwrap().clone() + *(*PUBLIC_KEYS).get(&k).unwrap() } } diff --git a/primitives/keyring/src/sr25519.rs b/primitives/keyring/src/sr25519.rs index 86bcac9d58cbb..115a7fce70122 100644 --- a/primitives/keyring/src/sr25519.rs +++ b/primitives/keyring/src/sr25519.rs @@ -168,7 +168,7 @@ impl From for AccountId32 { impl From for Public { fn from(k: Keyring) -> Self { - (*PUBLIC_KEYS).get(&k).unwrap().clone() + *(*PUBLIC_KEYS).get(&k).unwrap() } } diff --git a/primitives/keystore/src/testing.rs b/primitives/keystore/src/testing.rs index 2723b743c10db..a9ec6709d912a 100644 --- a/primitives/keystore/src/testing.rs +++ b/primitives/keystore/src/testing.rs @@ -316,7 +316,7 @@ impl SyncCryptoStore for KeyStore { fn has_keys(&self, public_keys: &[(Vec, KeyTypeId)]) -> bool { public_keys .iter() - .all(|(k, t)| self.keys.read().get(&t).and_then(|s| s.get(k)).is_some()) + .all(|(k, t)| self.keys.read().get(t).and_then(|s| s.get(k)).is_some()) } fn supported_keys( diff --git a/primitives/merkle-mountain-range/src/lib.rs b/primitives/merkle-mountain-range/src/lib.rs index a27536b8d35b7..60ef02c53001c 100644 --- a/primitives/merkle-mountain-range/src/lib.rs +++ b/primitives/merkle-mountain-range/src/lib.rs @@ -231,7 +231,7 @@ impl DataOrHash { pub fn hash(&self) -> H::Output { match *self { Self::Data(ref leaf) => leaf.using_encoded(::hash, true), - Self::Hash(ref hash) => hash.clone(), + Self::Hash(ref hash) => *hash, } } } diff --git a/primitives/npos-elections/fuzzer/src/common.rs b/primitives/npos-elections/fuzzer/src/common.rs index 1bef899d5e54c..6b89983c16abe 100644 --- a/primitives/npos-elections/fuzzer/src/common.rs +++ b/primitives/npos-elections/fuzzer/src/common.rs @@ -99,7 +99,7 @@ pub fn generate_random_npos_inputs( let mut chosen_candidates = Vec::with_capacity(n_candidates_chosen); chosen_candidates.extend(candidates.choose_multiple(&mut rng, n_candidates_chosen)); - chosen_candidates.sort(); + chosen_candidates.sort_unstable(); voters.push((id, vote_weight, chosen_candidates)); } diff --git a/primitives/npos-elections/fuzzer/src/phragmen_balancing.rs b/primitives/npos-elections/fuzzer/src/phragmen_balancing.rs index 76641fc2c79fe..cacf64e81c8f7 100644 --- a/primitives/npos-elections/fuzzer/src/phragmen_balancing.rs +++ b/primitives/npos-elections/fuzzer/src/phragmen_balancing.rs @@ -53,11 +53,9 @@ fn main() { let stake_of = |who: &AccountId| -> VoteWeight { *stake_of_tree.get(who).unwrap() }; let unbalanced_score = { - let staked = assignment_ratio_to_staked_normalized( - unbalanced.assignments.clone(), - &stake_of, - ) - .unwrap(); + let staked = + assignment_ratio_to_staked_normalized(unbalanced.assignments, &stake_of) + .unwrap(); let score = to_supports(staked.as_ref()).evaluate(); if score.minimal_stake == 0 { @@ -72,11 +70,9 @@ fn main() { seq_phragmen(to_elect, candidates, voters, Some((iterations, 0))).unwrap(); let balanced_score = { - let staked = assignment_ratio_to_staked_normalized( - balanced.assignments.clone(), - &stake_of, - ) - .unwrap(); + let staked = + assignment_ratio_to_staked_normalized(balanced.assignments, &stake_of) + .unwrap(); to_supports(staked.as_ref()).evaluate() }; diff --git a/primitives/npos-elections/fuzzer/src/phragmms_balancing.rs b/primitives/npos-elections/fuzzer/src/phragmms_balancing.rs index 09daf3f34d32e..988889428c9cf 100644 --- a/primitives/npos-elections/fuzzer/src/phragmms_balancing.rs +++ b/primitives/npos-elections/fuzzer/src/phragmms_balancing.rs @@ -53,11 +53,9 @@ fn main() { let stake_of = |who: &AccountId| -> VoteWeight { *stake_of_tree.get(who).unwrap() }; let unbalanced_score = { - let staked = assignment_ratio_to_staked_normalized( - unbalanced.assignments.clone(), - &stake_of, - ) - .unwrap(); + let staked = + assignment_ratio_to_staked_normalized(unbalanced.assignments, &stake_of) + .unwrap(); let score = to_supports(&staked).evaluate(); if score.minimal_stake == 0 { @@ -72,8 +70,7 @@ fn main() { let balanced_score = { let staked = - assignment_ratio_to_staked_normalized(balanced.assignments.clone(), &stake_of) - .unwrap(); + assignment_ratio_to_staked_normalized(balanced.assignments, &stake_of).unwrap(); to_supports(staked.as_ref()).evaluate() }; diff --git a/primitives/npos-elections/fuzzer/src/reduce.rs b/primitives/npos-elections/fuzzer/src/reduce.rs index a77b40ca56d54..605f2d6081a6f 100644 --- a/primitives/npos-elections/fuzzer/src/reduce.rs +++ b/primitives/npos-elections/fuzzer/src/reduce.rs @@ -118,7 +118,7 @@ fn reduce_and_compare(assignment: &Vec>, winners: &V let n = assignment.len() as u32; let m = winners.len() as u32; - let edges_before = assignment_len(&assignment); + let edges_before = assignment_len(assignment); let num_changed = reduce(&mut altered_assignment); let edges_after = edges_before - num_changed; diff --git a/primitives/npos-elections/src/helpers.rs b/primitives/npos-elections/src/helpers.rs index 76cfd59de6fb7..598eb3b2ecc2c 100644 --- a/primitives/npos-elections/src/helpers.rs +++ b/primitives/npos-elections/src/helpers.rs @@ -49,13 +49,9 @@ where for<'r> FS: Fn(&'r A) -> VoteWeight, { let mut staked = assignment_ratio_to_staked(ratio, &stake_of); - staked - .iter_mut() - .map(|a| { - a.try_normalize(stake_of(&a.who).into()) - .map_err(|err| Error::ArithmeticError(err)) - }) - .collect::>()?; + staked.iter_mut().try_for_each(|a| { + a.try_normalize(stake_of(&a.who).into()).map_err(Error::ArithmeticError) + })?; Ok(staked) } @@ -74,7 +70,7 @@ pub fn assignment_staked_to_ratio_normalized( ) -> Result>, Error> { let mut ratio = staked.into_iter().map(|a| a.into_assignment()).collect::>(); for assignment in ratio.iter_mut() { - assignment.try_normalize().map_err(|err| Error::ArithmeticError(err))?; + assignment.try_normalize().map_err(Error::ArithmeticError)?; } Ok(ratio) } diff --git a/primitives/npos-elections/src/lib.rs b/primitives/npos-elections/src/lib.rs index 93fb24eb4a3ca..63b9740b74639 100644 --- a/primitives/npos-elections/src/lib.rs +++ b/primitives/npos-elections/src/lib.rs @@ -456,8 +456,8 @@ pub fn to_support_map( let mut supports = >>::new(); // build support struct. - for StakedAssignment { who, distribution } in assignments.into_iter() { - for (c, weight_extended) in distribution.into_iter() { + for StakedAssignment { who, distribution } in assignments.iter() { + for (c, weight_extended) in distribution.iter() { let mut support = supports.entry(c.clone()).or_default(); support.total = support.total.saturating_add(*weight_extended); support.voters.push((who.clone(), *weight_extended)); diff --git a/primitives/npos-elections/src/phragmen.rs b/primitives/npos-elections/src/phragmen.rs index e8e925935f774..9b0bfa42215c3 100644 --- a/primitives/npos-elections/src/phragmen.rs +++ b/primitives/npos-elections/src/phragmen.rs @@ -97,8 +97,7 @@ pub fn seq_phragmen( voters.into_iter().filter_map(|v| v.into_assignment()).collect::>(); let _ = assignments .iter_mut() - .map(|a| a.try_normalize().map_err(|e| crate::Error::ArithmeticError(e))) - .collect::>()?; + .try_for_each(|a| a.try_normalize().map_err(crate::Error::ArithmeticError))?; let winners = winners .into_iter() .map(|w_ptr| (w_ptr.borrow().who.clone(), w_ptr.borrow().backed_stake)) @@ -200,7 +199,7 @@ pub fn seq_phragmen_core( // edge of all candidates that eventually have a non-zero weight must be elected. debug_assert!(voter.edges.iter().all(|e| e.candidate.borrow().elected)); // inc budget to sum the budget. - voter.try_normalize_elected().map_err(|e| crate::Error::ArithmeticError(e))?; + voter.try_normalize_elected().map_err(crate::Error::ArithmeticError)?; } Ok((candidates, voters)) diff --git a/primitives/npos-elections/src/phragmms.rs b/primitives/npos-elections/src/phragmms.rs index aa4c558bea1da..5d63517c8e229 100644 --- a/primitives/npos-elections/src/phragmms.rs +++ b/primitives/npos-elections/src/phragmms.rs @@ -70,9 +70,8 @@ pub fn phragmms( voters.into_iter().filter_map(|v| v.into_assignment()).collect::>(); let _ = assignments .iter_mut() - .map(|a| a.try_normalize()) - .collect::>() - .map_err(|e| crate::Error::ArithmeticError(e))?; + .try_for_each(|a| a.try_normalize()) + .map_err(crate::Error::ArithmeticError)?; let winners = winners .into_iter() .map(|w_ptr| (w_ptr.borrow().who.clone(), w_ptr.borrow().backed_stake)) @@ -157,16 +156,14 @@ pub(crate) fn calculate_max_score( // `RationalInfinite` as the score type does not introduce significant overhead. Then we // can switch the score type to `RationalInfinite` and ensure compatibility with any // crazy token scale. - let score_n = candidate - .approval_stake - .checked_mul(one) - .unwrap_or_else(|| Bounded::max_value()); + let score_n = + candidate.approval_stake.checked_mul(one).unwrap_or_else(Bounded::max_value); candidate.score = Rational128::from(score_n, score_d); // check if we have a new winner. if !candidate.elected && candidate.score > best_score { best_score = candidate.score; - best_candidate = Some(Rc::clone(&c_ptr)); + best_candidate = Some(Rc::clone(c_ptr)); } } else { candidate.score = Rational128::zero(); diff --git a/primitives/npos-elections/src/pjr.rs b/primitives/npos-elections/src/pjr.rs index 2d58ca49c8a22..914834fbb2aef 100644 --- a/primitives/npos-elections/src/pjr.rs +++ b/primitives/npos-elections/src/pjr.rs @@ -212,7 +212,7 @@ fn validate_pjr_challenge_core( None => return false, Some(candidate) => candidate.clone(), }; - pre_score(candidate, &voters, threshold) >= threshold + pre_score(candidate, voters, threshold) >= threshold } /// Convert the data types that the user runtime has into ones that can be used by this module. @@ -351,7 +351,7 @@ fn pre_score( debug_assert!(!unelected.borrow().elected); voters .iter() - .filter(|ref v| v.votes_for(&unelected.borrow().who)) + .filter(|v| v.votes_for(&unelected.borrow().who)) .fold(Zero::zero(), |acc: ExtendedBalance, voter| acc.saturating_add(slack(voter, t))) } diff --git a/primitives/npos-elections/src/reduce.rs b/primitives/npos-elections/src/reduce.rs index f089a37e3fff3..c802a29504709 100644 --- a/primitives/npos-elections/src/reduce.rs +++ b/primitives/npos-elections/src/reduce.rs @@ -94,7 +94,7 @@ fn merge(voter_root_path: Vec>, target_root_path: Vec shorter_path .iter() .zip(shorter_path.iter().skip(1)) - .for_each(|(voter, next)| Node::set_parent_of(&next, &voter)); + .for_each(|(voter, next)| Node::set_parent_of(next, voter)); Node::set_parent_of(&shorter_path[0], &longer_path[0]); } @@ -524,12 +524,10 @@ fn reduce_all(assignments: &mut Vec>) -> u32 } else { ass.distribution[idx].1.saturating_sub(min_value) } + } else if start_operation_add { + ass.distribution[idx].1.saturating_sub(min_value) } else { - if start_operation_add { - ass.distribution[idx].1.saturating_sub(min_value) - } else { - ass.distribution[idx].1.saturating_add(min_value) - } + ass.distribution[idx].1.saturating_add(min_value) }; if next_value.is_zero() { @@ -569,12 +567,10 @@ fn reduce_all(assignments: &mut Vec>) -> u32 } else { ass.distribution[idx].1.saturating_add(min_value) } + } else if start_operation_add { + ass.distribution[idx].1.saturating_add(min_value) } else { - if start_operation_add { - ass.distribution[idx].1.saturating_add(min_value) - } else { - ass.distribution[idx].1.saturating_sub(min_value) - } + ass.distribution[idx].1.saturating_sub(min_value) }; if next_value.is_zero() { diff --git a/primitives/panic-handler/src/lib.rs b/primitives/panic-handler/src/lib.rs index df1f78da1cbef..e06fe90ad6f3b 100644 --- a/primitives/panic-handler/src/lib.rs +++ b/primitives/panic-handler/src/lib.rs @@ -158,7 +158,7 @@ fn panic_hook(info: &PanicInfo, report_url: &str, version: &str) { }, }; - let msg = strip_control_codes(&msg); + let msg = strip_control_codes(msg); let thread = thread::current(); let name = thread.name().unwrap_or(""); @@ -167,13 +167,13 @@ fn panic_hook(info: &PanicInfo, report_url: &str, version: &str) { let mut stderr = io::stderr(); - let _ = writeln!(stderr, ""); + let _ = writeln!(stderr); let _ = writeln!(stderr, "===================="); - let _ = writeln!(stderr, ""); + let _ = writeln!(stderr); let _ = writeln!(stderr, "Version: {}", version); - let _ = writeln!(stderr, ""); + let _ = writeln!(stderr); let _ = writeln!(stderr, "{:?}", backtrace); - let _ = writeln!(stderr, ""); + let _ = writeln!(stderr); let _ = writeln!(stderr, "Thread '{}' panicked at '{}', {}:{}", name, msg, file, line); let _ = writeln!(stderr, ABOUT_PANIC!(), report_url); diff --git a/primitives/runtime-interface/proc-macro/src/pass_by/enum_.rs b/primitives/runtime-interface/proc-macro/src/pass_by/enum_.rs index 7c3f066f6c83b..e25295fdca5cb 100644 --- a/primitives/runtime-interface/proc-macro/src/pass_by/enum_.rs +++ b/primitives/runtime-interface/proc-macro/src/pass_by/enum_.rs @@ -79,7 +79,7 @@ pub fn derive_impl(input: DeriveInput) -> Result { /// /// Returns an error if the number of variants is greater than `256`, the given `data` is not an /// enum or a variant is not an unit. -fn get_enum_field_idents<'a>(data: &'a Data) -> Result>> { +fn get_enum_field_idents(data: &Data) -> Result>> { match data { Data::Enum(d) => if d.variants.len() <= 256 { diff --git a/primitives/runtime-interface/proc-macro/src/utils.rs b/primitives/runtime-interface/proc-macro/src/utils.rs index 19f7fea023c30..386eef153f45c 100644 --- a/primitives/runtime-interface/proc-macro/src/utils.rs +++ b/primitives/runtime-interface/proc-macro/src/utils.rs @@ -65,13 +65,11 @@ impl RuntimeInterfaceFunction { } }); - if should_trap_on_return { - if !matches!(item.sig.output, syn::ReturnType::Default) { - return Err(Error::new( - item.sig.ident.span(), - "Methods marked as #[trap_on_return] cannot return anything", - )) - } + if should_trap_on_return && !matches!(item.sig.output, syn::ReturnType::Default) { + return Err(Error::new( + item.sig.ident.span(), + "Methods marked as #[trap_on_return] cannot return anything", + )) } Ok(Self { item, should_trap_on_return }) @@ -212,7 +210,7 @@ pub fn create_function_ident_with_version(name: &Ident, version: u32) -> Ident { } /// Returns the function arguments of the given `Signature`, minus any `self` arguments. -pub fn get_function_arguments<'a>(sig: &'a Signature) -> impl Iterator + 'a { +pub fn get_function_arguments(sig: &Signature) -> impl Iterator + '_ { sig.inputs .iter() .filter_map(|a| match a { @@ -234,20 +232,20 @@ pub fn get_function_arguments<'a>(sig: &'a Signature) -> impl Iterator(sig: &'a Signature) -> impl Iterator> + 'a { +pub fn get_function_argument_names(sig: &Signature) -> impl Iterator> + '_ { get_function_arguments(sig).map(|pt| pt.pat) } /// Returns the function argument types of the given `Signature`, minus any `Self` type. -pub fn get_function_argument_types<'a>(sig: &'a Signature) -> impl Iterator> + 'a { +pub fn get_function_argument_types(sig: &Signature) -> impl Iterator> + '_ { get_function_arguments(sig).map(|pt| pt.ty) } /// Returns the function argument types, minus any `Self` type. If any of the arguments /// is a reference, the underlying type without the ref is returned. -pub fn get_function_argument_types_without_ref<'a>( - sig: &'a Signature, -) -> impl Iterator> + 'a { +pub fn get_function_argument_types_without_ref( + sig: &Signature, +) -> impl Iterator> + '_ { get_function_arguments(sig).map(|pt| pt.ty).map(|ty| match *ty { Type::Reference(type_ref) => type_ref.elem, _ => ty, @@ -256,9 +254,9 @@ pub fn get_function_argument_types_without_ref<'a>( /// Returns the function argument names and types, minus any `self`. If any of the arguments /// is a reference, the underlying type without the ref is returned. -pub fn get_function_argument_names_and_types_without_ref<'a>( - sig: &'a Signature, -) -> impl Iterator, Box)> + 'a { +pub fn get_function_argument_names_and_types_without_ref( + sig: &Signature, +) -> impl Iterator, Box)> + '_ { get_function_arguments(sig).map(|pt| match *pt.ty { Type::Reference(type_ref) => (pt.pat, type_ref.elem), _ => (pt.pat, pt.ty), @@ -267,9 +265,9 @@ pub fn get_function_argument_names_and_types_without_ref<'a>( /// Returns the `&`/`&mut` for all function argument types, minus the `self` arg. If a function /// argument is not a reference, `None` is returned. -pub fn get_function_argument_types_ref_and_mut<'a>( - sig: &'a Signature, -) -> impl Iterator)>> + 'a { +pub fn get_function_argument_types_ref_and_mut( + sig: &Signature, +) -> impl Iterator)>> + '_ { get_function_arguments(sig).map(|pt| pt.ty).map(|ty| match *ty { Type::Reference(type_ref) => Some((type_ref.and_token, type_ref.mutability)), _ => None, @@ -277,7 +275,7 @@ pub fn get_function_argument_types_ref_and_mut<'a>( } /// Returns an iterator over all trait methods for the given trait definition. -fn get_trait_methods<'a>(trait_def: &'a ItemTrait) -> impl Iterator { +fn get_trait_methods(trait_def: &ItemTrait) -> impl Iterator { trait_def.items.iter().filter_map(|i| match i { TraitItem::Method(ref method) => Some(method), _ => None, diff --git a/primitives/runtime/src/generic/digest.rs b/primitives/runtime/src/generic/digest.rs index 55e0d69fad33d..ec74ebb0d4e15 100644 --- a/primitives/runtime/src/generic/digest.rs +++ b/primitives/runtime/src/generic/digest.rs @@ -321,7 +321,7 @@ impl<'a> DigestItemRef<'a> { /// Cast this digest item into `PreRuntime` pub fn as_pre_runtime(&self) -> Option<(ConsensusEngineId, &'a [u8])> { match *self { - Self::PreRuntime(consensus_engine_id, ref data) => Some((*consensus_engine_id, data)), + Self::PreRuntime(consensus_engine_id, data) => Some((*consensus_engine_id, data)), _ => None, } } @@ -329,7 +329,7 @@ impl<'a> DigestItemRef<'a> { /// Cast this digest item into `Consensus` pub fn as_consensus(&self) -> Option<(ConsensusEngineId, &'a [u8])> { match *self { - Self::Consensus(consensus_engine_id, ref data) => Some((*consensus_engine_id, data)), + Self::Consensus(consensus_engine_id, data) => Some((*consensus_engine_id, data)), _ => None, } } @@ -337,7 +337,7 @@ impl<'a> DigestItemRef<'a> { /// Cast this digest item into `Seal` pub fn as_seal(&self) -> Option<(ConsensusEngineId, &'a [u8])> { match *self { - Self::Seal(consensus_engine_id, ref data) => Some((*consensus_engine_id, data)), + Self::Seal(consensus_engine_id, data) => Some((*consensus_engine_id, data)), _ => None, } } @@ -345,7 +345,7 @@ impl<'a> DigestItemRef<'a> { /// Cast this digest item into `PreRuntime` pub fn as_other(&self) -> Option<&'a [u8]> { match *self { - Self::Other(ref data) => Some(data), + Self::Other(data) => Some(data), _ => None, } } diff --git a/primitives/runtime/src/offchain/http.rs b/primitives/runtime/src/offchain/http.rs index c479062de5f11..dede4db5dd3de 100644 --- a/primitives/runtime/src/offchain/http.rs +++ b/primitives/runtime/src/offchain/http.rs @@ -452,7 +452,7 @@ impl Headers { let raw = name.as_bytes(); for &(ref key, ref val) in &self.raw { if &**key == raw { - return str::from_utf8(&val).ok() + return str::from_utf8(val).ok() } } None diff --git a/primitives/runtime/src/offchain/storage_lock.rs b/primitives/runtime/src/offchain/storage_lock.rs index 90f8df7945578..4ea9030745296 100644 --- a/primitives/runtime/src/offchain/storage_lock.rs +++ b/primitives/runtime/src/offchain/storage_lock.rs @@ -134,7 +134,7 @@ impl Lockable for Time { fn snooze(deadline: &Self::Deadline) { let now = offchain::timestamp(); - let remainder: Duration = now.diff(&deadline); + let remainder: Duration = now.diff(deadline); // do not snooze the full duration, but instead snooze max 100ms // it might get unlocked in another thread use core::cmp::{max, min}; diff --git a/primitives/runtime/src/runtime_string.rs b/primitives/runtime/src/runtime_string.rs index fcbdd2e787ff3..762af0acd9250 100644 --- a/primitives/runtime/src/runtime_string.rs +++ b/primitives/runtime/src/runtime_string.rs @@ -98,8 +98,8 @@ impl std::ops::Deref for RuntimeString { fn deref(&self) -> &str { match self { - Self::Borrowed(val) => &val, - Self::Owned(val) => &val, + Self::Borrowed(val) => val, + Self::Owned(val) => val, } } } diff --git a/primitives/runtime/src/traits.rs b/primitives/runtime/src/traits.rs index 9c71b2023d3f8..dc13567df58db 100644 --- a/primitives/runtime/src/traits.rs +++ b/primitives/runtime/src/traits.rs @@ -159,7 +159,7 @@ where use sp_application_crypto::IsWrappedBy; let inner: &S = self.as_ref(); let inner_pubkey = - <::Public as sp_application_crypto::AppPublic>::Generic::from_ref(&signer); + <::Public as sp_application_crypto::AppPublic>::Generic::from_ref(signer); Verify::verify(inner, msg, inner_pubkey) } } @@ -1284,7 +1284,7 @@ impl AccountIdConversion fo fn try_from_sub_account(x: &T) -> Option<(Self, S)> { x.using_encoded(|d| { - if &d[0..4] != Id::TYPE_ID { + if d[0..4] != Id::TYPE_ID { return None } let mut cursor = &d[4..]; diff --git a/primitives/sandbox/src/embedded_executor.rs b/primitives/sandbox/src/embedded_executor.rs index 8a20cc1b39b54..4410e26c8d122 100644 --- a/primitives/sandbox/src/embedded_executor.rs +++ b/primitives/sandbox/src/embedded_executor.rs @@ -266,7 +266,7 @@ impl super::SandboxInstance for Instance { let mut externals = GuestExternals { state, defined_host_functions: &self.defined_host_functions }; - let result = self.instance.invoke_export(&name, &args, &mut externals); + let result = self.instance.invoke_export(name, &args, &mut externals); match result { Ok(None) => Ok(ReturnValue::Unit), diff --git a/primitives/state-machine/src/backend.rs b/primitives/state-machine/src/backend.rs index 8d0ac2d1369c9..e9d4ac0fb8224 100644 --- a/primitives/state-machine/src/backend.rs +++ b/primitives/state-machine/src/backend.rs @@ -197,7 +197,7 @@ pub trait Backend: sp_std::fmt::Debug { // child first for (child_info, child_delta) in child_deltas { let (child_root, empty, child_txs) = - self.child_storage_root(&child_info, child_delta, state_version); + self.child_storage_root(child_info, child_delta, state_version); let prefixed_storage_key = child_info.prefixed_storage_key(); txs.consolidate(child_txs); if empty { @@ -311,7 +311,7 @@ pub struct BackendRuntimeCode<'a, B, H> { impl<'a, B: Backend, H: Hasher> sp_core::traits::FetchRuntimeCode for BackendRuntimeCode<'a, B, H> { - fn fetch_runtime_code<'b>(&'b self) -> Option> { + fn fetch_runtime_code(&self) -> Option> { self.backend .storage(sp_core::storage::well_known_keys::CODE) .ok() diff --git a/primitives/state-machine/src/basic.rs b/primitives/state-machine/src/basic.rs index 1f257550fbf7a..f1c09251faa5e 100644 --- a/primitives/state-machine/src/basic.rs +++ b/primitives/state-machine/src/basic.rs @@ -288,7 +288,7 @@ impl Externalities for BasicExternalities { let empty_hash = empty_child_trie_root::>(); for (prefixed_storage_key, child_info) in prefixed_keys { let child_root = self.child_storage_root(&child_info, state_version); - if &empty_hash[..] == &child_root[..] { + if empty_hash[..] == child_root[..] { top.remove(prefixed_storage_key.as_slice()); } else { top.insert(prefixed_storage_key.into_inner(), child_root); diff --git a/primitives/state-machine/src/ext.rs b/primitives/state-machine/src/ext.rs index 7b7e4b47f19ba..e33569e2a1f67 100644 --- a/primitives/state-machine/src/ext.rs +++ b/primitives/state-machine/src/ext.rs @@ -316,8 +316,8 @@ where match (&next_backend_key, overlay_changes.peek()) { (_, None) => next_backend_key, (Some(_), Some(_)) => { - while let Some(overlay_key) = overlay_changes.next() { - let cmp = next_backend_key.as_deref().map(|v| v.cmp(&overlay_key.0)); + for overlay_key in overlay_changes { + let cmp = next_backend_key.as_deref().map(|v| v.cmp(overlay_key.0)); // If `backend_key` is less than the `overlay_key`, we found out next key. if cmp == Some(Ordering::Less) { @@ -332,7 +332,7 @@ where // this key. next_backend_key = self .backend - .next_storage_key(&overlay_key.0) + .next_storage_key(overlay_key.0) .expect(EXT_NOT_ALLOWED_TO_FAIL); } } @@ -357,8 +357,8 @@ where match (&next_backend_key, overlay_changes.peek()) { (_, None) => next_backend_key, (Some(_), Some(_)) => { - while let Some(overlay_key) = overlay_changes.next() { - let cmp = next_backend_key.as_deref().map(|v| v.cmp(&overlay_key.0)); + for overlay_key in overlay_changes { + let cmp = next_backend_key.as_deref().map(|v| v.cmp(overlay_key.0)); // If `backend_key` is less than the `overlay_key`, we found out next key. if cmp == Some(Ordering::Less) { @@ -373,7 +373,7 @@ where // this key. next_backend_key = self .backend - .next_child_storage_key(child_info, &overlay_key.0) + .next_child_storage_key(child_info, overlay_key.0) .expect(EXT_NOT_ALLOWED_TO_FAIL); } } @@ -546,7 +546,7 @@ where .storage(prefixed_storage_key.as_slice()) .and_then(|k| Decode::decode(&mut &k[..]).ok()) // V1 is equivalent to V0 on empty root. - .unwrap_or_else(|| empty_child_trie_root::>()); + .unwrap_or_else(empty_child_trie_root::>); trace!( target: "state", method = "ChildStorageRoot", @@ -593,7 +593,7 @@ where .storage(prefixed_storage_key.as_slice()) .and_then(|k| Decode::decode(&mut &k[..]).ok()) // V1 is equivalent to V0 on empty root. - .unwrap_or_else(|| empty_child_trie_root::>()); + .unwrap_or_else(empty_child_trie_root::>); trace!( target: "state", diff --git a/primitives/state-machine/src/in_memory_backend.rs b/primitives/state-machine/src/in_memory_backend.rs index 2d8173452abfe..457d89b8c59aa 100644 --- a/primitives/state-machine/src/in_memory_backend.rs +++ b/primitives/state-machine/src/in_memory_backend.rs @@ -94,7 +94,7 @@ where H::Out: Codec + Ord, { fn clone(&self) -> Self { - TrieBackend::new(self.backend_storage().clone(), self.root().clone()) + TrieBackend::new(self.backend_storage().clone(), *self.root()) } } diff --git a/primitives/state-machine/src/lib.rs b/primitives/state-machine/src/lib.rs index 1a69d51d58abf..97a9ae8d88cd2 100644 --- a/primitives/state-machine/src/lib.rs +++ b/primitives/state-machine/src/lib.rs @@ -644,7 +644,7 @@ mod execution { H::Out: Ord + 'static + codec::Codec, Spawn: SpawnNamed + Send + 'static, { - let trie_backend = create_proof_check_backend::(root.into(), proof)?; + let trie_backend = create_proof_check_backend::(root, proof)?; execution_proof_check_on_trie_backend::<_, _, _>( &trie_backend, overlay, @@ -791,7 +791,7 @@ mod execution { self.0.last().and_then(|s| s.key_values.last().map(|kv| kv.0.clone())); if let Some(child_last) = child_last { - if last.len() == 0 { + if last.is_empty() { if let Some(top_last) = top_last { last.push(top_last) } else { @@ -866,7 +866,7 @@ mod execution { .storage(&storage_key) .map_err(|e| Box::new(e) as Box)? { - child_roots.insert(state_root.clone()); + child_roots.insert(state_root); } else { return Err(Box::new("Invalid range start child trie key.")) } @@ -880,7 +880,7 @@ mod execution { let (child_info, depth) = if let Some(storage_key) = child_key.as_ref() { let storage_key = PrefixedStorageKey::new_ref(storage_key); ( - Some(match ChildType::from_prefixed_key(&storage_key) { + Some(match ChildType::from_prefixed_key(storage_key) { Some((ChildType::ParentKeyId, storage_key)) => ChildInfo::new_default(storage_key), None => return Err(Box::new("Invalid range start child trie key.")), @@ -900,15 +900,15 @@ mod execution { None, start_at_ref, |key, value| { - if first { - if start_at_ref + if first && + start_at_ref .as_ref() .map(|start| &key.as_slice() > start) .unwrap_or(true) - { - first = false; - } + { + first = false; } + if first { true } else if depth < MAX_NESTED_TRIE_DEPTH && @@ -938,12 +938,10 @@ mod execution { if switch_child_key.is_none() { if depth == 1 { break + } else if completed { + start_at = child_key.take(); } else { - if completed { - start_at = child_key.take(); - } else { - break - } + break } } else { child_key = switch_child_key; @@ -1269,7 +1267,7 @@ mod execution { let storage_key = PrefixedStorageKey::new_ref(storage_key); ( - Some(match ChildType::from_prefixed_key(&storage_key) { + Some(match ChildType::from_prefixed_key(storage_key) { Some((ChildType::ParentKeyId, storage_key)) => ChildInfo::new_default(storage_key), None => return Err(Box::new("Invalid range start child trie key.")), @@ -1294,15 +1292,15 @@ mod execution { None, start_at_ref, |key, value| { - if first { - if start_at_ref + if first && + start_at_ref .as_ref() .map(|start| &key.as_slice() > start) .unwrap_or(true) - { - first = false; - } + { + first = false; } + if !first { values.push((key.to_vec(), value.to_vec())); } @@ -1390,7 +1388,7 @@ mod tests { let using_native = use_native && self.native_available; match (using_native, self.native_succeeds, self.fallback_succeeds, native_call) { (true, true, _, Some(call)) => { - let res = sp_externalities::set_and_run_with_externalities(ext, || call()); + let res = sp_externalities::set_and_run_with_externalities(ext, call); (res.map(NativeOrEncoded::Native).map_err(|_| 0), true) }, (true, true, _, None) | (false, _, true, None) => ( @@ -1584,13 +1582,13 @@ mod tests { .map(|(k, v)| (k.clone(), v.value().cloned())) .collect::>(), map![ - b"abc".to_vec() => None.into(), - b"abb".to_vec() => None.into(), - b"aba".to_vec() => None.into(), - b"abd".to_vec() => None.into(), + b"abc".to_vec() => None, + b"abb".to_vec() => None, + b"aba".to_vec() => None, + b"abd".to_vec() => None, - b"bab".to_vec() => Some(b"228".to_vec()).into(), - b"bbd".to_vec() => Some(b"42".to_vec()).into() + b"bab".to_vec() => Some(b"228".to_vec()), + b"bbd".to_vec() => Some(b"42".to_vec()) ], ); @@ -1608,12 +1606,12 @@ mod tests { .map(|(k, v)| (k.clone(), v.value().cloned())) .collect::>(), map![ - b"abb".to_vec() => None.into(), - b"aba".to_vec() => None.into(), - b"abd".to_vec() => None.into(), + b"abb".to_vec() => None, + b"aba".to_vec() => None, + b"abd".to_vec() => None, - b"bab".to_vec() => Some(b"228".to_vec()).into(), - b"bbd".to_vec() => Some(b"42".to_vec()).into() + b"bab".to_vec() => Some(b"228".to_vec()), + b"bbd".to_vec() => Some(b"42".to_vec()) ], ); } @@ -1647,15 +1645,15 @@ mod tests { overlay .children() .flat_map(|(iter, _child_info)| iter) - .map(|(k, v)| (k.clone(), v.value().clone())) + .map(|(k, v)| (k.clone(), v.value())) .collect::>(), map![ - b"1".to_vec() => None.into(), - b"2".to_vec() => None.into(), - b"3".to_vec() => None.into(), - b"4".to_vec() => None.into(), - b"a".to_vec() => None.into(), - b"b".to_vec() => None.into(), + b"1".to_vec() => None, + b"2".to_vec() => None, + b"3".to_vec() => None, + b"4".to_vec() => None, + b"a".to_vec() => None, + b"b".to_vec() => None, ], ); } @@ -1796,7 +1794,7 @@ mod tests { fn test_compact(remote_proof: StorageProof, remote_root: &sp_core::H256) -> StorageProof { let compact_remote_proof = - remote_proof.into_compact_proof::(remote_root.clone()).unwrap(); + remote_proof.into_compact_proof::(*remote_root).unwrap(); compact_remote_proof .to_storage_proof::(Some(remote_root)) .unwrap() @@ -1823,8 +1821,7 @@ mod tests { read_proof_check::(remote_root, remote_proof.clone(), &[b"value2"]) .unwrap(); let local_result2 = - read_proof_check::(remote_root, remote_proof.clone(), &[&[0xff]]) - .is_ok(); + read_proof_check::(remote_root, remote_proof, &[&[0xff]]).is_ok(); // check that results are correct assert_eq!( local_result1.into_iter().collect::>(), @@ -1852,7 +1849,7 @@ mod tests { .unwrap(); let local_result3 = read_child_proof_check::( remote_root, - remote_proof.clone(), + remote_proof, missing_child_info, &[b"dummy"], ) @@ -1899,7 +1896,7 @@ mod tests { let trie: InMemoryBackend = (storage.clone(), StateVersion::default()).into(); - let trie_root = trie.root().clone(); + let trie_root = trie.root(); let backend = crate::ProvingBackend::new(&trie); let mut queries = Vec::new(); for c in 0..(5 + nb_child_trie / 2) { @@ -1948,7 +1945,7 @@ mod tests { let storage_proof = backend.extract_proof(); let remote_proof = test_compact(storage_proof, &trie_root); let proof_check = - create_proof_check_backend::(trie_root, remote_proof).unwrap(); + create_proof_check_backend::(*trie_root, remote_proof).unwrap(); for (child_info, key, expected) in queries { assert_eq!( @@ -1998,15 +1995,9 @@ mod tests { prove_range_read_with_size(remote_backend, None, None, 50000, Some(&[])).unwrap(); assert_eq!(proof.clone().into_memory_db::().drain().len(), 11); assert_eq!(count, 132); - let (results, completed) = read_range_proof_check::( - remote_root, - proof.clone(), - None, - None, - None, - None, - ) - .unwrap(); + let (results, completed) = + read_range_proof_check::(remote_root, proof, None, None, None, None) + .unwrap(); assert_eq!(results.len() as u32, count); assert_eq!(completed, true); } @@ -2041,11 +2032,11 @@ mod tests { remote_proof }; - let remote_proof = check_proof(mdb.clone(), root.clone(), state_version); + let remote_proof = check_proof(mdb.clone(), root, state_version); // check full values in proof assert!(remote_proof.encode().len() > 1_100); assert!(remote_proof.encoded_size() > 1_100); - let root1 = root.clone(); + let root1 = root; // do switch state_version = StateVersion::V1; @@ -2057,9 +2048,9 @@ mod tests { trie.insert(b"foo", vec![1u8; 1000].as_slice()) // inner hash .expect("insert failed"); } - let root3 = root.clone(); + let root3 = root; assert!(root1 != root3); - let remote_proof = check_proof(mdb.clone(), root.clone(), state_version); + let remote_proof = check_proof(mdb.clone(), root, state_version); // nodes foo is replaced by its hashed value form. assert!(remote_proof.encode().len() < 1000); assert!(remote_proof.encoded_size() < 1000); @@ -2159,7 +2150,7 @@ mod tests { let remote_proof = test_compact(remote_proof, &remote_root); let local_result1 = read_child_proof_check::( remote_root, - remote_proof.clone(), + remote_proof, &child_info1, &[b"key1"], ) diff --git a/primitives/state-machine/src/overlayed_changes/mod.rs b/primitives/state-machine/src/overlayed_changes/mod.rs index 59f3e1cffa5f6..2161b343711c9 100644 --- a/primitives/state-machine/src/overlayed_changes/mod.rs +++ b/primitives/state-machine/src/overlayed_changes/mod.rs @@ -513,7 +513,7 @@ impl OverlayedChanges { pub fn drain_storage_changes, H: Hasher>( &mut self, backend: &B, - mut cache: &mut StorageTransactionCache, + cache: &mut StorageTransactionCache, state_version: StateVersion, ) -> Result, DefaultError> where @@ -521,7 +521,7 @@ impl OverlayedChanges { { // If the transaction does not exist, we generate it. if cache.transaction.is_none() { - self.storage_root(backend, &mut cache, state_version); + self.storage_root(backend, cache, state_version); } let (transaction, transaction_storage_root) = cache diff --git a/primitives/state-machine/src/overlayed_changes/offchain.rs b/primitives/state-machine/src/overlayed_changes/offchain.rs index 98457700013aa..a9643c265e755 100644 --- a/primitives/state-machine/src/overlayed_changes/offchain.rs +++ b/primitives/state-machine/src/overlayed_changes/offchain.rs @@ -42,7 +42,7 @@ impl OffchainOverlayedChanges { } /// Iterate over all key value pairs by reference. - pub fn iter<'a>(&'a self) -> impl Iterator> { + pub fn iter(&self) -> impl Iterator { self.0.changes().map(|kv| (kv.0, kv.1.value_ref())) } diff --git a/primitives/state-machine/src/proving_backend.rs b/primitives/state-machine/src/proving_backend.rs index eeffcc8e47052..b47e6c693a3e8 100644 --- a/primitives/state-machine/src/proving_backend.rs +++ b/primitives/state-machine/src/proving_backend.rs @@ -77,7 +77,7 @@ where .storage(storage_key)? .and_then(|r| Decode::decode(&mut &r[..]).ok()) // V1 is equivalent to V0 on empty trie - .unwrap_or_else(|| empty_child_trie_root::>()); + .unwrap_or_else(empty_child_trie_root::>); let mut read_overlay = S::Overlay::default(); let eph = Ephemeral::new(self.backend.backend_storage(), &mut read_overlay); @@ -88,7 +88,7 @@ where read_child_trie_value_with::, _, _>( child_info.keyspace(), &eph, - &root.as_ref(), + root.as_ref(), key, &mut *self.proof_recorder, ) @@ -203,7 +203,7 @@ where proof_recorder: ProofRecorder, ) -> Self { let essence = backend.essence(); - let root = essence.root().clone(); + let root = *essence.root(); let recorder = ProofRecorderBackend { backend: essence.backend_storage(), proof_recorder }; ProvingBackend(TrieBackend::new(recorder, root)) } @@ -238,7 +238,7 @@ impl<'a, S: 'a + TrieBackendStorage, H: 'a + Hasher> TrieBackendStorage } let backend_value = self.backend.get(key, prefix)?; - self.proof_recorder.record(key.clone(), backend_value.clone()); + self.proof_recorder.record(*key, backend_value.clone()); Ok(backend_value) } } @@ -395,9 +395,9 @@ mod tests { use sp_runtime::traits::BlakeTwo256; use sp_trie::PrefixedMemoryDB; - fn test_proving<'a>( - trie_backend: &'a TrieBackend, BlakeTwo256>, - ) -> ProvingBackend<'a, PrefixedMemoryDB, BlakeTwo256> { + fn test_proving( + trie_backend: &TrieBackend, BlakeTwo256>, + ) -> ProvingBackend, BlakeTwo256> { ProvingBackend::new(trie_backend) } @@ -474,7 +474,6 @@ mod tests { let trie_root = trie.storage_root(std::iter::empty(), state_version).0; assert_eq!(in_memory_root, trie_root); value_range - .clone() .for_each(|i| assert_eq!(trie.storage(&[i]).unwrap().unwrap(), vec![i; size_content])); let proving = ProvingBackend::new(trie); @@ -482,8 +481,7 @@ mod tests { let proof = proving.extract_proof(); - let proof_check = - create_proof_check_backend::(in_memory_root.into(), proof).unwrap(); + let proof_check = create_proof_check_backend::(in_memory_root, proof).unwrap(); assert_eq!(proof_check.storage(&[42]).unwrap().unwrap(), vec![42; size_content]); } @@ -530,8 +528,7 @@ mod tests { let proof = proving.extract_proof(); - let proof_check = - create_proof_check_backend::(in_memory_root.into(), proof).unwrap(); + let proof_check = create_proof_check_backend::(in_memory_root, proof).unwrap(); assert!(proof_check.storage(&[0]).is_err()); assert_eq!(proof_check.storage(&[42]).unwrap().unwrap(), vec![42]); // note that it is include in root because proof close @@ -542,8 +539,7 @@ mod tests { assert_eq!(proving.child_storage(child_info_1, &[64]), Ok(Some(vec![64]))); let proof = proving.extract_proof(); - let proof_check = - create_proof_check_backend::(in_memory_root.into(), proof).unwrap(); + let proof_check = create_proof_check_backend::(in_memory_root, proof).unwrap(); assert_eq!(proof_check.child_storage(child_info_1, &[64]).unwrap().unwrap(), vec![64]); } diff --git a/primitives/state-machine/src/trie_backend_essence.rs b/primitives/state-machine/src/trie_backend_essence.rs index 418e6f3d2dce1..0bbea7004848a 100644 --- a/primitives/state-machine/src/trie_backend_essence.rs +++ b/primitives/state-machine/src/trie_backend_essence.rs @@ -521,7 +521,7 @@ where Ok(None) => default_root, Err(e) => { warn!(target: "trie", "Failed to read child storage root: {}", e); - default_root.clone() + default_root }, }; @@ -580,16 +580,12 @@ impl<'a, S: 'a + TrieBackendStorage, H: Hasher> hash_db::HashDB for Ephemeral<'a, S, H> { fn get(&self, key: &H::Out, prefix: Prefix) -> Option { - match HashDB::get(self.overlay, key, prefix) { - Some(val) => Some(val), - None => match self.storage.get(&key, prefix) { - Ok(x) => x, - Err(e) => { - warn!(target: "trie", "Failed to read from DB: {}", e); - None - }, - }, - } + HashDB::get(self.overlay, key, prefix).or_else(|| { + self.storage.get(key, prefix).unwrap_or_else(|e| { + warn!(target: "trie", "Failed to read from DB: {}", e); + None + }) + }) } fn contains(&self, key: &H::Out, prefix: Prefix) -> bool { @@ -664,7 +660,7 @@ impl, H: Hasher> HashDB for TrieBackendEsse if *key == self.empty { return Some([0u8].to_vec()) } - match self.storage.get(&key, prefix) { + match self.storage.get(key, prefix) { Ok(x) => x, Err(e) => { warn!(target: "trie", "Failed to read from DB: {}", e); diff --git a/primitives/storage/src/lib.rs b/primitives/storage/src/lib.rs index fecd2b24dbb00..0948cf431158d 100644 --- a/primitives/storage/src/lib.rs +++ b/primitives/storage/src/lib.rs @@ -189,21 +189,21 @@ pub mod well_known_keys { /// Wasm code of the runtime. /// /// Stored as a raw byte vector. Required by substrate. - pub const CODE: &'static [u8] = b":code"; + pub const CODE: &[u8] = b":code"; /// Number of wasm linear memory pages required for execution of the runtime. /// /// The type of this value is encoded `u64`. - pub const HEAP_PAGES: &'static [u8] = b":heappages"; + pub const HEAP_PAGES: &[u8] = b":heappages"; /// Current extrinsic index (u32) is stored under this key. - pub const EXTRINSIC_INDEX: &'static [u8] = b":extrinsic_index"; + pub const EXTRINSIC_INDEX: &[u8] = b":extrinsic_index"; /// Prefix of child storage keys. - pub const CHILD_STORAGE_KEY_PREFIX: &'static [u8] = b":child_storage:"; + pub const CHILD_STORAGE_KEY_PREFIX: &[u8] = b":child_storage:"; /// Prefix of the default child storage keys in the top trie. - pub const DEFAULT_CHILD_STORAGE_KEY_PREFIX: &'static [u8] = b":child_storage:default:"; + pub const DEFAULT_CHILD_STORAGE_KEY_PREFIX: &[u8] = b":child_storage:default:"; /// Whether a key is a default child storage key. /// @@ -359,7 +359,7 @@ impl ChildType { fn do_prefix_key(&self, key: &mut Vec) { let parent_prefix = self.parent_prefix(); let key_len = key.len(); - if parent_prefix.len() > 0 { + if !parent_prefix.is_empty() { key.resize(key_len + parent_prefix.len(), 0); key.copy_within(..key_len, parent_prefix.len()); key[..parent_prefix.len()].copy_from_slice(parent_prefix); diff --git a/primitives/tracing/src/types.rs b/primitives/tracing/src/types.rs index d175e1f8f17e8..e7d5abfb27d9a 100644 --- a/primitives/tracing/src/types.rs +++ b/primitives/tracing/src/types.rs @@ -639,7 +639,7 @@ mod std_features { tracing::span::Span::child_of( a.parent_id.map(tracing_core::span::Id::from_u64), - &metadata, + metadata, &tracing::valueset! { metadata.fields(), target, name, file, line, module_path, ?params }, ) } @@ -658,7 +658,7 @@ mod std_features { tracing_core::Event::child_of( self.parent_id.map(tracing_core::span::Id::from_u64), - &metadata, + metadata, &tracing::valueset! { metadata.fields(), target, name, file, line, module_path, ?params }, ) } diff --git a/primitives/transaction-storage-proof/src/lib.rs b/primitives/transaction-storage-proof/src/lib.rs index 2e5aa3b2b9c71..ee0c8e4ec8e29 100644 --- a/primitives/transaction-storage-proof/src/lib.rs +++ b/primitives/transaction-storage-proof/src/lib.rs @@ -154,7 +154,7 @@ pub mod registration { B: BlockT, C: IndexedBody, { - let parent_number = client.number(parent.clone())?.unwrap_or(Zero::zero()); + let parent_number = client.number(*parent)?.unwrap_or(Zero::zero()); let number = parent_number .saturating_add(One::one()) .saturating_sub(DEFAULT_STORAGE_PERIOD.into()); @@ -214,10 +214,10 @@ pub mod registration { trie.commit(); } if target_chunk.is_some() && target_root == Default::default() { - target_root = transaction_root.clone(); + target_root = transaction_root; chunk_proof = sp_trie::generate_trie_proof::( &db, - transaction_root.clone(), + transaction_root, &[target_chunk_key.clone()], ) .map_err(|e| Error::Application(Box::new(e)))?; diff --git a/primitives/trie/src/lib.rs b/primitives/trie/src/lib.rs index c4d4c7210bd46..358cb43ab4e92 100644 --- a/primitives/trie/src/lib.rs +++ b/primitives/trie/src/lib.rs @@ -555,7 +555,7 @@ mod tests { for (x, y) in input.iter().rev() { t.insert(x, y).unwrap(); } - t.root().clone() + *t.root() }; assert_eq!(closed_form, persistent); } @@ -571,7 +571,7 @@ mod tests { } } { - let t = TrieDB::::new(&mut memdb, &root).unwrap(); + let t = TrieDB::::new(&memdb, &root).unwrap(); assert_eq!( input.iter().map(|(i, j)| (i.to_vec(), j.to_vec())).collect::>(), t.iter() @@ -752,7 +752,7 @@ mod tests { memtrie.commit(); if *memtrie.root() != real { println!("TRIE MISMATCH"); - println!(""); + println!(); println!("{:?} vs {:?}", memtrie.root(), real); for i in &x { println!("{:#x?} -> {:#x?}", i.0, i.1); @@ -764,7 +764,7 @@ mod tests { let hashed_null_node = hashed_null_node::(); if *memtrie.root() != hashed_null_node { println!("- TRIE MISMATCH"); - println!(""); + println!(); println!("{:?} vs {:?}", memtrie.root(), hashed_null_node); for i in &x { println!("{:#x?} -> {:#x?}", i.0, i.1); diff --git a/primitives/trie/src/trie_codec.rs b/primitives/trie/src/trie_codec.rs index a7f292271565f..d29f5a98f31b9 100644 --- a/primitives/trie/src/trie_codec.rs +++ b/primitives/trie/src/trie_codec.rs @@ -71,7 +71,7 @@ where // Only check root if expected root is passed as argument. if let Some(expected_root) = expected_root { if expected_root != &top_root { - return Err(Error::RootMismatch(top_root.clone(), expected_root.clone())) + return Err(Error::RootMismatch(top_root, *expected_root)) } } diff --git a/primitives/trie/src/trie_stream.rs b/primitives/trie/src/trie_stream.rs index a17d7c25e1b8a..ca798db47b552 100644 --- a/primitives/trie/src/trie_stream.rs +++ b/primitives/trie/src/trie_stream.rs @@ -53,7 +53,7 @@ fn branch_node_bit_mask(has_children: impl Iterator) -> (u8, u8) { } /// Create a leaf/branch node, encoding a number of nibbles. -fn fuse_nibbles_node<'a>(nibbles: &'a [u8], kind: NodeKind) -> impl Iterator + 'a { +fn fuse_nibbles_node(nibbles: &[u8], kind: NodeKind) -> impl Iterator + '_ { let size = sp_std::cmp::min(trie_constants::NIBBLE_SIZE_BOUND, nibbles.len()); let iter_start = match kind { diff --git a/primitives/version/proc-macro/src/decl_runtime_version.rs b/primitives/version/proc-macro/src/decl_runtime_version.rs index 9ca1a67cc7fd6..ee81940b53993 100644 --- a/primitives/version/proc-macro/src/decl_runtime_version.rs +++ b/primitives/version/proc-macro/src/decl_runtime_version.rs @@ -105,7 +105,7 @@ impl ParseRuntimeVersion { parser: impl FnOnce(&Expr) -> Result, ) -> Result<()> { if value.is_some() { - return Err(Error::new(field.span(), "field is already initialized before")) + Err(Error::new(field.span(), "field is already initialized before")) } else { *value = Some(parser(&field.expr)?); Ok(()) diff --git a/primitives/wasm-interface/src/wasmi_impl.rs b/primitives/wasm-interface/src/wasmi_impl.rs index 2239eb5f38658..977b4fc606fa7 100644 --- a/primitives/wasm-interface/src/wasmi_impl.rs +++ b/primitives/wasm-interface/src/wasmi_impl.rs @@ -73,7 +73,7 @@ impl From for wasmi::Signature { impl From<&wasmi::Signature> for Signature { fn from(sig: &wasmi::Signature) -> Self { Signature::new( - sig.params().into_iter().copied().map(Into::into).collect::>(), + sig.params().iter().copied().map(Into::into).collect::>(), sig.return_type().map(Into::into), ) } diff --git a/test-utils/client/src/lib.rs b/test-utils/client/src/lib.rs index 94a350b5f5df3..cdadfb0f10f03 100644 --- a/test-utils/client/src/lib.rs +++ b/test-utils/client/src/lib.rs @@ -342,7 +342,7 @@ impl RpcHandlersExt for RpcHandlers { extrinsic: OpaqueExtrinsic, ) -> Pin> + Send>> { let (tx, rx) = futures::channel::mpsc::unbounded(); - let mem = RpcSession::new(tx.into()); + let mem = RpcSession::new(tx); Box::pin( self.rpc_query( &mem, diff --git a/test-utils/runtime/src/genesismap.rs b/test-utils/runtime/src/genesismap.rs index 71118b4183ef9..3ece5165e1757 100644 --- a/test-utils/runtime/src/genesismap.rs +++ b/test-utils/runtime/src/genesismap.rs @@ -67,7 +67,7 @@ impl GenesisConfig { (well_known_keys::CODE.into(), wasm_runtime), ( well_known_keys::HEAP_PAGES.into(), - vec![].and(&(self.heap_pages_override.unwrap_or(16 as u64))), + vec![].and(&(self.heap_pages_override.unwrap_or(16_u64))), ), ] .into_iter(), @@ -80,8 +80,7 @@ impl GenesisConfig { // Assimilate the system genesis config. let mut storage = Storage { top: map, children_default: self.extra_storage.children_default.clone() }; - let mut config = system::GenesisConfig::default(); - config.authorities = self.authorities.clone(); + let config = system::GenesisConfig { authorities: self.authorities.clone() }; config .assimilate_storage(&mut storage) .expect("Adding `system::GensisConfig` to the genesis"); diff --git a/test-utils/runtime/src/system.rs b/test-utils/runtime/src/system.rs index 6df35421d3614..77cd18c028364 100644 --- a/test-utils/runtime/src/system.rs +++ b/test-utils/runtime/src/system.rs @@ -77,7 +77,7 @@ pub fn initialize_block(header: &Header) { // try to read something that depends on current header digest // so that it'll be included in execution proof if let Some(generic::DigestItem::Other(v)) = header.digest().logs().iter().next() { - let _: Option = storage::unhashed::get(&v); + let _: Option = storage::unhashed::get(v); } } diff --git a/utils/fork-tree/src/lib.rs b/utils/fork-tree/src/lib.rs index c23a4f55f44a1..b4985b77294b2 100644 --- a/utils/fork-tree/src/lib.rs +++ b/utils/fork-tree/src/lib.rs @@ -374,7 +374,7 @@ where let node = self.roots.swap_remove(position); self.roots = node.children; self.best_finalized_number = Some(node.number); - return node.data + node.data } /// Finalize a node in the tree. This method will make sure that the node @@ -539,18 +539,17 @@ where // tree, if we find a valid node that passes the predicate then we must // ensure that we're not finalizing past any of its child nodes. for node in self.node_iter() { - if predicate(&node.data) { - if node.hash == *hash || is_descendent_of(&node.hash, hash)? { - for child in node.children.iter() { - if child.number <= number && - (child.hash == *hash || is_descendent_of(&child.hash, hash)?) - { - return Err(Error::UnfinalizedAncestor) - } + if predicate(&node.data) && (node.hash == *hash || is_descendent_of(&node.hash, hash)?) + { + for child in node.children.iter() { + if child.number <= number && + (child.hash == *hash || is_descendent_of(&child.hash, hash)?) + { + return Err(Error::UnfinalizedAncestor) } - - return Ok(Some(self.roots.iter().any(|root| root.hash == node.hash))) } + + return Ok(Some(self.roots.iter().any(|root| root.hash == node.hash))) } } @@ -587,19 +586,18 @@ where // we're not finalizing past any children node. let mut position = None; for (i, root) in self.roots.iter().enumerate() { - if predicate(&root.data) { - if root.hash == *hash || is_descendent_of(&root.hash, hash)? { - for child in root.children.iter() { - if child.number <= number && - (child.hash == *hash || is_descendent_of(&child.hash, hash)?) - { - return Err(Error::UnfinalizedAncestor) - } + if predicate(&root.data) && (root.hash == *hash || is_descendent_of(&root.hash, hash)?) + { + for child in root.children.iter() { + if child.number <= number && + (child.hash == *hash || is_descendent_of(&child.hash, hash)?) + { + return Err(Error::UnfinalizedAncestor) } - - position = Some(i); - break } + + position = Some(i); + break } } @@ -1071,16 +1069,13 @@ mod test { let finalize_a = || { let (mut tree, ..) = test_fork_tree(); - assert_eq!( - tree.roots().map(|(h, n, _)| (h.clone(), n.clone())).collect::>(), - vec![("A", 1)], - ); + assert_eq!(tree.roots().map(|(h, n, _)| (*h, *n)).collect::>(), vec![("A", 1)]); // finalizing "A" opens up three possible forks tree.finalize_root(&"A"); assert_eq!( - tree.roots().map(|(h, n, _)| (h.clone(), n.clone())).collect::>(), + tree.roots().map(|(h, n, _)| (*h, *n)).collect::>(), vec![("B", 2), ("F", 2), ("J", 2)], ); @@ -1093,10 +1088,7 @@ mod test { // finalizing "B" will progress on its fork and remove any other competing forks tree.finalize_root(&"B"); - assert_eq!( - tree.roots().map(|(h, n, _)| (h.clone(), n.clone())).collect::>(), - vec![("C", 3)], - ); + assert_eq!(tree.roots().map(|(h, n, _)| (*h, *n)).collect::>(), vec![("C", 3)],); // all the other forks have been pruned assert!(tree.roots.len() == 1); @@ -1108,10 +1100,7 @@ mod test { // finalizing "J" will progress on its fork and remove any other competing forks tree.finalize_root(&"J"); - assert_eq!( - tree.roots().map(|(h, n, _)| (h.clone(), n.clone())).collect::>(), - vec![("K", 3)], - ); + assert_eq!(tree.roots().map(|(h, n, _)| (*h, *n)).collect::>(), vec![("K", 3)],); // all the other forks have been pruned assert!(tree.roots.len() == 1); @@ -1136,7 +1125,7 @@ mod test { ); assert_eq!( - tree.roots().map(|(h, n, _)| (h.clone(), n.clone())).collect::>(), + tree.roots().map(|(h, n, _)| (*h, *n)).collect::>(), vec![("B", 2), ("F", 2), ("J", 2)], ); @@ -1160,7 +1149,7 @@ mod test { ); assert_eq!( - tree.roots().map(|(h, n, _)| (h.clone(), n.clone())).collect::>(), + tree.roots().map(|(h, n, _)| (*h, *n)).collect::>(), vec![("L", 4), ("I", 4)], ); @@ -1194,7 +1183,7 @@ mod test { ); assert_eq!( - tree.roots().map(|(h, n, _)| (h.clone(), n.clone())).collect::>(), + tree.roots().map(|(h, n, _)| (*h, *n)).collect::>(), vec![("B", 2), ("F", 2), ("J", 2)], ); @@ -1208,7 +1197,7 @@ mod test { ); assert_eq!( - tree.roots().map(|(h, n, _)| (h.clone(), n.clone())).collect::>(), + tree.roots().map(|(h, n, _)| (*h, *n)).collect::>(), vec![("L", 4), ("I", 4)], ); @@ -1224,10 +1213,7 @@ mod test { Ok(FinalizationResult::Changed(None)), ); - assert_eq!( - tree.roots().map(|(h, n, _)| (h.clone(), n.clone())).collect::>(), - vec![], - ); + assert_eq!(tree.roots().map(|(h, n, _)| (*h, *n)).collect::>(), vec![],); assert_eq!(tree.best_finalized_number, Some(6)); } @@ -1306,10 +1292,7 @@ mod test { Ok(FinalizationResult::Changed(None)), ); - assert_eq!( - tree.roots().map(|(h, n, _)| (h.clone(), n.clone())).collect::>(), - vec![("A0", 1)], - ); + assert_eq!(tree.roots().map(|(h, n, _)| (*h, *n)).collect::>(), vec![("A0", 1)],); // finalizing "C" will finalize the node "A0" and prune it out of the tree assert_eq!( @@ -1327,10 +1310,7 @@ mod test { Ok(FinalizationResult::Changed(Some(Change { effective: 5 }))), ); - assert_eq!( - tree.roots().map(|(h, n, _)| (h.clone(), n.clone())).collect::>(), - vec![("D", 10)], - ); + assert_eq!(tree.roots().map(|(h, n, _)| (*h, *n)).collect::>(), vec![("D", 10)],); // finalizing "F" will fail since it would finalize past "E" without finalizing "D" first assert_eq!( @@ -1359,7 +1339,7 @@ mod test { fn iter_iterates_in_preorder() { let (tree, ..) = test_fork_tree(); assert_eq!( - tree.iter().map(|(h, n, _)| (h.clone(), n.clone())).collect::>(), + tree.iter().map(|(h, n, _)| (*h, *n)).collect::>(), vec![ ("A", 1), ("B", 2), diff --git a/utils/frame/benchmarking-cli/src/overhead/template.rs b/utils/frame/benchmarking-cli/src/overhead/template.rs index d5f90d4873866..44e2c1f02e30f 100644 --- a/utils/frame/benchmarking-cli/src/overhead/template.rs +++ b/utils/frame/benchmarking-cli/src/overhead/template.rs @@ -31,7 +31,7 @@ use crate::{ shared::{Stats, UnderscoreHelper}, }; -static VERSION: &'static str = env!("CARGO_PKG_VERSION"); +static VERSION: &str = env!("CARGO_PKG_VERSION"); static TEMPLATE: &str = include_str!("./weights.hbs"); /// Data consumed by Handlebar to fill out the `weights.hbs` template. @@ -93,13 +93,13 @@ impl TemplateData { let mut fd = fs::File::create(&out_path)?; info!("Writing weights to {:?}", fs::canonicalize(&out_path)?); handlebars - .render_template_to_write(&TEMPLATE, &self, &mut fd) + .render_template_to_write(TEMPLATE, &self, &mut fd) .map_err(|e| format!("HBS template write: {:?}", e).into()) } /// Build a path for the weight file. fn build_path(&self, weight_out: &Option) -> Result { - let mut path = weight_out.clone().unwrap_or(PathBuf::from(".")); + let mut path = weight_out.clone().unwrap_or_else(|| PathBuf::from(".")); if !path.is_dir() { return Err("Need directory as --weight-path".into()) diff --git a/utils/frame/benchmarking-cli/src/pallet/command.rs b/utils/frame/benchmarking-cli/src/pallet/command.rs index 89b8d018bcb5c..660f31b8f1529 100644 --- a/utils/frame/benchmarking-cli/src/pallet/command.rs +++ b/utils/frame/benchmarking-cli/src/pallet/command.rs @@ -123,9 +123,9 @@ impl PalletCmd { let spec = config.chain_spec; let wasm_method = self.wasm_method.into(); let strategy = self.execution.unwrap_or(ExecutionStrategy::Native); - let pallet = self.pallet.clone().unwrap_or_else(|| String::new()); + let pallet = self.pallet.clone().unwrap_or_default(); let pallet = pallet.as_bytes(); - let extrinsic = self.extrinsic.clone().unwrap_or_else(|| String::new()); + let extrinsic = self.extrinsic.clone().unwrap_or_default(); let extrinsic_split: Vec<&str> = extrinsic.split(',').collect(); let extrinsics: Vec<_> = extrinsic_split.iter().map(|x| x.trim().as_bytes()).collect(); @@ -155,7 +155,7 @@ impl PalletCmd { extensions.register(OffchainWorkerExt::new(offchain.clone())); extensions.register(OffchainDbExt::new(offchain)); extensions.register(TransactionPoolExt::new(pool)); - return extensions + extensions }; // Get Benchmark List @@ -339,7 +339,7 @@ impl PalletCmd { batches.extend(batch); // Show progress information - if let Some(elapsed) = timer.elapsed().ok() { + if let Ok(elapsed) = timer.elapsed() { if elapsed >= time::Duration::from_secs(5) { timer = time::SystemTime::now(); log::info!( @@ -401,7 +401,7 @@ impl PalletCmd { batches: &Vec, storage_info: &Vec, ) { - for batch in batches.into_iter() { + for batch in batches.iter() { // Print benchmark metadata println!( "Pallet: {:?}, Extrinsic: {:?}, Lowest values: {:?}, Highest values: {:?}, Steps: {:?}, Repeat: {:?}", @@ -420,12 +420,12 @@ impl PalletCmd { if !self.no_storage_info { let mut comments: Vec = Default::default(); - writer::add_storage_comments(&mut comments, &batch.db_results, &storage_info); + writer::add_storage_comments(&mut comments, &batch.db_results, storage_info); println!("Raw Storage Info\n========"); for comment in comments { println!("{}", comment); } - println!(""); + println!(); } // Conduct analysis. @@ -446,7 +446,7 @@ impl PalletCmd { { println!("Writes = {:?}", analysis); } - println!(""); + println!(); } if !self.no_min_squares { println!("Min Squares Analysis\n========"); @@ -465,7 +465,7 @@ impl PalletCmd { { println!("Writes = {:?}", analysis); } - println!(""); + println!(); } } } diff --git a/utils/frame/benchmarking-cli/src/pallet/writer.rs b/utils/frame/benchmarking-cli/src/pallet/writer.rs index 81b20ad445cd0..515582d8c6db9 100644 --- a/utils/frame/benchmarking-cli/src/pallet/writer.rs +++ b/utils/frame/benchmarking-cli/src/pallet/writer.rs @@ -35,7 +35,7 @@ use frame_support::traits::StorageInfo; use sp_core::hexdisplay::HexDisplay; use sp_runtime::traits::Zero; -const VERSION: &'static str = env!("CARGO_PKG_VERSION"); +const VERSION: &str = env!("CARGO_PKG_VERSION"); const TEMPLATE: &str = include_str!("./template.hbs"); // This is the final structure we will pass to the Handlebars template. @@ -280,12 +280,12 @@ pub fn write_results( // Which analysis function should be used when outputting benchmarks let analysis_choice: AnalysisChoice = - cmd.output_analysis.clone().try_into().map_err(|e| io_error(e))?; + cmd.output_analysis.clone().try_into().map_err(io_error)?; // Capture individual args let cmd_data = CmdData { - steps: cmd.steps.clone(), - repeat: cmd.repeat.clone(), + steps: cmd.steps, + repeat: cmd.repeat, lowest_range_values: cmd.lowest_range_values.clone(), highest_range_values: cmd.highest_range_values.clone(), execution: format!("{:?}", cmd.execution), @@ -374,7 +374,7 @@ pub(crate) fn add_storage_comments( // This tracks the keys we already identified, so we only generate a single comment. let mut identified = HashSet::>::new(); - for result in results.clone() { + for result in results { for (key, reads, writes, whitelisted) in &result.keys { // skip keys which are whitelisted if *whitelisted { diff --git a/utils/frame/benchmarking-cli/src/shared/stats.rs b/utils/frame/benchmarking-cli/src/shared/stats.rs index 7785965fed4a7..3234d5f2f94f7 100644 --- a/utils/frame/benchmarking-cli/src/shared/stats.rs +++ b/utils/frame/benchmarking-cli/src/shared/stats.rs @@ -73,7 +73,7 @@ impl Stats { if xs.is_empty() { return Err("Empty input is invalid".into()) } - let (avg, stddev) = Self::avg_and_stddev(&xs); + let (avg, stddev) = Self::avg_and_stddev(xs); Ok(Self { sum: xs.iter().sum(), @@ -112,7 +112,7 @@ impl Stats { /// Returns the specified percentile for the given data. /// This is best effort since it ignores the interpolation case. fn percentile(mut xs: Vec, p: f64) -> u64 { - xs.sort(); + xs.sort_unstable(); let index = (xs.len() as f64 * p).ceil() as usize - 1; xs[index.clamp(0, xs.len() - 1)] } @@ -120,9 +120,9 @@ impl Stats { impl fmt::Debug for Stats { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Total: {}\n", self.sum)?; - write!(f, "Min: {}, Max: {}\n", self.min, self.max)?; - write!(f, "Average: {}, Median: {}, Stddev: {}\n", self.avg, self.median, self.stddev)?; + writeln!(f, "Total: {}", self.sum)?; + writeln!(f, "Min: {}, Max: {}", self.min, self.max)?; + writeln!(f, "Average: {}, Median: {}, Stddev: {}", self.avg, self.median, self.stddev)?; write!(f, "Percentiles 99th, 95th, 75th: {}, {}, {}", self.p99, self.p95, self.p75) } } diff --git a/utils/frame/benchmarking-cli/src/storage/template.rs b/utils/frame/benchmarking-cli/src/storage/template.rs index 26aa8a962301b..8365c3841d422 100644 --- a/utils/frame/benchmarking-cli/src/storage/template.rs +++ b/utils/frame/benchmarking-cli/src/storage/template.rs @@ -25,7 +25,7 @@ use std::{env, fs, path::PathBuf}; use super::cmd::StorageParams; use crate::shared::{Stats, UnderscoreHelper}; -static VERSION: &'static str = env!("CARGO_PKG_VERSION"); +static VERSION: &str = env!("CARGO_PKG_VERSION"); static TEMPLATE: &str = include_str!("./weights.hbs"); /// Data consumed by Handlebar to fill out the `weights.hbs` template. diff --git a/utils/frame/frame-utilities-cli/src/pallet_id.rs b/utils/frame/frame-utilities-cli/src/pallet_id.rs index c39bee8b87463..c0a221d4bcd11 100644 --- a/utils/frame/frame-utilities-cli/src/pallet_id.rs +++ b/utils/frame/frame-utilities-cli/src/pallet_id.rs @@ -64,7 +64,7 @@ impl PalletIdCmd { R::AccountId: Ss58Codec, { if self.id.len() != 8 { - Err("a module id must be a string of 8 characters")? + return Err("a module id must be a string of 8 characters".into()) } let password = self.keystore_params.read_password()?; @@ -80,7 +80,7 @@ impl PalletIdCmd { &account_id.to_ss58check_with_version(unwrap_or_default_ss58_version(self.network)), password, self.network, - self.output_scheme.output_type.clone() + self.output_scheme.output_type ) ); diff --git a/utils/frame/remote-externalities/src/lib.rs b/utils/frame/remote-externalities/src/lib.rs index a717a93f072bc..1d9dc5dcd11da 100644 --- a/utils/frame/remote-externalities/src/lib.rs +++ b/utils/frame/remote-externalities/src/lib.rs @@ -275,8 +275,8 @@ impl Default for Builder { impl Builder { fn as_online(&self) -> &OnlineConfig { match &self.mode { - Mode::Online(config) => &config, - Mode::OfflineOrElseOnline(_, config) => &config, + Mode::Online(config) => config, + Mode::OfflineOrElseOnline(_, config) => config, _ => panic!("Unexpected mode: Online"), } } @@ -380,7 +380,7 @@ impl Builder { log::error!( target: LOG_TARGET, "failed to execute batch: {:?}. Error: {:?}", - chunk_keys.iter().map(|k| HexDisplay::from(k)).collect::>(), + chunk_keys.iter().map(HexDisplay::from).collect::>(), e ); "batch failed." @@ -388,7 +388,7 @@ impl Builder { assert_eq!(chunk_keys.len(), values.len()); - for (idx, key) in chunk_keys.into_iter().enumerate() { + for (idx, key) in chunk_keys.iter().enumerate() { let maybe_value = values[idx].clone(); let value = maybe_value.unwrap_or_else(|| { log::warn!(target: LOG_TARGET, "key {:?} had none corresponding value.", &key); @@ -452,7 +452,7 @@ impl Builder { assert_eq!(batch_child_key.len(), batch_response.len()); - for (idx, key) in batch_child_key.into_iter().enumerate() { + for (idx, key) in batch_child_key.iter().enumerate() { let maybe_value = batch_response[idx].clone(); let value = maybe_value.unwrap_or_else(|| { log::warn!(target: LOG_TARGET, "key {:?} had none corresponding value.", &key); @@ -571,7 +571,7 @@ impl Builder { &self, top_kv: &[KeyValue], ) -> Result { - let child_kv = self.load_child_remote(&top_kv).await?; + let child_kv = self.load_child_remote(top_kv).await?; if let Some(c) = &self.as_online().state_snapshot { self.save_child_snapshot(&child_kv, &c.path)?; } @@ -612,7 +612,7 @@ impl Builder { }, }; - child_kv.push((ChildInfo::new_default(&un_prefixed), child_kv_inner)); + child_kv.push((ChildInfo::new_default(un_prefixed), child_kv_inner)); } Ok(child_kv) @@ -624,8 +624,7 @@ impl Builder { let at = self .as_online() .at - .expect("online config must be initialized by this point; qed.") - .clone(); + .expect("online config must be initialized by this point; qed."); log::info!(target: LOG_TARGET, "scraping key-pairs from remote @ {:?}", at); let mut keys_and_values = if config.pallets.len() > 0 { @@ -848,7 +847,7 @@ impl Builder { info!( target: LOG_TARGET, "injecting a total of {} child keys", - child_kv.iter().flat_map(|(_, kv)| kv).count(), + child_kv.iter().flat_map(|(_, kv)| kv).count() ); for (info, key_values) in child_kv { diff --git a/utils/frame/rpc/state-trie-migration-rpc/src/lib.rs b/utils/frame/rpc/state-trie-migration-rpc/src/lib.rs index 98a3cf964843c..2e3dd08a7db7e 100644 --- a/utils/frame/rpc/state-trie-migration-rpc/src/lib.rs +++ b/utils/frame/rpc/state-trie-migration-rpc/src/lib.rs @@ -73,7 +73,7 @@ where return Err("No access to trie from backend.".to_string()) }; let essence = trie_backend.essence(); - let (nb_to_migrate, trie) = count_migrate(essence, &essence.root())?; + let (nb_to_migrate, trie) = count_migrate(essence, essence.root())?; let mut nb_to_migrate_child = 0; let mut child_roots: Vec<(ChildInfo, Vec)> = Vec::new(); diff --git a/utils/frame/try-runtime/cli/src/commands/execute_block.rs b/utils/frame/try-runtime/cli/src/commands/execute_block.rs index b1a56f7e8f8eb..12c36955c26cd 100644 --- a/utils/frame/try-runtime/cli/src/commands/execute_block.rs +++ b/utils/frame/try-runtime/cli/src/commands/execute_block.rs @@ -76,12 +76,12 @@ impl ExecuteBlockCmd { ::Err: Debug, { match (&self.block_at, &self.state) { - (Some(block_at), State::Snap { .. }) => hash_of::(&block_at), + (Some(block_at), State::Snap { .. }) => hash_of::(block_at), (Some(block_at), State::Live { .. }) => { log::warn!(target: LOG_TARGET, "--block-at is provided while state type is live. the `Live::at` will be ignored"); - hash_of::(&block_at) + hash_of::(block_at) }, - (None, State::Live { at: Some(at), .. }) => hash_of::(&at), + (None, State::Live { at: Some(at), .. }) => hash_of::(at), _ => { panic!("either `--block-at` must be provided, or state must be `live with a proper `--at``"); }, diff --git a/utils/frame/try-runtime/cli/src/commands/follow_chain.rs b/utils/frame/try-runtime/cli/src/commands/follow_chain.rs index db305fa590760..e2e6bd7244945 100644 --- a/utils/frame/try-runtime/cli/src/commands/follow_chain.rs +++ b/utils/frame/try-runtime/cli/src/commands/follow_chain.rs @@ -31,8 +31,8 @@ use sp_core::H256; use sp_runtime::traits::{Block as BlockT, Header, NumberFor}; use std::{fmt::Debug, str::FromStr}; -const SUB: &'static str = "chain_subscribeFinalizedHeads"; -const UN_SUB: &'static str = "chain_unsubscribeFinalizedHeads"; +const SUB: &str = "chain_subscribeFinalizedHeads"; +const UN_SUB: &str = "chain_unsubscribeFinalizedHeads"; /// Configurations of the [`Command::FollowChain`]. #[derive(Debug, Clone, clap::Parser)] @@ -68,7 +68,7 @@ where log::info!(target: LOG_TARGET, "subscribing to {:?} / {:?}", SUB, UN_SUB); let mut subscription: Subscription = - client.subscribe(&SUB, None, &UN_SUB).await.unwrap(); + client.subscribe(SUB, None, UN_SUB).await.unwrap(); let (code_key, code) = extract_code(&config.chain_spec)?; let executor = build_executor::(&shared, &config); @@ -104,7 +104,7 @@ where if maybe_state_ext.is_none() { let builder = Builder::::new().mode(Mode::Online(OnlineConfig { transport: command.uri.clone().into(), - at: Some(header.parent_hash().clone()), + at: Some(*header.parent_hash()), ..Default::default() })); @@ -136,7 +136,7 @@ where maybe_state_ext.as_mut().expect("state_ext either existed or was just created"); let (mut changes, encoded_result) = state_machine_call_with_proof::( - &state_ext, + state_ext, &executor, execution, "TryRuntime_execute_block_no_check", diff --git a/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs b/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs index 72136e9236de5..9aad901829772 100644 --- a/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs +++ b/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs @@ -68,12 +68,12 @@ impl OffchainWorkerCmd { ::Err: Debug, { match (&self.header_at, &self.state) { - (Some(header_at), State::Snap { .. }) => hash_of::(&header_at), + (Some(header_at), State::Snap { .. }) => hash_of::(header_at), (Some(header_at), State::Live { .. }) => { log::error!(target: LOG_TARGET, "--header-at is provided while state type is live, this will most likely lead to a nonsensical result."); - hash_of::(&header_at) + hash_of::(header_at) }, - (None, State::Live { at: Some(at), .. }) => hash_of::(&at), + (None, State::Live { at: Some(at), .. }) => hash_of::(at), _ => { panic!("either `--header-at` must be provided, or state must be `live` with a proper `--at`"); }, diff --git a/utils/frame/try-runtime/cli/src/lib.rs b/utils/frame/try-runtime/cli/src/lib.rs index a9047259838cf..c13bbb3626176 100644 --- a/utils/frame/try-runtime/cli/src/lib.rs +++ b/utils/frame/try-runtime/cli/src/lib.rs @@ -296,7 +296,7 @@ use std::{fmt::Debug, path::PathBuf, str::FromStr}; mod commands; pub(crate) mod parse; -pub(crate) const LOG_TARGET: &'static str = "try-runtime::cli"; +pub(crate) const LOG_TARGET: &str = "try-runtime::cli"; /// Possible commands of `try-runtime`. #[derive(Debug, Clone, clap::Subcommand)] @@ -738,7 +738,7 @@ pub(crate) fn state_machine_call_with_proof( executor: &NativeElseWasmExecutor, ) -> (String, u32, sp_core::storage::StateVersion) { let (_, encoded) = state_machine_call::( - &ext, - &executor, + ext, + executor, sc_cli::ExecutionStrategy::NativeElseWasm, "Core_version", &[], diff --git a/utils/wasm-builder/src/wasm_project.rs b/utils/wasm-builder/src/wasm_project.rs index e94703b610a48..005b428a3ccac 100644 --- a/utils/wasm-builder/src/wasm_project.rs +++ b/utils/wasm-builder/src/wasm_project.rs @@ -359,10 +359,11 @@ fn project_enabled_features( // this heuristic anymore. However, for the transition phase between now and namespaced // features already being present in nightly, we need this code to make // runtimes compile with all the possible rustc versions. - if v.len() == 1 && v.get(0).map_or(false, |v| *v == format!("dep:{}", f)) { - if std_enabled.as_ref().map(|e| e.iter().any(|ef| ef == *f)).unwrap_or(false) { - return false - } + if v.len() == 1 && + v.get(0).map_or(false, |v| *v == format!("dep:{}", f)) && + std_enabled.as_ref().map(|e| e.iter().any(|ef| ef == *f)).unwrap_or(false) + { + return false } // We don't want to enable the `std`/`default` feature for the wasm build and @@ -409,7 +410,7 @@ fn create_project( fs::create_dir_all(wasm_project_folder.join("src")) .expect("Wasm project dir create can not fail; qed"); - let mut enabled_features = project_enabled_features(&project_cargo_toml, &crate_metadata); + let mut enabled_features = project_enabled_features(project_cargo_toml, crate_metadata); if has_runtime_wasm_feature_declared(project_cargo_toml, crate_metadata) { enabled_features.push("runtime-wasm".into()); @@ -422,7 +423,7 @@ fn create_project( &wasm_project_folder, workspace_root_path, &crate_name, - &crate_path, + crate_path, &wasm_binary, enabled_features.into_iter(), ); @@ -788,7 +789,7 @@ fn package_rerun_if_changed(package: &DeduplicatePackage) { .filter(|p| { p.is_dir() || p.extension().map(|e| e == "rs" || e == "toml").unwrap_or_default() }) - .for_each(|p| rerun_if_changed(p)); + .for_each(rerun_if_changed); } /// Copy the WASM binary to the target directory set in `WASM_TARGET_DIRECTORY` environment From 6bd6eb924e6b634de650a1eeecc4fe09a14e852b Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Sat, 30 Apr 2022 23:49:57 +0200 Subject: [PATCH 172/484] add missing features to node-template-runtime (#11326) --- bin/node-template/runtime/Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bin/node-template/runtime/Cargo.toml b/bin/node-template/runtime/Cargo.toml index f4ff877290822..43dc98f254651 100644 --- a/bin/node-template/runtime/Cargo.toml +++ b/bin/node-template/runtime/Cargo.toml @@ -91,6 +91,7 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", "hex-literal", "pallet-balances/runtime-benchmarks", + "pallet-grandpa/runtime-benchmarks", "pallet-template/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "sp-runtime/runtime-benchmarks", @@ -99,10 +100,12 @@ try-runtime = [ "frame-executive/try-runtime", "frame-try-runtime", "frame-system/try-runtime", + "pallet-aura/try-runtime", "pallet-balances/try-runtime", "pallet-grandpa/try-runtime", "pallet-randomness-collective-flip/try-runtime", "pallet-sudo/try-runtime", + "pallet-template/try-runtime", "pallet-timestamp/try-runtime", "pallet-transaction-payment/try-runtime", ] From e4332222b05848b206867ae74dd4f5d7cbdcf42c Mon Sep 17 00:00:00 2001 From: Ikko Ashimine Date: Mon, 2 May 2022 19:23:53 +0900 Subject: [PATCH 173/484] Uniques: fix typo in README.md (#11329) existance -> existence --- frame/uniques/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/uniques/README.md b/frame/uniques/README.md index 8d6859d55e23c..09f9d346895a6 100644 --- a/frame/uniques/README.md +++ b/frame/uniques/README.md @@ -20,7 +20,7 @@ The supported dispatchable functions are documented in the [`uniques::Call`](htt * **Asset transfer:** The action of transferring an asset instance from one account to another. * **Asset burning:** The destruction of an asset instance. * **Non-fungible asset:** An asset for which each unit has unique characteristics. There is exactly - one instance of such an asset in existance and there is exactly one owning account. + one instance of such an asset in existence and there is exactly one owning account. ### Goals From 5049be147a6497b21ca3ce772038b4845e192bb9 Mon Sep 17 00:00:00 2001 From: Roy Yang Date: Tue, 3 May 2022 10:17:14 +1200 Subject: [PATCH 174/484] Added RuntimeDebug derive to enums in frame_support::tokens::misc (#11211) Co-authored-by: Roy Yang --- frame/support/src/traits/tokens/misc.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frame/support/src/traits/tokens/misc.rs b/frame/support/src/traits/tokens/misc.rs index 86304b14a33f6..dd87fe36cd019 100644 --- a/frame/support/src/traits/tokens/misc.rs +++ b/frame/support/src/traits/tokens/misc.rs @@ -24,7 +24,7 @@ use sp_runtime::{ArithmeticError, DispatchError, TokenError}; use sp_std::fmt::Debug; /// One of a number of consequences of withdrawing a fungible from an account. -#[derive(Copy, Clone, Eq, PartialEq)] +#[derive(Copy, Clone, RuntimeDebug, Eq, PartialEq)] pub enum WithdrawConsequence { /// Withdraw could not happen since the amount to be withdrawn is less than the total funds in /// the account. @@ -69,7 +69,7 @@ impl WithdrawConsequence { } /// One of a number of consequences of withdrawing a fungible from an account. -#[derive(Copy, Clone, Eq, PartialEq)] +#[derive(Copy, Clone, RuntimeDebug, Eq, PartialEq)] pub enum DepositConsequence { /// Deposit couldn't happen due to the amount being too low. This is usually because the /// account doesn't yet exist and the deposit wouldn't bring it to at least the minimum needed @@ -104,7 +104,7 @@ impl DepositConsequence { } /// Simple boolean for whether an account needs to be kept in existence. -#[derive(Copy, Clone, Eq, PartialEq)] +#[derive(Copy, Clone, RuntimeDebug, Eq, PartialEq)] pub enum ExistenceRequirement { /// Operation must not result in the account going out of existence. /// From fc9143b5a0ee2fcd32f9a91f1ac9c71564f47b0c Mon Sep 17 00:00:00 2001 From: Qinxuan Chen Date: Tue, 3 May 2022 18:23:37 +0800 Subject: [PATCH 175/484] sc-network: update cid v0.6.0 => v0.8.4 (#11325) Signed-off-by: koushiro --- Cargo.lock | 155 +++++++++++++------------------------- client/network/Cargo.toml | 13 +--- 2 files changed, 58 insertions(+), 110 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7a5ef8e09ae7b..ac1c66fdff68a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -352,19 +352,6 @@ dependencies = [ "syn", ] -[[package]] -name = "asynchronous-codec" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb4401f0a3622dad2e0763fa79e0eb328bc70fb7dccfdd645341f00d671247d6" -dependencies = [ - "bytes 1.1.0", - "futures-sink", - "futures-util", - "memchr", - "pin-project-lite 0.2.6", -] - [[package]] name = "asynchronous-codec" version = "0.6.0" @@ -660,30 +647,39 @@ dependencies = [ "constant_time_eq", ] +[[package]] +name = "blake2b_simd" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72936ee4afc7f8f736d1c38383b56480b5497b4617b4a77bdbf1d2ababc76127" +dependencies = [ + "arrayref", + "arrayvec 0.7.1", + "constant_time_eq", +] + [[package]] name = "blake2s_simd" -version = "0.5.11" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e461a7034e85b211a4acb57ee2e6730b32912b06c08cc242243c39fc21ae6a2" +checksum = "db539cc2b5f6003621f1cd9ef92d7ded8ea5232c7de0f9faa2de251cd98730d4" dependencies = [ "arrayref", - "arrayvec 0.5.2", + "arrayvec 0.7.1", "constant_time_eq", ] [[package]] name = "blake3" -version = "0.3.7" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9ff35b701f3914bdb8fad3368d822c766ef2858b2583198e41639b936f09d3f" +checksum = "a08e53fc5a564bb15bfe6fae56bd71522205f1f91893f9c0116edad6496c183f" dependencies = [ "arrayref", - "arrayvec 0.5.2", + "arrayvec 0.7.1", "cc", - "cfg-if 0.1.10", + "cfg-if 1.0.0", "constant_time_eq", - "crypto-mac 0.8.0", - "digest 0.9.0", ] [[package]] @@ -975,13 +971,15 @@ dependencies = [ [[package]] name = "cid" -version = "0.6.1" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff0e3bc0b6446b3f9663c1a6aba6ef06c5aeaa1bc92bd18077be337198ab9768" +checksum = "a52cffa791ce5cf490ac3b2d6df970dc04f931b04e727be3c3e220e17164dfc4" dependencies = [ + "core2", "multibase", - "multihash 0.13.2", - "unsigned-varint 0.5.1", + "multihash", + "serde", + "unsigned-varint", ] [[package]] @@ -3771,7 +3769,7 @@ dependencies = [ "libsecp256k1", "log 0.4.16", "multiaddr", - "multihash 0.16.2", + "multihash", "multistream-select", "parking_lot 0.12.0", "pin-project 1.0.10", @@ -3783,7 +3781,7 @@ dependencies = [ "sha2 0.10.2", "smallvec 1.8.0", "thiserror", - "unsigned-varint 0.7.1", + "unsigned-varint", "void", "zeroize", ] @@ -3837,7 +3835,7 @@ version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a90c989a7c0969c2ab63e898da9bc735e3be53fb4f376e9c045ce516bcc9f928" dependencies = [ - "asynchronous-codec 0.6.0", + "asynchronous-codec", "base64 0.13.0", "byteorder", "bytes 1.1.0", @@ -3855,7 +3853,7 @@ dependencies = [ "regex", "sha2 0.10.2", "smallvec 1.8.0", - "unsigned-varint 0.7.1", + "unsigned-varint", "wasm-timer", ] @@ -3883,7 +3881,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "564e6bd64d177446399ed835b9451a8825b07929d6daa6a94e6405592974725e" dependencies = [ "arrayvec 0.5.2", - "asynchronous-codec 0.6.0", + "asynchronous-codec", "bytes 1.1.0", "either", "fnv", @@ -3900,7 +3898,7 @@ dependencies = [ "smallvec 1.8.0", "thiserror", "uint", - "unsigned-varint 0.7.1", + "unsigned-varint", "void", ] @@ -3947,7 +3945,7 @@ version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "442eb0c9fff0bf22a34f015724b4143ce01877e079ed0963c722d94c07c72160" dependencies = [ - "asynchronous-codec 0.6.0", + "asynchronous-codec", "bytes 1.1.0", "futures 0.3.21", "libp2p-core", @@ -3956,7 +3954,7 @@ dependencies = [ "parking_lot 0.12.0", "rand 0.7.3", "smallvec 1.8.0", - "unsigned-varint 0.7.1", + "unsigned-varint", ] [[package]] @@ -4003,14 +4001,14 @@ version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "962c0fb0e7212fb96a69b87f2d09bcefd317935239bdc79cda900e7a8897a3fe" dependencies = [ - "asynchronous-codec 0.6.0", + "asynchronous-codec", "bytes 1.1.0", "futures 0.3.21", "libp2p-core", "log 0.4.16", "prost", "prost-build", - "unsigned-varint 0.7.1", + "unsigned-varint", "void", ] @@ -4034,7 +4032,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3aa754cb7bccef51ebc3c458c6bbcef89d83b578a9925438389be841527d408f" dependencies = [ - "asynchronous-codec 0.6.0", + "asynchronous-codec", "bytes 1.1.0", "either", "futures 0.3.21", @@ -4050,7 +4048,7 @@ dependencies = [ "smallvec 1.8.0", "static_assertions", "thiserror", - "unsigned-varint 0.7.1", + "unsigned-varint", "void", ] @@ -4060,7 +4058,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd0baab894c5b84da510b915d53264d566c3c35889f09931fe9edbd2a773bee" dependencies = [ - "asynchronous-codec 0.6.0", + "asynchronous-codec", "bimap", "futures 0.3.21", "futures-timer", @@ -4073,7 +4071,7 @@ dependencies = [ "rand 0.8.4", "sha2 0.10.2", "thiserror", - "unsigned-varint 0.7.1", + "unsigned-varint", "void", ] @@ -4092,7 +4090,7 @@ dependencies = [ "log 0.4.16", "rand 0.7.3", "smallvec 1.8.0", - "unsigned-varint 0.7.1", + "unsigned-varint", ] [[package]] @@ -4649,67 +4647,40 @@ dependencies = [ "bs58", "byteorder", "data-encoding", - "multihash 0.16.2", + "multihash", "percent-encoding 2.1.0", "serde", "static_assertions", - "unsigned-varint 0.7.1", + "unsigned-varint", "url 2.2.1", ] [[package]] name = "multibase" -version = "0.8.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b78c60039650ff12e140ae867ef5299a58e19dded4d334c849dc7177083667e2" +checksum = "9b3539ec3c1f04ac9748a260728e855f261b4977f5c3406612c884564f329404" dependencies = [ "base-x", "data-encoding", "data-encoding-macro", ] -[[package]] -name = "multihash" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dac63698b887d2d929306ea48b63760431ff8a24fac40ddb22f9c7f49fb7cab" -dependencies = [ - "blake2b_simd", - "blake2s_simd", - "blake3", - "digest 0.9.0", - "generic-array 0.14.4", - "multihash-derive 0.7.2", - "sha2 0.9.8", - "sha3 0.9.1", - "unsigned-varint 0.5.1", -] - [[package]] name = "multihash" version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3db354f401db558759dfc1e568d010a5d4146f4d3f637be1275ec4a3cf09689" dependencies = [ + "blake2b_simd 1.0.0", + "blake2s_simd", + "blake3", "core2", "digest 0.10.3", - "multihash-derive 0.8.0", + "multihash-derive", "sha2 0.10.2", - "unsigned-varint 0.7.1", -] - -[[package]] -name = "multihash-derive" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "424f6e86263cd5294cbd7f1e95746b95aca0e0d66bff31e5a40d6baa87b4aa99" -dependencies = [ - "proc-macro-crate 1.1.3", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", - "synstructure", + "sha3 0.10.0", + "unsigned-varint", ] [[package]] @@ -4743,7 +4714,7 @@ dependencies = [ "log 0.4.16", "pin-project 1.0.10", "smallvec 1.8.0", - "unsigned-varint 0.7.1", + "unsigned-varint", ] [[package]] @@ -8141,7 +8112,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb" dependencies = [ "base64 0.13.0", - "blake2b_simd", + "blake2b_simd 0.5.11", "constant_time_eq", "crossbeam-utils 0.8.5", ] @@ -8974,7 +8945,7 @@ dependencies = [ "assert_matches", "async-std", "async-trait", - "asynchronous-codec 0.5.0", + "asynchronous-codec", "bitflags", "bytes 1.1.0", "cid", @@ -9018,7 +8989,7 @@ dependencies = [ "substrate-test-runtime-client", "tempfile", "thiserror", - "unsigned-varint 0.6.0", + "unsigned-varint", "void", "zeroize", ] @@ -11933,31 +11904,13 @@ dependencies = [ "subtle", ] -[[package]] -name = "unsigned-varint" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fdeedbf205afadfe39ae559b75c3240f24e257d0ca27e85f85cb82aa19ac35" - -[[package]] -name = "unsigned-varint" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35581ff83d4101e58b582e607120c7f5ffb17e632a980b1f38334d76b36908b2" -dependencies = [ - "asynchronous-codec 0.5.0", - "bytes 1.1.0", - "futures-io", - "futures-util", -] - [[package]] name = "unsigned-varint" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86a8dc7f45e4c1b0d30e43038c38f274e77af056aa5f74b93c2cf9eb3c1c836" dependencies = [ - "asynchronous-codec 0.6.0", + "asynchronous-codec", "bytes 1.1.0", "futures-io", "futures-util", diff --git a/client/network/Cargo.toml b/client/network/Cargo.toml index 93b389d4361c1..f3983173575ce 100644 --- a/client/network/Cargo.toml +++ b/client/network/Cargo.toml @@ -19,17 +19,15 @@ prost-build = "0.9" [dependencies] async-trait = "0.1" bitflags = "1.3.2" -cid = "0.6.0" +cid = "0.8.4" bytes = "1" -codec = { package = "parity-scale-codec", version = "3.0.0", features = [ - "derive", -] } +codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } either = "1.5.3" fnv = "1.0.6" fork-tree = { version = "3.0.0", path = "../../utils/fork-tree" } futures = "0.3.21" futures-timer = "3.0.2" -asynchronous-codec = "0.5" +asynchronous-codec = "0.6" hex = "0.4.0" ip_network = "0.4.1" linked-hash-map = "0.5.4" @@ -56,10 +54,7 @@ sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } sc-utils = { version = "4.0.0-dev", path = "../utils" } sp-finality-grandpa = { version = "4.0.0-dev", path = "../../primitives/finality-grandpa" } thiserror = "1.0" -unsigned-varint = { version = "0.6.0", features = [ - "futures", - "asynchronous_codec", -] } +unsigned-varint = { version = "0.7.1", features = ["futures", "asynchronous_codec"] } void = "1.0.2" zeroize = "1.4.3" libp2p = "0.44.0" From 9024525dd8d041fcb142bccbf0931b5d0e7afcb8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 May 2022 13:05:45 +0200 Subject: [PATCH 176/484] Bump parity-db from 0.3.9 to 0.3.12 (#11340) Bumps [parity-db](https://github.com/paritytech/parity-db) from 0.3.9 to 0.3.12. - [Release notes](https://github.com/paritytech/parity-db/releases) - [Commits](https://github.com/paritytech/parity-db/commits/v0.3.12) --- updated-dependencies: - dependency-name: parity-db dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- client/db/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ac1c66fdff68a..77d9463cbd9b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6832,9 +6832,9 @@ dependencies = [ [[package]] name = "parity-db" -version = "0.3.9" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d121a9af17a43efd0a38c6afa508b927ba07785bd4709efb2ac03bf77efef8d" +checksum = "6e73cd0b0a78045276b19eaae8eaaa20e44a1da9a0217ff934a810d9492ae701" dependencies = [ "blake2-rfc", "crc32fast", diff --git a/client/db/Cargo.toml b/client/db/Cargo.toml index 071e7aa78ddac..9b6faaafbdb62 100644 --- a/client/db/Cargo.toml +++ b/client/db/Cargo.toml @@ -33,7 +33,7 @@ sc-state-db = { version = "0.10.0-dev", path = "../state-db" } sp-trie = { version = "6.0.0", path = "../../primitives/trie" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } sp-database = { version = "4.0.0-dev", path = "../../primitives/database" } -parity-db = { version = "0.3.9", optional = true } +parity-db = { version = "0.3.12", optional = true } [dev-dependencies] sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } From a84b2bec5ab480443e2e50c5baa061832ec59285 Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Tue, 3 May 2022 16:55:26 +0300 Subject: [PATCH 177/484] Network sync refactoring (part 2) (#11322) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Move `api.v1.proto` schema into new crate `sc-network-sync` * Move `sc_network::protocol::sync::state` module into `sc_network_sync::state` * Move `sc_network::protocol::sync::blocks` module into `sc_network_sync::blocks` and some data structures from `sc_network::protocol::message` module into `sc_network_sync::message` * Move some data structures from `sc_network::config` and `sc_network::request_responses` into new `sc-network-common` crate * Move `sc_network::protocol::sync::warm` and `sc_network::warp_request_handler` modules into `sc_network_sync` * Move `client/network/sync/src/lib.rs` to `client/network/sync/src/lib_old.rs` to preserve history of changes of the file in the next commit * Move `client/network/src/protocol/sync.rs` on top of `client/network/sync/src/lib.rs` to preserve history of changes * Move `sc_network::protocol::sync` to `sc_network_sync` with submodules, move message data structures around accordingly * Move `sc_network::block_request_handler` to `sc_network_sync::block_request_handler` * Move `sc_network::state_request_handler` to `sc_network_sync::state_request_handler` * Add re-exports for compatibility reasons * Apply suggestions from code review Co-authored-by: Bastian Köcher Co-authored-by: Bastian Köcher --- Cargo.lock | 49 ++++ Cargo.toml | 2 + client/network/Cargo.toml | 3 +- client/network/build.rs | 3 +- client/network/common/Cargo.toml | 26 ++ client/network/common/src/config.rs | 44 ++++ client/network/common/src/lib.rs | 23 ++ client/network/common/src/message.rs | 23 ++ .../network/common/src/request_responses.rs | 114 +++++++++ client/network/src/behaviour.rs | 12 +- client/network/src/config.rs | 41 +--- client/network/src/discovery.rs | 5 +- client/network/src/lib.rs | 15 +- client/network/src/light_client_requests.rs | 2 +- .../src/light_client_requests/handler.rs | 12 +- client/network/src/protocol.rs | 139 +++++------ client/network/src/protocol/message.rs | 222 ++---------------- client/network/src/request_responses.rs | 91 +------ client/network/src/schema.rs | 1 - client/network/src/service.rs | 8 +- client/network/src/service/tests.rs | 7 +- client/network/src/transactions.rs | 3 +- client/network/sync/Cargo.toml | 49 ++++ client/network/sync/build.rs | 5 + .../{ => sync}/src/block_request_handler.rs | 12 +- .../{src/protocol/sync => sync/src}/blocks.rs | 5 +- .../sync => sync/src}/extra_requests.rs | 16 +- .../{src/protocol/sync.rs => sync/src/lib.rs} | 56 ++--- client/network/sync/src/message.rs | 222 ++++++++++++++++++ client/network/sync/src/schema.rs | 23 ++ .../{ => sync}/src/schema/api.v1.proto | 0 .../{src/protocol/sync => sync/src}/state.rs | 4 +- .../{ => sync}/src/state_request_handler.rs | 14 +- .../{src/protocol/sync => sync/src}/warp.rs | 23 +- .../{ => sync}/src/warp_request_handler.rs | 7 +- client/network/test/Cargo.toml | 1 + client/network/test/src/lib.rs | 3 +- client/service/Cargo.toml | 1 + client/service/src/config.rs | 15 +- 39 files changed, 806 insertions(+), 495 deletions(-) create mode 100644 client/network/common/Cargo.toml create mode 100644 client/network/common/src/config.rs create mode 100644 client/network/common/src/lib.rs create mode 100644 client/network/common/src/message.rs create mode 100644 client/network/common/src/request_responses.rs create mode 100644 client/network/sync/Cargo.toml create mode 100644 client/network/sync/build.rs rename client/network/{ => sync}/src/block_request_handler.rs (99%) rename client/network/{src/protocol/sync => sync/src}/blocks.rs (99%) rename client/network/{src/protocol/sync => sync/src}/extra_requests.rs (98%) rename client/network/{src/protocol/sync.rs => sync/src/lib.rs} (98%) create mode 100644 client/network/sync/src/message.rs create mode 100644 client/network/sync/src/schema.rs rename client/network/{ => sync}/src/schema/api.v1.proto (100%) rename client/network/{src/protocol/sync => sync/src}/state.rs (99%) rename client/network/{ => sync}/src/state_request_handler.rs (97%) rename client/network/{src/protocol/sync => sync/src}/warp.rs (90%) rename client/network/{ => sync}/src/warp_request_handler.rs (97%) diff --git a/Cargo.lock b/Cargo.lock index 77d9463cbd9b8..00d45f10288a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8971,6 +8971,8 @@ dependencies = [ "sc-block-builder", "sc-client-api", "sc-consensus", + "sc-network-common", + "sc-network-sync", "sc-peerset", "sc-utils", "serde", @@ -8994,6 +8996,18 @@ dependencies = [ "zeroize", ] +[[package]] +name = "sc-network-common" +version = "0.10.0-dev" +dependencies = [ + "futures 0.3.21", + "libp2p", + "parity-scale-codec", + "prost-build", + "sc-peerset", + "smallvec 1.8.0", +] + [[package]] name = "sc-network-gossip" version = "0.10.0-dev" @@ -9013,6 +9027,39 @@ dependencies = [ "tracing", ] +[[package]] +name = "sc-network-sync" +version = "0.10.0-dev" +dependencies = [ + "bitflags", + "either", + "fork-tree", + "futures 0.3.21", + "libp2p", + "log 0.4.16", + "lru", + "parity-scale-codec", + "prost", + "prost-build", + "quickcheck", + "sc-block-builder", + "sc-client-api", + "sc-consensus", + "sc-network-common", + "sc-peerset", + "smallvec 1.8.0", + "sp-arithmetic", + "sp-blockchain", + "sp-consensus", + "sp-core", + "sp-finality-grandpa", + "sp-runtime", + "sp-test-primitives", + "sp-tracing", + "substrate-test-runtime-client", + "thiserror", +] + [[package]] name = "sc-network-test" version = "0.8.0" @@ -9029,6 +9076,7 @@ dependencies = [ "sc-client-api", "sc-consensus", "sc-network", + "sc-network-common", "sc-service", "sp-blockchain", "sp-consensus", @@ -9217,6 +9265,7 @@ dependencies = [ "sc-informant", "sc-keystore", "sc-network", + "sc-network-common", "sc-offchain", "sc-rpc", "sc-rpc-server", diff --git a/Cargo.toml b/Cargo.toml index f91e6226ccfd0..39ccceeb3a030 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,7 +42,9 @@ members = [ "client/informant", "client/keystore", "client/network", + "client/network/common", "client/network-gossip", + "client/network/sync", "client/network/test", "client/offchain", "client/peerset", diff --git a/client/network/Cargo.toml b/client/network/Cargo.toml index f3983173575ce..a8a1fc05bb365 100644 --- a/client/network/Cargo.toml +++ b/client/network/Cargo.toml @@ -51,6 +51,8 @@ sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/comm sc-consensus = { version = "0.10.0-dev", path = "../consensus/common" } sp-core = { version = "6.0.0", path = "../../primitives/core" } sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } +sc-network-common = { version = "0.10.0-dev", path = "./common" } +sc-network-sync = { version = "0.10.0-dev", path = "./sync" } sc-utils = { version = "4.0.0-dev", path = "../utils" } sp-finality-grandpa = { version = "4.0.0-dev", path = "../../primitives/finality-grandpa" } thiserror = "1.0" @@ -61,7 +63,6 @@ libp2p = "0.44.0" [dev-dependencies] assert_matches = "1.3" -libp2p = { version = "0.44.0", default-features = false } quickcheck = "1.0.3" rand = "0.7.2" sp-test-primitives = { version = "2.0.0", path = "../../primitives/test-primitives" } diff --git a/client/network/build.rs b/client/network/build.rs index 6e5b83d4e58ae..f551f61dab3d4 100644 --- a/client/network/build.rs +++ b/client/network/build.rs @@ -1,5 +1,4 @@ -const PROTOS: &[&str] = - &["src/schema/api.v1.proto", "src/schema/light.v1.proto", "src/schema/bitswap.v1.2.0.proto"]; +const PROTOS: &[&str] = &["src/schema/light.v1.proto", "src/schema/bitswap.v1.2.0.proto"]; fn main() { prost_build::compile_protos(PROTOS, &["src/schema"]).unwrap(); diff --git a/client/network/common/Cargo.toml b/client/network/common/Cargo.toml new file mode 100644 index 0000000000000..5e3150ee9bc82 --- /dev/null +++ b/client/network/common/Cargo.toml @@ -0,0 +1,26 @@ +[package] +description = "Substrate network common" +name = "sc-network-common" +version = "0.10.0-dev" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +authors = ["Parity Technologies "] +edition = "2021" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +documentation = "https://docs.rs/sc-network-sync" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[build-dependencies] +prost-build = "0.9" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", features = [ + "derive", +] } +futures = "0.3.21" +libp2p = "0.44.0" +sc-peerset = { version = "4.0.0-dev", path = "../../peerset" } +smallvec = "1.8.0" diff --git a/client/network/common/src/config.rs b/client/network/common/src/config.rs new file mode 100644 index 0000000000000..92f8df5cd380f --- /dev/null +++ b/client/network/common/src/config.rs @@ -0,0 +1,44 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Configuration of the networking layer. + +use std::{fmt, str}; + +/// Name of a protocol, transmitted on the wire. Should be unique for each chain. Always UTF-8. +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct ProtocolId(smallvec::SmallVec<[u8; 6]>); + +impl<'a> From<&'a str> for ProtocolId { + fn from(bytes: &'a str) -> ProtocolId { + Self(bytes.as_bytes().into()) + } +} + +impl AsRef for ProtocolId { + fn as_ref(&self) -> &str { + str::from_utf8(&self.0[..]) + .expect("the only way to build a ProtocolId is through a UTF-8 String; qed") + } +} + +impl fmt::Debug for ProtocolId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(self.as_ref(), f) + } +} diff --git a/client/network/common/src/lib.rs b/client/network/common/src/lib.rs new file mode 100644 index 0000000000000..81769e23debbb --- /dev/null +++ b/client/network/common/src/lib.rs @@ -0,0 +1,23 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Common data structures of the networking layer. + +pub mod config; +pub mod message; +pub mod request_responses; diff --git a/client/network/common/src/message.rs b/client/network/common/src/message.rs new file mode 100644 index 0000000000000..930fe5ca52847 --- /dev/null +++ b/client/network/common/src/message.rs @@ -0,0 +1,23 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Network packet message types. These get serialized and put into the lower level protocol +//! payload. + +/// A unique ID of a request. +pub type RequestId = u64; diff --git a/client/network/common/src/request_responses.rs b/client/network/common/src/request_responses.rs new file mode 100644 index 0000000000000..71570e6beb864 --- /dev/null +++ b/client/network/common/src/request_responses.rs @@ -0,0 +1,114 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Collection of generic data structures for request-response protocols. + +use futures::channel::{mpsc, oneshot}; +use libp2p::PeerId; +use sc_peerset::ReputationChange; +use std::{borrow::Cow, time::Duration}; + +/// Configuration for a single request-response protocol. +#[derive(Debug, Clone)] +pub struct ProtocolConfig { + /// Name of the protocol on the wire. Should be something like `/foo/bar`. + pub name: Cow<'static, str>, + + /// Maximum allowed size, in bytes, of a request. + /// + /// Any request larger than this value will be declined as a way to avoid allocating too + /// much memory for it. + pub max_request_size: u64, + + /// Maximum allowed size, in bytes, of a response. + /// + /// Any response larger than this value will be declined as a way to avoid allocating too + /// much memory for it. + pub max_response_size: u64, + + /// Duration after which emitted requests are considered timed out. + /// + /// If you expect the response to come back quickly, you should set this to a smaller duration. + pub request_timeout: Duration, + + /// Channel on which the networking service will send incoming requests. + /// + /// Every time a peer sends a request to the local node using this protocol, the networking + /// service will push an element on this channel. The receiving side of this channel then has + /// to pull this element, process the request, and send back the response to send back to the + /// peer. + /// + /// The size of the channel has to be carefully chosen. If the channel is full, the networking + /// service will discard the incoming request send back an error to the peer. Consequently, + /// the channel being full is an indicator that the node is overloaded. + /// + /// You can typically set the size of the channel to `T / d`, where `T` is the + /// `request_timeout` and `d` is the expected average duration of CPU and I/O it takes to + /// build a response. + /// + /// Can be `None` if the local node does not support answering incoming requests. + /// If this is `None`, then the local node will not advertise support for this protocol towards + /// other peers. If this is `Some` but the channel is closed, then the local node will + /// advertise support for this protocol, but any incoming request will lead to an error being + /// sent back. + pub inbound_queue: Option>, +} + +/// A single request received by a peer on a request-response protocol. +#[derive(Debug)] +pub struct IncomingRequest { + /// Who sent the request. + pub peer: PeerId, + + /// Request sent by the remote. Will always be smaller than + /// [`ProtocolConfig::max_request_size`]. + pub payload: Vec, + + /// Channel to send back the response. + /// + /// There are two ways to indicate that handling the request failed: + /// + /// 1. Drop `pending_response` and thus not changing the reputation of the peer. + /// + /// 2. Sending an `Err(())` via `pending_response`, optionally including reputation changes for + /// the given peer. + pub pending_response: oneshot::Sender, +} + +/// Response for an incoming request to be send by a request protocol handler. +#[derive(Debug)] +pub struct OutgoingResponse { + /// The payload of the response. + /// + /// `Err(())` if none is available e.g. due an error while handling the request. + pub result: Result, ()>, + + /// Reputation changes accrued while handling the request. To be applied to the reputation of + /// the peer sending the request. + pub reputation_changes: Vec, + + /// If provided, the `oneshot::Sender` will be notified when the request has been sent to the + /// peer. + /// + /// > **Note**: Operating systems typically maintain a buffer of a few dozen kilobytes of + /// > outgoing data for each TCP socket, and it is not possible for a user + /// > application to inspect this buffer. This channel here is not actually notified + /// > when the response has been fully sent out, but rather when it has fully been + /// > written to the buffer managed by the operating system. + pub sent_feedback: Option>, +} diff --git a/client/network/src/behaviour.rs b/client/network/src/behaviour.rs index b0bf3d6a1c135..091dd116e4c9c 100644 --- a/client/network/src/behaviour.rs +++ b/client/network/src/behaviour.rs @@ -18,7 +18,6 @@ use crate::{ bitswap::Bitswap, - config::ProtocolId, discovery::{DiscoveryBehaviour, DiscoveryConfig, DiscoveryOut}, peer_info, protocol::{message::Roles, CustomMessageOutcome, NotificationsSink, Protocol}, @@ -42,6 +41,7 @@ use log::debug; use prost::Message; use sc_client_api::{BlockBackend, ProofProvider}; use sc_consensus::import_queue::{IncomingBlock, Origin}; +use sc_network_common::{config::ProtocolId, request_responses::ProtocolConfig}; use sc_peerset::PeersetHandle; use sp_blockchain::{HeaderBackend, HeaderMetadata}; use sp_consensus::BlockOrigin; @@ -220,13 +220,13 @@ where user_agent: String, local_public_key: PublicKey, disco_config: DiscoveryConfig, - block_request_protocol_config: request_responses::ProtocolConfig, - state_request_protocol_config: request_responses::ProtocolConfig, - warp_sync_protocol_config: Option, + block_request_protocol_config: ProtocolConfig, + state_request_protocol_config: ProtocolConfig, + warp_sync_protocol_config: Option, bitswap: Option>, - light_client_request_protocol_config: request_responses::ProtocolConfig, + light_client_request_protocol_config: ProtocolConfig, // All remaining request protocol configs. - mut request_response_protocols: Vec, + mut request_response_protocols: Vec, peerset: PeersetHandle, ) -> Result { // Extract protocol name and add to `request_response_protocols`. diff --git a/client/network/src/config.rs b/client/network/src/config.rs index 2b448ed14eab0..cfb06331b55a1 100644 --- a/client/network/src/config.rs +++ b/client/network/src/config.rs @@ -21,12 +21,14 @@ //! The [`Params`] struct is the struct that must be passed in order to initialize the networking. //! See the documentation of [`Params`]. -pub use crate::{ +pub use sc_network_common::{ + config::ProtocolId, request_responses::{ IncomingRequest, OutgoingResponse, ProtocolConfig as RequestResponseConfig, }, - warp_request_handler::WarpSyncProvider, }; +pub use sc_network_sync::warp_request_handler::WarpSyncProvider; + pub use libp2p::{build_multiaddr, core::PublicKey, identity}; // Note: this re-export shouldn't be part of the public API of the crate and will be removed in @@ -111,10 +113,10 @@ where /// protocol name. In addition all of [`RequestResponseConfig`] is used to handle incoming /// block requests, if enabled. /// - /// Can be constructed either via [`crate::block_request_handler::generate_protocol_config`] - /// allowing outgoing but not incoming requests, or constructed via - /// [`crate::block_request_handler::BlockRequestHandler::new`] allowing both outgoing and - /// incoming requests. + /// Can be constructed either via + /// [`sc_network_sync::block_request_handler::generate_protocol_config`] allowing outgoing but + /// not incoming requests, or constructed via [`sc_network_sync::block_request_handler:: + /// BlockRequestHandler::new`] allowing both outgoing and incoming requests. pub block_request_protocol_config: RequestResponseConfig, /// Request response configuration for the light client request protocol. @@ -129,8 +131,8 @@ where /// Request response configuration for the state request protocol. /// /// Can be constructed either via - /// [`crate::block_request_handler::generate_protocol_config`] allowing outgoing but not - /// incoming requests, or constructed via + /// [`sc_network_sync::block_request_handler::generate_protocol_config`] allowing outgoing but + /// not incoming requests, or constructed via /// [`crate::state_request_handler::StateRequestHandler::new`] allowing /// both outgoing and incoming requests. pub state_request_protocol_config: RequestResponseConfig, @@ -232,29 +234,6 @@ impl TransactionPool for EmptyTransaction } } -/// Name of a protocol, transmitted on the wire. Should be unique for each chain. Always UTF-8. -#[derive(Clone, PartialEq, Eq, Hash)] -pub struct ProtocolId(smallvec::SmallVec<[u8; 6]>); - -impl<'a> From<&'a str> for ProtocolId { - fn from(bytes: &'a str) -> ProtocolId { - Self(bytes.as_bytes().into()) - } -} - -impl AsRef for ProtocolId { - fn as_ref(&self) -> &str { - str::from_utf8(&self.0[..]) - .expect("the only way to build a ProtocolId is through a UTF-8 String; qed") - } -} - -impl fmt::Debug for ProtocolId { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(self.as_ref(), f) - } -} - /// Parses a string address and splits it into Multiaddress and PeerId, if /// valid. /// diff --git a/client/network/src/discovery.rs b/client/network/src/discovery.rs index ae65d4f23cec7..2bae2fb807f7e 100644 --- a/client/network/src/discovery.rs +++ b/client/network/src/discovery.rs @@ -46,7 +46,7 @@ //! active mechanism that asks nodes for the addresses they are listening on. Whenever we learn //! of a node's address, you must call `add_self_reported_address`. -use crate::{config::ProtocolId, utils::LruHashSet}; +use crate::utils::LruHashSet; use futures::prelude::*; use futures_timer::Delay; use ip_network::IpNetwork; @@ -72,6 +72,7 @@ use libp2p::{ }, }; use log::{debug, error, info, trace, warn}; +use sc_network_common::config::ProtocolId; use sp_core::hexdisplay::HexDisplay; use std::{ cmp, @@ -1001,7 +1002,6 @@ impl MdnsWrapper { #[cfg(test)] mod tests { use super::{protocol_name_from_protocol_id, DiscoveryConfig, DiscoveryOut}; - use crate::config::ProtocolId; use futures::prelude::*; use libp2p::{ core::{ @@ -1013,6 +1013,7 @@ mod tests { swarm::{Swarm, SwarmEvent}, yamux, Multiaddr, PeerId, }; + use sc_network_common::config::ProtocolId; use std::{collections::HashSet, task::Poll}; #[test] diff --git a/client/network/src/lib.rs b/client/network/src/lib.rs index 973e0b15b7509..3957aab22cca9 100644 --- a/client/network/src/lib.rs +++ b/client/network/src/lib.rs @@ -255,22 +255,25 @@ mod transport; mod utils; pub mod bitswap; -pub mod block_request_handler; pub mod config; pub mod error; pub mod light_client_requests; pub mod network_state; -pub mod state_request_handler; pub mod transactions; -pub mod warp_request_handler; #[doc(inline)] pub use libp2p::{multiaddr, Multiaddr, PeerId}; pub use protocol::{ event::{DhtEvent, Event, ObservedRole}, - sync::{StateDownloadProgress, SyncState, WarpSyncPhase, WarpSyncProgress}, PeerInfo, }; +pub use sc_network_sync::{ + block_request_handler, + state::StateDownloadProgress, + state_request_handler, + warp::{WarpSyncPhase, WarpSyncProgress}, + warp_request_handler, SyncState, +}; pub use service::{ DecodingError, IfDisconnected, KademliaKey, Keypair, NetworkService, NetworkWorker, NotificationSender, NotificationSenderReady, OutboundFailure, PublicKey, RequestFailure, @@ -325,7 +328,7 @@ pub struct NetworkStatus { /// The total number of bytes sent. pub total_bytes_outbound: u64, /// State sync in progress. - pub state_sync: Option, + pub state_sync: Option, /// Warp sync in progress. - pub warp_sync: Option>, + pub warp_sync: Option>, } diff --git a/client/network/src/light_client_requests.rs b/client/network/src/light_client_requests.rs index d36158b2a373a..9eccef41e833d 100644 --- a/client/network/src/light_client_requests.rs +++ b/client/network/src/light_client_requests.rs @@ -21,7 +21,7 @@ /// For incoming light client requests. pub mod handler; -use crate::{config::ProtocolId, request_responses::ProtocolConfig}; +use sc_network_common::{config::ProtocolId, request_responses::ProtocolConfig}; use std::time::Duration; diff --git a/client/network/src/light_client_requests/handler.rs b/client/network/src/light_client_requests/handler.rs index cb9bd960767ff..bf65cba5f82e5 100644 --- a/client/network/src/light_client_requests/handler.rs +++ b/client/network/src/light_client_requests/handler.rs @@ -22,16 +22,16 @@ //! `crate::request_responses::RequestResponsesBehaviour` with //! [`LightClientRequestHandler`](handler::LightClientRequestHandler). -use crate::{ - config::ProtocolId, - request_responses::{IncomingRequest, OutgoingResponse, ProtocolConfig}, - schema, PeerId, -}; +use crate::{schema, PeerId}; use codec::{self, Decode, Encode}; use futures::{channel::mpsc, prelude::*}; use log::{debug, trace}; use prost::Message; use sc_client_api::{ProofProvider, StorageProof}; +use sc_network_common::{ + config::ProtocolId, + request_responses::{IncomingRequest, OutgoingResponse, ProtocolConfig}, +}; use sc_peerset::ReputationChange; use sp_core::{ hexdisplay::HexDisplay, @@ -55,7 +55,7 @@ where B: Block, Client: ProofProvider + Send + Sync + 'static, { - /// Create a new [`crate::block_request_handler::BlockRequestHandler`]. + /// Create a new [`sc_network_sync::block_request_handler::BlockRequestHandler`]. pub fn new(protocol_id: &ProtocolId, client: Arc) -> (Self, ProtocolConfig) { // For now due to lack of data on light client request handling in production systems, this // value is chosen to match the block request limit. diff --git a/client/network/src/protocol.rs b/client/network/src/protocol.rs index 7214e60172aaa..5db8f102d037b 100644 --- a/client/network/src/protocol.rs +++ b/client/network/src/protocol.rs @@ -17,12 +17,10 @@ // along with this program. If not, see . use crate::{ - config::{self, ProtocolId, WarpSyncProvider}, - error, + config, error, request_responses::RequestFailure, - schema::v1::StateResponse, utils::{interval, LruHashSet}, - warp_request_handler::EncodedProof, + warp_request_handler::{EncodedProof, WarpSyncProvider}, }; use bytes::Bytes; @@ -43,13 +41,23 @@ use libp2p::{ use log::{debug, error, info, log, trace, warn, Level}; use message::{ generic::{Message as GenericMessage, Roles}, - BlockAnnounce, Message, + Message, }; use notifications::{Notifications, NotificationsOut}; use prometheus_endpoint::{register, Gauge, GaugeVec, Opts, PrometheusError, Registry, U64}; use prost::Message as _; use sc_client_api::{BlockBackend, HeaderBackend, ProofProvider}; use sc_consensus::import_queue::{BlockImportError, BlockImportStatus, IncomingBlock, Origin}; +use sc_network_common::config::ProtocolId; +use sc_network_sync::{ + message::{ + BlockAnnounce, BlockAttributes, BlockData, BlockRequest, BlockResponse, BlockState, + FromBlock, + }, + schema::v1::StateResponse, + BadPeer, ChainSync, OnBlockData, OnBlockJustification, OnStateData, + PollBlockAnnounceValidation, Status as SyncStatus, +}; use sp_arithmetic::traits::SaturatedConversion; use sp_consensus::{block_validation::BlockAnnounceValidator, BlockOrigin}; use sp_runtime::{ @@ -67,13 +75,11 @@ use std::{ task::Poll, time, }; -use sync::{ChainSync, Status as SyncStatus}; mod notifications; pub mod event; pub mod message; -pub mod sync; pub use notifications::{NotificationsSink, NotifsHandlerError, Ready}; use sp_blockchain::HeaderMetadata; @@ -202,7 +208,7 @@ pub struct Protocol { #[derive(Debug)] enum PeerRequest { - Block(message::BlockRequest), + Block(BlockRequest), State, WarpProof, } @@ -240,15 +246,15 @@ pub struct ProtocolConfig { } impl ProtocolConfig { - fn sync_mode(&self) -> sync::SyncMode { + fn sync_mode(&self) -> sc_network_sync::SyncMode { if self.roles.is_light() { - sync::SyncMode::Light + sc_network_sync::SyncMode::Light } else { match self.sync_mode { - config::SyncMode::Full => sync::SyncMode::Full, + config::SyncMode::Full => sc_network_sync::SyncMode::Full, config::SyncMode::Fast { skip_proofs, storage_chain_mode } => - sync::SyncMode::LightState { skip_proofs, storage_chain_mode }, - config::SyncMode::Warp => sync::SyncMode::Warp, + sc_network_sync::SyncMode::LightState { skip_proofs, storage_chain_mode }, + config::SyncMode::Warp => sc_network_sync::SyncMode::Warp, } } } @@ -563,7 +569,7 @@ where fn prepare_block_request( &mut self, who: PeerId, - request: message::BlockRequest, + request: BlockRequest, ) -> CustomMessageOutcome { prepare_block_request::(&mut self.peers, who, request) } @@ -579,9 +585,7 @@ where } if let Some(_peer_data) = self.peers.remove(&peer) { - if let Some(sync::OnBlockData::Import(origin, blocks)) = - self.sync.peer_disconnected(&peer) - { + if let Some(OnBlockData::Import(origin, blocks)) = self.sync.peer_disconnected(&peer) { self.pending_messages .push_back(CustomMessageOutcome::BlockImport(origin, blocks)); } @@ -601,21 +605,21 @@ where pub fn on_block_response( &mut self, peer_id: PeerId, - request: message::BlockRequest, - response: crate::schema::v1::BlockResponse, + request: BlockRequest, + response: sc_network_sync::schema::v1::BlockResponse, ) -> CustomMessageOutcome { let blocks = response .blocks .into_iter() .map(|block_data| { - Ok(message::BlockData:: { + Ok(BlockData:: { hash: Decode::decode(&mut block_data.hash.as_ref())?, header: if !block_data.header.is_empty() { Some(Decode::decode(&mut block_data.header.as_ref())?) } else { None }, - body: if request.fields.contains(message::BlockAttributes::BODY) { + body: if request.fields.contains(BlockAttributes::BODY) { Some( block_data .body @@ -626,8 +630,7 @@ where } else { None }, - indexed_body: if request.fields.contains(message::BlockAttributes::INDEXED_BODY) - { + indexed_body: if request.fields.contains(BlockAttributes::INDEXED_BODY) { Some(block_data.indexed_body) } else { None @@ -667,7 +670,7 @@ where }, }; - let block_response = message::BlockResponse:: { id: request.id, blocks }; + let block_response = BlockResponse:: { id: request.id, blocks }; let blocks_range = || match ( block_response @@ -687,12 +690,12 @@ where blocks_range(), ); - if request.fields == message::BlockAttributes::JUSTIFICATION { + if request.fields == BlockAttributes::JUSTIFICATION { match self.sync.on_block_justification(peer_id, block_response) { - Ok(sync::OnBlockJustification::Nothing) => CustomMessageOutcome::None, - Ok(sync::OnBlockJustification::Import { peer, hash, number, justifications }) => + Ok(OnBlockJustification::Nothing) => CustomMessageOutcome::None, + Ok(OnBlockJustification::Import { peer, hash, number, justifications }) => CustomMessageOutcome::JustificationImport(peer, hash, number, justifications), - Err(sync::BadPeer(id, repu)) => { + Err(BadPeer(id, repu)) => { self.behaviour.disconnect_peer(&id, HARDCODED_PEERSETS_SYNC); self.peerset_handle.report_peer(id, repu); CustomMessageOutcome::None @@ -700,10 +703,10 @@ where } } else { match self.sync.on_block_data(&peer_id, Some(request), block_response) { - Ok(sync::OnBlockData::Import(origin, blocks)) => + Ok(OnBlockData::Import(origin, blocks)) => CustomMessageOutcome::BlockImport(origin, blocks), - Ok(sync::OnBlockData::Request(peer, req)) => self.prepare_block_request(peer, req), - Err(sync::BadPeer(id, repu)) => { + Ok(OnBlockData::Request(peer, req)) => self.prepare_block_request(peer, req), + Err(BadPeer(id, repu)) => { self.behaviour.disconnect_peer(&id, HARDCODED_PEERSETS_SYNC); self.peerset_handle.report_peer(id, repu); CustomMessageOutcome::None @@ -720,10 +723,10 @@ where response: StateResponse, ) -> CustomMessageOutcome { match self.sync.on_state_data(&peer_id, response) { - Ok(sync::OnStateData::Import(origin, block)) => + Ok(OnStateData::Import(origin, block)) => CustomMessageOutcome::BlockImport(origin, vec![block]), - Ok(sync::OnStateData::Continue) => CustomMessageOutcome::None, - Err(sync::BadPeer(id, repu)) => { + Ok(OnStateData::Continue) => CustomMessageOutcome::None, + Err(BadPeer(id, repu)) => { self.behaviour.disconnect_peer(&id, HARDCODED_PEERSETS_SYNC); self.peerset_handle.report_peer(id, repu); CustomMessageOutcome::None @@ -740,7 +743,7 @@ where ) -> CustomMessageOutcome { match self.sync.on_warp_sync_data(&peer_id, response) { Ok(()) => CustomMessageOutcome::None, - Err(sync::BadPeer(id, repu)) => { + Err(BadPeer(id, repu)) => { self.behaviour.disconnect_peer(&id, HARDCODED_PEERSETS_SYNC); self.peerset_handle.report_peer(id, repu); CustomMessageOutcome::None @@ -849,7 +852,7 @@ where let req = if peer.info.roles.is_full() { match self.sync.new_peer(who, peer.info.best_hash, peer.info.best_number) { Ok(req) => req, - Err(sync::BadPeer(id, repu)) => { + Err(BadPeer(id, repu)) => { self.behaviour.disconnect_peer(&id, HARDCODED_PEERSETS_SYNC); self.peerset_handle.report_peer(id, repu); return Err(()) @@ -906,13 +909,9 @@ where let inserted = peer.known_blocks.insert(hash); if inserted { trace!(target: "sync", "Announcing block {:?} to {}", hash, who); - let message = message::BlockAnnounce { + let message = BlockAnnounce { header: header.clone(), - state: if is_best { - Some(message::BlockState::Best) - } else { - Some(message::BlockState::Normal) - }, + state: if is_best { Some(BlockState::Best) } else { Some(BlockState::Normal) }, data: Some(data.clone()), }; @@ -949,9 +948,9 @@ where peer.known_blocks.insert(hash); - let is_best = match announce.state.unwrap_or(message::BlockState::Best) { - message::BlockState::Best => true, - message::BlockState::Normal => false, + let is_best = match announce.state.unwrap_or(BlockState::Best) { + BlockState::Best => true, + BlockState::Normal => false, }; if peer.info.roles.is_full() { @@ -962,11 +961,11 @@ where /// Process the result of the block announce validation. fn process_block_announce_validation_result( &mut self, - validation_result: sync::PollBlockAnnounceValidation, + validation_result: PollBlockAnnounceValidation, ) -> CustomMessageOutcome { let (header, is_best, who) = match validation_result { - sync::PollBlockAnnounceValidation::Skip => return CustomMessageOutcome::None, - sync::PollBlockAnnounceValidation::Nothing { is_best, who, announce } => { + PollBlockAnnounceValidation::Skip => return CustomMessageOutcome::None, + PollBlockAnnounceValidation::Nothing { is_best, who, announce } => { self.update_peer_info(&who); if let Some(data) = announce.data { @@ -987,7 +986,7 @@ where return CustomMessageOutcome::None } }, - sync::PollBlockAnnounceValidation::ImportHeader { announce, is_best, who } => { + PollBlockAnnounceValidation::ImportHeader { announce, is_best, who } => { self.update_peer_info(&who); if let Some(data) = announce.data { @@ -998,7 +997,7 @@ where (announce.header, is_best, who) }, - sync::PollBlockAnnounceValidation::Failure { who, disconnect } => { + PollBlockAnnounceValidation::Failure { who, disconnect } => { if disconnect { self.behaviour.disconnect_peer(&who, HARDCODED_PEERSETS_SYNC); } @@ -1015,9 +1014,9 @@ where let blocks_to_import = self.sync.on_block_data( &who, None, - message::generic::BlockResponse { + BlockResponse:: { id: 0, - blocks: vec![message::generic::BlockData { + blocks: vec![BlockData:: { hash: header.hash(), header: Some(header), body: None, @@ -1035,10 +1034,10 @@ where } match blocks_to_import { - Ok(sync::OnBlockData::Import(origin, blocks)) => + Ok(OnBlockData::Import(origin, blocks)) => CustomMessageOutcome::BlockImport(origin, blocks), - Ok(sync::OnBlockData::Request(peer, req)) => self.prepare_block_request(peer, req), - Err(sync::BadPeer(id, repu)) => { + Ok(OnBlockData::Request(peer, req)) => self.prepare_block_request(peer, req), + Err(BadPeer(id, repu)) => { self.behaviour.disconnect_peer(&id, HARDCODED_PEERSETS_SYNC); self.peerset_handle.report_peer(id, repu); CustomMessageOutcome::None @@ -1096,7 +1095,7 @@ where req, )); }, - Err(sync::BadPeer(id, repu)) => { + Err(BadPeer(id, repu)) => { self.behaviour.disconnect_peer(&id, HARDCODED_PEERSETS_SYNC); self.peerset_handle.report_peer(id, repu) }, @@ -1263,7 +1262,7 @@ where fn prepare_block_request( peers: &mut HashMap>, who: PeerId, - request: message::BlockRequest, + request: BlockRequest, ) -> CustomMessageOutcome { let (tx, rx) = oneshot::channel(); @@ -1271,13 +1270,13 @@ fn prepare_block_request( peer.request = Some((PeerRequest::Block(request.clone()), rx)); } - let request = crate::schema::v1::BlockRequest { + let request = sc_network_sync::schema::v1::BlockRequest { fields: request.fields.to_be_u32(), from_block: match request.from { - message::FromBlock::Hash(h) => - Some(crate::schema::v1::block_request::FromBlock::Hash(h.encode())), - message::FromBlock::Number(n) => - Some(crate::schema::v1::block_request::FromBlock::Number(n.encode())), + FromBlock::Hash(h) => + Some(sc_network_sync::schema::v1::block_request::FromBlock::Hash(h.encode())), + FromBlock::Number(n) => + Some(sc_network_sync::schema::v1::block_request::FromBlock::Number(n.encode())), }, to_block: request.to.map(|h| h.encode()).unwrap_or_default(), direction: request.direction as i32, @@ -1291,7 +1290,7 @@ fn prepare_block_request( fn prepare_state_request( peers: &mut HashMap>, who: PeerId, - request: crate::schema::v1::StateRequest, + request: sc_network_sync::schema::v1::StateRequest, ) -> CustomMessageOutcome { let (tx, rx) = oneshot::channel(); @@ -1348,13 +1347,13 @@ pub enum CustomMessageOutcome { /// A new block request must be emitted. BlockRequest { target: PeerId, - request: crate::schema::v1::BlockRequest, + request: sc_network_sync::schema::v1::BlockRequest, pending_response: oneshot::Sender, RequestFailure>>, }, /// A new storage request must be emitted. StateRequest { target: PeerId, - request: crate::schema::v1::StateRequest, + request: sc_network_sync::schema::v1::StateRequest, pending_response: oneshot::Sender, RequestFailure>>, }, /// A new warp sync request must be emitted. @@ -1458,7 +1457,9 @@ where match req { PeerRequest::Block(req) => { let protobuf_response = - match crate::schema::v1::BlockResponse::decode(&resp[..]) { + match sc_network_sync::schema::v1::BlockResponse::decode( + &resp[..], + ) { Ok(proto) => proto, Err(e) => { debug!( @@ -1478,7 +1479,9 @@ where }, PeerRequest::State => { let protobuf_response = - match crate::schema::v1::StateResponse::decode(&resp[..]) { + match sc_network_sync::schema::v1::StateResponse::decode( + &resp[..], + ) { Ok(proto) => proto, Err(e) => { debug!( @@ -1764,7 +1767,7 @@ where }, NotificationsOut::Notification { peer_id, set_id, message } => match set_id { HARDCODED_PEERSETS_SYNC if self.peers.contains_key(&peer_id) => { - if let Ok(announce) = message::BlockAnnounce::decode(&mut message.as_ref()) { + if let Ok(announce) = BlockAnnounce::decode(&mut message.as_ref()) { self.push_block_announce_validation(peer_id, announce); // Make sure that the newly added block announce validation future was diff --git a/client/network/src/protocol/message.rs b/client/network/src/protocol/message.rs index f173fff8503a5..a57740ec2746b 100644 --- a/client/network/src/protocol/message.rs +++ b/client/network/src/protocol/message.rs @@ -20,19 +20,13 @@ //! payload. pub use self::generic::{ - BlockAnnounce, FromBlock, RemoteCallRequest, RemoteChangesRequest, RemoteChangesResponse, - RemoteHeaderRequest, RemoteHeaderResponse, RemoteReadChildRequest, RemoteReadRequest, Roles, + RemoteCallRequest, RemoteChangesRequest, RemoteChangesResponse, RemoteHeaderRequest, + RemoteHeaderResponse, RemoteReadChildRequest, RemoteReadRequest, Roles, }; -use bitflags::bitflags; -use codec::{Decode, Encode, Error, Input, Output}; +use codec::{Decode, Encode}; use sc_client_api::StorageProof; -use sp_runtime::{ - traits::{Block as BlockT, Header as HeaderT}, - ConsensusEngineId, -}; - -/// A unique ID of a request. -pub type RequestId = u64; +use sc_network_common::message::RequestId; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; /// Type alias for using the message type using block type parameters. pub type Message = generic::Message< @@ -42,86 +36,9 @@ pub type Message = generic::Message< ::Extrinsic, >; -/// Type alias for using the block request type using block type parameters. -pub type BlockRequest = - generic::BlockRequest<::Hash, <::Header as HeaderT>::Number>; - -/// Type alias for using the BlockData type using block type parameters. -pub type BlockData = - generic::BlockData<::Header, ::Hash, ::Extrinsic>; - -/// Type alias for using the BlockResponse type using block type parameters. -pub type BlockResponse = - generic::BlockResponse<::Header, ::Hash, ::Extrinsic>; - /// A set of transactions. pub type Transactions = Vec; -// Bits of block data and associated artifacts to request. -bitflags! { - /// Node roles bitmask. - pub struct BlockAttributes: u8 { - /// Include block header. - const HEADER = 0b00000001; - /// Include block body. - const BODY = 0b00000010; - /// Include block receipt. - const RECEIPT = 0b00000100; - /// Include block message queue. - const MESSAGE_QUEUE = 0b00001000; - /// Include a justification for the block. - const JUSTIFICATION = 0b00010000; - /// Include indexed transactions for a block. - const INDEXED_BODY = 0b00100000; - } -} - -impl BlockAttributes { - /// Encodes attributes as big endian u32, compatible with SCALE-encoding (i.e the - /// significant byte has zero index). - pub fn to_be_u32(&self) -> u32 { - u32::from_be_bytes([self.bits(), 0, 0, 0]) - } - - /// Decodes attributes, encoded with the `encode_to_be_u32()` call. - pub fn from_be_u32(encoded: u32) -> Result { - Self::from_bits(encoded.to_be_bytes()[0]) - .ok_or_else(|| Error::from("Invalid BlockAttributes")) - } -} - -impl Encode for BlockAttributes { - fn encode_to(&self, dest: &mut T) { - dest.push_byte(self.bits()) - } -} - -impl codec::EncodeLike for BlockAttributes {} - -impl Decode for BlockAttributes { - fn decode(input: &mut I) -> Result { - Self::from_bits(input.read_byte()?).ok_or_else(|| Error::from("Invalid bytes")) - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy, Encode, Decode)] -/// Block enumeration direction. -pub enum Direction { - /// Enumerate in ascending order (from child to parent). - Ascending = 0, - /// Enumerate in descending order (from parent to canonical child). - Descending = 1, -} - -/// Block state in the chain. -#[derive(Debug, PartialEq, Eq, Clone, Copy, Encode, Decode)] -pub enum BlockState { - /// Block is not part of the best chain. - Normal, - /// Latest best block. - Best, -} - /// Remote call response. #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] pub struct RemoteCallResponse { @@ -140,35 +57,18 @@ pub struct RemoteReadResponse { pub proof: StorageProof, } -/// Announcement summary used for debug logging. -#[derive(Debug)] -pub struct AnnouncementSummary { - pub block_hash: H::Hash, - pub number: H::Number, - pub parent_hash: H::Hash, - pub state: Option, -} - -impl generic::BlockAnnounce { - pub fn summary(&self) -> AnnouncementSummary { - AnnouncementSummary { - block_hash: self.header.hash(), - number: *self.header.number(), - parent_hash: *self.header.parent_hash(), - state: self.state, - } - } -} - /// Generic types. pub mod generic { - use super::{ - BlockAttributes, BlockState, ConsensusEngineId, Direction, RemoteCallResponse, - RemoteReadResponse, RequestId, StorageProof, Transactions, - }; + use super::{RemoteCallResponse, RemoteReadResponse, Transactions}; use bitflags::bitflags; use codec::{Decode, Encode, Input, Output}; - use sp_runtime::{EncodedJustification, Justifications}; + use sc_client_api::StorageProof; + use sc_network_common::message::RequestId; + use sc_network_sync::message::{ + generic::{BlockRequest, BlockResponse}, + BlockAnnounce, + }; + use sp_runtime::ConsensusEngineId; bitflags! { /// Bitmask of the roles that a node fulfills. @@ -212,7 +112,7 @@ pub mod generic { } impl codec::Encode for Roles { - fn encode_to(&self, dest: &mut T) { + fn encode_to(&self, dest: &mut T) { dest.push_byte(self.bits()) } } @@ -220,7 +120,7 @@ pub mod generic { impl codec::EncodeLike for Roles {} impl codec::Decode for Roles { - fn decode(input: &mut I) -> Result { + fn decode(input: &mut I) -> Result { Self::from_bits(input.read_byte()?).ok_or_else(|| codec::Error::from("Invalid bytes")) } } @@ -234,36 +134,6 @@ pub mod generic { pub data: Vec, } - /// Block data sent in the response. - #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] - pub struct BlockData { - /// Block header hash. - pub hash: Hash, - /// Block header if requested. - pub header: Option

, - /// Block body if requested. - pub body: Option>, - /// Block body indexed transactions if requested. - pub indexed_body: Option>>, - /// Block receipt if requested. - pub receipt: Option>, - /// Block message queue if requested. - pub message_queue: Option>, - /// Justification if requested. - pub justification: Option, - /// Justifications if requested. - pub justifications: Option, - } - - /// Identifies starting point of a block sequence. - #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] - pub enum FromBlock { - /// Start with given hash. - Hash(Hash), - /// Start with given block number. - Number(Number), - } - /// A network message. #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] pub enum Message { @@ -380,68 +250,6 @@ pub mod generic { } } - /// Request block data from a peer. - #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] - pub struct BlockRequest { - /// Unique request id. - pub id: RequestId, - /// Bits of block data to request. - pub fields: BlockAttributes, - /// Start from this block. - pub from: FromBlock, - /// End at this block. An implementation defined maximum is used when unspecified. - pub to: Option, - /// Sequence direction. - pub direction: Direction, - /// Maximum number of blocks to return. An implementation defined maximum is used when - /// unspecified. - pub max: Option, - } - - /// Response to `BlockRequest` - #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] - pub struct BlockResponse { - /// Id of a request this response was made for. - pub id: RequestId, - /// Block data for the requested sequence. - pub blocks: Vec>, - } - - /// Announce a new complete relay chain block on the network. - #[derive(Debug, PartialEq, Eq, Clone)] - pub struct BlockAnnounce { - /// New block header. - pub header: H, - /// Block state. TODO: Remove `Option` and custom encoding when v4 becomes common. - pub state: Option, - /// Data associated with this block announcement, e.g. a candidate message. - pub data: Option>, - } - - // Custom Encode/Decode impl to maintain backwards compatibility with v3. - // This assumes that the packet contains nothing but the announcement message. - // TODO: Get rid of it once protocol v4 is common. - impl Encode for BlockAnnounce { - fn encode_to(&self, dest: &mut T) { - self.header.encode_to(dest); - if let Some(state) = &self.state { - state.encode_to(dest); - } - if let Some(data) = &self.data { - data.encode_to(dest) - } - } - } - - impl Decode for BlockAnnounce { - fn decode(input: &mut I) -> Result { - let header = H::decode(input)?; - let state = BlockState::decode(input).ok(); - let data = Vec::decode(input).ok(); - Ok(Self { header, state, data }) - } - } - #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] /// Remote call request. pub struct RemoteCallRequest { diff --git a/client/network/src/request_responses.rs b/client/network/src/request_responses.rs index 04d6ccb54339f..6c7b0f3fcdfc8 100644 --- a/client/network/src/request_responses.rs +++ b/client/network/src/request_responses.rs @@ -53,6 +53,7 @@ use libp2p::{ NetworkBehaviourAction, PollParameters, }, }; +use sc_network_common::request_responses::{IncomingRequest, OutgoingResponse, ProtocolConfig}; use std::{ borrow::Cow, collections::{hash_map::Entry, HashMap}, @@ -65,96 +66,6 @@ use std::{ pub use libp2p::request_response::{InboundFailure, OutboundFailure, RequestId}; use sc_peerset::{PeersetHandle, BANNED_THRESHOLD}; -/// Configuration for a single request-response protocol. -#[derive(Debug, Clone)] -pub struct ProtocolConfig { - /// Name of the protocol on the wire. Should be something like `/foo/bar`. - pub name: Cow<'static, str>, - - /// Maximum allowed size, in bytes, of a request. - /// - /// Any request larger than this value will be declined as a way to avoid allocating too - /// much memory for it. - pub max_request_size: u64, - - /// Maximum allowed size, in bytes, of a response. - /// - /// Any response larger than this value will be declined as a way to avoid allocating too - /// much memory for it. - pub max_response_size: u64, - - /// Duration after which emitted requests are considered timed out. - /// - /// If you expect the response to come back quickly, you should set this to a smaller duration. - pub request_timeout: Duration, - - /// Channel on which the networking service will send incoming requests. - /// - /// Every time a peer sends a request to the local node using this protocol, the networking - /// service will push an element on this channel. The receiving side of this channel then has - /// to pull this element, process the request, and send back the response to send back to the - /// peer. - /// - /// The size of the channel has to be carefully chosen. If the channel is full, the networking - /// service will discard the incoming request send back an error to the peer. Consequently, - /// the channel being full is an indicator that the node is overloaded. - /// - /// You can typically set the size of the channel to `T / d`, where `T` is the - /// `request_timeout` and `d` is the expected average duration of CPU and I/O it takes to - /// build a response. - /// - /// Can be `None` if the local node does not support answering incoming requests. - /// If this is `None`, then the local node will not advertise support for this protocol towards - /// other peers. If this is `Some` but the channel is closed, then the local node will - /// advertise support for this protocol, but any incoming request will lead to an error being - /// sent back. - pub inbound_queue: Option>, -} - -/// A single request received by a peer on a request-response protocol. -#[derive(Debug)] -pub struct IncomingRequest { - /// Who sent the request. - pub peer: PeerId, - - /// Request sent by the remote. Will always be smaller than - /// [`ProtocolConfig::max_request_size`]. - pub payload: Vec, - - /// Channel to send back the response. - /// - /// There are two ways to indicate that handling the request failed: - /// - /// 1. Drop `pending_response` and thus not changing the reputation of the peer. - /// - /// 2. Sending an `Err(())` via `pending_response`, optionally including reputation changes for - /// the given peer. - pub pending_response: oneshot::Sender, -} - -/// Response for an incoming request to be send by a request protocol handler. -#[derive(Debug)] -pub struct OutgoingResponse { - /// The payload of the response. - /// - /// `Err(())` if none is available e.g. due an error while handling the request. - pub result: Result, ()>, - - /// Reputation changes accrued while handling the request. To be applied to the reputation of - /// the peer sending the request. - pub reputation_changes: Vec, - - /// If provided, the `oneshot::Sender` will be notified when the request has been sent to the - /// peer. - /// - /// > **Note**: Operating systems typically maintain a buffer of a few dozen kilobytes of - /// > outgoing data for each TCP socket, and it is not possible for a user - /// > application to inspect this buffer. This channel here is not actually notified - /// > when the response has been fully sent out, but rather when it has fully been - /// > written to the buffer managed by the operating system. - pub sent_feedback: Option>, -} - /// Event generated by the [`RequestResponsesBehaviour`]. #[derive(Debug)] pub enum Event { diff --git a/client/network/src/schema.rs b/client/network/src/schema.rs index 032db5f1733c5..80301a59c29ef 100644 --- a/client/network/src/schema.rs +++ b/client/network/src/schema.rs @@ -19,7 +19,6 @@ //! Include sources generated from protobuf definitions. pub mod v1 { - include!(concat!(env!("OUT_DIR"), "/api.v1.rs")); pub mod light { include!(concat!(env!("OUT_DIR"), "/api.v1.light.rs")); } diff --git a/client/network/src/service.rs b/client/network/src/service.rs index 91517e14915bc..d2600e3295bf0 100644 --- a/client/network/src/service.rs +++ b/client/network/src/service.rs @@ -37,11 +37,8 @@ use crate::{ NetworkState, NotConnectedPeer as NetworkStateNotConnectedPeer, Peer as NetworkStatePeer, }, protocol::{ - self, - event::Event, - message::generic::Roles, - sync::{Status as SyncStatus, SyncState}, - NotificationsSink, NotifsHandlerError, PeerInfo, Protocol, Ready, + self, event::Event, message::generic::Roles, NotificationsSink, NotifsHandlerError, + PeerInfo, Protocol, Ready, }, transactions, transport, DhtEvent, ExHashT, NetworkStateInfo, NetworkStatus, ReputationChange, }; @@ -63,6 +60,7 @@ use metrics::{Histogram, HistogramVec, MetricSources, Metrics}; use parking_lot::Mutex; use sc_client_api::{BlockBackend, ProofProvider}; use sc_consensus::{BlockImportError, BlockImportStatus, ImportQueue, Link}; +use sc_network_sync::{Status as SyncStatus, SyncState}; use sc_peerset::PeersetHandle; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; use sp_blockchain::{HeaderBackend, HeaderMetadata}; diff --git a/client/network/src/service/tests.rs b/client/network/src/service/tests.rs index 03d647eade173..36205f32d33c6 100644 --- a/client/network/src/service/tests.rs +++ b/client/network/src/service/tests.rs @@ -17,13 +17,14 @@ // along with this program. If not, see . use crate::{ - block_request_handler::BlockRequestHandler, config, - light_client_requests::handler::LightClientRequestHandler, + config, light_client_requests::handler::LightClientRequestHandler, state_request_handler::StateRequestHandler, Event, NetworkService, NetworkWorker, }; use futures::prelude::*; use libp2p::PeerId; +use sc_network_common::config::ProtocolId; +use sc_network_sync::block_request_handler::BlockRequestHandler; use sp_runtime::traits::{Block as BlockT, Header as _}; use std::{borrow::Cow, sync::Arc, time::Duration}; use substrate_test_runtime_client::{TestClientBuilder, TestClientBuilderExt as _}; @@ -87,7 +88,7 @@ fn build_test_full_node( None, )); - let protocol_id = config::ProtocolId::from("/test-protocol-name"); + let protocol_id = ProtocolId::from("/test-protocol-name"); let block_request_protocol_config = { let (handler, protocol_config) = BlockRequestHandler::new(&protocol_id, client.clone(), 50); diff --git a/client/network/src/transactions.rs b/client/network/src/transactions.rs index 64e208d718889..1f54f05d7446f 100644 --- a/client/network/src/transactions.rs +++ b/client/network/src/transactions.rs @@ -27,7 +27,7 @@ //! `Future` that processes transactions. use crate::{ - config::{self, ProtocolId, TransactionImport, TransactionImportFuture, TransactionPool}, + config::{self, TransactionImport, TransactionImportFuture, TransactionPool}, error, protocol::message, service::NetworkService, @@ -40,6 +40,7 @@ use futures::{channel::mpsc, prelude::*, stream::FuturesUnordered}; use libp2p::{multiaddr, PeerId}; use log::{debug, trace, warn}; use prometheus_endpoint::{register, Counter, PrometheusError, Registry, U64}; +use sc_network_common::config::ProtocolId; use sp_runtime::traits::Block as BlockT; use std::{ borrow::Cow, diff --git a/client/network/sync/Cargo.toml b/client/network/sync/Cargo.toml new file mode 100644 index 0000000000000..c171194d5b24d --- /dev/null +++ b/client/network/sync/Cargo.toml @@ -0,0 +1,49 @@ +[package] +description = "Substrate sync network protocol" +name = "sc-network-sync" +version = "0.10.0-dev" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +authors = ["Parity Technologies "] +edition = "2021" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +documentation = "https://docs.rs/sc-network-sync" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[build-dependencies] +prost-build = "0.9" + +[dependencies] +bitflags = "1.3.2" +codec = { package = "parity-scale-codec", version = "3.0.0", features = [ + "derive", +] } +either = "1.5.3" +fork-tree = { version = "3.0.0", path = "../../../utils/fork-tree" } +futures = "0.3.21" +libp2p = "0.44.0" +log = "0.4.16" +lru = "0.7.5" +prost = "0.9" +sc-client-api = { version = "4.0.0-dev", path = "../../api" } +sc-consensus = { version = "0.10.0-dev", path = "../../consensus/common" } +sc-network-common = { version = "0.10.0-dev", path = "../common" } +sc-peerset = { version = "4.0.0-dev", path = "../../peerset" } +smallvec = "1.8.0" +sp-arithmetic = { version = "5.0.0", path = "../../../primitives/arithmetic" } +sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } +sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } +sp-core = { version = "6.0.0", path = "../../../primitives/core" } +sp-finality-grandpa = { version = "4.0.0-dev", path = "../../../primitives/finality-grandpa" } +sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } +thiserror = "1.0" + +[dev-dependencies] +quickcheck = "1.0.3" +sc-block-builder = { version = "0.10.0-dev", path = "../../block-builder" } +sp-test-primitives = { version = "2.0.0", path = "../../../primitives/test-primitives" } +sp-tracing = { version = "5.0.0", path = "../../../primitives/tracing" } +substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } diff --git a/client/network/sync/build.rs b/client/network/sync/build.rs new file mode 100644 index 0000000000000..55794919cdb42 --- /dev/null +++ b/client/network/sync/build.rs @@ -0,0 +1,5 @@ +const PROTOS: &[&str] = &["src/schema/api.v1.proto"]; + +fn main() { + prost_build::compile_protos(PROTOS, &["src/schema"]).unwrap(); +} diff --git a/client/network/src/block_request_handler.rs b/client/network/sync/src/block_request_handler.rs similarity index 99% rename from client/network/src/block_request_handler.rs rename to client/network/sync/src/block_request_handler.rs index 7458f421af397..b9ffd24cee4c7 100644 --- a/client/network/src/block_request_handler.rs +++ b/client/network/sync/src/block_request_handler.rs @@ -18,21 +18,23 @@ //! `crate::request_responses::RequestResponsesBehaviour`. use crate::{ - config::ProtocolId, - protocol::message::BlockAttributes, - request_responses::{IncomingRequest, OutgoingResponse, ProtocolConfig}, + message::BlockAttributes, schema::v1::{block_request::FromBlock, BlockResponse, Direction}, - PeerId, ReputationChange, }; use codec::{Decode, Encode}; use futures::{ channel::{mpsc, oneshot}, stream::StreamExt, }; +use libp2p::PeerId; use log::debug; use lru::LruCache; use prost::Message; use sc_client_api::BlockBackend; +use sc_network_common::{ + config::ProtocolId, + request_responses::{IncomingRequest, OutgoingResponse, ProtocolConfig}, +}; use sp_blockchain::HeaderBackend; use sp_runtime::{ generic::BlockId, @@ -51,7 +53,7 @@ const MAX_BODY_BYTES: usize = 8 * 1024 * 1024; const MAX_NUMBER_OF_SAME_REQUESTS_PER_PEER: usize = 2; mod rep { - use super::ReputationChange as Rep; + use sc_peerset::ReputationChange as Rep; /// Reputation change when a peer sent us the same request multiple times. pub const SAME_REQUEST: Rep = Rep::new_fatal("Same block request multiple times"); diff --git a/client/network/src/protocol/sync/blocks.rs b/client/network/sync/src/blocks.rs similarity index 99% rename from client/network/src/protocol/sync/blocks.rs rename to client/network/sync/src/blocks.rs index 43b70d17d8add..b897184e7a44c 100644 --- a/client/network/src/protocol/sync/blocks.rs +++ b/client/network/sync/src/blocks.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::protocol::message; +use crate::message; use libp2p::PeerId; use log::trace; use sp_runtime::traits::{Block as BlockT, NumberFor, One}; @@ -217,7 +217,8 @@ impl BlockCollection { #[cfg(test)] mod test { use super::{BlockCollection, BlockData, BlockRangeState}; - use crate::{protocol::message, PeerId}; + use crate::message; + use libp2p::PeerId; use sp_core::H256; use sp_runtime::testing::{Block as RawBlock, ExtrinsicWrapper}; diff --git a/client/network/src/protocol/sync/extra_requests.rs b/client/network/sync/src/extra_requests.rs similarity index 98% rename from client/network/src/protocol/sync/extra_requests.rs rename to client/network/sync/src/extra_requests.rs index 43122631d3c22..c684d8e72783e 100644 --- a/client/network/src/protocol/sync/extra_requests.rs +++ b/client/network/sync/src/extra_requests.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::protocol::sync::{PeerSync, PeerSyncState}; +use crate::{PeerSync, PeerSyncState}; use fork_tree::ForkTree; use libp2p::PeerId; use log::{debug, trace, warn}; @@ -31,7 +31,7 @@ use std::{ const EXTRA_RETRY_WAIT: Duration = Duration::from_secs(10); /// Pending extra data request for the given block (hash and number). -pub(crate) type ExtraRequest = (::Hash, NumberFor); +type ExtraRequest = (::Hash, NumberFor); /// Manages pending block extra data (e.g. justification) requests. /// @@ -57,11 +57,11 @@ pub(crate) struct ExtraRequests { } #[derive(Debug)] -pub(crate) struct Metrics { - pub(crate) pending_requests: u32, - pub(crate) active_requests: u32, - pub(crate) importing_requests: u32, - pub(crate) failed_requests: u32, +pub struct Metrics { + pub pending_requests: u32, + pub active_requests: u32, + pub importing_requests: u32, + pub failed_requests: u32, _priv: (), } @@ -352,7 +352,7 @@ impl<'a, B: BlockT> Matcher<'a, B> { #[cfg(test)] mod tests { use super::*; - use crate::protocol::sync::PeerSync; + use crate::PeerSync; use quickcheck::{Arbitrary, Gen, QuickCheck}; use sp_blockchain::Error as ClientError; use sp_test_primitives::{Block, BlockNumber, Hash}; diff --git a/client/network/src/protocol/sync.rs b/client/network/sync/src/lib.rs similarity index 98% rename from client/network/src/protocol/sync.rs rename to client/network/sync/src/lib.rs index fb89e12a44fe8..bc0ed46c3f068 100644 --- a/client/network/src/protocol/sync.rs +++ b/client/network/sync/src/lib.rs @@ -28,11 +28,25 @@ //! the network, or whenever a block has been successfully verified, call the appropriate method in //! order to update it. +pub mod block_request_handler; +pub mod blocks; +pub mod message; +pub mod schema; +pub mod state; +pub mod state_request_handler; +pub mod warp; +pub mod warp_request_handler; + use crate::{ - protocol::message::{self, BlockAnnounce, BlockAttributes, BlockRequest, BlockResponse}, + blocks::BlockCollection, + message::{BlockAnnounce, BlockAttributes, BlockRequest, BlockResponse}, schema::v1::{StateRequest, StateResponse}, + state::{StateDownloadProgress, StateSync}, + warp::{ + EncodedProof, WarpProofImportResult, WarpProofRequest, WarpSync, WarpSyncPhase, + WarpSyncProgress, WarpSyncProvider, + }, }; -use blocks::BlockCollection; use codec::Encode; use either::Either; use extra_requests::ExtraRequests; @@ -55,8 +69,6 @@ use sp_runtime::{ }, EncodedJustification, Justifications, }; -pub use state::StateDownloadProgress; -use state::StateSync; use std::{ collections::{hash_map::Entry, HashMap, HashSet}, fmt, @@ -64,13 +76,8 @@ use std::{ pin::Pin, sync::Arc, }; -use warp::{WarpProofRequest, WarpSync, WarpSyncProvider}; -pub use warp::{WarpSyncPhase, WarpSyncProgress}; -mod blocks; mod extra_requests; -mod state; -mod warp; /// Maximum blocks to request in a single packet. const MAX_BLOCKS_TO_REQUEST: usize = 64; @@ -332,18 +339,6 @@ pub enum SyncState { Downloading, } -impl fmt::Display for WarpSyncPhase { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::AwaitingPeers => write!(f, "Waiting for peers"), - Self::DownloadingWarpProofs => write!(f, "Downloading finality proofs"), - Self::DownloadingState => write!(f, "Downloading state"), - Self::ImportingState => write!(f, "Importing state"), - Self::DownloadingBlocks(n) => write!(f, "Downloading block history (#{})", n), - } - } -} - /// Syncing status and statistics. #[derive(Clone)] pub struct Status { @@ -1357,7 +1352,7 @@ where pub fn on_warp_sync_data( &mut self, who: &PeerId, - response: warp::EncodedProof, + response: EncodedProof, ) -> Result<(), BadPeer> { if let Some(peer) = self.peers.get_mut(who) { if let PeerSyncState::DownloadingWarpProof = peer.state { @@ -1379,8 +1374,8 @@ where }; match import_result { - warp::WarpProofImportResult::Success => Ok(()), - warp::WarpProofImportResult::BadResponse => { + WarpProofImportResult::Success => Ok(()), + WarpProofImportResult::BadResponse => { debug!(target: "sync", "Bad proof data received from {}", who); Err(BadPeer(*who, rep::BAD_BLOCK)) }, @@ -2173,7 +2168,7 @@ where } /// Return some key metrics. - pub(crate) fn metrics(&self) -> Metrics { + pub fn metrics(&self) -> Metrics { Metrics { queued_blocks: self.queue_blocks.len().try_into().unwrap_or(std::u32::MAX), fork_targets: self.fork_targets.len().try_into().unwrap_or(std::u32::MAX), @@ -2220,10 +2215,10 @@ fn legacy_justification_mapping( } #[derive(Debug)] -pub(crate) struct Metrics { - pub(crate) queued_blocks: u32, - pub(crate) fork_targets: u32, - pub(crate) justifications: extra_requests::Metrics, +pub struct Metrics { + pub queued_blocks: u32, + pub fork_targets: u32, + pub justifications: extra_requests::Metrics, _priv: (), } @@ -2575,9 +2570,10 @@ fn validate_blocks( #[cfg(test)] mod test { use super::{ - message::{BlockData, BlockState, FromBlock}, + message::{BlockState, FromBlock}, *, }; + use crate::message::BlockData; use futures::{executor::block_on, future::poll_fn}; use sc_block_builder::BlockBuilderProvider; use sp_blockchain::HeaderBackend; diff --git a/client/network/sync/src/message.rs b/client/network/sync/src/message.rs new file mode 100644 index 0000000000000..996ee5231cf2e --- /dev/null +++ b/client/network/sync/src/message.rs @@ -0,0 +1,222 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Network packet message types. These get serialized and put into the lower level protocol +//! payload. + +use bitflags::bitflags; +use codec::{Decode, Encode, Error, Input, Output}; +pub use generic::{BlockAnnounce, FromBlock}; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; + +/// Type alias for using the block request type using block type parameters. +pub type BlockRequest = + generic::BlockRequest<::Hash, <::Header as HeaderT>::Number>; + +/// Type alias for using the BlockData type using block type parameters. +pub type BlockData = + generic::BlockData<::Header, ::Hash, ::Extrinsic>; + +/// Type alias for using the BlockResponse type using block type parameters. +pub type BlockResponse = + generic::BlockResponse<::Header, ::Hash, ::Extrinsic>; + +// Bits of block data and associated artifacts to request. +bitflags! { + /// Node roles bitmask. + pub struct BlockAttributes: u8 { + /// Include block header. + const HEADER = 0b00000001; + /// Include block body. + const BODY = 0b00000010; + /// Include block receipt. + const RECEIPT = 0b00000100; + /// Include block message queue. + const MESSAGE_QUEUE = 0b00001000; + /// Include a justification for the block. + const JUSTIFICATION = 0b00010000; + /// Include indexed transactions for a block. + const INDEXED_BODY = 0b00100000; + } +} + +impl BlockAttributes { + /// Encodes attributes as big endian u32, compatible with SCALE-encoding (i.e the + /// significant byte has zero index). + pub fn to_be_u32(&self) -> u32 { + u32::from_be_bytes([self.bits(), 0, 0, 0]) + } + + /// Decodes attributes, encoded with the `encode_to_be_u32()` call. + pub fn from_be_u32(encoded: u32) -> Result { + Self::from_bits(encoded.to_be_bytes()[0]) + .ok_or_else(|| Error::from("Invalid BlockAttributes")) + } +} + +impl Encode for BlockAttributes { + fn encode_to(&self, dest: &mut T) { + dest.push_byte(self.bits()) + } +} + +impl codec::EncodeLike for BlockAttributes {} + +impl Decode for BlockAttributes { + fn decode(input: &mut I) -> Result { + Self::from_bits(input.read_byte()?).ok_or_else(|| Error::from("Invalid bytes")) + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Encode, Decode)] +/// Block enumeration direction. +pub enum Direction { + /// Enumerate in ascending order (from child to parent). + Ascending = 0, + /// Enumerate in descending order (from parent to canonical child). + Descending = 1, +} + +/// Block state in the chain. +#[derive(Debug, PartialEq, Eq, Clone, Copy, Encode, Decode)] +pub enum BlockState { + /// Block is not part of the best chain. + Normal, + /// Latest best block. + Best, +} + +/// Announcement summary used for debug logging. +#[derive(Debug)] +pub struct AnnouncementSummary { + pub block_hash: H::Hash, + pub number: H::Number, + pub parent_hash: H::Hash, + pub state: Option, +} + +impl BlockAnnounce { + pub fn summary(&self) -> AnnouncementSummary { + AnnouncementSummary { + block_hash: self.header.hash(), + number: *self.header.number(), + parent_hash: *self.header.parent_hash(), + state: self.state, + } + } +} + +/// Generic types. +pub mod generic { + use super::{BlockAttributes, BlockState, Direction}; + use codec::{Decode, Encode, Input, Output}; + use sc_network_common::message::RequestId; + use sp_runtime::{EncodedJustification, Justifications}; + + /// Block data sent in the response. + #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] + pub struct BlockData { + /// Block header hash. + pub hash: Hash, + /// Block header if requested. + pub header: Option
, + /// Block body if requested. + pub body: Option>, + /// Block body indexed transactions if requested. + pub indexed_body: Option>>, + /// Block receipt if requested. + pub receipt: Option>, + /// Block message queue if requested. + pub message_queue: Option>, + /// Justification if requested. + pub justification: Option, + /// Justifications if requested. + pub justifications: Option, + } + + /// Request block data from a peer. + #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] + pub struct BlockRequest { + /// Unique request id. + pub id: RequestId, + /// Bits of block data to request. + pub fields: BlockAttributes, + /// Start from this block. + pub from: FromBlock, + /// End at this block. An implementation defined maximum is used when unspecified. + pub to: Option, + /// Sequence direction. + pub direction: Direction, + /// Maximum number of blocks to return. An implementation defined maximum is used when + /// unspecified. + pub max: Option, + } + + /// Identifies starting point of a block sequence. + #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] + pub enum FromBlock { + /// Start with given hash. + Hash(Hash), + /// Start with given block number. + Number(Number), + } + + /// Response to `BlockRequest` + #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] + pub struct BlockResponse { + /// Id of a request this response was made for. + pub id: RequestId, + /// Block data for the requested sequence. + pub blocks: Vec>, + } + + /// Announce a new complete relay chain block on the network. + #[derive(Debug, PartialEq, Eq, Clone)] + pub struct BlockAnnounce { + /// New block header. + pub header: H, + /// Block state. TODO: Remove `Option` and custom encoding when v4 becomes common. + pub state: Option, + /// Data associated with this block announcement, e.g. a candidate message. + pub data: Option>, + } + + // Custom Encode/Decode impl to maintain backwards compatibility with v3. + // This assumes that the packet contains nothing but the announcement message. + // TODO: Get rid of it once protocol v4 is common. + impl Encode for BlockAnnounce { + fn encode_to(&self, dest: &mut T) { + self.header.encode_to(dest); + if let Some(state) = &self.state { + state.encode_to(dest); + } + if let Some(data) = &self.data { + data.encode_to(dest) + } + } + } + + impl Decode for BlockAnnounce { + fn decode(input: &mut I) -> Result { + let header = H::decode(input)?; + let state = BlockState::decode(input).ok(); + let data = Vec::decode(input).ok(); + Ok(Self { header, state, data }) + } + } +} diff --git a/client/network/sync/src/schema.rs b/client/network/sync/src/schema.rs new file mode 100644 index 0000000000000..aa3eb84621d8f --- /dev/null +++ b/client/network/sync/src/schema.rs @@ -0,0 +1,23 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Include sources generated from protobuf definitions. + +pub mod v1 { + include!(concat!(env!("OUT_DIR"), "/api.v1.rs")); +} diff --git a/client/network/src/schema/api.v1.proto b/client/network/sync/src/schema/api.v1.proto similarity index 100% rename from client/network/src/schema/api.v1.proto rename to client/network/sync/src/schema/api.v1.proto diff --git a/client/network/src/protocol/sync/state.rs b/client/network/sync/src/state.rs similarity index 99% rename from client/network/src/protocol/sync/state.rs rename to client/network/sync/src/state.rs index 6208b2bcdd18a..4041c28af0eba 100644 --- a/client/network/src/protocol/sync/state.rs +++ b/client/network/sync/src/state.rs @@ -16,6 +16,8 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +//! State sync support. + use crate::schema::v1::{StateEntry, StateRequest, StateResponse}; use codec::{Decode, Encode}; use log::debug; @@ -26,8 +28,6 @@ use sp_core::storage::well_known_keys; use sp_runtime::traits::{Block as BlockT, Header, NumberFor}; use std::{collections::HashMap, sync::Arc}; -/// State sync support. - /// State sync state machine. Accumulates partial state data until it /// is ready to be imported. pub struct StateSync { diff --git a/client/network/src/state_request_handler.rs b/client/network/sync/src/state_request_handler.rs similarity index 97% rename from client/network/src/state_request_handler.rs rename to client/network/sync/src/state_request_handler.rs index 7ac1a17e8e759..8e0bae14046da 100644 --- a/client/network/src/state_request_handler.rs +++ b/client/network/sync/src/state_request_handler.rs @@ -17,21 +17,21 @@ //! Helper for handling (i.e. answering) state requests from a remote peer via the //! `crate::request_responses::RequestResponsesBehaviour`. -use crate::{ - config::ProtocolId, - request_responses::{IncomingRequest, OutgoingResponse, ProtocolConfig}, - schema::v1::{KeyValueStateEntry, StateEntry, StateRequest, StateResponse}, - PeerId, ReputationChange, -}; +use crate::schema::v1::{KeyValueStateEntry, StateEntry, StateRequest, StateResponse}; use codec::{Decode, Encode}; use futures::{ channel::{mpsc, oneshot}, stream::StreamExt, }; +use libp2p::PeerId; use log::{debug, trace}; use lru::LruCache; use prost::Message; use sc_client_api::ProofProvider; +use sc_network_common::{ + config::ProtocolId, + request_responses::{IncomingRequest, OutgoingResponse, ProtocolConfig}, +}; use sp_runtime::{generic::BlockId, traits::Block as BlockT}; use std::{ hash::{Hash, Hasher}, @@ -44,7 +44,7 @@ const MAX_RESPONSE_BYTES: usize = 2 * 1024 * 1024; // Actual reponse may be bigg const MAX_NUMBER_OF_SAME_REQUESTS_PER_PEER: usize = 2; mod rep { - use super::ReputationChange as Rep; + use sc_peerset::ReputationChange as Rep; /// Reputation change when a peer sent us the same request multiple times. pub const SAME_REQUEST: Rep = Rep::new(i32::MIN, "Same state request multiple times"); diff --git a/client/network/src/protocol/sync/warp.rs b/client/network/sync/src/warp.rs similarity index 90% rename from client/network/src/protocol/sync/warp.rs rename to client/network/sync/src/warp.rs index 6845d6d1dc862..d3d9d7d244153 100644 --- a/client/network/src/protocol/sync/warp.rs +++ b/client/network/sync/src/warp.rs @@ -16,17 +16,20 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -///! Warp sync support. -use super::state::{ImportResult, StateSync}; -use crate::schema::v1::{StateRequest, StateResponse}; +//! Warp sync support. + pub use crate::warp_request_handler::{ EncodedProof, Request as WarpProofRequest, VerificationResult, WarpSyncProvider, }; +use crate::{ + schema::v1::{StateRequest, StateResponse}, + state::{ImportResult, StateSync}, +}; use sc_client_api::ProofProvider; use sp_blockchain::HeaderBackend; use sp_finality_grandpa::{AuthorityList, SetId}; use sp_runtime::traits::{Block as BlockT, NumberFor, Zero}; -use std::sync::Arc; +use std::{fmt, sync::Arc}; enum Phase { WarpProof { set_id: SetId, authorities: AuthorityList, last_hash: B::Hash }, @@ -48,6 +51,18 @@ pub enum WarpSyncPhase { DownloadingBlocks(NumberFor), } +impl fmt::Display for WarpSyncPhase { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::AwaitingPeers => write!(f, "Waiting for peers"), + Self::DownloadingWarpProofs => write!(f, "Downloading finality proofs"), + Self::DownloadingState => write!(f, "Downloading state"), + Self::ImportingState => write!(f, "Importing state"), + Self::DownloadingBlocks(n) => write!(f, "Downloading block history (#{})", n), + } + } +} + /// Reported warp sync progress. #[derive(Clone, Eq, PartialEq, Debug)] pub struct WarpSyncProgress { diff --git a/client/network/src/warp_request_handler.rs b/client/network/sync/src/warp_request_handler.rs similarity index 97% rename from client/network/src/warp_request_handler.rs rename to client/network/sync/src/warp_request_handler.rs index d5bee5833a12a..4f66e0a6daf17 100644 --- a/client/network/src/warp_request_handler.rs +++ b/client/network/sync/src/warp_request_handler.rs @@ -16,13 +16,18 @@ //! Helper for handling (i.e. answering) grandpa warp sync requests from a remote peer. -use crate::config::{IncomingRequest, OutgoingResponse, ProtocolId, RequestResponseConfig}; use codec::{Decode, Encode}; use futures::{ channel::{mpsc, oneshot}, stream::StreamExt, }; use log::debug; +use sc_network_common::{ + config::ProtocolId, + request_responses::{ + IncomingRequest, OutgoingResponse, ProtocolConfig as RequestResponseConfig, + }, +}; use sp_runtime::traits::Block as BlockT; use std::{sync::Arc, time::Duration}; diff --git a/client/network/test/Cargo.toml b/client/network/test/Cargo.toml index 39297dd3ea295..bf3317d0759ee 100644 --- a/client/network/test/Cargo.toml +++ b/client/network/test/Cargo.toml @@ -14,6 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] async-std = "1.11.0" +sc-network-common = { version = "0.10.0-dev", path = "../common" } sc-network = { version = "0.10.0-dev", path = "../" } log = "0.4.16" parking_lot = "0.12.0" diff --git a/client/network/test/src/lib.rs b/client/network/test/src/lib.rs index 20f1a3014867b..9e23a4cc678e9 100644 --- a/client/network/test/src/lib.rs +++ b/client/network/test/src/lib.rs @@ -51,12 +51,13 @@ use sc_network::{ block_request_handler::BlockRequestHandler, config::{ MultiaddrWithPeerId, NetworkConfiguration, NonDefaultSetConfig, NonReservedPeerMode, - ProtocolConfig, ProtocolId, Role, SyncMode, TransportConfig, + ProtocolConfig, Role, SyncMode, TransportConfig, }, light_client_requests::handler::LightClientRequestHandler, state_request_handler::StateRequestHandler, warp_request_handler, Multiaddr, NetworkService, NetworkWorker, }; +pub use sc_network_common::config::ProtocolId; use sc_service::client::Client; use sp_blockchain::{ well_known_cache_keys::{self, Id as CacheKeyId}, diff --git a/client/service/Cargo.toml b/client/service/Cargo.toml index 575305a597798..7138d9d384eeb 100644 --- a/client/service/Cargo.toml +++ b/client/service/Cargo.toml @@ -51,6 +51,7 @@ sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/comm sc-consensus = { version = "0.10.0-dev", path = "../../client/consensus/common" } sp-inherents = { version = "4.0.0-dev", path = "../../primitives/inherents" } sp-storage = { version = "6.0.0", path = "../../primitives/storage" } +sc-network-common = { version = "0.10.0-dev", path = "../network/common" } sc-network = { version = "0.10.0-dev", path = "../network" } sc-chain-spec = { version = "4.0.0-dev", path = "../chain-spec" } sc-client-api = { version = "4.0.0-dev", path = "../api" } diff --git a/client/service/src/config.rs b/client/service/src/config.rs index 7c7bb480aa5c3..56980ad14425f 100644 --- a/client/service/src/config.rs +++ b/client/service/src/config.rs @@ -23,12 +23,17 @@ pub use sc_client_db::{Database, DatabaseSource, KeepBlocks, PruningMode}; pub use sc_executor::WasmExecutionMethod; pub use sc_network::{ config::{ - IncomingRequest, MultiaddrWithPeerId, NetworkConfiguration, NodeKeyConfig, - NonDefaultSetConfig, OutgoingResponse, RequestResponseConfig, Role, SetConfig, - TransportConfig, + MultiaddrWithPeerId, NetworkConfiguration, NodeKeyConfig, NonDefaultSetConfig, Role, + SetConfig, TransportConfig, }, Multiaddr, }; +pub use sc_network_common::{ + config::ProtocolId, + request_responses::{ + IncomingRequest, OutgoingResponse, ProtocolConfig as RequestResponseConfig, + }, +}; use prometheus_endpoint::Registry; use sc_chain_spec::ChainSpec; @@ -208,7 +213,7 @@ impl Configuration { } /// Returns the network protocol id from the chain spec, or the default. - pub fn protocol_id(&self) -> sc_network::config::ProtocolId { + pub fn protocol_id(&self) -> ProtocolId { let protocol_id_full = match self.chain_spec.protocol_id() { Some(pid) => pid, None => { @@ -220,7 +225,7 @@ impl Configuration { crate::DEFAULT_PROTOCOL_ID }, }; - sc_network::config::ProtocolId::from(protocol_id_full) + ProtocolId::from(protocol_id_full) } } From 2d2b956296a1e5da812889da691cbe45611347fe Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Tue, 3 May 2022 23:13:58 +0100 Subject: [PATCH 178/484] Update Pallet Bags List to use `ListError` (#11342) * extract list error changes from kiz-revamp-sorted-list-providers-2-approval-stake * some fixes * weight -> score * Update tests.rs * Update tests.rs * more fixes * remove score updated event * Update frame/bags-list/src/lib.rs Co-authored-by: Keith Yeung Co-authored-by: Keith Yeung --- frame/bags-list/fuzzer/src/main.rs | 6 +-- frame/bags-list/src/lib.rs | 40 ++++++++++-------- frame/bags-list/src/list/mod.rs | 39 +++++++++++------- frame/bags-list/src/list/tests.rs | 16 ++++---- frame/bags-list/src/tests.rs | 48 +++++++++++----------- frame/election-provider-support/src/lib.rs | 16 ++++++-- frame/staking/src/pallet/impls.rs | 10 +++-- frame/staking/src/pallet/mod.rs | 13 +++--- 8 files changed, 109 insertions(+), 79 deletions(-) diff --git a/frame/bags-list/fuzzer/src/main.rs b/frame/bags-list/fuzzer/src/main.rs index 387c266d32256..d0586f0372ac4 100644 --- a/frame/bags-list/fuzzer/src/main.rs +++ b/frame/bags-list/fuzzer/src/main.rs @@ -64,19 +64,19 @@ fn main() { Action::Insert => { if BagsList::on_insert(id, vote_weight).is_err() { // this was a duplicate id, which is ok. We can just update it. - BagsList::on_update(&id, vote_weight); + BagsList::on_update(&id, vote_weight).unwrap(); } assert!(BagsList::contains(&id)); }, Action::Update => { let already_contains = BagsList::contains(&id); - BagsList::on_update(&id, vote_weight); + BagsList::on_update(&id, vote_weight).unwrap(); if already_contains { assert!(BagsList::contains(&id)); } }, Action::Remove => { - BagsList::on_remove(&id); + BagsList::on_remove(&id).unwrap(); assert!(!BagsList::contains(&id)); }, } diff --git a/frame/bags-list/src/lib.rs b/frame/bags-list/src/lib.rs index 53ccd5e4c19f1..77fe609ad8123 100644 --- a/frame/bags-list/src/lib.rs +++ b/frame/bags-list/src/lib.rs @@ -194,12 +194,14 @@ pub mod pallet { #[pallet::error] #[cfg_attr(test, derive(PartialEq))] pub enum Error { - /// Attempted to place node in front of a node in another bag. - NotInSameBag, - /// Id not found in list. - IdNotFound, - /// An Id does not have a greater score than another Id. - NotHeavier, + /// A error in the list interface implementation. + List(ListError), + } + + impl From for Error { + fn from(t: ListError) -> Self { + Error::::List(t) + } } #[pallet::call] @@ -231,7 +233,9 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::put_in_front_of())] pub fn put_in_front_of(origin: OriginFor, lighter: T::AccountId) -> DispatchResult { let heavier = ensure_signed(origin)?; - List::::put_in_front_of(&lighter, &heavier).map_err(Into::into) + List::::put_in_front_of(&lighter, &heavier) + .map_err::, _>(Into::into) + .map_err::(Into::into) } } @@ -250,16 +254,18 @@ pub mod pallet { impl, I: 'static> Pallet { /// Move an account from one bag to another, depositing an event on success. /// - /// If the account changed bags, returns `Some((from, to))`. - pub fn do_rebag(account: &T::AccountId, new_weight: T::Score) -> Option<(T::Score, T::Score)> { - // if no voter at that node, don't do anything. - // the caller just wasted the fee to call this. - let maybe_movement = list::Node::::get(account) - .and_then(|node| List::update_position_for(node, new_weight)); + /// If the account changed bags, returns `Ok(Some((from, to)))`. + pub fn do_rebag( + account: &T::AccountId, + new_score: T::Score, + ) -> Result, ListError> { + // If no voter at that node, don't do anything. the caller just wasted the fee to call this. + let node = list::Node::::get(&account).ok_or(ListError::NodeNotFound)?; + let maybe_movement = List::update_position_for(node, new_score); if let Some((from, to)) = maybe_movement { Self::deposit_event(Event::::Rebagged { who: account.clone(), from, to }); }; - maybe_movement + Ok(maybe_movement) } /// Equivalent to `ListBags::get`, but public. Useful for tests in outside of this crate. @@ -296,11 +302,11 @@ impl, I: 'static> SortedListProvider for Pallet List::::insert(id, score) } - fn on_update(id: &T::AccountId, new_score: T::Score) { - Pallet::::do_rebag(id, new_score); + fn on_update(id: &T::AccountId, new_score: T::Score) -> Result<(), ListError> { + Pallet::::do_rebag(id, new_score).map(|_| ()) } - fn on_remove(id: &T::AccountId) { + fn on_remove(id: &T::AccountId) -> Result<(), ListError> { List::::remove(id) } diff --git a/frame/bags-list/src/list/mod.rs b/frame/bags-list/src/list/mod.rs index 4de70569253da..f93bfca1081e9 100644 --- a/frame/bags-list/src/list/mod.rs +++ b/frame/bags-list/src/list/mod.rs @@ -27,7 +27,10 @@ use crate::Config; use codec::{Decode, Encode, MaxEncodedLen}; use frame_election_provider_support::ScoreProvider; -use frame_support::{traits::Get, DefaultNoBound}; +use frame_support::{ + traits::{Defensive, Get}, + DefaultNoBound, PalletError, +}; use scale_info::TypeInfo; use sp_runtime::traits::{Bounded, Zero}; use sp_std::{ @@ -38,10 +41,14 @@ use sp_std::{ vec::Vec, }; -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo, PalletError)] pub enum ListError { /// A duplicate id has been detected. Duplicate, + /// An Id does not have a greater score than another Id. + NotHeavier, + /// Attempted to place node in front of a node in another bag. + NotInSameBag, /// Given node id was not found. NodeNotFound, } @@ -321,9 +328,13 @@ impl, I: 'static> List { Ok(()) } - /// Remove an id from the list. - pub(crate) fn remove(id: &T::AccountId) { - Self::remove_many(sp_std::iter::once(id)); + /// Remove an id from the list, returning an error if `id` does not exists. + pub(crate) fn remove(id: &T::AccountId) -> Result<(), ListError> { + if !Self::contains(id) { + return Err(ListError::NodeNotFound) + } + let _ = Self::remove_many(sp_std::iter::once(id)); + Ok(()) } /// Remove many ids from the list. @@ -416,31 +427,31 @@ impl, I: 'static> List { pub(crate) fn put_in_front_of( lighter_id: &T::AccountId, heavier_id: &T::AccountId, - ) -> Result<(), crate::pallet::Error> { - use crate::pallet; + ) -> Result<(), ListError> { use frame_support::ensure; - let lighter_node = Node::::get(lighter_id).ok_or(pallet::Error::IdNotFound)?; - let heavier_node = Node::::get(heavier_id).ok_or(pallet::Error::IdNotFound)?; + let lighter_node = Node::::get(&lighter_id).ok_or(ListError::NodeNotFound)?; + let heavier_node = Node::::get(&heavier_id).ok_or(ListError::NodeNotFound)?; - ensure!(lighter_node.bag_upper == heavier_node.bag_upper, pallet::Error::NotInSameBag); + ensure!(lighter_node.bag_upper == heavier_node.bag_upper, ListError::NotInSameBag); // this is the most expensive check, so we do it last. ensure!( - T::ScoreProvider::score(heavier_id) > T::ScoreProvider::score(lighter_id), - pallet::Error::NotHeavier + T::ScoreProvider::score(&heavier_id) > T::ScoreProvider::score(&lighter_id), + ListError::NotHeavier ); // remove the heavier node from this list. Note that this removes the node from storage and // decrements the node counter. - Self::remove(heavier_id); + // defensive: both nodes have been checked to exist. + let _ = Self::remove(&heavier_id).defensive(); // re-fetch `lighter_node` from storage since it may have been updated when `heavier_node` // was removed. let lighter_node = Node::::get(lighter_id).ok_or_else(|| { debug_assert!(false, "id that should exist cannot be found"); crate::log!(warn, "id that should exist cannot be found"); - pallet::Error::IdNotFound + ListError::NodeNotFound })?; // insert `heavier_node` directly in front of `lighter_node`. This will update both nodes diff --git a/frame/bags-list/src/list/tests.rs b/frame/bags-list/src/list/tests.rs index c8e233f1e62cc..ff7dd2871c237 100644 --- a/frame/bags-list/src/list/tests.rs +++ b/frame/bags-list/src/list/tests.rs @@ -21,7 +21,7 @@ use crate::{ ListBags, ListNodes, }; use frame_election_provider_support::{SortedListProvider, VoteWeight}; -use frame_support::{assert_ok, assert_storage_noop}; +use frame_support::{assert_noop, assert_ok, assert_storage_noop}; #[test] fn basic_setup_works() { @@ -98,7 +98,7 @@ fn remove_last_node_in_bags_cleans_bag() { assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])]); // bump 1 to a bigger bag - List::::remove(&1); + List::::remove(&1).unwrap(); assert_ok!(List::::insert(1, 10_000)); // then the bag with bound 10 is wiped from storage. @@ -265,10 +265,10 @@ mod list { ExtBuilder::default().build_and_execute(|| { // removing a non-existent id is a noop assert!(!ListNodes::::contains_key(42)); - assert_storage_noop!(List::::remove(&42)); + assert_noop!(List::::remove(&42), ListError::NodeNotFound); // when removing a node from a bag with multiple nodes: - List::::remove(&2); + List::::remove(&2).unwrap(); // then assert_eq!(get_list_as_ids(), vec![3, 4, 1]); @@ -276,7 +276,7 @@ mod list { ensure_left(2, 3); // when removing a node from a bag with only one node: - List::::remove(&1); + List::::remove(&1).unwrap(); // then assert_eq!(get_list_as_ids(), vec![3, 4]); @@ -286,11 +286,11 @@ mod list { assert!(!ListBags::::contains_key(10)); // remove remaining ids to make sure storage cleans up as expected - List::::remove(&3); + List::::remove(&3).unwrap(); ensure_left(3, 1); assert_eq!(get_list_as_ids(), vec![4]); - List::::remove(&4); + List::::remove(&4).unwrap(); ensure_left(4, 0); assert_eq!(get_list_as_ids(), Vec::::new()); @@ -573,7 +573,7 @@ mod bags { }); // when we make a pre-existing bag empty - List::::remove(&1); + List::::remove(&1).unwrap(); // then assert_eq!(Bag::::get(10), None) diff --git a/frame/bags-list/src/tests.rs b/frame/bags-list/src/tests.rs index 941623229dc27..c63ab26d59a67 100644 --- a/frame/bags-list/src/tests.rs +++ b/frame/bags-list/src/tests.rs @@ -34,7 +34,7 @@ mod pallet { vec![(10, vec![1]), (20, vec![42]), (1_000, vec![2, 3, 4])] ); - // when increasing vote weight to the level of non-existent bag + // when increasing score to the level of non-existent bag StakingMock::set_score_of(&42, 2_000); assert_ok!(BagsList::rebag(Origin::signed(0), 42)); @@ -44,7 +44,7 @@ mod pallet { vec![(10, vec![1]), (1_000, vec![2, 3, 4]), (2_000, vec![42])] ); - // when decreasing weight within the range of the current bag + // when decreasing score within the range of the current bag StakingMock::set_score_of(&42, 1_001); assert_ok!(BagsList::rebag(Origin::signed(0), 42)); @@ -54,7 +54,7 @@ mod pallet { vec![(10, vec![1]), (1_000, vec![2, 3, 4]), (2_000, vec![42])] ); - // when reducing weight to the level of a non-existent bag + // when reducing score to the level of a non-existent bag StakingMock::set_score_of(&42, 30); assert_ok!(BagsList::rebag(Origin::signed(0), 42)); @@ -64,7 +64,7 @@ mod pallet { vec![(10, vec![1]), (30, vec![42]), (1_000, vec![2, 3, 4])] ); - // when increasing weight to the level of a pre-existing bag + // when increasing score to the level of a pre-existing bag StakingMock::set_score_of(&42, 500); assert_ok!(BagsList::rebag(Origin::signed(0), 42)); @@ -380,7 +380,7 @@ mod pallet { // then assert_noop!( BagsList::put_in_front_of(Origin::signed(3), 2), - crate::pallet::Error::::NotHeavier + crate::pallet::Error::::List(ListError::NotHeavier) ); }); } @@ -394,7 +394,7 @@ mod pallet { // then assert_noop!( BagsList::put_in_front_of(Origin::signed(3), 4), - crate::pallet::Error::::NotHeavier + crate::pallet::Error::::List(ListError::NotHeavier) ); }); } @@ -411,7 +411,7 @@ mod pallet { // then assert_noop!( BagsList::put_in_front_of(Origin::signed(5), 4), - crate::pallet::Error::::IdNotFound + crate::pallet::Error::::List(ListError::NodeNotFound) ); }); @@ -425,7 +425,7 @@ mod pallet { // then assert_noop!( BagsList::put_in_front_of(Origin::signed(4), 5), - crate::pallet::Error::::IdNotFound + crate::pallet::Error::::List(ListError::NodeNotFound) ); }); } @@ -439,7 +439,7 @@ mod pallet { // then assert_noop!( BagsList::put_in_front_of(Origin::signed(4), 1), - crate::pallet::Error::::NotInSameBag + crate::pallet::Error::::List(ListError::NotInSameBag) ); }); } @@ -491,12 +491,12 @@ mod sorted_list_provider { assert_eq!(BagsList::count(), 5); // when removing - BagsList::on_remove(&201); + BagsList::on_remove(&201).unwrap(); // then the count goes down assert_eq!(BagsList::count(), 4); // when updating - BagsList::on_update(&201, VoteWeight::MAX); + assert_noop!(BagsList::on_update(&201, VoteWeight::MAX), ListError::NodeNotFound); // then the count stays the same assert_eq!(BagsList::count(), 4); }); @@ -554,8 +554,8 @@ mod sorted_list_provider { ); assert_eq!(BagsList::count(), 5); - // when increasing weight to the level of non-existent bag - BagsList::on_update(&42, 2_000); + // when increasing score to the level of non-existent bag + BagsList::on_update(&42, 2_000).unwrap(); // then the bag is created with the id in it, assert_eq!( @@ -565,8 +565,8 @@ mod sorted_list_provider { // and the id position is updated in the list. assert_eq!(BagsList::iter().collect::>(), vec![42, 2, 3, 4, 1]); - // when decreasing weight within the range of the current bag - BagsList::on_update(&42, 1_001); + // when decreasing score within the range of the current bag + BagsList::on_update(&42, 1_001).unwrap(); // then the id does not change bags, assert_eq!( @@ -576,8 +576,8 @@ mod sorted_list_provider { // or change position in the list. assert_eq!(BagsList::iter().collect::>(), vec![42, 2, 3, 4, 1]); - // when increasing weight to the level of a non-existent bag with the max threshold - BagsList::on_update(&42, VoteWeight::MAX); + // when increasing score to the level of a non-existent bag with the max threshold + BagsList::on_update(&42, VoteWeight::MAX).unwrap(); // the the new bag is created with the id in it, assert_eq!( @@ -587,8 +587,8 @@ mod sorted_list_provider { // and the id position is updated in the list. assert_eq!(BagsList::iter().collect::>(), vec![42, 2, 3, 4, 1]); - // when decreasing the weight to a pre-existing bag - BagsList::on_update(&42, 1_000); + // when decreasing the score to a pre-existing bag + BagsList::on_update(&42, 1_000).unwrap(); // then id is moved to the correct bag (as the last member), assert_eq!( @@ -615,10 +615,10 @@ mod sorted_list_provider { ExtBuilder::default().build_and_execute(|| { // it is a noop removing a non-existent id assert!(!ListNodes::::contains_key(42)); - assert_storage_noop!(BagsList::on_remove(&42)); + assert_noop!(BagsList::on_remove(&42), ListError::NodeNotFound); // when removing a node from a bag with multiple nodes - BagsList::on_remove(&2); + BagsList::on_remove(&2).unwrap(); // then assert_eq!(get_list_as_ids(), vec![3, 4, 1]); @@ -626,7 +626,7 @@ mod sorted_list_provider { ensure_left(2, 3); // when removing a node from a bag with only one node - BagsList::on_remove(&1); + BagsList::on_remove(&1).unwrap(); // then assert_eq!(get_list_as_ids(), vec![3, 4]); @@ -634,10 +634,10 @@ mod sorted_list_provider { ensure_left(1, 2); // when removing all remaining ids - BagsList::on_remove(&4); + BagsList::on_remove(&4).unwrap(); assert_eq!(get_list_as_ids(), vec![3]); ensure_left(4, 1); - BagsList::on_remove(&3); + BagsList::on_remove(&3).unwrap(); // then the storage is completely cleaned up assert_eq!(get_list_as_ids(), Vec::::new()); diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index 69bd3025fa768..af366d5b8f919 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -171,7 +171,7 @@ pub mod traits; #[cfg(feature = "std")] use codec::{Decode, Encode}; use frame_support::{weights::Weight, BoundedVec, RuntimeDebug}; -use sp_runtime::traits::Bounded; +use sp_runtime::traits::{Bounded, Saturating, Zero}; use sp_std::{fmt::Debug, prelude::*}; /// Re-export the solution generation macro. @@ -439,7 +439,7 @@ pub trait SortedListProvider { type Error: sp_std::fmt::Debug; /// The type used by the list to compare nodes for ordering. - type Score: Bounded; + type Score: Bounded + Saturating + Zero; /// An iterator over the list, which can have `take` called on it. fn iter() -> Box>; @@ -456,13 +456,21 @@ pub trait SortedListProvider { fn contains(id: &AccountId) -> bool; /// Hook for inserting a new id. + /// + /// Implementation should return an error if duplicate item is being inserted. fn on_insert(id: AccountId, score: Self::Score) -> Result<(), Self::Error>; /// Hook for updating a single id. - fn on_update(id: &AccountId, score: Self::Score); + /// + /// The `new` score is given. + /// + /// Returns `Ok(())` iff it successfully updates an item, an `Err(_)` otherwise. + fn on_update(id: &AccountId, score: Self::Score) -> Result<(), Self::Error>; /// Hook for removing am id from the list. - fn on_remove(id: &AccountId); + /// + /// Returns `Ok(())` iff it successfully removes an item, an `Err(_)` otherwise. + fn on_remove(id: &AccountId) -> Result<(), Self::Error>; /// Regenerate this list from scratch. Returns the count of items inserted. /// diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 0ac61e21fb323..34d7a176670f2 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -803,7 +803,7 @@ impl Pallet { pub fn do_remove_nominator(who: &T::AccountId) -> bool { let outcome = if Nominators::::contains_key(who) { Nominators::::remove(who); - T::VoterList::on_remove(who); + let _ = T::VoterList::on_remove(who).defensive(); true } else { false @@ -850,7 +850,7 @@ impl Pallet { pub fn do_remove_validator(who: &T::AccountId) -> bool { let outcome = if Validators::::contains_key(who) { Validators::::remove(who); - T::VoterList::on_remove(who); + let _ = T::VoterList::on_remove(who).defensive(); true } else { false @@ -1343,11 +1343,13 @@ impl SortedListProvider for UseNominatorsAndValidatorsM // nothing to do on insert. Ok(()) } - fn on_update(_: &T::AccountId, _weight: Self::Score) { + fn on_update(_: &T::AccountId, _weight: VoteWeight) -> Result<(), Self::Error> { // nothing to do on update. + Ok(()) } - fn on_remove(_: &T::AccountId) { + fn on_remove(_: &T::AccountId) -> Result<(), Self::Error> { // nothing to do on remove. + Ok(()) } fn unsafe_regenerate( _: impl IntoIterator, diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 117bc6e7c15bb..e53464195de23 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -22,8 +22,8 @@ use frame_support::{ dispatch::Codec, pallet_prelude::*, traits::{ - Currency, CurrencyToVote, DefensiveSaturating, EnsureOrigin, EstimateNextNewSession, Get, - LockIdentifier, LockableCurrency, OnUnbalanced, UnixTime, + Currency, CurrencyToVote, Defensive, DefensiveSaturating, EnsureOrigin, + EstimateNextNewSession, Get, LockIdentifier, LockableCurrency, OnUnbalanced, UnixTime, }, weights::Weight, }; @@ -858,7 +858,8 @@ pub mod pallet { Self::update_ledger(&controller, &ledger); // update this staker in the sorted list, if they exist in it. if T::VoterList::contains(&stash) { - T::VoterList::on_update(&stash, Self::weight_of(&ledger.stash)); + let _ = + T::VoterList::on_update(&stash, Self::weight_of(&ledger.stash)).defensive(); debug_assert_eq!(T::VoterList::sanity_check(), Ok(())); } @@ -941,7 +942,8 @@ pub mod pallet { // update this staker in the sorted list, if they exist in it. if T::VoterList::contains(&ledger.stash) { - T::VoterList::on_update(&ledger.stash, Self::weight_of(&ledger.stash)); + let _ = T::VoterList::on_update(&ledger.stash, Self::weight_of(&ledger.stash)) + .defensive(); } Self::deposit_event(Event::::Unbonded(ledger.stash, value)); @@ -1426,7 +1428,8 @@ pub mod pallet { // NOTE: ledger must be updated prior to calling `Self::weight_of`. Self::update_ledger(&controller, &ledger); if T::VoterList::contains(&ledger.stash) { - T::VoterList::on_update(&ledger.stash, Self::weight_of(&ledger.stash)); + let _ = T::VoterList::on_update(&ledger.stash, Self::weight_of(&ledger.stash)) + .defensive(); } let removed_chunks = 1u32 // for the case where the last iterated chunk is not removed From 8f98f5c7d339ff35fc904cbe5561bbcc0ca7f70c Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Wed, 4 May 2022 01:54:08 +0100 Subject: [PATCH 179/484] Update docs (#11313) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update docs * Update primitives/runtime/src/traits.rs * Update primitives/runtime/src/traits.rs Co-authored-by: Bastian Köcher --- primitives/runtime/src/traits.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/primitives/runtime/src/traits.rs b/primitives/runtime/src/traits.rs index dc13567df58db..9f706878bba49 100644 --- a/primitives/runtime/src/traits.rs +++ b/primitives/runtime/src/traits.rs @@ -874,12 +874,7 @@ pub trait SignedExtension: /// Do any pre-flight stuff for a signed transaction. /// - /// Note this function by default delegates to `validate`, so that - /// all checks performed for the transaction queue are also performed during - /// the dispatch phase (applying the extrinsic). - /// - /// If you ever override this function, you need to make sure to always - /// perform the same validation as in `validate`. + /// Make sure to perform the same checks as in [`Self::validate`]. fn pre_dispatch( self, who: &Self::AccountId, From d913c6590b3bb83fb807ffb4d592a3fe77f8ad6c Mon Sep 17 00:00:00 2001 From: Alexander Gryaznov Date: Wed, 4 May 2022 10:35:30 +0300 Subject: [PATCH 180/484] [contracts] stabilize `seal_code_hash`, `seal_set_code_hash` and `seal_own_code_hash` (#11337) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * stabilize seal_code_hash, seal_set_code_hash, seal_own_code_hash * fix missed place found by CI * Fixed missed __unstable__ Co-authored-by: Alexander Theißen --- frame/contracts/fixtures/set_code_hash.wat | 2 +- frame/contracts/src/benchmarking/mod.rs | 6 +++--- frame/contracts/src/tests.rs | 1 - frame/contracts/src/wasm/mod.rs | 9 +++------ frame/contracts/src/wasm/runtime.rs | 12 +++--------- 5 files changed, 10 insertions(+), 20 deletions(-) diff --git a/frame/contracts/fixtures/set_code_hash.wat b/frame/contracts/fixtures/set_code_hash.wat index 0a7b2e7cbedfa..b4df1b133186b 100644 --- a/frame/contracts/fixtures/set_code_hash.wat +++ b/frame/contracts/fixtures/set_code_hash.wat @@ -1,7 +1,7 @@ (module (import "seal0" "seal_input" (func $seal_input (param i32 i32))) (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) - (import "__unstable__" "seal_set_code_hash" (func $seal_set_code_hash (param i32) (result i32))) + (import "seal0" "seal_set_code_hash" (func $seal_set_code_hash (param i32) (result i32))) (import "env" "memory" (memory 1 1)) diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs index 84f4cb6083e5f..6411bcb2b589a 100644 --- a/frame/contracts/src/benchmarking/mod.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -448,7 +448,7 @@ benchmarks! { let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "__unstable__", + module: "seal0", name: "seal_code_hash", params: vec![ValueType::I32, ValueType::I32, ValueType::I32], return_type: Some(ValueType::I32), @@ -484,7 +484,7 @@ benchmarks! { seal_own_code_hash { let r in 0 .. API_BENCHMARK_BATCHES; let instance = Contract::::new(WasmModule::getter( - "__unstable__", "seal_own_code_hash", r * API_BENCHMARK_BATCH_SIZE + "seal0", "seal_own_code_hash", r * API_BENCHMARK_BATCH_SIZE ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) @@ -2017,7 +2017,7 @@ benchmarks! { let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "__unstable__", + module: "seal0", name: "seal_set_code_hash", params: vec![ ValueType::I32, diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index 9d949eea9b948..eaec4df698e5a 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -3088,7 +3088,6 @@ fn code_rejected_error_works() { } #[test] -#[cfg(feature = "unstable-interface")] fn set_code_hash() { let (wasm, code_hash) = compile_module::("set_code_hash").unwrap(); let (new_wasm, new_code_hash) = compile_module::("new_set_code_hash_contract").unwrap(); diff --git a/frame/contracts/src/wasm/mod.rs b/frame/contracts/src/wasm/mod.rs index f12df06b938c8..7257fbea6336a 100644 --- a/frame/contracts/src/wasm/mod.rs +++ b/frame/contracts/src/wasm/mod.rs @@ -2407,12 +2407,11 @@ mod tests { } #[test] - #[cfg(feature = "unstable-interface")] fn code_hash_works() { /// calls `seal_code_hash` and compares the result with the constant. const CODE_CODE_HASH: &str = r#" (module - (import "__unstable__" "seal_code_hash" (func $seal_code_hash (param i32 i32 i32) (result i32))) + (import "seal0" "seal_code_hash" (func $seal_code_hash (param i32 i32 i32) (result i32))) (import "env" "memory" (memory 1 1)) ;; size of our buffer is 32 bytes @@ -2460,12 +2459,11 @@ mod tests { } #[test] - #[cfg(feature = "unstable-interface")] fn own_code_hash_works() { /// calls `seal_own_code_hash` and compares the result with the constant. const CODE_OWN_CODE_HASH: &str = r#" (module - (import "__unstable__" "seal_own_code_hash" (func $seal_own_code_hash (param i32 i32))) + (import "seal0" "seal_own_code_hash" (func $seal_own_code_hash (param i32 i32))) (import "env" "memory" (memory 1 1)) ;; size of our buffer is 32 bytes @@ -2546,11 +2544,10 @@ mod tests { } #[test] - #[cfg(feature = "unstable-interface")] fn set_code_hash() { const CODE: &str = r#" (module - (import "__unstable__" "seal_set_code_hash" (func $seal_set_code_hash (param i32) (result i32))) + (import "seal0" "seal_set_code_hash" (func $seal_set_code_hash (param i32) (result i32))) (import "env" "memory" (memory 1 1)) (func $assert (param i32) (block $ok diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index 7719d512b8aba..a6f42ba64a49c 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -147,10 +147,8 @@ pub enum RuntimeCosts { /// Weight of calling `seal_is_contract`. IsContract, /// Weight of calling `seal_code_hash`. - #[cfg(feature = "unstable-interface")] CodeHash, /// Weight of calling `seal_own_code_hash`. - #[cfg(feature = "unstable-interface")] OwnCodeHash, /// Weight of calling `seal_caller_is_origin`. CallerIsOrigin, @@ -225,7 +223,6 @@ pub enum RuntimeCosts { #[cfg(feature = "unstable-interface")] CallRuntime(Weight), /// Weight of calling `seal_set_code_hash` - #[cfg(feature = "unstable-interface")] SetCodeHash, /// Weight of calling `ecdsa_to_eth_address` #[cfg(feature = "unstable-interface")] @@ -245,9 +242,7 @@ impl RuntimeCosts { CopyToContract(len) => s.input_per_byte.saturating_mul(len.into()), Caller => s.caller, IsContract => s.is_contract, - #[cfg(feature = "unstable-interface")] CodeHash => s.code_hash, - #[cfg(feature = "unstable-interface")] OwnCodeHash => s.own_code_hash, CallerIsOrigin => s.caller_is_origin, Address => s.address, @@ -312,7 +307,6 @@ impl RuntimeCosts { #[cfg(feature = "unstable-interface")] CallRuntime(weight) => weight, - #[cfg(feature = "unstable-interface")] SetCodeHash => s.set_code_hash, #[cfg(feature = "unstable-interface")] EcdsaToEthAddress => s.ecdsa_to_eth_address, @@ -1401,7 +1395,7 @@ define_env!(Env, , // # Errors // // `ReturnCode::KeyNotFound` - [__unstable__] seal_code_hash(ctx, account_ptr: u32, out_ptr: u32, out_len_ptr: u32) -> ReturnCode => { + [seal0] seal_code_hash(ctx, account_ptr: u32, out_ptr: u32, out_len_ptr: u32) -> ReturnCode => { ctx.charge_gas(RuntimeCosts::CodeHash)?; let address: <::T as frame_system::Config>::AccountId = ctx.read_sandbox_memory_as(account_ptr)?; @@ -1420,7 +1414,7 @@ define_env!(Env, , // - `out_ptr`: pointer to the linear memory where the returning value is written to. // - `out_len_ptr`: in-out pointer into linear memory where the buffer length // is read from and the value length is written to. - [__unstable__] seal_own_code_hash(ctx, out_ptr: u32, out_len_ptr: u32) => { + [seal0] seal_own_code_hash(ctx, out_ptr: u32, out_len_ptr: u32) => { ctx.charge_gas(RuntimeCosts::OwnCodeHash)?; let code_hash_encoded = &ctx.ext.own_code_hash().encode(); Ok(ctx.write_sandbox_output(out_ptr, out_len_ptr, code_hash_encoded, false, already_charged)?) @@ -2048,7 +2042,7 @@ define_env!(Env, , // # Errors // // `ReturnCode::CodeNotFound` - [__unstable__] seal_set_code_hash(ctx, code_hash_ptr: u32) -> ReturnCode => { + [seal0] seal_set_code_hash(ctx, code_hash_ptr: u32) -> ReturnCode => { ctx.charge_gas(RuntimeCosts::SetCodeHash)?; let code_hash: CodeHash<::T> = ctx.read_sandbox_memory_as(code_hash_ptr)?; match ctx.ext.set_code_hash(code_hash) { From 05274b59d1dc8dd9d4a39266efb60ddb0c9cf73f Mon Sep 17 00:00:00 2001 From: Adrian Catangiu Date: Wed, 4 May 2022 12:36:52 +0300 Subject: [PATCH 181/484] BEEFY voter bugfixes (#11335) * beefy: gadget should always use current validator set The gadget/client-voter was using previous' session validator set to sign the 1st block in the new session (to have chained validator set handoffs). This is not necessary because: 1. BEEFY piggy-backs on GRANDPA and only works on canonical chain, so it need not concern itself with the validity of the block header (which contains digest with the new session's validator set). It can safely assume header is valid and simply use new validator set. 2. The BEEFY payload itself already contains a merkle root for the next validator set keys. So at the BEEFY-payload layer we already have a validated/trusted hand-off of authority. Signed-off-by: acatangiu * beefy: buffer votes for not yet finalized blocks Signed-off-by: acatangiu * beefy: add buffered votes regression test --- client/beefy/src/round.rs | 52 +++++-------------- client/beefy/src/tests.rs | 32 +++++++++--- client/beefy/src/worker.rs | 101 ++++++++++++++++++++++++++----------- 3 files changed, 112 insertions(+), 73 deletions(-) diff --git a/client/beefy/src/round.rs b/client/beefy/src/round.rs index a5a15bac5f8f9..fecb9557df6ea 100644 --- a/client/beefy/src/round.rs +++ b/client/beefy/src/round.rs @@ -73,7 +73,6 @@ pub(crate) struct Rounds { best_done: Option>, session_start: NumberFor, validator_set: ValidatorSet, - prev_validator_set: ValidatorSet, } impl Rounds @@ -81,18 +80,8 @@ where P: Ord + Hash + Clone, B: Block, { - pub(crate) fn new( - session_start: NumberFor, - validator_set: ValidatorSet, - prev_validator_set: ValidatorSet, - ) -> Self { - Rounds { - rounds: BTreeMap::new(), - best_done: None, - session_start, - validator_set, - prev_validator_set, - } + pub(crate) fn new(session_start: NumberFor, validator_set: ValidatorSet) -> Self { + Rounds { rounds: BTreeMap::new(), best_done: None, session_start, validator_set } } } @@ -101,24 +90,12 @@ where P: Ord + Hash + Clone, B: Block, { - pub(crate) fn validator_set_id_for(&self, block_number: NumberFor) -> ValidatorSetId { - if block_number > self.session_start { - self.validator_set.id() - } else { - self.prev_validator_set.id() - } - } - - pub(crate) fn validators_for(&self, block_number: NumberFor) -> &[Public] { - if block_number > self.session_start { - self.validator_set.validators() - } else { - self.prev_validator_set.validators() - } + pub(crate) fn validator_set_id(&self) -> ValidatorSetId { + self.validator_set.id() } - pub(crate) fn validator_set(&self) -> &ValidatorSet { - &self.validator_set + pub(crate) fn validators(&self) -> &[Public] { + self.validator_set.validators() } pub(crate) fn session_start(&self) -> &NumberFor { @@ -143,7 +120,7 @@ where round.1 ); false - } else if !self.validator_set.validators().iter().any(|id| vote.0 == *id) { + } else if !self.validators().iter().any(|id| vote.0 == *id) { debug!( target: "beefy", "🥩 received vote {:?} from validator that is not in the validator set, ignoring", @@ -170,12 +147,11 @@ where // remove this and older (now stale) rounds let signatures = self.rounds.remove(round)?.votes; self.rounds.retain(|&(_, number), _| number > round.1); - self.best_done = self.best_done.clone().max(Some(round.1.clone())); + self.best_done = self.best_done.max(Some(round.1)); debug!(target: "beefy", "🥩 Concluded round #{}", round.1); Some( - self.validator_set - .validators() + self.validators() .iter() .map(|authority_id| signatures.get(authority_id).cloned()) .collect(), @@ -247,13 +223,13 @@ mod tests { .unwrap(); let session_start = 1u64.into(); - let rounds = Rounds::::new(session_start, validators.clone(), validators); + let rounds = Rounds::::new(session_start, validators); - assert_eq!(42, rounds.validator_set_id_for(session_start)); + assert_eq!(42, rounds.validator_set_id()); assert_eq!(1, *rounds.session_start()); assert_eq!( &vec![Keyring::Alice.public(), Keyring::Bob.public(), Keyring::Charlie.public()], - rounds.validators_for(session_start) + rounds.validators() ); } @@ -274,7 +250,7 @@ mod tests { let round = (H256::from_low_u64_le(1), 1); let session_start = 1u64.into(); - let mut rounds = Rounds::::new(session_start, validators.clone(), validators); + let mut rounds = Rounds::::new(session_start, validators); // no self vote yet, should self vote assert!(rounds.should_self_vote(&round)); @@ -347,7 +323,7 @@ mod tests { .unwrap(); let session_start = 1u64.into(); - let mut rounds = Rounds::::new(session_start, validators.clone(), validators); + let mut rounds = Rounds::::new(session_start, validators); // round 1 assert!(rounds.add_vote( diff --git a/client/beefy/src/tests.rs b/client/beefy/src/tests.rs index e568daba8e112..5f5fbd2f1ff1f 100644 --- a/client/beefy/src/tests.rs +++ b/client/beefy/src/tests.rs @@ -469,8 +469,8 @@ fn finalize_block_and_wait_for_beefy( } if expected_beefy.is_empty() { - // run for 1 second then verify no new best beefy block available - let timeout = Some(Duration::from_millis(500)); + // run for quarter second then verify no new best beefy block available + let timeout = Some(Duration::from_millis(250)); streams_empty_after_timeout(best_blocks, &net, runtime, timeout); streams_empty_after_timeout(signed_commitments, &net, runtime, None); } else { @@ -535,8 +535,8 @@ fn lagging_validators() { let beefy_peers = peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect(); runtime.spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta)); - // push 42 blocks including `AuthorityChange` digests every 30 blocks. - net.generate_blocks(42, session_len, &validator_set, true); + // push 62 blocks including `AuthorityChange` digests every 30 blocks. + net.generate_blocks(62, session_len, &validator_set, true); net.block_until_sync(); let net = Arc::new(Mutex::new(net)); @@ -550,7 +550,7 @@ fn lagging_validators() { let (best_blocks, signed_commitments) = get_beefy_streams(&mut *net.lock(), peers); net.lock().peer(0).client().as_client().finalize_block(finalize, None).unwrap(); // verify nothing gets finalized by BEEFY - let timeout = Some(Duration::from_millis(500)); + let timeout = Some(Duration::from_millis(250)); streams_empty_after_timeout(best_blocks, &net, &mut runtime, timeout); streams_empty_after_timeout(signed_commitments, &net, &mut runtime, None); @@ -563,6 +563,26 @@ fn lagging_validators() { // Both finalize #30 (mandatory session) and #32 -> BEEFY finalize #30 (mandatory), #31, #32 finalize_block_and_wait_for_beefy(&net, peers, &mut runtime, &[30, 32], &[30, 31, 32]); + + // Verify that session-boundary votes get buffered by client and only processed once + // session-boundary block is GRANDPA-finalized (this guarantees authenticity for the new session + // validator set). + + // Alice finalizes session-boundary mandatory block #60, Bob lags behind + let (best_blocks, signed_commitments) = get_beefy_streams(&mut *net.lock(), peers); + let finalize = BlockId::number(60); + net.lock().peer(0).client().as_client().finalize_block(finalize, None).unwrap(); + // verify nothing gets finalized by BEEFY + let timeout = Some(Duration::from_millis(250)); + streams_empty_after_timeout(best_blocks, &net, &mut runtime, timeout); + streams_empty_after_timeout(signed_commitments, &net, &mut runtime, None); + + // Bob catches up and also finalizes #60 (and should have buffered Alice's vote on #60) + let (best_blocks, signed_commitments) = get_beefy_streams(&mut *net.lock(), peers); + net.lock().peer(1).client().as_client().finalize_block(finalize, None).unwrap(); + // verify beefy skips intermediary votes, and successfully finalizes mandatory block #40 + wait_for_best_beefy_blocks(best_blocks, &net, &mut runtime, &[60]); + wait_for_beefy_signed_commitments(signed_commitments, &net, &mut runtime, &[60]); } #[test] @@ -624,7 +644,7 @@ fn correct_beefy_payload() { .unwrap(); // verify consensus is _not_ reached - let timeout = Some(Duration::from_millis(500)); + let timeout = Some(Duration::from_millis(250)); streams_empty_after_timeout(best_blocks, &net, &mut runtime, timeout); streams_empty_after_timeout(signed_commitments, &net, &mut runtime, None); diff --git a/client/beefy/src/worker.rs b/client/beefy/src/worker.rs index 8ab18c58f9dd3..ae466a71abb57 100644 --- a/client/beefy/src/worker.rs +++ b/client/beefy/src/worker.rs @@ -16,7 +16,13 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use std::{collections::BTreeSet, fmt::Debug, marker::PhantomData, sync::Arc, time::Duration}; +use std::{ + collections::{BTreeMap, BTreeSet}, + fmt::Debug, + marker::PhantomData, + sync::Arc, + time::Duration, +}; use codec::{Codec, Decode, Encode}; use futures::{future, FutureExt, StreamExt}; @@ -27,7 +33,7 @@ use sc_client_api::{Backend, FinalityNotification, FinalityNotifications}; use sc_network_gossip::GossipEngine; use sp_api::{BlockId, ProvideRuntimeApi}; -use sp_arithmetic::traits::AtLeast32Bit; +use sp_arithmetic::traits::{AtLeast32Bit, Saturating}; use sp_consensus::SyncOracle; use sp_mmr_primitives::MmrApi; use sp_runtime::{ @@ -80,6 +86,8 @@ pub(crate) struct BeefyWorker { min_block_delta: u32, metrics: Option, rounds: Option>, + /// Buffer holding votes for blocks that the client hasn't seen finality for. + pending_votes: BTreeMap, Vec, AuthorityId, Signature>>>, finality_notifications: FinalityNotifications, /// Best block we received a GRANDPA notification for best_grandpa_block_header: ::Header, @@ -141,6 +149,7 @@ where min_block_delta: min_block_delta.max(1), metrics, rounds: None, + pending_votes: BTreeMap::new(), finality_notifications: client.finality_notification_stream(), best_grandpa_block_header: last_finalized_header, best_beefy_block: None, @@ -238,7 +247,11 @@ where } /// Handle session changes by starting new voting round for mandatory blocks. - fn init_session_at(&mut self, active: ValidatorSet, session_start: NumberFor) { + fn init_session_at( + &mut self, + active: ValidatorSet, + new_session_start: NumberFor, + ) { debug!(target: "beefy", "🥩 New active validator set: {:?}", active); metric_set!(self, beefy_validator_set_id, active.id()); // BEEFY should produce a signed commitment for each session @@ -246,23 +259,22 @@ where active.id() != GENESIS_AUTHORITY_SET_ID && self.last_signed_id != 0 { + debug!( + target: "beefy", "🥩 Detected skipped session: active-id {:?}, last-signed-id {:?}", + active.id(), + self.last_signed_id, + ); metric_inc!(self, beefy_skipped_sessions); } if log_enabled!(target: "beefy", log::Level::Debug) { // verify the new validator set - only do it if we're also logging the warning - let _ = self.verify_validator_set(&session_start, &active); + let _ = self.verify_validator_set(&new_session_start, &active); } - let prev_validator_set = if let Some(r) = &self.rounds { - r.validator_set().clone() - } else { - // no previous rounds present use new validator set instead (genesis case) - active.clone() - }; let id = active.id(); - self.rounds = Some(Rounds::new(session_start, active, prev_validator_set)); - info!(target: "beefy", "🥩 New Rounds for validator set id: {:?} with session_start {:?}", id, session_start); + self.rounds = Some(Rounds::new(new_session_start, active)); + info!(target: "beefy", "🥩 New Rounds for validator set id: {:?} with session_start {:?}", id, new_session_start); } fn handle_finality_notification(&mut self, notification: &FinalityNotification) { @@ -287,12 +299,36 @@ where self.init_session_at(new_validator_set, *header.number()); } + // Handle any pending votes for now finalized blocks. + self.check_pending_votes(); + // Vote if there's now a new vote target. if let Some(target_number) = self.current_vote_target() { self.do_vote(target_number); } } + // Handles all buffered votes for now finalized blocks. + fn check_pending_votes(&mut self) { + let not_finalized = self.best_grandpa_block_header.number().saturating_add(1u32.into()); + let still_pending = self.pending_votes.split_off(¬_finalized); + let votes_to_handle = std::mem::replace(&mut self.pending_votes, still_pending); + for (num, votes) in votes_to_handle.into_iter() { + if Some(num) > self.best_beefy_block { + debug!(target: "beefy", "🥩 Handling buffered votes for now GRANDPA finalized block: {:?}.", num); + for v in votes.into_iter() { + self.handle_vote( + (v.commitment.payload, v.commitment.block_number), + (v.id, v.signature), + false, + ); + } + } else { + debug!(target: "beefy", "🥩 Dropping outdated buffered votes for now BEEFY finalized block: {:?}.", num); + } + } + } + fn handle_vote( &mut self, round: (Payload, NumberFor), @@ -313,7 +349,7 @@ where self.gossip_validator.conclude_round(round.1); // id is stored for skipped session metric calculation - self.last_signed_id = rounds.validator_set_id_for(round.1); + self.last_signed_id = rounds.validator_set_id(); let block_num = round.1; let commitment = Commitment { @@ -390,7 +426,7 @@ where debug!(target: "beefy", "🥩 Don't double vote for block number: {:?}", target_number); return } - (rounds.validators_for(target_number), rounds.validator_set_id_for(target_number)) + (rounds.validators(), rounds.validator_set_id()) } else { debug!(target: "beefy", "🥩 Missing validator set - can't vote for: {:?}", target_hash); return @@ -506,11 +542,23 @@ where }, vote = votes.next().fuse() => { if let Some(vote) = vote { - self.handle_vote( - (vote.commitment.payload, vote.commitment.block_number), - (vote.id, vote.signature), - false - ); + let block_num = vote.commitment.block_number; + if block_num > *self.best_grandpa_block_header.number() { + // Only handle votes for blocks we _know_ have been finalized. + // Buffer vote to be handled later. + debug!( + target: "beefy", + "🥩 Buffering vote for not (yet) finalized block: {:?}.", + block_num + ); + self.pending_votes.entry(block_num).or_default().push(vote); + } else { + self.handle_vote( + (vote.commitment.payload, vote.commitment.block_number), + (vote.id, vote.signature), + false + ); + } } else { return; } @@ -854,8 +902,7 @@ pub(crate) mod tests { worker.best_grandpa_block_header = grandpa_header; worker.best_beefy_block = best_beefy; worker.min_block_delta = min_delta; - worker.rounds = - Some(Rounds::new(session_start, validator_set.clone(), validator_set.clone())); + worker.rounds = Some(Rounds::new(session_start, validator_set.clone())); }; // under min delta @@ -970,11 +1017,10 @@ pub(crate) mod tests { worker.init_session_at(validator_set.clone(), 1); let worker_rounds = worker.rounds.as_ref().unwrap(); - assert_eq!(worker_rounds.validator_set(), &validator_set); assert_eq!(worker_rounds.session_start(), &1); // in genesis case both current and prev validator sets are the same - assert_eq!(worker_rounds.validator_set_id_for(1), validator_set.id()); - assert_eq!(worker_rounds.validator_set_id_for(2), validator_set.id()); + assert_eq!(worker_rounds.validators(), validator_set.validators()); + assert_eq!(worker_rounds.validator_set_id(), validator_set.id()); // new validator set let keys = &[Keyring::Bob]; @@ -984,11 +1030,8 @@ pub(crate) mod tests { worker.init_session_at(new_validator_set.clone(), 11); let worker_rounds = worker.rounds.as_ref().unwrap(); - assert_eq!(worker_rounds.validator_set(), &new_validator_set); assert_eq!(worker_rounds.session_start(), &11); - // mandatory block gets prev set, further blocks get new set - assert_eq!(worker_rounds.validator_set_id_for(11), validator_set.id()); - assert_eq!(worker_rounds.validator_set_id_for(12), new_validator_set.id()); - assert_eq!(worker_rounds.validator_set_id_for(13), new_validator_set.id()); + assert_eq!(worker_rounds.validators(), new_validator_set.validators()); + assert_eq!(worker_rounds.validator_set_id(), new_validator_set.id()); } } From 12f3fb1c3dfa4f4383dd5d53dfbe59b30ee5b044 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Wed, 4 May 2022 12:15:11 +0200 Subject: [PATCH 182/484] sc-network: Do not return error on peer id only reserved nodes (#11346) When passing reserved nodes only with a peer id it was failing with the `DuplicateBootnode` error. Besides that there are some clean ups. We for example added the bootnodes twice to the `known_addresses`. --- client/network/src/protocol.rs | 6 ++++-- client/network/src/service.rs | 18 +++++++++++------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/client/network/src/protocol.rs b/client/network/src/protocol.rs index 5db8f102d037b..387a7b3fdde90 100644 --- a/client/network/src/protocol.rs +++ b/client/network/src/protocol.rs @@ -356,13 +356,15 @@ where let mut default_sets_reserved = HashSet::new(); for reserved in network_config.default_peers_set.reserved_nodes.iter() { default_sets_reserved.insert(reserved.peer_id); - known_addresses.push((reserved.peer_id, reserved.multiaddr.clone())); + + if !reserved.multiaddr.is_empty() { + known_addresses.push((reserved.peer_id, reserved.multiaddr.clone())); + } } let mut bootnodes = Vec::with_capacity(network_config.boot_nodes.len()); for bootnode in network_config.boot_nodes.iter() { bootnodes.push(bootnode.peer_id); - known_addresses.push((bootnode.peer_id, bootnode.multiaddr.clone())); } // Set number 0 is used for block announces. diff --git a/client/network/src/service.rs b/client/network/src/service.rs index d2600e3295bf0..edd30e9c9dee4 100644 --- a/client/network/src/service.rs +++ b/client/network/src/service.rs @@ -228,12 +228,10 @@ where )?; // List of multiaddresses that we know in the network. - let mut bootnodes = Vec::new(); let mut boot_node_ids = HashSet::new(); // Process the bootnodes. for bootnode in params.network_config.boot_nodes.iter() { - bootnodes.push(bootnode.peer_id); boot_node_ids.insert(bootnode.peer_id); known_addresses.push((bootnode.peer_id, bootnode.multiaddr.clone())); } @@ -241,12 +239,18 @@ where let boot_node_ids = Arc::new(boot_node_ids); // Check for duplicate bootnodes. - known_addresses.iter().try_for_each(|(peer_id, addr)| { - if let Some(other) = known_addresses.iter().find(|o| o.1 == *addr && o.0 != *peer_id) { + params.network_config.boot_nodes.iter().try_for_each(|bootnode| { + if let Some(other) = params + .network_config + .boot_nodes + .iter() + .filter(|o| o.multiaddr == bootnode.multiaddr) + .find(|o| o.peer_id != bootnode.peer_id) + { Err(Error::DuplicateBootnode { - address: addr.clone(), - first_id: *peer_id, - second_id: other.0, + address: bootnode.multiaddr.clone(), + first_id: bootnode.peer_id, + second_id: other.peer_id, }) } else { Ok(()) From 7fd7de4e1a96f2947d39a19434ab3003271495c6 Mon Sep 17 00:00:00 2001 From: Web3 Smith <31099392+Wizdave97@users.noreply.github.com> Date: Wed, 4 May 2022 11:40:11 +0100 Subject: [PATCH 183/484] Update MMR Runtime API with functionality to generate MMR proof for a series of leaf indices (#10635) * updated mmr rpc api with functions for batch generation of proof * update code comments * fix build errors * added tests to mmr-rpc * add tests to pallet-mmr * update comments * minor comment fix * remove unused variables * fix rust doc errors * refactor mmr runtime api * fix tests * minor fix * minor fix * fix node-runtime * revert to initial api * impl from proof fot batchproof * minor fix * minor fix * use explicit functions to convert btw batch proof and single proof * minor fix * add new variant to mmr error * fmt * update conversion to single leaf proof * fix style nit Co-authored-by: Adrian Catangiu --- bin/node/runtime/src/lib.rs | 38 +++++- client/beefy/src/tests.rs | 20 ++- frame/merkle-mountain-range/rpc/Cargo.toml | 2 +- frame/merkle-mountain-range/rpc/src/lib.rs | 118 +++++++++++++++++- frame/merkle-mountain-range/src/lib.rs | 54 ++++---- frame/merkle-mountain-range/src/mmr/mmr.rs | 86 +++++++++---- frame/merkle-mountain-range/src/mmr/mod.rs | 2 +- frame/merkle-mountain-range/src/tests.rs | 131 +++++++++++++++++--- primitives/merkle-mountain-range/src/lib.rs | 59 ++++++++- 9 files changed, 429 insertions(+), 81 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 8c999bb76fc14..660ba7ab86229 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1832,8 +1832,12 @@ impl_runtime_apis! { fn generate_proof(leaf_index: pallet_mmr::primitives::LeafIndex) -> Result<(mmr::EncodableOpaqueLeaf, mmr::Proof), mmr::Error> { - Mmr::generate_proof(leaf_index) - .map(|(leaf, proof)| (mmr::EncodableOpaqueLeaf::from_leaf(&leaf), proof)) + Mmr::generate_batch_proof(vec![leaf_index]).and_then(|(leaves, proof)| + Ok(( + mmr::EncodableOpaqueLeaf::from_leaf(&leaves[0]), + mmr::BatchProof::into_single_leaf_proof(proof)? + )) + ) } fn verify_proof(leaf: mmr::EncodableOpaqueLeaf, proof: mmr::Proof) @@ -1843,7 +1847,7 @@ impl_runtime_apis! { .into_opaque_leaf() .try_decode() .ok_or(mmr::Error::Verify)?; - Mmr::verify_leaf(leaf, proof) + Mmr::verify_leaves(vec![leaf], mmr::Proof::into_batch_proof(proof)) } fn verify_proof_stateless( @@ -1852,12 +1856,38 @@ impl_runtime_apis! { proof: mmr::Proof ) -> Result<(), mmr::Error> { let node = mmr::DataOrHash::Data(leaf.into_opaque_leaf()); - pallet_mmr::verify_leaf_proof::(root, node, proof) + pallet_mmr::verify_leaves_proof::(root, vec![node], mmr::Proof::into_batch_proof(proof)) } fn mmr_root() -> Result { Ok(Mmr::mmr_root()) } + + fn generate_batch_proof(leaf_indices: Vec) + -> Result<(Vec, mmr::BatchProof), mmr::Error> + { + Mmr::generate_batch_proof(leaf_indices) + .map(|(leaves, proof)| (leaves.into_iter().map(|leaf| mmr::EncodableOpaqueLeaf::from_leaf(&leaf)).collect(), proof)) + } + + fn verify_batch_proof(leaves: Vec, proof: mmr::BatchProof) + -> Result<(), mmr::Error> + { + let leaves = leaves.into_iter().map(|leaf| + leaf.into_opaque_leaf() + .try_decode() + .ok_or(mmr::Error::Verify)).collect::, mmr::Error>>()?; + Mmr::verify_leaves(leaves, proof) + } + + fn verify_batch_proof_stateless( + root: mmr::Hash, + leaves: Vec, + proof: mmr::BatchProof + ) -> Result<(), mmr::Error> { + let nodes = leaves.into_iter().map(|leaf|mmr::DataOrHash::Data(leaf.into_opaque_leaf())).collect(); + pallet_mmr::verify_leaves_proof::(root, nodes, proof) + } } impl sp_session::SessionKeys for Runtime { diff --git a/client/beefy/src/tests.rs b/client/beefy/src/tests.rs index 5f5fbd2f1ff1f..1d035a6a447c2 100644 --- a/client/beefy/src/tests.rs +++ b/client/beefy/src/tests.rs @@ -39,7 +39,9 @@ use beefy_primitives::{ crypto::AuthorityId, BeefyApi, ConsensusLog, MmrRootHash, ValidatorSet, BEEFY_ENGINE_ID, KEY_TYPE as BeefyKeyType, }; -use sp_mmr_primitives::{EncodableOpaqueLeaf, Error as MmrError, LeafIndex, MmrApi, Proof}; +use sp_mmr_primitives::{ + BatchProof, EncodableOpaqueLeaf, Error as MmrError, LeafIndex, MmrApi, Proof, +}; use sp_api::{ApiRef, ProvideRuntimeApi}; use sp_consensus::BlockOrigin; @@ -259,6 +261,22 @@ macro_rules! create_test_api { fn mmr_root() -> Result { Ok($mmr_root) } + + fn generate_batch_proof(_leaf_indices: Vec) -> Result<(Vec, BatchProof), MmrError> { + unimplemented!() + } + + fn verify_batch_proof(_leaves: Vec, _proof: BatchProof) -> Result<(), MmrError> { + unimplemented!() + } + + fn verify_batch_proof_stateless( + _root: MmrRootHash, + _leaves: Vec, + _proof: BatchProof + ) -> Result<(), MmrError> { + unimplemented!() + } } } } diff --git a/frame/merkle-mountain-range/rpc/Cargo.toml b/frame/merkle-mountain-range/rpc/Cargo.toml index 94c895ea91517..359ee88a9c485 100644 --- a/frame/merkle-mountain-range/rpc/Cargo.toml +++ b/frame/merkle-mountain-range/rpc/Cargo.toml @@ -22,7 +22,7 @@ serde = { version = "1.0.136", features = ["derive"] } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } sp-core = { version = "6.0.0", path = "../../../primitives/core" } -sp-mmr-primitives = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/merkle-mountain-range" } +sp-mmr-primitives = { version = "4.0.0-dev", path = "../../../primitives/merkle-mountain-range" } sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } [dev-dependencies] diff --git a/frame/merkle-mountain-range/rpc/src/lib.rs b/frame/merkle-mountain-range/rpc/src/lib.rs index 99359bfea8eb6..be1a74450d1f4 100644 --- a/frame/merkle-mountain-range/rpc/src/lib.rs +++ b/frame/merkle-mountain-range/rpc/src/lib.rs @@ -29,7 +29,7 @@ use serde::{Deserialize, Serialize}; use sp_api::ProvideRuntimeApi; use sp_blockchain::HeaderBackend; use sp_core::Bytes; -use sp_mmr_primitives::{Error as MmrError, LeafIndex, Proof}; +use sp_mmr_primitives::{BatchProof, Error as MmrError, LeafIndex, Proof}; use sp_runtime::{generic::BlockId, traits::Block as BlockT}; pub use sp_mmr_primitives::MmrApi as MmrRuntimeApi; @@ -57,6 +57,34 @@ impl LeafProof { } } +/// Retrieved MMR leaves and their proof. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct LeafBatchProof { + /// Block hash the proof was generated for. + pub block_hash: BlockHash, + /// SCALE-encoded vector of `LeafData`. + pub leaves: Bytes, + /// SCALE-encoded proof data. See [sp_mmr_primitives::BatchProof]. + pub proof: Bytes, +} + +impl LeafBatchProof { + /// Create new `LeafBatchProof` from a given vector of `Leaf` and a + /// [sp_mmr_primitives::BatchProof]. + pub fn new( + block_hash: BlockHash, + leaves: Vec, + proof: BatchProof, + ) -> Self + where + Leaf: Encode, + MmrHash: Encode, + { + Self { block_hash, leaves: Bytes(leaves.encode()), proof: Bytes(proof.encode()) } + } +} + /// MMR RPC methods. #[rpc] pub trait MmrApi { @@ -74,6 +102,23 @@ pub trait MmrApi { leaf_index: LeafIndex, at: Option, ) -> Result>; + + /// Generate MMR proof for the given leaf indices. + /// + /// This method calls into a runtime with MMR pallet included and attempts to generate + /// MMR proof for a set of leaves at the given `leaf_indices`. + /// Optionally, a block hash at which the runtime should be queried can be specified. + /// + /// Returns the leaves and a proof for these leaves (compact encoding, i.e. hash of + /// the leaves). Both parameters are SCALE-encoded. + /// The order of entries in the `leaves` field of the returned struct + /// is the same as the order of the entries in `leaf_indices` supplied + #[rpc(name = "mmr_generateBatchProof")] + fn generate_batch_proof( + &self, + leaf_indices: Vec, + at: Option, + ) -> Result>; } /// An implementation of MMR specific RPC methods. @@ -117,6 +162,28 @@ where Ok(LeafProof::new(block_hash, leaf, proof)) } + + fn generate_batch_proof( + &self, + leaf_indices: Vec, + at: Option<::Hash>, + ) -> Result::Hash>> { + let api = self.client.runtime_api(); + let block_hash = at.unwrap_or_else(|| + // If the block hash is not supplied assume the best block. + self.client.info().best_hash); + + let (leaves, proof) = api + .generate_batch_proof_with_context( + &BlockId::hash(block_hash), + sp_core::ExecutionContext::OffchainCall(None), + leaf_indices, + ) + .map_err(runtime_error_into_rpc_error)? + .map_err(mmr_error_into_rpc_error)?; + + Ok(LeafBatchProof::new(block_hash, leaves, proof)) + } } const RUNTIME_ERROR: i64 = 8000; @@ -179,6 +246,28 @@ mod tests { ); } + #[test] + fn should_serialize_leaf_batch_proof() { + // given + let leaf = vec![1_u8, 2, 3, 4]; + let proof = BatchProof { + leaf_indices: vec![1], + leaf_count: 9, + items: vec![H256::repeat_byte(1), H256::repeat_byte(2)], + }; + + let leaf_proof = LeafBatchProof::new(H256::repeat_byte(0), vec![leaf], proof); + + // when + let actual = serde_json::to_string(&leaf_proof).unwrap(); + + // then + assert_eq!( + actual, + r#"{"blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000","leaves":"0x041001020304","proof":"0x04010000000000000009000000000000000801010101010101010101010101010101010101010101010101010101010101010202020202020202020202020202020202020202020202020202020202020202"}"# + ); + } + #[test] fn should_deserialize_leaf_proof() { // given @@ -205,4 +294,31 @@ mod tests { // then assert_eq!(actual, expected); } + + #[test] + fn should_deserialize_leaf_batch_proof() { + // given + let expected = LeafBatchProof { + block_hash: H256::repeat_byte(0), + leaves: Bytes(vec![vec![1_u8, 2, 3, 4]].encode()), + proof: Bytes( + BatchProof { + leaf_indices: vec![1], + leaf_count: 9, + items: vec![H256::repeat_byte(1), H256::repeat_byte(2)], + } + .encode(), + ), + }; + + // when + let actual: LeafBatchProof = serde_json::from_str(r#"{ + "blockHash":"0x0000000000000000000000000000000000000000000000000000000000000000", + "leaves":"0x041001020304", + "proof":"0x04010000000000000009000000000000000801010101010101010101010101010101010101010101010101010101010101010202020202020202020202020202020202020202020202020202020202020202" + }"#).unwrap(); + + // then + assert_eq!(actual, expected); + } } diff --git a/frame/merkle-mountain-range/src/lib.rs b/frame/merkle-mountain-range/src/lib.rs index 855eb0a7436dc..d6cf3240692fc 100644 --- a/frame/merkle-mountain-range/src/lib.rs +++ b/frame/merkle-mountain-range/src/lib.rs @@ -71,6 +71,7 @@ mod tests; pub use pallet::*; pub use sp_mmr_primitives::{self as primitives, Error, LeafDataProvider, LeafIndex, NodeIndex}; +use sp_std::prelude::*; /// The most common use case for MMRs is to store historical block hashes, /// so that any point in time in the future we can receive a proof about some past @@ -228,22 +229,23 @@ type LeafOf = <>::LeafData as primitives::LeafDataProvider> /// Hashing used for the pallet. pub(crate) type HashingOf = >::Hashing; -/// Stateless MMR proof verification. +/// Stateless MMR proof verification for batch of leaves. /// -/// This function can be used to verify received MMR proof (`proof`) -/// for given leaf data (`leaf`) against a known MMR root hash (`root`). -/// -/// The verification does not require any storage access. -pub fn verify_leaf_proof( +/// This function can be used to verify received MMR [primitives::BatchProof] (`proof`) +/// for given leaves set (`leaves`) against a known MMR root hash (`root`). +/// Note, the leaves should be sorted such that corresponding leaves and leaf indices have the +/// same position in both the `leaves` vector and the `leaf_indices` vector contained in the +/// [primitives::BatchProof]. +pub fn verify_leaves_proof( root: H::Output, - leaf: mmr::Node, - proof: primitives::Proof, + leaves: Vec>, + proof: primitives::BatchProof, ) -> Result<(), primitives::Error> where H: traits::Hash, L: primitives::FullLeaf, { - let is_valid = mmr::verify_leaf_proof::(root, leaf, proof)?; + let is_valid = mmr::verify_leaves_proof::(root, leaves, proof)?; if is_valid { Ok(()) } else { @@ -255,29 +257,36 @@ impl, I: 'static> Pallet { fn offchain_key(pos: NodeIndex) -> sp_std::prelude::Vec { (T::INDEXING_PREFIX, pos).encode() } - - /// Generate a MMR proof for the given `leaf_index`. + /// Generate a MMR proof for the given `leaf_indices`. /// /// Note this method can only be used from an off-chain context /// (Offchain Worker or Runtime API call), since it requires /// all the leaves to be present. /// It may return an error or panic if used incorrectly. - pub fn generate_proof( - leaf_index: LeafIndex, - ) -> Result<(LeafOf, primitives::Proof<>::Hash>), primitives::Error> { + pub fn generate_batch_proof( + leaf_indices: Vec, + ) -> Result< + (Vec>, primitives::BatchProof<>::Hash>), + primitives::Error, + > { let mmr: ModuleMmr = mmr::Mmr::new(Self::mmr_leaves()); - mmr.generate_proof(leaf_index) + mmr.generate_batch_proof(leaf_indices) + } + + /// Return the on-chain MMR root hash. + pub fn mmr_root() -> >::Hash { + Self::mmr_root_hash() } - /// Verify MMR proof for given `leaf`. + /// Verify MMR proof for given `leaves`. /// /// This method is safe to use within the runtime code. /// It will return `Ok(())` if the proof is valid /// and an `Err(..)` if MMR is inconsistent (some leaves are missing) /// or the proof is invalid. - pub fn verify_leaf( - leaf: LeafOf, - proof: primitives::Proof<>::Hash>, + pub fn verify_leaves( + leaves: Vec>, + proof: primitives::BatchProof<>::Hash>, ) -> Result<(), primitives::Error> { if proof.leaf_count > Self::mmr_leaves() || proof.leaf_count == 0 || @@ -288,16 +297,11 @@ impl, I: 'static> Pallet { } let mmr: ModuleMmr = mmr::Mmr::new(proof.leaf_count); - let is_valid = mmr.verify_leaf_proof(leaf, proof)?; + let is_valid = mmr.verify_leaves_proof(leaves, proof)?; if is_valid { Ok(()) } else { Err(primitives::Error::Verify.log_debug("The proof is incorrect.")) } } - - /// Return the on-chain MMR root hash. - pub fn mmr_root() -> >::Hash { - Self::mmr_root_hash() - } } diff --git a/frame/merkle-mountain-range/src/mmr/mmr.rs b/frame/merkle-mountain-range/src/mmr/mmr.rs index a1516ee8607f4..44e684c1bdcac 100644 --- a/frame/merkle-mountain-range/src/mmr/mmr.rs +++ b/frame/merkle-mountain-range/src/mmr/mmr.rs @@ -24,27 +24,39 @@ use crate::{ primitives::{self, Error, NodeIndex}, Config, HashingOf, }; -#[cfg(not(feature = "std"))] -use sp_std::vec; +use sp_std::prelude::*; -/// Stateless verification of the leaf proof. -pub fn verify_leaf_proof( +/// Stateless verification of the proof for a batch of leaves. +/// Note, the leaves should be sorted such that corresponding leaves and leaf indices have the +/// same position in both the `leaves` vector and the `leaf_indices` vector contained in the +/// [primitives::BatchProof] +pub fn verify_leaves_proof( root: H::Output, - leaf: Node, - proof: primitives::Proof, + leaves: Vec>, + proof: primitives::BatchProof, ) -> Result where H: sp_runtime::traits::Hash, L: primitives::FullLeaf, { let size = NodesUtils::new(proof.leaf_count).size(); - let leaf_position = mmr_lib::leaf_index_to_pos(proof.leaf_index); + + if leaves.len() != proof.leaf_indices.len() { + return Err(Error::Verify.log_debug("Proof leaf_indices not same length with leaves")) + } + + let leaves_and_position_data = proof + .leaf_indices + .into_iter() + .map(|index| mmr_lib::leaf_index_to_pos(index)) + .zip(leaves.into_iter()) + .collect(); let p = mmr_lib::MerkleProof::, Hasher>::new( size, proof.items.into_iter().map(Node::Hash).collect(), ); - p.verify(Node::Hash(root), vec![(leaf_position, leaf)]) + p.verify(Node::Hash(root), leaves_and_position_data) .map_err(|e| Error::Verify.log_debug(e)) } @@ -76,19 +88,32 @@ where Self { mmr: mmr_lib::MMR::new(size, Default::default()), leaves } } - /// Verify proof of a single leaf. - pub fn verify_leaf_proof( + /// Verify proof for a set of leaves. + /// Note, the leaves should be sorted such that corresponding leaves and leaf indices have + /// the same position in both the `leaves` vector and the `leaf_indices` vector contained in the + /// [primitives::BatchProof] + pub fn verify_leaves_proof( &self, - leaf: L, - proof: primitives::Proof<>::Hash>, + leaves: Vec, + proof: primitives::BatchProof<>::Hash>, ) -> Result { let p = mmr_lib::MerkleProof::, Hasher, L>>::new( self.mmr.mmr_size(), proof.items.into_iter().map(Node::Hash).collect(), ); - let position = mmr_lib::leaf_index_to_pos(proof.leaf_index); + + if leaves.len() != proof.leaf_indices.len() { + return Err(Error::Verify.log_debug("Proof leaf_indices not same length with leaves")) + } + + let leaves_positions_and_data = proof + .leaf_indices + .into_iter() + .map(|index| mmr_lib::leaf_index_to_pos(index)) + .zip(leaves.into_iter().map(|leaf| Node::Data(leaf))) + .collect(); let root = self.mmr.get_root().map_err(|e| Error::GetRoot.log_error(e))?; - p.verify(root, vec![(position, Node::Data(leaf))]) + p.verify(root, leaves_positions_and_data) .map_err(|e| Error::Verify.log_debug(e)) } @@ -134,29 +159,36 @@ where I: 'static, L: primitives::FullLeaf + codec::Decode, { - /// Generate a proof for given leaf index. + /// Generate a proof for given leaf indices. /// /// Proof generation requires all the nodes (or their hashes) to be available in the storage. /// (i.e. you can't run the function in the pruned storage). - pub fn generate_proof( + pub fn generate_batch_proof( &self, - leaf_index: NodeIndex, - ) -> Result<(L, primitives::Proof<>::Hash>), Error> { - let position = mmr_lib::leaf_index_to_pos(leaf_index); + leaf_indices: Vec, + ) -> Result<(Vec, primitives::BatchProof<>::Hash>), Error> { + let positions = leaf_indices + .iter() + .map(|index| mmr_lib::leaf_index_to_pos(*index)) + .collect::>(); let store = >::default(); - let leaf = match mmr_lib::MMRStore::get_elem(&store, position) { - Ok(Some(Node::Data(leaf))) => leaf, - e => return Err(Error::LeafNotFound.log_debug(e)), - }; + let leaves = positions + .iter() + .map(|pos| match mmr_lib::MMRStore::get_elem(&store, *pos) { + Ok(Some(Node::Data(leaf))) => Ok(leaf), + e => Err(Error::LeafNotFound.log_debug(e)), + }) + .collect::, Error>>()?; + let leaf_count = self.leaves; self.mmr - .gen_proof(vec![position]) + .gen_proof(positions) .map_err(|e| Error::GenerateProof.log_error(e)) - .map(|p| primitives::Proof { - leaf_index, + .map(|p| primitives::BatchProof { + leaf_indices, leaf_count, items: p.proof_items().iter().map(|x| x.hash()).collect(), }) - .map(|p| (leaf, p)) + .map(|p| (leaves, p)) } } diff --git a/frame/merkle-mountain-range/src/mmr/mod.rs b/frame/merkle-mountain-range/src/mmr/mod.rs index 1cb4e8535b991..04fdfa199e72b 100644 --- a/frame/merkle-mountain-range/src/mmr/mod.rs +++ b/frame/merkle-mountain-range/src/mmr/mod.rs @@ -22,7 +22,7 @@ pub mod utils; use sp_mmr_primitives::{DataOrHash, FullLeaf}; use sp_runtime::traits; -pub use self::mmr::{verify_leaf_proof, Mmr}; +pub use self::mmr::{verify_leaves_proof, Mmr}; /// Node type for runtime `T`. pub type NodeOf = Node<>::Hashing, L>; diff --git a/frame/merkle-mountain-range/src/tests.rs b/frame/merkle-mountain-range/src/tests.rs index 70d1395aa94d5..d025910a9ee5c 100644 --- a/frame/merkle-mountain-range/src/tests.rs +++ b/frame/merkle-mountain-range/src/tests.rs @@ -23,7 +23,7 @@ use sp_core::{ offchain::{testing::TestOffchainExt, OffchainDbExt, OffchainWorkerExt}, H256, }; -use sp_mmr_primitives::{Compact, Proof}; +use sp_mmr_primitives::{BatchProof, Compact}; pub(crate) fn new_test_ext() -> sp_io::TestExternalities { frame_system::GenesisConfig::default().build_storage::().unwrap().into() @@ -225,16 +225,18 @@ fn should_generate_proofs_correctly() { // when generate proofs for all leaves let proofs = (0_u64..crate::NumberOfLeaves::::get()) .into_iter() - .map(|leaf_index| crate::Pallet::::generate_proof(leaf_index).unwrap()) + .map(|leaf_index| { + crate::Pallet::::generate_batch_proof(vec![leaf_index]).unwrap() + }) .collect::>(); // then assert_eq!( proofs[0], ( - Compact::new(((0, H256::repeat_byte(1)).into(), LeafData::new(1).into(),)), - Proof { - leaf_index: 0, + vec![Compact::new(((0, H256::repeat_byte(1)).into(), LeafData::new(1).into(),))], + BatchProof { + leaf_indices: vec![0], leaf_count: 7, items: vec![ hex("ad4cbc033833612ccd4626d5f023b9dfc50a35e838514dd1f3c86f8506728705"), @@ -247,9 +249,9 @@ fn should_generate_proofs_correctly() { assert_eq!( proofs[4], ( - Compact::new(((4, H256::repeat_byte(5)).into(), LeafData::new(5).into(),)), - Proof { - leaf_index: 4, + vec![Compact::new(((4, H256::repeat_byte(5)).into(), LeafData::new(5).into(),))], + BatchProof { + leaf_indices: vec![4], leaf_count: 7, items: vec![ hex("ae88a0825da50e953e7a359c55fe13c8015e48d03d301b8bdfc9193874da9252"), @@ -262,9 +264,9 @@ fn should_generate_proofs_correctly() { assert_eq!( proofs[6], ( - Compact::new(((6, H256::repeat_byte(7)).into(), LeafData::new(7).into(),)), - Proof { - leaf_index: 6, + vec![Compact::new(((6, H256::repeat_byte(7)).into(), LeafData::new(7).into(),))], + BatchProof { + leaf_indices: vec![6], leaf_count: 7, items: vec![ hex("ae88a0825da50e953e7a359c55fe13c8015e48d03d301b8bdfc9193874da9252"), @@ -276,6 +278,37 @@ fn should_generate_proofs_correctly() { }); } +#[test] +fn should_generate_batch_proof_correctly() { + let _ = env_logger::try_init(); + let mut ext = new_test_ext(); + // given + ext.execute_with(|| init_chain(7)); + ext.persist_offchain_overlay(); + + // Try to generate proofs now. This requires the offchain extensions to be present + // to retrieve full leaf data. + register_offchain_ext(&mut ext); + ext.execute_with(|| { + // when generate proofs for all leaves + let (.., proof) = crate::Pallet::::generate_batch_proof(vec![0, 4, 5]).unwrap(); + + // then + assert_eq!( + proof, + BatchProof { + leaf_indices: vec![0, 4, 5], + leaf_count: 7, + items: vec![ + hex("ad4cbc033833612ccd4626d5f023b9dfc50a35e838514dd1f3c86f8506728705"), + hex("cb24f4614ad5b2a5430344c99545b421d9af83c46fd632d70a332200884b4d46"), + hex("611c2174c6164952a66d985cfe1ec1a623794393e3acff96b136d198f37a648c"), + ], + } + ); + }); +} + #[test] fn should_verify() { let _ = env_logger::try_init(); @@ -289,15 +322,40 @@ fn should_verify() { // Try to generate proof now. This requires the offchain extensions to be present // to retrieve full leaf data. register_offchain_ext(&mut ext); - let (leaf, proof5) = ext.execute_with(|| { + let (leaves, proof5) = ext.execute_with(|| { + // when + crate::Pallet::::generate_batch_proof(vec![5]).unwrap() + }); + + ext.execute_with(|| { + init_chain(7); + // then + assert_eq!(crate::Pallet::::verify_leaves(leaves, proof5), Ok(())); + }); +} + +#[test] +fn should_verify_batch_proof() { + let _ = env_logger::try_init(); + + // Start off with chain initialisation and storing indexing data off-chain + // (MMR Leafs) + let mut ext = new_test_ext(); + ext.execute_with(|| init_chain(7)); + ext.persist_offchain_overlay(); + + // Try to generate proof now. This requires the offchain extensions to be present + // to retrieve full leaf data. + register_offchain_ext(&mut ext); + let (leaves, proof) = ext.execute_with(|| { // when - crate::Pallet::::generate_proof(5).unwrap() + crate::Pallet::::generate_batch_proof(vec![0, 4, 5]).unwrap() }); ext.execute_with(|| { init_chain(7); // then - assert_eq!(crate::Pallet::::verify_leaf(leaf, proof5), Ok(())); + assert_eq!(crate::Pallet::::verify_leaves(leaves, proof), Ok(())); }); } @@ -314,16 +372,49 @@ fn verification_should_be_stateless() { // Try to generate proof now. This requires the offchain extensions to be present // to retrieve full leaf data. register_offchain_ext(&mut ext); - let (leaf, proof5) = ext.execute_with(|| { + let (leaves, proof5) = ext.execute_with(|| { + // when + crate::Pallet::::generate_batch_proof(vec![5]).unwrap() + }); + let root = ext.execute_with(|| crate::Pallet::::mmr_root_hash()); + + // Verify proof without relying on any on-chain data. + let leaf = crate::primitives::DataOrHash::Data(leaves[0].clone()); + assert_eq!( + crate::verify_leaves_proof::<::Hashing, _>(root, vec![leaf], proof5), + Ok(()) + ); +} + +#[test] +fn should_verify_batch_proof_statelessly() { + let _ = env_logger::try_init(); + + // Start off with chain initialisation and storing indexing data off-chain + // (MMR Leafs) + let mut ext = new_test_ext(); + ext.execute_with(|| init_chain(7)); + ext.persist_offchain_overlay(); + + // Try to generate proof now. This requires the offchain extensions to be present + // to retrieve full leaf data. + register_offchain_ext(&mut ext); + let (leaves, proof) = ext.execute_with(|| { // when - crate::Pallet::::generate_proof(5).unwrap() + crate::Pallet::::generate_batch_proof(vec![0, 4, 5]).unwrap() }); let root = ext.execute_with(|| crate::Pallet::::mmr_root_hash()); // Verify proof without relying on any on-chain data. - let leaf = crate::primitives::DataOrHash::Data(leaf); assert_eq!( - crate::verify_leaf_proof::<::Hashing, _>(root, leaf, proof5), + crate::verify_leaves_proof::<::Hashing, _>( + root, + leaves + .into_iter() + .map(|leaf| crate::primitives::DataOrHash::Data(leaf)) + .collect(), + proof + ), Ok(()) ); } @@ -340,10 +431,10 @@ fn should_verify_on_the_next_block_since_there_is_no_pruning_yet() { ext.execute_with(|| { // when - let (leaf, proof5) = crate::Pallet::::generate_proof(5).unwrap(); + let (leaves, proof5) = crate::Pallet::::generate_batch_proof(vec![5]).unwrap(); new_block(); // then - assert_eq!(crate::Pallet::::verify_leaf(leaf, proof5), Ok(())); + assert_eq!(crate::Pallet::::verify_leaves(leaves, proof5), Ok(())); }); } diff --git a/primitives/merkle-mountain-range/src/lib.rs b/primitives/merkle-mountain-range/src/lib.rs index 60ef02c53001c..5a339d069062c 100644 --- a/primitives/merkle-mountain-range/src/lib.rs +++ b/primitives/merkle-mountain-range/src/lib.rs @@ -22,9 +22,9 @@ use sp_debug_derive::RuntimeDebug; use sp_runtime::traits; -use sp_std::fmt; #[cfg(not(feature = "std"))] use sp_std::prelude::Vec; +use sp_std::{fmt, vec}; /// A type to describe node position in the MMR (node index). pub type NodeIndex = u64; @@ -351,6 +351,38 @@ impl_leaf_data_for_tuple!(A:0, B:1, C:2); impl_leaf_data_for_tuple!(A:0, B:1, C:2, D:3); impl_leaf_data_for_tuple!(A:0, B:1, C:2, D:3, E:4); +/// A MMR proof data for a group of leaves. +#[derive(codec::Encode, codec::Decode, RuntimeDebug, Clone, PartialEq, Eq)] +pub struct BatchProof { + /// The indices of the leaves the proof is for. + pub leaf_indices: Vec, + /// Number of leaves in MMR, when the proof was generated. + pub leaf_count: NodeIndex, + /// Proof elements (hashes of siblings of inner nodes on the path to the leaf). + pub items: Vec, +} + +impl BatchProof { + /// Converts batch proof to single leaf proof + pub fn into_single_leaf_proof(proof: BatchProof) -> Result, Error> { + Ok(Proof { + leaf_index: *proof.leaf_indices.get(0).ok_or(Error::InvalidLeafIndex)?, + leaf_count: proof.leaf_count, + items: proof.items, + }) + } +} + +impl Proof { + /// Converts a single leaf proof into a batch proof + pub fn into_batch_proof(proof: Proof) -> BatchProof { + BatchProof { + leaf_indices: vec![proof.leaf_index], + leaf_count: proof.leaf_count, + items: proof.items, + } + } +} /// Merkle Mountain Range operation error. #[derive(RuntimeDebug, codec::Encode, codec::Decode, PartialEq, Eq)] pub enum Error { @@ -366,6 +398,10 @@ pub enum Error { Verify, /// Leaf not found in the storage. LeafNotFound, + /// Mmr Pallet not included in runtime + PalletNotIncluded, + /// Cannot find the requested leaf index + InvalidLeafIndex, } impl Error { @@ -417,6 +453,27 @@ sp_api::decl_runtime_apis! { /// Return the on-chain MMR root hash. fn mmr_root() -> Result; + + /// Generate MMR proof for a series of leaves under given indices. + fn generate_batch_proof(leaf_indices: Vec) -> Result<(Vec, BatchProof), Error>; + + /// Verify MMR proof against on-chain MMR for a batch of leaves. + /// + /// Note this function will use on-chain MMR root hash and check if the proof + /// matches the hash. + /// Note, the leaves should be sorted such that corresponding leaves and leaf indices have the + /// same position in both the `leaves` vector and the `leaf_indices` vector contained in the [BatchProof] + fn verify_batch_proof(leaves: Vec, proof: BatchProof) -> Result<(), Error>; + + /// Verify MMR proof against given root hash or a batch of leaves. + /// + /// Note this function does not require any on-chain storage - the + /// proof is verified against given MMR root hash. + /// + /// Note, the leaves should be sorted such that corresponding leaves and leaf indices have the + /// same position in both the `leaves` vector and the `leaf_indices` vector contained in the [BatchProof] + fn verify_batch_proof_stateless(root: Hash, leaves: Vec, proof: BatchProof) + -> Result<(), Error>; } } From bb05e3da26e6c0f6364188914048b011a916ab3f Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Wed, 4 May 2022 14:17:24 +0100 Subject: [PATCH 184/484] fix a few things with nomination pools (#11343) * fix a few things with nomination pools * fix typo * fix build * add missing try-runtime feat --- frame/nomination-pools/Cargo.toml | 1 + frame/nomination-pools/src/lib.rs | 25 +- frame/nomination-pools/src/tests.rs | 341 +++++++++++++++++++++++++++- 3 files changed, 356 insertions(+), 11 deletions(-) diff --git a/frame/nomination-pools/Cargo.toml b/frame/nomination-pools/Cargo.toml index 882a6f363a14f..8c2f9daf2777b 100644 --- a/frame/nomination-pools/Cargo.toml +++ b/frame/nomination-pools/Cargo.toml @@ -31,6 +31,7 @@ sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } [features] runtime-benchmarks = [] +try-runtime = [] default = ["std"] std = [ "codec/std", diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index bafed9fc2f5b4..8b26bad6c7df8 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -403,7 +403,6 @@ pub struct PoolMember { } impl PoolMember { - #[cfg(any(test, debug_assertions))] fn total_points(&self) -> BalanceOf { self.active_points().saturating_add(self.unbonding_points()) } @@ -739,6 +738,14 @@ impl BondedPool { ) -> Result<(), DispatchError> { let is_permissioned = caller == target_account; let is_depositor = *target_account == self.roles.depositor; + let is_full_unbond = unbonding_points == target_member.active_points(); + + // any partial unbonding is only ever allowed if this unbond is permissioned. + ensure!( + is_permissioned || is_full_unbond, + Error::::PartialUnbondNotAllowedPermissionlessly + ); + match (is_permissioned, is_depositor) { // If the pool is blocked, then an admin with kicking permissions can remove a // member. If the pool is being destroyed, anyone can remove a member @@ -1198,6 +1205,10 @@ pub mod pallet { Destroyed { pool_id: PoolId }, /// The state of a pool has changed StateChanged { pool_id: PoolId, new_state: PoolState }, + /// A member has been removed from a pool. + /// + /// The removal can be voluntary (withdrawn all unbonded funds) or involuntary (kicked). + MemberRemoved { pool_id: PoolId, member: T::AccountId }, } #[pallet::error] @@ -1256,6 +1267,8 @@ pub mod pallet { DefensiveError, /// Not enough points. Ty unbonding less. NotEnoughPointsToUnbond, + /// Partial unbonding now allowed permissionlessly. + PartialUnbondNotAllowedPermissionlessly, } #[pallet::call] @@ -1595,9 +1608,13 @@ pub mod pallet { amount: balance_to_unbond, }); - let post_info_weight = if member.active_points().is_zero() { + let post_info_weight = if member.total_points().is_zero() { // member being reaped. PoolMembers::::remove(&member_account); + Self::deposit_event(Event::::MemberRemoved { + pool_id: member.pool_id, + member: member_account.clone(), + }); if member_account == bonded_pool.roles.depositor { Pallet::::dissolve_pool(bonded_pool); @@ -2044,6 +2061,10 @@ impl Pallet { let member_payout = Self::calculate_member_payout(member, bonded_pool, reward_pool)?; + if member_payout.is_zero() { + return Ok(member_payout) + } + // Transfer payout to the member. T::Currency::transfer( &bonded_pool.reward_account(), diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 7df922280873b..cd66c3d774960 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -1386,6 +1386,17 @@ mod unbond { } } ); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true }, + Event::Bonded { member: 550, pool_id: 1, bonded: 550, joined: true }, + Event::PaidOut { member: 40, pool_id: 1, payout: 40 }, + Event::Unbonded { member: 40, pool_id: 1, amount: 6 } + ] + ); assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 94); assert_eq!( @@ -1421,6 +1432,13 @@ mod unbond { member_unbonding_eras!(0 + 3 => 550) ); assert_eq!(Balances::free_balance(&550), 550 + 550); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::PaidOut { member: 550, pool_id: 1, payout: 550 }, + Event::Unbonded { member: 550, pool_id: 1, amount: 92 } + ] + ); // When assert_ok!(fully_unbond_permissioned(10)); @@ -1448,6 +1466,13 @@ mod unbond { member_unbonding_eras!(0 + 3 => 550) ); assert_eq!(Balances::free_balance(&550), 550 + 550); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::PaidOut { member: 10, pool_id: 1, payout: 10 }, + Event::Unbonded { member: 10, pool_id: 1, amount: 2 } + ] + ); }); } @@ -1485,7 +1510,15 @@ mod unbond { current_era + 3 => UnbondPool { balance: 10, points: 10 }, }, }, - ) + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Unbonded { member: 10, pool_id: 1, amount: 10 } + ] + ); }); } @@ -1502,7 +1535,7 @@ mod unbond { assert_eq!(bonded_pool.roles.nominator, 901); assert_eq!(bonded_pool.roles.state_toggler, 902); - // When the nominator trys to kick, then its a noop + // When the nominator tries to kick, then its a noop assert_noop!( Pools::fully_unbond(Origin::signed(901), 100), Error::::NotKickerOrDestroying @@ -1511,9 +1544,25 @@ mod unbond { // When the root kicks then its ok assert_ok!(Pools::fully_unbond(Origin::signed(900), 100)); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, + Event::Bonded { member: 200, pool_id: 1, bonded: 200, joined: true }, + Event::Unbonded { member: 100, pool_id: 1, amount: 100 }, + ] + ); + // When the state toggler kicks then its ok assert_ok!(Pools::fully_unbond(Origin::signed(902), 200)); + assert_eq!( + pool_events_since_last_call(), + vec![Event::Unbonded { member: 200, pool_id: 1, amount: 200 }] + ); + assert_eq!( BondedPool::::get(1).unwrap(), BondedPool { @@ -1557,6 +1606,12 @@ mod unbond { Error::::NotKickerOrDestroying ); + // permissionless unbond must be full + assert_noop!( + Pools::unbond(Origin::signed(420), 100, 80), + Error::::PartialUnbondNotAllowedPermissionlessly, + ); + // Given the pool is destroying unsafe_set_state(1, PoolState::Destroying).unwrap(); @@ -1568,6 +1623,21 @@ mod unbond { // Any account can unbond a member that is not the depositor assert_ok!(Pools::fully_unbond(Origin::signed(420), 100)); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, + Event::Unbonded { member: 100, pool_id: 1, amount: 100 } + ] + ); + + // still permissionless unbond must be full + assert_noop!( + Pools::unbond(Origin::signed(420), 100, 80), + Error::::PartialUnbondNotAllowedPermissionlessly, + ); // Given the pool is blocked unsafe_set_state(1, PoolState::Blocked).unwrap(); @@ -1584,6 +1654,17 @@ mod unbond { // The depositor can be unbonded by anyone. assert_ok!(Pools::fully_unbond(Origin::signed(420), 10)); + assert_eq!( + pool_events_since_last_call(), + vec![Event::Unbonded { member: 10, pool_id: 1, amount: 10 }] + ); + + // still permissionless unbond must be full. + assert_noop!( + Pools::unbond(Origin::signed(420), 10, 5), + Error::::PartialUnbondNotAllowedPermissionlessly, + ); + assert_eq!(BondedPools::::get(1).unwrap().points, 0); assert_eq!( SubPoolsStorage::::get(1).unwrap(), @@ -1682,6 +1763,14 @@ mod unbond { } } ); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Unbonded { member: 10, pool_id: 1, amount: 1 } + ] + ); // when: casual further unbond, same era. assert_ok!(Pools::unbond(Origin::signed(10), 10, 5)); @@ -1703,6 +1792,10 @@ mod unbond { } } ); + assert_eq!( + pool_events_since_last_call(), + vec![Event::Unbonded { member: 10, pool_id: 1, amount: 5 }] + ); // when: casual further unbond, next era. CurrentEra::set(1); @@ -1726,6 +1819,10 @@ mod unbond { } } ); + assert_eq!( + pool_events_since_last_call(), + vec![Event::Unbonded { member: 10, pool_id: 1, amount: 1 }] + ); // when: unbonding more than our active: error assert_noop!( @@ -1753,6 +1850,10 @@ mod unbond { } } ); + assert_eq!( + pool_events_since_last_call(), + vec![Event::Unbonded { member: 10, pool_id: 1, amount: 3 }] + ); }); } @@ -1786,6 +1887,16 @@ mod unbond { PoolMembers::::get(10).unwrap().unbonding_eras, member_unbonding_eras!(3 => 2, 4 => 3, 5 => 1) ); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Unbonded { member: 10, pool_id: 1, amount: 2 }, + Event::Unbonded { member: 10, pool_id: 1, amount: 3 }, + Event::Unbonded { member: 10, pool_id: 1, amount: 1 } + ] + ); }) } @@ -1808,6 +1919,15 @@ mod unbond { Pools::unbond(Origin::signed(10), 10, 6), Error::::NotOnlyPoolMember ); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, + Event::Unbonded { member: 10, pool_id: 1, amount: 3 } + ] + ); }); } @@ -1828,6 +1948,14 @@ mod unbond { Pools::unbond(Origin::signed(10), 10, 7), Error::::NotOnlyPoolMember ); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true } + ] + ); }); } } @@ -1944,6 +2072,26 @@ mod withdraw_unbonded { assert!(!SubPoolsStorage::::contains_key(1),); assert!(!RewardPools::::contains_key(1),); assert!(!BondedPools::::contains_key(1),); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true }, + Event::Bonded { member: 550, pool_id: 1, bonded: 550, joined: true }, + Event::Unbonded { member: 550, pool_id: 1, amount: 550 }, + Event::Unbonded { member: 40, pool_id: 1, amount: 40 }, + Event::Unbonded { member: 10, pool_id: 1, amount: 10 }, + Event::Withdrawn { member: 550, pool_id: 1, amount: 545 }, + Event::MemberRemoved { pool_id: 1, member: 550 }, + Event::Withdrawn { member: 40, pool_id: 1, amount: 40 }, + Event::MemberRemoved { pool_id: 1, member: 40 }, + Event::Withdrawn { member: 10, pool_id: 1, amount: 10 }, + Event::MemberRemoved { pool_id: 1, member: 10 }, + Event::Destroyed { pool_id: 1 } + ] + ); }); } @@ -2006,9 +2154,29 @@ mod withdraw_unbonded { assert_eq!(Balances::free_balance(&default_bonded_account()), 0); assert!(!PoolMembers::::contains_key(10)); // Pools are removed from storage because the depositor left - assert!(!SubPoolsStorage::::contains_key(1),); - assert!(!RewardPools::::contains_key(1),); - assert!(!BondedPools::::contains_key(1),); + assert!(!SubPoolsStorage::::contains_key(1)); + assert!(!RewardPools::::contains_key(1)); + assert!(!BondedPools::::contains_key(1)); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true }, + Event::Bonded { member: 550, pool_id: 1, bonded: 550, joined: true }, + Event::Unbonded { member: 40, pool_id: 1, amount: 6 }, + Event::Unbonded { member: 550, pool_id: 1, amount: 92 }, + Event::Unbonded { member: 10, pool_id: 1, amount: 2 }, + Event::Withdrawn { member: 40, pool_id: 1, amount: 6 }, + Event::MemberRemoved { pool_id: 1, member: 40 }, + Event::Withdrawn { member: 550, pool_id: 1, amount: 92 }, + Event::MemberRemoved { pool_id: 1, member: 550 }, + Event::Withdrawn { member: 10, pool_id: 1, amount: 0 }, + Event::MemberRemoved { pool_id: 1, member: 10 }, + Event::Destroyed { pool_id: 1 } + ] + ); }); } @@ -2103,6 +2271,17 @@ mod withdraw_unbonded { Pools::withdraw_unbonded(Origin::signed(902), 100, 0), Error::::NotKickerOrDestroying ); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, + Event::Bonded { member: 200, pool_id: 1, bonded: 200, joined: true }, + Event::Unbonded { member: 100, pool_id: 1, amount: 100 }, + Event::Unbonded { member: 200, pool_id: 1, amount: 200 } + ] + ); // Given unsafe_set_state(1, PoolState::Blocked).unwrap(); @@ -2124,6 +2303,15 @@ mod withdraw_unbonded { assert!(!PoolMembers::::contains_key(100)); assert!(!PoolMembers::::contains_key(200)); assert_eq!(SubPoolsStorage::::get(1).unwrap(), Default::default()); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Withdrawn { member: 100, pool_id: 1, amount: 100 }, + Event::MemberRemoved { pool_id: 1, member: 100 }, + Event::Withdrawn { member: 200, pool_id: 1, amount: 200 }, + Event::MemberRemoved { pool_id: 1, member: 200 } + ] + ); }); } @@ -2162,6 +2350,17 @@ mod withdraw_unbonded { assert_eq!(SubPoolsStorage::::get(1).unwrap(), Default::default(),); assert_eq!(Balances::free_balance(100), 100 + 100); assert!(!PoolMembers::::contains_key(100)); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, + Event::Unbonded { member: 100, pool_id: 1, amount: 100 }, + Event::Withdrawn { member: 100, pool_id: 1, amount: 100 }, + Event::MemberRemoved { pool_id: 1, member: 100 } + ] + ); }); } @@ -2240,6 +2439,26 @@ mod withdraw_unbonded { assert!(!SubPoolsStorage::::contains_key(1)); assert!(!RewardPools::::contains_key(1)); assert!(!BondedPools::::contains_key(1)); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, + Event::Bonded { member: 200, pool_id: 1, bonded: 200, joined: true }, + Event::Unbonded { member: 100, pool_id: 1, amount: 100 }, + Event::Unbonded { member: 200, pool_id: 1, amount: 200 }, + Event::Unbonded { member: 10, pool_id: 1, amount: 10 }, + Event::Withdrawn { member: 100, pool_id: 1, amount: 100 }, + Event::MemberRemoved { pool_id: 1, member: 100 }, + Event::Withdrawn { member: 200, pool_id: 1, amount: 200 }, + Event::MemberRemoved { pool_id: 1, member: 200 }, + Event::Withdrawn { member: 10, pool_id: 1, amount: 10 }, + Event::MemberRemoved { pool_id: 1, member: 10 }, + Event::Destroyed { pool_id: 1 } + ] + ); }); } @@ -2325,9 +2544,7 @@ mod withdraw_unbonded { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::PaidOut { member: 10, pool_id: 1, payout: 0 }, Event::Unbonded { member: 10, pool_id: 1, amount: 6 }, - Event::PaidOut { member: 10, pool_id: 1, payout: 0 }, Event::Unbonded { member: 10, pool_id: 1, amount: 1 } ] ); @@ -2414,9 +2631,7 @@ mod withdraw_unbonded { Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 11, pool_id: 1, bonded: 10, joined: true }, - Event::PaidOut { member: 11, pool_id: 1, payout: 0 }, Event::Unbonded { member: 11, pool_id: 1, amount: 6 }, - Event::PaidOut { member: 11, pool_id: 1, payout: 0 }, Event::Unbonded { member: 11, pool_id: 1, amount: 1 } ] ); @@ -2473,6 +2688,114 @@ mod withdraw_unbonded { ); }); } + + #[test] + fn full_multi_step_withdrawing_non_depositor() { + ExtBuilder::default().add_members(vec![(100, 100)]).build_and_execute(|| { + // given + assert_ok!(Pools::unbond(Origin::signed(100), 100, 75)); + assert_eq!( + PoolMembers::::get(100).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 75) + ); + + // progress one era and unbond the leftover. + CurrentEra::set(1); + assert_ok!(Pools::unbond(Origin::signed(100), 100, 25)); + assert_eq!( + PoolMembers::::get(100).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 75, 4 => 25) + ); + + assert_noop!( + Pools::withdraw_unbonded(Origin::signed(100), 100, 0), + Error::::CannotWithdrawAny + ); + + // now the 75 should be free. + CurrentEra::set(3); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(100), 100, 0)); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, + Event::Unbonded { member: 100, pool_id: 1, amount: 75 }, + Event::Unbonded { member: 100, pool_id: 1, amount: 25 }, + Event::Withdrawn { member: 100, pool_id: 1, amount: 75 }, + ] + ); + assert_eq!( + PoolMembers::::get(100).unwrap().unbonding_eras, + member_unbonding_eras!(4 => 25) + ); + + // the 25 should be free now, and the member removed. + CurrentEra::set(4); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(100), 100, 0)); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Withdrawn { member: 100, pool_id: 1, amount: 25 }, + Event::MemberRemoved { pool_id: 1, member: 100 } + ] + ); + }) + } + + #[test] + fn full_multi_step_withdrawing_depositor() { + ExtBuilder::default().ed(1).build_and_execute(|| { + // given + assert_ok!(Pools::unbond(Origin::signed(10), 10, 7)); + + // progress one era and unbond the leftover. + CurrentEra::set(1); + unsafe_set_state(1, PoolState::Destroying).unwrap(); + assert_ok!(Pools::unbond(Origin::signed(10), 10, 3)); + assert_eq!( + PoolMembers::::get(10).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 7, 4 => 3) + ); + + assert_noop!( + Pools::withdraw_unbonded(Origin::signed(10), 10, 0), + Error::::CannotWithdrawAny + ); + + // now the 7 should be free. + CurrentEra::set(3); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 0)); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Unbonded { member: 10, pool_id: 1, amount: 7 }, + Event::Unbonded { member: 10, pool_id: 1, amount: 3 }, + Event::Withdrawn { member: 10, pool_id: 1, amount: 7 } + ] + ); + assert_eq!( + PoolMembers::::get(10).unwrap().unbonding_eras, + member_unbonding_eras!(4 => 3) + ); + + // the 25 should be free now, and the member removed. + CurrentEra::set(4); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 0)); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Withdrawn { member: 10, pool_id: 1, amount: 3 }, + Event::MemberRemoved { pool_id: 1, member: 10 }, + // the pool is also destroyed now. + Event::Destroyed { pool_id: 1 }, + ] + ); + }) + } } mod create { From 1002c00fb18cf3e52d421d52e3bed8eb50cc3592 Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Wed, 4 May 2022 14:38:54 +0100 Subject: [PATCH 185/484] Basic TOML Lint (#11348) * basic lint * lint ordering --- bin/node-template/pallets/template/Cargo.toml | 19 +++--- bin/node/executor/Cargo.toml | 8 +-- bin/node/primitives/Cargo.toml | 2 +- bin/node/rpc/Cargo.toml | 8 +-- bin/node/testing/Cargo.toml | 56 ++++++++--------- bin/utils/chain-spec-builder/Cargo.toml | 5 +- client/allocator/Cargo.toml | 4 +- client/api/Cargo.toml | 24 ++++---- client/authority-discovery/Cargo.toml | 8 +-- client/basic-authorship/Cargo.toml | 18 +++--- client/beefy/Cargo.toml | 35 +++++------ client/beefy/rpc/Cargo.toml | 27 ++++----- client/block-builder/Cargo.toml | 15 +++-- client/chain-spec/Cargo.toml | 12 ++-- client/chain-spec/derive/Cargo.toml | 2 - client/cli/Cargo.toml | 3 +- client/consensus/aura/Cargo.toml | 42 ++++++------- client/consensus/babe/Cargo.toml | 56 ++++++++--------- client/consensus/babe/rpc/Cargo.toml | 22 +++---- client/consensus/common/Cargo.toml | 24 ++++---- client/consensus/epochs/Cargo.toml | 6 +- client/consensus/manual-seal/Cargo.toml | 53 ++++++++-------- client/consensus/pow/Cargo.toml | 26 ++++---- client/consensus/slots/Cargo.toml | 20 +++---- client/consensus/uncles/Cargo.toml | 4 +- client/db/Cargo.toml | 29 +++++---- client/executor/common/Cargo.toml | 5 +- client/executor/runtime-test/Cargo.toml | 3 +- client/executor/wasmi/Cargo.toml | 5 +- client/executor/wasmtime/Cargo.toml | 7 +-- client/finality-grandpa/Cargo.toml | 53 ++++++++-------- client/finality-grandpa/rpc/Cargo.toml | 23 +++---- client/informant/Cargo.toml | 2 +- client/keystore/Cargo.toml | 7 +-- client/network-gossip/Cargo.toml | 5 +- client/network/Cargo.toml | 42 ++++++------- client/network/common/Cargo.toml | 4 +- client/network/sync/Cargo.toml | 8 +-- client/network/test/Cargo.toml | 28 ++++----- client/offchain/Cargo.toml | 22 +++---- client/peerset/Cargo.toml | 4 +- client/proposer-metrics/Cargo.toml | 2 +- client/rpc-api/Cargo.toml | 11 ++-- client/rpc-servers/Cargo.toml | 8 +-- client/rpc/Cargo.toml | 36 +++++------ client/service/test/Cargo.toml | 40 ++++++------- client/state-db/Cargo.toml | 8 +-- client/sync-state-rpc/Cargo.toml | 8 +-- client/sysinfo/Cargo.toml | 4 +- client/telemetry/Cargo.toml | 7 +-- client/tracing/Cargo.toml | 14 ++--- client/transaction-pool/Cargo.toml | 24 ++++---- client/transaction-pool/api/Cargo.toml | 3 +- client/utils/Cargo.toml | 4 +- frame/atomic-swap/Cargo.toml | 10 ++-- frame/aura/Cargo.toml | 18 +++--- frame/authority-discovery/Cargo.toml | 26 ++++---- frame/authorship/Cargo.toml | 14 ++--- frame/babe/Cargo.toml | 10 ++-- frame/bags-list/fuzzer/Cargo.toml | 5 +- frame/balances/Cargo.toml | 18 +++--- frame/beefy-mmr/Cargo.toml | 60 +++++++++---------- frame/beefy-mmr/primitives/Cargo.toml | 7 ++- frame/beefy/Cargo.toml | 27 ++++----- frame/benchmarking/Cargo.toml | 30 +++++----- frame/bounties/Cargo.toml | 24 ++++---- frame/child-bounties/Cargo.toml | 28 ++++----- frame/collective/Cargo.toml | 16 +++-- frame/conviction-voting/Cargo.toml | 26 ++++---- frame/democracy/Cargo.toml | 24 ++++---- frame/election-provider-support/Cargo.toml | 16 ++--- .../benchmarking/Cargo.toml | 12 ++-- frame/elections-phragmen/Cargo.toml | 24 ++++---- frame/examples/basic/Cargo.toml | 7 +-- frame/examples/offchain-worker/Cargo.toml | 7 +-- frame/examples/parallel/Cargo.toml | 3 +- frame/executive/Cargo.toml | 16 ++--- frame/gilt/Cargo.toml | 20 +++---- frame/grandpa/Cargo.toml | 38 ++++++------ frame/identity/Cargo.toml | 20 +++---- frame/im-online/Cargo.toml | 27 ++++----- frame/indices/Cargo.toml | 21 ++++--- frame/lottery/Cargo.toml | 15 +++-- frame/membership/Cargo.toml | 20 +++---- frame/merkle-mountain-range/Cargo.toml | 18 +++--- frame/merkle-mountain-range/rpc/Cargo.toml | 1 - frame/multisig/Cargo.toml | 13 ++-- frame/nicks/Cargo.toml | 14 ++--- frame/node-authorization/Cargo.toml | 6 +- frame/offences/Cargo.toml | 22 +++---- frame/offences/benchmarking/Cargo.toml | 8 +-- frame/preimage/Cargo.toml | 23 ++++--- frame/proxy/Cargo.toml | 11 ++-- frame/randomness-collective-flip/Cargo.toml | 13 ++-- frame/recovery/Cargo.toml | 18 +++--- frame/referenda/Cargo.toml | 32 +++++----- frame/remark/Cargo.toml | 16 ++--- frame/scheduler/Cargo.toml | 25 ++++---- frame/scored-pool/Cargo.toml | 8 +-- frame/session/Cargo.toml | 29 ++++----- frame/session/benchmarking/Cargo.toml | 22 ++++--- frame/society/Cargo.toml | 18 +++--- frame/staking/reward-curve/Cargo.toml | 6 +- frame/staking/reward-fn/Cargo.toml | 4 +- frame/state-trie-migration/Cargo.toml | 36 +++++------ frame/sudo/Cargo.toml | 12 ++-- frame/support/procedural/Cargo.toml | 4 +- frame/support/procedural/tools/Cargo.toml | 4 +- .../procedural/tools/derive/Cargo.toml | 2 +- frame/support/test/compile_pass/Cargo.toml | 10 ++-- frame/support/test/pallet/Cargo.toml | 2 +- frame/system/Cargo.toml | 20 +++---- frame/system/benchmarking/Cargo.toml | 14 ++--- frame/system/rpc/runtime-api/Cargo.toml | 4 +- frame/timestamp/Cargo.toml | 25 ++++---- frame/tips/Cargo.toml | 20 +++---- frame/transaction-payment/Cargo.toml | 16 +++-- frame/transaction-payment/rpc/Cargo.toml | 3 +- .../rpc/runtime-api/Cargo.toml | 4 +- frame/transaction-storage/Cargo.toml | 20 +++---- frame/treasury/Cargo.toml | 21 +++---- frame/try-runtime/Cargo.toml | 13 ++-- frame/uniques/Cargo.toml | 22 +++---- frame/utility/Cargo.toml | 11 ++-- frame/vesting/Cargo.toml | 18 +++--- frame/whitelist/Cargo.toml | 17 +++--- primitives/application-crypto/test/Cargo.toml | 8 +-- primitives/arithmetic/Cargo.toml | 15 +++-- primitives/arithmetic/fuzzer/Cargo.toml | 4 +- primitives/authority-discovery/Cargo.toml | 12 ++-- primitives/authorship/Cargo.toml | 8 +-- primitives/beefy/Cargo.toml | 13 ++-- primitives/block-builder/Cargo.toml | 10 ++-- primitives/blockchain/Cargo.toml | 8 +-- primitives/consensus/aura/Cargo.toml | 24 ++++---- primitives/consensus/babe/Cargo.toml | 26 ++++---- primitives/consensus/common/Cargo.toml | 10 ++-- primitives/consensus/pow/Cargo.toml | 12 ++-- primitives/consensus/vrf/Cargo.toml | 10 ++-- primitives/core/hashing/Cargo.toml | 9 ++- primitives/core/hashing/proc-macro/Cargo.toml | 8 +-- primitives/database/Cargo.toml | 3 +- primitives/externalities/Cargo.toml | 6 +- primitives/finality-grandpa/Cargo.toml | 13 ++-- primitives/inherents/Cargo.toml | 17 +++--- primitives/keyring/Cargo.toml | 4 +- primitives/keystore/Cargo.toml | 11 ++-- primitives/merkle-mountain-range/Cargo.toml | 3 +- primitives/npos-elections/Cargo.toml | 10 ++-- primitives/npos-elections/fuzzer/Cargo.toml | 3 +- primitives/offchain/Cargo.toml | 4 +- primitives/panic-handler/Cargo.toml | 2 +- primitives/rpc/Cargo.toml | 2 +- .../runtime-interface/proc-macro/Cargo.toml | 6 +- .../test-wasm-deprecated/Cargo.toml | 6 +- .../runtime-interface/test-wasm/Cargo.toml | 6 +- primitives/runtime-interface/test/Cargo.toml | 10 ++-- primitives/runtime/Cargo.toml | 47 +++++++-------- primitives/sandbox/Cargo.toml | 7 +-- primitives/session/Cargo.toml | 8 +-- primitives/state-machine/Cargo.toml | 34 +++++------ primitives/std/Cargo.toml | 1 - primitives/storage/Cargo.toml | 8 +-- primitives/tasks/Cargo.toml | 2 +- primitives/test-primitives/Cargo.toml | 8 +-- primitives/timestamp/Cargo.toml | 26 ++++---- .../transaction-storage-proof/Cargo.toml | 18 +++--- primitives/trie/Cargo.toml | 20 +++---- primitives/version/Cargo.toml | 20 +++---- primitives/version/proc-macro/Cargo.toml | 4 +- primitives/wasm-interface/Cargo.toml | 12 ++-- scripts/ci/node-template-release/Cargo.toml | 17 +++--- test-utils/Cargo.toml | 4 +- test-utils/client/Cargo.toml | 8 +-- test-utils/derive/Cargo.toml | 4 +- test-utils/runtime/client/Cargo.toml | 18 +++--- .../runtime/transaction-pool/Cargo.toml | 12 ++-- test-utils/test-crate/Cargo.toml | 2 +- utils/frame/benchmarking-cli/Cargo.toml | 57 +++++++++--------- utils/frame/frame-utilities-cli/Cargo.toml | 9 +-- utils/frame/remote-externalities/Cargo.toml | 16 +++-- utils/frame/rpc/support/Cargo.toml | 10 ++-- utils/frame/rpc/system/Cargo.toml | 16 ++--- utils/frame/try-runtime/cli/Cargo.toml | 16 +++-- utils/prometheus/Cargo.toml | 4 +- utils/wasm-builder/Cargo.toml | 4 +- 186 files changed, 1354 insertions(+), 1450 deletions(-) diff --git a/bin/node-template/pallets/template/Cargo.toml b/bin/node-template/pallets/template/Cargo.toml index 686dd38b577b1..0647761a62f9c 100644 --- a/bin/node-template/pallets/template/Cargo.toml +++ b/bin/node-template/pallets/template/Cargo.toml @@ -3,7 +3,7 @@ name = "pallet-template" version = "4.0.0-dev" description = "FRAME pallet template for defining custom runtime logic." authors = ["Substrate DevHub "] -homepage = "https://substrate.io/" +homepage = "https://substrate.io" edition = "2021" license = "Unlicense" publish = false @@ -17,24 +17,23 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = "derive", ] } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -frame-support = { default-features = false, version = "4.0.0-dev", path = "../../../../frame/support" } -frame-system = { default-features = false, version = "4.0.0-dev", path = "../../../../frame/system" } -frame-benchmarking = { default-features = false, version = "4.0.0-dev", path = "../../../../frame/benchmarking", optional = true } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../../../../frame/benchmarking" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../../../../frame/support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../../../../frame/system" } [dev-dependencies] -sp-core = { default-features = false, version = "6.0.0", path = "../../../../primitives/core" } -sp-io = { default-features = false, version = "6.0.0", path = "../../../../primitives/io" } -sp-runtime = { default-features = false, version = "6.0.0", path = "../../../../primitives/runtime" } +sp-core = { version = "6.0.0", default-features = false, path = "../../../../primitives/core" } +sp-io = { version = "6.0.0", default-features = false, path = "../../../../primitives/io" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../../../primitives/runtime" } [features] default = ["std"] std = [ "codec/std", - "scale-info/std", + "frame-benchmarking/std", "frame-support/std", "frame-system/std", - "frame-benchmarking/std", + "scale-info/std", ] - runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] try-runtime = ["frame-support/try-runtime"] diff --git a/bin/node/executor/Cargo.toml b/bin/node/executor/Cargo.toml index 3425a0df65d9b..909bd19103742 100644 --- a/bin/node/executor/Cargo.toml +++ b/bin/node/executor/Cargo.toml @@ -14,6 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } scale-info = { version = "2.0.1", features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", path = "../../../frame/benchmarking" } node-primitives = { version = "2.0.0", path = "../primitives" } node-runtime = { version = "3.0.0-dev", path = "../runtime" } sc-executor = { version = "0.10.0-dev", path = "../../../client/executor" } @@ -22,10 +23,11 @@ sp-keystore = { version = "0.12.0", path = "../../../primitives/keystore" } sp-state-machine = { version = "0.12.0", path = "../../../primitives/state-machine" } sp-tracing = { version = "5.0.0", path = "../../../primitives/tracing" } sp-trie = { version = "6.0.0", path = "../../../primitives/trie" } -frame-benchmarking = { version = "4.0.0-dev", path = "../../../frame/benchmarking" } [dev-dependencies] criterion = "0.3.0" +futures = "0.3.21" +wat = "1.0" frame-support = { version = "4.0.0-dev", path = "../../../frame/support" } frame-system = { version = "4.0.0-dev", path = "../../../frame/system" } node-testing = { version = "3.0.0-dev", path = "../testing" } @@ -36,11 +38,9 @@ pallet-timestamp = { version = "4.0.0-dev", path = "../../../frame/timestamp" } pallet-treasury = { version = "4.0.0-dev", path = "../../../frame/treasury" } sp-application-crypto = { version = "6.0.0", path = "../../../primitives/application-crypto" } sp-consensus-babe = { version = "0.10.0-dev", path = "../../../primitives/consensus/babe" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } sp-externalities = { version = "0.12.0", path = "../../../primitives/externalities" } sp-keyring = { version = "6.0.0", path = "../../../primitives/keyring" } -wat = "1.0" -futures = "0.3.21" +sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } [features] wasmtime = ["sc-executor/wasmtime"] diff --git a/bin/node/primitives/Cargo.toml b/bin/node/primitives/Cargo.toml index 711b30d33933d..810b41003109d 100644 --- a/bin/node/primitives/Cargo.toml +++ b/bin/node/primitives/Cargo.toml @@ -24,8 +24,8 @@ sp-runtime = { version = "6.0.0", default-features = false, path = "../../../pri default = ["std"] std = [ "codec/std", - "scale-info/std", "frame-system/std", + "scale-info/std", "sp-application-crypto/std", "sp-core/std", "sp-runtime/std", diff --git a/bin/node/rpc/Cargo.toml b/bin/node/rpc/Cargo.toml index 6c18e70f0d634..f0ae8b42e6398 100644 --- a/bin/node/rpc/Cargo.toml +++ b/bin/node/rpc/Cargo.toml @@ -16,23 +16,23 @@ node-primitives = { version = "2.0.0", path = "../primitives" } pallet-contracts-rpc = { version = "4.0.0-dev", path = "../../../frame/contracts/rpc/" } pallet-mmr-rpc = { version = "3.0.0", path = "../../../frame/merkle-mountain-range/rpc/" } pallet-transaction-payment-rpc = { version = "4.0.0-dev", path = "../../../frame/transaction-payment/rpc/" } +sc-chain-spec = { version = "4.0.0-dev", path = "../../../client/chain-spec" } sc-client-api = { version = "4.0.0-dev", path = "../../../client/api" } sc-consensus-babe = { version = "0.10.0-dev", path = "../../../client/consensus/babe" } sc-consensus-babe-rpc = { version = "0.10.0-dev", path = "../../../client/consensus/babe/rpc" } sc-consensus-epochs = { version = "0.10.0-dev", path = "../../../client/consensus/epochs" } -sc-chain-spec = { version = "4.0.0-dev", path = "../../../client/chain-spec" } sc-finality-grandpa = { version = "0.10.0-dev", path = "../../../client/finality-grandpa" } sc-finality-grandpa-rpc = { version = "0.10.0-dev", path = "../../../client/finality-grandpa/rpc" } -sc-rpc-api = { version = "0.10.0-dev", path = "../../../client/rpc-api" } sc-rpc = { version = "4.0.0-dev", path = "../../../client/rpc" } +sc-rpc-api = { version = "0.10.0-dev", path = "../../../client/rpc-api" } sc-sync-state-rpc = { version = "0.10.0-dev", path = "../../../client/sync-state-rpc" } +sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../../client/transaction-pool/api" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sp-block-builder = { version = "4.0.0-dev", path = "../../../primitives/block-builder" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -sp-keystore = { version = "0.12.0", path = "../../../primitives/keystore" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } sp-consensus-babe = { version = "0.10.0-dev", path = "../../../primitives/consensus/babe" } +sp-keystore = { version = "0.12.0", path = "../../../primitives/keystore" } sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } -sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../../client/transaction-pool/api" } substrate-frame-rpc-system = { version = "4.0.0-dev", path = "../../../utils/frame/rpc/system" } substrate-state-trie-migration-rpc = { version = "4.0.0-dev", path = "../../../utils/frame/rpc/state-trie-migration-rpc/" } diff --git a/bin/node/testing/Cargo.toml b/bin/node/testing/Cargo.toml index fc072312debaa..c4f97a3e47447 100644 --- a/bin/node/testing/Cargo.toml +++ b/bin/node/testing/Cargo.toml @@ -13,39 +13,39 @@ publish = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sc-service = { version = "0.10.0-dev", features = [ - "test-helpers", - "db", -], path = "../../../client/service" } -sc-client-db = { version = "0.10.0-dev", path = "../../../client/db/", features = [ - "kvdb-rocksdb", - "parity-db", -] } -sc-client-api = { version = "4.0.0-dev", path = "../../../client/api/" } -sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } codec = { package = "parity-scale-codec", version = "3.0.0" } -sp-keyring = { version = "6.0.0", path = "../../../primitives/keyring" } +fs_extra = "1" +futures = "0.3.21" +log = "0.4.16" +tempfile = "3.1.0" +frame-system = { version = "4.0.0-dev", path = "../../../frame/system" } node-executor = { version = "3.0.0-dev", path = "../executor" } node-primitives = { version = "2.0.0", path = "../primitives" } node-runtime = { version = "3.0.0-dev", path = "../runtime" } -sp-core = { version = "6.0.0", path = "../../../primitives/core" } -sp-io = { version = "6.0.0", path = "../../../primitives/io" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } -sc-executor = { version = "0.10.0-dev", path = "../../../client/executor", features = [ - "wasmtime", -] } -sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } -frame-system = { version = "4.0.0-dev", path = "../../../frame/system" } -substrate-test-client = { version = "2.0.0", path = "../../../test-utils/client" } -pallet-transaction-payment = { version = "4.0.0-dev", path = "../../../frame/transaction-payment" } pallet-asset-tx-payment = { version = "4.0.0-dev", path = "../../../frame/transaction-payment/asset-tx-payment/" } +pallet-transaction-payment = { version = "4.0.0-dev", path = "../../../frame/transaction-payment" } +sc-block-builder = { version = "0.10.0-dev", path = "../../../client/block-builder" } +sc-client-api = { version = "4.0.0-dev", path = "../../../client/api/" } +sc-client-db = { version = "0.10.0-dev", features = [ + "kvdb-rocksdb", + "parity-db", +], path = "../../../client/db/" } +sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } +sc-executor = { version = "0.10.0-dev", features = [ + "wasmtime", +], path = "../../../client/executor" } +sc-service = { version = "0.10.0-dev", features = [ + "test-helpers", + "db", +], path = "../../../client/service" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } -sp-timestamp = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/timestamp" } sp-block-builder = { version = "4.0.0-dev", path = "../../../primitives/block-builder" } -sc-block-builder = { version = "0.10.0-dev", path = "../../../client/block-builder" } -sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -log = "0.4.16" -tempfile = "3.1.0" -fs_extra = "1" -futures = "0.3.21" +sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } +sp-core = { version = "6.0.0", path = "../../../primitives/core" } +sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } +sp-io = { version = "6.0.0", path = "../../../primitives/io" } +sp-keyring = { version = "6.0.0", path = "../../../primitives/keyring" } +sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } +sp-timestamp = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/timestamp" } +substrate-test-client = { version = "2.0.0", path = "../../../test-utils/client" } diff --git a/bin/utils/chain-spec-builder/Cargo.toml b/bin/utils/chain-spec-builder/Cargo.toml index 1ea1c402151dd..e179a3308dd62 100644 --- a/bin/utils/chain-spec-builder/Cargo.toml +++ b/bin/utils/chain-spec-builder/Cargo.toml @@ -17,9 +17,8 @@ targets = ["x86_64-unknown-linux-gnu"] ansi_term = "0.12.1" clap = { version = "3.1.6", features = ["derive"] } rand = "0.8" - -sc-keystore = { version = "4.0.0-dev", path = "../../../client/keystore" } -sc-chain-spec = { version = "4.0.0-dev", path = "../../../client/chain-spec" } node-cli = { version = "3.0.0-dev", path = "../../node/cli" } +sc-chain-spec = { version = "4.0.0-dev", path = "../../../client/chain-spec" } +sc-keystore = { version = "4.0.0-dev", path = "../../../client/keystore" } sp-core = { version = "6.0.0", path = "../../../primitives/core" } sp-keystore = { version = "0.12.0", path = "../../../primitives/keystore" } diff --git a/client/allocator/Cargo.toml b/client/allocator/Cargo.toml index 190a150b57fff..ec5c19786b875 100644 --- a/client/allocator/Cargo.toml +++ b/client/allocator/Cargo.toml @@ -14,7 +14,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-core = { version = "6.0.0", path = "../../primitives/core" } -sp-wasm-interface = { version = "6.0.0", path = "../../primitives/wasm-interface" } log = "0.4.16" thiserror = "1.0.30" +sp-core = { version = "6.0.0", path = "../../primitives/core" } +sp-wasm-interface = { version = "6.0.0", path = "../../primitives/wasm-interface" } diff --git a/client/api/Cargo.toml b/client/api/Cargo.toml index 8230f0baf3e72..f69549b60c53e 100644 --- a/client/api/Cargo.toml +++ b/client/api/Cargo.toml @@ -15,30 +15,30 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ - "derive", + "derive", ] } -sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } -sc-executor = { version = "0.10.0-dev", path = "../executor" } -sp-externalities = { version = "0.12.0", path = "../../primitives/externalities" } fnv = "1.0.6" futures = "0.3.21" hash-db = { version = "0.15.2", default-features = false } -sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } log = "0.4.16" parking_lot = "0.12.0" -sp-database = { version = "4.0.0-dev", path = "../../primitives/database" } +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } +sc-executor = { version = "0.10.0-dev", path = "../executor" } +sc-transaction-pool-api = { version = "4.0.0-dev", path = "../transaction-pool/api" } +sc-utils = { version = "4.0.0-dev", path = "../utils" } +sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } +sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } +sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } +sp-database = { version = "4.0.0-dev", path = "../../primitives/database" } +sp-externalities = { version = "0.12.0", path = "../../primitives/externalities" } sp-keystore = { version = "0.12.0", default-features = false, path = "../../primitives/keystore" } -sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } -sc-utils = { version = "4.0.0-dev", path = "../utils" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } sp-state-machine = { version = "0.12.0", path = "../../primitives/state-machine" } -sp-trie = { version = "6.0.0", path = "../../primitives/trie" } sp-storage = { version = "6.0.0", path = "../../primitives/storage" } -sc-transaction-pool-api = { version = "4.0.0-dev", path = "../transaction-pool/api" } -prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } +sp-trie = { version = "6.0.0", path = "../../primitives/trie" } [dev-dependencies] +thiserror = "1.0.30" sp-test-primitives = { version = "2.0.0", path = "../../primitives/test-primitives" } substrate-test-runtime = { version = "2.0.0", path = "../../test-utils/runtime" } -thiserror = "1.0.30" diff --git a/client/authority-discovery/Cargo.toml b/client/authority-discovery/Cargo.toml index 0e107b19ce6ab..996e262ba2b44 100644 --- a/client/authority-discovery/Cargo.toml +++ b/client/authority-discovery/Cargo.toml @@ -18,24 +18,24 @@ prost-build = "0.9" [dependencies] async-trait = "0.1" -codec = { package = "parity-scale-codec", default-features = false, version = "3.0.0" } -thiserror = "1.0" +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } futures = "0.3.21" futures-timer = "3.0.1" ip_network = "0.4.1" libp2p = { version = "0.44.0", default-features = false, features = ["kad"] } log = "0.4.16" -prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus", version = "0.10.0-dev" } prost = "0.9" rand = "0.7.2" +thiserror = "1.0" +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } sc-client-api = { version = "4.0.0-dev", path = "../api" } sc-network = { version = "0.10.0-dev", path = "../network" } +sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } sp-authority-discovery = { version = "4.0.0-dev", path = "../../primitives/authority-discovery" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } sp-core = { version = "6.0.0", path = "../../primitives/core" } sp-keystore = { version = "0.12.0", path = "../../primitives/keystore" } sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } -sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } [dev-dependencies] quickcheck = "1.0.3" diff --git a/client/basic-authorship/Cargo.toml b/client/basic-authorship/Cargo.toml index 6e0c15e1af2cc..6ef679d7995e6 100644 --- a/client/basic-authorship/Cargo.toml +++ b/client/basic-authorship/Cargo.toml @@ -17,20 +17,20 @@ codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.21" futures-timer = "3.0.1" log = "0.4.16" -prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus", version = "0.10.0-dev"} +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } +sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } +sc-client-api = { version = "4.0.0-dev", path = "../api" } +sc-proposer-metrics = { version = "0.10.0-dev", path = "../proposer-metrics" } +sc-telemetry = { version = "4.0.0-dev", path = "../telemetry" } +sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../client/transaction-pool/api" } sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } -sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } -sc-client-api = { version = "4.0.0-dev", path = "../api" } sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } +sp-core = { version = "6.0.0", path = "../../primitives/core" } sp-inherents = { version = "4.0.0-dev", path = "../../primitives/inherents" } -sc-telemetry = { version = "4.0.0-dev", path = "../telemetry" } -sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../client/transaction-pool/api" } -sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } -sc-proposer-metrics = { version = "0.10.0-dev", path = "../proposer-metrics" } +sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } [dev-dependencies] +parking_lot = "0.12.0" sc-transaction-pool = { version = "4.0.0-dev", path = "../transaction-pool" } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } -parking_lot = "0.12.0" diff --git a/client/beefy/Cargo.toml b/client/beefy/Cargo.toml index 96248249200af..4b6496e52c2f2 100644 --- a/client/beefy/Cargo.toml +++ b/client/beefy/Cargo.toml @@ -6,8 +6,10 @@ edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" repository = "https://github.com/paritytech/substrate" description = "BEEFY Client gadget for substrate" +homepage = "https://substrate.io" [dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } fnv = "1.0.6" futures = "0.3" futures-timer = "3.0.1" @@ -16,10 +18,15 @@ log = "0.4" parking_lot = "0.12.0" thiserror = "1.0" wasm-timer = "0.2.5" - -codec = { version = "3.0.0", package = "parity-scale-codec", features = ["derive"] } -prometheus = { version = "0.10.0-dev", package = "substrate-prometheus-endpoint", path = "../../utils/prometheus" } - +beefy-primitives = { version = "4.0.0-dev", path = "../../primitives/beefy" } +prometheus = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } +sc-chain-spec = { version = "4.0.0-dev", path = "../../client/chain-spec" } +sc-client-api = { version = "4.0.0-dev", path = "../api" } +sc-finality-grandpa = { version = "0.10.0-dev", path = "../../client/finality-grandpa" } +sc-keystore = { version = "4.0.0-dev", path = "../keystore" } +sc-network = { version = "0.10.0-dev", path = "../network" } +sc-network-gossip = { version = "0.10.0-dev", path = "../network-gossip" } +sc-utils = { version = "4.0.0-dev", path = "../utils" } sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } sp-application-crypto = { version = "6.0.0", path = "../../primitives/application-crypto" } sp-arithmetic = { version = "5.0.0", path = "../../primitives/arithmetic" } @@ -30,26 +37,14 @@ sp-keystore = { version = "0.12.0", path = "../../primitives/keystore" } sp-mmr-primitives = { version = "4.0.0-dev", path = "../../primitives/merkle-mountain-range" } sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } -sc-chain-spec = { version = "4.0.0-dev", path = "../../client/chain-spec" } -sc-client-api = { version = "4.0.0-dev", path = "../api" } -sc-finality-grandpa = { version = "0.10.0-dev", path = "../../client/finality-grandpa" } -sc-keystore = { version = "4.0.0-dev", path = "../keystore" } -sc-network = { version = "0.10.0-dev", path = "../network" } -sc-network-gossip = { version = "0.10.0-dev", path = "../network-gossip" } -sc-utils = { version = "4.0.0-dev", path = "../utils" } - -beefy-primitives = { version = "4.0.0-dev", path = "../../primitives/beefy" } - [dev-dependencies] +serde = "1.0.136" +strum = { version = "0.23", features = ["derive"] } +tempfile = "3.1.0" +tokio = "1.15" sc-consensus = { version = "0.10.0-dev", path = "../consensus/common" } sc-network-test = { version = "0.8.0", path = "../network/test" } - sp-finality-grandpa = { version = "4.0.0-dev", path = "../../primitives/finality-grandpa" } sp-keyring = { version = "6.0.0", path = "../../primitives/keyring" } sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } - -serde = "1.0.136" -strum = { version = "0.23", features = ["derive"] } -tokio = "1.15" -tempfile = "3.1.0" diff --git a/client/beefy/rpc/Cargo.toml b/client/beefy/rpc/Cargo.toml index 47f2558c5ee71..adf29f1cef732 100644 --- a/client/beefy/rpc/Cargo.toml +++ b/client/beefy/rpc/Cargo.toml @@ -6,34 +6,29 @@ edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" repository = "https://github.com/paritytech/substrate" description = "RPC for the BEEFY Client gadget for substrate" +homepage = "https://substrate.io" [dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } futures = "0.3.21" -log = "0.4" -parking_lot = "0.12.0" -thiserror = "1.0" -serde = { version = "1.0.136", features = ["derive"] } - jsonrpc-core = "18.0.0" jsonrpc-core-client = "18.0.0" jsonrpc-derive = "18.0.0" jsonrpc-pubsub = "18.0.0" - -codec = { version = "3.0.0", package = "parity-scale-codec", features = ["derive"] } - +log = "0.4" +parking_lot = "0.12.0" +serde = { version = "1.0.136", features = ["derive"] } +thiserror = "1.0" +beefy-gadget = { version = "4.0.0-dev", path = "../." } +beefy-primitives = { version = "4.0.0-dev", path = "../../../primitives/beefy" } sc-rpc = { version = "4.0.0-dev", path = "../../rpc" } sc-utils = { version = "4.0.0-dev", path = "../../utils" } - sp-core = { version = "6.0.0", path = "../../../primitives/core" } sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } -beefy-gadget = { version = "4.0.0-dev", path = "../." } -beefy-primitives = { version = "4.0.0-dev", path = "../../../primitives/beefy" } - [dev-dependencies] serde_json = "1.0.79" - -sc-rpc = { version = "4.0.0-dev", path = "../../rpc", features = [ - "test-helpers", -] } +sc-rpc = { version = "4.0.0-dev", features = [ + "test-helpers", +], path = "../../rpc" } substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } diff --git a/client/block-builder/Cargo.toml b/client/block-builder/Cargo.toml index fff7740ff8d04..69b84132fe90b 100644 --- a/client/block-builder/Cargo.toml +++ b/client/block-builder/Cargo.toml @@ -12,19 +12,18 @@ readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] - [dependencies] -sp-state-machine = { version = "0.12.0", path = "../../primitives/state-machine" } -sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } +codec = { package = "parity-scale-codec", version = "3.0.0", features = [ + "derive", +] } +sc-client-api = { version = "4.0.0-dev", path = "../api" } sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } +sp-block-builder = { version = "4.0.0-dev", path = "../../primitives/block-builder" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } sp-core = { version = "6.0.0", path = "../../primitives/core" } -sp-block-builder = { version = "4.0.0-dev", path = "../../primitives/block-builder" } sp-inherents = { version = "4.0.0-dev", path = "../../primitives/inherents" } -sc-client-api = { version = "4.0.0-dev", path = "../api" } -codec = { package = "parity-scale-codec", version = "3.0.0", features = [ - "derive", -] } +sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } +sp-state-machine = { version = "0.12.0", path = "../../primitives/state-machine" } [dev-dependencies] substrate-test-runtime-client = { path = "../../test-utils/runtime/client" } diff --git a/client/chain-spec/Cargo.toml b/client/chain-spec/Cargo.toml index bd19fd59d699e..6ab559dea46fd 100644 --- a/client/chain-spec/Cargo.toml +++ b/client/chain-spec/Cargo.toml @@ -13,13 +13,13 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sc-chain-spec-derive = { version = "4.0.0-dev", path = "./derive" } +codec = { package = "parity-scale-codec", version = "3.0.0" } impl-trait-for-tuples = "0.2.2" -sc-network = { version = "0.10.0-dev", path = "../network" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } +memmap2 = "0.5.0" serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0.79" -sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } +sc-chain-spec-derive = { version = "4.0.0-dev", path = "./derive" } +sc-network = { version = "0.10.0-dev", path = "../network" } sc-telemetry = { version = "4.0.0-dev", path = "../telemetry" } -codec = { package = "parity-scale-codec", version = "3.0.0" } -memmap2 = "0.5.0" +sp-core = { version = "6.0.0", path = "../../primitives/core" } +sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } diff --git a/client/chain-spec/derive/Cargo.toml b/client/chain-spec/derive/Cargo.toml index 8cb5db5e42d91..06f397e1e7b10 100644 --- a/client/chain-spec/derive/Cargo.toml +++ b/client/chain-spec/derive/Cargo.toml @@ -19,5 +19,3 @@ proc-macro-crate = "1.1.3" proc-macro2 = "1.0.37" quote = "1.0.10" syn = "1.0.82" - -[dev-dependencies] diff --git a/client/cli/Cargo.toml b/client/cli/Cargo.toml index 0495f522c1ade..1a504f354ee87 100644 --- a/client/cli/Cargo.toml +++ b/client/cli/Cargo.toml @@ -21,6 +21,7 @@ hex = "0.4.2" libp2p = "0.44.0" log = "0.4.16" names = { version = "0.13.0", default-features = false } +parity-scale-codec = "3.0.0" rand = "0.7.3" regex = "1.5.5" rpassword = "5.0.0" @@ -29,8 +30,6 @@ serde_json = "1.0.79" thiserror = "1.0.30" tiny-bip39 = "0.8.2" tokio = { version = "1.17.0", features = ["signal", "rt-multi-thread", "parking_lot"] } - -parity-scale-codec = "3.0.0" sc-client-api = { version = "4.0.0-dev", path = "../api" } sc-client-db = { version = "0.10.0-dev", path = "../db" } sc-keystore = { version = "4.0.0-dev", path = "../keystore" } diff --git a/client/consensus/aura/Cargo.toml b/client/consensus/aura/Cargo.toml index beaa29d364672..448cf2549f2d9 100644 --- a/client/consensus/aura/Cargo.toml +++ b/client/consensus/aura/Cargo.toml @@ -13,36 +13,36 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-application-crypto = { version = "6.0.0", path = "../../../primitives/application-crypto" } -sp-consensus-aura = { version = "0.10.0-dev", path = "../../../primitives/consensus/aura" } -sp-block-builder = { version = "4.0.0-dev", path = "../../../primitives/block-builder" } -sc-block-builder = { version = "0.10.0-dev", path = "../../block-builder" } -sc-client-api = { version = "4.0.0-dev", path = "../../api" } +async-trait = "0.1.50" codec = { package = "parity-scale-codec", version = "3.0.0" } -sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } -sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } -sp-consensus-slots = { version = "0.10.0-dev", path = "../../../primitives/consensus/slots" } -thiserror = "1.0" futures = "0.3.21" -sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } log = "0.4.16" -sp-core = { version = "6.0.0", path = "../../../primitives/core" } -sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } +thiserror = "1.0" +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../../utils/prometheus" } +sc-block-builder = { version = "0.10.0-dev", path = "../../block-builder" } +sc-client-api = { version = "4.0.0-dev", path = "../../api" } +sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } sc-consensus-slots = { version = "0.10.0-dev", path = "../slots" } +sc-telemetry = { version = "4.0.0-dev", path = "../../telemetry" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } +sp-application-crypto = { version = "6.0.0", path = "../../../primitives/application-crypto" } +sp-block-builder = { version = "4.0.0-dev", path = "../../../primitives/block-builder" } +sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } +sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } +sp-consensus-aura = { version = "0.10.0-dev", path = "../../../primitives/consensus/aura" } +sp-consensus-slots = { version = "0.10.0-dev", path = "../../../primitives/consensus/slots" } +sp-core = { version = "6.0.0", path = "../../../primitives/core" } +sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } sp-keystore = { version = "0.12.0", path = "../../../primitives/keystore" } -sc-telemetry = { version = "4.0.0-dev", path = "../../telemetry" } -prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus", version = "0.10.0-dev" } -async-trait = "0.1.50" +sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } [dev-dependencies] -sp-timestamp = { version = "4.0.0-dev", path = "../../../primitives/timestamp" } -sp-keyring = { version = "6.0.0", path = "../../../primitives/keyring" } -sp-tracing = { version = "5.0.0", path = "../../../primitives/tracing" } +parking_lot = "0.12.0" +tempfile = "3.1.0" sc-keystore = { version = "4.0.0-dev", path = "../../keystore" } sc-network = { version = "0.10.0-dev", path = "../../network" } sc-network-test = { version = "0.8.0", path = "../../network/test" } +sp-keyring = { version = "6.0.0", path = "../../../primitives/keyring" } +sp-timestamp = { version = "4.0.0-dev", path = "../../../primitives/timestamp" } +sp-tracing = { version = "5.0.0", path = "../../../primitives/tracing" } substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } -tempfile = "3.1.0" -parking_lot = "0.12.0" diff --git a/client/consensus/babe/Cargo.toml b/client/consensus/babe/Cargo.toml index 48313cd0437e0..f421fdd36e37b 100644 --- a/client/consensus/babe/Cargo.toml +++ b/client/consensus/babe/Cargo.toml @@ -14,51 +14,51 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +async-trait = "0.1.50" codec = { package = "parity-scale-codec", version = "3.0.0", features = [ - "derive", + "derive", ] } -sp-consensus-babe = { version = "0.10.0-dev", path = "../../../primitives/consensus/babe" } -sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } -sp-core = { version = "6.0.0", path = "../../../primitives/core" } -sp-application-crypto = { version = "6.0.0", path = "../../../primitives/application-crypto" } -sp-keystore = { version = "0.12.0", path = "../../../primitives/keystore" } +futures = "0.3.21" +log = "0.4.16" +merlin = "2.0" num-bigint = "0.2.3" num-rational = "0.2.2" num-traits = "0.2.8" +parking_lot = "0.12.0" +rand = "0.7.2" +retain_mut = "0.1.4" +schnorrkel = { version = "0.9.1", features = ["preaudit_deprecated"] } serde = { version = "1.0.136", features = ["derive"] } -sp-version = { version = "5.0.0", path = "../../../primitives/version" } -sp-io = { version = "6.0.0", path = "../../../primitives/io" } -sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } -sc-telemetry = { version = "4.0.0-dev", path = "../../telemetry" } -sc-keystore = { version = "4.0.0-dev", path = "../../keystore" } +thiserror = "1.0" +fork-tree = { version = "3.0.0", path = "../../../utils/fork-tree" } +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../../utils/prometheus" } sc-client-api = { version = "4.0.0-dev", path = "../../api" } +sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } sc-consensus-epochs = { version = "0.10.0-dev", path = "../epochs" } +sc-consensus-slots = { version = "0.10.0-dev", path = "../slots" } +sc-keystore = { version = "4.0.0-dev", path = "../../keystore" } +sc-telemetry = { version = "4.0.0-dev", path = "../../telemetry" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } +sp-application-crypto = { version = "6.0.0", path = "../../../primitives/application-crypto" } sp-block-builder = { version = "4.0.0-dev", path = "../../../primitives/block-builder" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } +sp-consensus-babe = { version = "0.10.0-dev", path = "../../../primitives/consensus/babe" } sp-consensus-slots = { version = "0.10.0-dev", path = "../../../primitives/consensus/slots" } sp-consensus-vrf = { version = "0.10.0-dev", path = "../../../primitives/consensus/vrf" } -sc-consensus-slots = { version = "0.10.0-dev", path = "../slots" } +sp-core = { version = "6.0.0", path = "../../../primitives/core" } +sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } +sp-io = { version = "6.0.0", path = "../../../primitives/io" } +sp-keystore = { version = "0.12.0", path = "../../../primitives/keystore" } sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } -fork-tree = { version = "3.0.0", path = "../../../utils/fork-tree" } -prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus", version = "0.10.0-dev" } -futures = "0.3.21" -parking_lot = "0.12.0" -log = "0.4.16" -schnorrkel = { version = "0.9.1", features = ["preaudit_deprecated"] } -rand = "0.7.2" -merlin = "2.0" -thiserror = "1.0" -retain_mut = "0.1.4" -async-trait = "0.1.50" +sp-version = { version = "5.0.0", path = "../../../primitives/version" } [dev-dependencies] -sp-timestamp = { version = "4.0.0-dev", path = "../../../primitives/timestamp" } -sp-tracing = { version = "5.0.0", path = "../../../primitives/tracing" } +rand_chacha = "0.2.2" +tempfile = "3.1.0" +sc-block-builder = { version = "0.10.0-dev", path = "../../block-builder" } sc-network = { version = "0.10.0-dev", path = "../../network" } sc-network-test = { version = "0.8.0", path = "../../network/test" } +sp-timestamp = { version = "4.0.0-dev", path = "../../../primitives/timestamp" } +sp-tracing = { version = "5.0.0", path = "../../../primitives/tracing" } substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } -sc-block-builder = { version = "0.10.0-dev", path = "../../block-builder" } -rand_chacha = "0.2.2" -tempfile = "3.1.0" diff --git a/client/consensus/babe/rpc/Cargo.toml b/client/consensus/babe/rpc/Cargo.toml index cce4544f09705..0e7141c77f8b2 100644 --- a/client/consensus/babe/rpc/Cargo.toml +++ b/client/consensus/babe/rpc/Cargo.toml @@ -13,28 +13,28 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sc-consensus-babe = { version = "0.10.0-dev", path = "../" } -sc-rpc-api = { version = "0.10.0-dev", path = "../../../rpc-api" } +futures = "0.3.21" jsonrpc-core = "18.0.0" jsonrpc-core-client = "18.0.0" jsonrpc-derive = "18.0.0" -sp-consensus-babe = { version = "0.10.0-dev", path = "../../../../primitives/consensus/babe" } serde = { version = "1.0.136", features = ["derive"] } -sp-blockchain = { version = "4.0.0-dev", path = "../../../../primitives/blockchain" } -sp-runtime = { version = "6.0.0", path = "../../../../primitives/runtime" } -sc-consensus-epochs = { version = "0.10.0-dev", path = "../../epochs" } -futures = "0.3.21" thiserror = "1.0" +sc-consensus-babe = { version = "0.10.0-dev", path = "../" } +sc-consensus-epochs = { version = "0.10.0-dev", path = "../../epochs" } +sc-rpc-api = { version = "0.10.0-dev", path = "../../../rpc-api" } sp-api = { version = "4.0.0-dev", path = "../../../../primitives/api" } +sp-application-crypto = { version = "6.0.0", path = "../../../../primitives/application-crypto" } +sp-blockchain = { version = "4.0.0-dev", path = "../../../../primitives/blockchain" } sp-consensus = { version = "0.10.0-dev", path = "../../../../primitives/consensus/common" } +sp-consensus-babe = { version = "0.10.0-dev", path = "../../../../primitives/consensus/babe" } sp-core = { version = "6.0.0", path = "../../../../primitives/core" } -sp-application-crypto = { version = "6.0.0", path = "../../../../primitives/application-crypto" } sp-keystore = { version = "0.12.0", path = "../../../../primitives/keystore" } +sp-runtime = { version = "6.0.0", path = "../../../../primitives/runtime" } [dev-dependencies] -sc-consensus = { version = "0.10.0-dev", path = "../../../consensus/common" } serde_json = "1.0.79" -sp-keyring = { version = "6.0.0", path = "../../../../primitives/keyring" } +tempfile = "3.1.0" +sc-consensus = { version = "0.10.0-dev", path = "../../../consensus/common" } sc-keystore = { version = "4.0.0-dev", path = "../../../keystore" } +sp-keyring = { version = "6.0.0", path = "../../../../primitives/keyring" } substrate-test-runtime-client = { version = "2.0.0", path = "../../../../test-utils/runtime/client" } -tempfile = "3.1.0" diff --git a/client/consensus/common/Cargo.toml b/client/consensus/common/Cargo.toml index 2846a3e7898c5..4f8d7befb0d63 100644 --- a/client/consensus/common/Cargo.toml +++ b/client/consensus/common/Cargo.toml @@ -13,23 +13,23 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -thiserror = "1.0.30" -libp2p = { version = "0.44.0", default-features = false } -log = "0.4.16" +async-trait = "0.1.42" futures = { version = "0.3.21", features = ["thread-pool"] } futures-timer = "3.0.1" +libp2p = { version = "0.44.0", default-features = false } +log = "0.4.16" +parking_lot = "0.12.0" +serde = { version = "1.0", features = ["derive"] } +thiserror = "1.0.30" +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../../utils/prometheus" } sc-client-api = { version = "4.0.0-dev", path = "../../api" } -sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -sp-core = { path = "../../../primitives/core", version = "6.0.0"} -sp-consensus = { path = "../../../primitives/consensus/common", version = "0.10.0-dev" } -sp-state-machine = { version = "0.12.0", path = "../../../primitives/state-machine" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } sc-utils = { version = "4.0.0-dev", path = "../../utils" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } -parking_lot = "0.12.0" -serde = { version = "1.0", features = ["derive"] } -prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus", version = "0.10.0-dev" } -async-trait = "0.1.42" +sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } +sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } +sp-core = { version = "6.0.0", path = "../../../primitives/core" } +sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } +sp-state-machine = { version = "0.12.0", path = "../../../primitives/state-machine" } [dev-dependencies] sp-test-primitives = { version = "2.0.0", path = "../../../primitives/test-primitives" } diff --git a/client/consensus/epochs/Cargo.toml b/client/consensus/epochs/Cargo.toml index 2caf60547cceb..5c52b76185200 100644 --- a/client/consensus/epochs/Cargo.toml +++ b/client/consensus/epochs/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } fork-tree = { version = "3.0.0", path = "../../../utils/fork-tree" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } +sc-client-api = { version = "4.0.0-dev", path = "../../api" } +sc-consensus = { version = "0.10.0-dev", path = "../common" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -sc-client-api = { path = "../../api" , version = "4.0.0-dev"} -sc-consensus = { path = "../common" , version = "0.10.0-dev"} +sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } diff --git a/client/consensus/manual-seal/Cargo.toml b/client/consensus/manual-seal/Cargo.toml index c1568ea488337..9452d2b1afd08 100644 --- a/client/consensus/manual-seal/Cargo.toml +++ b/client/consensus/manual-seal/Cargo.toml @@ -13,41 +13,38 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -thiserror = "1.0" +assert_matches = "1.3.0" +async-trait = "0.1.50" +codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.21" jsonrpc-core = "18.0.0" jsonrpc-core-client = "18.0.0" jsonrpc-derive = "18.0.0" log = "0.4.16" -codec = { package = "parity-scale-codec", version = "3.0.0" } serde = { version = "1.0", features = ["derive"] } -assert_matches = "1.3.0" -async-trait = "0.1.50" - -sc-client-api = { path = "../../api", version = "4.0.0-dev" } +thiserror = "1.0" +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../../utils/prometheus" } +sc-client-api = { version = "4.0.0-dev", path = "../../api" } sc-consensus = { version = "0.10.0-dev", path = "../../consensus/common" } -sc-consensus-babe = { path = "../../consensus/babe", version = "0.10.0-dev" } -sc-consensus-aura = { path = "../../consensus/aura", version = "0.10.0-dev" } -sc-consensus-epochs = { path = "../../consensus/epochs", version = "0.10.0-dev" } -sp-consensus-babe = { path = "../../../primitives/consensus/babe", version = "0.10.0-dev" } -sp-consensus-aura = { path = "../../../primitives/consensus/aura", version = "0.10.0-dev" } - -sc-transaction-pool = { path = "../../transaction-pool", version = "4.0.0-dev" } -sp-blockchain = { path = "../../../primitives/blockchain", version = "4.0.0-dev" } -sp-consensus = { path = "../../../primitives/consensus/common", version = "0.10.0-dev" } -sp-consensus-slots = { path = "../../../primitives/consensus/slots", version = "0.10.0-dev" } -sp-inherents = { path = "../../../primitives/inherents", version = "4.0.0-dev" } -sp-runtime = { path = "../../../primitives/runtime", version = "6.0.0"} -sp-core = { path = "../../../primitives/core", version = "6.0.0"} -sp-keystore = { path = "../../../primitives/keystore", version = "0.12.0"} -sp-api = { path = "../../../primitives/api", version = "4.0.0-dev" } -sc-transaction-pool-api = { path = "../../../client/transaction-pool/api", version = "4.0.0-dev" } -sp-timestamp = { path = "../../../primitives/timestamp", version = "4.0.0-dev" } - -prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus", version = "0.10.0-dev" } +sc-consensus-aura = { version = "0.10.0-dev", path = "../../consensus/aura" } +sc-consensus-babe = { version = "0.10.0-dev", path = "../../consensus/babe" } +sc-consensus-epochs = { version = "0.10.0-dev", path = "../../consensus/epochs" } +sc-transaction-pool = { version = "4.0.0-dev", path = "../../transaction-pool" } +sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../../client/transaction-pool/api" } +sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } +sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } +sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } +sp-consensus-aura = { version = "0.10.0-dev", path = "../../../primitives/consensus/aura" } +sp-consensus-babe = { version = "0.10.0-dev", path = "../../../primitives/consensus/babe" } +sp-consensus-slots = { version = "0.10.0-dev", path = "../../../primitives/consensus/slots" } +sp-core = { version = "6.0.0", path = "../../../primitives/core" } +sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } +sp-keystore = { version = "0.12.0", path = "../../../primitives/keystore" } +sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } +sp-timestamp = { version = "4.0.0-dev", path = "../../../primitives/timestamp" } [dev-dependencies] tokio = { version = "1.17.0", features = ["rt-multi-thread", "macros"] } -sc-basic-authorship = { path = "../../basic-authorship", version = "0.10.0-dev" } -substrate-test-runtime-client = { path = "../../../test-utils/runtime/client", version = "2.0.0" } -substrate-test-runtime-transaction-pool = { path = "../../../test-utils/runtime/transaction-pool", version = "2.0.0" } +sc-basic-authorship = { version = "0.10.0-dev", path = "../../basic-authorship" } +substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } +substrate-test-runtime-transaction-pool = { version = "2.0.0", path = "../../../test-utils/runtime/transaction-pool" } diff --git a/client/consensus/pow/Cargo.toml b/client/consensus/pow/Cargo.toml index fc0151d293bbd..cc65687f80e13 100644 --- a/client/consensus/pow/Cargo.toml +++ b/client/consensus/pow/Cargo.toml @@ -13,21 +13,21 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +async-trait = "0.1.50" codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } -sp-core = { version = "6.0.0", path = "../../../primitives/core" } -sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } -sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } -sc-client-api = { version = "4.0.0-dev", path = "../../api" } -sp-block-builder = { version = "4.0.0-dev", path = "../../../primitives/block-builder" } -sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } -sp-consensus-pow = { version = "0.10.0-dev", path = "../../../primitives/consensus/pow" } -sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } -sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } -log = "0.4.16" futures = "0.3.21" futures-timer = "3.0.1" +log = "0.4.16" parking_lot = "0.12.0" thiserror = "1.0" -prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../../utils/prometheus", version = "0.10.0-dev"} -async-trait = "0.1.50" +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../../utils/prometheus" } +sc-client-api = { version = "4.0.0-dev", path = "../../api" } +sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } +sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } +sp-block-builder = { version = "4.0.0-dev", path = "../../../primitives/block-builder" } +sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } +sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } +sp-consensus-pow = { version = "0.10.0-dev", path = "../../../primitives/consensus/pow" } +sp-core = { version = "6.0.0", path = "../../../primitives/core" } +sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } +sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } diff --git a/client/consensus/slots/Cargo.toml b/client/consensus/slots/Cargo.toml index 8689818de8c17..27528b68e75ea 100644 --- a/client/consensus/slots/Cargo.toml +++ b/client/consensus/slots/Cargo.toml @@ -14,24 +14,24 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +async-trait = "0.1.50" codec = { package = "parity-scale-codec", version = "3.0.0" } +futures = "0.3.21" +futures-timer = "3.0.1" +log = "0.4.16" +thiserror = "1.0.30" sc-client-api = { version = "4.0.0-dev", path = "../../api" } -sp-core = { version = "6.0.0", path = "../../../primitives/core" } +sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } +sc-telemetry = { version = "4.0.0-dev", path = "../../telemetry" } sp-arithmetic = { version = "5.0.0", path = "../../../primitives/arithmetic" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } +sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } sp-consensus-slots = { version = "0.10.0-dev", path = "../../../primitives/consensus/slots" } +sp-core = { version = "6.0.0", path = "../../../primitives/core" } +sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } sp-state-machine = { version = "0.12.0", path = "../../../primitives/state-machine" } -sc-telemetry = { version = "4.0.0-dev", path = "../../telemetry" } -sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } -sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } sp-timestamp = { version = "4.0.0-dev", path = "../../../primitives/timestamp" } -futures = "0.3.21" -futures-timer = "3.0.1" -log = "0.4.16" -thiserror = "1.0.30" -async-trait = "0.1.50" [dev-dependencies] substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } diff --git a/client/consensus/uncles/Cargo.toml b/client/consensus/uncles/Cargo.toml index c512eb7e9f832..cf0aaf5cd30d7 100644 --- a/client/consensus/uncles/Cargo.toml +++ b/client/consensus/uncles/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +thiserror = "1.0.30" sc-client-api = { version = "4.0.0-dev", path = "../../api" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } sp-authorship = { version = "4.0.0-dev", path = "../../../primitives/authorship" } -thiserror = "1.0.30" +sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } diff --git a/client/db/Cargo.toml b/client/db/Cargo.toml index 9b6faaafbdb62..3c20cec72c695 100644 --- a/client/db/Cargo.toml +++ b/client/db/Cargo.toml @@ -13,34 +13,33 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -parking_lot = "0.12.0" -log = "0.4.16" +codec = { package = "parity-scale-codec", version = "3.0.0", features = [ + "derive", +] } +hash-db = "0.15.2" kvdb = "0.11.0" -kvdb-rocksdb = { version = "0.15.2", optional = true } kvdb-memorydb = "0.11.0" +kvdb-rocksdb = { version = "0.15.2", optional = true } linked-hash-map = "0.5.4" -hash-db = "0.15.2" -codec = { package = "parity-scale-codec", version = "3.0.0", features = [ - "derive", -] } - +log = "0.4.16" +parity-db = { version = "0.3.12", optional = true } +parking_lot = "0.12.0" sc-client-api = { version = "4.0.0-dev", path = "../api" } +sc-state-db = { version = "0.10.0-dev", path = "../state-db" } sp-arithmetic = { version = "5.0.0", path = "../../primitives/arithmetic" } +sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } sp-core = { version = "6.0.0", path = "../../primitives/core" } +sp-database = { version = "4.0.0-dev", path = "../../primitives/database" } sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } sp-state-machine = { version = "0.12.0", path = "../../primitives/state-machine" } -sc-state-db = { version = "0.10.0-dev", path = "../state-db" } sp-trie = { version = "6.0.0", path = "../../primitives/trie" } -sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } -sp-database = { version = "4.0.0-dev", path = "../../primitives/database" } -parity-db = { version = "0.3.12", optional = true } [dev-dependencies] -sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } -substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } -quickcheck = "1.0.3" kvdb-rocksdb = "0.15.1" +quickcheck = "1.0.3" tempfile = "3" +sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } +substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } [features] default = [] diff --git a/client/executor/common/Cargo.toml b/client/executor/common/Cargo.toml index 677148f709e6b..9ffdfb788474d 100644 --- a/client/executor/common/Cargo.toml +++ b/client/executor/common/Cargo.toml @@ -14,13 +14,12 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0" } environmental = "1.1.3" thiserror = "1.0.30" wasm-instrument = "0.1" -wasmi = "0.9.1" wasmer = { version = "2.2", features = ["singlepass"], optional = true } - -codec = { package = "parity-scale-codec", version = "3.0.0" } +wasmi = "0.9.1" sc-allocator = { version = "4.1.0-dev", path = "../../allocator" } sp-maybe-compressed-blob = { version = "4.1.0-dev", path = "../../../primitives/maybe-compressed-blob" } sp-sandbox = { version = "0.10.0-dev", path = "../../../primitives/sandbox" } diff --git a/client/executor/runtime-test/Cargo.toml b/client/executor/runtime-test/Cargo.toml index 507b35114d630..7a7848700c4c1 100644 --- a/client/executor/runtime-test/Cargo.toml +++ b/client/executor/runtime-test/Cargo.toml @@ -14,9 +14,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] paste = "1.0.6" - sp-core = { version = "6.0.0", default-features = false, path = "../../../primitives/core" } -sp-io = { version = "6.0.0", default-features = false, path = "../../../primitives/io", features = ["improved_panic_error_reporting"] } +sp-io = { version = "6.0.0", default-features = false, features = ["improved_panic_error_reporting"], path = "../../../primitives/io" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../../primitives/runtime" } sp-sandbox = { version = "0.10.0-dev", default-features = false, path = "../../../primitives/sandbox" } sp-std = { version = "4.0.0", default-features = false, path = "../../../primitives/std" } diff --git a/client/executor/wasmi/Cargo.toml b/client/executor/wasmi/Cargo.toml index 9d913d1e9cc3e..153deab49a305 100644 --- a/client/executor/wasmi/Cargo.toml +++ b/client/executor/wasmi/Cargo.toml @@ -14,12 +14,11 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0" } log = "0.4.16" wasmi = "0.9.1" - -codec = { package = "parity-scale-codec", version = "3.0.0" } sc-allocator = { version = "4.1.0-dev", path = "../../allocator" } sc-executor-common = { version = "0.10.0-dev", path = "../common" } -sp-sandbox = { version = "0.10.0-dev", path = "../../../primitives/sandbox" } sp-runtime-interface = { version = "6.0.0", path = "../../../primitives/runtime-interface" } +sp-sandbox = { version = "0.10.0-dev", path = "../../../primitives/sandbox" } sp-wasm-interface = { version = "6.0.0", path = "../../../primitives/wasm-interface" } diff --git a/client/executor/wasmtime/Cargo.toml b/client/executor/wasmtime/Cargo.toml index 5871629f47c0a..845fdfb72901c 100644 --- a/client/executor/wasmtime/Cargo.toml +++ b/client/executor/wasmtime/Cargo.toml @@ -14,6 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] cfg-if = "1.0" +codec = { package = "parity-scale-codec", version = "3.0.0" } libc = "0.2.121" log = "0.4.16" parity-wasm = "0.42.0" @@ -23,15 +24,13 @@ wasmtime = { version = "0.35.3", default-features = false, features = [ "jitdump", "parallel-compilation", ] } - -codec = { package = "parity-scale-codec", version = "3.0.0" } sc-allocator = { version = "4.1.0-dev", path = "../../allocator" } sc-executor-common = { version = "0.10.0-dev", path = "../common" } sp-runtime-interface = { version = "6.0.0", path = "../../../primitives/runtime-interface" } sp-sandbox = { version = "0.10.0-dev", path = "../../../primitives/sandbox" } -sp-wasm-interface = { version = "6.0.0", path = "../../../primitives/wasm-interface", features = ["wasmtime"] } +sp-wasm-interface = { version = "6.0.0", features = ["wasmtime"], path = "../../../primitives/wasm-interface" } [dev-dependencies] +wat = "1.0" sc-runtime-test = { version = "2.0.0", path = "../runtime-test" } sp-io = { version = "6.0.0", path = "../../../primitives/io" } -wat = "1.0" diff --git a/client/finality-grandpa/Cargo.toml b/client/finality-grandpa/Cargo.toml index 2d011fb96be59..a0e53da23fc54 100644 --- a/client/finality-grandpa/Cargo.toml +++ b/client/finality-grandpa/Cargo.toml @@ -14,52 +14,51 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -thiserror = "1.0" +ahash = "0.7.6" +async-trait = "0.1.50" dyn-clone = "1.0" -fork-tree = { version = "3.0.0", path = "../../utils/fork-tree" } +finality-grandpa = { version = "0.15.0", features = ["derive-codec"] } futures = "0.3.21" futures-timer = "3.0.1" hex = "0.4.2" log = "0.4.16" +parity-scale-codec = { version = "3.0.0", features = ["derive"] } parking_lot = "0.12.0" rand = "0.8.4" -ahash = "0.7.6" -parity-scale-codec = { version = "3.0.0", features = ["derive"] } -sp-application-crypto = { version = "6.0.0", path = "../../primitives/application-crypto" } -sp-arithmetic = { version = "5.0.0", path = "../../primitives/arithmetic" } -sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } +serde_json = "1.0.79" +thiserror = "1.0" +fork-tree = { version = "3.0.0", path = "../../utils/fork-tree" } +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } +sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } sc-chain-spec = { version = "4.0.0-dev", path = "../../client/chain-spec" } -sc-utils = { version = "4.0.0-dev", path = "../utils" } -sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } +sc-client-api = { version = "4.0.0-dev", path = "../api" } sc-consensus = { version = "0.10.0-dev", path = "../consensus/common" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } -sp-keystore = { version = "0.12.0", path = "../../primitives/keystore" } -sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } -sc-telemetry = { version = "4.0.0-dev", path = "../telemetry" } sc-keystore = { version = "4.0.0-dev", path = "../keystore" } -serde_json = "1.0.79" -sc-client-api = { version = "4.0.0-dev", path = "../api" } -sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } sc-network = { version = "0.10.0-dev", path = "../network" } sc-network-gossip = { version = "0.10.0-dev", path = "../network-gossip" } +sc-telemetry = { version = "4.0.0-dev", path = "../telemetry" } +sc-utils = { version = "4.0.0-dev", path = "../utils" } +sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } +sp-application-crypto = { version = "6.0.0", path = "../../primitives/application-crypto" } +sp-arithmetic = { version = "5.0.0", path = "../../primitives/arithmetic" } +sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } +sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } +sp-core = { version = "6.0.0", path = "../../primitives/core" } sp-finality-grandpa = { version = "4.0.0-dev", path = "../../primitives/finality-grandpa" } -prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus", version = "0.10.0-dev" } -sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } -finality-grandpa = { version = "0.15.0", features = ["derive-codec"] } -async-trait = "0.1.50" +sp-keystore = { version = "0.12.0", path = "../../primitives/keystore" } +sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } [dev-dependencies] assert_matches = "1.3.0" finality-grandpa = { version = "0.15.0", features = [ - "derive-codec", - "test-helpers", + "derive-codec", + "test-helpers", ] } +serde = "1.0.136" +tempfile = "3.1.0" +tokio = "1.17.0" sc-network = { version = "0.10.0-dev", path = "../network" } sc-network-test = { version = "0.8.0", path = "../network/test" } sp-keyring = { version = "6.0.0", path = "../../primitives/keyring" } -substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } - -serde = "1.0.136" -tokio = "1.17.0" -tempfile = "3.1.0" +substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } diff --git a/client/finality-grandpa/rpc/Cargo.toml b/client/finality-grandpa/rpc/Cargo.toml index 5e173e1a15fe4..40a4150f8dd98 100644 --- a/client/finality-grandpa/rpc/Cargo.toml +++ b/client/finality-grandpa/rpc/Cargo.toml @@ -7,31 +7,32 @@ repository = "https://github.com/paritytech/substrate/" edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" readme = "README.md" +homepage = "https://substrate.io" [dependencies] -sc-finality-grandpa = { version = "0.10.0-dev", path = "../" } -sc-rpc = { version = "4.0.0-dev", path = "../../rpc" } -sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -sp-core = { version = "6.0.0", path = "../../../primitives/core" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } finality-grandpa = { version = "0.15.0", features = ["derive-codec"] } +futures = "0.3.16" jsonrpc-core = "18.0.0" jsonrpc-core-client = "18.0.0" jsonrpc-derive = "18.0.0" jsonrpc-pubsub = "18.0.0" -futures = "0.3.16" +log = "0.4.8" +parity-scale-codec = { version = "3.0.0", features = ["derive"] } serde = { version = "1.0.105", features = ["derive"] } serde_json = "1.0.50" -log = "0.4.8" thiserror = "1.0" -parity-scale-codec = { version = "3.0.0", features = ["derive"] } sc-client-api = { version = "4.0.0-dev", path = "../../api" } +sc-finality-grandpa = { version = "0.10.0-dev", path = "../" } +sc-rpc = { version = "4.0.0-dev", path = "../../rpc" } +sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } +sp-core = { version = "6.0.0", path = "../../../primitives/core" } +sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } [dev-dependencies] sc-block-builder = { version = "0.10.0-dev", path = "../../block-builder" } -sc-rpc = { version = "4.0.0-dev", path = "../../rpc", features = [ - "test-helpers", -] } +sc-rpc = { version = "4.0.0-dev", features = [ + "test-helpers", +], path = "../../rpc" } sp-core = { version = "6.0.0", path = "../../../primitives/core" } sp-finality-grandpa = { version = "4.0.0-dev", path = "../../../primitives/finality-grandpa" } sp-keyring = { version = "6.0.0", path = "../../../primitives/keyring" } diff --git a/client/informant/Cargo.toml b/client/informant/Cargo.toml index 9ba994b5a66c7..70af073e4bcfe 100644 --- a/client/informant/Cargo.toml +++ b/client/informant/Cargo.toml @@ -20,6 +20,6 @@ log = "0.4.16" parity-util-mem = { version = "0.11.0", default-features = false, features = ["primitive-types"] } sc-client-api = { version = "4.0.0-dev", path = "../api" } sc-network = { version = "0.10.0-dev", path = "../network" } +sc-transaction-pool-api = { version = "4.0.0-dev", path = "../transaction-pool/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } -sc-transaction-pool-api = { version = "4.0.0-dev", path = "../transaction-pool/api" } diff --git a/client/keystore/Cargo.toml b/client/keystore/Cargo.toml index 844110f668869..be4adba2a52fe 100644 --- a/client/keystore/Cargo.toml +++ b/client/keystore/Cargo.toml @@ -13,16 +13,15 @@ readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] - [dependencies] async-trait = "0.1.50" +hex = "0.4.0" +parking_lot = "0.12.0" +serde_json = "1.0.79" thiserror = "1.0" sp-application-crypto = { version = "6.0.0", path = "../../primitives/application-crypto" } sp-core = { version = "6.0.0", path = "../../primitives/core" } sp-keystore = { version = "0.12.0", path = "../../primitives/keystore" } -hex = "0.4.0" -parking_lot = "0.12.0" -serde_json = "1.0.79" [dev-dependencies] tempfile = "3.1.0" diff --git a/client/network-gossip/Cargo.toml b/client/network-gossip/Cargo.toml index 79e07b597c65d..4529957c7c38f 100644 --- a/client/network-gossip/Cargo.toml +++ b/client/network-gossip/Cargo.toml @@ -13,18 +13,17 @@ readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] - [dependencies] +ahash = "0.7.6" futures = "0.3.21" futures-timer = "3.0.1" libp2p = { version = "0.44.0", default-features = false } log = "0.4.16" lru = "0.7.5" -ahash = "0.7.6" +tracing = "0.1.29" prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } sc-network = { version = "0.10.0-dev", path = "../network" } sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } -tracing = "0.1.29" [dev-dependencies] async-std = "1.11.0" diff --git a/client/network/Cargo.toml b/client/network/Cargo.toml index a8a1fc05bb365..51e7e762f5292 100644 --- a/client/network/Cargo.toml +++ b/client/network/Cargo.toml @@ -18,59 +18,59 @@ prost-build = "0.9" [dependencies] async-trait = "0.1" +asynchronous-codec = "0.6" bitflags = "1.3.2" -cid = "0.8.4" bytes = "1" +cid = "0.8.4" codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } either = "1.5.3" fnv = "1.0.6" -fork-tree = { version = "3.0.0", path = "../../utils/fork-tree" } futures = "0.3.21" futures-timer = "3.0.2" -asynchronous-codec = "0.6" hex = "0.4.0" ip_network = "0.4.1" -linked-hash-map = "0.5.4" +libp2p = "0.44.0" linked_hash_set = "0.1.3" -lru = "0.7.5" +linked-hash-map = "0.5.4" log = "0.4.16" +lru = "0.7.5" parking_lot = "0.12.0" pin-project = "1.0.10" -prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } prost = "0.9" rand = "0.7.2" -sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } -sc-client-api = { version = "4.0.0-dev", path = "../api" } -sc-peerset = { version = "4.0.0-dev", path = "../peerset" } serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0.79" smallvec = "1.8.0" -sp-arithmetic = { version = "5.0.0", path = "../../primitives/arithmetic" } -sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } -sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } +thiserror = "1.0" +unsigned-varint = { version = "0.7.1", features = ["futures", "asynchronous_codec"] } +void = "1.0.2" +zeroize = "1.4.3" +fork-tree = { version = "3.0.0", path = "../../utils/fork-tree" } +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } +sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } +sc-client-api = { version = "4.0.0-dev", path = "../api" } sc-consensus = { version = "0.10.0-dev", path = "../consensus/common" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } -sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } sc-network-common = { version = "0.10.0-dev", path = "./common" } sc-network-sync = { version = "0.10.0-dev", path = "./sync" } +sc-peerset = { version = "4.0.0-dev", path = "../peerset" } sc-utils = { version = "4.0.0-dev", path = "../utils" } +sp-arithmetic = { version = "5.0.0", path = "../../primitives/arithmetic" } +sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } +sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } +sp-core = { version = "6.0.0", path = "../../primitives/core" } sp-finality-grandpa = { version = "4.0.0-dev", path = "../../primitives/finality-grandpa" } -thiserror = "1.0" -unsigned-varint = { version = "0.7.1", features = ["futures", "asynchronous_codec"] } -void = "1.0.2" -zeroize = "1.4.3" -libp2p = "0.44.0" +sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } [dev-dependencies] assert_matches = "1.3" +async-std = "1.11.0" quickcheck = "1.0.3" rand = "0.7.2" +tempfile = "3.1.0" sp-test-primitives = { version = "2.0.0", path = "../../primitives/test-primitives" } sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } substrate-test-runtime = { version = "2.0.0", path = "../../test-utils/runtime" } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } -tempfile = "3.1.0" -async-std = "1.11.0" [features] default = [] diff --git a/client/network/common/Cargo.toml b/client/network/common/Cargo.toml index 5e3150ee9bc82..c41a7895888ae 100644 --- a/client/network/common/Cargo.toml +++ b/client/network/common/Cargo.toml @@ -18,9 +18,9 @@ prost-build = "0.9" [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", features = [ - "derive", + "derive", ] } futures = "0.3.21" libp2p = "0.44.0" -sc-peerset = { version = "4.0.0-dev", path = "../../peerset" } smallvec = "1.8.0" +sc-peerset = { version = "4.0.0-dev", path = "../../peerset" } diff --git a/client/network/sync/Cargo.toml b/client/network/sync/Cargo.toml index c171194d5b24d..9cf8afa58649c 100644 --- a/client/network/sync/Cargo.toml +++ b/client/network/sync/Cargo.toml @@ -19,27 +19,27 @@ prost-build = "0.9" [dependencies] bitflags = "1.3.2" codec = { package = "parity-scale-codec", version = "3.0.0", features = [ - "derive", + "derive", ] } either = "1.5.3" -fork-tree = { version = "3.0.0", path = "../../../utils/fork-tree" } futures = "0.3.21" libp2p = "0.44.0" log = "0.4.16" lru = "0.7.5" prost = "0.9" +smallvec = "1.8.0" +thiserror = "1.0" +fork-tree = { version = "3.0.0", path = "../../../utils/fork-tree" } sc-client-api = { version = "4.0.0-dev", path = "../../api" } sc-consensus = { version = "0.10.0-dev", path = "../../consensus/common" } sc-network-common = { version = "0.10.0-dev", path = "../common" } sc-peerset = { version = "4.0.0-dev", path = "../../peerset" } -smallvec = "1.8.0" sp-arithmetic = { version = "5.0.0", path = "../../../primitives/arithmetic" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } sp-core = { version = "6.0.0", path = "../../../primitives/core" } sp-finality-grandpa = { version = "4.0.0-dev", path = "../../../primitives/finality-grandpa" } sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } -thiserror = "1.0" [dev-dependencies] quickcheck = "1.0.3" diff --git a/client/network/test/Cargo.toml b/client/network/test/Cargo.toml index bf3317d0759ee..025658afcad22 100644 --- a/client/network/test/Cargo.toml +++ b/client/network/test/Cargo.toml @@ -14,24 +14,24 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] async-std = "1.11.0" -sc-network-common = { version = "0.10.0-dev", path = "../common" } -sc-network = { version = "0.10.0-dev", path = "../" } -log = "0.4.16" -parking_lot = "0.12.0" +async-trait = "0.1.50" futures = "0.3.21" futures-timer = "3.0.1" -rand = "0.7.2" libp2p = { version = "0.44.0", default-features = false } -sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } -sc-consensus = { version = "0.10.0-dev", path = "../../consensus/common" } +log = "0.4.16" +parking_lot = "0.12.0" +rand = "0.7.2" +sc-block-builder = { version = "0.10.0-dev", path = "../../block-builder" } sc-client-api = { version = "4.0.0-dev", path = "../../api" } +sc-consensus = { version = "0.10.0-dev", path = "../../consensus/common" } +sc-network = { version = "0.10.0-dev", path = "../" } +sc-network-common = { version = "0.10.0-dev", path = "../common" } +sc-service = { version = "0.10.0-dev", default-features = false, features = ["test-helpers"], path = "../../service" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } -sp-core = { version = "6.0.0", path = "../../../primitives/core" } -sc-block-builder = { version = "0.10.0-dev", path = "../../block-builder" } +sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } sp-consensus-babe = { version = "0.10.0-dev", path = "../../../primitives/consensus/babe" } -substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } -substrate-test-runtime = { version = "2.0.0", path = "../../../test-utils/runtime" } +sp-core = { version = "6.0.0", path = "../../../primitives/core" } +sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } sp-tracing = { version = "5.0.0", path = "../../../primitives/tracing" } -sc-service = { version = "0.10.0-dev", default-features = false, features = ["test-helpers"], path = "../../service" } -async-trait = "0.1.50" +substrate-test-runtime = { version = "2.0.0", path = "../../../test-utils/runtime" } +substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } diff --git a/client/offchain/Cargo.toml b/client/offchain/Cargo.toml index 6cb1f83e360be..d4ae0a2558b7f 100644 --- a/client/offchain/Cargo.toml +++ b/client/offchain/Cargo.toml @@ -15,36 +15,36 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] bytes = "1.1" codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } -hex = "0.4" fnv = "1.0.6" futures = "0.3.21" futures-timer = "3.0.2" +hex = "0.4" +hyper = { version = "0.14.16", features = ["stream", "http2"] } +hyper-rustls = "0.22.1" num_cpus = "1.13" +once_cell = "1.8" parking_lot = "0.12.0" rand = "0.7.2" +threadpool = "1.7" +tracing = "0.1.29" sc-client-api = { version = "4.0.0-dev", path = "../api" } sc-network = { version = "0.10.0-dev", path = "../network" } +sc-utils = { version = "4.0.0-dev", path = "../utils" } sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } sp-core = { version = "6.0.0", path = "../../primitives/core" } sp-offchain = { version = "4.0.0-dev", path = "../../primitives/offchain" } sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } -sc-utils = { version = "4.0.0-dev", path = "../utils" } -threadpool = "1.7" -hyper = { version = "0.14.16", features = ["stream", "http2"] } -hyper-rustls = "0.22.1" -once_cell = "1.8" -tracing = "0.1.29" [dev-dependencies] -sc-client-db = { version = "0.10.0-dev", default-features = true, path = "../db" } +lazy_static = "1.4.0" +tokio = "1.17.0" sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } +sc-client-db = { version = "0.10.0-dev", default-features = true, path = "../db" } sc-transaction-pool = { version = "4.0.0-dev", path = "../transaction-pool" } sc-transaction-pool-api = { version = "4.0.0-dev", path = "../transaction-pool/api" } -sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } +sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } -tokio = "1.17.0" -lazy_static = "1.4.0" [features] default = [] diff --git a/client/peerset/Cargo.toml b/client/peerset/Cargo.toml index ee83d186b5417..7bf324b68cdc6 100644 --- a/client/peerset/Cargo.toml +++ b/client/peerset/Cargo.toml @@ -1,6 +1,6 @@ [package] description = "Connectivity manager based on reputation" -homepage = "http://parity.io" +homepage = "https://substrate.io" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" name = "sc-peerset" version = "4.0.0-dev" @@ -16,10 +16,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] futures = "0.3.21" libp2p = { version = "0.44.0", default-features = false } -sc-utils = { version = "4.0.0-dev", path = "../utils"} log = "0.4.16" serde_json = "1.0.79" wasm-timer = "0.2" +sc-utils = { version = "4.0.0-dev", path = "../utils" } [dev-dependencies] rand = "0.7.2" diff --git a/client/proposer-metrics/Cargo.toml b/client/proposer-metrics/Cargo.toml index fc80d5b14fd8f..9df6ff4e2e76d 100644 --- a/client/proposer-metrics/Cargo.toml +++ b/client/proposer-metrics/Cargo.toml @@ -14,4 +14,4 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] log = "0.4.16" -prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus", version = "0.10.0-dev"} +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } diff --git a/client/rpc-api/Cargo.toml b/client/rpc-api/Cargo.toml index 21354737ce2cb..0287b0fd30799 100644 --- a/client/rpc-api/Cargo.toml +++ b/client/rpc-api/Cargo.toml @@ -21,15 +21,14 @@ jsonrpc-derive = "18.0.0" jsonrpc-pubsub = "18.0.0" log = "0.4.16" parking_lot = "0.12.0" -thiserror = "1.0" - scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -sp-core = { version = "6.0.0", path = "../../primitives/core" } -sp-version = { version = "5.0.0", path = "../../primitives/version" } -sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } -sc-chain-spec = { path = "../chain-spec", version = "4.0.0-dev" } serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0.79" +thiserror = "1.0" +sc-chain-spec = { version = "4.0.0-dev", path = "../chain-spec" } sc-transaction-pool-api = { version = "4.0.0-dev", path = "../transaction-pool/api" } +sp-core = { version = "6.0.0", path = "../../primitives/core" } sp-rpc = { version = "6.0.0", path = "../../primitives/rpc" } +sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } +sp-version = { version = "5.0.0", path = "../../primitives/version" } diff --git a/client/rpc-servers/Cargo.toml b/client/rpc-servers/Cargo.toml index 12f5c23493ad8..4a13e9624a58e 100644 --- a/client/rpc-servers/Cargo.toml +++ b/client/rpc-servers/Cargo.toml @@ -14,12 +14,12 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] futures = "0.3.21" +http = { package = "jsonrpc-http-server", version = "18.0.0" } +ipc = { package = "jsonrpc-ipc-server", version = "18.0.0" } jsonrpc-core = "18.0.0" -pubsub = { package = "jsonrpc-pubsub", version = "18.0.0" } log = "0.4.16" -prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus", version = "0.10.0-dev"} +pubsub = { package = "jsonrpc-pubsub", version = "18.0.0" } serde_json = "1.0.79" tokio = { version = "1.17.0", features = ["parking_lot"] } -http = { package = "jsonrpc-http-server", version = "18.0.0" } -ipc = { package = "jsonrpc-ipc-server", version = "18.0.0" } ws = { package = "jsonrpc-ws-server", version = "18.0.0" } +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } diff --git a/client/rpc/Cargo.toml b/client/rpc/Cargo.toml index 0035ddaa94502..f76665d6c97a7 100644 --- a/client/rpc/Cargo.toml +++ b/client/rpc/Cargo.toml @@ -13,40 +13,40 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sc-rpc-api = { version = "0.10.0-dev", path = "../rpc-api" } -sc-client-api = { version = "4.0.0-dev", path = "../api" } -sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.21" +hash-db = { version = "0.15.2", default-features = false } jsonrpc-pubsub = "18.0.0" +lazy_static = { version = "1.4.0", optional = true } log = "0.4.16" -sp-core = { version = "6.0.0", path = "../../primitives/core" } +parking_lot = "0.12.0" rpc = { package = "jsonrpc-core", version = "18.0.0" } -sp-version = { version = "5.0.0", path = "../../primitives/version" } serde_json = "1.0.79" -sp-session = { version = "4.0.0-dev", path = "../../primitives/session" } -sp-offchain = { version = "4.0.0-dev", path = "../../primitives/offchain" } -sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } -sc-utils = { version = "4.0.0-dev", path = "../utils" } -sp-rpc = { version = "6.0.0", path = "../../primitives/rpc" } -sp-keystore = { version = "0.12.0", path = "../../primitives/keystore" } -sc-chain-spec = { version = "4.0.0-dev", path = "../chain-spec" } sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } -sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } +sc-chain-spec = { version = "4.0.0-dev", path = "../chain-spec" } +sc-client-api = { version = "4.0.0-dev", path = "../api" } +sc-rpc-api = { version = "0.10.0-dev", path = "../rpc-api" } sc-tracing = { version = "4.0.0-dev", path = "../tracing" } -hash-db = { version = "0.15.2", default-features = false } -parking_lot = "0.12.0" -lazy_static = { version = "1.4.0", optional = true } sc-transaction-pool-api = { version = "4.0.0-dev", path = "../transaction-pool/api" } +sc-utils = { version = "4.0.0-dev", path = "../utils" } +sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } +sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } +sp-core = { version = "6.0.0", path = "../../primitives/core" } +sp-keystore = { version = "0.12.0", path = "../../primitives/keystore" } +sp-offchain = { version = "4.0.0-dev", path = "../../primitives/offchain" } +sp-rpc = { version = "6.0.0", path = "../../primitives/rpc" } +sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } +sp-session = { version = "4.0.0-dev", path = "../../primitives/session" } +sp-version = { version = "5.0.0", path = "../../primitives/version" } [dev-dependencies] assert_matches = "1.3.0" lazy_static = "1.4.0" sc-network = { version = "0.10.0-dev", path = "../network" } -sp-io = { version = "6.0.0", path = "../../primitives/io" } -substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } sc-transaction-pool = { version = "4.0.0-dev", path = "../transaction-pool" } sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } +sp-io = { version = "6.0.0", path = "../../primitives/io" } +substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } [features] test-helpers = ["lazy_static"] diff --git a/client/service/test/Cargo.toml b/client/service/test/Cargo.toml index 854e8f5b74659..15105765c207c 100644 --- a/client/service/test/Cargo.toml +++ b/client/service/test/Cargo.toml @@ -12,33 +12,33 @@ repository = "https://github.com/paritytech/substrate/" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +fdlimit = "0.2.1" +futures = "0.3.21" hex = "0.4" hex-literal = "0.3.4" -tempfile = "3.1.0" -tokio = { version = "1.17.0", features = ["time"] } log = "0.4.16" -fdlimit = "0.2.1" +parity-scale-codec = "3.0.0" parking_lot = "0.12.0" -sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } -sp-state-machine = { version = "0.12.0", path = "../../../primitives/state-machine" } -sp-externalities = { version = "0.12.0", path = "../../../primitives/externalities" } -sp-trie = { version = "6.0.0", path = "../../../primitives/trie" } -sp-storage = { version = "6.0.0", path = "../../../primitives/storage" } +tempfile = "3.1.0" +tokio = { version = "1.17.0", features = ["time"] } +sc-block-builder = { version = "0.10.0-dev", path = "../../block-builder" } +sc-client-api = { version = "4.0.0-dev", path = "../../api" } sc-client-db = { version = "0.10.0-dev", default-features = false, path = "../../db" } -futures = "0.3.21" -sc-service = { version = "0.10.0-dev", features = ["test-helpers"], path = "../../service" } +sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } +sc-executor = { version = "0.10.0-dev", path = "../../executor" } sc-network = { version = "0.10.0-dev", path = "../../network" } +sc-service = { version = "0.10.0-dev", features = ["test-helpers"], path = "../../service" } +sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../../client/transaction-pool/api" } +sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } +sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } -sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } sp-core = { version = "6.0.0", path = "../../../primitives/core" } -sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../../client/transaction-pool/api" } -substrate-test-runtime = { version = "2.0.0", path = "../../../test-utils/runtime" } -substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } -sc-client-api = { version = "4.0.0-dev", path = "../../api" } -sc-block-builder = { version = "0.10.0-dev", path = "../../block-builder" } -sc-executor = { version = "0.10.0-dev", path = "../../executor" } +sp-externalities = { version = "0.12.0", path = "../../../primitives/externalities" } sp-panic-handler = { version = "4.0.0", path = "../../../primitives/panic-handler" } -parity-scale-codec = "3.0.0" +sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } +sp-state-machine = { version = "0.12.0", path = "../../../primitives/state-machine" } +sp-storage = { version = "6.0.0", path = "../../../primitives/storage" } sp-tracing = { version = "5.0.0", path = "../../../primitives/tracing" } +sp-trie = { version = "6.0.0", path = "../../../primitives/trie" } +substrate-test-runtime = { version = "2.0.0", path = "../../../test-utils/runtime" } +substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } diff --git a/client/state-db/Cargo.toml b/client/state-db/Cargo.toml index 3ca5a9065045f..5a143ffc9f1b8 100644 --- a/client/state-db/Cargo.toml +++ b/client/state-db/Cargo.toml @@ -13,10 +13,10 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -parking_lot = "0.12.0" -log = "0.4.16" -sc-client-api = { version = "4.0.0-dev", path = "../api" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } +log = "0.4.16" parity-util-mem = { version = "0.11.0", default-features = false, features = ["primitive-types"] } parity-util-mem-derive = "0.1.0" +parking_lot = "0.12.0" +sc-client-api = { version = "4.0.0-dev", path = "../api" } +sp-core = { version = "6.0.0", path = "../../primitives/core" } diff --git a/client/sync-state-rpc/Cargo.toml b/client/sync-state-rpc/Cargo.toml index e13631f210bc0..a32849dc0e964 100644 --- a/client/sync-state-rpc/Cargo.toml +++ b/client/sync-state-rpc/Cargo.toml @@ -13,17 +13,17 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -thiserror = "1.0.30" +codec = { package = "parity-scale-codec", version = "3.0.0" } jsonrpc-core = "18.0.0" jsonrpc-core-client = "18.0.0" jsonrpc-derive = "18.0.0" +serde = { version = "1.0.136", features = ["derive"] } +serde_json = "1.0.79" +thiserror = "1.0.30" sc-chain-spec = { version = "4.0.0-dev", path = "../chain-spec" } sc-client-api = { version = "4.0.0-dev", path = "../api" } sc-consensus-babe = { version = "0.10.0-dev", path = "../consensus/babe" } sc-consensus-epochs = { version = "0.10.0-dev", path = "../consensus/epochs" } sc-finality-grandpa = { version = "0.10.0-dev", path = "../finality-grandpa" } -serde_json = "1.0.79" -serde = { version = "1.0.136", features = ["derive"] } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } -codec = { package = "parity-scale-codec", version = "3.0.0" } diff --git a/client/sysinfo/Cargo.toml b/client/sysinfo/Cargo.toml index 15e9545094cbf..abfa090d85547 100644 --- a/client/sysinfo/Cargo.toml +++ b/client/sysinfo/Cargo.toml @@ -15,14 +15,14 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] futures = "0.3.19" +libc = "0.2" log = "0.4.16" rand = "0.7.3" rand_pcg = "0.2.1" regex = "1" -libc = "0.2" serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0.79" +sc-telemetry = { version = "4.0.0-dev", path = "../telemetry" } sp-core = { version = "6.0.0", path = "../../primitives/core" } sp-io = { version = "6.0.0", path = "../../primitives/io" } sp-std = { version = "4.0.0", path = "../../primitives/std" } -sc-telemetry = { version = "4.0.0-dev", path = "../telemetry" } diff --git a/client/telemetry/Cargo.toml b/client/telemetry/Cargo.toml index 4dfefd06ac737..3c5942ad688fa 100644 --- a/client/telemetry/Cargo.toml +++ b/client/telemetry/Cargo.toml @@ -13,16 +13,15 @@ readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] - [dependencies] -parking_lot = "0.12.0" +chrono = "0.4.19" futures = "0.3.21" -wasm-timer = "0.2.5" libp2p = { version = "0.44.0", default-features = false, features = ["dns-async-std", "tcp-async-io", "wasm-ext", "websocket"] } log = "0.4.16" +parking_lot = "0.12.0" pin-project = "1.0.10" rand = "0.7.2" serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0.79" -chrono = "0.4.19" thiserror = "1.0.30" +wasm-timer = "0.2.5" diff --git a/client/tracing/Cargo.toml b/client/tracing/Cargo.toml index 4178e26fe7bf6..058b718a783a4 100644 --- a/client/tracing/Cargo.toml +++ b/client/tracing/Cargo.toml @@ -28,15 +28,15 @@ thiserror = "1.0.30" tracing = "0.1.29" tracing-log = { version = "0.1.3", features = ["interest-cache"] } tracing-subscriber = { version = "0.2.25", features = ["parking_lot"] } -sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } -sp-rpc = { version = "6.0.0", path = "../../primitives/rpc" } -sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } -sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } -sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } sc-client-api = { version = "4.0.0-dev", path = "../api" } -sc-tracing-proc-macro = { version = "4.0.0-dev", path = "./proc-macro" } sc-rpc-server = { version = "4.0.0-dev", path = "../rpc-servers" } +sc-tracing-proc-macro = { version = "4.0.0-dev", path = "./proc-macro" } +sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } +sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } +sp-core = { version = "6.0.0", path = "../../primitives/core" } +sp-rpc = { version = "6.0.0", path = "../../primitives/rpc" } +sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } +sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } [dev-dependencies] criterion = "0.3" diff --git a/client/transaction-pool/Cargo.toml b/client/transaction-pool/Cargo.toml index f651b2022032a..5d4996a424f86 100644 --- a/client/transaction-pool/Cargo.toml +++ b/client/transaction-pool/Cargo.toml @@ -14,35 +14,35 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } -thiserror = "1.0.30" futures = "0.3.21" futures-timer = "3.0.2" +linked-hash-map = "0.5.4" log = "0.4.16" parity-util-mem = { version = "0.11.0", default-features = false, features = ["primitive-types"] } parking_lot = "0.12.0" -prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus", version = "0.10.0-dev"} +retain_mut = "0.1.4" +serde = { version = "1.0.136", features = ["derive"] } +thiserror = "1.0.30" +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } sc-client-api = { version = "4.0.0-dev", path = "../api" } +sc-transaction-pool-api = { version = "4.0.0-dev", path = "./api" } +sc-utils = { version = "4.0.0-dev", path = "../utils" } sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } +sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } sp-core = { version = "6.0.0", path = "../../primitives/core" } sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } sp-transaction-pool = { version = "4.0.0-dev", path = "../../primitives/transaction-pool" } -sc-transaction-pool-api = { version = "4.0.0-dev", path = "./api" } -sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } -sc-utils = { version = "4.0.0-dev", path = "../utils" } -serde = { version = "1.0.136", features = ["derive"] } -linked-hash-map = "0.5.4" -retain_mut = "0.1.4" [dev-dependencies] assert_matches = "1.3.0" +criterion = "0.3" hex = "0.4" -sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } -substrate-test-runtime-transaction-pool = { version = "2.0.0", path = "../../test-utils/runtime/transaction-pool" } -substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } +sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } substrate-test-runtime = { version = "2.0.0", path = "../../test-utils/runtime" } -criterion = "0.3" +substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } +substrate-test-runtime-transaction-pool = { version = "2.0.0", path = "../../test-utils/runtime/transaction-pool" } [[bench]] name = "basics" diff --git a/client/transaction-pool/api/Cargo.toml b/client/transaction-pool/api/Cargo.toml index 84395aaf2eace..6ced3f6c7fc91 100644 --- a/client/transaction-pool/api/Cargo.toml +++ b/client/transaction-pool/api/Cargo.toml @@ -13,6 +13,5 @@ futures = "0.3.21" log = "0.4.16" serde = { version = "1.0.136", features = ["derive"] } thiserror = "1.0.30" - -sp-runtime = { version = "6.0.0", default-features = false, path = "../../../primitives/runtime" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../../primitives/runtime" } diff --git a/client/utils/Cargo.toml b/client/utils/Cargo.toml index 2f1338577e375..2df04be7fb4af 100644 --- a/client/utils/Cargo.toml +++ b/client/utils/Cargo.toml @@ -11,11 +11,11 @@ readme = "README.md" [dependencies] futures = "0.3.21" +futures-timer = "3.0.2" lazy_static = "1.4.0" +log = "0.4" parking_lot = "0.12.0" prometheus = { version = "0.13.0", default-features = false } -futures-timer = "3.0.2" -log = "0.4" [features] default = ["metered"] diff --git a/frame/atomic-swap/Cargo.toml b/frame/atomic-swap/Cargo.toml index 361b7d5833e67..4fd793496572e 100644 --- a/frame/atomic-swap/Cargo.toml +++ b/frame/atomic-swap/Cargo.toml @@ -17,10 +17,10 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } @@ -29,12 +29,12 @@ pallet-balances = { version = "4.0.0-dev", path = "../balances" } default = ["std"] std = [ "codec/std", - "scale-info/std", "frame-support/std", "frame-system/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", "sp-runtime/std", "sp-std/std", - "sp-io/std", - "sp-core/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/aura/Cargo.toml b/frame/aura/Cargo.toml index 96cfade56cc0f..2ce90ff0215ae 100644 --- a/frame/aura/Cargo.toml +++ b/frame/aura/Cargo.toml @@ -13,15 +13,15 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-application-crypto = { version = "6.0.0", default-features = false, path = "../../primitives/application-crypto" } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } -sp-consensus-aura = { version = "0.10.0-dev", path = "../../primitives/consensus/aura", default-features = false } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } pallet-timestamp = { version = "4.0.0-dev", default-features = false, path = "../timestamp" } +sp-application-crypto = { version = "6.0.0", default-features = false, path = "../../primitives/application-crypto" } +sp-consensus-aura = { version = "0.10.0-dev", default-features = false, path = "../../primitives/consensus/aura" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } @@ -30,14 +30,14 @@ sp-io = { version = "6.0.0", path = "../../primitives/io" } [features] default = ["std"] std = [ - "sp-application-crypto/std", "codec/std", - "scale-info/std", - "sp-std/std", - "sp-runtime/std", "frame-support/std", - "sp-consensus-aura/std", "frame-system/std", "pallet-timestamp/std", + "scale-info/std", + "sp-application-crypto/std", + "sp-consensus-aura/std", + "sp-runtime/std", + "sp-std/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/authority-discovery/Cargo.toml b/frame/authority-discovery/Cargo.toml index d379bdda6cf8a..bebe8e0f436a6 100644 --- a/frame/authority-discovery/Cargo.toml +++ b/frame/authority-discovery/Cargo.toml @@ -13,19 +13,19 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-authority-discovery = { version = "4.0.0-dev", default-features = false, path = "../../primitives/authority-discovery" } -sp-application-crypto = { version = "6.0.0", default-features = false, path = "../../primitives/application-crypto" } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -pallet-session = { version = "4.0.0-dev", features = [ - "historical", -], path = "../session", default-features = false } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +pallet-session = { version = "4.0.0-dev", default-features = false, features = [ + "historical", +], path = "../session" } +sp-application-crypto = { version = "6.0.0", default-features = false, path = "../../primitives/application-crypto" } +sp-authority-discovery = { version = "4.0.0-dev", default-features = false, path = "../../primitives/authority-discovery" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] sp-core = { version = "6.0.0", path = "../../primitives/core" } @@ -34,14 +34,14 @@ sp-io = { version = "6.0.0", path = "../../primitives/io" } [features] default = ["std"] std = [ - "sp-application-crypto/std", - "sp-authority-discovery/std", "codec/std", - "scale-info/std", - "sp-std/std", - "pallet-session/std", - "sp-runtime/std", "frame-support/std", "frame-system/std", + "pallet-session/std", + "scale-info/std", + "sp-application-crypto/std", + "sp-authority-discovery/std", + "sp-runtime/std", + "sp-std/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/authorship/Cargo.toml b/frame/authorship/Cargo.toml index 4557ee65f1a6b..bc7fad9684e1f 100644 --- a/frame/authorship/Cargo.toml +++ b/frame/authorship/Cargo.toml @@ -16,13 +16,13 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } +impl-trait-for-tuples = "0.2.2" scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -sp-authorship = { version = "4.0.0-dev", default-features = false, path = "../../primitives/authorship" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -impl-trait-for-tuples = "0.2.2" +sp-authorship = { version = "4.0.0-dev", default-features = false, path = "../../primitives/authorship" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] sp-core = { version = "6.0.0", path = "../../primitives/core" } @@ -32,11 +32,11 @@ sp-io = { version = "6.0.0", path = "../../primitives/io" } default = ["std"] std = [ "codec/std", - "scale-info/std", - "sp-runtime/std", - "sp-std/std", "frame-support/std", "frame-system/std", + "scale-info/std", "sp-authorship/std", + "sp-runtime/std", + "sp-std/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/babe/Cargo.toml b/frame/babe/Cargo.toml index 2c3af69b3b4a1..dfdc9d2163136 100644 --- a/frame/babe/Cargo.toml +++ b/frame/babe/Cargo.toml @@ -14,8 +14,9 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +log = { version = "0.4.16", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } pallet-authorship = { version = "4.0.0-dev", default-features = false, path = "../authorship" } @@ -29,27 +30,27 @@ sp-runtime = { version = "6.0.0", default-features = false, path = "../../primit sp-session = { version = "4.0.0-dev", default-features = false, path = "../../primitives/session" } sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -log = { version = "0.4.16", default-features = false } [dev-dependencies] +frame-election-provider-support = { version = "4.0.0-dev", path = "../election-provider-support" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } pallet-offences = { version = "4.0.0-dev", path = "../offences" } pallet-staking = { version = "4.0.0-dev", path = "../staking" } pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../staking/reward-curve" } sp-core = { version = "6.0.0", path = "../../primitives/core" } -frame-election-provider-support = { version = "4.0.0-dev", path = "../election-provider-support" } [features] default = ["std"] std = [ "codec/std", - "scale-info/std", "frame-benchmarking/std", "frame-support/std", "frame-system/std", + "log/std", "pallet-authorship/std", "pallet-session/std", "pallet-timestamp/std", + "scale-info/std", "sp-application-crypto/std", "sp-consensus-babe/std", "sp-consensus-vrf/std", @@ -58,7 +59,6 @@ std = [ "sp-session/std", "sp-staking/std", "sp-std/std", - "log/std", ] runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/bags-list/fuzzer/Cargo.toml b/frame/bags-list/fuzzer/Cargo.toml index 510000f631adc..ec7d98255b019 100644 --- a/frame/bags-list/fuzzer/Cargo.toml +++ b/frame/bags-list/fuzzer/Cargo.toml @@ -4,7 +4,7 @@ version = "4.0.0-dev" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Fuzzer for FRAME pallet bags list" readme = "README.md" @@ -13,9 +13,8 @@ publish = false [dependencies] honggfuzz = "0.5" rand = { version = "0.8", features = ["std", "small_rng"] } - +frame-election-provider-support = { version = "4.0.0-dev", features = ["runtime-benchmarks"], path = "../../election-provider-support" } pallet-bags-list = { version = "4.0.0-dev", features = ["fuzz"], path = ".." } -frame-election-provider-support = { version = "4.0.0-dev", path = "../../election-provider-support", features = ["runtime-benchmarks"] } [[bin]] name = "bags-list" diff --git a/frame/balances/Cargo.toml b/frame/balances/Cargo.toml index 46a15f9c6d7ef..6ec624130288b 100644 --- a/frame/balances/Cargo.toml +++ b/frame/balances/Cargo.toml @@ -14,30 +14,30 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } +log = { version = "0.4.16", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -log = { version = "0.4.16", default-features = false } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-io = { version = "6.0.0", path = "../../primitives/io" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } pallet-transaction-payment = { version = "4.0.0-dev", path = "../transaction-payment" } +sp-core = { version = "6.0.0", path = "../../primitives/core" } +sp-io = { version = "6.0.0", path = "../../primitives/io" } [features] default = ["std"] std = [ "codec/std", - "scale-info/std", - "sp-std/std", - "sp-runtime/std", "frame-benchmarking/std", "frame-support/std", "frame-system/std", "log/std", + "scale-info/std", + "sp-runtime/std", + "sp-std/std", ] runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/beefy-mmr/Cargo.toml b/frame/beefy-mmr/Cargo.toml index 40e16ad780a61..bdec17b8589c2 100644 --- a/frame/beefy-mmr/Cargo.toml +++ b/frame/beefy-mmr/Cargo.toml @@ -6,48 +6,46 @@ edition = "2021" license = "Apache-2.0" description = "BEEFY + MMR runtime utilities" repository = "https://github.com/paritytech/substrate" +homepage = "https://substrate.io" [dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } hex = { version = "0.4", optional = true } -codec = { version = "3.0.0", package = "parity-scale-codec", default-features = false, features = ["derive"] } log = { version = "0.4.16", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", optional = true } - -frame-support = { version = "4.0.0-dev", path = "../support", default-features = false } -frame-system = { version = "4.0.0-dev", path = "../system", default-features = false } -pallet-mmr = { version = "4.0.0-dev", path = "../merkle-mountain-range", default-features = false } -pallet-session = { version = "4.0.0-dev", path = "../session", default-features = false } - -sp-core = { version = "6.0.0", path = "../../primitives/core", default-features = false } -sp-io = { version = "6.0.0", path = "../../primitives/io", default-features = false } -sp-runtime = { version = "6.0.0", path = "../../primitives/runtime", default-features = false } -sp-std = { version = "4.0.0", path = "../../primitives/std", default-features = false } - -beefy-merkle-tree = { version = "4.0.0-dev", path = "./primitives", default-features = false } -beefy-primitives = { version = "4.0.0-dev", path = "../../primitives/beefy", default-features = false } -pallet-beefy = { version = "4.0.0-dev", path = "../beefy", default-features = false } +beefy-merkle-tree = { version = "4.0.0-dev", default-features = false, path = "./primitives" } +beefy-primitives = { version = "4.0.0-dev", default-features = false, path = "../../primitives/beefy" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +pallet-beefy = { version = "4.0.0-dev", default-features = false, path = "../beefy" } +pallet-mmr = { version = "4.0.0-dev", default-features = false, path = "../merkle-mountain-range" } +pallet-session = { version = "4.0.0-dev", default-features = false, path = "../session" } +sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-staking = { version = "4.0.0-dev", path = "../../primitives/staking" } hex-literal = "0.3" +sp-staking = { version = "4.0.0-dev", path = "../../primitives/staking" } [features] default = ["std"] std = [ - "beefy-merkle-tree/std", - "beefy-primitives/std", - "codec/std", - "frame-support/std", - "frame-system/std", - "hex", - "log/std", - "pallet-beefy/std", - "pallet-mmr/std", - "pallet-session/std", - "serde", - "sp-core/std", - "sp-io/std", - "sp-runtime/std", - "sp-std/std", + "beefy-merkle-tree/std", + "beefy-primitives/std", + "codec/std", + "frame-support/std", + "frame-system/std", + "hex", + "log/std", + "pallet-beefy/std", + "pallet-mmr/std", + "pallet-session/std", + "serde", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", ] diff --git a/frame/beefy-mmr/primitives/Cargo.toml b/frame/beefy-mmr/primitives/Cargo.toml index b54ac225e7818..7878dc3d22837 100644 --- a/frame/beefy-mmr/primitives/Cargo.toml +++ b/frame/beefy-mmr/primitives/Cargo.toml @@ -6,10 +6,11 @@ edition = "2021" license = "Apache-2.0" repository = "https://github.com/paritytech/substrate" description = "A no-std/Substrate compatible library to construct binary merkle tree." +homepage = "https://substrate.io" [dependencies] -hex = { version = "0.4", optional = true, default-features = false } -log = { version = "0.4", optional = true, default-features = false } +hex = { version = "0.4", default-features = false, optional = true } +log = { version = "0.4", default-features = false, optional = true } tiny-keccak = { version = "2.0.2", features = ["keccak"], optional = true } [dev-dependencies] @@ -19,6 +20,6 @@ hex-literal = "0.3" [features] debug = ["hex", "hex/std", "log"] -default = ["std", "debug", "keccak"] +default = ["debug", "keccak", "std"] keccak = ["tiny-keccak"] std = [] diff --git a/frame/beefy/Cargo.toml b/frame/beefy/Cargo.toml index 6ed9e7375bfe3..89a49831d9f62 100644 --- a/frame/beefy/Cargo.toml +++ b/frame/beefy/Cargo.toml @@ -6,21 +6,18 @@ edition = "2021" license = "Apache-2.0" repository = "https://github.com/paritytech/substrate" description = "BEEFY FRAME pallet" +homepage = "https://substrate.io" [dependencies] -codec = { version = "3.0.0", package = "parity-scale-codec", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", optional = true } - -frame-support = { version = "4.0.0-dev", path = "../support", default-features = false } -frame-system = { version = "4.0.0-dev", path = "../system", default-features = false } - -sp-runtime = { version = "6.0.0", path = "../../primitives/runtime", default-features = false } -sp-std = { version = "4.0.0", path = "../../primitives/std", default-features = false } - -pallet-session = { version = "4.0.0-dev", path = "../session", default-features = false } - -beefy-primitives = { version = "4.0.0-dev", path = "../../primitives/beefy", default-features = false } +beefy-primitives = { version = "4.0.0-dev", default-features = false, path = "../../primitives/beefy" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +pallet-session = { version = "4.0.0-dev", default-features = false, path = "../session" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] sp-core = { version = "6.0.0", path = "../../primitives/core" } @@ -30,13 +27,13 @@ sp-staking = { version = "4.0.0-dev", path = "../../primitives/staking" } [features] default = ["std"] std = [ - "codec/std", - "scale-info/std", - "serde", "beefy-primitives/std", + "codec/std", "frame-support/std", "frame-system/std", + "pallet-session/std", + "scale-info/std", + "serde", "sp-runtime/std", "sp-std/std", - "pallet-session/std", ] diff --git a/frame/benchmarking/Cargo.toml b/frame/benchmarking/Cargo.toml index aa551095d1d7e..d1d835e0d1d35 100644 --- a/frame/benchmarking/Cargo.toml +++ b/frame/benchmarking/Cargo.toml @@ -13,21 +13,21 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } linregress = { version = "0.4.4", optional = true } +log = { version = "0.4.16", default-features = false } paste = "1.0" -codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -sp-api = { version = "4.0.0-dev", path = "../../primitives/api", default-features = false } -sp-runtime-interface = { version = "6.0.0", path = "../../primitives/runtime-interface", default-features = false } -sp-runtime = { version = "6.0.0", path = "../../primitives/runtime", default-features = false } -sp-std = { version = "4.0.0", path = "../../primitives/std", default-features = false } -sp-io = { version = "6.0.0", path = "../../primitives/io", default-features = false } -sp-application-crypto = { version = "6.0.0", path = "../../primitives/application-crypto", default-features = false } -sp-storage = { version = "6.0.0", path = "../../primitives/storage", default-features = false } +serde = { version = "1.0.136", optional = true } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -log = { version = "0.4.16", default-features = false } -serde = { version = "1.0.136", optional = true } +sp-api = { version = "4.0.0-dev", default-features = false, path = "../../primitives/api" } +sp-application-crypto = { version = "6.0.0", default-features = false, path = "../../primitives/application-crypto" } +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-runtime-interface = { version = "6.0.0", default-features = false, path = "../../primitives/runtime-interface" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +sp-storage = { version = "6.0.0", default-features = false, path = "../../primitives/storage" } [dev-dependencies] hex-literal = "0.3.4" @@ -37,15 +37,15 @@ sp-keystore = { version = "0.12.0", path = "../../primitives/keystore" } default = ["std"] std = [ "codec/std", + "frame-support/std", + "frame-system/std", + "linregress", + "log/std", "scale-info/std", "serde", + "sp-api/std", "sp-runtime-interface/std", "sp-runtime/std", - "sp-api/std", "sp-std/std", - "frame-support/std", - "frame-system/std", - "linregress", - "log/std", ] runtime-benchmarks = [] diff --git a/frame/bounties/Cargo.toml b/frame/bounties/Cargo.toml index 2eb8f486d3556..c090052e91277 100644 --- a/frame/bounties/Cargo.toml +++ b/frame/bounties/Cargo.toml @@ -16,16 +16,16 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } +log = { version = "0.4.16", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } pallet-treasury = { version = "4.0.0-dev", default-features = false, path = "../treasury" } -sp-io = { version = "6.0.0", path = "../../primitives/io", default-features = false } -sp-core = { version = "6.0.0", path = "../../primitives/core", default-features = false } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } -log = { version = "0.4.16", default-features = false } +sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } @@ -34,15 +34,15 @@ pallet-balances = { version = "4.0.0-dev", path = "../balances" } default = ["std"] std = [ "codec/std", - "sp-core/std", - "sp-io/std", - "scale-info/std", - "sp-std/std", - "sp-runtime/std", "frame-support/std", "frame-system/std", - "pallet-treasury/std", "log/std", + "pallet-treasury/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", diff --git a/frame/child-bounties/Cargo.toml b/frame/child-bounties/Cargo.toml index b6a38fb22548d..eac338dc3c6aa 100644 --- a/frame/child-bounties/Cargo.toml +++ b/frame/child-bounties/Cargo.toml @@ -16,17 +16,17 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } +log = { version = "0.4.16", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -pallet-treasury = { version = "4.0.0-dev", default-features = false, path = "../treasury" } pallet-bounties = { version = "4.0.0-dev", default-features = false, path = "../bounties" } -sp-io = { version = "6.0.0", path = "../../primitives/io", default-features = false } -sp-core = { version = "6.0.0", path = "../../primitives/core", default-features = false } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } -log = { version = "0.4.16", default-features = false } +pallet-treasury = { version = "4.0.0-dev", default-features = false, path = "../treasury" } +sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } @@ -35,16 +35,16 @@ pallet-balances = { version = "4.0.0-dev", path = "../balances" } default = ["std"] std = [ "codec/std", - "sp-core/std", - "sp-io/std", - "scale-info/std", - "sp-std/std", - "sp-runtime/std", "frame-support/std", "frame-system/std", - "pallet-treasury/std", - "pallet-bounties/std", "log/std", + "pallet-bounties/std", + "pallet-treasury/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", ] runtime-benchmarks = [ "frame-benchmarking", diff --git a/frame/collective/Cargo.toml b/frame/collective/Cargo.toml index 7e1186143270c..d1ef4d253a352 100644 --- a/frame/collective/Cargo.toml +++ b/frame/collective/Cargo.toml @@ -16,34 +16,32 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } log = { version = "0.4.16", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } - +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } -frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } -frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } - [features] default = ["std"] std = [ "codec/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", "log/std", "scale-info/std", "sp-core/std", "sp-io/std", "sp-runtime/std", "sp-std/std", - "frame-benchmarking/std", - "frame-support/std", - "frame-system/std", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/conviction-voting/Cargo.toml b/frame/conviction-voting/Cargo.toml index 5877bf0c5da2d..4ebc3c8f4bc43 100644 --- a/frame/conviction-voting/Cargo.toml +++ b/frame/conviction-voting/Cargo.toml @@ -13,41 +13,41 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { version = "1.0.136", optional = true, features = ["derive"] } +assert_matches = "1.3.0" codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +serde = { version = "1.0.136", features = ["derive"], optional = true } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -assert_matches = "1.3.0" +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-core = { version = "6.0.0", path = "../../primitives/core" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } pallet-scheduler = { version = "4.0.0-dev", path = "../scheduler" } +sp-core = { version = "6.0.0", path = "../../primitives/core" } [features] default = ["std"] std = [ - "serde", "codec/std", - "scale-info/std", - "sp-std/std", - "sp-io/std", "frame-benchmarking/std", "frame-support/std", - "sp-runtime/std", "frame-system/std", + "scale-info/std", + "serde", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", ] runtime-benchmarks = [ "frame-benchmarking", - "frame-system/runtime-benchmarks", "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/democracy/Cargo.toml b/frame/democracy/Cargo.toml index 49b21d0eecdce..b36448bdf25ef 100644 --- a/frame/democracy/Cargo.toml +++ b/frame/democracy/Cargo.toml @@ -13,40 +13,40 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { version = "1.0.136", optional = true, features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +serde = { version = "1.0.136", features = ["derive"], optional = true } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-core = { version = "6.0.0", path = "../../primitives/core" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } pallet-scheduler = { version = "4.0.0-dev", path = "../scheduler" } +sp-core = { version = "6.0.0", path = "../../primitives/core" } [features] default = ["std"] std = [ - "serde", "codec/std", - "scale-info/std", - "sp-std/std", - "sp-io/std", "frame-benchmarking/std", "frame-support/std", - "sp-runtime/std", "frame-system/std", + "scale-info/std", + "serde", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", - "frame-system/runtime-benchmarks", "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/election-provider-support/Cargo.toml b/frame/election-provider-support/Cargo.toml index be0c05e46df32..8bc92f878c8ce 100644 --- a/frame/election-provider-support/Cargo.toml +++ b/frame/election-provider-support/Cargo.toml @@ -15,28 +15,28 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +frame-election-provider-solution-type = { version = "4.0.0-dev", path = "solution-type" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } sp-arithmetic = { version = "5.0.0", default-features = false, path = "../../primitives/arithmetic" } sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../primitives/npos-elections" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } -frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -frame-election-provider-solution-type = { version = "4.0.0-dev", path = "solution-type" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] rand = "0.7.3" -sp-npos-elections = { version = "4.0.0-dev", path = "../../primitives/npos-elections" } sp-core = { version = "6.0.0", path = "../../primitives/core" } sp-io = { version = "6.0.0", path = "../../primitives/io" } +sp-npos-elections = { version = "4.0.0-dev", path = "../../primitives/npos-elections" } [features] default = ["std"] std = [ "codec/std", - "sp-std/std", - "sp-npos-elections/std", - "sp-arithmetic/std", "frame-support/std", "frame-system/std", + "sp-arithmetic/std", + "sp-npos-elections/std", + "sp-std/std", ] runtime-benchmarks = [] diff --git a/frame/election-provider-support/benchmarking/Cargo.toml b/frame/election-provider-support/benchmarking/Cargo.toml index e9719be139a6d..14afff1aeb055 100644 --- a/frame/election-provider-support/benchmarking/Cargo.toml +++ b/frame/election-provider-support/benchmarking/Cargo.toml @@ -15,23 +15,21 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../../benchmarking" } +frame-election-provider-support = { version = "4.0.0-dev", default-features = false, path = ".." } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/npos-elections" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../../primitives/runtime" } -frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } -frame-election-provider-support = { version = "4.0.0-dev", default-features = false, path = ".." } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../benchmarking", optional = true } - [features] default = ["std"] std = [ "codec/std", - "sp-npos-elections/std", - "sp-runtime/std", "frame-benchmarking/std", "frame-system/std", + "sp-npos-elections/std", + "sp-runtime/std", ] - runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-election-provider-support/runtime-benchmarks", diff --git a/frame/elections-phragmen/Cargo.toml b/frame/elections-phragmen/Cargo.toml index f772a82d73d87..0f195f12cce3c 100644 --- a/frame/elections-phragmen/Cargo.toml +++ b/frame/elections-phragmen/Cargo.toml @@ -16,16 +16,16 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } +log = { version = "0.4.14", default-features = false } scale-info = { version = "2.0.0", default-features = false, features = ["derive"] } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../primitives/npos-elections" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } -log = { version = "0.4.14", default-features = false } +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } +sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../primitives/npos-elections" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } @@ -36,15 +36,15 @@ substrate-test-utils = { version = "4.0.0-dev", path = "../../test-utils" } default = ["std"] std = [ "codec/std", - "scale-info/std", "frame-support/std", - "sp-runtime/std", - "sp-npos-elections/std", "frame-system/std", - "sp-std/std", - "sp-io/std", - "sp-core/std", "log/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", + "sp-npos-elections/std", + "sp-runtime/std", + "sp-std/std", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", diff --git a/frame/examples/basic/Cargo.toml b/frame/examples/basic/Cargo.toml index d9051dff8f2cb..a528cf89bbaf4 100644 --- a/frame/examples/basic/Cargo.toml +++ b/frame/examples/basic/Cargo.toml @@ -16,8 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } log = { version = "0.4.16", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } - -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../benchmarking", optional = true } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../../balances" } @@ -26,7 +25,7 @@ sp-runtime = { version = "6.0.0", default-features = false, path = "../../../pri sp-std = { version = "4.0.0", default-features = false, path = "../../../primitives/std" } [dev-dependencies] -sp-core = { version = "6.0.0", path = "../../../primitives/core", default-features = false } +sp-core = { version = "6.0.0", default-features = false, path = "../../../primitives/core" } [features] default = ["std"] @@ -40,7 +39,7 @@ std = [ "scale-info/std", "sp-io/std", "sp-runtime/std", - "sp-std/std" + "sp-std/std", ] runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/examples/offchain-worker/Cargo.toml b/frame/examples/offchain-worker/Cargo.toml index ede25c2c939df..061f84bd40276 100644 --- a/frame/examples/offchain-worker/Cargo.toml +++ b/frame/examples/offchain-worker/Cargo.toml @@ -17,12 +17,11 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = lite-json = { version = "0.1", default-features = false } log = { version = "0.4.16", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } - frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } sp-core = { version = "6.0.0", default-features = false, path = "../../../primitives/core" } sp-io = { version = "6.0.0", default-features = false, path = "../../../primitives/io" } -sp-keystore = { version = "0.12.0", path = "../../../primitives/keystore", optional = true } +sp-keystore = { version = "0.12.0", optional = true, path = "../../../primitives/keystore" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../../primitives/runtime" } sp-std = { version = "4.0.0", default-features = false, path = "../../../primitives/std" } @@ -30,15 +29,15 @@ sp-std = { version = "4.0.0", default-features = false, path = "../../../primiti default = ["std"] std = [ "codec/std", - "scale-info/std", "frame-support/std", "frame-system/std", "lite-json/std", + "log/std", + "scale-info/std", "sp-core/std", "sp-io/std", "sp-keystore", "sp-runtime/std", "sp-std/std", - "log/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/examples/parallel/Cargo.toml b/frame/examples/parallel/Cargo.toml index 367b2e98aaa65..6834a9143ae97 100644 --- a/frame/examples/parallel/Cargo.toml +++ b/frame/examples/parallel/Cargo.toml @@ -14,7 +14,6 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } - frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } sp-core = { version = "6.0.0", default-features = false, path = "../../../primitives/core" } @@ -27,9 +26,9 @@ sp-tasks = { version = "4.0.0-dev", default-features = false, path = "../../../p default = ["std"] std = [ "codec/std", - "scale-info/std", "frame-support/std", "frame-system/std", + "scale-info/std", "sp-core/std", "sp-io/std", "sp-runtime/std", diff --git a/frame/executive/Cargo.toml b/frame/executive/Cargo.toml index f920affaab202..b60cffa478c7b 100644 --- a/frame/executive/Cargo.toml +++ b/frame/executive/Cargo.toml @@ -19,32 +19,32 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-tracing = { version = "5.0.0", default-features = false, path = "../../primitives/tracing" } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } +sp-tracing = { version = "5.0.0", default-features = false, path = "../../primitives/tracing" } [dev-dependencies] hex-literal = "0.3.4" -sp-core = { version = "6.0.0", path = "../../primitives/core" } -sp-io = { version = "6.0.0", path = "../../primitives/io" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } pallet-transaction-payment = { version = "4.0.0-dev", path = "../transaction-payment" } -sp-version = { version = "5.0.0", path = "../../primitives/version" } +sp-core = { version = "6.0.0", path = "../../primitives/core" } sp-inherents = { version = "4.0.0-dev", path = "../../primitives/inherents" } +sp-io = { version = "6.0.0", path = "../../primitives/io" } +sp-version = { version = "5.0.0", path = "../../primitives/version" } [features] default = ["std"] with-tracing = ["sp-tracing/with-tracing"] std = [ "codec/std", - "scale-info/std", "frame-support/std", "frame-system/std", + "scale-info/std", "sp-core/std", "sp-runtime/std", - "sp-tracing/std", "sp-std/std", + "sp-tracing/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/gilt/Cargo.toml b/frame/gilt/Cargo.toml index 837504e516cee..4916b950e47dd 100644 --- a/frame/gilt/Cargo.toml +++ b/frame/gilt/Cargo.toml @@ -15,29 +15,29 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-arithmetic = { version = "5.0.0", default-features = false, path = "../../primitives/arithmetic" } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-arithmetic = { version = "5.0.0", default-features = false, path = "../../primitives/arithmetic" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-io = { version = "6.0.0", path = "../../primitives/io" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } +sp-core = { version = "6.0.0", path = "../../primitives/core" } +sp-io = { version = "6.0.0", path = "../../primitives/io" } [features] default = ["std"] std = [ "codec/std", - "scale-info/std", - "sp-std/std", - "sp-runtime/std", - "sp-arithmetic/std", "frame-benchmarking/std", "frame-support/std", "frame-system/std", + "scale-info/std", + "sp-arithmetic/std", + "sp-runtime/std", + "sp-std/std", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", diff --git a/frame/grandpa/Cargo.toml b/frame/grandpa/Cargo.toml index 7312ae7da4cf8..b1c2da09b53ff 100644 --- a/frame/grandpa/Cargo.toml +++ b/frame/grandpa/Cargo.toml @@ -14,51 +14,51 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +log = { version = "0.4.16", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +pallet-authorship = { version = "4.0.0-dev", default-features = false, path = "../authorship" } +pallet-session = { version = "4.0.0-dev", default-features = false, path = "../session" } sp-application-crypto = { version = "6.0.0", default-features = false, path = "../../primitives/application-crypto" } sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } sp-finality-grandpa = { version = "4.0.0-dev", default-features = false, path = "../../primitives/finality-grandpa" } sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-session = { version = "4.0.0-dev", default-features = false, path = "../../primitives/session" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-session = { version = "4.0.0-dev", default-features = false, path = "../../primitives/session" } sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } -frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } -frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -pallet-authorship = { version = "4.0.0-dev", default-features = false, path = "../authorship" } -pallet-session = { version = "4.0.0-dev", default-features = false, path = "../session" } -log = { version = "0.4.16", default-features = false } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -frame-benchmarking = { version = "4.0.0-dev", path = "../benchmarking" } grandpa = { package = "finality-grandpa", version = "0.15.0", features = ["derive-codec"] } -sp-keyring = { version = "6.0.0", path = "../../primitives/keyring" } +frame-benchmarking = { version = "4.0.0-dev", path = "../benchmarking" } +frame-election-provider-support = { version = "4.0.0-dev", path = "../election-provider-support" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } pallet-offences = { version = "4.0.0-dev", path = "../offences" } pallet-staking = { version = "4.0.0-dev", path = "../staking" } pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../staking/reward-curve" } pallet-timestamp = { version = "4.0.0-dev", path = "../timestamp" } -frame-election-provider-support = { version = "4.0.0-dev", path = "../election-provider-support" } +sp-keyring = { version = "6.0.0", path = "../../primitives/keyring" } [features] default = ["std"] std = [ "codec/std", - "scale-info/std", "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "log/std", + "pallet-authorship/std", + "pallet-session/std", + "scale-info/std", "sp-application-crypto/std", "sp-core/std", "sp-finality-grandpa/std", - "sp-session/std", - "sp-std/std", - "frame-support/std", "sp-runtime/std", + "sp-session/std", "sp-staking/std", - "frame-system/std", - "pallet-authorship/std", - "pallet-session/std", - "log/std", + "sp-std/std", ] runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/identity/Cargo.toml b/frame/identity/Cargo.toml index 5dff7acc73e26..d5057e302339b 100644 --- a/frame/identity/Cargo.toml +++ b/frame/identity/Cargo.toml @@ -14,30 +14,30 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } enumflags2 = { version = "0.7.4" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-core = { version = "6.0.0", path = "../../primitives/core" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } +sp-core = { version = "6.0.0", path = "../../primitives/core" } [features] default = ["std"] std = [ "codec/std", - "scale-info/std", - "sp-std/std", - "sp-io/std", - "sp-runtime/std", "frame-benchmarking/std", "frame-support/std", "frame-system/std", + "scale-info/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", diff --git a/frame/im-online/Cargo.toml b/frame/im-online/Cargo.toml index 8ac2da0fbb053..1f04c8b1bb3d8 100644 --- a/frame/im-online/Cargo.toml +++ b/frame/im-online/Cargo.toml @@ -13,20 +13,19 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-application-crypto = { version = "6.0.0", default-features = false, path = "../../primitives/application-crypto" } -pallet-authorship = { version = "4.0.0-dev", default-features = false, path = "../authorship" } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +log = { version = "0.4.16", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +pallet-authorship = { version = "4.0.0-dev", default-features = false, path = "../authorship" } +sp-application-crypto = { version = "6.0.0", default-features = false, path = "../../primitives/application-crypto" } sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } -frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } -frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -log = { version = "0.4.16", default-features = false } - -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-session = { version = "4.0.0-dev", path = "../session" } @@ -34,18 +33,18 @@ pallet-session = { version = "4.0.0-dev", path = "../session" } [features] default = ["std"] std = [ - "sp-application-crypto/std", - "pallet-authorship/std", "codec/std", + "frame-support/std", + "frame-system/std", + "log/std", + "pallet-authorship/std", "scale-info/std", + "sp-application-crypto/std", "sp-core/std", - "sp-std/std", "sp-io/std", "sp-runtime/std", "sp-staking/std", - "frame-support/std", - "frame-system/std", - "log/std", + "sp-std/std", ] runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/indices/Cargo.toml b/frame/indices/Cargo.toml index 690fc9db14f95..99edf95fb332b 100644 --- a/frame/indices/Cargo.toml +++ b/frame/indices/Cargo.toml @@ -15,15 +15,14 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -sp-keyring = { version = "6.0.0", optional = true, path = "../../primitives/keyring" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } - -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } +sp-keyring = { version = "6.0.0", optional = true, path = "../../primitives/keyring" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } @@ -31,15 +30,15 @@ pallet-balances = { version = "4.0.0-dev", path = "../balances" } [features] default = ["std"] std = [ - "sp-keyring", "codec/std", + "frame-support/std", + "frame-system/std", "scale-info/std", "sp-core/std", - "sp-std/std", "sp-io/std", - "frame-support/std", + "sp-keyring", "sp-runtime/std", - "frame-system/std", + "sp-std/std", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", diff --git a/frame/lottery/Cargo.toml b/frame/lottery/Cargo.toml index 7932abdc21a04..9e5992195441e 100644 --- a/frame/lottery/Cargo.toml +++ b/frame/lottery/Cargo.toml @@ -17,12 +17,11 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = "derive", ] } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } - -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] frame-support-test = { version = "3.0.0", path = "../support/test" } @@ -34,15 +33,15 @@ sp-io = { version = "6.0.0", path = "../../primitives/io" } default = ["std"] std = [ "codec/std", - "scale-info/std", - "sp-std/std", "frame-support/std", - "sp-runtime/std", "frame-system/std", + "scale-info/std", + "sp-runtime/std", + "sp-std/std", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", - "frame-system/runtime-benchmarks", "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/membership/Cargo.toml b/frame/membership/Cargo.toml index a6ea127b79cba..1545b191c8719 100644 --- a/frame/membership/Cargo.toml +++ b/frame/membership/Cargo.toml @@ -14,36 +14,34 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } log = { version = "0.4.16", default-features = false } - +scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } -frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } -frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } - [features] default = ["std"] std = [ "codec/std", - "scale-info/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", "log/std", + "scale-info/std", "sp-core/std", "sp-io/std", "sp-runtime/std", "sp-std/std", - "frame-support/std", - "frame-system/std", - "frame-benchmarking/std", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/merkle-mountain-range/Cargo.toml b/frame/merkle-mountain-range/Cargo.toml index 8b67174e8385d..0b5d404fe23aa 100644 --- a/frame/merkle-mountain-range/Cargo.toml +++ b/frame/merkle-mountain-range/Cargo.toml @@ -13,19 +13,17 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +mmr-lib = { package = "ckb-merkle-mountain-range", version = "0.3.2", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -mmr-lib = { package = "ckb-merkle-mountain-range", default-features = false, version = "0.3.2" } - +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } sp-mmr-primitives = { version = "4.0.0-dev", default-features = false, path = "../../primitives/merkle-mountain-range" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } -frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } -frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } - [dev-dependencies] env_logger = "0.9" hex-literal = "0.3" @@ -34,16 +32,16 @@ hex-literal = "0.3" default = ["std"] std = [ "codec/std", - "scale-info/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", "mmr-lib/std", + "scale-info/std", "sp-core/std", "sp-io/std", "sp-mmr-primitives/std", "sp-runtime/std", "sp-std/std", - "frame-benchmarking/std", - "frame-support/std", - "frame-system/std", ] runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/merkle-mountain-range/rpc/Cargo.toml b/frame/merkle-mountain-range/rpc/Cargo.toml index 359ee88a9c485..9b3d3a43c6045 100644 --- a/frame/merkle-mountain-range/rpc/Cargo.toml +++ b/frame/merkle-mountain-range/rpc/Cargo.toml @@ -18,7 +18,6 @@ jsonrpc-core = "18.0.0" jsonrpc-core-client = "18.0.0" jsonrpc-derive = "18.0.0" serde = { version = "1.0.136", features = ["derive"] } - sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } sp-core = { version = "6.0.0", path = "../../../primitives/core" } diff --git a/frame/multisig/Cargo.toml b/frame/multisig/Cargo.toml index a2188ca18b3b3..c151016c594c7 100644 --- a/frame/multisig/Cargo.toml +++ b/frame/multisig/Cargo.toml @@ -15,28 +15,27 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } - -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } [dev-dependencies] -sp-core = { version = "6.0.0", path = "../../primitives/core" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } +sp-core = { version = "6.0.0", path = "../../primitives/core" } [features] default = ["std"] std = [ "codec/std", - "scale-info/std", - "sp-runtime/std", "frame-support/std", "frame-system/std", + "scale-info/std", "sp-io/std", - "sp-std/std" + "sp-runtime/std", + "sp-std/std", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", diff --git a/frame/nicks/Cargo.toml b/frame/nicks/Cargo.toml index 9e98864d24f99..5ee2e9193dd2a 100644 --- a/frame/nicks/Cargo.toml +++ b/frame/nicks/Cargo.toml @@ -15,25 +15,25 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-core = { version = "6.0.0", path = "../../primitives/core" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } +sp-core = { version = "6.0.0", path = "../../primitives/core" } [features] default = ["std"] std = [ "codec/std", + "frame-support/std", + "frame-system/std", "scale-info/std", - "sp-std/std", "sp-io/std", "sp-runtime/std", - "frame-support/std", - "frame-system/std", + "sp-std/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/node-authorization/Cargo.toml b/frame/node-authorization/Cargo.toml index cfd1939d36ed7..317b5f392ebfa 100644 --- a/frame/node-authorization/Cargo.toml +++ b/frame/node-authorization/Cargo.toml @@ -13,6 +13,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +log = { version = "0.4.16", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } @@ -20,19 +21,18 @@ sp-core = { version = "6.0.0", default-features = false, path = "../../primitive sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -log = { version = "0.4.16", default-features = false } [features] default = ["std"] std = [ "codec/std", - "scale-info/std", "frame-support/std", "frame-system/std", + "log/std", + "scale-info/std", "sp-core/std", "sp-io/std", "sp-runtime/std", "sp-std/std", - "log/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/offences/Cargo.toml b/frame/offences/Cargo.toml index bb728bd05f2a1..f4aa01643315b 100644 --- a/frame/offences/Cargo.toml +++ b/frame/offences/Cargo.toml @@ -13,34 +13,34 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../balances" } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +log = { version = "0.4.16", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } serde = { version = "1.0.136", optional = true } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -log = { version = "0.4.16", default-features = false } +pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../balances" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-io = { version = "6.0.0", path = "../../primitives/io" } sp-core = { version = "6.0.0", path = "../../primitives/core" } +sp-io = { version = "6.0.0", path = "../../primitives/io" } [features] default = ["std"] std = [ - "pallet-balances/std", "codec/std", + "frame-support/std", + "frame-system/std", + "log/std", + "pallet-balances/std", "scale-info/std", - "sp-std/std", "serde", "sp-runtime/std", "sp-staking/std", - "frame-support/std", - "frame-system/std", - "log/std", + "sp-std/std", ] runtime-benchmarks = [] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/offences/benchmarking/Cargo.toml b/frame/offences/benchmarking/Cargo.toml index 605e0c60a03c2..2b8e461b84192 100644 --- a/frame/offences/benchmarking/Cargo.toml +++ b/frame/offences/benchmarking/Cargo.toml @@ -16,6 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } scale-info = { version = "2.0.0", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../benchmarking" } +frame-election-provider-support = { version = "4.0.0-dev", default-features = false, path = "../../election-provider-support" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } pallet-babe = { version = "4.0.0-dev", default-features = false, path = "../../babe" } @@ -32,7 +33,6 @@ pallet-staking = { version = "4.0.0-dev", default-features = false, features = [ sp-runtime = { version = "6.0.0", default-features = false, path = "../../../primitives/runtime" } sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/staking" } sp-std = { version = "4.0.0", default-features = false, path = "../../../primitives/std" } -frame-election-provider-support = { version = "4.0.0-dev", default-features = false, path = "../../election-provider-support" } [dev-dependencies] pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../../staking/reward-curve" } @@ -43,7 +43,9 @@ sp-io = { version = "6.0.0", path = "../../../primitives/io" } [features] default = ["std"] std = [ + "codec/std", "frame-benchmarking/std", + "frame-election-provider-support/std", "frame-support/std", "frame-system/std", "pallet-babe/std", @@ -53,10 +55,8 @@ std = [ "pallet-offences/std", "pallet-session/std", "pallet-staking/std", + "scale-info/std", "sp-runtime/std", "sp-staking/std", - "frame-election-provider-support/std", "sp-std/std", - "codec/std", - "scale-info/std", ] diff --git a/frame/preimage/Cargo.toml b/frame/preimage/Cargo.toml index 58809f9e98964..9face65182f7e 100644 --- a/frame/preimage/Cargo.toml +++ b/frame/preimage/Cargo.toml @@ -12,18 +12,17 @@ readme = "README.md" [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } - -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } sp-core = { version = "6.0.0", default-features = false, optional = true, path = "../../primitives/core" } +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-core = { version = "6.0.0", path = "../../primitives/core", default-features = false } pallet-balances = { version = "4.0.0-dev", path = "../balances" } +sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } [features] default = ["std"] @@ -34,13 +33,13 @@ runtime-benchmarks = [ ] std = [ "codec/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", "scale-info/std", - "sp-std/std", - "sp-io/std", "sp-core/std", + "sp-io/std", "sp-runtime/std", - "frame-system/std", - "frame-support/std", - "frame-benchmarking/std", + "sp-std/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/proxy/Cargo.toml b/frame/proxy/Cargo.toml index bdc39a81e34a6..62cbb16dddf29 100644 --- a/frame/proxy/Cargo.toml +++ b/frame/proxy/Cargo.toml @@ -15,29 +15,28 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["max-encoded-len"] } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } - [dev-dependencies] -sp-core = { version = "6.0.0", path = "../../primitives/core" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } pallet-utility = { version = "4.0.0-dev", path = "../utility" } +sp-core = { version = "6.0.0", path = "../../primitives/core" } [features] default = ["std"] std = [ "codec/std", - "scale-info/std", - "sp-runtime/std", "frame-support/std", "frame-system/std", - "sp-std/std", + "scale-info/std", "sp-io/std", + "sp-runtime/std", + "sp-std/std", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", diff --git a/frame/randomness-collective-flip/Cargo.toml b/frame/randomness-collective-flip/Cargo.toml index 17d4ff461aa0f..692f33575e127 100644 --- a/frame/randomness-collective-flip/Cargo.toml +++ b/frame/randomness-collective-flip/Cargo.toml @@ -13,14 +13,13 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -safe-mix = { version = "1.0", default-features = false } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +safe-mix = { version = "1.0", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } - frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] sp-core = { version = "6.0.0", path = "../../primitives/core" } @@ -29,12 +28,12 @@ sp-io = { version = "6.0.0", path = "../../primitives/io" } [features] default = ["std"] std = [ - "safe-mix/std", "codec/std", + "frame-support/std", + "frame-system/std", + "safe-mix/std", "scale-info/std", "sp-runtime/std", "sp-std/std", - "frame-system/std", - "frame-support/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/recovery/Cargo.toml b/frame/recovery/Cargo.toml index 98dbfc0eb0d20..2a72963f6cf0d 100644 --- a/frame/recovery/Cargo.toml +++ b/frame/recovery/Cargo.toml @@ -15,16 +15,16 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-core = { version = "6.0.0", path = "../../primitives/core" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } +sp-core = { version = "6.0.0", path = "../../primitives/core" } [features] default = ["std"] @@ -36,12 +36,12 @@ runtime-benchmarks = [ ] std = [ "codec/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", "scale-info/std", - "sp-std/std", "sp-io/std", "sp-runtime/std", - "frame-support/std", - "frame-system/std", - "frame-benchmarking/std", + "sp-std/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/referenda/Cargo.toml b/frame/referenda/Cargo.toml index d85503a741f43..d13d08542f56e 100644 --- a/frame/referenda/Cargo.toml +++ b/frame/referenda/Cargo.toml @@ -13,44 +13,44 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { version = "1.0.136", optional = true, features = ["derive"] } +assert_matches = { version = "1.5", optional = true } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +serde = { version = "1.0.136", features = ["derive"], optional = true } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -assert_matches = { version = "1.5", optional = true } +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0-dev", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-core = { version = "6.0.0", path = "../../primitives/core" } +assert_matches = { version = "1.5" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } -pallet-scheduler = { version = "4.0.0-dev", path = "../scheduler" } pallet-preimage = { version = "4.0.0-dev", path = "../preimage" } -assert_matches = { version = "1.5" } +pallet-scheduler = { version = "4.0.0-dev", path = "../scheduler" } +sp-core = { version = "6.0.0", path = "../../primitives/core" } [features] default = ["std"] std = [ - "serde", "codec/std", - "scale-info/std", - "sp-std/std", - "sp-io/std", "frame-benchmarking/std", "frame-support/std", - "sp-runtime/std", "frame-system/std", + "scale-info/std", + "serde", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", ] runtime-benchmarks = [ + "assert_matches", "frame-benchmarking", - "frame-system/runtime-benchmarks", "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks", - "assert_matches", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/remark/Cargo.toml b/frame/remark/Cargo.toml index 82c76d679cc10..9759c37bfe92c 100644 --- a/frame/remark/Cargo.toml +++ b/frame/remark/Cargo.toml @@ -13,30 +13,30 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { version = "1.0.136", optional = true } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.136", optional = true } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } [dev-dependencies] -sp-core = { version = "6.0.0", path = "../../primitives/core", default-features = false } +sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } [features] default = ["std"] runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] std = [ - "serde", "codec/std", - "scale-info/std", - "sp-runtime/std", "frame-support/std", "frame-system/std", + "scale-info/std", + "serde", "sp-io/std", + "sp-runtime/std", "sp-std/std", ] diff --git a/frame/scheduler/Cargo.toml b/frame/scheduler/Cargo.toml index 577deb84e559a..90c2bf10d19da 100644 --- a/frame/scheduler/Cargo.toml +++ b/frame/scheduler/Cargo.toml @@ -11,20 +11,19 @@ readme = "README.md" [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } log = { version = "0.4.16", default-features = false } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } - sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-core = { version = "6.0.0", path = "../../primitives/core", default-features = false } -substrate-test-utils = { version = "4.0.0-dev", path = "../../test-utils" } pallet-preimage = { version = "4.0.0-dev", path = "../preimage" } +sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } +substrate-test-utils = { version = "4.0.0-dev", path = "../../test-utils" } [features] default = ["std"] @@ -35,13 +34,13 @@ runtime-benchmarks = [ ] std = [ "codec/std", - "scale-info/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", "log/std", - "sp-std/std", + "scale-info/std", "sp-io/std", "sp-runtime/std", - "frame-system/std", - "frame-support/std", - "frame-benchmarking/std", + "sp-std/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/scored-pool/Cargo.toml b/frame/scored-pool/Cargo.toml index 73fd25158ca88..c857dc9ab9ace 100644 --- a/frame/scored-pool/Cargo.toml +++ b/frame/scored-pool/Cargo.toml @@ -15,11 +15,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } -frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } @@ -29,11 +29,11 @@ sp-core = { version = "6.0.0", path = "../../primitives/core" } default = ["std"] std = [ "codec/std", + "frame-support/std", + "frame-system/std", "scale-info/std", "sp-io/std", "sp-runtime/std", "sp-std/std", - "frame-support/std", - "frame-system/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/session/Cargo.toml b/frame/session/Cargo.toml index d1407f1509307..01822ace9cf28 100644 --- a/frame/session/Cargo.toml +++ b/frame/session/Cargo.toml @@ -13,40 +13,37 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = { version = "0.4.16", default-features = false } -impl-trait-for-tuples = "0.2.2" - codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +impl-trait-for-tuples = "0.2.2" +log = { version = "0.4.16", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } - -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +pallet-timestamp = { version = "4.0.0-dev", default-features = false, path = "../timestamp" } sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } sp-session = { version = "4.0.0-dev", default-features = false, path = "../../primitives/session" } sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } -sp-trie = { version = "6.0.0", default-features = false, path = "../../primitives/trie", optional = true } - -frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } -frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -pallet-timestamp = { version = "4.0.0-dev", default-features = false, path = "../timestamp" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +sp-trie = { version = "6.0.0", default-features = false, optional = true, path = "../../primitives/trie" } [features] -default = ["std", "historical"] +default = ["historical", "std"] historical = ["sp-trie"] std = [ - "log/std", "codec/std", + "frame-support/std", + "frame-system/std", + "log/std", + "pallet-timestamp/std", "scale-info/std", - "sp-std/std", "sp-core/std", "sp-io/std", "sp-runtime/std", "sp-session/std", "sp-staking/std", + "sp-std/std", "sp-trie/std", - "frame-support/std", - "frame-system/std", - "pallet-timestamp/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/session/benchmarking/Cargo.toml b/frame/session/benchmarking/Cargo.toml index b00d1335d22a2..0449f3f674379 100644 --- a/frame/session/benchmarking/Cargo.toml +++ b/frame/session/benchmarking/Cargo.toml @@ -14,36 +14,34 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] rand = { version = "0.7.2", default-features = false } - -sp-std = { version = "4.0.0", default-features = false, path = "../../../primitives/std" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../../primitives/runtime" } -sp-session = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/session" } - frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } pallet-session = { version = "4.0.0-dev", default-features = false, path = "../../session" } pallet-staking = { version = "4.0.0-dev", default-features = false, features = ["runtime-benchmarks"], path = "../../staking" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-session = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/session" } +sp-std = { version = "4.0.0", default-features = false, path = "../../../primitives/std" } [dev-dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } scale-info = "2.0.1" -sp-core = { version = "6.0.0", path = "../../../primitives/core" } -sp-io = { version = "6.0.0", path = "../../../primitives/io" } +frame-election-provider-support = { version = "4.0.0-dev", path = "../../election-provider-support" } pallet-balances = { version = "4.0.0-dev", path = "../../balances" } -pallet-timestamp = { version = "4.0.0-dev", path = "../../timestamp" } pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../../staking/reward-curve" } -frame-election-provider-support = { version = "4.0.0-dev", path = "../../election-provider-support" } +pallet-timestamp = { version = "4.0.0-dev", path = "../../timestamp" } +sp-core = { version = "6.0.0", path = "../../../primitives/core" } +sp-io = { version = "6.0.0", path = "../../../primitives/io" } [features] default = ["std"] std = [ - "sp-std/std", - "sp-runtime/std", - "sp-session/std", "frame-benchmarking/std", "frame-support/std", "frame-system/std", "pallet-session/std", "pallet-staking/std", + "sp-runtime/std", + "sp-session/std", + "sp-std/std", ] diff --git a/frame/society/Cargo.toml b/frame/society/Cargo.toml index 1fd9693d0d00f..b19820c66fb58 100644 --- a/frame/society/Cargo.toml +++ b/frame/society/Cargo.toml @@ -14,32 +14,32 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +rand_chacha = { version = "0.2", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -rand_chacha = { version = "0.2", default-features = false } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-core = { version = "6.0.0", path = "../../primitives/core" } -sp-io ={ version = "6.0.0", path = "../../primitives/io" } frame-support-test = { version = "3.0.0", path = "../support/test" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } +sp-core = { version = "6.0.0", path = "../../primitives/core" } +sp-io = { version = "6.0.0", path = "../../primitives/io" } [features] default = ["std"] std = [ "codec/std", + "frame-support/std", + "frame-system/std", + "rand_chacha/std", "scale-info/std", "sp-runtime/std", - "rand_chacha/std", "sp-std/std", - "frame-support/std", - "frame-system/std", ] runtime-benchmarks = [ - "sp-runtime/runtime-benchmarks", "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/staking/reward-curve/Cargo.toml b/frame/staking/reward-curve/Cargo.toml index dd8361ece6386..0307f61b1dce6 100644 --- a/frame/staking/reward-curve/Cargo.toml +++ b/frame/staking/reward-curve/Cargo.toml @@ -15,10 +15,10 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -syn = { version = "1.0.82", features = ["full", "visit"] } -quote = "1.0.10" -proc-macro2 = "1.0.37" proc-macro-crate = "1.1.3" +proc-macro2 = "1.0.37" +quote = "1.0.10" +syn = { version = "1.0.82", features = ["full", "visit"] } [dev-dependencies] sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } diff --git a/frame/staking/reward-fn/Cargo.toml b/frame/staking/reward-fn/Cargo.toml index f396030a1ae9a..4a41de806e13f 100644 --- a/frame/staking/reward-fn/Cargo.toml +++ b/frame/staking/reward-fn/Cargo.toml @@ -14,12 +14,12 @@ targets = ["x86_64-unknown-linux-gnu"] [lib] [dependencies] -sp-arithmetic = { version = "5.0.0", default-features = false, path = "../../../primitives/arithmetic" } log = { version = "0.4.16", default-features = false } +sp-arithmetic = { version = "5.0.0", default-features = false, path = "../../../primitives/arithmetic" } [features] default = ["std"] std = [ - "sp-arithmetic/std", "log/std", + "sp-arithmetic/std", ] diff --git a/frame/state-trie-migration/Cargo.toml b/frame/state-trie-migration/Cargo.toml index 0c6ad240281e1..cae3bb1a9f975 100644 --- a/frame/state-trie-migration/Cargo.toml +++ b/frame/state-trie-migration/Cargo.toml @@ -4,7 +4,7 @@ version = "4.0.0-dev" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet migration of trie" readme = "README.md" @@ -13,40 +13,37 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } log = { version = "0.4.16", default-features = false } - -sp-std = { default-features = false, path = "../../primitives/std" } -sp-io = { default-features = false, path = "../../primitives/io" } -sp-core = { default-features = false, path = "../../primitives/core" } -sp-runtime = { default-features = false, path = "../../primitives/runtime" } -substrate-state-trie-migration-rpc = { optional = true, path = "../../utils/frame/rpc/state-trie-migration-rpc" } - -frame-support = { default-features = false, path = "../support" } -frame-system = { default-features = false, path = "../system" } -frame-benchmarking = { default-features = false, path = "../benchmarking", optional = true } - +scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } serde = { version = "1.0.133", optional = true } thousands = { version = "0.2.0", optional = true } -remote-externalities = { path = "../../utils/frame/remote-externalities", optional = true } zstd = { version = "0.10.0", default-features = false, optional = true } +frame-benchmarking = { default-features = false, optional = true, path = "../benchmarking" } +frame-support = { default-features = false, path = "../support" } +frame-system = { default-features = false, path = "../system" } +remote-externalities = { optional = true, path = "../../utils/frame/remote-externalities" } +sp-core = { default-features = false, path = "../../primitives/core" } +sp-io = { default-features = false, path = "../../primitives/io" } +sp-runtime = { default-features = false, path = "../../primitives/runtime" } +sp-std = { default-features = false, path = "../../primitives/std" } +substrate-state-trie-migration-rpc = { optional = true, path = "../../utils/frame/rpc/state-trie-migration-rpc" } [dev-dependencies] -pallet-balances = { path = "../balances" } parking_lot = "0.12.0" -sp-tracing = { path = "../../primitives/tracing" } tokio = { version = "1.10", features = ["macros"] } +pallet-balances = { path = "../balances" } +sp-tracing = { path = "../../primitives/tracing" } [features] default = ["std"] std = [ - "log/std", - "scale-info/std", "codec/std", "frame-benchmarking/std", "frame-support/std", "frame-system/std", + "log/std", + "scale-info/std", "sp-core/std", "sp-io/std", "sp-runtime/std", @@ -54,5 +51,4 @@ std = [ ] runtime-benchmarks = ["frame-benchmarking"] try-runtime = ["frame-support/try-runtime"] - -remote-test = [ "std", "zstd", "serde", "thousands", "remote-externalities", "substrate-state-trie-migration-rpc" ] +remote-test = [ "remote-externalities", "serde", "std", "substrate-state-trie-migration-rpc", "thousands", "zstd" ] diff --git a/frame/sudo/Cargo.toml b/frame/sudo/Cargo.toml index b209351ddaf07..d2b3f98e65d0b 100644 --- a/frame/sudo/Cargo.toml +++ b/frame/sudo/Cargo.toml @@ -15,11 +15,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] sp-core = { version = "6.0.0", path = "../../primitives/core" } @@ -28,11 +28,11 @@ sp-core = { version = "6.0.0", path = "../../primitives/core" } default = ["std"] std = [ "codec/std", + "frame-support/std", + "frame-system/std", "scale-info/std", - "sp-std/std", "sp-io/std", "sp-runtime/std", - "frame-support/std", - "frame-system/std", + "sp-std/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/support/procedural/Cargo.toml b/frame/support/procedural/Cargo.toml index 8f7438710dfdb..f8b325479c31d 100644 --- a/frame/support/procedural/Cargo.toml +++ b/frame/support/procedural/Cargo.toml @@ -15,11 +15,11 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -frame-support-procedural-tools = { version = "4.0.0-dev", path = "./tools" } +Inflector = "0.11.4" proc-macro2 = "1.0.37" quote = "1.0.10" -Inflector = "0.11.4" syn = { version = "1.0.82", features = ["full"] } +frame-support-procedural-tools = { version = "4.0.0-dev", path = "./tools" } [features] default = ["std"] diff --git a/frame/support/procedural/tools/Cargo.toml b/frame/support/procedural/tools/Cargo.toml index 00c453b1f1928..f76b2480f9838 100644 --- a/frame/support/procedural/tools/Cargo.toml +++ b/frame/support/procedural/tools/Cargo.toml @@ -12,8 +12,8 @@ description = "Proc macro helpers for procedural macros" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -frame-support-procedural-tools-derive = { version = "3.0.0", path = "./derive" } +proc-macro-crate = "1.1.3" proc-macro2 = "1.0.37" quote = "1.0.10" syn = { version = "1.0.82", features = ["full", "visit", "extra-traits"] } -proc-macro-crate = "1.1.3" +frame-support-procedural-tools-derive = { version = "3.0.0", path = "./derive" } diff --git a/frame/support/procedural/tools/derive/Cargo.toml b/frame/support/procedural/tools/derive/Cargo.toml index 8d536be1cb725..6aface4ae0937 100644 --- a/frame/support/procedural/tools/derive/Cargo.toml +++ b/frame/support/procedural/tools/derive/Cargo.toml @@ -17,4 +17,4 @@ proc-macro = true [dependencies] proc-macro2 = "1.0.37" quote = { version = "1.0.10", features = ["proc-macro"] } -syn = { version = "1.0.82", features = ["proc-macro" ,"full", "extra-traits", "parsing"] } +syn = { version = "1.0.82", features = ["proc-macro", "full", "extra-traits", "parsing"] } diff --git a/frame/support/test/compile_pass/Cargo.toml b/frame/support/test/compile_pass/Cargo.toml index 5850d2e5db148..c1619c6a56c97 100644 --- a/frame/support/test/compile_pass/Cargo.toml +++ b/frame/support/test/compile_pass/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" publish = false -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" [package.metadata.docs.rs] @@ -14,20 +14,20 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../../" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../../../system" } sp-core = { version = "6.0.0", default-features = false, path = "../../../../primitives/core" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../../../primitives/runtime" } sp-version = { version = "5.0.0", default-features = false, path = "../../../../primitives/version" } -frame-support = { version = "4.0.0-dev", default-features = false, path = "../../" } -frame-system = { version = "4.0.0-dev", default-features = false, path = "../../../system" } [features] default = ["std"] std = [ "codec/std", + "frame-support/std", + "frame-system/std", "scale-info/std", "sp-core/std", "sp-runtime/std", "sp-version/std", - "frame-support/std", - "frame-system/std", ] diff --git a/frame/support/test/pallet/Cargo.toml b/frame/support/test/pallet/Cargo.toml index 51b74d5ec55fe..bf5febeb45441 100644 --- a/frame/support/test/pallet/Cargo.toml +++ b/frame/support/test/pallet/Cargo.toml @@ -21,7 +21,7 @@ frame-system = { version = "4.0.0-dev", default-features = false, path = "../../ default = ["std"] std = [ "codec/std", - "scale-info/std", "frame-support/std", "frame-system/std", + "scale-info/std", ] diff --git a/frame/system/Cargo.toml b/frame/system/Cargo.toml index 935038837d955..f1d2491ad0919 100644 --- a/frame/system/Cargo.toml +++ b/frame/system/Cargo.toml @@ -13,16 +13,16 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { version = "1.0.136", optional = true, features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +log = { version = "0.4.16", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.136", features = ["derive"], optional = true } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-io = { version = "6.0.0", path = "../../primitives/io", default-features = false } +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } sp-version = { version = "5.0.0", default-features = false, path = "../../primitives/version" } -frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } -log = { version = "0.4.16", default-features = false } [dev-dependencies] criterion = "0.3.3" @@ -32,20 +32,20 @@ substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/ru [features] default = ["std"] std = [ - "serde", "codec/std", + "frame-support/std", + "log/std", "scale-info/std", + "serde", "sp-core/std", - "sp-std/std", "sp-io/std", - "frame-support/std", "sp-runtime/std", + "sp-std/std", "sp-version/std", - "log/std", ] runtime-benchmarks = [ - "sp-runtime/runtime-benchmarks", "frame-support/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/system/benchmarking/Cargo.toml b/frame/system/benchmarking/Cargo.toml index c543d5af0412e..b39b1a1c75b9d 100644 --- a/frame/system/benchmarking/Cargo.toml +++ b/frame/system/benchmarking/Cargo.toml @@ -15,12 +15,12 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0", default-features = false, path = "../../../primitives/std" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../../primitives/runtime" } frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../benchmarking" } -frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } sp-core = { version = "6.0.0", default-features = false, path = "../../../primitives/core" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../../primitives/std" } [dev-dependencies] sp-io = { version = "6.0.0", path = "../../../primitives/io" } @@ -29,11 +29,11 @@ sp-io = { version = "6.0.0", path = "../../../primitives/io" } default = ["std"] std = [ "codec/std", - "scale-info/std", - "sp-runtime/std", - "sp-std/std", "frame-benchmarking/std", - "frame-system/std", "frame-support/std", + "frame-system/std", + "scale-info/std", "sp-core/std", + "sp-runtime/std", + "sp-std/std", ] diff --git a/frame/system/rpc/runtime-api/Cargo.toml b/frame/system/rpc/runtime-api/Cargo.toml index e2f85000f5d05..63d76d731e26e 100644 --- a/frame/system/rpc/runtime-api/Cargo.toml +++ b/frame/system/rpc/runtime-api/Cargo.toml @@ -13,12 +13,12 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-api = { version = "4.0.0-dev", default-features = false, path = "../../../../primitives/api" } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +sp-api = { version = "4.0.0-dev", default-features = false, path = "../../../../primitives/api" } [features] default = ["std"] std = [ - "sp-api/std", "codec/std", + "sp-api/std", ] diff --git a/frame/timestamp/Cargo.toml b/frame/timestamp/Cargo.toml index 83eb8a38eca77..971a0ace95d73 100644 --- a/frame/timestamp/Cargo.toml +++ b/frame/timestamp/Cargo.toml @@ -13,37 +13,36 @@ readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] - [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } +log = { version = "0.4.16", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io", optional = true } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../../primitives/inherents" } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../../primitives/inherents" } +sp-io = { version = "6.0.0", default-features = false, optional = true, path = "../../primitives/io" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } sp-timestamp = { version = "4.0.0-dev", default-features = false, path = "../../primitives/timestamp" } -log = { version = "0.4.16", default-features = false } [dev-dependencies] -sp-io ={ version = "6.0.0", path = "../../primitives/io" } sp-core = { version = "6.0.0", path = "../../primitives/core" } +sp-io = { version = "6.0.0", path = "../../primitives/io" } [features] default = ["std"] std = [ - "sp-inherents/std", "codec/std", - "scale-info/std", - "sp-std/std", - "sp-runtime/std", "frame-benchmarking/std", "frame-support/std", "frame-system/std", - "sp-timestamp/std", "log/std", + "scale-info/std", + "sp-inherents/std", + "sp-runtime/std", + "sp-std/std", + "sp-timestamp/std", ] runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks", "sp-io"] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/tips/Cargo.toml b/frame/tips/Cargo.toml index 6cfdd2d1e7062..364135daa97f6 100644 --- a/frame/tips/Cargo.toml +++ b/frame/tips/Cargo.toml @@ -17,37 +17,33 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = log = { version = "0.4.16", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", features = ["derive"], optional = true } - +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +pallet-treasury = { version = "4.0.0-dev", default-features = false, path = "../treasury" } sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } -frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -pallet-treasury = { version = "4.0.0-dev", default-features = false, path = "../treasury" } - -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } - [dev-dependencies] -sp-storage = { version = "6.0.0", path = "../../primitives/storage" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } +sp-storage = { version = "6.0.0", path = "../../primitives/storage" } [features] default = ["std"] std = [ "codec/std", + "frame-support/std", + "frame-system/std", "log/std", + "pallet-treasury/std", "scale-info/std", "serde", - "sp-core/std", "sp-io/std", "sp-runtime/std", "sp-std/std", - "frame-support/std", - "frame-system/std", - "pallet-treasury/std", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", diff --git a/frame/transaction-payment/Cargo.toml b/frame/transaction-payment/Cargo.toml index d37a98deecb2a..6da8f4c699097 100644 --- a/frame/transaction-payment/Cargo.toml +++ b/frame/transaction-payment/Cargo.toml @@ -19,14 +19,12 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", optional = true } smallvec = "1.8.0" - -sp-core = { version = "6.0.0", path = "../../primitives/core", default-features = false } -sp-io = { version = "6.0.0", path = "../../primitives/io", default-features = false } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } - frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] serde_json = "1.0.79" @@ -35,14 +33,14 @@ pallet-balances = { version = "4.0.0-dev", path = "../balances" } [features] default = ["std"] std = [ - "serde", "codec/std", + "frame-support/std", + "frame-system/std", "scale-info/std", + "serde", "sp-core/std", "sp-io/std", "sp-runtime/std", "sp-std/std", - "frame-support/std", - "frame-system/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/transaction-payment/rpc/Cargo.toml b/frame/transaction-payment/rpc/Cargo.toml index 64d7007dfe6c2..b7a353916efbc 100644 --- a/frame/transaction-payment/rpc/Cargo.toml +++ b/frame/transaction-payment/rpc/Cargo.toml @@ -17,10 +17,9 @@ codec = { package = "parity-scale-codec", version = "3.0.0" } jsonrpc-core = "18.0.0" jsonrpc-core-client = "18.0.0" jsonrpc-derive = "18.0.0" - +pallet-transaction-payment-rpc-runtime-api = { version = "4.0.0-dev", path = "./runtime-api" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } sp-core = { version = "6.0.0", path = "../../../primitives/core" } sp-rpc = { version = "6.0.0", path = "../../../primitives/rpc" } sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } -pallet-transaction-payment-rpc-runtime-api = { version = "4.0.0-dev", path = "./runtime-api" } diff --git a/frame/transaction-payment/rpc/runtime-api/Cargo.toml b/frame/transaction-payment/rpc/runtime-api/Cargo.toml index d057361e1f5f7..5e1cb46753524 100644 --- a/frame/transaction-payment/rpc/runtime-api/Cargo.toml +++ b/frame/transaction-payment/rpc/runtime-api/Cargo.toml @@ -14,15 +14,15 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +pallet-transaction-payment = { version = "4.0.0-dev", default-features = false, path = "../../../transaction-payment" } sp-api = { version = "4.0.0-dev", default-features = false, path = "../../../../primitives/api" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../../../primitives/runtime" } -pallet-transaction-payment = { version = "4.0.0-dev", default-features = false, path = "../../../transaction-payment" } [features] default = ["std"] std = [ "codec/std", + "pallet-transaction-payment/std", "sp-api/std", "sp-runtime/std", - "pallet-transaction-payment/std", ] diff --git a/frame/transaction-storage/Cargo.toml b/frame/transaction-storage/Cargo.toml index eec505708ed7d..47cf69cf73f8b 100644 --- a/frame/transaction-storage/Cargo.toml +++ b/frame/transaction-storage/Cargo.toml @@ -13,36 +13,36 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { version = "1.0.136", optional = true } -hex-literal = { version = "0.3.4", optional = true } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +hex-literal = { version = "0.3.4", optional = true } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.136", optional = true } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../balances" } +sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../../primitives/inherents" } +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../../primitives/inherents" } sp-transaction-storage-proof = { version = "4.0.0-dev", default-features = false, path = "../../primitives/transaction-storage-proof" } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } [dev-dependencies] +sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } sp-transaction-storage-proof = { version = "4.0.0-dev", default-features = true, path = "../../primitives/transaction-storage-proof" } -sp-core = { version = "6.0.0", path = "../../primitives/core", default-features = false } [features] default = ["std"] runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks", "hex-literal"] std = [ - "serde", "codec/std", - "scale-info/std", - "sp-runtime/std", "frame-support/std", "frame-system/std", "pallet-balances/std", + "scale-info/std", + "serde", + "sp-inherents/std", "sp-io/std", + "sp-runtime/std", "sp-std/std", - "sp-inherents/std", ] diff --git a/frame/treasury/Cargo.toml b/frame/treasury/Cargo.toml index 85745c6c99fd4..4ae2c035e482a 100644 --- a/frame/treasury/Cargo.toml +++ b/frame/treasury/Cargo.toml @@ -17,34 +17,31 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = "derive", "max-encoded-len", ] } +impl-trait-for-tuples = "0.2.2" scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", features = ["derive"], optional = true } -impl-trait-for-tuples = "0.2.2" - -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } - -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../balances" } - +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-io = { version = "6.0.0", path = "../../primitives/io" } sp-core = { version = "6.0.0", path = "../../primitives/core" } +sp-io = { version = "6.0.0", path = "../../primitives/io" } [features] default = ["std"] std = [ "codec/std", - "scale-info/std", - "serde", - "sp-std/std", - "sp-runtime/std", "frame-support/std", "frame-system/std", "pallet-balances/std", + "scale-info/std", + "serde", + "sp-runtime/std", + "sp-std/std", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", diff --git a/frame/try-runtime/Cargo.toml b/frame/try-runtime/Cargo.toml index e40b92b8e98db..075de318c2a05 100644 --- a/frame/try-runtime/Cargo.toml +++ b/frame/try-runtime/Cargo.toml @@ -13,17 +13,16 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-api = { version = "4.0.0-dev", path = "../../primitives/api", default-features = false } -sp-std = { version = "4.0.0", path = "../../primitives/std" , default-features = false } -sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" , default-features = false } - -frame-support = { version = "4.0.0-dev", path = "../support", default-features = false } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +sp-api = { version = "4.0.0-dev", default-features = false, path = "../../primitives/api" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } [features] default = [ "std" ] std = [ + "frame-support/std", "sp-api/std", - "sp-std/std", "sp-runtime/std", - "frame-support/std", + "sp-std/std", ] diff --git a/frame/uniques/Cargo.toml b/frame/uniques/Cargo.toml index e8b95b8cb80c7..3f78df96b3517 100644 --- a/frame/uniques/Cargo.toml +++ b/frame/uniques/Cargo.toml @@ -14,34 +14,34 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +log = { version = "0.4.16", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } -log = { version = "0.4.16", default-features = false } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-std = { version = "4.0.0", path = "../../primitives/std" } +pallet-balances = { version = "4.0.0-dev", path = "../balances" } sp-core = { version = "6.0.0", path = "../../primitives/core" } sp-io = { version = "6.0.0", path = "../../primitives/io" } -pallet-balances = { version = "4.0.0-dev", path = "../balances" } +sp-std = { version = "4.0.0", path = "../../primitives/std" } [features] default = ["std"] std = [ "codec/std", - "scale-info/std", - "sp-std/std", - "sp-runtime/std", + "frame-benchmarking/std", "frame-support/std", "frame-system/std", - "frame-benchmarking/std", + "scale-info/std", + "sp-runtime/std", + "sp-std/std", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/utility/Cargo.toml b/frame/utility/Cargo.toml index 6235d1ee15556..0c307020b521b 100644 --- a/frame/utility/Cargo.toml +++ b/frame/utility/Cargo.toml @@ -15,28 +15,27 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } - -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } [dev-dependencies] -sp-core = { version = "6.0.0", path = "../../primitives/core" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } +sp-core = { version = "6.0.0", path = "../../primitives/core" } [features] default = ["std"] std = [ "codec/std", - "scale-info/std", - "sp-runtime/std", "frame-support/std", "frame-system/std", + "scale-info/std", "sp-io/std", + "sp-runtime/std", "sp-std/std", ] runtime-benchmarks = [ diff --git a/frame/vesting/Cargo.toml b/frame/vesting/Cargo.toml index 01ce92c6eaa4f..d8cb200caf062 100644 --- a/frame/vesting/Cargo.toml +++ b/frame/vesting/Cargo.toml @@ -16,28 +16,28 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } +log = { version = "0.4.16", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } -log = { version = "0.4.16", default-features = false } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } -sp-core = { version = "6.0.0", path = "../../primitives/core" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } +sp-core = { version = "6.0.0", path = "../../primitives/core" } +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } [features] default = ["std"] std = [ "codec/std", - "scale-info/std", - "sp-std/std", - "sp-runtime/std", "frame-support/std", "frame-system/std", + "scale-info/std", + "sp-runtime/std", + "sp-std/std", ] runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/whitelist/Cargo.toml b/frame/whitelist/Cargo.toml index 5f414e5d32033..c808cacb801af 100644 --- a/frame/whitelist/Cargo.toml +++ b/frame/whitelist/Cargo.toml @@ -15,30 +15,29 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } scale-info = { version = "2.0", default-features = false, features = ["derive"] } -sp-api = { version = "4.0.0-dev", default-features = false, path = "../../primitives/api" } -sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } - +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +sp-api = { version = "4.0.0-dev", default-features = false, path = "../../primitives/api" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-core = { version = "6.0.0", path = "../../primitives/core" } -sp-io = { version = "6.0.0", path = "../../primitives/io" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } pallet-preimage = { version = "4.0.0-dev", path = "../preimage" } +sp-core = { version = "6.0.0", path = "../../primitives/core" } +sp-io = { version = "6.0.0", path = "../../primitives/io" } [features] default = ["std"] std = [ "codec/std", + "frame-support/std", + "frame-system/std", "scale-info/std", "sp-api/std", "sp-runtime/std", "sp-std/std", - "frame-support/std", - "frame-system/std", ] runtime-benchmarks = [ "frame-benchmarking", diff --git a/primitives/application-crypto/test/Cargo.toml b/primitives/application-crypto/test/Cargo.toml index 9e93e78f69ffb..2962fb7477735 100644 --- a/primitives/application-crypto/test/Cargo.toml +++ b/primitives/application-crypto/test/Cargo.toml @@ -13,9 +13,9 @@ repository = "https://github.com/paritytech/substrate/" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-core = { version = "6.0.0", default-features = false, path = "../../core" } -sp-keystore = { version = "0.12.0", path = "../../keystore", default-features = false } -substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } -sp-runtime = { version = "6.0.0", path = "../../runtime" } sp-api = { version = "4.0.0-dev", path = "../../api" } sp-application-crypto = { version = "6.0.0", path = "../" } +sp-core = { version = "6.0.0", default-features = false, path = "../../core" } +sp-keystore = { version = "0.12.0", default-features = false, path = "../../keystore" } +sp-runtime = { version = "6.0.0", path = "../../runtime" } +substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } diff --git a/primitives/arithmetic/Cargo.toml b/primitives/arithmetic/Cargo.toml index 26ee7677363c9..31b893531a8d5 100644 --- a/primitives/arithmetic/Cargo.toml +++ b/primitives/arithmetic/Cargo.toml @@ -13,34 +13,33 @@ readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] - [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", "max-encoded-len", ] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } integer-sqrt = "0.1.2" -static_assertions = "1.1.0" num-traits = { version = "0.2.8", default-features = false } -sp-std = { version = "4.0.0", default-features = false, path = "../std" } -serde = { version = "1.0.136", optional = true, features = ["derive"] } +scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.136", features = ["derive"], optional = true } +static_assertions = "1.1.0" sp-debug-derive = { version = "4.0.0", default-features = false, path = "../debug-derive" } +sp-std = { version = "4.0.0", default-features = false, path = "../std" } [dev-dependencies] -rand = "0.7.2" criterion = "0.3" primitive-types = "0.11.1" +rand = "0.7.2" [features] default = ["std"] std = [ "codec/std", - "scale-info/std", "num-traits/std", - "sp-std/std", + "scale-info/std", "serde", "sp-debug-derive/std", + "sp-std/std", ] [[bench]] diff --git a/primitives/arithmetic/fuzzer/Cargo.toml b/primitives/arithmetic/fuzzer/Cargo.toml index e51dd4e415a6c..33bf313766545 100644 --- a/primitives/arithmetic/fuzzer/Cargo.toml +++ b/primitives/arithmetic/fuzzer/Cargo.toml @@ -14,10 +14,10 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-arithmetic = { version = "5.0.0", path = ".." } honggfuzz = "0.5.49" -primitive-types = "0.11.1" num-bigint = "0.2" +primitive-types = "0.11.1" +sp-arithmetic = { version = "5.0.0", path = ".." } [[bin]] name = "biguint" diff --git a/primitives/authority-discovery/Cargo.toml b/primitives/authority-discovery/Cargo.toml index c452aaa892020..6a822bf20b329 100644 --- a/primitives/authority-discovery/Cargo.toml +++ b/primitives/authority-discovery/Cargo.toml @@ -13,20 +13,20 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-application-crypto = { version = "6.0.0", default-features = false, path = "../application-crypto" } -codec = { package = "parity-scale-codec", default-features = false, version = "3.0.0" } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0", default-features = false, path = "../std" } sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } +sp-application-crypto = { version = "6.0.0", default-features = false, path = "../application-crypto" } sp-runtime = { version = "6.0.0", default-features = false, path = "../runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../std" } [features] default = ["std"] std = [ - "sp-application-crypto/std", "codec/std", "scale-info/std", - "sp-std/std", "sp-api/std", - "sp-runtime/std" + "sp-application-crypto/std", + "sp-runtime/std", + "sp-std/std", ] diff --git a/primitives/authorship/Cargo.toml b/primitives/authorship/Cargo.toml index 75e94b895f120..714d0a2610312 100644 --- a/primitives/authorship/Cargo.toml +++ b/primitives/authorship/Cargo.toml @@ -13,18 +13,18 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +async-trait = { version = "0.1.50", optional = true } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../inherents" } sp-runtime = { version = "6.0.0", default-features = false, path = "../runtime" } sp-std = { version = "4.0.0", default-features = false, path = "../std" } -codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -async-trait = { version = "0.1.50", optional = true } [features] default = [ "std" ] std = [ + "async-trait", "codec/std", - "sp-std/std", "sp-inherents/std", "sp-runtime/std", - "async-trait", + "sp-std/std", ] diff --git a/primitives/beefy/Cargo.toml b/primitives/beefy/Cargo.toml index cf901f4a34fc6..83896674175bc 100644 --- a/primitives/beefy/Cargo.toml +++ b/primitives/beefy/Cargo.toml @@ -13,14 +13,13 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { version = "3.0.0", package = "parity-scale-codec", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } - -sp-api = { version = "4.0.0-dev", path = "../api", default-features = false } -sp-application-crypto = { version = "6.0.0", path = "../application-crypto", default-features = false } -sp-core = { version = "6.0.0", path = "../core", default-features = false } -sp-runtime = { version = "6.0.0", path = "../runtime", default-features = false } -sp-std = { version = "4.0.0", path = "../std", default-features = false } +sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } +sp-application-crypto = { version = "6.0.0", default-features = false, path = "../application-crypto" } +sp-core = { version = "6.0.0", default-features = false, path = "../core" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../std" } [dev-dependencies] hex = "0.4.3" diff --git a/primitives/block-builder/Cargo.toml b/primitives/block-builder/Cargo.toml index 6d7a0a2789c2a..a081b56b9d98a 100644 --- a/primitives/block-builder/Cargo.toml +++ b/primitives/block-builder/Cargo.toml @@ -13,18 +13,18 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-runtime = { version = "6.0.0", default-features = false, path = "../runtime" } -sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } -sp-std = { version = "4.0.0", default-features = false, path = "../std" } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../inherents" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../std" } [features] default = [ "std" ] std = [ - "sp-runtime/std", "codec/std", - "sp-inherents/std", "sp-api/std", + "sp-inherents/std", + "sp-runtime/std", "sp-std/std", ] diff --git a/primitives/blockchain/Cargo.toml b/primitives/blockchain/Cargo.toml index f242132798efa..4389a867f7132 100644 --- a/primitives/blockchain/Cargo.toml +++ b/primitives/blockchain/Cargo.toml @@ -14,14 +14,14 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +futures = "0.3.21" log = "0.4.16" lru = "0.7.5" parking_lot = "0.12.0" thiserror = "1.0.30" -futures = "0.3.21" -codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +sp-api = { version = "4.0.0-dev", path = "../api" } sp-consensus = { version = "0.10.0-dev", path = "../consensus/common" } +sp-database = { version = "4.0.0-dev", path = "../database" } sp-runtime = { version = "6.0.0", path = "../runtime" } sp-state-machine = { version = "0.12.0", path = "../state-machine" } -sp-database = { version = "4.0.0-dev", path = "../database" } -sp-api = { version = "4.0.0-dev", path = "../api" } diff --git a/primitives/consensus/aura/Cargo.toml b/primitives/consensus/aura/Cargo.toml index 2c4a2f0a0de99..41d02ac0779a3 100644 --- a/primitives/consensus/aura/Cargo.toml +++ b/primitives/consensus/aura/Cargo.toml @@ -13,30 +13,30 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-application-crypto = { version = "6.0.0", default-features = false, path = "../../application-crypto" } +async-trait = { version = "0.1.50", optional = true } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0", default-features = false, path = "../../std" } sp-api = { version = "4.0.0-dev", default-features = false, path = "../../api" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../runtime" } +sp-application-crypto = { version = "6.0.0", default-features = false, path = "../../application-crypto" } +sp-consensus = { version = "0.10.0-dev", optional = true, path = "../common" } +sp-consensus-slots = { version = "0.10.0-dev", default-features = false, path = "../slots" } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../../inherents" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../std" } sp-timestamp = { version = "4.0.0-dev", default-features = false, path = "../../timestamp" } -sp-consensus-slots = { version = "0.10.0-dev", default-features = false, path = "../slots" } -sp-consensus = { version = "0.10.0-dev", path = "../common", optional = true } -async-trait = { version = "0.1.50", optional = true } [features] default = ["std"] std = [ - "sp-application-crypto/std", + "async-trait", "codec/std", "scale-info/std", - "sp-std/std", "sp-api/std", - "sp-runtime/std", + "sp-application-crypto/std", + "sp-consensus", + "sp-consensus-slots/std", "sp-inherents/std", + "sp-runtime/std", + "sp-std/std", "sp-timestamp/std", - "sp-consensus-slots/std", - "sp-consensus", - "async-trait", ] diff --git a/primitives/consensus/babe/Cargo.toml b/primitives/consensus/babe/Cargo.toml index 189dc5b2e80f7..57775a1b718da 100644 --- a/primitives/consensus/babe/Cargo.toml +++ b/primitives/consensus/babe/Cargo.toml @@ -13,32 +13,33 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-application-crypto = { version = "6.0.0", default-features = false, path = "../../application-crypto" } +async-trait = { version = "0.1.50", optional = true } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } merlin = { version = "2.0", default-features = false } -sp-std = { version = "4.0.0", default-features = false, path = "../../std" } +scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.136", features = ["derive"], optional = true } sp-api = { version = "4.0.0-dev", default-features = false, path = "../../api" } +sp-application-crypto = { version = "6.0.0", default-features = false, path = "../../application-crypto" } sp-consensus = { version = "0.10.0-dev", optional = true, path = "../common" } sp-consensus-slots = { version = "0.10.0-dev", default-features = false, path = "../slots" } -sp-consensus-vrf = { version = "0.10.0-dev", path = "../vrf", default-features = false } +sp-consensus-vrf = { version = "0.10.0-dev", default-features = false, path = "../vrf" } sp-core = { version = "6.0.0", default-features = false, path = "../../core" } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../../inherents" } -sp-keystore = { version = "0.12.0", default-features = false, path = "../../keystore", optional = true } +sp-keystore = { version = "0.12.0", default-features = false, optional = true, path = "../../keystore" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../runtime" } -sp-timestamp = { version = "4.0.0-dev", path = "../../timestamp", optional = true } -serde = { version = "1.0.136", features = ["derive"], optional = true } -async-trait = { version = "0.1.50", optional = true } +sp-std = { version = "4.0.0", default-features = false, path = "../../std" } +sp-timestamp = { version = "4.0.0-dev", optional = true, path = "../../timestamp" } [features] default = ["std"] std = [ - "sp-application-crypto/std", + "async-trait", "codec/std", - "scale-info/std", "merlin/std", - "sp-std/std", + "scale-info/std", + "serde", "sp-api/std", + "sp-application-crypto/std", "sp-consensus", "sp-consensus-slots/std", "sp-consensus-vrf/std", @@ -46,7 +47,6 @@ std = [ "sp-inherents/std", "sp-keystore", "sp-runtime/std", - "serde", + "sp-std/std", "sp-timestamp", - "async-trait", ] diff --git a/primitives/consensus/common/Cargo.toml b/primitives/consensus/common/Cargo.toml index afbe437a16fa4..5dc94872d6141 100644 --- a/primitives/consensus/common/Cargo.toml +++ b/primitives/consensus/common/Cargo.toml @@ -16,18 +16,18 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] async-trait = "0.1.42" codec = { package = "parity-scale-codec", version = "3.0.0", features = [ - "derive", + "derive", ] } futures = { version = "0.3.21", features = ["thread-pool"] } +futures-timer = "3.0.1" log = "0.4.16" -sp-core = { path = "../../core", version = "6.0.0"} +thiserror = "1.0.30" +sp-core = { version = "6.0.0", path = "../../core" } sp-inherents = { version = "4.0.0-dev", path = "../../inherents" } +sp-runtime = { version = "6.0.0", path = "../../runtime" } sp-state-machine = { version = "0.12.0", path = "../../state-machine" } -futures-timer = "3.0.1" sp-std = { version = "4.0.0", path = "../../std" } sp-version = { version = "5.0.0", path = "../../version" } -sp-runtime = { version = "6.0.0", path = "../../runtime" } -thiserror = "1.0.30" [dev-dependencies] futures = "0.3.21" diff --git a/primitives/consensus/pow/Cargo.toml b/primitives/consensus/pow/Cargo.toml index eb2db085c482b..f909b0b466a71 100644 --- a/primitives/consensus/pow/Cargo.toml +++ b/primitives/consensus/pow/Cargo.toml @@ -13,18 +13,18 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } sp-api = { version = "4.0.0-dev", default-features = false, path = "../../api" } -sp-std = { version = "4.0.0", default-features = false, path = "../../std" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../../runtime" } sp-core = { version = "6.0.0", default-features = false, path = "../../core" } -codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../std" } [features] default = ["std"] std = [ - "sp-std/std", + "codec/std", "sp-api/std", - "sp-runtime/std", "sp-core/std", - "codec/std", + "sp-runtime/std", + "sp-std/std", ] diff --git a/primitives/consensus/vrf/Cargo.toml b/primitives/consensus/vrf/Cargo.toml index 80d2d1ddb09d1..9c8dca910b7e6 100644 --- a/primitives/consensus/vrf/Cargo.toml +++ b/primitives/consensus/vrf/Cargo.toml @@ -13,18 +13,18 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { version = "3.0.0", package = "parity-scale-codec", default-features = false } -schnorrkel = { version = "0.9.1", features = ["preaudit_deprecated", "u64_backend"], default-features = false } -sp-std = { version = "4.0.0", path = "../../std", default-features = false } -sp-core = { version = "6.0.0", path = "../../core", default-features = false } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +schnorrkel = { version = "0.9.1", default-features = false, features = ["preaudit_deprecated", "u64_backend"] } +sp-core = { version = "6.0.0", default-features = false, path = "../../core" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../std" } [features] default = ["std"] std = [ "codec/std", "schnorrkel/std", - "sp-std/std", "sp-core/std", "sp-runtime/std", + "sp-std/std", ] diff --git a/primitives/core/hashing/Cargo.toml b/primitives/core/hashing/Cargo.toml index 0aee960f9e13d..b3ce080f7a704 100644 --- a/primitives/core/hashing/Cargo.toml +++ b/primitives/core/hashing/Cargo.toml @@ -4,7 +4,7 @@ version = "4.0.0" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Primitive core crate hashing implementation." documentation = "https://docs.rs/sp-core-hashing" @@ -13,21 +13,20 @@ documentation = "https://docs.rs/sp-core-hashing" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-std = { version = "4.0.0", default-features = false, path = "../../std" } +blake2 = { version = "0.10.2", default-features = false } byteorder = { version = "1.3.2", default-features = false } - digest = { version = "0.10.3", default-features = false } -blake2 = { version = "0.10.2", default-features = false } sha2 = { version = "0.10.2", default-features = false } sha3 = { version = "0.10.0", default-features = false } twox-hash = { version = "1.6.2", default-features = false, features = ["digest_0_10"] } +sp-std = { version = "4.0.0", default-features = false, path = "../../std" } [features] default = ["std"] std = [ - "sp-std/std", "blake2/std", "sha2/std", "sha3/std", + "sp-std/std", "twox-hash/std", ] diff --git a/primitives/core/hashing/proc-macro/Cargo.toml b/primitives/core/hashing/proc-macro/Cargo.toml index 9a5f991d04362..0d8431facb874 100644 --- a/primitives/core/hashing/proc-macro/Cargo.toml +++ b/primitives/core/hashing/proc-macro/Cargo.toml @@ -4,7 +4,7 @@ version = "5.0.0" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" -homepage = "https://substrate.dev" +homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "This crate provides procedural macros for calculating static hash." documentation = "https://docs.rs/sp-core-hashing-proc-macro" @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -syn = { version = "1.0.82", features = ["full", "parsing"] } -quote = "1.0.6" proc-macro2 = "1.0.37" -sp-core-hashing = { version = "4.0.0", path = "../", default-features = false } +quote = "1.0.6" +syn = { version = "1.0.82", features = ["full", "parsing"] } +sp-core-hashing = { version = "4.0.0", default-features = false, path = "../" } diff --git a/primitives/database/Cargo.toml b/primitives/database/Cargo.toml index 198f44510209e..5aa3d9a239aa3 100644 --- a/primitives/database/Cargo.toml +++ b/primitives/database/Cargo.toml @@ -11,6 +11,5 @@ documentation = "https://docs.rs/sp-database" readme = "README.md" [dependencies] -parking_lot = "0.12.0" kvdb = "0.11.0" - +parking_lot = "0.12.0" diff --git a/primitives/externalities/Cargo.toml b/primitives/externalities/Cargo.toml index 4fc8619a9ba5c..e84047d5da5ed 100644 --- a/primitives/externalities/Cargo.toml +++ b/primitives/externalities/Cargo.toml @@ -14,10 +14,10 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-storage = { version = "6.0.0", path = "../storage", default-features = false } -sp-std = { version = "4.0.0", path = "../std", default-features = false } -environmental = { version = "1.1.3", default-features = false } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +environmental = { version = "1.1.3", default-features = false } +sp-std = { version = "4.0.0", default-features = false, path = "../std" } +sp-storage = { version = "6.0.0", default-features = false, path = "../storage" } [features] default = ["std"] diff --git a/primitives/finality-grandpa/Cargo.toml b/primitives/finality-grandpa/Cargo.toml index e6464207e67f2..277236abc0f8c 100644 --- a/primitives/finality-grandpa/Cargo.toml +++ b/primitives/finality-grandpa/Cargo.toml @@ -13,28 +13,27 @@ readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] - [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } grandpa = { package = "finality-grandpa", version = "0.15.0", default-features = false, features = ["derive-codec"] } log = { version = "0.4.16", optional = true } -serde = { version = "1.0.136", optional = true, features = ["derive"] } +scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.136", features = ["derive"], optional = true } sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } sp-application-crypto = { version = "6.0.0", default-features = false, path = "../application-crypto" } sp-core = { version = "6.0.0", default-features = false, path = "../core" } -sp-keystore = { version = "0.12.0", default-features = false, path = "../keystore", optional = true } +sp-keystore = { version = "0.12.0", default-features = false, optional = true, path = "../keystore" } sp-runtime = { version = "6.0.0", default-features = false, path = "../runtime" } sp-std = { version = "4.0.0", default-features = false, path = "../std" } [features] default = ["std"] std = [ - "log", - "serde", "codec/std", - "scale-info/std", "grandpa/std", + "log", + "scale-info/std", + "serde", "sp-api/std", "sp-application-crypto/std", "sp-core/std", diff --git a/primitives/inherents/Cargo.toml b/primitives/inherents/Cargo.toml index 0e701a397d7d9..c7e10be32fe28 100644 --- a/primitives/inherents/Cargo.toml +++ b/primitives/inherents/Cargo.toml @@ -13,15 +13,14 @@ readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] - [dependencies] -sp-std = { version = "4.0.0", default-features = false, path = "../std" } -sp-core = { version = "6.0.0", default-features = false, path = "../core" } -sp-runtime = { version = "6.0.0", path = "../runtime", optional = true } +async-trait = { version = "0.1.50", optional = true } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -thiserror = { version = "1.0.30", optional = true } impl-trait-for-tuples = "0.2.2" -async-trait = { version = "0.1.50", optional = true } +thiserror = { version = "1.0.30", optional = true } +sp-core = { version = "6.0.0", default-features = false, path = "../core" } +sp-runtime = { version = "6.0.0", optional = true, path = "../runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../std" } [dev-dependencies] futures = "0.3.21" @@ -29,10 +28,10 @@ futures = "0.3.21" [features] default = [ "std" ] std = [ - "sp-std/std", + "async-trait", "codec/std", "sp-core/std", - "thiserror", "sp-runtime", - "async-trait", + "sp-std/std", + "thiserror", ] diff --git a/primitives/keyring/Cargo.toml b/primitives/keyring/Cargo.toml index 6186a51fef853..8f1adb6cf81f3 100644 --- a/primitives/keyring/Cargo.toml +++ b/primitives/keyring/Cargo.toml @@ -14,7 +14,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-core = { version = "6.0.0", path = "../core" } -sp-runtime = { version = "6.0.0", path = "../runtime" } lazy_static = "1.4.0" strum = { version = "0.23.0", features = ["derive"] } +sp-core = { version = "6.0.0", path = "../core" } +sp-runtime = { version = "6.0.0", path = "../runtime" } diff --git a/primitives/keystore/Cargo.toml b/primitives/keystore/Cargo.toml index f201cb8518bc6..551626adbc4e8 100644 --- a/primitives/keystore/Cargo.toml +++ b/primitives/keystore/Cargo.toml @@ -14,16 +14,15 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] async-trait = "0.1.50" +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } futures = "0.3.21" +merlin = { version = "2.0", default-features = false } parking_lot = { version = "0.12.0", default-features = false } +schnorrkel = { version = "0.9.1", default-features = false, features = ["preaudit_deprecated", "u64_backend"] } serde = { version = "1.0", optional = true } thiserror = "1.0" - -codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -schnorrkel = { version = "0.9.1", features = ["preaudit_deprecated", "u64_backend"], default-features = false } -merlin = { version = "2.0", default-features = false } sp-core = { version = "6.0.0", path = "../core" } -sp-externalities = { version = "0.12.0", path = "../externalities", default-features = false } +sp-externalities = { version = "0.12.0", default-features = false, path = "../externalities" } [dev-dependencies] rand = "0.7.2" @@ -32,6 +31,6 @@ rand_chacha = "0.2.2" [features] default = ["std"] std = [ - "serde", "schnorrkel/std", + "serde", ] diff --git a/primitives/merkle-mountain-range/Cargo.toml b/primitives/merkle-mountain-range/Cargo.toml index d6e244d50c495..831d07315f317 100644 --- a/primitives/merkle-mountain-range/Cargo.toml +++ b/primitives/merkle-mountain-range/Cargo.toml @@ -14,8 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } log = { version = "0.4.16", default-features = false } -serde = { version = "1.0.136", optional = true, features = ["derive"] } - +serde = { version = "1.0.136", features = ["derive"], optional = true } sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } sp-core = { version = "6.0.0", default-features = false, path = "../core" } sp-debug-derive = { version = "4.0.0", default-features = false, path = "../debug-derive" } diff --git a/primitives/npos-elections/Cargo.toml b/primitives/npos-elections/Cargo.toml index 13edbfe90008b..7cd3ed1489798 100644 --- a/primitives/npos-elections/Cargo.toml +++ b/primitives/npos-elections/Cargo.toml @@ -15,15 +15,15 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -serde = { version = "1.0.136", optional = true, features = ["derive"] } -sp-std = { version = "4.0.0", default-features = false, path = "../std" } +serde = { version = "1.0.136", features = ["derive"], optional = true } sp-arithmetic = { version = "5.0.0", default-features = false, path = "../arithmetic" } sp-core = { version = "6.0.0", default-features = false, path = "../core" } -sp-runtime = { version = "6.0.0", path = "../runtime", default-features = false } +sp-runtime = { version = "6.0.0", default-features = false, path = "../runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../std" } [dev-dependencies] -substrate-test-utils = { version = "4.0.0-dev", path = "../../test-utils" } rand = "0.7.3" +substrate-test-utils = { version = "4.0.0-dev", path = "../../test-utils" } [features] default = ["std"] @@ -32,8 +32,8 @@ std = [ "codec/std", "scale-info/std", "serde", - "sp-std/std", "sp-arithmetic/std", "sp-core/std", "sp-runtime/std", + "sp-std/std", ] diff --git a/primitives/npos-elections/fuzzer/Cargo.toml b/primitives/npos-elections/fuzzer/Cargo.toml index 71c0c07c3032a..f8cc21cb61077 100644 --- a/primitives/npos-elections/fuzzer/Cargo.toml +++ b/primitives/npos-elections/fuzzer/Cargo.toml @@ -15,10 +15,9 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] clap = { version = "3.1.6", features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } honggfuzz = "0.5" rand = { version = "0.8", features = ["std", "small_rng"] } - -codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } sp-npos-elections = { version = "4.0.0-dev", path = ".." } sp-runtime = { version = "6.0.0", path = "../../runtime" } diff --git a/primitives/offchain/Cargo.toml b/primitives/offchain/Cargo.toml index ff647f0e74205..f21c2fe837110 100644 --- a/primitives/offchain/Cargo.toml +++ b/primitives/offchain/Cargo.toml @@ -13,10 +13,10 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-core = { version = "6.0.0", default-features = false, path = "../core" } sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } +sp-core = { version = "6.0.0", default-features = false, path = "../core" } sp-runtime = { version = "6.0.0", default-features = false, path = "../runtime" } [features] default = ["std"] -std = ["sp-core/std", "sp-api/std", "sp-runtime/std"] +std = ["sp-api/std", "sp-core/std", "sp-runtime/std"] diff --git a/primitives/panic-handler/Cargo.toml b/primitives/panic-handler/Cargo.toml index 7b2023eff0bfc..bd429fd0e8af7 100644 --- a/primitives/panic-handler/Cargo.toml +++ b/primitives/panic-handler/Cargo.toml @@ -15,5 +15,5 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] backtrace = "0.3.64" -regex = "1.5.5" lazy_static = "1.4.0" +regex = "1.5.5" diff --git a/primitives/rpc/Cargo.toml b/primitives/rpc/Cargo.toml index dcfd48558de25..335eb6d6c9a0e 100644 --- a/primitives/rpc/Cargo.toml +++ b/primitives/rpc/Cargo.toml @@ -13,9 +13,9 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +rustc-hash = "1.1.0" serde = { version = "1.0.136", features = ["derive"] } sp-core = { version = "6.0.0", path = "../core" } -rustc-hash = "1.1.0" [dev-dependencies] serde_json = "1.0.79" diff --git a/primitives/runtime-interface/proc-macro/Cargo.toml b/primitives/runtime-interface/proc-macro/Cargo.toml index 3c66f371b7a0c..a3ae1fd48651c 100644 --- a/primitives/runtime-interface/proc-macro/Cargo.toml +++ b/primitives/runtime-interface/proc-macro/Cargo.toml @@ -16,8 +16,8 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -syn = { version = "1.0.82", features = ["full", "visit", "fold", "extra-traits"] } -quote = "1.0.10" -proc-macro2 = "1.0.37" Inflector = "0.11.4" proc-macro-crate = "1.1.3" +proc-macro2 = "1.0.37" +quote = "1.0.10" +syn = { version = "1.0.82", features = ["full", "visit", "fold", "extra-traits"] } diff --git a/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml b/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml index 1b0c9f57e5c44..2905cf2c9879e 100644 --- a/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml +++ b/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml @@ -13,14 +13,14 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] +sp-core = { version = "6.0.0", default-features = false, path = "../../core" } +sp-io = { version = "6.0.0", default-features = false, path = "../../io" } sp-runtime-interface = { version = "6.0.0", default-features = false, path = "../" } sp-std = { version = "4.0.0", default-features = false, path = "../../std" } -sp-io = { version = "6.0.0", default-features = false, path = "../../io" } -sp-core = { version = "6.0.0", default-features = false, path = "../../core" } [build-dependencies] substrate-wasm-builder = { version = "5.0.0-dev", path = "../../../utils/wasm-builder" } [features] default = [ "std" ] -std = [ "sp-runtime-interface/std", "sp-std/std", "sp-core/std", "sp-io/std" ] +std = [ "sp-core/std", "sp-io/std", "sp-runtime-interface/std", "sp-std/std" ] diff --git a/primitives/runtime-interface/test-wasm/Cargo.toml b/primitives/runtime-interface/test-wasm/Cargo.toml index 4dfa0fd015d89..f0e78e0e536b9 100644 --- a/primitives/runtime-interface/test-wasm/Cargo.toml +++ b/primitives/runtime-interface/test-wasm/Cargo.toml @@ -13,14 +13,14 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] +sp-core = { version = "6.0.0", default-features = false, path = "../../core" } +sp-io = { version = "6.0.0", default-features = false, path = "../../io" } sp-runtime-interface = { version = "6.0.0", default-features = false, path = "../" } sp-std = { version = "4.0.0", default-features = false, path = "../../std" } -sp-io = { version = "6.0.0", default-features = false, path = "../../io" } -sp-core = { version = "6.0.0", default-features = false, path = "../../core" } [build-dependencies] substrate-wasm-builder = { version = "5.0.0-dev", path = "../../../utils/wasm-builder" } [features] default = [ "std" ] -std = [ "sp-runtime-interface/std", "sp-std/std", "sp-core/std", "sp-io/std" ] +std = [ "sp-core/std", "sp-io/std", "sp-runtime-interface/std", "sp-std/std" ] diff --git a/primitives/runtime-interface/test/Cargo.toml b/primitives/runtime-interface/test/Cargo.toml index 60962bb16caf5..e897fc0bab71c 100644 --- a/primitives/runtime-interface/test/Cargo.toml +++ b/primitives/runtime-interface/test/Cargo.toml @@ -12,13 +12,13 @@ repository = "https://github.com/paritytech/substrate/" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-runtime-interface = { version = "6.0.0", path = "../" } +tracing = "0.1.29" +tracing-core = "0.1.26" sc-executor = { version = "0.10.0-dev", path = "../../../client/executor" } sc-executor-common = { version = "0.10.0-dev", path = "../../../client/executor/common" } +sp-io = { version = "6.0.0", path = "../../io" } +sp-runtime = { version = "6.0.0", path = "../../runtime" } +sp-runtime-interface = { version = "6.0.0", path = "../" } sp-runtime-interface-test-wasm = { version = "2.0.0", path = "../test-wasm" } sp-runtime-interface-test-wasm-deprecated = { version = "2.0.0", path = "../test-wasm-deprecated" } sp-state-machine = { version = "0.12.0", path = "../../state-machine" } -sp-runtime = { version = "6.0.0", path = "../../runtime" } -sp-io = { version = "6.0.0", path = "../../io" } -tracing = "0.1.29" -tracing-core = "0.1.26" diff --git a/primitives/runtime/Cargo.toml b/primitives/runtime/Cargo.toml index 2d6080f7af849..04e5c2a9fe6ec 100644 --- a/primitives/runtime/Cargo.toml +++ b/primitives/runtime/Cargo.toml @@ -13,49 +13,48 @@ readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] - [dependencies] -serde = { version = "1.0.136", optional = true, features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } +either = { version = "1.5", default-features = false } +hash256-std-hasher = { version = "0.15.2", default-features = false } +impl-trait-for-tuples = "0.2.2" +log = { version = "0.4.16", default-features = false } +parity-util-mem = { version = "0.11.0", default-features = false, features = ["primitive-types"] } +paste = "1.0" +rand = { version = "0.7.2", optional = true } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -sp-core = { version = "6.0.0", default-features = false, path = "../core" } +serde = { version = "1.0.136", features = ["derive"], optional = true } sp-application-crypto = { version = "6.0.0", default-features = false, path = "../application-crypto" } sp-arithmetic = { version = "5.0.0", default-features = false, path = "../arithmetic" } -sp-std = { version = "4.0.0", default-features = false, path = "../std" } +sp-core = { version = "6.0.0", default-features = false, path = "../core" } sp-io = { version = "6.0.0", default-features = false, path = "../io" } -log = { version = "0.4.16", default-features = false } -paste = "1.0" -rand = { version = "0.7.2", optional = true } -impl-trait-for-tuples = "0.2.2" -parity-util-mem = { version = "0.11.0", default-features = false, features = ["primitive-types"] } -hash256-std-hasher = { version = "0.15.2", default-features = false } -either = { version = "1.5", default-features = false } +sp-std = { version = "4.0.0", default-features = false, path = "../std" } [dev-dependencies] -serde_json = "1.0.79" rand = "0.7.2" -sp-state-machine = { version = "0.12.0", path = "../state-machine" } +serde_json = "1.0.79" +zstd = { version = "0.10.0", default-features = false } sp-api = { version = "4.0.0-dev", path = "../api" } -substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } +sp-state-machine = { version = "0.12.0", path = "../state-machine" } sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } -zstd = { version = "0.10.0", default-features = false } +substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } [features] bench = [] runtime-benchmarks = [] default = ["std"] std = [ - "sp-application-crypto/std", - "sp-arithmetic/std", "codec/std", - "scale-info/std", + "either/use_std", + "hash256-std-hasher/std", "log/std", - "sp-core/std", + "parity-util-mem/std", "rand", - "sp-std/std", - "sp-io/std", + "scale-info/std", "serde", - "parity-util-mem/std", - "hash256-std-hasher/std", - "either/use_std", + "sp-application-crypto/std", + "sp-arithmetic/std", + "sp-core/std", + "sp-io/std", + "sp-std/std", ] diff --git a/primitives/sandbox/Cargo.toml b/primitives/sandbox/Cargo.toml index db9301a53029b..6a83e20a94618 100644 --- a/primitives/sandbox/Cargo.toml +++ b/primitives/sandbox/Cargo.toml @@ -19,10 +19,9 @@ wasmi = { version = "0.9.1", default-features = false, features = ["core"] } wasmi = "0.9.0" [dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } log = { version = "0.4", default-features = false } wasmi = { version = "0.9.0", optional = true } - -codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } sp-core = { version = "6.0.0", default-features = false, path = "../core" } sp-io = { version = "6.0.0", default-features = false, path = "../io" } sp-std = { version = "4.0.0", default-features = false, path = "../std" } @@ -35,13 +34,13 @@ wat = "1.0" [features] default = ["std"] std = [ - "log/std", - "wasmi", "codec/std", + "log/std", "sp-core/std", "sp-io/std", "sp-std/std", "sp-wasm-interface/std", + "wasmi", ] strict = [] wasmer-sandbox = [] diff --git a/primitives/session/Cargo.toml b/primitives/session/Cargo.toml index 476bacc88ae34..b45e7131d5fee 100644 --- a/primitives/session/Cargo.toml +++ b/primitives/session/Cargo.toml @@ -17,9 +17,9 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } sp-core = { version = "6.0.0", default-features = false, path = "../core" } -sp-std = { version = "4.0.0", default-features = false, path = "../std" } -sp-staking = { version = "4.0.0-dev", default-features = false, path = "../staking" } sp-runtime = { version = "6.0.0", optional = true, path = "../runtime" } +sp-staking = { version = "4.0.0-dev", default-features = false, path = "../staking" } +sp-std = { version = "4.0.0", default-features = false, path = "../std" } [features] default = [ "std" ] @@ -28,7 +28,7 @@ std = [ "scale-info/std", "sp-api/std", "sp-core/std", - "sp-std/std", - "sp-staking/std", "sp-runtime/std", + "sp-staking/std", + "sp-std/std", ] diff --git a/primitives/state-machine/Cargo.toml b/primitives/state-machine/Cargo.toml index 0080c72b1f0d0..7ee9fe98877a6 100644 --- a/primitives/state-machine/Cargo.toml +++ b/primitives/state-machine/Cargo.toml @@ -14,43 +14,43 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = { version = "0.4.16", optional = true } -thiserror = { version = "1.0.30", optional = true } -parking_lot = { version = "0.12.0", optional = true } -hash-db = { version = "0.15.2", default-features = false } -trie-root = { version = "0.17.0", default-features = false } -sp-trie = { version = "6.0.0", path = "../trie", default-features = false } -sp-core = { version = "6.0.0", path = "../core", default-features = false } -sp-panic-handler = { version = "4.0.0", path = "../panic-handler", optional = true } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +hash-db = { version = "0.15.2", default-features = false } +log = { version = "0.4.16", optional = true } num-traits = { version = "0.2.8", default-features = false } +parking_lot = { version = "0.12.0", optional = true } rand = { version = "0.7.2", optional = true } -sp-externalities = { version = "0.12.0", path = "../externalities", default-features = false } smallvec = "1.8.0" -sp-std = { version = "4.0.0", default-features = false, path = "../std" } +thiserror = { version = "1.0.30", optional = true } tracing = { version = "0.1.29", optional = true } +trie-root = { version = "0.17.0", default-features = false } +sp-core = { version = "6.0.0", default-features = false, path = "../core" } +sp-externalities = { version = "0.12.0", default-features = false, path = "../externalities" } +sp-panic-handler = { version = "4.0.0", optional = true, path = "../panic-handler" } +sp-std = { version = "4.0.0", default-features = false, path = "../std" } +sp-trie = { version = "6.0.0", default-features = false, path = "../trie" } [dev-dependencies] hex-literal = "0.3.4" -sp-runtime = { version = "6.0.0", path = "../runtime" } pretty_assertions = "1.0.0" rand = "0.7.2" +sp-runtime = { version = "6.0.0", path = "../runtime" } [features] default = ["std"] std = [ "codec/std", "hash-db/std", + "log", "num-traits/std", + "parking_lot", + "rand", "sp-core/std", "sp-externalities/std", + "sp-panic-handler", "sp-std/std", "sp-trie/std", - "trie-root/std", - "log", "thiserror", - "parking_lot", - "rand", - "sp-panic-handler", - "tracing" + "tracing", + "trie-root/std", ] diff --git a/primitives/std/Cargo.toml b/primitives/std/Cargo.toml index 4c6aa8f40b498..e4a6a9f6a614f 100644 --- a/primitives/std/Cargo.toml +++ b/primitives/std/Cargo.toml @@ -6,7 +6,6 @@ edition = "2021" license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" - description = "Lowest-abstraction level for the Substrate runtime: just exports useful primitives from std or client/alloc to be used with any code that depends on the runtime." documentation = "https://docs.rs/sp-std" readme = "README.md" diff --git a/primitives/storage/Cargo.toml b/primitives/storage/Cargo.toml index a304bd660cc1e..b37a4eb4b331d 100644 --- a/primitives/storage/Cargo.toml +++ b/primitives/storage/Cargo.toml @@ -14,13 +14,13 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-std = { version = "4.0.0", default-features = false, path = "../std" } -serde = { version = "1.0.136", optional = true, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } impl-serde = { version = "0.3.1", optional = true } ref-cast = "1.0.0" +serde = { version = "1.0.136", features = ["derive"], optional = true } sp-debug-derive = { version = "4.0.0", default-features = false, path = "../debug-derive" } -codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +sp-std = { version = "4.0.0", default-features = false, path = "../std" } [features] default = [ "std" ] -std = [ "sp-std/std", "serde", "impl-serde", "codec/std", "sp-debug-derive/std" ] +std = [ "codec/std", "impl-serde", "serde", "sp-debug-derive/std", "sp-std/std" ] diff --git a/primitives/tasks/Cargo.toml b/primitives/tasks/Cargo.toml index bf97212d08914..647a8ed63ff91 100644 --- a/primitives/tasks/Cargo.toml +++ b/primitives/tasks/Cargo.toml @@ -22,7 +22,7 @@ sp-runtime-interface = { version = "6.0.0", default-features = false, path = ".. sp-std = { version = "4.0.0", default-features = false, path = "../std" } [dev-dependencies] -codec = { package = "parity-scale-codec", default-features = false, version = "3.0.0" } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } [features] default = ["std"] diff --git a/primitives/test-primitives/Cargo.toml b/primitives/test-primitives/Cargo.toml index 0d491ea217c6c..1333c340a68a7 100644 --- a/primitives/test-primitives/Cargo.toml +++ b/primitives/test-primitives/Cargo.toml @@ -12,18 +12,18 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-application-crypto = { version = "6.0.0", default-features = false, path = "../application-crypto" } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +parity-util-mem = { version = "0.11.0", default-features = false, features = ["primitive-types"] } +serde = { version = "1.0.136", features = ["derive"], optional = true } +sp-application-crypto = { version = "6.0.0", default-features = false, path = "../application-crypto" } sp-core = { version = "6.0.0", default-features = false, path = "../core" } -serde = { version = "1.0.136", optional = true, features = ["derive"] } sp-runtime = { version = "6.0.0", default-features = false, path = "../runtime" } -parity-util-mem = { version = "0.11.0", default-features = false, features = ["primitive-types"] } [features] default = [ "std", ] std = [ - "sp-application-crypto/std", "serde", + "sp-application-crypto/std", ] diff --git a/primitives/timestamp/Cargo.toml b/primitives/timestamp/Cargo.toml index db753b0b708d9..8b40050b4aaa5 100644 --- a/primitives/timestamp/Cargo.toml +++ b/primitives/timestamp/Cargo.toml @@ -13,26 +13,26 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } -sp-std = { version = "4.0.0", default-features = false, path = "../std" } -sp-runtime = { version = "6.0.0", default-features = false, path = "../runtime" } +async-trait = { version = "0.1.50", optional = true } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../inherents" } -thiserror = { version = "1.0.30", optional = true } -log = { version = "0.4.16", optional = true } futures-timer = { version = "3.0.2", optional = true } -async-trait = { version = "0.1.50", optional = true } +log = { version = "0.4.16", optional = true } +thiserror = { version = "1.0.30", optional = true } +sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } +sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../inherents" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../std" } [features] default = [ "std" ] std = [ - "sp-api/std", - "sp-std/std", - "sp-runtime/std", + "async-trait", "codec/std", + "futures-timer", + "log", + "sp-api/std", "sp-inherents/std", + "sp-runtime/std", + "sp-std/std", "thiserror", - "log", - "futures-timer", - "async-trait", ] diff --git a/primitives/transaction-storage-proof/Cargo.toml b/primitives/transaction-storage-proof/Cargo.toml index 7e8949d3da4b4..e017377ac63de 100644 --- a/primitives/transaction-storage-proof/Cargo.toml +++ b/primitives/transaction-storage-proof/Cargo.toml @@ -13,26 +13,26 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +async-trait = { version = "0.1.50", optional = true } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +log = { version = "0.4.16", optional = true } +scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +sp-core = { version = "6.0.0", optional = true, path = "../core" } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../inherents" } sp-runtime = { version = "6.0.0", default-features = false, path = "../runtime" } sp-std = { version = "4.0.0", default-features = false, path = "../std" } sp-trie = { version = "6.0.0", optional = true, path = "../trie" } -sp-core = { version = "6.0.0", path = "../core", optional = true } -codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -log = { version = "0.4.16", optional = true } -async-trait = { version = "0.1.50", optional = true } [features] default = [ "std" ] std = [ + "async-trait", "codec/std", + "log", "scale-info/std", - "sp-std/std", + "sp-core", "sp-inherents/std", "sp-runtime/std", + "sp-std/std", "sp-trie/std", - "sp-core", - "log", - "async-trait", ] diff --git a/primitives/trie/Cargo.toml b/primitives/trie/Cargo.toml index f434a15b8964a..60dd88b187bd2 100644 --- a/primitives/trie/Cargo.toml +++ b/primitives/trie/Cargo.toml @@ -19,33 +19,33 @@ harness = false [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0", default-features = false, path = "../std" } hash-db = { version = "0.15.2", default-features = false } +memory-db = { version = "0.29.0", default-features = false } +scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +thiserror = { version = "1.0.30", optional = true } trie-db = { version = "0.23.1", default-features = false } trie-root = { version = "0.17.0", default-features = false } -memory-db = { version = "0.29.0", default-features = false } sp-core = { version = "6.0.0", default-features = false, path = "../core" } -thiserror = { version = "1.0.30", optional = true } +sp-std = { version = "4.0.0", default-features = false, path = "../std" } [dev-dependencies] -trie-bench = "0.30.0" -trie-standardmap = "0.15.2" criterion = "0.3.3" hex-literal = "0.3.4" +trie-bench = "0.30.0" +trie-standardmap = "0.15.2" sp-runtime = { version = "6.0.0", path = "../runtime" } [features] default = ["std"] std = [ - "sp-std/std", "codec/std", - "scale-info/std", "hash-db/std", "memory-db/std", - "trie-db/std", - "trie-root/std", + "scale-info/std", "sp-core/std", + "sp-std/std", "thiserror", + "trie-db/std", + "trie-root/std", ] memory-tracker = [] diff --git a/primitives/version/Cargo.toml b/primitives/version/Cargo.toml index 7488d924070cb..8e48891a3c74e 100644 --- a/primitives/version/Cargo.toml +++ b/primitives/version/Cargo.toml @@ -14,26 +14,26 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -impl-serde = { version = "0.3.1", optional = true } -serde = { version = "1.0.136", optional = true, features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +impl-serde = { version = "0.3.1", optional = true } +parity-wasm = { version = "0.42.2", optional = true } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -sp-std = { version = "4.0.0", default-features = false, path = "../std" } +serde = { version = "1.0.136", features = ["derive"], optional = true } +thiserror = { version = "1.0.30", optional = true } +sp-core-hashing-proc-macro = { version = "5.0.0", path = "../core/hashing/proc-macro" } sp-runtime = { version = "6.0.0", default-features = false, path = "../runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../std" } sp-version-proc-macro = { version = "4.0.0-dev", default-features = false, path = "proc-macro" } -parity-wasm = { version = "0.42.2", optional = true } -sp-core-hashing-proc-macro = { version = "5.0.0", path = "../core/hashing/proc-macro" } -thiserror = { version = "1.0.30", optional = true } [features] default = ["std"] std = [ - "impl-serde", - "serde", "codec/std", + "impl-serde", + "parity-wasm", "scale-info/std", - "sp-std/std", + "serde", "sp-runtime/std", - "parity-wasm", + "sp-std/std", "thiserror", ] diff --git a/primitives/version/proc-macro/Cargo.toml b/primitives/version/proc-macro/Cargo.toml index b9ab7cee0ad2c..5b544c7ddd07d 100644 --- a/primitives/version/proc-macro/Cargo.toml +++ b/primitives/version/proc-macro/Cargo.toml @@ -16,10 +16,10 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", features = [ "derive" ] } +proc-macro2 = "1.0.37" quote = "1.0.10" syn = { version = "1.0.82", features = ["full", "fold", "extra-traits", "visit"] } -proc-macro2 = "1.0.37" -codec = { package = "parity-scale-codec", version = "3.0.0", features = [ "derive" ] } [dev-dependencies] sp-version = { version = "5.0.0", path = ".." } diff --git a/primitives/wasm-interface/Cargo.toml b/primitives/wasm-interface/Cargo.toml index 3473370179b83..07e118d0ecc86 100644 --- a/primitives/wasm-interface/Cargo.toml +++ b/primitives/wasm-interface/Cargo.toml @@ -14,13 +14,13 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -wasmi = { version = "0.9.1", optional = true } -wasmtime = { version = "0.35.3", optional = true, default-features = false } -log = { version = "0.4.16", optional = true } -impl-trait-for-tuples = "0.2.2" -sp-std = { version = "4.0.0", path = "../std", default-features = false } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +impl-trait-for-tuples = "0.2.2" +log = { version = "0.4.16", optional = true } +wasmi = { version = "0.9.1", optional = true } +wasmtime = { version = "0.35.3", default-features = false, optional = true } +sp-std = { version = "4.0.0", default-features = false, path = "../std" } [features] default = [ "std" ] -std = [ "wasmi", "sp-std/std", "codec/std", "log" ] +std = [ "codec/std", "log", "sp-std/std", "wasmi" ] diff --git a/scripts/ci/node-template-release/Cargo.toml b/scripts/ci/node-template-release/Cargo.toml index 667281f6dcad7..8871a04e19b16 100644 --- a/scripts/ci/node-template-release/Cargo.toml +++ b/scripts/ci/node-template-release/Cargo.toml @@ -4,18 +4,19 @@ version = "3.0.0" authors = ["Parity Technologies "] edition = "2021" license = "GPL-3.0" +homepage = "https://substrate.io" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] [dependencies] -toml = "0.4" -tar = "0.4" -glob = "0.2" clap = { version = "3.0", features = ["derive"] } -tempfile = "3" +flate2 = "1.0" fs_extra = "1" git2 = "0.8" -flate2 = "1.0" +glob = "0.2" +tar = "0.4" +tempfile = "3" +toml = "0.4" [workspace] - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] diff --git a/test-utils/Cargo.toml b/test-utils/Cargo.toml index 9f66fa812bb4b..b60183c180b4a 100644 --- a/test-utils/Cargo.toml +++ b/test-utils/Cargo.toml @@ -13,9 +13,9 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] futures = "0.3.16" -substrate-test-utils-derive = { version = "0.10.0-dev", path = "./derive" } tokio = { version = "1.17.0", features = ["macros", "time"] } +substrate-test-utils-derive = { version = "0.10.0-dev", path = "./derive" } [dev-dependencies] -sc-service = { version = "0.10.0-dev", path = "../client/service" } trybuild = { version = "1.0.53", features = [ "diff" ] } +sc-service = { version = "0.10.0-dev", path = "../client/service" } diff --git a/test-utils/client/Cargo.toml b/test-utils/client/Cargo.toml index f99300f27eacb..1ff7d0de1d676 100644 --- a/test-utils/client/Cargo.toml +++ b/test-utils/client/Cargo.toml @@ -12,6 +12,7 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] +async-trait = "0.1.50" codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.21" hex = "0.4" @@ -19,19 +20,18 @@ serde = "1.0.136" serde_json = "1.0.79" sc-client-api = { version = "4.0.0-dev", path = "../../client/api" } sc-client-db = { version = "0.10.0-dev", features = [ - "test-helpers", + "test-helpers", ], path = "../../client/db" } sc-consensus = { version = "0.10.0-dev", path = "../../client/consensus/common" } sc-executor = { version = "0.10.0-dev", path = "../../client/executor" } sc-offchain = { version = "4.0.0-dev", path = "../../client/offchain" } sc-service = { version = "0.10.0-dev", default-features = false, features = [ - "test-helpers", + "test-helpers", ], path = "../../client/service" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } sp-core = { version = "6.0.0", path = "../../primitives/core" } -sp-keystore = { version = "0.12.0", path = "../../primitives/keystore" } sp-keyring = { version = "6.0.0", path = "../../primitives/keyring" } +sp-keystore = { version = "0.12.0", path = "../../primitives/keystore" } sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } sp-state-machine = { version = "0.12.0", path = "../../primitives/state-machine" } -async-trait = "0.1.50" diff --git a/test-utils/derive/Cargo.toml b/test-utils/derive/Cargo.toml index afa729c5163e9..aa4f9bb5eab85 100644 --- a/test-utils/derive/Cargo.toml +++ b/test-utils/derive/Cargo.toml @@ -9,10 +9,10 @@ repository = "https://github.com/paritytech/substrate/" description = "Substrate test utilities macros" [dependencies] -quote = "1.0.10" -syn = { version = "1.0.82", features = ["full"] } proc-macro-crate = "1.1.3" proc-macro2 = "1.0.37" +quote = "1.0.10" +syn = { version = "1.0.82", features = ["full"] } [lib] proc-macro = true diff --git a/test-utils/runtime/client/Cargo.toml b/test-utils/runtime/client/Cargo.toml index a22d9f302a9e9..3a3cfcbe33add 100644 --- a/test-utils/runtime/client/Cargo.toml +++ b/test-utils/runtime/client/Cargo.toml @@ -12,15 +12,15 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } -sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } +codec = { package = "parity-scale-codec", version = "3.0.0" } +futures = "0.3.21" sc-block-builder = { version = "0.10.0-dev", path = "../../../client/block-builder" } -substrate-test-client = { version = "2.0.0", path = "../../client" } -sp-core = { version = "6.0.0", path = "../../../primitives/core" } -substrate-test-runtime = { version = "2.0.0", path = "../../runtime" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } +sc-client-api = { version = "4.0.0-dev", path = "../../../client/api" } +sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -codec = { package = "parity-scale-codec", version = "3.0.0" } -sc-client-api = { version = "4.0.0-dev", path = "../../../client/api" } -futures = "0.3.21" +sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } +sp-core = { version = "6.0.0", path = "../../../primitives/core" } +sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } +substrate-test-client = { version = "2.0.0", path = "../../client" } +substrate-test-runtime = { version = "2.0.0", path = "../../runtime" } diff --git a/test-utils/runtime/transaction-pool/Cargo.toml b/test-utils/runtime/transaction-pool/Cargo.toml index 59d576acdcb8f..98378309ad9c1 100644 --- a/test-utils/runtime/transaction-pool/Cargo.toml +++ b/test-utils/runtime/transaction-pool/Cargo.toml @@ -12,12 +12,12 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -substrate-test-runtime-client = { version = "2.0.0", path = "../client" } -parking_lot = "0.12.0" codec = { package = "parity-scale-codec", version = "3.0.0" } -sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } -sc-transaction-pool = { version = "4.0.0-dev", path = "../../../client/transaction-pool" } -sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../../client/transaction-pool/api" } futures = "0.3.21" +parking_lot = "0.12.0" thiserror = "1.0" +sc-transaction-pool = { version = "4.0.0-dev", path = "../../../client/transaction-pool" } +sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../../client/transaction-pool/api" } +sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } +sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } +substrate-test-runtime-client = { version = "2.0.0", path = "../client" } diff --git a/test-utils/test-crate/Cargo.toml b/test-utils/test-crate/Cargo.toml index f6fea8407eaa7..2b66df6ae6513 100644 --- a/test-utils/test-crate/Cargo.toml +++ b/test-utils/test-crate/Cargo.toml @@ -13,5 +13,5 @@ targets = ["x86_64-unknown-linux-gnu"] [dev-dependencies] tokio = { version = "1.17.0", features = ["macros"] } -test-utils = { version = "4.0.0-dev", path = "..", package = "substrate-test-utils" } sc-service = { version = "0.10.0-dev", path = "../../client/service" } +test-utils = { package = "substrate-test-utils", version = "4.0.0-dev", path = ".." } diff --git a/utils/frame/benchmarking-cli/Cargo.toml b/utils/frame/benchmarking-cli/Cargo.toml index 208099162c52b..871f4e925648b 100644 --- a/utils/frame/benchmarking-cli/Cargo.toml +++ b/utils/frame/benchmarking-cli/Cargo.toml @@ -13,50 +13,49 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +chrono = "0.4" +clap = { version = "3.1.6", features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.0.0" } +handlebars = "4.2.2" +hash-db = "0.15.2" +hex = "0.4.3" +Inflector = "0.11.4" +itertools = "0.10.3" +kvdb = "0.11.0" +lazy_static = "1.4.0" +linked-hash-map = "0.5.4" +log = "0.4.16" +memory-db = "0.29.0" +prettytable-rs = "0.8.0" +rand = { version = "0.8.4", features = ["small_rng"] } +rand_pcg = "0.3.1" +serde = "1.0.136" +serde_json = "1.0.79" +serde_nanos = "0.1.2" +tempfile = "3.2.0" +thiserror = "1.0.30" +thousands = "0.2.0" frame-benchmarking = { version = "4.0.0-dev", path = "../../../frame/benchmarking" } frame-support = { version = "4.0.0-dev", path = "../../../frame/support" } frame-system = { version = "4.0.0-dev", path = "../../../frame/system" } -sp-core = { version = "6.0.0", path = "../../../primitives/core" } sc-block-builder = { version = "0.10.0-dev", path = "../../../client/block-builder" } -sc-service = { version = "0.10.0-dev", default-features = false, path = "../../../client/service" } -sc-client-api = { version = "4.0.0-dev", path = "../../../client/api" } sc-cli = { version = "0.10.0-dev", path = "../../../client/cli" } +sc-client-api = { version = "4.0.0-dev", path = "../../../client/api" } sc-client-db = { version = "0.10.0-dev", path = "../../../client/db" } sc-executor = { version = "0.10.0-dev", path = "../../../client/executor" } +sc-service = { version = "0.10.0-dev", default-features = false, path = "../../../client/service" } sc-sysinfo = { version = "6.0.0-dev", path = "../../../client/sysinfo" } - sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } -sp-externalities = { version = "0.12.0", path = "../../../primitives/externalities" } -sp-database = { version = "4.0.0-dev", path = "../../../primitives/database" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } +sp-core = { version = "6.0.0", path = "../../../primitives/core" } +sp-database = { version = "4.0.0-dev", path = "../../../primitives/database" } +sp-externalities = { version = "0.12.0", path = "../../../primitives/externalities" } sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } sp-keystore = { version = "0.12.0", path = "../../../primitives/keystore" } -sp-storage = { version = "6.0.0", path = "../../../primitives/storage" } sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } sp-state-machine = { version = "0.12.0", path = "../../../primitives/state-machine" } +sp-storage = { version = "6.0.0", path = "../../../primitives/storage" } sp-trie = { version = "6.0.0", path = "../../../primitives/trie" } -codec = { version = "3.0.0", package = "parity-scale-codec" } -clap = { version = "3.1.6", features = ["derive"] } -chrono = "0.4" -serde = "1.0.136" -serde_json = "1.0.79" -handlebars = "4.2.2" -Inflector = "0.11.4" -linked-hash-map = "0.5.4" -log = "0.4.16" -itertools = "0.10.3" -serde_nanos = "0.1.2" -kvdb = "0.11.0" -hash-db = "0.15.2" -hex = "0.4.3" -memory-db = "0.29.0" -rand = { version = "0.8.4", features = ["small_rng"] } -thousands = "0.2.0" -prettytable-rs = "0.8.0" -tempfile = "3.2.0" -rand_pcg = "0.3.1" -lazy_static = "1.4.0" -thiserror = "1.0.30" [features] default = ["db", "sc-client-db/runtime-benchmarks"] diff --git a/utils/frame/frame-utilities-cli/Cargo.toml b/utils/frame/frame-utilities-cli/Cargo.toml index 43c8b31898959..cd9fc4a8cf1e3 100644 --- a/utils/frame/frame-utilities-cli/Cargo.toml +++ b/utils/frame/frame-utilities-cli/Cargo.toml @@ -12,14 +12,11 @@ readme = "README.md" [dependencies] clap = { version = "3.1.6", features = ["derive"] } - -sp-core = { version = "6.0.0", path = "../../../primitives/core" } +frame-support = { version = "4.0.0-dev", path = "../../../frame/support" } +frame-system = { version = "4.0.0-dev", path = "../../../frame/system" } sc-cli = { version = "0.10.0-dev", path = "../../../client/cli" } +sp-core = { version = "6.0.0", path = "../../../primitives/core" } sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } -frame-system = { version = "4.0.0-dev", path = "../../../frame/system" } -frame-support = { version = "4.0.0-dev", path = "../../../frame/support" } - -[dev-dependencies] [features] default = [] diff --git a/utils/frame/remote-externalities/Cargo.toml b/utils/frame/remote-externalities/Cargo.toml index 7ca5b2f255e28..49c004c3c074d 100644 --- a/utils/frame/remote-externalities/Cargo.toml +++ b/utils/frame/remote-externalities/Cargo.toml @@ -13,24 +13,22 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { version = "0.10.1", features = ["ws-client", "macros"] } - +codec = { package = "parity-scale-codec", version = "3.0.0" } env_logger = "0.9" -frame-support = { path = "../../../frame/support", optional = true, version = "4.0.0-dev" } +jsonrpsee = { version = "0.10.1", features = ["ws-client", "macros"] } log = "0.4.16" -codec = { package = "parity-scale-codec", version = "3.0.0" } -serde_json = "1.0" serde = "1.0.136" - -sp-io = { version = "6.0.0", path = "../../../primitives/io" } +serde_json = "1.0" +frame-support = { version = "4.0.0-dev", optional = true, path = "../../../frame/support" } sp-core = { version = "6.0.0", path = "../../../primitives/core" } +sp-io = { version = "6.0.0", path = "../../../primitives/io" } sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } sp-version = { version = "5.0.0", path = "../../../primitives/version" } [dev-dependencies] tokio = { version = "1.17.0", features = ["macros", "rt-multi-thread"] } -pallet-elections-phragmen = { path = "../../../frame/elections-phragmen", version = "5.0.0-dev" } -frame-support = { path = "../../../frame/support", version = "4.0.0-dev" } +frame-support = { version = "4.0.0-dev", path = "../../../frame/support" } +pallet-elections-phragmen = { version = "5.0.0-dev", path = "../../../frame/elections-phragmen" } [features] remote-test = ["frame-support"] diff --git a/utils/frame/rpc/support/Cargo.toml b/utils/frame/rpc/support/Cargo.toml index fe304370727f0..f9967758928e8 100644 --- a/utils/frame/rpc/support/Cargo.toml +++ b/utils/frame/rpc/support/Cargo.toml @@ -2,8 +2,8 @@ name = "substrate-frame-rpc-support" version = "3.0.0" authors = [ - "Parity Technologies ", - "Andrew Dirksen ", + "Parity Technologies ", + "Andrew Dirksen ", ] edition = "2021" license = "Apache-2.0" @@ -15,15 +15,15 @@ description = "Substrate RPC for FRAME's support" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.21" jsonrpc-client-transports = { version = "18.0.0", features = ["http"] } -codec = { package = "parity-scale-codec", version = "3.0.0" } serde = "1" frame-support = { version = "4.0.0-dev", path = "../../../../frame/support" } -sp-storage = { version = "6.0.0", path = "../../../../primitives/storage" } sc-rpc-api = { version = "0.10.0-dev", path = "../../../../client/rpc-api" } +sp-storage = { version = "6.0.0", path = "../../../../primitives/storage" } [dev-dependencies] -frame-system = { version = "4.0.0-dev", path = "../../../../frame/system" } scale-info = "2.0.1" tokio = "1.17.0" +frame-system = { version = "4.0.0-dev", path = "../../../../frame/system" } diff --git a/utils/frame/rpc/system/Cargo.toml b/utils/frame/rpc/system/Cargo.toml index 9f3f2c6f266c5..5252d96af3f75 100644 --- a/utils/frame/rpc/system/Cargo.toml +++ b/utils/frame/rpc/system/Cargo.toml @@ -13,23 +13,23 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sc-client-api = { version = "4.0.0-dev", path = "../../../../client/api" } codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.21" jsonrpc-core = "18.0.0" jsonrpc-core-client = "18.0.0" jsonrpc-derive = "18.0.0" log = "0.4.16" -sp-runtime = { version = "6.0.0", path = "../../../../primitives/runtime" } -sp-api = { version = "4.0.0-dev", path = "../../../../primitives/api" } frame-system-rpc-runtime-api = { version = "4.0.0-dev", path = "../../../../frame/system/rpc/runtime-api" } -sp-core = { version = "6.0.0", path = "../../../../primitives/core" } -sp-blockchain = { version = "4.0.0-dev", path = "../../../../primitives/blockchain" } +sc-client-api = { version = "4.0.0-dev", path = "../../../../client/api" } +sc-rpc-api = { version = "0.10.0-dev", path = "../../../../client/rpc-api" } sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../../../client/transaction-pool/api" } +sp-api = { version = "4.0.0-dev", path = "../../../../primitives/api" } sp-block-builder = { version = "4.0.0-dev", path = "../../../../primitives/block-builder" } -sc-rpc-api = { version = "0.10.0-dev", path = "../../../../client/rpc-api" } +sp-blockchain = { version = "4.0.0-dev", path = "../../../../primitives/blockchain" } +sp-core = { version = "6.0.0", path = "../../../../primitives/core" } +sp-runtime = { version = "6.0.0", path = "../../../../primitives/runtime" } [dev-dependencies] -substrate-test-runtime-client = { version = "2.0.0", path = "../../../../test-utils/runtime/client" } -sp-tracing = { version = "5.0.0", path = "../../../../primitives/tracing" } sc-transaction-pool = { version = "4.0.0-dev", path = "../../../../client/transaction-pool" } +sp-tracing = { version = "5.0.0", path = "../../../../primitives/tracing" } +substrate-test-runtime-client = { version = "2.0.0", path = "../../../../test-utils/runtime/client" } diff --git a/utils/frame/try-runtime/cli/Cargo.toml b/utils/frame/try-runtime/cli/Cargo.toml index 70c890a220a3a..2c0a2787b1dac 100644 --- a/utils/frame/try-runtime/cli/Cargo.toml +++ b/utils/frame/try-runtime/cli/Cargo.toml @@ -14,22 +14,20 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] clap = { version = "3.1.6", features = ["derive"] } +jsonrpsee = { version = "0.10.1", default-features = false, features = ["ws-client"] } log = "0.4.16" parity-scale-codec = "3.0.0" serde = "1.0.136" zstd = { version = "0.10.0", default-features = false } - -sc-service = { version = "0.10.0-dev", default-features = false, path = "../../../../client/service" } +remote-externalities = { version = "0.10.0-dev", path = "../../remote-externalities" } +sc-chain-spec = { version = "4.0.0-dev", path = "../../../../client/chain-spec" } sc-cli = { version = "0.10.0-dev", path = "../../../../client/cli" } sc-executor = { version = "0.10.0-dev", path = "../../../../client/executor" } -sc-chain-spec = { version = "4.0.0-dev", path = "../../../../client/chain-spec" } -sp-state-machine = { version = "0.12.0", path = "../../../../primitives/state-machine" } -sp-runtime = { version = "6.0.0", path = "../../../../primitives/runtime" } +sc-service = { version = "0.10.0-dev", default-features = false, path = "../../../../client/service" } sp-core = { version = "6.0.0", path = "../../../../primitives/core" } +sp-externalities = { version = "0.12.0", path = "../../../../primitives/externalities" } sp-io = { version = "6.0.0", path = "../../../../primitives/io" } sp-keystore = { version = "0.12.0", path = "../../../../primitives/keystore" } -sp-externalities = { version = "0.12.0", path = "../../../../primitives/externalities" } +sp-runtime = { version = "6.0.0", path = "../../../../primitives/runtime" } +sp-state-machine = { version = "0.12.0", path = "../../../../primitives/state-machine" } sp-version = { version = "5.0.0", path = "../../../../primitives/version" } - -remote-externalities = { version = "0.10.0-dev", path = "../../remote-externalities" } -jsonrpsee = { version = "0.10.1", default-features = false, features = ["ws-client"] } diff --git a/utils/prometheus/Cargo.toml b/utils/prometheus/Cargo.toml index a9c3fd03e1688..9864cfd0823ce 100644 --- a/utils/prometheus/Cargo.toml +++ b/utils/prometheus/Cargo.toml @@ -13,12 +13,12 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +futures-util = { version = "0.3.19", default-features = false, features = ["io"] } +hyper = { version = "0.14.16", default-features = false, features = ["http1", "server", "tcp"] } log = "0.4.16" prometheus = { version = "0.13.0", default-features = false } -futures-util = { version = "0.3.19", default-features = false, features = ["io"] } thiserror = "1.0" tokio = { version = "1.17.0", features = ["parking_lot"] } -hyper = { version = "0.14.16", default-features = false, features = ["http1", "server", "tcp"] } [dev-dependencies] hyper = { version = "0.14.16", features = ["client"] } diff --git a/utils/wasm-builder/Cargo.toml b/utils/wasm-builder/Cargo.toml index 66c04432c5dec..0cd1249628f22 100644 --- a/utils/wasm-builder/Cargo.toml +++ b/utils/wasm-builder/Cargo.toml @@ -13,12 +13,12 @@ homepage = "https://substrate.io" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +ansi_term = "0.12.1" build-helper = "0.1.1" cargo_metadata = "0.14.2" +strum = { version = "0.23.0", features = ["derive"] } tempfile = "3.1.0" toml = "0.5.4" walkdir = "2.3.2" wasm-gc-api = "0.1.11" -ansi_term = "0.12.1" sp-maybe-compressed-blob = { version = "4.1.0-dev", path = "../../primitives/maybe-compressed-blob" } -strum = { version = "0.23.0", features = ["derive"] } From b2f1e2ce02d6f5ff76bd627e070e96165361c3bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulo=20Silva=20de=20Souza?= <77391175+joao-paulo-parity@users.noreply.github.com> Date: Wed, 4 May 2022 11:45:23 -0300 Subject: [PATCH 186/484] customize check-dependent-* for release engineering (#11311) --- .gitlab-ci.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index bd75cb0bac63c..c97cb03d9c8ad 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -486,6 +486,7 @@ cargo-check-macos: --dependent-repo "$DEPENDENT_REPO" --github-api-token "$GITHUB_PR_TOKEN" --extra-dependencies "$EXTRA_DEPENDENCIES" + --companion-overrides "$COMPANION_OVERRIDES" # Individual jobs are set up for each dependent project so that they can be ran in parallel. # Arguably we could generate a job for each companion in the PR's description using Gitlab's @@ -495,12 +496,18 @@ check-dependent-polkadot: <<: *check-dependent-project variables: DEPENDENT_REPO: polkadot + COMPANION_OVERRIDES: | + substrate: polkadot-v* + polkadot: release-v* check-dependent-cumulus: <<: *check-dependent-project variables: DEPENDENT_REPO: cumulus EXTRA_DEPENDENCIES: polkadot + COMPANION_OVERRIDES: | + substrate: polkadot-v* + cumulus: polkadot-v* build-linux-substrate: From 67b254ef5c6c7d18375edef5b999f6f99e34a3ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= <123550+andresilva@users.noreply.github.com> Date: Wed, 4 May 2022 17:14:58 +0100 Subject: [PATCH 187/484] babe: only process vrf on module finalization (#11113) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * babe: only process vrf on block execution finalization * babe: rename CurrentBlockRandomness to PreviousBlockRandomness * babe: add test for initialization ordering * babe: rename PreviousBlockRandomness to ParentBlockRandomness * babe: re-add CurrentBlockRandomness with deprecation notice * babe: export CurrentBlockRandomness * babe: silence deprecation warning when exporting CurrentBlockRandomness * babe: suggestion from code review Co-authored-by: Bastian Köcher * babe: flatten nested option * babe: rustfmt Co-authored-by: Bastian Köcher --- Cargo.lock | 1 + frame/babe/src/lib.rs | 183 +++++++++++---------- frame/babe/src/randomness.rs | 50 ++++-- frame/babe/src/tests.rs | 54 ++++-- primitives/consensus/babe/src/digests.rs | 23 ++- primitives/consensus/vrf/Cargo.toml | 2 + primitives/consensus/vrf/src/schnorrkel.rs | 31 +++- 7 files changed, 220 insertions(+), 124 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 00d45f10288a5..e8e64d03de4c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10232,6 +10232,7 @@ name = "sp-consensus-vrf" version = "0.10.0-dev" dependencies = [ "parity-scale-codec", + "scale-info", "schnorrkel", "sp-core", "sp-runtime", diff --git a/frame/babe/src/lib.rs b/frame/babe/src/lib.rs index 2709316d87150..e578f0695ac7c 100644 --- a/frame/babe/src/lib.rs +++ b/frame/babe/src/lib.rs @@ -61,8 +61,10 @@ mod mock; mod tests; pub use equivocation::{BabeEquivocationOffence, EquivocationHandler, HandleEquivocation}; +#[allow(deprecated)] +pub use randomness::CurrentBlockRandomness; pub use randomness::{ - CurrentBlockRandomness, RandomnessFromOneEpochAgo, RandomnessFromTwoEpochsAgo, + ParentBlockRandomness, RandomnessFromOneEpochAgo, RandomnessFromTwoEpochsAgo, }; pub use pallet::*; @@ -104,8 +106,6 @@ impl EpochChangeTrigger for SameAuthoritiesForever { const UNDER_CONSTRUCTION_SEGMENT_LENGTH: u32 = 256; -type MaybeRandomness = Option; - #[frame_support::pallet] pub mod pallet { use super::*; @@ -271,15 +271,16 @@ pub mod pallet { /// if per-block initialization has already been called for current block. #[pallet::storage] #[pallet::getter(fn initialized)] - pub(super) type Initialized = StorageValue<_, MaybeRandomness>; + pub(super) type Initialized = StorageValue<_, Option>; /// This field should always be populated during block processing unless /// secondary plain slots are enabled (which don't contain a VRF output). /// - /// It is set in `on_initialize`, before it will contain the value from the last block. + /// It is set in `on_finalize`, before it will contain the value from the last block. #[pallet::storage] #[pallet::getter(fn author_vrf_randomness)] - pub(super) type AuthorVrfRandomness = StorageValue<_, MaybeRandomness, ValueQuery>; + pub(super) type AuthorVrfRandomness = + StorageValue<_, Option, ValueQuery>; /// The block numbers when the last and current epoch have started, respectively `N-1` and /// `N`. @@ -320,7 +321,7 @@ pub mod pallet { impl GenesisBuild for GenesisConfig { fn build(&self) { SegmentIndex::::put(0); - Pallet::::initialize_authorities(&self.authorities); + Pallet::::initialize_genesis_authorities(&self.authorities); EpochConfig::::put( self.epoch_config.clone().expect("epoch_config must not be None"), ); @@ -331,19 +332,60 @@ pub mod pallet { impl Hooks> for Pallet { /// Initialization fn on_initialize(now: BlockNumberFor) -> Weight { - Self::do_initialize(now); + Self::initialize(now); 0 } /// Block finalization - fn on_finalize(_n: BlockNumberFor) { + fn on_finalize(_now: BlockNumberFor) { // at the end of the block, we can safely include the new VRF output // from this block into the under-construction randomness. If we've determined // that this block was the first in a new epoch, the changeover logic has // already occurred at this point, so the under-construction randomness // will only contain outputs from the right epoch. - if let Some(Some(randomness)) = Initialized::::take() { - Self::deposit_randomness(&randomness); + if let Some(pre_digest) = Initialized::::take().flatten() { + let authority_index = pre_digest.authority_index(); + + if T::DisabledValidators::is_disabled(authority_index) { + panic!( + "Validator with index {:?} is disabled and should not be attempting to author blocks.", + authority_index, + ); + } + + if let Some((vrf_output, vrf_proof)) = pre_digest.vrf() { + let randomness: Option = Authorities::::get() + .get(authority_index as usize) + .and_then(|(authority, _)| { + schnorrkel::PublicKey::from_bytes(authority.as_slice()).ok() + }) + .and_then(|pubkey| { + let current_slot = CurrentSlot::::get(); + + let transcript = sp_consensus_babe::make_transcript( + &Self::randomness(), + current_slot, + EpochIndex::::get(), + ); + + // NOTE: this is verified by the client when importing the block, before + // execution. we don't run the verification again here to avoid slowing + // down the runtime. + debug_assert!(pubkey + .vrf_verify(transcript.clone(), vrf_output, vrf_proof) + .is_ok()); + + vrf_output.0.attach_input_hash(&pubkey, transcript).ok() + }) + .map(|inout| inout.make_bytes(sp_consensus_babe::BABE_VRF_INOUT_CONTEXT)); + + if let Some(randomness) = pre_digest.is_primary().then(|| randomness).flatten() + { + Self::deposit_randomness(&randomness); + } + + AuthorVrfRandomness::::put(randomness); + } } // remove temporary "environment" entry from storage @@ -451,11 +493,10 @@ impl IsMember for Pallet { impl pallet_session::ShouldEndSession for Pallet { fn should_end_session(now: T::BlockNumber) -> bool { // it might be (and it is in current implementation) that session module is calling - // should_end_session() from it's own on_initialize() handler - // => because pallet_session on_initialize() is called earlier than ours, let's ensure - // that we have synced with digest before checking if session should be ended. - Self::do_initialize(now); - + // `should_end_session` from it's own `on_initialize` handler, in which case it's + // possible that babe's own `on_initialize` has not run yet, so let's ensure that we + // have initialized the pallet and updated the current slot. + Self::initialize(now); Self::should_epoch_change(now) } } @@ -573,7 +614,7 @@ impl Pallet { } /// Finds the start slot of the current epoch. only guaranteed to - /// give correct results after `do_initialize` of the first block + /// give correct results after `initialize` of the first block /// in the chain (as its result is based off of `GenesisSlot`). pub fn current_epoch_start() -> Slot { Self::epoch_start(EpochIndex::::get()) @@ -649,15 +690,41 @@ impl Pallet { } } - fn do_initialize(now: T::BlockNumber) { - // since do_initialize can be called twice (if session module is present) - // => let's ensure that we only modify the storage once per block + fn initialize_genesis_authorities(authorities: &[(AuthorityId, BabeAuthorityWeight)]) { + if !authorities.is_empty() { + assert!(Authorities::::get().is_empty(), "Authorities are already initialized!"); + let bounded_authorities = + WeakBoundedVec::<_, T::MaxAuthorities>::try_from(authorities.to_vec()) + .expect("Initial number of authorities should be lower than T::MaxAuthorities"); + Authorities::::put(&bounded_authorities); + NextAuthorities::::put(&bounded_authorities); + } + } + + fn initialize_genesis_epoch(genesis_slot: Slot) { + GenesisSlot::::put(genesis_slot); + debug_assert_ne!(*GenesisSlot::::get(), 0); + + // deposit a log because this is the first block in epoch #0 + // we use the same values as genesis because we haven't collected any + // randomness yet. + let next = NextEpochDescriptor { + authorities: Self::authorities().to_vec(), + randomness: Self::randomness(), + }; + + Self::deposit_consensus(ConsensusLog::NextEpochData(next)); + } + + fn initialize(now: T::BlockNumber) { + // since `initialize` can be called twice (e.g. if session module is present) + // let's ensure that we only do the initialization once per block let initialized = Self::initialized().is_some(); if initialized { return } - let maybe_pre_digest: Option = + let pre_digest = >::digest() .logs .iter() @@ -671,76 +738,29 @@ impl Pallet { }) .next(); - let is_primary = matches!(maybe_pre_digest, Some(PreDigest::Primary(..))); + if let Some(ref pre_digest) = pre_digest { + // the slot number of the current block being initialized + let current_slot = pre_digest.slot(); - let maybe_randomness: MaybeRandomness = maybe_pre_digest.and_then(|digest| { // on the first non-zero block (i.e. block #1) // this is where the first epoch (epoch #0) actually starts. // we need to adjust internal storage accordingly. if *GenesisSlot::::get() == 0 { - GenesisSlot::::put(digest.slot()); - debug_assert_ne!(*GenesisSlot::::get(), 0); - - // deposit a log because this is the first block in epoch #0 - // we use the same values as genesis because we haven't collected any - // randomness yet. - let next = NextEpochDescriptor { - authorities: Self::authorities().to_vec(), - randomness: Self::randomness(), - }; - - Self::deposit_consensus(ConsensusLog::NextEpochData(next)) + Self::initialize_genesis_epoch(current_slot) } - // the slot number of the current block being initialized - let current_slot = digest.slot(); - // how many slots were skipped between current and last block let lateness = current_slot.saturating_sub(CurrentSlot::::get() + 1); let lateness = T::BlockNumber::from(*lateness as u32); Lateness::::put(lateness); CurrentSlot::::put(current_slot); + } - let authority_index = digest.authority_index(); - - if T::DisabledValidators::is_disabled(authority_index) { - panic!( - "Validator with index {:?} is disabled and should not be attempting to author blocks.", - authority_index, - ); - } - - // Extract out the VRF output if we have it - digest.vrf_output().and_then(|vrf_output| { - // Reconstruct the bytes of VRFInOut using the authority id. - Authorities::::get() - .get(authority_index as usize) - .and_then(|author| schnorrkel::PublicKey::from_bytes(author.0.as_slice()).ok()) - .and_then(|pubkey| { - let transcript = sp_consensus_babe::make_transcript( - &Self::randomness(), - current_slot, - EpochIndex::::get(), - ); - - vrf_output.0.attach_input_hash(&pubkey, transcript).ok() - }) - .map(|inout| inout.make_bytes(sp_consensus_babe::BABE_VRF_INOUT_CONTEXT)) - }) - }); - - // For primary VRF output we place it in the `Initialized` storage - // item and it'll be put onto the under-construction randomness later, - // once we've decided which epoch this block is in. - Initialized::::put(if is_primary { maybe_randomness } else { None }); - - // Place either the primary or secondary VRF output into the - // `AuthorVrfRandomness` storage item. - AuthorVrfRandomness::::put(maybe_randomness); + Initialized::::put(pre_digest); // enact epoch change, if necessary. - T::EpochChangeTrigger::trigger::(now) + T::EpochChangeTrigger::trigger::(now); } /// Call this function exactly once when an epoch changes, to update the @@ -762,17 +782,6 @@ impl Pallet { this_randomness } - fn initialize_authorities(authorities: &[(AuthorityId, BabeAuthorityWeight)]) { - if !authorities.is_empty() { - assert!(Authorities::::get().is_empty(), "Authorities are already initialized!"); - let bounded_authorities = - WeakBoundedVec::<_, T::MaxAuthorities>::try_from(authorities.to_vec()) - .expect("Initial number of authorities should be lower than T::MaxAuthorities"); - Authorities::::put(&bounded_authorities); - NextAuthorities::::put(&bounded_authorities); - } - } - fn do_report_equivocation( reporter: Option, equivocation_proof: EquivocationProof, @@ -891,7 +900,7 @@ impl OneSessionHandler for Pallet { I: Iterator, { let authorities = validators.map(|(_, k)| (k, 1)).collect::>(); - Self::initialize_authorities(&authorities); + Self::initialize_genesis_authorities(&authorities); } fn on_new_session<'a, I: 'a>(_changed: bool, validators: I, queued_validators: I) diff --git a/frame/babe/src/randomness.rs b/frame/babe/src/randomness.rs index 7be27f568e9fb..28b2ea7cd665e 100644 --- a/frame/babe/src/randomness.rs +++ b/frame/babe/src/randomness.rs @@ -22,7 +22,7 @@ use super::{ AuthorVrfRandomness, Config, EpochStart, NextRandomness, Randomness, VRF_OUTPUT_LENGTH, }; use frame_support::traits::Randomness as RandomnessT; -use sp_runtime::traits::Hash; +use sp_runtime::traits::{Hash, One, Saturating}; /// Randomness usable by consensus protocols that **depend** upon finality and take action /// based upon on-chain commitments made during the epoch before the previous epoch. @@ -38,7 +38,7 @@ use sp_runtime::traits::Hash; /// /// All input commitments used with `RandomnessFromTwoEpochsAgo` should come from at least /// three epochs ago. We require BABE session keys be registered at least three epochs -/// before being used to derive `CurrentBlockRandomness` for example. +/// before being used to derive `ParentBlockRandomness` for example. /// /// All users learn `RandomnessFromTwoEpochsAgo` when epoch `current_epoch - 1` starts, /// although some learn it a few block earlier inside epoch `current_epoch - 2`. @@ -76,7 +76,7 @@ pub struct RandomnessFromTwoEpochsAgo(sp_std::marker::PhantomData); /// end of every epoch, but they possess some influence over when they possess more slots. /// /// As an example usage, we determine parachain auctions ending times in Polkadot using -/// `RandomnessFromOneEpochAgo` because it reduces bias from `CurrentBlockRandomness` and +/// `RandomnessFromOneEpochAgo` because it reduces bias from `ParentBlockRandomness` and /// does not require the extra finality delay of `RandomnessFromTwoEpochsAgo`. pub struct RandomnessFromOneEpochAgo(sp_std::marker::PhantomData); @@ -89,28 +89,44 @@ pub struct RandomnessFromOneEpochAgo(sp_std::marker::PhantomData); /// wins whatever game they play. /// /// As with `RandomnessFromTwoEpochsAgo`, all input commitments combined with -/// `CurrentBlockRandomness` should come from at least two epoch ago, except preferably +/// `ParentBlockRandomness` should come from at least two epoch ago, except preferably /// not near epoch ending, and thus ideally three epochs ago. /// -/// Almost all users learn this randomness for a block when the block producer announces -/// the block, which makes this randomness appear quite fresh. Yet, the block producer +/// Almost all users learn this randomness for a given block by the time they receive it's +/// parent block, which makes this randomness appear fresh enough. Yet, the block producer /// themselves learned this randomness at the beginning of epoch `current_epoch - 2`, at /// the same time as they learn `RandomnessFromTwoEpochsAgo`. /// /// Aside from just biasing `RandomnessFromTwoEpochsAgo`, adversaries could also bias -/// `CurrentBlockRandomness` by never announcing their block if doing so yields an -/// unfavorable randomness. As such, `CurrentBlockRandomness` should be considered weaker -/// than both other randomness sources provided by BABE, but `CurrentBlockRandomness` +/// `ParentBlockRandomness` by never announcing their block if doing so yields an +/// unfavorable randomness. As such, `ParentBlockRandomness` should be considered weaker +/// than both other randomness sources provided by BABE, but `ParentBlockRandomness` /// remains constrained by declared staking, while a randomness source like block hash is /// only constrained by adversaries' unknowable computational power. /// /// As an example use, parachains could assign block production slots based upon the -/// `CurrentBlockRandomness` of their relay parent or relay parent's parent, provided the +/// `ParentBlockRandomness` of their relay parent or relay parent's parent, provided the /// parachain registers collators but avoids censorship sensitive functionality like /// slashing. Any parachain with slashing could operate BABE itself or perhaps better yet -/// a BABE-like approach that derives its `CurrentBlockRandomness`, and authorizes block -/// production, based upon the relay parent's `CurrentBlockRandomness` or more likely the +/// a BABE-like approach that derives its `ParentBlockRandomness`, and authorizes block +/// production, based upon the relay parent's `ParentBlockRandomness` or more likely the /// relay parent's `RandomnessFromTwoEpochsAgo`. +/// +/// NOTE: there is some nuance here regarding what is current and parent randomness. If +/// you are using this trait from within the runtime (i.e. as part of block execution) +/// then the randomness provided here will always be generated from the parent block. If +/// instead you are using this randomness externally, i.e. after block execution, then +/// this randomness will be provided by the "current" block (this stems from the fact that +/// we process VRF outputs on block execution finalization, i.e. `on_finalize`). +pub struct ParentBlockRandomness(sp_std::marker::PhantomData); + +/// Randomness produced semi-freshly with each block, but inherits limitations of +/// `RandomnessFromTwoEpochsAgo` from which it derives. +/// +/// See [`ParentBlockRandomness`]. +#[deprecated(note = "Should not be relied upon for correctness, \ + will not provide fresh randomness for the current block. \ + Please use `ParentBlockRandomness` instead.")] pub struct CurrentBlockRandomness(sp_std::marker::PhantomData); impl RandomnessT for RandomnessFromTwoEpochsAgo { @@ -133,7 +149,7 @@ impl RandomnessT for RandomnessFromOneEpochA } } -impl RandomnessT, T::BlockNumber> for CurrentBlockRandomness { +impl RandomnessT, T::BlockNumber> for ParentBlockRandomness { fn random(subject: &[u8]) -> (Option, T::BlockNumber) { let random = AuthorVrfRandomness::::get().map(|random| { let mut subject = subject.to_vec(); @@ -143,6 +159,14 @@ impl RandomnessT, T::BlockNumber> for CurrentBlockRan T::Hashing::hash(&subject[..]) }); + (random, >::block_number().saturating_sub(One::one())) + } +} + +#[allow(deprecated)] +impl RandomnessT, T::BlockNumber> for CurrentBlockRandomness { + fn random(subject: &[u8]) -> (Option, T::BlockNumber) { + let (random, _) = ParentBlockRandomness::::random(subject); (random, >::block_number()) } } diff --git a/frame/babe/src/tests.rs b/frame/babe/src/tests.rs index 65c9de85586e4..0859bb7a40849 100644 --- a/frame/babe/src/tests.rs +++ b/frame/babe/src/tests.rs @@ -26,6 +26,7 @@ use frame_support::{ use mock::*; use pallet_session::ShouldEndSession; use sp_consensus_babe::{AllowedSlots, BabeEpochConfiguration, Slot}; +use sp_consensus_vrf::schnorrkel::{VRFOutput, VRFProof}; use sp_core::crypto::Pair; const EMPTY_RANDOMNESS: [u8; 32] = [ @@ -76,11 +77,11 @@ fn first_block_epoch_zero_start() { assert_eq!(Babe::genesis_slot(), genesis_slot); assert_eq!(Babe::current_slot(), genesis_slot); assert_eq!(Babe::epoch_index(), 0); - assert_eq!(Babe::author_vrf_randomness(), Some(vrf_randomness)); Babe::on_finalize(1); let header = System::finalize(); + assert_eq!(Babe::author_vrf_randomness(), Some(vrf_randomness)); assert_eq!(SegmentIndex::::get(), 0); assert_eq!(UnderConstruction::::get(0), vec![vrf_randomness]); assert_eq!(Babe::randomness(), [0; 32]); @@ -105,48 +106,71 @@ fn first_block_epoch_zero_start() { } #[test] -fn author_vrf_output_for_primary() { +fn current_slot_is_processed_on_initialization() { let (pairs, mut ext) = new_test_ext_with_pairs(1); ext.execute_with(|| { let genesis_slot = Slot::from(10); let (vrf_output, vrf_proof, vrf_randomness) = make_vrf_output(genesis_slot, &pairs[0]); - let primary_pre_digest = make_primary_pre_digest(0, genesis_slot, vrf_output, vrf_proof); + let pre_digest = make_primary_pre_digest(0, genesis_slot, vrf_output, vrf_proof); System::reset_events(); - System::initialize(&1, &Default::default(), &primary_pre_digest); + System::initialize(&1, &Default::default(), &pre_digest); + assert_eq!(Babe::current_slot(), Slot::from(0)); + assert!(Babe::initialized().is_none()); - Babe::do_initialize(1); - assert_eq!(Babe::author_vrf_randomness(), Some(vrf_randomness)); + // current slot is updated on initialization + Babe::initialize(1); + assert_eq!(Babe::current_slot(), genesis_slot); + assert!(Babe::initialized().is_some()); + // but author vrf randomness isn't + assert_eq!(Babe::author_vrf_randomness(), None); + // instead it is updated on block finalization Babe::on_finalize(1); - System::finalize(); assert_eq!(Babe::author_vrf_randomness(), Some(vrf_randomness)); }) } -#[test] -fn author_vrf_output_for_secondary_vrf() { +fn test_author_vrf_output(make_pre_digest: F) +where + F: Fn(sp_consensus_babe::AuthorityIndex, Slot, VRFOutput, VRFProof) -> sp_runtime::Digest, +{ let (pairs, mut ext) = new_test_ext_with_pairs(1); ext.execute_with(|| { let genesis_slot = Slot::from(10); let (vrf_output, vrf_proof, vrf_randomness) = make_vrf_output(genesis_slot, &pairs[0]); - let secondary_vrf_pre_digest = - make_secondary_vrf_pre_digest(0, genesis_slot, vrf_output, vrf_proof); + let pre_digest = make_pre_digest(0, genesis_slot, vrf_output, vrf_proof); System::reset_events(); - System::initialize(&1, &Default::default(), &secondary_vrf_pre_digest); + System::initialize(&1, &Default::default(), &pre_digest); - Babe::do_initialize(1); - assert_eq!(Babe::author_vrf_randomness(), Some(vrf_randomness)); + // author vrf randomness is not updated on initialization + Babe::initialize(1); + assert_eq!(Babe::author_vrf_randomness(), None); + // instead it is updated on block finalization to account for any + // epoch changes that might happen during the block Babe::on_finalize(1); + assert_eq!(Babe::author_vrf_randomness(), Some(vrf_randomness)); + + // and it is kept after finalizing the block System::finalize(); assert_eq!(Babe::author_vrf_randomness(), Some(vrf_randomness)); }) } +#[test] +fn author_vrf_output_for_primary() { + test_author_vrf_output(make_primary_pre_digest); +} + +#[test] +fn author_vrf_output_for_secondary_vrf() { + test_author_vrf_output(make_secondary_vrf_pre_digest); +} + #[test] fn no_author_vrf_output_for_secondary_plain() { new_test_ext(1).execute_with(|| { @@ -157,7 +181,7 @@ fn no_author_vrf_output_for_secondary_plain() { System::initialize(&1, &Default::default(), &secondary_plain_pre_digest); assert_eq!(Babe::author_vrf_randomness(), None); - Babe::do_initialize(1); + Babe::initialize(1); assert_eq!(Babe::author_vrf_randomness(), None); Babe::on_finalize(1); diff --git a/primitives/consensus/babe/src/digests.rs b/primitives/consensus/babe/src/digests.rs index 0f21c913ac57e..1e4c820379d7a 100644 --- a/primitives/consensus/babe/src/digests.rs +++ b/primitives/consensus/babe/src/digests.rs @@ -22,13 +22,14 @@ use super::{ BabeEpochConfiguration, Slot, BABE_ENGINE_ID, }; use codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; use sp_runtime::{DigestItem, RuntimeDebug}; use sp_std::vec::Vec; use sp_consensus_vrf::schnorrkel::{Randomness, VRFOutput, VRFProof}; /// Raw BABE primary slot assignment pre-digest. -#[derive(Clone, RuntimeDebug, Encode, Decode)] +#[derive(Clone, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] pub struct PrimaryPreDigest { /// Authority index pub authority_index: super::AuthorityIndex, @@ -41,7 +42,7 @@ pub struct PrimaryPreDigest { } /// BABE secondary slot assignment pre-digest. -#[derive(Clone, RuntimeDebug, Encode, Decode)] +#[derive(Clone, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] pub struct SecondaryPlainPreDigest { /// Authority index /// @@ -55,7 +56,7 @@ pub struct SecondaryPlainPreDigest { } /// BABE secondary deterministic slot assignment with VRF outputs. -#[derive(Clone, RuntimeDebug, Encode, Decode)] +#[derive(Clone, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] pub struct SecondaryVRFPreDigest { /// Authority index pub authority_index: super::AuthorityIndex, @@ -70,7 +71,7 @@ pub struct SecondaryVRFPreDigest { /// A BABE pre-runtime digest. This contains all data required to validate a /// block and for the BABE runtime module. Slots can be assigned to a primary /// (VRF based) and to a secondary (slot number based). -#[derive(Clone, RuntimeDebug, Encode, Decode)] +#[derive(Clone, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] pub enum PreDigest { /// A primary VRF-based slot assignment. #[codec(index = 1)] @@ -102,6 +103,11 @@ impl PreDigest { } } + /// Returns true if this pre-digest is for a primary slot assignment. + pub fn is_primary(&self) -> bool { + matches!(self, PreDigest::Primary(..)) + } + /// Returns the weight _added_ by this digest, not the cumulative weight /// of the chain. pub fn added_weight(&self) -> crate::BabeBlockWeight { @@ -111,11 +117,12 @@ impl PreDigest { } } - /// Returns the VRF output, if it exists. - pub fn vrf_output(&self) -> Option<&VRFOutput> { + /// Returns the VRF output and proof, if they exist. + pub fn vrf(&self) -> Option<(&VRFOutput, &VRFProof)> { match self { - PreDigest::Primary(primary) => Some(&primary.vrf_output), - PreDigest::SecondaryVRF(secondary) => Some(&secondary.vrf_output), + PreDigest::Primary(primary) => Some((&primary.vrf_output, &primary.vrf_proof)), + PreDigest::SecondaryVRF(secondary) => + Some((&secondary.vrf_output, &secondary.vrf_proof)), PreDigest::SecondaryPlain(_) => None, } } diff --git a/primitives/consensus/vrf/Cargo.toml b/primitives/consensus/vrf/Cargo.toml index 9c8dca910b7e6..44aabf074d137 100644 --- a/primitives/consensus/vrf/Cargo.toml +++ b/primitives/consensus/vrf/Cargo.toml @@ -14,6 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +scale-info = { version = "2.0.1", default-features = false } schnorrkel = { version = "0.9.1", default-features = false, features = ["preaudit_deprecated", "u64_backend"] } sp-core = { version = "6.0.0", default-features = false, path = "../../core" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../runtime" } @@ -23,6 +24,7 @@ sp-std = { version = "4.0.0", default-features = false, path = "../../std" } default = ["std"] std = [ "codec/std", + "scale-info/std", "schnorrkel/std", "sp-core/std", "sp-runtime/std", diff --git a/primitives/consensus/vrf/src/schnorrkel.rs b/primitives/consensus/vrf/src/schnorrkel.rs index 094a398893ff6..8666de6c4bc0c 100644 --- a/primitives/consensus/vrf/src/schnorrkel.rs +++ b/primitives/consensus/vrf/src/schnorrkel.rs @@ -17,7 +17,8 @@ //! Schnorrkel-based VRF. -use codec::{Decode, Encode, EncodeLike}; +use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; +use scale_info::TypeInfo; use schnorrkel::errors::MultiSignatureStage; use sp_core::U512; use sp_std::{ @@ -65,6 +66,20 @@ impl Decode for VRFOutput { } } +impl MaxEncodedLen for VRFOutput { + fn max_encoded_len() -> usize { + <[u8; VRF_OUTPUT_LENGTH]>::max_encoded_len() + } +} + +impl TypeInfo for VRFOutput { + type Identity = [u8; VRF_OUTPUT_LENGTH]; + + fn type_info() -> scale_info::Type { + Self::Identity::type_info() + } +} + impl TryFrom<[u8; VRF_OUTPUT_LENGTH]> for VRFOutput { type Error = SignatureError; @@ -117,6 +132,20 @@ impl Decode for VRFProof { } } +impl MaxEncodedLen for VRFProof { + fn max_encoded_len() -> usize { + <[u8; VRF_PROOF_LENGTH]>::max_encoded_len() + } +} + +impl TypeInfo for VRFProof { + type Identity = [u8; VRF_PROOF_LENGTH]; + + fn type_info() -> scale_info::Type { + Self::Identity::type_info() + } +} + impl TryFrom<[u8; VRF_PROOF_LENGTH]> for VRFProof { type Error = SignatureError; From 0ab57544d260bc4b46a4a2a3c63597037ab7b005 Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Thu, 5 May 2022 11:50:14 +0200 Subject: [PATCH 188/484] [ci] fix cargo-check-nixos for nightly pipeline (#11358) --- .gitlab-ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c97cb03d9c8ad..224d5d5f0ef3f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -268,8 +268,9 @@ cargo-check-nixos: <<: *docker-env <<: *test-refs before_script: [] + # Don't use CI_IMAGE here because it breaks nightly checks of paritytech/ci-linux image + image: nixos/nix variables: - CI_IMAGE: "nixos/nix" SNAP: "DUMMY" WS_API: "DUMMY" script: From 7ece6571c9c606a8a89b4d3ce11f11f901ca1a75 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 5 May 2022 12:38:53 +0200 Subject: [PATCH 189/484] Bump git2 from 0.13.25 to 0.14.2 (#11341) Bumps [git2](https://github.com/rust-lang/git2-rs) from 0.13.25 to 0.14.2. - [Release notes](https://github.com/rust-lang/git2-rs/releases) - [Commits](https://github.com/rust-lang/git2-rs/compare/0.13.25...0.14.2) --- updated-dependencies: - dependency-name: git2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- utils/frame/generate-bags/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e8e64d03de4c7..1aaf0523765de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2768,9 +2768,9 @@ dependencies = [ [[package]] name = "git2" -version = "0.13.25" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29229cc1b24c0e6062f6e742aa3e256492a5323365e5ed3413599f8a5eff7d6" +checksum = "3826a6e0e2215d7a41c2bfc7c9244123969273f3476b939a226aac0ab56e9e3c" dependencies = [ "bitflags", "libc", @@ -3651,9 +3651,9 @@ checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" [[package]] name = "libgit2-sys" -version = "0.12.26+1.3.0" +version = "0.13.2+1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e1c899248e606fbfe68dcb31d8b0176ebab833b103824af31bddf4b7457494" +checksum = "3a42de9a51a5c12e00fc0e4ca6bc2ea43582fc6418488e8f615e905d886f258b" dependencies = [ "cc", "libc", diff --git a/utils/frame/generate-bags/Cargo.toml b/utils/frame/generate-bags/Cargo.toml index 7c7fdea056b22..34d62ab0d8b5f 100644 --- a/utils/frame/generate-bags/Cargo.toml +++ b/utils/frame/generate-bags/Cargo.toml @@ -21,5 +21,5 @@ sp-io = { version = "6.0.0", path = "../../../primitives/io" } # third party chrono = { version = "0.4.19" } -git2 = { version = "0.13.25", default-features = false } +git2 = { version = "0.14.2", default-features = false } num-format = { version = "0.4.0" } From 9a39f546782f952284157830a35ccd36a01544f3 Mon Sep 17 00:00:00 2001 From: Jun Jiang Date: Thu, 5 May 2022 18:44:13 +0800 Subject: [PATCH 190/484] Add force_batch to utility pallet (#11148) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add batch_try to utility pallet * lint * rename utility.batch_try -> utility.force_batch * Remove un-needed index field for utility.ItemFailed event * Remove indexes of utility,BatchCompletedWithErrors * Apply suggestions from code review Co-authored-by: Louis Merlin Co-authored-by: Louis Merlin Co-authored-by: Bastian Köcher --- frame/utility/src/benchmarking.rs | 13 ++++++ frame/utility/src/lib.rs | 74 +++++++++++++++++++++++++++++++ frame/utility/src/tests.rs | 32 +++++++++++++ frame/utility/src/weights.rs | 11 +++++ 4 files changed, 130 insertions(+) diff --git a/frame/utility/src/benchmarking.rs b/frame/utility/src/benchmarking.rs index 27f346f13f52f..018280f69baeb 100644 --- a/frame/utility/src/benchmarking.rs +++ b/frame/utility/src/benchmarking.rs @@ -73,5 +73,18 @@ benchmarks! { let pallets_origin = Into::::into(pallets_origin); }: _(RawOrigin::Root, Box::new(pallets_origin), call) + force_batch { + let c in 0 .. 1000; + let mut calls: Vec<::Call> = Vec::new(); + for i in 0 .. c { + let call = frame_system::Call::remark { remark: vec![] }.into(); + calls.push(call); + } + let caller = whitelisted_caller(); + }: _(RawOrigin::Signed(caller), calls) + verify { + assert_last_event::(Event::BatchCompleted.into()) + } + impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::tests::Test); } diff --git a/frame/utility/src/lib.rs b/frame/utility/src/lib.rs index ec48087e2ef48..9a8384f836f8b 100644 --- a/frame/utility/src/lib.rs +++ b/frame/utility/src/lib.rs @@ -113,8 +113,12 @@ pub mod pallet { BatchInterrupted { index: u32, error: DispatchError }, /// Batch of dispatches completed fully with no error. BatchCompleted, + /// Batch of dispatches completed but has errors. + BatchCompletedWithErrors, /// A single item within a Batch of dispatches has completed with no error. ItemCompleted, + /// A single item within a Batch of dispatches has completed with error. + ItemFailed { error: DispatchError }, /// A call was dispatched. DispatchedAs { result: DispatchResult }, } @@ -385,6 +389,76 @@ pub mod pallet { }); Ok(()) } + + /// Send a batch of dispatch calls. + /// Unlike `batch`, it allows errors and won't interrupt. + /// + /// May be called from any origin. + /// + /// - `calls`: The calls to be dispatched from the same origin. The number of call must not + /// exceed the constant: `batched_calls_limit` (available in constant metadata). + /// + /// If origin is root then call are dispatch without checking origin filter. (This includes + /// bypassing `frame_system::Config::BaseCallFilter`). + /// + /// # + /// - Complexity: O(C) where C is the number of calls to be batched. + /// # + #[pallet::weight({ + let dispatch_infos = calls.iter().map(|call| call.get_dispatch_info()).collect::>(); + let dispatch_weight = dispatch_infos.iter() + .map(|di| di.weight) + .fold(0, |total: Weight, weight: Weight| total.saturating_add(weight)) + .saturating_add(T::WeightInfo::force_batch(calls.len() as u32)); + let dispatch_class = { + let all_operational = dispatch_infos.iter() + .map(|di| di.class) + .all(|class| class == DispatchClass::Operational); + if all_operational { + DispatchClass::Operational + } else { + DispatchClass::Normal + } + }; + (dispatch_weight, dispatch_class) + })] + pub fn force_batch( + origin: OriginFor, + calls: Vec<::Call>, + ) -> DispatchResultWithPostInfo { + let is_root = ensure_root(origin.clone()).is_ok(); + let calls_len = calls.len(); + ensure!(calls_len <= Self::batched_calls_limit() as usize, Error::::TooManyCalls); + + // Track the actual weight of each of the batch calls. + let mut weight: Weight = 0; + // Track failed dispatch occur. + let mut has_error: bool = false; + for call in calls.into_iter() { + let info = call.get_dispatch_info(); + // If origin is root, don't apply any dispatch filters; root can call anything. + let result = if is_root { + call.dispatch_bypass_filter(origin.clone()) + } else { + call.dispatch(origin.clone()) + }; + // Add the weight of this call. + weight = weight.saturating_add(extract_actual_weight(&result, &info)); + if let Err(e) = result { + has_error = true; + Self::deposit_event(Event::ItemFailed { error: e.error }); + } else { + Self::deposit_event(Event::ItemCompleted); + } + } + if has_error { + Self::deposit_event(Event::BatchCompletedWithErrors); + } else { + Self::deposit_event(Event::BatchCompleted); + } + let base_weight = T::WeightInfo::batch(calls_len as u32); + Ok(Some(base_weight + weight).into()) + } } } diff --git a/frame/utility/src/tests.rs b/frame/utility/src/tests.rs index 44b07f70db14c..f53459a707b54 100644 --- a/frame/utility/src/tests.rs +++ b/frame/utility/src/tests.rs @@ -606,3 +606,35 @@ fn batch_limit() { assert_noop!(Utility::batch_all(Origin::signed(1), calls), Error::::TooManyCalls); }); } + +#[test] +fn force_batch_works() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(1), 10); + assert_eq!(Balances::free_balance(2), 10); + assert_ok!(Utility::force_batch( + Origin::signed(1), + vec![ + call_transfer(2, 5), + call_foobar(true, 75, None), + call_transfer(2, 10), + call_transfer(2, 5), + ] + ),); + System::assert_last_event(utility::Event::BatchCompletedWithErrors.into()); + System::assert_has_event( + utility::Event::ItemFailed { error: DispatchError::Other("") }.into(), + ); + assert_eq!(Balances::free_balance(1), 0); + assert_eq!(Balances::free_balance(2), 20); + + assert_ok!(Utility::force_batch( + Origin::signed(2), + vec![call_transfer(1, 5), call_transfer(1, 5),] + ),); + System::assert_last_event(utility::Event::BatchCompleted.into()); + + assert_ok!(Utility::force_batch(Origin::signed(1), vec![call_transfer(2, 50),]),); + System::assert_last_event(utility::Event::BatchCompletedWithErrors.into()); + }); +} diff --git a/frame/utility/src/weights.rs b/frame/utility/src/weights.rs index e5f3cb0f58fd4..34dad8d735a2e 100644 --- a/frame/utility/src/weights.rs +++ b/frame/utility/src/weights.rs @@ -50,6 +50,7 @@ pub trait WeightInfo { fn as_derivative() -> Weight; fn batch_all(c: u32, ) -> Weight; fn dispatch_as() -> Weight; + fn force_batch(c: u32, ) -> Weight; } /// Weights for pallet_utility using the Substrate node and recommended hardware. @@ -71,6 +72,11 @@ impl WeightInfo for SubstrateWeight { fn dispatch_as() -> Weight { (8_463_000 as Weight) } + fn force_batch(c: u32, ) -> Weight { + (13_988_000 as Weight) + // Standard Error: 1_000 + .saturating_add((2_481_000 as Weight).saturating_mul(c as Weight)) + } } // For backwards compatibility and tests @@ -91,4 +97,9 @@ impl WeightInfo for () { fn dispatch_as() -> Weight { (8_463_000 as Weight) } + fn force_batch(c: u32, ) -> Weight { + (13_988_000 as Weight) + // Standard Error: 1_000 + .saturating_add((2_481_000 as Weight).saturating_mul(c as Weight)) + } } From 32fee98d1293b5723effbfa4d7db55a313882aee Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Thu, 5 May 2022 12:43:52 +0100 Subject: [PATCH 191/484] Backfill missing TOML features (#11351) * only check std * add default-features=false * Revert "add default-features=false" This reverts commit bc9453757a1df670f418a2d57ee4ce203425ab1b. * missing features only to non-optional --- frame/beefy-mmr/Cargo.toml | 1 + frame/benchmarking/Cargo.toml | 3 +++ frame/election-provider-support/Cargo.toml | 2 ++ frame/election-provider-support/benchmarking/Cargo.toml | 1 + frame/executive/Cargo.toml | 1 + frame/grandpa/Cargo.toml | 1 + frame/remark/Cargo.toml | 1 + frame/session/benchmarking/Cargo.toml | 1 + frame/transaction-storage/Cargo.toml | 1 + frame/uniques/Cargo.toml | 1 + frame/utility/Cargo.toml | 1 + frame/vesting/Cargo.toml | 1 + primitives/core/hashing/Cargo.toml | 1 + primitives/keystore/Cargo.toml | 6 +++++- primitives/test-primitives/Cargo.toml | 4 ++++ 15 files changed, 25 insertions(+), 1 deletion(-) diff --git a/frame/beefy-mmr/Cargo.toml b/frame/beefy-mmr/Cargo.toml index bdec17b8589c2..6affcd60ccb34 100644 --- a/frame/beefy-mmr/Cargo.toml +++ b/frame/beefy-mmr/Cargo.toml @@ -43,6 +43,7 @@ std = [ "pallet-beefy/std", "pallet-mmr/std", "pallet-session/std", + "scale-info/std", "serde", "sp-core/std", "sp-io/std", diff --git a/frame/benchmarking/Cargo.toml b/frame/benchmarking/Cargo.toml index d1d835e0d1d35..c070e64d37b45 100644 --- a/frame/benchmarking/Cargo.toml +++ b/frame/benchmarking/Cargo.toml @@ -44,8 +44,11 @@ std = [ "scale-info/std", "serde", "sp-api/std", + "sp-application-crypto/std", + "sp-io/std", "sp-runtime-interface/std", "sp-runtime/std", "sp-std/std", + "sp-storage/std", ] runtime-benchmarks = [] diff --git a/frame/election-provider-support/Cargo.toml b/frame/election-provider-support/Cargo.toml index 8bc92f878c8ce..bfddd25a1fa3e 100644 --- a/frame/election-provider-support/Cargo.toml +++ b/frame/election-provider-support/Cargo.toml @@ -35,8 +35,10 @@ std = [ "codec/std", "frame-support/std", "frame-system/std", + "scale-info/std", "sp-arithmetic/std", "sp-npos-elections/std", + "sp-runtime/std", "sp-std/std", ] runtime-benchmarks = [] diff --git a/frame/election-provider-support/benchmarking/Cargo.toml b/frame/election-provider-support/benchmarking/Cargo.toml index 14afff1aeb055..00037d460db17 100644 --- a/frame/election-provider-support/benchmarking/Cargo.toml +++ b/frame/election-provider-support/benchmarking/Cargo.toml @@ -26,6 +26,7 @@ default = ["std"] std = [ "codec/std", "frame-benchmarking/std", + "frame-election-provider-support/std", "frame-system/std", "sp-npos-elections/std", "sp-runtime/std", diff --git a/frame/executive/Cargo.toml b/frame/executive/Cargo.toml index b60cffa478c7b..ed3c5282fc81d 100644 --- a/frame/executive/Cargo.toml +++ b/frame/executive/Cargo.toml @@ -43,6 +43,7 @@ std = [ "frame-system/std", "scale-info/std", "sp-core/std", + "sp-io/std", "sp-runtime/std", "sp-std/std", "sp-tracing/std", diff --git a/frame/grandpa/Cargo.toml b/frame/grandpa/Cargo.toml index b1c2da09b53ff..ea1ec173d8f44 100644 --- a/frame/grandpa/Cargo.toml +++ b/frame/grandpa/Cargo.toml @@ -55,6 +55,7 @@ std = [ "sp-application-crypto/std", "sp-core/std", "sp-finality-grandpa/std", + "sp-io/std", "sp-runtime/std", "sp-session/std", "sp-staking/std", diff --git a/frame/remark/Cargo.toml b/frame/remark/Cargo.toml index 9759c37bfe92c..573502779c586 100644 --- a/frame/remark/Cargo.toml +++ b/frame/remark/Cargo.toml @@ -36,6 +36,7 @@ std = [ "frame-system/std", "scale-info/std", "serde", + "sp-core/std", "sp-io/std", "sp-runtime/std", "sp-std/std", diff --git a/frame/session/benchmarking/Cargo.toml b/frame/session/benchmarking/Cargo.toml index 0449f3f674379..1cce8fec023db 100644 --- a/frame/session/benchmarking/Cargo.toml +++ b/frame/session/benchmarking/Cargo.toml @@ -41,6 +41,7 @@ std = [ "frame-system/std", "pallet-session/std", "pallet-staking/std", + "rand/std", "sp-runtime/std", "sp-session/std", "sp-std/std", diff --git a/frame/transaction-storage/Cargo.toml b/frame/transaction-storage/Cargo.toml index 47cf69cf73f8b..99bea1cd36882 100644 --- a/frame/transaction-storage/Cargo.toml +++ b/frame/transaction-storage/Cargo.toml @@ -45,4 +45,5 @@ std = [ "sp-io/std", "sp-runtime/std", "sp-std/std", + "sp-transaction-storage-proof/std", ] diff --git a/frame/uniques/Cargo.toml b/frame/uniques/Cargo.toml index 3f78df96b3517..1e6ca2ce687dc 100644 --- a/frame/uniques/Cargo.toml +++ b/frame/uniques/Cargo.toml @@ -35,6 +35,7 @@ std = [ "frame-benchmarking/std", "frame-support/std", "frame-system/std", + "log/std", "scale-info/std", "sp-runtime/std", "sp-std/std", diff --git a/frame/utility/Cargo.toml b/frame/utility/Cargo.toml index 0c307020b521b..066682e0e349e 100644 --- a/frame/utility/Cargo.toml +++ b/frame/utility/Cargo.toml @@ -34,6 +34,7 @@ std = [ "frame-support/std", "frame-system/std", "scale-info/std", + "sp-core/std", "sp-io/std", "sp-runtime/std", "sp-std/std", diff --git a/frame/vesting/Cargo.toml b/frame/vesting/Cargo.toml index d8cb200caf062..f73a7af9e6ae4 100644 --- a/frame/vesting/Cargo.toml +++ b/frame/vesting/Cargo.toml @@ -35,6 +35,7 @@ std = [ "codec/std", "frame-support/std", "frame-system/std", + "log/std", "scale-info/std", "sp-runtime/std", "sp-std/std", diff --git a/primitives/core/hashing/Cargo.toml b/primitives/core/hashing/Cargo.toml index b3ce080f7a704..c83e48e563010 100644 --- a/primitives/core/hashing/Cargo.toml +++ b/primitives/core/hashing/Cargo.toml @@ -25,6 +25,7 @@ sp-std = { version = "4.0.0", default-features = false, path = "../../std" } default = ["std"] std = [ "blake2/std", + "byteorder/std", "sha2/std", "sha3/std", "sp-std/std", diff --git a/primitives/keystore/Cargo.toml b/primitives/keystore/Cargo.toml index 551626adbc4e8..3c3b7933c50da 100644 --- a/primitives/keystore/Cargo.toml +++ b/primitives/keystore/Cargo.toml @@ -21,7 +21,7 @@ parking_lot = { version = "0.12.0", default-features = false } schnorrkel = { version = "0.9.1", default-features = false, features = ["preaudit_deprecated", "u64_backend"] } serde = { version = "1.0", optional = true } thiserror = "1.0" -sp-core = { version = "6.0.0", path = "../core" } +sp-core = { version = "6.0.0", default-features = false, path = "../core" } sp-externalities = { version = "0.12.0", default-features = false, path = "../externalities" } [dev-dependencies] @@ -31,6 +31,10 @@ rand_chacha = "0.2.2" [features] default = ["std"] std = [ + "codec/std", + "merlin/std", "schnorrkel/std", "serde", + "sp-core/std", + "sp-externalities/std", ] diff --git a/primitives/test-primitives/Cargo.toml b/primitives/test-primitives/Cargo.toml index 1333c340a68a7..2a20addf66b2b 100644 --- a/primitives/test-primitives/Cargo.toml +++ b/primitives/test-primitives/Cargo.toml @@ -24,6 +24,10 @@ default = [ "std", ] std = [ + "codec/std", + "parity-util-mem/std", "serde", "sp-application-crypto/std", + "sp-core/std", + "sp-runtime/std", ] From 377f5fa1bbf5ae21120208f96db389ca0b7367bd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 May 2022 01:21:56 +0200 Subject: [PATCH 192/484] Bump k256 from 0.10.2 to 0.10.4 (#11249) Bumps [k256](https://github.com/RustCrypto/elliptic-curves) from 0.10.2 to 0.10.4. - [Release notes](https://github.com/RustCrypto/elliptic-curves/releases) - [Commits](https://github.com/RustCrypto/elliptic-curves/compare/k256/v0.10.2...k256/v0.10.4) --- updated-dependencies: - dependency-name: k256 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- frame/support/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1aaf0523765de..67a9372b367ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3534,9 +3534,9 @@ dependencies = [ [[package]] name = "k256" -version = "0.10.2" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cc5937366afd3b38071f400d1ce5bd8b1d40b5083cc14e6f8dbcc4032a7f5bb" +checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" dependencies = [ "cfg-if 1.0.0", "ecdsa", diff --git a/frame/support/Cargo.toml b/frame/support/Cargo.toml index f0d663f7a26fa..712570696b9eb 100644 --- a/frame/support/Cargo.toml +++ b/frame/support/Cargo.toml @@ -35,7 +35,7 @@ impl-trait-for-tuples = "0.2.2" smallvec = "1.8.0" log = { version = "0.4.16", default-features = false } sp-core-hashing-proc-macro = { version = "5.0.0", path = "../../primitives/core/hashing/proc-macro" } -k256 = { version = "0.10.2", default-features = false, features = ["ecdsa"] } +k256 = { version = "0.10.4", default-features = false, features = ["ecdsa"] } [dev-dependencies] assert_matches = "1.3.0" From a64f0d143c052110ad0280a7543e4f444b48af79 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Fri, 6 May 2022 10:44:21 +0100 Subject: [PATCH 193/484] Add event tests to Nomination Pools (#11349) * fix a few things with nomination pools * fix typo * fix build * eventify tests * Update frame/nomination-pools/src/tests.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * fmt * comments * split Co-authored-by: kianenigma Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- frame/nomination-pools/src/tests.rs | 290 ++++++++++++++++++++++++++-- 1 file changed, 272 insertions(+), 18 deletions(-) diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index cd66c3d774960..ecda162c6bdbc 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -407,7 +407,17 @@ mod join { // When assert_ok!(Pools::join(Origin::signed(11), 2, 1)); - // then + // Then + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 11, pool_id: 1, bonded: 2, joined: true }, + ] + ); + assert_eq!( PoolMembers::::get(&11).unwrap(), PoolMember:: { pool_id: 1, points: 2, ..Default::default() } @@ -426,6 +436,11 @@ mod join { assert_ok!(Pools::join(Origin::signed(12), 12, 1)); // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::Bonded { member: 12, pool_id: 1, bonded: 12, joined: true }] + ); + assert_eq!( PoolMembers::::get(&12).unwrap(), PoolMember:: { pool_id: 1, points: 24, ..Default::default() } @@ -536,6 +551,16 @@ mod join { Balances::make_free_balance_be(&103, 100 + Balances::minimum_balance()); // Then + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 101, pool_id: 1, bonded: 100, joined: true }, + Event::Bonded { member: 102, pool_id: 1, bonded: 100, joined: true } + ] + ); + assert_noop!( Pools::join(Origin::signed(103), 100, 1), Error::::MaxPoolMembers @@ -554,6 +579,14 @@ mod join { .unwrap(); // Then + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 104, pool_id: 2 }, + Event::Bonded { member: 104, pool_id: 2, bonded: 100, joined: true } + ] + ); + assert_noop!( Pools::join(Origin::signed(103), 100, pool_account), Error::::MaxPoolMembers @@ -595,6 +628,17 @@ mod claim_payout { assert_ok!(Pools::claim_payout(Origin::signed(10))); // Then + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true }, + Event::Bonded { member: 50, pool_id: 1, bonded: 50, joined: true }, + Event::PaidOut { member: 10, pool_id: 1, payout: 10 }, + ] + ); + // Expect a payout of 10: (10 del virtual points / 100 pool points) * 100 pool // balance assert_eq!(PoolMembers::::get(10).unwrap(), del(10, 100)); @@ -609,6 +653,11 @@ mod claim_payout { assert_ok!(Pools::claim_payout(Origin::signed(40))); // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 40, pool_id: 1, payout: 40 }] + ); + // Expect payout 40: (400 del virtual points / 900 pool points) * 90 pool balance assert_eq!(PoolMembers::::get(40).unwrap(), del(40, 100)); assert_eq!( @@ -622,6 +671,11 @@ mod claim_payout { assert_ok!(Pools::claim_payout(Origin::signed(50))); // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 50, pool_id: 1, payout: 50 }] + ); + // Expect payout 50: (50 del virtual points / 50 pool points) * 50 pool balance assert_eq!(PoolMembers::::get(50).unwrap(), del(50, 100)); assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 100)); @@ -635,6 +689,11 @@ mod claim_payout { assert_ok!(Pools::claim_payout(Origin::signed(10))); // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 5 }] + ); + // Expect payout 5: (500 del virtual points / 5,000 pool points) * 50 pool balance assert_eq!(PoolMembers::::get(10).unwrap(), del(10, 150)); assert_eq!(RewardPools::::get(&1).unwrap(), rew(45, 5_000 - 50 * 10, 150)); @@ -645,6 +704,11 @@ mod claim_payout { assert_ok!(Pools::claim_payout(Origin::signed(40))); // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 40, pool_id: 1, payout: 20 }] + ); + // Expect payout 20: (2,000 del virtual points / 4,500 pool points) * 45 pool // balance assert_eq!(PoolMembers::::get(40).unwrap(), del(40, 150)); @@ -660,6 +724,11 @@ mod claim_payout { assert_ok!(Pools::claim_payout(Origin::signed(50))); // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 50, pool_id: 1, payout: 50 }] + ); + // We expect a payout of 50: (5,000 del virtual points / 7,5000 pool points) * 75 // pool balance assert_eq!(PoolMembers::::get(50).unwrap(), del(50, 200)); @@ -682,6 +751,11 @@ mod claim_payout { assert_ok!(Pools::claim_payout(Origin::signed(10))); // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 5 }] + ); + // We expect a payout of 5 assert_eq!(PoolMembers::::get(10).unwrap(), del(10, 200)); assert_eq!(RewardPools::::get(&1).unwrap(), rew(20, 2_500 - 10 * 50, 200)); @@ -696,6 +770,11 @@ mod claim_payout { assert_ok!(Pools::claim_payout(Origin::signed(10))); // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 40 }] + ); + // We expect a payout of 40 assert_eq!(PoolMembers::::get(10).unwrap(), del(10, 600)); assert_eq!( @@ -721,6 +800,11 @@ mod claim_payout { assert_ok!(Pools::claim_payout(Origin::signed(10))); // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 2 }] + ); + // Expect a payout of 2: (200 del virtual points / 38,000 pool points) * 400 pool // balance assert_eq!(PoolMembers::::get(10).unwrap(), del(10, 620)); @@ -735,6 +819,11 @@ mod claim_payout { assert_ok!(Pools::claim_payout(Origin::signed(40))); // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 40, pool_id: 1, payout: 188 }] + ); + // Expect a payout of 188: (18,800 del virtual points / 39,800 pool points) * 399 // pool balance assert_eq!(PoolMembers::::get(40).unwrap(), del(40, 620)); @@ -749,6 +838,11 @@ mod claim_payout { assert_ok!(Pools::claim_payout(Origin::signed(50))); // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 50, pool_id: 1, payout: 210 }] + ); + // Expect payout of 210: (21,000 / 21,000) * 210 assert_eq!(PoolMembers::::get(50).unwrap(), del(50, 620)); assert_eq!( @@ -830,6 +924,16 @@ mod claim_payout { Pools::do_reward_payout(&11, &mut member, &mut bonded_pool, &mut reward_pool,), Error::::FullyUnbonding ); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 11, pool_id: 1, bonded: 11, joined: true }, + Event::Unbonded { member: 11, pool_id: 1, amount: 11 } + ] + ); }); } @@ -910,6 +1014,16 @@ mod claim_payout { // PoolMember with 50 points let mut del_50 = PoolMembers::::get(50).unwrap(); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true }, + Event::Bonded { member: 50, pool_id: 1, bonded: 50, joined: true } + ] + ); + // Given we have a total of 100 points split among the members assert_eq!(del_50.points + del_40.points + del_10.points, 100); assert_eq!(bonded_pool.points, 100); @@ -1101,6 +1215,16 @@ mod claim_payout { let mut reward_pool = RewardPools::::get(1).unwrap(); let ed = Balances::minimum_balance(); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true }, + Event::Bonded { member: 50, pool_id: 1, bonded: 50, joined: true } + ] + ); + // Given the bonded pool has 100 points assert_eq!(bonded_pool.points, 100); // Each member currently has a free balance of @@ -1123,6 +1247,11 @@ mod claim_payout { )); // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 10 }] + ); + // Expect a payout of 10: (10 del virtual points / 100 pool points) * 100 pool // balance assert_eq!(del_10, del(10, 100)); @@ -1139,6 +1268,11 @@ mod claim_payout { )); // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 40, pool_id: 1, payout: 40 }] + ); + // Expect payout 40: (400 del virtual points / 900 pool points) * 90 pool balance assert_eq!(del_40, del(40, 100)); assert_eq!(reward_pool, rew(50, 9_000 - 100 * 40, 100)); @@ -1154,6 +1288,11 @@ mod claim_payout { )); // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 50, pool_id: 1, payout: 50 }] + ); + // Expect payout 50: (50 del virtual points / 50 pool points) * 50 pool balance assert_eq!(del_50, del(50, 100)); assert_eq!(reward_pool, rew(0, 0, 100)); @@ -1172,6 +1311,11 @@ mod claim_payout { )); // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 5 }] + ); + // Expect payout 5: (500 del virtual points / 5,000 pool points) * 50 pool balance assert_eq!(del_10, del(10, 150)); assert_eq!(reward_pool, rew(45, 5_000 - 50 * 10, 150)); @@ -1187,6 +1331,11 @@ mod claim_payout { )); // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 40, pool_id: 1, payout: 20 }] + ); + // Expect payout 20: (2,000 del virtual points / 4,500 pool points) * 45 pool // balance assert_eq!(del_40, del(40, 150)); @@ -1207,6 +1356,11 @@ mod claim_payout { )); // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 50, pool_id: 1, payout: 50 }] + ); + // We expect a payout of 50: (5,000 del virtual points / 7,5000 pool points) * 75 // pool balance assert_eq!(del_50, del(50, 200)); @@ -1234,6 +1388,11 @@ mod claim_payout { )); // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 5 }] + ); + // We expect a payout of 5 assert_eq!(del_10, del(10, 200)); assert_eq!(reward_pool, rew(20, 2_500 - 10 * 50, 200)); @@ -1253,6 +1412,11 @@ mod claim_payout { )); // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 40 }] + ); + // We expect a payout of 40 assert_eq!(del_10, del(10, 600)); assert_eq!( @@ -1283,6 +1447,11 @@ mod claim_payout { )); // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 2 }] + ); + // Expect a payout of 2: (200 del virtual points / 38,000 pool points) * 400 pool // balance assert_eq!(del_10, del(10, 620)); @@ -1299,6 +1468,11 @@ mod claim_payout { )); // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 40, pool_id: 1, payout: 188 }] + ); + // Expect a payout of 188: (18,800 del virtual points / 39,800 pool points) * 399 // pool balance assert_eq!(del_40, del(40, 620)); @@ -1315,6 +1489,11 @@ mod claim_payout { )); // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 50, pool_id: 1, payout: 210 }] + ); + // Expect payout of 210: (21,000 / 21,000) * 210 assert_eq!(del_50, del(50, 620)); assert_eq!(reward_pool, rew(0, 21_000 - 50 * 420, 620)); @@ -2372,13 +2551,35 @@ mod withdraw_unbonded { // Given assert_ok!(Pools::fully_unbond(Origin::signed(100), 100)); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, + Event::Bonded { member: 200, pool_id: 1, bonded: 200, joined: true }, + Event::Unbonded { member: 100, pool_id: 1, amount: 100 } + ] + ); + let mut current_era = 1; CurrentEra::set(current_era); assert_ok!(Pools::fully_unbond(Origin::signed(200), 200)); + + assert_eq!( + pool_events_since_last_call(), + vec![Event::Unbonded { member: 200, pool_id: 1, amount: 200 }] + ); + unsafe_set_state(1, PoolState::Destroying).unwrap(); assert_ok!(Pools::fully_unbond(Origin::signed(10), 10)); + assert_eq!( + pool_events_since_last_call(), + vec![Event::Unbonded { member: 10, pool_id: 1, amount: 10 }] + ); + assert_eq!( SubPoolsStorage::::get(1).unwrap(), SubPools { @@ -2402,6 +2603,15 @@ mod withdraw_unbonded { // Given assert_ok!(Pools::withdraw_unbonded(Origin::signed(420), 100, 0)); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Withdrawn { member: 100, pool_id: 1, amount: 100 }, + Event::MemberRemoved { pool_id: 1, member: 100 } + ] + ); + assert_eq!( SubPoolsStorage::::get(1).unwrap(), SubPools { @@ -2421,6 +2631,15 @@ mod withdraw_unbonded { // Given assert_ok!(Pools::withdraw_unbonded(Origin::signed(420), 200, 0)); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Withdrawn { member: 200, pool_id: 1, amount: 200 }, + Event::MemberRemoved { pool_id: 1, member: 200 } + ] + ); + assert_eq!( SubPoolsStorage::::get(1).unwrap(), SubPools { @@ -2433,32 +2652,22 @@ mod withdraw_unbonded { // The depositor can withdraw assert_ok!(Pools::withdraw_unbonded(Origin::signed(420), 10, 0)); - assert!(!PoolMembers::::contains_key(10)); - assert_eq!(Balances::free_balance(10), 10 + 10); - // Pools are removed from storage because the depositor left - assert!(!SubPoolsStorage::::contains_key(1)); - assert!(!RewardPools::::contains_key(1)); - assert!(!BondedPools::::contains_key(1)); assert_eq!( pool_events_since_last_call(), vec![ - Event::Created { depositor: 10, pool_id: 1 }, - Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, - Event::Bonded { member: 200, pool_id: 1, bonded: 200, joined: true }, - Event::Unbonded { member: 100, pool_id: 1, amount: 100 }, - Event::Unbonded { member: 200, pool_id: 1, amount: 200 }, - Event::Unbonded { member: 10, pool_id: 1, amount: 10 }, - Event::Withdrawn { member: 100, pool_id: 1, amount: 100 }, - Event::MemberRemoved { pool_id: 1, member: 100 }, - Event::Withdrawn { member: 200, pool_id: 1, amount: 200 }, - Event::MemberRemoved { pool_id: 1, member: 200 }, Event::Withdrawn { member: 10, pool_id: 1, amount: 10 }, Event::MemberRemoved { pool_id: 1, member: 10 }, Event::Destroyed { pool_id: 1 } ] ); + + assert!(!PoolMembers::::contains_key(10)); + assert_eq!(Balances::free_balance(10), 10 + 10); + // Pools are removed from storage because the depositor left + assert!(!SubPoolsStorage::::contains_key(1)); + assert!(!RewardPools::::contains_key(1)); + assert!(!BondedPools::::contains_key(1)); }); } @@ -2510,6 +2719,22 @@ mod withdraw_unbonded { assert!(!SubPoolsStorage::::contains_key(1)); assert!(!RewardPools::::contains_key(1)); assert!(!BondedPools::::contains_key(1)); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, + Event::Unbonded { member: 100, pool_id: 1, amount: 100 }, + Event::Unbonded { member: 10, pool_id: 1, amount: 10 }, + Event::Withdrawn { member: 100, pool_id: 1, amount: 100 }, + Event::MemberRemoved { pool_id: 1, member: 100 }, + Event::Withdrawn { member: 10, pool_id: 1, amount: 10 }, + Event::MemberRemoved { pool_id: 1, member: 10 }, + Event::Destroyed { pool_id: 1 } + ] + ); }); } @@ -2860,6 +3085,16 @@ mod create { total_earnings: Zero::zero(), } ); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Created { depositor: 11, pool_id: 2 }, + Event::Bonded { member: 11, pool_id: 2, bonded: 10, joined: true } + ] + ); }); } @@ -2981,6 +3216,16 @@ mod set_state { // Root can change state assert_ok!(Pools::set_state(Origin::signed(900), 1, PoolState::Blocked)); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::StateChanged { pool_id: 1, new_state: PoolState::Blocked } + ] + ); + assert_eq!(BondedPool::::get(1).unwrap().state, PoolState::Blocked); // State toggler can change state @@ -3024,6 +3269,15 @@ mod set_state { Pools::set_state(Origin::signed(11), 1, PoolState::Blocked), Error::::CanNotChangeState ); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::StateChanged { pool_id: 1, new_state: PoolState::Destroying }, + Event::StateChanged { pool_id: 1, new_state: PoolState::Destroying }, + Event::StateChanged { pool_id: 1, new_state: PoolState::Destroying } + ] + ); }); } } From 71fd5172473be1c4d9740b473de2d05d81b5e16b Mon Sep 17 00:00:00 2001 From: Roman Gafiyatullin Date: Fri, 6 May 2022 13:07:44 +0300 Subject: [PATCH 194/484] Remove the `--unsafe-pruning` CLI-argument (step 1) (#10995) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * sc-client-db: utils::open_database(...) — return OpenDbError so that the caller could tell the `OpenDbError::DoesNotExist` clearly * sc-client-db: utils::open_database(..) — accept the `create: bool` argument * sc-client-db: pruning — optional argument in the DatabaseSettings * sc-state-db: Split `Error` into separate `Error` and `StateDbError` * StateDb::open: choose the pruning-mode depending on the requested and stored values * sc-state-db: test for different combinations of stored and requested pruning-modes * CLI-argument: mark the unsafe-pruning as deprecated * Fix tests * tests: do not specify --pruning when running the substrate over the existing storage * fix types for benches * cargo fmt * Check whether the pruning-mode and sync-mode are compatible * cargo fmt * parity-db: 0.3.11 -> 0.3.12 * sc-state-db: MetaDb::set_meta — a better doc-test * cargo fmt * make MetaDb read-only again! * Remove the stray newline (and run the CI once again please) * Last nitpicks * A more comprehensive error message --- Cargo.lock | 6 +- bin/node/cli/benches/block_production.rs | 2 +- bin/node/cli/benches/transaction_pool.rs | 2 +- bin/node/cli/tests/benchmark_block_works.rs | 1 - bin/node/cli/tests/check_block_works.rs | 2 +- bin/node/cli/tests/export_import_flow.rs | 6 +- bin/node/cli/tests/inspect_works.rs | 2 +- bin/node/testing/src/bench.rs | 2 +- client/api/src/backend.rs | 3 + client/api/src/client.rs | 3 + client/api/src/in_mem.rs | 4 + client/cli/src/config.rs | 19 +- client/cli/src/params/import_params.rs | 12 +- client/cli/src/params/pruning_params.rs | 36 +-- client/db/src/lib.rs | 81 ++++-- client/db/src/upgrade.rs | 13 +- client/db/src/utils.rs | 168 ++++++------ client/service/src/builder.rs | 6 +- client/service/src/client/client.rs | 4 + client/service/src/config.rs | 2 +- client/service/test/src/client/mod.rs | 4 +- client/state-db/src/lib.rs | 280 ++++++++++++++++---- client/state-db/src/noncanonical.rs | 226 +++++++--------- 23 files changed, 546 insertions(+), 338 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 67a9372b367ea..14d1a547a8073 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11680,7 +11680,7 @@ dependencies = [ "chrono", "lazy_static", "matchers", - "parking_lot 0.11.2", + "parking_lot 0.9.0", "regex", "serde", "serde_json", @@ -11854,9 +11854,9 @@ version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ee73e6e4924fe940354b8d4d98cad5231175d615cd855b758adc658c0aac6a0" dependencies = [ - "cfg-if 1.0.0", + "cfg-if 0.1.10", "digest 0.10.3", - "rand 0.8.4", + "rand 0.6.5", "static_assertions", ] diff --git a/bin/node/cli/benches/block_production.rs b/bin/node/cli/benches/block_production.rs index ebb89c07da221..6eab08c39e5a2 100644 --- a/bin/node/cli/benches/block_production.rs +++ b/bin/node/cli/benches/block_production.rs @@ -74,7 +74,7 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase { database: DatabaseSource::RocksDb { path: root.join("db"), cache_size: 128 }, state_cache_size: 67108864, state_cache_child_ratio: None, - state_pruning: PruningMode::ArchiveAll, + state_pruning: Some(PruningMode::ArchiveAll), keep_blocks: KeepBlocks::All, chain_spec: spec, wasm_method: WasmExecutionMethod::Compiled, diff --git a/bin/node/cli/benches/transaction_pool.rs b/bin/node/cli/benches/transaction_pool.rs index a889399eda83a..eb0e24d2fdd37 100644 --- a/bin/node/cli/benches/transaction_pool.rs +++ b/bin/node/cli/benches/transaction_pool.rs @@ -65,7 +65,7 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase { database: DatabaseSource::RocksDb { path: root.join("db"), cache_size: 128 }, state_cache_size: 67108864, state_cache_child_ratio: None, - state_pruning: PruningMode::ArchiveAll, + state_pruning: Some(PruningMode::ArchiveAll), keep_blocks: KeepBlocks::All, chain_spec: spec, wasm_method: WasmExecutionMethod::Interpreted, diff --git a/bin/node/cli/tests/benchmark_block_works.rs b/bin/node/cli/tests/benchmark_block_works.rs index 359abf3e4265f..65cb474ea88d6 100644 --- a/bin/node/cli/tests/benchmark_block_works.rs +++ b/bin/node/cli/tests/benchmark_block_works.rs @@ -37,7 +37,6 @@ async fn benchmark_block_works() { .args(["benchmark", "block", "--dev"]) .arg("-d") .arg(base_dir.path()) - .args(["--pruning", "archive"]) .args(["--from", "1", "--to", "1"]) .args(["--repeat", "1"]) .args(["--execution", "wasm", "--wasm-execution", "compiled"]) diff --git a/bin/node/cli/tests/check_block_works.rs b/bin/node/cli/tests/check_block_works.rs index c5447fd2311c6..d4afc530bbcb3 100644 --- a/bin/node/cli/tests/check_block_works.rs +++ b/bin/node/cli/tests/check_block_works.rs @@ -31,7 +31,7 @@ async fn check_block_works() { common::run_node_for_a_while(base_path.path(), &["--dev", "--no-hardware-benchmarks"]).await; let status = Command::new(cargo_bin("substrate")) - .args(&["check-block", "--dev", "--pruning", "archive", "-d"]) + .args(&["check-block", "--dev", "-d"]) .arg(base_path.path()) .arg("1") .status() diff --git a/bin/node/cli/tests/export_import_flow.rs b/bin/node/cli/tests/export_import_flow.rs index 48fccc8ca0293..750b4f7acc121 100644 --- a/bin/node/cli/tests/export_import_flow.rs +++ b/bin/node/cli/tests/export_import_flow.rs @@ -79,9 +79,9 @@ impl<'a> ExportImportRevertExecutor<'a> { // Adding "--binary" if need be. let arguments: Vec<&str> = match format_opt { FormatOpt::Binary => { - vec![&sub_command_str, "--dev", "--pruning", "archive", "--binary", "-d"] + vec![&sub_command_str, "--dev", "--binary", "-d"] }, - FormatOpt::Json => vec![&sub_command_str, "--dev", "--pruning", "archive", "-d"], + FormatOpt::Json => vec![&sub_command_str, "--dev", "-d"], }; let tmp: TempDir; @@ -161,7 +161,7 @@ impl<'a> ExportImportRevertExecutor<'a> { /// Runs the `revert` command. fn run_revert(&self) { let output = Command::new(cargo_bin("substrate")) - .args(&["revert", "--dev", "--pruning", "archive", "-d"]) + .args(&["revert", "--dev", "-d"]) .arg(&self.base_path.path()) .output() .unwrap(); diff --git a/bin/node/cli/tests/inspect_works.rs b/bin/node/cli/tests/inspect_works.rs index 6f73cc69582a9..849fb913a18d0 100644 --- a/bin/node/cli/tests/inspect_works.rs +++ b/bin/node/cli/tests/inspect_works.rs @@ -31,7 +31,7 @@ async fn inspect_works() { common::run_node_for_a_while(base_path.path(), &["--dev", "--no-hardware-benchmarks"]).await; let status = Command::new(cargo_bin("substrate")) - .args(&["inspect", "--dev", "--pruning", "archive", "-d"]) + .args(&["inspect", "--dev", "-d"]) .arg(base_path.path()) .args(&["block", "1"]) .status() diff --git a/bin/node/testing/src/bench.rs b/bin/node/testing/src/bench.rs index 7e13c0a0ac5e0..e5287dc3c4af2 100644 --- a/bin/node/testing/src/bench.rs +++ b/bin/node/testing/src/bench.rs @@ -389,7 +389,7 @@ impl BenchDb { let db_config = sc_client_db::DatabaseSettings { state_cache_size: 16 * 1024 * 1024, state_cache_child_ratio: Some((0, 100)), - state_pruning: PruningMode::ArchiveAll, + state_pruning: Some(PruningMode::ArchiveAll), source: database_type.into_settings(dir.into()), keep_blocks: sc_client_db::KeepBlocks::All, }; diff --git a/client/api/src/backend.rs b/client/api/src/backend.rs index 394fcd420fda1..af8552886b72e 100644 --- a/client/api/src/backend.rs +++ b/client/api/src/backend.rs @@ -546,6 +546,9 @@ pub trait Backend: AuxStore + Send + Sync { /// something that the import of a block would interfere with, e.g. importing /// a new block or calculating the best head. fn get_import_lock(&self) -> &RwLock<()>; + + /// Tells whether the backend requires full-sync mode. + fn requires_full_sync(&self) -> bool; } /// Mark for all Backend implementations, that are making use of state data, stored locally. diff --git a/client/api/src/client.rs b/client/api/src/client.rs index c4b01fbd0abbd..b809e0ee61032 100644 --- a/client/api/src/client.rs +++ b/client/api/src/client.rs @@ -146,6 +146,9 @@ pub trait BlockBackend { fn has_indexed_transaction(&self, hash: &Block::Hash) -> sp_blockchain::Result { Ok(self.indexed_transaction(hash)?.is_some()) } + + /// Tells whether the current client configuration requires full-sync mode. + fn requires_full_sync(&self) -> bool; } /// Provide a list of potential uncle headers for a given block. diff --git a/client/api/src/in_mem.rs b/client/api/src/in_mem.rs index 8b8473287a7ca..a8a7442a8ef9f 100644 --- a/client/api/src/in_mem.rs +++ b/client/api/src/in_mem.rs @@ -795,6 +795,10 @@ where fn get_import_lock(&self) -> &RwLock<()> { &self.import_lock } + + fn requires_full_sync(&self) -> bool { + false + } } impl backend::LocalBackend for Backend where Block::Hash: Ord {} diff --git a/client/cli/src/config.rs b/client/cli/src/config.rs index 5c44a05ab68dd..aef1da8193757 100644 --- a/client/cli/src/config.rs +++ b/client/cli/src/config.rs @@ -251,9 +251,9 @@ pub trait CliConfiguration: Sized { /// /// By default this is retrieved from `PruningMode` if it is available. Otherwise its /// `PruningMode::default()`. - fn state_pruning(&self, unsafe_pruning: bool, role: &Role) -> Result { + fn state_pruning(&self) -> Result> { self.pruning_params() - .map(|x| x.state_pruning(unsafe_pruning, role)) + .map(|x| x.state_pruning()) .unwrap_or_else(|| Ok(Default::default())) } @@ -494,8 +494,6 @@ pub trait CliConfiguration: Sized { let telemetry_endpoints = self.telemetry_endpoints(&chain_spec)?; let runtime_cache_size = self.runtime_cache_size()?; - let unsafe_pruning = self.import_params().map(|p| p.unsafe_pruning).unwrap_or(false); - Ok(Configuration { impl_name: C::impl_name(), impl_version: C::impl_version(), @@ -516,7 +514,7 @@ pub trait CliConfiguration: Sized { database: self.database_config(&config_dir, database_cache_size, database, &role)?, state_cache_size: self.state_cache_size()?, state_cache_child_ratio: self.state_cache_child_ratio()?, - state_pruning: self.state_pruning(unsafe_pruning, &role)?, + state_pruning: self.state_pruning()?, keep_blocks: self.keep_blocks()?, wasm_method: self.wasm_method()?, wasm_runtime_overrides: self.wasm_runtime_overrides(), @@ -643,6 +641,17 @@ pub trait CliConfiguration: Sized { } } + if self.import_params().map_or(false, |p| { + #[allow(deprecated)] + p.unsafe_pruning + }) { + // according to https://github.com/substrate/issues/8103; + warn!( + "WARNING: \"--unsafe-pruning\" CLI-flag is deprecated and has no effect. \ + In future builds it will be removed, and providing this flag will lead to an error." + ); + } + Ok(()) } } diff --git a/client/cli/src/params/import_params.rs b/client/cli/src/params/import_params.rs index 1df11cff8d79f..4c9b334150557 100644 --- a/client/cli/src/params/import_params.rs +++ b/client/cli/src/params/import_params.rs @@ -40,12 +40,16 @@ pub struct ImportParams { #[clap(flatten)] pub database_params: DatabaseParams, - /// Force start with unsafe pruning settings. + /// THIS IS A DEPRECATED CLI-ARGUMENT. /// - /// When running as a validator it is highly recommended to disable state - /// pruning (i.e. 'archive') which is the default. The node will refuse to - /// start as a validator if pruning is enabled unless this option is set. + /// It has been preserved in order to not break the compatibility with the existing scripts. + /// Enabling this option will lead to a runtime warning. + /// In future this option will be removed completely, thus specifying it will lead to a start + /// up error. + /// + /// Details: #[clap(long)] + #[deprecated = "According to https://github.com/paritytech/substrate/issues/8103"] pub unsafe_pruning: bool, /// Method for executing Wasm runtime code. diff --git a/client/cli/src/params/pruning_params.rs b/client/cli/src/params/pruning_params.rs index de9628ecf7ad9..0f3d1013381e6 100644 --- a/client/cli/src/params/pruning_params.rs +++ b/client/cli/src/params/pruning_params.rs @@ -18,7 +18,7 @@ use crate::error; use clap::Args; -use sc_service::{KeepBlocks, PruningMode, Role}; +use sc_service::{KeepBlocks, PruningMode}; /// Parameters to define the pruning mode #[derive(Debug, Clone, PartialEq, Args)] @@ -39,29 +39,17 @@ pub struct PruningParams { impl PruningParams { /// Get the pruning value from the parameters - pub fn state_pruning(&self, unsafe_pruning: bool, role: &Role) -> error::Result { - // by default we disable pruning if the node is an authority (i.e. - // `ArchiveAll`), otherwise we keep state for the last 256 blocks. if the - // node is an authority and pruning is enabled explicitly, then we error - // unless `unsafe_pruning` is set. - Ok(match &self.pruning { - Some(ref s) if s == "archive" => PruningMode::ArchiveAll, - None if role.is_authority() => PruningMode::ArchiveAll, - None => PruningMode::default(), - Some(s) => { - if role.is_authority() && !unsafe_pruning { - return Err(error::Error::Input( - "Validators should run with state pruning disabled (i.e. archive). \ - You can ignore this check with `--unsafe-pruning`." - .to_string(), - )) - } - - PruningMode::keep_blocks(s.parse().map_err(|_| { - error::Error::Input("Invalid pruning mode specified".to_string()) - })?) - }, - }) + pub fn state_pruning(&self) -> error::Result> { + self.pruning + .as_ref() + .map(|s| match s.as_str() { + "archive" => Ok(PruningMode::ArchiveAll), + bc => bc + .parse() + .map_err(|_| error::Error::Input("Invalid pruning mode specified".to_string())) + .map(PruningMode::keep_blocks), + }) + .transpose() } /// Get the block pruning value from the parameters diff --git a/client/db/src/lib.rs b/client/db/src/lib.rs index 72422eb82d6dd..a32a666c3c980 100644 --- a/client/db/src/lib.rs +++ b/client/db/src/lib.rs @@ -296,8 +296,8 @@ pub struct DatabaseSettings { pub state_cache_size: usize, /// Ratio of cache size dedicated to child tries. pub state_cache_child_ratio: Option<(usize, usize)>, - /// State pruning mode. - pub state_pruning: PruningMode, + /// Requested state pruning mode. + pub state_pruning: Option, /// Where to find the database. pub source: DatabaseSource, /// Block pruning mode. @@ -341,7 +341,13 @@ pub enum DatabaseSource { }, /// Use a custom already-open database. - Custom(Arc>), + Custom { + /// the handle to the custom storage + db: Arc>, + + /// if set, the `create` flag will be required to open such datasource + require_create_flag: bool, + }, } impl DatabaseSource { @@ -354,7 +360,7 @@ impl DatabaseSource { // I would think rocksdb, but later parity-db. DatabaseSource::Auto { paritydb_path, .. } => Some(paritydb_path), DatabaseSource::RocksDb { path, .. } | DatabaseSource::ParityDb { path } => Some(path), - DatabaseSource::Custom(..) => None, + DatabaseSource::Custom { .. } => None, } } @@ -370,7 +376,7 @@ impl DatabaseSource { *path = p.into(); true }, - DatabaseSource::Custom(..) => false, + DatabaseSource::Custom { .. } => false, } } } @@ -381,7 +387,7 @@ impl std::fmt::Display for DatabaseSource { DatabaseSource::Auto { .. } => "Auto", DatabaseSource::RocksDb { .. } => "RocksDb", DatabaseSource::ParityDb { .. } => "ParityDb", - DatabaseSource::Custom(_) => "Custom", + DatabaseSource::Custom { .. } => "Custom", }; write!(f, "{}", name) } @@ -416,7 +422,7 @@ struct PendingBlock { struct StateMetaDb<'a>(&'a dyn Database); impl<'a> sc_state_db::MetaDb for StateMetaDb<'a> { - type Error = io::Error; + type Error = sp_database::error::DatabaseError; fn get_meta(&self, key: &[u8]) -> Result>, Self::Error> { Ok(self.0.get(columns::STATE_META, key)) @@ -1009,9 +1015,23 @@ impl Backend { /// Create a new instance of database backend. /// /// The pruning window is how old a block must be before the state is pruned. - pub fn new(config: DatabaseSettings, canonicalization_delay: u64) -> ClientResult { - let db = crate::utils::open_database::(&config, DatabaseType::Full)?; - Self::from_database(db as Arc<_>, canonicalization_delay, &config) + pub fn new(db_config: DatabaseSettings, canonicalization_delay: u64) -> ClientResult { + use utils::OpenDbError; + + let db_source = &db_config.source; + + let (needs_init, db) = + match crate::utils::open_database::(db_source, DatabaseType::Full, false) { + Ok(db) => (false, db), + Err(OpenDbError::DoesNotExist) => { + let db = + crate::utils::open_database::(db_source, DatabaseType::Full, true)?; + (true, db) + }, + Err(as_is) => return Err(as_is.into()), + }; + + Self::from_database(db as Arc<_>, canonicalization_delay, &db_config, needs_init) } /// Create new memory-backed client backend for tests. @@ -1028,8 +1048,8 @@ impl Backend { let db_setting = DatabaseSettings { state_cache_size: 16777216, state_cache_child_ratio: Some((50, 100)), - state_pruning: PruningMode::keep_blocks(keep_blocks), - source: DatabaseSource::Custom(db), + state_pruning: Some(PruningMode::keep_blocks(keep_blocks)), + source: DatabaseSource::Custom { db, require_create_flag: true }, keep_blocks: KeepBlocks::Some(keep_blocks), }; @@ -1057,18 +1077,31 @@ impl Backend { db: Arc>, canonicalization_delay: u64, config: &DatabaseSettings, + should_init: bool, ) -> ClientResult { - let is_archive_pruning = config.state_pruning.is_archive(); - let blockchain = BlockchainDb::new(db.clone())?; + let mut db_init_transaction = Transaction::new(); + + let requested_state_pruning = config.state_pruning.clone(); + let state_meta_db = StateMetaDb(db.as_ref()); let map_e = sp_blockchain::Error::from_state_db; - let state_db: StateDb<_, _> = StateDb::new( - config.state_pruning.clone(), + + let (state_db_init_commit_set, state_db) = StateDb::open( + &state_meta_db, + requested_state_pruning, !db.supports_ref_counting(), - &StateMetaDb(&*db), + should_init, ) .map_err(map_e)?; + + apply_state_commit(&mut db_init_transaction, state_db_init_commit_set); + + let state_pruning_used = state_db.pruning_mode(); + let is_archive_pruning = state_pruning_used.is_archive(); + let blockchain = BlockchainDb::new(db.clone())?; + let storage_db = StorageDb { db: db.clone(), state_db, prefix_keys: !db.supports_ref_counting() }; + let offchain_storage = offchain::LocalStorage::new(db.clone()); let backend = Backend { @@ -1105,6 +1138,9 @@ impl Backend { with_state: true, }); } + + db.commit(db_init_transaction)?; + Ok(backend) } @@ -2251,6 +2287,13 @@ impl sc_client_api::backend::Backend for Backend { fn get_import_lock(&self) -> &RwLock<()> { &*self.import_lock } + + fn requires_full_sync(&self) -> bool { + matches!( + self.storage.state_db.pruning_mode(), + PruningMode::ArchiveAll | PruningMode::ArchiveCanonical + ) + } } impl sc_client_api::backend::LocalBackend for Backend {} @@ -2390,8 +2433,8 @@ pub(crate) mod tests { DatabaseSettings { state_cache_size: 16777216, state_cache_child_ratio: Some((50, 100)), - state_pruning: PruningMode::keep_blocks(1), - source: DatabaseSource::Custom(backing), + state_pruning: Some(PruningMode::keep_blocks(1)), + source: DatabaseSource::Custom { db: backing, require_create_flag: false }, keep_blocks: KeepBlocks::All, }, 0, diff --git a/client/db/src/upgrade.rs b/client/db/src/upgrade.rs index cd18554fb0d06..292905663a20b 100644 --- a/client/db/src/upgrade.rs +++ b/client/db/src/upgrade.rs @@ -190,8 +190,7 @@ fn version_file_path(path: &Path) -> PathBuf { #[cfg(test)] mod tests { use super::*; - use crate::{tests::Block, DatabaseSettings, DatabaseSource, KeepBlocks}; - use sc_state_db::PruningMode; + use crate::{tests::Block, DatabaseSource}; fn create_db(db_path: &Path, version: Option) { if let Some(version) = version { @@ -203,16 +202,12 @@ mod tests { fn open_database(db_path: &Path, db_type: DatabaseType) -> sp_blockchain::Result<()> { crate::utils::open_database::( - &DatabaseSettings { - state_cache_size: 0, - state_cache_child_ratio: None, - state_pruning: PruningMode::ArchiveAll, - source: DatabaseSource::RocksDb { path: db_path.to_owned(), cache_size: 128 }, - keep_blocks: KeepBlocks::All, - }, + &DatabaseSource::RocksDb { path: db_path.to_owned(), cache_size: 128 }, db_type, + true, ) .map(|_| ()) + .map_err(|e| sp_blockchain::Error::Backend(e.to_string())) } #[test] diff --git a/client/db/src/utils.rs b/client/db/src/utils.rs index d3cb9a994fdd3..0227e4db8bcd0 100644 --- a/client/db/src/utils.rs +++ b/client/db/src/utils.rs @@ -23,7 +23,7 @@ use std::{fmt, fs, io, path::Path, sync::Arc}; use log::{debug, info}; -use crate::{Database, DatabaseSettings, DatabaseSource, DbHash}; +use crate::{Database, DatabaseSource, DbHash}; use codec::Decode; use sp_database::Transaction; use sp_runtime::{ @@ -177,41 +177,42 @@ where }) } -fn backend_err(feat: &'static str) -> sp_blockchain::Error { - sp_blockchain::Error::Backend(feat.to_string()) -} - /// Opens the configured database. pub fn open_database( - config: &DatabaseSettings, + db_source: &DatabaseSource, db_type: DatabaseType, -) -> sp_blockchain::Result>> { + create: bool, +) -> OpenDbResult { // Maybe migrate (copy) the database to a type specific subdirectory to make it // possible that light and full databases coexist // NOTE: This function can be removed in a few releases - maybe_migrate_to_type_subdir::(&config.source, db_type).map_err(|e| { - sp_blockchain::Error::Backend(format!("Error in migration to role subdirectory: {}", e)) - })?; + maybe_migrate_to_type_subdir::(db_source, db_type)?; - open_database_at::(&config.source, db_type) + open_database_at::(db_source, db_type, create) } fn open_database_at( - source: &DatabaseSource, + db_source: &DatabaseSource, db_type: DatabaseType, -) -> sp_blockchain::Result>> { - let db: Arc> = match &source { - DatabaseSource::ParityDb { path } => open_parity_db::(path, db_type, true)?, + create: bool, +) -> OpenDbResult { + let db: Arc> = match &db_source { + DatabaseSource::ParityDb { path } => open_parity_db::(path, db_type, create)?, DatabaseSource::RocksDb { path, cache_size } => - open_kvdb_rocksdb::(path, db_type, true, *cache_size)?, - DatabaseSource::Custom(db) => db.clone(), + open_kvdb_rocksdb::(path, db_type, create, *cache_size)?, + DatabaseSource::Custom { db, require_create_flag } => { + if *require_create_flag && !create { + return Err(OpenDbError::DoesNotExist) + } + db.clone() + }, DatabaseSource::Auto { paritydb_path, rocksdb_path, cache_size } => { // check if rocksdb exists first, if not, open paritydb match open_kvdb_rocksdb::(rocksdb_path, db_type, false, *cache_size) { Ok(db) => db, Err(OpenDbError::NotEnabled(_)) | Err(OpenDbError::DoesNotExist) => - open_parity_db::(paritydb_path, db_type, true)?, - Err(_) => return Err(backend_err("cannot open rocksdb. corrupted database")), + open_parity_db::(paritydb_path, db_type, create)?, + Err(as_is) => return Err(as_is), } }, }; @@ -221,12 +222,17 @@ fn open_database_at( } #[derive(Debug)] -enum OpenDbError { +pub enum OpenDbError { // constructed only when rocksdb and paritydb are disabled #[allow(dead_code)] NotEnabled(&'static str), DoesNotExist, Internal(String), + DatabaseError(sp_database::error::DatabaseError), + UnexpectedDbType { + expected: DatabaseType, + found: Vec, + }, } type OpenDbResult = Result>, OpenDbError>; @@ -239,6 +245,17 @@ impl fmt::Display for OpenDbError { OpenDbError::NotEnabled(feat) => { write!(f, "`{}` feature not enabled, database can not be opened", feat) }, + OpenDbError::DatabaseError(db_error) => { + write!(f, "Database Error: {}", db_error) + }, + OpenDbError::UnexpectedDbType { expected, found } => { + write!( + f, + "Unexpected DB-Type. Expected: {:?}, Found: {:?}", + expected.as_str().as_bytes(), + found + ) + }, } } } @@ -356,19 +373,19 @@ fn open_kvdb_rocksdb( pub fn check_database_type( db: &dyn Database, db_type: DatabaseType, -) -> sp_blockchain::Result<()> { +) -> Result<(), OpenDbError> { match db.get(COLUMN_META, meta_keys::TYPE) { Some(stored_type) => if db_type.as_str().as_bytes() != &*stored_type { - return Err(sp_blockchain::Error::Backend(format!( - "Unexpected database type. Expected: {}", - db_type.as_str() - ))) + return Err(OpenDbError::UnexpectedDbType { + expected: db_type, + found: stored_type.to_owned(), + }) }, None => { let mut transaction = Transaction::new(); transaction.set(COLUMN_META, meta_keys::TYPE, db_type.as_str().as_bytes()); - db.commit(transaction)?; + db.commit(transaction).map_err(OpenDbError::DatabaseError)?; }, } @@ -378,7 +395,7 @@ pub fn check_database_type( fn maybe_migrate_to_type_subdir( source: &DatabaseSource, db_type: DatabaseType, -) -> io::Result<()> { +) -> Result<(), OpenDbError> { if let Some(p) = source.path() { let mut basedir = p.to_path_buf(); basedir.pop(); @@ -393,14 +410,14 @@ fn maybe_migrate_to_type_subdir( // database stored in the target directory and close the database on success. let mut old_source = source.clone(); old_source.set_path(&basedir); - open_database_at::(&old_source, db_type) - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + open_database_at::(&old_source, db_type, false)?; info!( "Migrating database to a database-type-based subdirectory: '{:?}' -> '{:?}'", basedir, basedir.join(db_type.as_str()) ); + let mut tmp_dir = basedir.clone(); tmp_dir.pop(); tmp_dir.push("tmp"); @@ -580,9 +597,7 @@ impl<'a, 'b> codec::Input for JoinInput<'a, 'b> { #[cfg(test)] mod tests { use super::*; - use crate::KeepBlocks; use codec::Input; - use sc_state_db::PruningMode; use sp_runtime::testing::{Block as RawBlock, ExtrinsicWrapper}; use std::path::PathBuf; type Block = RawBlock>; @@ -601,18 +616,17 @@ mod tests { let old_db_path = base_path.path().join("chains/dev/db"); source.set_path(&old_db_path); - let settings = db_settings(source.clone()); { - let db_res = open_database::(&settings, db_type); + let db_res = open_database::(&source, db_type, true); assert!(db_res.is_ok(), "New database should be created."); assert!(old_db_path.join(db_check_file).exists()); assert!(!old_db_path.join(db_type.as_str()).join("db_version").exists()); } source.set_path(&old_db_path.join(db_type.as_str())); - let settings = db_settings(source); - let db_res = open_database::(&settings, db_type); + + let db_res = open_database::(&source, db_type, true); assert!(db_res.is_ok(), "Reopening the db with the same role should work"); // check if the database dir had been migrated assert!(!old_db_path.join(db_check_file).exists()); @@ -638,9 +652,8 @@ mod tests { let old_db_path = base_path.path().join("chains/dev/db"); let source = DatabaseSource::RocksDb { path: old_db_path.clone(), cache_size: 128 }; - let settings = db_settings(source); { - let db_res = open_database::(&settings, DatabaseType::Full); + let db_res = open_database::(&source, DatabaseType::Full, true); assert!(db_res.is_ok(), "New database should be created."); // check if the database dir had been migrated @@ -689,16 +702,6 @@ mod tests { assert_eq!(joined.remaining_len().unwrap(), Some(0)); } - fn db_settings(source: DatabaseSource) -> DatabaseSettings { - DatabaseSettings { - state_cache_size: 0, - state_cache_child_ratio: None, - state_pruning: PruningMode::ArchiveAll, - source, - keep_blocks: KeepBlocks::All, - } - } - #[cfg(feature = "with-parity-db")] #[cfg(any(feature = "with-kvdb-rocksdb", test))] #[test] @@ -712,31 +715,36 @@ mod tests { rocksdb_path: rocksdb_path.clone(), cache_size: 128, }; - let mut settings = db_settings(source); // it should create new auto (paritydb) database { - let db_res = open_database::(&settings, DatabaseType::Full); + let db_res = open_database::(&source, DatabaseType::Full, true); assert!(db_res.is_ok(), "New database should be created."); } // it should reopen existing auto (pairtydb) database { - let db_res = open_database::(&settings, DatabaseType::Full); + let db_res = open_database::(&source, DatabaseType::Full, true); assert!(db_res.is_ok(), "Existing parity database should be reopened"); } // it should fail to open existing auto (pairtydb) database { - settings.source = DatabaseSource::RocksDb { path: rocksdb_path, cache_size: 128 }; - let db_res = open_database::(&settings, DatabaseType::Full); + let db_res = open_database::( + &DatabaseSource::RocksDb { path: rocksdb_path, cache_size: 128 }, + DatabaseType::Full, + true, + ); assert!(db_res.is_ok(), "New database should be opened."); } // it should reopen existing auto (pairtydb) database { - settings.source = DatabaseSource::ParityDb { path: paritydb_path }; - let db_res = open_database::(&settings, DatabaseType::Full); + let db_res = open_database::( + &DatabaseSource::ParityDb { path: paritydb_path }, + DatabaseType::Full, + true, + ); assert!(db_res.is_ok(), "Existing parity database should be reopened"); } } @@ -751,36 +759,44 @@ mod tests { let rocksdb_path = db_path.join("rocksdb_path"); let source = DatabaseSource::RocksDb { path: rocksdb_path.clone(), cache_size: 128 }; - let mut settings = db_settings(source); // it should create new rocksdb database { - let db_res = open_database::(&settings, DatabaseType::Full); + let db_res = open_database::(&source, DatabaseType::Full, true); assert!(db_res.is_ok(), "New rocksdb database should be created"); } // it should reopen existing auto (rocksdb) database { - settings.source = DatabaseSource::Auto { - paritydb_path: paritydb_path.clone(), - rocksdb_path: rocksdb_path.clone(), - cache_size: 128, - }; - let db_res = open_database::(&settings, DatabaseType::Full); + let db_res = open_database::( + &DatabaseSource::Auto { + paritydb_path: paritydb_path.clone(), + rocksdb_path: rocksdb_path.clone(), + cache_size: 128, + }, + DatabaseType::Full, + true, + ); assert!(db_res.is_ok(), "Existing rocksdb database should be reopened"); } // it should fail to open existing auto (rocksdb) database { - settings.source = DatabaseSource::ParityDb { path: paritydb_path }; - let db_res = open_database::(&settings, DatabaseType::Full); + let db_res = open_database::( + &DatabaseSource::ParityDb { path: paritydb_path }, + DatabaseType::Full, + true, + ); assert!(db_res.is_ok(), "New paritydb database should be created"); } // it should reopen existing auto (pairtydb) database { - settings.source = DatabaseSource::RocksDb { path: rocksdb_path, cache_size: 128 }; - let db_res = open_database::(&settings, DatabaseType::Full); + let db_res = open_database::( + &DatabaseSource::RocksDb { path: rocksdb_path, cache_size: 128 }, + DatabaseType::Full, + true, + ); assert!(db_res.is_ok(), "Existing rocksdb database should be reopened"); } } @@ -795,32 +811,36 @@ mod tests { let rocksdb_path = db_path.join("rocksdb_path"); let source = DatabaseSource::ParityDb { path: paritydb_path.clone() }; - let mut settings = db_settings(source); // it should create new paritydb database { - let db_res = open_database::(&settings, DatabaseType::Full); + let db_res = open_database::(&source, DatabaseType::Full, true); assert!(db_res.is_ok(), "New database should be created."); } // it should reopen existing pairtydb database { - let db_res = open_database::(&settings, DatabaseType::Full); + let db_res = open_database::(&source, DatabaseType::Full, true); assert!(db_res.is_ok(), "Existing parity database should be reopened"); } // it should fail to open existing pairtydb database { - settings.source = - DatabaseSource::RocksDb { path: rocksdb_path.clone(), cache_size: 128 }; - let db_res = open_database::(&settings, DatabaseType::Full); + let db_res = open_database::( + &DatabaseSource::RocksDb { path: rocksdb_path.clone(), cache_size: 128 }, + DatabaseType::Full, + true, + ); assert!(db_res.is_ok(), "New rocksdb database should be created"); } // it should reopen existing auto (pairtydb) database { - settings.source = DatabaseSource::Auto { paritydb_path, rocksdb_path, cache_size: 128 }; - let db_res = open_database::(&settings, DatabaseType::Full); + let db_res = open_database::( + &DatabaseSource::Auto { paritydb_path, rocksdb_path, cache_size: 128 }, + DatabaseType::Full, + true, + ); assert!(db_res.is_ok(), "Existing parity database should be reopened"); } } diff --git a/client/service/src/builder.rs b/client/service/src/builder.rs index 35ea67d8fbf85..cabf004b2f707 100644 --- a/client/service/src/builder.rs +++ b/client/service/src/builder.rs @@ -307,7 +307,7 @@ where wasm_runtime_overrides: config.wasm_runtime_overrides.clone(), no_genesis: matches!( config.network.sync_mode, - sc_network::config::SyncMode::Fast { .. } | sc_network::config::SyncMode::Warp + SyncMode::Fast { .. } | SyncMode::Warp { .. } ), wasm_runtime_substitutes, }, @@ -781,12 +781,12 @@ where return Err("Warp sync enabled, but no warp sync provider configured.".into()) } - if config.state_pruning.is_archive() { + if client.requires_full_sync() { match config.network.sync_mode { SyncMode::Fast { .. } => return Err("Fast sync doesn't work for archive nodes".into()), SyncMode::Warp => return Err("Warp sync doesn't work for archive nodes".into()), SyncMode::Full => {}, - }; + } } let transaction_pool_adapter = Arc::new(TransactionPoolAdapter { diff --git a/client/service/src/client/client.rs b/client/service/src/client/client.rs index 48e4d0141c4e7..fb73b4c34c040 100644 --- a/client/service/src/client/client.rs +++ b/client/service/src/client/client.rs @@ -1989,6 +1989,10 @@ where ) -> sp_blockchain::Result>>> { self.backend.blockchain().block_indexed_body(*id) } + + fn requires_full_sync(&self) -> bool { + self.backend.requires_full_sync() + } } impl backend::AuxStore for Client diff --git a/client/service/src/config.rs b/client/service/src/config.rs index 56980ad14425f..e49e8b40a7b1a 100644 --- a/client/service/src/config.rs +++ b/client/service/src/config.rs @@ -73,7 +73,7 @@ pub struct Configuration { /// Size in percent of cache size dedicated to child tries pub state_cache_child_ratio: Option, /// State pruning settings. - pub state_pruning: PruningMode, + pub state_pruning: Option, /// Number of blocks to keep in the db. pub keep_blocks: KeepBlocks, /// Chain configuration. diff --git a/client/service/test/src/client/mod.rs b/client/service/test/src/client/mod.rs index 6aa047c6393da..136efad088fae 100644 --- a/client/service/test/src/client/mod.rs +++ b/client/service/test/src/client/mod.rs @@ -1199,7 +1199,7 @@ fn doesnt_import_blocks_that_revert_finality() { DatabaseSettings { state_cache_size: 1 << 20, state_cache_child_ratio: None, - state_pruning: PruningMode::ArchiveAll, + state_pruning: Some(PruningMode::ArchiveAll), keep_blocks: KeepBlocks::All, source: DatabaseSource::RocksDb { path: tmp.path().into(), cache_size: 1024 }, }, @@ -1426,7 +1426,7 @@ fn returns_status_for_pruned_blocks() { DatabaseSettings { state_cache_size: 1 << 20, state_cache_child_ratio: None, - state_pruning: PruningMode::keep_blocks(1), + state_pruning: Some(PruningMode::keep_blocks(1)), keep_blocks: KeepBlocks::All, source: DatabaseSource::RocksDb { path: tmp.path().into(), cache_size: 1024 }, }, diff --git a/client/state-db/src/lib.rs b/client/state-db/src/lib.rs index 8fe2cdc9d9a85..794dec3b954b7 100644 --- a/client/state-db/src/lib.rs +++ b/client/state-db/src/lib.rs @@ -115,9 +115,13 @@ pub trait NodeDb { } /// Error type. -pub enum Error { +pub enum Error { /// Database backend error. Db(E), + StateDb(StateDbError), +} + +pub enum StateDbError { /// `Codec` decoding error. Decoding(codec::Error), /// Trying to canonicalize invalid block. @@ -127,11 +131,19 @@ pub enum Error { /// Trying to insert block with unknown parent. InvalidParent, /// Invalid pruning mode specified. Contains expected mode. - InvalidPruningMode(String), + IncompatiblePruningModes { stored: PruningMode, requested: PruningMode }, /// Too many unfinalized sibling blocks inserted. TooManySiblingBlocks, /// Trying to insert existing block. BlockAlreadyExists, + /// Invalid metadata + Metadata(String), +} + +impl From for Error { + fn from(inner: StateDbError) -> Self { + Self::StateDb(inner) + } } /// Pinning error type. @@ -142,21 +154,34 @@ pub enum PinError { impl From for Error { fn from(x: codec::Error) -> Self { - Error::Decoding(x) + StateDbError::Decoding(x).into() } } impl fmt::Debug for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Error::Db(e) => e.fmt(f), - Error::Decoding(e) => write!(f, "Error decoding sliceable value: {}", e), - Error::InvalidBlock => write!(f, "Trying to canonicalize invalid block"), - Error::InvalidBlockNumber => write!(f, "Trying to insert block with invalid number"), - Error::InvalidParent => write!(f, "Trying to insert block with unknown parent"), - Error::InvalidPruningMode(e) => write!(f, "Expected pruning mode: {}", e), - Error::TooManySiblingBlocks => write!(f, "Too many sibling blocks inserted"), - Error::BlockAlreadyExists => write!(f, "Block already exists"), + Self::Db(e) => e.fmt(f), + Self::StateDb(e) => e.fmt(f), + } + } +} + +impl fmt::Debug for StateDbError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Decoding(e) => write!(f, "Error decoding sliceable value: {}", e), + Self::InvalidBlock => write!(f, "Trying to canonicalize invalid block"), + Self::InvalidBlockNumber => write!(f, "Trying to insert block with invalid number"), + Self::InvalidParent => write!(f, "Trying to insert block with unknown parent"), + Self::IncompatiblePruningModes { stored, requested } => write!( + f, + "Incompatible pruning modes [stored: {:?}; requested: {:?}]", + stored, requested + ), + Self::TooManySiblingBlocks => write!(f, "Too many sibling blocks inserted"), + Self::BlockAlreadyExists => write!(f, "Block already exists"), + Self::Metadata(message) => write!(f, "Invalid metadata: {}", message), } } } @@ -180,7 +205,7 @@ pub struct CommitSet { } /// Pruning constraints. If none are specified pruning is -#[derive(Default, Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq)] pub struct Constraints { /// Maximum blocks. Defaults to 0 when unspecified, effectively keeping only non-canonical /// states. @@ -222,11 +247,25 @@ impl PruningMode { PruningMode::Constrained(_) => PRUNING_MODE_CONSTRAINED, } } + pub fn from_id(id: &[u8]) -> Option { + match id { + PRUNING_MODE_ARCHIVE => Some(Self::ArchiveAll), + PRUNING_MODE_ARCHIVE_CANON => Some(Self::ArchiveCanonical), + PRUNING_MODE_CONSTRAINED => Some(Self::Constrained(Default::default())), + _ => None, + } + } } impl Default for PruningMode { fn default() -> Self { - PruningMode::keep_blocks(256) + PruningMode::Constrained(Default::default()) + } +} + +impl Default for Constraints { + fn default() -> Self { + Self { max_blocks: Some(256), max_mem: None } } } @@ -251,9 +290,6 @@ impl StateDbSync Result, Error> { trace!(target: "state-db", "StateDb settings: {:?}. Ref-counting: {}", mode, ref_counting); - // Check that settings match - Self::check_meta(&mode, db)?; - let non_canonical: NonCanonicalOverlay = NonCanonicalOverlay::new(db)?; let pruning: Option> = match mode { PruningMode::Constrained(Constraints { max_mem: Some(_), .. }) => unimplemented!(), @@ -264,19 +300,6 @@ impl StateDbSync(mode: &PruningMode, db: &D) -> Result<(), Error> { - let db_mode = db.get_meta(&to_meta_key(PRUNING_MODE, &())).map_err(Error::Db)?; - trace!(target: "state-db", - "DB pruning mode: {:?}", - db_mode.as_ref().map(|v| std::str::from_utf8(v)) - ); - match &db_mode { - Some(v) if v.as_slice() == mode.id() => Ok(()), - Some(v) => Err(Error::InvalidPruningMode(String::from_utf8_lossy(v).into())), - None => Ok(()), - } - } - fn insert_block( &mut self, hash: &BlockHash, @@ -284,25 +307,16 @@ impl StateDbSync, ) -> Result, Error> { - let mut meta = ChangeSet::default(); - if number == 0 { - // Save pruning mode when writing first block. - meta.inserted.push((to_meta_key(PRUNING_MODE, &()), self.mode.id().into())); - } - match self.mode { PruningMode::ArchiveAll => { changeset.deleted.clear(); // write changes immediately - Ok(CommitSet { data: changeset, meta }) - }, - PruningMode::Constrained(_) | PruningMode::ArchiveCanonical => { - let commit = self.non_canonical.insert(hash, number, parent_hash, changeset); - commit.map(|mut c| { - c.meta.inserted.extend(meta.inserted); - c - }) + Ok(CommitSet { data: changeset, meta: Default::default() }) }, + PruningMode::Constrained(_) | PruningMode::ArchiveCanonical => self + .non_canonical + .insert(hash, number, parent_hash, changeset) + .map_err(Into::into), } } @@ -319,7 +333,7 @@ impl StateDbSync return Err(e), + Err(e) => return Err(e.into()), }; if let Some(ref mut pruning) = self.pruning { pruning.note_canonical(hash, &mut commit); @@ -480,13 +494,56 @@ pub struct StateDb { } impl StateDb { - /// Creates a new instance. Does not expect any metadata in the database. - pub fn new( - mode: PruningMode, - ref_counting: bool, + /// Create an instance of [`StateDb`]. + pub fn open( db: &D, - ) -> Result, Error> { - Ok(StateDb { db: RwLock::new(StateDbSync::new(mode, ref_counting, db)?) }) + requested_mode: Option, + ref_counting: bool, + should_init: bool, + ) -> Result<(CommitSet, StateDb), Error> + where + D: MetaDb, + { + let stored_mode = fetch_stored_pruning_mode(db)?; + + let selected_mode = match (should_init, stored_mode, requested_mode) { + (true, stored_mode, requested_mode) => { + assert!(stored_mode.is_none(), "The storage has just been initialized. No meta-data is expected to be found in it."); + requested_mode.unwrap_or_default() + }, + + (false, None, _) => + return Err(StateDbError::Metadata( + "An existing StateDb does not have PRUNING_MODE stored in its meta-data".into(), + ) + .into()), + + (false, Some(stored), None) => stored, + + (false, Some(stored), Some(requested)) => choose_pruning_mode(stored, requested)?, + }; + + let db_init_commit_set = if should_init { + let mut cs: CommitSet = Default::default(); + + let key = to_meta_key(PRUNING_MODE, &()); + let value = selected_mode.id().to_owned(); + + cs.meta.inserted.push((key, value)); + + cs + } else { + Default::default() + }; + + let state_db = + StateDb { db: RwLock::new(StateDbSync::new(selected_mode, ref_counting, db)?) }; + + Ok((db_init_commit_set, state_db)) + } + + pub fn pruning_mode(&self) -> PruningMode { + self.db.read().mode.clone() } /// Add a new non-canonical block. @@ -571,18 +628,51 @@ impl StateDb(db: &D) -> Result, Error> { + let meta_key_mode = to_meta_key(PRUNING_MODE, &()); + if let Some(stored_mode) = db.get_meta(&meta_key_mode).map_err(Error::Db)? { + if let Some(mode) = PruningMode::from_id(&stored_mode) { + Ok(Some(mode)) + } else { + Err(StateDbError::Metadata(format!( + "Invalid value stored for PRUNING_MODE: {:02x?}", + stored_mode + )) + .into()) + } + } else { + Ok(None) + } +} + +fn choose_pruning_mode( + stored: PruningMode, + requested: PruningMode, +) -> Result { + match (stored, requested) { + (PruningMode::ArchiveAll, PruningMode::ArchiveAll) => Ok(PruningMode::ArchiveAll), + (PruningMode::ArchiveCanonical, PruningMode::ArchiveCanonical) => + Ok(PruningMode::ArchiveCanonical), + (PruningMode::Constrained(_), PruningMode::Constrained(requested)) => + Ok(PruningMode::Constrained(requested)), + (stored, requested) => Err(StateDbError::IncompatiblePruningModes { requested, stored }), + } +} + #[cfg(test)] mod tests { use crate::{ test::{make_changeset, make_db, TestDb}, - Constraints, PruningMode, StateDb, + Constraints, Error, PruningMode, StateDb, StateDbError, }; use sp_core::H256; use std::io; fn make_test_db(settings: PruningMode) -> (TestDb, StateDb) { let mut db = make_db(&[91, 921, 922, 93, 94]); - let state_db = StateDb::new(settings, false, &db).unwrap(); + let (state_db_init, state_db) = + StateDb::open(&mut db, Some(settings), false, true).unwrap(); + db.commit(&state_db_init); db.commit( &state_db @@ -697,7 +787,9 @@ mod tests { #[test] fn detects_incompatible_mode() { let mut db = make_db(&[]); - let state_db = StateDb::new(PruningMode::ArchiveAll, false, &db).unwrap(); + let (state_db_init, state_db) = + StateDb::open(&mut db, Some(PruningMode::ArchiveAll), false, true).unwrap(); + db.commit(&state_db_init); db.commit( &state_db .insert_block::( @@ -709,7 +801,85 @@ mod tests { .unwrap(), ); let new_mode = PruningMode::Constrained(Constraints { max_blocks: Some(2), max_mem: None }); - let state_db: Result, _> = StateDb::new(new_mode, false, &db); - assert!(state_db.is_err()); + let state_db_open_result: Result<(_, StateDb), _> = + StateDb::open(&mut db, Some(new_mode), false, false); + assert!(state_db_open_result.is_err()); + } + + fn check_stored_and_requested_mode_compatibility( + mode_when_created: Option, + mode_when_reopened: Option, + expected_effective_mode_when_reopenned: Result, + ) { + let mut db = make_db(&[]); + let (state_db_init, state_db) = + StateDb::::open(&mut db, mode_when_created, false, true).unwrap(); + db.commit(&state_db_init); + std::mem::drop(state_db); + + let state_db_reopen_result = + StateDb::::open(&mut db, mode_when_reopened, false, false); + if let Ok(expected_mode) = expected_effective_mode_when_reopenned { + let (state_db_init, state_db_reopened) = state_db_reopen_result.unwrap(); + db.commit(&state_db_init); + assert_eq!(state_db_reopened.pruning_mode(), expected_mode,) + } else { + assert!(matches!( + state_db_reopen_result, + Err(Error::StateDb(StateDbError::IncompatiblePruningModes { .. })) + )); + } + } + + #[test] + fn pruning_mode_compatibility() { + for (created, reopened, expected) in [ + (None, None, Ok(PruningMode::keep_blocks(256))), + (None, Some(PruningMode::keep_blocks(256)), Ok(PruningMode::keep_blocks(256))), + (None, Some(PruningMode::keep_blocks(128)), Ok(PruningMode::keep_blocks(128))), + (None, Some(PruningMode::keep_blocks(512)), Ok(PruningMode::keep_blocks(512))), + (None, Some(PruningMode::ArchiveAll), Err(())), + (None, Some(PruningMode::ArchiveCanonical), Err(())), + (Some(PruningMode::keep_blocks(256)), None, Ok(PruningMode::keep_blocks(256))), + ( + Some(PruningMode::keep_blocks(256)), + Some(PruningMode::keep_blocks(256)), + Ok(PruningMode::keep_blocks(256)), + ), + ( + Some(PruningMode::keep_blocks(256)), + Some(PruningMode::keep_blocks(128)), + Ok(PruningMode::keep_blocks(128)), + ), + ( + Some(PruningMode::keep_blocks(256)), + Some(PruningMode::keep_blocks(512)), + Ok(PruningMode::keep_blocks(512)), + ), + (Some(PruningMode::keep_blocks(256)), Some(PruningMode::ArchiveAll), Err(())), + (Some(PruningMode::keep_blocks(256)), Some(PruningMode::ArchiveCanonical), Err(())), + (Some(PruningMode::ArchiveAll), None, Ok(PruningMode::ArchiveAll)), + (Some(PruningMode::ArchiveAll), Some(PruningMode::keep_blocks(256)), Err(())), + (Some(PruningMode::ArchiveAll), Some(PruningMode::keep_blocks(128)), Err(())), + (Some(PruningMode::ArchiveAll), Some(PruningMode::keep_blocks(512)), Err(())), + ( + Some(PruningMode::ArchiveAll), + Some(PruningMode::ArchiveAll), + Ok(PruningMode::ArchiveAll), + ), + (Some(PruningMode::ArchiveAll), Some(PruningMode::ArchiveCanonical), Err(())), + (Some(PruningMode::ArchiveCanonical), None, Ok(PruningMode::ArchiveCanonical)), + (Some(PruningMode::ArchiveCanonical), Some(PruningMode::keep_blocks(256)), Err(())), + (Some(PruningMode::ArchiveCanonical), Some(PruningMode::keep_blocks(128)), Err(())), + (Some(PruningMode::ArchiveCanonical), Some(PruningMode::keep_blocks(512)), Err(())), + (Some(PruningMode::ArchiveCanonical), Some(PruningMode::ArchiveAll), Err(())), + ( + Some(PruningMode::ArchiveCanonical), + Some(PruningMode::ArchiveCanonical), + Ok(PruningMode::ArchiveCanonical), + ), + ] { + check_stored_and_requested_mode_compatibility(created, reopened, expected); + } } } diff --git a/client/state-db/src/noncanonical.rs b/client/state-db/src/noncanonical.rs index 30f2042863010..13cf5825b1b24 100644 --- a/client/state-db/src/noncanonical.rs +++ b/client/state-db/src/noncanonical.rs @@ -22,13 +22,10 @@ //! All pending changes are kept in memory until next call to `apply_pending` or //! `revert_pending` -use super::{to_meta_key, ChangeSet, CommitSet, DBValue, Error, Hash, MetaDb}; +use super::{to_meta_key, ChangeSet, CommitSet, DBValue, Error, Hash, MetaDb, StateDbError}; use codec::{Decode, Encode}; use log::trace; -use std::{ - collections::{hash_map::Entry, HashMap, VecDeque}, - fmt, -}; +use std::collections::{hash_map::Entry, HashMap, VecDeque}; const NON_CANONICAL_JOURNAL: &[u8] = b"noncanonical_journal"; const LAST_CANONICAL: &[u8] = b"last_canonical"; @@ -242,13 +239,13 @@ impl NonCanonicalOverlay { /// Insert a new block into the overlay. If inserted on the second level or lover expects parent /// to be present in the window. - pub fn insert( + pub fn insert( &mut self, hash: &BlockHash, number: u64, parent_hash: &BlockHash, changeset: ChangeSet, - ) -> Result, Error> { + ) -> Result, StateDbError> { let mut commit = CommitSet::default(); let front_block_number = self.front_block_number(); if self.levels.is_empty() && self.last_canonicalized.is_none() && number > 0 { @@ -267,7 +264,7 @@ impl NonCanonicalOverlay { front_block_number, front_block_number + self.levels.len() as u64, ); - return Err(Error::InvalidBlockNumber) + return Err(StateDbError::InvalidBlockNumber) } // check for valid parent if inserting on second level or higher if number == front_block_number { @@ -276,10 +273,10 @@ impl NonCanonicalOverlay { .as_ref() .map_or(false, |&(ref h, n)| h == parent_hash && n == number - 1) { - return Err(Error::InvalidParent) + return Err(StateDbError::InvalidParent) } } else if !self.parents.contains_key(parent_hash) { - return Err(Error::InvalidParent) + return Err(StateDbError::InvalidParent) } } let level = if self.levels.is_empty() || @@ -293,10 +290,10 @@ impl NonCanonicalOverlay { }; if level.blocks.len() >= MAX_BLOCKS_PER_LEVEL as usize { - return Err(Error::TooManySiblingBlocks) + return Err(StateDbError::TooManySiblingBlocks) } if level.blocks.iter().any(|b| b.hash == *hash) { - return Err(Error::BlockAlreadyExists) + return Err(StateDbError::BlockAlreadyExists) } let index = level.available_index(); @@ -380,21 +377,21 @@ impl NonCanonicalOverlay { /// Select a top-level root and canonicalized it. Discards all sibling subtrees and the root. /// Returns a set of changes that need to be added to the DB. - pub fn canonicalize( + pub fn canonicalize( &mut self, hash: &BlockHash, commit: &mut CommitSet, - ) -> Result<(), Error> { + ) -> Result<(), StateDbError> { trace!(target: "state-db", "Canonicalizing {:?}", hash); let level = self .levels .get(self.pending_canonicalizations.len()) - .ok_or(Error::InvalidBlock)?; + .ok_or(StateDbError::InvalidBlock)?; let index = level .blocks .iter() .position(|overlay| overlay.hash == *hash) - .ok_or(Error::InvalidBlock)?; + .ok_or(StateDbError::InvalidBlock)?; let mut discarded_journals = Vec::new(); let mut discarded_blocks = Vec::new(); @@ -640,10 +637,9 @@ mod tests { use super::{to_journal_key, NonCanonicalOverlay}; use crate::{ test::{make_changeset, make_db}, - ChangeSet, CommitSet, Error, MetaDb, + ChangeSet, CommitSet, MetaDb, StateDbError, }; use sp_core::H256; - use std::io; fn contains(overlay: &NonCanonicalOverlay, key: u64) -> bool { overlay.get(&H256::from_low_u64_be(key)) == @@ -665,7 +661,7 @@ mod tests { let db = make_db(&[]); let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); let mut commit = CommitSet::default(); - overlay.canonicalize::(&H256::default(), &mut commit).unwrap(); + overlay.canonicalize(&H256::default(), &mut commit).unwrap(); } #[test] @@ -675,10 +671,8 @@ mod tests { let h1 = H256::random(); let h2 = H256::random(); let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); - overlay - .insert::(&h1, 2, &H256::default(), ChangeSet::default()) - .unwrap(); - overlay.insert::(&h2, 1, &h1, ChangeSet::default()).unwrap(); + overlay.insert(&h1, 2, &H256::default(), ChangeSet::default()).unwrap(); + overlay.insert(&h2, 1, &h1, ChangeSet::default()).unwrap(); } #[test] @@ -688,10 +682,8 @@ mod tests { let h2 = H256::random(); let db = make_db(&[]); let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); - overlay - .insert::(&h1, 1, &H256::default(), ChangeSet::default()) - .unwrap(); - overlay.insert::(&h2, 3, &h1, ChangeSet::default()).unwrap(); + overlay.insert(&h1, 1, &H256::default(), ChangeSet::default()).unwrap(); + overlay.insert(&h2, 3, &h1, ChangeSet::default()).unwrap(); } #[test] @@ -701,12 +693,8 @@ mod tests { let h1 = H256::random(); let h2 = H256::random(); let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); - overlay - .insert::(&h1, 1, &H256::default(), ChangeSet::default()) - .unwrap(); - overlay - .insert::(&h2, 2, &H256::default(), ChangeSet::default()) - .unwrap(); + overlay.insert(&h1, 1, &H256::default(), ChangeSet::default()).unwrap(); + overlay.insert(&h2, 2, &H256::default(), ChangeSet::default()).unwrap(); } #[test] @@ -714,12 +702,10 @@ mod tests { let db = make_db(&[]); let h1 = H256::random(); let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); - overlay - .insert::(&h1, 2, &H256::default(), ChangeSet::default()) - .unwrap(); + overlay.insert(&h1, 2, &H256::default(), ChangeSet::default()).unwrap(); assert!(matches!( - overlay.insert::(&h1, 2, &H256::default(), ChangeSet::default()), - Err(Error::BlockAlreadyExists) + overlay.insert(&h1, 2, &H256::default(), ChangeSet::default()), + Err(StateDbError::BlockAlreadyExists) )); } @@ -730,11 +716,9 @@ mod tests { let h2 = H256::random(); let db = make_db(&[]); let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); - overlay - .insert::(&h1, 1, &H256::default(), ChangeSet::default()) - .unwrap(); + overlay.insert(&h1, 1, &H256::default(), ChangeSet::default()).unwrap(); let mut commit = CommitSet::default(); - overlay.canonicalize::(&h2, &mut commit).unwrap(); + overlay.canonicalize(&h2, &mut commit).unwrap(); } #[test] @@ -743,16 +727,14 @@ mod tests { let mut db = make_db(&[1, 2]); let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); let changeset = make_changeset(&[3, 4], &[2]); - let insertion = overlay - .insert::(&h1, 1, &H256::default(), changeset.clone()) - .unwrap(); + let insertion = overlay.insert(&h1, 1, &H256::default(), changeset.clone()).unwrap(); assert_eq!(insertion.data.inserted.len(), 0); assert_eq!(insertion.data.deleted.len(), 0); assert_eq!(insertion.meta.inserted.len(), 2); assert_eq!(insertion.meta.deleted.len(), 0); db.commit(&insertion); let mut finalization = CommitSet::default(); - overlay.canonicalize::(&h1, &mut finalization).unwrap(); + overlay.canonicalize(&h1, &mut finalization).unwrap(); assert_eq!(finalization.data.inserted.len(), changeset.inserted.len()); assert_eq!(finalization.data.deleted.len(), changeset.deleted.len()); assert_eq!(finalization.meta.inserted.len(), 1); @@ -769,10 +751,10 @@ mod tests { let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); db.commit( &overlay - .insert::(&h1, 10, &H256::default(), make_changeset(&[3, 4], &[2])) + .insert(&h1, 10, &H256::default(), make_changeset(&[3, 4], &[2])) .unwrap(), ); - db.commit(&overlay.insert::(&h2, 11, &h1, make_changeset(&[5], &[3])).unwrap()); + db.commit(&overlay.insert(&h2, 11, &h1, make_changeset(&[5], &[3])).unwrap()); assert_eq!(db.meta.len(), 3); let overlay2 = NonCanonicalOverlay::::new(&db).unwrap(); @@ -789,12 +771,12 @@ mod tests { let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); db.commit( &overlay - .insert::(&h1, 10, &H256::default(), make_changeset(&[3, 4], &[2])) + .insert(&h1, 10, &H256::default(), make_changeset(&[3, 4], &[2])) .unwrap(), ); - db.commit(&overlay.insert::(&h2, 11, &h1, make_changeset(&[5], &[3])).unwrap()); + db.commit(&overlay.insert(&h2, 11, &h1, make_changeset(&[5], &[3])).unwrap()); let mut commit = CommitSet::default(); - overlay.canonicalize::(&h1, &mut commit).unwrap(); + overlay.canonicalize(&h1, &mut commit).unwrap(); db.commit(&commit); overlay.apply_pending(); assert_eq!(overlay.levels.len(), 1); @@ -813,15 +795,15 @@ mod tests { let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); let changeset1 = make_changeset(&[5, 6], &[2]); let changeset2 = make_changeset(&[7, 8], &[5, 3]); - db.commit(&overlay.insert::(&h1, 1, &H256::default(), changeset1).unwrap()); + db.commit(&overlay.insert(&h1, 1, &H256::default(), changeset1).unwrap()); assert!(contains(&overlay, 5)); - db.commit(&overlay.insert::(&h2, 2, &h1, changeset2).unwrap()); + db.commit(&overlay.insert(&h2, 2, &h1, changeset2).unwrap()); assert!(contains(&overlay, 7)); assert!(contains(&overlay, 5)); assert_eq!(overlay.levels.len(), 2); assert_eq!(overlay.parents.len(), 2); let mut commit = CommitSet::default(); - overlay.canonicalize::(&h1, &mut commit).unwrap(); + overlay.canonicalize(&h1, &mut commit).unwrap(); db.commit(&commit); assert!(contains(&overlay, 5)); assert_eq!(overlay.levels.len(), 2); @@ -832,7 +814,7 @@ mod tests { assert!(!contains(&overlay, 5)); assert!(contains(&overlay, 7)); let mut commit = CommitSet::default(); - overlay.canonicalize::(&h2, &mut commit).unwrap(); + overlay.canonicalize(&h2, &mut commit).unwrap(); db.commit(&commit); overlay.apply_pending(); assert_eq!(overlay.levels.len(), 0); @@ -847,11 +829,11 @@ mod tests { let (h_2, c_2) = (H256::random(), make_changeset(&[1], &[])); let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); - db.commit(&overlay.insert::(&h_1, 1, &H256::default(), c_1).unwrap()); - db.commit(&overlay.insert::(&h_2, 1, &H256::default(), c_2).unwrap()); + db.commit(&overlay.insert(&h_1, 1, &H256::default(), c_1).unwrap()); + db.commit(&overlay.insert(&h_2, 1, &H256::default(), c_2).unwrap()); assert!(contains(&overlay, 1)); let mut commit = CommitSet::default(); - overlay.canonicalize::(&h_1, &mut commit).unwrap(); + overlay.canonicalize(&h_1, &mut commit).unwrap(); db.commit(&commit); assert!(contains(&overlay, 1)); overlay.apply_pending(); @@ -866,18 +848,14 @@ mod tests { let mut db = make_db(&[]); let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); let changeset = make_changeset(&[], &[]); - db.commit( - &overlay - .insert::(&h1, 1, &H256::default(), changeset.clone()) - .unwrap(), - ); - db.commit(&overlay.insert::(&h2, 2, &h1, changeset.clone()).unwrap()); + db.commit(&overlay.insert(&h1, 1, &H256::default(), changeset.clone()).unwrap()); + db.commit(&overlay.insert(&h2, 2, &h1, changeset.clone()).unwrap()); overlay.apply_pending(); let mut commit = CommitSet::default(); - overlay.canonicalize::(&h1, &mut commit).unwrap(); - overlay.canonicalize::(&h2, &mut commit).unwrap(); + overlay.canonicalize(&h1, &mut commit).unwrap(); + overlay.canonicalize(&h2, &mut commit).unwrap(); db.commit(&commit); - db.commit(&overlay.insert::(&h3, 3, &h2, changeset.clone()).unwrap()); + db.commit(&overlay.insert(&h3, 3, &h2, changeset.clone()).unwrap()); overlay.apply_pending(); assert_eq!(overlay.levels.len(), 1); } @@ -912,21 +890,21 @@ mod tests { let (h_2_1_1, c_2_1_1) = (H256::random(), make_changeset(&[211], &[])); let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); - db.commit(&overlay.insert::(&h_1, 1, &H256::default(), c_1).unwrap()); + db.commit(&overlay.insert(&h_1, 1, &H256::default(), c_1).unwrap()); - db.commit(&overlay.insert::(&h_1_1, 2, &h_1, c_1_1).unwrap()); - db.commit(&overlay.insert::(&h_1_2, 2, &h_1, c_1_2).unwrap()); + db.commit(&overlay.insert(&h_1_1, 2, &h_1, c_1_1).unwrap()); + db.commit(&overlay.insert(&h_1_2, 2, &h_1, c_1_2).unwrap()); - db.commit(&overlay.insert::(&h_2, 1, &H256::default(), c_2).unwrap()); + db.commit(&overlay.insert(&h_2, 1, &H256::default(), c_2).unwrap()); - db.commit(&overlay.insert::(&h_2_1, 2, &h_2, c_2_1).unwrap()); - db.commit(&overlay.insert::(&h_2_2, 2, &h_2, c_2_2).unwrap()); + db.commit(&overlay.insert(&h_2_1, 2, &h_2, c_2_1).unwrap()); + db.commit(&overlay.insert(&h_2_2, 2, &h_2, c_2_2).unwrap()); - db.commit(&overlay.insert::(&h_1_1_1, 3, &h_1_1, c_1_1_1).unwrap()); - db.commit(&overlay.insert::(&h_1_2_1, 3, &h_1_2, c_1_2_1).unwrap()); - db.commit(&overlay.insert::(&h_1_2_2, 3, &h_1_2, c_1_2_2).unwrap()); - db.commit(&overlay.insert::(&h_1_2_3, 3, &h_1_2, c_1_2_3).unwrap()); - db.commit(&overlay.insert::(&h_2_1_1, 3, &h_2_1, c_2_1_1).unwrap()); + db.commit(&overlay.insert(&h_1_1_1, 3, &h_1_1, c_1_1_1).unwrap()); + db.commit(&overlay.insert(&h_1_2_1, 3, &h_1_2, c_1_2_1).unwrap()); + db.commit(&overlay.insert(&h_1_2_2, 3, &h_1_2, c_1_2_2).unwrap()); + db.commit(&overlay.insert(&h_1_2_3, 3, &h_1_2, c_1_2_3).unwrap()); + db.commit(&overlay.insert(&h_2_1_1, 3, &h_2_1, c_2_1_1).unwrap()); assert!(contains(&overlay, 2)); assert!(contains(&overlay, 11)); @@ -946,7 +924,7 @@ mod tests { // canonicalize 1. 2 and all its children should be discarded let mut commit = CommitSet::default(); - overlay.canonicalize::(&h_1, &mut commit).unwrap(); + overlay.canonicalize(&h_1, &mut commit).unwrap(); db.commit(&commit); overlay.apply_pending(); assert_eq!(overlay.levels.len(), 2); @@ -967,7 +945,7 @@ mod tests { // canonicalize 1_2. 1_1 and all its children should be discarded let mut commit = CommitSet::default(); - overlay.canonicalize::(&h_1_2, &mut commit).unwrap(); + overlay.canonicalize(&h_1_2, &mut commit).unwrap(); db.commit(&commit); overlay.apply_pending(); assert_eq!(overlay.levels.len(), 1); @@ -984,7 +962,7 @@ mod tests { // canonicalize 1_2_2 let mut commit = CommitSet::default(); - overlay.canonicalize::(&h_1_2_2, &mut commit).unwrap(); + overlay.canonicalize(&h_1_2_2, &mut commit).unwrap(); db.commit(&commit); overlay.apply_pending(); assert_eq!(overlay.levels.len(), 0); @@ -1002,8 +980,8 @@ mod tests { assert!(overlay.revert_one().is_none()); let changeset1 = make_changeset(&[5, 6], &[2]); let changeset2 = make_changeset(&[7, 8], &[5, 3]); - db.commit(&overlay.insert::(&h1, 1, &H256::default(), changeset1).unwrap()); - db.commit(&overlay.insert::(&h2, 2, &h1, changeset2).unwrap()); + db.commit(&overlay.insert(&h1, 1, &H256::default(), changeset1).unwrap()); + db.commit(&overlay.insert(&h2, 2, &h1, changeset2).unwrap()); assert!(contains(&overlay, 7)); db.commit(&overlay.revert_one().unwrap()); assert_eq!(overlay.parents.len(), 1); @@ -1025,10 +1003,10 @@ mod tests { let changeset1 = make_changeset(&[5, 6], &[2]); let changeset2 = make_changeset(&[7, 8], &[5, 3]); let changeset3 = make_changeset(&[9], &[]); - overlay.insert::(&h1, 1, &H256::default(), changeset1).unwrap(); + overlay.insert(&h1, 1, &H256::default(), changeset1).unwrap(); assert!(contains(&overlay, 5)); - overlay.insert::(&h2_1, 2, &h1, changeset2).unwrap(); - overlay.insert::(&h2_2, 2, &h1, changeset3).unwrap(); + overlay.insert(&h2_1, 2, &h1, changeset2).unwrap(); + overlay.insert(&h2_2, 2, &h1, changeset3).unwrap(); assert!(contains(&overlay, 7)); assert!(contains(&overlay, 5)); assert!(contains(&overlay, 9)); @@ -1052,14 +1030,14 @@ mod tests { let (h_2, c_2) = (H256::random(), make_changeset(&[2], &[])); let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); - db.commit(&overlay.insert::(&h_1, 1, &H256::default(), c_1).unwrap()); - db.commit(&overlay.insert::(&h_2, 1, &H256::default(), c_2).unwrap()); + db.commit(&overlay.insert(&h_1, 1, &H256::default(), c_1).unwrap()); + db.commit(&overlay.insert(&h_2, 1, &H256::default(), c_2).unwrap()); overlay.apply_pending(); overlay.pin(&h_1); let mut commit = CommitSet::default(); - overlay.canonicalize::(&h_2, &mut commit).unwrap(); + overlay.canonicalize(&h_2, &mut commit).unwrap(); db.commit(&commit); overlay.apply_pending(); assert!(contains(&overlay, 1)); @@ -1082,15 +1060,15 @@ mod tests { let (h_3, c_3) = (H256::random(), make_changeset(&[], &[])); let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); - db.commit(&overlay.insert::(&h_1, 1, &H256::default(), c_1).unwrap()); - db.commit(&overlay.insert::(&h_2, 1, &H256::default(), c_2).unwrap()); - db.commit(&overlay.insert::(&h_3, 1, &H256::default(), c_3).unwrap()); + db.commit(&overlay.insert(&h_1, 1, &H256::default(), c_1).unwrap()); + db.commit(&overlay.insert(&h_2, 1, &H256::default(), c_2).unwrap()); + db.commit(&overlay.insert(&h_3, 1, &H256::default(), c_3).unwrap()); overlay.apply_pending(); overlay.pin(&h_1); let mut commit = CommitSet::default(); - overlay.canonicalize::(&h_3, &mut commit).unwrap(); + overlay.canonicalize(&h_3, &mut commit).unwrap(); db.commit(&commit); overlay.apply_pending(); // 1_2 should be discarded, 1_1 is pinned @@ -1112,15 +1090,15 @@ mod tests { let (h_21, c_21) = (H256::random(), make_changeset(&[], &[])); let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); - db.commit(&overlay.insert::(&h_11, 1, &H256::default(), c_11).unwrap()); - db.commit(&overlay.insert::(&h_12, 1, &H256::default(), c_12).unwrap()); - db.commit(&overlay.insert::(&h_21, 2, &h_11, c_21).unwrap()); + db.commit(&overlay.insert(&h_11, 1, &H256::default(), c_11).unwrap()); + db.commit(&overlay.insert(&h_12, 1, &H256::default(), c_12).unwrap()); + db.commit(&overlay.insert(&h_21, 2, &h_11, c_21).unwrap()); overlay.apply_pending(); overlay.pin(&h_21); let mut commit = CommitSet::default(); - overlay.canonicalize::(&h_12, &mut commit).unwrap(); + overlay.canonicalize(&h_12, &mut commit).unwrap(); db.commit(&commit); overlay.apply_pending(); // 1_1 and 2_1 should be both pinned @@ -1141,18 +1119,14 @@ mod tests { let h21 = H256::random(); let mut db = make_db(&[]); let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); - db.commit( - &overlay - .insert::(&root, 10, &H256::default(), make_changeset(&[], &[])) - .unwrap(), - ); - db.commit(&overlay.insert::(&h1, 11, &root, make_changeset(&[1], &[])).unwrap()); - db.commit(&overlay.insert::(&h2, 11, &root, make_changeset(&[2], &[])).unwrap()); - db.commit(&overlay.insert::(&h11, 12, &h1, make_changeset(&[11], &[])).unwrap()); - db.commit(&overlay.insert::(&h21, 12, &h2, make_changeset(&[21], &[])).unwrap()); + db.commit(&overlay.insert(&root, 10, &H256::default(), make_changeset(&[], &[])).unwrap()); + db.commit(&overlay.insert(&h1, 11, &root, make_changeset(&[1], &[])).unwrap()); + db.commit(&overlay.insert(&h2, 11, &root, make_changeset(&[2], &[])).unwrap()); + db.commit(&overlay.insert(&h11, 12, &h1, make_changeset(&[11], &[])).unwrap()); + db.commit(&overlay.insert(&h21, 12, &h2, make_changeset(&[21], &[])).unwrap()); let mut commit = CommitSet::default(); - overlay.canonicalize::(&root, &mut commit).unwrap(); - overlay.canonicalize::(&h2, &mut commit).unwrap(); // h11 should stay in the DB + overlay.canonicalize(&root, &mut commit).unwrap(); + overlay.canonicalize(&h2, &mut commit).unwrap(); // h11 should stay in the DB db.commit(&commit); overlay.apply_pending(); assert_eq!(overlay.levels.len(), 1); @@ -1166,7 +1140,7 @@ mod tests { assert!(contains(&overlay, 21)); let mut commit = CommitSet::default(); - overlay.canonicalize::(&h21, &mut commit).unwrap(); // h11 should stay in the DB + overlay.canonicalize(&h21, &mut commit).unwrap(); // h11 should stay in the DB db.commit(&commit); overlay.apply_pending(); assert!(!contains(&overlay, 21)); @@ -1183,25 +1157,21 @@ mod tests { let h21 = H256::random(); let mut db = make_db(&[]); let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); - db.commit( - &overlay - .insert::(&root, 10, &H256::default(), make_changeset(&[], &[])) - .unwrap(), - ); - db.commit(&overlay.insert::(&h1, 11, &root, make_changeset(&[1], &[])).unwrap()); - db.commit(&overlay.insert::(&h2, 11, &root, make_changeset(&[2], &[])).unwrap()); - db.commit(&overlay.insert::(&h11, 12, &h1, make_changeset(&[11], &[])).unwrap()); - db.commit(&overlay.insert::(&h21, 12, &h2, make_changeset(&[21], &[])).unwrap()); + db.commit(&overlay.insert(&root, 10, &H256::default(), make_changeset(&[], &[])).unwrap()); + db.commit(&overlay.insert(&h1, 11, &root, make_changeset(&[1], &[])).unwrap()); + db.commit(&overlay.insert(&h2, 11, &root, make_changeset(&[2], &[])).unwrap()); + db.commit(&overlay.insert(&h11, 12, &h1, make_changeset(&[11], &[])).unwrap()); + db.commit(&overlay.insert(&h21, 12, &h2, make_changeset(&[21], &[])).unwrap()); let mut commit = CommitSet::default(); - overlay.canonicalize::(&root, &mut commit).unwrap(); - overlay.canonicalize::(&h2, &mut commit).unwrap(); // h11 should stay in the DB + overlay.canonicalize(&root, &mut commit).unwrap(); + overlay.canonicalize(&h2, &mut commit).unwrap(); // h11 should stay in the DB db.commit(&commit); overlay.apply_pending(); // add another block at top level. It should reuse journal index 0 of previously discarded // block let h22 = H256::random(); - db.commit(&overlay.insert::(&h22, 12, &h2, make_changeset(&[22], &[])).unwrap()); + db.commit(&overlay.insert(&h22, 12, &h2, make_changeset(&[22], &[])).unwrap()); assert_eq!(overlay.levels[0].blocks[0].journal_index, 1); assert_eq!(overlay.levels[0].blocks[1].journal_index, 0); @@ -1221,15 +1191,11 @@ mod tests { let h21 = H256::random(); let mut db = make_db(&[]); let mut overlay = NonCanonicalOverlay::::new(&db).unwrap(); - db.commit( - &overlay - .insert::(&root, 10, &H256::default(), make_changeset(&[], &[])) - .unwrap(), - ); - db.commit(&overlay.insert::(&h1, 11, &root, make_changeset(&[1], &[])).unwrap()); - db.commit(&overlay.insert::(&h2, 11, &root, make_changeset(&[2], &[])).unwrap()); - db.commit(&overlay.insert::(&h11, 12, &h1, make_changeset(&[11], &[])).unwrap()); - db.commit(&overlay.insert::(&h21, 12, &h2, make_changeset(&[21], &[])).unwrap()); + db.commit(&overlay.insert(&root, 10, &H256::default(), make_changeset(&[], &[])).unwrap()); + db.commit(&overlay.insert(&h1, 11, &root, make_changeset(&[1], &[])).unwrap()); + db.commit(&overlay.insert(&h2, 11, &root, make_changeset(&[2], &[])).unwrap()); + db.commit(&overlay.insert(&h11, 12, &h1, make_changeset(&[11], &[])).unwrap()); + db.commit(&overlay.insert(&h21, 12, &h2, make_changeset(&[21], &[])).unwrap()); assert!(overlay.remove(&h1).is_none()); assert!(overlay.remove(&h2).is_none()); assert_eq!(overlay.levels.len(), 3); From 141a32e2994bf26e7f09d0616c73e379344fb2d8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 May 2022 12:47:50 +0200 Subject: [PATCH 195/484] Bump pretty_assertions from 1.0.0 to 1.2.1 (#11360) Bumps [pretty_assertions](https://github.com/colin-kiegel/rust-pretty-assertions) from 1.0.0 to 1.2.1. - [Release notes](https://github.com/colin-kiegel/rust-pretty-assertions/releases) - [Changelog](https://github.com/colin-kiegel/rust-pretty-assertions/blob/main/CHANGELOG.md) - [Commits](https://github.com/colin-kiegel/rust-pretty-assertions/compare/v1.0.0...v1.2.1) --- updated-dependencies: - dependency-name: pretty_assertions dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- frame/support/Cargo.toml | 2 +- frame/support/test/Cargo.toml | 2 +- primitives/state-machine/Cargo.toml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 14d1a547a8073..1e75fc0af75e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7318,9 +7318,9 @@ dependencies = [ [[package]] name = "pretty_assertions" -version = "1.0.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0cfe1b2403f172ba0f234e500906ee0a3e493fb81092dac23ebefe129301cc" +checksum = "c89f989ac94207d048d92db058e4f6ec7342b0971fc58d1271ca148b799b3563" dependencies = [ "ansi_term", "ctor", diff --git a/frame/support/Cargo.toml b/frame/support/Cargo.toml index 712570696b9eb..4132b9f98ebca 100644 --- a/frame/support/Cargo.toml +++ b/frame/support/Cargo.toml @@ -39,7 +39,7 @@ k256 = { version = "0.10.4", default-features = false, features = ["ecdsa"] } [dev-dependencies] assert_matches = "1.3.0" -pretty_assertions = "1.0.0" +pretty_assertions = "1.2.1" frame-system = { version = "4.0.0-dev", path = "../system" } parity-util-mem = { version = "0.11.0", default-features = false, features = ["primitive-types"] } diff --git a/frame/support/test/Cargo.toml b/frame/support/test/Cargo.toml index f5f690a0468af..4aa0ff7d5a347 100644 --- a/frame/support/test/Cargo.toml +++ b/frame/support/test/Cargo.toml @@ -24,7 +24,7 @@ sp-core = { version = "6.0.0", default-features = false, path = "../../../primit sp-std = { version = "4.0.0", default-features = false, path = "../../../primitives/std" } sp-version = { version = "5.0.0", default-features = false, path = "../../../primitives/version" } trybuild = { version = "1.0.60", features = [ "diff" ] } -pretty_assertions = "1.0.0" +pretty_assertions = "1.2.1" rustversion = "1.0.6" frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } # The "std" feature for this pallet is never activated on purpose, in order to test construct_runtime error message diff --git a/primitives/state-machine/Cargo.toml b/primitives/state-machine/Cargo.toml index 7ee9fe98877a6..58fa3be514e4c 100644 --- a/primitives/state-machine/Cargo.toml +++ b/primitives/state-machine/Cargo.toml @@ -32,7 +32,7 @@ sp-trie = { version = "6.0.0", default-features = false, path = "../trie" } [dev-dependencies] hex-literal = "0.3.4" -pretty_assertions = "1.0.0" +pretty_assertions = "1.2.1" rand = "0.7.2" sp-runtime = { version = "6.0.0", path = "../runtime" } From 02b88282506a7227d6ac5cd1f355548a7b1e1e6a Mon Sep 17 00:00:00 2001 From: Sergejs Kostjucenko <85877331+sergejparity@users.noreply.github.com> Date: Fri, 6 May 2022 15:39:03 +0300 Subject: [PATCH 196/484] update approval rules (#11368) --- .github/pr-custom-review.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/pr-custom-review.yml b/.github/pr-custom-review.yml index 518faf15a6113..769ac1e110d33 100644 --- a/.github/pr-custom-review.yml +++ b/.github/pr-custom-review.yml @@ -6,11 +6,22 @@ action-review-team: ci rules: - name: Core developers check_type: changed_files - condition: .* + condition: + include: .* + # excluding files from 'CI team' rules + exclude: ^\.gitlab-ci\.yml|^scripts/ci/.*|^\.github/.* min_approvals: 2 teams: - core-devs + - name: CI team + check_type: changed_files + condition: + include: ^\.gitlab-ci\.yml|^scripts/ci/.*|^\.github/.* + min_approvals: 2 + teams: + - ci + prevent-review-request: teams: - core-devs From c58b81428f580ae9755310edb8ade35688f62210 Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Fri, 6 May 2022 14:55:15 +0200 Subject: [PATCH 197/484] Break the ref cycle (#11371) --- bin/node/cli/tests/running_the_node_and_interrupt.rs | 7 +++++++ client/consensus/babe/src/lib.rs | 8 ++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/bin/node/cli/tests/running_the_node_and_interrupt.rs b/bin/node/cli/tests/running_the_node_and_interrupt.rs index ecdb4d7671b01..35f0fc106613c 100644 --- a/bin/node/cli/tests/running_the_node_and_interrupt.rs +++ b/bin/node/cli/tests/running_the_node_and_interrupt.rs @@ -38,6 +38,7 @@ async fn running_the_node_works_and_can_be_interrupted() { Command::new(cargo_bin("substrate")) .args(&["--dev", "-d"]) .arg(base_path.path()) + .arg("--db=paritydb") .arg("--no-hardware-benchmarks") .spawn() .unwrap(), @@ -52,6 +53,12 @@ async fn running_the_node_works_and_can_be_interrupted() { "the process must exit gracefully after signal {}", signal, ); + // Check if the database was closed gracefully. If it was not, + // there may exist a ref cycle that prevents the Client from being dropped properly. + // + // parity-db only writes the stats file on clean shutdown. + let stats_file = base_path.path().join("chains/dev/paritydb/full/stats.txt"); + assert!(std::path::Path::exists(&stats_file)); } run_command_and_kill(SIGINT).await; diff --git a/client/consensus/babe/src/lib.rs b/client/consensus/babe/src/lib.rs index 490fdfb174311..683df9ddacd62 100644 --- a/client/consensus/babe/src/lib.rs +++ b/client/consensus/babe/src/lib.rs @@ -1783,9 +1783,13 @@ where // startup rather than waiting until importing the next epoch change block. prune_finalized(client.clone(), &mut epoch_changes.shared_data())?; - let client_clone = client.clone(); + let client_weak = Arc::downgrade(&client); let on_finality = move |summary: &FinalityNotification| { - aux_storage_cleanup(client_clone.as_ref(), summary) + if let Some(client) = client_weak.upgrade() { + aux_storage_cleanup(client.as_ref(), summary) + } else { + Default::default() + } }; client.register_finality_action(Box::new(on_finality)); From ab8570ca80b94d0506229a7dc587be11c5309539 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Fri, 6 May 2022 14:21:54 +0100 Subject: [PATCH 198/484] Rename nomination pools pallet id (#11361) --- bin/node/runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 660ba7ab86229..db4539b82f31e 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -707,7 +707,7 @@ impl pallet_bags_list::Config for Runtime { parameter_types! { pub const PostUnbondPoolsWindow: u32 = 4; - pub const NominationPoolsPalletId: PalletId = PalletId(*b"py/npols"); + pub const NominationPoolsPalletId: PalletId = PalletId(*b"py/nopls"); } use sp_runtime::traits::Convert; From 221515706d125aa4f5f4b049b593ce45388dc805 Mon Sep 17 00:00:00 2001 From: cheme Date: Fri, 6 May 2022 15:22:39 +0200 Subject: [PATCH 199/484] Parity-db `Change` missing implementation. (#11049) * support for release as in kvdb (only if no rc). * Start impl * minimal implementation for paritydb rc * Update client/db/src/parity_db.rs Co-authored-by: Arkadiy Paronyan * Update client/db/src/parity_db.rs Co-authored-by: Arkadiy Paronyan * Update client/db/src/parity_db.rs Co-authored-by: Arkadiy Paronyan * Commit not panicking in DbAdapter * errors from string * update parity db version Co-authored-by: Arkadiy Paronyan --- Cargo.lock | 4 +-- client/db/Cargo.toml | 2 +- client/db/src/parity_db.rs | 54 +++++++++++++++++++++++++++++++++----- 3 files changed, 51 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1e75fc0af75e5..907c917d723a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6832,9 +6832,9 @@ dependencies = [ [[package]] name = "parity-db" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e73cd0b0a78045276b19eaae8eaaa20e44a1da9a0217ff934a810d9492ae701" +checksum = "55a7901b85874402471e131de3332dde0e51f38432c69a3853627c8e25433048" dependencies = [ "blake2-rfc", "crc32fast", diff --git a/client/db/Cargo.toml b/client/db/Cargo.toml index 3c20cec72c695..e548887e56fe2 100644 --- a/client/db/Cargo.toml +++ b/client/db/Cargo.toml @@ -22,7 +22,7 @@ kvdb-memorydb = "0.11.0" kvdb-rocksdb = { version = "0.15.2", optional = true } linked-hash-map = "0.5.4" log = "0.4.16" -parity-db = { version = "0.3.12", optional = true } +parity-db = { version = "0.3.13", optional = true } parking_lot = "0.12.0" sc-client-api = { version = "4.0.0-dev", path = "../api" } sc-state-db = { version = "0.10.0-dev", path = "../state-db" } diff --git a/client/db/src/parity_db.rs b/client/db/src/parity_db.rs index f88e6f2e91678..7ce1d6a683401 100644 --- a/client/db/src/parity_db.rs +++ b/client/db/src/parity_db.rs @@ -86,15 +86,57 @@ pub fn open>( Ok(std::sync::Arc::new(DbAdapter(db))) } +fn ref_counted_column(col: u32) -> bool { + col == columns::TRANSACTION || col == columns::STATE +} + impl> Database for DbAdapter { fn commit(&self, transaction: Transaction) -> Result<(), DatabaseError> { - handle_err(self.0.commit(transaction.0.into_iter().map(|change| match change { - Change::Set(col, key, value) => (col as u8, key, Some(value)), - Change::Remove(col, key) => (col as u8, key, None), - _ => unimplemented!(), - }))); + let mut not_ref_counted_column = Vec::new(); + let result = self.0.commit(transaction.0.into_iter().filter_map(|change| { + Some(match change { + Change::Set(col, key, value) => (col as u8, key, Some(value)), + Change::Remove(col, key) => (col as u8, key, None), + Change::Store(col, key, value) => + if ref_counted_column(col) { + (col as u8, key.as_ref().to_vec(), Some(value)) + } else { + if !not_ref_counted_column.contains(&col) { + not_ref_counted_column.push(col); + } + return None + }, + Change::Reference(col, key) => + if ref_counted_column(col) { + // FIXME accessing value is not strictly needed, optimize this in parity-db. + let value = >::get(self, col, key.as_ref()); + (col as u8, key.as_ref().to_vec(), value) + } else { + if !not_ref_counted_column.contains(&col) { + not_ref_counted_column.push(col); + } + return None + }, + Change::Release(col, key) => + if ref_counted_column(col) { + (col as u8, key.as_ref().to_vec(), None) + } else { + if !not_ref_counted_column.contains(&col) { + not_ref_counted_column.push(col); + } + return None + }, + }) + })); + + if not_ref_counted_column.len() > 0 { + return Err(DatabaseError(Box::new(parity_db::Error::InvalidInput(format!( + "Ref counted operation on non ref counted columns {:?}", + not_ref_counted_column + ))))) + } - Ok(()) + result.map_err(|e| DatabaseError(Box::new(e))) } fn get(&self, col: ColumnId, key: &[u8]) -> Option> { From 52388643d3659286a9154d71f8e8a01b9cfe19f8 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Fri, 6 May 2022 14:25:07 +0100 Subject: [PATCH 200/484] allow defensive operations to take a proof (#11353) * allow defensive operations to take a proof * Update frame/support/src/traits/misc.rs Co-authored-by: Oliver Tale-Yazdi * Update frame/bags-list/src/list/mod.rs Co-authored-by: Keith Yeung * Update frame/support/src/traits/misc.rs Co-authored-by: Keith Yeung * Update frame/support/src/traits/misc.rs Co-authored-by: Keith Yeung * Fix build * fix build again * fmt Co-authored-by: Oliver Tale-Yazdi Co-authored-by: Keith Yeung --- frame/bags-list/src/list/mod.rs | 4 ++-- frame/staking/src/slashing.rs | 6 +++--- frame/support/src/traits/misc.rs | 31 +++++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/frame/bags-list/src/list/mod.rs b/frame/bags-list/src/list/mod.rs index f93bfca1081e9..2e65c3be25b24 100644 --- a/frame/bags-list/src/list/mod.rs +++ b/frame/bags-list/src/list/mod.rs @@ -443,8 +443,8 @@ impl, I: 'static> List { // remove the heavier node from this list. Note that this removes the node from storage and // decrements the node counter. - // defensive: both nodes have been checked to exist. - let _ = Self::remove(&heavier_id).defensive(); + let _ = + Self::remove(&heavier_id).defensive_proof("both nodes have been checked to exist; qed"); // re-fetch `lighter_node` from storage since it may have been updated when `heavier_node` // was removed. diff --git a/frame/staking/src/slashing.rs b/frame/staking/src/slashing.rs index 0d55eb7fe0ddb..25954a3699b31 100644 --- a/frame/staking/src/slashing.rs +++ b/frame/staking/src/slashing.rs @@ -56,7 +56,7 @@ use crate::{ use codec::{Decode, Encode}; use frame_support::{ ensure, - traits::{Currency, Get, Imbalance, OnUnbalanced}, + traits::{Currency, Defensive, Get, Imbalance, OnUnbalanced}, }; use scale_info::TypeInfo; use sp_runtime::{ @@ -600,8 +600,8 @@ pub fn do_slash( slashed_imbalance: &mut NegativeImbalanceOf, slash_era: EraIndex, ) { - let controller = match >::bonded(stash) { - None => return, // defensive: should always exist. + let controller = match >::bonded(stash).defensive() { + None => return, Some(c) => c, }; diff --git a/frame/support/src/traits/misc.rs b/frame/support/src/traits/misc.rs index b67eac2fbe7bc..03420f64dd55b 100644 --- a/frame/support/src/traits/misc.rs +++ b/frame/support/src/traits/misc.rs @@ -50,6 +50,16 @@ macro_rules! defensive { $error ); debug_assert!(false, "{}: {:?}", $crate::traits::DEFENSIVE_OP_INTERNAL_ERROR, $error); + }; + ($error:tt, $proof:tt) => { + frame_support::log::error!( + target: "runtime", + "{}: {:?}: {:?}", + $crate::traits::DEFENSIVE_OP_PUBLIC_ERROR, + $error, + $proof, + ); + debug_assert!(false, "{}: {:?}: {:?}", $crate::traits::DEFENSIVE_OP_INTERNAL_ERROR, $error, $proof); } } @@ -102,6 +112,10 @@ pub trait Defensive { /// } /// ``` fn defensive(self) -> Self; + + /// Same as [`Defensive::defensive`], but it takes a proof as input, and displays it if the + /// defensive operation has been triggered. + fn defensive_proof(self, proof: &'static str) -> Self; } /// Subset of methods similar to [`Defensive`] that can only work for a `Result`. @@ -184,6 +198,13 @@ impl Defensive for Option { }, } } + + fn defensive_proof(self, proof: &'static str) -> Self { + if self.is_none() { + defensive!(proof); + } + self + } } impl Defensive for Result { @@ -229,6 +250,16 @@ impl Defensive for Result { }, } } + + fn defensive_proof(self, proof: &'static str) -> Self { + match self { + Ok(inner) => Ok(inner), + Err(e) => { + defensive!(e, proof); + Err(e) + }, + } + } } impl DefensiveResult for Result { From 340e14ab7aa4cfc258db859980034a50caa5283c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Mon, 9 May 2022 11:25:14 +0200 Subject: [PATCH 201/484] contracts: Prevent PoV attack vector with minimal viable solution (#11372) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add ContractAccessWeight * Apply suggestions from code review Co-authored-by: Michael Müller Co-authored-by: Michael Müller --- bin/node/runtime/src/lib.rs | 8 +++-- frame/contracts/src/lib.rs | 50 ++++++++++++++++++++++++-- frame/contracts/src/tests.rs | 5 +-- frame/contracts/src/wasm/code_cache.rs | 10 ++++-- 4 files changed, 63 insertions(+), 10 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index db4539b82f31e..fee288cb2b7f8 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1059,8 +1059,11 @@ parameter_types! { pub const DepositPerByte: Balance = deposit(0, 1); pub const MaxValueSize: u32 = 16 * 1024; // The lazy deletion runs inside on_initialize. - pub DeletionWeightLimit: Weight = AVERAGE_ON_INITIALIZE_RATIO * - RuntimeBlockWeights::get().max_block; + pub DeletionWeightLimit: Weight = RuntimeBlockWeights::get() + .per_class + .get(DispatchClass::Normal) + .max_total + .unwrap_or(RuntimeBlockWeights::get().max_block); // The weight needed for decoding the queue should be less or equal than a fifth // of the overall weight dedicated to the lazy deletion. pub DeletionQueueDepth: u32 = ((DeletionWeightLimit::get() / ( @@ -1093,6 +1096,7 @@ impl pallet_contracts::Config for Runtime { type DeletionWeightLimit = DeletionWeightLimit; type Schedule = Schedule; type AddressGenerator = pallet_contracts::DefaultAddressGenerator; + type ContractAccessWeight = pallet_contracts::DefaultContractAccessWeight; } impl pallet_sudo::Config for Runtime { diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index 4edf43a672152..0ae326f6c1e8f 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -115,9 +115,9 @@ use frame_support::{ dispatch::Dispatchable, ensure, traits::{Contains, Currency, Get, Randomness, ReservableCurrency, StorageVersion, Time}, - weights::{GetDispatchInfo, Pays, PostDispatchInfo, Weight}, + weights::{DispatchClass, GetDispatchInfo, Pays, PostDispatchInfo, Weight}, }; -use frame_system::Pallet as System; +use frame_system::{limits::BlockWeights, Pallet as System}; use pallet_contracts_primitives::{ Code, CodeUploadResult, CodeUploadReturnValue, ContractAccessError, ContractExecResult, ContractInstantiateResult, ExecReturnValue, GetStorageResult, InstantiateReturnValue, @@ -126,7 +126,7 @@ use pallet_contracts_primitives::{ use scale_info::TypeInfo; use sp_core::{crypto::UncheckedFrom, Bytes}; use sp_runtime::traits::{Convert, Hash, Saturating, StaticLookup}; -use sp_std::{fmt::Debug, prelude::*}; +use sp_std::{fmt::Debug, marker::PhantomData, prelude::*}; type CodeHash = ::Hash; type TrieId = Vec; @@ -193,6 +193,29 @@ where } } +/// A conservative implementation to be used for [`pallet::Config::ContractAccessWeight`]. +/// +/// This derives the weight from the [`BlockWeights`] passed as `B` and the `maxPovSize` passed +/// as `P`. The default value for `P` is the `maxPovSize` used by Polkadot and Kusama. +/// +/// It simply charges from the weight meter pro rata: If loading the contract code would consume +/// 50% of the max storage proof then this charges 50% of the max block weight. +pub struct DefaultContractAccessWeight, const P: u32 = 5_242_880>( + PhantomData, +); + +impl, const P: u32> Get for DefaultContractAccessWeight { + fn get() -> Weight { + let block_weights = B::get(); + block_weights + .per_class + .get(DispatchClass::Normal) + .max_total + .unwrap_or(block_weights.max_block) / + Weight::from(P) + } +} + #[frame_support::pallet] pub mod pallet { use super::*; @@ -297,6 +320,27 @@ pub mod pallet { #[pallet::constant] type DepositPerByte: Get>; + /// The weight per byte of code that is charged when loading a contract from storage. + /// + /// Currently, FRAME only charges fees for computation incurred but not for PoV + /// consumption caused for storage access. This is usually not exploitable because + /// accessing storage carries some substantial weight costs, too. However in case + /// of contract code very much PoV consumption can be caused while consuming very little + /// computation. This could be used to keep the chain busy without paying the + /// proper fee for it. Until this is resolved we charge from the weight meter for + /// contract access. + /// + /// For more information check out: + /// + /// [`DefaultContractAccessWeight`] is a safe default to be used for polkadot or kusama + /// parachains. + /// + /// # Note + /// + /// This is only relevant for parachains. Set to zero in case of a standalone chain. + #[pallet::constant] + type ContractAccessWeight: Get; + /// The amount of balance a caller has to pay for each storage item. /// /// # Note diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index eaec4df698e5a..407e1e999dde1 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -24,8 +24,8 @@ use crate::{ storage::Storage, wasm::{PrefabWasmModule, ReturnCode as RuntimeReturnCode}, weights::WeightInfo, - BalanceOf, Code, CodeStorage, Config, ContractInfoOf, DefaultAddressGenerator, Error, Pallet, - Schedule, + BalanceOf, Code, CodeStorage, Config, ContractInfoOf, DefaultAddressGenerator, + DefaultContractAccessWeight, Error, Pallet, Schedule, }; use assert_matches::assert_matches; use codec::Encode; @@ -287,6 +287,7 @@ impl Config for Test { type DepositPerByte = DepositPerByte; type DepositPerItem = DepositPerItem; type AddressGenerator = DefaultAddressGenerator; + type ContractAccessWeight = DefaultContractAccessWeight; } pub const ALICE: AccountId32 = AccountId32::new([1u8; 32]); diff --git a/frame/contracts/src/wasm/code_cache.rs b/frame/contracts/src/wasm/code_cache.rs index ca91fa3131474..b194c283e90cd 100644 --- a/frame/contracts/src/wasm/code_cache.rs +++ b/frame/contracts/src/wasm/code_cache.rs @@ -38,7 +38,7 @@ use crate::{ use frame_support::{ dispatch::{DispatchError, DispatchResult}, ensure, - traits::ReservableCurrency, + traits::{Get, ReservableCurrency}, }; use sp_core::crypto::UncheckedFrom; use sp_runtime::traits::BadOrigin; @@ -216,8 +216,12 @@ impl Token for CodeToken { // size of the contract. match *self { Reinstrument(len) => T::WeightInfo::reinstrument(len), - Load(len) => T::WeightInfo::call_with_code_per_byte(len) - .saturating_sub(T::WeightInfo::call_with_code_per_byte(0)), + Load(len) => { + let computation = T::WeightInfo::call_with_code_per_byte(len) + .saturating_sub(T::WeightInfo::call_with_code_per_byte(0)); + let bandwith = T::ContractAccessWeight::get().saturating_mul(len.into()); + computation.max(bandwith) + }, } } } From 849c7b827738e988f2ee7cc6822e56bbfb51b280 Mon Sep 17 00:00:00 2001 From: Alexander Gryaznov Date: Tue, 10 May 2022 11:07:13 +0300 Subject: [PATCH 202/484] stabilize `seal_contains_storage` (`seal0`) and `seal_set_storage` (`seal1`) (#11378) --- frame/contracts/src/benchmarking/mod.rs | 10 +++++----- frame/contracts/src/wasm/mod.rs | 6 ++---- frame/contracts/src/wasm/runtime.rs | 6 ++---- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs index 6411bcb2b589a..194f97f8c878d 100644 --- a/frame/contracts/src/benchmarking/mod.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -896,7 +896,7 @@ benchmarks! { let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "__unstable__", + module: "seal1", name: "seal_set_storage", params: vec![ValueType::I32, ValueType::I32, ValueType::I32], return_type: Some(ValueType::I32), @@ -942,7 +942,7 @@ benchmarks! { let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "__unstable__", + module: "seal1", name: "seal_set_storage", params: vec![ValueType::I32, ValueType::I32, ValueType::I32], return_type: Some(ValueType::I32), @@ -988,7 +988,7 @@ benchmarks! { let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "__unstable__", + module: "seal1", name: "seal_set_storage", params: vec![ValueType::I32, ValueType::I32, ValueType::I32], return_type: Some(ValueType::I32), @@ -1233,7 +1233,7 @@ benchmarks! { let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "__unstable__", + module: "seal0", name: "seal_contains_storage", params: vec![ValueType::I32], return_type: Some(ValueType::I32), @@ -1278,7 +1278,7 @@ benchmarks! { let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "__unstable__", + module: "seal0", name: "seal_contains_storage", params: vec![ValueType::I32], return_type: Some(ValueType::I32), diff --git a/frame/contracts/src/wasm/mod.rs b/frame/contracts/src/wasm/mod.rs index 7257fbea6336a..d710cad199f93 100644 --- a/frame/contracts/src/wasm/mod.rs +++ b/frame/contracts/src/wasm/mod.rs @@ -819,13 +819,12 @@ mod tests { } #[test] - #[cfg(feature = "unstable-interface")] fn contains_storage_works() { const CODE: &str = r#" (module (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) (import "seal0" "seal_input" (func $seal_input (param i32 i32))) - (import "__unstable__" "seal_contains_storage" (func $seal_contains_storage (param i32) (result i32))) + (import "seal0" "seal_contains_storage" (func $seal_contains_storage (param i32) (result i32))) (import "env" "memory" (memory 1 1)) ;; [0, 4) size of input buffer (32 byte as we copy the key here) @@ -2163,13 +2162,12 @@ mod tests { } #[test] - #[cfg(feature = "unstable-interface")] fn set_storage_works() { const CODE: &str = r#" (module (import "seal0" "seal_input" (func $seal_input (param i32 i32))) (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) - (import "__unstable__" "seal_set_storage" (func $seal_set_storage (param i32 i32 i32) (result i32))) + (import "seal1" "seal_set_storage" (func $seal_set_storage (param i32 i32 i32) (result i32))) (import "env" "memory" (memory 1 1)) ;; 0x1000 = 4k in little endian diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index a6f42ba64a49c..11dfc77616e69 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -185,7 +185,6 @@ pub enum RuntimeCosts { /// Weight of calling `seal_clear_storage` per cleared byte. ClearStorage(u32), /// Weight of calling `seal_contains_storage` per byte of the checked item. - #[cfg(feature = "unstable-interface")] ContainsStorage(u32), /// Weight of calling `seal_get_storage` with the specified size in storage. GetStorage(u32), @@ -269,7 +268,6 @@ impl RuntimeCosts { ClearStorage(len) => s .clear_storage .saturating_add(s.clear_storage_per_byte.saturating_mul(len.into())), - #[cfg(feature = "unstable-interface")] ContainsStorage(len) => s .contains_storage .saturating_add(s.contains_storage_per_byte.saturating_mul(len.into())), @@ -889,7 +887,7 @@ define_env!(Env, , // // Returns the size of the pre-existing value at the specified key if any. Otherwise // `SENTINEL` is returned as a sentinel value. - [__unstable__] seal_set_storage(ctx, key_ptr: u32, value_ptr: u32, value_len: u32) -> u32 => { + [seal1] seal_set_storage(ctx, key_ptr: u32, value_ptr: u32, value_len: u32) -> u32 => { ctx.set_storage(key_ptr, value_ptr, value_len) }, @@ -951,7 +949,7 @@ define_env!(Env, , // // Returns the size of the pre-existing value at the specified key if any. Otherwise // `SENTINEL` is returned as a sentinel value. - [__unstable__] seal_contains_storage(ctx, key_ptr: u32) -> u32 => { + [seal0] seal_contains_storage(ctx, key_ptr: u32) -> u32 => { let charged = ctx.charge_gas(RuntimeCosts::ContainsStorage(ctx.ext.max_value_size()))?; let mut key: StorageKey = [0; 32]; ctx.read_sandbox_memory_into_buf(key_ptr, &mut key)?; From 9aed1ccc91a72a73eeaaaa89bfabb2d4f9beb9b2 Mon Sep 17 00:00:00 2001 From: David Date: Tue, 10 May 2022 10:52:19 +0200 Subject: [PATCH 203/484] jsonrpsee integration (#8783) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add tokio * No need to map CallError to CallError * jsonrpsee proc macros (#9673) * port error types to `JsonRpseeError` * migrate chain module to proc macro api * make it compile with proc macros * update branch * update branch * update to jsonrpsee master * port system rpc * port state rpc * port childstate & offchain * frame system rpc * frame transaction payment * bring back CORS hack to work with polkadot UI * port babe rpc * port manual seal rpc * port frame mmr rpc * port frame contracts rpc * port finality grandpa rpc * port sync state rpc * resolve a few TODO + no jsonrpc deps * Update bin/node/rpc-client/src/main.rs * Update bin/node/rpc-client/src/main.rs * Update bin/node/rpc-client/src/main.rs * Update bin/node/rpc-client/src/main.rs * Port over system_ rpc tests * Make it compile * Use prost 0.8 * Use prost 0.8 * Make it compile * Ignore more failing tests * Comment out WIP tests * fix nit in frame system api * Update lockfile * No more juggling tokio versions * No more wait_for_stop ? * Remove browser-testing * Arguments must be arrays * Use same argument names * Resolve todo: no wait_for_stop for WS server Add todo: is parse_rpc_result used? Cleanup imports * fmt * log * One test passes * update jsonrpsee * update jsonrpsee * cleanup rpc-servers crate * jsonrpsee: add host and origin filtering (#9787) * add access control in the jsonrpsee servers * use master * fix nits * rpc runtime_version safe * fix nits * fix grumbles * remove unused files * resolve some todos * jsonrpsee more cleanup (#9803) * more cleanup * resolve TODOs * fix some unwraps * remove type hints * update jsonrpsee * downgrade zeroize * pin jsonrpsee rev * remove unwrap nit * Comment out more tests that aren't ported * Comment out more tests * Fix tests after merge * Subscription test * Invalid nonce test * Pending exts * WIP removeExtrinsic test * Test remove_extrinsic * Make state test: should_return_storage work * Uncomment/fix the other non-subscription related state tests * test: author_insertKey * test: author_rotateKeys * Get rest of state tests passing * asyncify a little more * Add todo to note #msg change * Crashing test for has_session_keys * Fix error conversion to avoid stack overflows Port author_hasSessionKeys test fmt * test author_hasKey * Add two missing tests Add a check on the return type Add todos for James's concerns * RPC tests for state, author and system (#9859) * Fix test runner * Impl Default for SubscriptionTaskExecutor * Keep the minimul amount of code needed to compile tests * Re-instate `RpcSession` (for now) * cleanup * Port over RPC tests * Add tokio * No need to map CallError to CallError * Port over system_ rpc tests * Make it compile * Use prost 0.8 * Use prost 0.8 * Make it compile * Ignore more failing tests * Comment out WIP tests * Update lockfile * No more juggling tokio versions * No more wait_for_stop ? * Remove browser-testing * Arguments must be arrays * Use same argument names * Resolve todo: no wait_for_stop for WS server Add todo: is parse_rpc_result used? Cleanup imports * fmt * log * One test passes * Comment out more tests that aren't ported * Comment out more tests * Fix tests after merge * Subscription test * Invalid nonce test * Pending exts * WIP removeExtrinsic test * Test remove_extrinsic * Make state test: should_return_storage work * Uncomment/fix the other non-subscription related state tests * test: author_insertKey * test: author_rotateKeys * Get rest of state tests passing * asyncify a little more * Add todo to note #msg change * Crashing test for has_session_keys * Fix error conversion to avoid stack overflows Port author_hasSessionKeys test fmt * test author_hasKey * Add two missing tests Add a check on the return type Add todos for James's concerns * offchain rpc tests * Address todos * fmt Co-authored-by: James Wilson * fix drop in state test * update jsonrpsee * fix ignored system test * fix chain tests * remove some boiler plate * Port BEEFY RPC (#9883) * Merge master * Port beefy RPC (ty @niklas!) * trivial changes left over from merge * Remove unused code * Update jsonrpsee * fix build * make tests compile again * beefy update jsonrpsee * fix: respect rpc methods policy * update cargo.lock * update jsonrpsee * update jsonrpsee * downgrade error logs * update jsonrpsee * Fix typo * remove unused file * Better name * Port Babe RPC tests * Put docs back * Resolve todo * Port tests for System RPCs * Resolve todo * fix build * Updated jsonrpsee to current master * fix: port finality grandpa rpc tests * Move .into() outside of the match * more review grumbles * jsonrpsee: add `rpc handlers` back (#10245) * add back RpcHandlers * cargo fmt * fix docs * fix grumble: remove needless alloc * resolve TODO * fmt * Fix typo * grumble: Use constants based on BASE_ERROR * grumble: DRY whitelisted listening addresses grumble: s/JSONRPC/JSON-RPC/ * cleanup * grumbles: Making readers aware of the possibility of gaps * review grumbles * grumbles * remove notes from niklasad1 * Update `jsonrpsee` * fix: jsonrpsee features * jsonrpsee: fallback to random port in case the specified port failed (#10304) * jsonrpsee: fallback to random port * better comment * Update client/rpc-servers/src/lib.rs Co-authored-by: Maciej Hirsz <1096222+maciejhirsz@users.noreply.github.com> * Update client/rpc-servers/src/lib.rs Co-authored-by: Maciej Hirsz <1096222+maciejhirsz@users.noreply.github.com> * address grumbles * cargo fmt * addrs already slice Co-authored-by: Maciej Hirsz <1096222+maciejhirsz@users.noreply.github.com> * Update jsonrpsee to 092081a0a2b8904c6ebd2cd99e16c7bc13ffc3ae * lockfile * update jsonrpsee * fix warning * Don't fetch jsonrpsee from crates * make tests compile again * fix rpc tests * remove unused deps * update tokio * fix rpc tests again * fix: test runner `HttpServerBuilder::builder` fails unless it's called within tokio runtime * cargo fmt * grumbles: fix subscription aliases * make clippy happy * update remaining subscriptions alias * cleanup * cleanup * fix chain subscription: less boiler plate (#10285) * fix chain subscription: less boiler plate * fix bad merge * cargo fmt * Switch to jsonrpsee 0.5 * fix build * add missing features * fix nit: remove needless Box::pin * Integrate jsonrpsee metrics (#10395) * draft metrics impl * Use latest api * Add missing file * Http server metrics * cleanup * bump jsonrpsee * Remove `ServerMetrics` and use a single middleware for both connection counting (aka sessions) and call metrics. * fix build * remove needless Arc::clone * Update to jsonrpsee 0.6 * lolz * fix metrics * Revert "lolz" This reverts commit eed6c6a56e78d8e307b4950f4c52a1c3a2322ba1. * fix: in-memory rpc support subscriptions * commit Cargo.lock * Update tests to 0.7 * fix TODOs * ws server: generate subscriptionIDs as Strings Some libraries seems to expect the subscription IDs to be Strings, let's not break this in this PR. * Increase timeout * Port over tests * cleanup * Using error codes from the spec * fix clippy * cargo fmt * update jsonrpsee * fix nits * fix: rpc_query * enable custom subid gen through spawn_tasks * remove unsed deps * unify tokio deps * Revert "enable custom subid gen through spawn_tasks" This reverts commit 5c5eb70328fe39d154fdb55c56e637b4548cf470. * fix bad merge of `test-utils` * fix more nits * downgrade wasm-instrument to 0.1.0 * [jsonrpsee]: enable custom RPC subscription ID generatation (#10731) * enable custom subid gen through spawn_tasks * fix nits * Update client/service/src/builder.rs Co-authored-by: David * add Poc; needs jsonrpsee pr * update jsonrpsee * add re-exports * add docs Co-authored-by: David * cargo fmt * fmt * port RPC-API dev * Remove unused file * fix nit: remove async trait * fix doc links * fix merge nit: remove jsonrpc deps * kill namespace on rpc apis * companion for jsonrpsee v0.10 (#11158) * companion for jsonrpsee v0.10 * update versions v0.10.0 * add some fixes * spelling * fix spaces Co-authored-by: Niklas Adolfsson * send error before subs are closed * fix unsubscribe method names: chain * fix tests * jsonrpc server: print binded local address * grumbles: kill SubscriptionTaskExecutor * Update client/sync-state-rpc/src/lib.rs Co-authored-by: Bastian Köcher * Update client/rpc/src/chain/chain_full.rs Co-authored-by: Bastian Köcher * Update client/rpc/src/chain/chain_full.rs Co-authored-by: Bastian Köcher * sync-state-rpc: kill anyhow * no more anyhow * remove todo * jsonrpsee: fix bad params in subscriptions. (#11251) * update jsonrpsee * fix error responses * revert error codes * dont do weird stuff in drop impl * rpc servers: remove needless clone * Remove silly constants * chore: update jsonrpsee v0.12 * commit Cargo.lock * deps: downgrade git2 * feat: CLI flag max subscriptions per connection * metrics: use old logging format * fix: read WS address from substrate output (#11379) Co-authored-by: Niklas Adolfsson Co-authored-by: James Wilson Co-authored-by: Maciej Hirsz Co-authored-by: Maciej Hirsz <1096222+maciejhirsz@users.noreply.github.com> Co-authored-by: Bastian Köcher --- Cargo.lock | 1701 +++++------------ bin/node-template/node/Cargo.toml | 2 +- bin/node-template/node/src/rpc.rs | 23 +- bin/node-template/node/src/service.rs | 5 +- bin/node/cli/Cargo.toml | 1 + bin/node/cli/benches/block_production.rs | 4 + bin/node/cli/benches/transaction_pool.rs | 4 + bin/node/cli/src/service.rs | 10 +- bin/node/cli/tests/common.rs | 52 +- .../tests/running_the_node_and_interrupt.rs | 19 +- bin/node/cli/tests/temp_base_path_works.rs | 10 +- bin/node/rpc/Cargo.toml | 2 +- bin/node/rpc/src/lib.rs | 92 +- client/beefy/Cargo.toml | 2 +- client/beefy/rpc/Cargo.toml | 6 +- client/beefy/rpc/src/lib.rs | 308 ++- client/cli/src/commands/run_cmd.rs | 18 +- client/cli/src/config.rs | 14 + client/consensus/babe/rpc/Cargo.toml | 5 +- client/consensus/babe/rpc/src/lib.rs | 204 +- client/consensus/manual-seal/Cargo.toml | 4 +- client/consensus/manual-seal/src/error.rs | 34 +- client/consensus/manual-seal/src/rpc.rs | 83 +- client/finality-grandpa/rpc/Cargo.toml | 6 +- client/finality-grandpa/rpc/src/error.rs | 17 +- client/finality-grandpa/rpc/src/lib.rs | 345 ++-- client/rpc-api/Cargo.toml | 5 +- client/rpc-api/src/author/error.rs | 180 +- client/rpc-api/src/author/mod.rs | 70 +- client/rpc-api/src/chain/error.rs | 25 +- client/rpc-api/src/chain/mod.rs | 111 +- client/rpc-api/src/child_state/mod.rs | 59 +- client/rpc-api/src/dev/error.rs | 42 +- client/rpc-api/src/dev/mod.rs | 9 +- client/rpc-api/src/errors.rs | 28 - client/rpc-api/src/helpers.rs | 41 - client/rpc-api/src/lib.rs | 8 +- client/rpc-api/src/metadata.rs | 60 - client/rpc-api/src/offchain/error.rs | 20 +- client/rpc-api/src/offchain/mod.rs | 17 +- client/rpc-api/src/policy.rs | 24 +- client/rpc-api/src/state/error.rs | 34 +- client/rpc-api/src/state/mod.rs | 155 +- client/rpc-api/src/system/error.rs | 33 +- client/rpc-api/src/system/mod.rs | 95 +- client/rpc-servers/Cargo.toml | 6 +- client/rpc-servers/src/lib.rs | 340 ++-- client/rpc-servers/src/middleware.rs | 227 +-- client/rpc/Cargo.toml | 10 +- client/rpc/src/author/mod.rs | 133 +- client/rpc/src/author/tests.rs | 380 ++-- client/rpc/src/chain/chain_full.rs | 114 +- client/rpc/src/chain/mod.rs | 221 +-- client/rpc/src/chain/tests.rs | 261 ++- client/rpc/src/dev/mod.rs | 16 +- client/rpc/src/dev/tests.rs | 35 +- client/rpc/src/lib.rs | 37 +- client/rpc/src/offchain/mod.rs | 17 +- client/rpc/src/offchain/tests.rs | 9 +- client/rpc/src/state/mod.rs | 385 ++-- client/rpc/src/state/state_full.rs | 417 ++-- client/rpc/src/state/tests.rs | 450 ++--- client/rpc/src/system/mod.rs | 141 +- client/rpc/src/system/tests.rs | 250 ++- client/rpc/src/testing.rs | 33 +- client/service/Cargo.toml | 3 +- client/service/src/builder.rs | 190 +- client/service/src/config.rs | 12 + client/service/src/lib.rs | 274 ++- client/service/test/src/lib.rs | 4 + client/sync-state-rpc/Cargo.toml | 4 +- client/sync-state-rpc/src/lib.rs | 60 +- client/tracing/src/block/mod.rs | 2 +- client/transaction-pool/api/src/error.rs | 4 +- client/transaction-pool/api/src/lib.rs | 11 +- frame/bags-list/remote-tests/Cargo.toml | 1 - frame/contracts/rpc/Cargo.toml | 4 +- frame/contracts/rpc/src/lib.rs | 126 +- frame/merkle-mountain-range/rpc/Cargo.toml | 4 +- frame/merkle-mountain-range/rpc/src/lib.rs | 99 +- frame/state-trie-migration/Cargo.toml | 2 +- frame/transaction-payment/rpc/Cargo.toml | 4 +- frame/transaction-payment/rpc/src/lib.rs | 115 +- test-utils/client/src/lib.rs | 106 +- test-utils/runtime/src/lib.rs | 13 + utils/frame/remote-externalities/Cargo.toml | 2 +- .../rpc/state-trie-migration-rpc/Cargo.toml | 4 +- .../rpc/state-trie-migration-rpc/src/lib.rs | 33 +- utils/frame/rpc/support/Cargo.toml | 3 +- utils/frame/rpc/support/src/lib.rs | 56 +- utils/frame/rpc/system/Cargo.toml | 7 +- utils/frame/rpc/system/src/lib.rs | 229 ++- utils/frame/try-runtime/cli/Cargo.toml | 2 +- 93 files changed, 3781 insertions(+), 5062 deletions(-) delete mode 100644 client/rpc-api/src/errors.rs delete mode 100644 client/rpc-api/src/helpers.rs delete mode 100644 client/rpc-api/src/metadata.rs diff --git a/Cargo.lock b/Cargo.lock index 907c917d723a7..928be6b18fc15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,7 +70,7 @@ checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ "getrandom 0.2.3", "once_cell", - "version_check 0.9.2", + "version_check", ] [[package]] @@ -88,7 +88,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] @@ -225,21 +225,21 @@ dependencies = [ "concurrent-queue", "futures-lite", "libc", - "log 0.4.16", + "log", "once_cell", "parking", "polling", "slab", "socket2 0.4.4", "waker-fn", - "winapi 0.3.9", + "winapi", ] [[package]] name = "async-lock" -version = "2.3.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1996609732bde4a9988bc42125f55f2af5f3c36370e27c778d5191a4a1b63bfb" +checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6" dependencies = [ "event-listener", ] @@ -255,9 +255,9 @@ dependencies = [ [[package]] name = "async-process" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83137067e3a2a6a06d67168e49e68a0957d215410473a740cea95a2425c0b7c6" +checksum = "cf2c06e30a24e8c78a3987d07f0930edf76ef35e027e7bdb063fccafdad1f60c" dependencies = [ "async-io", "blocking", @@ -267,7 +267,7 @@ dependencies = [ "libc", "once_cell", "signal-hook", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -282,14 +282,14 @@ dependencies = [ "async-io", "async-lock", "async-process", - "crossbeam-utils 0.8.5", + "crossbeam-utils", "futures-channel", "futures-core", "futures-io", "futures-lite", "gloo-timers", "kv-log-macro", - "log 0.4.16", + "log", "memchr", "num_cpus", "once_cell", @@ -358,7 +358,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0de5164e5edbf51c45fb8c2d9664ae1c095cce1b265ecf7569093c0d66ef690" dependencies = [ - "bytes 1.1.0", + "bytes", "futures-sink", "futures-util", "memchr", @@ -388,7 +388,7 @@ checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -436,25 +436,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" -[[package]] -name = "base64" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643" -dependencies = [ - "byteorder", - "safemem", -] - -[[package]] -name = "base64" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" -dependencies = [ - "byteorder", -] - [[package]] name = "base64" version = "0.13.0" @@ -476,10 +457,10 @@ version = "4.0.0-dev" dependencies = [ "beefy-primitives", "fnv", - "futures 0.3.21", + "futures", "futures-timer", "hex", - "log 0.4.16", + "log", "parity-scale-codec", "parking_lot 0.12.0", "sc-chain-spec", @@ -519,12 +500,9 @@ version = "4.0.0-dev" dependencies = [ "beefy-gadget", "beefy-primitives", - "futures 0.3.21", - "jsonrpc-core", - "jsonrpc-core-client", - "jsonrpc-derive", - "jsonrpc-pubsub", - "log 0.4.16", + "futures", + "jsonrpsee", + "log", "parity-scale-codec", "parking_lot 0.12.0", "sc-rpc", @@ -535,6 +513,7 @@ dependencies = [ "sp-runtime", "substrate-test-runtime-client", "thiserror", + "tokio", ] [[package]] @@ -544,7 +523,7 @@ dependencies = [ "env_logger 0.9.0", "hex", "hex-literal", - "log 0.4.16", + "log", "tiny-keccak", ] @@ -814,16 +793,6 @@ version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" -[[package]] -name = "bytes" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" -dependencies = [ - "byteorder", - "iovec", -] - [[package]] name = "bytes" version = "1.1.0" @@ -966,7 +935,7 @@ dependencies = [ "num-integer", "num-traits", "time", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -1185,9 +1154,9 @@ dependencies = [ "cranelift-codegen-shared 0.76.0", "cranelift-entity 0.76.0", "gimli 0.25.0", - "log 0.4.16", + "log", "regalloc 0.0.31", - "smallvec 1.8.0", + "smallvec", "target-lexicon", ] @@ -1202,9 +1171,9 @@ dependencies = [ "cranelift-codegen-shared 0.82.3", "cranelift-entity 0.82.3", "gimli 0.26.1", - "log 0.4.16", + "log", "regalloc 0.0.34", - "smallvec 1.8.0", + "smallvec", "target-lexicon", ] @@ -1261,8 +1230,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "279afcc0d3e651b773f94837c3d581177b348c8d69e928104b2e9fccb226f921" dependencies = [ "cranelift-codegen 0.76.0", - "log 0.4.16", - "smallvec 1.8.0", + "log", + "smallvec", "target-lexicon", ] @@ -1273,8 +1242,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a006e3e32d80ce0e4ba7f1f9ddf66066d052a8c884a110b91d05404d6ce26dce" dependencies = [ "cranelift-codegen 0.82.3", - "log 0.4.16", - "smallvec 1.8.0", + "log", + "smallvec", "target-lexicon", ] @@ -1299,8 +1268,8 @@ dependencies = [ "cranelift-entity 0.82.3", "cranelift-frontend 0.82.3", "itertools", - "log 0.4.16", - "smallvec 1.8.0", + "log", + "smallvec", "wasmparser 0.83.0", "wasmtime-types", ] @@ -1325,7 +1294,7 @@ dependencies = [ "clap 2.34.0", "criterion-plot", "csv", - "futures 0.3.21", + "futures", "itertools", "lazy_static", "num-traits", @@ -1359,7 +1328,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.5", + "crossbeam-utils", ] [[package]] @@ -1370,7 +1339,7 @@ checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", - "crossbeam-utils 0.8.5", + "crossbeam-utils", ] [[package]] @@ -1380,23 +1349,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" dependencies = [ "cfg-if 1.0.0", - "crossbeam-utils 0.8.5", + "crossbeam-utils", "lazy_static", "memoffset", "scopeguard", ] -[[package]] -name = "crossbeam-utils" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" -dependencies = [ - "autocfg 1.0.1", - "cfg-if 0.1.10", - "lazy_static", -] - [[package]] name = "crossbeam-utils" version = "0.8.5" @@ -1706,7 +1664,7 @@ checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" dependencies = [ "libc", "redox_users 0.3.5", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -1717,7 +1675,7 @@ checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" dependencies = [ "libc", "redox_users 0.4.0", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -1728,7 +1686,7 @@ checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ "libc", "redox_users 0.4.0", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -1964,7 +1922,7 @@ checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" dependencies = [ "atty", "humantime 1.3.0", - "log 0.4.16", + "log", "regex", "termcolor", ] @@ -1975,7 +1933,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" dependencies = [ - "log 0.4.16", + "log", "regex", ] @@ -1987,7 +1945,7 @@ checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" dependencies = [ "atty", "humantime 2.1.0", - "log 0.4.16", + "log", "regex", "termcolor", ] @@ -2006,7 +1964,7 @@ checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" dependencies = [ "errno-dragonfly", "libc", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -2031,7 +1989,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e43f2f1833d64e33f15592464d6fdd70f349dda7b1a53088eb83cd94014008c5" dependencies = [ - "futures 0.3.21", + "futures", ] [[package]] @@ -2081,7 +2039,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fdbe0d94371f9ce939b555dd342d0686cc4c0cadbcd4b61d70af5ff97eb4126" dependencies = [ "env_logger 0.7.1", - "log 0.4.16", + "log", ] [[package]] @@ -2091,9 +2049,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9def033d8505edf199f6a5d07aa7e6d2d6185b164293b77f0efd108f4f3e11d" dependencies = [ "either", - "futures 0.3.21", + "futures", "futures-timer", - "log 0.4.16", + "log", "num-traits", "parity-scale-codec", "parking_lot 0.11.2", @@ -2138,21 +2096,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[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 = "3.0.0" @@ -2167,7 +2110,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" dependencies = [ "matches", - "percent-encoding 2.1.0", + "percent-encoding", ] [[package]] @@ -2178,7 +2121,7 @@ dependencies = [ "frame-system", "hex-literal", "linregress", - "log 0.4.16", + "log", "parity-scale-codec", "paste 1.0.6", "scale-info", @@ -2210,7 +2153,7 @@ dependencies = [ "kvdb", "lazy_static", "linked-hash-map", - "log 0.4.16", + "log", "memory-db", "parity-scale-codec", "prettytable-rs", @@ -2249,7 +2192,7 @@ dependencies = [ "frame-election-provider-support", "frame-support", "parity-scale-codec", - "proc-macro-crate 1.1.3", + "proc-macro-crate", "proc-macro2", "quote", "scale-info", @@ -2336,7 +2279,7 @@ dependencies = [ "frame-system", "impl-trait-for-tuples", "k256", - "log 0.4.16", + "log", "once_cell", "parity-scale-codec", "parity-util-mem", @@ -2344,7 +2287,7 @@ dependencies = [ "pretty_assertions", "scale-info", "serde", - "smallvec 1.8.0", + "smallvec", "sp-arithmetic", "sp-core", "sp-core-hashing-proc-macro", @@ -2374,7 +2317,7 @@ name = "frame-support-procedural-tools" version = "4.0.0-dev" dependencies = [ "frame-support-procedural-tools-derive", - "proc-macro-crate 1.1.3", + "proc-macro-crate", "proc-macro2", "quote", "syn", @@ -2440,7 +2383,7 @@ version = "4.0.0-dev" dependencies = [ "criterion", "frame-support", - "log 0.4.16", + "log", "parity-scale-codec", "scale-info", "serde", @@ -2495,7 +2438,7 @@ dependencies = [ "lazy_static", "libc", "libloading 0.5.2", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -2505,7 +2448,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" dependencies = [ "libc", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -2520,34 +2463,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" -[[package]] -name = "fuchsia-zircon" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -dependencies = [ - "bitflags", - "fuchsia-zircon-sys", -] - -[[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" - [[package]] name = "funty" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" -[[package]] -name = "futures" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" - [[package]] name = "futures" version = "0.3.21" @@ -2658,7 +2579,6 @@ version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" dependencies = [ - "futures 0.1.31", "futures-channel", "futures-core", "futures-io", @@ -2707,7 +2627,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" dependencies = [ "typenum", - "version_check 0.9.2", + "version_check", ] [[package]] @@ -2775,8 +2695,8 @@ dependencies = [ "bitflags", "libc", "libgit2-sys", - "log 0.4.16", - "url 2.2.1", + "log", + "url", ] [[package]] @@ -2794,7 +2714,7 @@ dependencies = [ "aho-corasick", "bstr", "fnv", - "log 0.4.16", + "log", "regex", ] @@ -2828,7 +2748,7 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f072413d126e57991455e0a922b31e4c8ba7c2ffbebf6b78b4f8521397d65cd" dependencies = [ - "bytes 1.1.0", + "bytes", "fnv", "futures-core", "futures-sink", @@ -2853,7 +2773,7 @@ version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d6a30320f094710245150395bc763ad23128d6a1ebbad7594dc4164b62c56b" dependencies = [ - "log 0.4.16", + "log", "pest", "pest_derive", "quick-error 2.0.0", @@ -2986,7 +2906,7 @@ checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" dependencies = [ "libc", "match_cfg", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -2995,7 +2915,7 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7245cd7449cc792608c3c8a9eaf69bd4eabbabf802713748fd739c98b82f0747" dependencies = [ - "bytes 1.1.0", + "bytes", "fnv", "itoa 0.4.8", ] @@ -3006,7 +2926,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60daa14be0e0786db0f03a9e57cb404c9d756eed2b6c62b9ea98ec5743ec75a9" dependencies = [ - "bytes 1.1.0", + "bytes", "http", "pin-project-lite 0.2.6", ] @@ -3038,32 +2958,13 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" -[[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.14.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7ec3e62bdc98a2f0393a5048e4c30ef659440ea6e0e572965103e72bd836f55" dependencies = [ - "bytes 1.1.0", + "bytes", "futures-channel", "futures-core", "futures-util", @@ -3089,8 +2990,8 @@ checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" dependencies = [ "ct-logs", "futures-util", - "hyper 0.14.16", - "log 0.4.16", + "hyper", + "log", "rustls 0.19.1", "rustls-native-certs 0.5.0", "tokio", @@ -3098,36 +2999,12 @@ dependencies = [ "webpki 0.21.4", ] -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes 1.1.0", - "hyper 0.14.16", - "native-tls", - "tokio", - "tokio-native-tls", -] - [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -[[package]] -name = "idna" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "idna" version = "0.2.3" @@ -3146,7 +3023,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cbc0fa01ffc752e9dbc72818cdb072cd028b86be5e09dd04c5a643704fe101a9" dependencies = [ "libc", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -3158,10 +3035,10 @@ dependencies = [ "async-io", "core-foundation", "fnv", - "futures 0.3.21", + "futures", "if-addrs", "ipnet", - "log 0.4.16", + "log", "rtnetlink", "system-configuration", "windows", @@ -3231,15 +3108,6 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec58677acfea8a15352d42fc87d11d63596ade9239e0a7c9352914417515dbe6" -[[package]] -name = "iovec" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" -dependencies = [ - "libc", -] - [[package]] name = "ip_network" version = "0.4.1" @@ -3254,7 +3122,7 @@ checksum = "723519edce41262b05d4143ceb95050e4c614f483e78e9fd9e39a8275a84ad98" dependencies = [ "socket2 0.4.4", "widestring", - "winapi 0.3.9", + "winapi", "winreg", ] @@ -3303,161 +3171,28 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "jsonrpc-client-transports" -version = "18.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b99d4207e2a04fb4581746903c2bb7eb376f88de9c699d0f3e10feeac0cd3a" -dependencies = [ - "derive_more", - "futures 0.3.21", - "hyper 0.14.16", - "hyper-tls", - "jsonrpc-core", - "jsonrpc-pubsub", - "log 0.4.16", - "serde", - "serde_json", - "tokio", - "url 1.7.2", - "websocket", -] - -[[package]] -name = "jsonrpc-core" -version = "18.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f7f76aef2d054868398427f6c54943cf3d1caa9a7ec7d0c38d69df97a965eb" -dependencies = [ - "futures 0.3.21", - "futures-executor", - "futures-util", - "log 0.4.16", - "serde", - "serde_derive", - "serde_json", -] - -[[package]] -name = "jsonrpc-core-client" -version = "18.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b51da17abecbdab3e3d4f26b01c5ec075e88d3abe3ab3b05dc9aa69392764ec0" -dependencies = [ - "futures 0.3.21", - "jsonrpc-client-transports", -] - -[[package]] -name = "jsonrpc-derive" -version = "18.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b939a78fa820cdfcb7ee7484466746a7377760970f6f9c6fe19f9edcc8a38d2" -dependencies = [ - "proc-macro-crate 0.1.5", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "jsonrpc-http-server" -version = "18.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1dea6e07251d9ce6a552abfb5d7ad6bc290a4596c8dcc3d795fae2bbdc1f3ff" -dependencies = [ - "futures 0.3.21", - "hyper 0.14.16", - "jsonrpc-core", - "jsonrpc-server-utils", - "log 0.4.16", - "net2", - "parking_lot 0.11.2", - "unicase 2.6.0", -] - -[[package]] -name = "jsonrpc-ipc-server" -version = "18.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "382bb0206323ca7cda3dcd7e245cea86d37d02457a02a975e3378fb149a48845" -dependencies = [ - "futures 0.3.21", - "jsonrpc-core", - "jsonrpc-server-utils", - "log 0.4.16", - "parity-tokio-ipc", - "parking_lot 0.11.2", - "tower-service", -] - -[[package]] -name = "jsonrpc-pubsub" -version = "18.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240f87695e6c6f62fb37f05c02c04953cf68d6408b8c1c89de85c7a0125b1011" -dependencies = [ - "futures 0.3.21", - "jsonrpc-core", - "lazy_static", - "log 0.4.16", - "parking_lot 0.11.2", - "rand 0.7.3", - "serde", -] - -[[package]] -name = "jsonrpc-server-utils" -version = "18.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4fdea130485b572c39a460d50888beb00afb3e35de23ccd7fad8ff19f0e0d4" -dependencies = [ - "bytes 1.1.0", - "futures 0.3.21", - "globset", - "jsonrpc-core", - "lazy_static", - "log 0.4.16", - "tokio", - "tokio-stream", - "tokio-util 0.6.7", - "unicase 2.6.0", -] - -[[package]] -name = "jsonrpc-ws-server" -version = "18.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f892c7d766369475ab7b0669f417906302d7c0fb521285c0a0c92e52e7c8e946" -dependencies = [ - "futures 0.3.21", - "jsonrpc-core", - "jsonrpc-server-utils", - "log 0.4.16", - "parity-ws", - "parking_lot 0.11.2", - "slab", -] - [[package]] name = "jsonrpsee" -version = "0.10.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91dc760c341fa81173f9a434931aaf32baad5552b0230cc6c93e8fb7eaad4c19" +checksum = "ad6f9ff3481f3069c92474b697c104502f7e9191d29b34bfa38ae9a19415f1cd" dependencies = [ "jsonrpsee-core", + "jsonrpsee-http-server", "jsonrpsee-proc-macros", "jsonrpsee-types", "jsonrpsee-ws-client", + "jsonrpsee-ws-server", + "tracing", ] [[package]] name = "jsonrpsee-client-transport" -version = "0.10.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "765f7a36d5087f74e3b3b47805c2188fef8eb54afcb587b078d9f8ebfe9c7220" +checksum = "4358e100faf43b2f3b7b0ecf0ad4ce3e6275fe12fda8428dedda2979751dd184" dependencies = [ - "futures 0.3.21", + "futures-util", "http", "jsonrpsee-core", "jsonrpsee-types", @@ -3474,18 +3209,22 @@ dependencies = [ [[package]] name = "jsonrpsee-core" -version = "0.10.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82ef77ecd20c2254d54f5da8c0738eacca61e6b6511268a8f2753e3148c6c706" +checksum = "8e1d26ab3868749d6f716345a5fbd3334a100c0709fe464bd9189ee9d78adcde" dependencies = [ "anyhow", "arrayvec 0.7.1", + "async-lock", "async-trait", "beef", "futures-channel", + "futures-timer", "futures-util", - "hyper 0.14.16", + "hyper", "jsonrpsee-types", + "parking_lot 0.12.0", + "rand 0.8.4", "rustc-hash", "serde", "serde_json", @@ -3495,13 +3234,32 @@ dependencies = [ "tracing", ] +[[package]] +name = "jsonrpsee-http-server" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee87f19a7a01a55248fc4b4861d822331c4fd60151d99e7ac9c6771999132671" +dependencies = [ + "futures-channel", + "futures-util", + "globset", + "hyper", + "jsonrpsee-core", + "jsonrpsee-types", + "lazy_static", + "serde_json", + "tokio", + "tracing", + "unicase", +] + [[package]] name = "jsonrpsee-proc-macros" -version = "0.10.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7291c72805bc7d413b457e50d8ef3e87aa554da65ecbbc278abb7dfc283e7f0" +checksum = "b75da57d54817577801c2f7a1b638610819dfd86f0470c21a2af81b06eb41ba6" dependencies = [ - "proc-macro-crate 1.1.3", + "proc-macro-crate", "proc-macro2", "quote", "syn", @@ -3509,9 +3267,9 @@ dependencies = [ [[package]] name = "jsonrpsee-types" -version = "0.10.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b6aa52f322cbf20c762407629b8300f39bcc0cf0619840d9252a2f65fd2dd9" +checksum = "f5fe5a629443d17a30ff564881ba68881a710fd7eb02a538087b0bc51cb4962c" dependencies = [ "anyhow", "beef", @@ -3523,15 +3281,32 @@ dependencies = [ [[package]] name = "jsonrpsee-ws-client" -version = "0.10.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd66d18bab78d956df24dd0d2e41e4c00afbb818fda94a98264bdd12ce8506ac" +checksum = "ba31eb2b9a4b73d8833f53fe55e579516289f8b31adb6104b3dbc629755acf7d" dependencies = [ "jsonrpsee-client-transport", "jsonrpsee-core", "jsonrpsee-types", ] +[[package]] +name = "jsonrpsee-ws-server" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "179fe584af5c0145f922c581770d073c661a514ae6cdfa5b1a0bce41fdfdf646" +dependencies = [ + "futures-channel", + "futures-util", + "jsonrpsee-core", + "jsonrpsee-types", + "serde_json", + "soketto", + "tokio", + "tokio-util 0.7.1", + "tracing", +] + [[package]] name = "k256" version = "0.10.4" @@ -3561,23 +3336,13 @@ dependencies = [ "tiny-keccak", ] -[[package]] -name = "kernel32-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -dependencies = [ - "winapi 0.2.8", - "winapi-build", -] - [[package]] name = "kv-log-macro" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" dependencies = [ - "log 0.4.16", + "log", ] [[package]] @@ -3587,7 +3352,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a301d8ecb7989d4a6e2c57a49baca77d353bdbf879909debe3f375fe25d61f86" dependencies = [ "parity-util-mem", - "smallvec 1.8.0", + "smallvec", ] [[package]] @@ -3609,22 +3374,16 @@ checksum = "ca7fbdfd71cd663dceb0faf3367a99f8cf724514933e9867cec4995b6027cbc1" dependencies = [ "fs-swap", "kvdb", - "log 0.4.16", + "log", "num_cpus", "owning_ref", "parity-util-mem", "parking_lot 0.12.0", "regex", "rocksdb", - "smallvec 1.8.0", + "smallvec", ] -[[package]] -name = "language-tags" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" - [[package]] name = "lazy_static" version = "1.4.0" @@ -3651,9 +3410,9 @@ checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" [[package]] name = "libgit2-sys" -version = "0.13.2+1.4.2" +version = "0.13.3+1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a42de9a51a5c12e00fc0e4ca6bc2ea43582fc6418488e8f615e905d886f258b" +checksum = "c24d36c3ac9b9996a2418d6bf428cc0bc5d1a814a84303fc60986088c5ed60de" dependencies = [ "cc", "libc", @@ -3668,7 +3427,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753" dependencies = [ "cc", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -3678,7 +3437,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" dependencies = [ "cfg-if 1.0.0", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -3694,8 +3453,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "475ce2ac4a9727e53a519f6ee05b38abfcba8f0d39c4d24f103d184e36fd5b0f" dependencies = [ "atomic", - "bytes 1.1.0", - "futures 0.3.21", + "bytes", + "futures", "futures-timer", "getrandom 0.2.3", "instant", @@ -3729,7 +3488,7 @@ dependencies = [ "parking_lot 0.12.0", "pin-project 1.0.10", "rand 0.7.3", - "smallvec 1.8.0", + "smallvec", ] [[package]] @@ -3739,13 +3498,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13b690e65046af6a09c0b27bd9508fa1cab0efce889de74b0b643b9d2a98f9a" dependencies = [ "async-trait", - "futures 0.3.21", + "futures", "futures-timer", "instant", "libp2p-core", "libp2p-request-response", "libp2p-swarm", - "log 0.4.16", + "log", "prost", "prost-build", "rand 0.8.4", @@ -3762,12 +3521,12 @@ dependencies = [ "ed25519-dalek", "either", "fnv", - "futures 0.3.21", + "futures", "futures-timer", "instant", "lazy_static", "libsecp256k1", - "log 0.4.16", + "log", "multiaddr", "multihash", "multistream-select", @@ -3779,7 +3538,7 @@ dependencies = [ "ring", "rw-stream-sink", "sha2 0.10.2", - "smallvec 1.8.0", + "smallvec", "thiserror", "unsigned-varint", "void", @@ -3793,7 +3552,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b1d37f042f748e224f04785d0e987ae09a2aa518d6401d82d412dad83e360ed" dependencies = [ "flate2", - "futures 0.3.21", + "futures", "libp2p-core", ] @@ -3804,10 +3563,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "066e33e854e10b5c93fc650458bf2179c7e0d143db260b0963e44a94859817f1" dependencies = [ "async-std-resolver", - "futures 0.3.21", + "futures", "libp2p-core", - "log 0.4.16", - "smallvec 1.8.0", + "log", + "smallvec", "trust-dns-resolver", ] @@ -3819,14 +3578,14 @@ checksum = "733d3ea6ebe7a7a85df2bc86678b93f24b015fae5fe3b3acc4c400e795a55d2d" dependencies = [ "cuckoofilter", "fnv", - "futures 0.3.21", + "futures", "libp2p-core", "libp2p-swarm", - "log 0.4.16", + "log", "prost", "prost-build", "rand 0.7.3", - "smallvec 1.8.0", + "smallvec", ] [[package]] @@ -3836,23 +3595,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a90c989a7c0969c2ab63e898da9bc735e3be53fb4f376e9c045ce516bcc9f928" dependencies = [ "asynchronous-codec", - "base64 0.13.0", + "base64", "byteorder", - "bytes 1.1.0", + "bytes", "fnv", - "futures 0.3.21", + "futures", "hex_fmt", "instant", "libp2p-core", "libp2p-swarm", - "log 0.4.16", + "log", "prometheus-client", "prost", "prost-build", "rand 0.7.3", "regex", "sha2 0.10.2", - "smallvec 1.8.0", + "smallvec", "unsigned-varint", "wasm-timer", ] @@ -3863,15 +3622,15 @@ version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5ef5a5b57904c7c33d6713ef918d239dc6b7553458f3475d87f8a18e9c651c8" dependencies = [ - "futures 0.3.21", + "futures", "futures-timer", "libp2p-core", "libp2p-swarm", - "log 0.4.16", + "log", "lru", "prost", "prost-build", - "smallvec 1.8.0", + "smallvec", ] [[package]] @@ -3882,20 +3641,20 @@ checksum = "564e6bd64d177446399ed835b9451a8825b07929d6daa6a94e6405592974725e" dependencies = [ "arrayvec 0.5.2", "asynchronous-codec", - "bytes 1.1.0", + "bytes", "either", "fnv", - "futures 0.3.21", + "futures", "futures-timer", "instant", "libp2p-core", "libp2p-swarm", - "log 0.4.16", + "log", "prost", "prost-build", "rand 0.7.3", "sha2 0.10.2", - "smallvec 1.8.0", + "smallvec", "thiserror", "uint", "unsigned-varint", @@ -3911,14 +3670,14 @@ dependencies = [ "async-io", "data-encoding", "dns-parser", - "futures 0.3.21", + "futures", "if-watch", "lazy_static", "libp2p-core", "libp2p-swarm", - "log 0.4.16", + "log", "rand 0.8.4", - "smallvec 1.8.0", + "smallvec", "socket2 0.4.4", "void", ] @@ -3946,14 +3705,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "442eb0c9fff0bf22a34f015724b4143ce01877e079ed0963c722d94c07c72160" dependencies = [ "asynchronous-codec", - "bytes 1.1.0", - "futures 0.3.21", + "bytes", + "futures", "libp2p-core", - "log 0.4.16", + "log", "nohash-hasher", "parking_lot 0.12.0", "rand 0.7.3", - "smallvec 1.8.0", + "smallvec", "unsigned-varint", ] @@ -3963,12 +3722,12 @@ version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dd7e0c94051cda67123be68cf6b65211ba3dde7277be9068412de3e7ffd63ef" dependencies = [ - "bytes 1.1.0", + "bytes", "curve25519-dalek 3.0.2", - "futures 0.3.21", + "futures", "lazy_static", "libp2p-core", - "log 0.4.16", + "log", "prost", "prost-build", "rand 0.8.4", @@ -3985,12 +3744,12 @@ version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf57a3c2e821331dda9fe612d4654d676ab6e33d18d9434a18cced72630df6ad" dependencies = [ - "futures 0.3.21", + "futures", "futures-timer", "instant", "libp2p-core", "libp2p-swarm", - "log 0.4.16", + "log", "rand 0.7.3", "void", ] @@ -4002,10 +3761,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "962c0fb0e7212fb96a69b87f2d09bcefd317935239bdc79cda900e7a8897a3fe" dependencies = [ "asynchronous-codec", - "bytes 1.1.0", - "futures 0.3.21", + "bytes", + "futures", "libp2p-core", - "log 0.4.16", + "log", "prost", "prost-build", "unsigned-varint", @@ -4018,8 +3777,8 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f1a458bbda880107b5b36fcb9b5a1ef0c329685da0e203ed692a8ebe64cc92c" dependencies = [ - "futures 0.3.21", - "log 0.4.16", + "futures", + "log", "pin-project 1.0.10", "rand 0.7.3", "salsa20", @@ -4033,19 +3792,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3aa754cb7bccef51ebc3c458c6bbcef89d83b578a9925438389be841527d408f" dependencies = [ "asynchronous-codec", - "bytes 1.1.0", + "bytes", "either", - "futures 0.3.21", + "futures", "futures-timer", "instant", "libp2p-core", "libp2p-swarm", - "log 0.4.16", + "log", "pin-project 1.0.10", "prost", "prost-build", "rand 0.8.4", - "smallvec 1.8.0", + "smallvec", "static_assertions", "thiserror", "unsigned-varint", @@ -4060,12 +3819,12 @@ checksum = "bbd0baab894c5b84da510b915d53264d566c3c35889f09931fe9edbd2a773bee" dependencies = [ "asynchronous-codec", "bimap", - "futures 0.3.21", + "futures", "futures-timer", "instant", "libp2p-core", "libp2p-swarm", - "log 0.4.16", + "log", "prost", "prost-build", "rand 0.8.4", @@ -4082,14 +3841,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6a6fc6c9ad95661f46989473b34bd2993d14a4de497ff3b2668a910d4b869" dependencies = [ "async-trait", - "bytes 1.1.0", - "futures 0.3.21", + "bytes", + "futures", "instant", "libp2p-core", "libp2p-swarm", - "log 0.4.16", + "log", "rand 0.7.3", - "smallvec 1.8.0", + "smallvec", "unsigned-varint", ] @@ -4101,14 +3860,14 @@ checksum = "8f0c69ad9e8f7c5fc50ad5ad9c7c8b57f33716532a2b623197f69f93e374d14c" dependencies = [ "either", "fnv", - "futures 0.3.21", + "futures", "futures-timer", "instant", "libp2p-core", - "log 0.4.16", + "log", "pin-project 1.0.10", "rand 0.7.3", - "smallvec 1.8.0", + "smallvec", "thiserror", "void", ] @@ -4130,13 +3889,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "193447aa729c85aac2376828df76d171c1a589c9e6b58fcc7f9d9a020734122c" dependencies = [ "async-io", - "futures 0.3.21", + "futures", "futures-timer", "if-watch", "ipnet", "libc", "libp2p-core", - "log 0.4.16", + "log", "socket2 0.4.4", ] @@ -4147,9 +3906,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24bdab114f7f2701757d6541266e1131b429bbae382008f207f2114ee4222dcb" dependencies = [ "async-std", - "futures 0.3.21", + "futures", "libp2p-core", - "log 0.4.16", + "log", ] [[package]] @@ -4158,7 +3917,7 @@ version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f6ea0f84a967ef59a16083f222c18115ae2e91db69809dce275df62e101b279" dependencies = [ - "futures 0.3.21", + "futures", "js-sys", "libp2p-core", "parity-send-wrapper", @@ -4173,14 +3932,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c932834c3754501c368d1bf3d0fb458487a642b90fc25df082a3a2f3d3b32e37" dependencies = [ "either", - "futures 0.3.21", + "futures", "futures-rustls", "libp2p-core", - "log 0.4.16", + "log", "quicksink", "rw-stream-sink", "soketto", - "url 2.2.1", + "url", "webpki-roots", ] @@ -4190,7 +3949,7 @@ version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be902ebd89193cd020e89e89107726a38cfc0d16d18f613f4a37d046e92c7517" dependencies = [ - "futures 0.3.21", + "futures", "libp2p-core", "parking_lot 0.12.0", "thiserror", @@ -4219,7 +3978,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0452aac8bab02242429380e9b2f94ea20cea2b37e2c1777a1358799bbe97f37" dependencies = [ "arrayref", - "base64 0.13.0", + "base64", "digest 0.9.0", "hmac-drbg", "libsecp256k1-core", @@ -4321,15 +4080,6 @@ dependencies = [ "paste 0.1.18", ] -[[package]] -name = "lock_api" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" -dependencies = [ - "scopeguard", -] - [[package]] name = "lock_api" version = "0.4.6" @@ -4339,15 +4089,6 @@ dependencies = [ "scopeguard", ] -[[package]] -name = "log" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" -dependencies = [ - "log 0.4.16", -] - [[package]] name = "log" version = "0.4.16" @@ -4462,12 +4203,6 @@ dependencies = [ "rawpointer", ] -[[package]] -name = "maybe-uninit" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" - [[package]] name = "memchr" version = "2.4.1" @@ -4481,7 +4216,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" dependencies = [ "libc", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -4540,15 +4275,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "mime" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" -dependencies = [ - "log 0.3.9", -] - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -4565,25 +4291,6 @@ dependencies = [ "autocfg 1.0.1", ] -[[package]] -name = "mio" -version = "0.6.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" -dependencies = [ - "cfg-if 0.1.10", - "fuchsia-zircon", - "fuchsia-zircon-sys", - "iovec", - "kernel32-sys", - "libc", - "log 0.4.16", - "miow 0.2.2", - "net2", - "slab", - "winapi 0.2.8", -] - [[package]] name = "mio" version = "0.8.0" @@ -4591,34 +4298,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2" dependencies = [ "libc", - "log 0.4.16", - "miow 0.3.6", + "log", + "miow", "ntapi", - "winapi 0.3.9", -] - -[[package]] -name = "mio-extras" -version = "2.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" -dependencies = [ - "lazycell", - "log 0.4.16", - "mio 0.6.23", - "slab", -] - -[[package]] -name = "miow" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" -dependencies = [ - "kernel32-sys", - "net2", - "winapi 0.2.8", - "ws2_32-sys", + "winapi", ] [[package]] @@ -4628,7 +4311,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897" dependencies = [ "socket2 0.3.19", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -4648,11 +4331,11 @@ dependencies = [ "byteorder", "data-encoding", "multihash", - "percent-encoding 2.1.0", + "percent-encoding", "serde", "static_assertions", "unsigned-varint", - "url 2.2.1", + "url", ] [[package]] @@ -4689,7 +4372,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc076939022111618a5026d3be019fd8b366e76314538ff9a1b59ffbcbf98bcd" dependencies = [ - "proc-macro-crate 1.1.3", + "proc-macro-crate", "proc-macro-error", "proc-macro2", "quote", @@ -4709,11 +4392,11 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "363a84be6453a70e63513660f4894ef815daf88e3356bffcda9ca27d810ce83b" dependencies = [ - "bytes 1.1.0", - "futures 0.3.21", - "log 0.4.16", + "bytes", + "futures", + "log", "pin-project 1.0.10", - "smallvec 1.8.0", + "smallvec", "unsigned-varint", ] @@ -4755,35 +4438,6 @@ dependencies = [ "rand 0.8.4", ] -[[package]] -name = "native-tls" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d" -dependencies = [ - "lazy_static", - "libc", - "log 0.4.16", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "net2" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" -dependencies = [ - "cfg-if 0.1.10", - "libc", - "winapi 0.3.9", -] - [[package]] name = "netlink-packet-core" version = "0.4.2" @@ -4828,9 +4482,9 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef8785b8141e8432aa45fceb922a7e876d7da3fad37fa7e7ec702ace3aa0826b" dependencies = [ - "bytes 1.1.0", - "futures 0.3.21", - "log 0.4.16", + "bytes", + "futures", + "log", "netlink-packet-core", "netlink-sys", "tokio", @@ -4843,10 +4497,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e4c9f9547a08241bee7b6558b9b98e1f290d187de8b7cfca2bbb4937bcaa8f8" dependencies = [ "async-io", - "bytes 1.1.0", - "futures 0.3.21", + "bytes", + "futures", "libc", - "log 0.4.16", + "log", ] [[package]] @@ -4882,13 +4536,13 @@ dependencies = [ "clap 3.1.6", "derive_more", "fs_extra", - "futures 0.3.21", + "futures", "hash-db", "hex", "kvdb", "kvdb-rocksdb", "lazy_static", - "log 0.4.16", + "log", "node-primitives", "node-runtime", "node-testing", @@ -4924,9 +4578,10 @@ dependencies = [ "frame-benchmarking-cli", "frame-system", "frame-system-rpc-runtime-api", - "futures 0.3.21", + "futures", "hex-literal", - "log 0.4.16", + "jsonrpsee", + "log", "nix 0.23.1", "node-executor", "node-inspect", @@ -5003,7 +4658,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "futures 0.3.21", + "futures", "node-primitives", "node-runtime", "node-testing", @@ -5060,7 +4715,7 @@ dependencies = [ name = "node-rpc" version = "3.0.0-dev" dependencies = [ - "jsonrpc-core", + "jsonrpsee", "node-primitives", "pallet-contracts-rpc", "pallet-mmr-rpc", @@ -5100,7 +4755,7 @@ dependencies = [ "frame-system-rpc-runtime-api", "frame-try-runtime", "hex-literal", - "log 0.4.16", + "log", "node-primitives", "pallet-asset-tx-payment", "pallet-assets", @@ -5195,7 +4850,7 @@ dependencies = [ "frame-benchmarking", "frame-benchmarking-cli", "frame-system", - "jsonrpc-core", + "jsonrpsee", "node-template-runtime", "pallet-transaction-payment", "pallet-transaction-payment-rpc", @@ -5272,8 +4927,8 @@ version = "3.0.0-dev" dependencies = [ "frame-system", "fs_extra", - "futures 0.3.21", - "log 0.4.16", + "futures", + "log", "node-executor", "node-primitives", "node-runtime", @@ -5320,7 +4975,7 @@ checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" dependencies = [ "memchr", "minimal-lexical", - "version_check 0.9.2", + "version_check", ] [[package]] @@ -5329,7 +4984,7 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] @@ -5462,39 +5117,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" -[[package]] -name = "openssl" -version = "0.10.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "549430950c79ae24e6d02e0b7404534ecf311d94cc9f861e9e4020187d13d885" -dependencies = [ - "bitflags", - "cfg-if 1.0.0", - "foreign-types", - "libc", - "once_cell", - "openssl-sys", -] - [[package]] name = "openssl-probe" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" -[[package]] -name = "openssl-sys" -version = "0.9.65" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a7907e3bfa08bb85105209cdfcb6c63d109f8f6c1ed6ca318fff5c1853fbc1d" -dependencies = [ - "autocfg 1.0.1", - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "os_str_bytes" version = "6.0.0" @@ -5510,7 +5138,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] @@ -5536,7 +5164,7 @@ dependencies = [ "scale-info", "serde", "serde_json", - "smallvec 1.8.0", + "smallvec", "sp-core", "sp-io", "sp-runtime", @@ -5633,7 +5261,7 @@ dependencies = [ "frame-election-provider-support", "frame-support", "frame-system", - "log 0.4.16", + "log", "pallet-authorship", "pallet-balances", "pallet-offences", @@ -5662,7 +5290,7 @@ dependencies = [ "frame-election-provider-support", "frame-support", "frame-system", - "log 0.4.16", + "log", "pallet-balances", "parity-scale-codec", "scale-info", @@ -5690,7 +5318,7 @@ dependencies = [ "frame-election-provider-support", "frame-support", "frame-system", - "log 0.4.16", + "log", "pallet-bags-list", "pallet-staking", "remote-externalities", @@ -5699,7 +5327,6 @@ dependencies = [ "sp-std", "sp-storage", "sp-tracing", - "tokio", ] [[package]] @@ -5709,7 +5336,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.16", + "log", "pallet-transaction-payment", "parity-scale-codec", "scale-info", @@ -5747,7 +5374,7 @@ dependencies = [ "frame-system", "hex", "hex-literal", - "log 0.4.16", + "log", "pallet-beefy", "pallet-mmr", "pallet-session", @@ -5768,7 +5395,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.16", + "log", "pallet-balances", "pallet-treasury", "parity-scale-codec", @@ -5786,7 +5413,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.16", + "log", "pallet-balances", "pallet-bounties", "pallet-treasury", @@ -5805,7 +5432,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.16", + "log", "parity-scale-codec", "scale-info", "sp-core", @@ -5825,7 +5452,7 @@ dependencies = [ "frame-support", "frame-system", "hex-literal", - "log 0.4.16", + "log", "pallet-balances", "pallet-contracts-primitives", "pallet-contracts-proc-macro", @@ -5838,7 +5465,7 @@ dependencies = [ "rand_pcg 0.3.1", "scale-info", "serde", - "smallvec 1.8.0", + "smallvec", "sp-core", "sp-io", "sp-keystore", @@ -5877,9 +5504,7 @@ dependencies = [ name = "pallet-contracts-rpc" version = "4.0.0-dev" dependencies = [ - "jsonrpc-core", - "jsonrpc-core-client", - "jsonrpc-derive", + "jsonrpsee", "pallet-contracts-primitives", "pallet-contracts-rpc-runtime-api", "parity-scale-codec", @@ -5949,7 +5574,7 @@ dependencies = [ "frame-election-provider-support", "frame-support", "frame-system", - "log 0.4.16", + "log", "pallet-balances", "pallet-election-provider-support-benchmarking", "parity-scale-codec", @@ -5986,7 +5611,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.16", + "log", "pallet-balances", "parity-scale-codec", "scale-info", @@ -6005,7 +5630,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.16", + "log", "pallet-balances", "parity-scale-codec", "scale-info", @@ -6022,7 +5647,7 @@ dependencies = [ "frame-support", "frame-system", "lite-json", - "log 0.4.16", + "log", "parity-scale-codec", "scale-info", "sp-core", @@ -6073,7 +5698,7 @@ dependencies = [ "frame-election-provider-support", "frame-support", "frame-system", - "log 0.4.16", + "log", "pallet-authorship", "pallet-balances", "pallet-offences", @@ -6118,7 +5743,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.16", + "log", "pallet-authorship", "pallet-session", "parity-scale-codec", @@ -6172,7 +5797,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.16", + "log", "parity-scale-codec", "scale-info", "sp-core", @@ -6204,9 +5829,7 @@ dependencies = [ name = "pallet-mmr-rpc" version = "3.0.0" dependencies = [ - "jsonrpc-core", - "jsonrpc-core-client", - "jsonrpc-derive", + "jsonrpsee", "parity-scale-codec", "serde", "serde_json", @@ -6254,7 +5877,7 @@ version = "4.0.0-dev" dependencies = [ "frame-support", "frame-system", - "log 0.4.16", + "log", "parity-scale-codec", "scale-info", "sp-core", @@ -6309,7 +5932,7 @@ version = "4.0.0-dev" dependencies = [ "frame-support", "frame-system", - "log 0.4.16", + "log", "pallet-balances", "parity-scale-codec", "scale-info", @@ -6454,7 +6077,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.16", + "log", "pallet-preimage", "parity-scale-codec", "scale-info", @@ -6487,7 +6110,7 @@ dependencies = [ "frame-support", "frame-system", "impl-trait-for-tuples", - "log 0.4.16", + "log", "pallet-timestamp", "parity-scale-codec", "scale-info", @@ -6548,7 +6171,7 @@ dependencies = [ "frame-election-provider-support", "frame-support", "frame-system", - "log 0.4.16", + "log", "pallet-authorship", "pallet-bags-list", "pallet-balances", @@ -6574,7 +6197,7 @@ dependencies = [ name = "pallet-staking-reward-curve" version = "4.0.0-dev" dependencies = [ - "proc-macro-crate 1.1.3", + "proc-macro-crate", "proc-macro2", "quote", "sp-runtime", @@ -6585,7 +6208,7 @@ dependencies = [ name = "pallet-staking-reward-fn" version = "4.0.0-dev" dependencies = [ - "log 0.4.16", + "log", "sp-arithmetic", ] @@ -6596,7 +6219,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.16", + "log", "pallet-balances", "parity-scale-codec", "parking_lot 0.12.0", @@ -6649,7 +6272,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.16", + "log", "parity-scale-codec", "scale-info", "sp-core", @@ -6667,7 +6290,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.16", + "log", "pallet-balances", "pallet-treasury", "parity-scale-codec", @@ -6691,7 +6314,7 @@ dependencies = [ "scale-info", "serde", "serde_json", - "smallvec 1.8.0", + "smallvec", "sp-core", "sp-io", "sp-runtime", @@ -6702,9 +6325,7 @@ dependencies = [ name = "pallet-transaction-payment-rpc" version = "4.0.0-dev" dependencies = [ - "jsonrpc-core", - "jsonrpc-core-client", - "jsonrpc-derive", + "jsonrpsee", "pallet-transaction-payment-rpc-runtime-api", "parity-scale-codec", "sp-api", @@ -6769,7 +6390,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.16", + "log", "pallet-balances", "parity-scale-codec", "scale-info", @@ -6802,7 +6423,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "log 0.4.16", + "log", "pallet-balances", "parity-scale-codec", "scale-info", @@ -6841,7 +6462,7 @@ dependencies = [ "fs2", "hex", "libc", - "log 0.4.16", + "log", "lz4", "memmap2 0.2.1", "parking_lot 0.11.2", @@ -6869,7 +6490,7 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c6e626dc84025ff56bf1476ed0e30d10c84d7f89a475ef46ebabee1095a8fba" dependencies = [ - "proc-macro-crate 1.1.3", + "proc-macro-crate", "proc-macro2", "quote", "syn", @@ -6881,20 +6502,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa9777aa91b8ad9dd5aaa04a9b6bcb02c7f1deb952fca5a66034d5e63afc5c6f" -[[package]] -name = "parity-tokio-ipc" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9981e32fb75e004cc148f5fb70342f393830e0a4aa62e3cc93b50976218d42b6" -dependencies = [ - "futures 0.3.21", - "libc", - "log 0.4.16", - "rand 0.7.3", - "tokio", - "winapi 0.3.9", -] - [[package]] name = "parity-util-mem" version = "0.11.0" @@ -6907,8 +6514,8 @@ dependencies = [ "parity-util-mem-derive", "parking_lot 0.12.0", "primitive-types", - "smallvec 1.8.0", - "winapi 0.3.9", + "smallvec", + "winapi", ] [[package]] @@ -6937,41 +6544,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" -[[package]] -name = "parity-ws" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0ab8a461779bd022964cae2b4989fa9c99deb270bec162da2125ec03c09fcaa" -dependencies = [ - "byteorder", - "bytes 0.4.12", - "httparse", - "log 0.4.16", - "mio 0.6.23", - "mio-extras", - "rand 0.7.3", - "sha-1 0.8.2", - "slab", - "url 2.2.1", -] - [[package]] name = "parking" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" -[[package]] -name = "parking_lot" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" -dependencies = [ - "lock_api 0.3.4", - "parking_lot_core 0.6.2", - "rustc_version 0.2.3", -] - [[package]] name = "parking_lot" version = "0.11.2" @@ -6979,7 +6557,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", - "lock_api 0.4.6", + "lock_api", "parking_lot_core 0.8.5", ] @@ -6989,25 +6567,10 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" dependencies = [ - "lock_api 0.4.6", + "lock_api", "parking_lot_core 0.9.1", ] -[[package]] -name = "parking_lot_core" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" -dependencies = [ - "cfg-if 0.1.10", - "cloudabi", - "libc", - "redox_syscall 0.1.57", - "rustc_version 0.2.3", - "smallvec 0.6.14", - "winapi 0.3.9", -] - [[package]] name = "parking_lot_core" version = "0.8.5" @@ -7018,8 +6581,8 @@ dependencies = [ "instant", "libc", "redox_syscall 0.2.10", - "smallvec 1.8.0", - "winapi 0.3.9", + "smallvec", + "winapi", ] [[package]] @@ -7031,7 +6594,7 @@ dependencies = [ "cfg-if 1.0.0", "libc", "redox_syscall 0.2.10", - "smallvec 1.8.0", + "smallvec", "windows-sys", ] @@ -7084,12 +6647,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" -[[package]] -name = "percent-encoding" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" - [[package]] name = "percent-encoding" version = "2.1.0" @@ -7255,9 +6812,9 @@ checksum = "a2a7bc6b2a29e632e45451c941832803a18cce6781db04de8a04696cdca8bde4" dependencies = [ "cfg-if 0.1.10", "libc", - "log 0.4.16", + "log", "wepoll-sys", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -7355,15 +6912,6 @@ dependencies = [ "uint", ] -[[package]] -name = "proc-macro-crate" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" -dependencies = [ - "toml", -] - [[package]] name = "proc-macro-crate" version = "1.1.3" @@ -7384,7 +6932,7 @@ dependencies = [ "proc-macro2", "quote", "syn", - "version_check 0.9.2", + "version_check", ] [[package]] @@ -7395,7 +6943,7 @@ checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ "proc-macro2", "quote", - "version_check 0.9.2", + "version_check", ] [[package]] @@ -7406,9 +6954,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro2" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1" +checksum = "9027b48e9d4c9175fa2218adf3557f91c1137021739951d4932f5f8268ac48aa" dependencies = [ "unicode-xid", ] @@ -7456,7 +7004,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" dependencies = [ - "bytes 1.1.0", + "bytes", "prost-derive", ] @@ -7466,11 +7014,11 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" dependencies = [ - "bytes 1.1.0", + "bytes", "heck 0.3.2", "itertools", "lazy_static", - "log 0.4.16", + "log", "multimap", "petgraph", "prost", @@ -7499,7 +7047,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" dependencies = [ - "bytes 1.1.0", + "bytes", "prost", ] @@ -7551,7 +7099,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" dependencies = [ "env_logger 0.8.4", - "log 0.4.16", + "log", "rand 0.8.4", ] @@ -7597,7 +7145,7 @@ dependencies = [ "rand_os", "rand_pcg 0.1.2", "rand_xorshift", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -7743,7 +7291,7 @@ checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" dependencies = [ "libc", "rand_core 0.4.2", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -7757,7 +7305,7 @@ dependencies = [ "libc", "rand_core 0.4.2", "rdrand", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -7823,7 +7371,7 @@ checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" dependencies = [ "crossbeam-channel", "crossbeam-deque", - "crossbeam-utils 0.8.5", + "crossbeam-utils", "lazy_static", "num_cpus", ] @@ -7899,9 +7447,9 @@ version = "0.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "571f7f397d61c4755285cd37853fe8e03271c243424a907415909379659381c5" dependencies = [ - "log 0.4.16", + "log", "rustc-hash", - "smallvec 1.8.0", + "smallvec", ] [[package]] @@ -7910,9 +7458,9 @@ version = "0.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62446b1d3ebf980bdc68837700af1d77b37bc430e524bf95319c6eada2a4cc02" dependencies = [ - "log 0.4.16", + "log", "rustc-hash", - "smallvec 1.8.0", + "smallvec", ] [[package]] @@ -7951,7 +7499,7 @@ dependencies = [ "bitflags", "libc", "mach", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -7963,7 +7511,7 @@ dependencies = [ "bitflags", "libc", "mach", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -7973,7 +7521,7 @@ dependencies = [ "env_logger 0.9.0", "frame-support", "jsonrpsee", - "log 0.4.16", + "log", "pallet-elections-phragmen", "parity-scale-codec", "serde", @@ -7991,7 +7539,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] @@ -8042,7 +7590,7 @@ dependencies = [ "spin", "untrusted", "web-sys", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -8087,7 +7635,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffc936cf8a7ea60c58f030fd36a612a48f440610214dc54bc36431f9ea0c3efb" dependencies = [ "libc", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -8097,8 +7645,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f54290e54521dac3de4149d83ddf9f62a359b3cc93bcb494a794a41e6f4744b" dependencies = [ "async-global-executor", - "futures 0.3.21", - "log 0.4.16", + "futures", + "log", "netlink-packet-route", "netlink-proto", "nix 0.22.3", @@ -8111,10 +7659,10 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb" dependencies = [ - "base64 0.13.0", + "base64", "blake2b_simd 0.5.11", "constant_time_eq", - "crossbeam-utils 0.8.5", + "crossbeam-utils", ] [[package]] @@ -8173,7 +7721,7 @@ dependencies = [ "io-lifetimes", "libc", "linux-raw-sys", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -8182,8 +7730,8 @@ version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" dependencies = [ - "base64 0.13.0", - "log 0.4.16", + "base64", + "log", "ring", "sct 0.6.0", "webpki 0.21.4", @@ -8195,7 +7743,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d37e5e2290f3e040b594b1a9e04377c2c671f1a1cfd9bfdef82106ac1c113f84" dependencies = [ - "log 0.4.16", + "log", "ring", "sct 0.7.0", "webpki 0.22.0", @@ -8231,7 +7779,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" dependencies = [ - "base64 0.13.0", + "base64", ] [[package]] @@ -8246,7 +7794,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4da5fcb054c46f5a5dff833b129285a93d3f0179531735e6c866e8cc307d2020" dependencies = [ - "futures 0.3.21", + "futures", "pin-project 0.4.27", "static_assertions", ] @@ -8266,12 +7814,6 @@ dependencies = [ "rustc_version 0.2.3", ] -[[package]] -name = "safemem" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" - [[package]] name = "salsa20" version = "0.9.0" @@ -8294,7 +7836,7 @@ dependencies = [ name = "sc-allocator" version = "4.1.0-dev" dependencies = [ - "log 0.4.16", + "log", "sp-core", "sp-wasm-interface", "thiserror", @@ -8305,11 +7847,11 @@ name = "sc-authority-discovery" version = "0.10.0-dev" dependencies = [ "async-trait", - "futures 0.3.21", + "futures", "futures-timer", "ip_network", "libp2p", - "log 0.4.16", + "log", "parity-scale-codec", "prost", "prost-build", @@ -8333,9 +7875,9 @@ dependencies = [ name = "sc-basic-authorship" version = "0.10.0-dev" dependencies = [ - "futures 0.3.21", + "futures", "futures-timer", - "log 0.4.16", + "log", "parity-scale-codec", "parking_lot 0.12.0", "sc-block-builder", @@ -8390,7 +7932,7 @@ dependencies = [ name = "sc-chain-spec-derive" version = "4.0.0-dev" dependencies = [ - "proc-macro-crate 1.1.3", + "proc-macro-crate", "proc-macro2", "quote", "syn", @@ -8403,10 +7945,10 @@ dependencies = [ "chrono", "clap 3.1.6", "fdlimit", - "futures 0.3.21", + "futures", "hex", "libp2p", - "log 0.4.16", + "log", "names", "parity-scale-codec", "rand 0.7.3", @@ -8440,9 +7982,9 @@ name = "sc-client-api" version = "4.0.0-dev" dependencies = [ "fnv", - "futures 0.3.21", + "futures", "hash-db", - "log 0.4.16", + "log", "parity-scale-codec", "parking_lot 0.12.0", "sc-executor", @@ -8474,7 +8016,7 @@ dependencies = [ "kvdb-memorydb", "kvdb-rocksdb", "linked-hash-map", - "log 0.4.16", + "log", "parity-db", "parity-scale-codec", "parking_lot 0.12.0", @@ -8498,10 +8040,10 @@ name = "sc-consensus" version = "0.10.0-dev" dependencies = [ "async-trait", - "futures 0.3.21", + "futures", "futures-timer", "libp2p", - "log 0.4.16", + "log", "parking_lot 0.12.0", "sc-client-api", "sc-utils", @@ -8522,8 +8064,8 @@ name = "sc-consensus-aura" version = "0.10.0-dev" dependencies = [ "async-trait", - "futures 0.3.21", - "log 0.4.16", + "futures", + "log", "parity-scale-codec", "parking_lot 0.12.0", "sc-block-builder", @@ -8560,8 +8102,8 @@ version = "0.10.0-dev" dependencies = [ "async-trait", "fork-tree", - "futures 0.3.21", - "log 0.4.16", + "futures", + "log", "merlin", "num-bigint", "num-rational 0.2.4", @@ -8608,10 +8150,8 @@ dependencies = [ name = "sc-consensus-babe-rpc" version = "0.10.0-dev" dependencies = [ - "futures 0.3.21", - "jsonrpc-core", - "jsonrpc-core-client", - "jsonrpc-derive", + "futures", + "jsonrpsee", "sc-consensus", "sc-consensus-babe", "sc-consensus-epochs", @@ -8631,6 +8171,7 @@ dependencies = [ "substrate-test-runtime-client", "tempfile", "thiserror", + "tokio", ] [[package]] @@ -8651,11 +8192,9 @@ version = "0.10.0-dev" dependencies = [ "assert_matches", "async-trait", - "futures 0.3.21", - "jsonrpc-core", - "jsonrpc-core-client", - "jsonrpc-derive", - "log 0.4.16", + "futures", + "jsonrpsee", + "log", "parity-scale-codec", "sc-basic-authorship", "sc-client-api", @@ -8689,9 +8228,9 @@ name = "sc-consensus-pow" version = "0.10.0-dev" dependencies = [ "async-trait", - "futures 0.3.21", + "futures", "futures-timer", - "log 0.4.16", + "log", "parity-scale-codec", "parking_lot 0.12.0", "sc-client-api", @@ -8713,9 +8252,9 @@ name = "sc-consensus-slots" version = "0.10.0-dev" dependencies = [ "async-trait", - "futures 0.3.21", + "futures", "futures-timer", - "log 0.4.16", + "log", "parity-scale-codec", "sc-client-api", "sc-consensus", @@ -8803,7 +8342,7 @@ dependencies = [ name = "sc-executor-wasmi" version = "0.10.0-dev" dependencies = [ - "log 0.4.16", + "log", "parity-scale-codec", "sc-allocator", "sc-executor-common", @@ -8819,7 +8358,7 @@ version = "0.10.0-dev" dependencies = [ "cfg-if 1.0.0", "libc", - "log 0.4.16", + "log", "parity-scale-codec", "parity-wasm 0.42.2", "sc-allocator", @@ -8843,10 +8382,10 @@ dependencies = [ "dyn-clone", "finality-grandpa", "fork-tree", - "futures 0.3.21", + "futures", "futures-timer", "hex", - "log 0.4.16", + "log", "parity-scale-codec", "parking_lot 0.12.0", "rand 0.8.4", @@ -8885,12 +8424,9 @@ name = "sc-finality-grandpa-rpc" version = "0.10.0-dev" dependencies = [ "finality-grandpa", - "futures 0.3.21", - "jsonrpc-core", - "jsonrpc-core-client", - "jsonrpc-derive", - "jsonrpc-pubsub", - "log 0.4.16", + "futures", + "jsonrpsee", + "log", "parity-scale-codec", "sc-block-builder", "sc-client-api", @@ -8905,6 +8441,7 @@ dependencies = [ "sp-runtime", "substrate-test-runtime-client", "thiserror", + "tokio", ] [[package]] @@ -8912,9 +8449,9 @@ name = "sc-informant" version = "0.10.0-dev" dependencies = [ "ansi_term", - "futures 0.3.21", + "futures", "futures-timer", - "log 0.4.16", + "log", "parity-util-mem", "sc-client-api", "sc-network", @@ -8947,19 +8484,19 @@ dependencies = [ "async-trait", "asynchronous-codec", "bitflags", - "bytes 1.1.0", + "bytes", "cid", "either", "fnv", "fork-tree", - "futures 0.3.21", + "futures", "futures-timer", "hex", "ip_network", "libp2p", "linked-hash-map", "linked_hash_set", - "log 0.4.16", + "log", "lru", "parity-scale-codec", "parking_lot 0.12.0", @@ -8977,7 +8514,7 @@ dependencies = [ "sc-utils", "serde", "serde_json", - "smallvec 1.8.0", + "smallvec", "sp-arithmetic", "sp-blockchain", "sp-consensus", @@ -9000,12 +8537,12 @@ dependencies = [ name = "sc-network-common" version = "0.10.0-dev" dependencies = [ - "futures 0.3.21", + "futures", "libp2p", "parity-scale-codec", "prost-build", "sc-peerset", - "smallvec 1.8.0", + "smallvec", ] [[package]] @@ -9014,10 +8551,10 @@ version = "0.10.0-dev" dependencies = [ "ahash", "async-std", - "futures 0.3.21", + "futures", "futures-timer", "libp2p", - "log 0.4.16", + "log", "lru", "quickcheck", "sc-network", @@ -9034,9 +8571,9 @@ dependencies = [ "bitflags", "either", "fork-tree", - "futures 0.3.21", + "futures", "libp2p", - "log 0.4.16", + "log", "lru", "parity-scale-codec", "prost", @@ -9047,7 +8584,7 @@ dependencies = [ "sc-consensus", "sc-network-common", "sc-peerset", - "smallvec 1.8.0", + "smallvec", "sp-arithmetic", "sp-blockchain", "sp-consensus", @@ -9066,10 +8603,10 @@ version = "0.8.0" dependencies = [ "async-std", "async-trait", - "futures 0.3.21", + "futures", "futures-timer", "libp2p", - "log 0.4.16", + "log", "parking_lot 0.12.0", "rand 0.7.3", "sc-block-builder", @@ -9092,12 +8629,12 @@ dependencies = [ name = "sc-offchain" version = "4.0.0-dev" dependencies = [ - "bytes 1.1.0", + "bytes", "fnv", - "futures 0.3.21", + "futures", "futures-timer", "hex", - "hyper 0.14.16", + "hyper", "hyper-rustls", "lazy_static", "num_cpus", @@ -9128,9 +8665,9 @@ dependencies = [ name = "sc-peerset" version = "4.0.0-dev" dependencies = [ - "futures 0.3.21", + "futures", "libp2p", - "log 0.4.16", + "log", "rand 0.7.3", "sc-utils", "serde_json", @@ -9141,7 +8678,7 @@ dependencies = [ name = "sc-proposer-metrics" version = "0.10.0-dev" dependencies = [ - "log 0.4.16", + "log", "substrate-prometheus-endpoint", ] @@ -9150,12 +8687,12 @@ name = "sc-rpc" version = "4.0.0-dev" dependencies = [ "assert_matches", - "futures 0.3.21", + "env_logger 0.9.0", + "futures", "hash-db", - "jsonrpc-core", - "jsonrpc-pubsub", + "jsonrpsee", "lazy_static", - "log 0.4.16", + "log", "parity-scale-codec", "parking_lot 0.12.0", "sc-block-builder", @@ -9180,18 +8717,16 @@ dependencies = [ "sp-session", "sp-version", "substrate-test-runtime-client", + "tokio", ] [[package]] name = "sc-rpc-api" version = "0.10.0-dev" dependencies = [ - "futures 0.3.21", - "jsonrpc-core", - "jsonrpc-core-client", - "jsonrpc-derive", - "jsonrpc-pubsub", - "log 0.4.16", + "futures", + "jsonrpsee", + "log", "parity-scale-codec", "parking_lot 0.12.0", "sc-chain-spec", @@ -9211,13 +8746,9 @@ dependencies = [ name = "sc-rpc-server" version = "4.0.0-dev" dependencies = [ - "futures 0.3.21", - "jsonrpc-core", - "jsonrpc-http-server", - "jsonrpc-ipc-server", - "jsonrpc-pubsub", - "jsonrpc-ws-server", - "log 0.4.16", + "futures", + "jsonrpsee", + "log", "serde_json", "substrate-prometheus-endpoint", "tokio", @@ -9245,12 +8776,11 @@ dependencies = [ "async-trait", "directories", "exit-future", - "futures 0.3.21", + "futures", "futures-timer", "hash-db", - "jsonrpc-core", - "jsonrpc-pubsub", - "log 0.4.16", + "jsonrpsee", + "log", "parity-scale-codec", "parity-util-mem", "parking_lot 0.12.0", @@ -9310,10 +8840,10 @@ name = "sc-service-test" version = "2.0.0" dependencies = [ "fdlimit", - "futures 0.3.21", + "futures", "hex", "hex-literal", - "log 0.4.16", + "log", "parity-scale-codec", "parking_lot 0.12.0", "sc-block-builder", @@ -9345,7 +8875,7 @@ dependencies = [ name = "sc-state-db" version = "0.10.0-dev" dependencies = [ - "log 0.4.16", + "log", "parity-scale-codec", "parity-util-mem", "parity-util-mem-derive", @@ -9358,9 +8888,7 @@ dependencies = [ name = "sc-sync-state-rpc" version = "0.10.0-dev" dependencies = [ - "jsonrpc-core", - "jsonrpc-core-client", - "jsonrpc-derive", + "jsonrpsee", "parity-scale-codec", "sc-chain-spec", "sc-client-api", @@ -9378,9 +8906,9 @@ dependencies = [ name = "sc-sysinfo" version = "6.0.0-dev" dependencies = [ - "futures 0.3.21", + "futures", "libc", - "log 0.4.16", + "log", "rand 0.7.3", "rand_pcg 0.2.1", "regex", @@ -9397,9 +8925,9 @@ name = "sc-telemetry" version = "4.0.0-dev" dependencies = [ "chrono", - "futures 0.3.21", + "futures", "libp2p", - "log 0.4.16", + "log", "parking_lot 0.12.0", "pin-project 1.0.10", "rand 0.7.3", @@ -9419,7 +8947,7 @@ dependencies = [ "criterion", "lazy_static", "libc", - "log 0.4.16", + "log", "once_cell", "parking_lot 0.12.0", "regex", @@ -9444,7 +8972,7 @@ dependencies = [ name = "sc-tracing-proc-macro" version = "4.0.0-dev" dependencies = [ - "proc-macro-crate 1.1.3", + "proc-macro-crate", "proc-macro2", "quote", "syn", @@ -9456,11 +8984,11 @@ version = "4.0.0-dev" dependencies = [ "assert_matches", "criterion", - "futures 0.3.21", + "futures", "futures-timer", "hex", "linked-hash-map", - "log 0.4.16", + "log", "parity-scale-codec", "parity-util-mem", "parking_lot 0.12.0", @@ -9488,8 +9016,8 @@ dependencies = [ name = "sc-transaction-pool-api" version = "4.0.0-dev" dependencies = [ - "futures 0.3.21", - "log 0.4.16", + "futures", + "log", "serde", "sp-blockchain", "sp-runtime", @@ -9500,10 +9028,10 @@ dependencies = [ name = "sc-utils" version = "4.0.0-dev" dependencies = [ - "futures 0.3.21", + "futures", "futures-timer", "lazy_static", - "log 0.4.16", + "log", "parking_lot 0.12.0", "prometheus", "tokio-test", @@ -9529,7 +9057,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7805950c36512db9e3251c970bb7ac425f326716941862205d612ab3b5e46e2" dependencies = [ - "proc-macro-crate 1.1.3", + "proc-macro-crate", "proc-macro2", "quote", "syn", @@ -9542,7 +9070,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" dependencies = [ "lazy_static", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -9793,12 +9321,6 @@ dependencies = [ "opaque-debug 0.3.0", ] -[[package]] -name = "sha1" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" - [[package]] name = "sha2" version = "0.8.2" @@ -9919,15 +9441,6 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" -[[package]] -name = "smallvec" -version = "0.6.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" -dependencies = [ - "maybe-uninit", -] - [[package]] name = "smallvec" version = "1.8.0" @@ -9965,7 +9478,7 @@ checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" dependencies = [ "cfg-if 1.0.0", "libc", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -9975,7 +9488,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" dependencies = [ "libc", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -9984,12 +9497,12 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d1c5305e39e09653383c2c7244f2f78b3bcae37cf50c64cb4789c9f5096ec2" dependencies = [ - "base64 0.13.0", - "bytes 1.1.0", + "base64", + "bytes", "flate2", - "futures 0.3.21", + "futures", "httparse", - "log 0.4.16", + "log", "rand 0.8.4", "sha-1 0.9.4", ] @@ -9999,7 +9512,7 @@ name = "sp-api" version = "4.0.0-dev" dependencies = [ "hash-db", - "log 0.4.16", + "log", "parity-scale-codec", "sp-api-proc-macro", "sp-core", @@ -10016,7 +9529,7 @@ name = "sp-api-proc-macro" version = "4.0.0-dev" dependencies = [ "blake2", - "proc-macro-crate 1.1.3", + "proc-macro-crate", "proc-macro2", "quote", "syn", @@ -10027,8 +9540,8 @@ name = "sp-api-test" version = "2.0.1" dependencies = [ "criterion", - "futures 0.3.21", - "log 0.4.16", + "futures", + "log", "parity-scale-codec", "rustversion", "sc-block-builder", @@ -10132,8 +9645,8 @@ dependencies = [ name = "sp-blockchain" version = "4.0.0-dev" dependencies = [ - "futures 0.3.21", - "log 0.4.16", + "futures", + "log", "lru", "parity-scale-codec", "parking_lot 0.12.0", @@ -10150,9 +9663,9 @@ name = "sp-consensus" version = "0.10.0-dev" dependencies = [ "async-trait", - "futures 0.3.21", + "futures", "futures-timer", - "log 0.4.16", + "log", "parity-scale-codec", "sp-core", "sp-inherents", @@ -10250,7 +9763,7 @@ dependencies = [ "criterion", "dyn-clonable", "ed25519-dalek", - "futures 0.3.21", + "futures", "hash-db", "hash256-std-hasher", "hex", @@ -10258,7 +9771,7 @@ dependencies = [ "impl-serde", "lazy_static", "libsecp256k1", - "log 0.4.16", + "log", "merlin", "num-traits", "parity-scale-codec", @@ -10344,7 +9857,7 @@ name = "sp-finality-grandpa" version = "4.0.0-dev" dependencies = [ "finality-grandpa", - "log 0.4.16", + "log", "parity-scale-codec", "scale-info", "serde", @@ -10361,7 +9874,7 @@ name = "sp-inherents" version = "4.0.0-dev" dependencies = [ "async-trait", - "futures 0.3.21", + "futures", "impl-trait-for-tuples", "parity-scale-codec", "sp-core", @@ -10374,10 +9887,10 @@ dependencies = [ name = "sp-io" version = "6.0.0" dependencies = [ - "futures 0.3.21", + "futures", "hash-db", "libsecp256k1", - "log 0.4.16", + "log", "parity-scale-codec", "parking_lot 0.12.0", "secp256k1", @@ -10409,7 +9922,7 @@ name = "sp-keystore" version = "0.12.0" dependencies = [ "async-trait", - "futures 0.3.21", + "futures", "merlin", "parity-scale-codec", "parking_lot 0.12.0", @@ -10435,7 +9948,7 @@ name = "sp-mmr-primitives" version = "4.0.0-dev" dependencies = [ "hex-literal", - "log 0.4.16", + "log", "parity-scale-codec", "serde", "sp-api", @@ -10508,7 +10021,7 @@ dependencies = [ "either", "hash256-std-hasher", "impl-trait-for-tuples", - "log 0.4.16", + "log", "parity-scale-codec", "parity-util-mem", "paste 1.0.6", @@ -10555,7 +10068,7 @@ name = "sp-runtime-interface-proc-macro" version = "5.0.0" dependencies = [ "Inflector", - "proc-macro-crate 1.1.3", + "proc-macro-crate", "proc-macro2", "quote", "syn", @@ -10604,7 +10117,7 @@ name = "sp-sandbox" version = "0.10.0-dev" dependencies = [ "assert_matches", - "log 0.4.16", + "log", "parity-scale-codec", "sp-core", "sp-io", @@ -10651,13 +10164,13 @@ version = "0.12.0" dependencies = [ "hash-db", "hex-literal", - "log 0.4.16", + "log", "num-traits", "parity-scale-codec", "parking_lot 0.12.0", "pretty_assertions", "rand 0.7.3", - "smallvec 1.8.0", + "smallvec", "sp-core", "sp-externalities", "sp-panic-handler", @@ -10689,7 +10202,7 @@ dependencies = [ name = "sp-tasks" version = "4.0.0-dev" dependencies = [ - "log 0.4.16", + "log", "parity-scale-codec", "sp-core", "sp-externalities", @@ -10716,7 +10229,7 @@ version = "4.0.0-dev" dependencies = [ "async-trait", "futures-timer", - "log 0.4.16", + "log", "parity-scale-codec", "sp-api", "sp-inherents", @@ -10749,7 +10262,7 @@ name = "sp-transaction-storage-proof" version = "4.0.0-dev" dependencies = [ "async-trait", - "log 0.4.16", + "log", "parity-scale-codec", "scale-info", "sp-core", @@ -10811,7 +10324,7 @@ name = "sp-wasm-interface" version = "6.0.0" dependencies = [ "impl-trait-for-tuples", - "log 0.4.16", + "log", "parity-scale-codec", "sp-std", "wasmi", @@ -10938,8 +10451,8 @@ version = "3.0.0" dependencies = [ "frame-support", "frame-system", - "futures 0.3.21", - "jsonrpc-client-transports", + "futures", + "jsonrpsee", "parity-scale-codec", "sc-rpc-api", "scale-info", @@ -10952,17 +10465,17 @@ dependencies = [ name = "substrate-frame-rpc-system" version = "4.0.0-dev" dependencies = [ + "assert_matches", "frame-system-rpc-runtime-api", - "futures 0.3.21", - "jsonrpc-core", - "jsonrpc-core-client", - "jsonrpc-derive", - "log 0.4.16", + "futures", + "jsonrpsee", + "log", "parity-scale-codec", "sc-client-api", "sc-rpc-api", "sc-transaction-pool", "sc-transaction-pool-api", + "serde_json", "sp-api", "sp-block-builder", "sp-blockchain", @@ -10970,6 +10483,7 @@ dependencies = [ "sp-runtime", "sp-tracing", "substrate-test-runtime-client", + "tokio", ] [[package]] @@ -10977,8 +10491,8 @@ name = "substrate-prometheus-endpoint" version = "0.10.0-dev" dependencies = [ "futures-util", - "hyper 0.14.16", - "log 0.4.16", + "hyper", + "log", "prometheus", "thiserror", "tokio", @@ -10988,10 +10502,8 @@ dependencies = [ name = "substrate-state-trie-migration-rpc" version = "4.0.0-dev" dependencies = [ - "jsonrpc-core", - "jsonrpc-core-client", - "jsonrpc-derive", - "log 0.4.16", + "jsonrpsee", + "log", "parity-scale-codec", "sc-client-api", "sc-rpc-api", @@ -11012,7 +10524,7 @@ name = "substrate-test-client" version = "2.0.1" dependencies = [ "async-trait", - "futures 0.3.21", + "futures", "hex", "parity-scale-codec", "sc-client-api", @@ -11041,8 +10553,8 @@ dependencies = [ "frame-support", "frame-system", "frame-system-rpc-runtime-api", - "futures 0.3.21", - "log 0.4.16", + "futures", + "log", "memory-db", "pallet-babe", "pallet-timestamp", @@ -11083,7 +10595,7 @@ dependencies = [ name = "substrate-test-runtime-client" version = "2.0.0" dependencies = [ - "futures 0.3.21", + "futures", "parity-scale-codec", "sc-block-builder", "sc-client-api", @@ -11101,7 +10613,7 @@ dependencies = [ name = "substrate-test-runtime-transaction-pool" version = "2.0.0" dependencies = [ - "futures 0.3.21", + "futures", "parity-scale-codec", "parking_lot 0.12.0", "sc-transaction-pool", @@ -11116,7 +10628,7 @@ dependencies = [ name = "substrate-test-utils" version = "4.0.0-dev" dependencies = [ - "futures 0.3.21", + "futures", "sc-service", "substrate-test-utils-derive", "tokio", @@ -11127,7 +10639,7 @@ dependencies = [ name = "substrate-test-utils-derive" version = "0.10.0-dev" dependencies = [ - "proc-macro-crate 1.1.3", + "proc-macro-crate", "proc-macro2", "quote", "syn", @@ -11230,7 +10742,7 @@ dependencies = [ "libc", "redox_syscall 0.2.10", "remove_dir_all", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -11241,7 +10753,7 @@ checksum = "edd106a334b7657c10b7c540a0106114feadeb4dc314513e97df481d5d966f42" dependencies = [ "byteorder", "dirs", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -11331,7 +10843,7 @@ checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" dependencies = [ "libc", "wasi 0.10.0+wasi-snapshot-preview1", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -11393,10 +10905,10 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" dependencies = [ - "bytes 1.1.0", + "bytes", "libc", "memchr", - "mio 0.8.0", + "mio", "num_cpus", "once_cell", "parking_lot 0.12.0", @@ -11404,39 +10916,7 @@ dependencies = [ "signal-hook-registry", "socket2 0.4.4", "tokio-macros", - "winapi 0.3.9", -] - -[[package]] -name = "tokio-codec" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b2998660ba0e70d18684de5d06b70b70a3a747469af9dea7618cc59e75976b" -dependencies = [ - "bytes 0.4.12", - "futures 0.1.31", - "tokio-io", -] - -[[package]] -name = "tokio-executor" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb2d1b8f4548dbf5e1f7818512e9c406860678f29c300cdf0ebac72d1a3a1671" -dependencies = [ - "crossbeam-utils 0.7.2", - "futures 0.1.31", -] - -[[package]] -name = "tokio-io" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674" -dependencies = [ - "bytes 0.4.12", - "futures 0.1.31", - "log 0.4.16", + "winapi", ] [[package]] @@ -11450,35 +10930,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-reactor" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09bc590ec4ba8ba87652da2068d150dcada2cfa2e07faae270a5e0409aa51351" -dependencies = [ - "crossbeam-utils 0.7.2", - "futures 0.1.31", - "lazy_static", - "log 0.4.16", - "mio 0.6.23", - "num_cpus", - "parking_lot 0.9.0", - "slab", - "tokio-executor", - "tokio-io", - "tokio-sync", -] - [[package]] name = "tokio-rustls" version = "0.22.0" @@ -11512,30 +10963,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-sync" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edfe50152bc8164fcc456dab7891fa9bf8beaf01c5ee7e1dd43a397c3cf87dee" -dependencies = [ - "fnv", - "futures 0.1.31", -] - -[[package]] -name = "tokio-tcp" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98df18ed66e3b72e742f185882a9e201892407957e45fbff8da17ae7a7c51f72" -dependencies = [ - "bytes 0.4.12", - "futures 0.1.31", - "iovec", - "mio 0.6.23", - "tokio-io", - "tokio-reactor", -] - [[package]] name = "tokio-test" version = "0.4.2" @@ -11543,33 +10970,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53474327ae5e166530d17f2d956afcb4f8a004de581b3cae10f12006bc8163e3" dependencies = [ "async-stream", - "bytes 1.1.0", + "bytes", "futures-core", "tokio", "tokio-stream", ] -[[package]] -name = "tokio-tls" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "354b8cd83825b3c20217a9dc174d6a0c67441a2fae5c41bcb1ea6679f6ae0f7c" -dependencies = [ - "futures 0.1.31", - "native-tls", - "tokio-io", -] - [[package]] name = "tokio-util" version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1caa0b0c8d94a049db56b5acf8cba99dc0623aab1b26d5b5f5e2d945846b3592" dependencies = [ - "bytes 1.1.0", + "bytes", "futures-core", "futures-sink", - "log 0.4.16", + "log", "pin-project-lite 0.2.6", "tokio", ] @@ -11580,7 +10996,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0edfdeb067411dba2044da6d1cb2df793dd35add7888d73c16e3381ded401764" dependencies = [ - "bytes 1.1.0", + "bytes", "futures-core", "futures-io", "futures-sink", @@ -11610,7 +11026,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" dependencies = [ "cfg-if 1.0.0", - "log 0.4.16", + "log", "pin-project-lite 0.2.6", "tracing-attributes", "tracing-core", @@ -11655,7 +11071,7 @@ checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" dependencies = [ "ahash", "lazy_static", - "log 0.4.16", + "log", "lru", "tracing-core", ] @@ -11685,7 +11101,7 @@ dependencies = [ "serde", "serde_json", "sharded-slab", - "smallvec 1.8.0", + "smallvec", "thread_local", "tracing", "tracing-core", @@ -11693,12 +11109,6 @@ dependencies = [ "tracing-serde", ] -[[package]] -name = "traitobject" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" - [[package]] name = "treeline" version = "0.1.0" @@ -11729,9 +11139,9 @@ checksum = "d32d034c0d3db64b43c31de38e945f15b40cd4ca6d2dcfc26d4798ce8de4ab83" dependencies = [ "hash-db", "hashbrown 0.12.0", - "log 0.4.16", + "log", "rustc-hex", - "smallvec 1.8.0", + "smallvec", ] [[package]] @@ -11766,15 +11176,15 @@ dependencies = [ "futures-channel", "futures-io", "futures-util", - "idna 0.2.3", + "idna", "ipnet", "lazy_static", - "log 0.4.16", + "log", "rand 0.8.4", - "smallvec 1.8.0", + "smallvec", "thiserror", "tinyvec", - "url 2.2.1", + "url", ] [[package]] @@ -11787,11 +11197,11 @@ dependencies = [ "futures-util", "ipconfig", "lazy_static", - "log 0.4.16", + "log", "lru-cache", "parking_lot 0.12.0", "resolv-conf", - "smallvec 1.8.0", + "smallvec", "thiserror", "trust-dns-proto", ] @@ -11808,7 +11218,7 @@ version = "0.10.0-dev" dependencies = [ "clap 3.1.6", "jsonrpsee", - "log 0.4.16", + "log", "parity-scale-codec", "remote-externalities", "sc-chain-spec", @@ -11860,12 +11270,6 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "typeable" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" - [[package]] name = "typenum" version = "1.15.0" @@ -11890,22 +11294,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.2", + "version_check", ] [[package]] @@ -11940,9 +11335,9 @@ checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" [[package]] name = "unicode-xid" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" [[package]] name = "universal-hash" @@ -11961,7 +11356,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86a8dc7f45e4c1b0d30e43038c38f274e77af056aa5f74b93c2cf9eb3c1c836" dependencies = [ "asynchronous-codec", - "bytes 1.1.0", + "bytes", "futures-io", "futures-util", ] @@ -11972,17 +11367,6 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" -[[package]] -name = "url" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" -dependencies = [ - "idna 0.1.5", - "matches", - "percent-encoding 1.0.1", -] - [[package]] name = "url" version = "2.2.1" @@ -11990,9 +11374,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ccd964113622c8e9322cfac19eb1004a07e636c545f325da085d5cdde6f1f8b" dependencies = [ "form_urlencoded", - "idna 0.2.3", + "idna", "matches", - "percent-encoding 2.1.0", + "percent-encoding", ] [[package]] @@ -12008,7 +11392,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79923f7731dc61ebfba3633098bf3ac533bbd35ccd8c57e7088d9a5eebe0263f" dependencies = [ "ctor", - "version_check 0.9.2", + "version_check", ] [[package]] @@ -12023,12 +11407,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eafc1b9b2dfc6f5529177b62cf806484db55b32dc7c9658a118e11bbeb33061d" -[[package]] -name = "version_check" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" - [[package]] name = "version_check" version = "0.9.2" @@ -12063,7 +11441,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" dependencies = [ "same-file", - "winapi 0.3.9", + "winapi", "winapi-util", ] @@ -12073,7 +11451,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" dependencies = [ - "log 0.4.16", + "log", "try-lock", ] @@ -12107,7 +11485,7 @@ checksum = "f34c405b4f0658583dba0c1c7c9b694f3cac32655db463b56c254a1c75269523" dependencies = [ "bumpalo", "lazy_static", - "log 0.4.16", + "log", "proc-macro2", "quote", "syn", @@ -12161,7 +11539,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0c32691b6c7e6c14e7f8fd55361a9088b507aa49620fcd06c09b3a1082186b9" dependencies = [ - "log 0.4.16", + "log", "parity-wasm 0.32.0", "rustc-demangle", ] @@ -12181,7 +11559,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" dependencies = [ - "futures 0.3.21", + "futures", "js-sys", "parking_lot 0.11.2", "pin-utils", @@ -12214,7 +11592,7 @@ dependencies = [ "wasmer-types", "wasmer-vm", "wat", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -12228,7 +11606,7 @@ dependencies = [ "rkyv", "serde", "serde_bytes", - "smallvec 1.8.0", + "smallvec", "target-lexicon", "thiserror", "wasmer-types", @@ -12249,7 +11627,7 @@ dependencies = [ "loupe", "more-asserts", "rayon", - "smallvec 1.8.0", + "smallvec", "target-lexicon", "tracing", "wasmer-compiler", @@ -12270,7 +11648,7 @@ dependencies = [ "loupe", "more-asserts", "rayon", - "smallvec 1.8.0", + "smallvec", "wasmer-compiler", "wasmer-types", "wasmer-vm", @@ -12352,7 +11730,7 @@ dependencies = [ "wasmer-engine", "wasmer-types", "wasmer-vm", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -12400,7 +11778,7 @@ dependencies = [ "serde", "thiserror", "wasmer-types", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -12454,7 +11832,7 @@ dependencies = [ "indexmap", "lazy_static", "libc", - "log 0.4.16", + "log", "object 0.27.1", "once_cell", "paste 1.0.6", @@ -12469,7 +11847,7 @@ dependencies = [ "wasmtime-environ", "wasmtime-jit", "wasmtime-runtime", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -12479,16 +11857,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85c6ab24291fa7cb3a181f5669f6c72599b7ef781669759b45c7828c5999d0c0" dependencies = [ "anyhow", - "base64 0.13.0", + "base64", "bincode", "directories-next", "file-per-thread-logger", - "log 0.4.16", + "log", "rustix", "serde", "sha2 0.9.8", "toml", - "winapi 0.3.9", + "winapi", "zstd", ] @@ -12505,7 +11883,7 @@ dependencies = [ "cranelift-native", "cranelift-wasm", "gimli 0.26.1", - "log 0.4.16", + "log", "more-asserts", "object 0.27.1", "target-lexicon", @@ -12524,7 +11902,7 @@ dependencies = [ "cranelift-entity 0.82.3", "gimli 0.26.1", "indexmap", - "log 0.4.16", + "log", "more-asserts", "object 0.27.1", "serde", @@ -12546,7 +11924,7 @@ dependencies = [ "cfg-if 1.0.0", "cpp_demangle", "gimli 0.26.1", - "log 0.4.16", + "log", "object 0.27.1", "region 2.2.0", "rustc-demangle", @@ -12557,7 +11935,7 @@ dependencies = [ "wasmtime-environ", "wasmtime-jit-debug", "wasmtime-runtime", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -12583,7 +11961,7 @@ dependencies = [ "cfg-if 1.0.0", "indexmap", "libc", - "log 0.4.16", + "log", "mach", "memoffset", "more-asserts", @@ -12593,7 +11971,7 @@ dependencies = [ "thiserror", "wasmtime-environ", "wasmtime-jit-debug", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -12665,47 +12043,6 @@ dependencies = [ "webpki 0.22.0", ] -[[package]] -name = "websocket" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413b37840b9e27b340ce91b319ede10731de8c72f5bc4cb0206ec1ca4ce581d0" -dependencies = [ - "bytes 0.4.12", - "futures 0.1.31", - "hyper 0.10.16", - "native-tls", - "rand 0.6.5", - "tokio-codec", - "tokio-io", - "tokio-reactor", - "tokio-tcp", - "tokio-tls", - "unicase 1.4.2", - "url 1.7.2", - "websocket-base", -] - -[[package]] -name = "websocket-base" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e3810f0d00c4dccb54c30a4eee815e703232819dec7b007db115791c42aa374" -dependencies = [ - "base64 0.10.1", - "bitflags", - "byteorder", - "bytes 0.4.12", - "futures 0.1.31", - "native-tls", - "rand 0.6.5", - "sha1", - "tokio-codec", - "tokio-io", - "tokio-tcp", - "tokio-tls", -] - [[package]] name = "wepoll-sys" version = "3.0.1" @@ -12731,12 +12068,6 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983" -[[package]] -name = "winapi" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" - [[package]] name = "winapi" version = "0.3.9" @@ -12747,12 +12078,6 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu", ] -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" - [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" @@ -12765,7 +12090,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] @@ -12866,17 +12191,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" dependencies = [ - "winapi 0.3.9", -] - -[[package]] -name = "ws2_32-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" -dependencies = [ - "winapi 0.2.8", - "winapi-build", + "winapi", ] [[package]] @@ -12905,8 +12220,8 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c0608f53c1dc0bad505d03a34bbd49fbf2ad7b51eb036123e896365532745a1" dependencies = [ - "futures 0.3.21", - "log 0.4.16", + "futures", + "log", "nohash-hasher", "parking_lot 0.12.0", "rand 0.8.4", diff --git a/bin/node-template/node/Cargo.toml b/bin/node-template/node/Cargo.toml index e642ce3c0411e..ab91dc7990380 100644 --- a/bin/node-template/node/Cargo.toml +++ b/bin/node-template/node/Cargo.toml @@ -42,7 +42,7 @@ frame-system = { version = "4.0.0-dev", path = "../../../frame/system" } pallet-transaction-payment = { version = "4.0.0-dev", default-features = false, path = "../../../frame/transaction-payment" } # These dependencies are used for the node template's RPCs -jsonrpc-core = "18.0.0" +jsonrpsee = { version = "0.12.0", features = ["server"] } sc-rpc = { version = "4.0.0-dev", path = "../../../client/rpc" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sc-rpc-api = { version = "0.10.0-dev", path = "../../../client/rpc-api" } diff --git a/bin/node-template/node/src/rpc.rs b/bin/node-template/node/src/rpc.rs index 7f3701b5ab74f..7edae4d81474f 100644 --- a/bin/node-template/node/src/rpc.rs +++ b/bin/node-template/node/src/rpc.rs @@ -7,13 +7,15 @@ use std::sync::Arc; +use jsonrpsee::RpcModule; use node_template_runtime::{opaque::Block, AccountId, Balance, Index}; -pub use sc_rpc_api::DenyUnsafe; use sc_transaction_pool_api::TransactionPool; use sp_api::ProvideRuntimeApi; use sp_block_builder::BlockBuilder; use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}; +pub use sc_rpc_api::DenyUnsafe; + /// Full client dependencies. pub struct FullDeps { /// The client instance to use. @@ -25,7 +27,9 @@ pub struct FullDeps { } /// Instantiate all full RPC extensions. -pub fn create_full(deps: FullDeps) -> jsonrpc_core::IoHandler +pub fn create_full( + deps: FullDeps, +) -> Result, Box> where C: ProvideRuntimeApi, C: HeaderBackend + HeaderMetadata + 'static, @@ -35,20 +39,19 @@ where C::Api: BlockBuilder, P: TransactionPool + 'static, { - use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApi}; - use substrate_frame_rpc_system::{FullSystem, SystemApi}; + use pallet_transaction_payment_rpc::{TransactionPaymentApiServer, TransactionPaymentRpc}; + use substrate_frame_rpc_system::{SystemApiServer, SystemRpc}; - let mut io = jsonrpc_core::IoHandler::default(); + let mut module = RpcModule::new(()); let FullDeps { client, pool, deny_unsafe } = deps; - io.extend_with(SystemApi::to_delegate(FullSystem::new(client.clone(), pool, deny_unsafe))); - - io.extend_with(TransactionPaymentApi::to_delegate(TransactionPayment::new(client))); + module.merge(SystemRpc::new(client.clone(), pool.clone(), deny_unsafe).into_rpc())?; + module.merge(TransactionPaymentRpc::new(client).into_rpc())?; // Extend this RPC with a custom API by using the following syntax. // `YourRpcStruct` should have a reference to a client, which is needed // to call into the runtime. - // `io.extend_with(YourRpcTrait::to_delegate(YourRpcStruct::new(ReferenceToClient, ...)));` + // `module.merge(YourRpcTrait::into_rpc(YourRpcStruct::new(ReferenceToClient, ...)))?;` - io + Ok(module) } diff --git a/bin/node-template/node/src/service.rs b/bin/node-template/node/src/service.rs index 5f46a16a9668f..f45f914d94f44 100644 --- a/bin/node-template/node/src/service.rs +++ b/bin/node-template/node/src/service.rs @@ -228,8 +228,7 @@ pub fn new_full(mut config: Configuration) -> Result Box::new(move |deny_unsafe, _| { let deps = crate::rpc::FullDeps { client: client.clone(), pool: pool.clone(), deny_unsafe }; - - Ok(crate::rpc::create_full(deps)) + crate::rpc::create_full(deps).map_err(Into::into) }) }; @@ -239,7 +238,7 @@ pub fn new_full(mut config: Configuration) -> Result keystore: keystore_container.sync_keystore(), task_manager: &mut task_manager, transaction_pool: transaction_pool.clone(), - rpc_extensions_builder, + rpc_builder: rpc_extensions_builder, backend, system_rpc_tx, config, diff --git a/bin/node/cli/Cargo.toml b/bin/node/cli/Cargo.toml index 6bb36b9f9ab94..c18f2f5d1a108 100644 --- a/bin/node/cli/Cargo.toml +++ b/bin/node/cli/Cargo.toml @@ -37,6 +37,7 @@ crate-type = ["cdylib", "rlib"] clap = { version = "3.1.6", features = ["derive"], optional = true } codec = { package = "parity-scale-codec", version = "3.0.0" } serde = { version = "1.0.136", features = ["derive"] } +jsonrpsee = { version = "0.12.0", features = ["server"] } futures = "0.3.21" hex-literal = "0.3.4" log = "0.4.16" diff --git a/bin/node/cli/benches/block_production.rs b/bin/node/cli/benches/block_production.rs index 6eab08c39e5a2..376241d8157bf 100644 --- a/bin/node/cli/benches/block_production.rs +++ b/bin/node/cli/benches/block_production.rs @@ -92,6 +92,10 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase { rpc_cors: None, rpc_methods: Default::default(), rpc_max_payload: None, + rpc_max_request_size: None, + rpc_max_response_size: None, + rpc_id_provider: None, + rpc_max_subs_per_conn: None, ws_max_out_buffer_capacity: None, prometheus_config: None, telemetry_endpoints: None, diff --git a/bin/node/cli/benches/transaction_pool.rs b/bin/node/cli/benches/transaction_pool.rs index eb0e24d2fdd37..f1fce16d8c1b3 100644 --- a/bin/node/cli/benches/transaction_pool.rs +++ b/bin/node/cli/benches/transaction_pool.rs @@ -84,6 +84,10 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase { rpc_cors: None, rpc_methods: Default::default(), rpc_max_payload: None, + rpc_max_request_size: None, + rpc_max_response_size: None, + rpc_id_provider: None, + rpc_max_subs_per_conn: None, ws_max_out_buffer_capacity: None, prometheus_config: None, telemetry_endpoints: None, diff --git a/bin/node/cli/src/service.rs b/bin/node/cli/src/service.rs index 01c7eb9abe1b7..bff4be88002fb 100644 --- a/bin/node/cli/src/service.rs +++ b/bin/node/cli/src/service.rs @@ -134,7 +134,7 @@ pub fn new_partial( impl Fn( node_rpc::DenyUnsafe, sc_rpc::SubscriptionTaskExecutor, - ) -> Result, + ) -> Result, sc_service::Error>, ( sc_consensus_babe::BabeBlockImport, grandpa::LinkHalf, @@ -236,7 +236,7 @@ pub fn new_partial( let justification_stream = grandpa_link.justification_stream(); let shared_authority_set = grandpa_link.shared_authority_set().clone(); let shared_voter_state = grandpa::SharedVoterState::empty(); - let rpc_setup = shared_voter_state.clone(); + let shared_voter_state2 = shared_voter_state.clone(); let finality_proof_provider = grandpa::FinalityProofProvider::new_for_service( backend.clone(), @@ -277,7 +277,7 @@ pub fn new_partial( node_rpc::create_full(deps, rpc_backend.clone()).map_err(Into::into) }; - (rpc_extensions_builder, rpc_setup) + (rpc_extensions_builder, shared_voter_state2) }; Ok(sc_service::PartialComponents { @@ -332,7 +332,7 @@ pub fn new_full_base( keystore_container, select_chain, transaction_pool, - other: (rpc_extensions_builder, import_setup, rpc_setup, mut telemetry), + other: (rpc_builder, import_setup, rpc_setup, mut telemetry), } = new_partial(&config)?; let shared_voter_state = rpc_setup; @@ -386,7 +386,7 @@ pub fn new_full_base( client: client.clone(), keystore: keystore_container.sync_keystore(), network: network.clone(), - rpc_extensions_builder: Box::new(rpc_extensions_builder), + rpc_builder: Box::new(rpc_builder), transaction_pool: transaction_pool.clone(), task_manager: &mut task_manager, system_rpc_tx, diff --git a/bin/node/cli/tests/common.rs b/bin/node/cli/tests/common.rs index c17cabfa1d38a..9c739c2cf2d28 100644 --- a/bin/node/cli/tests/common.rs +++ b/bin/node/cli/tests/common.rs @@ -26,15 +26,14 @@ use nix::{ use node_primitives::Block; use remote_externalities::rpc_api; use std::{ + io::{BufRead, BufReader, Read}, ops::{Deref, DerefMut}, path::Path, - process::{Child, Command, ExitStatus}, + process::{self, Child, Command, ExitStatus}, time::Duration, }; use tokio::time::timeout; -static LOCALHOST_WS: &str = "ws://127.0.0.1:9944/"; - /// Wait for the given `child` the given number of `secs`. /// /// Returns the `Some(exit status)` or `None` if the process did not finish in the given time. @@ -63,8 +62,9 @@ pub fn wait_for(child: &mut Child, secs: u64) -> Result { pub async fn wait_n_finalized_blocks( n: usize, timeout_secs: u64, + url: &str, ) -> Result<(), tokio::time::error::Elapsed> { - timeout(Duration::from_secs(timeout_secs), wait_n_finalized_blocks_from(n, LOCALHOST_WS)).await + timeout(Duration::from_secs(timeout_secs), wait_n_finalized_blocks_from(n, url)).await } /// Wait for at least n blocks to be finalized from a specified node @@ -85,12 +85,23 @@ pub async fn wait_n_finalized_blocks_from(n: usize, url: &str) { /// Run the node for a while (3 blocks) pub async fn run_node_for_a_while(base_path: &Path, args: &[&str]) { - let mut cmd = Command::new(cargo_bin("substrate")); + let mut cmd = Command::new(cargo_bin("substrate")) + .stdout(process::Stdio::piped()) + .stderr(process::Stdio::piped()) + .args(args) + .arg("-d") + .arg(base_path) + .spawn() + .unwrap(); - let mut child = KillChildOnDrop(cmd.args(args).arg("-d").arg(base_path).spawn().unwrap()); + let stderr = cmd.stderr.take().unwrap(); + + let mut child = KillChildOnDrop(cmd); + + let (ws_url, _) = find_ws_url_from_output(stderr); // Let it produce some blocks. - let _ = wait_n_finalized_blocks(3, 30).await; + let _ = wait_n_finalized_blocks(3, 30, &ws_url).await; assert!(child.try_wait().unwrap().is_none(), "the process should still be running"); @@ -134,3 +145,30 @@ impl DerefMut for KillChildOnDrop { &mut self.0 } } + +/// Read the WS address from the output. +/// +/// This is hack to get the actual binded sockaddr because +/// substrate assigns a random port if the specified port was already binded. +pub fn find_ws_url_from_output(read: impl Read + Send) -> (String, String) { + let mut data = String::new(); + + let ws_url = BufReader::new(read) + .lines() + .find_map(|line| { + let line = + line.expect("failed to obtain next line from stdout for WS address discovery"); + data.push_str(&line); + + // does the line contain our port (we expect this specific output from substrate). + let sock_addr = match line.split_once("Running JSON-RPC WS server: addr=") { + None => return None, + Some((_, after)) => after.split_once(",").unwrap().0, + }; + + Some(format!("ws://{}", sock_addr)) + }) + .expect("We should get a WebSocket address"); + + (ws_url, data) +} diff --git a/bin/node/cli/tests/running_the_node_and_interrupt.rs b/bin/node/cli/tests/running_the_node_and_interrupt.rs index 35f0fc106613c..6d4a4b40425c4 100644 --- a/bin/node/cli/tests/running_the_node_and_interrupt.rs +++ b/bin/node/cli/tests/running_the_node_and_interrupt.rs @@ -25,7 +25,7 @@ use nix::{ }, unistd::Pid, }; -use std::process::{Child, Command}; +use std::process::{self, Child, Command}; use tempfile::tempdir; pub mod common; @@ -36,6 +36,8 @@ async fn running_the_node_works_and_can_be_interrupted() { let base_path = tempdir().expect("could not create a temp dir"); let mut cmd = common::KillChildOnDrop( Command::new(cargo_bin("substrate")) + .stdout(process::Stdio::piped()) + .stderr(process::Stdio::piped()) .args(&["--dev", "-d"]) .arg(base_path.path()) .arg("--db=paritydb") @@ -44,7 +46,13 @@ async fn running_the_node_works_and_can_be_interrupted() { .unwrap(), ); - common::wait_n_finalized_blocks(3, 30).await.unwrap(); + let stderr = cmd.stderr.take().unwrap(); + + let (ws_url, _) = common::find_ws_url_from_output(stderr); + + common::wait_n_finalized_blocks(3, 30, &ws_url) + .await + .expect("Blocks are produced in time"); assert!(cmd.try_wait().unwrap().is_none(), "the process should still be running"); kill(Pid::from_raw(cmd.id().try_into().unwrap()), signal).unwrap(); assert_eq!( @@ -69,6 +77,8 @@ async fn running_the_node_works_and_can_be_interrupted() { async fn running_two_nodes_with_the_same_ws_port_should_work() { fn start_node() -> Child { Command::new(cargo_bin("substrate")) + .stdout(process::Stdio::piped()) + .stderr(process::Stdio::piped()) .args(&["--dev", "--tmp", "--ws-port=45789", "--no-hardware-benchmarks"]) .spawn() .unwrap() @@ -77,7 +87,10 @@ async fn running_two_nodes_with_the_same_ws_port_should_work() { let mut first_node = common::KillChildOnDrop(start_node()); let mut second_node = common::KillChildOnDrop(start_node()); - let _ = common::wait_n_finalized_blocks(3, 30).await; + let stderr = first_node.stderr.take().unwrap(); + let (ws_url, _) = common::find_ws_url_from_output(stderr); + + common::wait_n_finalized_blocks(3, 30, &ws_url).await.unwrap(); assert!(first_node.try_wait().unwrap().is_none(), "The first node should still be running"); assert!(second_node.try_wait().unwrap().is_none(), "The second node should still be running"); diff --git a/bin/node/cli/tests/temp_base_path_works.rs b/bin/node/cli/tests/temp_base_path_works.rs index df293161e3234..98422a21f5308 100644 --- a/bin/node/cli/tests/temp_base_path_works.rs +++ b/bin/node/cli/tests/temp_base_path_works.rs @@ -43,8 +43,11 @@ async fn temp_base_path_works() { .unwrap(), ); + let mut stderr = child.stderr.take().unwrap(); + let (ws_url, mut data) = common::find_ws_url_from_output(&mut stderr); + // Let it produce some blocks. - common::wait_n_finalized_blocks(3, 30).await.unwrap(); + common::wait_n_finalized_blocks(3, 30, &ws_url).await.unwrap(); assert!(child.try_wait().unwrap().is_none(), "the process should still be running"); // Stop the process @@ -52,10 +55,9 @@ async fn temp_base_path_works() { assert!(common::wait_for(&mut child, 40).map(|x| x.success()).unwrap_or_default()); // Ensure the database has been deleted - let mut stderr = String::new(); - child.stderr.as_mut().unwrap().read_to_string(&mut stderr).unwrap(); + stderr.read_to_string(&mut data).unwrap(); let re = Regex::new(r"Database: .+ at (\S+)").unwrap(); - let db_path = PathBuf::from(re.captures(stderr.as_str()).unwrap().get(1).unwrap().as_str()); + let db_path = PathBuf::from(re.captures(data.as_str()).unwrap().get(1).unwrap().as_str()); assert!(!db_path.exists()); } diff --git a/bin/node/rpc/Cargo.toml b/bin/node/rpc/Cargo.toml index f0ae8b42e6398..9520c621d3165 100644 --- a/bin/node/rpc/Cargo.toml +++ b/bin/node/rpc/Cargo.toml @@ -11,7 +11,7 @@ repository = "https://github.com/paritytech/substrate/" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpc-core = "18.0.0" +jsonrpsee = { version = "0.12.0", features = ["server"] } node-primitives = { version = "2.0.0", path = "../primitives" } pallet-contracts-rpc = { version = "4.0.0-dev", path = "../../../frame/contracts/rpc/" } pallet-mmr-rpc = { version = "3.0.0", path = "../../../frame/merkle-mountain-range/rpc/" } diff --git a/bin/node/rpc/src/lib.rs b/bin/node/rpc/src/lib.rs index b8349e26cd1da..05aa973e102b1 100644 --- a/bin/node/rpc/src/lib.rs +++ b/bin/node/rpc/src/lib.rs @@ -29,18 +29,20 @@ //! be placed here or imported from corresponding FRAME RPC definitions. #![warn(missing_docs)] +#![warn(unused_crate_dependencies)] use std::sync::Arc; +use jsonrpsee::RpcModule; use node_primitives::{AccountId, Balance, Block, BlockNumber, Hash, Index}; use sc_client_api::AuxStore; use sc_consensus_babe::{Config, Epoch}; -use sc_consensus_babe_rpc::BabeRpcHandler; +use sc_consensus_babe_rpc::BabeRpc; use sc_consensus_epochs::SharedEpochChanges; use sc_finality_grandpa::{ FinalityProofProvider, GrandpaJustificationStream, SharedAuthoritySet, SharedVoterState, }; -use sc_finality_grandpa_rpc::GrandpaRpcHandler; +use sc_finality_grandpa_rpc::GrandpaRpc; use sc_rpc::SubscriptionTaskExecutor; pub use sc_rpc_api::DenyUnsafe; use sc_transaction_pool_api::TransactionPool; @@ -93,14 +95,11 @@ pub struct FullDeps { pub grandpa: GrandpaDeps, } -/// A IO handler that uses all Full RPC extensions. -pub type IoHandler = jsonrpc_core::IoHandler; - /// Instantiate all Full RPC extensions. pub fn create_full( deps: FullDeps, backend: Arc, -) -> Result, Box> +) -> Result, Box> where C: ProvideRuntimeApi + sc_client_api::BlockBackend @@ -121,13 +120,17 @@ where B: sc_client_api::Backend + Send + Sync + 'static, B::State: sc_client_api::backend::StateBackend>, { - use pallet_contracts_rpc::{Contracts, ContractsApi}; - use pallet_mmr_rpc::{Mmr, MmrApi}; - use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApi}; - use sc_rpc::dev::{Dev, DevApi}; - use substrate_frame_rpc_system::{FullSystem, SystemApi}; + use pallet_contracts_rpc::{ContractsApiServer, ContractsRpc}; + use pallet_mmr_rpc::{MmrApiServer, MmrRpc}; + use pallet_transaction_payment_rpc::{TransactionPaymentApiServer, TransactionPaymentRpc}; + use sc_consensus_babe_rpc::BabeApiServer; + use sc_finality_grandpa_rpc::GrandpaApiServer; + use sc_rpc::dev::{Dev, DevApiServer}; + use sc_sync_state_rpc::{SyncStateRpc, SyncStateRpcApiServer}; + use substrate_frame_rpc_system::{SystemApiServer, SystemRpc}; + use substrate_state_trie_migration_rpc::StateMigrationApiServer; - let mut io = jsonrpc_core::IoHandler::default(); + let mut io = RpcModule::new(()); let FullDeps { client, pool, select_chain, chain_spec, deny_unsafe, babe, grandpa } = deps; let BabeDeps { keystore, babe_config, shared_epoch_changes } = babe; @@ -139,40 +142,45 @@ where finality_provider, } = grandpa; - io.extend_with(SystemApi::to_delegate(FullSystem::new(client.clone(), pool, deny_unsafe))); + io.merge(SystemRpc::new(client.clone(), pool, deny_unsafe).into_rpc())?; // Making synchronous calls in light client freezes the browser currently, // more context: https://github.com/paritytech/substrate/pull/3480 // These RPCs should use an asynchronous caller instead. - io.extend_with(ContractsApi::to_delegate(Contracts::new(client.clone()))); - io.extend_with(MmrApi::to_delegate(Mmr::new(client.clone()))); - io.extend_with(TransactionPaymentApi::to_delegate(TransactionPayment::new(client.clone()))); - io.extend_with(sc_consensus_babe_rpc::BabeApi::to_delegate(BabeRpcHandler::new( - client.clone(), - shared_epoch_changes.clone(), - keystore, - babe_config, - select_chain, - deny_unsafe, - ))); - io.extend_with(sc_finality_grandpa_rpc::GrandpaApi::to_delegate(GrandpaRpcHandler::new( - shared_authority_set.clone(), - shared_voter_state, - justification_stream, - subscription_executor, - finality_provider, - ))); - io.extend_with(substrate_state_trie_migration_rpc::StateMigrationApi::to_delegate( - substrate_state_trie_migration_rpc::MigrationRpc::new(client.clone(), backend, deny_unsafe), - )); - io.extend_with(sc_sync_state_rpc::SyncStateRpcApi::to_delegate( - sc_sync_state_rpc::SyncStateRpcHandler::new( - chain_spec, + io.merge(ContractsRpc::new(client.clone()).into_rpc())?; + io.merge(MmrRpc::new(client.clone()).into_rpc())?; + io.merge(TransactionPaymentRpc::new(client.clone()).into_rpc())?; + io.merge( + BabeRpc::new( client.clone(), - shared_authority_set, - shared_epoch_changes, - )?, - )); - io.extend_with(DevApi::to_delegate(Dev::new(client, deny_unsafe))); + shared_epoch_changes.clone(), + keystore, + babe_config, + select_chain, + deny_unsafe, + ) + .into_rpc(), + )?; + io.merge( + GrandpaRpc::new( + subscription_executor, + shared_authority_set.clone(), + shared_voter_state, + justification_stream, + finality_provider, + ) + .into_rpc(), + )?; + + io.merge( + SyncStateRpc::new(chain_spec, client.clone(), shared_authority_set, shared_epoch_changes)? + .into_rpc(), + )?; + + io.merge( + substrate_state_trie_migration_rpc::MigrationRpc::new(client.clone(), backend, deny_unsafe) + .into_rpc(), + )?; + io.merge(Dev::new(client, deny_unsafe).into_rpc())?; Ok(io) } diff --git a/client/beefy/Cargo.toml b/client/beefy/Cargo.toml index 4b6496e52c2f2..bd25496f2dfea 100644 --- a/client/beefy/Cargo.toml +++ b/client/beefy/Cargo.toml @@ -41,7 +41,7 @@ sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } serde = "1.0.136" strum = { version = "0.23", features = ["derive"] } tempfile = "3.1.0" -tokio = "1.15" +tokio = "1.17.0" sc-consensus = { version = "0.10.0-dev", path = "../consensus/common" } sc-network-test = { version = "0.8.0", path = "../network/test" } sp-finality-grandpa = { version = "4.0.0-dev", path = "../../primitives/finality-grandpa" } diff --git a/client/beefy/rpc/Cargo.toml b/client/beefy/rpc/Cargo.toml index adf29f1cef732..f8ca6470f267a 100644 --- a/client/beefy/rpc/Cargo.toml +++ b/client/beefy/rpc/Cargo.toml @@ -11,10 +11,7 @@ homepage = "https://substrate.io" [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } futures = "0.3.21" -jsonrpc-core = "18.0.0" -jsonrpc-core-client = "18.0.0" -jsonrpc-derive = "18.0.0" -jsonrpc-pubsub = "18.0.0" +jsonrpsee = { version = "0.12.0", features = ["server", "macros"] } log = "0.4" parking_lot = "0.12.0" serde = { version = "1.0.136", features = ["derive"] } @@ -32,3 +29,4 @@ sc-rpc = { version = "4.0.0-dev", features = [ "test-helpers", ], path = "../../rpc" } substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } +tokio = { version = "1.17.0", features = ["macros"] } diff --git a/client/beefy/rpc/src/lib.rs b/client/beefy/rpc/src/lib.rs index e49af3352ae4e..e4c8c76419ccb 100644 --- a/client/beefy/rpc/src/lib.rs +++ b/client/beefy/rpc/src/lib.rs @@ -23,19 +23,22 @@ use parking_lot::RwLock; use std::sync::Arc; +use sc_rpc::SubscriptionTaskExecutor; use sp_runtime::traits::Block as BlockT; -use futures::{task::SpawnError, FutureExt, SinkExt, StreamExt, TryFutureExt}; -use jsonrpc_derive::rpc; -use jsonrpc_pubsub::{manager::SubscriptionManager, typed::Subscriber, SubscriptionId}; +use futures::{task::SpawnError, FutureExt, StreamExt}; +use jsonrpsee::{ + core::{async_trait, Error as JsonRpseeError, RpcResult}, + proc_macros::rpc, + types::{error::CallError, ErrorObject}, + PendingSubscription, +}; use log::warn; use beefy_gadget::notification::{BeefyBestBlockStream, BeefySignedCommitmentStream}; mod notification; -type FutureResult = jsonrpc_core::BoxFuture>; - #[derive(Debug, thiserror::Error)] /// Top-level error type for the RPC handler pub enum Error { @@ -64,195 +67,149 @@ impl From for ErrorCode { } } -impl From for jsonrpc_core::Error { +impl From for JsonRpseeError { fn from(error: Error) -> Self { - let message = format!("{}", error); + let message = error.to_string(); let code = ErrorCode::from(error); - jsonrpc_core::Error { + JsonRpseeError::Call(CallError::Custom(ErrorObject::owned( + code as i32, message, - code: jsonrpc_core::ErrorCode::ServerError(code as i64), - data: None, - } + None::<()>, + ))) } } -/// Provides RPC methods for interacting with BEEFY. -#[rpc] +// Provides RPC methods for interacting with BEEFY. +#[rpc(client, server)] pub trait BeefyApi { - /// RPC Metadata - type Metadata; - /// Returns the block most recently finalized by BEEFY, alongside side its justification. - #[pubsub( - subscription = "beefy_justifications", - subscribe, - name = "beefy_subscribeJustifications" - )] - fn subscribe_justifications( - &self, - metadata: Self::Metadata, - subscriber: Subscriber, - ); - - /// Unsubscribe from receiving notifications about recently finalized blocks. - #[pubsub( - subscription = "beefy_justifications", - unsubscribe, - name = "beefy_unsubscribeJustifications" + #[subscription( + name = "beefy_subscribeJustifications" => "beefy_justifications", + unsubscribe = "beefy_unsubscribeJustifications", + item = Notification, )] - fn unsubscribe_justifications( - &self, - metadata: Option, - id: SubscriptionId, - ) -> jsonrpc_core::Result; + fn subscribe_justifications(&self); /// Returns hash of the latest BEEFY finalized block as seen by this client. /// /// The latest BEEFY block might not be available if the BEEFY gadget is not running /// in the network or if the client is still initializing or syncing with the network. /// In such case an error would be returned. - #[rpc(name = "beefy_getFinalizedHead")] - fn latest_finalized(&self) -> FutureResult; + #[method(name = "beefy_getFinalizedHead")] + async fn latest_finalized(&self) -> RpcResult; } /// Implements the BeefyApi RPC trait for interacting with BEEFY. pub struct BeefyRpcHandler { signed_commitment_stream: BeefySignedCommitmentStream, beefy_best_block: Arc>>, - manager: SubscriptionManager, + executor: SubscriptionTaskExecutor, } -impl BeefyRpcHandler { +impl BeefyRpcHandler +where + Block: BlockT, +{ /// Creates a new BeefyRpcHandler instance. - pub fn new( + pub fn new( signed_commitment_stream: BeefySignedCommitmentStream, best_block_stream: BeefyBestBlockStream, - executor: E, - ) -> Result - where - E: futures::task::Spawn + Send + Sync + 'static, - { + executor: SubscriptionTaskExecutor, + ) -> Result { let beefy_best_block = Arc::new(RwLock::new(None)); let stream = best_block_stream.subscribe(); let closure_clone = beefy_best_block.clone(); let future = stream.for_each(move |best_beefy| { let async_clone = closure_clone.clone(); - async move { - *async_clone.write() = Some(best_beefy); - } + async move { *async_clone.write() = Some(best_beefy) } }); - executor - .spawn_obj(futures::task::FutureObj::new(Box::pin(future))) - .map_err(|e| { - log::error!("Failed to spawn BEEFY RPC background task; err: {}", e); - e - })?; - - let manager = SubscriptionManager::new(Arc::new(executor)); - Ok(Self { signed_commitment_stream, beefy_best_block, manager }) + executor.spawn("substrate-rpc-subscription", Some("rpc"), future.map(drop).boxed()); + Ok(Self { signed_commitment_stream, beefy_best_block, executor }) } } -impl BeefyApi for BeefyRpcHandler +#[async_trait] +impl BeefyApiServer + for BeefyRpcHandler where Block: BlockT, { - type Metadata = sc_rpc::Metadata; - - fn subscribe_justifications( - &self, - _metadata: Self::Metadata, - subscriber: Subscriber, - ) { + fn subscribe_justifications(&self, pending: PendingSubscription) { let stream = self .signed_commitment_stream .subscribe() - .map(|x| Ok::<_, ()>(Ok(notification::EncodedSignedCommitment::new::(x)))); + .map(|sc| notification::EncodedSignedCommitment::new::(sc)); - self.manager.add(subscriber, |sink| { - stream - .forward(sink.sink_map_err(|e| warn!("Error sending notifications: {:?}", e))) - .map(|_| ()) - }); - } + let fut = async move { + if let Some(mut sink) = pending.accept() { + sink.pipe_from_stream(stream).await; + } + } + .boxed(); - fn unsubscribe_justifications( - &self, - _metadata: Option, - id: SubscriptionId, - ) -> jsonrpc_core::Result { - Ok(self.manager.cancel(id)) + self.executor + .spawn("substrate-rpc-subscription", Some("rpc"), fut.map(drop).boxed()); } - fn latest_finalized(&self) -> FutureResult { - let result: Result = self - .beefy_best_block + async fn latest_finalized(&self) -> RpcResult { + self.beefy_best_block .read() .as_ref() .cloned() - .ok_or_else(|| Error::EndpointNotReady.into()); - let future = async move { result }.boxed(); - future.map_err(jsonrpc_core::Error::from).boxed() + .ok_or(Error::EndpointNotReady) + .map_err(Into::into) } } #[cfg(test)] mod tests { use super::*; - use jsonrpc_core::{types::Params, Notification, Output}; - use beefy_gadget::notification::{BeefySignedCommitment, BeefySignedCommitmentSender}; + use beefy_gadget::notification::{ + BeefyBestBlockStream, BeefySignedCommitment, BeefySignedCommitmentSender, + }; use beefy_primitives::{known_payload_ids, Payload}; use codec::{Decode, Encode}; + use jsonrpsee::{types::EmptyParams, RpcModule}; use sp_runtime::traits::{BlakeTwo256, Hash}; use substrate_test_runtime_client::runtime::Block; - fn setup_io_handler( - ) -> (jsonrpc_core::MetaIoHandler, BeefySignedCommitmentSender) { + fn setup_io_handler() -> (RpcModule>, BeefySignedCommitmentSender) + { let (_, stream) = BeefyBestBlockStream::::channel(); setup_io_handler_with_best_block_stream(stream) } fn setup_io_handler_with_best_block_stream( best_block_stream: BeefyBestBlockStream, - ) -> (jsonrpc_core::MetaIoHandler, BeefySignedCommitmentSender) { + ) -> (RpcModule>, BeefySignedCommitmentSender) { let (commitment_sender, commitment_stream) = BeefySignedCommitmentStream::::channel(); - let handler: BeefyRpcHandler = BeefyRpcHandler::new( + let handler = BeefyRpcHandler::new( commitment_stream, best_block_stream, - sc_rpc::testing::TaskExecutor, + sc_rpc::testing::test_executor(), ) - .unwrap(); + .expect("Setting up the BEEFY RPC handler works"); - let mut io = jsonrpc_core::MetaIoHandler::default(); - io.extend_with(BeefyApi::to_delegate(handler)); - - (io, commitment_sender) - } - - fn setup_session() -> (sc_rpc::Metadata, futures::channel::mpsc::UnboundedReceiver) { - let (tx, rx) = futures::channel::mpsc::unbounded(); - let meta = sc_rpc::Metadata::new(tx); - (meta, rx) + (handler.into_rpc(), commitment_sender) } - #[test] - fn uninitialized_rpc_handler() { - let (io, _) = setup_io_handler(); - + #[tokio::test] + async fn uninitialized_rpc_handler() { + let (rpc, _) = setup_io_handler(); let request = r#"{"jsonrpc":"2.0","method":"beefy_getFinalizedHead","params":[],"id":1}"#; - let response = r#"{"jsonrpc":"2.0","error":{"code":1,"message":"BEEFY RPC endpoint not ready"},"id":1}"#; + let expected_response = r#"{"jsonrpc":"2.0","error":{"code":1,"message":"BEEFY RPC endpoint not ready"},"id":1}"#.to_string(); + let (result, _) = rpc.raw_json_request(&request).await.unwrap(); - let meta = sc_rpc::Metadata::default(); - assert_eq!(Some(response.into()), io.handle_request_sync(request, meta)); + assert_eq!(expected_response, result,); } - #[test] - fn latest_finalized_rpc() { + #[tokio::test] + async fn latest_finalized_rpc() { let (sender, stream) = BeefyBestBlockStream::::channel(); let (io, _) = setup_io_handler_with_best_block_stream(stream); @@ -266,83 +223,78 @@ mod tests { \"jsonrpc\":\"2.0\",\ \"result\":\"0x2f0039e93a27221fcf657fb877a1d4f60307106113e885096cb44a461cd0afbf\",\ \"id\":1\ - }"; + }" + .to_string(); let not_ready = "{\ \"jsonrpc\":\"2.0\",\ \"error\":{\"code\":1,\"message\":\"BEEFY RPC endpoint not ready\"},\ \"id\":1\ - }"; + }" + .to_string(); let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2); while std::time::Instant::now() < deadline { - let meta = sc_rpc::Metadata::default(); - let response = io.handle_request_sync(request, meta); - // Retry "not ready" responses. - if response != Some(not_ready.into()) { - assert_eq!(response, Some(expected.into())); + let (response, _) = io.raw_json_request(request).await.expect("RPC requests work"); + if response != not_ready { + assert_eq!(response, expected); // Success return } - std::thread::sleep(std::time::Duration::from_millis(50)); + std::thread::sleep(std::time::Duration::from_millis(50)) } + panic!( "Deadline reached while waiting for best BEEFY block to update. Perhaps the background task is broken?" ); } - #[test] - fn subscribe_and_unsubscribe_to_justifications() { - let (io, _) = setup_io_handler(); - let (meta, _) = setup_session(); + #[tokio::test] + async fn subscribe_and_unsubscribe_to_justifications() { + let (rpc, _) = setup_io_handler(); - // Subscribe - let sub_request = - r#"{"jsonrpc":"2.0","method":"beefy_subscribeJustifications","params":[],"id":1}"#; - let resp = io.handle_request_sync(sub_request, meta.clone()); - let resp: Output = serde_json::from_str(&resp.unwrap()).unwrap(); + // Subscribe call. + let sub = rpc + .subscribe("beefy_subscribeJustifications", EmptyParams::new()) + .await + .unwrap(); - let sub_id = match resp { - Output::Success(success) => success.result, - _ => panic!(), - }; + let ser_id = serde_json::to_string(sub.subscription_id()).unwrap(); // Unsubscribe let unsub_req = format!( - r#"{{"jsonrpc":"2.0","method":"beefy_unsubscribeJustifications","params":[{}],"id":1}}"#, - sub_id - ); - assert_eq!( - io.handle_request_sync(&unsub_req, meta.clone()), - Some(r#"{"jsonrpc":"2.0","result":true,"id":1}"#.into()), + "{{\"jsonrpc\":\"2.0\",\"method\":\"beefy_unsubscribeJustifications\",\"params\":[{}],\"id\":1}}", + ser_id ); + let (response, _) = rpc.raw_json_request(&unsub_req).await.unwrap(); + + assert_eq!(response, r#"{"jsonrpc":"2.0","result":true,"id":1}"#); // Unsubscribe again and fail - assert_eq!( - io.handle_request_sync(&unsub_req, meta), - Some(r#"{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid subscription id."},"id":1}"#.into()), - ); - } + let (response, _) = rpc.raw_json_request(&unsub_req).await.unwrap(); + let expected = r#"{"jsonrpc":"2.0","result":false,"id":1}"#; - #[test] - fn subscribe_and_unsubscribe_with_wrong_id() { - let (io, _) = setup_io_handler(); - let (meta, _) = setup_session(); + assert_eq!(response, expected); + } - // Subscribe - let sub_request = - r#"{"jsonrpc":"2.0","method":"beefy_subscribeJustifications","params":[],"id":1}"#; - let resp = io.handle_request_sync(sub_request, meta.clone()); - let resp: Output = serde_json::from_str(&resp.unwrap()).unwrap(); - assert!(matches!(resp, Output::Success(_))); + #[tokio::test] + async fn subscribe_and_unsubscribe_with_wrong_id() { + let (rpc, _) = setup_io_handler(); + // Subscribe call. + let _sub = rpc + .subscribe("beefy_subscribeJustifications", EmptyParams::new()) + .await + .unwrap(); // Unsubscribe with wrong ID - assert_eq!( - io.handle_request_sync( + let (response, _) = rpc + .raw_json_request( r#"{"jsonrpc":"2.0","method":"beefy_unsubscribeJustifications","params":["FOO"],"id":1}"#, - meta.clone() - ), - Some(r#"{"jsonrpc":"2.0","error":{"code":-32602,"message":"Invalid subscription id."},"id":1}"#.into()) - ); + ) + .await + .unwrap(); + let expected = r#"{"jsonrpc":"2.0","result":false,"id":1}"#; + + assert_eq!(response, expected); } fn create_commitment() -> BeefySignedCommitment { @@ -357,18 +309,15 @@ mod tests { } } - #[test] - fn subscribe_and_listen_to_one_justification() { - let (io, commitment_sender) = setup_io_handler(); - let (meta, receiver) = setup_session(); + #[tokio::test] + async fn subscribe_and_listen_to_one_justification() { + let (rpc, commitment_sender) = setup_io_handler(); // Subscribe - let sub_request = - r#"{"jsonrpc":"2.0","method":"beefy_subscribeJustifications","params":[],"id":1}"#; - - let resp = io.handle_request_sync(sub_request, meta.clone()); - let mut resp: serde_json::Value = serde_json::from_str(&resp.unwrap()).unwrap(); - let sub_id: String = serde_json::from_value(resp["result"].take()).unwrap(); + let mut sub = rpc + .subscribe("beefy_subscribeJustifications", EmptyParams::new()) + .await + .unwrap(); // Notify with commitment let commitment = create_commitment(); @@ -376,21 +325,10 @@ mod tests { r.unwrap(); // Inspect what we received - let recv = futures::executor::block_on(receiver.take(1).collect::>()); - let recv: Notification = serde_json::from_str(&recv[0]).unwrap(); - let mut json_map = match recv.params { - Params::Map(json_map) => json_map, - _ => panic!(), - }; - - let recv_sub_id: String = serde_json::from_value(json_map["subscription"].take()).unwrap(); - let recv_commitment: sp_core::Bytes = - serde_json::from_value(json_map["result"].take()).unwrap(); + let (bytes, recv_sub_id) = sub.next::().await.unwrap().unwrap(); let recv_commitment: BeefySignedCommitment = - Decode::decode(&mut &recv_commitment[..]).unwrap(); - - assert_eq!(recv.method, "beefy_justifications"); - assert_eq!(recv_sub_id, sub_id); + Decode::decode(&mut &bytes[..]).unwrap(); + assert_eq!(&recv_sub_id, sub.subscription_id()); assert_eq!(recv_commitment, commitment); } } diff --git a/client/cli/src/commands/run_cmd.rs b/client/cli/src/commands/run_cmd.rs index dea1e2a6dae5b..6cb0de0ebd04c 100644 --- a/client/cli/src/commands/run_cmd.rs +++ b/client/cli/src/commands/run_cmd.rs @@ -100,18 +100,28 @@ pub struct RunCmd { #[clap(long)] pub unsafe_ws_external: bool, - /// Set the the maximum RPC payload size for both requests and responses (both http and ws), in - /// megabytes. Default is 15MiB. + /// DEPRECATED, this has no affect anymore. Use `rpc_max_request_size` or + /// `rpc_max_response_size` instead. #[clap(long)] pub rpc_max_payload: Option, + /// Set the the maximum RPC request payload size for both HTTP and WS in megabytes. + /// Default is 15MiB. + #[clap(long)] + pub rpc_max_request_size: Option, + + /// Set the the maximum RPC response payload size for both HTTP and WS in megabytes. + /// Default is 15MiB. + #[clap(long)] + pub rpc_max_response_size: Option, + /// Expose Prometheus exporter on all interfaces. /// /// Default is local. #[clap(long)] pub prometheus_external: bool, - /// Specify IPC RPC server path + /// DEPRECATED, IPC support has been removed. #[clap(long, value_name = "PATH")] pub ipc_path: Option, @@ -127,7 +137,7 @@ pub struct RunCmd { #[clap(long, value_name = "COUNT")] pub ws_max_connections: Option, - /// Set the the maximum WebSocket output buffer size in MiB. Default is 16. + /// DEPRECATED, this has no affect anymore. Use `rpc_max_response_size` instead. #[clap(long)] pub ws_max_out_buffer_capacity: Option, diff --git a/client/cli/src/config.rs b/client/cli/src/config.rs index aef1da8193757..e38d34b92c74d 100644 --- a/client/cli/src/config.rs +++ b/client/cli/src/config.rs @@ -359,6 +359,16 @@ pub trait CliConfiguration: Sized { Ok(None) } + /// Get maximum RPC request payload size. + fn rpc_max_request_size(&self) -> Result> { + Ok(None) + } + + /// Get maximum RPC response payload size. + fn rpc_max_response_size(&self) -> Result> { + Ok(None) + } + /// Get maximum WS output buffer capacity. fn ws_max_out_buffer_capacity(&self) -> Result> { Ok(None) @@ -526,6 +536,10 @@ pub trait CliConfiguration: Sized { rpc_ws_max_connections: self.rpc_ws_max_connections()?, rpc_cors: self.rpc_cors(is_dev)?, rpc_max_payload: self.rpc_max_payload()?, + rpc_max_request_size: self.rpc_max_request_size()?, + rpc_max_response_size: self.rpc_max_response_size()?, + rpc_id_provider: None, + rpc_max_subs_per_conn: None, ws_max_out_buffer_capacity: self.ws_max_out_buffer_capacity()?, prometheus_config: self .prometheus_config(DCV::prometheus_listen_port(), &chain_spec)?, diff --git a/client/consensus/babe/rpc/Cargo.toml b/client/consensus/babe/rpc/Cargo.toml index 0e7141c77f8b2..4be5d1f8bba90 100644 --- a/client/consensus/babe/rpc/Cargo.toml +++ b/client/consensus/babe/rpc/Cargo.toml @@ -13,10 +13,8 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +jsonrpsee = { version = "0.12.0", features = ["server", "macros"] } futures = "0.3.21" -jsonrpc-core = "18.0.0" -jsonrpc-core-client = "18.0.0" -jsonrpc-derive = "18.0.0" serde = { version = "1.0.136", features = ["derive"] } thiserror = "1.0" sc-consensus-babe = { version = "0.10.0-dev", path = "../" } @@ -34,6 +32,7 @@ sp-runtime = { version = "6.0.0", path = "../../../../primitives/runtime" } [dev-dependencies] serde_json = "1.0.79" tempfile = "3.1.0" +tokio = "1.17.0" sc-consensus = { version = "0.10.0-dev", path = "../../../consensus/common" } sc-keystore = { version = "4.0.0-dev", path = "../../../keystore" } sp-keyring = { version = "6.0.0", path = "../../../../primitives/keyring" } diff --git a/client/consensus/babe/rpc/src/lib.rs b/client/consensus/babe/rpc/src/lib.rs index 2d0c81afc7775..d5f21606c62ed 100644 --- a/client/consensus/babe/rpc/src/lib.rs +++ b/client/consensus/babe/rpc/src/lib.rs @@ -18,9 +18,13 @@ //! RPC api for babe. -use futures::{FutureExt, TryFutureExt}; -use jsonrpc_core::Error as RpcError; -use jsonrpc_derive::rpc; +use futures::TryFutureExt; +use jsonrpsee::{ + core::{async_trait, Error as JsonRpseeError, RpcResult}, + proc_macros::rpc, + types::{error::CallError, ErrorObject}, +}; + use sc_consensus_babe::{authorship, Config, Epoch}; use sc_consensus_epochs::{descendent_query, Epoch as EpochT, SharedEpochChanges}; use sc_rpc_api::DenyUnsafe; @@ -35,19 +39,17 @@ use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; use sp_runtime::traits::{Block as BlockT, Header as _}; use std::{collections::HashMap, sync::Arc}; -type FutureResult = jsonrpc_core::BoxFuture>; - /// Provides rpc methods for interacting with Babe. -#[rpc] +#[rpc(client, server)] pub trait BabeApi { /// Returns data about which slots (primary or secondary) can be claimed in the current epoch /// with the keys in the keystore. - #[rpc(name = "babe_epochAuthorship")] - fn epoch_authorship(&self) -> FutureResult>; + #[method(name = "babe_epochAuthorship")] + async fn epoch_authorship(&self) -> RpcResult>; } -/// Implements the BabeRpc trait for interacting with Babe. -pub struct BabeRpcHandler { +/// Provides RPC methods for interacting with Babe. +pub struct BabeRpc { /// shared reference to the client. client: Arc, /// shared reference to EpochChanges @@ -62,7 +64,7 @@ pub struct BabeRpcHandler { deny_unsafe: DenyUnsafe, } -impl BabeRpcHandler { +impl BabeRpc { /// Creates a new instance of the BabeRpc handler. pub fn new( client: Arc, @@ -76,7 +78,8 @@ impl BabeRpcHandler { } } -impl BabeApi for BabeRpcHandler +#[async_trait] +impl BabeApiServer for BabeRpc where B: BlockT, C: ProvideRuntimeApi @@ -86,71 +89,63 @@ where C::Api: BabeRuntimeApi, SC: SelectChain + Clone + 'static, { - fn epoch_authorship(&self) -> FutureResult> { - if let Err(err) = self.deny_unsafe.check_if_safe() { - return async move { Err(err.into()) }.boxed() - } - - let (babe_config, keystore, shared_epoch, client, select_chain) = ( - self.babe_config.clone(), - self.keystore.clone(), - self.shared_epoch_changes.clone(), - self.client.clone(), - self.select_chain.clone(), - ); - - async move { - let header = select_chain.best_chain().map_err(Error::Consensus).await?; - let epoch_start = client - .runtime_api() - .current_epoch_start(&BlockId::Hash(header.hash())) - .map_err(|err| Error::StringError(err.to_string()))?; - let epoch = - epoch_data(&shared_epoch, &client, &babe_config, *epoch_start, &select_chain) - .await?; - let (epoch_start, epoch_end) = (epoch.start_slot(), epoch.end_slot()); - - let mut claims: HashMap = HashMap::new(); - - let keys = { - epoch - .authorities - .iter() - .enumerate() - .filter_map(|(i, a)| { - if SyncCryptoStore::has_keys( - &*keystore, - &[(a.0.to_raw_vec(), AuthorityId::ID)], - ) { - Some((a.0.clone(), i)) - } else { - None - } - }) - .collect::>() - }; - - for slot in *epoch_start..*epoch_end { - if let Some((claim, key)) = - authorship::claim_slot_using_keys(slot.into(), &epoch, &keystore, &keys) - { - match claim { - PreDigest::Primary { .. } => { - claims.entry(key).or_default().primary.push(slot); - }, - PreDigest::SecondaryPlain { .. } => { - claims.entry(key).or_default().secondary.push(slot); - }, - PreDigest::SecondaryVRF { .. } => { - claims.entry(key).or_default().secondary_vrf.push(slot); - }, - }; - } + async fn epoch_authorship(&self) -> RpcResult> { + self.deny_unsafe.check_if_safe()?; + let header = self.select_chain.best_chain().map_err(Error::Consensus).await?; + let epoch_start = self + .client + .runtime_api() + .current_epoch_start(&BlockId::Hash(header.hash())) + .map_err(|err| Error::StringError(format!("{:?}", err)))?; + + let epoch = epoch_data( + &self.shared_epoch_changes, + &self.client, + &self.babe_config, + *epoch_start, + &self.select_chain, + ) + .await?; + let (epoch_start, epoch_end) = (epoch.start_slot(), epoch.end_slot()); + let mut claims: HashMap = HashMap::new(); + + let keys = { + epoch + .authorities + .iter() + .enumerate() + .filter_map(|(i, a)| { + if SyncCryptoStore::has_keys( + &*self.keystore, + &[(a.0.to_raw_vec(), AuthorityId::ID)], + ) { + Some((a.0.clone(), i)) + } else { + None + } + }) + .collect::>() + }; + + for slot in *epoch_start..*epoch_end { + if let Some((claim, key)) = + authorship::claim_slot_using_keys(slot.into(), &epoch, &self.keystore, &keys) + { + match claim { + PreDigest::Primary { .. } => { + claims.entry(key).or_default().primary.push(slot); + }, + PreDigest::SecondaryPlain { .. } => { + claims.entry(key).or_default().secondary.push(slot); + }, + PreDigest::SecondaryVRF { .. } => { + claims.entry(key).or_default().secondary_vrf.push(slot.into()); + }, + }; } - - Ok(claims) } - .boxed() + + Ok(claims) } } @@ -176,13 +171,13 @@ pub enum Error { StringError(String), } -impl From for jsonrpc_core::Error { +impl From for JsonRpseeError { fn from(error: Error) -> Self { - jsonrpc_core::Error { - message: format!("{}", error), - code: jsonrpc_core::ErrorCode::ServerError(1234), - data: None, - } + JsonRpseeError::Call(CallError::Custom(ErrorObject::owned( + 1234, + error.to_string(), + None::<()>, + ))) } } @@ -226,7 +221,6 @@ mod tests { TestClientBuilderExt, }; - use jsonrpc_core::IoHandler; use sc_consensus_babe::{block_import, AuthorityPair, Config}; use std::sync::Arc; @@ -243,9 +237,9 @@ mod tests { (keystore, keystore_path) } - fn test_babe_rpc_handler( + fn test_babe_rpc_module( deny_unsafe: DenyUnsafe, - ) -> BabeRpcHandler> { + ) -> BabeRpc> { let builder = TestClientBuilder::new(); let (client, longest_chain) = builder.build_with_longest_chain(); let client = Arc::new(client); @@ -256,40 +250,30 @@ mod tests { let epoch_changes = link.epoch_changes().clone(); let keystore = create_temp_keystore::(Sr25519Keyring::Alice).0; - BabeRpcHandler::new( - client.clone(), - epoch_changes, - keystore, - config, - longest_chain, - deny_unsafe, - ) + BabeRpc::new(client.clone(), epoch_changes, keystore, config, longest_chain, deny_unsafe) } - #[test] - fn epoch_authorship_works() { - let handler = test_babe_rpc_handler(DenyUnsafe::No); - let mut io = IoHandler::new(); + #[tokio::test] + async fn epoch_authorship_works() { + let babe_rpc = test_babe_rpc_module(DenyUnsafe::No); + let api = babe_rpc.into_rpc(); - io.extend_with(BabeApi::to_delegate(handler)); let request = r#"{"jsonrpc":"2.0","method":"babe_epochAuthorship","params": [],"id":1}"#; - let response = r#"{"jsonrpc":"2.0","result":{"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY":{"primary":[0],"secondary":[1,2,4],"secondary_vrf":[]}},"id":1}"#; + let (response, _) = api.raw_json_request(request).await.unwrap(); + let expected = r#"{"jsonrpc":"2.0","result":{"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY":{"primary":[0],"secondary":[1,2,4],"secondary_vrf":[]}},"id":1}"#; - assert_eq!(Some(response.into()), io.handle_request_sync(request)); + assert_eq!(&response, expected); } - #[test] - fn epoch_authorship_is_unsafe() { - let handler = test_babe_rpc_handler(DenyUnsafe::Yes); - let mut io = IoHandler::new(); - - io.extend_with(BabeApi::to_delegate(handler)); - let request = r#"{"jsonrpc":"2.0","method":"babe_epochAuthorship","params": [],"id":1}"#; + #[tokio::test] + async fn epoch_authorship_is_unsafe() { + let babe_rpc = test_babe_rpc_module(DenyUnsafe::Yes); + let api = babe_rpc.into_rpc(); - let response = io.handle_request_sync(request).unwrap(); - let mut response: serde_json::Value = serde_json::from_str(&response).unwrap(); - let error: RpcError = serde_json::from_value(response["error"].take()).unwrap(); + let request = r#"{"jsonrpc":"2.0","method":"babe_epochAuthorship","params":[],"id":1}"#; + let (response, _) = api.raw_json_request(request).await.unwrap(); + let expected = r#"{"jsonrpc":"2.0","error":{"code":-32601,"message":"RPC call is unsafe to be called externally"},"id":1}"#; - assert_eq!(error, sc_rpc_api::UnsafeRpcError.into()) + assert_eq!(&response, expected); } } diff --git a/client/consensus/manual-seal/Cargo.toml b/client/consensus/manual-seal/Cargo.toml index 9452d2b1afd08..e8f4e20ab0e55 100644 --- a/client/consensus/manual-seal/Cargo.toml +++ b/client/consensus/manual-seal/Cargo.toml @@ -13,13 +13,11 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +jsonrpsee = { version = "0.12.0", features = ["server", "macros"] } assert_matches = "1.3.0" async-trait = "0.1.50" codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.21" -jsonrpc-core = "18.0.0" -jsonrpc-core-client = "18.0.0" -jsonrpc-derive = "18.0.0" log = "0.4.16" serde = { version = "1.0", features = ["derive"] } thiserror = "1.0" diff --git a/client/consensus/manual-seal/src/error.rs b/client/consensus/manual-seal/src/error.rs index 7c3211203bf54..a056c541c3cef 100644 --- a/client/consensus/manual-seal/src/error.rs +++ b/client/consensus/manual-seal/src/error.rs @@ -20,6 +20,10 @@ //! This is suitable for a testing environment. use futures::channel::{mpsc::SendError, oneshot}; +use jsonrpsee::{ + core::Error as JsonRpseeError, + types::error::{CallError, ErrorObject}, +}; use sc_consensus::ImportResult; use sp_blockchain::Error as BlockchainError; use sp_consensus::Error as ConsensusError; @@ -27,14 +31,14 @@ use sp_inherents::Error as InherentsError; /// Error code for rpc mod codes { - pub const SERVER_SHUTTING_DOWN: i64 = 10_000; - pub const BLOCK_IMPORT_FAILED: i64 = 11_000; - pub const EMPTY_TRANSACTION_POOL: i64 = 12_000; - pub const BLOCK_NOT_FOUND: i64 = 13_000; - pub const CONSENSUS_ERROR: i64 = 14_000; - pub const INHERENTS_ERROR: i64 = 15_000; - pub const BLOCKCHAIN_ERROR: i64 = 16_000; - pub const UNKNOWN_ERROR: i64 = 20_000; + pub const SERVER_SHUTTING_DOWN: i32 = 10_000; + pub const BLOCK_IMPORT_FAILED: i32 = 11_000; + pub const EMPTY_TRANSACTION_POOL: i32 = 12_000; + pub const BLOCK_NOT_FOUND: i32 = 13_000; + pub const CONSENSUS_ERROR: i32 = 14_000; + pub const INHERENTS_ERROR: i32 = 15_000; + pub const BLOCKCHAIN_ERROR: i32 = 16_000; + pub const UNKNOWN_ERROR: i32 = 20_000; } /// errors encountered by background block authorship task @@ -71,7 +75,7 @@ pub enum Error { SendError(#[from] SendError), /// Some other error. #[error("Other error: {0}")] - Other(#[from] Box), + Other(Box), } impl From for Error { @@ -87,7 +91,7 @@ impl From for Error { } impl Error { - fn to_code(&self) -> i64 { + fn to_code(&self) -> i32 { use Error::*; match self { BlockImportError(_) => codes::BLOCK_IMPORT_FAILED, @@ -102,12 +106,8 @@ impl Error { } } -impl From for jsonrpc_core::Error { - fn from(error: Error) -> Self { - jsonrpc_core::Error { - code: jsonrpc_core::ErrorCode::ServerError(error.to_code()), - message: format!("{}", error), - data: None, - } +impl From for JsonRpseeError { + fn from(err: Error) -> Self { + CallError::Custom(ErrorObject::owned(err.to_code(), err.to_string(), None::<()>)).into() } } diff --git a/client/consensus/manual-seal/src/rpc.rs b/client/consensus/manual-seal/src/rpc.rs index 4a8dcbc0cb765..b9bb06551f818 100644 --- a/client/consensus/manual-seal/src/rpc.rs +++ b/client/consensus/manual-seal/src/rpc.rs @@ -18,21 +18,21 @@ //! RPC interface for the `ManualSeal` Engine. -pub use self::gen_client::Client as ManualSealClient; +use crate::error::Error; use futures::{ channel::{mpsc, oneshot}, - FutureExt, SinkExt, TryFutureExt, + SinkExt, +}; +use jsonrpsee::{ + core::{async_trait, Error as JsonRpseeError, RpcResult}, + proc_macros::rpc, }; -use jsonrpc_core::Error; -use jsonrpc_derive::rpc; use sc_consensus::ImportedAux; use serde::{Deserialize, Serialize}; use sp_runtime::EncodedJustification; -/// Future's type for jsonrpc -type FutureResult = jsonrpc_core::BoxFuture>; -/// sender passed to the authorship task to report errors or successes. -pub type Sender = Option>>; +/// Sender passed to the authorship task to report errors or successes. +pub type Sender = Option>>; /// Message sent to the background authorship task, usually by RPC. pub enum EngineCommand { @@ -65,27 +65,27 @@ pub enum EngineCommand { } /// RPC trait that provides methods for interacting with the manual-seal authorship task over rpc. -#[rpc] +#[rpc(client, server)] pub trait ManualSealApi { /// Instructs the manual-seal authorship task to create a new block - #[rpc(name = "engine_createBlock")] - fn create_block( + #[method(name = "engine_createBlock")] + async fn create_block( &self, create_empty: bool, finalize: bool, parent_hash: Option, - ) -> FutureResult>; + ) -> RpcResult>; /// Instructs the manual-seal authorship task to finalize a block - #[rpc(name = "engine_finalizeBlock")] - fn finalize_block( + #[method(name = "engine_finalizeBlock")] + async fn finalize_block( &self, hash: Hash, justification: Option, - ) -> FutureResult; + ) -> RpcResult; } -/// A struct that implements the [`ManualSealApi`]. +/// A struct that implements the [`ManualSealApiServer`]. pub struct ManualSeal { import_block_channel: mpsc::Sender>, } @@ -106,44 +106,43 @@ impl ManualSeal { } } -impl ManualSealApi for ManualSeal { - fn create_block( +#[async_trait] +impl ManualSealApiServer for ManualSeal { + async fn create_block( &self, create_empty: bool, finalize: bool, parent_hash: Option, - ) -> FutureResult> { + ) -> RpcResult> { let mut sink = self.import_block_channel.clone(); - async move { - let (sender, receiver) = oneshot::channel(); - let command = EngineCommand::SealNewBlock { - create_empty, - finalize, - parent_hash, - sender: Some(sender), - }; - sink.send(command).await?; - receiver.await? + let (sender, receiver) = oneshot::channel(); + // NOTE: this sends a Result over the channel. + let command = EngineCommand::SealNewBlock { + create_empty, + finalize, + parent_hash, + sender: Some(sender), + }; + + sink.send(command).await?; + + match receiver.await { + Ok(Ok(rx)) => Ok(rx), + Ok(Err(e)) => Err(e.into()), + Err(e) => Err(JsonRpseeError::to_call_error(e)), } - .map_err(Error::from) - .boxed() } - fn finalize_block( + async fn finalize_block( &self, hash: Hash, justification: Option, - ) -> FutureResult { + ) -> RpcResult { let mut sink = self.import_block_channel.clone(); - async move { - let (sender, receiver) = oneshot::channel(); - sink.send(EngineCommand::FinalizeBlock { hash, sender: Some(sender), justification }) - .await?; - - receiver.await?.map(|_| true) - } - .map_err(Error::from) - .boxed() + let (sender, receiver) = oneshot::channel(); + let command = EngineCommand::FinalizeBlock { hash, sender: Some(sender), justification }; + sink.send(command).await?; + receiver.await.map(|_| true).map_err(|e| JsonRpseeError::to_call_error(e)) } } diff --git a/client/finality-grandpa/rpc/Cargo.toml b/client/finality-grandpa/rpc/Cargo.toml index 40a4150f8dd98..c124712e3fa84 100644 --- a/client/finality-grandpa/rpc/Cargo.toml +++ b/client/finality-grandpa/rpc/Cargo.toml @@ -12,10 +12,7 @@ homepage = "https://substrate.io" [dependencies] finality-grandpa = { version = "0.15.0", features = ["derive-codec"] } futures = "0.3.16" -jsonrpc-core = "18.0.0" -jsonrpc-core-client = "18.0.0" -jsonrpc-derive = "18.0.0" -jsonrpc-pubsub = "18.0.0" +jsonrpsee = { version = "0.12.0", features = ["server", "macros"] } log = "0.4.8" parity-scale-codec = { version = "3.0.0", features = ["derive"] } serde = { version = "1.0.105", features = ["derive"] } @@ -37,3 +34,4 @@ sp-core = { version = "6.0.0", path = "../../../primitives/core" } sp-finality-grandpa = { version = "4.0.0-dev", path = "../../../primitives/finality-grandpa" } sp-keyring = { version = "6.0.0", path = "../../../primitives/keyring" } substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } +tokio = { version = "1.17.0", features = ["macros"] } diff --git a/client/finality-grandpa/rpc/src/error.rs b/client/finality-grandpa/rpc/src/error.rs index 845b4d99dcc1a..197c0b8a72102 100644 --- a/client/finality-grandpa/rpc/src/error.rs +++ b/client/finality-grandpa/rpc/src/error.rs @@ -16,6 +16,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +use jsonrpsee::{ + core::Error as JsonRpseeError, + types::error::{CallError, ErrorObject}, +}; + #[derive(Debug, thiserror::Error)] /// Top-level error type for the RPC handler pub enum Error { @@ -56,15 +61,15 @@ impl From for ErrorCode { } } -impl From for jsonrpc_core::Error { +impl From for JsonRpseeError { fn from(error: Error) -> Self { - let message = format!("{}", error); + let message = error.to_string(); let code = ErrorCode::from(error); - jsonrpc_core::Error { + JsonRpseeError::Call(CallError::Custom(ErrorObject::owned( + code as i32, message, - code: jsonrpc_core::ErrorCode::ServerError(code as i64), - data: None, - } + None::<()>, + ))) } } diff --git a/client/finality-grandpa/rpc/src/lib.rs b/client/finality-grandpa/rpc/src/lib.rs index 9c51bc3d226a7..82962d716d589 100644 --- a/client/finality-grandpa/rpc/src/lib.rs +++ b/client/finality-grandpa/rpc/src/lib.rs @@ -19,167 +19,137 @@ //! RPC API for GRANDPA. #![warn(missing_docs)] -use futures::{task::Spawn, FutureExt, SinkExt, StreamExt, TryFutureExt}; -use jsonrpc_derive::rpc; -use jsonrpc_pubsub::{manager::SubscriptionManager, typed::Subscriber, SubscriptionId}; +use futures::{FutureExt, StreamExt}; use log::warn; use std::sync::Arc; +use jsonrpsee::{ + core::{async_trait, RpcResult}, + proc_macros::rpc, + PendingSubscription, +}; + mod error; mod finality; mod notification; mod report; use sc_finality_grandpa::GrandpaJustificationStream; +use sc_rpc::SubscriptionTaskExecutor; use sp_runtime::traits::{Block as BlockT, NumberFor}; use finality::{EncodedFinalityProof, RpcFinalityProofProvider}; use notification::JustificationNotification; use report::{ReportAuthoritySet, ReportVoterState, ReportedRoundStates}; -type FutureResult = jsonrpc_core::BoxFuture>; - /// Provides RPC methods for interacting with GRANDPA. -#[rpc] +#[rpc(client, server)] pub trait GrandpaApi { - /// RPC Metadata - type Metadata; - /// Returns the state of the current best round state as well as the /// ongoing background rounds. - #[rpc(name = "grandpa_roundState")] - fn round_state(&self) -> FutureResult; + #[method(name = "grandpa_roundState")] + async fn round_state(&self) -> RpcResult; /// Returns the block most recently finalized by Grandpa, alongside /// side its justification. - #[pubsub( - subscription = "grandpa_justifications", - subscribe, - name = "grandpa_subscribeJustifications" - )] - fn subscribe_justifications( - &self, - metadata: Self::Metadata, - subscriber: Subscriber, - ); - - /// Unsubscribe from receiving notifications about recently finalized blocks. - #[pubsub( - subscription = "grandpa_justifications", - unsubscribe, - name = "grandpa_unsubscribeJustifications" + #[subscription( + name = "grandpa_subscribeJustifications" => "grandpa_justifications", + unsubscribe = "grandpa_unsubscribeJustifications", + item = Notification )] - fn unsubscribe_justifications( - &self, - metadata: Option, - id: SubscriptionId, - ) -> jsonrpc_core::Result; + fn subscribe_justifications(&self); /// Prove finality for the given block number by returning the Justification for the last block /// in the set and all the intermediary headers to link them together. - #[rpc(name = "grandpa_proveFinality")] - fn prove_finality(&self, block: Number) -> FutureResult>; + #[method(name = "grandpa_proveFinality")] + async fn prove_finality(&self, block: Number) -> RpcResult>; } -/// Implements the GrandpaApi RPC trait for interacting with GRANDPA. -pub struct GrandpaRpcHandler { +/// Provides RPC methods for interacting with GRANDPA. +pub struct GrandpaRpc { + executor: SubscriptionTaskExecutor, authority_set: AuthoritySet, voter_state: VoterState, justification_stream: GrandpaJustificationStream, - manager: SubscriptionManager, finality_proof_provider: Arc, } - impl - GrandpaRpcHandler + GrandpaRpc { - /// Creates a new GrandpaRpcHandler instance. - pub fn new( + /// Prepare a new [`GrandpaRpc`] + pub fn new( + executor: SubscriptionTaskExecutor, authority_set: AuthoritySet, voter_state: VoterState, justification_stream: GrandpaJustificationStream, - executor: E, finality_proof_provider: Arc, - ) -> Self - where - E: Spawn + Sync + Send + 'static, - { - let manager = SubscriptionManager::new(Arc::new(executor)); - Self { authority_set, voter_state, justification_stream, manager, finality_proof_provider } + ) -> Self { + Self { executor, authority_set, voter_state, justification_stream, finality_proof_provider } } } +#[async_trait] impl - GrandpaApi> - for GrandpaRpcHandler + GrandpaApiServer> + for GrandpaRpc where VoterState: ReportVoterState + Send + Sync + 'static, AuthoritySet: ReportAuthoritySet + Send + Sync + 'static, Block: BlockT, ProofProvider: RpcFinalityProofProvider + Send + Sync + 'static, { - type Metadata = sc_rpc::Metadata; - - fn round_state(&self) -> FutureResult { - let round_states = ReportedRoundStates::from(&self.authority_set, &self.voter_state); - let future = async move { round_states }.boxed(); - future.map_err(jsonrpc_core::Error::from).boxed() + async fn round_state(&self) -> RpcResult { + ReportedRoundStates::from(&self.authority_set, &self.voter_state).map_err(Into::into) } - fn subscribe_justifications( - &self, - _metadata: Self::Metadata, - subscriber: Subscriber, - ) { - let stream = self - .justification_stream - .subscribe() - .map(|x| Ok(Ok::<_, jsonrpc_core::Error>(JustificationNotification::from(x)))); - - self.manager.add(subscriber, |sink| { - stream - .forward(sink.sink_map_err(|e| warn!("Error sending notifications: {:?}", e))) - .map(|_| ()) - }); - } + fn subscribe_justifications(&self, pending: PendingSubscription) { + let stream = self.justification_stream.subscribe().map( + |x: sc_finality_grandpa::GrandpaJustification| { + JustificationNotification::from(x) + }, + ); - fn unsubscribe_justifications( - &self, - _metadata: Option, - id: SubscriptionId, - ) -> jsonrpc_core::Result { - Ok(self.manager.cancel(id)) + let fut = async move { + if let Some(mut sink) = pending.accept() { + sink.pipe_from_stream(stream).await; + } + } + .boxed(); + + self.executor + .spawn("substrate-rpc-subscription", Some("rpc"), fut.map(drop).boxed()); } - fn prove_finality( + async fn prove_finality( &self, block: NumberFor, - ) -> FutureResult> { - let result = self.finality_proof_provider.rpc_prove_finality(block); - let future = async move { result }.boxed(); - future + ) -> RpcResult> { + self.finality_proof_provider + .rpc_prove_finality(block) .map_err(|e| { warn!("Error proving finality: {}", e); error::Error::ProveFinalityFailed(e) }) - .map_err(jsonrpc_core::Error::from) - .boxed() + .map_err(Into::into) } } #[cfg(test)] mod tests { use super::*; - use jsonrpc_core::{types::Params, Notification, Output}; - use std::{collections::HashSet, sync::Arc}; + use std::{collections::HashSet, convert::TryInto, sync::Arc}; + use jsonrpsee::{ + types::{EmptyParams, SubscriptionId}, + RpcModule, + }; use parity_scale_codec::{Decode, Encode}; use sc_block_builder::{BlockBuilder, RecordProof}; use sc_finality_grandpa::{ report, AuthorityId, FinalityProof, GrandpaJustification, GrandpaJustificationSender, }; use sp_blockchain::HeaderBackend; - use sp_core::crypto::ByteArray; + use sp_core::{crypto::ByteArray, testing::TaskExecutor}; use sp_keyring::Ed25519Keyring; use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; use substrate_test_runtime_client::{ @@ -274,7 +244,10 @@ mod tests { fn setup_io_handler( voter_state: VoterState, - ) -> (jsonrpc_core::MetaIoHandler, GrandpaJustificationSender) + ) -> ( + RpcModule>, + GrandpaJustificationSender, + ) where VoterState: ReportVoterState + Send + Sync + 'static, { @@ -284,120 +257,107 @@ mod tests { fn setup_io_handler_with_finality_proofs( voter_state: VoterState, finality_proof: Option>, - ) -> (jsonrpc_core::MetaIoHandler, GrandpaJustificationSender) + ) -> ( + RpcModule>, + GrandpaJustificationSender, + ) where VoterState: ReportVoterState + Send + Sync + 'static, { let (justification_sender, justification_stream) = GrandpaJustificationStream::channel(); let finality_proof_provider = Arc::new(TestFinalityProofProvider { finality_proof }); + let executor = Arc::new(TaskExecutor::default()); - let handler = GrandpaRpcHandler::new( + let rpc = GrandpaRpc::new( + executor, TestAuthoritySet, voter_state, justification_stream, - sc_rpc::testing::TaskExecutor, finality_proof_provider, - ); - - let mut io = jsonrpc_core::MetaIoHandler::default(); - io.extend_with(GrandpaApi::to_delegate(handler)); + ) + .into_rpc(); - (io, justification_sender) + (rpc, justification_sender) } - #[test] - fn uninitialized_rpc_handler() { - let (io, _) = setup_io_handler(EmptyVoterState); - - let request = r#"{"jsonrpc":"2.0","method":"grandpa_roundState","params":[],"id":1}"#; - let response = r#"{"jsonrpc":"2.0","error":{"code":1,"message":"GRANDPA RPC endpoint not ready"},"id":1}"#; + #[tokio::test] + async fn uninitialized_rpc_handler() { + let (rpc, _) = setup_io_handler(EmptyVoterState); + let expected_response = r#"{"jsonrpc":"2.0","error":{"code":1,"message":"GRANDPA RPC endpoint not ready"},"id":0}"#.to_string(); + let request = r#"{"jsonrpc":"2.0","method":"grandpa_roundState","params":[],"id":0}"#; + let (result, _) = rpc.raw_json_request(&request).await.unwrap(); - let meta = sc_rpc::Metadata::default(); - assert_eq!(Some(response.into()), io.handle_request_sync(request, meta)); + assert_eq!(expected_response, result,); } - #[test] - fn working_rpc_handler() { - let (io, _) = setup_io_handler(TestVoterState); - - let request = r#"{"jsonrpc":"2.0","method":"grandpa_roundState","params":[],"id":1}"#; - let response = "{\"jsonrpc\":\"2.0\",\"result\":{\ - \"background\":[{\ - \"precommits\":{\"currentWeight\":100,\"missing\":[]},\ - \"prevotes\":{\"currentWeight\":100,\"missing\":[]},\ - \"round\":1,\"thresholdWeight\":67,\"totalWeight\":100\ - }],\ + #[tokio::test] + async fn working_rpc_handler() { + let (rpc, _) = setup_io_handler(TestVoterState); + let expected_response = "{\"jsonrpc\":\"2.0\",\"result\":{\ + \"setId\":1,\ \"best\":{\ - \"precommits\":{\"currentWeight\":0,\"missing\":[\"5C62Ck4UrFPiBtoCmeSrgF7x9yv9mn38446dhCpsi2mLHiFT\",\"5C7LYpP2ZH3tpKbvVvwiVe54AapxErdPBbvkYhe6y9ZBkqWt\"]},\ + \"round\":2,\"totalWeight\":100,\"thresholdWeight\":67,\ \"prevotes\":{\"currentWeight\":50,\"missing\":[\"5C7LYpP2ZH3tpKbvVvwiVe54AapxErdPBbvkYhe6y9ZBkqWt\"]},\ - \"round\":2,\"thresholdWeight\":67,\"totalWeight\":100\ + \"precommits\":{\"currentWeight\":0,\"missing\":[\"5C62Ck4UrFPiBtoCmeSrgF7x9yv9mn38446dhCpsi2mLHiFT\",\"5C7LYpP2ZH3tpKbvVvwiVe54AapxErdPBbvkYhe6y9ZBkqWt\"]}\ },\ - \"setId\":1\ - },\"id\":1}"; - - let meta = sc_rpc::Metadata::default(); - assert_eq!(io.handle_request_sync(request, meta), Some(response.into())); - } + \"background\":[{\ + \"round\":1,\"totalWeight\":100,\"thresholdWeight\":67,\ + \"prevotes\":{\"currentWeight\":100,\"missing\":[]},\ + \"precommits\":{\"currentWeight\":100,\"missing\":[]}\ + }]\ + },\"id\":0}".to_string(); - fn setup_session() -> (sc_rpc::Metadata, futures::channel::mpsc::UnboundedReceiver) { - let (tx, rx) = futures::channel::mpsc::unbounded(); - let meta = sc_rpc::Metadata::new(tx); - (meta, rx) + let request = r#"{"jsonrpc":"2.0","method":"grandpa_roundState","params":[],"id":0}"#; + let (result, _) = rpc.raw_json_request(&request).await.unwrap(); + assert_eq!(expected_response, result); } - #[test] - fn subscribe_and_unsubscribe_to_justifications() { - let (io, _) = setup_io_handler(TestVoterState); - let (meta, _) = setup_session(); + #[tokio::test] + async fn subscribe_and_unsubscribe_to_justifications() { + let (rpc, _) = setup_io_handler(TestVoterState); + // Subscribe call. + let sub = rpc + .subscribe("grandpa_subscribeJustifications", EmptyParams::new()) + .await + .unwrap(); - // Subscribe - let sub_request = - r#"{"jsonrpc":"2.0","method":"grandpa_subscribeJustifications","params":[],"id":1}"#; - let resp = io.handle_request_sync(sub_request, meta.clone()); - let resp: Output = serde_json::from_str(&resp.unwrap()).unwrap(); - - let sub_id = match resp { - Output::Success(success) => success.result, - _ => panic!(), - }; + let ser_id = serde_json::to_string(sub.subscription_id()).unwrap(); // Unsubscribe let unsub_req = format!( "{{\"jsonrpc\":\"2.0\",\"method\":\"grandpa_unsubscribeJustifications\",\"params\":[{}],\"id\":1}}", - sub_id - ); - assert_eq!( - io.handle_request_sync(&unsub_req, meta.clone()), - Some(r#"{"jsonrpc":"2.0","result":true,"id":1}"#.into()), + ser_id ); + let (response, _) = rpc.raw_json_request(&unsub_req).await.unwrap(); + + assert_eq!(response, r#"{"jsonrpc":"2.0","result":true,"id":1}"#); // Unsubscribe again and fail - assert_eq!( - io.handle_request_sync(&unsub_req, meta), - Some("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid subscription id.\"},\"id\":1}".into()), - ); - } + let (response, _) = rpc.raw_json_request(&unsub_req).await.unwrap(); + let expected = r#"{"jsonrpc":"2.0","result":false,"id":1}"#; - #[test] - fn subscribe_and_unsubscribe_with_wrong_id() { - let (io, _) = setup_io_handler(TestVoterState); - let (meta, _) = setup_session(); + assert_eq!(response, expected); + } - // Subscribe - let sub_request = - r#"{"jsonrpc":"2.0","method":"grandpa_subscribeJustifications","params":[],"id":1}"#; - let resp = io.handle_request_sync(sub_request, meta.clone()); - let resp: Output = serde_json::from_str(&resp.unwrap()).unwrap(); - assert!(matches!(resp, Output::Success(_))); + #[tokio::test] + async fn subscribe_and_unsubscribe_with_wrong_id() { + let (rpc, _) = setup_io_handler(TestVoterState); + // Subscribe call. + let _sub = rpc + .subscribe("grandpa_subscribeJustifications", EmptyParams::new()) + .await + .unwrap(); // Unsubscribe with wrong ID - assert_eq!( - io.handle_request_sync( + let (response, _) = rpc + .raw_json_request( r#"{"jsonrpc":"2.0","method":"grandpa_unsubscribeJustifications","params":["FOO"],"id":1}"#, - meta.clone() - ), - Some("{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32602,\"message\":\"Invalid subscription id.\"},\"id\":1}".into()) - ); + ) + .await + .unwrap(); + let expected = r#"{"jsonrpc":"2.0","result":false,"id":1}"#; + + assert_eq!(response, expected); } fn create_justification() -> GrandpaJustification { @@ -454,60 +414,41 @@ mod tests { justification } - #[test] - fn subscribe_and_listen_to_one_justification() { - let (io, justification_sender) = setup_io_handler(TestVoterState); - let (meta, receiver) = setup_session(); - - // Subscribe - let sub_request = - r#"{"jsonrpc":"2.0","method":"grandpa_subscribeJustifications","params":[],"id":1}"#; + #[tokio::test] + async fn subscribe_and_listen_to_one_justification() { + let (rpc, justification_sender) = setup_io_handler(TestVoterState); - let resp = io.handle_request_sync(sub_request, meta.clone()); - let mut resp: serde_json::Value = serde_json::from_str(&resp.unwrap()).unwrap(); - let sub_id: String = serde_json::from_value(resp["result"].take()).unwrap(); + let mut sub = rpc + .subscribe("grandpa_subscribeJustifications", EmptyParams::new()) + .await + .unwrap(); // Notify with a header and justification let justification = create_justification(); justification_sender.notify(|| Ok::<_, ()>(justification.clone())).unwrap(); // Inspect what we received - let recv = futures::executor::block_on(receiver.take(1).collect::>()); - let recv: Notification = serde_json::from_str(&recv[0]).unwrap(); - let mut json_map = match recv.params { - Params::Map(json_map) => json_map, - _ => panic!(), - }; - - let recv_sub_id: String = serde_json::from_value(json_map["subscription"].take()).unwrap(); - let recv_justification: sp_core::Bytes = - serde_json::from_value(json_map["result"].take()).unwrap(); + let (recv_justification, recv_sub_id): (sp_core::Bytes, SubscriptionId) = + sub.next().await.unwrap().unwrap(); let recv_justification: GrandpaJustification = Decode::decode(&mut &recv_justification[..]).unwrap(); - assert_eq!(recv.method, "grandpa_justifications"); - assert_eq!(recv_sub_id, sub_id); + assert_eq!(&recv_sub_id, sub.subscription_id()); assert_eq!(recv_justification, justification); } - #[test] - fn prove_finality_with_test_finality_proof_provider() { + #[tokio::test] + async fn prove_finality_with_test_finality_proof_provider() { let finality_proof = FinalityProof { block: header(42).hash(), justification: create_justification().encode(), unknown_headers: vec![header(2)], }; - let (io, _) = + let (rpc, _) = setup_io_handler_with_finality_proofs(TestVoterState, Some(finality_proof.clone())); - let request = - "{\"jsonrpc\":\"2.0\",\"method\":\"grandpa_proveFinality\",\"params\":[42],\"id\":1}"; - - let meta = sc_rpc::Metadata::default(); - let resp = io.handle_request_sync(request, meta); - let mut resp: serde_json::Value = serde_json::from_str(&resp.unwrap()).unwrap(); - let result: sp_core::Bytes = serde_json::from_value(resp["result"].take()).unwrap(); - let finality_proof_rpc: FinalityProof
= Decode::decode(&mut &result[..]).unwrap(); + let bytes: sp_core::Bytes = rpc.call("grandpa_proveFinality", [42]).await.unwrap(); + let finality_proof_rpc: FinalityProof
= Decode::decode(&mut &bytes[..]).unwrap(); assert_eq!(finality_proof_rpc, finality_proof); } } diff --git a/client/rpc-api/Cargo.toml b/client/rpc-api/Cargo.toml index 0287b0fd30799..f8dfaab2a58a3 100644 --- a/client/rpc-api/Cargo.toml +++ b/client/rpc-api/Cargo.toml @@ -15,10 +15,6 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.21" -jsonrpc-core = "18.0.0" -jsonrpc-core-client = "18.0.0" -jsonrpc-derive = "18.0.0" -jsonrpc-pubsub = "18.0.0" log = "0.4.16" parking_lot = "0.12.0" scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } @@ -32,3 +28,4 @@ sp-rpc = { version = "6.0.0", path = "../../primitives/rpc" } sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } sp-version = { version = "5.0.0", path = "../../primitives/version" } +jsonrpsee = { version = "0.12.0", features = ["server", "macros"] } diff --git a/client/rpc-api/src/author/error.rs b/client/rpc-api/src/author/error.rs index 1f8c65c471398..57a27d48de3ad 100644 --- a/client/rpc-api/src/author/error.rs +++ b/client/rpc-api/src/author/error.rs @@ -18,28 +18,27 @@ //! Authoring RPC module errors. -use crate::errors; -use jsonrpc_core as rpc; +use jsonrpsee::{ + core::Error as JsonRpseeError, + types::error::{CallError, ErrorObject}, +}; use sp_runtime::transaction_validity::InvalidTransaction; /// Author RPC Result type. pub type Result = std::result::Result; -/// Author RPC future Result type. -pub type FutureResult = jsonrpc_core::BoxFuture>; - /// Author RPC errors. #[derive(Debug, thiserror::Error)] pub enum Error { /// Client error. #[error("Client error: {}", .0)] - Client(Box), + Client(Box), /// Transaction pool error, #[error("Transaction pool error: {}", .0)] Pool(#[from] sc_transaction_pool_api::error::Error), /// Verification error #[error("Extrinsic verification error: {}", .0)] - Verification(Box), + Verification(Box), /// Incorrect extrinsic format. #[error("Invalid extrinsic format: {}", .0)] BadFormat(#[from] codec::Error), @@ -58,98 +57,127 @@ pub enum Error { } /// Base code for all authorship errors. -const BASE_ERROR: i64 = 1000; +const BASE_ERROR: i32 = 1000; /// Extrinsic has an invalid format. -const BAD_FORMAT: i64 = BASE_ERROR + 1; +const BAD_FORMAT: i32 = BASE_ERROR + 1; /// Error during transaction verification in runtime. -const VERIFICATION_ERROR: i64 = BASE_ERROR + 2; +const VERIFICATION_ERROR: i32 = BASE_ERROR + 2; /// Pool rejected the transaction as invalid -const POOL_INVALID_TX: i64 = BASE_ERROR + 10; +const POOL_INVALID_TX: i32 = BASE_ERROR + 10; /// Cannot determine transaction validity. -const POOL_UNKNOWN_VALIDITY: i64 = POOL_INVALID_TX + 1; +const POOL_UNKNOWN_VALIDITY: i32 = POOL_INVALID_TX + 1; /// The transaction is temporarily banned. -const POOL_TEMPORARILY_BANNED: i64 = POOL_INVALID_TX + 2; +const POOL_TEMPORARILY_BANNED: i32 = POOL_INVALID_TX + 2; /// The transaction is already in the pool -const POOL_ALREADY_IMPORTED: i64 = POOL_INVALID_TX + 3; +const POOL_ALREADY_IMPORTED: i32 = POOL_INVALID_TX + 3; /// Transaction has too low priority to replace existing one in the pool. -const POOL_TOO_LOW_PRIORITY: i64 = POOL_INVALID_TX + 4; +const POOL_TOO_LOW_PRIORITY: i32 = POOL_INVALID_TX + 4; /// Including this transaction would cause a dependency cycle. -const POOL_CYCLE_DETECTED: i64 = POOL_INVALID_TX + 5; +const POOL_CYCLE_DETECTED: i32 = POOL_INVALID_TX + 5; /// The transaction was not included to the pool because of the limits. -const POOL_IMMEDIATELY_DROPPED: i64 = POOL_INVALID_TX + 6; +const POOL_IMMEDIATELY_DROPPED: i32 = POOL_INVALID_TX + 6; /// The transaction was not included to the pool since it is unactionable, /// it is not propagable and the local node does not author blocks. -const POOL_UNACTIONABLE: i64 = POOL_INVALID_TX + 8; +const POOL_UNACTIONABLE: i32 = POOL_INVALID_TX + 8; +/// Transaction does not provide any tags, so the pool can't identify it. +const POOL_NO_TAGS: i32 = POOL_INVALID_TX + 9; +/// Invalid block ID. +const POOL_INVALID_BLOCK_ID: i32 = POOL_INVALID_TX + 10; +/// The pool is not accepting future transactions. +const POOL_FUTURE_TX: i32 = POOL_INVALID_TX + 11; -impl From for rpc::Error { +impl From for JsonRpseeError { fn from(e: Error) -> Self { use sc_transaction_pool_api::error::Error as PoolError; match e { - Error::BadFormat(e) => rpc::Error { - code: rpc::ErrorCode::ServerError(BAD_FORMAT), - message: format!("Extrinsic has invalid format: {}", e), - data: None, - }, - Error::Verification(e) => rpc::Error { - code: rpc::ErrorCode::ServerError(VERIFICATION_ERROR), - message: format!("Verification Error: {}", e), - data: Some(e.to_string().into()), - }, - Error::Pool(PoolError::InvalidTransaction(InvalidTransaction::Custom(e))) => rpc::Error { - code: rpc::ErrorCode::ServerError(POOL_INVALID_TX), - message: "Invalid Transaction".into(), - data: Some(format!("Custom error: {}", e).into()), + Error::BadFormat(e) => CallError::Custom(ErrorObject::owned( + BAD_FORMAT, + format!("Extrinsic has invalid format: {}", e), + None::<()>, + )), + Error::Verification(e) => CallError::Custom(ErrorObject::owned( + VERIFICATION_ERROR, + format!("Verification Error: {}", e), + Some(format!("{:?}", e)), + )), + Error::Pool(PoolError::InvalidTransaction(InvalidTransaction::Custom(e))) => { + CallError::Custom(ErrorObject::owned( + POOL_INVALID_TX, + "Invalid Transaction", + Some(format!("Custom error: {}", e)), + )) }, Error::Pool(PoolError::InvalidTransaction(e)) => { let msg: &str = e.into(); - rpc::Error { - code: rpc::ErrorCode::ServerError(POOL_INVALID_TX), - message: "Invalid Transaction".into(), - data: Some(msg.into()), - } - }, - Error::Pool(PoolError::UnknownTransaction(e)) => rpc::Error { - code: rpc::ErrorCode::ServerError(POOL_UNKNOWN_VALIDITY), - message: "Unknown Transaction Validity".into(), - data: serde_json::to_value(e).ok(), - }, - Error::Pool(PoolError::TemporarilyBanned) => rpc::Error { - code: rpc::ErrorCode::ServerError(POOL_TEMPORARILY_BANNED), - message: "Transaction is temporarily banned".into(), - data: None, - }, - Error::Pool(PoolError::AlreadyImported(hash)) => rpc::Error { - code: rpc::ErrorCode::ServerError(POOL_ALREADY_IMPORTED), - message: "Transaction Already Imported".into(), - data: Some(format!("{:?}", hash).into()), - }, - Error::Pool(PoolError::TooLowPriority { old, new }) => rpc::Error { - code: rpc::ErrorCode::ServerError(POOL_TOO_LOW_PRIORITY), - message: format!("Priority is too low: ({} vs {})", old, new), - data: Some("The transaction has too low priority to replace another transaction already in the pool.".into()), - }, - Error::Pool(PoolError::CycleDetected) => rpc::Error { - code: rpc::ErrorCode::ServerError(POOL_CYCLE_DETECTED), - message: "Cycle Detected".into(), - data: None, + CallError::Custom(ErrorObject::owned( + POOL_INVALID_TX, + "Invalid Transaction", + Some(msg), + )) }, - Error::Pool(PoolError::ImmediatelyDropped) => rpc::Error { - code: rpc::ErrorCode::ServerError(POOL_IMMEDIATELY_DROPPED), - message: "Immediately Dropped".into(), - data: Some("The transaction couldn't enter the pool because of the limit".into()), + Error::Pool(PoolError::UnknownTransaction(e)) => { + CallError::Custom(ErrorObject::owned( + POOL_UNKNOWN_VALIDITY, + "Unknown Transaction Validity", + Some(format!("{:?}", e)), + )) }, - Error::Pool(PoolError::Unactionable) => rpc::Error { - code: rpc::ErrorCode::ServerError(POOL_UNACTIONABLE), - message: "Unactionable".into(), - data: Some( - "The transaction is unactionable since it is not propagable and \ - the local node does not author blocks".into(), - ), + Error::Pool(PoolError::TemporarilyBanned) => + CallError::Custom(ErrorObject::owned( + POOL_TEMPORARILY_BANNED, + "Transaction is temporarily banned", + None::<()>, + )), + Error::Pool(PoolError::AlreadyImported(hash)) => + CallError::Custom(ErrorObject::owned( + POOL_ALREADY_IMPORTED, + "Transaction Already Imported", + Some(format!("{:?}", hash)), + )), + Error::Pool(PoolError::TooLowPriority { old, new }) => CallError::Custom(ErrorObject::owned( + POOL_TOO_LOW_PRIORITY, + format!("Priority is too low: ({} vs {})", old, new), + Some("The transaction has too low priority to replace another transaction already in the pool.") + )), + Error::Pool(PoolError::CycleDetected) => + CallError::Custom(ErrorObject::owned( + POOL_CYCLE_DETECTED, + "Cycle Detected", + None::<()> + )), + Error::Pool(PoolError::ImmediatelyDropped) => CallError::Custom(ErrorObject::owned( + POOL_IMMEDIATELY_DROPPED, + "Immediately Dropped", + Some("The transaction couldn't enter the pool because of the limit"), + )), + Error::Pool(PoolError::Unactionable) => CallError::Custom(ErrorObject::owned( + POOL_UNACTIONABLE, + "Unactionable", + Some("The transaction is unactionable since it is not propagable and \ + the local node does not author blocks") + )), + Error::Pool(PoolError::NoTagsProvided) => CallError::Custom(ErrorObject::owned( + POOL_NO_TAGS, + "No tags provided", + Some("Transaction does not provide any tags, so the pool can't identify it") + )), + Error::Pool(PoolError::InvalidBlockId(_)) => + CallError::Custom(ErrorObject::owned( + POOL_INVALID_BLOCK_ID, + "The provided block ID is not valid", + None::<()> + )), + Error::Pool(PoolError::RejectedFutureTransaction) => { + CallError::Custom(ErrorObject::owned( + POOL_FUTURE_TX, + "The pool is not accepting future transactions", + None::<()>, + )) }, Error::UnsafeRpcCalled(e) => e.into(), - e => errors::internal(e), - } + e => CallError::Failed(e.into()), + }.into() } } diff --git a/client/rpc-api/src/author/mod.rs b/client/rpc-api/src/author/mod.rs index 84167ee95d108..feba7640e3b9f 100644 --- a/client/rpc-api/src/author/mod.rs +++ b/client/rpc-api/src/author/mod.rs @@ -18,85 +18,61 @@ //! Substrate block-author/full-node API. -pub mod error; -pub mod hash; - -use self::error::{FutureResult, Result}; -use jsonrpc_derive::rpc; -use jsonrpc_pubsub::{typed::Subscriber, SubscriptionId}; +use jsonrpsee::{core::RpcResult, proc_macros::rpc}; use sc_transaction_pool_api::TransactionStatus; use sp_core::Bytes; -pub use self::gen_client::Client as AuthorClient; +pub mod error; +pub mod hash; /// Substrate authoring RPC API -#[rpc] +#[rpc(client, server)] pub trait AuthorApi { - /// RPC metadata - type Metadata; - /// Submit hex-encoded extrinsic for inclusion in block. - #[rpc(name = "author_submitExtrinsic")] - fn submit_extrinsic(&self, extrinsic: Bytes) -> FutureResult; + #[method(name = "author_submitExtrinsic")] + async fn submit_extrinsic(&self, extrinsic: Bytes) -> RpcResult; /// Insert a key into the keystore. - #[rpc(name = "author_insertKey")] - fn insert_key(&self, key_type: String, suri: String, public: Bytes) -> Result<()>; + #[method(name = "author_insertKey")] + fn insert_key(&self, key_type: String, suri: String, public: Bytes) -> RpcResult<()>; /// Generate new session keys and returns the corresponding public keys. - #[rpc(name = "author_rotateKeys")] - fn rotate_keys(&self) -> Result; + #[method(name = "author_rotateKeys")] + fn rotate_keys(&self) -> RpcResult; /// Checks if the keystore has private keys for the given session public keys. /// /// `session_keys` is the SCALE encoded session keys object from the runtime. /// /// Returns `true` iff all private keys could be found. - #[rpc(name = "author_hasSessionKeys")] - fn has_session_keys(&self, session_keys: Bytes) -> Result; + #[method(name = "author_hasSessionKeys")] + fn has_session_keys(&self, session_keys: Bytes) -> RpcResult; /// Checks if the keystore has private keys for the given public key and key type. /// /// Returns `true` if a private key could be found. - #[rpc(name = "author_hasKey")] - fn has_key(&self, public_key: Bytes, key_type: String) -> Result; + #[method(name = "author_hasKey")] + fn has_key(&self, public_key: Bytes, key_type: String) -> RpcResult; /// Returns all pending extrinsics, potentially grouped by sender. - #[rpc(name = "author_pendingExtrinsics")] - fn pending_extrinsics(&self) -> Result>; + #[method(name = "author_pendingExtrinsics")] + fn pending_extrinsics(&self) -> RpcResult>; /// Remove given extrinsic from the pool and temporarily ban it to prevent reimporting. - #[rpc(name = "author_removeExtrinsic")] + #[method(name = "author_removeExtrinsic")] fn remove_extrinsic( &self, bytes_or_hash: Vec>, - ) -> Result>; + ) -> RpcResult>; /// Submit an extrinsic to watch. /// /// See [`TransactionStatus`](sc_transaction_pool_api::TransactionStatus) for details on /// transaction life cycle. - #[pubsub( - subscription = "author_extrinsicUpdate", - subscribe, - name = "author_submitAndWatchExtrinsic" + #[subscription( + name = "author_submitAndWatchExtrinsic" => "author_extrinsicUpdate", + unsubscribe = "author_unwatchExtrinsic", + item = TransactionStatus, )] - fn watch_extrinsic( - &self, - metadata: Self::Metadata, - subscriber: Subscriber>, - bytes: Bytes, - ); - - /// Unsubscribe from extrinsic watching. - #[pubsub( - subscription = "author_extrinsicUpdate", - unsubscribe, - name = "author_unwatchExtrinsic" - )] - fn unwatch_extrinsic( - &self, - metadata: Option, - id: SubscriptionId, - ) -> Result; + fn watch_extrinsic(&self, bytes: Bytes); } diff --git a/client/rpc-api/src/chain/error.rs b/client/rpc-api/src/chain/error.rs index a0cacb6739155..670e221cf1cde 100644 --- a/client/rpc-api/src/chain/error.rs +++ b/client/rpc-api/src/chain/error.rs @@ -18,38 +18,33 @@ //! Error helpers for Chain RPC module. -use crate::errors; -use jsonrpc_core as rpc; - +use jsonrpsee::{ + core::Error as JsonRpseeError, + types::error::{CallError, ErrorObject}, +}; /// Chain RPC Result type. pub type Result = std::result::Result; -/// Chain RPC future Result type. -pub type FutureResult = jsonrpc_core::BoxFuture>; - /// Chain RPC errors. #[derive(Debug, thiserror::Error)] pub enum Error { /// Client error. #[error("Client error: {}", .0)] - Client(#[from] Box), + Client(#[from] Box), /// Other error type. #[error("{0}")] Other(String), } /// Base error code for all chain errors. -const BASE_ERROR: i64 = 3000; +const BASE_ERROR: i32 = 3000; -impl From for rpc::Error { +impl From for JsonRpseeError { fn from(e: Error) -> Self { match e { - Error::Other(message) => rpc::Error { - code: rpc::ErrorCode::ServerError(BASE_ERROR + 1), - message, - data: None, - }, - e => errors::internal(e), + Error::Other(message) => + CallError::Custom(ErrorObject::owned(BASE_ERROR + 1, message, None::<()>)).into(), + e => e.into(), } } } diff --git a/client/rpc-api/src/chain/mod.rs b/client/rpc-api/src/chain/mod.rs index d7d598942f1ea..f5f9524264e34 100644 --- a/client/rpc-api/src/chain/mod.rs +++ b/client/rpc-api/src/chain/mod.rs @@ -18,96 +18,59 @@ //! Substrate blockchain API. -pub mod error; - -use self::error::{FutureResult, Result}; -use jsonrpc_core::Result as RpcResult; -use jsonrpc_derive::rpc; -use jsonrpc_pubsub::{typed::Subscriber, SubscriptionId}; +use jsonrpsee::{core::RpcResult, proc_macros::rpc}; use sp_rpc::{list::ListOrValue, number::NumberOrHex}; -pub use self::gen_client::Client as ChainClient; +pub mod error; -/// Substrate blockchain API -#[rpc] +#[rpc(client, server)] pub trait ChainApi { - /// RPC metadata - type Metadata; - - /// Get header of a relay chain block. - #[rpc(name = "chain_getHeader")] - fn header(&self, hash: Option) -> FutureResult>; + /// Get header. + #[method(name = "chain_getHeader")] + async fn header(&self, hash: Option) -> RpcResult>; /// Get header and body of a relay chain block. - #[rpc(name = "chain_getBlock")] - fn block(&self, hash: Option) -> FutureResult>; + #[method(name = "chain_getBlock")] + async fn block(&self, hash: Option) -> RpcResult>; /// Get hash of the n-th block in the canon chain. /// /// By default returns latest block hash. - #[rpc(name = "chain_getBlockHash", alias("chain_getHead"))] + #[method(name = "chain_getBlockHash", aliases = ["chain_getHead"])] fn block_hash( &self, hash: Option>, - ) -> Result>>; + ) -> RpcResult>>; /// Get hash of the last finalized block in the canon chain. - #[rpc(name = "chain_getFinalizedHead", alias("chain_getFinalisedHead"))] - fn finalized_head(&self) -> Result; - - /// All head subscription - #[pubsub(subscription = "chain_allHead", subscribe, name = "chain_subscribeAllHeads")] - fn subscribe_all_heads(&self, metadata: Self::Metadata, subscriber: Subscriber
); - - /// Unsubscribe from all head subscription. - #[pubsub(subscription = "chain_allHead", unsubscribe, name = "chain_unsubscribeAllHeads")] - fn unsubscribe_all_heads( - &self, - metadata: Option, - id: SubscriptionId, - ) -> RpcResult; - - /// New head subscription - #[pubsub( - subscription = "chain_newHead", - subscribe, - name = "chain_subscribeNewHeads", - alias("subscribe_newHead", "chain_subscribeNewHead") + #[method(name = "chain_getFinalizedHead", aliases = ["chain_getFinalisedHead"])] + fn finalized_head(&self) -> RpcResult; + + /// All head subscription. + #[subscription( + name = "chain_subscribeAllHeads" => "chain_allHead", + unsubscribe = "chain_unsubscribeAllHeads", + item = Header )] - fn subscribe_new_heads(&self, metadata: Self::Metadata, subscriber: Subscriber
); - - /// Unsubscribe from new head subscription. - #[pubsub( - subscription = "chain_newHead", - unsubscribe, - name = "chain_unsubscribeNewHeads", - alias("unsubscribe_newHead", "chain_unsubscribeNewHead") + fn subscribe_all_heads(&self); + + /// New head subscription. + #[subscription( + name = "chain_subscribeNewHeads" => "chain_newHead", + aliases = ["subscribe_newHead", "chain_subscribeNewHead"], + unsubscribe = "chain_unsubscribeNewHeads", + unsubscribe_aliases = ["unsubscribe_newHead", "chain_unsubscribeNewHead"], + item = Header )] - fn unsubscribe_new_heads( - &self, - metadata: Option, - id: SubscriptionId, - ) -> RpcResult; - - /// Finalized head subscription - #[pubsub( - subscription = "chain_finalizedHead", - subscribe, - name = "chain_subscribeFinalizedHeads", - alias("chain_subscribeFinalisedHeads") + fn subscribe_new_heads(&self); + + /// Finalized head subscription. + #[subscription( + name = "chain_subscribeFinalizedHeads" => "chain_finalizedHead", + aliases = ["chain_subscribeFinalisedHeads"], + unsubscribe = "chain_unsubscribeFinalizedHeads", + unsubscribe_aliases = ["chain_unsubscribeFinalisedHeads"], + item = Header )] - fn subscribe_finalized_heads(&self, metadata: Self::Metadata, subscriber: Subscriber
); - - /// Unsubscribe from finalized head subscription. - #[pubsub( - subscription = "chain_finalizedHead", - unsubscribe, - name = "chain_unsubscribeFinalizedHeads", - alias("chain_unsubscribeFinalisedHeads") - )] - fn unsubscribe_finalized_heads( - &self, - metadata: Option, - id: SubscriptionId, - ) -> RpcResult; + fn subscribe_finalized_heads(&self); } diff --git a/client/rpc-api/src/child_state/mod.rs b/client/rpc-api/src/child_state/mod.rs index 6b4cd20f22605..a15b1a0e7ee05 100644 --- a/client/rpc-api/src/child_state/mod.rs +++ b/client/rpc-api/src/child_state/mod.rs @@ -16,89 +16,82 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! Substrate state API. - -use crate::state::error::FutureResult; -use jsonrpc_derive::rpc; -use sp_core::storage::{PrefixedStorageKey, StorageData, StorageKey}; - -pub use self::gen_client::Client as ChildStateClient; +//! Substrate child state API use crate::state::ReadProof; +use jsonrpsee::{core::RpcResult, proc_macros::rpc}; +use sp_core::storage::{PrefixedStorageKey, StorageData, StorageKey}; /// Substrate child state API /// /// Note that all `PrefixedStorageKey` are deserialized /// from json and not guaranteed valid. -#[rpc] +#[rpc(client, server)] pub trait ChildStateApi { - /// RPC Metadata - type Metadata; - - /// DEPRECATED: Please use `childstate_getKeysPaged` with proper paging support. /// Returns the keys with prefix from a child storage, leave empty to get all the keys - #[rpc(name = "childstate_getKeys")] - fn storage_keys( + #[method(name = "childstate_getKeys")] + #[deprecated(since = "2.0.0", note = "Please use `getKeysPaged` with proper paging support")] + async fn storage_keys( &self, child_storage_key: PrefixedStorageKey, prefix: StorageKey, hash: Option, - ) -> FutureResult>; + ) -> RpcResult>; /// Returns the keys with prefix from a child storage with pagination support. /// Up to `count` keys will be returned. /// If `start_key` is passed, return next keys in storage in lexicographic order. - #[rpc(name = "childstate_getKeysPaged", alias("childstate_getKeysPagedAt"))] - fn storage_keys_paged( + #[method(name = "childstate_getKeysPaged", aliases = ["childstate_getKeysPagedAt"])] + async fn storage_keys_paged( &self, child_storage_key: PrefixedStorageKey, prefix: Option, count: u32, start_key: Option, hash: Option, - ) -> FutureResult>; + ) -> RpcResult>; /// Returns a child storage entry at a specific block's state. - #[rpc(name = "childstate_getStorage")] - fn storage( + #[method(name = "childstate_getStorage")] + async fn storage( &self, child_storage_key: PrefixedStorageKey, key: StorageKey, hash: Option, - ) -> FutureResult>; + ) -> RpcResult>; /// Returns child storage entries for multiple keys at a specific block's state. - #[rpc(name = "childstate_getStorageEntries")] - fn storage_entries( + #[method(name = "childstate_getStorageEntries")] + async fn storage_entries( &self, child_storage_key: PrefixedStorageKey, keys: Vec, hash: Option, - ) -> FutureResult>>; + ) -> RpcResult>>; /// Returns the hash of a child storage entry at a block's state. - #[rpc(name = "childstate_getStorageHash")] - fn storage_hash( + #[method(name = "childstate_getStorageHash")] + async fn storage_hash( &self, child_storage_key: PrefixedStorageKey, key: StorageKey, hash: Option, - ) -> FutureResult>; + ) -> RpcResult>; /// Returns the size of a child storage entry at a block's state. - #[rpc(name = "childstate_getStorageSize")] - fn storage_size( + #[method(name = "childstate_getStorageSize")] + async fn storage_size( &self, child_storage_key: PrefixedStorageKey, key: StorageKey, hash: Option, - ) -> FutureResult>; + ) -> RpcResult>; /// Returns proof of storage for child key entries at a specific block's state. - #[rpc(name = "state_getChildReadProof")] - fn read_child_proof( + #[method(name = "state_getChildReadProof")] + async fn read_child_proof( &self, child_storage_key: PrefixedStorageKey, keys: Vec, hash: Option, - ) -> FutureResult>; + ) -> RpcResult>; } diff --git a/client/rpc-api/src/dev/error.rs b/client/rpc-api/src/dev/error.rs index 1a14b0d78994e..fe74dea256376 100644 --- a/client/rpc-api/src/dev/error.rs +++ b/client/rpc-api/src/dev/error.rs @@ -18,14 +18,10 @@ //! Error helpers for Dev RPC module. -use crate::errors; -use jsonrpc_core as rpc; - -/// Dev RPC Result type. -pub type Result = std::result::Result; - -/// Dev RPC future Result type. -pub type FutureResult = jsonrpc_core::BoxFuture>; +use jsonrpsee::{ + core::Error as JsonRpseeError, + types::error::{CallError, ErrorObject}, +}; /// Dev RPC errors. #[derive(Debug, thiserror::Error)] @@ -45,27 +41,21 @@ pub enum Error { } /// Base error code for all dev errors. -const BASE_ERROR: i64 = 6000; +const BASE_ERROR: i32 = 6000; -impl From for rpc::Error { +impl From for JsonRpseeError { fn from(e: Error) -> Self { + let msg = e.to_string(); + match e { - Error::BlockQueryError(_) => rpc::Error { - code: rpc::ErrorCode::ServerError(BASE_ERROR + 1), - message: e.to_string(), - data: None, - }, - Error::BlockExecutionFailed => rpc::Error { - code: rpc::ErrorCode::ServerError(BASE_ERROR + 3), - message: e.to_string(), - data: None, - }, - Error::WitnessCompactionFailed => rpc::Error { - code: rpc::ErrorCode::ServerError(BASE_ERROR + 4), - message: e.to_string(), - data: None, - }, - e => errors::internal(e), + Error::BlockQueryError(_) => + CallError::Custom(ErrorObject::owned(BASE_ERROR + 1, msg, None::<()>)), + Error::BlockExecutionFailed => + CallError::Custom(ErrorObject::owned(BASE_ERROR + 3, msg, None::<()>)), + Error::WitnessCompactionFailed => + CallError::Custom(ErrorObject::owned(BASE_ERROR + 4, msg, None::<()>)), + Error::UnsafeRpcCalled(e) => e.into(), } + .into() } } diff --git a/client/rpc-api/src/dev/mod.rs b/client/rpc-api/src/dev/mod.rs index b1ae8934af8a1..afd83272a0127 100644 --- a/client/rpc-api/src/dev/mod.rs +++ b/client/rpc-api/src/dev/mod.rs @@ -22,9 +22,8 @@ pub mod error; -use self::error::Result; use codec::{Decode, Encode}; -use jsonrpc_derive::rpc; +use jsonrpsee::{core::RpcResult, proc_macros::rpc}; use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; @@ -52,13 +51,13 @@ pub struct BlockStats { /// /// This API contains unstable and unsafe methods only meant for development nodes. They /// are all flagged as unsafe for this reason. -#[rpc] +#[rpc(client, server)] pub trait DevApi { /// Reexecute the specified `block_hash` and gather statistics while doing so. /// /// This function requires the specified block and its parent to be available /// at the queried node. If either the specified block or the parent is pruned, /// this function will return `None`. - #[rpc(name = "dev_getBlockStats")] - fn block_stats(&self, block_hash: Hash) -> Result>; + #[method(name = "dev_getBlockStats")] + fn block_stats(&self, block_hash: Hash) -> RpcResult>; } diff --git a/client/rpc-api/src/errors.rs b/client/rpc-api/src/errors.rs deleted file mode 100644 index e59b1b0eda5ce..0000000000000 --- a/client/rpc-api/src/errors.rs +++ /dev/null @@ -1,28 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use log::warn; - -pub fn internal(e: E) -> jsonrpc_core::Error { - warn!("Unknown error: {}", e); - jsonrpc_core::Error { - code: jsonrpc_core::ErrorCode::InternalError, - message: "Unknown error occurred".into(), - data: Some(e.to_string().into()), - } -} diff --git a/client/rpc-api/src/helpers.rs b/client/rpc-api/src/helpers.rs deleted file mode 100644 index 2fbd2f5040463..0000000000000 --- a/client/rpc-api/src/helpers.rs +++ /dev/null @@ -1,41 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use futures::{channel::oneshot, Future}; -use std::pin::Pin; - -/// Wraps around `oneshot::Receiver` and adjusts the error type to produce an internal error if the -/// sender gets dropped. -pub struct Receiver(pub oneshot::Receiver); - -impl Future for Receiver { - type Output = Result; - - fn poll( - mut self: Pin<&mut Self>, - cx: &mut std::task::Context<'_>, - ) -> std::task::Poll { - Future::poll(Pin::new(&mut self.0), cx).map_err(|_| jsonrpc_core::Error::internal_error()) - } -} - -impl jsonrpc_core::WrapFuture for Receiver { - fn into_future(self) -> jsonrpc_core::BoxFuture> { - Box::pin(async { self.await }) - } -} diff --git a/client/rpc-api/src/lib.rs b/client/rpc-api/src/lib.rs index e06f30bf9cd87..a0cbbcee80e3e 100644 --- a/client/rpc-api/src/lib.rs +++ b/client/rpc-api/src/lib.rs @@ -22,15 +22,9 @@ #![warn(missing_docs)] -mod errors; -mod helpers; -mod metadata; mod policy; -pub use helpers::Receiver; -pub use jsonrpc_core::IoHandlerExtension as RpcExtension; -pub use metadata::Metadata; -pub use policy::{DenyUnsafe, UnsafeRpcError}; +pub use policy::DenyUnsafe; pub mod author; pub mod chain; diff --git a/client/rpc-api/src/metadata.rs b/client/rpc-api/src/metadata.rs deleted file mode 100644 index 3c798782062e9..0000000000000 --- a/client/rpc-api/src/metadata.rs +++ /dev/null @@ -1,60 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -//! RPC Metadata -use std::sync::Arc; - -use futures::channel::mpsc; -use jsonrpc_pubsub::{PubSubMetadata, Session}; - -/// RPC Metadata. -/// -/// Manages persistent session for transports that support it -/// and may contain some additional info extracted from specific transports -/// (like remote client IP address, request headers, etc) -#[derive(Default, Clone)] -pub struct Metadata { - session: Option>, -} - -impl jsonrpc_core::Metadata for Metadata {} -impl PubSubMetadata for Metadata { - fn session(&self) -> Option> { - self.session.clone() - } -} - -impl Metadata { - /// Create new `Metadata` with session (Pub/Sub) support. - pub fn new(transport: mpsc::UnboundedSender) -> Self { - Metadata { session: Some(Arc::new(Session::new(transport))) } - } - - /// Create new `Metadata` for tests. - #[cfg(test)] - pub fn new_test() -> (mpsc::UnboundedReceiver, Self) { - let (tx, rx) = mpsc::unbounded(); - (rx, Self::new(tx)) - } -} - -impl From> for Metadata { - fn from(sender: mpsc::UnboundedSender) -> Self { - Self::new(sender) - } -} diff --git a/client/rpc-api/src/offchain/error.rs b/client/rpc-api/src/offchain/error.rs index 41f1416bfb367..be72e05fc4460 100644 --- a/client/rpc-api/src/offchain/error.rs +++ b/client/rpc-api/src/offchain/error.rs @@ -18,7 +18,10 @@ //! Offchain RPC errors. -use jsonrpc_core as rpc; +use jsonrpsee::{ + core::Error as JsonRpseeError, + types::error::{CallError, ErrorObject}, +}; /// Offchain RPC Result type. pub type Result = std::result::Result; @@ -35,16 +38,17 @@ pub enum Error { } /// Base error code for all offchain errors. -const BASE_ERROR: i64 = 5000; +const BASE_ERROR: i32 = 5000; -impl From for rpc::Error { +impl From for JsonRpseeError { fn from(e: Error) -> Self { match e { - Error::UnavailableStorageKind => rpc::Error { - code: rpc::ErrorCode::ServerError(BASE_ERROR + 1), - message: "This storage kind is not available yet".into(), - data: None, - }, + Error::UnavailableStorageKind => CallError::Custom(ErrorObject::owned( + BASE_ERROR + 1, + "This storage kind is not available yet", + None::<()>, + )) + .into(), Error::UnsafeRpcCalled(e) => e.into(), } } diff --git a/client/rpc-api/src/offchain/mod.rs b/client/rpc-api/src/offchain/mod.rs index c76e83011072d..d9435d9a875fe 100644 --- a/client/rpc-api/src/offchain/mod.rs +++ b/client/rpc-api/src/offchain/mod.rs @@ -18,22 +18,19 @@ //! Substrate offchain API. -pub mod error; - -use self::error::Result; -use jsonrpc_derive::rpc; +use jsonrpsee::{core::RpcResult, proc_macros::rpc}; use sp_core::{offchain::StorageKind, Bytes}; -pub use self::gen_client::Client as OffchainClient; +pub mod error; /// Substrate offchain RPC API -#[rpc] +#[rpc(client, server)] pub trait OffchainApi { /// Set offchain local storage under given key and prefix. - #[rpc(name = "offchain_localStorageSet")] - fn set_local_storage(&self, kind: StorageKind, key: Bytes, value: Bytes) -> Result<()>; + #[method(name = "offchain_localStorageSet")] + fn set_local_storage(&self, kind: StorageKind, key: Bytes, value: Bytes) -> RpcResult<()>; /// Get offchain local storage under given key and prefix. - #[rpc(name = "offchain_localStorageGet")] - fn get_local_storage(&self, kind: StorageKind, key: Bytes) -> Result>; + #[method(name = "offchain_localStorageGet")] + fn get_local_storage(&self, kind: StorageKind, key: Bytes) -> RpcResult>; } diff --git a/client/rpc-api/src/policy.rs b/client/rpc-api/src/policy.rs index dc0753c1b9139..69ca8958520a6 100644 --- a/client/rpc-api/src/policy.rs +++ b/client/rpc-api/src/policy.rs @@ -21,7 +21,13 @@ //! Contains a `DenyUnsafe` type that can be used to deny potentially unsafe //! RPC when accessed externally. -use jsonrpc_core as rpc; +use jsonrpsee::{ + core::Error as JsonRpseeError, + types::{ + error::{CallError, ErrorCode}, + ErrorObject, + }, +}; /// Signifies whether a potentially unsafe RPC should be denied. #[derive(Clone, Copy, Debug)] @@ -55,8 +61,18 @@ impl std::fmt::Display for UnsafeRpcError { impl std::error::Error for UnsafeRpcError {} -impl From for rpc::Error { - fn from(error: UnsafeRpcError) -> rpc::Error { - rpc::Error { code: rpc::ErrorCode::MethodNotFound, message: error.to_string(), data: None } +impl From for CallError { + fn from(e: UnsafeRpcError) -> CallError { + CallError::Custom(ErrorObject::owned( + ErrorCode::MethodNotFound.code(), + e.to_string(), + None::<()>, + )) + } +} + +impl From for JsonRpseeError { + fn from(e: UnsafeRpcError) -> JsonRpseeError { + JsonRpseeError::Call(e.into()) } } diff --git a/client/rpc-api/src/state/error.rs b/client/rpc-api/src/state/error.rs index 4414629e2e294..b1df64b4789ab 100644 --- a/client/rpc-api/src/state/error.rs +++ b/client/rpc-api/src/state/error.rs @@ -18,21 +18,19 @@ //! State RPC errors. -use crate::errors; -use jsonrpc_core as rpc; - +use jsonrpsee::{ + core::Error as JsonRpseeError, + types::error::{CallError, ErrorObject}, +}; /// State RPC Result type. pub type Result = std::result::Result; -/// State RPC future Result type. -pub type FutureResult = jsonrpc_core::BoxFuture>; - /// State RPC errors. #[derive(Debug, thiserror::Error)] pub enum Error { /// Client error. #[error("Client error: {}", .0)] - Client(#[from] Box), + Client(#[from] Box), /// Provided block range couldn't be resolved to a list of blocks. #[error("Cannot resolve a block range ['{:?}' ... '{:?}]. {}", .from, .to, .details)] InvalidBlockRange { @@ -57,22 +55,18 @@ pub enum Error { } /// Base code for all state errors. -const BASE_ERROR: i64 = 4000; +const BASE_ERROR: i32 = 4000; -impl From for rpc::Error { +impl From for JsonRpseeError { fn from(e: Error) -> Self { match e { - Error::InvalidBlockRange { .. } => rpc::Error { - code: rpc::ErrorCode::ServerError(BASE_ERROR + 1), - message: format!("{}", e), - data: None, - }, - Error::InvalidCount { .. } => rpc::Error { - code: rpc::ErrorCode::ServerError(BASE_ERROR + 2), - message: format!("{}", e), - data: None, - }, - e => errors::internal(e), + Error::InvalidBlockRange { .. } => + CallError::Custom(ErrorObject::owned(BASE_ERROR + 1, e.to_string(), None::<()>)) + .into(), + Error::InvalidCount { .. } => + CallError::Custom(ErrorObject::owned(BASE_ERROR + 2, e.to_string(), None::<()>)) + .into(), + e => Self::to_call_error(e), } } } diff --git a/client/rpc-api/src/state/mod.rs b/client/rpc-api/src/state/mod.rs index 42e927580960c..fba023e830262 100644 --- a/client/rpc-api/src/state/mod.rs +++ b/client/rpc-api/src/state/mod.rs @@ -18,161 +18,122 @@ //! Substrate state API. -pub mod error; -pub mod helpers; - -use self::error::FutureResult; -use jsonrpc_core::Result as RpcResult; -use jsonrpc_derive::rpc; -use jsonrpc_pubsub::{typed::Subscriber, SubscriptionId}; +use jsonrpsee::{core::RpcResult, proc_macros::rpc}; use sp_core::{ storage::{StorageChangeSet, StorageData, StorageKey}, Bytes, }; use sp_version::RuntimeVersion; -pub use self::{gen_client::Client as StateClient, helpers::ReadProof}; +pub mod error; +pub mod helpers; + +pub use self::helpers::ReadProof; /// Substrate state API -#[rpc] +#[rpc(client, server)] pub trait StateApi { - /// RPC Metadata - type Metadata; - /// Call a contract at a block's state. - #[rpc(name = "state_call", alias("state_callAt"))] - fn call(&self, name: String, bytes: Bytes, hash: Option) -> FutureResult; + #[method(name = "state_call", aliases = ["state_callAt"])] + async fn call(&self, name: String, bytes: Bytes, hash: Option) -> RpcResult; - /// DEPRECATED: Please use `state_getKeysPaged` with proper paging support. /// Returns the keys with prefix, leave empty to get all the keys. - #[rpc(name = "state_getKeys")] - fn storage_keys(&self, prefix: StorageKey, hash: Option) - -> FutureResult>; + #[method(name = "state_getKeys")] + #[deprecated(since = "2.0.0", note = "Please use `getKeysPaged` with proper paging support")] + async fn storage_keys( + &self, + prefix: StorageKey, + hash: Option, + ) -> RpcResult>; /// Returns the keys with prefix, leave empty to get all the keys - #[rpc(name = "state_getPairs")] - fn storage_pairs( + #[method(name = "state_getPairs")] + async fn storage_pairs( &self, prefix: StorageKey, hash: Option, - ) -> FutureResult>; + ) -> RpcResult>; /// Returns the keys with prefix with pagination support. /// Up to `count` keys will be returned. /// If `start_key` is passed, return next keys in storage in lexicographic order. - #[rpc(name = "state_getKeysPaged", alias("state_getKeysPagedAt"))] - fn storage_keys_paged( + #[method(name = "state_getKeysPaged", aliases = ["state_getKeysPagedAt"])] + async fn storage_keys_paged( &self, prefix: Option, count: u32, start_key: Option, hash: Option, - ) -> FutureResult>; + ) -> RpcResult>; /// Returns a storage entry at a specific block's state. - #[rpc(name = "state_getStorage", alias("state_getStorageAt"))] - fn storage(&self, key: StorageKey, hash: Option) -> FutureResult>; + #[method(name = "state_getStorage", aliases = ["state_getStorageAt"])] + async fn storage(&self, key: StorageKey, hash: Option) -> RpcResult>; /// Returns the hash of a storage entry at a block's state. - #[rpc(name = "state_getStorageHash", alias("state_getStorageHashAt"))] - fn storage_hash(&self, key: StorageKey, hash: Option) -> FutureResult>; + #[method(name = "state_getStorageHash", aliases = ["state_getStorageHashAt"])] + async fn storage_hash(&self, key: StorageKey, hash: Option) -> RpcResult>; /// Returns the size of a storage entry at a block's state. - #[rpc(name = "state_getStorageSize", alias("state_getStorageSizeAt"))] - fn storage_size(&self, key: StorageKey, hash: Option) -> FutureResult>; + #[method(name = "state_getStorageSize", aliases = ["state_getStorageSizeAt"])] + async fn storage_size(&self, key: StorageKey, hash: Option) -> RpcResult>; /// Returns the runtime metadata as an opaque blob. - #[rpc(name = "state_getMetadata")] - fn metadata(&self, hash: Option) -> FutureResult; + #[method(name = "state_getMetadata")] + async fn metadata(&self, hash: Option) -> RpcResult; /// Get the runtime version. - #[rpc(name = "state_getRuntimeVersion", alias("chain_getRuntimeVersion"))] - fn runtime_version(&self, hash: Option) -> FutureResult; + #[method(name = "state_getRuntimeVersion", aliases = ["chain_getRuntimeVersion"])] + async fn runtime_version(&self, hash: Option) -> RpcResult; /// Query historical storage entries (by key) starting from a block given as the second /// parameter. /// /// NOTE This first returned result contains the initial state of storage for all keys. /// Subsequent values in the vector represent changes to the previous state (diffs). - #[rpc(name = "state_queryStorage")] - fn query_storage( + #[method(name = "state_queryStorage")] + async fn query_storage( &self, keys: Vec, block: Hash, hash: Option, - ) -> FutureResult>>; + ) -> RpcResult>>; /// Query storage entries (by key) starting at block hash given as the second parameter. - #[rpc(name = "state_queryStorageAt")] - fn query_storage_at( + #[method(name = "state_queryStorageAt")] + async fn query_storage_at( &self, keys: Vec, at: Option, - ) -> FutureResult>>; + ) -> RpcResult>>; /// Returns proof of storage entries at a specific block's state. - #[rpc(name = "state_getReadProof")] - fn read_proof( + #[method(name = "state_getReadProof")] + async fn read_proof( &self, keys: Vec, hash: Option, - ) -> FutureResult>; + ) -> RpcResult>; /// New runtime version subscription - #[pubsub( - subscription = "state_runtimeVersion", - subscribe, - name = "state_subscribeRuntimeVersion", - alias("chain_subscribeRuntimeVersion") + #[subscription( + name = "state_subscribeRuntimeVersion" => "state_runtimeVersion", + unsubscribe = "state_unsubscribeRuntimeVersion", + aliases = ["chain_subscribeRuntimeVersion"], + unsubscribe_aliases = ["chain_unsubscribeRuntimeVersion"], + item = RuntimeVersion, )] - fn subscribe_runtime_version( - &self, - metadata: Self::Metadata, - subscriber: Subscriber, - ); + fn subscribe_runtime_version(&self); - /// Unsubscribe from runtime version subscription - #[pubsub( - subscription = "state_runtimeVersion", - unsubscribe, - name = "state_unsubscribeRuntimeVersion", - alias("chain_unsubscribeRuntimeVersion") + /// New storage subscription + #[subscription( + name = "state_subscribeStorage" => "state_storage", + unsubscribe = "state_unsubscribeStorage", + item = StorageChangeSet, )] - fn unsubscribe_runtime_version( - &self, - metadata: Option, - id: SubscriptionId, - ) -> RpcResult; - - /// Subscribe to the changes in the storage. - /// - /// This RPC endpoint has two modes of operation: - /// 1) When `keys` is not `None` you'll only be informed about the changes - /// done to the specified keys; this is RPC-safe. - /// 2) When `keys` is `None` you'll be informed of *all* of the changes; - /// **this is RPC-unsafe**. - /// - /// When subscribed to all of the changes this API will emit every storage - /// change for every block that is imported. These changes will only be sent - /// after a block is imported. If you require a consistent view across all changes - /// of every block, you need to take this into account. - #[pubsub(subscription = "state_storage", subscribe, name = "state_subscribeStorage")] - fn subscribe_storage( - &self, - metadata: Self::Metadata, - subscriber: Subscriber>, - keys: Option>, - ); - - /// Unsubscribe from storage subscription - #[pubsub(subscription = "state_storage", unsubscribe, name = "state_unsubscribeStorage")] - fn unsubscribe_storage( - &self, - metadata: Option, - id: SubscriptionId, - ) -> RpcResult; + fn subscribe_storage(&self, keys: Option>); - /// The `state_traceBlock` RPC provides a way to trace the re-execution of a single + /// The `traceBlock` RPC provides a way to trace the re-execution of a single /// block, collecting Spans and Events from both the client and the relevant WASM runtime. /// The Spans and Events are conceptually equivalent to those from the [Tracing][1] crate. /// @@ -323,13 +284,13 @@ pub trait StateApi { /// narrow down the traces using a smaller set of targets and/or storage keys. /// /// If you are having issues with maximum payload size you can use the flag - /// `-lstate_tracing=trace` to get some logging during tracing. - #[rpc(name = "state_traceBlock")] - fn trace_block( + /// `-ltracing=trace` to get some logging during tracing. + #[method(name = "state_traceBlock")] + async fn trace_block( &self, block: Hash, targets: Option, storage_keys: Option, methods: Option, - ) -> FutureResult; + ) -> RpcResult; } diff --git a/client/rpc-api/src/system/error.rs b/client/rpc-api/src/system/error.rs index 050d79b6ad636..777f8c6c6df0b 100644 --- a/client/rpc-api/src/system/error.rs +++ b/client/rpc-api/src/system/error.rs @@ -19,7 +19,10 @@ //! System RPC module errors. use crate::system::helpers::Health; -use jsonrpc_core as rpc; +use jsonrpsee::{ + core::Error as JsonRpseeError, + types::error::{CallError, ErrorObject}, +}; /// System RPC Result type. pub type Result = std::result::Result; @@ -35,22 +38,24 @@ pub enum Error { MalformattedPeerArg(String), } -/// Base code for all system errors. -const BASE_ERROR: i64 = 2000; +// Base code for all system errors. +const BASE_ERROR: i32 = 2000; +// Provided block range couldn't be resolved to a list of blocks. +const NOT_HEALTHY_ERROR: i32 = BASE_ERROR + 1; +// Peer argument is malformatted. +const MALFORMATTED_PEER_ARG_ERROR: i32 = BASE_ERROR + 2; -impl From for rpc::Error { +impl From for JsonRpseeError { fn from(e: Error) -> Self { match e { - Error::NotHealthy(ref h) => rpc::Error { - code: rpc::ErrorCode::ServerError(BASE_ERROR + 1), - message: format!("{}", e), - data: serde_json::to_value(h).ok(), - }, - Error::MalformattedPeerArg(ref e) => rpc::Error { - code: rpc::ErrorCode::ServerError(BASE_ERROR + 2), - message: e.clone(), - data: None, - }, + Error::NotHealthy(ref h) => + CallError::Custom(ErrorObject::owned(NOT_HEALTHY_ERROR, e.to_string(), Some(h))), + Error::MalformattedPeerArg(e) => CallError::Custom(ErrorObject::owned( + MALFORMATTED_PEER_ARG_ERROR + 2, + e, + None::<()>, + )), } + .into() } } diff --git a/client/rpc-api/src/system/mod.rs b/client/rpc-api/src/system/mod.rs index b610094f5b58d..1e12d5be87ee8 100644 --- a/client/rpc-api/src/system/mod.rs +++ b/client/rpc-api/src/system/mod.rs @@ -18,65 +18,61 @@ //! Substrate system API. -pub mod error; -pub mod helpers; - -use crate::helpers::Receiver; -use jsonrpc_core::BoxFuture; -use jsonrpc_derive::rpc; +use jsonrpsee::{ + core::{JsonValue, RpcResult}, + proc_macros::rpc, +}; -use self::error::Result as SystemResult; +pub use self::helpers::{Health, NodeRole, PeerInfo, SyncState, SystemInfo}; -pub use self::{ - gen_client::Client as SystemClient, - helpers::{Health, NodeRole, PeerInfo, SyncState, SystemInfo}, -}; +pub mod error; +pub mod helpers; /// Substrate system RPC API -#[rpc] +#[rpc(client, server)] pub trait SystemApi { /// Get the node's implementation name. Plain old string. - #[rpc(name = "system_name")] - fn system_name(&self) -> SystemResult; + #[method(name = "system_name")] + fn system_name(&self) -> RpcResult; /// Get the node implementation's version. Should be a semver string. - #[rpc(name = "system_version")] - fn system_version(&self) -> SystemResult; + #[method(name = "system_version")] + fn system_version(&self) -> RpcResult; /// Get the chain's name. Given as a string identifier. - #[rpc(name = "system_chain")] - fn system_chain(&self) -> SystemResult; + #[method(name = "system_chain")] + fn system_chain(&self) -> RpcResult; /// Get the chain's type. - #[rpc(name = "system_chainType")] - fn system_type(&self) -> SystemResult; + #[method(name = "system_chainType")] + fn system_type(&self) -> RpcResult; /// Get a custom set of properties as a JSON object, defined in the chain spec. - #[rpc(name = "system_properties")] - fn system_properties(&self) -> SystemResult; + #[method(name = "system_properties")] + fn system_properties(&self) -> RpcResult; /// Return health status of the node. /// /// Node is considered healthy if it is: /// - connected to some peers (unless running in dev mode) /// - not performing a major sync - #[rpc(name = "system_health", returns = "Health")] - fn system_health(&self) -> Receiver; + #[method(name = "system_health")] + async fn system_health(&self) -> RpcResult; /// Returns the base58-encoded PeerId of the node. - #[rpc(name = "system_localPeerId", returns = "String")] - fn system_local_peer_id(&self) -> Receiver; + #[method(name = "system_localPeerId")] + async fn system_local_peer_id(&self) -> RpcResult; - /// Returns the multiaddresses that the local node is listening on + /// Returns the multi-addresses that the local node is listening on /// /// The addresses include a trailing `/p2p/` with the local PeerId, and are thus suitable to - /// be passed to `system_addReservedPeer` or as a bootnode address for example. - #[rpc(name = "system_localListenAddresses", returns = "Vec")] - fn system_local_listen_addresses(&self) -> Receiver>; + /// be passed to `addReservedPeer` or as a bootnode address for example. + #[method(name = "system_localListenAddresses")] + async fn system_local_listen_addresses(&self) -> RpcResult>; /// Returns currently connected peers - #[rpc(name = "system_peers", returns = "Vec>")] - fn system_peers(&self) -> BoxFuture>>>; + #[method(name = "system_peers")] + async fn system_peers(&self) -> RpcResult>>; /// Returns current state of the network. /// @@ -84,47 +80,44 @@ pub trait SystemApi { /// as its format might change at any time. // TODO: the future of this call is uncertain: https://github.com/paritytech/substrate/issues/1890 // https://github.com/paritytech/substrate/issues/5541 - #[rpc(name = "system_unstable_networkState", returns = "jsonrpc_core::Value")] - fn system_network_state(&self) -> BoxFuture>; + #[method(name = "system_unstable_networkState")] + async fn system_network_state(&self) -> RpcResult; /// Adds a reserved peer. Returns the empty string or an error. The string /// parameter should encode a `p2p` multiaddr. /// /// `/ip4/198.51.100.19/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV` /// is an example of a valid, passing multiaddr with PeerId attached. - #[rpc(name = "system_addReservedPeer", returns = "()")] - fn system_add_reserved_peer(&self, peer: String) -> BoxFuture>; + #[method(name = "system_addReservedPeer")] + async fn system_add_reserved_peer(&self, peer: String) -> RpcResult<()>; /// Remove a reserved peer. Returns the empty string or an error. The string /// should encode only the PeerId e.g. `QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV`. - #[rpc(name = "system_removeReservedPeer", returns = "()")] - fn system_remove_reserved_peer( - &self, - peer_id: String, - ) -> BoxFuture>; + #[method(name = "system_removeReservedPeer")] + async fn system_remove_reserved_peer(&self, peer_id: String) -> RpcResult<()>; /// Returns the list of reserved peers - #[rpc(name = "system_reservedPeers", returns = "Vec")] - fn system_reserved_peers(&self) -> Receiver>; + #[method(name = "system_reservedPeers")] + async fn system_reserved_peers(&self) -> RpcResult>; /// Returns the roles the node is running as. - #[rpc(name = "system_nodeRoles", returns = "Vec")] - fn system_node_roles(&self) -> Receiver>; + #[method(name = "system_nodeRoles")] + async fn system_node_roles(&self) -> RpcResult>; /// Returns the state of the syncing of the node: starting block, current best block, highest /// known block. - #[rpc(name = "system_syncState", returns = "SyncState")] - fn system_sync_state(&self) -> Receiver>; + #[method(name = "system_syncState")] + async fn system_sync_state(&self) -> RpcResult>; /// Adds the supplied directives to the current log filter /// /// The syntax is identical to the CLI `=`: /// /// `sync=debug,state=trace` - #[rpc(name = "system_addLogFilter", returns = "()")] - fn system_add_log_filter(&self, directives: String) -> Result<(), jsonrpc_core::Error>; + #[method(name = "system_addLogFilter")] + fn system_add_log_filter(&self, directives: String) -> RpcResult<()>; /// Resets the log filter to Substrate defaults - #[rpc(name = "system_resetLogFilter", returns = "()")] - fn system_reset_log_filter(&self) -> Result<(), jsonrpc_core::Error>; + #[method(name = "system_resetLogFilter")] + fn system_reset_log_filter(&self) -> RpcResult<()>; } diff --git a/client/rpc-servers/Cargo.toml b/client/rpc-servers/Cargo.toml index 4a13e9624a58e..ad01f3bdd6199 100644 --- a/client/rpc-servers/Cargo.toml +++ b/client/rpc-servers/Cargo.toml @@ -14,12 +14,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] futures = "0.3.21" -http = { package = "jsonrpc-http-server", version = "18.0.0" } -ipc = { package = "jsonrpc-ipc-server", version = "18.0.0" } -jsonrpc-core = "18.0.0" +jsonrpsee = { version = "0.12.0", features = ["server"] } log = "0.4.16" -pubsub = { package = "jsonrpc-pubsub", version = "18.0.0" } serde_json = "1.0.79" tokio = { version = "1.17.0", features = ["parking_lot"] } -ws = { package = "jsonrpc-ws-server", version = "18.0.0" } prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } diff --git a/client/rpc-servers/src/lib.rs b/client/rpc-servers/src/lib.rs index 963d9aec072f5..4f69413895a9b 100644 --- a/client/rpc-servers/src/lib.rs +++ b/client/rpc-servers/src/lib.rs @@ -20,213 +20,193 @@ #![warn(missing_docs)] -mod middleware; - -use jsonrpc_core::{IoHandlerExtension, MetaIoHandler}; -use log::error; -use prometheus_endpoint::{register, Counter, PrometheusError, Registry, U64}; -use pubsub::PubSubMetadata; -use std::io; +use jsonrpsee::{ + http_server::{AccessControlBuilder, HttpServerBuilder, HttpServerHandle}, + ws_server::{WsServerBuilder, WsServerHandle}, + RpcModule, +}; +use std::{error::Error as StdError, net::SocketAddr}; + +pub use crate::middleware::{RpcMetrics, RpcMiddleware}; +pub use jsonrpsee::core::{ + id_providers::{RandomIntegerIdProvider, RandomStringIdProvider}, + traits::IdProvider, +}; const MEGABYTE: usize = 1024 * 1024; /// Maximal payload accepted by RPC servers. pub const RPC_MAX_PAYLOAD_DEFAULT: usize = 15 * MEGABYTE; -/// Maximal buffer size in WS server. -pub const WS_MAX_BUFFER_CAPACITY_DEFAULT: usize = 16 * MEGABYTE; - /// Default maximum number of connections for WS RPC servers. const WS_MAX_CONNECTIONS: usize = 100; -/// The RPC IoHandler containing all requested APIs. -pub type RpcHandler = pubsub::PubSubHandler; - -pub use middleware::{method_names, RpcMetrics, RpcMiddleware}; - -/// Construct rpc `IoHandler` -pub fn rpc_handler( - extension: impl IoHandlerExtension, - rpc_middleware: RpcMiddleware, -) -> RpcHandler { - let io_handler = MetaIoHandler::with_middleware(rpc_middleware); - let mut io = pubsub::PubSubHandler::new(io_handler); - extension.augment(&mut io); - - // add an endpoint to list all available methods. - let mut methods = io.iter().map(|x| x.0.clone()).collect::>(); - io.add_method("rpc_methods", { - methods.sort(); - let methods = serde_json::to_value(&methods) - .expect("Serialization of Vec is infallible; qed"); - - move |_| { - let methods = methods.clone(); - async move { - Ok(serde_json::json!({ - "version": 1, - "methods": methods, - })) - } - } - }); - io -} +/// Default maximum number subscriptions per connection for WS RPC servers. +const WS_MAX_SUBS_PER_CONN: usize = 1024; -/// RPC server-specific prometheus metrics. -#[derive(Debug, Clone, Default)] -pub struct ServerMetrics { - /// Number of sessions opened. - session_opened: Option>, - /// Number of sessions closed. - session_closed: Option>, -} +pub mod middleware; -impl ServerMetrics { - /// Create new WebSocket RPC server metrics. - pub fn new(registry: Option<&Registry>) -> Result { - registry - .map(|r| { - Ok(Self { - session_opened: register( - Counter::new( - "substrate_rpc_sessions_opened", - "Number of persistent RPC sessions opened", - )?, - r, - )? - .into(), - session_closed: register( - Counter::new( - "substrate_rpc_sessions_closed", - "Number of persistent RPC sessions closed", - )?, - r, - )? - .into(), - }) - }) - .unwrap_or_else(|| Ok(Default::default())) - } -} - -/// Type alias for ipc server -pub type IpcServer = ipc::Server; /// Type alias for http server -pub type HttpServer = http::Server; +pub type HttpServer = HttpServerHandle; /// Type alias for ws server -pub type WsServer = ws::Server; - -impl ws::SessionStats for ServerMetrics { - fn open_session(&self, _id: ws::SessionId) { - self.session_opened.as_ref().map(|m| m.inc()); - } +pub type WsServer = WsServerHandle; + +/// WebSocket specific settings on the server. +pub struct WsConfig { + /// Maximum connections. + pub max_connections: Option, + /// Maximum subscriptions per connection. + pub max_subs_per_conn: Option, + /// Maximum rpc request payload size. + pub max_payload_in_mb: Option, + /// Maximum rpc response payload size. + pub max_payload_out_mb: Option, +} - fn close_session(&self, _id: ws::SessionId) { - self.session_closed.as_ref().map(|m| m.inc()); +impl WsConfig { + // Deconstructs the config to get the finalized inner values. + // + // `Payload size` or `max subs per connection` bigger than u32::MAX will be truncated. + fn deconstruct(self) -> (u32, u32, u64, u32) { + let max_conns = self.max_connections.unwrap_or(WS_MAX_CONNECTIONS) as u64; + let max_payload_in_mb = payload_size_or_default(self.max_payload_in_mb) as u32; + let max_payload_out_mb = payload_size_or_default(self.max_payload_out_mb) as u32; + let max_subs_per_conn = self.max_subs_per_conn.unwrap_or(WS_MAX_SUBS_PER_CONN) as u32; + + (max_payload_in_mb, max_payload_out_mb, max_conns, max_subs_per_conn) } } /// Start HTTP server listening on given address. -pub fn start_http( - addr: &std::net::SocketAddr, +pub async fn start_http( + addrs: [SocketAddr; 2], cors: Option<&Vec>, - io: RpcHandler, - maybe_max_payload_mb: Option, - tokio_handle: tokio::runtime::Handle, -) -> io::Result { - let max_request_body_size = maybe_max_payload_mb - .map(|mb| mb.saturating_mul(MEGABYTE)) - .unwrap_or(RPC_MAX_PAYLOAD_DEFAULT); - - http::ServerBuilder::new(io) - .threads(1) - .event_loop_executor(tokio_handle) - .health_api(("/health", "system_health")) - .allowed_hosts(hosts_filtering(cors.is_some())) - .rest_api(if cors.is_some() { http::RestApi::Secure } else { http::RestApi::Unsecure }) - .cors(map_cors::(cors)) - .max_request_body_size(max_request_body_size) - .start_http(addr) -} - -/// Start IPC server listening on given path. -pub fn start_ipc( - addr: &str, - io: RpcHandler, - server_metrics: ServerMetrics, -) -> io::Result { - let builder = ipc::ServerBuilder::new(io); - #[cfg(target_os = "unix")] - builder.set_security_attributes({ - let security_attributes = ipc::SecurityAttributes::empty(); - security_attributes.set_mode(0o600)?; - security_attributes - }); - builder.session_stats(server_metrics).start(addr) + max_payload_in_mb: Option, + max_payload_out_mb: Option, + metrics: Option, + rpc_api: RpcModule, + rt: tokio::runtime::Handle, +) -> Result> { + let max_payload_in = payload_size_or_default(max_payload_in_mb); + let max_payload_out = payload_size_or_default(max_payload_out_mb); + + let mut acl = AccessControlBuilder::new(); + + if let Some(cors) = cors { + // Whitelist listening address. + // NOTE: set_allowed_hosts will whitelist both ports but only one will used. + acl = acl.set_allowed_hosts(format_allowed_hosts(&addrs[..]))?; + acl = acl.set_allowed_origins(cors)?; + }; + + let builder = HttpServerBuilder::new() + .max_request_body_size(max_payload_in as u32) + .max_response_body_size(max_payload_out as u32) + .set_access_control(acl.build()) + .custom_tokio_runtime(rt); + + let rpc_api = build_rpc_api(rpc_api); + let (handle, addr) = if let Some(metrics) = metrics { + let middleware = RpcMiddleware::new(metrics, "http".into()); + let builder = builder.set_middleware(middleware); + let server = builder.build(&addrs[..]).await?; + let addr = server.local_addr(); + (server.start(rpc_api)?, addr) + } else { + let server = builder.build(&addrs[..]).await?; + let addr = server.local_addr(); + (server.start(rpc_api)?, addr) + }; + + log::info!( + "Running JSON-RPC HTTP server: addr={}, allowed origins={:?}", + addr.map_or_else(|_| "unknown".to_string(), |a| a.to_string()), + cors + ); + + Ok(handle) } /// Start WS server listening on given address. -pub fn start_ws< - M: pubsub::PubSubMetadata + From>, ->( - addr: &std::net::SocketAddr, - max_connections: Option, +pub async fn start_ws( + addrs: [SocketAddr; 2], cors: Option<&Vec>, - io: RpcHandler, - maybe_max_payload_mb: Option, - maybe_max_out_buffer_capacity_mb: Option, - server_metrics: ServerMetrics, - tokio_handle: tokio::runtime::Handle, -) -> io::Result { - let max_payload = maybe_max_payload_mb - .map(|mb| mb.saturating_mul(MEGABYTE)) - .unwrap_or(RPC_MAX_PAYLOAD_DEFAULT); - let max_out_buffer_capacity = maybe_max_out_buffer_capacity_mb - .map(|mb| mb.saturating_mul(MEGABYTE)) - .unwrap_or(WS_MAX_BUFFER_CAPACITY_DEFAULT); - - if max_payload > max_out_buffer_capacity { - log::warn!( - "maximum payload ({}) is more than maximum output buffer ({}) size in ws server, the payload will actually be limited by the buffer size", - max_payload, - max_out_buffer_capacity, - ) + ws_config: WsConfig, + metrics: Option, + rpc_api: RpcModule, + rt: tokio::runtime::Handle, + id_provider: Option>, +) -> Result> { + let (max_payload_in, max_payload_out, max_connections, max_subs_per_conn) = + ws_config.deconstruct(); + + let mut builder = WsServerBuilder::new() + .max_request_body_size(max_payload_in) + .max_response_body_size(max_payload_out) + .max_connections(max_connections) + .max_subscriptions_per_connection(max_subs_per_conn) + .custom_tokio_runtime(rt); + + if let Some(provider) = id_provider { + builder = builder.set_id_provider(provider); + } else { + builder = builder.set_id_provider(RandomStringIdProvider::new(16)); + }; + + if let Some(cors) = cors { + // Whitelist listening address. + // NOTE: set_allowed_hosts will whitelist both ports but only one will used. + builder = builder.set_allowed_hosts(format_allowed_hosts(&addrs[..]))?; + builder = builder.set_allowed_origins(cors)?; } - ws::ServerBuilder::with_meta_extractor(io, |context: &ws::RequestContext| { - context.sender().into() - }) - .event_loop_executor(tokio_handle) - .max_payload(max_payload) - .max_connections(max_connections.unwrap_or(WS_MAX_CONNECTIONS)) - .max_out_buffer_capacity(max_out_buffer_capacity) - .allowed_origins(map_cors(cors)) - .allowed_hosts(hosts_filtering(cors.is_some())) - .session_stats(server_metrics) - .start(addr) - .map_err(|err| match err { - ws::Error::Io(io) => io, - ws::Error::ConnectionClosed => io::ErrorKind::BrokenPipe.into(), - e => { - error!("{}", e); - io::ErrorKind::Other.into() - }, - }) + let rpc_api = build_rpc_api(rpc_api); + let (handle, addr) = if let Some(metrics) = metrics { + let middleware = RpcMiddleware::new(metrics, "ws".into()); + let builder = builder.set_middleware(middleware); + let server = builder.build(&addrs[..]).await?; + let addr = server.local_addr(); + (server.start(rpc_api)?, addr) + } else { + let server = builder.build(&addrs[..]).await?; + let addr = server.local_addr(); + (server.start(rpc_api)?, addr) + }; + + log::info!( + "Running JSON-RPC WS server: addr={}, allowed origins={:?}", + addr.map_or_else(|_| "unknown".to_string(), |a| a.to_string()), + cors + ); + + Ok(handle) } -fn map_cors From<&'a str>>(cors: Option<&Vec>) -> http::DomainsValidation { - cors.map(|x| x.iter().map(AsRef::as_ref).map(Into::into).collect::>()) - .into() +fn format_allowed_hosts(addrs: &[SocketAddr]) -> Vec { + let mut hosts = Vec::with_capacity(addrs.len() * 2); + for addr in addrs { + hosts.push(format!("localhost:{}", addr.port())); + hosts.push(format!("127.0.0.1:{}", addr.port())); + } + hosts } -fn hosts_filtering(enable: bool) -> http::DomainsValidation { - if enable { - // NOTE The listening address is whitelisted by default. - // Setting an empty vector here enables the validation - // and allows only the listening address. - http::DomainsValidation::AllowOnly(vec![]) - } else { - http::DomainsValidation::Disabled - } +fn build_rpc_api(mut rpc_api: RpcModule) -> RpcModule { + let mut available_methods = rpc_api.method_names().collect::>(); + available_methods.sort_unstable(); + + rpc_api + .register_method("rpc_methods", move |_, _| { + Ok(serde_json::json!({ + "version": 1, + "methods": available_methods, + })) + }) + .expect("infallible all other methods have their own address space; qed"); + + rpc_api +} + +fn payload_size_or_default(size_mb: Option) -> usize { + size_mb.map_or(RPC_MAX_PAYLOAD_DEFAULT, |mb| mb.saturating_mul(MEGABYTE)) } diff --git a/client/rpc-servers/src/middleware.rs b/client/rpc-servers/src/middleware.rs index d4ac787ce9f0c..5b2ee4bedb7dd 100644 --- a/client/rpc-servers/src/middleware.rs +++ b/client/rpc-servers/src/middleware.rs @@ -16,34 +16,38 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! Middleware for RPC requests. +//! RPC middlware to collect prometheus metrics on RPC calls. -use std::collections::HashSet; - -use jsonrpc_core::{FutureOutput, FutureResponse, Metadata, Middleware as RequestMiddleware}; +use jsonrpsee::core::middleware::Middleware; use prometheus_endpoint::{ - register, CounterVec, HistogramOpts, HistogramVec, Opts, PrometheusError, Registry, U64, + register, Counter, CounterVec, HistogramOpts, HistogramVec, Opts, PrometheusError, Registry, + U64, }; -use futures::{future::Either, Future, FutureExt}; -use pubsub::PubSubMetadata; - -use crate::RpcHandler; - -/// Metrics for RPC middleware +/// Metrics for RPC middleware storing information about the number of requests started/completed, +/// calls started/completed and their timings. #[derive(Debug, Clone)] pub struct RpcMetrics { + /// Number of RPC requests received since the server started. requests_started: CounterVec, + /// Number of RPC requests completed since the server started. requests_finished: CounterVec, + /// Histogram over RPC execution times. calls_time: HistogramVec, + /// Number of calls started. calls_started: CounterVec, + /// Number of calls completed. calls_finished: CounterVec, + /// Number of Websocket sessions opened (Websocket only). + ws_sessions_opened: Option>, + /// Number of Websocket sessions closed (Websocket only). + ws_sessions_closed: Option>, } impl RpcMetrics { /// Create an instance of metrics pub fn new(metrics_registry: Option<&Registry>) -> Result, PrometheusError> { - if let Some(r) = metrics_registry { + if let Some(metrics_registry) = metrics_registry { Ok(Some(Self { requests_started: register( CounterVec::new( @@ -53,7 +57,7 @@ impl RpcMetrics { ), &["protocol"], )?, - r, + metrics_registry, )?, requests_finished: register( CounterVec::new( @@ -63,7 +67,7 @@ impl RpcMetrics { ), &["protocol"], )?, - r, + metrics_registry, )?, calls_time: register( HistogramVec::new( @@ -73,7 +77,7 @@ impl RpcMetrics { ), &["protocol", "method"], )?, - r, + metrics_registry, )?, calls_started: register( CounterVec::new( @@ -83,7 +87,7 @@ impl RpcMetrics { ), &["protocol", "method"], )?, - r, + metrics_registry, )?, calls_finished: register( CounterVec::new( @@ -93,8 +97,24 @@ impl RpcMetrics { ), &["protocol", "method", "is_error"], )?, - r, + metrics_registry, )?, + ws_sessions_opened: register( + Counter::new( + "substrate_rpc_sessions_opened", + "Number of persistent RPC sessions opened", + )?, + metrics_registry, + )? + .into(), + ws_sessions_closed: register( + Counter::new( + "substrate_rpc_sessions_closed", + "Number of persistent RPC sessions closed", + )?, + metrics_registry, + )? + .into(), })) } else { Ok(None) @@ -102,140 +122,71 @@ impl RpcMetrics { } } -/// Instantiates a dummy `IoHandler` given a builder function to extract supported method names. -pub fn method_names(gen_handler: F) -> Result, E> -where - F: FnOnce(RpcMiddleware) -> Result, E>, - M: PubSubMetadata, -{ - let io = gen_handler(RpcMiddleware::new(None, HashSet::new(), "dummy"))?; - Ok(io.iter().map(|x| x.0.clone()).collect()) -} - +#[derive(Clone)] /// Middleware for RPC calls pub struct RpcMiddleware { - metrics: Option, - known_rpc_method_names: HashSet, - transport_label: String, + metrics: RpcMetrics, + transport_label: &'static str, } impl RpcMiddleware { - /// Create an instance of middleware. - /// - /// - `metrics`: Will be used to report statistics. - /// - `transport_label`: The label that is used when reporting the statistics. - pub fn new( - metrics: Option, - known_rpc_method_names: HashSet, - transport_label: &str, - ) -> Self { - RpcMiddleware { metrics, known_rpc_method_names, transport_label: transport_label.into() } + /// Create a new [`RpcMiddleware`] with the provided [`RpcMetrics`]. + pub fn new(metrics: RpcMetrics, transport_label: &'static str) -> Self { + Self { metrics, transport_label } } } -impl RequestMiddleware for RpcMiddleware { - type Future = FutureResponse; - type CallFuture = FutureOutput; - - fn on_request( - &self, - request: jsonrpc_core::Request, - meta: M, - next: F, - ) -> Either - where - F: Fn(jsonrpc_core::Request, M) -> X + Send + Sync, - X: Future> + Send + 'static, - { - let metrics = self.metrics.clone(); - let transport_label = self.transport_label.clone(); - if let Some(ref metrics) = metrics { - metrics.requests_started.with_label_values(&[transport_label.as_str()]).inc(); - } - let r = next(request, meta); - Either::Left( - async move { - let r = r.await; - if let Some(ref metrics) = metrics { - metrics.requests_finished.with_label_values(&[transport_label.as_str()]).inc(); - } - r - } - .boxed(), - ) +impl Middleware for RpcMiddleware { + type Instant = std::time::Instant; + + fn on_connect(&self) { + self.metrics.ws_sessions_opened.as_ref().map(|counter| counter.inc()); } - fn on_call( - &self, - call: jsonrpc_core::Call, - meta: M, - next: F, - ) -> Either - where - F: Fn(jsonrpc_core::Call, M) -> X + Send + Sync, - X: Future> + Send + 'static, - { - let start = std::time::Instant::now(); - let name = call_name(&call, &self.known_rpc_method_names).to_owned(); - let metrics = self.metrics.clone(); - let transport_label = self.transport_label.clone(); - log::trace!(target: "rpc_metrics", "[{}] {} call: {:?}", transport_label, name, &call); - if let Some(ref metrics) = metrics { - metrics - .calls_started - .with_label_values(&[transport_label.as_str(), name.as_str()]) - .inc(); - } - let r = next(call, meta); - Either::Left( - async move { - let r = r.await; - let micros = start.elapsed().as_micros(); - if let Some(ref metrics) = metrics { - metrics - .calls_time - .with_label_values(&[transport_label.as_str(), name.as_str()]) - .observe(micros as _); - metrics - .calls_finished - .with_label_values(&[ - transport_label.as_str(), - name.as_str(), - if is_success(&r) { "true" } else { "false" }, - ]) - .inc(); - } - log::debug!( - target: "rpc_metrics", - "[{}] {} call took {} μs", - transport_label, - name, - micros, - ); - r - } - .boxed(), - ) + fn on_request(&self) -> Self::Instant { + let now = std::time::Instant::now(); + self.metrics.requests_started.with_label_values(&[self.transport_label]).inc(); + now } -} -fn call_name<'a>(call: &'a jsonrpc_core::Call, known_methods: &HashSet) -> &'a str { - // To prevent bloating metric with all invalid method names we filter them out here. - let only_known = |method: &'a String| { - if known_methods.contains(method) { - method.as_str() - } else { - "invalid method" - } - }; + fn on_call(&self, name: &str) { + log::trace!(target: "rpc_metrics", "[{}] on_call name={}", self.transport_label, name); + self.metrics + .calls_started + .with_label_values(&[self.transport_label, name]) + .inc(); + } - match call { - jsonrpc_core::Call::Invalid { .. } => "invalid call", - jsonrpc_core::Call::MethodCall(ref call) => only_known(&call.method), - jsonrpc_core::Call::Notification(ref notification) => only_known(¬ification.method), + fn on_result(&self, name: &str, success: bool, started_at: Self::Instant) { + let micros = started_at.elapsed().as_micros(); + log::debug!( + target: "rpc_metrics", + "[{}] {} call took {} μs", + self.transport_label, + name, + micros, + ); + self.metrics + .calls_time + .with_label_values(&[self.transport_label, name]) + .observe(micros as _); + + self.metrics + .calls_finished + .with_label_values(&[ + self.transport_label, + name, + if success { "true" } else { "false" }, + ]) + .inc(); + } + + fn on_response(&self, started_at: Self::Instant) { + log::trace!(target: "rpc_metrics", "[{}] on_response started_at={:?}", self.transport_label, started_at); + self.metrics.requests_finished.with_label_values(&[self.transport_label]).inc(); } -} -fn is_success(output: &Option) -> bool { - matches!(output, Some(jsonrpc_core::Output::Success(..))) + fn on_disconnect(&self) { + self.metrics.ws_sessions_closed.as_ref().map(|counter| counter.inc()); + } } diff --git a/client/rpc/Cargo.toml b/client/rpc/Cargo.toml index f76665d6c97a7..515de401119d4 100644 --- a/client/rpc/Cargo.toml +++ b/client/rpc/Cargo.toml @@ -16,11 +16,10 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.21" hash-db = { version = "0.15.2", default-features = false } -jsonrpc-pubsub = "18.0.0" +jsonrpsee = { version = "0.12.0", features = ["server"] } lazy_static = { version = "1.4.0", optional = true } log = "0.4.16" parking_lot = "0.12.0" -rpc = { package = "jsonrpc-core", version = "18.0.0" } serde_json = "1.0.79" sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } sc-chain-spec = { version = "4.0.0-dev", path = "../chain-spec" } @@ -39,14 +38,19 @@ sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } sp-session = { version = "4.0.0-dev", path = "../../primitives/session" } sp-version = { version = "5.0.0", path = "../../primitives/version" } +tokio = { version = "1.17.0", optional = true } + [dev-dependencies] +env_logger = "0.9" assert_matches = "1.3.0" lazy_static = "1.4.0" +sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } sc-network = { version = "0.10.0-dev", path = "../network" } sc-transaction-pool = { version = "4.0.0-dev", path = "../transaction-pool" } sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } +tokio = "1.17.0" sp-io = { version = "6.0.0", path = "../../primitives/io" } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } [features] -test-helpers = ["lazy_static"] +test-helpers = ["lazy_static", "tokio"] diff --git a/client/rpc/src/author/mod.rs b/client/rpc/src/author/mod.rs index 2821eea2cc09d..d10398afc813b 100644 --- a/client/rpc/src/author/mod.rs +++ b/client/rpc/src/author/mod.rs @@ -23,26 +23,27 @@ mod tests; use std::sync::Arc; -use sp_blockchain::HeaderBackend; +use crate::SubscriptionTaskExecutor; use codec::{Decode, Encode}; -use futures::{ - future::{FutureExt, TryFutureExt}, - SinkExt, StreamExt as _, +use futures::{FutureExt, TryFutureExt}; +use jsonrpsee::{ + core::{async_trait, Error as JsonRpseeError, RpcResult}, + PendingSubscription, }; -use jsonrpc_pubsub::{manager::SubscriptionManager, typed::Subscriber, SubscriptionId}; use sc_rpc_api::DenyUnsafe; use sc_transaction_pool_api::{ error::IntoPoolError, BlockHash, InPoolTransaction, TransactionFor, TransactionPool, - TransactionSource, TransactionStatus, TxHash, + TransactionSource, TxHash, }; use sp_api::ProvideRuntimeApi; +use sp_blockchain::HeaderBackend; use sp_core::Bytes; use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; use sp_runtime::{generic, traits::Block as BlockT}; use sp_session::SessionKeys; -use self::error::{Error, FutureResult, Result}; +use self::error::{Error, Result}; /// Re-export the API for backward compatibility. pub use sc_rpc_api::author::*; @@ -52,12 +53,12 @@ pub struct Author { client: Arc, /// Transactions pool pool: Arc

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

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

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

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

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

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

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

>("chain_getHeader", [H256::from_low_u64_be(5)]) + .await + .unwrap(), + None ); } -#[test] -fn should_return_a_block() { +#[tokio::test] +async fn should_return_a_block() { let mut client = Arc::new(substrate_test_runtime_client::new()); - let api = new_full(client.clone(), SubscriptionManager::new(Arc::new(TaskExecutor))); + let api = new_full(client.clone(), test_executor()).into_rpc(); let block = client.new_block(Default::default()).unwrap().build().unwrap().block; let block_hash = block.hash(); - executor::block_on(client.import(BlockOrigin::Own, block)).unwrap(); + client.import(BlockOrigin::Own, block).await.unwrap(); + + let res: SignedBlock = + api.call("chain_getBlock", [H256::from(client.genesis_hash())]).await.unwrap(); // Genesis block is not justified - assert_matches!( - executor::block_on(api.block(Some(client.genesis_hash()).into())), - Ok(Some(SignedBlock { justifications: None, .. })) - ); + assert!(res.justifications.is_none()); - assert_matches!( - executor::block_on(api.block(Some(block_hash).into())), - Ok(Some(ref x)) if x.block == Block { + let res: SignedBlock = + api.call("chain_getBlock", [H256::from(block_hash)]).await.unwrap(); + assert_eq!( + res.block, + Block { header: Header { parent_hash: client.genesis_hash(), number: 1, - state_root: x.block.header.state_root.clone(), - extrinsics_root: - "03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314".parse().unwrap(), + state_root: res.block.header.state_root.clone(), + extrinsics_root: "03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314" + .parse() + .unwrap(), digest: Default::default(), }, extrinsics: vec![], } ); - assert_matches!( - executor::block_on(api.block(None.into())), - Ok(Some(ref x)) if x.block == Block { + let res: SignedBlock = api.call("chain_getBlock", Vec::::new()).await.unwrap(); + assert_eq!( + res.block, + Block { header: Header { parent_hash: client.genesis_hash(), number: 1, - state_root: x.block.header.state_root.clone(), - extrinsics_root: - "03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314".parse().unwrap(), + state_root: res.block.header.state_root.clone(), + extrinsics_root: "03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314" + .parse() + .unwrap(), digest: Default::default(), }, extrinsics: vec![], } ); - assert_matches!(executor::block_on(api.block(Some(H256::from_low_u64_be(5)).into())), Ok(None)); + assert_matches!( + api.call::<_, Option
>("chain_getBlock", [H256::from_low_u64_be(5)]) + .await + .unwrap(), + None + ); } -#[test] -fn should_return_block_hash() { +#[tokio::test] +async fn should_return_block_hash() { let mut client = Arc::new(substrate_test_runtime_client::new()); - let api = new_full(client.clone(), SubscriptionManager::new(Arc::new(TaskExecutor))); + let api = new_full(client.clone(), test_executor()).into_rpc(); - assert_matches!( - api.block_hash(None.into()), - Ok(ListOrValue::Value(Some(ref x))) if x == &client.genesis_hash() - ); + let res: ListOrValue> = + api.call("chain_getBlockHash", EmptyParams::new()).await.unwrap(); assert_matches!( - api.block_hash(Some(ListOrValue::Value(0u64.into())).into()), - Ok(ListOrValue::Value(Some(ref x))) if x == &client.genesis_hash() + res, + ListOrValue::Value(Some(ref x)) if x == &client.genesis_hash() ); + let res: ListOrValue> = + api.call("chain_getBlockHash", [ListOrValue::from(0_u64)]).await.unwrap(); assert_matches!( - api.block_hash(Some(ListOrValue::Value(1u64.into())).into()), - Ok(ListOrValue::Value(None)) + res, + ListOrValue::Value(Some(ref x)) if x == &client.genesis_hash() ); + let res: Option>> = + api.call("chain_getBlockHash", [ListOrValue::from(1_u64)]).await.unwrap(); + assert_matches!(res, None); + let block = client.new_block(Default::default()).unwrap().build().unwrap().block; - executor::block_on(client.import(BlockOrigin::Own, block.clone())).unwrap(); + client.import(BlockOrigin::Own, block.clone()).await.unwrap(); + let res: ListOrValue> = + api.call("chain_getBlockHash", [ListOrValue::from(0_u64)]).await.unwrap(); assert_matches!( - api.block_hash(Some(ListOrValue::Value(0u64.into())).into()), - Ok(ListOrValue::Value(Some(ref x))) if x == &client.genesis_hash() + res, + ListOrValue::Value(Some(ref x)) if x == &client.genesis_hash() ); + + let res: ListOrValue> = + api.call("chain_getBlockHash", [ListOrValue::from(1_u64)]).await.unwrap(); assert_matches!( - api.block_hash(Some(ListOrValue::Value(1u64.into())).into()), - Ok(ListOrValue::Value(Some(ref x))) if x == &block.hash() + res, + ListOrValue::Value(Some(ref x)) if x == &block.hash() ); + + let res: ListOrValue> = api + .call("chain_getBlockHash", [ListOrValue::Value(sp_core::U256::from(1_u64))]) + .await + .unwrap(); assert_matches!( - api.block_hash(Some(ListOrValue::Value(sp_core::U256::from(1u64).into())).into()), - Ok(ListOrValue::Value(Some(ref x))) if x == &block.hash() + res, + ListOrValue::Value(Some(ref x)) if x == &block.hash() ); + let res: ListOrValue> = api + .call("chain_getBlockHash", [ListOrValue::List(vec![0_u64, 1_u64, 2_u64])]) + .await + .unwrap(); assert_matches!( - api.block_hash(Some(vec![0u64.into(), 1u64.into(), 2u64.into()].into())), - Ok(ListOrValue::List(list)) if list == &[client.genesis_hash().into(), block.hash().into(), None] + res, + ListOrValue::List(list) if list == &[client.genesis_hash().into(), block.hash().into(), None] ); } -#[test] -fn should_return_finalized_hash() { +#[tokio::test] +async fn should_return_finalized_hash() { let mut client = Arc::new(substrate_test_runtime_client::new()); - let api = new_full(client.clone(), SubscriptionManager::new(Arc::new(TaskExecutor))); + let api = new_full(client.clone(), test_executor()).into_rpc(); - assert_matches!( - api.finalized_head(), - Ok(ref x) if x == &client.genesis_hash() - ); + let res: H256 = api.call("chain_getFinalizedHead", EmptyParams::new()).await.unwrap(); + assert_eq!(res, client.genesis_hash()); // import new block let block = client.new_block(Default::default()).unwrap().build().unwrap().block; - executor::block_on(client.import(BlockOrigin::Own, block)).unwrap(); + client.import(BlockOrigin::Own, block).await.unwrap(); + // no finalization yet - assert_matches!( - api.finalized_head(), - Ok(ref x) if x == &client.genesis_hash() - ); + let res: H256 = api.call("chain_getFinalizedHead", EmptyParams::new()).await.unwrap(); + assert_eq!(res, client.genesis_hash()); // finalize client.finalize_block(BlockId::number(1), None).unwrap(); - assert_matches!( - api.finalized_head(), - Ok(ref x) if x == &client.block_hash(1).unwrap().unwrap() - ); + let res: H256 = api.call("chain_getFinalizedHead", EmptyParams::new()).await.unwrap(); + assert_eq!(res, client.block_hash(1).unwrap().unwrap()); } -#[test] -fn should_notify_about_latest_block() { - let (subscriber, id, mut transport) = Subscriber::new_test("test"); - - { - let mut client = Arc::new(substrate_test_runtime_client::new()); - let api = new_full(client.clone(), SubscriptionManager::new(Arc::new(TaskExecutor))); - - api.subscribe_all_heads(Default::default(), subscriber); - - // assert id assigned - assert!(matches!(executor::block_on(id), Ok(Ok(SubscriptionId::String(_))))); - - let block = client.new_block(Default::default()).unwrap().build().unwrap().block; - executor::block_on(client.import(BlockOrigin::Own, block)).unwrap(); - } - - // Check for the correct number of notifications - executor::block_on((&mut transport).take(2).collect::>()); - assert!(executor::block_on(transport.next()).is_none()); +#[tokio::test] +async fn should_notify_about_latest_block() { + test_head_subscription("chain_subscribeAllHeads").await; } -#[test] -fn should_notify_about_best_block() { - let (subscriber, id, mut transport) = Subscriber::new_test("test"); - - { - let mut client = Arc::new(substrate_test_runtime_client::new()); - let api = new_full(client.clone(), SubscriptionManager::new(Arc::new(TaskExecutor))); - - api.subscribe_new_heads(Default::default(), subscriber); - - // assert id assigned - assert!(matches!(executor::block_on(id), Ok(Ok(SubscriptionId::String(_))))); - - let block = client.new_block(Default::default()).unwrap().build().unwrap().block; - executor::block_on(client.import(BlockOrigin::Own, block)).unwrap(); - } - - // Assert that the correct number of notifications have been sent. - executor::block_on((&mut transport).take(2).collect::>()); - assert!(executor::block_on(transport.next()).is_none()); +#[tokio::test] +async fn should_notify_about_best_block() { + test_head_subscription("chain_subscribeNewHeads").await; } -#[test] -fn should_notify_about_finalized_block() { - let (subscriber, id, mut transport) = Subscriber::new_test("test"); - - { - let mut client = Arc::new(substrate_test_runtime_client::new()); - let api = new_full(client.clone(), SubscriptionManager::new(Arc::new(TaskExecutor))); - - api.subscribe_finalized_heads(Default::default(), subscriber); +#[tokio::test] +async fn should_notify_about_finalized_block() { + test_head_subscription("chain_subscribeFinalizedHeads").await; +} - // assert id assigned - assert!(matches!(executor::block_on(id), Ok(Ok(SubscriptionId::String(_))))); +async fn test_head_subscription(method: &str) { + let mut client = Arc::new(substrate_test_runtime_client::new()); + let mut sub = { + let api = new_full(client.clone(), test_executor()).into_rpc(); + let sub = api.subscribe(method, EmptyParams::new()).await.unwrap(); let block = client.new_block(Default::default()).unwrap().build().unwrap().block; - executor::block_on(client.import(BlockOrigin::Own, block)).unwrap(); + client.import(BlockOrigin::Own, block).await.unwrap(); client.finalize_block(BlockId::number(1), None).unwrap(); - } + sub + }; + + assert_matches!(timeout_secs(10, sub.next::
()).await, Ok(Some(_))); + assert_matches!(timeout_secs(10, sub.next::
()).await, Ok(Some(_))); - // Assert that the correct number of notifications have been sent. - executor::block_on((&mut transport).take(2).collect::>()); - assert!(executor::block_on(transport.next()).is_none()); + sub.close(); + assert_matches!(timeout_secs(10, sub.next::
()).await, Ok(None)); } diff --git a/client/rpc/src/dev/mod.rs b/client/rpc/src/dev/mod.rs index d782a03feae43..7f4b68f56f6f6 100644 --- a/client/rpc/src/dev/mod.rs +++ b/client/rpc/src/dev/mod.rs @@ -16,19 +16,15 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! Implementation of the [`DevApi`] trait providing debug utilities for Substrate based +//! Implementation of the [`DevApiServer`] trait providing debug utilities for Substrate based //! blockchains. #[cfg(test)] mod tests; -pub use sc_rpc_api::dev::{BlockStats, DevApi}; - +use jsonrpsee::core::RpcResult; use sc_client_api::{BlockBackend, HeaderBackend}; -use sc_rpc_api::{ - dev::error::{Error, Result}, - DenyUnsafe, -}; +use sc_rpc_api::{dev::error::Error, DenyUnsafe}; use sp_api::{ApiExt, Core, ProvideRuntimeApi}; use sp_core::Encode; use sp_runtime::{ @@ -40,6 +36,8 @@ use std::{ sync::Arc, }; +pub use sc_rpc_api::dev::{BlockStats, DevApiServer}; + type HasherOf = <::Header as Header>::Hashing; /// The Dev API. All methods are unsafe. @@ -56,7 +54,7 @@ impl Dev { } } -impl DevApi for Dev +impl DevApiServer for Dev where Block: BlockT + 'static, Client: BlockBackend @@ -67,7 +65,7 @@ where + 'static, Client::Api: Core, { - fn block_stats(&self, hash: Block::Hash) -> Result> { + fn block_stats(&self, hash: Block::Hash) -> RpcResult> { self.deny_unsafe.check_if_safe()?; let block = { diff --git a/client/rpc/src/dev/tests.rs b/client/rpc/src/dev/tests.rs index 1d31abe38b640..b7a0de8f5ae0b 100644 --- a/client/rpc/src/dev/tests.rs +++ b/client/rpc/src/dev/tests.rs @@ -18,25 +18,32 @@ use super::*; use assert_matches::assert_matches; -use futures::executor; +use jsonrpsee::{core::Error as JsonRpseeError, types::error::CallError}; use sc_block_builder::BlockBuilderProvider; use sp_blockchain::HeaderBackend; use sp_consensus::BlockOrigin; use substrate_test_runtime_client::{prelude::*, runtime::Block}; -#[test] -fn block_stats_work() { +#[tokio::test] +async fn block_stats_work() { let mut client = Arc::new(substrate_test_runtime_client::new()); - let api = >::new(client.clone(), DenyUnsafe::No); + let api = >::new(client.clone(), DenyUnsafe::No).into_rpc(); let block = client.new_block(Default::default()).unwrap().build().unwrap().block; - executor::block_on(client.import(BlockOrigin::Own, block)).unwrap(); + client.import(BlockOrigin::Own, block).await.unwrap(); // Can't gather stats for a block without a parent. - assert_eq!(api.block_stats(client.genesis_hash()).unwrap(), None); + assert_eq!( + api.call::<_, Option>("dev_getBlockStats", [client.genesis_hash()]) + .await + .unwrap(), + None + ); assert_eq!( - api.block_stats(client.info().best_hash).unwrap(), + api.call::<_, Option>("dev_getBlockStats", [client.info().best_hash]) + .await + .unwrap(), Some(BlockStats { witness_len: 597, witness_compact_len: 500, @@ -46,13 +53,17 @@ fn block_stats_work() { ); } -#[test] -fn deny_unsafe_works() { +#[tokio::test] +async fn deny_unsafe_works() { let mut client = Arc::new(substrate_test_runtime_client::new()); - let api = >::new(client.clone(), DenyUnsafe::Yes); + let api = >::new(client.clone(), DenyUnsafe::Yes).into_rpc(); let block = client.new_block(Default::default()).unwrap().build().unwrap().block; - executor::block_on(client.import(BlockOrigin::Own, block)).unwrap(); + client.import(BlockOrigin::Own, block).await.unwrap(); - assert_matches!(api.block_stats(client.info().best_hash), Err(Error::UnsafeRpcCalled(_))); + assert_matches!( + api.call::<_, Option>("dev_getBlockStats", [client.info().best_hash]) + .await, + Err(JsonRpseeError::Call(CallError::Custom(err))) if err.message().contains("RPC call is unsafe to be called externally") + ); } diff --git a/client/rpc/src/lib.rs b/client/rpc/src/lib.rs index 59a1d542d365a..a0e810eafbb62 100644 --- a/client/rpc/src/lib.rs +++ b/client/rpc/src/lib.rs @@ -22,15 +22,14 @@ #![warn(missing_docs)] -use futures::{ - task::{FutureObj, Spawn, SpawnError}, - FutureExt, +pub use jsonrpsee::core::{ + id_providers::{ + RandomIntegerIdProvider as RandomIntegerSubscriptionId, + RandomStringIdProvider as RandomStringSubscriptionId, + }, + traits::IdProvider as RpcSubscriptionIdProvider, }; -use sp_core::traits::SpawnNamed; -use std::sync::Arc; - -pub use rpc::IoHandlerExtension as RpcExtension; -pub use sc_rpc_api::{DenyUnsafe, Metadata}; +pub use sc_rpc_api::DenyUnsafe; pub mod author; pub mod chain; @@ -43,24 +42,4 @@ pub mod system; pub mod testing; /// Task executor that is being used by RPC subscriptions. -#[derive(Clone)] -pub struct SubscriptionTaskExecutor(Arc); - -impl SubscriptionTaskExecutor { - /// Create a new `Self` with the given spawner. - pub fn new(spawn: impl SpawnNamed + 'static) -> Self { - Self(Arc::new(spawn)) - } -} - -impl Spawn for SubscriptionTaskExecutor { - fn spawn_obj(&self, future: FutureObj<'static, ()>) -> Result<(), SpawnError> { - self.0 - .spawn("substrate-rpc-subscription", Some("rpc"), future.map(drop).boxed()); - Ok(()) - } - - fn status(&self) -> Result<(), SpawnError> { - Ok(()) - } -} +pub type SubscriptionTaskExecutor = std::sync::Arc; diff --git a/client/rpc/src/offchain/mod.rs b/client/rpc/src/offchain/mod.rs index 67b97d31ab949..b66b78274a64e 100644 --- a/client/rpc/src/offchain/mod.rs +++ b/client/rpc/src/offchain/mod.rs @@ -21,7 +21,8 @@ #[cfg(test)] mod tests; -use self::error::{Error, Result}; +use self::error::Error; +use jsonrpsee::core::{async_trait, Error as JsonRpseeError, RpcResult}; use parking_lot::RwLock; /// Re-export the API for backward compatibility. pub use sc_rpc_api::offchain::*; @@ -47,27 +48,27 @@ impl Offchain { } } -impl OffchainApi for Offchain { - /// Set offchain local storage under given key and prefix. - fn set_local_storage(&self, kind: StorageKind, key: Bytes, value: Bytes) -> Result<()> { +#[async_trait] +impl OffchainApiServer for Offchain { + fn set_local_storage(&self, kind: StorageKind, key: Bytes, value: Bytes) -> RpcResult<()> { self.deny_unsafe.check_if_safe()?; let prefix = match kind { StorageKind::PERSISTENT => sp_offchain::STORAGE_PREFIX, - StorageKind::LOCAL => return Err(Error::UnavailableStorageKind), + StorageKind::LOCAL => return Err(JsonRpseeError::from(Error::UnavailableStorageKind)), }; self.storage.write().set(prefix, &*key, &*value); Ok(()) } - /// Get offchain local storage under given key and prefix. - fn get_local_storage(&self, kind: StorageKind, key: Bytes) -> Result> { + fn get_local_storage(&self, kind: StorageKind, key: Bytes) -> RpcResult> { self.deny_unsafe.check_if_safe()?; let prefix = match kind { StorageKind::PERSISTENT => sp_offchain::STORAGE_PREFIX, - StorageKind::LOCAL => return Err(Error::UnavailableStorageKind), + StorageKind::LOCAL => return Err(JsonRpseeError::from(Error::UnavailableStorageKind)), }; + Ok(self.storage.read().get(prefix, &*key).map(Into::into)) } } diff --git a/client/rpc/src/offchain/tests.rs b/client/rpc/src/offchain/tests.rs index 219eeb192dfdd..28a7b6115b657 100644 --- a/client/rpc/src/offchain/tests.rs +++ b/client/rpc/src/offchain/tests.rs @@ -39,6 +39,7 @@ fn local_storage_should_work() { #[test] fn offchain_calls_considered_unsafe() { + use jsonrpsee::types::error::CallError; let storage = InMemOffchainStorage::default(); let offchain = Offchain::new(storage, DenyUnsafe::Yes); let key = Bytes(b"offchain_storage".to_vec()); @@ -46,10 +47,14 @@ fn offchain_calls_considered_unsafe() { assert_matches!( offchain.set_local_storage(StorageKind::PERSISTENT, key.clone(), value.clone()), - Err(Error::UnsafeRpcCalled(_)) + Err(JsonRpseeError::Call(CallError::Custom(err))) => { + assert_eq!(err.message(), "RPC call is unsafe to be called externally") + } ); assert_matches!( offchain.get_local_storage(StorageKind::PERSISTENT, key), - Err(Error::UnsafeRpcCalled(_)) + Err(JsonRpseeError::Call(CallError::Custom(err))) => { + assert_eq!(err.message(), "RPC call is unsafe to be called externally") + } ); } diff --git a/client/rpc/src/state/mod.rs b/client/rpc/src/state/mod.rs index c9806a30b4549..a45651c5e7990 100644 --- a/client/rpc/src/state/mod.rs +++ b/client/rpc/src/state/mod.rs @@ -23,11 +23,15 @@ mod state_full; #[cfg(test)] mod tests; -use futures::FutureExt; -use jsonrpc_pubsub::{manager::SubscriptionManager, typed::Subscriber, SubscriptionId}; -use rpc::Result as RpcResult; use std::sync::Arc; +use crate::SubscriptionTaskExecutor; + +use jsonrpsee::{ + core::{async_trait, Error as JsonRpseeError, RpcResult}, + ws_server::PendingSubscription, +}; + use sc_rpc_api::{state::ReadProof, DenyUnsafe}; use sp_core::{ storage::{PrefixedStorageKey, StorageChangeSet, StorageData, StorageKey}, @@ -38,7 +42,7 @@ use sp_version::RuntimeVersion; use sp_api::{CallApiAt, Metadata, ProvideRuntimeApi}; -use self::error::{Error, FutureResult}; +use self::error::Error; use sc_client_api::{ Backend, BlockBackend, BlockchainEvents, ExecutorProvider, ProofProvider, StorageProvider, @@ -49,144 +53,122 @@ use sp_blockchain::{HeaderBackend, HeaderMetadata}; const STORAGE_KEYS_PAGED_MAX_COUNT: u32 = 1000; /// State backend API. +#[async_trait] pub trait StateBackend: Send + Sync + 'static where Block: BlockT + 'static, Client: Send + Sync + 'static, { /// Call runtime method at given block. - fn call( + async fn call( &self, block: Option, method: String, call_data: Bytes, - ) -> FutureResult; + ) -> Result; /// Returns the keys with prefix, leave empty to get all the keys. - fn storage_keys( + async fn storage_keys( &self, block: Option, prefix: StorageKey, - ) -> FutureResult>; + ) -> Result, Error>; /// Returns the keys with prefix along with their values, leave empty to get all the pairs. - fn storage_pairs( + async fn storage_pairs( &self, block: Option, prefix: StorageKey, - ) -> FutureResult>; + ) -> Result, Error>; /// Returns the keys with prefix with pagination support. - fn storage_keys_paged( + async fn storage_keys_paged( &self, block: Option, prefix: Option, count: u32, start_key: Option, - ) -> FutureResult>; + ) -> Result, Error>; /// Returns a storage entry at a specific block's state. - fn storage( + async fn storage( &self, block: Option, key: StorageKey, - ) -> FutureResult>; + ) -> Result, Error>; /// Returns the hash of a storage entry at a block's state. - fn storage_hash( + async fn storage_hash( &self, block: Option, key: StorageKey, - ) -> FutureResult>; + ) -> Result, Error>; /// Returns the size of a storage entry at a block's state. /// /// If data is available at `key`, it is returned. Else, the sum of values who's key has `key` /// prefix is returned, i.e. all the storage (double) maps that have this prefix. - fn storage_size( + async fn storage_size( &self, block: Option, key: StorageKey, - ) -> FutureResult>; + ) -> Result, Error>; /// Returns the runtime metadata as an opaque blob. - fn metadata(&self, block: Option) -> FutureResult; + async fn metadata(&self, block: Option) -> Result; /// Get the runtime version. - fn runtime_version(&self, block: Option) -> FutureResult; + async fn runtime_version(&self, block: Option) -> Result; /// Query historical storage entries (by key) starting from a block given as the second /// parameter. /// /// NOTE This first returned result contains the initial state of storage for all keys. /// Subsequent values in the vector represent changes to the previous state (diffs). - fn query_storage( + async fn query_storage( &self, from: Block::Hash, to: Option, keys: Vec, - ) -> FutureResult>>; + ) -> Result>, Error>; /// Query storage entries (by key) starting at block hash given as the second parameter. - fn query_storage_at( + async fn query_storage_at( &self, keys: Vec, at: Option, - ) -> FutureResult>>; + ) -> Result>, Error>; /// Returns proof of storage entries at a specific block's state. - fn read_proof( + async fn read_proof( &self, block: Option, keys: Vec, - ) -> FutureResult>; - - /// New runtime version subscription - fn subscribe_runtime_version( - &self, - _meta: crate::Metadata, - subscriber: Subscriber, - ); - - /// Unsubscribe from runtime version subscription - fn unsubscribe_runtime_version( - &self, - _meta: Option, - id: SubscriptionId, - ) -> RpcResult; - - /// New storage subscription - fn subscribe_storage( - &self, - _meta: crate::Metadata, - subscriber: Subscriber>, - keys: Option>, - ); - - /// Unsubscribe from storage subscription - fn unsubscribe_storage( - &self, - _meta: Option, - id: SubscriptionId, - ) -> RpcResult; + ) -> Result, Error>; /// Trace storage changes for block - fn trace_block( + async fn trace_block( &self, block: Block::Hash, targets: Option, storage_keys: Option, methods: Option, - ) -> FutureResult; + ) -> Result; + + /// New runtime version subscription + fn subscribe_runtime_version(&self, sink: PendingSubscription); + + /// New storage subscription + fn subscribe_storage(&self, sink: PendingSubscription, keys: Option>); } /// Create new state API that works on full node. pub fn new_full( client: Arc, - subscriptions: SubscriptionManager, + executor: SubscriptionTaskExecutor, deny_unsafe: DenyUnsafe, rpc_max_payload: Option, -) -> (State, ChildState) +) -> (StateApi, ChildState) where Block: BlockT + 'static, Block::Hash: Unpin, @@ -207,168 +189,127 @@ where { let child_backend = Box::new(self::state_full::FullState::new( client.clone(), - subscriptions.clone(), + executor.clone(), rpc_max_payload, )); - let backend = - Box::new(self::state_full::FullState::new(client, subscriptions, rpc_max_payload)); - (State { backend, deny_unsafe }, ChildState { backend: child_backend }) + let backend = Box::new(self::state_full::FullState::new(client, executor, rpc_max_payload)); + (StateApi { backend, deny_unsafe }, ChildState { backend: child_backend }) } /// State API with subscriptions support. -pub struct State { +pub struct StateApi { backend: Box>, /// Whether to deny unsafe calls deny_unsafe: DenyUnsafe, } -impl StateApi for State +#[async_trait] +impl StateApiServer for StateApi where Block: BlockT + 'static, Client: Send + Sync + 'static, { - type Metadata = crate::Metadata; - - fn call(&self, method: String, data: Bytes, block: Option) -> FutureResult { - self.backend.call(block, method, data) + async fn call( + &self, + method: String, + data: Bytes, + block: Option, + ) -> RpcResult { + self.backend.call(block, method, data).await.map_err(Into::into) } - fn storage_keys( + async fn storage_keys( &self, key_prefix: StorageKey, block: Option, - ) -> FutureResult> { - self.backend.storage_keys(block, key_prefix) + ) -> RpcResult> { + self.backend.storage_keys(block, key_prefix).await.map_err(Into::into) } - fn storage_pairs( + async fn storage_pairs( &self, key_prefix: StorageKey, block: Option, - ) -> FutureResult> { - if let Err(err) = self.deny_unsafe.check_if_safe() { - return async move { Err(err.into()) }.boxed() - } - - self.backend.storage_pairs(block, key_prefix) + ) -> RpcResult> { + self.deny_unsafe.check_if_safe()?; + self.backend.storage_pairs(block, key_prefix).await.map_err(Into::into) } - fn storage_keys_paged( + async fn storage_keys_paged( &self, prefix: Option, count: u32, start_key: Option, block: Option, - ) -> FutureResult> { + ) -> RpcResult> { if count > STORAGE_KEYS_PAGED_MAX_COUNT { - return async move { - Err(Error::InvalidCount { value: count, max: STORAGE_KEYS_PAGED_MAX_COUNT }) - } - .boxed() + return Err(JsonRpseeError::from(Error::InvalidCount { + value: count, + max: STORAGE_KEYS_PAGED_MAX_COUNT, + })) } - self.backend.storage_keys_paged(block, prefix, count, start_key) + self.backend + .storage_keys_paged(block, prefix, count, start_key) + .await + .map_err(Into::into) } - fn storage( + async fn storage( &self, key: StorageKey, block: Option, - ) -> FutureResult> { - self.backend.storage(block, key) + ) -> RpcResult> { + self.backend.storage(block, key).await.map_err(Into::into) } - fn storage_hash( + async fn storage_hash( &self, key: StorageKey, block: Option, - ) -> FutureResult> { - self.backend.storage_hash(block, key) + ) -> RpcResult> { + self.backend.storage_hash(block, key).await.map_err(Into::into) } - fn storage_size( + async fn storage_size( &self, key: StorageKey, block: Option, - ) -> FutureResult> { - self.backend.storage_size(block, key) + ) -> RpcResult> { + self.backend.storage_size(block, key).await.map_err(Into::into) + } + + async fn metadata(&self, block: Option) -> RpcResult { + self.backend.metadata(block).await.map_err(Into::into) } - fn metadata(&self, block: Option) -> FutureResult { - self.backend.metadata(block) + async fn runtime_version(&self, at: Option) -> RpcResult { + self.backend.runtime_version(at).await.map_err(Into::into) } - fn query_storage( + async fn query_storage( &self, keys: Vec, from: Block::Hash, to: Option, - ) -> FutureResult>> { - if let Err(err) = self.deny_unsafe.check_if_safe() { - return async move { Err(err.into()) }.boxed() - } - - self.backend.query_storage(from, to, keys) + ) -> RpcResult>> { + self.deny_unsafe.check_if_safe()?; + self.backend.query_storage(from, to, keys).await.map_err(Into::into) } - fn query_storage_at( + async fn query_storage_at( &self, keys: Vec, at: Option, - ) -> FutureResult>> { - self.backend.query_storage_at(keys, at) + ) -> RpcResult>> { + self.backend.query_storage_at(keys, at).await.map_err(Into::into) } - fn read_proof( + async fn read_proof( &self, keys: Vec, block: Option, - ) -> FutureResult> { - self.backend.read_proof(block, keys) - } - - fn subscribe_storage( - &self, - meta: Self::Metadata, - subscriber: Subscriber>, - keys: Option>, - ) { - if keys.is_none() { - if let Err(err) = self.deny_unsafe.check_if_safe() { - subscriber.reject(err.into()) - .expect("subscription rejection can only fail if it's been already rejected, and we're rejecting it for the first time; qed"); - return - } - } - - self.backend.subscribe_storage(meta, subscriber, keys) - } - - fn unsubscribe_storage( - &self, - meta: Option, - id: SubscriptionId, - ) -> RpcResult { - self.backend.unsubscribe_storage(meta, id) - } - - fn runtime_version(&self, at: Option) -> FutureResult { - self.backend.runtime_version(at) - } - - fn subscribe_runtime_version( - &self, - meta: Self::Metadata, - subscriber: Subscriber, - ) { - self.backend.subscribe_runtime_version(meta, subscriber); - } - - fn unsubscribe_runtime_version( - &self, - meta: Option, - id: SubscriptionId, - ) -> RpcResult { - self.backend.unsubscribe_runtime_version(meta, id) + ) -> RpcResult> { + self.backend.read_proof(block, keys).await.map_err(Into::into) } /// Re-execute the given block with the tracing targets given in `targets` @@ -376,88 +317,102 @@ where /// /// Note: requires the node to run with `--rpc-methods=Unsafe`. /// Note: requires runtimes compiled with wasm tracing support, `--features with-tracing`. - fn trace_block( + async fn trace_block( &self, block: Block::Hash, targets: Option, storage_keys: Option, methods: Option, - ) -> FutureResult { - if let Err(err) = self.deny_unsafe.check_if_safe() { - return async move { Err(err.into()) }.boxed() + ) -> RpcResult { + self.deny_unsafe.check_if_safe()?; + self.backend + .trace_block(block, targets, storage_keys, methods) + .await + .map_err(Into::into) + } + + fn subscribe_runtime_version(&self, sink: PendingSubscription) { + self.backend.subscribe_runtime_version(sink) + } + + fn subscribe_storage(&self, sink: PendingSubscription, keys: Option>) { + if keys.is_none() { + if let Err(err) = self.deny_unsafe.check_if_safe() { + let _ = sink.reject(JsonRpseeError::from(err)); + return + } } - self.backend.trace_block(block, targets, storage_keys, methods) + self.backend.subscribe_storage(sink, keys) } } /// Child state backend API. +#[async_trait] pub trait ChildStateBackend: Send + Sync + 'static where Block: BlockT + 'static, Client: Send + Sync + 'static, { /// Returns proof of storage for a child key entries at a specific block's state. - fn read_child_proof( + async fn read_child_proof( &self, block: Option, storage_key: PrefixedStorageKey, keys: Vec, - ) -> FutureResult>; + ) -> Result, Error>; /// Returns the keys with prefix from a child storage, /// leave prefix empty to get all the keys. - fn storage_keys( + async fn storage_keys( &self, block: Option, storage_key: PrefixedStorageKey, prefix: StorageKey, - ) -> FutureResult>; + ) -> Result, Error>; /// Returns the keys with prefix from a child storage with pagination support. - fn storage_keys_paged( + async fn storage_keys_paged( &self, block: Option, storage_key: PrefixedStorageKey, prefix: Option, count: u32, start_key: Option, - ) -> FutureResult>; + ) -> Result, Error>; /// Returns a child storage entry at a specific block's state. - fn storage( + async fn storage( &self, block: Option, storage_key: PrefixedStorageKey, key: StorageKey, - ) -> FutureResult>; + ) -> Result, Error>; /// Returns child storage entries at a specific block's state. - fn storage_entries( + async fn storage_entries( &self, block: Option, storage_key: PrefixedStorageKey, keys: Vec, - ) -> FutureResult>>; + ) -> Result>, Error>; /// Returns the hash of a child storage entry at a block's state. - fn storage_hash( + async fn storage_hash( &self, block: Option, storage_key: PrefixedStorageKey, key: StorageKey, - ) -> FutureResult>; + ) -> Result, Error>; /// Returns the size of a child storage entry at a block's state. - fn storage_size( + async fn storage_size( &self, block: Option, storage_key: PrefixedStorageKey, key: StorageKey, - ) -> FutureResult> { - self.storage(block, storage_key, key) - .map(|x| x.map(|r| r.map(|v| v.0.len() as u64))) - .boxed() + ) -> Result, Error> { + self.storage(block, storage_key, key).await.map(|x| x.map(|x| x.0.len() as u64)) } } @@ -466,76 +421,84 @@ pub struct ChildState { backend: Box>, } -impl ChildStateApi for ChildState +#[async_trait] +impl ChildStateApiServer for ChildState where Block: BlockT + 'static, Client: Send + Sync + 'static, { - type Metadata = crate::Metadata; - - fn read_child_proof( + async fn storage_keys( &self, - child_storage_key: PrefixedStorageKey, - keys: Vec, + storage_key: PrefixedStorageKey, + key_prefix: StorageKey, block: Option, - ) -> FutureResult> { - self.backend.read_child_proof(block, child_storage_key, keys) + ) -> RpcResult> { + self.backend + .storage_keys(block, storage_key, key_prefix) + .await + .map_err(Into::into) } - fn storage( + async fn storage_keys_paged( &self, storage_key: PrefixedStorageKey, - key: StorageKey, + prefix: Option, + count: u32, + start_key: Option, block: Option, - ) -> FutureResult> { - self.backend.storage(block, storage_key, key) + ) -> RpcResult> { + self.backend + .storage_keys_paged(block, storage_key, prefix, count, start_key) + .await + .map_err(Into::into) } - fn storage_entries( + async fn storage( &self, storage_key: PrefixedStorageKey, - keys: Vec, + key: StorageKey, block: Option, - ) -> FutureResult>> { - self.backend.storage_entries(block, storage_key, keys) + ) -> RpcResult> { + self.backend.storage(block, storage_key, key).await.map_err(Into::into) } - fn storage_keys( + async fn storage_entries( &self, storage_key: PrefixedStorageKey, - key_prefix: StorageKey, + keys: Vec, block: Option, - ) -> FutureResult> { - self.backend.storage_keys(block, storage_key, key_prefix) + ) -> RpcResult>> { + self.backend.storage_entries(block, storage_key, keys).await.map_err(Into::into) } - fn storage_keys_paged( + async fn storage_hash( &self, storage_key: PrefixedStorageKey, - prefix: Option, - count: u32, - start_key: Option, + key: StorageKey, block: Option, - ) -> FutureResult> { - self.backend.storage_keys_paged(block, storage_key, prefix, count, start_key) + ) -> RpcResult> { + self.backend.storage_hash(block, storage_key, key).await.map_err(Into::into) } - fn storage_hash( + async fn storage_size( &self, storage_key: PrefixedStorageKey, key: StorageKey, block: Option, - ) -> FutureResult> { - self.backend.storage_hash(block, storage_key, key) + ) -> RpcResult> { + self.backend.storage_size(block, storage_key, key).await.map_err(Into::into) } - fn storage_size( + async fn read_child_proof( &self, - storage_key: PrefixedStorageKey, - key: StorageKey, + child_storage_key: PrefixedStorageKey, + keys: Vec, block: Option, - ) -> FutureResult> { - self.backend.storage_size(block, storage_key, key) + ) -> RpcResult> { + self.backend + .read_child_proof(block, child_storage_key, keys) + .await + .map_err(Into::into) } } diff --git a/client/rpc/src/state/state_full.rs b/client/rpc/src/state/state_full.rs index 38f9b078d87a7..48165e912b03a 100644 --- a/client/rpc/src/state/state_full.rs +++ b/client/rpc/src/state/state_full.rs @@ -18,17 +18,26 @@ //! State API backend for full nodes. -use futures::{ - future, - future::{err, try_join_all}, - stream, FutureExt, SinkExt, StreamExt, +use std::{collections::HashMap, marker::PhantomData, sync::Arc}; + +use super::{ + client_err, + error::{Error, Result}, + ChildStateBackend, StateBackend, }; -use jsonrpc_pubsub::{manager::SubscriptionManager, typed::Subscriber, SubscriptionId}; -use log::warn; -use rpc::Result as RpcResult; -use std::{collections::HashMap, sync::Arc}; +use crate::SubscriptionTaskExecutor; +use futures::{future, stream, FutureExt, StreamExt}; +use jsonrpsee::{ + core::{async_trait, Error as JsonRpseeError}, + PendingSubscription, +}; +use sc_client_api::{ + Backend, BlockBackend, BlockchainEvents, CallExecutor, ExecutorProvider, ProofProvider, + StorageProvider, +}; use sc_rpc_api::state::ReadProof; +use sp_api::{CallApiAt, Metadata, ProvideRuntimeApi}; use sp_blockchain::{ CachedHeaderMetadata, Error as ClientError, HeaderBackend, HeaderMetadata, Result as ClientResult, @@ -42,19 +51,6 @@ use sp_core::{ use sp_runtime::{generic::BlockId, traits::Block as BlockT}; use sp_version::RuntimeVersion; -use sp_api::{CallApiAt, Metadata, ProvideRuntimeApi}; - -use super::{ - client_err, - error::{Error, FutureResult, Result}, - ChildStateBackend, StateBackend, -}; -use sc_client_api::{ - Backend, BlockBackend, BlockchainEvents, CallExecutor, ExecutorProvider, ProofProvider, - StorageNotification, StorageProvider, -}; -use std::marker::PhantomData; - /// Ranges to query in state_queryStorage. struct QueryStorageRange { /// Hashes of all the blocks in the range. @@ -64,7 +60,7 @@ struct QueryStorageRange { /// State API backend for full nodes. pub struct FullState { client: Arc, - subscriptions: SubscriptionManager, + executor: SubscriptionTaskExecutor, _phantom: PhantomData<(BE, Block)>, rpc_max_payload: Option, } @@ -81,10 +77,10 @@ where /// Create new state API backend for full nodes. pub fn new( client: Arc, - subscriptions: SubscriptionManager, + executor: SubscriptionTaskExecutor, rpc_max_payload: Option, ) -> Self { - Self { client, subscriptions, _phantom: PhantomData, rpc_max_payload } + Self { client, executor, _phantom: PhantomData, rpc_max_payload } } /// Returns given block hash or best block hash if None is passed. @@ -174,6 +170,7 @@ where } } +#[async_trait] impl StateBackend for FullState where Block: BlockT + 'static, @@ -193,14 +190,13 @@ where + 'static, Client::Api: Metadata, { - fn call( + async fn call( &self, block: Option, method: String, call_data: Bytes, - ) -> FutureResult { - let r = self - .block_or_best(block) + ) -> std::result::Result { + self.block_or_best(block) .and_then(|block| { self.client .executor() @@ -213,43 +209,37 @@ where ) .map(Into::into) }) - .map_err(client_err); - async move { r }.boxed() + .map_err(client_err) } - fn storage_keys( + async fn storage_keys( &self, block: Option, prefix: StorageKey, - ) -> FutureResult> { - let r = self - .block_or_best(block) + ) -> std::result::Result, Error> { + self.block_or_best(block) .and_then(|block| self.client.storage_keys(&BlockId::Hash(block), &prefix)) - .map_err(client_err); - async move { r }.boxed() + .map_err(client_err) } - fn storage_pairs( + async fn storage_pairs( &self, block: Option, prefix: StorageKey, - ) -> FutureResult> { - let r = self - .block_or_best(block) + ) -> std::result::Result, Error> { + self.block_or_best(block) .and_then(|block| self.client.storage_pairs(&BlockId::Hash(block), &prefix)) - .map_err(client_err); - async move { r }.boxed() + .map_err(client_err) } - fn storage_keys_paged( + async fn storage_keys_paged( &self, block: Option, prefix: Option, count: u32, start_key: Option, - ) -> FutureResult> { - let r = self - .block_or_best(block) + ) -> std::result::Result, Error> { + self.block_or_best(block) .and_then(|block| { self.client.storage_keys_iter( &BlockId::Hash(block), @@ -258,40 +248,36 @@ where ) }) .map(|iter| iter.take(count as usize).collect()) - .map_err(client_err); - async move { r }.boxed() + .map_err(client_err) } - fn storage( + async fn storage( &self, block: Option, key: StorageKey, - ) -> FutureResult> { - let r = self - .block_or_best(block) + ) -> std::result::Result, Error> { + self.block_or_best(block) .and_then(|block| self.client.storage(&BlockId::Hash(block), &key)) - .map_err(client_err); - async move { r }.boxed() + .map_err(client_err) } - fn storage_size( + async fn storage_size( &self, block: Option, key: StorageKey, - ) -> FutureResult> { + ) -> std::result::Result, Error> { let block = match self.block_or_best(block) { Ok(b) => b, - Err(e) => return async move { Err(client_err(e)) }.boxed(), + Err(e) => return Err(client_err(e)), }; match self.client.storage(&BlockId::Hash(block), &key) { - Ok(Some(d)) => return async move { Ok(Some(d.0.len() as u64)) }.boxed(), - Err(e) => return async move { Err(client_err(e)) }.boxed(), + Ok(Some(d)) => return Ok(Some(d.0.len() as u64)), + Err(e) => return Err(client_err(e)), Ok(None) => {}, } - let r = self - .client + self.client .storage_pairs(&BlockId::Hash(block), &key) .map(|kv| { let item_sum = kv.iter().map(|(_, v)| v.0.len() as u64).sum::(); @@ -301,48 +287,46 @@ where None } }) - .map_err(client_err); - async move { r }.boxed() + .map_err(client_err) } - fn storage_hash( + async fn storage_hash( &self, block: Option, key: StorageKey, - ) -> FutureResult> { - let r = self - .block_or_best(block) + ) -> std::result::Result, Error> { + self.block_or_best(block) .and_then(|block| self.client.storage_hash(&BlockId::Hash(block), &key)) - .map_err(client_err); - async move { r }.boxed() + .map_err(client_err) } - fn metadata(&self, block: Option) -> FutureResult { - let r = self.block_or_best(block).map_err(client_err).and_then(|block| { + async fn metadata(&self, block: Option) -> std::result::Result { + self.block_or_best(block).map_err(client_err).and_then(|block| { self.client .runtime_api() .metadata(&BlockId::Hash(block)) .map(Into::into) .map_err(|e| Error::Client(Box::new(e))) - }); - async move { r }.boxed() + }) } - fn runtime_version(&self, block: Option) -> FutureResult { - let r = self.block_or_best(block).map_err(client_err).and_then(|block| { + async fn runtime_version( + &self, + block: Option, + ) -> std::result::Result { + self.block_or_best(block).map_err(client_err).and_then(|block| { self.client .runtime_version_at(&BlockId::Hash(block)) .map_err(|e| Error::Client(Box::new(e))) - }); - async move { r }.boxed() + }) } - fn query_storage( + async fn query_storage( &self, from: Block::Hash, to: Option, keys: Vec, - ) -> FutureResult>> { + ) -> std::result::Result>, Error> { let call_fn = move || { let range = self.query_storage_range(from, to)?; let mut changes = Vec::new(); @@ -350,168 +334,151 @@ where self.query_storage_unfiltered(&range, &keys, &mut last_values, &mut changes)?; Ok(changes) }; - - let r = call_fn(); - async move { r }.boxed() + call_fn() } - fn query_storage_at( + async fn query_storage_at( &self, keys: Vec, at: Option, - ) -> FutureResult>> { + ) -> std::result::Result>, Error> { let at = at.unwrap_or_else(|| self.client.info().best_hash); - self.query_storage(at, Some(at), keys) + self.query_storage(at, Some(at), keys).await } - fn read_proof( + async fn read_proof( &self, block: Option, keys: Vec, - ) -> FutureResult> { - let r = self - .block_or_best(block) + ) -> std::result::Result, Error> { + self.block_or_best(block) .and_then(|block| { self.client .read_proof(&BlockId::Hash(block), &mut keys.iter().map(|key| key.0.as_ref())) .map(|proof| proof.iter_nodes().map(|node| node.into()).collect()) .map(|proof| ReadProof { at: block, proof }) }) - .map_err(client_err); - async move { r }.boxed() + .map_err(client_err) } - fn subscribe_runtime_version( - &self, - _meta: crate::Metadata, - subscriber: Subscriber, - ) { - self.subscriptions.add(subscriber, |sink| { - let version = self - .block_or_best(None) - .and_then(|block| { - self.client.runtime_version_at(&BlockId::Hash(block)).map_err(Into::into) - }) - .map_err(client_err) - .map_err(Into::into); + fn subscribe_runtime_version(&self, pending: PendingSubscription) { + let client = self.client.clone(); - let client = self.client.clone(); - let mut previous_version = version.clone(); + let initial = match self + .block_or_best(None) + .and_then(|block| { + self.client.runtime_version_at(&BlockId::Hash(block)).map_err(Into::into) + }) + .map_err(|e| Error::Client(Box::new(e))) + { + Ok(initial) => initial, + Err(e) => { + pending.reject(JsonRpseeError::from(e)); + return + }, + }; - // A stream of all best blocks. - let stream = - client.import_notification_stream().filter(|n| future::ready(n.is_new_best)); + let mut previous_version = initial.clone(); - let stream = stream.filter_map(move |n| { + // A stream of new versions + let version_stream = client + .import_notification_stream() + .filter(|n| future::ready(n.is_new_best)) + .filter_map(move |n| { let version = client .runtime_version_at(&BlockId::hash(n.hash)) - .map_err(|e| Error::Client(Box::new(e))) - .map_err(Into::into); - - if previous_version != version { - previous_version = version.clone(); - future::ready(Some(Ok::<_, ()>(version))) - } else { - future::ready(None) + .map_err(|e| Error::Client(Box::new(e))); + + match version { + Ok(version) if version != previous_version => { + previous_version = version.clone(); + future::ready(Some(version)) + }, + _ => future::ready(None), } }); - stream::iter(vec![Ok(version)]) - .chain(stream) - .forward(sink.sink_map_err(|e| warn!("Error sending notifications: {:?}", e))) - // we ignore the resulting Stream (if the first stream is over we are unsubscribed) - .map(|_| ()) - }); - } + let stream = futures::stream::once(future::ready(initial)).chain(version_stream); - fn unsubscribe_runtime_version( - &self, - _meta: Option, - id: SubscriptionId, - ) -> RpcResult { - Ok(self.subscriptions.cancel(id)) + let fut = async move { + if let Some(mut sink) = pending.accept() { + sink.pipe_from_stream(stream).await; + } + } + .boxed(); + + self.executor + .spawn("substrate-rpc-subscription", Some("rpc"), fut.map(drop).boxed()); } - fn subscribe_storage( - &self, - _meta: crate::Metadata, - subscriber: Subscriber>, - keys: Option>, - ) { - let keys = Into::>>::into(keys); + fn subscribe_storage(&self, pending: PendingSubscription, keys: Option>) { let stream = match self.client.storage_changes_notification_stream(keys.as_deref(), None) { Ok(stream) => stream, - Err(err) => { - let _ = subscriber.reject(client_err(err).into()); + Err(blockchain_err) => { + pending.reject(JsonRpseeError::from(Error::Client(Box::new(blockchain_err)))); return }, }; // initial values - let initial = stream::iter( - keys.map(|keys| { - let block = self.client.info().best_hash; - let changes = keys - .into_iter() - .map(|key| { - let v = self.client.storage(&BlockId::Hash(block), &key).ok().flatten(); - (key, v) - }) - .collect(); - vec![Ok(Ok(StorageChangeSet { block, changes }))] - }) - .unwrap_or_default(), - ); - - self.subscriptions.add(subscriber, |sink| { - let stream = stream.map(|StorageNotification { block, changes }| { - Ok(Ok::<_, rpc::Error>(StorageChangeSet { - block, - changes: changes - .iter() - .filter_map(|(o_sk, k, v)| o_sk.is_none().then(|| (k.clone(), v.cloned()))) - .collect(), - })) - }); - - initial - .chain(stream) - .forward(sink.sink_map_err(|e| warn!("Error sending notifications: {:?}", e))) - // we ignore the resulting Stream (if the first stream is over we are unsubscribed) - .map(|_| ()) + let initial = stream::iter(keys.map(|keys| { + let block = self.client.info().best_hash; + let changes = keys + .into_iter() + .map(|key| { + let v = self.client.storage(&BlockId::Hash(block), &key).ok().flatten(); + (key, v) + }) + .collect(); + StorageChangeSet { block, changes } + })); + + // let storage_stream = stream.map(|(block, changes)| StorageChangeSet { + let storage_stream = stream.map(|storage_notif| StorageChangeSet { + block: storage_notif.block, + changes: storage_notif + .changes + .iter() + .filter_map(|(o_sk, k, v)| o_sk.is_none().then(|| (k.clone(), v.cloned()))) + .collect(), }); - } - fn unsubscribe_storage( - &self, - _meta: Option, - id: SubscriptionId, - ) -> RpcResult { - Ok(self.subscriptions.cancel(id)) + let stream = initial + .chain(storage_stream) + .filter(|storage| future::ready(!storage.changes.is_empty())); + + let fut = async move { + if let Some(mut sink) = pending.accept() { + sink.pipe_from_stream(stream).await; + } + } + .boxed(); + + self.executor + .spawn("substrate-rpc-subscription", Some("rpc"), fut.map(drop).boxed()); } - fn trace_block( + async fn trace_block( &self, block: Block::Hash, targets: Option, storage_keys: Option, methods: Option, - ) -> FutureResult { - let block_executor = sc_tracing::block::BlockExecutor::new( + ) -> std::result::Result { + sc_tracing::block::BlockExecutor::new( self.client.clone(), block, targets, storage_keys, methods, self.rpc_max_payload, - ); - let r = block_executor - .trace_block() - .map_err(|e| invalid_block::(block, None, e.to_string())); - async move { r }.boxed() + ) + .trace_block() + .map_err(|e| invalid_block::(block, None, e.to_string())) } } +#[async_trait] impl ChildStateBackend for FullState where Block: BlockT + 'static, @@ -530,14 +497,13 @@ where + 'static, Client::Api: Metadata, { - fn read_child_proof( + async fn read_child_proof( &self, block: Option, storage_key: PrefixedStorageKey, keys: Vec, - ) -> FutureResult> { - let r = self - .block_or_best(block) + ) -> std::result::Result, Error> { + self.block_or_best(block) .and_then(|block| { let child_info = match ChildType::from_prefixed_key(&storage_key) { Some((ChildType::ParentKeyId, storage_key)) => @@ -553,19 +519,16 @@ where .map(|proof| proof.iter_nodes().map(|node| node.into()).collect()) .map(|proof| ReadProof { at: block, proof }) }) - .map_err(client_err); - - async move { r }.boxed() + .map_err(client_err) } - fn storage_keys( + async fn storage_keys( &self, block: Option, storage_key: PrefixedStorageKey, prefix: StorageKey, - ) -> FutureResult> { - let r = self - .block_or_best(block) + ) -> std::result::Result, Error> { + self.block_or_best(block) .and_then(|block| { let child_info = match ChildType::from_prefixed_key(&storage_key) { Some((ChildType::ParentKeyId, storage_key)) => @@ -574,21 +537,18 @@ where }; self.client.child_storage_keys(&BlockId::Hash(block), &child_info, &prefix) }) - .map_err(client_err); - - async move { r }.boxed() + .map_err(client_err) } - fn storage_keys_paged( + async fn storage_keys_paged( &self, block: Option, storage_key: PrefixedStorageKey, prefix: Option, count: u32, start_key: Option, - ) -> FutureResult> { - let r = self - .block_or_best(block) + ) -> std::result::Result, Error> { + self.block_or_best(block) .and_then(|block| { let child_info = match ChildType::from_prefixed_key(&storage_key) { Some((ChildType::ParentKeyId, storage_key)) => @@ -603,19 +563,16 @@ where ) }) .map(|iter| iter.take(count as usize).collect()) - .map_err(client_err); - - async move { r }.boxed() + .map_err(client_err) } - fn storage( + async fn storage( &self, block: Option, storage_key: PrefixedStorageKey, key: StorageKey, - ) -> FutureResult> { - let r = self - .block_or_best(block) + ) -> std::result::Result, Error> { + self.block_or_best(block) .and_then(|block| { let child_info = match ChildType::from_prefixed_key(&storage_key) { Some((ChildType::ParentKeyId, storage_key)) => @@ -624,28 +581,25 @@ where }; self.client.child_storage(&BlockId::Hash(block), &child_info, &key) }) - .map_err(client_err); - - async move { r }.boxed() + .map_err(client_err) } - fn storage_entries( + async fn storage_entries( &self, block: Option, storage_key: PrefixedStorageKey, keys: Vec, - ) -> FutureResult>> { - let child_info = match ChildType::from_prefixed_key(&storage_key) { - Some((ChildType::ParentKeyId, storage_key)) => - Arc::new(ChildInfo::new_default(storage_key)), - None => return err(client_err(sp_blockchain::Error::InvalidChildStorageKey)).boxed(), - }; - let block = match self.block_or_best(block) { - Ok(b) => b, - Err(e) => return err(client_err(e)).boxed(), + ) -> std::result::Result>, Error> { + let child_info = if let Some((ChildType::ParentKeyId, storage_key)) = + ChildType::from_prefixed_key(&storage_key) + { + Arc::new(ChildInfo::new_default(storage_key)) + } else { + return Err(client_err(sp_blockchain::Error::InvalidChildStorageKey)) }; + let block = self.block_or_best(block).map_err(client_err)?; let client = self.client.clone(); - try_join_all(keys.into_iter().map(move |key| { + future::try_join_all(keys.into_iter().map(move |key| { let res = client .clone() .child_storage(&BlockId::Hash(block), &child_info, &key) @@ -653,17 +607,16 @@ where async move { res } })) - .boxed() + .await } - fn storage_hash( + async fn storage_hash( &self, block: Option, storage_key: PrefixedStorageKey, key: StorageKey, - ) -> FutureResult> { - let r = self - .block_or_best(block) + ) -> std::result::Result, Error> { + self.block_or_best(block) .and_then(|block| { let child_info = match ChildType::from_prefixed_key(&storage_key) { Some((ChildType::ParentKeyId, storage_key)) => @@ -672,9 +625,7 @@ where }; self.client.child_storage_hash(&BlockId::Hash(block), &child_info, &key) }) - .map_err(client_err); - - async move { r }.boxed() + .map_err(client_err) } } diff --git a/client/rpc/src/state/tests.rs b/client/rpc/src/state/tests.rs index 19e474ee6459a..a375a30d2c1a2 100644 --- a/client/rpc/src/state/tests.rs +++ b/client/rpc/src/state/tests.rs @@ -18,9 +18,13 @@ use self::error::Error; use super::*; -use crate::testing::TaskExecutor; +use crate::testing::{test_executor, timeout_secs}; use assert_matches::assert_matches; -use futures::{executor, StreamExt}; +use futures::executor; +use jsonrpsee::{ + core::Error as RpcError, + types::{error::CallError as RpcCallError, EmptyParams, ErrorObject}, +}; use sc_block_builder::BlockBuilderProvider; use sc_rpc_api::DenyUnsafe; use sp_consensus::BlockOrigin; @@ -36,8 +40,8 @@ fn prefixed_storage_key() -> PrefixedStorageKey { child_info.prefixed_storage_key() } -#[test] -fn should_return_storage() { +#[tokio::test] +async fn should_return_storage() { const KEY: &[u8] = b":mock"; const VALUE: &[u8] = b"hello world"; const CHILD_VALUE: &[u8] = b"hello world !"; @@ -51,49 +55,46 @@ fn should_return_storage() { .add_extra_storage(b":map:acc2".to_vec(), vec![1, 2, 3]) .build(); let genesis_hash = client.genesis_hash(); - let (client, child) = new_full( - Arc::new(client), - SubscriptionManager::new(Arc::new(TaskExecutor)), - DenyUnsafe::No, - None, - ); + let (client, child) = new_full(Arc::new(client), test_executor(), DenyUnsafe::No, None); let key = StorageKey(KEY.to_vec()); assert_eq!( - executor::block_on(client.storage(key.clone(), Some(genesis_hash).into())) + client + .storage(key.clone(), Some(genesis_hash).into()) + .await .map(|x| x.map(|x| x.0.len())) .unwrap() .unwrap() as usize, VALUE.len(), ); assert_matches!( - executor::block_on(client.storage_hash(key.clone(), Some(genesis_hash).into())) + client + .storage_hash(key.clone(), Some(genesis_hash).into()) + .await .map(|x| x.is_some()), Ok(true) ); assert_eq!( - executor::block_on(client.storage_size(key.clone(), None)).unwrap().unwrap() as usize, + client.storage_size(key.clone(), None).await.unwrap().unwrap() as usize, VALUE.len(), ); assert_eq!( - executor::block_on(client.storage_size(StorageKey(b":map".to_vec()), None)) - .unwrap() - .unwrap() as usize, + client.storage_size(StorageKey(b":map".to_vec()), None).await.unwrap().unwrap() as usize, 2 + 3, ); assert_eq!( - executor::block_on( - child - .storage(prefixed_storage_key(), key, Some(genesis_hash).into()) - .map(|x| x.map(|x| x.unwrap().0.len())) - ) - .unwrap() as usize, + child + .storage(prefixed_storage_key(), key, Some(genesis_hash).into()) + .await + .map(|x| x.map(|x| x.0.len())) + .unwrap() + .unwrap() as usize, CHILD_VALUE.len(), ); } -#[test] -fn should_return_storage_entries() { +#[tokio::test] +async fn should_return_storage_entries() { const KEY1: &[u8] = b":mock"; const KEY2: &[u8] = b":turtle"; const VALUE: &[u8] = b"hello world"; @@ -107,22 +108,15 @@ fn should_return_storage_entries() { .add_extra_child_storage(&child_info, KEY2.to_vec(), CHILD_VALUE2.to_vec()) .build(); let genesis_hash = client.genesis_hash(); - let (_client, child) = new_full( - Arc::new(client), - SubscriptionManager::new(Arc::new(TaskExecutor)), - DenyUnsafe::No, - None, - ); + let (_client, child) = new_full(Arc::new(client), test_executor(), DenyUnsafe::No, None); let keys = &[StorageKey(KEY1.to_vec()), StorageKey(KEY2.to_vec())]; assert_eq!( - executor::block_on(child.storage_entries( - prefixed_storage_key(), - keys.to_vec(), - Some(genesis_hash).into() - )) - .map(|x| x.into_iter().map(|x| x.map(|x| x.0.len()).unwrap()).sum::()) - .unwrap(), + child + .storage_entries(prefixed_storage_key(), keys.to_vec(), Some(genesis_hash).into()) + .await + .map(|x| x.into_iter().map(|x| x.map(|x| x.0.len()).unwrap()).sum::()) + .unwrap(), CHILD_VALUE1.len() + CHILD_VALUE2.len() ); @@ -130,18 +124,16 @@ fn should_return_storage_entries() { let mut failing_keys = vec![StorageKey(b":soup".to_vec())]; failing_keys.extend_from_slice(keys); assert_matches!( - executor::block_on(child.storage_entries( - prefixed_storage_key(), - failing_keys, - Some(genesis_hash).into() - )) - .map(|x| x.iter().all(|x| x.is_some())), + child + .storage_entries(prefixed_storage_key(), failing_keys, Some(genesis_hash).into()) + .await + .map(|x| x.iter().all(|x| x.is_some())), Ok(false) ); } -#[test] -fn should_return_child_storage() { +#[tokio::test] +async fn should_return_child_storage() { let child_info = ChildInfo::new_default(STORAGE_KEY); let client = Arc::new( substrate_test_runtime_client::TestClientBuilder::new() @@ -149,49 +141,30 @@ fn should_return_child_storage() { .build(), ); let genesis_hash = client.genesis_hash(); - let (_client, child) = - new_full(client, SubscriptionManager::new(Arc::new(TaskExecutor)), DenyUnsafe::No, None); + let (_client, child) = new_full(client, test_executor(), DenyUnsafe::No, None); let child_key = prefixed_storage_key(); let key = StorageKey(b"key".to_vec()); assert_matches!( - executor::block_on(child.storage( + child.storage( child_key.clone(), key.clone(), Some(genesis_hash).into(), - )), + ).await, Ok(Some(StorageData(ref d))) if d[0] == 42 && d.len() == 1 ); - - // should fail if key does not exist. - let failing_key = StorageKey(b":soup".to_vec()); assert_matches!( - executor::block_on(child.storage( - prefixed_storage_key(), - failing_key, - Some(genesis_hash).into() - )) - .map(|x| x.is_some()), - Ok(false) - ); - - assert_matches!( - executor::block_on(child.storage_hash( - child_key.clone(), - key.clone(), - Some(genesis_hash).into(), - )) - .map(|x| x.is_some()), + child + .storage_hash(child_key.clone(), key.clone(), Some(genesis_hash).into(),) + .await + .map(|x| x.is_some()), Ok(true) ); - assert_matches!( - executor::block_on(child.storage_size(child_key.clone(), key.clone(), None)), - Ok(Some(1)) - ); + assert_matches!(child.storage_size(child_key.clone(), key.clone(), None).await, Ok(Some(1))); } -#[test] -fn should_return_child_storage_entries() { +#[tokio::test] +async fn should_return_child_storage_entries() { let child_info = ChildInfo::new_default(STORAGE_KEY); let client = Arc::new( substrate_test_runtime_client::TestClientBuilder::new() @@ -200,17 +173,14 @@ fn should_return_child_storage_entries() { .build(), ); let genesis_hash = client.genesis_hash(); - let (_client, child) = - new_full(client, SubscriptionManager::new(Arc::new(TaskExecutor)), DenyUnsafe::No, None); + let (_client, child) = new_full(client, test_executor(), DenyUnsafe::No, None); let child_key = prefixed_storage_key(); let keys = vec![StorageKey(b"key1".to_vec()), StorageKey(b"key2".to_vec())]; - let res = executor::block_on(child.storage_entries( - child_key.clone(), - keys.clone(), - Some(genesis_hash).into(), - )) - .unwrap(); + let res = child + .storage_entries(child_key.clone(), keys.clone(), Some(genesis_hash).into()) + .await + .unwrap(); assert_matches!( res[0], @@ -232,46 +202,37 @@ fn should_return_child_storage_entries() { Ok(true) ); assert_matches!( - executor::block_on(child.storage_size(child_key.clone(), keys[0].clone(), None)), + child.storage_size(child_key.clone(), keys[0].clone(), None).await, Ok(Some(1)) ); } -#[test] -fn should_call_contract() { +#[tokio::test] +async fn should_call_contract() { let client = Arc::new(substrate_test_runtime_client::new()); let genesis_hash = client.genesis_hash(); - let (client, _child) = - new_full(client, SubscriptionManager::new(Arc::new(TaskExecutor)), DenyUnsafe::No, None); + let (client, _child) = new_full(client, test_executor(), DenyUnsafe::No, None); + + use jsonrpsee::{core::Error, types::error::CallError}; assert_matches!( - executor::block_on(client.call( - "balanceOf".into(), - Bytes(vec![1, 2, 3]), - Some(genesis_hash).into() - )), - Err(Error::Client(_)) + client + .call("balanceOf".into(), Bytes(vec![1, 2, 3]), Some(genesis_hash).into()) + .await, + Err(Error::Call(CallError::Failed(_))) ) } -#[test] -fn should_notify_about_storage_changes() { - let (subscriber, id, mut transport) = Subscriber::new_test("test"); - - { +#[tokio::test] +async fn should_notify_about_storage_changes() { + let mut sub = { let mut client = Arc::new(substrate_test_runtime_client::new()); - let (api, _child) = new_full( - client.clone(), - SubscriptionManager::new(Arc::new(TaskExecutor)), - DenyUnsafe::No, - None, - ); - - api.subscribe_storage(Default::default(), subscriber, None.into()); + let (api, _child) = new_full(client.clone(), test_executor(), DenyUnsafe::No, None); - // assert id assigned - assert!(matches!(executor::block_on(id), Ok(Ok(SubscriptionId::String(_))))); + let api_rpc = api.into_rpc(); + let sub = api_rpc.subscribe("state_subscribeStorage", EmptyParams::new()).await.unwrap(); + // Cause a change: let mut builder = client.new_block(Default::default()).unwrap(); builder .push_transfer(runtime::Transfer { @@ -282,38 +243,32 @@ fn should_notify_about_storage_changes() { }) .unwrap(); let block = builder.build().unwrap().block; - executor::block_on(client.import(BlockOrigin::Own, block)).unwrap(); - } + client.import(BlockOrigin::Own, block).await.unwrap(); - // Check notification sent to transport - executor::block_on((&mut transport).take(2).collect::>()); - assert!(executor::block_on(transport.next()).is_none()); -} + sub + }; -#[test] -fn should_send_initial_storage_changes_and_notifications() { - let (subscriber, id, mut transport) = Subscriber::new_test("test"); + // We should get a message back on our subscription about the storage change: + // NOTE: previous versions of the subscription code used to return an empty value for the + // "initial" storage change here + assert_matches!(timeout_secs(1, sub.next::>()).await, Ok(Some(_))); + assert_matches!(timeout_secs(1, sub.next::>()).await, Ok(None)); +} - { +#[tokio::test] +async fn should_send_initial_storage_changes_and_notifications() { + let mut sub = { let mut client = Arc::new(substrate_test_runtime_client::new()); - let (api, _child) = new_full( - client.clone(), - SubscriptionManager::new(Arc::new(TaskExecutor)), - DenyUnsafe::No, - None, - ); + let (api, _child) = new_full(client.clone(), test_executor(), DenyUnsafe::No, None); let alice_balance_key = blake2_256(&runtime::system::balance_of_key(AccountKeyring::Alice.into())); - api.subscribe_storage( - Default::default(), - subscriber, - Some(vec![StorageKey(alice_balance_key.to_vec())]).into(), - ); - - // assert id assigned - assert!(matches!(executor::block_on(id), Ok(Ok(SubscriptionId::String(_))))); + let api_rpc = api.into_rpc(); + let sub = api_rpc + .subscribe("state_subscribeStorage", [[StorageKey(alice_balance_key.to_vec())]]) + .await + .unwrap(); let mut builder = client.new_block(Default::default()).unwrap(); builder @@ -325,23 +280,22 @@ fn should_send_initial_storage_changes_and_notifications() { }) .unwrap(); let block = builder.build().unwrap().block; - executor::block_on(client.import(BlockOrigin::Own, block)).unwrap(); - } + client.import(BlockOrigin::Own, block).await.unwrap(); + + sub + }; + + assert_matches!(timeout_secs(1, sub.next::>()).await, Ok(Some(_))); + assert_matches!(timeout_secs(1, sub.next::>()).await, Ok(Some(_))); - // Check for the correct number of notifications - executor::block_on((&mut transport).take(2).collect::>()); - assert!(executor::block_on(transport.next()).is_none()); + // No more messages to follow + assert_matches!(timeout_secs(1, sub.next::>()).await, Ok(None)); } -#[test] -fn should_query_storage() { - fn run_tests(mut client: Arc) { - let (api, _child) = new_full( - client.clone(), - SubscriptionManager::new(Arc::new(TaskExecutor)), - DenyUnsafe::No, - None, - ); +#[tokio::test] +async fn should_query_storage() { + async fn run_tests(mut client: Arc) { + let (api, _child) = new_full(client.clone(), test_executor(), DenyUnsafe::No, None); let mut add_block = |nonce| { let mut builder = client.new_block(Default::default()).unwrap(); @@ -393,7 +347,7 @@ fn should_query_storage() { let keys = (1..6).map(|k| StorageKey(vec![k])).collect::>(); let result = api.query_storage(keys.clone(), genesis_hash, Some(block1_hash).into()); - assert_eq!(executor::block_on(result).unwrap(), expected); + assert_eq!(result.await.unwrap(), expected); // Query all changes let result = api.query_storage(keys.clone(), genesis_hash, None.into()); @@ -406,23 +360,28 @@ fn should_query_storage() { (StorageKey(vec![5]), Some(StorageData(vec![1]))), ], }); - assert_eq!(executor::block_on(result).unwrap(), expected); + assert_eq!(result.await.unwrap(), expected); // Query changes up to block2. let result = api.query_storage(keys.clone(), genesis_hash, Some(block2_hash)); - assert_eq!(executor::block_on(result).unwrap(), expected); + assert_eq!(result.await.unwrap(), expected); // Inverted range. let result = api.query_storage(keys.clone(), block1_hash, Some(genesis_hash)); assert_eq!( - executor::block_on(result).map_err(|e| e.to_string()), - Err(Error::InvalidBlockRange { - from: format!("1 ({:?})", block1_hash), - to: format!("0 ({:?})", genesis_hash), - details: "from number > to number".to_owned(), - }) + result.await.map_err(|e| e.to_string()), + Err(RpcError::Call(RpcCallError::Custom(ErrorObject::owned( + 4001, + Error::InvalidBlockRange { + from: format!("1 ({:?})", block1_hash), + to: format!("0 ({:?})", genesis_hash), + details: "from number > to number".to_owned(), + } + .to_string(), + None::<()>, + )))) .map_err(|e| e.to_string()) ); @@ -433,15 +392,20 @@ fn should_query_storage() { let result = api.query_storage(keys.clone(), genesis_hash, Some(random_hash1)); assert_eq!( - executor::block_on(result).map_err(|e| e.to_string()), - Err(Error::InvalidBlockRange { - from: format!("{:?}", genesis_hash), - to: format!("{:?}", Some(random_hash1)), - details: format!( - "UnknownBlock: Header was not found in the database: {:?}", - random_hash1 - ), - }) + result.await.map_err(|e| e.to_string()), + Err(RpcError::Call(RpcCallError::Custom(ErrorObject::owned( + 4001, + Error::InvalidBlockRange { + from: format!("{:?}", genesis_hash), + to: format!("{:?}", Some(random_hash1)), + details: format!( + "UnknownBlock: Header was not found in the database: {:?}", + random_hash1 + ), + } + .to_string(), + None::<()>, + )))) .map_err(|e| e.to_string()) ); @@ -449,15 +413,20 @@ fn should_query_storage() { let result = api.query_storage(keys.clone(), random_hash1, Some(genesis_hash)); assert_eq!( - executor::block_on(result).map_err(|e| e.to_string()), - Err(Error::InvalidBlockRange { - from: format!("{:?}", random_hash1), - to: format!("{:?}", Some(genesis_hash)), - details: format!( - "UnknownBlock: Header was not found in the database: {:?}", - random_hash1 - ), - }) + result.await.map_err(|e| e.to_string()), + Err(RpcError::Call(RpcCallError::Custom(ErrorObject::owned( + 4001, + Error::InvalidBlockRange { + from: format!("{:?}", random_hash1), + to: format!("{:?}", Some(genesis_hash)), + details: format!( + "UnknownBlock: Header was not found in the database: {:?}", + random_hash1 + ), + } + .to_string(), + None::<()>, + )))) .map_err(|e| e.to_string()), ); @@ -465,15 +434,20 @@ fn should_query_storage() { let result = api.query_storage(keys.clone(), random_hash1, None); assert_eq!( - executor::block_on(result).map_err(|e| e.to_string()), - Err(Error::InvalidBlockRange { - from: format!("{:?}", random_hash1), - to: format!("{:?}", Some(block2_hash)), // Best block hash. - details: format!( - "UnknownBlock: Header was not found in the database: {:?}", - random_hash1 - ), - }) + result.await.map_err(|e| e.to_string()), + Err(RpcError::Call(RpcCallError::Custom(ErrorObject::owned( + 4001, + Error::InvalidBlockRange { + from: format!("{:?}", random_hash1), + to: format!("{:?}", Some(block2_hash)), // Best block hash. + details: format!( + "UnknownBlock: Header was not found in the database: {:?}", + random_hash1 + ), + } + .to_string(), + None::<()>, + )))) .map_err(|e| e.to_string()), ); @@ -481,15 +455,20 @@ fn should_query_storage() { let result = api.query_storage(keys.clone(), random_hash1, Some(random_hash2)); assert_eq!( - executor::block_on(result).map_err(|e| e.to_string()), - Err(Error::InvalidBlockRange { - from: format!("{:?}", random_hash1), // First hash not found. - to: format!("{:?}", Some(random_hash2)), - details: format!( - "UnknownBlock: Header was not found in the database: {:?}", - random_hash1 - ), - }) + result.await.map_err(|e| e.to_string()), + Err(RpcError::Call(RpcCallError::Custom(ErrorObject::owned( + 4001, + Error::InvalidBlockRange { + from: format!("{:?}", random_hash1), // First hash not found. + to: format!("{:?}", Some(random_hash2)), + details: format!( + "UnknownBlock: Header was not found in the database: {:?}", + random_hash1 + ), + } + .to_string(), + None::<()> + )))) .map_err(|e| e.to_string()), ); @@ -497,7 +476,7 @@ fn should_query_storage() { let result = api.query_storage_at(keys.clone(), Some(block1_hash)); assert_eq!( - executor::block_on(result).unwrap(), + result.await.unwrap(), vec![StorageChangeSet { block: block1_hash, changes: vec![ @@ -511,19 +490,14 @@ fn should_query_storage() { ); } - run_tests(Arc::new(substrate_test_runtime_client::new())); - run_tests(Arc::new(TestClientBuilder::new().build())); + run_tests(Arc::new(substrate_test_runtime_client::new())).await; + run_tests(Arc::new(TestClientBuilder::new().build())).await; } -#[test] -fn should_return_runtime_version() { +#[tokio::test] +async fn should_return_runtime_version() { let client = Arc::new(substrate_test_runtime_client::new()); - let (api, _child) = new_full( - client.clone(), - SubscriptionManager::new(Arc::new(TaskExecutor)), - DenyUnsafe::No, - None, - ); + let (api, _child) = new_full(client.clone(), test_executor(), DenyUnsafe::No, None); let result = "{\"specName\":\"test\",\"implName\":\"parity-test\",\"authoringVersion\":1,\ \"specVersion\":2,\"implVersion\":2,\"apis\":[[\"0xdf6acb689907609b\",4],\ @@ -532,7 +506,7 @@ fn should_return_runtime_version() { [\"0xf78b278be53f454c\",2],[\"0xab3c0572291feb8b\",1],[\"0xbc9d89904f5b923f\",1]],\ \"transactionVersion\":1,\"stateVersion\":1}"; - let runtime_version = executor::block_on(api.runtime_version(None.into())).unwrap(); + let runtime_version = api.runtime_version(None.into()).await.unwrap(); let serialized = serde_json::to_string(&runtime_version).unwrap(); assert_eq!(serialized, result); @@ -540,28 +514,26 @@ fn should_return_runtime_version() { assert_eq!(deserialized, runtime_version); } -#[test] -fn should_notify_on_runtime_version_initially() { - let (subscriber, id, mut transport) = Subscriber::new_test("test"); - - { +#[tokio::test] +async fn should_notify_on_runtime_version_initially() { + let mut sub = { let client = Arc::new(substrate_test_runtime_client::new()); - let (api, _child) = new_full( - client.clone(), - SubscriptionManager::new(Arc::new(TaskExecutor)), - DenyUnsafe::No, - None, - ); + let (api, _child) = new_full(client, test_executor(), DenyUnsafe::No, None); - api.subscribe_runtime_version(Default::default(), subscriber); + let api_rpc = api.into_rpc(); + let sub = api_rpc + .subscribe("state_subscribeRuntimeVersion", EmptyParams::new()) + .await + .unwrap(); - // assert id assigned - assert!(matches!(executor::block_on(id), Ok(Ok(SubscriptionId::String(_))))); - } + sub + }; // assert initial version sent. - executor::block_on((&mut transport).take(1).collect::>()); - assert!(executor::block_on(transport.next()).is_none()); + assert_matches!(timeout_secs(10, sub.next::()).await, Ok(Some(_))); + + sub.close(); + assert_matches!(timeout_secs(10, sub.next::()).await, Ok(None)); } #[test] @@ -572,38 +544,24 @@ fn should_deserialize_storage_key() { assert_eq!(k.0.len(), 32); } -#[test] -fn wildcard_storage_subscriptions_are_rpc_unsafe() { - let (subscriber, id, _) = Subscriber::new_test("test"); - +#[tokio::test] +async fn wildcard_storage_subscriptions_are_rpc_unsafe() { let client = Arc::new(substrate_test_runtime_client::new()); - let (api, _child) = new_full( - client.clone(), - SubscriptionManager::new(Arc::new(TaskExecutor)), - DenyUnsafe::Yes, - None, - ); + let (api, _child) = new_full(client, test_executor(), DenyUnsafe::Yes, None); - api.subscribe_storage(Default::default(), subscriber, None.into()); - - let error = executor::block_on(id).unwrap().unwrap_err(); - assert_eq!(error.to_string(), "Method not found: RPC call is unsafe to be called externally"); + let api_rpc = api.into_rpc(); + let err = api_rpc.subscribe("state_subscribeStorage", EmptyParams::new()).await; + assert_matches!(err, Err(RpcError::Call(RpcCallError::Custom(e))) if e.message() == "RPC call is unsafe to be called externally"); } -#[test] -fn concrete_storage_subscriptions_are_rpc_safe() { - let (subscriber, id, _) = Subscriber::new_test("test"); - +#[tokio::test] +async fn concrete_storage_subscriptions_are_rpc_safe() { let client = Arc::new(substrate_test_runtime_client::new()); - let (api, _child) = new_full( - client.clone(), - SubscriptionManager::new(Arc::new(TaskExecutor)), - DenyUnsafe::Yes, - None, - ); + let (api, _child) = new_full(client, test_executor(), DenyUnsafe::Yes, None); + let api_rpc = api.into_rpc(); let key = StorageKey(STORAGE_KEY.to_vec()); - api.subscribe_storage(Default::default(), subscriber, Some(vec![key])); + let sub = api_rpc.subscribe("state_subscribeStorage", [[key]]).await; - assert!(matches!(executor::block_on(id), Ok(Ok(SubscriptionId::String(_))))); + assert!(sub.is_ok()); } diff --git a/client/rpc/src/system/mod.rs b/client/rpc/src/system/mod.rs index 534e446e140ad..ea24524cd2ea9 100644 --- a/client/rpc/src/system/mod.rs +++ b/client/rpc/src/system/mod.rs @@ -18,30 +18,23 @@ //! Substrate system API. -use self::error::Result; -use futures::{channel::oneshot, FutureExt}; -use sc_rpc_api::{DenyUnsafe, Receiver}; +#[cfg(test)] +mod tests; + +use futures::channel::oneshot; +use jsonrpsee::{ + core::{async_trait, error::Error as JsonRpseeError, JsonValue, RpcResult}, + types::error::{CallError, ErrorCode, ErrorObject}, +}; +use sc_rpc_api::DenyUnsafe; use sc_tracing::logging; use sc_utils::mpsc::TracingUnboundedSender; use sp_runtime::traits::{self, Header as HeaderT}; -pub use self::{ - gen_client::Client as SystemClient, - helpers::{Health, NodeRole, PeerInfo, SyncState, SystemInfo}, -}; -pub use sc_rpc_api::system::*; - -#[cfg(test)] -mod tests; +use self::error::Result; -/// Early exit for RPCs that require `--rpc-methods=Unsafe` to be enabled -macro_rules! bail_if_unsafe { - ($value: expr) => { - if let Err(err) = $value.check_if_safe() { - return async move { Err(err.into()) }.boxed() - } - }; -} +pub use self::helpers::{Health, NodeRole, PeerInfo, SyncState, SystemInfo}; +pub use sc_rpc_api::system::*; /// System API implementation pub struct System { @@ -62,7 +55,7 @@ pub enum Request { /// Must return information about the peers we are connected to. Peers(oneshot::Sender::Number>>>), /// Must return the state of the network. - NetworkState(oneshot::Sender), + NetworkState(oneshot::Sender), /// Must return any potential parse error. NetworkAddReservedPeer(String, oneshot::Sender>), /// Must return any potential parse error. @@ -89,121 +82,123 @@ impl System { } } -impl SystemApi::Number> for System { - fn system_name(&self) -> Result { +#[async_trait] +impl SystemApiServer::Number> for System { + fn system_name(&self) -> RpcResult { Ok(self.info.impl_name.clone()) } - fn system_version(&self) -> Result { + fn system_version(&self) -> RpcResult { Ok(self.info.impl_version.clone()) } - fn system_chain(&self) -> Result { + fn system_chain(&self) -> RpcResult { Ok(self.info.chain_name.clone()) } - fn system_type(&self) -> Result { + fn system_type(&self) -> RpcResult { Ok(self.info.chain_type.clone()) } - fn system_properties(&self) -> Result { + fn system_properties(&self) -> RpcResult { Ok(self.info.properties.clone()) } - fn system_health(&self) -> Receiver { + async fn system_health(&self) -> RpcResult { let (tx, rx) = oneshot::channel(); let _ = self.send_back.unbounded_send(Request::Health(tx)); - Receiver(rx) + rx.await.map_err(|e| JsonRpseeError::to_call_error(e)) } - fn system_local_peer_id(&self) -> Receiver { + async fn system_local_peer_id(&self) -> RpcResult { let (tx, rx) = oneshot::channel(); let _ = self.send_back.unbounded_send(Request::LocalPeerId(tx)); - Receiver(rx) + rx.await.map_err(|e| JsonRpseeError::to_call_error(e)) } - fn system_local_listen_addresses(&self) -> Receiver> { + async fn system_local_listen_addresses(&self) -> RpcResult> { let (tx, rx) = oneshot::channel(); let _ = self.send_back.unbounded_send(Request::LocalListenAddresses(tx)); - Receiver(rx) + rx.await.map_err(|e| JsonRpseeError::to_call_error(e)) } - fn system_peers( + async fn system_peers( &self, - ) -> rpc::BoxFuture::Number>>>> { - bail_if_unsafe!(self.deny_unsafe); - + ) -> RpcResult::Number>>> { + self.deny_unsafe.check_if_safe()?; let (tx, rx) = oneshot::channel(); let _ = self.send_back.unbounded_send(Request::Peers(tx)); - - async move { rx.await.map_err(|_| rpc::Error::internal_error()) }.boxed() + rx.await.map_err(|e| JsonRpseeError::to_call_error(e)) } - fn system_network_state(&self) -> rpc::BoxFuture> { - bail_if_unsafe!(self.deny_unsafe); - + async fn system_network_state(&self) -> RpcResult { + self.deny_unsafe.check_if_safe()?; let (tx, rx) = oneshot::channel(); let _ = self.send_back.unbounded_send(Request::NetworkState(tx)); - - async move { rx.await.map_err(|_| rpc::Error::internal_error()) }.boxed() + rx.await.map_err(|e| JsonRpseeError::to_call_error(e)) } - fn system_add_reserved_peer(&self, peer: String) -> rpc::BoxFuture> { - bail_if_unsafe!(self.deny_unsafe); - + async fn system_add_reserved_peer(&self, peer: String) -> RpcResult<()> { + self.deny_unsafe.check_if_safe()?; let (tx, rx) = oneshot::channel(); let _ = self.send_back.unbounded_send(Request::NetworkAddReservedPeer(peer, tx)); - async move { - match rx.await { - Ok(Ok(())) => Ok(()), - Ok(Err(e)) => Err(rpc::Error::from(e)), - Err(_) => Err(rpc::Error::internal_error()), - } + match rx.await { + Ok(Ok(())) => Ok(()), + Ok(Err(e)) => Err(JsonRpseeError::from(e)), + Err(e) => Err(JsonRpseeError::to_call_error(e)), } - .boxed() } - fn system_remove_reserved_peer(&self, peer: String) -> rpc::BoxFuture> { - bail_if_unsafe!(self.deny_unsafe); - + async fn system_remove_reserved_peer(&self, peer: String) -> RpcResult<()> { + self.deny_unsafe.check_if_safe()?; let (tx, rx) = oneshot::channel(); let _ = self.send_back.unbounded_send(Request::NetworkRemoveReservedPeer(peer, tx)); - async move { - match rx.await { - Ok(Ok(())) => Ok(()), - Ok(Err(e)) => Err(rpc::Error::from(e)), - Err(_) => Err(rpc::Error::internal_error()), - } + match rx.await { + Ok(Ok(())) => Ok(()), + Ok(Err(e)) => Err(JsonRpseeError::from(e)), + Err(e) => Err(JsonRpseeError::to_call_error(e)), } - .boxed() } - fn system_reserved_peers(&self) -> Receiver> { + async fn system_reserved_peers(&self) -> RpcResult> { let (tx, rx) = oneshot::channel(); let _ = self.send_back.unbounded_send(Request::NetworkReservedPeers(tx)); - Receiver(rx) + rx.await.map_err(|e| JsonRpseeError::to_call_error(e)) } - fn system_node_roles(&self) -> Receiver> { + async fn system_node_roles(&self) -> RpcResult> { let (tx, rx) = oneshot::channel(); let _ = self.send_back.unbounded_send(Request::NodeRoles(tx)); - Receiver(rx) + rx.await.map_err(|e| JsonRpseeError::to_call_error(e)) } - fn system_sync_state(&self) -> Receiver::Number>> { + async fn system_sync_state(&self) -> RpcResult::Number>> { let (tx, rx) = oneshot::channel(); let _ = self.send_back.unbounded_send(Request::SyncState(tx)); - Receiver(rx) + rx.await.map_err(|e| JsonRpseeError::to_call_error(e)) } - fn system_add_log_filter(&self, directives: String) -> rpc::Result<()> { + fn system_add_log_filter(&self, directives: String) -> RpcResult<()> { self.deny_unsafe.check_if_safe()?; + logging::add_directives(&directives); - logging::reload_filter().map_err(|_e| rpc::Error::internal_error()) + logging::reload_filter().map_err(|e| { + JsonRpseeError::Call(CallError::Custom(ErrorObject::owned( + ErrorCode::InternalError.code(), + e, + None::<()>, + ))) + }) } - fn system_reset_log_filter(&self) -> rpc::Result<()> { + fn system_reset_log_filter(&self) -> RpcResult<()> { self.deny_unsafe.check_if_safe()?; - logging::reset_log_filter().map_err(|_e| rpc::Error::internal_error()) + logging::reset_log_filter().map_err(|e| { + JsonRpseeError::Call(CallError::Custom(ErrorObject::owned( + ErrorCode::InternalError.code(), + e, + None::<()>, + ))) + }) } } diff --git a/client/rpc/src/system/tests.rs b/client/rpc/src/system/tests.rs index 5d6945b714200..77acdf8418ccc 100644 --- a/client/rpc/src/system/tests.rs +++ b/client/rpc/src/system/tests.rs @@ -16,12 +16,18 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use super::*; - +use super::{helpers::SyncState, *}; use assert_matches::assert_matches; -use futures::{executor, prelude::*}; +use futures::prelude::*; +use jsonrpsee::{ + core::Error as RpcError, + types::{error::CallError, EmptyParams}, + RpcModule, +}; use sc_network::{self, config::Role, PeerId}; +use sc_rpc_api::system::helpers::PeerInfo; use sc_utils::mpsc::tracing_unbounded; +use sp_core::H256; use std::{ env, io::{BufRead, BufReader, Write}, @@ -43,7 +49,7 @@ impl Default for Status { } } -fn api>>(sync: T) -> System { +fn api>>(sync: T) -> RpcModule> { let status = sync.into().unwrap_or_default(); let should_have_peers = !status.is_dev; let (tx, rx) = tracing_unbounded("rpc_system_tests"); @@ -136,98 +142,122 @@ fn api>>(sync: T) -> System { tx, sc_rpc_api::DenyUnsafe::No, ) + .into_rpc() } -fn wait_receiver(rx: Receiver) -> T { - futures::executor::block_on(rx).unwrap() +#[tokio::test] +async fn system_name_works() { + assert_eq!( + api(None).call::<_, String>("system_name", EmptyParams::new()).await.unwrap(), + "testclient".to_string(), + ); } -#[test] -fn system_name_works() { - assert_eq!(api(None).system_name().unwrap(), "testclient".to_owned()); +#[tokio::test] +async fn system_version_works() { + assert_eq!( + api(None).call::<_, String>("system_version", EmptyParams::new()).await.unwrap(), + "0.2.0".to_string(), + ); } -#[test] -fn system_version_works() { - assert_eq!(api(None).system_version().unwrap(), "0.2.0".to_owned()); +#[tokio::test] +async fn system_chain_works() { + assert_eq!( + api(None).call::<_, String>("system_chain", EmptyParams::new()).await.unwrap(), + "testchain".to_string(), + ); } -#[test] -fn system_chain_works() { - assert_eq!(api(None).system_chain().unwrap(), "testchain".to_owned()); -} +#[tokio::test] +async fn system_properties_works() { + type Map = serde_json::map::Map; -#[test] -fn system_properties_works() { - assert_eq!(api(None).system_properties().unwrap(), serde_json::map::Map::new()); + assert_eq!( + api(None).call::<_, Map>("system_properties", EmptyParams::new()).await.unwrap(), + Map::new() + ); } -#[test] -fn system_type_works() { - assert_eq!(api(None).system_type().unwrap(), Default::default()); +#[tokio::test] +async fn system_type_works() { + assert_eq!( + api(None) + .call::<_, String>("system_chainType", EmptyParams::new()) + .await + .unwrap(), + "Live".to_owned(), + ); } -#[test] -fn system_health() { - assert_matches!( - wait_receiver(api(None).system_health()), - Health { peers: 0, is_syncing: false, should_have_peers: true } +#[tokio::test] +async fn system_health() { + assert_eq!( + api(None).call::<_, Health>("system_health", EmptyParams::new()).await.unwrap(), + Health { peers: 0, is_syncing: false, should_have_peers: true }, ); - assert_matches!( - wait_receiver( - api(Status { peer_id: PeerId::random(), peers: 5, is_syncing: true, is_dev: true }) - .system_health() - ), - Health { peers: 5, is_syncing: true, should_have_peers: false } + assert_eq!( + api(Status { peer_id: PeerId::random(), peers: 5, is_syncing: true, is_dev: true }) + .call::<_, Health>("system_health", EmptyParams::new()) + .await + .unwrap(), + Health { peers: 5, is_syncing: true, should_have_peers: false }, ); assert_eq!( - wait_receiver( - api(Status { peer_id: PeerId::random(), peers: 5, is_syncing: false, is_dev: false }) - .system_health() - ), - Health { peers: 5, is_syncing: false, should_have_peers: true } + api(Status { peer_id: PeerId::random(), peers: 5, is_syncing: false, is_dev: false }) + .call::<_, Health>("system_health", EmptyParams::new()) + .await + .unwrap(), + Health { peers: 5, is_syncing: false, should_have_peers: true }, ); assert_eq!( - wait_receiver( - api(Status { peer_id: PeerId::random(), peers: 0, is_syncing: false, is_dev: true }) - .system_health() - ), - Health { peers: 0, is_syncing: false, should_have_peers: false } + api(Status { peer_id: PeerId::random(), peers: 0, is_syncing: false, is_dev: true }) + .call::<_, Health>("system_health", EmptyParams::new()) + .await + .unwrap(), + Health { peers: 0, is_syncing: false, should_have_peers: false }, ); } -#[test] -fn system_local_peer_id_works() { +#[tokio::test] +async fn system_local_peer_id_works() { assert_eq!( - wait_receiver(api(None).system_local_peer_id()), - "QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV".to_owned(), + api(None) + .call::<_, String>("system_localPeerId", EmptyParams::new()) + .await + .unwrap(), + "QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV".to_owned() ); } -#[test] -fn system_local_listen_addresses_works() { +#[tokio::test] +async fn system_local_listen_addresses_works() { assert_eq!( - wait_receiver(api(None).system_local_listen_addresses()), + api(None) + .call::<_, Vec>("system_localListenAddresses", EmptyParams::new()) + .await + .unwrap(), vec![ - "/ip4/198.51.100.19/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV" - .to_string(), + "/ip4/198.51.100.19/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV", "/ip4/127.0.0.1/tcp/30334/ws/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV" - .to_string(), ] ); } -#[test] -fn system_peers() { +#[tokio::test] +async fn system_peers() { let peer_id = PeerId::random(); - let req = api(Status { peer_id, peers: 1, is_syncing: false, is_dev: true }).system_peers(); - let res = executor::block_on(req).unwrap(); + let peer_info: Vec> = + api(Status { peer_id, peers: 1, is_syncing: false, is_dev: true }) + .call("system_peers", EmptyParams::new()) + .await + .unwrap(); assert_eq!( - res, + peer_info, vec![PeerInfo { peer_id: peer_id.to_base58(), roles: "FULL".into(), @@ -237,14 +267,16 @@ fn system_peers() { ); } -#[test] -fn system_network_state() { - let req = api(None).system_network_state(); - let res = executor::block_on(req).unwrap(); - +#[tokio::test] +async fn system_network_state() { + use sc_network::network_state::NetworkState; + let network_state: NetworkState = api(None) + .call("system_unstable_networkState", EmptyParams::new()) + .await + .unwrap(); assert_eq!( - serde_json::from_value::(res).unwrap(), - sc_network::network_state::NetworkState { + network_state, + NetworkState { peer_id: String::new(), listened_addresses: Default::default(), external_addresses: Default::default(), @@ -255,50 +287,59 @@ fn system_network_state() { ); } -#[test] -fn system_node_roles() { - assert_eq!(wait_receiver(api(None).system_node_roles()), vec![NodeRole::Authority]); +#[tokio::test] +async fn system_node_roles() { + let node_roles: Vec = + api(None).call("system_nodeRoles", EmptyParams::new()).await.unwrap(); + assert_eq!(node_roles, vec![NodeRole::Authority]); } - -#[test] -fn system_sync_state() { +#[tokio::test] +async fn system_sync_state() { + let sync_state: SyncState = + api(None).call("system_syncState", EmptyParams::new()).await.unwrap(); assert_eq!( - wait_receiver(api(None).system_sync_state()), + sync_state, SyncState { starting_block: 1, current_block: 2, highest_block: Some(3) } ); } -#[test] -fn system_network_add_reserved() { +#[tokio::test] +async fn system_network_add_reserved() { let good_peer_id = - "/ip4/198.51.100.19/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV"; - let bad_peer_id = "/ip4/198.51.100.19/tcp/30333"; + ["/ip4/198.51.100.19/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV"]; + let _good: () = api(None) + .call("system_addReservedPeer", good_peer_id) + .await + .expect("good peer id works"); - let good_fut = api(None).system_add_reserved_peer(good_peer_id.into()); - let bad_fut = api(None).system_add_reserved_peer(bad_peer_id.into()); - assert_eq!(executor::block_on(good_fut), Ok(())); - assert!(executor::block_on(bad_fut).is_err()); + let bad_peer_id = ["/ip4/198.51.100.19/tcp/30333"]; + assert_matches!( + api(None).call::<_, ()>("system_addReservedPeer", bad_peer_id).await, + Err(RpcError::Call(CallError::Custom(err))) if err.message().contains("Peer id is missing from the address") + ); } -#[test] -fn system_network_remove_reserved() { - let good_peer_id = "QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV"; - let bad_peer_id = - "/ip4/198.51.100.19/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV"; +#[tokio::test] +async fn system_network_remove_reserved() { + let _good_peer: () = api(None) + .call("system_removeReservedPeer", ["QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV"]) + .await + .expect("call with good peer id works"); - let good_fut = api(None).system_remove_reserved_peer(good_peer_id.into()); - let bad_fut = api(None).system_remove_reserved_peer(bad_peer_id.into()); - assert_eq!(executor::block_on(good_fut), Ok(())); - assert!(executor::block_on(bad_fut).is_err()); -} + let bad_peer_id = + ["/ip4/198.51.100.19/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV"]; -#[test] -fn system_network_reserved_peers() { - assert_eq!( - wait_receiver(api(None).system_reserved_peers()), - vec!["QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV".to_string()] + assert_matches!( + api(None).call::<_, String>("system_removeReservedPeer", bad_peer_id).await, + Err(RpcError::Call(CallError::Custom(err))) if err.message().contains("base-58 decode error: provided string contained invalid character '/' at byte 0") ); } +#[tokio::test] +async fn system_network_reserved_peers() { + let reserved_peers: Vec = + api(None).call("system_reservedPeers", EmptyParams::new()).await.unwrap(); + assert_eq!(reserved_peers, vec!["QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV".to_string()],); +} #[test] fn test_add_reset_log_filter() { @@ -315,15 +356,20 @@ fn test_add_reset_log_filter() { for line in std::io::stdin().lock().lines() { let line = line.expect("Failed to read bytes"); if line.contains("add_reload") { - api(None) - .system_add_log_filter("test_after_add".into()) - .expect("`system_add_log_filter` failed"); + let filter = "test_after_add"; + let fut = + async move { api(None).call::<_, ()>("system_addLogFilter", [filter]).await }; + futures::executor::block_on(fut).expect("`system_addLogFilter` failed"); } else if line.contains("add_trace") { - api(None) - .system_add_log_filter("test_before_add=trace".into()) - .expect("`system_add_log_filter` failed"); + let filter = "test_before_add=trace"; + let fut = + async move { api(None).call::<_, ()>("system_addLogFilter", [filter]).await }; + futures::executor::block_on(fut).expect("`system_addLogFilter (trace)` failed"); } else if line.contains("reset") { - api(None).system_reset_log_filter().expect("`system_reset_log_filter` failed"); + let fut = async move { + api(None).call::<_, ()>("system_resetLogFilter", EmptyParams::new()).await + }; + futures::executor::block_on(fut).expect("`system_resetLogFilter` failed"); } else if line.contains("exit") { return } diff --git a/client/rpc/src/testing.rs b/client/rpc/src/testing.rs index bfb91adb81d31..584e4a9901eab 100644 --- a/client/rpc/src/testing.rs +++ b/client/rpc/src/testing.rs @@ -18,29 +18,16 @@ //! Testing utils used by the RPC tests. -use futures::{ - executor, - task::{FutureObj, Spawn, SpawnError}, -}; - -// Executor shared by all tests. -// -// This shared executor is used to prevent `Too many open files` errors -// on systems with a lot of cores. -lazy_static::lazy_static! { - static ref EXECUTOR: executor::ThreadPool = executor::ThreadPool::new() - .expect("Failed to create thread pool executor for tests"); +use std::{future::Future, sync::Arc}; + +use sp_core::testing::TaskExecutor; + +/// Executor for testing. +pub fn test_executor() -> Arc { + Arc::new(TaskExecutor::default()) } -/// Executor for use in testing -pub struct TaskExecutor; -impl Spawn for TaskExecutor { - fn spawn_obj(&self, future: FutureObj<'static, ()>) -> Result<(), SpawnError> { - EXECUTOR.spawn_ok(future); - Ok(()) - } - - fn status(&self) -> Result<(), SpawnError> { - Ok(()) - } +/// Wrap a future in a timeout a little more concisely +pub fn timeout_secs>(s: u64, f: F) -> tokio::time::Timeout { + tokio::time::timeout(std::time::Duration::from_secs(s), f) } diff --git a/client/service/Cargo.toml b/client/service/Cargo.toml index 7138d9d384eeb..a62298a260aa4 100644 --- a/client/service/Cargo.toml +++ b/client/service/Cargo.toml @@ -22,10 +22,9 @@ wasmtime = ["sc-executor/wasmtime"] test-helpers = [] [dependencies] +jsonrpsee = { version = "0.12.0", features = ["server"] } thiserror = "1.0.30" futures = "0.3.21" -jsonrpc-pubsub = "18.0" -jsonrpc-core = "18.0" rand = "0.7.3" parking_lot = "0.12.0" log = "0.4.16" diff --git a/client/service/src/builder.rs b/client/service/src/builder.rs index cabf004b2f707..5319bf24d5e72 100644 --- a/client/service/src/builder.rs +++ b/client/service/src/builder.rs @@ -25,7 +25,7 @@ use crate::{ start_rpc_servers, RpcHandlers, SpawnTaskHandle, TaskManager, TransactionPoolAdapter, }; use futures::{channel::oneshot, future::ready, FutureExt, StreamExt}; -use jsonrpc_pubsub::manager::SubscriptionManager; +use jsonrpsee::RpcModule; use log::info; use prometheus_endpoint::Registry; use sc_chain_spec::get_extension; @@ -45,6 +45,14 @@ use sc_network::{ warp_request_handler::{self, RequestHandler as WarpSyncRequestHandler, WarpSyncProvider}, NetworkService, }; +use sc_rpc::{ + author::AuthorApiServer, + chain::ChainApiServer, + offchain::OffchainApiServer, + state::{ChildStateApiServer, StateApiServer}, + system::SystemApiServer, + DenyUnsafe, SubscriptionTaskExecutor, +}; use sc_telemetry::{telemetry, ConnectionMessage, Telemetry, TelemetryHandle, SUBSTRATE_INFO}; use sc_transaction_pool_api::MaintainedTransactionPool; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedSender}; @@ -62,69 +70,6 @@ use sp_runtime::{ }; use std::{str::FromStr, sync::Arc, time::SystemTime}; -/// A utility trait for building an RPC extension given a `DenyUnsafe` instance. -/// This is useful since at service definition time we don't know whether the -/// specific interface where the RPC extension will be exposed is safe or not. -/// This trait allows us to lazily build the RPC extension whenever we bind the -/// service to an interface. -pub trait RpcExtensionBuilder { - /// The type of the RPC extension that will be built. - type Output: sc_rpc::RpcExtension; - - /// Returns an instance of the RPC extension for a particular `DenyUnsafe` - /// value, e.g. the RPC extension might not expose some unsafe methods. - fn build( - &self, - deny: sc_rpc::DenyUnsafe, - subscription_executor: sc_rpc::SubscriptionTaskExecutor, - ) -> Result; -} - -impl RpcExtensionBuilder for F -where - F: Fn(sc_rpc::DenyUnsafe, sc_rpc::SubscriptionTaskExecutor) -> Result, - R: sc_rpc::RpcExtension, -{ - type Output = R; - - fn build( - &self, - deny: sc_rpc::DenyUnsafe, - subscription_executor: sc_rpc::SubscriptionTaskExecutor, - ) -> Result { - (*self)(deny, subscription_executor) - } -} - -/// A utility struct for implementing an `RpcExtensionBuilder` given a cloneable -/// `RpcExtension`, the resulting builder will simply ignore the provided -/// `DenyUnsafe` instance and return a static `RpcExtension` instance. -pub struct NoopRpcExtensionBuilder(pub R); - -impl RpcExtensionBuilder for NoopRpcExtensionBuilder -where - R: Clone + sc_rpc::RpcExtension, -{ - type Output = R; - - fn build( - &self, - _deny: sc_rpc::DenyUnsafe, - _subscription_executor: sc_rpc::SubscriptionTaskExecutor, - ) -> Result { - Ok(self.0.clone()) - } -} - -impl From for NoopRpcExtensionBuilder -where - R: sc_rpc::RpcExtension, -{ - fn from(e: R) -> NoopRpcExtensionBuilder { - NoopRpcExtensionBuilder(e) - } -} - /// Full client type. pub type TFullClient = Client, TFullCallExecutor, TBl, TRtApi>; @@ -389,9 +334,9 @@ pub struct SpawnTasksParams<'a, TBl: BlockT, TCl, TExPool, TRpc, Backend> { pub keystore: SyncCryptoStorePtr, /// A shared transaction pool. pub transaction_pool: Arc, - /// A RPC extension builder. Use `NoopRpcExtensionBuilder` if you just want to pass in the - /// extensions directly. - pub rpc_extensions_builder: Box + Send>, + /// Builds additional [`RpcModule`]s that should be added to the server + pub rpc_builder: + Box Result, Error>>, /// A shared network instance. pub network: Arc::Hash>>, /// A Sender for RPC requests. @@ -463,7 +408,6 @@ where TExPool: MaintainedTransactionPool::Hash> + parity_util_mem::MallocSizeOf + 'static, - TRpc: sc_rpc::RpcExtension, { let SpawnTasksParams { mut config, @@ -472,7 +416,7 @@ where backend, keystore, transaction_pool, - rpc_extensions_builder, + rpc_builder, network, system_rpc_tx, telemetry, @@ -536,35 +480,25 @@ where metrics_service.run(client.clone(), transaction_pool.clone(), network.clone()), ); - // RPC - let gen_handler = |deny_unsafe: sc_rpc::DenyUnsafe, - rpc_middleware: sc_rpc_server::RpcMiddleware| { - gen_handler( + let rpc_id_provider = config.rpc_id_provider.take(); + + // jsonrpsee RPC + let gen_rpc_module = |deny_unsafe: DenyUnsafe| { + gen_rpc_module( deny_unsafe, - rpc_middleware, - &config, task_manager.spawn_handle(), client.clone(), transaction_pool.clone(), keystore.clone(), - &*rpc_extensions_builder, - backend.offchain_storage(), system_rpc_tx.clone(), + &config, + backend.offchain_storage(), + &*rpc_builder, ) }; - let rpc_metrics = sc_rpc_server::RpcMetrics::new(config.prometheus_registry())?; - let server_metrics = sc_rpc_server::ServerMetrics::new(config.prometheus_registry())?; - let rpc = start_rpc_servers(&config, gen_handler, rpc_metrics.clone(), server_metrics)?; - // This is used internally, so don't restrict access to unsafe RPC - let known_rpc_method_names = - sc_rpc_server::method_names(|m| gen_handler(sc_rpc::DenyUnsafe::No, m))?; - let rpc_handlers = RpcHandlers(Arc::new( - gen_handler( - sc_rpc::DenyUnsafe::No, - sc_rpc_server::RpcMiddleware::new(rpc_metrics, known_rpc_method_names, "inbrowser"), - )? - .into(), - )); + + let rpc = start_rpc_servers(&config, gen_rpc_module, rpc_id_provider)?; + let rpc_handlers = RpcHandlers(Arc::new(gen_rpc_module(sc_rpc::DenyUnsafe::No)?.into())); // Spawn informant task spawn_handle.spawn( @@ -578,7 +512,7 @@ where ), ); - task_manager.keep_alive((config.base_path, rpc, rpc_handlers.clone())); + task_manager.keep_alive((config.base_path, rpc)); Ok(rpc_handlers) } @@ -642,18 +576,17 @@ fn init_telemetry>( Ok(telemetry.handle()) } -fn gen_handler( - deny_unsafe: sc_rpc::DenyUnsafe, - rpc_middleware: sc_rpc_server::RpcMiddleware, - config: &Configuration, +fn gen_rpc_module( + deny_unsafe: DenyUnsafe, spawn_handle: SpawnTaskHandle, client: Arc, transaction_pool: Arc, keystore: SyncCryptoStorePtr, - rpc_extensions_builder: &(dyn RpcExtensionBuilder + Send), - offchain_storage: Option<>::OffchainStorage>, system_rpc_tx: TracingUnboundedSender>, -) -> Result, Error> + config: &Configuration, + offchain_storage: Option<>::OffchainStorage>, + rpc_builder: &(dyn Fn(DenyUnsafe, SubscriptionTaskExecutor) -> Result, Error>), +) -> Result, Error> where TBl: BlockT, TCl: ProvideRuntimeApi @@ -668,15 +601,12 @@ where + Send + Sync + 'static, - TExPool: MaintainedTransactionPool::Hash> + 'static, TBackend: sc_client_api::backend::Backend + 'static, - TRpc: sc_rpc::RpcExtension, >::Api: sp_session::SessionKeys + sp_api::Metadata, + TExPool: MaintainedTransactionPool::Hash> + 'static, TBl::Hash: Unpin, TBl::Header: Unpin, { - use sc_rpc::{author, chain, offchain, state, system}; - let system_info = sc_rpc::system::SystemInfo { chain_name: config.chain_spec.name().into(), impl_name: config.impl_name.clone(), @@ -685,42 +615,50 @@ where chain_type: config.chain_spec.chain_type(), }; - let task_executor = sc_rpc::SubscriptionTaskExecutor::new(spawn_handle); - let subscriptions = SubscriptionManager::new(Arc::new(task_executor.clone())); + let mut rpc_api = RpcModule::new(()); + let task_executor = Arc::new(spawn_handle); let (chain, state, child_state) = { - // Full nodes - let chain = sc_rpc::chain::new_full(client.clone(), subscriptions.clone()); + let chain = sc_rpc::chain::new_full(client.clone(), task_executor.clone()).into_rpc(); let (state, child_state) = sc_rpc::state::new_full( client.clone(), - subscriptions.clone(), + task_executor.clone(), deny_unsafe, config.rpc_max_payload, ); + let state = state.into_rpc(); + let child_state = child_state.into_rpc(); + (chain, state, child_state) }; - let author = - sc_rpc::author::Author::new(client, transaction_pool, subscriptions, keystore, deny_unsafe); - let system = system::System::new(system_info, system_rpc_tx, deny_unsafe); + let author = sc_rpc::author::Author::new( + client.clone(), + transaction_pool, + keystore, + deny_unsafe, + task_executor.clone(), + ) + .into_rpc(); - let maybe_offchain_rpc = offchain_storage.map(|storage| { - let offchain = sc_rpc::offchain::Offchain::new(storage, deny_unsafe); - offchain::OffchainApi::to_delegate(offchain) - }); + let system = sc_rpc::system::System::new(system_info, system_rpc_tx, deny_unsafe).into_rpc(); - Ok(sc_rpc_server::rpc_handler( - ( - state::StateApi::to_delegate(state), - state::ChildStateApi::to_delegate(child_state), - chain::ChainApi::to_delegate(chain), - maybe_offchain_rpc, - author::AuthorApi::to_delegate(author), - system::SystemApi::to_delegate(system), - rpc_extensions_builder.build(deny_unsafe, task_executor)?, - ), - rpc_middleware, - )) + if let Some(storage) = offchain_storage { + let offchain = sc_rpc::offchain::Offchain::new(storage, deny_unsafe).into_rpc(); + + rpc_api.merge(offchain).map_err(|e| Error::Application(e.into()))?; + } + + rpc_api.merge(chain).map_err(|e| Error::Application(e.into()))?; + rpc_api.merge(author).map_err(|e| Error::Application(e.into()))?; + rpc_api.merge(system).map_err(|e| Error::Application(e.into()))?; + rpc_api.merge(state).map_err(|e| Error::Application(e.into()))?; + rpc_api.merge(child_state).map_err(|e| Error::Application(e.into()))?; + // Additional [`RpcModule`]s defined in the node to fit the specific blockchain + let extra_rpcs = rpc_builder(deny_unsafe, task_executor.clone())?; + rpc_api.merge(extra_rpcs).map_err(|e| Error::Application(e.into()))?; + + Ok(rpc_api) } /// Parameters to pass into `build_network`. diff --git a/client/service/src/config.rs b/client/service/src/config.rs index e49e8b40a7b1a..35380da11fc71 100644 --- a/client/service/src/config.rs +++ b/client/service/src/config.rs @@ -100,6 +100,18 @@ pub struct Configuration { pub rpc_methods: RpcMethods, /// Maximum payload of rpc request/responses. pub rpc_max_payload: Option, + /// Maximum payload of a rpc request + pub rpc_max_request_size: Option, + /// Maximum payload of a rpc request + pub rpc_max_response_size: Option, + /// Custom JSON-RPC subscription ID provider. + /// + /// Default: [`crate::RandomStringSubscriptionId`]. + pub rpc_id_provider: Option>, + /// Maximum allowed subscriptions per rpc connection + /// + /// Default: 1024. + pub rpc_max_subs_per_conn: Option, /// Maximum size of the output buffer capacity for websocket connections. pub ws_max_out_buffer_capacity: Option, /// Prometheus endpoint configuration. `None` if disabled. diff --git a/client/service/src/lib.rs b/client/service/src/lib.rs index 0d2461376d961..027b704789635 100644 --- a/client/service/src/lib.rs +++ b/client/service/src/lib.rs @@ -34,13 +34,15 @@ mod client; mod metrics; mod task_manager; -use std::{collections::HashMap, io, net::SocketAddr, pin::Pin}; +use std::{collections::HashMap, net::SocketAddr}; use codec::{Decode, Encode}; -use futures::{Future, FutureExt, StreamExt}; +use futures::{channel::mpsc, FutureExt, StreamExt}; +use jsonrpsee::{core::Error as JsonRpseeError, RpcModule}; use log::{debug, error, warn}; -use sc_client_api::{BlockBackend, ProofProvider}; +use sc_client_api::{blockchain::HeaderBackend, BlockBackend, BlockchainEvents, ProofProvider}; use sc_network::PeerId; +use sc_rpc_server::WsConfig; use sc_utils::mpsc::TracingUnboundedReceiver; use sp_blockchain::HeaderMetadata; use sp_runtime::{ @@ -52,8 +54,7 @@ pub use self::{ builder::{ build_network, build_offchain_workers, new_client, new_db_backend, new_full_client, new_full_parts, spawn_tasks, BuildNetworkParams, KeystoreContainer, NetworkStarter, - NoopRpcExtensionBuilder, RpcExtensionBuilder, SpawnTasksParams, TFullBackend, - TFullCallExecutor, TFullClient, + SpawnTasksParams, TFullBackend, TFullCallExecutor, TFullClient, }, client::{ClientConfig, LocalCallExecutor}, error::Error, @@ -65,12 +66,14 @@ pub use sc_chain_spec::{ ChainSpec, ChainType, Extension as ChainSpecExtension, GenericChainSpec, NoExtension, Properties, RuntimeGenesis, }; -use sc_client_api::{blockchain::HeaderBackend, BlockchainEvents}; + pub use sc_consensus::ImportQueue; pub use sc_executor::NativeExecutionDispatch; #[doc(hidden)] pub use sc_network::config::{TransactionImport, TransactionImportFuture}; -pub use sc_rpc::Metadata as RpcMetadata; +pub use sc_rpc::{ + RandomIntegerSubscriptionId, RandomStringSubscriptionId, RpcSubscriptionIdProvider, +}; pub use sc_tracing::TracingReceiver; pub use sc_transaction_pool::Options as TransactionPoolOptions; pub use sc_transaction_pool_api::{error::IntoPoolError, InPoolTransaction, TransactionPool}; @@ -82,32 +85,27 @@ const DEFAULT_PROTOCOL_ID: &str = "sup"; /// RPC handlers that can perform RPC queries. #[derive(Clone)] -pub struct RpcHandlers( - Arc>, -); +pub struct RpcHandlers(Arc>); impl RpcHandlers { /// Starts an RPC query. /// - /// The query is passed as a string and must be a JSON text similar to what an HTTP client - /// would for example send. + /// The query is passed as a string and must be valid JSON-RPC request object. /// - /// Returns a `Future` that contains the optional response. + /// Returns a response and a stream if the call successful, fails if the + /// query could not be decoded as a JSON-RPC request object. /// - /// If the request subscribes you to events, the `Sender` in the `RpcSession` object is used to - /// send back spontaneous events. - pub fn rpc_query( + /// If the request subscribes you to events, the `stream` can be used to + /// retrieve the events. + pub async fn rpc_query( &self, - mem: &RpcSession, - request: &str, - ) -> Pin> + Send>> { - self.0.handle_request(request, mem.metadata.clone()).boxed() + json_query: &str, + ) -> Result<(String, mpsc::UnboundedReceiver), JsonRpseeError> { + self.0.raw_json_request(json_query).await } - /// Provides access to the underlying `MetaIoHandler` - pub fn io_handler( - &self, - ) -> Arc> { + /// Provides access to the underlying `RpcModule` + pub fn handle(&self) -> Arc> { self.0.clone() } } @@ -284,74 +282,41 @@ async fn build_network_future< // Wrapper for HTTP and WS servers that makes sure they are properly shut down. mod waiting { pub struct HttpServer(pub Option); - impl Drop for HttpServer { - fn drop(&mut self) { - if let Some(server) = self.0.take() { - server.close_handle().close(); - server.wait(); - } - } - } - pub struct IpcServer(pub Option); - impl Drop for IpcServer { + impl Drop for HttpServer { fn drop(&mut self) { if let Some(server) = self.0.take() { - server.close_handle().close(); - let _ = server.wait(); + // This doesn't not wait for the server to be stopped but fires the signal. + let _ = server.stop(); } } } pub struct WsServer(pub Option); + impl Drop for WsServer { fn drop(&mut self) { if let Some(server) = self.0.take() { - server.close_handle().close(); - let _ = server.wait(); + // This doesn't not wait for the server to be stopped but fires the signal. + let _ = server.stop(); } } } } -/// Starts RPC servers that run in their own thread, and returns an opaque object that keeps them -/// alive. -fn start_rpc_servers< - H: FnMut( - sc_rpc::DenyUnsafe, - sc_rpc_server::RpcMiddleware, - ) -> Result, Error>, ->( +/// Starts RPC servers. +fn start_rpc_servers( config: &Configuration, - mut gen_handler: H, - rpc_metrics: Option, - server_metrics: sc_rpc_server::ServerMetrics, -) -> Result, Error> { - fn maybe_start_server( - address: Option, - mut start: F, - ) -> Result, Error> - where - F: FnMut(&SocketAddr) -> Result, - { - address - .map(|mut address| { - start(&address).or_else(|e| match e { - Error::Io(e) => match e.kind() { - io::ErrorKind::AddrInUse | io::ErrorKind::PermissionDenied => { - warn!("Unable to bind RPC server to {}. Trying random port.", address); - address.set_port(0); - start(&address) - }, - _ => Err(e.into()), - }, - e => Err(e), - }) - }) - .transpose() - } + gen_rpc_module: R, + rpc_id_provider: Option>, +) -> Result, error::Error> +where + R: Fn(sc_rpc::DenyUnsafe) -> Result, Error>, +{ + let (max_request_size, ws_max_response_size, http_max_response_size) = + legacy_cli_parsing(config); - fn deny_unsafe(addr: &SocketAddr, methods: &RpcMethods) -> sc_rpc::DenyUnsafe { + fn deny_unsafe(addr: SocketAddr, methods: &RpcMethods) -> sc_rpc::DenyUnsafe { let is_exposed_addr = !addr.ip().is_loopback(); match (is_exposed_addr, methods) { | (_, RpcMethods::Unsafe) | (false, RpcMethods::Auto) => sc_rpc::DenyUnsafe::No, @@ -359,85 +324,54 @@ fn start_rpc_servers< } } - let rpc_method_names = sc_rpc_server::method_names(|m| gen_handler(sc_rpc::DenyUnsafe::No, m))?; - Ok(Box::new(( - config - .rpc_ipc - .as_ref() - .map(|path| { - sc_rpc_server::start_ipc( - &*path, - gen_handler( - sc_rpc::DenyUnsafe::No, - sc_rpc_server::RpcMiddleware::new( - rpc_metrics.clone(), - rpc_method_names.clone(), - "ipc", - ), - )?, - server_metrics.clone(), - ) - .map_err(Error::from) - }) - .transpose()?, - maybe_start_server(config.rpc_http, |address| { - sc_rpc_server::start_http( - address, - config.rpc_cors.as_ref(), - gen_handler( - deny_unsafe(address, &config.rpc_methods), - sc_rpc_server::RpcMiddleware::new( - rpc_metrics.clone(), - rpc_method_names.clone(), - "http", - ), - )?, - config.rpc_max_payload, - config.tokio_handle.clone(), - ) - .map_err(Error::from) - })? - .map(|s| waiting::HttpServer(Some(s))), - maybe_start_server(config.rpc_ws, |address| { - sc_rpc_server::start_ws( - address, - config.rpc_ws_max_connections, - config.rpc_cors.as_ref(), - gen_handler( - deny_unsafe(address, &config.rpc_methods), - sc_rpc_server::RpcMiddleware::new( - rpc_metrics.clone(), - rpc_method_names.clone(), - "ws", - ), - )?, - config.rpc_max_payload, - config.ws_max_out_buffer_capacity, - server_metrics.clone(), - config.tokio_handle.clone(), - ) - .map_err(Error::from) - })? - .map(|s| waiting::WsServer(Some(s))), - ))) -} + let random_port = |mut addr: SocketAddr| { + addr.set_port(0); + addr + }; -/// An RPC session. Used to perform in-memory RPC queries (ie. RPC queries that don't go through -/// the HTTP or WebSockets server). -#[derive(Clone)] -pub struct RpcSession { - metadata: sc_rpc::Metadata, -} + let ws_addr = config + .rpc_ws + .unwrap_or_else(|| "127.0.0.1:9944".parse().expect("valid sockaddr; qed")); + let ws_addr2 = random_port(ws_addr); + let http_addr = config + .rpc_http + .unwrap_or_else(|| "127.0.0.1:9933".parse().expect("valid sockaddr; qed")); + let http_addr2 = random_port(http_addr); + + let metrics = sc_rpc_server::RpcMetrics::new(config.prometheus_registry())?; + + let http_fut = sc_rpc_server::start_http( + [http_addr, http_addr2], + config.rpc_cors.as_ref(), + max_request_size, + http_max_response_size, + metrics.clone(), + gen_rpc_module(deny_unsafe(ws_addr, &config.rpc_methods))?, + config.tokio_handle.clone(), + ); + + let ws_config = WsConfig { + max_connections: config.rpc_ws_max_connections, + max_payload_in_mb: max_request_size, + max_payload_out_mb: ws_max_response_size, + max_subs_per_conn: config.rpc_max_subs_per_conn, + }; -impl RpcSession { - /// Creates an RPC session. - /// - /// The `sender` is stored inside the `RpcSession` and is used to communicate spontaneous JSON - /// messages. - /// - /// The `RpcSession` must be kept alive in order to receive messages on the sender. - pub fn new(sender: futures::channel::mpsc::UnboundedSender) -> RpcSession { - RpcSession { metadata: sender.into() } + let ws_fut = sc_rpc_server::start_ws( + [ws_addr, ws_addr2], + config.rpc_cors.as_ref(), + ws_config, + metrics, + gen_rpc_module(deny_unsafe(http_addr, &config.rpc_methods))?, + config.tokio_handle.clone(), + rpc_id_provider, + ); + + match tokio::task::block_in_place(|| { + config.tokio_handle.block_on(futures::future::try_join(http_fut, ws_fut)) + }) { + Ok((http, ws)) => Ok(Box::new((http, ws))), + Err(e) => Err(Error::Application(e)), } } @@ -545,6 +479,44 @@ where } } +fn legacy_cli_parsing(config: &Configuration) -> (Option, Option, Option) { + let ws_max_response_size = config.ws_max_out_buffer_capacity.map(|max| { + eprintln!("DEPRECATED: `--ws_max_out_buffer_capacity` has been removed use `rpc-max-response-size or rpc-max-request-size` instead"); + eprintln!("Setting WS `rpc-max-response-size` to `max(ws_max_out_buffer_capacity, rpc_max_response_size)`"); + std::cmp::max(max, config.rpc_max_response_size.unwrap_or(0)) + }); + + let max_request_size = match (config.rpc_max_payload, config.rpc_max_request_size) { + (Some(legacy_max), max) => { + eprintln!("DEPRECATED: `--rpc_max_payload` has been removed use `rpc-max-response-size or rpc-max-request-size` instead"); + eprintln!( + "Setting `rpc-max-response-size` to `max(rpc_max_payload, rpc_max_request_size)`" + ); + Some(std::cmp::max(legacy_max, max.unwrap_or(0))) + }, + (None, Some(max)) => Some(max), + (None, None) => None, + }; + + let http_max_response_size = match (config.rpc_max_payload, config.rpc_max_request_size) { + (Some(legacy_max), max) => { + eprintln!("DEPRECATED: `--rpc_max_payload` has been removed use `rpc-max-response-size or rpc-max-request-size` instead"); + eprintln!( + "Setting HTTP `rpc-max-response-size` to `max(rpc_max_payload, rpc_max_response_size)`" + ); + Some(std::cmp::max(legacy_max, max.unwrap_or(0))) + }, + (None, Some(max)) => Some(max), + (None, None) => None, + }; + + if config.rpc_ipc.is_some() { + eprintln!("DEPRECATED: `--ipc-path` has no effect anymore IPC support has been removed"); + } + + (max_request_size, ws_max_response_size, http_max_response_size) +} + #[cfg(test)] mod tests { use super::*; diff --git a/client/service/test/src/lib.rs b/client/service/test/src/lib.rs index ef04e16a65d26..749c83c6eeac7 100644 --- a/client/service/test/src/lib.rs +++ b/client/service/test/src/lib.rs @@ -246,6 +246,10 @@ fn node_config< rpc_cors: None, rpc_methods: Default::default(), rpc_max_payload: None, + rpc_max_request_size: None, + rpc_max_response_size: None, + rpc_id_provider: None, + rpc_max_subs_per_conn: None, ws_max_out_buffer_capacity: None, prometheus_config: None, telemetry_endpoints: None, diff --git a/client/sync-state-rpc/Cargo.toml b/client/sync-state-rpc/Cargo.toml index a32849dc0e964..f42c307ffa84c 100644 --- a/client/sync-state-rpc/Cargo.toml +++ b/client/sync-state-rpc/Cargo.toml @@ -14,9 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } -jsonrpc-core = "18.0.0" -jsonrpc-core-client = "18.0.0" -jsonrpc-derive = "18.0.0" +jsonrpsee = { version = "0.12.0", features = ["server", "macros"] } serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0.79" thiserror = "1.0.30" diff --git a/client/sync-state-rpc/src/lib.rs b/client/sync-state-rpc/src/lib.rs index d7696c662e856..a0a5b66cb86fc 100644 --- a/client/sync-state-rpc/src/lib.rs +++ b/client/sync-state-rpc/src/lib.rs @@ -37,10 +37,15 @@ //! ``` //! //! If the [`LightSyncStateExtension`] is not added as an extension to the chain spec, -//! the [`SyncStateRpcHandler`] will fail at instantiation. +//! the [`SyncStateRpc`] will fail at instantiation. #![deny(unused_crate_dependencies)] +use jsonrpsee::{ + core::{Error as JsonRpseeError, RpcResult}, + proc_macros::rpc, + types::{error::CallError, ErrorObject}, +}; use sc_client_api::StorageData; use sp_blockchain::HeaderBackend; use sp_runtime::{ @@ -49,8 +54,6 @@ use sp_runtime::{ }; use std::sync::Arc; -use jsonrpc_derive::rpc; - type SharedAuthoritySet = sc_finality_grandpa::SharedAuthoritySet<::Hash, NumberFor>; type SharedEpochChanges = @@ -76,13 +79,13 @@ pub enum Error { LightSyncStateExtensionNotFound, } -impl From> for jsonrpc_core::Error { +impl From> for JsonRpseeError { fn from(error: Error) -> Self { let message = match error { Error::JsonRpc(s) => s, _ => error.to_string(), }; - jsonrpc_core::Error { message, code: jsonrpc_core::ErrorCode::ServerError(1), data: None } + CallError::Custom(ErrorObject::owned(1, message, None::<()>)).into() } } @@ -101,7 +104,7 @@ fn serialize_encoded( /// chain-spec as an extension. pub type LightSyncStateExtension = Option; -/// Hardcoded infomation that allows light clients to sync quickly. +/// Hardcoded information that allows light clients to sync quickly. #[derive(serde::Serialize, Clone)] #[serde(rename_all = "camelCase")] #[serde(deny_unknown_fields)] @@ -121,30 +124,30 @@ pub struct LightSyncState { } /// An api for sync state RPC calls. -#[rpc] +#[rpc(client, server)] pub trait SyncStateRpcApi { - /// Returns the json-serialized chainspec running the node, with a sync state. - #[rpc(name = "sync_state_genSyncSpec", returns = "jsonrpc_core::Value")] - fn system_gen_sync_spec(&self, raw: bool) -> jsonrpc_core::Result; + /// Returns the JSON serialized chainspec running the node, with a sync state. + #[method(name = "sync_state_genSyncSpec")] + fn system_gen_sync_spec(&self, raw: bool) -> RpcResult; } -/// The handler for sync state RPC calls. -pub struct SyncStateRpcHandler { +/// An api for sync state RPC calls. +pub struct SyncStateRpc { chain_spec: Box, - client: Arc, + client: Arc, shared_authority_set: SharedAuthoritySet, shared_epoch_changes: SharedEpochChanges, } -impl SyncStateRpcHandler +impl SyncStateRpc where Block: BlockT, - Backend: HeaderBackend + sc_client_api::AuxStore + 'static, + Client: HeaderBackend + sc_client_api::AuxStore + 'static, { - /// Create a new handler. + /// Create a new sync state RPC helper. pub fn new( chain_spec: Box, - client: Arc, + client: Arc, shared_authority_set: SharedAuthoritySet, shared_epoch_changes: SharedEpochChanges, ) -> Result> { @@ -177,32 +180,23 @@ where } } -impl SyncStateRpcApi for SyncStateRpcHandler +impl SyncStateRpcApiServer for SyncStateRpc where Block: BlockT, Backend: HeaderBackend + sc_client_api::AuxStore + 'static, { - fn system_gen_sync_spec(&self, raw: bool) -> jsonrpc_core::Result { + fn system_gen_sync_spec(&self, raw: bool) -> RpcResult { + let current_sync_state = self.build_sync_state()?; let mut chain_spec = self.chain_spec.cloned_box(); - let sync_state = self.build_sync_state().map_err(map_error::>)?; - let extension = sc_chain_spec::get_extension_mut::( chain_spec.extensions_mut(), ) - .ok_or_else(|| { - Error::::JsonRpc("Could not find `LightSyncState` chain-spec extension!".into()) - })?; - - *extension = - Some(serde_json::to_value(&sync_state).map_err(|err| map_error::(err))?); + .ok_or(Error::::LightSyncStateExtensionNotFound)?; - let json_string = chain_spec.as_json(raw).map_err(map_error::)?; + let val = serde_json::to_value(¤t_sync_state)?; + *extension = Some(val); - serde_json::from_str(&json_string).map_err(|err| map_error::(err)) + chain_spec.as_json(raw).map_err(|e| Error::::JsonRpc(e).into()) } } - -fn map_error(error: S) -> jsonrpc_core::Error { - Error::::JsonRpc(error.to_string()).into() -} diff --git a/client/tracing/src/block/mod.rs b/client/tracing/src/block/mod.rs index 6de3a0220e15e..2a034f06ce8a8 100644 --- a/client/tracing/src/block/mod.rs +++ b/client/tracing/src/block/mod.rs @@ -53,7 +53,7 @@ const AVG_SPAN: usize = 100 * 8; // are used for the RPC Id this may need to be adjusted. Note: The base payload // does not include the RPC result. // -// The estimate is based on the JSONRPC response message which has the following format: +// The estimate is based on the JSON-RPC response message which has the following format: // `{"jsonrpc":"2.0","result":[],"id":18446744073709551615}`. // // We care about the total size of the payload because jsonrpc-server will simply ignore diff --git a/client/transaction-pool/api/src/error.rs b/client/transaction-pool/api/src/error.rs index b093657f739b1..aada44734d053 100644 --- a/client/transaction-pool/api/src/error.rs +++ b/client/transaction-pool/api/src/error.rs @@ -46,7 +46,7 @@ pub enum Error { TemporarilyBanned, #[error("[{0:?}] Already imported")] - AlreadyImported(Box), + AlreadyImported(Box), #[error("Too low priority ({} > {})", old, new)] TooLowPriority { @@ -72,7 +72,7 @@ pub enum Error { } /// Transaction pool error conversion. -pub trait IntoPoolError: std::error::Error + Send + Sized { +pub trait IntoPoolError: std::error::Error + Send + Sized + Sync { /// Try to extract original `Error` /// /// This implementation is optional and used only to diff --git a/client/transaction-pool/api/src/lib.rs b/client/transaction-pool/api/src/lib.rs index 448578b327b03..0ebb8f9d4cd9c 100644 --- a/client/transaction-pool/api/src/lib.rs +++ b/client/transaction-pool/api/src/lib.rs @@ -22,16 +22,17 @@ pub mod error; use futures::{Future, Stream}; -use serde::{Deserialize, Serialize}; -pub use sp_runtime::transaction_validity::{ - TransactionLongevity, TransactionPriority, TransactionSource, TransactionTag, -}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, Member, NumberFor}, }; use std::{collections::HashMap, hash::Hash, pin::Pin, sync::Arc}; +pub use sp_runtime::transaction_validity::{ + TransactionLongevity, TransactionPriority, TransactionSource, TransactionTag, +}; + /// Transaction pool status. #[derive(Debug)] pub struct PoolStatus { @@ -177,7 +178,7 @@ pub trait TransactionPool: Send + Sync { /// Block type. type Block: BlockT; /// Transaction hash type. - type Hash: Hash + Eq + Member + Serialize; + type Hash: Hash + Eq + Member + Serialize + DeserializeOwned; /// In-pool transaction type. type InPoolTransaction: InPoolTransaction< Transaction = TransactionFor, diff --git a/frame/bags-list/remote-tests/Cargo.toml b/frame/bags-list/remote-tests/Cargo.toml index 05675741ae51b..760115917c6d4 100644 --- a/frame/bags-list/remote-tests/Cargo.toml +++ b/frame/bags-list/remote-tests/Cargo.toml @@ -32,4 +32,3 @@ remote-externalities = { path = "../../../utils/frame/remote-externalities", ver # others log = "0.4.16" -tokio = { version = "1", features = ["macros"] } diff --git a/frame/contracts/rpc/Cargo.toml b/frame/contracts/rpc/Cargo.toml index e506e78a2fbdc..36f6c06328501 100644 --- a/frame/contracts/rpc/Cargo.toml +++ b/frame/contracts/rpc/Cargo.toml @@ -14,9 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } -jsonrpc-core = "18.0.0" -jsonrpc-core-client = "18.0.0" -jsonrpc-derive = "18.0.0" +jsonrpsee = { version = "0.12.0", features = ["server", "macros"] } serde = { version = "1", features = ["derive"] } # Substrate Dependencies diff --git a/frame/contracts/rpc/src/lib.rs b/frame/contracts/rpc/src/lib.rs index e83e4e6249b92..599e80676cb19 100644 --- a/frame/contracts/rpc/src/lib.rs +++ b/frame/contracts/rpc/src/lib.rs @@ -17,11 +17,16 @@ //! Node-specific RPC methods for interaction with contracts. -use std::sync::Arc; +#![warn(unused_crate_dependencies)] + +use std::{marker::PhantomData, sync::Arc}; use codec::Codec; -use jsonrpc_core::{Error, ErrorCode, Result}; -use jsonrpc_derive::rpc; +use jsonrpsee::{ + core::{async_trait, Error as JsonRpseeError, RpcResult}, + proc_macros::rpc, + types::error::{CallError, ErrorCode, ErrorObject}, +}; use pallet_contracts_primitives::{ Code, CodeUploadResult, ContractExecResult, ContractInstantiateResult, }; @@ -37,8 +42,8 @@ use sp_runtime::{ pub use pallet_contracts_rpc_runtime_api::ContractsApi as ContractsRuntimeApi; -const RUNTIME_ERROR: i64 = 1; -const CONTRACT_DOESNT_EXIST: i64 = 2; +const RUNTIME_ERROR: i32 = 1; +const CONTRACT_DOESNT_EXIST: i32 = 2; pub type Weight = u64; @@ -58,15 +63,17 @@ const GAS_LIMIT: Weight = 5 * GAS_PER_SECOND; /// A private newtype for converting `ContractAccessError` into an RPC error. struct ContractAccessError(pallet_contracts_primitives::ContractAccessError); -impl From for Error { - fn from(e: ContractAccessError) -> Error { + +impl From for JsonRpseeError { + fn from(e: ContractAccessError) -> Self { use pallet_contracts_primitives::ContractAccessError::*; match e.0 { - DoesntExist => Error { - code: ErrorCode::ServerError(CONTRACT_DOESNT_EXIST), - message: "The specified contract doesn't exist.".into(), - data: None, - }, + DoesntExist => CallError::Custom(ErrorObject::owned( + CONTRACT_DOESNT_EXIST, + "The specified contract doesn't exist.", + None::<()>, + )) + .into(), } } } @@ -109,7 +116,7 @@ pub struct CodeUploadRequest { } /// Contracts RPC methods. -#[rpc] +#[rpc(client, server)] pub trait ContractsApi where Balance: Copy + TryFrom + Into, @@ -121,12 +128,12 @@ where /// /// This method is useful for calling getter-like methods on contracts or to dry-run a /// a contract call in order to determine the `gas_limit`. - #[rpc(name = "contracts_call")] + #[method(name = "contracts_call")] fn call( &self, call_request: CallRequest, at: Option, - ) -> Result>; + ) -> RpcResult>; /// Instantiate a new contract. /// @@ -134,12 +141,12 @@ where /// is not actually created. /// /// This method is useful for UIs to dry-run contract instantiations. - #[rpc(name = "contracts_instantiate")] + #[method(name = "contracts_instantiate")] fn instantiate( &self, instantiate_request: InstantiateRequest, at: Option, - ) -> Result>; + ) -> RpcResult>; /// Upload new code without instantiating a contract from it. /// @@ -147,48 +154,50 @@ where /// won't change any state. /// /// This method is useful for UIs to dry-run code upload. - #[rpc(name = "contracts_upload_code")] + #[method(name = "contracts_upload_code")] fn upload_code( &self, upload_request: CodeUploadRequest, at: Option, - ) -> Result>; + ) -> RpcResult>; /// Returns the value under a specified storage `key` in a contract given by `address` param, /// or `None` if it is not set. - #[rpc(name = "contracts_getStorage")] + #[method(name = "contracts_getStorage")] fn get_storage( &self, address: AccountId, key: H256, at: Option, - ) -> Result>; + ) -> RpcResult>; } -/// An implementation of contract specific RPC methods. -pub struct Contracts { - client: Arc, - _marker: std::marker::PhantomData, +/// Contracts RPC methods. +pub struct ContractsRpc { + client: Arc, + _marker: PhantomData, } -impl Contracts { +impl ContractsRpc { /// Create new `Contracts` with the given reference to the client. - pub fn new(client: Arc) -> Self { - Contracts { client, _marker: Default::default() } + pub fn new(client: Arc) -> Self { + Self { client, _marker: Default::default() } } } -impl - ContractsApi< + +#[async_trait] +impl + ContractsApiServer< ::Hash, <::Header as HeaderT>::Number, AccountId, Balance, Hash, - > for Contracts + > for ContractsRpc where Block: BlockT, - C: Send + Sync + 'static + ProvideRuntimeApi + HeaderBackend, - C::Api: ContractsRuntimeApi< + Client: Send + Sync + 'static + ProvideRuntimeApi + HeaderBackend, + Client::Api: ContractsRuntimeApi< Block, AccountId, Balance, @@ -203,7 +212,7 @@ where &self, call_request: CallRequest, at: Option<::Hash>, - ) -> Result> { + ) -> RpcResult> { let api = self.client.runtime_api(); let at = BlockId::hash(at.unwrap_or_else(|| // If the block hash is not supplied assume the best block. @@ -226,7 +235,7 @@ where &self, instantiate_request: InstantiateRequest, at: Option<::Hash>, - ) -> Result> { + ) -> RpcResult> { let api = self.client.runtime_api(); let at = BlockId::hash(at.unwrap_or_else(|| // If the block hash is not supplied assume the best block. @@ -265,7 +274,7 @@ where &self, upload_request: CodeUploadRequest, at: Option<::Hash>, - ) -> Result> { + ) -> RpcResult> { let api = self.client.runtime_api(); let at = BlockId::hash(at.unwrap_or_else(|| // If the block hash is not supplied assume the best block. @@ -285,12 +294,9 @@ where address: AccountId, key: H256, at: Option<::Hash>, - ) -> Result> { + ) -> RpcResult> { let api = self.client.runtime_api(); - let at = BlockId::hash(at.unwrap_or_else(|| - // If the block hash is not supplied assume the best block. - self.client.info().best_hash)); - + let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash)); let result = api .get_storage(&at, address, key.into()) .map_err(runtime_error_into_rpc_err)? @@ -302,32 +308,35 @@ where } /// Converts a runtime trap into an RPC error. -fn runtime_error_into_rpc_err(err: impl std::fmt::Display) -> Error { - Error { - code: ErrorCode::ServerError(RUNTIME_ERROR), - message: "Runtime error".into(), - data: Some(err.to_string().into()), - } +fn runtime_error_into_rpc_err(err: impl std::fmt::Debug) -> JsonRpseeError { + CallError::Custom(ErrorObject::owned( + RUNTIME_ERROR, + "Runtime error", + Some(format!("{:?}", err)), + )) + .into() } -fn decode_hex>(from: H, name: &str) -> Result { - from.try_into().map_err(|_| Error { - code: ErrorCode::InvalidParams, - message: format!("{:?} does not fit into the {} type", from, name), - data: None, +fn decode_hex>(from: H, name: &str) -> RpcResult { + from.try_into().map_err(|_| { + JsonRpseeError::Call(CallError::Custom(ErrorObject::owned( + ErrorCode::InvalidParams.code(), + format!("{:?} does not fit into the {} type", from, name), + None::<()>, + ))) }) } -fn limit_gas(gas_limit: Weight) -> Result<()> { +fn limit_gas(gas_limit: Weight) -> RpcResult<()> { if gas_limit > GAS_LIMIT { - Err(Error { - code: ErrorCode::InvalidParams, - message: format!( + Err(JsonRpseeError::Call(CallError::Custom(ErrorObject::owned( + ErrorCode::InvalidParams.code(), + format!( "Requested gas limit is greater than maximum allowed: {} > {}", gas_limit, GAS_LIMIT ), - data: None, - }) + None::<()>, + )))) } else { Ok(()) } @@ -336,6 +345,7 @@ fn limit_gas(gas_limit: Weight) -> Result<()> { #[cfg(test)] mod tests { use super::*; + use pallet_contracts_primitives::{ContractExecResult, ContractInstantiateResult}; use sp_core::U256; fn trim(json: &str) -> String { diff --git a/frame/merkle-mountain-range/rpc/Cargo.toml b/frame/merkle-mountain-range/rpc/Cargo.toml index 9b3d3a43c6045..2d3bfebc6633f 100644 --- a/frame/merkle-mountain-range/rpc/Cargo.toml +++ b/frame/merkle-mountain-range/rpc/Cargo.toml @@ -14,9 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } -jsonrpc-core = "18.0.0" -jsonrpc-core-client = "18.0.0" -jsonrpc-derive = "18.0.0" +jsonrpsee = { version = "0.12.0", features = ["server", "macros"] } serde = { version = "1.0.136", features = ["derive"] } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } diff --git a/frame/merkle-mountain-range/rpc/src/lib.rs b/frame/merkle-mountain-range/rpc/src/lib.rs index be1a74450d1f4..12e4e11f88256 100644 --- a/frame/merkle-mountain-range/rpc/src/lib.rs +++ b/frame/merkle-mountain-range/rpc/src/lib.rs @@ -16,14 +16,18 @@ // limitations under the License. #![warn(missing_docs)] +#![warn(unused_crate_dependencies)] //! Node-specific RPC methods for interaction with Merkle Mountain Range pallet. -use std::sync::Arc; +use std::{marker::PhantomData, sync::Arc}; use codec::{Codec, Encode}; -use jsonrpc_core::{Error, ErrorCode, Result}; -use jsonrpc_derive::rpc; +use jsonrpsee::{ + core::{async_trait, RpcResult}, + proc_macros::rpc, + types::error::{CallError, ErrorObject}, +}; use serde::{Deserialize, Serialize}; use sp_api::ProvideRuntimeApi; @@ -34,6 +38,11 @@ use sp_runtime::{generic::BlockId, traits::Block as BlockT}; pub use sp_mmr_primitives::MmrApi as MmrRuntimeApi; +const RUNTIME_ERROR: i32 = 8000; +const MMR_ERROR: i32 = 8010; +const LEAF_NOT_FOUND_ERROR: i32 = MMR_ERROR + 1; +const GENERATE_PROOF_ERROR: i32 = MMR_ERROR + 2; + /// Retrieved MMR leaf and its proof. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] @@ -86,7 +95,7 @@ impl LeafBatchProof { } /// MMR RPC methods. -#[rpc] +#[rpc(client, server)] pub trait MmrApi { /// Generate MMR proof for given leaf index. /// @@ -96,12 +105,12 @@ pub trait MmrApi { /// /// Returns the (full) leaf itself and a proof for this leaf (compact encoding, i.e. hash of /// the leaf). Both parameters are SCALE-encoded. - #[rpc(name = "mmr_generateProof")] + #[method(name = "mmr_generateProof")] fn generate_proof( &self, leaf_index: LeafIndex, at: Option, - ) -> Result>; + ) -> RpcResult>; /// Generate MMR proof for the given leaf indices. /// @@ -113,43 +122,43 @@ pub trait MmrApi { /// the leaves). Both parameters are SCALE-encoded. /// The order of entries in the `leaves` field of the returned struct /// is the same as the order of the entries in `leaf_indices` supplied - #[rpc(name = "mmr_generateBatchProof")] + #[method(name = "mmr_generateBatchProof")] fn generate_batch_proof( &self, leaf_indices: Vec, at: Option, - ) -> Result>; + ) -> RpcResult>; } -/// An implementation of MMR specific RPC methods. -pub struct Mmr { - client: Arc, - _marker: std::marker::PhantomData, +/// MMR RPC methods. +pub struct MmrRpc { + client: Arc, + _marker: PhantomData, } -impl Mmr { +impl MmrRpc { /// Create new `Mmr` with the given reference to the client. pub fn new(client: Arc) -> Self { Self { client, _marker: Default::default() } } } -impl MmrApi<::Hash> for Mmr +#[async_trait] +impl MmrApiServer<::Hash> + for MmrRpc where Block: BlockT, - C: Send + Sync + 'static + ProvideRuntimeApi + HeaderBackend, - C::Api: MmrRuntimeApi, + Client: Send + Sync + 'static + ProvideRuntimeApi + HeaderBackend, + Client::Api: MmrRuntimeApi, MmrHash: Codec + Send + Sync + 'static, { fn generate_proof( &self, leaf_index: LeafIndex, at: Option<::Hash>, - ) -> Result::Hash>> { + ) -> RpcResult> { let api = self.client.runtime_api(); - let block_hash = at.unwrap_or_else(|| - // If the block hash is not supplied assume the best block. - self.client.info().best_hash); + let block_hash = at.unwrap_or_else(|| self.client.info().best_hash); let (leaf, proof) = api .generate_proof_with_context( @@ -167,7 +176,7 @@ where &self, leaf_indices: Vec, at: Option<::Hash>, - ) -> Result::Hash>> { + ) -> RpcResult::Hash>> { let api = self.client.runtime_api(); let block_hash = at.unwrap_or_else(|| // If the block hash is not supplied assume the best block. @@ -186,37 +195,31 @@ where } } -const RUNTIME_ERROR: i64 = 8000; -const MMR_ERROR: i64 = 8010; - -/// Converts a mmr-specific error into an RPC error. -fn mmr_error_into_rpc_error(err: MmrError) -> Error { +/// Converts a mmr-specific error into a [`CallError`]. +fn mmr_error_into_rpc_error(err: MmrError) -> CallError { + let data = format!("{:?}", err); match err { - MmrError::LeafNotFound => Error { - code: ErrorCode::ServerError(MMR_ERROR + 1), - message: "Leaf was not found".into(), - data: Some(format!("{:?}", err).into()), - }, - MmrError::GenerateProof => Error { - code: ErrorCode::ServerError(MMR_ERROR + 2), - message: "Error while generating the proof".into(), - data: Some(format!("{:?}", err).into()), - }, - _ => Error { - code: ErrorCode::ServerError(MMR_ERROR), - message: "Unexpected MMR error".into(), - data: Some(format!("{:?}", err).into()), - }, + MmrError::LeafNotFound => CallError::Custom(ErrorObject::owned( + LEAF_NOT_FOUND_ERROR, + "Leaf was not found", + Some(data), + )), + MmrError::GenerateProof => CallError::Custom(ErrorObject::owned( + GENERATE_PROOF_ERROR, + "Error while generating the proof", + Some(data), + )), + _ => CallError::Custom(ErrorObject::owned(MMR_ERROR, "Unexpected MMR error", Some(data))), } } -/// Converts a runtime trap into an RPC error. -fn runtime_error_into_rpc_error(err: impl std::fmt::Display) -> Error { - Error { - code: ErrorCode::ServerError(RUNTIME_ERROR), - message: "Runtime trapped".into(), - data: Some(err.to_string().into()), - } +/// Converts a runtime trap into a [`CallError`]. +fn runtime_error_into_rpc_error(err: impl std::fmt::Debug) -> CallError { + CallError::Custom(ErrorObject::owned( + RUNTIME_ERROR, + "Runtime trapped", + Some(format!("{:?}", err)), + )) } #[cfg(test)] diff --git a/frame/state-trie-migration/Cargo.toml b/frame/state-trie-migration/Cargo.toml index cae3bb1a9f975..958ab50315427 100644 --- a/frame/state-trie-migration/Cargo.toml +++ b/frame/state-trie-migration/Cargo.toml @@ -31,7 +31,7 @@ substrate-state-trie-migration-rpc = { optional = true, path = "../../utils/fram [dev-dependencies] parking_lot = "0.12.0" -tokio = { version = "1.10", features = ["macros"] } +tokio = { version = "1.17.0", features = ["macros"] } pallet-balances = { path = "../balances" } sp-tracing = { path = "../../primitives/tracing" } diff --git a/frame/transaction-payment/rpc/Cargo.toml b/frame/transaction-payment/rpc/Cargo.toml index b7a353916efbc..6133d3a4b6da1 100644 --- a/frame/transaction-payment/rpc/Cargo.toml +++ b/frame/transaction-payment/rpc/Cargo.toml @@ -14,9 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } -jsonrpc-core = "18.0.0" -jsonrpc-core-client = "18.0.0" -jsonrpc-derive = "18.0.0" +jsonrpsee = { version = "0.12.0", features = ["server", "macros"] } pallet-transaction-payment-rpc-runtime-api = { version = "4.0.0-dev", path = "./runtime-api" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } diff --git a/frame/transaction-payment/rpc/src/lib.rs b/frame/transaction-payment/rpc/src/lib.rs index 29d94fa260105..b0be19fdb22a9 100644 --- a/frame/transaction-payment/rpc/src/lib.rs +++ b/frame/transaction-payment/rpc/src/lib.rs @@ -17,11 +17,14 @@ //! RPC interface for the transaction payment pallet. -pub use self::gen_client::Client as TransactionPaymentClient; +use std::{convert::TryInto, sync::Arc}; + use codec::{Codec, Decode}; -use jsonrpc_core::{Error as RpcError, ErrorCode, Result}; -use jsonrpc_derive::rpc; -pub use pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi as TransactionPaymentRuntimeApi; +use jsonrpsee::{ + core::{async_trait, Error as JsonRpseeError, RpcResult}, + proc_macros::rpc, + types::error::{CallError, ErrorCode, ErrorObject}, +}; use pallet_transaction_payment_rpc_runtime_api::{FeeDetails, InclusionFee, RuntimeDispatchInfo}; use sp_api::ProvideRuntimeApi; use sp_blockchain::HeaderBackend; @@ -31,28 +34,31 @@ use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, MaybeDisplay}, }; -use std::sync::Arc; -#[rpc] +pub use pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi as TransactionPaymentRuntimeApi; + +#[rpc(client, server)] pub trait TransactionPaymentApi { - #[rpc(name = "payment_queryInfo")] - fn query_info(&self, encoded_xt: Bytes, at: Option) -> Result; - #[rpc(name = "payment_queryFeeDetails")] + #[method(name = "payment_queryInfo")] + fn query_info(&self, encoded_xt: Bytes, at: Option) -> RpcResult; + + #[method(name = "payment_queryFeeDetails")] fn query_fee_details( &self, encoded_xt: Bytes, at: Option, - ) -> Result>; + ) -> RpcResult>; } -/// A struct that implements the [`TransactionPaymentApi`]. -pub struct TransactionPayment { +/// Provides RPC methods to query a dispatchable's class, weight and fee. +pub struct TransactionPaymentRpc { + /// Shared reference to the client. client: Arc, _marker: std::marker::PhantomData

, } -impl TransactionPayment { - /// Create new `TransactionPayment` with the given reference to the client. +impl TransactionPaymentRpc { + /// Creates a new instance of the TransactionPaymentRpc helper. pub fn new(client: Arc) -> Self { Self { client, _marker: Default::default() } } @@ -66,8 +72,8 @@ pub enum Error { RuntimeError, } -impl From for i64 { - fn from(e: Error) -> i64 { +impl From for i32 { + fn from(e: Error) -> i32 { match e { Error::RuntimeError => 1, Error::DecodeError => 2, @@ -75,66 +81,75 @@ impl From for i64 { } } -impl TransactionPaymentApi<::Hash, RuntimeDispatchInfo> - for TransactionPayment +#[async_trait] +impl + TransactionPaymentApiServer<::Hash, RuntimeDispatchInfo> + for TransactionPaymentRpc where Block: BlockT, - C: 'static + ProvideRuntimeApi + HeaderBackend, + C: ProvideRuntimeApi + HeaderBackend + Send + Sync + 'static, C::Api: TransactionPaymentRuntimeApi, - Balance: Codec + MaybeDisplay + Copy + TryInto, + Balance: Codec + MaybeDisplay + Copy + TryInto + Send + Sync + 'static, { fn query_info( &self, encoded_xt: Bytes, - at: Option<::Hash>, - ) -> Result> { + at: Option, + ) -> RpcResult> { let api = self.client.runtime_api(); - let at = BlockId::hash(at.unwrap_or_else(|| - // If the block hash is not supplied assume the best block. - self.client.info().best_hash)); + let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash)); let encoded_len = encoded_xt.len() as u32; - let uxt: Block::Extrinsic = Decode::decode(&mut &*encoded_xt).map_err(|e| RpcError { - code: ErrorCode::ServerError(Error::DecodeError.into()), - message: "Unable to query dispatch info.".into(), - data: Some(format!("{:?}", e).into()), + let uxt: Block::Extrinsic = Decode::decode(&mut &*encoded_xt).map_err(|e| { + CallError::Custom(ErrorObject::owned( + Error::DecodeError.into(), + "Unable to query dispatch info.", + Some(format!("{:?}", e)), + )) })?; - api.query_info(&at, uxt, encoded_len).map_err(|e| RpcError { - code: ErrorCode::ServerError(Error::RuntimeError.into()), - message: "Unable to query dispatch info.".into(), - data: Some(e.to_string().into()), + api.query_info(&at, uxt, encoded_len).map_err(|e| { + CallError::Custom(ErrorObject::owned( + Error::RuntimeError.into(), + "Unable to query dispatch info.", + Some(e.to_string()), + )) + .into() }) } fn query_fee_details( &self, encoded_xt: Bytes, - at: Option<::Hash>, - ) -> Result> { + at: Option, + ) -> RpcResult> { let api = self.client.runtime_api(); - let at = BlockId::hash(at.unwrap_or_else(|| - // If the block hash is not supplied assume the best block. - self.client.info().best_hash)); + let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash)); let encoded_len = encoded_xt.len() as u32; - let uxt: Block::Extrinsic = Decode::decode(&mut &*encoded_xt).map_err(|e| RpcError { - code: ErrorCode::ServerError(Error::DecodeError.into()), - message: "Unable to query fee details.".into(), - data: Some(format!("{:?}", e).into()), + let uxt: Block::Extrinsic = Decode::decode(&mut &*encoded_xt).map_err(|e| { + CallError::Custom(ErrorObject::owned( + Error::DecodeError.into(), + "Unable to query fee details.", + Some(format!("{:?}", e)), + )) })?; - let fee_details = api.query_fee_details(&at, uxt, encoded_len).map_err(|e| RpcError { - code: ErrorCode::ServerError(Error::RuntimeError.into()), - message: "Unable to query fee details.".into(), - data: Some(e.to_string().into()), + let fee_details = api.query_fee_details(&at, uxt, encoded_len).map_err(|e| { + CallError::Custom(ErrorObject::owned( + Error::RuntimeError.into(), + "Unable to query fee details.", + Some(e.to_string()), + )) })?; let try_into_rpc_balance = |value: Balance| { - value.try_into().map_err(|_| RpcError { - code: ErrorCode::InvalidParams, - message: format!("{} doesn't fit in NumberOrHex representation", value), - data: None, + value.try_into().map_err(|_| { + JsonRpseeError::Call(CallError::Custom(ErrorObject::owned( + ErrorCode::InvalidParams.code(), + format!("{} doesn't fit in NumberOrHex representation", value), + None::<()>, + ))) }) }; diff --git a/test-utils/client/src/lib.rs b/test-utils/client/src/lib.rs index cdadfb0f10f03..148f34246044d 100644 --- a/test-utils/client/src/lib.rs +++ b/test-utils/client/src/lib.rs @@ -28,7 +28,7 @@ pub use sc_client_api::{ }; pub use sc_client_db::{self, Backend}; pub use sc_executor::{self, NativeElseWasmExecutor, WasmExecutionMethod}; -pub use sc_service::{client, RpcHandlers, RpcSession}; +pub use sc_service::{client, RpcHandlers}; pub use sp_consensus; pub use sp_keyring::{ ed25519::Keyring as Ed25519Keyring, sr25519::Keyring as Sr25519Keyring, AccountKeyring, @@ -37,10 +37,7 @@ pub use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; pub use sp_runtime::{Storage, StorageChild}; pub use sp_state_machine::ExecutionStrategy; -use futures::{ - future::{Future, FutureExt}, - stream::StreamExt, -}; +use futures::{future::Future, stream::StreamExt}; use sc_client_api::BlockchainEvents; use sc_service::client::{ClientConfig, LocalCallExecutor}; use serde::Deserialize; @@ -297,16 +294,14 @@ impl /// The output of an RPC transaction. pub struct RpcTransactionOutput { /// The output string of the transaction if any. - pub result: Option, - /// The session object. - pub session: RpcSession, + pub result: String, /// An async receiver if data will be returned via a callback. pub receiver: futures::channel::mpsc::UnboundedReceiver, } impl std::fmt::Debug for RpcTransactionOutput { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "RpcTransactionOutput {{ result: {:?}, session, receiver }}", self.result) + write!(f, "RpcTransactionOutput {{ result: {:?}, receiver }}", self.result) } } @@ -328,56 +323,51 @@ impl std::fmt::Display for RpcTransactionError { } /// An extension trait for `RpcHandlers`. +#[async_trait::async_trait] pub trait RpcHandlersExt { /// Send a transaction through the RpcHandlers. - fn send_transaction( + async fn send_transaction( &self, extrinsic: OpaqueExtrinsic, - ) -> Pin> + Send>>; + ) -> Result; } +#[async_trait::async_trait] impl RpcHandlersExt for RpcHandlers { - fn send_transaction( + async fn send_transaction( &self, extrinsic: OpaqueExtrinsic, - ) -> Pin> + Send>> { - let (tx, rx) = futures::channel::mpsc::unbounded(); - let mem = RpcSession::new(tx); - Box::pin( - self.rpc_query( - &mem, - &format!( - r#"{{ + ) -> Result { + let (result, rx) = self + .rpc_query(&format!( + r#"{{ "jsonrpc": "2.0", "method": "author_submitExtrinsic", "params": ["0x{}"], "id": 0 }}"#, - hex::encode(extrinsic.encode()) - ), - ) - .map(move |result| parse_rpc_result(result, mem, rx)), - ) + hex::encode(extrinsic.encode()) + )) + .await + .expect("valid JSON-RPC request object; qed"); + parse_rpc_result(result, rx) } } pub(crate) fn parse_rpc_result( - result: Option, - session: RpcSession, + result: String, receiver: futures::channel::mpsc::UnboundedReceiver, ) -> Result { - if let Some(ref result) = result { - let json: serde_json::Value = - serde_json::from_str(result).expect("the result can only be a JSONRPC string; qed"); - let error = json.as_object().expect("JSON result is always an object; qed").get("error"); - - if let Some(error) = error { - return Err(serde_json::from_value(error.clone()) - .expect("the JSONRPC result's error is always valid; qed")) - } + let json: serde_json::Value = + serde_json::from_str(&result).expect("the result can only be a JSONRPC string; qed"); + let error = json.as_object().expect("JSON result is always an object; qed").get("error"); + + if let Some(error) = error { + return Err(serde_json::from_value(error.clone()) + .expect("the JSONRPC result's error is always valid; qed")) } - Ok(RpcTransactionOutput { result, session, receiver }) + Ok(RpcTransactionOutput { result, receiver }) } /// An extension trait for `BlockchainEvents`. @@ -418,40 +408,23 @@ where #[cfg(test)] mod tests { - use sc_service::RpcSession; - - fn create_session_and_receiver( - ) -> (RpcSession, futures::channel::mpsc::UnboundedReceiver) { - let (tx, rx) = futures::channel::mpsc::unbounded(); - let mem = RpcSession::new(tx.into()); - - (mem, rx) - } - #[test] fn parses_error_properly() { - let (mem, rx) = create_session_and_receiver(); - assert!(super::parse_rpc_result(None, mem, rx).is_ok()); - - let (mem, rx) = create_session_and_receiver(); + let (_, rx) = futures::channel::mpsc::unbounded(); assert!(super::parse_rpc_result( - Some( - r#"{ + r#"{ "jsonrpc": "2.0", "result": 19, "id": 1 }"# - .to_string() - ), - mem, + .to_string(), rx ) - .is_ok(),); + .is_ok()); - let (mem, rx) = create_session_and_receiver(); + let (_, rx) = futures::channel::mpsc::unbounded(); let error = super::parse_rpc_result( - Some( - r#"{ + r#"{ "jsonrpc": "2.0", "error": { "code": -32601, @@ -459,9 +432,7 @@ mod tests { }, "id": 1 }"# - .to_string(), - ), - mem, + .to_string(), rx, ) .unwrap_err(); @@ -469,10 +440,9 @@ mod tests { assert_eq!(error.message, "Method not found"); assert!(error.data.is_none()); - let (mem, rx) = create_session_and_receiver(); + let (_, rx) = futures::channel::mpsc::unbounded(); let error = super::parse_rpc_result( - Some( - r#"{ + r#"{ "jsonrpc": "2.0", "error": { "code": -32601, @@ -481,9 +451,7 @@ mod tests { }, "id": 1 }"# - .to_string(), - ), - mem, + .to_string(), rx, ) .unwrap_err(); diff --git a/test-utils/runtime/src/lib.rs b/test-utils/runtime/src/lib.rs index 94325ae9c1ab1..aef061d952a96 100644 --- a/test-utils/runtime/src/lib.rs +++ b/test-utils/runtime/src/lib.rs @@ -176,6 +176,19 @@ impl serde::Serialize for Extrinsic { } } +// rustc can't deduce this trait bound https://github.com/rust-lang/rust/issues/48214 +#[cfg(feature = "std")] +impl<'a> serde::Deserialize<'a> for Extrinsic { + fn deserialize(de: D) -> Result + where + D: serde::Deserializer<'a>, + { + let r = sp_core::bytes::deserialize(de)?; + Decode::decode(&mut &r[..]) + .map_err(|e| serde::de::Error::custom(format!("Decode error: {}", e))) + } +} + impl BlindCheckable for Extrinsic { type Checked = Self; diff --git a/utils/frame/remote-externalities/Cargo.toml b/utils/frame/remote-externalities/Cargo.toml index 49c004c3c074d..4a931470eafac 100644 --- a/utils/frame/remote-externalities/Cargo.toml +++ b/utils/frame/remote-externalities/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } env_logger = "0.9" -jsonrpsee = { version = "0.10.1", features = ["ws-client", "macros"] } +jsonrpsee = { version = "0.12.0", features = ["ws-client", "macros"] } log = "0.4.16" serde = "1.0.136" serde_json = "1.0" diff --git a/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml b/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml index 3913bae425757..726cc9f989ced 100644 --- a/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml +++ b/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml @@ -25,9 +25,7 @@ sp-state-machine = { path = "../../../../primitives/state-machine" } sp-trie = { path = "../../../../primitives/trie" } trie-db = { version = "0.23.1" } -jsonrpc-core = "18.0.0" -jsonrpc-core-client = "18.0.0" -jsonrpc-derive = "18.0.0" +jsonrpsee = { version = "0.12.0", features = ["server", "macros"] } # Substrate Dependencies sc-client-api = { version = "4.0.0-dev", path = "../../../../client/api" } diff --git a/utils/frame/rpc/state-trie-migration-rpc/src/lib.rs b/utils/frame/rpc/state-trie-migration-rpc/src/lib.rs index 2e3dd08a7db7e..531bf463f6523 100644 --- a/utils/frame/rpc/state-trie-migration-rpc/src/lib.rs +++ b/utils/frame/rpc/state-trie-migration-rpc/src/lib.rs @@ -17,8 +17,11 @@ //! Rpc for state migration. -use jsonrpc_core::{Error, ErrorCode, Result}; -use jsonrpc_derive::rpc; +use jsonrpsee::{ + core::{Error as JsonRpseeError, RpcResult}, + proc_macros::rpc, + types::error::{CallError, ErrorCode, ErrorObject}, +}; use sc_rpc_api::DenyUnsafe; use serde::{Deserialize, Serialize}; use sp_runtime::{generic::BlockId, traits::Block as BlockT}; @@ -107,15 +110,15 @@ pub struct MigrationStatusResult { } /// Migration RPC methods. -#[rpc] +#[rpc(server)] pub trait StateMigrationApi { /// Check current migration state. /// /// This call is performed locally without submitting any transactions. Thus executing this /// won't change any state. Nonetheless it is a VERY costy call that should be /// only exposed to trusted peers. - #[rpc(name = "state_trieMigrationStatus")] - fn call(&self, at: Option) -> Result; + #[method(name = "state_trieMigrationStatus")] + fn call(&self, at: Option) -> RpcResult; } /// An implementation of state migration specific RPC methods. @@ -133,16 +136,14 @@ impl MigrationRpc { } } -impl StateMigrationApi<::Hash> for MigrationRpc +impl StateMigrationApiServer<::Hash> for MigrationRpc where B: BlockT, C: Send + Sync + 'static + sc_client_api::HeaderBackend, BA: 'static + sc_client_api::backend::Backend, { - fn call(&self, at: Option<::Hash>) -> Result { - if let Err(err) = self.deny_unsafe.check_if_safe() { - return Err(err.into()) - } + fn call(&self, at: Option<::Hash>) -> RpcResult { + self.deny_unsafe.check_if_safe()?; let block_id = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash)); let state = self.backend.state_at(block_id).map_err(error_into_rpc_err)?; @@ -155,10 +156,10 @@ where } } -fn error_into_rpc_err(err: impl std::fmt::Display) -> Error { - Error { - code: ErrorCode::InternalError, - message: "Error while checking migration state".into(), - data: Some(err.to_string().into()), - } +fn error_into_rpc_err(err: impl std::fmt::Display) -> JsonRpseeError { + JsonRpseeError::Call(CallError::Custom(ErrorObject::owned( + ErrorCode::InternalError.code(), + "Error while checking migration state", + Some(err.to_string()), + ))) } diff --git a/utils/frame/rpc/support/Cargo.toml b/utils/frame/rpc/support/Cargo.toml index f9967758928e8..0c6d082406421 100644 --- a/utils/frame/rpc/support/Cargo.toml +++ b/utils/frame/rpc/support/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.21" -jsonrpc-client-transports = { version = "18.0.0", features = ["http"] } +jsonrpsee = { version = "0.12.0", features = ["jsonrpsee-types"] } serde = "1" frame-support = { version = "4.0.0-dev", path = "../../../../frame/support" } sc-rpc-api = { version = "0.10.0-dev", path = "../../../../client/rpc-api" } @@ -25,5 +25,6 @@ sp-storage = { version = "6.0.0", path = "../../../../primitives/storage" } [dev-dependencies] scale-info = "2.0.1" +jsonrpsee = { version = "0.12.0", features = ["ws-client", "jsonrpsee-types"] } tokio = "1.17.0" frame-system = { version = "4.0.0-dev", path = "../../../../frame/system" } diff --git a/utils/frame/rpc/support/src/lib.rs b/utils/frame/rpc/support/src/lib.rs index 5d7cba19f643c..2ee007c84f0aa 100644 --- a/utils/frame/rpc/support/src/lib.rs +++ b/utils/frame/rpc/support/src/lib.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Combines [sc_rpc_api::state::StateClient] with [frame_support::storage::generator] traits +//! Combines [sc_rpc_api::state::StateApiClient] with [frame_support::storage::generator] traits //! to provide strongly typed chain state queries over rpc. #![warn(missing_docs)] @@ -23,29 +23,26 @@ use codec::{DecodeAll, FullCodec, FullEncode}; use core::marker::PhantomData; use frame_support::storage::generator::{StorageDoubleMap, StorageMap, StorageValue}; -use jsonrpc_client_transports::RpcError; -use sc_rpc_api::state::StateClient; +use jsonrpsee::core::Error as RpcError; +use sc_rpc_api::state::StateApiClient; use serde::{de::DeserializeOwned, Serialize}; use sp_storage::{StorageData, StorageKey}; /// A typed query on chain state usable from an RPC client. /// /// ```no_run -/// # use jsonrpc_client_transports::RpcError; -/// # use jsonrpc_client_transports::transports::http; +/// # use jsonrpsee::core::Error as RpcError; +/// # use jsonrpsee::ws_client::WsClientBuilder; /// # use codec::Encode; /// # use frame_support::{decl_storage, decl_module}; /// # use substrate_frame_rpc_support::StorageQuery; /// # use frame_system::Config; -/// # use sc_rpc_api::state::StateClient; +/// # use sc_rpc_api::state::StateApiClient; /// # /// # // Hash would normally be ::Hash, but we don't have /// # // frame_system::Config implemented for TestRuntime. Here we just pretend. /// # type Hash = (); /// # -/// # fn main() -> Result<(), RpcError> { -/// # tokio::runtime::Runtime::new().unwrap().block_on(test()) -/// # } /// # /// # struct TestRuntime; /// # @@ -66,24 +63,25 @@ use sp_storage::{StorageData, StorageKey}; /// } /// } /// -/// # async fn test() -> Result<(), RpcError> { -/// let conn = http::connect("http://[::1]:9933").await?; -/// let cl = StateClient::::new(conn); +/// #[tokio::main] +/// async fn main() -> Result<(), RpcError> { +/// let cl = WsClientBuilder::default().build("ws://[::1]:9944").await?; /// -/// let q = StorageQuery::value::(); -/// let _: Option = q.get(&cl, None).await?; +/// let q = StorageQuery::value::(); +/// let hash = None::; +/// let _: Option = q.get(&cl, hash).await?; /// -/// let q = StorageQuery::map::((0, 0, 0)); -/// let _: Option = q.get(&cl, None).await?; +/// let q = StorageQuery::map::((0, 0, 0)); +/// let _: Option = q.get(&cl, hash).await?; /// -/// let q = StorageQuery::map::(12); -/// let _: Option = q.get(&cl, None).await?; +/// let q = StorageQuery::map::(12); +/// let _: Option = q.get(&cl, hash).await?; /// -/// let q = StorageQuery::double_map::(3, (0, 0, 0)); -/// let _: Option = q.get(&cl, None).await?; -/// # -/// # Ok(()) -/// # } +/// let q = StorageQuery::double_map::(3, (0, 0, 0)); +/// let _: Option = q.get(&cl, hash).await?; +/// +/// Ok(()) +/// } /// ``` #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] pub struct StorageQuery { @@ -120,14 +118,18 @@ impl StorageQuery { /// /// block_index indicates the block for which state will be queried. A value of None indicates /// the latest block. - pub async fn get( + pub async fn get( self, - state_client: &StateClient, + state_client: &StateClient, block_index: Option, - ) -> Result, RpcError> { + ) -> Result, RpcError> + where + Hash: Send + Sync + 'static + DeserializeOwned + Serialize, + StateClient: StateApiClient + Sync, + { let opt: Option = state_client.storage(self.key, block_index).await?; opt.map(|encoded| V::decode_all(&mut &encoded.0[..])) .transpose() - .map_err(|decode_err| RpcError::Other(Box::new(decode_err))) + .map_err(|decode_err| RpcError::Custom(decode_err.to_string())) } } diff --git a/utils/frame/rpc/system/Cargo.toml b/utils/frame/rpc/system/Cargo.toml index 5252d96af3f75..c95ae4793ca6a 100644 --- a/utils/frame/rpc/system/Cargo.toml +++ b/utils/frame/rpc/system/Cargo.toml @@ -13,11 +13,10 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +serde_json = "1" codec = { package = "parity-scale-codec", version = "3.0.0" } +jsonrpsee = { version = "0.12.0", features = ["server"] } futures = "0.3.21" -jsonrpc-core = "18.0.0" -jsonrpc-core-client = "18.0.0" -jsonrpc-derive = "18.0.0" log = "0.4.16" frame-system-rpc-runtime-api = { version = "4.0.0-dev", path = "../../../../frame/system/rpc/runtime-api" } sc-client-api = { version = "4.0.0-dev", path = "../../../../client/api" } @@ -31,5 +30,7 @@ sp-runtime = { version = "6.0.0", path = "../../../../primitives/runtime" } [dev-dependencies] sc-transaction-pool = { version = "4.0.0-dev", path = "../../../../client/transaction-pool" } +tokio = "1.17.0" +assert_matches = "1.3.0" sp-tracing = { version = "5.0.0", path = "../../../../primitives/tracing" } substrate-test-runtime-client = { version = "2.0.0", path = "../../../../test-utils/runtime/client" } diff --git a/utils/frame/rpc/system/src/lib.rs b/utils/frame/rpc/system/src/lib.rs index b7da7730f0920..b044035c8120e 100644 --- a/utils/frame/rpc/system/src/lib.rs +++ b/utils/frame/rpc/system/src/lib.rs @@ -17,12 +17,15 @@ //! System FRAME specific RPC methods. -use std::sync::Arc; +use std::{fmt::Display, sync::Arc}; + +use codec::{self, Codec, Decode, Encode}; +use jsonrpsee::{ + core::{async_trait, RpcResult}, + proc_macros::rpc, + types::error::{CallError, ErrorObject}, +}; -use codec::{Codec, Decode, Encode}; -use futures::FutureExt; -use jsonrpc_core::{Error as RpcError, ErrorCode}; -use jsonrpc_derive::rpc; use sc_rpc_api::DenyUnsafe; use sc_transaction_pool_api::{InPoolTransaction, TransactionPool}; use sp_api::ApiExt; @@ -31,26 +34,22 @@ use sp_blockchain::HeaderBackend; use sp_core::{hexdisplay::HexDisplay, Bytes}; use sp_runtime::{generic::BlockId, legacy, traits}; -pub use self::gen_client::Client as SystemClient; pub use frame_system_rpc_runtime_api::AccountNonceApi; -/// Future that resolves to account nonce. -type FutureResult = jsonrpc_core::BoxFuture>; - /// System RPC methods. -#[rpc] +#[rpc(client, server)] pub trait SystemApi { /// Returns the next valid index (aka nonce) for given account. /// /// This method takes into consideration all pending transactions /// currently in the pool and if no transactions are found in the pool /// it fallbacks to query the index from the runtime (aka. state nonce). - #[rpc(name = "system_accountNextIndex", alias("account_nextIndex"))] - fn nonce(&self, account: AccountId) -> FutureResult; + #[method(name = "system_accountNextIndex", aliases = ["account_nextIndex"])] + async fn nonce(&self, account: AccountId) -> RpcResult; /// Dry run an extrinsic at a given block. Return SCALE encoded ApplyExtrinsicResult. - #[rpc(name = "system_dryRun", alias("system_dryRunAt"))] - fn dry_run(&self, extrinsic: Bytes, at: Option) -> FutureResult; + #[method(name = "system_dryRun", aliases = ["system_dryRunAt"])] + async fn dry_run(&self, extrinsic: Bytes, at: Option) -> RpcResult; } /// Error type of this RPC api. @@ -61,8 +60,8 @@ pub enum Error { RuntimeError, } -impl From for i64 { - fn from(e: Error) -> i64 { +impl From for i32 { + fn from(e: Error) -> i32 { match e { Error::RuntimeError => 1, Error::DecodeError => 2, @@ -71,22 +70,23 @@ impl From for i64 { } /// An implementation of System-specific RPC methods on full client. -pub struct FullSystem { +pub struct SystemRpc { client: Arc, pool: Arc

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

, deny_unsafe: DenyUnsafe) -> Self { - FullSystem { client, pool, deny_unsafe, _marker: Default::default() } + Self { client, pool, deny_unsafe, _marker: Default::default() } } } -impl SystemApi<::Hash, AccountId, Index> - for FullSystem +#[async_trait] +impl + SystemApiServer<::Hash, AccountId, Index> for SystemRpc where C: sp_api::ProvideRuntimeApi, C: HeaderBackend, @@ -95,88 +95,83 @@ where C::Api: BlockBuilder, P: TransactionPool + 'static, Block: traits::Block, - AccountId: Clone + std::fmt::Display + Codec, - Index: Clone + std::fmt::Display + Codec + Send + traits::AtLeast32Bit + 'static, + AccountId: Clone + Display + Codec + Send + 'static, + Index: Clone + Display + Codec + Send + traits::AtLeast32Bit + 'static, { - fn nonce(&self, account: AccountId) -> FutureResult { - let get_nonce = || { - let api = self.client.runtime_api(); - let best = self.client.info().best_hash; - let at = BlockId::hash(best); - - let nonce = api.account_nonce(&at, account.clone()).map_err(|e| RpcError { - code: ErrorCode::ServerError(Error::RuntimeError.into()), - message: "Unable to query nonce.".into(), - data: Some(e.to_string().into()), - })?; - - Ok(adjust_nonce(&*self.pool, account, nonce)) - }; - - let res = get_nonce(); - async move { res }.boxed() + async fn nonce(&self, account: AccountId) -> RpcResult { + let api = self.client.runtime_api(); + let best = self.client.info().best_hash; + let at = BlockId::hash(best); + + let nonce = api.account_nonce(&at, account.clone()).map_err(|e| { + CallError::Custom(ErrorObject::owned( + Error::RuntimeError.into(), + "Unable to query nonce.", + Some(e.to_string()), + )) + })?; + Ok(adjust_nonce(&*self.pool, account, nonce)) } - fn dry_run( + async fn dry_run( &self, extrinsic: Bytes, at: Option<::Hash>, - ) -> FutureResult { - if let Err(err) = self.deny_unsafe.check_if_safe() { - return async move { Err(err.into()) }.boxed() - } + ) -> RpcResult { + self.deny_unsafe.check_if_safe()?; + let api = self.client.runtime_api(); + let at = BlockId::::hash(at.unwrap_or_else(|| + // If the block hash is not supplied assume the best block. + self.client.info().best_hash)); + + let uxt: ::Extrinsic = + Decode::decode(&mut &*extrinsic).map_err(|e| { + CallError::Custom(ErrorObject::owned( + Error::DecodeError.into(), + "Unable to dry run extrinsic", + Some(e.to_string()), + )) + })?; - let dry_run = || { - let api = self.client.runtime_api(); - let at = BlockId::::hash(at.unwrap_or_else(|| - // If the block hash is not supplied assume the best block. - self.client.info().best_hash)); - - let uxt: ::Extrinsic = Decode::decode(&mut &*extrinsic) - .map_err(|e| RpcError { - code: ErrorCode::ServerError(Error::DecodeError.into()), - message: "Unable to dry run extrinsic.".into(), - data: Some(e.to_string().into()), - })?; - - let api_version = api - .api_version::>(&at) - .map_err(|e| RpcError { - code: ErrorCode::ServerError(Error::RuntimeError.into()), - message: "Unable to dry run extrinsic.".into(), - data: Some(e.to_string().into()), - })? - .ok_or_else(|| RpcError { - code: ErrorCode::ServerError(Error::RuntimeError.into()), - message: "Unable to dry run extrinsic.".into(), - data: Some( - format!("Could not find `BlockBuilder` api for block `{:?}`.", at).into(), - ), - })?; - - let result = if api_version < 6 { - #[allow(deprecated)] - api.apply_extrinsic_before_version_6(&at, uxt) - .map(legacy::byte_sized_error::convert_to_latest) - .map_err(|e| RpcError { - code: ErrorCode::ServerError(Error::RuntimeError.into()), - message: "Unable to dry run extrinsic.".into(), - data: Some(e.to_string().into()), - })? - } else { - api.apply_extrinsic(&at, uxt).map_err(|e| RpcError { - code: ErrorCode::ServerError(Error::RuntimeError.into()), - message: "Unable to dry run extrinsic.".into(), - data: Some(e.to_string().into()), - })? - }; + let api_version = api + .api_version::>(&at) + .map_err(|e| { + CallError::Custom(ErrorObject::owned( + Error::RuntimeError.into(), + "Unable to dry run extrinsic.", + Some(e.to_string()), + )) + })? + .ok_or_else(|| { + CallError::Custom(ErrorObject::owned( + Error::RuntimeError.into(), + "Unable to dry run extrinsic.", + Some(format!("Could not find `BlockBuilder` api for block `{:?}`.", at)), + )) + })?; - Ok(Encode::encode(&result).into()) + let result = if api_version < 6 { + #[allow(deprecated)] + api.apply_extrinsic_before_version_6(&at, uxt) + .map(legacy::byte_sized_error::convert_to_latest) + .map_err(|e| { + CallError::Custom(ErrorObject::owned( + Error::RuntimeError.into(), + "Unable to dry run extrinsic.", + Some(e.to_string()), + )) + })? + } else { + api.apply_extrinsic(&at, uxt).map_err(|e| { + CallError::Custom(ErrorObject::owned( + Error::RuntimeError.into(), + "Unable to dry run extrinsic.", + Some(e.to_string()), + )) + })? }; - let res = dry_run(); - - async move { res }.boxed() + Ok(Encode::encode(&result).into()) } } @@ -220,7 +215,9 @@ where mod tests { use super::*; + use assert_matches::assert_matches; use futures::executor::block_on; + use jsonrpsee::{core::Error as JsonRpseeError, types::error::CallError}; use sc_transaction_pool::BasicPool; use sp_runtime::{ transaction_validity::{InvalidTransaction, TransactionValidityError}, @@ -228,8 +225,8 @@ mod tests { }; use substrate_test_runtime_client::{runtime::Transfer, AccountKeyring}; - #[test] - fn should_return_next_nonce_for_some_account() { + #[tokio::test] + async fn should_return_next_nonce_for_some_account() { sp_tracing::try_init_simple(); // given @@ -254,17 +251,17 @@ mod tests { let ext1 = new_transaction(1); block_on(pool.submit_one(&BlockId::number(0), source, ext1)).unwrap(); - let accounts = FullSystem::new(client, pool, DenyUnsafe::Yes); + let accounts = SystemRpc::new(client, pool, DenyUnsafe::Yes); // when - let nonce = accounts.nonce(AccountKeyring::Alice.into()); + let nonce = accounts.nonce(AccountKeyring::Alice.into()).await; // then - assert_eq!(block_on(nonce).unwrap(), 2); + assert_eq!(nonce.unwrap(), 2); } - #[test] - fn dry_run_should_deny_unsafe() { + #[tokio::test] + async fn dry_run_should_deny_unsafe() { sp_tracing::try_init_simple(); // given @@ -273,17 +270,17 @@ mod tests { let pool = BasicPool::new_full(Default::default(), true.into(), None, spawner, client.clone()); - let accounts = FullSystem::new(client, pool, DenyUnsafe::Yes); + let accounts = SystemRpc::new(client, pool, DenyUnsafe::Yes); // when - let res = accounts.dry_run(vec![].into(), None); - - // then - assert_eq!(block_on(res), Err(sc_rpc_api::UnsafeRpcError.into())); + let res = accounts.dry_run(vec![].into(), None).await; + assert_matches!(res, Err(JsonRpseeError::Call(CallError::Custom(e))) => { + assert!(e.message().contains("RPC call is unsafe to be called externally")); + }); } - #[test] - fn dry_run_should_work() { + #[tokio::test] + async fn dry_run_should_work() { sp_tracing::try_init_simple(); // given @@ -292,7 +289,7 @@ mod tests { let pool = BasicPool::new_full(Default::default(), true.into(), None, spawner, client.clone()); - let accounts = FullSystem::new(client, pool, DenyUnsafe::No); + let accounts = SystemRpc::new(client, pool, DenyUnsafe::No); let tx = Transfer { from: AccountKeyring::Alice.into(), @@ -303,16 +300,15 @@ mod tests { .into_signed_tx(); // when - let res = accounts.dry_run(tx.encode().into(), None); + let bytes = accounts.dry_run(tx.encode().into(), None).await.expect("Call is successful"); // then - let bytes = block_on(res).unwrap().0; - let apply_res: ApplyExtrinsicResult = Decode::decode(&mut bytes.as_slice()).unwrap(); + let apply_res: ApplyExtrinsicResult = Decode::decode(&mut bytes.as_ref()).unwrap(); assert_eq!(apply_res, Ok(Ok(()))); } - #[test] - fn dry_run_should_indicate_error() { + #[tokio::test] + async fn dry_run_should_indicate_error() { sp_tracing::try_init_simple(); // given @@ -321,7 +317,7 @@ mod tests { let pool = BasicPool::new_full(Default::default(), true.into(), None, spawner, client.clone()); - let accounts = FullSystem::new(client, pool, DenyUnsafe::No); + let accounts = SystemRpc::new(client, pool, DenyUnsafe::No); let tx = Transfer { from: AccountKeyring::Alice.into(), @@ -332,11 +328,10 @@ mod tests { .into_signed_tx(); // when - let res = accounts.dry_run(tx.encode().into(), None); + let bytes = accounts.dry_run(tx.encode().into(), None).await.expect("Call is successful"); // then - let bytes = block_on(res).unwrap().0; - let apply_res: ApplyExtrinsicResult = Decode::decode(&mut bytes.as_slice()).unwrap(); + let apply_res: ApplyExtrinsicResult = Decode::decode(&mut bytes.as_ref()).unwrap(); assert_eq!(apply_res, Err(TransactionValidityError::Invalid(InvalidTransaction::Stale))); } } diff --git a/utils/frame/try-runtime/cli/Cargo.toml b/utils/frame/try-runtime/cli/Cargo.toml index 2c0a2787b1dac..a5e658fc68476 100644 --- a/utils/frame/try-runtime/cli/Cargo.toml +++ b/utils/frame/try-runtime/cli/Cargo.toml @@ -14,12 +14,12 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] clap = { version = "3.1.6", features = ["derive"] } -jsonrpsee = { version = "0.10.1", default-features = false, features = ["ws-client"] } log = "0.4.16" parity-scale-codec = "3.0.0" serde = "1.0.136" zstd = { version = "0.10.0", default-features = false } remote-externalities = { version = "0.10.0-dev", path = "../../remote-externalities" } +jsonrpsee = { version = "0.12.0", default-features = false, features = ["ws-client"] } sc-chain-spec = { version = "4.0.0-dev", path = "../../../../client/chain-spec" } sc-cli = { version = "0.10.0-dev", path = "../../../../client/cli" } sc-executor = { version = "0.10.0-dev", path = "../../../../client/executor" } From 213a280a117f95459cdbd5cd716bc06a991b666e Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Tue, 10 May 2022 15:37:51 +0200 Subject: [PATCH 204/484] Sanity check for Babe's configuration (#11385) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Prevent div by zero in native babe code * Additional sanity check for babe config * Further sanity checks and postpone threshold computation * Apply suggestions from code review Co-authored-by: Bastian Köcher --- client/consensus/babe/src/authorship.rs | 16 ++++++++++------ frame/babe/src/lib.rs | 15 +++++++++++++-- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/client/consensus/babe/src/authorship.rs b/client/consensus/babe/src/authorship.rs index 7a9b09495cd6f..43df26a9a29ae 100644 --- a/client/consensus/babe/src/authorship.rs +++ b/client/consensus/babe/src/authorship.rs @@ -41,6 +41,14 @@ pub(super) fn calculate_primary_threshold( use num_rational::BigRational; use num_traits::{cast::ToPrimitive, identities::One}; + // Prevent div by zero and out of bounds access. + // While Babe's pallet implementation that ships with FRAME performs a sanity check over + // configuration parameters, this is not sufficient to guarantee that `c.1` is non-zero + // (i.e. third party implementations are possible). + if c.1 == 0 || authority_index >= authorities.len() { + return 0 + } + let c = c.0 as f64 / c.1 as f64; let theta = authorities[authority_index].1 as f64 / @@ -235,12 +243,6 @@ fn claim_primary_slot( for (authority_id, authority_index) in keys { let transcript = make_transcript(randomness, slot, *epoch_index); let transcript_data = make_transcript_data(randomness, slot, *epoch_index); - // Compute the threshold we will use. - // - // We already checked that authorities contains `key.public()`, so it can't - // be empty. Therefore, this division in `calculate_threshold` is safe. - let threshold = calculate_primary_threshold(c, authorities, *authority_index); - let result = SyncCryptoStore::sr25519_vrf_sign( &**keystore, AuthorityId::ID, @@ -253,6 +255,8 @@ fn claim_primary_slot( Ok(inout) => inout, Err(_) => continue, }; + + let threshold = calculate_primary_threshold(c, authorities, *authority_index); if check_primary_threshold(&inout, threshold) { let pre_digest = PreDigest::Primary(PrimaryPreDigest { slot, diff --git a/frame/babe/src/lib.rs b/frame/babe/src/lib.rs index e578f0695ac7c..1effc2c1989fa 100644 --- a/frame/babe/src/lib.rs +++ b/frame/babe/src/lib.rs @@ -24,6 +24,7 @@ use codec::{Decode, Encode}; use frame_support::{ dispatch::DispatchResultWithPostInfo, + ensure, traits::{ ConstU32, DisabledValidators, FindAuthor, Get, KeyOwnerProofSystem, OnTimestampSet, OneSessionHandler, @@ -42,8 +43,8 @@ use sp_std::prelude::*; use sp_consensus_babe::{ digests::{NextConfigDescriptor, NextEpochDescriptor, PreDigest}, - BabeAuthorityWeight, BabeEpochConfiguration, ConsensusLog, Epoch, EquivocationProof, Slot, - BABE_ENGINE_ID, + AllowedSlots, BabeAuthorityWeight, BabeEpochConfiguration, ConsensusLog, Epoch, + EquivocationProof, Slot, BABE_ENGINE_ID, }; use sp_consensus_vrf::schnorrkel; @@ -185,6 +186,8 @@ pub mod pallet { InvalidKeyOwnershipProof, /// A given equivocation report is valid but already previously reported. DuplicateOffenceReport, + /// Submitted configuration is invalid. + InvalidConfiguration, } /// Current epoch index. @@ -447,6 +450,14 @@ pub mod pallet { config: NextConfigDescriptor, ) -> DispatchResult { ensure_root(origin)?; + match config { + NextConfigDescriptor::V1 { c, allowed_slots } => { + ensure!( + (c.0 != 0 || allowed_slots != AllowedSlots::PrimarySlots) && c.1 != 0, + Error::::InvalidConfiguration + ); + }, + } PendingEpochConfigChange::::put(config); Ok(()) } From e49b819e25ed44ff255365d93577b336d0d6b6da Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Tue, 10 May 2022 16:06:23 +0200 Subject: [PATCH 205/484] Refactory of Fork-Tree data structure (#11228) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Iterative version of some fork-tree methods * Prune doesn't require its generic args to be 'clone' * Fork-tree import and drain-filter iterative implementations * Fork-tree map iterative implementation * Optimization of some operations, e.g. rebalance only when required * Destructuring assignments not supported yet by stable rustc 1.57 * Safe implementation of 'map' and 'drain_filter' methods * Remove dummy comment * Trigger CI pipeline * Test map for multi-root fork-tree and refactory of `find_node_index_where` * Fix find node index with predicate * Nits * Tree traversal algorithm is not specified * Move unspecified tree traversal warning to 'map' * Immutable 'drain_filter' predicate * Apply suggestions from code review * Remove double mapping Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com> --- client/finality-grandpa/src/authorities.rs | 4 +- utils/fork-tree/src/lib.rs | 779 ++++++++++----------- 2 files changed, 359 insertions(+), 424 deletions(-) diff --git a/client/finality-grandpa/src/authorities.rs b/client/finality-grandpa/src/authorities.rs index c2d44371986ca..0803e6b3c2931 100644 --- a/client/finality-grandpa/src/authorities.rs +++ b/client/finality-grandpa/src/authorities.rs @@ -229,7 +229,7 @@ where where F: Fn(&H, &H) -> Result, { - let mut filter = |node_hash: &H, node_num: &N, _: &PendingChange| { + let filter = |node_hash: &H, node_num: &N, _: &PendingChange| { if number >= *node_num && (is_descendent_of(node_hash, &hash).unwrap_or_default() || *node_hash == hash) { @@ -245,7 +245,7 @@ where }; // Remove standard changes. - let _ = self.pending_standard_changes.drain_filter(&mut filter); + let _ = self.pending_standard_changes.drain_filter(&filter); // Remove forced changes. self.pending_forced_changes diff --git a/utils/fork-tree/src/lib.rs b/utils/fork-tree/src/lib.rs index b4985b77294b2..45957f4390532 100644 --- a/utils/fork-tree/src/lib.rs +++ b/utils/fork-tree/src/lib.rs @@ -81,6 +81,7 @@ pub enum FilterAction { } /// A tree data structure that stores several nodes across multiple branches. +/// /// Top-level branches are called roots. The tree has functionality for /// finalizing nodes, which means that that node is traversed, and all competing /// branches are pruned. It also guarantees that nodes in the tree are finalized @@ -93,90 +94,6 @@ pub struct ForkTree { best_finalized_number: Option, } -impl ForkTree -where - H: PartialEq + Clone, - N: Ord + Clone, - V: Clone, -{ - /// Prune the tree, removing all non-canonical nodes. We find the node in the - /// tree that is the deepest ancestor of the given hash and that passes the - /// given predicate. If such a node exists, we re-root the tree to this - /// node. Otherwise the tree remains unchanged. The given function - /// `is_descendent_of` should return `true` if the second hash (target) is a - /// descendent of the first hash (base). - /// - /// Returns all pruned node data. - pub fn prune( - &mut self, - hash: &H, - number: &N, - is_descendent_of: &F, - predicate: &P, - ) -> Result, Error> - where - E: std::error::Error, - F: Fn(&H, &H) -> Result, - P: Fn(&V) -> bool, - { - let new_root_index = - self.find_node_index_where(hash, number, is_descendent_of, predicate)?; - - let removed = if let Some(mut root_index) = new_root_index { - let mut old_roots = std::mem::take(&mut self.roots); - - let mut root = None; - let mut cur_children = Some(&mut old_roots); - - while let Some(cur_index) = root_index.pop() { - if let Some(children) = cur_children.take() { - if root_index.is_empty() { - root = Some(children.remove(cur_index)); - } else { - cur_children = Some(&mut children[cur_index].children); - } - } - } - - let mut root = root.expect( - "find_node_index_where will return array with at least one index; \ - this results in at least one item in removed; qed", - ); - - let mut removed = old_roots; - - // we found the deepest ancestor of the finalized block, so we prune - // out any children that don't include the finalized block. - let root_children = std::mem::take(&mut root.children); - let mut is_first = true; - - for child in root_children { - if is_first && - (child.number == *number && child.hash == *hash || - child.number < *number && is_descendent_of(&child.hash, hash)?) - { - root.children.push(child); - // assuming that the tree is well formed only one child should pass this - // requirement due to ancestry restrictions (i.e. they must be different forks). - is_first = false; - } else { - removed.push(child); - } - } - - self.roots = vec![root]; - - removed - } else { - Vec::new() - }; - - self.rebalance(); - - Ok(RemovedIterator { stack: removed }) - } -} - impl ForkTree where H: PartialEq, @@ -187,19 +104,19 @@ where ForkTree { roots: Vec::new(), best_finalized_number: None } } - /// Rebalance the tree, i.e. sort child nodes by max branch depth - /// (decreasing). + /// Rebalance the tree, i.e. sort child nodes by max branch depth (decreasing). /// /// Most operations in the tree are performed with depth-first search /// starting from the leftmost node at every level, since this tree is meant /// to be used in a blockchain context, a good heuristic is that the node - /// we'll be looking - /// for at any point will likely be in one of the deepest chains (i.e. the - /// longest ones). + /// we'll be looking for at any point will likely be in one of the deepest chains + /// (i.e. the longest ones). pub fn rebalance(&mut self) { self.roots.sort_by_key(|n| Reverse(n.max_depth())); - for root in &mut self.roots { - root.rebalance(); + let mut stack: Vec<_> = self.roots.iter_mut().collect(); + while let Some(node) = stack.pop() { + node.children.sort_by_key(|n| Reverse(n.max_depth())); + stack.extend(node.children.iter_mut()); } } @@ -211,9 +128,9 @@ where /// Returns `true` if the imported node is a root. pub fn import( &mut self, - mut hash: H, - mut number: N, - mut data: V, + hash: H, + number: N, + data: V, is_descendent_of: &F, ) -> Result> where @@ -226,29 +143,33 @@ where } } - for root in self.roots.iter_mut() { - if root.hash == hash { + let mut children = &mut self.roots; + let mut i = 0; + while i < children.len() { + let child = &children[i]; + if child.hash == hash { return Err(Error::Duplicate) } - - match root.import(hash, number, data, is_descendent_of)? { - Some((h, n, d)) => { - hash = h; - number = n; - data = d; - }, - None => { - self.rebalance(); - return Ok(false) - }, + if child.number < number && is_descendent_of(&child.hash, &hash)? { + children = &mut children[i].children; + i = 0; + } else { + i += 1; } } - self.roots.push(Node { data, hash, number, children: Vec::new() }); + let is_first = children.is_empty(); + children.push(Node { data, hash, number, children: Default::default() }); - self.rebalance(); + // Quick way to check if the pushed node is a root + let is_root = children.as_ptr() == self.roots.as_ptr(); + + if is_first { + // Rebalance is required only if we've extended the branch depth. + self.rebalance(); + } - Ok(true) + Ok(is_root) } /// Iterates over the existing roots in the tree. @@ -267,6 +188,53 @@ where self.node_iter().map(|node| (&node.hash, &node.number, &node.data)) } + /// Map fork tree into values of new types. + /// + /// Tree traversal technique (e.g. BFS vs DFS) is left as not specified and + /// may be subject to change in the future. In other words, your predicates + /// should not rely on the observed traversal technique currently in use. + pub fn map(self, f: &mut F) -> ForkTree + where + F: FnMut(&H, &N, V) -> VT, + { + let mut queue: Vec<_> = + self.roots.into_iter().rev().map(|node| (usize::MAX, node)).collect(); + let mut next_queue = Vec::new(); + let mut output = Vec::new(); + + while !queue.is_empty() { + for (parent_index, node) in queue.drain(..) { + let new_data = f(&node.hash, &node.number, node.data); + let new_node = Node { + hash: node.hash, + number: node.number, + data: new_data, + children: Vec::with_capacity(node.children.len()), + }; + + let node_id = output.len(); + output.push((parent_index, new_node)); + + for child in node.children.into_iter().rev() { + next_queue.push((node_id, child)); + } + } + + std::mem::swap(&mut queue, &mut next_queue); + } + + let mut roots = Vec::new(); + while let Some((parent_index, new_node)) = output.pop() { + if parent_index == usize::MAX { + roots.push(new_node); + } else { + output[parent_index].1.children.push(new_node); + } + } + + ForkTree { roots, best_finalized_number: self.best_finalized_number } + } + /// Find a node in the tree that is the deepest ancestor of the given /// block hash and which passes the given predicate. The given function /// `is_descendent_of` should return `true` if the second hash (target) @@ -283,27 +251,12 @@ where F: Fn(&H, &H) -> Result, P: Fn(&V) -> bool, { - // search for node starting from all roots - for root in self.roots.iter() { - let node = root.find_node_where(hash, number, is_descendent_of, predicate)?; - - // found the node, early exit - if let FindOutcome::Found(node) = node { - return Ok(Some(node)) - } - } - - Ok(None) - } - - /// Map fork tree into values of new types. - pub fn map(self, f: &mut F) -> ForkTree - where - F: FnMut(&H, &N, V) -> VT, - { - let roots = self.roots.into_iter().map(|root| root.map(f)).collect(); - - ForkTree { roots, best_finalized_number: self.best_finalized_number } + let maybe_path = self.find_node_index_where(hash, number, is_descendent_of, predicate)?; + Ok(maybe_path.map(|path| { + let children = + path.iter().take(path.len() - 1).fold(&self.roots, |curr, &i| &curr[i].children); + &children[path[path.len() - 1]] + })) } /// Same as [`find_node_where`](ForkTree::find_node_where), but returns mutable reference. @@ -319,44 +272,121 @@ where F: Fn(&H, &H) -> Result, P: Fn(&V) -> bool, { - // search for node starting from all roots - for root in self.roots.iter_mut() { - let node = root.find_node_where_mut(hash, number, is_descendent_of, predicate)?; + let maybe_path = self.find_node_index_where(hash, number, is_descendent_of, predicate)?; + Ok(maybe_path.map(|path| { + let children = path + .iter() + .take(path.len() - 1) + .fold(&mut self.roots, |curr, &i| &mut curr[i].children); + &mut children[path[path.len() - 1]] + })) + } - // found the node, early exit - if let FindOutcome::Found(node) = node { - return Ok(Some(node)) + /// Same as [`find_node_where`](ForkTree::find_node_where), but returns indices. + /// + /// The returned indices represent the full path to reach the matching node starting + /// from first to last, i.e. the earliest index in the traverse path goes first, and the final + /// index in the traverse path goes last. If a node is found that matches the predicate + /// the returned path should always contain at least one index, otherwise `None` is + /// returned. + pub fn find_node_index_where( + &self, + hash: &H, + number: &N, + is_descendent_of: &F, + predicate: &P, + ) -> Result>, Error> + where + E: std::error::Error, + F: Fn(&H, &H) -> Result, + P: Fn(&V) -> bool, + { + let mut path = vec![]; + let mut children = &self.roots; + let mut i = 0; + let mut best_depth = 0; + + while i < children.len() { + let node = &children[i]; + if node.number < *number && is_descendent_of(&node.hash, hash)? { + path.push(i); + if predicate(&node.data) { + best_depth = path.len(); + } + i = 0; + children = &node.children; + } else { + i += 1; } } - Ok(None) + Ok(if best_depth == 0 { + None + } else { + path.truncate(best_depth); + Some(path) + }) } - /// Same as [`find_node_where`](ForkTree::find_node_where), but returns indexes. - pub fn find_node_index_where( - &self, + /// Prune the tree, removing all non-canonical nodes. We find the node in the + /// tree that is the deepest ancestor of the given hash and that passes the + /// given predicate. If such a node exists, we re-root the tree to this + /// node. Otherwise the tree remains unchanged. The given function + /// `is_descendent_of` should return `true` if the second hash (target) is a + /// descendent of the first hash (base). + /// + /// Returns all pruned node data. + pub fn prune( + &mut self, hash: &H, number: &N, is_descendent_of: &F, predicate: &P, - ) -> Result>, Error> + ) -> Result, Error> where E: std::error::Error, F: Fn(&H, &H) -> Result, P: Fn(&V) -> bool, { - // search for node starting from all roots - for (index, root) in self.roots.iter().enumerate() { - let node = root.find_node_index_where(hash, number, is_descendent_of, predicate)?; - - // found the node, early exit - if let FindOutcome::Found(mut node) = node { - node.push(index); - return Ok(Some(node)) + let root_index = + match self.find_node_index_where(hash, number, is_descendent_of, predicate)? { + Some(idx) => idx, + None => return Ok(RemovedIterator { stack: Vec::new() }), + }; + + let mut old_roots = std::mem::take(&mut self.roots); + + let curr_children = root_index + .iter() + .take(root_index.len() - 1) + .fold(&mut old_roots, |curr, idx| &mut curr[*idx].children); + let mut root = curr_children.remove(root_index[root_index.len() - 1]); + + let mut removed = old_roots; + + // we found the deepest ancestor of the finalized block, so we prune + // out any children that don't include the finalized block. + let root_children = std::mem::take(&mut root.children); + let mut is_first = true; + + for child in root_children { + if is_first && + (child.number == *number && child.hash == *hash || + child.number < *number && is_descendent_of(&child.hash, hash)?) + { + root.children.push(child); + // assuming that the tree is well formed only one child should pass this + // requirement due to ancestry restrictions (i.e. they must be different forks). + is_first = false; + } else { + removed.push(child); } } - Ok(None) + self.roots = vec![root]; + self.rebalance(); + + Ok(RemovedIterator { stack: removed }) } /// Finalize a root in the tree and return it, return `None` in case no root @@ -638,44 +668,71 @@ where } /// Remove from the tree some nodes (and their subtrees) using a `filter` predicate. + /// /// The `filter` is called over tree nodes and returns a filter action: /// - `Remove` if the node and its subtree should be removed; /// - `KeepNode` if we should maintain the node and keep processing the tree. /// - `KeepTree` if we should maintain the node and its entire subtree. + /// /// An iterator over all the pruned nodes is returned. - pub fn drain_filter(&mut self, mut filter: F) -> impl Iterator + pub fn drain_filter(&mut self, filter: F) -> impl Iterator where - F: FnMut(&H, &N, &V) -> FilterAction, + F: Fn(&H, &N, &V) -> FilterAction, { - let mut removed = Vec::new(); - let mut i = 0; - while i < self.roots.len() { - if self.roots[i].drain_filter(&mut filter, &mut removed) { - removed.push(self.roots.remove(i)); + let mut removed = vec![]; + let mut retained = Vec::new(); + + let mut queue: Vec<_> = std::mem::take(&mut self.roots) + .into_iter() + .rev() + .map(|node| (usize::MAX, node)) + .collect(); + let mut next_queue = Vec::new(); + + while !queue.is_empty() { + for (parent_idx, mut node) in queue.drain(..) { + match filter(&node.hash, &node.number, &node.data) { + FilterAction::KeepNode => { + let node_idx = retained.len(); + let children = std::mem::take(&mut node.children); + retained.push((parent_idx, node)); + for child in children.into_iter().rev() { + next_queue.push((node_idx, child)); + } + }, + FilterAction::KeepTree => { + retained.push((parent_idx, node)); + }, + FilterAction::Remove => { + removed.push(node); + }, + } + } + + std::mem::swap(&mut queue, &mut next_queue); + } + + while let Some((parent_idx, node)) = retained.pop() { + if parent_idx == usize::MAX { + self.roots.push(node); } else { - i += 1; + retained[parent_idx].1.children.push(node); } } - self.rebalance(); + + if !removed.is_empty() { + self.rebalance(); + } RemovedIterator { stack: removed } } } // Workaround for: https://github.com/rust-lang/rust/issues/34537 +use node_implementation::Node; + mod node_implementation { use super::*; - /// The outcome of a search within a node. - pub enum FindOutcome { - // this is the node we were looking for. - Found(T), - // not the node we're looking for. contains a flag indicating - // whether the node was a descendent. true implies the predicate failed. - Failure(bool), - // Abort search. - Abort, - } - #[derive(Clone, Debug, Decode, Encode, PartialEq)] pub struct Node { pub hash: H, @@ -685,239 +742,21 @@ mod node_implementation { } impl Node { - /// Rebalance the tree, i.e. sort child nodes by max branch depth (decreasing). - pub fn rebalance(&mut self) { - self.children.sort_by_key(|n| Reverse(n.max_depth())); - for child in &mut self.children { - child.rebalance(); - } - } - /// Finds the max depth among all branches descendent from this node. pub fn max_depth(&self) -> usize { - let mut max = 0; - - for node in &self.children { - max = node.max_depth().max(max) - } - - max + 1 - } - - /// Map node data into values of new types. - pub fn map(self, f: &mut F) -> Node - where - F: FnMut(&H, &N, V) -> VT, - { - let children = self.children.into_iter().map(|node| node.map(f)).collect(); - - let vt = f(&self.hash, &self.number, self.data); - Node { hash: self.hash, number: self.number, data: vt, children } - } - - pub fn import( - &mut self, - mut hash: H, - mut number: N, - mut data: V, - is_descendent_of: &F, - ) -> Result, Error> - where - E: fmt::Debug, - F: Fn(&H, &H) -> Result, - { - if self.hash == hash { - return Err(Error::Duplicate) - }; - - if number <= self.number { - return Ok(Some((hash, number, data))) - } - - for node in self.children.iter_mut() { - match node.import(hash, number, data, is_descendent_of)? { - Some((h, n, d)) => { - hash = h; - number = n; - data = d; - }, - None => return Ok(None), + let mut max: usize = 0; + let mut stack = vec![(self, 0)]; + while let Some((node, height)) = stack.pop() { + if height > max { + max = height; } + node.children.iter().for_each(|n| stack.push((n, height + 1))); } - - if is_descendent_of(&self.hash, &hash)? { - self.children.push(Node { data, hash, number, children: Vec::new() }); - - Ok(None) - } else { - Ok(Some((hash, number, data))) - } - } - - /// Find a node in the tree that is the deepest ancestor of the given - /// block hash which also passes the given predicate, backtracking - /// when the predicate fails. - /// The given function `is_descendent_of` should return `true` if the second hash (target) - /// is a descendent of the first hash (base). - /// - /// The returned indices are from last to first. The earliest index in the traverse path - /// goes last, and the final index in the traverse path goes first. An empty list means - /// that the current node is the result. - pub fn find_node_index_where( - &self, - hash: &H, - number: &N, - is_descendent_of: &F, - predicate: &P, - ) -> Result>, Error> - where - E: std::error::Error, - F: Fn(&H, &H) -> Result, - P: Fn(&V) -> bool, - { - // stop searching this branch - if *number < self.number { - return Ok(FindOutcome::Failure(false)) - } - - let mut known_descendent_of = false; - - // continue depth-first search through all children - for (i, node) in self.children.iter().enumerate() { - // found node, early exit - match node.find_node_index_where(hash, number, is_descendent_of, predicate)? { - FindOutcome::Abort => return Ok(FindOutcome::Abort), - FindOutcome::Found(mut x) => { - x.push(i); - return Ok(FindOutcome::Found(x)) - }, - FindOutcome::Failure(true) => { - // if the block was a descendent of this child, - // then it cannot be a descendent of any others, - // so we don't search them. - known_descendent_of = true; - break - }, - FindOutcome::Failure(false) => {}, - } - } - - // node not found in any of the descendents, if the node we're - // searching for is a descendent of this node then we will stop the - // search here, since there aren't any more children and we found - // the correct node so we don't want to backtrack. - let is_descendent_of = known_descendent_of || is_descendent_of(&self.hash, hash)?; - if is_descendent_of { - // if the predicate passes we return the node - if predicate(&self.data) { - return Ok(FindOutcome::Found(Vec::new())) - } - } - - // otherwise, tell our ancestor that we failed, and whether - // the block was a descendent. - Ok(FindOutcome::Failure(is_descendent_of)) - } - - /// Find a node in the tree that is the deepest ancestor of the given - /// block hash which also passes the given predicate, backtracking - /// when the predicate fails. - /// The given function `is_descendent_of` should return `true` if the second hash (target) - /// is a descendent of the first hash (base). - pub fn find_node_where( - &self, - hash: &H, - number: &N, - is_descendent_of: &F, - predicate: &P, - ) -> Result>, Error> - where - E: std::error::Error, - F: Fn(&H, &H) -> Result, - P: Fn(&V) -> bool, - { - let outcome = self.find_node_index_where(hash, number, is_descendent_of, predicate)?; - - match outcome { - FindOutcome::Abort => Ok(FindOutcome::Abort), - FindOutcome::Failure(f) => Ok(FindOutcome::Failure(f)), - FindOutcome::Found(mut indexes) => { - let mut cur = self; - - while let Some(i) = indexes.pop() { - cur = &cur.children[i]; - } - Ok(FindOutcome::Found(cur)) - }, - } - } - - /// Find a node in the tree that is the deepest ancestor of the given - /// block hash which also passes the given predicate, backtracking - /// when the predicate fails. - /// The given function `is_descendent_of` should return `true` if the second hash (target) - /// is a descendent of the first hash (base). - pub fn find_node_where_mut( - &mut self, - hash: &H, - number: &N, - is_descendent_of: &F, - predicate: &P, - ) -> Result>, Error> - where - E: std::error::Error, - F: Fn(&H, &H) -> Result, - P: Fn(&V) -> bool, - { - let outcome = self.find_node_index_where(hash, number, is_descendent_of, predicate)?; - - match outcome { - FindOutcome::Abort => Ok(FindOutcome::Abort), - FindOutcome::Failure(f) => Ok(FindOutcome::Failure(f)), - FindOutcome::Found(mut indexes) => { - let mut cur = self; - - while let Some(i) = indexes.pop() { - cur = &mut cur.children[i]; - } - Ok(FindOutcome::Found(cur)) - }, - } - } - - /// Calls a `filter` predicate for the given node. - /// The `filter` is called over tree nodes and returns a filter action: - /// - `Remove` if the node and its subtree should be removed; - /// - `KeepNode` if we should maintain the node and keep processing the tree; - /// - `KeepTree` if we should maintain the node and its entire subtree. - /// Pruned subtrees are added to the `removed` list. - /// Returns a booleans indicateing if this node (and its subtree) should be removed. - pub fn drain_filter(&mut self, filter: &mut F, removed: &mut Vec>) -> bool - where - F: FnMut(&H, &N, &V) -> FilterAction, - { - match filter(&self.hash, &self.number, &self.data) { - FilterAction::KeepNode => { - let mut i = 0; - while i < self.children.len() { - if self.children[i].drain_filter(filter, removed) { - removed.push(self.children.remove(i)); - } else { - i += 1; - } - } - false - }, - FilterAction::KeepTree => false, - FilterAction::Remove => true, - } + max } } } -// Workaround for: https://github.com/rust-lang/rust/issues/34537 -use node_implementation::{FindOutcome, Node}; - struct ForkTreeIterator<'a, H, N, V> { stack: Vec<&'a Node>, } @@ -985,7 +824,7 @@ mod test { // / / // A - F - H - I // \ \ - // \ - L - M + // \ - L - M - N // \ \ // \ - O // - J - K @@ -996,7 +835,7 @@ mod test { // diagram above. the children will be ordered by subtree depth and the longest branches // will be on the leftmost side of the tree. let is_descendent_of = |base: &&str, block: &&str| -> Result { - let letters = vec!["B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "O"]; + let letters = vec!["B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O"]; match (*base, *block) { ("A", b) => Ok(letters.into_iter().any(|n| n == b)), ("B", b) => Ok(b == "C" || b == "D" || b == "E"), @@ -1004,14 +843,14 @@ mod test { ("D", b) => Ok(b == "E"), ("E", _) => Ok(false), ("F", b) => - Ok(b == "G" || b == "H" || b == "I" || b == "L" || b == "M" || b == "O"), + Ok(b == "G" || b == "H" || b == "I" || b == "L" || b == "M" || b == "N" || b == "O"), ("G", _) => Ok(false), - ("H", b) => Ok(b == "I" || b == "L" || b == "M" || b == "O"), + ("H", b) => Ok(b == "I" || b == "L" || b == "M" || b == "N" || b == "O"), ("I", _) => Ok(false), ("J", b) => Ok(b == "K"), ("K", _) => Ok(false), - ("L", b) => Ok(b == "M" || b == "O"), - ("M", _) => Ok(false), + ("L", b) => Ok(b == "M" || b == "O" || b == "N"), + ("M", b) => Ok(b == "N"), ("O", _) => Ok(false), ("0", _) => Ok(true), _ => Ok(false), @@ -1237,7 +1076,7 @@ mod test { // // Nodes B, C, F and G are not part of the tree. match (*base, *block) { - ("A0", b) => Ok(b == "B" || b == "C" || b == "D" || b == "G"), + ("A0", b) => Ok(b == "B" || b == "C" || b == "D" || b == "E" || b == "G"), ("A1", _) => Ok(false), ("C", b) => Ok(b == "D"), ("D", b) => Ok(b == "E" || b == "F" || b == "G"), @@ -1246,10 +1085,16 @@ mod test { } }; - tree.import("A0", 1, Change { effective: 5 }, &is_descendent_of).unwrap(); - tree.import("A1", 1, Change { effective: 5 }, &is_descendent_of).unwrap(); - tree.import("D", 10, Change { effective: 10 }, &is_descendent_of).unwrap(); - tree.import("E", 15, Change { effective: 50 }, &is_descendent_of).unwrap(); + let is_root = tree.import("A0", 1, Change { effective: 5 }, &is_descendent_of).unwrap(); + assert!(is_root); + let is_root = tree.import("A1", 1, Change { effective: 5 }, &is_descendent_of).unwrap(); + assert!(is_root); + let is_root = + tree.import("D", 10, Change { effective: 10 }, &is_descendent_of).unwrap(); + assert!(!is_root); + let is_root = + tree.import("E", 15, Change { effective: 50 }, &is_descendent_of).unwrap(); + assert!(!is_root); (tree, is_descendent_of) }; @@ -1416,20 +1261,25 @@ mod test { } #[test] - fn find_node_works() { - let (tree, is_descendent_of) = test_fork_tree(); - - let node = tree.find_node_where(&"D", &4, &is_descendent_of, &|_| true).unwrap().unwrap(); + fn map_works() { + let (mut tree, _) = test_fork_tree(); - assert_eq!(node.hash, "C"); - assert_eq!(node.number, 3); - } + // Extend the single root fork-tree to also excercise the roots order during map. + let is_descendent_of = |_: &&str, _: &&str| -> Result { Ok(false) }; + let is_root = tree.import("A1", 1, (), &is_descendent_of).unwrap(); + assert!(is_root); + let is_root = tree.import("A2", 1, (), &is_descendent_of).unwrap(); + assert!(is_root); - #[test] - fn map_works() { - let (tree, _is_descendent_of) = test_fork_tree(); + let old_tree = tree.clone(); + let new_tree = tree.map(&mut |hash, _, _| hash.to_owned()); - let _tree = tree.map(&mut |_, _, _| ()); + // Check content and order + assert!(new_tree.iter().all(|(hash, _, data)| hash == data)); + assert_eq!( + old_tree.iter().map(|(hash, _, _)| *hash).collect::>(), + new_tree.iter().map(|(hash, _, _)| *hash).collect::>(), + ); } #[test] @@ -1489,7 +1339,7 @@ mod test { } #[test] - fn tree_rebalance() { + fn rebalance_works() { let (mut tree, _) = test_fork_tree(); // the tree is automatically rebalanced on import, therefore we should iterate in preorder @@ -1503,7 +1353,7 @@ mod test { // let's add a block "P" which is a descendent of block "O" let is_descendent_of = |base: &&str, block: &&str| -> Result { match (*base, *block) { - (b, "P") => Ok(vec!["A", "F", "L", "O"].into_iter().any(|n| n == b)), + (b, "P") => Ok(vec!["A", "F", "H", "L", "O"].into_iter().any(|n| n == b)), _ => Ok(false), } }; @@ -1520,7 +1370,7 @@ mod test { } #[test] - fn tree_drain_filter() { + fn drain_filter_works() { let (mut tree, _) = test_fork_tree(); let filter = |h: &&str, _: &u64, _: &()| match *h { @@ -1539,7 +1389,92 @@ mod test { assert_eq!( removed.map(|(h, _, _)| h).collect::>(), - ["J", "K", "H", "L", "M", "O", "I"] + ["H", "L", "M", "O", "I", "J", "K"] ); } + + #[test] + fn find_node_index_works() { + let (tree, is_descendent_of) = test_fork_tree(); + + let path = tree + .find_node_index_where(&"D", &4, &is_descendent_of, &|_| true) + .unwrap() + .unwrap(); + assert_eq!(path, [0, 0, 0]); + + let path = tree + .find_node_index_where(&"O", &5, &is_descendent_of, &|_| true) + .unwrap() + .unwrap(); + assert_eq!(path, [0, 1, 0, 0]); + + let path = tree + .find_node_index_where(&"N", &6, &is_descendent_of, &|_| true) + .unwrap() + .unwrap(); + assert_eq!(path, [0, 1, 0, 0, 0]); + } + + #[test] + fn find_node_index_with_predicate_works() { + fn is_descendent_of(parent: &char, child: &char) -> Result { + match *parent { + 'A' => Ok(['B', 'C', 'D', 'E', 'F'].contains(child)), + 'B' => Ok(['C', 'D'].contains(child)), + 'C' => Ok(['D'].contains(child)), + 'E' => Ok(['F'].contains(child)), + 'D' | 'F' => Ok(false), + _ => unreachable!(), + } + } + + // A(t) --- B(f) --- C(t) --- D(f) + // \-- E(t) --- F(f) + let mut tree: ForkTree = ForkTree::new(); + tree.import('A', 1, true, &is_descendent_of).unwrap(); + tree.import('B', 2, false, &is_descendent_of).unwrap(); + tree.import('C', 3, true, &is_descendent_of).unwrap(); + tree.import('D', 4, false, &is_descendent_of).unwrap(); + + tree.import('E', 2, true, &is_descendent_of).unwrap(); + tree.import('F', 3, false, &is_descendent_of).unwrap(); + + let path = tree + .find_node_index_where(&'D', &4, &is_descendent_of, &|&value| !value) + .unwrap() + .unwrap(); + assert_eq!(path, [0, 0]); + + let path = tree + .find_node_index_where(&'D', &4, &is_descendent_of, &|&value| value) + .unwrap() + .unwrap(); + assert_eq!(path, [0, 0, 0]); + + let path = tree + .find_node_index_where(&'F', &3, &is_descendent_of, &|&value| !value) + .unwrap(); + assert_eq!(path, None); + + let path = tree + .find_node_index_where(&'F', &3, &is_descendent_of, &|&value| value) + .unwrap() + .unwrap(); + assert_eq!(path, [0, 1]); + } + + #[test] + fn find_node_works() { + let (tree, is_descendent_of) = test_fork_tree(); + + let node = tree.find_node_where(&"D", &4, &is_descendent_of, &|_| true).unwrap().unwrap(); + assert_eq!((node.hash, node.number), ("C", 3)); + + let node = tree.find_node_where(&"O", &5, &is_descendent_of, &|_| true).unwrap().unwrap(); + assert_eq!((node.hash, node.number), ("L", 4)); + + let node = tree.find_node_where(&"N", &6, &is_descendent_of, &|_| true).unwrap().unwrap(); + assert_eq!((node.hash, node.number), ("M", 5)); + } } From 9ac477525a8ab3fba8cec517cacdc421c5f6a716 Mon Sep 17 00:00:00 2001 From: Joshy Orndorff Date: Tue, 10 May 2022 12:18:56 -0400 Subject: [PATCH 206/484] sc-consensus-slots: rename `client` -> `select_chain` (#11375) * rename `client` -> `select_chain` * missed one * `cargo fmt` (only the file I touched) * Missed another one * Revert "`cargo fmt` (only the file I touched)" This reverts commit 96a0c6cd131cdc08013f7ddb9e7bf2af34ae3ee3. * `cargo fmt` (again) (only the LINES I touched) --- client/consensus/slots/src/slots.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/client/consensus/slots/src/slots.rs b/client/consensus/slots/src/slots.rs index a7b9f3e3ff611..accf24b6b4e78 100644 --- a/client/consensus/slots/src/slots.rs +++ b/client/consensus/slots/src/slots.rs @@ -91,33 +91,37 @@ impl SlotInfo { } /// A stream that returns every time there is a new slot. -pub(crate) struct Slots { +pub(crate) struct Slots { last_slot: Slot, slot_duration: Duration, inner_delay: Option, create_inherent_data_providers: IDP, - client: C, + select_chain: SC, _phantom: std::marker::PhantomData, } -impl Slots { +impl Slots { /// Create a new `Slots` stream. - pub fn new(slot_duration: Duration, create_inherent_data_providers: IDP, client: C) -> Self { + pub fn new( + slot_duration: Duration, + create_inherent_data_providers: IDP, + select_chain: SC, + ) -> Self { Slots { last_slot: 0.into(), slot_duration, inner_delay: None, create_inherent_data_providers, - client, + select_chain, _phantom: Default::default(), } } } -impl Slots +impl Slots where Block: BlockT, - C: SelectChain, + SC: SelectChain, IDP: CreateInherentDataProviders, IDP::InherentDataProviders: crate::InherentDataProviderExt, { @@ -145,7 +149,7 @@ where let ends_at = Instant::now() + ends_in; - let chain_head = match self.client.best_chain().await { + let chain_head = match self.select_chain.best_chain().await { Ok(x) => x, Err(e) => { log::warn!( From 8b0534e8785c331ab2a5628aa3e9a88fcbc8cfbe Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 10 May 2022 18:20:42 +0200 Subject: [PATCH 207/484] update Cargo.lock (#11384) --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 928be6b18fc15..1c73e59ff10d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11096,7 +11096,7 @@ dependencies = [ "chrono", "lazy_static", "matchers", - "parking_lot 0.9.0", + "parking_lot 0.11.2", "regex", "serde", "serde_json", From df6995bcab3f084bbef8be4c2693170f5c80555d Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Tue, 10 May 2022 17:34:59 +0100 Subject: [PATCH 208/484] fix a few more things with nomination pools (#11373) * fix a few more things with nomination pools * add bench * use weight fn * cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_nomination_pools --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/nomination-pools/src/weights.rs --template=./.maintain/frame-weight-template.hbs * allow real root to also set roles * Update frame/nomination-pools/src/lib.rs Co-authored-by: Oliver Tale-Yazdi * Update frame/nomination-pools/src/lib.rs Co-authored-by: Oliver Tale-Yazdi * move out of the closure * fix a few more things Co-authored-by: Parity Bot Co-authored-by: Oliver Tale-Yazdi --- .../nomination-pools/benchmarking/src/lib.rs | 22 +++ frame/nomination-pools/src/lib.rs | 81 ++++++++- frame/nomination-pools/src/tests.rs | 165 +++++++++++++++++- frame/nomination-pools/src/weights.rs | 83 +++++---- primitives/staking/src/lib.rs | 14 +- 5 files changed, 310 insertions(+), 55 deletions(-) diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index 7ae6338e6304a..aa4c093dcf0d4 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -631,6 +631,28 @@ frame_benchmarking::benchmarks! { assert_eq!(MaxPoolMembersPerPool::::get(), Some(u32::MAX)); } + update_roles { + let first_id = pallet_nomination_pools::LastPoolId::::get() + 1; + let (root, _) = create_pool_account::(0, CurrencyOf::::minimum_balance() * 2u32.into()); + let random: T::AccountId = account("but is anything really random in computers..?", 0, USER_SEED); + }:_( + Origin::Signed(root.clone()), + first_id, + Some(random.clone()), + Some(random.clone()), + Some(random.clone()) + ) verify { + assert_eq!( + pallet_nomination_pools::BondedPools::::get(first_id).unwrap().roles, + pallet_nomination_pools::PoolRoles { + depositor: root, + nominator: random.clone(), + state_toggler: random.clone(), + root: random, + }, + ) + } + impl_benchmark_test_suite!( Pallet, crate::mock::new_test_ext(), diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 8b26bad6c7df8..017eac8ee2aeb 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -348,7 +348,7 @@ pub const POINTS_TO_BALANCE_INIT_RATIO: u32 = 1; /// Possible operations on the configuration values of this pallet. #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound, PartialEq, Clone)] -pub enum ConfigOp { +pub enum ConfigOp { /// Don't change. Noop, /// Set the given value. @@ -505,11 +505,11 @@ pub enum PoolState { /// Pool adminstration roles. #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, Debug, PartialEq, Clone)] pub struct PoolRoles { - /// Creates the pool and is the initial member. They can only leave the pool once all - /// other members have left. Once they fully leave, the pool is destroyed. + /// Creates the pool and is the initial member. They can only leave the pool once all other + /// members have left. Once they fully leave, the pool is destroyed. pub depositor: AccountId, - /// Can change the nominator, state-toggler, or itself and can perform any of the actions - /// the nominator or state-toggler can. + /// Can change the nominator, state-toggler, or itself and can perform any of the actions the + /// nominator or state-toggler can. pub root: AccountId, /// Can select which validators the pool nominates. pub nominator: AccountId, @@ -665,6 +665,10 @@ impl BondedPool { .saturating_sub(T::StakingInterface::active_stake(&account).unwrap_or_default()) } + fn can_update_roles(&self, who: &T::AccountId) -> bool { + *who == self.roles.root + } + fn can_nominate(&self, who: &T::AccountId) -> bool { *who == self.roles.root || *who == self.roles.nominator } @@ -1141,9 +1145,14 @@ pub mod pallet { pub type Metadata = CountedStorageMap<_, Twox64Concat, PoolId, BoundedVec, ValueQuery>; + /// Ever increasing number of all pools created so far. #[pallet::storage] pub type LastPoolId = StorageValue<_, u32, ValueQuery>; + /// A reverse lookup from the pool's account id to its id. + /// + /// This is only used for slashing. In all other instances, the pool id is used, and the + /// accounts are deterministically derived from it. #[pallet::storage] pub type ReversePoolIdLookup = CountedStorageMap<_, Twox64Concat, T::AccountId, PoolId, OptionQuery>; @@ -1209,6 +1218,8 @@ pub mod pallet { /// /// The removal can be voluntary (withdrawn all unbonded funds) or involuntary (kicked). MemberRemoved { pool_id: PoolId, member: T::AccountId }, + /// The roles of a pool have been updated to the given new roles. + RolesUpdated { root: T::AccountId, state_toggler: T::AccountId, nominator: T::AccountId }, } #[pallet::error] @@ -1436,9 +1447,9 @@ pub mod pallet { bonded_pool.ok_to_unbond_with(&caller, &member_account, &member, unbonding_points)?; - // Claim the the payout prior to unbonding. Once the user is unbonding their points - // no longer exist in the bonded pool and thus they can no longer claim their payouts. - // It is not strictly necessary to claim the rewards, but we do it here for UX. + // Claim the the payout prior to unbonding. Once the user is unbonding their points no + // longer exist in the bonded pool and thus they can no longer claim their payouts. It + // is not strictly necessary to claim the rewards, but we do it here for UX. Self::do_reward_payout( &member_account, &mut member, @@ -1827,6 +1838,60 @@ pub mod pallet { Ok(()) } + + /// Update the roles of the pool. + /// + /// The root is the only entity that can change any of the roles, including itself, + /// excluding the depositor, who can never change. + /// + /// It emits an event, notifying UIs of the role change. This event is quite relevant to + /// most pool members and they should be informed of changes to pool roles. + #[pallet::weight(T::WeightInfo::update_roles())] + pub fn update_roles( + origin: OriginFor, + pool_id: PoolId, + root: Option, + nominator: Option, + state_toggler: Option, + ) -> DispatchResult { + let o1 = origin; + let o2 = o1.clone(); + + let mut bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; + let is_pool_root = || -> Result<(), sp_runtime::DispatchError> { + let who = ensure_signed(o1)?; + ensure!(bonded_pool.can_update_roles(&who), Error::::DoesNotHavePermission); + Ok(()) + }; + let is_root = || -> Result<(), sp_runtime::DispatchError> { + ensure_root(o2)?; + Ok(()) + }; + + let _ = is_root().or_else(|_| is_pool_root())?; + + match root { + None => (), + Some(v) => bonded_pool.roles.root = v, + }; + match nominator { + None => (), + Some(v) => bonded_pool.roles.nominator = v, + }; + match state_toggler { + None => (), + Some(v) => bonded_pool.roles.state_toggler = v, + }; + + Self::deposit_event(Event::::RolesUpdated { + root: bonded_pool.roles.root.clone(), + nominator: bonded_pool.roles.nominator.clone(), + state_toggler: bonded_pool.roles.state_toggler.clone(), + }); + + bonded_pool.put(); + Ok(()) + } } #[pallet::hooks] diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index ecda162c6bdbc..f39b5c92375c4 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -69,7 +69,20 @@ fn test_setup_works() { assert_eq!( PoolMembers::::get(10).unwrap(), PoolMember:: { pool_id: last_pool, points: 10, ..Default::default() } - ) + ); + + let bonded_account = Pools::create_bonded_account(last_pool); + let reward_account = Pools::create_reward_account(last_pool); + + // the bonded_account should be bonded by the depositor's funds. + assert_eq!(StakingMock::active_stake(&bonded_account).unwrap(), 10); + assert_eq!(StakingMock::total_stake(&bonded_account).unwrap(), 10); + + // but not nominating yet. + assert!(Nominations::get().is_empty()); + + // reward account should have an initial ED in it. + assert_eq!(Balances::free_balance(&reward_account), Balances::minimum_balance()); }) } @@ -2082,7 +2095,7 @@ mod unbond { // depositor can unbond inly up to `MinCreateBond`. #[test] fn depositor_permissioned_partial_unbond() { - ExtBuilder::default().ed(1).add_members(vec![(100, 100)]).build_and_execute(|| { + ExtBuilder::default().ed(1).build_and_execute(|| { // given assert_eq!(MinCreateBond::::get(), 2); assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 10); @@ -2098,12 +2111,12 @@ mod unbond { Pools::unbond(Origin::signed(10), 10, 6), Error::::NotOnlyPoolMember ); + assert_eq!( pool_events_since_last_call(), vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, Event::Unbonded { member: 10, pool_id: 1, amount: 3 } ] ); @@ -2113,7 +2126,7 @@ mod unbond { // same as above, but the pool is slashed and therefore the depositor cannot partially unbond. #[test] fn depositor_permissioned_partial_unbond_slashed() { - ExtBuilder::default().ed(1).add_members(vec![(100, 100)]).build_and_execute(|| { + ExtBuilder::default().ed(1).build_and_execute(|| { // given assert_eq!(MinCreateBond::::get(), 2); assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 10); @@ -2132,11 +2145,75 @@ mod unbond { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true } ] ); }); } + + #[test] + fn every_unbonding_triggers_payout() { + ExtBuilder::default().build_and_execute(|| { + let initial_reward_account = Balances::free_balance(Pools::create_reward_account(1)); + assert_eq!(initial_reward_account, Balances::minimum_balance()); + assert_eq!(initial_reward_account, 5); + + // set the pool to destroying so that depositor can leave. + unsafe_set_state(1, PoolState::Destroying).unwrap(); + + Balances::make_free_balance_be( + &Pools::create_reward_account(1), + 2 * Balances::minimum_balance(), + ); + + assert_ok!(Pools::unbond(Origin::signed(10), 10, 2)); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + // exactly equal to ed, all that can be claimed. + Event::PaidOut { member: 10, pool_id: 1, payout: 5 }, + Event::Unbonded { member: 10, pool_id: 1, amount: 2 } + ] + ); + + CurrentEra::set(1); + Balances::make_free_balance_be( + &Pools::create_reward_account(1), + 2 * Balances::minimum_balance(), + ); + + assert_ok!(Pools::unbond(Origin::signed(10), 10, 3)); + assert_eq!( + pool_events_since_last_call(), + vec![ + // exactly equal to ed, all that can be claimed. + Event::PaidOut { member: 10, pool_id: 1, payout: 5 }, + Event::Unbonded { member: 10, pool_id: 1, amount: 3 } + ] + ); + + CurrentEra::set(2); + Balances::make_free_balance_be( + &Pools::create_reward_account(1), + 2 * Balances::minimum_balance(), + ); + + assert_ok!(Pools::unbond(Origin::signed(10), 10, 5)); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::PaidOut { member: 10, pool_id: 1, payout: 5 }, + Event::Unbonded { member: 10, pool_id: 1, amount: 5 } + ] + ); + + assert_eq!( + PoolMembers::::get(10).unwrap().unbonding_eras, + member_unbonding_eras!(3 => 2, 4 => 3, 5 => 5) + ); + }); + } } mod pool_withdraw_unbonded { @@ -3504,3 +3581,81 @@ mod bond_extra { }) } } + +mod update_roles { + use super::*; + + #[test] + fn update_roles_works() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!( + BondedPools::::get(1).unwrap().roles, + PoolRoles { depositor: 10, root: 900, nominator: 901, state_toggler: 902 }, + ); + + // non-existent pools + assert_noop!( + Pools::update_roles(Origin::signed(1), 2, Some(5), Some(6), Some(7)), + Error::::PoolNotFound, + ); + + // depositor cannot change roles. + assert_noop!( + Pools::update_roles(Origin::signed(1), 1, Some(5), Some(6), Some(7)), + Error::::DoesNotHavePermission, + ); + + // nominator cannot change roles. + assert_noop!( + Pools::update_roles(Origin::signed(901), 1, Some(5), Some(6), Some(7)), + Error::::DoesNotHavePermission, + ); + // state-toggler + assert_noop!( + Pools::update_roles(Origin::signed(902), 1, Some(5), Some(6), Some(7)), + Error::::DoesNotHavePermission, + ); + + // but root can + assert_ok!(Pools::update_roles(Origin::signed(900), 1, Some(5), Some(6), Some(7))); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::RolesUpdated { root: 5, state_toggler: 7, nominator: 6 } + ] + ); + assert_eq!( + BondedPools::::get(1).unwrap().roles, + PoolRoles { depositor: 10, root: 5, nominator: 6, state_toggler: 7 }, + ); + + // also root origin can + assert_ok!(Pools::update_roles(Origin::root(), 1, Some(1), Some(2), Some(3))); + + assert_eq!( + pool_events_since_last_call(), + vec![Event::RolesUpdated { root: 1, state_toggler: 3, nominator: 2 }] + ); + assert_eq!( + BondedPools::::get(1).unwrap().roles, + PoolRoles { depositor: 10, root: 1, nominator: 2, state_toggler: 3 }, + ); + + // None is a noop + assert_ok!(Pools::update_roles(Origin::root(), 1, Some(11), None, None)); + + assert_eq!( + pool_events_since_last_call(), + vec![Event::RolesUpdated { root: 11, state_toggler: 3, nominator: 2 }] + ); + + assert_eq!( + BondedPools::::get(1).unwrap().roles, + PoolRoles { depositor: 10, root: 11, nominator: 2, state_toggler: 3 }, + ); + }) + } +} diff --git a/frame/nomination-pools/src/weights.rs b/frame/nomination-pools/src/weights.rs index 4bd291f5276f8..0b2a84ddd2ac3 100644 --- a/frame/nomination-pools/src/weights.rs +++ b/frame/nomination-pools/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_nomination_pools //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-04-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-10, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -58,6 +58,7 @@ pub trait WeightInfo { fn set_state() -> Weight; fn set_metadata(n: u32, ) -> Weight; fn set_configs() -> Weight; + fn update_roles() -> Weight; } /// Weights for pallet_nomination_pools using the Substrate node and recommended hardware. @@ -78,7 +79,7 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn join() -> Weight { - (117_870_000 as Weight) + (119_253_000 as Weight) .saturating_add(T::DbWeight::get().reads(18 as Weight)) .saturating_add(T::DbWeight::get().writes(12 as Weight)) } @@ -93,7 +94,7 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn bond_extra_transfer() -> Weight { - (110_176_000 as Weight) + (110_157_000 as Weight) .saturating_add(T::DbWeight::get().reads(14 as Weight)) .saturating_add(T::DbWeight::get().writes(13 as Weight)) } @@ -108,7 +109,7 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList ListNodes (r:2 w:2) // Storage: BagsList ListBags (r:2 w:2) fn bond_extra_reward() -> Weight { - (122_829_000 as Weight) + (124_003_000 as Weight) .saturating_add(T::DbWeight::get().reads(14 as Weight)) .saturating_add(T::DbWeight::get().writes(13 as Weight)) } @@ -118,7 +119,7 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools RewardPools (r:1 w:1) // Storage: System Account (r:1 w:1) fn claim_payout() -> Weight { - (50_094_000 as Weight) + (51_767_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } @@ -138,7 +139,7 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools SubPoolsStorage (r:1 w:1) // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) fn unbond() -> Weight { - (119_288_000 as Weight) + (116_959_000 as Weight) .saturating_add(T::DbWeight::get().reads(19 as Weight)) .saturating_add(T::DbWeight::get().writes(14 as Weight)) } @@ -148,9 +149,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking CurrentEra (r:1 w:0) // Storage: Balances Locks (r:1 w:1) fn pool_withdraw_unbonded(s: u32, ) -> Weight { - (39_986_000 as Weight) + (41_124_000 as Weight) // Standard Error: 0 - .saturating_add((50_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((52_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -164,9 +165,9 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) // Storage: NominationPools CounterForPoolMembers (r:1 w:1) fn withdraw_unbonded_update(s: u32, ) -> Weight { - (76_897_000 as Weight) + (80_654_000 as Weight) // Standard Error: 0 - .saturating_add((48_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((50_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(9 as Weight)) .saturating_add(T::DbWeight::get().writes(8 as Weight)) } @@ -191,7 +192,7 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools CounterForBondedPools (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn withdraw_unbonded_kill(_s: u32, ) -> Weight { - (135_837_000 as Weight) + (140_296_000 as Weight) .saturating_add(T::DbWeight::get().reads(20 as Weight)) .saturating_add(T::DbWeight::get().writes(17 as Weight)) } @@ -219,7 +220,7 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn create() -> Weight { - (129_265_000 as Weight) + (130_543_000 as Weight) .saturating_add(T::DbWeight::get().reads(23 as Weight)) .saturating_add(T::DbWeight::get().writes(16 as Weight)) } @@ -236,9 +237,9 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList CounterForListNodes (r:1 w:1) // Storage: Staking CounterForNominators (r:1 w:1) fn nominate(n: u32, ) -> Weight { - (45_546_000 as Weight) - // Standard Error: 11_000 - .saturating_add((2_075_000 as Weight).saturating_mul(n as Weight)) + (46_152_000 as Weight) + // Standard Error: 15_000 + .saturating_add((2_114_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(12 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) .saturating_add(T::DbWeight::get().writes(5 as Weight)) @@ -246,7 +247,7 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Ledger (r:1 w:0) fn set_state() -> Weight { - (23_256_000 as Weight) + (23_544_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -254,7 +255,7 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools Metadata (r:1 w:1) // Storage: NominationPools CounterForMetadata (r:1 w:1) fn set_metadata(n: u32, ) -> Weight { - (10_893_000 as Weight) + (11_032_000 as Weight) // Standard Error: 0 .saturating_add((1_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) @@ -266,9 +267,15 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools MinCreateBond (r:0 w:1) // Storage: NominationPools MaxPools (r:0 w:1) fn set_configs() -> Weight { - (2_793_000 as Weight) + (2_910_000 as Weight) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } + // Storage: NominationPools BondedPools (r:1 w:1) + fn update_roles() -> Weight { + (18_608_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } } // For backwards compatibility and tests @@ -288,7 +295,7 @@ impl WeightInfo for () { // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn join() -> Weight { - (117_870_000 as Weight) + (119_253_000 as Weight) .saturating_add(RocksDbWeight::get().reads(18 as Weight)) .saturating_add(RocksDbWeight::get().writes(12 as Weight)) } @@ -303,7 +310,7 @@ impl WeightInfo for () { // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn bond_extra_transfer() -> Weight { - (110_176_000 as Weight) + (110_157_000 as Weight) .saturating_add(RocksDbWeight::get().reads(14 as Weight)) .saturating_add(RocksDbWeight::get().writes(13 as Weight)) } @@ -318,7 +325,7 @@ impl WeightInfo for () { // Storage: BagsList ListNodes (r:2 w:2) // Storage: BagsList ListBags (r:2 w:2) fn bond_extra_reward() -> Weight { - (122_829_000 as Weight) + (124_003_000 as Weight) .saturating_add(RocksDbWeight::get().reads(14 as Weight)) .saturating_add(RocksDbWeight::get().writes(13 as Weight)) } @@ -328,7 +335,7 @@ impl WeightInfo for () { // Storage: NominationPools RewardPools (r:1 w:1) // Storage: System Account (r:1 w:1) fn claim_payout() -> Weight { - (50_094_000 as Weight) + (51_767_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } @@ -348,7 +355,7 @@ impl WeightInfo for () { // Storage: NominationPools SubPoolsStorage (r:1 w:1) // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) fn unbond() -> Weight { - (119_288_000 as Weight) + (116_959_000 as Weight) .saturating_add(RocksDbWeight::get().reads(19 as Weight)) .saturating_add(RocksDbWeight::get().writes(14 as Weight)) } @@ -358,9 +365,9 @@ impl WeightInfo for () { // Storage: Staking CurrentEra (r:1 w:0) // Storage: Balances Locks (r:1 w:1) fn pool_withdraw_unbonded(s: u32, ) -> Weight { - (39_986_000 as Weight) + (41_124_000 as Weight) // Standard Error: 0 - .saturating_add((50_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((52_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -374,9 +381,9 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) // Storage: NominationPools CounterForPoolMembers (r:1 w:1) fn withdraw_unbonded_update(s: u32, ) -> Weight { - (76_897_000 as Weight) + (80_654_000 as Weight) // Standard Error: 0 - .saturating_add((48_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((50_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(9 as Weight)) .saturating_add(RocksDbWeight::get().writes(8 as Weight)) } @@ -401,7 +408,7 @@ impl WeightInfo for () { // Storage: NominationPools CounterForBondedPools (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn withdraw_unbonded_kill(_s: u32, ) -> Weight { - (135_837_000 as Weight) + (140_296_000 as Weight) .saturating_add(RocksDbWeight::get().reads(20 as Weight)) .saturating_add(RocksDbWeight::get().writes(17 as Weight)) } @@ -429,7 +436,7 @@ impl WeightInfo for () { // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn create() -> Weight { - (129_265_000 as Weight) + (130_543_000 as Weight) .saturating_add(RocksDbWeight::get().reads(23 as Weight)) .saturating_add(RocksDbWeight::get().writes(16 as Weight)) } @@ -446,9 +453,9 @@ impl WeightInfo for () { // Storage: BagsList CounterForListNodes (r:1 w:1) // Storage: Staking CounterForNominators (r:1 w:1) fn nominate(n: u32, ) -> Weight { - (45_546_000 as Weight) - // Standard Error: 11_000 - .saturating_add((2_075_000 as Weight).saturating_mul(n as Weight)) + (46_152_000 as Weight) + // Standard Error: 15_000 + .saturating_add((2_114_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(12 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) @@ -456,7 +463,7 @@ impl WeightInfo for () { // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Ledger (r:1 w:0) fn set_state() -> Weight { - (23_256_000 as Weight) + (23_544_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -464,7 +471,7 @@ impl WeightInfo for () { // Storage: NominationPools Metadata (r:1 w:1) // Storage: NominationPools CounterForMetadata (r:1 w:1) fn set_metadata(n: u32, ) -> Weight { - (10_893_000 as Weight) + (11_032_000 as Weight) // Standard Error: 0 .saturating_add((1_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) @@ -476,7 +483,13 @@ impl WeightInfo for () { // Storage: NominationPools MinCreateBond (r:0 w:1) // Storage: NominationPools MaxPools (r:0 w:1) fn set_configs() -> Weight { - (2_793_000 as Weight) + (2_910_000 as Weight) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } + // Storage: NominationPools BondedPools (r:1 w:1) + fn update_roles() -> Weight { + (18_608_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } } diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 7ff0808af0a63..a2a6432485be8 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -80,10 +80,10 @@ pub trait StakingInterface { /// This should be the latest planned era that the staking system knows about. fn current_era() -> EraIndex; - /// The amount of active stake that `controller` has in the staking system. - fn active_stake(controller: &Self::AccountId) -> Option; + /// The amount of active stake that `stash` has in the staking system. + fn active_stake(stash: &Self::AccountId) -> Option; - /// The total stake that `controller` has in the staking system. This includes the + /// The total stake that `stash` has in the staking system. This includes the /// [`Self::active_stake`], and any funds currently in the process of unbonding via /// [`Self::unbond`]. /// @@ -91,7 +91,7 @@ pub trait StakingInterface { /// /// This is only guaranteed to reflect the amount locked by the staking system. If there are /// non-staking locks on the bonded pair's balance this may not be accurate. - fn total_stake(controller: &Self::AccountId) -> Option; + fn total_stake(stash: &Self::AccountId) -> Option; /// Bond (lock) `value` of `stash`'s balance. `controller` will be set as the account /// controlling `stash`. This creates what is referred to as "bonded pair". @@ -111,7 +111,7 @@ pub trait StakingInterface { /// Bond some extra amount in the _Stash_'s free balance against the active bonded balance of /// the account. The amount extra actually bonded will never be more than the _Stash_'s free /// balance. - fn bond_extra(controller: Self::AccountId, extra: Self::Balance) -> DispatchResult; + fn bond_extra(stash: Self::AccountId, extra: Self::Balance) -> DispatchResult; /// Schedule a portion of the active bonded balance to be unlocked at era /// [Self::current_era] + [`Self::bonding_duration`]. @@ -122,11 +122,11 @@ pub trait StakingInterface { /// The amount of times this can be successfully called is limited based on how many distinct /// eras funds are schedule to unlock in. Calling [`Self::withdraw_unbonded`] after some unlock /// schedules have reached their unlocking era should allow more calls to this function. - fn unbond(controller: Self::AccountId, value: Self::Balance) -> DispatchResult; + fn unbond(stash: Self::AccountId, value: Self::Balance) -> DispatchResult; /// Unlock any funds schedule to unlock before or at the current era. fn withdraw_unbonded( - controller: Self::AccountId, + stash: Self::AccountId, num_slashing_spans: u32, ) -> Result; } From 7f1aef902a3aea4f891a6e96eccdc0d9eb1fb272 Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Wed, 11 May 2022 11:01:02 +0200 Subject: [PATCH 209/484] [ci] Divide .gitlab-ci.yml into several files (#11333) * rebase * update Cargo.lock * revert Cargo.lock * fix Cargo.lock from 11384 --- .gitlab-ci.yml | 645 ++----------------------- scripts/ci/gitlab/pipeline/build.yml | 121 +++++ scripts/ci/gitlab/pipeline/check.yml | 54 +++ scripts/ci/gitlab/pipeline/publish.yml | 149 ++++++ scripts/ci/gitlab/pipeline/test.yml | 271 +++++++++++ 5 files changed, 633 insertions(+), 607 deletions(-) create mode 100644 scripts/ci/gitlab/pipeline/build.yml create mode 100644 scripts/ci/gitlab/pipeline/check.yml create mode 100644 scripts/ci/gitlab/pipeline/publish.yml create mode 100644 scripts/ci/gitlab/pipeline/test.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 224d5d5f0ef3f..b0af4cda995ad 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,7 +3,15 @@ # substrate # # pipelines can be triggered manually in the web - +# +# Currently the file is divided into subfiles. Each stage has a different file which +# can be found here: scripts/ci/gitlab/pipeline/.yml +# +# Instead of YAML anchors "extends" is used. +# Useful links: +# https://docs.gitlab.com/ee/ci/yaml/index.html#extends +# https://docs.gitlab.com/ee/ci/yaml/yaml_optimization.html#reference-tags +# # SAMPLE JOB TEMPLATE - This is not a complete example but is enough to build a # simple CI job. For full documentation, visit https://docs.gitlab.com/ee/ci/yaml/ # @@ -50,7 +58,7 @@ default: interruptible: true cache: {} -.collect-artifacts: &collect-artifacts +.collect-artifacts: artifacts: name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}" when: on_success @@ -58,7 +66,7 @@ default: paths: - artifacts/ -.collect-artifacts-short: &collect-artifacts-short +.collect-artifacts-short: artifacts: name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}" when: on_success @@ -66,25 +74,26 @@ default: paths: - artifacts/ -.kubernetes-env: &kubernetes-env +.kubernetes-env: + image: "${CI_IMAGE}" tags: - kubernetes-parity-build .rust-info-script: &rust-info-script - - rustup show - - cargo --version - - rustup +nightly show - - cargo +nightly --version - - sccache -s + script: + - rustup show + - cargo --version + - rustup +nightly show + - cargo +nightly --version -.docker-env: &docker-env +.docker-env: image: "${CI_IMAGE}" before_script: - - *rust-info-script + - !reference [.rust-info-script, script] tags: - linux-docker -.test-refs: &test-refs +.test-refs: rules: - if: $CI_PIPELINE_SOURCE == "web" - if: $CI_PIPELINE_SOURCE == "schedule" @@ -92,7 +101,7 @@ default: - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 -.test-refs-no-trigger: &test-refs-no-trigger +.test-refs-no-trigger: rules: - if: $CI_PIPELINE_SOURCE == "pipeline" when: never @@ -103,7 +112,7 @@ default: - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 - if: $CI_COMMIT_REF_NAME =~ /^ci-release-.*$/ -.test-refs-no-trigger-prs-only: &test-refs-no-trigger-prs-only +.test-refs-no-trigger-prs-only: rules: - if: $CI_PIPELINE_SOURCE == "pipeline" when: never @@ -111,7 +120,7 @@ default: - if: $CI_PIPELINE_SOURCE == "schedule" - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs -.test-refs-wasmer-sandbox: &test-refs-wasmer-sandbox +.test-refs-wasmer-sandbox: rules: - if: $CI_PIPELINE_SOURCE == "web" - if: $CI_PIPELINE_SOURCE == "schedule" @@ -131,7 +140,7 @@ default: - frame/contracts/**/* - primitives/sandbox/**/* -.build-refs: &build-refs +.build-refs: rules: - if: $CI_PIPELINE_SOURCE == "pipeline" when: never @@ -140,35 +149,17 @@ default: - if: $CI_COMMIT_REF_NAME == "master" - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 -.nightly-pipeline: &nightly-pipeline +.nightly-pipeline: rules: # this job runs only on nightly pipeline with the mentioned variable, against `master` branch - if: $CI_COMMIT_REF_NAME == "master" && $CI_PIPELINE_SOURCE == "schedule" && $PIPELINE == "nightly" -.merge-ref-into-master-script: &merge-ref-into-master-script - - if [ $CI_COMMIT_REF_NAME != "master" ]; then - git fetch origin +master:master; - git fetch origin +$CI_COMMIT_REF_NAME:$CI_COMMIT_REF_NAME; - git checkout master; - git config user.email "ci@gitlab.parity.io"; - git merge $CI_COMMIT_REF_NAME --verbose --no-edit; - fi - -.cargo-check-benches-script: &cargo-check-benches-script - - mkdir -p ./artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA - - SKIP_WASM_BUILD=1 time cargo +nightly check --benches --all - - 'cargo run --release -p node-bench -- ::node::import::native::sr25519::transfer_keep_alive::paritydb::small --json - | tee ./artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA/::node::import::native::sr25519::transfer_keep_alive::paritydb::small.json' - - 'cargo run --release -p node-bench -- ::trie::read::small --json - | tee ./artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA/::trie::read::small.json' - - sccache -s - - #### stage: .pre skip-if-draft: - image: paritytech/tools:latest - <<: *kubernetes-env + extends: .kubernetes-env + variables: + CI_IMAGE: "paritytech/tools:latest" stage: .pre rules: - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs @@ -178,576 +169,16 @@ skip-if-draft: - echo "pipeline source is ${CI_PIPELINE_SOURCE}" - ./scripts/ci/gitlab/skip_if_draft.sh -#### stage: check +include: + # check jobs + - scripts/ci/gitlab/pipeline/check.yml + # tests jobs + - scripts/ci/gitlab/pipeline/test.yml + # build jobs + - scripts/ci/gitlab/pipeline/build.yml + # publish jobs + - scripts/ci/gitlab/pipeline/publish.yml -check-runtime: - stage: check - image: paritytech/tools:latest - <<: *kubernetes-env - rules: - - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs - variables: - <<: *default-vars - GITLAB_API: "https://gitlab.parity.io/api/v4" - GITHUB_API_PROJECT: "parity%2Finfrastructure%2Fgithub-api" - script: - - ./scripts/ci/gitlab/check_runtime.sh - allow_failure: true - -check-signed-tag: - stage: check - image: paritytech/tools:latest - <<: *kubernetes-env - rules: - - if: $CI_COMMIT_REF_NAME =~ /^ci-release-.*$/ - - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 - script: - - ./scripts/ci/gitlab/check_signed.sh - -test-dependency-rules: - stage: check - image: paritytech/tools:latest - <<: *kubernetes-env - rules: - - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs - script: - - ./scripts/ci/gitlab/ensure-deps.sh - -test-prometheus-alerting-rules: - stage: check - image: paritytech/tools:latest - <<: *kubernetes-env - rules: - - if: $CI_PIPELINE_SOURCE == "pipeline" - when: never - - if: $CI_COMMIT_BRANCH - changes: - - .gitlab-ci.yml - - ./scripts/ci/monitoring/**/* - script: - - promtool check rules ./scripts/ci/monitoring/alerting-rules/alerting-rules.yaml - - cat ./scripts/ci/monitoring/alerting-rules/alerting-rules.yaml | - promtool test rules ./scripts/ci/monitoring/alerting-rules/alerting-rule-tests.yaml - -#### stage: test - -cargo-deny: - stage: test - <<: *docker-env - <<: *nightly-pipeline - script: - - cargo deny check --hide-inclusion-graph -c ./scripts/ci/deny.toml - after_script: - - echo "___The complete log is in the artifacts___" - - cargo deny check -c ./scripts/ci/deny.toml 2> deny.log - artifacts: - name: $CI_COMMIT_SHORT_SHA - expire_in: 3 days - when: always - paths: - - deny.log - # FIXME: Temporarily allow to fail. - allow_failure: true - -cargo-fmt: - stage: test - <<: *docker-env - <<: *test-refs - script: - - cargo +nightly fmt --all -- --check - -cargo-clippy: - stage: test - <<: *docker-env - <<: *test-refs - script: - - SKIP_WASM_BUILD=1 env -u RUSTFLAGS cargo +nightly clippy --all-targets - -cargo-check-nixos: - stage: test - <<: *docker-env - <<: *test-refs - before_script: [] - # Don't use CI_IMAGE here because it breaks nightly checks of paritytech/ci-linux image - image: nixos/nix - variables: - SNAP: "DUMMY" - WS_API: "DUMMY" - script: - - nix-channel --update - - nix-shell shell.nix - - nix-shell --run "cargo check --workspace --all-targets --all-features" - -cargo-check-benches: - stage: test - <<: *docker-env - <<: *test-refs - <<: *collect-artifacts - before_script: - # merges in the master branch on PRs - - *merge-ref-into-master-script - - *rust-info-script - script: - - *cargo-check-benches-script - tags: - - linux-docker-benches - -node-bench-regression-guard: - # it's not belong to `build` semantically, but dag jobs can't depend on each other - # within the single stage - https://gitlab.com/gitlab-org/gitlab/-/issues/30632 - # more: https://github.com/paritytech/substrate/pull/8519#discussion_r608012402 - stage: build - <<: *docker-env - <<: *test-refs-no-trigger-prs-only - needs: - # this is a DAG - - job: cargo-check-benches - artifacts: true - # this does not like a DAG, just polls the artifact - - project: $CI_PROJECT_PATH - job: cargo-check-benches - ref: master - artifacts: true - variables: - CI_IMAGE: "paritytech/node-bench-regression-guard:latest" - before_script: [""] - script: - - echo "------- IMPORTANT -------" - - echo "node-bench-regression-guard depends on the results of a cargo-check-benches job" - - echo "In case of this job failure, check your pipeline's cargo-check-benches" - - 'node-bench-regression-guard --reference artifacts/benches/master-* - --compare-with artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA' - -cargo-check-subkey: - stage: test - <<: *docker-env - <<: *test-refs - script: - - cd ./bin/utils/subkey - - SKIP_WASM_BUILD=1 time cargo check --release - - sccache -s - -cargo-check-try-runtime: - stage: test - <<: *docker-env - <<: *test-refs - script: - - time cargo check --features try-runtime - - sccache -s - -cargo-check-wasmer-sandbox: - stage: test - <<: *docker-env - <<: *test-refs - script: - - time cargo check --features wasmer-sandbox - - sccache -s - -test-deterministic-wasm: - stage: test - <<: *docker-env - <<: *test-refs - variables: - <<: *default-vars - WASM_BUILD_NO_COLOR: 1 - script: - # build runtime - - cargo build --verbose --release -p node-runtime - # make checksum - - sha256sum ./target/release/wbuild/node-runtime/target/wasm32-unknown-unknown/release/node_runtime.wasm > checksum.sha256 - # clean up – FIXME: can we reuse some of the artifacts? - - cargo clean - # build again - - cargo build --verbose --release -p node-runtime - # confirm checksum - - sha256sum -c ./checksum.sha256 - - sccache -s - -test-linux-stable: &test-linux - stage: test - <<: *docker-env - <<: *test-refs - variables: - <<: *default-vars - # Enable debug assertions since we are running optimized builds for testing - # but still want to have debug assertions. - RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" - RUST_BACKTRACE: 1 - WASM_BUILD_NO_COLOR: 1 - # Ensure we run the UI tests. - RUN_UI_TESTS: 1 - script: - # this job runs all tests in former runtime-benchmarks, frame-staking and wasmtime tests - - time cargo test --workspace --locked --release --verbose --features runtime-benchmarks --manifest-path ./bin/node/cli/Cargo.toml - - time cargo test -p frame-support-test --features=conditional-storage,no-metadata-docs --manifest-path ./frame/support/test/Cargo.toml --test pallet # does not reuse cache 1 min 44 sec - - SUBSTRATE_TEST_TIMEOUT=1 time cargo test -p substrate-test-utils --release --verbose --locked -- --ignored timeout - - sccache -s - -test-frame-examples-compile-to-wasm: - # into one job - stage: test - <<: *docker-env - <<: *test-refs - variables: - <<: *default-vars - # Enable debug assertions since we are running optimized builds for testing - # but still want to have debug assertions. - RUSTFLAGS: "-Cdebug-assertions=y" - RUST_BACKTRACE: 1 - script: - - cd ./frame/examples/offchain-worker/ - - cargo +nightly build --target=wasm32-unknown-unknown --no-default-features - - cd ../basic - - cargo +nightly build --target=wasm32-unknown-unknown --no-default-features - - sccache -s - -test-linux-stable-int: - <<: *test-linux - stage: test - script: - - echo "___Logs will be partly shown at the end in case of failure.___" - - echo "___Full log will be saved to the job artifacts only in case of failure.___" - - WASM_BUILD_NO_COLOR=1 - RUST_LOG=sync=trace,consensus=trace,client=trace,state-db=trace,db=trace,forks=trace,state_db=trace,storage_cache=trace - time cargo test -p node-cli --release --verbose --locked -- --ignored - &> ${CI_COMMIT_SHORT_SHA}_int_failure.log - - sccache -s - after_script: - - awk '/FAILED|^error\[/,0' ${CI_COMMIT_SHORT_SHA}_int_failure.log - artifacts: - name: $CI_COMMIT_SHORT_SHA - when: on_failure - expire_in: 3 days - paths: - - ${CI_COMMIT_SHORT_SHA}_int_failure.log - -check-tracing: - stage: test - <<: *docker-env - <<: *test-refs - script: - # with-tracing must be explicitly activated, we run a test to ensure this works as expected in both cases - - time cargo +nightly test --manifest-path ./primitives/tracing/Cargo.toml --no-default-features - - time cargo +nightly test --manifest-path ./primitives/tracing/Cargo.toml --no-default-features --features=with-tracing - - sccache -s - -test-full-crypto-feature: - stage: test - <<: *docker-env - <<: *test-refs - variables: - <<: *default-vars - # Enable debug assertions since we are running optimized builds for testing - # but still want to have debug assertions. - RUSTFLAGS: "-Cdebug-assertions=y" - RUST_BACKTRACE: 1 - script: - - cd primitives/core/ - - time cargo +nightly build --verbose --no-default-features --features full_crypto - - cd ../application-crypto - - time cargo +nightly build --verbose --no-default-features --features full_crypto - - sccache -s - -test-wasmer-sandbox: - stage: test - <<: *docker-env - <<: *test-refs-wasmer-sandbox - variables: - <<: *default-vars - script: - - time cargo test --release --features runtime-benchmarks,wasmer-sandbox,disable-ui-tests - - sccache -s - -cargo-check-macos: - stage: test - # shell runner on mac ignores the image set in *docker-env - <<: *docker-env - <<: *test-refs-no-trigger - script: - - SKIP_WASM_BUILD=1 time cargo check --release - - sccache -s - tags: - - osx - -#### stage: build - -# PIPELINE_SCRIPTS_TAG can be found in the project variables - -.check-dependent-project: &check-dependent-project - stage: build - <<: *docker-env - <<: *test-refs-no-trigger-prs-only - script: - - git clone - --depth=1 - "--branch=$PIPELINE_SCRIPTS_TAG" - https://github.com/paritytech/pipeline-scripts - - ./pipeline-scripts/check_dependent_project.sh - --org paritytech - --dependent-repo "$DEPENDENT_REPO" - --github-api-token "$GITHUB_PR_TOKEN" - --extra-dependencies "$EXTRA_DEPENDENCIES" - --companion-overrides "$COMPANION_OVERRIDES" - -# Individual jobs are set up for each dependent project so that they can be ran in parallel. -# Arguably we could generate a job for each companion in the PR's description using Gitlab's -# parent-child pipelines but that's more complicated. - -check-dependent-polkadot: - <<: *check-dependent-project - variables: - DEPENDENT_REPO: polkadot - COMPANION_OVERRIDES: | - substrate: polkadot-v* - polkadot: release-v* - -check-dependent-cumulus: - <<: *check-dependent-project - variables: - DEPENDENT_REPO: cumulus - EXTRA_DEPENDENCIES: polkadot - COMPANION_OVERRIDES: | - substrate: polkadot-v* - cumulus: polkadot-v* - - -build-linux-substrate: - stage: build - <<: *collect-artifacts - <<: *docker-env - <<: *build-refs - needs: - - job: test-linux-stable - artifacts: false - before_script: - - mkdir -p ./artifacts/substrate/ - script: - - WASM_BUILD_NO_COLOR=1 time cargo build --release --verbose - - mv ./target/release/substrate ./artifacts/substrate/. - - echo -n "Substrate version = " - - if [ "${CI_COMMIT_TAG}" ]; then - echo "${CI_COMMIT_TAG}" | tee ./artifacts/substrate/VERSION; - else - ./artifacts/substrate/substrate --version | - cut -d ' ' -f 2 | tee ./artifacts/substrate/VERSION; - fi - - sha256sum ./artifacts/substrate/substrate | tee ./artifacts/substrate/substrate.sha256 - - cp -r ./scripts/ci/docker/substrate.Dockerfile ./artifacts/substrate/ - - printf '\n# building node-template\n\n' - - ./scripts/ci/node-template-release.sh ./artifacts/substrate/substrate-node-template.tar.gz - -build-linux-subkey: &build-subkey - stage: build - <<: *collect-artifacts - <<: *docker-env - <<: *build-refs - needs: - - job: cargo-check-subkey - artifacts: false - before_script: - - mkdir -p ./artifacts/subkey - script: - - cd ./bin/utils/subkey - - SKIP_WASM_BUILD=1 time cargo build --release --verbose - - cd - - - mv ./target/release/subkey ./artifacts/subkey/. - - echo -n "Subkey version = " - - ./artifacts/subkey/subkey --version | - sed -n -E 's/^subkey ([0-9.]+.*)/\1/p' | - tee ./artifacts/subkey/VERSION; - - sha256sum ./artifacts/subkey/subkey | tee ./artifacts/subkey/subkey.sha256 - - cp -r ./scripts/ci/docker/subkey.Dockerfile ./artifacts/subkey/ - - sccache -s - -build-macos-subkey: - <<: *build-subkey - tags: - - osx - -check-rustdoc: - stage: test - <<: *docker-env - <<: *test-refs - variables: - <<: *default-vars - SKIP_WASM_BUILD: 1 - RUSTDOCFLAGS: "-Dwarnings" - script: - - time cargo +nightly doc --workspace --all-features --verbose --no-deps - - sccache -s - -build-rustdoc: - stage: build - <<: *docker-env - <<: *test-refs - variables: - <<: *default-vars - SKIP_WASM_BUILD: 1 - DOC_INDEX_PAGE: "sc_service/index.html" # default redirected page - artifacts: - name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}-doc" - when: on_success - expire_in: 7 days - paths: - - ./crate-docs/ - script: - - time cargo +nightly doc --workspace --all-features --verbose - - rm -f ./target/doc/.lock - - mv ./target/doc ./crate-docs - # FIXME: remove me after CI image gets nonroot - - chown -R nonroot:nonroot ./crate-docs - - echo "" > ./crate-docs/index.html - - sccache -s - -#### stage: publish - -.build-push-docker-image: &build-push-docker-image - <<: *build-refs - <<: *kubernetes-env - image: quay.io/buildah/stable - variables: &docker-build-vars - <<: *default-vars - GIT_STRATEGY: none - DOCKERFILE: $PRODUCT.Dockerfile - IMAGE_NAME: docker.io/parity/$PRODUCT - before_script: - - cd ./artifacts/$PRODUCT/ - - VERSION="$(cat ./VERSION)" - - echo "${PRODUCT} version = ${VERSION}" - - test -z "${VERSION}" && exit 1 - script: - - test "$Docker_Hub_User_Parity" -a "$Docker_Hub_Pass_Parity" || - ( echo "no docker credentials provided"; exit 1 ) - - buildah bud - --format=docker - --build-arg VCS_REF="${CI_COMMIT_SHA}" - --build-arg BUILD_DATE="$(date -u '+%Y-%m-%dT%H:%M:%SZ')" - --tag "$IMAGE_NAME:$VERSION" - --tag "$IMAGE_NAME:latest" - --file "$DOCKERFILE" . - - echo "$Docker_Hub_Pass_Parity" | - buildah login --username "$Docker_Hub_User_Parity" --password-stdin docker.io - - buildah info - - buildah push --format=v2s2 "$IMAGE_NAME:$VERSION" - - buildah push --format=v2s2 "$IMAGE_NAME:latest" - after_script: - - buildah logout --all - - echo "SUBSTRATE_IMAGE_NAME=${IMAGE_NAME}" | tee -a ./artifacts/$PRODUCT/build.env - - IMAGE_TAG="$(cat ./artifacts/$PRODUCT/VERSION)" - - echo "SUBSTRATE_IMAGE_TAG=${IMAGE_TAG}" | tee -a ./artifacts/$PRODUCT/build.env - - cat ./artifacts/$PRODUCT/build.env - -publish-docker-substrate: - stage: publish - <<: *build-push-docker-image - <<: *build-refs - needs: - - job: build-linux-substrate - artifacts: true - variables: - <<: *docker-build-vars - PRODUCT: substrate - -publish-docker-subkey: - stage: publish - <<: *build-push-docker-image - needs: - - job: build-linux-subkey - artifacts: true - variables: - <<: *docker-build-vars - PRODUCT: subkey - -publish-s3-release: - stage: publish - <<: *build-refs - <<: *kubernetes-env - needs: - - job: build-linux-substrate - artifacts: true - - job: build-linux-subkey - artifacts: true - image: paritytech/awscli:latest - variables: - GIT_STRATEGY: none - BUCKET: "releases.parity.io" - PREFIX: "substrate/${ARCH}-${DOCKER_OS}" - script: - - aws s3 sync ./artifacts/ s3://${BUCKET}/${PREFIX}/$(cat ./artifacts/substrate/VERSION)/ - - echo "update objects in latest path" - - aws s3 sync s3://${BUCKET}/${PREFIX}/$(cat ./artifacts/substrate/VERSION)/ s3://${BUCKET}/${PREFIX}/latest/ - after_script: - - aws s3 ls s3://${BUCKET}/${PREFIX}/latest/ - --recursive --human-readable --summarize - -publish-rustdoc: - stage: publish - <<: *kubernetes-env - image: node:16 - variables: - GIT_DEPTH: 100 - RUSTDOCS_DEPLOY_REFS: "master" - rules: - - if: $CI_PIPELINE_SOURCE == "pipeline" - when: never - - if: $CI_PIPELINE_SOURCE == "web" && $CI_COMMIT_REF_NAME == "master" - - if: $CI_COMMIT_REF_NAME == "master" - - if: $CI_COMMIT_REF_NAME =~ /^monthly-20[0-9]{2}-[0-9]{2}.*$/ # to support: monthly-2021-09+1 - - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 - # `needs:` can be removed after CI image gets nonroot. In this case `needs:` stops other - # artifacts from being dowloaded by this job. - needs: - - job: build-rustdoc - artifacts: true - script: - # If $CI_COMMIT_REF_NAME doesn't match one of $RUSTDOCS_DEPLOY_REFS space-separated values, we - # exit immediately. - # Putting spaces at the front and back to ensure we are not matching just any substring, but the - # whole space-separated value. - - '[[ " ${RUSTDOCS_DEPLOY_REFS} " =~ " ${CI_COMMIT_REF_NAME} " ]] || exit 0' - # setup ssh - - eval $(ssh-agent) - - ssh-add - <<< ${GITHUB_SSH_PRIV_KEY} - - mkdir ~/.ssh && touch ~/.ssh/known_hosts - - ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts - # Set git config - - git config user.email "devops-team@parity.io" - - git config user.name "${GITHUB_USER}" - - git config remote.origin.url "git@github.com:/paritytech/${CI_PROJECT_NAME}.git" - - git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*" - - git fetch origin gh-pages - # Save README and docs - - cp -r ./crate-docs/ /tmp/doc/ - - cp README.md /tmp/doc/ - # we don't need to commit changes because we copy docs to /tmp - - git checkout gh-pages --force - # Install `index-tpl-crud` and generate index.html based on RUSTDOCS_DEPLOY_REFS - - which index-tpl-crud &> /dev/null || yarn global add @substrate/index-tpl-crud - - index-tpl-crud upsert ./index.html ${CI_COMMIT_REF_NAME} - # Ensure the destination dir doesn't exist. - - rm -rf ${CI_COMMIT_REF_NAME} - - mv -f /tmp/doc ${CI_COMMIT_REF_NAME} - # Upload files - - git add --all - # `git commit` has an exit code of > 0 if there is nothing to commit. - # This causes GitLab to exit immediately and marks this job failed. - # We don't want to mark the entire job failed if there's nothing to - # publish though, hence the `|| true`. - - git commit -m "___Updated docs for ${CI_COMMIT_REF_NAME}___" || - echo "___Nothing to commit___" - - git push origin gh-pages --force - after_script: - - rm -rf .git/ ./* - -publish-draft-release: - stage: publish - image: paritytech/tools:latest - rules: - - if: $CI_COMMIT_REF_NAME =~ /^ci-release-.*$/ - - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 - script: - - ./scripts/ci/gitlab/publish_draft_release.sh - allow_failure: true #### stage: deploy diff --git a/scripts/ci/gitlab/pipeline/build.yml b/scripts/ci/gitlab/pipeline/build.yml new file mode 100644 index 0000000000000..4ab83f53e7bdf --- /dev/null +++ b/scripts/ci/gitlab/pipeline/build.yml @@ -0,0 +1,121 @@ +# This file is part of .gitlab-ci.yml +# Here are all jobs that are executed during "build" stage + +# PIPELINE_SCRIPTS_TAG can be found in the project variables + +.check-dependent-project: + stage: build + extends: + - .docker-env + - .test-refs-no-trigger-prs-only + script: + - git clone + --depth=1 + "--branch=$PIPELINE_SCRIPTS_TAG" + https://github.com/paritytech/pipeline-scripts + - ./pipeline-scripts/check_dependent_project.sh + --org paritytech + --dependent-repo "$DEPENDENT_REPO" + --github-api-token "$GITHUB_PR_TOKEN" + --extra-dependencies "$EXTRA_DEPENDENCIES" + --companion-overrides "$COMPANION_OVERRIDES" + +# Individual jobs are set up for each dependent project so that they can be ran in parallel. +# Arguably we could generate a job for each companion in the PR's description using Gitlab's +# parent-child pipelines but that's more complicated. + +check-dependent-polkadot: + extends: .check-dependent-project + variables: + DEPENDENT_REPO: polkadot + COMPANION_OVERRIDES: | + substrate: polkadot-v* + polkadot: release-v* + +check-dependent-cumulus: + extends: .check-dependent-project + variables: + DEPENDENT_REPO: cumulus + EXTRA_DEPENDENCIES: polkadot + COMPANION_OVERRIDES: | + substrate: polkadot-v* + polkadot: release-v* + +build-linux-substrate: + stage: build + extends: + - .collect-artifacts + - .docker-env + - .build-refs + needs: + - job: test-linux-stable + artifacts: false + before_script: + - mkdir -p ./artifacts/substrate/ + script: + - WASM_BUILD_NO_COLOR=1 time cargo build --release --verbose + - mv ./target/release/substrate ./artifacts/substrate/. + - echo -n "Substrate version = " + - if [ "${CI_COMMIT_TAG}" ]; then + echo "${CI_COMMIT_TAG}" | tee ./artifacts/substrate/VERSION; + else + ./artifacts/substrate/substrate --version | + cut -d ' ' -f 2 | tee ./artifacts/substrate/VERSION; + fi + - sha256sum ./artifacts/substrate/substrate | tee ./artifacts/substrate/substrate.sha256 + - cp -r ./scripts/ci/docker/substrate.Dockerfile ./artifacts/substrate/ + - printf '\n# building node-template\n\n' + - ./scripts/ci/node-template-release.sh ./artifacts/substrate/substrate-node-template.tar.gz + +.build-subkey: + stage: build + extends: + - .collect-artifacts + - .docker-env + - .build-refs + needs: + - job: cargo-check-subkey + artifacts: false + before_script: + - mkdir -p ./artifacts/subkey + script: + - cd ./bin/utils/subkey + - SKIP_WASM_BUILD=1 time cargo build --release --verbose + - cd - + - mv ./target/release/subkey ./artifacts/subkey/. + - echo -n "Subkey version = " + - ./artifacts/subkey/subkey --version | + sed -n -E 's/^subkey ([0-9.]+.*)/\1/p' | + tee ./artifacts/subkey/VERSION; + - sha256sum ./artifacts/subkey/subkey | tee ./artifacts/subkey/subkey.sha256 + - cp -r ./scripts/ci/docker/subkey.Dockerfile ./artifacts/subkey/ + +build-subkey-linux: + extends: .build-subkey + +build-subkey-macos: + extends: .build-subkey + tags: + - osx + +build-rustdoc: + stage: build + extends: + - .docker-env + - .test-refs + variables: + SKIP_WASM_BUILD: 1 + DOC_INDEX_PAGE: "sc_service/index.html" # default redirected page + artifacts: + name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}-doc" + when: on_success + expire_in: 7 days + paths: + - ./crate-docs/ + script: + - time cargo +nightly doc --workspace --all-features --verbose + - rm -f ./target/doc/.lock + - mv ./target/doc ./crate-docs + # FIXME: remove me after CI image gets nonroot + - chown -R nonroot:nonroot ./crate-docs + - echo "" > ./crate-docs/index.html diff --git a/scripts/ci/gitlab/pipeline/check.yml b/scripts/ci/gitlab/pipeline/check.yml new file mode 100644 index 0000000000000..3166e13313f2a --- /dev/null +++ b/scripts/ci/gitlab/pipeline/check.yml @@ -0,0 +1,54 @@ +# This file is part of .gitlab-ci.yml +# Here are all jobs that are executed during "check" stage + +check-runtime: + stage: check + extends: + - .kubernetes-env + - .test-refs-no-trigger-prs-only + variables: + CI_IMAGE: "paritytech/tools:latest" + GITLAB_API: "https://gitlab.parity.io/api/v4" + GITHUB_API_PROJECT: "parity%2Finfrastructure%2Fgithub-api" + script: + - ./scripts/ci/gitlab/check_runtime.sh + allow_failure: true + +check-signed-tag: + stage: check + extends: .kubernetes-env + variables: + CI_IMAGE: "paritytech/tools:latest" + rules: + - if: $CI_COMMIT_REF_NAME =~ /^ci-release-.*$/ + - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 + script: + - ./scripts/ci/gitlab/check_signed.sh + +test-dependency-rules: + stage: check + extends: + - .kubernetes-env + - .test-refs-no-trigger-prs-only + variables: + CI_IMAGE: "paritytech/tools:latest" + script: + - ./scripts/ci/gitlab/ensure-deps.sh + +test-prometheus-alerting-rules: + stage: check + extends: .kubernetes-env + variables: + CI_IMAGE: "paritytech/tools:latest" + rules: + - if: $CI_PIPELINE_SOURCE == "pipeline" + when: never + - if: $CI_COMMIT_BRANCH + changes: + - .gitlab-ci.yml + - ./scripts/ci/monitoring/**/* + script: + - promtool check rules ./scripts/ci/monitoring/alerting-rules/alerting-rules.yaml + - cat ./scripts/ci/monitoring/alerting-rules/alerting-rules.yaml | + promtool test rules ./scripts/ci/monitoring/alerting-rules/alerting-rule-tests.yaml + diff --git a/scripts/ci/gitlab/pipeline/publish.yml b/scripts/ci/gitlab/pipeline/publish.yml new file mode 100644 index 0000000000000..c02b50e0cbc1d --- /dev/null +++ b/scripts/ci/gitlab/pipeline/publish.yml @@ -0,0 +1,149 @@ + +# This file is part of .gitlab-ci.yml +# Here are all jobs that are executed during "publish" stage + +.build-push-docker-image: + extends: + - .build-refs + - .kubernetes-env + variables: + CI_IMAGE: quay.io/buildah/stable + GIT_STRATEGY: none + DOCKERFILE: $PRODUCT.Dockerfile + IMAGE_NAME: docker.io/parity/$PRODUCT + before_script: + - cd ./artifacts/$PRODUCT/ + - VERSION="$(cat ./VERSION)" + - echo "${PRODUCT} version = ${VERSION}" + - test -z "${VERSION}" && exit 1 + script: + - test "$Docker_Hub_User_Parity" -a "$Docker_Hub_Pass_Parity" || + ( echo "no docker credentials provided"; exit 1 ) + - buildah bud + --format=docker + --build-arg VCS_REF="${CI_COMMIT_SHA}" + --build-arg BUILD_DATE="$(date -u '+%Y-%m-%dT%H:%M:%SZ')" + --tag "$IMAGE_NAME:$VERSION" + --tag "$IMAGE_NAME:latest" + --file "$DOCKERFILE" . + - echo "$Docker_Hub_Pass_Parity" | + buildah login --username "$Docker_Hub_User_Parity" --password-stdin docker.io + - buildah info + - buildah push --format=v2s2 "$IMAGE_NAME:$VERSION" + - buildah push --format=v2s2 "$IMAGE_NAME:latest" + after_script: + - buildah logout --all + - echo "SUBSTRATE_IMAGE_NAME=${IMAGE_NAME}" | tee -a ./artifacts/$PRODUCT/build.env + - IMAGE_TAG="$(cat ./artifacts/$PRODUCT/VERSION)" + - echo "SUBSTRATE_IMAGE_TAG=${IMAGE_TAG}" | tee -a ./artifacts/$PRODUCT/build.env + - cat ./artifacts/$PRODUCT/build.env + +publish-docker-substrate: + stage: publish + extends: .build-push-docker-image + needs: + - job: build-linux-substrate + artifacts: true + variables: + PRODUCT: substrate + +publish-docker-subkey: + stage: publish + extends: .build-push-docker-image + needs: + - job: build-subkey-linux + artifacts: true + variables: + PRODUCT: subkey + +publish-s3-release: + stage: publish + extends: + - .build-refs + - .kubernetes-env + needs: + - job: build-linux-substrate + artifacts: true + - job: build-subkey-linux + artifacts: true + image: paritytech/awscli:latest + variables: + GIT_STRATEGY: none + BUCKET: "releases.parity.io" + PREFIX: "substrate/${ARCH}-${DOCKER_OS}" + script: + - aws s3 sync ./artifacts/ s3://${BUCKET}/${PREFIX}/$(cat ./artifacts/substrate/VERSION)/ + - echo "update objects in latest path" + - aws s3 sync s3://${BUCKET}/${PREFIX}/$(cat ./artifacts/substrate/VERSION)/ s3://${BUCKET}/${PREFIX}/latest/ + after_script: + - aws s3 ls s3://${BUCKET}/${PREFIX}/latest/ + --recursive --human-readable --summarize + +publish-rustdoc: + stage: publish + extends: .kubernetes-env + variables: + CI_IMAGE: node:16 + GIT_DEPTH: 100 + RUSTDOCS_DEPLOY_REFS: "master" + rules: + - if: $CI_PIPELINE_SOURCE == "pipeline" + when: never + - if: $CI_PIPELINE_SOURCE == "web" && $CI_COMMIT_REF_NAME == "master" + - if: $CI_COMMIT_REF_NAME == "master" + - if: $CI_COMMIT_REF_NAME =~ /^monthly-20[0-9]{2}-[0-9]{2}.*$/ # to support: monthly-2021-09+1 + - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 + # `needs:` can be removed after CI image gets nonroot. In this case `needs:` stops other + # artifacts from being dowloaded by this job. + needs: + - job: build-rustdoc + artifacts: true + script: + # If $CI_COMMIT_REF_NAME doesn't match one of $RUSTDOCS_DEPLOY_REFS space-separated values, we + # exit immediately. + # Putting spaces at the front and back to ensure we are not matching just any substring, but the + # whole space-separated value. + - '[[ " ${RUSTDOCS_DEPLOY_REFS} " =~ " ${CI_COMMIT_REF_NAME} " ]] || exit 0' + # setup ssh + - eval $(ssh-agent) + - ssh-add - <<< ${GITHUB_SSH_PRIV_KEY} + - mkdir ~/.ssh && touch ~/.ssh/known_hosts + - ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts + # Set git config + - git config user.email "devops-team@parity.io" + - git config user.name "${GITHUB_USER}" + - git config remote.origin.url "git@github.com:/paritytech/${CI_PROJECT_NAME}.git" + - git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*" + - git fetch origin gh-pages + # Save README and docs + - cp -r ./crate-docs/ /tmp/doc/ + - cp README.md /tmp/doc/ + # we don't need to commit changes because we copy docs to /tmp + - git checkout gh-pages --force + # Install `index-tpl-crud` and generate index.html based on RUSTDOCS_DEPLOY_REFS + - which index-tpl-crud &> /dev/null || yarn global add @substrate/index-tpl-crud + - index-tpl-crud upsert ./index.html ${CI_COMMIT_REF_NAME} + # Ensure the destination dir doesn't exist. + - rm -rf ${CI_COMMIT_REF_NAME} + - mv -f /tmp/doc ${CI_COMMIT_REF_NAME} + # Upload files + - git add --all + # `git commit` has an exit code of > 0 if there is nothing to commit. + # This causes GitLab to exit immediately and marks this job failed. + # We don't want to mark the entire job failed if there's nothing to + # publish though, hence the `|| true`. + - git commit -m "___Updated docs for ${CI_COMMIT_REF_NAME}___" || + echo "___Nothing to commit___" + - git push origin gh-pages --force + after_script: + - rm -rf .git/ ./* + +publish-draft-release: + stage: publish + image: paritytech/tools:latest + rules: + - if: $CI_COMMIT_REF_NAME =~ /^ci-release-.*$/ + - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 + script: + - ./scripts/ci/gitlab/publish_draft_release.sh + allow_failure: true diff --git a/scripts/ci/gitlab/pipeline/test.yml b/scripts/ci/gitlab/pipeline/test.yml new file mode 100644 index 0000000000000..bd2c059c3ed68 --- /dev/null +++ b/scripts/ci/gitlab/pipeline/test.yml @@ -0,0 +1,271 @@ +# This file is part of .gitlab-ci.yml +# Here are all jobs that are executed during "test" stage + +cargo-deny: + stage: test + extends: + - .docker-env + - .nightly-pipeline + script: + - cargo deny check --hide-inclusion-graph -c ./scripts/ci/deny.toml + after_script: + - echo "___The complete log is in the artifacts___" + - cargo deny check -c ./scripts/ci/deny.toml 2> deny.log + artifacts: + name: $CI_COMMIT_SHORT_SHA + expire_in: 3 days + when: always + paths: + - deny.log + # FIXME: Temporarily allow to fail. + allow_failure: true + +cargo-fmt: + stage: test + extends: + - .docker-env + - .test-refs + script: + - cargo +nightly fmt --all -- --check + +cargo-clippy: + stage: test + extends: + - .docker-env + - .test-refs + script: + - SKIP_WASM_BUILD=1 env -u RUSTFLAGS cargo +nightly clippy --all-targets + +cargo-check-nixos: + stage: test + extends: + - .docker-env + - .test-refs + before_script: [] + # Don't use CI_IMAGE here because it breaks nightly checks of paritytech/ci-linux image + image: nixos/nix + variables: + SNAP: "DUMMY" + WS_API: "DUMMY" + script: + - nix-channel --update + - nix-shell shell.nix + - nix-shell --run "cargo check --workspace --all-targets --all-features" + +cargo-check-benches: + stage: test + extends: + - .docker-env + - .test-refs + - .collect-artifacts + before_script: + # merges in the master branch on PRs + - if [ $CI_COMMIT_REF_NAME != "master" ]; then + git fetch origin +master:master; + git fetch origin +$CI_COMMIT_REF_NAME:$CI_COMMIT_REF_NAME; + git checkout master; + git config user.email "ci@gitlab.parity.io"; + git merge $CI_COMMIT_REF_NAME --verbose --no-edit; + fi + - !reference [.rust-info-script, script] + script: + - mkdir -p ./artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA + - SKIP_WASM_BUILD=1 time cargo +nightly check --benches --all + - 'cargo run --release -p node-bench -- ::node::import::native::sr25519::transfer_keep_alive::paritydb::small --json + | tee ./artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA/::node::import::native::sr25519::transfer_keep_alive::paritydb::small.json' + - 'cargo run --release -p node-bench -- ::trie::read::small --json + | tee ./artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA/::trie::read::small.json' + - sccache -s + tags: + - linux-docker-benches + +node-bench-regression-guard: + # it's not belong to `build` semantically, but dag jobs can't depend on each other + # within the single stage - https://gitlab.com/gitlab-org/gitlab/-/issues/30632 + # more: https://github.com/paritytech/substrate/pull/8519#discussion_r608012402 + stage: build + extends: + - .docker-env + - .test-refs-no-trigger-prs-only + needs: + # this is a DAG + - job: cargo-check-benches + artifacts: true + # this does not like a DAG, just polls the artifact + - project: $CI_PROJECT_PATH + job: cargo-check-benches + ref: master + artifacts: true + variables: + CI_IMAGE: "paritytech/node-bench-regression-guard:latest" + before_script: [""] + script: + - echo "------- IMPORTANT -------" + - echo "node-bench-regression-guard depends on the results of a cargo-check-benches job" + - echo "In case of this job failure, check your pipeline's cargo-check-benches" + - 'node-bench-regression-guard --reference artifacts/benches/master-* + --compare-with artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA' + +cargo-check-subkey: + stage: test + extends: + - .docker-env + - .test-refs + script: + - cd ./bin/utils/subkey + - SKIP_WASM_BUILD=1 time cargo check --release + +cargo-check-try-runtime: + stage: test + extends: + - .docker-env + - .test-refs + script: + - time cargo check --features try-runtime + +cargo-check-wasmer-sandbox: + stage: test + extends: + - .docker-env + - .test-refs + script: + - time cargo check --features wasmer-sandbox + +test-deterministic-wasm: + stage: test + extends: + - .docker-env + - .test-refs + variables: + WASM_BUILD_NO_COLOR: 1 + script: + # build runtime + - cargo build --verbose --release -p node-runtime + # make checksum + - sha256sum ./target/release/wbuild/node-runtime/target/wasm32-unknown-unknown/release/node_runtime.wasm > checksum.sha256 + # clean up – FIXME: can we reuse some of the artifacts? + - cargo clean + # build again + - cargo build --verbose --release -p node-runtime + # confirm checksum + - sha256sum -c ./checksum.sha256 + +test-linux-stable: + stage: test + extends: + - .docker-env + - .test-refs + variables: + # Enable debug assertions since we are running optimized builds for testing + # but still want to have debug assertions. + RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" + RUST_BACKTRACE: 1 + WASM_BUILD_NO_COLOR: 1 + # Ensure we run the UI tests. + RUN_UI_TESTS: 1 + script: + # this job runs all tests in former runtime-benchmarks, frame-staking and wasmtime tests + - time cargo test --workspace --locked --release --verbose --features runtime-benchmarks --manifest-path ./bin/node/cli/Cargo.toml + - time cargo test -p frame-support-test --features=conditional-storage,no-metadata-docs --manifest-path ./frame/support/test/Cargo.toml --test pallet # does not reuse cache 1 min 44 sec + - SUBSTRATE_TEST_TIMEOUT=1 time cargo test -p substrate-test-utils --release --verbose --locked -- --ignored timeout + +test-frame-examples-compile-to-wasm: + # into one job + stage: test + extends: + - .docker-env + - .test-refs + variables: + # Enable debug assertions since we are running optimized builds for testing + # but still want to have debug assertions. + RUSTFLAGS: "-Cdebug-assertions=y" + RUST_BACKTRACE: 1 + script: + - cd ./frame/examples/offchain-worker/ + - cargo +nightly build --target=wasm32-unknown-unknown --no-default-features + - cd ../basic + - cargo +nightly build --target=wasm32-unknown-unknown --no-default-features + +test-linux-stable-int: + stage: test + extends: + - .docker-env + - .test-refs + variables: + # Enable debug assertions since we are running optimized builds for testing + # but still want to have debug assertions. + RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" + RUST_BACKTRACE: 1 + WASM_BUILD_NO_COLOR: 1 + # Ensure we run the UI tests. + RUN_UI_TESTS: 1 + script: + - echo "___Logs will be partly shown at the end in case of failure.___" + - echo "___Full log will be saved to the job artifacts only in case of failure.___" + - WASM_BUILD_NO_COLOR=1 + RUST_LOG=sync=trace,consensus=trace,client=trace,state-db=trace,db=trace,forks=trace,state_db=trace,storage_cache=trace + time cargo test -p node-cli --release --verbose --locked -- --ignored + &> ${CI_COMMIT_SHORT_SHA}_int_failure.log + after_script: + - awk '/FAILED|^error\[/,0' ${CI_COMMIT_SHORT_SHA}_int_failure.log + artifacts: + name: $CI_COMMIT_SHORT_SHA + when: on_failure + expire_in: 3 days + paths: + - ${CI_COMMIT_SHORT_SHA}_int_failure.log + +check-tracing: + stage: test + extends: + - .docker-env + - .test-refs + script: + # with-tracing must be explicitly activated, we run a test to ensure this works as expected in both cases + - time cargo +nightly test --manifest-path ./primitives/tracing/Cargo.toml --no-default-features + - time cargo +nightly test --manifest-path ./primitives/tracing/Cargo.toml --no-default-features --features=with-tracing + +test-full-crypto-feature: + stage: test + extends: + - .docker-env + - .test-refs + variables: + # Enable debug assertions since we are running optimized builds for testing + # but still want to have debug assertions. + RUSTFLAGS: "-Cdebug-assertions=y" + RUST_BACKTRACE: 1 + script: + - cd primitives/core/ + - time cargo +nightly build --verbose --no-default-features --features full_crypto + - cd ../application-crypto + - time cargo +nightly build --verbose --no-default-features --features full_crypto + +test-wasmer-sandbox: + stage: test + extends: + - .docker-env + - .test-refs-wasmer-sandbox + script: + - time cargo test --release --features runtime-benchmarks,wasmer-sandbox,disable-ui-tests + +cargo-check-macos: + stage: test + extends: .test-refs-no-trigger + before_script: + - !reference [.rust-info-script, script] + script: + - SKIP_WASM_BUILD=1 time cargo check --release + tags: + - osx + +check-rustdoc: + stage: test + extends: + - .docker-env + - .test-refs + variables: + SKIP_WASM_BUILD: 1 + RUSTDOCFLAGS: "-Dwarnings" + script: + - time cargo +nightly doc --workspace --all-features --verbose --no-deps From 5636d55062f8da562858d35f189148616502812a Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Wed, 11 May 2022 12:39:39 +0200 Subject: [PATCH 210/484] jsonrpc http server: expose health endpoint (#11395) --- Cargo.lock | 36 +++++++++---------- bin/node-template/node/Cargo.toml | 2 +- bin/node/cli/Cargo.toml | 2 +- bin/node/rpc/Cargo.toml | 2 +- client/beefy/rpc/Cargo.toml | 2 +- client/consensus/babe/rpc/Cargo.toml | 2 +- client/consensus/manual-seal/Cargo.toml | 2 +- client/finality-grandpa/rpc/Cargo.toml | 2 +- client/rpc-api/Cargo.toml | 2 +- client/rpc-servers/Cargo.toml | 2 +- client/rpc-servers/src/lib.rs | 1 + client/rpc/Cargo.toml | 2 +- client/service/Cargo.toml | 2 +- client/sync-state-rpc/Cargo.toml | 2 +- frame/contracts/rpc/Cargo.toml | 2 +- frame/merkle-mountain-range/rpc/Cargo.toml | 2 +- frame/transaction-payment/rpc/Cargo.toml | 2 +- utils/frame/remote-externalities/Cargo.toml | 2 +- .../rpc/state-trie-migration-rpc/Cargo.toml | 2 +- utils/frame/rpc/support/Cargo.toml | 4 +-- utils/frame/rpc/system/Cargo.toml | 2 +- utils/frame/try-runtime/cli/Cargo.toml | 2 +- 22 files changed, 40 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1c73e59ff10d2..68272e1df9c8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3173,9 +3173,9 @@ dependencies = [ [[package]] name = "jsonrpsee" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad6f9ff3481f3069c92474b697c104502f7e9191d29b34bfa38ae9a19415f1cd" +checksum = "eae63f7fdeb51700b35e9b28bf92e8d233951590968c186ed79510b6c12fa3d9" dependencies = [ "jsonrpsee-core", "jsonrpsee-http-server", @@ -3188,9 +3188,9 @@ dependencies = [ [[package]] name = "jsonrpsee-client-transport" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4358e100faf43b2f3b7b0ecf0ad4ce3e6275fe12fda8428dedda2979751dd184" +checksum = "32feb1f2f0b5ce37a03b96a988a6dadccc3f529a2f930356bac93f13c09cf385" dependencies = [ "futures-util", "http", @@ -3209,9 +3209,9 @@ dependencies = [ [[package]] name = "jsonrpsee-core" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d26ab3868749d6f716345a5fbd3334a100c0709fe464bd9189ee9d78adcde" +checksum = "31e6b13067b615dd050ced7c19517a52cde490eee2c754d5447ce513f2275f7d" dependencies = [ "anyhow", "arrayvec 0.7.1", @@ -3236,9 +3236,9 @@ dependencies = [ [[package]] name = "jsonrpsee-http-server" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee87f19a7a01a55248fc4b4861d822331c4fd60151d99e7ac9c6771999132671" +checksum = "b34f1090bdc8f7f14ad8811fc84501867c23a9046ce79d49c0cd929a256c501e" dependencies = [ "futures-channel", "futures-util", @@ -3255,9 +3255,9 @@ dependencies = [ [[package]] name = "jsonrpsee-proc-macros" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75da57d54817577801c2f7a1b638610819dfd86f0470c21a2af81b06eb41ba6" +checksum = "5d8dc7a8b629e371cd5ca9d128883763ae00c5b63158ace4a6a61345726a21b7" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -3267,9 +3267,9 @@ dependencies = [ [[package]] name = "jsonrpsee-types" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5fe5a629443d17a30ff564881ba68881a710fd7eb02a538087b0bc51cb4962c" +checksum = "44f1835f131e77cd766b4dcb025873944cb1e479cd5debb639e2dc11f90df24a" dependencies = [ "anyhow", "beef", @@ -3281,9 +3281,9 @@ dependencies = [ [[package]] name = "jsonrpsee-ws-client" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba31eb2b9a4b73d8833f53fe55e579516289f8b31adb6104b3dbc629755acf7d" +checksum = "9d75df866743c9733b3e2f5421e56df2f5b4630f7de39f82c44eaab350604926" dependencies = [ "jsonrpsee-client-transport", "jsonrpsee-core", @@ -3292,9 +3292,9 @@ dependencies = [ [[package]] name = "jsonrpsee-ws-server" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "179fe584af5c0145f922c581770d073c661a514ae6cdfa5b1a0bce41fdfdf646" +checksum = "099971913436e7f6b1bc80180d4e5f014ec944660636da45d2f372c23d6308c3" dependencies = [ "futures-channel", "futures-util", @@ -11264,9 +11264,9 @@ version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ee73e6e4924fe940354b8d4d98cad5231175d615cd855b758adc658c0aac6a0" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", "digest 0.10.3", - "rand 0.6.5", + "rand 0.8.4", "static_assertions", ] diff --git a/bin/node-template/node/Cargo.toml b/bin/node-template/node/Cargo.toml index ab91dc7990380..7ae343fd56dbe 100644 --- a/bin/node-template/node/Cargo.toml +++ b/bin/node-template/node/Cargo.toml @@ -42,7 +42,7 @@ frame-system = { version = "4.0.0-dev", path = "../../../frame/system" } pallet-transaction-payment = { version = "4.0.0-dev", default-features = false, path = "../../../frame/transaction-payment" } # These dependencies are used for the node template's RPCs -jsonrpsee = { version = "0.12.0", features = ["server"] } +jsonrpsee = { version = "0.13.0", features = ["server"] } sc-rpc = { version = "4.0.0-dev", path = "../../../client/rpc" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sc-rpc-api = { version = "0.10.0-dev", path = "../../../client/rpc-api" } diff --git a/bin/node/cli/Cargo.toml b/bin/node/cli/Cargo.toml index c18f2f5d1a108..b574fa948b857 100644 --- a/bin/node/cli/Cargo.toml +++ b/bin/node/cli/Cargo.toml @@ -37,7 +37,7 @@ crate-type = ["cdylib", "rlib"] clap = { version = "3.1.6", features = ["derive"], optional = true } codec = { package = "parity-scale-codec", version = "3.0.0" } serde = { version = "1.0.136", features = ["derive"] } -jsonrpsee = { version = "0.12.0", features = ["server"] } +jsonrpsee = { version = "0.13.0", features = ["server"] } futures = "0.3.21" hex-literal = "0.3.4" log = "0.4.16" diff --git a/bin/node/rpc/Cargo.toml b/bin/node/rpc/Cargo.toml index 9520c621d3165..ea6e8a5eb9c90 100644 --- a/bin/node/rpc/Cargo.toml +++ b/bin/node/rpc/Cargo.toml @@ -11,7 +11,7 @@ repository = "https://github.com/paritytech/substrate/" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { version = "0.12.0", features = ["server"] } +jsonrpsee = { version = "0.13.0", features = ["server"] } node-primitives = { version = "2.0.0", path = "../primitives" } pallet-contracts-rpc = { version = "4.0.0-dev", path = "../../../frame/contracts/rpc/" } pallet-mmr-rpc = { version = "3.0.0", path = "../../../frame/merkle-mountain-range/rpc/" } diff --git a/client/beefy/rpc/Cargo.toml b/client/beefy/rpc/Cargo.toml index f8ca6470f267a..98596c36f84d2 100644 --- a/client/beefy/rpc/Cargo.toml +++ b/client/beefy/rpc/Cargo.toml @@ -11,7 +11,7 @@ homepage = "https://substrate.io" [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } futures = "0.3.21" -jsonrpsee = { version = "0.12.0", features = ["server", "macros"] } +jsonrpsee = { version = "0.13.0", features = ["server", "macros"] } log = "0.4" parking_lot = "0.12.0" serde = { version = "1.0.136", features = ["derive"] } diff --git a/client/consensus/babe/rpc/Cargo.toml b/client/consensus/babe/rpc/Cargo.toml index 4be5d1f8bba90..4fb45d3817036 100644 --- a/client/consensus/babe/rpc/Cargo.toml +++ b/client/consensus/babe/rpc/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { version = "0.12.0", features = ["server", "macros"] } +jsonrpsee = { version = "0.13.0", features = ["server", "macros"] } futures = "0.3.21" serde = { version = "1.0.136", features = ["derive"] } thiserror = "1.0" diff --git a/client/consensus/manual-seal/Cargo.toml b/client/consensus/manual-seal/Cargo.toml index e8f4e20ab0e55..9806fbdcaac3c 100644 --- a/client/consensus/manual-seal/Cargo.toml +++ b/client/consensus/manual-seal/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { version = "0.12.0", features = ["server", "macros"] } +jsonrpsee = { version = "0.13.0", features = ["server", "macros"] } assert_matches = "1.3.0" async-trait = "0.1.50" codec = { package = "parity-scale-codec", version = "3.0.0" } diff --git a/client/finality-grandpa/rpc/Cargo.toml b/client/finality-grandpa/rpc/Cargo.toml index c124712e3fa84..0648c874f7dff 100644 --- a/client/finality-grandpa/rpc/Cargo.toml +++ b/client/finality-grandpa/rpc/Cargo.toml @@ -12,7 +12,7 @@ homepage = "https://substrate.io" [dependencies] finality-grandpa = { version = "0.15.0", features = ["derive-codec"] } futures = "0.3.16" -jsonrpsee = { version = "0.12.0", features = ["server", "macros"] } +jsonrpsee = { version = "0.13.0", features = ["server", "macros"] } log = "0.4.8" parity-scale-codec = { version = "3.0.0", features = ["derive"] } serde = { version = "1.0.105", features = ["derive"] } diff --git a/client/rpc-api/Cargo.toml b/client/rpc-api/Cargo.toml index f8dfaab2a58a3..a9d5101733342 100644 --- a/client/rpc-api/Cargo.toml +++ b/client/rpc-api/Cargo.toml @@ -28,4 +28,4 @@ sp-rpc = { version = "6.0.0", path = "../../primitives/rpc" } sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } sp-version = { version = "5.0.0", path = "../../primitives/version" } -jsonrpsee = { version = "0.12.0", features = ["server", "macros"] } +jsonrpsee = { version = "0.13.0", features = ["server", "macros"] } diff --git a/client/rpc-servers/Cargo.toml b/client/rpc-servers/Cargo.toml index ad01f3bdd6199..7d6ca821adc3c 100644 --- a/client/rpc-servers/Cargo.toml +++ b/client/rpc-servers/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] futures = "0.3.21" -jsonrpsee = { version = "0.12.0", features = ["server"] } +jsonrpsee = { version = "0.13.0", features = ["server"] } log = "0.4.16" serde_json = "1.0.79" tokio = { version = "1.17.0", features = ["parking_lot"] } diff --git a/client/rpc-servers/src/lib.rs b/client/rpc-servers/src/lib.rs index 4f69413895a9b..78b8e16f8f13d 100644 --- a/client/rpc-servers/src/lib.rs +++ b/client/rpc-servers/src/lib.rs @@ -103,6 +103,7 @@ pub async fn start_http( .max_request_body_size(max_payload_in as u32) .max_response_body_size(max_payload_out as u32) .set_access_control(acl.build()) + .health_api("/health", "system_health") .custom_tokio_runtime(rt); let rpc_api = build_rpc_api(rpc_api); diff --git a/client/rpc/Cargo.toml b/client/rpc/Cargo.toml index 515de401119d4..50b0dff20cfcd 100644 --- a/client/rpc/Cargo.toml +++ b/client/rpc/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.21" hash-db = { version = "0.15.2", default-features = false } -jsonrpsee = { version = "0.12.0", features = ["server"] } +jsonrpsee = { version = "0.13.0", features = ["server"] } lazy_static = { version = "1.4.0", optional = true } log = "0.4.16" parking_lot = "0.12.0" diff --git a/client/service/Cargo.toml b/client/service/Cargo.toml index a62298a260aa4..d59e8e8d19c4c 100644 --- a/client/service/Cargo.toml +++ b/client/service/Cargo.toml @@ -22,7 +22,7 @@ wasmtime = ["sc-executor/wasmtime"] test-helpers = [] [dependencies] -jsonrpsee = { version = "0.12.0", features = ["server"] } +jsonrpsee = { version = "0.13.0", features = ["server"] } thiserror = "1.0.30" futures = "0.3.21" rand = "0.7.3" diff --git a/client/sync-state-rpc/Cargo.toml b/client/sync-state-rpc/Cargo.toml index f42c307ffa84c..3a2da48907a36 100644 --- a/client/sync-state-rpc/Cargo.toml +++ b/client/sync-state-rpc/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } -jsonrpsee = { version = "0.12.0", features = ["server", "macros"] } +jsonrpsee = { version = "0.13.0", features = ["server", "macros"] } serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0.79" thiserror = "1.0.30" diff --git a/frame/contracts/rpc/Cargo.toml b/frame/contracts/rpc/Cargo.toml index 36f6c06328501..c8777d50b90a1 100644 --- a/frame/contracts/rpc/Cargo.toml +++ b/frame/contracts/rpc/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } -jsonrpsee = { version = "0.12.0", features = ["server", "macros"] } +jsonrpsee = { version = "0.13.0", features = ["server", "macros"] } serde = { version = "1", features = ["derive"] } # Substrate Dependencies diff --git a/frame/merkle-mountain-range/rpc/Cargo.toml b/frame/merkle-mountain-range/rpc/Cargo.toml index 2d3bfebc6633f..75238e5e329eb 100644 --- a/frame/merkle-mountain-range/rpc/Cargo.toml +++ b/frame/merkle-mountain-range/rpc/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } -jsonrpsee = { version = "0.12.0", features = ["server", "macros"] } +jsonrpsee = { version = "0.13.0", features = ["server", "macros"] } serde = { version = "1.0.136", features = ["derive"] } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } diff --git a/frame/transaction-payment/rpc/Cargo.toml b/frame/transaction-payment/rpc/Cargo.toml index 6133d3a4b6da1..f58049a0062ab 100644 --- a/frame/transaction-payment/rpc/Cargo.toml +++ b/frame/transaction-payment/rpc/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } -jsonrpsee = { version = "0.12.0", features = ["server", "macros"] } +jsonrpsee = { version = "0.13.0", features = ["server", "macros"] } pallet-transaction-payment-rpc-runtime-api = { version = "4.0.0-dev", path = "./runtime-api" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } diff --git a/utils/frame/remote-externalities/Cargo.toml b/utils/frame/remote-externalities/Cargo.toml index 4a931470eafac..f3876e41f4d02 100644 --- a/utils/frame/remote-externalities/Cargo.toml +++ b/utils/frame/remote-externalities/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } env_logger = "0.9" -jsonrpsee = { version = "0.12.0", features = ["ws-client", "macros"] } +jsonrpsee = { version = "0.13.0", features = ["ws-client", "macros"] } log = "0.4.16" serde = "1.0.136" serde_json = "1.0" diff --git a/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml b/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml index 726cc9f989ced..71fadfdbc041c 100644 --- a/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml +++ b/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml @@ -25,7 +25,7 @@ sp-state-machine = { path = "../../../../primitives/state-machine" } sp-trie = { path = "../../../../primitives/trie" } trie-db = { version = "0.23.1" } -jsonrpsee = { version = "0.12.0", features = ["server", "macros"] } +jsonrpsee = { version = "0.13.0", features = ["server", "macros"] } # Substrate Dependencies sc-client-api = { version = "4.0.0-dev", path = "../../../../client/api" } diff --git a/utils/frame/rpc/support/Cargo.toml b/utils/frame/rpc/support/Cargo.toml index 0c6d082406421..262da29b01d9a 100644 --- a/utils/frame/rpc/support/Cargo.toml +++ b/utils/frame/rpc/support/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.21" -jsonrpsee = { version = "0.12.0", features = ["jsonrpsee-types"] } +jsonrpsee = { version = "0.13.0", features = ["jsonrpsee-types"] } serde = "1" frame-support = { version = "4.0.0-dev", path = "../../../../frame/support" } sc-rpc-api = { version = "0.10.0-dev", path = "../../../../client/rpc-api" } @@ -25,6 +25,6 @@ sp-storage = { version = "6.0.0", path = "../../../../primitives/storage" } [dev-dependencies] scale-info = "2.0.1" -jsonrpsee = { version = "0.12.0", features = ["ws-client", "jsonrpsee-types"] } +jsonrpsee = { version = "0.13.0", features = ["ws-client", "jsonrpsee-types"] } tokio = "1.17.0" frame-system = { version = "4.0.0-dev", path = "../../../../frame/system" } diff --git a/utils/frame/rpc/system/Cargo.toml b/utils/frame/rpc/system/Cargo.toml index c95ae4793ca6a..10163aacd1969 100644 --- a/utils/frame/rpc/system/Cargo.toml +++ b/utils/frame/rpc/system/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] serde_json = "1" codec = { package = "parity-scale-codec", version = "3.0.0" } -jsonrpsee = { version = "0.12.0", features = ["server"] } +jsonrpsee = { version = "0.13.0", features = ["server"] } futures = "0.3.21" log = "0.4.16" frame-system-rpc-runtime-api = { version = "4.0.0-dev", path = "../../../../frame/system/rpc/runtime-api" } diff --git a/utils/frame/try-runtime/cli/Cargo.toml b/utils/frame/try-runtime/cli/Cargo.toml index a5e658fc68476..4bbcaf1c216a5 100644 --- a/utils/frame/try-runtime/cli/Cargo.toml +++ b/utils/frame/try-runtime/cli/Cargo.toml @@ -19,7 +19,7 @@ parity-scale-codec = "3.0.0" serde = "1.0.136" zstd = { version = "0.10.0", default-features = false } remote-externalities = { version = "0.10.0-dev", path = "../../remote-externalities" } -jsonrpsee = { version = "0.12.0", default-features = false, features = ["ws-client"] } +jsonrpsee = { version = "0.13.0", default-features = false, features = ["ws-client"] } sc-chain-spec = { version = "4.0.0-dev", path = "../../../../client/chain-spec" } sc-cli = { version = "0.10.0-dev", path = "../../../../client/cli" } sc-executor = { version = "0.10.0-dev", path = "../../../../client/executor" } From a2eed806b8d682065aa5f66fec913015c4c6b9a3 Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Wed, 11 May 2022 07:27:26 -0400 Subject: [PATCH 211/484] Only read storage after origin check (#11391) * only read storage after origin check * fmt --- frame/nomination-pools/src/lib.rs | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 017eac8ee2aeb..0fff470eec43b 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1854,22 +1854,17 @@ pub mod pallet { nominator: Option, state_toggler: Option, ) -> DispatchResult { - let o1 = origin; - let o2 = o1.clone(); - - let mut bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; - let is_pool_root = || -> Result<(), sp_runtime::DispatchError> { - let who = ensure_signed(o1)?; - ensure!(bonded_pool.can_update_roles(&who), Error::::DoesNotHavePermission); - Ok(()) - }; - let is_root = || -> Result<(), sp_runtime::DispatchError> { - ensure_root(o2)?; - Ok(()) + let mut bonded_pool = match ensure_root(origin.clone()) { + Ok(()) => BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?, + Err(frame_support::error::BadOrigin) => { + let who = ensure_signed(origin)?; + let bonded_pool = + BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; + ensure!(bonded_pool.can_update_roles(&who), Error::::DoesNotHavePermission); + bonded_pool + }, }; - let _ = is_root().or_else(|_| is_pool_root())?; - match root { None => (), Some(v) => bonded_pool.roles.root = v, From 4a71b7aedf4a3d274ec110cf3805033871fcb363 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Wed, 11 May 2022 18:45:59 +0100 Subject: [PATCH 212/484] New sub-trait to mock staking miner (#11350) * new separate config trait for staking miner * fix some docs and stuff * relax trait bounds * some cleanup * Update frame/election-provider-multi-phase/src/unsigned.rs * add comment * self review and fix build * fix docs Co-authored-by: Niklas Adolfsson --- bin/node/runtime/src/lib.rs | 29 +- .../src/benchmarking.rs | 47 +- .../src/helpers.rs | 48 +- .../election-provider-multi-phase/src/lib.rs | 86 ++- .../election-provider-multi-phase/src/mock.rs | 116 +--- .../src/signed.rs | 36 +- .../src/unsigned.rs | 580 ++++++++++-------- 7 files changed, 478 insertions(+), 464 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index fee288cb2b7f8..019c14b2c7dfd 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -23,7 +23,9 @@ #![recursion_limit = "256"] use codec::{Decode, Encode, MaxEncodedLen}; -use frame_election_provider_support::{onchain, ExtendedBalance, SequentialPhragmen, VoteWeight}; +use frame_election_provider_support::{ + onchain, ElectionDataProvider, ExtendedBalance, SequentialPhragmen, VoteWeight, +}; use frame_support::{ construct_runtime, pallet_prelude::Get, @@ -660,6 +662,25 @@ impl onchain::BoundedConfig for OnChainSeqPhragmen { type TargetsBound = ConstU32<2_000>; } +impl pallet_election_provider_multi_phase::MinerConfig for Runtime { + type AccountId = AccountId; + type MaxLength = MinerMaxLength; + type MaxWeight = MinerMaxWeight; + type Solution = NposSolution16; + type MaxVotesPerVoter = + <::DataProvider as ElectionDataProvider>::MaxVotesPerVoter; + + // The unsigned submissions have to respect the weight of the submit_unsigned call, thus their + // weight estimate function is wired to this call's weight. + fn solution_weight(v: u32, t: u32, a: u32, d: u32) -> Weight { + < + ::WeightInfo + as + pallet_election_provider_multi_phase::WeightInfo + >::submit_unsigned(v, t, a, d) + } +} + impl pallet_election_provider_multi_phase::Config for Runtime { type Event = Event; type Currency = Balances; @@ -669,9 +690,8 @@ impl pallet_election_provider_multi_phase::Config for Runtime { type BetterUnsignedThreshold = BetterUnsignedThreshold; type BetterSignedThreshold = (); type OffchainRepeat = OffchainRepeat; - type MinerMaxWeight = MinerMaxWeight; - type MinerMaxLength = MinerMaxLength; type MinerTxPriority = MultiPhaseUnsignedPriority; + type MinerConfig = Self; type SignedMaxSubmissions = ConstU32<10>; type SignedRewardBase = SignedRewardBase; type SignedDepositBase = SignedDepositBase; @@ -682,7 +702,6 @@ impl pallet_election_provider_multi_phase::Config for Runtime { type SlashHandler = (); // burn slashes type RewardHandler = (); // nothing to do upon rewards type DataProvider = Staking; - type Solution = NposSolution16; type Fallback = onchain::BoundedExecution; type GovernanceFallback = onchain::BoundedExecution; type Solver = SequentialPhragmen, OffchainRandomBalancing>; @@ -2017,7 +2036,7 @@ mod tests { #[test] fn perbill_as_onchain_accuracy() { type OnChainAccuracy = - <::Solution as NposSolution>::Accuracy; + <::Solution as NposSolution>::Accuracy; let maximum_chain_accuracy: Vec> = (0..MaxNominations::get()) .map(|_| >::from(OnChainAccuracy::one().deconstruct())) .collect(); diff --git a/frame/election-provider-multi-phase/src/benchmarking.rs b/frame/election-provider-multi-phase/src/benchmarking.rs index a1c643754158b..a8195df7305ff 100644 --- a/frame/election-provider-multi-phase/src/benchmarking.rs +++ b/frame/election-provider-multi-phase/src/benchmarking.rs @@ -39,15 +39,15 @@ fn solution_with_size( size: SolutionOrSnapshotSize, active_voters_count: u32, desired_targets: u32, -) -> Result>, &'static str> { +) -> Result>, &'static str> { ensure!(size.targets >= desired_targets, "must have enough targets"); ensure!( - size.targets >= (>::LIMIT * 2) as u32, + size.targets >= (>::LIMIT * 2) as u32, "must have enough targets for unique votes." ); ensure!(size.voters >= active_voters_count, "must have enough voters"); ensure!( - (>::LIMIT as u32) < desired_targets, + (>::LIMIT as u32) < desired_targets, "must have enough winners to give them votes." ); @@ -74,10 +74,10 @@ fn solution_with_size( // chose a random subset of winners. let winner_votes: BoundedVec<_, _> = winners .as_slice() - .choose_multiple(&mut rng, >::LIMIT) + .choose_multiple(&mut rng, >::LIMIT) .cloned() .try_collect() - .expect(">::LIMIT is the correct bound; qed."); + .expect(">::LIMIT is the correct bound; qed."); let voter = frame_benchmarking::account::("Voter", i, SEED); (voter, stake, winner_votes) }) @@ -92,10 +92,10 @@ fn solution_with_size( let rest_voters = (active_voters_count..size.voters) .map(|i| { let votes: BoundedVec<_, _> = (&non_winners) - .choose_multiple(&mut rng, >::LIMIT) + .choose_multiple(&mut rng, >::LIMIT) .cloned() .try_collect() - .expect(">::LIMIT is the correct bound; qed."); + .expect(">::LIMIT is the correct bound; qed."); let voter = frame_benchmarking::account::("Voter", i, SEED); (voter, stake, votes) }) @@ -120,12 +120,12 @@ fn solution_with_size( // down the road. T::DataProvider::put_snapshot(all_voters.clone(), targets.clone(), Some(stake)); - let cache = helpers::generate_voter_cache::(&all_voters); - let stake_of = helpers::stake_of_fn::(&all_voters, &cache); - let voter_index = helpers::voter_index_fn::(&cache); - let target_index = helpers::target_index_fn::(&targets); - let voter_at = helpers::voter_at_fn::(&all_voters); - let target_at = helpers::target_at_fn::(&targets); + let cache = helpers::generate_voter_cache::(&all_voters); + let stake_of = helpers::stake_of_fn::(&all_voters, &cache); + let voter_index = helpers::voter_index_fn::(&cache); + let target_index = helpers::target_index_fn::(&targets); + let voter_at = helpers::voter_at_fn::(&all_voters); + let target_at = helpers::target_at_fn::(&targets); let assignments = active_voters .iter() @@ -143,7 +143,8 @@ fn solution_with_size( .collect::>(); let solution = - >::from_assignment(&assignments, &voter_index, &target_index).unwrap(); + >::from_assignment(&assignments, &voter_index, &target_index) + .unwrap(); let score = solution.clone().score(stake_of, voter_at, target_at).unwrap(); let round = >::round(); @@ -480,14 +481,14 @@ frame_benchmarking::benchmarks! { let witness = SolutionOrSnapshotSize { voters: v, targets: t }; let RawSolution { solution, .. } = solution_with_size::(witness, a, d)?; let RoundSnapshot { voters, targets } = MultiPhase::::snapshot().ok_or("snapshot missing")?; - let voter_at = helpers::voter_at_fn::(&voters); - let target_at = helpers::target_at_fn::(&targets); + let voter_at = helpers::voter_at_fn::(&voters); + let target_at = helpers::target_at_fn::(&targets); let mut assignments = solution.into_assignment(voter_at, target_at).expect("solution generated by `solution_with_size` must be valid."); // make a voter cache and some helper functions for access - let cache = helpers::generate_voter_cache::(&voters); - let voter_index = helpers::voter_index_fn::(&cache); - let target_index = helpers::target_index_fn::(&targets); + let cache = helpers::generate_voter_cache::(&voters); + let voter_index = helpers::voter_index_fn::(&cache); + let target_index = helpers::target_index_fn::(&targets); // sort assignments by decreasing voter stake assignments.sort_by_key(|crate::unsigned::Assignment:: { who, .. }| { @@ -504,21 +505,21 @@ frame_benchmarking::benchmarks! { .collect::, _>>() .unwrap(); - let encoded_size_of = |assignments: &[IndexAssignmentOf]| { - SolutionOf::::try_from(assignments).map(|solution| solution.encoded_size()) + let encoded_size_of = |assignments: &[IndexAssignmentOf]| { + SolutionOf::::try_from(assignments).map(|solution| solution.encoded_size()) }; let desired_size = Percent::from_percent(100 - f.saturated_into::()) .mul_ceil(encoded_size_of(index_assignments.as_slice()).unwrap()); log!(trace, "desired_size = {}", desired_size); }: { - MultiPhase::::trim_assignments_length( + crate::Miner::::trim_assignments_length( desired_size.saturated_into(), &mut index_assignments, &encoded_size_of, ).unwrap(); } verify { - let solution = SolutionOf::::try_from(index_assignments.as_slice()).unwrap(); + let solution = SolutionOf::::try_from(index_assignments.as_slice()).unwrap(); let encoding = solution.encode(); log!( trace, diff --git a/frame/election-provider-multi-phase/src/helpers.rs b/frame/election-provider-multi-phase/src/helpers.rs index 48da194cc65d9..0a7240c5d27af 100644 --- a/frame/election-provider-multi-phase/src/helpers.rs +++ b/frame/election-provider-multi-phase/src/helpers.rs @@ -17,7 +17,10 @@ //! Some helper functions/macros for this crate. -use crate::{unsigned::VoterOf, Config, SolutionTargetIndexOf, SolutionVoterIndexOf, VoteWeight}; +use crate::{ + unsigned::{MinerConfig, MinerVoterOf}, + SolutionTargetIndexOf, SolutionVoterIndexOf, VoteWeight, +}; use sp_std::{collections::btree_map::BTreeMap, prelude::*}; #[macro_export] @@ -30,11 +33,22 @@ macro_rules! log { }; } +// This is only useful for a context where a `` is not in scope. +#[macro_export] +macro_rules! log_no_system { + ($level:tt, $pattern:expr $(, $values:expr)* $(,)?) => { + log::$level!( + target: $crate::LOG_TARGET, + concat!("🗳 ", $pattern) $(, $values)* + ) + }; +} + /// Generate a btree-map cache of the voters and their indices. /// /// This can be used to efficiently build index getter closures. -pub fn generate_voter_cache( - snapshot: &Vec>, +pub fn generate_voter_cache( + snapshot: &Vec>, ) -> BTreeMap { let mut cache: BTreeMap = BTreeMap::new(); snapshot.iter().enumerate().for_each(|(i, (x, _, _))| { @@ -54,7 +68,7 @@ pub fn generate_voter_cache( /// ## Warning /// /// Note that this will represent the snapshot data from which the `cache` is generated. -pub fn voter_index_fn( +pub fn voter_index_fn( cache: &BTreeMap, ) -> impl Fn(&T::AccountId) -> Option> + '_ { move |who| { @@ -68,7 +82,7 @@ pub fn voter_index_fn( /// /// Same as [`voter_index_fn`] but the returned function owns all its necessary data; nothing is /// borrowed. -pub fn voter_index_fn_owned( +pub fn voter_index_fn_owned( cache: BTreeMap, ) -> impl Fn(&T::AccountId) -> Option> { move |who| { @@ -83,7 +97,7 @@ pub fn voter_index_fn_owned( /// ## Warning /// /// Note that this will represent the snapshot data from which the `cache` is generated. -pub fn voter_index_fn_usize( +pub fn voter_index_fn_usize( cache: &BTreeMap, ) -> impl Fn(&T::AccountId) -> Option + '_ { move |who| cache.get(who).cloned() @@ -96,8 +110,8 @@ pub fn voter_index_fn_usize( /// /// Not meant to be used in production. #[cfg(test)] -pub fn voter_index_fn_linear( - snapshot: &Vec>, +pub fn voter_index_fn_linear( + snapshot: &Vec>, ) -> impl Fn(&T::AccountId) -> Option> + '_ { move |who| { snapshot @@ -114,7 +128,7 @@ pub fn voter_index_fn_linear( /// Note: to the extent possible, the returned function should be cached and reused. Producing that /// function requires a `O(n log n)` data transform. Each invocation of that function completes /// in `O(log n)`. -pub fn target_index_fn( +pub fn target_index_fn( snapshot: &Vec, ) -> impl Fn(&T::AccountId) -> Option> + '_ { let cache: BTreeMap<_, _> = @@ -134,7 +148,7 @@ pub fn target_index_fn( /// /// Not meant to be used in production. #[cfg(test)] -pub fn target_index_fn_linear( +pub fn target_index_fn_linear( snapshot: &Vec, ) -> impl Fn(&T::AccountId) -> Option> + '_ { move |who| { @@ -147,8 +161,8 @@ pub fn target_index_fn_linear( /// Create a function that can map a voter index ([`SolutionVoterIndexOf`]) to the actual voter /// account using a linearly indexible snapshot. -pub fn voter_at_fn( - snapshot: &Vec>, +pub fn voter_at_fn( + snapshot: &Vec>, ) -> impl Fn(SolutionVoterIndexOf) -> Option + '_ { move |i| { as TryInto>::try_into(i) @@ -159,7 +173,7 @@ pub fn voter_at_fn( /// Create a function that can map a target index ([`SolutionTargetIndexOf`]) to the actual target /// account using a linearly indexible snapshot. -pub fn target_at_fn( +pub fn target_at_fn( snapshot: &Vec, ) -> impl Fn(SolutionTargetIndexOf) -> Option + '_ { move |i| { @@ -173,8 +187,8 @@ pub fn target_at_fn( /// /// This is not optimized and uses a linear search. #[cfg(test)] -pub fn stake_of_fn_linear( - snapshot: &Vec>, +pub fn stake_of_fn_linear( + snapshot: &Vec>, ) -> impl Fn(&T::AccountId) -> VoteWeight + '_ { move |who| { snapshot @@ -191,8 +205,8 @@ pub fn stake_of_fn_linear( /// /// The cache need must be derived from the same snapshot. Zero is returned if a voter is /// non-existent. -pub fn stake_of_fn<'a, T: Config>( - snapshot: &'a Vec>, +pub fn stake_of_fn<'a, T: MinerConfig>( + snapshot: &'a Vec>, cache: &'a BTreeMap, ) -> impl Fn(&T::AccountId) -> VoteWeight + 'a { move |who| { diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 4aea5b9da4794..36d2373c9f623 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -103,7 +103,7 @@ //! //! Validators will only submit solutions if the one that they have computed is sufficiently better //! than the best queued one (see [`pallet::Config::BetterUnsignedThreshold`]) and will limit the -//! weight of the solution to [`pallet::Config::MinerMaxWeight`]. +//! weight of the solution to [`MinerConfig::MaxWeight`]. //! //! The unsigned phase can be made passive depending on how the previous signed phase went, by //! setting the first inner value of [`Phase`] to `false`. For now, the signed phase is always @@ -276,16 +276,18 @@ pub use signed::{ BalanceOf, NegativeImbalanceOf, PositiveImbalanceOf, SignedSubmission, SignedSubmissionOf, SignedSubmissions, SubmissionIndicesOf, }; +pub use unsigned::{Miner, MinerConfig}; /// The solution type used by this crate. -pub type SolutionOf = ::Solution; +pub type SolutionOf = ::Solution; /// The voter index. Derived from [`SolutionOf`]. pub type SolutionVoterIndexOf = as NposSolution>::VoterIndex; /// The target index. Derived from [`SolutionOf`]. pub type SolutionTargetIndexOf = as NposSolution>::TargetIndex; /// The accuracy of the election, when submitted from offchain. Derived from [`SolutionOf`]. -pub type SolutionAccuracyOf = as NposSolution>::Accuracy; +pub type SolutionAccuracyOf = + ::MinerConfig> as NposSolution>::Accuracy; /// The fallback election type. pub type FallbackErrorOf = <::Fallback as ElectionProvider>::Error; @@ -488,7 +490,7 @@ pub enum ElectionError { /// An error happened in the feasibility check sub-system. Feasibility(FeasibilityError), /// An error in the miner (offchain) sub-system. - Miner(unsigned::MinerError), + Miner(unsigned::MinerError), /// An error happened in the data provider. DataProvider(&'static str), /// An error nested in the fallback. @@ -520,8 +522,8 @@ impl From for ElectionError { } } -impl From> for ElectionError { - fn from(e: unsigned::MinerError) -> Self { +impl From for ElectionError { + fn from(e: unsigned::MinerError) -> Self { ElectionError::Miner(e) } } @@ -605,12 +607,14 @@ pub mod pallet { #[pallet::constant] type MinerTxPriority: Get; - /// Maximum weight that the miner should consume. + /// Configurations of the embedded miner. /// - /// The miner will ensure that the total weight of the unsigned solution will not exceed - /// this value, based on [`WeightInfo::submit_unsigned`]. - #[pallet::constant] - type MinerMaxWeight: Get; + /// Any external software implementing this can use the [`unsigned::Miner`] type provided, + /// which can mine new solutions and trim them accordingly. + type MinerConfig: crate::unsigned::MinerConfig< + AccountId = Self::AccountId, + MaxVotesPerVoter = ::MaxVotesPerVoter, + >; /// Maximum number of signed submissions that can be queued. /// @@ -624,7 +628,9 @@ pub mod pallet { /// Maximum weight of a signed solution. /// - /// This should probably be similar to [`Config::MinerMaxWeight`]. + /// If [`Config::MinerConfig`] is being implemented to submit signed solutions (outside of + /// this pallet), then [`MinerConfig::solution_weight`] is used to compare against + /// this value. #[pallet::constant] type SignedMaxWeight: Get; @@ -652,11 +658,11 @@ pub mod pallet { /// are only over a single block, but once multi-block elections are introduced they will /// take place over multiple blocks. #[pallet::constant] - type MaxElectingVoters: Get>; + type MaxElectingVoters: Get>; /// The maximum number of electable targets to put in the snapshot. #[pallet::constant] - type MaxElectableTargets: Get>; + type MaxElectableTargets: Get>; /// Handler for the slashed deposits. type SlashHandler: OnUnbalanced>; @@ -664,30 +670,12 @@ pub mod pallet { /// Handler for the rewards. type RewardHandler: OnUnbalanced>; - /// Maximum length (bytes) that the mined solution should consume. - /// - /// The miner will ensure that the total length of the unsigned solution will not exceed - /// this value. - #[pallet::constant] - type MinerMaxLength: Get; - /// Something that will provide the election data. type DataProvider: ElectionDataProvider< AccountId = Self::AccountId, BlockNumber = Self::BlockNumber, >; - /// The solution type. - type Solution: codec::Codec - + Default - + PartialEq - + Eq - + Clone - + sp_std::fmt::Debug - + Ord - + NposSolution - + TypeInfo; - /// Configuration for the fallback. type Fallback: InstantElectionProvider< AccountId = Self::AccountId, @@ -824,12 +812,12 @@ pub mod pallet { use sp_std::mem::size_of; // The index type of both voters and targets need to be smaller than that of usize (very // unlikely to be the case, but anyhow).. - assert!(size_of::>() <= size_of::()); - assert!(size_of::>() <= size_of::()); + assert!(size_of::>() <= size_of::()); + assert!(size_of::>() <= size_of::()); // ---------------------------- // Based on the requirements of [`sp_npos_elections::Assignment::try_normalize`]. - let max_vote: usize = as NposSolution>::LIMIT; + let max_vote: usize = as NposSolution>::LIMIT; // 2. Maximum sum of [SolutionAccuracy; 16] must fit into `UpperOf`. let maximum_chain_accuracy: Vec>> = (0..max_vote) @@ -850,7 +838,7 @@ pub mod pallet { // solution cannot represent any voters more than `LIMIT` anyhow. assert_eq!( ::MaxVotesPerVoter::get(), - as NposSolution>::LIMIT as u32, + as NposSolution>::LIMIT as u32, ); // While it won't cause any failures, setting `SignedMaxRefunds` gt @@ -887,7 +875,7 @@ pub mod pallet { ))] pub fn submit_unsigned( origin: OriginFor, - raw_solution: Box>>, + raw_solution: Box>>, witness: SolutionOrSnapshotSize, ) -> DispatchResultWithPostInfo { ensure_none(origin)?; @@ -976,7 +964,7 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::submit())] pub fn submit( origin: OriginFor, - raw_solution: Box>>, + raw_solution: Box>>, ) -> DispatchResult { let who = ensure_signed(origin)?; @@ -991,7 +979,7 @@ pub mod pallet { let size = Self::snapshot_metadata().ok_or(Error::::MissingSnapshotMetadata)?; ensure!( - Self::feasibility_weight_of(&raw_solution, size) < T::SignedMaxWeight::get(), + Self::solution_weight_of(&raw_solution, size) < T::SignedMaxWeight::get(), Error::::SignedTooMuchWeight, ); @@ -1429,7 +1417,7 @@ impl Pallet { /// Checks the feasibility of a solution. pub fn feasibility_check( - raw_solution: RawSolution>, + raw_solution: RawSolution>, compute: ElectionCompute, ) -> Result, FeasibilityError> { let RawSolution { solution, score, round } = raw_solution; @@ -1459,10 +1447,10 @@ impl Pallet { Self::snapshot().ok_or(FeasibilityError::SnapshotUnavailable)?; // ----- Start building. First, we need some closures. - let cache = helpers::generate_voter_cache::(&snapshot_voters); - let voter_at = helpers::voter_at_fn::(&snapshot_voters); - let target_at = helpers::target_at_fn::(&snapshot_targets); - let voter_index = helpers::voter_index_fn_usize::(&cache); + let cache = helpers::generate_voter_cache::(&snapshot_voters); + let voter_at = helpers::voter_at_fn::(&snapshot_voters); + let target_at = helpers::target_at_fn::(&snapshot_targets); + let voter_index = helpers::voter_index_fn_usize::(&cache); // Then convert solution -> assignment. This will fail if any of the indices are gibberish, // namely any of the voters or targets. @@ -1493,7 +1481,7 @@ impl Pallet { })?; // ----- Start building support. First, we need one more closure. - let stake_of = helpers::stake_of_fn::(&snapshot_voters, &cache); + let stake_of = helpers::stake_of_fn::(&snapshot_voters, &cache); // This might fail if the normalization fails. Very unlikely. See `integrity_test`. let staked_assignments = assignment_ratio_to_staked_normalized(assignments, stake_of) @@ -1803,8 +1791,8 @@ mod tests { use super::*; use crate::{ mock::{ - multi_phase_events, roll_to, AccountId, ExtBuilder, MockWeightInfo, MultiPhase, - Runtime, SignedMaxSubmissions, System, TargetIndex, Targets, + multi_phase_events, roll_to, AccountId, ExtBuilder, MockWeightInfo, MockedWeightInfo, + MultiPhase, Runtime, SignedMaxSubmissions, System, TargetIndex, Targets, }, Phase, }; @@ -2123,7 +2111,7 @@ mod tests { // set the solution balancing to get the desired score. crate::mock::Balancing::set(Some((2, 0))); - let (solution, _) = MultiPhase::mine_solution::<::Solver>().unwrap(); + let (solution, _) = MultiPhase::mine_solution().unwrap(); // Default solution's score. assert!(matches!(solution.score, ElectionScore { minimal_stake: 50, .. })); @@ -2147,7 +2135,7 @@ mod tests { #[test] fn number_of_voters_allowed_2sec_block() { // Just a rough estimate with the substrate weights. - assert!(!MockWeightInfo::get()); + assert_eq!(MockWeightInfo::get(), MockedWeightInfo::Real); let all_voters: u32 = 10_000; let all_targets: u32 = 5_000; diff --git a/frame/election-provider-multi-phase/src/mock.rs b/frame/election-provider-multi-phase/src/mock.rs index 38d9c8dfc1b7e..afce24ff6e3f0 100644 --- a/frame/election-provider-multi-phase/src/mock.rs +++ b/frame/election-provider-multi-phase/src/mock.rs @@ -16,7 +16,7 @@ // limitations under the License. use super::*; -use crate as multi_phase; +use crate::{self as multi_phase, unsigned::MinerConfig}; use frame_election_provider_support::{ data_provider, onchain, ElectionDataProvider, NposSolution, SequentialPhragmen, }; @@ -239,6 +239,13 @@ impl pallet_balances::Config for Runtime { type WeightInfo = (); } +#[derive(Eq, PartialEq, Debug, Clone, Copy)] +pub enum MockedWeightInfo { + Basic, + Complex, + Real, +} + parameter_types! { pub static Targets: Vec = vec![10, 20, 30, 40]; pub static Voters: Vec> = vec![ @@ -269,7 +276,7 @@ parameter_types! { pub static OffchainRepeat: BlockNumber = 5; pub static MinerMaxWeight: Weight = BlockWeights::get().max_block; pub static MinerMaxLength: u32 = 256; - pub static MockWeightInfo: bool = false; + pub static MockWeightInfo: MockedWeightInfo = MockedWeightInfo::Real; pub static MaxElectingVoters: VoterIndex = u32::max_value(); pub static MaxElectableTargets: TargetIndex = TargetIndex::max_value(); @@ -314,85 +321,6 @@ impl InstantElectionProvider for MockFallback { } } -// Hopefully this won't be too much of a hassle to maintain. -pub struct DualMockWeightInfo; -impl multi_phase::weights::WeightInfo for DualMockWeightInfo { - fn on_initialize_nothing() -> Weight { - if MockWeightInfo::get() { - Zero::zero() - } else { - <() as multi_phase::weights::WeightInfo>::on_initialize_nothing() - } - } - fn create_snapshot_internal(v: u32, t: u32) -> Weight { - if MockWeightInfo::get() { - Zero::zero() - } else { - <() as multi_phase::weights::WeightInfo>::create_snapshot_internal(v, t) - } - } - fn on_initialize_open_signed() -> Weight { - if MockWeightInfo::get() { - Zero::zero() - } else { - <() as multi_phase::weights::WeightInfo>::on_initialize_open_signed() - } - } - fn on_initialize_open_unsigned() -> Weight { - if MockWeightInfo::get() { - Zero::zero() - } else { - <() as multi_phase::weights::WeightInfo>::on_initialize_open_unsigned() - } - } - fn elect_queued(a: u32, d: u32) -> Weight { - if MockWeightInfo::get() { - Zero::zero() - } else { - <() as multi_phase::weights::WeightInfo>::elect_queued(a, d) - } - } - fn finalize_signed_phase_accept_solution() -> Weight { - if MockWeightInfo::get() { - Zero::zero() - } else { - <() as multi_phase::weights::WeightInfo>::finalize_signed_phase_accept_solution() - } - } - fn finalize_signed_phase_reject_solution() -> Weight { - if MockWeightInfo::get() { - Zero::zero() - } else { - <() as multi_phase::weights::WeightInfo>::finalize_signed_phase_reject_solution() - } - } - fn submit() -> Weight { - if MockWeightInfo::get() { - Zero::zero() - } else { - <() as multi_phase::weights::WeightInfo>::submit() - } - } - fn submit_unsigned(v: u32, t: u32, a: u32, d: u32) -> Weight { - if MockWeightInfo::get() { - // 10 base - // 5 per edge. - (10 as Weight).saturating_add((5 as Weight).saturating_mul(a as Weight)) - } else { - <() as multi_phase::weights::WeightInfo>::submit_unsigned(v, t, a, d) - } - } - fn feasibility_check(v: u32, t: u32, a: u32, d: u32) -> Weight { - if MockWeightInfo::get() { - // 10 base - // 5 per edge. - (10 as Weight).saturating_add((5 as Weight).saturating_mul(a as Weight)) - } else { - <() as multi_phase::weights::WeightInfo>::feasibility_check(v, t, a, d) - } - } -} - parameter_types! { pub static Balancing: Option<(usize, ExtendedBalance)> = Some((0, 0)); } @@ -410,6 +338,24 @@ impl BenchmarkingConfig for TestBenchmarkingConfig { const MAXIMUM_TARGETS: u32 = 200; } +impl MinerConfig for Runtime { + type AccountId = AccountId; + type MaxLength = MinerMaxLength; + type MaxWeight = MinerMaxWeight; + type MaxVotesPerVoter = ::MaxVotesPerVoter; + type Solution = TestNposSolution; + + fn solution_weight(v: u32, t: u32, a: u32, d: u32) -> Weight { + match MockWeightInfo::get() { + MockedWeightInfo::Basic => + (10 as Weight).saturating_add((5 as Weight).saturating_mul(a as Weight)), + MockedWeightInfo::Complex => (0 * v + 0 * t + 1000 * a + 0 * d) as Weight, + MockedWeightInfo::Real => + <() as multi_phase::weights::WeightInfo>::feasibility_check(v, t, a, d), + } + } +} + impl crate::Config for Runtime { type Event = Event; type Currency = Balances; @@ -419,8 +365,6 @@ impl crate::Config for Runtime { type BetterUnsignedThreshold = BetterUnsignedThreshold; type BetterSignedThreshold = BetterSignedThreshold; type OffchainRepeat = OffchainRepeat; - type MinerMaxWeight = MinerMaxWeight; - type MinerMaxLength = MinerMaxLength; type MinerTxPriority = MinerTxPriority; type SignedRewardBase = SignedRewardBase; type SignedDepositBase = SignedDepositBase; @@ -432,14 +376,14 @@ impl crate::Config for Runtime { type SlashHandler = (); type RewardHandler = (); type DataProvider = StakingMock; - type WeightInfo = DualMockWeightInfo; + type WeightInfo = (); type BenchmarkingConfig = TestBenchmarkingConfig; type Fallback = MockFallback; type GovernanceFallback = NoFallback; type ForceOrigin = frame_system::EnsureRoot; - type Solution = TestNposSolution; type MaxElectingVoters = MaxElectingVoters; type MaxElectableTargets = MaxElectableTargets; + type MinerConfig = Self; type Solver = SequentialPhragmen, Balancing>; } @@ -562,7 +506,7 @@ impl ExtBuilder { ::set(weight); self } - pub fn mock_weight_info(self, mock: bool) -> Self { + pub fn mock_weight_info(self, mock: MockedWeightInfo) -> Self { ::set(mock); self } diff --git a/frame/election-provider-multi-phase/src/signed.rs b/frame/election-provider-multi-phase/src/signed.rs index 5f61eb7575da4..eca75139f925a 100644 --- a/frame/election-provider-multi-phase/src/signed.rs +++ b/frame/election-provider-multi-phase/src/signed.rs @@ -18,9 +18,9 @@ //! The signed phase implementation. use crate::{ - Config, ElectionCompute, Pallet, QueuedSolution, RawSolution, ReadySolution, - SignedSubmissionIndices, SignedSubmissionNextIndex, SignedSubmissionsMap, SolutionOf, - SolutionOrSnapshotSize, Weight, WeightInfo, + unsigned::MinerConfig, Config, ElectionCompute, Pallet, QueuedSolution, RawSolution, + ReadySolution, SignedSubmissionIndices, SignedSubmissionNextIndex, SignedSubmissionsMap, + SolutionOf, SolutionOrSnapshotSize, Weight, WeightInfo, }; use codec::{Decode, Encode, HasCompact}; use frame_election_provider_support::NposSolution; @@ -93,8 +93,11 @@ pub type PositiveImbalanceOf = <::Currency as Currency< pub type NegativeImbalanceOf = <::Currency as Currency< ::AccountId, >>::NegativeImbalance; -pub type SignedSubmissionOf = - SignedSubmission<::AccountId, BalanceOf, SolutionOf>; +pub type SignedSubmissionOf = SignedSubmission< + ::AccountId, + BalanceOf, + <::MinerConfig as MinerConfig>::Solution, +>; pub type SubmissionIndicesOf = BoundedBTreeMap::SignedMaxSubmissions>; @@ -482,12 +485,12 @@ impl Pallet { T::SlashHandler::on_unbalanced(negative_imbalance); } - /// The feasibility weight of the given raw solution. - pub fn feasibility_weight_of( - raw_solution: &RawSolution>, + /// The weight of the given raw solution. + pub fn solution_weight_of( + raw_solution: &RawSolution>, size: SolutionOrSnapshotSize, ) -> Weight { - T::WeightInfo::feasibility_check( + T::MinerConfig::solution_weight( size.voters, size.targets, raw_solution.solution.voter_count() as u32, @@ -503,12 +506,12 @@ impl Pallet { /// 2. a per-byte deposit, for renting the state usage. /// 3. a per-weight deposit, for the potential weight usage in an upcoming on_initialize pub fn deposit_for( - raw_solution: &RawSolution>, + raw_solution: &RawSolution>, size: SolutionOrSnapshotSize, ) -> BalanceOf { let encoded_len: u32 = raw_solution.encoded_size().saturated_into(); let encoded_len: BalanceOf = encoded_len.into(); - let feasibility_weight = Self::feasibility_weight_of(raw_solution, size); + let feasibility_weight = Self::solution_weight_of(raw_solution, size); let len_deposit = T::SignedDepositByte::get().saturating_mul(encoded_len); let weight_deposit = @@ -525,8 +528,8 @@ mod tests { use super::*; use crate::{ mock::{ - balances, raw_solution, roll_to, Balances, ExtBuilder, MultiPhase, Origin, Runtime, - SignedMaxRefunds, SignedMaxSubmissions, SignedMaxWeight, + balances, raw_solution, roll_to, Balances, ExtBuilder, MockedWeightInfo, MultiPhase, + Origin, Runtime, SignedMaxRefunds, SignedMaxSubmissions, SignedMaxWeight, }, Error, Perbill, Phase, }; @@ -955,14 +958,13 @@ mod tests { fn cannot_consume_too_much_future_weight() { ExtBuilder::default() .signed_weight(40) - .mock_weight_info(true) + .mock_weight_info(MockedWeightInfo::Basic) .build_and_execute(|| { roll_to(15); assert!(MultiPhase::current_phase().is_signed()); - let (raw, witness) = - MultiPhase::mine_solution::<::Solver>().unwrap(); - let solution_weight = ::WeightInfo::feasibility_check( + let (raw, witness) = MultiPhase::mine_solution().unwrap(); + let solution_weight = ::solution_weight( witness.voters, witness.targets, raw.solution.voter_count() as u32, diff --git a/frame/election-provider-multi-phase/src/unsigned.rs b/frame/election-provider-multi-phase/src/unsigned.rs index 126dcb10416cc..9a1b52d354569 100644 --- a/frame/election-provider-multi-phase/src/unsigned.rs +++ b/frame/election-provider-multi-phase/src/unsigned.rs @@ -20,14 +20,15 @@ use crate::{ helpers, Call, Config, ElectionCompute, Error, FeasibilityError, Pallet, RawSolution, ReadySolution, RoundSnapshot, SolutionAccuracyOf, SolutionOf, SolutionOrSnapshotSize, Weight, - WeightInfo, }; use codec::Encode; -use frame_election_provider_support::{NposSolution, NposSolver, PerThing128}; -use frame_support::{dispatch::DispatchResult, ensure, traits::Get}; +use frame_election_provider_support::{NposSolution, NposSolver, PerThing128, VoteWeight}; +use frame_support::{dispatch::DispatchResult, ensure, traits::Get, BoundedVec}; use frame_system::offchain::SubmitTransaction; +use scale_info::TypeInfo; use sp_npos_elections::{ assignment_ratio_to_staked_normalized, assignment_staked_to_ratio_normalized, ElectionResult, + ElectionScore, }; use sp_runtime::{ offchain::storage::{MutateStorageError, StorageValueRef}, @@ -47,6 +48,12 @@ pub(crate) const OFFCHAIN_CACHED_CALL: &[u8] = b"parity/multi-phase-unsigned-ele /// voted. pub type VoterOf = frame_election_provider_support::VoterOf<::DataProvider>; +/// Same as [`VoterOf`], but parameterized by the `MinerConfig`. +pub type MinerVoterOf = frame_election_provider_support::Voter< + ::AccountId, + ::MaxVotesPerVoter, +>; + /// The relative distribution of a voter's stake among the winning targets. pub type Assignment = sp_npos_elections::Assignment<::AccountId, SolutionAccuracyOf>; @@ -59,7 +66,7 @@ pub type IndexAssignmentOf = frame_election_provider_support::IndexAssignment pub type SolverErrorOf = <::Solver as NposSolver>::Error; /// Error type for operations related to the OCW npos solution miner. #[derive(frame_support::DebugNoBound, frame_support::PartialEqNoBound)] -pub enum MinerError { +pub enum MinerError { /// An internal error in the NPoS elections crate. NposElections(sp_npos_elections::Error), /// Snapshot data was unavailable unexpectedly. @@ -81,23 +88,23 @@ pub enum MinerError { /// There are no more voters to remove to trim the solution. NoMoreVoters, /// An error from the solver. - Solver(SolverErrorOf), + Solver, } -impl From for MinerError { +impl From for MinerError { fn from(e: sp_npos_elections::Error) -> Self { MinerError::NposElections(e) } } -impl From for MinerError { +impl From for MinerError { fn from(e: FeasibilityError) -> Self { MinerError::Feasibility(e) } } /// Save a given call into OCW storage. -fn save_solution(call: &Call) -> Result<(), MinerError> { +fn save_solution(call: &Call) -> Result<(), MinerError> { log!(debug, "saving a call to the offchain storage."); let storage = StorageValueRef::persistent(OFFCHAIN_CACHED_CALL); match storage.mutate::<_, (), _>(|_| Ok(call.clone())) { @@ -115,7 +122,7 @@ fn save_solution(call: &Call) -> Result<(), MinerError> { } /// Get a saved solution from OCW storage if it exists. -fn restore_solution() -> Result, MinerError> { +fn restore_solution() -> Result, MinerError> { StorageValueRef::persistent(OFFCHAIN_CACHED_CALL) .get() .ok() @@ -146,9 +153,46 @@ fn ocw_solution_exists() -> bool { } impl Pallet { + /// Mine a new npos solution. + /// + /// The Npos Solver type, `S`, must have the same AccountId and Error type as the + /// [`crate::Config::Solver`] in order to create a unified return type. + pub fn mine_solution( + ) -> Result<(RawSolution>, SolutionOrSnapshotSize), MinerError> { + let RoundSnapshot { voters, targets } = + Self::snapshot().ok_or(MinerError::SnapshotUnAvailable)?; + let desired_targets = Self::desired_targets().ok_or(MinerError::SnapshotUnAvailable)?; + let (solution, score, size) = Miner::::mine_solution_with_snapshot::< + T::Solver, + >(voters, targets, desired_targets)?; + let round = Self::round(); + Ok((RawSolution { solution, score, round }, size)) + } + + /// Convert a raw solution from [`sp_npos_elections::ElectionResult`] to [`RawSolution`], which + /// is ready to be submitted to the chain. + /// + /// Will always reduce the solution as well. + pub fn prepare_election_result( + election_result: ElectionResult, + ) -> Result<(RawSolution>, SolutionOrSnapshotSize), MinerError> { + let RoundSnapshot { voters, targets } = + Self::snapshot().ok_or(MinerError::SnapshotUnAvailable)?; + let desired_targets = Self::desired_targets().ok_or(MinerError::SnapshotUnAvailable)?; + let (solution, score, size) = + Miner::::prepare_election_result_with_snapshot( + election_result, + voters, + targets, + desired_targets, + )?; + let round = Self::round(); + Ok((RawSolution { solution, score, round }, size)) + } + /// Attempt to restore a solution from cache. Otherwise, compute it fresh. Either way, submit /// if our call's score is greater than that of the cached solution. - pub fn restore_or_compute_then_maybe_submit() -> Result<(), MinerError> { + pub fn restore_or_compute_then_maybe_submit() -> Result<(), MinerError> { log!(debug, "miner attempting to restore or compute an unsigned solution."); let call = restore_solution::() @@ -162,7 +206,7 @@ impl Pallet { Err(MinerError::SolutionCallInvalid) } }) - .or_else::, _>(|error| { + .or_else::(|error| { log!(debug, "restoring solution failed due to {:?}", error); match error { MinerError::NoStoredSolution => { @@ -193,7 +237,7 @@ impl Pallet { } /// Mine a new solution, cache it, and submit it back to the chain as an unsigned transaction. - pub fn mine_check_save_submit() -> Result<(), MinerError> { + pub fn mine_check_save_submit() -> Result<(), MinerError> { log!(debug, "miner attempting to compute an unsigned solution."); let call = Self::mine_checked_call()?; @@ -202,7 +246,7 @@ impl Pallet { } /// Mine a new solution as a call. Performs all checks. - pub fn mine_checked_call() -> Result, MinerError> { + pub fn mine_checked_call() -> Result, MinerError> { // get the solution, with a load of checks to ensure if submitted, IT IS ABSOLUTELY VALID. let (raw_solution, witness) = Self::mine_and_check()?; @@ -219,7 +263,7 @@ impl Pallet { Ok(call) } - fn submit_call(call: Call) -> Result<(), MinerError> { + fn submit_call(call: Call) -> Result<(), MinerError> { log!(debug, "miner submitting a solution as an unsigned transaction"); SubmitTransaction::>::submit_unsigned_transaction(call.into()) @@ -230,9 +274,9 @@ impl Pallet { // // Performance: note that it internally clones the provided solution. pub fn basic_checks( - raw_solution: &RawSolution>, + raw_solution: &RawSolution>, solution_type: &str, - ) -> Result<(), MinerError> { + ) -> Result<(), MinerError> { Self::unsigned_pre_dispatch_checks(raw_solution).map_err(|err| { log!(debug, "pre-dispatch checks failed for {} solution: {:?}", solution_type, err); MinerError::PreDispatchChecksFailed(err) @@ -255,45 +299,155 @@ impl Pallet { /// If you want a checked solution and submit it at the same time, use /// [`Pallet::mine_check_save_submit`]. pub fn mine_and_check( - ) -> Result<(RawSolution>, SolutionOrSnapshotSize), MinerError> { - let (raw_solution, witness) = Self::mine_solution::()?; + ) -> Result<(RawSolution>, SolutionOrSnapshotSize), MinerError> { + let (raw_solution, witness) = Self::mine_solution()?; Self::basic_checks(&raw_solution, "mined")?; Ok((raw_solution, witness)) } - /// Mine a new npos solution. + /// Checks if an execution of the offchain worker is permitted at the given block number, or + /// not. /// - /// The Npos Solver type, `S`, must have the same AccountId and Error type as the - /// [`crate::Config::Solver`] in order to create a unified return type. - pub fn mine_solution( - ) -> Result<(RawSolution>, SolutionOrSnapshotSize), MinerError> - where - S: NposSolver>, - { - let RoundSnapshot { voters, targets } = - Self::snapshot().ok_or(MinerError::SnapshotUnAvailable)?; - let desired_targets = Self::desired_targets().ok_or(MinerError::SnapshotUnAvailable)?; + /// This makes sure that + /// 1. we don't run on previous blocks in case of a re-org + /// 2. we don't run twice within a window of length `T::OffchainRepeat`. + /// + /// Returns `Ok(())` if offchain worker limit is respected, `Err(reason)` otherwise. If `Ok()` + /// is returned, `now` is written in storage and will be used in further calls as the baseline. + pub fn ensure_offchain_repeat_frequency(now: T::BlockNumber) -> Result<(), MinerError> { + let threshold = T::OffchainRepeat::get(); + let last_block = StorageValueRef::persistent(OFFCHAIN_LAST_BLOCK); - S::solve(desired_targets as usize, targets, voters) - .map_err(|e| MinerError::Solver::(e)) - .and_then(|e| Self::prepare_election_result::(e)) + let mutate_stat = last_block.mutate::<_, &'static str, _>( + |maybe_head: Result, _>| { + match maybe_head { + Ok(Some(head)) if now < head => Err("fork."), + Ok(Some(head)) if now >= head && now <= head + threshold => + Err("recently executed."), + Ok(Some(head)) if now > head + threshold => { + // we can run again now. Write the new head. + Ok(now) + }, + _ => { + // value doesn't exists. Probably this node just booted up. Write, and run + Ok(now) + }, + } + }, + ); + + match mutate_stat { + // all good + Ok(_) => Ok(()), + // failed to write. + Err(MutateStorageError::ConcurrentModification(_)) => + Err(MinerError::Lock("failed to write to offchain db (concurrent modification).")), + // fork etc. + Err(MutateStorageError::ValueFunctionFailed(why)) => Err(MinerError::Lock(why)), + } } - /// Convert a raw solution from [`sp_npos_elections::ElectionResult`] to [`RawSolution`], which - /// is ready to be submitted to the chain. + /// Do the basics checks that MUST happen during the validation and pre-dispatch of an unsigned + /// transaction. /// - /// Will always reduce the solution as well. - pub fn prepare_election_result( - election_result: ElectionResult, - ) -> Result<(RawSolution>, SolutionOrSnapshotSize), MinerError> { - // NOTE: This code path is generally not optimized as it is run offchain. Could use some at - // some point though. + /// Can optionally also be called during dispatch, if needed. + /// + /// NOTE: Ideally, these tests should move more and more outside of this and more to the miner's + /// code, so that we do less and less storage reads here. + pub fn unsigned_pre_dispatch_checks( + raw_solution: &RawSolution>, + ) -> DispatchResult { + // ensure solution is timely. Don't panic yet. This is a cheap check. + ensure!(Self::current_phase().is_unsigned_open(), Error::::PreDispatchEarlySubmission); - // storage items. Note: we have already read this from storage, they must be in cache. - let RoundSnapshot { voters, targets } = - Self::snapshot().ok_or(MinerError::SnapshotUnAvailable)?; - let desired_targets = Self::desired_targets().ok_or(MinerError::SnapshotUnAvailable)?; + // ensure round is current + ensure!(Self::round() == raw_solution.round, Error::::OcwCallWrongEra); + // ensure correct number of winners. + ensure!( + Self::desired_targets().unwrap_or_default() == + raw_solution.solution.unique_targets().len() as u32, + Error::::PreDispatchWrongWinnerCount, + ); + + // ensure score is being improved. Panic henceforth. + ensure!( + Self::queued_solution().map_or(true, |q: ReadySolution<_>| raw_solution + .score + .strict_threshold_better(q.score, T::BetterUnsignedThreshold::get())), + Error::::PreDispatchWeakSubmission, + ); + + Ok(()) + } +} + +/// Configurations for a miner that comes with this pallet. +pub trait MinerConfig { + /// The account id type. + type AccountId: Ord + Clone + codec::Codec + sp_std::fmt::Debug; + /// The solution that the miner is mining. + type Solution: codec::Codec + + Default + + PartialEq + + Eq + + Clone + + sp_std::fmt::Debug + + Ord + + NposSolution + + TypeInfo; + /// Maximum number of votes per voter in the snapshots. + type MaxVotesPerVoter; + /// Maximum length of the solution that the miner is allowed to generate. + /// + /// Solutions are trimmed to respect this. + type MaxLength: Get; + /// Maximum weight of the solution that the miner is allowed to generate. + /// + /// Solutions are trimmed to respect this. + /// + /// The weight is computed using `solution_weight`. + type MaxWeight: Get; + /// Something that can compute the weight of a solution. + /// + /// This weight estimate is then used to trim the solution, based on [`MinerConfig::MaxWeight`]. + fn solution_weight(voters: u32, targets: u32, active_voters: u32, degree: u32) -> Weight; +} + +/// A base miner, suitable to be used for both signed and unsigned submissions. +pub struct Miner(sp_std::marker::PhantomData); +impl Miner { + /// Same as [`Pallet::mine_solution`], but the input snapshot data must be given. + pub fn mine_solution_with_snapshot( + voters: Vec<(T::AccountId, VoteWeight, BoundedVec)>, + targets: Vec, + desired_targets: u32, + ) -> Result<(SolutionOf, ElectionScore, SolutionOrSnapshotSize), MinerError> + where + S: NposSolver, + { + S::solve(desired_targets as usize, targets.clone(), voters.clone()) + .map_err(|e| { + log_no_system!(error, "solver error: {:?}", e); + MinerError::Solver + }) + .and_then(|e| { + Self::prepare_election_result_with_snapshot::( + e, + voters, + targets, + desired_targets, + ) + }) + } + + /// Same as [`Pallet::prepare_election_result`], but the input snapshot mut be given as inputs. + pub fn prepare_election_result_with_snapshot( + election_result: ElectionResult, + voters: Vec<(T::AccountId, VoteWeight, BoundedVec)>, + targets: Vec, + desired_targets: u32, + ) -> Result<(SolutionOf, ElectionScore, SolutionOrSnapshotSize), MinerError> { // now make some helper closures. let cache = helpers::generate_voter_cache::(&voters); let voter_index = helpers::voter_index_fn::(&cache); @@ -355,11 +509,11 @@ impl Pallet { Self::trim_assignments_weight( desired_targets, size, - T::MinerMaxWeight::get(), + T::MaxWeight::get(), &mut index_assignments, ); Self::trim_assignments_length( - T::MinerMaxLength::get(), + T::MaxLength::get(), &mut index_assignments, &encoded_size_of, )?; @@ -370,43 +524,7 @@ impl Pallet { // re-calc score. let score = solution.clone().score(stake_of, voter_at, target_at)?; - let round = Self::round(); - Ok((RawSolution { solution, score, round }, size)) - } - - /// Greedily reduce the size of the solution to fit into the block w.r.t. weight. - /// - /// The weight of the solution is foremost a function of the number of voters (i.e. - /// `assignments.len()`). Aside from this, the other components of the weight are invariant. The - /// number of winners shall not be changed (otherwise the solution is invalid) and the - /// `ElectionSize` is merely a representation of the total number of stakers. - /// - /// Thus, we reside to stripping away some voters from the `assignments`. - /// - /// Note that the solution is already computed, and the winners are elected based on the merit - /// of the entire stake in the system. Nonetheless, some of the voters will be removed further - /// down the line. - /// - /// Indeed, the score must be computed **after** this step. If this step reduces the score too - /// much or remove a winner, then the solution must be discarded **after** this step. - pub fn trim_assignments_weight( - desired_targets: u32, - size: SolutionOrSnapshotSize, - max_weight: Weight, - assignments: &mut Vec>, - ) { - let maximum_allowed_voters = - Self::maximum_voter_for_weight::(desired_targets, size, max_weight); - let removing: usize = - assignments.len().saturating_sub(maximum_allowed_voters.saturated_into()); - log!( - debug, - "from {} assignments, truncating to {} for weight, removing {}", - assignments.len(), - maximum_allowed_voters, - removing, - ); - assignments.truncate(maximum_allowed_voters as usize); + Ok((solution, score, size)) } /// Greedily reduce the size of the solution to fit into the block w.r.t length. @@ -427,7 +545,7 @@ impl Pallet { max_allowed_length: u32, assignments: &mut Vec>, encoded_size_of: impl Fn(&[IndexAssignmentOf]) -> Result, - ) -> Result<(), MinerError> { + ) -> Result<(), MinerError> { // Perform a binary search for the max subset of which can fit into the allowed // length. Having discovered that, we can truncate efficiently. let max_allowed_length: usize = max_allowed_length.saturated_into(); @@ -470,7 +588,7 @@ impl Pallet { // after this point, we never error. // check before edit. - log!( + log_no_system!( debug, "from {} assignments, truncating to {} for length, removing {}", assignments.len(), @@ -482,10 +600,45 @@ impl Pallet { Ok(()) } + /// Greedily reduce the size of the solution to fit into the block w.r.t. weight. + /// + /// The weight of the solution is foremost a function of the number of voters (i.e. + /// `assignments.len()`). Aside from this, the other components of the weight are invariant. The + /// number of winners shall not be changed (otherwise the solution is invalid) and the + /// `ElectionSize` is merely a representation of the total number of stakers. + /// + /// Thus, we reside to stripping away some voters from the `assignments`. + /// + /// Note that the solution is already computed, and the winners are elected based on the merit + /// of the entire stake in the system. Nonetheless, some of the voters will be removed further + /// down the line. + /// + /// Indeed, the score must be computed **after** this step. If this step reduces the score too + /// much or remove a winner, then the solution must be discarded **after** this step. + pub fn trim_assignments_weight( + desired_targets: u32, + size: SolutionOrSnapshotSize, + max_weight: Weight, + assignments: &mut Vec>, + ) { + let maximum_allowed_voters = + Self::maximum_voter_for_weight(desired_targets, size, max_weight); + let removing: usize = + assignments.len().saturating_sub(maximum_allowed_voters.saturated_into()); + log_no_system!( + debug, + "from {} assignments, truncating to {} for weight, removing {}", + assignments.len(), + maximum_allowed_voters, + removing, + ); + assignments.truncate(maximum_allowed_voters as usize); + } + /// Find the maximum `len` that a solution can have in order to fit into the block weight. /// /// This only returns a value between zero and `size.nominators`. - pub fn maximum_voter_for_weight( + pub fn maximum_voter_for_weight( desired_winners: u32, size: SolutionOrSnapshotSize, max_weight: Weight, @@ -499,7 +652,7 @@ impl Pallet { // helper closures. let weight_with = |active_voters: u32| -> Weight { - W::submit_unsigned(size.voters, size.targets, active_voters, desired_winners) + T::solution_weight(size.voters, size.targets, active_voters, desired_winners) }; let next_voters = |current_weight: Weight, voters: u32, step: u32| -> Result { @@ -553,176 +706,65 @@ impl Pallet { ); final_decision } - - /// Checks if an execution of the offchain worker is permitted at the given block number, or - /// not. - /// - /// This makes sure that - /// 1. we don't run on previous blocks in case of a re-org - /// 2. we don't run twice within a window of length `T::OffchainRepeat`. - /// - /// Returns `Ok(())` if offchain worker limit is respected, `Err(reason)` otherwise. If `Ok()` - /// is returned, `now` is written in storage and will be used in further calls as the baseline. - pub fn ensure_offchain_repeat_frequency(now: T::BlockNumber) -> Result<(), MinerError> { - let threshold = T::OffchainRepeat::get(); - let last_block = StorageValueRef::persistent(OFFCHAIN_LAST_BLOCK); - - let mutate_stat = last_block.mutate::<_, &'static str, _>( - |maybe_head: Result, _>| { - match maybe_head { - Ok(Some(head)) if now < head => Err("fork."), - Ok(Some(head)) if now >= head && now <= head + threshold => - Err("recently executed."), - Ok(Some(head)) if now > head + threshold => { - // we can run again now. Write the new head. - Ok(now) - }, - _ => { - // value doesn't exists. Probably this node just booted up. Write, and run - Ok(now) - }, - } - }, - ); - - match mutate_stat { - // all good - Ok(_) => Ok(()), - // failed to write. - Err(MutateStorageError::ConcurrentModification(_)) => - Err(MinerError::Lock("failed to write to offchain db (concurrent modification).")), - // fork etc. - Err(MutateStorageError::ValueFunctionFailed(why)) => Err(MinerError::Lock(why)), - } - } - - /// Do the basics checks that MUST happen during the validation and pre-dispatch of an unsigned - /// transaction. - /// - /// Can optionally also be called during dispatch, if needed. - /// - /// NOTE: Ideally, these tests should move more and more outside of this and more to the miner's - /// code, so that we do less and less storage reads here. - pub fn unsigned_pre_dispatch_checks( - raw_solution: &RawSolution>, - ) -> DispatchResult { - // ensure solution is timely. Don't panic yet. This is a cheap check. - ensure!(Self::current_phase().is_unsigned_open(), Error::::PreDispatchEarlySubmission); - - // ensure round is current - ensure!(Self::round() == raw_solution.round, Error::::OcwCallWrongEra); - - // ensure correct number of winners. - ensure!( - Self::desired_targets().unwrap_or_default() == - raw_solution.solution.unique_targets().len() as u32, - Error::::PreDispatchWrongWinnerCount, - ); - - // ensure score is being improved. Panic henceforth. - ensure!( - Self::queued_solution().map_or(true, |q: ReadySolution<_>| raw_solution - .score - .strict_threshold_better(q.score, T::BetterUnsignedThreshold::get())), - Error::::PreDispatchWeakSubmission, - ); - - Ok(()) - } } #[cfg(test)] mod max_weight { #![allow(unused_variables)] use super::*; - use crate::mock::MultiPhase; - - struct TestWeight; - impl crate::weights::WeightInfo for TestWeight { - fn elect_queued(a: u32, d: u32) -> Weight { - unreachable!() - } - fn create_snapshot_internal(v: u32, t: u32) -> Weight { - unreachable!() - } - fn on_initialize_nothing() -> Weight { - unreachable!() - } - fn on_initialize_open_signed() -> Weight { - unreachable!() - } - fn on_initialize_open_unsigned() -> Weight { - unreachable!() - } - fn finalize_signed_phase_accept_solution() -> Weight { - unreachable!() - } - fn finalize_signed_phase_reject_solution() -> Weight { - unreachable!() - } - fn submit() -> Weight { - unreachable!() - } - fn submit_unsigned(v: u32, t: u32, a: u32, d: u32) -> Weight { - (0 * v + 0 * t + 1000 * a + 0 * d) as Weight - } - fn feasibility_check(v: u32, _t: u32, a: u32, d: u32) -> Weight { - unreachable!() - } - } - + use crate::mock::{MockWeightInfo, Runtime}; #[test] fn find_max_voter_binary_search_works() { let w = SolutionOrSnapshotSize { voters: 10, targets: 0 }; - - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 0), 0); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1), 0); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 999), 0); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1000), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1001), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1990), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1999), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 2000), 2); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 2001), 2); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 2010), 2); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 2990), 2); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 2999), 2); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 3000), 3); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 3333), 3); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 5500), 5); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 7777), 7); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 9999), 9); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 10_000), 10); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 10_999), 10); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 11_000), 10); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 22_000), 10); + MockWeightInfo::set(crate::mock::MockedWeightInfo::Complex); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 0), 0); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 1), 0); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 999), 0); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 1000), 1); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 1001), 1); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 1990), 1); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 1999), 1); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 2000), 2); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 2001), 2); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 2010), 2); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 2990), 2); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 2999), 2); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 3000), 3); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 3333), 3); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 5500), 5); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 7777), 7); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 9999), 9); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 10_000), 10); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 10_999), 10); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 11_000), 10); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 22_000), 10); let w = SolutionOrSnapshotSize { voters: 1, targets: 0 }; - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 0), 0); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1), 0); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 999), 0); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1000), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1001), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1990), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1999), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 2000), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 2001), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 2010), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 3333), 1); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 0), 0); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 1), 0); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 999), 0); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 1000), 1); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 1001), 1); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 1990), 1); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 1999), 1); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 2000), 1); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 2001), 1); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 2010), 1); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 3333), 1); let w = SolutionOrSnapshotSize { voters: 2, targets: 0 }; - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 0), 0); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1), 0); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 999), 0); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1000), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1001), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 1999), 1); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 2000), 2); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 2001), 2); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 2010), 2); - assert_eq!(MultiPhase::maximum_voter_for_weight::(0, w, 3333), 2); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 0), 0); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 1), 0); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 999), 0); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 1000), 1); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 1001), 1); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 1999), 1); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 2000), 2); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 2001), 2); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 2010), 2); + assert_eq!(Miner::::maximum_voter_for_weight(0, w, 3333), 2); } } @@ -988,8 +1030,7 @@ mod tests { assert_eq!(MultiPhase::desired_targets().unwrap(), 2); // mine seq_phragmen solution with 2 iters. - let (solution, witness) = - MultiPhase::mine_solution::<::Solver>().unwrap(); + let (solution, witness) = MultiPhase::mine_solution().unwrap(); // ensure this solution is valid. assert!(MultiPhase::queued_solution().is_none()); @@ -1002,14 +1043,13 @@ mod tests { fn miner_trims_weight() { ExtBuilder::default() .miner_weight(100) - .mock_weight_info(true) + .mock_weight_info(crate::mock::MockedWeightInfo::Basic) .build_and_execute(|| { roll_to(25); assert!(MultiPhase::current_phase().is_unsigned()); - let (raw, witness) = - MultiPhase::mine_solution::<::Solver>().unwrap(); - let solution_weight = ::WeightInfo::submit_unsigned( + let (raw, witness) = MultiPhase::mine_solution().unwrap(); + let solution_weight = ::solution_weight( witness.voters, witness.targets, raw.solution.voter_count() as u32, @@ -1022,9 +1062,8 @@ mod tests { // now reduce the max weight ::set(25); - let (raw, witness) = - MultiPhase::mine_solution::<::Solver>().unwrap(); - let solution_weight = ::WeightInfo::submit_unsigned( + let (raw, witness) = MultiPhase::mine_solution().unwrap(); + let solution_weight = ::solution_weight( witness.voters, witness.targets, raw.solution.voter_count() as u32, @@ -1044,8 +1083,7 @@ mod tests { assert!(MultiPhase::current_phase().is_unsigned()); // Force the number of winners to be bigger to fail - let (mut solution, _) = - MultiPhase::mine_solution::<::Solver>().unwrap(); + let (mut solution, _) = MultiPhase::mine_solution().unwrap(); solution.solution.votes1[0].1 = 4; assert_eq!( @@ -1460,8 +1498,12 @@ mod tests { let solution_clone = solution.clone(); // when - MultiPhase::trim_assignments_length(encoded_len, &mut assignments, encoded_size_of) - .unwrap(); + Miner::::trim_assignments_length( + encoded_len, + &mut assignments, + encoded_size_of, + ) + .unwrap(); // then let solution = SolutionOf::::try_from(assignments.as_slice()).unwrap(); @@ -1481,7 +1523,7 @@ mod tests { let solution_clone = solution.clone(); // when - MultiPhase::trim_assignments_length( + Miner::::trim_assignments_length( encoded_len as u32 - 1, &mut assignments, encoded_size_of, @@ -1514,8 +1556,12 @@ mod tests { .unwrap(); // when - MultiPhase::trim_assignments_length(encoded_len - 1, &mut assignments, encoded_size_of) - .unwrap(); + Miner::::trim_assignments_length( + encoded_len - 1, + &mut assignments, + encoded_size_of, + ) + .unwrap(); // then assert_eq!(assignments.len(), count - 1, "we must have removed exactly one assignment"); @@ -1542,11 +1588,11 @@ mod tests { assert_eq!(min_solution_size, SolutionOf::::LIMIT); // all of this should not panic. - MultiPhase::trim_assignments_length(0, &mut assignments, encoded_size_of.clone()) + Miner::::trim_assignments_length(0, &mut assignments, encoded_size_of.clone()) .unwrap(); - MultiPhase::trim_assignments_length(1, &mut assignments, encoded_size_of.clone()) + Miner::::trim_assignments_length(1, &mut assignments, encoded_size_of.clone()) .unwrap(); - MultiPhase::trim_assignments_length( + Miner::::trim_assignments_length( min_solution_size as u32, &mut assignments, encoded_size_of, @@ -1563,7 +1609,7 @@ mod tests { // trim to min solution size. let min_solution_size = SolutionOf::::LIMIT as u32; - MultiPhase::trim_assignments_length( + Miner::::trim_assignments_length( min_solution_size, &mut assignments, encoded_size_of, @@ -1582,15 +1628,15 @@ mod tests { roll_to(25); // how long would the default solution be? - let solution = MultiPhase::mine_solution::<::Solver>().unwrap(); - let max_length = ::MinerMaxLength::get(); + let solution = MultiPhase::mine_solution().unwrap(); + let max_length = ::MaxLength::get(); let solution_size = solution.0.solution.encoded_size(); assert!(solution_size <= max_length as usize); // now set the max size to less than the actual size and regenerate - ::MinerMaxLength::set(solution_size as u32 - 1); - let solution = MultiPhase::mine_solution::<::Solver>().unwrap(); - let max_length = ::MinerMaxLength::get(); + ::MaxLength::set(solution_size as u32 - 1); + let solution = MultiPhase::mine_solution().unwrap(); + let max_length = ::MaxLength::get(); let solution_size = solution.0.solution.encoded_size(); assert!(solution_size <= max_length as usize); }); From 09ca4f4bb8d20dfa9c57045e81a64a145859cec5 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 12 May 2022 03:34:38 +0200 Subject: [PATCH 213/484] Whitelist Transactional key (#11394) * Make constants public Signed-off-by: Oliver Tale-Yazdi * Whitelist the transactional layer Signed-off-by: Oliver Tale-Yazdi * cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_nomination_pools --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/nomination-pools/src/weights.rs --template=./.maintain/frame-weight-template.hbs Co-authored-by: Parity Bot --- frame/benchmarking/src/lib.rs | 6 + frame/nomination-pools/src/weights.rs | 160 +++++++++------------ frame/support/src/storage/transactional.rs | 6 +- 3 files changed, 80 insertions(+), 92 deletions(-) diff --git a/frame/benchmarking/src/lib.rs b/frame/benchmarking/src/lib.rs index ca836e431e5ee..afd53915cc397 100644 --- a/frame/benchmarking/src/lib.rs +++ b/frame/benchmarking/src/lib.rs @@ -1021,6 +1021,12 @@ macro_rules! impl_benchmark { $crate::whitelisted_caller::() ); whitelist.push(whitelisted_caller_key.into()); + // Whitelist the transactional layer. + let transactional_layer_key = $crate::TrackedStorageKey::new( + $crate::frame_support::storage::transactional::TRANSACTION_LEVEL_KEY.into() + ); + whitelist.push(transactional_layer_key); + $crate::benchmarking::set_whitelist(whitelist); let mut results: $crate::Vec<$crate::BenchmarkResult> = $crate::Vec::new(); diff --git a/frame/nomination-pools/src/weights.rs b/frame/nomination-pools/src/weights.rs index 0b2a84ddd2ac3..6a5a38ac0abf7 100644 --- a/frame/nomination-pools/src/weights.rs +++ b/frame/nomination-pools/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_nomination_pools //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-10, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-11, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -64,7 +64,6 @@ pub trait WeightInfo { /// Weights for pallet_nomination_pools using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: NominationPools MinJoinBond (r:1 w:0) // Storage: NominationPools PoolMembers (r:1 w:1) // Storage: NominationPools BondedPools (r:1 w:1) @@ -79,11 +78,10 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn join() -> Weight { - (119_253_000 as Weight) - .saturating_add(T::DbWeight::get().reads(18 as Weight)) - .saturating_add(T::DbWeight::get().writes(12 as Weight)) + (122_806_000 as Weight) + .saturating_add(T::DbWeight::get().reads(17 as Weight)) + .saturating_add(T::DbWeight::get().writes(11 as Weight)) } - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: NominationPools PoolMembers (r:1 w:1) // Storage: NominationPools BondedPools (r:1 w:1) // Storage: NominationPools RewardPools (r:1 w:1) @@ -94,11 +92,10 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn bond_extra_transfer() -> Weight { - (110_157_000 as Weight) - .saturating_add(T::DbWeight::get().reads(14 as Weight)) - .saturating_add(T::DbWeight::get().writes(13 as Weight)) + (114_398_000 as Weight) + .saturating_add(T::DbWeight::get().reads(13 as Weight)) + .saturating_add(T::DbWeight::get().writes(12 as Weight)) } - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: NominationPools PoolMembers (r:1 w:1) // Storage: NominationPools BondedPools (r:1 w:1) // Storage: NominationPools RewardPools (r:1 w:1) @@ -109,21 +106,19 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList ListNodes (r:2 w:2) // Storage: BagsList ListBags (r:2 w:2) fn bond_extra_reward() -> Weight { - (124_003_000 as Weight) - .saturating_add(T::DbWeight::get().reads(14 as Weight)) - .saturating_add(T::DbWeight::get().writes(13 as Weight)) + (128_370_000 as Weight) + .saturating_add(T::DbWeight::get().reads(13 as Weight)) + .saturating_add(T::DbWeight::get().writes(12 as Weight)) } - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: NominationPools PoolMembers (r:1 w:1) // Storage: NominationPools BondedPools (r:1 w:1) // Storage: NominationPools RewardPools (r:1 w:1) // Storage: System Account (r:1 w:1) fn claim_payout() -> Weight { - (51_767_000 as Weight) - .saturating_add(T::DbWeight::get().reads(5 as Weight)) - .saturating_add(T::DbWeight::get().writes(5 as Weight)) + (52_338_000 as Weight) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) } - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: NominationPools PoolMembers (r:1 w:1) // Storage: NominationPools BondedPools (r:1 w:1) // Storage: NominationPools RewardPools (r:1 w:1) @@ -139,23 +134,21 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools SubPoolsStorage (r:1 w:1) // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) fn unbond() -> Weight { - (116_959_000 as Weight) - .saturating_add(T::DbWeight::get().reads(19 as Weight)) - .saturating_add(T::DbWeight::get().writes(14 as Weight)) + (120_866_000 as Weight) + .saturating_add(T::DbWeight::get().reads(18 as Weight)) + .saturating_add(T::DbWeight::get().writes(13 as Weight)) } - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: NominationPools BondedPools (r:1 w:0) // Storage: Staking Ledger (r:1 w:1) // Storage: Staking CurrentEra (r:1 w:0) // Storage: Balances Locks (r:1 w:1) fn pool_withdraw_unbonded(s: u32, ) -> Weight { - (41_124_000 as Weight) + (42_038_000 as Weight) // Standard Error: 0 .saturating_add((52_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(5 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) } - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: NominationPools PoolMembers (r:1 w:1) // Storage: Staking CurrentEra (r:1 w:0) // Storage: NominationPools BondedPools (r:1 w:1) @@ -165,13 +158,12 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) // Storage: NominationPools CounterForPoolMembers (r:1 w:1) fn withdraw_unbonded_update(s: u32, ) -> Weight { - (80_654_000 as Weight) + (83_220_000 as Weight) // Standard Error: 0 - .saturating_add((50_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(9 as Weight)) - .saturating_add(T::DbWeight::get().writes(8 as Weight)) + .saturating_add((48_000 as Weight).saturating_mul(s as Weight)) + .saturating_add(T::DbWeight::get().reads(8 as Weight)) + .saturating_add(T::DbWeight::get().writes(7 as Weight)) } - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: NominationPools PoolMembers (r:1 w:1) // Storage: Staking CurrentEra (r:1 w:0) // Storage: NominationPools BondedPools (r:1 w:1) @@ -192,11 +184,10 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools CounterForBondedPools (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn withdraw_unbonded_kill(_s: u32, ) -> Weight { - (140_296_000 as Weight) - .saturating_add(T::DbWeight::get().reads(20 as Weight)) - .saturating_add(T::DbWeight::get().writes(17 as Weight)) + (143_981_000 as Weight) + .saturating_add(T::DbWeight::get().reads(19 as Weight)) + .saturating_add(T::DbWeight::get().writes(16 as Weight)) } - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: Staking MinNominatorBond (r:1 w:0) // Storage: NominationPools MinCreateBond (r:1 w:0) // Storage: NominationPools MinJoinBond (r:1 w:0) @@ -220,9 +211,9 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn create() -> Weight { - (130_543_000 as Weight) - .saturating_add(T::DbWeight::get().reads(23 as Weight)) - .saturating_add(T::DbWeight::get().writes(16 as Weight)) + (134_974_000 as Weight) + .saturating_add(T::DbWeight::get().reads(22 as Weight)) + .saturating_add(T::DbWeight::get().writes(15 as Weight)) } // Storage: NominationPools BondedPools (r:1 w:0) // Storage: Staking Ledger (r:1 w:0) @@ -237,9 +228,9 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList CounterForListNodes (r:1 w:1) // Storage: Staking CounterForNominators (r:1 w:1) fn nominate(n: u32, ) -> Weight { - (46_152_000 as Weight) - // Standard Error: 15_000 - .saturating_add((2_114_000 as Weight).saturating_mul(n as Weight)) + (47_085_000 as Weight) + // Standard Error: 11_000 + .saturating_add((2_222_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(12 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) .saturating_add(T::DbWeight::get().writes(5 as Weight)) @@ -247,7 +238,7 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Ledger (r:1 w:0) fn set_state() -> Weight { - (23_544_000 as Weight) + (24_192_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -255,7 +246,7 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools Metadata (r:1 w:1) // Storage: NominationPools CounterForMetadata (r:1 w:1) fn set_metadata(n: u32, ) -> Weight { - (11_032_000 as Weight) + (11_448_000 as Weight) // Standard Error: 0 .saturating_add((1_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) @@ -267,12 +258,12 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools MinCreateBond (r:0 w:1) // Storage: NominationPools MaxPools (r:0 w:1) fn set_configs() -> Weight { - (2_910_000 as Weight) + (2_829_000 as Weight) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } // Storage: NominationPools BondedPools (r:1 w:1) fn update_roles() -> Weight { - (18_608_000 as Weight) + (19_017_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -280,7 +271,6 @@ impl WeightInfo for SubstrateWeight { // For backwards compatibility and tests impl WeightInfo for () { - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: NominationPools MinJoinBond (r:1 w:0) // Storage: NominationPools PoolMembers (r:1 w:1) // Storage: NominationPools BondedPools (r:1 w:1) @@ -295,11 +285,10 @@ impl WeightInfo for () { // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn join() -> Weight { - (119_253_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(18 as Weight)) - .saturating_add(RocksDbWeight::get().writes(12 as Weight)) + (122_806_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(17 as Weight)) + .saturating_add(RocksDbWeight::get().writes(11 as Weight)) } - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: NominationPools PoolMembers (r:1 w:1) // Storage: NominationPools BondedPools (r:1 w:1) // Storage: NominationPools RewardPools (r:1 w:1) @@ -310,11 +299,10 @@ impl WeightInfo for () { // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn bond_extra_transfer() -> Weight { - (110_157_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(14 as Weight)) - .saturating_add(RocksDbWeight::get().writes(13 as Weight)) + (114_398_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(13 as Weight)) + .saturating_add(RocksDbWeight::get().writes(12 as Weight)) } - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: NominationPools PoolMembers (r:1 w:1) // Storage: NominationPools BondedPools (r:1 w:1) // Storage: NominationPools RewardPools (r:1 w:1) @@ -325,21 +313,19 @@ impl WeightInfo for () { // Storage: BagsList ListNodes (r:2 w:2) // Storage: BagsList ListBags (r:2 w:2) fn bond_extra_reward() -> Weight { - (124_003_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(14 as Weight)) - .saturating_add(RocksDbWeight::get().writes(13 as Weight)) + (128_370_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(13 as Weight)) + .saturating_add(RocksDbWeight::get().writes(12 as Weight)) } - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: NominationPools PoolMembers (r:1 w:1) // Storage: NominationPools BondedPools (r:1 w:1) // Storage: NominationPools RewardPools (r:1 w:1) // Storage: System Account (r:1 w:1) fn claim_payout() -> Weight { - (51_767_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(5 as Weight)) - .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + (52_338_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(4 as Weight)) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: NominationPools PoolMembers (r:1 w:1) // Storage: NominationPools BondedPools (r:1 w:1) // Storage: NominationPools RewardPools (r:1 w:1) @@ -355,23 +341,21 @@ impl WeightInfo for () { // Storage: NominationPools SubPoolsStorage (r:1 w:1) // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) fn unbond() -> Weight { - (116_959_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(19 as Weight)) - .saturating_add(RocksDbWeight::get().writes(14 as Weight)) + (120_866_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(18 as Weight)) + .saturating_add(RocksDbWeight::get().writes(13 as Weight)) } - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: NominationPools BondedPools (r:1 w:0) // Storage: Staking Ledger (r:1 w:1) // Storage: Staking CurrentEra (r:1 w:0) // Storage: Balances Locks (r:1 w:1) fn pool_withdraw_unbonded(s: u32, ) -> Weight { - (41_124_000 as Weight) + (42_038_000 as Weight) // Standard Error: 0 .saturating_add((52_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(5 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + .saturating_add(RocksDbWeight::get().reads(4 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: NominationPools PoolMembers (r:1 w:1) // Storage: Staking CurrentEra (r:1 w:0) // Storage: NominationPools BondedPools (r:1 w:1) @@ -381,13 +365,12 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) // Storage: NominationPools CounterForPoolMembers (r:1 w:1) fn withdraw_unbonded_update(s: u32, ) -> Weight { - (80_654_000 as Weight) + (83_220_000 as Weight) // Standard Error: 0 - .saturating_add((50_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(9 as Weight)) - .saturating_add(RocksDbWeight::get().writes(8 as Weight)) + .saturating_add((48_000 as Weight).saturating_mul(s as Weight)) + .saturating_add(RocksDbWeight::get().reads(8 as Weight)) + .saturating_add(RocksDbWeight::get().writes(7 as Weight)) } - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: NominationPools PoolMembers (r:1 w:1) // Storage: Staking CurrentEra (r:1 w:0) // Storage: NominationPools BondedPools (r:1 w:1) @@ -408,11 +391,10 @@ impl WeightInfo for () { // Storage: NominationPools CounterForBondedPools (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn withdraw_unbonded_kill(_s: u32, ) -> Weight { - (140_296_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(20 as Weight)) - .saturating_add(RocksDbWeight::get().writes(17 as Weight)) + (143_981_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(19 as Weight)) + .saturating_add(RocksDbWeight::get().writes(16 as Weight)) } - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: Staking MinNominatorBond (r:1 w:0) // Storage: NominationPools MinCreateBond (r:1 w:0) // Storage: NominationPools MinJoinBond (r:1 w:0) @@ -436,9 +418,9 @@ impl WeightInfo for () { // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn create() -> Weight { - (130_543_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(23 as Weight)) - .saturating_add(RocksDbWeight::get().writes(16 as Weight)) + (134_974_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(22 as Weight)) + .saturating_add(RocksDbWeight::get().writes(15 as Weight)) } // Storage: NominationPools BondedPools (r:1 w:0) // Storage: Staking Ledger (r:1 w:0) @@ -453,9 +435,9 @@ impl WeightInfo for () { // Storage: BagsList CounterForListNodes (r:1 w:1) // Storage: Staking CounterForNominators (r:1 w:1) fn nominate(n: u32, ) -> Weight { - (46_152_000 as Weight) - // Standard Error: 15_000 - .saturating_add((2_114_000 as Weight).saturating_mul(n as Weight)) + (47_085_000 as Weight) + // Standard Error: 11_000 + .saturating_add((2_222_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(12 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) @@ -463,7 +445,7 @@ impl WeightInfo for () { // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Ledger (r:1 w:0) fn set_state() -> Weight { - (23_544_000 as Weight) + (24_192_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -471,7 +453,7 @@ impl WeightInfo for () { // Storage: NominationPools Metadata (r:1 w:1) // Storage: NominationPools CounterForMetadata (r:1 w:1) fn set_metadata(n: u32, ) -> Weight { - (11_032_000 as Weight) + (11_448_000 as Weight) // Standard Error: 0 .saturating_add((1_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) @@ -483,12 +465,12 @@ impl WeightInfo for () { // Storage: NominationPools MinCreateBond (r:0 w:1) // Storage: NominationPools MaxPools (r:0 w:1) fn set_configs() -> Weight { - (2_910_000 as Weight) + (2_829_000 as Weight) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } // Storage: NominationPools BondedPools (r:1 w:1) fn update_roles() -> Weight { - (18_608_000 as Weight) + (19_017_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } diff --git a/frame/support/src/storage/transactional.rs b/frame/support/src/storage/transactional.rs index d1c59d44e2581..c46eb9258e0ca 100644 --- a/frame/support/src/storage/transactional.rs +++ b/frame/support/src/storage/transactional.rs @@ -30,11 +30,11 @@ use sp_io::storage::{commit_transaction, rollback_transaction, start_transaction use sp_runtime::{DispatchError, TransactionOutcome, TransactionalError}; /// The type that is being used to store the current number of active layers. -type Layer = u32; +pub type Layer = u32; /// The key that is holds the current number of active layers. -const TRANSACTION_LEVEL_KEY: &[u8] = b":transaction_level:"; +pub const TRANSACTION_LEVEL_KEY: &[u8] = b":transaction_level:"; /// The maximum number of nested layers. -const TRANSACTIONAL_LIMIT: Layer = 255; +pub const TRANSACTIONAL_LIMIT: Layer = 255; /// Returns the current number of nested transactional layers. fn get_transaction_level() -> Layer { From 7ac32b38a14cf48c0791450619b66b9270c2ce9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Thu, 12 May 2022 13:02:29 +0200 Subject: [PATCH 214/484] Make clippy happy on latest nightly (#11403) Co-authored-by: Vladimir Istyufeev --- bin/utils/chain-spec-builder/src/main.rs | 2 +- client/service/src/error.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/utils/chain-spec-builder/src/main.rs b/bin/utils/chain-spec-builder/src/main.rs index e972e130fcf78..641416154115b 100644 --- a/bin/utils/chain-spec-builder/src/main.rs +++ b/bin/utils/chain-spec-builder/src/main.rs @@ -159,7 +159,7 @@ fn generate_chain_spec( Default::default(), ); - chain_spec.as_json(false).map_err(|err| err) + chain_spec.as_json(false) } fn generate_authority_keys_and_store(seeds: &[String], keystore_path: &Path) -> Result<(), String> { diff --git a/client/service/src/error.rs b/client/service/src/error.rs index be2199de2643d..0d702c7f37b98 100644 --- a/client/service/src/error.rs +++ b/client/service/src/error.rs @@ -71,7 +71,7 @@ impl<'a> From<&'a str> for Error { } } -impl<'a> From for Error { +impl From for Error { fn from(s: String) -> Self { Error::Other(s) } From 4eceb4064004ed247e7ab297e846592bf02716db Mon Sep 17 00:00:00 2001 From: Tarek Mohamed Abdalla Date: Fri, 13 May 2022 19:21:21 +0200 Subject: [PATCH 215/484] [Tests] [pallet-timestamp] Add tests for OnTimestampSet. (#11323) * NF: move tests mod to another file * NF: separate test setup to mock mod * test: check if OnTimestampSet ran correctly. * docs: add license header. * fixup! NF: separate test setup to mock mod * NF: formatting changes --- frame/timestamp/src/benchmarking.rs | 2 +- frame/timestamp/src/lib.rs | 108 +-------------------------- frame/timestamp/src/mock.rs | 111 ++++++++++++++++++++++++++++ frame/timestamp/src/tests.rs | 52 +++++++++++++ 4 files changed, 168 insertions(+), 105 deletions(-) create mode 100644 frame/timestamp/src/mock.rs create mode 100644 frame/timestamp/src/tests.rs diff --git a/frame/timestamp/src/benchmarking.rs b/frame/timestamp/src/benchmarking.rs index 0da71dbdd1a59..8974eb95692af 100644 --- a/frame/timestamp/src/benchmarking.rs +++ b/frame/timestamp/src/benchmarking.rs @@ -56,5 +56,5 @@ benchmarks! { ensure!(!DidUpdate::::exists(), "Time was not removed."); } - impl_benchmark_test_suite!(Timestamp, crate::tests::new_test_ext(), crate::tests::Test); + impl_benchmark_test_suite!(Timestamp, crate::mock::new_test_ext(), crate::mock::Test); } diff --git a/frame/timestamp/src/lib.rs b/frame/timestamp/src/lib.rs index c75a0ac9765b9..81ed67913c2e6 100644 --- a/frame/timestamp/src/lib.rs +++ b/frame/timestamp/src/lib.rs @@ -103,6 +103,10 @@ #![cfg_attr(not(feature = "std"), no_std)] mod benchmarking; +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; pub mod weights; use frame_support::traits::{OnTimestampSet, Time, UnixTime}; @@ -309,107 +313,3 @@ impl UnixTime for Pallet { core::time::Duration::from_millis(now.saturated_into::()) } } - -#[cfg(test)] -mod tests { - use super::*; - use crate as pallet_timestamp; - - use frame_support::{ - assert_ok, parameter_types, - traits::{ConstU32, ConstU64}, - }; - use sp_core::H256; - use sp_io::TestExternalities; - use sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, IdentityLookup}, - }; - - pub fn new_test_ext() -> TestExternalities { - let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - TestExternalities::new(t) - } - - type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; - type Block = frame_system::mocking::MockBlock; - - frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, - } - ); - - parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(1024); - } - impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type Origin = Origin; - type Index = u64; - type BlockNumber = u64; - type Call = Call; - type Hash = H256; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; - type Header = Header; - type Event = Event; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; - } - - impl Config for Test { - type Moment = u64; - type OnTimestampSet = (); - type MinimumPeriod = ConstU64<5>; - type WeightInfo = (); - } - - #[test] - fn timestamp_works() { - new_test_ext().execute_with(|| { - Timestamp::set_timestamp(42); - assert_ok!(Timestamp::set(Origin::none(), 69)); - assert_eq!(Timestamp::now(), 69); - }); - } - - #[test] - #[should_panic(expected = "Timestamp must be updated only once in the block")] - fn double_timestamp_should_fail() { - new_test_ext().execute_with(|| { - Timestamp::set_timestamp(42); - assert_ok!(Timestamp::set(Origin::none(), 69)); - let _ = Timestamp::set(Origin::none(), 70); - }); - } - - #[test] - #[should_panic( - expected = "Timestamp must increment by at least between sequential blocks" - )] - fn block_period_minimum_enforced() { - new_test_ext().execute_with(|| { - Timestamp::set_timestamp(42); - let _ = Timestamp::set(Origin::none(), 46); - }); - } -} diff --git a/frame/timestamp/src/mock.rs b/frame/timestamp/src/mock.rs new file mode 100644 index 0000000000000..9536414c54db6 --- /dev/null +++ b/frame/timestamp/src/mock.rs @@ -0,0 +1,111 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Tests Utilities. + +use super::*; +use crate as pallet_timestamp; +use sp_std::cell::RefCell; + +use frame_support::{ + parameter_types, + traits::{ConstU32, ConstU64}, +}; +use sp_core::H256; +use sp_io::TestExternalities; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, +}; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; +type Moment = u64; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, + } +); + +parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(1024); +} +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type Origin = Origin; + type Index = u64; + type BlockNumber = u64; + type Call = Call; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +thread_local! { + pub static CAPTURED_MOMENT: RefCell> = RefCell::new(None); +} + +pub struct MockOnTimestampSet; +impl OnTimestampSet for MockOnTimestampSet { + fn on_timestamp_set(moment: Moment) { + CAPTURED_MOMENT.with(|x| *x.borrow_mut() = Some(moment)); + } +} + +impl Config for Test { + type Moment = Moment; + type OnTimestampSet = MockOnTimestampSet; + type MinimumPeriod = ConstU64<5>; + type WeightInfo = (); +} + +pub(crate) fn clear_captured_moment() { + CAPTURED_MOMENT.with(|x| *x.borrow_mut() = None); +} + +pub(crate) fn get_captured_moment() -> Option { + CAPTURED_MOMENT.with(|x| x.borrow().clone()) +} + +pub(crate) fn new_test_ext() -> TestExternalities { + let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + clear_captured_moment(); + TestExternalities::new(t) +} diff --git a/frame/timestamp/src/tests.rs b/frame/timestamp/src/tests.rs new file mode 100644 index 0000000000000..f52ba7849c951 --- /dev/null +++ b/frame/timestamp/src/tests.rs @@ -0,0 +1,52 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Tests for the Timestamp module. + +use crate::mock::*; +use frame_support::assert_ok; + +#[test] +fn timestamp_works() { + new_test_ext().execute_with(|| { + Timestamp::set_timestamp(42); + assert_ok!(Timestamp::set(Origin::none(), 69)); + assert_eq!(Timestamp::now(), 69); + assert_eq!(Some(69), get_captured_moment()); + }); +} + +#[test] +#[should_panic(expected = "Timestamp must be updated only once in the block")] +fn double_timestamp_should_fail() { + new_test_ext().execute_with(|| { + Timestamp::set_timestamp(42); + assert_ok!(Timestamp::set(Origin::none(), 69)); + let _ = Timestamp::set(Origin::none(), 70); + }); +} + +#[test] +#[should_panic( + expected = "Timestamp must increment by at least between sequential blocks" +)] +fn block_period_minimum_enforced() { + new_test_ext().execute_with(|| { + Timestamp::set_timestamp(42); + let _ = Timestamp::set(Origin::none(), 46); + }); +} From 8d79da692a36f6c721f5c77ee97fad1fef271769 Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Sat, 14 May 2022 10:17:53 +0300 Subject: [PATCH 216/484] Network sync refactoring (part 3) (#11347) * Move `light.v1.proto` schema into new crate `sc-network-light` * Move `sc_network::light_client_requests` and submodule to `sc_network_light::light_client_requests` * Fix apparently outdated reference in documentation and visibility modifier * Fix rustdoc check * Update lock file --- Cargo.lock | 20 +++++++++++ Cargo.toml | 3 +- client/network/Cargo.toml | 1 + client/network/build.rs | 2 +- client/network/light/Cargo.toml | 33 +++++++++++++++++++ client/network/light/build.rs | 5 +++ client/network/light/src/lib.rs | 22 +++++++++++++ .../{ => light}/src/light_client_requests.rs | 0 .../src/light_client_requests/handler.rs | 5 +-- client/network/light/src/schema.rs | 25 ++++++++++++++ .../{ => light}/src/schema/light.v1.proto | 0 client/network/src/config.rs | 8 ++--- client/network/src/lib.rs | 2 +- client/network/src/schema.rs | 6 ---- client/network/src/service/tests.rs | 4 +-- .../network/sync/src/block_request_handler.rs | 4 +-- 16 files changed, 120 insertions(+), 20 deletions(-) create mode 100644 client/network/light/Cargo.toml create mode 100644 client/network/light/build.rs create mode 100644 client/network/light/src/lib.rs rename client/network/{ => light}/src/light_client_requests.rs (100%) rename client/network/{ => light}/src/light_client_requests/handler.rs (98%) create mode 100644 client/network/light/src/schema.rs rename client/network/{ => light}/src/schema/light.v1.proto (100%) diff --git a/Cargo.lock b/Cargo.lock index 68272e1df9c8c..1683cc2b06b69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8509,6 +8509,7 @@ dependencies = [ "sc-client-api", "sc-consensus", "sc-network-common", + "sc-network-light", "sc-network-sync", "sc-peerset", "sc-utils", @@ -8564,6 +8565,25 @@ dependencies = [ "tracing", ] +[[package]] +name = "sc-network-light" +version = "0.10.0-dev" +dependencies = [ + "futures", + "libp2p", + "log", + "parity-scale-codec", + "prost", + "prost-build", + "sc-client-api", + "sc-network-common", + "sc-peerset", + "sp-blockchain", + "sp-core", + "sp-runtime", + "thiserror", +] + [[package]] name = "sc-network-sync" version = "0.10.0-dev" diff --git a/Cargo.toml b/Cargo.toml index 39ccceeb3a030..41739fe6f1ebc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,8 +42,9 @@ members = [ "client/informant", "client/keystore", "client/network", - "client/network/common", "client/network-gossip", + "client/network/common", + "client/network/light", "client/network/sync", "client/network/test", "client/offchain", diff --git a/client/network/Cargo.toml b/client/network/Cargo.toml index 51e7e762f5292..7e12d9862f8ef 100644 --- a/client/network/Cargo.toml +++ b/client/network/Cargo.toml @@ -51,6 +51,7 @@ sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } sc-client-api = { version = "4.0.0-dev", path = "../api" } sc-consensus = { version = "0.10.0-dev", path = "../consensus/common" } sc-network-common = { version = "0.10.0-dev", path = "./common" } +sc-network-light = { version = "0.10.0-dev", path = "./light" } sc-network-sync = { version = "0.10.0-dev", path = "./sync" } sc-peerset = { version = "4.0.0-dev", path = "../peerset" } sc-utils = { version = "4.0.0-dev", path = "../utils" } diff --git a/client/network/build.rs b/client/network/build.rs index f551f61dab3d4..671277230a774 100644 --- a/client/network/build.rs +++ b/client/network/build.rs @@ -1,4 +1,4 @@ -const PROTOS: &[&str] = &["src/schema/light.v1.proto", "src/schema/bitswap.v1.2.0.proto"]; +const PROTOS: &[&str] = &["src/schema/bitswap.v1.2.0.proto"]; fn main() { prost_build::compile_protos(PROTOS, &["src/schema"]).unwrap(); diff --git a/client/network/light/Cargo.toml b/client/network/light/Cargo.toml new file mode 100644 index 0000000000000..8bbbce9274c0c --- /dev/null +++ b/client/network/light/Cargo.toml @@ -0,0 +1,33 @@ +[package] +description = "Substrate light network protocol" +name = "sc-network-light" +version = "0.10.0-dev" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +authors = ["Parity Technologies "] +edition = "2021" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +documentation = "https://docs.rs/sc-network-light" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[build-dependencies] +prost-build = "0.9" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", features = [ + "derive", +] } +futures = "0.3.21" +libp2p = "0.44.0" +log = "0.4.16" +prost = "0.9" +sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } +sc-client-api = { version = "4.0.0-dev", path = "../../api" } +sc-network-common = { version = "0.10.0-dev", path = "../common" } +sc-peerset = { version = "4.0.0-dev", path = "../../peerset" } +sp-core = { version = "6.0.0", path = "../../../primitives/core" } +sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } +thiserror = "1.0" diff --git a/client/network/light/build.rs b/client/network/light/build.rs new file mode 100644 index 0000000000000..9c44bcd293181 --- /dev/null +++ b/client/network/light/build.rs @@ -0,0 +1,5 @@ +const PROTOS: &[&str] = &["src/schema/light.v1.proto"]; + +fn main() { + prost_build::compile_protos(PROTOS, &["src/schema"]).unwrap(); +} diff --git a/client/network/light/src/lib.rs b/client/network/light/src/lib.rs new file mode 100644 index 0000000000000..2b7cf226f90dd --- /dev/null +++ b/client/network/light/src/lib.rs @@ -0,0 +1,22 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Light client data structures of the networking layer. + +pub mod light_client_requests; +mod schema; diff --git a/client/network/src/light_client_requests.rs b/client/network/light/src/light_client_requests.rs similarity index 100% rename from client/network/src/light_client_requests.rs rename to client/network/light/src/light_client_requests.rs diff --git a/client/network/src/light_client_requests/handler.rs b/client/network/light/src/light_client_requests/handler.rs similarity index 98% rename from client/network/src/light_client_requests/handler.rs rename to client/network/light/src/light_client_requests/handler.rs index bf65cba5f82e5..3c87ccfd6ed9f 100644 --- a/client/network/src/light_client_requests/handler.rs +++ b/client/network/light/src/light_client_requests/handler.rs @@ -22,9 +22,10 @@ //! `crate::request_responses::RequestResponsesBehaviour` with //! [`LightClientRequestHandler`](handler::LightClientRequestHandler). -use crate::{schema, PeerId}; +use crate::schema; use codec::{self, Decode, Encode}; use futures::{channel::mpsc, prelude::*}; +use libp2p::PeerId; use log::{debug, trace}; use prost::Message; use sc_client_api::{ProofProvider, StorageProof}; @@ -55,7 +56,7 @@ where B: Block, Client: ProofProvider + Send + Sync + 'static, { - /// Create a new [`sc_network_sync::block_request_handler::BlockRequestHandler`]. + /// Create a new [`LightClientRequestHandler`]. pub fn new(protocol_id: &ProtocolId, client: Arc) -> (Self, ProtocolConfig) { // For now due to lack of data on light client request handling in production systems, this // value is chosen to match the block request limit. diff --git a/client/network/light/src/schema.rs b/client/network/light/src/schema.rs new file mode 100644 index 0000000000000..09cc82cc2811a --- /dev/null +++ b/client/network/light/src/schema.rs @@ -0,0 +1,25 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Include sources generated from protobuf definitions. + +pub(crate) mod v1 { + pub(crate) mod light { + include!(concat!(env!("OUT_DIR"), "/api.v1.light.rs")); + } +} diff --git a/client/network/src/schema/light.v1.proto b/client/network/light/src/schema/light.v1.proto similarity index 100% rename from client/network/src/schema/light.v1.proto rename to client/network/light/src/schema/light.v1.proto diff --git a/client/network/src/config.rs b/client/network/src/config.rs index cfb06331b55a1..e44977e5be6b3 100644 --- a/client/network/src/config.rs +++ b/client/network/src/config.rs @@ -122,10 +122,10 @@ where /// Request response configuration for the light client request protocol. /// /// Can be constructed either via - /// [`crate::light_client_requests::generate_protocol_config`] allowing outgoing but not - /// incoming requests, or constructed via - /// [`crate::light_client_requests::handler::LightClientRequestHandler::new`] allowing - /// both outgoing and incoming requests. + /// [`sc_network_light::light_client_requests::generate_protocol_config`] allowing outgoing but + /// not incoming requests, or constructed via + /// [`sc_network_light::light_client_requests::handler::LightClientRequestHandler::new`] + /// allowing both outgoing and incoming requests. pub light_client_request_protocol_config: RequestResponseConfig, /// Request response configuration for the state request protocol. diff --git a/client/network/src/lib.rs b/client/network/src/lib.rs index 3957aab22cca9..fff30550eb8c9 100644 --- a/client/network/src/lib.rs +++ b/client/network/src/lib.rs @@ -257,7 +257,6 @@ mod utils; pub mod bitswap; pub mod config; pub mod error; -pub mod light_client_requests; pub mod network_state; pub mod transactions; @@ -267,6 +266,7 @@ pub use protocol::{ event::{DhtEvent, Event, ObservedRole}, PeerInfo, }; +pub use sc_network_light::light_client_requests; pub use sc_network_sync::{ block_request_handler, state::StateDownloadProgress, diff --git a/client/network/src/schema.rs b/client/network/src/schema.rs index 80301a59c29ef..4893bc28a7355 100644 --- a/client/network/src/schema.rs +++ b/client/network/src/schema.rs @@ -18,12 +18,6 @@ //! Include sources generated from protobuf definitions. -pub mod v1 { - pub mod light { - include!(concat!(env!("OUT_DIR"), "/api.v1.light.rs")); - } -} - pub mod bitswap { include!(concat!(env!("OUT_DIR"), "/bitswap.message.rs")); } diff --git a/client/network/src/service/tests.rs b/client/network/src/service/tests.rs index 36205f32d33c6..808546d67fc7c 100644 --- a/client/network/src/service/tests.rs +++ b/client/network/src/service/tests.rs @@ -17,13 +17,13 @@ // along with this program. If not, see . use crate::{ - config, light_client_requests::handler::LightClientRequestHandler, - state_request_handler::StateRequestHandler, Event, NetworkService, NetworkWorker, + config, state_request_handler::StateRequestHandler, Event, NetworkService, NetworkWorker, }; use futures::prelude::*; use libp2p::PeerId; use sc_network_common::config::ProtocolId; +use sc_network_light::light_client_requests::handler::LightClientRequestHandler; use sc_network_sync::block_request_handler::BlockRequestHandler; use sp_runtime::traits::{Block as BlockT, Header as _}; use std::{borrow::Cow, sync::Arc, time::Duration}; diff --git a/client/network/sync/src/block_request_handler.rs b/client/network/sync/src/block_request_handler.rs index b9ffd24cee4c7..78d6a1a7d1680 100644 --- a/client/network/sync/src/block_request_handler.rs +++ b/client/network/sync/src/block_request_handler.rs @@ -75,9 +75,7 @@ pub fn generate_protocol_config(protocol_id: &ProtocolId) -> ProtocolConfig { } /// Generate the block protocol name from chain specific protocol identifier. -// Visibility `pub(crate)` to allow `crate::light_client_requests::sender` to generate block request -// protocol name and send block requests. -pub(crate) fn generate_protocol_name(protocol_id: &ProtocolId) -> String { +fn generate_protocol_name(protocol_id: &ProtocolId) -> String { format!("/{}/sync/2", protocol_id.as_ref()) } From 1fbcbb74e574471330028dd4f83e86526e818b5a Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Sun, 15 May 2022 08:25:48 +0100 Subject: [PATCH 217/484] make pool roles optional (#11411) * make pool roles optional * undo lock file changes? * add migration * Fix * fix review comments --- Cargo.lock | 1 + frame/nomination-pools/Cargo.toml | 2 + .../nomination-pools/benchmarking/src/lib.rs | 24 +-- frame/nomination-pools/src/lib.rs | 108 +++++++++---- frame/nomination-pools/src/migration.rs | 105 ++++++++++++ frame/nomination-pools/src/tests.rs | 149 ++++++++++++++---- frame/staking/src/lib.rs | 7 +- frame/staking/src/tests.rs | 76 ++++++++- 8 files changed, 400 insertions(+), 72 deletions(-) create mode 100644 frame/nomination-pools/src/migration.rs diff --git a/Cargo.lock b/Cargo.lock index 1683cc2b06b69..1995ebb46023e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5892,6 +5892,7 @@ version = "1.0.0" dependencies = [ "frame-support", "frame-system", + "log", "pallet-balances", "parity-scale-codec", "scale-info", diff --git a/frame/nomination-pools/Cargo.toml b/frame/nomination-pools/Cargo.toml index 8c2f9daf2777b..1ac8caa5f64b4 100644 --- a/frame/nomination-pools/Cargo.toml +++ b/frame/nomination-pools/Cargo.toml @@ -23,6 +23,7 @@ sp-runtime = { version = "6.0.0", default-features = false, path = "../../primit sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } +log = { version = "0.4.0", default-features = false } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } @@ -42,4 +43,5 @@ std = [ "sp-std/std", "sp-staking/std", "sp-core/std", + "log/std", ] diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index aa4c093dcf0d4..275b914cda297 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -526,9 +526,9 @@ frame_benchmarking::benchmarks! { member_counter: 1, roles: PoolRoles { depositor: depositor.clone(), - root: depositor.clone(), - nominator: depositor.clone(), - state_toggler: depositor.clone(), + root: Some(depositor.clone()), + nominator: Some(depositor.clone()), + state_toggler: Some(depositor.clone()), }, } ); @@ -567,9 +567,9 @@ frame_benchmarking::benchmarks! { member_counter: 1, roles: PoolRoles { depositor: depositor.clone(), - root: depositor.clone(), - nominator: depositor.clone(), - state_toggler: depositor.clone(), + root: Some(depositor.clone()), + nominator: Some(depositor.clone()), + state_toggler: Some(depositor.clone()), } } ); @@ -638,17 +638,17 @@ frame_benchmarking::benchmarks! { }:_( Origin::Signed(root.clone()), first_id, - Some(random.clone()), - Some(random.clone()), - Some(random.clone()) + ConfigOp::Set(random.clone()), + ConfigOp::Set(random.clone()), + ConfigOp::Set(random.clone()) ) verify { assert_eq!( pallet_nomination_pools::BondedPools::::get(first_id).unwrap().roles, pallet_nomination_pools::PoolRoles { depositor: root, - nominator: random.clone(), - state_toggler: random.clone(), - root: random, + nominator: Some(random.clone()), + state_toggler: Some(random.clone()), + root: Some(random), }, ) } diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 0fff470eec43b..d68a6f09c31d1 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -324,11 +324,26 @@ use sp_runtime::traits::{AccountIdConversion, Bounded, CheckedSub, Convert, Satu use sp_staking::{EraIndex, OnStakerSlash, StakingInterface}; use sp_std::{collections::btree_map::BTreeMap, fmt::Debug, ops::Div, vec::Vec}; +/// The log target of this pallet. +pub const LOG_TARGET: &'static str = "runtime::nomination-pools"; + +// syntactic sugar for logging. +#[macro_export] +macro_rules! log { + ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { + log::$level!( + target: crate::LOG_TARGET, + concat!("[{:?}] 🏊‍♂️ ", $patter), >::block_number() $(, $values)* + ) + }; +} + #[cfg(test)] mod mock; #[cfg(test)] mod tests; +pub mod migration; pub mod weights; pub use pallet::*; @@ -502,7 +517,11 @@ pub enum PoolState { Destroying, } -/// Pool adminstration roles. +/// Pool administration roles. +/// +/// Any pool has a depositor, which can never change. But, all the other roles are optional, and +/// cannot exist. Note that if `root` is set to `None`, it basically means that the roles of this +/// pool can never change again (except via governance). #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, Debug, PartialEq, Clone)] pub struct PoolRoles { /// Creates the pool and is the initial member. They can only leave the pool once all other @@ -510,11 +529,11 @@ pub struct PoolRoles { pub depositor: AccountId, /// Can change the nominator, state-toggler, or itself and can perform any of the actions the /// nominator or state-toggler can. - pub root: AccountId, + pub root: Option, /// Can select which validators the pool nominates. - pub nominator: AccountId, + pub nominator: Option, /// Can change the pools state and kick members if the pool is blocked. - pub state_toggler: AccountId, + pub state_toggler: Option, } /// Pool permissions and state @@ -665,25 +684,36 @@ impl BondedPool { .saturating_sub(T::StakingInterface::active_stake(&account).unwrap_or_default()) } + fn is_root(&self, who: &T::AccountId) -> bool { + self.roles.root.as_ref().map_or(false, |root| root == who) + } + + fn is_state_toggler(&self, who: &T::AccountId) -> bool { + self.roles + .state_toggler + .as_ref() + .map_or(false, |state_toggler| state_toggler == who) + } + fn can_update_roles(&self, who: &T::AccountId) -> bool { - *who == self.roles.root + self.is_root(who) } fn can_nominate(&self, who: &T::AccountId) -> bool { - *who == self.roles.root || *who == self.roles.nominator + self.is_root(who) || + self.roles.nominator.as_ref().map_or(false, |nominator| nominator == who) } fn can_kick(&self, who: &T::AccountId) -> bool { - (*who == self.roles.root || *who == self.roles.state_toggler) && - self.state == PoolState::Blocked + self.state == PoolState::Blocked && (self.is_root(who) || self.is_state_toggler(who)) } fn can_toggle_state(&self, who: &T::AccountId) -> bool { - (*who == self.roles.root || *who == self.roles.state_toggler) && !self.is_destroying() + (self.is_root(who) || self.is_state_toggler(who)) && !self.is_destroying() } fn can_set_metadata(&self, who: &T::AccountId) -> bool { - *who == self.roles.root || *who == self.roles.state_toggler + self.is_root(who) || self.is_state_toggler(who) } fn is_destroying(&self) -> bool { @@ -987,11 +1017,12 @@ impl SubPools { /// /// This is often used whilst getting the sub-pool from storage, thus it consumes and returns /// `Self` for ergonomic purposes. - fn maybe_merge_pools(mut self, unbond_era: EraIndex) -> Self { + fn maybe_merge_pools(mut self, current_era: EraIndex) -> Self { // Ex: if `TotalUnbondingPools` is 5 and current era is 10, we only want to retain pools // 6..=10. Note that in the first few eras where `checked_sub` is `None`, we don't remove // anything. - if let Some(newest_era_to_remove) = unbond_era.checked_sub(TotalUnbondingPools::::get()) + if let Some(newest_era_to_remove) = + current_era.checked_sub(T::PostUnbondingPoolsWindow::get()) { self.with_era.retain(|k, v| { if *k > newest_era_to_remove { @@ -1045,11 +1076,15 @@ impl Get for TotalUnbondingPools { #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::transactional; + use frame_support::{traits::StorageVersion, transactional}; use frame_system::{ensure_signed, pallet_prelude::*}; + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + #[pallet::pallet] #[pallet::generate_store(pub(crate) trait Store)] + #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); #[pallet::config] @@ -1218,8 +1253,13 @@ pub mod pallet { /// /// The removal can be voluntary (withdrawn all unbonded funds) or involuntary (kicked). MemberRemoved { pool_id: PoolId, member: T::AccountId }, - /// The roles of a pool have been updated to the given new roles. - RolesUpdated { root: T::AccountId, state_toggler: T::AccountId, nominator: T::AccountId }, + /// The roles of a pool have been updated to the given new roles. Note that the depositor + /// can never change. + RolesUpdated { + root: Option, + state_toggler: Option, + nominator: Option, + }, } #[pallet::error] @@ -1470,7 +1510,7 @@ pub mod pallet { // Note that we lazily create the unbonding pools here if they don't already exist let mut sub_pools = SubPoolsStorage::::get(member.pool_id) .unwrap_or_default() - .maybe_merge_pools(unbond_era); + .maybe_merge_pools(current_era); // Update the unbond pool associated with the current era with the unbonded funds. Note // that we lazily create the unbond pool if it does not yet exist. @@ -1693,7 +1733,12 @@ pub mod pallet { }); let mut bonded_pool = BondedPool::::new( pool_id, - PoolRoles { root, nominator, state_toggler, depositor: who.clone() }, + PoolRoles { + root: Some(root), + nominator: Some(nominator), + state_toggler: Some(state_toggler), + depositor: who.clone(), + }, ); bonded_pool.try_inc_members()?; @@ -1850,9 +1895,9 @@ pub mod pallet { pub fn update_roles( origin: OriginFor, pool_id: PoolId, - root: Option, - nominator: Option, - state_toggler: Option, + new_root: ConfigOp, + new_nominator: ConfigOp, + new_state_toggler: ConfigOp, ) -> DispatchResult { let mut bonded_pool = match ensure_root(origin.clone()) { Ok(()) => BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?, @@ -1865,17 +1910,20 @@ pub mod pallet { }, }; - match root { - None => (), - Some(v) => bonded_pool.roles.root = v, + match new_root { + ConfigOp::Noop => (), + ConfigOp::Remove => bonded_pool.roles.root = None, + ConfigOp::Set(v) => bonded_pool.roles.root = Some(v), }; - match nominator { - None => (), - Some(v) => bonded_pool.roles.nominator = v, + match new_nominator { + ConfigOp::Noop => (), + ConfigOp::Remove => bonded_pool.roles.nominator = None, + ConfigOp::Set(v) => bonded_pool.roles.nominator = Some(v), }; - match state_toggler { - None => (), - Some(v) => bonded_pool.roles.state_toggler = v, + match new_state_toggler { + ConfigOp::Noop => (), + ConfigOp::Remove => bonded_pool.roles.state_toggler = None, + ConfigOp::Set(v) => bonded_pool.roles.state_toggler = Some(v), }; Self::deposit_event(Event::::RolesUpdated { @@ -2282,7 +2330,7 @@ impl OnStakerSlash> for Pallet { _slashed_bonded: BalanceOf, slashed_unlocking: &BTreeMap>, ) { - if let Some(pool_id) = ReversePoolIdLookup::::get(pool_account) { + if let Some(pool_id) = ReversePoolIdLookup::::get(pool_account).defensive() { let mut sub_pools = match SubPoolsStorage::::get(pool_id).defensive() { Some(sub_pools) => sub_pools, None => return, diff --git a/frame/nomination-pools/src/migration.rs b/frame/nomination-pools/src/migration.rs new file mode 100644 index 0000000000000..e23a35fe85602 --- /dev/null +++ b/frame/nomination-pools/src/migration.rs @@ -0,0 +1,105 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::*; + +pub mod v1 { + use super::*; + use crate::log; + use frame_support::traits::OnRuntimeUpgrade; + + #[derive(Decode)] + pub struct OldPoolRoles { + pub depositor: AccountId, + pub root: AccountId, + pub nominator: AccountId, + pub state_toggler: AccountId, + } + + impl OldPoolRoles { + fn migrate_to_v1(self) -> PoolRoles { + PoolRoles { + depositor: self.depositor, + root: Some(self.root), + nominator: Some(self.nominator), + state_toggler: Some(self.state_toggler), + } + } + } + + #[derive(Decode)] + pub struct OldBondedPoolInner { + pub points: BalanceOf, + pub state: PoolState, + pub member_counter: u32, + pub roles: OldPoolRoles, + } + + impl OldBondedPoolInner { + fn migrate_to_v1(self) -> BondedPoolInner { + BondedPoolInner { + member_counter: self.member_counter, + points: self.points, + state: self.state, + roles: self.roles.migrate_to_v1(), + } + } + } + + /// Trivial migration which makes the roles of each pool optional. + /// + /// Note: The depositor is not optional since he can never change. + pub struct MigrateToV1(sp_std::marker::PhantomData); + impl OnRuntimeUpgrade for MigrateToV1 { + fn on_runtime_upgrade() -> Weight { + let current = Pallet::::current_storage_version(); + let onchain = Pallet::::on_chain_storage_version(); + + log!( + info, + "Running migration with current storage version {:?} / onchain {:?}", + current, + onchain + ); + + if current == 1 && onchain == 0 { + // this is safe to execute on any runtime that has a bounded number of pools. + let mut translated = 0u64; + BondedPools::::translate::, _>(|_key, old_value| { + translated.saturating_inc(); + Some(old_value.migrate_to_v1()) + }); + + current.put::>(); + + log!(info, "Upgraded {} pools, storage to version {:?}", translated, current); + + T::DbWeight::get().reads_writes(translated + 1, translated + 1) + } else { + log!(info, "Migration did not executed. This probably should be removed"); + T::DbWeight::get().reads(1) + } + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade() -> Result<(), &'static str> { + // new version must be set. + assert_eq!(Pallet::::on_chain_storage_version(), 1); + Ok(()) + } + } +} diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index f39b5c92375c4..fe78da3bb14af 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -38,7 +38,7 @@ macro_rules! member_unbonding_eras { } pub const DEFAULT_ROLES: PoolRoles = - PoolRoles { depositor: 10, root: 900, nominator: 901, state_toggler: 902 }; + PoolRoles { depositor: 10, root: Some(900), nominator: Some(901), state_toggler: Some(902) }; #[test] fn test_setup_works() { @@ -333,6 +333,8 @@ mod sub_pools { fn maybe_merge_pools_works() { ExtBuilder::default().build_and_execute(|| { assert_eq!(TotalUnbondingPools::::get(), 5); + assert_eq!(BondingDuration::get(), 3); + assert_eq!(PostUnbondingPoolsWindow::get(), 2); // Given let mut sub_pool_0 = SubPools:: { @@ -347,19 +349,19 @@ mod sub_pools { }; // When `current_era < TotalUnbondingPools`, - let sub_pool_1 = sub_pool_0.clone().maybe_merge_pools(3); + let sub_pool_1 = sub_pool_0.clone().maybe_merge_pools(0); // Then it exits early without modifications assert_eq!(sub_pool_1, sub_pool_0); // When `current_era == TotalUnbondingPools`, - let sub_pool_1 = sub_pool_1.maybe_merge_pools(4); + let sub_pool_1 = sub_pool_1.maybe_merge_pools(1); // Then it exits early without modifications assert_eq!(sub_pool_1, sub_pool_0); // When `current_era - TotalUnbondingPools == 0`, - let mut sub_pool_1 = sub_pool_1.maybe_merge_pools(5); + let mut sub_pool_1 = sub_pool_1.maybe_merge_pools(2); // Then era 0 is merged into the `no_era` pool sub_pool_0.no_era = sub_pool_0.with_era.remove(&0).unwrap(); @@ -376,7 +378,7 @@ mod sub_pools { .unwrap(); // When `current_era - TotalUnbondingPools == 1` - let sub_pool_2 = sub_pool_1.maybe_merge_pools(6); + let sub_pool_2 = sub_pool_1.maybe_merge_pools(3); let era_1_pool = sub_pool_0.with_era.remove(&1).unwrap(); // Then era 1 is merged into the `no_era` pool @@ -385,7 +387,7 @@ mod sub_pools { assert_eq!(sub_pool_2, sub_pool_0); // When `current_era - TotalUnbondingPools == 5`, so all pools with era <= 4 are removed - let sub_pool_3 = sub_pool_2.maybe_merge_pools(10); + let sub_pool_3 = sub_pool_2.maybe_merge_pools(7); // Then all eras <= 5 are merged into the `no_era` pool for era in 2..=5 { @@ -1723,9 +1725,9 @@ mod unbond { // Given unsafe_set_state(1, PoolState::Blocked).unwrap(); let bonded_pool = BondedPool::::get(1).unwrap(); - assert_eq!(bonded_pool.roles.root, 900); - assert_eq!(bonded_pool.roles.nominator, 901); - assert_eq!(bonded_pool.roles.state_toggler, 902); + assert_eq!(bonded_pool.roles.root.unwrap(), 900); + assert_eq!(bonded_pool.roles.nominator.unwrap(), 901); + assert_eq!(bonded_pool.roles.state_toggler.unwrap(), 902); // When the nominator tries to kick, then its a noop assert_noop!( @@ -3143,9 +3145,9 @@ mod create { state: PoolState::Open, roles: PoolRoles { depositor: 11, - root: 123, - nominator: 456, - state_toggler: 789 + root: Some(123), + nominator: Some(456), + state_toggler: Some(789) } } } @@ -3590,71 +3592,164 @@ mod update_roles { ExtBuilder::default().build_and_execute(|| { assert_eq!( BondedPools::::get(1).unwrap().roles, - PoolRoles { depositor: 10, root: 900, nominator: 901, state_toggler: 902 }, + PoolRoles { + depositor: 10, + root: Some(900), + nominator: Some(901), + state_toggler: Some(902) + }, ); // non-existent pools assert_noop!( - Pools::update_roles(Origin::signed(1), 2, Some(5), Some(6), Some(7)), + Pools::update_roles( + Origin::signed(1), + 2, + ConfigOp::Set(5), + ConfigOp::Set(6), + ConfigOp::Set(7) + ), Error::::PoolNotFound, ); // depositor cannot change roles. assert_noop!( - Pools::update_roles(Origin::signed(1), 1, Some(5), Some(6), Some(7)), + Pools::update_roles( + Origin::signed(1), + 1, + ConfigOp::Set(5), + ConfigOp::Set(6), + ConfigOp::Set(7) + ), Error::::DoesNotHavePermission, ); // nominator cannot change roles. assert_noop!( - Pools::update_roles(Origin::signed(901), 1, Some(5), Some(6), Some(7)), + Pools::update_roles( + Origin::signed(901), + 1, + ConfigOp::Set(5), + ConfigOp::Set(6), + ConfigOp::Set(7) + ), Error::::DoesNotHavePermission, ); // state-toggler assert_noop!( - Pools::update_roles(Origin::signed(902), 1, Some(5), Some(6), Some(7)), + Pools::update_roles( + Origin::signed(902), + 1, + ConfigOp::Set(5), + ConfigOp::Set(6), + ConfigOp::Set(7) + ), Error::::DoesNotHavePermission, ); // but root can - assert_ok!(Pools::update_roles(Origin::signed(900), 1, Some(5), Some(6), Some(7))); + assert_ok!(Pools::update_roles( + Origin::signed(900), + 1, + ConfigOp::Set(5), + ConfigOp::Set(6), + ConfigOp::Set(7) + )); assert_eq!( pool_events_since_last_call(), vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::RolesUpdated { root: 5, state_toggler: 7, nominator: 6 } + Event::RolesUpdated { + root: Some(5), + state_toggler: Some(7), + nominator: Some(6) + } ] ); assert_eq!( BondedPools::::get(1).unwrap().roles, - PoolRoles { depositor: 10, root: 5, nominator: 6, state_toggler: 7 }, + PoolRoles { + depositor: 10, + root: Some(5), + nominator: Some(6), + state_toggler: Some(7) + }, ); // also root origin can - assert_ok!(Pools::update_roles(Origin::root(), 1, Some(1), Some(2), Some(3))); + assert_ok!(Pools::update_roles( + Origin::root(), + 1, + ConfigOp::Set(1), + ConfigOp::Set(2), + ConfigOp::Set(3) + )); + + assert_eq!( + pool_events_since_last_call(), + vec![Event::RolesUpdated { + root: Some(1), + state_toggler: Some(3), + nominator: Some(2) + }] + ); + assert_eq!( + BondedPools::::get(1).unwrap().roles, + PoolRoles { + depositor: 10, + root: Some(1), + nominator: Some(2), + state_toggler: Some(3) + }, + ); + + // Noop works + assert_ok!(Pools::update_roles( + Origin::root(), + 1, + ConfigOp::Set(11), + ConfigOp::Noop, + ConfigOp::Noop + )); assert_eq!( pool_events_since_last_call(), - vec![Event::RolesUpdated { root: 1, state_toggler: 3, nominator: 2 }] + vec![Event::RolesUpdated { + root: Some(11), + state_toggler: Some(3), + nominator: Some(2) + }] ); + assert_eq!( BondedPools::::get(1).unwrap().roles, - PoolRoles { depositor: 10, root: 1, nominator: 2, state_toggler: 3 }, + PoolRoles { + depositor: 10, + root: Some(11), + nominator: Some(2), + state_toggler: Some(3) + }, ); - // None is a noop - assert_ok!(Pools::update_roles(Origin::root(), 1, Some(11), None, None)); + // Remove works + assert_ok!(Pools::update_roles( + Origin::root(), + 1, + ConfigOp::Set(69), + ConfigOp::Remove, + ConfigOp::Remove + )); assert_eq!( pool_events_since_last_call(), - vec![Event::RolesUpdated { root: 11, state_toggler: 3, nominator: 2 }] + vec![Event::RolesUpdated { root: Some(69), state_toggler: None, nominator: None }] ); assert_eq!( BondedPools::::get(1).unwrap().roles, - PoolRoles { depositor: 10, root: 11, nominator: 2, state_toggler: 3 }, + PoolRoles { depositor: 10, root: Some(69), nominator: None, state_toggler: None }, ); }) } diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 2d4c1ea9a3488..f17d09b413606 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -533,10 +533,12 @@ impl StakingLedger { /// case that either the active bonded or some unlocking chunks become dust after slashing. /// Returns the amount of funds actually slashed. /// + /// `slash_era` is the era in which the slash (which is being enacted now) actually happened. + /// /// # Note /// - /// This calls `Config::OnStakerSlash::on_slash` with information as to how the slash - /// was applied. + /// This calls `Config::OnStakerSlash::on_slash` with information as to how the slash was + /// applied. fn slash( &mut self, slash_amount: BalanceOf, @@ -615,6 +617,7 @@ impl StakingLedger { break } } + self.unlocking.retain(|c| !c.value.is_zero()); T::OnStakerSlash::on_slash(&self.stash, self.active, &slashed_unlocking); pre_slash_total.saturating_sub(self.total) diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 21d4714985c6b..ccd9558c5c21d 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -2787,6 +2787,80 @@ fn deferred_slashes_are_deferred() { }) } +#[test] +fn staker_cannot_bail_deferred_slash() { + // as long as SlashDeferDuration is less than BondingDuration, this should not be possible. + ExtBuilder::default().slash_defer_duration(2).build_and_execute(|| { + mock::start_active_era(1); + + assert_eq!(Balances::free_balance(11), 1000); + assert_eq!(Balances::free_balance(101), 2000); + + let exposure = Staking::eras_stakers(active_era(), 11); + let nominated_value = exposure.others.iter().find(|o| o.who == 101).unwrap().value; + + on_offence_now( + &[OffenceDetails { + offender: (11, Staking::eras_stakers(active_era(), 11)), + reporters: vec![], + }], + &[Perbill::from_percent(10)], + ); + + // now we chill + assert_ok!(Staking::chill(Origin::signed(100))); + assert_ok!(Staking::unbond(Origin::signed(100), 500)); + + assert_eq!(Staking::current_era().unwrap(), 1); + assert_eq!(active_era(), 1); + + assert_eq!( + Ledger::::get(100).unwrap(), + StakingLedger { + active: 0, + total: 500, + stash: 101, + claimed_rewards: Default::default(), + unlocking: bounded_vec![UnlockChunk { era: 4u32, value: 500 }], + } + ); + + // no slash yet. + assert_eq!(Balances::free_balance(11), 1000); + assert_eq!(Balances::free_balance(101), 2000); + + // no slash yet. + mock::start_active_era(2); + assert_eq!(Balances::free_balance(11), 1000); + assert_eq!(Balances::free_balance(101), 2000); + assert_eq!(Staking::current_era().unwrap(), 2); + assert_eq!(active_era(), 2); + + // no slash yet. + mock::start_active_era(3); + assert_eq!(Balances::free_balance(11), 1000); + assert_eq!(Balances::free_balance(101), 2000); + assert_eq!(Staking::current_era().unwrap(), 3); + assert_eq!(active_era(), 3); + + // and cannot yet unbond: + assert_storage_noop!(assert!(Staking::withdraw_unbonded(Origin::signed(100), 0).is_ok())); + assert_eq!( + Ledger::::get(100).unwrap().unlocking.into_inner(), + vec![UnlockChunk { era: 4u32, value: 500 as Balance }], + ); + + // at the start of era 4, slashes from era 1 are processed, + // after being deferred for at least 2 full eras. + mock::start_active_era(4); + + assert_eq!(Balances::free_balance(11), 900); + assert_eq!(Balances::free_balance(101), 2000 - (nominated_value / 10)); + + // and the leftover of the funds can now be unbonded. + }) +} + #[test] fn remove_deferred() { ExtBuilder::default().slash_defer_duration(2).build_and_execute(|| { @@ -4856,7 +4930,7 @@ fn force_apply_min_commission_works() { } #[test] -fn ledger_slash_works() { +fn proportional_ledger_slash_works() { let c = |era, value| UnlockChunk:: { era, value }; // Given let mut ledger = StakingLedger:: { From 3162c4fe8c53133e16ab16e9a6b934555de94e2f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 15 May 2022 21:37:23 -0400 Subject: [PATCH 218/484] Bump clap from 3.1.6 to 3.1.18 (#11376) * Bump clap from 3.1.6 to 3.1.17 Bumps [clap](https://github.com/clap-rs/clap) from 3.1.6 to 3.1.17. - [Release notes](https://github.com/clap-rs/clap/releases) - [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md) - [Commits](https://github.com/clap-rs/clap/compare/v3.1.6...v3.1.17) --- updated-dependencies: - dependency-name: clap dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Update utils/frame/try-runtime/cli/Cargo.toml * Update frame/election-provider-support/solution-type/fuzzer/Cargo.toml * use 3.1.18 Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Shawn Tabrizi --- Cargo.lock | 54 ++++++++++--------- bin/node-template/node/Cargo.toml | 2 +- bin/node/bench/Cargo.toml | 2 +- bin/node/cli/Cargo.toml | 4 +- bin/utils/chain-spec-builder/Cargo.toml | 2 +- bin/utils/subkey/Cargo.toml | 2 +- client/cli/Cargo.toml | 2 +- .../solution-type/fuzzer/Cargo.toml | 2 +- primitives/npos-elections/fuzzer/Cargo.toml | 2 +- utils/frame/benchmarking-cli/Cargo.toml | 2 +- utils/frame/frame-utilities-cli/Cargo.toml | 2 +- .../generate-bags/node-runtime/Cargo.toml | 2 +- utils/frame/try-runtime/cli/Cargo.toml | 2 +- 13 files changed, 43 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1995ebb46023e..4dad89fbd97f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -916,7 +916,7 @@ name = "chain-spec-builder" version = "2.0.0" dependencies = [ "ansi_term", - "clap 3.1.6", + "clap 3.1.18", "node-cli", "rand 0.8.4", "sc-chain-spec", @@ -993,16 +993,16 @@ dependencies = [ [[package]] name = "clap" -version = "3.1.6" +version = "3.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8c93436c21e4698bacadf42917db28b23017027a4deccb35dbe47a7e7840123" +checksum = "d2dbdf4bdacb33466e854ce889eee8dfd5729abf7ccd7664d0a2d60cd384440b" dependencies = [ "atty", "bitflags", "clap_derive", + "clap_lex", "indexmap", "lazy_static", - "os_str_bytes", "strsim", "termcolor", "textwrap 0.15.0", @@ -1014,14 +1014,14 @@ version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a394f7ec0715b42a4e52b294984c27c9a61f77c8d82f7774c5198350be143f19" dependencies = [ - "clap 3.1.6", + "clap 3.1.18", ] [[package]] name = "clap_derive" -version = "3.1.4" +version = "3.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da95d038ede1a964ce99f49cbe27a7fb538d1da595e4b4f70b8c8f338d17bf16" +checksum = "25320346e922cffe59c0bbc5410c8d8784509efb321488971081313cb1e1a33c" dependencies = [ "heck 0.4.0", "proc-macro-error", @@ -1030,6 +1030,15 @@ dependencies = [ "syn", ] +[[package]] +name = "clap_lex" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213" +dependencies = [ + "os_str_bytes", +] + [[package]] name = "cloudabi" version = "0.0.3" @@ -2142,7 +2151,7 @@ version = "4.0.0-dev" dependencies = [ "Inflector", "chrono", - "clap 3.1.6", + "clap 3.1.18", "frame-benchmarking", "frame-support", "frame-system", @@ -2223,7 +2232,7 @@ dependencies = [ name = "frame-election-solution-type-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 3.1.6", + "clap 3.1.18", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-support", @@ -3410,9 +3419,9 @@ checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" [[package]] name = "libgit2-sys" -version = "0.13.3+1.4.2" +version = "0.13.2+1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c24d36c3ac9b9996a2418d6bf428cc0bc5d1a814a84303fc60986088c5ed60de" +checksum = "3a42de9a51a5c12e00fc0e4ca6bc2ea43582fc6418488e8f615e905d886f258b" dependencies = [ "cc", "libc", @@ -4533,7 +4542,7 @@ dependencies = [ name = "node-bench" version = "0.9.0-dev" dependencies = [ - "clap 3.1.6", + "clap 3.1.18", "derive_more", "fs_extra", "futures", @@ -4572,7 +4581,7 @@ version = "3.0.0-dev" dependencies = [ "assert_cmd", "async-std", - "clap 3.1.6", + "clap 3.1.18", "clap_complete", "criterion", "frame-benchmarking-cli", @@ -4687,7 +4696,7 @@ dependencies = [ name = "node-inspect" version = "0.9.0-dev" dependencies = [ - "clap 3.1.6", + "clap 3.1.18", "parity-scale-codec", "sc-cli", "sc-client-api", @@ -4837,7 +4846,7 @@ dependencies = [ name = "node-runtime-generate-bags" version = "3.0.0" dependencies = [ - "clap 3.1.6", + "clap 3.1.18", "generate-bags", "node-runtime", ] @@ -4846,7 +4855,7 @@ dependencies = [ name = "node-template" version = "4.0.0-dev" dependencies = [ - "clap 3.1.6", + "clap 3.1.18", "frame-benchmarking", "frame-benchmarking-cli", "frame-system", @@ -5128,9 +5137,6 @@ name = "os_str_bytes" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" -dependencies = [ - "memchr", -] [[package]] name = "output_vt100" @@ -7944,7 +7950,7 @@ name = "sc-cli" version = "0.10.0-dev" dependencies = [ "chrono", - "clap 3.1.6", + "clap 3.1.18", "fdlimit", "futures", "hex", @@ -9998,7 +10004,7 @@ dependencies = [ name = "sp-npos-elections-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 3.1.6", + "clap 3.1.18", "honggfuzz", "parity-scale-codec", "rand 0.8.4", @@ -10430,7 +10436,7 @@ dependencies = [ name = "subkey" version = "2.0.1" dependencies = [ - "clap 3.1.6", + "clap 3.1.18", "sc-cli", ] @@ -10458,7 +10464,7 @@ dependencies = [ name = "substrate-frame-cli" version = "4.0.0-dev" dependencies = [ - "clap 3.1.6", + "clap 3.1.18", "frame-support", "frame-system", "sc-cli", @@ -11237,7 +11243,7 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" name = "try-runtime-cli" version = "0.10.0-dev" dependencies = [ - "clap 3.1.6", + "clap 3.1.18", "jsonrpsee", "log", "parity-scale-codec", diff --git a/bin/node-template/node/Cargo.toml b/bin/node-template/node/Cargo.toml index 7ae343fd56dbe..8a6b041bbd518 100644 --- a/bin/node-template/node/Cargo.toml +++ b/bin/node-template/node/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] name = "node-template" [dependencies] -clap = { version = "3.1.6", features = ["derive"] } +clap = { version = "3.1.18", features = ["derive"] } sc-cli = { version = "0.10.0-dev", path = "../../../client/cli", features = ["wasmtime"] } sp-core = { version = "6.0.0", path = "../../../primitives/core" } diff --git a/bin/node/bench/Cargo.toml b/bin/node/bench/Cargo.toml index 2675c12d0d3b4..d9fb495ffc65c 100644 --- a/bin/node/bench/Cargo.toml +++ b/bin/node/bench/Cargo.toml @@ -9,7 +9,7 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -clap = { version = "3.1.6", features = ["derive"] } +clap = { version = "3.1.18", features = ["derive"] } log = "0.4.16" node-primitives = { version = "2.0.0", path = "../primitives" } node-testing = { version = "3.0.0-dev", path = "../testing" } diff --git a/bin/node/cli/Cargo.toml b/bin/node/cli/Cargo.toml index b574fa948b857..50852cfb393ae 100644 --- a/bin/node/cli/Cargo.toml +++ b/bin/node/cli/Cargo.toml @@ -34,7 +34,7 @@ crate-type = ["cdylib", "rlib"] [dependencies] # third-party dependencies -clap = { version = "3.1.6", features = ["derive"], optional = true } +clap = { version = "3.1.18", features = ["derive"], optional = true } codec = { package = "parity-scale-codec", version = "3.0.0" } serde = { version = "1.0.136", features = ["derive"] } jsonrpsee = { version = "0.13.0", features = ["server"] } @@ -135,7 +135,7 @@ remote-externalities = { path = "../../../utils/frame/remote-externalities" } pallet-timestamp = { version = "4.0.0-dev", path = "../../../frame/timestamp" } [build-dependencies] -clap = { version = "3.1.6", optional = true } +clap = { version = "3.1.18", optional = true } clap_complete = { version = "3.0", optional = true } node-inspect = { version = "0.9.0-dev", optional = true, path = "../inspect" } frame-benchmarking-cli = { version = "4.0.0-dev", optional = true, path = "../../../utils/frame/benchmarking-cli" } diff --git a/bin/utils/chain-spec-builder/Cargo.toml b/bin/utils/chain-spec-builder/Cargo.toml index e179a3308dd62..e3deb54ea8ac9 100644 --- a/bin/utils/chain-spec-builder/Cargo.toml +++ b/bin/utils/chain-spec-builder/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] ansi_term = "0.12.1" -clap = { version = "3.1.6", features = ["derive"] } +clap = { version = "3.1.18", features = ["derive"] } rand = "0.8" node-cli = { version = "3.0.0-dev", path = "../../node/cli" } sc-chain-spec = { version = "4.0.0-dev", path = "../../../client/chain-spec" } diff --git a/bin/utils/subkey/Cargo.toml b/bin/utils/subkey/Cargo.toml index c8132fe7b5b4b..26bc949b74ae7 100644 --- a/bin/utils/subkey/Cargo.toml +++ b/bin/utils/subkey/Cargo.toml @@ -17,5 +17,5 @@ path = "src/main.rs" name = "subkey" [dependencies] -clap = { version = "3.1.6", features = ["derive"] } +clap = { version = "3.1.18", features = ["derive"] } sc-cli = { version = "0.10.0-dev", path = "../../../client/cli" } diff --git a/client/cli/Cargo.toml b/client/cli/Cargo.toml index 1a504f354ee87..ecc58e0b23450 100644 --- a/client/cli/Cargo.toml +++ b/client/cli/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] chrono = "0.4.10" -clap = { version = "3.1.6", features = ["derive"] } +clap = { version = "3.1.18", features = ["derive"] } fdlimit = "0.2.1" futures = "0.3.21" hex = "0.4.2" diff --git a/frame/election-provider-support/solution-type/fuzzer/Cargo.toml b/frame/election-provider-support/solution-type/fuzzer/Cargo.toml index e2bae3f72a4f7..6520f6e632230 100644 --- a/frame/election-provider-support/solution-type/fuzzer/Cargo.toml +++ b/frame/election-provider-support/solution-type/fuzzer/Cargo.toml @@ -13,7 +13,7 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -clap = { version = "3.0", features = ["derive"] } +clap = { version = "3.1.18", features = ["derive"] } honggfuzz = "0.5" rand = { version = "0.8", features = ["std", "small_rng"] } diff --git a/primitives/npos-elections/fuzzer/Cargo.toml b/primitives/npos-elections/fuzzer/Cargo.toml index f8cc21cb61077..a0c420329b3d0 100644 --- a/primitives/npos-elections/fuzzer/Cargo.toml +++ b/primitives/npos-elections/fuzzer/Cargo.toml @@ -14,7 +14,7 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -clap = { version = "3.1.6", features = ["derive"] } +clap = { version = "3.1.18", features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } honggfuzz = "0.5" rand = { version = "0.8", features = ["std", "small_rng"] } diff --git a/utils/frame/benchmarking-cli/Cargo.toml b/utils/frame/benchmarking-cli/Cargo.toml index 871f4e925648b..6266f4eb8bb2a 100644 --- a/utils/frame/benchmarking-cli/Cargo.toml +++ b/utils/frame/benchmarking-cli/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] chrono = "0.4" -clap = { version = "3.1.6", features = ["derive"] } +clap = { version = "3.1.18", features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.0.0" } handlebars = "4.2.2" hash-db = "0.15.2" diff --git a/utils/frame/frame-utilities-cli/Cargo.toml b/utils/frame/frame-utilities-cli/Cargo.toml index cd9fc4a8cf1e3..f01e2f1a5d51f 100644 --- a/utils/frame/frame-utilities-cli/Cargo.toml +++ b/utils/frame/frame-utilities-cli/Cargo.toml @@ -11,7 +11,7 @@ documentation = "https://docs.rs/substrate-frame-cli" readme = "README.md" [dependencies] -clap = { version = "3.1.6", features = ["derive"] } +clap = { version = "3.1.18", features = ["derive"] } frame-support = { version = "4.0.0-dev", path = "../../../frame/support" } frame-system = { version = "4.0.0-dev", path = "../../../frame/system" } sc-cli = { version = "0.10.0-dev", path = "../../../client/cli" } diff --git a/utils/frame/generate-bags/node-runtime/Cargo.toml b/utils/frame/generate-bags/node-runtime/Cargo.toml index d5c8cab7ba0d8..c5f3b1998fa97 100644 --- a/utils/frame/generate-bags/node-runtime/Cargo.toml +++ b/utils/frame/generate-bags/node-runtime/Cargo.toml @@ -14,4 +14,4 @@ node-runtime = { version = "3.0.0-dev", path = "../../../../bin/node/runtime" } generate-bags = { version = "4.0.0-dev", path = "../" } # third-party -clap = { version = "3.1.6", features = ["derive"] } +clap = { version = "3.1.18", features = ["derive"] } diff --git a/utils/frame/try-runtime/cli/Cargo.toml b/utils/frame/try-runtime/cli/Cargo.toml index 4bbcaf1c216a5..48e9c1d6b36a1 100644 --- a/utils/frame/try-runtime/cli/Cargo.toml +++ b/utils/frame/try-runtime/cli/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -clap = { version = "3.1.6", features = ["derive"] } +clap = { version = "3.1.18", features = ["derive"] } log = "0.4.16" parity-scale-codec = "3.0.0" serde = "1.0.136" From 8adf8edb7b17bff00a4b713ef0676324642d7fba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 15 May 2022 22:21:04 -0400 Subject: [PATCH 219/484] Bump log from 0.4.16 to 0.4.17 (#11369) * Bump log from 0.4.16 to 0.4.17 Bumps [log](https://github.com/rust-lang/log) from 0.4.16 to 0.4.17. - [Release notes](https://github.com/rust-lang/log/releases) - [Changelog](https://github.com/rust-lang/log/blob/master/CHANGELOG.md) - [Commits](https://github.com/rust-lang/log/commits/0.4.17) --- updated-dependencies: - dependency-name: log dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Update Cargo.lock Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Shawn Tabrizi --- Cargo.lock | 8 ++++---- bin/node/bench/Cargo.toml | 2 +- bin/node/cli/Cargo.toml | 2 +- bin/node/runtime/Cargo.toml | 2 +- bin/node/testing/Cargo.toml | 2 +- client/allocator/Cargo.toml | 2 +- client/api/Cargo.toml | 2 +- client/authority-discovery/Cargo.toml | 2 +- client/basic-authorship/Cargo.toml | 2 +- client/cli/Cargo.toml | 2 +- client/consensus/aura/Cargo.toml | 2 +- client/consensus/babe/Cargo.toml | 2 +- client/consensus/common/Cargo.toml | 2 +- client/consensus/manual-seal/Cargo.toml | 2 +- client/consensus/pow/Cargo.toml | 2 +- client/consensus/slots/Cargo.toml | 2 +- client/db/Cargo.toml | 2 +- client/executor/wasmi/Cargo.toml | 2 +- client/executor/wasmtime/Cargo.toml | 2 +- client/finality-grandpa/Cargo.toml | 2 +- client/informant/Cargo.toml | 2 +- client/network-gossip/Cargo.toml | 2 +- client/network/Cargo.toml | 2 +- client/network/sync/Cargo.toml | 2 +- client/network/test/Cargo.toml | 2 +- client/peerset/Cargo.toml | 2 +- client/proposer-metrics/Cargo.toml | 2 +- client/rpc-api/Cargo.toml | 2 +- client/rpc-servers/Cargo.toml | 2 +- client/rpc/Cargo.toml | 2 +- client/service/Cargo.toml | 2 +- client/service/test/Cargo.toml | 2 +- client/state-db/Cargo.toml | 2 +- client/sysinfo/Cargo.toml | 2 +- client/telemetry/Cargo.toml | 2 +- client/tracing/Cargo.toml | 2 +- client/transaction-pool/Cargo.toml | 2 +- client/transaction-pool/api/Cargo.toml | 2 +- frame/babe/Cargo.toml | 2 +- frame/bags-list/Cargo.toml | 2 +- frame/bags-list/remote-tests/Cargo.toml | 2 +- frame/balances/Cargo.toml | 2 +- frame/beefy-mmr/Cargo.toml | 2 +- frame/benchmarking/Cargo.toml | 2 +- frame/bounties/Cargo.toml | 2 +- frame/child-bounties/Cargo.toml | 2 +- frame/collective/Cargo.toml | 2 +- frame/election-provider-multi-phase/Cargo.toml | 2 +- frame/examples/basic/Cargo.toml | 2 +- frame/examples/offchain-worker/Cargo.toml | 2 +- frame/grandpa/Cargo.toml | 2 +- frame/im-online/Cargo.toml | 2 +- frame/membership/Cargo.toml | 2 +- frame/node-authorization/Cargo.toml | 2 +- frame/offences/Cargo.toml | 2 +- frame/scheduler/Cargo.toml | 2 +- frame/session/Cargo.toml | 2 +- frame/staking/Cargo.toml | 2 +- frame/staking/reward-fn/Cargo.toml | 2 +- frame/state-trie-migration/Cargo.toml | 2 +- frame/support/Cargo.toml | 2 +- frame/system/Cargo.toml | 2 +- frame/timestamp/Cargo.toml | 2 +- frame/tips/Cargo.toml | 2 +- frame/uniques/Cargo.toml | 2 +- frame/vesting/Cargo.toml | 2 +- primitives/api/Cargo.toml | 2 +- primitives/api/test/Cargo.toml | 2 +- primitives/blockchain/Cargo.toml | 2 +- primitives/consensus/common/Cargo.toml | 2 +- primitives/core/Cargo.toml | 2 +- primitives/finality-grandpa/Cargo.toml | 2 +- primitives/io/Cargo.toml | 2 +- primitives/merkle-mountain-range/Cargo.toml | 2 +- primitives/runtime/Cargo.toml | 2 +- primitives/state-machine/Cargo.toml | 2 +- primitives/tasks/Cargo.toml | 2 +- primitives/timestamp/Cargo.toml | 2 +- primitives/transaction-storage-proof/Cargo.toml | 2 +- primitives/wasm-interface/Cargo.toml | 2 +- test-utils/runtime/Cargo.toml | 2 +- utils/frame/benchmarking-cli/Cargo.toml | 2 +- utils/frame/remote-externalities/Cargo.toml | 2 +- utils/frame/rpc/state-trie-migration-rpc/Cargo.toml | 2 +- utils/frame/rpc/system/Cargo.toml | 2 +- utils/frame/try-runtime/cli/Cargo.toml | 2 +- utils/prometheus/Cargo.toml | 2 +- 87 files changed, 90 insertions(+), 90 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4dad89fbd97f7..abd9cd7b46e02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4100,9 +4100,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if 1.0.0", "value-bag", @@ -11414,9 +11414,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "value-bag" -version = "1.0.0-alpha.8" +version = "1.0.0-alpha.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79923f7731dc61ebfba3633098bf3ac533bbd35ccd8c57e7088d9a5eebe0263f" +checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55" dependencies = [ "ctor", "version_check", diff --git a/bin/node/bench/Cargo.toml b/bin/node/bench/Cargo.toml index d9fb495ffc65c..5013383af1cea 100644 --- a/bin/node/bench/Cargo.toml +++ b/bin/node/bench/Cargo.toml @@ -10,7 +10,7 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0" [dependencies] clap = { version = "3.1.18", features = ["derive"] } -log = "0.4.16" +log = "0.4.17" node-primitives = { version = "2.0.0", path = "../primitives" } node-testing = { version = "3.0.0-dev", path = "../testing" } node-runtime = { version = "3.0.0-dev", path = "../runtime" } diff --git a/bin/node/cli/Cargo.toml b/bin/node/cli/Cargo.toml index 50852cfb393ae..ab6644a379bb5 100644 --- a/bin/node/cli/Cargo.toml +++ b/bin/node/cli/Cargo.toml @@ -40,7 +40,7 @@ serde = { version = "1.0.136", features = ["derive"] } jsonrpsee = { version = "0.13.0", features = ["server"] } futures = "0.3.21" hex-literal = "0.3.4" -log = "0.4.16" +log = "0.4.17" rand = "0.8" # primitives diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 09f7534e7f521..46d9ff03ed36e 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -21,7 +21,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } static_assertions = "1.1.0" hex-literal = { version = "0.3.4", optional = true } -log = { version = "0.4.16", default-features = false } +log = { version = "0.4.17", default-features = false } # primitives sp-authority-discovery = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/authority-discovery" } diff --git a/bin/node/testing/Cargo.toml b/bin/node/testing/Cargo.toml index c4f97a3e47447..f7a78d10910b3 100644 --- a/bin/node/testing/Cargo.toml +++ b/bin/node/testing/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0" } fs_extra = "1" futures = "0.3.21" -log = "0.4.16" +log = "0.4.17" tempfile = "3.1.0" frame-system = { version = "4.0.0-dev", path = "../../../frame/system" } node-executor = { version = "3.0.0-dev", path = "../executor" } diff --git a/client/allocator/Cargo.toml b/client/allocator/Cargo.toml index ec5c19786b875..aded67ad80cde 100644 --- a/client/allocator/Cargo.toml +++ b/client/allocator/Cargo.toml @@ -14,7 +14,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = "0.4.16" +log = "0.4.17" thiserror = "1.0.30" sp-core = { version = "6.0.0", path = "../../primitives/core" } sp-wasm-interface = { version = "6.0.0", path = "../../primitives/wasm-interface" } diff --git a/client/api/Cargo.toml b/client/api/Cargo.toml index f69549b60c53e..c8e9dc7482823 100644 --- a/client/api/Cargo.toml +++ b/client/api/Cargo.toml @@ -20,7 +20,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = fnv = "1.0.6" futures = "0.3.21" hash-db = { version = "0.15.2", default-features = false } -log = "0.4.16" +log = "0.4.17" parking_lot = "0.12.0" prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } sc-executor = { version = "0.10.0-dev", path = "../executor" } diff --git a/client/authority-discovery/Cargo.toml b/client/authority-discovery/Cargo.toml index 996e262ba2b44..2545447001511 100644 --- a/client/authority-discovery/Cargo.toml +++ b/client/authority-discovery/Cargo.toml @@ -23,7 +23,7 @@ futures = "0.3.21" futures-timer = "3.0.1" ip_network = "0.4.1" libp2p = { version = "0.44.0", default-features = false, features = ["kad"] } -log = "0.4.16" +log = "0.4.17" prost = "0.9" rand = "0.7.2" thiserror = "1.0" diff --git a/client/basic-authorship/Cargo.toml b/client/basic-authorship/Cargo.toml index 6ef679d7995e6..a2ccba486ae29 100644 --- a/client/basic-authorship/Cargo.toml +++ b/client/basic-authorship/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.21" futures-timer = "3.0.1" -log = "0.4.16" +log = "0.4.17" prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } sc-client-api = { version = "4.0.0-dev", path = "../api" } diff --git a/client/cli/Cargo.toml b/client/cli/Cargo.toml index ecc58e0b23450..456489e5f6639 100644 --- a/client/cli/Cargo.toml +++ b/client/cli/Cargo.toml @@ -19,7 +19,7 @@ fdlimit = "0.2.1" futures = "0.3.21" hex = "0.4.2" libp2p = "0.44.0" -log = "0.4.16" +log = "0.4.17" names = { version = "0.13.0", default-features = false } parity-scale-codec = "3.0.0" rand = "0.7.3" diff --git a/client/consensus/aura/Cargo.toml b/client/consensus/aura/Cargo.toml index 448cf2549f2d9..69499fa346e31 100644 --- a/client/consensus/aura/Cargo.toml +++ b/client/consensus/aura/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] async-trait = "0.1.50" codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.21" -log = "0.4.16" +log = "0.4.17" thiserror = "1.0" prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../../utils/prometheus" } sc-block-builder = { version = "0.10.0-dev", path = "../../block-builder" } diff --git a/client/consensus/babe/Cargo.toml b/client/consensus/babe/Cargo.toml index f421fdd36e37b..765fc367f424f 100644 --- a/client/consensus/babe/Cargo.toml +++ b/client/consensus/babe/Cargo.toml @@ -19,7 +19,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", features = [ "derive", ] } futures = "0.3.21" -log = "0.4.16" +log = "0.4.17" merlin = "2.0" num-bigint = "0.2.3" num-rational = "0.2.2" diff --git a/client/consensus/common/Cargo.toml b/client/consensus/common/Cargo.toml index 4f8d7befb0d63..1508dfa82a363 100644 --- a/client/consensus/common/Cargo.toml +++ b/client/consensus/common/Cargo.toml @@ -17,7 +17,7 @@ async-trait = "0.1.42" futures = { version = "0.3.21", features = ["thread-pool"] } futures-timer = "3.0.1" libp2p = { version = "0.44.0", default-features = false } -log = "0.4.16" +log = "0.4.17" parking_lot = "0.12.0" serde = { version = "1.0", features = ["derive"] } thiserror = "1.0.30" diff --git a/client/consensus/manual-seal/Cargo.toml b/client/consensus/manual-seal/Cargo.toml index 9806fbdcaac3c..dbc7c29c7f300 100644 --- a/client/consensus/manual-seal/Cargo.toml +++ b/client/consensus/manual-seal/Cargo.toml @@ -18,7 +18,7 @@ assert_matches = "1.3.0" async-trait = "0.1.50" codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.21" -log = "0.4.16" +log = "0.4.17" serde = { version = "1.0", features = ["derive"] } thiserror = "1.0" prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../../utils/prometheus" } diff --git a/client/consensus/pow/Cargo.toml b/client/consensus/pow/Cargo.toml index cc65687f80e13..7e9b43fac8a57 100644 --- a/client/consensus/pow/Cargo.toml +++ b/client/consensus/pow/Cargo.toml @@ -17,7 +17,7 @@ async-trait = "0.1.50" codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } futures = "0.3.21" futures-timer = "3.0.1" -log = "0.4.16" +log = "0.4.17" parking_lot = "0.12.0" thiserror = "1.0" prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../../utils/prometheus" } diff --git a/client/consensus/slots/Cargo.toml b/client/consensus/slots/Cargo.toml index 27528b68e75ea..41a6c1ad5e641 100644 --- a/client/consensus/slots/Cargo.toml +++ b/client/consensus/slots/Cargo.toml @@ -18,7 +18,7 @@ async-trait = "0.1.50" codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.21" futures-timer = "3.0.1" -log = "0.4.16" +log = "0.4.17" thiserror = "1.0.30" sc-client-api = { version = "4.0.0-dev", path = "../../api" } sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } diff --git a/client/db/Cargo.toml b/client/db/Cargo.toml index e548887e56fe2..babbb0b91c8fb 100644 --- a/client/db/Cargo.toml +++ b/client/db/Cargo.toml @@ -21,7 +21,7 @@ kvdb = "0.11.0" kvdb-memorydb = "0.11.0" kvdb-rocksdb = { version = "0.15.2", optional = true } linked-hash-map = "0.5.4" -log = "0.4.16" +log = "0.4.17" parity-db = { version = "0.3.13", optional = true } parking_lot = "0.12.0" sc-client-api = { version = "4.0.0-dev", path = "../api" } diff --git a/client/executor/wasmi/Cargo.toml b/client/executor/wasmi/Cargo.toml index 153deab49a305..46bacf54a42c6 100644 --- a/client/executor/wasmi/Cargo.toml +++ b/client/executor/wasmi/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } -log = "0.4.16" +log = "0.4.17" wasmi = "0.9.1" sc-allocator = { version = "4.1.0-dev", path = "../../allocator" } sc-executor-common = { version = "0.10.0-dev", path = "../common" } diff --git a/client/executor/wasmtime/Cargo.toml b/client/executor/wasmtime/Cargo.toml index 845fdfb72901c..c514a93be1d88 100644 --- a/client/executor/wasmtime/Cargo.toml +++ b/client/executor/wasmtime/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] cfg-if = "1.0" codec = { package = "parity-scale-codec", version = "3.0.0" } libc = "0.2.121" -log = "0.4.16" +log = "0.4.17" parity-wasm = "0.42.0" wasmtime = { version = "0.35.3", default-features = false, features = [ "cache", diff --git a/client/finality-grandpa/Cargo.toml b/client/finality-grandpa/Cargo.toml index a0e53da23fc54..2f7fcd4d052b1 100644 --- a/client/finality-grandpa/Cargo.toml +++ b/client/finality-grandpa/Cargo.toml @@ -21,7 +21,7 @@ finality-grandpa = { version = "0.15.0", features = ["derive-codec"] } futures = "0.3.21" futures-timer = "3.0.1" hex = "0.4.2" -log = "0.4.16" +log = "0.4.17" parity-scale-codec = { version = "3.0.0", features = ["derive"] } parking_lot = "0.12.0" rand = "0.8.4" diff --git a/client/informant/Cargo.toml b/client/informant/Cargo.toml index 70af073e4bcfe..528365d62c18b 100644 --- a/client/informant/Cargo.toml +++ b/client/informant/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] ansi_term = "0.12.1" futures = "0.3.21" futures-timer = "3.0.1" -log = "0.4.16" +log = "0.4.17" parity-util-mem = { version = "0.11.0", default-features = false, features = ["primitive-types"] } sc-client-api = { version = "4.0.0-dev", path = "../api" } sc-network = { version = "0.10.0-dev", path = "../network" } diff --git a/client/network-gossip/Cargo.toml b/client/network-gossip/Cargo.toml index 4529957c7c38f..7ba45f6ee9b87 100644 --- a/client/network-gossip/Cargo.toml +++ b/client/network-gossip/Cargo.toml @@ -18,7 +18,7 @@ ahash = "0.7.6" futures = "0.3.21" futures-timer = "3.0.1" libp2p = { version = "0.44.0", default-features = false } -log = "0.4.16" +log = "0.4.17" lru = "0.7.5" tracing = "0.1.29" prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } diff --git a/client/network/Cargo.toml b/client/network/Cargo.toml index 7e12d9862f8ef..4d6af584d8610 100644 --- a/client/network/Cargo.toml +++ b/client/network/Cargo.toml @@ -32,7 +32,7 @@ ip_network = "0.4.1" libp2p = "0.44.0" linked_hash_set = "0.1.3" linked-hash-map = "0.5.4" -log = "0.4.16" +log = "0.4.17" lru = "0.7.5" parking_lot = "0.12.0" pin-project = "1.0.10" diff --git a/client/network/sync/Cargo.toml b/client/network/sync/Cargo.toml index 9cf8afa58649c..e570310ceb955 100644 --- a/client/network/sync/Cargo.toml +++ b/client/network/sync/Cargo.toml @@ -24,7 +24,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", features = [ either = "1.5.3" futures = "0.3.21" libp2p = "0.44.0" -log = "0.4.16" +log = "0.4.17" lru = "0.7.5" prost = "0.9" smallvec = "1.8.0" diff --git a/client/network/test/Cargo.toml b/client/network/test/Cargo.toml index 025658afcad22..fa34feb22df1d 100644 --- a/client/network/test/Cargo.toml +++ b/client/network/test/Cargo.toml @@ -18,7 +18,7 @@ async-trait = "0.1.50" futures = "0.3.21" futures-timer = "3.0.1" libp2p = { version = "0.44.0", default-features = false } -log = "0.4.16" +log = "0.4.17" parking_lot = "0.12.0" rand = "0.7.2" sc-block-builder = { version = "0.10.0-dev", path = "../../block-builder" } diff --git a/client/peerset/Cargo.toml b/client/peerset/Cargo.toml index 7bf324b68cdc6..29e67c73c18da 100644 --- a/client/peerset/Cargo.toml +++ b/client/peerset/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] futures = "0.3.21" libp2p = { version = "0.44.0", default-features = false } -log = "0.4.16" +log = "0.4.17" serde_json = "1.0.79" wasm-timer = "0.2" sc-utils = { version = "4.0.0-dev", path = "../utils" } diff --git a/client/proposer-metrics/Cargo.toml b/client/proposer-metrics/Cargo.toml index 9df6ff4e2e76d..97a7c076bd9e7 100644 --- a/client/proposer-metrics/Cargo.toml +++ b/client/proposer-metrics/Cargo.toml @@ -13,5 +13,5 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = "0.4.16" +log = "0.4.17" prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } diff --git a/client/rpc-api/Cargo.toml b/client/rpc-api/Cargo.toml index a9d5101733342..0ad0b7cc5bfdc 100644 --- a/client/rpc-api/Cargo.toml +++ b/client/rpc-api/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.21" -log = "0.4.16" +log = "0.4.17" parking_lot = "0.12.0" scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", features = ["derive"] } diff --git a/client/rpc-servers/Cargo.toml b/client/rpc-servers/Cargo.toml index 7d6ca821adc3c..f5cd943b24e09 100644 --- a/client/rpc-servers/Cargo.toml +++ b/client/rpc-servers/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] futures = "0.3.21" jsonrpsee = { version = "0.13.0", features = ["server"] } -log = "0.4.16" +log = "0.4.17" serde_json = "1.0.79" tokio = { version = "1.17.0", features = ["parking_lot"] } prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } diff --git a/client/rpc/Cargo.toml b/client/rpc/Cargo.toml index 50b0dff20cfcd..f783ce8a94fb7 100644 --- a/client/rpc/Cargo.toml +++ b/client/rpc/Cargo.toml @@ -18,7 +18,7 @@ futures = "0.3.21" hash-db = { version = "0.15.2", default-features = false } jsonrpsee = { version = "0.13.0", features = ["server"] } lazy_static = { version = "1.4.0", optional = true } -log = "0.4.16" +log = "0.4.17" parking_lot = "0.12.0" serde_json = "1.0.79" sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } diff --git a/client/service/Cargo.toml b/client/service/Cargo.toml index d59e8e8d19c4c..22bac652c30c4 100644 --- a/client/service/Cargo.toml +++ b/client/service/Cargo.toml @@ -27,7 +27,7 @@ thiserror = "1.0.30" futures = "0.3.21" rand = "0.7.3" parking_lot = "0.12.0" -log = "0.4.16" +log = "0.4.17" futures-timer = "3.0.1" exit-future = "0.2.0" pin-project = "1.0.10" diff --git a/client/service/test/Cargo.toml b/client/service/test/Cargo.toml index 15105765c207c..d003db57eb7ac 100644 --- a/client/service/test/Cargo.toml +++ b/client/service/test/Cargo.toml @@ -16,7 +16,7 @@ fdlimit = "0.2.1" futures = "0.3.21" hex = "0.4" hex-literal = "0.3.4" -log = "0.4.16" +log = "0.4.17" parity-scale-codec = "3.0.0" parking_lot = "0.12.0" tempfile = "3.1.0" diff --git a/client/state-db/Cargo.toml b/client/state-db/Cargo.toml index 5a143ffc9f1b8..08856ce3f48a9 100644 --- a/client/state-db/Cargo.toml +++ b/client/state-db/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } -log = "0.4.16" +log = "0.4.17" parity-util-mem = { version = "0.11.0", default-features = false, features = ["primitive-types"] } parity-util-mem-derive = "0.1.0" parking_lot = "0.12.0" diff --git a/client/sysinfo/Cargo.toml b/client/sysinfo/Cargo.toml index abfa090d85547..0973631a3cc24 100644 --- a/client/sysinfo/Cargo.toml +++ b/client/sysinfo/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] futures = "0.3.19" libc = "0.2" -log = "0.4.16" +log = "0.4.17" rand = "0.7.3" rand_pcg = "0.2.1" regex = "1" diff --git a/client/telemetry/Cargo.toml b/client/telemetry/Cargo.toml index 3c5942ad688fa..e8abdfee44a2e 100644 --- a/client/telemetry/Cargo.toml +++ b/client/telemetry/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] chrono = "0.4.19" futures = "0.3.21" libp2p = { version = "0.44.0", default-features = false, features = ["dns-async-std", "tcp-async-io", "wasm-ext", "websocket"] } -log = "0.4.16" +log = "0.4.17" parking_lot = "0.12.0" pin-project = "1.0.10" rand = "0.7.2" diff --git a/client/tracing/Cargo.toml b/client/tracing/Cargo.toml index 058b718a783a4..3f0bbbe922d3f 100644 --- a/client/tracing/Cargo.toml +++ b/client/tracing/Cargo.toml @@ -18,7 +18,7 @@ atty = "0.2.13" chrono = "0.4.19" lazy_static = "1.4.0" libc = "0.2.121" -log = { version = "0.4.16" } +log = { version = "0.4.17" } once_cell = "1.8.0" parking_lot = "0.12.0" regex = "1.5.5" diff --git a/client/transaction-pool/Cargo.toml b/client/transaction-pool/Cargo.toml index 5d4996a424f86..cabde4b6307ae 100644 --- a/client/transaction-pool/Cargo.toml +++ b/client/transaction-pool/Cargo.toml @@ -17,7 +17,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.21" futures-timer = "3.0.2" linked-hash-map = "0.5.4" -log = "0.4.16" +log = "0.4.17" parity-util-mem = { version = "0.11.0", default-features = false, features = ["primitive-types"] } parking_lot = "0.12.0" retain_mut = "0.1.4" diff --git a/client/transaction-pool/api/Cargo.toml b/client/transaction-pool/api/Cargo.toml index 6ced3f6c7fc91..d34ffe512b023 100644 --- a/client/transaction-pool/api/Cargo.toml +++ b/client/transaction-pool/api/Cargo.toml @@ -10,7 +10,7 @@ description = "Transaction pool client facing API." [dependencies] futures = "0.3.21" -log = "0.4.16" +log = "0.4.17" serde = { version = "1.0.136", features = ["derive"] } thiserror = "1.0.30" sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } diff --git a/frame/babe/Cargo.toml b/frame/babe/Cargo.toml index dfdc9d2163136..23f8f1517cb63 100644 --- a/frame/babe/Cargo.toml +++ b/frame/babe/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -log = { version = "0.4.16", default-features = false } +log = { version = "0.4.17", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } diff --git a/frame/bags-list/Cargo.toml b/frame/bags-list/Cargo.toml index 8a47fd7d9e7d2..4efa1fbe287fa 100644 --- a/frame/bags-list/Cargo.toml +++ b/frame/bags-list/Cargo.toml @@ -27,7 +27,7 @@ frame-system = { version = "4.0.0-dev", default-features = false, path = "../sys frame-election-provider-support = { version = "4.0.0-dev", default-features = false, path = "../election-provider-support" } # third party -log = { version = "0.4.16", default-features = false } +log = { version = "0.4.17", default-features = false } # Optional imports for benchmarking frame-benchmarking = { version = "4.0.0-dev", path = "../benchmarking", optional = true, default-features = false } diff --git a/frame/bags-list/remote-tests/Cargo.toml b/frame/bags-list/remote-tests/Cargo.toml index 760115917c6d4..3e2de430f6424 100644 --- a/frame/bags-list/remote-tests/Cargo.toml +++ b/frame/bags-list/remote-tests/Cargo.toml @@ -31,4 +31,4 @@ sp-std = { path = "../../../primitives/std", version = "4.0.0" } remote-externalities = { path = "../../../utils/frame/remote-externalities", version = "0.10.0-dev" } # others -log = "0.4.16" +log = "0.4.17" diff --git a/frame/balances/Cargo.toml b/frame/balances/Cargo.toml index 6ec624130288b..b2840eb3e1787 100644 --- a/frame/balances/Cargo.toml +++ b/frame/balances/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } -log = { version = "0.4.16", default-features = false } +log = { version = "0.4.17", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } diff --git a/frame/beefy-mmr/Cargo.toml b/frame/beefy-mmr/Cargo.toml index 6affcd60ccb34..85986c2a41529 100644 --- a/frame/beefy-mmr/Cargo.toml +++ b/frame/beefy-mmr/Cargo.toml @@ -11,7 +11,7 @@ homepage = "https://substrate.io" [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } hex = { version = "0.4", optional = true } -log = { version = "0.4.16", default-features = false } +log = { version = "0.4.17", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", optional = true } beefy-merkle-tree = { version = "4.0.0-dev", default-features = false, path = "./primitives" } diff --git a/frame/benchmarking/Cargo.toml b/frame/benchmarking/Cargo.toml index c070e64d37b45..6dddcf7e247ae 100644 --- a/frame/benchmarking/Cargo.toml +++ b/frame/benchmarking/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } linregress = { version = "0.4.4", optional = true } -log = { version = "0.4.16", default-features = false } +log = { version = "0.4.17", default-features = false } paste = "1.0" scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", optional = true } diff --git a/frame/bounties/Cargo.toml b/frame/bounties/Cargo.toml index c090052e91277..d37c06a96e88e 100644 --- a/frame/bounties/Cargo.toml +++ b/frame/bounties/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } -log = { version = "0.4.16", default-features = false } +log = { version = "0.4.17", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } diff --git a/frame/child-bounties/Cargo.toml b/frame/child-bounties/Cargo.toml index eac338dc3c6aa..722ee0af01f95 100644 --- a/frame/child-bounties/Cargo.toml +++ b/frame/child-bounties/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } -log = { version = "0.4.16", default-features = false } +log = { version = "0.4.17", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } diff --git a/frame/collective/Cargo.toml b/frame/collective/Cargo.toml index d1ef4d253a352..b810c3f3bd11d 100644 --- a/frame/collective/Cargo.toml +++ b/frame/collective/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -log = { version = "0.4.16", default-features = false } +log = { version = "0.4.17", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } diff --git a/frame/election-provider-multi-phase/Cargo.toml b/frame/election-provider-multi-phase/Cargo.toml index 50afd274c3f7d..6f3e500577af6 100644 --- a/frame/election-provider-multi-phase/Cargo.toml +++ b/frame/election-provider-multi-phase/Cargo.toml @@ -20,7 +20,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = scale-info = { version = "2.0.1", default-features = false, features = [ "derive", ] } -log = { version = "0.4.16", default-features = false } +log = { version = "0.4.17", default-features = false } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } diff --git a/frame/examples/basic/Cargo.toml b/frame/examples/basic/Cargo.toml index a528cf89bbaf4..d6bea8b3c290f 100644 --- a/frame/examples/basic/Cargo.toml +++ b/frame/examples/basic/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } -log = { version = "0.4.16", default-features = false } +log = { version = "0.4.17", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } diff --git a/frame/examples/offchain-worker/Cargo.toml b/frame/examples/offchain-worker/Cargo.toml index 061f84bd40276..bcc845823e137 100644 --- a/frame/examples/offchain-worker/Cargo.toml +++ b/frame/examples/offchain-worker/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } lite-json = { version = "0.1", default-features = false } -log = { version = "0.4.16", default-features = false } +log = { version = "0.4.17", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } diff --git a/frame/grandpa/Cargo.toml b/frame/grandpa/Cargo.toml index ea1ec173d8f44..fb2ffe42f526b 100644 --- a/frame/grandpa/Cargo.toml +++ b/frame/grandpa/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -log = { version = "0.4.16", default-features = false } +log = { version = "0.4.17", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } diff --git a/frame/im-online/Cargo.toml b/frame/im-online/Cargo.toml index 1f04c8b1bb3d8..0279e29169006 100644 --- a/frame/im-online/Cargo.toml +++ b/frame/im-online/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -log = { version = "0.4.16", default-features = false } +log = { version = "0.4.17", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } diff --git a/frame/membership/Cargo.toml b/frame/membership/Cargo.toml index 1545b191c8719..356fc12b09507 100644 --- a/frame/membership/Cargo.toml +++ b/frame/membership/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } -log = { version = "0.4.16", default-features = false } +log = { version = "0.4.17", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } diff --git a/frame/node-authorization/Cargo.toml b/frame/node-authorization/Cargo.toml index 317b5f392ebfa..a22af2d87578e 100644 --- a/frame/node-authorization/Cargo.toml +++ b/frame/node-authorization/Cargo.toml @@ -13,7 +13,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -log = { version = "0.4.16", default-features = false } +log = { version = "0.4.17", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } diff --git a/frame/offences/Cargo.toml b/frame/offences/Cargo.toml index f4aa01643315b..91553ea414f7e 100644 --- a/frame/offences/Cargo.toml +++ b/frame/offences/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -log = { version = "0.4.16", default-features = false } +log = { version = "0.4.17", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", optional = true } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } diff --git a/frame/scheduler/Cargo.toml b/frame/scheduler/Cargo.toml index 90c2bf10d19da..0908fc110802a 100644 --- a/frame/scheduler/Cargo.toml +++ b/frame/scheduler/Cargo.toml @@ -11,7 +11,7 @@ readme = "README.md" [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -log = { version = "0.4.16", default-features = false } +log = { version = "0.4.17", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } diff --git a/frame/session/Cargo.toml b/frame/session/Cargo.toml index 01822ace9cf28..56c72eeaf6d4b 100644 --- a/frame/session/Cargo.toml +++ b/frame/session/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } impl-trait-for-tuples = "0.2.2" -log = { version = "0.4.16", default-features = false } +log = { version = "0.4.17", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } diff --git a/frame/staking/Cargo.toml b/frame/staking/Cargo.toml index 88080df0b5912..cc8e19b1de892 100644 --- a/frame/staking/Cargo.toml +++ b/frame/staking/Cargo.toml @@ -30,7 +30,7 @@ pallet-session = { version = "4.0.0-dev", default-features = false, features = [ pallet-authorship = { version = "4.0.0-dev", default-features = false, path = "../authorship" } sp-application-crypto = { version = "6.0.0", default-features = false, path = "../../primitives/application-crypto" } frame-election-provider-support = { version = "4.0.0-dev", default-features = false, path = "../election-provider-support" } -log = { version = "0.4.16", default-features = false } +log = { version = "0.4.17", default-features = false } # Optional imports for benchmarking frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } diff --git a/frame/staking/reward-fn/Cargo.toml b/frame/staking/reward-fn/Cargo.toml index 4a41de806e13f..f16131f481494 100644 --- a/frame/staking/reward-fn/Cargo.toml +++ b/frame/staking/reward-fn/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [lib] [dependencies] -log = { version = "0.4.16", default-features = false } +log = { version = "0.4.17", default-features = false } sp-arithmetic = { version = "5.0.0", default-features = false, path = "../../../primitives/arithmetic" } [features] diff --git a/frame/state-trie-migration/Cargo.toml b/frame/state-trie-migration/Cargo.toml index 958ab50315427..be3e4392ac918 100644 --- a/frame/state-trie-migration/Cargo.toml +++ b/frame/state-trie-migration/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } -log = { version = "0.4.16", default-features = false } +log = { version = "0.4.17", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } serde = { version = "1.0.133", optional = true } thousands = { version = "0.2.0", optional = true } diff --git a/frame/support/Cargo.toml b/frame/support/Cargo.toml index 4132b9f98ebca..2d510fd4690ca 100644 --- a/frame/support/Cargo.toml +++ b/frame/support/Cargo.toml @@ -33,7 +33,7 @@ sp-state-machine = { version = "0.12.0", optional = true, path = "../../primitiv bitflags = "1.3" impl-trait-for-tuples = "0.2.2" smallvec = "1.8.0" -log = { version = "0.4.16", default-features = false } +log = { version = "0.4.17", default-features = false } sp-core-hashing-proc-macro = { version = "5.0.0", path = "../../primitives/core/hashing/proc-macro" } k256 = { version = "0.10.4", default-features = false, features = ["ecdsa"] } diff --git a/frame/system/Cargo.toml b/frame/system/Cargo.toml index f1d2491ad0919..38a2b934cef17 100644 --- a/frame/system/Cargo.toml +++ b/frame/system/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -log = { version = "0.4.16", default-features = false } +log = { version = "0.4.17", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", features = ["derive"], optional = true } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } diff --git a/frame/timestamp/Cargo.toml b/frame/timestamp/Cargo.toml index 971a0ace95d73..6ce1592cae3f9 100644 --- a/frame/timestamp/Cargo.toml +++ b/frame/timestamp/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } -log = { version = "0.4.16", default-features = false } +log = { version = "0.4.17", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } diff --git a/frame/tips/Cargo.toml b/frame/tips/Cargo.toml index 364135daa97f6..23f61dc4f96cd 100644 --- a/frame/tips/Cargo.toml +++ b/frame/tips/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -log = { version = "0.4.16", default-features = false } +log = { version = "0.4.17", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", features = ["derive"], optional = true } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } diff --git a/frame/uniques/Cargo.toml b/frame/uniques/Cargo.toml index 1e6ca2ce687dc..0441289b3a351 100644 --- a/frame/uniques/Cargo.toml +++ b/frame/uniques/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } -log = { version = "0.4.16", default-features = false } +log = { version = "0.4.17", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } diff --git a/frame/vesting/Cargo.toml b/frame/vesting/Cargo.toml index f73a7af9e6ae4..1690e9eb19f72 100644 --- a/frame/vesting/Cargo.toml +++ b/frame/vesting/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } -log = { version = "0.4.16", default-features = false } +log = { version = "0.4.17", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } diff --git a/primitives/api/Cargo.toml b/primitives/api/Cargo.toml index 3a94cd8e0bd88..f3d091266d5d4 100644 --- a/primitives/api/Cargo.toml +++ b/primitives/api/Cargo.toml @@ -23,7 +23,7 @@ sp-state-machine = { version = "0.12.0", optional = true, path = "../state-machi hash-db = { version = "0.15.2", optional = true } thiserror = { version = "1.0.30", optional = true } -log = { version = "0.4.16", default-features = false } +log = { version = "0.4.17", default-features = false } [dev-dependencies] sp-test-primitives = { version = "2.0.0", path = "../test-primitives" } diff --git a/primitives/api/test/Cargo.toml b/primitives/api/test/Cargo.toml index a36e68ecf9fe0..25000072c88a9 100644 --- a/primitives/api/test/Cargo.toml +++ b/primitives/api/test/Cargo.toml @@ -27,7 +27,7 @@ rustversion = "1.0.6" [dev-dependencies] criterion = "0.3.0" futures = "0.3.21" -log = "0.4.16" +log = "0.4.17" sp-core = { version = "6.0.0", path = "../../core" } [[bench]] diff --git a/primitives/blockchain/Cargo.toml b/primitives/blockchain/Cargo.toml index 4389a867f7132..a5137606e16a2 100644 --- a/primitives/blockchain/Cargo.toml +++ b/primitives/blockchain/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } futures = "0.3.21" -log = "0.4.16" +log = "0.4.17" lru = "0.7.5" parking_lot = "0.12.0" thiserror = "1.0.30" diff --git a/primitives/consensus/common/Cargo.toml b/primitives/consensus/common/Cargo.toml index 5dc94872d6141..973cb3e410e0d 100644 --- a/primitives/consensus/common/Cargo.toml +++ b/primitives/consensus/common/Cargo.toml @@ -20,7 +20,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", features = [ ] } futures = { version = "0.3.21", features = ["thread-pool"] } futures-timer = "3.0.1" -log = "0.4.16" +log = "0.4.17" thiserror = "1.0.30" sp-core = { version = "6.0.0", path = "../../core" } sp-inherents = { version = "4.0.0-dev", path = "../../inherents" } diff --git a/primitives/core/Cargo.toml b/primitives/core/Cargo.toml index 9fbdeb15a0366..852812c1c5659 100644 --- a/primitives/core/Cargo.toml +++ b/primitives/core/Cargo.toml @@ -18,7 +18,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = "max-encoded-len", ] } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -log = { version = "0.4.16", default-features = false } +log = { version = "0.4.17", default-features = false } serde = { version = "1.0.136", optional = true, features = ["derive"] } byteorder = { version = "1.3.2", default-features = false } primitive-types = { version = "0.11.1", default-features = false, features = ["codec", "scale-info"] } diff --git a/primitives/finality-grandpa/Cargo.toml b/primitives/finality-grandpa/Cargo.toml index 277236abc0f8c..c8b24c97bbafd 100644 --- a/primitives/finality-grandpa/Cargo.toml +++ b/primitives/finality-grandpa/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } grandpa = { package = "finality-grandpa", version = "0.15.0", default-features = false, features = ["derive-codec"] } -log = { version = "0.4.16", optional = true } +log = { version = "0.4.17", optional = true } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", features = ["derive"], optional = true } sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } diff --git a/primitives/io/Cargo.toml b/primitives/io/Cargo.toml index 6d0ac398e473d..bd288dd896d1b 100644 --- a/primitives/io/Cargo.toml +++ b/primitives/io/Cargo.toml @@ -27,7 +27,7 @@ sp-runtime-interface = { version = "6.0.0", default-features = false, path = ".. sp-trie = { version = "6.0.0", optional = true, path = "../trie" } sp-externalities = { version = "0.12.0", optional = true, path = "../externalities" } sp-tracing = { version = "5.0.0", default-features = false, path = "../tracing" } -log = { version = "0.4.16", optional = true } +log = { version = "0.4.17", optional = true } futures = { version = "0.3.21", features = ["thread-pool"], optional = true } parking_lot = { version = "0.12.0", optional = true } secp256k1 = { version = "0.21.2", features = ["recovery", "global-context"], optional = true } diff --git a/primitives/merkle-mountain-range/Cargo.toml b/primitives/merkle-mountain-range/Cargo.toml index 831d07315f317..2be3f592b2b20 100644 --- a/primitives/merkle-mountain-range/Cargo.toml +++ b/primitives/merkle-mountain-range/Cargo.toml @@ -13,7 +13,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } -log = { version = "0.4.16", default-features = false } +log = { version = "0.4.17", default-features = false } serde = { version = "1.0.136", features = ["derive"], optional = true } sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } sp-core = { version = "6.0.0", default-features = false, path = "../core" } diff --git a/primitives/runtime/Cargo.toml b/primitives/runtime/Cargo.toml index 04e5c2a9fe6ec..ca4ba82e1aee8 100644 --- a/primitives/runtime/Cargo.toml +++ b/primitives/runtime/Cargo.toml @@ -18,7 +18,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = either = { version = "1.5", default-features = false } hash256-std-hasher = { version = "0.15.2", default-features = false } impl-trait-for-tuples = "0.2.2" -log = { version = "0.4.16", default-features = false } +log = { version = "0.4.17", default-features = false } parity-util-mem = { version = "0.11.0", default-features = false, features = ["primitive-types"] } paste = "1.0" rand = { version = "0.7.2", optional = true } diff --git a/primitives/state-machine/Cargo.toml b/primitives/state-machine/Cargo.toml index 58fa3be514e4c..e472b66c5d9ee 100644 --- a/primitives/state-machine/Cargo.toml +++ b/primitives/state-machine/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } hash-db = { version = "0.15.2", default-features = false } -log = { version = "0.4.16", optional = true } +log = { version = "0.4.17", optional = true } num-traits = { version = "0.2.8", default-features = false } parking_lot = { version = "0.12.0", optional = true } rand = { version = "0.7.2", optional = true } diff --git a/primitives/tasks/Cargo.toml b/primitives/tasks/Cargo.toml index 647a8ed63ff91..c37a8a66f94df 100644 --- a/primitives/tasks/Cargo.toml +++ b/primitives/tasks/Cargo.toml @@ -14,7 +14,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -log = { version = "0.4.16", optional = true } +log = { version = "0.4.17", optional = true } sp-core = { version = "6.0.0", default-features = false, path = "../core" } sp-externalities = { version = "0.12.0", optional = true, path = "../externalities" } sp-io = { version = "6.0.0", default-features = false, path = "../io" } diff --git a/primitives/timestamp/Cargo.toml b/primitives/timestamp/Cargo.toml index 8b40050b4aaa5..42701f5ad3bf1 100644 --- a/primitives/timestamp/Cargo.toml +++ b/primitives/timestamp/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] async-trait = { version = "0.1.50", optional = true } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } futures-timer = { version = "3.0.2", optional = true } -log = { version = "0.4.16", optional = true } +log = { version = "0.4.17", optional = true } thiserror = { version = "1.0.30", optional = true } sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../inherents" } diff --git a/primitives/transaction-storage-proof/Cargo.toml b/primitives/transaction-storage-proof/Cargo.toml index e017377ac63de..9501f86cca055 100644 --- a/primitives/transaction-storage-proof/Cargo.toml +++ b/primitives/transaction-storage-proof/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] async-trait = { version = "0.1.50", optional = true } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -log = { version = "0.4.16", optional = true } +log = { version = "0.4.17", optional = true } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } sp-core = { version = "6.0.0", optional = true, path = "../core" } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../inherents" } diff --git a/primitives/wasm-interface/Cargo.toml b/primitives/wasm-interface/Cargo.toml index 07e118d0ecc86..2314bfb7be5eb 100644 --- a/primitives/wasm-interface/Cargo.toml +++ b/primitives/wasm-interface/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } impl-trait-for-tuples = "0.2.2" -log = { version = "0.4.16", optional = true } +log = { version = "0.4.17", optional = true } wasmi = { version = "0.9.1", optional = true } wasmtime = { version = "0.35.3", default-features = false, optional = true } sp-std = { version = "4.0.0", default-features = false, path = "../std" } diff --git a/test-utils/runtime/Cargo.toml b/test-utils/runtime/Cargo.toml index 64767877548a9..8c529bb6a00a9 100644 --- a/test-utils/runtime/Cargo.toml +++ b/test-utils/runtime/Cargo.toml @@ -48,7 +48,7 @@ sp-externalities = { version = "0.12.0", default-features = false, path = "../.. # 3rd party cfg-if = "1.0" -log = { version = "0.4.16", default-features = false } +log = { version = "0.4.17", default-features = false } serde = { version = "1.0.136", optional = true, features = ["derive"] } [dev-dependencies] diff --git a/utils/frame/benchmarking-cli/Cargo.toml b/utils/frame/benchmarking-cli/Cargo.toml index 6266f4eb8bb2a..68be768012d51 100644 --- a/utils/frame/benchmarking-cli/Cargo.toml +++ b/utils/frame/benchmarking-cli/Cargo.toml @@ -24,7 +24,7 @@ itertools = "0.10.3" kvdb = "0.11.0" lazy_static = "1.4.0" linked-hash-map = "0.5.4" -log = "0.4.16" +log = "0.4.17" memory-db = "0.29.0" prettytable-rs = "0.8.0" rand = { version = "0.8.4", features = ["small_rng"] } diff --git a/utils/frame/remote-externalities/Cargo.toml b/utils/frame/remote-externalities/Cargo.toml index f3876e41f4d02..a0adb8edb2d67 100644 --- a/utils/frame/remote-externalities/Cargo.toml +++ b/utils/frame/remote-externalities/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0" } env_logger = "0.9" jsonrpsee = { version = "0.13.0", features = ["ws-client", "macros"] } -log = "0.4.16" +log = "0.4.17" serde = "1.0.136" serde_json = "1.0" frame-support = { version = "4.0.0-dev", optional = true, path = "../../../frame/support" } diff --git a/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml b/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml index 71fadfdbc041c..8fe0ad8d61d6e 100644 --- a/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml +++ b/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } serde = { version = "1", features = ["derive"] } -log = { version = "0.4.16", default-features = false } +log = { version = "0.4.17", default-features = false } sp-std = { path = "../../../../primitives/std" } sp-io = { path = "../../../../primitives/io" } diff --git a/utils/frame/rpc/system/Cargo.toml b/utils/frame/rpc/system/Cargo.toml index 10163aacd1969..5f28c4e16231b 100644 --- a/utils/frame/rpc/system/Cargo.toml +++ b/utils/frame/rpc/system/Cargo.toml @@ -17,7 +17,7 @@ serde_json = "1" codec = { package = "parity-scale-codec", version = "3.0.0" } jsonrpsee = { version = "0.13.0", features = ["server"] } futures = "0.3.21" -log = "0.4.16" +log = "0.4.17" frame-system-rpc-runtime-api = { version = "4.0.0-dev", path = "../../../../frame/system/rpc/runtime-api" } sc-client-api = { version = "4.0.0-dev", path = "../../../../client/api" } sc-rpc-api = { version = "0.10.0-dev", path = "../../../../client/rpc-api" } diff --git a/utils/frame/try-runtime/cli/Cargo.toml b/utils/frame/try-runtime/cli/Cargo.toml index 48e9c1d6b36a1..a357378dc1903 100644 --- a/utils/frame/try-runtime/cli/Cargo.toml +++ b/utils/frame/try-runtime/cli/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] clap = { version = "3.1.18", features = ["derive"] } -log = "0.4.16" +log = "0.4.17" parity-scale-codec = "3.0.0" serde = "1.0.136" zstd = { version = "0.10.0", default-features = false } diff --git a/utils/prometheus/Cargo.toml b/utils/prometheus/Cargo.toml index 9864cfd0823ce..3c2f8321befbe 100644 --- a/utils/prometheus/Cargo.toml +++ b/utils/prometheus/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] futures-util = { version = "0.3.19", default-features = false, features = ["io"] } hyper = { version = "0.14.16", default-features = false, features = ["http1", "server", "tcp"] } -log = "0.4.16" +log = "0.4.17" prometheus = { version = "0.13.0", default-features = false } thiserror = "1.0" tokio = { version = "1.17.0", features = ["parking_lot"] } From 9cac589a18c969dacbfc14abbc325e30f45d5808 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 May 2022 10:07:00 +0200 Subject: [PATCH 220/484] Bump scale-info from 2.0.1 to 2.1.1 (#11420) Bumps [scale-info](https://github.com/paritytech/scale-info) from 2.0.1 to 2.1.1. - [Release notes](https://github.com/paritytech/scale-info/releases) - [Changelog](https://github.com/paritytech/scale-info/blob/master/CHANGELOG.md) - [Commits](https://github.com/paritytech/scale-info/commits/v2.1.1) --- updated-dependencies: - dependency-name: scale-info dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- bin/node-template/pallets/template/Cargo.toml | 2 +- bin/node-template/runtime/Cargo.toml | 2 +- bin/node/executor/Cargo.toml | 2 +- bin/node/primitives/Cargo.toml | 2 +- bin/node/runtime/Cargo.toml | 2 +- client/rpc-api/Cargo.toml | 2 +- frame/assets/Cargo.toml | 2 +- frame/atomic-swap/Cargo.toml | 2 +- frame/aura/Cargo.toml | 2 +- frame/authority-discovery/Cargo.toml | 2 +- frame/authorship/Cargo.toml | 2 +- frame/babe/Cargo.toml | 2 +- frame/bags-list/Cargo.toml | 2 +- frame/balances/Cargo.toml | 2 +- frame/beefy-mmr/Cargo.toml | 2 +- frame/beefy/Cargo.toml | 2 +- frame/benchmarking/Cargo.toml | 2 +- frame/bounties/Cargo.toml | 2 +- frame/child-bounties/Cargo.toml | 2 +- frame/collective/Cargo.toml | 2 +- frame/contracts/Cargo.toml | 2 +- frame/contracts/rpc/runtime-api/Cargo.toml | 2 +- frame/conviction-voting/Cargo.toml | 2 +- frame/democracy/Cargo.toml | 2 +- frame/election-provider-multi-phase/Cargo.toml | 2 +- frame/election-provider-support/Cargo.toml | 2 +- frame/election-provider-support/solution-type/Cargo.toml | 2 +- .../solution-type/fuzzer/Cargo.toml | 2 +- frame/examples/basic/Cargo.toml | 2 +- frame/examples/offchain-worker/Cargo.toml | 2 +- frame/examples/parallel/Cargo.toml | 2 +- frame/executive/Cargo.toml | 2 +- frame/gilt/Cargo.toml | 2 +- frame/grandpa/Cargo.toml | 2 +- frame/identity/Cargo.toml | 2 +- frame/im-online/Cargo.toml | 2 +- frame/indices/Cargo.toml | 2 +- frame/lottery/Cargo.toml | 2 +- frame/membership/Cargo.toml | 2 +- frame/merkle-mountain-range/Cargo.toml | 2 +- frame/multisig/Cargo.toml | 2 +- frame/nicks/Cargo.toml | 2 +- frame/node-authorization/Cargo.toml | 2 +- frame/nomination-pools/Cargo.toml | 2 +- frame/nomination-pools/benchmarking/Cargo.toml | 2 +- frame/offences/Cargo.toml | 2 +- frame/preimage/Cargo.toml | 2 +- frame/proxy/Cargo.toml | 2 +- frame/randomness-collective-flip/Cargo.toml | 2 +- frame/recovery/Cargo.toml | 2 +- frame/referenda/Cargo.toml | 2 +- frame/remark/Cargo.toml | 2 +- frame/scheduler/Cargo.toml | 2 +- frame/scored-pool/Cargo.toml | 2 +- frame/session/Cargo.toml | 2 +- frame/session/benchmarking/Cargo.toml | 2 +- frame/society/Cargo.toml | 2 +- frame/staking/Cargo.toml | 2 +- frame/state-trie-migration/Cargo.toml | 2 +- frame/sudo/Cargo.toml | 2 +- frame/support/Cargo.toml | 2 +- frame/support/test/Cargo.toml | 2 +- frame/support/test/compile_pass/Cargo.toml | 2 +- frame/system/Cargo.toml | 2 +- frame/system/benchmarking/Cargo.toml | 2 +- frame/timestamp/Cargo.toml | 2 +- frame/tips/Cargo.toml | 2 +- frame/transaction-payment/Cargo.toml | 2 +- frame/transaction-payment/asset-tx-payment/Cargo.toml | 2 +- frame/transaction-storage/Cargo.toml | 2 +- frame/treasury/Cargo.toml | 2 +- frame/uniques/Cargo.toml | 2 +- frame/utility/Cargo.toml | 2 +- frame/vesting/Cargo.toml | 2 +- frame/whitelist/Cargo.toml | 2 +- primitives/application-crypto/Cargo.toml | 2 +- primitives/arithmetic/Cargo.toml | 2 +- primitives/authority-discovery/Cargo.toml | 2 +- primitives/beefy/Cargo.toml | 2 +- primitives/consensus/aura/Cargo.toml | 2 +- primitives/consensus/babe/Cargo.toml | 2 +- primitives/consensus/vrf/Cargo.toml | 2 +- primitives/core/Cargo.toml | 2 +- primitives/finality-grandpa/Cargo.toml | 2 +- primitives/npos-elections/Cargo.toml | 2 +- primitives/npos-elections/fuzzer/Cargo.toml | 2 +- primitives/runtime/Cargo.toml | 2 +- primitives/session/Cargo.toml | 2 +- primitives/staking/Cargo.toml | 2 +- primitives/transaction-storage-proof/Cargo.toml | 2 +- primitives/trie/Cargo.toml | 2 +- primitives/version/Cargo.toml | 2 +- test-utils/runtime/Cargo.toml | 2 +- utils/frame/rpc/state-trie-migration-rpc/Cargo.toml | 2 +- utils/frame/rpc/support/Cargo.toml | 2 +- 96 files changed, 99 insertions(+), 99 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index abd9cd7b46e02..5d86af6c74262 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9066,9 +9066,9 @@ dependencies = [ [[package]] name = "scale-info" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0563970d79bcbf3c537ce3ad36d859b30d36fc5b190efd227f1f7a84d7cf0d42" +checksum = "8980cafbe98a7ee7a9cc16b32ebce542c77883f512d83fbf2ddc8f6a85ea74c9" dependencies = [ "bitvec", "cfg-if 1.0.0", @@ -9080,9 +9080,9 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7805950c36512db9e3251c970bb7ac425f326716941862205d612ab3b5e46e2" +checksum = "4260c630e8a8a33429d1688eff2f163f24c65a4e1b1578ef6b565061336e4b6f" dependencies = [ "proc-macro-crate", "proc-macro2", diff --git a/bin/node-template/pallets/template/Cargo.toml b/bin/node-template/pallets/template/Cargo.toml index 0647761a62f9c..6f7a4b1d25841 100644 --- a/bin/node-template/pallets/template/Cargo.toml +++ b/bin/node-template/pallets/template/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../../../../frame/benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../../../../frame/support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../../../../frame/system" } diff --git a/bin/node-template/runtime/Cargo.toml b/bin/node-template/runtime/Cargo.toml index 43dc98f254651..734ed089aa4bd 100644 --- a/bin/node-template/runtime/Cargo.toml +++ b/bin/node-template/runtime/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } pallet-aura = { version = "4.0.0-dev", default-features = false, path = "../../../frame/aura" } pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../../../frame/balances" } diff --git a/bin/node/executor/Cargo.toml b/bin/node/executor/Cargo.toml index 909bd19103742..6b6a1709d5aa0 100644 --- a/bin/node/executor/Cargo.toml +++ b/bin/node/executor/Cargo.toml @@ -13,7 +13,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } -scale-info = { version = "2.0.1", features = ["derive"] } +scale-info = { version = "2.1.1", features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", path = "../../../frame/benchmarking" } node-primitives = { version = "2.0.0", path = "../primitives" } node-runtime = { version = "3.0.0-dev", path = "../runtime" } diff --git a/bin/node/primitives/Cargo.toml b/bin/node/primitives/Cargo.toml index 810b41003109d..bc6fa669ca4ea 100644 --- a/bin/node/primitives/Cargo.toml +++ b/bin/node/primitives/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-system = { version = "4.0.0-dev", default-features = false, path = "../../../frame/system" } sp-application-crypto = { version = "6.0.0", default-features = false, path = "../../../primitives/application-crypto" } sp-core = { version = "6.0.0", default-features = false, path = "../../../primitives/core" } diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 46d9ff03ed36e..1b3e9083d1a1d 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -18,7 +18,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = "derive", "max-encoded-len", ] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } static_assertions = "1.1.0" hex-literal = { version = "0.3.4", optional = true } log = { version = "0.4.17", default-features = false } diff --git a/client/rpc-api/Cargo.toml b/client/rpc-api/Cargo.toml index 0ad0b7cc5bfdc..f63f8ee65fbec 100644 --- a/client/rpc-api/Cargo.toml +++ b/client/rpc-api/Cargo.toml @@ -17,7 +17,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.21" log = "0.4.17" parking_lot = "0.12.0" -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0.79" thiserror = "1.0" diff --git a/frame/assets/Cargo.toml b/frame/assets/Cargo.toml index d3685e6582324..9e98d4e15aed4 100644 --- a/frame/assets/Cargo.toml +++ b/frame/assets/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } # Needed for various traits. In our case, `OnFinalize`. sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } diff --git a/frame/atomic-swap/Cargo.toml b/frame/atomic-swap/Cargo.toml index 4fd793496572e..e70041f21ca96 100644 --- a/frame/atomic-swap/Cargo.toml +++ b/frame/atomic-swap/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } diff --git a/frame/aura/Cargo.toml b/frame/aura/Cargo.toml index 2ce90ff0215ae..7dad0b6b1b098 100644 --- a/frame/aura/Cargo.toml +++ b/frame/aura/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } pallet-timestamp = { version = "4.0.0-dev", default-features = false, path = "../timestamp" } diff --git a/frame/authority-discovery/Cargo.toml b/frame/authority-discovery/Cargo.toml index bebe8e0f436a6..514fd7e244ef9 100644 --- a/frame/authority-discovery/Cargo.toml +++ b/frame/authority-discovery/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } pallet-session = { version = "4.0.0-dev", default-features = false, features = [ diff --git a/frame/authorship/Cargo.toml b/frame/authorship/Cargo.toml index bc7fad9684e1f..3078b9dfa295a 100644 --- a/frame/authorship/Cargo.toml +++ b/frame/authorship/Cargo.toml @@ -17,7 +17,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = "derive", ] } impl-trait-for-tuples = "0.2.2" -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } sp-authorship = { version = "4.0.0-dev", default-features = false, path = "../../primitives/authorship" } diff --git a/frame/babe/Cargo.toml b/frame/babe/Cargo.toml index 23f8f1517cb63..dd76726df3017 100644 --- a/frame/babe/Cargo.toml +++ b/frame/babe/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } diff --git a/frame/bags-list/Cargo.toml b/frame/bags-list/Cargo.toml index 4efa1fbe287fa..9590d3d3ec4a4 100644 --- a/frame/bags-list/Cargo.toml +++ b/frame/bags-list/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] # parity codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } # primitives sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } diff --git a/frame/balances/Cargo.toml b/frame/balances/Cargo.toml index b2840eb3e1787..10150f0895906 100644 --- a/frame/balances/Cargo.toml +++ b/frame/balances/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } diff --git a/frame/beefy-mmr/Cargo.toml b/frame/beefy-mmr/Cargo.toml index 85986c2a41529..8da182f1c29fc 100644 --- a/frame/beefy-mmr/Cargo.toml +++ b/frame/beefy-mmr/Cargo.toml @@ -12,7 +12,7 @@ homepage = "https://substrate.io" codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } hex = { version = "0.4", optional = true } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", optional = true } beefy-merkle-tree = { version = "4.0.0-dev", default-features = false, path = "./primitives" } beefy-primitives = { version = "4.0.0-dev", default-features = false, path = "../../primitives/beefy" } diff --git a/frame/beefy/Cargo.toml b/frame/beefy/Cargo.toml index 89a49831d9f62..eecce963d19f0 100644 --- a/frame/beefy/Cargo.toml +++ b/frame/beefy/Cargo.toml @@ -10,7 +10,7 @@ homepage = "https://substrate.io" [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", optional = true } beefy-primitives = { version = "4.0.0-dev", default-features = false, path = "../../primitives/beefy" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } diff --git a/frame/benchmarking/Cargo.toml b/frame/benchmarking/Cargo.toml index 6dddcf7e247ae..4205274b5dbc3 100644 --- a/frame/benchmarking/Cargo.toml +++ b/frame/benchmarking/Cargo.toml @@ -17,7 +17,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = linregress = { version = "0.4.4", optional = true } log = { version = "0.4.17", default-features = false } paste = "1.0" -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", optional = true } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } diff --git a/frame/bounties/Cargo.toml b/frame/bounties/Cargo.toml index d37c06a96e88e..645772fb27669 100644 --- a/frame/bounties/Cargo.toml +++ b/frame/bounties/Cargo.toml @@ -17,7 +17,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = "derive", ] } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } diff --git a/frame/child-bounties/Cargo.toml b/frame/child-bounties/Cargo.toml index 722ee0af01f95..575f3e38c8183 100644 --- a/frame/child-bounties/Cargo.toml +++ b/frame/child-bounties/Cargo.toml @@ -17,7 +17,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = "derive", ] } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } diff --git a/frame/collective/Cargo.toml b/frame/collective/Cargo.toml index b810c3f3bd11d..0cb2a8b136044 100644 --- a/frame/collective/Cargo.toml +++ b/frame/collective/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } diff --git a/frame/contracts/Cargo.toml b/frame/contracts/Cargo.toml index 2e906b69f32fc..d27801df33bda 100644 --- a/frame/contracts/Cargo.toml +++ b/frame/contracts/Cargo.toml @@ -18,7 +18,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = "derive", "max-encoded-len", ] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } log = { version = "0.4", default-features = false } wasm-instrument = { version = "0.1", default-features = false } serde = { version = "1", optional = true, features = ["derive"] } diff --git a/frame/contracts/rpc/runtime-api/Cargo.toml b/frame/contracts/rpc/runtime-api/Cargo.toml index e50a41624e7e9..bd07d577ec272 100644 --- a/frame/contracts/rpc/runtime-api/Cargo.toml +++ b/frame/contracts/rpc/runtime-api/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } # Substrate Dependencies pallet-contracts-primitives = { version = "6.0.0", default-features = false, path = "../../common" } diff --git a/frame/conviction-voting/Cargo.toml b/frame/conviction-voting/Cargo.toml index 4ebc3c8f4bc43..ff6cfcc3efdee 100644 --- a/frame/conviction-voting/Cargo.toml +++ b/frame/conviction-voting/Cargo.toml @@ -17,7 +17,7 @@ assert_matches = "1.3.0" codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", features = ["derive"], optional = true } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } diff --git a/frame/democracy/Cargo.toml b/frame/democracy/Cargo.toml index b36448bdf25ef..e0b85ed7d18df 100644 --- a/frame/democracy/Cargo.toml +++ b/frame/democracy/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", features = ["derive"], optional = true } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } diff --git a/frame/election-provider-multi-phase/Cargo.toml b/frame/election-provider-multi-phase/Cargo.toml index 6f3e500577af6..6c8e537a72c64 100644 --- a/frame/election-provider-multi-phase/Cargo.toml +++ b/frame/election-provider-multi-phase/Cargo.toml @@ -17,7 +17,7 @@ static_assertions = "1.1.0" codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } -scale-info = { version = "2.0.1", default-features = false, features = [ +scale-info = { version = "2.1.1", default-features = false, features = [ "derive", ] } log = { version = "0.4.17", default-features = false } diff --git a/frame/election-provider-support/Cargo.toml b/frame/election-provider-support/Cargo.toml index bfddd25a1fa3e..67e1ea63cb655 100644 --- a/frame/election-provider-support/Cargo.toml +++ b/frame/election-provider-support/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-election-provider-solution-type = { version = "4.0.0-dev", path = "solution-type" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } diff --git a/frame/election-provider-support/solution-type/Cargo.toml b/frame/election-provider-support/solution-type/Cargo.toml index a4e7256127115..8fd7ddbff434d 100644 --- a/frame/election-provider-support/solution-type/Cargo.toml +++ b/frame/election-provider-support/solution-type/Cargo.toml @@ -22,7 +22,7 @@ proc-macro-crate = "1.1.3" [dev-dependencies] parity-scale-codec = "3.0.0" -scale-info = "2.0.1" +scale-info = "2.1.1" sp-arithmetic = { version = "5.0.0", path = "../../../primitives/arithmetic" } # used by generate_solution_type: frame-election-provider-support = { version = "4.0.0-dev", path = ".." } diff --git a/frame/election-provider-support/solution-type/fuzzer/Cargo.toml b/frame/election-provider-support/solution-type/fuzzer/Cargo.toml index 6520f6e632230..10f82cd316851 100644 --- a/frame/election-provider-support/solution-type/fuzzer/Cargo.toml +++ b/frame/election-provider-support/solution-type/fuzzer/Cargo.toml @@ -18,7 +18,7 @@ honggfuzz = "0.5" rand = { version = "0.8", features = ["std", "small_rng"] } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-election-provider-solution-type = { version = "4.0.0-dev", path = ".." } frame-election-provider-support = { version = "4.0.0-dev", path = "../.." } sp-arithmetic = { version = "5.0.0", path = "../../../../primitives/arithmetic" } diff --git a/frame/examples/basic/Cargo.toml b/frame/examples/basic/Cargo.toml index d6bea8b3c290f..a5f0c7c89321a 100644 --- a/frame/examples/basic/Cargo.toml +++ b/frame/examples/basic/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } diff --git a/frame/examples/offchain-worker/Cargo.toml b/frame/examples/offchain-worker/Cargo.toml index bcc845823e137..e63b82757c030 100644 --- a/frame/examples/offchain-worker/Cargo.toml +++ b/frame/examples/offchain-worker/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } lite-json = { version = "0.1", default-features = false } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } sp-core = { version = "6.0.0", default-features = false, path = "../../../primitives/core" } diff --git a/frame/examples/parallel/Cargo.toml b/frame/examples/parallel/Cargo.toml index 6834a9143ae97..f4d2c72a23a49 100644 --- a/frame/examples/parallel/Cargo.toml +++ b/frame/examples/parallel/Cargo.toml @@ -13,7 +13,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } sp-core = { version = "6.0.0", default-features = false, path = "../../../primitives/core" } diff --git a/frame/executive/Cargo.toml b/frame/executive/Cargo.toml index ed3c5282fc81d..b67f3313e612b 100644 --- a/frame/executive/Cargo.toml +++ b/frame/executive/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } diff --git a/frame/gilt/Cargo.toml b/frame/gilt/Cargo.toml index 4916b950e47dd..d6f61c6d250ba 100644 --- a/frame/gilt/Cargo.toml +++ b/frame/gilt/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } diff --git a/frame/grandpa/Cargo.toml b/frame/grandpa/Cargo.toml index fb2ffe42f526b..cb4233a26fb33 100644 --- a/frame/grandpa/Cargo.toml +++ b/frame/grandpa/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } diff --git a/frame/identity/Cargo.toml b/frame/identity/Cargo.toml index d5057e302339b..8e821537fd9b2 100644 --- a/frame/identity/Cargo.toml +++ b/frame/identity/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } enumflags2 = { version = "0.7.4" } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } diff --git a/frame/im-online/Cargo.toml b/frame/im-online/Cargo.toml index 0279e29169006..a90b95b21cd88 100644 --- a/frame/im-online/Cargo.toml +++ b/frame/im-online/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } diff --git a/frame/indices/Cargo.toml b/frame/indices/Cargo.toml index 99edf95fb332b..90eb18a106000 100644 --- a/frame/indices/Cargo.toml +++ b/frame/indices/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } diff --git a/frame/lottery/Cargo.toml b/frame/lottery/Cargo.toml index 9e5992195441e..8f7c8eefe800d 100644 --- a/frame/lottery/Cargo.toml +++ b/frame/lottery/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } diff --git a/frame/membership/Cargo.toml b/frame/membership/Cargo.toml index 356fc12b09507..0473fd46956af 100644 --- a/frame/membership/Cargo.toml +++ b/frame/membership/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } diff --git a/frame/merkle-mountain-range/Cargo.toml b/frame/merkle-mountain-range/Cargo.toml index 0b5d404fe23aa..f5f8bdee0855d 100644 --- a/frame/merkle-mountain-range/Cargo.toml +++ b/frame/merkle-mountain-range/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } mmr-lib = { package = "ckb-merkle-mountain-range", version = "0.3.2", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } diff --git a/frame/multisig/Cargo.toml b/frame/multisig/Cargo.toml index c151016c594c7..a370215032714 100644 --- a/frame/multisig/Cargo.toml +++ b/frame/multisig/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } diff --git a/frame/nicks/Cargo.toml b/frame/nicks/Cargo.toml index 5ee2e9193dd2a..1d378b257f5a2 100644 --- a/frame/nicks/Cargo.toml +++ b/frame/nicks/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } diff --git a/frame/node-authorization/Cargo.toml b/frame/node-authorization/Cargo.toml index a22af2d87578e..0b27028228c10 100644 --- a/frame/node-authorization/Cargo.toml +++ b/frame/node-authorization/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } diff --git a/frame/nomination-pools/Cargo.toml b/frame/nomination-pools/Cargo.toml index 1ac8caa5f64b4..0820125d77f44 100644 --- a/frame/nomination-pools/Cargo.toml +++ b/frame/nomination-pools/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] # parity codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } # FRAME frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } diff --git a/frame/nomination-pools/benchmarking/Cargo.toml b/frame/nomination-pools/benchmarking/Cargo.toml index 58158c20cf195..2e045c95ec9b3 100644 --- a/frame/nomination-pools/benchmarking/Cargo.toml +++ b/frame/nomination-pools/benchmarking/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] # parity codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } # FRAME frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../benchmarking" } diff --git a/frame/offences/Cargo.toml b/frame/offences/Cargo.toml index 91553ea414f7e..ddbed3d3297fa 100644 --- a/frame/offences/Cargo.toml +++ b/frame/offences/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", optional = true } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } diff --git a/frame/preimage/Cargo.toml b/frame/preimage/Cargo.toml index 9face65182f7e..325e906c61a3c 100644 --- a/frame/preimage/Cargo.toml +++ b/frame/preimage/Cargo.toml @@ -11,7 +11,7 @@ readme = "README.md" [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } diff --git a/frame/proxy/Cargo.toml b/frame/proxy/Cargo.toml index 62cbb16dddf29..aaacaa23021e7 100644 --- a/frame/proxy/Cargo.toml +++ b/frame/proxy/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["max-encoded-len"] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } diff --git a/frame/randomness-collective-flip/Cargo.toml b/frame/randomness-collective-flip/Cargo.toml index 692f33575e127..03f0022a42e29 100644 --- a/frame/randomness-collective-flip/Cargo.toml +++ b/frame/randomness-collective-flip/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } safe-mix = { version = "1.0", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } diff --git a/frame/recovery/Cargo.toml b/frame/recovery/Cargo.toml index 2a72963f6cf0d..396555b3e2758 100644 --- a/frame/recovery/Cargo.toml +++ b/frame/recovery/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } diff --git a/frame/referenda/Cargo.toml b/frame/referenda/Cargo.toml index d13d08542f56e..75888c6d14f3d 100644 --- a/frame/referenda/Cargo.toml +++ b/frame/referenda/Cargo.toml @@ -17,7 +17,7 @@ assert_matches = { version = "1.5", optional = true } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", features = ["derive"], optional = true } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } diff --git a/frame/remark/Cargo.toml b/frame/remark/Cargo.toml index 573502779c586..71a65ce554975 100644 --- a/frame/remark/Cargo.toml +++ b/frame/remark/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", optional = true } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } diff --git a/frame/scheduler/Cargo.toml b/frame/scheduler/Cargo.toml index 0908fc110802a..d92d0df0c8037 100644 --- a/frame/scheduler/Cargo.toml +++ b/frame/scheduler/Cargo.toml @@ -12,7 +12,7 @@ readme = "README.md" [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } diff --git a/frame/scored-pool/Cargo.toml b/frame/scored-pool/Cargo.toml index c857dc9ab9ace..2ec765498be9e 100644 --- a/frame/scored-pool/Cargo.toml +++ b/frame/scored-pool/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } diff --git a/frame/session/Cargo.toml b/frame/session/Cargo.toml index 56c72eeaf6d4b..14996782eae87 100644 --- a/frame/session/Cargo.toml +++ b/frame/session/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } impl-trait-for-tuples = "0.2.2" log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } pallet-timestamp = { version = "4.0.0-dev", default-features = false, path = "../timestamp" } diff --git a/frame/session/benchmarking/Cargo.toml b/frame/session/benchmarking/Cargo.toml index 1cce8fec023db..930ddb0ce7057 100644 --- a/frame/session/benchmarking/Cargo.toml +++ b/frame/session/benchmarking/Cargo.toml @@ -25,7 +25,7 @@ sp-std = { version = "4.0.0", default-features = false, path = "../../../primiti [dev-dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } -scale-info = "2.0.1" +scale-info = "2.1.1" frame-election-provider-support = { version = "4.0.0-dev", path = "../../election-provider-support" } pallet-balances = { version = "4.0.0-dev", path = "../../balances" } pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../../staking/reward-curve" } diff --git a/frame/society/Cargo.toml b/frame/society/Cargo.toml index b19820c66fb58..5e13c95d74eb3 100644 --- a/frame/society/Cargo.toml +++ b/frame/society/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } rand_chacha = { version = "0.2", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } diff --git a/frame/staking/Cargo.toml b/frame/staking/Cargo.toml index cc8e19b1de892..0950478fba089 100644 --- a/frame/staking/Cargo.toml +++ b/frame/staking/Cargo.toml @@ -17,7 +17,7 @@ serde = { version = "1.0.136", optional = true } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } diff --git a/frame/state-trie-migration/Cargo.toml b/frame/state-trie-migration/Cargo.toml index be3e4392ac918..9c8c05a4fada4 100644 --- a/frame/state-trie-migration/Cargo.toml +++ b/frame/state-trie-migration/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.133", optional = true } thousands = { version = "0.2.0", optional = true } zstd = { version = "0.10.0", default-features = false, optional = true } diff --git a/frame/sudo/Cargo.toml b/frame/sudo/Cargo.toml index d2b3f98e65d0b..efa75813af543 100644 --- a/frame/sudo/Cargo.toml +++ b/frame/sudo/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } diff --git a/frame/support/Cargo.toml b/frame/support/Cargo.toml index 2d510fd4690ca..7d88f7fe8eb6f 100644 --- a/frame/support/Cargo.toml +++ b/frame/support/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] serde = { version = "1.0.136", optional = true, features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-metadata = { version = "15.0.0", default-features = false, features = ["v14"] } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } diff --git a/frame/support/test/Cargo.toml b/frame/support/test/Cargo.toml index 4aa0ff7d5a347..dd23d7e6b0d96 100644 --- a/frame/support/test/Cargo.toml +++ b/frame/support/test/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] serde = { version = "1.0.136", default-features = false, features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } sp-arithmetic = { version = "5.0.0", default-features = false, path = "../../../primitives/arithmetic" } sp-io = { version = "6.0.0", path = "../../../primitives/io", default-features = false } sp-state-machine = { version = "0.12.0", optional = true, path = "../../../primitives/state-machine" } diff --git a/frame/support/test/compile_pass/Cargo.toml b/frame/support/test/compile_pass/Cargo.toml index c1619c6a56c97..34bd980e0187b 100644 --- a/frame/support/test/compile_pass/Cargo.toml +++ b/frame/support/test/compile_pass/Cargo.toml @@ -13,7 +13,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../../" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../../../system" } sp-core = { version = "6.0.0", default-features = false, path = "../../../../primitives/core" } diff --git a/frame/system/Cargo.toml b/frame/system/Cargo.toml index 38a2b934cef17..3429c6546c7fd 100644 --- a/frame/system/Cargo.toml +++ b/frame/system/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", features = ["derive"], optional = true } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } diff --git a/frame/system/benchmarking/Cargo.toml b/frame/system/benchmarking/Cargo.toml index b39b1a1c75b9d..437d06a17c781 100644 --- a/frame/system/benchmarking/Cargo.toml +++ b/frame/system/benchmarking/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } diff --git a/frame/timestamp/Cargo.toml b/frame/timestamp/Cargo.toml index 6ce1592cae3f9..8967733f7c5c8 100644 --- a/frame/timestamp/Cargo.toml +++ b/frame/timestamp/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } diff --git a/frame/tips/Cargo.toml b/frame/tips/Cargo.toml index 23f61dc4f96cd..e2ca152148db6 100644 --- a/frame/tips/Cargo.toml +++ b/frame/tips/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", features = ["derive"], optional = true } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } diff --git a/frame/transaction-payment/Cargo.toml b/frame/transaction-payment/Cargo.toml index 6da8f4c699097..519433a8ce0a8 100644 --- a/frame/transaction-payment/Cargo.toml +++ b/frame/transaction-payment/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ "derive", ] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", optional = true } smallvec = "1.8.0" frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } diff --git a/frame/transaction-payment/asset-tx-payment/Cargo.toml b/frame/transaction-payment/asset-tx-payment/Cargo.toml index 0c2bcb730aa75..fb000dbfdb103 100644 --- a/frame/transaction-payment/asset-tx-payment/Cargo.toml +++ b/frame/transaction-payment/asset-tx-payment/Cargo.toml @@ -25,7 +25,7 @@ pallet-transaction-payment = { version = "4.0.0-dev", default-features = false, # Other dependencies codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", optional = true } [dev-dependencies] diff --git a/frame/transaction-storage/Cargo.toml b/frame/transaction-storage/Cargo.toml index 99bea1cd36882..a5195c4f75974 100644 --- a/frame/transaction-storage/Cargo.toml +++ b/frame/transaction-storage/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } hex-literal = { version = "0.3.4", optional = true } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", optional = true } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } diff --git a/frame/treasury/Cargo.toml b/frame/treasury/Cargo.toml index 4ae2c035e482a..56c2fcdab58f7 100644 --- a/frame/treasury/Cargo.toml +++ b/frame/treasury/Cargo.toml @@ -18,7 +18,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = "max-encoded-len", ] } impl-trait-for-tuples = "0.2.2" -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", features = ["derive"], optional = true } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } diff --git a/frame/uniques/Cargo.toml b/frame/uniques/Cargo.toml index 0441289b3a351..19b0790947f84 100644 --- a/frame/uniques/Cargo.toml +++ b/frame/uniques/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } diff --git a/frame/utility/Cargo.toml b/frame/utility/Cargo.toml index 066682e0e349e..7b56d7974e4b5 100644 --- a/frame/utility/Cargo.toml +++ b/frame/utility/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } diff --git a/frame/vesting/Cargo.toml b/frame/vesting/Cargo.toml index 1690e9eb19f72..eb902c0633331 100644 --- a/frame/vesting/Cargo.toml +++ b/frame/vesting/Cargo.toml @@ -17,7 +17,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = "derive", ] } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } diff --git a/frame/whitelist/Cargo.toml b/frame/whitelist/Cargo.toml index c808cacb801af..daee560904a08 100644 --- a/frame/whitelist/Cargo.toml +++ b/frame/whitelist/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } -scale-info = { version = "2.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } diff --git a/primitives/application-crypto/Cargo.toml b/primitives/application-crypto/Cargo.toml index a8181ca5380c2..117e84e959392 100644 --- a/primitives/application-crypto/Cargo.toml +++ b/primitives/application-crypto/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] sp-core = { version = "6.0.0", default-features = false, path = "../core" } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", optional = true, features = ["derive"] } sp-std = { version = "4.0.0", default-features = false, path = "../std" } sp-io = { version = "6.0.0", default-features = false, path = "../io" } diff --git a/primitives/arithmetic/Cargo.toml b/primitives/arithmetic/Cargo.toml index 31b893531a8d5..4fdf983943c41 100644 --- a/primitives/arithmetic/Cargo.toml +++ b/primitives/arithmetic/Cargo.toml @@ -20,7 +20,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = ] } integer-sqrt = "0.1.2" num-traits = { version = "0.2.8", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", features = ["derive"], optional = true } static_assertions = "1.1.0" sp-debug-derive = { version = "4.0.0", default-features = false, path = "../debug-derive" } diff --git a/primitives/authority-discovery/Cargo.toml b/primitives/authority-discovery/Cargo.toml index 6a822bf20b329..b5491931d19ba 100644 --- a/primitives/authority-discovery/Cargo.toml +++ b/primitives/authority-discovery/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } sp-application-crypto = { version = "6.0.0", default-features = false, path = "../application-crypto" } sp-runtime = { version = "6.0.0", default-features = false, path = "../runtime" } diff --git a/primitives/beefy/Cargo.toml b/primitives/beefy/Cargo.toml index 83896674175bc..320a30770ab42 100644 --- a/primitives/beefy/Cargo.toml +++ b/primitives/beefy/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } sp-application-crypto = { version = "6.0.0", default-features = false, path = "../application-crypto" } sp-core = { version = "6.0.0", default-features = false, path = "../core" } diff --git a/primitives/consensus/aura/Cargo.toml b/primitives/consensus/aura/Cargo.toml index 41d02ac0779a3..dbb67a27c5144 100644 --- a/primitives/consensus/aura/Cargo.toml +++ b/primitives/consensus/aura/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] async-trait = { version = "0.1.50", optional = true } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } sp-api = { version = "4.0.0-dev", default-features = false, path = "../../api" } sp-application-crypto = { version = "6.0.0", default-features = false, path = "../../application-crypto" } sp-consensus = { version = "0.10.0-dev", optional = true, path = "../common" } diff --git a/primitives/consensus/babe/Cargo.toml b/primitives/consensus/babe/Cargo.toml index 57775a1b718da..736a78ab67b1a 100644 --- a/primitives/consensus/babe/Cargo.toml +++ b/primitives/consensus/babe/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] async-trait = { version = "0.1.50", optional = true } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } merlin = { version = "2.0", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", features = ["derive"], optional = true } sp-api = { version = "4.0.0-dev", default-features = false, path = "../../api" } sp-application-crypto = { version = "6.0.0", default-features = false, path = "../../application-crypto" } diff --git a/primitives/consensus/vrf/Cargo.toml b/primitives/consensus/vrf/Cargo.toml index 44aabf074d137..3209fb230b5aa 100644 --- a/primitives/consensus/vrf/Cargo.toml +++ b/primitives/consensus/vrf/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } -scale-info = { version = "2.0.1", default-features = false } +scale-info = { version = "2.1.1", default-features = false } schnorrkel = { version = "0.9.1", default-features = false, features = ["preaudit_deprecated", "u64_backend"] } sp-core = { version = "6.0.0", default-features = false, path = "../../core" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../runtime" } diff --git a/primitives/core/Cargo.toml b/primitives/core/Cargo.toml index 852812c1c5659..34727f8fb6b78 100644 --- a/primitives/core/Cargo.toml +++ b/primitives/core/Cargo.toml @@ -17,7 +17,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = "derive", "max-encoded-len", ] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } log = { version = "0.4.17", default-features = false } serde = { version = "1.0.136", optional = true, features = ["derive"] } byteorder = { version = "1.3.2", default-features = false } diff --git a/primitives/finality-grandpa/Cargo.toml b/primitives/finality-grandpa/Cargo.toml index c8b24c97bbafd..f86e2ab07e673 100644 --- a/primitives/finality-grandpa/Cargo.toml +++ b/primitives/finality-grandpa/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } grandpa = { package = "finality-grandpa", version = "0.15.0", default-features = false, features = ["derive-codec"] } log = { version = "0.4.17", optional = true } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", features = ["derive"], optional = true } sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } sp-application-crypto = { version = "6.0.0", default-features = false, path = "../application-crypto" } diff --git a/primitives/npos-elections/Cargo.toml b/primitives/npos-elections/Cargo.toml index 7cd3ed1489798..db42199a52984 100644 --- a/primitives/npos-elections/Cargo.toml +++ b/primitives/npos-elections/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", features = ["derive"], optional = true } sp-arithmetic = { version = "5.0.0", default-features = false, path = "../arithmetic" } sp-core = { version = "6.0.0", default-features = false, path = "../core" } diff --git a/primitives/npos-elections/fuzzer/Cargo.toml b/primitives/npos-elections/fuzzer/Cargo.toml index a0c420329b3d0..42a80296a4530 100644 --- a/primitives/npos-elections/fuzzer/Cargo.toml +++ b/primitives/npos-elections/fuzzer/Cargo.toml @@ -18,7 +18,7 @@ clap = { version = "3.1.18", features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } honggfuzz = "0.5" rand = { version = "0.8", features = ["std", "small_rng"] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } sp-npos-elections = { version = "4.0.0-dev", path = ".." } sp-runtime = { version = "6.0.0", path = "../../runtime" } diff --git a/primitives/runtime/Cargo.toml b/primitives/runtime/Cargo.toml index ca4ba82e1aee8..8655a9808d5bf 100644 --- a/primitives/runtime/Cargo.toml +++ b/primitives/runtime/Cargo.toml @@ -22,7 +22,7 @@ log = { version = "0.4.17", default-features = false } parity-util-mem = { version = "0.11.0", default-features = false, features = ["primitive-types"] } paste = "1.0" rand = { version = "0.7.2", optional = true } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", features = ["derive"], optional = true } sp-application-crypto = { version = "6.0.0", default-features = false, path = "../application-crypto" } sp-arithmetic = { version = "5.0.0", default-features = false, path = "../arithmetic" } diff --git a/primitives/session/Cargo.toml b/primitives/session/Cargo.toml index b45e7131d5fee..fb04b06e75327 100644 --- a/primitives/session/Cargo.toml +++ b/primitives/session/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } sp-core = { version = "6.0.0", default-features = false, path = "../core" } sp-runtime = { version = "6.0.0", optional = true, path = "../runtime" } diff --git a/primitives/staking/Cargo.toml b/primitives/staking/Cargo.toml index ac6c9d0e6428c..7afc13d7c5723 100644 --- a/primitives/staking/Cargo.toml +++ b/primitives/staking/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } sp-runtime = { version = "6.0.0", default-features = false, path = "../runtime" } sp-std = { version = "4.0.0", default-features = false, path = "../std" } diff --git a/primitives/transaction-storage-proof/Cargo.toml b/primitives/transaction-storage-proof/Cargo.toml index 9501f86cca055..a5a80528c6734 100644 --- a/primitives/transaction-storage-proof/Cargo.toml +++ b/primitives/transaction-storage-proof/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] async-trait = { version = "0.1.50", optional = true } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } log = { version = "0.4.17", optional = true } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } sp-core = { version = "6.0.0", optional = true, path = "../core" } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../inherents" } sp-runtime = { version = "6.0.0", default-features = false, path = "../runtime" } diff --git a/primitives/trie/Cargo.toml b/primitives/trie/Cargo.toml index 60dd88b187bd2..a3754461f890e 100644 --- a/primitives/trie/Cargo.toml +++ b/primitives/trie/Cargo.toml @@ -21,7 +21,7 @@ harness = false codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } hash-db = { version = "0.15.2", default-features = false } memory-db = { version = "0.29.0", default-features = false } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } thiserror = { version = "1.0.30", optional = true } trie-db = { version = "0.23.1", default-features = false } trie-root = { version = "0.17.0", default-features = false } diff --git a/primitives/version/Cargo.toml b/primitives/version/Cargo.toml index 8e48891a3c74e..0ca78940fbbbc 100644 --- a/primitives/version/Cargo.toml +++ b/primitives/version/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } impl-serde = { version = "0.3.1", optional = true } parity-wasm = { version = "0.42.2", optional = true } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", features = ["derive"], optional = true } thiserror = { version = "1.0.30", optional = true } sp-core-hashing-proc-macro = { version = "5.0.0", path = "../core/hashing/proc-macro" } diff --git a/test-utils/runtime/Cargo.toml b/test-utils/runtime/Cargo.toml index 8c529bb6a00a9..739be9ec8bdd8 100644 --- a/test-utils/runtime/Cargo.toml +++ b/test-utils/runtime/Cargo.toml @@ -19,7 +19,7 @@ sp-consensus-aura = { version = "0.10.0-dev", default-features = false, path = " sp-consensus-babe = { version = "0.10.0-dev", default-features = false, path = "../../primitives/consensus/babe" } sp-block-builder = { version = "4.0.0-dev", default-features = false, path = "../../primitives/block-builder" } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../../primitives/inherents" } sp-keyring = { version = "6.0.0", optional = true, path = "../../primitives/keyring" } memory-db = { version = "0.29.0", default-features = false } diff --git a/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml b/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml index 8fe0ad8d61d6e..f4fbb1acbc0b1 100644 --- a/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml +++ b/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } serde = { version = "1", features = ["derive"] } log = { version = "0.4.17", default-features = false } diff --git a/utils/frame/rpc/support/Cargo.toml b/utils/frame/rpc/support/Cargo.toml index 262da29b01d9a..b19c3f235c7c7 100644 --- a/utils/frame/rpc/support/Cargo.toml +++ b/utils/frame/rpc/support/Cargo.toml @@ -24,7 +24,7 @@ sc-rpc-api = { version = "0.10.0-dev", path = "../../../../client/rpc-api" } sp-storage = { version = "6.0.0", path = "../../../../primitives/storage" } [dev-dependencies] -scale-info = "2.0.1" +scale-info = "2.1.1" jsonrpsee = { version = "0.13.0", features = ["ws-client", "jsonrpsee-types"] } tokio = "1.17.0" frame-system = { version = "4.0.0-dev", path = "../../../../frame/system" } From 1d251c21a6469625ba523d6b70b655e506d387eb Mon Sep 17 00:00:00 2001 From: yjh Date: Mon, 16 May 2022 16:25:34 +0800 Subject: [PATCH 221/484] remove unused error InvalidCode and improve docs (#11383) --- client/executor/common/src/error.rs | 3 --- client/executor/src/wasm_runtime.rs | 6 +----- frame/executive/src/lib.rs | 2 +- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/client/executor/common/src/error.rs b/client/executor/common/src/error.rs index 5ffcafd7e92c4..09e070bb3bae5 100644 --- a/client/executor/common/src/error.rs +++ b/client/executor/common/src/error.rs @@ -43,9 +43,6 @@ pub enum Error { #[error("Method not found: '{0}'")] MethodNotFound(String), - #[error("Invalid Code (expected single byte): '{0}'")] - InvalidCode(String), - #[error("On-chain runtime does not specify version")] VersionInvalid, diff --git a/client/executor/src/wasm_runtime.rs b/client/executor/src/wasm_runtime.rs index 9c07dd7abf1f2..85c33ecacf8ff 100644 --- a/client/executor/src/wasm_runtime.rs +++ b/client/executor/src/wasm_runtime.rs @@ -191,8 +191,6 @@ impl RuntimeCache { /// This uses internal cache to find available instance or create a new one. /// # Parameters /// - /// `code` - Provides external code or tells the executor to fetch it from storage. - /// /// `runtime_code` - The runtime wasm code used setup the runtime. /// /// `default_heap_pages` - Number of 64KB pages to allocate for Wasm execution. @@ -201,8 +199,6 @@ impl RuntimeCache { /// /// `allow_missing_func_imports` - Ignore missing function imports. /// - /// `max_runtime_instances` - The size of the instances cache. - /// /// `f` - Function to execute. /// /// `H` - A compile-time list of host functions to expose to the runtime. @@ -210,7 +206,7 @@ impl RuntimeCache { /// # Returns result of `f` wrapped in an additional result. /// In case of failure one of two errors can be returned: /// - /// `Err::InvalidCode` is returned for runtime code issues. + /// `Err::RuntimeConstruction` is returned for runtime construction issues. /// /// `Error::InvalidMemoryReference` is returned if no memory export with the /// identifier `memory` can be found in the runtime. diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index f1e7485d7440c..4affc26f831fd 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -1299,7 +1299,7 @@ mod tests { sp_version::RuntimeVersion { spec_version: 1, ..Default::default() } }); - // set block number to non zero so events are not exlcuded + // set block number to non zero so events are not excluded System::set_block_number(1); Executive::initialize_block(&Header::new( From 06e53868ae6d2c79627f7b35786d8e795dd2b4ff Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 May 2022 10:01:12 +0000 Subject: [PATCH 222/484] Bump ss58-registry from 1.17.0 to 1.18.0 (#11419) Bumps [ss58-registry](https://github.com/paritytech/ss58-registry) from 1.17.0 to 1.18.0. - [Release notes](https://github.com/paritytech/ss58-registry/releases) - [Changelog](https://github.com/paritytech/ss58-registry/blob/main/CHANGELOG.md) - [Commits](https://github.com/paritytech/ss58-registry/compare/v1.17.0...v1.18.0) --- updated-dependencies: - dependency-name: ss58-registry dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- primitives/core/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5d86af6c74262..4733f33fd87d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10366,9 +10366,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "ss58-registry" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b84a70894df7a73666e0694f44b41a9571625e9546fb58a0818a565d2c7e084" +checksum = "ceb8b72a924ccfe7882d0e26144c114503760a4d1248bb5cd06c8ab2d55404cc" dependencies = [ "Inflector", "num-format", diff --git a/primitives/core/Cargo.toml b/primitives/core/Cargo.toml index 34727f8fb6b78..cdc12c677e4f3 100644 --- a/primitives/core/Cargo.toml +++ b/primitives/core/Cargo.toml @@ -57,7 +57,7 @@ hex = { version = "0.4", default-features = false, optional = true } libsecp256k1 = { version = "0.7", default-features = false, features = ["static-context"], optional = true } merlin = { version = "2.0", default-features = false, optional = true } secp256k1 = { version = "0.21.2", default-features = false, features = ["recovery", "alloc"], optional = true } -ss58-registry = { version = "1.17.0", default-features = false } +ss58-registry = { version = "1.18.0", default-features = false } sp-core-hashing = { version = "4.0.0", path = "./hashing", default-features = false, optional = true } sp-runtime-interface = { version = "6.0.0", default-features = false, path = "../runtime-interface" } From d4a572ffc1a79a81456ced678521ba3a36a62beb Mon Sep 17 00:00:00 2001 From: Dmitriy Shabanov Date: Mon, 16 May 2022 16:03:22 +0500 Subject: [PATCH 223/484] serde::{Serialize, Deserialize} for BoundedVec (#11314) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * serde::{Serialize, Deserialize} for BoundedVec * Apply suggestions from code review * FMT * :facepalm: Co-authored-by: Bastian Köcher Co-authored-by: Bastian Köcher --- Cargo.lock | 1 + frame/support/Cargo.toml | 1 + frame/support/src/storage/bounded_vec.rs | 92 +++++++++++++++++++++++- 3 files changed, 93 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 4733f33fd87d2..e77daaafd0778 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2296,6 +2296,7 @@ dependencies = [ "pretty_assertions", "scale-info", "serde", + "serde_json", "smallvec", "sp-arithmetic", "sp-core", diff --git a/frame/support/Cargo.toml b/frame/support/Cargo.toml index 7d88f7fe8eb6f..ca26d3a5e32f2 100644 --- a/frame/support/Cargo.toml +++ b/frame/support/Cargo.toml @@ -38,6 +38,7 @@ sp-core-hashing-proc-macro = { version = "5.0.0", path = "../../primitives/core/ k256 = { version = "0.10.4", default-features = false, features = ["ecdsa"] } [dev-dependencies] +serde_json = "1.0.79" assert_matches = "1.3.0" pretty_assertions = "1.2.1" frame-system = { version = "4.0.0-dev", path = "../system" } diff --git a/frame/support/src/storage/bounded_vec.rs b/frame/support/src/storage/bounded_vec.rs index 92ca167f98436..e99ec6850b9f4 100644 --- a/frame/support/src/storage/bounded_vec.rs +++ b/frame/support/src/storage/bounded_vec.rs @@ -28,6 +28,11 @@ use core::{ ops::{Deref, Index, IndexMut, RangeBounds}, slice::SliceIndex, }; +#[cfg(feature = "std")] +use serde::{ + de::{Error, SeqAccess, Visitor}, + Deserialize, Deserializer, Serialize, +}; use sp_std::{marker::PhantomData, prelude::*}; /// A bounded vector. @@ -37,9 +42,67 @@ use sp_std::{marker::PhantomData, prelude::*}; /// /// As the name suggests, the length of the queue is always bounded. All internal operations ensure /// this bound is respected. +#[cfg_attr(feature = "std", derive(Serialize), serde(transparent))] #[derive(Encode, scale_info::TypeInfo)] #[scale_info(skip_type_params(S))] -pub struct BoundedVec(Vec, PhantomData); +pub struct BoundedVec( + Vec, + #[cfg_attr(feature = "std", serde(skip_serializing))] PhantomData, +); + +#[cfg(feature = "std")] +impl<'de, T, S: Get> Deserialize<'de> for BoundedVec +where + T: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct VecVisitor>(PhantomData<(T, S)>); + + impl<'de, T, S: Get> Visitor<'de> for VecVisitor + where + T: Deserialize<'de>, + { + type Value = Vec; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a sequence") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let size = seq.size_hint().unwrap_or(0); + let max = match usize::try_from(S::get()) { + Ok(n) => n, + Err(_) => return Err(A::Error::custom("can't convert to usize")), + }; + if size > max { + Err(A::Error::custom("out of bounds")) + } else { + let mut values = Vec::with_capacity(size); + + while let Some(value) = seq.next_element()? { + values.push(value); + if values.len() > max { + return Err(A::Error::custom("out of bounds")) + } + } + + Ok(values) + } + } + } + + let visitor: VecVisitor = VecVisitor(PhantomData); + deserializer + .deserialize_seq(visitor) + .map(|v| BoundedVec::::try_from(v).map_err(|_| Error::custom("out of bounds")))? + } +} /// A bounded slice. /// @@ -908,4 +971,31 @@ pub mod test { assert!(b.try_extend(vec![4, 5, 6].into_iter()).is_err()); assert_eq!(*b, vec![1, 2, 3]); } + + #[test] + fn test_serializer() { + let c: BoundedVec> = bounded_vec![0, 1, 2]; + assert_eq!(serde_json::json!(&c).to_string(), r#"[0,1,2]"#); + } + + #[test] + fn test_deserializer() { + let c: BoundedVec> = serde_json::from_str(r#"[0,1,2]"#).unwrap(); + + assert_eq!(c.len(), 3); + assert_eq!(c[0], 0); + assert_eq!(c[1], 1); + assert_eq!(c[2], 2); + } + + #[test] + fn test_deserializer_failed() { + let c: Result>, serde_json::error::Error> = + serde_json::from_str(r#"[0,1,2,3,4,5]"#); + + match c { + Err(msg) => assert_eq!(msg.to_string(), "out of bounds at line 1 column 11"), + _ => unreachable!("deserializer must raise error"), + } + } } From e71fce15bd159392912327d695e8f02b6524f67b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 May 2022 13:09:12 +0200 Subject: [PATCH 224/484] Bump prost from 0.9.0 to 0.10.3 (#11425) Bumps [prost](https://github.com/tokio-rs/prost) from 0.9.0 to 0.10.3. - [Release notes](https://github.com/tokio-rs/prost/releases) - [Commits](https://github.com/tokio-rs/prost/compare/v0.9.0...v0.10.3) --- updated-dependencies: - dependency-name: prost dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 57 +++++++++++++++++++-------- client/authority-discovery/Cargo.toml | 2 +- client/network/Cargo.toml | 2 +- client/network/light/Cargo.toml | 2 +- client/network/sync/Cargo.toml | 2 +- 5 files changed, 44 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e77daaafd0778..201feb7e06d7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3515,7 +3515,7 @@ dependencies = [ "libp2p-request-response", "libp2p-swarm", "log", - "prost", + "prost 0.9.0", "prost-build", "rand 0.8.4", ] @@ -3542,7 +3542,7 @@ dependencies = [ "multistream-select", "parking_lot 0.12.0", "pin-project 1.0.10", - "prost", + "prost 0.9.0", "prost-build", "rand 0.8.4", "ring", @@ -3592,7 +3592,7 @@ dependencies = [ "libp2p-core", "libp2p-swarm", "log", - "prost", + "prost 0.9.0", "prost-build", "rand 0.7.3", "smallvec", @@ -3616,7 +3616,7 @@ dependencies = [ "libp2p-swarm", "log", "prometheus-client", - "prost", + "prost 0.9.0", "prost-build", "rand 0.7.3", "regex", @@ -3638,7 +3638,7 @@ dependencies = [ "libp2p-swarm", "log", "lru", - "prost", + "prost 0.9.0", "prost-build", "smallvec", ] @@ -3660,7 +3660,7 @@ dependencies = [ "libp2p-core", "libp2p-swarm", "log", - "prost", + "prost 0.9.0", "prost-build", "rand 0.7.3", "sha2 0.10.2", @@ -3738,7 +3738,7 @@ dependencies = [ "lazy_static", "libp2p-core", "log", - "prost", + "prost 0.9.0", "prost-build", "rand 0.8.4", "sha2 0.10.2", @@ -3775,7 +3775,7 @@ dependencies = [ "futures", "libp2p-core", "log", - "prost", + "prost 0.9.0", "prost-build", "unsigned-varint", "void", @@ -3811,7 +3811,7 @@ dependencies = [ "libp2p-swarm", "log", "pin-project 1.0.10", - "prost", + "prost 0.9.0", "prost-build", "rand 0.8.4", "smallvec", @@ -3835,7 +3835,7 @@ dependencies = [ "libp2p-core", "libp2p-swarm", "log", - "prost", + "prost 0.9.0", "prost-build", "rand 0.8.4", "sha2 0.10.2", @@ -7013,7 +7013,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" dependencies = [ "bytes", - "prost-derive", + "prost-derive 0.9.0", +] + +[[package]] +name = "prost" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc03e116981ff7d8da8e5c220e374587b98d294af7ba7dd7fda761158f00086f" +dependencies = [ + "bytes", + "prost-derive 0.10.1", ] [[package]] @@ -7029,7 +7039,7 @@ dependencies = [ "log", "multimap", "petgraph", - "prost", + "prost 0.9.0", "prost-types", "regex", "tempfile", @@ -7049,6 +7059,19 @@ dependencies = [ "syn", ] +[[package]] +name = "prost-derive" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b670f45da57fb8542ebdbb6105a925fe571b67f9e7ed9f47a06a84e72b4e7cc" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "prost-types" version = "0.9.0" @@ -7056,7 +7079,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" dependencies = [ "bytes", - "prost", + "prost 0.9.0", ] [[package]] @@ -7861,7 +7884,7 @@ dependencies = [ "libp2p", "log", "parity-scale-codec", - "prost", + "prost 0.10.3", "prost-build", "quickcheck", "rand 0.7.3", @@ -8509,7 +8532,7 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.0", "pin-project 1.0.10", - "prost", + "prost 0.10.3", "prost-build", "quickcheck", "rand 0.7.3", @@ -8581,7 +8604,7 @@ dependencies = [ "libp2p", "log", "parity-scale-codec", - "prost", + "prost 0.10.3", "prost-build", "sc-client-api", "sc-network-common", @@ -8604,7 +8627,7 @@ dependencies = [ "log", "lru", "parity-scale-codec", - "prost", + "prost 0.10.3", "prost-build", "quickcheck", "sc-block-builder", diff --git a/client/authority-discovery/Cargo.toml b/client/authority-discovery/Cargo.toml index 2545447001511..96c07b5dae278 100644 --- a/client/authority-discovery/Cargo.toml +++ b/client/authority-discovery/Cargo.toml @@ -24,7 +24,7 @@ futures-timer = "3.0.1" ip_network = "0.4.1" libp2p = { version = "0.44.0", default-features = false, features = ["kad"] } log = "0.4.17" -prost = "0.9" +prost = "0.10" rand = "0.7.2" thiserror = "1.0" prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } diff --git a/client/network/Cargo.toml b/client/network/Cargo.toml index 4d6af584d8610..f222c39673516 100644 --- a/client/network/Cargo.toml +++ b/client/network/Cargo.toml @@ -36,7 +36,7 @@ log = "0.4.17" lru = "0.7.5" parking_lot = "0.12.0" pin-project = "1.0.10" -prost = "0.9" +prost = "0.10" rand = "0.7.2" serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0.79" diff --git a/client/network/light/Cargo.toml b/client/network/light/Cargo.toml index 8bbbce9274c0c..aa0b003e00763 100644 --- a/client/network/light/Cargo.toml +++ b/client/network/light/Cargo.toml @@ -23,7 +23,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", features = [ futures = "0.3.21" libp2p = "0.44.0" log = "0.4.16" -prost = "0.9" +prost = "0.10" sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } sc-client-api = { version = "4.0.0-dev", path = "../../api" } sc-network-common = { version = "0.10.0-dev", path = "../common" } diff --git a/client/network/sync/Cargo.toml b/client/network/sync/Cargo.toml index e570310ceb955..c557c9bf42b92 100644 --- a/client/network/sync/Cargo.toml +++ b/client/network/sync/Cargo.toml @@ -26,7 +26,7 @@ futures = "0.3.21" libp2p = "0.44.0" log = "0.4.17" lru = "0.7.5" -prost = "0.9" +prost = "0.10" smallvec = "1.8.0" thiserror = "1.0" fork-tree = { version = "3.0.0", path = "../../../utils/fork-tree" } From c01e9f2ddbddf92c064343228e749d8af3f3cdee Mon Sep 17 00:00:00 2001 From: Koute Date: Mon, 16 May 2022 21:25:15 +0900 Subject: [PATCH 225/484] Make sure the commit hash is always of the same length in `impl_version` (#11404) * Make sure the commit hash is always of the same length in `impl_version` * Add a comment regarding the length of the commit hash --- utils/build-script-utils/src/version.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/utils/build-script-utils/src/version.rs b/utils/build-script-utils/src/version.rs index 9432f23e9b8ee..19b507ba261e0 100644 --- a/utils/build-script-utils/src/version.rs +++ b/utils/build-script-utils/src/version.rs @@ -22,7 +22,10 @@ pub fn generate_cargo_keys() { let commit = if let Ok(hash) = std::env::var("SUBSTRATE_CLI_GIT_COMMIT_HASH") { Cow::from(hash.trim().to_owned()) } else { - match Command::new("git").args(&["rev-parse", "--short", "HEAD"]).output() { + // We deliberately set the length here to `11` to ensure that + // the emitted hash is always of the same length; otherwise + // it can (and will!) vary between different build environments. + match Command::new("git").args(&["rev-parse", "--short=11", "HEAD"]).output() { Ok(o) if o.status.success() => { let sha = String::from_utf8_lossy(&o.stdout).trim().to_owned(); Cow::from(sha) From 0ecf397d316ebd782b70f1a176f930091032be48 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Mon, 16 May 2022 15:38:12 +0200 Subject: [PATCH 226/484] Use 'Items' and 'Collections' in uniques pallet (#11389) * Rename class to collection * Use "assets collection" instead of "asset collection" * Rename 'instance' to 'asset' * Change "asset `collection`" to "`collection`" * A bit more clean up * Rename Asset to Item * Add a storage hack * Typos * fix compile * fmt * Fix * cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_uniques --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/uniques/src/weights.rs --template=./.maintain/frame-weight-template.hbs * Update frame/uniques/src/lib.rs * cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_uniques --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/uniques/src/weights.rs --template=./.maintain/frame-weight-template.hbs * Change 'items collection' to 'collection' * Apply suggestions Co-authored-by: Shawn Tabrizi Co-authored-by: Parity Bot --- bin/node/runtime/src/lib.rs | 8 +- frame/support/src/traits/tokens/misc.rs | 8 +- .../support/src/traits/tokens/nonfungible.rs | 125 ++- .../support/src/traits/tokens/nonfungibles.rs | 172 ++-- frame/uniques/src/benchmarking.rs | 264 ++--- frame/uniques/src/functions.rs | 180 ++-- frame/uniques/src/impl_nonfungibles.rs | 114 +-- frame/uniques/src/lib.rs | 898 +++++++++--------- frame/uniques/src/migration.rs | 4 +- frame/uniques/src/mock.rs | 10 +- frame/uniques/src/tests.rs | 182 ++-- frame/uniques/src/types.rs | 72 +- frame/uniques/src/weights.rs | 165 ++-- 13 files changed, 1120 insertions(+), 1082 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 019c14b2c7dfd..1257aabb9d5cc 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1414,12 +1414,12 @@ parameter_types! { impl pallet_uniques::Config for Runtime { type Event = Event; - type ClassId = u32; - type InstanceId = u32; + type CollectionId = u32; + type ItemId = u32; type Currency = Balances; type ForceOrigin = frame_system::EnsureRoot; - type ClassDeposit = ClassDeposit; - type InstanceDeposit = InstanceDeposit; + type CollectionDeposit = ClassDeposit; + type ItemDeposit = InstanceDeposit; type MetadataDepositBase = MetadataDepositBase; type AttributeDepositBase = MetadataDepositBase; type DepositPerByte = MetadataDepositPerByte; diff --git a/frame/support/src/traits/tokens/misc.rs b/frame/support/src/traits/tokens/misc.rs index dd87fe36cd019..fbbef6c31822f 100644 --- a/frame/support/src/traits/tokens/misc.rs +++ b/frame/support/src/traits/tokens/misc.rs @@ -182,16 +182,16 @@ pub trait BalanceConversion { /// Trait to handle asset locking mechanism to ensure interactions with the asset can be implemented /// downstream to extend logic of Uniques current functionality. -pub trait Locker { +pub trait Locker { /// Check if the asset should be locked and prevent interactions with the asset from executing. - fn is_locked(class: ClassId, instance: InstanceId) -> bool; + fn is_locked(collection: CollectionId, item: ItemId) -> bool; } -impl Locker for () { +impl Locker for () { // Default will be false if not implemented downstream. // Note: The logic check in this function must be constant time and consistent for benchmarks // to work. - fn is_locked(_class: ClassId, _instance: InstanceId) -> bool { + fn is_locked(_collection: CollectionId, _item: ItemId) -> bool { false } } diff --git a/frame/support/src/traits/tokens/nonfungible.rs b/frame/support/src/traits/tokens/nonfungible.rs index 5cf9638131b28..fe0d2e729930e 100644 --- a/frame/support/src/traits/tokens/nonfungible.rs +++ b/frame/support/src/traits/tokens/nonfungible.rs @@ -15,9 +15,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Traits for dealing with a single non-fungible asset class. +//! Traits for dealing with a single non-fungible collection of items. //! -//! This assumes a single level namespace identified by `Inspect::InstanceId`, and could +//! This assumes a single level namespace identified by `Inspect::ItemId`, and could //! reasonably be implemented by pallets which wants to expose a single collection of NFT-like //! objects. //! @@ -30,167 +30,164 @@ use codec::{Decode, Encode}; use sp_runtime::TokenError; use sp_std::prelude::*; -/// Trait for providing an interface to a read-only NFT-like set of asset instances. +/// Trait for providing an interface to a read-only NFT-like set of items. pub trait Inspect { - /// Type for identifying an asset instance. - type InstanceId; + /// Type for identifying an item. + type ItemId; - /// Returns the owner of asset `instance`, or `None` if the asset doesn't exist or has no + /// Returns the owner of `item`, or `None` if the item doesn't exist or has no /// owner. - fn owner(instance: &Self::InstanceId) -> Option; + fn owner(item: &Self::ItemId) -> Option; - /// Returns the attribute value of `instance` corresponding to `key`. + /// Returns the attribute value of `item` corresponding to `key`. /// /// By default this is `None`; no attributes are defined. - fn attribute(_instance: &Self::InstanceId, _key: &[u8]) -> Option> { + fn attribute(_item: &Self::ItemId, _key: &[u8]) -> Option> { None } - /// Returns the strongly-typed attribute value of `instance` corresponding to `key`. + /// Returns the strongly-typed attribute value of `item` corresponding to `key`. /// /// By default this just attempts to use `attribute`. - fn typed_attribute(instance: &Self::InstanceId, key: &K) -> Option { - key.using_encoded(|d| Self::attribute(instance, d)) + fn typed_attribute(item: &Self::ItemId, key: &K) -> Option { + key.using_encoded(|d| Self::attribute(item, d)) .and_then(|v| V::decode(&mut &v[..]).ok()) } - /// Returns `true` if the asset `instance` may be transferred. + /// Returns `true` if the `item` may be transferred. /// - /// Default implementation is that all assets are transferable. - fn can_transfer(_instance: &Self::InstanceId) -> bool { + /// Default implementation is that all items are transferable. + fn can_transfer(_item: &Self::ItemId) -> bool { true } } -/// Interface for enumerating assets in existence or owned by a given account over a collection +/// Interface for enumerating items in existence or owned by a given account over a collection /// of NFTs. pub trait InspectEnumerable: Inspect { - /// Returns an iterator of the instances of an asset `class` in existence. - fn instances() -> Box>; + /// Returns an iterator of the items within a `collection` in existence. + fn items() -> Box>; - /// Returns an iterator of the asset instances of all classes owned by `who`. - fn owned(who: &AccountId) -> Box>; + /// Returns an iterator of the items of all collections owned by `who`. + fn owned(who: &AccountId) -> Box>; } -/// Trait for providing an interface for NFT-like assets which may be minted, burned and/or have +/// Trait for providing an interface for NFT-like items which may be minted, burned and/or have /// attributes set on them. pub trait Mutate: Inspect { - /// Mint some asset `instance` to be owned by `who`. + /// Mint some `item` to be owned by `who`. /// /// By default, this is not a supported operation. - fn mint_into(_instance: &Self::InstanceId, _who: &AccountId) -> DispatchResult { + fn mint_into(_item: &Self::ItemId, _who: &AccountId) -> DispatchResult { Err(TokenError::Unsupported.into()) } - /// Burn some asset `instance`. + /// Burn some `item`. /// /// By default, this is not a supported operation. - fn burn( - _instance: &Self::InstanceId, - _maybe_check_owner: Option<&AccountId>, - ) -> DispatchResult { + fn burn(_item: &Self::ItemId, _maybe_check_owner: Option<&AccountId>) -> DispatchResult { Err(TokenError::Unsupported.into()) } - /// Set attribute `value` of asset `instance`'s `key`. + /// Set attribute `value` of `item`'s `key`. /// /// By default, this is not a supported operation. - fn set_attribute(_instance: &Self::InstanceId, _key: &[u8], _value: &[u8]) -> DispatchResult { + fn set_attribute(_item: &Self::ItemId, _key: &[u8], _value: &[u8]) -> DispatchResult { Err(TokenError::Unsupported.into()) } - /// Attempt to set the strongly-typed attribute `value` of `instance`'s `key`. + /// Attempt to set the strongly-typed attribute `value` of `item`'s `key`. /// /// By default this just attempts to use `set_attribute`. fn set_typed_attribute( - instance: &Self::InstanceId, + item: &Self::ItemId, key: &K, value: &V, ) -> DispatchResult { - key.using_encoded(|k| value.using_encoded(|v| Self::set_attribute(instance, k, v))) + key.using_encoded(|k| value.using_encoded(|v| Self::set_attribute(item, k, v))) } } -/// Trait for providing a non-fungible set of assets which can only be transferred. +/// Trait for providing a non-fungible set of items which can only be transferred. pub trait Transfer: Inspect { - /// Transfer asset `instance` into `destination` account. - fn transfer(instance: &Self::InstanceId, destination: &AccountId) -> DispatchResult; + /// Transfer `item` into `destination` account. + fn transfer(item: &Self::ItemId, destination: &AccountId) -> DispatchResult; } /// Convert a `fungibles` trait implementation into a `fungible` trait implementation by identifying /// a single item. pub struct ItemOf< F: nonfungibles::Inspect, - A: Get<>::ClassId>, + A: Get<>::CollectionId>, AccountId, >(sp_std::marker::PhantomData<(F, A, AccountId)>); impl< F: nonfungibles::Inspect, - A: Get<>::ClassId>, + A: Get<>::CollectionId>, AccountId, > Inspect for ItemOf { - type InstanceId = >::InstanceId; - fn owner(instance: &Self::InstanceId) -> Option { - >::owner(&A::get(), instance) + type ItemId = >::ItemId; + fn owner(item: &Self::ItemId) -> Option { + >::owner(&A::get(), item) } - fn attribute(instance: &Self::InstanceId, key: &[u8]) -> Option> { - >::attribute(&A::get(), instance, key) + fn attribute(item: &Self::ItemId, key: &[u8]) -> Option> { + >::attribute(&A::get(), item, key) } - fn typed_attribute(instance: &Self::InstanceId, key: &K) -> Option { - >::typed_attribute(&A::get(), instance, key) + fn typed_attribute(item: &Self::ItemId, key: &K) -> Option { + >::typed_attribute(&A::get(), item, key) } - fn can_transfer(instance: &Self::InstanceId) -> bool { - >::can_transfer(&A::get(), instance) + fn can_transfer(item: &Self::ItemId) -> bool { + >::can_transfer(&A::get(), item) } } impl< F: nonfungibles::InspectEnumerable, - A: Get<>::ClassId>, + A: Get<>::CollectionId>, AccountId, > InspectEnumerable for ItemOf { - fn instances() -> Box> { - >::instances(&A::get()) + fn items() -> Box> { + >::items(&A::get()) } - fn owned(who: &AccountId) -> Box> { - >::owned_in_class(&A::get(), who) + fn owned(who: &AccountId) -> Box> { + >::owned_in_collection(&A::get(), who) } } impl< F: nonfungibles::Mutate, - A: Get<>::ClassId>, + A: Get<>::CollectionId>, AccountId, > Mutate for ItemOf { - fn mint_into(instance: &Self::InstanceId, who: &AccountId) -> DispatchResult { - >::mint_into(&A::get(), instance, who) + fn mint_into(item: &Self::ItemId, who: &AccountId) -> DispatchResult { + >::mint_into(&A::get(), item, who) } - fn burn(instance: &Self::InstanceId, maybe_check_owner: Option<&AccountId>) -> DispatchResult { - >::burn(&A::get(), instance, maybe_check_owner) + fn burn(item: &Self::ItemId, maybe_check_owner: Option<&AccountId>) -> DispatchResult { + >::burn(&A::get(), item, maybe_check_owner) } - fn set_attribute(instance: &Self::InstanceId, key: &[u8], value: &[u8]) -> DispatchResult { - >::set_attribute(&A::get(), instance, key, value) + fn set_attribute(item: &Self::ItemId, key: &[u8], value: &[u8]) -> DispatchResult { + >::set_attribute(&A::get(), item, key, value) } fn set_typed_attribute( - instance: &Self::InstanceId, + item: &Self::ItemId, key: &K, value: &V, ) -> DispatchResult { - >::set_typed_attribute(&A::get(), instance, key, value) + >::set_typed_attribute(&A::get(), item, key, value) } } impl< F: nonfungibles::Transfer, - A: Get<>::ClassId>, + A: Get<>::CollectionId>, AccountId, > Transfer for ItemOf { - fn transfer(instance: &Self::InstanceId, destination: &AccountId) -> DispatchResult { - >::transfer(&A::get(), instance, destination) + fn transfer(item: &Self::ItemId, destination: &AccountId) -> DispatchResult { + >::transfer(&A::get(), item, destination) } } diff --git a/frame/support/src/traits/tokens/nonfungibles.rs b/frame/support/src/traits/tokens/nonfungibles.rs index 8bd731b20342c..d043a87ce7c10 100644 --- a/frame/support/src/traits/tokens/nonfungibles.rs +++ b/frame/support/src/traits/tokens/nonfungibles.rs @@ -15,9 +15,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Traits for dealing with multiple collections of non-fungible assets. +//! Traits for dealing with multiple collections of non-fungible items. //! -//! This assumes a dual-level namespace identified by `Inspect::InstanceId`, and could +//! This assumes a dual-level namespace identified by `Inspect::ItemId`, and could //! reasonably be implemented by pallets which want to expose multiple independent collections of //! NFT-like objects. //! @@ -32,196 +32,210 @@ use codec::{Decode, Encode}; use sp_runtime::TokenError; use sp_std::prelude::*; -/// Trait for providing an interface to many read-only NFT-like sets of asset instances. +/// Trait for providing an interface to many read-only NFT-like sets of items. pub trait Inspect { - /// Type for identifying an asset instance. - type InstanceId; + /// Type for identifying an item. + type ItemId; - /// Type for identifying an asset class (an identifier for an independent collection of asset - /// instances). - type ClassId; + /// Type for identifying a collection (an identifier for an independent collection of + /// items). + type CollectionId; - /// Returns the owner of asset `instance` of `class`, or `None` if the asset doesn't exist (or - /// somehow has no owner). - fn owner(class: &Self::ClassId, instance: &Self::InstanceId) -> Option; + /// Returns the owner of `item` of `collection`, or `None` if the item doesn't exist + /// (or somehow has no owner). + fn owner(collection: &Self::CollectionId, item: &Self::ItemId) -> Option; - /// Returns the owner of the asset `class`, if there is one. For many NFTs this may not make - /// any sense, so users of this API should not be surprised to find an asset class results in - /// `None` here. - fn class_owner(_class: &Self::ClassId) -> Option { + /// Returns the owner of the `collection`, if there is one. For many NFTs this may not + /// make any sense, so users of this API should not be surprised to find a collection + /// results in `None` here. + fn collection_owner(_collection: &Self::CollectionId) -> Option { None } - /// Returns the attribute value of `instance` of `class` corresponding to `key`. + /// Returns the attribute value of `item` of `collection` corresponding to `key`. /// /// By default this is `None`; no attributes are defined. fn attribute( - _class: &Self::ClassId, - _instance: &Self::InstanceId, + _collection: &Self::CollectionId, + _item: &Self::ItemId, _key: &[u8], ) -> Option> { None } - /// Returns the strongly-typed attribute value of `instance` of `class` corresponding to `key`. + /// Returns the strongly-typed attribute value of `item` of `collection` corresponding to + /// `key`. /// /// By default this just attempts to use `attribute`. fn typed_attribute( - class: &Self::ClassId, - instance: &Self::InstanceId, + collection: &Self::CollectionId, + item: &Self::ItemId, key: &K, ) -> Option { - key.using_encoded(|d| Self::attribute(class, instance, d)) + key.using_encoded(|d| Self::attribute(collection, item, d)) .and_then(|v| V::decode(&mut &v[..]).ok()) } - /// Returns the attribute value of `class` corresponding to `key`. + /// Returns the attribute value of `collection` corresponding to `key`. /// /// By default this is `None`; no attributes are defined. - fn class_attribute(_class: &Self::ClassId, _key: &[u8]) -> Option> { + fn collection_attribute(_collection: &Self::CollectionId, _key: &[u8]) -> Option> { None } - /// Returns the strongly-typed attribute value of `class` corresponding to `key`. + /// Returns the strongly-typed attribute value of `collection` corresponding to `key`. /// - /// By default this just attempts to use `class_attribute`. - fn typed_class_attribute(class: &Self::ClassId, key: &K) -> Option { - key.using_encoded(|d| Self::class_attribute(class, d)) + /// By default this just attempts to use `collection_attribute`. + fn typed_collection_attribute( + collection: &Self::CollectionId, + key: &K, + ) -> Option { + key.using_encoded(|d| Self::collection_attribute(collection, d)) .and_then(|v| V::decode(&mut &v[..]).ok()) } - /// Returns `true` if the asset `instance` of `class` may be transferred. + /// Returns `true` if the `item` of `collection` may be transferred. /// - /// Default implementation is that all assets are transferable. - fn can_transfer(_class: &Self::ClassId, _instance: &Self::InstanceId) -> bool { + /// Default implementation is that all items are transferable. + fn can_transfer(_collection: &Self::CollectionId, _item: &Self::ItemId) -> bool { true } } -/// Interface for enumerating assets in existence or owned by a given account over many collections +/// Interface for enumerating items in existence or owned by a given account over many collections /// of NFTs. pub trait InspectEnumerable: Inspect { - /// Returns an iterator of the asset classes in existence. - fn classes() -> Box>; + /// Returns an iterator of the collections in existence. + fn collections() -> Box>; - /// Returns an iterator of the instances of an asset `class` in existence. - fn instances(class: &Self::ClassId) -> Box>; + /// Returns an iterator of the items of a `collection` in existence. + fn items(collection: &Self::CollectionId) -> Box>; - /// Returns an iterator of the asset instances of all classes owned by `who`. - fn owned(who: &AccountId) -> Box>; + /// Returns an iterator of the items of all collections owned by `who`. + fn owned(who: &AccountId) -> Box>; - /// Returns an iterator of the asset instances of `class` owned by `who`. - fn owned_in_class( - class: &Self::ClassId, + /// Returns an iterator of the items of `collection` owned by `who`. + fn owned_in_collection( + collection: &Self::CollectionId, who: &AccountId, - ) -> Box>; + ) -> Box>; } -/// Trait for providing the ability to create classes of nonfungible assets. +/// Trait for providing the ability to create collections of nonfungible items. pub trait Create: Inspect { - /// Create a `class` of nonfungible assets to be owned by `who` and managed by `admin`. - fn create_class(class: &Self::ClassId, who: &AccountId, admin: &AccountId) -> DispatchResult; + /// Create a `collection` of nonfungible items to be owned by `who` and managed by `admin`. + fn create_collection( + collection: &Self::CollectionId, + who: &AccountId, + admin: &AccountId, + ) -> DispatchResult; } -/// Trait for providing the ability to destroy classes of nonfungible assets. +/// Trait for providing the ability to destroy collections of nonfungible items. pub trait Destroy: Inspect { - /// The witness data needed to destroy an asset. + /// The witness data needed to destroy an item. type DestroyWitness; - /// Provide the appropriate witness data needed to destroy an asset. - fn get_destroy_witness(class: &Self::ClassId) -> Option; + /// Provide the appropriate witness data needed to destroy an item. + fn get_destroy_witness(collection: &Self::CollectionId) -> Option; - /// Destroy an existing fungible asset. - /// * `class`: The `ClassId` to be destroyed. + /// Destroy an existing fungible item. + /// * `collection`: The `CollectionId` to be destroyed. /// * `witness`: Any witness data that needs to be provided to complete the operation /// successfully. /// * `maybe_check_owner`: An optional account id that can be used to authorize the destroy /// command. If not provided, we will not do any authorization checks before destroying the - /// asset. + /// item. /// - /// If successful, this function will return the actual witness data from the destroyed asset. + /// If successful, this function will return the actual witness data from the destroyed item. /// This may be different than the witness data provided, and can be used to refund weight. fn destroy( - class: Self::ClassId, + collection: Self::CollectionId, witness: Self::DestroyWitness, maybe_check_owner: Option, ) -> Result; } -/// Trait for providing an interface for multiple classes of NFT-like assets which may be minted, -/// burned and/or have attributes set on them. +/// Trait for providing an interface for multiple collections of NFT-like items which may be +/// minted, burned and/or have attributes set on them. pub trait Mutate: Inspect { - /// Mint some asset `instance` of `class` to be owned by `who`. + /// Mint some `item` of `collection` to be owned by `who`. /// /// By default, this is not a supported operation. fn mint_into( - _class: &Self::ClassId, - _instance: &Self::InstanceId, + _collection: &Self::CollectionId, + _item: &Self::ItemId, _who: &AccountId, ) -> DispatchResult { Err(TokenError::Unsupported.into()) } - /// Burn some asset `instance` of `class`. + /// Burn some `item` of `collection`. /// /// By default, this is not a supported operation. fn burn( - _class: &Self::ClassId, - _instance: &Self::InstanceId, + _collection: &Self::CollectionId, + _item: &Self::ItemId, _maybe_check_owner: Option<&AccountId>, ) -> DispatchResult { Err(TokenError::Unsupported.into()) } - /// Set attribute `value` of asset `instance` of `class`'s `key`. + /// Set attribute `value` of `item` of `collection`'s `key`. /// /// By default, this is not a supported operation. fn set_attribute( - _class: &Self::ClassId, - _instance: &Self::InstanceId, + _collection: &Self::CollectionId, + _item: &Self::ItemId, _key: &[u8], _value: &[u8], ) -> DispatchResult { Err(TokenError::Unsupported.into()) } - /// Attempt to set the strongly-typed attribute `value` of `instance` of `class`'s `key`. + /// Attempt to set the strongly-typed attribute `value` of `item` of `collection`'s `key`. /// /// By default this just attempts to use `set_attribute`. fn set_typed_attribute( - class: &Self::ClassId, - instance: &Self::InstanceId, + collection: &Self::CollectionId, + item: &Self::ItemId, key: &K, value: &V, ) -> DispatchResult { - key.using_encoded(|k| value.using_encoded(|v| Self::set_attribute(class, instance, k, v))) + key.using_encoded(|k| value.using_encoded(|v| Self::set_attribute(collection, item, k, v))) } - /// Set attribute `value` of asset `class`'s `key`. + /// Set attribute `value` of `collection`'s `key`. /// /// By default, this is not a supported operation. - fn set_class_attribute(_class: &Self::ClassId, _key: &[u8], _value: &[u8]) -> DispatchResult { + fn set_collection_attribute( + _collection: &Self::CollectionId, + _key: &[u8], + _value: &[u8], + ) -> DispatchResult { Err(TokenError::Unsupported.into()) } - /// Attempt to set the strongly-typed attribute `value` of `class`'s `key`. + /// Attempt to set the strongly-typed attribute `value` of `collection`'s `key`. /// /// By default this just attempts to use `set_attribute`. - fn set_typed_class_attribute( - class: &Self::ClassId, + fn set_typed_collection_attribute( + collection: &Self::CollectionId, key: &K, value: &V, ) -> DispatchResult { - key.using_encoded(|k| value.using_encoded(|v| Self::set_class_attribute(class, k, v))) + key.using_encoded(|k| { + value.using_encoded(|v| Self::set_collection_attribute(collection, k, v)) + }) } } -/// Trait for providing a non-fungible sets of assets which can only be transferred. +/// Trait for providing a non-fungible sets of items which can only be transferred. pub trait Transfer: Inspect { - /// Transfer asset `instance` of `class` into `destination` account. + /// Transfer `item` of `collection` into `destination` account. fn transfer( - class: &Self::ClassId, - instance: &Self::InstanceId, + collection: &Self::CollectionId, + item: &Self::ItemId, destination: &AccountId, ) -> DispatchResult; } diff --git a/frame/uniques/src/benchmarking.rs b/frame/uniques/src/benchmarking.rs index b743ca12913a3..cd66d03922141 100644 --- a/frame/uniques/src/benchmarking.rs +++ b/frame/uniques/src/benchmarking.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Assets pallet benchmarking. +//! Uniques pallet benchmarking. #![cfg(feature = "runtime-benchmarks")] @@ -36,32 +36,32 @@ use crate::Pallet as Uniques; const SEED: u32 = 0; -fn create_class, I: 'static>( -) -> (T::ClassId, T::AccountId, ::Source) { +fn create_collection, I: 'static>( +) -> (T::CollectionId, T::AccountId, ::Source) { let caller: T::AccountId = whitelisted_caller(); let caller_lookup = T::Lookup::unlookup(caller.clone()); - let class = T::Helper::class(0); + let collection = T::Helper::collection(0); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); assert!(Uniques::::force_create( SystemOrigin::Root.into(), - class, + collection, caller_lookup.clone(), false, ) .is_ok()); - (class, caller, caller_lookup) + (collection, caller, caller_lookup) } -fn add_class_metadata, I: 'static>( +fn add_collection_metadata, I: 'static>( ) -> (T::AccountId, ::Source) { - let caller = Class::::get(T::Helper::class(0)).unwrap().owner; + let caller = Collection::::get(T::Helper::collection(0)).unwrap().owner; if caller != whitelisted_caller() { whitelist_account!(caller); } let caller_lookup = T::Lookup::unlookup(caller.clone()); - assert!(Uniques::::set_class_metadata( + assert!(Uniques::::set_collection_metadata( SystemOrigin::Signed(caller.clone()).into(), - T::Helper::class(0), + T::Helper::collection(0), vec![0; T::StringLimit::get() as usize].try_into().unwrap(), false, ) @@ -69,37 +69,37 @@ fn add_class_metadata, I: 'static>( (caller, caller_lookup) } -fn mint_instance, I: 'static>( +fn mint_item, I: 'static>( index: u16, -) -> (T::InstanceId, T::AccountId, ::Source) { - let caller = Class::::get(T::Helper::class(0)).unwrap().admin; +) -> (T::ItemId, T::AccountId, ::Source) { + let caller = Collection::::get(T::Helper::collection(0)).unwrap().admin; if caller != whitelisted_caller() { whitelist_account!(caller); } let caller_lookup = T::Lookup::unlookup(caller.clone()); - let instance = T::Helper::instance(index); + let item = T::Helper::item(index); assert!(Uniques::::mint( SystemOrigin::Signed(caller.clone()).into(), - T::Helper::class(0), - instance, + T::Helper::collection(0), + item, caller_lookup.clone(), ) .is_ok()); - (instance, caller, caller_lookup) + (item, caller, caller_lookup) } -fn add_instance_metadata, I: 'static>( - instance: T::InstanceId, +fn add_item_metadata, I: 'static>( + item: T::ItemId, ) -> (T::AccountId, ::Source) { - let caller = Class::::get(T::Helper::class(0)).unwrap().owner; + let caller = Collection::::get(T::Helper::collection(0)).unwrap().owner; if caller != whitelisted_caller() { whitelist_account!(caller); } let caller_lookup = T::Lookup::unlookup(caller.clone()); assert!(Uniques::::set_metadata( SystemOrigin::Signed(caller.clone()).into(), - T::Helper::class(0), - instance, + T::Helper::collection(0), + item, vec![0; T::StringLimit::get() as usize].try_into().unwrap(), false, ) @@ -107,10 +107,10 @@ fn add_instance_metadata, I: 'static>( (caller, caller_lookup) } -fn add_instance_attribute, I: 'static>( - instance: T::InstanceId, +fn add_item_attribute, I: 'static>( + item: T::ItemId, ) -> (BoundedVec, T::AccountId, ::Source) { - let caller = Class::::get(T::Helper::class(0)).unwrap().owner; + let caller = Collection::::get(T::Helper::collection(0)).unwrap().owner; if caller != whitelisted_caller() { whitelist_account!(caller); } @@ -118,8 +118,8 @@ fn add_instance_attribute, I: 'static>( let key: BoundedVec<_, _> = vec![0; T::KeyLimit::get() as usize].try_into().unwrap(); assert!(Uniques::::set_attribute( SystemOrigin::Signed(caller.clone()).into(), - T::Helper::class(0), - Some(instance), + T::Helper::collection(0), + Some(item), key.clone(), vec![0; T::ValueLimit::get() as usize].try_into().unwrap(), ) @@ -137,24 +137,24 @@ fn assert_last_event, I: 'static>(generic_event: >:: benchmarks_instance_pallet! { create { - let class = T::Helper::class(0); - let origin = T::CreateOrigin::successful_origin(&class); - let caller = T::CreateOrigin::ensure_origin(origin.clone(), &class).unwrap(); + let collection = T::Helper::collection(0); + let origin = T::CreateOrigin::successful_origin(&collection); + let caller = T::CreateOrigin::ensure_origin(origin.clone(), &collection).unwrap(); whitelist_account!(caller); let admin = T::Lookup::unlookup(caller.clone()); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); - let call = Call::::create { class, admin }; + let call = Call::::create { collection, admin }; }: { call.dispatch_bypass_filter(origin)? } verify { - assert_last_event::(Event::Created { class: T::Helper::class(0), creator: caller.clone(), owner: caller }.into()); + assert_last_event::(Event::Created { collection: T::Helper::collection(0), creator: caller.clone(), owner: caller }.into()); } force_create { let caller: T::AccountId = whitelisted_caller(); let caller_lookup = T::Lookup::unlookup(caller.clone()); - }: _(SystemOrigin::Root, T::Helper::class(0), caller_lookup, true) + }: _(SystemOrigin::Root, T::Helper::collection(0), caller_lookup, true) verify { - assert_last_event::(Event::ForceCreated { class: T::Helper::class(0), owner: caller }.into()); + assert_last_event::(Event::ForceCreated { collection: T::Helper::collection(0), owner: caller }.into()); } destroy { @@ -162,57 +162,57 @@ benchmarks_instance_pallet! { let m in 0 .. 1_000; let a in 0 .. 1_000; - let (class, caller, caller_lookup) = create_class::(); - add_class_metadata::(); + let (collection, caller, caller_lookup) = create_collection::(); + add_collection_metadata::(); for i in 0..n { - mint_instance::(i as u16); + mint_item::(i as u16); } for i in 0..m { - add_instance_metadata::(T::Helper::instance(i as u16)); + add_item_metadata::(T::Helper::item(i as u16)); } for i in 0..a { - add_instance_attribute::(T::Helper::instance(i as u16)); + add_item_attribute::(T::Helper::item(i as u16)); } - let witness = Class::::get(class).unwrap().destroy_witness(); - }: _(SystemOrigin::Signed(caller), class, witness) + let witness = Collection::::get(collection).unwrap().destroy_witness(); + }: _(SystemOrigin::Signed(caller), collection, witness) verify { - assert_last_event::(Event::Destroyed { class }.into()); + assert_last_event::(Event::Destroyed { collection }.into()); } mint { - let (class, caller, caller_lookup) = create_class::(); - let instance = T::Helper::instance(0); - }: _(SystemOrigin::Signed(caller.clone()), class, instance, caller_lookup) + let (collection, caller, caller_lookup) = create_collection::(); + let item = T::Helper::item(0); + }: _(SystemOrigin::Signed(caller.clone()), collection, item, caller_lookup) verify { - assert_last_event::(Event::Issued { class, instance, owner: caller }.into()); + assert_last_event::(Event::Issued { collection, item, owner: caller }.into()); } burn { - let (class, caller, caller_lookup) = create_class::(); - let (instance, ..) = mint_instance::(0); - }: _(SystemOrigin::Signed(caller.clone()), class, instance, Some(caller_lookup)) + let (collection, caller, caller_lookup) = create_collection::(); + let (item, ..) = mint_item::(0); + }: _(SystemOrigin::Signed(caller.clone()), collection, item, Some(caller_lookup)) verify { - assert_last_event::(Event::Burned { class, instance, owner: caller }.into()); + assert_last_event::(Event::Burned { collection, item, owner: caller }.into()); } transfer { - let (class, caller, caller_lookup) = create_class::(); - let (instance, ..) = mint_instance::(0); + let (collection, caller, caller_lookup) = create_collection::(); + let (item, ..) = mint_item::(0); let target: T::AccountId = account("target", 0, SEED); let target_lookup = T::Lookup::unlookup(target.clone()); - }: _(SystemOrigin::Signed(caller.clone()), class, instance, target_lookup) + }: _(SystemOrigin::Signed(caller.clone()), collection, item, target_lookup) verify { - assert_last_event::(Event::Transferred { class, instance, from: caller, to: target }.into()); + assert_last_event::(Event::Transferred { collection, item, from: caller, to: target }.into()); } redeposit { let i in 0 .. 5_000; - let (class, caller, caller_lookup) = create_class::(); - let instances = (0..i).map(|x| mint_instance::(x as u16).0).collect::>(); - Uniques::::force_asset_status( + let (collection, caller, caller_lookup) = create_collection::(); + let items = (0..i).map(|x| mint_item::(x as u16).0).collect::>(); + Uniques::::force_item_status( SystemOrigin::Root.into(), - class, + collection, caller_lookup.clone(), caller_lookup.clone(), caller_lookup.clone(), @@ -220,80 +220,80 @@ benchmarks_instance_pallet! { true, false, )?; - }: _(SystemOrigin::Signed(caller.clone()), class, instances.clone()) + }: _(SystemOrigin::Signed(caller.clone()), collection, items.clone()) verify { - assert_last_event::(Event::Redeposited { class, successful_instances: instances }.into()); + assert_last_event::(Event::Redeposited { collection, successful_items: items }.into()); } freeze { - let (class, caller, caller_lookup) = create_class::(); - let (instance, ..) = mint_instance::(0); - }: _(SystemOrigin::Signed(caller.clone()), T::Helper::class(0), T::Helper::instance(0)) + let (collection, caller, caller_lookup) = create_collection::(); + let (item, ..) = mint_item::(0); + }: _(SystemOrigin::Signed(caller.clone()), T::Helper::collection(0), T::Helper::item(0)) verify { - assert_last_event::(Event::Frozen { class: T::Helper::class(0), instance: T::Helper::instance(0) }.into()); + assert_last_event::(Event::Frozen { collection: T::Helper::collection(0), item: T::Helper::item(0) }.into()); } thaw { - let (class, caller, caller_lookup) = create_class::(); - let (instance, ..) = mint_instance::(0); + let (collection, caller, caller_lookup) = create_collection::(); + let (item, ..) = mint_item::(0); Uniques::::freeze( SystemOrigin::Signed(caller.clone()).into(), - class, - instance, + collection, + item, )?; - }: _(SystemOrigin::Signed(caller.clone()), class, instance) + }: _(SystemOrigin::Signed(caller.clone()), collection, item) verify { - assert_last_event::(Event::Thawed { class, instance }.into()); + assert_last_event::(Event::Thawed { collection, item }.into()); } - freeze_class { - let (class, caller, caller_lookup) = create_class::(); - }: _(SystemOrigin::Signed(caller.clone()), class) + freeze_collection { + let (collection, caller, caller_lookup) = create_collection::(); + }: _(SystemOrigin::Signed(caller.clone()), collection) verify { - assert_last_event::(Event::ClassFrozen { class }.into()); + assert_last_event::(Event::CollectionFrozen { collection }.into()); } - thaw_class { - let (class, caller, caller_lookup) = create_class::(); + thaw_collection { + let (collection, caller, caller_lookup) = create_collection::(); let origin = SystemOrigin::Signed(caller.clone()).into(); - Uniques::::freeze_class(origin, class)?; - }: _(SystemOrigin::Signed(caller.clone()), class) + Uniques::::freeze_collection(origin, collection)?; + }: _(SystemOrigin::Signed(caller.clone()), collection) verify { - assert_last_event::(Event::ClassThawed { class }.into()); + assert_last_event::(Event::CollectionThawed { collection }.into()); } transfer_ownership { - let (class, caller, _) = create_class::(); + let (collection, caller, _) = create_collection::(); let target: T::AccountId = account("target", 0, SEED); let target_lookup = T::Lookup::unlookup(target.clone()); T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance()); let origin = SystemOrigin::Signed(target.clone()).into(); - Uniques::::set_accept_ownership(origin, Some(class))?; - }: _(SystemOrigin::Signed(caller), class, target_lookup) + Uniques::::set_accept_ownership(origin, Some(collection))?; + }: _(SystemOrigin::Signed(caller), collection, target_lookup) verify { - assert_last_event::(Event::OwnerChanged { class, new_owner: target }.into()); + assert_last_event::(Event::OwnerChanged { collection, new_owner: target }.into()); } set_team { - let (class, caller, _) = create_class::(); + let (collection, caller, _) = create_collection::(); let target0 = T::Lookup::unlookup(account("target", 0, SEED)); let target1 = T::Lookup::unlookup(account("target", 1, SEED)); let target2 = T::Lookup::unlookup(account("target", 2, SEED)); - }: _(SystemOrigin::Signed(caller), class, target0, target1, target2) + }: _(SystemOrigin::Signed(caller), collection, target0, target1, target2) verify { assert_last_event::(Event::TeamChanged{ - class, + collection, issuer: account("target", 0, SEED), admin: account("target", 1, SEED), freezer: account("target", 2, SEED), }.into()); } - force_asset_status { - let (class, caller, caller_lookup) = create_class::(); + force_item_status { + let (collection, caller, caller_lookup) = create_collection::(); let origin = T::ForceOrigin::successful_origin(); - let call = Call::::force_asset_status { - class, + let call = Call::::force_item_status { + collection, owner: caller_lookup.clone(), issuer: caller_lookup.clone(), admin: caller_lookup.clone(), @@ -303,98 +303,98 @@ benchmarks_instance_pallet! { }; }: { call.dispatch_bypass_filter(origin)? } verify { - assert_last_event::(Event::AssetStatusChanged { class }.into()); + assert_last_event::(Event::ItemStatusChanged { collection }.into()); } set_attribute { let key: BoundedVec<_, _> = vec![0u8; T::KeyLimit::get() as usize].try_into().unwrap(); let value: BoundedVec<_, _> = vec![0u8; T::ValueLimit::get() as usize].try_into().unwrap(); - let (class, caller, _) = create_class::(); - let (instance, ..) = mint_instance::(0); - add_instance_metadata::(instance); - }: _(SystemOrigin::Signed(caller), class, Some(instance), key.clone(), value.clone()) + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + add_item_metadata::(item); + }: _(SystemOrigin::Signed(caller), collection, Some(item), key.clone(), value.clone()) verify { - assert_last_event::(Event::AttributeSet { class, maybe_instance: Some(instance), key, value }.into()); + assert_last_event::(Event::AttributeSet { collection, maybe_item: Some(item), key, value }.into()); } clear_attribute { - let (class, caller, _) = create_class::(); - let (instance, ..) = mint_instance::(0); - add_instance_metadata::(instance); - let (key, ..) = add_instance_attribute::(instance); - }: _(SystemOrigin::Signed(caller), class, Some(instance), key.clone()) + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + add_item_metadata::(item); + let (key, ..) = add_item_attribute::(item); + }: _(SystemOrigin::Signed(caller), collection, Some(item), key.clone()) verify { - assert_last_event::(Event::AttributeCleared { class, maybe_instance: Some(instance), key }.into()); + assert_last_event::(Event::AttributeCleared { collection, maybe_item: Some(item), key }.into()); } set_metadata { let data: BoundedVec<_, _> = vec![0u8; T::StringLimit::get() as usize].try_into().unwrap(); - let (class, caller, _) = create_class::(); - let (instance, ..) = mint_instance::(0); - }: _(SystemOrigin::Signed(caller), class, instance, data.clone(), false) + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + }: _(SystemOrigin::Signed(caller), collection, item, data.clone(), false) verify { - assert_last_event::(Event::MetadataSet { class, instance, data, is_frozen: false }.into()); + assert_last_event::(Event::MetadataSet { collection, item, data, is_frozen: false }.into()); } clear_metadata { - let (class, caller, _) = create_class::(); - let (instance, ..) = mint_instance::(0); - add_instance_metadata::(instance); - }: _(SystemOrigin::Signed(caller), class, instance) + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + add_item_metadata::(item); + }: _(SystemOrigin::Signed(caller), collection, item) verify { - assert_last_event::(Event::MetadataCleared { class, instance }.into()); + assert_last_event::(Event::MetadataCleared { collection, item }.into()); } - set_class_metadata { + set_collection_metadata { let data: BoundedVec<_, _> = vec![0u8; T::StringLimit::get() as usize].try_into().unwrap(); - let (class, caller, _) = create_class::(); - }: _(SystemOrigin::Signed(caller), class, data.clone(), false) + let (collection, caller, _) = create_collection::(); + }: _(SystemOrigin::Signed(caller), collection, data.clone(), false) verify { - assert_last_event::(Event::ClassMetadataSet { class, data, is_frozen: false }.into()); + assert_last_event::(Event::CollectionMetadataSet { collection, data, is_frozen: false }.into()); } - clear_class_metadata { - let (class, caller, _) = create_class::(); - add_class_metadata::(); - }: _(SystemOrigin::Signed(caller), class) + clear_collection_metadata { + let (collection, caller, _) = create_collection::(); + add_collection_metadata::(); + }: _(SystemOrigin::Signed(caller), collection) verify { - assert_last_event::(Event::ClassMetadataCleared { class }.into()); + assert_last_event::(Event::CollectionMetadataCleared { collection }.into()); } approve_transfer { - let (class, caller, _) = create_class::(); - let (instance, ..) = mint_instance::(0); + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); let delegate: T::AccountId = account("delegate", 0, SEED); let delegate_lookup = T::Lookup::unlookup(delegate.clone()); - }: _(SystemOrigin::Signed(caller.clone()), class, instance, delegate_lookup) + }: _(SystemOrigin::Signed(caller.clone()), collection, item, delegate_lookup) verify { - assert_last_event::(Event::ApprovedTransfer { class, instance, owner: caller, delegate }.into()); + assert_last_event::(Event::ApprovedTransfer { collection, item, owner: caller, delegate }.into()); } cancel_approval { - let (class, caller, _) = create_class::(); - let (instance, ..) = mint_instance::(0); + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); let delegate: T::AccountId = account("delegate", 0, SEED); let delegate_lookup = T::Lookup::unlookup(delegate.clone()); let origin = SystemOrigin::Signed(caller.clone()).into(); - Uniques::::approve_transfer(origin, class, instance, delegate_lookup.clone())?; - }: _(SystemOrigin::Signed(caller.clone()), class, instance, Some(delegate_lookup)) + Uniques::::approve_transfer(origin, collection, item, delegate_lookup.clone())?; + }: _(SystemOrigin::Signed(caller.clone()), collection, item, Some(delegate_lookup)) verify { - assert_last_event::(Event::ApprovalCancelled { class, instance, owner: caller, delegate }.into()); + assert_last_event::(Event::ApprovalCancelled { collection, item, owner: caller, delegate }.into()); } set_accept_ownership { let caller: T::AccountId = whitelisted_caller(); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); - let class = T::Helper::class(0); - }: _(SystemOrigin::Signed(caller.clone()), Some(class)) + let collection = T::Helper::collection(0); + }: _(SystemOrigin::Signed(caller.clone()), Some(collection)) verify { assert_last_event::(Event::OwnershipAcceptanceChanged { who: caller, - maybe_class: Some(class), + maybe_collection: Some(collection), }.into()); } diff --git a/frame/uniques/src/functions.rs b/frame/uniques/src/functions.rs index ea9eecd767dcf..20e1410a8a849 100644 --- a/frame/uniques/src/functions.rs +++ b/frame/uniques/src/functions.rs @@ -23,168 +23,174 @@ use sp_runtime::{DispatchError, DispatchResult}; impl, I: 'static> Pallet { pub fn do_transfer( - class: T::ClassId, - instance: T::InstanceId, + collection: T::CollectionId, + item: T::ItemId, dest: T::AccountId, with_details: impl FnOnce( - &ClassDetailsFor, - &mut InstanceDetailsFor, + &CollectionDetailsFor, + &mut ItemDetailsFor, ) -> DispatchResult, ) -> DispatchResult { - let class_details = Class::::get(&class).ok_or(Error::::UnknownClass)?; - ensure!(!class_details.is_frozen, Error::::Frozen); - ensure!(!T::Locker::is_locked(class, instance), Error::::Locked); + let collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + ensure!(!collection_details.is_frozen, Error::::Frozen); + ensure!(!T::Locker::is_locked(collection, item), Error::::Locked); let mut details = - Asset::::get(&class, &instance).ok_or(Error::::UnknownClass)?; + Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; ensure!(!details.is_frozen, Error::::Frozen); - with_details(&class_details, &mut details)?; + with_details(&collection_details, &mut details)?; - Account::::remove((&details.owner, &class, &instance)); - Account::::insert((&dest, &class, &instance), ()); + Account::::remove((&details.owner, &collection, &item)); + Account::::insert((&dest, &collection, &item), ()); let origin = details.owner; details.owner = dest; - Asset::::insert(&class, &instance, &details); + Item::::insert(&collection, &item, &details); Self::deposit_event(Event::Transferred { - class, - instance, + collection, + item, from: origin, to: details.owner, }); Ok(()) } - pub fn do_create_class( - class: T::ClassId, + pub fn do_create_collection( + collection: T::CollectionId, owner: T::AccountId, admin: T::AccountId, deposit: DepositBalanceOf, free_holding: bool, event: Event, ) -> DispatchResult { - ensure!(!Class::::contains_key(class), Error::::InUse); + ensure!(!Collection::::contains_key(collection), Error::::InUse); T::Currency::reserve(&owner, deposit)?; - Class::::insert( - class, - ClassDetails { + Collection::::insert( + collection, + CollectionDetails { owner: owner.clone(), issuer: admin.clone(), admin: admin.clone(), freezer: admin, total_deposit: deposit, free_holding, - instances: 0, - instance_metadatas: 0, + items: 0, + item_metadatas: 0, attributes: 0, is_frozen: false, }, ); - ClassAccount::::insert(&owner, &class, ()); + CollectionAccount::::insert(&owner, &collection, ()); Self::deposit_event(event); Ok(()) } - pub fn do_destroy_class( - class: T::ClassId, + pub fn do_destroy_collection( + collection: T::CollectionId, witness: DestroyWitness, maybe_check_owner: Option, ) -> Result { - Class::::try_mutate_exists(class, |maybe_details| { - let class_details = maybe_details.take().ok_or(Error::::UnknownClass)?; + Collection::::try_mutate_exists(collection, |maybe_details| { + let collection_details = + maybe_details.take().ok_or(Error::::UnknownCollection)?; if let Some(check_owner) = maybe_check_owner { - ensure!(class_details.owner == check_owner, Error::::NoPermission); + ensure!(collection_details.owner == check_owner, Error::::NoPermission); } - ensure!(class_details.instances == witness.instances, Error::::BadWitness); + ensure!(collection_details.items == witness.items, Error::::BadWitness); ensure!( - class_details.instance_metadatas == witness.instance_metadatas, + collection_details.item_metadatas == witness.item_metadatas, Error::::BadWitness ); - ensure!(class_details.attributes == witness.attributes, Error::::BadWitness); + ensure!(collection_details.attributes == witness.attributes, Error::::BadWitness); - for (instance, details) in Asset::::drain_prefix(&class) { - Account::::remove((&details.owner, &class, &instance)); + for (item, details) in Item::::drain_prefix(&collection) { + Account::::remove((&details.owner, &collection, &item)); } - InstanceMetadataOf::::remove_prefix(&class, None); - ClassMetadataOf::::remove(&class); - Attribute::::remove_prefix((&class,), None); - ClassAccount::::remove(&class_details.owner, &class); - T::Currency::unreserve(&class_details.owner, class_details.total_deposit); + ItemMetadataOf::::remove_prefix(&collection, None); + CollectionMetadataOf::::remove(&collection); + Attribute::::remove_prefix((&collection,), None); + CollectionAccount::::remove(&collection_details.owner, &collection); + T::Currency::unreserve(&collection_details.owner, collection_details.total_deposit); - Self::deposit_event(Event::Destroyed { class }); + Self::deposit_event(Event::Destroyed { collection }); Ok(DestroyWitness { - instances: class_details.instances, - instance_metadatas: class_details.instance_metadatas, - attributes: class_details.attributes, + items: collection_details.items, + item_metadatas: collection_details.item_metadatas, + attributes: collection_details.attributes, }) }) } pub fn do_mint( - class: T::ClassId, - instance: T::InstanceId, + collection: T::CollectionId, + item: T::ItemId, owner: T::AccountId, - with_details: impl FnOnce(&ClassDetailsFor) -> DispatchResult, + with_details: impl FnOnce(&CollectionDetailsFor) -> DispatchResult, ) -> DispatchResult { - ensure!(!Asset::::contains_key(class, instance), Error::::AlreadyExists); - - Class::::try_mutate(&class, |maybe_class_details| -> DispatchResult { - let class_details = maybe_class_details.as_mut().ok_or(Error::::UnknownClass)?; - - with_details(class_details)?; - - let instances = - class_details.instances.checked_add(1).ok_or(ArithmeticError::Overflow)?; - class_details.instances = instances; - - let deposit = match class_details.free_holding { - true => Zero::zero(), - false => T::InstanceDeposit::get(), - }; - T::Currency::reserve(&class_details.owner, deposit)?; - class_details.total_deposit += deposit; - - let owner = owner.clone(); - Account::::insert((&owner, &class, &instance), ()); - let details = InstanceDetails { owner, approved: None, is_frozen: false, deposit }; - Asset::::insert(&class, &instance, details); - Ok(()) - })?; + ensure!(!Item::::contains_key(collection, item), Error::::AlreadyExists); + + Collection::::try_mutate( + &collection, + |maybe_collection_details| -> DispatchResult { + let collection_details = + maybe_collection_details.as_mut().ok_or(Error::::UnknownCollection)?; + + with_details(collection_details)?; + + let items = + collection_details.items.checked_add(1).ok_or(ArithmeticError::Overflow)?; + collection_details.items = items; + + let deposit = match collection_details.free_holding { + true => Zero::zero(), + false => T::ItemDeposit::get(), + }; + T::Currency::reserve(&collection_details.owner, deposit)?; + collection_details.total_deposit += deposit; + + let owner = owner.clone(); + Account::::insert((&owner, &collection, &item), ()); + let details = ItemDetails { owner, approved: None, is_frozen: false, deposit }; + Item::::insert(&collection, &item, details); + Ok(()) + }, + )?; - Self::deposit_event(Event::Issued { class, instance, owner }); + Self::deposit_event(Event::Issued { collection, item, owner }); Ok(()) } pub fn do_burn( - class: T::ClassId, - instance: T::InstanceId, - with_details: impl FnOnce(&ClassDetailsFor, &InstanceDetailsFor) -> DispatchResult, + collection: T::CollectionId, + item: T::ItemId, + with_details: impl FnOnce(&CollectionDetailsFor, &ItemDetailsFor) -> DispatchResult, ) -> DispatchResult { - let owner = Class::::try_mutate( - &class, - |maybe_class_details| -> Result { - let class_details = - maybe_class_details.as_mut().ok_or(Error::::UnknownClass)?; - let details = - Asset::::get(&class, &instance).ok_or(Error::::UnknownClass)?; - with_details(class_details, &details)?; + let owner = Collection::::try_mutate( + &collection, + |maybe_collection_details| -> Result { + let collection_details = + maybe_collection_details.as_mut().ok_or(Error::::UnknownCollection)?; + let details = Item::::get(&collection, &item) + .ok_or(Error::::UnknownCollection)?; + with_details(collection_details, &details)?; // Return the deposit. - T::Currency::unreserve(&class_details.owner, details.deposit); - class_details.total_deposit.saturating_reduce(details.deposit); - class_details.instances.saturating_dec(); + T::Currency::unreserve(&collection_details.owner, details.deposit); + collection_details.total_deposit.saturating_reduce(details.deposit); + collection_details.items.saturating_dec(); Ok(details.owner) }, )?; - Asset::::remove(&class, &instance); - Account::::remove((&owner, &class, &instance)); + Item::::remove(&collection, &item); + Account::::remove((&owner, &collection, &item)); - Self::deposit_event(Event::Burned { class, instance, owner }); + Self::deposit_event(Event::Burned { collection, item, owner }); Ok(()) } } diff --git a/frame/uniques/src/impl_nonfungibles.rs b/frame/uniques/src/impl_nonfungibles.rs index 492befc46967c..cead6f562ab58 100644 --- a/frame/uniques/src/impl_nonfungibles.rs +++ b/frame/uniques/src/impl_nonfungibles.rs @@ -26,59 +26,59 @@ use sp_runtime::{DispatchError, DispatchResult}; use sp_std::prelude::*; impl, I: 'static> Inspect<::AccountId> for Pallet { - type InstanceId = T::InstanceId; - type ClassId = T::ClassId; + type ItemId = T::ItemId; + type CollectionId = T::CollectionId; fn owner( - class: &Self::ClassId, - instance: &Self::InstanceId, + collection: &Self::CollectionId, + item: &Self::ItemId, ) -> Option<::AccountId> { - Asset::::get(class, instance).map(|a| a.owner) + Item::::get(collection, item).map(|a| a.owner) } - fn class_owner(class: &Self::ClassId) -> Option<::AccountId> { - Class::::get(class).map(|a| a.owner) + fn collection_owner(collection: &Self::CollectionId) -> Option<::AccountId> { + Collection::::get(collection).map(|a| a.owner) } - /// Returns the attribute value of `instance` of `class` corresponding to `key`. + /// Returns the attribute value of `item` of `collection` corresponding to `key`. /// - /// When `key` is empty, we return the instance metadata value. + /// When `key` is empty, we return the item metadata value. /// /// By default this is `None`; no attributes are defined. fn attribute( - class: &Self::ClassId, - instance: &Self::InstanceId, + collection: &Self::CollectionId, + item: &Self::ItemId, key: &[u8], ) -> Option> { if key.is_empty() { - // We make the empty key map to the instance metadata value. - InstanceMetadataOf::::get(class, instance).map(|m| m.data.into()) + // We make the empty key map to the item metadata value. + ItemMetadataOf::::get(collection, item).map(|m| m.data.into()) } else { let key = BoundedSlice::<_, _>::try_from(key).ok()?; - Attribute::::get((class, Some(instance), key)).map(|a| a.0.into()) + Attribute::::get((collection, Some(item), key)).map(|a| a.0.into()) } } - /// Returns the attribute value of `instance` of `class` corresponding to `key`. + /// Returns the attribute value of `item` of `collection` corresponding to `key`. /// - /// When `key` is empty, we return the instance metadata value. + /// When `key` is empty, we return the item metadata value. /// /// By default this is `None`; no attributes are defined. - fn class_attribute(class: &Self::ClassId, key: &[u8]) -> Option> { + fn collection_attribute(collection: &Self::CollectionId, key: &[u8]) -> Option> { if key.is_empty() { - // We make the empty key map to the instance metadata value. - ClassMetadataOf::::get(class).map(|m| m.data.into()) + // We make the empty key map to the item metadata value. + CollectionMetadataOf::::get(collection).map(|m| m.data.into()) } else { let key = BoundedSlice::<_, _>::try_from(key).ok()?; - Attribute::::get((class, Option::::None, key)).map(|a| a.0.into()) + Attribute::::get((collection, Option::::None, key)).map(|a| a.0.into()) } } - /// Returns `true` if the asset `instance` of `class` may be transferred. + /// Returns `true` if the `item` of `collection` may be transferred. /// - /// Default implementation is that all assets are transferable. - fn can_transfer(class: &Self::ClassId, instance: &Self::InstanceId) -> bool { - match (Class::::get(class), Asset::::get(class, instance)) { + /// Default implementation is that all items are transferable. + fn can_transfer(collection: &Self::CollectionId, item: &Self::ItemId) -> bool { + match (Collection::::get(collection), Item::::get(collection, item)) { (Some(cd), Some(id)) if !cd.is_frozen && !id.is_frozen => true, _ => false, } @@ -86,19 +86,19 @@ impl, I: 'static> Inspect<::AccountId> for Palle } impl, I: 'static> Create<::AccountId> for Pallet { - /// Create a `class` of nonfungible assets to be owned by `who` and managed by `admin`. - fn create_class( - class: &Self::ClassId, + /// Create a `collection` of nonfungible items to be owned by `who` and managed by `admin`. + fn create_collection( + collection: &Self::CollectionId, who: &T::AccountId, admin: &T::AccountId, ) -> DispatchResult { - Self::do_create_class( - *class, + Self::do_create_collection( + *collection, who.clone(), admin.clone(), - T::ClassDeposit::get(), + T::CollectionDeposit::get(), false, - Event::Created { class: *class, creator: who.clone(), owner: admin.clone() }, + Event::Created { collection: *collection, creator: who.clone(), owner: admin.clone() }, ) } } @@ -106,34 +106,34 @@ impl, I: 'static> Create<::AccountId> for Pallet impl, I: 'static> Destroy<::AccountId> for Pallet { type DestroyWitness = DestroyWitness; - fn get_destroy_witness(class: &Self::ClassId) -> Option { - Class::::get(class).map(|a| a.destroy_witness()) + fn get_destroy_witness(collection: &Self::CollectionId) -> Option { + Collection::::get(collection).map(|a| a.destroy_witness()) } fn destroy( - class: Self::ClassId, + collection: Self::CollectionId, witness: Self::DestroyWitness, maybe_check_owner: Option, ) -> Result { - Self::do_destroy_class(class, witness, maybe_check_owner) + Self::do_destroy_collection(collection, witness, maybe_check_owner) } } impl, I: 'static> Mutate<::AccountId> for Pallet { fn mint_into( - class: &Self::ClassId, - instance: &Self::InstanceId, + collection: &Self::CollectionId, + item: &Self::ItemId, who: &T::AccountId, ) -> DispatchResult { - Self::do_mint(*class, *instance, who.clone(), |_| Ok(())) + Self::do_mint(*collection, *item, who.clone(), |_| Ok(())) } fn burn( - class: &Self::ClassId, - instance: &Self::InstanceId, + collection: &Self::CollectionId, + item: &Self::ItemId, maybe_check_owner: Option<&T::AccountId>, ) -> DispatchResult { - Self::do_burn(*class, *instance, |_, d| { + Self::do_burn(*collection, *item, |_, d| { if let Some(check_owner) = maybe_check_owner { if &d.owner != check_owner { return Err(Error::::NoPermission.into()) @@ -146,43 +146,43 @@ impl, I: 'static> Mutate<::AccountId> for Pallet impl, I: 'static> Transfer for Pallet { fn transfer( - class: &Self::ClassId, - instance: &Self::InstanceId, + collection: &Self::CollectionId, + item: &Self::ItemId, destination: &T::AccountId, ) -> DispatchResult { - Self::do_transfer(*class, *instance, destination.clone(), |_, _| Ok(())) + Self::do_transfer(*collection, *item, destination.clone(), |_, _| Ok(())) } } impl, I: 'static> InspectEnumerable for Pallet { - /// Returns an iterator of the asset classes in existence. + /// Returns an iterator of the collections in existence. /// /// NOTE: iterating this list invokes a storage read per item. - fn classes() -> Box> { - Box::new(ClassMetadataOf::::iter_keys()) + fn collections() -> Box> { + Box::new(CollectionMetadataOf::::iter_keys()) } - /// Returns an iterator of the instances of an asset `class` in existence. + /// Returns an iterator of the items of a `collection` in existence. /// /// NOTE: iterating this list invokes a storage read per item. - fn instances(class: &Self::ClassId) -> Box> { - Box::new(InstanceMetadataOf::::iter_key_prefix(class)) + fn items(collection: &Self::CollectionId) -> Box> { + Box::new(ItemMetadataOf::::iter_key_prefix(collection)) } - /// Returns an iterator of the asset instances of all classes owned by `who`. + /// Returns an iterator of the items of all collections owned by `who`. /// /// NOTE: iterating this list invokes a storage read per item. - fn owned(who: &T::AccountId) -> Box> { + fn owned(who: &T::AccountId) -> Box> { Box::new(Account::::iter_key_prefix((who,))) } - /// Returns an iterator of the asset instances of `class` owned by `who`. + /// Returns an iterator of the items of `collection` owned by `who`. /// /// NOTE: iterating this list invokes a storage read per item. - fn owned_in_class( - class: &Self::ClassId, + fn owned_in_collection( + collection: &Self::CollectionId, who: &T::AccountId, - ) -> Box> { - Box::new(Account::::iter_key_prefix((who, class))) + ) -> Box> { + Box::new(Account::::iter_key_prefix((who, collection))) } } diff --git a/frame/uniques/src/lib.rs b/frame/uniques/src/lib.rs index a360a02ebc702..0a8cf201415a1 100644 --- a/frame/uniques/src/lib.rs +++ b/frame/uniques/src/lib.rs @@ -15,9 +15,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! # Unique (Assets) Module +//! # Unique (Items) Module //! -//! A simple, secure module for dealing with non-fungible assets. +//! A simple, secure module for dealing with non-fungible items. //! //! ## Related Modules //! @@ -67,16 +67,16 @@ pub mod pallet { pub struct Pallet(_); #[cfg(feature = "runtime-benchmarks")] - pub trait BenchmarkHelper { - fn class(i: u16) -> ClassId; - fn instance(i: u16) -> InstanceId; + pub trait BenchmarkHelper { + fn collection(i: u16) -> CollectionId; + fn item(i: u16) -> ItemId; } #[cfg(feature = "runtime-benchmarks")] - impl, InstanceId: From> BenchmarkHelper for () { - fn class(i: u16) -> ClassId { + impl, ItemId: From> BenchmarkHelper for () { + fn collection(i: u16) -> CollectionId { i.into() } - fn instance(i: u16) -> InstanceId { + fn item(i: u16) -> ItemId { i.into() } } @@ -87,43 +87,43 @@ pub mod pallet { /// The overarching event type. type Event: From> + IsType<::Event>; - /// Identifier for the class of asset. - type ClassId: Member + Parameter + MaxEncodedLen + Copy; + /// Identifier for the collection of item. + type CollectionId: Member + Parameter + MaxEncodedLen + Copy; - /// The type used to identify a unique asset within an asset class. - type InstanceId: Member + Parameter + MaxEncodedLen + Copy; + /// The type used to identify a unique item within a collection. + type ItemId: Member + Parameter + MaxEncodedLen + Copy; /// The currency mechanism, used for paying for reserves. type Currency: ReservableCurrency; - /// The origin which may forcibly create or destroy an asset or otherwise alter privileged + /// The origin which may forcibly create or destroy an item or otherwise alter privileged /// attributes. type ForceOrigin: EnsureOrigin; - /// Standard class creation is only allowed if the origin attempting it and the class are - /// in this set. + /// Standard collection creation is only allowed if the origin attempting it and the + /// collection are in this set. type CreateOrigin: EnsureOriginWithArg< Success = Self::AccountId, Self::Origin, - Self::ClassId, + Self::CollectionId, >; /// Locker trait to enable Locking mechanism downstream. - type Locker: Locker; + type Locker: Locker; - /// The basic amount of funds that must be reserved for an asset class. + /// The basic amount of funds that must be reserved for collection. #[pallet::constant] - type ClassDeposit: Get>; + type CollectionDeposit: Get>; - /// The basic amount of funds that must be reserved for an asset instance. + /// The basic amount of funds that must be reserved for an item. #[pallet::constant] - type InstanceDeposit: Get>; + type ItemDeposit: Get>; - /// The basic amount of funds that must be reserved when adding metadata to your asset. + /// The basic amount of funds that must be reserved when adding metadata to your item. #[pallet::constant] type MetadataDepositBase: Get>; - /// The basic amount of funds that must be reserved when adding an attribute to an asset. + /// The basic amount of funds that must be reserved when adding an attribute to an item. #[pallet::constant] type AttributeDepositBase: Get>; @@ -146,94 +146,99 @@ pub mod pallet { #[cfg(feature = "runtime-benchmarks")] /// A set of helper functions for benchmarking. - type Helper: BenchmarkHelper; + type Helper: BenchmarkHelper; /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; } #[pallet::storage] - /// Details of an asset class. - pub(super) type Class, I: 'static = ()> = StorageMap< + #[pallet::storage_prefix = "Class"] + /// Details of a collection. + pub(super) type Collection, I: 'static = ()> = StorageMap< _, Blake2_128Concat, - T::ClassId, - ClassDetails>, + T::CollectionId, + CollectionDetails>, >; #[pallet::storage] - /// The class, if any, of which an account is willing to take ownership. + /// The collection, if any, of which an account is willing to take ownership. pub(super) type OwnershipAcceptance, I: 'static = ()> = - StorageMap<_, Blake2_128Concat, T::AccountId, T::ClassId>; + StorageMap<_, Blake2_128Concat, T::AccountId, T::CollectionId>; #[pallet::storage] - /// The assets held by any given account; set out this way so that assets owned by a single + /// The items held by any given account; set out this way so that items owned by a single /// account can be enumerated. pub(super) type Account, I: 'static = ()> = StorageNMap< _, ( NMapKey, // owner - NMapKey, - NMapKey, + NMapKey, + NMapKey, ), (), OptionQuery, >; #[pallet::storage] - /// The classes owned by any given account; set out this way so that classes owned by a single - /// account can be enumerated. - pub(super) type ClassAccount, I: 'static = ()> = StorageDoubleMap< + #[pallet::storage_prefix = "ClassAccount"] + /// The collections owned by any given account; set out this way so that collections owned by + /// a single account can be enumerated. + pub(super) type CollectionAccount, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, T::AccountId, Blake2_128Concat, - T::ClassId, + T::CollectionId, (), OptionQuery, >; #[pallet::storage] - /// The assets in existence and their ownership details. - pub(super) type Asset, I: 'static = ()> = StorageDoubleMap< + #[pallet::storage_prefix = "Asset"] + /// The items in existence and their ownership details. + pub(super) type Item, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, - T::ClassId, + T::CollectionId, Blake2_128Concat, - T::InstanceId, - InstanceDetails>, + T::ItemId, + ItemDetails>, OptionQuery, >; #[pallet::storage] - /// Metadata of an asset class. - pub(super) type ClassMetadataOf, I: 'static = ()> = StorageMap< + #[pallet::storage_prefix = "ClassMetadataOf"] + /// Metadata of a collection. + pub(super) type CollectionMetadataOf, I: 'static = ()> = StorageMap< _, Blake2_128Concat, - T::ClassId, - ClassMetadata, T::StringLimit>, + T::CollectionId, + CollectionMetadata, T::StringLimit>, OptionQuery, >; #[pallet::storage] - /// Metadata of an asset instance. - pub(super) type InstanceMetadataOf, I: 'static = ()> = StorageDoubleMap< + #[pallet::storage_prefix = "InstanceMetadataOf"] + /// Metadata of an item. + pub(super) type ItemMetadataOf, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, - T::ClassId, + T::CollectionId, Blake2_128Concat, - T::InstanceId, - InstanceMetadata, T::StringLimit>, + T::ItemId, + ItemMetadata, T::StringLimit>, OptionQuery, >; #[pallet::storage] - /// Metadata of an asset class. + /// Attributes of a collection. pub(super) type Attribute, I: 'static = ()> = StorageNMap< _, ( - NMapKey, - NMapKey>, + NMapKey, + NMapKey>, NMapKey>, ), (BoundedVec, DepositBalanceOf), @@ -243,109 +248,109 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event, I: 'static = ()> { - /// An asset class was created. - Created { class: T::ClassId, creator: T::AccountId, owner: T::AccountId }, - /// An asset class was force-created. - ForceCreated { class: T::ClassId, owner: T::AccountId }, - /// An asset `class` was destroyed. - Destroyed { class: T::ClassId }, - /// An asset `instance` was issued. - Issued { class: T::ClassId, instance: T::InstanceId, owner: T::AccountId }, - /// An asset `instance` was transferred. + /// A `collection` was created. + Created { collection: T::CollectionId, creator: T::AccountId, owner: T::AccountId }, + /// A `collection` was force-created. + ForceCreated { collection: T::CollectionId, owner: T::AccountId }, + /// A `collection` was destroyed. + Destroyed { collection: T::CollectionId }, + /// An `item` was issued. + Issued { collection: T::CollectionId, item: T::ItemId, owner: T::AccountId }, + /// An `item` was transferred. Transferred { - class: T::ClassId, - instance: T::InstanceId, + collection: T::CollectionId, + item: T::ItemId, from: T::AccountId, to: T::AccountId, }, - /// An asset `instance` was destroyed. - Burned { class: T::ClassId, instance: T::InstanceId, owner: T::AccountId }, - /// Some asset `instance` was frozen. - Frozen { class: T::ClassId, instance: T::InstanceId }, - /// Some asset `instance` was thawed. - Thawed { class: T::ClassId, instance: T::InstanceId }, - /// Some asset `class` was frozen. - ClassFrozen { class: T::ClassId }, - /// Some asset `class` was thawed. - ClassThawed { class: T::ClassId }, + /// An `item` was destroyed. + Burned { collection: T::CollectionId, item: T::ItemId, owner: T::AccountId }, + /// Some `item` was frozen. + Frozen { collection: T::CollectionId, item: T::ItemId }, + /// Some `item` was thawed. + Thawed { collection: T::CollectionId, item: T::ItemId }, + /// Some `collection` was frozen. + CollectionFrozen { collection: T::CollectionId }, + /// Some `collection` was thawed. + CollectionThawed { collection: T::CollectionId }, /// The owner changed. - OwnerChanged { class: T::ClassId, new_owner: T::AccountId }, + OwnerChanged { collection: T::CollectionId, new_owner: T::AccountId }, /// The management team changed. TeamChanged { - class: T::ClassId, + collection: T::CollectionId, issuer: T::AccountId, admin: T::AccountId, freezer: T::AccountId, }, - /// An `instance` of an asset `class` has been approved by the `owner` for transfer by a - /// `delegate`. + /// An `item` of a `collection` has been approved by the `owner` for transfer by + /// a `delegate`. ApprovedTransfer { - class: T::ClassId, - instance: T::InstanceId, + collection: T::CollectionId, + item: T::ItemId, owner: T::AccountId, delegate: T::AccountId, }, - /// An approval for a `delegate` account to transfer the `instance` of an asset `class` was - /// cancelled by its `owner`. + /// An approval for a `delegate` account to transfer the `item` of an item + /// `collection` was cancelled by its `owner`. ApprovalCancelled { - class: T::ClassId, - instance: T::InstanceId, + collection: T::CollectionId, + item: T::ItemId, owner: T::AccountId, delegate: T::AccountId, }, - /// An asset `class` has had its attributes changed by the `Force` origin. - AssetStatusChanged { class: T::ClassId }, - /// New metadata has been set for an asset class. - ClassMetadataSet { - class: T::ClassId, + /// A `collection` has had its attributes changed by the `Force` origin. + ItemStatusChanged { collection: T::CollectionId }, + /// New metadata has been set for a `collection`. + CollectionMetadataSet { + collection: T::CollectionId, data: BoundedVec, is_frozen: bool, }, - /// Metadata has been cleared for an asset class. - ClassMetadataCleared { class: T::ClassId }, - /// New metadata has been set for an asset instance. + /// Metadata has been cleared for a `collection`. + CollectionMetadataCleared { collection: T::CollectionId }, + /// New metadata has been set for an item. MetadataSet { - class: T::ClassId, - instance: T::InstanceId, + collection: T::CollectionId, + item: T::ItemId, data: BoundedVec, is_frozen: bool, }, - /// Metadata has been cleared for an asset instance. - MetadataCleared { class: T::ClassId, instance: T::InstanceId }, - /// Metadata has been cleared for an asset instance. - Redeposited { class: T::ClassId, successful_instances: Vec }, - /// New attribute metadata has been set for an asset class or instance. + /// Metadata has been cleared for an item. + MetadataCleared { collection: T::CollectionId, item: T::ItemId }, + /// Metadata has been cleared for an item. + Redeposited { collection: T::CollectionId, successful_items: Vec }, + /// New attribute metadata has been set for a `collection` or `item`. AttributeSet { - class: T::ClassId, - maybe_instance: Option, + collection: T::CollectionId, + maybe_item: Option, key: BoundedVec, value: BoundedVec, }, - /// Attribute metadata has been cleared for an asset class or instance. + /// Attribute metadata has been cleared for a `collection` or `item`. AttributeCleared { - class: T::ClassId, - maybe_instance: Option, + collection: T::CollectionId, + maybe_item: Option, key: BoundedVec, }, /// Ownership acceptance has changed for an account. - OwnershipAcceptanceChanged { who: T::AccountId, maybe_class: Option }, + OwnershipAcceptanceChanged { who: T::AccountId, maybe_collection: Option }, } #[pallet::error] pub enum Error { /// The signing account has no permission to do the operation. NoPermission, - /// The given asset ID is unknown. - UnknownClass, - /// The asset instance ID has already been used for an asset. + /// The given item ID is unknown. + UnknownCollection, + /// The item ID has already been used for an item. AlreadyExists, /// The owner turned out to be different to what was expected. WrongOwner, /// Invalid witness data given. BadWitness, - /// The asset ID is already taken. + /// The item ID is already taken. InUse, - /// The asset instance or class is frozen. + /// The item or collection is frozen. Frozen, /// The delegate turned out to be different to what was expected. WrongDelegate, @@ -353,38 +358,38 @@ pub mod pallet { NoDelegate, /// No approval exists that would allow the transfer. Unapproved, - /// The named owner has not signed ownership of the class is acceptable. + /// The named owner has not signed ownership of the collection is acceptable. Unaccepted, - /// The asset instance is locked. + /// The item is locked. Locked, } impl, I: 'static> Pallet { - /// Get the owner of the asset instance, if the asset exists. - pub fn owner(class: T::ClassId, instance: T::InstanceId) -> Option { - Asset::::get(class, instance).map(|i| i.owner) + /// Get the owner of the item, if the item exists. + pub fn owner(collection: T::CollectionId, item: T::ItemId) -> Option { + Item::::get(collection, item).map(|i| i.owner) } - /// Get the owner of the asset instance, if the asset exists. - pub fn class_owner(class: T::ClassId) -> Option { - Class::::get(class).map(|i| i.owner) + /// Get the owner of the item, if the item exists. + pub fn collection_owner(collection: T::CollectionId) -> Option { + Collection::::get(collection).map(|i| i.owner) } } #[pallet::call] impl, I: 'static> Pallet { - /// Issue a new class of non-fungible assets from a public origin. + /// Issue a new collection of non-fungible items from a public origin. /// - /// This new asset class has no assets initially and its owner is the origin. + /// This new collection has no items initially and its owner is the origin. /// /// The origin must be Signed and the sender must have sufficient funds free. /// - /// `AssetDeposit` funds of sender are reserved. + /// `ItemDeposit` funds of sender are reserved. /// /// Parameters: - /// - `class`: The identifier of the new asset class. This must not be currently in use. - /// - `admin`: The admin of this class of assets. The admin is the initial address of each - /// member of the asset class's admin team. + /// - `collection`: The identifier of the new collection. This must not be currently in use. + /// - `admin`: The admin of this collection. The admin is the initial address of each + /// member of the collection's admin team. /// /// Emits `Created` event when successful. /// @@ -392,33 +397,34 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::create())] pub fn create( origin: OriginFor, - class: T::ClassId, + collection: T::CollectionId, admin: ::Source, ) -> DispatchResult { - let owner = T::CreateOrigin::ensure_origin(origin, &class)?; + let owner = T::CreateOrigin::ensure_origin(origin, &collection)?; let admin = T::Lookup::lookup(admin)?; - Self::do_create_class( - class, + Self::do_create_collection( + collection, owner.clone(), admin.clone(), - T::ClassDeposit::get(), + T::CollectionDeposit::get(), false, - Event::Created { class, creator: owner, owner: admin }, + Event::Created { collection, creator: owner, owner: admin }, ) } - /// Issue a new class of non-fungible assets from a privileged origin. + /// Issue a new collection of non-fungible items from a privileged origin. /// - /// This new asset class has no assets initially. + /// This new collection has no items initially. /// /// The origin must conform to `ForceOrigin`. /// /// Unlike `create`, no funds are reserved. /// - /// - `class`: The identifier of the new asset. This must not be currently in use. - /// - `owner`: The owner of this class of assets. The owner has full superuser permissions - /// over this asset, but may later change and configure the permissions using + /// - `collection`: The identifier of the new item. This must not be currently in use. + /// - `owner`: The owner of this collection of items. The owner has full superuser + /// permissions + /// over this item, but may later change and configure the permissions using /// `transfer_ownership` and `set_team`. /// /// Emits `ForceCreated` event when successful. @@ -427,69 +433,69 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::force_create())] pub fn force_create( origin: OriginFor, - class: T::ClassId, + collection: T::CollectionId, owner: ::Source, free_holding: bool, ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; let owner = T::Lookup::lookup(owner)?; - Self::do_create_class( - class, + Self::do_create_collection( + collection, owner.clone(), owner.clone(), Zero::zero(), free_holding, - Event::ForceCreated { class, owner }, + Event::ForceCreated { collection, owner }, ) } - /// Destroy a class of fungible assets. + /// Destroy a collection of fungible items. /// /// The origin must conform to `ForceOrigin` or must be `Signed` and the sender must be the - /// owner of the asset `class`. + /// owner of the `collection`. /// - /// - `class`: The identifier of the asset class to be destroyed. - /// - `witness`: Information on the instances minted in the asset class. This must be + /// - `collection`: The identifier of the collection to be destroyed. + /// - `witness`: Information on the items minted in the collection. This must be /// correct. /// /// Emits `Destroyed` event when successful. /// /// Weight: `O(n + m)` where: - /// - `n = witness.instances` - /// - `m = witness.instance_metadatas` + /// - `n = witness.items` + /// - `m = witness.item_metadatas` /// - `a = witness.attributes` #[pallet::weight(T::WeightInfo::destroy( - witness.instances, - witness.instance_metadatas, + witness.items, + witness.item_metadatas, witness.attributes, ))] pub fn destroy( origin: OriginFor, - class: T::ClassId, + collection: T::CollectionId, witness: DestroyWitness, ) -> DispatchResultWithPostInfo { let maybe_check_owner = match T::ForceOrigin::try_origin(origin) { Ok(_) => None, Err(origin) => Some(ensure_signed(origin)?), }; - let details = Self::do_destroy_class(class, witness, maybe_check_owner)?; + let details = Self::do_destroy_collection(collection, witness, maybe_check_owner)?; Ok(Some(T::WeightInfo::destroy( - details.instances, - details.instance_metadatas, + details.items, + details.item_metadatas, details.attributes, )) .into()) } - /// Mint an asset instance of a particular class. + /// Mint an item of a particular collection. /// - /// The origin must be Signed and the sender must be the Issuer of the asset `class`. + /// The origin must be Signed and the sender must be the Issuer of the `collection`. /// - /// - `class`: The class of the asset to be minted. - /// - `instance`: The instance value of the asset to be minted. - /// - `beneficiary`: The initial owner of the minted asset. + /// - `collection`: The collection of the item to be minted. + /// - `item`: The item value of the item to be minted. + /// - `beneficiary`: The initial owner of the minted item. /// /// Emits `Issued` event when successful. /// @@ -497,27 +503,27 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::mint())] pub fn mint( origin: OriginFor, - class: T::ClassId, - instance: T::InstanceId, + collection: T::CollectionId, + item: T::ItemId, owner: ::Source, ) -> DispatchResult { let origin = ensure_signed(origin)?; let owner = T::Lookup::lookup(owner)?; - Self::do_mint(class, instance, owner, |class_details| { - ensure!(class_details.issuer == origin, Error::::NoPermission); + Self::do_mint(collection, item, owner, |collection_details| { + ensure!(collection_details.issuer == origin, Error::::NoPermission); Ok(()) }) } - /// Destroy a single asset instance. + /// Destroy a single item. /// - /// Origin must be Signed and the sender should be the Admin of the asset `class`. + /// Origin must be Signed and the sender should be the Admin of the `collection`. /// - /// - `class`: The class of the asset to be burned. - /// - `instance`: The instance of the asset to be burned. + /// - `collection`: The collection of the item to be burned. + /// - `item`: The item of the item to be burned. /// - `check_owner`: If `Some` then the operation will fail with `WrongOwner` unless the - /// asset is owned by this value. + /// item is owned by this value. /// /// Emits `Burned` with the actual amount burned. /// @@ -526,15 +532,15 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::burn())] pub fn burn( origin: OriginFor, - class: T::ClassId, - instance: T::InstanceId, + collection: T::CollectionId, + item: T::ItemId, check_owner: Option<::Source>, ) -> DispatchResult { let origin = ensure_signed(origin)?; let check_owner = check_owner.map(T::Lookup::lookup).transpose()?; - Self::do_burn(class, instance, |class_details, details| { - let is_permitted = class_details.admin == origin || details.owner == origin; + Self::do_burn(collection, item, |collection_details, details| { + let is_permitted = collection_details.admin == origin || details.owner == origin; ensure!(is_permitted, Error::::NoPermission); ensure!( check_owner.map_or(true, |o| o == details.owner), @@ -544,17 +550,17 @@ pub mod pallet { }) } - /// Move an asset from the sender account to another. + /// Move an item from the sender account to another. /// /// Origin must be Signed and the signing account must be either: - /// - the Admin of the asset `class`; - /// - the Owner of the asset `instance`; - /// - the approved delegate for the asset `instance` (in this case, the approval is reset). + /// - the Admin of the `collection`; + /// - the Owner of the `item`; + /// - the approved delegate for the `item` (in this case, the approval is reset). /// /// Arguments: - /// - `class`: The class of the asset to be transferred. - /// - `instance`: The instance of the asset to be transferred. - /// - `dest`: The account to receive ownership of the asset. + /// - `collection`: The collection of the item to be transferred. + /// - `item`: The item of the item to be transferred. + /// - `dest`: The account to receive ownership of the item. /// /// Emits `Transferred`. /// @@ -562,15 +568,15 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::transfer())] pub fn transfer( origin: OriginFor, - class: T::ClassId, - instance: T::InstanceId, + collection: T::CollectionId, + item: T::ItemId, dest: ::Source, ) -> DispatchResult { let origin = ensure_signed(origin)?; let dest = T::Lookup::lookup(dest)?; - Self::do_transfer(class, instance, dest, |class_details, details| { - if details.owner != origin && class_details.admin != origin { + Self::do_transfer(collection, item, dest, |collection_details, details| { + if details.owner != origin && collection_details.admin != origin { let approved = details.approved.take().map_or(false, |i| i == origin); ensure!(approved, Error::::NoPermission); } @@ -578,79 +584,79 @@ pub mod pallet { }) } - /// Reevaluate the deposits on some assets. + /// Reevaluate the deposits on some items. /// - /// Origin must be Signed and the sender should be the Owner of the asset `class`. + /// Origin must be Signed and the sender should be the Owner of the `collection`. /// - /// - `class`: The class of the asset to be frozen. - /// - `instances`: The instances of the asset class whose deposits will be reevaluated. + /// - `collection`: The collection to be frozen. + /// - `items`: The items of the collection whose deposits will be reevaluated. /// - /// NOTE: This exists as a best-effort function. Any asset instances which are unknown or + /// NOTE: This exists as a best-effort function. Any items which are unknown or /// in the case that the owner account does not have reservable funds to pay for a - /// deposit increase are ignored. Generally the owner isn't going to call this on instances + /// deposit increase are ignored. Generally the owner isn't going to call this on items /// whose existing deposit is less than the refreshed deposit as it would only cost them, /// so it's of little consequence. /// - /// It will still return an error in the case that the class is unknown of the signer is - /// not permitted to call it. + /// It will still return an error in the case that the collection is unknown of the signer + /// is not permitted to call it. /// - /// Weight: `O(instances.len())` - #[pallet::weight(T::WeightInfo::redeposit(instances.len() as u32))] + /// Weight: `O(items.len())` + #[pallet::weight(T::WeightInfo::redeposit(items.len() as u32))] pub fn redeposit( origin: OriginFor, - class: T::ClassId, - instances: Vec, + collection: T::CollectionId, + items: Vec, ) -> DispatchResult { let origin = ensure_signed(origin)?; - let mut class_details = - Class::::get(&class).ok_or(Error::::UnknownClass)?; - ensure!(class_details.owner == origin, Error::::NoPermission); - let deposit = match class_details.free_holding { + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + ensure!(collection_details.owner == origin, Error::::NoPermission); + let deposit = match collection_details.free_holding { true => Zero::zero(), - false => T::InstanceDeposit::get(), + false => T::ItemDeposit::get(), }; - let mut successful = Vec::with_capacity(instances.len()); - for instance in instances.into_iter() { - let mut details = match Asset::::get(&class, &instance) { + let mut successful = Vec::with_capacity(items.len()); + for item in items.into_iter() { + let mut details = match Item::::get(&collection, &item) { Some(x) => x, None => continue, }; let old = details.deposit; if old > deposit { - T::Currency::unreserve(&class_details.owner, old - deposit); + T::Currency::unreserve(&collection_details.owner, old - deposit); } else if deposit > old { - if T::Currency::reserve(&class_details.owner, deposit - old).is_err() { - // NOTE: No alterations made to class_details in this iteration so far, so - // this is OK to do. + if T::Currency::reserve(&collection_details.owner, deposit - old).is_err() { + // NOTE: No alterations made to collection_details in this iteration so far, + // so this is OK to do. continue } } else { continue } - class_details.total_deposit.saturating_accrue(deposit); - class_details.total_deposit.saturating_reduce(old); + collection_details.total_deposit.saturating_accrue(deposit); + collection_details.total_deposit.saturating_reduce(old); details.deposit = deposit; - Asset::::insert(&class, &instance, &details); - successful.push(instance); + Item::::insert(&collection, &item, &details); + successful.push(item); } - Class::::insert(&class, &class_details); + Collection::::insert(&collection, &collection_details); Self::deposit_event(Event::::Redeposited { - class, - successful_instances: successful, + collection, + successful_items: successful, }); Ok(()) } - /// Disallow further unprivileged transfer of an asset instance. + /// Disallow further unprivileged transfer of an item. /// - /// Origin must be Signed and the sender should be the Freezer of the asset `class`. + /// Origin must be Signed and the sender should be the Freezer of the `collection`. /// - /// - `class`: The class of the asset to be frozen. - /// - `instance`: The instance of the asset to be frozen. + /// - `collection`: The collection of the item to be frozen. + /// - `item`: The item of the item to be frozen. /// /// Emits `Frozen`. /// @@ -658,29 +664,30 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::freeze())] pub fn freeze( origin: OriginFor, - class: T::ClassId, - instance: T::InstanceId, + collection: T::CollectionId, + item: T::ItemId, ) -> DispatchResult { let origin = ensure_signed(origin)?; let mut details = - Asset::::get(&class, &instance).ok_or(Error::::UnknownClass)?; - let class_details = Class::::get(&class).ok_or(Error::::UnknownClass)?; - ensure!(class_details.freezer == origin, Error::::NoPermission); + Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; + let collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + ensure!(collection_details.freezer == origin, Error::::NoPermission); details.is_frozen = true; - Asset::::insert(&class, &instance, &details); + Item::::insert(&collection, &item, &details); - Self::deposit_event(Event::::Frozen { class, instance }); + Self::deposit_event(Event::::Frozen { collection, item }); Ok(()) } - /// Re-allow unprivileged transfer of an asset instance. + /// Re-allow unprivileged transfer of an item. /// - /// Origin must be Signed and the sender should be the Freezer of the asset `class`. + /// Origin must be Signed and the sender should be the Freezer of the `collection`. /// - /// - `class`: The class of the asset to be thawed. - /// - `instance`: The instance of the asset to be thawed. + /// - `collection`: The collection of the item to be thawed. + /// - `item`: The item of the item to be thawed. /// /// Emits `Thawed`. /// @@ -688,78 +695,85 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::thaw())] pub fn thaw( origin: OriginFor, - class: T::ClassId, - instance: T::InstanceId, + collection: T::CollectionId, + item: T::ItemId, ) -> DispatchResult { let origin = ensure_signed(origin)?; let mut details = - Asset::::get(&class, &instance).ok_or(Error::::UnknownClass)?; - let class_details = Class::::get(&class).ok_or(Error::::UnknownClass)?; - ensure!(class_details.admin == origin, Error::::NoPermission); + Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; + let collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + ensure!(collection_details.admin == origin, Error::::NoPermission); details.is_frozen = false; - Asset::::insert(&class, &instance, &details); + Item::::insert(&collection, &item, &details); - Self::deposit_event(Event::::Thawed { class, instance }); + Self::deposit_event(Event::::Thawed { collection, item }); Ok(()) } - /// Disallow further unprivileged transfers for a whole asset class. + /// Disallow further unprivileged transfers for a whole collection. /// - /// Origin must be Signed and the sender should be the Freezer of the asset `class`. + /// Origin must be Signed and the sender should be the Freezer of the `collection`. /// - /// - `class`: The asset class to be frozen. + /// - `collection`: The collection to be frozen. /// - /// Emits `ClassFrozen`. + /// Emits `CollectionFrozen`. /// /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::freeze_class())] - pub fn freeze_class(origin: OriginFor, class: T::ClassId) -> DispatchResult { + #[pallet::weight(T::WeightInfo::freeze_collection())] + pub fn freeze_collection( + origin: OriginFor, + collection: T::CollectionId, + ) -> DispatchResult { let origin = ensure_signed(origin)?; - Class::::try_mutate(class, |maybe_details| { - let details = maybe_details.as_mut().ok_or(Error::::UnknownClass)?; + Collection::::try_mutate(collection, |maybe_details| { + let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; ensure!(origin == details.freezer, Error::::NoPermission); details.is_frozen = true; - Self::deposit_event(Event::::ClassFrozen { class }); + Self::deposit_event(Event::::CollectionFrozen { collection }); Ok(()) }) } - /// Re-allow unprivileged transfers for a whole asset class. + /// Re-allow unprivileged transfers for a whole collection. /// - /// Origin must be Signed and the sender should be the Admin of the asset `class`. + /// Origin must be Signed and the sender should be the Admin of the `collection`. /// - /// - `class`: The class to be thawed. + /// - `collection`: The collection to be thawed. /// - /// Emits `ClassThawed`. + /// Emits `CollectionThawed`. /// /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::thaw_class())] - pub fn thaw_class(origin: OriginFor, class: T::ClassId) -> DispatchResult { + #[pallet::weight(T::WeightInfo::thaw_collection())] + pub fn thaw_collection( + origin: OriginFor, + collection: T::CollectionId, + ) -> DispatchResult { let origin = ensure_signed(origin)?; - Class::::try_mutate(class, |maybe_details| { - let details = maybe_details.as_mut().ok_or(Error::::UnknownClass)?; + Collection::::try_mutate(collection, |maybe_details| { + let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; ensure!(origin == details.admin, Error::::NoPermission); details.is_frozen = false; - Self::deposit_event(Event::::ClassThawed { class }); + Self::deposit_event(Event::::CollectionThawed { collection }); Ok(()) }) } - /// Change the Owner of an asset class. + /// Change the Owner of a collection. /// - /// Origin must be Signed and the sender should be the Owner of the asset `class`. + /// Origin must be Signed and the sender should be the Owner of the `collection`. /// - /// - `class`: The asset class whose owner should be changed. - /// - `owner`: The new Owner of this asset class. They must have called - /// `set_accept_ownership` with `class` in order for this operation to succeed. + /// - `collection`: The collection whose owner should be changed. + /// - `owner`: The new Owner of this collection. They must have called + /// `set_accept_ownership` with `collection` in order for this operation to succeed. /// /// Emits `OwnerChanged`. /// @@ -767,17 +781,17 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::transfer_ownership())] pub fn transfer_ownership( origin: OriginFor, - class: T::ClassId, + collection: T::CollectionId, owner: ::Source, ) -> DispatchResult { let origin = ensure_signed(origin)?; let owner = T::Lookup::lookup(owner)?; - let acceptable_class = OwnershipAcceptance::::get(&owner); - ensure!(acceptable_class.as_ref() == Some(&class), Error::::Unaccepted); + let acceptable_collection = OwnershipAcceptance::::get(&owner); + ensure!(acceptable_collection.as_ref() == Some(&collection), Error::::Unaccepted); - Class::::try_mutate(class, |maybe_details| { - let details = maybe_details.as_mut().ok_or(Error::::UnknownClass)?; + Collection::::try_mutate(collection, |maybe_details| { + let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; ensure!(origin == details.owner, Error::::NoPermission); if details.owner == owner { return Ok(()) @@ -790,24 +804,24 @@ pub mod pallet { details.total_deposit, Reserved, )?; - ClassAccount::::remove(&details.owner, &class); - ClassAccount::::insert(&owner, &class, ()); + CollectionAccount::::remove(&details.owner, &collection); + CollectionAccount::::insert(&owner, &collection, ()); details.owner = owner.clone(); OwnershipAcceptance::::remove(&owner); - Self::deposit_event(Event::OwnerChanged { class, new_owner: owner }); + Self::deposit_event(Event::OwnerChanged { collection, new_owner: owner }); Ok(()) }) } - /// Change the Issuer, Admin and Freezer of an asset class. + /// Change the Issuer, Admin and Freezer of a collection. /// - /// Origin must be Signed and the sender should be the Owner of the asset `class`. + /// Origin must be Signed and the sender should be the Owner of the `collection`. /// - /// - `class`: The asset class whose team should be changed. - /// - `issuer`: The new Issuer of this asset class. - /// - `admin`: The new Admin of this asset class. - /// - `freezer`: The new Freezer of this asset class. + /// - `collection`: The collection whose team should be changed. + /// - `issuer`: The new Issuer of this collection. + /// - `admin`: The new Admin of this collection. + /// - `freezer`: The new Freezer of this collection. /// /// Emits `TeamChanged`. /// @@ -815,7 +829,7 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::set_team())] pub fn set_team( origin: OriginFor, - class: T::ClassId, + collection: T::CollectionId, issuer: ::Source, admin: ::Source, freezer: ::Source, @@ -825,26 +839,26 @@ pub mod pallet { let admin = T::Lookup::lookup(admin)?; let freezer = T::Lookup::lookup(freezer)?; - Class::::try_mutate(class, |maybe_details| { - let details = maybe_details.as_mut().ok_or(Error::::UnknownClass)?; + Collection::::try_mutate(collection, |maybe_details| { + let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; ensure!(origin == details.owner, Error::::NoPermission); details.issuer = issuer.clone(); details.admin = admin.clone(); details.freezer = freezer.clone(); - Self::deposit_event(Event::TeamChanged { class, issuer, admin, freezer }); + Self::deposit_event(Event::TeamChanged { collection, issuer, admin, freezer }); Ok(()) }) } - /// Approve an instance to be transferred by a delegated third-party account. + /// Approve an item to be transferred by a delegated third-party account. /// - /// Origin must be Signed and must be the owner of the asset `instance`. + /// Origin must be Signed and must be the owner of the `item`. /// - /// - `class`: The class of the asset to be approved for delegated transfer. - /// - `instance`: The instance of the asset to be approved for delegated transfer. - /// - `delegate`: The account to delegate permission to transfer the asset. + /// - `collection`: The collection of the item to be approved for delegated transfer. + /// - `item`: The item of the item to be approved for delegated transfer. + /// - `delegate`: The account to delegate permission to transfer the item. /// /// Emits `ApprovedTransfer` on success. /// @@ -852,8 +866,8 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::approve_transfer())] pub fn approve_transfer( origin: OriginFor, - class: T::ClassId, - instance: T::InstanceId, + collection: T::CollectionId, + item: T::ItemId, delegate: ::Source, ) -> DispatchResult { let maybe_check: Option = T::ForceOrigin::try_origin(origin) @@ -862,22 +876,23 @@ pub mod pallet { let delegate = T::Lookup::lookup(delegate)?; - let class_details = Class::::get(&class).ok_or(Error::::UnknownClass)?; + let collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; let mut details = - Asset::::get(&class, &instance).ok_or(Error::::UnknownClass)?; + Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; if let Some(check) = maybe_check { - let permitted = check == class_details.admin || check == details.owner; + let permitted = check == collection_details.admin || check == details.owner; ensure!(permitted, Error::::NoPermission); } details.approved = Some(delegate); - Asset::::insert(&class, &instance, &details); + Item::::insert(&collection, &item, &details); let delegate = details.approved.expect("set as Some above; qed"); Self::deposit_event(Event::ApprovedTransfer { - class, - instance, + collection, + item, owner: details.owner, delegate, }); @@ -885,16 +900,16 @@ pub mod pallet { Ok(()) } - /// Cancel the prior approval for the transfer of an asset by a delegate. + /// Cancel the prior approval for the transfer of an item by a delegate. /// /// Origin must be either: /// - the `Force` origin; - /// - `Signed` with the signer being the Admin of the asset `class`; - /// - `Signed` with the signer being the Owner of the asset `instance`; + /// - `Signed` with the signer being the Admin of the `collection`; + /// - `Signed` with the signer being the Owner of the `item`; /// /// Arguments: - /// - `class`: The class of the asset of whose approval will be cancelled. - /// - `instance`: The instance of the asset of whose approval will be cancelled. + /// - `collection`: The collection of the item of whose approval will be cancelled. + /// - `item`: The item of the item of whose approval will be cancelled. /// - `maybe_check_delegate`: If `Some` will ensure that the given account is the one to /// which permission of transfer is delegated. /// @@ -904,19 +919,20 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::cancel_approval())] pub fn cancel_approval( origin: OriginFor, - class: T::ClassId, - instance: T::InstanceId, + collection: T::CollectionId, + item: T::ItemId, maybe_check_delegate: Option<::Source>, ) -> DispatchResult { let maybe_check: Option = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; - let class_details = Class::::get(&class).ok_or(Error::::UnknownClass)?; + let collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; let mut details = - Asset::::get(&class, &instance).ok_or(Error::::UnknownClass)?; + Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; if let Some(check) = maybe_check { - let permitted = check == class_details.admin || check == details.owner; + let permitted = check == collection_details.admin || check == details.owner; ensure!(permitted, Error::::NoPermission); } let maybe_check_delegate = maybe_check_delegate.map(T::Lookup::lookup).transpose()?; @@ -925,10 +941,10 @@ pub mod pallet { ensure!(check_delegate == old, Error::::WrongDelegate); } - Asset::::insert(&class, &instance, &details); + Item::::insert(&collection, &item, &details); Self::deposit_event(Event::ApprovalCancelled { - class, - instance, + collection, + item, owner: details.owner, delegate: old, }); @@ -936,27 +952,26 @@ pub mod pallet { Ok(()) } - /// Alter the attributes of a given asset. + /// Alter the attributes of a given item. /// /// Origin must be `ForceOrigin`. /// - /// - `class`: The identifier of the asset. - /// - `owner`: The new Owner of this asset. - /// - `issuer`: The new Issuer of this asset. - /// - `admin`: The new Admin of this asset. - /// - `freezer`: The new Freezer of this asset. - /// - `free_holding`: Whether a deposit is taken for holding an instance of this asset - /// class. - /// - `is_frozen`: Whether this asset class is frozen except for permissioned/admin + /// - `collection`: The identifier of the item. + /// - `owner`: The new Owner of this item. + /// - `issuer`: The new Issuer of this item. + /// - `admin`: The new Admin of this item. + /// - `freezer`: The new Freezer of this item. + /// - `free_holding`: Whether a deposit is taken for holding an item of this collection. + /// - `is_frozen`: Whether this collection is frozen except for permissioned/admin /// instructions. /// - /// Emits `AssetStatusChanged` with the identity of the asset. + /// Emits `ItemStatusChanged` with the identity of the item. /// /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::force_asset_status())] - pub fn force_asset_status( + #[pallet::weight(T::WeightInfo::force_item_status())] + pub fn force_item_status( origin: OriginFor, - class: T::ClassId, + collection: T::CollectionId, owner: ::Source, issuer: ::Source, admin: ::Source, @@ -966,36 +981,36 @@ pub mod pallet { ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; - Class::::try_mutate(class, |maybe_asset| { - let mut asset = maybe_asset.take().ok_or(Error::::UnknownClass)?; - let old_owner = asset.owner; + Collection::::try_mutate(collection, |maybe_item| { + let mut item = maybe_item.take().ok_or(Error::::UnknownCollection)?; + let old_owner = item.owner; let new_owner = T::Lookup::lookup(owner)?; - asset.owner = new_owner.clone(); - asset.issuer = T::Lookup::lookup(issuer)?; - asset.admin = T::Lookup::lookup(admin)?; - asset.freezer = T::Lookup::lookup(freezer)?; - asset.free_holding = free_holding; - asset.is_frozen = is_frozen; - *maybe_asset = Some(asset); - ClassAccount::::remove(&old_owner, &class); - ClassAccount::::insert(&new_owner, &class, ()); - - Self::deposit_event(Event::AssetStatusChanged { class }); + item.owner = new_owner.clone(); + item.issuer = T::Lookup::lookup(issuer)?; + item.admin = T::Lookup::lookup(admin)?; + item.freezer = T::Lookup::lookup(freezer)?; + item.free_holding = free_holding; + item.is_frozen = is_frozen; + *maybe_item = Some(item); + CollectionAccount::::remove(&old_owner, &collection); + CollectionAccount::::insert(&new_owner, &collection, ()); + + Self::deposit_event(Event::ItemStatusChanged { collection }); Ok(()) }) } - /// Set an attribute for an asset class or instance. + /// Set an attribute for a collection or item. /// /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the - /// asset `class`. + /// `collection`. /// /// If the origin is Signed, then funds of signer are reserved according to the formula: /// `MetadataDepositBase + DepositPerByte * (key.len + value.len)` taking into /// account any already reserved funds. /// - /// - `class`: The identifier of the asset class whose instance's metadata to set. - /// - `maybe_instance`: The identifier of the asset instance whose metadata to set. + /// - `collection`: The identifier of the collection whose item's metadata to set. + /// - `maybe_item`: The identifier of the item whose metadata to set. /// - `key`: The key of the attribute. /// - `value`: The value to which to set the attribute. /// @@ -1005,8 +1020,8 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::set_attribute())] pub fn set_attribute( origin: OriginFor, - class: T::ClassId, - maybe_instance: Option, + collection: T::CollectionId, + maybe_item: Option, key: BoundedVec, value: BoundedVec, ) -> DispatchResult { @@ -1014,52 +1029,51 @@ pub mod pallet { .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; - let mut class_details = - Class::::get(&class).ok_or(Error::::UnknownClass)?; + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &class_details.owner, Error::::NoPermission); + ensure!(check_owner == &collection_details.owner, Error::::NoPermission); } - let maybe_is_frozen = match maybe_instance { - None => ClassMetadataOf::::get(class).map(|v| v.is_frozen), - Some(instance) => - InstanceMetadataOf::::get(class, instance).map(|v| v.is_frozen), + let maybe_is_frozen = match maybe_item { + None => CollectionMetadataOf::::get(collection).map(|v| v.is_frozen), + Some(item) => ItemMetadataOf::::get(collection, item).map(|v| v.is_frozen), }; ensure!(!maybe_is_frozen.unwrap_or(false), Error::::Frozen); - let attribute = Attribute::::get((class, maybe_instance, &key)); + let attribute = Attribute::::get((collection, maybe_item, &key)); if attribute.is_none() { - class_details.attributes.saturating_inc(); + collection_details.attributes.saturating_inc(); } let old_deposit = attribute.map_or(Zero::zero(), |m| m.1); - class_details.total_deposit.saturating_reduce(old_deposit); + collection_details.total_deposit.saturating_reduce(old_deposit); let mut deposit = Zero::zero(); - if !class_details.free_holding && maybe_check_owner.is_some() { + if !collection_details.free_holding && maybe_check_owner.is_some() { deposit = T::DepositPerByte::get() .saturating_mul(((key.len() + value.len()) as u32).into()) .saturating_add(T::AttributeDepositBase::get()); } - class_details.total_deposit.saturating_accrue(deposit); + collection_details.total_deposit.saturating_accrue(deposit); if deposit > old_deposit { - T::Currency::reserve(&class_details.owner, deposit - old_deposit)?; + T::Currency::reserve(&collection_details.owner, deposit - old_deposit)?; } else if deposit < old_deposit { - T::Currency::unreserve(&class_details.owner, old_deposit - deposit); + T::Currency::unreserve(&collection_details.owner, old_deposit - deposit); } - Attribute::::insert((&class, maybe_instance, &key), (&value, deposit)); - Class::::insert(class, &class_details); - Self::deposit_event(Event::AttributeSet { class, maybe_instance, key, value }); + Attribute::::insert((&collection, maybe_item, &key), (&value, deposit)); + Collection::::insert(collection, &collection_details); + Self::deposit_event(Event::AttributeSet { collection, maybe_item, key, value }); Ok(()) } - /// Clear an attribute for an asset class or instance. + /// Clear an attribute for a collection or item. /// /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the - /// asset `class`. + /// `collection`. /// - /// Any deposit is freed for the asset class owner. + /// Any deposit is freed for the collection's owner. /// - /// - `class`: The identifier of the asset class whose instance's metadata to clear. - /// - `maybe_instance`: The identifier of the asset instance whose metadata to clear. + /// - `collection`: The identifier of the collection whose item's metadata to clear. + /// - `maybe_item`: The identifier of the item whose metadata to clear. /// - `key`: The key of the attribute. /// /// Emits `AttributeCleared`. @@ -1068,48 +1082,47 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::clear_attribute())] pub fn clear_attribute( origin: OriginFor, - class: T::ClassId, - maybe_instance: Option, + collection: T::CollectionId, + maybe_item: Option, key: BoundedVec, ) -> DispatchResult { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; - let mut class_details = - Class::::get(&class).ok_or(Error::::UnknownClass)?; + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &class_details.owner, Error::::NoPermission); + ensure!(check_owner == &collection_details.owner, Error::::NoPermission); } - let maybe_is_frozen = match maybe_instance { - None => ClassMetadataOf::::get(class).map(|v| v.is_frozen), - Some(instance) => - InstanceMetadataOf::::get(class, instance).map(|v| v.is_frozen), + let maybe_is_frozen = match maybe_item { + None => CollectionMetadataOf::::get(collection).map(|v| v.is_frozen), + Some(item) => ItemMetadataOf::::get(collection, item).map(|v| v.is_frozen), }; ensure!(!maybe_is_frozen.unwrap_or(false), Error::::Frozen); - if let Some((_, deposit)) = Attribute::::take((class, maybe_instance, &key)) { - class_details.attributes.saturating_dec(); - class_details.total_deposit.saturating_reduce(deposit); - T::Currency::unreserve(&class_details.owner, deposit); - Class::::insert(class, &class_details); - Self::deposit_event(Event::AttributeCleared { class, maybe_instance, key }); + if let Some((_, deposit)) = Attribute::::take((collection, maybe_item, &key)) { + collection_details.attributes.saturating_dec(); + collection_details.total_deposit.saturating_reduce(deposit); + T::Currency::unreserve(&collection_details.owner, deposit); + Collection::::insert(collection, &collection_details); + Self::deposit_event(Event::AttributeCleared { collection, maybe_item, key }); } Ok(()) } - /// Set the metadata for an asset instance. + /// Set the metadata for an item. /// /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the - /// asset `class`. + /// `collection`. /// /// If the origin is Signed, then funds of signer are reserved according to the formula: /// `MetadataDepositBase + DepositPerByte * data.len` taking into /// account any already reserved funds. /// - /// - `class`: The identifier of the asset class whose instance's metadata to set. - /// - `instance`: The identifier of the asset instance whose metadata to set. - /// - `data`: The general information of this asset. Limited in length by `StringLimit`. + /// - `collection`: The identifier of the collection whose item's metadata to set. + /// - `item`: The identifier of the item whose metadata to set. + /// - `data`: The general information of this item. Limited in length by `StringLimit`. /// - `is_frozen`: Whether the metadata should be frozen against further changes. /// /// Emits `MetadataSet`. @@ -1118,8 +1131,8 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::set_metadata())] pub fn set_metadata( origin: OriginFor, - class: T::ClassId, - instance: T::InstanceId, + collection: T::CollectionId, + item: T::ItemId, data: BoundedVec, is_frozen: bool, ) -> DispatchResult { @@ -1127,52 +1140,52 @@ pub mod pallet { .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; - let mut class_details = - Class::::get(&class).ok_or(Error::::UnknownClass)?; + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &class_details.owner, Error::::NoPermission); + ensure!(check_owner == &collection_details.owner, Error::::NoPermission); } - InstanceMetadataOf::::try_mutate_exists(class, instance, |metadata| { + ItemMetadataOf::::try_mutate_exists(collection, item, |metadata| { let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen); ensure!(maybe_check_owner.is_none() || !was_frozen, Error::::Frozen); if metadata.is_none() { - class_details.instance_metadatas.saturating_inc(); + collection_details.item_metadatas.saturating_inc(); } let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); - class_details.total_deposit.saturating_reduce(old_deposit); + collection_details.total_deposit.saturating_reduce(old_deposit); let mut deposit = Zero::zero(); - if !class_details.free_holding && maybe_check_owner.is_some() { + if !collection_details.free_holding && maybe_check_owner.is_some() { deposit = T::DepositPerByte::get() .saturating_mul(((data.len()) as u32).into()) .saturating_add(T::MetadataDepositBase::get()); } if deposit > old_deposit { - T::Currency::reserve(&class_details.owner, deposit - old_deposit)?; + T::Currency::reserve(&collection_details.owner, deposit - old_deposit)?; } else if deposit < old_deposit { - T::Currency::unreserve(&class_details.owner, old_deposit - deposit); + T::Currency::unreserve(&collection_details.owner, old_deposit - deposit); } - class_details.total_deposit.saturating_accrue(deposit); + collection_details.total_deposit.saturating_accrue(deposit); - *metadata = Some(InstanceMetadata { deposit, data: data.clone(), is_frozen }); + *metadata = Some(ItemMetadata { deposit, data: data.clone(), is_frozen }); - Class::::insert(&class, &class_details); - Self::deposit_event(Event::MetadataSet { class, instance, data, is_frozen }); + Collection::::insert(&collection, &collection_details); + Self::deposit_event(Event::MetadataSet { collection, item, data, is_frozen }); Ok(()) }) } - /// Clear the metadata for an asset instance. + /// Clear the metadata for an item. /// /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the - /// asset `instance`. + /// `item`. /// - /// Any deposit is freed for the asset class owner. + /// Any deposit is freed for the collection's owner. /// - /// - `class`: The identifier of the asset class whose instance's metadata to clear. - /// - `instance`: The identifier of the asset instance whose metadata to clear. + /// - `collection`: The identifier of the collection whose item's metadata to clear. + /// - `item`: The identifier of the item whose metadata to clear. /// /// Emits `MetadataCleared`. /// @@ -1180,56 +1193,56 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::clear_metadata())] pub fn clear_metadata( origin: OriginFor, - class: T::ClassId, - instance: T::InstanceId, + collection: T::CollectionId, + item: T::ItemId, ) -> DispatchResult { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; - let mut class_details = - Class::::get(&class).ok_or(Error::::UnknownClass)?; + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &class_details.owner, Error::::NoPermission); + ensure!(check_owner == &collection_details.owner, Error::::NoPermission); } - InstanceMetadataOf::::try_mutate_exists(class, instance, |metadata| { + ItemMetadataOf::::try_mutate_exists(collection, item, |metadata| { let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen); ensure!(maybe_check_owner.is_none() || !was_frozen, Error::::Frozen); if metadata.is_some() { - class_details.instance_metadatas.saturating_dec(); + collection_details.item_metadatas.saturating_dec(); } - let deposit = metadata.take().ok_or(Error::::UnknownClass)?.deposit; - T::Currency::unreserve(&class_details.owner, deposit); - class_details.total_deposit.saturating_reduce(deposit); + let deposit = metadata.take().ok_or(Error::::UnknownCollection)?.deposit; + T::Currency::unreserve(&collection_details.owner, deposit); + collection_details.total_deposit.saturating_reduce(deposit); - Class::::insert(&class, &class_details); - Self::deposit_event(Event::MetadataCleared { class, instance }); + Collection::::insert(&collection, &collection_details); + Self::deposit_event(Event::MetadataCleared { collection, item }); Ok(()) }) } - /// Set the metadata for an asset class. + /// Set the metadata for a collection. /// /// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Owner of - /// the asset `class`. + /// the `collection`. /// /// If the origin is `Signed`, then funds of signer are reserved according to the formula: /// `MetadataDepositBase + DepositPerByte * data.len` taking into /// account any already reserved funds. /// - /// - `class`: The identifier of the asset whose metadata to update. - /// - `data`: The general information of this asset. Limited in length by `StringLimit`. + /// - `collection`: The identifier of the item whose metadata to update. + /// - `data`: The general information of this item. Limited in length by `StringLimit`. /// - `is_frozen`: Whether the metadata should be frozen against further changes. /// - /// Emits `ClassMetadataSet`. + /// Emits `CollectionMetadataSet`. /// /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::set_class_metadata())] - pub fn set_class_metadata( + #[pallet::weight(T::WeightInfo::set_collection_metadata())] + pub fn set_collection_metadata( origin: OriginFor, - class: T::ClassId, + collection: T::CollectionId, data: BoundedVec, is_frozen: bool, ) -> DispatchResult { @@ -1237,12 +1250,13 @@ pub mod pallet { .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; - let mut details = Class::::get(&class).ok_or(Error::::UnknownClass)?; + let mut details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; if let Some(check_owner) = &maybe_check_owner { ensure!(check_owner == &details.owner, Error::::NoPermission); } - ClassMetadataOf::::try_mutate_exists(class, |metadata| { + CollectionMetadataOf::::try_mutate_exists(collection, |metadata| { let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen); ensure!(maybe_check_owner.is_none() || !was_frozen, Error::::Frozen); @@ -1261,67 +1275,71 @@ pub mod pallet { } details.total_deposit.saturating_accrue(deposit); - Class::::insert(&class, details); + Collection::::insert(&collection, details); - *metadata = Some(ClassMetadata { deposit, data: data.clone(), is_frozen }); + *metadata = Some(CollectionMetadata { deposit, data: data.clone(), is_frozen }); - Self::deposit_event(Event::ClassMetadataSet { class, data, is_frozen }); + Self::deposit_event(Event::CollectionMetadataSet { collection, data, is_frozen }); Ok(()) }) } - /// Clear the metadata for an asset class. + /// Clear the metadata for a collection. /// /// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Owner of - /// the asset `class`. + /// the `collection`. /// - /// Any deposit is freed for the asset class owner. + /// Any deposit is freed for the collection's owner. /// - /// - `class`: The identifier of the asset class whose metadata to clear. + /// - `collection`: The identifier of the collection whose metadata to clear. /// - /// Emits `ClassMetadataCleared`. + /// Emits `CollectionMetadataCleared`. /// /// Weight: `O(1)` - #[pallet::weight(T::WeightInfo::clear_class_metadata())] - pub fn clear_class_metadata(origin: OriginFor, class: T::ClassId) -> DispatchResult { + #[pallet::weight(T::WeightInfo::clear_collection_metadata())] + pub fn clear_collection_metadata( + origin: OriginFor, + collection: T::CollectionId, + ) -> DispatchResult { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some))?; - let details = Class::::get(&class).ok_or(Error::::UnknownClass)?; + let details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; if let Some(check_owner) = &maybe_check_owner { ensure!(check_owner == &details.owner, Error::::NoPermission); } - ClassMetadataOf::::try_mutate_exists(class, |metadata| { + CollectionMetadataOf::::try_mutate_exists(collection, |metadata| { let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen); ensure!(maybe_check_owner.is_none() || !was_frozen, Error::::Frozen); - let deposit = metadata.take().ok_or(Error::::UnknownClass)?.deposit; + let deposit = metadata.take().ok_or(Error::::UnknownCollection)?.deposit; T::Currency::unreserve(&details.owner, deposit); - Self::deposit_event(Event::ClassMetadataCleared { class }); + Self::deposit_event(Event::CollectionMetadataCleared { collection }); Ok(()) }) } /// Set (or reset) the acceptance of ownership for a particular account. /// - /// Origin must be `Signed` and if `maybe_class` is `Some`, then the signer must have a + /// Origin must be `Signed` and if `maybe_collection` is `Some`, then the signer must have a /// provider reference. /// - /// - `maybe_class`: The identifier of the asset class whose ownership the signer is willing - /// to accept, or if `None`, an indication that the signer is willing to accept no + /// - `maybe_collection`: The identifier of the collection whose ownership the signer is + /// willing to accept, or if `None`, an indication that the signer is willing to accept no /// ownership transferal. /// /// Emits `OwnershipAcceptanceChanged`. #[pallet::weight(T::WeightInfo::set_accept_ownership())] pub fn set_accept_ownership( origin: OriginFor, - maybe_class: Option, + maybe_collection: Option, ) -> DispatchResult { let who = ensure_signed(origin)?; let old = OwnershipAcceptance::::get(&who); - match (old.is_some(), maybe_class.is_some()) { + match (old.is_some(), maybe_collection.is_some()) { (false, true) => { frame_system::Pallet::::inc_consumers(&who)?; }, @@ -1330,12 +1348,12 @@ pub mod pallet { }, _ => {}, } - if let Some(class) = maybe_class.as_ref() { - OwnershipAcceptance::::insert(&who, class); + if let Some(collection) = maybe_collection.as_ref() { + OwnershipAcceptance::::insert(&who, collection); } else { OwnershipAcceptance::::remove(&who); } - Self::deposit_event(Event::OwnershipAcceptanceChanged { who, maybe_class }); + Self::deposit_event(Event::OwnershipAcceptanceChanged { who, maybe_collection }); Ok(()) } } diff --git a/frame/uniques/src/migration.rs b/frame/uniques/src/migration.rs index 2bacfc8f43b61..d301f0a3d1eb1 100644 --- a/frame/uniques/src/migration.rs +++ b/frame/uniques/src/migration.rs @@ -34,8 +34,8 @@ pub fn migrate_to_v1, I: 'static, P: GetStorageVersion + PalletInfo if on_chain_storage_version < 1 { let mut count = 0; - for (class, detail) in Class::::iter() { - ClassAccount::::insert(&detail.owner, &class, ()); + for (collection, detail) in Collection::::iter() { + CollectionAccount::::insert(&detail.owner, &collection, ()); count += 1; } StorageVersion::new(1).put::

(); diff --git a/frame/uniques/src/mock.rs b/frame/uniques/src/mock.rs index f32540f6ef7ba..ff7b791de4950 100644 --- a/frame/uniques/src/mock.rs +++ b/frame/uniques/src/mock.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Test environment for Assets pallet. +//! Test environment for Uniques pallet. use super::*; use crate as pallet_uniques; @@ -86,14 +86,14 @@ impl pallet_balances::Config for Test { impl Config for Test { type Event = Event; - type ClassId = u32; - type InstanceId = u32; + type CollectionId = u32; + type ItemId = u32; type Currency = Balances; type CreateOrigin = AsEnsureOriginWithArg>; type ForceOrigin = frame_system::EnsureRoot; type Locker = (); - type ClassDeposit = ConstU64<2>; - type InstanceDeposit = ConstU64<1>; + type CollectionDeposit = ConstU64<2>; + type ItemDeposit = ConstU64<1>; type MetadataDepositBase = ConstU64<1>; type AttributeDepositBase = ConstU64<1>; type DepositPerByte = ConstU64<1>; diff --git a/frame/uniques/src/tests.rs b/frame/uniques/src/tests.rs index 364073ad37cde..6bd397eefa7ef 100644 --- a/frame/uniques/src/tests.rs +++ b/frame/uniques/src/tests.rs @@ -23,13 +23,13 @@ use frame_support::{assert_noop, assert_ok, traits::Currency}; use pallet_balances::Error as BalancesError; use sp_std::prelude::*; -fn assets() -> Vec<(u64, u32, u32)> { +fn items() -> Vec<(u64, u32, u32)> { let mut r: Vec<_> = Account::::iter().map(|x| x.0).collect(); r.sort(); - let mut s: Vec<_> = Asset::::iter().map(|x| (x.2.owner, x.0, x.1)).collect(); + let mut s: Vec<_> = Item::::iter().map(|x| (x.2.owner, x.0, x.1)).collect(); s.sort(); assert_eq!(r, s); - for class in Asset::::iter() + for collection in Item::::iter() .map(|x| x.0) .scan(None, |s, item| { if s.map_or(false, |last| last == item) { @@ -41,17 +41,17 @@ fn assets() -> Vec<(u64, u32, u32)> { }) .flatten() { - let details = Class::::get(class).unwrap(); - let instances = Asset::::iter_prefix(class).count() as u32; - assert_eq!(details.instances, instances); + let details = Collection::::get(collection).unwrap(); + let items = Item::::iter_prefix(collection).count() as u32; + assert_eq!(details.items, items); } r } -fn classes() -> Vec<(u64, u32)> { - let mut r: Vec<_> = ClassAccount::::iter().map(|x| (x.0, x.1)).collect(); +fn collections() -> Vec<(u64, u32)> { + let mut r: Vec<_> = CollectionAccount::::iter().map(|x| (x.0, x.1)).collect(); r.sort(); - let mut s: Vec<_> = Class::::iter().map(|x| (x.1.owner, x.0)).collect(); + let mut s: Vec<_> = Collection::::iter().map(|x| (x.1.owner, x.0)).collect(); s.sort(); assert_eq!(r, s); r @@ -63,8 +63,8 @@ macro_rules! bvec { } } -fn attributes(class: u32) -> Vec<(Option, Vec, Vec)> { - let mut s: Vec<_> = Attribute::::iter_prefix((class,)) +fn attributes(collection: u32) -> Vec<(Option, Vec, Vec)> { + let mut s: Vec<_> = Attribute::::iter_prefix((collection,)) .map(|(k, v)| (k.0, k.1.into(), v.0.into())) .collect(); s.sort(); @@ -74,7 +74,7 @@ fn attributes(class: u32) -> Vec<(Option, Vec, Vec)> { #[test] fn basic_setup_works() { new_test_ext().execute_with(|| { - assert_eq!(assets(), vec![]); + assert_eq!(items(), vec![]); }); } @@ -82,14 +82,14 @@ fn basic_setup_works() { fn basic_minting_should_work() { new_test_ext().execute_with(|| { assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); - assert_eq!(classes(), vec![(1, 0)]); + assert_eq!(collections(), vec![(1, 0)]); assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); - assert_eq!(assets(), vec![(1, 0, 42)]); + assert_eq!(items(), vec![(1, 0, 42)]); assert_ok!(Uniques::force_create(Origin::root(), 1, 2, true)); - assert_eq!(classes(), vec![(1, 0), (2, 1)]); + assert_eq!(collections(), vec![(1, 0), (2, 1)]); assert_ok!(Uniques::mint(Origin::signed(2), 1, 69, 1)); - assert_eq!(assets(), vec![(1, 0, 42), (1, 1, 69)]); + assert_eq!(items(), vec![(1, 0, 42), (1, 1, 69)]); }); } @@ -99,40 +99,40 @@ fn lifecycle_should_work() { Balances::make_free_balance_be(&1, 100); assert_ok!(Uniques::create(Origin::signed(1), 0, 1)); assert_eq!(Balances::reserved_balance(&1), 2); - assert_eq!(classes(), vec![(1, 0)]); - assert_ok!(Uniques::set_class_metadata(Origin::signed(1), 0, bvec![0, 0], false)); + assert_eq!(collections(), vec![(1, 0)]); + assert_ok!(Uniques::set_collection_metadata(Origin::signed(1), 0, bvec![0, 0], false)); assert_eq!(Balances::reserved_balance(&1), 5); - assert!(ClassMetadataOf::::contains_key(0)); + assert!(CollectionMetadataOf::::contains_key(0)); assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 10)); assert_eq!(Balances::reserved_balance(&1), 6); assert_ok!(Uniques::mint(Origin::signed(1), 0, 69, 20)); assert_eq!(Balances::reserved_balance(&1), 7); - assert_eq!(assets(), vec![(10, 0, 42), (20, 0, 69)]); - assert_eq!(Class::::get(0).unwrap().instances, 2); - assert_eq!(Class::::get(0).unwrap().instance_metadatas, 0); + assert_eq!(items(), vec![(10, 0, 42), (20, 0, 69)]); + assert_eq!(Collection::::get(0).unwrap().items, 2); + assert_eq!(Collection::::get(0).unwrap().item_metadatas, 0); assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 42, bvec![42, 42], false)); assert_eq!(Balances::reserved_balance(&1), 10); - assert!(InstanceMetadataOf::::contains_key(0, 42)); + assert!(ItemMetadataOf::::contains_key(0, 42)); assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 69, bvec![69, 69], false)); assert_eq!(Balances::reserved_balance(&1), 13); - assert!(InstanceMetadataOf::::contains_key(0, 69)); + assert!(ItemMetadataOf::::contains_key(0, 69)); - let w = Class::::get(0).unwrap().destroy_witness(); - assert_eq!(w.instances, 2); - assert_eq!(w.instance_metadatas, 2); + let w = Collection::::get(0).unwrap().destroy_witness(); + assert_eq!(w.items, 2); + assert_eq!(w.item_metadatas, 2); assert_ok!(Uniques::destroy(Origin::signed(1), 0, w)); assert_eq!(Balances::reserved_balance(&1), 0); - assert!(!Class::::contains_key(0)); - assert!(!Asset::::contains_key(0, 42)); - assert!(!Asset::::contains_key(0, 69)); - assert!(!ClassMetadataOf::::contains_key(0)); - assert!(!InstanceMetadataOf::::contains_key(0, 42)); - assert!(!InstanceMetadataOf::::contains_key(0, 69)); - assert_eq!(classes(), vec![]); - assert_eq!(assets(), vec![]); + assert!(!Collection::::contains_key(0)); + assert!(!Item::::contains_key(0, 42)); + assert!(!Item::::contains_key(0, 69)); + assert!(!CollectionMetadataOf::::contains_key(0)); + assert!(!ItemMetadataOf::::contains_key(0, 42)); + assert!(!ItemMetadataOf::::contains_key(0, 69)); + assert_eq!(collections(), vec![]); + assert_eq!(items(), vec![]); }); } @@ -142,7 +142,7 @@ fn destroy_with_bad_witness_should_not_work() { Balances::make_free_balance_be(&1, 100); assert_ok!(Uniques::create(Origin::signed(1), 0, 1)); - let w = Class::::get(0).unwrap().destroy_witness(); + let w = Collection::::get(0).unwrap().destroy_witness(); assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); assert_noop!(Uniques::destroy(Origin::signed(1), 0, w), Error::::BadWitness); }); @@ -154,8 +154,8 @@ fn mint_should_work() { assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); assert_eq!(Uniques::owner(0, 42).unwrap(), 1); - assert_eq!(classes(), vec![(1, 0)]); - assert_eq!(assets(), vec![(1, 0, 42)]); + assert_eq!(collections(), vec![(1, 0)]); + assert_eq!(items(), vec![(1, 0, 42)]); }); } @@ -166,7 +166,7 @@ fn transfer_should_work() { assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 2)); assert_ok!(Uniques::transfer(Origin::signed(2), 0, 42, 3)); - assert_eq!(assets(), vec![(3, 0, 42)]); + assert_eq!(items(), vec![(3, 0, 42)]); assert_noop!(Uniques::transfer(Origin::signed(2), 0, 42, 4), Error::::NoPermission); assert_ok!(Uniques::approve_transfer(Origin::signed(3), 0, 42, 2)); @@ -183,10 +183,10 @@ fn freezing_should_work() { assert_noop!(Uniques::transfer(Origin::signed(1), 0, 42, 2), Error::::Frozen); assert_ok!(Uniques::thaw(Origin::signed(1), 0, 42)); - assert_ok!(Uniques::freeze_class(Origin::signed(1), 0)); + assert_ok!(Uniques::freeze_collection(Origin::signed(1), 0)); assert_noop!(Uniques::transfer(Origin::signed(1), 0, 42, 2), Error::::Frozen); - assert_ok!(Uniques::thaw_class(Origin::signed(1), 0)); + assert_ok!(Uniques::thaw_collection(Origin::signed(1), 0)); assert_ok!(Uniques::transfer(Origin::signed(1), 0, 42, 2)); }); } @@ -208,7 +208,7 @@ fn origin_guards_should_work() { assert_noop!(Uniques::thaw(Origin::signed(2), 0, 42), Error::::NoPermission); assert_noop!(Uniques::mint(Origin::signed(2), 0, 69, 2), Error::::NoPermission); assert_noop!(Uniques::burn(Origin::signed(2), 0, 42, None), Error::::NoPermission); - let w = Class::::get(0).unwrap().destroy_witness(); + let w = Collection::::get(0).unwrap().destroy_witness(); assert_noop!(Uniques::destroy(Origin::signed(2), 0, w), Error::::NoPermission); }); } @@ -220,7 +220,7 @@ fn transfer_owner_should_work() { Balances::make_free_balance_be(&2, 100); Balances::make_free_balance_be(&3, 100); assert_ok!(Uniques::create(Origin::signed(1), 0, 1)); - assert_eq!(classes(), vec![(1, 0)]); + assert_eq!(collections(), vec![(1, 0)]); assert_noop!( Uniques::transfer_ownership(Origin::signed(1), 0, 2), Error::::Unaccepted @@ -228,7 +228,7 @@ fn transfer_owner_should_work() { assert_ok!(Uniques::set_accept_ownership(Origin::signed(2), Some(0))); assert_ok!(Uniques::transfer_ownership(Origin::signed(1), 0, 2)); - assert_eq!(classes(), vec![(2, 0)]); + assert_eq!(collections(), vec![(2, 0)]); assert_eq!(Balances::total_balance(&1), 98); assert_eq!(Balances::total_balance(&2), 102); assert_eq!(Balances::reserved_balance(&1), 0); @@ -241,12 +241,12 @@ fn transfer_owner_should_work() { ); // Mint and set metadata now and make sure that deposit gets transferred back. - assert_ok!(Uniques::set_class_metadata(Origin::signed(2), 0, bvec![0u8; 20], false)); + assert_ok!(Uniques::set_collection_metadata(Origin::signed(2), 0, bvec![0u8; 20], false)); assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); assert_ok!(Uniques::set_metadata(Origin::signed(2), 0, 42, bvec![0u8; 20], false)); assert_ok!(Uniques::set_accept_ownership(Origin::signed(3), Some(0))); assert_ok!(Uniques::transfer_ownership(Origin::signed(2), 0, 3)); - assert_eq!(classes(), vec![(3, 0)]); + assert_eq!(collections(), vec![(3, 0)]); assert_eq!(Balances::total_balance(&2), 57); assert_eq!(Balances::total_balance(&3), 145); assert_eq!(Balances::reserved_balance(&2), 0); @@ -276,73 +276,76 @@ fn set_team_should_work() { } #[test] -fn set_class_metadata_should_work() { +fn set_collection_metadata_should_work() { new_test_ext().execute_with(|| { - // Cannot add metadata to unknown asset + // Cannot add metadata to unknown item assert_noop!( - Uniques::set_class_metadata(Origin::signed(1), 0, bvec![0u8; 20], false), - Error::::UnknownClass, + Uniques::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 20], false), + Error::::UnknownCollection, ); assert_ok!(Uniques::force_create(Origin::root(), 0, 1, false)); - // Cannot add metadata to unowned asset + // Cannot add metadata to unowned item assert_noop!( - Uniques::set_class_metadata(Origin::signed(2), 0, bvec![0u8; 20], false), + Uniques::set_collection_metadata(Origin::signed(2), 0, bvec![0u8; 20], false), Error::::NoPermission, ); // Successfully add metadata and take deposit Balances::make_free_balance_be(&1, 30); - assert_ok!(Uniques::set_class_metadata(Origin::signed(1), 0, bvec![0u8; 20], false)); + assert_ok!(Uniques::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 20], false)); assert_eq!(Balances::free_balance(&1), 9); - assert!(ClassMetadataOf::::contains_key(0)); + assert!(CollectionMetadataOf::::contains_key(0)); // Force origin works, too. - assert_ok!(Uniques::set_class_metadata(Origin::root(), 0, bvec![0u8; 18], false)); + assert_ok!(Uniques::set_collection_metadata(Origin::root(), 0, bvec![0u8; 18], false)); // Update deposit - assert_ok!(Uniques::set_class_metadata(Origin::signed(1), 0, bvec![0u8; 15], false)); + assert_ok!(Uniques::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 15], false)); assert_eq!(Balances::free_balance(&1), 14); - assert_ok!(Uniques::set_class_metadata(Origin::signed(1), 0, bvec![0u8; 25], false)); + assert_ok!(Uniques::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 25], false)); assert_eq!(Balances::free_balance(&1), 4); // Cannot over-reserve assert_noop!( - Uniques::set_class_metadata(Origin::signed(1), 0, bvec![0u8; 40], false), + Uniques::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 40], false), BalancesError::::InsufficientBalance, ); // Can't set or clear metadata once frozen - assert_ok!(Uniques::set_class_metadata(Origin::signed(1), 0, bvec![0u8; 15], true)); + assert_ok!(Uniques::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 15], true)); assert_noop!( - Uniques::set_class_metadata(Origin::signed(1), 0, bvec![0u8; 15], false), + Uniques::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 15], false), Error::::Frozen, ); - assert_noop!(Uniques::clear_class_metadata(Origin::signed(1), 0), Error::::Frozen); + assert_noop!( + Uniques::clear_collection_metadata(Origin::signed(1), 0), + Error::::Frozen + ); // Clear Metadata - assert_ok!(Uniques::set_class_metadata(Origin::root(), 0, bvec![0u8; 15], false)); + assert_ok!(Uniques::set_collection_metadata(Origin::root(), 0, bvec![0u8; 15], false)); assert_noop!( - Uniques::clear_class_metadata(Origin::signed(2), 0), + Uniques::clear_collection_metadata(Origin::signed(2), 0), Error::::NoPermission ); assert_noop!( - Uniques::clear_class_metadata(Origin::signed(1), 1), - Error::::UnknownClass + Uniques::clear_collection_metadata(Origin::signed(1), 1), + Error::::UnknownCollection ); - assert_ok!(Uniques::clear_class_metadata(Origin::signed(1), 0)); - assert!(!ClassMetadataOf::::contains_key(0)); + assert_ok!(Uniques::clear_collection_metadata(Origin::signed(1), 0)); + assert!(!CollectionMetadataOf::::contains_key(0)); }); } #[test] -fn set_instance_metadata_should_work() { +fn set_item_metadata_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 30); - // Cannot add metadata to unknown asset + // Cannot add metadata to unknown item assert_ok!(Uniques::force_create(Origin::root(), 0, 1, false)); assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); - // Cannot add metadata to unowned asset + // Cannot add metadata to unowned item assert_noop!( Uniques::set_metadata(Origin::signed(2), 0, 42, bvec![0u8; 20], false), Error::::NoPermission, @@ -351,7 +354,7 @@ fn set_instance_metadata_should_work() { // Successfully add metadata and take deposit assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 42, bvec![0u8; 20], false)); assert_eq!(Balances::free_balance(&1), 8); - assert!(InstanceMetadataOf::::contains_key(0, 42)); + assert!(ItemMetadataOf::::contains_key(0, 42)); // Force origin works, too. assert_ok!(Uniques::set_metadata(Origin::root(), 0, 42, bvec![0u8; 18], false)); @@ -384,10 +387,10 @@ fn set_instance_metadata_should_work() { ); assert_noop!( Uniques::clear_metadata(Origin::signed(1), 1, 42), - Error::::UnknownClass + Error::::UnknownCollection ); assert_ok!(Uniques::clear_metadata(Origin::signed(1), 0, 42)); - assert!(!InstanceMetadataOf::::contains_key(0, 42)); + assert!(!ItemMetadataOf::::contains_key(0, 42)); }); } @@ -429,7 +432,7 @@ fn set_attribute_should_work() { ); assert_eq!(Balances::reserved_balance(1), 15); - let w = Class::::get(0).unwrap().destroy_witness(); + let w = Collection::::get(0).unwrap().destroy_witness(); assert_ok!(Uniques::destroy(Origin::signed(1), 0, w)); assert_eq!(attributes(0), vec![]); assert_eq!(Balances::reserved_balance(1), 0); @@ -456,7 +459,7 @@ fn set_attribute_should_respect_freeze() { ); assert_eq!(Balances::reserved_balance(1), 9); - assert_ok!(Uniques::set_class_metadata(Origin::signed(1), 0, bvec![], true)); + assert_ok!(Uniques::set_collection_metadata(Origin::signed(1), 0, bvec![], true)); let e = Error::::Frozen; assert_noop!(Uniques::set_attribute(Origin::signed(1), 0, None, bvec![0], bvec![0]), e); assert_ok!(Uniques::set_attribute(Origin::signed(1), 0, Some(0), bvec![0], bvec![1])); @@ -469,20 +472,20 @@ fn set_attribute_should_respect_freeze() { } #[test] -fn force_asset_status_should_work() { +fn force_item_status_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); assert_ok!(Uniques::force_create(Origin::root(), 0, 1, false)); assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); assert_ok!(Uniques::mint(Origin::signed(1), 0, 69, 2)); - assert_ok!(Uniques::set_class_metadata(Origin::signed(1), 0, bvec![0; 20], false)); + assert_ok!(Uniques::set_collection_metadata(Origin::signed(1), 0, bvec![0; 20], false)); assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 42, bvec![0; 20], false)); assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 69, bvec![0; 20], false)); assert_eq!(Balances::reserved_balance(1), 65); - // force asset status to be free holding - assert_ok!(Uniques::force_asset_status(Origin::root(), 0, 1, 1, 1, 1, true, false)); + // force item status to be free holding + assert_ok!(Uniques::force_item_status(Origin::root(), 0, 1, 1, 1, 1, true, false)); assert_ok!(Uniques::mint(Origin::signed(1), 0, 142, 1)); assert_ok!(Uniques::mint(Origin::signed(1), 0, 169, 2)); assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 142, bvec![0; 20], false)); @@ -498,7 +501,7 @@ fn force_asset_status_should_work() { assert_ok!(Uniques::set_metadata(Origin::signed(1), 0, 69, bvec![0; 20], false)); assert_eq!(Balances::reserved_balance(1), 21); - assert_ok!(Uniques::set_class_metadata(Origin::signed(1), 0, bvec![0; 20], false)); + assert_ok!(Uniques::set_collection_metadata(Origin::signed(1), 0, bvec![0; 20], false)); assert_eq!(Balances::reserved_balance(1), 0); }); } @@ -510,7 +513,10 @@ fn burn_works() { assert_ok!(Uniques::force_create(Origin::root(), 0, 1, false)); assert_ok!(Uniques::set_team(Origin::signed(1), 0, 2, 3, 4)); - assert_noop!(Uniques::burn(Origin::signed(5), 0, 42, Some(5)), Error::::UnknownClass); + assert_noop!( + Uniques::burn(Origin::signed(5), 0, 42, Some(5)), + Error::::UnknownCollection + ); assert_ok!(Uniques::mint(Origin::signed(2), 0, 42, 5)); assert_ok!(Uniques::mint(Origin::signed(2), 0, 69, 5)); @@ -533,7 +539,7 @@ fn approval_lifecycle_works() { assert_ok!(Uniques::approve_transfer(Origin::signed(2), 0, 42, 3)); assert_ok!(Uniques::transfer(Origin::signed(3), 0, 42, 4)); assert_noop!(Uniques::transfer(Origin::signed(3), 0, 42, 3), Error::::NoPermission); - assert!(Asset::::get(0, 42).unwrap().approved.is_none()); + assert!(Item::::get(0, 42).unwrap().approved.is_none()); assert_ok!(Uniques::approve_transfer(Origin::signed(4), 0, 42, 2)); assert_ok!(Uniques::transfer(Origin::signed(2), 0, 42, 2)); @@ -549,11 +555,11 @@ fn cancel_approval_works() { assert_ok!(Uniques::approve_transfer(Origin::signed(2), 0, 42, 3)); assert_noop!( Uniques::cancel_approval(Origin::signed(2), 1, 42, None), - Error::::UnknownClass + Error::::UnknownCollection ); assert_noop!( Uniques::cancel_approval(Origin::signed(2), 0, 43, None), - Error::::UnknownClass + Error::::UnknownCollection ); assert_noop!( Uniques::cancel_approval(Origin::signed(3), 0, 42, None), @@ -581,11 +587,11 @@ fn cancel_approval_works_with_admin() { assert_ok!(Uniques::approve_transfer(Origin::signed(2), 0, 42, 3)); assert_noop!( Uniques::cancel_approval(Origin::signed(1), 1, 42, None), - Error::::UnknownClass + Error::::UnknownCollection ); assert_noop!( Uniques::cancel_approval(Origin::signed(1), 0, 43, None), - Error::::UnknownClass + Error::::UnknownCollection ); assert_noop!( Uniques::cancel_approval(Origin::signed(1), 0, 42, Some(4)), @@ -609,11 +615,11 @@ fn cancel_approval_works_with_force() { assert_ok!(Uniques::approve_transfer(Origin::signed(2), 0, 42, 3)); assert_noop!( Uniques::cancel_approval(Origin::root(), 1, 42, None), - Error::::UnknownClass + Error::::UnknownCollection ); assert_noop!( Uniques::cancel_approval(Origin::root(), 0, 43, None), - Error::::UnknownClass + Error::::UnknownCollection ); assert_noop!( Uniques::cancel_approval(Origin::root(), 0, 42, Some(4)), diff --git a/frame/uniques/src/types.rs b/frame/uniques/src/types.rs index b5aee6912fec2..d7706fd7e3a58 100644 --- a/frame/uniques/src/types.rs +++ b/frame/uniques/src/types.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Various basic types for use in the assets pallet. +//! Various basic types for use in the Uniques pallet. use super::*; use frame_support::{ @@ -26,13 +26,13 @@ use scale_info::TypeInfo; pub(super) type DepositBalanceOf = <>::Currency as Currency<::AccountId>>::Balance; -pub(super) type ClassDetailsFor = - ClassDetails<::AccountId, DepositBalanceOf>; -pub(super) type InstanceDetailsFor = - InstanceDetails<::AccountId, DepositBalanceOf>; +pub(super) type CollectionDetailsFor = + CollectionDetails<::AccountId, DepositBalanceOf>; +pub(super) type ItemDetailsFor = + ItemDetails<::AccountId, DepositBalanceOf>; #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] -pub struct ClassDetails { +pub struct CollectionDetails { /// Can change `owner`, `issuer`, `freezer` and `admin` accounts. pub(super) owner: AccountId, /// Can mint tokens. @@ -41,55 +41,55 @@ pub struct ClassDetails { pub(super) admin: AccountId, /// Can freeze tokens. pub(super) freezer: AccountId, - /// The total balance deposited for the all storage associated with this asset class. Used by - /// `destroy`. + /// The total balance deposited for the all storage associated with this collection. + /// Used by `destroy`. pub(super) total_deposit: DepositBalance, - /// If `true`, then no deposit is needed to hold instances of this class. + /// If `true`, then no deposit is needed to hold items of this collection. pub(super) free_holding: bool, - /// The total number of outstanding instances of this asset class. - pub(super) instances: u32, - /// The total number of outstanding instance metadata of this asset class. - pub(super) instance_metadatas: u32, - /// The total number of attributes for this asset class. + /// The total number of outstanding items of this collection. + pub(super) items: u32, + /// The total number of outstanding item metadata of this collection. + pub(super) item_metadatas: u32, + /// The total number of attributes for this collection. pub(super) attributes: u32, - /// Whether the asset is frozen for non-admin transfers. + /// Whether the collection is frozen for non-admin transfers. pub(super) is_frozen: bool, } /// Witness data for the destroy transactions. #[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct DestroyWitness { - /// The total number of outstanding instances of this asset class. + /// The total number of outstanding items of this collection. #[codec(compact)] - pub instances: u32, - /// The total number of outstanding instance metadata of this asset class. + pub items: u32, + /// The total number of items in this collection that have outstanding item metadata. #[codec(compact)] - pub instance_metadatas: u32, + pub item_metadatas: u32, #[codec(compact)] - /// The total number of attributes for this asset class. + /// The total number of attributes for this collection. pub attributes: u32, } -impl ClassDetails { +impl CollectionDetails { pub fn destroy_witness(&self) -> DestroyWitness { DestroyWitness { - instances: self.instances, - instance_metadatas: self.instance_metadatas, + items: self.items, + item_metadatas: self.item_metadatas, attributes: self.attributes, } } } -/// Information concerning the ownership of a single unique asset. +/// Information concerning the ownership of a single unique item. #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)] -pub struct InstanceDetails { - /// The owner of this asset. +pub struct ItemDetails { + /// The owner of this item. pub(super) owner: AccountId, - /// The approved transferrer of this asset, if one is set. + /// The approved transferrer of this item, if one is set. pub(super) approved: Option, - /// Whether the asset can be transferred or not. + /// Whether the item can be transferred or not. pub(super) is_frozen: bool, - /// The amount held in the pallet's default account for this asset. Free-hold assets will have + /// The amount held in the pallet's default account for this item. Free-hold items will have /// this as zero. pub(super) deposit: DepositBalance, } @@ -97,31 +97,31 @@ pub struct InstanceDetails { #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(StringLimit))] #[codec(mel_bound(DepositBalance: MaxEncodedLen))] -pub struct ClassMetadata> { +pub struct CollectionMetadata> { /// The balance deposited for this metadata. /// /// This pays for the data stored in this struct. pub(super) deposit: DepositBalance, - /// General information concerning this asset. Limited in length by `StringLimit`. This will - /// generally be either a JSON dump or the hash of some JSON which can be found on a + /// General information concerning this collection. Limited in length by `StringLimit`. This + /// will generally be either a JSON dump or the hash of some JSON which can be found on a /// hash-addressable global publication system such as IPFS. pub(super) data: BoundedVec, - /// Whether the asset metadata may be changed by a non Force origin. + /// Whether the collection's metadata may be changed by a non Force origin. pub(super) is_frozen: bool, } #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(StringLimit))] #[codec(mel_bound(DepositBalance: MaxEncodedLen))] -pub struct InstanceMetadata> { +pub struct ItemMetadata> { /// The balance deposited for this metadata. /// /// This pays for the data stored in this struct. pub(super) deposit: DepositBalance, - /// General information concerning this asset. Limited in length by `StringLimit`. This will + /// General information concerning this item. Limited in length by `StringLimit`. This will /// generally be either a JSON dump or the hash of some JSON which can be found on a /// hash-addressable global publication system such as IPFS. pub(super) data: BoundedVec, - /// Whether the asset metadata may be changed by a non Force origin. + /// Whether the item metadata may be changed by a non Force origin. pub(super) is_frozen: bool, } diff --git a/frame/uniques/src/weights.rs b/frame/uniques/src/weights.rs index eb9067b7133a0..9b0cbda423ace 100644 --- a/frame/uniques/src/weights.rs +++ b/frame/uniques/src/weights.rs @@ -18,12 +18,13 @@ //! Autogenerated weights for pallet_uniques //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-01-31, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-12, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// ./target/production/substrate +// target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -33,9 +34,7 @@ // --wasm-execution=compiled // --heap-pages=4096 // --output=./frame/uniques/src/weights.rs -// --template=.maintain/frame-weight-template.hbs -// --header=HEADER-APACHE2 -// --raw +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -55,17 +54,17 @@ pub trait WeightInfo { fn redeposit(i: u32, ) -> Weight; fn freeze() -> Weight; fn thaw() -> Weight; - fn freeze_class() -> Weight; - fn thaw_class() -> Weight; + fn freeze_collection() -> Weight; + fn thaw_collection() -> Weight; fn transfer_ownership() -> Weight; fn set_team() -> Weight; - fn force_asset_status() -> Weight; + fn force_item_status() -> Weight; fn set_attribute() -> Weight; fn clear_attribute() -> Weight; fn set_metadata() -> Weight; fn clear_metadata() -> Weight; - fn set_class_metadata() -> Weight; - fn clear_class_metadata() -> Weight; + fn set_collection_metadata() -> Weight; + fn clear_collection_metadata() -> Weight; fn approve_transfer() -> Weight; fn cancel_approval() -> Weight; fn set_accept_ownership() -> Weight; @@ -77,14 +76,14 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn create() -> Weight { - (24_063_000 as Weight) + (24_319_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn force_create() -> Weight { - (13_017_000 as Weight) + (13_572_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -97,12 +96,12 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Account (r:0 w:20) fn destroy(n: u32, m: u32, a: u32, ) -> Weight { (0 as Weight) - // Standard Error: 14_000 - .saturating_add((9_248_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 14_000 - .saturating_add((854_000 as Weight).saturating_mul(m as Weight)) - // Standard Error: 14_000 - .saturating_add((758_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 15_000 + .saturating_add((9_433_000 as Weight).saturating_mul(n as Weight)) + // Standard Error: 15_000 + .saturating_add((980_000 as Weight).saturating_mul(m as Weight)) + // Standard Error: 15_000 + .saturating_add((852_000 as Weight).saturating_mul(a as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) .saturating_add(T::DbWeight::get().writes(3 as Weight)) @@ -114,7 +113,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Class (r:1 w:1) // Storage: Uniques Account (r:0 w:1) fn mint() -> Weight { - (29_865_000 as Weight) + (29_884_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -122,7 +121,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Account (r:0 w:1) fn burn() -> Weight { - (31_603_000 as Weight) + (31_384_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -130,7 +129,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Account (r:0 w:2) fn transfer() -> Weight { - (23_331_000 as Weight) + (23_254_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -138,8 +137,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Asset (r:100 w:100) fn redeposit(i: u32, ) -> Weight { (0 as Weight) - // Standard Error: 12_000 - .saturating_add((11_527_000 as Weight).saturating_mul(i as Weight)) + // Standard Error: 13_000 + .saturating_add((11_718_000 as Weight).saturating_mul(i as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -148,47 +147,47 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn freeze() -> Weight { - (18_617_000 as Weight) + (17_807_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn thaw() -> Weight { - (18_618_000 as Weight) + (17_904_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) - fn freeze_class() -> Weight { - (13_570_000 as Weight) + fn freeze_collection() -> Weight { + (13_828_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) - fn thaw_class() -> Weight { - (13_937_000 as Weight) + fn thaw_collection() -> Weight { + (13_636_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } + // Storage: Uniques OwnershipAcceptance (r:1 w:1) // Storage: Uniques Class (r:1 w:1) - // Storage: System Account (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:2) fn transfer_ownership() -> Weight { - (31_021_000 as Weight) + (20_897_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn set_team() -> Weight { - (14_739_000 as Weight) + (14_340_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) - fn force_asset_status() -> Weight { - (16_826_000 as Weight) + fn force_item_status() -> Weight { + (16_355_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -196,7 +195,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn set_attribute() -> Weight { - (37_010_000 as Weight) + (37_035_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -204,56 +203,55 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn clear_attribute() -> Weight { - (34_432_000 as Weight) + (34_957_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:1) fn set_metadata() -> Weight { - (28_575_000 as Weight) + (28_337_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:1) fn clear_metadata() -> Weight { - (28_730_000 as Weight) + (29_166_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassMetadataOf (r:1 w:1) - fn set_class_metadata() -> Weight { - (28_225_000 as Weight) + fn set_collection_metadata() -> Weight { + (28_246_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques ClassMetadataOf (r:1 w:1) - fn clear_class_metadata() -> Weight { - (26_455_000 as Weight) + fn clear_collection_metadata() -> Weight { + (26_637_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) fn approve_transfer() -> Weight { - (19_587_000 as Weight) + (18_938_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) fn cancel_approval() -> Weight { - (19_417_000 as Weight) + (18_465_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } - // Storage: Uniques Class (r:1 w:0) - // Storage: Uniques Asset (r:1 w:1) + // Storage: Uniques OwnershipAcceptance (r:1 w:1) fn set_accept_ownership() -> Weight { - (19_417_000 as Weight) + (16_675_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -264,14 +262,14 @@ impl WeightInfo for () { // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn create() -> Weight { - (24_063_000 as Weight) + (24_319_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn force_create() -> Weight { - (13_017_000 as Weight) + (13_572_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -284,12 +282,12 @@ impl WeightInfo for () { // Storage: Uniques Account (r:0 w:20) fn destroy(n: u32, m: u32, a: u32, ) -> Weight { (0 as Weight) - // Standard Error: 14_000 - .saturating_add((9_248_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 14_000 - .saturating_add((854_000 as Weight).saturating_mul(m as Weight)) - // Standard Error: 14_000 - .saturating_add((758_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 15_000 + .saturating_add((9_433_000 as Weight).saturating_mul(n as Weight)) + // Standard Error: 15_000 + .saturating_add((980_000 as Weight).saturating_mul(m as Weight)) + // Standard Error: 15_000 + .saturating_add((852_000 as Weight).saturating_mul(a as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) @@ -301,7 +299,7 @@ impl WeightInfo for () { // Storage: Uniques Class (r:1 w:1) // Storage: Uniques Account (r:0 w:1) fn mint() -> Weight { - (29_865_000 as Weight) + (29_884_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -309,7 +307,7 @@ impl WeightInfo for () { // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Account (r:0 w:1) fn burn() -> Weight { - (31_603_000 as Weight) + (31_384_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -317,7 +315,7 @@ impl WeightInfo for () { // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Account (r:0 w:2) fn transfer() -> Weight { - (23_331_000 as Weight) + (23_254_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -325,8 +323,8 @@ impl WeightInfo for () { // Storage: Uniques Asset (r:100 w:100) fn redeposit(i: u32, ) -> Weight { (0 as Weight) - // Standard Error: 12_000 - .saturating_add((11_527_000 as Weight).saturating_mul(i as Weight)) + // Standard Error: 13_000 + .saturating_add((11_718_000 as Weight).saturating_mul(i as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(i as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -335,47 +333,47 @@ impl WeightInfo for () { // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn freeze() -> Weight { - (18_617_000 as Weight) + (17_807_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn thaw() -> Weight { - (18_618_000 as Weight) + (17_904_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) - fn freeze_class() -> Weight { - (13_570_000 as Weight) + fn freeze_collection() -> Weight { + (13_828_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) - fn thaw_class() -> Weight { - (13_937_000 as Weight) + fn thaw_collection() -> Weight { + (13_636_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } + // Storage: Uniques OwnershipAcceptance (r:1 w:1) // Storage: Uniques Class (r:1 w:1) - // Storage: System Account (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:2) fn transfer_ownership() -> Weight { - (31_021_000 as Weight) + (20_897_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn set_team() -> Weight { - (14_739_000 as Weight) + (14_340_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) - fn force_asset_status() -> Weight { - (16_826_000 as Weight) + fn force_item_status() -> Weight { + (16_355_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -383,7 +381,7 @@ impl WeightInfo for () { // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn set_attribute() -> Weight { - (37_010_000 as Weight) + (37_035_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -391,56 +389,55 @@ impl WeightInfo for () { // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn clear_attribute() -> Weight { - (34_432_000 as Weight) + (34_957_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:1) fn set_metadata() -> Weight { - (28_575_000 as Weight) + (28_337_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:1) fn clear_metadata() -> Weight { - (28_730_000 as Weight) + (29_166_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassMetadataOf (r:1 w:1) - fn set_class_metadata() -> Weight { - (28_225_000 as Weight) + fn set_collection_metadata() -> Weight { + (28_246_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques ClassMetadataOf (r:1 w:1) - fn clear_class_metadata() -> Weight { - (26_455_000 as Weight) + fn clear_collection_metadata() -> Weight { + (26_637_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) fn approve_transfer() -> Weight { - (19_587_000 as Weight) + (18_938_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) fn cancel_approval() -> Weight { - (19_417_000 as Weight) + (18_465_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } - // Storage: Uniques Class (r:1 w:0) - // Storage: Uniques Asset (r:1 w:1) + // Storage: Uniques OwnershipAcceptance (r:1 w:1) fn set_accept_ownership() -> Weight { - (19_417_000 as Weight) + (16_675_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } From 0d49643429cd746c33e08228404255e660c54f5d Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Tue, 17 May 2022 05:50:03 -0400 Subject: [PATCH 227/484] remove deprecated (#11432) --- client/consensus/babe/src/tests.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/client/consensus/babe/src/tests.rs b/client/consensus/babe/src/tests.rs index db19deda06fd8..9875ff00673f5 100644 --- a/client/consensus/babe/src/tests.rs +++ b/client/consensus/babe/src/tests.rs @@ -18,9 +18,6 @@ //! BABE testsuite -// FIXME #2532: need to allow deprecated until refactor is done -// https://github.com/paritytech/substrate/issues/2532 -#![allow(deprecated)] use super::*; use authorship::claim_slot; use futures::executor::block_on; From 29ba417b639c6bc493bb22f2ab161578ded4de3e Mon Sep 17 00:00:00 2001 From: mikolaichuk <45576473+mikolaichuk@users.noreply.github.com> Date: Tue, 17 May 2022 16:03:03 +0600 Subject: [PATCH 228/484] Small typo (#11392) Small typo --- primitives/runtime/src/traits.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/primitives/runtime/src/traits.rs b/primitives/runtime/src/traits.rs index 9f706878bba49..393de086f3c58 100644 --- a/primitives/runtime/src/traits.rs +++ b/primitives/runtime/src/traits.rs @@ -821,7 +821,7 @@ impl Dispatchable for () { type Info = (); type PostInfo = (); fn dispatch(self, _origin: Self::Origin) -> crate::DispatchResultWithInfo { - panic!("This implemention should not be used for actual dispatch."); + panic!("This implementation should not be used for actual dispatch."); } } From a758ce4a38b0e22b17dc2ec2530c4cdfee71083b Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 17 May 2022 15:57:08 +0200 Subject: [PATCH 229/484] More `benchmark machine` args (#11428) * Add ExecutionLimits to sc-sysinfo and return float Signed-off-by: Oliver Tale-Yazdi * Increase benchmarking duration and add options Signed-off-by: Oliver Tale-Yazdi * Fix tests Signed-off-by: Oliver Tale-Yazdi * Fix tests Signed-off-by: Oliver Tale-Yazdi --- bin/node/cli/tests/benchmark_machine_works.rs | 24 ++++++++- client/sysinfo/src/sysinfo.rs | 51 ++++++++++--------- .../frame/benchmarking-cli/src/machine/mod.rs | 16 ++++-- 3 files changed, 62 insertions(+), 29 deletions(-) diff --git a/bin/node/cli/tests/benchmark_machine_works.rs b/bin/node/cli/tests/benchmark_machine_works.rs index bf4a2b7b85e65..193e6b701ec94 100644 --- a/bin/node/cli/tests/benchmark_machine_works.rs +++ b/bin/node/cli/tests/benchmark_machine_works.rs @@ -24,7 +24,16 @@ use std::process::Command; fn benchmark_machine_works() { let status = Command::new(cargo_bin("substrate")) .args(["benchmark", "machine", "--dev"]) - .args(["--verify-duration", "0.1", "--disk-duration", "0.1"]) + .args([ + "--verify-duration", + "0.1", + "--disk-duration", + "0.1", + "--memory-duration", + "0.1", + "--hash-duration", + "0.1", + ]) // Make it succeed. .args(["--allow-fail"]) .status() @@ -41,7 +50,18 @@ fn benchmark_machine_works() { fn benchmark_machine_fails_with_slow_hardware() { let output = Command::new(cargo_bin("substrate")) .args(["benchmark", "machine", "--dev"]) - .args(["--verify-duration", "0.1", "--disk-duration", "2", "--tolerance", "0"]) + .args([ + "--verify-duration", + "1.0", + "--disk-duration", + "2", + "--hash-duration", + "1.0", + "--memory-duration", + "1.0", + "--tolerance", + "0", + ]) .output() .unwrap(); diff --git a/client/sysinfo/src/sysinfo.rs b/client/sysinfo/src/sysinfo.rs index cd6adcf623e66..fc347c1cc2eb3 100644 --- a/client/sysinfo/src/sysinfo.rs +++ b/client/sysinfo/src/sysinfo.rs @@ -60,9 +60,9 @@ pub(crate) fn benchmark( let score = ((size * count) as f64 / elapsed.as_secs_f64()) / (1024.0 * 1024.0); log::trace!( - "Calculated {} of {}MB/s in {} iterations in {}ms", + "Calculated {} of {:.2}MB/s in {} iterations in {}ms", name, - score as u64, + score, count, elapsed.as_millis() ); @@ -116,8 +116,12 @@ fn clobber_value(input: &mut T) { } } +/// A default [`ExecutionLimit`] that can be used to call [`benchmark_cpu`]. +pub const DEFAULT_CPU_EXECUTION_LIMIT: ExecutionLimit = + ExecutionLimit::Both { max_iterations: 4 * 1024, max_duration: Duration::from_millis(100) }; + // This benchmarks the CPU speed as measured by calculating BLAKE2b-256 hashes, in MB/s. -pub fn benchmark_cpu() -> u64 { +pub fn benchmark_cpu(limit: ExecutionLimit) -> f64 { // In general the results of this benchmark are somewhat sensitive to how much // data we hash at the time. The smaller this is the *less* MB/s we can hash, // the bigger this is the *more* MB/s we can hash, up until a certain point @@ -131,8 +135,6 @@ pub fn benchmark_cpu() -> u64 { // picked in such a way as to still measure how fast the hasher is at hashing, // but without hitting its theoretical maximum speed. const SIZE: usize = 32 * 1024; - const MAX_ITERATIONS: usize = 4 * 1024; - const MAX_DURATION: Duration = Duration::from_millis(100); let mut buffer = Vec::new(); buffer.resize(SIZE, 0x66); @@ -146,16 +148,20 @@ pub fn benchmark_cpu() -> u64 { Ok(()) }; - benchmark("CPU score", SIZE, MAX_ITERATIONS, MAX_DURATION, run) - .expect("benchmark cannot fail; qed") as u64 + benchmark("CPU score", SIZE, limit.max_iterations(), limit.max_duration(), run) + .expect("benchmark cannot fail; qed") } +/// A default [`ExecutionLimit`] that can be used to call [`benchmark_memory`]. +pub const DEFAULT_MEMORY_EXECUTION_LIMIT: ExecutionLimit = + ExecutionLimit::Both { max_iterations: 32, max_duration: Duration::from_millis(100) }; + // This benchmarks the effective `memcpy` memory bandwidth available in MB/s. // // It doesn't technically measure the absolute maximum memory bandwidth available, // but that's fine, because real code most of the time isn't optimized to take // advantage of the full memory bandwidth either. -pub fn benchmark_memory() -> u64 { +pub fn benchmark_memory(limit: ExecutionLimit) -> f64 { // Ideally this should be at least as big as the CPU's L3 cache, // and it should be big enough so that the `memcpy` takes enough // time to be actually measurable. @@ -163,8 +169,6 @@ pub fn benchmark_memory() -> u64 { // As long as it's big enough increasing it further won't change // the benchmark's results. const SIZE: usize = 64 * 1024 * 1024; - const MAX_ITERATIONS: usize = 32; - const MAX_DURATION: Duration = Duration::from_millis(100); let mut src = Vec::new(); let mut dst = Vec::new(); @@ -192,8 +196,8 @@ pub fn benchmark_memory() -> u64 { Ok(()) }; - benchmark("memory score", SIZE, MAX_ITERATIONS, MAX_DURATION, run) - .expect("benchmark cannot fail; qed") as u64 + benchmark("memory score", SIZE, limit.max_iterations(), limit.max_duration(), run) + .expect("benchmark cannot fail; qed") } struct TemporaryFile { @@ -249,7 +253,7 @@ pub const DEFAULT_DISK_EXECUTION_LIMIT: ExecutionLimit = pub fn benchmark_disk_sequential_writes( limit: ExecutionLimit, directory: &Path, -) -> Result { +) -> Result { const SIZE: usize = 64 * 1024 * 1024; let buffer = random_data(SIZE); @@ -286,13 +290,12 @@ pub fn benchmark_disk_sequential_writes( limit.max_duration(), run, ) - .map(|s| s as u64) } pub fn benchmark_disk_random_writes( limit: ExecutionLimit, directory: &Path, -) -> Result { +) -> Result { const SIZE: usize = 64 * 1024 * 1024; let buffer = random_data(SIZE); @@ -353,7 +356,6 @@ pub fn benchmark_disk_random_writes( limit.max_duration(), run, ) - .map(|s| s as u64) } /// Benchmarks the verification speed of sr25519 signatures. @@ -400,8 +402,8 @@ pub fn benchmark_sr25519_verify(limit: ExecutionLimit) -> f64 { pub fn gather_hwbench(scratch_directory: Option<&Path>) -> HwBench { #[allow(unused_mut)] let mut hwbench = HwBench { - cpu_hashrate_score: benchmark_cpu(), - memory_memcpy_score: benchmark_memory(), + cpu_hashrate_score: benchmark_cpu(DEFAULT_CPU_EXECUTION_LIMIT) as u64, + memory_memcpy_score: benchmark_memory(DEFAULT_MEMORY_EXECUTION_LIMIT) as u64, disk_sequential_write_score: None, disk_random_write_score: None, }; @@ -410,7 +412,7 @@ pub fn gather_hwbench(scratch_directory: Option<&Path>) -> HwBench { hwbench.disk_sequential_write_score = match benchmark_disk_sequential_writes(DEFAULT_DISK_EXECUTION_LIMIT, scratch_directory) { - Ok(score) => Some(score), + Ok(score) => Some(score as u64), Err(error) => { log::warn!("Failed to run the sequential write disk benchmark: {}", error); None @@ -419,7 +421,7 @@ pub fn gather_hwbench(scratch_directory: Option<&Path>) -> HwBench { hwbench.disk_random_write_score = match benchmark_disk_random_writes(DEFAULT_DISK_EXECUTION_LIMIT, scratch_directory) { - Ok(score) => Some(score), + Ok(score) => Some(score as u64), Err(error) => { log::warn!("Failed to run the random write disk benchmark: {}", error); None @@ -448,26 +450,27 @@ mod tests { #[test] fn test_benchmark_cpu() { - assert_ne!(benchmark_cpu(), 0); + assert!(benchmark_cpu(DEFAULT_CPU_EXECUTION_LIMIT) > 0.0); } #[test] fn test_benchmark_memory() { - assert_ne!(benchmark_memory(), 0); + assert!(benchmark_memory(DEFAULT_MEMORY_EXECUTION_LIMIT) > 0.0); } #[test] fn test_benchmark_disk_sequential_writes() { assert!( benchmark_disk_sequential_writes(DEFAULT_DISK_EXECUTION_LIMIT, "./".as_ref()).unwrap() > - 0 + 0.0 ); } #[test] fn test_benchmark_disk_random_writes() { assert!( - benchmark_disk_random_writes(DEFAULT_DISK_EXECUTION_LIMIT, "./".as_ref()).unwrap() > 0 + benchmark_disk_random_writes(DEFAULT_DISK_EXECUTION_LIMIT, "./".as_ref()).unwrap() > + 0.0 ); } diff --git a/utils/frame/benchmarking-cli/src/machine/mod.rs b/utils/frame/benchmarking-cli/src/machine/mod.rs index 9e25e58921d71..d5cd2420a3833 100644 --- a/utils/frame/benchmarking-cli/src/machine/mod.rs +++ b/utils/frame/benchmarking-cli/src/machine/mod.rs @@ -63,9 +63,17 @@ pub struct MachineCmd { pub tolerance: f64, /// Time limit for the verification benchmark. - #[clap(long, default_value = "2.0", value_name = "SECONDS")] + #[clap(long, default_value = "5.0", value_name = "SECONDS")] pub verify_duration: f32, + /// Time limit for the hash function benchmark. + #[clap(long, default_value = "5.0", value_name = "SECONDS")] + pub hash_duration: f32, + + /// Time limit for the memory benchmark. + #[clap(long, default_value = "5.0", value_name = "SECONDS")] + pub memory_duration: f32, + /// Time limit for each disk benchmark. #[clap(long, default_value = "5.0", value_name = "SECONDS")] pub disk_duration: f32, @@ -134,11 +142,13 @@ impl MachineCmd { fn measure(&self, metric: &Metric, dir: &Path) -> Result { let verify_limit = ExecutionLimit::from_secs_f32(self.verify_duration); let disk_limit = ExecutionLimit::from_secs_f32(self.disk_duration); + let hash_limit = ExecutionLimit::from_secs_f32(self.hash_duration); + let memory_limit = ExecutionLimit::from_secs_f32(self.memory_duration); let score = match metric { - Metric::Blake2256 => Throughput::MiBs(benchmark_cpu() as f64), + Metric::Blake2256 => Throughput::MiBs(benchmark_cpu(hash_limit) as f64), Metric::Sr25519Verify => Throughput::MiBs(benchmark_sr25519_verify(verify_limit)), - Metric::MemCopy => Throughput::MiBs(benchmark_memory() as f64), + Metric::MemCopy => Throughput::MiBs(benchmark_memory(memory_limit) as f64), Metric::DiskSeqWrite => Throughput::MiBs(benchmark_disk_sequential_writes(disk_limit, dir)? as f64), Metric::DiskRndWrite => From d21d32504ae9c0cb14f580fd22e567cdb9f369f5 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 17 May 2022 18:29:21 +0200 Subject: [PATCH 230/484] fix: regression of `sync_state_genSyncSpec` #11435 (#11437) * fix: #11435 * address grumbles: better safe than sorry --- client/sync-state-rpc/src/lib.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/client/sync-state-rpc/src/lib.rs b/client/sync-state-rpc/src/lib.rs index a0a5b66cb86fc..02a22a838b8b2 100644 --- a/client/sync-state-rpc/src/lib.rs +++ b/client/sync-state-rpc/src/lib.rs @@ -128,7 +128,7 @@ pub struct LightSyncState { pub trait SyncStateRpcApi { /// Returns the JSON serialized chainspec running the node, with a sync state. #[method(name = "sync_state_genSyncSpec")] - fn system_gen_sync_spec(&self, raw: bool) -> RpcResult; + fn system_gen_sync_spec(&self, raw: bool) -> RpcResult; } /// An api for sync state RPC calls. @@ -185,7 +185,7 @@ where Block: BlockT, Backend: HeaderBackend + sc_client_api::AuxStore + 'static, { - fn system_gen_sync_spec(&self, raw: bool) -> RpcResult { + fn system_gen_sync_spec(&self, raw: bool) -> RpcResult { let current_sync_state = self.build_sync_state()?; let mut chain_spec = self.chain_spec.cloned_box(); @@ -197,6 +197,7 @@ where let val = serde_json::to_value(¤t_sync_state)?; *extension = Some(val); - chain_spec.as_json(raw).map_err(|e| Error::::JsonRpc(e).into()) + let json_str = chain_spec.as_json(raw).map_err(|e| Error::::JsonRpc(e))?; + serde_json::from_str(&json_str).map_err(Into::into) } } From b8857a9f49fbdfd3362e57502c339f79b014a438 Mon Sep 17 00:00:00 2001 From: Qinxuan Chen Date: Wed, 18 May 2022 00:38:51 +0800 Subject: [PATCH 231/484] Migrate abandoned `prettytable-rs` to `comfy-table` (#11430) * Migrate to comfy-table Signed-off-by: koushiro * disable the default features Signed-off-by: koushiro --- Cargo.lock | 109 +++--------------- utils/frame/benchmarking-cli/Cargo.toml | 2 +- .../frame/benchmarking-cli/src/machine/mod.rs | 25 ++-- 3 files changed, 34 insertions(+), 102 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 201feb7e06d7d..14196e4f85364 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -615,17 +615,6 @@ dependencies = [ "constant_time_eq", ] -[[package]] -name = "blake2b_simd" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" -dependencies = [ - "arrayref", - "arrayvec 0.5.2", - "constant_time_eq", -] - [[package]] name = "blake2b_simd" version = "1.0.0" @@ -1048,6 +1037,17 @@ dependencies = [ "bitflags", ] +[[package]] +name = "comfy-table" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b103d85ca6e209388771bfb7aa6b68a7aeec4afbf6f0a0264bfbf50360e5212e" +dependencies = [ + "strum", + "strum_macros", + "unicode-width", +] + [[package]] name = "concurrent-queue" version = "1.2.2" @@ -1665,17 +1665,6 @@ dependencies = [ "dirs-sys-next", ] -[[package]] -name = "dirs" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" -dependencies = [ - "libc", - "redox_users 0.3.5", - "winapi", -] - [[package]] name = "dirs-sys" version = "0.3.6" @@ -1683,7 +1672,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" dependencies = [ "libc", - "redox_users 0.4.0", + "redox_users", "winapi", ] @@ -1694,7 +1683,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ "libc", - "redox_users 0.4.0", + "redox_users", "winapi", ] @@ -1844,12 +1833,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "encode_unicode" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" - [[package]] name = "enum-as-inner" version = "0.4.0" @@ -2152,6 +2135,7 @@ dependencies = [ "Inflector", "chrono", "clap 3.1.18", + "comfy-table", "frame-benchmarking", "frame-support", "frame-system", @@ -2165,7 +2149,6 @@ dependencies = [ "log", "memory-db", "parity-scale-codec", - "prettytable-rs", "rand 0.8.4", "rand_pcg 0.3.1", "sc-block-builder", @@ -4365,7 +4348,7 @@ version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3db354f401db558759dfc1e568d010a5d4146f4d3f637be1275ec4a3cf09689" dependencies = [ - "blake2b_simd 1.0.0", + "blake2b_simd", "blake2s_simd", "blake3", "core2", @@ -6588,7 +6571,7 @@ dependencies = [ "cfg-if 1.0.0", "instant", "libc", - "redox_syscall 0.2.10", + "redox_syscall", "smallvec", "winapi", ] @@ -6601,7 +6584,7 @@ checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954" dependencies = [ "cfg-if 1.0.0", "libc", - "redox_syscall 0.2.10", + "redox_syscall", "smallvec", "windows-sys", ] @@ -6893,20 +6876,6 @@ dependencies = [ "output_vt100", ] -[[package]] -name = "prettytable-rs" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fd04b170004fa2daccf418a7f8253aaf033c27760b5f225889024cf66d7ac2e" -dependencies = [ - "atty", - "csv", - "encode_unicode", - "lazy_static", - "term", - "unicode-width", -] - [[package]] name = "primitive-types" version = "0.11.1" @@ -7416,12 +7385,6 @@ dependencies = [ "rand_core 0.3.1", ] -[[package]] -name = "redox_syscall" -version = "0.1.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" - [[package]] name = "redox_syscall" version = "0.2.10" @@ -7431,17 +7394,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "redox_users" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" -dependencies = [ - "getrandom 0.1.16", - "redox_syscall 0.1.57", - "rust-argon2", -] - [[package]] name = "redox_users" version = "0.4.0" @@ -7449,7 +7401,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" dependencies = [ "getrandom 0.2.3", - "redox_syscall 0.2.10", + "redox_syscall", ] [[package]] @@ -7684,18 +7636,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "rust-argon2" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b18820d944b33caa75a71378964ac46f58517c92b6ae5f762636247c09e78fb" -dependencies = [ - "base64", - "blake2b_simd 0.5.11", - "constant_time_eq", - "crossbeam-utils", -] - [[package]] name = "rustc-demangle" version = "0.1.18" @@ -10791,22 +10731,11 @@ dependencies = [ "cfg-if 1.0.0", "fastrand", "libc", - "redox_syscall 0.2.10", + "redox_syscall", "remove_dir_all", "winapi", ] -[[package]] -name = "term" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edd106a334b7657c10b7c540a0106114feadeb4dc314513e97df481d5d966f42" -dependencies = [ - "byteorder", - "dirs", - "winapi", -] - [[package]] name = "termcolor" version = "1.1.2" diff --git a/utils/frame/benchmarking-cli/Cargo.toml b/utils/frame/benchmarking-cli/Cargo.toml index 68be768012d51..5f30e21b2986a 100644 --- a/utils/frame/benchmarking-cli/Cargo.toml +++ b/utils/frame/benchmarking-cli/Cargo.toml @@ -16,6 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] chrono = "0.4" clap = { version = "3.1.18", features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.0.0" } +comfy-table = { version = "5.0.1", default-features = false } handlebars = "4.2.2" hash-db = "0.15.2" hex = "0.4.3" @@ -26,7 +27,6 @@ lazy_static = "1.4.0" linked-hash-map = "0.5.4" log = "0.4.17" memory-db = "0.29.0" -prettytable-rs = "0.8.0" rand = { version = "0.8.4", features = ["small_rng"] } rand_pcg = "0.3.1" serde = "1.0.136" diff --git a/utils/frame/benchmarking-cli/src/machine/mod.rs b/utils/frame/benchmarking-cli/src/machine/mod.rs index d5cd2420a3833..5f27c71983905 100644 --- a/utils/frame/benchmarking-cli/src/machine/mod.rs +++ b/utils/frame/benchmarking-cli/src/machine/mod.rs @@ -20,6 +20,12 @@ pub mod hardware; +use std::{boxed::Box, fs, path::Path}; + +use clap::Parser; +use comfy_table::{Row, Table}; +use log::{error, info, warn}; + use sc_cli::{CliConfiguration, Result, SharedParams}; use sc_service::Configuration; use sc_sysinfo::{ @@ -27,11 +33,6 @@ use sc_sysinfo::{ benchmark_memory, benchmark_sr25519_verify, ExecutionLimit, }; -use clap::Parser; -use log::{error, info, warn}; -use prettytable::{cell, row, table}; -use std::{boxed::Box, fmt::Debug, fs, path::Path}; - use crate::shared::check_build_profile; pub use hardware::{Metric, Requirement, Requirements, Throughput, SUBSTRATE_REFERENCE_HARDWARE}; @@ -160,7 +161,8 @@ impl MachineCmd { /// Prints a human-readable summary. fn print_summary(&self, requirements: Requirements, results: Vec) -> Result<()> { // Use a table for nicer console output. - let mut table = table!(["Category", "Function", "Score", "Minimum", "Result"]); + let mut table = Table::new(); + table.set_header(["Category", "Function", "Score", "Minimum", "Result"]); // Count how many passed and how many failed. let (mut passed, mut failed) = (0, 0); for (requirement, result) in requirements.0.iter().zip(results.iter()) { @@ -217,15 +219,16 @@ impl MachineCmd { impl BenchResult { /// Format [`Self`] as row that can be printed in a table. - fn to_row(&self, req: &Requirement) -> prettytable::Row { + fn to_row(&self, req: &Requirement) -> Row { let passed = if self.passed { "✅ Pass" } else { "❌ Fail" }; - row![ - req.metric.category(), - req.metric.name(), + vec![ + req.metric.category().into(), + req.metric.name().into(), format!("{}", self.score), format!("{}", req.minimum), - format!("{} ({: >5.1?} %)", passed, self.rel_score * 100.0) + format!("{} ({: >5.1?} %)", passed, self.rel_score * 100.0), ] + .into() } } From 073c08112d47e6018a98c5d30c2efc571c22c175 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Tue, 17 May 2022 19:16:04 +0100 Subject: [PATCH 232/484] Add MinPointsToBalance to nomination pools (#11377) * add MinPointsToBalance to pools * add min_points_to_balance to benchmark mock * fmt * comments Co-authored-by: Oliver Tale-Yazdi * check min_points_to_balance.is_zero * comment * comment * storage to constant * fix * comment Co-authored-by: Oliver Tale-Yazdi Co-authored-by: parity-processbot <> --- bin/node/runtime/src/lib.rs | 2 + .../nomination-pools/benchmarking/src/mock.rs | 2 + frame/nomination-pools/src/lib.rs | 31 ++++++++--- frame/nomination-pools/src/mock.rs | 2 + frame/nomination-pools/src/tests.rs | 55 ++++++++++++++----- 5 files changed, 69 insertions(+), 23 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 1257aabb9d5cc..40316cee9b2b7 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -727,6 +727,7 @@ impl pallet_bags_list::Config for Runtime { parameter_types! { pub const PostUnbondPoolsWindow: u32 = 4; pub const NominationPoolsPalletId: PalletId = PalletId(*b"py/nopls"); + pub const MinPointsToBalance: u32 = 10; } use sp_runtime::traits::Convert; @@ -754,6 +755,7 @@ impl pallet_nomination_pools::Config for Runtime { type MaxMetadataLen = ConstU32<256>; type MaxUnbonding = ConstU32<8>; type PalletId = NominationPoolsPalletId; + type MinPointsToBalance = MinPointsToBalance; } parameter_types! { diff --git a/frame/nomination-pools/benchmarking/src/mock.rs b/frame/nomination-pools/benchmarking/src/mock.rs index d98bcb542f514..eb884869f6d32 100644 --- a/frame/nomination-pools/benchmarking/src/mock.rs +++ b/frame/nomination-pools/benchmarking/src/mock.rs @@ -144,6 +144,7 @@ impl Convert for U256ToBalance { parameter_types! { pub static PostUnbondingPoolsWindow: u32 = 10; pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls"); + pub const MinPointsToBalance: u32 = 10; } impl pallet_nomination_pools::Config for Runtime { @@ -157,6 +158,7 @@ impl pallet_nomination_pools::Config for Runtime { type MaxMetadataLen = ConstU32<256>; type MaxUnbonding = ConstU32<8>; type PalletId = PoolsPalletId; + type MinPointsToBalance = MinPointsToBalance; } impl crate::Config for Runtime {} diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index d68a6f09c31d1..cdc3bdaf34389 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -740,16 +740,22 @@ impl BondedPool { // We checked for zero above .div(bonded_balance); + let min_points_to_balance = T::MinPointsToBalance::get(); + // Pool points can inflate relative to balance, but only if the pool is slashed. // If we cap the ratio of points:balance so one cannot join a pool that has been slashed - // 90%, - ensure!(points_to_balance_ratio_floor < 10u32.into(), Error::::OverflowRisk); - // while restricting the balance to 1/10th of max total issuance, + // by `min_points_to_balance`%, if not zero. + ensure!( + points_to_balance_ratio_floor < min_points_to_balance.into(), + Error::::OverflowRisk + ); + // while restricting the balance to `min_points_to_balance` of max total issuance, let next_bonded_balance = bonded_balance.saturating_add(new_funds); ensure!( - next_bonded_balance < BalanceOf::::max_value().div(10u32.into()), + next_bonded_balance < BalanceOf::::max_value().div(min_points_to_balance.into()), Error::::OverflowRisk ); + // then we can be decently confident the bonding pool points will not overflow // `BalanceOf`. Note that these are just heuristics. @@ -1102,6 +1108,14 @@ pub mod pallet { #[pallet::constant] type PalletId: Get; + /// The minimum pool points-to-balance ratio that must be maintained for it to be `open`. + /// This is important in the event slashing takes place and the pool's points-to-balance + /// ratio becomes disproportional. + /// For a value of 10, the threshold would be a pool points-to-balance ratio of 10:1. + /// Such a scenario would also be the equivalent of the pool being 90% slashed. + #[pallet::constant] + type MinPointsToBalance: Get; + /// Infallible method for converting `Currency::Balance` to `U256`. type BalanceToU256: Convert, U256>; @@ -1844,7 +1858,7 @@ pub mod pallet { Ok(()) } - /// Update configurations for the nomination pools. The origin must for this call must be + /// Update configurations for the nomination pools. The origin for this call must be /// Root. /// /// # Arguments @@ -1880,7 +1894,6 @@ pub mod pallet { config_op_exp!(MaxPools::, max_pools); config_op_exp!(MaxPoolMembers::, max_members); config_op_exp!(MaxPoolMembersPerPool::, max_members_per_pool); - Ok(()) } @@ -1940,12 +1953,15 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { fn integrity_test() { + assert!( + T::MinPointsToBalance::get() > 0, + "Minimum points to balance ratio must be greater than 0" + ); assert!( sp_std::mem::size_of::() >= 2 * sp_std::mem::size_of::>(), "bit-length of the reward points must be at least twice as much as balance" ); - assert!( T::StakingInterface::bonding_duration() < TotalUnbondingPools::::get(), "There must be more unbonding pools then the bonding duration / @@ -2304,7 +2320,6 @@ impl Pallet { sum_unbonding_balance ); } - Ok(()) } diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index 5498496965adb..1dadd7e00c528 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -162,6 +162,7 @@ parameter_types! { pub static MaxMetadataLen: u32 = 2; pub static CheckLevel: u8 = 255; pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls"); + pub const MinPointsToBalance: u32 = 10; } impl pools::Config for Runtime { type Event = Event; @@ -174,6 +175,7 @@ impl pools::Config for Runtime { type PalletId = PoolsPalletId; type MaxMetadataLen = MaxMetadataLen; type MaxUnbonding = MaxUnbonding; + type MinPointsToBalance = MinPointsToBalance; } type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index fe78da3bb14af..c16c1c9da9e90 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -199,23 +199,34 @@ mod bonded_pool { }, }; + let min_points_to_balance: u128 = MinPointsToBalance::get().into(); + // Simulate a 100% slashed pool StakingMock::set_bonded_balance(pool.bonded_account(), 0); assert_noop!(pool.ok_to_join(0), Error::::OverflowRisk); - // Simulate a 89% - StakingMock::set_bonded_balance(pool.bonded_account(), 11); + // Simulate a slashed pool at `MinPointsToBalance` + 1 slashed pool + StakingMock::set_bonded_balance( + pool.bonded_account(), + min_points_to_balance.saturating_add(1).into(), + ); assert_ok!(pool.ok_to_join(0)); - // Simulate a 90% slashed pool - StakingMock::set_bonded_balance(pool.bonded_account(), 10); + // Simulate a slashed pool at `MinPointsToBalance` + StakingMock::set_bonded_balance(pool.bonded_account(), min_points_to_balance); assert_noop!(pool.ok_to_join(0), Error::::OverflowRisk); - StakingMock::set_bonded_balance(pool.bonded_account(), Balance::MAX / 10); - // New bonded balance would be over 1/10th of Balance type + StakingMock::set_bonded_balance( + pool.bonded_account(), + Balance::MAX / min_points_to_balance, + ); + // New bonded balance would be over threshold of Balance type assert_noop!(pool.ok_to_join(0), Error::::OverflowRisk); // and a sanity check - StakingMock::set_bonded_balance(pool.bonded_account(), Balance::MAX / 10 - 1); + StakingMock::set_bonded_balance( + pool.bonded_account(), + Balance::MAX / min_points_to_balance - 1, + ); assert_ok!(pool.ok_to_join(0)); }); } @@ -503,22 +514,36 @@ mod join { }, ); - // Force the points:balance ratio to 100/10 - StakingMock::set_bonded_balance(Pools::create_bonded_account(123), 10); + // Force the points:balance ratio to `MinPointsToBalance` (100/10) + let min_points_to_balance: u128 = MinPointsToBalance::get().into(); + + StakingMock::set_bonded_balance( + Pools::create_bonded_account(123), + min_points_to_balance, + ); assert_noop!(Pools::join(Origin::signed(11), 420, 123), Error::::OverflowRisk); - StakingMock::set_bonded_balance(Pools::create_bonded_account(123), Balance::MAX / 10); - // Balance is gt 1/10 of Balance::MAX + StakingMock::set_bonded_balance( + Pools::create_bonded_account(123), + Balance::MAX / min_points_to_balance, + ); + // Balance needs to be gt Balance::MAX / `MinPointsToBalance` assert_noop!(Pools::join(Origin::signed(11), 5, 123), Error::::OverflowRisk); - StakingMock::set_bonded_balance(Pools::create_bonded_account(1), 10); + StakingMock::set_bonded_balance(Pools::create_bonded_account(1), min_points_to_balance); // Cannot join a pool that isn't open unsafe_set_state(123, PoolState::Blocked).unwrap(); - assert_noop!(Pools::join(Origin::signed(11), 10, 123), Error::::NotOpen); + assert_noop!( + Pools::join(Origin::signed(11), min_points_to_balance, 123), + Error::::NotOpen + ); unsafe_set_state(123, PoolState::Destroying).unwrap(); - assert_noop!(Pools::join(Origin::signed(11), 10, 123), Error::::NotOpen); + assert_noop!( + Pools::join(Origin::signed(11), min_points_to_balance, 123), + Error::::NotOpen + ); // Given MinJoinBond::::put(100); @@ -3434,7 +3459,7 @@ mod set_configs { ConfigOp::Remove, ConfigOp::Remove, ConfigOp::Remove, - ConfigOp::Remove + ConfigOp::Remove, )); assert_eq!(MinJoinBond::::get(), 0); assert_eq!(MinCreateBond::::get(), 0); From f8414b0bb3044d38687d2172a5c4022bb3d89273 Mon Sep 17 00:00:00 2001 From: Qinxuan Chen Date: Wed, 18 May 2022 02:28:34 +0800 Subject: [PATCH 233/484] frame_support::pallet_prelude: reexport StorageVersion (#11408) * frame_support::pallet_prelude: reexport StorageVersion Signed-off-by: koushiro * cargo fmt Signed-off-by: koushiro --- frame/collective/src/lib.rs | 4 +--- frame/collective/src/tests.rs | 2 +- frame/contracts/src/lib.rs | 18 +++++++++--------- frame/contracts/src/migration.rs | 2 -- frame/elections-phragmen/src/lib.rs | 20 ++++++++++---------- frame/grandpa/src/lib.rs | 11 +++++------ frame/membership/src/lib.rs | 6 ++---- frame/session/src/historical/mod.rs | 2 +- frame/session/src/lib.rs | 2 +- frame/session/src/tests.rs | 2 +- frame/support/src/lib.rs | 2 +- frame/support/test/tests/pallet.rs | 6 +----- frame/tips/src/lib.rs | 2 +- frame/tips/src/tests.rs | 2 +- frame/vesting/src/lib.rs | 12 ++++++++---- frame/vesting/src/mock.rs | 2 +- 16 files changed, 44 insertions(+), 51 deletions(-) diff --git a/frame/collective/src/lib.rs b/frame/collective/src/lib.rs index 817e4e176bd4c..9fce1762c6ea7 100644 --- a/frame/collective/src/lib.rs +++ b/frame/collective/src/lib.rs @@ -51,9 +51,7 @@ use frame_support::{ codec::{Decode, Encode}, dispatch::{DispatchError, DispatchResultWithPostInfo, Dispatchable, PostDispatchInfo}, ensure, - traits::{ - Backing, ChangeMembers, EnsureOrigin, Get, GetBacking, InitializeMembers, StorageVersion, - }, + traits::{Backing, ChangeMembers, EnsureOrigin, Get, GetBacking, InitializeMembers}, weights::{GetDispatchInfo, Weight}, }; diff --git a/frame/collective/src/tests.rs b/frame/collective/src/tests.rs index a8abfb0c52358..063479e42fe77 100644 --- a/frame/collective/src/tests.rs +++ b/frame/collective/src/tests.rs @@ -19,7 +19,7 @@ use super::{Event as CollectiveEvent, *}; use crate as pallet_collective; use frame_support::{ assert_noop, assert_ok, parameter_types, - traits::{ConstU32, ConstU64, GenesisBuild}, + traits::{ConstU32, ConstU64, GenesisBuild, StorageVersion}, weights::Pays, Hashable, }; diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index 0ae326f6c1e8f..ca639d7781ac2 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -114,7 +114,7 @@ use codec::{Encode, HasCompact}; use frame_support::{ dispatch::Dispatchable, ensure, - traits::{Contains, Currency, Get, Randomness, ReservableCurrency, StorageVersion, Time}, + traits::{Contains, Currency, Get, Randomness, ReservableCurrency, Time}, weights::{DispatchClass, GetDispatchInfo, Pays, PostDispatchInfo, Weight}, }; use frame_system::{limits::BlockWeights, Pallet as System}; @@ -133,9 +133,6 @@ type TrieId = Vec; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; -/// The current storage version. -const STORAGE_VERSION: StorageVersion = StorageVersion::new(7); - /// Used as a sentinel value when reading and writing contract memory. /// /// It is usually used to signal `None` to a contract when only a primitive is allowed @@ -222,6 +219,14 @@ pub mod pallet { use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(7); + + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] + #[pallet::without_storage_info] + pub struct Pallet(PhantomData); + #[pallet::config] pub trait Config: frame_system::Config { /// The time implementation used to supply timestamps to conntracts through `seal_now`. @@ -353,11 +358,6 @@ pub mod pallet { type AddressGenerator: AddressGenerator; } - #[pallet::pallet] - #[pallet::storage_version(STORAGE_VERSION)] - #[pallet::without_storage_info] - pub struct Pallet(PhantomData); - #[pallet::hooks] impl Hooks> for Pallet where diff --git a/frame/contracts/src/migration.rs b/frame/contracts/src/migration.rs index 035e3b4409cf9..fb99078451ef9 100644 --- a/frame/contracts/src/migration.rs +++ b/frame/contracts/src/migration.rs @@ -28,8 +28,6 @@ use sp_std::{marker::PhantomData, prelude::*}; /// Wrapper for all migrations of this pallet, based on `StorageVersion`. pub fn migrate() -> Weight { - use frame_support::traits::StorageVersion; - let version = StorageVersion::get::>(); let mut weight: Weight = 0; diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 270d3853e2ca5..ec2234cde5a6e 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -104,7 +104,7 @@ use frame_support::{ traits::{ defensive_prelude::*, ChangeMembers, Contains, ContainsLengthBound, Currency, CurrencyToVote, Get, InitializeMembers, LockIdentifier, LockableCurrency, OnUnbalanced, - ReservableCurrency, SortedMembers, StorageVersion, WithdrawReasons, + ReservableCurrency, SortedMembers, WithdrawReasons, }, weights::Weight, }; @@ -123,9 +123,6 @@ pub use weights::WeightInfo; /// All migrations. pub mod migrations; -/// The current storage version. -const STORAGE_VERSION: StorageVersion = StorageVersion::new(4); - /// The maximum votes allowed per voter. pub const MAXIMUM_VOTE: usize = 16; @@ -186,6 +183,15 @@ pub mod pallet { use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(4); + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + #[pallet::storage_version(STORAGE_VERSION)] + #[pallet::without_storage_info] + pub struct Pallet(PhantomData); + #[pallet::config] pub trait Config: frame_system::Config { type Event: From> + IsType<::Event>; @@ -247,12 +253,6 @@ pub mod pallet { type WeightInfo: WeightInfo; } - #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] - #[pallet::storage_version(STORAGE_VERSION)] - #[pallet::without_storage_info] - pub struct Pallet(PhantomData); - #[pallet::hooks] impl Hooks> for Pallet { /// What to do at the end of each block. diff --git a/frame/grandpa/src/lib.rs b/frame/grandpa/src/lib.rs index 75244c72cfd16..be613e302b21e 100644 --- a/frame/grandpa/src/lib.rs +++ b/frame/grandpa/src/lib.rs @@ -43,10 +43,11 @@ use frame_support::{ dispatch::DispatchResultWithPostInfo, pallet_prelude::Get, storage, - traits::{KeyOwnerProofSystem, OneSessionHandler, StorageVersion}, + traits::{KeyOwnerProofSystem, OneSessionHandler}, weights::{Pays, Weight}, WeakBoundedVec, }; +use scale_info::TypeInfo; use sp_runtime::{generic::DigestItem, traits::Zero, DispatchResult, KeyTypeId}; use sp_session::{GetSessionNumber, GetValidatorCount}; use sp_staking::SessionIndex; @@ -69,17 +70,15 @@ pub use equivocation::{ pub use pallet::*; -use scale_info::TypeInfo; - -/// The current storage version. -const STORAGE_VERSION: StorageVersion = StorageVersion::new(4); - #[frame_support::pallet] pub mod pallet { use super::*; use frame_support::{dispatch::DispatchResult, pallet_prelude::*}; use frame_system::pallet_prelude::*; + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(4); + #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] #[pallet::storage_version(STORAGE_VERSION)] diff --git a/frame/membership/src/lib.rs b/frame/membership/src/lib.rs index ad6273e78f0f2..8e7ea9eeec313 100644 --- a/frame/membership/src/lib.rs +++ b/frame/membership/src/lib.rs @@ -23,9 +23,7 @@ // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] -use frame_support::traits::{ - ChangeMembers, Contains, Get, InitializeMembers, SortedMembers, StorageVersion, -}; +use frame_support::traits::{ChangeMembers, Contains, Get, InitializeMembers, SortedMembers}; use sp_std::prelude::*; pub mod migrations; @@ -507,7 +505,7 @@ mod tests { use frame_support::{ assert_noop, assert_ok, ord_parameter_types, parameter_types, - traits::{ConstU32, ConstU64, GenesisBuild}, + traits::{ConstU32, ConstU64, GenesisBuild, StorageVersion}, }; use frame_system::EnsureSignedBy; diff --git a/frame/session/src/historical/mod.rs b/frame/session/src/historical/mod.rs index 76cefeb7b0532..2a749f2aae9dd 100644 --- a/frame/session/src/historical/mod.rs +++ b/frame/session/src/historical/mod.rs @@ -45,7 +45,7 @@ use sp_trie::{ use frame_support::{ print, - traits::{KeyOwnerProofSystem, StorageVersion, ValidatorSet, ValidatorSetWithIdentification}, + traits::{KeyOwnerProofSystem, ValidatorSet, ValidatorSetWithIdentification}, Parameter, }; diff --git a/frame/session/src/lib.rs b/frame/session/src/lib.rs index a460c650ec602..71ee9d1e0758a 100644 --- a/frame/session/src/lib.rs +++ b/frame/session/src/lib.rs @@ -121,7 +121,7 @@ use frame_support::{ ensure, traits::{ EstimateNextNewSession, EstimateNextSessionRotation, FindAuthor, Get, OneSessionHandler, - StorageVersion, ValidatorRegistration, ValidatorSet, + ValidatorRegistration, ValidatorSet, }, weights::Weight, Parameter, diff --git a/frame/session/src/tests.rs b/frame/session/src/tests.rs index 4f2108a9a4380..c9d2dbb53d9ba 100644 --- a/frame/session/src/tests.rs +++ b/frame/session/src/tests.rs @@ -459,7 +459,7 @@ fn test_migration_v1() { historical::{HistoricalSessions, StoredRange}, mock::Historical, }; - use frame_support::traits::PalletInfoAccess; + use frame_support::traits::{PalletInfoAccess, StorageVersion}; new_test_ext().execute_with(|| { assert!(>::iter_values().count() > 0); diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index 3e84c009b5ca6..33830257cdd82 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -1409,7 +1409,7 @@ pub mod pallet_prelude { }, traits::{ ConstU32, EnsureOrigin, Get, GetDefault, GetStorageVersion, Hooks, IsType, - PalletInfoAccess, StorageInfoTrait, + PalletInfoAccess, StorageInfoTrait, StorageVersion, }, weights::{DispatchClass, Pays, Weight}, Blake2_128, Blake2_128Concat, Blake2_256, CloneNoBound, DebugNoBound, EqNoBound, Identity, diff --git a/frame/support/test/tests/pallet.rs b/frame/support/test/tests/pallet.rs index 83f6a722f93aa..ec97c1794c27b 100644 --- a/frame/support/test/tests/pallet.rs +++ b/frame/support/test/tests/pallet.rs @@ -96,13 +96,9 @@ impl SomeAssociation2 for u64 { #[frame_support::pallet] pub mod pallet { - use super::{ - SomeAssociation1, SomeAssociation2, SomeType1, SomeType2, SomeType3, SomeType4, SomeType5, - SomeType6, SomeType7, StorageVersion, - }; + use super::*; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; - use scale_info::TypeInfo; type BalanceOf = ::Balance; diff --git a/frame/tips/src/lib.rs b/frame/tips/src/lib.rs index b9868dac43011..f43545e2b4fe8 100644 --- a/frame/tips/src/lib.rs +++ b/frame/tips/src/lib.rs @@ -70,7 +70,7 @@ use codec::{Decode, Encode}; use frame_support::{ traits::{ ContainsLengthBound, Currency, EnsureOrigin, ExistenceRequirement::KeepAlive, Get, - OnUnbalanced, ReservableCurrency, SortedMembers, StorageVersion, + OnUnbalanced, ReservableCurrency, SortedMembers, }, Parameter, }; diff --git a/frame/tips/src/tests.rs b/frame/tips/src/tests.rs index 0c58a949958fe..27cce5576a3b9 100644 --- a/frame/tips/src/tests.rs +++ b/frame/tips/src/tests.rs @@ -34,7 +34,7 @@ use frame_support::{ pallet_prelude::GenesisBuild, parameter_types, storage::StoragePrefixedMap, - traits::{ConstU32, ConstU64, SortedMembers}, + traits::{ConstU32, ConstU64, SortedMembers, StorageVersion}, PalletId, }; diff --git a/frame/vesting/src/lib.rs b/frame/vesting/src/lib.rs index 23b7d231343b4..9fb7eb8037916 100644 --- a/frame/vesting/src/lib.rs +++ b/frame/vesting/src/lib.rs @@ -57,15 +57,15 @@ pub mod weights; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ + dispatch::{DispatchError, DispatchResult}, ensure, - pallet_prelude::*, + storage::bounded_vec::BoundedVec, traits::{ Currency, ExistenceRequirement, Get, LockIdentifier, LockableCurrency, VestingSchedule, WithdrawReasons, }, + weights::Weight, }; -use frame_system::{ensure_root, ensure_signed, pallet_prelude::*}; -pub use pallet::*; use scale_info::TypeInfo; use sp_runtime::{ traits::{ @@ -74,7 +74,9 @@ use sp_runtime::{ }, RuntimeDebug, }; -use sp_std::{fmt::Debug, prelude::*}; +use sp_std::{fmt::Debug, marker::PhantomData, prelude::*}; + +pub use pallet::*; pub use vesting_info::*; pub use weights::WeightInfo; @@ -146,6 +148,8 @@ impl Get for MaxVestingSchedulesGet { #[frame_support::pallet] pub mod pallet { use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; #[pallet::config] pub trait Config: frame_system::Config { diff --git a/frame/vesting/src/mock.rs b/frame/vesting/src/mock.rs index 8a830d72b26b8..9ad8e57500e89 100644 --- a/frame/vesting/src/mock.rs +++ b/frame/vesting/src/mock.rs @@ -17,7 +17,7 @@ use frame_support::{ parameter_types, - traits::{ConstU32, ConstU64}, + traits::{ConstU32, ConstU64, GenesisBuild}, }; use sp_core::H256; use sp_runtime::{ From d99d78f4a422df32ac66c10d0ffcac33aec4fadc Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Tue, 17 May 2022 23:12:59 +0300 Subject: [PATCH 234/484] Remove unnecessary RPC boxing (#11434) --- client/beefy/rpc/src/lib.rs | 6 ++---- client/finality-grandpa/rpc/src/lib.rs | 6 ++---- client/rpc/src/author/mod.rs | 6 ++---- client/rpc/src/chain/chain_full.rs | 5 ++--- client/rpc/src/state/state_full.rs | 12 ++++-------- 5 files changed, 12 insertions(+), 23 deletions(-) diff --git a/client/beefy/rpc/src/lib.rs b/client/beefy/rpc/src/lib.rs index e4c8c76419ccb..ea35678a48b8f 100644 --- a/client/beefy/rpc/src/lib.rs +++ b/client/beefy/rpc/src/lib.rs @@ -146,11 +146,9 @@ where if let Some(mut sink) = pending.accept() { sink.pipe_from_stream(stream).await; } - } - .boxed(); + }; - self.executor - .spawn("substrate-rpc-subscription", Some("rpc"), fut.map(drop).boxed()); + self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); } async fn latest_finalized(&self) -> RpcResult { diff --git a/client/finality-grandpa/rpc/src/lib.rs b/client/finality-grandpa/rpc/src/lib.rs index 82962d716d589..cb51d71b20bf4 100644 --- a/client/finality-grandpa/rpc/src/lib.rs +++ b/client/finality-grandpa/rpc/src/lib.rs @@ -113,11 +113,9 @@ where if let Some(mut sink) = pending.accept() { sink.pipe_from_stream(stream).await; } - } - .boxed(); + }; - self.executor - .spawn("substrate-rpc-subscription", Some("rpc"), fut.map(drop).boxed()); + self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); } async fn prove_finality( diff --git a/client/rpc/src/author/mod.rs b/client/rpc/src/author/mod.rs index d10398afc813b..b8c4f5d582808 100644 --- a/client/rpc/src/author/mod.rs +++ b/client/rpc/src/author/mod.rs @@ -210,10 +210,8 @@ where }; sink.pipe_from_stream(stream).await; - } - .boxed(); + }; - self.executor - .spawn("substrate-rpc-subscription", Some("rpc"), fut.map(drop).boxed()); + self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); } } diff --git a/client/rpc/src/chain/chain_full.rs b/client/rpc/src/chain/chain_full.rs index 9ca6b3edcfe60..2d507f7b9b684 100644 --- a/client/rpc/src/chain/chain_full.rs +++ b/client/rpc/src/chain/chain_full.rs @@ -147,8 +147,7 @@ fn subscribe_headers( if let Some(mut sink) = pending.accept() { sink.pipe_from_stream(stream).await; } - } - .boxed(); + }; - executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.map(drop).boxed()); + executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); } diff --git a/client/rpc/src/state/state_full.rs b/client/rpc/src/state/state_full.rs index 48165e912b03a..0e20832b30508 100644 --- a/client/rpc/src/state/state_full.rs +++ b/client/rpc/src/state/state_full.rs @@ -404,11 +404,9 @@ where if let Some(mut sink) = pending.accept() { sink.pipe_from_stream(stream).await; } - } - .boxed(); + }; - self.executor - .spawn("substrate-rpc-subscription", Some("rpc"), fut.map(drop).boxed()); + self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); } fn subscribe_storage(&self, pending: PendingSubscription, keys: Option>) { @@ -451,11 +449,9 @@ where if let Some(mut sink) = pending.accept() { sink.pipe_from_stream(stream).await; } - } - .boxed(); + }; - self.executor - .spawn("substrate-rpc-subscription", Some("rpc"), fut.map(drop).boxed()); + self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); } async fn trace_block( From 00583212ca1893675dff626d827361830cf97139 Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Tue, 17 May 2022 17:12:02 -0400 Subject: [PATCH 235/484] Explicitly note that existing `AccountIdConversion` is truncating and add fallible `try_into...` (#10719) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * note truncating, add fallible try_into * fmt * migrate all to `truncating` * typo * uno mas * Update primitives/runtime/src/traits.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * check the bytes before and after are sensible * fmt * Update lib.rs * Update primitives/runtime/src/traits.rs Co-authored-by: Bastian Köcher Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Co-authored-by: Bastian Köcher --- frame/bounties/src/lib.rs | 4 +- frame/child-bounties/src/lib.rs | 2 +- frame/lottery/src/lib.rs | 2 +- frame/nomination-pools/src/lib.rs | 4 +- frame/society/src/lib.rs | 4 +- frame/tips/src/lib.rs | 2 +- frame/treasury/src/lib.rs | 2 +- primitives/runtime/src/traits.rs | 89 ++++++++++++++++--- .../frame-utilities-cli/src/pallet_id.rs | 2 +- 9 files changed, 87 insertions(+), 24 deletions(-) diff --git a/frame/bounties/src/lib.rs b/frame/bounties/src/lib.rs index dfeef36a1fae0..b01a36b430a4e 100644 --- a/frame/bounties/src/lib.rs +++ b/frame/bounties/src/lib.rs @@ -799,14 +799,14 @@ impl Pallet { /// This actually does computation. If you need to keep using it, then make sure you cache the /// value and only call this once. pub fn account_id() -> T::AccountId { - T::PalletId::get().into_account() + T::PalletId::get().into_account_truncating() } /// The account ID of a bounty account pub fn bounty_account_id(id: BountyIndex) -> T::AccountId { // only use two byte prefix to support 16 byte account id (used by test) // "modl" ++ "py/trsry" ++ "bt" is 14 bytes, and two bytes remaining for bounty index - T::PalletId::get().into_sub_account(("bt", id)) + T::PalletId::get().into_sub_account_truncating(("bt", id)) } fn create_bounty( diff --git a/frame/child-bounties/src/lib.rs b/frame/child-bounties/src/lib.rs index a8496d4e7af91..4f25fdcf8903a 100644 --- a/frame/child-bounties/src/lib.rs +++ b/frame/child-bounties/src/lib.rs @@ -786,7 +786,7 @@ impl Pallet { // This function is taken from the parent (bounties) pallet, but the // prefix is changed to have different AccountId when the index of // parent and child is same. - T::PalletId::get().into_sub_account(("cb", id)) + T::PalletId::get().into_sub_account_truncating(("cb", id)) } fn create_child_bounty( diff --git a/frame/lottery/src/lib.rs b/frame/lottery/src/lib.rs index bc96a029a4210..02df65a3336bf 100644 --- a/frame/lottery/src/lib.rs +++ b/frame/lottery/src/lib.rs @@ -389,7 +389,7 @@ impl Pallet { /// This actually does computation. If you need to keep using it, then make sure you cache the /// value and only call this once. pub fn account_id() -> T::AccountId { - T::PalletId::get().into_account() + T::PalletId::get().into_account_truncating() } /// Return the pot account and amount of money in the pot. diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index cdc3bdaf34389..ff77949cf6952 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -2018,14 +2018,14 @@ impl Pallet { /// Create the main, bonded account of a pool with the given id. pub fn create_bonded_account(id: PoolId) -> T::AccountId { - T::PalletId::get().into_sub_account((AccountType::Bonded, id)) + T::PalletId::get().into_sub_account_truncating((AccountType::Bonded, id)) } /// Create the reward account of a pool with the given id. pub fn create_reward_account(id: PoolId) -> T::AccountId { // NOTE: in order to have a distinction in the test account id type (u128), we put // account_type first so it does not get truncated out. - T::PalletId::get().into_sub_account((AccountType::Reward, id)) + T::PalletId::get().into_sub_account_truncating((AccountType::Reward, id)) } /// Get the member with their associated bonded and reward pool. diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index 645a0b8ba1715..2973ecd68092e 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -1726,7 +1726,7 @@ impl, I: 'static> Pallet { /// This actually does computation. If you need to keep using it, then make sure you cache the /// value and only call this once. pub fn account_id() -> T::AccountId { - T::PalletId::get().into_account() + T::PalletId::get().into_account_truncating() } /// The account ID of the payouts pot. This is where payouts are made from. @@ -1734,7 +1734,7 @@ impl, I: 'static> Pallet { /// This actually does computation. If you need to keep using it, then make sure you cache the /// value and only call this once. pub fn payouts() -> T::AccountId { - T::PalletId::get().into_sub_account(b"payouts") + T::PalletId::get().into_sub_account_truncating(b"payouts") } /// Return the duration of the lock, in blocks, with the given number of members. diff --git a/frame/tips/src/lib.rs b/frame/tips/src/lib.rs index f43545e2b4fe8..e1c7b5e77c062 100644 --- a/frame/tips/src/lib.rs +++ b/frame/tips/src/lib.rs @@ -467,7 +467,7 @@ impl Pallet { /// This actually does computation. If you need to keep using it, then make sure you cache the /// value and only call this once. pub fn account_id() -> T::AccountId { - T::PalletId::get().into_account() + T::PalletId::get().into_account_truncating() } /// Given a mutable reference to an `OpenTip`, insert the tip into it and check whether it diff --git a/frame/treasury/src/lib.rs b/frame/treasury/src/lib.rs index d080d346ba3de..419970ed18afa 100644 --- a/frame/treasury/src/lib.rs +++ b/frame/treasury/src/lib.rs @@ -437,7 +437,7 @@ impl, I: 'static> Pallet { /// This actually does computation. If you need to keep using it, then make sure you cache the /// value and only call this once. pub fn account_id() -> T::AccountId { - T::PalletId::get().into_account() + T::PalletId::get().into_account_truncating() } /// The needed bond for a proposal whose spend is `value`. diff --git a/primitives/runtime/src/traits.rs b/primitives/runtime/src/traits.rs index 393de086f3c58..1b21e7c65ddf7 100644 --- a/primitives/runtime/src/traits.rs +++ b/primitives/runtime/src/traits.rs @@ -1243,9 +1243,16 @@ impl<'a> codec::Input for TrailingZeroInput<'a> { /// This type can be converted into and possibly from an AccountId (which itself is generic). pub trait AccountIdConversion: Sized { - /// Convert into an account ID. This is infallible. - fn into_account(&self) -> AccountId { - self.into_sub_account(&()) + /// Convert into an account ID. This is infallible, and may truncate bytes to provide a result. + /// This may lead to duplicate accounts if the size of `AccountId` is less than the seed. + fn into_account_truncating(&self) -> AccountId { + self.into_sub_account_truncating(&()) + } + + /// Convert into an account ID, checking that all bytes of the seed are being used in the final + /// `AccountId` generated. If any bytes are dropped, this returns `None`. + fn try_into_account(&self) -> Option { + self.try_into_sub_account(&()) } /// Try to convert an account ID into this type. Might not succeed. @@ -1253,8 +1260,8 @@ pub trait AccountIdConversion: Sized { Self::try_from_sub_account::<()>(a).map(|x| x.0) } - /// Convert this value amalgamated with the a secondary "sub" value into an account ID. This is - /// infallible. + /// Convert this value amalgamated with the a secondary "sub" value into an account ID, + /// truncating any unused bytes. This is infallible. /// /// NOTE: The account IDs from this and from `into_account` are *not* guaranteed to be distinct /// for any given value of `self`, nor are different invocations to this with different types @@ -1262,19 +1269,45 @@ pub trait AccountIdConversion: Sized { /// - `self.into_sub_account(0u32)` /// - `self.into_sub_account(vec![0u8; 0])` /// - `self.into_account()` - fn into_sub_account(&self, sub: S) -> AccountId; + /// + /// Also, if the seed provided to this function is greater than the number of bytes which fit + /// into this `AccountId` type, then it will lead to truncation of the seed, and potentially + /// non-unique accounts. + fn into_sub_account_truncating(&self, sub: S) -> AccountId; + + /// Same as `into_sub_account_truncating`, but ensuring that all bytes of the account's seed are + /// used when generating an account. This can help guarantee that different accounts are unique, + /// besides types which encode the same as noted above. + fn try_into_sub_account(&self, sub: S) -> Option; /// Try to convert an account ID into this type. Might not succeed. fn try_from_sub_account(x: &AccountId) -> Option<(Self, S)>; } -/// Format is TYPE_ID ++ encode(parachain ID) ++ 00.... where 00... is indefinite trailing zeroes to +/// Format is TYPE_ID ++ encode(sub-seed) ++ 00.... where 00... is indefinite trailing zeroes to /// fill AccountId. impl AccountIdConversion for Id { - fn into_sub_account(&self, sub: S) -> T { + // Take the `sub` seed, and put as much of it as possible into the generated account, but + // allowing truncation of the seed if it would not fit into the account id in full. This can + // lead to two different `sub` seeds with the same account generated. + fn into_sub_account_truncating(&self, sub: S) -> T { (Id::TYPE_ID, self, sub) .using_encoded(|b| T::decode(&mut TrailingZeroInput(b))) - .expect("`AccountId` type is never greater than 32 bytes; qed") + .expect("All byte sequences are valid `AccountIds`; qed") + } + + // Same as `into_sub_account_truncating`, but returns `None` if any bytes would be truncated. + fn try_into_sub_account(&self, sub: S) -> Option { + let encoded_seed = (Id::TYPE_ID, self, sub).encode(); + let account = T::decode(&mut TrailingZeroInput(&encoded_seed)) + .expect("All byte sequences are valid `AccountIds`; qed"); + // If the `account` generated has less bytes than the `encoded_seed`, then we know that + // bytes were truncated, and we return `None`. + if encoded_seed.len() <= account.encoded_size() { + Some(account) + } else { + None + } } fn try_from_sub_account(x: &T) -> Option<(Self, S)> { @@ -1652,6 +1685,13 @@ mod tests { let _ = s.verify(&[0u8; 100][..], &Public::unchecked_from([0; 32])); } + #[derive(Encode, Decode, Default, PartialEq, Debug)] + struct U128Value(u128); + impl super::TypeId for U128Value { + const TYPE_ID: [u8; 4] = [0x0d, 0xf0, 0x0d, 0xf0]; + } + // f00df00d + #[derive(Encode, Decode, Default, PartialEq, Debug)] struct U32Value(u32); impl super::TypeId for U32Value { @@ -1669,9 +1709,19 @@ mod tests { type AccountId = u64; #[test] - fn into_account_should_work() { - let r: AccountId = U32Value::into_account(&U32Value(0xdeadbeef)); + fn into_account_truncating_should_work() { + let r: AccountId = U32Value::into_account_truncating(&U32Value(0xdeadbeef)); + assert_eq!(r, 0x_deadbeef_cafef00d); + } + + #[test] + fn try_into_account_should_work() { + let r: AccountId = U32Value::try_into_account(&U32Value(0xdeadbeef)).unwrap(); assert_eq!(r, 0x_deadbeef_cafef00d); + + // u128 is bigger than u64 would fit + let maybe: Option = U128Value::try_into_account(&U128Value(u128::MAX)); + assert!(maybe.is_none()); } #[test] @@ -1681,9 +1731,22 @@ mod tests { } #[test] - fn into_account_with_fill_should_work() { - let r: AccountId = U16Value::into_account(&U16Value(0xc0da)); + fn into_account_truncating_with_fill_should_work() { + let r: AccountId = U16Value::into_account_truncating(&U16Value(0xc0da)); + assert_eq!(r, 0x_0000_c0da_f00dcafe); + } + + #[test] + fn try_into_sub_account_should_work() { + let r: AccountId = U16Value::try_into_account(&U16Value(0xc0da)).unwrap(); assert_eq!(r, 0x_0000_c0da_f00dcafe); + + let maybe: Option = U16Value::try_into_sub_account( + &U16Value(0xc0da), + "a really large amount of additional encoded information which will certainly overflow the account id type ;)" + ); + + assert!(maybe.is_none()) } #[test] diff --git a/utils/frame/frame-utilities-cli/src/pallet_id.rs b/utils/frame/frame-utilities-cli/src/pallet_id.rs index c0a221d4bcd11..4ad82a01c2433 100644 --- a/utils/frame/frame-utilities-cli/src/pallet_id.rs +++ b/utils/frame/frame-utilities-cli/src/pallet_id.rs @@ -72,7 +72,7 @@ impl PalletIdCmd { "Cannot convert argument to palletid: argument should be 8-character string" })?; - let account_id: R::AccountId = PalletId(id_fixed_array).into_account(); + let account_id: R::AccountId = PalletId(id_fixed_array).into_account_truncating(); with_crypto_scheme!( self.crypto_scheme.scheme, From 087932b83857cc9c97d4d71492e51cc1e56fb9c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Wed, 18 May 2022 00:45:56 +0200 Subject: [PATCH 236/484] generate_storage_alias: Rewrite as proc macro attribute (#11387) * generate_storage_alias: Rewrite as proc macro attribute This rewrites the `generate_storage_alias!` declarative macro as proc-macro attribute. While doing this the name is changed to `storage_alias`. The prefix can now also be the name of a pallet. This makes storage aliases work in migrations for all kind of chains and not just for the ones that use predefined prefixes. * Fix compilation and FMT * Moare fixes * :facepalm: * ...... * Rework the syntax and support instancing * FMT * Prefix variants with `Storage` * Make it compile * Fix where clause on rust stable --- frame/bags-list/src/list/tests.rs | 10 +- frame/bags-list/src/migrations.rs | 5 +- frame/contracts/src/migration.rs | 64 +- frame/elections-phragmen/src/migrations/v3.rs | 98 +-- frame/offences/src/migration.rs | 11 +- frame/staking/src/migrations.rs | 63 +- frame/support/procedural/src/lib.rs | 8 + frame/support/procedural/src/storage_alias.rs | 566 ++++++++++++++++++ frame/support/src/lib.rs | 232 +++---- .../support/src/storage/bounded_btree_map.rs | 15 +- .../support/src/storage/bounded_btree_set.rs | 15 +- frame/support/src/storage/bounded_vec.rs | 15 +- .../src/storage/generator/double_map.rs | 6 +- frame/support/src/storage/generator/map.rs | 3 +- frame/support/src/storage/generator/nmap.rs | 22 +- frame/support/src/storage/mod.rs | 19 +- frame/support/src/storage/types/nmap.rs | 8 +- frame/support/src/storage/weak_bounded_vec.rs | 13 +- frame/support/test/tests/pallet.rs | 15 + frame/support/test/tests/pallet_instance.rs | 13 + frame/system/src/migrations/mod.rs | 66 +- 21 files changed, 886 insertions(+), 381 deletions(-) create mode 100644 frame/support/procedural/src/storage_alias.rs diff --git a/frame/bags-list/src/list/tests.rs b/frame/bags-list/src/list/tests.rs index ff7dd2871c237..40a174b35a5d3 100644 --- a/frame/bags-list/src/list/tests.rs +++ b/frame/bags-list/src/list/tests.rs @@ -368,12 +368,10 @@ mod list { assert_eq!(crate::ListNodes::::count(), 4); // we do some wacky stuff here to get access to the counter, since it is (reasonably) // not exposed as mutable in any sense. - frame_support::generate_storage_alias!( - BagsList, - CounterForListNodes - => Value - ); - CounterForListNodes::mutate(|counter| *counter += 1); + #[frame_support::storage_alias] + type CounterForListNodes = + StorageValue, u32, frame_support::pallet_prelude::ValueQuery>; + CounterForListNodes::::mutate(|counter| *counter += 1); assert_eq!(crate::ListNodes::::count(), 5); assert_eq!(List::::sanity_check(), Err("iter_count != stored_count")); diff --git a/frame/bags-list/src/migrations.rs b/frame/bags-list/src/migrations.rs index 696733e8c7ba5..f8041327f10be 100644 --- a/frame/bags-list/src/migrations.rs +++ b/frame/bags-list/src/migrations.rs @@ -30,11 +30,12 @@ impl OnRuntimeUpgrade for CheckCounterPrefix { fn pre_upgrade() -> Result<(), &'static str> { use frame_support::ensure; // The old explicit storage item. - frame_support::generate_storage_alias!(BagsList, CounterForListNodes => Value); + #[frame_support::storage_alias] + type CounterForListNodes = StorageValue, u32>; // ensure that a value exists in the counter struct. ensure!( - crate::ListNodes::::count() == CounterForListNodes::get().unwrap(), + crate::ListNodes::::count() == CounterForListNodes::::get().unwrap(), "wrong list node counter" ); diff --git a/frame/contracts/src/migration.rs b/frame/contracts/src/migration.rs index fb99078451ef9..0832ebadac9c6 100644 --- a/frame/contracts/src/migration.rs +++ b/frame/contracts/src/migration.rs @@ -18,11 +18,8 @@ use crate::{BalanceOf, CodeHash, Config, Pallet, TrieId, Weight}; use codec::{Decode, Encode}; use frame_support::{ - codec, generate_storage_alias, - pallet_prelude::*, - storage::migration, - traits::{Get, PalletInfoAccess}, - Identity, Twox64Concat, + codec, pallet_prelude::*, storage::migration, storage_alias, traits::Get, Identity, + Twox64Concat, }; use sp_std::{marker::PhantomData, prelude::*}; @@ -117,15 +114,16 @@ mod v5 { trie_id: TrieId, } - generate_storage_alias!( - Contracts, - ContractInfoOf => Map<(Twox64Concat, T::AccountId), ContractInfo> - ); + #[storage_alias] + type ContractInfoOf = StorageMap< + Pallet, + Twox64Concat, + ::AccountId, + ContractInfo, + >; - generate_storage_alias!( - Contracts, - DeletionQueue => Value> - ); + #[storage_alias] + type DeletionQueue = StorageValue, Vec>; pub fn migrate() -> Weight { let mut weight: Weight = 0; @@ -142,7 +140,7 @@ mod v5 { } }); - DeletionQueue::translate(|old: Option>| { + DeletionQueue::::translate(|old: Option>| { weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); old.map(|old| old.into_iter().map(|o| DeletedContract { trie_id: o.trie_id }).collect()) }) @@ -202,20 +200,19 @@ mod v6 { type ContractInfo = RawContractInfo, BalanceOf>; - generate_storage_alias!( - Contracts, - ContractInfoOf => Map<(Twox64Concat, T::AccountId), ContractInfo> - ); + #[storage_alias] + type ContractInfoOf = StorageMap< + Pallet, + Twox64Concat, + ::AccountId, + ContractInfo, + >; - generate_storage_alias!( - Contracts, - CodeStorage => Map<(Identity, CodeHash), PrefabWasmModule> - ); + #[storage_alias] + type CodeStorage = StorageMap, Identity, CodeHash, PrefabWasmModule>; - generate_storage_alias!( - Contracts, - OwnerInfoOf => Map<(Identity, CodeHash), OwnerInfo> - ); + #[storage_alias] + type OwnerInfoOf = StorageMap, Identity, CodeHash, OwnerInfo>; pub fn migrate() -> Weight { let mut weight: Weight = 0; @@ -259,15 +256,12 @@ mod v7 { use super::*; pub fn migrate() -> Weight { - generate_storage_alias!( - Contracts, - AccountCounter => Value - ); - generate_storage_alias!( - Contracts, - Nonce => Value - ); - Nonce::set(AccountCounter::take()); + #[storage_alias] + type AccountCounter = StorageValue, u64, ValueQuery>; + #[storage_alias] + type Nonce = StorageValue, u64, ValueQuery>; + + Nonce::::set(AccountCounter::::take()); T::DbWeight::get().reads_writes(1, 2) } } diff --git a/frame/elections-phragmen/src/migrations/v3.rs b/frame/elections-phragmen/src/migrations/v3.rs index c6a7ce7e7ca1b..b1cdd4be98541 100644 --- a/frame/elections-phragmen/src/migrations/v3.rs +++ b/frame/elections-phragmen/src/migrations/v3.rs @@ -17,12 +17,10 @@ //! Migrations to version [`3.0.0`], as denoted by the changelog. +use crate::{Config, Pallet}; use codec::{Decode, Encode, FullCodec}; use frame_support::{ - pallet_prelude::ValueQuery, - traits::{PalletInfoAccess, StorageVersion}, - weights::Weight, - RuntimeDebug, Twox64Concat, + pallet_prelude::ValueQuery, traits::StorageVersion, weights::Weight, RuntimeDebug, Twox64Concat, }; use sp_std::prelude::*; @@ -42,9 +40,6 @@ struct Voter { /// Trait to implement to give information about types used for migration pub trait V2ToV3 { - /// The elections-phragmen pallet. - type Pallet: 'static + PalletInfoAccess; - /// System config account id type AccountId: 'static + FullCodec; @@ -52,30 +47,31 @@ pub trait V2ToV3 { type Balance: 'static + FullCodec + Copy; } -frame_support::generate_storage_alias!( - PhragmenElection, Candidates => Value< - Vec<(T::AccountId, T::Balance)>, - ValueQuery - > -); -frame_support::generate_storage_alias!( - PhragmenElection, Members => Value< - Vec>, - ValueQuery - > -); -frame_support::generate_storage_alias!( - PhragmenElection, RunnersUp => Value< - Vec>, - ValueQuery - > -); -frame_support::generate_storage_alias!( - PhragmenElection, Voting => Map< - (Twox64Concat, T::AccountId), - Voter - > -); +#[frame_support::storage_alias] +type Candidates = + StorageValue, Vec<(::AccountId, ::Balance)>, ValueQuery>; + +#[frame_support::storage_alias] +type Members = StorageValue< + Pallet, + Vec::AccountId, ::Balance>>, + ValueQuery, +>; + +#[frame_support::storage_alias] +type RunnersUp = StorageValue< + Pallet, + Vec::AccountId, ::Balance>>, + ValueQuery, +>; + +#[frame_support::storage_alias] +type Voting = StorageMap< + Pallet, + Twox64Concat, + ::AccountId, + Voter<::AccountId, ::Balance>, +>; /// Apply all of the migrations from 2 to 3. /// @@ -86,8 +82,11 @@ frame_support::generate_storage_alias!( /// /// Be aware that this migration is intended to be used only for the mentioned versions. Use /// with care and run at your own risk. -pub fn apply(old_voter_bond: T::Balance, old_candidacy_bond: T::Balance) -> Weight { - let storage_version = StorageVersion::get::(); +pub fn apply( + old_voter_bond: V::Balance, + old_candidacy_bond: V::Balance, +) -> Weight { + let storage_version = StorageVersion::get::>(); log::info!( target: "runtime::elections-phragmen", "Running migration for elections-phragmen with storage version {:?}", @@ -95,12 +94,12 @@ pub fn apply(old_voter_bond: T::Balance, old_candidacy_bond: T::Balan ); if storage_version <= 2 { - migrate_voters_to_recorded_deposit::(old_voter_bond); - migrate_candidates_to_recorded_deposit::(old_candidacy_bond); - migrate_runners_up_to_recorded_deposit::(old_candidacy_bond); - migrate_members_to_recorded_deposit::(old_candidacy_bond); + migrate_voters_to_recorded_deposit::(old_voter_bond); + migrate_candidates_to_recorded_deposit::(old_candidacy_bond); + migrate_runners_up_to_recorded_deposit::(old_candidacy_bond); + migrate_members_to_recorded_deposit::(old_candidacy_bond); - StorageVersion::new(3).put::(); + StorageVersion::new(3).put::>(); Weight::max_value() } else { @@ -114,21 +113,21 @@ pub fn apply(old_voter_bond: T::Balance, old_candidacy_bond: T::Balan } /// Migrate from the old legacy voting bond (fixed) to the new one (per-vote dynamic). -pub fn migrate_voters_to_recorded_deposit(old_deposit: T::Balance) { - >::translate::<(T::Balance, Vec), _>(|_who, (stake, votes)| { +pub fn migrate_voters_to_recorded_deposit(old_deposit: V::Balance) { + >::translate::<(V::Balance, Vec), _>(|_who, (stake, votes)| { Some(Voter { votes, stake, deposit: old_deposit }) }); log::info!( target: "runtime::elections-phragmen", "migrated {} voter accounts.", - >::iter().count(), + >::iter().count(), ); } /// Migrate all candidates to recorded deposit. -pub fn migrate_candidates_to_recorded_deposit(old_deposit: T::Balance) { - let _ = >::translate::, _>(|maybe_old_candidates| { +pub fn migrate_candidates_to_recorded_deposit(old_deposit: V::Balance) { + let _ = >::translate::, _>(|maybe_old_candidates| { maybe_old_candidates.map(|old_candidates| { log::info!( target: "runtime::elections-phragmen", @@ -141,8 +140,8 @@ pub fn migrate_candidates_to_recorded_deposit(old_deposit: T::Balance } /// Migrate all members to recorded deposit. -pub fn migrate_members_to_recorded_deposit(old_deposit: T::Balance) { - let _ = >::translate::, _>(|maybe_old_members| { +pub fn migrate_members_to_recorded_deposit(old_deposit: V::Balance) { + let _ = >::translate::, _>(|maybe_old_members| { maybe_old_members.map(|old_members| { log::info!( target: "runtime::elections-phragmen", @@ -158,9 +157,9 @@ pub fn migrate_members_to_recorded_deposit(old_deposit: T::Balance) { } /// Migrate all runners-up to recorded deposit. -pub fn migrate_runners_up_to_recorded_deposit(old_deposit: T::Balance) { - let _ = - >::translate::, _>(|maybe_old_runners_up| { +pub fn migrate_runners_up_to_recorded_deposit(old_deposit: V::Balance) { + let _ = >::translate::, _>( + |maybe_old_runners_up| { maybe_old_runners_up.map(|old_runners_up| { log::info!( target: "runtime::elections-phragmen", @@ -172,5 +171,6 @@ pub fn migrate_runners_up_to_recorded_deposit(old_deposit: T::Balance .map(|(who, stake)| SeatHolder { who, stake, deposit: old_deposit }) .collect::>() }) - }); + }, + ); } diff --git a/frame/offences/src/migration.rs b/frame/offences/src/migration.rs index 0d6c98b564cb1..e8fab1c0babc7 100644 --- a/frame/offences/src/migration.rs +++ b/frame/offences/src/migration.rs @@ -16,9 +16,7 @@ // limitations under the License. use super::{Config, OffenceDetails, Perbill, SessionIndex}; -use frame_support::{ - generate_storage_alias, pallet_prelude::ValueQuery, traits::Get, weights::Weight, -}; +use frame_support::{pallet_prelude::ValueQuery, storage_alias, traits::Get, weights::Weight}; use sp_staking::offence::{DisableStrategy, OnOffenceHandler}; use sp_std::vec::Vec; @@ -31,10 +29,9 @@ type DeferredOffenceOf = ( // Deferred reports that have been rejected by the offence handler and need to be submitted // at a later time. -generate_storage_alias!( - Offences, - DeferredOffences => Value>, ValueQuery> -); +#[storage_alias] +type DeferredOffences = + StorageValue, Vec>, ValueQuery>; pub fn remove_deferred_storage() -> Weight { let mut weight = T::DbWeight::get().reads_writes(1, 1); diff --git a/frame/staking/src/migrations.rs b/frame/staking/src/migrations.rs index 96c905f4e5942..14846da8a5d54 100644 --- a/frame/staking/src/migrations.rs +++ b/frame/staking/src/migrations.rs @@ -139,18 +139,20 @@ pub mod v8 { pub mod v7 { use super::*; - use frame_support::generate_storage_alias; + use frame_support::storage_alias; - generate_storage_alias!(Staking, CounterForValidators => Value); - generate_storage_alias!(Staking, CounterForNominators => Value); + #[storage_alias] + type CounterForValidators = StorageValue, u32>; + #[storage_alias] + type CounterForNominators = StorageValue, u32>; pub fn pre_migrate() -> Result<(), &'static str> { assert!( - CounterForValidators::get().unwrap().is_zero(), + CounterForValidators::::get().unwrap().is_zero(), "CounterForValidators already set." ); assert!( - CounterForNominators::get().unwrap().is_zero(), + CounterForNominators::::get().unwrap().is_zero(), "CounterForNominators already set." ); assert!(Validators::::count().is_zero(), "Validators already set."); @@ -164,8 +166,8 @@ pub mod v7 { let validator_count = Validators::::iter().count() as u32; let nominator_count = Nominators::::iter().count() as u32; - CounterForValidators::put(validator_count); - CounterForNominators::put(nominator_count); + CounterForValidators::::put(validator_count); + CounterForNominators::::put(nominator_count); StorageVersion::::put(Releases::V7_0_0); log!(info, "Completed staking migration to Releases::V7_0_0"); @@ -176,26 +178,35 @@ pub mod v7 { pub mod v6 { use super::*; - use frame_support::{generate_storage_alias, traits::Get, weights::Weight}; + use frame_support::{storage_alias, traits::Get, weights::Weight}; // NOTE: value type doesn't matter, we just set it to () here. - generate_storage_alias!(Staking, SnapshotValidators => Value<()>); - generate_storage_alias!(Staking, SnapshotNominators => Value<()>); - generate_storage_alias!(Staking, QueuedElected => Value<()>); - generate_storage_alias!(Staking, QueuedScore => Value<()>); - generate_storage_alias!(Staking, EraElectionStatus => Value<()>); - generate_storage_alias!(Staking, IsCurrentSessionFinal => Value<()>); + #[storage_alias] + type SnapshotValidators = StorageValue, ()>; + #[storage_alias] + type SnapshotNominators = StorageValue, ()>; + #[storage_alias] + type QueuedElected = StorageValue, ()>; + #[storage_alias] + type QueuedScore = StorageValue, ()>; + #[storage_alias] + type EraElectionStatus = StorageValue, ()>; + #[storage_alias] + type IsCurrentSessionFinal = StorageValue, ()>; /// check to execute prior to migration. pub fn pre_migrate() -> Result<(), &'static str> { // these may or may not exist. - log!(info, "SnapshotValidators.exits()? {:?}", SnapshotValidators::exists()); - log!(info, "SnapshotNominators.exits()? {:?}", SnapshotNominators::exists()); - log!(info, "QueuedElected.exits()? {:?}", QueuedElected::exists()); - log!(info, "QueuedScore.exits()? {:?}", QueuedScore::exists()); + log!(info, "SnapshotValidators.exits()? {:?}", SnapshotValidators::::exists()); + log!(info, "SnapshotNominators.exits()? {:?}", SnapshotNominators::::exists()); + log!(info, "QueuedElected.exits()? {:?}", QueuedElected::::exists()); + log!(info, "QueuedScore.exits()? {:?}", QueuedScore::::exists()); // these must exist. - assert!(IsCurrentSessionFinal::exists(), "IsCurrentSessionFinal storage item not found!"); - assert!(EraElectionStatus::exists(), "EraElectionStatus storage item not found!"); + assert!( + IsCurrentSessionFinal::::exists(), + "IsCurrentSessionFinal storage item not found!" + ); + assert!(EraElectionStatus::::exists(), "EraElectionStatus storage item not found!"); Ok(()) } @@ -203,12 +214,12 @@ pub mod v6 { pub fn migrate() -> Weight { log!(info, "Migrating staking to Releases::V6_0_0"); - SnapshotValidators::kill(); - SnapshotNominators::kill(); - QueuedElected::kill(); - QueuedScore::kill(); - EraElectionStatus::kill(); - IsCurrentSessionFinal::kill(); + SnapshotValidators::::kill(); + SnapshotNominators::::kill(); + QueuedElected::::kill(); + QueuedScore::::kill(); + EraElectionStatus::::kill(); + IsCurrentSessionFinal::::kill(); StorageVersion::::put(Releases::V6_0_0); log!(info, "Done."); diff --git a/frame/support/procedural/src/lib.rs b/frame/support/procedural/src/lib.rs index 92564e94493c1..f8aaa5fe37749 100644 --- a/frame/support/procedural/src/lib.rs +++ b/frame/support/procedural/src/lib.rs @@ -31,6 +31,7 @@ mod pallet; mod pallet_error; mod partial_eq_no_bound; mod storage; +mod storage_alias; mod transactional; mod tt_macro; @@ -575,3 +576,10 @@ pub fn derive_pallet_error(input: TokenStream) -> TokenStream { pub fn __create_tt_macro(input: TokenStream) -> TokenStream { tt_macro::create_tt_return_macro(input) } + +#[proc_macro_attribute] +pub fn storage_alias(_: TokenStream, input: TokenStream) -> TokenStream { + storage_alias::storage_alias(input.into()) + .unwrap_or_else(|r| r.into_compile_error()) + .into() +} diff --git a/frame/support/procedural/src/storage_alias.rs b/frame/support/procedural/src/storage_alias.rs new file mode 100644 index 0000000000000..adec995016653 --- /dev/null +++ b/frame/support/procedural/src/storage_alias.rs @@ -0,0 +1,566 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Implementation of the `storage_alias` attribute macro. + +use frame_support_procedural_tools::generate_crate_access_2018; +use proc_macro2::{Span, TokenStream}; +use quote::{quote, ToTokens}; +use syn::{ + ext::IdentExt, + parenthesized, + parse::{Parse, ParseStream}, + punctuated::Punctuated, + token, Attribute, Error, Ident, Result, Token, Type, TypeParam, Visibility, WhereClause, +}; + +/// Represents a path that only consists of [`Ident`] separated by `::`. +struct SimplePath { + leading_colon: Option, + segments: Punctuated, +} + +impl SimplePath { + /// Returns the [`Ident`] of this path. + /// + /// It only returns `Some(_)` if there is exactly one element and no leading colon. + fn get_ident(&self) -> Option<&Ident> { + if self.segments.len() != 1 || self.leading_colon.is_some() { + None + } else { + self.segments.first() + } + } +} + +impl Parse for SimplePath { + fn parse(input: ParseStream<'_>) -> Result { + Ok(Self { + leading_colon: if input.peek(Token![::]) { Some(input.parse()?) } else { None }, + segments: Punctuated::parse_separated_nonempty_with(input, |p| Ident::parse_any(p))?, + }) + } +} + +impl ToTokens for SimplePath { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.leading_colon.to_tokens(tokens); + self.segments.to_tokens(tokens); + } +} + +/// Represents generics which only support [`Ident`] separated by commas as you would pass it to a +/// type. +struct TypeGenerics { + lt_token: Token![<], + params: Punctuated, + gt_token: Token![>], +} + +impl TypeGenerics { + /// Returns the generics for types declarations etc. + fn iter(&self) -> impl Iterator { + self.params.iter() + } +} + +impl Parse for TypeGenerics { + fn parse(input: ParseStream<'_>) -> Result { + Ok(Self { + lt_token: input.parse()?, + params: Punctuated::parse_separated_nonempty(input)?, + gt_token: input.parse()?, + }) + } +} + +impl ToTokens for TypeGenerics { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.lt_token.to_tokens(tokens); + self.params.to_tokens(tokens); + self.gt_token.to_tokens(tokens); + } +} + +/// Represents generics which only support [`TypeParam`] separated by commas. +struct SimpleGenerics { + lt_token: Token![<], + params: Punctuated, + gt_token: Token![>], +} + +impl SimpleGenerics { + /// Returns the generics for types declarations etc. + fn type_generics(&self) -> impl Iterator { + self.params.iter().map(|p| &p.ident) + } + + /// Returns the generics for the `impl` block. + fn impl_generics(&self) -> impl Iterator { + self.params.iter() + } +} + +impl Parse for SimpleGenerics { + fn parse(input: ParseStream<'_>) -> Result { + Ok(Self { + lt_token: input.parse()?, + params: Punctuated::parse_separated_nonempty(input)?, + gt_token: input.parse()?, + }) + } +} + +impl ToTokens for SimpleGenerics { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.lt_token.to_tokens(tokens); + self.params.to_tokens(tokens); + self.gt_token.to_tokens(tokens); + } +} + +mod storage_types { + syn::custom_keyword!(StorageValue); + syn::custom_keyword!(StorageMap); + syn::custom_keyword!(StorageDoubleMap); + syn::custom_keyword!(StorageNMap); +} + +/// The supported storage types +enum StorageType { + Value { + _kw: storage_types::StorageValue, + _lt_token: Token![<], + prefix: SimplePath, + prefix_generics: Option, + _value_comma: Token![,], + value_ty: Type, + query_type: Option<(Token![,], Type)>, + _trailing_comma: Option, + _gt_token: Token![>], + }, + Map { + _kw: storage_types::StorageMap, + _lt_token: Token![<], + prefix: SimplePath, + prefix_generics: Option, + _hasher_comma: Token![,], + hasher_ty: Type, + _key_comma: Token![,], + key_ty: Type, + _value_comma: Token![,], + value_ty: Type, + query_type: Option<(Token![,], Type)>, + _trailing_comma: Option, + _gt_token: Token![>], + }, + DoubleMap { + _kw: storage_types::StorageDoubleMap, + _lt_token: Token![<], + prefix: SimplePath, + prefix_generics: Option, + _hasher1_comma: Token![,], + hasher1_ty: Type, + _key1_comma: Token![,], + key1_ty: Type, + _hasher2_comma: Token![,], + hasher2_ty: Type, + _key2_comma: Token![,], + key2_ty: Type, + _value_comma: Token![,], + value_ty: Type, + query_type: Option<(Token![,], Type)>, + _trailing_comma: Option, + _gt_token: Token![>], + }, + NMap { + _kw: storage_types::StorageNMap, + _lt_token: Token![<], + prefix: SimplePath, + prefix_generics: Option, + _paren_comma: Token![,], + _paren_token: token::Paren, + key_types: Punctuated, + _value_comma: Token![,], + value_ty: Type, + query_type: Option<(Token![,], Type)>, + _trailing_comma: Option, + _gt_token: Token![>], + }, +} + +impl StorageType { + /// Generate the actual type declaration. + fn generate_type_declaration( + &self, + crate_: &Ident, + storage_instance: &StorageInstance, + storage_name: &Ident, + storage_generics: Option<&SimpleGenerics>, + visibility: &Visibility, + attributes: &[Attribute], + ) -> TokenStream { + let storage_instance = &storage_instance.name; + let attributes = attributes.iter(); + let storage_generics = storage_generics.map(|g| { + let generics = g.type_generics(); + + quote!( < #( #generics ),* > ) + }); + + match self { + Self::Value { value_ty, query_type, prefix_generics, .. } => { + let query_type = query_type.as_ref().map(|(c, t)| quote!(#c #t)); + + quote! { + #( #attributes )* + #visibility type #storage_name #storage_generics = #crate_::storage::types::StorageValue< + #storage_instance #prefix_generics, + #value_ty + #query_type + >; + } + }, + Self::Map { value_ty, query_type, hasher_ty, key_ty, prefix_generics, .. } => { + let query_type = query_type.as_ref().map(|(c, t)| quote!(#c #t)); + + quote! { + #( #attributes )* + #visibility type #storage_name #storage_generics = #crate_::storage::types::StorageMap< + #storage_instance #prefix_generics, + #hasher_ty, + #key_ty, + #value_ty + #query_type + >; + } + }, + Self::DoubleMap { + value_ty, + query_type, + hasher1_ty, + key1_ty, + hasher2_ty, + key2_ty, + prefix_generics, + .. + } => { + let query_type = query_type.as_ref().map(|(c, t)| quote!(#c #t)); + + quote! { + #( #attributes )* + #visibility type #storage_name #storage_generics = #crate_::storage::types::StorageDoubleMap< + #storage_instance #prefix_generics, + #hasher1_ty, + #key1_ty, + #hasher2_ty, + #key2_ty, + #value_ty + #query_type + >; + } + }, + Self::NMap { value_ty, query_type, key_types, prefix_generics, .. } => { + let query_type = query_type.as_ref().map(|(c, t)| quote!(#c #t)); + let key_types = key_types.iter(); + + quote! { + #( #attributes )* + #visibility type #storage_name #storage_generics = #crate_::storage::types::StorageNMap< + #storage_instance #prefix_generics, + ( #( #key_types ),* ), + #value_ty + #query_type + >; + } + }, + } + } + + /// The prefix for this storage type. + fn prefix(&self) -> &SimplePath { + match self { + Self::Value { prefix, .. } | + Self::Map { prefix, .. } | + Self::NMap { prefix, .. } | + Self::DoubleMap { prefix, .. } => prefix, + } + } + + /// The prefix generics for this storage type. + fn prefix_generics(&self) -> Option<&TypeGenerics> { + match self { + Self::Value { prefix_generics, .. } | + Self::Map { prefix_generics, .. } | + Self::NMap { prefix_generics, .. } | + Self::DoubleMap { prefix_generics, .. } => prefix_generics.as_ref(), + } + } +} + +impl Parse for StorageType { + fn parse(input: ParseStream<'_>) -> Result { + let lookahead = input.lookahead1(); + + let parse_query_type = |input: ParseStream<'_>| -> Result> { + if input.peek(Token![,]) && !input.peek2(Token![>]) { + Ok(Some((input.parse()?, input.parse()?))) + } else { + Ok(None) + } + }; + + let parse_pallet_generics = |input: ParseStream<'_>| -> Result> { + let lookahead = input.lookahead1(); + if lookahead.peek(Token![<]) { + Ok(Some(input.parse()?)) + } else if lookahead.peek(Token![,]) { + Ok(None) + } else { + Err(lookahead.error()) + } + }; + + if lookahead.peek(storage_types::StorageValue) { + Ok(Self::Value { + _kw: input.parse()?, + _lt_token: input.parse()?, + prefix: input.parse()?, + prefix_generics: parse_pallet_generics(input)?, + _value_comma: input.parse()?, + value_ty: input.parse()?, + query_type: parse_query_type(input)?, + _trailing_comma: input.peek(Token![,]).then(|| input.parse()).transpose()?, + _gt_token: input.parse()?, + }) + } else if lookahead.peek(storage_types::StorageMap) { + Ok(Self::Map { + _kw: input.parse()?, + _lt_token: input.parse()?, + prefix: input.parse()?, + prefix_generics: parse_pallet_generics(input)?, + _hasher_comma: input.parse()?, + hasher_ty: input.parse()?, + _key_comma: input.parse()?, + key_ty: input.parse()?, + _value_comma: input.parse()?, + value_ty: input.parse()?, + query_type: parse_query_type(input)?, + _trailing_comma: input.peek(Token![,]).then(|| input.parse()).transpose()?, + _gt_token: input.parse()?, + }) + } else if lookahead.peek(storage_types::StorageDoubleMap) { + Ok(Self::DoubleMap { + _kw: input.parse()?, + _lt_token: input.parse()?, + prefix: input.parse()?, + prefix_generics: parse_pallet_generics(input)?, + _hasher1_comma: input.parse()?, + hasher1_ty: input.parse()?, + _key1_comma: input.parse()?, + key1_ty: input.parse()?, + _hasher2_comma: input.parse()?, + hasher2_ty: input.parse()?, + _key2_comma: input.parse()?, + key2_ty: input.parse()?, + _value_comma: input.parse()?, + value_ty: input.parse()?, + query_type: parse_query_type(input)?, + _trailing_comma: input.peek(Token![,]).then(|| input.parse()).transpose()?, + _gt_token: input.parse()?, + }) + } else if lookahead.peek(storage_types::StorageNMap) { + let content; + Ok(Self::NMap { + _kw: input.parse()?, + _lt_token: input.parse()?, + prefix: input.parse()?, + prefix_generics: parse_pallet_generics(input)?, + _paren_comma: input.parse()?, + _paren_token: parenthesized!(content in input), + key_types: Punctuated::parse_terminated(&content)?, + _value_comma: input.parse()?, + value_ty: input.parse()?, + query_type: parse_query_type(input)?, + _trailing_comma: input.peek(Token![,]).then(|| input.parse()).transpose()?, + _gt_token: input.parse()?, + }) + } else { + Err(lookahead.error()) + } + } +} + +/// The input expected by this macro. +struct Input { + attributes: Vec, + visibility: Visibility, + _type: Token![type], + storage_name: Ident, + storage_generics: Option, + where_clause: Option, + _equal: Token![=], + storage_type: StorageType, + _semicolon: Token![;], +} + +impl Parse for Input { + fn parse(input: ParseStream<'_>) -> Result { + let attributes = input.call(Attribute::parse_outer)?; + let visibility = input.parse()?; + let _type = input.parse()?; + let storage_name = input.parse()?; + + let lookahead = input.lookahead1(); + let storage_generics = if lookahead.peek(Token![<]) { + Some(input.parse()?) + } else if lookahead.peek(Token![=]) { + None + } else { + return Err(lookahead.error()) + }; + + let lookahead = input.lookahead1(); + let where_clause = if lookahead.peek(Token![where]) { + Some(input.parse()?) + } else if lookahead.peek(Token![=]) { + None + } else { + return Err(lookahead.error()) + }; + + let _equal = input.parse()?; + + let storage_type = input.parse()?; + + let _semicolon = input.parse()?; + + Ok(Self { + attributes, + visibility, + _type, + storage_name, + storage_generics, + _equal, + storage_type, + where_clause, + _semicolon, + }) + } +} + +/// Implementation of the `storage_alias` attribute macro. +pub fn storage_alias(input: TokenStream) -> Result { + let input = syn::parse2::(input)?; + let crate_ = generate_crate_access_2018("frame-support")?; + + let storage_instance = generate_storage_instance( + &crate_, + &input.storage_name, + input.storage_generics.as_ref(), + input.where_clause.as_ref(), + input.storage_type.prefix(), + input.storage_type.prefix_generics(), + &input.visibility, + )?; + + let definition = input.storage_type.generate_type_declaration( + &crate_, + &storage_instance, + &input.storage_name, + input.storage_generics.as_ref(), + &input.visibility, + &input.attributes, + ); + + let storage_instance_code = storage_instance.code; + + Ok(quote! { + #storage_instance_code + + #definition + }) +} + +/// The storage instance to use for the storage alias. +struct StorageInstance { + name: Ident, + code: TokenStream, +} + +/// Generate the [`StorageInstance`] for the storage alias. +fn generate_storage_instance( + crate_: &Ident, + storage_name: &Ident, + storage_generics: Option<&SimpleGenerics>, + storage_where_clause: Option<&WhereClause>, + prefix: &SimplePath, + prefix_generics: Option<&TypeGenerics>, + visibility: &Visibility, +) -> Result { + let (pallet_prefix, impl_generics, type_generics) = + if let Some((prefix_generics, storage_generics)) = + prefix_generics.and_then(|p| storage_generics.map(|s| (p, s))) + { + let type_generics = prefix_generics.iter(); + let type_generics2 = prefix_generics.iter(); + let impl_generics = storage_generics + .impl_generics() + .filter(|g| prefix_generics.params.iter().any(|pg| *pg == g.ident)); + + ( + quote! { + <#prefix < #( #type_generics2 ),* > as #crate_::traits::PalletInfoAccess>::name() + }, + quote!( #( #impl_generics ),* ), + quote!( #( #type_generics ),* ), + ) + } else if let Some(prefix) = prefix.get_ident() { + let prefix_str = prefix.to_string(); + + (quote!(#prefix_str), quote!(), quote!()) + } else { + return Err(Error::new_spanned( + prefix, + "If there are no generics, the prefix is only allowed to be an identifier.", + )) + }; + + let where_clause = storage_where_clause.map(|w| quote!(#w)).unwrap_or_default(); + + let name = Ident::new(&format!("{}_Storage_Instance", storage_name), Span::call_site()); + let storage_name_str = storage_name.to_string(); + + // Implement `StorageInstance` trait. + let code = quote! { + #visibility struct #name< #impl_generics >( + #crate_::sp_std::marker::PhantomData<(#type_generics)> + ) #where_clause; + + impl<#impl_generics> #crate_::traits::StorageInstance + for #name< #type_generics > #where_clause + { + fn pallet_prefix() -> &'static str { + #pallet_prefix + } + + const STORAGE_PREFIX: &'static str = #storage_name_str; + } + }; + + Ok(StorageInstance { name, code }) +} diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index 33830257cdd82..d73a01187bfcc 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -166,178 +166,74 @@ macro_rules! bounded_btree_map { /// Useful for creating a *storage-like* struct for test and migrations. /// /// ``` -/// # use frame_support::generate_storage_alias; +/// # use frame_support::storage_alias; /// use frame_support::codec; /// use frame_support::Twox64Concat; /// // generate a storage value with type u32. -/// generate_storage_alias!(Prefix, StorageName => Value); +/// #[storage_alias] +/// type StorageName = StorageValue; /// /// // generate a double map from `(u32, u32)` (with hashers `Twox64Concat` for each key) /// // to `Vec` -/// generate_storage_alias!( -/// OtherPrefix, OtherStorageName => DoubleMap< -/// (Twox64Concat, u32), -/// (Twox64Concat, u32), -/// Vec -/// > -/// ); +/// #[storage_alias] +/// type OtherStorageName = StorageDoubleMap< +/// OtherPrefix, +/// Twox64Concat, +/// u32, +/// Twox64Concat, +/// u32, +/// Vec, +/// >; /// /// // optionally specify the query type /// use frame_support::pallet_prelude::{ValueQuery, OptionQuery}; -/// generate_storage_alias!(Prefix, ValueName => Value); -/// generate_storage_alias!( -/// Prefix, SomeStorageName => DoubleMap< -/// (Twox64Concat, u32), -/// (Twox64Concat, u32), -/// Vec, -/// ValueQuery -/// > -/// ); +/// #[storage_alias] +/// type ValueName = StorageValue; +/// #[storage_alias] +/// type SomeStorageName = StorageMap< +/// Prefix, +/// Twox64Concat, +/// u32, +/// Vec, +/// ValueQuery, +/// >; /// /// // generate a map from `Config::AccountId` (with hasher `Twox64Concat`) to `Vec` /// trait Config { type AccountId: codec::FullCodec; } -/// generate_storage_alias!( -/// Prefix, GenericStorage => Map<(Twox64Concat, T::AccountId), Vec> -/// ); +/// #[storage_alias] +/// type GenericStorage = StorageMap::AccountId, Vec>; +/// +/// // It also supports NMap +/// use frame_support::storage::types::Key as NMapKey; +/// +/// #[storage_alias] +/// type SomeNMap = StorageNMap, NMapKey), Vec>; +/// +/// // Using pallet name as prefix. +/// // +/// // When the first generic argument is taking generic arguments it is expected to be a pallet. +/// // The prefix will then be the pallet name as configured in the runtime through +/// // `construct_runtime!`. +/// +/// # struct Pallet(std::marker::PhantomData<(T, I)>); +/// # impl frame_support::traits::PalletInfoAccess for Pallet { +/// # fn index() -> usize { 0 } +/// # fn name() -> &'static str { "pallet" } +/// # fn module_name() -> &'static str { "module" } +/// # fn crate_version() -> frame_support::traits::CrateVersion { unimplemented!() } +/// # } +/// +/// #[storage_alias] +/// type SomeValue = StorageValue, u64>; +/// +/// // Pallet with instance +/// +/// #[storage_alias] +/// type SomeValue2 = StorageValue, u64>; +/// /// # fn main() {} /// ``` -#[macro_export] -macro_rules! generate_storage_alias { - // without generic for $name. - ($pallet:ident, $name:ident => Map<($hasher:ty, $key:ty), $value:ty $(, $querytype:ty)?>) => { - $crate::paste::paste! { - $crate::generate_storage_alias!(@GENERATE_INSTANCE_STRUCT $pallet, $name); - type $name = $crate::storage::types::StorageMap< - [<$name Instance>], - $hasher, - $key, - $value, - $( $querytype )? - >; - } - }; - ( - $pallet:ident, - $name:ident - => DoubleMap<($hasher1:ty, $key1:ty), ($hasher2:ty, $key2:ty), $value:ty $(, $querytype:ty)?> - ) => { - $crate::paste::paste! { - $crate::generate_storage_alias!(@GENERATE_INSTANCE_STRUCT $pallet, $name); - type $name = $crate::storage::types::StorageDoubleMap< - [<$name Instance>], - $hasher1, - $key1, - $hasher2, - $key2, - $value, - $( $querytype )? - >; - } - }; - ( - $pallet:ident, - $name:ident - => NMap, $value:ty $(, $querytype:ty)?> - ) => { - $crate::paste::paste! { - $crate::generate_storage_alias!(@GENERATE_INSTANCE_STRUCT $pallet, $name); - type $name = $crate::storage::types::StorageNMap< - [<$name Instance>], - ( - $( $crate::storage::types::Key<$hasher, $key>, )+ - ), - $value, - $( $querytype )? - >; - } - }; - ($pallet:ident, $name:ident => Value<$value:ty $(, $querytype:ty)?>) => { - $crate::paste::paste! { - $crate::generate_storage_alias!(@GENERATE_INSTANCE_STRUCT $pallet, $name); - type $name = $crate::storage::types::StorageValue< - [<$name Instance>], - $value, - $( $querytype )? - >; - } - }; - // with generic for $name. - ( - $pallet:ident, - $name:ident<$t:ident : $bounds:tt> - => Map<($hasher:ty, $key:ty), $value:ty $(, $querytype:ty)?> - ) => { - $crate::paste::paste! { - $crate::generate_storage_alias!(@GENERATE_INSTANCE_STRUCT $pallet, $name); - #[allow(type_alias_bounds)] - type $name<$t : $bounds> = $crate::storage::types::StorageMap< - [<$name Instance>], - $hasher, - $key, - $value, - $( $querytype )? - >; - } - }; - ( - $pallet:ident, - $name:ident<$t:ident : $bounds:tt> - => DoubleMap<($hasher1:ty, $key1:ty), ($hasher2:ty, $key2:ty), $value:ty $(, $querytype:ty)?> - ) => { - $crate::paste::paste! { - $crate::generate_storage_alias!(@GENERATE_INSTANCE_STRUCT $pallet, $name); - #[allow(type_alias_bounds)] - type $name<$t : $bounds> = $crate::storage::types::StorageDoubleMap< - [<$name Instance>], - $hasher1, - $key1, - $hasher2, - $key2, - $value, - $( $querytype )? - >; - } - }; - ( - $pallet:ident, - $name:ident<$t:ident : $bounds:tt> - => NMap<$(($hasher:ty, $key:ty),)+ $value:ty $(, $querytype:ty)?> - ) => { - $crate::paste::paste! { - $crate::generate_storage_alias!(@GENERATE_INSTANCE_STRUCT $pallet, $name); - #[allow(type_alias_bounds)] - type $name<$t : $bounds> = $crate::storage::types::StorageNMap< - [<$name Instance>], - ( - $( $crate::storage::types::Key<$hasher, $key>, )+ - ), - $value, - $( $querytype )? - >; - } - }; - ($pallet:ident, $name:ident<$t:ident : $bounds:tt> => Value<$value:ty $(, $querytype:ty)?>) => { - $crate::paste::paste! { - $crate::generate_storage_alias!(@GENERATE_INSTANCE_STRUCT $pallet, $name); - #[allow(type_alias_bounds)] - type $name<$t : $bounds> = $crate::storage::types::StorageValue< - [<$name Instance>], - $value, - $( $querytype )? - >; - } - }; - // helper used in all arms. - (@GENERATE_INSTANCE_STRUCT $pallet:ident, $name:ident) => { - $crate::paste::paste! { - struct [<$name Instance>]; - impl $crate::traits::StorageInstance for [<$name Instance>] { - fn pallet_prefix() -> &'static str { stringify!($pallet) } - const STORAGE_PREFIX: &'static str = stringify!($name); - } - } - }; -} +pub use frame_support_procedural::storage_alias; /// Create new implementations of the [`Get`](crate::traits::Get) trait. /// @@ -995,16 +891,28 @@ pub mod tests { } #[test] - fn generate_storage_alias_works() { + fn storage_alias_works() { new_test_ext().execute_with(|| { - generate_storage_alias!( + #[crate::storage_alias] + type GenericData2 = StorageMap< Test, - GenericData2 => Map<(Blake2_128Concat, T::BlockNumber), T::BlockNumber> - ); + Blake2_128Concat, + ::BlockNumber, + ::BlockNumber, + >; assert_eq!(Module::::generic_data2(5), None); GenericData2::::insert(5, 5); assert_eq!(Module::::generic_data2(5), Some(5)); + + /// Some random docs that ensure that docs are accepted + #[crate::storage_alias] + pub type GenericData = StorageMap< + Test2, + Blake2_128Concat, + ::BlockNumber, + ::BlockNumber, + >; }); } diff --git a/frame/support/src/storage/bounded_btree_map.rs b/frame/support/src/storage/bounded_btree_map.rs index 0d589994bcc2c..390330457b9b9 100644 --- a/frame/support/src/storage/bounded_btree_map.rs +++ b/frame/support/src/storage/bounded_btree_map.rs @@ -348,12 +348,15 @@ pub mod test { use frame_support::traits::ConstU32; use sp_io::TestExternalities; - crate::generate_storage_alias! { Prefix, Foo => Value>> } - crate::generate_storage_alias! { Prefix, FooMap => Map<(Twox128, u32), BoundedBTreeMap>> } - crate::generate_storage_alias! { - Prefix, - FooDoubleMap => DoubleMap<(Twox128, u32), (Twox128, u32), BoundedBTreeMap>> - } + #[crate::storage_alias] + type Foo = StorageValue>>; + + #[crate::storage_alias] + type FooMap = StorageMap>>; + + #[crate::storage_alias] + type FooDoubleMap = + StorageDoubleMap>>; fn map_from_keys(keys: &[K]) -> BTreeMap where diff --git a/frame/support/src/storage/bounded_btree_set.rs b/frame/support/src/storage/bounded_btree_set.rs index f38952bf545d9..275bb07c6275c 100644 --- a/frame/support/src/storage/bounded_btree_set.rs +++ b/frame/support/src/storage/bounded_btree_set.rs @@ -322,12 +322,15 @@ pub mod test { use frame_support::traits::ConstU32; use sp_io::TestExternalities; - crate::generate_storage_alias! { Prefix, Foo => Value>> } - crate::generate_storage_alias! { Prefix, FooMap => Map<(Twox128, u32), BoundedBTreeSet>> } - crate::generate_storage_alias! { - Prefix, - FooDoubleMap => DoubleMap<(Twox128, u32), (Twox128, u32), BoundedBTreeSet>> - } + #[crate::storage_alias] + type Foo = StorageValue>>; + + #[crate::storage_alias] + type FooMap = StorageMap>>; + + #[crate::storage_alias] + type FooDoubleMap = + StorageDoubleMap>>; fn set_from_keys(keys: &[T]) -> BTreeSet where diff --git a/frame/support/src/storage/bounded_vec.rs b/frame/support/src/storage/bounded_vec.rs index e99ec6850b9f4..d3f9bfdd21d3b 100644 --- a/frame/support/src/storage/bounded_vec.rs +++ b/frame/support/src/storage/bounded_vec.rs @@ -672,12 +672,15 @@ pub mod test { use crate::{bounded_vec, traits::ConstU32, Twox128}; use sp_io::TestExternalities; - crate::generate_storage_alias! { Prefix, Foo => Value>> } - crate::generate_storage_alias! { Prefix, FooMap => Map<(Twox128, u32), BoundedVec>> } - crate::generate_storage_alias! { - Prefix, - FooDoubleMap => DoubleMap<(Twox128, u32), (Twox128, u32), BoundedVec>> - } + #[crate::storage_alias] + type Foo = StorageValue>>; + + #[crate::storage_alias] + type FooMap = StorageMap>>; + + #[crate::storage_alias] + type FooDoubleMap = + StorageDoubleMap>>; #[test] fn slide_works() { diff --git a/frame/support/src/storage/generator/double_map.rs b/frame/support/src/storage/generator/double_map.rs index 16fcaf940c62a..1c308bc351902 100644 --- a/frame/support/src/storage/generator/double_map.rs +++ b/frame/support/src/storage/generator/double_map.rs @@ -525,10 +525,8 @@ mod test_iterators { fn double_map_iter_from() { sp_io::TestExternalities::default().execute_with(|| { use crate::hash::Identity; - crate::generate_storage_alias!( - MyModule, - MyDoubleMap => DoubleMap<(Identity, u64), (Identity, u64), u64> - ); + #[crate::storage_alias] + type MyDoubleMap = StorageDoubleMap; MyDoubleMap::insert(1, 10, 100); MyDoubleMap::insert(1, 21, 201); diff --git a/frame/support/src/storage/generator/map.rs b/frame/support/src/storage/generator/map.rs index da48952bcba87..bd24e14e9e0d8 100644 --- a/frame/support/src/storage/generator/map.rs +++ b/frame/support/src/storage/generator/map.rs @@ -384,7 +384,8 @@ mod test_iterators { fn map_iter_from() { sp_io::TestExternalities::default().execute_with(|| { use crate::hash::Identity; - crate::generate_storage_alias!(MyModule, MyMap => Map<(Identity, u64), u64>); + #[crate::storage_alias] + type MyMap = StorageMap; MyMap::insert(1, 10); MyMap::insert(2, 20); diff --git a/frame/support/src/storage/generator/nmap.rs b/frame/support/src/storage/generator/nmap.rs index be085ca2d9db6..18c912cf294ef 100755 --- a/frame/support/src/storage/generator/nmap.rs +++ b/frame/support/src/storage/generator/nmap.rs @@ -475,10 +475,12 @@ mod test_iterators { fn n_map_iter_from() { sp_io::TestExternalities::default().execute_with(|| { use crate::{hash::Identity, storage::Key as NMapKey}; - crate::generate_storage_alias!( + #[crate::storage_alias] + type MyNMap = StorageNMap< MyModule, - MyNMap => NMap, u64> - ); + (NMapKey, NMapKey, NMapKey), + u64, + >; MyNMap::insert((1, 1, 1), 11); MyNMap::insert((1, 1, 2), 21); @@ -518,11 +520,15 @@ mod test_iterators { let key_hash = NMap::hashed_key_for((1, 2)); { - crate::generate_storage_alias!(Test, NMap => DoubleMap< - (crate::Blake2_128Concat, u16), - (crate::Twox64Concat, u32), - u64 - >); + #[crate::storage_alias] + type NMap = StorageDoubleMap< + Test, + crate::Blake2_128Concat, + u16, + crate::Twox64Concat, + u32, + u64, + >; let value = NMap::get(1, 2).unwrap(); assert_eq!(value, 50); diff --git a/frame/support/src/storage/mod.rs b/frame/support/src/storage/mod.rs index 115f179d803a7..7135ead850b6d 100644 --- a/frame/support/src/storage/mod.rs +++ b/frame/support/src/storage/mod.rs @@ -1545,10 +1545,8 @@ mod test { fn prefix_iterator_pagination_works() { TestExternalities::default().execute_with(|| { use crate::{hash::Identity, storage::generator::map::StorageMap}; - crate::generate_storage_alias! { - MyModule, - MyStorageMap => Map<(Identity, u64), u64> - } + #[crate::storage_alias] + type MyStorageMap = StorageMap; MyStorageMap::insert(1, 10); MyStorageMap::insert(2, 20); @@ -1663,12 +1661,13 @@ mod test { }); } - crate::generate_storage_alias! { Prefix, Foo => Value>> } - crate::generate_storage_alias! { Prefix, FooMap => Map<(Twox128, u32), BoundedVec>> } - crate::generate_storage_alias! { - Prefix, - FooDoubleMap => DoubleMap<(Twox128, u32), (Twox128, u32), BoundedVec>> - } + #[crate::storage_alias] + type Foo = StorageValue>>; + #[crate::storage_alias] + type FooMap = StorageMap>>; + #[crate::storage_alias] + type FooDoubleMap = + StorageDoubleMap>>; #[test] fn try_append_works() { diff --git a/frame/support/src/storage/types/nmap.rs b/frame/support/src/storage/types/nmap.rs index 16dc30ea03903..5faeb5d8cac28 100755 --- a/frame/support/src/storage/types/nmap.rs +++ b/frame/support/src/storage/types/nmap.rs @@ -544,7 +544,7 @@ mod test { use crate::{ hash::{StorageHasher as _, *}, metadata::{StorageEntryModifier, StorageHasher}, - storage::types::{Key, ValueQuery}, + storage::types::{Key, Key as NMapKey, ValueQuery}, }; use sp_io::{hashing::twox_128, TestExternalities}; @@ -589,10 +589,8 @@ mod test { assert_eq!(AValueQueryWithAnOnEmpty::get((3,)), 10); { - crate::generate_storage_alias!(test, Foo => NMap< - Key<(Blake2_128Concat, u16)>, - u32 - >); + #[crate::storage_alias] + type Foo = StorageNMap), u32>; assert_eq!(Foo::contains_key((3,)), true); assert_eq!(Foo::get((3,)), Some(10)); diff --git a/frame/support/src/storage/weak_bounded_vec.rs b/frame/support/src/storage/weak_bounded_vec.rs index 21cc487b49082..8d1d38374d8a7 100644 --- a/frame/support/src/storage/weak_bounded_vec.rs +++ b/frame/support/src/storage/weak_bounded_vec.rs @@ -321,12 +321,13 @@ pub mod test { use frame_support::traits::ConstU32; use sp_io::TestExternalities; - crate::generate_storage_alias! { Prefix, Foo => Value>> } - crate::generate_storage_alias! { Prefix, FooMap => Map<(Twox128, u32), WeakBoundedVec>> } - crate::generate_storage_alias! { - Prefix, - FooDoubleMap => DoubleMap<(Twox128, u32), (Twox128, u32), WeakBoundedVec>> - } + #[crate::storage_alias] + type Foo = StorageValue>>; + #[crate::storage_alias] + type FooMap = StorageMap>>; + #[crate::storage_alias] + type FooDoubleMap = + StorageDoubleMap>>; #[test] fn bound_returns_correct_value() { diff --git a/frame/support/test/tests/pallet.rs b/frame/support/test/tests/pallet.rs index ec97c1794c27b..792fd93299705 100644 --- a/frame/support/test/tests/pallet.rs +++ b/frame/support/test/tests/pallet.rs @@ -17,6 +17,7 @@ use frame_support::{ dispatch::{Parameter, UnfilteredDispatchable}, + pallet_prelude::ValueQuery, storage::unhashed, traits::{ ConstU32, GetCallName, GetStorageVersion, OnFinalize, OnGenesis, OnInitialize, @@ -1631,3 +1632,17 @@ fn assert_type_all_pallets_without_system_reversed_is_correct() { _a(t) } } + +#[test] +fn test_storage_alias() { + #[frame_support::storage_alias] + type Value + where + ::AccountId: From + SomeAssociation1, + = StorageValue, u32, ValueQuery>; + + TestExternalities::default().execute_with(|| { + pallet::Value::::put(10); + assert_eq!(10, Value::::get()); + }) +} diff --git a/frame/support/test/tests/pallet_instance.rs b/frame/support/test/tests/pallet_instance.rs index 118794e2fa20a..29074843f4bbb 100644 --- a/frame/support/test/tests/pallet_instance.rs +++ b/frame/support/test/tests/pallet_instance.rs @@ -17,6 +17,7 @@ use frame_support::{ dispatch::UnfilteredDispatchable, + pallet_prelude::ValueQuery, storage::unhashed, traits::{ConstU32, GetCallName, OnFinalize, OnGenesis, OnInitialize, OnRuntimeUpgrade}, weights::{DispatchClass, DispatchInfo, GetDispatchInfo, Pays}, @@ -821,3 +822,15 @@ fn test_pallet_info_access() { assert_eq!(::index(), 3); assert_eq!(::index(), 4); } + +#[test] +fn test_storage_alias() { + #[frame_support::storage_alias] + type Value, I: 'static> = + StorageValue, u32, ValueQuery>; + + TestExternalities::default().execute_with(|| { + pallet::Value::::put(10); + assert_eq!(10, Value::::get()); + }) +} diff --git a/frame/system/src/migrations/mod.rs b/frame/system/src/migrations/mod.rs index 872cf389d246c..f02af7a316fe1 100644 --- a/frame/system/src/migrations/mod.rs +++ b/frame/system/src/migrations/mod.rs @@ -17,6 +17,7 @@ //! Migrate the reference counting state. +use crate::{Config, Pallet}; use codec::{Decode, Encode, FullCodec}; use frame_support::{ pallet_prelude::ValueQuery, traits::PalletInfoAccess, weights::Weight, Blake2_128Concat, @@ -52,43 +53,24 @@ pub trait V2ToV3 { type AccountData: 'static + FullCodec; } -// ### Warning -// -// The call below is only valid because the name System is enforced -// at runtime construction level for the system pallet. -frame_support::generate_storage_alias!( - System, UpgradedToU32RefCount => Value< - bool, - ValueQuery - > -); - -// ### Warning -// -// The call below is only valid because the name System is enforced -// at runtime construction level for the system pallet. -frame_support::generate_storage_alias!( - System, UpgradedToTripleRefCount => Value< - bool, - ValueQuery - > -); - -// ### Warning -// -// The call below is only valid because the name System is enforced -// at runtime construction level for the system pallet. -frame_support::generate_storage_alias!( - System, Account => Map< - (Blake2_128Concat, T::AccountId), - AccountInfo - > -); +#[frame_support::storage_alias] +type UpgradedToU32RefCount = StorageValue, bool, ValueQuery>; + +#[frame_support::storage_alias] +type UpgradedToTripleRefCount = StorageValue, bool, ValueQuery>; + +#[frame_support::storage_alias] +type Account = StorageMap< + Pallet, + Blake2_128Concat, + ::AccountId, + AccountInfo<::Index, ::AccountData>, +>; /// Migrate from unique `u8` reference counting to triple `u32` reference counting. -pub fn migrate_from_single_u8_to_triple_ref_count() -> Weight { +pub fn migrate_from_single_u8_to_triple_ref_count() -> Weight { let mut translated: usize = 0; - >::translate::<(T::Index, u8, T::AccountData), _>(|_key, (nonce, rc, data)| { + >::translate::<(V::Index, u8, V::AccountData), _>(|_key, (nonce, rc, data)| { translated += 1; Some(AccountInfo { nonce, consumers: rc as RefCount, providers: 1, sufficients: 0, data }) }); @@ -97,15 +79,15 @@ pub fn migrate_from_single_u8_to_triple_ref_count() -> Weight { "Applied migration from single u8 to triple reference counting to {:?} elements.", translated ); - ::put(true); - ::put(true); + >::put(true); + >::put(true); Weight::max_value() } /// Migrate from unique `u32` reference counting to triple `u32` reference counting. -pub fn migrate_from_single_to_triple_ref_count() -> Weight { +pub fn migrate_from_single_to_triple_ref_count() -> Weight { let mut translated: usize = 0; - >::translate::<(T::Index, RefCount, T::AccountData), _>( + >::translate::<(V::Index, RefCount, V::AccountData), _>( |_key, (nonce, consumers, data)| { translated += 1; Some(AccountInfo { nonce, consumers, providers: 1, sufficients: 0, data }) @@ -116,14 +98,14 @@ pub fn migrate_from_single_to_triple_ref_count() -> Weight { "Applied migration from single to triple reference counting to {:?} elements.", translated ); - ::put(true); + >::put(true); Weight::max_value() } /// Migrate from dual `u32` reference counting to triple `u32` reference counting. -pub fn migrate_from_dual_to_triple_ref_count() -> Weight { +pub fn migrate_from_dual_to_triple_ref_count() -> Weight { let mut translated: usize = 0; - >::translate::<(T::Index, RefCount, RefCount, T::AccountData), _>( + >::translate::<(V::Index, RefCount, RefCount, V::AccountData), _>( |_key, (nonce, consumers, providers, data)| { translated += 1; Some(AccountInfo { nonce, consumers, providers, sufficients: 0, data }) @@ -134,6 +116,6 @@ pub fn migrate_from_dual_to_triple_ref_count() -> Weight { "Applied migration from dual to triple reference counting to {:?} elements.", translated ); - ::put(true); + >::put(true); Weight::max_value() } From 07ca213ace1fe2066f186228017910beaf1cd441 Mon Sep 17 00:00:00 2001 From: Qinxuan Chen Date: Wed, 18 May 2022 15:23:15 +0800 Subject: [PATCH 237/484] Prune some duplicated dependencies in the dep graph (#11433) Signed-off-by: koushiro --- Cargo.lock | 153 ++++---------------------- bin/node/bench/Cargo.toml | 2 +- bin/node/bench/src/tempdb.rs | 2 +- bin/node/bench/src/trie.rs | 4 +- client/authority-discovery/Cargo.toml | 2 +- client/db/Cargo.toml | 2 +- client/network-gossip/Cargo.toml | 2 +- client/network/Cargo.toml | 1 - client/network/sync/Cargo.toml | 2 +- client/offchain/Cargo.toml | 2 +- client/offchain/src/api/http.rs | 10 +- 11 files changed, 36 insertions(+), 146 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 14196e4f85364..ff14da370db9c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1069,12 +1069,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - [[package]] name = "core-foundation" version = "0.9.3" @@ -1444,15 +1438,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "ct-logs" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1a816186fa68d9e426e3cb4ae4dff1fcd8e4a2c34b781bf7a822574a0d0aac8" -dependencies = [ - "sct 0.6.0", -] - [[package]] name = "ctor" version = "0.1.19" @@ -1594,14 +1579,12 @@ dependencies = [ [[package]] name = "derive_more" -version = "0.99.16" +version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40eebddd2156ce1bb37b20bbe5151340a31828b1f2d22ba4141f3531710e38df" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ - "convert_case", "proc-macro2", "quote", - "rustc_version 0.3.3", "syn", ] @@ -1919,16 +1902,6 @@ dependencies = [ "termcolor", ] -[[package]] -name = "env_logger" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" -dependencies = [ - "log", - "regex", -] - [[package]] name = "env_logger" version = "0.9.0" @@ -2544,8 +2517,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01fe9932a224b72b45336d96040aa86386d674a31d0af27d800ea7bc8ca97fe" dependencies = [ "futures-io", - "rustls 0.20.2", - "webpki 0.22.0", + "rustls", + "webpki", ] [[package]] @@ -2977,19 +2950,17 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.22.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" +checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" dependencies = [ - "ct-logs", - "futures-util", + "http", "hyper", "log", - "rustls 0.19.1", - "rustls-native-certs 0.5.0", + "rustls", + "rustls-native-certs", "tokio", - "tokio-rustls 0.22.0", - "webpki 0.21.4", + "tokio-rustls", ] [[package]] @@ -3190,11 +3161,11 @@ dependencies = [ "jsonrpsee-core", "jsonrpsee-types", "pin-project 1.0.10", - "rustls-native-certs 0.6.1", + "rustls-native-certs", "soketto", "thiserror", "tokio", - "tokio-rustls 0.23.2", + "tokio-rustls", "tokio-util 0.7.1", "tracing", "webpki-roots", @@ -7098,8 +7069,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" dependencies = [ - "env_logger 0.8.4", - "log", "rand 0.8.4", ] @@ -7663,15 +7632,6 @@ dependencies = [ "semver 0.9.0", ] -[[package]] -name = "rustc_version" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" -dependencies = [ - "semver 0.11.0", -] - [[package]] name = "rustc_version" version = "0.4.0" @@ -7695,19 +7655,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "rustls" -version = "0.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" -dependencies = [ - "base64", - "log", - "ring", - "sct 0.6.0", - "webpki 0.21.4", -] - [[package]] name = "rustls" version = "0.20.2" @@ -7716,20 +7663,8 @@ checksum = "d37e5e2290f3e040b594b1a9e04377c2c671f1a1cfd9bfdef82106ac1c113f84" dependencies = [ "log", "ring", - "sct 0.7.0", - "webpki 0.22.0", -] - -[[package]] -name = "rustls-native-certs" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a07b7c1885bd8ed3831c289b7870b13ef46fe0e856d288c30d9cc17d75a2092" -dependencies = [ - "openssl-probe", - "rustls 0.19.1", - "schannel", - "security-framework", + "sct", + "webpki", ] [[package]] @@ -8474,7 +8409,6 @@ dependencies = [ "pin-project 1.0.10", "prost 0.10.3", "prost-build", - "quickcheck", "rand 0.7.3", "sc-block-builder", "sc-client-api", @@ -9088,16 +9022,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" -[[package]] -name = "sct" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "sct" version = "0.7.0" @@ -9183,7 +9107,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a3186ec9e65071a2095434b1f5bb24838d4e8e130f584c790f6033c79943537" dependencies = [ - "semver-parser 0.7.0", + "semver-parser", ] [[package]] @@ -9192,16 +9116,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" dependencies = [ - "semver-parser 0.7.0", -] - -[[package]] -name = "semver" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" -dependencies = [ - "semver-parser 0.10.2", + "semver-parser", ] [[package]] @@ -9219,15 +9134,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -[[package]] -name = "semver-parser" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" -dependencies = [ - "pest", -] - [[package]] name = "serde" version = "1.0.136" @@ -10910,26 +10816,15 @@ dependencies = [ "syn", ] -[[package]] -name = "tokio-rustls" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" -dependencies = [ - "rustls 0.19.1", - "tokio", - "webpki 0.21.4", -] - [[package]] name = "tokio-rustls" version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a27d5f2b839802bd8267fa19b0530f5a08b9c08cd417976be2a65d130fe1c11b" dependencies = [ - "rustls 0.20.2", + "rustls", "tokio", - "webpki 0.22.0", + "webpki", ] [[package]] @@ -11994,16 +11889,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webpki" -version = "0.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "webpki" version = "0.22.0" @@ -12020,7 +11905,7 @@ version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "552ceb903e957524388c4d3475725ff2c8b7960922063af6ce53c9a43da07449" dependencies = [ - "webpki 0.22.0", + "webpki", ] [[package]] diff --git a/bin/node/bench/Cargo.toml b/bin/node/bench/Cargo.toml index 5013383af1cea..90c325b8e5b32 100644 --- a/bin/node/bench/Cargo.toml +++ b/bin/node/bench/Cargo.toml @@ -19,7 +19,7 @@ sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } sp-state-machine = { version = "0.12.0", path = "../../../primitives/state-machine" } serde = "1.0.136" serde_json = "1.0.79" -derive_more = "0.99.16" +derive_more = { version = "0.99.17", default-features = false, features = ["display"] } kvdb = "0.11.0" kvdb-rocksdb = "0.15.1" sp-trie = { version = "6.0.0", path = "../../../primitives/trie" } diff --git a/bin/node/bench/src/tempdb.rs b/bin/node/bench/src/tempdb.rs index 1e5f3dfbce148..22c5980fd6524 100644 --- a/bin/node/bench/src/tempdb.rs +++ b/bin/node/bench/src/tempdb.rs @@ -20,7 +20,7 @@ use kvdb::{DBTransaction, KeyValueDB}; use kvdb_rocksdb::{Database, DatabaseConfig}; use std::{io, path::PathBuf, sync::Arc}; -#[derive(Debug, Clone, Copy, derive_more::Display)] +#[derive(Clone, Copy, Debug)] pub enum DatabaseType { RocksDb, ParityDb, diff --git a/bin/node/bench/src/trie.rs b/bin/node/bench/src/trie.rs index 0539c9ce11462..d508dc712e1c3 100644 --- a/bin/node/bench/src/trie.rs +++ b/bin/node/bench/src/trie.rs @@ -155,7 +155,7 @@ impl core::BenchmarkDescription for TrieReadBenchmarkDescription { fn name(&self) -> Cow<'static, str> { format!( - "Trie read benchmark({} database ({} keys), db_type: {})", + "Trie read benchmark({:?} database ({} keys), db_type: {:?})", self.database_size, pretty_print(self.database_size.keys()), self.database_type, @@ -260,7 +260,7 @@ impl core::BenchmarkDescription for TrieWriteBenchmarkDescription { fn name(&self) -> Cow<'static, str> { format!( - "Trie write benchmark({} database ({} keys), db_type = {})", + "Trie write benchmark({:?} database ({} keys), db_type = {:?})", self.database_size, pretty_print(self.database_size.keys()), self.database_type, diff --git a/client/authority-discovery/Cargo.toml b/client/authority-discovery/Cargo.toml index 96c07b5dae278..136c2606a384e 100644 --- a/client/authority-discovery/Cargo.toml +++ b/client/authority-discovery/Cargo.toml @@ -38,6 +38,6 @@ sp-keystore = { version = "0.12.0", path = "../../primitives/keystore" } sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } [dev-dependencies] -quickcheck = "1.0.3" +quickcheck = { version = "1.0.3", default-features = false } sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } diff --git a/client/db/Cargo.toml b/client/db/Cargo.toml index babbb0b91c8fb..e1472bcbda01a 100644 --- a/client/db/Cargo.toml +++ b/client/db/Cargo.toml @@ -36,7 +36,7 @@ sp-trie = { version = "6.0.0", path = "../../primitives/trie" } [dev-dependencies] kvdb-rocksdb = "0.15.1" -quickcheck = "1.0.3" +quickcheck = { version = "1.0.3", default-features = false } tempfile = "3" sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } diff --git a/client/network-gossip/Cargo.toml b/client/network-gossip/Cargo.toml index 7ba45f6ee9b87..641574db288d7 100644 --- a/client/network-gossip/Cargo.toml +++ b/client/network-gossip/Cargo.toml @@ -27,5 +27,5 @@ sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } [dev-dependencies] async-std = "1.11.0" -quickcheck = "1.0.3" +quickcheck = { version = "1.0.3", default-features = false } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } diff --git a/client/network/Cargo.toml b/client/network/Cargo.toml index f222c39673516..89a36bc483e6c 100644 --- a/client/network/Cargo.toml +++ b/client/network/Cargo.toml @@ -65,7 +65,6 @@ sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } [dev-dependencies] assert_matches = "1.3" async-std = "1.11.0" -quickcheck = "1.0.3" rand = "0.7.2" tempfile = "3.1.0" sp-test-primitives = { version = "2.0.0", path = "../../primitives/test-primitives" } diff --git a/client/network/sync/Cargo.toml b/client/network/sync/Cargo.toml index c557c9bf42b92..f11cc753203ef 100644 --- a/client/network/sync/Cargo.toml +++ b/client/network/sync/Cargo.toml @@ -42,7 +42,7 @@ sp-finality-grandpa = { version = "4.0.0-dev", path = "../../../primitives/final sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } [dev-dependencies] -quickcheck = "1.0.3" +quickcheck = { version = "1.0.3", default-features = false } sc-block-builder = { version = "0.10.0-dev", path = "../../block-builder" } sp-test-primitives = { version = "2.0.0", path = "../../../primitives/test-primitives" } sp-tracing = { version = "5.0.0", path = "../../../primitives/tracing" } diff --git a/client/offchain/Cargo.toml b/client/offchain/Cargo.toml index d4ae0a2558b7f..8da2d4be3adde 100644 --- a/client/offchain/Cargo.toml +++ b/client/offchain/Cargo.toml @@ -20,7 +20,7 @@ futures = "0.3.21" futures-timer = "3.0.2" hex = "0.4" hyper = { version = "0.14.16", features = ["stream", "http2"] } -hyper-rustls = "0.22.1" +hyper-rustls = { version = "0.23.0", features = ["http2"] } num_cpus = "1.13" once_cell = "1.8" parking_lot = "0.12.0" diff --git a/client/offchain/src/api/http.rs b/client/offchain/src/api/http.rs index f4fa7e0800b2d..4c97e5a47058d 100644 --- a/client/offchain/src/api/http.rs +++ b/client/offchain/src/api/http.rs @@ -32,7 +32,7 @@ use bytes::buf::{Buf, Reader}; use fnv::FnvHashMap; use futures::{channel::mpsc, future, prelude::*}; use hyper::{client, Body, Client as HyperClient}; -use hyper_rustls::HttpsConnector; +use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder}; use once_cell::sync::Lazy; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; use sp_core::offchain::{HttpError, HttpRequestId, HttpRequestStatus, Timestamp}; @@ -53,7 +53,13 @@ pub struct SharedClient(Arc Self { Self(Arc::new(Lazy::new(|| { - HyperClient::builder().build(HttpsConnector::with_native_roots()) + let connector = HttpsConnectorBuilder::new() + .with_native_roots() + .https_or_http() + .enable_http1() + .enable_http2() + .build(); + HyperClient::builder().build(connector) }))) } } From d593ef32c3833683861c7e545875f5d40f9139d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Wed, 18 May 2022 09:40:53 +0200 Subject: [PATCH 238/484] contracts: Get rid of `#[pallet::without_storage_info]` (#11414) * Implement `MaxEncodeLen` for pallet-contracts storage * Remove redundant debug println * Move code len check to PrefabWasmModule::from_code --- bin/node/runtime/src/lib.rs | 2 ++ frame/contracts/src/benchmarking/mod.rs | 8 ++--- frame/contracts/src/lib.rs | 43 +++++++++++++++---------- frame/contracts/src/schedule.rs | 8 +---- frame/contracts/src/storage.rs | 29 +++++++++-------- frame/contracts/src/tests.rs | 2 ++ frame/contracts/src/wasm/code_cache.rs | 10 ++++-- frame/contracts/src/wasm/mod.rs | 31 ++++++++++++++---- frame/contracts/src/wasm/prepare.rs | 33 ++++++++++--------- 9 files changed, 99 insertions(+), 67 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 40316cee9b2b7..de6f8eeb9f1c0 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1118,6 +1118,8 @@ impl pallet_contracts::Config for Runtime { type Schedule = Schedule; type AddressGenerator = pallet_contracts::DefaultAddressGenerator; type ContractAccessWeight = pallet_contracts::DefaultContractAccessWeight; + type MaxCodeLen = ConstU32<{ 128 * 1024 }>; + type RelaxedMaxCodeLen = ConstU32<{ 256 * 1024 }>; } impl pallet_sudo::Config for Runtime { diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs index 194f97f8c878d..01e62e07e1ce0 100644 --- a/frame/contracts/src/benchmarking/mod.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -231,7 +231,7 @@ benchmarks! { // first time after a new schedule was deployed: For every new schedule a contract needs // to re-run the instrumentation once. reinstrument { - let c in 0 .. T::Schedule::get().limits.code_len; + let c in 0 .. Perbill::from_percent(49).mul_ceil(T::MaxCodeLen::get()); let WasmModule { code, hash, .. } = WasmModule::::sized(c, Location::Call); Contracts::::store_code_raw(code, whitelisted_caller())?; let schedule = T::Schedule::get(); @@ -247,7 +247,7 @@ benchmarks! { // which is in the wasm module but not executed on `call`. // The results are supposed to be used as `call_with_code_kb(c) - call_with_code_kb(0)`. call_with_code_per_byte { - let c in 0 .. T::Schedule::get().limits.code_len; + let c in 0 .. T::MaxCodeLen::get(); let instance = Contract::::with_caller( whitelisted_caller(), WasmModule::sized(c, Location::Deploy), vec![], )?; @@ -271,7 +271,7 @@ benchmarks! { // We cannot let `c` grow to the maximum code size because the code is not allowed // to be larger than the maximum size **after instrumentation**. instantiate_with_code { - let c in 0 .. Perbill::from_percent(49).mul_ceil(T::Schedule::get().limits.code_len); + let c in 0 .. Perbill::from_percent(49).mul_ceil(T::MaxCodeLen::get()); let s in 0 .. code::max_pages::() * 64 * 1024; let salt = vec![42u8; s as usize]; let value = T::Currency::minimum_balance(); @@ -360,7 +360,7 @@ benchmarks! { // We cannot let `c` grow to the maximum code size because the code is not allowed // to be larger than the maximum size **after instrumentation**. upload_code { - let c in 0 .. Perbill::from_percent(50).mul_ceil(T::Schedule::get().limits.code_len); + let c in 0 .. Perbill::from_percent(49).mul_ceil(T::MaxCodeLen::get()); let caller = whitelisted_caller(); T::Currency::make_free_balance_be(&caller, caller_funding::()); let WasmModule { code, hash, .. } = WasmModule::::sized(c, Location::Call); diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index ca639d7781ac2..ffac3e222bbce 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -114,8 +114,9 @@ use codec::{Encode, HasCompact}; use frame_support::{ dispatch::Dispatchable, ensure, - traits::{Contains, Currency, Get, Randomness, ReservableCurrency, Time}, + traits::{ConstU32, Contains, Currency, Get, Randomness, ReservableCurrency, Time}, weights::{DispatchClass, GetDispatchInfo, Pays, PostDispatchInfo, Weight}, + BoundedVec, }; use frame_system::{limits::BlockWeights, Pallet as System}; use pallet_contracts_primitives::{ @@ -129,9 +130,11 @@ use sp_runtime::traits::{Convert, Hash, Saturating, StaticLookup}; use sp_std::{fmt::Debug, marker::PhantomData, prelude::*}; type CodeHash = ::Hash; -type TrieId = Vec; +type TrieId = BoundedVec>; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; +type CodeVec = BoundedVec::MaxCodeLen>; +type RelaxedCodeVec = BoundedVec::RelaxedMaxCodeLen>; /// Used as a sentinel value when reading and writing contract memory. /// @@ -224,7 +227,6 @@ pub mod pallet { #[pallet::pallet] #[pallet::storage_version(STORAGE_VERSION)] - #[pallet::without_storage_info] pub struct Pallet(PhantomData); #[pallet::config] @@ -356,6 +358,20 @@ pub mod pallet { /// The address generator used to generate the addresses of contracts. type AddressGenerator: AddressGenerator; + + /// The maximum length of a contract code in bytes. This limit applies to the instrumented + /// version of the code. Therefore `instantiate_with_code` can fail even when supplying + /// a wasm binary below this maximum size. + type MaxCodeLen: Get; + + /// The maximum length of a contract code after reinstrumentation. + /// + /// When uploading a new contract the size defined by [`Self::MaxCodeLen`] is used for both + /// the pristine **and** the instrumented version. When a existing contract needs to be + /// reinstrumented after a runtime upgrade we apply this bound. The reason is that if the + /// new instrumentation increases the size beyond the limit it would make that contract + /// inaccessible until rectified by another runtime upgrade. + type RelaxedMaxCodeLen: Get; } #[pallet::hooks] @@ -698,7 +714,7 @@ pub mod pallet { /// A mapping from an original code hash to the original code, untouched by instrumentation. #[pallet::storage] - pub(crate) type PristineCode = StorageMap<_, Identity, CodeHash, Vec>; + pub(crate) type PristineCode = StorageMap<_, Identity, CodeHash, CodeVec>; /// A mapping between an original code hash and instrumented wasm code, ready for execution. #[pallet::storage] @@ -746,7 +762,8 @@ pub mod pallet { /// Child trie deletion is a heavy operation depending on the amount of storage items /// stored in said trie. Therefore this operation is performed lazily in `on_initialize`. #[pallet::storage] - pub(crate) type DeletionQueue = StorageValue<_, Vec, ValueQuery>; + pub(crate) type DeletionQueue = + StorageValue<_, BoundedVec, ValueQuery>; } /// Return type of the private [`Pallet::internal_call`] function. @@ -864,8 +881,8 @@ where storage_deposit_limit: Option>, ) -> CodeUploadResult, BalanceOf> { let schedule = T::Schedule::get(); - let module = PrefabWasmModule::from_code(code, &schedule, origin) - .map_err(|_| >::CodeRejected)?; + let module = + PrefabWasmModule::from_code(code, &schedule, origin).map_err(|(err, _)| err)?; let deposit = module.open_deposit(); if let Some(storage_deposit_limit) = storage_deposit_limit { ensure!(storage_deposit_limit >= deposit, >::StorageDepositLimitExhausted); @@ -971,19 +988,11 @@ where let schedule = T::Schedule::get(); let (extra_deposit, executable) = match code { Code::Upload(Bytes(binary)) => { - ensure!( - binary.len() as u32 <= schedule.limits.code_len, - >::CodeTooLarge - ); let executable = PrefabWasmModule::from_code(binary, &schedule, origin.clone()) - .map_err(|msg| { + .map_err(|(err, msg)| { debug_message.as_mut().map(|buffer| buffer.extend(msg.as_bytes())); - >::CodeRejected + err })?; - ensure!( - executable.code_len() <= schedule.limits.code_len, - >::CodeTooLarge - ); // The open deposit will be charged during execution when the // uploaded module does not already exist. This deposit is not part of the // storage meter because it is not transfered to the contract but diff --git a/frame/contracts/src/schedule.rs b/frame/contracts/src/schedule.rs index 9e9f213fabb76..907ce9e088648 100644 --- a/frame/contracts/src/schedule.rs +++ b/frame/contracts/src/schedule.rs @@ -33,7 +33,7 @@ use wasm_instrument::{gas_metering, parity_wasm::elements}; /// How many API calls are executed in a single batch. The reason for increasing the amount /// of API calls in batches (per benchmark component increase) is so that the linear regression /// has an easier time determining the contribution of that component. -pub const API_BENCHMARK_BATCH_SIZE: u32 = 100; +pub const API_BENCHMARK_BATCH_SIZE: u32 = 80; /// How many instructions are executed in a single batch. The reasoning is the same /// as for `API_BENCHMARK_BATCH_SIZE`. @@ -147,11 +147,6 @@ pub struct Limits { /// The maximum size of a storage value and event payload in bytes. pub payload_len: u32, - - /// The maximum length of a contract code in bytes. This limit applies to the instrumented - /// version of the code. Therefore `instantiate_with_code` can fail even when supplying - /// a wasm binary below this maximum size. - pub code_len: u32, } impl Limits { @@ -522,7 +517,6 @@ impl Default for Limits { subject_len: 32, call_depth: 32, payload_len: 16 * 1024, - code_len: 128 * 1024, } } } diff --git a/frame/contracts/src/storage.rs b/frame/contracts/src/storage.rs index 2bdacc15cb11f..a9a78f12581d8 100644 --- a/frame/contracts/src/storage.rs +++ b/frame/contracts/src/storage.rs @@ -24,11 +24,10 @@ use crate::{ weights::WeightInfo, BalanceOf, CodeHash, Config, ContractInfoOf, DeletionQueue, Error, TrieId, SENTINEL, }; -use codec::{Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ dispatch::{DispatchError, DispatchResult}, storage::child::{self, ChildInfo, KillStorageResult}, - traits::Get, weights::Weight, }; use scale_info::TypeInfo; @@ -44,7 +43,7 @@ pub type ContractInfo = RawContractInfo, BalanceOf>; /// Information for managing an account and its sub trie abstraction. /// This is the required info to cache for an account. -#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct RawContractInfo { /// Unique ID for the subtree encoded as a bytes vector. pub trie_id: TrieId, @@ -67,7 +66,7 @@ fn child_trie_info(trie_id: &[u8]) -> ChildInfo { ChildInfo::new_default(trie_id) } -#[derive(Encode, Decode, TypeInfo)] +#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)] pub struct DeletedContract { pub(crate) trie_id: TrieId, } @@ -217,12 +216,8 @@ where /// /// You must make sure that the contract is also removed when queuing the trie for deletion. pub fn queue_trie_for_deletion(contract: &ContractInfo) -> DispatchResult { - if >::decode_len().unwrap_or(0) >= T::DeletionQueueDepth::get() as usize { - Err(Error::::DeletionQueueFull.into()) - } else { - >::append(DeletedContract { trie_id: contract.trie_id.clone() }); - Ok(()) - } + >::try_append(DeletedContract { trie_id: contract.trie_id.clone() }) + .map_err(|_| >::DeletionQueueFull.into()) } /// Calculates the weight that is necessary to remove one key from the trie and how many @@ -293,7 +288,11 @@ where /// Generates a unique trie id by returning `hash(account_id ++ nonce)`. pub fn generate_trie_id(account_id: &AccountIdOf, nonce: u64) -> TrieId { let buf: Vec<_> = account_id.as_ref().iter().chain(&nonce.to_le_bytes()).cloned().collect(); - T::Hashing::hash(&buf).as_ref().into() + T::Hashing::hash(&buf) + .as_ref() + .to_vec() + .try_into() + .expect("Runtime uses a reasonable hash size. Hence sizeof(T::Hash) <= 128; qed") } /// Returns the code hash of the contract specified by `account` ID. @@ -305,9 +304,11 @@ where /// Fill up the queue in order to exercise the limits during testing. #[cfg(test)] pub fn fill_queue_with_dummies() { - let queue: Vec<_> = (0..T::DeletionQueueDepth::get()) - .map(|_| DeletedContract { trie_id: vec![] }) + use frame_support::{traits::Get, BoundedVec}; + let queue: Vec = (0..T::DeletionQueueDepth::get()) + .map(|_| DeletedContract { trie_id: TrieId::default() }) .collect(); - >::put(queue); + let bounded: BoundedVec<_, _> = queue.try_into().unwrap(); + >::put(bounded); } } diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index 407e1e999dde1..e52ce5ca0e156 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -288,6 +288,8 @@ impl Config for Test { type DepositPerItem = DepositPerItem; type AddressGenerator = DefaultAddressGenerator; type ContractAccessWeight = DefaultContractAccessWeight; + type MaxCodeLen = ConstU32<{ 128 * 1024 }>; + type RelaxedMaxCodeLen = ConstU32<{ 256 * 1024 }>; } pub const ALICE: AccountId32 = AccountId32::new([1u8; 32]); diff --git a/frame/contracts/src/wasm/code_cache.rs b/frame/contracts/src/wasm/code_cache.rs index b194c283e90cd..10de436bfb155 100644 --- a/frame/contracts/src/wasm/code_cache.rs +++ b/frame/contracts/src/wasm/code_cache.rs @@ -163,7 +163,8 @@ pub fn load( where T::AccountId: UncheckedFrom + AsRef<[u8]>, { - let charged = gas_meter.charge(CodeToken::Load(schedule.limits.code_len))?; + let max_code_len = T::MaxCodeLen::get(); + let charged = gas_meter.charge(CodeToken::Load(max_code_len))?; let mut prefab_module = >::get(code_hash).ok_or(Error::::CodeNotFound)?; gas_meter.adjust_gas(charged, CodeToken::Load(prefab_module.code.len() as u32)); @@ -172,7 +173,7 @@ where if prefab_module.instruction_weights_version < schedule.instruction_weights.version { // The instruction weights have changed. // We need to re-instrument the code with the new instruction weights. - let charged = gas_meter.charge(CodeToken::Reinstrument(schedule.limits.code_len))?; + let charged = gas_meter.charge(CodeToken::Reinstrument(max_code_len))?; let code_size = reinstrument(&mut prefab_module, schedule)?; gas_meter.adjust_gas(charged, CodeToken::Reinstrument(code_size)); } @@ -190,7 +191,10 @@ pub fn reinstrument( let original_code = >::get(&prefab_module.code_hash).ok_or(Error::::CodeNotFound)?; let original_code_len = original_code.len(); - prefab_module.code = prepare::reinstrument_contract::(original_code, schedule)?; + prefab_module.code = prepare::reinstrument_contract::(&original_code, schedule) + .map_err(|_| >::CodeRejected)? + .try_into() + .map_err(|_| >::CodeTooLarge)?; prefab_module.instruction_weights_version = schedule.instruction_weights.version; >::insert(&prefab_module.code_hash, &*prefab_module); Ok(original_code_len as u32) diff --git a/frame/contracts/src/wasm/mod.rs b/frame/contracts/src/wasm/mod.rs index d710cad199f93..8764bf3690b82 100644 --- a/frame/contracts/src/wasm/mod.rs +++ b/frame/contracts/src/wasm/mod.rs @@ -31,10 +31,15 @@ use crate::{ exec::{ExecResult, Executable, ExportedFunction, Ext}, gas::GasMeter, wasm::env_def::FunctionImplProvider, - AccountIdOf, BalanceOf, CodeHash, CodeStorage, Config, Schedule, + AccountIdOf, BalanceOf, CodeHash, CodeStorage, CodeVec, Config, Error, RelaxedCodeVec, + Schedule, }; use codec::{Decode, Encode, MaxEncodedLen}; -use frame_support::dispatch::{DispatchError, DispatchResult}; +use frame_support::{ + dispatch::{DispatchError, DispatchResult}, + ensure, + traits::Get, +}; use sp_core::crypto::UncheckedFrom; use sp_sandbox::{SandboxEnvironmentBuilder, SandboxInstance, SandboxMemory}; use sp_std::prelude::*; @@ -50,7 +55,8 @@ pub use tests::MockExt; /// `instruction_weights_version` and `code` change when a contract with an outdated instrumentation /// is called. Therefore one must be careful when holding any in-memory representation of this /// type while calling into a contract as those fields can get out of date. -#[derive(Clone, Encode, Decode, scale_info::TypeInfo)] +#[derive(Clone, Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)] +#[codec(mel_bound())] #[scale_info(skip_type_params(T))] pub struct PrefabWasmModule { /// Version of the instruction weights with which the code was instrumented. @@ -63,14 +69,14 @@ pub struct PrefabWasmModule { #[codec(compact)] maximum: u32, /// Code instrumented with the latest schedule. - code: Vec, + code: RelaxedCodeVec, /// The uninstrumented, pristine version of the code. /// /// It is not stored because the pristine code has its own storage item. The value /// is only `Some` when this module was created from an `original_code` and `None` if /// it was loaded from storage. #[codec(skip)] - original_code: Option>, + original_code: Option>, /// The code hash of the stored code which is defined as the hash over the `original_code`. /// /// As the map key there is no need to store the hash in the value, too. It is set manually @@ -122,8 +128,19 @@ where original_code: Vec, schedule: &Schedule, owner: AccountIdOf, - ) -> Result { - prepare::prepare_contract(original_code, schedule, owner) + ) -> Result { + let module = prepare::prepare_contract( + original_code.try_into().map_err(|_| (>::CodeTooLarge.into(), ""))?, + schedule, + owner, + )?; + // When instrumenting a new code we apply a stricter limit than enforced by the + // `RelaxedCodeVec` in order to leave some headroom for reinstrumentation. + ensure!( + module.code.len() as u32 <= T::MaxCodeLen::get(), + (>::CodeTooLarge.into(), ""), + ); + Ok(module) } /// Store the code without instantiating it. diff --git a/frame/contracts/src/wasm/prepare.rs b/frame/contracts/src/wasm/prepare.rs index 6e9babe1264ed..f6fff20de6b1a 100644 --- a/frame/contracts/src/wasm/prepare.rs +++ b/frame/contracts/src/wasm/prepare.rs @@ -23,10 +23,10 @@ use crate::{ chain_extension::ChainExtension, storage::meter::Diff, wasm::{env_def::ImportSatisfyCheck, OwnerInfo, PrefabWasmModule}, - AccountIdOf, Config, Schedule, + AccountIdOf, CodeVec, Config, Error, Schedule, }; use codec::{Encode, MaxEncodedLen}; -use sp_runtime::traits::Hash; +use sp_runtime::{traits::Hash, DispatchError}; use sp_std::prelude::*; use wasm_instrument::parity_wasm::elements::{ self, External, Internal, MemoryType, Type, ValueType, @@ -401,19 +401,19 @@ fn check_and_instrument( } fn do_preparation( - original_code: Vec, + original_code: CodeVec, schedule: &Schedule, owner: AccountIdOf, -) -> Result, &'static str> { - let (code, (initial, maximum)) = - check_and_instrument::(original_code.as_ref(), schedule)?; +) -> Result, (DispatchError, &'static str)> { + let (code, (initial, maximum)) = check_and_instrument::(original_code.as_ref(), schedule) + .map_err(|msg| (>::CodeRejected.into(), msg))?; let original_code_len = original_code.len(); let mut module = PrefabWasmModule { instruction_weights_version: schedule.instruction_weights.version, initial, maximum, - code, + code: code.try_into().map_err(|_| (>::CodeTooLarge.into(), ""))?, code_hash: T::Hashing::hash(&original_code), original_code: Some(original_code), owner_info: None, @@ -446,10 +446,10 @@ fn do_preparation( /// /// The preprocessing includes injecting code for gas metering and metering the height of stack. pub fn prepare_contract( - original_code: Vec, + original_code: CodeVec, schedule: &Schedule, owner: AccountIdOf, -) -> Result, &'static str> { +) -> Result, (DispatchError, &'static str)> { do_preparation::(original_code, schedule, owner) } @@ -459,10 +459,10 @@ pub fn prepare_contract( /// /// Use this when an existing contract should be re-instrumented with a newer schedule version. pub fn reinstrument_contract( - original_code: Vec, + original_code: &[u8], schedule: &Schedule, ) -> Result, &'static str> { - Ok(check_and_instrument::(&original_code, schedule)?.0) + Ok(check_and_instrument::(original_code, schedule)?.0) } /// Alternate (possibly unsafe) preparation functions used only for benchmarking. @@ -493,9 +493,12 @@ pub mod benchmarking { instruction_weights_version: schedule.instruction_weights.version, initial: memory_limits.0, maximum: memory_limits.1, - code: contract_module.into_wasm_code()?, code_hash: T::Hashing::hash(&original_code), - original_code: Some(original_code), + original_code: Some(original_code.try_into().map_err(|_| "Original code too large")?), + code: contract_module + .into_wasm_code()? + .try_into() + .map_err(|_| "Instrumented code too large")?, owner_info: Some(OwnerInfo { owner, // this is a helper function for benchmarking which skips deposit collection @@ -546,7 +549,7 @@ mod tests { ($name:ident, $wat:expr, $($expected:tt)*) => { #[test] fn $name() { - let wasm = wat::parse_str($wat).unwrap(); + let wasm = wat::parse_str($wat).unwrap().try_into().unwrap(); let schedule = Schedule { limits: Limits { globals: 3, @@ -559,7 +562,7 @@ mod tests { .. Default::default() }; let r = do_preparation::(wasm, &schedule, ALICE); - assert_matches::assert_matches!(r, $($expected)*); + assert_matches::assert_matches!(r.map_err(|(_, msg)| msg), $($expected)*); } }; } From 85d8e8095b3a394e27ffcab8e82484a1e92a2844 Mon Sep 17 00:00:00 2001 From: Nathan Whitaker <17734409+nathanwhit@users.noreply.github.com> Date: Wed, 18 May 2022 03:42:53 -0400 Subject: [PATCH 239/484] Return a successful response on repeated small block request (#11429) --- client/network/sync/src/block_request_handler.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/network/sync/src/block_request_handler.rs b/client/network/sync/src/block_request_handler.rs index 78d6a1a7d1680..4cd69d1fac4f5 100644 --- a/client/network/sync/src/block_request_handler.rs +++ b/client/network/sync/src/block_request_handler.rs @@ -204,15 +204,15 @@ where let mut reputation_change = None; + let small_request = attributes + .difference(BlockAttributes::HEADER | BlockAttributes::JUSTIFICATION) + .is_empty(); + match self.seen_requests.get_mut(&key) { Some(SeenRequestsValue::First) => {}, Some(SeenRequestsValue::Fulfilled(ref mut requests)) => { *requests = requests.saturating_add(1); - let small_request = attributes - .difference(BlockAttributes::HEADER | BlockAttributes::JUSTIFICATION) - .is_empty(); - if *requests > MAX_NUMBER_OF_SAME_REQUESTS_PER_PEER { reputation_change = Some(if small_request { rep::SAME_SMALL_REQUEST @@ -237,7 +237,7 @@ where attributes, ); - let result = if reputation_change.is_none() { + let result = if reputation_change.is_none() || small_request { let block_response = self.get_block_response( attributes, from_block_id, From 1ab9bb6270e52bc53c91b24ad596187c5b3b40cf Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Wed, 18 May 2022 13:14:25 +0200 Subject: [PATCH 240/484] [ci] Adjust job order in pipeline test stage with Gitlab DAG (#11442) * [Do Not Merge] Test gitlab DAG in pipeline * add jobs for pipeline cancel * add check-tracing to cancel-pipeline --- .gitlab-ci.yml | 26 ++++++++++++++++++++++++++ scripts/ci/gitlab/pipeline/test.yml | 28 ++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b0af4cda995ad..87e7fa6fc61b0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -201,3 +201,29 @@ deploy-prometheus-alerting-rules: changes: - .gitlab-ci.yml - ./scripts/ci/monitoring/**/* + +#### stage: .post + +# This job cancels the whole pipeline if any of provided jobs fail. +# In a DAG, every jobs chain is executed independently of others. The `fail_fast` principle suggests +# to fail the pipeline as soon as possible to shorten the feedback loop. +cancel-pipeline: + stage: .post + needs: + - job: test-linux-stable + artifacts: false + - job: test-linux-stable-int + artifacts: false + - job: cargo-check-subkey + artifacts: false + - job: cargo-check-benches + artifacts: false + - job: check-tracing + artifacts: false + rules: + - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs + when: on_failure + variables: + PROJECT_ID: "${CI_PROJECT_ID}" + PIPELINE_ID: "${CI_PIPELINE_ID}" + trigger: "parity/infrastructure/ci_cd/pipeline-stopper" diff --git a/scripts/ci/gitlab/pipeline/test.yml b/scripts/ci/gitlab/pipeline/test.yml index bd2c059c3ed68..769d1cd30d0d5 100644 --- a/scripts/ci/gitlab/pipeline/test.yml +++ b/scripts/ci/gitlab/pipeline/test.yml @@ -30,6 +30,10 @@ cargo-fmt: cargo-clippy: stage: test + # this is an artificial job dependency, for pipeline optimization using GitLab's DAGs + needs: + - job: cargo-fmt + artifacts: false extends: - .docker-env - .test-refs @@ -38,6 +42,10 @@ cargo-clippy: cargo-check-nixos: stage: test + # this is an artificial job dependency, for pipeline optimization using GitLab's DAGs + needs: + - job: cargo-clippy + artifacts: false extends: - .docker-env - .test-refs @@ -117,6 +125,10 @@ cargo-check-subkey: cargo-check-try-runtime: stage: test + # this is an artificial job dependency, for pipeline optimization using GitLab's DAGs + needs: + - job: cargo-check-subkey + artifacts: false extends: - .docker-env - .test-refs @@ -125,6 +137,10 @@ cargo-check-try-runtime: cargo-check-wasmer-sandbox: stage: test + # this is an artificial job dependency, for pipeline optimization using GitLab's DAGs + needs: + - job: cargo-check-try-runtime + artifacts: false extends: - .docker-env - .test-refs @@ -133,6 +149,10 @@ cargo-check-wasmer-sandbox: test-deterministic-wasm: stage: test + # this is an artificial job dependency, for pipeline optimization using GitLab's DAGs + needs: + - job: cargo-check-wasmer-sandbox + artifacts: false extends: - .docker-env - .test-refs @@ -217,6 +237,10 @@ test-linux-stable-int: check-tracing: stage: test + # this is an artificial job dependency, for pipeline optimization using GitLab's DAGs + needs: + - job: test-linux-stable-int + artifacts: false extends: - .docker-env - .test-refs @@ -227,6 +251,10 @@ check-tracing: test-full-crypto-feature: stage: test + # this is an artificial job dependency, for pipeline optimization using GitLab's DAGs + needs: + - job: check-tracing + artifacts: false extends: - .docker-env - .test-refs From 26adb2476b478f76112b454ec9e9275cb4c454e1 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Wed, 18 May 2022 13:23:44 +0200 Subject: [PATCH 241/484] Allow to set the max supply for collection (#11441) * Allow to set the max supply for collection * Update error * Add weights info * cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_uniques --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/uniques/src/weights.rs --template=./.maintain/frame-weight-template.hbs * Update frame/uniques/src/lib.rs Co-authored-by: Oliver Tale-Yazdi Co-authored-by: Parity Bot Co-authored-by: Oliver Tale-Yazdi --- frame/uniques/src/benchmarking.rs | 10 +++ frame/uniques/src/functions.rs | 4 + frame/uniques/src/lib.rs | 52 +++++++++++ frame/uniques/src/tests.rs | 57 +++++++++++- frame/uniques/src/weights.rs | 139 +++++++++++++++++------------- 5 files changed, 199 insertions(+), 63 deletions(-) diff --git a/frame/uniques/src/benchmarking.rs b/frame/uniques/src/benchmarking.rs index cd66d03922141..14a3e2c61809a 100644 --- a/frame/uniques/src/benchmarking.rs +++ b/frame/uniques/src/benchmarking.rs @@ -398,5 +398,15 @@ benchmarks_instance_pallet! { }.into()); } + set_collection_max_supply { + let (collection, caller, _) = create_collection::(); + }: _(SystemOrigin::Signed(caller.clone()), collection, u32::MAX) + verify { + assert_last_event::(Event::CollectionMaxSupplySet { + collection, + max_supply: u32::MAX, + }.into()); + } + impl_benchmark_test_suite!(Uniques, crate::mock::new_test_ext(), crate::mock::Test); } diff --git a/frame/uniques/src/functions.rs b/frame/uniques/src/functions.rs index 20e1410a8a849..cdaa31d4e1c8a 100644 --- a/frame/uniques/src/functions.rs +++ b/frame/uniques/src/functions.rs @@ -142,6 +142,10 @@ impl, I: 'static> Pallet { with_details(collection_details)?; + if let Ok(max_supply) = CollectionMaxSupply::::try_get(&collection) { + ensure!(collection_details.items < max_supply, Error::::MaxSupplyReached); + } + let items = collection_details.items.checked_add(1).ok_or(ArithmeticError::Overflow)?; collection_details.items = items; diff --git a/frame/uniques/src/lib.rs b/frame/uniques/src/lib.rs index 0a8cf201415a1..5c7adeac0eff2 100644 --- a/frame/uniques/src/lib.rs +++ b/frame/uniques/src/lib.rs @@ -245,6 +245,11 @@ pub mod pallet { OptionQuery, >; + #[pallet::storage] + /// Keeps track of the number of items a collection might have. + pub(super) type CollectionMaxSupply, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, T::CollectionId, u32, OptionQuery>; + #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event, I: 'static = ()> { @@ -334,6 +339,8 @@ pub mod pallet { }, /// Ownership acceptance has changed for an account. OwnershipAcceptanceChanged { who: T::AccountId, maybe_collection: Option }, + /// Max supply has been set for a collection. + CollectionMaxSupplySet { collection: T::CollectionId, max_supply: u32 }, } #[pallet::error] @@ -362,6 +369,12 @@ pub mod pallet { Unaccepted, /// The item is locked. Locked, + /// All items have been minted. + MaxSupplyReached, + /// The max supply has already been set. + MaxSupplyAlreadySet, + /// The provided max supply is less to the amount of items a collection already has. + MaxSupplyTooSmall, } impl, I: 'static> Pallet { @@ -1356,5 +1369,44 @@ pub mod pallet { Self::deposit_event(Event::OwnershipAcceptanceChanged { who, maybe_collection }); Ok(()) } + + /// Set the maximum amount of items a collection could have. + /// + /// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Owner of + /// the `collection`. + /// + /// Note: This function can only succeed once per collection. + /// + /// - `collection`: The identifier of the collection to change. + /// - `max_supply`: The maximum amount of items a collection could have. + /// + /// Emits `CollectionMaxSupplySet` event when successful. + #[pallet::weight(T::WeightInfo::set_collection_max_supply())] + pub fn set_collection_max_supply( + origin: OriginFor, + collection: T::CollectionId, + max_supply: u32, + ) -> DispatchResult { + let maybe_check_owner = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some))?; + + ensure!( + !CollectionMaxSupply::::contains_key(&collection), + Error::::MaxSupplyAlreadySet + ); + + let details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + if let Some(check_owner) = &maybe_check_owner { + ensure!(check_owner == &details.owner, Error::::NoPermission); + } + + ensure!(details.items <= max_supply, Error::::MaxSupplyTooSmall); + + CollectionMaxSupply::::insert(&collection, max_supply); + Self::deposit_event(Event::CollectionMaxSupplySet { collection, max_supply }); + Ok(()) + } } } diff --git a/frame/uniques/src/tests.rs b/frame/uniques/src/tests.rs index 6bd397eefa7ef..98e81863114e2 100644 --- a/frame/uniques/src/tests.rs +++ b/frame/uniques/src/tests.rs @@ -17,8 +17,7 @@ //! Tests for Uniques pallet. -use super::*; -use crate::mock::*; +use crate::{mock::*, Event, *}; use frame_support::{assert_noop, assert_ok, traits::Currency}; use pallet_balances::Error as BalancesError; use sp_std::prelude::*; @@ -71,6 +70,18 @@ fn attributes(collection: u32) -> Vec<(Option, Vec, Vec)> { s } +fn events() -> Vec> { + let result = System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let mock::Event::Uniques(inner) = e { Some(inner) } else { None }) + .collect::>(); + + System::reset_events(); + + result +} + #[test] fn basic_setup_works() { new_test_ext().execute_with(|| { @@ -633,3 +644,45 @@ fn cancel_approval_works_with_force() { ); }); } + +#[test] +fn max_supply_should_work() { + new_test_ext().execute_with(|| { + let collection_id = 0; + let user_id = 1; + let max_supply = 2; + + // validate set_collection_max_supply + assert_ok!(Uniques::force_create(Origin::root(), collection_id, user_id, true)); + assert!(!CollectionMaxSupply::::contains_key(collection_id)); + + assert_ok!(Uniques::set_collection_max_supply( + Origin::signed(user_id), + collection_id, + max_supply + )); + assert_eq!(CollectionMaxSupply::::get(collection_id).unwrap(), max_supply); + + assert!(events().contains(&Event::::CollectionMaxSupplySet { + collection: collection_id, + max_supply, + })); + + assert_noop!( + Uniques::set_collection_max_supply( + Origin::signed(user_id), + collection_id, + max_supply + 1 + ), + Error::::MaxSupplyAlreadySet + ); + + // validate we can't mint more to max supply + assert_ok!(Uniques::mint(Origin::signed(user_id), collection_id, 0, user_id)); + assert_ok!(Uniques::mint(Origin::signed(user_id), collection_id, 1, user_id)); + assert_noop!( + Uniques::mint(Origin::signed(user_id), collection_id, 2, user_id), + Error::::MaxSupplyReached + ); + }); +} diff --git a/frame/uniques/src/weights.rs b/frame/uniques/src/weights.rs index 9b0cbda423ace..98cc823f0ccbd 100644 --- a/frame/uniques/src/weights.rs +++ b/frame/uniques/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_uniques //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-12, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-17, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -68,6 +68,7 @@ pub trait WeightInfo { fn approve_transfer() -> Weight; fn cancel_approval() -> Weight; fn set_accept_ownership() -> Weight; + fn set_collection_max_supply() -> Weight; } /// Weights for pallet_uniques using the Substrate node and recommended hardware. @@ -76,14 +77,14 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn create() -> Weight { - (24_319_000 as Weight) + (23_770_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn force_create() -> Weight { - (13_572_000 as Weight) + (13_759_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -96,12 +97,12 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Account (r:0 w:20) fn destroy(n: u32, m: u32, a: u32, ) -> Weight { (0 as Weight) - // Standard Error: 15_000 - .saturating_add((9_433_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 15_000 - .saturating_add((980_000 as Weight).saturating_mul(m as Weight)) - // Standard Error: 15_000 - .saturating_add((852_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 16_000 + .saturating_add((9_387_000 as Weight).saturating_mul(n as Weight)) + // Standard Error: 16_000 + .saturating_add((967_000 as Weight).saturating_mul(m as Weight)) + // Standard Error: 16_000 + .saturating_add((799_000 as Weight).saturating_mul(a as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) .saturating_add(T::DbWeight::get().writes(3 as Weight)) @@ -111,17 +112,18 @@ impl WeightInfo for SubstrateWeight { } // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:1) + // Storage: Uniques CollectionMaxSupply (r:1 w:0) // Storage: Uniques Account (r:0 w:1) fn mint() -> Weight { - (29_884_000 as Weight) - .saturating_add(T::DbWeight::get().reads(2 as Weight)) + (31_588_000 as Weight) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Account (r:0 w:1) fn burn() -> Weight { - (31_384_000 as Weight) + (31_125_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -129,7 +131,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Account (r:0 w:2) fn transfer() -> Weight { - (23_254_000 as Weight) + (22_955_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -137,8 +139,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Asset (r:100 w:100) fn redeposit(i: u32, ) -> Weight { (0 as Weight) - // Standard Error: 13_000 - .saturating_add((11_718_000 as Weight).saturating_mul(i as Weight)) + // Standard Error: 12_000 + .saturating_add((11_761_000 as Weight).saturating_mul(i as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -147,26 +149,26 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn freeze() -> Weight { - (17_807_000 as Weight) + (17_808_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn thaw() -> Weight { - (17_904_000 as Weight) + (18_342_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn freeze_collection() -> Weight { - (13_828_000 as Weight) + (13_423_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn thaw_collection() -> Weight { - (13_636_000 as Weight) + (13_453_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -174,20 +176,20 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:2) fn transfer_ownership() -> Weight { - (20_897_000 as Weight) + (20_745_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn set_team() -> Weight { - (14_340_000 as Weight) + (14_192_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn force_item_status() -> Weight { - (16_355_000 as Weight) + (16_488_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -195,7 +197,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn set_attribute() -> Weight { - (37_035_000 as Weight) + (37_207_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -203,58 +205,65 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn clear_attribute() -> Weight { - (34_957_000 as Weight) + (35_432_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:1) fn set_metadata() -> Weight { - (28_337_000 as Weight) + (28_655_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:1) fn clear_metadata() -> Weight { - (29_166_000 as Weight) + (28_898_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassMetadataOf (r:1 w:1) fn set_collection_metadata() -> Weight { - (28_246_000 as Weight) + (27_890_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques ClassMetadataOf (r:1 w:1) fn clear_collection_metadata() -> Weight { - (26_637_000 as Weight) + (26_538_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) fn approve_transfer() -> Weight { - (18_938_000 as Weight) + (18_961_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) fn cancel_approval() -> Weight { - (18_465_000 as Weight) + (19_116_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques OwnershipAcceptance (r:1 w:1) fn set_accept_ownership() -> Weight { - (16_675_000 as Weight) + (17_154_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } + // Storage: Uniques CollectionMaxSupply (r:1 w:1) + // Storage: Uniques Class (r:1 w:0) + fn set_collection_max_supply() -> Weight { + (16_060_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } } // For backwards compatibility and tests @@ -262,14 +271,14 @@ impl WeightInfo for () { // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn create() -> Weight { - (24_319_000 as Weight) + (23_770_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn force_create() -> Weight { - (13_572_000 as Weight) + (13_759_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -282,12 +291,12 @@ impl WeightInfo for () { // Storage: Uniques Account (r:0 w:20) fn destroy(n: u32, m: u32, a: u32, ) -> Weight { (0 as Weight) - // Standard Error: 15_000 - .saturating_add((9_433_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 15_000 - .saturating_add((980_000 as Weight).saturating_mul(m as Weight)) - // Standard Error: 15_000 - .saturating_add((852_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 16_000 + .saturating_add((9_387_000 as Weight).saturating_mul(n as Weight)) + // Standard Error: 16_000 + .saturating_add((967_000 as Weight).saturating_mul(m as Weight)) + // Standard Error: 16_000 + .saturating_add((799_000 as Weight).saturating_mul(a as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) @@ -297,17 +306,18 @@ impl WeightInfo for () { } // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:1) + // Storage: Uniques CollectionMaxSupply (r:1 w:0) // Storage: Uniques Account (r:0 w:1) fn mint() -> Weight { - (29_884_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + (31_588_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Account (r:0 w:1) fn burn() -> Weight { - (31_384_000 as Weight) + (31_125_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -315,7 +325,7 @@ impl WeightInfo for () { // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Account (r:0 w:2) fn transfer() -> Weight { - (23_254_000 as Weight) + (22_955_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -323,8 +333,8 @@ impl WeightInfo for () { // Storage: Uniques Asset (r:100 w:100) fn redeposit(i: u32, ) -> Weight { (0 as Weight) - // Standard Error: 13_000 - .saturating_add((11_718_000 as Weight).saturating_mul(i as Weight)) + // Standard Error: 12_000 + .saturating_add((11_761_000 as Weight).saturating_mul(i as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(i as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -333,26 +343,26 @@ impl WeightInfo for () { // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn freeze() -> Weight { - (17_807_000 as Weight) + (17_808_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn thaw() -> Weight { - (17_904_000 as Weight) + (18_342_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn freeze_collection() -> Weight { - (13_828_000 as Weight) + (13_423_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn thaw_collection() -> Weight { - (13_636_000 as Weight) + (13_453_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -360,20 +370,20 @@ impl WeightInfo for () { // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:2) fn transfer_ownership() -> Weight { - (20_897_000 as Weight) + (20_745_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn set_team() -> Weight { - (14_340_000 as Weight) + (14_192_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn force_item_status() -> Weight { - (16_355_000 as Weight) + (16_488_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -381,7 +391,7 @@ impl WeightInfo for () { // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn set_attribute() -> Weight { - (37_035_000 as Weight) + (37_207_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -389,56 +399,63 @@ impl WeightInfo for () { // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn clear_attribute() -> Weight { - (34_957_000 as Weight) + (35_432_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:1) fn set_metadata() -> Weight { - (28_337_000 as Weight) + (28_655_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:1) fn clear_metadata() -> Weight { - (29_166_000 as Weight) + (28_898_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassMetadataOf (r:1 w:1) fn set_collection_metadata() -> Weight { - (28_246_000 as Weight) + (27_890_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques ClassMetadataOf (r:1 w:1) fn clear_collection_metadata() -> Weight { - (26_637_000 as Weight) + (26_538_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) fn approve_transfer() -> Weight { - (18_938_000 as Weight) + (18_961_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) fn cancel_approval() -> Weight { - (18_465_000 as Weight) + (19_116_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques OwnershipAcceptance (r:1 w:1) fn set_accept_ownership() -> Weight { - (16_675_000 as Weight) + (17_154_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } + // Storage: Uniques CollectionMaxSupply (r:1 w:1) + // Storage: Uniques Class (r:1 w:0) + fn set_collection_max_supply() -> Weight { + (16_060_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } } From 75e2633c6c5014c6621f82c13e130603e98003db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulo=20Silva=20de=20Souza?= <77391175+joao-paulo-parity@users.noreply.github.com> Date: Wed, 18 May 2022 08:35:15 -0300 Subject: [PATCH 242/484] disable check-dependent-cumulus (#11450) --- scripts/ci/gitlab/pipeline/build.yml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/scripts/ci/gitlab/pipeline/build.yml b/scripts/ci/gitlab/pipeline/build.yml index 4ab83f53e7bdf..f7ce974924d60 100644 --- a/scripts/ci/gitlab/pipeline/build.yml +++ b/scripts/ci/gitlab/pipeline/build.yml @@ -32,14 +32,15 @@ check-dependent-polkadot: substrate: polkadot-v* polkadot: release-v* -check-dependent-cumulus: - extends: .check-dependent-project - variables: - DEPENDENT_REPO: cumulus - EXTRA_DEPENDENCIES: polkadot - COMPANION_OVERRIDES: | - substrate: polkadot-v* - polkadot: release-v* +# TODO: Re-enable after https://github.com/paritytech/pipeline-scripts/issues/44 +#check-dependent-cumulus: +# extends: .check-dependent-project +# variables: +# DEPENDENT_REPO: cumulus +# EXTRA_DEPENDENCIES: polkadot +# COMPANION_OVERRIDES: | +# substrate: polkadot-v* +# polkadot: release-v* build-linux-substrate: stage: build From be6acf6f7852cdcb7a8bd7cdba38ae91eea4d946 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Wed, 18 May 2022 21:20:47 +0200 Subject: [PATCH 243/484] trie: Optimize `keys` function (#11457) * trie: Optimize `keys` function Instead of iterating the entire state and collecting all keys that match the given prefix, we can directly use the optimized prefix iterator. * Add a test --- primitives/state-machine/src/trie_backend.rs | 24 +++++++++++++++++-- .../state-machine/src/trie_backend_essence.rs | 19 +++------------ 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/primitives/state-machine/src/trie_backend.rs b/primitives/state-machine/src/trie_backend.rs index 3b985ec2b99f6..c0a620120bff2 100644 --- a/primitives/state-machine/src/trie_backend.rs +++ b/primitives/state-machine/src/trie_backend.rs @@ -190,8 +190,8 @@ pub mod tests { use sp_core::H256; use sp_runtime::traits::BlakeTwo256; use sp_trie::{ - trie_types::{TrieDBMutV0, TrieDBMutV1}, - KeySpacedDBMut, PrefixedMemoryDB, TrieMut, + trie_types::{TrieDB, TrieDBMutV0, TrieDBMutV1}, + KeySpacedDBMut, PrefixedMemoryDB, Trie, TrieMut, }; use std::{collections::HashSet, iter}; @@ -369,4 +369,24 @@ pub mod tests { expected.insert(b"value2".to_vec()); assert_eq!(seen, expected); } + + #[test] + fn keys_with_empty_prefix_returns_all_keys() { + keys_with_empty_prefix_returns_all_keys_inner(StateVersion::V0); + keys_with_empty_prefix_returns_all_keys_inner(StateVersion::V1); + } + fn keys_with_empty_prefix_returns_all_keys_inner(state_version: StateVersion) { + let (test_db, test_root) = test_db(state_version); + let expected = TrieDB::new(&test_db, &test_root) + .unwrap() + .iter() + .unwrap() + .map(|d| d.unwrap().0.to_vec()) + .collect::>(); + + let trie = test_trie(state_version); + let keys = trie.keys(&[]); + + assert_eq!(expected, keys); + } } diff --git a/primitives/state-machine/src/trie_backend_essence.rs b/primitives/state-machine/src/trie_backend_essence.rs index 0bbea7004848a..11cac92efd2f4 100644 --- a/primitives/state-machine/src/trie_backend_essence.rs +++ b/primitives/state-machine/src/trie_backend_essence.rs @@ -453,22 +453,9 @@ where /// Returns all keys that start with the given `prefix`. pub fn keys(&self, prefix: &[u8]) -> Vec { - let collect_all = || -> sp_std::result::Result<_, Box>> { - let trie = TrieDB::::new(self, &self.root)?; - let mut v = Vec::new(); - for x in trie.iter()? { - let (key, _) = x?; - if key.starts_with(prefix) { - v.push(key.to_vec()); - } - } - - Ok(v) - }; - - collect_all() - .map_err(|e| debug!(target: "trie", "Error extracting trie keys: {}", e)) - .unwrap_or_default() + let mut keys = Vec::new(); + self.for_keys_with_prefix(prefix, |k| keys.push(k.to_vec())); + keys } /// Return the storage root after applying the given `delta`. From 237ceb70e2bf6b2618cec989eb0ba386f7f82e17 Mon Sep 17 00:00:00 2001 From: Sergejs Kostjucenko <85877331+sergejparity@users.noreply.github.com> Date: Thu, 19 May 2022 00:26:44 +0300 Subject: [PATCH 244/484] add GHA support to dependabot (#11448) * add GHA support to dependabot * fix formatting * add labels * add label --- .github/dependabot.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index a321729dcbc81..cca9219e6c5e1 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,3 +5,8 @@ updates: labels: ["A2-insubstantial", "B0-silent", "C1-low 📌"] schedule: interval: "daily" + - package-ecosystem: github-actions + directory: '/' + labels: ["A2-insubstantial", "B0-silent", "C1-low 📌", "E3-dependencies"] + schedule: + interval: daily From 5d1fb3a3b47f1ac9f51a5fc8471285496b8ceb97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Thu, 19 May 2022 01:50:22 +0200 Subject: [PATCH 245/484] contracts: Add `set_code` root dispatchable (#11451) * Add `set_code` dispatchable * cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_contracts --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/contracts/src/weights.rs --template=./.maintain/frame-weight-template.hbs Co-authored-by: Parity Bot --- frame/contracts/src/benchmarking/mod.rs | 14 + frame/contracts/src/lib.rs | 36 + frame/contracts/src/tests.rs | 82 +- frame/contracts/src/weights.rs | 1390 ++++++++++++----------- 4 files changed, 836 insertions(+), 686 deletions(-) diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs index 01e62e07e1ce0..57a330ae275f2 100644 --- a/frame/contracts/src/benchmarking/mod.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -391,6 +391,20 @@ benchmarks! { assert!(>::code_removed(&hash)); } + set_code { + let instance = >::with_caller( + whitelisted_caller(), WasmModule::dummy(), vec![], + )?; + // we just add some bytes so that the code hash is different + let WasmModule { code, hash, .. } = >::dummy_with_bytes(128); + >::store_code_raw(code, instance.caller.clone())?; + let callee = instance.addr.clone(); + assert_ne!(instance.info()?.code_hash, hash); + }: _(RawOrigin::Root, callee, hash) + verify { + assert_eq!(instance.info()?.code_hash, hash); + } + seal_caller { let r in 0 .. API_BENCHMARK_BATCHES; let instance = Contract::::new(WasmModule::getter( diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index ffac3e222bbce..a8d1e18efd2cc 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -586,6 +586,42 @@ pub mod pallet { // we waive the fee because removing unused code is beneficial Ok(Pays::No.into()) } + + /// Privileged function that changes the code of an existing contract. + /// + /// This takes care of updating refcounts and all other necessary operations. Returns + /// an error if either the `code_hash` or `dest` do not exist. + /// + /// # Note + /// + /// This does **not** change the address of the contract in question. This means + /// that the contract address is no longer derived from its code hash after calling + /// this dispatchable. + #[pallet::weight(T::WeightInfo::set_code())] + pub fn set_code( + origin: OriginFor, + dest: ::Source, + code_hash: CodeHash, + ) -> DispatchResult { + ensure_root(origin)?; + let dest = T::Lookup::lookup(dest)?; + >::try_mutate(&dest, |contract| { + let contract = if let Some(contract) = contract { + contract + } else { + return Err(>::ContractNotFound.into()) + }; + >::add_user(code_hash)?; + >::remove_user(contract.code_hash); + Self::deposit_event(Event::ContractCodeUpdated { + contract: dest.clone(), + new_code_hash: code_hash, + old_code_hash: contract.code_hash, + }); + contract.code_hash = code_hash; + Ok(()) + }) + } } #[pallet::event] diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index e52ce5ca0e156..2125fe24d1a07 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -40,7 +40,7 @@ use frame_support::{ weights::{constants::WEIGHT_PER_SECOND, DispatchClass, PostDispatchInfo, Weight}, }; use frame_system::{self as system, EventRecord, Phase}; -use pretty_assertions::assert_eq; +use pretty_assertions::{assert_eq, assert_ne}; use sp_core::Bytes; use sp_io::hashing::blake2_256; use sp_keystore::{testing::KeyStore, KeystoreExt}; @@ -2862,6 +2862,86 @@ fn storage_deposit_works() { }); } +#[test] +fn set_code_extrinsic() { + let (wasm, code_hash) = compile_module::("dummy").unwrap(); + let (new_wasm, new_code_hash) = compile_module::("crypto_hashes").unwrap(); + + assert_ne!(code_hash, new_code_hash); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + + assert_ok!(Contracts::instantiate_with_code( + Origin::signed(ALICE), + 0, + GAS_LIMIT, + None, + wasm, + vec![], + vec![], + )); + let addr = Contracts::contract_address(&ALICE, &code_hash, &[]); + + assert_ok!(Contracts::upload_code(Origin::signed(ALICE), new_wasm, None,)); + + // Drop previous events + initialize_block(2); + + assert_eq!(>::get(&addr).unwrap().code_hash, code_hash); + assert_refcount!(&code_hash, 1); + assert_refcount!(&new_code_hash, 0); + + // only root can execute this extrinsic + assert_noop!( + Contracts::set_code(Origin::signed(ALICE), addr.clone(), new_code_hash), + sp_runtime::traits::BadOrigin, + ); + assert_eq!(>::get(&addr).unwrap().code_hash, code_hash); + assert_refcount!(&code_hash, 1); + assert_refcount!(&new_code_hash, 0); + assert_eq!(System::events(), vec![],); + + // contract must exist + assert_noop!( + Contracts::set_code(Origin::root(), BOB, new_code_hash), + >::ContractNotFound, + ); + assert_eq!(>::get(&addr).unwrap().code_hash, code_hash); + assert_refcount!(&code_hash, 1); + assert_refcount!(&new_code_hash, 0); + assert_eq!(System::events(), vec![],); + + // new code hash must exist + assert_noop!( + Contracts::set_code(Origin::root(), addr.clone(), Default::default()), + >::CodeNotFound, + ); + assert_eq!(>::get(&addr).unwrap().code_hash, code_hash); + assert_refcount!(&code_hash, 1); + assert_refcount!(&new_code_hash, 0); + assert_eq!(System::events(), vec![],); + + // successful call + assert_ok!(Contracts::set_code(Origin::root(), addr.clone(), new_code_hash)); + assert_eq!(>::get(&addr).unwrap().code_hash, new_code_hash); + assert_refcount!(&code_hash, 0); + assert_refcount!(&new_code_hash, 1); + assert_eq!( + System::events(), + vec![EventRecord { + phase: Phase::Initialization, + event: Event::Contracts(pallet_contracts::Event::ContractCodeUpdated { + contract: addr, + new_code_hash, + old_code_hash: code_hash, + }), + topics: vec![], + },] + ); + }); +} + #[test] fn call_after_killed_account_needs_funding() { let (wasm, code_hash) = compile_module::("dummy").unwrap(); diff --git a/frame/contracts/src/weights.rs b/frame/contracts/src/weights.rs index 85ff2548ca698..256e0ffc9d808 100644 --- a/frame/contracts/src/weights.rs +++ b/frame/contracts/src/weights.rs @@ -18,12 +18,13 @@ //! Autogenerated weights for pallet_contracts //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-03-21, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-18, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: // target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -54,6 +55,7 @@ pub trait WeightInfo { fn call() -> Weight; fn upload_code(c: u32, ) -> Weight; fn remove_code() -> Weight; + fn set_code() -> Weight; fn seal_caller(r: u32, ) -> Weight; fn seal_is_contract(r: u32, ) -> Weight; fn seal_code_hash(r: u32, ) -> Weight; @@ -163,14 +165,14 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: Contracts DeletionQueue (r:1 w:0) fn on_initialize() -> Weight { - (1_569_000 as Weight) + (1_641_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn on_initialize_per_trie_key(k: u32, ) -> Weight { - (9_620_000 as Weight) + (11_282_000 as Weight) // Standard Error: 0 - .saturating_add((748_000 as Weight).saturating_mul(k as Weight)) + .saturating_add((758_000 as Weight).saturating_mul(k as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(k as Weight))) @@ -178,17 +180,17 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts DeletionQueue (r:1 w:0) fn on_initialize_per_queue_item(q: u32, ) -> Weight { (0 as Weight) - // Standard Error: 4_000 - .saturating_add((1_795_000 as Weight).saturating_mul(q as Weight)) + // Standard Error: 5_000 + .saturating_add((1_892_000 as Weight).saturating_mul(q as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Contracts PristineCode (r:1 w:0) // Storage: Contracts CodeStorage (r:0 w:1) fn reinstrument(c: u32, ) -> Weight { - (12_256_000 as Weight) + (19_908_000 as Weight) // Standard Error: 0 - .saturating_add((49_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((48_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -197,9 +199,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Timestamp Now (r:1 w:0) // Storage: System Account (r:1 w:1) fn call_with_code_per_byte(c: u32, ) -> Weight { - (213_494_000 as Weight) + (200_315_000 as Weight) // Standard Error: 0 - .saturating_add((53_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((51_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -211,9 +213,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts PristineCode (r:0 w:1) // Storage: Contracts OwnerInfoOf (r:0 w:1) fn instantiate_with_code(c: u32, s: u32, ) -> Weight { - (231_180_000 as Weight) + (252_036_000 as Weight) // Standard Error: 0 - .saturating_add((125_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((121_000 as Weight).saturating_mul(c as Weight)) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) @@ -226,7 +228,7 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:1 w:1) fn instantiate(s: u32, ) -> Weight { - (172_238_000 as Weight) + (177_274_000 as Weight) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(6 as Weight)) @@ -237,7 +239,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Timestamp Now (r:1 w:0) // Storage: System Account (r:1 w:1) fn call() -> Weight { - (140_912_000 as Weight) + (146_086_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -245,7 +247,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts PristineCode (r:0 w:1) // Storage: Contracts OwnerInfoOf (r:0 w:1) fn upload_code(c: u32, ) -> Weight { - (42_493_000 as Weight) + (43_995_000 as Weight) // Standard Error: 0 .saturating_add((49_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) @@ -255,18 +257,25 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:0 w:1) // Storage: Contracts PristineCode (r:0 w:1) fn remove_code() -> Weight { - (24_533_000 as Weight) + (25_007_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } + // Storage: Contracts ContractInfoOf (r:1 w:1) + // Storage: Contracts OwnerInfoOf (r:2 w:2) + fn set_code() -> Weight { + (22_940_000 as Weight) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) + } // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_caller(r: u32, ) -> Weight { - (220_009_000 as Weight) - // Standard Error: 80_000 - .saturating_add((47_887_000 as Weight).saturating_mul(r as Weight)) + (207_387_000 as Weight) + // Standard Error: 88_000 + .saturating_add((40_577_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -275,11 +284,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_is_contract(r: u32, ) -> Weight { - (71_779_000 as Weight) - // Standard Error: 900_000 - .saturating_add((371_278_000 as Weight).saturating_mul(r as Weight)) + (96_692_000 as Weight) + // Standard Error: 688_000 + .saturating_add((313_348_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(T::DbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: System Account (r:1 w:0) @@ -287,11 +296,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_code_hash(r: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 2_329_000 - .saturating_add((451_731_000 as Weight).saturating_mul(r as Weight)) + (107_076_000 as Weight) + // Standard Error: 721_000 + .saturating_add((370_976_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(T::DbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: System Account (r:1 w:0) @@ -299,9 +308,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_own_code_hash(r: u32, ) -> Weight { - (227_824_000 as Weight) - // Standard Error: 128_000 - .saturating_add((52_843_000 as Weight).saturating_mul(r as Weight)) + (207_147_000 as Weight) + // Standard Error: 112_000 + .saturating_add((43_974_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -310,9 +319,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_caller_is_origin(r: u32, ) -> Weight { - (213_057_000 as Weight) - // Standard Error: 43_000 - .saturating_add((21_023_000 as Weight).saturating_mul(r as Weight)) + (203_029_000 as Weight) + // Standard Error: 31_000 + .saturating_add((16_811_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -321,9 +330,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_address(r: u32, ) -> Weight { - (219_066_000 as Weight) - // Standard Error: 117_000 - .saturating_add((48_056_000 as Weight).saturating_mul(r as Weight)) + (206_641_000 as Weight) + // Standard Error: 86_000 + .saturating_add((40_239_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -332,9 +341,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_gas_left(r: u32, ) -> Weight { - (218_844_000 as Weight) - // Standard Error: 101_000 - .saturating_add((47_325_000 as Weight).saturating_mul(r as Weight)) + (205_195_000 as Weight) + // Standard Error: 85_000 + .saturating_add((39_915_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -343,9 +352,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_balance(r: u32, ) -> Weight { - (219_234_000 as Weight) - // Standard Error: 171_000 - .saturating_add((142_534_000 as Weight).saturating_mul(r as Weight)) + (213_345_000 as Weight) + // Standard Error: 126_000 + .saturating_add((116_249_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -354,9 +363,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_value_transferred(r: u32, ) -> Weight { - (215_128_000 as Weight) - // Standard Error: 119_000 - .saturating_add((48_392_000 as Weight).saturating_mul(r as Weight)) + (206_141_000 as Weight) + // Standard Error: 96_000 + .saturating_add((39_999_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -365,9 +374,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_minimum_balance(r: u32, ) -> Weight { - (214_603_000 as Weight) - // Standard Error: 115_000 - .saturating_add((48_041_000 as Weight).saturating_mul(r as Weight)) + (205_569_000 as Weight) + // Standard Error: 104_000 + .saturating_add((40_046_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -376,9 +385,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_block_number(r: u32, ) -> Weight { - (214_091_000 as Weight) - // Standard Error: 126_000 - .saturating_add((48_067_000 as Weight).saturating_mul(r as Weight)) + (205_782_000 as Weight) + // Standard Error: 89_000 + .saturating_add((39_757_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -387,9 +396,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_now(r: u32, ) -> Weight { - (214_418_000 as Weight) - // Standard Error: 100_000 - .saturating_add((47_791_000 as Weight).saturating_mul(r as Weight)) + (205_465_000 as Weight) + // Standard Error: 87_000 + .saturating_add((39_605_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -399,9 +408,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Timestamp Now (r:1 w:0) // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) fn seal_weight_to_fee(r: u32, ) -> Weight { - (229_261_000 as Weight) - // Standard Error: 150_000 - .saturating_add((121_988_000 as Weight).saturating_mul(r as Weight)) + (208_900_000 as Weight) + // Standard Error: 108_000 + .saturating_add((101_341_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -410,9 +419,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_gas(r: u32, ) -> Weight { - (127_983_000 as Weight) - // Standard Error: 56_000 - .saturating_add((24_016_000 as Weight).saturating_mul(r as Weight)) + (132_788_000 as Weight) + // Standard Error: 22_000 + .saturating_add((19_129_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -421,9 +430,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_input(r: u32, ) -> Weight { - (216_634_000 as Weight) - // Standard Error: 114_000 - .saturating_add((46_864_000 as Weight).saturating_mul(r as Weight)) + (205_111_000 as Weight) + // Standard Error: 96_000 + .saturating_add((39_317_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -432,9 +441,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_input_per_kb(n: u32, ) -> Weight { - (285_180_000 as Weight) - // Standard Error: 4_000 - .saturating_add((11_899_000 as Weight).saturating_mul(n as Weight)) + (263_644_000 as Weight) + // Standard Error: 3_000 + .saturating_add((9_593_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -442,8 +451,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - fn seal_return(_r: u32, ) -> Weight { - (215_379_000 as Weight) + fn seal_return(r: u32, ) -> Weight { + (201_299_000 as Weight) + // Standard Error: 903_000 + .saturating_add((947_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -452,9 +463,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_return_per_kb(n: u32, ) -> Weight { - (213_957_000 as Weight) + (202_730_000 as Weight) // Standard Error: 0 - .saturating_add((201_000 as Weight).saturating_mul(n as Weight)) + .saturating_add((183_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -465,9 +476,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts DeletionQueue (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:1 w:1) fn seal_terminate(r: u32, ) -> Weight { - (215_782_000 as Weight) - // Standard Error: 149_000 - .saturating_add((52_421_000 as Weight).saturating_mul(r as Weight)) + (203_795_000 as Weight) + // Standard Error: 759_000 + .saturating_add((52_720_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().reads((4 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -479,9 +490,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Timestamp Now (r:1 w:0) // Storage: RandomnessCollectiveFlip RandomMaterial (r:1 w:0) fn seal_random(r: u32, ) -> Weight { - (217_910_000 as Weight) - // Standard Error: 149_000 - .saturating_add((157_525_000 as Weight).saturating_mul(r as Weight)) + (215_805_000 as Weight) + // Standard Error: 158_000 + .saturating_add((127_687_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -490,9 +501,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_deposit_event(r: u32, ) -> Weight { - (230_787_000 as Weight) - // Standard Error: 210_000 - .saturating_add((296_973_000 as Weight).saturating_mul(r as Weight)) + (215_300_000 as Weight) + // Standard Error: 143_000 + .saturating_add((229_183_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -500,137 +511,137 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:100 w:100) + // Storage: System EventTopics (r:80 w:80) fn seal_deposit_event_per_topic_and_kb(t: u32, n: u32, ) -> Weight { - (539_238_000 as Weight) - // Standard Error: 1_701_000 - .saturating_add((294_348_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 335_000 - .saturating_add((82_116_000 as Weight).saturating_mul(n as Weight)) + (459_865_000 as Weight) + // Standard Error: 1_733_000 + .saturating_add((235_448_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 341_000 + .saturating_add((66_363_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(t as Weight))) + .saturating_add(T::DbWeight::get().reads((80 as Weight).saturating_mul(t as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) - .saturating_add(T::DbWeight::get().writes((100 as Weight).saturating_mul(t as Weight))) + .saturating_add(T::DbWeight::get().writes((80 as Weight).saturating_mul(t as Weight))) } // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_debug_message(r: u32, ) -> Weight { - (135_081_000 as Weight) - // Standard Error: 94_000 - .saturating_add((39_247_000 as Weight).saturating_mul(r as Weight)) + (137_820_000 as Weight) + // Standard Error: 77_000 + .saturating_add((31_956_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_set_storage(r: u32, ) -> Weight { - (41_752_000 as Weight) - // Standard Error: 1_107_000 - .saturating_add((403_473_000 as Weight).saturating_mul(r as Weight)) + (94_007_000 as Weight) + // Standard Error: 589_000 + .saturating_add((328_631_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(T::DbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) - .saturating_add(T::DbWeight::get().writes((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(T::DbWeight::get().writes((80 as Weight).saturating_mul(r as Weight))) } // Storage: Skipped Metadata (r:0 w:0) fn seal_set_storage_per_new_kb(n: u32, ) -> Weight { - (602_028_000 as Weight) - // Standard Error: 255_000 - .saturating_add((28_303_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(T::DbWeight::get().reads(105 as Weight)) - .saturating_add(T::DbWeight::get().writes(103 as Weight)) + (528_794_000 as Weight) + // Standard Error: 233_000 + .saturating_add((21_533_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(85 as Weight)) + .saturating_add(T::DbWeight::get().writes(83 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_set_storage_per_old_kb(n: u32, ) -> Weight { - (620_964_000 as Weight) - // Standard Error: 308_000 - .saturating_add((11_338_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(T::DbWeight::get().reads(105 as Weight)) - .saturating_add(T::DbWeight::get().writes(103 as Weight)) + (543_507_000 as Weight) + // Standard Error: 270_000 + .saturating_add((9_142_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(85 as Weight)) + .saturating_add(T::DbWeight::get().writes(83 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_clear_storage(r: u32, ) -> Weight { - (88_113_000 as Weight) - // Standard Error: 851_000 - .saturating_add((381_671_000 as Weight).saturating_mul(r as Weight)) + (129_893_000 as Weight) + // Standard Error: 471_000 + .saturating_add((313_807_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) - .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(T::DbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(3 as Weight)) - .saturating_add(T::DbWeight::get().writes((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(T::DbWeight::get().writes((80 as Weight).saturating_mul(r as Weight))) } // Storage: Skipped Metadata (r:0 w:0) fn seal_clear_storage_per_kb(n: u32, ) -> Weight { - (603_193_000 as Weight) - // Standard Error: 262_000 - .saturating_add((10_286_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(T::DbWeight::get().reads(105 as Weight)) - .saturating_add(T::DbWeight::get().writes(103 as Weight)) + (532_323_000 as Weight) + // Standard Error: 204_000 + .saturating_add((8_729_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(85 as Weight)) + .saturating_add(T::DbWeight::get().writes(83 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_get_storage(r: u32, ) -> Weight { - (112_477_000 as Weight) - // Standard Error: 666_000 - .saturating_add((324_824_000 as Weight).saturating_mul(r as Weight)) + (131_771_000 as Weight) + // Standard Error: 415_000 + .saturating_add((269_174_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(T::DbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_get_storage_per_kb(n: u32, ) -> Weight { - (564_781_000 as Weight) - // Standard Error: 403_000 - .saturating_add((63_824_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(T::DbWeight::get().reads(104 as Weight)) + (494_409_000 as Weight) + // Standard Error: 280_000 + .saturating_add((50_499_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(84 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_contains_storage(r: u32, ) -> Weight { - (115_207_000 as Weight) - // Standard Error: 672_000 - .saturating_add((290_919_000 as Weight).saturating_mul(r as Weight)) + (133_366_000 as Weight) + // Standard Error: 400_000 + .saturating_add((235_761_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) - .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(T::DbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_contains_storage_per_kb(n: u32, ) -> Weight { - (511_026_000 as Weight) - // Standard Error: 224_000 - .saturating_add((10_138_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(T::DbWeight::get().reads(104 as Weight)) + (447_828_000 as Weight) + // Standard Error: 197_000 + .saturating_add((8_190_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(84 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_take_storage(r: u32, ) -> Weight { - (79_113_000 as Weight) - // Standard Error: 904_000 - .saturating_add((417_022_000 as Weight).saturating_mul(r as Weight)) + (121_933_000 as Weight) + // Standard Error: 509_000 + .saturating_add((342_550_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) - .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(T::DbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(3 as Weight)) - .saturating_add(T::DbWeight::get().writes((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(T::DbWeight::get().writes((80 as Weight).saturating_mul(r as Weight))) } // Storage: Skipped Metadata (r:0 w:0) fn seal_take_storage_per_kb(n: u32, ) -> Weight { - (651_769_000 as Weight) - // Standard Error: 338_000 - .saturating_add((65_576_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(T::DbWeight::get().reads(105 as Weight)) - .saturating_add(T::DbWeight::get().writes(103 as Weight)) + (578_648_000 as Weight) + // Standard Error: 320_000 + .saturating_add((51_767_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(85 as Weight)) + .saturating_add(T::DbWeight::get().writes(83 as Weight)) } // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_transfer(r: u32, ) -> Weight { - (93_588_000 as Weight) - // Standard Error: 1_444_000 - .saturating_add((1_803_217_000 as Weight).saturating_mul(r as Weight)) + (146_890_000 as Weight) + // Standard Error: 789_000 + .saturating_add((1_427_230_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) - .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(T::DbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(2 as Weight)) - .saturating_add(T::DbWeight::get().writes((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(T::DbWeight::get().writes((80 as Weight).saturating_mul(r as Weight))) } // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) @@ -638,12 +649,12 @@ impl WeightInfo for SubstrateWeight { // Storage: Timestamp Now (r:1 w:0) fn seal_call(r: u32, ) -> Weight { (0 as Weight) - // Standard Error: 3_050_000 - .saturating_add((19_925_209_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 2_542_000 + .saturating_add((15_063_367_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) - .saturating_add(T::DbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(T::DbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) - .saturating_add(T::DbWeight::get().writes((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(T::DbWeight::get().writes((80 as Weight).saturating_mul(r as Weight))) } // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) @@ -651,56 +662,56 @@ impl WeightInfo for SubstrateWeight { // Storage: Timestamp Now (r:1 w:0) fn seal_delegate_call(r: u32, ) -> Weight { (0 as Weight) - // Standard Error: 7_377_000 - .saturating_add((19_978_301_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads((99 as Weight).saturating_mul(r as Weight))) + // Standard Error: 3_267_000 + .saturating_add((15_037_276_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads((79 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:101 w:101) + // Storage: Contracts ContractInfoOf (r:81 w:81) // Storage: Contracts CodeStorage (r:2 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_call_per_transfer_clone_kb(t: u32, c: u32, ) -> Weight { - (11_124_804_000 as Weight) - // Standard Error: 21_475_000 - .saturating_add((1_635_442_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 9_000 - .saturating_add((11_981_000 as Weight).saturating_mul(c as Weight)) - .saturating_add(T::DbWeight::get().reads(105 as Weight)) - .saturating_add(T::DbWeight::get().reads((101 as Weight).saturating_mul(t as Weight))) - .saturating_add(T::DbWeight::get().writes(101 as Weight)) - .saturating_add(T::DbWeight::get().writes((101 as Weight).saturating_mul(t as Weight))) + (9_081_513_000 as Weight) + // Standard Error: 18_081_000 + .saturating_add((1_388_391_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 7_000 + .saturating_add((9_653_000 as Weight).saturating_mul(c as Weight)) + .saturating_add(T::DbWeight::get().reads(85 as Weight)) + .saturating_add(T::DbWeight::get().reads((81 as Weight).saturating_mul(t as Weight))) + .saturating_add(T::DbWeight::get().writes(81 as Weight)) + .saturating_add(T::DbWeight::get().writes((81 as Weight).saturating_mul(t as Weight))) } // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) // Storage: Contracts Nonce (r:1 w:1) - // Storage: Contracts OwnerInfoOf (r:100 w:100) + // Storage: Contracts OwnerInfoOf (r:80 w:80) fn seal_instantiate(r: u32, ) -> Weight { (0 as Weight) - // Standard Error: 47_682_000 - .saturating_add((27_883_754_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 34_560_000 + .saturating_add((20_828_575_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(6 as Weight)) - .saturating_add(T::DbWeight::get().reads((400 as Weight).saturating_mul(r as Weight))) + .saturating_add(T::DbWeight::get().reads((320 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(3 as Weight)) - .saturating_add(T::DbWeight::get().writes((400 as Weight).saturating_mul(r as Weight))) + .saturating_add(T::DbWeight::get().writes((320 as Weight).saturating_mul(r as Weight))) } - // Storage: System Account (r:101 w:101) - // Storage: Contracts ContractInfoOf (r:101 w:101) + // Storage: System Account (r:81 w:81) + // Storage: Contracts ContractInfoOf (r:81 w:81) // Storage: Contracts CodeStorage (r:2 w:1) // Storage: Timestamp Now (r:1 w:0) // Storage: Contracts Nonce (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:1 w:1) fn seal_instantiate_per_transfer_salt_kb(t: u32, s: u32, ) -> Weight { - (14_824_308_000 as Weight) - // Standard Error: 39_823_000 - .saturating_add((880_630_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 18_000 - .saturating_add((156_232_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(207 as Weight)) + (12_124_898_000 as Weight) + // Standard Error: 34_430_000 + .saturating_add((573_971_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 16_000 + .saturating_add((123_415_000 as Weight).saturating_mul(s as Weight)) + .saturating_add(T::DbWeight::get().reads(167 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(t as Weight))) - .saturating_add(T::DbWeight::get().writes(205 as Weight)) + .saturating_add(T::DbWeight::get().writes(165 as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(t as Weight))) } // Storage: System Account (r:1 w:0) @@ -708,9 +719,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_sha2_256(r: u32, ) -> Weight { - (218_378_000 as Weight) - // Standard Error: 131_000 - .saturating_add((78_260_000 as Weight).saturating_mul(r as Weight)) + (203_422_000 as Weight) + // Standard Error: 126_000 + .saturating_add((65_317_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -719,9 +730,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_sha2_256_per_kb(n: u32, ) -> Weight { - (202_849_000 as Weight) - // Standard Error: 61_000 - .saturating_add((466_532_000 as Weight).saturating_mul(n as Weight)) + (411_079_000 as Weight) + // Standard Error: 20_000 + .saturating_add((354_947_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -730,9 +741,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_keccak_256(r: u32, ) -> Weight { - (220_258_000 as Weight) - // Standard Error: 147_000 - .saturating_add((90_363_000 as Weight).saturating_mul(r as Weight)) + (203_192_000 as Weight) + // Standard Error: 116_000 + .saturating_add((73_562_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -741,9 +752,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_keccak_256_per_kb(n: u32, ) -> Weight { - (232_371_000 as Weight) - // Standard Error: 23_000 - .saturating_add((307_036_000 as Weight).saturating_mul(n as Weight)) + (227_572_000 as Weight) + // Standard Error: 13_000 + .saturating_add((244_646_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -752,9 +763,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_blake2_256(r: u32, ) -> Weight { - (217_991_000 as Weight) - // Standard Error: 124_000 - .saturating_add((62_273_000 as Weight).saturating_mul(r as Weight)) + (204_922_000 as Weight) + // Standard Error: 112_000 + .saturating_add((51_349_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -763,9 +774,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_blake2_256_per_kb(n: u32, ) -> Weight { - (396_282_000 as Weight) - // Standard Error: 13_000 - .saturating_add((119_575_000 as Weight).saturating_mul(n as Weight)) + (247_293_000 as Weight) + // Standard Error: 17_000 + .saturating_add((94_654_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -774,9 +785,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_blake2_128(r: u32, ) -> Weight { - (217_578_000 as Weight) - // Standard Error: 104_000 - .saturating_add((62_189_000 as Weight).saturating_mul(r as Weight)) + (203_511_000 as Weight) + // Standard Error: 108_000 + .saturating_add((50_585_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -785,9 +796,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_blake2_128_per_kb(n: u32, ) -> Weight { - (358_167_000 as Weight) - // Standard Error: 15_000 - .saturating_add((119_692_000 as Weight).saturating_mul(n as Weight)) + (253_133_000 as Weight) + // Standard Error: 10_000 + .saturating_add((94_606_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -796,9 +807,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_ecdsa_recover(r: u32, ) -> Weight { - (292_884_000 as Weight) - // Standard Error: 683_000 - .saturating_add((3_824_902_000 as Weight).saturating_mul(r as Weight)) + (280_685_000 as Weight) + // Standard Error: 627_000 + .saturating_add((3_062_997_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -807,9 +818,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_ecdsa_to_eth_address(r: u32, ) -> Weight { - (272_893_000 as Weight) - // Standard Error: 1_438_000 - .saturating_add((15_412_877_000 as Weight).saturating_mul(r as Weight)) + (212_204_000 as Weight) + // Standard Error: 469_000 + .saturating_add((2_034_397_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -817,268 +828,268 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: Contracts OwnerInfoOf (r:36 w:36) + // Storage: Contracts OwnerInfoOf (r:16 w:16) fn seal_set_code_hash(r: u32, ) -> Weight { (0 as Weight) - // Standard Error: 2_302_000 - .saturating_add((922_467_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(T::DbWeight::get().reads((99 as Weight).saturating_mul(r as Weight))) - .saturating_add(T::DbWeight::get().writes((99 as Weight).saturating_mul(r as Weight))) + // Standard Error: 1_507_000 + .saturating_add((747_481_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads((79 as Weight).saturating_mul(r as Weight))) + .saturating_add(T::DbWeight::get().writes((79 as Weight).saturating_mul(r as Weight))) } fn instr_i64const(r: u32, ) -> Weight { - (74_516_000 as Weight) + (74_627_000 as Weight) // Standard Error: 1_000 - .saturating_add((592_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((593_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64load(r: u32, ) -> Weight { - (74_430_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_320_000 as Weight).saturating_mul(r as Weight)) + (74_587_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_294_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64store(r: u32, ) -> Weight { - (74_440_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_428_000 as Weight).saturating_mul(r as Weight)) + (74_392_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_381_000 as Weight).saturating_mul(r as Weight)) } fn instr_select(r: u32, ) -> Weight { - (74_151_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_782_000 as Weight).saturating_mul(r as Weight)) + (73_823_000 as Weight) + // Standard Error: 3_000 + .saturating_add((1_780_000 as Weight).saturating_mul(r as Weight)) } fn instr_if(r: u32, ) -> Weight { - (74_225_000 as Weight) - // Standard Error: 3_000 - .saturating_add((1_887_000 as Weight).saturating_mul(r as Weight)) + (74_072_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_946_000 as Weight).saturating_mul(r as Weight)) } fn instr_br(r: u32, ) -> Weight { - (73_987_000 as Weight) + (73_981_000 as Weight) // Standard Error: 1_000 - .saturating_add((898_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((930_000 as Weight).saturating_mul(r as Weight)) } fn instr_br_if(r: u32, ) -> Weight { - (73_305_000 as Weight) - // Standard Error: 4_000 - .saturating_add((1_465_000 as Weight).saturating_mul(r as Weight)) + (73_690_000 as Weight) + // Standard Error: 3_000 + .saturating_add((1_433_000 as Weight).saturating_mul(r as Weight)) } fn instr_br_table(r: u32, ) -> Weight { - (73_037_000 as Weight) - // Standard Error: 3_000 - .saturating_add((1_605_000 as Weight).saturating_mul(r as Weight)) + (74_317_000 as Weight) + // Standard Error: 8_000 + .saturating_add((1_571_000 as Weight).saturating_mul(r as Weight)) } fn instr_br_table_per_entry(e: u32, ) -> Weight { - (76_434_000 as Weight) + (76_373_000 as Weight) // Standard Error: 0 .saturating_add((4_000 as Weight).saturating_mul(e as Weight)) } fn instr_call(r: u32, ) -> Weight { - (75_461_000 as Weight) + (76_013_000 as Weight) // Standard Error: 10_000 - .saturating_add((7_446_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((6_848_000 as Weight).saturating_mul(r as Weight)) } fn instr_call_indirect(r: u32, ) -> Weight { - (87_222_000 as Weight) - // Standard Error: 15_000 - .saturating_add((9_406_000 as Weight).saturating_mul(r as Weight)) + (88_704_000 as Weight) + // Standard Error: 16_000 + .saturating_add((8_824_000 as Weight).saturating_mul(r as Weight)) } fn instr_call_indirect_per_param(p: u32, ) -> Weight { - (97_204_000 as Weight) + (97_846_000 as Weight) // Standard Error: 1_000 - .saturating_add((472_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((471_000 as Weight).saturating_mul(p as Weight)) } fn instr_local_get(r: u32, ) -> Weight { - (75_299_000 as Weight) + (75_010_000 as Weight) // Standard Error: 1_000 - .saturating_add((601_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((612_000 as Weight).saturating_mul(r as Weight)) } fn instr_local_set(r: u32, ) -> Weight { - (74_827_000 as Weight) - // Standard Error: 3_000 - .saturating_add((686_000 as Weight).saturating_mul(r as Weight)) + (74_636_000 as Weight) + // Standard Error: 1_000 + .saturating_add((681_000 as Weight).saturating_mul(r as Weight)) } fn instr_local_tee(r: u32, ) -> Weight { - (74_624_000 as Weight) - // Standard Error: 2_000 - .saturating_add((895_000 as Weight).saturating_mul(r as Weight)) + (74_196_000 as Weight) + // Standard Error: 0 + .saturating_add((916_000 as Weight).saturating_mul(r as Weight)) } fn instr_global_get(r: u32, ) -> Weight { - (77_435_000 as Weight) - // Standard Error: 3_000 - .saturating_add((1_201_000 as Weight).saturating_mul(r as Weight)) + (77_349_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_176_000 as Weight).saturating_mul(r as Weight)) } fn instr_global_set(r: u32, ) -> Weight { - (76_693_000 as Weight) - // Standard Error: 3_000 - .saturating_add((1_410_000 as Weight).saturating_mul(r as Weight)) + (76_861_000 as Weight) + // Standard Error: 4_000 + .saturating_add((1_383_000 as Weight).saturating_mul(r as Weight)) } fn instr_memory_current(r: u32, ) -> Weight { - (74_244_000 as Weight) - // Standard Error: 1_000 - .saturating_add((660_000 as Weight).saturating_mul(r as Weight)) + (74_696_000 as Weight) + // Standard Error: 2_000 + .saturating_add((655_000 as Weight).saturating_mul(r as Weight)) } fn instr_memory_grow(r: u32, ) -> Weight { - (73_527_000 as Weight) - // Standard Error: 931_000 - .saturating_add((184_946_000 as Weight).saturating_mul(r as Weight)) + (73_522_000 as Weight) + // Standard Error: 50_000 + .saturating_add((179_720_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64clz(r: u32, ) -> Weight { - (74_181_000 as Weight) - // Standard Error: 6_000 - .saturating_add((906_000 as Weight).saturating_mul(r as Weight)) + (74_292_000 as Weight) + // Standard Error: 1_000 + .saturating_add((884_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64ctz(r: u32, ) -> Weight { - (74_339_000 as Weight) + (74_299_000 as Weight) // Standard Error: 1_000 - .saturating_add((896_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((882_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64popcnt(r: u32, ) -> Weight { - (74_444_000 as Weight) - // Standard Error: 3_000 - .saturating_add((889_000 as Weight).saturating_mul(r as Weight)) + (74_289_000 as Weight) + // Standard Error: 1_000 + .saturating_add((883_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64eqz(r: u32, ) -> Weight { - (74_572_000 as Weight) - // Standard Error: 1_000 - .saturating_add((908_000 as Weight).saturating_mul(r as Weight)) + (74_120_000 as Weight) + // Standard Error: 2_000 + .saturating_add((899_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64extendsi32(r: u32, ) -> Weight { - (74_349_000 as Weight) - // Standard Error: 2_000 - .saturating_add((881_000 as Weight).saturating_mul(r as Weight)) + (74_599_000 as Weight) + // Standard Error: 1_000 + .saturating_add((851_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64extendui32(r: u32, ) -> Weight { - (74_426_000 as Weight) - // Standard Error: 1_000 - .saturating_add((875_000 as Weight).saturating_mul(r as Weight)) + (74_622_000 as Weight) + // Standard Error: 2_000 + .saturating_add((854_000 as Weight).saturating_mul(r as Weight)) } fn instr_i32wrapi64(r: u32, ) -> Weight { - (74_172_000 as Weight) + (74_504_000 as Weight) // Standard Error: 2_000 - .saturating_add((906_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((874_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64eq(r: u32, ) -> Weight { - (74_169_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_362_000 as Weight).saturating_mul(r as Weight)) + (74_532_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_338_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64ne(r: u32, ) -> Weight { - (74_205_000 as Weight) - // Standard Error: 4_000 - .saturating_add((1_360_000 as Weight).saturating_mul(r as Weight)) + (74_130_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_347_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64lts(r: u32, ) -> Weight { - (74_237_000 as Weight) - // Standard Error: 3_000 - .saturating_add((1_356_000 as Weight).saturating_mul(r as Weight)) + (74_172_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_343_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64ltu(r: u32, ) -> Weight { - (74_181_000 as Weight) + (74_356_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_357_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_338_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64gts(r: u32, ) -> Weight { - (74_038_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_360_000 as Weight).saturating_mul(r as Weight)) + (74_181_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_343_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64gtu(r: u32, ) -> Weight { - (73_881_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_372_000 as Weight).saturating_mul(r as Weight)) + (74_045_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_358_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64les(r: u32, ) -> Weight { - (73_969_000 as Weight) - // Standard Error: 0 - .saturating_add((1_361_000 as Weight).saturating_mul(r as Weight)) + (74_246_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_341_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64leu(r: u32, ) -> Weight { - (74_497_000 as Weight) - // Standard Error: 3_000 - .saturating_add((1_359_000 as Weight).saturating_mul(r as Weight)) + (73_998_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_363_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64ges(r: u32, ) -> Weight { - (74_275_000 as Weight) + (74_035_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_354_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_348_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64geu(r: u32, ) -> Weight { - (74_349_000 as Weight) - // Standard Error: 3_000 - .saturating_add((1_357_000 as Weight).saturating_mul(r as Weight)) + (74_511_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_335_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64add(r: u32, ) -> Weight { - (74_192_000 as Weight) + (74_305_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_333_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_319_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64sub(r: u32, ) -> Weight { - (74_271_000 as Weight) + (74_214_000 as Weight) // Standard Error: 2_000 - .saturating_add((1_340_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_321_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64mul(r: u32, ) -> Weight { - (73_971_000 as Weight) + (74_073_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_340_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_323_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64divs(r: u32, ) -> Weight { - (74_546_000 as Weight) + (74_295_000 as Weight) // Standard Error: 2_000 - .saturating_add((1_995_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_986_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64divu(r: u32, ) -> Weight { - (74_194_000 as Weight) + (74_109_000 as Weight) // Standard Error: 2_000 - .saturating_add((2_050_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((2_023_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64rems(r: u32, ) -> Weight { - (74_106_000 as Weight) + (73_990_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_997_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_993_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64remu(r: u32, ) -> Weight { - (74_219_000 as Weight) - // Standard Error: 5_000 - .saturating_add((2_061_000 as Weight).saturating_mul(r as Weight)) + (73_940_000 as Weight) + // Standard Error: 1_000 + .saturating_add((2_056_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64and(r: u32, ) -> Weight { - (74_157_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_338_000 as Weight).saturating_mul(r as Weight)) + (74_261_000 as Weight) + // Standard Error: 5_000 + .saturating_add((1_326_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64or(r: u32, ) -> Weight { - (74_135_000 as Weight) + (74_014_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_336_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_324_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64xor(r: u32, ) -> Weight { - (74_038_000 as Weight) - // Standard Error: 3_000 - .saturating_add((1_345_000 as Weight).saturating_mul(r as Weight)) + (74_220_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_323_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64shl(r: u32, ) -> Weight { - (74_011_000 as Weight) - // Standard Error: 3_000 - .saturating_add((1_362_000 as Weight).saturating_mul(r as Weight)) + (74_139_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_343_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64shrs(r: u32, ) -> Weight { - (74_054_000 as Weight) + (73_923_000 as Weight) // Standard Error: 2_000 .saturating_add((1_356_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64shru(r: u32, ) -> Weight { - (73_900_000 as Weight) + (73_999_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_358_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_352_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64rotl(r: u32, ) -> Weight { - (73_948_000 as Weight) - // Standard Error: 0 - .saturating_add((1_357_000 as Weight).saturating_mul(r as Weight)) + (74_312_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_340_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64rotr(r: u32, ) -> Weight { - (73_972_000 as Weight) + (73_988_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_358_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_349_000 as Weight).saturating_mul(r as Weight)) } } @@ -1086,14 +1097,14 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { // Storage: Contracts DeletionQueue (r:1 w:0) fn on_initialize() -> Weight { - (1_569_000 as Weight) + (1_641_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn on_initialize_per_trie_key(k: u32, ) -> Weight { - (9_620_000 as Weight) + (11_282_000 as Weight) // Standard Error: 0 - .saturating_add((748_000 as Weight).saturating_mul(k as Weight)) + .saturating_add((758_000 as Weight).saturating_mul(k as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(k as Weight))) @@ -1101,17 +1112,17 @@ impl WeightInfo for () { // Storage: Contracts DeletionQueue (r:1 w:0) fn on_initialize_per_queue_item(q: u32, ) -> Weight { (0 as Weight) - // Standard Error: 4_000 - .saturating_add((1_795_000 as Weight).saturating_mul(q as Weight)) + // Standard Error: 5_000 + .saturating_add((1_892_000 as Weight).saturating_mul(q as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Contracts PristineCode (r:1 w:0) // Storage: Contracts CodeStorage (r:0 w:1) fn reinstrument(c: u32, ) -> Weight { - (12_256_000 as Weight) + (19_908_000 as Weight) // Standard Error: 0 - .saturating_add((49_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((48_000 as Weight).saturating_mul(c as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1120,9 +1131,9 @@ impl WeightInfo for () { // Storage: Timestamp Now (r:1 w:0) // Storage: System Account (r:1 w:1) fn call_with_code_per_byte(c: u32, ) -> Weight { - (213_494_000 as Weight) + (200_315_000 as Weight) // Standard Error: 0 - .saturating_add((53_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((51_000 as Weight).saturating_mul(c as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -1134,9 +1145,9 @@ impl WeightInfo for () { // Storage: Contracts PristineCode (r:0 w:1) // Storage: Contracts OwnerInfoOf (r:0 w:1) fn instantiate_with_code(c: u32, s: u32, ) -> Weight { - (231_180_000 as Weight) + (252_036_000 as Weight) // Standard Error: 0 - .saturating_add((125_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((121_000 as Weight).saturating_mul(c as Weight)) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) @@ -1149,7 +1160,7 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:1 w:1) fn instantiate(s: u32, ) -> Weight { - (172_238_000 as Weight) + (177_274_000 as Weight) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(6 as Weight)) @@ -1160,7 +1171,7 @@ impl WeightInfo for () { // Storage: Timestamp Now (r:1 w:0) // Storage: System Account (r:1 w:1) fn call() -> Weight { - (140_912_000 as Weight) + (146_086_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -1168,7 +1179,7 @@ impl WeightInfo for () { // Storage: Contracts PristineCode (r:0 w:1) // Storage: Contracts OwnerInfoOf (r:0 w:1) fn upload_code(c: u32, ) -> Weight { - (42_493_000 as Weight) + (43_995_000 as Weight) // Standard Error: 0 .saturating_add((49_000 as Weight).saturating_mul(c as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) @@ -1178,18 +1189,25 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:0 w:1) // Storage: Contracts PristineCode (r:0 w:1) fn remove_code() -> Weight { - (24_533_000 as Weight) + (25_007_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } + // Storage: Contracts ContractInfoOf (r:1 w:1) + // Storage: Contracts OwnerInfoOf (r:2 w:2) + fn set_code() -> Weight { + (22_940_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + } // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_caller(r: u32, ) -> Weight { - (220_009_000 as Weight) - // Standard Error: 80_000 - .saturating_add((47_887_000 as Weight).saturating_mul(r as Weight)) + (207_387_000 as Weight) + // Standard Error: 88_000 + .saturating_add((40_577_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1198,11 +1216,11 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_is_contract(r: u32, ) -> Weight { - (71_779_000 as Weight) - // Standard Error: 900_000 - .saturating_add((371_278_000 as Weight).saturating_mul(r as Weight)) + (96_692_000 as Weight) + // Standard Error: 688_000 + .saturating_add((313_348_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(RocksDbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: System Account (r:1 w:0) @@ -1210,11 +1228,11 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_code_hash(r: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 2_329_000 - .saturating_add((451_731_000 as Weight).saturating_mul(r as Weight)) + (107_076_000 as Weight) + // Standard Error: 721_000 + .saturating_add((370_976_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(RocksDbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: System Account (r:1 w:0) @@ -1222,9 +1240,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_own_code_hash(r: u32, ) -> Weight { - (227_824_000 as Weight) - // Standard Error: 128_000 - .saturating_add((52_843_000 as Weight).saturating_mul(r as Weight)) + (207_147_000 as Weight) + // Standard Error: 112_000 + .saturating_add((43_974_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1233,9 +1251,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_caller_is_origin(r: u32, ) -> Weight { - (213_057_000 as Weight) - // Standard Error: 43_000 - .saturating_add((21_023_000 as Weight).saturating_mul(r as Weight)) + (203_029_000 as Weight) + // Standard Error: 31_000 + .saturating_add((16_811_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1244,9 +1262,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_address(r: u32, ) -> Weight { - (219_066_000 as Weight) - // Standard Error: 117_000 - .saturating_add((48_056_000 as Weight).saturating_mul(r as Weight)) + (206_641_000 as Weight) + // Standard Error: 86_000 + .saturating_add((40_239_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1255,9 +1273,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_gas_left(r: u32, ) -> Weight { - (218_844_000 as Weight) - // Standard Error: 101_000 - .saturating_add((47_325_000 as Weight).saturating_mul(r as Weight)) + (205_195_000 as Weight) + // Standard Error: 85_000 + .saturating_add((39_915_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1266,9 +1284,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_balance(r: u32, ) -> Weight { - (219_234_000 as Weight) - // Standard Error: 171_000 - .saturating_add((142_534_000 as Weight).saturating_mul(r as Weight)) + (213_345_000 as Weight) + // Standard Error: 126_000 + .saturating_add((116_249_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1277,9 +1295,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_value_transferred(r: u32, ) -> Weight { - (215_128_000 as Weight) - // Standard Error: 119_000 - .saturating_add((48_392_000 as Weight).saturating_mul(r as Weight)) + (206_141_000 as Weight) + // Standard Error: 96_000 + .saturating_add((39_999_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1288,9 +1306,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_minimum_balance(r: u32, ) -> Weight { - (214_603_000 as Weight) - // Standard Error: 115_000 - .saturating_add((48_041_000 as Weight).saturating_mul(r as Weight)) + (205_569_000 as Weight) + // Standard Error: 104_000 + .saturating_add((40_046_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1299,9 +1317,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_block_number(r: u32, ) -> Weight { - (214_091_000 as Weight) - // Standard Error: 126_000 - .saturating_add((48_067_000 as Weight).saturating_mul(r as Weight)) + (205_782_000 as Weight) + // Standard Error: 89_000 + .saturating_add((39_757_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1310,9 +1328,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_now(r: u32, ) -> Weight { - (214_418_000 as Weight) - // Standard Error: 100_000 - .saturating_add((47_791_000 as Weight).saturating_mul(r as Weight)) + (205_465_000 as Weight) + // Standard Error: 87_000 + .saturating_add((39_605_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1322,9 +1340,9 @@ impl WeightInfo for () { // Storage: Timestamp Now (r:1 w:0) // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) fn seal_weight_to_fee(r: u32, ) -> Weight { - (229_261_000 as Weight) - // Standard Error: 150_000 - .saturating_add((121_988_000 as Weight).saturating_mul(r as Weight)) + (208_900_000 as Weight) + // Standard Error: 108_000 + .saturating_add((101_341_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1333,9 +1351,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_gas(r: u32, ) -> Weight { - (127_983_000 as Weight) - // Standard Error: 56_000 - .saturating_add((24_016_000 as Weight).saturating_mul(r as Weight)) + (132_788_000 as Weight) + // Standard Error: 22_000 + .saturating_add((19_129_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1344,9 +1362,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_input(r: u32, ) -> Weight { - (216_634_000 as Weight) - // Standard Error: 114_000 - .saturating_add((46_864_000 as Weight).saturating_mul(r as Weight)) + (205_111_000 as Weight) + // Standard Error: 96_000 + .saturating_add((39_317_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1355,9 +1373,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_input_per_kb(n: u32, ) -> Weight { - (285_180_000 as Weight) - // Standard Error: 4_000 - .saturating_add((11_899_000 as Weight).saturating_mul(n as Weight)) + (263_644_000 as Weight) + // Standard Error: 3_000 + .saturating_add((9_593_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1365,8 +1383,10 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - fn seal_return(_r: u32, ) -> Weight { - (215_379_000 as Weight) + fn seal_return(r: u32, ) -> Weight { + (201_299_000 as Weight) + // Standard Error: 903_000 + .saturating_add((947_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1375,9 +1395,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_return_per_kb(n: u32, ) -> Weight { - (213_957_000 as Weight) + (202_730_000 as Weight) // Standard Error: 0 - .saturating_add((201_000 as Weight).saturating_mul(n as Weight)) + .saturating_add((183_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1388,9 +1408,9 @@ impl WeightInfo for () { // Storage: Contracts DeletionQueue (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:1 w:1) fn seal_terminate(r: u32, ) -> Weight { - (215_782_000 as Weight) - // Standard Error: 149_000 - .saturating_add((52_421_000 as Weight).saturating_mul(r as Weight)) + (203_795_000 as Weight) + // Standard Error: 759_000 + .saturating_add((52_720_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().reads((4 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -1402,9 +1422,9 @@ impl WeightInfo for () { // Storage: Timestamp Now (r:1 w:0) // Storage: RandomnessCollectiveFlip RandomMaterial (r:1 w:0) fn seal_random(r: u32, ) -> Weight { - (217_910_000 as Weight) - // Standard Error: 149_000 - .saturating_add((157_525_000 as Weight).saturating_mul(r as Weight)) + (215_805_000 as Weight) + // Standard Error: 158_000 + .saturating_add((127_687_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1413,9 +1433,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_deposit_event(r: u32, ) -> Weight { - (230_787_000 as Weight) - // Standard Error: 210_000 - .saturating_add((296_973_000 as Weight).saturating_mul(r as Weight)) + (215_300_000 as Weight) + // Standard Error: 143_000 + .saturating_add((229_183_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1423,137 +1443,137 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:100 w:100) + // Storage: System EventTopics (r:80 w:80) fn seal_deposit_event_per_topic_and_kb(t: u32, n: u32, ) -> Weight { - (539_238_000 as Weight) - // Standard Error: 1_701_000 - .saturating_add((294_348_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 335_000 - .saturating_add((82_116_000 as Weight).saturating_mul(n as Weight)) + (459_865_000 as Weight) + // Standard Error: 1_733_000 + .saturating_add((235_448_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 341_000 + .saturating_add((66_363_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().reads((100 as Weight).saturating_mul(t as Weight))) + .saturating_add(RocksDbWeight::get().reads((80 as Weight).saturating_mul(t as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes((100 as Weight).saturating_mul(t as Weight))) + .saturating_add(RocksDbWeight::get().writes((80 as Weight).saturating_mul(t as Weight))) } // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_debug_message(r: u32, ) -> Weight { - (135_081_000 as Weight) - // Standard Error: 94_000 - .saturating_add((39_247_000 as Weight).saturating_mul(r as Weight)) + (137_820_000 as Weight) + // Standard Error: 77_000 + .saturating_add((31_956_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_set_storage(r: u32, ) -> Weight { - (41_752_000 as Weight) - // Standard Error: 1_107_000 - .saturating_add((403_473_000 as Weight).saturating_mul(r as Weight)) + (94_007_000 as Weight) + // Standard Error: 589_000 + .saturating_add((328_631_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(RocksDbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(RocksDbWeight::get().writes((80 as Weight).saturating_mul(r as Weight))) } // Storage: Skipped Metadata (r:0 w:0) fn seal_set_storage_per_new_kb(n: u32, ) -> Weight { - (602_028_000 as Weight) - // Standard Error: 255_000 - .saturating_add((28_303_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(RocksDbWeight::get().reads(105 as Weight)) - .saturating_add(RocksDbWeight::get().writes(103 as Weight)) + (528_794_000 as Weight) + // Standard Error: 233_000 + .saturating_add((21_533_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(RocksDbWeight::get().reads(85 as Weight)) + .saturating_add(RocksDbWeight::get().writes(83 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_set_storage_per_old_kb(n: u32, ) -> Weight { - (620_964_000 as Weight) - // Standard Error: 308_000 - .saturating_add((11_338_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(RocksDbWeight::get().reads(105 as Weight)) - .saturating_add(RocksDbWeight::get().writes(103 as Weight)) + (543_507_000 as Weight) + // Standard Error: 270_000 + .saturating_add((9_142_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(RocksDbWeight::get().reads(85 as Weight)) + .saturating_add(RocksDbWeight::get().writes(83 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_clear_storage(r: u32, ) -> Weight { - (88_113_000 as Weight) - // Standard Error: 851_000 - .saturating_add((381_671_000 as Weight).saturating_mul(r as Weight)) + (129_893_000 as Weight) + // Standard Error: 471_000 + .saturating_add((313_807_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) - .saturating_add(RocksDbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(RocksDbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(RocksDbWeight::get().writes((80 as Weight).saturating_mul(r as Weight))) } // Storage: Skipped Metadata (r:0 w:0) fn seal_clear_storage_per_kb(n: u32, ) -> Weight { - (603_193_000 as Weight) - // Standard Error: 262_000 - .saturating_add((10_286_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(RocksDbWeight::get().reads(105 as Weight)) - .saturating_add(RocksDbWeight::get().writes(103 as Weight)) + (532_323_000 as Weight) + // Standard Error: 204_000 + .saturating_add((8_729_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(RocksDbWeight::get().reads(85 as Weight)) + .saturating_add(RocksDbWeight::get().writes(83 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_get_storage(r: u32, ) -> Weight { - (112_477_000 as Weight) - // Standard Error: 666_000 - .saturating_add((324_824_000 as Weight).saturating_mul(r as Weight)) + (131_771_000 as Weight) + // Standard Error: 415_000 + .saturating_add((269_174_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(RocksDbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_get_storage_per_kb(n: u32, ) -> Weight { - (564_781_000 as Weight) - // Standard Error: 403_000 - .saturating_add((63_824_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(RocksDbWeight::get().reads(104 as Weight)) + (494_409_000 as Weight) + // Standard Error: 280_000 + .saturating_add((50_499_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(RocksDbWeight::get().reads(84 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_contains_storage(r: u32, ) -> Weight { - (115_207_000 as Weight) - // Standard Error: 672_000 - .saturating_add((290_919_000 as Weight).saturating_mul(r as Weight)) + (133_366_000 as Weight) + // Standard Error: 400_000 + .saturating_add((235_761_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) - .saturating_add(RocksDbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(RocksDbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_contains_storage_per_kb(n: u32, ) -> Weight { - (511_026_000 as Weight) - // Standard Error: 224_000 - .saturating_add((10_138_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(RocksDbWeight::get().reads(104 as Weight)) + (447_828_000 as Weight) + // Standard Error: 197_000 + .saturating_add((8_190_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(RocksDbWeight::get().reads(84 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_take_storage(r: u32, ) -> Weight { - (79_113_000 as Weight) - // Standard Error: 904_000 - .saturating_add((417_022_000 as Weight).saturating_mul(r as Weight)) + (121_933_000 as Weight) + // Standard Error: 509_000 + .saturating_add((342_550_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) - .saturating_add(RocksDbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(RocksDbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(RocksDbWeight::get().writes((80 as Weight).saturating_mul(r as Weight))) } // Storage: Skipped Metadata (r:0 w:0) fn seal_take_storage_per_kb(n: u32, ) -> Weight { - (651_769_000 as Weight) - // Standard Error: 338_000 - .saturating_add((65_576_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(RocksDbWeight::get().reads(105 as Weight)) - .saturating_add(RocksDbWeight::get().writes(103 as Weight)) + (578_648_000 as Weight) + // Standard Error: 320_000 + .saturating_add((51_767_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(RocksDbWeight::get().reads(85 as Weight)) + .saturating_add(RocksDbWeight::get().writes(83 as Weight)) } // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_transfer(r: u32, ) -> Weight { - (93_588_000 as Weight) - // Standard Error: 1_444_000 - .saturating_add((1_803_217_000 as Weight).saturating_mul(r as Weight)) + (146_890_000 as Weight) + // Standard Error: 789_000 + .saturating_add((1_427_230_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) - .saturating_add(RocksDbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(RocksDbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(RocksDbWeight::get().writes((80 as Weight).saturating_mul(r as Weight))) } // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) @@ -1561,12 +1581,12 @@ impl WeightInfo for () { // Storage: Timestamp Now (r:1 w:0) fn seal_call(r: u32, ) -> Weight { (0 as Weight) - // Standard Error: 3_050_000 - .saturating_add((19_925_209_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 2_542_000 + .saturating_add((15_063_367_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) - .saturating_add(RocksDbWeight::get().reads((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(RocksDbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes((100 as Weight).saturating_mul(r as Weight))) + .saturating_add(RocksDbWeight::get().writes((80 as Weight).saturating_mul(r as Weight))) } // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) @@ -1574,56 +1594,56 @@ impl WeightInfo for () { // Storage: Timestamp Now (r:1 w:0) fn seal_delegate_call(r: u32, ) -> Weight { (0 as Weight) - // Standard Error: 7_377_000 - .saturating_add((19_978_301_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads((99 as Weight).saturating_mul(r as Weight))) + // Standard Error: 3_267_000 + .saturating_add((15_037_276_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(RocksDbWeight::get().reads((79 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:101 w:101) + // Storage: Contracts ContractInfoOf (r:81 w:81) // Storage: Contracts CodeStorage (r:2 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_call_per_transfer_clone_kb(t: u32, c: u32, ) -> Weight { - (11_124_804_000 as Weight) - // Standard Error: 21_475_000 - .saturating_add((1_635_442_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 9_000 - .saturating_add((11_981_000 as Weight).saturating_mul(c as Weight)) - .saturating_add(RocksDbWeight::get().reads(105 as Weight)) - .saturating_add(RocksDbWeight::get().reads((101 as Weight).saturating_mul(t as Weight))) - .saturating_add(RocksDbWeight::get().writes(101 as Weight)) - .saturating_add(RocksDbWeight::get().writes((101 as Weight).saturating_mul(t as Weight))) + (9_081_513_000 as Weight) + // Standard Error: 18_081_000 + .saturating_add((1_388_391_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 7_000 + .saturating_add((9_653_000 as Weight).saturating_mul(c as Weight)) + .saturating_add(RocksDbWeight::get().reads(85 as Weight)) + .saturating_add(RocksDbWeight::get().reads((81 as Weight).saturating_mul(t as Weight))) + .saturating_add(RocksDbWeight::get().writes(81 as Weight)) + .saturating_add(RocksDbWeight::get().writes((81 as Weight).saturating_mul(t as Weight))) } // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) // Storage: Contracts Nonce (r:1 w:1) - // Storage: Contracts OwnerInfoOf (r:100 w:100) + // Storage: Contracts OwnerInfoOf (r:80 w:80) fn seal_instantiate(r: u32, ) -> Weight { (0 as Weight) - // Standard Error: 47_682_000 - .saturating_add((27_883_754_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 34_560_000 + .saturating_add((20_828_575_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(6 as Weight)) - .saturating_add(RocksDbWeight::get().reads((400 as Weight).saturating_mul(r as Weight))) + .saturating_add(RocksDbWeight::get().reads((320 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) - .saturating_add(RocksDbWeight::get().writes((400 as Weight).saturating_mul(r as Weight))) + .saturating_add(RocksDbWeight::get().writes((320 as Weight).saturating_mul(r as Weight))) } - // Storage: System Account (r:101 w:101) - // Storage: Contracts ContractInfoOf (r:101 w:101) + // Storage: System Account (r:81 w:81) + // Storage: Contracts ContractInfoOf (r:81 w:81) // Storage: Contracts CodeStorage (r:2 w:1) // Storage: Timestamp Now (r:1 w:0) // Storage: Contracts Nonce (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:1 w:1) fn seal_instantiate_per_transfer_salt_kb(t: u32, s: u32, ) -> Weight { - (14_824_308_000 as Weight) - // Standard Error: 39_823_000 - .saturating_add((880_630_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 18_000 - .saturating_add((156_232_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(207 as Weight)) + (12_124_898_000 as Weight) + // Standard Error: 34_430_000 + .saturating_add((573_971_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 16_000 + .saturating_add((123_415_000 as Weight).saturating_mul(s as Weight)) + .saturating_add(RocksDbWeight::get().reads(167 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(t as Weight))) - .saturating_add(RocksDbWeight::get().writes(205 as Weight)) + .saturating_add(RocksDbWeight::get().writes(165 as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(t as Weight))) } // Storage: System Account (r:1 w:0) @@ -1631,9 +1651,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_sha2_256(r: u32, ) -> Weight { - (218_378_000 as Weight) - // Standard Error: 131_000 - .saturating_add((78_260_000 as Weight).saturating_mul(r as Weight)) + (203_422_000 as Weight) + // Standard Error: 126_000 + .saturating_add((65_317_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1642,9 +1662,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_sha2_256_per_kb(n: u32, ) -> Weight { - (202_849_000 as Weight) - // Standard Error: 61_000 - .saturating_add((466_532_000 as Weight).saturating_mul(n as Weight)) + (411_079_000 as Weight) + // Standard Error: 20_000 + .saturating_add((354_947_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1653,9 +1673,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_keccak_256(r: u32, ) -> Weight { - (220_258_000 as Weight) - // Standard Error: 147_000 - .saturating_add((90_363_000 as Weight).saturating_mul(r as Weight)) + (203_192_000 as Weight) + // Standard Error: 116_000 + .saturating_add((73_562_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1664,9 +1684,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_keccak_256_per_kb(n: u32, ) -> Weight { - (232_371_000 as Weight) - // Standard Error: 23_000 - .saturating_add((307_036_000 as Weight).saturating_mul(n as Weight)) + (227_572_000 as Weight) + // Standard Error: 13_000 + .saturating_add((244_646_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1675,9 +1695,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_blake2_256(r: u32, ) -> Weight { - (217_991_000 as Weight) - // Standard Error: 124_000 - .saturating_add((62_273_000 as Weight).saturating_mul(r as Weight)) + (204_922_000 as Weight) + // Standard Error: 112_000 + .saturating_add((51_349_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1686,9 +1706,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_blake2_256_per_kb(n: u32, ) -> Weight { - (396_282_000 as Weight) - // Standard Error: 13_000 - .saturating_add((119_575_000 as Weight).saturating_mul(n as Weight)) + (247_293_000 as Weight) + // Standard Error: 17_000 + .saturating_add((94_654_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1697,9 +1717,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_blake2_128(r: u32, ) -> Weight { - (217_578_000 as Weight) - // Standard Error: 104_000 - .saturating_add((62_189_000 as Weight).saturating_mul(r as Weight)) + (203_511_000 as Weight) + // Standard Error: 108_000 + .saturating_add((50_585_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1708,9 +1728,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_blake2_128_per_kb(n: u32, ) -> Weight { - (358_167_000 as Weight) - // Standard Error: 15_000 - .saturating_add((119_692_000 as Weight).saturating_mul(n as Weight)) + (253_133_000 as Weight) + // Standard Error: 10_000 + .saturating_add((94_606_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1719,9 +1739,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_ecdsa_recover(r: u32, ) -> Weight { - (292_884_000 as Weight) - // Standard Error: 683_000 - .saturating_add((3_824_902_000 as Weight).saturating_mul(r as Weight)) + (280_685_000 as Weight) + // Standard Error: 627_000 + .saturating_add((3_062_997_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1730,9 +1750,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_ecdsa_to_eth_address(r: u32, ) -> Weight { - (272_893_000 as Weight) - // Standard Error: 1_438_000 - .saturating_add((15_412_877_000 as Weight).saturating_mul(r as Weight)) + (212_204_000 as Weight) + // Standard Error: 469_000 + .saturating_add((2_034_397_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1740,267 +1760,267 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: Contracts OwnerInfoOf (r:36 w:36) + // Storage: Contracts OwnerInfoOf (r:16 w:16) fn seal_set_code_hash(r: u32, ) -> Weight { (0 as Weight) - // Standard Error: 2_302_000 - .saturating_add((922_467_000 as Weight).saturating_mul(r as Weight)) - .saturating_add(RocksDbWeight::get().reads((99 as Weight).saturating_mul(r as Weight))) - .saturating_add(RocksDbWeight::get().writes((99 as Weight).saturating_mul(r as Weight))) + // Standard Error: 1_507_000 + .saturating_add((747_481_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(RocksDbWeight::get().reads((79 as Weight).saturating_mul(r as Weight))) + .saturating_add(RocksDbWeight::get().writes((79 as Weight).saturating_mul(r as Weight))) } fn instr_i64const(r: u32, ) -> Weight { - (74_516_000 as Weight) + (74_627_000 as Weight) // Standard Error: 1_000 - .saturating_add((592_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((593_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64load(r: u32, ) -> Weight { - (74_430_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_320_000 as Weight).saturating_mul(r as Weight)) + (74_587_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_294_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64store(r: u32, ) -> Weight { - (74_440_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_428_000 as Weight).saturating_mul(r as Weight)) + (74_392_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_381_000 as Weight).saturating_mul(r as Weight)) } fn instr_select(r: u32, ) -> Weight { - (74_151_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_782_000 as Weight).saturating_mul(r as Weight)) + (73_823_000 as Weight) + // Standard Error: 3_000 + .saturating_add((1_780_000 as Weight).saturating_mul(r as Weight)) } fn instr_if(r: u32, ) -> Weight { - (74_225_000 as Weight) - // Standard Error: 3_000 - .saturating_add((1_887_000 as Weight).saturating_mul(r as Weight)) + (74_072_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_946_000 as Weight).saturating_mul(r as Weight)) } fn instr_br(r: u32, ) -> Weight { - (73_987_000 as Weight) + (73_981_000 as Weight) // Standard Error: 1_000 - .saturating_add((898_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((930_000 as Weight).saturating_mul(r as Weight)) } fn instr_br_if(r: u32, ) -> Weight { - (73_305_000 as Weight) - // Standard Error: 4_000 - .saturating_add((1_465_000 as Weight).saturating_mul(r as Weight)) + (73_690_000 as Weight) + // Standard Error: 3_000 + .saturating_add((1_433_000 as Weight).saturating_mul(r as Weight)) } fn instr_br_table(r: u32, ) -> Weight { - (73_037_000 as Weight) - // Standard Error: 3_000 - .saturating_add((1_605_000 as Weight).saturating_mul(r as Weight)) + (74_317_000 as Weight) + // Standard Error: 8_000 + .saturating_add((1_571_000 as Weight).saturating_mul(r as Weight)) } fn instr_br_table_per_entry(e: u32, ) -> Weight { - (76_434_000 as Weight) + (76_373_000 as Weight) // Standard Error: 0 .saturating_add((4_000 as Weight).saturating_mul(e as Weight)) } fn instr_call(r: u32, ) -> Weight { - (75_461_000 as Weight) + (76_013_000 as Weight) // Standard Error: 10_000 - .saturating_add((7_446_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((6_848_000 as Weight).saturating_mul(r as Weight)) } fn instr_call_indirect(r: u32, ) -> Weight { - (87_222_000 as Weight) - // Standard Error: 15_000 - .saturating_add((9_406_000 as Weight).saturating_mul(r as Weight)) + (88_704_000 as Weight) + // Standard Error: 16_000 + .saturating_add((8_824_000 as Weight).saturating_mul(r as Weight)) } fn instr_call_indirect_per_param(p: u32, ) -> Weight { - (97_204_000 as Weight) + (97_846_000 as Weight) // Standard Error: 1_000 - .saturating_add((472_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((471_000 as Weight).saturating_mul(p as Weight)) } fn instr_local_get(r: u32, ) -> Weight { - (75_299_000 as Weight) + (75_010_000 as Weight) // Standard Error: 1_000 - .saturating_add((601_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((612_000 as Weight).saturating_mul(r as Weight)) } fn instr_local_set(r: u32, ) -> Weight { - (74_827_000 as Weight) - // Standard Error: 3_000 - .saturating_add((686_000 as Weight).saturating_mul(r as Weight)) + (74_636_000 as Weight) + // Standard Error: 1_000 + .saturating_add((681_000 as Weight).saturating_mul(r as Weight)) } fn instr_local_tee(r: u32, ) -> Weight { - (74_624_000 as Weight) - // Standard Error: 2_000 - .saturating_add((895_000 as Weight).saturating_mul(r as Weight)) + (74_196_000 as Weight) + // Standard Error: 0 + .saturating_add((916_000 as Weight).saturating_mul(r as Weight)) } fn instr_global_get(r: u32, ) -> Weight { - (77_435_000 as Weight) - // Standard Error: 3_000 - .saturating_add((1_201_000 as Weight).saturating_mul(r as Weight)) + (77_349_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_176_000 as Weight).saturating_mul(r as Weight)) } fn instr_global_set(r: u32, ) -> Weight { - (76_693_000 as Weight) - // Standard Error: 3_000 - .saturating_add((1_410_000 as Weight).saturating_mul(r as Weight)) + (76_861_000 as Weight) + // Standard Error: 4_000 + .saturating_add((1_383_000 as Weight).saturating_mul(r as Weight)) } fn instr_memory_current(r: u32, ) -> Weight { - (74_244_000 as Weight) - // Standard Error: 1_000 - .saturating_add((660_000 as Weight).saturating_mul(r as Weight)) + (74_696_000 as Weight) + // Standard Error: 2_000 + .saturating_add((655_000 as Weight).saturating_mul(r as Weight)) } fn instr_memory_grow(r: u32, ) -> Weight { - (73_527_000 as Weight) - // Standard Error: 931_000 - .saturating_add((184_946_000 as Weight).saturating_mul(r as Weight)) + (73_522_000 as Weight) + // Standard Error: 50_000 + .saturating_add((179_720_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64clz(r: u32, ) -> Weight { - (74_181_000 as Weight) - // Standard Error: 6_000 - .saturating_add((906_000 as Weight).saturating_mul(r as Weight)) + (74_292_000 as Weight) + // Standard Error: 1_000 + .saturating_add((884_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64ctz(r: u32, ) -> Weight { - (74_339_000 as Weight) + (74_299_000 as Weight) // Standard Error: 1_000 - .saturating_add((896_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((882_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64popcnt(r: u32, ) -> Weight { - (74_444_000 as Weight) - // Standard Error: 3_000 - .saturating_add((889_000 as Weight).saturating_mul(r as Weight)) + (74_289_000 as Weight) + // Standard Error: 1_000 + .saturating_add((883_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64eqz(r: u32, ) -> Weight { - (74_572_000 as Weight) - // Standard Error: 1_000 - .saturating_add((908_000 as Weight).saturating_mul(r as Weight)) + (74_120_000 as Weight) + // Standard Error: 2_000 + .saturating_add((899_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64extendsi32(r: u32, ) -> Weight { - (74_349_000 as Weight) - // Standard Error: 2_000 - .saturating_add((881_000 as Weight).saturating_mul(r as Weight)) + (74_599_000 as Weight) + // Standard Error: 1_000 + .saturating_add((851_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64extendui32(r: u32, ) -> Weight { - (74_426_000 as Weight) - // Standard Error: 1_000 - .saturating_add((875_000 as Weight).saturating_mul(r as Weight)) + (74_622_000 as Weight) + // Standard Error: 2_000 + .saturating_add((854_000 as Weight).saturating_mul(r as Weight)) } fn instr_i32wrapi64(r: u32, ) -> Weight { - (74_172_000 as Weight) + (74_504_000 as Weight) // Standard Error: 2_000 - .saturating_add((906_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((874_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64eq(r: u32, ) -> Weight { - (74_169_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_362_000 as Weight).saturating_mul(r as Weight)) + (74_532_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_338_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64ne(r: u32, ) -> Weight { - (74_205_000 as Weight) - // Standard Error: 4_000 - .saturating_add((1_360_000 as Weight).saturating_mul(r as Weight)) + (74_130_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_347_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64lts(r: u32, ) -> Weight { - (74_237_000 as Weight) - // Standard Error: 3_000 - .saturating_add((1_356_000 as Weight).saturating_mul(r as Weight)) + (74_172_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_343_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64ltu(r: u32, ) -> Weight { - (74_181_000 as Weight) + (74_356_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_357_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_338_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64gts(r: u32, ) -> Weight { - (74_038_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_360_000 as Weight).saturating_mul(r as Weight)) + (74_181_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_343_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64gtu(r: u32, ) -> Weight { - (73_881_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_372_000 as Weight).saturating_mul(r as Weight)) + (74_045_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_358_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64les(r: u32, ) -> Weight { - (73_969_000 as Weight) - // Standard Error: 0 - .saturating_add((1_361_000 as Weight).saturating_mul(r as Weight)) + (74_246_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_341_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64leu(r: u32, ) -> Weight { - (74_497_000 as Weight) - // Standard Error: 3_000 - .saturating_add((1_359_000 as Weight).saturating_mul(r as Weight)) + (73_998_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_363_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64ges(r: u32, ) -> Weight { - (74_275_000 as Weight) + (74_035_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_354_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_348_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64geu(r: u32, ) -> Weight { - (74_349_000 as Weight) - // Standard Error: 3_000 - .saturating_add((1_357_000 as Weight).saturating_mul(r as Weight)) + (74_511_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_335_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64add(r: u32, ) -> Weight { - (74_192_000 as Weight) + (74_305_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_333_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_319_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64sub(r: u32, ) -> Weight { - (74_271_000 as Weight) + (74_214_000 as Weight) // Standard Error: 2_000 - .saturating_add((1_340_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_321_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64mul(r: u32, ) -> Weight { - (73_971_000 as Weight) + (74_073_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_340_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_323_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64divs(r: u32, ) -> Weight { - (74_546_000 as Weight) + (74_295_000 as Weight) // Standard Error: 2_000 - .saturating_add((1_995_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_986_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64divu(r: u32, ) -> Weight { - (74_194_000 as Weight) + (74_109_000 as Weight) // Standard Error: 2_000 - .saturating_add((2_050_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((2_023_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64rems(r: u32, ) -> Weight { - (74_106_000 as Weight) + (73_990_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_997_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_993_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64remu(r: u32, ) -> Weight { - (74_219_000 as Weight) - // Standard Error: 5_000 - .saturating_add((2_061_000 as Weight).saturating_mul(r as Weight)) + (73_940_000 as Weight) + // Standard Error: 1_000 + .saturating_add((2_056_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64and(r: u32, ) -> Weight { - (74_157_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_338_000 as Weight).saturating_mul(r as Weight)) + (74_261_000 as Weight) + // Standard Error: 5_000 + .saturating_add((1_326_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64or(r: u32, ) -> Weight { - (74_135_000 as Weight) + (74_014_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_336_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_324_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64xor(r: u32, ) -> Weight { - (74_038_000 as Weight) - // Standard Error: 3_000 - .saturating_add((1_345_000 as Weight).saturating_mul(r as Weight)) + (74_220_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_323_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64shl(r: u32, ) -> Weight { - (74_011_000 as Weight) - // Standard Error: 3_000 - .saturating_add((1_362_000 as Weight).saturating_mul(r as Weight)) + (74_139_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_343_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64shrs(r: u32, ) -> Weight { - (74_054_000 as Weight) + (73_923_000 as Weight) // Standard Error: 2_000 .saturating_add((1_356_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64shru(r: u32, ) -> Weight { - (73_900_000 as Weight) + (73_999_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_358_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_352_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64rotl(r: u32, ) -> Weight { - (73_948_000 as Weight) - // Standard Error: 0 - .saturating_add((1_357_000 as Weight).saturating_mul(r as Weight)) + (74_312_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_340_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64rotr(r: u32, ) -> Weight { - (73_972_000 as Weight) + (73_988_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_358_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_349_000 as Weight).saturating_mul(r as Weight)) } } From 60fea6dc840250d12180412cd449db7de50cc7e4 Mon Sep 17 00:00:00 2001 From: Koute Date: Thu, 19 May 2022 16:32:53 +0900 Subject: [PATCH 246/484] Switch to pooling copy-on-write instantiation strategy for WASM (#11232) * Switch to pooling copy-on-write instantiation strategy for WASM * Fix benchmark compilation * Fix `cargo fmt` * Fix compilation of another benchmark I've missed * Cleanups according to review comments * Move `max_memory_size` to `Semantics` * Set `memory_guaranteed_dense_image_size` to `max_memory_size` * Rename `wasm_instantiation_strategy` to `wasmtime_instantiation_strategy` * Update the doc-comments regarding the instantiation strategy * Extend the integration tests to test every instantiation strategy * Don't drop the temporary directory until the runtime is dropped in benchmarks * Don't drop the temporary directory until the runtime is dropped in tests --- Cargo.lock | 13 + bin/node/cli/benches/block_production.rs | 6 +- bin/node/executor/benches/bench.rs | 6 +- bin/node/testing/src/bench.rs | 11 +- client/cli/src/arg_enums.rs | 69 ++++- client/cli/src/params/import_params.rs | 32 +- client/executor/Cargo.toml | 2 + client/executor/benches/bench.rs | 265 +++++++++++++---- client/executor/benches/kusama_runtime.wasm | Bin 5554150 -> 7365767 bytes .../executor/src/integration_tests/linux.rs | 8 +- client/executor/src/integration_tests/mod.rs | 124 +++++++- client/executor/src/lib.rs | 3 + client/executor/src/wasm_runtime.rs | 39 ++- client/executor/wasmtime/Cargo.toml | 3 + client/executor/wasmtime/src/lib.rs | 2 +- client/executor/wasmtime/src/runtime.rs | 279 ++++++++++++------ client/executor/wasmtime/src/tests.rs | 61 ++-- client/service/src/config.rs | 2 + .../benchmarking-cli/src/pallet/command.rs | 7 +- .../frame/benchmarking-cli/src/pallet/mod.rs | 16 +- utils/frame/try-runtime/cli/src/lib.rs | 18 +- 21 files changed, 728 insertions(+), 238 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ff14da370db9c..03bc44a0a848a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4173,6 +4173,15 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +[[package]] +name = "memfd" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6627dc657574b49d6ad27105ed671822be56e0d2547d413bfbf3e8d8fa92e7a" +dependencies = [ + "libc", +] + [[package]] name = "memmap" version = "0.7.0" @@ -8197,6 +8206,7 @@ dependencies = [ "hex-literal", "lazy_static", "lru", + "num_cpus", "parity-scale-codec", "parking_lot 0.12.0", "paste 1.0.6", @@ -8221,6 +8231,7 @@ dependencies = [ "sp-version", "sp-wasm-interface", "substrate-test-runtime", + "tempfile", "tracing", "tracing-subscriber", "wasmi", @@ -8274,6 +8285,7 @@ dependencies = [ "sp-runtime-interface", "sp-sandbox", "sp-wasm-interface", + "tempfile", "wasmtime", "wat", ] @@ -11838,6 +11850,7 @@ dependencies = [ "libc", "log", "mach", + "memfd", "memoffset", "more-asserts", "rand 0.8.4", diff --git a/bin/node/cli/benches/block_production.rs b/bin/node/cli/benches/block_production.rs index 376241d8157bf..ad16ba8e4072b 100644 --- a/bin/node/cli/benches/block_production.rs +++ b/bin/node/cli/benches/block_production.rs @@ -29,7 +29,7 @@ use sc_consensus::{ use sc_service::{ config::{ DatabaseSource, KeepBlocks, KeystoreConfig, NetworkConfiguration, OffchainWorkerConfig, - PruningMode, WasmExecutionMethod, + PruningMode, WasmExecutionMethod, WasmtimeInstantiationStrategy, }, BasePath, Configuration, Role, }; @@ -77,7 +77,9 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase { state_pruning: Some(PruningMode::ArchiveAll), keep_blocks: KeepBlocks::All, chain_spec: spec, - wasm_method: WasmExecutionMethod::Compiled, + wasm_method: WasmExecutionMethod::Compiled { + instantiation_strategy: WasmtimeInstantiationStrategy::PoolingCopyOnWrite, + }, execution_strategies: ExecutionStrategies { syncing: execution_strategy, importing: execution_strategy, diff --git a/bin/node/executor/benches/bench.rs b/bin/node/executor/benches/bench.rs index 3d7c264a89d1c..61e2d1b053012 100644 --- a/bin/node/executor/benches/bench.rs +++ b/bin/node/executor/benches/bench.rs @@ -25,6 +25,8 @@ use node_runtime::{ UncheckedExtrinsic, }; use node_testing::keyring::*; +#[cfg(feature = "wasmtime")] +use sc_executor::WasmtimeInstantiationStrategy; use sc_executor::{Externalities, NativeElseWasmExecutor, RuntimeVersionOf, WasmExecutionMethod}; use sp_core::{ storage::well_known_keys, @@ -183,7 +185,9 @@ fn bench_execute_block(c: &mut Criterion) { ExecutionMethod::Native, ExecutionMethod::Wasm(WasmExecutionMethod::Interpreted), #[cfg(feature = "wasmtime")] - ExecutionMethod::Wasm(WasmExecutionMethod::Compiled), + ExecutionMethod::Wasm(WasmExecutionMethod::Compiled { + instantiation_strategy: WasmtimeInstantiationStrategy::PoolingCopyOnWrite, + }), ]; for strategy in execution_methods { diff --git a/bin/node/testing/src/bench.rs b/bin/node/testing/src/bench.rs index e5287dc3c4af2..00ce7f64bc3f0 100644 --- a/bin/node/testing/src/bench.rs +++ b/bin/node/testing/src/bench.rs @@ -46,7 +46,7 @@ use sc_client_api::{ }; use sc_client_db::PruningMode; use sc_consensus::{BlockImport, BlockImportParams, ForkChoiceStrategy, ImportResult, ImportedAux}; -use sc_executor::{NativeElseWasmExecutor, WasmExecutionMethod}; +use sc_executor::{NativeElseWasmExecutor, WasmExecutionMethod, WasmtimeInstantiationStrategy}; use sp_api::ProvideRuntimeApi; use sp_block_builder::BlockBuilder; use sp_consensus::BlockOrigin; @@ -398,7 +398,14 @@ impl BenchDb { let backend = sc_service::new_db_backend(db_config).expect("Should not fail"); let client = sc_service::new_client( backend.clone(), - NativeElseWasmExecutor::new(WasmExecutionMethod::Compiled, None, 8, 2), + NativeElseWasmExecutor::new( + WasmExecutionMethod::Compiled { + instantiation_strategy: WasmtimeInstantiationStrategy::PoolingCopyOnWrite, + }, + None, + 8, + 2, + ), &keyring.generate_genesis(), None, None, diff --git a/client/cli/src/arg_enums.rs b/client/cli/src/arg_enums.rs index ac50413803278..bc0989cf34659 100644 --- a/client/cli/src/arg_enums.rs +++ b/client/cli/src/arg_enums.rs @@ -20,6 +20,36 @@ use clap::ArgEnum; +/// The instantiation strategy to use in compiled mode. +#[derive(Debug, Clone, Copy, ArgEnum)] +#[clap(rename_all = "kebab-case")] +pub enum WasmtimeInstantiationStrategy { + /// Pool the instances to avoid initializing everything from scratch + /// on each instantiation. Use copy-on-write memory when possible. + PoolingCopyOnWrite, + + /// Recreate the instance from scratch on every instantiation. + /// Use copy-on-write memory when possible. + RecreateInstanceCopyOnWrite, + + /// Pool the instances to avoid initializing everything from scratch + /// on each instantiation. + Pooling, + + /// Recreate the instance from scratch on every instantiation. Very slow. + RecreateInstance, + + /// Legacy instance reuse mechanism. DEPRECATED. Will be removed in the future. + /// + /// Should only be used in case of encountering any issues with the new default + /// instantiation strategy. + LegacyInstanceReuse, +} + +/// The default [`WasmtimeInstantiationStrategy`]. +pub const DEFAULT_WASMTIME_INSTANTIATION_STRATEGY: WasmtimeInstantiationStrategy = + WasmtimeInstantiationStrategy::PoolingCopyOnWrite; + /// How to execute Wasm runtime code. #[derive(Debug, Clone, Copy)] pub enum WasmExecutionMethod { @@ -71,18 +101,33 @@ impl WasmExecutionMethod { } } -impl Into for WasmExecutionMethod { - fn into(self) -> sc_service::config::WasmExecutionMethod { - match self { - WasmExecutionMethod::Interpreted => - sc_service::config::WasmExecutionMethod::Interpreted, - #[cfg(feature = "wasmtime")] - WasmExecutionMethod::Compiled => sc_service::config::WasmExecutionMethod::Compiled, - #[cfg(not(feature = "wasmtime"))] - WasmExecutionMethod::Compiled => panic!( - "Substrate must be compiled with \"wasmtime\" feature for compiled Wasm execution" - ), - } +/// Converts the execution method and instantiation strategy command line arguments +/// into an execution method which can be used internally. +pub fn execution_method_from_cli( + execution_method: WasmExecutionMethod, + _instantiation_strategy: WasmtimeInstantiationStrategy, +) -> sc_service::config::WasmExecutionMethod { + match execution_method { + WasmExecutionMethod::Interpreted => sc_service::config::WasmExecutionMethod::Interpreted, + #[cfg(feature = "wasmtime")] + WasmExecutionMethod::Compiled => sc_service::config::WasmExecutionMethod::Compiled { + instantiation_strategy: match _instantiation_strategy { + WasmtimeInstantiationStrategy::PoolingCopyOnWrite => + sc_service::config::WasmtimeInstantiationStrategy::PoolingCopyOnWrite, + WasmtimeInstantiationStrategy::RecreateInstanceCopyOnWrite => + sc_service::config::WasmtimeInstantiationStrategy::RecreateInstanceCopyOnWrite, + WasmtimeInstantiationStrategy::Pooling => + sc_service::config::WasmtimeInstantiationStrategy::Pooling, + WasmtimeInstantiationStrategy::RecreateInstance => + sc_service::config::WasmtimeInstantiationStrategy::RecreateInstance, + WasmtimeInstantiationStrategy::LegacyInstanceReuse => + sc_service::config::WasmtimeInstantiationStrategy::LegacyInstanceReuse, + }, + }, + #[cfg(not(feature = "wasmtime"))] + WasmExecutionMethod::Compiled => panic!( + "Substrate must be compiled with \"wasmtime\" feature for compiled Wasm execution" + ), } } diff --git a/client/cli/src/params/import_params.rs b/client/cli/src/params/import_params.rs index 4c9b334150557..aef7511ffc371 100644 --- a/client/cli/src/params/import_params.rs +++ b/client/cli/src/params/import_params.rs @@ -18,10 +18,11 @@ use crate::{ arg_enums::{ - ExecutionStrategy, WasmExecutionMethod, DEFAULT_EXECUTION_BLOCK_CONSTRUCTION, - DEFAULT_EXECUTION_IMPORT_BLOCK, DEFAULT_EXECUTION_IMPORT_BLOCK_VALIDATOR, - DEFAULT_EXECUTION_OFFCHAIN_WORKER, DEFAULT_EXECUTION_OTHER, DEFAULT_EXECUTION_SYNCING, - DEFAULT_WASM_EXECUTION_METHOD, + ExecutionStrategy, WasmExecutionMethod, WasmtimeInstantiationStrategy, + DEFAULT_EXECUTION_BLOCK_CONSTRUCTION, DEFAULT_EXECUTION_IMPORT_BLOCK, + DEFAULT_EXECUTION_IMPORT_BLOCK_VALIDATOR, DEFAULT_EXECUTION_OFFCHAIN_WORKER, + DEFAULT_EXECUTION_OTHER, DEFAULT_EXECUTION_SYNCING, + DEFAULT_WASMTIME_INSTANTIATION_STRATEGY, DEFAULT_WASM_EXECUTION_METHOD, }, params::{DatabaseParams, PruningParams}, }; @@ -62,6 +63,27 @@ pub struct ImportParams { )] pub wasm_method: WasmExecutionMethod, + /// The WASM instantiation method to use. + /// + /// Only has an effect when `wasm-execution` is set to `compiled`. + /// + /// The copy-on-write strategies are only supported on Linux. + /// If the copy-on-write variant of a strategy is unsupported + /// the executor will fall back to the non-CoW equivalent. + /// + /// The fastest (and the default) strategy available is `pooling-copy-on-write`. + /// + /// The `legacy-instance-reuse` strategy is deprecated and will + /// be removed in the future. It should only be used in case of + /// issues with the default instantiation strategy. + #[clap( + long, + value_name = "STRATEGY", + default_value_t = DEFAULT_WASMTIME_INSTANTIATION_STRATEGY, + arg_enum, + )] + pub wasmtime_instantiation_strategy: WasmtimeInstantiationStrategy, + /// Specify the path where local WASM runtimes are stored. /// /// These runtimes will override on-chain runtimes when the version matches. @@ -85,7 +107,7 @@ impl ImportParams { /// Get the WASM execution method from the parameters pub fn wasm_method(&self) -> sc_service::config::WasmExecutionMethod { - self.wasm_method.into() + crate::execution_method_from_cli(self.wasm_method, self.wasmtime_instantiation_strategy) } /// Enable overriding on-chain WASM with locally-stored WASM diff --git a/client/executor/Cargo.toml b/client/executor/Cargo.toml index ad2288b9272d3..566ed0a50fc0f 100644 --- a/client/executor/Cargo.toml +++ b/client/executor/Cargo.toml @@ -50,6 +50,8 @@ paste = "1.0" regex = "1.5.5" criterion = "0.3" env_logger = "0.9" +num_cpus = "1.13.1" +tempfile = "3.3.0" [[bench]] name = "bench" diff --git a/client/executor/benches/bench.rs b/client/executor/benches/bench.rs index 49ea8be50624e..fcefe408603d7 100644 --- a/client/executor/benches/bench.rs +++ b/client/executor/benches/bench.rs @@ -17,26 +17,43 @@ use criterion::{criterion_group, criterion_main, Criterion}; -use sc_executor_common::{runtime_blob::RuntimeBlob, wasm_runtime::WasmModule}; +use codec::Encode; + +use sc_executor_common::{ + runtime_blob::RuntimeBlob, + wasm_runtime::{WasmInstance, WasmModule}, +}; +#[cfg(feature = "wasmtime")] +use sc_executor_wasmtime::InstantiationStrategy; use sc_runtime_test::wasm_binary_unwrap as test_runtime; use sp_wasm_interface::HostFunctions as _; -use std::sync::Arc; +use std::sync::{ + atomic::{AtomicBool, AtomicUsize, Ordering}, + Arc, +}; +#[derive(Clone)] enum Method { Interpreted, #[cfg(feature = "wasmtime")] Compiled { - fast_instance_reuse: bool, + instantiation_strategy: InstantiationStrategy, + precompile: bool, }, } -// This is just a bog-standard Kusama runtime with the extra `test_empty_return` -// function copy-pasted from the test runtime. +// This is just a bog-standard Kusama runtime with an extra +// `test_empty_return` and `test_dirty_plenty_memory` functions +// copy-pasted from the test runtime. fn kusama_runtime() -> &'static [u8] { include_bytes!("kusama_runtime.wasm") } -fn initialize(runtime: &[u8], method: Method) -> Arc { +fn initialize( + _tmpdir: &mut Option, + runtime: &[u8], + method: Method, +) -> Arc { let blob = RuntimeBlob::uncompress_if_needed(runtime).unwrap(); let host_functions = sp_io::SubstrateHostFunctions::host_functions(); let heap_pages = 2048; @@ -51,80 +68,200 @@ fn initialize(runtime: &[u8], method: Method) -> Arc { ) .map(|runtime| -> Arc { Arc::new(runtime) }), #[cfg(feature = "wasmtime")] - Method::Compiled { fast_instance_reuse } => - sc_executor_wasmtime::create_runtime::( - blob, - sc_executor_wasmtime::Config { + Method::Compiled { instantiation_strategy, precompile } => { + let config = sc_executor_wasmtime::Config { + allow_missing_func_imports, + cache_path: None, + semantics: sc_executor_wasmtime::Semantics { + extra_heap_pages: heap_pages, + instantiation_strategy, + deterministic_stack_limit: None, + canonicalize_nans: false, + parallel_compilation: true, max_memory_size: None, - allow_missing_func_imports, - cache_path: None, - semantics: sc_executor_wasmtime::Semantics { - extra_heap_pages: heap_pages, - fast_instance_reuse, - deterministic_stack_limit: None, - canonicalize_nans: false, - parallel_compilation: true, - }, }, - ) - .map(|runtime| -> Arc { Arc::new(runtime) }), + }; + + if precompile { + let precompiled_blob = + sc_executor_wasmtime::prepare_runtime_artifact(blob, &config.semantics) + .unwrap(); + + // Create a fresh temporary directory to make absolutely sure + // we'll use the right module. + *_tmpdir = Some(tempfile::tempdir().unwrap()); + let tmpdir = _tmpdir.as_ref().unwrap(); + + let path = tmpdir.path().join("module.bin"); + std::fs::write(&path, &precompiled_blob).unwrap(); + unsafe { + sc_executor_wasmtime::create_runtime_from_artifact::< + sp_io::SubstrateHostFunctions, + >(&path, config) + } + } else { + sc_executor_wasmtime::create_runtime::(blob, config) + } + .map(|runtime| -> Arc { Arc::new(runtime) }) + }, } .unwrap() } +fn run_benchmark( + c: &mut Criterion, + benchmark_name: &str, + thread_count: usize, + runtime: &dyn WasmModule, + testcase: impl Fn(&mut Box) + Copy + Send + 'static, +) { + c.bench_function(benchmark_name, |b| { + // Here we deliberately start a bunch of extra threads which will just + // keep on independently instantiating the runtime over and over again. + // + // We don't really have to measure how much time those take since the + // work done is essentially the same on each thread, and what we're + // interested in here is only how those extra threads affect the execution + // on the current thread. + // + // In an ideal case assuming we have enough CPU cores those extra threads + // shouldn't affect the main thread's runtime at all, however in practice + // they're not completely independent. There might be per-process + // locks in the kernel which are briefly held during instantiation, etc., + // and how much those affect the execution here is what we want to measure. + let is_benchmark_running = Arc::new(AtomicBool::new(true)); + let threads_running = Arc::new(AtomicUsize::new(0)); + let aux_threads: Vec<_> = (0..thread_count - 1) + .map(|_| { + let mut instance = runtime.new_instance().unwrap(); + let is_benchmark_running = is_benchmark_running.clone(); + let threads_running = threads_running.clone(); + std::thread::spawn(move || { + threads_running.fetch_add(1, Ordering::SeqCst); + while is_benchmark_running.load(Ordering::Relaxed) { + testcase(&mut instance); + } + }) + }) + .collect(); + + while threads_running.load(Ordering::SeqCst) != (thread_count - 1) { + std::thread::yield_now(); + } + + let mut instance = runtime.new_instance().unwrap(); + b.iter(|| testcase(&mut instance)); + + is_benchmark_running.store(false, Ordering::SeqCst); + for thread in aux_threads { + thread.join().unwrap(); + } + }); +} + fn bench_call_instance(c: &mut Criterion) { let _ = env_logger::try_init(); - #[cfg(feature = "wasmtime")] - { - let runtime = initialize(test_runtime(), Method::Compiled { fast_instance_reuse: true }); - c.bench_function("call_instance_test_runtime_with_fast_instance_reuse", |b| { - let mut instance = runtime.new_instance().unwrap(); - b.iter(|| instance.call_export("test_empty_return", &[0]).unwrap()) - }); - } + let strategies = [ + #[cfg(feature = "wasmtime")] + ( + "legacy_instance_reuse", + Method::Compiled { + instantiation_strategy: InstantiationStrategy::LegacyInstanceReuse, + precompile: false, + }, + ), + #[cfg(feature = "wasmtime")] + ( + "recreate_instance_vanilla", + Method::Compiled { + instantiation_strategy: InstantiationStrategy::RecreateInstance, + precompile: false, + }, + ), + #[cfg(feature = "wasmtime")] + ( + "recreate_instance_cow_fresh", + Method::Compiled { + instantiation_strategy: InstantiationStrategy::RecreateInstanceCopyOnWrite, + precompile: false, + }, + ), + #[cfg(feature = "wasmtime")] + ( + "recreate_instance_cow_precompiled", + Method::Compiled { + instantiation_strategy: InstantiationStrategy::RecreateInstanceCopyOnWrite, + precompile: true, + }, + ), + #[cfg(feature = "wasmtime")] + ( + "pooling_vanilla", + Method::Compiled { + instantiation_strategy: InstantiationStrategy::Pooling, + precompile: false, + }, + ), + #[cfg(feature = "wasmtime")] + ( + "pooling_cow_fresh", + Method::Compiled { + instantiation_strategy: InstantiationStrategy::PoolingCopyOnWrite, + precompile: false, + }, + ), + #[cfg(feature = "wasmtime")] + ( + "pooling_cow_precompiled", + Method::Compiled { + instantiation_strategy: InstantiationStrategy::PoolingCopyOnWrite, + precompile: true, + }, + ), + ("interpreted", Method::Interpreted), + ]; - #[cfg(feature = "wasmtime")] - { - let runtime = initialize(test_runtime(), Method::Compiled { fast_instance_reuse: false }); - c.bench_function("call_instance_test_runtime_without_fast_instance_reuse", |b| { - let mut instance = runtime.new_instance().unwrap(); - b.iter(|| instance.call_export("test_empty_return", &[0]).unwrap()); - }); - } + let runtimes = [("kusama_runtime", kusama_runtime()), ("test_runtime", test_runtime())]; - #[cfg(feature = "wasmtime")] - { - let runtime = initialize(kusama_runtime(), Method::Compiled { fast_instance_reuse: true }); - c.bench_function("call_instance_kusama_runtime_with_fast_instance_reuse", |b| { - let mut instance = runtime.new_instance().unwrap(); - b.iter(|| instance.call_export("test_empty_return", &[0]).unwrap()) - }); - } + let thread_counts = [1, 2, 4, 8, 16]; - #[cfg(feature = "wasmtime")] - { - let runtime = initialize(kusama_runtime(), Method::Compiled { fast_instance_reuse: false }); - c.bench_function("call_instance_kusama_runtime_without_fast_instance_reuse", |b| { - let mut instance = runtime.new_instance().unwrap(); - b.iter(|| instance.call_export("test_empty_return", &[0]).unwrap()); - }); + fn test_call_empty_function(instance: &mut Box) { + instance.call_export("test_empty_return", &[0]).unwrap(); } - { - let runtime = initialize(test_runtime(), Method::Interpreted); - c.bench_function("call_instance_test_runtime_interpreted", |b| { - let mut instance = runtime.new_instance().unwrap(); - b.iter(|| instance.call_export("test_empty_return", &[0]).unwrap()) - }); + fn test_dirty_1mb_of_memory(instance: &mut Box) { + instance.call_export("test_dirty_plenty_memory", &(0, 16).encode()).unwrap(); } - { - let runtime = initialize(kusama_runtime(), Method::Interpreted); - c.bench_function("call_instance_kusama_runtime_interpreted", |b| { - let mut instance = runtime.new_instance().unwrap(); - b.iter(|| instance.call_export("test_empty_return", &[0]).unwrap()) - }); + let testcases = [ + ("call_empty_function", test_call_empty_function as fn(&mut Box)), + ("dirty_1mb_of_memory", test_dirty_1mb_of_memory), + ]; + + let num_cpus = num_cpus::get_physical(); + let mut tmpdir = None; + + for (strategy_name, strategy) in strategies { + for (runtime_name, runtime) in runtimes { + let runtime = initialize(&mut tmpdir, runtime, strategy.clone()); + + for (testcase_name, testcase) in testcases { + for thread_count in thread_counts { + if thread_count > num_cpus { + // If there are not enough cores available the benchmark is pointless. + continue + } + + let benchmark_name = format!( + "{}_from_{}_with_{}_on_{}_threads", + testcase_name, runtime_name, strategy_name, thread_count + ); + + run_benchmark(c, &benchmark_name, thread_count, &*runtime, testcase); + } + } + } } } diff --git a/client/executor/benches/kusama_runtime.wasm b/client/executor/benches/kusama_runtime.wasm index 3470237fb5aeea2fc21c9efc5a214b0754f3afd6..28adce962340037a0dca62724add224218c50b66 100755 GIT binary patch literal 7365767 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 zμ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[)KHDA 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

;|yl9p^?{cx{jFz*{=H1E&=|keM%=s_UR$( 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

I0%%1?tE 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

6qrq9yfh73kB)lN+ec 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%0Ay+$#vj9XOpHGUq$b86IHAEL zQX{%Fhv0P%!TqU-Xg7>zNdfY^KmQqa)ecQ>Gc>T2#ds^p*%-#u)@LUeg-UG1Iv5k+ zZL#$YV-`1-7M%?+!I$6jfc8hQw=D_lJ6;51J~PLm{Kk7f&f31J`obE&5n~EG<8e`6 z>tIXDqAlqghbNg2mx-9jEli20Qo@wZkRfBe9L*ZJxWVq=RkUj|P~Lehcr@2mZJ>vI z3w4^%j}{Tq&J1lBm%h7QQ^gk}Tmmw#xCH50B}V(iPvR2dvBa;GCP*~UB3?ze;Ij;g zJ|bF9U27`yjH_z}G39rH0IL1wDUKTKB1*FAm9FSn65ZiVJC`_NgW9>o`}H1UrA<%> zGD=Y`aV~Lum3~YEi^tTN0tALh(^%LsvTC-_p{YvU7}C}I(40-m>p>d1iFEZup6IHQ zs0G4$7s?~7eSWH0qO4zq%SfcvQq@9%R>i#s;%WvgCK6R>JVj<`tkT5BQ)HUPs#c}7 zS&zP+Eh2q==S)63#EFf3YoO&1S-uB}F-!16BD9vlWf#{VdSr|x0SZJzaUE>bF?ivN z-Gjojw{U$GCRwpu!P);n{&KHr*%(8-0c3dB1c-OjQ4pEMKk7X~Q7oh4a!dMGy`v@d zcix1?QSZRPg?E(wMPJ{7FcDh8(?RchX7uG&55>CP_!o#6@r^~jp{`hWfYAlazp$@! zfqgCKU|r4cc~)Gbd1=pzYb?moLDQO}torBFV&kfISc}DSYZ2MVxW?~ytpzbMOat%U zoZj8$@4ljie11zgd$WBJ_+7mj`CW58?TO#btcIIX4U+1XOVS(ZG^y=%e%ERf_-#yd5-U$1HTeQOycOh}pYoXdJQ z%Cg#&v?u4vu~QPpL|}=o9ATiNu-K*x`~Yn|egH?|=8*M)nKgGPe&Kyb=oyQ!l1|ci zaH?M6p2+Uzhd0dd8rN3++|P*TwQMSNp0_I1m4-AvrdMERONvg6pC|*!G2O!mj_4h% z(rJ1Jmn;l-yawPkfY{PIJh$`?54_{EK+CJD`yS*sa?!v_;-Xpcbp4U;Fn7x+?Kpi& z+95OiW@v{{dZZm>k!aA4n3e-VsfhWAft`-HFJhz|9pw5URgh%g!1-jX63z$l!*j6N zW+Tq;33KXnD*B{!`-BGv@@sixzdl00qCH7Yl$8-}JM) z|0^ofs_7Opg#;|jQG#fJk>%f`mqI;2#J;@k;-(Zl2wVEd3?&ovcf!$oO#wasZ>Jt+ zocNj0y!M*(Ob+J8LpC9qXAGwL{HnMaGjBAXZ)xya(%M2$O zmb<1w`En8Ee=~FP^Bz&cj>7kACM7b0oMfbi@e<+6+$8L*3kKU^UcK*@z6!Qp`ljXl zHA<{#)?su$!8$DexDiY#_c$*~m{Q(xUX(DUP9%lBLC}8!uD^DLPgv4T;yptyzZoT^{8*eGsO#QGXa1&3vRzz0IbG zlR=b?DgL-X`ZL*(r$LKo;*F((#v_Lz&AF zf}C{9c@X4OjSF&AVS-#|ePgu=a)2^5xFDw*T#(Bo$Yo7I&T1PoRwM7qSZyMx8>?Le zRb4KEGUCplX{^TC|Df7EkpBQw)wYy$;|F2-ucKT>YO(fyB&(%2c&|>fmDB1$ODy9; zfxSSF2l&n#xbf+};t(epYAIZ3;?4f5J{JEdu0$J2@5_lm20?u>;23#wY6NYW-rz7% zfvNT(uT`1(o`C{#!RENRTxTYN5)Q>TTo^UEbnMs-`~Xd;8S3`Q3O7-=kM((?ZvR3n z>Gs(cQMWJ0YCl0#>{l?FGo4AZe~k6fWWR}@pxD>+Yulqq7`2)+ap(vpn$2xa)}baR zk{a<6XT$%Ri33=?drPe{6XU#fHazi8%zTDaEBGWCw;z%6-DQ>*@)n9&$Jg@{*6|uH zS;s@Hn6G0r&t%fB;~r`uRCVhNkctAywZUWfmhHn@SYVA7XY4txB~`Jw3!M#rah>$I z?>)u_6dKdMlXvd6Sk=bbt$VVRdOkv@c@fHJ3eWNb;KSeiKrIB9c1U{ zC%luZ;{N@22-(olwrn%P73`ewhBL_bopU7cTM0urM}r~GCJCYfB%k;tw`s4_b>4~ zN$0CsOhVZIoMlnd(64D4;7I=Ksus|eP-`cU>;JlQDYmR44HNs)kNSd7)uP3uP%gZW zPP8t^k*zfPBS$t-T&*TcaUj2i8#b;E>S8gC5$}IpCt=!NVf79L;YO6}TnqrEuKcjT zX`B+>>XhhBdOD^O9kDP|<&X&BS3Z#ltxX|9_za`cGZ;!F=xPmtfD{3AB0;#4CCmjm z3gkXwE}ZOyxpt3XE}M|35Upb_)Ih4Ege#n~j>Ql=&EGdWlxM^U=3ybFb?EKG~=rS-31m{o6-aVod{FPcO)vb6TV~%c^M;&v?-N=6C&PK znPcPvX>GYxE)~_(17(t-WG^2XsF^P(>CqEfFrXN-j^okLU`}(TES`WDl$3T7bDGO7 z-w}h!Z1GSykNOLC*T~bL(odM-SVqgnoYGxqd47Nr*5WnEr85SGTNl9_#Z*&vP)B8>HVi5!l=E+jDH1NTg!5vmf z%u|X=DcGaNJPo&MMKyI+3GIj=zRoHE((PrB;xz6kvPuSf_2sFq))bC*X_Ps-T1%mx zFul(nZ*)cEjdo3S6>acFVc&*vTK8%krSU@WB1{eX4(El;o;LwRg=m~S0W(iMzP2J> z=&?WKH}OKdhA_jNCHQ%u7{ZVINfVWGcMv90?jV}AGH^bV5Se+@aVU?;$}A?hUdT*v z2P$U-MsHuyY7JVPrAutS5M)X~VINuyqjJr%r@M}Yfd>0ZX}mudgMmT5;zg- zNZuVk#>x;v#5(%}JHlx{5b_)fsjZ$zqyv#nNauAF(^2e+5$9mb7tUGMV8&Py^$}aw zxG)7j1};X!YI4oQl?7b}&Z9N|pQh?qKiJ&sn+V!@ubnA5X0EErNgGRwn#*PGA&g#W z(|ilVVxfsV^!XNseJ(iHREf(`Zvt%(dfQ?hy)E zhfGc~<3iV^w=GPzGlkbB^|lt_q-ts7w}#@-ruD9my}xD_0~it-f?}lW&(xfJPuBw1 zP3^a72?{M-KS@yfT6mWKgOm>B_erE;Wmg58C(S8$l}O?Laso6|74GK?>n1;6J4VgI ziA6^2Z7xLA3rLHUfhUn_j!RPp3s6)K;5++d6uZirBd}DnC7yQ@M7oi*R=7yjlZkdjBKVBsQ34CpihPQ9h0}k>9ibCM9}J?i2iq&N)aH}(SEi^HhU~H4hUd>b8n@Zz2@XAD#C&qf1@lq ztP%P*BaJXg;_&4fdlNHe&md$T2Ws3BI7HebGao5igYn4yRh{~#W8}!y;9!|oe_~Hh z`62Et5iM9y{@$Aoc{d)ElHsxM&J5@2=y!d!W?v1Dqz^u}cMPkMp6LH1)>o9PzzsJH z#h^Nu2w%NY4MR&`In<4*7z${`%W>2A+hr)WJOP$d!74EY6_uc%@-60X!-iNT$b$7^ zwvH924axcu{eG}`Q**!67APKjeT&xGgy@@w+r~KGG5x!o!8DS%ZF1Y>5 zO)eA@nT?gWZSv}F199klgWWg=6sB$S5I5R3r}P`P&8=LrYu+T7OKN!By2JU`^c0EO zS&oKVSV;G-(bhR>2|66o-s7hm@Y25GO$S>$C?JiKFqA{b2NIOEs{ct7&uFz8EpS=& z4^_Z0Q^7tJAkw6QB^7|?Qo*;Vfa&mp{IJNAX^^!S$9u{6la*kt-BFgc?DqDXlkZT& zi&IE7>woNNZ*AoCBY5qLkl5BX_Lgoh+s^LzPVQ7Uy|L;Gy$8{ojT;zx)WNV#eG=e^ zds>=3;%Bj|&V0xUo2DTGChR5+NV~It2$+V%9Wc#R&b<^RH6E(NC5dGxpmP&cC&J~q zR)R~`IEvm+=REegU{28aql5Wy9(%M3&DS%NP33(r2?@>Rx~Oc~gGa4q_KI?NOkE z*yYl$+(z+$_StYPGzYW=!tmdUVHkYl?8YGf^hcZ*EH#gbL+r$GAVpn7rtgVCPh{ue=>3o*&jf)kD|<-why*$>VS{3OR-fW7;x~)#S!z{JB&Xv=dZw zyjDfM+@$z(_^e>7?<)7+7FPufDIv4%Q18@JtO46^?;p#&( z!zjh`Pum1U#NgGUJD7B0E`y-}BX(Mt>a193QT#$#();gwH&74-m{+tOs0lE_?;s(2 zn+p?z#H_0v7R}2OkLKm@XkMO}G%q*9lQ^QprFl6}?C&Qw&C9_PUY_{0=vPrxAAtdo zl|y|o^Af)wZI)ScpRAP~RDFmp%dROjDeO)17S~nlLBuPpLmVgOSk|0kz?4W{hXaGd z)Ox}0gROrA%-F>E426l&=?gUCvm*{g5?f^xqOvq0MvLOCr1xwh^j?~XAm*toO+u^s zF15j^y{M%oK52_-Gm$u@e;dnuzU@5`X*t0795mTo;gpn^;qw9&&@m@VZ$LcMszyH7 zh1qdFx0u4`ns6Cye305G9*WPkw66{E@EHB7^SLG-!sjxXuFc0VX?*UOR?T1uVl=K+ zlci7lWMm{y-Ym0(ZV<_As-cCHu~V^i8BY`}VF$}Gc! z`1qlOZ)GI!lQ9<&q#MgKvO~8hZ|kfe%#VtR{$JR>y0SEhASk%VoYZJSRS{hyJt9kyFm~lL*fIK(f_VrX@KPW_8aRPz47+!O(QtgL3P)Pj4n~+GZj`} zxMke6W9Vc!K`&?+%H>Ff;Q1^^?0py5dlKRoRv%Yz7($Mpo)=U&rhn3C)FIPvB(>n{ zYH0s%p?lOD$7^W+M=64{xt<(#?wKYX?4YCU-t;k{{Z3t$WKK2R>!JPCg!Uio)_r{& zY6Elzk4BxrGnP023pu@>k~qNFe|CmvSHYO1(KBJo22;4NUOEqz1@p6|*=SUxJXBtd z)Uia>5A#qdl0W64s>kII^H8Y<<)QK#JO-5*O)bx89x6QgJP(z}_!p@38a7HF1>5f$gxU$hVswsIevPzqg zXn*w{X-ck8(_0cPg8MU^cWQGI?U|pMBGLX*cPP=;b4i10lr#d~bu)Q)5JI9vyGwp& zAQe*{naK5stXNdHCJcDmB!)Fia_=^YwjE7!Uywv=HU}3dp{e$EU7~#o0%SyzTDVAb zUgbtlRh|Smug6HQ%4__G-ZutBp+1^aW&==J8bHX5uCmnmAcd+db=+7Kb$QrXP;P#BGE4E8 zmZ{)tOnJFpxs<)pCoeJwUtmrq@fGJNpTt+3jfN95Jx0C)(Vsp%I%03s!5~SXd?Iib z=WCn=#uQ^MPEWEo($BO3o3^wT=nq}g9 zRA#lmOk)f7s_ur{rhV(;*9$89uYqdFT+d+(7+{0$3-<|muo^eV z-YjRWT-NA0^R5t2O1{1p+ZJYRRt%Ua($-@vk`L(or>3SOX@sO~$o~`N(K-PsF{6AvzRR|ICE1u`;llx3v+N0RLtB< zCdE3GgEnr2#Zn<&jIZ~~)0?l%73@I%q>{2y-$j0_6C7sSE~>oS))-c;(~KS^HNU0fH8*C{H~6U55?;nCqsQi;+Lv1v)^p8~kPs*bn=*;A(Y zbP0X3I#eMZ>OwU65(2^9xP(qH1m)urdS0DwCl|7lsz?+L2T7ZNr)Ho9x(yz|_n-<- z68d9`9E0;+({C{O-mPo+y%TNN5!e2cyYPDkJvowTQlnb5*UXqOlNE z{vLvN(<+~{fY7&EKq$qY^-d3ce*JwGQQ0uG_%gX%Z&yZ`mGjPsFu^e6M7R(UX7&;h z?v!mmEVO#<2R;4Bf{-*BHU(tjH6D3+uV7PGgB?p+@?si5z!4+d6ovs_Koob6y%GMAT8{k z>O={2T{`ET5n*%2Y_P?2;(QF(!2>OxfbztBj|`2%V3uSQ{^{<}`*4*|hdQe2zrPMs z$?*XDl>^lzFEAc5J1pW87QkmP(}XJp7T06%z+DVlVQB|i$dLwQcHST`wt~?Kf5)uM zv>vORvnVZ%aYbqt(-UInvv?Mw2 zIKOflra9|U9S;LyhD4Y16PcU18NuBIv>Ke_ddzqZac!b(gh-3p3#ddgD@v)mO!}Ab6|L%p}D6DCLt7>snNO=G7@) z35fM!8mlQFk+)&pVfZP`i~DOIpflbZZ=|Lnp-zY zaFdl?c8)&W9e3XD9&zXG{P1M(nCQ4|D1Kr@0qA>3b-($J+cBTdFp%KTlsJMTAZ-5Y z@E^EyN&q7}o^@Hrogcd4K?m)q@=_FnfO;jF9)G9C(LLSNatOWe{OKt+#}bV^JH{7q zOYThwUiLW_q)u{+agzI-kb9%k_XB@5#p!!JXTQ6T1!b*6ZKAo1-uSplB-cFQt52I5 zd&}7m!{7|TG>5TL|RJb$wd#R&T+8WHy8ka)qi4{xTc?A{aMEihpo1F)Xd z0QUNS0sDDqMsf6nBgG1(f7Sj!F*qnEz= z)sGLcOf`*meWjLgS_mx~?-Yq=w^9@8!$8eO?iLQc?U5Qj$jxIV)&*v3!y2X#ha33sG_lZ3^6D|)?4uw4*pP|>jfq#E9$WtZ7~%l(q<06KE4KWPRpyX84K>MyYp-cf=cgaB zm*CXv+K?BBX?3cXxdUMWXZ4v zxfwBR$z>2&D^c&u>UHYZfVG#DW~6KR6Fb$_L1ts#B@z6;FfH9OE1tH(&)4#KJtI7% z*cf?D#Ei%`JtndmBPJdy@kaf#$>KA~&%!vUWrYXW_soY#wsANf8Nj@S&~DxWQ^n{z zfIRR)sR%f?B9_YDqL=w$ZJ$J}*I~A5RJz7&aoodml-Y`D;?=)$Qf5PdPRtguc+-AG_q0lZ&sLV_hDyyNpR8~WYD&r%uwx_Ia;lbjxF5A@&cGL%LcB<_^h*YDpYkAXq zf!JmrWy~>OEs;%VU#rAejwP@wh5RYbr2dcsV?;&HS#nU)d=>VnOB_cYHoI@_2MP?s z{eP-v^iuyfev&V3DrktIk)<_`a?lIj!V(=dLVu8)h^?3?b!EiT9B?_8zIcj*rKwFx ziD*Wb3yo)1ou1UH(>GMI7{1|hTjLu%6SjwET=pB!mX8j0glAmtjL$%|Of#9vm{l&% za`_Zj%uNGjn*5x~q0v1YWdN3B^4$8sP<1 zM%J^_1s)rUmYgChJTB;S*Pyd-(EgfRv1CYoyIJ)2^p zEZGnaAIfot!o}g1J?FD(0a;#_qQxvA%gdZ7u+5Ehcv)I#qa9x6WWnC^^s<`JAvCqB ziyX0jg&RRHaJkDaqjhw~@4!%pY3PLQ@zb{R)JvmM$5WvFSLYVI4DT%5NJI(521s1eo6tL@3iPE`DNBC?NYQFH-r)wd zW8b`w^=SGCU0NydJ;6j7=y?1 zY9>X%9yT0SlC-B}@huldYY9we-Br{7KYMQM4xKf3CsZlyZZy#zDdR81f!&}t(Q8q8>v038fT3<J?mM| z`}3?}*;{QbI%mW6A=U(u(vx8-YD&N=m8T3duA3-fX-)nDyNiBk+dxgFH?k^(J6$Cy z|5OREtXa)J^NRSbG5phWiAVV-MRj8oSgyD@=bw3XyWs&CB>q|3O;t^!=ARFm{c(48 zzdi&1B+QPzXkm8Ic-qMG!mVgM9ZrDNW?0X9N}6u4QG990dYXE>ZHO`{9yd{bVgf9; zp@u;O1lV@)pWzLUm4l2R>=`dO&-G3(IL~vY$(bWvH8Z&9b-pE%jwY5iRW?)Gc%3Sn zlaw)?iB+#*T8$6ZO0_S?$Uv#embm!z+@?Z~*Tw=z2A&<0cebLSCu9Fw4FRe1JHV;q zu*zg%vJ1L4J)MpK@Qj&AM-J(d0ATv~rVPLqc!|mY(MEJ%+eRe16T=xM%K#_G$N+zK zS~9@VX^S4Mivq9pzS)zw;7qrh=dR_vjDo=K>`|3NfTBL)Wf3R@useG|<&-39L+yJ9 zi$7J_Zlhmt>sxiOW%m7+NaDR#RJoAE0DaG+G0OPmVHDRjSxu!unh;N@GYsEI<%;~7b#Zuh z`12uPTJz`oxR9X?<510?pV4xIP1q}j!*Ly>B7g4cw~1;V-i~K@k`I9w^df_Pjt&ko zH^N9hi%$GGl^a2M3vipwgM(Iu^=6Q4Rj++M4-WcDC#-S8!9o9=Lt)dV?D~jh|8&a& zS*3%ZX)USFPqxA?u+xKNx71MynC?P>^{M(;G*c1Kj0!i9xQ;Xn1eejiIP6S2&nz=7 z^~y@(M4wci?DNymh+{YF*93DtOVUYfZwZMny~Oq?BMu$+9zyRo5FN3B^o+k;-&~Jm zbE@cw&Gd|@vAqUp!4US2@B=FnH};qZ(D`{v&p2hR^N)3ngBz=%{}&&S4e2Z~j;?VK z<2TTthW<0rwFjlZRKHn6KR9|3I^d+^9KA|eLk1PJHdYxf8uFfc=LAFEmwMp@j=W(7 zW1^*=eAa0}&lrSgi1W%pUd@U}hye_y_pPoT`P3)BeDBFEZ=fwF#x9mYx$0om9IUz5 zGqBgN_sHT9Y;qwoiYBB^H`8Ilw|jQg4SLab<>&@u9&R~WW_7~HaC2Lf5!p%-dZX2!7P2H1`N>!Ey+PQp4o3*t?WtbKCeaX(;-$=xhi!czB9!Ck zX@qV4su|EslWn6^RcfzohHYW=#YkeIPUxhr3Q&tr`W+bnGBgI|AWRc^UwzQJd7 z%YiGkNe*UKfB#DRWY}-ojh0YrW5|tWq5r$H$8knzJx<6eVeXz2oM7^~cVI0e@Hyy# z7bn}R)SN{s(&A%~t>>8!(M>tHLD02M=d0tV#wjD$K2DR3B|8cWc#cD(%) zCVesZ5azJ6Wd&O2c>Ce7XaiCVa2VF#FaV(@PuAaR26x{~6WxB2)y;x`#v}-Y$7jq0 zi=23%pDI{nIlF2A46k+HJV)hOSPTVZOD!A3rt78A0~}9iewgL#`o0I8c)eLO3bH8X z<$r=tb7yk4AFm?hjEZ>}in_@#fuN4@xBBTR<7>V*dqdQo;MJO(T^T(~h%k`K=%UF>z5LO)3uM9iofY!e&BeU-Hu0_-CMX{8uy)O_X)X}DYU8K96UxbYmWi|?2u;S*7$ya)5Mm$zV8#`w!aZs0w0DLz zh4Vtby@5dIy%?yyK#agf2UOpx-!KG^G3UKAD0YXmFUu%bT$@jg#cakIXInS1m;z2S z(m1b@d4V;!J;EfRG0hZNFH1m6#;w#P#RF1w`vT3-zzYr!)ZF8wFhP1uH7#$Www%zO#@t8?t*8{ z02I?jAN7bJSBru02$#XB=l0LCs@{vOiHMEH(srekn*5+a|(>tRu zxFeHXU7JkL_R6;c(|d4I7~Fc5Sk9b!y(mu~#qc_jenBz6btF9!{<|j)VOs zBslZVC0Q0xXjT1{BP~=xxK~bdl7+a@e%UByWDX$zj2YFuiZd}{Tg}pDoblY@K@ScPGhk}o zOiE&f7<%1?G!TUpwaLoDulunb^xWv)4VfCUSSo=PvBu-7TvlxrLTwo>RV&4Z5G{|- zFrd)@Of!OxHCjHA)Cq<-JY$04<7Si_GFo0?wY+=*5R7!n!UJ3OHFe{Z7OI+ zC{oVQ7U2tg%nr1dT-i{c(D-PgH4Ac-EP@3&>PCjZ?foJ_p1E{1xW81=@vH7Yts+e)QpYDZekOpG<)n zQ2P;xaWaK%w2&0#C8egY)c0lk)m@Ng-rF}pr8YE-&yCWJ(cWP9k0~kjqRWqp6nsV$ zoD)VgKq#9N@V3WEKnP({F@i&M)XIvgP^5Q2dFyijAA4{EI^^W^mxsorzdR#} zl4XViOmgpPclnlIcwftqw#vBzc_0M{OJ{IitDMS##K-emgA?ni;JZI5Coo=E{^G1E z{zyrwRW9d!NKMt*-R5QJ>mK=Qao_)W5@Qs5)Uz{G&8m=HVvcOkRJZpJHn%d4Y(F8Pot$8KR$e1AB7tGvjzmjDjnG8@u`xiwgVCguYN)w)nZUZ zbV46hT=H`E&Mhf*KKR5dD=Cm&(V<(DHp-|0*`6Ik(1VrQW%69-&5V)BpT|;=g>Y$th2@*@fd$ zbR9*6^>!hV8-X%UwjADj$S6FB4~^eY2q0lfHyxF`Y%_sY-p^jp{n_fF$XYN{zeL?- z(e--rmd`fg1njU@YcJSen|ftj5U)WN1;G5$vGkhhfqkN1TB%kWtEm}1 zkWU|ZXgs;!JhAnKKe6>sYvEm-jP<`TU#{7oNy~;GV9`2?>bH^ny)mJ9GSC03F{Y4r zot7yCYxi^vneAFdAp=SA6DCy1yx3>Y%4$}qte1>#&{KjB@xdt8k+sf5Q@eII-Cqt8 zlWJsd!{7n+XcZw!P-`vHGFaO-WNEXT){pFY(OKGt>qwcUEoPZ_%Sf3uAQ_{r3Yr_N zM3g_n zqrYGgLod@NYCC0EBdi;i9KRVG{k+9SKlTd~6!b4mt#pnZOzgoU#M`>!+^26n(IgRaP=Q?j$@k)S63gb7Tr?0 zMRqTRU|g8=RAYKM`3{oV`FMGsGSAD&x&3*4qT zqXdpy#WOd`u!+0p*$sy=D)O!9)Y+I)`MbR)S8{~$esf_?dOF7uB<^d%?v8F3z{rPRI4!mM==o9`<#D<#sF`F+hq$*ff$@_}s?u z`9HP1J>-98J6HKfRtbe|7VkDi#X?OBcaCf?-s6f&W~vGv#o+D9fWDahqwd@>CqDgL zm`&}`PnHxC{52MIIM!bivp?54jOzXxoybt|UTu6cQkXXP5gUs@!fVew5{TsJOfy9Vl$K#IiB|Fxl03 zMv*0Vo2zm2^h#vBZ`sI_yCxZ~HmT$bPbDAPq>_)qCZ0Dc`H?8DN-W!G{N3j){%+HD z_N_=Iquc1%B)8EQT8UIg^KUlF9((s)@qh{HpPPvxgxTLLLjV?rBQiVSi?%Z&ieLLr zh*73kU^+VPURC?OdNLjb(Y3IfWg^U>#97nw@8dyN}7v z%Sl?PA6*W~c{znn82|?-NX4qF;$2iWXCwS5&4A@HK04R%>8=?@d&i>S`L{V0wha#Bf*-`So zS#?%U0wClaAN%rZvhBxBp+r5s$xdW2PD&Li%$Y0W2POa5a z8W8koX|)GVORFWwB4qyBm{?yM44Uq03b6C)fC@X?SgNFIW&a*Z`;3%BI?c{uGR z0ME7(fZM+7kpF9|gVW3J!ZKhU?mFH(!x-n;zlSx9RO!FDr~d*K@_*w&%iR$)8Nl^f z%Vw5gZaMjII(5r2=t?t(P+@V{To9&$x^Ko3>z-CTtP_Iw+aJ=L~b z9>eaCP=u&+On|RwI{>+MF*?3d)DL=!~(*qI34O$oTOQO zQ9s7iIIBh!4r&m3dS$Rq3msBUL<;hkHhxTyK9I$GMjLIm*Jwi(?QvMMTw@Ij(8U^X8KhbdYrv}-YcAz(ux77@%vUgnlEXtBODHg& zV`VG)x0J_UH^~7hPmw560xkekcgGr$mQ;P1hz&G#cifu0fNf{XqL0fEn(516%L%S+X1S?+Zi4+h^a<` zQ52x?rU1<8>Z~7-ebzG1{^x64(gIPtX%2~z66I`#ZxHNYjn=sVEEu5{w#hE#gA+B4 zfhAscK(ADQvEzg%S|nZa}4ksM892fmXT z>h^w7)C+bv?8|a zIUH_i|5>y4U@8Zp?LkY1oRNc2y~XVWlH0=4Mchup$FQBmikiGeaF9gH*V%?-xICxP zg_LRhE5PTnb|pDa8z-A--YWI8W6({Zc^n}ifl>uX>$Q?KPa zEMFgawWtq6ei(&_D<4xHZ#D}N6e|8$&8V_!OWYr}8Q>R-8{|<`mC`62A^*>S=2^AP zHVW_D(&ka16+FuM*_-QoxB9&oCtH*(+~rYHq2s1%c$9OwH`P5jzsO#dYykjg9lSV! zXkYv0x;Nudaz%=}wt3W_#tmXT3V2-zkAh;iSi=aVAIlF$xduc&gUE}fU&IG7ddqwe zvA2^CXj575x{K;vcTt!4|N2xfC!suTM>vi-`sbTJ25f2zNs3l)NSe{k{D-DUUM#l&t)sMIO#6v5MSu&QsDrezNJ+;KeE6=S41q_(D~6KtB2+g ztspOkLwFVfTUwMlvKS8GnGbAfv89gaAfj3RpOHdn1xH5fTnNbJoFJqfiu7{$mt{iu z3DH0_&Wi0}5F-sUIX~xM;zkro0Dgjm|hZBKAxIbC!gm*uWNp)eQMKWZ_t@ zzNOF0M=v3|`1zk?HikevVP&}31j^!(=mSb|*D`uYlgDb(H%$oOF|`$m%kFZC%N}Hh zS(D3UR?2?}>$*{`-xl>-m)5hnHSw&I55_t35R%`q>UCR$#q9w3s#MFfYI9@Vt<%jA z5^?udl|3s_YfW{@Bq=+7mn*6Xyr!H%yJEwsIvyL-b1&0iYBN$4%IwA5O9_EILWX4t zwTLBT+PRlzQg&LkvE0j8GwqqTZyR~aJiCHoJ^Ymv@19e z&=<-#2#a93$spASRSCK^94PrUfnPH;Ot7lbU|SA@R%A^Y^o6;fm#Y#dME>pc99HU^ znDsbTZ7|7iBlRJ)3=Gf5B7k(~oHgYgexv1IS?iQB#UT38LxDGYcc)Q<;X@yii^W8m z7lhp3#qv~~vm<8U>VhO|9fU*sU6i}hKE1EL+?^fM1Gn&i9m8{K7t2u@(>gwuTR33y zG(rb4*#;+zjf!1p!@{u#n@XwCR0mpRq1;n>$(VA9Au-&F@fU6)cP8 za4-o7b@{`82;?m%i{qj=+h%t-9zy0wdtZFvv5^;w`mD7ob;c6|AHL&@ ztEW7UtsVZ`;X2yiY4JafvUTVmX_aYFoKqiEcqu;7OlI!{QsGmj+s;rCP|!pWgx((Xn{^ms*ZR2u~fG z`*Uei&qzkRTCj2y|NTiQ{>Kr;FHZJW)8=ZU+hMkB`xPXAaHB|mqBe8?cIz})nYrtM zH9-N|ZW{^-UY_11U3@`)#bG`xvCYLo~`I#4R+UX zwQfbCZ@gMJ&|EJPJ)l{r+l?4ha}-JYa3WUcTQ9CMV))}nCt!G+h(AJI&Ki-R;3orm zj|hB!)}Z%&*{S)_T67%yI!~6#@DB%z%-t=aY?!wcAB;koGnP=Oh*9EDz~-WT))u)y zIqY)3ZgkvggLi+FXpBy9xd}#!A~Kn+t2;|!c+!*$yCKL^7Ll?$L#g0re%PH|WnU?r zy*oQVn~HPWO)gC}sD9Zns_#E7RBt3Fy%5aS5J3I|0Jttp53y(=Ie@tm2Q(iCTxhzg z8<_HJ$xK%KfwguC5`@n!1KKYl_;A z&+E!CuLHyL>S%-{lSG;9slRQ>WNjw(sp)h!M!XyyQeeuoxbwp%hpC=S>JUZM4|63+I8MAT*wivMP^gB;dph-3UwzQXLUpi01WIU9^2u}Xk1Y&>V6Qzn4r zwbLg@jra5n(JkPea@D)2DaW_k#E zIYwGDvUaOkvg{5ggxt@eB)c;aKYw<2c8hWw(HJNWV3gI z#x@Rwwj(KvUrTKau`9LRj6F#7>_{+nVPxwZDfDa!ZntO{ZOM(1bS1ae2qwvH%*};2 z>y1%gAEcb?gjvI;E~e5F-;Q8UL|nOB;)=Ed;@Q6RhvDUuA%T*3fp567I5>%0!4BF!x#pLQyx+N zrfcxBlrF+tcFmC(EVB;BmFfuMlEG;GSPw2wiRGBxDuLjxlxiI06C< zdn1WWOm@=&A5F^%y9H@3yi;Wa`HFzRdrRUP__&M^8FXp&g>0o8a}5rwpNHy+iBDTw ziQkdSfCohiLuQw=ixeEKSYffkA%YzU7w12S0?ZPj!XXiJzSHB81h(@*#X3k=(nXBW z&sNsi@RN3X>p(WLTi8vk@{4A-pZ3`g8%vx2Qr={+$hO<_EmT?ti-irE#hk^90nB}~ zEeBc(=kx24KxPw_oOBYo_o4}89!+2|9nXp81`}9#qF03DvK7P`9JPu|4Dq{)DesZs zXKI)@6b!RhUD623P-)+ok9%qYcQR_-ZVil1>Xj|+{Zsm)GeWSoQ?%(ra zg>7W7Ez|G225Lrlp>2!tWGxZ5K#GwOOPHaR6)Pc593zQZbS8_wHSy@#gjN3FT+hQ# zv$thYa$KETXA?8}xQ%OZh_Ob~QKqjh`Pe4+?JL+kINeDlx_`RPCHe}#j7ugu&MhHf z=NPwi(~0f@oYf<-&+%z!_i<01QS`VT06vfD69C>d4S^m`Nw{mek$>99Q@YJ8tar8o z5VVAY+y!3COVb>WckvBGsRdgekYL-ZH1b~YvG)bsd(0wKfe`ie?Z@h|mRG|D&tQ~`Y4;SL{23n9 zEG@N7%=#+?4!529FV+>qYj;H! z932dZUe~~f`yzN_KKxP}wY{(9g9D5D<{2dZ@D{~*AItaTdjT9@AK~k&r+dV>a@#dp z6Njm-SD*MZ7NNgItGGI4i2%syy!X)5!Rq!1L~_djRw9JjuRw207GfA%%Z2!UCXAlJ zz~wlr!hQ}{2>Bx zlCN9*EB)%xy?%9=D}r@{vE_(ihec%9ZKRWtTFzr@gZ@d{X)IOpeb0c&P6WtS6O4LY3UMD9Ys^6YcJY2 zB6--6*!pF-gw_&7SR8k4L#_m+N6>D9qQaRoNo9p?NbDC=#YaD;jl(^(acY?8XTr5z zSV|9%h~Abw!i%)FRqF=1_{7eCc^x!&A zU7XB@)hccit^kdTlM`t*_5Id-lGOd*(-Z}zBhwKl|E~s0#5^`0=%o0`2I0&IX6q+z zy~w0A`Nd!We8lns4$DxJ_=s)cM(4&eG1_tAkJ2XAap=WgiwiO9*0)3jrK?hq?}TdG zK?}fFY$g$*0Mk2G7VtKnU~RwK!aF15T8b+>Ufs55K$}{AMY#?)4$o=S(bAd|hOM#* zUwJrfGzqEPHE09=MdKMAoM0Ozb#%`6h zh*0d67#{Q3qyVrFhV0qA^kYl}T<(&&Huq$CVy*B8?s#am zw$U^HIzDeQN%-<}N)kTSvdES#x(W&XixJKN5sktTJB}k-4dlh&=t$zy*jSX33>+hz z%u3#Z0L2KjfTQqZJW1v)W#q7b(qaT_9Y(M${#48OdW7p*8?fkI<7ch|m>g#OEM7-` z7Oq{=MfJ*LUrCC8aA;_lg!k!VZ}FLio7gTjZMV(roI&i248n^S@Mt*#0A8~exAVdu zwA_&0U`-+%!@R&=Z4%9^X4RSL#MUI5EvnUOiW3Y?OiSDfSF;cEC0c9jVWuQ|x(DBp0@iQJd?x4((pnLMt>pc48kyR8~Y(0>uG7-O>9( zcE=yu>KQ}`l!meOs8V5CUXKaA=+x)f2fSVbL2Va~prFd=|fBLf!)FQ8#!)BQ2lG$U}79V#iW0&X>`4x-a>gXL2}RUJTS+gLtZBaj>X>fh*(u%&Q0!lAS*V}`Bbc#(0Q zu}H+8w%3+esG!Sn+vP?|o4t|Cls)SM1pO$wAr?Ed7vZEjzNMh6HPJf=jw5-Cz3|)6 zQ`{JpS8y~QEPB(|KeGWGJvxCWYJp@{U>|pBs1K`53O`2IYy(hG;p+C{AMdCUcS6Z` zrjEj|mZ1%2 zYz0N|d0{J;wb+X0U(*VcbUlHzrWQdU)_uML_&!sG{w}2BE}!ys5^g95nK#8l9eF zz4oGsdM!xpY`mq{K7H4x8_Es2uLX*CWsOA9RS3+;4y`IB)3~fmQ>18tf-~)!qP3bL zMeEY}Ss(-{O^VNWB)Y+@r4N`0COHZVkY+csYg`{#8WFO_D?~o>7|8&WQG~+~pOc=s z7U8w9-XZKm68>q3JsX$rgV+;=@F4aW2Wc!Sh&{$YVvC7A##Ox~6L}va_7Hg;WEqpa zqflYgv619ixziVUkf>^l!_>Yt^MQmN0T-%wMA$bpwe}4NWkN|!t{eHu)C@d!V(v0{ zx#3}o%QC;ZdZim4@+u|->vn!Kf2`eO;zljMMa+$Gi;Os4N1XNR?z{cXCSZ*srD1iw z<4#YW|42(8AC8!1nGZJ^F^XAb>wMkBd`SkvXF??nuWupCR{D!?9uIy@MsQufx-JuK zv%8{g7L*w^z|>Ka3%}PIW~cP9#*sF`m-G)?W0(*5FqgCI`X!jINURr2adZufK5pU14kis!lhQ_T8Y{pc50$Jz&3@miX1ixXYTx9z~XzJTL6d^#2`(C$*bVcRfQ z6gMho(-Hgf@!V8DUYsmfsXO$|@M-(aoxdYnjyImDEUH(5=t^KfS*L>lde$PhaJeRhoD zm_ynH$4v$g4jFH&l?PBJ@V2_i7!r4lAs5eyBYR^Ce=HYw^tRt>$VFS>fBYq6`}|}? zZBZLJs7}N7dFuqL7F##zqfd@7+f5UwNmYj5B{XiiaXg%vXU*imwWK)l9uwGxS%RHOF=+nqFZ!A6_cPB`^W&(}wA(V0g z-+tVw3Ne;`Lkk|^wA(g_>@6PD$k)|asHWw~eR4yd+!r87VF=APgA~T`fPm|pK?=3F z-a_q-1u2{!@f|U&2PV%p7TFo^;B-o}Nqn6kN3z$ZpeCMf65m=P=!ggF#kc(2urLt3 zr~ui#sN-Y-b7@q!no_k9Q6Lz0Hsf2KrT7+A6K>*LT$66;=lnJ#=)k&L@h$Iv*MqBy z!Fuvze4a7}>#D|M+A?0iH>||AWRNC)UZq(uEVx}|d-3ij;?V@(WO!WiN0!k^ackbx z^J=|mqM79GCe+mfg%#JgAu~y~Lb+ZOl}M+t5%*`5IFwesYx|gKF@w-P)lc&kZsl^R zOo9i_gf8@{wmBta9Upv=LD;k{c^5tO->jGG9i9Hv-8OAj8%A+ zqoM|+mMm}_O4%b*$068S>oC5J*kUZ?vMt*!@UWc==5a*pTVx(+s~Bw;lFzC>=8z>o z#Tbd2i(C#v>|`$IVLV4o#TaqOR4*nS_E}Of4zx9lF+n^h3?j9f;7o3vAf9JK!#LoZ z=qf&((c*--OGkBpyK7)6`7!!)mF?vbOtd^1-7~t?N7N1B?!}3f>5wfRONEc$3u0p*+4K1w$}0cTcd&PnCE-_9llO*|9cx!YeGT7+%l=7irRC6 z<#W8l=Exg0s&9Hm>+_4>X;GxE^}m!34v1S>fr$lSmW&eW^nqcSCm`M~9@4T zq%kq1TkjEiG5RH(3WB~n54Ly9W?9vwP$Pa<%cK!%#M4xpC1wP~48eiVK!TbY@!J`J zRn1E3JXN?;Gl=@_P06e#>rTVN!C)~i>WItB0|&F}k!>br_gZOqn^HB(1z0>-XU%p` zHy4q-cwlMWww)Zb4N)K;=;W;ktf1FBi6~!9AW+dX{p2@V7Cg3MBcivgd1NfsWfNMm z5A*W#F9d};C6iU|iaD$qDA028_|AjXhPt7En}ZFN%Ot-}*qjsWLz5@y0N6m<%^yuQ z4zJ;O`kROM-iGy!J1l!_^zd%0I@w9S7|}QCG?8U0TO=Zggpu`F;s&!Tx@m`z)xo@x zr&_rDROrAgi%m)h`+LZ;XgQt3gL+<~<$Z*#l~ryS!D8{zjw^Aa$YT%X72YyYcJFtD z{ZB?7U_JyRqb>DzdHT_{RVgJ8e~HgpDOqZ5mrI)O|G5><)IqQq_11)&yTl!@qeR|~ zJ=caJ*dr-ISustq9GINCT>MiLk-yVf`_McV!oKK)UXF!()WH`1Fl&^mZW!vaRD@*- zKpuk{Vo+iVL9H^x=+mfD#OpZ0ZYZiIDCSoDH)rIU;4Yuw-r_xt_xqAb6UEY3N8!I$eE{12oIFt4C7?p&A+&At?ez#;K_k>ZRVch_}wMcyz z-rFTWxnr4a`=U*Nh_#&c+M_vx!WmA(DD&5E`tPg8{zo&{MhOr!GNrd@@Zb#Nn7$L} z$CL=rG|;LEP~IXyj6&%3;(*797ZgiAKH z+OMH;dQ&X~XrSIyk2KD!B+nRhjoy_h!!UZN3DBer9r%wj2?>3W067IoaR?iMXpt0t z8ro{y821nyHj<17HbOGq)?u(UMzAORSWKaUWHVsiHx}6%B5N>Vh-`f=)_Bb4nZ~%0 zH8mGt@a^~izU|5O`+ICRlQKID#Dvs*ALUY(B(m6%QdnUUjh3gklVfbg10R!_EG%2u zP`jy3qFYr&gipvg65ePJC51)8@55^nI@g5N!SDi!7Se728B-&q0SL>-xuC@-BrGKh z8?iC{1Ust&TaD9C>hc; zs=qX&>lhV8n+Yf|qBR)^G_GzOoT3UKw94{!R>hllT5<@xvs^f0=s1(ns&ObEXI%ZFFwT4)k;r0)IdbJjVfMC5<)+yfd z_eX7qv^0e&2uUea=s@2H$(bumz=1WgZa1PZ87&y0QpNv zS34^JnLT=f@={6hd83`|#wT*LUOrFcidmOw;-r!4oI zS;`^uu>tf4k-QDynt-HWLl%%rI|a~Xpu`0x(WC0{)Ef^n>#TYr$E1{d-)VKr^#=!R zMvP0`bM$&_5|UuuzRe}Sh;pUXt@eG~>^Nv3SIXvPcHec1fUc%hrx|#JhsQr_gOYhG zV{pg~h=`c3rIGWXCP28MhOIBa%><`5@Xi7t+-@?VRchtJ%W|p9r}H=VGl*PT+;pcF zGNmQFOj8c;GT7-|T7MqV7v5#`wEM2u`IgMyS`RYe2k+&j1!V^tGY`WyyT}+OeU_G_ z0CF~M=wF`fOH^?@Ek1cWfaUkp>s4+1&V_}~^%|wW%j*chjA~5O!)O{-)o4QcH2+BJ zuBgv+6}C`@q9w{R6>$EvVm$6JR=N>;0w9Hi)Z)CxXz9XH)@9VNFHo9~Xer}sOQL6N zi*Apb9RTN+M2{BGsTb3fofxr~kCEwpv7d`F6VdEp!En>HOh2IrTD5Z=mw`5^AkU?8 zZSB?yY0*voD1I$?6wI7pYq#GhQI`+{wo6USTFzER2o+EtqP_HeBFbJZ{opGb(GQqG z&0|lr%nKt~AxJ}=?0D80EvQpWhNfYJr)e1BX&Od&n#LnMPD#miaJ+Z82Cw^o&dLD7 zNJ>cY3Bl7hfkA8pHe$Fu^;rZ~e^Q=DO|Db6r7u^aTQ znc?4$pW#n8W{L?jj5NSl6i^{)HPE>1+6oRqsoZOzaycvo7L}tf1$KtUk__Wgs96o~ z!0G^-6?u1CYN1su;N?+?B8WDOAl_f`SP8bElF(&C_UmP)Xm>}O~TjG$4`=|4u zGQdX%bVWN;x|UhK3F=f8nyQm|qf22h?3gjKn29pTy3IUI9&tIrraU@&b5Z+LjdS|V z#)C$09-b;-68tE;{*5G-!5fhVY^r97GaBHgAb-u!#&@9XiV$b*FB%;~8~;Dk+TqWp z0Wq9+qdi$05dZwp7#BBQJ_x-@JaS?_32NntpW&J&n|c1=P3bn44Ab2 z!n=OuG+nGa)#|0Ropmgc2nEl?U-?-0uj;81RN8JOh5>7 zA=F!Pk_iZPlNi35Y7w)^*LwjIaH759iAjfpeW2x~??@U&ol+`l8JU>~i`l=7OLfr& zrcvEyc`oOQ8gze@j`zIcQi6;JpN+P!(>t`csvY~7<>c6xjErMnLPqV_N8p3*MXMJ; zx>VXeLZ1_+{aw%gtYV3RXTOb1!L#2+rr_CcBU9tq-!DO;oVmITo_&d(7~2}xe*OAM zuKoQbu6@=*Ju%#ijC*tT$}mgTkNZPC>lIBc&;zQnmZG_g&t;V^*%u%}mn*|vcBEWX zfS68t)5$NcHs-Xv5%nKk&qDjlhm3RkG&WQM1q0je+}c_MD;2vPSMI?n-zrtH(aYJDo(AYs5o_PAyS-7H2d=? zxa{q-d#tQLwlYRBce`a&%%MH(Qjy(`Vy+DcrLiqKM)X>D5Bg)1<-l4EWrMb~?xUwE zsQpu8?mo@<-x#Auv@c4JOs6GkG=vxT!^An@jL=KG6%`*5*5M|%nlK4AX zixGFzIol{S8DBubI@rOMzNN0buhFw7s4IqWVMu?|I#*#RaH$oaZEQImOj3Vds<-;_ON>dg8$@YR-k zWMf(%rpj-gxcsGWwty_P-uKDI>@;D&vwmh?2`8)>Svf^rvYXS!IE8wcIjTuG2=x-= zs5jgJAwcAJ2S+XFB|*|&FRVrqAwcT$io`_#mSjmL9$fE{ z$LmL)Y(4VC_(v9AkP?XhW4zhuMO^~%wnIVs7yg?y*-;nC%=p>32n`Pu+bn@tTP@6x zVNj`?%-qoq&j6b3vlqU#OCWxFk_;D}CsE)ZRRAFQ=B%~SW4KM?ohHqF$r-dTwYEI} z_YGN|huIB1jA1ufV|YHcPPz2jKzQ1l<_~ZE!fH~@i!8P+J5{NcPW%c?8~05uzipOF zw=y;arB5Z5dhL9^4v7IRsUN*9+Wl^6pCH3p)jm{uYA*h0I-MJ*5A*Ux z3_pAXk*YjX?bY&oZs0->;GAe_g{ zU_g|)!E3+HBUX7+t@6y9oX;Qr3wLGZoV1>KH|r(k{MBXZsXuEN2&eRA6g^P4?KFk(K?u9}+Fzs`0jIv1{spX|rV7&>ngTd2FId z{K0!Cn8Y8-a$*&mV|<6qI-iba#yTHu#H=H#ktcX5B%@eXKH?r0mBtuTV1HpZZXci| za<1#11iLN=gxYqL=|y{yQiN_3^^S`Ew96D0*=|R-%VftsZTAb2RlnNhkXxvXcg|7B zty&8D(XP4kO~#MJ)$EBfuqI{3!rJP1a$`_sncJq&sMn0ibK7{_y&!O??e=pEsw!|X zGy!I29B6D@I*fhiD*q^(H6Gb<ck#3Q6&&jaHGvIaXT%$U1tASOMD@dT=!L#^5THObzr>}|SRV0i$)!>yr>wjN!U zM^g>ICQ=B=CxL#v5S6`SJ%oF1{H^gEgcYy3$Fg$A@XbJg_cJigXQebq zDX}U95CN(h$3g&+K`-D~2q2=V#<3=VNEJQW0YsAfAi}kIU}L30qyYHZUK-??m0{sU zT*4%Mapb_yieD2$@dwMnj3kGB9ME$)5`}?lIeYZ$=(eoQVh;PbFxx3xNjYPSRgznK zc{zKhMALoc;X4P(YssaNF#H&@-Fkg z|F|UB#0UjaCim`> zsC{zr(4I`~0Hk{4nYw*->X6@$gEBt4#w~>sX4C3Xh>R2%zq((~$zqu9yOBQE@;%wL z{7lnTe60Deui-1)U@y|~+D9J+rqfmS+QIY=no)f6>e0KcaR&>x?I#R`0z$}&7{wu5 z3D;+5K@i@iH7N5PgRBy&h${cU4!sYXm%U?bY#%FR2DWi8sBpVFGAumH#{>MFxgO3D z&J3{xAhc=gVkO%$he>PJ)D_sQspbcb%<%$7Cr6p%$fV-|{yfj-v2_JGLuY+Oy+IKY zq!OE`niRi%RAeW&$^hsQ*sT=RQVynyH@YTfs`~cswS2K8{yP2!_ z79>FgB|Gr2QEewVBGQ^V!{43Vq@1mrD3c5YDgH@OY72q`#~g|xkD(ZO>jbphX7ec) zdYn}Pnv?WX@!8_AaThZy!m&_=#JH#sQ~X{udg5EqARm+vQ6@WNrilN-36Lk2_Njd^ zjr=f}xpOd!@j^FP5Zm;fgSn9NRL+syjZ1}uEokNbE##*4=XINN7hcg3`R2)(micDt zE%#De0jRJ5S5q*-6R67B?kBqPIvDP3;rq7qD4VcP51;jpGQD?S z`4fj=O*l&4f*r5dpJ&+>!!~s-2QKs1Pl)OF)TbtfV5DKrn=OK;t{p5W=?v?cTpIIW z2JoB*CxD^d7`$~IslZ3as}A-TOlA=hg$F{Oogm$PM(n*0KpEUdlg<`m1d>lUd55yv z&CT5HdRAa4=k5g(i@-X92e{ynBc{(LJ98(qZOygkbL}-l?@gMk!U6@%=X$!Y-7>YP ztb1S>%d6CtIOGZoC07_tSW0%-RPl{>vY#T#_*_R8_B1A|!-HOsg&j3yGATB(8IVkd z>#lkRLhVGnftXAdD&Bp_fa5)F$UuU@`9>>OH_j2gDO9j|U%Em?G=+cVj)%r8d>ks+ ziz>{J_51B14o;2=F&Ly|F_^q~hkh_dK|?VxbmiL;zAs}(^067kLpM!}zi^Ltwjv7Y z9%XyXk0&!-%f05?Yj)B_`ze?pvc&7~% zrZY^aTl?z&ojZR9TTZ&+|9jkfc9h9Zv}#Qor-moPsr``B%*`u;qy_)Z4tmAsoK-`Z zA9O+ODxNSoi!L-E=5SAPYG+*_rECZ zegCqwG5o92#rJ-H%rN`_?H^eo9im6G@2pF4+lhc-5d7IHQF{p4@DVH)Ru_fU1-czM_cNnkt*!Dpm$M= z56lX6C4}YYA~x&`T1Ji4xrk47%#KcdxrW!VOXRY3nQ~={oego=RGo7&C9jv_H)VDq z=~1#%?QAqE*DGbIF8NX5%PVSFe+sKnqBZdG?ngr@CEc5$n&Bz|)FatCW!{VnbhH*L z^M=_-sxKo}$wxj^y^z9h1-*wzHdz!}fo!~DsSi%eiL2^37BS-VwzF?* zWA`&Rcwbe@Xc=K)s!`C)y$ztWwDkxJ+8U)j=;l{cD&3b7NYb~MwA$X8M|Vx+c4T zE`!PQO6(5NXHI`(`blvb#VScP3MR~vv`FhhGU7xJ_~X>8}TxuETcw)mQ@=+)*= z;L|*L?Vkl1gsq*X)wM!bp1J3tmb|W5lAsFn&ieb$=8f!rbhhhuMC|;6S^G>h6y%ZI z^?|%zd{Ms`P9RBKwJ-f4b2Bh2CJ${po)mZg26vp#rm_4*lk`|S2pv5glK+O$kwx1*mLAFTeCWsJ^s8ys7^%B}c&^-kDvwA=~G z)s5Cr6UeOknw}2C!0t$1om|C@u%O}r3~{`%5B^SnpT;U1CV!b!93!+Vs~(_JLT_|~ zL;*G#U$t-PNYwsHelTRi*6_O@HBf2ZX6$F1j;aqwXTu6=!aug~shR0sP#_eX7!0;2 zXId-7@`NM^c`)d_WxxA!ST`Ic{jf^JM-O_oNJi=&mPN1M+XuH|iDvnnbJ#*-KwZ-g zV5V9z=6hkj*CP#I--k0W`~;(!6g6!Xu}fMU8Z@!0wg8zC3A-zj_q!4@Gjv7Y*d z7_45W-rN^aUIZ5m4TLskrO8*ogoTPcSD}m5spIW^;vF4PRHfUV6je|A#7Bz&%BPWp z69M+(`MH7pi6uE-F>I6RuXimRzv)im_WX!oZ)vzjX?Xg5X3l^eRc{yk+mevIzZ!i;};5eG7=-%)TcM{li~w zkDvH?%el*T4ALEgE#=%kI@&QHjJY^2YqmNl0F7yBO}Ch{S2^L zaPggJa~IU6%f+i5!5HWgMARdkvGiIJ z836ybsiTa*Czc1aUf=kHN2u^ZwxU~1NY2OY;FB{4odz#_Y@xS}%{y~Lj%FXWZaXAa zo}S%o#^{EadCXg!lIr1vqe<5_s?;xDhD1ezze_)_p-p&d)F%H zpj_&oE82`Aaj<_LSF>t?5@KKvE8Ek^q#_JnR=0rYQV=l0xV9jpJuOH+FApuVBAw?O z4McwnCj*!!C!WAA=_kBp1{+!W7RBREiQZv73)EK}8oG*q8;-IcUpf?&TZO-T&#%Tk zj&y}X20e;O#`<%vMjZ!yu6E`HqBZAgpgZgvW)Ev%oaZi-=QH#3%k#{^ zQJz~aUEP00xvhV0wo+~}P-zzfWdUg$xAN1sJCI&c_5K43d2YE4UiBaPTNY#r66wS+ zqz}wQ`oL&p^i+F9?rR9<+0vY|a>=5-Wm-aY1kg)>SUW%jaKt_VvPkZR@!5VlKf96#ZypvtRVRp@h`LY6I&~;f6ZspUSR}-7YnxKa07 z6O_HjJvH-_=vA?(A#H`BEh5YFF*{&~2OeN)E9kcMC8*S#z(SY!al>zh`EAWJ(1DIF z%*c_pBn0ktxe;u&`PaN#L-(f6*uAM9e4KpAh7(n)$FQrzxlJ~n@gLbiQWmt*!}^ty zNIYte9UY?PL@{*d$X*6QPOSv1u>o_cU=5?|^So)dB8~OdLdQz`q5k~1sJXbu%ysJq zB-6;;fFzp$B5t%Fy^s-eng*j`qe*A-sJR&#kY+;ETrX_2nYR6irz~({lAuh8rm1G= zJ*mv2ZprppyYabg3>m>WZp3ot`xHWddT0CIwlzIF3gnNBvtEO&N%1}t?s7T0M8u)h zAVl#qYSlvP^5%3PQyR!-w*e;_k6=vH=S`^{uUlErjNu6DYic%ODExj)if{trp=7H+ zdNi~pI7Vvbbd_H_n6IwGpz#*rn6%kccjYVgP&xGj&<1UDTb&y`_T)MQ(Jcva)Ib#g zS1jDseJ-ZaX^BTnevoGnhUptVGR6DN_RH_pv2S1qDE>t1kd{sZTtaW=qv&s;L+*r? zOxJflt~>SfqTe;kU2r=M{Lt@zhOWMdAu3^YrLy(6GP^D1qC*M>mA;u#FER{7Hs+~8 zGA^cR{!9dBW{Kd5*7@(vT#R_4P%T+2%?w-ox_Oj&{S{as%p`qafW767d`IKEio0a6 znP)WPwl>ebNo-m$4LthVK9+e#$xqi+!LrQCi_1ncQqFvZ4Nkb6YSnE3C+fIrbM-er z%o6msb$PB42DQ;!APk4F8ghl@X?M2DBat?ot*=fSEKJjB0}6mR?=7!dv>a9(%%|Hyd%WZ6;oc0t+7J3^VVh;yA2hXv8gG1Xlpk*&D{z!2P=doKXVQ1tSXw%dOCb;+NbQjDE7^AX&;>%taGty)KmCQ=R6U;u`Qm*+i1QhAm&IFSz{qL z$52>J*`ireD=W`~qC8zx*8by%$zh{-=!3?SF8n{OVmIByxw_PiR5)zS8lWs$K#vAv z-5sy3Sz^9mq2^<3S(2@Q)~TY+QR=)t>?P5!D_b6|ULizQm$rd##id2NqvO|QgcR{Z z%NVdob%hdiyA3BGvGy|fgNoC}eFxY`q_Pk;h%d?1X5bt=yxfMB#8ZG~#OZlP9|!=% zAxfx5{ZB*_vtrITxI*sTPo56anY4P{$MPvN%kj_Aj+7;e7L| zHS}oF_M4;+Ddqx|kdYsvW^LQmR5E}p*@soL-^|2rRt%@<7bnR8Ve1+eGDkjP@n*K> zNtR1=sj|29Gi6LoS5yD1qG2l%Wq2Ju{znkb`|I%yU1B*&2ZVJiqU;B@*h5NKvwPoG z-<$cp7bnOH*Ot?7wkPsys#W-bF!~w7(fp;5sa}=84tY&)yT0y0Q4PH9RY?kx3C(I`1*T^m!y zP1EhnQ5h?;K+93Wi#3nG)Y5_*qSLr={aAIRI$X(;yG_)kQvg1q3-iQw3C!Ace7`{E zYo0J`)BwNvMbh=L#MqWHm5(!5!>+U6Y}fhEDOl6parTsUUCP;TzU!v6>)1eg&s3!i zr+p~ z5nR7$>w90?`t+r(uW>LE6s+i})7G0RKK^#?Iv}$*M7Lg;y{0e2pn#sObkIAC`hc@T zMJVmkZMw+~a;DBWmcw^YKrOva!Llu5K{g@1jsUK)204rW^bKMh(}PXGNi?VoiXire ze>evdwE@#P*Uc|BJb;5(oyuCjI*?pd!&zh|%w zuS*|Qwa*&%0UqsVmq1Rkx(%*83)LQ2Aq$zmesGTIz^M{WB#c3n64^Ksl*b$X7t{kT zF=%X)#A+I2p-VEG&QfDy>r3B$YJ0O9{V&fE|>la8*Tj~o|T`|1;V)NV9 z*Qd6sM1J{+nVNPT6D4x8LDgIfBSA*0RmlO|&3MjJOF191c&zL`o5t|if znq?NaM-wyX?-HSox%f0B?Jmt>`(SC0-l`3xw=$xu%1rz609V2XWXz^+4vov%RR_zZ zf6vtEfC~$G z8=59Zvb4=}pjw@Q^K8~^LtMrY{*RV&VgXTI%|k5dRGI}f4FJ=oS^*G%=8H?Mh&9{p znN(xoCwjTUqv|vL+NT3R<$ub>9wXHdWsh1@UgNv;LqiyQBJxmp`D{?pGNHd&ZKX6w zI&!&3RTCiGl4preDra;e`HaG~AzU+e>R1{h^s}6t2R%3XKW_xXtQM$#SC_M10}Ijw zxhkGfM3{C_T^j<@*Ms+UK8yxhJT-F6`^|*GSWC0AFbt6yLDh;aKul@E zJaVm$93>Q?Mc!`9%XNaj%W=kNFvC! zC`r*e_+ATuZbivE*Rvq`atR|^1c7Ko5a;ePSgs8zx!&2&Y(b?-@S*`+!m2q`*I!dj zNq#{-MH116+z+W?vyeJl(WFO}E1a$jA{KYB^%{wF?zpNH3f$7M#us_-ybsm{F(r50{=ctmkS1~VczwEfFkN_s^A|N{} zzrA+nETFPL?~b%&Mn(WZ;#1Nr+X0HKyEa%=bMTNHC&=Qic(krqOvC1o87d^kG zu4X0>vR@*9=eHGw1oBDg2=s{29-u&|>Gr$fZkL;@b4^S)j6-eOjdf=uKLXV@>u!}Z z`|Q+|GTO36vWDJ9R5u)#EhZjJVQ9>2AP^eG>|nZhPfd1d@G>=dB)iN-L$NY71I)1^ zyB3O?i|k@2m5mnbemO6)J8xu{D!X@5?ALKwFa@ecOJ)(FXZxur2~9KDorzo`d2N9s zFKmx2mbORuAaA%_a4!&dK~kXNtO_0Qp`az8oM4=hOb&O6W{NL`DbHFfm?2v-{Fb9}n zxcubDiFg@E;_r;(nBv*dy?_KmG*Zc@5sc370H`LFn}u#{(N93=Mrol+Ed^SrP=!DX zUzCMzZUD!?d=&4;I&bOd=HB9Vb1xA`fwCOYYtVHTn_=Cl#5>Ge(ux__2Ebk;oC(mYcH3hUM+u%krJ#4!-Asu{)5c*-|}3w@+QgtzkbwY>k3&) zSkuV9Js%emuaVm}g4;~zK?_lmnfR3?y@mPts?92t- zu=pd`dKQ0Zhlh8uPY5C~<_aE^b0i%I-rVt4U0OZ@T~<{7xsDIyfUH`>3Cv4mr@?+t zpI&sRoB|oZrflEIzx&PG?)>XpKk+rT{0W9Fm&sj9(}%#^uFqqt>$E#Ns_##zH)ZDX z_vqZg;)A#Nv`zoBADK_7p%;H;*&w}rH&_`YO$-_0dQ6NM884^6f+E9-qGyu)#CHe4 z_2u-vx@rD$-S2W5l1=#-TR?p^;DCNTj zu6M=6(oD$5dfb&&)1sdyK933}nTVIooR=9?yerN94pq+8gIa3cgC~O>2^DD(EVbTl zb*=5D2^f2k(0L360{C+WcbZ+fOcs86K6_as4NLQ@y)FO%Bz#Wo%+NNWjCQ-#$N+i5 zA!?W@(rSyGgP52VmZWcHlDGhJ!saRZfnX&9JAh9~`&mvvZ~Zzo*rFAnXj6I((kOp1 z=0Y9Z@{oxYE*+%QRy1BKT5Ag%Y;I8KW|5U`RsI(2)N}4x2Agf&$cU z)NsDUZ*}07KLgI6ORHPH*u9v7&4hj+Kn5%+2it@We7m539s?X_I_D11rBBL%o6h-g zDoaRx$?&+k01yJHZL*oaOg3|QQbmd27_?K_ylpezRx+WM&Af1mBb#}`%J+qy;u65p zZRV+rBr%Q%YzDyvDHgm#=!$)^j4bZjW zIATM;09d5Cbx{pvJY(uxgk5u8?D|2V(JPgFgJXUn;3Hr|XO}1@tSeLUby6Xgf31;= zq!oIdap#_)s|9g&)tku{{gzV| zmCB#)RshNaoy;MGN6q0Tu~lLqLbN_)ift+Y(ZC_4LS4QsLCoxKna#Wc5HxLE1t5FO zDgfy*MHTD$Nfm&4Fb5UV^9n#7eBc#;Mr0SGM5Fdm;R7!)k%rAXDxz+nvew;)U{pqT zp(z`Uq58vLAR@0aezxi6_*ROZij}pVKJpNI>34iDf%Z-GRFefh*JOcr^YqzRO(Hmf z#Nt+kYdzc9W-$|-YMT8T^RC-aCb$c!h!5srm&AdPiu@{H$Z~dlClkCph^#z7w&26K zs3;bVyX47E`DiyT%aN_qNZO5r=53|1>@>pHaUr+)Iel8RZ>z$Gy$Ei;jXPW1Jhk3S zSlHR@Tq$LIg&_p4oB9AxnMr=O_nnpB+$beLc%FBR z!p^KB!&yPN$g20!1$VCaoj=c2bk=9pZCph^epdYwSJGenPEy6CIo(L3YIAvGU9!Pv zFPUb#JUe8Ht6WH(C%&XS@pDWsdlMkwJ1QF;Dr?{nxFA~$nwt0Hk{cETX}hgV@$2eL z@#`fRc!uJrvXHF^?q0BClu=~W(OBzY;hyOx(X3sKH@CGfzGSK_OlKC12_7R%NAlAk zZ7D=ZTyigH;OwgHHR8u92ExO8>+tB;gt!#*+juRVC67lZC?Q}M%79Qu%(snz2*|wD zw$ygEgCeB#22F3&kz}ER=E9Mr+X`bc6S9XfnOnufm{by&)IdfM-9aqfNFsyA1W6iW zJ+%ai_~;|*$YrCN`_}V%~eFb&ndE< zAtDgEFKMow^5hmD$jhiqpsESEh?-+;Ua4e)jCqjL;MySoWKZ%s@89xcSTzY5d}t>x zdFTv8gqavxWmnSopEs?>f`2|E+bQpUD^(D;LDiWiU*6h3Pd{sMZt~iomWXr?DD<^x zC_S9^mvjT!dgrL7LV%Wd(s3+I1xQRiMUbOBPcJqfo{C8NMR~CNj{0Jmf(3EZ^dkmO zQi-kA4qq$F{ki|IIG>=BZY)q7`#VYVC)8H_eh~S#P}X3ZnGm;>J2WXS-GnGW8?Y%Y zF^i33L25%P#a6W@G&+_V&3zVI#9h7e!P&(la zs-N&fJ9J7rgfEb83@K>GO5i$aqlyW9oc?kn=)GG>L|OIJmmbph`;B&ZSb%+XYTn6G z%S4MGvxgqFhaTm-1&cm7&Iu{EXDHJA1s7q2@a|<|zr7v(z&cbTPm0OUhT4 z2CxRG4e&^Q>z~gpJ4(LnSjgfEol_34OR936vco~G&lmp8Ygx{$ zK6`;>m$89ctF{yv@dc_KEBbp*%r0XQ%F;jtv;sIAa1GAc(Law{&M*0LKI>$a*9PbA zTU|Z!sZW0S-jiE?#3-(GCRTS}()>`K`&O2zb>|y)@LW>dp|mtcp>$MfLM&PogtfBl zHEy~$+$3?~eBE{hn~xQ=$9%E1!AmxH^TyV5h;ux&pM__-2s1_lg%Pa*KzX=lFBlYj z6ncHX^LlvmE8BBwy#XnC$Jn1pYTS1N_I>)^u0O@T2e*E6W|1zZv+_ za6Y&5{|{G5*}rTDq+hoz56*upgGmp+fd$H7HgVeLmFF{U!SaX8b1&O*@AcOU&h$1| zqMui?QcQaZw*w1uxqkSO9lS=b;HTniM*u&R-ZH9CRD2^r_qpH5voVRXxL)h&(GS|4 z^rM=4gY&Pa^Ybrf(a(bz1su5C4BRy>bSAuUy7yw(L6PpYc2Jk@jqIRE_fQ56w&w~m zf(_sokEOZZB%!9SHr{zn>l@_A2B`h>6enYxG9;wA|9aXeDdFIV3rWeIxe%$rMOP5> zH!nyQsGrc@-2HMCniJ-LSj+^h6~9IgW}L-1tSaol}#Pim! zbwj3PEpD+%uBI{vA^*CwyWGan7~w6W8bxUw#UMv-wunfeyhBTNn}wA!Jawmg0F;BP z<+5{FKJ22gZX1Os&)u31%&kXNFN;%3M(*-{uGi)s*G07&U3&{ZJC!TUEsIqm)^P2m zu8Z&}5Q;T1?Z_%=2`Z){MPiUOH8FV~eEfh_#pE&QXHq>FD`M`Ww3bZFX+_y-*4=fL zd(f%vR{p?|bh}r?^aE>^Q#DrUt~&a)P2G8WCMd)-p;Wa~tJRYT3R>4xinJ@tsdAI6 z_v;rI$Brx@j_H=ew7;imUrWQ0Tm%WC;pDnS1H~nzuTTGt_5H?h2-oTnw%n~LKk&jM z`V23>lCSKm1E7KH*Rdq612hTJKs!^&z)M7?fNf!VdN17o)(9o@g#ykp_krERSC6vO zEDx{^Fz+D7rdGHUgs+Qo(y8g82D$E5B@!#@p$j)pS-^Q+&TlB-JRYP|8cApco!CJ- z*^2%ZimwxPLNJoB!U9f4g3WbApGBPOh(0;Bi15JEiEK%w3@w+HeKvi`uSF8vrz$f6 zhRU?KB_75n7Yk#-=b{hJcG^&1G90p&+x2qD&g3Ihrq1lV7|lw-w7Ec~nLvYl!&(se z$e!jNIYaH{jh>A&g`00dlA!y467%bg_!bPg8S;~o36KG+AL^M?(jO4 zPXy6UN6YQ7u+t}qwdea!vtDlEQ%i(uu}bEj^pW+DqW)kKZTzqPgW@iYKa~K>cvw30 zOaV_c{xwsyo|#o!$xCc$JgZ<%i!onP>{?%XN+AGaXNo6oW&<<%UO3<_srH%xX;pW+ zvH%6LsjVzA1BsO{dW;vDkH=zdySKy4>@5Ou>n%Yk@VD3iJCAvv8{j*?Jl8$lZ*4SG z8UuK7#0ZpP@MUgq)>hVjm-1a?w%%#DIKyPK9RoiT*uy-NYf3Yy>X5v_ zoRv_i`~<%sdLwLhdnTJ;dE9Wq?(s(QqGgs!i(lh92$C$&rcE+d%(n%nhMyd zgR&X&-d32>95iN}&{E*&%U9?-$X2aKLeAajQzl~JI^r1=>D!vD}sW7cfMo}J3Xavk`{xM+ouF83^_4$Lf zQjx$g;k_-8i?v+hARdLc!I|9>AP)d)h+{A*1(zWEj`D$$rMWOKwK?SXGnBWdl|`^q zJSI8o|7Y(_fb6WUJkR&Ow`8U=m3l%Fk}az0%Pd7qY8r>t4jRcW_mn;jc2QIAC|pB0 zm>I{6BgPTU2%^V02oK$jiv(mNvso+#0m4Wiv+tXQ0V9Kqg~7I1EtbNvF^d6%Fq<&j z{Ql?OZ+lDTdrz4t!D*R%-+I4$&prD+=iL5e_eFpiZ#nUDxf~GGE!pt5_86&V4KcIp zoMXkTJWvP-6UU#sxE@EVg=}-j#_QNqp&m#5GPcFVH(kXBa#jA*mJo!R zT5XO(BLZnw@L2GJEgl9dB89BapH+J`oynti^(AJocTw=gA#h5gUVk0XSeMmJ!&13yh; z(%EE`f0=A_BZZ+6-N;nPqTA6zwS$L?EF*w_U|r>4{mJ0kF(BZ?;o3{RRqeGL98`tfG!uaa#iMw>iaVl+cjkaToi`HGx$#ArZ@x@0Y$y5)`9O5TtV4J-0C+hQ1h=?3sc)3)wwA6@rtC7ReZG=l)G!d{`H@`9qw zFD@-tY$0(Ew1_iTLBaH0!5T$-p%NXW8UsuIi_ajy8?6ykS;UZrVyuQE3vM`Ctfwa3 z5{acemi~i>`#=c-$SpZme?SQ$d*C~tD3E*m`Qbs#1lk%1xk|9SG}E4gxnH$E_B8z@ z7ncVVMuLukzJ~K{iE5zXWK_ULpI~ijq3hRjO3*RxxN|lFJ3ZQ^eEnyJ387eh8bM=>MX&f*S^}z zR~adP7-{c9Xr>q)8h^A;=Ik+4;^BsdFVFxeKE=0T?DmKEDhoY?I1q$TpClIGK%ikI z97I>qa)v_^*RePq3eh08uZv)TmRI|~q^9i;O=EUB;B!FUo#88gL9U%K#u}E8OEl_f z2>VOuUKi@bOFKn{LipNK(%6wWRwQR9)F*`7h2I+0sX_F-=5>GF%q2V8Hw~R}n}fY^ zoe8%hL5r(gi`kc;0B*E0_mH1%>8z?40{R+w4XgjTa&dkv*BYu=TlbORs7bb3ahD4r zl*3<&T`$%SKlpxYTl^h}6^a2tTzDOaT3=1nx`@DB<>=FZ4*QrM^{XTzhvt|xcaK90 z)=ciz6#DD#acDuF$vpxc2K_w_EjZNf*-G;;xwX+%$|X{`TYQ~1RLl1!uFuI{=eu*c zemmFaCD)g-5iJ+DaB==fA&s>An&rQu#1?+Qx`0*oURNBl-}Oaysx5t@u(60<(-Uej zQ7|AO#E%%`%pZ}x;*Vyz;!@VfAAnM5#{~1a>cDPQQ`Ldp5gV5Hqxs?9*-kBATKXCY z$S!0XW$Cd0CCFG%I7D?Jp{(n5kvK~Di=D4?ypRW=B{;OH>Gc?n!K4}6pKvpd?i03$ zmD;1)jRW2*6fi9+Ff`91B~NclRz2F<$mpb1G%CWQUGSNjYI564deLt#G%5bdJ(2D7 zMn(t=xfdNgrZ+M|jS$*QZ)5}kO>bmGgf4MB&bcwYkluUmlgqn1yIm$>z?iYEpHV>T^!IqNnj8SLUmV3PECS(N_6o+_Kg(+;9&d_|PORjX0XAnA&>Mwxcwd=3XW$5JDO^i_E@2FQ zF6T&YFgDupbN%1NBqXJliBw{N&E*cM^TTwXE@;}bY9i_ym(BbzJjnH&PS#?3qxCnw zWr#iz-}qU}Z3ouH>ZRb=Zp!S2?<>fDo`yV~GW$_VnT;!hp1kwHR32ipBqH#t3?i}e zTwFBD{~Z0+@7#thEIwi3`4(N)XsPI){=aUm@X6MNv-pG{2E%geo-_%2NOGp6+LMf% zOVh-Xm3^0N?jWgw?>v)9#kyOR*|yCUaWq0sGr(wfQnqcY$$mBK%?6KG&E=fQ(PFU5 zcidgV$K;>#BM)g}MD}p}GK;XeA;V+G-?mMj#CoS}7fL|p9ZtO#Re!^cRBwVVf z02*44nvsMYKR0d=L6}U*5(G`+{N!)QRyQZ6egT?3gg8U|N6enKU*W<*T1K+%S5&h< zSv9oj&5;x$Dq!i@{D_QzL^V}ho68R&p0!{(i1ih%>lwcC5x?&WpP1{SMr%$(TcD&! zR-WYRDlRT85u#URZ)&*wdR(nKMCVdMp(+XGo=OlMXQqauDq8e_QcZ`21=aX9Zc;`5 zy3JqcMDlg&1hX(9uSo2!WC%P!{TGVXxP~}A*q9RVX~c77FQVS4Q3l;qo?AqU9;km? zK^8mGRodwL@BNe+{8aSf`~1e11;mwb($;AE`hW$GE{*$ABCx|mad@O-QT@p-X{Pg^t3aSJfGFcd_ga5YErDCxxzG2}^7 z;uF+NNHocRPVPgW5~KdyYG^_suHkDR87<8Imec@|CwtcF4jMRTifBHBA76BniwDPN z+IZ0Tps_T!nG`S9$(~@j{s|mp*^40r34dGC0e>rH_?!9ze^W=`ZwJwO!r$PnWD;Z; z+`*2)9po6?A$bO8lH4;mbeIHsp22acp22Y`g<-)e_lLYnxuRh3YKjoOXef z!9jH~i|1}?_v5E%`)ufba|CtwJxewej8W5V@23`)ak~*oQ~tZ8p8pCZyU{O&l1yI) zB@_PJ1SO3HBsh^%Feefvv;5bgWJbeGKA)mwUc)>vbQ)%RDCsmziJjd;$=Fuka#hw= z&uEy(7gO(5m|?l%ou0wf*(|EgAPu{A!S1SVNyvI@_3*fh%yTdSYE|oR{B(E6(T11q z*B^ii`tS$vjh80u8>?ILzf)sjFRt7!|GV~l@juq>8~LB%?=%ba@^-hR@`sTH#ufhF zxw2y;`>hGX_SUNH6*0CuRi!O_cdG3LCh{9pU#ErU6uxQIc3QQa7(L>?i!*uzn3i~| z&{UG+)}r>k2_vZ1-&j3io|x9(cw_=qhNR9x-QwZ>={HQl9c%H>ESIc5UZzQ+mQ1jz zamqOowGAfqw5d@@eA&Be{eBqbY;0;=`ErIuU{fb-;p!P_7>8nJ17|qPgHzjKM61n7Z(w;x-i6`zN4A>Q6;Eo3Vb!e%a+2>NxrTAn<2`k{=2p@u)Pl?I|8gH2 z;$u;9$O`1EN{9JR-6wqLsw-SY>+xrD*ZD$R3nsp^*o(0MKQ4*P*3-a>wD%-^Z zhYXXgK24GUcetMA6O=F3W3KjJCIQX@ZQ@F%%4%O3(d&}NQ=;!+dy+$a0xE-&`6`uhbFR^_*K^wo) zwU5Qu9M0Lbq4=7cGrRUFU30Uq>9uR0iLYhey%S%hdoX#qq+CAY$v;!*4VH_wS-g*U zou)D`wMi06BEN4crR&K%affI4C0o;j5I|a42_RkpaZg*#I3xR8%74k{<|etAE|EB} z1=~*+EM_e(Yy?xu;-O2+h@Z+&?Uzix#B`SL~lHgzu z7(~vb^%2(BWQR)TtJMbSOrn)v##3j>GhUb#`nOjD?TzJA&@^!+e-Fu~B=?evni1+I zMq&ihWa5Z}W7l$H1a4n6V)%vgyk$F&$+@ix&x&OX#OZ87gb%0`Ep?Xcp2fIPEXe%V zhzUfXp7>XyR^lfDX3`q4M##Y`A}dNqkg_DkNxQv+r9x0*kkp9!279z2%_>p(pDBgW zqZmj!M2r88R@@h>Y7f6nAz*dFvhCUSLoX2yh$!dTZzj){+_Rr3uxfZ@SoylWQ96Pw z8#dbF$#>r8#6C&@Ur$t$j8+T(J!yf>J@vIu7EmF`_GgGu?Z~eut&NV-LqS-Ls1U*k zI)sVg+WkUjMH`OTt`}ejy<-!3DjZOkNZaVJ9ZcAFiCbs_i4PYL8?&(mwk*8_@iuk| zy#OI2mY8X02*osd$F#L2UWQOgS|V*iD7FV%6GE}V*p9}ObRQSr7NL|PM<_}0Z3;7F z2&Lo*g;b( z4V1DpV67Pqh?-(Ts)~B0r5bHCrKgcXF{a-CWLvsq2ni_{I7*z$l?FC z(iohb3|8qmN`OVxEyBy1sk*RnR9%xYWO2rveXcg(2$tKc$QJ3`w%Y~@v#i@f5n{nC zkh5&0;P-Lpb1NlwA1P!K9c9-(nH1g2{=t0T& z=yLJ*@pveX0frNsJgTgG``bzV<#6YoJg zD#{P4VcETLHZvr(Qbf`;>+ zH)FIPMIVEVE+c0%a47j?rk5-*PZk4+Yx0(4;lyMD-Lz?&{jgLl$7yX1UbhZZ8E>1V z;)l#I$q%}2`V1NjE-9>w=;$N7#^GFm2uo=ufgFW_DF-cPJGR1N?ZFm~+=QFgb*i$y zN;B41Zo&FAg)BLUR7{hq*BqwZr6z@^h`aR7gu7|N;BM3I(nP2?Gq>7v7rj~u%EwgW zCAQ7PlH``yI%Y~N&F*JRREwmN{4qZ@PiY3mz#9qLIj+SeW;@@sKn(GW0*v~8yC%UU~LMaNWG?I=`Dv*t?CU+E@~sNCew z;#>uw5LH+#^P{ae!*5_|DFk_Es2a44Va!Ey1?|TVLtldWGr~T>c3taheeMa0=k;>y zKj=e$wKBRl^O073{7A-zn$C#6Cb90Uz(9vh`MA2DT7m31)6@!F-!#sl6tNRhYz3<9 zrndswi-#`2MkOngRkKyBJtoU$H>#i;jQCRh`h~NjIRsCR0uAaFS>f~Hoog+n&96aSKILcu4ogzE`^+#^()#0wOTE9ol9c*6>ht)^XV1t)(bOjuW)ZF zm5OXRkZf8HhZH_c;8VkxF?#j3 zT55%vtv9a9l8eg@gd`uu)uFMxOX*_4BNJ_dr$Gj7*x)C92mhpLt%$#o37yyGdg#YFJLXiZ;_}eZ$if>waRPc3sBS{e~CZT&p4Uilx zmm*k9g8v99abNpnfeJAB^eAi1rw9_2NY7mq4F(lX|6=W-#3wrJ0+W)+X?o$ZL8#_i zI-E6zs%czz)x2#+|9XNP0nrh?VS{#$Fzx1id06YuYP5A#GbtF@Yc_ti}EW3hz-(6fH8- zBxpHglsBG&WBBV7oR1u(3I?7R8)Wlj%0#*-Z7rVw&2-+TGrISDMt7R?DlW_XOou4K z9-3zDm=e;SKW!Sb_dI4lG3_aygs6rG+L5(w$XOL^EBSIMa!UBfg;?5}(S?|GIRcn~ z15kO60H-^K)`|S5L90 zWsXP35qLD8M&Rk~*yF$rOMUYmhgJ!Oen%e^GOMj(d4F+1B6C8hD3R4x@p*sw+A3-N zBQ^oRN+ie5I}q}@e~O|`bN}iS2GUl_aVv5n)YVu?^Z&9nR>=QrsIii^r_GP7SM_yP ztWZ-4k1+0+J_jlSb>zTs&KCeumHQU?LQc2t|RquXb=So$2 z%2sIVbMBW);(j3$&zvWG)7&qNRc|Ejmr4RXiy`WZ-7l5I{eu6;wIImho~g^6tVG`Z zVzs3RII^W;bH7w-Y3gHEEnyv43cQLOxivl%7Gbj(1^RffJRyC!i(?2p+)gp3scbT9 zB*3qUx1{CjffVj`yp3m-C&_F!LXBO_;`P|QvY33rIx|K|C~q~p*tGcU|FcN7_-uB@ zqMn_xsBw|VqVAivs6AecA8!Gzv~5vO>#uuWe|;tKKxbK?oQBhp4)u|lZbiHuj`vPE z&1Y>W&C!jnvyXQd#pM)wlWc>}YB@vO3|oCZ{w`ZtyGZEr{j2`jq(=PWRFLQ;aiL^aHybQaeh&*D0Dny*fRU*Pt-a>Y$k za^)z7mbB;vF}3G|=bd6!n>0T>3O^NB@Wrzjd+eDnJfWrEI`yAV8E4L^|M9ws^VvJT zWX$Lz6&dYn{u+SWb;Unbtty;d|CQ5HlXg-HsR@@HK|QfjF`O=h8P^B_9kJMK&a+ zb=6F3H9Esg>jT3XGOZ~h%>UxZ25(wd&9rvU%CzQLZ(8?%-QGw{>uO?JR|UJow62#Z6v+2yp`-t z@VsKLZ5g%@&Rj|baDo%uoI2UwxbAhZK#YaX&Z(*J&k&jI;qHPGe8-rsSoojZB8giW z+Km;$ErUINbw^st>optfwz?$luZC$3H-+mhhkJDRSu_r<)sW^poCHH9MBX|jFLqChqE>zRbcd9n>2+&l$If$AXYrjk3|XO2da#4Rw~3P!1a zN#a&MV765a_qxLE)2i7XSIu6UUb2}4)$GPfv429{`*XcSk4@eddlHzYCL!G*k_o^ zf3iMdQmzaZnNif;L+Hy_ItIm6ch{i|7RG#4Eq(~F%t!I zKq;^@<*KLrrZyuggeVY_ir9Mt{4fv+$4m`6a%$bG2HdFSmH|0toY>2YBsGyFsmKg; z==9PYC$>SR5_6BowKq*&h)v}@CpJ@g?pUVsbZ`g?TOd4|3g!MEpXSGtz>lY$>wDh0 z{-@Xkn%H!+4A0K^IGq&BSUG!~XE$9s6bqzbfG$lpcaDhCQp18+GF zAkzWx8w|_I?=EqXINQQ-#R+g-!~%%7z$--2K@NVa_L?IN!P4%qP#~);{?VXS`Aj(gYl034FSZZB}N^4Ywr$1sWve z^}PbdxC%JPpia&y@F4$-I|o9oEb#-RQ8hz?7l-HOpXJ!^J3 z9?ZF__J0J9`b^3H8FA>J#f;h3f_T@Fa`bRz;y~EjK1mbvlFa~Vb5~V6Q?<7Jo~X{U#f5m&weKy^!N!&tS;hdbYE`A z4=V0yzDbw@?#P5MCXu~vwdOBo3(3ct@w#5)byo(qC)FixRk9i_Fdu3lgC(cSx3YJ} z^r3#V>~Ue9I9`W4?M=I$_PpyU74~8aELu!w+`$@mH{(sjGuqtLtLPZZ^IS#8IN?86(UH;$0{U3N6zTfVl@l0D z5e(G@Qv@=3Y_3J4E}UX82q@r?nIim2&T*b`Od!6_9|p~18tRspu+ zC1tMKi>~;?9xX6;oi@NQ;>7>j;PE$Ku1$qJs)0z-{J6H3>@JN*o4JMCRv|P}WT&E; zHNlGZw2raob&N|BC3{-O=&5rcbicgy+p(Bf$NGiVprppzl2B9qLW%`Wuu%m#{}XbE zz0qJGss$YfpSYO1xQvjMI(7wyoTtrwAtSQP5KV~D{vdBS@vx!-Ux=gESRlapKIhfU}dznI!L(`wY7SEE)ZoM>8&nm9G;{mMj@IGy&y zHxwF)gQ#7dRW!(A%T>px6YS2cIyz6hD{$JYWykkR#~*~o*~q)bR4a1^vAz3Yt+F$g zYf|d_v=?5h{H|6EjxwtiQ~kOjolj_MSaP2Vq0KK&haXKs_|dLvu@ZaW8>%aGY9U*A zOkyFIEtt{y-&xz&S|8D*p?US{7Fg|`<#)Q<=D+QHfz>r5%np+pQk`M8YeA`+w9uq} zl(p6nOhVnHCT!icCALmmjcmQ}nZi;9o>+>jAV*#;`s7lccL|vxM+b3*yj`niP}^{d z<(jO?pDBFSQ(m0p)vIYXq##dLtsO#Z&k>Gm*|D{2 zcPOGLwd#m9~iVE{GD@^!#sFhI(OP?(Kp!j#ix!F=G zRTs9+LksXLh3G~=i=Wabi*x_3@E=(|u;DV0WV8i0R2#BZ6!e-KrVTf_WJBaqGQwli5mN`>Kd)|KKa>%Ayfg)66pm&p6|HU5jLGu(C-K za4A|{O!B<3bK@PiSJ{r+1ncL>>BtN@ZS>@{Hj}JMN4G!1F)k3*V2&&#fv&)BzU;?r$}G?OwFJMj-lTKKGM;gf|= z)Ahe=JEWQV5sF{YEd9?(TmR9uHB$KWa6Y7YtLX!q`5V-dIHv=>djY8?$j4KFI2#_2 zBrNHM$uu_)x-m1kVd7mkOuP>d+6_tQLxyK9ErlEfUl>w|Z=b7GGQ)&xSX)XqIilI+ zZUe-Rfv=BL-I*8ucH>P)21VE~mhQab0L!=~zcY+M~hMgw5zZnP$ga1TAV>r@q6A zh@BRNVdE6J+*)@xrJ=7=f!GpxCleM{*L2xCv$=|KciUJ+yQzq8*M=)AMn~7&`lCm# z+Bx$}Tw@*lh-iWCmZPu2Q)FWD+&;tA{Bsf?@Mfw=U@$LWampT}1HF(rIOu8}Ne{QJ zDGKnRI3`b=A4albcempC@a@$bN9OhA^DNrmif64*X7cx2a-N{FX#?T}(btZMzP563 zcCnEEpWAeaIR-;QlMP4`Ln|jJxa=`v370_zJC`5;8{&P`&7cz3O0&v#dmgjzn8xf$!0cxN zR&)@CY;K9dji4=Ho~CKl!YFHzJV%^uHO?TV}M2gf&t*p;HF2TLm> zbFoT}4r4@d&DLo!)Yx@Hqp-;HvoZ4*o5zL~bIggXiIZMOY{QZ%p+xKeRl ztt4GlUsUzO`dL8{^seB17tdcq7g_Wj3AUHa0?C44n9Bg+u*jk{1&6YSgKlJifLTgM z`KQJu01Evt1*!kOcGz&)m5g+bgQ#+3RMz$zg#&@FC3|eY?vc^LY=VnM3k4ZOs(gZ- z)*F_UOTy+4N_<5>mZN@vd#tmRYfqicyfEdzFPyTQk8t$?O?eHu(BPA?NF=5XL*iRxSo*ngj>c zR%7MVoL_-Ac*repee||>1Pnd>nncZ@VRSAo&H&$|dmiwsZSGw(9gt+ZK}+qHN!PHg z;2$c*YHY9WC>%DNlvxEaYwK2d8sOs;PwgGH{FOj+jE{>=k_Gjh*9z0VWthUmW1aH< zb2mIN$_iKg{V3Fx96i#cJZ5HD6y%?g!I2`oLnxZ`1W*#e#FhomvoS8-n38We-xYH6 zb;tZZkcL*_lySrhw5LT#f2Orwm0INwMUHB>x@ZUWGz(|ps)NI_VYN!&rm+~{umsF6 z&XgCWLWWa9K}LwV!4lcX?pt6rpMnFaKajFI0wX$z)=SY>WT3m+un>4{-IYv%V0|#` zho6JAm&-_Sh;rj9VRa=8g*r?%@sb{a&L$vX7d;HrT}lAhBLLb{&Zo$-A`!A?~;0Gx^5udwOckAvgyXb{w=GCoSg$@kL@TVyehi#qz6H>Petpm;J5a3$F0DHgmZx^$?>D* z6xByr_06JsSggIe-a}f9-o{eXK=$F7IZ_o&0(OBD0HPXlJ$qBPL$@(CM~j9^4q12} zc#G-!jpLVDm8=-SF_i;c*-clh{H2F-QJ5lCs7=o$H=-l!7^Ure&<_EX=%1PYJG z4>`VQjH8L4&UYZLIQ$~iar@h>(qDytXmoVvz$$rq4eU+UDFZDL#IJ4^VM&tW9EJ2m zQtr&VTn`9?WYzyjZbJcw^5Yl%Kg%J2UIsGecW+V$VSCMzUR&kU(FFx!6pEmi1q&A_ zZASZ$w-$yAh0qFxS*hjZ0+qy!Ik*eKa!u)g$3$~Y)nnplvi|vh6q8(9^pOOyz^XS5 ztLTq}zfo{+7Smhp!Xg5761o3784le9ZSTHt8f}@kj{@2n%fOv)yQUkCE?7xAjvY~J z&L8!a+!Jg6Yw1ADFrzg+Tq-Wvapna5#B1YhVydS=>vNg=NM_&Dp+x;JB)^h9x|T z5D;@4UsD*V*A&7oHH8IE3eDvQO`*oDC7J>~%qa?Uhm6hCoT9KGuP7WSZ1ReNU|+8( zxcTt9mRA*Yt%x>i<>kmY6e1~aEyeN@uePF%_}x+Td|oU&SO8Q95EH@<7F{QBNoJF@jgom0T{ZzjRHC{qG;AkC#Ia4pZ5@3K&Z`AEoZPfW0qp2)GYSgmYQtrpgOl7r`heoy)m(kfLVPbfxTc zdbR~77DLP#c@DgE-cc`|(Jd0rWzsp3-wtKwdX%XNl=*K2Q4;Pqt3S~b*R-Q2{-B&% zyG!br+yz}*L{F}hMP&PVWO<(yKs>Rlwa}t7>>lmm7DoSR?AVyOi`iwgp2*HG+YW^g%S9eG{#)*WCmgoc-H~2ZGJ!LhWSJk{WQ4PcKQW ziN{X*2~+f`XzgYEHnS!)?8KaCt@C|XmQ0w}`L0w4=%?mFSX*rDYDVW99ZS$AIH<-F zn;cHj+9+34#hUYlfV1@7ECPBy8uO;e6=Y~89gTN651c(M8^g&(6n`o1VsA}FWUyTS z)ZG!RW=u^cLf$zpNFbIPO=p{_9C~3o2N}~@0ueEg5s1aubZ!)g=B!BMQOowI>kK|A1d@h`~t`N+1G|j6j5tG}(DKoZeZI z*s1#^c4`ZO7;~MhK#YknxdsWxoJZHZscMCxrv+j*v2{#y^#c`%F{|5hO@`IMDiiK@ zQ#Gij^;2_pXIQNL(5zZe2}eB>&Oz@mu0>HJIEXlwnudIVBN;!*&3x-S170lcF z$hX29p>6k4&*I>- z!!8kVpL|6?x2uZrfShim*%6;;?L9IkuE+Gy*hwQ(&U@73y+?7%h{3LD#)uEoYfuWL zp!#BgU?S)wXq(XH*YutoI6>QX96_fwQ3_5^U7l?y1s9g7+E)D6QtYm;C(5@clPFV? zWV~oz!p7RkNdeY8KH>&%NTH~6P(qYZeQ0k%g(9Xa%=hmsX0vU63_XpZ;V;Wce8|$$l92A1GPNkK zN*Gz%l5563gL;(tikh*{a5?i8*ShOm{wqtCmJX3B1PGh+UCeLZlSXZ89t|FQThw&> zK#C|_2V$yd9wlvTsi|9FS0N!NOzzk%pnV|CL!R^5e>F_~0)$Bi@(ZliuXhZ5nM2U@ zkR(+vB@8;5xb1V45wZp^yA0uyBL{3IPpg_j`n)Hf6MV^zPa`+cR}qAF$P-|0w@?Hnh)tdm0-CIg$l-O zZdyDdswyNvwh^AXINF2v@jBw!^3fBfMJ>>tYdH910r*|tf(XTm#CH;*a<`Dj6JdqE z^O#}-EC@wwg!HYkFFa8G2TZfjR_@iV&2E!r>V8~a&m4Exs4eOXMDagnT~In^PJw?iAS` zm7L3IrXraO;F{&tx@#~Aps#CXro|CL#zFAV)2xd{`$hM?%Wv!;fO3J#2tT~Ah#y>9 zp?;zT-}p@7Z|$h}8-G_AinczZ*Y6c7ge#-Xf08}!n)a!02QtYdg4g30|`NWgl?4HGJ0QaiVW&YHJSv9)IpMqU|+&$Nw zj*nf2Q-)oJXA)R$nGYvrj9nYwpD^rlO?m8ksV7~%smHEWv0qbsWnBISkIMisCgbM6 zDR}pq?TNdmw{J%Oz|6r}wY^jqwK7m+^I(2}L5ZJ1ZNpm`m??2t85ofEP#Neqnje_a zAN6w9C$Lurdi$dp{Pgrk@Rk9`G(58yfJqE;k?EVo57Q>T$#l(D+*)g1Z2st!7c*bt z#Z(z^VHqj^voMSrJ6&gZF&*V${HEf?)hsV2QY_1h)dk0kD~=aqqo%x=H>)WxwkQuh zmf^+3W@UMC+?RwGR~1ENJYJI4))aD*R`7}*_^zm-t%)3S5@*|FMEAJnG?`-7xA{{h zm8ptu@TW|!IQu33lu2ejqYL~glg*?>r}V9O1u;OA=s|yqGyxZ)yZkALq8|OwpMo%Y_$q%2;^_1ue+mMr<#q0Ke0pz3 znyeW`5Hv~?UX?To`RHN3?#5LvV{oITbxt`vi>PVV&Lho5FkfP0k`3cd7OB}$Vm3W0X0B9r*0QxYM;uT<^ zc!f9sXd(^(iiiV%8gKydioN&&73S~*3e4q)sh`IWQ;x$S5UoDpE)9SnVi&*kryvS*8{O_tK^W2b zRsIyj(YqV`DF}3?J2YhRd}+163qpY((Hl2gSAp zb7FtOz1RII*d;sa-~1`qrH8k=(}}^ZCyr~1UESDGt|^aQr;lODKfhbp)iZ5JfnDNQ z+fHd>$r~wpTfVm^7WocyZgnq6^dfVkOZ_PvPrURje+tJF5}x8u!AXR0^v22dF^Ohr zt}pmga8e546Yg|kaPqcQ6NZznDUXxKjbVK~+YwH-qm8AcJ~%U;2D7@5bF#P)XtKBv zWU{yrV6wOnT(Ym}{}iq} zrl!<#a?m z?eM2IsnzIv-*FE!x&1u+u)oVB4|4i5f68R5sT=(%ldewwz@OUWSEIG=bbK0Y#{)8Y zLQ@*-#v*b}c^Z6a4FB5Bj%cu6>!?i21?0kgYYm@Z7&)&(itp8mV@Bc|)o8POTuenS zwj2B@_;y9wkeG~IY!~{w;G5n(%biXPzP;ES-@4I5TvHz3t{;Qvzu6JKwZrp0PI~cM zo0a4HGY7GM#Q6~b;{320aei2gI6tgKoFA4V&JQb*r40)a=ZAG5ktGMj`HfYW#VpRx zRLRn2iezasHHhPBIbhoN3+IX!a&*wlVoqtJVZyLb0dDl9Ye*9(^X`}alnLD851B;i zOt<<|CQ}O9RsPiEL?t@UpE9|+dx}40l8^U?Zla?nN&%$+mgT%O-*Qigr3mR?|DnAwF_^REJ54dC8*cBK zY6EkQ?Pz^v8gp1Zt&a|fUb|j?ip=U=1Ln*?F?ilRCCt(34tF{}=Cq@iu0LtQFvm6J zF=xXVJZE)Bn3L5LQt$-D0(6D+pO(!m?T@+sAI*s7c)%4uKU`fHM*~E+UdyUebgBM^ z?sC(`+qI`OMUiey7uS?Wk+FhP{=8daX*xJX4paG{Zhy6jODm1(pBzhzup|J z_opaET4p%YpQ0G)bhST)3|VQ4Ui*{tXL-fwMRz(rlWIpX+W6h37}$+?b4_^+JiXdF zeBk)5nN+<_oPv8sUt++Z!pd|(SC#34o+{G?{Zyt4dZ|noz%J7TaLaT7%rad7FN*Or z(*p|C=#saDb4?vnO@WS~Aa~PQ?ll2lZl4qUDU%@a`tB}kRFfeSey2ZWQskR@(w{Op zxCEj{{3(+pPubo6l*y8R=>~twr0I0MI~|`U+7aAmu4zgW-Ed{sR2ww$r;fOCR&Y<# zgo0-#I|OE$CMJg_HjJUn{=RFPNLkdhGFzB^tcw>NSDBp|QOweKzoPgr z))?8-Dnv(!puNVgB{bB2vzCs1T-BTi<3ETW68H|}2eHcj0hUs#A3`RBBS}yAg6o&- zRP9HX*r-N=uaJFC*AE@;^LL)5AQ)p%%>_cCbwC*g{VZcxhr5R2Pf>S1{gj6paH?&K zTmKqC>%4l@aDTM#;lm|jA;X@bphTVtNx`7N!7E8av{HIOZKraQ=n5;IL5d3Z>;V8mJ9>%QoK%{99JhvEI$Wa7di64*1QU@}DZvjiz5C44pEK z{mK_&tVa%8vMEoy4mR|T$NJRuK1MlI468#W!BZnMA7nuhH6lBnEASX;2%NhjjQ)wYEGV0V?T)!*ip06OZ^687rh1s%~>BL44a|`5>+n#q;1^+$DAIm=(u_wQzRr;vb4Ib%{aJ`p_Kl zr<%1{qo8IuTdY?J=iwYx8!v`?iPNgeI2%-GPx4vd;Lt3tFm1izpzw?62Jp+!w%6c9 z4zT#k&|EOWYC`l5%`-sEOEPAQ5ZK{EdxuM6zxkKzprP zQTUgc$_;J`ku_O}I9P@A7;`w!F<+e&I5Bttq`hiqUMnCK^_|*qO_1Y1z;uw9HyQPx zA0AvdJp1V3y~4f1+QGx~=^Ir%7HG2UNnbEV&GO)!qr>@E>EYVJdj5+Gc{`jP&RKQP z@ce~>_5y2vFP;r`fiDZy1~nUlmSNos(8Z2sg?mSrLic+cyypSqmBaJGxyEpmR$UBd z^8C=pK($$*S`l8N+OWfev}csx^UpJ@Ov#|fHDRTy!NPfFX&z^hnGI(1;rh4A!dOkc z)u0`+jdKPa_}Iy05bO{xF$BXU{e(kv@C@u77Kaw78{yt`wZ2u%;G{2fC2sKJNrMYr zgZsD!|B9e~ka7ug2+4t=peDE+E)e4>hx5gT%0l3fACPs#O}3siu$IHWA_I_nA784e z4ELd45I^%&0B<2vqh{SJwX1{yvC1~Mkdb{H%;2CF{#7_f2Xn%Gm_!a{3n>l{=j(WW z*dNZ|fPoIN0g2`dhP}5YC|sb@8Q}uK0J`s;8Gzh^yCL_iaHiz`EKN+Ln!t?62xpck zbqjMpFYF!qYasd8!+UGqhyI!;_7*h_Y8E+KLTiIYRD(up#fS}J=0()M70K%vA?bY0 z(;PnJ5tae%?dEKje)bCa35Pyz7|l~7D~D(Dh9M<@VUH!+?I{!nnZ7w;_$x$GuH6hjB}1@~q6P`I~sL6f+*n2ycy-i|HJ z_bf=fLr6Pl)Znq!$gsSkp{qTCFSqu7i#r z8`uWJnZj|$%h64Xu{eZW0`qHbh25`uXt4Y&ZscrD0906BM(= zMm4p|#W`yn&pue~av!N_csX17qZ-7D0wusG#_gFvWuqbU|;h8Pak z9y0@D&?sckRBf+#LZisuM#XbhUh#CoD#nrd;6~9A)9^f`vF5^SoSLM;M0oL#OFbUe&QaIl* zp4Vcv*LNNC*K5s+Y0~)6Jl-&Uh1v~AYK*BI+5OR(tjXvMQfLJhtFnPwDmmORl(IBJ zr>7B(ae}Ei3p_3Ew6kloBEG2-uzB1k*5{tMc|JT|wBstTCxV2@UC2@VpGslvidwhl(cgRuEW)J&__dWEsjiq>ue8ZR=Ag_xegm`$c@Yaoa!EWfZSlXb@QFh2VBh@tJTiZN zc|MQ$t(cah- zM;)<>j{3?RPOMVc=Qu1z`>2h*^h8Zx`P|ICxF{QqJbx|%x`MmyfLJ$@Idd4voH^w= z`H`p%TU=9fek3%P8Odx;{77c=gpI^p4bB*(pK?L_HrOcHL1R{KB-rxtNaX6Vk>D~- zMk2qljbx@DN#jh^YV^NWE7$VL7uSw^O?+S&P>~w8MiTz z>;C|k{Y(t-u=O9$Ytnz$95XSaW&dZ+r2jK#vR6Uw+iK4DUmvml%cq<6Unjo*YTWwI zXEObtk+v}-Zlf>P|2{7J{%dvH`rn)BzZ~`kf9mYEpoAGS=>Lov@0!5yjs?OD!#Pne@_qn z@98Ov*fZZ&bN<`SLzOdIgPxDBnwSsU7>C#B>PC%*q$<+J{4 z0gq>ml2y{w`Y#6OnP5!6`t{tXT4fB?YPp&pgL>%4pdQ-yt%nIWQV;zY)I%EsJrS&B>c9$=F~bW{0BzX$5h(D1q76-mq{yLSSQ# z>p6R5%z?R9Y-Bi;)lnFjhK8FynTm95JZ@Jz9o()C2gmB_c!OJSkBm7u*NP2}#njB; z*4mjClCu-X<6RBzS}G}ZGIN{8AKdow2j^O`!EMbAZd=pA@pxB*d#=O5?Hqq_YXVK~ zm^0^EvB8Z7*_m5yXZ173<6RAIebCv=U1~ialjgRLKRDNl4Q^9zaGRSBj>p>>95lO~ zZx=|&oaQ)%Xqw|>MBp^X*+U!$qJ+mll+sY4;%POYBS|@}28@fhlcNTJw+MdlR<_GD z-hPyX2~&HmO)VznU*6O*dL|RZmJ-(tv3USXj0e!qnm#aXxB(cfa{&)qvCi0@=Gc3P zV>h7XwBeRffTyzI)-ZFJaUE$%JI1$4N6B~?$K-acm{qznXO-@1YL)Uh9Kcp)%vINd zTf$D)@NMlulKzwX&iq`ZpjfRIs_~Q@VgVvU`p3TTjXRf!;&D0?tse(KR zde%Q7H(kbKXYPL+P2%;FTWeqU73aL84fEI~Z4}csbf`A?hCWaq*VVrzlcN?MHe7Te zUMwpG={V)r)5~m2P;r^a%0iaYiG?0{x>;}Hfie!r+Ih-rlf+@4Qr_dhwx;B_acfG~ zF8gI6s#uC5r8!{vX>_!xlXAqa4V-A#sD(p7pcMTF(p~tl+VwCRmXvd^3T~NKb>dzH zyfn+&7pWvlj{a06s97-PisBb(TB*`}I@78)N>r;Q1xCfKR-&q%q#cs`rW93=WZzXA z{xaZ95Mm)ZO0z@e?rhgrPLeV1#X?^)#<|hF%oycKqN^pPF9@zwN(Z(BD{J2}n0kRO zkPgtVO0=8}GT1k)Wa|oJ&P0K+jX4wa8Q=y&BR8oLE1+C7L?qUJVMJMWH%Lx+QqP5c zoh}UepQsee$M}WTT5g>+#-aucvN>=#;*g&f3H2pLlpS;w{j2rSc#9BS)j}tOV7Zjd zL$pZ+QNEnIm0If?>xtK#F7yM43OWvGkZbRr<*sQsf@{EUd>MogY_dHdMThBfJpkQm zS6r>vbybDutp{ZnA5QF#F4F@o9t+5Y#id@5N;|J5jW#r;an`kF;UdTbGUm%q4`r!Qu7v7odarn(`xldT28BDasw+x zZsM)t1G$fgNj1)#u4ZRWyWxcVOTbJ@U$Hj&J)7nDNDI-)zVj~z#Ux)TS3cgdZv;NA z-rF8TLU*OC6buV!1nlq_q;w?PQ^KRKC71R3_;}v0J*Aie4YI{sHmk{7LOHF$v8<02 zvgLKY0Puvl;-i`G_G0PTa@)4m_TsVW(-5@(O< zBd}Wi#0YLo(fT!P%&F=>NUmb?LE}%}6lPnaVKzw82M}3&AZAo-maP0izGLYJ?mO(8 z8Z0+9q1`%cB-w^zZq>4FV;|M9Dtgjx%5F=^qRaXMFs8Z4|~ zYjg%V6Hsb$dW}p(s+q}9l6-bD*vLxfVLW-po1~^PLpES|!^lu?uv}v}EV2^mHs#sz zp!f4>?cJxZv%e;@>=lV#$5S(tnw`v7ie=RaP`!XA%+VO54Ut~??-|_0_hCT#j0q3x zwCn9B3e>fyw*t_xO@&DhbvYwFOnO>VbT{1bm4!c=r(Fp9CsEu(O0dIRd#KUN(}G_4&3PJ?ECv6!m)Sidk2&!3=9AQ5c1q|4I+w?3U-+b)`o{(W+R zrVEReGF!5;5Dbs95D%<(Eo&Hb>}#q5LUD-NgXwkOHFD+`m(~=AQ1%v=Ru_l*g5`Sj zNM(E36y`R?vXS<#AYC3cb~so#j3V((3R?7jDeU1Y7pOw<&5)}~JSCm;OC0xnaiQ5g zl)qSYkm1t~o3JJw-BBE7Rz`y7ib+>l|AB?jxb@F{)*uOvxEsMj<1D=Pd!IxHy5+i! zeCYcUT$OI}=Zgz9I0IJ{)Gps>p!!7NYtanbq_GfvTr()w3Y*UoJzH2o%@R+=Gq|Ox z-I;8OxkFTy1QW0Ct)r^_992i)qh=E#xOUQ2@;MMhk_ zRKK3Ij^+@`ItmP`CoC2xAh+g(MUu2&D^Z#!EV8o&A*VVTF@~c*yK)*=oUX^eH)_{g>ryhJkEyXj^u&2c2UpQV#etlh z6#GwCQd+kg6;vM4M-z44x;owMqwY>@`kTy||~N@ce>(vtMr><%TqHnl}%qXP+* zZ5(JWzt(cSnfzLlzPhVFp|X=PDN50%n94@aHY$4uC>WJ(p0hN#QkDm>TCZPEWv>ZL zkl9XI=k;{9v%fW+g^tdo(x!AK)*dVuIoaNpB15pop#Y*QzPV_8TH7RAxDq?U9IL^c6}t7&J3W;B7Y15Q=_wEk&9=wJ9B_+nDpOIDZ*7(5 zD%x2x9%ow9X1nrS<_f%y$8i&vA~bG64*yM|xPRf6vRtVp)jM`q7Z-|4brmDQu!`-~ zAuhMax^Y#Ug!w0p@i7l<)y|V{zjk|4*K8kdw`F^Bt-eJJr+kYh1od|L>$J!WUwSEU zr9YIv-(=kt))|=~$i&)h*bWtWYFE772g)%Ut1oBxUm0%<(-*nK(oO;LyJ zPWM3V)tlYm!6|ch88tdOz-Y78nJn2vaA#fjL#2^b@Zu*jV-oU8$cP>ddCju}j*4vw zI@yQSnZB@EyUS~xcx;;mqi|EaokQmAKzFu-Gkx2gJ;#|oXlG&H z($ZF!A47|@TYE!bq=p?VK%&-Y0gwt*m;lDEz5N~i^r44GWwg!24G05#yP-C6-dSxB6yguzW=RM^7|2EVHPFk#&jg=%lEkxZm^7?ZtBx z#r=*yWhqOGa&JE9I-|QR$6e%4=Q&;y_lkNMJx2`E1M3POKh8flWFL?2eZaM{lv6%= zy+8dJr+o5se;RViCwJZN9{xC|eDZ#O`af_Q^hdw(EidrgtT!qhF<2@o2{78~@6C6Q zZt|x!PAS>DfAmTE$=)-=Mc@#Wr=fj52W<2C2(X&CiN;vQ_1r z)q^h~cEN!B`v?0#Cg2&nbJSTqS|f-p9T}M3{&C9#`=EkpllTDL17%LHHds91Z|qBe zZe=q&izkRqZffxak<6J!T`Z998o!EK?kt`_m3wGb%AmUUqEp4b8V06`?HF7tzd|4y z!fin6q;n;FnL`w@Lzpk8haqP^KU`YKW%O^jfwOl?Sw{liw|z9}z$;e5jG8Y|(2>6F zTxVrnq)@kOs@``HQ1^yAuOvPLb=}Y>`a}L2coamxO|Q{x;eS&dCsap2WSosSKw@=7 zD;&$MBisiKot`086BE+8+xpMWVZpWaSg@bNf=1gnh$x!X1PwlGlP}YExgE*!UDgg7 zWae^R3p9|ws2Lh0HgjO54b7z#M61mIv&^Hx$!kp`KhmSYn%PT(jUElwX3*epfCCyF z+5`>2)0SwkpP@m{?A4?%NHAf6*D+aR;(BPvK!c6QfJcQc9P8{<*oV-=VZ&SL%zS0(2 z5l5%HYO-hpFi&$YrIU&&s|7B}O3lyO1Z5+jV^9L<-2>&t?7Cp<|L#E7Eg&;M-IiIb z&n|d1K~Y~9)-7oWfggGg1`ri$-&Md(cfgFUN}*_FToZ(Znt%d0n?X&GFa_O>InZ74 z^o~`h-}KrYQG$bK<#6!om%j7b{ikog-~x}NdC)m#pWt8vQhKV$;wF@xE+%HtbnE!2 zDG)XXuu=JLqvnk%P+L)XE7YVSrH~XNKM&u2ikd(E`Kx!ma>1D!zMB$xh6{k2^;{qi z;P#0;gPXtU7&mR@D3BsTIFm(~Fio8KwIYH>{0em!EHt_|g=!8}#TyZD9&GD!U^{Q; zs-IqT>D^oR?glL-Q5+3;Z6rm|3{lj9wi8r+Gy~urR2KHyYS2tT<*6S)qmw$ZxL{@N zScL<1$pN;t0IQd$4IE%j0_yW~pk8;~P4`~+yH(G5u>$Yxh6AL6s0kA=qN!s7J2G|t zGPv7@fSPPB@O%o`PPu@20*niMKL_L0zdrr#TVK2V7A^faNq<4A3cv&&dHdzxf9tX9 zzxDm5T(%BVvw*CV^ivCvf9f8PwFFr&$N}=f6E;47=ZkONa(l{7U&+a8#|=3+KKsizPkr;W7yfitY79*}B-`MTlP$Hc?;et= z)c$cQOCasH&)RwHuB*>^>^~E_4=Z1A<_z)FUe1`u z80V=^V?#=&-N zd?ub`=S)(tK=et)KLoW68vuQOJ?t|{WEnqll0@eG$hg1dN3KMFil@M~QYtI=Ogyua z0G{x6IX^4%BPY(!O5#SY$is9>JR23ckx#zmd(R%f?!mimFqWX$@zjm1$nrWjvUcrC z-N==(+{gfFTkM2yZD_W^XeV1_y%R&VwRgUieKeSetc^ML@UyKOUpndfd)K_2YEhja zc~`>!`cj$zwz(s>(ta#exZnt_b}G=U{)z4Y$5~%b#!Gv^AxRPFz{z!`=ur>*6v*ZU*`(V04_HLbX#@A)7#X z&-W!c0RQ5Vd+z(=AJ03Ey{)`Ip%dKhitnt-@SW^h)>uN+SbF1vt4YsbMOl0J5uBqj z1{1cBaCr*t91cr5rl!ZGIdGnJ?>(zddGd*0l@oAwz{XR>Xp$BZvP-jBU1moq!%D`Q z2ETBljOHc_V@-6KlGA0Lx)6@Do4U+x@pN>q%eY-rZAm|(j`GfQ8Dd78v61W=WE(7HvW>>OVyJem%cQ2xww?6ya4X6Ef z--HGx-DbQYqnK;&&5q>aL9tvs*)NYF7Y{PIh~mNV!NY#jswK7)O{0v2=ltH4=S}sP zXotHpQMybzO*vkavW!HfuM1tcA~!ehYnS_y)J`5x|63no9i-DK$Cwk1#71Q zHC^e*aQLv%w}gF^d0=v!Poo)$+NoBUDCBRXT462G3g>3_PisW{E*0}9Q>eFv%qgQ; zuMife$pGr}cAm6x?dI>l_w7_M@5HIn9WuxDVe)!;8=QQytq^F+rf9Qjt|I|!FQ>BpTR^>wLa_yt}rPJ$(h)@volQL15vYOw|l=K?yFV15p8zt zdCjdyY2Fs&YOd<5a=5zf%%5F$(mm&G`&@#n9kAV0jD7%`cN-`^*}USc6u_PGiZoQ> z>Ky#P_sgA^zH{OW7d(<$>KPK491nLJTz9hd=Zm|CWEvy+gH>6v_S)99mtJ=L$!GsI zh2&(1m+t7&tt2NPZY5FveOfWtKS ze(9Vy-$@~PO^USOd}5e5?)Sg0pdKvxhV(E-`w-U1KDyiaz{GhbZ%${RLAgt*{)ahS-Td$+Z(P0Z$M;bN%A3p`c+pci z`2lz)+u#+G?N7TW1#m02+lp7D%S0J>87N+tjr~Dj_;ndAc}a)M(Wt^oiB+M0)Z|4L zHm8~-2@+G9gfj{aDzUS+(YB)xsAcNTt>k-P3)lPD;$1NgtXrTKCL*}&fpm@$JOps(ZMMogHxPX1uyy^+uyp{y{NQO$HD!AI(DNrUYo$ zbO_p6No>Vb25lSCZ?e~NKaS{)@3QQ+Y)W5AwDkWo!AAzX8fT!=RK}m>}}juAy^)a zTPGz`f~GeLr6#vnL2&jPd;1?mffbtVQ< zWhoH|u_ludLUqK}Tyt;kEM&$yDL6LSeC(bS!kzN5)PlV=$H#VU{MoUmU;Os^1t4 zPMlAqg~mMr=3>3+{{7vE3p@V%t<4&I24mD9do72`Z=8GKtLI*G!hO&3l6T2=W7ouD zte!D67hcVkmJdrZ+*vI^pI>CWJ;!Z-a`~h0pRx9gpZ~iA${mQU$yXaPi)3$S7|OOn zTMpYMYWqo(uePsiF%2oSGCEa`_53(DH4ne=_D%O)|LZ?Il~Ta3QWAlgZ@BoX^MCo? zD|f{U#1p~Hsqov(te*nmXB$$mcB-?b@#q=khPi)WYrPrQ>l9GhZtfnEshGSYhpYEq zf66_-I)3}E|0!W3oiOXJwBIQZe{k#gfNU9mkQ(db;$#OJN4=OEL;uaN)+vCKca9HA z(><-4c0RZ?#mP5Q_6T)9eCo9qoV5P^M~?X5aIy_RPImmkgw)dBoFnelFFp9=1rP4n z^rZxllY@~NE;$9{&Cj~W$iGhf$uH$F^4`-Qz3};m-u~9=G(c|B+2j;}*KbDHz*w5~R!gX7&y-k{Od~ z3TOxCb`Qza@pN~N>hFE>kLR3y%S{h2O|*l_LCFjtrvPuSf-kzlh~y$u0b{TR2} z>2yo5l7(Tn;bRbOwKMl>be}(&z&O5p(&=nV(@T})t+_e8^TG?S`N?-5eB>Xx;w$4T z6NXdS6$2nO+Pp&Mhh<6pi58%jyCF>+zb6Oghfmq?@H<=Yzkg8z=8T{m&$XB?@=jJH zT}Ba^j;16kUGT`1*=PUy+;*+ei({Zb8qAk*grHT{J3@$~no>BBiKc8r8J+BWz}Hg< zkFVF~*Y476oN)`oWM*h`TvgjM0VW+rtW986$c99*Z;%zuvO-cP|9GhjKTnw)@LT)H z+PBu7e%gjlIVP9)^u?kglfYNMu-s8nNE!woxCD(Tk z$hnEVbYBjTZ#;R;gWo;(?uWjUYWb6HdQJgI-q<}PQxAUz(2P`V|SkSmUr#tIcQh*(iDK?ZQVmMCGK${nRx(6)`NNiAo)^8l(!eqFRn>CGfDmW zi8E#*{UzTjwTDfsI^$ z+dU*x9x&eWUZ(s9ETFFZ#{_VI=Q0%zMzh<%jq7_Fd$X)1D`5`e5z(^bh>C3K<*kL} zI`h0_3kJmwl79Hs+KU?eB-&Fm5P;S zDW&61G_ybfmn5$=s@1NzTBQmBIIcmL5L)rcqrIWeAr07vkBm}W|GmrskFsIkVbQFA z($wizri~%j#t<9-@sQh&EnDAG4Y_7=Q`z3`fziwHRMu^^BPn8T4cixRTRd%*C}cB+ zwP!v0!1LR$eB7qZ_R`rB2PoQJIZR(T;&gopZlT|-Z{oDZjcJ)2XdIX{d*Uke)}IE-*Qx9b$0;Hgd3#go?>pEaD#^J zMR3)&%?+~qpiKew@`e<^tq{7E|0oUI`E#!L^{TTTKla~ttbgjclrv0vrt@zyhEaQE zQPUNwl945nr}b$&@I>fzX{rA+;ZrW^+{bhWpdKd zb_$^C6Dd$TMb*@=^iU4wFQ0SA8Sg#t;@PjIsM-Oq=*|+lBBQ~#neXY0_$Z@-qiM$a zI$HWj_5^*%Mx!<6%nrGnfJEsX(8rzVC#nAO{%KBY&uMOL?s?mNx#fp*lXB8iSN`C{ zpPu^aW7fa2c6si^vC^Fq-hF=PWc#Iec8~U{gnT3i;m3Y|;j`D>an5@`Ng><;ujvl$ z+h8D*4aqmUhh%Cn{yGQA?W0>yz5M6fjypGXOiwx_H&%;5Y#6Q~{G{X(n=MUZbx6_G1rRG>Y#S1Dy&Nim7?{K>B3tnEG;fQ$Gf}y zGhr}W>T-dXP|>-0=!)Qr>bbV1f4_J{6#RqEsAgp2!9ZfDWA_=gduR% z3UzsOV3l92ff~s>QS=o>PP1-4L-|# zE-m3_v%9M<6!m;D+I$7^K^PUm=ZDJ+8AcF2bh%wwh)-oU;{%KX0RaP1=^tm>D8?Jx zJ}aZqo%hFBu^S`xsFIHK#f`bKZJyg|Y$!@?kFD4b9!I^kTWqNPVIi6!KX)lw6NmNm z@1@3Ge}#`nLG4*R*Z+9gXA>Ez7+i8zqG3s3v0BZYEE7|b{KWD1_-mzdr*R#R=Gf5s z?KeIB{foBTVtCfE#2jN$Cu+2%fsz8)UUTz`dno#M`$LRIjyZ4^J~y+(%xD)4NU$4? z!kN+`4C(-Q7v!KSyc?dsOL)43Lw-aC(u*?T7oT0ZqAm>62cDqG`Dd-Q?Sa$Y0by|t;9DXDX6`B7K}2)XFqqS4ABPi z1M{bM*u1y$1Q%0JaIajz+{LAeZea_J_Hb0(4gc@2V^?Jp|F2=F`G3D1Z^mo>-;17k z@z2kn{@us#HVifYZ-Sxb|IIJOPLcz-39p0&7GZ%2uPn6Ul}wm)lV-fKO~1ME%8Hoe z0fYi0OqVfPcfaVdx#DQI1zuq}UiX3uq~?GpkbNckPA2>p>P$FQ2=bVOUPQ0LiHBt=xr zTb7@wbD7}i0&rA|9SeBB5|#fE^)QG?FQsu*sG+KUYC{z%QFejo%gRudhizFze}>Ed zY%j5FPM~&e-IYc-d}gR>@6?8S!s<$+qDmB~U&yPdq&F+^9!ET(1r$BTT8hdM(~YpI zzBTrR2Qb9xP-Ub5Fz8EFS}ikMS6M%rH<Pyyx!Gf_m&hBn|qQm4l_APz?H`kMYDA z?uh|(skxlKztvulE>%)L3eil68rjX!0NSd%h9Ym-A?%k0I3+Lw)(D*eg3NsAX>`bF zzi9Mg^sd^^y>@0>o{eZ^wx>V{TMBJeX~8!>Q}|mu>ix#w6^5d%&*=4g)o8df+WaRe zZ?36dyQYroX^lD0b%Z%!j|YweQM12bmIvG5Ou$wuSE@a|eKYz82K%ET$T83#1^o2) zMS2-oWU#lI$6m+Mg}oJrz11xC@@6%~ z-U=8#tj99gi-gExZ`_vzd#f?_R)oFPM(lm&1WcKss-V$qQsQeZCue4VG#h+=*fpa` z0`$>yCqAfFHCaGAy2syT(jfEb7Jtg*>F!njlu2Cg4%Hh?rXF77?=q>gI6c;%GP$pP zJL$TgK$xn9l8-L2Ag9*_i{*=iFBcl7<>BP|B&gL`-`7RPEIvT7-F%4Fu)->pv z@-(<%4DS7E*EE<;XHYBv9q0=-`WH?Mu--9waWp%c;Q?3t{BYHBNyV9?H}2d5>wHmv zLwC6;o4)aqrfj+!6md;?6nSY36nUW|6cM#E!bHGVrUiVVKUx5_vB)7{C17rsj;XioX%n*e*@&Vf7X zF)~rE_)2DPUMR;YrGhOI<;6TSGq7^uT^~s-uS}{Y4!+NIJ4nnSr4<)L3b?Gil9=u|UCMXYQ)E332w&0-Jcc#E&wq%CqQdw}3|#Sd_| z>sozH0!!K%f2K2h%x(e|Zn^(@)75r5FL+H<&E{Iw5VH^g93_O>fXM(uPHupY#pOBt z^b%mQ0?|1inyhQCrgyY9D(Ve1Xyl*E@-j znG;}oV;-u&7?LQ#}`W&oA90`#jo8!oa7V0czN(>ESri?_Q3MM6Y zBBg-bxnniJiMTTn5skQ$>Pc#4ga)K0N0c#3kpXK98L@@1ZoWM!PQ@x_k)RtyoX}_j z@}iL?iid&?h9cMyBFa)4+AV`aD`)^ZL`7p<5y6Z&PdOWAG)9=aZ%e|A%u|ufVXA;k zus044Hjr#cNCGCnZj9=@g1saK_OMV5N3kKQ?;0HD5um!DFmFh~JX};GY*-IA5Kn&Y z9vtiupt?!H?oWX|EL2PUqL!<%nvO898XV>kpt@+dIwgt2MYRn1YQe^0ae}>cP_ReA zur==ohAla}Gg{Y~2J*&Mx5)-UyQe07K}11+wGi$}((dUsl~}Q=li3m#O}Shv-WTj04!UQ|*&!&yAis@6gXnCty;_J=C!&+_vUCY{qCgpeoXrm0C%k1Q zBw|X8LnFG$ICLR=$;P1(@1YVeq~p+qbR4?CIJ9i%Q=lb`=Ijun$egodFc%(C*g?aX z@`@25_0ia~XmmDyFT7s*AK>ySJeq~y`>&Hn?YMj#kEY}IJ;f6HgeR@|F;nA6mJk1y z@=^$Iv6zIjdz2SI->;ZTtsiujqw`WC4*(Vz46;t*2QJoZ~ z*Y|BmEKp?jL;;Et3B^EB?P)1J4nYHeYDm>X;G`)|1nKHk2S&(j@L0c_CisV1Vu#|7 z_8sJD#m}8L2>R%RJOV_(9jK!LSSk;eV)QcOZPS9b0$f{$zTtv`m4?l5f|iKkuwxk+ z?oE3nim)Il#$g?P*lnb#cLj9Ygzq9hN!U(~%quXJBUhmuuZ_=Z-oJK;d?gxO0q?HHl)uu+$ z-ekr~O;*}pT`r)`RZ)-e9P=%ND5(+1x3a68;CM@nL*RJn>(5=j<({n@nj;)1L?1X# zPeG+|TnIC`T*(~`)2v@z2ny<$F^0~7K-tV?fK>E{;Zw^94C8s`9^h%%J+)KE>X_u% zelq$T{(0C%9bm^hsnHR=Vw0Kzr4*+NloQv9#qSV{xO{SCm3aPa9tX^&Ekf8+FKozh zVho{H55i=)fyZ#hQINv=oDMVnxaLRcNLuo_a@+!UiN;REQ%#rWkoc_Hs%p1XSR@OV4} z5B(h9Lm?iApT%0`PvRQ?xu(K@!o6Y1ALDaa$R8zY{Chlt?ffpjhe!Es{OnfL&5LV1 zx~9gtxHnp?1D_*yFgsBrV?N+w1PoXs)NA;Kl9>Zd(`!VCQk{lSM17 z7(2=2Nlz*HJju+-=ZWhNeJ_8acf#SoD5NTt5bdGQ5Y#G?{&Lk^GIWQWw;Y1PRD?+4 zV;R(lTPH0cs33>*!AZX3`kcw7pu6P{nB%~LGEIhRJ^HrHqT^^nU-KCwT#WL8LmbTK z@lk8V#RD-V66(_{Vmyc0A=Ci#lC0vpOqJ^J{$6Sq1r=964}yeu-J^7jWOxx6M?M^5sniVTuR@9Ie&y{ad9^s1iZpYER|@Acy$ zPaLMVA{#(=JCdWoxeKySu@iE``7FtHh!P0#yvnkglLQS4>!~YT`IPg9)Ebz+)`SZ1 zF)P+EjHDu%B|fgrcB1^IpK<0K_D$O_qkY`9c0%R^s3rxfNc{xp*cf9}Lj+2KtTjNv zuA~9L3@waR-80ylx@Qp8x@XA_owf|>&P;a(s5(QXhg<#piMbOJscDkE z4g6c_ZGvMz859xWWmXF!aVox&C_o3yGC=Kbkf$Iw=}cA=6c7B7@3pl^UH{%$g-v5! z7+Fy9L$~<^FG}XZ$^T%g{XwGIKa*;)CYNpI!#VsArUB>tR$v+k{=HVO z=EM6ODV-0$DaD#u?9VXu0ysE%;JD~TGq6JVcuy-ah4*4{^!{Mp13vqnhEIHY2b&87 zE$oy-vqsZ`tB%MRk=ck^K`u}PxdL?AD5_$#l15UML-iDzs%;Gs^?kMN3HJN}I<9n3 zgwfMK@WU0`0h*)N(kT`;a0@(zLj4Z7p^uJ5m zkCyyO#4YGJ*H>G901nV`uECC;w0Bk~;YosL;x@ib(^aVs>XBF${={MGAVXNXe#pEzN5w-iY4N4O%TQXp~SsR1At> zATO^2;!qHJd_Pe}5nzaw4{Pv1>U}3s`kR6-Te`#fOQVYZbA-^pQG|9l->kTMBvgJNG&%vf*=CPw<{laSA5MFjsacLv7315Y?$8(B2$T2}s)`S)}A_V&x zRe!Wp->~L-W~d_U!OC5!94S6IjMqHFnj_!}Nk~g#LDM~x>@XAxco79;7#mZ75Q;mI zqypWIIw~NlOc5T03XN#MZxh0oMibIC_W(t*6e0sf)H1X8K+8xhfv%--5fbVJ8mkWL zByK|aRr3+bxk3Wyv`jB3XUcyPelt$PY6_sEqJqi{8PIDywb4C2EMX6IPwqLO=K)*!% zbE;dCc#0HEhu1NB*6N6nTPsb;8=+~JwH?Xs8ND=e(@qPj`rc@olzFGZv!nkk<6fg* z0@}ZliB^K&;3kwx|0e5E2_b@<0;p`=AG#a_`SZ|%U(JK|^WA^sH~znK z%h%t5u{~ZPl87lAtZDEWxsZ*lP$B0XnMuuHiaw-s;+H-wHeN;XDMHie8k&ai47&bi zD61$k9J**-#X$$^=-#ZWXqz;-JZ0*%=?5I73oH)M1s2nFfyFdkU@^5M3oNGS0*kUP zut0Dc9v2s8aDfGu0C0f?QvbQYLR0>^K7&d8Nb#S9AEfppdEcb;bA1NZ3~+tMRQw=M z7OAgH*~h{duFt@N8U#?n11ECQ!B1RV@%p3pbq!$wO`7!JShJj_5!nyal}V3k?X4jH zOf>dua|t*6%4Q#^Q-&KEmn^Z-j~fS zSPr}vZZfxEJ$`$yxdjXI+gr>nSn*1833lXnOUy0Uk^r8fw~R3jxMIMD_%S|{Q6Z-n zqKQqrdTZycm=?1WoA638lH}?w*!^Lz9MP&wn)WjTtloNgFX^3_>86xxbq)B0%ecT0 zT*d`P;4&^S0GDxr@La|Pf^!)c2+d_&ATYHI2um#kf>O(Xkkm3DASg!NK`jG z)G{Cvh^vv)?3Ib)#E6R}D-%JRFXh^<7ao6*dV`os>xQhmfk>xnKYT{(BMBdlt0pNa z@wc0|Y7W91%q>VuLA=b|g2WW!^UWSGp%V;{%{925z&;hnDAMQKjr;)g?AbtRcigpm=Zh_UFF%XQfd#@BhXT$e4_ z{QoLah&n4xs29t18FocdQiwI#SQ%m$JfcM40{|2&nVcm+#+m0TCTq}_BnT*=%XM=d z4nhN{R~F?Jd#4jGKw&5;NgVctS8rUe3Epm&+Fo;e zlDv}b&;j@~9f0fe1A$f-{1NV&1%J$5GmCDuDns7423XCtBO>om+(jYV#A+@tMSm>D zE`%373(BUIBAC^PxEL;ijPx^IgvECxc`b4g@F`scShIjX(c)4JT^cU%)~XDztsH>Y79s1#5f&N> zuO){jF)BTGVsUAf-pSyDa9TLoATGVQ^tIa_=yJkm`0qfAOV_T+WRa10ky@2uk#hzZ zy`DT`79n2^{1-FPw5he8WO)ikBlIQVQqq@%dqQ6lZV7!!xFhr>;fBzcg!@5X5^e{5 zNw^#ICE;e!mxOykUlMKweMz_z^d;d&(3ga~)0b4Yfu1Ettm-CAc$OHV7s8$Qud7v} zjY&E%IWd|%3^hKjHIl3|aeJ4!1$ht0?J{!clYSGU) zf_+%kb>&{Wr2U?ZU2M-q-N3;5d<(?!> zMi%45EYn&_XaG=@q7Twh^g%L;K1fB;2Z<>9APq$yB%$bo6cl}sfT9o7Q}lsyi2j5m zho?R(;S;L9EF$n(142NSc0Ch)OoDK5_Le>(VO~hQ1%WsUe9XKBp&0jBV{SpPgK)dj z+=6fw+;*8;5Rj3mZBI*M>V9D}4|5BGvZ@|)3&L`o*R8jM%aWO188AY#Vf184t1`0m zfghha(T|~z7Y*ek%}vKweLnN?NCnKxSM#CI1q2xTZ1p8671Kr<&EO~ogAXV0!V<&P zekaOvG>5_8*B$q-oo2kjc#Px3aI>%5lFq=h?S2%U+J;LeGC;z7h^GrNvGb$Y6GHt{ zNx#KQAB8aO1Yj_+j+GJ^XCho~hFJhtALw#n6kx7+$`R*)5_RgVqOBd(v1+`lD~`dT z^p2?mKuF-WIr!H@Z_rN;{@C;O&hn-e@LMU^=!{1CnXkY-Ed+i7j0Z*jlLlBH2*4~* z4}qTzZ~#=*_65ulx?8-M*p|1@~g&OHZ-*iK+0w?21<;36~-uoZe zhFph0{%>3df4O$gsaTOU6^unu#dD#Srp4f1U7y`AAJcMBm+UaNGVVk8$=pKmFc$on zxrO3k2x+a}j*jBlc2}kmAIZ2+t1^n`%9h3)`0lVc5Yk#`WH*NPJSNH^vl3>$)EZ4s z9e7at!82waqIQ3#+Wi@7_YYRP->!Ba^H{X|nAf1)$9x9uKISoK_c3Ci-G`@1mmeM` zU4D3$bot>?(&dLINtYiUB&g1T16_VtIgpjsjgwTcYMi8kHRB|eS~0pCuwG;ym?<(1 zc!*XC!AZIsm{g&=F>Auzz;xE(5RvCU5ko#S_Q)zVRF;{=p+P>pP1|b)>zr>c!9Lu! z@3W89r2rc-8ob@yf}JS$Tg)xk3d9S0%&nO=yT;st&FGz+Yi_}A42FNYOKS$(1-O0Q z+=Bf?XP8^CA;aBwn_I9W+r8f0f-PCqGIOi!87?-rU{g-l?f6I=Qg#ium|L(dYktVw z8t3e8a|mzZ0yHDT`mP-_Nz6IaihTV?Zbow)_Ov!QNt3$~{QU1n~< z{_N3d<`!Z=n74l*HII&ty!Gx(9XXP~hgM~D_-kGtT8V^u9X0Bj_RYcQXvlMZgZf?!&Qet6#+cFr1X}wx27TSOL-w&RK;| z;OoEvyuqz{VgB!At&Vxkj~Cy>sL%#!S@N}P{6|N-SBI1pe zY~ja*QG)*AF0^K{5NH0C^Ao%T6y>#CH0sSOujC1HvZoVUl+E5l?04>zd(OrSnPS+2 z3SMAwdOU7;mR@4L9kWvbKc3A+D#nlH3Tbvnc|T-bb-SgDL!~>xBW*v#iEZa}4}W2H!I;M1!QfhiD@bH^}8>?0|S)Y^iqG7s;(YCTZObQrmkHH+L7>xbGDJ@}Ja z{@P`?hDa&Su^39Ree4n)o?Az$Z-X#s1#E#LfJhcb;*vz07gI{eqkHya)+XMwUnynK zJ(`pfR$*t?q$$3S`L-PiS0b&$IWcU`Y*!N)=C3)c8ckh&4(T za>&uo6S0$?T70W_B1;n_P{Ul_2yc}NV#&v*G%uP9WWDCaoTTv#{?x`7M$ zu4XzBJ9)d|Hiv^v>JulNZdEsygxDRxB!g_TWEPV-(C{4phu`23QP#IYGk}vkOm)ie zw@~qXj%|;Y)owl)#$h=*bwg#JT(|_vb5b;lKJ7dKO|24Y6YEWI!a7ErCb1T(dK92z@*NhS3lz z0I>BK{-I*{$JrSEQ3DLS9Mwl4wi86Eun5Fi86e^%_y8*QrMJlmGMKO71QrSSY0`M2 zUhL@>HCC=-7MW_8jiFJ@IM+4ig`dB{He?%*A+`bbi`tMKX0QQZ+@UPKCC;yrzBKR_ zGWsGD+YILfx?DuAn21EKBd?Nox{Gl}lHF=y=^2o1n~@Z4z_9i7tfxJvWIbJ#S;NB2 zr>T0kHRnNn#2X zLN~IspLYT7Bz6XAdH-sWAIW?WmQc3}oAW52rMASuz6nST;2vq|eH&+^Lhzp1w=q$D zc+WfR+2r!m#>)&SPS9yr%JZ4!D=$;=HQm%CBj8R08yEmxt5J zZ|g(G(!|D4zc#ZoMQ$Z`lt(0BQ6`YUO&96<^abcLP zN0F)dY=YVVWo7$BATzanVtcGZ5s%f)LCCx{+b6={&cjpVswOh(8f~ALxg*rO2Mle? z#5zu^-yqbRm-RPd4%{o-hHicdaw^ox*funSaUm?oGa%z`yBrKDUy==h_pHqmo54Y{ z3t42)4+wl0XS4hEzs_EI}P5YHcymVbt$>4{E)eM zA~t&Tb)(14S1PPv5HfSMg+IC>6zWbuUaYj3m0&FfY_Jo-2A>0LLQiP1$1|aQ^PWMW z1!~|XGPq7mBw%i^c_LuO7LCO3;;WJ{w=2x?{;4Uj+YQ)d0L48YTXg@_TCfjRuzLpw zTVNL0Yrt->c_P5hE7<)jlVA@E)fo5GqZ)ZvMD=}x!#o017Zm1gS0rH`E~+J`s}|Kb zW|&}KKRDPUKy{OXy(R_ruuv_zLA5aB&=JDCY;c%Ifa;>cygDU`!$q}ho>&Vu_7Ni3 z3kL^#1gLISuzQTM8-W7BLfeL1{lVJ3p+JBjDv%W`_Jpg8SzsTesD5#9ut$LEl7hWt z=bi5PGyE|Z84yQhBYqV_ne--GK{EJxW=GUwIG1;+VGz7YkT+b_W7J?Sd=lQ@ z5qNa(ee$S-z9vT+IlEO|fJ?Yvv+%P@%e3S2X%ym?`C1PKVZ z+%EGM?RBha_C4&kb?E-J+?n>Qb%N3CooQ#GY3w9f%jn=S?13uFC`O2H44-10a@5RI zI3C@A?5y^A`Ot_X?nxc!BNu;MD!>k zhKic6Hbf2f(i(NtNMLeUsKNf^hHK7Dam_=&Jn8hiK6`M({~>c}N#We!)X4y(lk;A$ zTh!#jzflFTe1mY!JiL4cqWdlU-v3*9L>_+*kB-Fewg=_WTwLM_ZG=(nv=LA+v=Ljh zOn}SZp%9J4Df);;;srdSk+}K+!=+MFjgjadrVhdVR~M(Gk6mBV)5*j;`AcqP*mBm`sJ8@ zLFfDzPkQII7rQR&|0=bR>O+pFe~0ubLhKIXcM2*%sQ;$-+!CI&FqqO*PF;?AT4e)b8rtXiSyd@EFrIQTgFMhY^dn4No?l1nB5QnPg)@_Vedw z=Gt%o*);rrI8fb%Js6`gmp5$>va1<+Hni0caVp5+{|YO1Nn_XO?hX zhJu^_gDFtZ%N+Ncnk7A_{!13GN!%;S&h9fcBIePo9MjIdr;AW+We z?StB~gUbqaMRU0E34x^@mpofwI)2~RLSCz_wF4a*igx5S{K*fSH0ksf5@w7b3 z9_f9P41+bpT4o^JO`*}BqPVLcOiiWjof`Hj*bXlL6Ujd{JoR^`^{I-esWGCahF>9~ zB)1^N*wbi=ropSiUUSptclAEMZl{;c0@D|?O$nxlPii9$F00v998631`G26@qZSBF#@Tr&S>u1c?&RWoR za4;+Ee%^F*7DaeEp1iQd5Nwu=V_Na(G<{UWrCN8dPDvlKplwEQNchDU{$FQe;m`$b zGlN6ni2_>up8J9}8+P5)`J^T&a)Bcq9UkhONK{HV{bBk(r z=1&>-@Mx4z4({j1HFBpN`14psAJsSukC4%Tv+2V&(%TCcYmHb&V{0td)CeCZ`hEkB z42W*l^2R{FoY&?woU?ds;y2rJi4V5$#(c1b*GuR;Ag|(v#EZqr7rT)hX200XYx~6` zi5IJrFD~VcX?z##?tCV?bzvB~oj2AU-oP7+=C#S2<;ic(;*9}#n~Q5(*%sc|?{Qp6 ze1qiL2oZ~M9Rx>t>w%B}^0fxPJl*3~{+`UFg`>P~UK?7j#&wFAEASwI-?5ax7y&J2 zVN4Y+Su@3s1U53kMl7?-EM$N1a} zFy>mtU%-GCSUL!+^ z{ojo1`Okd%?$%SE`}L`pL&AIUfI;N-{6)iSSQr4^%HK!SUUu`^@&K+=6j*@=OjKIR zU$nfKg~3D%`Fo}s?k-*%2zI$O5cK0g0BPUKUud|^_+=NK;_o{3c6xbjD%gPQR2O>i zfVm?$1;^tq+pF+)P$$F9lsAT!%XbrO+ z(I_ri|7bl=!jo**YtU}8e4<}(7Cb*5*!6kQ+Ffs6`j3MaIc6g-YulJ|$vII!++6P< zAiO|5h2;LZ$Tr14MMq))8jXQ^mO}C2kv9yJ&i2%BRE-wKJh9$6d zzS*xGtq4pOZhQb(e2*!%AH0-MH~^`OTv3A-($^?sRO97_n2n%!ags~J%&8Q znk#v8Nt;(ZvB}0zdh;n!6Y<)U6RRk#K@Ka(Cy9XOIsGXj%5BEr1@gozAgK6#5zSrL zHr_4YaK1<-R#y2{BeNU4P7ta}@kN;=&G-hpIP?4{zDbadZ*WosPK}Z`2@3KJve!Wz zd6OU{-(Z;#m-4f=h?8O4kYSAV9(3A}U18oMmb%J_?~DfA*Mtt@+YPc-1PkvuML|Uq za3@>}kxwBhL^lwkrsxKIR*Y`IVa?GE@_PwK+`L7ro_)eK>cFHq8;qOZmbtuwJG!ab zu`gv`Tzw^{fzdNh=pk}hB=cldEYt0U&M# zQ6@C;-x8(u44SC4Y!P0;bj=)RN@bFiw&$eeLVK``o+vY!g({UYRG)O&sJ^2+gUOsu z5~YQ+6?p|>w&@ipIZ3ZT#*$uv5UqLz0=D1^xG^ZiI%iN3MN^b&CppM|fDaU#Rep<( zH7|EbW(Y2Y7llqB5ZoEri9zWE`pNNbjt~h;s8I_^VC4;Du@V%?bZ8@=5<0ZetZnckdk9p{w1JHu z-3r`F@dWHDm)KaL6O;w;qj?5y=gK`eFBTPI!l^pAPBUqOQa9ke-n_9}rIoFN7$aoXobjL*b>6@28=p@6_UeQAZluR zPLSPJ$rsoYl~NX&o&47vcM_xwW=GCXQnC4$MER*=cFfCs*dhl=!AQ+%Fa-Nz#jW0>v%X%vM`wM#dXLWfdVA{8xebI0y3LLKJZK>b6$clQpU3rK z@F~>klSB>c^l_XOIei@VMNXf~bzC)m9(b9oqaz2dYlX3(NXQ<+4R~EnhK9ax66KcS zcGwi<_WzmS-+~;>1~;n%>`k2yduFN@i)E|{GC0V*sMwc^3-t3Q&~%kfQ*0t`E_k^U^Cv*Rh^Og6*rQ{*KoS~Q z@o51J-BEFb*ajhv|8$H}kRU>VYJ-1)aa+9*s>4*0Q`N!pE_MscaYv}A*e{G!=R#kk z8il+nGz)M-I$_lX4i7`mc|skx6?54mZVrK=HQYUUUDGT;rJgX&V*5QsHJdl6hLu9! z(9_J;v8k4xdKO?+5ZlvMB51UqdNdy^f%hEsRJ~_EI#W;Km$qu%W}fCT$HCk+U30nO z;f#<_JY!0RI@NOiB7A+c4#}W{tzm?@7~1F+4%w4aqvRMy9Q8uf;DE-0T)};oE^t2$ zbFiYne?iV2DF`{tmN}`;t8=s@%t3EU+25QcXt^grxM5Vngd{n(jwCGgHY2*`L1dDD zdM=$xKz`^at-+hcP8#ZVV#{%5XFbK5Lk`B7~wE;~!GEo5Gad_}bYr-BC;YyK0pNK+WYW}Lgn zr^yadeggdAv8q3gr^?HChOtnVfDL+>UoF;ThnO_h0G*=inEJD@>M;g9jnWx#M(fUH zSPUz#vHl53AqiCZ$50=Tf(UyiPk^HdRGe4gVd)`lBDV(lWvaxHqexg{0!2x|spTR$ zsV$8n@@Y;4#xm1Tj)LTby^0?!3mIKbcp5qFBp-6`0ahw4)@=<6C)TBcp&Ljz>U$3p zo!GEsyZri%wl&B|QV_QX*$tej&V|=@%Y>;(59QK+9sb~G<>1s22hxt#ln;~PBK4s$Rjs+%j{XrsEts>=w440N#Xl?IFLAkU81&0H63 ztkzNf-5DS@V1QaLh937UM)i^|%;A)Tg>@7RF9?FfE=v4OIxC|_+ieV{96(jR?bN8o zABH*B=t1%ro|Y1H=6$DHpr1|{v<-ZUYmREsjy%ThOj=LIWJadL%$AXsiZ5adTMMc7DE-!VbJjqV zQ`|s5IG{&23AFWm(pKC!8dzM>4aHS8uf8V6u1UWKaTW1<^2Yg%d`;O98C6Lwb>CVa zuJ&_j&TQK(!)pSD&DX(Aq$}Y7Q?#OIAhKJ2bs2@;;cg;MxXVAb;`li82QSuhu>rb+qojOWaqgF?v*XYP%m&SvMWL-K#0&;aLB;vb|n4@ zfzx(c%O6JV=$%PPiY3ck=~JgKOv9c(!oc^I>uJDWJkxs zanutCn}XJno?zTlI4#l>E?!$bLCIA;krO?^o>@KNm8b@X29WX7lEd{z0M#ol^6TQ9 zDlP)_=0)V@9G*du5ZHeg#RawH#VTNl2VIVrP&OXG#3LRr_R1Lo6#|`YBP5m)8;JT?hA6Ac zkdW$>uJL0L?klCt8|{-7VQcjV7GXj}A%;1Gs{_RMH&I>PtrKK{_cf=#DJ-dv(1olV zDS#6J;!_V`vgJA6CZ4NO1l{Bv%51lv=scl7Cr*ww_`LOUq`|h@2K(*T!sT&;@pDys zi3WGtPa3*I=x0Kc)Vgi&+gXtT-8QQzV>hI)WUbc?er&xm2$iy4mRbW_FHj5d;5usE zxv#19;@D8D&REAz_9IsB7uASb%V?ZzV;RQxcsP%CyTQa3Gd}{3rCAel^ z6Y7PrA=Cg$@Sa$#Qa)08>^hKGos>|5v5%zwBh*=9ET%@@$HUZMMFwO}?O;U=X02V1 z24wU}7_7gC6x69x_zxqjTI_mV)qSKD#v7U74mMZ8%?9E54Cd2iyHLGNu?w}9LngRT zy-n&ub^koWg&I>5tlfJgcxhaa;GUt7pn~WE4($ye*~#!U&!?Ql0X`P*PJx7VVXAJj z`lDdlk8Z(((l=c9MDP~nqw2!uV~k6;wY+jADbB~7Ty|^oF-zZpb7;OYJ~L`zj`Dox zA5n)_zH1dK;6T~4Cp@vTx_Y+L?>56c1p=jv+pU8RkHca*oU*AlM;aa?5`OutYY3q) z;|w4E8pjzP_Sz=2G903X#?Y37lQtL9NdV12Mkm&MyO3Q)R> zI7TfphD-O0-ZsF-OuYn)WLaS{Ult*mESPR{;VPz-BGYM^V}SvD0zYY-Qxho8&oelu z9w5%C2QqPvPh?|qptu$>)RV$u@BfEEr`+x2-ChEF*=U0OVG8Vz1^~ON0+f)YT3nRA z!m%1hFNPs9saATb8PaCS7XRuDdyDJ&xqP9iSS%8M6kd@%saD8a7EDV}u@(RshuY!2 zP~8AiaY0@yY}nRcFo;7r5d7ntNZ15?6*sZmECSL?&+AA}7Q@0_i8-3aWJQ8hxg0}w za+zB`+cdTu19mpH4Fh)NGVaw6OfkTX-C#m|!V4fx7F;I%NL;4nu@A7)EzM61JRf~oMm zCFbklw?k1jH6WHS#sqTV=mLeqL@uq6Aa@8>Xo7QxiCkJjL2e^U2sI;mpe-jnEWSiM z`At#Vj}!zt6A2+rrKkrnm9m*oL+9oXgRRtr%2~lF6Z!|!dzf+Q{Xk}XnDRy85c$Fk zU#3GAn{gQWY%?y&uNZO&Y*I*v95S5C9CE-EI;G$5;fO{CVVTvZxMRGGXqJ9iM>Ip~ z*#0=88B)h4Ml^`o>|=q0jv1p7O&LzMXy2`F6s`&TX z81IZEFce$03n#`qdv7o~B;-zvci<@{UGV`!n#1JWiSZ8hqy>H_#yb+oN62tuyrc6Y z4WJL)FMP8V$)TQRZ-^R5~A%Z#Gr7KhCq0(lW`05TYcHyJ3`T zW}~HnFA3&k7sRfR8JA3s<3t@B>O(kp#9hc?MA*61SzdM~YJzWwJjPv3F-lGsl%QA!O1LE`Q3BKu zC4}eW7$r>gW(RsFcbdfp`vxU9jT%axiMeDCQ1Zz@D0yS}dC^sv7o>2)F9)@TmWv#x z@(!9v^JPtrzOLExPW0`TZaveEsrt&5Ru9?Vz?+1uVW0XaWU48ji_lK;nz&_wq;W7< zYZEbK9tDZZ%rFSBt`zgXPFb@I__VV^*@&k#jAT~v8D=vyc-4fwq#CD8cB$e-xJ042 zxeKRk(1OoNqQ;s7(5vR~B|lk8Q+39w1N%Tu=4Omgb4GVU&-h^^tDPL^uk=hk5 zY0%ZaCPjvFK`p@*q4*cC6|K~8F{2^rXy}Xux|@;m-?Ejh^!(9X^ywG zsalv{TnzLUuECC(9(OjtDzU$Hn(>2#OmN7PTT|Kru~hAqVtKvv%?>4;gJ{Emv1D(p1dTg#ST~M?ZGr zm(Yn+(L+))yiRMQx{Tf2MHccI!@=$vKf^yLiHq3f&`Qxyq!<7lEVs)_Q5xD<9ePlH zns3D>u*)bO_-5uu!qqwl=>)E1!(g4Z1U>i^OH+WkIQNp6l1VP6TFGaUb5*AeMZAEm zD07J)mQCi8A9fWj?w%{Ud5gRHBwDNF>whKMHL2N5*|(HL|C@bijQ0vSVGmk44^9k( zvjuXfHlSC@Iai)Qmw#7sdpdZl+xkHAxM74@IS*Z*I$42-Oq8Ip@1WaPI_hRIQ+Pjx`_4q0$GEynB<`_$rRk66 zBP9Eyvysvt4(x?gBkzx7@-lY{UE*nu1RBC(x&(LFTtSpfmw~TKIBZT?GTzXT_dM!hqx(@%0JGS*6i)H&{!ZQH5r+K#CW>C6V3{TYd@1d zlvm5X#GWp-#JVj{mpF4EYes1~u5idF*#e8A<%PsJv52FiiEjXkMW*q3 zXm~2R7?Iub4DN(7pq60^slkiZ%VD6){dUZGPeZwWCM=U`RXkNGf`N}r`*BIHz}L0FgF32?Ooc%66(4puJ1kP%<)hI?XHt?O8SXW|KM zC-$>XJfZL9g;>CxnwI6E2- zaqN&<#adegZe$Hih{jmC&krRXT5`8QQuEbT*yx%lD?nb-+G&Quo4+ne>uHe^csRP7 zRK(n}WW*}Z%dAbZEYoI-lZpkDDim@BlrkzIbiadLqp49UF2%}NDL#f90}F!3kg-H* zqD)Fd;2g|L$=o-~Q$m>nM*8Yi^qpjmk$-FZn1m6m;&3^&*sxu6rnG5!fp^Vl!@GG1@L~)6 zO#a1|*_rTad4aco)Zm3~SmImUwn2}zwPACLNW)M8wW7>&z(kijsmQ3O*2r`0LY~lM zEm3*dO?)+~r!r+#7|BtW$u8ic%N+H;u++_sb(Ybpt$?PrHP@PkhOI(R%5Luot;$BB zRb>bB1T1V&N{j0Ut!g1DT1eoL8AxQ~-ZUg?3C6pQM^|F~UINxir5p!N2|N%z)Yk?- z;;mbPP`^hMF@z#|v^?0=*Dl0f zmMz!vBCVrm9f9W9GIF&}OG~aLO=)sGEuEogL}^l#mQf@w!5Ji{mKXk~WwbJaMC6FR zhSKQ&1f>($qt~`qC~Y`1rgme_RKKR4GfC|%&eZb4nWXmU_(8x!cjQyhYi@4NHK#d~ z97vzWaA3rl}n z#*(BScfBN$JF_H7%Znt9o`()tWAD5ONh%g|T#lKbEhVNumY7JAC^7WTomOtl0|_zK zoZ_P8MSs&fx01xssFfdnfp4Wh6V^P!-h?DYawM^Y{YH|E4UMI#vE)-avH~^xbgTDHYLTn`M=;(OQMpMPo-&j*=)T-V`q4a0@C|X|N9laAd9uv^6 z%`yrDM$1$4a@Y_8PovGdF+s3{n@Gty;PbD$^wx{d`t9j!e((){z(c?t>_LthAerkz z1CLxPJB4Exb2fMBmAK>KC9MHc9ggyfe5`wvnu$vsrsI~k--Kq-we?Q2s#W}0Hytl_ z+C#)yjFUDxe-$)pcV_Ds0A2<}##u?(JW`-1}V5NB>VYrS?dsR>qXN?CsaL-1%A8 zga0@oQ$v=ids8xX*Sbg7pKGGmd2HlReQW=A`>}bn%kY zH@<%Tl`mg~L!_xtj8$Og6INlI^?i)>$s4M4jXwGKo;$jK|KP$MbF%fx7@Mi{PfhB+ zORjxj;{(sWwscnmks4#fu1m@P+Vzh;(RU-AB951zktWdr*^ z#)#dP;@Hn_>N@YD_3v!@(YQqHn<-e&+kNZC+n!w7^>PD_9b@r&D+TPs&;Rniw{HLC zvRfN)!59O3SBhod?tXgFx~|`x{M`7&>Yd1aK0N62>ueVcdaNga! zpImbKH6I_FEni~{>-H3^*PnCotq(o$y8wxAKiT2#ur}L`33SFX?tt^!7;|` zJ1JPNcyMvwhi^RnYB&yq5y@-uqVHa>m)`Y&~RLa>4IYuy$>H>--fP9(?+i@rl^?Q^3C0{n^5cPuum; z-;PVf?o7dY?Yj4`x%SC*k3Q4Dc8#$v_#g%B8~0zn>g7AnzV^cgz>cwaeV79FuH7rH z_}%jtfATNcTrkG4ew2cB(dO$vx$eTtA3Go$)-h&+T`5>k+4Ae>cb~WE>Q@^u!59O3 zQOb|)?^%A;vS%+iecSj1_NElD5AJyVo`t8}zIL1f`&4QGzVU)nA2_SG|4bWEu64M^ zm<>Ko@$8!Jjf-B{wz%v525d0K%HWd}u-or_?a3z>eY*O4*~(yyVZA=ZvYUU^^U~_B zcP{!b;}X_SQ+U1jp`E)|U-!YA;}g~wQquL}b7x$+XyuAUKgq`H7z@{?6s#++zVWih zK7ViBIE@?Lo?_T@wynE)=S3Hs)I2T`Yi1Wr@VIt@p;pC?u_kyg#RczQcg>5p?q0I) z-?9-q#<1>AX@X0yU3Kw0@AdrlTjLVe&r+~%`DE#H4{Yqa!_S6wj5WdMDOj(#=GFTb zU3%3ir;X21;xj3R{oUu=Z#e(+=id9D;}Wr7q!4@enztY9S@FuMC&#D%(sf#Ly5ov3 zp1t;+o!u85n2p#m)&wV|5WDj1lYf8X4NqQjOg5}zEL|t1@cR6sm6yE!z+>kwZXjJ_ z4D87%V4wQryvHuNZDIG;1{^!az@Cx<_T76q^jlaIP0cOXTWj{;Jc5GVv%FXv& zea^Zm*(^K8ur5p?cF`wmHr&4Ai#PwbaS7|GDOk_{{EG9wxNy#Evnn zr=^(SckirNd()#k*8Wj8tYgdsi&L=PvGwKO-h9`+yT|Di?dd6C7hkq@<@Qw%tr(xv zb7!Pr{rq>U-nn++m7l&lKBcQCWxAfY;o6sPxo_pYKhEaZF_x|~Q;6-q@%(iseR|X7 z|JS&L^{f=Er@egTvX?$MdDHRPu#T}NI6DRF2iNtjzy8CkuV#{R)+;P{y?sxk1fwzL*mF{t{cPdqH{N_+?<)=B#$$}wb5pQhynWR(&;5G!sf{L)#u%{= zrv{13pIQF#rRSfw^3TU5$F50n?8-}?*!`;wk6pH%0A%UVQS>zB^Vo7|)KeDmX8N*mIZO@#uSf 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

WejO&|h)Vla-k|`94sj3>dV*tPkL#6^^Yu2pG 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 z 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%0A(0)2jA2Y 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 _{*L-+^^b;PSVqqER~ZurusyqKq4<3tm!mf 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 z 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 R@EV##%=Od9YW3M5K^Vb=n#@W3T{1r zPRvX|14gk-zBdr~i0T8%LRFEF`eaIopSe&o?@2zYGTqR5h2^5@o|)YmvlYG%15uHx zIs_{et{3VSNL7JE+qWnPrQX23ig_0b*_3WBU=hg>o&1sj#ZjMlLM% zHd%>dGz>9}$Dw0PMXlj%=@?dF?o2v{6htjWM0osJ(lH$Aqji~djJi)N=op8SJYW?{ z9pi9|aNHOj183A&9iyob2g8Y4!jvdHYOg_QpK_m+bCA^bqtg;h)iz{LmM3_06^W&2 zg=YUfZY{y-VvzWkVWP!!bw`nk$l2^p#l7J^x~MBeNnuan%N(aGFhrR}_AQgnDkNzA z+b;`z1}N9eq?ib)ECUf$a~Bt;lkAF!WElv;hi?T5qt2*WXw{co70}08#r{f*+i0Yw z$B!m8AYjFi9x$nzT4sWb=%=2CR?$y~6VO70pv*ce{t-@6v&6L5QAw96%KKcVugjz>kTmtJb|B@_OlYi-jl7B_sWNt@ph0nNt zFh)19;Qz38U{&_bCq+7f`J~npm2eL7KoFH@;2P}WX!UhAtK5~lUxfV?y;GPEKcr*o z$0aGp_3U^$3{asg6xCcqLYs=>a9!KYQ{=!F((HXZAi7>K&gGPxs8#J1Iu7kpcl01j zH_r1RPWL*Cw`>aWmf^ux97bc}DTZ=3`Q9TozBRPrsY(oXcc|BgIVU2q0PNYo-5(at zt_=Y%LGnyMF{%r^(4=&9z(=i0jtfcJ2JXuZ6uBqU6DHdGG(G_<`)7j~5>Wb_wc9e6 zLMpWn3987drJk-LG0fPnPt4gW2Lr6BL)P^c(S6iUhSy&5wJv$_Y)y|?PitVvy5(m2 zqPzA6%yGF)%$2l9fb=Ymst*@ZNYWDkN_xtKc~GIFep8pLcA4q&VY?Is!5OjY4bJyN z)Bo@pB0V9LF>4MBE1Oh&Oc5Y&-<$36=a2Jh=9fw`;v{;lqRJ`RTKdlrg}Mjl@-oRK zD~EUB@liCJDV)^byF5%~dkS!QgKYT8RE`P(lD%Lsp4{>|c^w`%Y4#P+3Z@q*U` zY-(F-@$?}IzyZe!|LUZ?DfyTzaU|5cH9Q;UXHQf>Xk^aB9u$*c4CAZ~uw`yX_%9u#nsJkjrXuX--<2E~j&G?A zM}_47u%lx5$WI>nVN)RiKj`jAQXU(^LSQqMHMT|TiRV5%sZGmh zY*#Oo`cVi0>W#8d%=7R>%0L0|Aq032@0N*c@*x&StyqtG&-ta!$r*@=Yg#u_*D?!t z=<-;G@U!d?rLKhv$-ESp;SuYM!BkS$GC8J6T~mTuQ<`(T*%U74lRGHokfs&yJ(ZNVPFc<}cW;!0`2;b|O z``4SC7Gr-owL90J+P?bK3R}xN(AAc*ec5(0Qj1qfSC+erpw|oK#&7PD=fZC&fdYMl z8`sT+@CrD!xctSV|Ge;01OzGIlP}ut(X?Cn)a;?<#`4OcZ~b&70ex>Kl~VTft(kJm zU;phleZP5=a>hv`w;hatN<_;x#P0;%mGm6m(VoT~1i$-Rg~-Ldw!5*+E?^GFHwD4O zG`8ehe_aK%etnOI#jooby9#RQpBHc6uAJ5RtkpF9Wshqy(feoroNTYvjkE&_; zkrSVJ_oohh>AjBvBtb!wKGhs-Co!1+ucz^mrochVh3U42 zS@NN@v6v-^Fw*8cYtt&m+v71i&b50xUrFQtkxM{k1sDW?HGGCTugf*;itrm-Y=T%Y zPp;)NXjtz=BNu^Jb{9|dPU{2GKXm6fo2NyzBzs0hrB@quyZv1|hJRaU)x;c~D zYbh%mowFy4j>_?|I$F7)|0Wc7DX+b=q P9{jwFF+}f_6XOoFuw^xPAWY{(;`VMk zy&-x1sBQewSxUpY@?}C9t#ophL(6#o_*NU^TJKAmCkGhUHBctQPF&%LrdP0)Ylf*Co=jMxhr~w( zj|2p()h^J1F?}JIAR|QsU8@`Vw1dEuf$!sYr=^*Vsx$44p2}d2 z?nTi|J>gA_l5BXWn3RP`sPG-Kdj-w^aA*eS+7nlN6o1*U0%SsIJ=4`QY0d`?sU*yr zhE#e8#BvFu3&F%UD{7MND0&p`RJ;UkB3j^G?~Hm2v{^Br_Vj!!!*L=E@?|ASsSH)A zN&fT{hmUANY0ea@7XpZbD|Tjs9A;}0b!Rs0>K=JF?Yu+&6)sg+mo`(1S!oY|eG*A( zsp~D_xF(HO9j8HR6^gZa1Ou*;eraqiD;Xey#DF4OtP~=iULcYuJIg{Q*!)T(HO8m! zq!=}u21Mx@62?e>pyE)oLyRqzC3DK|1u8_iDuqgx+9Fp*a2RxKvM!D`RQhW26-w{F zc4tiQ=k+MCv8)z@x|gaICHgdG=s`GNN&&(eJyxeir+I7CLP?D>f=(@@tgw;A8SCuG z9x$DQ01sOE)tfA5jzmSzq1!knMBJ_`K?wmE6l)4w>HViMlf=6bGtU0x2XDzJTam`S zHX0-QBO=&SOpHEaQw@#z+=zcd%e|(iqZi5v2w&II?p8TeMzIUNS30EVo1qKKP1zBn z-#df!D6w2+yKMNzh*jRpu-bm9449#Rl^^4j8WHdUk2|S~`Tn$6K;AKoPa=O-yDEEB zM6tkv(^6Z9y^Rxx#k(ILW#ECebkay6828F6 zU6)JNF>J%M@tD)b0cj})5{^SBuRIV4szTHdBpe#OmDidT>$(Bt-LyR`fh(z%z&x!l z*dA8E3RDCL!0Ri#A#w&Uly-bV%i{MPv_~r~&U9QnrN>5CipKh+I7-f>Cukt@!>sF} ziDP9x)ibC}Kxh^g2&tYywPfs!JBGmnoJc{5qIekv)Xl}iGuCdFxA~yPx_wk|T}HhX z+W84&P5B?>pgJpEIVb!}{ViPiNr|e{J6!R>5=Ux*5Uo}%T4L!Vfy)U4LQXoUQZEMr zS5Xbbvs@6wYTAL5eU3awI9g$l`BrN_vvUx{aIx3Vo8l?mSX#>^L9M#hlTbf}9F_{0 zh*)B3m@;B)Jw_qD^zC=yw4oa2mY-|m!Bzv7qEue_#7Y_}(&HCFbEk~ZkuFU6-`D#dCPnNfTyCCavR-HvE3<2e?O-b)gj z@w_4r0~(3v>@7-qX~8?obGxbc6% zbyXtR9++dHBij`#Xgb>s23hk3Flghn1~TWL&i4{;bQY>cH^PkXIu}(VkcrGT@Mh8% z?HMPBxO0Dm#X5!dUM(qbF@t0RWEq6Lw5~#hP5YEpt>h0djOBe_eQZHID>|Ju`D%E0 ziBhbUt&E&956(^J0p}?83QVv#RfkOm12KACS~l?AoBC*QS2W%es`?(Hs;_=iDZj1r zkz?&B#`{#`U+}H4+IBvq)@Qt#gm>#5|8NX{S2)iF#VXaFcsAM&TBGmFRb<_4Jm)Zj zOG8v!(RDOM&&5}K+_s%KLJThP_S7j3!e`^JX@#NAM2`pzqj!>-eG!N4@?174zXl>?3XcTGlIjVt z|FZu~b`ee4%%vv{XK$59|My4aPZ#Q0Yd2=M9!FNFk=-}K}q)1 zKmyM!Ju?#24VsF_^O@?8BVwf4G;!$pPz2K}UpCkF`k=yfJVO^mDvX|?O|=Hp#tUPE z=u}$(OoafqoM2*9QPzNqJKzKD9+?;n1q4sc%gKv^Itda?AZQ!MbL1;$0p?C%T4?`(|sPYO1Pnp)77 zf6@!*GmMq!5%>1JR==0_+hV(z#u8G`UjNr>av2_1N&4Y2NVE!WQlDsC_=Zzq~&tPnFZ#?BmFi;BKfckIq2OCR7S(`xIGO!lWW%IFLKiXAbqfh`$acBe`>x;r zgp3XO0*XCuc%(u?GBRieS?k-lLLniF($xwH{ooJi$_oi~FA@?C0gM*b!rMKSNmeP| z4s3Xx9DqZaf2#-P?6sD(@3ENaGor%NceSkbn4ETEn_Yy)(c2?4N5DOz@>G3OG^lG} zH3hP3Ti3EcNP)1oFq4`g^_bG51fkYzIo?>13w_K99sa+?^02}nFJ*2|8da81x#WibRl2#cm(l5$AD8NgD;2rHCa2GA=sZ(i6CTNVTwkzRWM1seWlzWYQ@17H8P_jIJ5tBl^n_ALg%oW(>7?XnFsox8!q&mT%oR zam!8l)S>00KiCYl@B7}Ly(YU!1#L{m|G%1sOi=&1$7iLKrm~K3jqne~WN_XPH0nzx z4G`S*@_q$wK9jl(DOTp=EuQI}iCJeEI9_Ge%IO$o*0j>$lf$g58oT}!YVmnn$q+L-l1Lio!h18}}XA)IxL^h~i z(ivp~2@R#rbZe zp%VsVkCE8a6az2B8{!V*z&BAp)UPjZdB^N6wO8ba-Y^SI?YWEvbCNjS=rb04qkH+Q zlroZch$k1jsE~~=WTZGenSoTipk<<4Wpqo9fGjkWE(%)l;(${daPRQ8>YX^~3Q|?#2PsP7 zAC&P`{UErvl-C^zd=`B4T=PTJ6iJNDt65?vmVQ%*z_7-0?-XiF-awAS3l zN$XOrm2?m6TFE?-x9&CGN~xwfP2nz5Z1agnBxakmQmarT8*;#e zP>W@^Fe=(M%To?$l&4e+(QKAvvG@$JSzW=96~gV3&*`DZIYEarGUno$yoO$yz*(7a zt561#lebTrI1Er=OO+;#c_9W8C%xY$y>H`_o_So8wqqnGIOCN8ow@oRT+XWR!R4&_ z9$d~&fJ~`$D6B(dD1zAuo!kUFk@beaEr!_1VvU_p zm#$}Q%F;}t z{lD8-TpP15OQ-ZQ2w)9;PWImD3|#NH{wo$Ol1Fou3ML=CsXN|E!s_vgUAwZl^Jkc# zbSi-DPu3oA!_YZIDx=&g+%!~k)R>{yR^i5>no+V#g10TPf%iO1wOqu37lv#i_0e# zZjsG-a$g4}6sz+@b9v?Ci)b?^zj^21vOy8JqULcCZBCsJvl#?i^!$^M3?SqW$pGNa z6`0?CE*r@JeEABd>q!C_F%zCeFdkYggwaG;in10COJQ|R!(t@0TZ=JJR$f?+SOdFN zYz*+BHW};6Zjqq%a}rY0|JemStRRkEKH(zEmlns6Q@%d+l*~z%FD5ouC6E)_o1NH< z@NTzj7UuK^J9q=E?Ja6Mw7mNJTAtHy{=lc2H`!@Dwi-xKi{1F6zpY0Lk=|6^-gyH| z-P-Xizt!$2yNtcAMfMB)-OeK(ABN|v7(&j$HqBfV%xgkrbG_TvQ)LFIwQ5y)btI#V zs$J#gr!`kCtCG9|{+h+4)iMJPIQGou@evEA31Vx)G(kO*1YH5Uy@wO*_BrhK{i$Gg z_;p9`6YLCT(X`@wpis$dN>(G%IeV<8206Ud^!k)cbt^^UwCxct{WAsOjy{rZbC zrS4=hzT2L$!8bn0wS8igF?qW&BrJw%c!6wILV5xd7P2JC^5HM`L2u~K7I!%B_+TNGovmH1w(_@WVzQiv+f{J&3)702FJQ=^+t_BxLIH3j>l zyud2yE01s)yk+0yvg=e@B|H(onfY$-GiiTQ?QmNM$Ee*_mue^m(d7^o2F*3?qw#DO zefDjVWYW}bomf8krtU4vC%^X9ziuGSvTKDWdn&mmhBh$4llD$F)c25$j)zal<=+ zYJFe4<~??dLSs)GU`u>JXc5_fP8P=EFQ;44S$PylGhc83%Gn{=L4bn)B|k_Mp6UIL zsZ&iqIWZ+TF$J75PL&XDrqCy|-W`tW{VQ?EGzo#+W9))_0D1KouBFCb!KoAbyeyFW zS?pMF>I!R(+ymi@vf;Zwn7WaevT@>FBq?y(_l>*Y!)TDZ>;fuKXqOM4h1VeQT- zMC8F?*;GaPY=K%3DN|1zB4TuP%z+0c&S&`#Xpp_%6-qE)@aWbl?NL)8D#poUd}qx_ zc`Ap#`c6VdqY-jpaX=>wy%Zp~pm+$7@7E@==+7^*;os`2-9H+%uBsc!e&d+*YBkCQeLv8RO zeFHt=;D_2~g&us*cPDRwmGQDzogMuEB*&#ROfIj!?(_p!m3$ABj0f(N8#RR$Ys=ff zMaJ?xa4DUGmy#n~-d+N_J)P-NF1pML+GV_7UZB)@8&sMU_>w{I6+>L>y{TEJlK^U4<>1ykcgVO^@l_2V9vdjLx#KD(W z?*8IaKG~&08fzxIJmbGyvdi1W5XL6EXs#PecKN-?RECpX-k}>LyX^M|%yadlN_P2u zjp6)CcKL$2aHH|Xa zCDGA2PIfsHv(BW={})PjIRmGbRj^L7%c*ZD*=6OAS1x}#*=2`q)hlKc+}c>OOTy`M zm+W$=GD?%Z2kh*=7ERNOnoA z`ONBufQM3Wj*?ye&>6Vk{S-}hsc0Buvn4_#`s^pW92}GE@<#$^HQ7aZ0ha7?I7XsZ z-*7M`yBw@WqMvHXF6#q+qscDo_hQajg|<@Kg}k-q?vo|4v1FG6#){p~d(f`y2^;TH z+o9#-?^2?`{@?q|I>|17tVf@+$u4H|xOYe}t%n8WLLG)_g1T&yAk~4L(kp_K>D>;y z6DI__eee9xy2&oaY)V%1luvev@C+>G>?gbI8Lf;o7Ko|2$75P5yVn% z+4AvszNMb*LLSSRlU;uL>13C@v9V+q&aOUhl3nb?_@`8|OCmJit7b{a=X0{lU8WgS zl3k9x@n3&*$u2vdvdJ#!RJ4t2W!$N6yTI<*WOdg_ z>E#i<-@7}^xRPf#A9Cz~LnRAkE@j>oYbVu|2{Xk8@@tg6yG9lbI8jZwK_Ah+Vr9Xv zJoKGvQqAEo{_#mQD|fv8>*JGZLLS^1#qSeLs(B_ITU75#l&RvmJCoHTJ)UGN)rqdP zk)9T6blud(18x677JjBZolSQ? znG0&hGSgLmR5MR$l(lam2gl(fv(_?Lak{Oc<_nCrdIS%>^{%#-I2Z|eQMdhwH+ zbXf49Ox=r@&fvoigMGyAij+Taxb zC{F(;fH_pW;W7PD(#5aq#1E z&GCtqN9HQ$`K}&F?JE7?Ybe`|t`JwZC@B8Bu zdcYQl{8yk0f8OwUzSQS(kRuGcw+t33jeP*fFH)T{tbuY^S(9*xi1yRB9Y2}@@|>oQ z-lYxd-Iwj`PlG=4lQASqxebQ^g9Y7}53Ht!m}Z{&20EpHWxabM$2c*scR76y$Bc62 zC-UhRz$#`?9n^Q$q6r2j6tUqM*X2knPgmtl#R?sKuKeHVjcc0)E;0946I0VhF4SS!c&QTRxNdyDw2 z9oIaJI48I%-^y`41j?w=OEHQA?QnW?mkQ~Fu8wf-tBRUce7Gt*#>W1DB;9!4b32{! zOc5>Np6$K2EsYr>%QrW&#&!~nBrQb=_%&dStQrEnqbHx3XW6ETH~wW~9nv$vJ`e$W z_e1LfmW7Uh&2(8mO8Xp;9@|Nt1kw|l3*C*+-aD_=F}xSffn5A6v09O))Tpp$IXy)i zy8?X~D$K+9IIOzGpfs+>con(8bh!lS{)*|lDLzwN^*|jG>iX!ozZo($XN4cr@b99f90%Lzn@?4KZlbYJ(MeQdkmIHK9en z$!t})LnTmnG@K;Wg=O@DdGC zR)vbxPjtks;!&4*sh^>t8`Pp_8n8B2+4V76HMV#lY!hp^8`~_OMvjW3oiH3t&@bKF zY_Xy&ohbwj89;XGRAr-+?-bgulK=)g*~FQN!%EkBh6!IVf&iERp&B-ecBSLEyo#qP zdwLa56d;$6@DsPe%obav%Awm}XwrcKZGo{s=39+s#*R>@7&uSdtIz;*jEc_k+47yL zN~~gKw{3Cea4UZU?}JwAC+n8y#(8-q*+Un>qENtN+lb5hX{YQnt5*if&hGr*$UWb z)+_P@w;Q!1h*b|pUcRR&t(*H$ z&bif(VrNy{hq(5OZ@s1NQk2pRH5v(XA@`R26B3g;JVLG;qu3lXdZG70;~P~ihR3HX z%Fxr~Eei|WiRyP!2&Gl-rM3uKrGSg;RoWsVD2524$Zwzl0`xai%B8_U6sVf=__vDB z%}3{Bs4j7FkWMb6f}{$1{J;)Ji>jfj*}{-beJqYfSBbAI_@1h%)8dLs(#GVI_Tu2$ zhKzwKnd!)4=l4ccKzESve#g%5jRISD5`tN+@QNn(<&J4$K|@N~f_D=&2LDALF6>oq zAK$;LWp=N3#KoB%4lU=g#(}YCM)mtRzM&b)*QycSwpDy+sL>`RQt(5G0lFxl`Nx~C zZhYCQ705IHJgfqrA^$S1-k%03%8N3^Ry*u(=kKXb??d6vX%D916j$BtSe4>IGsv=_ zcd=h$qIl0oj;-h%iFSXA14o)K;ZPZDmEd>M=g8WxE}U1uU_QM+3!k!u!?c`mSNf5O z-iN~-R3;yG@$tCyh!qA}Kba*w)7*uEXj(ZsnK>R#%7L&B+S|Vy`uWD+enbytIkh(IWnBHr@lrn& z@$7}mOg`=@cYwJsv+H*dNYZUiYLU?k?$cj9V5%ul?6)WfwFG(}sl#~x1XernJ2=t% zT5w~#6+p2z`OqR0wKJ@!m4{^}#W_Z1^enseba;}-6O7@?ez}^%r=8;D1J@_+fbZcry($WG&N}E9d}!si1?c>I()_IDGP0xYee$ zDm3^X1HAssF<}bJ#Ls{&cHB=u7|u!>iwSLEf{q0O3l#SO1B3|R7Do&4NxW{dIO&$N z$utDP`}n+yh-doAJD>_EEQh*kc!b;F7`NM+mzz(#ZXT*QQ^hN)BUh=vG;(#T;cAX+ zjl7j}h=HEF)N%I`{-7{F)4s!)+F?vM5IJ&;Affz3xMX_=6l~(?D&aE4iFCfaaQZ?0 z1($891Hb4bTsBT&u6H=aA4QLubA-y_NCd2`KWJkE{aNtuh;ehM=G#siBYZ}#vqAR! zxruOMlGx|ELdZc}iXx%D7$QUzB}ZKtac>P_#GNw84L;zWkr@Nia=N&ZFwS-rA6r*qWeODw0$QQw zE`Pavhapm=#_^x-a(D6@2l7%f{$>cJrSC>|FXyUD^b_-uMC?l{46TW9(4)Cwqa>%h z{j-XewDc|WghPK8r!x)~>er0_kkL;3P__3;*!(EHYr3)b;AbyP3a;BbV^Z|Nr%~;_ zGWOLq#%|1b-QG7c_7!s^kB;4h`MSMtWb6?l5#GjW*X?yfW5$W`QIq`@MO}vZbPm0g zUSd<&M|{H^5%xi;b%!j>F4_&hEm*wT*kc(A&|;&wb@G}o{pin`%C0tZ4;s57pMql)5mg$5JrAW$ zfb9g9ZYVBQ49Qr9o*Or&ei)O)eluraNY}s|rz8$4M~Y8rOqCPGr!?-WAH6=76KEXg z#Xuf8Uk0)lX6_z%Jk%`Ss99qs_o6j8bIGM9HJGM-=iQE^($wnD=(afrvVx(sSO7G*E zZFid@Y9W;7Au?GI&Ic7rvnUwc$`civQGGXMO*LX=ngHdq>g8{yoc8_>@@Lc(|x!x6>q?{Ern1{LS4&Yc*G*=LekzRv>pR8YnwNclY+t5 zVvJzpjb^IYXU;sbrl0@KI7nt#|HF(BS!setG?IrAH=Vk|#P%61!hz)BiMM{>b1UhP za!4}Hd?R1Hbu@F5<7Jx#I7AXSM@>r`l(Xp11r3&7&A~^rXTr^RZWI4My@v<0BB#rqJJCd!xJ;rJN9&LCfLgg+&?1T>`F~{YaNY=OEVl*g+8*@m z=y7ESK(Ki)q}u>wNb$$ufAGyy6}~B}a~ScZI@J+BQNCP0dIehV29m>r9FJdL5Hh)X z6`4VUwbcXKe`}>SSBxq&o0LxsIwOaK-+P*7=kW!)5u-RJ(vZIscc3sl&p1o{I_a>W z`K!py+Hc&)K8sVX_epy`Sc>>vr=bq|JvHs_H~~$((7if_fu<&!qzWVN?et$$p;3SK zdJ?QDj%a(c(kvqj6tvLzX89+yP4T02_2skHyw?la7Sbgd@hXfe8@}IE&l*5Q9GSzQG%-u=dc9%F>r#eiVt%n4?@L`C<1FiHG`I* zI|?mVbI&_yMMIa>(|QB)p(*c@wsLtf+540bpsQ1`uV`RW@bndpuXf~=zM}E9j$D*i zaQdWNl=8;QMM)N6r~eH411I^n#JX<83c(g9PgXRpX$oifBec`)m8s%*Bc!^4r8&&n z1L*7_zPj;aEW=M>oGDbpI$f3hJbz%;Jf+P2HU1te4>F) z^PFabj5oe2XY6esIf^(cv;Kv9UwMbZb~7~*4)yiJD_jl&UgDvTlsYd8&RuM(cio{5 zzuRUGUN}~?CosHi zK@+HNTj=LwhP&2CPXi(QV989hpf|xN`15OQO~4Cv0ZYNUUBHxC3a((i4skBUu_DCh zM8^r>VTGEeHIyLvjDsX+&MG2ais?=%rdKB96PGks@#_o8J~u%P3bnlaB_iRifmaPQsq0Xp-&T@HQ#PD8cpL(WDqGwlTUl9 zr2M{!ED^Ev*oZUAZi9zS6w-1+iXmS*T=|k-NlxjR{8_BWZ2m0t99Z4uP>S;dK6*Ln zpZ)`H_~pe!_`x79FWUYiG7Xm2n1(q(U>c;@8q?suF%2Z1F^##g6Yo8F6YoVpq26(J z4QjyuV~1VZI-n1@?$^^gpeD?FUZDF|=g}2RJZQ1b{zB#Ru9NU#u^QI2L=G*PJ|71^ zllQc0GCD*08Y-(EicdJ0_n;c9)wEt+um~$jjB+YGwglPd6T+)?;V~QFL7J-#%q&|D z_ByBVm`d=FKWGFV`g{qG+4bQOSl+OOq#TZau^f)lhH<4cyin0e#O^(Z!+4WSNLbD< z&cPN0RbdfOCtIOF$1q9_LXMy#27N2~^ThUz{Sys}em%C~SgJX`H7Lo%!Ec2`bPXrh zC-kg^!($>dzdZJBHzO$cF|zE*EnRg+#A5)GE7UV3;@J(2n1`};ws%BZeqa(Tw;@7v zFjGYYGPuquBFG@~Ai+#P1jE0qSTvm1=i}_zy=I6|Oc;+HPaN{DkY0Qle<&zVvazhW ze(5mAyTa+(004;-^GWcoaGoySm4%vjg#%Qxp^W2gvMZX!+|E7RnSIRr?$_BLj6}lAWsW%o0Z&K>(eQcYR){W(CC0qO(ZiO%-qX9R)OhMqbGfgdH% z5)gO=zjgc%qCQ7yi&MGI!;CwZGZPqUn$|z{4ba|T&PzQISsdOTVG{n#)Q#oHC6x|b z$9LTnQnp(edR%m*{0Oszp-tg*^jM+93VnQFLs`OPWYmG=vpbt0lH(rGk3(Y4K(1r8 z5wT9N4c;gtVn?z>#3If>#JW=<;_t#bN+Om$I!wfma&n0>;m87Sd|iJb;s^9s1{e7M zkX;ww3kRNjg`a_l>1|BX9HeJjf+x*w$ro?RH|LW#4Yuc-uRnCti~7xc`|=l${`121 z{cO3>-@*US=KqWN|1$o+i2t|o|5pCr4u}}U!A;7b(_*15Nq}j`<8i;-7Qo_`*+u+1 z={GLpl7j^GxHfK|^2=v)x$2iYjJ`MZw@QFEa=CW(xv0`aC3Tt7O*pXni(DWE*@kZX zc5A+UkIp@7_#Jpv&YL5=~69twa8222{>XO zGp!G^lVivVni&uxP{j4@mblAh-mSbGtmm`KUvQZn_A9^G_QWJ%6P}SYNza%dO1qk znor*B>Bsr@eDP-ezWxv`w)++A?R_>nA6j}lK1&}rTW_^z@71%j4C11fGNf(xkA0af zKlw%1tIfX*z*wKnvA1pcmYel^+?Qu%*si*Qw4KY7ez}#)6MngkP1SgSC$+=c5+_L| z9MN6`UI1FtUINI+Fn>Gi$h8;ztdj@C6srFPDNvTds5$CY)Swc>j^2NFX*Ps)DuYo~ zg+lzBG8iR;tAwx~zZ4h~!Wt(9vUbZn>ta(t=SR0!WJn66f(;tjdQu<-=*}vLATv&> z#j8@~?oz}Yi-x&7jMeT`xjV{Krsr(ArOLq#QaDH|+KFkndj~oYch_HWcl|Z572Msf zi!Ydt{=CLlI?%sTG0{HeTdlcF$F^5>EZye+37um-_x!m|9yYA{xz;N4gU<`xlLt&^ zZ06hkTjSIEYn2JYnVfS$U7QcebkbCZ8wZ*>v3dpzm(~U=+;si(WRo&xE+l?pLqT*Oo8T$(Q=yC z-Dq{96x>1bpfj4fxs1Dz+UAq_MK>{)ZL%R2uOBoI-88^AMa!hLOoqkiQSEpV&29UD z@uT5s8aGo9N2bP058ZV0VA3v8K4T-QT;nDAcz_mZ=CM3j6T!V&!gC%)gP+)?`GkR(W zxb}JCvYqUp4Uazz^fNi-N2TdhtMHb6U9?ZvGZWr=*eVPVl$r zPDLX>v(xl2IKLL*YA;4`n!c~(q>UQb#tOn)jjGAJyjF zs}QK%5*ExEUy>;~1|n1F{c(}0)2C{w%5_Qs7W}QcPDNM3x$r~OzLsCs$FKSlu&j%s zMUSNq=f>tA^$#eRd^SH#X_vFvoic1kc^!~i!DX!!UE74Pduk{#_aa4Hebb=%zcIlG z%tp^FrMz&9@1m(@vked25-DtNqLwE3$e;Mqh)tk|9v-UUH~U_A+{)6dZZgRK&*#k&D0$ z6haGarttZ5e9?zk3AG%Ym33_XKsFYI?PkS3E<>T4Te;lo53I~;qbb!+BzS`0@~5O1_iq7K7f0V3hAqp9Jr z11OG2^2Ud$_6mHMB+)%Sk#Fu_lyAYoKH0x4U+nJ?;3XO>L_*#A=4_(5C0XPue}FzrhtD8}&E{iL1Int^%l!(iA-2#VKxtFX80RL&um{x%VRz zE2kH&4X{?XC&-`EPZolIeO==G{&rp4M3#leX}aD zLz}Yz9mydk#;PrnLyR>y&6Ym6NvkVWn|-=+QtDAdq5+IY-|$?tfr1&{wN}tpDCVTW zi~OEK(YkBP2oDf{9SGd=af;hrnHV8Fv1EjBMX@=3O;0hS(0|~Np+9;OXA~WXBL)~~ z4FY%7`1nL%uUZC%pkS|9eA5&Dr0ti!RYBhlC$gmy=xr0e{ThM0tn1zhVT$@)?Fa`- z_Gh>ks-c zvErphg)N^Zw%u~|Ub1Ts`uP>+il9_Gy2??`d(rw8`sx@gAj)Etz6B ziJ*WL0e+HF<*L;S{_r^jR48tJ;P3u?g^i#}7%O(Sui?||oQPa5I-Qp9`NW4lK<;(# zZ_G3?hn?~`JjTw)^!eRrw(4J8ILe90Yvm!Mkua&ZXoDNSMF=KL92W6uVVH4d`>b(i zV62T*W4f#dguOYa^5}YxC%}~&48b_A~ z9l`AJKbVh&`N^{(=gi84D?^%YC|9QX+tryHI8<{x&D>g4blV>*$ykg;MTV^6HdC@~ z=y0kbA|7v39Ij-#6z`uHvMIKOeS}ZITaynvXDV9Rup`F}!YzXQJHT3{R3_1Pz#1x% z%e$>%TM5+Js~T^fapyOw*#X*bLe}IRO9WH)S4fS2Ym`9rQH_2YHl5`}u=9;22xp~& z@voG&SO`X&^|Im)k#c`xdDeESPZ=GXijU-`ZsZUbq1prbxi*ClrXv>#yn)`GGEKWH zzoRBH@IAUfm&j7YJhc8pFIDFY44m31x z7=rA=8c<~pyqwzNj_I`F6!q~z(%~%d9goRQNgX< zWQJBbnVtT;F-*0WD!u?Bm3_5?VS(ElI~V?CTV$BFTqyq2_|0IuJm6bsBqBi8j`AxA z3-FJ7z=?OD;K&2c0?eAetD_y@_uF1+nWw#1S_paimHm*+;v*kyA$03F z^1sao6{N%ilq|eK21MEd$`MKgkjqOOs&GXeYp@eE$8<85$9N- z*5ce=kJY)o5-6V#x3A_k7Q2VJ{Z`qQ?6sl{m1iKM36>ei>p0?#wJQO6qLzSc*0#98 z{|~t>>&?22RltxzrhrWivCg{bRArrxZ7e`!M1glYQpLuSa}KdbGD*<3tQN`PCY;B4 z8r!3F-ua|j9KacjlCL&b4+qZ z;h1nj#*slp`Xc`haZJ6mqL-X)+TeZQWU>3>aIVSV0LPu>}S>(^aTLz(-o_bjT< z%Pod)s0IX7+k@rjO!n`)I7Vw|9%G z4b=(b9FT<~ab8#&GYp3p`BNU{C823uFbu-Wu;sPnKvB&|<8@L$|L9!BE3!4cXkA zV`eNwUfX8dQ^=TzO=`;!u^1LTXQPx#BW%Eiybm>N2t|QnElsTt?U& zrm=8jTdA?QKpLm91hM3RtY|Dw`+R?lt*1&7S{`|!e1A^zLcL!GQq|Ub9Lfy1R^_Cn z(&S?iDpr_3L-VoM+HnNcG#dGlR3_QXzl~}%ED^nH1u8b++8WJLc-q+VKe*d0=wSx& zrK;g03Vg1r@wi`%?{M4A1#^Vy{*oI#M$wi{@5>NoTrr|Vl!R=P&k690GXAM=wDShrXr~8aItSYm#%#2s6Mw)hBT0rouqs$(IT515cw^pT zHNnbOm)FKz^b&TU?GzBTLpfn@xg?OWq1?~>OkSw^H=4!!t@lrDpMjw|xv6OAYr6z*S2a5`iEiNC~dJ}o3s)Ov}uKqy_bu+2@XML#5L=0^0NijiU zX>vCD)Z)Xbl-MW`hrzej1mSZd{gx%l)bH=v;s8KmoUI&V7~W^+hhhyDL_WiyKfJg8)nocyepL$Z-kDl~)uarU z8nCOrlEx`A6I9z4zP>SFBZ6VpVRA9(ypA~l^8B41mibc)q zTha>z2 zBYc+qDNp)x^@{t8y{&Ql*GzG1<$$}Pv~7Cy4X0u*_DfkZwE(Q|SgnM4J)<7mB);-_du6B^eu?L6j~ z!$rDM?f5T~%)k#E{9z9Ldp6v@ibEJNZ(SsfSC( z&XjQ%hJ9Zr($v4N{Hn_LaVvOov%Zg&pS~|uJf0h4Ba5fXR7YleYUUN}z&Ji2IaY4x zmR^51Cn(u2V)RjEM4OXDagnHanWvzl7;@%9HFx*3{F30cqF^u#i_bRtK2+l&UTb9s zUx9}#6APkqOPB1jL*Ixdm0Ge>USpkx+$u6v{Fe9yif0<1E8cVbsMJ_H1)Pt(X$EhT z42IS9y3nchwnyN9RCsXn4C%^*ZF z6?tQf; zH7 z-?IFH4UQ2?k}xxsj1*ur(Kt(oLOE-PQUxjo#R}AR2uc;GK3nSWU}q)gF)JQ!p;c>= z8K=xqxJ#ElEm~v{Fkw!dl0q%c72+cJa89-QRDh@h{Op69k3P~0NH%*hLt{sYG8USL z_Vo4%<9hE?QM=g>v|8;~HE+ox>Rnl4o`JL3`>X;wu)!7+V))ZC7Yq&g(Iqbm=(mgR z%i-A+?7nXMq2=x^Y%rxV2s58NK&}#X`RtW^ih6wQz9s%og?fCjF>3b{3}?LXrEScn_0QW6zUrw-G)k7C50p=9wA#KR;l;T5mnJO{N}c zq1&j~MuFnC%69tO#+q9UC0k@D1Qa!nhA>la!vk9c@nRLfNc=XOD4qTIarG9t7L??e zc4b`A|GSyJ3b18>hrSEGAi8V5U^~aAn4~xBk#;e^O1q}aqYk#b6;9aFaqrpe9Zhw* z(9epmN=j(K%l{w%Whkj~-9*Dm*Uc6e^XO;&Q8vvK&*Z1c7n@>nAwPdN++~e#3zy^% zbh)$QKgDs8Jt7?-WVJ=W5y;QUiLI=vlMOY^emiTnBa%Kcm9Vt<@TVDTK_O`-3jg8H zbnsc&V zj_k?aTMQqCzqHW>!?3mDt=0)0aiz!e@bH)Pa0yga4@5pPHQyFU!MLnAVx3@8Aj2xZ zv_R!Oc%J{FHalKcs7?BO(o~~+%+nVKY@a&80Cugfh)|%r(a6!;`V?0Joxid z+$2hIpZz$de|twYz{c$Lpmhn5-WzS$%^;PeCo=FPbxaYp{a7xWjUX&|;lHCk_j|L= z=|~4B7ATwR!=kHEEN?REp;Gwzf=JCDcbN!g14Bph9(U|EkLA5p8M+2l(UsuCU)xYg z)#Zg@qVhif`t9s{GOhdds_3?7o{J?Q4CC0$8j88xVptS;*fG4%dg7rpfD#_ zJvjM3)#yCDp!aQ=?b)3%`=1xZst_4Ci^viFBaITS0`*-fIhET8(xS z#F&EP2uIM0gd)=tPePt7^hh2@c1$CHk>2~;u#l^=qxxxL^{VVnALqUWmG}2+rp`eI zdgf8c+kz-fIi?@f9*|q_{hYdhPiT7k_2?sNzl)LH_ZUuUu36gN$Amr?YXS5%j&NJ( zeZbj?ejT#|Z|r6uWI_C=F+3lNij z!S(?+j;jTM>FZYQWE?iYO9m6gs`+2j87<@>V;?a8+^2QBff102u2I*RMbY!8A-r_xy8|5`U5exl>15m`W z=9i6O>$^NHC0yOF{dBNs^)%1sHdMW2$i$nyJK~a_nCi#DGP09XY}~PW%#g2TE>08^ zuaE;L4^Q>>ngo`K&x;eq{{-n&kDJbYMZ?gNXcl7u*jKlP7qn;M*#dq_nf<`}9N0?B%p;0fIMoM0Jlj$9TgLYP)#YomnP_n0lt}DM!dV zs~P18+r@+S0K&a%pUJLGpU6i0SMT;Hnm%2IkL+ljZs+r=YDtIcSeJYwd`^0_4_cMc zBQGUO79Pn#MFz{s-nTRtu_kYrKJ%kyK+6V^9nGlh#i6*VulAqVxfwBb8nGh0kmS0z zv&{Zd+0LXICo{IogB-g4m8)B~3MV6m^(@#5&1J=_mLV$vL19<<1lS8P4_GE%x_vOe ze6qcAAfI1`3qSVQUo>929rM0GuPXlTbBL~f*G?KL?gZ20p)v@Xc5#>^mmXS@`0IfSoIRFHhJbBQOL9jL$jpS>}nbCD6 zFdb;yb$;k+Y<8b{soIQ_Ny0u z9rX&L7w}U9;U`yzn>%}tINe+8b9d$!NJsQ%uH2dT%3n)6b67X`ax&BQVRwb6A^`f# zG4F8jdH8#u{*tD70`#Z?0=-5n&|bb3y=nzz?0#VyAUml^X>9l1h5+w`jubTt>!??tb_Z{ppN7X|f=U-Q0V9BLs#gb2+AXFr zpm@1k{2D^VM$gz*HMVIQn1&A5GIZdzFr!nJ7mS{Lx-BA!tPR)xB+|eM-{;)WsT2%tABTD zX0gI)CIkh{1|)adOh0fwO(yiuOd%Bv1uhJU1SoSwisYfwCBVu4?l1uJb}CtMo48hk zJw(nU{TJTFpit#Ii`|*KM4&md-D4C%T4ICpqK-{>v^}<^*=Pv%ov8fV3GdDty8)G1 zh?DJ|y?+)$g9cI?R#={_AbXvohUlR90iH~dDE$_?+2c{1xyh#-6+ZUYroiIt2$kK&BN@AFMz`NRF zLI(kNH0~E8vF?}BxaU`h>;0FJm2DQFxyfWl_c$?qvJ?dPv#y|gF&INN}g1L3p z7^QQbnVv=8>IYCnE{~YAIZ%+As}pd{e~ElLjplpln>wb0Z>+QjSi?lDpbqIiON@=;Ck8|J_oUO$-G4w7;T48#ta53(D0hX#x(l}O^0JF!ToBuKp_{KoNH zTnRO@D$*&O0Q@Q2cSbC+_h}jO4H5CAp87${mC4fzD3m0-%hrNL8yF5Yf_~wx&_FEO(S>S!UEsS`eMF1P?4dbS3}BT{GG}n0 z%^(@#qp)`UeIyK3v5zhm>29;U?16sQ7~aG1e(bY=j_?CIR`|m)+J1oEaUgQJraeBA zC;I54h#j)#8|ovw0TeyKu}~cbg6`B{F-~xrplkvi5SX>3c;PB<48mE10=_JBTrDcnK0M!u&zV8Vsr4g$7%2h3RvcPd?B_eqfq1g0pbBLx&SIf6NUi$@_IPzaQP(aey_BrU;N-E&KH7)6wb4x@+?(V-$P z1ENH97)6wb4x@+?(P0!(B07vB$`+I%T7xAPabzp4^ipjFJ+{%ht<)>3m=SRTAQ_*$ zKj?NRVepf^(~$sAw)^1sX_%ct=oAsC zW9YP%PYSs@h0tkRL?>gsa{jC$-GvZxWPu~h61KbpBc)HaOeAk-~iDqk`yao ztbWH9bYO$llvXQC4m`_D3Hmp}bVdwZfil*~X+iH|N?>G##90$w5L8T@wYx#s@PV#C z++f~%1mPjD`DFNT@C-6~$&yY0rXi%wmq&Oufu8vyuuRW0U883IxpilQlj*XL-d`*G zFAUoz786~)?T(ltu`hbx-J;~})25^B;lSVT3yuAG*MOMc>G-_bi(9E$KnOQ@xtXcX zl#7C`UEFTBrFt}(xeS43WmAOr{Gj_bWTKTCd0Y7b!#zJ7ACOiPhGlwBE3(`XI^4WdUuF#>(Fg`FK zFX?4aG;8<5?Rod*hnDT7TWH|N%7Na$33@04_Wj3NH|FRk%4W)5;ifQlA!Cp!jNvB{ z98F=sWoZZaMuoYj5Gzt`SM|43WUrJd{2+#mzn`}hcyRha9Q6J=sy%vM2^|A!jlKf; zuTKDL{aZ}kXi7^G zYd9ne$h1+H9x4Znz~{k;T&7o+^kX#DlF$JGr^M5vHd_3u@#7(d^kr%+Uzg5WQq73I zx*GJ={YhV4I1sIUKB=+9bVLoaxTlg;>K2HAiC?YuQKQD>OfV(L(iP=-;Cv9z$DHPm z{0z7#a8_&)o;KvHI3$Oz{~Cl`-Ugy)*jdq?PAP3M%cVGRCkAA3QL+CaMaTct?Vor$ zIDvp537p=T4@nW_al7K@=0t^DKx6ETZkf5D)U?P9gCoDA<8~d(Sxi$qBjiFxZhRpH z3T6z3A*|r6T!DjgH!Ol641D~sWS$m(?%wmUOOpIYPTUPL(?ZeVG0P#(3r;8|*s2~A znvvK9-gXksrd$vqW&{vViEnfZ47y-7xxkDg_f(}$d>)MJ?(`_v>?eP&t@M$sOd&aZ z`3-}X-;FE%ME~YQ;_MjB4nb0m(VU!`o>5%UN7wiCCSq)C>?Mm{8G129Lklr(YwsQS zO&7lScL+01F3)(a+AB>RvIps}I9|T1?At5#vUPFg&feQ!PY>ZM{sO}9PBYF0@hckp zw}@2zbN?a_XjjFv`Q9dSTHqf2Fcy92Xnd7jZ45tGC?&P^gaITQb+4{^QMT6ljvRMHFxIgy&t-`;{#Cs3vySRpMnS2W;Y07_fN@c5#!g{$)KF2r7t5(kQ5oX&-5P9BDfQVUk}v=`#3@=p)nhS z|7><>V=$;F#<&0=Bh}r$P@}q%#L@>mGz4$R3KA@W0K|K23jTzltF)>C>D!4_$W;2i zvFViS-YULh4LA;5N9MdJYe<@OtN5}tBrO^++Ot%cB~y&$l9eIe03;+tXd)Gqv_WoN zu*Y;i!N?lSMAidQgHS3f7$3yDb>p4H2LSh%bf7NCi4dNuVZHdBwf8>ea+%3*5=Fzq zRC*uNhV3es!23ky5;P%$i}CQx{hNfAG0&1GPR0IeUwM#L^R+;BY&oApcjnp4y^f-h zj926d`uUi(;$w}~ez_T_c%VOu0<2u9_Q15j%v@=^e8`|bv^eEjgypNOmM?H+JzssH z1aoD@UqeHN5?R^a&?=5?aR$#9k8&9*AY&+;q)H^#d^j^$V@sPGfZ@?CmXKV-gL8ld zQ-SzO7$ZAFirH|`RC{HQUYt%&a%MWb6CT-|IpK^@%v6P9f^dDx5)N{utBNVZxD_gi zoENy2Dv4|mw@5Jm;W-SQIEU%Rng~H}N0h(!<40H6b(9JSz!r*o_RFG;NzNuIMWe=j zxDb6R!hkSEp(0wVxxeKIR|snvk=m%k>h5o1R1BjERP( zM1%ilX7@noewM{T$Ewv(dOLDy@S+p2ytR0N) z6K5Dz4)oolb?@)@yTAYU zcR@+^*Jjp2UT)6TElTN}UVUB=`@e7H9mXH3jPbKdVGm^jfQg_J3F<*zy49 zxmb`^Na>WREs-QF5$&&D!|75j5;5Lj4t^brr1XQ-wN-kgolFkdcoICr@ zx?#m;?^Iv*82{ig$$smnyn%Y8zvAFaORuAcHs zNE>>)vaZ<&X(w&9s~j>Cb+GNoq(~IrL^6>gln~5G%Waw%1&_*bk=wJaj2{UKENB}G zppsY@(|$@Zv!uAxEB;8F<0z@B=sYDy3ZKKF0t6AiqrEZsO^%Tde?(KDq=4M^gn}Gd zrPkh3NZQ*<)n4XmXsh&pG;=26yA=7tiEeL9L&{qR^HBw{oNKlS9vpH!42*{DCInS5 zHMs9yOy!hkeKfd-^#S+u z5qfhJI5LT;r^Qp1k5oj2uBi1T>Z#o^#$hjIA1;mC9>UAGiV_z?xyVmEY|foSlDHgG z`=k16&Yi{91PNtH7zo8A*j1Z4U z&c2`|P7z_WpWq=TbAp9#$`~6UN^J}>G=JJ6Bs;NMA6vwyS9S)D!?75R($U3gZXBCg z)06g#Ged3^Vuh@Ib%__o{nv*qLl(9iwCvJZU4FnlX)M$*BbN0#!#J{W&0)NAYN7uw&UL>on9+wy*F93*UH51yc>f)# zrL+EX(OGYf{LkxG>6Q0Hq!=$PmGcG&cKcO!bfn^H;p0}w{phy3Bma^$m;LskRqu*V z4q-;#v-^Tpz2JHLvKU6c|9@vYY06g8k$NcoEZa$&FH9)ik!1)=2oT`BpB!rtRDaRd z=`CjVER)oWZlGA>HFhP%XulSkX%3g$mG*_t*%eM5Sgr1A)5baSjkV$^RAgt9iIUWY ziksA{xT_5nH>n@`r#3Xbxc$xiX^A$@HP1;w(n(!|k^PgXuGSup9aM1UWXtyCapPpe zzRLxor&d8;?WSIcd2zeGJ9of7|_fnLYqo>0xBSlBwtIx6bBcM6Lm| z9@6zjwAy#*-3^qmHx!I#@6>OB`Z#qvUbaDJiu{3L;@^$LqD{@>)7J9wTFZ*H{0=Q4 z(!{KjUm>~dcTic?-NTe}Iw|!PMCe~Y6n7s_FpPKUv){L&o4s2X%)n{{B-d}~gpt{c z^DDpd_+K9SU^*WTWE5O9(tal%$#}%PQBE8Xx@U$TE*r78V&h`j2nu84k~KQ5mW{{C z#@g%0%SO|-{X-|L@o015ilm`UUnU5ygTz6%WWqT%rX7{3BCKy)P&iRoJgsatyHty4 zLw`@`Z)Y)iCNk|+!E+Im= zdc|bG1aP#B2DW&Jk-$=@9M+R$6e&=XVxf4f+;r_^ILHY=M;P7vrJp*|&!v$Q9ATVh zWWT*xy6Oux8XI`)P@~Q0O;w{-=U;)ABMJSE%pX1Zr=NH>y^#gfx?$Fq*lE5#mi=^q znfMfIP@6;bcO}i|s(miD&sC(Rc9^Ntg4u3P3~IOOIE|E4sCJu? z+G~WU_8OYv^CCA6{%tPWIG-Lta@nfm-!pV@Izn8~L^<<3xu{rin(j+Ch6LdTNmvVa ze68w^Cb*bho&2Pj%5nBEIa4Vo#&1zDM@CNUO&a|TS6g>#LbK|sX|igNH^erW$SC+OII z1pf^|vF3fY%v7`ZZLK>#AhD?h{~!w(q_cBqEh-D6Y2(Wb7|M_ z!>di6@Yo}iCV?R4&;G@4yzh$-{>-mE4HpxK5wQqKtevs!{YpdT7ql$L8@ICiw1lLz z=syi8z=JrKpv_dHf2*dK~O^12h zF9Dv`2Rll5Z+WDwDTFv#AyHi$exNXH9n9oqm7tU9O0y{RVetS{Kx%wTF(#Yq8RqKD zTx@tn;txol(qFc2NCb*)v)e)aGjJS@TuUJGuTKXe&3;yMmHu6}+9VB>+NYUhzw6jB z`DbZf5XSu7Dr<44>N$uQCU5MILI9I^>L#HK{lih0qZ|*e@$m4*Tt74@LC*&i2Gd3= z|J%UZG^_xAj6PVmp@1SDP1y|Dk{%O-nS&aoHCTVgAx|zH23Y$h&^^BD{CA+fG0@yT3wN_@J)ts#pS4lCErA9<{%+^P*=uAy9u{To$XtVmrg z^Nu7g^NuE^%o8_VOXjJ4Et#kGwPc>!*OGZ^UrXkxy+%C4GLHodRXmr>dzX0#q8GVj zKPLM`22vuCeNt5Py_)RPogn*krzZP!C&)hCsmVUw39?UjqUu1g|OnV6CP2Yq`i$QxtzARZ|pyK}hPUDE@+wR8tgxK}f0z@i!{* zH##8x!paKbudT%*fa*891jk+S_;0AeHnjOKXr?9_*+Sd#f_++N$@lk}grvPzZcX|b z?_6cLvG@SvP`$h;7fjJO$@l?hExALMnXTN4M!jgB$Z5ATxLoXSwS=QH;Q>AoC7g_~ z(b*s!c}YUL$6jpx*$_lsl6O#v6>c$}`bhJYb%sx$*;ka|tumf2uksB)V0&SyV%qwF zoPUtiCL$mc0wQJz$|Smu?~?eNinC}?#!57cN1Oem6p|+}*BA{l&}1Qrg*@m2GWgn- zgZBjgX+C-A21~*4{(QnP9^Dy|Mr`tUnKt~2e<3%H2bt{npGURMC*O#i8qsmgXe{~2 z8~a(l;WbF3mi=SLF&oYBFtq~_QD`i~JRCJP(vH}9AhJJ*vkgnkIJ)!rBkU@|(5vxy z=bL4tv3b)f?U|?OD&%p9n(&^%q(#WPYYg7f1I4#7

Hz|*xcAeMXZU7Q~HVn&;!eBMamm#EgR~uzYqS46yw6MX>zwu;!kF<%5q4_tiPS=aq`zYh{2E za04f+we#5q9vg`PU6#(eQk{v;I+*F@E)wRHL9kjRoM0{a0_+5%MI| zTuitu7n!r=HwXo~l~FYy6zFx8P}mrR0(|uc#9T|EfKaLlg_A9p3MMeb7+fAqWDf-f z4SIdV*$1U3voiF3KVe!Le{>HxLod6p z^hV9Cp>|X8)b3i3o|mXPjF`TzX!N?x1I+_bZ;OA$&u%lvjm_}8(;rl!^yYV`Hy+Zz zH-}$09m?3`K*-Yyr_-6KjCc}J)f1oi4SnFhm-+WcRFuooK<3ixu$;%rvWX)|^wi_G zBUDLBVp4)fMRBm*=Hn6|Z|(U_Z6Y6vcky)=J*?%oH?JL^m#AT0(+F`cS{6 zv9}IUaGn7Yt@B~NB8cki0(R7jdq=*uaOcL_ow@XNq4lPS%?{6>tJy}B@KAXUfL)*< zXeX^MSIIou5U6=q<2v=1XQ%IC|Iy;-zWk!Q?$pJhU;eYkU3bnef9l9R$(=6BarGZg z2}${|R}BN?{Gc|BfGOGRD0%7YBp|O!Z{kxYAx#toAaF?C^S@i~vGVr>5hE78V^MjS z;wjZx0NPKqda&nxjax&FpRkdO^dEYDQXNp}R|_6b=+a2RMz5!}(Tj)}mn(Le>hc-8 z)M95Y%QE#o^q)SVmmlTY@=DJB7`5l*k}V#6k~{2i=YLBRho}y$Mjlr~k#@e-au#z4!b2BL(c`tiz2@s?&?T^h;^j>7k`%5FZ5uPBw(GSam z$64%8Wzjjna>$GLPuu zjbxc^e2tWE`^QvfuMI-*fDa_II5Sb30mo)7hiKQcYM8goFF3&s$mkrp5s@Af1m$^rG2rcOTVUBVQSbKthllv`pjRXZuLYb zJYnBa!)RzIJB^0q7QG^-7@x3L)DWMrSJW^O`Y2zCPuS7$dLj!?*lDF|NJ4`h%&3NT zXs{gsYG{Q9%eq%XP$-pW?*nUwC-99&%TsbyeD1uKr%Mc%8qo%org4rn*B-V(&!vPl zSQU%S()ox}A8JAY6PF&5ATXbw79m$(GZ_?xKrxP_Mu-S*) zH>?s~6o8S(MLKVvH`4>n19{r#$OKd}iqUb8{VZev*@Wr2osZ&*?grK75w?u-zb19; zl{ac$-)=F@b7OSSjy`JDN(<2)Q?$m{S#X|agrR@3<(JZh0^+3AkQ0HsCDF#9R+;z4 z;Bi8)ncDKJL_mzcg&pB1>lt-VJV!>|e4_J?5HMPkI_izQae<=El_#4DD7LXzkzQn? z?n-1|1U(3Yb&Bxhf_iw@l?HmPhj)F=)3`=>mk`Hjk#lg!c?b-KI6z>}mQkbCgjH6_ zFrtE1Y<@23$D@OdR(G+93oarzM5>S!jP9I~>H5w#2-HgE5!shEXz>jFMMITK-)CW= zsiEIzDDEd0>)Mf(MdRqb)F_)zpBJ+yw&FCA`BJ;yb zq5%V~Tvv=)$PW7qVv8wSuehKTEs=3OA?z@x)~vC!`zK1Zz#eXh0dgooBQT-W`Bcik z_I#=iS7s#l^ooaf#3JEzvX)=?e5zDB9t;obrP&*+FVb$*HY6(n zN<21pW803)2f+BlBes?g!wXjli^EtTTFXaTm9Q{d0+^HbBMNto-~1h92bC>a0%#&= z!&RdYWunFdr8appWh}Jpq)rENqZff?$Z;TiSsNz!MaC7OsHuF+5 zH#ekMiO3(d$}Vj{)%duB-K1;?4Rw{jhF|eK^RLRJs{vXOb-0W_Ecs`)xdp{ha13xwb?2jTCuj; zq1=$6SA1H$yT1JYgod=}57rL2kM9Mx&e5Zh?^}$Ud6v6b(Z<55hydy6xb{K~G6G-0 zFDx4fNoOWAq?-nrA?;z=0$>P_I%{7{*JSPYaOj9zd=+bF7RyOZmOPH(+CZ|1UrXMt zx7DDg1z)7rGpO^(f-+82S9{cG&eFQtqekTPT_z_#rC^ zb~T*A=F$8pA)riC%%pyZn-a%owq{4Ij^oPdtcqb;5Uu;djD2 z&YR2Lh~Y-Uu`gk{|yW1425$>qu2-v%y}aC=MCntyNT>I z7iGi29C5mJIDXA?~th^dzPB+dO5bAeon} zP=rQ&{un85QcejskVS+5KIB{En~Hlv7fR-(2SLE_nf>LP@t?8Iem`KQisj?bef)`&T9)wYt%M^aR8%%x;+k?-g(C4_u~qcM5$t$9+u!2;hEOp}%-}G>ujYm#eKEyR(&qo8#t68dy-%Cg zEyI(PMu@psL1Zjhp_s996Fu2~{j~Uh1`g`7?p^rml|g=V6P{tI0Nn9$z-T_JD%0dl zCbG!{u$z=F*(e_R-0v?odTD{H#6ySbZjdFWt8$qbI-fMb=3nPbh*~yN{6753#~HX` zai4!opTE*_cVT*5@1M!b9X#M9d==^a~EzAwUC zfqNJ;sH;Y{(CAZori`RES8hDXPvjmWZANmBkv1ba2JszvMYvTMWHKd0o8vmPdQf(3aSJx&lfx)G(qikd9 z6@RRro)(!6#iZ9uaKI+hox77SBBt@p8Kt#W?{U~8fs?9+pWRZ34_D0W!A`B zDIQ=u45PWvhqN7)eSucJdeCb?YxElEf#@}`GwYMc5~^x*u>iY3{8q|A1P~6dZ^voB zRV;n}an|TYU-+!q5RxZ$47jS&LILYRsN2MBF3hU%c~{dc95OXPIP7YmdUzO^zd}P= z^aqWxXIrHOc9u&!r=IZfiyMa%Ko(A@kJMV(V`qtvsw_C*8uX~VumEQ2S1=NzHz@>T zMu(m?S5g*jw%=LstM7M~ZyVgOSB`vY6e%c?NN1djpEPXkEIhAKsE2IXIw~CcCBm@C zNg^msWh=xn5-?;HIBFg(tG=PmN(?%um|r3&x)?krK=}t5&!=c1PWoCA?^Wq5P<_Hf z>G#qEMp)doMpez1OYDbUEAkNiZY*TaygP6Kv`bElrBbopnoGY|Iw*e+3^)>e4+L?< zNa^bfsHFJqFNk_aOR?p!F<;V(iKxB-FDlJZNEox!v$=#pcE2gJYkemf5`k>q70`zJ zv#z{o2M7;L1)!URkBRKlTo_s(MfZ>i-JILKz znL!XOk=v^Wnd|%$9cYjPZ^5^3V%Ufy9BF%-ClWk)6x4TNppo+h>zQbc&x|(?3z?DJ z$VHiOjo(f&T?%jM=+)LaHR)`j(~++4zZ z99yU(MQ(~;;jem(XCC{nL46|`(l^!_xheFHZ989F67>4 z04ky!AjktXdi(@AR0Cci*;_^slETYl3%kV}8r2c&bg9y^f2}sUQ%_OO;j`ONU&VUMFuo&{OD% zB)r29c!pUA1YC0qhZsLrXKVuj+lfMSEGv`R^y{-gxn z#>gD(Bn{ZmB)q7f4Yb08CiTLm*qTq;)zOyYkoDKMvmuyE-kV}zqgNy!P5PU4)HW+Z zX>FP-H_)=Zx`%)TeJvCxBm&$YqT}^6T+Sl_2+{@Fc3#~5F9>w=laoYAE1<}3l27T! z6z^}=!6_O!Y6j&obTo+&s3}$x<2UoFro?Y%G&~uVWir+R{-L_;_hLB+9qPx?5u9Nl zaKBa(p7#a$E&8#A+*dJ$7x1gyd;RkCeH8O zu8UGR8z_54AG^!6-9ZFc(PcQb^8{;CPjD0q+Qs)3$?baenEkS2SDFRcN#1$(U&z=B zdc%_hymTl{q~y)u)|QS9!=E(L26rHz&-sC{1}BDLk&V+FOKYe75!Ga;TaZ%bO{11z zt;J!>Ht(tpBtSX#&i?cOESqVcP`a@WKgUE&uIG5${dXyamE&zu>{&v^-LK$^qi+qH zc5W2Ow2n+a@jj^DmcPcSW>+`!e6s}hehKBxtRweJZZ>IUm#|LZw*nM-wQWGHOrqHBqj3caBV&>j|SXpl=;uNY-U zgGGbi#E5Hk}1M3o1+%6uRjI8=oF6wQ|E6DAGgBlGD+OI-b-F zPoap!I;bMvg)6sQOucd~kxXMG@i^>VVslx9>sYLsoVBRoEA zpsNV)$(FbVGkBa-T-i~Z^ET<91qUqB|6aDE3P?)5>}`Ra@n_l(cjNduAzAacN2KEA zTlKJ1zO#+! zg4`$wz!#Ouoq7wsz`)`<35?H|Pc!FnN!h>=P0EHCh8B)Q{tYapkk)3ImaHfP(dNn! zWd)Qfmxv4^l(J>j8i4>m2C8+IkmF9{f+{abg;*R3x_0--cQr>*uiJMA1$$*Z&#*7r z_;0Cjw^6t`VaRlDS5t%uiakNH(z)&QN+>cZwzv|*l{+bw%;ru?<@LCeQmpZqxoOVC znrO6~X^4N&KJ1;md6jqaO(rv=y}FD=2Ck^ry|#BUI4vvht+^7hcSU@n-scphyQG~ zGj*FTX{3+Iuc@^TpAk2Zi2R}ev9Ix6W`fBz^gmsjybf-kUfJug`XCRZs#s%9u6f!x z*dFE{C`{Pb1kB(0H1KhUr>$y&epe)~>$k5>n2{}Z_iyIy(&1UH{*K!ns=*hRwLz;| z*2K0_DY-)aHHLhV#+7y@?*Pm;Tx4H#raop3H4ov(tfA&Zl*_O!K7gv$0Ql@D)76I)D*tE;nVhLo zHvuZkSWQ^k!!_Ey!O~asKwR15>l8?i>8wKRhuxVPh;P6cMHysvHDfeT;TjbRj2yG8 z((9c1zL!5pF<3{w(P zr`$FQPDToX^&d?DVNAxn?*bEOkn>SJ3-AX%Yw}mP#K$As(dIhPjWiYDL?{4-B7^{&DIYXP=ED*2t2$XUHRx4$t9H#dq6-@1l^EbfD!~9A92uY*0Js z18y$}FU6n#^e1TBT($)sVe2`tHB*GJMjey2M#M%bT@`JfX{V!ZK_ff9AAtT^T$4K$K9MG=elggj|2{YbUb3mxWDNZ+0c9pMgHmE z#d`$N(IU%w+~Bnr>`HLTCkZ(sn^Sv*&O7Ep(rU3jShP6>h?i$@3i;Te@_V85RAr(2x|`Z|VOs0ZL^YJV8bwVOR4 z1QhV3h}i=Pb;a-O=j_Ns8jOGFFm6>Tx;4sTQy7ImQ-X{&fr94EQe%V<=@j-3fYG678aBnf?WfH;$SXH+iWo7FvN)Q&nHTk8rJ^0aG<* zvSeOVPV2U9lzR~kQUY`UJKYOJrPR?;?V+s{O*+&)v{mv8XFC%?>C@0y2E7*8Bffty zPrW2OgF@h@)u3vFA)jTO;_{Ghxn~&#Qe=z{vLtB{QK_yg?qGaH(_%HI*{{48YS6PJ zIVHf<5;AndC?^oIJU>3iIcZmJE{P(mZ0n zlnmpk5Vrc~KEwM=j?{U&&%qwloB#Z(2J~has;wm1Z#DJZCa4+JCMcZR1bwFE#2wdk zsdlp<9UyAQD^&)Q?tO}uzuF<7Nc<5&ztwarSf8mNrCPY zEi*V}*q9VPc5ym{34(ThCm!iLmY^;fOSm~t z2C+7-I{=RNXt8n#sk$Q~%fqlff7p6LoQ!6FBN0ABq`-VuGXSui{iH%Z$ieK_M7VPI zbaZM0-O9}K-7(BT1HtUF!?GgT2>Rb3lT0guPksaRa+zf4Z8pbmCj*93gcohQCc_4d z>KS?gKD4K%*^FuSSMRZfY%5X>(AnB`{k97to^vLQY>EP>7B_H4+?PCe=G@m&T9r7$ z`92ozaVfWUbq~(0=y+h#*}y#o_rg6c<=?gU)(}DFvoX=q-#n*a_50x<5}iGjXHdy8 zy-5HqTuqvfU2Pd5Ilnta@XfCXuFTPc2`E$PqJk~)?6LvAn!c2{l!S(@9Dd_1UQfCE zRarDll`>;Cb|}fD-oiFZ|v+ z?;VfTzeVY2BFKCXVmqYe%4H<`8oy`?^S;I}n8GaHh~%(wKZG1;nmEAc7!F@4(GhSj zkGd0s?wH=CJ0h0y4gpZ35#3;cmjOk27>|RIIN%bOg82!dinJ7zE(sZW&yKlXRs5FJ zYxUlM42s|2mwn5AeS}{&CVrt0muA<2s~ZogM_e-NC|FJh!=$QNW#^$*r^-4_Vq;5n zGx1sK2}RDg^=wpAN{8xzL@#1MO`=}Z9J;O z&iHqUCC$wUr;`J_^I8f8Q<41R3kyGcvQ-y(y3 zF1_k;}e@R0aP05IHWEmnLM(9nAxM8-O!$n*5g>-9Hq(()hY>p-=2M24<0{ z^E#)c(t-Ia8n3&s6w1;eZe4resTQ5`ctD8|?;RlS;v;PbDaD<7C8DZPLd`L*1xP#j zxJAu)Q0d%~o+~l?sr#F)X6x?ct|Ufe&NdJB)7_T-1V=&9LaVfG$Sv{Y6un2P)`&z# z(G!m-eW9tg9$VdK_h+Ztb}VB=MA*>tY(G@ID*hIuA-m68H`wp?HzW*no>p&GN#u{I}zL20U|#oi&9 zd?$2O3}gYP#WPL#goY?dh{f%fB0u08l7Lfi7(Bx$x@Vg1?}-V()dZzj(R$$-*5`wg z2;IV<^XJPgXV_<(F{1aCmyL{&G&ev)r^eSzn{xuXuz#cWhmY1Ger?E>w4o5!an&Es zum9)jKMr4MGBM1jK#hm_FeZI3LUkf~;J-lrKC@$Z@8Zub=DgKflqOHrNu)P}@_lKa zc-&3sZ&;4B$UZaiaa0UQ88?-5J(@}nolBQe_iaau^gi2UROY~^O6-H(Hn619$+0xC z|D{keV+kP-dN+kcOhST4zb68jHH#1OZT40!u#D|?o?g))<@`0=B%EGEh}P1r!Lf!8dH zQ@rl0wrZ5Dq)@$SpSl)KPHGCEk$=@yo;@a4Jd#aGqOgdbr7H1zJUyV%P}|?!vEH{O z)k{;Nq)plbHB^%eX9FB+_Kchkvs4g~*)NNxWT`ksJp}0A}m#tLy+EAEa(S9uDDTz`US#V%pgsI|!ko1WF0!0H@G6cpOf1GLQL|X8$pK z<*()vo$LnAh(8bS`w(9+`TA|a_^c3^7Ot&qr{1_yjl^taI}s1G8=0vcii&m->le&5 z4o0XpZzcZ-vK2aSzKWF&g3w7$=r26S(Utv5;4dIE@1P%Mzp94ntX7u547g|ASu$uP zfiRIr!r85|lCiPUcZrd}Q0~(w_^Yq0mgbR)uvr?}Pv65AN?Fh7j;51oLgKxX^@m`7 z$~oc2eJ~*us8gf2^hTNo(DnQvJDonL?B|i<_#KQwDN%ZQi9>+!Od<(*K^N9Ta56#o zBiWzn%hJ0?vK3v1FJd8EM2}+YM#jE=5r8Igw4zw5>7&fTk61COFCPVzoIMf%C^Q;c zoV+7i^!5oBI=}2VF^v7lo+WY-Vv#)0O1+ym(zHSuN5_31MLGi58G^w>wGP3T~ky%0Tit2K6#R zZw^5Ig^;r^df3re2pzyUOnq8Tvj6ON_BEbJEe)Bwk6KWtRHCTw#%VC{LP1!aeN+(M zfVpVDe$6qY(MW9&g-ZMxll=>egSSc^E-Ye(|NhU4wQnD2hTz3A9ldxKYwn?dV3!mo z*Rm7#QAxQqsma|A`daRNW}p61#?19xO#;pJJKwiKu16lk>|^GamqXGx80nx8l4r3t zE=iR^1vN?)_4Ob|i2fobI@N+_?e&oX9fFVPOSRF05cXAQd?>x*0V9Rg#TyYc*{!9L z1MJb{md396OrXd4FP0B0%W~KTI0EhB6zg#Y4M`j!Z7MFkt`_`lB?%vXKEtud)V1iK*t%?b@hM8bT!Jms{f>;b}{^G5R z6 zXPNecLFqf^Uozlo!L5Sg@?0C|RqNA?Pm=(t+$o7jbOi;g&V(&1rT7?&HbJ9|mRa$z zHI&YUX7OQbD7_EO;(lu=-4lGw92f7a^KVw8{0e5C$`pfn4|)r{!N^C9J(^-TB4q6` z+%gk!$i^iToFeD}mmd=Y6J5%!;>$1yFo`)FRU#D|uMJVZd^*+W!-Q+|bCA>^@Ypbn zK2AeA=wN9UGFjm_`i9DwMT3>BH(C2^G?$~m*-zA(xcvY#ZV=$GyxE*~eKEla|xGQGS$-Mf3LJ zj)kK62B{aLc}MVQ5^6CMKDTs{kY<&)*(u~%dnmSS@#KxZlq+;VnlTDDY7+~a0dHT} zo8s|d@87&?t8cs~Zz<4wsHZaYszocG54CFD($fqFE$v}|Oyp}%A22a}d5wNk(`V5B z!Q`GBm|C^jTQ;h>n|LAn_W>LJvhWk#l=}lP3E7W@3W0=+`9{z#wT?_nG{d%3G=od) zQYoAP8y|c#!^um9Li9SIR2&V}|14wj{juhsRm_V*>~eOp6mNXt**%9!*xYSNo*FsClBeq#{c3mH5B|;Uz@x_E9^!A$E%p~ z{bun57#ZFL_lo|=waImWy~;BfSz{C2m=;h8_7Xs@7TGbqS{9SDNrsaDTD(9Qpo9H%n{1+g!m7LGkeB~UB zUN4ugh~r-U#YogaFq$a;LSxyovjxSr#PV^B*KR%*Z$4o+<>@nTE}ru2<9-i2+k+#j zNGcb4_6gnS^8xLA8}YabA(G(S2oFWxQ*!cEAri`6x$jcyam|N393uIWcq*3nnFK!( zz3cH-U<>r`9{PN<$o7skx-&(5ck$#6$!Boh!Ef! zUQk%nunRQurzoYK!v$JWIO||laHQrJq}W&K7aR_bv@AGM_Y0mkIC4I};KW7y1*M}x z?-vmq(HkNS4~~cuJvai|YQYgBNKt6hDU`?du^bd+4Ew4cgvcxxXO+~dNsZt@0t*)< zHEyU$jXxTe8aA}gi|F<>zH9;&26F`o3fCqpOl~i+2bUbm&a;)U!aRm0$4Vm6ar5&M z9a^-f#us+WbxJv`wRMSDgX6);k>u_+JNOfFesWoTwNmPr}#P%PF+de-DFo3q#%5uHWmFCw<{%JkJ*)W zV9zWNr0)^^Fz*Wo(3zi=oM=+NdRyWW(O)$+`qPMl&H3iw-mGPw(KM#^K`*H{*kaNH z(M!5nma4@}2Q$yg8Vg%wG~ih0@FuTB*;HXe9FxDfEKN=`gwg;=2#(@yjZacIr z-+%alhvxq-xo3X=TlgjQ`S1SjpWSl^jeFDlHM)1rTbTA{&PEG8ZuTBGmp#HWfrbrG zX)!=Rlc&kT{)6+Rm4?VP%6T?5&*^-+){S=(0l{bv^)?k$eo?@*Rp_*a=AdKN$~4-Y z;y23#qv=96tls!p-oT=jS04ygt(Q&Wx%G*EgODi=;=y z9rUDB`GHxsijz&in%!gjCm6`dCW)R*WG+3`>@(h{^ouodn!m*g&r)3}`#J4CwFSg; zD?DdAV7rEITXeR{c!8`&=%mklrEdWx4&AyceO7&Hy>Rjf-Jz{eCCCY-rHsPt&uw=r zeRBYe73pg2<&2fyh_JvP_@)>=VDQw}d@@ozq*C)@v1qc=XA?du&mZ0}W^YUcIw1NsEPlvg{*2YEZDO zOWDi16Ba=?-=dbd_CYx}pJ2hW*uo=*g{B>I*#&&yMRO{?@H3AuQah>G%<=(0BM-p( z95mA?i*|sm(iuX@Ft&pnW+wkL9XuIc+z2cu6u;~GhZKe3G2B9%LPs@h17857BUH-$ zfBf>}Y~nz9OAE(N%LK+;%KJ(y3#72j!d9_U)IWbW3u5`uKfP0Vd-Ok!EdmC-Uk7R}J*rF2A>m&DJ5ykw7Cke5nOc6o_P8RR9q2!g!q zaOLt+*00OU39ejTQX4hMOG=&udAX4*mzPx3rXRrNpG3AtxOT zNX)DzF{il~C1yt5!%|{Sd5=?Nk0|aj@F`R?Pfebti%ezRl5Yv%GK#TIA|M#ep$cF2PJ06xZ_SuuxtquvsrwFH;DC{a8hYLNz7*P=d`vYF)880B~IU=2R>gt z@E2TWo=*8yE;|x@SV3Hx@N5=e;?jgC<|C(Vq%J<2#ph@@;fcY=r3p`%`imQ%M%R$L z%)Xg@Px2@Fo!Q_%t4o9`LZw;!HE(HpwTYqZa27LKq9o@DNz5%SG1IC>%8Ah)r(b>( zs8G(B5So=VDTal-lG|y$9@RDwtx~1z` zeJZgNg9i5L7Orr$ggiY=0bOmQkpzyRLj|lyrS<&Adl842?jH#CIW!>i4Co#DoT}Tr zrznnmx>>Dn>BHOl>EuX5x%T)Cr<}iO#Edfe;j7XoDS3wj8y4;NVAnF$YxgB^)_f2D zjM2A0-15#Ww}M4Yy*pV2%M>24`DO2xbfqI}W8|zJgN?wj%((4w%`OiwSRPcIi_K)R zrFob?gW9J$vYbclis6SVv%Z*H>xXT0Q4+EKY>$BimDYb;s-@EnagyZ-6&FIJPo8ep zjJ!XMZb=D)aI*BuXcPu-{myg@vnd))>ynQ|36Z2N*dOml|RP}0o7qD>BRtvBaD z2ZHlTrm#BAq=b(JNxe*s7rC#co4g%k)JkysejN_yw1!$b#+$6c-GB`UHl&;U$WYEi z>QX_;Brj;T$&YN1)qjmh<;sQZw?m=ebMm}Wv<~%yKL@f;hISloJjjqE>1&@GEzGKH z!QDd(6OS!&Z2rjAO{&B1Yupht+Fv4ah$YQ{ud)Z((IF2J{&f+97VpXGa=EZA` zG;llI0kYaLAx1;1C&a9ve18)XVnEyF7|3E2)PN9%83lh&+i)TyRI@QmH3N~D$4s+v zIyDcn&DR2Z6Rb`lb50@qvkzD-#`@&^_T{uVad zF%%TyrPbvhNhSss@ODC*Ffd#leTw%SmtH;0H(l%Xl#*{zZmF_+H!)BA&e~6erxVsD z4Dc_sl6^c@Z@L534svDNtTpdzYMs%1ekcc`9PZL2g(Xa$)j<-(VW6pL7Tm2x@0 zdoC_PQI=NG2*bet>XwG&+Xw(0kbom~fhTN_1fJGFMzR-6((*^lSKvO)nk6c?fP4dk zmZ&$FC8`IaR%a&;Rfe9rF>|)uD1Z{+I)|`4oRKKM{BR7Fl~Xa=;ZV~ZrdNc;rzgXX zEv>u9VS)O4GLepeFUF#rvK+J`wQgacTFiIYwh+cci5)g#qU8cq7>HPYrHWP@$uA_OumG&!dr4XV|BA-(u?wpMAfX+tM;N4KIZ9=`*p>I~ zv);5i29LAx<8|Kq5uCO)GuH`GqRW)ULCpvCPwIy0jC!!9_-ygjmT9q_cae=~Va;a$ zWpG&qMF#)4Ru_GrH+iMLtWcq%jp(A#&VBrcl?4O_cNs(;{KBs+O#iU5_-K@0Lo&=) z(zm3%QUNhd(d^A{4`xj~;rTObI&r!0&Q=!Ua7|k=o#C3c&R|&NZu)B$Rc|F_K%Te@3m`gj?2_wo<SFHoY*7-g=V%G~Q$A?- zf&NIyy8zf^K$Z1uU)v)gNl^~fvsLyts*SqhcdTb?TL=Ld8m20}-XzPuL=WAekpN5g ziea0+RuwYg|3FSa2a0m%Xu1r7;j_E6Ud}eGl&YNVowaJVajU8W-P}R3AO(Xsg!&Tf zADKcSH5C2i{HhnO#6hF>j+#02H!)qr)-uVbq3}_dv*m-7YD0B^C@P0^{W~s3*Pkc) zCV!>Q8}PN!mpa0iE(>Z&sFNg><}#(5C~B5R%OPKtskkjh(t?}R6dGY`xvV;LTT&hl zy#HXovD@-Ue9$MCmlvxl2|D|^_=1&Wi%0o3qr&Y`2&^l(m(_S9s1UJ~5ZVS7XPqO;#&Ft~FnD;`=@a$JK?B*+hBpkcF zKy0`;zl>Ak%9JXPE4bbKyEL!mxIB*Yc5G6!xVN)V{MMWPV6pMg zycWyinC(5q`9k}@{W4myw;vxlla1_N0ix|`DG3>z~L=RUt>{Rl|BvR zEd;vMBNM;FoM=3(zlZb*!dOBLkT^Y#;b{bscV&QfmEp=qK+PlXWsrGyb}rqriCE5} z=b;0eRSLZZb>=?u)r(FQw*vU)sniSQ`gX**PuONscW|C)63sH`?roeV*HloqvG`kg z_Qf|FF|e|*=TB33-HPRg0yNQke=b>wmuJgMwU9iE?DFJU8^Kqra*_paaqCO97G9?1 zb5)C!flq5#NjLHwjihj5Rc>N9ug?f{O*jFQVZ+A}N;gArI%_r7MCKLnGy($>$kX*{ z4n;j9L@rCc5UA>=(E%mhQMA3c0Rc71|EVXbypmS&EEVwYeWT7qeKvi)6qZgwEh=UO z)}K{{aeKJUzG~Pen=Oj}!A6hU4)!{+y?J2%>_h+KzkVR;W${93A&Hd|N_9|GQanvE zAhrCC{K+FPx@-RQn~!~Y>`rnk$T8WNZ@QDRs+;nSWSDHU*N%^fmDp>iM^?W^WsIaa zGs2#0dp*0epQ&uBUT1LZvY}W)YNs4V*Rn|}_%A**a-iVRVn8Mf{1<{TY<6A6~{)gaY1{;~q^5Lqrpqh=HD*o9S z7}XYys$1N`-Xt#_An_AEg(_8SVKQkvd`n7;61XCkGogzORY)lwT?@sTqqvSE^udSb zPkr>me>8TNWe+)U!C9ESL23Uj1}F~v6G2WDO(RG?*Fi^jlAL6IkmFSGHS#7KfgUHJ z#k&~`htG=KPXF!HUH1E##^CRl4E~%bf98{S&L6w)eLp$oR5f`cd#Jvi7SdVmyhfW1 zi7|$A10G}UvUs>XVdSIn#j&pby7d!C2S|X)S8=9im-nXvzF6nkJDe5H-)$>`1r=5V ze<$zklZTjRH^R#IHdn^`*=x0d!q14Y#ZO->?k1?rKCNmvjO^LeiB)XDMy{of7182F zbBK+adP6;Me-VPQ<5xs@eUgGG+AVyR`?4T>tuziJ)gS3bPIgjpjPB@rly7V@UYbwl z*=wM(RkPWs*-UaW4LvfF@s@ zfo4y6A?m%zb|on9Mc(W5}UeF^I*_dskSmZo}sM9P?EJVR`u1h+Zi|?~s zTIwBwhs%xLdboU+t#Zv3)2Wflr>TVtXEXiC{tDHf(TLt?P{>{b6!fr!#gnFAR~zdk zEB^dFlERxRDQrTvFPq5aOTQB(Zh?e>x?HvjiSY)LTwBWU0X@!;J{rTDd()@d44D;u zy4|0i?Qeddzg2XwwYc1P;5!r1ZhYxSOM}B#uxF1CVoJ%`hi-rJLH%9nZEgpd?B%xQHt1tGNl@j zGa_R~IaO@o7|!ws%HVYPQKq4rf=eFOX^tsk?o5>JTSemO-8Mm_TfvxmTh+?^JF;E` z2Z=EFRsaV;5_8MD+85fi{efZDn#}51&(d1XS|C_^zKm&F7W%eP)kFAB0H#Pxl_HplOF5Rz( z6H@T8N#GxBgM+9v5Lp&lIU4gh%h=*P9RG0TZUF3g*L9lRDD8fQNiXh;KOn=S1DDPC z|0LrXrhAiBe;*|8Mzq2~$$Jqk&s*|_Z#|dfwSdkTdLmK=uHDcsa8V_C^bf)~@@+Yu zv+ZE+RCA%XgTJKa_jdAknZK9j+q9~DU5R))`3{!E_L$p8{0no>e(9|e<;HV_=yNG-hrf%>?3euxTP~qhwLLT5o6+AhnRL?843i>!uBT8>_1j=V`tH!vT4|HQ$W z2fq8e$`*{;o|K;5p{3-r&k*<3x;r!C%pgb45ip;@zY1f$%(e5~P>O!RUb}s9R$J@f zQXkjk`a9^jFZyDHYG(){js8xx2mL|q3`A7Yzf|p>qo_2#Rv&f492}DPbQ-$SFS-gH zxHb?{8>OHaX~9XfkuwsN5!f1}wbYj4z<~tq0n##3s*#qF6r^QjxU!10K4j@9VV8pp z7l9^#ZYomQw5Uwy+V}V5d-5r#vON&obfB{7^gupHWmEZ{gGPKtWr1`>m)Cnhg{xO- zpVD>-GRtV~8Qeqy{an!3)vjFd*46G@5ax<6UFHaNL4`${UzWW$Cf=MGF}o|j%nt_7 zBUwrA<&mN!=krKRQl9gYj(8CPbs3(gk(|-Vy>3tz{p6Ps4booY%d)pf9W2hIfta^{ zA71(G1uo0B{zw2cm9H$nfS$L(Y;}*2sB=Z_$O$=s^gF7*ECk6jxj-P$R9a4N2mjCW z?Y$`vAqY1xJeAIv);?-hOukiGQb%faLmT=!Oix$X<@%F>RVJ^6|t9Ej_?Tm%kqX&uHsSs-68L&@?VWRFwFj?u4oxtkwT1( zHEMck*#s-?xk9R@95OWWW4Do!C(x8TYsnN-U-?F3Q&%MIqN^QeW?7;Kr%YgLP_zx? zAhsuP+NXpC)3WyrG%>~*)^2!8xLd+uhKEu8Bf7e;LJlB&Saw=Hx*8#9C2Fn^EsFM8 z0d}Fxbxr$$o@Ls0L#lvD_Biqc3kYRkYc&CLPi^nbGKu17Gb~;h*LXfF7@y%NOkc$O z(IU1b&C1Tr)jNxv-XZp|qqh}@Q(XK=vM~l_i=RKd2O(?~w}dUw2U5NxjlTLKZ*(AM zR7$=$@Fr`Ae%Rt?x})C9XVlY7^g1})cdmweFJA@sJ?q1LT5wN*w&~+D4te&D0{Q6y z$WIIM)-%AK7UT`%_5;Z8^wmU1Gm13=6xVv09i3{gCpM4414zvFwnMuHoaSwE7dDW; z0+di>^bq@yL>475^XRqPUNm*;qW8gjt6t~>Qju&J23=f83!mAF%do#ydX740nZD`c zigDz{*Yr30n0wvJ)+atuYt3^}ST2FIVrm`x;e#3{nQQbGv?uslx!`M7h}$)AjqEE? zhT$!mv2bZ}TRsCP1-8xGSrMG5X~qtUp(%(MJ=<-m0;8gZqgcWz5H&^*!M9+;koG3- zfxwYU0$imuvc)s?D8*=U|%K7yJXrb9cQP#s#{(NLAYi zXQ7Y<5aj|bSf?le`USG&{DhRHLi@HrOf9W1v3dj^oKdQ~yrrnd>>iT|KlLieVp{ky z+Ruv#@uFELG`EnQOzORg+N_^=i+Z@08)4FZ_;WEFBq^EE8~1f@*s$)?N8qa9x80+_v`bax?p5w`>LGo}IHGQrK+S z15??56EOtz*ydfK0N+4E`hnTIW;=)!q!++2sGMjdk6vY9Vt7gR*XpK=rl$G#5zG9D zf)MqdOZQC)K>{6PGm~Bnn$LnJki%NYyZ^_*?62M?snp>u*L>^moTJvUYcY7H4m$xA zhl7G#e^B9)!>L4X97j2dfyapPoVHCzp_BOZz(dxT;DNo);}^5FN<%Hr)$~|Ss_|Nrtg_T=cG}mh{Gbo z5V3US&_@&XkKhZW6Scd7SR&B+^kK#4wH!{Aq-}GMf@fnu1Q}7}wjx!7hdn51e-A0iAL zcY=DFn_j9Z?bZ><5$?PsQT4lF>6q{`w=?)c1h%vP;CxHJo*FbQ9cBix;+c3TG8!eU z6A9qbfJCJbsxVwgr7Ryc0Eqjd7#JTzL@XHNlq?abY+0^aGW78c3HP$nd@{8Kd^+u4 zVt`0NG?^&oc>)q?Z(DvzZw5YX&PafRW~m3a&Ek{dFQ^*li3GHGmXBBkaVczP7ED_4!K_-E{T(g#dXNLiJL<*DxM0(+BosDGC!8c9yk z9tlghCOQ!Rs&GglC;aGbx7)#Fw-F_>^v7m?X)s>1tKDeD5{X8ugCRR&bTk8kX7ZiX z_hO?cUja!BqC4B~DrICGX(AfzcbS}7i8LIiJReh49Wh%M-Qp2%LFBz5pM5zYDWzyE zE{6a*Bpse(>a3RQPzt(=qRd{WR0NJgz6}SUUhd`gEZ1Yw9jZJ3#_mAv2)5pK?0l~X zlfQ+eInMmM@|UQG#2L`(d_X zXw}IrdGC)>`67G5HAn|FdK~JF{A{2LP+#94RhfSlr45;S?ndKUj#E+}yZO$1hO_k` zr+@9iKCMhn3?R=*t9S<3iEpD9A>jqrKIq{@^k>F`fLJ7!=Y+o26_&Q? zjcw{v7nj6W_i(hS$m~|MF#fzA8rn3!`~3WDRC7-PKLe+Aezqij#sWX8zqp!S?-ig# z{(U?lItu~t7?U!7@=S^(9JkrYTF9x2;J)1t5yMrE%)R{0`Fn`Jfg7t@qWOKrJamno zWL%%U$#Mg$nM*d=!6fM$5GEo)mwh5EUn8`hdZP#vBV;mdv@|OLx8o%`DO+e*3n5zz z$ADCHWZNe9q`j)OZGnrVIQ>SACMI9H26fE>+#)GnQU;(*uiC%R$A`_9+!&U!);Zbr zNpbmLE3}H+44>V=vl#rB(TZ0)4PyJ83A=uuR{XT}R%m7ytvG8#D?jja>xZ;|vD5%R zMjtcBj2qx+wYfRlt?q({y}avjG1&Mn$xGPBYin+pb~{n)>d zZz~_WW+A^M{4lrK;sm1=CPe=o5zwIt_ekd3wcrig{QLAsDY8sh8Oudj{k+0kyvmC% ztS*g|PuRfXO?vZ*DD33qXHU_%^Y~Na{cYKQti&C$*Gp~nfA44 zG9BAmL1>TGTEI@vg3!xoEdzGmgPh^CR*^gF0pnvAttc@Oq3QvIZle`HS48NTUCFSoW{DaSM8jXxZb%TVo^$?*GtYAa*~xLDNjvK8I|3+iOh?*sIsx*U#p!Vza{OJI z$bPS)8iD5$JWDnF1!vhdT5I@ID?r*qYYl(1_E80nk*0KhpLu<~dS)#|do&z3wq(}i zDAGrv+K%RFMw>ZJiClMB>?MFY@Pl%Jbzm7Kw5m|;l4?FE5bC*~$tx8km3sP&6m3ge zL`CJAd@t4!Z4cgq*fTZ7pj4#ABWlUM@MhMVn|=nc!G(FO-81EBscn3d4wU7oB(kW{ zJJ7SgG0YQRiIE`Y>zFeO2RrrG)>Ao(Fud~%(zwAMsytVMi9Yz zC;FrvQV*JqU3#qH_;!GNH>cEWJ~1nSta_O)NL9@a5o`8`C^2|{dI_|R!!HMwE|d({ zVqCaX6s}b(P9YQ3tjr9>6%p-p#5xk(YoUa&j-;Njjua{Y;K4wo?tr`=u{GGmq8Ftv zoWP3mm=(_(uLq?5Zua<3$qY*t#ysCZ|1ofLe#J0Ik^DBDbEg`r;M>y_&25F z&5tePN{s12W5sX%ch*4>bMI{+3M12>fgpQ_B$A6>Ir|^yPQEjkzxGTpi49J{EQ102 zc(QluZ5#?^gjEh$7^p`}%x~V6{A@^pgn>?#KVX2>QQM$^`+4?m&;HQ$LKq86Vp(Hi ziI_=VABQh#nTIxxxD*7xvm;1D>|{|Ln_H6K=Btz{86BFa zlH_??GCQ2Xo<);%f2LfSjPlZ#%g%KY5*QBo_^qfha4<*PsQxZCdsN2Y0C>?84jNK= z07;A{L3I$?)-vz|pvKA{0Lth^e0nCdQlLpf(cFLPo*3jfG|?g9Fk0vWDH?b+Bz%5x zD5&_O7%))PR1|yo5>a*Kqt!%_C#2svm1&{})pU&TZ{@V*TUFmokK9w9A^U)W^Wd4g zrS8U~p=a;WnPMy#=55bVBf=KInYdTSXU#x6G1_b^z*gkeC^xMM!9NASVpvG)1Xzt% zK`En1+6q<@KFmJs5?XerSWH@E}_-|l06}xs6Vpk zl?es*$QgrA*y-A66(bB3LVCQObYl1W!>lc^BKhM9BL}4`vwL2!ku?ZFR*ac(QN<8jcS|}+Le9z`kHLf4&$dFlj-C)kM z+1uQ?WfZnn&gFl;_iT@*Exfs!HLK+tG|}hEwfvY5SsFzzXL#s2 zgp8flm?LN?4Hndj!TLUjnwwGW#zNLkg9W$j5yPaY2w{>m zEE`p@Z35fuk(hDBtZXP+aUKpYg`o{a?d4F2qpVswK{84q4@<^H3-B7wYa;o?IO;md zC+>b6cNRw=%9k6q!;NX!op>d=&o;%26OzqJ5EAdmT8yXf3_Th%zQe z>A6?&$g~((SwEm7FB*;ok*k7DxQ#3pBJjGu!cwsLI_W}1VvT${B#hg8YJJ{NkF@8= zlX6mvi0Vv|Y1TQ!+MzdLxXr{84TSnDe0X;MqyM}-cq~*h!t0A6Ge=Qen%}T+Rga|Ex{Er?dJ2h%Dk1T z3UzjH{6N0B0F*ne0=rj%y(!lIXzJ zqoZvb^)`!f=2FOWZk}SfAn#%-&4zX$WQ0Sep>(M|*H8w0QT#h1s`EtP)3#7RF|IDYshRnlhKf2L7R)-A6 zxsJY|C$zA@>3TgostvdVVQs8=j)tp{>_Nzjo#4$lJrfjE2^I7SpDP_~O8dqB4p;aL z&I%@n8=j$W?~O9GQLv5pf~Ej3nl7FqC~&I|3~DgD(8d$wWrUkkesfBQV8jR#Ab|c= zV26%F8vz+4Nmbeon|QcX*< z&WqCq_z315pu$@MDi{mPOql~3kK4RS_GPx_@+(@nX7MJ96>8Ngs)Rd%B`jN%$6PaH zPzpn?pu+llRZvME5n&>=xwzX=DZSmoSagXs0`R@;0idBUNPp^;CY%kbdW*eJTlI0> zv(>R6hZA0DbvstPPHPv{byUsWe8+|nK5Fx-WdKB#f?Itl0>N!#qLHmcxsANt+cq&F zS;MDU=X?*!9vXq7lMI3f^q^RUNYN9^`rAcAT))yXnKkJCPC%o9lRM5ZWeFipbmJ1c z(df^re#4Y#bp(ndT^lIVwchb89|-~E?(Mx%Tl!?sK(2}akzysX#N}=G(dx~l!?I$- zjOB9|387_f+Mf+T$VrlcLzGx<`hy@B{KU+zXRlCLWm2i86^4>23Wu5Pv)fPKf$%hi zah4X4P@*~kTWL%aJQ_#96XgiLUn3xLC22^fGykIe&2$+NMy`#DIRgd(iIG`?;aF`v zN+yNcVKn*Js|-ebLnD&4;e{meEU<9 zkjmHMF-0OQmp%`uL6K+B$OIowU~GY>Z53&;V}V&=SNpzUsXZqQjDHlSuXWw-b&8cn zP#l!jMt9^${!9IFt^{sA1g7;<&NTfs{h5jqmf;$}79!Z&oOQ|5Wa-!Zwn(-+3EUNk z-IKjF22Bet9;Mp>I2&S(_3*p&Is-n-*plAFAeZIfBK`JJbA3@o zZO=g^cIqkm;*WVyPc(bEA+-&AuweOiR57L{+c9o#7s|+s(>szY+d-~jitx3i#*JQ) z{L4PV`F0C*LLTA}K??ze5&^h0^(yP#*0_);=7-kD;~>>4H^X(hFJm2|`@3S%U{xwt z07?V1!iMxMj1BP@rCzSsKIWdnz$`P#3IrS9)BG%Xc)M?REyyKCfv_j0t%QIQ3{#aIcZ&oz#5oIQc%;$aF2p)| zU~dByT1QX*{Lw=+1BNd8+`1-uijO>~a~`)AhkukR8|68Vk(Bo<4ADj{i<1D?OoH(n zP>jIJ8Q@cM!vO%14z3lJyPi*XT46!&^CZ`S6_#j)C979hD*h58GGPvw=0j_&oWs%j z91Jr%fj0B@cP#u2e>V`%^miHa*u`4usG|EW0g2W4_jh{3D2lWCmPJv%8a+r{G}?R@ z`}0h=4B|@M`XBK0OTZrSES9$rhc&D&s0oFgRTk@qFnh#BWe zG|tEJe*`XGFFdOFNQ}$*hitz z_#z2mL+=^Sm#YIQL$?H2WY$l?!_2IQOAMLy%y!7EXU@Kr$%3g^zJEYd&KcB(V~w3(ScdZ>M2;uGKnO%)U^VXQ^8seMq)?SGWSGt zdJAjW&V!chWje(6WG#oI!lr_?3~Jq42BhP@VcKk7nVl$oL;ds;8D|C;Izo~wk+b>_ z0NKIRdu07`tpSbGgpu1b1E8hsGiJdLZtT&GJ{Xbc-=ITc6D<{LsFOEfWUxbOL90KZ zUn>4EUMddo_9WkM5aVou49I-qRcJAL>C@DT(g4Q7AsZ0PO}OQ|jqFd>*>xM*4(j|8 zeOhz%@+jkmf4su_+rN~XT1xouH0b_}X(waSpG|qx7R;bO8#~6Dk!%mjBQJe@+B3>y zT|3bRvYKQclZU62?1Q2&K8kjN9cZJk)Dh@uU4?4;c`8(gOH-LbbjpD>?NM+YxMDLH&mCw7VD zv8c_b)LPr8#I}W%w(Mk8R3G*QDcg6@7liO*-SsW-BLVF0-RMn1_gLldWuQ;BYrct? z&y~+?SAfESpiA zM*tAP31mjT`<1515QiaQg{h?SV3ZOWz{uQ08{ih=+Jdp>opm=$xkv6(RrMDo0B&5<4OY&u*k?D=_46S&!kXCv{$*WYz;WoW^A4HBVtVtlpD{7JyL;j?aGtGT8 zSPj;uQ#||5PhbO2gv<#+&65;Dc7#CgIXv}riMZVls9NfT{A}{OEi(^gY}$Syy+(Al zN=$O(E;swh0x5V6%~}p8e=TI@`{%Nsw|tMV4=#JJ6-aYsH`n@$J$d<5U!af1{F@Wy zp_{e<W` zQii@6169j>3}*+Pw33Djx2-XPizsJUJTug5{qxe>v;hoQubVrwPQ5Y}(Q(Z8^N4$^ zSEe*g5ipC)%5x~qBocyLsk$z!VwP*j>y$ z5IWGh`n;#`U}cqoW>+%658Pb3Gq23R#N^_#(OEBj9k%#@YmVB?gq9}9B|9;U<8|j# z;-qX-CJ2~FlJYX#4i(WMZ#657Gj6r0h{ZZ^$|it7K30|08heP-9rJ`uN0Tx@;E2*N zJH?G6SQ3KxzV(M=&w5r^R(?>d#g|f(Kqk+P^4fa*yF)tGzD6T<@>+F5l5IzSZ~~ga zXi@+F6&|TXp)zaMnW-{E2ijmi9dI4nyd;K7q&0M+x-FZq{VD4A8;Y;}!tdiyi*b#R z)F4+B%-&K71ZN3fmHW6{hN4nRWkXs1vXUXdfz?`T@PzUBkS=DmB!G(*HSy`dG-Hx~ zLMCQ8$$s2AqQYo)#0fAGhr<~!4&vkCi!jYmWUC_n0yxo!#ar*0PeDi%ZyqTY!ma$O zXz8m^OI?y0YMG+sT0m{rP}?!o;qtJrx$;S!G_|wtV}-Ff(EpWNYr5GKa;+lV^od|P8+;*0$!+3EWCt- z@>O^lK*s>PBTb?{2JKrS0GKG0 z-;k;22#AXL?b|EtJ~SXgDDQ*)SR2po@7HfrAZ=LU5^nYS_~2*86i9Uf)?veM=h_||f~|2o9nuNA#{}&PAXH4r#5}k$kc!!LvLCaw zXSVoh9pD3bo9DIK!q3CZ4DjrLTD55+ZX1LYwZ&9|gS}|B8(^l)YF!r!YfBPGc8!Hd ziumAajglH=25%)nHX8dCF}|?yfxrWN-v0H#M|(d5biBxBRp`N>Gkm<2nAfV~tz3qJ z3ws4|T9yqk`D)pmf$Oa60KbEftUvjc$ayzlKE>5;PKQZ!Ws^`B1$Ih0 zs|Q7s?)>7BKU@CY2DN4x-{l^sBcQ!;{^`bD{D09(-BKmNw!E^USv061QS|k4&E<*r zkZiy%^T`K#t$C|6snH?57%R>b{a}jJccct4uVRSUxEXI>JBV?nJt9*}woC?=`7=g2 zKz&3!#d7ZvgcI;(Svz*0^2YS{#(eUI*?yM&?Gg91irCuo$*c7l3Lm$r9c-D>`r?`& zK?+M4v16MLNIIzO7EN2Q#{G-h&DfW(f}6C;&cOPSEYlPG;zSg6A#fATXLO27Q+$c8 zgGH;7S*(W9{=WS3*I@8ARds?QQs_Pn!Q%+KB=-5{p~GB;!-jqB6-{<@^UPoAji4}D z1NALvaBayNC~rZ7t4h{DjV;S9pd&xi%Hr*TN+kVk;Xn66a9m6F22K$d;l*%LlNT;0 zU`9E8iI&11CO_7BT_j=FUjULKir8_gf(2tr@C>+7cAw$KO69qYIXrJMg;Vbm2ijP1 zC59@-$Y|Xx?b}`)lz%2*0s}y6?`B_CiF)8N77a88S(&OX0kco(Gz*c#m}*IDTH2{S_HpC;)br1PZ}v^J#TM;GZ-?`DNrP7E7e&>z@qlGAfJ20Ff&eEO zV}a#h=g7e%G8h;WA0iti$cb|zgGt83AtJ{lLvV8C#7XYw`}?ic-Fwg03v7sm%GT`e z)!pmyTfg`Et=}R!fqj{M&V=aM=%w_Nh$~Z?md5%N42u+xsV39Q^yW&No$~BXsU7D= zbz@UWr`keWCqZqVty7@3&Gr$XXnGfNN4YiU_1oQ+?&P;TV*h2cvT7WomvjTv8U|FQ_Lxl9FAKaf#hZ5`5hz6i0;9`npr^#cc70`kCHf@n z%`$AW4a#C~vauIKSQeK*rO#NtNxT<`O=X)<8Ek^lu9tlBUI3VY13T7)aEJXXF@5`?r>q`fwfiej4RBeKO{H@xgch@jI6P1Zz zmLzO5R}F-fd7eBssm$*XO)6$sdnJuK)=etVsx+zAyGaEYEEq@O?*c#t3fs&900E@2 z2_IbGiCs!3z#CbT3LdpCna9aNbUYJ`H=IM3%&-pdI@kh|GI?Z5P|PrE82LF@Y|4J$ zrJayc%gVLgh2>Ezxh2y+OX^R|u8rhwLJ&l#X)A|QWi725{kn}5M~0*!xf(^gt9_3@ zOq2vcpf_4!HDy}z56NF3EV=xSL3mwJUEKcSr^qw=1GHBBe|sN7ZMlOJ zP=1?>GLno%p#fLe2tl80`?n>M{$ST^zX!c$zvsVxpy{Yj2{R)XmZk8~F*<%iUPI0< zOQcxNB?CFZ@C;2F_@fIpu{9A>DP81=^wYn!z>4fT z;W;*ZU^~j&`am_+@^Y}TAHibzthB=*X+nw;MoyDx^WV$CHhG}bkkP7K_{7p%y<;;3 zhD!AQ-%Qfh4M{v;Z?07yuvgavUZ`suBv`futcE0Gp+F|jsiDgy(C%oP8hOO~j z_$|73SAV0y^J(4J4A--DU)imBOA1_Lb>7a~OofzKo#w8lm@A4#^{4+(crP%t&tO4Z`&fF||JBrUYV~}05cn-}XD3?dMWPw;MC7eOY;5!A!v+ABcZQPN4 z-u$d^+oMbcN492rv`RUi^TsP0nVnTmb4mj*?tP(D7Cww8lg}nPqct9tmXGu?p^1x5 z>#6?enW-)X_QWer>#4r&%v4uX{lA@tQ_Z?JP6T7(MU%^KAuElyxX0)qt}#D$YMCXmK0Yo73!I@<2>Fk7T?2n_RZM?B%J zyj62y3$du4qEY?n_k??aFObwc;jLqM!dnBL@Md<$%Gp-Hw0pvdTv5N(&$=hP-f4Nl z`vzq|==X&80p(bpaMIqWEqTHT7SUz!gyYpemM5H`fY$Tr3EzCW&-R`(vt7~l>CMJJ zzH+K1k$yCnxCkUmPv6zdPn?9xw7PoyPQg*BSb)==Z$Sda8f>%v4uX%@L=kF;#!^%v4uXz3H@`>buWO zbv4zex2(SB%v4uXefDWh)xUFQs;jBqcv?^Oe?K$T)l_dd;Z$o=Mw2}e`HOK+2Zyws zneA$}*Pn2XSFlIyR(=6WNQ!DZLM>pPr zbdo=M7;xVi3@BN{#Ol*pczt^1RR8G4U}UHDRR7+YsjjGc)oDG|zkg<`tErwkt*3hb znW?U(dh)cM>if@3bv4ywusV%>3ZFSM)zws!oa{87>d&5;>T0SQVFoXfww{^oYPNMWO7{5@A|2g+p}b|6RFW;0x1^UzvIpbsvD1Q)#yq-q z2Q!8#LyfX_k}z`c5u3J;@kqYwxmHYwcOXrZ8=5g6-l0aB4-e-r`QJD^Cgw&XBO5;8 z?BmLSragD^Y>)PEL6g6&rA^}8PfVx&2(0FR?VihYYGv4|Hr=)jY}4e5w@>t?r@q&@ zLouWMwsa$RyOpnRTS{vDytyr%(GLmOwxzG+_h4g(j&osi8qV(Yrzn{}_!Yf+hwb7W*%wsdIqD_V&!lD2%S)x44Tedgn@M>*s%uI(-?XhI7aj&6G zOhf0m`)M*{yyhS=J8xpnF3J5rxb$xBO{-XBv0~m_J-L;_A#BAOA(zOA+I6zZ|o(rNzT8k?M-8A`+k9q}x!!KO_?v1k?8|r(iJHZr*#EPrPQ_jcZ z>DI=B&FPK^BmU<(PB}j@c?-F|`BMGh=ndtKpJ(dY+3lJ-eYz_pTYvg$J1}F}CW{y9 zEx~Klv;D#!o!Mu==gq1stgu-|>yO1cpG-Ism#oojx}Bcj;Ws!sYm$V_q{kO3lgwDh ziqt;hmKmG?ww%xF+jG?mXkbM65jlv;fbxiNsa__+5Y$6iK4Mq%shZazQ2=y4)oB@% zyhXdJ$0y0kY~s9bO^}rtL-9U@vSvPUN#jYS&KHYc{yPpWQC4OL%I?pDT}7un`(ZbS zP7=wCburdd1$(Er@U2s5Vi$%|_CuopeRwK#7J z;^n772KhHzB}@@Vg(d3jQ|c_V!R3-&asV0!>eyu~@61A8d|W&LSeE{|Tvy#Sp>jN?CA+NTk*b6qeKFh_j*$$N zDUCR$BVWAF6^@NMa}dB>5YhKhXLTxb9-#5$BbPK5q1cgp;;mqph~xt_Zzp%?dGTT0 zsbX;53U0T8TJG4ptiY+p-cSoa7AA!YjWkhJD*uUC{uBM>Y^!%1y`;bVi^d_u2Ds|0 z=|K5}tN292*~QF?ff#N>LieVUVQM|wBZlE}f?K%K$DM=BfCfDT=}fvS^*sE#zS)$S zn0}erJT}KqL6Nul*^j?V>WMlA|_EY%E^66Oa2lAEWKP*fFZiY$SfJviRO<)%q#)^}sU9U9BqO6Hy9?JGF( z(abqc?oD|3bCaS!by$35uNf6|Cwp3_5~-Uy-XSu4BeyLa0pXK42W5oxD$eE+UkFh* z%8&@lz(UAyB$+^yFd#T9r@6qkHM8D+;O$L}wopI{cqN42re1`vN0G{V#1gHtZRvtv z9~q)pZ&LHqAM$S}C(0AHDJx;((8nQ+bGel8>QqoPvWTNakmrddcX_?xBb zgA%R6Edbty$wS;);_xIw<(x|zWl!$Vyi>;!n`<5T=m}kPKf1%t-QcNILz{FJJ934- z>WR!6`YC*)Dt+^I=#Nq>FiNzyqt$PS$BPVkAFvs7JfeEX zQC%vRFr*))>7kcLlol068B%|zDil(G3*12+ z3n*Nms}wI)@_S^sA@e9l(;z@bi=+3WDsrg1#Eo1PN5*(b*p1%-Rc5sKTYsUL@+|Y5 z24M5)FxAW(W(elF2IaHh<;AG1g)nX5nd5-I5#*1E^udESV}S%NAj~(KAI-k2bB?$Z z03oK!dR_fo-W$)J(G&X5yCSk)D!p>=qtf2eO{V%g5=>7zf1(YL9Md%2)bkSxS4ncM zDmckQhw`?YngEVKA@M$a&pS6UW7gHYb<P8;w zFae#4i(YJ`%E_=02S=xukY#(4B#1@95JurrShHTYgRmehin(xlj9R8qx|CdCY5ftZnuv9dsnXtKOp z!F2>VAYd8uG?${1<_xT9TYA9Z@lhCeqp_{=iQ*(9zW!;nm%?#@*CL!_X7vrc+zdtDqvM5a}sn1xDq2| z01UyNWRGhu%-MyBF3|FN@ymAnX{$6goyL0Y;$wCO>UiG+W>v(m@3@Rd0E*gyo>RUP zws0W3G2n<^=UB7}rhQt{brE}Z3`sVPF>Q5mZnbbi=VNYZE~I``>+V#?5*ES>fc_AZ zqb~2~)+K-dup9Chf3_n|IbZN~y>t_pr3r?BClUK8CM-0AU8-xtx;zWlLUQA6{V*6D zx<>3QStw;(j`70XBC_YB*-jg3oN-3$853#+5m#~^%|Z*{hYy5Q@gVvHt0M4fG^rjk zbC1XfutBbMtqGudc@#1I;$<&%XKGVj!l+QRS=1$r3XdgW_Nr~P1}vz5bY4A!!URA? zBs$WNnRrU)*dbW#>_D^)W?;YzeuVe>+~7}-O^*g#@ZbtxkJ{0y4xXc&F?z(0B&o|@ zG|rX4Q6b2u8Lopz#o)KRO+!R5u!HvLu@ZJut2>e$%giN$EZcPo0D*`&pk!MTN_2&GVws3@ST|7sBs+EDK zHN9bJKnTp;Tvx5C{N`@8Jist&>jO6E%j@d{CTxJjW##&Sd8ezh&rD-CEE7a39ih_X z9a0e0L0mE>A*)`=g=>kAn|8b1dNg#)}^Qo&jy}4t5Ubo4Z@gV@jx2ui&9uTxN6sFmf>NL=dEGg5sV}TUXc$B>( zL6ADzQYn+@{d&{%OXHN=QXw|AACUTT#IN`0`kByX_O2j~T+6L24nf{HWU1x`gTm%f zJq2}o-~fP%z@+WVO>zr28eCC<8y$LMRB-kg2{y*SbWGT)3}Y;&T&&&9FLJo-1K^^@D0nB#!ehHmCs!Hhkrn8w?CoUtq1 z;0-Hn(9MV?wB%SKd~wrMNAiw}(?^BC!JzIKQfkVF8NLx2YCU$4&@X+{*JC}g9_z_q zkCzj5S(fvubQ$#)e8CUI+?GD3PrM|Zx}g*OO3SOuRX(@aPUi0lKU3<-5rdP7?O#h_ zeacg8jlleLu&DG&+3coR*plSq9AMse(AlF_P_rZ+wt|`^@t_sJ34>`7PFcIZ3-9aD zg4x-iIxJXL0YxnI4Q^KR0^6*n7vf$tPEC`vkP9bP~H5TJSkZ zvyZs2C1Qc~;n&#u-69|0p&2P1e@Mx64_gLl(>hAO!}m{r9KKe}f0y28i?#%%VTm^$ z{RfqOT(uj4&9`m0{$eq!?H;|ZPeFX^4ZYY?{$HP@x8C}vv=MkvF19c*M zGB~=w$iPhnNeS?BCF*Q3vGAIEq1(<^N)&3k_va4B6y6ppOc>XpZkT0hluM$8sy?h3 zrqa722>~mir?ZJ1jZQkl!CcJWCLpgH0CM=onR`F77Z+s@2(f#tV&c=IDJIKSf*M%U zf@i{?ASF^MAj)%I-UH3J*ktdcXqfWtQcq4&?!8@}JGaZJ_oB=W>BiRooLa~Cvf4a} z#ulEhvBma$t=8JWLnkfDRAN*mHfBc0inJ%G-{H_=%~4ba+-)zlplyB?nKniTwdun_ zrWmQ!{9D6}IqLLJB!w74x4P)q+88a`ub%7_NB>@g&WFHZZYh~wgE!v1uqO6fMdw;n zkPgvyI>(oqCG~sZnY(e5;yTHQDE#eYdc=KNTn2UNh;+Vc>(ucJY&|xvR5R9(pXKN8 zVZ*M;R~wfx9a9Q|pfka~r>F;o#krtHP`P&9k%?76iZ4C;)b~Xul<&ge#1(n*9R5S8%qIUoq(!xH4!OZ z*OCGUy1aHXp9g~nt~igBSG*K|#c6VWRe!r^WRDT|(a@~Vq*vOKx2~>2?6iSX`?WX@ zT~xSSSb!N69+yDxvoE`cX(qi~#7%5o-nwGatiHuY4P7oPny;DEm*PnS`UleaKf|^0 za@tBmn*H@oI#i))_D$XAoqJ{v-E@2F9r^0nvg%I-XZ}lIWN2xmj%fzkXfBEX{x1?aak^U#y-SPtrHhofcGB)@*oovkB z`ELXYW_Njj;g;q_(`)RZCKKoGTB%XawK}hw)J$nY_?B1**Q2Z;@~)`L*sKNqRWFM? ztX>xRSiLOrvQTEme9#vl%0oCa=O@jxp@yQOXZC}Tl&}572L=8L9SUv@72v6clJaeU zObfd-S50v#AMRNaNjZ}X+uV_TRYxL6zR7E7dCYojj+LPTg(Lg6bD4Z?zGik|Y0p=e zo7eT$%}NKPu{Gw~neR$|ZfX7^&M_`E znfzr~{1wG~r_#QsnuHKoJQ5L|IISKO_K9s-3~bQ~1s2wKK;(=n6e4?CstzT)*d4Qs z=qqoFA>`}w_1T}_M0G;mt%z%IN9jt8!D|f)Ohka==$Z0taS=M4@PuX7t#g~8C za9YV)_~;q5qEz_8F(?vT|I=NExWe_6=y`v-(=M{ZHl-|2H6^v*Fu^teCwLp6lNKMC zjw?XB588obdhrHso!Hf5YSbluZOyaaxq<%;~DEt{JD^YjE4 z)*Jb`@e$m5Z*UDtF9rkPi*C>AMdVoR#gbyid?qF4BX8k4DzRG|ZxGU31o-MoQ(}b@ zMRv%_W%1dr>$^^4GwB=9Lu}0v)Ai@&NH_JuyaA=A73vMzn5kh3!#$Q>MkE3Y<&h5t zeY;3ld$q|aKLh<1Muv5Arf~=5B4k_C1`i2x?QU;OTj$(aY7VSs@r7>CbnBfH`Yo^C z5n`eK@c-B^--h@g1t+IHiB>JtECcX#1Rg|mh;ns#+JX4(pY_2t8CcL5?O%%Oc zbmC~A;N3Jg_I~MSM`1%I)I4_1?0A)#Q1dvuvdn54E44C&S_5a?QRzB++kFNKB}=w4 zLU~*@j9nFjTuP(5cbFbJx57x0UjTTx!?hsvE>QC{l{6Z0f%!G{^s48g_t+2`i$ z9Bd{)!T^0F0a-sLCLQ`~PqKejh?So2ZTtR@eeGaLrT~@ly9u~p(KM>eslv>u0U&MT znO)NYCcM^Qs!_i;n1X_gm1obKa!YCl7N1DX`Lo4;SXJpc25=axmSz$7z4q*VU;NCc zC$2MZO&<6-3PxK}yzR0{fM<{>>E?xUxF{Yz`>n7v^H5v-$Jp&-)@=qBM1mDQQ5L%V zu)?RxLKikx_>2__kzAr!;d5nS?em=-!N<{1GE8qOK5?Hwi`&}3is!>Za<-+5f-;!A z-ypxGx!l~I9t_t9(@4*7~hwVeF^a16AO2yc^R^&^^q8+|A|% zD^dFH0I-K`L`-^Us3F{Xuq^b3tnmJ_&>OPCdsT>-wZMKOBES{Rh>e#pTT~|!ieYO+ zaLgwY=PHOKnIJ2zuW%5rS&u~|~PbyRw zZ)x^j*E6@$Iu(&P(;pZi(m#4V(;#p~QwJg5)!yECPclIozBsXnj2dmfF#P2O=PSi- zn?rgZN!h88ura7gtYV>^{c6CBy^&8jc%pWu>D~)=@3r^$w@u|Y230%&9G{8t)9GcZ z?>1vSam_i#)$lBp!(Ma#CBn=!&)%F`NqiD$VSDmaBzamCqMHGu5IhNyE#2Ed4W?VT9XDOg#&PZ&L&ntNm+neI%SH7?9A-c>8#ML8XK&Vpp+99he9 zjhrKE)$OCf$)vTA5N0MG3nO(!Ts5Y&fYr*swtIdqHcOK*O>_-9G%c!ytr~ob{}7V* zg{KYS*GJ8ro0Yy5;n=_4`zLb^jIECe8GwtE0~bNGL=TV3OFaM<8Op^@VDTmebPcO) ziuKn7?Y~H*D)1OjDMvniJVl0X#az9vVQiMR*x$OB+hE%B} zxmGr%zGyU2Z7IG_MTx5Vv>t;)sQDK_ev}7MfkaRNaS9`n>+XFVvoQ$h}xcR8TT587s0&<3+qx)G|CA3>xELs8>xL2hQ zmuP4{zZVbX3sfGAPEb38d$E1` zHnT15E zVdVZ`a)sHiDpLWK#d_Qr1>dV31U0tfm?nCj4_rh%BM2~`=e%;FN$#SGMk$s(?0wK! z(Gzu@ND8TXvr>a?PaID!O(iee?9HT=ArIh8ED2 zpLzrZUD5UQc!FI+gtku<3peb*c|B3=;&+WPuF2`O>1D94wbRmo*Ginwt8vAYorOFy zoSw+fGIpl$H}o-{Ui3?>Bu(tl>asMGUi&Jr!qf<2fx6CAd!4J;HPLz9CfNIU@s1tS z>si`aw_{qdJ8SvZH5s=)??UqVI8uz5MYyA?+i=zN2)0=(R5bQFwi=NQ@Y{-ZFg- zQOyxN`PtJWOp4RZ%#PkhaunzDDd*>c zzH(IOoN|7qV|mza04ZWa@96a?mm00Gu?uyr^2NrE-e%p1Be0a+dx>r~aKi$5@1?qV zS-y!|R^@s#+^Xm2<}cIFdHDwYT#%3HXWI8IGv&IQ$v1|VUz)EES1-xeg{#f^`Qhrq zJP%hrDZcW)G(dw9YcE1d08)1%gajD%H9|%J(Lf?Z1PBIM14JCZiA!OmLqN~~k_G@l z18Cd$1&lllA6U@C00pM60)mbHJRki9KKf}Nea``O>OeqRL?E<`&RYy+sj&k}IwxOS zphT{P&{$OgEuqH)UxqzoU-cy|VQeUQER=w^VduV3jm~e(&!!q_HKO!hoEb+^M!e1k z9%skPflBVci4)@GK<-@1`H2(ZX^n6?lX!L90L)?rOV{EG}@5O4xEVQT08N@osUQ7)ByY{t{TB;b&*NTlK z`Yyc1)*ePAz)<3ovQZ_Pl-}4IeTbTgUOV5Y;fhjvoi|E?}aC zaL%)k80+Bm8;d!1F7qxU+bGG+H3O@L1IUDv{DIYkj))2=fcDD*Yy@LeWdS;bv9YoM z9YQ=V-g9$Na5m6-H(#$=Sx-bY59o$TBf2&X6QP@eH!{kwMcun9LK})35vjvlO2n`! za{Lll@<<*6SRQFYpv(gyfCaBYj;xdTTZgpn0;TTHO*SWRNYxO=gimqUh&z(8ZwBD3k``*Fwl}NF9F{a%Tx(X}b+?w$Fi`f6lD7$Ey8{ViU--sS)@WT~iXoR~hgIELB>nG4>{n*9OSwHg19_O%GO|G`#q}-^)~~ z#wb;R`*MuNr~)kgea0BTsQZ!P$^yPp4sU$ZCZAcMrygw-dKp3ZtOgC4Ic7wo$QA}V zp69%M#Mr{YAmP_ja1j_FBn-FgWA1_&6G%K4|7sZ%$`px4w?!bHFRcb-m1S(aPvfT<;NnQdWAY*^rM{ zBsB|pk%cF;`b3-bO)S!0>FSVs!FZuPsqdMWqr=v&VceZn6`VPX`6k}7J~xEb;c-uyf5jgLaiAnzO{v)Au-D<+aIQ@6-I=E%vNHw z)dcUowM?{>HQ7~jDSW7Sld#>Vra|X-`VthoZ`k*y_LPoFzl#{N(st2Lgbysc3qk<_ z1lbZw0RbG^0dgjNqn;DWnn)c6!u~{c4WEFU%ZQ+Ba+3XoUO^XW)vH=>;JFYDjtZXd ziAg+onfN)?$l$2+ZVO4W%1aR?e~6E)hg|Z-Xn%V@(Jk&m)q_t66hcGP6OmKd{h;MV z{lokl?vNd_JpF+6pbGPaG=r1&n+Q0$N4j4t1V2!TDADw#3vAlBzPltLTeD7+>yZpsa2R!_fB@Sssl!VzkM#rSuGhi z$72AWVY3##X(juMn@sCrDD>S}d-j(qinPpzn1N+*^PUY`29KJCz_FBKn>33#b0IF_ zjx)@@vPXd6 z-uAHAyW*4W5LLn~nhH!%X_#J|&A!$z9Sg=7T+f=%p61vxoYZE#@<$KzLz@E{Q8np$ z=p=Fk={3PU^1vMTBU~p}Os`8XYhDJ_%)7G=1(hL_>Bq%}aW3IUG`@_NnPz}NB9YLO zIfcrf>e_6u$~GaNL{p{~Y?=#40B1coW5le@Hmk}r>^{o*i6%j$Vbn+IPo>3t9&MLc zMONKP#gI5G^$V1~(D3@wkgZIuNhG*iTQXtNnx&N9CRTqSN?!F-|vq?FTqL(9=8p%!!b z3IuL7mj^Q!15I`n>J0cH8Dv&f7v$J)3eukjG1Z)1{)f-qe&^racK@@~fp^SpC#RLi z-(#FaJ{Qk)5Cq_)aO`b8v)>^XSZ_cq#rNSw-_m?jZ^P9T&PWaR7#PBLZEAb%t4+<^ zM8Axy^j;Qd(I0QI#8XN?lqWQ^B=3C|wW7FFMXc^+`4$g!k++jI-%X(82DC~pI->8b zji;KjiYy`s-Hlkg<_=_QNQ$cmiwZtAp83lm>6Qs4w;t8I%QPB!*EEa`>|%K2-GT@W zUC(D@WOkrYF0R+6UK8K^bsQj*^oz}&NX8&oDj>}<5iD(NP#TLL6iQJTZRNt zw69z@LV~nzzAe44pHd(3MIAXXrp4;1-dzU124k&-`SGVmMYCvB$=gix zoJp%Bd@P-1Hw({;#bxEl5l&*O91#gQw(d5Em1BluYo$Fk^m@QnG$^|j1QF-9s!A%A z5H0HAHTtr&T52M?(pH8nbkT%>>anfg3%=P_M4;k&As74OG8N!4b)Xh;Pble7ssFw~ zEJhhdiA!x6Ig0cZs+)v9tf8=&)_Z*I*%y}KA?ZD~I4x~Zi_n#Z0Co?kfTdny)tR=j zoZN_(X)2@Fh<~S?jL15|WHhBu#tEH25&P+%GhejHdMB6x#?CiBwC@wlU(!`F<2v~IP4_w(!*hta5q4kbVOzwYZ&G)r^!iDhQU-{N-Q^iFRB75D!tPRf&kkQPM#L7Aw#$HBA8*r#NlDg2aIyCWoA_jTgF zh@){A*TWr|=sjBigY8m`asrBZF{y-wa4I%4?tU*Gw_zOz0GIb5cw^#++D%lvst$!jbmu-|YOyzukDXV&`6;w@7!} zD8BRcKbXVOQ4k!!OSDWowpFu#55tybX4(7^116KF$Fp6xGQD7jgUq^23}x8bpd^(z z@U)|0tlH7A8&4aqc6YHfNlf|Czwp)@w0@x?EfuM&L5$$yqB|iFlV?m7T=|<}qs9@Y zB*5m7ZBh0q5J4J@4_M*+cyvOd>6EXzPGPSxvp`w6tBE<(DK8I~m&(DjU-AmYWuJbT zge8+-L=|woA1{O|Sfothlgl6#SJa&ScUP&j@oPbJi-+Mh{o-@qE#1xlm+cFR*Bz3&kUyB>IBIghUh2nH~5XAv|67*Vc=8H%i&1jYLdWBkiG zBD7ScdPcpMG-%E!XV+5?(S^}6ka>}L{?KoLYQ?IuOmlB!=;YMvK)EF)E)XvQeMc7- z^03Q*GB2gL7L8zKC2$omfwT8+ptXPmkMJ?psqwMi_0hBsGv`QnFk$HC< zwAv{H7Kw)n(`=d?lWwvt9lS0e@9RjHM__kCt_7~|W4|==GAKBJxI^@>0WShoQ%v-jkK8j&a!DMGE^sgOq@xJwRz4PD!^HCun7Cc^W?=VDgC#mn^fV6_ zi|D97Mx(-cg?P&3$@WPWt$8xOV{0SC8i#pE5JU2ilynInN)YU^EKgq17`rq%U@$Mv z@^Zi=4o+R*b>!-!TxIfVNX;C?bz!8+vKo#PtLW%&^D{ElY@`YLhrLJbW;uB;Oa4L) z#iYB<^kg!6X4&JzEklfBEr%JHcI7Zo0XvA5*wS>wy!i@7cWJVVUd!^@$3nHCktl9* z2*p{55EM63QXC+T)W-JZ^jOq3CN;tkCdRpgFhZ8Gi3@BGMta#wQcRr`Q%JE$C0zyx zaZJ={WDL^CKk`N_QTYJ4?6HDFKK0irn5XDK#D8N#$BB3Wj1vCST@f&AX4{+O%qgW` zT5w{MfU~P}Ddd;R7=@+U z$jMhto;Pv6o{bn2?w>EsmAE!l;+GOve!OJPN?h+LdF{O*`cVrB zE&E%I!bp-f#4ckOHHYOe0fTt0NV=B1n7z#tn}WC!Cb`yC`p-;^xo#%KpHcP%p&E zPn8F*%8*u75?^4vQHea<*H0@qom1ttap`@7gUTy7Xk3RG*X}MLf_1GF?YlZ!XJOl1 zr`K^tqbZ61)ff(n!V39aV;WPPBuSd>mMIaE11LCQFQh30+tP#T!yVX;4_ka6`k02` zxM|ELz5*Y3m<0UN2GBn>&xV9L{!X)D!}Rf%{Mu)U7gSyYi^^@fRo*|{y8(-dVcvnR zU_zDaY@@Kw8t04^!%kTa3d?nF1^>&r#B(fuU#&-wa58vfeL;d#&`jc``pLCd)4skO{)^78A$P}Ff_fHXG>BxMO8EDo28>b2*044YklbrS8=^x0|4cew%|ZM z7C>DPk7ajwE{d&<1JI{g&A^(?)f+XyV`;|TN&bqxSWvqpWncjCJt@;lH(uj=MP~d+ zJj$l0&i-8Z@b(Z6Sc>{#%5@a=lR?y9IH+7JuX4dLl0mUv1#xg9k%l>mY}MT`nLb*n z*F_U8D9ChuO)yXVT5}`02~I);0I%{Ob2>y?{HqD`RfNRXKH`%_mXJaJ3=eWJ!h5aR z#hw2L8{=>H1VF_;ULm+Ll*V%h7qs?_0%UfSt$BitvkPB5zqX~`h|&bUodmh1xtR3E zW|R3lF+q9r9jfwOy1IIYOi+y=)}$6?m$vFzU{I=A8}?l*j=@qOP2aw+qcH5PjZbyt zAK2RX-HxNcy&V)xePl4snz zer!hAAG`{a#=^g%U*~SPjUjQ(_Sog@zt8qi*>Ky#attKV_CQZxX?v*5ZI58y1L{&$ z@yX+F$xR%Bl_P04lh5j*6q>psrQ#LLM1{uahcJ`faQXUw`AVZktjmXA3Q4*pqeh0B zp45z*|0Sbe?|~YwBO#K?<1?h3-`%-1IovgUvGJGsMMM9Zej(&to$Zakvg_h^fg1X65^wOZmplB-vskZsqR- zw5_*-7h9a8$HF5(c7W*owJ-eAE#{3i)BX)M!gy!vZs$%AQ0WM^*+! z`VLDr3s3ts9f@VLXQY-5@+%i)E2MlW%3PJJOZfv-XVUDD@hiO?PH8OG#N80WEkHpavPuY16Z#>uGlW3p>{ zZpUS{)WP?l)tL+*DzdtblQG$W{&=DR|r$u4>Yx&_@pjFKlfo24;18@y6s9QQbEN&>!?Wob* zBJ>j_({iWye|Jo!2@C(G(Kspkdq$h`SXh7`?p}a2ErU_yY4#Dq5-1<)=vXP#i_SES z7x!7#W?Lt4`lIYURZ`19dKA7h76t-z5cA+!bNUD4xA;!D6YH#0{aTt#D>UAJd#o|M zX{7Mss*vIjggYZ2RW zf!f8nG+$3A1$e6U)lE5Pxlm@>`xqe=>g1FU;Ju27Hi`2XqXw%I-IE~SNW*RjF?$7L`@rxITs;;W#09^f*pkOYR| zT+tbZ4r2f_gXcs{BSe}x)4-QAz%;^AN&~1D&NLY6ahZl@pd?sR3`OB~9My5xA$;)t z&Kz5V@cpQoy~;!ijDhtBq@30t0&75D6fbcm10O)vYokD56&*|^Hs3(~qwYp<&Vb0# zMoItAof*_$E=EkbwKRnXl)P=@X_|B!Lj3P-s+PgUse-kCNui6dP^R2*j#*NMFX|YR zf)^dm!NP485l8k_v%R;PZLK4Pvo=JGOs)L(RJ4Mv&o1r$@)zWNHD|YuQ<*YfD;>(Y zoDmMh0HYN&t5!}KKfxI~0z(<3bX=xJ>9|ZIr86Yjhel3B=I&ap$hmS`8V$e{Cs{+$f|nz# z+n9$5Qqv>4gH8b-;}v%_+tQz*xjGsg%?WOD&>gN|`UPCUvu=y&8BZ7qI)iqEm|T{^ zJ9q+yTGQZUp0|pyfp7anxwRu(eXs8$mbs z50=8x#c)gC79BtR6&4-CIqiYrGMP{%pb8wc<{*PbyQy!_^~2LLE-R!Bu(YB7=#XawFnIS}UIj{)48uaC?Z>I;CONwbmz0?3P=x z;JadDq9r-9`=t)7Y$$7@vla;eYm(HEekk84zLt)uqLO=2FSZeDIZJQm{I%F|`pUBT z(5R)4j@OczHe!^KFM5WrhXorc%4jSfwSXn)(-ur6VR1mmDD(!Jrmsh^l6ruoQy7d{ z9KncAjrCj%jy~*A(^6b90v>_@{w z{|7sHxem(}Dx*n%RE!7?zHo3TKwzfvL0dyJa5eCqJnt3D4`z0WM|;VSkBQc|z?|bC z6aq9nR!gR}Q0$-~5ElpHb_H9j*7So)qxbkhNPMZ_u>NVYDQZJgARer@K!B1S+tzf9x|mGS=7a1DZyXaEyqJxaq^EHMjqOdp!riTn(`O6aC8- zB1qtSnJ5~1^(Zu7e-`6m=l`kr1C<-XnU1l#mB9^hb9vCfQ67RxwXZ5Wn8^1~7{NAedD$rB{U)c~2*SejE?L;!|o z^fJKe(lEq_y;`JBlW-MVuJiCk(ngE@~gK zs4?71ZvGal&153v%)L{5T+er`a+BP(^XFwFzjk}`&`3^x&{p2YoBng`!X=&z7LN8N zp?I@?(b?zu)q0ZK-KwoxskL%*1bFMR&&=_s_d}41w;!KKaXzGjk7VWfFz0PJe3AWL zu)Q?`2b5MYiIFImJ$}Zv8bgu+7)gi|P)8q7m0$R?hjgfrRi9j%JXAHK1|Iws=9Rbd zFg)u*HAJ4!89#+Jsx14{2zLB2yzpr46!b$@@^G!>fR#L0l?c-J{j+`H+f$LB+?7iQVQWw;d5#dP#YQ#9vA*fw=3O}xa6$Ry-5BVl&0C# z94zAgeovTLKGm?_6B_jS@VODtK3tW1_3qc$HKHUpDZ-IGd7SzBI&_^ISm@gO+D)#a_Y1MYEO7(VzGg*8} zd0`LEF6~kHEHkTv(okkd#$Gin7fIPLqJ|g_7H_r6#G!UtJe$xD3q5dGUqbujh}S($jVtUDgxZ!~PIcpAM5EgE?;-ppvk&RLC68O#*WatT3q|-S zImukBa-3w2^HnFAPpB%OVcJ-(V}M7dS8x+^iPu4fHi9SQL%Yj3=sZ7E9cDh@24h zN?x`&k&qZ)u;~nejo}yF0TYbkbQzqS&QB?ohQ1nTE0FspGqfn3PGz%ScJFq7>2d@TF@zyYDb zL3oMIz`)m8(5EdZ0VxR?Jb1RE>bzIePM8jQsj&y%kq|7m=J|=QfgAI^U5!RLZ6`1- z+P*47y`G_-W`A0Ks zh6o1nGY5^f!JaI>&6qY4p=BI{A{3DbtXKp^Mp&uHVbq)%in5i&t1-e->DnDNATh*R z(pZ(72Bh-ZF%v2`R;BX(q_JpTyHk{|fB*L;!R$)fHX%PN(=MO^pzVoz@=v0iBH5!cB7Tlc#=!~{X?}dMZouKVTnYviRnrggXtJ6%-gPa zDGGgsPF4Z=`_jesgVO!F7=9^A>blroD;c7TEd*VR7oq~j*YT37!=-)}(8XLF*=n#M zUW8&Lr40G{if!sCF1fI2T{A&MIj}{SR(JNNI)hP`<^q@?X&};dQE3-UOJNscf(Js1 z|CJZ#6yuhphKde#@(CB$Ba*7|#?scIN#adu0?gaL&rF;%kk~ZpNc5u0S zpArYq3?~ygd@%d7{Xs&ahx3Mp;O2<2LZ&|y*7RU7AQbv!*qe{5kjQM*VDZ`~?H$?+ z6J~|qDhq41eWxs}wfn5sHsh7Ov#AEl$+x=R6U%Bhl-uTm(CL4YIhSyg6YgVzp#t5p{wB*1P z7VQQhiNXlU5h%H+@Yy zUA(WUY?gHby8RE_I~+d)))1gA4WHtI=?Tzwh{>P;2w7lPY-`*BZxHXfT#6KL^R*79 zB~4!GLtZGIoxyc0i5&cj?pMB}?pKzpbmiA_TVBY6Npptg6Yj)>BL@G}F{(r}_OsO* z@+})PG$b>^wb@-&d6R|f%Ola_^y~jut7{ujOOMms_$hdtL=~s{U+Jal%1dS2QOC?z z<+{AI`f1y_B)##FF;|Le>W<2QM4z6eGC27VMeC4kp9Ao&e)pCbo0UhCQ^PQGz8Re@ za|At^#O2+3`zoh>WCIs9bxY;+!OQ#At=l>MV-6Iuj@YQU=6!PVe9ccK!n@yuIe=sb ztOE(ITlJi@1M(8+Qq5U^94Mu%SGo1a0psO3pC!cg%=+X2ir`&+<@ejg!=u<`@T5G%cvIwaMS$U+(zpU`=UbQ zGiuzCNInZkqApb`Qh?}DVO9-|REp)Q?$svIqYAKIhoF*$9h?Lz^`p@)M`53fTKg&= zhDIts4jS#!^z8FGW3n4Bpt5$HV0(jF%G1L&jYiNzV1Zec@pfR`UPP`n`>TQ`4oQ1YIWzq{@;VUt$#%Z!84dJq??W|bt z`zOttqH^0ZEL1h$mYWt@EaAMiowTWSJdri&0GhbvsGB4z{GG@7iY&HjNVKBHYb7LO zq2#D%%FsIjDCr(m$r{Z=`W-{oG;Qb>xZvp1dMi%2u67_pMB{AoZ9wHH+|9s+S+XK% zDx3i{LkdXt-=eie6}x9gaY4nGqv0!4zt%L;%L=w+<(;jK%@D)LYp~ZxUb{(uUrjt0 z0GHE}Cu7Rn$A#m=?yM7{GWMJJ5`9p zk{6IN6igc_ekXhYcbRyJqIzwz1aq_;A9bmz_}rB|xwZ(P_e)|bH5Y%sB&Je>F^Dg~ zlMgfrMkAjjUSuwq$DJWdER#-X4 zVUk=;>`s*^xmY8CSoP?7J-S|3gzts@P;uVETT}U}>s8Z{-Z{jFwRirtEUei; zH$fti!Ie$yq7V{PYrn#!ubgW5^p2Nw4neVwSO%L9`n(Q1$vS5MXS3}e=?P}W!SF_T z{GnzR6s|TrJ9d=neGO>^jDDn@YxV%l0T@F*b!RUTJFKxwkEBBK8_%vD&=VTT{?fUr zJ~`Oz*+I^rvZb~w!zuLfOU20!m9pAY6T~+Q$jxc|Fk2gsG^bI{wl*GCpzvl>2lP>t zzFn?d`to*^9DTA^(*k8wk{5MFdy#y)D6gqc=2L8*oEldo(}{?HRhi}ssm6f^^p@!< zCD(#5i%qU+&!YHur@b{Uy;QouCI{ZfL={U{4iALwcyT^QLwx4`ugo>3&zhJP%&ElD zT&qA^kO#e~J4t{IR_|+aq(!rM%JBC{O_-1yk@BTj65xoUMX5tmC@nk=#KF`LHl9ye zHi`AGnmnJcgpMX>@R%THM)UPS&a9VXr<61!->;+@m82OpNi*6nX-GRQNz)yWG)S4( z;HZ?`g4e8+EI&J$TPi#COl%qzT8x_X>y}5q;?$PfoQ#VboM$p45E3T+M*gjGW#I@b zo*Ou@w$5P%z+ddGDtYV19FAE#{hbW+FuLmq(t~UAhvf70Gt0>bUOjP0743Ii{_j z`~^SNyx1_jn5&9eZxYdt`^!xDiHxITP}KdTjEiCMH0HwY-df?j;o)6X2{3Gw#*ooW z9~G%9MlT-qx)({YLI?C+;kb{C+c8WbtO*BW%DQtF4Gp-X(kmm=hYX{j((MFIA!;BdN25VRD zo#6Gsy%U-Q({l6y5-cK#PgQ86Di>Dl!yJ_~)wNW!!;+i3Y-57Hit(Pz)~$)q-4fZ$ z7E+qHfB(|r9BwM#Gp%PgTb!_^>4NiWIp7J^jlyi3G#}Fq2$83Rh8|K&hh*?OakyCz9~Q#<#AsZ#iBWl} zF4iVS<)zBlKQRxRU%^?HzZR?_Yb4uIzw5J@=J1NeuJU0NV`gMA2%PuLaDceUQ_+z~ zVSq#sBrT+vHsrNS%^xek9Iso!TprDYY}b$!R=Id6j)w*)udu1jOoG{102!F{Y)!c# z(ld)pr6_}Ohou4w=(S2&Qi>;FCKf(hj)teRPd$0(-yLQDRed8fS|>@@Jw{uAcjp7h z(Wl-J?9;E_lx}KUZqSDn9K9Jw)p*jT5Jf8xLL^Os_bXd+1J__8dUmx|Q?^2_^?586 z(IIMW9C@>lJ{zIaETqUr2sVrNa|hlF`GP!3>&+G z1KeiwgVF6B7xiC;aa@LIvR$CZ9n(K;95S~pHbbf%(H0YuuI=0oqJ&u3mq^X@^?xzT zxc(p4ZI$h)Qa$$>R#8-=GL`kIp1?kgm~)5y@&!;ELV^NRz7wkr_&`wSCe{Zf zQIg{O#9w2WR?SaVl33t0GWbvZWkv>D$}%G-6W=EP;eV&9hKQXFQ$tifObt=FNxKCu z`la1~;<#_<+;_y$_co*PC9H5>kF@8BoDZAiPB*rXJ`fHMoDRq7xGES|{Y+uteb@>H z2^XWK$so(wZD`j(F_~IK(Iw5xfZ1j_?TZnG<@-~7SqaMEOp1;nj;vnUl)(N|Fg=#p zl)#_sWi}-!OQ8(Ufvyg~m#9eKDi_f4vcVhCj?&@{2vg2}@eRJ`Bc@YC^e5cc=9};p z$j33>oH~HQ%bGu$QBZhee6#2Jm9izzJ%52XuyiSH!*J_U#fkxomZpu)1c9C)*3B(# zA3PJOR`r;3+KQhmi=()=;+@DXm-kd+QcuNEyh@iqYc$;~=oRVH62Ar&;#LG7u*mQE z3(&JCqacP&;Dz!T5~_{v_|F`moxfW~ge(FrSZrP{bf#a&h^G_mc~y30z1mHH6C z;bL17Kd4&j#;PrAGA~I8;;~ccp)=RDI_M_B2#iA{*^xyC$#l5&|How zb1AlQOqoOKp6RK`BqmYtl}t1x!w0WA;|d~^WD5jrq?dteP+>1KHP2lqK^mVH6jEzfSbFTb6)1t|Yzx&!*{K~}<|#~yqbq{# z>b_Safj8eMXY4ERTDd$ZvTNTeOY&O8hsD$rDf2dv1nQtZ+Ln}gN<@Y)wyaf~T<|2> zpFvoNJ@UDJkka;v)0#_9km~y~Uw)A4-w&460iW!JVRZzdf8VSQoc&?2y8dQg6s(R| z?C*)y9X=MTJ3NHd@v^H`r^M8P5tA0;gJMY`gngMl<@qN<{q#VPVE9`bUuhEV!S5djYvhk?9dRofx{NZZ za9ZS)H{>A`lLU+Lty`ELi6V8)!zaw3@?aFCV`WWAd2zq0`#(HtMTLN(3aqx+6ao_O zkby;*;>YyM-fD?O`4$)WmE4vuO51!TV;x}GzrM?&C?tox)B1~l`C~UxF6A+Lmu*BL z-j;6_IW(B4N!;z2iwz;v$QK&~6}{cR%AgP^y2gK78rW#Q$}pp8F-TPl2rqXC&qxha zPx!lQrMOv6dZykTO*o9Uu36W$>JFH|gehoQc9slGQ=Bs?aY1#Sbk&(RiE)#{>5CIM z9stS$Cqm>T*X&cLhv>EPJJ3(yTnU~6vpy%&NkeDoLbX?H%Ex~0IC{1kM~Vx2(z6+T zsb|lv)Uy>PcWQdJ^+&rQYyGivhNwyEH;n-A)OKbnfWg&o+n=V}q3So&yh8n+!@RGl z-zo7sngq;>QJv!2(q3bZ)Tx$$)4?TR+%sAMUZF>C>{Fv{3D|ZaFttNPi?0CJb!eGA z5SK^O*aEEu%k(YKf&#Ag`deXyDQZ{l3f#e|^ZjJjO1}Rk_auhTK`qsk0GtRjS;*FimOqnn!0bTQHEx9NgKRz2J8ov<@-nUQLcq2g9uvsT7@(?y6k5TBMOQWH%8$Pe6-X z&c1RK!|LgrI$e32@Ip26AywJZybSQ?zk{ouzYYqP#k=Lwy~ixo#l(kO{ASlvatxV| zFyEetLwg{NF({NwxE3QG${ppa^EIV~9Cpp`@8bgdBugW-;g>imw&OgdP#c&@7@DsE z%@;s(3qFHrIDS%19ji%*Kt|+v&dH<=PH6WKjM$+%LadIO0x>cuTO}GoF)Y^&#pUk~ zJC-f_A_I1;9YpEz0VK)C45iQNTbAr7n#&FrF@0bsp~~w2Xy`vgJlBl<<>nbfX)c!I ziaM}gF^X>u9|foI&Df?QA96!{!?3#MQT4^uFxx{3bJ*f4U=P7A0NR*`%a#hb=+Pk& zkJFoYLTsgclzZLE2-RTWR?RC$cy8{wk+-~+tu~|A7IXjBnq772m~9oPcsTEuIct)>C`SG<}1vG(jaEiIb~VK+qP zo?nF1I}THKb#%wx9?F!INF7eE0r4#=g^Ga5;Ncx!V7@NB5Yb*BNAr^I7pw6~;tMf- zg>r7`kV;p*U4=(&?yAEl*+-mw>SOzR>SH@^hd!zruVkQ)Ac{*?5~?GsFq9*xm(NG0YdGmF&j>Z1Ck?K?-^d3SLLfK(70cKkZea#-WOfx)+ z?G{VxS=Y+G61JyWOXN;m-?I?PBkOzZ?2tcW?c)6Du5j+a`O~J;ZBPT$SnCzW7t6|z z{>dCz?}>CdKzUX-y{_4AsUOQBLLB^7E43rVp|)aDx21~&*=}ttox|RY_NymJKEPR{ z?SjP%TS#$A4zHs#?#O;+XW$8Zpd}9NRrI5UR15pDJc>~_5!05T@z1|ge3Z$dU0di$ zwKk$<6?#!Tz_H{^iRz;;DfZcY&yt5F#eH@s0ahu}*}Ke)=Zxya7PpO2B*hXve88K+ zprWz3WRuvUSGUy6>4qwd!IYjl8$ir$h`rvmA@&-y9WRG98lP{hlaG+9Bp+ow5vrrp zic7S(i7JM!*gk;fPZ{p0gNTU2hm~|T6Ns1fZ9F@%BoOx2qNbbZ>C37DA z4=r=Zcl6?sIil)g%N$#2igXJ#Kx3ze3?i-7FiLY;k|!#9P9rUu1Mu7^s<_`F~N!>%)JEUH~y1IK3f~3h*8-J zKDR$yoo%J@Ec0t_gPUI#19ant0ds{cqs`*0lGj@sW?TUTJHztuh`gZ_lEqQaqogQx zQ9P*P?h+$$9r+aSI|>O^@w^KNe7ljq|DcQu#NSDwtw3}thFu^09JVffB4!JYW)jATF--a#gW7>LhVY4onAB+y%xNFgtRN1@?AWLtjWlF zD!q9U;|IP7wX`9YxLS+EqBO12fffclP;htI~DQ2GOXi%(20@2@s&VH}gX?pmr0 zuTyp`9mje@{4K9%A6_)fQ`xVztk#D|daX;7<@I&{N8TEe)jBvy%5n8M(-ACn7(^AQ zeA$7pLK$M&PWJp;qLEuO-Ej=GSfi2eL?LLB;ilwbo@aX$)u+%yjSL@5QXFX8{&*Ij z%%LzXg#=gK11Q1TYrfdt-dxtE0;!)%d?}aDQ|BPTr6tQi`&09Vbnmo$NammZVfhZE z~ztFVQAs?Ev}bm$BZEJuEmW4 zwy7ICwmEV>$AS|zT z{a^=o)z+3|PMR4DE9PFhW3w;5{Q7y#grs0&<>E{G6c%5iQ*Nql%Pxj+yNI9Z_Yz+$ z+@`q7z9po(IA(mt32jj@a3)fh+M2wlj_-w2yi{yU@|?`d_M{%%O-QO@V*><{shAVg zDTb;VKiMPW`6KY6tKXxaRk08W7TyV1p}BP zy;VBWarHJ3t}PsyG`raZ5GijYeB@#;J8PNu(hzD8#wBN??ebCYlUFp@$GKToiayY* zZ!$q3!JsuM_kiGQ+}hxXA0Ok%!rL6c5NH;6#)knB=eE>a2&v_eVmPk;u4L^qdk-j}oyCb^rlgxw{Y$K<3Z zRMq@NYGfN2Gd6@CdsBKagd%&Z^k4-|u7cp0jwwG@nJ^!p9H>MyI-{PE^T~3m*YlXHrbk9?HJ$E9QCNiJpg1vvv0ERZtf<_yE*P+k|Qp) zk&b8lUKD;W4!?`A7JEP*VJI;B%g7bx5WcUO^s_oG{p=8$x9O(R!O5oR_!M7FwbJVQ zRD&RzYEhq4Qd0c*5ccv@B`Y!dv#3|%ZR?1&#py^Hu>*feS*`<#1O@c9(6l|K*y+Ck`5!qab*WG0Wj5?5Sak~}3&SJrbt-zx-jljq3 z5pzo1B5;mt#%f3$Nmr?))N!z zbJK@E+*@7(o5d1yC^f~xs5X}7cs7N;VpEq2gO%a`r&Qpgz#wxecS2GS+mq}nyvh)p z&t4f2I!vy8Ahga&dMl*2FTHzja)ee@)<2P{$D$dctK531a*toL@)an5D55s`V;sl% z!Y@dO;}^?Au|Dl%i!(zFz#kt_yjWBNfu3qcktHw$0bs~bqK4y2g&F~yYRKdGRzt7* z2NCcy6r@FONAdY)WjsAc5Us>Cz&y%j)Pgk!AX7K$vFvy94*W8lwC`m< z2;oi*Ba^5rX;~On(=T`tw{z;oL}bXYNXh0bV)c0SKxe(@<;imNaxMX?rjVed>`eNU z63RE$qj*M{JBxU;LqB4Gi&>um%}!2=c*KgQ$f+IlQ-Co5*cG**YFeeFZEwCQM+BL2 zW#Y?JhHObJRy;=kX%co3Z>=GdeH&4mHhL5H%+9={*V0P-WG8R2>Z~M(M7>zL7$U^P zrE<4wLqZay8)1>2!%<|F7Bm0%Hedz6>5}LqTG+~YJuf)GH$5SoP)0{*V8htjFPMe) zY<~%Rc+$mz`e<1Zc?PrP?}8V+(yk)tfP! zRc|KQU#SXDwSAF3yUjrDeE_R&1!u_cNOsCLwy)y!NZA#Xg*FB;@b2X8omUcS`cIGi z(H$T9qpy7`rxtaQ@}iUdW3Vh20bS4*Bql7&5@UBQK(vpS!?=%^h&Nc)t@w`g_5GSb z1pS+~n--uLHG>L3&YrSya6->OH?n6#2fnvfYpnse{??8O7J3d{&yeaIZ(9o=Shn9} zS9wyRG_o1#5%gEPK!id}^!_ zQc{mwwBTmbcv^JgqOgOgwh@UUQ>AMAkSKz{hNO|Xh$@b(9;Lk22bCL@tGqvd#^X@zFY<-&-{r;Zf0)nD`^MbwO>j-{ zyD!PU8E8YbSat7e?bwn8D^-Qo_IqoEe;-E^df6~GwF*)+nA2xBZE88 z(A^exTwBcbxVmue(>%?XwL#UTGN3{WG4oDDCSYm6IF`oY;mS|w0j+b;m>2|6>&D8d zgBO`CBE02DebpqVjiQGrqCQpdaYz_nkSPSmVY~RJr*$Mjr|7ajSVU$grq^?3H;Y9A z7429YC29p^Wry{^&d{mpD`d(6O=|iI9Aps&7EI9x#jS-eR&9AIn$H{K_NRU-nq$nC z=H6sBQBr!?(!OLieLW0B&P(s^A!FUsT1`LbmlhCekzCL#bMs{jWFq%fBJo3N&qR@f z1JS@MLzMRp-!$;axM{%oS7YLQahY&LXJ7uOz)Ol8fDA+)%D+`bDI+z5nqv7AfrW6R zZ*R2921!z7!)bCa<2K^4YHC{p19m4xEj5nHteSz;IOHb7fDwMD1qKt*b!HNgOa=ru zgQ#O_3X@q(D@3W_ch3ThSnt`#_e*(bM*UYc0+ zjX8T@N;C^#kDJ?MF(9|uPJb{2r7QDAa|C|CU^JdMV|`O-(^^fTy=2orFx7@8HP)cY zwhW+-0FVw^Gfxpx6RepfE-iT3{C7v2Oav*pwMzoM6R*4lhiYz?NcOJm77>X`L*&#^ z2m~24iAoe|fcGI0reb~O2|S)2VulnKinYx{Yqmyb{J>3_7L&G^+qSyGJ5WbSX(oUPjcU%I=n=JGen`su`8YrY6 z&Em4YI_G>MChTU%8iP~u2}w2bB#EZM-1qAD#%&x7 z9CFm!M1*RX0I#$r%Fd+a}qQW+;hFL*_{XX zyzi&4>vioL(KfK|a*QSkb;s&?9K~r|4mFEgAp@}q@XlwR16St23av^Tu|os%x@UcN zw|)&NJTVlrNK?#8b|5H0AD}iM$^1p*<2_cgzgF^2D-i}*56vQx$lZ&I0XJ^S@KDQE z@x!WkqJ)c~hp4&V_l6Ey!Q{{&6DzB-U4hB9{d94R4(Vl*0b+l(Lur!9EeYp?@o(r>`Zm7UJn0+ap zQwAcOR5g%Etc4Cco`Hy#j$Q(4D-#w_DkMD9N`>aB%Jhkd7hd_tj#~x$0S^8;`vE-B zn9OmZnx@Ws>Xl?)3+IMOf;pm;BFrJ~h-8HlV_t|%012&7qAbo$S?D6vRLz=@JFy(S zg@uu0ia}30IL~Ar7gaqujb0l>QI!p%XsB+8QDmyPFnCY_Q<+s$Q@~V4uM$L|q^cM( zH`9J|a>(wJU2*okJ_1MI=Ly}R1SX3yM|5}m<=DDJOJfM!Bzq+A8@LyY3!RbTU3eAs zdvj~Lqkhh$Pxy|^;OvXCNrK!RUq=kjXo>a!PNa+2p6!pIn&oEp<XMm(i#=g{KqjE^HCWJ=<%;YDBYO7bFRr8YrYBgm22%RGoa% zzBZCfFz|Hni3v>{PmRla?M+GnfI6EK#JZ_hJDLP1i1;r~s=__;#eNr(KTj!!%NzNT zh8Xn#SoSw=M|1QW=bez{KYYRNcN5PHNar};c;#e9 zGAdtbQ=I!udTvy;sn6C<9bG-kqUOg~6w8ESl6n?1Czj7_3Duuzi7~=Ia3Kju7LdH2 z><;5nVtkr4Sc)To>Q;fXBWkwp+q~AR!@xFqE5zpu&@ogSnV;B|s}EkaF*b24FRXDp zY5rbVg{))=J6ojlP3&w1Xo6>eR@x=7nP1CZW0(=C?;VL5?7LOa1< zW~#g82`zP)QLn&N_o=uRK6(Xu2`90?wx;B0B#ZAxXLO}R zT5eFwe6ZQUz{x*cVsJx@K#Hr3bNo)O3|Og@*JxB*fpphsR9nIAs>XOnrOXz0m4!}~ z_U651q0^BSl1HIZC0zRj{+VgL)C)1pC#mA%0zKenj&RYz!$u=)pAB5kvP^%QDmkVd>K2*{{%De1MGgw!cft0#&;dU{?60cj2= zaI>LZJ5m@Fd1B<<@=u(kVwwZWSmrF=FQH1Ho8_%o;G|QVQyh*fNSgrFZqdjdbFwhk zk)v#lp}Qavm{tf971o5r^D0Q95HVlUEuBGmz);IGhgsZ>s9&y^^eyeo*CQ0)7Z-G* z_w;P71$F;__TE3ruB*E9y!XEM>Q&WyRrN~0E&0cN5A}3uwLPe_+_oE7GrF|`m?4=! zmY0`jE&lKy{NW|Jd$5P`VrFDp!2$%hJ0c-LfV(3TgGFL>i^edbF(8POD8U2)b|b(^ z)WHcNKyVV0n1oE`^WFQLbMJdqW&JQt(lc1g_3l0Q-t%MsI(zT4&pvWZ^Q%)~j)svq( zv5y2ggZF9Uu=k1Dst^CgrzswIaj^^U{3BW%3%N-IZZv=lSc)BD+GL5ilu=pYt@I|5475K~!G$(Q>W(Bl?l5V%RyH9Iav z<_Fn2_LRAAXqTkGo~!gcg8?7Y(t~XsLR{5Ol^CG!jma33Yg39@XzxML%r0R~3*CdTQ~o z${)sf=(gxyG?!G$K758D;0vAO|MV0$f%@p9I+lwkcHrC|`Tqa*E9G6)8d{ly|FEva zAZ8zw!It>HA>me0nQEzPR$_qQbCtTtTxE|6x!&pI@raX$>wd{>J1#&0a|4nvk99S8 zzk|fl<;+MG5Thw@5GhIuIkY&hmdxmKi#+`&xJ6z%r`4v%9RZrfWy&1@^Mw59`Oq%U z*kz&8Z`&mjMIK*sr9ALo%s;tbW%Oa+;qMT+_poHnBadtT@Y$n}6nsd9Ao@u7T+Y5~ z6WQeS%Xmyvf9eUV?gwIdyY>ps6#F>{7s9F@e%BZFp`=vB%SP>OOT`9RV7!YQAmU2S z*0Z0W<7Imrg%YFrA}JB}Wg$j_6)ZP>5X|ZLcsG1b+sP7clK5nGs!`%5kBiP|xddSs zY>nB;xrLTcuWo@nR+(Zb1wX#DY6KbCE+C#UafVnxJSg*9{e}K5AF>ra!rZSyQMwLf z3xumr$sqwJ7RU%h1FxLO7w<7hc+=nJ3uQ7)lprjS5m~JGPGGu{Dh$p$0W(1}i9iZn zI)T_Z4Nf35xJ+U-Bap`(qlrKce)qfxgwHRAK#D*hO4fl3K?DL+7)~URb|8>WB#51M$4x)PtTMvvq$Pr+JP_43si`l{>}nGia9Cq`@#vV6WP zw)pquBY8+XIQWubm#s<-QKqx@Rz|OwmObrVmRLA|a98>aUw9bzsjRNs7et$DM}{ly z=TujsoUYXt#IPAPKWv>@U2$_WhE13j-CX<(VH3Vm>X=!v`bjlf_8qg;R5lqSpfU?6 zK!1(1wJly^YN_h9NafK9XwyXC=z%E``R!b&iu`s|iZB~x%~!)%J{P@%=sBO1mMv8i zsnFCoQEA3Bb!nj6T<089M8hKfGFu88L}%{6r1tjBM}{-x7Lzi!K>IOH@z(PT7s5Zb zicQJLlU12TtCA*+1X0OCE<`(aw#g5-tQxEOEuvhr?3yfd-pPy@O+Tu;d+&LMRrZ9H ze{PU?b_p$T~id}QbA1#kgqvEKx!|%uR1BCh$XbTSnzo47|dRG z;(Wnlr>QW2M_bV;00T3p)cNv+Ld7sz`Lnel!V|4R!E4eVW3G;+JaZ4+I-L_z)*;ti zVfp5YyF$jfV{;d<=_v}Ki~mg_X#FgNpiR%rdK8)&wO9b=yTQmIk_4x2Je zc?>2K=^sWCd06gYK4?{hMaR{X>Oe;(9EAi=j}@AkKulyTl&NvQbh6kmGak+Sj0+CL zUq`Ai>v>b-M+W}DGSQ^jENAbGcig}K%r7Er^xgS=4-Y9on*ENn{T1cO)3>KO%#pW3 zbzGn9jHYBspx2|`=D_RF-F_I-Nlp~$3SRscIOnYcQ7qaC9Ttr{H~9CqWd1^<5t{j8 zZ02~QnPZ*N^mA%vJ~Z>+s~JV%J?_nD47ZGID6jJ_`s#s*O3yqUtfAfpuzcXqZ~fvA zkY)AJ+5)BKd?{HFgsfo$7#GcDmXn0r9@aaM3p60@K#>xa1vp}gW?S|%#Y zdY~2`4K1SkT7*78xwRG=wJC#++L591W6%KPp^M0GkT8`yI1$WBDz#8E)rua7BC@|{F!_mbB z0*%OdeiCil`KEpQXs(tI0_0K#EKh2XhGOXkpY3q%!dX{Fze7mis*Fe;Dq`#uv(@5M zIx0v)39M-?z|N$m;lcZDE59*$5v4V>ILBIB$L&au;e3~v&za^y6XuRBTCqB*FvF3Cs!(ptVAX{H! zR_6rp;WWSU-kUo?<6`f`z4}~~IZ5Pd4#(m+onxOjsaF&mB@ERFDEA8}&CCCWOFz?uUqso~5$JQGi9h9wDZ&cJcusoMI8y990 zAxUO5mO52#iia@PSngE031O8ty0B9HpraS6etw;dCXHl5@i}jeWYu<~M)KdO zU1USlNJM~)sum5l4j|}%<4w*XJS{!xMW2&-E%qVDZ{TcXFUgcwC0pjUS2HFT!SmqP zHIvV64=?^knAe4PqX13N<0{KR=-yNoX&WI!1_H-AB*I~hqCV0PO~YnIJz(CPX$qv# ztT#&i9uon7k;0tXnYA?2tk68@6`wHbRdk<|^u}}_jajfw>!X?jRp3ifG@^D@Q0qC> z{d{eq2kJxhLGJ3Veu%p?A}LiTLjRDaQjB6(->rs_5>bRTm86@XSU~@9!!S<5obty^ zNm_zVO-WixO417YGs2*eoBIFs0(G2PBuJ}RX@wjzc5G@j2d!ikg=WJ3NHZ5oFVnx- zAD03k){S-Sz9&idzdLxM&^u7So z$o=r)sNosyALq{E`*o*H(>J|17rUduW)JF@1P;HfUsDCMU$x8Xt9DcrhY@irxwT^c zKQOhsy64?ea%Y2*t3K*Y`WV9qnO}|t4K^r7j3;WXJK!F4Mo0-MSOm>c{DQzQJ`jng-6aGz^yEGwl=`hHu1Id=6b>|PNq6$eHwhf*PtN5;)sg)#-h!R1d zxR#VeQmYCY8aj}nK>{g=z$lTd^`_90VDiF7Js(SjjTS#mA(`K$(u1W1z1n{>X zV#%ti%|1s&_@HJ2Lr_xI>T;+dyX9G}yFZY~73>b@m{2f;1w|`K`n1Uqv-jX2tgfCV zO4sopSa4)AG);*!sjB>rk~QOQ<#C%DYmD1u!GR+K%t*_wA5AH4lh-A@xh8HCuTGh> z^2$NiM}PBC8GSmdHaA1%pjIe*F}|`$7(-pRBquMOyENd;IqZGW6khOH^^0ly`+=ARTNEZS|hQ^3#9ziFg0icTPY2ho)P^UV+}hd+s$JmE3LiRvwmVD9_|L8~%S=%Rf;- z!OZS*58&ZL5z+YG72Lwvhop=GsAKFRBW*wXSKwI5v~IQK*6Nns?%*Iupc9WL>nfLv zWiH!4)Y?eXFoXFA0f+{^z=-aXP~W0X7Zq7;a;0xf5nw}`XRr>;Riu2|hxKR$cA73{ zAJ*Wcae3##Vcu*b`mWVSTlr)}`fW@s<82$PrhGo(qfYW4_;-!^+EFgCEjF#sU&R<8 zF-DOS;RR}k3-nJ63OJvW`Cg!Ds=hAOEZLRv_;^O2S*|>z&mxF3vkU)P5%ka*BM^Pt z=W2s}JI593t@&+)YF)*xuK>{Gz9^FuE3~R)+qv_!J+QG+l^ugC?22Qk=r(DfSQh#!{5+c(L&*BI?&1AcoZFI(#;({sCAv{peeBqyry-qs()L+D}BL> zDD8!c7@009c)sCcKEO-D_qQZkBCI4;9a?(y`{89>f4%YHpj`CRQ62Dur^2%f^tt-i zP=X)X<&wnpZh3Gg^zM*$jd6ud(NU-xY<-SQH7@z$xa=`oA*m;UzHN#Txm?itq;Q81 zN8(fnXt)ax=}4JSyuw>(mZd&eL%C76CrLu`^wUNXtEk03&WL?u5mSrxQB5iucC;CT ziafsP0cre^v%XcKc#q_S66N6EjsdLBheUeR-kydR%58<0+abRir^j_rLmk%`}=MC`yKraNSlPe z#*b6}=41jDoAIsDE&O0CKQ+>)`}**QI(KV>EZ=;$v>9=G{2e zOXtlNz8G_oPu4}dk;5uFVrwY>V%7n6NOqZlsn{58YSapnTqe#G6pgb31r0_3hZHpE z-?s=QZ`oRG_?S>6T)S*KoZCDdtQw1Aj#8g)3@+GNF0D=qbG3Cy`aKry-Y)xWSeR*c zf)zjjNy~A@Znkwn`X69|pd46(dm1qb5R&->Z8b|{oYWl0zBF&5zg3%mqp3miD2|HC zPT#`v3_+5s)~Uc{-ymD}%4PtcD_yG&(?3M_FN%x=zFJUI@8ll8D*Ky`kbhP7*By_n z{eDLo=U99iJ9-s`cbLdcI>V%asXYQHWgNnY8WU2dm}6Jy{Bt3nGq|9fn%wruux`>C zYk3qjXicfyR-v_XEe{1!)1VGRX0B1-;|Bwp`cfnb7P}M`U~wo{wzEfrHgZBr6)acg z1a`f@4o6nR2UHe=1?AD!Hd?(3Y^8u=+`M8A&oh(J>sT9pN)=8aeF#sNJI90!(4Ru! z57mex?F_UZBj9Fnr#KGiy2<_ z0~TmMtiEvleHLi3owyFO&B&6H+G>YDag67u!@D2myRU_Qs>K0q={#}!l8!Ctp7W4> zRe??nSnKreEcn`jIwn!?0@bs+B71Jqj;I39iaaVr-IA#(hz3hyqs7h`+Mu~u;fnw%B{-RLvy#73|Tai<(KTKod^P*`k8r?qw-ss+PCQ==uwyA2olK29I_x%ojQ+W8N=BBH6{Kli^Dl%Dg7&nnw_V|TdJJ%GX zNlGfT&k)x6G!!43Pf#{CO}Jx&b@(*EasMR1-bX>PB7=+KJ>BX!!zBi+KnY_%BkyNA z2v2?EFuXd6%Z&DgjDzuq55#3`yy)}NqV;<&pIo(m7Jt{W1U{X#gpJYD@-EE?Atb!z ziJQ&SLYU19!kl=E=Ol2gEN%__3!yJ~XvkaA@X&ZFYP~%OUS8X~cZz~p2j*M++sDL2 zgBNALtzUU}Sjw<1fMVrIg+;3fMlxv?A<{?ES6A0ov~A^Zq^6RVRC{}H#N#Wf{h|S* z)oRY^tf7)mW+oE&lf0MQcm`Q;33#yVM1RAHji!=oqL96a+Zv))KrocU!Neg7(Bu~# zaq=FMR2Y+9?r!~t1PkTv*>?f5gcwR94Dl327&`#5T@QQOG2+VHjTXJa{#JEJ(d6vw zH=$OY=pc>H`Zbv2a5UkBa3F2mfc|lmn*CZGc_{4_PCp5>dT?QWzXVaT^x`QcnNmvV*^CLR#rdzdcn{VZXR^-@f z!p!*#K1uCp?Ha8wJ2#j$M7id*RyNvDwr?o;^XhGEIX2w#R}=24CMgx%skiSeyGQRF z{GqAw_sS!vIavfVc!#%Uj`8ocaM*i>mZu0nx?k@+l~t$Sj9~xQqwby;G1F57Fp!LL z0zH+zQqx7X@4k<82XC>Olupb?|AWQ~HNbc#C60;|X=lcJH-wcC0N{c!ST07e%*wpw zxJ#%}RH7F)Nc%`CY#@u7&;!{N>U~XEXI}zg*Dz&M2f(X?09Xrnqx4NX$;Q$^7`-@a zO&ZAm3q*?`zlqGB)B~Nz01}f4_Mya^ubP27U+2Pcf17I8)W~+uLW^1uX49Ue^!wV-WVgUBh?P) z61&=IC!e)BZn=8^2AWFCGd9(nffIs%%#4p~3T5#i_RO+)AoM+p2h8u$Hq*61M>IY5 zNpW_(qZidV`}WHD zxEWfVc2UCxZ!~0%m79!Gd4uNlzLA>BYndj1v&a|wuwwpm>RVy3L$BV4!Q1|=clSC9 zByX{DN}NuE@4n5;k=myMVz0Zlb&^Zd=ADwu!6zJtUgit!LS*!tMVTGF=V}AZ-)P`e zBU50ueC;m`?Fb-=2LOn74l5y8(aRyg3|+cR``S=A!UW` zv^mu9U?eFf^YEPBx@L?Z_Q!NdOG{bU-C{bziajkl4oaXzz{f2(kC~%91AVTDuB_G9 zEjP+`w-nql9Xa=C*({Qof3pdEB2ZU55A3s1WpS-0>&wq5>^Hje@OgFz&r9z?TOnk& z8DDF`-ybsU)GICYHSM{{#+0h_?W;C1U1VP^a|J8~PQmbCwN>YQeAO9aCK)p0E-WXq z@fZt^5O}_b47jEB^ctFD0K{5d4cQ-_MD~IRXw8TXkrH%i=e@f6u!5@2H_BE=uUsUN z&(TMENndOuB{c23`>Aj3ZELc08h$`qyGdN+dm0HP!;fK+o$A}Ohv$KO#Jep!CVQ8U z-pM5BOOoMXT$5CHZ?Zigjb0!L&aRG_Z%rabo5C(?PSccoj{O*GjK$*+8xNafVMj2y zN8@dZl$%y1vae02gK{q)Va2fF z9hMy=a##7_D{o@prstT~lhf(iyQ|OH9+En5$LX2f)wkc<46n0~UB}P|wOOo6-9FMs zbve36b_ymrf|pF!r%C*FTplxC3tf3KI;w?CmK6&vNXoXnx3GASRtlkz&+vc>-F>~I zcyeYay~B$6Wj48&+!hL>fFxM$w|&{EXAah6HmKaRCL6!^9q9wa zyjuTCOBKP+?yx$l;L*WjQC&R%u#Gqi?Eodjqndg>>s$;=AsIHwf}6NEON;D!B*A{5 z2uLMl$_~sdM9egPq^}nWU)xpZaR}P}8Y|ryz)77RDQ%_F*aB+eI1JN_&+2w%ywGV* zr`jeBiD@td2tgAB(VZX$>F+DCEes3W)@T=<4?y+tklg3(Qo7Q%4iQ@W=<=Bl&)>Ct{F`6@^=z+jX85 zu3D7QDlM{l&+mL*ZW`S|msr&5r|nvqh#dzPP4NS!U?``H<{s9A1pK-8iaZN0LO$3{ z(Yw_lvqtZ4l;}0%&v(~_5-sn5IYEMUYWdOtE{VB$CUq?t;JPV}jySw)`Hdg=;+w8$ zWGePfQR(0p-sMCDV9a>(lvi6Uh6#_=jQKZ+5EYkxPp;hRaD9$U_3at4usm8xUg7tG zOwp-)C+A+VnFyD?0z@t<^)Zf)GdfYM@7pT`G8g|R8Z{qBUjLh5YKi2>+kLc;Tp2uQ z3?SdV)X3I!J#+8V8F0NM^N1zLS;GQDhsMQj%23iwSyh{-!+AizJjuDU_g7 zB=~f?Ib|gBP12NwknifGmHgL62?6L%G+(I;MKoVvZc*qY2go%JEx4;XZQcbA8%>Und4Rsyfe@lCG^P`b!0Q^p7o>w z)H~EkvX3~ts8qsVc)4+(1Q|KR;o5!9pfGwrgrTleKA4QiKjz?P) zMpVwX+bHMf(y?+{B=A-ZUp`iT#qQA+l=D+I+-)`w9sn_Zm(sEn4y1cwBT$SY&%`P1v z<&0@KOcN)=+y}r!bRp8&dbc5*f6BggBO!YGSU*lhQ**cu3mqnf=FqJkA%?Q6z~_?l zs&0S~J7xa{{dx5$m%o?N8+Cgl(W7&1XT)RZgMkr`a9K0rp{}pE*b9Sq;-Ht9ntaEZ z^Vzp9&51~_tu+xHlTXJR;GB7aeWLVR{r_Sii5G~;0`TO=YRbnAh}6!v!}ke`~b zvHgsd{O)`$w4b(;C+ZU6{&V=m&*o}zskm=U?ot-ocP-TY{Rptwu5Ctq%*+g$Ph|G% zRzmZEm0Q&(tmLfC5UKDZD%?Ta?!I$Wo&w{vQ=~T11?y2}wml#-z&$j45`^@uYC6~* z&N!CEcwmg}5K^^4jP@A#dB|+D=pwg^>Sm_rs&j|3zOv)tw&7;`v>IYCscH16;a6+9e!N`bZ8Dv*)(3qF=SCjsXJDRWv35xXarueX{hr2kRw(;fh zyW`UhdU_b)G~am4KG;z6!nSgHZ#l0!PAuFu;+z`P9*Kgzd+jt0bl4fSF2FJL{-d?0nKMqs-V##JQNLP;bL=Vk}Cw322ze@&!^LuzUq+KG=hE>Gh}PVi*|+2f3mRuMRBCNeC%secaPM%!oiTM}IBfyZ!>SaUb9V<6$&%Rw947@)b4Z-bVNuvN0@V|C`kCm zM$_p0fNszyf~(8t4mfIHt}c-*3P6#}#Io2nY@2+icO1OgMe4EPv?o87Qkb-Xp}a>M zK$0H<$JMlKu?-Go38EX3L7i7`6Hx(Orom1k!AYa6ZzrrBgNaRl#60RZ7saNn5hUE(GUj zq^`W!8ixx}2>=qbaJgH&Tg>K1VMfO(jGgfQw1+IGB7wWZsg`wxL&$A6BImT*Zp6_7 z^RNzLSB74GbTBP1>w9fORNTlEc`yl@;|GVI5|c#q2MMdrBMe+zNHaZ#{x8pw`H7@3 z@KkoW@c%Yh@!Im}D4JUnMXuyxH@gQD$WNX7-BNxcjgWnoBSPJ#lamw?V&NQILTz9b0{BR;akGT#Z?^s*I_0uQAXpAY9rx-{;acOrECAC}(r?ibr z{F5dnn&KXvM}*3%Xu~d3;acL}tg!R2Y}~G)V!-5G9aHSj#u=qyoKz~b&NOY96?Tru z-fbASnPNoLq%8vmeaSkANQuG=vuM0OOcAcnxlw~nl}etYMPr;NneC$1f^>ZfM9(%y z5~fSz#2ypQb66$5`@1cvBx#E*m9*tY(iXK^(w4VOfk$oMLpTIKQQAtIDw14qs$Er~ z@`Kp)dMzp^)hK^Qlt>`l;9f~%4GXomk6A>oTzi{N?M+tTa{0Kn3G3aYi5q!4g9Z9A zX_2&Z*K}O~9qG!I7X*+1+7v)gvp*X|0kovAJz1tz5G>}eSwp1gne;|epDilb z5boN?B=mjftSgqX15M5jYcV6A%Q?kN#ZFCE9GWg4@!JZQj|x}F8x^i4Z{n)d#KM`g z$(rh=CMbpyo!7#Pbw#vMmrIf~S5nWRUUqBsva4f`e_hKqNAUw`$@>I}RI7Xb({__a zKrJYpSm0p~y3|gkheqwho^ErF4XIJUsHvZ@j1H^CF=5>lK!TT3=67t!*xG{_+tMwR z*sYH`HdW%`;9B9>TCshc_O*g5Kdx!ildI4ye@rYqI=2}TR>H{FXsL@us@hw~Jz(}0 zR<0Imxm@13=4zpP$V^wSeaW`R-DlYtt_CzLZT4*0-w%UBm3JW16m&?P*#SnT^#xjf zd*M!~k2@>45Hm}Xz48ZtrGMA*;Sc@a|Ko~_ctfbXKV^?WLlO(6t*JM|HP_v~3&RXo6mIY|<;@EXM zvTN@-vTG?vmYcpSdDVQ65(8mnBrvw)I_M8Nvf7!*r#WdEk^-U3L@{MB_H={EWiW}b z2+K%e8qX_}y8;LPo*yP>HD(niKdgmJs)9DVJEH+2Rk%pPgtFg&~x7oNuQF1`itb@EViMBE!32#Cyl&?@5KXDr)~(w zOk2u16CP^Z3-d#LojJ%gs@Tu!cdFj(J`kJF7QvyLH+{-pZo{>oR|MVm_}iB|Ewu%(4?<{AU54wO9mZ_>hv*6l?}Kuf34gCOL7~| z+hIyJ;B$L!9n^zglxNR3xgLRB*YK60NfB4JQCX-q#_UttFChTPnJc!n;9achXNhB*wm)`oi17^+Od%FFmb|2bQz&aXCm<oSQA~NoCkWouF-kds$j7*Z!+KL@qhR9muqK){6!2h`0Cwz zw8AwZk2G$G6Zz_1&oLK;1)T_!Gv_3EKM64L2Oa#r7gx^~EyOmWKjeKeh;UW^*OpnG` z_>#l)7JbR}>|j3I^lEZ2k7;mmwaeD%mJfZMaFKz+MfUCcTMrlcUfaV(HaDY1&OGtu zQ;ld53pqx~JD@eO^Is|C<&E`v;-(8&Km+2Yh9d7SZXL~o6se<*85sl1_+n|@&AbfV zlxY}y(OfuWi>~7%)L=RW^S`2E8VYroBE1H~)5H>Cth*jzh$acGTBgVtR-)RLj1C#m zlI)sBSP83(go)TZ%R3hwR$^fb$u4Tlr-Bzo#n^nRvNh+^U>Ak?lwjhO^QofLJ^osY zmp;Q#^v~*9Tmsu#eMy?JO^dQt3FB={<2&3O3)A@3{s>2?l67ht?{ekII{l0f%}^|< ziLIK`I3*MA7oXT=ilHNhaCkXA%`x9xI#nSZ8(%tAAtPYNa=o4^!{2?QRyUeKD_24a z^KsABjDZctG8h@ATB0&8?GQ*!u)*STq)e&-GC}mDp$7ai9;MpiA~uq6@(FWG{&|^M z@6zwwHQCSWR~ln+ZT4QfuA;g1b6sP6hBo*s1=dP?H+r2KJYc7PSwH0<{H{IbYuoFy z!B*&lAS=T`mKu(rW{y!*XdI}f7zbZTv?e7?<5FlE@Yz)8fdlrrDm2_U@uZhe+RG|* zNvW6BtXwdsWW=+z{2~2ff1(s+NA0@$2p*m}ZAZ-r*NMm&rt;Rs& z+DQfqa$$9abew@$;J6qDN^IL;pv1Q4!9d2h&jSOwCIuo-*cSfp?j1`7M> zo{fPHiRcL~3o($MG#Kbxpt)wCr@33fK(^}$2Ks&NK7xV1 zreEmmIP>?{?K&~gSGkoO3+yw?o#y_?J{AI%#9>1SUoZ`g8#Sxa(6bsfbZkv&7-gZz zX@x8dG&YXPK#Z%RQoBt|7Mtvf0rD2Yi}+}45Hh7hjtxlSO%;Kr#~fL*uCy8_N7k413xRw|zkx8mX4gr` ze39D#1(C-=50GLbzczMQFo(;)tr9(=R+D=u6Ba!vk-08DaW!MwpZq}}UsMQtQ?n9p zhNrrtMz2LZBW}8XZEjThm`~8^*D1dgqSO*N4>rH<5aGf9qdNp$@c*EJ9Mp8d)Tv#4 z!chvIvRQ=JsZgYmJa1}?(SIU@hxs_Y&<9*Q7;U~4;A@#4Fd5!=ecC1l`X5?q6#8W2 z_NEl|u~68QqAFZV4Kdb(vK=@vdfv%UYuUa-?*wO=`zAK;MI=cJ zV({>KjWaMogE}2qjw4?fsf zHGSbSG*?L|bnsZAwn+P%4jUD!S5O}!P}k~5&`4@Z{D@Ja8!b#M3r>k59vuVx^0}hN z5?9A#96V+LZ{Vs4A$}F6x2$;XgR*H9JZozs&XSn(=3ECAFOwjW% zo`a$hbJp0_YRRyV0s1_>zUHnvOAI5?9!6Zd&wJ_PHJxlTqF+h)`M|hJ#}Oe0EG>d6 zKpXc`H-BLg3adm?*9>MX3xBH~;R)+-X7L3@W8qw20ew?K<}NgSLM?^fhEnvQ`D zaosQE$9#L*;1@y(=S&V9&l_^!k@HIp<9fJQ&E{_*)}`_IB0@t%2EUK7r!Cd#o4+@& zNY7*9&v##xn$YgRFX&hRS)Y_ws757U`oP%x#}| zj#j39;)q3~f_ak)N&}#a#x?*c(S{;KHWv+q>xrRNPaLfQ;d)}EpeIrBa32Uyj5qYe zdBc2pHu@p;_88b*^hwYS1i?-owql!OG&2nDd818-rk~*R z@HAhtQIsHoAOUC?B~e@A2&Pd+^6cur|Wtw{M*-bCo^4|4l8Gop|~$dX1G5HrD>kJs)i>l~`~z#OKC`z;S!xuPG0cA0Rr z#C0btC!~9@&E?~HCTx3}Q1HXpo(4kMQZ^$j*02FoXJ?`?ZYdj3=%yWdVcRi@V!M${ z7(MODR9}qg)ODUJMMm#VT`5JzpKDD{7y!XF^N!=3=LsnnN(BL*M+#S@OX7PNvL(nPNd({zTCS9kPOTb-_U^m`w$|Uii&{R+SH1tSk~1Nfu-%>P1=3lx`@`6wm_d z4zwgqIeS;$A?gBiUV!(`T1y2u@T@;xz@}EJZO2Y~D9e0{Zo>s})vd*qN49W;q9^<- zsxOX5(iGK!mU(CzD}P1x3pT3DY#{Vci+drA=9JDFO<^<#)P%bYn~iyXPtSb7sbWy1 z?S`O^vUp*BD(tKfZ%U|V;%n05>k6`TsMwuWwbWq?OVMM|d+4y>U@t+4US>o^86jr> z_+ewq%Fh`#r{K&B1`m|Yu|tj#aHC}>pI)^Jo>!+^g7?tz36zIZ_>yq(uKZ_oz^C&w zGsZ}rsV>Hb2#MWEIsx18GVEB=bD_lCCW4=6hU<>@i=Rg?R@Oyk8Kx%UlN+@^ zW3`i}mnx4~?TCUOQtehF?a@XW4YM_nnX>kj)TSeK`FU%vP~seVK&z<-XRQZa>%n#E z07Uio*nvXH<46bG=n+C3m~g;#S;$LCjJS7JY~T|pQdm==XiVbg6s~{WDoc!Y-J{FB z5G_3v+LY*pwCOLJ`R8RjW0|WIRwL7?zvr^b%F0-lEQ!o^#Txk{8YSrL#WU8jSibSK zB9{EkMl-^S7dHB%ld8P1(eZsytQYRA8)B-a5LRi9`&cS$d=5#6HyaHdPKAvw9ZH3b z&ZF3e&l|&pIfaBn0SR!EZqLYPDxBCSXHY;IpPWjCjml4_!bUgvkQViMqr+!YVPj}d zr@{tgj%d~vBR3&~!#NZ-K9`{JHyaHdOofdupm3LC?O(FKYl0TgHK9dO<3 z%;{9v=nOPN6MD1JnUkro(U}veu+f?0sj$%*cwU`(oufs1kyx+U6{$v6*64{uw3RhF zvM-i3`tgi4NqvoOJRQp#y@1cv3yw@M{S@*vi_nt*6zKR_EJWVKqS#lAgJm+B=)) zt&~B#yJwVpxg%emQyF5 zLrd~Ay1}LZ`pu8dot9RH?uyHXdMLz}oIGXB3vAs>FGMp=3e5=bMWZ6GB)*VWXS!jW zWzqW|@;nao#MnR?Rz`-y!2|DMR9kJFA>fheHCE6&TIB8c{h*=dbL`|JG6hC0GaK#D zGqzrbfGEK%dV-|eF8`b?1=ib?Gr<(nq8hNWvfqxNA_q){U8YnpBTiXGE83BT{9Xq8 ze$VhrX17Dc?zHzE$0d!Ag$CJ}KX&UNganYz*84tWf zWws$%W!XrsH>SAv$?OHYsz+l@J6IS(0P{j`@KFIH>%H%v=AFAmBG2t#nL$VMSLQ`o zw%V6pcImdQmu%U*Y2$|V>lOzK^K<>)>`bZurn~ktRhFHsx3Wp&5~q{-8B5^4G}WEP zddV;-24506b2+FlH|!ofu2L4{|t5&MrS8fR6QJ z?VGg*F%A*lGD}VAhNk~X{!cJVJ5vS>+gSF}x$QP+^`mY)JbJ;}k49@h8m{ul(jb31 zf!+x1rE?`@tOG+zBfZ!@RG0Z#=OYXH5Kj1Dm?O*jxBC7gJ~Z3HNHg`_4ODwkNtWbz zrz#ta4s1}q+CI%)VNxeH7vFg4+~uouJi<Q@rUn)pR3gxvBr&Rg%;}s38fmvm&W34v9*LEO(wb#MLnI}ip#o%=qmG3$4Si* z*+KLT^~#1@hb_I}|J=dT&ax~=erlrhEIFb`yw(hM9pPE@Nl2>Lt7-&;&q`Z#q4;4q7=hRkdC@wWzhVL|uf8Ws6+*c8&U*PTiZok)&?~Wtm;=+gvEAq( zyt9*f0f6zo$Sij%)ev3(h-cHRSbyBQ{2j}zKS5q=KeXD6x})Ts(q^AP zWF}lzz<8%2Y6XZ-$j1bZ$;CT1(P$Y$n|?QqO)_B^5@Z@iwkhNMuK)mTE6sd|!T880 zM?cNzFS(?3$t7X>h85-h1sGhJ@69!F1T(FhypfMYj2|SZ8j=`lFz1Fb=2VlZ+1OhI zzqM#w9+z3vf{{qwXKP8AO8H*{U;~ZuoRf1u<@%37#F*$=USARozI1K_Um9n`wzwbb z%z6sOI>Wu~!DObs3xoa@bL(i2p zENhGXGzBm6nx^0e0S#6V5bq@5wX(&Wc(f*zxv_ypyf08NHr&SeG@Acw_k25FT+FN* zNj4|y)OCHYF*W+cMjap%sU;$HqF&%Om*pGmKhuf>1YNTDnH|k)tlubczdNbMu7k2H ztYckc#y6}b#uMCZ`grM_E)CMLdsc5i+aNxC>6|W&KjM3V{=%E*gD_U@1)S@gi*#uc z{y9}RUstHhu?o3QSefG{5R@(_Mv_p`-u=Qnh;~UC8>11!2sZtH`AEsXWfnZ?w<_)ByRAJess*@_rDg_g^x_j;sKp2{nww5s z{zGi8ADiosHJ5)!tYB6vQ_V6Hvf0_$oShP(*2v|lrUoz*vET=a-O=S(cP5}<(`Q~- z13N|7%?7Si!B_*4IIPxqvwD@RFilJ=>dSdn$(xR)+f1wNSbKCvC-Mdm#(o7pwuj>4 z1~{GmpATbC+Zko-wMAjU*c;cyF>qVsy|Iq2q4ZviA#MaG_QKa?ouR#@BIv($XY_`2 zGJ}gQgYPNYEGx0MIqULY-t89p?Y6qzwthQA_X&Z4=BKzZaiuL30n-sD+@m!XgWsE! z?aKSJ9k_KSMSG%MAR1{`mt(XW?<0*0z{XCEb`2_e;qO|gjT-o56|46w4?WArdzP#& zI#yP15MWO~BT4m3yw7X&GEfbYWotMRh;NTFuCbi6^^|%Xu`*?*}RErP6)4c{%l} z`Vggasq_G)^OSy!(gn~duQ=fXJF<6kp50lJ*IktznjzGzsE#WVp!&}QKYg+loV@C0 zDaZ9fIUl~AEBkV@Q%kR$4OcUKhr>GDsdj3^I-0zzM>@;2FL-meLXoqGr7M~WGyNGQ1 zWOOF)o3n|)@Pr^98T8Fx&kUy0J_uagZysAJ@dEelrYX)lCy+mpA>fHLVo*juAH&dM6=7v@WIm%jj4$qqPfba zdYhMZP=r*l&8=xOso<%=mS%?;im*EKIdccJl9bp=Ksfr?9~ zTpKHNJ5v>}nClYvHRE(EqIaB}iKGP40oG?kpuW80ZSdycJ?=1P{7}LPa@PDbkm2%3 zYqY4}ldaLZa$UK22a3j%*4CLv1do)I4N>Z+Gn#{u5VB=R8O z^F^M3KaM7oKgbrOg%E{cE_;qB=Xt)UYD7&}cPRr^4zm>OazdpED>pq!f9PCggHSfD zvgzvhCr;?_#6BcI%`m|-ytQwjV4ys)mD&W!J?W(v%vc1>2gb6lTP1<9Kr?r^8cqW|+X6E!QqtKE5asX2z#UV{ zYoe4sWFjii9kQkd7btA7Mw2HH-hi3hzRX=*W~_~Vyu-v=?@M7tt;LC-e|a(|kICNU z)8E=RJ(k}HN}r>c*0mkr*bDO15mX@u6!7krTqcs(8#U|Y?h&l~^N z&5G9{S(|pn)GYYILd?Ll`cz9IpJP#5Lqy?K+1FZ{-mc2N+R~wrS7l#mAy0v0QO=(m zj)>tN%XD$Mt{nJ)IeRSUEJFZ{e_6F~K@>}Zu*U14YXEctiUV7WE*Cv6*y9J(?^U1% zIFYPZf`_Hur5$dPx;AaJF%?GFA%42Ug>)597dLcyEiftn$ZuTg+vV-|nju;Fmw4GNWxaAV`kM>LNh=gn|!|tvUaT zIXKu#RDo*MzQdS@H3ZF+3~=!Fe+y(v`Me<-bsC+GAG44VPm+~De40gG#g$ZFX4`vp zIDZAFfbEvj(ybP5Q~Vv;%S4sUx?~oaC|4xD>O#(jx@M`WtE#ADs=7kXwJFU|%r@31 z<~i`@kgQjWzc^FO)M2z|BpE^+0`Y*?7WZl796(TS0Ab2Kl+y%y#%>)rsgr_vXIAfN z-l=9^SM9ugC}GhpX9b)oJDHZ*GAw#C(I3iTAbt-MKB!|Gi2&^h2<)a-pU%rgCbTc!Yh2Th|t^QIm9*q%PL9+d znkG@evGMwI^Jso|$XeE|y0?vHfkuDWmBzu3pat5|4!h8lDXzko6bs6D*H%O3DkX7D z?>64#+q*=K{v{DI-RjQz9(}9JPN?*p5YR5(yL69g-mU)6`ko^+y4C+t--D7a$KW;k z#-a!PEql^!dYqm^U~g}alG9H~fZPmdn7WZ7fmyi+1h3%~i`hNZ<*zhC&@m9R)fF`% zyhg+YpsX68#-ueXodPtSek35W7Z7=-E^tJitqV3AX1^N?=#gkMbW1%bTT0Y#_$p`N z*8QfbtApNyo1DC?BkBXJp7cy=gV@Jv>ojZYoVzwc{-fFqkx^e~_u$KKMPNjNN?RnH zTXOWR(|TTEZAi&4r?9Gm&wQ#nNQl@}^#xKyBFHe{rfjFjRP|ep5*D>7|8%27p@CD? z#~LMyAe^c`)F=@>BQ&iSb$)oKbA+dtr);$FD`p=P4dkwfOn(oFYGdQJ47ELOo1Vnf z>MW&J*JGImFj}x7sHz_yF@=>#-$kg;W2jKL7GxW8KHasM+-wLMmx3lR6y)autCnfU z^HzR$epelIl%=R4CN5}a{~taIWTyu2b8}7o`Gaq$sGEv*V(tvyXd=B(XPHH>L!ai& zZ17!EkgyC0+M%!LHq&$uaiLyX1LfcoX2)~8RO}?M2(O6>3Z{^y;%c!;T23Y=EDJ;E zeXz)j80vJY*=woD3S6@7QPv@*l)?Mef1?g+ogQK95fPu6L4$><*;fs^3xhVfG4n2) zFYsOVAT57ag8ow8W1_KRP_HSra7Q{;Dd1O1YrCdc(hoY@HO1BZg1Aabds(pSxp)aB z2Oj&p-oDOWTjI6rDZF}j_4wa?dLOSr9D`4(!mR=T)h}_{<45HErBmEoDv;<&5FQ!_ z`#n_J2zELZ0aa$b_w?EYE|mQ4spv*IHoa%m3X>N*gdpvF@VJ?un&i=6Bwq-zLfi~K z63VF&$H0)uY)9*Qrb>Qx*(}UkuW6!5pwz#z`=E;1tv+ful}NQc|#o4S||WCz(~a})dBxIE0IvJx(-0rzDAQ)TGRL-Lf@_qiLk8!S02LGgsN`CN;+Zfu!Jrs? z?9C9OKNCoS0n(qOMbm@-tS4YSjib&|;+q%AONXZ3VCSs*`Pmm|c`27^wLa>S)sa7A z2;9k;f=$7k*%|z$#w|pP02F9?vteCV>MkQ1JQjLp{bbI?SBAYjJ@_O4^eQMs2h0gj z697QxQ{%SB)BsRD^%r!Zx;DIeYCnuw-6z$j-lqwJujpHdq78l9GSN3}$`ZsUS;6UJ zZEjc-ONy;M0wI++6Az0)R8tvR8Thd?URtwvgRwv0B2fq%f!Ie4rffXGR+=>1b^ZFahWd`Y1Rw_K7T_Mp7aivG`OS=$L?{zlQ`$t1#gwYs|TMG z(GKZwD3k6ICP!B3^4n^4>1SGFU@gL1#RN}lsMSM%^yt2fjNYa>6}y;^#zU~f@NrxU zI*k#AxankyOXhLfb)?%YOI5MpB=ko}st(`958Q-W>Q!Z-C5w&k#AXB3PehYGM21JC zsZoHFfM=bGyhruwHqV_oh0?flvAuiw1lr5KY3X<@m|1%z?}Lnb2;2)9u`^PL8xRP3 z4E0Au)F}AthTeAuy)UUTQoXeI-K>3wAsDUoKE&KWdLJYqJ5Z`0MH?h{Y&13tZ1)P( zU+r~j(SgCAN={a}g7u1B%8zt?h{qA&UxB7gjVR&v1s9RPv<%>7ZUclSr9obaVy@Ws zT8mubE@+W2vHnb>7hY#q(hz@~$4?^KOp(OLer@p+lmtccdVB3UUX$b^lR?lVt?{Tz zGE~|?!**3tmsbYuVaZ8B;>q@4npTli$rh)>M`86-_3i}L;qpNJjDPS2ll$iChV-;9 z6K5B0+*=e|5n=k5y^FCv#syd*GWrLwJ}#!`N51|jbo38xH~no<6j*#O5RYnq-vkGV z8=T4{0P;x=GRr9@Qg9CHN5%lJ@DK<14Rn-7yL#Pi)j+PbcD47ms??Qyu#yPD`D(JE zHBGT(!%|lIyjgM+1PhbAW{)H|bXK5kpj#En-|7P>iG8!VKpvEGRd*kaRnb!%Jf;LM&; z+ZjRvrumP_thL_`-$f2V=B|9VwJ^yZysPV!Px@^u}vADB^Sbmoqgx%TZ@&XDEHUVO!^JpCer?ne@AgP!jDDEDQ<(o*V`q(~fUDRmTl5Ap_%L1VEjJw9ONyV~ z4SeJ|KtK&OcZch>W(p>mv{ul`82HPI4voW4xjOx47Wvav_U|9fzG}Fxgl>NNsJyEzEeR|lnnkr3n+~-tiY8*y zsf-rAtZvm*h(LQM@L?>7Xf%@2$jTB`wMAV^tWJ=|=Q@6RIQ5KY5+DSG_2vBSLc@Hy z{x&Swd6sw|2TV7XaQRR3!tHY{0XqlxpOYD^sA}tt75$Eigv_h5(~zvmWLDzrvXFt( zmTQoa;I4p9oxj5o7<|?$ljWief|=407>x-k0!aTEH?45Xiwij zEr(=zO-i&wIYXrjhx5_8=ul9{1C<&XXs_{fln)XLM=YYSZJUq{3r9>Y8K`OKaJV~? zOB8Mlz#Rjh)r;hdLI=?pF9A%r)Q=I+9Mx>%|II++wlfRVu8^m0V)FM zXsbjRZ$1Mm=JcRbZq(0Wx!J2g)<;o@tS>{w2#?b1(Y#Pni<$$>8iH@d1{t~yD2X5` zH<6)<29^GiajqwLmcb-V5U438A zM4fJ6wyQt2f@BD_t8ZFCGH%FlX%QY)k;kV<(#;}znvy5hY2y`CL9et~wUf#~Z8URK z7Mu^KZ92lzgS>+v6FS1Uu+-Lta^s(oVZ)9?Mi$xon1F`BFkKl(L@Gd>>`ap!W(y%_ zLC_#|BJ6kxrVSNyL*GMP&=LYj7UpeYPje>2r1DUopia|fw=@09nc?7G9||4T z>(ZS7Z9H#pu7EP|K|YEm)WYf9nG^NEwFSqA3n2k;{AGuX%P1c1XqB&0SV0h4NI)W@ z_9N2Pm7Uwm^|xE53z_pu2W;pNWm%pJn-Foc7!p^dEc4s^a40LvQn~=g354XH!KYJg zxx;R>tL_f>C9x3M$BdQ~I5@LA7)^Uv_oyx%7S(%y5BIsRTok(?It@)x#zjV|Cc>Y> z@!XzqX~69#|BZbhX@1Z47w2jiYPDTvTEveQJW$E0h|csr(91W+MV1r2+T^osExC-? ztQsj2-d1;pS`x*c#zA3M9j=H8=IInyTkX9rSC`n+X|A@|)eKielk(mySL^IOb#l?3 z_PMhC2)NHJbb|L3!9Q#7X>-7gy{CN#7RpR$h=!y3v~8rY_t;lr!6|IXQP?LUHY2aq zaFZx5{NnR!Zq*1uJ>fm@sqyov!o`Q57 z!cyQ~Ukdfdvtn`J!%wBt7!3#S;z*Y)qlp zS`G%FVP6j!H&V6`R^oaMPN%i3;NY)|V!E0uRE<{>nXb?>FUdZ7VInBWw-DC~ezl8N zp}?z9J9Y?9x(L^(>AKQuTXK!!e!v-mFWa^>TvNQ(okKTMh!)P@GpxIvO)KlTMvnyh zqNR0CUZ`U#d?^wblSUF_!&Rnuh#ZA=WCd;R$UcaUc#C@R+UQVfF$=y9C#O0=`OPZl z_GXGrk;7<_KUpi&lf9GEw(i*hR2uWSk9SYSC}J$DjCD}OkmRZzJb>?AJ)CxzSF1wb z;$=+QI4~(fXchj1+GoR)WG49&x}()WQddcBjo+Z9QJI#Iy%Ws`)HPRjnmlM8#!wNG}hkphxAEspC93xJ77=Xt;&T&kHs)TYIx8c-v{&Bhkmaa zEfB$hBZvCkr=~c|uKhmq@ zAo*`ruR7;4-zPtX#a2zHSV*SqU;e{KDGKZUEMYS@;^%y$yj+De2}1Y&9>OCxqFEnZ zF5g^cNB0kY$=3Svm-W6e*CE`5NWk`8DWTpeVgbYY6CM`)A*}M4FT{)`TY*x+GDYk9VNGOqzqxtBDjD_wOC4IkdaRW3wX3b-tY*@*F1U)MhsqYV?U#=*tW%=g})JPHlL_$6j_uX?VqVX%N9HEZKEk0 zJUuJh_7X2p!XAL-oW(he(;Ld}wxi4Wn<*bLc6G_zGnzJSD6I$Cv5?~_n(}~XG0#+_ zhQcRo!CW0zPmMk81`^<09X-J>#~ubj!lwhS&!iNy|FNiZ@m_fJ5ZkeFyV-&*u*e}=86*tLP+SOz_kF!6EX}3XZ;3z=580`Qpro_My|Bl*cu=_ z`O)N6R3#W5JozS5K8X}UxzTnKLOlxY#bFDI)?(oWgj$9BC+yDRot+~_Bu$mf6|-XP zQi5#O`@3XQyWLxNch>jT+w0{5MKk84htqWaj+SqiL(lq2p(^}Q0&E5HB&h(%PTr6uFyuo(EpR=o>K z(XdIzRi)OE$U&oZq(D_=BN~=kQ#35z7>NmrHb6|id5N_jZE>~=N-HB0_OsE|>)n$7 zrm!FvBQ(Bm5B?PcLuLtX(Nyt88W z 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�E@^ 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

nkyoJ7^W9c3~jN;vc3sOtk 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ψ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

7%jb0-+Ae% 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

V_ga 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

`Dr66(fQTy| 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

6{u>RgkeeoM-jy+Hb*aa*^iCl~@))V-T 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]() { - $method_name(WasmExecutionMethod::Compiled); + fn [<$method_name _compiled_recreate_instance_cow>]() { + $method_name(WasmExecutionMethod::Compiled { + instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::RecreateInstanceCopyOnWrite + }); + } + + #[test] + #[cfg(feature = "wasmtime")] + fn [<$method_name _compiled_recreate_instance_vanilla>]() { + $method_name(WasmExecutionMethod::Compiled { + instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::RecreateInstance + }); + } + + #[test] + #[cfg(feature = "wasmtime")] + fn [<$method_name _compiled_pooling_cow>]() { + $method_name(WasmExecutionMethod::Compiled { + instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::PoolingCopyOnWrite + }); + } + + #[test] + #[cfg(feature = "wasmtime")] + fn [<$method_name _compiled_pooling_vanilla>]() { + $method_name(WasmExecutionMethod::Compiled { + instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::Pooling + }); + } + + #[test] + #[cfg(feature = "wasmtime")] + fn [<$method_name _compiled_legacy_instance_reuse>]() { + $method_name(WasmExecutionMethod::Compiled { + instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::LegacyInstanceReuse + }); } } }; @@ -88,14 +122,82 @@ macro_rules! test_wasm_execution_sandbox { #[test] #[cfg(feature = "wasmtime")] - fn [<$method_name _compiled_host_executor>]() { - $method_name(WasmExecutionMethod::Compiled, "_host"); + fn [<$method_name _compiled_pooling_cow_host_executor>]() { + $method_name(WasmExecutionMethod::Compiled { + instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::PoolingCopyOnWrite + }, "_host"); + } + + #[test] + #[cfg(feature = "wasmtime")] + fn [<$method_name _compiled_pooling_cow_embedded_executor>]() { + $method_name(WasmExecutionMethod::Compiled { + instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::PoolingCopyOnWrite + }, "_embedded"); + } + + #[test] + #[cfg(feature = "wasmtime")] + fn [<$method_name _compiled_pooling_vanilla_host_executor>]() { + $method_name(WasmExecutionMethod::Compiled { + instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::Pooling + }, "_host"); + } + + #[test] + #[cfg(feature = "wasmtime")] + fn [<$method_name _compiled_pooling_vanilla_embedded_executor>]() { + $method_name(WasmExecutionMethod::Compiled { + instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::Pooling + }, "_embedded"); + } + + #[test] + #[cfg(feature = "wasmtime")] + fn [<$method_name _compiled_recreate_instance_cow_host_executor>]() { + $method_name(WasmExecutionMethod::Compiled { + instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::RecreateInstanceCopyOnWrite + }, "_host"); + } + + #[test] + #[cfg(feature = "wasmtime")] + fn [<$method_name _compiled_recreate_instance_cow_embedded_executor>]() { + $method_name(WasmExecutionMethod::Compiled { + instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::RecreateInstanceCopyOnWrite + }, "_embedded"); + } + + #[test] + #[cfg(feature = "wasmtime")] + fn [<$method_name _compiled_recreate_instance_vanilla_host_executor>]() { + $method_name(WasmExecutionMethod::Compiled { + instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::RecreateInstance + }, "_host"); + } + + #[test] + #[cfg(feature = "wasmtime")] + fn [<$method_name _compiled_recreate_instance_vanilla_embedded_executor>]() { + $method_name(WasmExecutionMethod::Compiled { + instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::RecreateInstance + }, "_embedded"); + } + + #[test] + #[cfg(feature = "wasmtime")] + fn [<$method_name _compiled_legacy_instance_reuse_host_executor>]() { + $method_name(WasmExecutionMethod::Compiled { + instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::LegacyInstanceReuse + }, "_host"); } #[test] #[cfg(feature = "wasmtime")] - fn [<$method_name _compiled_embedded_executor>]() { - $method_name(WasmExecutionMethod::Compiled, "_embedded"); + fn [<$method_name _compiled_legacy_instance_reuse_embedded_executor>]() { + $method_name(WasmExecutionMethod::Compiled { + instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::LegacyInstanceReuse + }, "_embedded"); } } }; @@ -153,7 +255,7 @@ fn call_not_existing_function(wasm_method: WasmExecutionMethod) { let expected = match wasm_method { WasmExecutionMethod::Interpreted => "Trap: Host(Other(\"Function `missing_external` is only a stub. Calling a stub is not allowed.\"))", #[cfg(feature = "wasmtime")] - WasmExecutionMethod::Compiled => "call to a missing function env:missing_external" + WasmExecutionMethod::Compiled { .. } => "call to a missing function env:missing_external" }; assert_eq!(error.message, expected); }, @@ -173,7 +275,7 @@ fn call_yet_another_not_existing_function(wasm_method: WasmExecutionMethod) { let expected = match wasm_method { WasmExecutionMethod::Interpreted => "Trap: Host(Other(\"Function `yet_another_missing_external` is only a stub. Calling a stub is not allowed.\"))", #[cfg(feature = "wasmtime")] - WasmExecutionMethod::Compiled => "call to a missing function env:yet_another_missing_external" + WasmExecutionMethod::Compiled { .. } => "call to a missing function env:yet_another_missing_external" }; assert_eq!(error.message, expected); }, @@ -473,7 +575,9 @@ fn should_trap_when_heap_exhausted(wasm_method: WasmExecutionMethod) { match err { #[cfg(feature = "wasmtime")] - Error::AbortedDueToTrap(error) if wasm_method == WasmExecutionMethod::Compiled => { + Error::AbortedDueToTrap(error) + if matches!(wasm_method, WasmExecutionMethod::Compiled { .. }) => + { assert_eq!( error.message, r#"host code panicked while being called by the runtime: Failed to allocate memory: "Allocator ran out of space""# @@ -807,7 +911,7 @@ fn unreachable_intrinsic(wasm_method: WasmExecutionMethod) { let expected = match wasm_method { WasmExecutionMethod::Interpreted => "Trap: Unreachable", #[cfg(feature = "wasmtime")] - WasmExecutionMethod::Compiled => "wasm trap: wasm `unreachable` instruction executed", + WasmExecutionMethod::Compiled { .. } => "wasm trap: wasm `unreachable` instruction executed", }; assert_eq!(error.message, expected); }, diff --git a/client/executor/src/lib.rs b/client/executor/src/lib.rs index 5cd04b9e4ee6a..fefb84ede9105 100644 --- a/client/executor/src/lib.rs +++ b/client/executor/src/lib.rs @@ -51,6 +51,9 @@ pub use wasmi; pub use sc_executor_common::{error, sandbox}; +#[cfg(feature = "wasmtime")] +pub use sc_executor_wasmtime::InstantiationStrategy as WasmtimeInstantiationStrategy; + /// Extracts the runtime version of a given runtime code. pub trait RuntimeVersionOf { /// Extract [`RuntimeVersion`](sp_version::RuntimeVersion) of the given `runtime_code`. diff --git a/client/executor/src/wasm_runtime.rs b/client/executor/src/wasm_runtime.rs index 85c33ecacf8ff..1dee739c50f9e 100644 --- a/client/executor/src/wasm_runtime.rs +++ b/client/executor/src/wasm_runtime.rs @@ -46,7 +46,10 @@ pub enum WasmExecutionMethod { Interpreted, /// Uses the Wasmtime compiled runtime. #[cfg(feature = "wasmtime")] - Compiled, + Compiled { + /// The instantiation strategy to use. + instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy, + }, } impl Default for WasmExecutionMethod { @@ -71,6 +74,9 @@ struct VersionedRuntime { module: Arc, /// Runtime version according to `Core_version` if any. version: Option, + + // TODO: Remove this once the legacy instance reuse instantiation strategy + // for `wasmtime` is gone, as this only makes sense with that particular strategy. /// Cached instance pool. instances: Arc>>>>, } @@ -310,22 +316,23 @@ where .map(|runtime| -> Arc { Arc::new(runtime) }) }, #[cfg(feature = "wasmtime")] - WasmExecutionMethod::Compiled => sc_executor_wasmtime::create_runtime::( - blob, - sc_executor_wasmtime::Config { - max_memory_size: None, - allow_missing_func_imports, - cache_path: cache_path.map(ToOwned::to_owned), - semantics: sc_executor_wasmtime::Semantics { - extra_heap_pages: heap_pages, - fast_instance_reuse: true, - deterministic_stack_limit: None, - canonicalize_nans: false, - parallel_compilation: true, + WasmExecutionMethod::Compiled { instantiation_strategy } => + sc_executor_wasmtime::create_runtime::( + blob, + sc_executor_wasmtime::Config { + allow_missing_func_imports, + cache_path: cache_path.map(ToOwned::to_owned), + semantics: sc_executor_wasmtime::Semantics { + extra_heap_pages: heap_pages, + instantiation_strategy, + deterministic_stack_limit: None, + canonicalize_nans: false, + parallel_compilation: true, + max_memory_size: None, + }, }, - }, - ) - .map(|runtime| -> Arc { Arc::new(runtime) }), + ) + .map(|runtime| -> Arc { Arc::new(runtime) }), } } diff --git a/client/executor/wasmtime/Cargo.toml b/client/executor/wasmtime/Cargo.toml index c514a93be1d88..cbe6e1a0bd101 100644 --- a/client/executor/wasmtime/Cargo.toml +++ b/client/executor/wasmtime/Cargo.toml @@ -23,6 +23,8 @@ wasmtime = { version = "0.35.3", default-features = false, features = [ "cranelift", "jitdump", "parallel-compilation", + "memory-init-cow", + "pooling-allocator", ] } sc-allocator = { version = "4.1.0-dev", path = "../../allocator" } sc-executor-common = { version = "0.10.0-dev", path = "../common" } @@ -34,3 +36,4 @@ sp-wasm-interface = { version = "6.0.0", features = ["wasmtime"], path = "../../ wat = "1.0" sc-runtime-test = { version = "2.0.0", path = "../runtime-test" } sp-io = { version = "6.0.0", path = "../../../primitives/io" } +tempfile = "3.3.0" diff --git a/client/executor/wasmtime/src/lib.rs b/client/executor/wasmtime/src/lib.rs index c54c8305f3e4b..4d0e4c37c947e 100644 --- a/client/executor/wasmtime/src/lib.rs +++ b/client/executor/wasmtime/src/lib.rs @@ -38,5 +38,5 @@ mod tests; pub use runtime::{ create_runtime, create_runtime_from_artifact, prepare_runtime_artifact, Config, - DeterministicStackLimit, Semantics, + DeterministicStackLimit, InstantiationStrategy, Semantics, }; diff --git a/client/executor/wasmtime/src/runtime.rs b/client/executor/wasmtime/src/runtime.rs index fa3b567cc0abc..dbe0b129757b7 100644 --- a/client/executor/wasmtime/src/runtime.rs +++ b/client/executor/wasmtime/src/runtime.rs @@ -80,7 +80,7 @@ impl StoreData { pub(crate) type Store = wasmtime::Store; enum Strategy { - FastInstanceReuse { + LegacyInstanceReuse { instance_wrapper: InstanceWrapper, globals_snapshot: GlobalsSnapshot, data_segments_snapshot: Arc, @@ -136,41 +136,42 @@ struct InstanceSnapshotData { pub struct WasmtimeRuntime { engine: wasmtime::Engine, instance_pre: Arc>, - snapshot_data: Option, + instantiation_strategy: InternalInstantiationStrategy, config: Config, } impl WasmModule for WasmtimeRuntime { fn new_instance(&self) -> Result> { - let strategy = if let Some(ref snapshot_data) = self.snapshot_data { - let mut instance_wrapper = InstanceWrapper::new( - &self.engine, - &self.instance_pre, - self.config.max_memory_size, - )?; - let heap_base = instance_wrapper.extract_heap_base()?; - - // This function panics if the instance was created from a runtime blob different from - // which the mutable globals were collected. Here, it is easy to see that there is only - // a single runtime blob and thus it's the same that was used for both creating the - // instance and collecting the mutable globals. - let globals_snapshot = GlobalsSnapshot::take( - &snapshot_data.mutable_globals, - &mut InstanceGlobals { instance: &mut instance_wrapper }, - ); + let strategy = match self.instantiation_strategy { + InternalInstantiationStrategy::LegacyInstanceReuse(ref snapshot_data) => { + let mut instance_wrapper = InstanceWrapper::new( + &self.engine, + &self.instance_pre, + self.config.semantics.max_memory_size, + )?; + let heap_base = instance_wrapper.extract_heap_base()?; - Strategy::FastInstanceReuse { - instance_wrapper, - globals_snapshot, - data_segments_snapshot: snapshot_data.data_segments_snapshot.clone(), - heap_base, - } - } else { - Strategy::RecreateInstance(InstanceCreator { + // This function panics if the instance was created from a runtime blob different + // from which the mutable globals were collected. Here, it is easy to see that there + // is only a single runtime blob and thus it's the same that was used for both + // creating the instance and collecting the mutable globals. + let globals_snapshot = GlobalsSnapshot::take( + &snapshot_data.mutable_globals, + &mut InstanceGlobals { instance: &mut instance_wrapper }, + ); + + Strategy::LegacyInstanceReuse { + instance_wrapper, + globals_snapshot, + data_segments_snapshot: snapshot_data.data_segments_snapshot.clone(), + heap_base, + } + }, + InternalInstantiationStrategy::Builtin => Strategy::RecreateInstance(InstanceCreator { engine: self.engine.clone(), instance_pre: self.instance_pre.clone(), - max_memory_size: self.config.max_memory_size, - }) + max_memory_size: self.config.semantics.max_memory_size, + }), }; Ok(Box::new(WasmtimeInstance { strategy })) @@ -186,7 +187,7 @@ pub struct WasmtimeInstance { impl WasmInstance for WasmtimeInstance { fn call(&mut self, method: InvokeMethod, data: &[u8]) -> Result> { match &mut self.strategy { - Strategy::FastInstanceReuse { + Strategy::LegacyInstanceReuse { ref mut instance_wrapper, globals_snapshot, data_segments_snapshot, @@ -225,7 +226,7 @@ impl WasmInstance for WasmtimeInstance { fn get_global_const(&mut self, name: &str) -> Result> { match &mut self.strategy { - Strategy::FastInstanceReuse { instance_wrapper, .. } => + Strategy::LegacyInstanceReuse { instance_wrapper, .. } => instance_wrapper.get_global_val(name), Strategy::RecreateInstance(ref mut instance_creator) => instance_creator.instantiate()?.get_global_val(name), @@ -239,7 +240,7 @@ impl WasmInstance for WasmtimeInstance { // associated with it. None }, - Strategy::FastInstanceReuse { instance_wrapper, .. } => + Strategy::LegacyInstanceReuse { instance_wrapper, .. } => Some(instance_wrapper.base_ptr()), } } @@ -326,6 +327,48 @@ fn common_config(semantics: &Semantics) -> std::result::Result (true, true), + InstantiationStrategy::Pooling => (true, false), + InstantiationStrategy::RecreateInstanceCopyOnWrite => (false, true), + InstantiationStrategy::RecreateInstance => (false, false), + InstantiationStrategy::LegacyInstanceReuse => (false, false), + }; + + config.memory_init_cow(use_cow); + config.memory_guaranteed_dense_image_size( + semantics.max_memory_size.map(|max| max as u64).unwrap_or(u64::MAX), + ); + + if use_pooling { + config.allocation_strategy(wasmtime::InstanceAllocationStrategy::Pooling { + strategy: wasmtime::PoolingAllocationStrategy::ReuseAffinity, + + // Pooling needs a bunch of hard limits to be set; if we go over + // any of these then the instantiation will fail. + instance_limits: wasmtime::InstanceLimits { + // Current minimum values for kusama (as of 2022-04-14): + // size: 32384 + // table_elements: 1249 + // memory_pages: 2070 + size: 64 * 1024, + table_elements: 2048, + memory_pages: 4096, + + // We can only have a single of those. + tables: 1, + memories: 1, + + // This determines how many instances of the module can be + // instantiated in parallel from the same `Module`. + // + // This includes nested instances spawned with `sp_tasks::spawn` + // from *within* the runtime. + count: 32, + }, + }); + } + Ok(config) } @@ -373,18 +416,47 @@ pub struct DeterministicStackLimit { pub native_stack_max: u32, } -pub struct Semantics { - /// Enabling this will lead to some optimization shenanigans that make calling [`WasmInstance`] - /// extremely fast. - /// - /// Primarily this is achieved by not recreating the instance for each call and performing a - /// bare minimum clean up: reapplying the data segments and restoring the values for global - /// variables. +/// The instantiation strategy to use for the WASM executor. +/// +/// All of the CoW strategies (with `CopyOnWrite` suffix) are only supported when either: +/// a) we're running on Linux, +/// b) we're running on an Unix-like system and we're precompiling +/// our module beforehand. +/// +/// If the CoW variant of a strategy is unsupported the executor will +/// fall back to the non-CoW equivalent. +#[non_exhaustive] +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub enum InstantiationStrategy { + /// Pool the instances to avoid initializing everything from scratch + /// on each instantiation. Use copy-on-write memory when possible. /// - /// Since this feature depends on instrumentation, it can be set only if runtime is - /// instantiated using the runtime blob, e.g. using [`create_runtime`]. - // I.e. if [`CodeSupplyMode::Verbatim`] is used. - pub fast_instance_reuse: bool, + /// This is the fastest instantiation strategy. + PoolingCopyOnWrite, + + /// Recreate the instance from scratch on every instantiation. + /// Use copy-on-write memory when possible. + RecreateInstanceCopyOnWrite, + + /// Pool the instances to avoid initializing everything from scratch + /// on each instantiation. + Pooling, + + /// Recreate the instance from scratch on every instantiation. Very slow. + RecreateInstance, + + /// Legacy instance reuse mechanism. DEPRECATED. Will be removed. Do not use. + LegacyInstanceReuse, +} + +enum InternalInstantiationStrategy { + LegacyInstanceReuse(InstanceSnapshotData), + Builtin, +} + +pub struct Semantics { + /// The instantiation strategy to use. + pub instantiation_strategy: InstantiationStrategy, /// Specifying `Some` will enable deterministic stack height. That is, all executor /// invocations will reach stack overflow at the exactly same point across different wasmtime @@ -418,9 +490,7 @@ pub struct Semantics { /// The number of extra WASM pages which will be allocated /// on top of what is requested by the WASM blob itself. pub extra_heap_pages: u64, -} -pub struct Config { /// The total amount of memory in bytes an instance can request. /// /// If specified, the runtime will be able to allocate only that much of wasm memory. @@ -436,7 +506,9 @@ pub struct Config { /// /// The default is `None`. pub max_memory_size: Option, +} +pub struct Config { /// The WebAssembly standard requires all imports of an instantiated module to be resolved, /// otherwise, the instantiation fails. If this option is set to `true`, then this behavior is /// overriden and imports that are requested by the module and not provided by the host @@ -452,24 +524,16 @@ pub struct Config { enum CodeSupplyMode<'a> { /// The runtime is instantiated using the given runtime blob. - Verbatim { - // Rationale to take the `RuntimeBlob` here is so that the client will be able to reuse - // the blob e.g. if they did a prevalidation. If they didn't they can pass a `RuntimeBlob` - // instance and it will be used anyway in most cases, because we are going to do at least - // some instrumentations for both anticipated paths: substrate execution and PVF execution. - // - // Should there raise a need in performing no instrumentation and the client doesn't need - // to do any checks, then we can provide a `Cow` like semantics here: if we need the blob - // and the user got `RuntimeBlob` then extract it, or otherwise create it from the given - // bytecode. - blob: RuntimeBlob, - }, + Fresh(RuntimeBlob), - /// The code is supplied in a form of a compiled artifact. + /// The runtime is instantiated using a precompiled module. /// /// This assumes that the code is already prepared for execution and the same `Config` was /// used. - Artifact { compiled_artifact: &'a [u8] }, + /// + /// We use a `Path` here instead of simply passing a byte slice to allow `wasmtime` to + /// map the runtime's linear memory on supported platforms in a copy-on-write fashion. + Precompiled(&'a Path), } /// Create a new `WasmtimeRuntime` given the code. This function performs translation from Wasm to @@ -484,29 +548,34 @@ pub fn create_runtime( where H: HostFunctions, { - // SAFETY: this is safe because it doesn't use `CodeSupplyMode::Artifact`. - unsafe { do_create_runtime::(CodeSupplyMode::Verbatim { blob }, config) } + // SAFETY: this is safe because it doesn't use `CodeSupplyMode::Precompiled`. + unsafe { do_create_runtime::(CodeSupplyMode::Fresh(blob), config) } } -/// The same as [`create_runtime`] but takes a precompiled artifact, which makes this function -/// considerably faster than [`create_runtime`]. +/// The same as [`create_runtime`] but takes a path to a precompiled artifact, +/// which makes this function considerably faster than [`create_runtime`]. /// /// # Safety /// -/// The caller must ensure that the compiled artifact passed here was produced by -/// [`prepare_runtime_artifact`]. Otherwise, there is a risk of arbitrary code execution with all -/// implications. +/// The caller must ensure that the compiled artifact passed here was: +/// 1) produced by [`prepare_runtime_artifact`], +/// 2) written to the disk as a file, +/// 3) was not modified, +/// 4) will not be modified while any runtime using this artifact is alive, or is being +/// instantiated. +/// +/// Failure to adhere to these requirements might lead to crashes and arbitrary code execution. /// -/// It is ok though if the `compiled_artifact` was created by code of another version or with +/// It is ok though if the compiled artifact was created by code of another version or with /// different configuration flags. In such case the caller will receive an `Err` deterministically. pub unsafe fn create_runtime_from_artifact( - compiled_artifact: &[u8], + compiled_artifact_path: &Path, config: Config, ) -> std::result::Result where H: HostFunctions, { - do_create_runtime::(CodeSupplyMode::Artifact { compiled_artifact }, config) + do_create_runtime::(CodeSupplyMode::Precompiled(compiled_artifact_path), config) } /// # Safety @@ -520,7 +589,6 @@ unsafe fn do_create_runtime( where H: HostFunctions, { - // Create the engine, store and finally the module from the given code. let mut wasmtime_config = common_config(&config.semantics)?; if let Some(ref cache_path) = config.cache_path { if let Err(reason) = setup_wasmtime_caching(cache_path, &mut wasmtime_config) { @@ -534,45 +602,71 @@ where let engine = Engine::new(&wasmtime_config) .map_err(|e| WasmError::Other(format!("cannot create the wasmtime engine: {}", e)))?; - let (module, snapshot_data) = match code_supply_mode { - CodeSupplyMode::Verbatim { blob } => { + let (module, instantiation_strategy) = match code_supply_mode { + CodeSupplyMode::Fresh(blob) => { let blob = prepare_blob_for_compilation(blob, &config.semantics)?; let serialized_blob = blob.clone().serialize(); let module = wasmtime::Module::new(&engine, &serialized_blob) .map_err(|e| WasmError::Other(format!("cannot create module: {}", e)))?; - if config.semantics.fast_instance_reuse { - let data_segments_snapshot = DataSegmentsSnapshot::take(&blob).map_err(|e| { - WasmError::Other(format!("cannot take data segments snapshot: {}", e)) - })?; - let data_segments_snapshot = Arc::new(data_segments_snapshot); - let mutable_globals = ExposedMutableGlobalsSet::collect(&blob); - - (module, Some(InstanceSnapshotData { data_segments_snapshot, mutable_globals })) - } else { - (module, None) + match config.semantics.instantiation_strategy { + InstantiationStrategy::LegacyInstanceReuse => { + let data_segments_snapshot = + DataSegmentsSnapshot::take(&blob).map_err(|e| { + WasmError::Other(format!("cannot take data segments snapshot: {}", e)) + })?; + let data_segments_snapshot = Arc::new(data_segments_snapshot); + let mutable_globals = ExposedMutableGlobalsSet::collect(&blob); + + ( + module, + InternalInstantiationStrategy::LegacyInstanceReuse(InstanceSnapshotData { + data_segments_snapshot, + mutable_globals, + }), + ) + }, + InstantiationStrategy::Pooling | + InstantiationStrategy::PoolingCopyOnWrite | + InstantiationStrategy::RecreateInstance | + InstantiationStrategy::RecreateInstanceCopyOnWrite => + (module, InternalInstantiationStrategy::Builtin), } }, - CodeSupplyMode::Artifact { compiled_artifact } => { - // SAFETY: The unsafity of `deserialize` is covered by this function. The + CodeSupplyMode::Precompiled(compiled_artifact_path) => { + if let InstantiationStrategy::LegacyInstanceReuse = + config.semantics.instantiation_strategy + { + return Err(WasmError::Other("the legacy instance reuse instantiation strategy is incompatible with precompiled modules".into())); + } + + // SAFETY: The unsafety of `deserialize_file` is covered by this function. The // responsibilities to maintain the invariants are passed to the caller. - let module = wasmtime::Module::deserialize(&engine, compiled_artifact) + // + // See [`create_runtime_from_artifact`] for more details. + let module = wasmtime::Module::deserialize_file(&engine, compiled_artifact_path) .map_err(|e| WasmError::Other(format!("cannot deserialize module: {}", e)))?; - (module, None) + (module, InternalInstantiationStrategy::Builtin) }, }; let mut linker = wasmtime::Linker::new(&engine); crate::imports::prepare_imports::(&mut linker, &module, config.allow_missing_func_imports)?; - let mut store = crate::instance_wrapper::create_store(module.engine(), config.max_memory_size); + let mut store = + crate::instance_wrapper::create_store(module.engine(), config.semantics.max_memory_size); let instance_pre = linker .instantiate_pre(&mut store, &module) .map_err(|e| WasmError::Other(format!("cannot preinstantiate module: {}", e)))?; - Ok(WasmtimeRuntime { engine, instance_pre: Arc::new(instance_pre), snapshot_data, config }) + Ok(WasmtimeRuntime { + engine, + instance_pre: Arc::new(instance_pre), + instantiation_strategy, + config, + }) } fn prepare_blob_for_compilation( @@ -583,16 +677,17 @@ fn prepare_blob_for_compilation( blob = blob.inject_stack_depth_metering(logical_max)?; } - // If enabled, this should happen after all other passes that may introduce global variables. - if semantics.fast_instance_reuse { + if let InstantiationStrategy::LegacyInstanceReuse = semantics.instantiation_strategy { + // When this strategy is used this must be called after all other passes which may introduce + // new global variables, otherwise they will not be reset when we call into the runtime + // again. blob.expose_mutable_globals(); } // We don't actually need the memory to be imported so we can just convert any memory // import into an export with impunity. This simplifies our code since `wasmtime` will - // now automatically take care of creating the memory for us, and it also allows us - // to potentially enable `wasmtime`'s instance pooling at a later date. (Imported - // memories are ineligible for pooling.) + // now automatically take care of creating the memory for us, and it is also necessary + // to enable `wasmtime`'s instance pooling. (Imported memories are ineligible for pooling.) blob.convert_memory_import_into_export()?; blob.add_extra_heap_pages_to_memory_section( semantics diff --git a/client/executor/wasmtime/src/tests.rs b/client/executor/wasmtime/src/tests.rs index d5b92f2f24a76..51875d1eb6b50 100644 --- a/client/executor/wasmtime/src/tests.rs +++ b/client/executor/wasmtime/src/tests.rs @@ -19,18 +19,20 @@ use codec::{Decode as _, Encode as _}; use sc_executor_common::{error::Error, runtime_blob::RuntimeBlob, wasm_runtime::WasmModule}; use sc_runtime_test::wasm_binary_unwrap; -use std::sync::Arc; + +use crate::InstantiationStrategy; type HostFunctions = sp_io::SubstrateHostFunctions; struct RuntimeBuilder { code: Option, - fast_instance_reuse: bool, + instantiation_strategy: InstantiationStrategy, canonicalize_nans: bool, deterministic_stack: bool, extra_heap_pages: u64, max_memory_size: Option, precompile_runtime: bool, + tmpdir: Option, } impl RuntimeBuilder { @@ -39,41 +41,42 @@ impl RuntimeBuilder { fn new_on_demand() -> Self { Self { code: None, - fast_instance_reuse: false, + instantiation_strategy: InstantiationStrategy::RecreateInstance, canonicalize_nans: false, deterministic_stack: false, extra_heap_pages: 1024, max_memory_size: None, precompile_runtime: false, + tmpdir: None, } } - fn use_wat(&mut self, code: String) -> &mut Self { + fn use_wat(mut self, code: String) -> Self { self.code = Some(code); self } - fn canonicalize_nans(&mut self, canonicalize_nans: bool) -> &mut Self { + fn canonicalize_nans(mut self, canonicalize_nans: bool) -> Self { self.canonicalize_nans = canonicalize_nans; self } - fn deterministic_stack(&mut self, deterministic_stack: bool) -> &mut Self { + fn deterministic_stack(mut self, deterministic_stack: bool) -> Self { self.deterministic_stack = deterministic_stack; self } - fn precompile_runtime(&mut self, precompile_runtime: bool) -> &mut Self { + fn precompile_runtime(mut self, precompile_runtime: bool) -> Self { self.precompile_runtime = precompile_runtime; self } - fn max_memory_size(&mut self, max_memory_size: Option) -> &mut Self { + fn max_memory_size(mut self, max_memory_size: Option) -> Self { self.max_memory_size = max_memory_size; self } - fn build(&mut self) -> Arc { + fn build<'a>(&'a mut self) -> impl WasmModule + 'a { let blob = { let wasm: Vec; @@ -90,11 +93,10 @@ impl RuntimeBuilder { }; let config = crate::Config { - max_memory_size: self.max_memory_size, allow_missing_func_imports: true, cache_path: None, semantics: crate::Semantics { - fast_instance_reuse: self.fast_instance_reuse, + instantiation_strategy: self.instantiation_strategy, deterministic_stack_limit: match self.deterministic_stack { true => Some(crate::DeterministicStackLimit { logical_max: 65536, @@ -105,24 +107,31 @@ impl RuntimeBuilder { canonicalize_nans: self.canonicalize_nans, parallel_compilation: true, extra_heap_pages: self.extra_heap_pages, + max_memory_size: self.max_memory_size, }, }; - let rt = if self.precompile_runtime { + if self.precompile_runtime { + let dir = tempfile::tempdir().unwrap(); + let path = dir.path().join("runtime.bin"); + + // Delay the removal of the temporary directory until we're dropped. + self.tmpdir = Some(dir); + let artifact = crate::prepare_runtime_artifact(blob, &config.semantics).unwrap(); - unsafe { crate::create_runtime_from_artifact::(&artifact, config) } + std::fs::write(&path, artifact).unwrap(); + unsafe { crate::create_runtime_from_artifact::(&path, config) } } else { crate::create_runtime::(blob, config) } - .expect("cannot create runtime"); - - Arc::new(rt) as Arc + .expect("cannot create runtime") } } #[test] fn test_nan_canonicalization() { - let runtime = RuntimeBuilder::new_on_demand().canonicalize_nans(true).build(); + let mut builder = RuntimeBuilder::new_on_demand().canonicalize_nans(true); + let runtime = builder.build(); let mut instance = runtime.new_instance().expect("failed to instantiate a runtime"); @@ -161,10 +170,11 @@ fn test_nan_canonicalization() { fn test_stack_depth_reaching() { const TEST_GUARD_PAGE_SKIP: &str = include_str!("test-guard-page-skip.wat"); - let runtime = RuntimeBuilder::new_on_demand() + let mut builder = RuntimeBuilder::new_on_demand() .use_wat(TEST_GUARD_PAGE_SKIP.to_string()) - .deterministic_stack(true) - .build(); + .deterministic_stack(true); + + let runtime = builder.build(); let mut instance = runtime.new_instance().expect("failed to instantiate a runtime"); match instance.call_export("test-many-locals", &[]).unwrap_err() { @@ -202,11 +212,12 @@ fn test_max_memory_pages(import_memory: bool, precompile_runtime: bool) { wat: String, precompile_runtime: bool, ) -> Result<(), Box> { - let runtime = RuntimeBuilder::new_on_demand() + let mut builder = RuntimeBuilder::new_on_demand() .use_wat(wat) .max_memory_size(max_memory_size) - .precompile_runtime(precompile_runtime) - .build(); + .precompile_runtime(precompile_runtime); + + let runtime = builder.build(); let mut instance = runtime.new_instance()?; let _ = instance.call_export("main", &[])?; Ok(()) @@ -377,15 +388,15 @@ fn test_instances_without_reuse_are_not_leaked() { let runtime = crate::create_runtime::( RuntimeBlob::uncompress_if_needed(wasm_binary_unwrap()).unwrap(), crate::Config { - max_memory_size: None, allow_missing_func_imports: true, cache_path: None, semantics: crate::Semantics { - fast_instance_reuse: false, + instantiation_strategy: InstantiationStrategy::RecreateInstance, deterministic_stack_limit: None, canonicalize_nans: false, parallel_compilation: true, extra_heap_pages: 2048, + max_memory_size: None, }, }, ) diff --git a/client/service/src/config.rs b/client/service/src/config.rs index 35380da11fc71..c895300fea4d1 100644 --- a/client/service/src/config.rs +++ b/client/service/src/config.rs @@ -21,6 +21,8 @@ pub use sc_client_api::execution_extensions::{ExecutionStrategies, ExecutionStrategy}; pub use sc_client_db::{Database, DatabaseSource, KeepBlocks, PruningMode}; pub use sc_executor::WasmExecutionMethod; +#[cfg(feature = "wasmtime")] +pub use sc_executor::WasmtimeInstantiationStrategy; pub use sc_network::{ config::{ MultiaddrWithPeerId, NetworkConfiguration, NodeKeyConfig, NonDefaultSetConfig, Role, diff --git a/utils/frame/benchmarking-cli/src/pallet/command.rs b/utils/frame/benchmarking-cli/src/pallet/command.rs index 660f31b8f1529..aa95223619b45 100644 --- a/utils/frame/benchmarking-cli/src/pallet/command.rs +++ b/utils/frame/benchmarking-cli/src/pallet/command.rs @@ -23,7 +23,9 @@ use frame_benchmarking::{ }; use frame_support::traits::StorageInfo; use linked_hash_map::LinkedHashMap; -use sc_cli::{CliConfiguration, ExecutionStrategy, Result, SharedParams}; +use sc_cli::{ + execution_method_from_cli, CliConfiguration, ExecutionStrategy, Result, SharedParams, +}; use sc_client_db::BenchmarkingState; use sc_executor::NativeElseWasmExecutor; use sc_service::{Configuration, NativeExecutionDispatch}; @@ -121,7 +123,6 @@ impl PalletCmd { } let spec = config.chain_spec; - let wasm_method = self.wasm_method.into(); let strategy = self.execution.unwrap_or(ExecutionStrategy::Native); let pallet = self.pallet.clone().unwrap_or_default(); let pallet = pallet.as_bytes(); @@ -141,7 +142,7 @@ impl PalletCmd { let state_without_tracking = BenchmarkingState::::new(genesis_storage, cache_size, self.record_proof, false)?; let executor = NativeElseWasmExecutor::::new( - wasm_method, + execution_method_from_cli(self.wasm_method, self.wasmtime_instantiation_strategy), self.heap_pages, 2, // The runtime instances cache size. 2, // The runtime cache size diff --git a/utils/frame/benchmarking-cli/src/pallet/mod.rs b/utils/frame/benchmarking-cli/src/pallet/mod.rs index 48ddcc7ce8eec..227c9b2f8a7b6 100644 --- a/utils/frame/benchmarking-cli/src/pallet/mod.rs +++ b/utils/frame/benchmarking-cli/src/pallet/mod.rs @@ -18,7 +18,10 @@ mod command; mod writer; -use sc_cli::{ExecutionStrategy, WasmExecutionMethod, DEFAULT_WASM_EXECUTION_METHOD}; +use sc_cli::{ + ExecutionStrategy, WasmExecutionMethod, WasmtimeInstantiationStrategy, + DEFAULT_WASMTIME_INSTANTIATION_STRATEGY, DEFAULT_WASM_EXECUTION_METHOD, +}; use std::{fmt::Debug, path::PathBuf}; // Add a more relaxed parsing for pallet names by allowing pallet directory names with `-` to be @@ -131,6 +134,17 @@ pub struct PalletCmd { )] pub wasm_method: WasmExecutionMethod, + /// The WASM instantiation method to use. + /// + /// Only has an effect when `wasm-execution` is set to `compiled`. + #[clap( + long = "wasm-instantiation-strategy", + value_name = "STRATEGY", + default_value_t = DEFAULT_WASMTIME_INSTANTIATION_STRATEGY, + arg_enum, + )] + pub wasmtime_instantiation_strategy: WasmtimeInstantiationStrategy, + /// Limit the memory the database cache can use. #[clap(long = "db-cache", value_name = "MiB", default_value = "1024")] pub database_cache_size: u32, diff --git a/utils/frame/try-runtime/cli/src/lib.rs b/utils/frame/try-runtime/cli/src/lib.rs index c13bbb3626176..71d258a68982e 100644 --- a/utils/frame/try-runtime/cli/src/lib.rs +++ b/utils/frame/try-runtime/cli/src/lib.rs @@ -271,7 +271,9 @@ use remote_externalities::{ }; use sc_chain_spec::ChainSpec; use sc_cli::{ - CliConfiguration, ExecutionStrategy, WasmExecutionMethod, DEFAULT_WASM_EXECUTION_METHOD, + execution_method_from_cli, CliConfiguration, ExecutionStrategy, WasmExecutionMethod, + WasmtimeInstantiationStrategy, DEFAULT_WASMTIME_INSTANTIATION_STRATEGY, + DEFAULT_WASM_EXECUTION_METHOD, }; use sc_executor::NativeElseWasmExecutor; use sc_service::{Configuration, NativeExecutionDispatch}; @@ -400,6 +402,17 @@ pub struct SharedParams { )] pub wasm_method: WasmExecutionMethod, + /// The WASM instantiation method to use. + /// + /// Only has an effect when `wasm-execution` is set to `compiled`. + #[clap( + long = "wasm-instantiation-strategy", + value_name = "STRATEGY", + default_value_t = DEFAULT_WASMTIME_INSTANTIATION_STRATEGY, + arg_enum, + )] + pub wasmtime_instantiation_strategy: WasmtimeInstantiationStrategy, + /// The number of 64KB pages to allocate for Wasm execution. Defaults to /// [`sc_service::Configuration.default_heap_pages`]. #[clap(long)] @@ -675,13 +688,12 @@ pub(crate) fn build_executor( shared: &SharedParams, config: &sc_service::Configuration, ) -> NativeElseWasmExecutor { - let wasm_method = shared.wasm_method; let heap_pages = shared.heap_pages.or(config.default_heap_pages); let max_runtime_instances = config.max_runtime_instances; let runtime_cache_size = config.runtime_cache_size; NativeElseWasmExecutor::::new( - wasm_method.into(), + execution_method_from_cli(shared.wasm_method, shared.wasmtime_instantiation_strategy), heap_pages, max_runtime_instances, runtime_cache_size, From 258e2a327e64c3811875eeb9f2dfd497b358fe6f Mon Sep 17 00:00:00 2001 From: kostekIV <27210860+kostekIV@users.noreply.github.com> Date: Thu, 19 May 2022 11:10:49 +0200 Subject: [PATCH 247/484] Make fields of `EraRewardPoints` public (#11422) --- frame/staking/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index f17d09b413606..331095774b741 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -369,9 +369,9 @@ pub struct ActiveEraInfo { #[derive(PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct EraRewardPoints { /// Total number of points. Equals the sum of reward points for each validator. - total: RewardPoint, + pub total: RewardPoint, /// The reward points earned by a given validator. - individual: BTreeMap, + pub individual: BTreeMap, } impl Default for EraRewardPoints { From 794b6baf4f4ca7d4e3cc75c37c95add5f8fcc335 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Thu, 19 May 2022 12:54:40 +0100 Subject: [PATCH 248/484] add missing events to elections fallback (#11436) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add missing events to elections fallback * Update frame/election-provider-multi-phase/src/lib.rs Co-authored-by: Bastian Köcher * Update frame/election-provider-multi-phase/src/lib.rs Co-authored-by: Bastian Köcher * add test * fix * fmt * Update frame/support/src/storage/types/nmap.rs Co-authored-by: Bastian Köcher --- .../election-provider-multi-phase/src/lib.rs | 56 ++++++++++++++++++- .../election-provider-multi-phase/src/mock.rs | 6 +- 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 36d2373c9f623..7d8559050f300 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -948,6 +948,11 @@ pub mod pallet { compute: ElectionCompute::Emergency, }; + Self::deposit_event(Event::SolutionStored { + election_compute: ElectionCompute::Emergency, + prev_ejected: QueuedSolution::::exists(), + }); + >::put(solution); Ok(()) } @@ -1057,6 +1062,11 @@ pub mod pallet { compute: ElectionCompute::Fallback, }; + Self::deposit_event(Event::SolutionStored { + election_compute: ElectionCompute::Fallback, + prev_ejected: QueuedSolution::::exists(), + }); + >::put(solution); Ok(()) } @@ -1792,7 +1802,7 @@ mod tests { use crate::{ mock::{ multi_phase_events, roll_to, AccountId, ExtBuilder, MockWeightInfo, MockedWeightInfo, - MultiPhase, Runtime, SignedMaxSubmissions, System, TargetIndex, Targets, + MultiPhase, Origin, Runtime, SignedMaxSubmissions, System, TargetIndex, Targets, }, Phase, }; @@ -2038,6 +2048,50 @@ mod tests { }) } + #[test] + fn governance_fallback_works() { + ExtBuilder::default().onchain_fallback(false).build_and_execute(|| { + roll_to(25); + assert_eq!(MultiPhase::current_phase(), Phase::Unsigned((true, 25))); + + // Zilch solutions thus far. + assert!(MultiPhase::queued_solution().is_none()); + assert_eq!(MultiPhase::elect().unwrap_err(), ElectionError::Fallback("NoFallback.")); + + // phase is now emergency. + assert_eq!(MultiPhase::current_phase(), Phase::Emergency); + assert!(MultiPhase::queued_solution().is_none()); + + // no single account can trigger this + assert_noop!( + MultiPhase::governance_fallback(Origin::signed(99), None, None), + DispatchError::BadOrigin + ); + + // only root can + assert_ok!(MultiPhase::governance_fallback(Origin::root(), None, None)); + // something is queued now + assert!(MultiPhase::queued_solution().is_some()); + // next election call with fix everything.; + assert!(MultiPhase::elect().is_ok()); + assert_eq!(MultiPhase::current_phase(), Phase::Off); + + assert_eq!( + multi_phase_events(), + vec![ + Event::SignedPhaseStarted { round: 1 }, + Event::UnsignedPhaseStarted { round: 1 }, + Event::ElectionFinalized { election_compute: None }, + Event::SolutionStored { + election_compute: ElectionCompute::Fallback, + prev_ejected: false + }, + Event::ElectionFinalized { election_compute: Some(ElectionCompute::Fallback) } + ] + ); + }) + } + #[test] fn snapshot_too_big_failure_onchain_fallback() { // the `MockStaking` is designed such that if it has too many targets, it simply fails. diff --git a/frame/election-provider-multi-phase/src/mock.rs b/frame/election-provider-multi-phase/src/mock.rs index afce24ff6e3f0..bbc2d6d43beee 100644 --- a/frame/election-provider-multi-phase/src/mock.rs +++ b/frame/election-provider-multi-phase/src/mock.rs @@ -18,7 +18,9 @@ use super::*; use crate::{self as multi_phase, unsigned::MinerConfig}; use frame_election_provider_support::{ - data_provider, onchain, ElectionDataProvider, NposSolution, SequentialPhragmen, + data_provider, + onchain::{self, UnboundedExecution}, + ElectionDataProvider, NposSolution, SequentialPhragmen, }; pub use frame_support::{assert_noop, assert_ok, pallet_prelude::GetDefault}; use frame_support::{ @@ -379,7 +381,7 @@ impl crate::Config for Runtime { type WeightInfo = (); type BenchmarkingConfig = TestBenchmarkingConfig; type Fallback = MockFallback; - type GovernanceFallback = NoFallback; + type GovernanceFallback = UnboundedExecution; type ForceOrigin = frame_system::EnsureRoot; type MaxElectingVoters = MaxElectingVoters; type MaxElectableTargets = MaxElectableTargets; From 78d15835ed3ae83e881cf5578bed59b4ef894641 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Thu, 19 May 2022 15:39:43 +0200 Subject: [PATCH 249/484] Fix renaming artifacts (#11455) --- bin/node/runtime/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index de6f8eeb9f1c0..c1e6dad7bf0d4 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1410,8 +1410,8 @@ impl pallet_gilt::Config for Runtime { } parameter_types! { - pub const ClassDeposit: Balance = 100 * DOLLARS; - pub const InstanceDeposit: Balance = 1 * DOLLARS; + pub const CollectionDeposit: Balance = 100 * DOLLARS; + pub const ItemDeposit: Balance = 1 * DOLLARS; pub const KeyLimit: u32 = 32; pub const ValueLimit: u32 = 256; } @@ -1422,8 +1422,8 @@ impl pallet_uniques::Config for Runtime { type ItemId = u32; type Currency = Balances; type ForceOrigin = frame_system::EnsureRoot; - type CollectionDeposit = ClassDeposit; - type ItemDeposit = InstanceDeposit; + type CollectionDeposit = CollectionDeposit; + type ItemDeposit = ItemDeposit; type MetadataDepositBase = MetadataDepositBase; type AttributeDepositBase = MetadataDepositBase; type DepositPerByte = MetadataDepositPerByte; From 716756ce51245b9f8ee8706e4c438ea37fb29ac4 Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Thu, 19 May 2022 12:40:31 -0400 Subject: [PATCH 250/484] Add Score to Bags List (#11357) * Add Score to Bags List * fix ordering * make compile * in progress migration * make migration compile * remove old check * remove runtime specific migration * fix warning * Apply suggestions from code review Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * improve migration * fix * fix merge * fmt * Update migrations.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- bin/node/runtime/src/lib.rs | 2 +- frame/bags-list/fuzzer/src/main.rs | 11 +- frame/bags-list/src/lib.rs | 40 +++++++- frame/bags-list/src/list/mod.rs | 69 +++++++------ frame/bags-list/src/list/tests.rs | 111 ++++++++++++--------- frame/bags-list/src/migrations.rs | 105 +++++++++++++++++-- frame/bags-list/src/tests.rs | 21 ++-- frame/election-provider-support/src/lib.rs | 34 +++++-- frame/staking/src/pallet/impls.rs | 5 +- 9 files changed, 291 insertions(+), 107 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index c1e6dad7bf0d4..c98bb5cc13f85 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1572,7 +1572,7 @@ pub type Executive = frame_executive::Executive< frame_system::ChainContext, Runtime, AllPalletsWithSystem, - pallet_bags_list::migrations::CheckCounterPrefix, + (), >; /// MMR helper types. diff --git a/frame/bags-list/fuzzer/src/main.rs b/frame/bags-list/fuzzer/src/main.rs index d0586f0372ac4..c17fbe0a2f77f 100644 --- a/frame/bags-list/fuzzer/src/main.rs +++ b/frame/bags-list/fuzzer/src/main.rs @@ -70,13 +70,20 @@ fn main() { }, Action::Update => { let already_contains = BagsList::contains(&id); - BagsList::on_update(&id, vote_weight).unwrap(); if already_contains { + BagsList::on_update(&id, vote_weight).unwrap(); assert!(BagsList::contains(&id)); + } else { + BagsList::on_update(&id, vote_weight).unwrap_err(); } }, Action::Remove => { - BagsList::on_remove(&id).unwrap(); + let already_contains = BagsList::contains(&id); + if already_contains { + BagsList::on_remove(&id).unwrap(); + } else { + BagsList::on_remove(&id).unwrap_err(); + } assert!(!BagsList::contains(&id)); }, } diff --git a/frame/bags-list/src/lib.rs b/frame/bags-list/src/lib.rs index 77fe609ad8123..7eee8fdfa23d8 100644 --- a/frame/bags-list/src/lib.rs +++ b/frame/bags-list/src/lib.rs @@ -82,7 +82,10 @@ macro_rules! log { ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { log::$level!( target: crate::LOG_TARGET, - concat!("[{:?}] 👜", $patter), >::block_number() $(, $values)* + concat!("[{:?}] 👜 [{}]", $patter), + >::block_number(), + as frame_support::traits::PalletInfoAccess>::name() + $(, $values)* ) }; } @@ -189,6 +192,8 @@ pub mod pallet { pub enum Event, I: 'static = ()> { /// Moved an account from one bag to another. Rebagged { who: T::AccountId, from: T::Score, to: T::Score }, + /// Updated the score of some account to the given amount. + ScoreUpdated { who: T::AccountId, new_score: T::Score }, } #[pallet::error] @@ -212,13 +217,16 @@ pub mod pallet { /// /// Anyone can call this function about any potentially dislocated account. /// - /// Will never return an error; if `dislocated` does not exist or doesn't need a rebag, then - /// it is a noop and fees are still collected from `origin`. + /// Will always update the stored score of `dislocated` to the correct score, based on + /// `ScoreProvider`. + /// + /// If `dislocated` does not exists, it returns an error. #[pallet::weight(T::WeightInfo::rebag_non_terminal().max(T::WeightInfo::rebag_terminal()))] pub fn rebag(origin: OriginFor, dislocated: T::AccountId) -> DispatchResult { ensure_signed(origin)?; let current_score = T::ScoreProvider::score(&dislocated); - let _ = Pallet::::do_rebag(&dislocated, current_score); + let _ = Pallet::::do_rebag(&dislocated, current_score) + .map_err::, _>(Into::into)?; Ok(()) } @@ -265,6 +273,7 @@ impl, I: 'static> Pallet { if let Some((from, to)) = maybe_movement { Self::deposit_event(Event::::Rebagged { who: account.clone(), from, to }); }; + Self::deposit_event(Event::::ScoreUpdated { who: account.clone(), new_score }); Ok(maybe_movement) } @@ -302,6 +311,10 @@ impl, I: 'static> SortedListProvider for Pallet List::::insert(id, score) } + fn get_score(id: &T::AccountId) -> Result { + List::::get_score(id) + } + fn on_update(id: &T::AccountId, new_score: T::Score) -> Result<(), ListError> { Pallet::::do_rebag(id, new_score).map(|_| ()) } @@ -359,3 +372,22 @@ impl, I: 'static> SortedListProvider for Pallet } } } + +impl, I: 'static> ScoreProvider for Pallet { + type Score = as SortedListProvider>::Score; + + fn score(id: &T::AccountId) -> T::Score { + Node::::get(id).map(|node| node.score()).unwrap_or_default() + } + + #[cfg(any(feature = "runtime-benchmarks", test))] + fn set_score_of(id: &T::AccountId, new_score: T::Score) { + ListNodes::::mutate(id, |maybe_node| { + if let Some(node) = maybe_node.as_mut() { + node.set_score(new_score) + } else { + panic!("trying to mutate {:?} which does not exists", id); + } + }) + } +} diff --git a/frame/bags-list/src/list/mod.rs b/frame/bags-list/src/list/mod.rs index 2e65c3be25b24..d95c5de8132ea 100644 --- a/frame/bags-list/src/list/mod.rs +++ b/frame/bags-list/src/list/mod.rs @@ -28,6 +28,7 @@ use crate::Config; use codec::{Decode, Encode, MaxEncodedLen}; use frame_election_provider_support::ScoreProvider; use frame_support::{ + ensure, traits::{Defensive, Get}, DefaultNoBound, PalletError, }; @@ -38,7 +39,7 @@ use sp_std::{ collections::{btree_map::BTreeMap, btree_set::BTreeSet}, iter, marker::PhantomData, - vec::Vec, + prelude::*, }; #[derive(Debug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo, PalletError)] @@ -227,6 +228,11 @@ impl, I: 'static> List { crate::ListNodes::::contains_key(id) } + /// Get the score of the given node, + pub fn get_score(id: &T::AccountId) -> Result { + Node::::get(id).map(|node| node.score()).ok_or(ListError::NodeNotFound) + } + /// Iterate over all nodes in all bags in the list. /// /// Full iteration can be expensive; it's recommended to limit the number of items with @@ -310,7 +316,7 @@ impl, I: 'static> List { let bag_score = notional_bag_for::(score); let mut bag = Bag::::get_or_make(bag_score); // unchecked insertion is okay; we just got the correct `notional_bag_for`. - bag.insert_unchecked(id.clone()); + bag.insert_unchecked(id.clone(), score); // new inserts are always the tail, so we must write the bag. bag.put(); @@ -381,16 +387,18 @@ impl, I: 'static> List { /// If the node was in the correct bag, no effect. If the node was in the incorrect bag, they /// are moved into the correct bag. /// - /// Returns `Some((old_idx, new_idx))` if the node moved, otherwise `None`. + /// Returns `Some((old_idx, new_idx))` if the node moved, otherwise `None`. In both cases, the + /// node's score is written to the `score` field. Thus, this is not a noop, even if `None`. /// /// This operation is somewhat more efficient than simply calling [`self.remove`] followed by /// [`self.insert`]. However, given large quantities of nodes to move, it may be more efficient /// to call [`self.remove_many`] followed by [`self.insert_many`]. pub(crate) fn update_position_for( - node: Node, + mut node: Node, new_score: T::Score, ) -> Option<(T::Score, T::Score)> { - node.is_misplaced(new_score).then(move || { + node.score = new_score; + if node.is_misplaced(new_score) { let old_bag_upper = node.bag_upper; if !node.is_terminal() { @@ -402,12 +410,9 @@ impl, I: 'static> List { bag.remove_node_unchecked(&node); bag.put(); } else { - crate::log!( - error, - "Node {:?} did not have a bag; ListBags is in an inconsistent state", - node.id, + frame_support::defensive!( + "Node did not have a bag; BagsList is in an inconsistent state" ); - debug_assert!(false, "every node must have an extant bag associated with it"); } // put the node into the appropriate new bag. @@ -418,8 +423,12 @@ impl, I: 'static> List { bag.insert_node_unchecked(node); bag.put(); - (old_bag_upper, new_bag_upper) - }) + Some((old_bag_upper, new_bag_upper)) + } else { + // just write the new score. + node.put(); + None + } } /// Put `heavier_id` to the position directly in front of `lighter_id`. Both ids must be in the @@ -428,8 +437,6 @@ impl, I: 'static> List { lighter_id: &T::AccountId, heavier_id: &T::AccountId, ) -> Result<(), ListError> { - use frame_support::ensure; - let lighter_node = Node::::get(&lighter_id).ok_or(ListError::NodeNotFound)?; let heavier_node = Node::::get(&heavier_id).ok_or(ListError::NodeNotFound)?; @@ -510,7 +517,6 @@ impl, I: 'static> List { /// all bags and nodes are checked per *any* update to `List`. #[cfg(feature = "std")] pub(crate) fn sanity_check() -> Result<(), &'static str> { - use frame_support::ensure; let mut seen_in_list = BTreeSet::new(); ensure!( Self::iter().map(|node| node.id).all(|id| seen_in_list.insert(id)), @@ -523,7 +529,7 @@ impl, I: 'static> List { ensure!(iter_count == stored_count, "iter_count != stored_count"); ensure!(stored_count == nodes_count, "stored_count != nodes_count"); - crate::log!(debug, "count of nodes: {}", stored_count); + crate::log!(trace, "count of nodes: {}", stored_count); let active_bags = { let thresholds = T::BagThresholds::get().iter().copied(); @@ -544,7 +550,7 @@ impl, I: 'static> List { active_bags.clone().fold(0u32, |acc, cur| acc + cur.iter().count() as u32); ensure!(nodes_count == nodes_in_bags_count, "stored_count != nodes_in_bags_count"); - crate::log!(debug, "count of active bags {}", active_bags.count()); + crate::log!(trace, "count of active bags {}", active_bags.count()); // check that all nodes are sane. We check the `ListNodes` storage item directly in case we // have some "stale" nodes that are not in a bag. @@ -667,7 +673,7 @@ impl, I: 'static> Bag { /// /// Storage note: this modifies storage, but only for the nodes. You still need to call /// `self.put()` after use. - fn insert_unchecked(&mut self, id: T::AccountId) { + fn insert_unchecked(&mut self, id: T::AccountId, score: T::Score) { // insert_node will overwrite `prev`, `next` and `bag_upper` to the proper values. As long // as this bag is the correct one, we're good. All calls to this must come after getting the // correct [`notional_bag_for`]. @@ -676,6 +682,7 @@ impl, I: 'static> Bag { prev: None, next: None, bag_upper: Zero::zero(), + score, _phantom: PhantomData, }); } @@ -784,11 +791,6 @@ impl, I: 'static> Bag { Ok(()) } - #[cfg(not(feature = "std"))] - fn sanity_check(&self) -> Result<(), &'static str> { - Ok(()) - } - /// Iterate over the nodes in this bag (public for tests). #[cfg(feature = "std")] #[allow(dead_code)] @@ -809,12 +811,13 @@ impl, I: 'static> Bag { #[scale_info(skip_type_params(T, I))] #[cfg_attr(feature = "std", derive(frame_support::DebugNoBound, Clone, PartialEq))] pub struct Node, I: 'static = ()> { - id: T::AccountId, - prev: Option, - next: Option, - bag_upper: T::Score, + pub(crate) id: T::AccountId, + pub(crate) prev: Option, + pub(crate) next: Option, + pub(crate) bag_upper: T::Score, + pub(crate) score: T::Score, #[codec(skip)] - _phantom: PhantomData, + pub(crate) _phantom: PhantomData, } impl, I: 'static> Node { @@ -877,6 +880,11 @@ impl, I: 'static> Node { &self.id } + /// Get the current vote weight of the node. + pub(crate) fn score(&self) -> T::Score { + self.score + } + /// Get the underlying voter (public fo tests). #[cfg(feature = "std")] #[allow(dead_code)] @@ -884,6 +892,11 @@ impl, I: 'static> Node { &self.id } + #[cfg(any(feature = "runtime-benchmarks", test))] + pub fn set_score(&mut self, s: T::Score) { + self.score = s + } + /// The bag this nodes belongs to (public for benchmarks). #[cfg(feature = "runtime-benchmarks")] #[allow(dead_code)] diff --git a/frame/bags-list/src/list/tests.rs b/frame/bags-list/src/list/tests.rs index 40a174b35a5d3..9bdd54289fd88 100644 --- a/frame/bags-list/src/list/tests.rs +++ b/frame/bags-list/src/list/tests.rs @@ -21,20 +21,20 @@ use crate::{ ListBags, ListNodes, }; use frame_election_provider_support::{SortedListProvider, VoteWeight}; -use frame_support::{assert_noop, assert_ok, assert_storage_noop}; +use frame_support::{assert_ok, assert_storage_noop}; + +fn node( + id: AccountId, + prev: Option, + next: Option, + bag_upper: VoteWeight, +) -> Node { + Node:: { id, prev, next, bag_upper, score: bag_upper, _phantom: PhantomData } +} #[test] fn basic_setup_works() { ExtBuilder::default().build_and_execute(|| { - // syntactic sugar to create a raw node - let node = |id, prev, next, bag_upper| Node:: { - id, - prev, - next, - bag_upper, - _phantom: PhantomData, - }; - assert_eq!(ListNodes::::count(), 4); assert_eq!(ListNodes::::iter().count(), 4); assert_eq!(ListBags::::iter().count(), 2); @@ -83,10 +83,10 @@ fn notional_bag_for_works() { let max_explicit_threshold = *::BagThresholds::get().last().unwrap(); assert_eq!(max_explicit_threshold, 10_000); - // if the max explicit threshold is less than T::Value::max_value(), + // if the max explicit threshold is less than T::Score::max_value(), assert!(VoteWeight::MAX > max_explicit_threshold); - // then anything above it will belong to the T::Value::max_value() bag. + // then anything above it will belong to the T::Score::max_value() bag. assert_eq!(notional_bag_for::(max_explicit_threshold), max_explicit_threshold); assert_eq!(notional_bag_for::(max_explicit_threshold + 1), VoteWeight::MAX); } @@ -154,6 +154,8 @@ fn migrate_works() { } mod list { + use frame_support::assert_noop; + use super::*; #[test] @@ -175,7 +177,7 @@ mod list { ] ); - // when adding an id that has a higher weight than pre-existing ids in the bag + // when adding an id that has a higher score than pre-existing ids in the bag assert_ok!(List::::insert(7, 10)); // then @@ -246,10 +248,7 @@ mod list { assert!(get_list_as_ids().contains(&3)); // then - assert_storage_noop!(assert_eq!( - List::::insert(3, 20).unwrap_err(), - ListError::Duplicate - )); + assert_noop!(List::::insert(3, 20), ListError::Duplicate); }); } @@ -317,37 +316,36 @@ mod list { ExtBuilder::default().build_and_execute(|| { // given a correctly placed account 1 at bag 10. let node = Node::::get(&1).unwrap(); + assert_eq!(node.score, 10); assert!(!node.is_misplaced(10)); - // .. it is invalid with weight 20 + // .. it is invalid with score 20 assert!(node.is_misplaced(20)); // move it to bag 20. - assert_eq!(List::::update_position_for(node, 20), Some((10, 20))); + assert_eq!(List::::update_position_for(node.clone(), 20), Some((10, 20))); + assert_eq!(Node::::get(&1).unwrap().score, 20); assert_eq!(List::::get_bags(), vec![(20, vec![1]), (1_000, vec![2, 3, 4])]); - // get the new updated node; try and update the position with no change in weight. + // get the new updated node; try and update the position with no change in score. let node = Node::::get(&1).unwrap(); assert_storage_noop!(assert_eq!( List::::update_position_for(node.clone(), 20), None )); - // then move it to bag 1_000 by giving it weight 500. + // then move it to bag 1_000 by giving it score 500. assert_eq!(List::::update_position_for(node.clone(), 500), Some((20, 1_000))); + assert_eq!(Node::::get(&1).unwrap().score, 500); assert_eq!(List::::get_bags(), vec![(1_000, vec![2, 3, 4, 1])]); // moving within that bag again is a noop let node = Node::::get(&1).unwrap(); - assert_storage_noop!(assert_eq!( - List::::update_position_for(node.clone(), 750), - None, - )); - assert_storage_noop!(assert_eq!( - List::::update_position_for(node, 1_000), - None, - )); + assert_eq!(List::::update_position_for(node.clone(), 750), None); + assert_eq!(Node::::get(&1).unwrap().score, 750); + assert_eq!(List::::update_position_for(node.clone(), 1_000), None,); + assert_eq!(Node::::get(&1).unwrap().score, 1_000); }); } @@ -359,7 +357,7 @@ mod list { // make sure there are no duplicates. ExtBuilder::default().build_and_execute_no_post_check(|| { - Bag::::get(10).unwrap().insert_unchecked(2); + Bag::::get(10).unwrap().insert_unchecked(2, 10); assert_eq!(List::::sanity_check(), Err("duplicate identified")); }); @@ -397,6 +395,7 @@ mod list { prev: None, next: None, bag_upper: 15, + score: 15, _phantom: PhantomData, }; let node_11_no_bag = Node:: { @@ -404,6 +403,7 @@ mod list { prev: None, next: None, bag_upper: 15, + score: 15, _phantom: PhantomData, }; @@ -435,6 +435,7 @@ mod list { prev: Some(1), next: Some(2), bag_upper: 1_000, + score: 1_000, _phantom: PhantomData, }; assert!(!crate::ListNodes::::contains_key(42)); @@ -464,6 +465,7 @@ mod list { prev: Some(4), next: None, bag_upper: 1_000, + score: 1_000, _phantom: PhantomData, }; assert!(!crate::ListNodes::::contains_key(42)); @@ -493,6 +495,7 @@ mod list { prev: None, next: Some(2), bag_upper: 1_000, + score: 1_000, _phantom: PhantomData, }; assert!(!crate::ListNodes::::contains_key(42)); @@ -522,6 +525,7 @@ mod list { prev: Some(42), next: Some(42), bag_upper: 1_000, + score: 1_000, _phantom: PhantomData, }; assert!(!crate::ListNodes::::contains_key(42)); @@ -586,6 +590,7 @@ mod bags { prev: None, next: None, bag_upper, + score: bag_upper, _phantom: PhantomData, }; @@ -596,7 +601,14 @@ mod bags { assert_eq!( ListNodes::::get(&42).unwrap(), - Node { bag_upper: 10, prev: Some(1), next: None, id: 42, _phantom: PhantomData } + Node { + bag_upper: 10, + score: 5, + prev: Some(1), + next: None, + id: 42, + _phantom: PhantomData + } ); }); } @@ -609,6 +621,7 @@ mod bags { prev: None, next: None, bag_upper, + score: bag_upper, _phantom: PhantomData, }; @@ -636,6 +649,7 @@ mod bags { prev: Some(21), next: Some(101), bag_upper: 20, + score: 20, _phantom: PhantomData, }; bag_20.insert_node_unchecked(node_61); @@ -649,6 +663,7 @@ mod bags { prev: Some(62), next: None, bag_upper: 20, + score: 20, _phantom: PhantomData, } ); @@ -665,13 +680,6 @@ mod bags { // Document improper ways `insert_node` may be getting used. #[test] fn insert_node_bad_paths_documented() { - let node = |id, prev, next, bag_upper| Node:: { - id, - prev, - next, - bag_upper, - _phantom: PhantomData, - }; ExtBuilder::default().build_and_execute_no_post_check(|| { // when inserting a node with both prev & next pointing at an account in an incorrect // bag. @@ -684,7 +692,14 @@ mod bags { // and when the node is re-fetched all the info is correct assert_eq!( Node::::get(&42).unwrap(), - node(42, Some(4), None, bag_1000.bag_upper) + Node:: { + id: 42, + prev: Some(4), + next: None, + bag_upper: bag_1000.bag_upper, + score: 500, + _phantom: PhantomData + } ); }); @@ -720,7 +735,14 @@ mod bags { // and the re-fetched node has bad pointers assert_eq!( Node::::get(&2).unwrap(), - node(2, Some(4), None, bag_1000.bag_upper) + Node:: { + id: 2, + prev: Some(4), + next: None, + bag_upper: bag_1000.bag_upper, + score: 0, + _phantom: PhantomData + }, ); // ^^^ despite being the bags head, it has a prev @@ -739,14 +761,6 @@ mod bags { )] fn insert_node_duplicate_tail_panics_with_debug_assert() { ExtBuilder::default().build_and_execute(|| { - let node = |id, prev, next, bag_upper| Node:: { - id, - prev, - next, - bag_upper, - _phantom: PhantomData, - }; - // given assert_eq!(List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4])],); let mut bag_1000 = Bag::::get(1_000).unwrap(); @@ -877,6 +891,7 @@ mod bags { prev: None, next: Some(3), bag_upper: 10, // should be 1_000 + score: 10, _phantom: PhantomData, }; let mut bag_1000 = Bag::::get(1_000).unwrap(); diff --git a/frame/bags-list/src/migrations.rs b/frame/bags-list/src/migrations.rs index f8041327f10be..a77beb23bd667 100644 --- a/frame/bags-list/src/migrations.rs +++ b/frame/bags-list/src/migrations.rs @@ -17,34 +17,125 @@ //! The migrations of this pallet. +use codec::{Decode, Encode}; +use core::marker::PhantomData; +use frame_election_provider_support::ScoreProvider; use frame_support::traits::OnRuntimeUpgrade; +use sp_runtime::traits::Zero; + +#[cfg(feature = "try-runtime")] +use frame_support::ensure; /// A struct that does not migration, but only checks that the counter prefix exists and is correct. -pub struct CheckCounterPrefix(sp_std::marker::PhantomData); -impl OnRuntimeUpgrade for CheckCounterPrefix { +pub struct CheckCounterPrefix, I: 'static>(sp_std::marker::PhantomData<(T, I)>); +impl, I: 'static> OnRuntimeUpgrade for CheckCounterPrefix { fn on_runtime_upgrade() -> frame_support::weights::Weight { - 0 + frame_support::weights::Weight::zero() } #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result<(), &'static str> { - use frame_support::ensure; // The old explicit storage item. #[frame_support::storage_alias] - type CounterForListNodes = StorageValue, u32>; + type CounterForListNodes, I: 'static> = + StorageValue, u32>; // ensure that a value exists in the counter struct. ensure!( - crate::ListNodes::::count() == CounterForListNodes::::get().unwrap(), + crate::ListNodes::::count() == CounterForListNodes::::get().unwrap(), "wrong list node counter" ); crate::log!( info, "checked bags-list prefix to be correct and have {} nodes", - crate::ListNodes::::count() + crate::ListNodes::::count() ); Ok(()) } } + +mod old { + use super::*; + use frame_support::pallet_prelude::*; + + #[derive(Encode, Decode)] + pub struct PreScoreNode, I: 'static = ()> { + pub id: T::AccountId, + pub prev: Option, + pub next: Option, + pub bag_upper: T::Score, + #[codec(skip)] + pub _phantom: PhantomData, + } + + #[frame_support::storage_alias] + pub type ListNodes, I: 'static> = StorageMap< + crate::Pallet, + Twox64Concat, + ::AccountId, + PreScoreNode, + >; + + #[frame_support::storage_alias] + pub type CounterForListNodes, I: 'static> = + StorageValue, u32, ValueQuery>; + + #[frame_support::storage_alias] + pub type TempStorage, I: 'static> = + StorageValue, u32, ValueQuery>; +} + +/// A struct that migrates all bags lists to contain a score value. +pub struct AddScore, I: 'static = ()>(sp_std::marker::PhantomData<(T, I)>); +impl, I: 'static> OnRuntimeUpgrade for AddScore { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result<(), &'static str> { + // The list node data should be corrupt at this point, so this is zero. + ensure!(crate::ListNodes::::iter().count() == 0, "list node data is not corrupt"); + // We can use the helper `old::ListNode` to get the existing data. + let iter_node_count: u32 = old::ListNodes::::iter().count() as u32; + let tracked_node_count: u32 = old::CounterForListNodes::::get(); + crate::log!(info, "number of nodes before: {:?} {:?}", iter_node_count, tracked_node_count); + ensure!(iter_node_count == tracked_node_count, "Node count is wrong."); + old::TempStorage::::put(iter_node_count); + Ok(()) + } + + fn on_runtime_upgrade() -> frame_support::weights::Weight { + for (_key, node) in old::ListNodes::::iter() { + let score = T::ScoreProvider::score(&node.id); + + let new_node = crate::Node { + id: node.id.clone(), + prev: node.prev, + next: node.next, + bag_upper: node.bag_upper, + score, + _phantom: node._phantom, + }; + + crate::ListNodes::::insert(node.id, new_node); + } + + return frame_support::weights::Weight::MAX + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade() -> Result<(), &'static str> { + let node_count_before = old::TempStorage::::take(); + // Now, the list node data is not corrupt anymore. + let iter_node_count_after: u32 = crate::ListNodes::::iter().count() as u32; + let tracked_node_count_after: u32 = crate::ListNodes::::count(); + crate::log!( + info, + "number of nodes after: {:?} {:?}", + iter_node_count_after, + tracked_node_count_after, + ); + ensure!(iter_node_count_after == node_count_before, "Not all nodes were migrated."); + ensure!(tracked_node_count_after == iter_node_count_after, "Node count is wrong."); + Ok(()) + } +} diff --git a/frame/bags-list/src/tests.rs b/frame/bags-list/src/tests.rs index c63ab26d59a67..01c1642f882c1 100644 --- a/frame/bags-list/src/tests.rs +++ b/frame/bags-list/src/tests.rs @@ -35,8 +35,10 @@ mod pallet { ); // when increasing score to the level of non-existent bag + assert_eq!(List::::get_score(&42).unwrap(), 20); StakingMock::set_score_of(&42, 2_000); assert_ok!(BagsList::rebag(Origin::signed(0), 42)); + assert_eq!(List::::get_score(&42).unwrap(), 2_000); // then a new bag is created and the id moves into it assert_eq!( @@ -53,6 +55,8 @@ mod pallet { List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4]), (2_000, vec![42])] ); + // but the score is updated + assert_eq!(List::::get_score(&42).unwrap(), 1_001); // when reducing score to the level of a non-existent bag StakingMock::set_score_of(&42, 30); @@ -63,6 +67,7 @@ mod pallet { List::::get_bags(), vec![(10, vec![1]), (30, vec![42]), (1_000, vec![2, 3, 4])] ); + assert_eq!(List::::get_score(&42).unwrap(), 30); // when increasing score to the level of a pre-existing bag StakingMock::set_score_of(&42, 500); @@ -73,6 +78,7 @@ mod pallet { List::::get_bags(), vec![(10, vec![1]), (1_000, vec![2, 3, 4, 42])] ); + assert_eq!(List::::get_score(&42).unwrap(), 500); }); } @@ -145,21 +151,20 @@ mod pallet { } #[test] - fn wrong_rebag_is_noop() { + fn wrong_rebag_errs() { ExtBuilder::default().build_and_execute(|| { let node_3 = list::Node::::get(&3).unwrap(); - // when account 3 is _not_ misplaced with weight 500 + // when account 3 is _not_ misplaced with score 500 NextVoteWeight::set(500); assert!(!node_3.is_misplaced(500)); - // then calling rebag on account 3 with weight 500 is a noop + // then calling rebag on account 3 with score 500 is a noop assert_storage_noop!(assert_eq!(BagsList::rebag(Origin::signed(0), 3), Ok(()))); // when account 42 is not in the list assert!(!BagsList::contains(&42)); - - // then rebag-ing account 42 is a noop - assert_storage_noop!(assert_eq!(BagsList::rebag(Origin::signed(0), 42), Ok(()))); + // then rebag-ing account 42 is an error + assert_storage_noop!(assert!(matches!(BagsList::rebag(Origin::signed(0), 42), Err(_)))); }); } @@ -182,7 +187,6 @@ mod pallet { #[test] fn empty_threshold_works() { BagThresholds::set(Default::default()); // which is the same as passing `()` to `Get<_>`. - ExtBuilder::default().build_and_execute(|| { // everyone in the same bag. assert_eq!(List::::get_bags(), vec![(VoteWeight::MAX, vec![1, 2, 3, 4])]); @@ -196,8 +200,7 @@ mod pallet { ); // any rebag is noop. - assert_storage_noop!(assert!(BagsList::rebag(Origin::signed(0), 1).is_ok())); - assert_storage_noop!(assert!(BagsList::rebag(Origin::signed(0), 10).is_ok())); + assert_storage_noop!(assert_eq!(BagsList::rebag(Origin::signed(0), 1), Ok(()))); }) } diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index af366d5b8f919..27bf7a37f9622 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -168,15 +168,12 @@ pub mod onchain; pub mod traits; -#[cfg(feature = "std")] -use codec::{Decode, Encode}; -use frame_support::{weights::Weight, BoundedVec, RuntimeDebug}; use sp_runtime::traits::{Bounded, Saturating, Zero}; use sp_std::{fmt::Debug, prelude::*}; /// Re-export the solution generation macro. pub use frame_election_provider_solution_type::generate_solution_type; -pub use frame_support::traits::Get; +pub use frame_support::{traits::Get, weights::Weight, BoundedVec, RuntimeDebug}; /// Re-export some type as they are used in the interface. pub use sp_arithmetic::PerThing; pub use sp_npos_elections::{ @@ -223,7 +220,7 @@ impl __OrInvalidIndex for Option { /// making it fast to repeatedly encode into a `SolutionOf`. This property turns out /// to be important when trimming for solution length. #[derive(RuntimeDebug, Clone, Default)] -#[cfg_attr(feature = "std", derive(PartialEq, Eq, Encode, Decode))] +#[cfg_attr(feature = "std", derive(PartialEq, Eq, codec::Encode, codec::Decode))] pub struct IndexAssignment { /// Index of the voter among the voters list. pub who: VoterIndex, @@ -467,6 +464,29 @@ pub trait SortedListProvider { /// Returns `Ok(())` iff it successfully updates an item, an `Err(_)` otherwise. fn on_update(id: &AccountId, score: Self::Score) -> Result<(), Self::Error>; + /// Get the score of `id`. + fn get_score(id: &AccountId) -> Result; + + /// Same as `on_update`, but incorporate some increased score. + fn on_increase(id: &AccountId, additional: Self::Score) -> Result<(), Self::Error> { + let old_score = Self::get_score(id)?; + let new_score = old_score.saturating_add(additional); + Self::on_update(id, new_score) + } + + /// Same as `on_update`, but incorporate some decreased score. + /// + /// If the new score of the item is `Zero`, it is removed. + fn on_decrease(id: &AccountId, decreased: Self::Score) -> Result<(), Self::Error> { + let old_score = Self::get_score(id)?; + let new_score = old_score.saturating_sub(decreased); + if new_score.is_zero() { + Self::on_remove(id) + } else { + Self::on_update(id, new_score) + } + } + /// Hook for removing am id from the list. /// /// Returns `Ok(())` iff it successfully removes an item, an `Err(_)` otherwise. @@ -504,7 +524,7 @@ pub trait SortedListProvider { } } -/// Something that can provide the `VoteWeight` of an account. Similar to [`ElectionProvider`] and +/// Something that can provide the `Score` of an account. Similar to [`ElectionProvider`] and /// [`ElectionDataProvider`], this should typically be implementing by whoever is supposed to *use* /// `SortedListProvider`. pub trait ScoreProvider { @@ -513,7 +533,7 @@ pub trait ScoreProvider { /// Get the current `Score` of `who`. fn score(who: &AccountId) -> Self::Score; - /// For tests and benchmarks, set the `VoteWeight`. + /// For tests and benchmarks, set the `score`. #[cfg(any(feature = "runtime-benchmarks", test))] fn set_score_of(_: &AccountId, _: Self::Score) {} } diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 34d7a176670f2..4de1ea36cb591 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -1343,7 +1343,10 @@ impl SortedListProvider for UseNominatorsAndValidatorsM // nothing to do on insert. Ok(()) } - fn on_update(_: &T::AccountId, _weight: VoteWeight) -> Result<(), Self::Error> { + fn get_score(id: &T::AccountId) -> Result { + Ok(Pallet::::weight_of(id)) + } + fn on_update(_: &T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> { // nothing to do on update. Ok(()) } From df7a9d7e2e5d142337c7c6d09b4525c422037cf6 Mon Sep 17 00:00:00 2001 From: Alexander Gryaznov Date: Fri, 20 May 2022 14:20:41 +0300 Subject: [PATCH 251/484] Stabilize ecdsa_ functions (#11486) --- frame/contracts/fixtures/ecdsa_recover.wat | 2 +- frame/contracts/src/benchmarking/mod.rs | 4 ++-- frame/contracts/src/tests.rs | 1 - frame/contracts/src/wasm/mod.rs | 7 ++----- frame/contracts/src/wasm/runtime.rs | 10 ++-------- 5 files changed, 7 insertions(+), 17 deletions(-) diff --git a/frame/contracts/fixtures/ecdsa_recover.wat b/frame/contracts/fixtures/ecdsa_recover.wat index c196e88094d2c..d694b3215e86b 100644 --- a/frame/contracts/fixtures/ecdsa_recover.wat +++ b/frame/contracts/fixtures/ecdsa_recover.wat @@ -4,7 +4,7 @@ ;; 3) Validates that result is Success ;; 4) Returns recovered compressed public key (module - (import "__unstable__" "seal_ecdsa_recover" (func $seal_ecdsa_recover (param i32 i32 i32) (result i32))) + (import "seal0" "seal_ecdsa_recover" (func $seal_ecdsa_recover (param i32 i32 i32) (result i32))) (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) (import "seal0" "seal_input" (func $seal_input (param i32 i32))) (import "env" "memory" (memory 1 1)) diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs index 57a330ae275f2..4241456389ec7 100644 --- a/frame/contracts/src/benchmarking/mod.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -1949,7 +1949,7 @@ benchmarks! { let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "__unstable__", + module: "seal0", name: "seal_ecdsa_recover", params: vec![ValueType::I32, ValueType::I32, ValueType::I32], return_type: Some(ValueType::I32), @@ -1992,7 +1992,7 @@ benchmarks! { let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "__unstable__", + module: "seal0", name: "seal_ecdsa_to_eth_address", params: vec![ValueType::I32, ValueType::I32], return_type: Some(ValueType::I32), diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index 2125fe24d1a07..5a6a29786c629 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -2277,7 +2277,6 @@ fn gas_estimation_call_runtime() { } #[test] -#[cfg(feature = "unstable-interface")] fn ecdsa_recover() { let (wasm, code_hash) = compile_module::("ecdsa_recover").unwrap(); diff --git a/frame/contracts/src/wasm/mod.rs b/frame/contracts/src/wasm/mod.rs index 8764bf3690b82..c385b5d2f7cc2 100644 --- a/frame/contracts/src/wasm/mod.rs +++ b/frame/contracts/src/wasm/mod.rs @@ -1059,7 +1059,6 @@ mod tests { ); } - #[cfg(feature = "unstable-interface")] const CODE_ECDSA_RECOVER: &str = r#" (module ;; seal_ecdsa_recover( @@ -1067,7 +1066,7 @@ mod tests { ;; message_hash_ptr: u32, ;; output_ptr: u32 ;; ) -> u32 - (import "__unstable__" "seal_ecdsa_recover" (func $seal_ecdsa_recover (param i32 i32 i32) (result i32))) + (import "seal0" "seal_ecdsa_recover" (func $seal_ecdsa_recover (param i32 i32 i32) (result i32))) (import "env" "memory" (memory 1 1)) (func (export "call") (drop @@ -1097,7 +1096,6 @@ mod tests { "#; #[test] - #[cfg(feature = "unstable-interface")] fn contract_ecdsa_recover() { let mut mock_ext = MockExt::default(); assert_ok!(execute(&CODE_ECDSA_RECOVER, vec![], &mut mock_ext)); @@ -1105,13 +1103,12 @@ mod tests { } #[test] - #[cfg(feature = "unstable-interface")] fn contract_ecdsa_to_eth_address() { /// calls `seal_ecdsa_to_eth_address` for the contstant and ensures the result equals the /// expected one. const CODE_ECDSA_TO_ETH_ADDRESS: &str = r#" (module - (import "__unstable__" "seal_ecdsa_to_eth_address" (func $seal_ecdsa_to_eth_address (param i32 i32) (result i32))) + (import "seal0" "seal_ecdsa_to_eth_address" (func $seal_ecdsa_to_eth_address (param i32 i32) (result i32))) (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) (import "env" "memory" (memory 1 1)) diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index 11dfc77616e69..850794fc9b09e 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -74,7 +74,6 @@ pub enum ReturnCode { /// ECDSA pubkey recovery failed (most probably wrong recovery id or signature), or /// ECDSA compressed pubkey conversion into Ethereum address failed (most probably /// wrong pubkey provided). - #[cfg(feature = "unstable-interface")] EcdsaRecoverFailed = 11, } @@ -214,7 +213,6 @@ pub enum RuntimeCosts { /// Weight of calling `seal_hash_blake2_128` for the given input size. HashBlake128(u32), /// Weight of calling `seal_ecdsa_recover`. - #[cfg(feature = "unstable-interface")] EcdsaRecovery, /// Weight charged by a chain extension through `seal_call_chain_extension`. ChainExtension(u64), @@ -224,7 +222,6 @@ pub enum RuntimeCosts { /// Weight of calling `seal_set_code_hash` SetCodeHash, /// Weight of calling `ecdsa_to_eth_address` - #[cfg(feature = "unstable-interface")] EcdsaToEthAddress, } @@ -299,14 +296,11 @@ impl RuntimeCosts { HashBlake128(len) => s .hash_blake2_128 .saturating_add(s.hash_blake2_128_per_byte.saturating_mul(len.into())), - #[cfg(feature = "unstable-interface")] EcdsaRecovery => s.ecdsa_recover, ChainExtension(amount) => amount, - #[cfg(feature = "unstable-interface")] CallRuntime(weight) => weight, SetCodeHash => s.set_code_hash, - #[cfg(feature = "unstable-interface")] EcdsaToEthAddress => s.ecdsa_to_eth_address, }; RuntimeToken { @@ -1993,7 +1987,7 @@ define_env!(Env, , // # Errors // // `ReturnCode::EcdsaRecoverFailed` - [__unstable__] seal_ecdsa_recover(ctx, signature_ptr: u32, message_hash_ptr: u32, output_ptr: u32) -> ReturnCode => { + [seal0] seal_ecdsa_recover(ctx, signature_ptr: u32, message_hash_ptr: u32, output_ptr: u32) -> ReturnCode => { ctx.charge_gas(RuntimeCosts::EcdsaRecovery)?; let mut signature: [u8; 65] = [0; 65]; @@ -2069,7 +2063,7 @@ define_env!(Env, , // # Errors // // `ReturnCode::EcdsaRecoverFailed` - [__unstable__] seal_ecdsa_to_eth_address(ctx, key_ptr: u32, out_ptr: u32) -> ReturnCode => { + [seal0] seal_ecdsa_to_eth_address(ctx, key_ptr: u32, out_ptr: u32) -> ReturnCode => { ctx.charge_gas(RuntimeCosts::EcdsaToEthAddress)?; let mut compressed_key: [u8; 33] = [0;33]; ctx.read_sandbox_memory_into_buf(key_ptr, &mut compressed_key)?; From d481f5615dd9ee0a7d4d65909e8402ad0b1b01ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 May 2022 15:48:27 +0300 Subject: [PATCH 252/484] Bump actions/checkout from 2 to 3 (#11463) Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v2...v3) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-labels.yml | 2 +- .github/workflows/md-link-check.yml | 2 +- .github/workflows/monthly-tag.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/check-labels.yml b/.github/workflows/check-labels.yml index 74fdd9b2d8188..de204ce9d3776 100644 --- a/.github/workflows/check-labels.yml +++ b/.github/workflows/check-labels.yml @@ -8,7 +8,7 @@ jobs: check-labels: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 ref: ${{ github.event.pull_request.head.ref }} diff --git a/.github/workflows/md-link-check.yml b/.github/workflows/md-link-check.yml index 868569911d471..0ad56f9e1b278 100644 --- a/.github/workflows/md-link-check.yml +++ b/.github/workflows/md-link-check.yml @@ -12,7 +12,7 @@ jobs: markdown-link-check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: gaurav-nelson/github-action-markdown-link-check@7481451f70251762f149d69596e3e276ebf2b236 with: use-quiet-mode: 'yes' diff --git a/.github/workflows/monthly-tag.yml b/.github/workflows/monthly-tag.yml index 9fed865396013..ade8bd4717c39 100644 --- a/.github/workflows/monthly-tag.yml +++ b/.github/workflows/monthly-tag.yml @@ -16,7 +16,7 @@ jobs: echo "::set-output name=new::$(date +'monthly-%Y-%m')" echo "::set-output name=old::$(date -d'1 month ago' +'monthly-%Y-%m')" - name: Checkout branch "master" - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: ref: 'master' fetch-depth: 0 From b39aa96c98571f9991125c77693540609139c3a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 May 2022 16:22:01 +0200 Subject: [PATCH 253/484] Bump gaurav-nelson/github-action-markdown-link-check from 1.0.9 to 1.0.14 (#11464) * Bump gaurav-nelson/github-action-markdown-link-check Bumps [gaurav-nelson/github-action-markdown-link-check](https://github.com/gaurav-nelson/github-action-markdown-link-check) from 1.0.9 to 1.0.14. - [Release notes](https://github.com/gaurav-nelson/github-action-markdown-link-check/releases) - [Commits](https://github.com/gaurav-nelson/github-action-markdown-link-check/compare/7481451f70251762f149d69596e3e276ebf2b236...58f84fd654812d0d8da4e4d4a559eda087daf8ce) --- updated-dependencies: - dependency-name: gaurav-nelson/github-action-markdown-link-check dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * Update .github/workflows/md-link-check.yml Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Denis Pisarev --- .github/workflows/md-link-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/md-link-check.yml b/.github/workflows/md-link-check.yml index 0ad56f9e1b278..4b6b9166be2df 100644 --- a/.github/workflows/md-link-check.yml +++ b/.github/workflows/md-link-check.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: gaurav-nelson/github-action-markdown-link-check@7481451f70251762f149d69596e3e276ebf2b236 + - uses: gaurav-nelson/github-action-markdown-link-check@9710f0fec812ce0a3b98bef4c9d842fc1f39d976 # v1.0.13 with: use-quiet-mode: 'yes' config-file: '.github/workflows/mlc_config.json' From 3e6e8afaecaf125f580e6a08cfd8af0dc77f28ff Mon Sep 17 00:00:00 2001 From: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Date: Fri, 20 May 2022 22:16:35 +0300 Subject: [PATCH 254/484] RPC: Mark storage methods as `blocking` (#11459) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * client/api: Make `storage_keys` blocking Signed-off-by: Alexandru Vasile * client/api: Ensure `state_*` RPC methods are blocking Signed-off-by: Alexandru Vasile * client/rpc: Ensure `childstate_*` RPC methods are blocking Signed-off-by: Alexandru Vasile * client/rpc: `ChainApi` make RPC methods sync Signed-off-by: Alexandru Vasile * Remove unused async-traits Signed-off-by: Alexandru Vasile * client/rpc-api: Make chain RPC methods blocking Signed-off-by: Alexandru Vasile * Update client/rpc/src/state/state_full.rs Co-authored-by: Bastian Köcher * Add `blocking` to `state_getKeysPaged` RPC call Signed-off-by: Alexandru Vasile * Fix build and warning Signed-off-by: Alexandru Vasile * Remove `async_trait` tidyup Signed-off-by: Alexandru Vasile Co-authored-by: Bastian Köcher --- client/rpc-api/src/chain/mod.rs | 12 +-- client/rpc-api/src/child_state/mod.rs | 28 +++--- client/rpc-api/src/state/mod.rs | 60 +++++------- client/rpc/src/chain/chain_full.rs | 7 +- client/rpc/src/chain/mod.rs | 19 ++-- client/rpc/src/state/mod.rs | 136 +++++++++++--------------- client/rpc/src/state/state_full.rs | 65 ++++++------ client/rpc/src/state/tests.rs | 60 ++++-------- 8 files changed, 164 insertions(+), 223 deletions(-) diff --git a/client/rpc-api/src/chain/mod.rs b/client/rpc-api/src/chain/mod.rs index f5f9524264e34..c7cc97463983d 100644 --- a/client/rpc-api/src/chain/mod.rs +++ b/client/rpc-api/src/chain/mod.rs @@ -26,24 +26,24 @@ pub mod error; #[rpc(client, server)] pub trait ChainApi { /// Get header. - #[method(name = "chain_getHeader")] - async fn header(&self, hash: Option) -> RpcResult>; + #[method(name = "chain_getHeader", blocking)] + fn header(&self, hash: Option) -> RpcResult>; /// Get header and body of a relay chain block. - #[method(name = "chain_getBlock")] - async fn block(&self, hash: Option) -> RpcResult>; + #[method(name = "chain_getBlock", blocking)] + fn block(&self, hash: Option) -> RpcResult>; /// Get hash of the n-th block in the canon chain. /// /// By default returns latest block hash. - #[method(name = "chain_getBlockHash", aliases = ["chain_getHead"])] + #[method(name = "chain_getBlockHash", aliases = ["chain_getHead"], blocking)] fn block_hash( &self, hash: Option>, ) -> RpcResult>>; /// Get hash of the last finalized block in the canon chain. - #[method(name = "chain_getFinalizedHead", aliases = ["chain_getFinalisedHead"])] + #[method(name = "chain_getFinalizedHead", aliases = ["chain_getFinalisedHead"], blocking)] fn finalized_head(&self) -> RpcResult; /// All head subscription. diff --git a/client/rpc-api/src/child_state/mod.rs b/client/rpc-api/src/child_state/mod.rs index a15b1a0e7ee05..be279a97463b0 100644 --- a/client/rpc-api/src/child_state/mod.rs +++ b/client/rpc-api/src/child_state/mod.rs @@ -28,9 +28,9 @@ use sp_core::storage::{PrefixedStorageKey, StorageData, StorageKey}; #[rpc(client, server)] pub trait ChildStateApi { /// Returns the keys with prefix from a child storage, leave empty to get all the keys - #[method(name = "childstate_getKeys")] + #[method(name = "childstate_getKeys", blocking)] #[deprecated(since = "2.0.0", note = "Please use `getKeysPaged` with proper paging support")] - async fn storage_keys( + fn storage_keys( &self, child_storage_key: PrefixedStorageKey, prefix: StorageKey, @@ -40,8 +40,8 @@ pub trait ChildStateApi { /// Returns the keys with prefix from a child storage with pagination support. /// Up to `count` keys will be returned. /// If `start_key` is passed, return next keys in storage in lexicographic order. - #[method(name = "childstate_getKeysPaged", aliases = ["childstate_getKeysPagedAt"])] - async fn storage_keys_paged( + #[method(name = "childstate_getKeysPaged", aliases = ["childstate_getKeysPagedAt"], blocking)] + fn storage_keys_paged( &self, child_storage_key: PrefixedStorageKey, prefix: Option, @@ -51,8 +51,8 @@ pub trait ChildStateApi { ) -> RpcResult>; /// Returns a child storage entry at a specific block's state. - #[method(name = "childstate_getStorage")] - async fn storage( + #[method(name = "childstate_getStorage", blocking)] + fn storage( &self, child_storage_key: PrefixedStorageKey, key: StorageKey, @@ -60,8 +60,8 @@ pub trait ChildStateApi { ) -> RpcResult>; /// Returns child storage entries for multiple keys at a specific block's state. - #[method(name = "childstate_getStorageEntries")] - async fn storage_entries( + #[method(name = "childstate_getStorageEntries", blocking)] + fn storage_entries( &self, child_storage_key: PrefixedStorageKey, keys: Vec, @@ -69,8 +69,8 @@ pub trait ChildStateApi { ) -> RpcResult>>; /// Returns the hash of a child storage entry at a block's state. - #[method(name = "childstate_getStorageHash")] - async fn storage_hash( + #[method(name = "childstate_getStorageHash", blocking)] + fn storage_hash( &self, child_storage_key: PrefixedStorageKey, key: StorageKey, @@ -78,8 +78,8 @@ pub trait ChildStateApi { ) -> RpcResult>; /// Returns the size of a child storage entry at a block's state. - #[method(name = "childstate_getStorageSize")] - async fn storage_size( + #[method(name = "childstate_getStorageSize", blocking)] + fn storage_size( &self, child_storage_key: PrefixedStorageKey, key: StorageKey, @@ -87,8 +87,8 @@ pub trait ChildStateApi { ) -> RpcResult>; /// Returns proof of storage for child key entries at a specific block's state. - #[method(name = "state_getChildReadProof")] - async fn read_child_proof( + #[method(name = "state_getChildReadProof", blocking)] + fn read_child_proof( &self, child_storage_key: PrefixedStorageKey, keys: Vec, diff --git a/client/rpc-api/src/state/mod.rs b/client/rpc-api/src/state/mod.rs index fba023e830262..54bf21674a8bd 100644 --- a/client/rpc-api/src/state/mod.rs +++ b/client/rpc-api/src/state/mod.rs @@ -34,21 +34,17 @@ pub use self::helpers::ReadProof; #[rpc(client, server)] pub trait StateApi { /// Call a contract at a block's state. - #[method(name = "state_call", aliases = ["state_callAt"])] - async fn call(&self, name: String, bytes: Bytes, hash: Option) -> RpcResult; + #[method(name = "state_call", aliases = ["state_callAt"], blocking)] + fn call(&self, name: String, bytes: Bytes, hash: Option) -> RpcResult; /// Returns the keys with prefix, leave empty to get all the keys. - #[method(name = "state_getKeys")] + #[method(name = "state_getKeys", blocking)] #[deprecated(since = "2.0.0", note = "Please use `getKeysPaged` with proper paging support")] - async fn storage_keys( - &self, - prefix: StorageKey, - hash: Option, - ) -> RpcResult>; + fn storage_keys(&self, prefix: StorageKey, hash: Option) -> RpcResult>; /// Returns the keys with prefix, leave empty to get all the keys - #[method(name = "state_getPairs")] - async fn storage_pairs( + #[method(name = "state_getPairs", blocking)] + fn storage_pairs( &self, prefix: StorageKey, hash: Option, @@ -57,8 +53,8 @@ pub trait StateApi { /// Returns the keys with prefix with pagination support. /// Up to `count` keys will be returned. /// If `start_key` is passed, return next keys in storage in lexicographic order. - #[method(name = "state_getKeysPaged", aliases = ["state_getKeysPagedAt"])] - async fn storage_keys_paged( + #[method(name = "state_getKeysPaged", aliases = ["state_getKeysPagedAt"], blocking)] + fn storage_keys_paged( &self, prefix: Option, count: u32, @@ -67,32 +63,32 @@ pub trait StateApi { ) -> RpcResult>; /// Returns a storage entry at a specific block's state. - #[method(name = "state_getStorage", aliases = ["state_getStorageAt"])] - async fn storage(&self, key: StorageKey, hash: Option) -> RpcResult>; + #[method(name = "state_getStorage", aliases = ["state_getStorageAt"], blocking)] + fn storage(&self, key: StorageKey, hash: Option) -> RpcResult>; /// Returns the hash of a storage entry at a block's state. - #[method(name = "state_getStorageHash", aliases = ["state_getStorageHashAt"])] - async fn storage_hash(&self, key: StorageKey, hash: Option) -> RpcResult>; + #[method(name = "state_getStorageHash", aliases = ["state_getStorageHashAt"], blocking)] + fn storage_hash(&self, key: StorageKey, hash: Option) -> RpcResult>; /// Returns the size of a storage entry at a block's state. - #[method(name = "state_getStorageSize", aliases = ["state_getStorageSizeAt"])] - async fn storage_size(&self, key: StorageKey, hash: Option) -> RpcResult>; + #[method(name = "state_getStorageSize", aliases = ["state_getStorageSizeAt"], blocking)] + fn storage_size(&self, key: StorageKey, hash: Option) -> RpcResult>; /// Returns the runtime metadata as an opaque blob. - #[method(name = "state_getMetadata")] - async fn metadata(&self, hash: Option) -> RpcResult; + #[method(name = "state_getMetadata", blocking)] + fn metadata(&self, hash: Option) -> RpcResult; /// Get the runtime version. - #[method(name = "state_getRuntimeVersion", aliases = ["chain_getRuntimeVersion"])] - async fn runtime_version(&self, hash: Option) -> RpcResult; + #[method(name = "state_getRuntimeVersion", aliases = ["chain_getRuntimeVersion"], blocking)] + fn runtime_version(&self, hash: Option) -> RpcResult; /// Query historical storage entries (by key) starting from a block given as the second /// parameter. /// /// NOTE This first returned result contains the initial state of storage for all keys. /// Subsequent values in the vector represent changes to the previous state (diffs). - #[method(name = "state_queryStorage")] - async fn query_storage( + #[method(name = "state_queryStorage", blocking)] + fn query_storage( &self, keys: Vec, block: Hash, @@ -100,20 +96,16 @@ pub trait StateApi { ) -> RpcResult>>; /// Query storage entries (by key) starting at block hash given as the second parameter. - #[method(name = "state_queryStorageAt")] - async fn query_storage_at( + #[method(name = "state_queryStorageAt", blocking)] + fn query_storage_at( &self, keys: Vec, at: Option, ) -> RpcResult>>; /// Returns proof of storage entries at a specific block's state. - #[method(name = "state_getReadProof")] - async fn read_proof( - &self, - keys: Vec, - hash: Option, - ) -> RpcResult>; + #[method(name = "state_getReadProof", blocking)] + fn read_proof(&self, keys: Vec, hash: Option) -> RpcResult>; /// New runtime version subscription #[subscription( @@ -285,8 +277,8 @@ pub trait StateApi { /// /// If you are having issues with maximum payload size you can use the flag /// `-ltracing=trace` to get some logging during tracing. - #[method(name = "state_traceBlock")] - async fn trace_block( + #[method(name = "state_traceBlock", blocking)] + fn trace_block( &self, block: Hash, targets: Option, diff --git a/client/rpc/src/chain/chain_full.rs b/client/rpc/src/chain/chain_full.rs index 2d507f7b9b684..c00c6e5875d94 100644 --- a/client/rpc/src/chain/chain_full.rs +++ b/client/rpc/src/chain/chain_full.rs @@ -26,7 +26,7 @@ use futures::{ future::{self, FutureExt}, stream::{self, Stream, StreamExt}, }; -use jsonrpsee::{core::async_trait, PendingSubscription}; +use jsonrpsee::PendingSubscription; use sc_client_api::{BlockBackend, BlockchainEvents}; use sp_blockchain::HeaderBackend; use sp_runtime::{ @@ -51,7 +51,6 @@ impl FullChain { } } -#[async_trait] impl ChainBackend for FullChain where Block: BlockT + 'static, @@ -62,11 +61,11 @@ where &self.client } - async fn header(&self, hash: Option) -> Result, Error> { + fn header(&self, hash: Option) -> Result, Error> { self.client.header(BlockId::Hash(self.unwrap_or_best(hash))).map_err(client_err) } - async fn block(&self, hash: Option) -> Result>, Error> { + fn block(&self, hash: Option) -> Result>, Error> { self.client.block(&BlockId::Hash(self.unwrap_or_best(hash))).map_err(client_err) } diff --git a/client/rpc/src/chain/mod.rs b/client/rpc/src/chain/mod.rs index a79c66e0a18f6..a300c80271fd1 100644 --- a/client/rpc/src/chain/mod.rs +++ b/client/rpc/src/chain/mod.rs @@ -27,10 +27,7 @@ use std::sync::Arc; use crate::SubscriptionTaskExecutor; -use jsonrpsee::{ - core::{async_trait, RpcResult}, - PendingSubscription, -}; +use jsonrpsee::{core::RpcResult, PendingSubscription}; use sc_client_api::BlockchainEvents; use sp_rpc::{list::ListOrValue, number::NumberOrHex}; use sp_runtime::{ @@ -45,7 +42,6 @@ pub use sc_rpc_api::chain::*; use sp_blockchain::HeaderBackend; /// Blockchain backend API -#[async_trait] trait ChainBackend: Send + Sync + 'static where Block: BlockT + 'static, @@ -64,10 +60,10 @@ where } /// Get header of a relay chain block. - async fn header(&self, hash: Option) -> Result, Error>; + fn header(&self, hash: Option) -> Result, Error>; /// Get header and body of a relay chain block. - async fn block(&self, hash: Option) -> Result>, Error>; + fn block(&self, hash: Option) -> Result>, Error>; /// Get hash of the n-th block in the canon chain. /// @@ -126,7 +122,6 @@ pub struct Chain { backend: Box>, } -#[async_trait] impl ChainApiServer, Block::Hash, Block::Header, SignedBlock> for Chain where @@ -134,12 +129,12 @@ where Block::Header: Unpin, Client: HeaderBackend + BlockchainEvents + 'static, { - async fn header(&self, hash: Option) -> RpcResult> { - self.backend.header(hash).await.map_err(Into::into) + fn header(&self, hash: Option) -> RpcResult> { + self.backend.header(hash).map_err(Into::into) } - async fn block(&self, hash: Option) -> RpcResult>> { - self.backend.block(hash).await.map_err(Into::into) + fn block(&self, hash: Option) -> RpcResult>> { + self.backend.block(hash).map_err(Into::into) } fn block_hash( diff --git a/client/rpc/src/state/mod.rs b/client/rpc/src/state/mod.rs index a45651c5e7990..4b5b74950a486 100644 --- a/client/rpc/src/state/mod.rs +++ b/client/rpc/src/state/mod.rs @@ -28,7 +28,7 @@ use std::sync::Arc; use crate::SubscriptionTaskExecutor; use jsonrpsee::{ - core::{async_trait, Error as JsonRpseeError, RpcResult}, + core::{Error as JsonRpseeError, RpcResult}, ws_server::PendingSubscription, }; @@ -53,14 +53,13 @@ use sp_blockchain::{HeaderBackend, HeaderMetadata}; const STORAGE_KEYS_PAGED_MAX_COUNT: u32 = 1000; /// State backend API. -#[async_trait] pub trait StateBackend: Send + Sync + 'static where Block: BlockT + 'static, Client: Send + Sync + 'static, { /// Call runtime method at given block. - async fn call( + fn call( &self, block: Option, method: String, @@ -68,21 +67,21 @@ where ) -> Result; /// Returns the keys with prefix, leave empty to get all the keys. - async fn storage_keys( + fn storage_keys( &self, block: Option, prefix: StorageKey, ) -> Result, Error>; /// Returns the keys with prefix along with their values, leave empty to get all the pairs. - async fn storage_pairs( + fn storage_pairs( &self, block: Option, prefix: StorageKey, ) -> Result, Error>; /// Returns the keys with prefix with pagination support. - async fn storage_keys_paged( + fn storage_keys_paged( &self, block: Option, prefix: Option, @@ -91,14 +90,14 @@ where ) -> Result, Error>; /// Returns a storage entry at a specific block's state. - async fn storage( + fn storage( &self, block: Option, key: StorageKey, ) -> Result, Error>; /// Returns the hash of a storage entry at a block's state. - async fn storage_hash( + fn storage_hash( &self, block: Option, key: StorageKey, @@ -108,24 +107,24 @@ where /// /// If data is available at `key`, it is returned. Else, the sum of values who's key has `key` /// prefix is returned, i.e. all the storage (double) maps that have this prefix. - async fn storage_size( + fn storage_size( &self, block: Option, key: StorageKey, ) -> Result, Error>; /// Returns the runtime metadata as an opaque blob. - async fn metadata(&self, block: Option) -> Result; + fn metadata(&self, block: Option) -> Result; /// Get the runtime version. - async fn runtime_version(&self, block: Option) -> Result; + fn runtime_version(&self, block: Option) -> Result; /// Query historical storage entries (by key) starting from a block given as the second /// parameter. /// /// NOTE This first returned result contains the initial state of storage for all keys. /// Subsequent values in the vector represent changes to the previous state (diffs). - async fn query_storage( + fn query_storage( &self, from: Block::Hash, to: Option, @@ -133,21 +132,21 @@ where ) -> Result>, Error>; /// Query storage entries (by key) starting at block hash given as the second parameter. - async fn query_storage_at( + fn query_storage_at( &self, keys: Vec, at: Option, ) -> Result>, Error>; /// Returns proof of storage entries at a specific block's state. - async fn read_proof( + fn read_proof( &self, block: Option, keys: Vec, ) -> Result, Error>; /// Trace storage changes for block - async fn trace_block( + fn trace_block( &self, block: Block::Hash, targets: Option, @@ -203,39 +202,33 @@ pub struct StateApi { deny_unsafe: DenyUnsafe, } -#[async_trait] impl StateApiServer for StateApi where Block: BlockT + 'static, Client: Send + Sync + 'static, { - async fn call( - &self, - method: String, - data: Bytes, - block: Option, - ) -> RpcResult { - self.backend.call(block, method, data).await.map_err(Into::into) + fn call(&self, method: String, data: Bytes, block: Option) -> RpcResult { + self.backend.call(block, method, data).map_err(Into::into) } - async fn storage_keys( + fn storage_keys( &self, key_prefix: StorageKey, block: Option, ) -> RpcResult> { - self.backend.storage_keys(block, key_prefix).await.map_err(Into::into) + self.backend.storage_keys(block, key_prefix).map_err(Into::into) } - async fn storage_pairs( + fn storage_pairs( &self, key_prefix: StorageKey, block: Option, ) -> RpcResult> { self.deny_unsafe.check_if_safe()?; - self.backend.storage_pairs(block, key_prefix).await.map_err(Into::into) + self.backend.storage_pairs(block, key_prefix).map_err(Into::into) } - async fn storage_keys_paged( + fn storage_keys_paged( &self, prefix: Option, count: u32, @@ -250,66 +243,61 @@ where } self.backend .storage_keys_paged(block, prefix, count, start_key) - .await .map_err(Into::into) } - async fn storage( + fn storage( &self, key: StorageKey, block: Option, ) -> RpcResult> { - self.backend.storage(block, key).await.map_err(Into::into) + self.backend.storage(block, key).map_err(Into::into) } - async fn storage_hash( + fn storage_hash( &self, key: StorageKey, block: Option, ) -> RpcResult> { - self.backend.storage_hash(block, key).await.map_err(Into::into) + self.backend.storage_hash(block, key).map_err(Into::into) } - async fn storage_size( - &self, - key: StorageKey, - block: Option, - ) -> RpcResult> { - self.backend.storage_size(block, key).await.map_err(Into::into) + fn storage_size(&self, key: StorageKey, block: Option) -> RpcResult> { + self.backend.storage_size(block, key).map_err(Into::into) } - async fn metadata(&self, block: Option) -> RpcResult { - self.backend.metadata(block).await.map_err(Into::into) + fn metadata(&self, block: Option) -> RpcResult { + self.backend.metadata(block).map_err(Into::into) } - async fn runtime_version(&self, at: Option) -> RpcResult { - self.backend.runtime_version(at).await.map_err(Into::into) + fn runtime_version(&self, at: Option) -> RpcResult { + self.backend.runtime_version(at).map_err(Into::into) } - async fn query_storage( + fn query_storage( &self, keys: Vec, from: Block::Hash, to: Option, ) -> RpcResult>> { self.deny_unsafe.check_if_safe()?; - self.backend.query_storage(from, to, keys).await.map_err(Into::into) + self.backend.query_storage(from, to, keys).map_err(Into::into) } - async fn query_storage_at( + fn query_storage_at( &self, keys: Vec, at: Option, ) -> RpcResult>> { - self.backend.query_storage_at(keys, at).await.map_err(Into::into) + self.backend.query_storage_at(keys, at).map_err(Into::into) } - async fn read_proof( + fn read_proof( &self, keys: Vec, block: Option, ) -> RpcResult> { - self.backend.read_proof(block, keys).await.map_err(Into::into) + self.backend.read_proof(block, keys).map_err(Into::into) } /// Re-execute the given block with the tracing targets given in `targets` @@ -317,7 +305,7 @@ where /// /// Note: requires the node to run with `--rpc-methods=Unsafe`. /// Note: requires runtimes compiled with wasm tracing support, `--features with-tracing`. - async fn trace_block( + fn trace_block( &self, block: Block::Hash, targets: Option, @@ -327,7 +315,6 @@ where self.deny_unsafe.check_if_safe()?; self.backend .trace_block(block, targets, storage_keys, methods) - .await .map_err(Into::into) } @@ -348,14 +335,13 @@ where } /// Child state backend API. -#[async_trait] pub trait ChildStateBackend: Send + Sync + 'static where Block: BlockT + 'static, Client: Send + Sync + 'static, { /// Returns proof of storage for a child key entries at a specific block's state. - async fn read_child_proof( + fn read_child_proof( &self, block: Option, storage_key: PrefixedStorageKey, @@ -364,7 +350,7 @@ where /// Returns the keys with prefix from a child storage, /// leave prefix empty to get all the keys. - async fn storage_keys( + fn storage_keys( &self, block: Option, storage_key: PrefixedStorageKey, @@ -372,7 +358,7 @@ where ) -> Result, Error>; /// Returns the keys with prefix from a child storage with pagination support. - async fn storage_keys_paged( + fn storage_keys_paged( &self, block: Option, storage_key: PrefixedStorageKey, @@ -382,7 +368,7 @@ where ) -> Result, Error>; /// Returns a child storage entry at a specific block's state. - async fn storage( + fn storage( &self, block: Option, storage_key: PrefixedStorageKey, @@ -390,7 +376,7 @@ where ) -> Result, Error>; /// Returns child storage entries at a specific block's state. - async fn storage_entries( + fn storage_entries( &self, block: Option, storage_key: PrefixedStorageKey, @@ -398,7 +384,7 @@ where ) -> Result>, Error>; /// Returns the hash of a child storage entry at a block's state. - async fn storage_hash( + fn storage_hash( &self, block: Option, storage_key: PrefixedStorageKey, @@ -406,13 +392,13 @@ where ) -> Result, Error>; /// Returns the size of a child storage entry at a block's state. - async fn storage_size( + fn storage_size( &self, block: Option, storage_key: PrefixedStorageKey, key: StorageKey, ) -> Result, Error> { - self.storage(block, storage_key, key).await.map(|x| x.map(|x| x.0.len() as u64)) + self.storage(block, storage_key, key).map(|x| x.map(|x| x.0.len() as u64)) } } @@ -421,25 +407,21 @@ pub struct ChildState { backend: Box>, } -#[async_trait] impl ChildStateApiServer for ChildState where Block: BlockT + 'static, Client: Send + Sync + 'static, { - async fn storage_keys( + fn storage_keys( &self, storage_key: PrefixedStorageKey, key_prefix: StorageKey, block: Option, ) -> RpcResult> { - self.backend - .storage_keys(block, storage_key, key_prefix) - .await - .map_err(Into::into) + self.backend.storage_keys(block, storage_key, key_prefix).map_err(Into::into) } - async fn storage_keys_paged( + fn storage_keys_paged( &self, storage_key: PrefixedStorageKey, prefix: Option, @@ -449,47 +431,46 @@ where ) -> RpcResult> { self.backend .storage_keys_paged(block, storage_key, prefix, count, start_key) - .await .map_err(Into::into) } - async fn storage( + fn storage( &self, storage_key: PrefixedStorageKey, key: StorageKey, block: Option, ) -> RpcResult> { - self.backend.storage(block, storage_key, key).await.map_err(Into::into) + self.backend.storage(block, storage_key, key).map_err(Into::into) } - async fn storage_entries( + fn storage_entries( &self, storage_key: PrefixedStorageKey, keys: Vec, block: Option, ) -> RpcResult>> { - self.backend.storage_entries(block, storage_key, keys).await.map_err(Into::into) + self.backend.storage_entries(block, storage_key, keys).map_err(Into::into) } - async fn storage_hash( + fn storage_hash( &self, storage_key: PrefixedStorageKey, key: StorageKey, block: Option, ) -> RpcResult> { - self.backend.storage_hash(block, storage_key, key).await.map_err(Into::into) + self.backend.storage_hash(block, storage_key, key).map_err(Into::into) } - async fn storage_size( + fn storage_size( &self, storage_key: PrefixedStorageKey, key: StorageKey, block: Option, ) -> RpcResult> { - self.backend.storage_size(block, storage_key, key).await.map_err(Into::into) + self.backend.storage_size(block, storage_key, key).map_err(Into::into) } - async fn read_child_proof( + fn read_child_proof( &self, child_storage_key: PrefixedStorageKey, keys: Vec, @@ -497,7 +478,6 @@ where ) -> RpcResult> { self.backend .read_child_proof(block, child_storage_key, keys) - .await .map_err(Into::into) } } diff --git a/client/rpc/src/state/state_full.rs b/client/rpc/src/state/state_full.rs index 0e20832b30508..c58638c870ab3 100644 --- a/client/rpc/src/state/state_full.rs +++ b/client/rpc/src/state/state_full.rs @@ -28,10 +28,7 @@ use super::{ use crate::SubscriptionTaskExecutor; use futures::{future, stream, FutureExt, StreamExt}; -use jsonrpsee::{ - core::{async_trait, Error as JsonRpseeError}, - PendingSubscription, -}; +use jsonrpsee::{core::Error as JsonRpseeError, PendingSubscription}; use sc_client_api::{ Backend, BlockBackend, BlockchainEvents, CallExecutor, ExecutorProvider, ProofProvider, StorageProvider, @@ -170,7 +167,6 @@ where } } -#[async_trait] impl StateBackend for FullState where Block: BlockT + 'static, @@ -190,7 +186,7 @@ where + 'static, Client::Api: Metadata, { - async fn call( + fn call( &self, block: Option, method: String, @@ -212,7 +208,7 @@ where .map_err(client_err) } - async fn storage_keys( + fn storage_keys( &self, block: Option, prefix: StorageKey, @@ -222,7 +218,7 @@ where .map_err(client_err) } - async fn storage_pairs( + fn storage_pairs( &self, block: Option, prefix: StorageKey, @@ -232,7 +228,7 @@ where .map_err(client_err) } - async fn storage_keys_paged( + fn storage_keys_paged( &self, block: Option, prefix: Option, @@ -251,7 +247,7 @@ where .map_err(client_err) } - async fn storage( + fn storage( &self, block: Option, key: StorageKey, @@ -261,7 +257,7 @@ where .map_err(client_err) } - async fn storage_size( + fn storage_size( &self, block: Option, key: StorageKey, @@ -290,7 +286,7 @@ where .map_err(client_err) } - async fn storage_hash( + fn storage_hash( &self, block: Option, key: StorageKey, @@ -300,7 +296,7 @@ where .map_err(client_err) } - async fn metadata(&self, block: Option) -> std::result::Result { + fn metadata(&self, block: Option) -> std::result::Result { self.block_or_best(block).map_err(client_err).and_then(|block| { self.client .runtime_api() @@ -310,7 +306,7 @@ where }) } - async fn runtime_version( + fn runtime_version( &self, block: Option, ) -> std::result::Result { @@ -321,7 +317,7 @@ where }) } - async fn query_storage( + fn query_storage( &self, from: Block::Hash, to: Option, @@ -337,16 +333,16 @@ where call_fn() } - async fn query_storage_at( + fn query_storage_at( &self, keys: Vec, at: Option, ) -> std::result::Result>, Error> { let at = at.unwrap_or_else(|| self.client.info().best_hash); - self.query_storage(at, Some(at), keys).await + self.query_storage(at, Some(at), keys) } - async fn read_proof( + fn read_proof( &self, block: Option, keys: Vec, @@ -454,7 +450,7 @@ where self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); } - async fn trace_block( + fn trace_block( &self, block: Block::Hash, targets: Option, @@ -474,7 +470,6 @@ where } } -#[async_trait] impl ChildStateBackend for FullState where Block: BlockT + 'static, @@ -493,7 +488,7 @@ where + 'static, Client::Api: Metadata, { - async fn read_child_proof( + fn read_child_proof( &self, block: Option, storage_key: PrefixedStorageKey, @@ -518,7 +513,7 @@ where .map_err(client_err) } - async fn storage_keys( + fn storage_keys( &self, block: Option, storage_key: PrefixedStorageKey, @@ -536,7 +531,7 @@ where .map_err(client_err) } - async fn storage_keys_paged( + fn storage_keys_paged( &self, block: Option, storage_key: PrefixedStorageKey, @@ -562,7 +557,7 @@ where .map_err(client_err) } - async fn storage( + fn storage( &self, block: Option, storage_key: PrefixedStorageKey, @@ -580,7 +575,7 @@ where .map_err(client_err) } - async fn storage_entries( + fn storage_entries( &self, block: Option, storage_key: PrefixedStorageKey, @@ -595,18 +590,18 @@ where }; let block = self.block_or_best(block).map_err(client_err)?; let client = self.client.clone(); - future::try_join_all(keys.into_iter().map(move |key| { - let res = client - .clone() - .child_storage(&BlockId::Hash(block), &child_info, &key) - .map_err(client_err); - - async move { res } - })) - .await + + keys.into_iter() + .map(move |key| { + client + .clone() + .child_storage(&BlockId::Hash(block), &child_info, &key) + .map_err(client_err) + }) + .collect() } - async fn storage_hash( + fn storage_hash( &self, block: Option, storage_key: PrefixedStorageKey, diff --git a/client/rpc/src/state/tests.rs b/client/rpc/src/state/tests.rs index a375a30d2c1a2..53dd8ebf50499 100644 --- a/client/rpc/src/state/tests.rs +++ b/client/rpc/src/state/tests.rs @@ -61,31 +61,23 @@ async fn should_return_storage() { assert_eq!( client .storage(key.clone(), Some(genesis_hash).into()) - .await .map(|x| x.map(|x| x.0.len())) .unwrap() .unwrap() as usize, VALUE.len(), ); assert_matches!( - client - .storage_hash(key.clone(), Some(genesis_hash).into()) - .await - .map(|x| x.is_some()), + client.storage_hash(key.clone(), Some(genesis_hash).into()).map(|x| x.is_some()), Ok(true) ); + assert_eq!(client.storage_size(key.clone(), None).unwrap().unwrap() as usize, VALUE.len(),); assert_eq!( - client.storage_size(key.clone(), None).await.unwrap().unwrap() as usize, - VALUE.len(), - ); - assert_eq!( - client.storage_size(StorageKey(b":map".to_vec()), None).await.unwrap().unwrap() as usize, + client.storage_size(StorageKey(b":map".to_vec()), None).unwrap().unwrap() as usize, 2 + 3, ); assert_eq!( child .storage(prefixed_storage_key(), key, Some(genesis_hash).into()) - .await .map(|x| x.map(|x| x.0.len())) .unwrap() .unwrap() as usize, @@ -114,7 +106,6 @@ async fn should_return_storage_entries() { assert_eq!( child .storage_entries(prefixed_storage_key(), keys.to_vec(), Some(genesis_hash).into()) - .await .map(|x| x.into_iter().map(|x| x.map(|x| x.0.len()).unwrap()).sum::()) .unwrap(), CHILD_VALUE1.len() + CHILD_VALUE2.len() @@ -126,7 +117,6 @@ async fn should_return_storage_entries() { assert_matches!( child .storage_entries(prefixed_storage_key(), failing_keys, Some(genesis_hash).into()) - .await .map(|x| x.iter().all(|x| x.is_some())), Ok(false) ); @@ -150,17 +140,16 @@ async fn should_return_child_storage() { child_key.clone(), key.clone(), Some(genesis_hash).into(), - ).await, + ), Ok(Some(StorageData(ref d))) if d[0] == 42 && d.len() == 1 ); assert_matches!( child .storage_hash(child_key.clone(), key.clone(), Some(genesis_hash).into(),) - .await .map(|x| x.is_some()), Ok(true) ); - assert_matches!(child.storage_size(child_key.clone(), key.clone(), None).await, Ok(Some(1))); + assert_matches!(child.storage_size(child_key.clone(), key.clone(), None), Ok(Some(1))); } #[tokio::test] @@ -179,7 +168,6 @@ async fn should_return_child_storage_entries() { let res = child .storage_entries(child_key.clone(), keys.clone(), Some(genesis_hash).into()) - .await .unwrap(); assert_matches!( @@ -193,18 +181,12 @@ async fn should_return_child_storage_entries() { if d[0] == 43 && d[1] == 44 && d.len() == 2 ); assert_matches!( - executor::block_on(child.storage_hash( - child_key.clone(), - keys[0].clone(), - Some(genesis_hash).into() - )) - .map(|x| x.is_some()), + child + .storage_hash(child_key.clone(), keys[0].clone(), Some(genesis_hash).into()) + .map(|x| x.is_some()), Ok(true) ); - assert_matches!( - child.storage_size(child_key.clone(), keys[0].clone(), None).await, - Ok(Some(1)) - ); + assert_matches!(child.storage_size(child_key.clone(), keys[0].clone(), None), Ok(Some(1))); } #[tokio::test] @@ -216,9 +198,7 @@ async fn should_call_contract() { use jsonrpsee::{core::Error, types::error::CallError}; assert_matches!( - client - .call("balanceOf".into(), Bytes(vec![1, 2, 3]), Some(genesis_hash).into()) - .await, + client.call("balanceOf".into(), Bytes(vec![1, 2, 3]), Some(genesis_hash).into()), Err(Error::Call(CallError::Failed(_))) ) } @@ -347,7 +327,7 @@ async fn should_query_storage() { let keys = (1..6).map(|k| StorageKey(vec![k])).collect::>(); let result = api.query_storage(keys.clone(), genesis_hash, Some(block1_hash).into()); - assert_eq!(result.await.unwrap(), expected); + assert_eq!(result.unwrap(), expected); // Query all changes let result = api.query_storage(keys.clone(), genesis_hash, None.into()); @@ -360,18 +340,18 @@ async fn should_query_storage() { (StorageKey(vec![5]), Some(StorageData(vec![1]))), ], }); - assert_eq!(result.await.unwrap(), expected); + assert_eq!(result.unwrap(), expected); // Query changes up to block2. let result = api.query_storage(keys.clone(), genesis_hash, Some(block2_hash)); - assert_eq!(result.await.unwrap(), expected); + assert_eq!(result.unwrap(), expected); // Inverted range. let result = api.query_storage(keys.clone(), block1_hash, Some(genesis_hash)); assert_eq!( - result.await.map_err(|e| e.to_string()), + result.map_err(|e| e.to_string()), Err(RpcError::Call(RpcCallError::Custom(ErrorObject::owned( 4001, Error::InvalidBlockRange { @@ -392,7 +372,7 @@ async fn should_query_storage() { let result = api.query_storage(keys.clone(), genesis_hash, Some(random_hash1)); assert_eq!( - result.await.map_err(|e| e.to_string()), + result.map_err(|e| e.to_string()), Err(RpcError::Call(RpcCallError::Custom(ErrorObject::owned( 4001, Error::InvalidBlockRange { @@ -413,7 +393,7 @@ async fn should_query_storage() { let result = api.query_storage(keys.clone(), random_hash1, Some(genesis_hash)); assert_eq!( - result.await.map_err(|e| e.to_string()), + result.map_err(|e| e.to_string()), Err(RpcError::Call(RpcCallError::Custom(ErrorObject::owned( 4001, Error::InvalidBlockRange { @@ -434,7 +414,7 @@ async fn should_query_storage() { let result = api.query_storage(keys.clone(), random_hash1, None); assert_eq!( - result.await.map_err(|e| e.to_string()), + result.map_err(|e| e.to_string()), Err(RpcError::Call(RpcCallError::Custom(ErrorObject::owned( 4001, Error::InvalidBlockRange { @@ -455,7 +435,7 @@ async fn should_query_storage() { let result = api.query_storage(keys.clone(), random_hash1, Some(random_hash2)); assert_eq!( - result.await.map_err(|e| e.to_string()), + result.map_err(|e| e.to_string()), Err(RpcError::Call(RpcCallError::Custom(ErrorObject::owned( 4001, Error::InvalidBlockRange { @@ -476,7 +456,7 @@ async fn should_query_storage() { let result = api.query_storage_at(keys.clone(), Some(block1_hash)); assert_eq!( - result.await.unwrap(), + result.unwrap(), vec![StorageChangeSet { block: block1_hash, changes: vec![ @@ -506,7 +486,7 @@ async fn should_return_runtime_version() { [\"0xf78b278be53f454c\",2],[\"0xab3c0572291feb8b\",1],[\"0xbc9d89904f5b923f\",1]],\ \"transactionVersion\":1,\"stateVersion\":1}"; - let runtime_version = api.runtime_version(None.into()).await.unwrap(); + let runtime_version = api.runtime_version(None.into()).unwrap(); let serialized = serde_json::to_string(&runtime_version).unwrap(); assert_eq!(serialized, result); From 941c5ae16776bfa9d8d9bc0f4e4eca807778c456 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Sat, 21 May 2022 07:24:24 +0200 Subject: [PATCH 255/484] InMemoryBackend: Make it generic over the key hasher (#11488) * InMemoryBackend: Make it generic over the key hasher * Update primitives/state-machine/src/in_memory_backend.rs Co-authored-by: cheme * Update primitives/state-machine/src/in_memory_backend.rs Co-authored-by: cheme * FMT Co-authored-by: cheme --- primitives/state-machine/src/basic.rs | 4 +- .../state-machine/src/in_memory_backend.rs | 56 +++++++++++++------ primitives/state-machine/src/lib.rs | 10 ++-- primitives/trie/src/lib.rs | 3 +- 4 files changed, 47 insertions(+), 26 deletions(-) diff --git a/primitives/state-machine/src/basic.rs b/primitives/state-machine/src/basic.rs index f1c09251faa5e..bb387e6406243 100644 --- a/primitives/state-machine/src/basic.rs +++ b/primitives/state-machine/src/basic.rs @@ -30,7 +30,7 @@ use sp_core::{ Blake2Hasher, }; use sp_externalities::{Extension, Extensions}; -use sp_trie::{empty_child_trie_root, LayoutV0, LayoutV1, TrieConfiguration}; +use sp_trie::{empty_child_trie_root, HashKey, LayoutV0, LayoutV1, TrieConfiguration}; use std::{ any::{Any, TypeId}, collections::BTreeMap, @@ -310,7 +310,7 @@ impl Externalities for BasicExternalities { ) -> Vec { if let Some(child) = self.inner.children_default.get(child_info.storage_key()) { let delta = child.data.iter().map(|(k, v)| (k.as_ref(), Some(v.as_ref()))); - crate::in_memory_backend::new_in_mem::() + crate::in_memory_backend::new_in_mem::>() .child_storage_root(&child.child_info, delta, state_version) .0 } else { diff --git a/primitives/state-machine/src/in_memory_backend.rs b/primitives/state-machine/src/in_memory_backend.rs index 457d89b8c59aa..6df23cdb7096e 100644 --- a/primitives/state-machine/src/in_memory_backend.rs +++ b/primitives/state-machine/src/in_memory_backend.rs @@ -23,22 +23,36 @@ use crate::{ use codec::Codec; use hash_db::Hasher; use sp_core::storage::{ChildInfo, StateVersion, Storage}; -use sp_trie::{empty_trie_root, LayoutV1, MemoryDB}; +use sp_trie::{empty_trie_root, GenericMemoryDB, HashKey, KeyFunction, LayoutV1, MemoryDB}; use std::collections::{BTreeMap, HashMap}; /// Create a new empty instance of in-memory backend. -pub fn new_in_mem() -> TrieBackend, H> +/// +/// It will use [`HashKey`] to store the keys internally. +pub fn new_in_mem_hash_key() -> TrieBackend, H> where + H: Hasher, H::Out: Codec + Ord, { - let db = MemoryDB::default(); + new_in_mem::>() +} + +/// Create a new empty instance of in-memory backend. +pub fn new_in_mem() -> TrieBackend, H> +where + H: Hasher, + H::Out: Codec + Ord, + KF: KeyFunction + Send + Sync, +{ + let db = GenericMemoryDB::default(); // V1 is same as V0 for an empty trie. TrieBackend::new(db, empty_trie_root::>()) } -impl TrieBackend, H> +impl TrieBackend, H> where H::Out: Codec + Ord, + KF: KeyFunction + Send + Sync, { /// Copy the state, with applied updates pub fn update, StorageCollection)>>( @@ -70,14 +84,14 @@ where } /// Merge trie nodes into this backend. - pub fn update_backend(&self, root: H::Out, changes: MemoryDB) -> Self { + pub fn update_backend(&self, root: H::Out, changes: GenericMemoryDB) -> Self { let mut clone = self.backend_storage().clone(); clone.consolidate(changes); Self::new(clone, root) } /// Apply the given transaction to this backend and set the root to the given value. - pub fn apply_transaction(&mut self, root: H::Out, transaction: MemoryDB) { + pub fn apply_transaction(&mut self, root: H::Out, transaction: GenericMemoryDB) { let mut storage = sp_std::mem::take(self).into_storage(); storage.consolidate(transaction); *self = TrieBackend::new(storage, root); @@ -89,28 +103,33 @@ where } } -impl Clone for TrieBackend, H> +impl Clone for TrieBackend, H> where H::Out: Codec + Ord, + KF: KeyFunction + Send + Sync, { fn clone(&self) -> Self { TrieBackend::new(self.backend_storage().clone(), *self.root()) } } -impl Default for TrieBackend, H> +impl Default for TrieBackend, H> where + H: Hasher, H::Out: Codec + Ord, + KF: KeyFunction + Send + Sync, { fn default() -> Self { new_in_mem() } } -impl From<(HashMap, BTreeMap>, StateVersion)> - for TrieBackend, H> +impl + From<(HashMap, BTreeMap>, StateVersion)> + for TrieBackend, H> where H::Out: Codec + Ord, + KF: KeyFunction + Send + Sync, { fn from( (inner, state_version): ( @@ -129,9 +148,10 @@ where } } -impl From<(Storage, StateVersion)> for TrieBackend, H> +impl From<(Storage, StateVersion)> for TrieBackend, H> where H::Out: Codec + Ord, + KF: KeyFunction + Send + Sync, { fn from((inners, state_version): (Storage, StateVersion)) -> Self { let mut inner: HashMap, BTreeMap> = inners @@ -144,10 +164,11 @@ where } } -impl From<(BTreeMap, StateVersion)> - for TrieBackend, H> +impl From<(BTreeMap, StateVersion)> + for TrieBackend, H> where H::Out: Codec + Ord, + KF: KeyFunction + Send + Sync, { fn from((inner, state_version): (BTreeMap, StateVersion)) -> Self { let mut expanded = HashMap::new(); @@ -156,10 +177,11 @@ where } } -impl From<(Vec<(Option, StorageCollection)>, StateVersion)> - for TrieBackend, H> +impl From<(Vec<(Option, StorageCollection)>, StateVersion)> + for TrieBackend, H> where H::Out: Codec + Ord, + KF: KeyFunction + Send + Sync, { fn from( (inner, state_version): (Vec<(Option, StorageCollection)>, StateVersion), @@ -189,7 +211,7 @@ mod tests { #[test] fn in_memory_with_child_trie_only() { let state_version = StateVersion::default(); - let storage = new_in_mem::(); + let storage = new_in_mem_hash_key::(); let child_info = ChildInfo::new_default(b"1"); let child_info = &child_info; let storage = storage.update( @@ -205,7 +227,7 @@ mod tests { #[test] fn insert_multiple_times_child_data_works() { let state_version = StateVersion::default(); - let mut storage = new_in_mem::(); + let mut storage = new_in_mem_hash_key::(); let child_info = ChildInfo::new_default(b"1"); storage.insert( diff --git a/primitives/state-machine/src/lib.rs b/primitives/state-machine/src/lib.rs index 97a9ae8d88cd2..e5b521588aa79 100644 --- a/primitives/state-machine/src/lib.rs +++ b/primitives/state-machine/src/lib.rs @@ -143,7 +143,7 @@ mod std_reexport { pub use crate::{ basic::BasicExternalities, error::{Error, ExecutionError}, - in_memory_backend::new_in_mem, + in_memory_backend::{new_in_mem, new_in_mem_hash_key}, proving_backend::{ create_proof_check_backend, ProofRecorder, ProvingBackend, ProvingBackendRecorder, }, @@ -1347,7 +1347,7 @@ mod execution { #[cfg(test)] mod tests { use super::{ext::Ext, *}; - use crate::execution::CallResult; + use crate::{execution::CallResult, in_memory_backend::new_in_mem_hash_key}; use codec::{Decode, Encode}; use sp_core::{ map, @@ -1687,7 +1687,7 @@ mod tests { fn set_child_storage_works() { let child_info = ChildInfo::new_default(b"sub1"); let child_info = &child_info; - let state = new_in_mem::(); + let state = new_in_mem_hash_key::(); let backend = state.as_trie_backend().unwrap(); let mut overlay = OverlayedChanges::default(); let mut cache = StorageTransactionCache::default(); @@ -1703,7 +1703,7 @@ mod tests { fn append_storage_works() { let reference_data = vec![b"data1".to_vec(), b"2".to_vec(), b"D3".to_vec(), b"d4".to_vec()]; let key = b"key".to_vec(); - let state = new_in_mem::(); + let state = new_in_mem_hash_key::(); let backend = state.as_trie_backend().unwrap(); let mut overlay = OverlayedChanges::default(); let mut cache = StorageTransactionCache::default(); @@ -1740,7 +1740,7 @@ mod tests { let key = b"events".to_vec(); let mut cache = StorageTransactionCache::default(); - let state = new_in_mem::(); + let state = new_in_mem_hash_key::(); let backend = state.as_trie_backend().unwrap(); let mut overlay = OverlayedChanges::default(); diff --git a/primitives/trie/src/lib.rs b/primitives/trie/src/lib.rs index 358cb43ab4e92..1dca617acf912 100644 --- a/primitives/trie/src/lib.rs +++ b/primitives/trie/src/lib.rs @@ -31,9 +31,8 @@ pub use error::Error; /// Various re-exports from the `hash-db` crate. pub use hash_db::{HashDB as HashDBT, EMPTY_PREFIX}; use hash_db::{Hasher, Prefix}; -pub use memory_db::prefixed_key; /// Various re-exports from the `memory-db` crate. -pub use memory_db::KeyFunction; +pub use memory_db::{prefixed_key, HashKey, KeyFunction, PrefixedKey}; /// The Substrate format implementation of `NodeCodec`. pub use node_codec::NodeCodec; use sp_std::{borrow::Borrow, boxed::Box, marker::PhantomData, vec::Vec}; From fe7626bac36143cb2d8197974dbe5aae9467ad37 Mon Sep 17 00:00:00 2001 From: Qinxuan Chen Date: Sat, 21 May 2022 14:13:09 +0800 Subject: [PATCH 256/484] Unify rpc api and implementation name (#11469) * Unify rpc api and implementation name Signed-off-by: koushiro * MauanlSeal ==> ManualSealRpc Signed-off-by: koushiro * Remove extra Rpc naming in the structs Signed-off-by: koushiro * Update doc Signed-off-by: koushiro * fix merge Co-authored-by: Shawn Tabrizi --- bin/node-template/node/src/rpc.rs | 8 ++-- bin/node/rpc/src/lib.rs | 37 ++++++++----------- client/beefy/rpc/src/lib.rs | 23 +++++------- client/consensus/babe/rpc/src/lib.rs | 12 +++--- client/finality-grandpa/rpc/src/lib.rs | 14 +++---- client/rpc/src/state/mod.rs | 8 ++-- client/sync-state-rpc/src/lib.rs | 10 ++--- frame/contracts/rpc/src/lib.rs | 6 +-- frame/merkle-mountain-range/rpc/src/lib.rs | 7 ++-- frame/transaction-payment/rpc/src/lib.rs | 8 ++-- .../rpc/state-trie-migration-rpc/src/lib.rs | 8 ++-- utils/frame/rpc/system/src/lib.rs | 14 +++---- 12 files changed, 72 insertions(+), 83 deletions(-) diff --git a/bin/node-template/node/src/rpc.rs b/bin/node-template/node/src/rpc.rs index 7edae4d81474f..981f375d0b462 100644 --- a/bin/node-template/node/src/rpc.rs +++ b/bin/node-template/node/src/rpc.rs @@ -39,14 +39,14 @@ where C::Api: BlockBuilder, P: TransactionPool + 'static, { - use pallet_transaction_payment_rpc::{TransactionPaymentApiServer, TransactionPaymentRpc}; - use substrate_frame_rpc_system::{SystemApiServer, SystemRpc}; + use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApiServer}; + use substrate_frame_rpc_system::{System, SystemApiServer}; let mut module = RpcModule::new(()); let FullDeps { client, pool, deny_unsafe } = deps; - module.merge(SystemRpc::new(client.clone(), pool.clone(), deny_unsafe).into_rpc())?; - module.merge(TransactionPaymentRpc::new(client).into_rpc())?; + module.merge(System::new(client.clone(), pool.clone(), deny_unsafe).into_rpc())?; + module.merge(TransactionPayment::new(client).into_rpc())?; // Extend this RPC with a custom API by using the following syntax. // `YourRpcStruct` should have a reference to a client, which is needed diff --git a/bin/node/rpc/src/lib.rs b/bin/node/rpc/src/lib.rs index 05aa973e102b1..e5b666195e1bc 100644 --- a/bin/node/rpc/src/lib.rs +++ b/bin/node/rpc/src/lib.rs @@ -37,12 +37,10 @@ use jsonrpsee::RpcModule; use node_primitives::{AccountId, Balance, Block, BlockNumber, Hash, Index}; use sc_client_api::AuxStore; use sc_consensus_babe::{Config, Epoch}; -use sc_consensus_babe_rpc::BabeRpc; use sc_consensus_epochs::SharedEpochChanges; use sc_finality_grandpa::{ FinalityProofProvider, GrandpaJustificationStream, SharedAuthoritySet, SharedVoterState, }; -use sc_finality_grandpa_rpc::GrandpaRpc; use sc_rpc::SubscriptionTaskExecutor; pub use sc_rpc_api::DenyUnsafe; use sc_transaction_pool_api::TransactionPool; @@ -120,15 +118,15 @@ where B: sc_client_api::Backend + Send + Sync + 'static, B::State: sc_client_api::backend::StateBackend>, { - use pallet_contracts_rpc::{ContractsApiServer, ContractsRpc}; - use pallet_mmr_rpc::{MmrApiServer, MmrRpc}; - use pallet_transaction_payment_rpc::{TransactionPaymentApiServer, TransactionPaymentRpc}; - use sc_consensus_babe_rpc::BabeApiServer; - use sc_finality_grandpa_rpc::GrandpaApiServer; + use pallet_contracts_rpc::{Contracts, ContractsApiServer}; + use pallet_mmr_rpc::{Mmr, MmrApiServer}; + use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApiServer}; + use sc_consensus_babe_rpc::{Babe, BabeApiServer}; + use sc_finality_grandpa_rpc::{Grandpa, GrandpaApiServer}; use sc_rpc::dev::{Dev, DevApiServer}; - use sc_sync_state_rpc::{SyncStateRpc, SyncStateRpcApiServer}; - use substrate_frame_rpc_system::{SystemApiServer, SystemRpc}; - use substrate_state_trie_migration_rpc::StateMigrationApiServer; + use sc_sync_state_rpc::{SyncState, SyncStateApiServer}; + use substrate_frame_rpc_system::{System, SystemApiServer}; + use substrate_state_trie_migration_rpc::{StateMigration, StateMigrationApiServer}; let mut io = RpcModule::new(()); let FullDeps { client, pool, select_chain, chain_spec, deny_unsafe, babe, grandpa } = deps; @@ -142,15 +140,15 @@ where finality_provider, } = grandpa; - io.merge(SystemRpc::new(client.clone(), pool, deny_unsafe).into_rpc())?; + io.merge(System::new(client.clone(), pool, deny_unsafe).into_rpc())?; // Making synchronous calls in light client freezes the browser currently, // more context: https://github.com/paritytech/substrate/pull/3480 // These RPCs should use an asynchronous caller instead. - io.merge(ContractsRpc::new(client.clone()).into_rpc())?; - io.merge(MmrRpc::new(client.clone()).into_rpc())?; - io.merge(TransactionPaymentRpc::new(client.clone()).into_rpc())?; + io.merge(Contracts::new(client.clone()).into_rpc())?; + io.merge(Mmr::new(client.clone()).into_rpc())?; + io.merge(TransactionPayment::new(client.clone()).into_rpc())?; io.merge( - BabeRpc::new( + Babe::new( client.clone(), shared_epoch_changes.clone(), keystore, @@ -161,7 +159,7 @@ where .into_rpc(), )?; io.merge( - GrandpaRpc::new( + Grandpa::new( subscription_executor, shared_authority_set.clone(), shared_voter_state, @@ -172,14 +170,11 @@ where )?; io.merge( - SyncStateRpc::new(chain_spec, client.clone(), shared_authority_set, shared_epoch_changes)? + SyncState::new(chain_spec, client.clone(), shared_authority_set, shared_epoch_changes)? .into_rpc(), )?; - io.merge( - substrate_state_trie_migration_rpc::MigrationRpc::new(client.clone(), backend, deny_unsafe) - .into_rpc(), - )?; + io.merge(StateMigration::new(client.clone(), backend, deny_unsafe).into_rpc())?; io.merge(Dev::new(client, deny_unsafe).into_rpc())?; Ok(io) diff --git a/client/beefy/rpc/src/lib.rs b/client/beefy/rpc/src/lib.rs index ea35678a48b8f..c248d33cb6c23 100644 --- a/client/beefy/rpc/src/lib.rs +++ b/client/beefy/rpc/src/lib.rs @@ -100,17 +100,17 @@ pub trait BeefyApi { } /// Implements the BeefyApi RPC trait for interacting with BEEFY. -pub struct BeefyRpcHandler { +pub struct Beefy { signed_commitment_stream: BeefySignedCommitmentStream, beefy_best_block: Arc>>, executor: SubscriptionTaskExecutor, } -impl BeefyRpcHandler +impl Beefy where Block: BlockT, { - /// Creates a new BeefyRpcHandler instance. + /// Creates a new Beefy Rpc handler instance. pub fn new( signed_commitment_stream: BeefySignedCommitmentStream, best_block_stream: BeefyBestBlockStream, @@ -131,8 +131,7 @@ where } #[async_trait] -impl BeefyApiServer - for BeefyRpcHandler +impl BeefyApiServer for Beefy where Block: BlockT, { @@ -174,24 +173,20 @@ mod tests { use sp_runtime::traits::{BlakeTwo256, Hash}; use substrate_test_runtime_client::runtime::Block; - fn setup_io_handler() -> (RpcModule>, BeefySignedCommitmentSender) - { + fn setup_io_handler() -> (RpcModule>, BeefySignedCommitmentSender) { let (_, stream) = BeefyBestBlockStream::::channel(); setup_io_handler_with_best_block_stream(stream) } fn setup_io_handler_with_best_block_stream( best_block_stream: BeefyBestBlockStream, - ) -> (RpcModule>, BeefySignedCommitmentSender) { + ) -> (RpcModule>, BeefySignedCommitmentSender) { let (commitment_sender, commitment_stream) = BeefySignedCommitmentStream::::channel(); - let handler = BeefyRpcHandler::new( - commitment_stream, - best_block_stream, - sc_rpc::testing::test_executor(), - ) - .expect("Setting up the BEEFY RPC handler works"); + let handler = + Beefy::new(commitment_stream, best_block_stream, sc_rpc::testing::test_executor()) + .expect("Setting up the BEEFY RPC handler works"); (handler.into_rpc(), commitment_sender) } diff --git a/client/consensus/babe/rpc/src/lib.rs b/client/consensus/babe/rpc/src/lib.rs index d5f21606c62ed..af19d410346e3 100644 --- a/client/consensus/babe/rpc/src/lib.rs +++ b/client/consensus/babe/rpc/src/lib.rs @@ -49,7 +49,7 @@ pub trait BabeApi { } /// Provides RPC methods for interacting with Babe. -pub struct BabeRpc { +pub struct Babe { /// shared reference to the client. client: Arc, /// shared reference to EpochChanges @@ -64,8 +64,8 @@ pub struct BabeRpc { deny_unsafe: DenyUnsafe, } -impl BabeRpc { - /// Creates a new instance of the BabeRpc handler. +impl Babe { + /// Creates a new instance of the Babe Rpc handler. pub fn new( client: Arc, shared_epoch_changes: SharedEpochChanges, @@ -79,7 +79,7 @@ impl BabeRpc { } #[async_trait] -impl BabeApiServer for BabeRpc +impl BabeApiServer for Babe where B: BlockT, C: ProvideRuntimeApi @@ -239,7 +239,7 @@ mod tests { fn test_babe_rpc_module( deny_unsafe: DenyUnsafe, - ) -> BabeRpc> { + ) -> Babe> { let builder = TestClientBuilder::new(); let (client, longest_chain) = builder.build_with_longest_chain(); let client = Arc::new(client); @@ -250,7 +250,7 @@ mod tests { let epoch_changes = link.epoch_changes().clone(); let keystore = create_temp_keystore::(Sr25519Keyring::Alice).0; - BabeRpc::new(client.clone(), epoch_changes, keystore, config, longest_chain, deny_unsafe) + Babe::new(client.clone(), epoch_changes, keystore, config, longest_chain, deny_unsafe) } #[tokio::test] diff --git a/client/finality-grandpa/rpc/src/lib.rs b/client/finality-grandpa/rpc/src/lib.rs index cb51d71b20bf4..bdb86c125e20a 100644 --- a/client/finality-grandpa/rpc/src/lib.rs +++ b/client/finality-grandpa/rpc/src/lib.rs @@ -66,7 +66,7 @@ pub trait GrandpaApi { } /// Provides RPC methods for interacting with GRANDPA. -pub struct GrandpaRpc { +pub struct Grandpa { executor: SubscriptionTaskExecutor, authority_set: AuthoritySet, voter_state: VoterState, @@ -74,9 +74,9 @@ pub struct GrandpaRpc { finality_proof_provider: Arc, } impl - GrandpaRpc + Grandpa { - /// Prepare a new [`GrandpaRpc`] + /// Prepare a new [`Grandpa`] Rpc handler. pub fn new( executor: SubscriptionTaskExecutor, authority_set: AuthoritySet, @@ -91,7 +91,7 @@ impl #[async_trait] impl GrandpaApiServer> - for GrandpaRpc + for Grandpa where VoterState: ReportVoterState + Send + Sync + 'static, AuthoritySet: ReportAuthoritySet + Send + Sync + 'static, @@ -243,7 +243,7 @@ mod tests { fn setup_io_handler( voter_state: VoterState, ) -> ( - RpcModule>, + RpcModule>, GrandpaJustificationSender, ) where @@ -256,7 +256,7 @@ mod tests { voter_state: VoterState, finality_proof: Option>, ) -> ( - RpcModule>, + RpcModule>, GrandpaJustificationSender, ) where @@ -266,7 +266,7 @@ mod tests { let finality_proof_provider = Arc::new(TestFinalityProofProvider { finality_proof }); let executor = Arc::new(TaskExecutor::default()); - let rpc = GrandpaRpc::new( + let rpc = Grandpa::new( executor, TestAuthoritySet, voter_state, diff --git a/client/rpc/src/state/mod.rs b/client/rpc/src/state/mod.rs index 4b5b74950a486..232be4edc8aab 100644 --- a/client/rpc/src/state/mod.rs +++ b/client/rpc/src/state/mod.rs @@ -167,7 +167,7 @@ pub fn new_full( executor: SubscriptionTaskExecutor, deny_unsafe: DenyUnsafe, rpc_max_payload: Option, -) -> (StateApi, ChildState) +) -> (State, ChildState) where Block: BlockT + 'static, Block::Hash: Unpin, @@ -192,17 +192,17 @@ where rpc_max_payload, )); let backend = Box::new(self::state_full::FullState::new(client, executor, rpc_max_payload)); - (StateApi { backend, deny_unsafe }, ChildState { backend: child_backend }) + (State { backend, deny_unsafe }, ChildState { backend: child_backend }) } /// State API with subscriptions support. -pub struct StateApi { +pub struct State { backend: Box>, /// Whether to deny unsafe calls deny_unsafe: DenyUnsafe, } -impl StateApiServer for StateApi +impl StateApiServer for State where Block: BlockT + 'static, Client: Send + Sync + 'static, diff --git a/client/sync-state-rpc/src/lib.rs b/client/sync-state-rpc/src/lib.rs index 02a22a838b8b2..9540d94c57918 100644 --- a/client/sync-state-rpc/src/lib.rs +++ b/client/sync-state-rpc/src/lib.rs @@ -37,7 +37,7 @@ //! ``` //! //! If the [`LightSyncStateExtension`] is not added as an extension to the chain spec, -//! the [`SyncStateRpc`] will fail at instantiation. +//! the [`SyncState`] will fail at instantiation. #![deny(unused_crate_dependencies)] @@ -125,21 +125,21 @@ pub struct LightSyncState { /// An api for sync state RPC calls. #[rpc(client, server)] -pub trait SyncStateRpcApi { +pub trait SyncStateApi { /// Returns the JSON serialized chainspec running the node, with a sync state. #[method(name = "sync_state_genSyncSpec")] fn system_gen_sync_spec(&self, raw: bool) -> RpcResult; } /// An api for sync state RPC calls. -pub struct SyncStateRpc { +pub struct SyncState { chain_spec: Box, client: Arc, shared_authority_set: SharedAuthoritySet, shared_epoch_changes: SharedEpochChanges, } -impl SyncStateRpc +impl SyncState where Block: BlockT, Client: HeaderBackend + sc_client_api::AuxStore + 'static, @@ -180,7 +180,7 @@ where } } -impl SyncStateRpcApiServer for SyncStateRpc +impl SyncStateApiServer for SyncState where Block: BlockT, Backend: HeaderBackend + sc_client_api::AuxStore + 'static, diff --git a/frame/contracts/rpc/src/lib.rs b/frame/contracts/rpc/src/lib.rs index 599e80676cb19..77ae3f3ed35e3 100644 --- a/frame/contracts/rpc/src/lib.rs +++ b/frame/contracts/rpc/src/lib.rs @@ -173,12 +173,12 @@ where } /// Contracts RPC methods. -pub struct ContractsRpc { +pub struct Contracts { client: Arc, _marker: PhantomData, } -impl ContractsRpc { +impl Contracts { /// Create new `Contracts` with the given reference to the client. pub fn new(client: Arc) -> Self { Self { client, _marker: Default::default() } @@ -193,7 +193,7 @@ impl AccountId, Balance, Hash, - > for ContractsRpc + > for Contracts where Block: BlockT, Client: Send + Sync + 'static + ProvideRuntimeApi + HeaderBackend, diff --git a/frame/merkle-mountain-range/rpc/src/lib.rs b/frame/merkle-mountain-range/rpc/src/lib.rs index 12e4e11f88256..75032d40f492a 100644 --- a/frame/merkle-mountain-range/rpc/src/lib.rs +++ b/frame/merkle-mountain-range/rpc/src/lib.rs @@ -131,12 +131,12 @@ pub trait MmrApi { } /// MMR RPC methods. -pub struct MmrRpc { +pub struct Mmr { client: Arc, _marker: PhantomData, } -impl MmrRpc { +impl Mmr { /// Create new `Mmr` with the given reference to the client. pub fn new(client: Arc) -> Self { Self { client, _marker: Default::default() } @@ -144,8 +144,7 @@ impl MmrRpc { } #[async_trait] -impl MmrApiServer<::Hash> - for MmrRpc +impl MmrApiServer<::Hash> for Mmr where Block: BlockT, Client: Send + Sync + 'static + ProvideRuntimeApi + HeaderBackend, diff --git a/frame/transaction-payment/rpc/src/lib.rs b/frame/transaction-payment/rpc/src/lib.rs index b0be19fdb22a9..75ec42321ef5e 100644 --- a/frame/transaction-payment/rpc/src/lib.rs +++ b/frame/transaction-payment/rpc/src/lib.rs @@ -51,14 +51,14 @@ pub trait TransactionPaymentApi { } /// Provides RPC methods to query a dispatchable's class, weight and fee. -pub struct TransactionPaymentRpc { +pub struct TransactionPayment { /// Shared reference to the client. client: Arc, _marker: std::marker::PhantomData

, } -impl TransactionPaymentRpc { - /// Creates a new instance of the TransactionPaymentRpc helper. +impl TransactionPayment { + /// Creates a new instance of the TransactionPayment Rpc helper. pub fn new(client: Arc) -> Self { Self { client, _marker: Default::default() } } @@ -84,7 +84,7 @@ impl From for i32 { #[async_trait] impl TransactionPaymentApiServer<::Hash, RuntimeDispatchInfo> - for TransactionPaymentRpc + for TransactionPayment where Block: BlockT, C: ProvideRuntimeApi + HeaderBackend + Send + Sync + 'static, diff --git a/utils/frame/rpc/state-trie-migration-rpc/src/lib.rs b/utils/frame/rpc/state-trie-migration-rpc/src/lib.rs index 531bf463f6523..b6d403ff2fcfd 100644 --- a/utils/frame/rpc/state-trie-migration-rpc/src/lib.rs +++ b/utils/frame/rpc/state-trie-migration-rpc/src/lib.rs @@ -122,21 +122,21 @@ pub trait StateMigrationApi { } /// An implementation of state migration specific RPC methods. -pub struct MigrationRpc { +pub struct StateMigration { client: Arc, backend: Arc, deny_unsafe: DenyUnsafe, _marker: std::marker::PhantomData<(B, BA)>, } -impl MigrationRpc { +impl StateMigration { /// Create new state migration rpc for the given reference to the client. pub fn new(client: Arc, backend: Arc, deny_unsafe: DenyUnsafe) -> Self { - MigrationRpc { client, backend, deny_unsafe, _marker: Default::default() } + StateMigration { client, backend, deny_unsafe, _marker: Default::default() } } } -impl StateMigrationApiServer<::Hash> for MigrationRpc +impl StateMigrationApiServer<::Hash> for StateMigration where B: BlockT, C: Send + Sync + 'static + sc_client_api::HeaderBackend, diff --git a/utils/frame/rpc/system/src/lib.rs b/utils/frame/rpc/system/src/lib.rs index b044035c8120e..72ad99e435f72 100644 --- a/utils/frame/rpc/system/src/lib.rs +++ b/utils/frame/rpc/system/src/lib.rs @@ -70,14 +70,14 @@ impl From for i32 { } /// An implementation of System-specific RPC methods on full client. -pub struct SystemRpc { +pub struct System { client: Arc, pool: Arc

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

, deny_unsafe: DenyUnsafe) -> Self { Self { client, pool, deny_unsafe, _marker: Default::default() } @@ -86,7 +86,7 @@ impl SystemRpc { #[async_trait] impl - SystemApiServer<::Hash, AccountId, Index> for SystemRpc + SystemApiServer<::Hash, AccountId, Index> for System where C: sp_api::ProvideRuntimeApi, C: HeaderBackend, @@ -251,7 +251,7 @@ mod tests { let ext1 = new_transaction(1); block_on(pool.submit_one(&BlockId::number(0), source, ext1)).unwrap(); - let accounts = SystemRpc::new(client, pool, DenyUnsafe::Yes); + let accounts = System::new(client, pool, DenyUnsafe::Yes); // when let nonce = accounts.nonce(AccountKeyring::Alice.into()).await; @@ -270,7 +270,7 @@ mod tests { let pool = BasicPool::new_full(Default::default(), true.into(), None, spawner, client.clone()); - let accounts = SystemRpc::new(client, pool, DenyUnsafe::Yes); + let accounts = System::new(client, pool, DenyUnsafe::Yes); // when let res = accounts.dry_run(vec![].into(), None).await; @@ -289,7 +289,7 @@ mod tests { let pool = BasicPool::new_full(Default::default(), true.into(), None, spawner, client.clone()); - let accounts = SystemRpc::new(client, pool, DenyUnsafe::No); + let accounts = System::new(client, pool, DenyUnsafe::No); let tx = Transfer { from: AccountKeyring::Alice.into(), @@ -317,7 +317,7 @@ mod tests { let pool = BasicPool::new_full(Default::default(), true.into(), None, spawner, client.clone()); - let accounts = SystemRpc::new(client, pool, DenyUnsafe::No); + let accounts = System::new(client, pool, DenyUnsafe::No); let tx = Transfer { from: AccountKeyring::Alice.into(), From a65838f71e5b98dd3c6bcc04eacac0b29b650f02 Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Sat, 21 May 2022 10:45:57 +0200 Subject: [PATCH 257/484] rpc servers CLI: add `--max--subscriptions--per--connection` + fix a few bugs (#11461) * cli: fix RPC CLI nits * remove needless lines * cargo fmt * Update client/service/src/lib.rs Co-authored-by: James Wilson Co-authored-by: James Wilson --- client/cli/src/commands/run_cmd.rs | 17 +++++++++++++++++ client/cli/src/config.rs | 7 ++++++- client/service/src/lib.rs | 19 +++++++++++++------ 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/client/cli/src/commands/run_cmd.rs b/client/cli/src/commands/run_cmd.rs index 6cb0de0ebd04c..b4a3885e39906 100644 --- a/client/cli/src/commands/run_cmd.rs +++ b/client/cli/src/commands/run_cmd.rs @@ -115,6 +115,11 @@ pub struct RunCmd { #[clap(long)] pub rpc_max_response_size: Option, + /// Set the the maximum concurrent subscriptions per connection. + /// Default is 1024. + #[clap(long)] + pub rpc_max_subscriptions_per_connection: Option, + /// Expose Prometheus exporter on all interfaces. /// /// Default is local. @@ -459,6 +464,18 @@ impl CliConfiguration for RunCmd { Ok(self.rpc_max_payload) } + fn rpc_max_request_size(&self) -> Result> { + Ok(self.rpc_max_request_size) + } + + fn rpc_max_response_size(&self) -> Result> { + Ok(self.rpc_max_response_size) + } + + fn rpc_max_subscriptions_per_connection(&self) -> Result> { + Ok(self.rpc_max_subscriptions_per_connection) + } + fn ws_max_out_buffer_capacity(&self) -> Result> { Ok(self.ws_max_out_buffer_capacity) } diff --git a/client/cli/src/config.rs b/client/cli/src/config.rs index e38d34b92c74d..5e91cf6c74dae 100644 --- a/client/cli/src/config.rs +++ b/client/cli/src/config.rs @@ -369,6 +369,11 @@ pub trait CliConfiguration: Sized { Ok(None) } + /// Get maximum number of subscriptions per connection. + fn rpc_max_subscriptions_per_connection(&self) -> Result> { + Ok(None) + } + /// Get maximum WS output buffer capacity. fn ws_max_out_buffer_capacity(&self) -> Result> { Ok(None) @@ -539,7 +544,7 @@ pub trait CliConfiguration: Sized { rpc_max_request_size: self.rpc_max_request_size()?, rpc_max_response_size: self.rpc_max_response_size()?, rpc_id_provider: None, - rpc_max_subs_per_conn: None, + rpc_max_subs_per_conn: self.rpc_max_subscriptions_per_connection()?, ws_max_out_buffer_capacity: self.ws_max_out_buffer_capacity()?, prometheus_config: self .prometheus_config(DCV::prometheus_listen_port(), &chain_spec)?, diff --git a/client/service/src/lib.rs b/client/service/src/lib.rs index 027b704789635..24ba670cfcd65 100644 --- a/client/service/src/lib.rs +++ b/client/service/src/lib.rs @@ -480,11 +480,18 @@ where } fn legacy_cli_parsing(config: &Configuration) -> (Option, Option, Option) { - let ws_max_response_size = config.ws_max_out_buffer_capacity.map(|max| { - eprintln!("DEPRECATED: `--ws_max_out_buffer_capacity` has been removed use `rpc-max-response-size or rpc-max-request-size` instead"); - eprintln!("Setting WS `rpc-max-response-size` to `max(ws_max_out_buffer_capacity, rpc_max_response_size)`"); - std::cmp::max(max, config.rpc_max_response_size.unwrap_or(0)) - }); + let ws_max_response_size = match ( + config.ws_max_out_buffer_capacity, + config.rpc_max_response_size, + ) { + (Some(legacy_max), max) => { + eprintln!("DEPRECATED: `--ws_max_out_buffer_capacity` has been removed; use `rpc-max-response-size or rpc-max-request-size` instead"); + eprintln!("Setting WS `rpc-max-response-size` to `max(ws_max_out_buffer_capacity, rpc_max_response_size)`"); + Some(std::cmp::max(legacy_max, max.unwrap_or(0))) + }, + (None, Some(m)) => Some(m), + (None, None) => None, + }; let max_request_size = match (config.rpc_max_payload, config.rpc_max_request_size) { (Some(legacy_max), max) => { @@ -498,7 +505,7 @@ fn legacy_cli_parsing(config: &Configuration) -> (Option, Option, (None, None) => None, }; - let http_max_response_size = match (config.rpc_max_payload, config.rpc_max_request_size) { + let http_max_response_size = match (config.rpc_max_payload, config.rpc_max_response_size) { (Some(legacy_max), max) => { eprintln!("DEPRECATED: `--rpc_max_payload` has been removed use `rpc-max-response-size or rpc-max-request-size` instead"); eprintln!( From d0d334cc67942ad352089e31c1db1dc524efeac1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Sun, 22 May 2022 09:19:07 +0200 Subject: [PATCH 258/484] Make `--dev` listen by default on `/ip4/0.0.0.0/tcp/30333` (#11492) If `--validator` is passed we also listen on this address and as `--dev` is a shortcut for multiple CLI args, including `--validator`, we should make it consistent. --- client/cli/src/params/network_params.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/cli/src/params/network_params.rs b/client/cli/src/params/network_params.rs index ac5039fb89754..74c2db92c3215 100644 --- a/client/cli/src/params/network_params.rs +++ b/client/cli/src/params/network_params.rs @@ -163,7 +163,7 @@ impl NetworkParams { let port = self.port.unwrap_or(default_listen_port); let listen_addresses = if self.listen_addr.is_empty() { - if is_validator { + if is_validator || is_dev { vec![ Multiaddr::empty() .with(Protocol::Ip6([0, 0, 0, 0, 0, 0, 0, 0].into())) From 085698d0c587d0980932b4388f69997464555827 Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Mon, 23 May 2022 06:10:23 -0400 Subject: [PATCH 259/484] Fix State Trie Migration Benchmarks (#11502) * enable signed migrations in benchmarks * T instead of Test Signed-off-by: Oliver Tale-Yazdi * Remove 'mut' Signed-off-by: Oliver Tale-Yazdi Co-authored-by: Oliver Tale-Yazdi --- frame/state-trie-migration/src/lib.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/frame/state-trie-migration/src/lib.rs b/frame/state-trie-migration/src/lib.rs index d78fd7d9ca932..899250cc3f3c3 100644 --- a/frame/state-trie-migration/src/lib.rs +++ b/frame/state-trie-migration/src/lib.rs @@ -848,6 +848,8 @@ mod benchmarks { // function. let null = MigrationLimits::default(); let caller = frame_benchmarking::whitelisted_caller(); + // Allow signed migrations. + SignedMigrationMaxLimits::::put(MigrationLimits { size: 1024, item: 5 }); }: _(frame_system::RawOrigin::Signed(caller), null, 0, StateTrieMigration::::migration_process()) verify { assert_eq!(StateTrieMigration::::migration_process(), Default::default()) @@ -1146,14 +1148,7 @@ mod mock { } sp_tracing::try_init_simple(); - let mut ext: sp_io::TestExternalities = (custom_storage, version).into(); - - // set some genesis values for this pallet as well. - ext.execute_with(|| { - SignedMigrationMaxLimits::::put(MigrationLimits { size: 1024, item: 5 }); - }); - - ext + (custom_storage, version).into() } pub(crate) fn run_to_block(n: u32) -> (H256, u64) { @@ -1292,6 +1287,9 @@ mod test { new_test_ext(StateVersion::V0, true, None, None).execute_with(|| { assert_eq!(MigrationProcess::::get(), Default::default()); + // Allow signed migrations. + SignedMigrationMaxLimits::::put(MigrationLimits { size: 1024, item: 5 }); + // can't submit if limit is too high. frame_support::assert_err!( StateTrieMigration::continue_migrate( From 489a5958de54e719b7ac497056880f99ebd517ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulo=20Silva=20de=20Souza?= <77391175+joao-paulo-parity@users.noreply.github.com> Date: Mon, 23 May 2022 10:14:20 -0300 Subject: [PATCH 260/484] Use API for pr-custom-review (#11487) * use API for pr-custom-review * bump action tag * temporary: disable draft skip * temporary: use staging * try it with the prod instance * revert draft skip --- .github/workflows/pr-custom-review.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-custom-review.yml b/.github/workflows/pr-custom-review.yml index 6cb16d931d6f4..8e40c9ee72989 100644 --- a/.github/workflows/pr-custom-review.yml +++ b/.github/workflows/pr-custom-review.yml @@ -37,6 +37,6 @@ jobs: if: github.event.pull_request.draft == true run: exit 1 - name: pr-custom-review - uses: paritytech/pr-custom-review@v2 + uses: paritytech/pr-custom-review@action-v3 with: - token: ${{ secrets.PRCR_TOKEN }} + checks-reviews-api: http://pcr.parity-prod.parity.io/api/v1/check_reviews From 1723c4307856819e12bddafacf5312f0dda0318e Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Mon, 23 May 2022 17:47:36 +0100 Subject: [PATCH 261/484] Introduce #[pallet::call_index] attribute to dispatchables (#11381) * Introduce #[pallet::call_index] attribute to dispatchables * cargo fmt * Add more docs and prevent duplicates of call indices * Add UI test for conflicting call indices * cargo fmt Co-authored-by: parity-processbot <> --- .../procedural/src/pallet/expand/call.rs | 2 + .../procedural/src/pallet/parse/call.rs | 92 ++++++++++++++++--- frame/support/src/lib.rs | 19 +++- .../pallet_ui/call_conflicting_indices.rs | 24 +++++ .../pallet_ui/call_conflicting_indices.stderr | 11 +++ .../test/tests/pallet_ui/call_invalid_attr.rs | 20 ++++ .../tests/pallet_ui/call_invalid_attr.stderr | 5 + .../tests/pallet_ui/call_invalid_index.rs | 21 +++++ .../tests/pallet_ui/call_invalid_index.stderr | 5 + .../pallet_ui/call_multiple_call_index.rs | 22 +++++ .../pallet_ui/call_multiple_call_index.stderr | 5 + 11 files changed, 208 insertions(+), 18 deletions(-) create mode 100644 frame/support/test/tests/pallet_ui/call_conflicting_indices.rs create mode 100644 frame/support/test/tests/pallet_ui/call_conflicting_indices.stderr create mode 100644 frame/support/test/tests/pallet_ui/call_invalid_attr.rs create mode 100644 frame/support/test/tests/pallet_ui/call_invalid_attr.stderr create mode 100644 frame/support/test/tests/pallet_ui/call_invalid_index.rs create mode 100644 frame/support/test/tests/pallet_ui/call_invalid_index.stderr create mode 100644 frame/support/test/tests/pallet_ui/call_multiple_call_index.rs create mode 100644 frame/support/test/tests/pallet_ui/call_multiple_call_index.stderr diff --git a/frame/support/procedural/src/pallet/expand/call.rs b/frame/support/procedural/src/pallet/expand/call.rs index 60546e1a96779..ac52463b8aab6 100644 --- a/frame/support/procedural/src/pallet/expand/call.rs +++ b/frame/support/procedural/src/pallet/expand/call.rs @@ -42,6 +42,7 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { let pallet_ident = &def.pallet_struct.pallet; let fn_name = methods.iter().map(|method| &method.name).collect::>(); + let call_index = methods.iter().map(|method| method.call_index).collect::>(); let new_call_variant_fn_name = fn_name .iter() .map(|fn_name| quote::format_ident!("new_call_variant_{}", fn_name)) @@ -177,6 +178,7 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { ), #( #( #[doc = #fn_doc] )* + #[codec(index = #call_index)] #fn_name { #( #[allow(missing_docs)] diff --git a/frame/support/procedural/src/pallet/parse/call.rs b/frame/support/procedural/src/pallet/parse/call.rs index f532d339f1205..75c85474dcfe7 100644 --- a/frame/support/procedural/src/pallet/parse/call.rs +++ b/frame/support/procedural/src/pallet/parse/call.rs @@ -18,6 +18,7 @@ use super::helper; use frame_support_procedural_tools::get_doc_literals; use quote::ToTokens; +use std::collections::HashMap; use syn::spanned::Spanned; /// List of additional token to be used for parsing. @@ -25,6 +26,7 @@ mod keyword { syn::custom_keyword!(Call); syn::custom_keyword!(OriginFor); syn::custom_keyword!(weight); + syn::custom_keyword!(call_index); syn::custom_keyword!(compact); syn::custom_keyword!(T); syn::custom_keyword!(pallet); @@ -55,14 +57,17 @@ pub struct CallVariantDef { pub args: Vec<(bool, syn::Ident, Box)>, /// Weight formula. pub weight: syn::Expr, + /// Call index of the dispatchable. + pub call_index: u8, /// Docs, used for metadata. pub docs: Vec, } /// Attributes for functions in call impl block. -/// Parse for `#[pallet::weight(expr)]` -pub struct FunctionAttr { - weight: syn::Expr, +/// Parse for `#[pallet::weight(expr)]` or `#[pallet::call_index(expr)] +pub enum FunctionAttr { + CallIndex(u8), + Weight(syn::Expr), } impl syn::parse::Parse for FunctionAttr { @@ -72,11 +77,22 @@ impl syn::parse::Parse for FunctionAttr { syn::bracketed!(content in input); content.parse::()?; content.parse::()?; - content.parse::()?; - let weight_content; - syn::parenthesized!(weight_content in content); - Ok(FunctionAttr { weight: weight_content.parse::()? }) + let lookahead = content.lookahead1(); + if lookahead.peek(keyword::weight) { + content.parse::()?; + let weight_content; + syn::parenthesized!(weight_content in content); + Ok(FunctionAttr::Weight(weight_content.parse::()?)) + } else if lookahead.peek(keyword::call_index) { + content.parse::()?; + let call_index_content; + syn::parenthesized!(call_index_content in content); + let index = call_index_content.parse::()?; + Ok(FunctionAttr::CallIndex(index.base10_parse()?)) + } else { + Err(lookahead.error()) + } } } @@ -145,6 +161,8 @@ impl CallDef { } let mut methods = vec![]; + let mut indices = HashMap::new(); + let mut last_index: Option = None; for impl_item in &mut item.items { if let syn::ImplItem::Method(method) = impl_item { if !matches!(method.vis, syn::Visibility::Public(_)) { @@ -182,18 +200,58 @@ impl CallDef { return Err(syn::Error::new(method.sig.span(), msg)) } - let mut call_var_attrs: Vec = - helper::take_item_pallet_attrs(&mut method.attrs)?; - - if call_var_attrs.len() != 1 { - let msg = if call_var_attrs.is_empty() { + let (mut weight_attrs, mut call_idx_attrs): (Vec, Vec) = + helper::take_item_pallet_attrs(&mut method.attrs)?.into_iter().partition( + |attr| { + if let FunctionAttr::Weight(_) = attr { + true + } else { + false + } + }, + ); + + if weight_attrs.len() != 1 { + let msg = if weight_attrs.is_empty() { "Invalid pallet::call, requires weight attribute i.e. `#[pallet::weight($expr)]`" } else { "Invalid pallet::call, too many weight attributes given" }; return Err(syn::Error::new(method.sig.span(), msg)) } - let weight = call_var_attrs.pop().unwrap().weight; + let weight = match weight_attrs.pop().unwrap() { + FunctionAttr::Weight(w) => w, + _ => unreachable!("checked during creation of the let binding"), + }; + + if call_idx_attrs.len() > 1 { + let msg = "Invalid pallet::call, too many call_index attributes given"; + return Err(syn::Error::new(method.sig.span(), msg)) + } + let call_index = call_idx_attrs.pop().map(|attr| match attr { + FunctionAttr::CallIndex(idx) => idx, + _ => unreachable!("checked during creation of the let binding"), + }); + + let final_index = match call_index { + Some(i) => i, + None => + last_index.map_or(Some(0), |idx| idx.checked_add(1)).ok_or_else(|| { + let msg = "Call index doesn't fit into u8, index is 256"; + syn::Error::new(method.sig.span(), msg) + })?, + }; + last_index = Some(final_index); + + if let Some(used_fn) = indices.insert(final_index, method.sig.ident.clone()) { + let msg = format!( + "Call indices are conflicting: Both functions {} and {} are at index {}", + used_fn, method.sig.ident, final_index, + ); + let mut err = syn::Error::new(used_fn.span(), &msg); + err.combine(syn::Error::new(method.sig.ident.span(), msg)); + return Err(err) + } let mut args = vec![]; for arg in method.sig.inputs.iter_mut().skip(1) { @@ -223,7 +281,13 @@ impl CallDef { let docs = get_doc_literals(&method.attrs); - methods.push(CallVariantDef { name: method.sig.ident.clone(), weight, args, docs }); + methods.push(CallVariantDef { + name: method.sig.ident.clone(), + weight, + call_index: final_index, + args, + docs, + }); } else { let msg = "Invalid pallet::call, only method accepted"; return Err(syn::Error::new(impl_item.span(), msg)) diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index d73a01187bfcc..03f1553293a47 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -1553,6 +1553,15 @@ pub mod pallet_prelude { /// used using `#[pallet::compact]`, function must return `DispatchResultWithPostInfo` or /// `DispatchResult`. /// +/// Each dispatchable may also be annotated with the `#[pallet::call_index($idx)]` attribute, +/// which defines and sets the codec index for the dispatchable function in the `Call` enum. +/// +/// All call indexes start from 0, until it encounters a dispatchable function with a defined +/// call index. The dispatchable function that lexically follows the function with a defined +/// call index will have that call index, but incremented by 1, e.g. if there are 3 +/// dispatchable functions `fn foo`, `fn bar` and `fn qux` in that order, and only `fn bar` has +/// a call index of 10, then `fn qux` will have an index of 11, instead of 1. +/// /// All arguments must implement `Debug`, `PartialEq`, `Eq`, `Decode`, `Encode`, `Clone`. For /// ease of use, bound the trait `Member` available in frame_support::pallet_prelude. /// @@ -1566,16 +1575,18 @@ pub mod pallet_prelude { /// **WARNING**: modifying dispatchables, changing their order, removing some must be done with /// care. Indeed this will change the outer runtime call type (which is an enum with one /// variant per pallet), this outer runtime call can be stored on-chain (e.g. in -/// pallet-scheduler). Thus migration might be needed. +/// pallet-scheduler). Thus migration might be needed. To mitigate against some of this, the +/// `#[pallet::call_index($idx)]` attribute can be used to fix the order of the dispatchable so +/// that the `Call` enum encoding does not change after modification. /// /// ### Macro expansion /// -/// The macro create an enum `Call` with one variant per dispatchable. This enum implements: +/// The macro creates an enum `Call` with one variant per dispatchable. This enum implements: /// `Clone`, `Eq`, `PartialEq`, `Debug` (with stripped implementation in `not("std")`), /// `Encode`, `Decode`, `GetDispatchInfo`, `GetCallName`, `UnfilteredDispatchable`. /// -/// The macro implement on `Pallet`, the `Callable` trait and a function `call_functions` which -/// returns the dispatchable metadatas. +/// The macro implement the `Callable` trait on `Pallet` and a function `call_functions` which +/// returns the dispatchable metadata. /// /// # Extra constants: `#[pallet::extra_constants]` optional /// diff --git a/frame/support/test/tests/pallet_ui/call_conflicting_indices.rs b/frame/support/test/tests/pallet_ui/call_conflicting_indices.rs new file mode 100644 index 0000000000000..395766c7cd331 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_conflicting_indices.rs @@ -0,0 +1,24 @@ +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::DispatchResultWithPostInfo; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::call] + impl Pallet { + #[pallet::weight(0)] + #[pallet::call_index(10)] + pub fn foo(origin: OriginFor) -> DispatchResultWithPostInfo {} + + #[pallet::weight(0)] + #[pallet::call_index(10)] + pub fn bar(origin: OriginFor) -> DispatchResultWithPostInfo {} + } +} + +fn main() { +} diff --git a/frame/support/test/tests/pallet_ui/call_conflicting_indices.stderr b/frame/support/test/tests/pallet_ui/call_conflicting_indices.stderr new file mode 100644 index 0000000000000..5d0c90609c2a1 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_conflicting_indices.stderr @@ -0,0 +1,11 @@ +error: Call indices are conflicting: Both functions foo and bar are at index 10 + --> tests/pallet_ui/call_conflicting_indices.rs:15:10 + | +15 | pub fn foo(origin: OriginFor) -> DispatchResultWithPostInfo {} + | ^^^ + +error: Call indices are conflicting: Both functions foo and bar are at index 10 + --> tests/pallet_ui/call_conflicting_indices.rs:19:10 + | +19 | pub fn bar(origin: OriginFor) -> DispatchResultWithPostInfo {} + | ^^^ diff --git a/frame/support/test/tests/pallet_ui/call_invalid_attr.rs b/frame/support/test/tests/pallet_ui/call_invalid_attr.rs new file mode 100644 index 0000000000000..118d3c92f360d --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_invalid_attr.rs @@ -0,0 +1,20 @@ +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::DispatchResultWithPostInfo; + use frame_system::pallet_prelude::OriginFor; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::call] + impl Pallet { + #[pallet::weird_attr] + pub fn foo(origin: OriginFor) -> DispatchResultWithPostInfo {} + } +} + +fn main() { +} diff --git a/frame/support/test/tests/pallet_ui/call_invalid_attr.stderr b/frame/support/test/tests/pallet_ui/call_invalid_attr.stderr new file mode 100644 index 0000000000000..3f680203a262f --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_invalid_attr.stderr @@ -0,0 +1,5 @@ +error: expected `weight` or `call_index` + --> tests/pallet_ui/call_invalid_attr.rs:14:13 + | +14 | #[pallet::weird_attr] + | ^^^^^^^^^^ diff --git a/frame/support/test/tests/pallet_ui/call_invalid_index.rs b/frame/support/test/tests/pallet_ui/call_invalid_index.rs new file mode 100644 index 0000000000000..0b40691ca43a3 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_invalid_index.rs @@ -0,0 +1,21 @@ +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::DispatchResultWithPostInfo; + use frame_system::pallet_prelude::OriginFor; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::call] + impl Pallet { + #[pallet::weight(0)] + #[pallet::call_index(256)] + pub fn foo(origin: OriginFor) -> DispatchResultWithPostInfo {} + } +} + +fn main() { +} diff --git a/frame/support/test/tests/pallet_ui/call_invalid_index.stderr b/frame/support/test/tests/pallet_ui/call_invalid_index.stderr new file mode 100644 index 0000000000000..1e07a4974bf69 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_invalid_index.stderr @@ -0,0 +1,5 @@ +error: number too large to fit in target type + --> tests/pallet_ui/call_invalid_index.rs:15:24 + | +15 | #[pallet::call_index(256)] + | ^^^ diff --git a/frame/support/test/tests/pallet_ui/call_multiple_call_index.rs b/frame/support/test/tests/pallet_ui/call_multiple_call_index.rs new file mode 100644 index 0000000000000..5753ecd076c80 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_multiple_call_index.rs @@ -0,0 +1,22 @@ +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::DispatchResultWithPostInfo; + use frame_system::pallet_prelude::OriginFor; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::call] + impl Pallet { + #[pallet::weight(0)] + #[pallet::call_index(1)] + #[pallet::call_index(2)] + pub fn foo(origin: OriginFor) -> DispatchResultWithPostInfo {} + } +} + +fn main() { +} diff --git a/frame/support/test/tests/pallet_ui/call_multiple_call_index.stderr b/frame/support/test/tests/pallet_ui/call_multiple_call_index.stderr new file mode 100644 index 0000000000000..ba22b012745c1 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_multiple_call_index.stderr @@ -0,0 +1,5 @@ +error: Invalid pallet::call, too many call_index attributes given + --> tests/pallet_ui/call_multiple_call_index.rs:17:7 + | +17 | pub fn foo(origin: OriginFor) -> DispatchResultWithPostInfo {} + | ^^ From c3a29d5aab6487579b5302619a78bf615c633963 Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Mon, 23 May 2022 12:56:42 -0400 Subject: [PATCH 262/484] Create Script to Run All Benchmarks (#11493) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Create run_all_benchmarks.sh * Update run_all_benchmarks.sh * Update run_all_benchmarks.sh * Review fixes Signed-off-by: Oliver Tale-Yazdi * Update scripts/run_all_benchmarks.sh Co-authored-by: Bastian Köcher * typo Signed-off-by: Oliver Tale-Yazdi * add default for $1 * Typo Signed-off-by: Oliver Tale-Yazdi * Update run_all_benchmarks.sh * new weights on benchmarking machine * prefer `--chain=dev` * fix compile * fix command * fmt * dont use square brackets * Extend doc Signed-off-by: Oliver Tale-Yazdi * Remove +nightly Signed-off-by: Oliver Tale-Yazdi * Add error file an run execute everything optimistically Signed-off-by: Oliver Tale-Yazdi Co-authored-by: Oliver Tale-Yazdi Co-authored-by: Bastian Köcher --- frame/assets/src/weights.rs | 144 +- frame/bags-list/src/weights.rs | 20 +- frame/balances/src/weights.rs | 36 +- frame/benchmarking/src/weights.rs | 56 +- frame/bounties/src/weights.rs | 60 +- frame/child-bounties/src/weights.rs | 44 +- frame/collective/src/weights.rs | 136 +- frame/contracts/src/weights.rs | 1227 ++++++++--------- frame/conviction-voting/src/weights.rs | 54 +- frame/democracy/src/weights.rs | 172 ++- .../src/weights.rs | 116 +- frame/elections-phragmen/src/weights.rs | 104 +- frame/gilt/src/weights.rs | 64 +- frame/identity/src/weights.rs | 196 ++- frame/im-online/src/weights.rs | 20 +- frame/indices/src/weights.rs | 28 +- frame/lottery/src/weights.rs | 40 +- frame/membership/src/weights.rs | 68 +- frame/multisig/src/weights.rs | 104 +- frame/nomination-pools/src/weights.rs | 87 +- frame/preimage/src/weights.rs | 44 +- frame/proxy/src/weights.rs | 120 +- frame/recovery/src/weights.rs | 82 +- frame/referenda/src/weights.rs | 120 +- frame/remark/src/weights.rs | 12 +- frame/scheduler/src/weights.rs | 148 +- frame/session/src/weights.rs | 16 +- frame/staking/src/weights.rs | 310 ++--- frame/support/src/weights.rs | 5 +- frame/support/src/weights/block_weights.rs | 81 +- .../support/src/weights/extrinsic_weights.rs | 81 +- frame/system/src/weights.rs | 28 +- frame/timestamp/src/weights.rs | 16 +- frame/tips/src/weights.rs | 56 +- frame/transaction-storage/src/weights.rs | 16 +- frame/treasury/src/weights.rs | 39 +- frame/uniques/src/weights.rs | 127 +- frame/utility/src/weights.rs | 40 +- frame/vesting/src/weights.rs | 112 +- frame/whitelist/src/weights.rs | 28 +- scripts/run_all_benchmarks.sh | 163 +++ .../benchmarking-cli/src/overhead/weights.hbs | 6 +- .../benchmarking-cli/src/storage/weights.hbs | 8 +- 43 files changed, 2291 insertions(+), 2143 deletions(-) create mode 100755 scripts/run_all_benchmarks.sh diff --git a/frame/assets/src/weights.rs b/frame/assets/src/weights.rs index a1cd77faee82e..453df22902ba2 100644 --- a/frame/assets/src/weights.rs +++ b/frame/assets/src/weights.rs @@ -18,12 +18,13 @@ //! Autogenerated weights for pallet_assets //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-01-30, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: // ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -31,11 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 +// --template=./.maintain/frame-weight-template.hbs // --output=./frame/assets/src/weights.rs -// --template=.maintain/frame-weight-template.hbs -// --header=HEADER-APACHE2 -// --raw #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -76,13 +74,13 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: Assets Asset (r:1 w:1) fn create() -> Weight { - (22_102_000 as Weight) + (23_081_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:1) fn force_create() -> Weight { - (12_124_000 as Weight) + (12_782_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -93,12 +91,12 @@ impl WeightInfo for SubstrateWeight { // Storage: Assets Approvals (r:501 w:500) fn destroy(c: u32, s: u32, a: u32, ) -> Weight { (0 as Weight) - // Standard Error: 34_000 - .saturating_add((14_683_000 as Weight).saturating_mul(c as Weight)) - // Standard Error: 34_000 - .saturating_add((17_080_000 as Weight).saturating_mul(s as Weight)) - // Standard Error: 342_000 - .saturating_add((16_533_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 36_000 + .saturating_add((15_327_000 as Weight).saturating_mul(c as Weight)) + // Standard Error: 36_000 + .saturating_add((17_817_000 as Weight).saturating_mul(s as Weight)) + // Standard Error: 362_000 + .saturating_add((16_692_000 as Weight).saturating_mul(a as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(c as Weight))) .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(s as Weight))) @@ -111,14 +109,14 @@ impl WeightInfo for SubstrateWeight { // Storage: Assets Asset (r:1 w:1) // Storage: Assets Account (r:1 w:1) fn mint() -> Weight { - (26_632_000 as Weight) + (25_993_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Assets Asset (r:1 w:1) // Storage: Assets Account (r:1 w:1) fn burn() -> Weight { - (30_048_000 as Weight) + (30_795_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -126,7 +124,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Assets Account (r:2 w:2) // Storage: System Account (r:1 w:1) fn transfer() -> Weight { - (44_000_000 as Weight) + (44_054_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -134,7 +132,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Assets Account (r:2 w:2) // Storage: System Account (r:1 w:1) fn transfer_keep_alive() -> Weight { - (37_286_000 as Weight) + (36_948_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -142,95 +140,87 @@ impl WeightInfo for SubstrateWeight { // Storage: Assets Account (r:2 w:2) // Storage: System Account (r:1 w:1) fn force_transfer() -> Weight { - (44_120_000 as Weight) + (44_446_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } // Storage: Assets Asset (r:1 w:0) // Storage: Assets Account (r:1 w:1) fn freeze() -> Weight { - (18_309_000 as Weight) + (18_381_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:0) // Storage: Assets Account (r:1 w:1) fn thaw() -> Weight { - (18_290_000 as Weight) + (18_215_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:1) fn freeze_asset() -> Weight { - (14_744_000 as Weight) + (14_885_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:1) fn thaw_asset() -> Weight { - (14_833_000 as Weight) + (14_834_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:1) // Storage: Assets Metadata (r:1 w:0) fn transfer_ownership() -> Weight { - (16_654_000 as Weight) + (16_033_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:1) fn set_team() -> Weight { - (15_351_000 as Weight) + (14_344_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:0) // Storage: Assets Metadata (r:1 w:1) - fn set_metadata(n: u32, s: u32, ) -> Weight { - (27_588_000 as Weight) - // Standard Error: 0 - .saturating_add((6_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 0 - .saturating_add((3_000 as Weight).saturating_mul(s as Weight)) + fn set_metadata(_n: u32, _s: u32, ) -> Weight { + (27_805_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:0) // Storage: Assets Metadata (r:1 w:1) fn clear_metadata() -> Weight { - (27_710_000 as Weight) + (28_466_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:0) // Storage: Assets Metadata (r:1 w:1) - fn force_set_metadata(n: u32, s: u32, ) -> Weight { - (15_345_000 as Weight) - // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(s as Weight)) + fn force_set_metadata(_n: u32, _s: u32, ) -> Weight { + (15_604_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:0) // Storage: Assets Metadata (r:1 w:1) fn force_clear_metadata() -> Weight { - (27_552_000 as Weight) + (28_278_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:1) fn force_asset_status() -> Weight { - (13_755_000 as Weight) + (13_556_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:1) // Storage: Assets Approvals (r:1 w:1) fn approve_transfer() -> Weight { - (30_831_000 as Weight) + (31_252_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -239,21 +229,21 @@ impl WeightInfo for SubstrateWeight { // Storage: Assets Account (r:2 w:2) // Storage: System Account (r:1 w:1) fn transfer_approved() -> Weight { - (56_267_000 as Weight) + (55_281_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } // Storage: Assets Asset (r:1 w:1) // Storage: Assets Approvals (r:1 w:1) fn cancel_approval() -> Weight { - (31_964_000 as Weight) + (30_784_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Assets Asset (r:1 w:1) // Storage: Assets Approvals (r:1 w:1) fn force_cancel_approval() -> Weight { - (31_806_000 as Weight) + (32_011_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -263,13 +253,13 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { // Storage: Assets Asset (r:1 w:1) fn create() -> Weight { - (22_102_000 as Weight) + (23_081_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:1) fn force_create() -> Weight { - (12_124_000 as Weight) + (12_782_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -280,12 +270,12 @@ impl WeightInfo for () { // Storage: Assets Approvals (r:501 w:500) fn destroy(c: u32, s: u32, a: u32, ) -> Weight { (0 as Weight) - // Standard Error: 34_000 - .saturating_add((14_683_000 as Weight).saturating_mul(c as Weight)) - // Standard Error: 34_000 - .saturating_add((17_080_000 as Weight).saturating_mul(s as Weight)) - // Standard Error: 342_000 - .saturating_add((16_533_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 36_000 + .saturating_add((15_327_000 as Weight).saturating_mul(c as Weight)) + // Standard Error: 36_000 + .saturating_add((17_817_000 as Weight).saturating_mul(s as Weight)) + // Standard Error: 362_000 + .saturating_add((16_692_000 as Weight).saturating_mul(a as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().reads((2 as Weight).saturating_mul(c as Weight))) .saturating_add(RocksDbWeight::get().reads((2 as Weight).saturating_mul(s as Weight))) @@ -298,14 +288,14 @@ impl WeightInfo for () { // Storage: Assets Asset (r:1 w:1) // Storage: Assets Account (r:1 w:1) fn mint() -> Weight { - (26_632_000 as Weight) + (25_993_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Assets Asset (r:1 w:1) // Storage: Assets Account (r:1 w:1) fn burn() -> Weight { - (30_048_000 as Weight) + (30_795_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -313,7 +303,7 @@ impl WeightInfo for () { // Storage: Assets Account (r:2 w:2) // Storage: System Account (r:1 w:1) fn transfer() -> Weight { - (44_000_000 as Weight) + (44_054_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } @@ -321,7 +311,7 @@ impl WeightInfo for () { // Storage: Assets Account (r:2 w:2) // Storage: System Account (r:1 w:1) fn transfer_keep_alive() -> Weight { - (37_286_000 as Weight) + (36_948_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } @@ -329,95 +319,87 @@ impl WeightInfo for () { // Storage: Assets Account (r:2 w:2) // Storage: System Account (r:1 w:1) fn force_transfer() -> Weight { - (44_120_000 as Weight) + (44_446_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } // Storage: Assets Asset (r:1 w:0) // Storage: Assets Account (r:1 w:1) fn freeze() -> Weight { - (18_309_000 as Weight) + (18_381_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:0) // Storage: Assets Account (r:1 w:1) fn thaw() -> Weight { - (18_290_000 as Weight) + (18_215_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:1) fn freeze_asset() -> Weight { - (14_744_000 as Weight) + (14_885_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:1) fn thaw_asset() -> Weight { - (14_833_000 as Weight) + (14_834_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:1) // Storage: Assets Metadata (r:1 w:0) fn transfer_ownership() -> Weight { - (16_654_000 as Weight) + (16_033_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:1) fn set_team() -> Weight { - (15_351_000 as Weight) + (14_344_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:0) // Storage: Assets Metadata (r:1 w:1) - fn set_metadata(n: u32, s: u32, ) -> Weight { - (27_588_000 as Weight) - // Standard Error: 0 - .saturating_add((6_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 0 - .saturating_add((3_000 as Weight).saturating_mul(s as Weight)) + fn set_metadata(_n: u32, _s: u32, ) -> Weight { + (27_805_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:0) // Storage: Assets Metadata (r:1 w:1) fn clear_metadata() -> Weight { - (27_710_000 as Weight) + (28_466_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:0) // Storage: Assets Metadata (r:1 w:1) - fn force_set_metadata(n: u32, s: u32, ) -> Weight { - (15_345_000 as Weight) - // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(s as Weight)) + fn force_set_metadata(_n: u32, _s: u32, ) -> Weight { + (15_604_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:0) // Storage: Assets Metadata (r:1 w:1) fn force_clear_metadata() -> Weight { - (27_552_000 as Weight) + (28_278_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:1) fn force_asset_status() -> Weight { - (13_755_000 as Weight) + (13_556_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:1) // Storage: Assets Approvals (r:1 w:1) fn approve_transfer() -> Weight { - (30_831_000 as Weight) + (31_252_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -426,21 +408,21 @@ impl WeightInfo for () { // Storage: Assets Account (r:2 w:2) // Storage: System Account (r:1 w:1) fn transfer_approved() -> Weight { - (56_267_000 as Weight) + (55_281_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } // Storage: Assets Asset (r:1 w:1) // Storage: Assets Approvals (r:1 w:1) fn cancel_approval() -> Weight { - (31_964_000 as Weight) + (30_784_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Assets Asset (r:1 w:1) // Storage: Assets Approvals (r:1 w:1) fn force_cancel_approval() -> Weight { - (31_806_000 as Weight) + (32_011_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } diff --git a/frame/bags-list/src/weights.rs b/frame/bags-list/src/weights.rs index f35c0aaee6d6a..8a8a14a75218d 100644 --- a/frame/bags-list/src/weights.rs +++ b/frame/bags-list/src/weights.rs @@ -18,12 +18,13 @@ //! Autogenerated weights for pallet_bags_list //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-01-30, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: // ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -31,11 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 +// --template=./.maintain/frame-weight-template.hbs // --output=./frame/bags-list/src/weights.rs -// --template=.maintain/frame-weight-template.hbs -// --header=HEADER-APACHE2 -// --raw #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -59,7 +57,7 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList ListNodes (r:4 w:4) // Storage: BagsList ListBags (r:1 w:1) fn rebag_non_terminal() -> Weight { - (46_965_000 as Weight) + (51_415_000 as Weight) .saturating_add(T::DbWeight::get().reads(7 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } @@ -68,7 +66,7 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn rebag_terminal() -> Weight { - (45_952_000 as Weight) + (49_459_000 as Weight) .saturating_add(T::DbWeight::get().reads(7 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } @@ -78,7 +76,7 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList CounterForListNodes (r:1 w:1) // Storage: BagsList ListBags (r:1 w:1) fn put_in_front_of() -> Weight { - (52_928_000 as Weight) + (53_682_000 as Weight) .saturating_add(T::DbWeight::get().reads(10 as Weight)) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } @@ -91,7 +89,7 @@ impl WeightInfo for () { // Storage: BagsList ListNodes (r:4 w:4) // Storage: BagsList ListBags (r:1 w:1) fn rebag_non_terminal() -> Weight { - (46_965_000 as Weight) + (51_415_000 as Weight) .saturating_add(RocksDbWeight::get().reads(7 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } @@ -100,7 +98,7 @@ impl WeightInfo for () { // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn rebag_terminal() -> Weight { - (45_952_000 as Weight) + (49_459_000 as Weight) .saturating_add(RocksDbWeight::get().reads(7 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } @@ -110,7 +108,7 @@ impl WeightInfo for () { // Storage: BagsList CounterForListNodes (r:1 w:1) // Storage: BagsList ListBags (r:1 w:1) fn put_in_front_of() -> Weight { - (52_928_000 as Weight) + (53_682_000 as Weight) .saturating_add(RocksDbWeight::get().reads(10 as Weight)) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } diff --git a/frame/balances/src/weights.rs b/frame/balances/src/weights.rs index 435438055b333..dd22e7ca778f8 100644 --- a/frame/balances/src/weights.rs +++ b/frame/balances/src/weights.rs @@ -18,12 +18,13 @@ //! Autogenerated weights for pallet_balances //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-01-30, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: // ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -31,11 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 +// --template=./.maintain/frame-weight-template.hbs // --output=./frame/balances/src/weights.rs -// --template=.maintain/frame-weight-template.hbs -// --header=HEADER-APACHE2 -// --raw #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -60,43 +58,43 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) fn transfer() -> Weight { - (34_200_000 as Weight) + (35_278_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: System Account (r:1 w:1) fn transfer_keep_alive() -> Weight { - (27_263_000 as Weight) + (27_822_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: System Account (r:1 w:1) fn set_balance_creating() -> Weight { - (17_425_000 as Weight) + (17_943_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: System Account (r:1 w:1) fn set_balance_killing() -> Weight { - (19_979_000 as Weight) + (20_974_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: System Account (r:2 w:2) fn force_transfer() -> Weight { - (34_783_000 as Weight) + (36_078_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: System Account (r:1 w:1) fn transfer_all() -> Weight { - (31_620_000 as Weight) + (32_794_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: System Account (r:1 w:1) fn force_unreserve() -> Weight { - (15_750_000 as Weight) + (16_227_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -106,43 +104,43 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { // Storage: System Account (r:1 w:1) fn transfer() -> Weight { - (34_200_000 as Weight) + (35_278_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: System Account (r:1 w:1) fn transfer_keep_alive() -> Weight { - (27_263_000 as Weight) + (27_822_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: System Account (r:1 w:1) fn set_balance_creating() -> Weight { - (17_425_000 as Weight) + (17_943_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: System Account (r:1 w:1) fn set_balance_killing() -> Weight { - (19_979_000 as Weight) + (20_974_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: System Account (r:2 w:2) fn force_transfer() -> Weight { - (34_783_000 as Weight) + (36_078_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: System Account (r:1 w:1) fn transfer_all() -> Weight { - (31_620_000 as Weight) + (32_794_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: System Account (r:1 w:1) fn force_unreserve() -> Weight { - (15_750_000 as Weight) + (16_227_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } diff --git a/frame/benchmarking/src/weights.rs b/frame/benchmarking/src/weights.rs index a49204fbf0b90..24643bb391152 100644 --- a/frame/benchmarking/src/weights.rs +++ b/frame/benchmarking/src/weights.rs @@ -18,12 +18,13 @@ //! Autogenerated weights for frame_benchmarking //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-01-30, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: // ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -31,11 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 +// --template=./.maintain/frame-weight-template.hbs // --output=./frame/benchmarking/src/weights.rs -// --template=.maintain/frame-weight-template.hbs -// --header=HEADER-APACHE2 -// --raw #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -60,37 +58,39 @@ pub trait WeightInfo { pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { fn addition(_i: u32, ) -> Weight { - (106_000 as Weight) + (126_000 as Weight) } fn subtraction(_i: u32, ) -> Weight { - (111_000 as Weight) + (121_000 as Weight) } fn multiplication(_i: u32, ) -> Weight { - (119_000 as Weight) + (132_000 as Weight) } fn division(_i: u32, ) -> Weight { - (111_000 as Weight) + (122_000 as Weight) } - fn hashing(_i: u32, ) -> Weight { - (21_203_386_000 as Weight) + fn hashing(i: u32, ) -> Weight { + (21_059_079_000 as Weight) + // Standard Error: 117_000 + .saturating_add((1_121_000 as Weight).saturating_mul(i as Weight)) } fn sr25519_verification(i: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 6_000 - .saturating_add((47_077_000 as Weight).saturating_mul(i as Weight)) + (425_000 as Weight) + // Standard Error: 7_000 + .saturating_add((47_172_000 as Weight).saturating_mul(i as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn storage_read(i: u32, ) -> Weight { (0 as Weight) // Standard Error: 3_000 - .saturating_add((1_963_000 as Weight).saturating_mul(i as Weight)) + .saturating_add((2_118_000 as Weight).saturating_mul(i as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight))) } // Storage: Skipped Metadata (r:0 w:0) fn storage_write(i: u32, ) -> Weight { (0 as Weight) // Standard Error: 0 - .saturating_add((364_000 as Weight).saturating_mul(i as Weight)) + .saturating_add((373_000 as Weight).saturating_mul(i as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(i as Weight))) } } @@ -98,37 +98,39 @@ impl WeightInfo for SubstrateWeight { // For backwards compatibility and tests impl WeightInfo for () { fn addition(_i: u32, ) -> Weight { - (106_000 as Weight) + (126_000 as Weight) } fn subtraction(_i: u32, ) -> Weight { - (111_000 as Weight) + (121_000 as Weight) } fn multiplication(_i: u32, ) -> Weight { - (119_000 as Weight) + (132_000 as Weight) } fn division(_i: u32, ) -> Weight { - (111_000 as Weight) + (122_000 as Weight) } - fn hashing(_i: u32, ) -> Weight { - (21_203_386_000 as Weight) + fn hashing(i: u32, ) -> Weight { + (21_059_079_000 as Weight) + // Standard Error: 117_000 + .saturating_add((1_121_000 as Weight).saturating_mul(i as Weight)) } fn sr25519_verification(i: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 6_000 - .saturating_add((47_077_000 as Weight).saturating_mul(i as Weight)) + (425_000 as Weight) + // Standard Error: 7_000 + .saturating_add((47_172_000 as Weight).saturating_mul(i as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn storage_read(i: u32, ) -> Weight { (0 as Weight) // Standard Error: 3_000 - .saturating_add((1_963_000 as Weight).saturating_mul(i as Weight)) + .saturating_add((2_118_000 as Weight).saturating_mul(i as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(i as Weight))) } // Storage: Skipped Metadata (r:0 w:0) fn storage_write(i: u32, ) -> Weight { (0 as Weight) // Standard Error: 0 - .saturating_add((364_000 as Weight).saturating_mul(i as Weight)) + .saturating_add((373_000 as Weight).saturating_mul(i as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(i as Weight))) } } diff --git a/frame/bounties/src/weights.rs b/frame/bounties/src/weights.rs index 405e11acea5aa..a1019b3e5c52c 100644 --- a/frame/bounties/src/weights.rs +++ b/frame/bounties/src/weights.rs @@ -18,12 +18,13 @@ //! Autogenerated weights for pallet_bounties //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-01-30, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: // ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -31,11 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 +// --template=./.maintain/frame-weight-template.hbs // --output=./frame/bounties/src/weights.rs -// --template=.maintain/frame-weight-template.hbs -// --header=HEADER-APACHE2 -// --raw #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -67,43 +65,43 @@ impl WeightInfo for SubstrateWeight { // Storage: Bounties BountyDescriptions (r:0 w:1) // Storage: Bounties Bounties (r:0 w:1) fn propose_bounty(d: u32, ) -> Weight { - (23_620_000 as Weight) + (25_277_000 as Weight) // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(d as Weight)) + .saturating_add((1_000 as Weight).saturating_mul(d as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } // Storage: Bounties Bounties (r:1 w:1) // Storage: Bounties BountyApprovals (r:1 w:1) fn approve_bounty() -> Weight { - (6_655_000 as Weight) + (7_646_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Bounties Bounties (r:1 w:1) fn propose_curator() -> Weight { - (5_720_000 as Weight) + (5_712_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Bounties Bounties (r:1 w:1) // Storage: System Account (r:1 w:1) fn unassign_curator() -> Weight { - (26_958_000 as Weight) + (25_220_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Bounties Bounties (r:1 w:1) // Storage: System Account (r:1 w:1) fn accept_curator() -> Weight { - (21_457_000 as Weight) + (21_985_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Bounties Bounties (r:1 w:1) // Storage: ChildBounties ParentChildBounties (r:1 w:0) fn award_bounty() -> Weight { - (17_483_000 as Weight) + (18_341_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -112,7 +110,7 @@ impl WeightInfo for SubstrateWeight { // Storage: ChildBounties ChildrenCuratorFees (r:1 w:1) // Storage: Bounties BountyDescriptions (r:0 w:1) fn claim_bounty() -> Weight { - (61_763_000 as Weight) + (63_146_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } @@ -121,7 +119,7 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) // Storage: Bounties BountyDescriptions (r:0 w:1) fn close_bounty_proposed() -> Weight { - (28_862_000 as Weight) + (29_362_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -130,13 +128,13 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:2 w:2) // Storage: Bounties BountyDescriptions (r:0 w:1) fn close_bounty_active() -> Weight { - (45_129_000 as Weight) + (46_519_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } // Storage: Bounties Bounties (r:1 w:1) fn extend_bounty_expiry() -> Weight { - (15_169_000 as Weight) + (15_363_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -145,8 +143,8 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:2 w:2) fn spend_funds(b: u32, ) -> Weight { (0 as Weight) - // Standard Error: 14_000 - .saturating_add((28_850_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 18_000 + .saturating_add((29_482_000 as Weight).saturating_mul(b as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(b as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -161,43 +159,43 @@ impl WeightInfo for () { // Storage: Bounties BountyDescriptions (r:0 w:1) // Storage: Bounties Bounties (r:0 w:1) fn propose_bounty(d: u32, ) -> Weight { - (23_620_000 as Weight) + (25_277_000 as Weight) // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(d as Weight)) + .saturating_add((1_000 as Weight).saturating_mul(d as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } // Storage: Bounties Bounties (r:1 w:1) // Storage: Bounties BountyApprovals (r:1 w:1) fn approve_bounty() -> Weight { - (6_655_000 as Weight) + (7_646_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Bounties Bounties (r:1 w:1) fn propose_curator() -> Weight { - (5_720_000 as Weight) + (5_712_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Bounties Bounties (r:1 w:1) // Storage: System Account (r:1 w:1) fn unassign_curator() -> Weight { - (26_958_000 as Weight) + (25_220_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Bounties Bounties (r:1 w:1) // Storage: System Account (r:1 w:1) fn accept_curator() -> Weight { - (21_457_000 as Weight) + (21_985_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Bounties Bounties (r:1 w:1) // Storage: ChildBounties ParentChildBounties (r:1 w:0) fn award_bounty() -> Weight { - (17_483_000 as Weight) + (18_341_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -206,7 +204,7 @@ impl WeightInfo for () { // Storage: ChildBounties ChildrenCuratorFees (r:1 w:1) // Storage: Bounties BountyDescriptions (r:0 w:1) fn claim_bounty() -> Weight { - (61_763_000 as Weight) + (63_146_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } @@ -215,7 +213,7 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) // Storage: Bounties BountyDescriptions (r:0 w:1) fn close_bounty_proposed() -> Weight { - (28_862_000 as Weight) + (29_362_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -224,13 +222,13 @@ impl WeightInfo for () { // Storage: System Account (r:2 w:2) // Storage: Bounties BountyDescriptions (r:0 w:1) fn close_bounty_active() -> Weight { - (45_129_000 as Weight) + (46_519_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } // Storage: Bounties Bounties (r:1 w:1) fn extend_bounty_expiry() -> Weight { - (15_169_000 as Weight) + (15_363_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -239,8 +237,8 @@ impl WeightInfo for () { // Storage: System Account (r:2 w:2) fn spend_funds(b: u32, ) -> Weight { (0 as Weight) - // Standard Error: 14_000 - .saturating_add((28_850_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 18_000 + .saturating_add((29_482_000 as Weight).saturating_mul(b as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(b as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) diff --git a/frame/child-bounties/src/weights.rs b/frame/child-bounties/src/weights.rs index 002388810b8a0..d11de89bcf5c3 100644 --- a/frame/child-bounties/src/weights.rs +++ b/frame/child-bounties/src/weights.rs @@ -18,12 +18,13 @@ //! Autogenerated weights for pallet_child_bounties //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-01-30, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: // ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -31,11 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 +// --template=./.maintain/frame-weight-template.hbs // --output=./frame/child-bounties/src/weights.rs -// --template=.maintain/frame-weight-template.hbs -// --header=HEADER-APACHE2 -// --raw #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -66,9 +64,9 @@ impl WeightInfo for SubstrateWeight { // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) // Storage: ChildBounties ChildBounties (r:0 w:1) fn add_child_bounty(d: u32, ) -> Weight { - (44_503_000 as Weight) + (45_917_000 as Weight) // Standard Error: 0 - .saturating_add((3_000 as Weight).saturating_mul(d as Weight)) + .saturating_add((1_000 as Weight).saturating_mul(d as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } @@ -76,7 +74,7 @@ impl WeightInfo for SubstrateWeight { // Storage: ChildBounties ChildBounties (r:1 w:1) // Storage: ChildBounties ChildrenCuratorFees (r:1 w:1) fn propose_curator() -> Weight { - (11_661_000 as Weight) + (11_855_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -84,7 +82,7 @@ impl WeightInfo for SubstrateWeight { // Storage: ChildBounties ChildBounties (r:1 w:1) // Storage: System Account (r:1 w:1) fn accept_curator() -> Weight { - (24_162_000 as Weight) + (24_488_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -92,14 +90,14 @@ impl WeightInfo for SubstrateWeight { // Storage: Bounties Bounties (r:1 w:0) // Storage: System Account (r:1 w:1) fn unassign_curator() -> Weight { - (27_924_000 as Weight) + (28_645_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Bounties Bounties (r:1 w:0) // Storage: ChildBounties ChildBounties (r:1 w:1) fn award_child_bounty() -> Weight { - (19_332_000 as Weight) + (19_760_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -108,7 +106,7 @@ impl WeightInfo for SubstrateWeight { // Storage: ChildBounties ParentChildBounties (r:1 w:1) // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) fn claim_child_bounty() -> Weight { - (60_363_000 as Weight) + (62_194_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } @@ -119,7 +117,7 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:2 w:2) // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) fn close_child_bounty_added() -> Weight { - (42_415_000 as Weight) + (44_136_000 as Weight) .saturating_add(T::DbWeight::get().reads(6 as Weight)) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } @@ -130,7 +128,7 @@ impl WeightInfo for SubstrateWeight { // Storage: ChildBounties ParentChildBounties (r:1 w:1) // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) fn close_child_bounty_active() -> Weight { - (52_743_000 as Weight) + (53_168_000 as Weight) .saturating_add(T::DbWeight::get().reads(7 as Weight)) .saturating_add(T::DbWeight::get().writes(7 as Weight)) } @@ -145,9 +143,9 @@ impl WeightInfo for () { // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) // Storage: ChildBounties ChildBounties (r:0 w:1) fn add_child_bounty(d: u32, ) -> Weight { - (44_503_000 as Weight) + (45_917_000 as Weight) // Standard Error: 0 - .saturating_add((3_000 as Weight).saturating_mul(d as Weight)) + .saturating_add((1_000 as Weight).saturating_mul(d as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } @@ -155,7 +153,7 @@ impl WeightInfo for () { // Storage: ChildBounties ChildBounties (r:1 w:1) // Storage: ChildBounties ChildrenCuratorFees (r:1 w:1) fn propose_curator() -> Weight { - (11_661_000 as Weight) + (11_855_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -163,7 +161,7 @@ impl WeightInfo for () { // Storage: ChildBounties ChildBounties (r:1 w:1) // Storage: System Account (r:1 w:1) fn accept_curator() -> Weight { - (24_162_000 as Weight) + (24_488_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -171,14 +169,14 @@ impl WeightInfo for () { // Storage: Bounties Bounties (r:1 w:0) // Storage: System Account (r:1 w:1) fn unassign_curator() -> Weight { - (27_924_000 as Weight) + (28_645_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Bounties Bounties (r:1 w:0) // Storage: ChildBounties ChildBounties (r:1 w:1) fn award_child_bounty() -> Weight { - (19_332_000 as Weight) + (19_760_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -187,7 +185,7 @@ impl WeightInfo for () { // Storage: ChildBounties ParentChildBounties (r:1 w:1) // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) fn claim_child_bounty() -> Weight { - (60_363_000 as Weight) + (62_194_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } @@ -198,7 +196,7 @@ impl WeightInfo for () { // Storage: System Account (r:2 w:2) // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) fn close_child_bounty_added() -> Weight { - (42_415_000 as Weight) + (44_136_000 as Weight) .saturating_add(RocksDbWeight::get().reads(6 as Weight)) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } @@ -209,7 +207,7 @@ impl WeightInfo for () { // Storage: ChildBounties ParentChildBounties (r:1 w:1) // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) fn close_child_bounty_active() -> Weight { - (52_743_000 as Weight) + (53_168_000 as Weight) .saturating_add(RocksDbWeight::get().reads(7 as Weight)) .saturating_add(RocksDbWeight::get().writes(7 as Weight)) } diff --git a/frame/collective/src/weights.rs b/frame/collective/src/weights.rs index 1280ced89eeea..6392f86de6530 100644 --- a/frame/collective/src/weights.rs +++ b/frame/collective/src/weights.rs @@ -18,12 +18,13 @@ //! Autogenerated weights for pallet_collective //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-01-30, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: // ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -31,11 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 +// --template=./.maintain/frame-weight-template.hbs // --output=./frame/collective/src/weights.rs -// --template=.maintain/frame-weight-template.hbs -// --header=HEADER-APACHE2 -// --raw #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -67,12 +65,12 @@ impl WeightInfo for SubstrateWeight { // Storage: Council Prime (r:0 w:1) fn set_members(m: u32, n: u32, p: u32, ) -> Weight { (0 as Weight) - // Standard Error: 10_000 - .saturating_add((14_493_000 as Weight).saturating_mul(m as Weight)) - // Standard Error: 10_000 - .saturating_add((23_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 10_000 - .saturating_add((16_909_000 as Weight).saturating_mul(p as Weight)) + // Standard Error: 8_000 + .saturating_add((10_362_000 as Weight).saturating_mul(m as Weight)) + // Standard Error: 8_000 + .saturating_add((56_000 as Weight).saturating_mul(n as Weight)) + // Standard Error: 8_000 + .saturating_add((13_187_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(p as Weight))) .saturating_add(T::DbWeight::get().writes(2 as Weight)) @@ -80,21 +78,21 @@ impl WeightInfo for SubstrateWeight { } // Storage: Council Members (r:1 w:0) fn execute(b: u32, m: u32, ) -> Weight { - (12_790_000 as Weight) + (13_571_000 as Weight) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) // Standard Error: 0 - .saturating_add((73_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((31_000 as Weight).saturating_mul(m as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) } // Storage: Council Members (r:1 w:0) // Storage: Council ProposalOf (r:1 w:0) fn propose_execute(b: u32, m: u32, ) -> Weight { - (15_087_000 as Weight) + (15_851_000 as Weight) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) // Standard Error: 0 - .saturating_add((135_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((53_000 as Weight).saturating_mul(m as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) } // Storage: Council Members (r:1 w:0) @@ -103,22 +101,22 @@ impl WeightInfo for SubstrateWeight { // Storage: Council ProposalCount (r:1 w:1) // Storage: Council Voting (r:0 w:1) fn propose_proposed(b: u32, m: u32, p: u32, ) -> Weight { - (18_269_000 as Weight) + (19_593_000 as Weight) // Standard Error: 0 - .saturating_add((8_000 as Weight).saturating_mul(b as Weight)) + .saturating_add((7_000 as Weight).saturating_mul(b as Weight)) // Standard Error: 1_000 - .saturating_add((77_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((40_000 as Weight).saturating_mul(m as Weight)) // Standard Error: 1_000 - .saturating_add((203_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((165_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } // Storage: Council Members (r:1 w:0) // Storage: Council Voting (r:1 w:1) fn vote(m: u32, ) -> Weight { - (26_624_000 as Weight) + (27_221_000 as Weight) // Standard Error: 2_000 - .saturating_add((161_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((82_000 as Weight).saturating_mul(m as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -127,11 +125,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Council Proposals (r:1 w:1) // Storage: Council ProposalOf (r:0 w:1) fn close_early_disapproved(m: u32, p: u32, ) -> Weight { - (26_527_000 as Weight) + (26_295_000 as Weight) // Standard Error: 1_000 - .saturating_add((127_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((55_000 as Weight).saturating_mul(m as Weight)) // Standard Error: 1_000 - .saturating_add((155_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((130_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -140,13 +138,13 @@ impl WeightInfo for SubstrateWeight { // Storage: Council ProposalOf (r:1 w:1) // Storage: Council Proposals (r:1 w:1) fn close_early_approved(b: u32, m: u32, p: u32, ) -> Weight { - (26_352_000 as Weight) + (28_609_000 as Weight) // Standard Error: 0 .saturating_add((6_000 as Weight).saturating_mul(b as Weight)) // Standard Error: 1_000 - .saturating_add((154_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((68_000 as Weight).saturating_mul(m as Weight)) // Standard Error: 1_000 - .saturating_add((203_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((161_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -156,11 +154,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Council Proposals (r:1 w:1) // Storage: Council ProposalOf (r:0 w:1) fn close_disapproved(m: u32, p: u32, ) -> Weight { - (28_638_000 as Weight) + (30_497_000 as Weight) // Standard Error: 1_000 - .saturating_add((133_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((48_000 as Weight).saturating_mul(m as Weight)) // Standard Error: 1_000 - .saturating_add((162_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((120_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -170,13 +168,13 @@ impl WeightInfo for SubstrateWeight { // Storage: Council ProposalOf (r:1 w:1) // Storage: Council Proposals (r:1 w:1) fn close_approved(b: u32, m: u32, p: u32, ) -> Weight { - (29_946_000 as Weight) + (31_402_000 as Weight) // Standard Error: 0 .saturating_add((5_000 as Weight).saturating_mul(b as Weight)) - // Standard Error: 2_000 - .saturating_add((151_000 as Weight).saturating_mul(m as Weight)) - // Standard Error: 2_000 - .saturating_add((201_000 as Weight).saturating_mul(p as Weight)) + // Standard Error: 1_000 + .saturating_add((73_000 as Weight).saturating_mul(m as Weight)) + // Standard Error: 1_000 + .saturating_add((159_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -184,9 +182,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Council Voting (r:0 w:1) // Storage: Council ProposalOf (r:0 w:1) fn disapprove_proposal(p: u32, ) -> Weight { - (15_778_000 as Weight) + (16_391_000 as Weight) // Standard Error: 1_000 - .saturating_add((206_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((169_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -200,12 +198,12 @@ impl WeightInfo for () { // Storage: Council Prime (r:0 w:1) fn set_members(m: u32, n: u32, p: u32, ) -> Weight { (0 as Weight) - // Standard Error: 10_000 - .saturating_add((14_493_000 as Weight).saturating_mul(m as Weight)) - // Standard Error: 10_000 - .saturating_add((23_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 10_000 - .saturating_add((16_909_000 as Weight).saturating_mul(p as Weight)) + // Standard Error: 8_000 + .saturating_add((10_362_000 as Weight).saturating_mul(m as Weight)) + // Standard Error: 8_000 + .saturating_add((56_000 as Weight).saturating_mul(n as Weight)) + // Standard Error: 8_000 + .saturating_add((13_187_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(p as Weight))) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) @@ -213,21 +211,21 @@ impl WeightInfo for () { } // Storage: Council Members (r:1 w:0) fn execute(b: u32, m: u32, ) -> Weight { - (12_790_000 as Weight) + (13_571_000 as Weight) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) // Standard Error: 0 - .saturating_add((73_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((31_000 as Weight).saturating_mul(m as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) } // Storage: Council Members (r:1 w:0) // Storage: Council ProposalOf (r:1 w:0) fn propose_execute(b: u32, m: u32, ) -> Weight { - (15_087_000 as Weight) + (15_851_000 as Weight) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) // Standard Error: 0 - .saturating_add((135_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((53_000 as Weight).saturating_mul(m as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) } // Storage: Council Members (r:1 w:0) @@ -236,22 +234,22 @@ impl WeightInfo for () { // Storage: Council ProposalCount (r:1 w:1) // Storage: Council Voting (r:0 w:1) fn propose_proposed(b: u32, m: u32, p: u32, ) -> Weight { - (18_269_000 as Weight) + (19_593_000 as Weight) // Standard Error: 0 - .saturating_add((8_000 as Weight).saturating_mul(b as Weight)) + .saturating_add((7_000 as Weight).saturating_mul(b as Weight)) // Standard Error: 1_000 - .saturating_add((77_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((40_000 as Weight).saturating_mul(m as Weight)) // Standard Error: 1_000 - .saturating_add((203_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((165_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } // Storage: Council Members (r:1 w:0) // Storage: Council Voting (r:1 w:1) fn vote(m: u32, ) -> Weight { - (26_624_000 as Weight) + (27_221_000 as Weight) // Standard Error: 2_000 - .saturating_add((161_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((82_000 as Weight).saturating_mul(m as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -260,11 +258,11 @@ impl WeightInfo for () { // Storage: Council Proposals (r:1 w:1) // Storage: Council ProposalOf (r:0 w:1) fn close_early_disapproved(m: u32, p: u32, ) -> Weight { - (26_527_000 as Weight) + (26_295_000 as Weight) // Standard Error: 1_000 - .saturating_add((127_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((55_000 as Weight).saturating_mul(m as Weight)) // Standard Error: 1_000 - .saturating_add((155_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((130_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -273,13 +271,13 @@ impl WeightInfo for () { // Storage: Council ProposalOf (r:1 w:1) // Storage: Council Proposals (r:1 w:1) fn close_early_approved(b: u32, m: u32, p: u32, ) -> Weight { - (26_352_000 as Weight) + (28_609_000 as Weight) // Standard Error: 0 .saturating_add((6_000 as Weight).saturating_mul(b as Weight)) // Standard Error: 1_000 - .saturating_add((154_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((68_000 as Weight).saturating_mul(m as Weight)) // Standard Error: 1_000 - .saturating_add((203_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((161_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -289,11 +287,11 @@ impl WeightInfo for () { // Storage: Council Proposals (r:1 w:1) // Storage: Council ProposalOf (r:0 w:1) fn close_disapproved(m: u32, p: u32, ) -> Weight { - (28_638_000 as Weight) + (30_497_000 as Weight) // Standard Error: 1_000 - .saturating_add((133_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((48_000 as Weight).saturating_mul(m as Weight)) // Standard Error: 1_000 - .saturating_add((162_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((120_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -303,13 +301,13 @@ impl WeightInfo for () { // Storage: Council ProposalOf (r:1 w:1) // Storage: Council Proposals (r:1 w:1) fn close_approved(b: u32, m: u32, p: u32, ) -> Weight { - (29_946_000 as Weight) + (31_402_000 as Weight) // Standard Error: 0 .saturating_add((5_000 as Weight).saturating_mul(b as Weight)) - // Standard Error: 2_000 - .saturating_add((151_000 as Weight).saturating_mul(m as Weight)) - // Standard Error: 2_000 - .saturating_add((201_000 as Weight).saturating_mul(p as Weight)) + // Standard Error: 1_000 + .saturating_add((73_000 as Weight).saturating_mul(m as Weight)) + // Standard Error: 1_000 + .saturating_add((159_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -317,9 +315,9 @@ impl WeightInfo for () { // Storage: Council Voting (r:0 w:1) // Storage: Council ProposalOf (r:0 w:1) fn disapprove_proposal(p: u32, ) -> Weight { - (15_778_000 as Weight) + (16_391_000 as Weight) // Standard Error: 1_000 - .saturating_add((206_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((169_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } diff --git a/frame/contracts/src/weights.rs b/frame/contracts/src/weights.rs index 256e0ffc9d808..c6e83d34e90b1 100644 --- a/frame/contracts/src/weights.rs +++ b/frame/contracts/src/weights.rs @@ -18,11 +18,11 @@ //! Autogenerated weights for pallet_contracts //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-18, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/production/substrate +// ./target/production/substrate // benchmark // pallet // --chain=dev @@ -32,9 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 -// --output=./frame/contracts/src/weights.rs // --template=./.maintain/frame-weight-template.hbs +// --output=./frame/contracts/src/weights.rs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -165,12 +164,12 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: Contracts DeletionQueue (r:1 w:0) fn on_initialize() -> Weight { - (1_641_000 as Weight) + (1_684_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn on_initialize_per_trie_key(k: u32, ) -> Weight { - (11_282_000 as Weight) + (11_878_000 as Weight) // Standard Error: 0 .saturating_add((758_000 as Weight).saturating_mul(k as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) @@ -179,18 +178,18 @@ impl WeightInfo for SubstrateWeight { } // Storage: Contracts DeletionQueue (r:1 w:0) fn on_initialize_per_queue_item(q: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 5_000 - .saturating_add((1_892_000 as Weight).saturating_mul(q as Weight)) + (10_240_000 as Weight) + // Standard Error: 4_000 + .saturating_add((1_899_000 as Weight).saturating_mul(q as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Contracts PristineCode (r:1 w:0) // Storage: Contracts CodeStorage (r:0 w:1) fn reinstrument(c: u32, ) -> Weight { - (19_908_000 as Weight) + (18_012_000 as Weight) // Standard Error: 0 - .saturating_add((48_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((51_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -199,9 +198,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Timestamp Now (r:1 w:0) // Storage: System Account (r:1 w:1) fn call_with_code_per_byte(c: u32, ) -> Weight { - (200_315_000 as Weight) + (206_036_000 as Weight) // Standard Error: 0 - .saturating_add((51_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((52_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -213,9 +212,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts PristineCode (r:0 w:1) // Storage: Contracts OwnerInfoOf (r:0 w:1) fn instantiate_with_code(c: u32, s: u32, ) -> Weight { - (252_036_000 as Weight) + (243_162_000 as Weight) // Standard Error: 0 - .saturating_add((121_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((122_000 as Weight).saturating_mul(c as Weight)) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) @@ -228,7 +227,7 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:1 w:1) fn instantiate(s: u32, ) -> Weight { - (177_274_000 as Weight) + (180_607_000 as Weight) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(6 as Weight)) @@ -239,7 +238,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Timestamp Now (r:1 w:0) // Storage: System Account (r:1 w:1) fn call() -> Weight { - (146_086_000 as Weight) + (146_032_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -247,7 +246,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts PristineCode (r:0 w:1) // Storage: Contracts OwnerInfoOf (r:0 w:1) fn upload_code(c: u32, ) -> Weight { - (43_995_000 as Weight) + (45_113_000 as Weight) // Standard Error: 0 .saturating_add((49_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) @@ -257,14 +256,14 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:0 w:1) // Storage: Contracts PristineCode (r:0 w:1) fn remove_code() -> Weight { - (25_007_000 as Weight) + (25_722_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:2 w:2) fn set_code() -> Weight { - (22_940_000 as Weight) + (23_135_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -273,9 +272,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_caller(r: u32, ) -> Weight { - (207_387_000 as Weight) - // Standard Error: 88_000 - .saturating_add((40_577_000 as Weight).saturating_mul(r as Weight)) + (205_061_000 as Weight) + // Standard Error: 76_000 + .saturating_add((40_732_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -284,9 +283,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_is_contract(r: u32, ) -> Weight { - (96_692_000 as Weight) - // Standard Error: 688_000 - .saturating_add((313_348_000 as Weight).saturating_mul(r as Weight)) + (97_971_000 as Weight) + // Standard Error: 741_000 + .saturating_add((308_361_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -296,9 +295,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_code_hash(r: u32, ) -> Weight { - (107_076_000 as Weight) - // Standard Error: 721_000 - .saturating_add((370_976_000 as Weight).saturating_mul(r as Weight)) + (109_052_000 as Weight) + // Standard Error: 716_000 + .saturating_add((366_257_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -308,9 +307,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_own_code_hash(r: u32, ) -> Weight { - (207_147_000 as Weight) - // Standard Error: 112_000 - .saturating_add((43_974_000 as Weight).saturating_mul(r as Weight)) + (205_748_000 as Weight) + // Standard Error: 87_000 + .saturating_add((44_474_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -319,9 +318,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_caller_is_origin(r: u32, ) -> Weight { - (203_029_000 as Weight) - // Standard Error: 31_000 - .saturating_add((16_811_000 as Weight).saturating_mul(r as Weight)) + (201_898_000 as Weight) + // Standard Error: 55_000 + .saturating_add((16_703_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -330,9 +329,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_address(r: u32, ) -> Weight { - (206_641_000 as Weight) - // Standard Error: 86_000 - .saturating_add((40_239_000 as Weight).saturating_mul(r as Weight)) + (204_668_000 as Weight) + // Standard Error: 65_000 + .saturating_add((40_459_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -341,9 +340,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_gas_left(r: u32, ) -> Weight { - (205_195_000 as Weight) - // Standard Error: 85_000 - .saturating_add((39_915_000 as Weight).saturating_mul(r as Weight)) + (203_240_000 as Weight) + // Standard Error: 68_000 + .saturating_add((40_270_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -352,9 +351,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_balance(r: u32, ) -> Weight { - (213_345_000 as Weight) - // Standard Error: 126_000 - .saturating_add((116_249_000 as Weight).saturating_mul(r as Weight)) + (211_535_000 as Weight) + // Standard Error: 73_000 + .saturating_add((114_954_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -363,9 +362,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_value_transferred(r: u32, ) -> Weight { - (206_141_000 as Weight) - // Standard Error: 96_000 - .saturating_add((39_999_000 as Weight).saturating_mul(r as Weight)) + (204_653_000 as Weight) + // Standard Error: 71_000 + .saturating_add((40_188_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -374,9 +373,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_minimum_balance(r: u32, ) -> Weight { - (205_569_000 as Weight) - // Standard Error: 104_000 - .saturating_add((40_046_000 as Weight).saturating_mul(r as Weight)) + (204_690_000 as Weight) + // Standard Error: 82_000 + .saturating_add((40_260_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -385,9 +384,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_block_number(r: u32, ) -> Weight { - (205_782_000 as Weight) - // Standard Error: 89_000 - .saturating_add((39_757_000 as Weight).saturating_mul(r as Weight)) + (205_004_000 as Weight) + // Standard Error: 62_000 + .saturating_add((40_018_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -396,9 +395,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_now(r: u32, ) -> Weight { - (205_465_000 as Weight) - // Standard Error: 87_000 - .saturating_add((39_605_000 as Weight).saturating_mul(r as Weight)) + (204_341_000 as Weight) + // Standard Error: 93_000 + .saturating_add((39_920_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -408,9 +407,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Timestamp Now (r:1 w:0) // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) fn seal_weight_to_fee(r: u32, ) -> Weight { - (208_900_000 as Weight) - // Standard Error: 108_000 - .saturating_add((101_341_000 as Weight).saturating_mul(r as Weight)) + (208_702_000 as Weight) + // Standard Error: 115_000 + .saturating_add((101_441_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -419,9 +418,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_gas(r: u32, ) -> Weight { - (132_788_000 as Weight) - // Standard Error: 22_000 - .saturating_add((19_129_000 as Weight).saturating_mul(r as Weight)) + (131_983_000 as Weight) + // Standard Error: 17_000 + .saturating_add((19_153_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -430,9 +429,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_input(r: u32, ) -> Weight { - (205_111_000 as Weight) - // Standard Error: 96_000 - .saturating_add((39_317_000 as Weight).saturating_mul(r as Weight)) + (203_768_000 as Weight) + // Standard Error: 57_000 + .saturating_add((39_316_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -441,9 +440,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_input_per_kb(n: u32, ) -> Weight { - (263_644_000 as Weight) + (273_930_000 as Weight) // Standard Error: 3_000 - .saturating_add((9_593_000 as Weight).saturating_mul(n as Weight)) + .saturating_add((9_513_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -452,9 +451,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_return(r: u32, ) -> Weight { - (201_299_000 as Weight) - // Standard Error: 903_000 - .saturating_add((947_000 as Weight).saturating_mul(r as Weight)) + (199_311_000 as Weight) + // Standard Error: 601_000 + .saturating_add((2_181_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -463,9 +462,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_return_per_kb(n: u32, ) -> Weight { - (202_730_000 as Weight) + (201_130_000 as Weight) // Standard Error: 0 - .saturating_add((183_000 as Weight).saturating_mul(n as Weight)) + .saturating_add((184_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -476,9 +475,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts DeletionQueue (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:1 w:1) fn seal_terminate(r: u32, ) -> Weight { - (203_795_000 as Weight) - // Standard Error: 759_000 - .saturating_add((52_720_000 as Weight).saturating_mul(r as Weight)) + (202_063_000 as Weight) + // Standard Error: 100_000 + .saturating_add((54_190_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().reads((4 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -490,9 +489,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Timestamp Now (r:1 w:0) // Storage: RandomnessCollectiveFlip RandomMaterial (r:1 w:0) fn seal_random(r: u32, ) -> Weight { - (215_805_000 as Weight) - // Standard Error: 158_000 - .saturating_add((127_687_000 as Weight).saturating_mul(r as Weight)) + (206_528_000 as Weight) + // Standard Error: 120_000 + .saturating_add((136_384_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -501,9 +500,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_deposit_event(r: u32, ) -> Weight { - (215_300_000 as Weight) - // Standard Error: 143_000 - .saturating_add((229_183_000 as Weight).saturating_mul(r as Weight)) + (210_309_000 as Weight) + // Standard Error: 138_000 + .saturating_add((236_583_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -513,11 +512,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Timestamp Now (r:1 w:0) // Storage: System EventTopics (r:80 w:80) fn seal_deposit_event_per_topic_and_kb(t: u32, n: u32, ) -> Weight { - (459_865_000 as Weight) - // Standard Error: 1_733_000 - .saturating_add((235_448_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 341_000 - .saturating_add((66_363_000 as Weight).saturating_mul(n as Weight)) + (434_046_000 as Weight) + // Standard Error: 1_678_000 + .saturating_add((242_928_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 330_000 + .saturating_add((66_716_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().reads((80 as Weight).saturating_mul(t as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -528,17 +527,17 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_debug_message(r: u32, ) -> Weight { - (137_820_000 as Weight) - // Standard Error: 77_000 - .saturating_add((31_956_000 as Weight).saturating_mul(r as Weight)) + (138_934_000 as Weight) + // Standard Error: 34_000 + .saturating_add((31_927_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_set_storage(r: u32, ) -> Weight { - (94_007_000 as Weight) - // Standard Error: 589_000 - .saturating_add((328_631_000 as Weight).saturating_mul(r as Weight)) + (88_315_000 as Weight) + // Standard Error: 594_000 + .saturating_add((328_984_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -546,25 +545,25 @@ impl WeightInfo for SubstrateWeight { } // Storage: Skipped Metadata (r:0 w:0) fn seal_set_storage_per_new_kb(n: u32, ) -> Weight { - (528_794_000 as Weight) - // Standard Error: 233_000 - .saturating_add((21_533_000 as Weight).saturating_mul(n as Weight)) + (529_349_000 as Weight) + // Standard Error: 223_000 + .saturating_add((21_065_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(85 as Weight)) .saturating_add(T::DbWeight::get().writes(83 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_set_storage_per_old_kb(n: u32, ) -> Weight { - (543_507_000 as Weight) - // Standard Error: 270_000 - .saturating_add((9_142_000 as Weight).saturating_mul(n as Weight)) + (546_447_000 as Weight) + // Standard Error: 261_000 + .saturating_add((8_709_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(85 as Weight)) .saturating_add(T::DbWeight::get().writes(83 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_clear_storage(r: u32, ) -> Weight { - (129_893_000 as Weight) - // Standard Error: 471_000 - .saturating_add((313_807_000 as Weight).saturating_mul(r as Weight)) + (118_849_000 as Weight) + // Standard Error: 518_000 + .saturating_add((309_800_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(3 as Weight)) @@ -572,51 +571,51 @@ impl WeightInfo for SubstrateWeight { } // Storage: Skipped Metadata (r:0 w:0) fn seal_clear_storage_per_kb(n: u32, ) -> Weight { - (532_323_000 as Weight) - // Standard Error: 204_000 - .saturating_add((8_729_000 as Weight).saturating_mul(n as Weight)) + (537_039_000 as Weight) + // Standard Error: 235_000 + .saturating_add((8_071_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(85 as Weight)) .saturating_add(T::DbWeight::get().writes(83 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_get_storage(r: u32, ) -> Weight { - (131_771_000 as Weight) - // Standard Error: 415_000 - .saturating_add((269_174_000 as Weight).saturating_mul(r as Weight)) + (125_427_000 as Weight) + // Standard Error: 635_000 + .saturating_add((276_126_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_get_storage_per_kb(n: u32, ) -> Weight { - (494_409_000 as Weight) - // Standard Error: 280_000 - .saturating_add((50_499_000 as Weight).saturating_mul(n as Weight)) + (500_356_000 as Weight) + // Standard Error: 279_000 + .saturating_add((49_746_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(84 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_contains_storage(r: u32, ) -> Weight { - (133_366_000 as Weight) - // Standard Error: 400_000 - .saturating_add((235_761_000 as Weight).saturating_mul(r as Weight)) + (129_046_000 as Weight) + // Standard Error: 408_000 + .saturating_add((237_117_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_contains_storage_per_kb(n: u32, ) -> Weight { - (447_828_000 as Weight) - // Standard Error: 197_000 - .saturating_add((8_190_000 as Weight).saturating_mul(n as Weight)) + (451_122_000 as Weight) + // Standard Error: 200_000 + .saturating_add((7_750_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(84 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_take_storage(r: u32, ) -> Weight { - (121_933_000 as Weight) - // Standard Error: 509_000 - .saturating_add((342_550_000 as Weight).saturating_mul(r as Weight)) + (118_085_000 as Weight) + // Standard Error: 526_000 + .saturating_add((338_332_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(3 as Weight)) @@ -624,9 +623,9 @@ impl WeightInfo for SubstrateWeight { } // Storage: Skipped Metadata (r:0 w:0) fn seal_take_storage_per_kb(n: u32, ) -> Weight { - (578_648_000 as Weight) - // Standard Error: 320_000 - .saturating_add((51_767_000 as Weight).saturating_mul(n as Weight)) + (569_270_000 as Weight) + // Standard Error: 294_000 + .saturating_add((51_071_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(85 as Weight)) .saturating_add(T::DbWeight::get().writes(83 as Weight)) } @@ -635,9 +634,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_transfer(r: u32, ) -> Weight { - (146_890_000 as Weight) - // Standard Error: 789_000 - .saturating_add((1_427_230_000 as Weight).saturating_mul(r as Weight)) + (124_818_000 as Weight) + // Standard Error: 1_251_000 + .saturating_add((1_455_607_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(2 as Weight)) @@ -649,8 +648,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Timestamp Now (r:1 w:0) fn seal_call(r: u32, ) -> Weight { (0 as Weight) - // Standard Error: 2_542_000 - .saturating_add((15_063_367_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 4_575_000 + .saturating_add((14_645_061_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -662,8 +661,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Timestamp Now (r:1 w:0) fn seal_delegate_call(r: u32, ) -> Weight { (0 as Weight) - // Standard Error: 3_267_000 - .saturating_add((15_037_276_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 5_742_000 + .saturating_add((14_623_917_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads((79 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -672,11 +671,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:2 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_call_per_transfer_clone_kb(t: u32, c: u32, ) -> Weight { - (9_081_513_000 as Weight) - // Standard Error: 18_081_000 - .saturating_add((1_388_391_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 7_000 - .saturating_add((9_653_000 as Weight).saturating_mul(c as Weight)) + (9_081_635_000 as Weight) + // Standard Error: 11_326_000 + .saturating_add((1_335_139_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 4_000 + .saturating_add((9_575_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(85 as Weight)) .saturating_add(T::DbWeight::get().reads((81 as Weight).saturating_mul(t as Weight))) .saturating_add(T::DbWeight::get().writes(81 as Weight)) @@ -690,8 +689,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts OwnerInfoOf (r:80 w:80) fn seal_instantiate(r: u32, ) -> Weight { (0 as Weight) - // Standard Error: 34_560_000 - .saturating_add((20_828_575_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 34_958_000 + .saturating_add((20_700_850_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(6 as Weight)) .saturating_add(T::DbWeight::get().reads((320 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(3 as Weight)) @@ -704,11 +703,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts Nonce (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:1 w:1) fn seal_instantiate_per_transfer_salt_kb(t: u32, s: u32, ) -> Weight { - (12_124_898_000 as Weight) - // Standard Error: 34_430_000 - .saturating_add((573_971_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 16_000 - .saturating_add((123_415_000 as Weight).saturating_mul(s as Weight)) + (12_091_206_000 as Weight) + // Standard Error: 104_884_000 + .saturating_add((635_259_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 49_000 + .saturating_add((122_935_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(167 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(t as Weight))) .saturating_add(T::DbWeight::get().writes(165 as Weight)) @@ -719,9 +718,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_sha2_256(r: u32, ) -> Weight { - (203_422_000 as Weight) - // Standard Error: 126_000 - .saturating_add((65_317_000 as Weight).saturating_mul(r as Weight)) + (203_315_000 as Weight) + // Standard Error: 74_000 + .saturating_add((60_223_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -730,9 +729,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_sha2_256_per_kb(n: u32, ) -> Weight { - (411_079_000 as Weight) - // Standard Error: 20_000 - .saturating_add((354_947_000 as Weight).saturating_mul(n as Weight)) + (355_672_000 as Weight) + // Standard Error: 25_000 + .saturating_add((319_519_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -741,9 +740,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_keccak_256(r: u32, ) -> Weight { - (203_192_000 as Weight) - // Standard Error: 116_000 - .saturating_add((73_562_000 as Weight).saturating_mul(r as Weight)) + (203_117_000 as Weight) + // Standard Error: 94_000 + .saturating_add((77_363_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -752,9 +751,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_keccak_256_per_kb(n: u32, ) -> Weight { - (227_572_000 as Weight) + (196_575_000 as Weight) // Standard Error: 13_000 - .saturating_add((244_646_000 as Weight).saturating_mul(n as Weight)) + .saturating_add((243_479_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -763,9 +762,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_blake2_256(r: u32, ) -> Weight { - (204_922_000 as Weight) - // Standard Error: 112_000 - .saturating_add((51_349_000 as Weight).saturating_mul(r as Weight)) + (203_938_000 as Weight) + // Standard Error: 97_000 + .saturating_add((50_708_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -774,9 +773,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_blake2_256_per_kb(n: u32, ) -> Weight { - (247_293_000 as Weight) - // Standard Error: 17_000 - .saturating_add((94_654_000 as Weight).saturating_mul(n as Weight)) + (247_065_000 as Weight) + // Standard Error: 8_000 + .saturating_add((94_160_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -785,9 +784,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_blake2_128(r: u32, ) -> Weight { - (203_511_000 as Weight) - // Standard Error: 108_000 - .saturating_add((50_585_000 as Weight).saturating_mul(r as Weight)) + (204_389_000 as Weight) + // Standard Error: 86_000 + .saturating_add((50_663_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -796,9 +795,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_blake2_128_per_kb(n: u32, ) -> Weight { - (253_133_000 as Weight) - // Standard Error: 10_000 - .saturating_add((94_606_000 as Weight).saturating_mul(n as Weight)) + (284_700_000 as Weight) + // Standard Error: 9_000 + .saturating_add((94_231_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -807,9 +806,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_ecdsa_recover(r: u32, ) -> Weight { - (280_685_000 as Weight) - // Standard Error: 627_000 - .saturating_add((3_062_997_000 as Weight).saturating_mul(r as Weight)) + (235_813_000 as Weight) + // Standard Error: 521_000 + .saturating_add((3_044_204_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -818,9 +817,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_ecdsa_to_eth_address(r: u32, ) -> Weight { - (212_204_000 as Weight) - // Standard Error: 469_000 - .saturating_add((2_034_397_000 as Weight).saturating_mul(r as Weight)) + (204_095_000 as Weight) + // Standard Error: 495_000 + .saturating_add((2_027_914_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -831,265 +830,265 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts OwnerInfoOf (r:16 w:16) fn seal_set_code_hash(r: u32, ) -> Weight { (0 as Weight) - // Standard Error: 1_507_000 - .saturating_add((747_481_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 1_604_000 + .saturating_add((759_511_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads((79 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes((79 as Weight).saturating_mul(r as Weight))) } fn instr_i64const(r: u32, ) -> Weight { - (74_627_000 as Weight) + (74_210_000 as Weight) // Standard Error: 1_000 - .saturating_add((593_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((601_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64load(r: u32, ) -> Weight { - (74_587_000 as Weight) + (74_123_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_294_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_315_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64store(r: u32, ) -> Weight { - (74_392_000 as Weight) + (74_145_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_381_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_388_000 as Weight).saturating_mul(r as Weight)) } fn instr_select(r: u32, ) -> Weight { - (73_823_000 as Weight) - // Standard Error: 3_000 - .saturating_add((1_780_000 as Weight).saturating_mul(r as Weight)) + (73_931_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_768_000 as Weight).saturating_mul(r as Weight)) } fn instr_if(r: u32, ) -> Weight { - (74_072_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_946_000 as Weight).saturating_mul(r as Weight)) + (73_829_000 as Weight) + // Standard Error: 0 + .saturating_add((1_957_000 as Weight).saturating_mul(r as Weight)) } fn instr_br(r: u32, ) -> Weight { - (73_981_000 as Weight) - // Standard Error: 1_000 - .saturating_add((930_000 as Weight).saturating_mul(r as Weight)) + (73_760_000 as Weight) + // Standard Error: 0 + .saturating_add((932_000 as Weight).saturating_mul(r as Weight)) } fn instr_br_if(r: u32, ) -> Weight { - (73_690_000 as Weight) - // Standard Error: 3_000 - .saturating_add((1_433_000 as Weight).saturating_mul(r as Weight)) + (73_714_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_420_000 as Weight).saturating_mul(r as Weight)) } fn instr_br_table(r: u32, ) -> Weight { - (74_317_000 as Weight) - // Standard Error: 8_000 - .saturating_add((1_571_000 as Weight).saturating_mul(r as Weight)) + (73_496_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_575_000 as Weight).saturating_mul(r as Weight)) } fn instr_br_table_per_entry(e: u32, ) -> Weight { - (76_373_000 as Weight) + (76_036_000 as Weight) // Standard Error: 0 - .saturating_add((4_000 as Weight).saturating_mul(e as Weight)) + .saturating_add((5_000 as Weight).saturating_mul(e as Weight)) } fn instr_call(r: u32, ) -> Weight { - (76_013_000 as Weight) - // Standard Error: 10_000 - .saturating_add((6_848_000 as Weight).saturating_mul(r as Weight)) + (76_015_000 as Weight) + // Standard Error: 19_000 + .saturating_add((6_954_000 as Weight).saturating_mul(r as Weight)) } fn instr_call_indirect(r: u32, ) -> Weight { - (88_704_000 as Weight) - // Standard Error: 16_000 - .saturating_add((8_824_000 as Weight).saturating_mul(r as Weight)) + (88_247_000 as Weight) + // Standard Error: 9_000 + .saturating_add((8_957_000 as Weight).saturating_mul(r as Weight)) } fn instr_call_indirect_per_param(p: u32, ) -> Weight { - (97_846_000 as Weight) - // Standard Error: 1_000 - .saturating_add((471_000 as Weight).saturating_mul(p as Weight)) + (98_336_000 as Weight) + // Standard Error: 2_000 + .saturating_add((474_000 as Weight).saturating_mul(p as Weight)) } fn instr_local_get(r: u32, ) -> Weight { - (75_010_000 as Weight) - // Standard Error: 1_000 - .saturating_add((612_000 as Weight).saturating_mul(r as Weight)) + (74_565_000 as Weight) + // Standard Error: 4_000 + .saturating_add((627_000 as Weight).saturating_mul(r as Weight)) } fn instr_local_set(r: u32, ) -> Weight { - (74_636_000 as Weight) + (74_414_000 as Weight) // Standard Error: 1_000 - .saturating_add((681_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((684_000 as Weight).saturating_mul(r as Weight)) } fn instr_local_tee(r: u32, ) -> Weight { - (74_196_000 as Weight) - // Standard Error: 0 - .saturating_add((916_000 as Weight).saturating_mul(r as Weight)) + (74_346_000 as Weight) + // Standard Error: 1_000 + .saturating_add((911_000 as Weight).saturating_mul(r as Weight)) } fn instr_global_get(r: u32, ) -> Weight { - (77_349_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_176_000 as Weight).saturating_mul(r as Weight)) + (76_649_000 as Weight) + // Standard Error: 0 + .saturating_add((1_183_000 as Weight).saturating_mul(r as Weight)) } fn instr_global_set(r: u32, ) -> Weight { - (76_861_000 as Weight) - // Standard Error: 4_000 - .saturating_add((1_383_000 as Weight).saturating_mul(r as Weight)) + (76_995_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_370_000 as Weight).saturating_mul(r as Weight)) } fn instr_memory_current(r: u32, ) -> Weight { - (74_696_000 as Weight) - // Standard Error: 2_000 - .saturating_add((655_000 as Weight).saturating_mul(r as Weight)) + (73_927_000 as Weight) + // Standard Error: 1_000 + .saturating_add((666_000 as Weight).saturating_mul(r as Weight)) } fn instr_memory_grow(r: u32, ) -> Weight { - (73_522_000 as Weight) - // Standard Error: 50_000 - .saturating_add((179_720_000 as Weight).saturating_mul(r as Weight)) + (73_479_000 as Weight) + // Standard Error: 24_000 + .saturating_add((180_808_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64clz(r: u32, ) -> Weight { - (74_292_000 as Weight) + (74_048_000 as Weight) // Standard Error: 1_000 - .saturating_add((884_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((885_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64ctz(r: u32, ) -> Weight { - (74_299_000 as Weight) + (73_894_000 as Weight) // Standard Error: 1_000 - .saturating_add((882_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((889_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64popcnt(r: u32, ) -> Weight { - (74_289_000 as Weight) - // Standard Error: 1_000 - .saturating_add((883_000 as Weight).saturating_mul(r as Weight)) + (73_728_000 as Weight) + // Standard Error: 0 + .saturating_add((896_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64eqz(r: u32, ) -> Weight { - (74_120_000 as Weight) - // Standard Error: 2_000 - .saturating_add((899_000 as Weight).saturating_mul(r as Weight)) + (74_049_000 as Weight) + // Standard Error: 1_000 + .saturating_add((897_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64extendsi32(r: u32, ) -> Weight { - (74_599_000 as Weight) + (74_118_000 as Weight) // Standard Error: 1_000 - .saturating_add((851_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((863_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64extendui32(r: u32, ) -> Weight { - (74_622_000 as Weight) - // Standard Error: 2_000 - .saturating_add((854_000 as Weight).saturating_mul(r as Weight)) + (74_042_000 as Weight) + // Standard Error: 1_000 + .saturating_add((865_000 as Weight).saturating_mul(r as Weight)) } fn instr_i32wrapi64(r: u32, ) -> Weight { - (74_504_000 as Weight) - // Standard Error: 2_000 - .saturating_add((874_000 as Weight).saturating_mul(r as Weight)) + (73_885_000 as Weight) + // Standard Error: 1_000 + .saturating_add((884_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64eq(r: u32, ) -> Weight { - (74_532_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_338_000 as Weight).saturating_mul(r as Weight)) + (73_788_000 as Weight) + // Standard Error: 0 + .saturating_add((1_355_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64ne(r: u32, ) -> Weight { - (74_130_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_347_000 as Weight).saturating_mul(r as Weight)) + (73_727_000 as Weight) + // Standard Error: 0 + .saturating_add((1_352_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64lts(r: u32, ) -> Weight { - (74_172_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_343_000 as Weight).saturating_mul(r as Weight)) + (73_825_000 as Weight) + // Standard Error: 0 + .saturating_add((1_351_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64ltu(r: u32, ) -> Weight { - (74_356_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_338_000 as Weight).saturating_mul(r as Weight)) + (73_638_000 as Weight) + // Standard Error: 0 + .saturating_add((1_355_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64gts(r: u32, ) -> Weight { - (74_181_000 as Weight) + (73_688_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_343_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_357_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64gtu(r: u32, ) -> Weight { - (74_045_000 as Weight) + (73_895_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_358_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_359_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64les(r: u32, ) -> Weight { - (74_246_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_341_000 as Weight).saturating_mul(r as Weight)) + (73_860_000 as Weight) + // Standard Error: 0 + .saturating_add((1_350_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64leu(r: u32, ) -> Weight { - (73_998_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_363_000 as Weight).saturating_mul(r as Weight)) + (73_864_000 as Weight) + // Standard Error: 0 + .saturating_add((1_361_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64ges(r: u32, ) -> Weight { - (74_035_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_348_000 as Weight).saturating_mul(r as Weight)) + (72_730_000 as Weight) + // Standard Error: 7_000 + .saturating_add((1_432_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64geu(r: u32, ) -> Weight { - (74_511_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_335_000 as Weight).saturating_mul(r as Weight)) + (73_998_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_346_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64add(r: u32, ) -> Weight { - (74_305_000 as Weight) + (73_708_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_319_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_333_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64sub(r: u32, ) -> Weight { - (74_214_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_321_000 as Weight).saturating_mul(r as Weight)) + (74_033_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_322_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64mul(r: u32, ) -> Weight { - (74_073_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_323_000 as Weight).saturating_mul(r as Weight)) + (74_203_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_322_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64divs(r: u32, ) -> Weight { - (74_295_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_986_000 as Weight).saturating_mul(r as Weight)) + (73_990_000 as Weight) + // Standard Error: 1_000 + .saturating_add((2_010_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64divu(r: u32, ) -> Weight { - (74_109_000 as Weight) - // Standard Error: 2_000 - .saturating_add((2_023_000 as Weight).saturating_mul(r as Weight)) + (73_918_000 as Weight) + // Standard Error: 1_000 + .saturating_add((2_019_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64rems(r: u32, ) -> Weight { - (73_990_000 as Weight) + (73_927_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_993_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((2_001_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64remu(r: u32, ) -> Weight { - (73_940_000 as Weight) - // Standard Error: 1_000 - .saturating_add((2_056_000 as Weight).saturating_mul(r as Weight)) + (73_691_000 as Weight) + // Standard Error: 0 + .saturating_add((2_062_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64and(r: u32, ) -> Weight { - (74_261_000 as Weight) - // Standard Error: 5_000 - .saturating_add((1_326_000 as Weight).saturating_mul(r as Weight)) + (73_869_000 as Weight) + // Standard Error: 0 + .saturating_add((1_327_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64or(r: u32, ) -> Weight { - (74_014_000 as Weight) + (73_890_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_324_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_327_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64xor(r: u32, ) -> Weight { - (74_220_000 as Weight) + (73_866_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_323_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_327_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64shl(r: u32, ) -> Weight { - (74_139_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_343_000 as Weight).saturating_mul(r as Weight)) + (73_793_000 as Weight) + // Standard Error: 0 + .saturating_add((1_352_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64shrs(r: u32, ) -> Weight { - (73_923_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_356_000 as Weight).saturating_mul(r as Weight)) + (73_695_000 as Weight) + // Standard Error: 0 + .saturating_add((1_354_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64shru(r: u32, ) -> Weight { - (73_999_000 as Weight) - // Standard Error: 1_000 + (73_743_000 as Weight) + // Standard Error: 0 .saturating_add((1_352_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64rotl(r: u32, ) -> Weight { - (74_312_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_340_000 as Weight).saturating_mul(r as Weight)) + (73_781_000 as Weight) + // Standard Error: 0 + .saturating_add((1_352_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64rotr(r: u32, ) -> Weight { - (73_988_000 as Weight) + (73_941_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_349_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_348_000 as Weight).saturating_mul(r as Weight)) } } @@ -1097,12 +1096,12 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { // Storage: Contracts DeletionQueue (r:1 w:0) fn on_initialize() -> Weight { - (1_641_000 as Weight) + (1_684_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn on_initialize_per_trie_key(k: u32, ) -> Weight { - (11_282_000 as Weight) + (11_878_000 as Weight) // Standard Error: 0 .saturating_add((758_000 as Weight).saturating_mul(k as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) @@ -1111,18 +1110,18 @@ impl WeightInfo for () { } // Storage: Contracts DeletionQueue (r:1 w:0) fn on_initialize_per_queue_item(q: u32, ) -> Weight { - (0 as Weight) - // Standard Error: 5_000 - .saturating_add((1_892_000 as Weight).saturating_mul(q as Weight)) + (10_240_000 as Weight) + // Standard Error: 4_000 + .saturating_add((1_899_000 as Weight).saturating_mul(q as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Contracts PristineCode (r:1 w:0) // Storage: Contracts CodeStorage (r:0 w:1) fn reinstrument(c: u32, ) -> Weight { - (19_908_000 as Weight) + (18_012_000 as Weight) // Standard Error: 0 - .saturating_add((48_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((51_000 as Weight).saturating_mul(c as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1131,9 +1130,9 @@ impl WeightInfo for () { // Storage: Timestamp Now (r:1 w:0) // Storage: System Account (r:1 w:1) fn call_with_code_per_byte(c: u32, ) -> Weight { - (200_315_000 as Weight) + (206_036_000 as Weight) // Standard Error: 0 - .saturating_add((51_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((52_000 as Weight).saturating_mul(c as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -1145,9 +1144,9 @@ impl WeightInfo for () { // Storage: Contracts PristineCode (r:0 w:1) // Storage: Contracts OwnerInfoOf (r:0 w:1) fn instantiate_with_code(c: u32, s: u32, ) -> Weight { - (252_036_000 as Weight) + (243_162_000 as Weight) // Standard Error: 0 - .saturating_add((121_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((122_000 as Weight).saturating_mul(c as Weight)) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) @@ -1160,7 +1159,7 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:1 w:1) fn instantiate(s: u32, ) -> Weight { - (177_274_000 as Weight) + (180_607_000 as Weight) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(6 as Weight)) @@ -1171,7 +1170,7 @@ impl WeightInfo for () { // Storage: Timestamp Now (r:1 w:0) // Storage: System Account (r:1 w:1) fn call() -> Weight { - (146_086_000 as Weight) + (146_032_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -1179,7 +1178,7 @@ impl WeightInfo for () { // Storage: Contracts PristineCode (r:0 w:1) // Storage: Contracts OwnerInfoOf (r:0 w:1) fn upload_code(c: u32, ) -> Weight { - (43_995_000 as Weight) + (45_113_000 as Weight) // Standard Error: 0 .saturating_add((49_000 as Weight).saturating_mul(c as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) @@ -1189,14 +1188,14 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:0 w:1) // Storage: Contracts PristineCode (r:0 w:1) fn remove_code() -> Weight { - (25_007_000 as Weight) + (25_722_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:2 w:2) fn set_code() -> Weight { - (22_940_000 as Weight) + (23_135_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -1205,9 +1204,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_caller(r: u32, ) -> Weight { - (207_387_000 as Weight) - // Standard Error: 88_000 - .saturating_add((40_577_000 as Weight).saturating_mul(r as Weight)) + (205_061_000 as Weight) + // Standard Error: 76_000 + .saturating_add((40_732_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1216,9 +1215,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_is_contract(r: u32, ) -> Weight { - (96_692_000 as Weight) - // Standard Error: 688_000 - .saturating_add((313_348_000 as Weight).saturating_mul(r as Weight)) + (97_971_000 as Weight) + // Standard Error: 741_000 + .saturating_add((308_361_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -1228,9 +1227,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_code_hash(r: u32, ) -> Weight { - (107_076_000 as Weight) - // Standard Error: 721_000 - .saturating_add((370_976_000 as Weight).saturating_mul(r as Weight)) + (109_052_000 as Weight) + // Standard Error: 716_000 + .saturating_add((366_257_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -1240,9 +1239,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_own_code_hash(r: u32, ) -> Weight { - (207_147_000 as Weight) - // Standard Error: 112_000 - .saturating_add((43_974_000 as Weight).saturating_mul(r as Weight)) + (205_748_000 as Weight) + // Standard Error: 87_000 + .saturating_add((44_474_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1251,9 +1250,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_caller_is_origin(r: u32, ) -> Weight { - (203_029_000 as Weight) - // Standard Error: 31_000 - .saturating_add((16_811_000 as Weight).saturating_mul(r as Weight)) + (201_898_000 as Weight) + // Standard Error: 55_000 + .saturating_add((16_703_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1262,9 +1261,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_address(r: u32, ) -> Weight { - (206_641_000 as Weight) - // Standard Error: 86_000 - .saturating_add((40_239_000 as Weight).saturating_mul(r as Weight)) + (204_668_000 as Weight) + // Standard Error: 65_000 + .saturating_add((40_459_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1273,9 +1272,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_gas_left(r: u32, ) -> Weight { - (205_195_000 as Weight) - // Standard Error: 85_000 - .saturating_add((39_915_000 as Weight).saturating_mul(r as Weight)) + (203_240_000 as Weight) + // Standard Error: 68_000 + .saturating_add((40_270_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1284,9 +1283,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_balance(r: u32, ) -> Weight { - (213_345_000 as Weight) - // Standard Error: 126_000 - .saturating_add((116_249_000 as Weight).saturating_mul(r as Weight)) + (211_535_000 as Weight) + // Standard Error: 73_000 + .saturating_add((114_954_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1295,9 +1294,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_value_transferred(r: u32, ) -> Weight { - (206_141_000 as Weight) - // Standard Error: 96_000 - .saturating_add((39_999_000 as Weight).saturating_mul(r as Weight)) + (204_653_000 as Weight) + // Standard Error: 71_000 + .saturating_add((40_188_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1306,9 +1305,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_minimum_balance(r: u32, ) -> Weight { - (205_569_000 as Weight) - // Standard Error: 104_000 - .saturating_add((40_046_000 as Weight).saturating_mul(r as Weight)) + (204_690_000 as Weight) + // Standard Error: 82_000 + .saturating_add((40_260_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1317,9 +1316,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_block_number(r: u32, ) -> Weight { - (205_782_000 as Weight) - // Standard Error: 89_000 - .saturating_add((39_757_000 as Weight).saturating_mul(r as Weight)) + (205_004_000 as Weight) + // Standard Error: 62_000 + .saturating_add((40_018_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1328,9 +1327,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_now(r: u32, ) -> Weight { - (205_465_000 as Weight) - // Standard Error: 87_000 - .saturating_add((39_605_000 as Weight).saturating_mul(r as Weight)) + (204_341_000 as Weight) + // Standard Error: 93_000 + .saturating_add((39_920_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1340,9 +1339,9 @@ impl WeightInfo for () { // Storage: Timestamp Now (r:1 w:0) // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) fn seal_weight_to_fee(r: u32, ) -> Weight { - (208_900_000 as Weight) - // Standard Error: 108_000 - .saturating_add((101_341_000 as Weight).saturating_mul(r as Weight)) + (208_702_000 as Weight) + // Standard Error: 115_000 + .saturating_add((101_441_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1351,9 +1350,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_gas(r: u32, ) -> Weight { - (132_788_000 as Weight) - // Standard Error: 22_000 - .saturating_add((19_129_000 as Weight).saturating_mul(r as Weight)) + (131_983_000 as Weight) + // Standard Error: 17_000 + .saturating_add((19_153_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1362,9 +1361,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_input(r: u32, ) -> Weight { - (205_111_000 as Weight) - // Standard Error: 96_000 - .saturating_add((39_317_000 as Weight).saturating_mul(r as Weight)) + (203_768_000 as Weight) + // Standard Error: 57_000 + .saturating_add((39_316_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1373,9 +1372,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_input_per_kb(n: u32, ) -> Weight { - (263_644_000 as Weight) + (273_930_000 as Weight) // Standard Error: 3_000 - .saturating_add((9_593_000 as Weight).saturating_mul(n as Weight)) + .saturating_add((9_513_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1384,9 +1383,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_return(r: u32, ) -> Weight { - (201_299_000 as Weight) - // Standard Error: 903_000 - .saturating_add((947_000 as Weight).saturating_mul(r as Weight)) + (199_311_000 as Weight) + // Standard Error: 601_000 + .saturating_add((2_181_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1395,9 +1394,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_return_per_kb(n: u32, ) -> Weight { - (202_730_000 as Weight) + (201_130_000 as Weight) // Standard Error: 0 - .saturating_add((183_000 as Weight).saturating_mul(n as Weight)) + .saturating_add((184_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1408,9 +1407,9 @@ impl WeightInfo for () { // Storage: Contracts DeletionQueue (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:1 w:1) fn seal_terminate(r: u32, ) -> Weight { - (203_795_000 as Weight) - // Standard Error: 759_000 - .saturating_add((52_720_000 as Weight).saturating_mul(r as Weight)) + (202_063_000 as Weight) + // Standard Error: 100_000 + .saturating_add((54_190_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().reads((4 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -1422,9 +1421,9 @@ impl WeightInfo for () { // Storage: Timestamp Now (r:1 w:0) // Storage: RandomnessCollectiveFlip RandomMaterial (r:1 w:0) fn seal_random(r: u32, ) -> Weight { - (215_805_000 as Weight) - // Standard Error: 158_000 - .saturating_add((127_687_000 as Weight).saturating_mul(r as Weight)) + (206_528_000 as Weight) + // Standard Error: 120_000 + .saturating_add((136_384_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1433,9 +1432,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_deposit_event(r: u32, ) -> Weight { - (215_300_000 as Weight) - // Standard Error: 143_000 - .saturating_add((229_183_000 as Weight).saturating_mul(r as Weight)) + (210_309_000 as Weight) + // Standard Error: 138_000 + .saturating_add((236_583_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1445,11 +1444,11 @@ impl WeightInfo for () { // Storage: Timestamp Now (r:1 w:0) // Storage: System EventTopics (r:80 w:80) fn seal_deposit_event_per_topic_and_kb(t: u32, n: u32, ) -> Weight { - (459_865_000 as Weight) - // Standard Error: 1_733_000 - .saturating_add((235_448_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 341_000 - .saturating_add((66_363_000 as Weight).saturating_mul(n as Weight)) + (434_046_000 as Weight) + // Standard Error: 1_678_000 + .saturating_add((242_928_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 330_000 + .saturating_add((66_716_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().reads((80 as Weight).saturating_mul(t as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -1460,17 +1459,17 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_debug_message(r: u32, ) -> Weight { - (137_820_000 as Weight) - // Standard Error: 77_000 - .saturating_add((31_956_000 as Weight).saturating_mul(r as Weight)) + (138_934_000 as Weight) + // Standard Error: 34_000 + .saturating_add((31_927_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_set_storage(r: u32, ) -> Weight { - (94_007_000 as Weight) - // Standard Error: 589_000 - .saturating_add((328_631_000 as Weight).saturating_mul(r as Weight)) + (88_315_000 as Weight) + // Standard Error: 594_000 + .saturating_add((328_984_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -1478,25 +1477,25 @@ impl WeightInfo for () { } // Storage: Skipped Metadata (r:0 w:0) fn seal_set_storage_per_new_kb(n: u32, ) -> Weight { - (528_794_000 as Weight) - // Standard Error: 233_000 - .saturating_add((21_533_000 as Weight).saturating_mul(n as Weight)) + (529_349_000 as Weight) + // Standard Error: 223_000 + .saturating_add((21_065_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(85 as Weight)) .saturating_add(RocksDbWeight::get().writes(83 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_set_storage_per_old_kb(n: u32, ) -> Weight { - (543_507_000 as Weight) - // Standard Error: 270_000 - .saturating_add((9_142_000 as Weight).saturating_mul(n as Weight)) + (546_447_000 as Weight) + // Standard Error: 261_000 + .saturating_add((8_709_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(85 as Weight)) .saturating_add(RocksDbWeight::get().writes(83 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_clear_storage(r: u32, ) -> Weight { - (129_893_000 as Weight) - // Standard Error: 471_000 - .saturating_add((313_807_000 as Weight).saturating_mul(r as Weight)) + (118_849_000 as Weight) + // Standard Error: 518_000 + .saturating_add((309_800_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) @@ -1504,51 +1503,51 @@ impl WeightInfo for () { } // Storage: Skipped Metadata (r:0 w:0) fn seal_clear_storage_per_kb(n: u32, ) -> Weight { - (532_323_000 as Weight) - // Standard Error: 204_000 - .saturating_add((8_729_000 as Weight).saturating_mul(n as Weight)) + (537_039_000 as Weight) + // Standard Error: 235_000 + .saturating_add((8_071_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(85 as Weight)) .saturating_add(RocksDbWeight::get().writes(83 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_get_storage(r: u32, ) -> Weight { - (131_771_000 as Weight) - // Standard Error: 415_000 - .saturating_add((269_174_000 as Weight).saturating_mul(r as Weight)) + (125_427_000 as Weight) + // Standard Error: 635_000 + .saturating_add((276_126_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_get_storage_per_kb(n: u32, ) -> Weight { - (494_409_000 as Weight) - // Standard Error: 280_000 - .saturating_add((50_499_000 as Weight).saturating_mul(n as Weight)) + (500_356_000 as Weight) + // Standard Error: 279_000 + .saturating_add((49_746_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(84 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_contains_storage(r: u32, ) -> Weight { - (133_366_000 as Weight) - // Standard Error: 400_000 - .saturating_add((235_761_000 as Weight).saturating_mul(r as Weight)) + (129_046_000 as Weight) + // Standard Error: 408_000 + .saturating_add((237_117_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_contains_storage_per_kb(n: u32, ) -> Weight { - (447_828_000 as Weight) - // Standard Error: 197_000 - .saturating_add((8_190_000 as Weight).saturating_mul(n as Weight)) + (451_122_000 as Weight) + // Standard Error: 200_000 + .saturating_add((7_750_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(84 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn seal_take_storage(r: u32, ) -> Weight { - (121_933_000 as Weight) - // Standard Error: 509_000 - .saturating_add((342_550_000 as Weight).saturating_mul(r as Weight)) + (118_085_000 as Weight) + // Standard Error: 526_000 + .saturating_add((338_332_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) @@ -1556,9 +1555,9 @@ impl WeightInfo for () { } // Storage: Skipped Metadata (r:0 w:0) fn seal_take_storage_per_kb(n: u32, ) -> Weight { - (578_648_000 as Weight) - // Standard Error: 320_000 - .saturating_add((51_767_000 as Weight).saturating_mul(n as Weight)) + (569_270_000 as Weight) + // Standard Error: 294_000 + .saturating_add((51_071_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(85 as Weight)) .saturating_add(RocksDbWeight::get().writes(83 as Weight)) } @@ -1567,9 +1566,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_transfer(r: u32, ) -> Weight { - (146_890_000 as Weight) - // Standard Error: 789_000 - .saturating_add((1_427_230_000 as Weight).saturating_mul(r as Weight)) + (124_818_000 as Weight) + // Standard Error: 1_251_000 + .saturating_add((1_455_607_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) @@ -1581,8 +1580,8 @@ impl WeightInfo for () { // Storage: Timestamp Now (r:1 w:0) fn seal_call(r: u32, ) -> Weight { (0 as Weight) - // Standard Error: 2_542_000 - .saturating_add((15_063_367_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 4_575_000 + .saturating_add((14_645_061_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -1594,8 +1593,8 @@ impl WeightInfo for () { // Storage: Timestamp Now (r:1 w:0) fn seal_delegate_call(r: u32, ) -> Weight { (0 as Weight) - // Standard Error: 3_267_000 - .saturating_add((15_037_276_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 5_742_000 + .saturating_add((14_623_917_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads((79 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1604,11 +1603,11 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:2 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_call_per_transfer_clone_kb(t: u32, c: u32, ) -> Weight { - (9_081_513_000 as Weight) - // Standard Error: 18_081_000 - .saturating_add((1_388_391_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 7_000 - .saturating_add((9_653_000 as Weight).saturating_mul(c as Weight)) + (9_081_635_000 as Weight) + // Standard Error: 11_326_000 + .saturating_add((1_335_139_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 4_000 + .saturating_add((9_575_000 as Weight).saturating_mul(c as Weight)) .saturating_add(RocksDbWeight::get().reads(85 as Weight)) .saturating_add(RocksDbWeight::get().reads((81 as Weight).saturating_mul(t as Weight))) .saturating_add(RocksDbWeight::get().writes(81 as Weight)) @@ -1622,8 +1621,8 @@ impl WeightInfo for () { // Storage: Contracts OwnerInfoOf (r:80 w:80) fn seal_instantiate(r: u32, ) -> Weight { (0 as Weight) - // Standard Error: 34_560_000 - .saturating_add((20_828_575_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 34_958_000 + .saturating_add((20_700_850_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(6 as Weight)) .saturating_add(RocksDbWeight::get().reads((320 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) @@ -1636,11 +1635,11 @@ impl WeightInfo for () { // Storage: Contracts Nonce (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:1 w:1) fn seal_instantiate_per_transfer_salt_kb(t: u32, s: u32, ) -> Weight { - (12_124_898_000 as Weight) - // Standard Error: 34_430_000 - .saturating_add((573_971_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 16_000 - .saturating_add((123_415_000 as Weight).saturating_mul(s as Weight)) + (12_091_206_000 as Weight) + // Standard Error: 104_884_000 + .saturating_add((635_259_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 49_000 + .saturating_add((122_935_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(167 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(t as Weight))) .saturating_add(RocksDbWeight::get().writes(165 as Weight)) @@ -1651,9 +1650,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_sha2_256(r: u32, ) -> Weight { - (203_422_000 as Weight) - // Standard Error: 126_000 - .saturating_add((65_317_000 as Weight).saturating_mul(r as Weight)) + (203_315_000 as Weight) + // Standard Error: 74_000 + .saturating_add((60_223_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1662,9 +1661,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_sha2_256_per_kb(n: u32, ) -> Weight { - (411_079_000 as Weight) - // Standard Error: 20_000 - .saturating_add((354_947_000 as Weight).saturating_mul(n as Weight)) + (355_672_000 as Weight) + // Standard Error: 25_000 + .saturating_add((319_519_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1673,9 +1672,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_keccak_256(r: u32, ) -> Weight { - (203_192_000 as Weight) - // Standard Error: 116_000 - .saturating_add((73_562_000 as Weight).saturating_mul(r as Weight)) + (203_117_000 as Weight) + // Standard Error: 94_000 + .saturating_add((77_363_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1684,9 +1683,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_keccak_256_per_kb(n: u32, ) -> Weight { - (227_572_000 as Weight) + (196_575_000 as Weight) // Standard Error: 13_000 - .saturating_add((244_646_000 as Weight).saturating_mul(n as Weight)) + .saturating_add((243_479_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1695,9 +1694,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_blake2_256(r: u32, ) -> Weight { - (204_922_000 as Weight) - // Standard Error: 112_000 - .saturating_add((51_349_000 as Weight).saturating_mul(r as Weight)) + (203_938_000 as Weight) + // Standard Error: 97_000 + .saturating_add((50_708_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1706,9 +1705,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_blake2_256_per_kb(n: u32, ) -> Weight { - (247_293_000 as Weight) - // Standard Error: 17_000 - .saturating_add((94_654_000 as Weight).saturating_mul(n as Weight)) + (247_065_000 as Weight) + // Standard Error: 8_000 + .saturating_add((94_160_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1717,9 +1716,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_blake2_128(r: u32, ) -> Weight { - (203_511_000 as Weight) - // Standard Error: 108_000 - .saturating_add((50_585_000 as Weight).saturating_mul(r as Weight)) + (204_389_000 as Weight) + // Standard Error: 86_000 + .saturating_add((50_663_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1728,9 +1727,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_hash_blake2_128_per_kb(n: u32, ) -> Weight { - (253_133_000 as Weight) - // Standard Error: 10_000 - .saturating_add((94_606_000 as Weight).saturating_mul(n as Weight)) + (284_700_000 as Weight) + // Standard Error: 9_000 + .saturating_add((94_231_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1739,9 +1738,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_ecdsa_recover(r: u32, ) -> Weight { - (280_685_000 as Weight) - // Standard Error: 627_000 - .saturating_add((3_062_997_000 as Weight).saturating_mul(r as Weight)) + (235_813_000 as Weight) + // Standard Error: 521_000 + .saturating_add((3_044_204_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1750,9 +1749,9 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) fn seal_ecdsa_to_eth_address(r: u32, ) -> Weight { - (212_204_000 as Weight) - // Standard Error: 469_000 - .saturating_add((2_034_397_000 as Weight).saturating_mul(r as Weight)) + (204_095_000 as Weight) + // Standard Error: 495_000 + .saturating_add((2_027_914_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1763,264 +1762,264 @@ impl WeightInfo for () { // Storage: Contracts OwnerInfoOf (r:16 w:16) fn seal_set_code_hash(r: u32, ) -> Weight { (0 as Weight) - // Standard Error: 1_507_000 - .saturating_add((747_481_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 1_604_000 + .saturating_add((759_511_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads((79 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes((79 as Weight).saturating_mul(r as Weight))) } fn instr_i64const(r: u32, ) -> Weight { - (74_627_000 as Weight) + (74_210_000 as Weight) // Standard Error: 1_000 - .saturating_add((593_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((601_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64load(r: u32, ) -> Weight { - (74_587_000 as Weight) + (74_123_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_294_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_315_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64store(r: u32, ) -> Weight { - (74_392_000 as Weight) + (74_145_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_381_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_388_000 as Weight).saturating_mul(r as Weight)) } fn instr_select(r: u32, ) -> Weight { - (73_823_000 as Weight) - // Standard Error: 3_000 - .saturating_add((1_780_000 as Weight).saturating_mul(r as Weight)) + (73_931_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_768_000 as Weight).saturating_mul(r as Weight)) } fn instr_if(r: u32, ) -> Weight { - (74_072_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_946_000 as Weight).saturating_mul(r as Weight)) + (73_829_000 as Weight) + // Standard Error: 0 + .saturating_add((1_957_000 as Weight).saturating_mul(r as Weight)) } fn instr_br(r: u32, ) -> Weight { - (73_981_000 as Weight) - // Standard Error: 1_000 - .saturating_add((930_000 as Weight).saturating_mul(r as Weight)) + (73_760_000 as Weight) + // Standard Error: 0 + .saturating_add((932_000 as Weight).saturating_mul(r as Weight)) } fn instr_br_if(r: u32, ) -> Weight { - (73_690_000 as Weight) - // Standard Error: 3_000 - .saturating_add((1_433_000 as Weight).saturating_mul(r as Weight)) + (73_714_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_420_000 as Weight).saturating_mul(r as Weight)) } fn instr_br_table(r: u32, ) -> Weight { - (74_317_000 as Weight) - // Standard Error: 8_000 - .saturating_add((1_571_000 as Weight).saturating_mul(r as Weight)) + (73_496_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_575_000 as Weight).saturating_mul(r as Weight)) } fn instr_br_table_per_entry(e: u32, ) -> Weight { - (76_373_000 as Weight) + (76_036_000 as Weight) // Standard Error: 0 - .saturating_add((4_000 as Weight).saturating_mul(e as Weight)) + .saturating_add((5_000 as Weight).saturating_mul(e as Weight)) } fn instr_call(r: u32, ) -> Weight { - (76_013_000 as Weight) - // Standard Error: 10_000 - .saturating_add((6_848_000 as Weight).saturating_mul(r as Weight)) + (76_015_000 as Weight) + // Standard Error: 19_000 + .saturating_add((6_954_000 as Weight).saturating_mul(r as Weight)) } fn instr_call_indirect(r: u32, ) -> Weight { - (88_704_000 as Weight) - // Standard Error: 16_000 - .saturating_add((8_824_000 as Weight).saturating_mul(r as Weight)) + (88_247_000 as Weight) + // Standard Error: 9_000 + .saturating_add((8_957_000 as Weight).saturating_mul(r as Weight)) } fn instr_call_indirect_per_param(p: u32, ) -> Weight { - (97_846_000 as Weight) - // Standard Error: 1_000 - .saturating_add((471_000 as Weight).saturating_mul(p as Weight)) + (98_336_000 as Weight) + // Standard Error: 2_000 + .saturating_add((474_000 as Weight).saturating_mul(p as Weight)) } fn instr_local_get(r: u32, ) -> Weight { - (75_010_000 as Weight) - // Standard Error: 1_000 - .saturating_add((612_000 as Weight).saturating_mul(r as Weight)) + (74_565_000 as Weight) + // Standard Error: 4_000 + .saturating_add((627_000 as Weight).saturating_mul(r as Weight)) } fn instr_local_set(r: u32, ) -> Weight { - (74_636_000 as Weight) + (74_414_000 as Weight) // Standard Error: 1_000 - .saturating_add((681_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((684_000 as Weight).saturating_mul(r as Weight)) } fn instr_local_tee(r: u32, ) -> Weight { - (74_196_000 as Weight) - // Standard Error: 0 - .saturating_add((916_000 as Weight).saturating_mul(r as Weight)) + (74_346_000 as Weight) + // Standard Error: 1_000 + .saturating_add((911_000 as Weight).saturating_mul(r as Weight)) } fn instr_global_get(r: u32, ) -> Weight { - (77_349_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_176_000 as Weight).saturating_mul(r as Weight)) + (76_649_000 as Weight) + // Standard Error: 0 + .saturating_add((1_183_000 as Weight).saturating_mul(r as Weight)) } fn instr_global_set(r: u32, ) -> Weight { - (76_861_000 as Weight) - // Standard Error: 4_000 - .saturating_add((1_383_000 as Weight).saturating_mul(r as Weight)) + (76_995_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_370_000 as Weight).saturating_mul(r as Weight)) } fn instr_memory_current(r: u32, ) -> Weight { - (74_696_000 as Weight) - // Standard Error: 2_000 - .saturating_add((655_000 as Weight).saturating_mul(r as Weight)) + (73_927_000 as Weight) + // Standard Error: 1_000 + .saturating_add((666_000 as Weight).saturating_mul(r as Weight)) } fn instr_memory_grow(r: u32, ) -> Weight { - (73_522_000 as Weight) - // Standard Error: 50_000 - .saturating_add((179_720_000 as Weight).saturating_mul(r as Weight)) + (73_479_000 as Weight) + // Standard Error: 24_000 + .saturating_add((180_808_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64clz(r: u32, ) -> Weight { - (74_292_000 as Weight) + (74_048_000 as Weight) // Standard Error: 1_000 - .saturating_add((884_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((885_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64ctz(r: u32, ) -> Weight { - (74_299_000 as Weight) + (73_894_000 as Weight) // Standard Error: 1_000 - .saturating_add((882_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((889_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64popcnt(r: u32, ) -> Weight { - (74_289_000 as Weight) - // Standard Error: 1_000 - .saturating_add((883_000 as Weight).saturating_mul(r as Weight)) + (73_728_000 as Weight) + // Standard Error: 0 + .saturating_add((896_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64eqz(r: u32, ) -> Weight { - (74_120_000 as Weight) - // Standard Error: 2_000 - .saturating_add((899_000 as Weight).saturating_mul(r as Weight)) + (74_049_000 as Weight) + // Standard Error: 1_000 + .saturating_add((897_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64extendsi32(r: u32, ) -> Weight { - (74_599_000 as Weight) + (74_118_000 as Weight) // Standard Error: 1_000 - .saturating_add((851_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((863_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64extendui32(r: u32, ) -> Weight { - (74_622_000 as Weight) - // Standard Error: 2_000 - .saturating_add((854_000 as Weight).saturating_mul(r as Weight)) + (74_042_000 as Weight) + // Standard Error: 1_000 + .saturating_add((865_000 as Weight).saturating_mul(r as Weight)) } fn instr_i32wrapi64(r: u32, ) -> Weight { - (74_504_000 as Weight) - // Standard Error: 2_000 - .saturating_add((874_000 as Weight).saturating_mul(r as Weight)) + (73_885_000 as Weight) + // Standard Error: 1_000 + .saturating_add((884_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64eq(r: u32, ) -> Weight { - (74_532_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_338_000 as Weight).saturating_mul(r as Weight)) + (73_788_000 as Weight) + // Standard Error: 0 + .saturating_add((1_355_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64ne(r: u32, ) -> Weight { - (74_130_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_347_000 as Weight).saturating_mul(r as Weight)) + (73_727_000 as Weight) + // Standard Error: 0 + .saturating_add((1_352_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64lts(r: u32, ) -> Weight { - (74_172_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_343_000 as Weight).saturating_mul(r as Weight)) + (73_825_000 as Weight) + // Standard Error: 0 + .saturating_add((1_351_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64ltu(r: u32, ) -> Weight { - (74_356_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_338_000 as Weight).saturating_mul(r as Weight)) + (73_638_000 as Weight) + // Standard Error: 0 + .saturating_add((1_355_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64gts(r: u32, ) -> Weight { - (74_181_000 as Weight) + (73_688_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_343_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_357_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64gtu(r: u32, ) -> Weight { - (74_045_000 as Weight) + (73_895_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_358_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_359_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64les(r: u32, ) -> Weight { - (74_246_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_341_000 as Weight).saturating_mul(r as Weight)) + (73_860_000 as Weight) + // Standard Error: 0 + .saturating_add((1_350_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64leu(r: u32, ) -> Weight { - (73_998_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_363_000 as Weight).saturating_mul(r as Weight)) + (73_864_000 as Weight) + // Standard Error: 0 + .saturating_add((1_361_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64ges(r: u32, ) -> Weight { - (74_035_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_348_000 as Weight).saturating_mul(r as Weight)) + (72_730_000 as Weight) + // Standard Error: 7_000 + .saturating_add((1_432_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64geu(r: u32, ) -> Weight { - (74_511_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_335_000 as Weight).saturating_mul(r as Weight)) + (73_998_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_346_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64add(r: u32, ) -> Weight { - (74_305_000 as Weight) + (73_708_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_319_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_333_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64sub(r: u32, ) -> Weight { - (74_214_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_321_000 as Weight).saturating_mul(r as Weight)) + (74_033_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_322_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64mul(r: u32, ) -> Weight { - (74_073_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_323_000 as Weight).saturating_mul(r as Weight)) + (74_203_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_322_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64divs(r: u32, ) -> Weight { - (74_295_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_986_000 as Weight).saturating_mul(r as Weight)) + (73_990_000 as Weight) + // Standard Error: 1_000 + .saturating_add((2_010_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64divu(r: u32, ) -> Weight { - (74_109_000 as Weight) - // Standard Error: 2_000 - .saturating_add((2_023_000 as Weight).saturating_mul(r as Weight)) + (73_918_000 as Weight) + // Standard Error: 1_000 + .saturating_add((2_019_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64rems(r: u32, ) -> Weight { - (73_990_000 as Weight) + (73_927_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_993_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((2_001_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64remu(r: u32, ) -> Weight { - (73_940_000 as Weight) - // Standard Error: 1_000 - .saturating_add((2_056_000 as Weight).saturating_mul(r as Weight)) + (73_691_000 as Weight) + // Standard Error: 0 + .saturating_add((2_062_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64and(r: u32, ) -> Weight { - (74_261_000 as Weight) - // Standard Error: 5_000 - .saturating_add((1_326_000 as Weight).saturating_mul(r as Weight)) + (73_869_000 as Weight) + // Standard Error: 0 + .saturating_add((1_327_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64or(r: u32, ) -> Weight { - (74_014_000 as Weight) + (73_890_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_324_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_327_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64xor(r: u32, ) -> Weight { - (74_220_000 as Weight) + (73_866_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_323_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_327_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64shl(r: u32, ) -> Weight { - (74_139_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_343_000 as Weight).saturating_mul(r as Weight)) + (73_793_000 as Weight) + // Standard Error: 0 + .saturating_add((1_352_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64shrs(r: u32, ) -> Weight { - (73_923_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_356_000 as Weight).saturating_mul(r as Weight)) + (73_695_000 as Weight) + // Standard Error: 0 + .saturating_add((1_354_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64shru(r: u32, ) -> Weight { - (73_999_000 as Weight) - // Standard Error: 1_000 + (73_743_000 as Weight) + // Standard Error: 0 .saturating_add((1_352_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64rotl(r: u32, ) -> Weight { - (74_312_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_340_000 as Weight).saturating_mul(r as Weight)) + (73_781_000 as Weight) + // Standard Error: 0 + .saturating_add((1_352_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64rotr(r: u32, ) -> Weight { - (73_988_000 as Weight) + (73_941_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_349_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_348_000 as Weight).saturating_mul(r as Weight)) } } diff --git a/frame/conviction-voting/src/weights.rs b/frame/conviction-voting/src/weights.rs index da15aac0b47ca..6946317475ae2 100644 --- a/frame/conviction-voting/src/weights.rs +++ b/frame/conviction-voting/src/weights.rs @@ -18,12 +18,13 @@ //! Autogenerated weights for pallet_conviction_voting //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-01-09, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 +//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/substrate +// ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -31,9 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 -// --output=./frame/conviction-voting/src/weights.rs // --template=./.maintain/frame-weight-template.hbs +// --output=./frame/conviction-voting/src/weights.rs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -62,7 +62,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn vote_new() -> Weight { - (159_647_000 as Weight) + (145_945_000 as Weight) .saturating_add(T::DbWeight::get().reads(6 as Weight)) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } @@ -72,7 +72,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn vote_existing() -> Weight { - (339_851_000 as Weight) + (316_356_000 as Weight) .saturating_add(T::DbWeight::get().reads(6 as Weight)) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } @@ -80,14 +80,14 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn remove_vote() -> Weight { - (317_673_000 as Weight) + (298_447_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } // Storage: ConvictionVoting VotingFor (r:1 w:1) // Storage: Referenda ReferendumInfoFor (r:1 w:0) fn remove_other_vote() -> Weight { - (52_222_000 as Weight) + (48_705_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -97,9 +97,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn delegate(r: u32, ) -> Weight { - (61_553_000 as Weight) - // Standard Error: 123_000 - .saturating_add((33_092_000 as Weight).saturating_mul(r as Weight)) + (51_221_000 as Weight) + // Standard Error: 140_000 + .saturating_add((25_392_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(4 as Weight)) @@ -109,9 +109,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn undelegate(r: u32, ) -> Weight { - (42_037_000 as Weight) - // Standard Error: 582_000 - .saturating_add((32_296_000 as Weight).saturating_mul(r as Weight)) + (34_419_000 as Weight) + // Standard Error: 517_000 + .saturating_add((26_295_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(2 as Weight)) @@ -121,7 +121,7 @@ impl WeightInfo for SubstrateWeight { // Storage: ConvictionVoting ClassLocksFor (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn unlock() -> Weight { - (69_017_000 as Weight) + (62_414_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -135,7 +135,7 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn vote_new() -> Weight { - (159_647_000 as Weight) + (145_945_000 as Weight) .saturating_add(RocksDbWeight::get().reads(6 as Weight)) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } @@ -145,7 +145,7 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn vote_existing() -> Weight { - (339_851_000 as Weight) + (316_356_000 as Weight) .saturating_add(RocksDbWeight::get().reads(6 as Weight)) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } @@ -153,14 +153,14 @@ impl WeightInfo for () { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn remove_vote() -> Weight { - (317_673_000 as Weight) + (298_447_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } // Storage: ConvictionVoting VotingFor (r:1 w:1) // Storage: Referenda ReferendumInfoFor (r:1 w:0) fn remove_other_vote() -> Weight { - (52_222_000 as Weight) + (48_705_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -170,9 +170,9 @@ impl WeightInfo for () { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn delegate(r: u32, ) -> Weight { - (61_553_000 as Weight) - // Standard Error: 123_000 - .saturating_add((33_092_000 as Weight).saturating_mul(r as Weight)) + (51_221_000 as Weight) + // Standard Error: 140_000 + .saturating_add((25_392_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) @@ -182,9 +182,9 @@ impl WeightInfo for () { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn undelegate(r: u32, ) -> Weight { - (42_037_000 as Weight) - // Standard Error: 582_000 - .saturating_add((32_296_000 as Weight).saturating_mul(r as Weight)) + (34_419_000 as Weight) + // Standard Error: 517_000 + .saturating_add((26_295_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) @@ -194,7 +194,7 @@ impl WeightInfo for () { // Storage: ConvictionVoting ClassLocksFor (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn unlock() -> Weight { - (69_017_000 as Weight) + (62_414_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } diff --git a/frame/democracy/src/weights.rs b/frame/democracy/src/weights.rs index 19bf602c5d291..50b4f3ab6ba22 100644 --- a/frame/democracy/src/weights.rs +++ b/frame/democracy/src/weights.rs @@ -18,12 +18,13 @@ //! Autogenerated weights for pallet_democracy //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-01-30, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: // ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -31,11 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 +// --template=./.maintain/frame-weight-template.hbs // --output=./frame/democracy/src/weights.rs -// --template=.maintain/frame-weight-template.hbs -// --header=HEADER-APACHE2 -// --raw #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -82,15 +80,15 @@ impl WeightInfo for SubstrateWeight { // Storage: Democracy Blacklist (r:1 w:0) // Storage: Democracy DepositOf (r:0 w:1) fn propose() -> Weight { - (42_560_000 as Weight) + (43_581_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: Democracy DepositOf (r:1 w:1) fn second(s: u32, ) -> Weight { - (25_603_000 as Weight) + (26_887_000 as Weight) // Standard Error: 1_000 - .saturating_add((167_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((134_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -98,9 +96,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Democracy VotingOf (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vote_new(r: u32, ) -> Weight { - (34_248_000 as Weight) + (35_520_000 as Weight) // Standard Error: 2_000 - .saturating_add((135_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((131_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -108,8 +106,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Democracy VotingOf (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vote_existing(r: u32, ) -> Weight { - (33_927_000 as Weight) - // Standard Error: 2_000 + (34_793_000 as Weight) + // Standard Error: 1_000 .saturating_add((138_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) @@ -117,7 +115,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy Cancellations (r:1 w:1) fn emergency_cancel() -> Weight { - (14_714_000 as Weight) + (15_401_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -128,45 +126,45 @@ impl WeightInfo for SubstrateWeight { // Storage: Democracy DepositOf (r:1 w:1) // Storage: System Account (r:1 w:1) fn blacklist(p: u32, ) -> Weight { - (49_423_000 as Weight) + (51_233_000 as Weight) // Standard Error: 3_000 - .saturating_add((275_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((189_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } // Storage: Democracy NextExternal (r:1 w:1) // Storage: Democracy Blacklist (r:1 w:0) fn external_propose(v: u32, ) -> Weight { - (7_321_000 as Weight) + (7_650_000 as Weight) // Standard Error: 0 - .saturating_add((75_000 as Weight).saturating_mul(v as Weight)) + .saturating_add((30_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Democracy NextExternal (r:0 w:1) fn external_propose_majority() -> Weight { - (1_325_000 as Weight) + (1_341_000 as Weight) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Democracy NextExternal (r:0 w:1) fn external_propose_default() -> Weight { - (1_283_000 as Weight) + (1_360_000 as Weight) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Democracy NextExternal (r:1 w:1) // Storage: Democracy ReferendumCount (r:1 w:1) // Storage: Democracy ReferendumInfoOf (r:0 w:1) fn fast_track() -> Weight { - (15_355_000 as Weight) + (16_021_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: Democracy NextExternal (r:1 w:1) // Storage: Democracy Blacklist (r:1 w:1) fn veto_external(v: u32, ) -> Weight { - (16_597_000 as Weight) + (17_350_000 as Weight) // Standard Error: 0 - .saturating_add((90_000 as Weight).saturating_mul(v as Weight)) + .saturating_add((46_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -174,23 +172,23 @@ impl WeightInfo for SubstrateWeight { // Storage: Democracy DepositOf (r:1 w:1) // Storage: System Account (r:1 w:1) fn cancel_proposal(p: u32, ) -> Weight { - (37_695_000 as Weight) + (39_418_000 as Weight) // Standard Error: 2_000 - .saturating_add((258_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((170_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: Democracy ReferendumInfoOf (r:0 w:1) fn cancel_referendum() -> Weight { - (9_456_000 as Weight) + (10_239_000 as Weight) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Scheduler Lookup (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn cancel_queued(r: u32, ) -> Weight { - (19_915_000 as Weight) + (20_483_000 as Weight) // Standard Error: 1_000 - .saturating_add((609_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((565_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -198,9 +196,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Democracy ReferendumCount (r:1 w:0) // Storage: Democracy ReferendumInfoOf (r:1 w:0) fn on_initialize_base(r: u32, ) -> Weight { - (3_787_000 as Weight) + (3_871_000 as Weight) // Standard Error: 2_000 - .saturating_add((2_852_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((3_038_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -212,9 +210,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Democracy PublicProps (r:1 w:0) // Storage: Democracy ReferendumInfoOf (r:1 w:0) fn on_initialize_base_with_launch_period(r: u32, ) -> Weight { - (7_890_000 as Weight) + (7_597_000 as Weight) // Standard Error: 2_000 - .saturating_add((2_856_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((3_046_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -223,9 +221,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn delegate(r: u32, ) -> Weight { - (31_190_000 as Weight) + (32_554_000 as Weight) // Standard Error: 3_000 - .saturating_add((3_753_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((3_903_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(4 as Weight)) @@ -234,9 +232,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Democracy VotingOf (r:2 w:2) // Storage: Democracy ReferendumInfoOf (r:1 w:1) fn undelegate(r: u32, ) -> Weight { - (15_121_000 as Weight) + (15_440_000 as Weight) // Standard Error: 3_000 - .saturating_add((3_852_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((3_972_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(2 as Weight)) @@ -244,12 +242,12 @@ impl WeightInfo for SubstrateWeight { } // Storage: Democracy PublicProps (r:0 w:1) fn clear_public_proposals() -> Weight { - (1_583_000 as Weight) + (1_662_000 as Weight) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Democracy Preimages (r:1 w:1) fn note_preimage(b: u32, ) -> Weight { - (22_681_000 as Weight) + (24_170_000 as Weight) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) @@ -257,7 +255,7 @@ impl WeightInfo for SubstrateWeight { } // Storage: Democracy Preimages (r:1 w:1) fn note_imminent_preimage(b: u32, ) -> Weight { - (15_599_000 as Weight) + (16_470_000 as Weight) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) @@ -266,7 +264,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Democracy Preimages (r:1 w:1) // Storage: System Account (r:1 w:0) fn reap_preimage(b: u32, ) -> Weight { - (23_663_000 as Weight) + (24_547_000 as Weight) // Standard Error: 0 .saturating_add((1_000 as Weight).saturating_mul(b as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) @@ -276,7 +274,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) fn unlock_remove(r: u32, ) -> Weight { - (21_928_000 as Weight) + (22_596_000 as Weight) // Standard Error: 1_000 .saturating_add((61_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) @@ -286,27 +284,27 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) fn unlock_set(r: u32, ) -> Weight { - (21_548_000 as Weight) + (21_589_000 as Weight) // Standard Error: 1_000 - .saturating_add((123_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((125_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy VotingOf (r:1 w:1) fn remove_vote(r: u32, ) -> Weight { - (11_797_000 as Weight) + (12_120_000 as Weight) // Standard Error: 1_000 - .saturating_add((117_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((106_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy VotingOf (r:1 w:1) fn remove_other_vote(r: u32, ) -> Weight { - (11_930_000 as Weight) + (12_563_000 as Weight) // Standard Error: 1_000 - .saturating_add((122_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((105_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -319,15 +317,15 @@ impl WeightInfo for () { // Storage: Democracy Blacklist (r:1 w:0) // Storage: Democracy DepositOf (r:0 w:1) fn propose() -> Weight { - (42_560_000 as Weight) + (43_581_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: Democracy DepositOf (r:1 w:1) fn second(s: u32, ) -> Weight { - (25_603_000 as Weight) + (26_887_000 as Weight) // Standard Error: 1_000 - .saturating_add((167_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((134_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -335,9 +333,9 @@ impl WeightInfo for () { // Storage: Democracy VotingOf (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vote_new(r: u32, ) -> Weight { - (34_248_000 as Weight) + (35_520_000 as Weight) // Standard Error: 2_000 - .saturating_add((135_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((131_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -345,8 +343,8 @@ impl WeightInfo for () { // Storage: Democracy VotingOf (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vote_existing(r: u32, ) -> Weight { - (33_927_000 as Weight) - // Standard Error: 2_000 + (34_793_000 as Weight) + // Standard Error: 1_000 .saturating_add((138_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) @@ -354,7 +352,7 @@ impl WeightInfo for () { // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy Cancellations (r:1 w:1) fn emergency_cancel() -> Weight { - (14_714_000 as Weight) + (15_401_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -365,45 +363,45 @@ impl WeightInfo for () { // Storage: Democracy DepositOf (r:1 w:1) // Storage: System Account (r:1 w:1) fn blacklist(p: u32, ) -> Weight { - (49_423_000 as Weight) + (51_233_000 as Weight) // Standard Error: 3_000 - .saturating_add((275_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((189_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } // Storage: Democracy NextExternal (r:1 w:1) // Storage: Democracy Blacklist (r:1 w:0) fn external_propose(v: u32, ) -> Weight { - (7_321_000 as Weight) + (7_650_000 as Weight) // Standard Error: 0 - .saturating_add((75_000 as Weight).saturating_mul(v as Weight)) + .saturating_add((30_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Democracy NextExternal (r:0 w:1) fn external_propose_majority() -> Weight { - (1_325_000 as Weight) + (1_341_000 as Weight) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Democracy NextExternal (r:0 w:1) fn external_propose_default() -> Weight { - (1_283_000 as Weight) + (1_360_000 as Weight) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Democracy NextExternal (r:1 w:1) // Storage: Democracy ReferendumCount (r:1 w:1) // Storage: Democracy ReferendumInfoOf (r:0 w:1) fn fast_track() -> Weight { - (15_355_000 as Weight) + (16_021_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: Democracy NextExternal (r:1 w:1) // Storage: Democracy Blacklist (r:1 w:1) fn veto_external(v: u32, ) -> Weight { - (16_597_000 as Weight) + (17_350_000 as Weight) // Standard Error: 0 - .saturating_add((90_000 as Weight).saturating_mul(v as Weight)) + .saturating_add((46_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -411,23 +409,23 @@ impl WeightInfo for () { // Storage: Democracy DepositOf (r:1 w:1) // Storage: System Account (r:1 w:1) fn cancel_proposal(p: u32, ) -> Weight { - (37_695_000 as Weight) + (39_418_000 as Weight) // Standard Error: 2_000 - .saturating_add((258_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((170_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: Democracy ReferendumInfoOf (r:0 w:1) fn cancel_referendum() -> Weight { - (9_456_000 as Weight) + (10_239_000 as Weight) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Scheduler Lookup (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn cancel_queued(r: u32, ) -> Weight { - (19_915_000 as Weight) + (20_483_000 as Weight) // Standard Error: 1_000 - .saturating_add((609_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((565_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -435,9 +433,9 @@ impl WeightInfo for () { // Storage: Democracy ReferendumCount (r:1 w:0) // Storage: Democracy ReferendumInfoOf (r:1 w:0) fn on_initialize_base(r: u32, ) -> Weight { - (3_787_000 as Weight) + (3_871_000 as Weight) // Standard Error: 2_000 - .saturating_add((2_852_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((3_038_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -449,9 +447,9 @@ impl WeightInfo for () { // Storage: Democracy PublicProps (r:1 w:0) // Storage: Democracy ReferendumInfoOf (r:1 w:0) fn on_initialize_base_with_launch_period(r: u32, ) -> Weight { - (7_890_000 as Weight) + (7_597_000 as Weight) // Standard Error: 2_000 - .saturating_add((2_856_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((3_046_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -460,9 +458,9 @@ impl WeightInfo for () { // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn delegate(r: u32, ) -> Weight { - (31_190_000 as Weight) + (32_554_000 as Weight) // Standard Error: 3_000 - .saturating_add((3_753_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((3_903_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) @@ -471,9 +469,9 @@ impl WeightInfo for () { // Storage: Democracy VotingOf (r:2 w:2) // Storage: Democracy ReferendumInfoOf (r:1 w:1) fn undelegate(r: u32, ) -> Weight { - (15_121_000 as Weight) + (15_440_000 as Weight) // Standard Error: 3_000 - .saturating_add((3_852_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((3_972_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) @@ -481,12 +479,12 @@ impl WeightInfo for () { } // Storage: Democracy PublicProps (r:0 w:1) fn clear_public_proposals() -> Weight { - (1_583_000 as Weight) + (1_662_000 as Weight) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Democracy Preimages (r:1 w:1) fn note_preimage(b: u32, ) -> Weight { - (22_681_000 as Weight) + (24_170_000 as Weight) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) @@ -494,7 +492,7 @@ impl WeightInfo for () { } // Storage: Democracy Preimages (r:1 w:1) fn note_imminent_preimage(b: u32, ) -> Weight { - (15_599_000 as Weight) + (16_470_000 as Weight) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) @@ -503,7 +501,7 @@ impl WeightInfo for () { // Storage: Democracy Preimages (r:1 w:1) // Storage: System Account (r:1 w:0) fn reap_preimage(b: u32, ) -> Weight { - (23_663_000 as Weight) + (24_547_000 as Weight) // Standard Error: 0 .saturating_add((1_000 as Weight).saturating_mul(b as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) @@ -513,7 +511,7 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) fn unlock_remove(r: u32, ) -> Weight { - (21_928_000 as Weight) + (22_596_000 as Weight) // Standard Error: 1_000 .saturating_add((61_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) @@ -523,27 +521,27 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) fn unlock_set(r: u32, ) -> Weight { - (21_548_000 as Weight) + (21_589_000 as Weight) // Standard Error: 1_000 - .saturating_add((123_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((125_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy VotingOf (r:1 w:1) fn remove_vote(r: u32, ) -> Weight { - (11_797_000 as Weight) + (12_120_000 as Weight) // Standard Error: 1_000 - .saturating_add((117_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((106_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy VotingOf (r:1 w:1) fn remove_other_vote(r: u32, ) -> Weight { - (11_930_000 as Weight) + (12_563_000 as Weight) // Standard Error: 1_000 - .saturating_add((122_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((105_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } diff --git a/frame/election-provider-multi-phase/src/weights.rs b/frame/election-provider-multi-phase/src/weights.rs index 54c519681922b..d4802210ba23f 100644 --- a/frame/election-provider-multi-phase/src/weights.rs +++ b/frame/election-provider-multi-phase/src/weights.rs @@ -18,12 +18,13 @@ //! Autogenerated weights for pallet_election_provider_multi_phase //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-03-03, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/production/substrate +// ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -31,9 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 -// --output=./frame/election-provider-multi-phase/src/weights.rs // --template=./.maintain/frame-weight-template.hbs +// --output=./frame/election-provider-multi-phase/src/weights.rs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -68,33 +68,33 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking ForceEra (r:1 w:0) // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) fn on_initialize_nothing() -> Weight { - (13_342_000 as Weight) + (12_920_000 as Weight) .saturating_add(T::DbWeight::get().reads(8 as Weight)) } // Storage: ElectionProviderMultiPhase Round (r:1 w:0) // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) fn on_initialize_open_signed() -> Weight { - (13_503_000 as Weight) + (13_366_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: ElectionProviderMultiPhase Round (r:1 w:0) // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) fn on_initialize_open_unsigned() -> Weight { - (13_688_000 as Weight) + (13_487_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: System Account (r:1 w:1) // Storage: ElectionProviderMultiPhase QueuedSolution (r:0 w:1) fn finalize_signed_phase_accept_solution() -> Weight { - (29_124_000 as Weight) + (28_656_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: System Account (r:1 w:1) fn finalize_signed_phase_reject_solution() -> Weight { - (21_950_000 as Weight) + (21_910_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -102,11 +102,11 @@ impl WeightInfo for SubstrateWeight { // Storage: ElectionProviderMultiPhase DesiredTargets (r:0 w:1) // Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) fn create_snapshot_internal(v: u32, t: u32, ) -> Weight { - (17_274_000 as Weight) + (0 as Weight) // Standard Error: 1_000 - .saturating_add((191_000 as Weight).saturating_mul(v as Weight)) + .saturating_add((210_000 as Weight).saturating_mul(v as Weight)) // Standard Error: 3_000 - .saturating_add((53_000 as Weight).saturating_mul(t as Weight)) + .saturating_add((77_000 as Weight).saturating_mul(t as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: ElectionProviderMultiPhase SignedSubmissionIndices (r:1 w:1) @@ -119,11 +119,11 @@ impl WeightInfo for SubstrateWeight { // Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) fn elect_queued(a: u32, d: u32, ) -> Weight { - (145_826_000 as Weight) + (97_857_000 as Weight) + // Standard Error: 2_000 + .saturating_add((651_000 as Weight).saturating_mul(a as Weight)) // Standard Error: 4_000 - .saturating_add((604_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 6_000 - .saturating_add((72_000 as Weight).saturating_mul(d as Weight)) + .saturating_add((133_000 as Weight).saturating_mul(d as Weight)) .saturating_add(T::DbWeight::get().reads(6 as Weight)) .saturating_add(T::DbWeight::get().writes(8 as Weight)) } @@ -134,7 +134,7 @@ impl WeightInfo for SubstrateWeight { // Storage: ElectionProviderMultiPhase SignedSubmissionNextIndex (r:1 w:1) // Storage: ElectionProviderMultiPhase SignedSubmissionsMap (r:0 w:1) fn submit() -> Weight { - (41_579_000 as Weight) + (43_892_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -148,13 +148,13 @@ impl WeightInfo for SubstrateWeight { fn submit_unsigned(v: u32, t: u32, a: u32, d: u32, ) -> Weight { (0 as Weight) // Standard Error: 3_000 - .saturating_add((882_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 7_000 - .saturating_add((144_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 12_000 - .saturating_add((6_534_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 18_000 - .saturating_add((1_312_000 as Weight).saturating_mul(d as Weight)) + .saturating_add((900_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 6_000 + .saturating_add((141_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 11_000 + .saturating_add((6_817_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 17_000 + .saturating_add((1_533_000 as Weight).saturating_mul(d as Weight)) .saturating_add(T::DbWeight::get().reads(7 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -162,14 +162,16 @@ impl WeightInfo for SubstrateWeight { // Storage: ElectionProviderMultiPhase DesiredTargets (r:1 w:0) // Storage: ElectionProviderMultiPhase MinimumUntrustedScore (r:1 w:0) // Storage: ElectionProviderMultiPhase Snapshot (r:1 w:0) - fn feasibility_check(v: u32, _t: u32, a: u32, d: u32, ) -> Weight { + fn feasibility_check(v: u32, t: u32, a: u32, d: u32, ) -> Weight { (0 as Weight) // Standard Error: 3_000 - .saturating_add((835_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 12_000 - .saturating_add((5_395_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 19_000 - .saturating_add((1_243_000 as Weight).saturating_mul(d as Weight)) + .saturating_add((885_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 6_000 + .saturating_add((205_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 11_000 + .saturating_add((5_532_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 16_000 + .saturating_add((1_138_000 as Weight).saturating_mul(d as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) } } @@ -185,33 +187,33 @@ impl WeightInfo for () { // Storage: Staking ForceEra (r:1 w:0) // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) fn on_initialize_nothing() -> Weight { - (13_342_000 as Weight) + (12_920_000 as Weight) .saturating_add(RocksDbWeight::get().reads(8 as Weight)) } // Storage: ElectionProviderMultiPhase Round (r:1 w:0) // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) fn on_initialize_open_signed() -> Weight { - (13_503_000 as Weight) + (13_366_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: ElectionProviderMultiPhase Round (r:1 w:0) // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) fn on_initialize_open_unsigned() -> Weight { - (13_688_000 as Weight) + (13_487_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: System Account (r:1 w:1) // Storage: ElectionProviderMultiPhase QueuedSolution (r:0 w:1) fn finalize_signed_phase_accept_solution() -> Weight { - (29_124_000 as Weight) + (28_656_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: System Account (r:1 w:1) fn finalize_signed_phase_reject_solution() -> Weight { - (21_950_000 as Weight) + (21_910_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -219,11 +221,11 @@ impl WeightInfo for () { // Storage: ElectionProviderMultiPhase DesiredTargets (r:0 w:1) // Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) fn create_snapshot_internal(v: u32, t: u32, ) -> Weight { - (17_274_000 as Weight) + (0 as Weight) // Standard Error: 1_000 - .saturating_add((191_000 as Weight).saturating_mul(v as Weight)) + .saturating_add((210_000 as Weight).saturating_mul(v as Weight)) // Standard Error: 3_000 - .saturating_add((53_000 as Weight).saturating_mul(t as Weight)) + .saturating_add((77_000 as Weight).saturating_mul(t as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: ElectionProviderMultiPhase SignedSubmissionIndices (r:1 w:1) @@ -236,11 +238,11 @@ impl WeightInfo for () { // Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) fn elect_queued(a: u32, d: u32, ) -> Weight { - (145_826_000 as Weight) + (97_857_000 as Weight) + // Standard Error: 2_000 + .saturating_add((651_000 as Weight).saturating_mul(a as Weight)) // Standard Error: 4_000 - .saturating_add((604_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 6_000 - .saturating_add((72_000 as Weight).saturating_mul(d as Weight)) + .saturating_add((133_000 as Weight).saturating_mul(d as Weight)) .saturating_add(RocksDbWeight::get().reads(6 as Weight)) .saturating_add(RocksDbWeight::get().writes(8 as Weight)) } @@ -251,7 +253,7 @@ impl WeightInfo for () { // Storage: ElectionProviderMultiPhase SignedSubmissionNextIndex (r:1 w:1) // Storage: ElectionProviderMultiPhase SignedSubmissionsMap (r:0 w:1) fn submit() -> Weight { - (41_579_000 as Weight) + (43_892_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -265,13 +267,13 @@ impl WeightInfo for () { fn submit_unsigned(v: u32, t: u32, a: u32, d: u32, ) -> Weight { (0 as Weight) // Standard Error: 3_000 - .saturating_add((882_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 7_000 - .saturating_add((144_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 12_000 - .saturating_add((6_534_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 18_000 - .saturating_add((1_312_000 as Weight).saturating_mul(d as Weight)) + .saturating_add((900_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 6_000 + .saturating_add((141_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 11_000 + .saturating_add((6_817_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 17_000 + .saturating_add((1_533_000 as Weight).saturating_mul(d as Weight)) .saturating_add(RocksDbWeight::get().reads(7 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -279,14 +281,16 @@ impl WeightInfo for () { // Storage: ElectionProviderMultiPhase DesiredTargets (r:1 w:0) // Storage: ElectionProviderMultiPhase MinimumUntrustedScore (r:1 w:0) // Storage: ElectionProviderMultiPhase Snapshot (r:1 w:0) - fn feasibility_check(v: u32, _t: u32, a: u32, d: u32, ) -> Weight { + fn feasibility_check(v: u32, t: u32, a: u32, d: u32, ) -> Weight { (0 as Weight) // Standard Error: 3_000 - .saturating_add((835_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 12_000 - .saturating_add((5_395_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 19_000 - .saturating_add((1_243_000 as Weight).saturating_mul(d as Weight)) + .saturating_add((885_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 6_000 + .saturating_add((205_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 11_000 + .saturating_add((5_532_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 16_000 + .saturating_add((1_138_000 as Weight).saturating_mul(d as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) } } diff --git a/frame/elections-phragmen/src/weights.rs b/frame/elections-phragmen/src/weights.rs index e973334b833cc..6cb2924fd7cf8 100644 --- a/frame/elections-phragmen/src/weights.rs +++ b/frame/elections-phragmen/src/weights.rs @@ -18,12 +18,13 @@ //! Autogenerated weights for pallet_elections_phragmen //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-03-29, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/production/substrate +// ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -31,9 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 -// --output=./frame/elections-phragmen/src/weights.rs // --template=./.maintain/frame-weight-template.hbs +// --output=./frame/elections-phragmen/src/weights.rs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -68,9 +68,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vote_equal(v: u32, ) -> Weight { - (22_981_000 as Weight) - // Standard Error: 6_000 - .saturating_add((232_000 as Weight).saturating_mul(v as Weight)) + (23_819_000 as Weight) + // Standard Error: 5_000 + .saturating_add((242_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -80,9 +80,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vote_more(v: u32, ) -> Weight { - (36_170_000 as Weight) - // Standard Error: 8_000 - .saturating_add((219_000 as Weight).saturating_mul(v as Weight)) + (37_141_000 as Weight) + // Standard Error: 6_000 + .saturating_add((259_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -92,16 +92,16 @@ impl WeightInfo for SubstrateWeight { // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vote_less(v: u32, ) -> Weight { - (35_798_000 as Weight) - // Standard Error: 8_000 - .saturating_add((241_000 as Weight).saturating_mul(v as Weight)) + (37_494_000 as Weight) + // Standard Error: 10_000 + .saturating_add((227_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn remove_voter() -> Weight { - (33_060_000 as Weight) + (34_528_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -109,17 +109,17 @@ impl WeightInfo for SubstrateWeight { // Storage: Elections Members (r:1 w:0) // Storage: Elections RunnersUp (r:1 w:0) fn submit_candidacy(c: u32, ) -> Weight { - (35_384_000 as Weight) - // Standard Error: 0 - .saturating_add((124_000 as Weight).saturating_mul(c as Weight)) + (38_330_000 as Weight) + // Standard Error: 1_000 + .saturating_add((88_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Elections Candidates (r:1 w:1) fn renounce_candidacy_candidate(c: u32, ) -> Weight { - (31_555_000 as Weight) + (32_770_000 as Weight) // Standard Error: 1_000 - .saturating_add((78_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((81_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -129,13 +129,13 @@ impl WeightInfo for SubstrateWeight { // Storage: Council Proposals (r:1 w:0) // Storage: Council Members (r:0 w:1) fn renounce_candidacy_members() -> Weight { - (41_531_000 as Weight) + (43_413_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } // Storage: Elections RunnersUp (r:1 w:1) fn renounce_candidacy_runners_up() -> Weight { - (30_762_000 as Weight) + (32_206_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -150,13 +150,13 @@ impl WeightInfo for SubstrateWeight { // Storage: Council Proposals (r:1 w:0) // Storage: Council Members (r:0 w:1) fn remove_member_with_replacement() -> Weight { - (48_287_000 as Weight) + (50_135_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } // Storage: Elections RunnersUp (r:1 w:0) fn remove_member_wrong_refund() -> Weight { - (4_747_000 as Weight) + (5_063_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) } // Storage: Elections Voting (r:251 w:250) @@ -167,8 +167,8 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:250 w:250) fn clean_defunct_voters(v: u32, _d: u32, ) -> Weight { (0 as Weight) - // Standard Error: 37_000 - .saturating_add((49_564_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 62_000 + .saturating_add((52_093_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(v as Weight))) .saturating_add(T::DbWeight::get().writes((3 as Weight).saturating_mul(v as Weight))) @@ -184,10 +184,10 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:2 w:2) fn election_phragmen(c: u32, v: u32, e: u32, ) -> Weight { (0 as Weight) - // Standard Error: 1_656_000 - .saturating_add((29_011_000 as Weight).saturating_mul(c as Weight)) + // Standard Error: 1_658_000 + .saturating_add((30_489_000 as Weight).saturating_mul(c as Weight)) // Standard Error: 689_000 - .saturating_add((49_204_000 as Weight).saturating_mul(v as Weight)) + .saturating_add((49_624_000 as Weight).saturating_mul(v as Weight)) // Standard Error: 47_000 .saturating_add((3_352_000 as Weight).saturating_mul(e as Weight)) .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(c as Weight))) @@ -204,9 +204,9 @@ impl WeightInfo for () { // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vote_equal(v: u32, ) -> Weight { - (22_981_000 as Weight) - // Standard Error: 6_000 - .saturating_add((232_000 as Weight).saturating_mul(v as Weight)) + (23_819_000 as Weight) + // Standard Error: 5_000 + .saturating_add((242_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -216,9 +216,9 @@ impl WeightInfo for () { // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vote_more(v: u32, ) -> Weight { - (36_170_000 as Weight) - // Standard Error: 8_000 - .saturating_add((219_000 as Weight).saturating_mul(v as Weight)) + (37_141_000 as Weight) + // Standard Error: 6_000 + .saturating_add((259_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -228,16 +228,16 @@ impl WeightInfo for () { // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vote_less(v: u32, ) -> Weight { - (35_798_000 as Weight) - // Standard Error: 8_000 - .saturating_add((241_000 as Weight).saturating_mul(v as Weight)) + (37_494_000 as Weight) + // Standard Error: 10_000 + .saturating_add((227_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn remove_voter() -> Weight { - (33_060_000 as Weight) + (34_528_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -245,17 +245,17 @@ impl WeightInfo for () { // Storage: Elections Members (r:1 w:0) // Storage: Elections RunnersUp (r:1 w:0) fn submit_candidacy(c: u32, ) -> Weight { - (35_384_000 as Weight) - // Standard Error: 0 - .saturating_add((124_000 as Weight).saturating_mul(c as Weight)) + (38_330_000 as Weight) + // Standard Error: 1_000 + .saturating_add((88_000 as Weight).saturating_mul(c as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Elections Candidates (r:1 w:1) fn renounce_candidacy_candidate(c: u32, ) -> Weight { - (31_555_000 as Weight) + (32_770_000 as Weight) // Standard Error: 1_000 - .saturating_add((78_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((81_000 as Weight).saturating_mul(c as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -265,13 +265,13 @@ impl WeightInfo for () { // Storage: Council Proposals (r:1 w:0) // Storage: Council Members (r:0 w:1) fn renounce_candidacy_members() -> Weight { - (41_531_000 as Weight) + (43_413_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } // Storage: Elections RunnersUp (r:1 w:1) fn renounce_candidacy_runners_up() -> Weight { - (30_762_000 as Weight) + (32_206_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -286,13 +286,13 @@ impl WeightInfo for () { // Storage: Council Proposals (r:1 w:0) // Storage: Council Members (r:0 w:1) fn remove_member_with_replacement() -> Weight { - (48_287_000 as Weight) + (50_135_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } // Storage: Elections RunnersUp (r:1 w:0) fn remove_member_wrong_refund() -> Weight { - (4_747_000 as Weight) + (5_063_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) } // Storage: Elections Voting (r:251 w:250) @@ -303,8 +303,8 @@ impl WeightInfo for () { // Storage: System Account (r:250 w:250) fn clean_defunct_voters(v: u32, _d: u32, ) -> Weight { (0 as Weight) - // Standard Error: 37_000 - .saturating_add((49_564_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 62_000 + .saturating_add((52_093_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(v as Weight))) .saturating_add(RocksDbWeight::get().writes((3 as Weight).saturating_mul(v as Weight))) @@ -320,10 +320,10 @@ impl WeightInfo for () { // Storage: System Account (r:2 w:2) fn election_phragmen(c: u32, v: u32, e: u32, ) -> Weight { (0 as Weight) - // Standard Error: 1_656_000 - .saturating_add((29_011_000 as Weight).saturating_mul(c as Weight)) + // Standard Error: 1_658_000 + .saturating_add((30_489_000 as Weight).saturating_mul(c as Weight)) // Standard Error: 689_000 - .saturating_add((49_204_000 as Weight).saturating_mul(v as Weight)) + .saturating_add((49_624_000 as Weight).saturating_mul(v as Weight)) // Standard Error: 47_000 .saturating_add((3_352_000 as Weight).saturating_mul(e as Weight)) .saturating_add(RocksDbWeight::get().reads((2 as Weight).saturating_mul(c as Weight))) diff --git a/frame/gilt/src/weights.rs b/frame/gilt/src/weights.rs index 6084d494c5c3a..99e58d070e493 100644 --- a/frame/gilt/src/weights.rs +++ b/frame/gilt/src/weights.rs @@ -18,12 +18,13 @@ //! Autogenerated weights for pallet_gilt //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-01-31, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: // ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -31,11 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 +// --template=./.maintain/frame-weight-template.hbs // --output=./frame/gilt/src/weights.rs -// --template=.maintain/frame-weight-template.hbs -// --header=HEADER-APACHE2 -// --raw #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -62,44 +60,44 @@ impl WeightInfo for SubstrateWeight { // Storage: Gilt Queues (r:1 w:1) // Storage: Gilt QueueTotals (r:1 w:1) fn place_bid(l: u32, ) -> Weight { - (32_753_000 as Weight) + (36_598_000 as Weight) // Standard Error: 0 - .saturating_add((96_000 as Weight).saturating_mul(l as Weight)) + .saturating_add((61_000 as Weight).saturating_mul(l as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Gilt Queues (r:1 w:1) // Storage: Gilt QueueTotals (r:1 w:1) fn place_bid_max() -> Weight { - (124_814_000 as Weight) + (92_021_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Gilt Queues (r:1 w:1) // Storage: Gilt QueueTotals (r:1 w:1) fn retract_bid(l: u32, ) -> Weight { - (33_544_000 as Weight) + (37_133_000 as Weight) // Standard Error: 0 - .saturating_add((86_000 as Weight).saturating_mul(l as Weight)) + .saturating_add((51_000 as Weight).saturating_mul(l as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Gilt ActiveTotal (r:1 w:1) fn set_target() -> Weight { - (2_648_000 as Weight) + (2_807_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Gilt Active (r:1 w:1) // Storage: Gilt ActiveTotal (r:1 w:1) fn thaw() -> Weight { - (41_549_000 as Weight) + (43_275_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Gilt ActiveTotal (r:1 w:0) fn pursue_target_noop() -> Weight { - (1_583_000 as Weight) + (1_693_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) } // Storage: Gilt ActiveTotal (r:1 w:1) @@ -107,9 +105,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Gilt Queues (r:1 w:1) // Storage: Gilt Active (r:0 w:1) fn pursue_target_per_item(b: u32, ) -> Weight { - (35_836_000 as Weight) - // Standard Error: 1_000 - .saturating_add((4_008_000 as Weight).saturating_mul(b as Weight)) + (37_977_000 as Weight) + // Standard Error: 2_000 + .saturating_add((4_129_000 as Weight).saturating_mul(b as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(b as Weight))) @@ -119,9 +117,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Gilt Queues (r:1 w:1) // Storage: Gilt Active (r:0 w:1) fn pursue_target_per_queue(q: u32, ) -> Weight { - (12_139_000 as Weight) - // Standard Error: 6_000 - .saturating_add((7_819_000 as Weight).saturating_mul(q as Weight)) + (11_367_000 as Weight) + // Standard Error: 7_000 + .saturating_add((8_275_000 as Weight).saturating_mul(q as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(q as Weight))) .saturating_add(T::DbWeight::get().writes(2 as Weight)) @@ -134,44 +132,44 @@ impl WeightInfo for () { // Storage: Gilt Queues (r:1 w:1) // Storage: Gilt QueueTotals (r:1 w:1) fn place_bid(l: u32, ) -> Weight { - (32_753_000 as Weight) + (36_598_000 as Weight) // Standard Error: 0 - .saturating_add((96_000 as Weight).saturating_mul(l as Weight)) + .saturating_add((61_000 as Weight).saturating_mul(l as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Gilt Queues (r:1 w:1) // Storage: Gilt QueueTotals (r:1 w:1) fn place_bid_max() -> Weight { - (124_814_000 as Weight) + (92_021_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Gilt Queues (r:1 w:1) // Storage: Gilt QueueTotals (r:1 w:1) fn retract_bid(l: u32, ) -> Weight { - (33_544_000 as Weight) + (37_133_000 as Weight) // Standard Error: 0 - .saturating_add((86_000 as Weight).saturating_mul(l as Weight)) + .saturating_add((51_000 as Weight).saturating_mul(l as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Gilt ActiveTotal (r:1 w:1) fn set_target() -> Weight { - (2_648_000 as Weight) + (2_807_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Gilt Active (r:1 w:1) // Storage: Gilt ActiveTotal (r:1 w:1) fn thaw() -> Weight { - (41_549_000 as Weight) + (43_275_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Gilt ActiveTotal (r:1 w:0) fn pursue_target_noop() -> Weight { - (1_583_000 as Weight) + (1_693_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) } // Storage: Gilt ActiveTotal (r:1 w:1) @@ -179,9 +177,9 @@ impl WeightInfo for () { // Storage: Gilt Queues (r:1 w:1) // Storage: Gilt Active (r:0 w:1) fn pursue_target_per_item(b: u32, ) -> Weight { - (35_836_000 as Weight) - // Standard Error: 1_000 - .saturating_add((4_008_000 as Weight).saturating_mul(b as Weight)) + (37_977_000 as Weight) + // Standard Error: 2_000 + .saturating_add((4_129_000 as Weight).saturating_mul(b as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(b as Weight))) @@ -191,9 +189,9 @@ impl WeightInfo for () { // Storage: Gilt Queues (r:1 w:1) // Storage: Gilt Active (r:0 w:1) fn pursue_target_per_queue(q: u32, ) -> Weight { - (12_139_000 as Weight) - // Standard Error: 6_000 - .saturating_add((7_819_000 as Weight).saturating_mul(q as Weight)) + (11_367_000 as Weight) + // Standard Error: 7_000 + .saturating_add((8_275_000 as Weight).saturating_mul(q as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(q as Weight))) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) diff --git a/frame/identity/src/weights.rs b/frame/identity/src/weights.rs index cf164d3cbd73f..77c5915f0ec99 100644 --- a/frame/identity/src/weights.rs +++ b/frame/identity/src/weights.rs @@ -18,12 +18,13 @@ //! Autogenerated weights for pallet_identity //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-01-31, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: // ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -31,11 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 +// --template=./.maintain/frame-weight-template.hbs // --output=./frame/identity/src/weights.rs -// --template=.maintain/frame-weight-template.hbs -// --header=HEADER-APACHE2 -// --raw #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -69,19 +67,19 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: Identity Registrars (r:1 w:1) fn add_registrar(r: u32, ) -> Weight { - (13_150_000 as Weight) + (13_318_000 as Weight) // Standard Error: 3_000 - .saturating_add((253_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((203_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Identity IdentityOf (r:1 w:1) fn set_identity(r: u32, x: u32, ) -> Weight { - (27_745_000 as Weight) - // Standard Error: 7_000 - .saturating_add((210_000 as Weight).saturating_mul(r as Weight)) + (27_683_000 as Weight) + // Standard Error: 8_000 + .saturating_add((242_000 as Weight).saturating_mul(r as Weight)) // Standard Error: 1_000 - .saturating_add((289_000 as Weight).saturating_mul(x as Weight)) + .saturating_add((307_000 as Weight).saturating_mul(x as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -89,9 +87,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Identity SubsOf (r:1 w:1) // Storage: Identity SuperOf (r:1 w:1) fn set_subs_new(s: u32, ) -> Weight { - (25_184_000 as Weight) + (26_126_000 as Weight) // Standard Error: 1_000 - .saturating_add((2_722_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((2_826_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(s as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -101,9 +99,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Identity SubsOf (r:1 w:1) // Storage: Identity SuperOf (r:0 w:1) fn set_subs_old(p: u32, ) -> Weight { - (24_144_000 as Weight) + (24_645_000 as Weight) // Standard Error: 0 - .saturating_add((890_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((893_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(p as Weight))) @@ -112,13 +110,13 @@ impl WeightInfo for SubstrateWeight { // Storage: Identity IdentityOf (r:1 w:1) // Storage: Identity SuperOf (r:0 w:100) fn clear_identity(r: u32, s: u32, x: u32, ) -> Weight { - (32_167_000 as Weight) - // Standard Error: 8_000 - .saturating_add((46_000 as Weight).saturating_mul(r as Weight)) - // Standard Error: 0 - .saturating_add((875_000 as Weight).saturating_mul(s as Weight)) - // Standard Error: 0 - .saturating_add((184_000 as Weight).saturating_mul(x as Weight)) + (31_546_000 as Weight) + // Standard Error: 12_000 + .saturating_add((110_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 1_000 + .saturating_add((872_000 as Weight).saturating_mul(s as Weight)) + // Standard Error: 1_000 + .saturating_add((182_000 as Weight).saturating_mul(x as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) @@ -126,56 +124,56 @@ impl WeightInfo for SubstrateWeight { // Storage: Identity Registrars (r:1 w:0) // Storage: Identity IdentityOf (r:1 w:1) fn request_judgement(r: u32, x: u32, ) -> Weight { - (30_009_000 as Weight) + (31_117_000 as Weight) // Standard Error: 3_000 - .saturating_add((287_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((207_000 as Weight).saturating_mul(r as Weight)) // Standard Error: 0 - .saturating_add((331_000 as Weight).saturating_mul(x as Weight)) + .saturating_add((347_000 as Weight).saturating_mul(x as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Identity IdentityOf (r:1 w:1) fn cancel_request(r: u32, x: u32, ) -> Weight { - (28_195_000 as Weight) + (28_134_000 as Weight) // Standard Error: 3_000 - .saturating_add((137_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((154_000 as Weight).saturating_mul(r as Weight)) // Standard Error: 0 - .saturating_add((328_000 as Weight).saturating_mul(x as Weight)) + .saturating_add((343_000 as Weight).saturating_mul(x as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Identity Registrars (r:1 w:1) fn set_fee(r: u32, ) -> Weight { - (5_080_000 as Weight) - // Standard Error: 2_000 - .saturating_add((202_000 as Weight).saturating_mul(r as Weight)) + (4_884_000 as Weight) + // Standard Error: 3_000 + .saturating_add((159_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Identity Registrars (r:1 w:1) fn set_account_id(r: u32, ) -> Weight { - (5_298_000 as Weight) + (5_015_000 as Weight) // Standard Error: 2_000 - .saturating_add((195_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((143_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Identity Registrars (r:1 w:1) fn set_fields(r: u32, ) -> Weight { - (5_263_000 as Weight) - // Standard Error: 2_000 - .saturating_add((191_000 as Weight).saturating_mul(r as Weight)) + (4_947_000 as Weight) + // Standard Error: 3_000 + .saturating_add((152_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Identity Registrars (r:1 w:0) // Storage: Identity IdentityOf (r:1 w:1) fn provide_judgement(r: u32, x: u32, ) -> Weight { - (21_511_000 as Weight) + (20_570_000 as Weight) // Standard Error: 3_000 - .saturating_add((219_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((213_000 as Weight).saturating_mul(r as Weight)) // Standard Error: 0 - .saturating_add((327_000 as Weight).saturating_mul(x as Weight)) + .saturating_add((345_000 as Weight).saturating_mul(x as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -184,11 +182,11 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) // Storage: Identity SuperOf (r:0 w:100) fn kill_identity(r: u32, s: u32, _x: u32, ) -> Weight { - (40_654_000 as Weight) - // Standard Error: 6_000 - .saturating_add((179_000 as Weight).saturating_mul(r as Weight)) + (41_448_000 as Weight) + // Standard Error: 7_000 + .saturating_add((149_000 as Weight).saturating_mul(r as Weight)) // Standard Error: 0 - .saturating_add((908_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((882_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) @@ -197,18 +195,18 @@ impl WeightInfo for SubstrateWeight { // Storage: Identity SuperOf (r:1 w:1) // Storage: Identity SubsOf (r:1 w:1) fn add_sub(s: u32, ) -> Weight { - (31_573_000 as Weight) + (32_346_000 as Weight) // Standard Error: 1_000 - .saturating_add((151_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((114_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Identity IdentityOf (r:1 w:0) // Storage: Identity SuperOf (r:1 w:1) fn rename_sub(s: u32, ) -> Weight { - (10_452_000 as Weight) + (10_211_000 as Weight) // Standard Error: 0 - .saturating_add((45_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((51_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -216,18 +214,18 @@ impl WeightInfo for SubstrateWeight { // Storage: Identity SuperOf (r:1 w:1) // Storage: Identity SubsOf (r:1 w:1) fn remove_sub(s: u32, ) -> Weight { - (32_711_000 as Weight) + (33_083_000 as Weight) // Standard Error: 1_000 - .saturating_add((150_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((112_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Identity SuperOf (r:1 w:1) // Storage: Identity SubsOf (r:1 w:1) fn quit_sub(s: u32, ) -> Weight { - (21_975_000 as Weight) - // Standard Error: 0 - .saturating_add((149_000 as Weight).saturating_mul(s as Weight)) + (22_517_000 as Weight) + // Standard Error: 1_000 + .saturating_add((110_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -237,19 +235,19 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { // Storage: Identity Registrars (r:1 w:1) fn add_registrar(r: u32, ) -> Weight { - (13_150_000 as Weight) + (13_318_000 as Weight) // Standard Error: 3_000 - .saturating_add((253_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((203_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Identity IdentityOf (r:1 w:1) fn set_identity(r: u32, x: u32, ) -> Weight { - (27_745_000 as Weight) - // Standard Error: 7_000 - .saturating_add((210_000 as Weight).saturating_mul(r as Weight)) + (27_683_000 as Weight) + // Standard Error: 8_000 + .saturating_add((242_000 as Weight).saturating_mul(r as Weight)) // Standard Error: 1_000 - .saturating_add((289_000 as Weight).saturating_mul(x as Weight)) + .saturating_add((307_000 as Weight).saturating_mul(x as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -257,9 +255,9 @@ impl WeightInfo for () { // Storage: Identity SubsOf (r:1 w:1) // Storage: Identity SuperOf (r:1 w:1) fn set_subs_new(s: u32, ) -> Weight { - (25_184_000 as Weight) + (26_126_000 as Weight) // Standard Error: 1_000 - .saturating_add((2_722_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((2_826_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(s as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -269,9 +267,9 @@ impl WeightInfo for () { // Storage: Identity SubsOf (r:1 w:1) // Storage: Identity SuperOf (r:0 w:1) fn set_subs_old(p: u32, ) -> Weight { - (24_144_000 as Weight) + (24_645_000 as Weight) // Standard Error: 0 - .saturating_add((890_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((893_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(p as Weight))) @@ -280,13 +278,13 @@ impl WeightInfo for () { // Storage: Identity IdentityOf (r:1 w:1) // Storage: Identity SuperOf (r:0 w:100) fn clear_identity(r: u32, s: u32, x: u32, ) -> Weight { - (32_167_000 as Weight) - // Standard Error: 8_000 - .saturating_add((46_000 as Weight).saturating_mul(r as Weight)) - // Standard Error: 0 - .saturating_add((875_000 as Weight).saturating_mul(s as Weight)) - // Standard Error: 0 - .saturating_add((184_000 as Weight).saturating_mul(x as Weight)) + (31_546_000 as Weight) + // Standard Error: 12_000 + .saturating_add((110_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 1_000 + .saturating_add((872_000 as Weight).saturating_mul(s as Weight)) + // Standard Error: 1_000 + .saturating_add((182_000 as Weight).saturating_mul(x as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) @@ -294,56 +292,56 @@ impl WeightInfo for () { // Storage: Identity Registrars (r:1 w:0) // Storage: Identity IdentityOf (r:1 w:1) fn request_judgement(r: u32, x: u32, ) -> Weight { - (30_009_000 as Weight) + (31_117_000 as Weight) // Standard Error: 3_000 - .saturating_add((287_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((207_000 as Weight).saturating_mul(r as Weight)) // Standard Error: 0 - .saturating_add((331_000 as Weight).saturating_mul(x as Weight)) + .saturating_add((347_000 as Weight).saturating_mul(x as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Identity IdentityOf (r:1 w:1) fn cancel_request(r: u32, x: u32, ) -> Weight { - (28_195_000 as Weight) + (28_134_000 as Weight) // Standard Error: 3_000 - .saturating_add((137_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((154_000 as Weight).saturating_mul(r as Weight)) // Standard Error: 0 - .saturating_add((328_000 as Weight).saturating_mul(x as Weight)) + .saturating_add((343_000 as Weight).saturating_mul(x as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Identity Registrars (r:1 w:1) fn set_fee(r: u32, ) -> Weight { - (5_080_000 as Weight) - // Standard Error: 2_000 - .saturating_add((202_000 as Weight).saturating_mul(r as Weight)) + (4_884_000 as Weight) + // Standard Error: 3_000 + .saturating_add((159_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Identity Registrars (r:1 w:1) fn set_account_id(r: u32, ) -> Weight { - (5_298_000 as Weight) + (5_015_000 as Weight) // Standard Error: 2_000 - .saturating_add((195_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((143_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Identity Registrars (r:1 w:1) fn set_fields(r: u32, ) -> Weight { - (5_263_000 as Weight) - // Standard Error: 2_000 - .saturating_add((191_000 as Weight).saturating_mul(r as Weight)) + (4_947_000 as Weight) + // Standard Error: 3_000 + .saturating_add((152_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Identity Registrars (r:1 w:0) // Storage: Identity IdentityOf (r:1 w:1) fn provide_judgement(r: u32, x: u32, ) -> Weight { - (21_511_000 as Weight) + (20_570_000 as Weight) // Standard Error: 3_000 - .saturating_add((219_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((213_000 as Weight).saturating_mul(r as Weight)) // Standard Error: 0 - .saturating_add((327_000 as Weight).saturating_mul(x as Weight)) + .saturating_add((345_000 as Weight).saturating_mul(x as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -352,11 +350,11 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) // Storage: Identity SuperOf (r:0 w:100) fn kill_identity(r: u32, s: u32, _x: u32, ) -> Weight { - (40_654_000 as Weight) - // Standard Error: 6_000 - .saturating_add((179_000 as Weight).saturating_mul(r as Weight)) + (41_448_000 as Weight) + // Standard Error: 7_000 + .saturating_add((149_000 as Weight).saturating_mul(r as Weight)) // Standard Error: 0 - .saturating_add((908_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((882_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) @@ -365,18 +363,18 @@ impl WeightInfo for () { // Storage: Identity SuperOf (r:1 w:1) // Storage: Identity SubsOf (r:1 w:1) fn add_sub(s: u32, ) -> Weight { - (31_573_000 as Weight) + (32_346_000 as Weight) // Standard Error: 1_000 - .saturating_add((151_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((114_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Identity IdentityOf (r:1 w:0) // Storage: Identity SuperOf (r:1 w:1) fn rename_sub(s: u32, ) -> Weight { - (10_452_000 as Weight) + (10_211_000 as Weight) // Standard Error: 0 - .saturating_add((45_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((51_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -384,18 +382,18 @@ impl WeightInfo for () { // Storage: Identity SuperOf (r:1 w:1) // Storage: Identity SubsOf (r:1 w:1) fn remove_sub(s: u32, ) -> Weight { - (32_711_000 as Weight) + (33_083_000 as Weight) // Standard Error: 1_000 - .saturating_add((150_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((112_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Identity SuperOf (r:1 w:1) // Storage: Identity SubsOf (r:1 w:1) fn quit_sub(s: u32, ) -> Weight { - (21_975_000 as Weight) - // Standard Error: 0 - .saturating_add((149_000 as Weight).saturating_mul(s as Weight)) + (22_517_000 as Weight) + // Standard Error: 1_000 + .saturating_add((110_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } diff --git a/frame/im-online/src/weights.rs b/frame/im-online/src/weights.rs index f7d4d1441da27..187499b1f957f 100644 --- a/frame/im-online/src/weights.rs +++ b/frame/im-online/src/weights.rs @@ -18,12 +18,13 @@ //! Autogenerated weights for pallet_im_online //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-01-31, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: // ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -31,11 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 +// --template=./.maintain/frame-weight-template.hbs // --output=./frame/im-online/src/weights.rs -// --template=.maintain/frame-weight-template.hbs -// --header=HEADER-APACHE2 -// --raw #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -58,11 +56,11 @@ impl WeightInfo for SubstrateWeight { // Storage: ImOnline AuthoredBlocks (r:1 w:0) // Storage: ImOnline Keys (r:1 w:0) fn validate_unsigned_and_then_heartbeat(k: u32, e: u32, ) -> Weight { - (74_137_000 as Weight) + (74_221_000 as Weight) // Standard Error: 0 - .saturating_add((125_000 as Weight).saturating_mul(k as Weight)) + .saturating_add((41_000 as Weight).saturating_mul(k as Weight)) // Standard Error: 0 - .saturating_add((291_000 as Weight).saturating_mul(e as Weight)) + .saturating_add((294_000 as Weight).saturating_mul(e as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -76,11 +74,11 @@ impl WeightInfo for () { // Storage: ImOnline AuthoredBlocks (r:1 w:0) // Storage: ImOnline Keys (r:1 w:0) fn validate_unsigned_and_then_heartbeat(k: u32, e: u32, ) -> Weight { - (74_137_000 as Weight) + (74_221_000 as Weight) // Standard Error: 0 - .saturating_add((125_000 as Weight).saturating_mul(k as Weight)) + .saturating_add((41_000 as Weight).saturating_mul(k as Weight)) // Standard Error: 0 - .saturating_add((291_000 as Weight).saturating_mul(e as Weight)) + .saturating_add((294_000 as Weight).saturating_mul(e as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } diff --git a/frame/indices/src/weights.rs b/frame/indices/src/weights.rs index 1687237394fbd..5fba8fc2377a0 100644 --- a/frame/indices/src/weights.rs +++ b/frame/indices/src/weights.rs @@ -18,12 +18,13 @@ //! Autogenerated weights for pallet_indices //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-01-31, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: // ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -31,11 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 +// --template=./.maintain/frame-weight-template.hbs // --output=./frame/indices/src/weights.rs -// --template=.maintain/frame-weight-template.hbs -// --header=HEADER-APACHE2 -// --raw #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -58,33 +56,33 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: Indices Accounts (r:1 w:1) fn claim() -> Weight { - (21_121_000 as Weight) + (21_333_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Indices Accounts (r:1 w:1) // Storage: System Account (r:1 w:1) fn transfer() -> Weight { - (26_843_000 as Weight) + (27_479_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Indices Accounts (r:1 w:1) fn free() -> Weight { - (22_174_000 as Weight) + (22_802_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Indices Accounts (r:1 w:1) // Storage: System Account (r:1 w:1) fn force_transfer() -> Weight { - (22_191_000 as Weight) + (23_369_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Indices Accounts (r:1 w:1) fn freeze() -> Weight { - (24_813_000 as Weight) + (25_740_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -94,33 +92,33 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { // Storage: Indices Accounts (r:1 w:1) fn claim() -> Weight { - (21_121_000 as Weight) + (21_333_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Indices Accounts (r:1 w:1) // Storage: System Account (r:1 w:1) fn transfer() -> Weight { - (26_843_000 as Weight) + (27_479_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Indices Accounts (r:1 w:1) fn free() -> Weight { - (22_174_000 as Weight) + (22_802_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Indices Accounts (r:1 w:1) // Storage: System Account (r:1 w:1) fn force_transfer() -> Weight { - (22_191_000 as Weight) + (23_369_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Indices Accounts (r:1 w:1) fn freeze() -> Weight { - (24_813_000 as Weight) + (25_740_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } diff --git a/frame/lottery/src/weights.rs b/frame/lottery/src/weights.rs index 3b6114a73cfaa..d3626dc9080ec 100644 --- a/frame/lottery/src/weights.rs +++ b/frame/lottery/src/weights.rs @@ -18,12 +18,13 @@ //! Autogenerated weights for pallet_lottery //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-01-31, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: // ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -31,11 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 +// --template=./.maintain/frame-weight-template.hbs // --output=./frame/lottery/src/weights.rs -// --template=.maintain/frame-weight-template.hbs -// --header=HEADER-APACHE2 -// --raw #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -65,28 +63,28 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) // Storage: Lottery Tickets (r:0 w:1) fn buy_ticket() -> Weight { - (37_257_000 as Weight) + (39_387_000 as Weight) .saturating_add(T::DbWeight::get().reads(6 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } // Storage: Lottery CallIndices (r:0 w:1) fn set_calls(n: u32, ) -> Weight { - (8_966_000 as Weight) - // Standard Error: 5_000 - .saturating_add((302_000 as Weight).saturating_mul(n as Weight)) + (9_353_000 as Weight) + // Standard Error: 4_000 + .saturating_add((297_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Lottery Lottery (r:1 w:1) // Storage: Lottery LotteryIndex (r:1 w:1) // Storage: System Account (r:1 w:1) fn start_lottery() -> Weight { - (32_282_000 as Weight) + (33_157_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: Lottery Lottery (r:1 w:1) fn stop_repeat() -> Weight { - (3_903_000 as Weight) + (4_015_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -96,7 +94,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Lottery TicketsCount (r:1 w:1) // Storage: Lottery Tickets (r:1 w:0) fn on_initialize_end() -> Weight { - (51_489_000 as Weight) + (53_539_000 as Weight) .saturating_add(T::DbWeight::get().reads(6 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -107,7 +105,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Lottery Tickets (r:1 w:0) // Storage: Lottery LotteryIndex (r:1 w:1) fn on_initialize_repeat() -> Weight { - (53_046_000 as Weight) + (56_103_000 as Weight) .saturating_add(T::DbWeight::get().reads(7 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } @@ -123,28 +121,28 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) // Storage: Lottery Tickets (r:0 w:1) fn buy_ticket() -> Weight { - (37_257_000 as Weight) + (39_387_000 as Weight) .saturating_add(RocksDbWeight::get().reads(6 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } // Storage: Lottery CallIndices (r:0 w:1) fn set_calls(n: u32, ) -> Weight { - (8_966_000 as Weight) - // Standard Error: 5_000 - .saturating_add((302_000 as Weight).saturating_mul(n as Weight)) + (9_353_000 as Weight) + // Standard Error: 4_000 + .saturating_add((297_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Lottery Lottery (r:1 w:1) // Storage: Lottery LotteryIndex (r:1 w:1) // Storage: System Account (r:1 w:1) fn start_lottery() -> Weight { - (32_282_000 as Weight) + (33_157_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: Lottery Lottery (r:1 w:1) fn stop_repeat() -> Weight { - (3_903_000 as Weight) + (4_015_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -154,7 +152,7 @@ impl WeightInfo for () { // Storage: Lottery TicketsCount (r:1 w:1) // Storage: Lottery Tickets (r:1 w:0) fn on_initialize_end() -> Weight { - (51_489_000 as Weight) + (53_539_000 as Weight) .saturating_add(RocksDbWeight::get().reads(6 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } @@ -165,7 +163,7 @@ impl WeightInfo for () { // Storage: Lottery Tickets (r:1 w:0) // Storage: Lottery LotteryIndex (r:1 w:1) fn on_initialize_repeat() -> Weight { - (53_046_000 as Weight) + (56_103_000 as Weight) .saturating_add(RocksDbWeight::get().reads(7 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } diff --git a/frame/membership/src/weights.rs b/frame/membership/src/weights.rs index 0d4936cfba1f9..7372ad37cdea2 100644 --- a/frame/membership/src/weights.rs +++ b/frame/membership/src/weights.rs @@ -18,12 +18,13 @@ //! Autogenerated weights for pallet_membership //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-01-31, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: // ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -31,11 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 +// --template=./.maintain/frame-weight-template.hbs // --output=./frame/membership/src/weights.rs -// --template=.maintain/frame-weight-template.hbs -// --header=HEADER-APACHE2 -// --raw #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -63,9 +61,9 @@ impl WeightInfo for SubstrateWeight { // Storage: TechnicalCommittee Members (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) fn add_member(m: u32, ) -> Weight { - (14_884_000 as Weight) - // Standard Error: 0 - .saturating_add((95_000 as Weight).saturating_mul(m as Weight)) + (15_572_000 as Weight) + // Standard Error: 1_000 + .saturating_add((48_000 as Weight).saturating_mul(m as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -75,9 +73,9 @@ impl WeightInfo for SubstrateWeight { // Storage: TechnicalCommittee Members (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) fn remove_member(m: u32, ) -> Weight { - (17_612_000 as Weight) + (18_090_000 as Weight) // Standard Error: 0 - .saturating_add((86_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((43_000 as Weight).saturating_mul(m as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -87,9 +85,9 @@ impl WeightInfo for SubstrateWeight { // Storage: TechnicalCommittee Members (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) fn swap_member(m: u32, ) -> Weight { - (17_580_000 as Weight) + (18_199_000 as Weight) // Standard Error: 0 - .saturating_add((98_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((54_000 as Weight).saturating_mul(m as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -99,9 +97,9 @@ impl WeightInfo for SubstrateWeight { // Storage: TechnicalCommittee Members (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) fn reset_member(m: u32, ) -> Weight { - (17_610_000 as Weight) + (18_238_000 as Weight) // Standard Error: 0 - .saturating_add((199_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((158_000 as Weight).saturating_mul(m as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -111,9 +109,9 @@ impl WeightInfo for SubstrateWeight { // Storage: TechnicalCommittee Members (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) fn change_key(m: u32, ) -> Weight { - (18_208_000 as Weight) + (18_911_000 as Weight) // Standard Error: 0 - .saturating_add((96_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((53_000 as Weight).saturating_mul(m as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -121,18 +119,18 @@ impl WeightInfo for SubstrateWeight { // Storage: TechnicalMembership Prime (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) fn set_prime(m: u32, ) -> Weight { - (4_661_000 as Weight) + (4_843_000 as Weight) // Standard Error: 0 - .saturating_add((70_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((27_000 as Weight).saturating_mul(m as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: TechnicalMembership Prime (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) fn clear_prime(m: u32, ) -> Weight { - (1_574_000 as Weight) + (1_662_000 as Weight) // Standard Error: 0 - .saturating_add((1_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((2_000 as Weight).saturating_mul(m as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } } @@ -144,9 +142,9 @@ impl WeightInfo for () { // Storage: TechnicalCommittee Members (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) fn add_member(m: u32, ) -> Weight { - (14_884_000 as Weight) - // Standard Error: 0 - .saturating_add((95_000 as Weight).saturating_mul(m as Weight)) + (15_572_000 as Weight) + // Standard Error: 1_000 + .saturating_add((48_000 as Weight).saturating_mul(m as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -156,9 +154,9 @@ impl WeightInfo for () { // Storage: TechnicalCommittee Members (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) fn remove_member(m: u32, ) -> Weight { - (17_612_000 as Weight) + (18_090_000 as Weight) // Standard Error: 0 - .saturating_add((86_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((43_000 as Weight).saturating_mul(m as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -168,9 +166,9 @@ impl WeightInfo for () { // Storage: TechnicalCommittee Members (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) fn swap_member(m: u32, ) -> Weight { - (17_580_000 as Weight) + (18_199_000 as Weight) // Standard Error: 0 - .saturating_add((98_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((54_000 as Weight).saturating_mul(m as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -180,9 +178,9 @@ impl WeightInfo for () { // Storage: TechnicalCommittee Members (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) fn reset_member(m: u32, ) -> Weight { - (17_610_000 as Weight) + (18_238_000 as Weight) // Standard Error: 0 - .saturating_add((199_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((158_000 as Weight).saturating_mul(m as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -192,9 +190,9 @@ impl WeightInfo for () { // Storage: TechnicalCommittee Members (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) fn change_key(m: u32, ) -> Weight { - (18_208_000 as Weight) + (18_911_000 as Weight) // Standard Error: 0 - .saturating_add((96_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((53_000 as Weight).saturating_mul(m as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } @@ -202,18 +200,18 @@ impl WeightInfo for () { // Storage: TechnicalMembership Prime (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) fn set_prime(m: u32, ) -> Weight { - (4_661_000 as Weight) + (4_843_000 as Weight) // Standard Error: 0 - .saturating_add((70_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((27_000 as Weight).saturating_mul(m as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: TechnicalMembership Prime (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) fn clear_prime(m: u32, ) -> Weight { - (1_574_000 as Weight) + (1_662_000 as Weight) // Standard Error: 0 - .saturating_add((1_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((2_000 as Weight).saturating_mul(m as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } } diff --git a/frame/multisig/src/weights.rs b/frame/multisig/src/weights.rs index 501fb9c184194..563dd20bae63d 100644 --- a/frame/multisig/src/weights.rs +++ b/frame/multisig/src/weights.rs @@ -18,12 +18,13 @@ //! Autogenerated weights for pallet_multisig //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-01-31, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: // ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -31,11 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 +// --template=./.maintain/frame-weight-template.hbs // --output=./frame/multisig/src/weights.rs -// --template=.maintain/frame-weight-template.hbs -// --header=HEADER-APACHE2 -// --raw #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -61,17 +59,15 @@ pub trait WeightInfo { /// Weights for pallet_multisig using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - fn as_multi_threshold_1(z: u32, ) -> Weight { - (16_534_000 as Weight) - // Standard Error: 0 - .saturating_add((1_000 as Weight).saturating_mul(z as Weight)) + fn as_multi_threshold_1(_z: u32, ) -> Weight { + (12_456_000 as Weight) } // Storage: Multisig Multisigs (r:1 w:1) // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) fn as_multi_create(s: u32, z: u32, ) -> Weight { - (30_917_000 as Weight) - // Standard Error: 1_000 - .saturating_add((131_000 as Weight).saturating_mul(s as Weight)) + (31_781_000 as Weight) + // Standard Error: 0 + .saturating_add((93_000 as Weight).saturating_mul(s as Weight)) // Standard Error: 0 .saturating_add((1_000 as Weight).saturating_mul(z as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) @@ -81,9 +77,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Multisig Calls (r:1 w:1) // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) fn as_multi_create_store(s: u32, z: u32, ) -> Weight { - (33_570_000 as Weight) + (35_669_000 as Weight) // Standard Error: 1_000 - .saturating_add((138_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((95_000 as Weight).saturating_mul(s as Weight)) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(z as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) @@ -91,9 +87,9 @@ impl WeightInfo for SubstrateWeight { } // Storage: Multisig Multisigs (r:1 w:1) fn as_multi_approve(s: u32, z: u32, ) -> Weight { - (21_051_000 as Weight) + (21_326_000 as Weight) // Standard Error: 0 - .saturating_add((126_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((91_000 as Weight).saturating_mul(s as Weight)) // Standard Error: 0 .saturating_add((1_000 as Weight).saturating_mul(z as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) @@ -102,9 +98,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Multisig Multisigs (r:1 w:1) // Storage: Multisig Calls (r:1 w:1) fn as_multi_approve_store(s: u32, z: u32, ) -> Weight { - (32_581_000 as Weight) + (35_296_000 as Weight) // Standard Error: 1_000 - .saturating_add((135_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((88_000 as Weight).saturating_mul(s as Weight)) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(z as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) @@ -114,9 +110,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Multisig Calls (r:1 w:1) // Storage: System Account (r:1 w:1) fn as_multi_complete(s: u32, z: u32, ) -> Weight { - (39_215_000 as Weight) + (39_872_000 as Weight) // Standard Error: 1_000 - .saturating_add((221_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((147_000 as Weight).saturating_mul(s as Weight)) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(z as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) @@ -125,18 +121,18 @@ impl WeightInfo for SubstrateWeight { // Storage: Multisig Multisigs (r:1 w:1) // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) fn approve_as_multi_create(s: u32, ) -> Weight { - (28_572_000 as Weight) - // Standard Error: 1_000 - .saturating_add((146_000 as Weight).saturating_mul(s as Weight)) + (29_680_000 as Weight) + // Standard Error: 0 + .saturating_add((110_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Multisig Multisigs (r:1 w:1) // Storage: Multisig Calls (r:1 w:0) fn approve_as_multi_approve(s: u32, ) -> Weight { - (18_032_000 as Weight) + (18_514_000 as Weight) // Standard Error: 0 - .saturating_add((151_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((111_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -144,18 +140,18 @@ impl WeightInfo for SubstrateWeight { // Storage: Multisig Calls (r:1 w:1) // Storage: System Account (r:1 w:1) fn approve_as_multi_complete(s: u32, ) -> Weight { - (56_353_000 as Weight) + (57_404_000 as Weight) // Standard Error: 1_000 - .saturating_add((239_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((157_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: Multisig Multisigs (r:1 w:1) // Storage: Multisig Calls (r:1 w:1) fn cancel_as_multi(s: u32, ) -> Weight { - (44_544_000 as Weight) - // Standard Error: 0 - .saturating_add((158_000 as Weight).saturating_mul(s as Weight)) + (45_860_000 as Weight) + // Standard Error: 1_000 + .saturating_add((111_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -163,17 +159,15 @@ impl WeightInfo for SubstrateWeight { // For backwards compatibility and tests impl WeightInfo for () { - fn as_multi_threshold_1(z: u32, ) -> Weight { - (16_534_000 as Weight) - // Standard Error: 0 - .saturating_add((1_000 as Weight).saturating_mul(z as Weight)) + fn as_multi_threshold_1(_z: u32, ) -> Weight { + (12_456_000 as Weight) } // Storage: Multisig Multisigs (r:1 w:1) // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) fn as_multi_create(s: u32, z: u32, ) -> Weight { - (30_917_000 as Weight) - // Standard Error: 1_000 - .saturating_add((131_000 as Weight).saturating_mul(s as Weight)) + (31_781_000 as Weight) + // Standard Error: 0 + .saturating_add((93_000 as Weight).saturating_mul(s as Weight)) // Standard Error: 0 .saturating_add((1_000 as Weight).saturating_mul(z as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) @@ -183,9 +177,9 @@ impl WeightInfo for () { // Storage: Multisig Calls (r:1 w:1) // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) fn as_multi_create_store(s: u32, z: u32, ) -> Weight { - (33_570_000 as Weight) + (35_669_000 as Weight) // Standard Error: 1_000 - .saturating_add((138_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((95_000 as Weight).saturating_mul(s as Weight)) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(z as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) @@ -193,9 +187,9 @@ impl WeightInfo for () { } // Storage: Multisig Multisigs (r:1 w:1) fn as_multi_approve(s: u32, z: u32, ) -> Weight { - (21_051_000 as Weight) + (21_326_000 as Weight) // Standard Error: 0 - .saturating_add((126_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((91_000 as Weight).saturating_mul(s as Weight)) // Standard Error: 0 .saturating_add((1_000 as Weight).saturating_mul(z as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) @@ -204,9 +198,9 @@ impl WeightInfo for () { // Storage: Multisig Multisigs (r:1 w:1) // Storage: Multisig Calls (r:1 w:1) fn as_multi_approve_store(s: u32, z: u32, ) -> Weight { - (32_581_000 as Weight) + (35_296_000 as Weight) // Standard Error: 1_000 - .saturating_add((135_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((88_000 as Weight).saturating_mul(s as Weight)) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(z as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) @@ -216,9 +210,9 @@ impl WeightInfo for () { // Storage: Multisig Calls (r:1 w:1) // Storage: System Account (r:1 w:1) fn as_multi_complete(s: u32, z: u32, ) -> Weight { - (39_215_000 as Weight) + (39_872_000 as Weight) // Standard Error: 1_000 - .saturating_add((221_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((147_000 as Weight).saturating_mul(s as Weight)) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(z as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) @@ -227,18 +221,18 @@ impl WeightInfo for () { // Storage: Multisig Multisigs (r:1 w:1) // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) fn approve_as_multi_create(s: u32, ) -> Weight { - (28_572_000 as Weight) - // Standard Error: 1_000 - .saturating_add((146_000 as Weight).saturating_mul(s as Weight)) + (29_680_000 as Weight) + // Standard Error: 0 + .saturating_add((110_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Multisig Multisigs (r:1 w:1) // Storage: Multisig Calls (r:1 w:0) fn approve_as_multi_approve(s: u32, ) -> Weight { - (18_032_000 as Weight) + (18_514_000 as Weight) // Standard Error: 0 - .saturating_add((151_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((111_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -246,18 +240,18 @@ impl WeightInfo for () { // Storage: Multisig Calls (r:1 w:1) // Storage: System Account (r:1 w:1) fn approve_as_multi_complete(s: u32, ) -> Weight { - (56_353_000 as Weight) + (57_404_000 as Weight) // Standard Error: 1_000 - .saturating_add((239_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((157_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: Multisig Multisigs (r:1 w:1) // Storage: Multisig Calls (r:1 w:1) fn cancel_as_multi(s: u32, ) -> Weight { - (44_544_000 as Weight) - // Standard Error: 0 - .saturating_add((158_000 as Weight).saturating_mul(s as Weight)) + (45_860_000 as Weight) + // Standard Error: 1_000 + .saturating_add((111_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } diff --git a/frame/nomination-pools/src/weights.rs b/frame/nomination-pools/src/weights.rs index 6a5a38ac0abf7..ef339231fe7bb 100644 --- a/frame/nomination-pools/src/weights.rs +++ b/frame/nomination-pools/src/weights.rs @@ -18,11 +18,11 @@ //! Autogenerated weights for pallet_nomination_pools //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-11, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/production/substrate +// ./target/production/substrate // benchmark // pallet // --chain=dev @@ -32,9 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 -// --output=./frame/nomination-pools/src/weights.rs // --template=./.maintain/frame-weight-template.hbs +// --output=./frame/nomination-pools/src/weights.rs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -78,7 +77,7 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn join() -> Weight { - (122_806_000 as Weight) + (124_114_000 as Weight) .saturating_add(T::DbWeight::get().reads(17 as Weight)) .saturating_add(T::DbWeight::get().writes(11 as Weight)) } @@ -92,7 +91,7 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn bond_extra_transfer() -> Weight { - (114_398_000 as Weight) + (115_734_000 as Weight) .saturating_add(T::DbWeight::get().reads(13 as Weight)) .saturating_add(T::DbWeight::get().writes(12 as Weight)) } @@ -106,7 +105,7 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList ListNodes (r:2 w:2) // Storage: BagsList ListBags (r:2 w:2) fn bond_extra_reward() -> Weight { - (128_370_000 as Weight) + (127_873_000 as Weight) .saturating_add(T::DbWeight::get().reads(13 as Weight)) .saturating_add(T::DbWeight::get().writes(12 as Weight)) } @@ -115,7 +114,7 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools RewardPools (r:1 w:1) // Storage: System Account (r:1 w:1) fn claim_payout() -> Weight { - (52_338_000 as Weight) + (51_510_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -134,7 +133,7 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools SubPoolsStorage (r:1 w:1) // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) fn unbond() -> Weight { - (120_866_000 as Weight) + (120_744_000 as Weight) .saturating_add(T::DbWeight::get().reads(18 as Weight)) .saturating_add(T::DbWeight::get().writes(13 as Weight)) } @@ -143,9 +142,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking CurrentEra (r:1 w:0) // Storage: Balances Locks (r:1 w:1) fn pool_withdraw_unbonded(s: u32, ) -> Weight { - (42_038_000 as Weight) + (41_267_000 as Weight) // Standard Error: 0 - .saturating_add((52_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((53_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -158,7 +157,7 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) // Storage: NominationPools CounterForPoolMembers (r:1 w:1) fn withdraw_unbonded_update(s: u32, ) -> Weight { - (83_220_000 as Weight) + (82_234_000 as Weight) // Standard Error: 0 .saturating_add((48_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(8 as Weight)) @@ -183,8 +182,10 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) // Storage: NominationPools CounterForBondedPools (r:1 w:1) // Storage: Staking Payee (r:0 w:1) - fn withdraw_unbonded_kill(_s: u32, ) -> Weight { - (143_981_000 as Weight) + fn withdraw_unbonded_kill(s: u32, ) -> Weight { + (142_699_000 as Weight) + // Standard Error: 0 + .saturating_add((5_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(19 as Weight)) .saturating_add(T::DbWeight::get().writes(16 as Weight)) } @@ -211,7 +212,7 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn create() -> Weight { - (134_974_000 as Weight) + (133_702_000 as Weight) .saturating_add(T::DbWeight::get().reads(22 as Weight)) .saturating_add(T::DbWeight::get().writes(15 as Weight)) } @@ -228,9 +229,9 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList CounterForListNodes (r:1 w:1) // Storage: Staking CounterForNominators (r:1 w:1) fn nominate(n: u32, ) -> Weight { - (47_085_000 as Weight) - // Standard Error: 11_000 - .saturating_add((2_222_000 as Weight).saturating_mul(n as Weight)) + (46_887_000 as Weight) + // Standard Error: 12_000 + .saturating_add((2_277_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(12 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) .saturating_add(T::DbWeight::get().writes(5 as Weight)) @@ -238,7 +239,7 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Ledger (r:1 w:0) fn set_state() -> Weight { - (24_192_000 as Weight) + (24_030_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -246,9 +247,9 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools Metadata (r:1 w:1) // Storage: NominationPools CounterForMetadata (r:1 w:1) fn set_metadata(n: u32, ) -> Weight { - (11_448_000 as Weight) + (11_336_000 as Weight) // Standard Error: 0 - .saturating_add((1_000 as Weight).saturating_mul(n as Weight)) + .saturating_add((2_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -258,12 +259,12 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools MinCreateBond (r:0 w:1) // Storage: NominationPools MaxPools (r:0 w:1) fn set_configs() -> Weight { - (2_829_000 as Weight) + (2_927_000 as Weight) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } // Storage: NominationPools BondedPools (r:1 w:1) fn update_roles() -> Weight { - (19_017_000 as Weight) + (19_184_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -285,7 +286,7 @@ impl WeightInfo for () { // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn join() -> Weight { - (122_806_000 as Weight) + (124_114_000 as Weight) .saturating_add(RocksDbWeight::get().reads(17 as Weight)) .saturating_add(RocksDbWeight::get().writes(11 as Weight)) } @@ -299,7 +300,7 @@ impl WeightInfo for () { // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn bond_extra_transfer() -> Weight { - (114_398_000 as Weight) + (115_734_000 as Weight) .saturating_add(RocksDbWeight::get().reads(13 as Weight)) .saturating_add(RocksDbWeight::get().writes(12 as Weight)) } @@ -313,7 +314,7 @@ impl WeightInfo for () { // Storage: BagsList ListNodes (r:2 w:2) // Storage: BagsList ListBags (r:2 w:2) fn bond_extra_reward() -> Weight { - (128_370_000 as Weight) + (127_873_000 as Weight) .saturating_add(RocksDbWeight::get().reads(13 as Weight)) .saturating_add(RocksDbWeight::get().writes(12 as Weight)) } @@ -322,7 +323,7 @@ impl WeightInfo for () { // Storage: NominationPools RewardPools (r:1 w:1) // Storage: System Account (r:1 w:1) fn claim_payout() -> Weight { - (52_338_000 as Weight) + (51_510_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } @@ -341,7 +342,7 @@ impl WeightInfo for () { // Storage: NominationPools SubPoolsStorage (r:1 w:1) // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) fn unbond() -> Weight { - (120_866_000 as Weight) + (120_744_000 as Weight) .saturating_add(RocksDbWeight::get().reads(18 as Weight)) .saturating_add(RocksDbWeight::get().writes(13 as Weight)) } @@ -350,9 +351,9 @@ impl WeightInfo for () { // Storage: Staking CurrentEra (r:1 w:0) // Storage: Balances Locks (r:1 w:1) fn pool_withdraw_unbonded(s: u32, ) -> Weight { - (42_038_000 as Weight) + (41_267_000 as Weight) // Standard Error: 0 - .saturating_add((52_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((53_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -365,7 +366,7 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) // Storage: NominationPools CounterForPoolMembers (r:1 w:1) fn withdraw_unbonded_update(s: u32, ) -> Weight { - (83_220_000 as Weight) + (82_234_000 as Weight) // Standard Error: 0 .saturating_add((48_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(8 as Weight)) @@ -390,8 +391,10 @@ impl WeightInfo for () { // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) // Storage: NominationPools CounterForBondedPools (r:1 w:1) // Storage: Staking Payee (r:0 w:1) - fn withdraw_unbonded_kill(_s: u32, ) -> Weight { - (143_981_000 as Weight) + fn withdraw_unbonded_kill(s: u32, ) -> Weight { + (142_699_000 as Weight) + // Standard Error: 0 + .saturating_add((5_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(19 as Weight)) .saturating_add(RocksDbWeight::get().writes(16 as Weight)) } @@ -418,7 +421,7 @@ impl WeightInfo for () { // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn create() -> Weight { - (134_974_000 as Weight) + (133_702_000 as Weight) .saturating_add(RocksDbWeight::get().reads(22 as Weight)) .saturating_add(RocksDbWeight::get().writes(15 as Weight)) } @@ -435,9 +438,9 @@ impl WeightInfo for () { // Storage: BagsList CounterForListNodes (r:1 w:1) // Storage: Staking CounterForNominators (r:1 w:1) fn nominate(n: u32, ) -> Weight { - (47_085_000 as Weight) - // Standard Error: 11_000 - .saturating_add((2_222_000 as Weight).saturating_mul(n as Weight)) + (46_887_000 as Weight) + // Standard Error: 12_000 + .saturating_add((2_277_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(12 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) @@ -445,7 +448,7 @@ impl WeightInfo for () { // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Ledger (r:1 w:0) fn set_state() -> Weight { - (24_192_000 as Weight) + (24_030_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -453,9 +456,9 @@ impl WeightInfo for () { // Storage: NominationPools Metadata (r:1 w:1) // Storage: NominationPools CounterForMetadata (r:1 w:1) fn set_metadata(n: u32, ) -> Weight { - (11_448_000 as Weight) + (11_336_000 as Weight) // Standard Error: 0 - .saturating_add((1_000 as Weight).saturating_mul(n as Weight)) + .saturating_add((2_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -465,12 +468,12 @@ impl WeightInfo for () { // Storage: NominationPools MinCreateBond (r:0 w:1) // Storage: NominationPools MaxPools (r:0 w:1) fn set_configs() -> Weight { - (2_829_000 as Weight) + (2_927_000 as Weight) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } // Storage: NominationPools BondedPools (r:1 w:1) fn update_roles() -> Weight { - (19_017_000 as Weight) + (19_184_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } diff --git a/frame/preimage/src/weights.rs b/frame/preimage/src/weights.rs index 0d7f583727c99..801ad627e07d9 100644 --- a/frame/preimage/src/weights.rs +++ b/frame/preimage/src/weights.rs @@ -18,12 +18,13 @@ //! Autogenerated weights for pallet_preimage //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-01-31, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: // ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -31,11 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 +// --template=./.maintain/frame-weight-template.hbs // --output=./frame/preimage/src/weights.rs -// --template=.maintain/frame-weight-template.hbs -// --header=HEADER-APACHE2 -// --raw #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -93,58 +91,58 @@ impl WeightInfo for SubstrateWeight { // Storage: Preimage StatusFor (r:1 w:1) // Storage: Preimage PreimageFor (r:0 w:1) fn unnote_preimage() -> Weight { - (39_239_000 as Weight) + (38_459_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Preimage StatusFor (r:1 w:1) // Storage: Preimage PreimageFor (r:0 w:1) fn unnote_no_deposit_preimage() -> Weight { - (24_905_000 as Weight) + (24_172_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Preimage StatusFor (r:1 w:1) fn request_preimage() -> Weight { - (37_451_000 as Weight) + (37_200_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Preimage StatusFor (r:1 w:1) fn request_no_deposit_preimage() -> Weight { - (23_397_000 as Weight) + (24_071_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Preimage StatusFor (r:1 w:1) fn request_unnoted_preimage() -> Weight { - (13_407_000 as Weight) + (13_974_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Preimage StatusFor (r:1 w:1) fn request_requested_preimage() -> Weight { - (4_202_000 as Weight) + (4_401_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Preimage StatusFor (r:1 w:1) // Storage: Preimage PreimageFor (r:0 w:1) fn unrequest_preimage() -> Weight { - (24_858_000 as Weight) + (24_756_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Preimage StatusFor (r:1 w:1) // Storage: Preimage PreimageFor (r:0 w:1) fn unrequest_unnoted_preimage() -> Weight { - (13_763_000 as Weight) + (14_351_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Preimage StatusFor (r:1 w:1) fn unrequest_multi_referenced_preimage() -> Weight { - (4_262_000 as Weight) + (4_359_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -182,58 +180,58 @@ impl WeightInfo for () { // Storage: Preimage StatusFor (r:1 w:1) // Storage: Preimage PreimageFor (r:0 w:1) fn unnote_preimage() -> Weight { - (39_239_000 as Weight) + (38_459_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Preimage StatusFor (r:1 w:1) // Storage: Preimage PreimageFor (r:0 w:1) fn unnote_no_deposit_preimage() -> Weight { - (24_905_000 as Weight) + (24_172_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Preimage StatusFor (r:1 w:1) fn request_preimage() -> Weight { - (37_451_000 as Weight) + (37_200_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Preimage StatusFor (r:1 w:1) fn request_no_deposit_preimage() -> Weight { - (23_397_000 as Weight) + (24_071_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Preimage StatusFor (r:1 w:1) fn request_unnoted_preimage() -> Weight { - (13_407_000 as Weight) + (13_974_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Preimage StatusFor (r:1 w:1) fn request_requested_preimage() -> Weight { - (4_202_000 as Weight) + (4_401_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Preimage StatusFor (r:1 w:1) // Storage: Preimage PreimageFor (r:0 w:1) fn unrequest_preimage() -> Weight { - (24_858_000 as Weight) + (24_756_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Preimage StatusFor (r:1 w:1) // Storage: Preimage PreimageFor (r:0 w:1) fn unrequest_unnoted_preimage() -> Weight { - (13_763_000 as Weight) + (14_351_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Preimage StatusFor (r:1 w:1) fn unrequest_multi_referenced_preimage() -> Weight { - (4_262_000 as Weight) + (4_359_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } diff --git a/frame/proxy/src/weights.rs b/frame/proxy/src/weights.rs index 176f8683e0b5f..a0df59204f673 100644 --- a/frame/proxy/src/weights.rs +++ b/frame/proxy/src/weights.rs @@ -18,12 +18,13 @@ //! Autogenerated weights for pallet_proxy //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-01-31, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: // ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -31,11 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 +// --template=./.maintain/frame-weight-template.hbs // --output=./frame/proxy/src/weights.rs -// --template=.maintain/frame-weight-template.hbs -// --header=HEADER-APACHE2 -// --raw #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -63,42 +61,42 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: Proxy Proxies (r:1 w:0) fn proxy(p: u32, ) -> Weight { - (13_323_000 as Weight) + (13_918_000 as Weight) // Standard Error: 1_000 - .saturating_add((114_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((66_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) } // Storage: Proxy Proxies (r:1 w:0) // Storage: Proxy Announcements (r:1 w:1) // Storage: System Account (r:1 w:1) fn proxy_announced(a: u32, p: u32, ) -> Weight { - (28_825_000 as Weight) - // Standard Error: 1_000 - .saturating_add((254_000 as Weight).saturating_mul(a as Weight)) + (30_328_000 as Weight) // Standard Error: 2_000 - .saturating_add((120_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((159_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 2_000 + .saturating_add((64_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Proxy Announcements (r:1 w:1) // Storage: System Account (r:1 w:1) fn remove_announcement(a: u32, p: u32, ) -> Weight { - (20_594_000 as Weight) + (21_667_000 as Weight) // Standard Error: 1_000 - .saturating_add((259_000 as Weight).saturating_mul(a as Weight)) + .saturating_add((167_000 as Weight).saturating_mul(a as Weight)) // Standard Error: 1_000 - .saturating_add((15_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((4_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Proxy Announcements (r:1 w:1) // Storage: System Account (r:1 w:1) fn reject_announcement(a: u32, p: u32, ) -> Weight { - (20_316_000 as Weight) + (21_554_000 as Weight) // Standard Error: 1_000 - .saturating_add((265_000 as Weight).saturating_mul(a as Weight)) + .saturating_add((171_000 as Weight).saturating_mul(a as Weight)) // Standard Error: 1_000 - .saturating_add((18_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((5_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -106,52 +104,52 @@ impl WeightInfo for SubstrateWeight { // Storage: Proxy Announcements (r:1 w:1) // Storage: System Account (r:1 w:1) fn announce(a: u32, p: u32, ) -> Weight { - (27_696_000 as Weight) + (28_774_000 as Weight) // Standard Error: 2_000 - .saturating_add((241_000 as Weight).saturating_mul(a as Weight)) + .saturating_add((150_000 as Weight).saturating_mul(a as Weight)) // Standard Error: 2_000 - .saturating_add((111_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((67_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Proxy Proxies (r:1 w:1) fn add_proxy(p: u32, ) -> Weight { - (22_849_000 as Weight) - // Standard Error: 2_000 - .saturating_add((140_000 as Weight).saturating_mul(p as Weight)) + (23_571_000 as Weight) + // Standard Error: 1_000 + .saturating_add((110_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Proxy Proxies (r:1 w:1) fn remove_proxy(p: u32, ) -> Weight { - (19_350_000 as Weight) + (23_613_000 as Weight) // Standard Error: 2_000 - .saturating_add((147_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((120_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Proxy Proxies (r:1 w:1) fn remove_proxies(p: u32, ) -> Weight { - (18_878_000 as Weight) - // Standard Error: 2_000 - .saturating_add((112_000 as Weight).saturating_mul(p as Weight)) + (19_968_000 as Weight) + // Standard Error: 1_000 + .saturating_add((74_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) // Storage: Proxy Proxies (r:1 w:1) fn anonymous(p: u32, ) -> Weight { - (25_743_000 as Weight) + (26_479_000 as Weight) // Standard Error: 2_000 - .saturating_add((25_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((37_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Proxy Proxies (r:1 w:1) fn kill_anonymous(p: u32, ) -> Weight { - (20_484_000 as Weight) - // Standard Error: 2_000 - .saturating_add((105_000 as Weight).saturating_mul(p as Weight)) + (20_816_000 as Weight) + // Standard Error: 1_000 + .saturating_add((67_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -161,42 +159,42 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { // Storage: Proxy Proxies (r:1 w:0) fn proxy(p: u32, ) -> Weight { - (13_323_000 as Weight) + (13_918_000 as Weight) // Standard Error: 1_000 - .saturating_add((114_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((66_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) } // Storage: Proxy Proxies (r:1 w:0) // Storage: Proxy Announcements (r:1 w:1) // Storage: System Account (r:1 w:1) fn proxy_announced(a: u32, p: u32, ) -> Weight { - (28_825_000 as Weight) - // Standard Error: 1_000 - .saturating_add((254_000 as Weight).saturating_mul(a as Weight)) + (30_328_000 as Weight) // Standard Error: 2_000 - .saturating_add((120_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((159_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 2_000 + .saturating_add((64_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Proxy Announcements (r:1 w:1) // Storage: System Account (r:1 w:1) fn remove_announcement(a: u32, p: u32, ) -> Weight { - (20_594_000 as Weight) + (21_667_000 as Weight) // Standard Error: 1_000 - .saturating_add((259_000 as Weight).saturating_mul(a as Weight)) + .saturating_add((167_000 as Weight).saturating_mul(a as Weight)) // Standard Error: 1_000 - .saturating_add((15_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((4_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Proxy Announcements (r:1 w:1) // Storage: System Account (r:1 w:1) fn reject_announcement(a: u32, p: u32, ) -> Weight { - (20_316_000 as Weight) + (21_554_000 as Weight) // Standard Error: 1_000 - .saturating_add((265_000 as Weight).saturating_mul(a as Weight)) + .saturating_add((171_000 as Weight).saturating_mul(a as Weight)) // Standard Error: 1_000 - .saturating_add((18_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((5_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -204,52 +202,52 @@ impl WeightInfo for () { // Storage: Proxy Announcements (r:1 w:1) // Storage: System Account (r:1 w:1) fn announce(a: u32, p: u32, ) -> Weight { - (27_696_000 as Weight) + (28_774_000 as Weight) // Standard Error: 2_000 - .saturating_add((241_000 as Weight).saturating_mul(a as Weight)) + .saturating_add((150_000 as Weight).saturating_mul(a as Weight)) // Standard Error: 2_000 - .saturating_add((111_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((67_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Proxy Proxies (r:1 w:1) fn add_proxy(p: u32, ) -> Weight { - (22_849_000 as Weight) - // Standard Error: 2_000 - .saturating_add((140_000 as Weight).saturating_mul(p as Weight)) + (23_571_000 as Weight) + // Standard Error: 1_000 + .saturating_add((110_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Proxy Proxies (r:1 w:1) fn remove_proxy(p: u32, ) -> Weight { - (19_350_000 as Weight) + (23_613_000 as Weight) // Standard Error: 2_000 - .saturating_add((147_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((120_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Proxy Proxies (r:1 w:1) fn remove_proxies(p: u32, ) -> Weight { - (18_878_000 as Weight) - // Standard Error: 2_000 - .saturating_add((112_000 as Weight).saturating_mul(p as Weight)) + (19_968_000 as Weight) + // Standard Error: 1_000 + .saturating_add((74_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) // Storage: Proxy Proxies (r:1 w:1) fn anonymous(p: u32, ) -> Weight { - (25_743_000 as Weight) + (26_479_000 as Weight) // Standard Error: 2_000 - .saturating_add((25_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((37_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Proxy Proxies (r:1 w:1) fn kill_anonymous(p: u32, ) -> Weight { - (20_484_000 as Weight) - // Standard Error: 2_000 - .saturating_add((105_000 as Weight).saturating_mul(p as Weight)) + (20_816_000 as Weight) + // Standard Error: 1_000 + .saturating_add((67_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } diff --git a/frame/recovery/src/weights.rs b/frame/recovery/src/weights.rs index 4e5fb91ae69e1..34a58b9e51ff2 100644 --- a/frame/recovery/src/weights.rs +++ b/frame/recovery/src/weights.rs @@ -18,24 +18,22 @@ //! Autogenerated weights for pallet_recovery //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-04-26, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/production/substrate +// ./target/production/substrate // benchmark // pallet // --chain=dev // --steps=50 // --repeat=20 -// --pallet=pallet-recovery +// --pallet=pallet_recovery // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 +// --template=./.maintain/frame-weight-template.hbs // --output=./frame/recovery/src/weights.rs -// --template=.maintain/frame-weight-template.hbs -// --header=HEADER-APACHE2 #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -62,35 +60,35 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: Recovery Proxy (r:1 w:0) fn as_recovered() -> Weight { - (3_667_000 as Weight) + (3_732_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) } // Storage: Recovery Proxy (r:0 w:1) fn set_recovered() -> Weight { - (10_149_000 as Weight) + (10_201_000 as Weight) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Recovery Recoverable (r:1 w:1) fn create_recovery(n: u32, ) -> Weight { - (23_154_000 as Weight) - // Standard Error: 16_000 - .saturating_add((235_000 as Weight).saturating_mul(n as Weight)) + (23_334_000 as Weight) + // Standard Error: 13_000 + .saturating_add((201_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Recovery Recoverable (r:1 w:0) // Storage: Recovery ActiveRecoveries (r:1 w:1) fn initiate_recovery() -> Weight { - (28_857_000 as Weight) + (28_358_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Recovery Recoverable (r:1 w:0) // Storage: Recovery ActiveRecoveries (r:1 w:1) fn vouch_recovery(n: u32, ) -> Weight { - (17_841_000 as Weight) - // Standard Error: 10_000 - .saturating_add((349_000 as Weight).saturating_mul(n as Weight)) + (17_667_000 as Weight) + // Standard Error: 13_000 + .saturating_add((342_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -98,33 +96,33 @@ impl WeightInfo for SubstrateWeight { // Storage: Recovery ActiveRecoveries (r:1 w:0) // Storage: Recovery Proxy (r:1 w:1) fn claim_recovery(n: u32, ) -> Weight { - (24_927_000 as Weight) + (24_591_000 as Weight) // Standard Error: 14_000 - .saturating_add((222_000 as Weight).saturating_mul(n as Weight)) + .saturating_add((242_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Recovery ActiveRecoveries (r:1 w:1) // Storage: System Account (r:1 w:1) fn close_recovery(n: u32, ) -> Weight { - (28_201_000 as Weight) - // Standard Error: 17_000 - .saturating_add((272_000 as Weight).saturating_mul(n as Weight)) + (28_763_000 as Weight) + // Standard Error: 12_000 + .saturating_add((142_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Recovery ActiveRecoveries (r:1 w:0) // Storage: Recovery Recoverable (r:1 w:1) fn remove_recovery(n: u32, ) -> Weight { - (27_531_000 as Weight) - // Standard Error: 10_000 - .saturating_add((218_000 as Weight).saturating_mul(n as Weight)) + (27_357_000 as Weight) + // Standard Error: 17_000 + .saturating_add((212_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Recovery Proxy (r:1 w:1) fn cancel_recovered() -> Weight { - (9_015_000 as Weight) + (9_068_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -134,35 +132,35 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { // Storage: Recovery Proxy (r:1 w:0) fn as_recovered() -> Weight { - (3_667_000 as Weight) + (3_732_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) } // Storage: Recovery Proxy (r:0 w:1) fn set_recovered() -> Weight { - (10_149_000 as Weight) + (10_201_000 as Weight) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Recovery Recoverable (r:1 w:1) fn create_recovery(n: u32, ) -> Weight { - (23_154_000 as Weight) - // Standard Error: 16_000 - .saturating_add((235_000 as Weight).saturating_mul(n as Weight)) + (23_334_000 as Weight) + // Standard Error: 13_000 + .saturating_add((201_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Recovery Recoverable (r:1 w:0) // Storage: Recovery ActiveRecoveries (r:1 w:1) fn initiate_recovery() -> Weight { - (28_857_000 as Weight) + (28_358_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Recovery Recoverable (r:1 w:0) // Storage: Recovery ActiveRecoveries (r:1 w:1) fn vouch_recovery(n: u32, ) -> Weight { - (17_841_000 as Weight) - // Standard Error: 10_000 - .saturating_add((349_000 as Weight).saturating_mul(n as Weight)) + (17_667_000 as Weight) + // Standard Error: 13_000 + .saturating_add((342_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -170,33 +168,33 @@ impl WeightInfo for () { // Storage: Recovery ActiveRecoveries (r:1 w:0) // Storage: Recovery Proxy (r:1 w:1) fn claim_recovery(n: u32, ) -> Weight { - (24_927_000 as Weight) + (24_591_000 as Weight) // Standard Error: 14_000 - .saturating_add((222_000 as Weight).saturating_mul(n as Weight)) + .saturating_add((242_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Recovery ActiveRecoveries (r:1 w:1) // Storage: System Account (r:1 w:1) fn close_recovery(n: u32, ) -> Weight { - (28_201_000 as Weight) - // Standard Error: 17_000 - .saturating_add((272_000 as Weight).saturating_mul(n as Weight)) + (28_763_000 as Weight) + // Standard Error: 12_000 + .saturating_add((142_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Recovery ActiveRecoveries (r:1 w:0) // Storage: Recovery Recoverable (r:1 w:1) fn remove_recovery(n: u32, ) -> Weight { - (27_531_000 as Weight) - // Standard Error: 10_000 - .saturating_add((218_000 as Weight).saturating_mul(n as Weight)) + (27_357_000 as Weight) + // Standard Error: 17_000 + .saturating_add((212_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Recovery Proxy (r:1 w:1) fn cancel_recovered() -> Weight { - (9_015_000 as Weight) + (9_068_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } diff --git a/frame/referenda/src/weights.rs b/frame/referenda/src/weights.rs index 202901bdd10bd..d9ddbed49e883 100644 --- a/frame/referenda/src/weights.rs +++ b/frame/referenda/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) 2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,12 +18,13 @@ //! Autogenerated weights for pallet_referenda //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2021-12-19, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 +//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/substrate +// ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -31,9 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 -// --output=./frame/referenda/src/weights.rs // --template=./.maintain/frame-weight-template.hbs +// --output=./frame/referenda/src/weights.rs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -80,14 +80,14 @@ impl WeightInfo for SubstrateWeight { // Storage: Scheduler Agenda (r:1 w:1) // Storage: Referenda ReferendumInfoFor (r:0 w:1) fn submit() -> Weight { - (42_395_000 as Weight) + (30_596_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn place_decision_deposit_preparing() -> Weight { - (48_113_000 as Weight) + (40_115_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -95,7 +95,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda DecidingCount (r:1 w:0) // Storage: Referenda TrackQueue (r:1 w:1) fn place_decision_deposit_queued() -> Weight { - (53_624_000 as Weight) + (45_739_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -103,7 +103,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda DecidingCount (r:1 w:0) // Storage: Referenda TrackQueue (r:1 w:1) fn place_decision_deposit_not_queued() -> Weight { - (52_560_000 as Weight) + (45_741_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -111,7 +111,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda DecidingCount (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn place_decision_deposit_passing() -> Weight { - (54_067_000 as Weight) + (51_422_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -119,34 +119,34 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda DecidingCount (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn place_decision_deposit_failing() -> Weight { - (52_457_000 as Weight) + (48_714_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) fn refund_decision_deposit() -> Weight { - (28_504_000 as Weight) + (24_896_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn cancel() -> Weight { - (40_425_000 as Weight) + (30_511_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn kill() -> Weight { - (65_974_000 as Weight) + (55_662_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: Referenda TrackQueue (r:1 w:0) // Storage: Referenda DecidingCount (r:1 w:1) fn one_fewer_deciding_queue_empty() -> Weight { - (8_904_000 as Weight) + (6_659_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -154,7 +154,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn one_fewer_deciding_failing() -> Weight { - (181_387_000 as Weight) + (109_530_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -162,7 +162,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn one_fewer_deciding_passing() -> Weight { - (179_753_000 as Weight) + (110_576_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -170,7 +170,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda TrackQueue (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_requeued_insertion() -> Weight { - (53_592_000 as Weight) + (39_327_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -178,7 +178,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda TrackQueue (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_requeued_slide() -> Weight { - (53_173_000 as Weight) + (39_179_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -187,7 +187,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda TrackQueue (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_queued() -> Weight { - (55_770_000 as Weight) + (41_251_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -196,27 +196,27 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda TrackQueue (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_not_queued() -> Weight { - (53_922_000 as Weight) + (41_009_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_no_deposit() -> Weight { - (26_906_000 as Weight) + (20_228_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_preparing() -> Weight { - (27_943_000 as Weight) + (20_456_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) fn nudge_referendum_timed_out() -> Weight { - (20_256_000 as Weight) + (16_202_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -224,7 +224,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda DecidingCount (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_begin_deciding_failing() -> Weight { - (30_964_000 as Weight) + (30_335_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -232,35 +232,35 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda DecidingCount (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_begin_deciding_passing() -> Weight { - (31_763_000 as Weight) + (32_700_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_begin_confirming() -> Weight { - (28_892_000 as Weight) + (27_225_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_end_confirming() -> Weight { - (29_666_000 as Weight) + (28_355_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_continue_not_confirming() -> Weight { - (29_740_000 as Weight) + (26_515_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_continue_confirming() -> Weight { - (29_661_000 as Weight) + (24_991_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -269,14 +269,14 @@ impl WeightInfo for SubstrateWeight { // Storage: Scheduler Lookup (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) fn nudge_referendum_approved() -> Weight { - (55_736_000 as Weight) + (45_164_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_rejected() -> Weight { - (32_726_000 as Weight) + (28_026_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -288,14 +288,14 @@ impl WeightInfo for () { // Storage: Scheduler Agenda (r:1 w:1) // Storage: Referenda ReferendumInfoFor (r:0 w:1) fn submit() -> Weight { - (42_395_000 as Weight) + (30_596_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn place_decision_deposit_preparing() -> Weight { - (48_113_000 as Weight) + (40_115_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -303,7 +303,7 @@ impl WeightInfo for () { // Storage: Referenda DecidingCount (r:1 w:0) // Storage: Referenda TrackQueue (r:1 w:1) fn place_decision_deposit_queued() -> Weight { - (53_624_000 as Weight) + (45_739_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -311,7 +311,7 @@ impl WeightInfo for () { // Storage: Referenda DecidingCount (r:1 w:0) // Storage: Referenda TrackQueue (r:1 w:1) fn place_decision_deposit_not_queued() -> Weight { - (52_560_000 as Weight) + (45_741_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -319,7 +319,7 @@ impl WeightInfo for () { // Storage: Referenda DecidingCount (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn place_decision_deposit_passing() -> Weight { - (54_067_000 as Weight) + (51_422_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } @@ -327,34 +327,34 @@ impl WeightInfo for () { // Storage: Referenda DecidingCount (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn place_decision_deposit_failing() -> Weight { - (52_457_000 as Weight) + (48_714_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) fn refund_decision_deposit() -> Weight { - (28_504_000 as Weight) + (24_896_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn cancel() -> Weight { - (40_425_000 as Weight) + (30_511_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn kill() -> Weight { - (65_974_000 as Weight) + (55_662_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: Referenda TrackQueue (r:1 w:0) // Storage: Referenda DecidingCount (r:1 w:1) fn one_fewer_deciding_queue_empty() -> Weight { - (8_904_000 as Weight) + (6_659_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -362,7 +362,7 @@ impl WeightInfo for () { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn one_fewer_deciding_failing() -> Weight { - (181_387_000 as Weight) + (109_530_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } @@ -370,7 +370,7 @@ impl WeightInfo for () { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn one_fewer_deciding_passing() -> Weight { - (179_753_000 as Weight) + (110_576_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } @@ -378,7 +378,7 @@ impl WeightInfo for () { // Storage: Referenda TrackQueue (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_requeued_insertion() -> Weight { - (53_592_000 as Weight) + (39_327_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -386,7 +386,7 @@ impl WeightInfo for () { // Storage: Referenda TrackQueue (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_requeued_slide() -> Weight { - (53_173_000 as Weight) + (39_179_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -395,7 +395,7 @@ impl WeightInfo for () { // Storage: Referenda TrackQueue (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_queued() -> Weight { - (55_770_000 as Weight) + (41_251_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -404,27 +404,27 @@ impl WeightInfo for () { // Storage: Referenda TrackQueue (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_not_queued() -> Weight { - (53_922_000 as Weight) + (41_009_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_no_deposit() -> Weight { - (26_906_000 as Weight) + (20_228_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_preparing() -> Weight { - (27_943_000 as Weight) + (20_456_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) fn nudge_referendum_timed_out() -> Weight { - (20_256_000 as Weight) + (16_202_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -432,7 +432,7 @@ impl WeightInfo for () { // Storage: Referenda DecidingCount (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_begin_deciding_failing() -> Weight { - (30_964_000 as Weight) + (30_335_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -440,35 +440,35 @@ impl WeightInfo for () { // Storage: Referenda DecidingCount (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_begin_deciding_passing() -> Weight { - (31_763_000 as Weight) + (32_700_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_begin_confirming() -> Weight { - (28_892_000 as Weight) + (27_225_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_end_confirming() -> Weight { - (29_666_000 as Weight) + (28_355_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_continue_not_confirming() -> Weight { - (29_740_000 as Weight) + (26_515_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_continue_confirming() -> Weight { - (29_661_000 as Weight) + (24_991_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -477,14 +477,14 @@ impl WeightInfo for () { // Storage: Scheduler Lookup (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) fn nudge_referendum_approved() -> Weight { - (55_736_000 as Weight) + (45_164_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_rejected() -> Weight { - (32_726_000 as Weight) + (28_026_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } diff --git a/frame/remark/src/weights.rs b/frame/remark/src/weights.rs index 50b0fc3ebfc19..b667a03bbd472 100644 --- a/frame/remark/src/weights.rs +++ b/frame/remark/src/weights.rs @@ -18,12 +18,13 @@ //! Autogenerated weights for pallet_remark //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-03-11, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/release/substrate +// ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -31,9 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 -// --output=./frame/remark/src/weights.rs // --template=./.maintain/frame-weight-template.hbs +// --output=./frame/remark/src/weights.rs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -52,7 +52,7 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) fn store(l: u32, ) -> Weight { - (18_328_000 as Weight) + (9_699_000 as Weight) // Standard Error: 0 .saturating_add((1_000 as Weight).saturating_mul(l as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) @@ -63,7 +63,7 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) fn store(l: u32, ) -> Weight { - (18_328_000 as Weight) + (9_699_000 as Weight) // Standard Error: 0 .saturating_add((1_000 as Weight).saturating_mul(l as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) diff --git a/frame/scheduler/src/weights.rs b/frame/scheduler/src/weights.rs index dd00b8dfe6cd6..d56c3208b44af 100644 --- a/frame/scheduler/src/weights.rs +++ b/frame/scheduler/src/weights.rs @@ -18,12 +18,13 @@ //! Autogenerated weights for pallet_scheduler //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-01-31, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: // ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -31,11 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 +// --template=./.maintain/frame-weight-template.hbs // --output=./frame/scheduler/src/weights.rs -// --template=.maintain/frame-weight-template.hbs -// --header=HEADER-APACHE2 -// --raw #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -70,9 +68,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Preimage StatusFor (r:1 w:1) // Storage: Scheduler Lookup (r:0 w:1) fn on_initialize_periodic_named_resolved(s: u32, ) -> Weight { - (11_587_000 as Weight) - // Standard Error: 17_000 - .saturating_add((17_428_000 as Weight).saturating_mul(s as Weight)) + (9_814_000 as Weight) + // Standard Error: 13_000 + .saturating_add((18_293_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(s as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -83,9 +81,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Preimage StatusFor (r:1 w:1) // Storage: Scheduler Lookup (r:0 w:1) fn on_initialize_named_resolved(s: u32, ) -> Weight { - (8_965_000 as Weight) + (9_050_000 as Weight) // Standard Error: 11_000 - .saturating_add((13_410_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((14_029_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(s as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -95,9 +93,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Preimage PreimageFor (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) fn on_initialize_periodic_resolved(s: u32, ) -> Weight { - (8_654_000 as Weight) - // Standard Error: 17_000 - .saturating_add((14_990_000 as Weight).saturating_mul(s as Weight)) + (9_717_000 as Weight) + // Standard Error: 14_000 + .saturating_add((15_584_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(s as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -107,9 +105,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Preimage PreimageFor (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) fn on_initialize_resolved(s: u32, ) -> Weight { - (9_303_000 as Weight) - // Standard Error: 10_000 - .saturating_add((12_244_000 as Weight).saturating_mul(s as Weight)) + (9_422_000 as Weight) + // Standard Error: 9_000 + .saturating_add((12_812_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(s as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -119,9 +117,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Preimage PreimageFor (r:1 w:0) // Storage: Scheduler Lookup (r:0 w:1) fn on_initialize_named_aborted(s: u32, ) -> Weight { - (7_506_000 as Weight) - // Standard Error: 3_000 - .saturating_add((5_208_000 as Weight).saturating_mul(s as Weight)) + (6_504_000 as Weight) + // Standard Error: 7_000 + .saturating_add((5_410_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(s as Weight))) .saturating_add(T::DbWeight::get().writes(2 as Weight)) @@ -130,9 +128,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Scheduler Agenda (r:2 w:2) // Storage: Preimage PreimageFor (r:1 w:0) fn on_initialize_aborted(s: u32, ) -> Weight { - (8_046_000 as Weight) + (8_690_000 as Weight) // Standard Error: 3_000 - .saturating_add((2_914_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((2_951_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(s as Weight))) .saturating_add(T::DbWeight::get().writes(2 as Weight)) @@ -140,9 +138,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Scheduler Agenda (r:2 w:2) // Storage: Scheduler Lookup (r:0 w:1) fn on_initialize_periodic_named(s: u32, ) -> Weight { - (13_704_000 as Weight) - // Standard Error: 4_000 - .saturating_add((8_186_000 as Weight).saturating_mul(s as Weight)) + (14_613_000 as Weight) + // Standard Error: 5_000 + .saturating_add((8_479_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(s as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -150,9 +148,9 @@ impl WeightInfo for SubstrateWeight { } // Storage: Scheduler Agenda (r:2 w:2) fn on_initialize_periodic(s: u32, ) -> Weight { - (12_668_000 as Weight) - // Standard Error: 5_000 - .saturating_add((5_868_000 as Weight).saturating_mul(s as Weight)) + (13_356_000 as Weight) + // Standard Error: 4_000 + .saturating_add((6_112_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(s as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -161,42 +159,42 @@ impl WeightInfo for SubstrateWeight { // Storage: Scheduler Agenda (r:1 w:1) // Storage: Scheduler Lookup (r:0 w:1) fn on_initialize_named(s: u32, ) -> Weight { - (13_946_000 as Weight) - // Standard Error: 4_000 - .saturating_add((4_367_000 as Weight).saturating_mul(s as Weight)) + (14_254_000 as Weight) + // Standard Error: 3_000 + .saturating_add((4_529_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) } // Storage: Scheduler Agenda (r:1 w:1) fn on_initialize(s: u32, ) -> Weight { - (13_151_000 as Weight) + (13_680_000 as Weight) // Standard Error: 4_000 - .saturating_add((3_455_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((3_559_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Scheduler Agenda (r:1 w:1) fn schedule(s: u32, ) -> Weight { - (14_040_000 as Weight) + (14_821_000 as Weight) // Standard Error: 1_000 - .saturating_add((89_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((85_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Scheduler Agenda (r:1 w:1) // Storage: Scheduler Lookup (r:0 w:1) fn cancel(s: u32, ) -> Weight { - (14_376_000 as Weight) + (15_029_000 as Weight) // Standard Error: 1_000 - .saturating_add((576_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((595_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Scheduler Lookup (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn schedule_named(s: u32, ) -> Weight { - (16_806_000 as Weight) + (17_498_000 as Weight) // Standard Error: 1_000 .saturating_add((102_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) @@ -205,9 +203,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Scheduler Lookup (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn cancel_named(s: u32, ) -> Weight { - (15_852_000 as Weight) - // Standard Error: 2_000 - .saturating_add((590_000 as Weight).saturating_mul(s as Weight)) + (16_695_000 as Weight) + // Standard Error: 1_000 + .saturating_add((610_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -220,9 +218,9 @@ impl WeightInfo for () { // Storage: Preimage StatusFor (r:1 w:1) // Storage: Scheduler Lookup (r:0 w:1) fn on_initialize_periodic_named_resolved(s: u32, ) -> Weight { - (11_587_000 as Weight) - // Standard Error: 17_000 - .saturating_add((17_428_000 as Weight).saturating_mul(s as Weight)) + (9_814_000 as Weight) + // Standard Error: 13_000 + .saturating_add((18_293_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(s as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -233,9 +231,9 @@ impl WeightInfo for () { // Storage: Preimage StatusFor (r:1 w:1) // Storage: Scheduler Lookup (r:0 w:1) fn on_initialize_named_resolved(s: u32, ) -> Weight { - (8_965_000 as Weight) + (9_050_000 as Weight) // Standard Error: 11_000 - .saturating_add((13_410_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((14_029_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().reads((2 as Weight).saturating_mul(s as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -245,9 +243,9 @@ impl WeightInfo for () { // Storage: Preimage PreimageFor (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) fn on_initialize_periodic_resolved(s: u32, ) -> Weight { - (8_654_000 as Weight) - // Standard Error: 17_000 - .saturating_add((14_990_000 as Weight).saturating_mul(s as Weight)) + (9_717_000 as Weight) + // Standard Error: 14_000 + .saturating_add((15_584_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(s as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -257,9 +255,9 @@ impl WeightInfo for () { // Storage: Preimage PreimageFor (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) fn on_initialize_resolved(s: u32, ) -> Weight { - (9_303_000 as Weight) - // Standard Error: 10_000 - .saturating_add((12_244_000 as Weight).saturating_mul(s as Weight)) + (9_422_000 as Weight) + // Standard Error: 9_000 + .saturating_add((12_812_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().reads((2 as Weight).saturating_mul(s as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -269,9 +267,9 @@ impl WeightInfo for () { // Storage: Preimage PreimageFor (r:1 w:0) // Storage: Scheduler Lookup (r:0 w:1) fn on_initialize_named_aborted(s: u32, ) -> Weight { - (7_506_000 as Weight) - // Standard Error: 3_000 - .saturating_add((5_208_000 as Weight).saturating_mul(s as Weight)) + (6_504_000 as Weight) + // Standard Error: 7_000 + .saturating_add((5_410_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(s as Weight))) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) @@ -280,9 +278,9 @@ impl WeightInfo for () { // Storage: Scheduler Agenda (r:2 w:2) // Storage: Preimage PreimageFor (r:1 w:0) fn on_initialize_aborted(s: u32, ) -> Weight { - (8_046_000 as Weight) + (8_690_000 as Weight) // Standard Error: 3_000 - .saturating_add((2_914_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((2_951_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(s as Weight))) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) @@ -290,9 +288,9 @@ impl WeightInfo for () { // Storage: Scheduler Agenda (r:2 w:2) // Storage: Scheduler Lookup (r:0 w:1) fn on_initialize_periodic_named(s: u32, ) -> Weight { - (13_704_000 as Weight) - // Standard Error: 4_000 - .saturating_add((8_186_000 as Weight).saturating_mul(s as Weight)) + (14_613_000 as Weight) + // Standard Error: 5_000 + .saturating_add((8_479_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(s as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -300,9 +298,9 @@ impl WeightInfo for () { } // Storage: Scheduler Agenda (r:2 w:2) fn on_initialize_periodic(s: u32, ) -> Weight { - (12_668_000 as Weight) - // Standard Error: 5_000 - .saturating_add((5_868_000 as Weight).saturating_mul(s as Weight)) + (13_356_000 as Weight) + // Standard Error: 4_000 + .saturating_add((6_112_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(s as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -311,42 +309,42 @@ impl WeightInfo for () { // Storage: Scheduler Agenda (r:1 w:1) // Storage: Scheduler Lookup (r:0 w:1) fn on_initialize_named(s: u32, ) -> Weight { - (13_946_000 as Weight) - // Standard Error: 4_000 - .saturating_add((4_367_000 as Weight).saturating_mul(s as Weight)) + (14_254_000 as Weight) + // Standard Error: 3_000 + .saturating_add((4_529_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) } // Storage: Scheduler Agenda (r:1 w:1) fn on_initialize(s: u32, ) -> Weight { - (13_151_000 as Weight) + (13_680_000 as Weight) // Standard Error: 4_000 - .saturating_add((3_455_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((3_559_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Scheduler Agenda (r:1 w:1) fn schedule(s: u32, ) -> Weight { - (14_040_000 as Weight) + (14_821_000 as Weight) // Standard Error: 1_000 - .saturating_add((89_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((85_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Scheduler Agenda (r:1 w:1) // Storage: Scheduler Lookup (r:0 w:1) fn cancel(s: u32, ) -> Weight { - (14_376_000 as Weight) + (15_029_000 as Weight) // Standard Error: 1_000 - .saturating_add((576_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((595_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Scheduler Lookup (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn schedule_named(s: u32, ) -> Weight { - (16_806_000 as Weight) + (17_498_000 as Weight) // Standard Error: 1_000 .saturating_add((102_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) @@ -355,9 +353,9 @@ impl WeightInfo for () { // Storage: Scheduler Lookup (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn cancel_named(s: u32, ) -> Weight { - (15_852_000 as Weight) - // Standard Error: 2_000 - .saturating_add((590_000 as Weight).saturating_mul(s as Weight)) + (16_695_000 as Weight) + // Standard Error: 1_000 + .saturating_add((610_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } diff --git a/frame/session/src/weights.rs b/frame/session/src/weights.rs index 39276fb0b17ee..eabb26dcb32f4 100644 --- a/frame/session/src/weights.rs +++ b/frame/session/src/weights.rs @@ -18,12 +18,13 @@ //! Autogenerated weights for pallet_session //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-01-31, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: // ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -31,11 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 +// --template=./.maintain/frame-weight-template.hbs // --output=./frame/session/src/weights.rs -// --template=.maintain/frame-weight-template.hbs -// --header=HEADER-APACHE2 -// --raw #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -57,7 +55,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Session NextKeys (r:1 w:1) // Storage: Session KeyOwner (r:4 w:4) fn set_keys() -> Weight { - (42_131_000 as Weight) + (43_230_000 as Weight) .saturating_add(T::DbWeight::get().reads(6 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } @@ -65,7 +63,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Session NextKeys (r:1 w:1) // Storage: Session KeyOwner (r:0 w:4) fn purge_keys() -> Weight { - (32_374_000 as Weight) + (33_000_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } @@ -77,7 +75,7 @@ impl WeightInfo for () { // Storage: Session NextKeys (r:1 w:1) // Storage: Session KeyOwner (r:4 w:4) fn set_keys() -> Weight { - (42_131_000 as Weight) + (43_230_000 as Weight) .saturating_add(RocksDbWeight::get().reads(6 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } @@ -85,7 +83,7 @@ impl WeightInfo for () { // Storage: Session NextKeys (r:1 w:1) // Storage: Session KeyOwner (r:0 w:4) fn purge_keys() -> Weight { - (32_374_000 as Weight) + (33_000_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } diff --git a/frame/staking/src/weights.rs b/frame/staking/src/weights.rs index 9d43cd8822600..e7f2e06e6dd07 100644 --- a/frame/staking/src/weights.rs +++ b/frame/staking/src/weights.rs @@ -18,12 +18,13 @@ //! Autogenerated weights for pallet_staking //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-03-02, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/production/substrate +// ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -31,9 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 -// --output=./frame/staking/src/weights.rs // --template=./.maintain/frame-weight-template.hbs +// --output=./frame/staking/src/weights.rs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -86,7 +86,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn bond() -> Weight { - (37_238_000 as Weight) + (38_923_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -96,7 +96,7 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn bond_extra() -> Weight { - (64_061_000 as Weight) + (68_860_000 as Weight) .saturating_add(T::DbWeight::get().reads(8 as Weight)) .saturating_add(T::DbWeight::get().writes(7 as Weight)) } @@ -110,7 +110,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking Bonded (r:1 w:0) // Storage: BagsList ListBags (r:2 w:2) fn unbond() -> Weight { - (70_069_000 as Weight) + (74_815_000 as Weight) .saturating_add(T::DbWeight::get().reads(12 as Weight)) .saturating_add(T::DbWeight::get().writes(8 as Weight)) } @@ -119,9 +119,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) fn withdraw_unbonded_update(s: u32, ) -> Weight { - (29_855_000 as Weight) + (30_797_000 as Weight) // Standard Error: 0 - .saturating_add((53_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((57_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -138,10 +138,8 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) // Storage: Balances Locks (r:1 w:1) // Storage: Staking Payee (r:0 w:1) - fn withdraw_unbonded_kill(s: u32, ) -> Weight { - (57_313_000 as Weight) - // Standard Error: 0 - .saturating_add((1_000 as Weight).saturating_mul(s as Weight)) + fn withdraw_unbonded_kill(_s: u32, ) -> Weight { + (60_515_000 as Weight) .saturating_add(T::DbWeight::get().reads(13 as Weight)) .saturating_add(T::DbWeight::get().writes(11 as Weight)) } @@ -150,23 +148,23 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking MinCommission (r:1 w:0) // Storage: Staking Validators (r:1 w:1) // Storage: Staking MaxValidatorsCount (r:1 w:0) - // Storage: Staking Nominators (r:1 w:1) - // Storage: Staking CounterForNominators (r:1 w:1) - // Storage: BagsList ListNodes (r:2 w:2) + // Storage: Staking Nominators (r:1 w:0) + // Storage: Staking Bonded (r:1 w:0) + // Storage: BagsList ListNodes (r:1 w:1) // Storage: BagsList ListBags (r:1 w:1) // Storage: BagsList CounterForListNodes (r:1 w:1) // Storage: Staking CounterForValidators (r:1 w:1) fn validate() -> Weight { - (44_448_000 as Weight) - .saturating_add(T::DbWeight::get().reads(12 as Weight)) - .saturating_add(T::DbWeight::get().writes(8 as Weight)) + (48_461_000 as Weight) + .saturating_add(T::DbWeight::get().reads(11 as Weight)) + .saturating_add(T::DbWeight::get().writes(5 as Weight)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking Nominators (r:1 w:1) fn kick(k: u32, ) -> Weight { - (13_902_000 as Weight) - // Standard Error: 12_000 - .saturating_add((7_421_000 as Weight).saturating_mul(k as Weight)) + (18_839_000 as Weight) + // Standard Error: 11_000 + .saturating_add((7_658_000 as Weight).saturating_mul(k as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(k as Weight))) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(k as Weight))) @@ -183,9 +181,9 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList CounterForListNodes (r:1 w:1) // Storage: Staking CounterForNominators (r:1 w:1) fn nominate(n: u32, ) -> Weight { - (49_580_000 as Weight) - // Standard Error: 9_000 - .saturating_add((3_362_000 as Weight).saturating_mul(n as Weight)) + (51_547_000 as Weight) + // Standard Error: 10_000 + .saturating_add((3_610_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(12 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) .saturating_add(T::DbWeight::get().writes(6 as Weight)) @@ -198,47 +196,47 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList ListBags (r:1 w:1) // Storage: BagsList CounterForListNodes (r:1 w:1) fn chill() -> Weight { - (44_180_000 as Weight) + (45_828_000 as Weight) .saturating_add(T::DbWeight::get().reads(8 as Weight)) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking Payee (r:0 w:1) fn set_payee() -> Weight { - (7_922_000 as Weight) + (7_646_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Staking Bonded (r:1 w:1) // Storage: Staking Ledger (r:2 w:2) fn set_controller() -> Weight { - (15_436_000 as Weight) + (15_974_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: Staking ValidatorCount (r:0 w:1) fn set_validator_count() -> Weight { - (1_091_000 as Weight) + (1_248_000 as Weight) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Staking ForceEra (r:0 w:1) fn force_no_eras() -> Weight { - (1_204_000 as Weight) + (1_350_000 as Weight) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Staking ForceEra (r:0 w:1) fn force_new_era() -> Weight { - (1_208_000 as Weight) + (1_276_000 as Weight) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Staking ForceEra (r:0 w:1) fn force_new_era_always() -> Weight { - (1_220_000 as Weight) + (1_321_000 as Weight) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Staking Invulnerables (r:0 w:1) fn set_invulnerables(v: u32, ) -> Weight { - (1_473_000 as Weight) + (1_517_000 as Weight) // Standard Error: 0 .saturating_add((9_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -257,18 +255,18 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking Payee (r:0 w:1) // Storage: Staking SpanSlash (r:0 w:2) fn force_unstake(s: u32, ) -> Weight { - (55_815_000 as Weight) + (58_451_000 as Weight) // Standard Error: 1_000 - .saturating_add((808_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((835_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(11 as Weight)) .saturating_add(T::DbWeight::get().writes(12 as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) } // Storage: Staking UnappliedSlashes (r:1 w:1) fn cancel_deferred_slash(s: u32, ) -> Weight { - (903_077_000 as Weight) - // Standard Error: 53_000 - .saturating_add((4_434_000 as Weight).saturating_mul(s as Weight)) + (895_257_000 as Weight) + // Standard Error: 56_000 + .saturating_add((4_978_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -283,9 +281,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking Payee (r:2 w:0) // Storage: System Account (r:2 w:2) fn payout_stakers_dead_controller(n: u32, ) -> Weight { - (79_440_000 as Weight) - // Standard Error: 14_000 - .saturating_add((24_005_000 as Weight).saturating_mul(n as Weight)) + (71_290_000 as Weight) + // Standard Error: 12_000 + .saturating_add((23_818_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(10 as Weight)) .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(n as Weight))) .saturating_add(T::DbWeight::get().writes(2 as Weight)) @@ -303,9 +301,9 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:2 w:2) // Storage: Balances Locks (r:2 w:2) fn payout_stakers_alive_staked(n: u32, ) -> Weight { - (99_118_000 as Weight) - // Standard Error: 20_000 - .saturating_add((33_274_000 as Weight).saturating_mul(n as Weight)) + (86_724_000 as Weight) + // Standard Error: 29_000 + .saturating_add((33_575_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(11 as Weight)) .saturating_add(T::DbWeight::get().reads((5 as Weight).saturating_mul(n as Weight))) .saturating_add(T::DbWeight::get().writes(3 as Weight)) @@ -318,9 +316,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking Bonded (r:1 w:0) // Storage: BagsList ListBags (r:2 w:2) fn rebond(l: u32, ) -> Weight { - (63_335_000 as Weight) - // Standard Error: 2_000 - .saturating_add((53_000 as Weight).saturating_mul(l as Weight)) + (68_577_000 as Weight) + // Standard Error: 3_000 + .saturating_add((55_000 as Weight).saturating_mul(l as Weight)) .saturating_add(T::DbWeight::get().reads(9 as Weight)) .saturating_add(T::DbWeight::get().writes(8 as Weight)) } @@ -336,7 +334,7 @@ impl WeightInfo for SubstrateWeight { fn set_history_depth(e: u32, ) -> Weight { (0 as Weight) // Standard Error: 58_000 - .saturating_add((20_386_000 as Weight).saturating_mul(e as Weight)) + .saturating_add((20_425_000 as Weight).saturating_mul(e as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) .saturating_add(T::DbWeight::get().writes((7 as Weight).saturating_mul(e as Weight))) @@ -355,22 +353,22 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking Payee (r:0 w:1) // Storage: Staking SpanSlash (r:0 w:1) fn reap_stash(s: u32, ) -> Weight { - (61_486_000 as Weight) + (63_865_000 as Weight) // Standard Error: 0 - .saturating_add((809_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((839_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(12 as Weight)) .saturating_add(T::DbWeight::get().writes(12 as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) } - // Storage: Staking CounterForNominators (r:1 w:0) - // Storage: Staking CounterForValidators (r:1 w:0) + // Storage: BagsList CounterForListNodes (r:1 w:0) + // Storage: Staking SlashingSpans (r:1 w:0) + // Storage: BagsList ListBags (r:200 w:0) + // Storage: BagsList ListNodes (r:101 w:0) + // Storage: Staking Nominators (r:101 w:0) // Storage: Staking Validators (r:2 w:0) // Storage: Staking Bonded (r:101 w:0) // Storage: Staking Ledger (r:101 w:0) - // Storage: Staking SlashingSpans (r:1 w:0) - // Storage: BagsList ListBags (r:200 w:0) - // Storage: BagsList ListNodes (r:100 w:0) - // Storage: Staking Nominators (r:100 w:0) + // Storage: Staking CounterForValidators (r:1 w:0) // Storage: Staking ValidatorCount (r:1 w:0) // Storage: Staking MinimumValidatorCount (r:1 w:0) // Storage: Staking CurrentEra (r:1 w:1) @@ -382,43 +380,42 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking ErasStartSessionIndex (r:0 w:1) fn new_era(v: u32, n: u32, ) -> Weight { (0 as Weight) - // Standard Error: 878_000 - .saturating_add((212_233_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 881_000 + .saturating_add((211_756_000 as Weight).saturating_mul(v as Weight)) // Standard Error: 44_000 - .saturating_add((30_364_000 as Weight).saturating_mul(n as Weight)) + .saturating_add((30_413_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(208 as Weight)) - .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(v as Weight))) + .saturating_add(T::DbWeight::get().reads((5 as Weight).saturating_mul(v as Weight))) .saturating_add(T::DbWeight::get().reads((4 as Weight).saturating_mul(n as Weight))) .saturating_add(T::DbWeight::get().writes(3 as Weight)) .saturating_add(T::DbWeight::get().writes((3 as Weight).saturating_mul(v as Weight))) } - // Storage: Staking CounterForNominators (r:1 w:0) - // Storage: Staking CounterForValidators (r:1 w:0) - // Storage: Staking Validators (r:501 w:0) - // Storage: Staking Bonded (r:1500 w:0) - // Storage: Staking Ledger (r:1500 w:0) + // Storage: BagsList CounterForListNodes (r:1 w:0) // Storage: Staking SlashingSpans (r:21 w:0) // Storage: BagsList ListBags (r:200 w:0) - // Storage: BagsList ListNodes (r:1000 w:0) - // Storage: Staking Nominators (r:1000 w:0) + // Storage: BagsList ListNodes (r:1500 w:0) + // Storage: Staking Nominators (r:1500 w:0) + // Storage: Staking Validators (r:500 w:0) + // Storage: Staking Bonded (r:1500 w:0) + // Storage: Staking Ledger (r:1500 w:0) fn get_npos_voters(v: u32, n: u32, s: u32, ) -> Weight { (0 as Weight) - // Standard Error: 95_000 - .saturating_add((18_439_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 95_000 - .saturating_add((20_382_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 3_232_000 - .saturating_add((4_870_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(204 as Weight)) - .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(v as Weight))) + // Standard Error: 86_000 + .saturating_add((23_814_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 86_000 + .saturating_add((21_657_000 as Weight).saturating_mul(n as Weight)) + // Standard Error: 2_924_000 + .saturating_add((19_067_000 as Weight).saturating_mul(s as Weight)) + .saturating_add(T::DbWeight::get().reads(202 as Weight)) + .saturating_add(T::DbWeight::get().reads((5 as Weight).saturating_mul(v as Weight))) .saturating_add(T::DbWeight::get().reads((4 as Weight).saturating_mul(n as Weight))) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(s as Weight))) } // Storage: Staking Validators (r:501 w:0) fn get_npos_targets(v: u32, ) -> Weight { (0 as Weight) - // Standard Error: 29_000 - .saturating_add((7_552_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 32_000 + .saturating_add((7_861_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(v as Weight))) } @@ -429,7 +426,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking MaxNominatorsCount (r:0 w:1) // Storage: Staking MinNominatorBond (r:0 w:1) fn set_staking_configs_all_set() -> Weight { - (3_597_000 as Weight) + (3_688_000 as Weight) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } // Storage: Staking MinCommission (r:0 w:1) @@ -439,7 +436,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking MaxNominatorsCount (r:0 w:1) // Storage: Staking MinNominatorBond (r:0 w:1) fn set_staking_configs_all_remove() -> Weight { - (3_198_000 as Weight) + (3_414_000 as Weight) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } // Storage: Staking Ledger (r:1 w:0) @@ -453,14 +450,14 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList ListBags (r:1 w:1) // Storage: BagsList CounterForListNodes (r:1 w:1) fn chill_other() -> Weight { - (55_725_000 as Weight) + (56_962_000 as Weight) .saturating_add(T::DbWeight::get().reads(11 as Weight)) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } // Storage: Staking MinCommission (r:1 w:0) // Storage: Staking Validators (r:1 w:1) fn force_apply_min_commission() -> Weight { - (8_946_000 as Weight) + (9_465_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -475,7 +472,7 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn bond() -> Weight { - (37_238_000 as Weight) + (38_923_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } @@ -485,7 +482,7 @@ impl WeightInfo for () { // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn bond_extra() -> Weight { - (64_061_000 as Weight) + (68_860_000 as Weight) .saturating_add(RocksDbWeight::get().reads(8 as Weight)) .saturating_add(RocksDbWeight::get().writes(7 as Weight)) } @@ -499,7 +496,7 @@ impl WeightInfo for () { // Storage: Staking Bonded (r:1 w:0) // Storage: BagsList ListBags (r:2 w:2) fn unbond() -> Weight { - (70_069_000 as Weight) + (74_815_000 as Weight) .saturating_add(RocksDbWeight::get().reads(12 as Weight)) .saturating_add(RocksDbWeight::get().writes(8 as Weight)) } @@ -508,9 +505,9 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) fn withdraw_unbonded_update(s: u32, ) -> Weight { - (29_855_000 as Weight) + (30_797_000 as Weight) // Standard Error: 0 - .saturating_add((53_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((57_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -527,10 +524,8 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) // Storage: Balances Locks (r:1 w:1) // Storage: Staking Payee (r:0 w:1) - fn withdraw_unbonded_kill(s: u32, ) -> Weight { - (57_313_000 as Weight) - // Standard Error: 0 - .saturating_add((1_000 as Weight).saturating_mul(s as Weight)) + fn withdraw_unbonded_kill(_s: u32, ) -> Weight { + (60_515_000 as Weight) .saturating_add(RocksDbWeight::get().reads(13 as Weight)) .saturating_add(RocksDbWeight::get().writes(11 as Weight)) } @@ -539,23 +534,23 @@ impl WeightInfo for () { // Storage: Staking MinCommission (r:1 w:0) // Storage: Staking Validators (r:1 w:1) // Storage: Staking MaxValidatorsCount (r:1 w:0) - // Storage: Staking Nominators (r:1 w:1) - // Storage: Staking CounterForNominators (r:1 w:1) - // Storage: BagsList ListNodes (r:2 w:2) + // Storage: Staking Nominators (r:1 w:0) + // Storage: Staking Bonded (r:1 w:0) + // Storage: BagsList ListNodes (r:1 w:1) // Storage: BagsList ListBags (r:1 w:1) // Storage: BagsList CounterForListNodes (r:1 w:1) // Storage: Staking CounterForValidators (r:1 w:1) fn validate() -> Weight { - (44_448_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(12 as Weight)) - .saturating_add(RocksDbWeight::get().writes(8 as Weight)) + (48_461_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(11 as Weight)) + .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking Nominators (r:1 w:1) fn kick(k: u32, ) -> Weight { - (13_902_000 as Weight) - // Standard Error: 12_000 - .saturating_add((7_421_000 as Weight).saturating_mul(k as Weight)) + (18_839_000 as Weight) + // Standard Error: 11_000 + .saturating_add((7_658_000 as Weight).saturating_mul(k as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(k as Weight))) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(k as Weight))) @@ -572,9 +567,9 @@ impl WeightInfo for () { // Storage: BagsList CounterForListNodes (r:1 w:1) // Storage: Staking CounterForNominators (r:1 w:1) fn nominate(n: u32, ) -> Weight { - (49_580_000 as Weight) - // Standard Error: 9_000 - .saturating_add((3_362_000 as Weight).saturating_mul(n as Weight)) + (51_547_000 as Weight) + // Standard Error: 10_000 + .saturating_add((3_610_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(12 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) @@ -587,47 +582,47 @@ impl WeightInfo for () { // Storage: BagsList ListBags (r:1 w:1) // Storage: BagsList CounterForListNodes (r:1 w:1) fn chill() -> Weight { - (44_180_000 as Weight) + (45_828_000 as Weight) .saturating_add(RocksDbWeight::get().reads(8 as Weight)) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking Payee (r:0 w:1) fn set_payee() -> Weight { - (7_922_000 as Weight) + (7_646_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Staking Bonded (r:1 w:1) // Storage: Staking Ledger (r:2 w:2) fn set_controller() -> Weight { - (15_436_000 as Weight) + (15_974_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: Staking ValidatorCount (r:0 w:1) fn set_validator_count() -> Weight { - (1_091_000 as Weight) + (1_248_000 as Weight) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Staking ForceEra (r:0 w:1) fn force_no_eras() -> Weight { - (1_204_000 as Weight) + (1_350_000 as Weight) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Staking ForceEra (r:0 w:1) fn force_new_era() -> Weight { - (1_208_000 as Weight) + (1_276_000 as Weight) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Staking ForceEra (r:0 w:1) fn force_new_era_always() -> Weight { - (1_220_000 as Weight) + (1_321_000 as Weight) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Staking Invulnerables (r:0 w:1) fn set_invulnerables(v: u32, ) -> Weight { - (1_473_000 as Weight) + (1_517_000 as Weight) // Standard Error: 0 .saturating_add((9_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -646,18 +641,18 @@ impl WeightInfo for () { // Storage: Staking Payee (r:0 w:1) // Storage: Staking SpanSlash (r:0 w:2) fn force_unstake(s: u32, ) -> Weight { - (55_815_000 as Weight) + (58_451_000 as Weight) // Standard Error: 1_000 - .saturating_add((808_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((835_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(11 as Weight)) .saturating_add(RocksDbWeight::get().writes(12 as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) } // Storage: Staking UnappliedSlashes (r:1 w:1) fn cancel_deferred_slash(s: u32, ) -> Weight { - (903_077_000 as Weight) - // Standard Error: 53_000 - .saturating_add((4_434_000 as Weight).saturating_mul(s as Weight)) + (895_257_000 as Weight) + // Standard Error: 56_000 + .saturating_add((4_978_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -672,9 +667,9 @@ impl WeightInfo for () { // Storage: Staking Payee (r:2 w:0) // Storage: System Account (r:2 w:2) fn payout_stakers_dead_controller(n: u32, ) -> Weight { - (79_440_000 as Weight) - // Standard Error: 14_000 - .saturating_add((24_005_000 as Weight).saturating_mul(n as Weight)) + (71_290_000 as Weight) + // Standard Error: 12_000 + .saturating_add((23_818_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(10 as Weight)) .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(n as Weight))) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) @@ -692,9 +687,9 @@ impl WeightInfo for () { // Storage: System Account (r:2 w:2) // Storage: Balances Locks (r:2 w:2) fn payout_stakers_alive_staked(n: u32, ) -> Weight { - (99_118_000 as Weight) - // Standard Error: 20_000 - .saturating_add((33_274_000 as Weight).saturating_mul(n as Weight)) + (86_724_000 as Weight) + // Standard Error: 29_000 + .saturating_add((33_575_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(11 as Weight)) .saturating_add(RocksDbWeight::get().reads((5 as Weight).saturating_mul(n as Weight))) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) @@ -707,9 +702,9 @@ impl WeightInfo for () { // Storage: Staking Bonded (r:1 w:0) // Storage: BagsList ListBags (r:2 w:2) fn rebond(l: u32, ) -> Weight { - (63_335_000 as Weight) - // Standard Error: 2_000 - .saturating_add((53_000 as Weight).saturating_mul(l as Weight)) + (68_577_000 as Weight) + // Standard Error: 3_000 + .saturating_add((55_000 as Weight).saturating_mul(l as Weight)) .saturating_add(RocksDbWeight::get().reads(9 as Weight)) .saturating_add(RocksDbWeight::get().writes(8 as Weight)) } @@ -725,7 +720,7 @@ impl WeightInfo for () { fn set_history_depth(e: u32, ) -> Weight { (0 as Weight) // Standard Error: 58_000 - .saturating_add((20_386_000 as Weight).saturating_mul(e as Weight)) + .saturating_add((20_425_000 as Weight).saturating_mul(e as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) .saturating_add(RocksDbWeight::get().writes((7 as Weight).saturating_mul(e as Weight))) @@ -744,22 +739,22 @@ impl WeightInfo for () { // Storage: Staking Payee (r:0 w:1) // Storage: Staking SpanSlash (r:0 w:1) fn reap_stash(s: u32, ) -> Weight { - (61_486_000 as Weight) + (63_865_000 as Weight) // Standard Error: 0 - .saturating_add((809_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((839_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(12 as Weight)) .saturating_add(RocksDbWeight::get().writes(12 as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) } - // Storage: Staking CounterForNominators (r:1 w:0) - // Storage: Staking CounterForValidators (r:1 w:0) + // Storage: BagsList CounterForListNodes (r:1 w:0) + // Storage: Staking SlashingSpans (r:1 w:0) + // Storage: BagsList ListBags (r:200 w:0) + // Storage: BagsList ListNodes (r:101 w:0) + // Storage: Staking Nominators (r:101 w:0) // Storage: Staking Validators (r:2 w:0) // Storage: Staking Bonded (r:101 w:0) // Storage: Staking Ledger (r:101 w:0) - // Storage: Staking SlashingSpans (r:1 w:0) - // Storage: BagsList ListBags (r:200 w:0) - // Storage: BagsList ListNodes (r:100 w:0) - // Storage: Staking Nominators (r:100 w:0) + // Storage: Staking CounterForValidators (r:1 w:0) // Storage: Staking ValidatorCount (r:1 w:0) // Storage: Staking MinimumValidatorCount (r:1 w:0) // Storage: Staking CurrentEra (r:1 w:1) @@ -771,43 +766,42 @@ impl WeightInfo for () { // Storage: Staking ErasStartSessionIndex (r:0 w:1) fn new_era(v: u32, n: u32, ) -> Weight { (0 as Weight) - // Standard Error: 878_000 - .saturating_add((212_233_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 881_000 + .saturating_add((211_756_000 as Weight).saturating_mul(v as Weight)) // Standard Error: 44_000 - .saturating_add((30_364_000 as Weight).saturating_mul(n as Weight)) + .saturating_add((30_413_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(208 as Weight)) - .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(v as Weight))) + .saturating_add(RocksDbWeight::get().reads((5 as Weight).saturating_mul(v as Weight))) .saturating_add(RocksDbWeight::get().reads((4 as Weight).saturating_mul(n as Weight))) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) .saturating_add(RocksDbWeight::get().writes((3 as Weight).saturating_mul(v as Weight))) } - // Storage: Staking CounterForNominators (r:1 w:0) - // Storage: Staking CounterForValidators (r:1 w:0) - // Storage: Staking Validators (r:501 w:0) - // Storage: Staking Bonded (r:1500 w:0) - // Storage: Staking Ledger (r:1500 w:0) + // Storage: BagsList CounterForListNodes (r:1 w:0) // Storage: Staking SlashingSpans (r:21 w:0) // Storage: BagsList ListBags (r:200 w:0) - // Storage: BagsList ListNodes (r:1000 w:0) - // Storage: Staking Nominators (r:1000 w:0) + // Storage: BagsList ListNodes (r:1500 w:0) + // Storage: Staking Nominators (r:1500 w:0) + // Storage: Staking Validators (r:500 w:0) + // Storage: Staking Bonded (r:1500 w:0) + // Storage: Staking Ledger (r:1500 w:0) fn get_npos_voters(v: u32, n: u32, s: u32, ) -> Weight { (0 as Weight) - // Standard Error: 95_000 - .saturating_add((18_439_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 95_000 - .saturating_add((20_382_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 3_232_000 - .saturating_add((4_870_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(204 as Weight)) - .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(v as Weight))) + // Standard Error: 86_000 + .saturating_add((23_814_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 86_000 + .saturating_add((21_657_000 as Weight).saturating_mul(n as Weight)) + // Standard Error: 2_924_000 + .saturating_add((19_067_000 as Weight).saturating_mul(s as Weight)) + .saturating_add(RocksDbWeight::get().reads(202 as Weight)) + .saturating_add(RocksDbWeight::get().reads((5 as Weight).saturating_mul(v as Weight))) .saturating_add(RocksDbWeight::get().reads((4 as Weight).saturating_mul(n as Weight))) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(s as Weight))) } // Storage: Staking Validators (r:501 w:0) fn get_npos_targets(v: u32, ) -> Weight { (0 as Weight) - // Standard Error: 29_000 - .saturating_add((7_552_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 32_000 + .saturating_add((7_861_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(v as Weight))) } @@ -818,7 +812,7 @@ impl WeightInfo for () { // Storage: Staking MaxNominatorsCount (r:0 w:1) // Storage: Staking MinNominatorBond (r:0 w:1) fn set_staking_configs_all_set() -> Weight { - (3_597_000 as Weight) + (3_688_000 as Weight) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } // Storage: Staking MinCommission (r:0 w:1) @@ -828,7 +822,7 @@ impl WeightInfo for () { // Storage: Staking MaxNominatorsCount (r:0 w:1) // Storage: Staking MinNominatorBond (r:0 w:1) fn set_staking_configs_all_remove() -> Weight { - (3_198_000 as Weight) + (3_414_000 as Weight) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } // Storage: Staking Ledger (r:1 w:0) @@ -842,14 +836,14 @@ impl WeightInfo for () { // Storage: BagsList ListBags (r:1 w:1) // Storage: BagsList CounterForListNodes (r:1 w:1) fn chill_other() -> Weight { - (55_725_000 as Weight) + (56_962_000 as Weight) .saturating_add(RocksDbWeight::get().reads(11 as Weight)) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } // Storage: Staking MinCommission (r:1 w:0) // Storage: Staking Validators (r:1 w:1) fn force_apply_min_commission() -> Weight { - (8_946_000 as Weight) + (9_465_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } diff --git a/frame/support/src/weights.rs b/frame/support/src/weights.rs index 292c7d3e91d06..fc85a031b0852 100644 --- a/frame/support/src/weights.rs +++ b/frame/support/src/weights.rs @@ -168,10 +168,7 @@ pub mod constants { pub const WEIGHT_PER_NANOS: Weight = WEIGHT_PER_MICROS / 1000; // 1_000 // Expose the Block and Extrinsic base weights. - pub use super::{ - block_weights::constants::BlockExecutionWeight, - extrinsic_weights::constants::ExtrinsicBaseWeight, - }; + pub use super::{block_weights::BlockExecutionWeight, extrinsic_weights::ExtrinsicBaseWeight}; // Expose the DB weights. pub use super::{ diff --git a/frame/support/src/weights/block_weights.rs b/frame/support/src/weights/block_weights.rs index 4db90f0c0207a..d1d5f8320d104 100644 --- a/frame/support/src/weights/block_weights.rs +++ b/frame/support/src/weights/block_weights.rs @@ -15,32 +15,61 @@ // See the License for the specific language governing permissions and // limitations under the License. -pub mod constants { - use frame_support::{ - parameter_types, - weights::{constants, Weight}, - }; - - parameter_types! { - /// Importing a block with 0 Extrinsics. - pub const BlockExecutionWeight: Weight = 5_000_000 * constants::WEIGHT_PER_NANOS; - } +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-05-23 (Y/M/D) +//! +//! SHORT-NAME: `block`, LONG-NAME: `BlockExecution`, RUNTIME: `Development` +//! WARMUPS: `10`, REPEAT: `100` +//! WEIGHT-PATH: `./frame/support/src/weights/` +//! WEIGHT-METRIC: `Average`, WEIGHT-MUL: `1`, WEIGHT-ADD: `0` + +// Executed Command: +// ./target/production/substrate +// benchmark +// overhead +// --dev +// --execution=wasm +// --wasm-execution=compiled +// --weight-path=./frame/support/src/weights/ +// --warmup=10 +// --repeat=100 + +use frame_support::{ + parameter_types, + weights::{constants::WEIGHT_PER_NANOS, Weight}, +}; + +parameter_types! { + /// Time to execute an empty block. + /// Calculated by multiplying the *Average* with `1` and adding `0`. + /// + /// Stats nanoseconds: + /// Min, Max: 5_295_788, 5_473_440 + /// Average: 5_343_308 + /// Median: 5_323_240 + /// Std-Dev: 38368.68 + /// + /// Percentiles nanoseconds: + /// 99th: 5_470_141 + /// 95th: 5_418_269 + /// 75th: 5_355_579 + pub const BlockExecutionWeight: Weight = 5_343_308 * WEIGHT_PER_NANOS; +} + +#[cfg(test)] +mod test_weights { + use frame_support::weights::constants; + + /// Checks that the weight exists and is sane. + // NOTE: If this test fails but you are sure that the generated values are fine, + // you can delete it. + #[test] + fn sane() { + let w = super::BlockExecutionWeight::get(); - #[cfg(test)] - mod test_weights { - use frame_support::weights::constants; - - /// Checks that the weight exists and is sane. - // NOTE: If this test fails but you are sure that the generated values are fine, - // you can delete it. - #[test] - fn sane() { - let w = super::constants::BlockExecutionWeight::get(); - - // At least 100 µs. - assert!(w >= 100 * constants::WEIGHT_PER_MICROS, "Weight should be at least 100 µs."); - // At most 50 ms. - assert!(w <= 50 * constants::WEIGHT_PER_MILLIS, "Weight should be at most 50 ms."); - } + // At least 100 µs. + assert!(w >= 100 * constants::WEIGHT_PER_MICROS, "Weight should be at least 100 µs."); + // At most 50 ms. + assert!(w <= 50 * constants::WEIGHT_PER_MILLIS, "Weight should be at most 50 ms."); } } diff --git a/frame/support/src/weights/extrinsic_weights.rs b/frame/support/src/weights/extrinsic_weights.rs index 158ba99c6a4c1..1a5a4e7a460c0 100644 --- a/frame/support/src/weights/extrinsic_weights.rs +++ b/frame/support/src/weights/extrinsic_weights.rs @@ -15,32 +15,61 @@ // See the License for the specific language governing permissions and // limitations under the License. -pub mod constants { - use frame_support::{ - parameter_types, - weights::{constants, Weight}, - }; - - parameter_types! { - /// Executing a NO-OP `System::remarks` Extrinsic. - pub const ExtrinsicBaseWeight: Weight = 125_000 * constants::WEIGHT_PER_NANOS; - } +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-05-23 (Y/M/D) +//! +//! SHORT-NAME: `extrinsic`, LONG-NAME: `ExtrinsicBase`, RUNTIME: `Development` +//! WARMUPS: `10`, REPEAT: `100` +//! WEIGHT-PATH: `./frame/support/src/weights/` +//! WEIGHT-METRIC: `Average`, WEIGHT-MUL: `1`, WEIGHT-ADD: `0` + +// Executed Command: +// ./target/production/substrate +// benchmark +// overhead +// --dev +// --execution=wasm +// --wasm-execution=compiled +// --weight-path=./frame/support/src/weights/ +// --warmup=10 +// --repeat=100 + +use frame_support::{ + parameter_types, + weights::{constants::WEIGHT_PER_NANOS, Weight}, +}; + +parameter_types! { + /// Time to execute a NO-OP extrinsic, for example `System::remark`. + /// Calculated by multiplying the *Average* with `1` and adding `0`. + /// + /// Stats nanoseconds: + /// Min, Max: 85_423, 86_142 + /// Average: 85_795 + /// Median: 85_790 + /// Std-Dev: 162.37 + /// + /// Percentiles nanoseconds: + /// 99th: 86_115 + /// 95th: 86_069 + /// 75th: 85_937 + pub const ExtrinsicBaseWeight: Weight = 85_795 * WEIGHT_PER_NANOS; +} + +#[cfg(test)] +mod test_weights { + use frame_support::weights::constants; + + /// Checks that the weight exists and is sane. + // NOTE: If this test fails but you are sure that the generated values are fine, + // you can delete it. + #[test] + fn sane() { + let w = super::ExtrinsicBaseWeight::get(); - #[cfg(test)] - mod test_weights { - use frame_support::weights::constants; - - /// Checks that the weight exists and is sane. - // NOTE: If this test fails but you are sure that the generated values are fine, - // you can delete it. - #[test] - fn sane() { - let w = super::constants::ExtrinsicBaseWeight::get(); - - // At least 10 µs. - assert!(w >= 10 * constants::WEIGHT_PER_MICROS, "Weight should be at least 10 µs."); - // At most 1 ms. - assert!(w <= constants::WEIGHT_PER_MILLIS, "Weight should be at most 1 ms."); - } + // At least 10 µs. + assert!(w >= 10 * constants::WEIGHT_PER_MICROS, "Weight should be at least 10 µs."); + // At most 1 ms. + assert!(w <= constants::WEIGHT_PER_MILLIS, "Weight should be at most 1 ms."); } } diff --git a/frame/system/src/weights.rs b/frame/system/src/weights.rs index a016fe6e7d037..813183ecce188 100644 --- a/frame/system/src/weights.rs +++ b/frame/system/src/weights.rs @@ -18,12 +18,13 @@ //! Autogenerated weights for frame_system //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-01-31, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: // ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -31,11 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 +// --template=./.maintain/frame-weight-template.hbs // --output=./frame/system/src/weights.rs -// --template=.maintain/frame-weight-template.hbs -// --header=HEADER-APACHE2 -// --raw #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -68,7 +66,7 @@ impl WeightInfo for SubstrateWeight { // Storage: System Digest (r:1 w:1) // Storage: unknown [0x3a686561707061676573] (r:0 w:1) fn set_heap_pages() -> Weight { - (2_864_000 as Weight) + (3_235_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -76,21 +74,21 @@ impl WeightInfo for SubstrateWeight { fn set_storage(i: u32, ) -> Weight { (0 as Weight) // Standard Error: 0 - .saturating_add((389_000 as Weight).saturating_mul(i as Weight)) + .saturating_add((407_000 as Weight).saturating_mul(i as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(i as Weight))) } // Storage: Skipped Metadata (r:0 w:0) fn kill_storage(i: u32, ) -> Weight { (0 as Weight) // Standard Error: 0 - .saturating_add((285_000 as Weight).saturating_mul(i as Weight)) + .saturating_add((307_000 as Weight).saturating_mul(i as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(i as Weight))) } // Storage: Skipped Metadata (r:0 w:0) fn kill_prefix(p: u32, ) -> Weight { - (753_000 as Weight) + (0 as Weight) // Standard Error: 0 - .saturating_add((630_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((672_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(p as Weight))) } } @@ -108,7 +106,7 @@ impl WeightInfo for () { // Storage: System Digest (r:1 w:1) // Storage: unknown [0x3a686561707061676573] (r:0 w:1) fn set_heap_pages() -> Weight { - (2_864_000 as Weight) + (3_235_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -116,21 +114,21 @@ impl WeightInfo for () { fn set_storage(i: u32, ) -> Weight { (0 as Weight) // Standard Error: 0 - .saturating_add((389_000 as Weight).saturating_mul(i as Weight)) + .saturating_add((407_000 as Weight).saturating_mul(i as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(i as Weight))) } // Storage: Skipped Metadata (r:0 w:0) fn kill_storage(i: u32, ) -> Weight { (0 as Weight) // Standard Error: 0 - .saturating_add((285_000 as Weight).saturating_mul(i as Weight)) + .saturating_add((307_000 as Weight).saturating_mul(i as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(i as Weight))) } // Storage: Skipped Metadata (r:0 w:0) fn kill_prefix(p: u32, ) -> Weight { - (753_000 as Weight) + (0 as Weight) // Standard Error: 0 - .saturating_add((630_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((672_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(p as Weight))) } } diff --git a/frame/timestamp/src/weights.rs b/frame/timestamp/src/weights.rs index 33d7d6a4b9e37..7c130d69c51d6 100644 --- a/frame/timestamp/src/weights.rs +++ b/frame/timestamp/src/weights.rs @@ -18,12 +18,13 @@ //! Autogenerated weights for pallet_timestamp //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-01-31, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: // ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -31,11 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 +// --template=./.maintain/frame-weight-template.hbs // --output=./frame/timestamp/src/weights.rs -// --template=.maintain/frame-weight-template.hbs -// --header=HEADER-APACHE2 -// --raw #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -56,12 +54,12 @@ impl WeightInfo for SubstrateWeight { // Storage: Timestamp Now (r:1 w:1) // Storage: Babe CurrentSlot (r:1 w:0) fn set() -> Weight { - (5_247_000 as Weight) + (5_490_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn on_finalize() -> Weight { - (2_604_000 as Weight) + (2_618_000 as Weight) } } @@ -70,11 +68,11 @@ impl WeightInfo for () { // Storage: Timestamp Now (r:1 w:1) // Storage: Babe CurrentSlot (r:1 w:0) fn set() -> Weight { - (5_247_000 as Weight) + (5_490_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } fn on_finalize() -> Weight { - (2_604_000 as Weight) + (2_618_000 as Weight) } } diff --git a/frame/tips/src/weights.rs b/frame/tips/src/weights.rs index 2ba15a0c2fcc0..9e49dce410ef8 100644 --- a/frame/tips/src/weights.rs +++ b/frame/tips/src/weights.rs @@ -18,12 +18,13 @@ //! Autogenerated weights for pallet_tips //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-01-31, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: // ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -31,11 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 +// --template=./.maintain/frame-weight-template.hbs // --output=./frame/tips/src/weights.rs -// --template=.maintain/frame-weight-template.hbs -// --header=HEADER-APACHE2 -// --raw #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -60,7 +58,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Tips Reasons (r:1 w:1) // Storage: Tips Tips (r:1 w:1) fn report_awesome(r: u32, ) -> Weight { - (25_262_000 as Weight) + (26_613_000 as Weight) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) @@ -69,7 +67,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Tips Tips (r:1 w:1) // Storage: Tips Reasons (r:0 w:1) fn retract_tip() -> Weight { - (24_162_000 as Weight) + (25_001_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -77,20 +75,20 @@ impl WeightInfo for SubstrateWeight { // Storage: Tips Reasons (r:1 w:1) // Storage: Tips Tips (r:0 w:1) fn tip_new(r: u32, t: u32, ) -> Weight { - (16_435_000 as Weight) + (17_490_000 as Weight) // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(r as Weight)) - // Standard Error: 4_000 - .saturating_add((231_000 as Weight).saturating_mul(t as Weight)) + .saturating_add((1_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 5_000 + .saturating_add((183_000 as Weight).saturating_mul(t as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Elections Members (r:1 w:0) // Storage: Tips Tips (r:1 w:1) fn tip(t: u32, ) -> Weight { - (10_427_000 as Weight) + (9_634_000 as Weight) // Standard Error: 7_000 - .saturating_add((507_000 as Weight).saturating_mul(t as Weight)) + .saturating_add((343_000 as Weight).saturating_mul(t as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -99,18 +97,18 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) // Storage: Tips Reasons (r:0 w:1) fn close_tip(t: u32, ) -> Weight { - (40_901_000 as Weight) + (41_967_000 as Weight) // Standard Error: 10_000 - .saturating_add((281_000 as Weight).saturating_mul(t as Weight)) + .saturating_add((264_000 as Weight).saturating_mul(t as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: Tips Tips (r:1 w:1) // Storage: Tips Reasons (r:0 w:1) fn slash_tip(t: u32, ) -> Weight { - (14_636_000 as Weight) + (14_917_000 as Weight) // Standard Error: 4_000 - .saturating_add((29_000 as Weight).saturating_mul(t as Weight)) + .saturating_add((61_000 as Weight).saturating_mul(t as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -121,7 +119,7 @@ impl WeightInfo for () { // Storage: Tips Reasons (r:1 w:1) // Storage: Tips Tips (r:1 w:1) fn report_awesome(r: u32, ) -> Weight { - (25_262_000 as Weight) + (26_613_000 as Weight) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) @@ -130,7 +128,7 @@ impl WeightInfo for () { // Storage: Tips Tips (r:1 w:1) // Storage: Tips Reasons (r:0 w:1) fn retract_tip() -> Weight { - (24_162_000 as Weight) + (25_001_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -138,20 +136,20 @@ impl WeightInfo for () { // Storage: Tips Reasons (r:1 w:1) // Storage: Tips Tips (r:0 w:1) fn tip_new(r: u32, t: u32, ) -> Weight { - (16_435_000 as Weight) + (17_490_000 as Weight) // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(r as Weight)) - // Standard Error: 4_000 - .saturating_add((231_000 as Weight).saturating_mul(t as Weight)) + .saturating_add((1_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 5_000 + .saturating_add((183_000 as Weight).saturating_mul(t as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Elections Members (r:1 w:0) // Storage: Tips Tips (r:1 w:1) fn tip(t: u32, ) -> Weight { - (10_427_000 as Weight) + (9_634_000 as Weight) // Standard Error: 7_000 - .saturating_add((507_000 as Weight).saturating_mul(t as Weight)) + .saturating_add((343_000 as Weight).saturating_mul(t as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -160,18 +158,18 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) // Storage: Tips Reasons (r:0 w:1) fn close_tip(t: u32, ) -> Weight { - (40_901_000 as Weight) + (41_967_000 as Weight) // Standard Error: 10_000 - .saturating_add((281_000 as Weight).saturating_mul(t as Weight)) + .saturating_add((264_000 as Weight).saturating_mul(t as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: Tips Tips (r:1 w:1) // Storage: Tips Reasons (r:0 w:1) fn slash_tip(t: u32, ) -> Weight { - (14_636_000 as Weight) + (14_917_000 as Weight) // Standard Error: 4_000 - .saturating_add((29_000 as Weight).saturating_mul(t as Weight)) + .saturating_add((61_000 as Weight).saturating_mul(t as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } diff --git a/frame/transaction-storage/src/weights.rs b/frame/transaction-storage/src/weights.rs index a7033da7b80ce..8063783b4cc22 100644 --- a/frame/transaction-storage/src/weights.rs +++ b/frame/transaction-storage/src/weights.rs @@ -18,12 +18,13 @@ //! Autogenerated weights for pallet_transaction_storage //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-01-31, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: // ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -31,11 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 +// --template=./.maintain/frame-weight-template.hbs // --output=./frame/transaction-storage/src/weights.rs -// --template=.maintain/frame-weight-template.hbs -// --header=HEADER-APACHE2 -// --raw #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -74,7 +72,7 @@ impl WeightInfo for SubstrateWeight { // Storage: TransactionStorage BlockTransactions (r:1 w:1) // Storage: TransactionStorage MaxBlockTransactions (r:1 w:0) fn renew() -> Weight { - (41_286_000 as Weight) + (45_011_000 as Weight) .saturating_add(T::DbWeight::get().reads(6 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -84,7 +82,7 @@ impl WeightInfo for SubstrateWeight { // Storage: System ParentHash (r:1 w:0) // Storage: TransactionStorage Transactions (r:1 w:0) fn check_proof_max() -> Weight { - (136_957_000 as Weight) + (98_295_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -112,7 +110,7 @@ impl WeightInfo for () { // Storage: TransactionStorage BlockTransactions (r:1 w:1) // Storage: TransactionStorage MaxBlockTransactions (r:1 w:0) fn renew() -> Weight { - (41_286_000 as Weight) + (45_011_000 as Weight) .saturating_add(RocksDbWeight::get().reads(6 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -122,7 +120,7 @@ impl WeightInfo for () { // Storage: System ParentHash (r:1 w:0) // Storage: TransactionStorage Transactions (r:1 w:0) fn check_proof_max() -> Weight { - (136_957_000 as Weight) + (98_295_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } diff --git a/frame/treasury/src/weights.rs b/frame/treasury/src/weights.rs index c846a7f6885e9..4ae82d3b230d2 100644 --- a/frame/treasury/src/weights.rs +++ b/frame/treasury/src/weights.rs @@ -18,11 +18,11 @@ //! Autogenerated weights for pallet_treasury //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-04-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/production/substrate +// ./target/production/substrate // benchmark // pallet // --chain=dev @@ -32,9 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 -// --output=./frame/treasury/src/weights.rs // --template=./.maintain/frame-weight-template.hbs +// --output=./frame/treasury/src/weights.rs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -58,29 +57,29 @@ impl WeightInfo for SubstrateWeight { // Storage: Treasury ProposalCount (r:1 w:1) // Storage: Treasury Proposals (r:0 w:1) fn propose_spend() -> Weight { - (22_063_000 as Weight) + (22_397_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Treasury Proposals (r:1 w:1) // Storage: System Account (r:1 w:1) fn reject_proposal() -> Weight { - (25_965_000 as Weight) + (26_004_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Treasury Proposals (r:1 w:0) // Storage: Treasury Approvals (r:1 w:1) fn approve_proposal(p: u32, ) -> Weight { - (7_299_000 as Weight) + (7_415_000 as Weight) // Standard Error: 0 - .saturating_add((103_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((105_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Treasury Approvals (r:1 w:1) fn remove_approval() -> Weight { - (3_867_000 as Weight) + (3_827_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -89,9 +88,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Treasury Proposals (r:2 w:2) // Storage: System Account (r:4 w:4) fn on_initialize_proposals(p: u32, ) -> Weight { - (24_020_000 as Weight) - // Standard Error: 22_000 - .saturating_add((28_198_000 as Weight).saturating_mul(p as Weight)) + (25_129_000 as Weight) + // Standard Error: 16_000 + .saturating_add((28_612_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(p as Weight))) .saturating_add(T::DbWeight::get().writes(2 as Weight)) @@ -104,29 +103,29 @@ impl WeightInfo for () { // Storage: Treasury ProposalCount (r:1 w:1) // Storage: Treasury Proposals (r:0 w:1) fn propose_spend() -> Weight { - (22_063_000 as Weight) + (22_397_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Treasury Proposals (r:1 w:1) // Storage: System Account (r:1 w:1) fn reject_proposal() -> Weight { - (25_965_000 as Weight) + (26_004_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Treasury Proposals (r:1 w:0) // Storage: Treasury Approvals (r:1 w:1) fn approve_proposal(p: u32, ) -> Weight { - (7_299_000 as Weight) + (7_415_000 as Weight) // Standard Error: 0 - .saturating_add((103_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((105_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Treasury Approvals (r:1 w:1) fn remove_approval() -> Weight { - (3_867_000 as Weight) + (3_827_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -135,9 +134,9 @@ impl WeightInfo for () { // Storage: Treasury Proposals (r:2 w:2) // Storage: System Account (r:4 w:4) fn on_initialize_proposals(p: u32, ) -> Weight { - (24_020_000 as Weight) - // Standard Error: 22_000 - .saturating_add((28_198_000 as Weight).saturating_mul(p as Weight)) + (25_129_000 as Weight) + // Standard Error: 16_000 + .saturating_add((28_612_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(p as Weight))) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) diff --git a/frame/uniques/src/weights.rs b/frame/uniques/src/weights.rs index 98cc823f0ccbd..127e5ccbb12a3 100644 --- a/frame/uniques/src/weights.rs +++ b/frame/uniques/src/weights.rs @@ -18,11 +18,11 @@ //! Autogenerated weights for pallet_uniques //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-17, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/production/substrate +// ./target/production/substrate // benchmark // pallet // --chain=dev @@ -32,9 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 -// --output=./frame/uniques/src/weights.rs // --template=./.maintain/frame-weight-template.hbs +// --output=./frame/uniques/src/weights.rs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -77,14 +76,14 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn create() -> Weight { - (23_770_000 as Weight) + (23_990_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn force_create() -> Weight { - (13_759_000 as Weight) + (13_472_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -97,12 +96,12 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Account (r:0 w:20) fn destroy(n: u32, m: u32, a: u32, ) -> Weight { (0 as Weight) - // Standard Error: 16_000 - .saturating_add((9_387_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 16_000 - .saturating_add((967_000 as Weight).saturating_mul(m as Weight)) - // Standard Error: 16_000 - .saturating_add((799_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 13_000 + .saturating_add((9_507_000 as Weight).saturating_mul(n as Weight)) + // Standard Error: 13_000 + .saturating_add((912_000 as Weight).saturating_mul(m as Weight)) + // Standard Error: 13_000 + .saturating_add((801_000 as Weight).saturating_mul(a as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) .saturating_add(T::DbWeight::get().writes(3 as Weight)) @@ -115,7 +114,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques CollectionMaxSupply (r:1 w:0) // Storage: Uniques Account (r:0 w:1) fn mint() -> Weight { - (31_588_000 as Weight) + (32_519_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -123,7 +122,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Account (r:0 w:1) fn burn() -> Weight { - (31_125_000 as Weight) + (32_024_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -131,7 +130,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Account (r:0 w:2) fn transfer() -> Weight { - (22_955_000 as Weight) + (23_948_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -139,8 +138,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Asset (r:100 w:100) fn redeposit(i: u32, ) -> Weight { (0 as Weight) - // Standard Error: 12_000 - .saturating_add((11_761_000 as Weight).saturating_mul(i as Weight)) + // Standard Error: 11_000 + .saturating_add((11_911_000 as Weight).saturating_mul(i as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -149,26 +148,26 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn freeze() -> Weight { - (17_808_000 as Weight) + (18_448_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn thaw() -> Weight { - (18_342_000 as Weight) + (18_704_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn freeze_collection() -> Weight { - (13_423_000 as Weight) + (13_925_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn thaw_collection() -> Weight { - (13_453_000 as Weight) + (13_902_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -176,20 +175,20 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:2) fn transfer_ownership() -> Weight { - (20_745_000 as Weight) + (20_949_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn set_team() -> Weight { - (14_192_000 as Weight) + (14_531_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn force_item_status() -> Weight { - (16_488_000 as Weight) + (16_623_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -197,7 +196,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn set_attribute() -> Weight { - (37_207_000 as Weight) + (37_593_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -205,62 +204,62 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn clear_attribute() -> Weight { - (35_432_000 as Weight) + (35_304_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:1) fn set_metadata() -> Weight { - (28_655_000 as Weight) + (29_353_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:1) fn clear_metadata() -> Weight { - (28_898_000 as Weight) + (29_323_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassMetadataOf (r:1 w:1) fn set_collection_metadata() -> Weight { - (27_890_000 as Weight) + (28_806_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques ClassMetadataOf (r:1 w:1) fn clear_collection_metadata() -> Weight { - (26_538_000 as Weight) + (26_744_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) fn approve_transfer() -> Weight { - (18_961_000 as Weight) + (19_485_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) fn cancel_approval() -> Weight { - (19_116_000 as Weight) + (19_494_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques OwnershipAcceptance (r:1 w:1) fn set_accept_ownership() -> Weight { - (17_154_000 as Weight) + (17_456_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques CollectionMaxSupply (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn set_collection_max_supply() -> Weight { - (16_060_000 as Weight) + (16_780_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -271,14 +270,14 @@ impl WeightInfo for () { // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn create() -> Weight { - (23_770_000 as Weight) + (23_990_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn force_create() -> Weight { - (13_759_000 as Weight) + (13_472_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -291,12 +290,12 @@ impl WeightInfo for () { // Storage: Uniques Account (r:0 w:20) fn destroy(n: u32, m: u32, a: u32, ) -> Weight { (0 as Weight) - // Standard Error: 16_000 - .saturating_add((9_387_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 16_000 - .saturating_add((967_000 as Weight).saturating_mul(m as Weight)) - // Standard Error: 16_000 - .saturating_add((799_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 13_000 + .saturating_add((9_507_000 as Weight).saturating_mul(n as Weight)) + // Standard Error: 13_000 + .saturating_add((912_000 as Weight).saturating_mul(m as Weight)) + // Standard Error: 13_000 + .saturating_add((801_000 as Weight).saturating_mul(a as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) @@ -309,7 +308,7 @@ impl WeightInfo for () { // Storage: Uniques CollectionMaxSupply (r:1 w:0) // Storage: Uniques Account (r:0 w:1) fn mint() -> Weight { - (31_588_000 as Weight) + (32_519_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -317,7 +316,7 @@ impl WeightInfo for () { // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Account (r:0 w:1) fn burn() -> Weight { - (31_125_000 as Weight) + (32_024_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -325,7 +324,7 @@ impl WeightInfo for () { // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Account (r:0 w:2) fn transfer() -> Weight { - (22_955_000 as Weight) + (23_948_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -333,8 +332,8 @@ impl WeightInfo for () { // Storage: Uniques Asset (r:100 w:100) fn redeposit(i: u32, ) -> Weight { (0 as Weight) - // Standard Error: 12_000 - .saturating_add((11_761_000 as Weight).saturating_mul(i as Weight)) + // Standard Error: 11_000 + .saturating_add((11_911_000 as Weight).saturating_mul(i as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(i as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -343,26 +342,26 @@ impl WeightInfo for () { // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn freeze() -> Weight { - (17_808_000 as Weight) + (18_448_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn thaw() -> Weight { - (18_342_000 as Weight) + (18_704_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn freeze_collection() -> Weight { - (13_423_000 as Weight) + (13_925_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn thaw_collection() -> Weight { - (13_453_000 as Weight) + (13_902_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -370,20 +369,20 @@ impl WeightInfo for () { // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:2) fn transfer_ownership() -> Weight { - (20_745_000 as Weight) + (20_949_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn set_team() -> Weight { - (14_192_000 as Weight) + (14_531_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn force_item_status() -> Weight { - (16_488_000 as Weight) + (16_623_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -391,7 +390,7 @@ impl WeightInfo for () { // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn set_attribute() -> Weight { - (37_207_000 as Weight) + (37_593_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -399,62 +398,62 @@ impl WeightInfo for () { // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn clear_attribute() -> Weight { - (35_432_000 as Weight) + (35_304_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:1) fn set_metadata() -> Weight { - (28_655_000 as Weight) + (29_353_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:1) fn clear_metadata() -> Weight { - (28_898_000 as Weight) + (29_323_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassMetadataOf (r:1 w:1) fn set_collection_metadata() -> Weight { - (27_890_000 as Weight) + (28_806_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques ClassMetadataOf (r:1 w:1) fn clear_collection_metadata() -> Weight { - (26_538_000 as Weight) + (26_744_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) fn approve_transfer() -> Weight { - (18_961_000 as Weight) + (19_485_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) fn cancel_approval() -> Weight { - (19_116_000 as Weight) + (19_494_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques OwnershipAcceptance (r:1 w:1) fn set_accept_ownership() -> Weight { - (17_154_000 as Weight) + (17_456_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques CollectionMaxSupply (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn set_collection_max_supply() -> Weight { - (16_060_000 as Weight) + (16_780_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } diff --git a/frame/utility/src/weights.rs b/frame/utility/src/weights.rs index 34dad8d735a2e..d3cd7530c2584 100644 --- a/frame/utility/src/weights.rs +++ b/frame/utility/src/weights.rs @@ -18,12 +18,13 @@ //! Autogenerated weights for pallet_utility //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-01-31, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: // ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -31,11 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 +// --template=./.maintain/frame-weight-template.hbs // --output=./frame/utility/src/weights.rs -// --template=.maintain/frame-weight-template.hbs -// --header=HEADER-APACHE2 -// --raw #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -57,49 +55,49 @@ pub trait WeightInfo { pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { fn batch(c: u32, ) -> Weight { - (18_598_000 as Weight) + (14_744_000 as Weight) // Standard Error: 1_000 - .saturating_add((2_374_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((2_549_000 as Weight).saturating_mul(c as Weight)) } fn as_derivative() -> Weight { - (1_650_000 as Weight) + (1_412_000 as Weight) } fn batch_all(c: u32, ) -> Weight { - (13_988_000 as Weight) + (20_835_000 as Weight) // Standard Error: 1_000 - .saturating_add((2_481_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((2_704_000 as Weight).saturating_mul(c as Weight)) } fn dispatch_as() -> Weight { - (8_463_000 as Weight) + (8_889_000 as Weight) } fn force_batch(c: u32, ) -> Weight { - (13_988_000 as Weight) + (13_155_000 as Weight) // Standard Error: 1_000 - .saturating_add((2_481_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((2_555_000 as Weight).saturating_mul(c as Weight)) } } // For backwards compatibility and tests impl WeightInfo for () { fn batch(c: u32, ) -> Weight { - (18_598_000 as Weight) + (14_744_000 as Weight) // Standard Error: 1_000 - .saturating_add((2_374_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((2_549_000 as Weight).saturating_mul(c as Weight)) } fn as_derivative() -> Weight { - (1_650_000 as Weight) + (1_412_000 as Weight) } fn batch_all(c: u32, ) -> Weight { - (13_988_000 as Weight) + (20_835_000 as Weight) // Standard Error: 1_000 - .saturating_add((2_481_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((2_704_000 as Weight).saturating_mul(c as Weight)) } fn dispatch_as() -> Weight { - (8_463_000 as Weight) + (8_889_000 as Weight) } fn force_batch(c: u32, ) -> Weight { - (13_988_000 as Weight) + (13_155_000 as Weight) // Standard Error: 1_000 - .saturating_add((2_481_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((2_555_000 as Weight).saturating_mul(c as Weight)) } } diff --git a/frame/vesting/src/weights.rs b/frame/vesting/src/weights.rs index 140c1889d6e54..356cd003d8848 100644 --- a/frame/vesting/src/weights.rs +++ b/frame/vesting/src/weights.rs @@ -18,12 +18,13 @@ //! Autogenerated weights for pallet_vesting //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-01-31, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: // ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -31,11 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 +// --template=./.maintain/frame-weight-template.hbs // --output=./frame/vesting/src/weights.rs -// --template=.maintain/frame-weight-template.hbs -// --header=HEADER-APACHE2 -// --raw #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -62,22 +60,22 @@ impl WeightInfo for SubstrateWeight { // Storage: Vesting Vesting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vest_locked(l: u32, s: u32, ) -> Weight { - (27_037_000 as Weight) + (27_818_000 as Weight) // Standard Error: 1_000 - .saturating_add((88_000 as Weight).saturating_mul(l as Weight)) + .saturating_add((81_000 as Weight).saturating_mul(l as Weight)) // Standard Error: 2_000 - .saturating_add((69_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((74_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Vesting Vesting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vest_unlocked(l: u32, s: u32, ) -> Weight { - (26_627_000 as Weight) + (27_708_000 as Weight) // Standard Error: 1_000 - .saturating_add((82_000 as Weight).saturating_mul(l as Weight)) + .saturating_add((79_000 as Weight).saturating_mul(l as Weight)) // Standard Error: 2_000 - .saturating_add((53_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((43_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -85,11 +83,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) fn vest_other_locked(l: u32, s: u32, ) -> Weight { - (26_672_000 as Weight) + (27_854_000 as Weight) // Standard Error: 1_000 - .saturating_add((85_000 as Weight).saturating_mul(l as Weight)) + .saturating_add((77_000 as Weight).saturating_mul(l as Weight)) // Standard Error: 2_000 - .saturating_add((77_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((73_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -97,11 +95,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) fn vest_other_unlocked(l: u32, s: u32, ) -> Weight { - (26_682_000 as Weight) + (27_738_000 as Weight) // Standard Error: 1_000 - .saturating_add((74_000 as Weight).saturating_mul(l as Weight)) + .saturating_add((71_000 as Weight).saturating_mul(l as Weight)) // Standard Error: 2_000 - .saturating_add((51_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((42_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -109,11 +107,11 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vested_transfer(l: u32, s: u32, ) -> Weight { - (42_066_000 as Weight) + (43_640_000 as Weight) // Standard Error: 1_000 - .saturating_add((83_000 as Weight).saturating_mul(l as Weight)) + .saturating_add((71_000 as Weight).saturating_mul(l as Weight)) // Standard Error: 3_000 - .saturating_add((43_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((45_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -121,11 +119,11 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:2 w:2) // Storage: Balances Locks (r:1 w:1) fn force_vested_transfer(l: u32, s: u32, ) -> Weight { - (41_672_000 as Weight) + (42_850_000 as Weight) // Standard Error: 1_000 - .saturating_add((84_000 as Weight).saturating_mul(l as Weight)) + .saturating_add((81_000 as Weight).saturating_mul(l as Weight)) // Standard Error: 3_000 - .saturating_add((46_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((48_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -133,11 +131,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) fn not_unlocking_merge_schedules(l: u32, s: u32, ) -> Weight { - (27_627_000 as Weight) - // Standard Error: 0 - .saturating_add((86_000 as Weight).saturating_mul(l as Weight)) + (28_683_000 as Weight) + // Standard Error: 1_000 + .saturating_add((81_000 as Weight).saturating_mul(l as Weight)) // Standard Error: 2_000 - .saturating_add((68_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((63_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -145,11 +143,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) fn unlocking_merge_schedules(l: u32, s: u32, ) -> Weight { - (27_143_000 as Weight) - // Standard Error: 0 - .saturating_add((88_000 as Weight).saturating_mul(l as Weight)) + (28_510_000 as Weight) // Standard Error: 1_000 - .saturating_add((72_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((77_000 as Weight).saturating_mul(l as Weight)) + // Standard Error: 2_000 + .saturating_add((65_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -160,22 +158,22 @@ impl WeightInfo for () { // Storage: Vesting Vesting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vest_locked(l: u32, s: u32, ) -> Weight { - (27_037_000 as Weight) + (27_818_000 as Weight) // Standard Error: 1_000 - .saturating_add((88_000 as Weight).saturating_mul(l as Weight)) + .saturating_add((81_000 as Weight).saturating_mul(l as Weight)) // Standard Error: 2_000 - .saturating_add((69_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((74_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Vesting Vesting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vest_unlocked(l: u32, s: u32, ) -> Weight { - (26_627_000 as Weight) + (27_708_000 as Weight) // Standard Error: 1_000 - .saturating_add((82_000 as Weight).saturating_mul(l as Weight)) + .saturating_add((79_000 as Weight).saturating_mul(l as Weight)) // Standard Error: 2_000 - .saturating_add((53_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((43_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -183,11 +181,11 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) fn vest_other_locked(l: u32, s: u32, ) -> Weight { - (26_672_000 as Weight) + (27_854_000 as Weight) // Standard Error: 1_000 - .saturating_add((85_000 as Weight).saturating_mul(l as Weight)) + .saturating_add((77_000 as Weight).saturating_mul(l as Weight)) // Standard Error: 2_000 - .saturating_add((77_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((73_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -195,11 +193,11 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) fn vest_other_unlocked(l: u32, s: u32, ) -> Weight { - (26_682_000 as Weight) + (27_738_000 as Weight) // Standard Error: 1_000 - .saturating_add((74_000 as Weight).saturating_mul(l as Weight)) + .saturating_add((71_000 as Weight).saturating_mul(l as Weight)) // Standard Error: 2_000 - .saturating_add((51_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((42_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -207,11 +205,11 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vested_transfer(l: u32, s: u32, ) -> Weight { - (42_066_000 as Weight) + (43_640_000 as Weight) // Standard Error: 1_000 - .saturating_add((83_000 as Weight).saturating_mul(l as Weight)) + .saturating_add((71_000 as Weight).saturating_mul(l as Weight)) // Standard Error: 3_000 - .saturating_add((43_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((45_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -219,11 +217,11 @@ impl WeightInfo for () { // Storage: System Account (r:2 w:2) // Storage: Balances Locks (r:1 w:1) fn force_vested_transfer(l: u32, s: u32, ) -> Weight { - (41_672_000 as Weight) + (42_850_000 as Weight) // Standard Error: 1_000 - .saturating_add((84_000 as Weight).saturating_mul(l as Weight)) + .saturating_add((81_000 as Weight).saturating_mul(l as Weight)) // Standard Error: 3_000 - .saturating_add((46_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((48_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } @@ -231,11 +229,11 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) fn not_unlocking_merge_schedules(l: u32, s: u32, ) -> Weight { - (27_627_000 as Weight) - // Standard Error: 0 - .saturating_add((86_000 as Weight).saturating_mul(l as Weight)) + (28_683_000 as Weight) + // Standard Error: 1_000 + .saturating_add((81_000 as Weight).saturating_mul(l as Weight)) // Standard Error: 2_000 - .saturating_add((68_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((63_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -243,11 +241,11 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) fn unlocking_merge_schedules(l: u32, s: u32, ) -> Weight { - (27_143_000 as Weight) - // Standard Error: 0 - .saturating_add((88_000 as Weight).saturating_mul(l as Weight)) + (28_510_000 as Weight) // Standard Error: 1_000 - .saturating_add((72_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((77_000 as Weight).saturating_mul(l as Weight)) + // Standard Error: 2_000 + .saturating_add((65_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } diff --git a/frame/whitelist/src/weights.rs b/frame/whitelist/src/weights.rs index fdba734db64ba..b074cbf00d40d 100644 --- a/frame/whitelist/src/weights.rs +++ b/frame/whitelist/src/weights.rs @@ -18,12 +18,13 @@ //! Autogenerated weights for pallet_whitelist //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-02-25, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/production/substrate +// ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -31,9 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 -// --output=./frame/whitelist/src/weights.rs // --template=./.maintain/frame-weight-template.hbs +// --output=./frame/whitelist/src/weights.rs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -56,7 +56,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Whitelist WhitelistedCall (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) fn whitelist_call() -> Weight { - (16_254_000 as Weight) + (16_953_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -64,7 +64,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Preimage StatusFor (r:1 w:1) // Storage: Preimage PreimageFor (r:0 w:1) fn remove_whitelisted_call() -> Weight { - (18_348_000 as Weight) + (18_515_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -72,7 +72,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Preimage PreimageFor (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) fn dispatch_whitelisted_call() -> Weight { - (6_618_241_000 as Weight) + (5_870_296_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -80,9 +80,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Preimage StatusFor (r:1 w:1) // Storage: Preimage PreimageFor (r:0 w:1) fn dispatch_whitelisted_call_with_preimage(n: u32, ) -> Weight { - (20_619_000 as Weight) + (21_179_000 as Weight) // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(n as Weight)) + .saturating_add((1_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -93,7 +93,7 @@ impl WeightInfo for () { // Storage: Whitelist WhitelistedCall (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) fn whitelist_call() -> Weight { - (16_254_000 as Weight) + (16_953_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -101,7 +101,7 @@ impl WeightInfo for () { // Storage: Preimage StatusFor (r:1 w:1) // Storage: Preimage PreimageFor (r:0 w:1) fn remove_whitelisted_call() -> Weight { - (18_348_000 as Weight) + (18_515_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -109,7 +109,7 @@ impl WeightInfo for () { // Storage: Preimage PreimageFor (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) fn dispatch_whitelisted_call() -> Weight { - (6_618_241_000 as Weight) + (5_870_296_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -117,9 +117,9 @@ impl WeightInfo for () { // Storage: Preimage StatusFor (r:1 w:1) // Storage: Preimage PreimageFor (r:0 w:1) fn dispatch_whitelisted_call_with_preimage(n: u32, ) -> Weight { - (20_619_000 as Weight) + (21_179_000 as Weight) // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(n as Weight)) + .saturating_add((1_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } diff --git a/scripts/run_all_benchmarks.sh b/scripts/run_all_benchmarks.sh new file mode 100755 index 0000000000000..8d0d3d5411ed9 --- /dev/null +++ b/scripts/run_all_benchmarks.sh @@ -0,0 +1,163 @@ +#!/usr/bin/env bash + +# This file is part of Substrate. +# Copyright (C) 2022 Parity Technologies (UK) Ltd. +# SPDX-License-Identifier: Apache-2.0 +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This script has three parts which all use the Substrate runtime: +# - Pallet benchmarking to update the pallet weights +# - Overhead benchmarking for the Extrinsic and Block weights +# - Machine benchmarking +# +# Should be run on a reference machine to gain accurate benchmarks +# current reference machine: https://github.com/paritytech/substrate/pull/5848 + +while getopts 'bfp:v' flag; do + case "${flag}" in + b) + # Skip build. + skip_build='true' + ;; + f) + # Fail if any sub-command in a pipe fails, not just the last one. + set -o pipefail + # Fail on undeclared variables. + set -u + # Fail if any sub-command fails. + set -e + # Fail on traps. + set -E + ;; + p) + # Start at pallet + start_pallet="${OPTARG}" + ;; + v) + # Echo all executed commands. + set -x + ;; + *) + # Exit early. + echo "Bad options. Check Script." + exit 1 + ;; + esac +done + + +if [ "$skip_build" != true ] +then + echo "[+] Compiling Substrate benchmarks..." + cargo build --profile=production --locked --features=runtime-benchmarks +fi + +# The executable to use. +SUBSTRATE=./target/production/substrate + +# Manually exclude some pallets. +EXCLUDED_PALLETS=( + # Helper pallets + "pallet_election_provider_support_benchmarking" + # Pallets without automatic benchmarking + "pallet_babe" + "pallet_grandpa" + "pallet_mmr" + "pallet_offences" +) + +# Load all pallet names in an array. +ALL_PALLETS=($( + $SUBSTRATE benchmark pallet --list --chain=dev |\ + tail -n+2 |\ + cut -d',' -f1 |\ + sort |\ + uniq +)) + +# Filter out the excluded pallets by concatenating the arrays and discarding duplicates. +PALLETS=($({ printf '%s\n' "${ALL_PALLETS[@]}" "${EXCLUDED_PALLETS[@]}"; } | sort | uniq -u)) + +echo "[+] Benchmarking ${#PALLETS[@]} Substrate pallets by excluding ${#EXCLUDED_PALLETS[@]} from ${#ALL_PALLETS[@]}." + +# Define the error file. +ERR_FILE="benchmarking_errors.txt" +# Delete the error file before each run. +rm -f $ERR_FILE + +# Benchmark each pallet. +for PALLET in "${PALLETS[@]}"; do + # If `-p` is used, skip benchmarks until the start pallet. + if [ ! -z "$start_pallet" ] && [ "$start_pallet" != "$PALLET" ] + then + echo "[+] Skipping ${PALLET}..." + continue + else + unset start_pallet + fi + + FOLDER="$(echo "${PALLET#*_}" | tr '_' '-')"; + WEIGHT_FILE="./frame/${FOLDER}/src/weights.rs" + echo "[+] Benchmarking $PALLET with weight file $WEIGHT_FILE"; + + OUTPUT=$( + $SUBSTRATE benchmark pallet \ + --chain=dev \ + --steps=50 \ + --repeat=20 \ + --pallet="$PALLET" \ + --extrinsic="*" \ + --execution=wasm \ + --wasm-execution=compiled \ + --template=./.maintain/frame-weight-template.hbs \ + --output="$WEIGHT_FILE" 2>&1 + ) + if [ $? -ne 0 ]; then + echo "$OUTPUT" >> "$ERR_FILE" + echo "[-] Failed to benchmark $PALLET. Error written to $ERR_FILE; continuing..." + fi +done + +# Update the block and extrinsic overhead weights. +echo "[+] Benchmarking block and extrinsic overheads..." +OUTPUT=$( + $SUBSTRATE benchmark overhead \ + --chain=dev \ + --execution=wasm \ + --wasm-execution=compiled \ + --weight-path="./frame/support/src/weights/" \ + --warmup=10 \ + --repeat=100 2>&1 +) +if [ $? -ne 0 ]; then + echo "$OUTPUT" >> "$ERR_FILE" + echo "[-] Failed to benchmark the block and extrinsic overheads. Error written to $ERR_FILE; continuing..." +fi + +echo "[+] Benchmarking the machine..." +OUTPUT=$( + $SUBSTRATE benchmark machine --chain=dev 2>&1 +) +if [ $? -ne 0 ]; then + # Do not write the error to the error file since it is not a benchmarking error. + echo "[-] Failed the machine benchmark:\n$OUTPUT" +fi + +# Check if the error file exists. +if [ -f "$ERR_FILE" ]; then + echo "[-] Some benchmarks failed. See: $ERR_FILE" + exit 1 +else + echo "[+] All benchmarks passed." + exit 0 +fi diff --git a/utils/frame/benchmarking-cli/src/overhead/weights.hbs b/utils/frame/benchmarking-cli/src/overhead/weights.hbs index ad33f55a9f363..6d3ae471d1cf2 100644 --- a/utils/frame/benchmarking-cli/src/overhead/weights.hbs +++ b/utils/frame/benchmarking-cli/src/overhead/weights.hbs @@ -41,13 +41,13 @@ parameter_types! { {{/if}} /// Calculated by multiplying the *{{params.weight.weight_metric}}* with `{{params.weight.weight_mul}}` and adding `{{params.weight.weight_add}}`. /// - /// Stats [NS]: + /// Stats nanoseconds: /// Min, Max: {{underscore stats.min}}, {{underscore stats.max}} /// Average: {{underscore stats.avg}} /// Median: {{underscore stats.median}} /// Std-Dev: {{stats.stddev}} /// - /// Percentiles [NS]: + /// Percentiles nanoseconds: /// 99th: {{underscore stats.p99}} /// 95th: {{underscore stats.p95}} /// 75th: {{underscore stats.p75}} @@ -65,7 +65,7 @@ mod test_weights { fn sane() { let w = super::{{long_name}}Weight::get(); - {{#if (eq short_name "block")}} + {{#if (eq short_name "block")}} // At least 100 µs. assert!(w >= 100 * constants::WEIGHT_PER_MICROS, "Weight should be at least 100 µs."); // At most 50 ms. diff --git a/utils/frame/benchmarking-cli/src/storage/weights.hbs b/utils/frame/benchmarking-cli/src/storage/weights.hbs index 63f896e1104b8..51878141d1449 100644 --- a/utils/frame/benchmarking-cli/src/storage/weights.hbs +++ b/utils/frame/benchmarking-cli/src/storage/weights.hbs @@ -49,13 +49,13 @@ pub mod constants { /// Time to read one storage item. /// Calculated by multiplying the *{{params.weight_params.weight_metric}}* of all values with `{{params.weight_params.weight_mul}}` and adding `{{params.weight_params.weight_add}}`. /// - /// Stats [NS]: + /// Stats nanoseconds: /// Min, Max: {{underscore read.0.min}}, {{underscore read.0.max}} /// Average: {{underscore read.0.avg}} /// Median: {{underscore read.0.median}} /// Std-Dev: {{read.0.stddev}} /// - /// Percentiles [NS]: + /// Percentiles nanoseconds: /// 99th: {{underscore read.0.p99}} /// 95th: {{underscore read.0.p95}} /// 75th: {{underscore read.0.p75}} @@ -64,13 +64,13 @@ pub mod constants { /// Time to write one storage item. /// Calculated by multiplying the *{{params.weight_params.weight_metric}}* of all values with `{{params.weight_params.weight_mul}}` and adding `{{params.weight_params.weight_add}}`. /// - /// Stats [NS]: + /// Stats nanoseconds: /// Min, Max: {{underscore write.0.min}}, {{underscore write.0.max}} /// Average: {{underscore write.0.avg}} /// Median: {{underscore write.0.median}} /// Std-Dev: {{write.0.stddev}} /// - /// Percentiles [NS]: + /// Percentiles nanoseconds: /// 99th: {{underscore write.0.p99}} /// 95th: {{underscore write.0.p95}} /// 75th: {{underscore write.0.p75}} From 4658390351b5f91d7e3a5dcfa474864a6081c7ca Mon Sep 17 00:00:00 2001 From: Koute Date: Tue, 24 May 2022 14:37:04 +0900 Subject: [PATCH 263/484] Adjust maximum memory pages hard limit for the pooling instantiation strategy (#11482) * Run `sc-executor-wasmtime` unit tests for all instantiation strategies * Adjust maximum memory pages hard limit for the pooling instantiation strategy --- Cargo.lock | 1 + client/executor/wasmtime/Cargo.toml | 1 + client/executor/wasmtime/src/runtime.rs | 17 +++- client/executor/wasmtime/src/tests.rs | 112 +++++++++++++++++++----- 4 files changed, 106 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 03bc44a0a848a..5b2ceb36f6152 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8278,6 +8278,7 @@ dependencies = [ "log", "parity-scale-codec", "parity-wasm 0.42.2", + "paste 1.0.6", "sc-allocator", "sc-executor-common", "sc-runtime-test", diff --git a/client/executor/wasmtime/Cargo.toml b/client/executor/wasmtime/Cargo.toml index cbe6e1a0bd101..83859ee61be34 100644 --- a/client/executor/wasmtime/Cargo.toml +++ b/client/executor/wasmtime/Cargo.toml @@ -37,3 +37,4 @@ wat = "1.0" sc-runtime-test = { version = "2.0.0", path = "../runtime-test" } sp-io = { version = "6.0.0", path = "../../../primitives/io" } tempfile = "3.3.0" +paste = "1.0" diff --git a/client/executor/wasmtime/src/runtime.rs b/client/executor/wasmtime/src/runtime.rs index dbe0b129757b7..8f6826653f27d 100644 --- a/client/executor/wasmtime/src/runtime.rs +++ b/client/executor/wasmtime/src/runtime.rs @@ -341,6 +341,21 @@ fn common_config(semantics: &Semantics) -> std::result::Result std::result::Result { + paste::item! { + #[test] + fn [<$method_name _recreate_instance_cow>]() { + $method_name( + InstantiationStrategy::RecreateInstanceCopyOnWrite + ); + } + + #[test] + fn [<$method_name _recreate_instance_vanilla>]() { + $method_name( + InstantiationStrategy::RecreateInstance + ); + } + + #[test] + fn [<$method_name _pooling_cow>]() { + $method_name( + InstantiationStrategy::PoolingCopyOnWrite + ); + } + + #[test] + fn [<$method_name _pooling_vanilla>]() { + $method_name( + InstantiationStrategy::Pooling + ); + } + } + }; + + ($method_name:ident) => { + test_wasm_execution!(@no_legacy_instance_reuse $method_name); + + paste::item! { + #[test] + fn [<$method_name _legacy_instance_reuse>]() { + $method_name( + InstantiationStrategy::LegacyInstanceReuse + ); + } + } + }; +} + struct RuntimeBuilder { code: Option, instantiation_strategy: InstantiationStrategy, @@ -36,12 +84,10 @@ struct RuntimeBuilder { } impl RuntimeBuilder { - /// Returns a new builder that won't use the fast instance reuse mechanism, but instead will - /// create a new runtime instance each time. - fn new_on_demand() -> Self { + fn new(instantiation_strategy: InstantiationStrategy) -> Self { Self { code: None, - instantiation_strategy: InstantiationStrategy::RecreateInstance, + instantiation_strategy, canonicalize_nans: false, deterministic_stack: false, extra_heap_pages: 1024, @@ -128,9 +174,9 @@ impl RuntimeBuilder { } } -#[test] -fn test_nan_canonicalization() { - let mut builder = RuntimeBuilder::new_on_demand().canonicalize_nans(true); +test_wasm_execution!(test_nan_canonicalization); +fn test_nan_canonicalization(instantiation_strategy: InstantiationStrategy) { + let mut builder = RuntimeBuilder::new(instantiation_strategy).canonicalize_nans(true); let runtime = builder.build(); let mut instance = runtime.new_instance().expect("failed to instantiate a runtime"); @@ -166,11 +212,11 @@ fn test_nan_canonicalization() { assert_eq!(res, CANONICAL_NAN_BITS); } -#[test] -fn test_stack_depth_reaching() { +test_wasm_execution!(test_stack_depth_reaching); +fn test_stack_depth_reaching(instantiation_strategy: InstantiationStrategy) { const TEST_GUARD_PAGE_SKIP: &str = include_str!("test-guard-page-skip.wat"); - let mut builder = RuntimeBuilder::new_on_demand() + let mut builder = RuntimeBuilder::new(instantiation_strategy) .use_wat(TEST_GUARD_PAGE_SKIP.to_string()) .deterministic_stack(true); @@ -186,33 +232,46 @@ fn test_stack_depth_reaching() { } } -#[test] -fn test_max_memory_pages_imported_memory_without_precompilation() { - test_max_memory_pages(true, false); +test_wasm_execution!(test_max_memory_pages_imported_memory_without_precompilation); +fn test_max_memory_pages_imported_memory_without_precompilation( + instantiation_strategy: InstantiationStrategy, +) { + test_max_memory_pages(instantiation_strategy, true, false); } -#[test] -fn test_max_memory_pages_exported_memory_without_precompilation() { - test_max_memory_pages(false, false); +test_wasm_execution!(test_max_memory_pages_exported_memory_without_precompilation); +fn test_max_memory_pages_exported_memory_without_precompilation( + instantiation_strategy: InstantiationStrategy, +) { + test_max_memory_pages(instantiation_strategy, false, false); } -#[test] -fn test_max_memory_pages_imported_memory_with_precompilation() { - test_max_memory_pages(true, true); +test_wasm_execution!(@no_legacy_instance_reuse test_max_memory_pages_imported_memory_with_precompilation); +fn test_max_memory_pages_imported_memory_with_precompilation( + instantiation_strategy: InstantiationStrategy, +) { + test_max_memory_pages(instantiation_strategy, true, true); } -#[test] -fn test_max_memory_pages_exported_memory_with_precompilation() { - test_max_memory_pages(false, true); +test_wasm_execution!(@no_legacy_instance_reuse test_max_memory_pages_exported_memory_with_precompilation); +fn test_max_memory_pages_exported_memory_with_precompilation( + instantiation_strategy: InstantiationStrategy, +) { + test_max_memory_pages(instantiation_strategy, false, true); } -fn test_max_memory_pages(import_memory: bool, precompile_runtime: bool) { +fn test_max_memory_pages( + instantiation_strategy: InstantiationStrategy, + import_memory: bool, + precompile_runtime: bool, +) { fn try_instantiate( max_memory_size: Option, wat: String, + instantiation_strategy: InstantiationStrategy, precompile_runtime: bool, ) -> Result<(), Box> { - let mut builder = RuntimeBuilder::new_on_demand() + let mut builder = RuntimeBuilder::new(instantiation_strategy) .use_wat(wat) .max_memory_size(max_memory_size) .precompile_runtime(precompile_runtime); @@ -265,6 +324,7 @@ fn test_max_memory_pages(import_memory: bool, precompile_runtime: bool) { */ memory(64511, None, import_memory) ), + instantiation_strategy, precompile_runtime, ) .unwrap(); @@ -288,6 +348,7 @@ fn test_max_memory_pages(import_memory: bool, precompile_runtime: bool) { // 1 initial, max is not specified. memory(1, None, import_memory) ), + instantiation_strategy, precompile_runtime, ) .unwrap(); @@ -309,6 +370,7 @@ fn test_max_memory_pages(import_memory: bool, precompile_runtime: bool) { // Max is 2048. memory(1, Some(2048), import_memory) ), + instantiation_strategy, precompile_runtime, ) .unwrap(); @@ -342,6 +404,7 @@ fn test_max_memory_pages(import_memory: bool, precompile_runtime: bool) { // Zero starting pages. memory(0, None, import_memory) ), + instantiation_strategy, precompile_runtime, ) .unwrap(); @@ -375,6 +438,7 @@ fn test_max_memory_pages(import_memory: bool, precompile_runtime: bool) { // Initial=1, meaning after heap pages mount the total will be already 1025. memory(1, None, import_memory) ), + instantiation_strategy, precompile_runtime, ) .unwrap(); From 0e3bf256e155b05089aa5615b330527952b35fc1 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Tue, 24 May 2022 07:23:05 +0100 Subject: [PATCH 264/484] Optimize offchain worker memory usage a bit. (#11454) * add missing events to elections fallback * Merged * add some logs and stuff * undo a bunch of things * undo lock file * remove unused err * fix build --- client/allocator/src/freeing_bump.rs | 1 - .../src/unsigned.rs | 59 +++++++++++-------- frame/support/src/storage/types/nmap.rs | 4 +- primitives/npos-elections/src/assignments.rs | 23 ++++---- .../cli/src/commands/execute_block.rs | 5 ++ .../cli/src/commands/offchain_worker.rs | 5 ++ utils/frame/try-runtime/cli/src/lib.rs | 2 +- 7 files changed, 58 insertions(+), 41 deletions(-) diff --git a/client/allocator/src/freeing_bump.rs b/client/allocator/src/freeing_bump.rs index f14c31c79c483..79d6fca6f91b6 100644 --- a/client/allocator/src/freeing_bump.rs +++ b/client/allocator/src/freeing_bump.rs @@ -216,7 +216,6 @@ impl Link { /// | 0 | next element link | /// +--------------+-------------------+ /// ``` -/// /// ## Occupied header /// ```ignore /// 64 32 0 diff --git a/frame/election-provider-multi-phase/src/unsigned.rs b/frame/election-provider-multi-phase/src/unsigned.rs index 9a1b52d354569..de25355f0ca5b 100644 --- a/frame/election-provider-multi-phase/src/unsigned.rs +++ b/frame/election-provider-multi-phase/src/unsigned.rs @@ -169,27 +169,6 @@ impl Pallet { Ok((RawSolution { solution, score, round }, size)) } - /// Convert a raw solution from [`sp_npos_elections::ElectionResult`] to [`RawSolution`], which - /// is ready to be submitted to the chain. - /// - /// Will always reduce the solution as well. - pub fn prepare_election_result( - election_result: ElectionResult, - ) -> Result<(RawSolution>, SolutionOrSnapshotSize), MinerError> { - let RoundSnapshot { voters, targets } = - Self::snapshot().ok_or(MinerError::SnapshotUnAvailable)?; - let desired_targets = Self::desired_targets().ok_or(MinerError::SnapshotUnAvailable)?; - let (solution, score, size) = - Miner::::prepare_election_result_with_snapshot( - election_result, - voters, - targets, - desired_targets, - )?; - let round = Self::round(); - Ok((RawSolution { solution, score, round }, size)) - } - /// Attempt to restore a solution from cache. Otherwise, compute it fresh. Either way, submit /// if our call's score is greater than that of the cached solution. pub fn restore_or_compute_then_maybe_submit() -> Result<(), MinerError> { @@ -441,7 +420,10 @@ impl Miner { }) } - /// Same as [`Pallet::prepare_election_result`], but the input snapshot mut be given as inputs. + /// Convert a raw solution from [`sp_npos_elections::ElectionResult`] to [`RawSolution`], which + /// is ready to be submitted to the chain. + /// + /// Will always reduce the solution as well. pub fn prepare_election_result_with_snapshot( election_result: ElectionResult, voters: Vec<(T::AccountId, VoteWeight, BoundedVec)>, @@ -1118,7 +1100,19 @@ mod tests { distribution: vec![(10, PerU16::one())], }], }; - let (solution, witness) = MultiPhase::prepare_election_result(result).unwrap(); + + let RoundSnapshot { voters, targets } = MultiPhase::snapshot().unwrap(); + let desired_targets = MultiPhase::desired_targets().unwrap(); + + let (raw, score, witness) = + Miner::::prepare_election_result_with_snapshot( + result, + voters.clone(), + targets.clone(), + desired_targets, + ) + .unwrap(); + let solution = RawSolution { solution: raw, score, round: MultiPhase::round() }; assert_ok!(MultiPhase::unsigned_pre_dispatch_checks(&solution)); assert_ok!(MultiPhase::submit_unsigned( Origin::none(), @@ -1139,7 +1133,14 @@ mod tests { }, ], }; - let (solution, _) = MultiPhase::prepare_election_result(result).unwrap(); + let (raw, score, _) = Miner::::prepare_election_result_with_snapshot( + result, + voters.clone(), + targets.clone(), + desired_targets, + ) + .unwrap(); + let solution = RawSolution { solution: raw, score, round: MultiPhase::round() }; // 12 is not 50% more than 10 assert_eq!(solution.score.minimal_stake, 12); assert_noop!( @@ -1161,7 +1162,15 @@ mod tests { }, ], }; - let (solution, witness) = MultiPhase::prepare_election_result(result).unwrap(); + let (raw, score, witness) = + Miner::::prepare_election_result_with_snapshot( + result, + voters.clone(), + targets.clone(), + desired_targets, + ) + .unwrap(); + let solution = RawSolution { solution: raw, score, round: MultiPhase::round() }; assert_eq!(solution.score.minimal_stake, 17); // and it is fine diff --git a/frame/support/src/storage/types/nmap.rs b/frame/support/src/storage/types/nmap.rs index 5faeb5d8cac28..a15c7482105e9 100755 --- a/frame/support/src/storage/types/nmap.rs +++ b/frame/support/src/storage/types/nmap.rs @@ -544,7 +544,7 @@ mod test { use crate::{ hash::{StorageHasher as _, *}, metadata::{StorageEntryModifier, StorageHasher}, - storage::types::{Key, Key as NMapKey, ValueQuery}, + storage::types::{Key, ValueQuery}, }; use sp_io::{hashing::twox_128, TestExternalities}; @@ -590,7 +590,7 @@ mod test { { #[crate::storage_alias] - type Foo = StorageNMap), u32>; + type Foo = StorageNMap), u32>; assert_eq!(Foo::contains_key((3,)), true); assert_eq!(Foo::get((3,)), Some(10)); diff --git a/primitives/npos-elections/src/assignments.rs b/primitives/npos-elections/src/assignments.rs index 9422ccdf65884..fc88ef40010b7 100644 --- a/primitives/npos-elections/src/assignments.rs +++ b/primitives/npos-elections/src/assignments.rs @@ -120,18 +120,17 @@ impl StakedAssignment { AccountId: IdentifierT, { let stake = self.total(); - let distribution = self - .distribution - .into_iter() - .filter_map(|(target, w)| { - let per_thing = P::from_rational(w, stake); - if per_thing == Bounded::min_value() { - None - } else { - Some((target, per_thing)) - } - }) - .collect::>(); + // most likely, the size of the staked assignment and normal assignments will be the same, + // so we pre-allocate it to prevent a sudden 2x allocation. `filter_map` starts with a size + // of 0 by default. + // https://www.reddit.com/r/rust/comments/3spfh1/does_collect_allocate_more_than_once_while/ + let mut distribution = Vec::<(AccountId, P)>::with_capacity(self.distribution.len()); + self.distribution.into_iter().for_each(|(target, w)| { + let per_thing = P::from_rational(w, stake); + if per_thing != Bounded::min_value() { + distribution.push((target, per_thing)); + } + }); Assignment { who: self.who, distribution } } diff --git a/utils/frame/try-runtime/cli/src/commands/execute_block.rs b/utils/frame/try-runtime/cli/src/commands/execute_block.rs index 12c36955c26cd..204acd879312f 100644 --- a/utils/frame/try-runtime/cli/src/commands/execute_block.rs +++ b/utils/frame/try-runtime/cli/src/commands/execute_block.rs @@ -142,6 +142,11 @@ where .overwrite_online_at(parent_hash.to_owned()); let builder = if command.overwrite_wasm_code { + log::info!( + target: LOG_TARGET, + "replacing the in-storage :code: with the local code from {}'s chain_spec (your local repo)", + config.chain_spec.name(), + ); let (code_key, code) = extract_code(&config.chain_spec)?; builder.inject_hashed_key_value(&[(code_key, code)]) } else { diff --git a/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs b/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs index 9aad901829772..50780f4513b2f 100644 --- a/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs +++ b/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs @@ -131,6 +131,11 @@ where let builder = command.state.builder::()?; let builder = if command.overwrite_wasm_code { + log::info!( + target: LOG_TARGET, + "replacing the in-storage :code: with the local code from {}'s chain_spec (your local repo)", + config.chain_spec.name(), + ); let (code_key, code) = extract_code(&config.chain_spec)?; builder.inject_hashed_key_value(&[(code_key, code)]) } else { diff --git a/utils/frame/try-runtime/cli/src/lib.rs b/utils/frame/try-runtime/cli/src/lib.rs index 71d258a68982e..c09a33cf3f16a 100644 --- a/utils/frame/try-runtime/cli/src/lib.rs +++ b/utils/frame/try-runtime/cli/src/lib.rs @@ -722,7 +722,7 @@ pub(crate) fn state_machine_call(Into::into)?; Ok((changes, encoded_results)) From fa2f15ae8b551e01f4d42e8522d404c61a48dfd1 Mon Sep 17 00:00:00 2001 From: Sergejs Kostjucenko <85877331+sergejparity@users.noreply.github.com> Date: Tue, 24 May 2022 13:10:13 +0300 Subject: [PATCH 265/484] add rule to the ci job (#11511) --- scripts/ci/gitlab/pipeline/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/ci/gitlab/pipeline/build.yml b/scripts/ci/gitlab/pipeline/build.yml index f7ce974924d60..e896eac7238b1 100644 --- a/scripts/ci/gitlab/pipeline/build.yml +++ b/scripts/ci/gitlab/pipeline/build.yml @@ -31,6 +31,8 @@ check-dependent-polkadot: COMPANION_OVERRIDES: | substrate: polkadot-v* polkadot: release-v* + rules: + - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ #PRs # TODO: Re-enable after https://github.com/paritytech/pipeline-scripts/issues/44 #check-dependent-cumulus: From 1f3a7a7ed9eabe9168d28056685c549370185451 Mon Sep 17 00:00:00 2001 From: Achim Schneider Date: Tue, 24 May 2022 16:17:23 +0200 Subject: [PATCH 266/484] Contracts pallet: removal on idle (#11202) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * on_initialize -> on_idle * use remaining_weight info * no weight_limit for on_idle * call on_idle in tests * attempt to fix tests * run on_initiaize when queue full * add on_idle to weight info * add on_idle weight info to on_idle hook * add basic test for on_initialize with full queue * disbale check for all keys gone in full queue, full block test * queue_deth as usize, add comment * comment was removed by accident * Update frame/contracts/src/lib.rs Co-authored-by: Alexander Theißen * cargo +nightly fmt * update lazy_removal_does_no_run_on_full_queue_and_full_block * remove changes in weights.rs * weights on_idle -> on_process_deletion_queue_batch * use block number for on_idle * use BlockNumber for on_initialize * cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_contracts --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/contracts/src/weights.rs --template=./.maintain/frame-weight-template.hbs * Update frame/contracts/src/lib.rs Co-authored-by: Alexander Theißen * remove outcommented code * add check that queue still full for test * cargo fmt * cargo +nightly fmt * Update frame/contracts/src/benchmarking/mod.rs Co-authored-by: Alexander Gryaznov * fix weights.rs * add lazy_removal_does_no_run_on_low_remaining_weight test * Apply suggestions from code review Co-authored-by: Alexander Gryaznov Co-authored-by: Alexander Theißen Co-authored-by: Parity Bot Co-authored-by: Alexander Gryaznov --- frame/contracts/src/benchmarking/mod.rs | 4 +- frame/contracts/src/lib.rs | 29 ++++-- frame/contracts/src/storage.rs | 2 +- frame/contracts/src/tests.rs | 113 +++++++++++++-------- frame/contracts/src/weights.rs | 126 +++++++++++++++++++----- 5 files changed, 200 insertions(+), 74 deletions(-) diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs index 4241456389ec7..8f17cacff328d 100644 --- a/frame/contracts/src/benchmarking/mod.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -202,8 +202,8 @@ benchmarks! { as codec::HasCompact>::Type: Clone + Eq + PartialEq + sp_std::fmt::Debug + scale_info::TypeInfo + codec::Encode, } - // The base weight without any actual work performed apart from the setup costs. - on_initialize {}: { + // The base weight consumed on processing contracts deletion queue. + on_process_deletion_queue_batch {}: { Storage::::process_deletion_queue_batch(Weight::MAX) } diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index a8d1e18efd2cc..edb7fcf131de8 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -380,15 +380,28 @@ pub mod pallet { T::AccountId: UncheckedFrom, T::AccountId: AsRef<[u8]>, { + fn on_idle(_block: T::BlockNumber, remaining_weight: Weight) -> Weight { + Storage::::process_deletion_queue_batch(remaining_weight) + .saturating_add(T::WeightInfo::on_process_deletion_queue_batch()) + } + fn on_initialize(_block: T::BlockNumber) -> Weight { - // We do not want to go above the block limit and rather avoid lazy deletion - // in that case. This should only happen on runtime upgrades. - let weight_limit = T::BlockWeights::get() - .max_block - .saturating_sub(System::::block_weight().total()) - .min(T::DeletionWeightLimit::get()); - Storage::::process_deletion_queue_batch(weight_limit) - .saturating_add(T::WeightInfo::on_initialize()) + // We want to process the deletion_queue in the on_idle hook. Only in the case + // that the queue length has reached its maximal depth, we process it here. + let max_len = T::DeletionQueueDepth::get() as usize; + let queue_len = >::decode_len().unwrap_or(0); + if queue_len >= max_len { + // We do not want to go above the block limit and rather avoid lazy deletion + // in that case. This should only happen on runtime upgrades. + let weight_limit = T::BlockWeights::get() + .max_block + .saturating_sub(System::::block_weight().total()) + .min(T::DeletionWeightLimit::get()); + Storage::::process_deletion_queue_batch(weight_limit) + .saturating_add(T::WeightInfo::on_process_deletion_queue_batch()) + } else { + T::WeightInfo::on_process_deletion_queue_batch() + } } } diff --git a/frame/contracts/src/storage.rs b/frame/contracts/src/storage.rs index a9a78f12581d8..af6dbe3c62bfc 100644 --- a/frame/contracts/src/storage.rs +++ b/frame/contracts/src/storage.rs @@ -224,7 +224,7 @@ where /// of those keys can be deleted from the deletion queue given the supplied queue length /// and weight limit. pub fn deletion_budget(queue_len: usize, weight_limit: Weight) -> (u64, u32) { - let base_weight = T::WeightInfo::on_initialize(); + let base_weight = T::WeightInfo::on_process_deletion_queue_batch(); let weight_per_queue_item = T::WeightInfo::on_initialize_per_queue_item(1) - T::WeightInfo::on_initialize_per_queue_item(0); let weight_per_key = T::WeightInfo::on_initialize_per_trie_key(1) - diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index 5a6a29786c629..d4a5434cd5a46 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -25,7 +25,7 @@ use crate::{ wasm::{PrefabWasmModule, ReturnCode as RuntimeReturnCode}, weights::WeightInfo, BalanceOf, Code, CodeStorage, Config, ContractInfoOf, DefaultAddressGenerator, - DefaultContractAccessWeight, Error, Pallet, Schedule, + DefaultContractAccessWeight, DeletionQueue, Error, Pallet, Schedule, }; use assert_matches::assert_matches; use codec::Encode; @@ -35,7 +35,8 @@ use frame_support::{ parameter_types, storage::child, traits::{ - BalanceStatus, ConstU32, ConstU64, Contains, Currency, OnInitialize, ReservableCurrency, + BalanceStatus, ConstU32, ConstU64, Contains, Currency, Get, OnIdle, OnInitialize, + ReservableCurrency, }, weights::{constants::WEIGHT_PER_SECOND, DispatchClass, PostDispatchInfo, Weight}, }; @@ -1610,13 +1611,32 @@ fn lazy_removal_works() { assert_matches!(child::get(trie, &[99]), Some(42)); // Run the lazy removal - Contracts::on_initialize(Weight::max_value()); + Contracts::on_idle(System::block_number(), Weight::max_value()); // Value should be gone now assert_matches!(child::get::(trie, &[99]), None); }); } +#[test] +fn lazy_removal_on_full_queue_works_on_initialize() { + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + // Fill the deletion queue with dummy values, so that on_initialize attempts + // to clear the queue + Storage::::fill_queue_with_dummies(); + + let queue_len_initial = >::decode_len().unwrap_or(0); + + // Run the lazy removal + Contracts::on_initialize(System::block_number()); + + let queue_len_after_on_initialize = >::decode_len().unwrap_or(0); + + // Queue length should be decreased after call of on_initialize() + assert!(queue_len_initial - queue_len_after_on_initialize > 0); + }); +} + #[test] fn lazy_batch_removal_works() { let (code, hash) = compile_module::("self_destruct").unwrap(); @@ -1661,7 +1681,7 @@ fn lazy_batch_removal_works() { } // Run single lazy removal - Contracts::on_initialize(Weight::max_value()); + Contracts::on_idle(System::block_number(), Weight::max_value()); // The single lazy removal should have removed all queued tries for trie in tries.iter() { @@ -1761,7 +1781,34 @@ fn lazy_removal_partial_remove_works() { } #[test] -fn lazy_removal_does_no_run_on_full_block() { +fn lazy_removal_does_no_run_on_full_queue_and_full_block() { + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + // Fill up the block which should prevent the lazy storage removal from running. + System::register_extra_weight_unchecked( + ::BlockWeights::get().max_block, + DispatchClass::Mandatory, + ); + + // Fill the deletion queue with dummy values, so that on_initialize attempts + // to clear the queue + Storage::::fill_queue_with_dummies(); + + // Check that on_initialize() tries to perform lazy removal but removes nothing + // as no more weight is left for that. + let weight_used = Contracts::on_initialize(System::block_number()); + let base = <::WeightInfo as WeightInfo>::on_process_deletion_queue_batch(); + assert_eq!(weight_used, base); + + // Check that the deletion queue is still full after execution of the + // on_initialize() hook. + let max_len: u32 = ::DeletionQueueDepth::get(); + let queue_len: u32 = >::decode_len().unwrap_or(0).try_into().unwrap(); + assert_eq!(max_len, queue_len); + }); +} + +#[test] +fn lazy_removal_does_no_run_on_low_remaining_weight() { let (code, hash) = compile_module::("self_destruct").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); @@ -1779,19 +1826,10 @@ fn lazy_removal_does_no_run_on_full_block() { let addr = Contracts::contract_address(&ALICE, &hash, &[]); let info = >::get(&addr).unwrap(); - let max_keys = 30; - - // Create some storage items for the contract. - let vals: Vec<_> = (0..max_keys) - .map(|i| (blake2_256(&i.encode()), (i as u32), (i as u32).encode())) - .collect(); + let trie = &info.child_trie_info(); // Put value into the contracts child trie - for val in &vals { - Storage::::write(&info.trie_id, &val.0, Some(val.2.clone()), None, false) - .unwrap(); - } - >::insert(&addr, info.clone()); + child::put(trie, &[99], &42); // Terminate the contract assert_ok!(Contracts::call( @@ -1806,37 +1844,30 @@ fn lazy_removal_does_no_run_on_full_block() { // Contract info should be gone assert!(!>::contains_key(&addr)); - let trie = info.child_trie_info(); - // But value should be still there as the lazy removal did not run, yet. - for val in &vals { - assert_eq!(child::get::(&trie, &blake2_256(&val.0)), Some(val.1)); - } + assert_matches!(child::get(trie, &[99]), Some(42)); - // Fill up the block which should prevent the lazy storage removal from running. - System::register_extra_weight_unchecked( - ::BlockWeights::get().max_block, - DispatchClass::Mandatory, - ); + // Assign a remaining weight which is too low for a successfull deletion of the contract + let low_remaining_weight = + <::WeightInfo as WeightInfo>::on_process_deletion_queue_batch(); - // Run the lazy removal without any limit so that all keys would be removed if there - // had been some weight left in the block. - let weight_used = Contracts::on_initialize(Weight::max_value()); - let base = <::WeightInfo as WeightInfo>::on_initialize(); - assert_eq!(weight_used, base); + // Run the lazy removal + Contracts::on_idle(System::block_number(), low_remaining_weight); - // All the keys are still in place - for val in &vals { - assert_eq!(child::get::(&trie, &blake2_256(&val.0)), Some(val.1)); - } + // Value should still be there, since remaining weight was too low for removal + assert_matches!(child::get::(trie, &[99]), Some(42)); - // Run the lazy removal directly which disregards the block limits - Storage::::process_deletion_queue_batch(Weight::max_value()); + // Run the lazy removal while deletion_queue is not full + Contracts::on_initialize(System::block_number()); - // Now the keys should be gone - for val in &vals { - assert_eq!(child::get::(&trie, &blake2_256(&val.0)), None); - } + // Value should still be there, since deletion_queue was not full + assert_matches!(child::get::(trie, &[99]), Some(42)); + + // Run on_idle with max remaining weight, this should remove the value + Contracts::on_idle(System::block_number(), Weight::max_value()); + + // Value should be gone + assert_matches!(child::get::(trie, &[99]), None); }); } diff --git a/frame/contracts/src/weights.rs b/frame/contracts/src/weights.rs index c6e83d34e90b1..b18c259ebd433 100644 --- a/frame/contracts/src/weights.rs +++ b/frame/contracts/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_contracts //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-04-24, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -44,7 +44,7 @@ use sp_std::marker::PhantomData; /// Weight functions needed for pallet_contracts. pub trait WeightInfo { - fn on_initialize() -> Weight; + fn on_process_deletion_queue_batch() -> Weight; fn on_initialize_per_trie_key(k: u32, ) -> Weight; fn on_initialize_per_queue_item(q: u32, ) -> Weight; fn reinstrument(c: u32, ) -> Weight; @@ -163,8 +163,8 @@ pub trait WeightInfo { pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: Contracts DeletionQueue (r:1 w:0) - fn on_initialize() -> Weight { - (1_684_000 as Weight) + fn on_process_deletion_queue_batch() -> Weight { + (1_664_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) @@ -196,6 +196,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: System Account (r:1 w:1) fn call_with_code_per_byte(c: u32, ) -> Weight { (206_036_000 as Weight) @@ -208,6 +209,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts Nonce (r:1 w:1) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: System Account (r:1 w:1) // Storage: Contracts PristineCode (r:0 w:1) // Storage: Contracts OwnerInfoOf (r:0 w:1) @@ -217,25 +219,27 @@ impl WeightInfo for SubstrateWeight { .saturating_add((122_000 as Weight).saturating_mul(c as Weight)) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(5 as Weight)) - .saturating_add(T::DbWeight::get().writes(6 as Weight)) + .saturating_add(T::DbWeight::get().reads(6 as Weight)) + .saturating_add(T::DbWeight::get().writes(7 as Weight)) } // Storage: Contracts CodeStorage (r:1 w:1) // Storage: Contracts Nonce (r:1 w:1) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: System Account (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:1 w:1) fn instantiate(s: u32, ) -> Weight { (180_607_000 as Weight) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(6 as Weight)) - .saturating_add(T::DbWeight::get().writes(5 as Weight)) + .saturating_add(T::DbWeight::get().reads(7 as Weight)) + .saturating_add(T::DbWeight::get().writes(6 as Weight)) } // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: System Account (r:1 w:1) fn call() -> Weight { (146_032_000 as Weight) @@ -271,6 +275,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_caller(r: u32, ) -> Weight { (205_061_000 as Weight) // Standard Error: 76_000 @@ -282,6 +287,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_is_contract(r: u32, ) -> Weight { (97_971_000 as Weight) // Standard Error: 741_000 @@ -294,6 +300,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_code_hash(r: u32, ) -> Weight { (109_052_000 as Weight) // Standard Error: 716_000 @@ -306,6 +313,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_own_code_hash(r: u32, ) -> Weight { (205_748_000 as Weight) // Standard Error: 87_000 @@ -317,6 +325,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_caller_is_origin(r: u32, ) -> Weight { (201_898_000 as Weight) // Standard Error: 55_000 @@ -328,6 +337,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_address(r: u32, ) -> Weight { (204_668_000 as Weight) // Standard Error: 65_000 @@ -339,6 +349,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_gas_left(r: u32, ) -> Weight { (203_240_000 as Weight) // Standard Error: 68_000 @@ -350,6 +361,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_balance(r: u32, ) -> Weight { (211_535_000 as Weight) // Standard Error: 73_000 @@ -361,6 +373,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_value_transferred(r: u32, ) -> Weight { (204_653_000 as Weight) // Standard Error: 71_000 @@ -372,6 +385,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_minimum_balance(r: u32, ) -> Weight { (204_690_000 as Weight) // Standard Error: 82_000 @@ -383,6 +397,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_block_number(r: u32, ) -> Weight { (205_004_000 as Weight) // Standard Error: 62_000 @@ -394,6 +409,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_now(r: u32, ) -> Weight { (204_341_000 as Weight) // Standard Error: 93_000 @@ -405,6 +421,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) fn seal_weight_to_fee(r: u32, ) -> Weight { (208_702_000 as Weight) @@ -417,6 +434,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_gas(r: u32, ) -> Weight { (131_983_000 as Weight) // Standard Error: 17_000 @@ -428,6 +446,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_input(r: u32, ) -> Weight { (203_768_000 as Weight) // Standard Error: 57_000 @@ -439,6 +458,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_input_per_kb(n: u32, ) -> Weight { (273_930_000 as Weight) // Standard Error: 3_000 @@ -461,6 +481,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_return_per_kb(n: u32, ) -> Weight { (201_130_000 as Weight) // Standard Error: 0 @@ -472,6 +493,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: Contracts DeletionQueue (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:1 w:1) fn seal_terminate(r: u32, ) -> Weight { @@ -480,13 +502,14 @@ impl WeightInfo for SubstrateWeight { .saturating_add((54_190_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().reads((4 as Weight).saturating_mul(r as Weight))) - .saturating_add(T::DbWeight::get().writes(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) .saturating_add(T::DbWeight::get().writes((5 as Weight).saturating_mul(r as Weight))) } // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: RandomnessCollectiveFlip RandomMaterial (r:1 w:0) fn seal_random(r: u32, ) -> Weight { (206_528_000 as Weight) @@ -499,6 +522,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_deposit_event(r: u32, ) -> Weight { (210_309_000 as Weight) // Standard Error: 138_000 @@ -526,6 +550,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_debug_message(r: u32, ) -> Weight { (138_934_000 as Weight) // Standard Error: 34_000 @@ -633,6 +658,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_transfer(r: u32, ) -> Weight { (124_818_000 as Weight) // Standard Error: 1_251_000 @@ -646,6 +672,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_call(r: u32, ) -> Weight { (0 as Weight) // Standard Error: 4_575_000 @@ -659,6 +686,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_delegate_call(r: u32, ) -> Weight { (0 as Weight) // Standard Error: 5_742_000 @@ -670,6 +698,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:81 w:81) // Storage: Contracts CodeStorage (r:2 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_call_per_transfer_clone_kb(t: u32, c: u32, ) -> Weight { (9_081_635_000 as Weight) // Standard Error: 11_326_000 @@ -685,6 +714,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: Contracts Nonce (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:80 w:80) fn seal_instantiate(r: u32, ) -> Weight { @@ -700,6 +730,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:81 w:81) // Storage: Contracts CodeStorage (r:2 w:1) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: Contracts Nonce (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:1 w:1) fn seal_instantiate_per_transfer_salt_kb(t: u32, s: u32, ) -> Weight { @@ -717,6 +748,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_hash_sha2_256(r: u32, ) -> Weight { (203_315_000 as Weight) // Standard Error: 74_000 @@ -728,6 +760,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_hash_sha2_256_per_kb(n: u32, ) -> Weight { (355_672_000 as Weight) // Standard Error: 25_000 @@ -739,6 +772,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_hash_keccak_256(r: u32, ) -> Weight { (203_117_000 as Weight) // Standard Error: 94_000 @@ -750,6 +784,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_hash_keccak_256_per_kb(n: u32, ) -> Weight { (196_575_000 as Weight) // Standard Error: 13_000 @@ -761,6 +796,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_hash_blake2_256(r: u32, ) -> Weight { (203_938_000 as Weight) // Standard Error: 97_000 @@ -772,6 +808,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_hash_blake2_256_per_kb(n: u32, ) -> Weight { (247_065_000 as Weight) // Standard Error: 8_000 @@ -783,6 +820,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_hash_blake2_128(r: u32, ) -> Weight { (204_389_000 as Weight) // Standard Error: 86_000 @@ -794,6 +832,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_hash_blake2_128_per_kb(n: u32, ) -> Weight { (284_700_000 as Weight) // Standard Error: 9_000 @@ -805,6 +844,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_ecdsa_recover(r: u32, ) -> Weight { (235_813_000 as Weight) // Standard Error: 521_000 @@ -816,6 +856,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_ecdsa_to_eth_address(r: u32, ) -> Weight { (204_095_000 as Weight) // Standard Error: 495_000 @@ -1021,9 +1062,9 @@ impl WeightInfo for SubstrateWeight { .saturating_add((1_333_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64sub(r: u32, ) -> Weight { - (74_033_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_322_000 as Weight).saturating_mul(r as Weight)) + (74_312_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_339_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64mul(r: u32, ) -> Weight { (74_203_000 as Weight) @@ -1095,8 +1136,8 @@ impl WeightInfo for SubstrateWeight { // For backwards compatibility and tests impl WeightInfo for () { // Storage: Contracts DeletionQueue (r:1 w:0) - fn on_initialize() -> Weight { - (1_684_000 as Weight) + fn on_process_deletion_queue_batch() -> Weight { + (1_641_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) @@ -1128,6 +1169,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: System Account (r:1 w:1) fn call_with_code_per_byte(c: u32, ) -> Weight { (206_036_000 as Weight) @@ -1140,6 +1182,7 @@ impl WeightInfo for () { // Storage: Contracts Nonce (r:1 w:1) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: System Account (r:1 w:1) // Storage: Contracts PristineCode (r:0 w:1) // Storage: Contracts OwnerInfoOf (r:0 w:1) @@ -1149,25 +1192,27 @@ impl WeightInfo for () { .saturating_add((122_000 as Weight).saturating_mul(c as Weight)) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(5 as Weight)) - .saturating_add(RocksDbWeight::get().writes(6 as Weight)) + .saturating_add(RocksDbWeight::get().reads(6 as Weight)) + .saturating_add(RocksDbWeight::get().writes(7 as Weight)) } // Storage: Contracts CodeStorage (r:1 w:1) // Storage: Contracts Nonce (r:1 w:1) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: System Account (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:1 w:1) fn instantiate(s: u32, ) -> Weight { (180_607_000 as Weight) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(6 as Weight)) - .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + .saturating_add(RocksDbWeight::get().reads(7 as Weight)) + .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: System Account (r:1 w:1) fn call() -> Weight { (146_032_000 as Weight) @@ -1203,6 +1248,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_caller(r: u32, ) -> Weight { (205_061_000 as Weight) // Standard Error: 76_000 @@ -1214,6 +1260,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_is_contract(r: u32, ) -> Weight { (97_971_000 as Weight) // Standard Error: 741_000 @@ -1226,6 +1273,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_code_hash(r: u32, ) -> Weight { (109_052_000 as Weight) // Standard Error: 716_000 @@ -1238,6 +1286,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_own_code_hash(r: u32, ) -> Weight { (205_748_000 as Weight) // Standard Error: 87_000 @@ -1249,6 +1298,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_caller_is_origin(r: u32, ) -> Weight { (201_898_000 as Weight) // Standard Error: 55_000 @@ -1260,6 +1310,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_address(r: u32, ) -> Weight { (204_668_000 as Weight) // Standard Error: 65_000 @@ -1271,6 +1322,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_gas_left(r: u32, ) -> Weight { (203_240_000 as Weight) // Standard Error: 68_000 @@ -1282,6 +1334,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_balance(r: u32, ) -> Weight { (211_535_000 as Weight) // Standard Error: 73_000 @@ -1293,6 +1346,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_value_transferred(r: u32, ) -> Weight { (204_653_000 as Weight) // Standard Error: 71_000 @@ -1304,6 +1358,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_minimum_balance(r: u32, ) -> Weight { (204_690_000 as Weight) // Standard Error: 82_000 @@ -1315,6 +1370,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_block_number(r: u32, ) -> Weight { (205_004_000 as Weight) // Standard Error: 62_000 @@ -1326,6 +1382,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_now(r: u32, ) -> Weight { (204_341_000 as Weight) // Standard Error: 93_000 @@ -1337,6 +1394,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) fn seal_weight_to_fee(r: u32, ) -> Weight { (208_702_000 as Weight) @@ -1349,6 +1407,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_gas(r: u32, ) -> Weight { (131_983_000 as Weight) // Standard Error: 17_000 @@ -1360,6 +1419,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_input(r: u32, ) -> Weight { (203_768_000 as Weight) // Standard Error: 57_000 @@ -1371,6 +1431,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_input_per_kb(n: u32, ) -> Weight { (273_930_000 as Weight) // Standard Error: 3_000 @@ -1393,6 +1454,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_return_per_kb(n: u32, ) -> Weight { (201_130_000 as Weight) // Standard Error: 0 @@ -1404,6 +1466,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: Contracts DeletionQueue (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:1 w:1) fn seal_terminate(r: u32, ) -> Weight { @@ -1412,13 +1475,14 @@ impl WeightInfo for () { .saturating_add((54_190_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().reads((4 as Weight).saturating_mul(r as Weight))) - .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) .saturating_add(RocksDbWeight::get().writes((5 as Weight).saturating_mul(r as Weight))) } // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: RandomnessCollectiveFlip RandomMaterial (r:1 w:0) fn seal_random(r: u32, ) -> Weight { (206_528_000 as Weight) @@ -1431,6 +1495,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_deposit_event(r: u32, ) -> Weight { (210_309_000 as Weight) // Standard Error: 138_000 @@ -1458,6 +1523,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_debug_message(r: u32, ) -> Weight { (138_934_000 as Weight) // Standard Error: 34_000 @@ -1565,6 +1631,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_transfer(r: u32, ) -> Weight { (124_818_000 as Weight) // Standard Error: 1_251_000 @@ -1578,6 +1645,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_call(r: u32, ) -> Weight { (0 as Weight) // Standard Error: 4_575_000 @@ -1591,6 +1659,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_delegate_call(r: u32, ) -> Weight { (0 as Weight) // Standard Error: 5_742_000 @@ -1602,6 +1671,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:81 w:81) // Storage: Contracts CodeStorage (r:2 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_call_per_transfer_clone_kb(t: u32, c: u32, ) -> Weight { (9_081_635_000 as Weight) // Standard Error: 11_326_000 @@ -1617,6 +1687,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: Contracts Nonce (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:80 w:80) fn seal_instantiate(r: u32, ) -> Weight { @@ -1632,6 +1703,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:81 w:81) // Storage: Contracts CodeStorage (r:2 w:1) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: Contracts Nonce (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:1 w:1) fn seal_instantiate_per_transfer_salt_kb(t: u32, s: u32, ) -> Weight { @@ -1649,6 +1721,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_hash_sha2_256(r: u32, ) -> Weight { (203_315_000 as Weight) // Standard Error: 74_000 @@ -1660,6 +1733,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_hash_sha2_256_per_kb(n: u32, ) -> Weight { (355_672_000 as Weight) // Standard Error: 25_000 @@ -1671,6 +1745,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_hash_keccak_256(r: u32, ) -> Weight { (203_117_000 as Weight) // Standard Error: 94_000 @@ -1682,6 +1757,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_hash_keccak_256_per_kb(n: u32, ) -> Weight { (196_575_000 as Weight) // Standard Error: 13_000 @@ -1693,6 +1769,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_hash_blake2_256(r: u32, ) -> Weight { (203_938_000 as Weight) // Standard Error: 97_000 @@ -1704,6 +1781,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_hash_blake2_256_per_kb(n: u32, ) -> Weight { (247_065_000 as Weight) // Standard Error: 8_000 @@ -1715,6 +1793,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_hash_blake2_128(r: u32, ) -> Weight { (204_389_000 as Weight) // Standard Error: 86_000 @@ -1726,6 +1805,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_hash_blake2_128_per_kb(n: u32, ) -> Weight { (284_700_000 as Weight) // Standard Error: 9_000 @@ -1737,6 +1817,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_ecdsa_recover(r: u32, ) -> Weight { (235_813_000 as Weight) // Standard Error: 521_000 @@ -1748,6 +1829,7 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) + // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) fn seal_ecdsa_to_eth_address(r: u32, ) -> Weight { (204_095_000 as Weight) // Standard Error: 495_000 @@ -1953,9 +2035,9 @@ impl WeightInfo for () { .saturating_add((1_333_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64sub(r: u32, ) -> Weight { - (74_033_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_322_000 as Weight).saturating_mul(r as Weight)) + (74_312_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_339_000 as Weight).saturating_mul(r as Weight)) } fn instr_i64mul(r: u32, ) -> Weight { (74_203_000 as Weight) From 2676144c012cefb8880eb4aaa87dbc2ec2986aad Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Tue, 24 May 2022 19:24:55 +0200 Subject: [PATCH 267/484] Fix Babe revert when last finalized block is a leaf (#11500) * Fix Babe revert when a leaf is the last finalized block Without this fix the last finalized block weight data is wrongly removed on revert scenario where the last finalized block is a leaf. * Remove redundant check * Added test to exercise the fix * Rename test * Give variables better names --- client/consensus/babe/src/lib.rs | 34 +++++++++++++------------- client/consensus/babe/src/tests.rs | 38 ++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 17 deletions(-) diff --git a/client/consensus/babe/src/lib.rs b/client/consensus/babe/src/lib.rs index 683df9ddacd62..84a3c618b3803 100644 --- a/client/consensus/babe/src/lib.rs +++ b/client/consensus/babe/src/lib.rs @@ -126,7 +126,7 @@ use sp_inherents::{CreateInherentDataProviders, InherentData, InherentDataProvid use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; use sp_runtime::{ generic::{BlockId, OpaqueDigestItemId}, - traits::{Block as BlockT, Header, NumberFor, One, SaturatedConversion, Saturating, Zero}, + traits::{Block as BlockT, Header, NumberFor, SaturatedConversion, Saturating, Zero}, DigestItem, }; @@ -1875,13 +1875,10 @@ where let finalized = client.info().finalized_number; let revertible = blocks.min(best_number - finalized); - let number = best_number - revertible; - let hash = client - .block_hash_from_id(&BlockId::Number(number))? - .ok_or(ClientError::Backend(format!( - "Unexpected hash lookup failure for block number: {}", - number - )))?; + let revert_up_to_number = best_number - revertible; + let revert_up_to_hash = client.hash(revert_up_to_number)?.ok_or(ClientError::Backend( + format!("Unexpected hash lookup failure for block number: {}", revert_up_to_number), + ))?; // Revert epoch changes tree. @@ -1890,34 +1887,37 @@ where aux_schema::load_epoch_changes::(&*client, config.genesis_config())?; let mut epoch_changes = epoch_changes.shared_data(); - if number == Zero::zero() { + if revert_up_to_number == Zero::zero() { // Special case, no epoch changes data were present on genesis. *epoch_changes = EpochChangesFor::::default(); } else { - epoch_changes.revert(descendent_query(&*client), hash, number); + epoch_changes.revert(descendent_query(&*client), revert_up_to_hash, revert_up_to_number); } // Remove block weights added after the revert point. let mut weight_keys = HashSet::with_capacity(revertible.saturated_into()); + let leaves = backend.blockchain().leaves()?.into_iter().filter(|&leaf| { - sp_blockchain::tree_route(&*client, hash, leaf) + sp_blockchain::tree_route(&*client, revert_up_to_hash, leaf) .map(|route| route.retracted().is_empty()) .unwrap_or_default() }); + for leaf in leaves { let mut hash = leaf; - // Insert parent after parent until we don't hit an already processed - // branch or we reach a direct child of the rollback point. - while weight_keys.insert(aux_schema::block_weight_key(hash)) { + loop { let meta = client.header_metadata(hash)?; - if meta.number <= number + One::one() { - // We've reached a child of the revert point, stop here. + if meta.number <= revert_up_to_number || + !weight_keys.insert(aux_schema::block_weight_key(hash)) + { + // We've reached the revert point or an already processed branch, stop here. break } - hash = client.header_metadata(hash)?.parent; + hash = meta.parent; } } + let weight_keys: Vec<_> = weight_keys.iter().map(|val| val.as_slice()).collect(); // Write epoch changes and remove weights in one shot. diff --git a/client/consensus/babe/src/tests.rs b/client/consensus/babe/src/tests.rs index 9875ff00673f5..e0590fc0cd86e 100644 --- a/client/consensus/babe/src/tests.rs +++ b/client/consensus/babe/src/tests.rs @@ -814,6 +814,44 @@ fn revert_prunes_epoch_changes_and_removes_weights() { assert!(weight_data_check(&fork3, false)); } +#[test] +fn revert_not_allowed_for_finalized() { + let mut net = BabeTestNet::new(1); + + let peer = net.peer(0); + let data = peer.data.as_ref().expect("babe link set up during initialization"); + + let client = peer.client().as_client(); + let backend = peer.client().as_backend(); + let mut block_import = data.block_import.lock().take().expect("import set up during init"); + + let mut proposer_factory = DummyFactory { + client: client.clone(), + config: data.link.config.clone(), + epoch_changes: data.link.epoch_changes.clone(), + mutator: Arc::new(|_, _| ()), + }; + + let mut propose_and_import_blocks_wrap = |parent_id, n| { + propose_and_import_blocks(&client, &mut proposer_factory, &mut block_import, parent_id, n) + }; + + let canon = propose_and_import_blocks_wrap(BlockId::Number(0), 3); + + // Finalize best block + client.finalize_block(BlockId::Hash(canon[2]), None, false).unwrap(); + + // Revert canon chain to last finalized block + revert(client.clone(), backend, 100).expect("revert should work for baked test scenario"); + + let weight_data_check = |hashes: &[Hash], expected: bool| { + hashes.iter().all(|hash| { + aux_schema::load_block_weight(&*client, hash).unwrap().is_some() == expected + }) + }; + assert!(weight_data_check(&canon, true)); +} + #[test] fn importing_epoch_change_block_prunes_tree() { let mut net = BabeTestNet::new(1); From 1f376a3bef04054d71aba94dc8a40edf41380dd0 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Wed, 25 May 2022 05:47:21 +0200 Subject: [PATCH 268/484] Document benchmarking CLI (#11246) * Decrese default repeats Signed-off-by: Oliver Tale-Yazdi * Add benchmarking READMEs Signed-off-by: Oliver Tale-Yazdi * Update docs Signed-off-by: Oliver Tale-Yazdi * Update docs Signed-off-by: Oliver Tale-Yazdi * Update README Signed-off-by: Oliver Tale-Yazdi * Review fixes Co-authored-by: Shawn Tabrizi Co-authored-by: parity-processbot <> Co-authored-by: Shawn Tabrizi --- frame/benchmarking/README.md | 14 +- utils/frame/benchmarking-cli/README.md | 47 +++++- .../benchmarking-cli/src/block/README.md | 118 +++++++++++++++ .../benchmarking-cli/src/machine/README.md | 71 +++++++++ .../benchmarking-cli/src/overhead/README.md | 136 ++++++++++++++++++ .../benchmarking-cli/src/overhead/bench.rs | 4 +- .../benchmarking-cli/src/pallet/README.md | 3 + .../benchmarking-cli/src/shared/README.md | 15 ++ .../benchmarking-cli/src/storage/README.md | 105 ++++++++++++++ 9 files changed, 505 insertions(+), 8 deletions(-) create mode 100644 utils/frame/benchmarking-cli/src/block/README.md create mode 100644 utils/frame/benchmarking-cli/src/machine/README.md create mode 100644 utils/frame/benchmarking-cli/src/overhead/README.md create mode 100644 utils/frame/benchmarking-cli/src/pallet/README.md create mode 100644 utils/frame/benchmarking-cli/src/shared/README.md create mode 100644 utils/frame/benchmarking-cli/src/storage/README.md diff --git a/frame/benchmarking/README.md b/frame/benchmarking/README.md index 38c683cb8db5b..f0fe05cc140f2 100644 --- a/frame/benchmarking/README.md +++ b/frame/benchmarking/README.md @@ -43,7 +43,7 @@ The benchmarking framework comes with the following tools: * [A set of macros](./src/lib.rs) (`benchmarks!`, `add_benchmark!`, etc...) to make it easy to write, test, and add runtime benchmarks. * [A set of linear regression analysis functions](./src/analysis.rs) for processing benchmark data. -* [A CLI extension](../../utils/frame/benchmarking-cli/) to make it easy to execute benchmarks on your +* [A CLI extension](../../utils/frame/benchmarking-cli/README.md) to make it easy to execute benchmarks on your node. The end-to-end benchmarking pipeline is disabled by default when compiling a node. If you want to @@ -150,9 +150,13 @@ feature flag: ```bash cd bin/node/cli -cargo build --release --features runtime-benchmarks +cargo build --profile=production --features runtime-benchmarks ``` +The production profile applies various compiler optimizations. +These optimizations slow down the compilation process *a lot*. +If you are just testing things out and don't need final numbers, don't include `--profile=production`. + ## Running Benchmarks Finally, once you have a node binary with benchmarks enabled, you need to execute your various @@ -161,13 +165,13 @@ benchmarks. You can get a list of the available benchmarks by running: ```bash -./target/release/substrate benchmark --chain dev --pallet "*" --extrinsic "*" --repeat 0 +./target/production/substrate benchmark pallet --chain dev --pallet "*" --extrinsic "*" --repeat 0 ``` Then you can run a benchmark like so: ```bash -./target/release/substrate benchmark \ +./target/production/substrate benchmark pallet \ --chain dev \ # Configurable Chain Spec --execution=wasm \ # Always test with Wasm --wasm-execution=compiled \ # Always used `wasm-time` @@ -200,7 +204,7 @@ used for joining all the arguments passed to the CLI. To get a full list of available options when running benchmarks, run: ```bash -./target/release/substrate benchmark --help +./target/production/substrate benchmark --help ``` License: Apache-2.0 diff --git a/utils/frame/benchmarking-cli/README.md b/utils/frame/benchmarking-cli/README.md index 9718db58b37e9..e6a48b61fd227 100644 --- a/utils/frame/benchmarking-cli/README.md +++ b/utils/frame/benchmarking-cli/README.md @@ -1 +1,46 @@ -License: Apache-2.0 \ No newline at end of file +# The Benchmarking CLI + +This crate contains commands to benchmark various aspects of Substrate and the hardware. +All commands are exposed by the Substrate node but can be exposed by any Substrate client. +The goal is to have a comprehensive suite of benchmarks that cover all aspects of Substrate and the hardware that its running on. + +Invoking the root benchmark command prints a help menu: +```sh +$ cargo run --profile=production -- benchmark + +Sub-commands concerned with benchmarking. + +USAGE: + substrate benchmark + +OPTIONS: + -h, --help Print help information + -V, --version Print version information + +SUBCOMMANDS: + block Benchmark the execution time of historic blocks + machine Command to benchmark the hardware. + overhead Benchmark the execution overhead per-block and per-extrinsic + pallet Benchmark the extrinsic weight of FRAME Pallets + storage Benchmark the storage speed of a chain snapshot +``` + +All examples use the `production` profile for correctness which makes the compilation *very* slow; for testing you can use `--release`. +For the final results the `production` profile and reference hardware should be used, otherwise the results are not comparable. + +The sub-commands are explained in depth here: +- [block] Compare the weight of a historic block to its actual resource usage +- [machine] Gauges the speed of the hardware +- [overhead] Creates weight files for the *Block*- and *Extrinsic*-base weights +- [pallet] Creates weight files for a Pallet +- [storage] Creates weight files for *Read* and *Write* storage operations + +License: Apache-2.0 + + + +[pallet]: ../../../frame/benchmarking/README.md +[machine]: src/machine/README.md +[storage]: src/storage/README.md +[overhead]: src/overhead/README.md +[block]: src/block/README.md diff --git a/utils/frame/benchmarking-cli/src/block/README.md b/utils/frame/benchmarking-cli/src/block/README.md new file mode 100644 index 0000000000000..7e99f0df9d43b --- /dev/null +++ b/utils/frame/benchmarking-cli/src/block/README.md @@ -0,0 +1,118 @@ +# The `benchmark block` command + +The whole benchmarking process in Substrate aims to predict the resource usage of an unexecuted block. +This command measures how accurate this prediction was by executing a block and comparing the predicted weight to its actual resource usage. +It can be used to measure the accuracy of the pallet benchmarking. + +In the following it will be explained once for Polkadot and once for Substrate. + +## Polkadot # 1 +(Also works for Kusama, Westend and Rococo) + + +Suppose you either have a synced Polkadot node or downloaded a snapshot from [Polkachu]. +This example uses a pruned ParityDB snapshot from the 2022-4-19 with the last block being 9939462. +For pruned snapshots you need to know the number of the last block (to be improved [here]). +Pruned snapshots normally store the last 256 blocks, archive nodes can use any block range. + +In this example we will benchmark just the last 10 blocks: +```sh +cargo run --profile=production -- benchmark block --from 9939453 --to 9939462 --db paritydb +``` + +Output: +```pre +Block 9939453 with 2 tx used 4.57% of its weight ( 26,458,801 of 579,047,053 ns) +Block 9939454 with 3 tx used 4.80% of its weight ( 28,335,826 of 590,414,831 ns) +Block 9939455 with 2 tx used 4.76% of its weight ( 27,889,567 of 586,484,595 ns) +Block 9939456 with 2 tx used 4.65% of its weight ( 27,101,306 of 582,789,723 ns) +Block 9939457 with 2 tx used 4.62% of its weight ( 26,908,882 of 582,789,723 ns) +Block 9939458 with 2 tx used 4.78% of its weight ( 28,211,440 of 590,179,467 ns) +Block 9939459 with 4 tx used 4.78% of its weight ( 27,866,077 of 583,260,451 ns) +Block 9939460 with 3 tx used 4.72% of its weight ( 27,845,836 of 590,462,629 ns) +Block 9939461 with 2 tx used 4.58% of its weight ( 26,685,119 of 582,789,723 ns) +Block 9939462 with 2 tx used 4.60% of its weight ( 26,840,938 of 583,697,101 ns) +``` + +### Output Interpretation + +(Only results from reference hardware are relevant) + +Each block is executed multiple times and the results are averaged. +The percent number is the interesting part and indicates how much weight was used as compared to how much was predicted. +The closer to 100% this is without exceeding 100%, the better. +If it exceeds 100%, the block is marked with "**OVER WEIGHT!**" to easier spot them. This is not good since then the benchmarking under-estimated the weight. +This would mean that an honest validator would possibly not be able to keep up with importing blocks since users did not pay for enough weight. +If that happens the validator could lag behind the chain and get slashed for missing deadlines. +It is therefore important to investigate any overweight blocks. + +In this example you can see an unexpected result; only < 5% of the weight was used! +The measured blocks can be executed much faster than predicted. +This means that the benchmarking process massively over-estimated the execution time. +Since they are off by so much, it is an issue [polkadot#5192]. + +The ideal range for these results would be 85-100%. + +## Polkadot # 2 + +Let's take a more interesting example where the blocks use more of their predicted weight. +Every day when validators pay out rewards, the blocks are nearly full. +Using an archive node here is the easiest. + +The Polkadot blocks TODO-TODO for example contain large batch transactions for staking payout. + +```sh +cargo run --profile=production -- benchmark block --from TODO --to TODO --db paritydb +``` + +```pre +TODO +``` + +## Substrate + +It is also possible to try the procedure in Substrate, although it's a bit boring. + +First you need to create some blocks with either a local or dev chain. +This example will use the standard development spec. +Pick a non existing directory where the chain data will be stored, eg `/tmp/dev`. +```sh +cargo run --profile=production -- --dev -d /tmp/dev +``` +You should see after some seconds that it started to produce blocks: +```pre +… +✨ Imported #1 (0x801d…9189) +… +``` +You can now kill the node with `Ctrl+C`. Then measure how long it takes to execute these blocks: +```sh +cargo run --profile=production -- benchmark block --from 1 --to 1 --dev -d /tmp/dev --pruning archive +``` +This will benchmark the first block. If you killed the node at a later point, you can measure multiple blocks. +```pre +Block 1 with 1 tx used 72.04% of its weight ( 4,945,664 of 6,864,702 ns) +``` + +In this example the block used ~72% of its weight. +The benchmarking therefore over-estimated the effort to execute the block. +Since this block is empty, its not very interesting. + +## Arguments + +- `--from` Number of the first block to measure (inclusive). +- `--to` Number of the last block to measure (inclusive). +- `--repeat` How often each block should be measured. +- [`--db`] +- [`--pruning`] + +License: Apache-2.0 + + + +[Polkachu]: https://polkachu.com/snapshots +[here]: https://github.com/paritytech/substrate/issues/11141 +[polkadot#5192]: https://github.com/paritytech/polkadot/issues/5192 + +[`--db`]: ../shared/README.md#arguments +[`--pruning`]: ../shared/README.md#arguments diff --git a/utils/frame/benchmarking-cli/src/machine/README.md b/utils/frame/benchmarking-cli/src/machine/README.md new file mode 100644 index 0000000000000..f22a8ea54b81d --- /dev/null +++ b/utils/frame/benchmarking-cli/src/machine/README.md @@ -0,0 +1,71 @@ +# The `benchmark machine` command + +Different Substrate chains can have different hardware requirements. +It is therefore important to be able to quickly gauge if a piece of hardware fits a chains' requirements. +The `benchmark machine` command archives this by measuring key metrics and making them comparable. + +Invoking the command looks like this: +```sh +cargo run --profile=production -- benchmark machine --dev +``` + +## Output + +The output on reference hardware: + +```pre ++----------+----------------+---------------+--------------+-------------------+ +| Category | Function | Score | Minimum | Result | ++----------+----------------+---------------+--------------+-------------------+ +| CPU | BLAKE2-256 | 1023.00 MiB/s | 1.00 GiB/s | ✅ Pass ( 99.4 %) | ++----------+----------------+---------------+--------------+-------------------+ +| CPU | SR25519-Verify | 665.13 KiB/s | 666.00 KiB/s | ✅ Pass ( 99.9 %) | ++----------+----------------+---------------+--------------+-------------------+ +| Memory | Copy | 14.39 GiB/s | 14.32 GiB/s | ✅ Pass (100.4 %) | ++----------+----------------+---------------+--------------+-------------------+ +| Disk | Seq Write | 457.00 MiB/s | 450.00 MiB/s | ✅ Pass (101.6 %) | ++----------+----------------+---------------+--------------+-------------------+ +| Disk | Rnd Write | 190.00 MiB/s | 200.00 MiB/s | ✅ Pass ( 95.0 %) | ++----------+----------------+---------------+--------------+-------------------+ +``` + +The *score* is the average result of each benchmark. It always adheres to "higher is better". + +The *category* indicate which part of the hardware was benchmarked: +- **CPU** Processor intensive task +- **Memory** RAM intensive task +- **Disk** Hard drive intensive task + +The *function* is the concrete benchmark that was run: +- **BLAKE2-256** The throughput of the [Blake2-256] cryptographic hashing function with 32 KiB input. The [blake2_256 function] is used in many places in Substrate. The throughput of a hash function strongly depends on the input size, therefore we settled to use a fixed input size for comparable results. +- **SR25519 Verify** Sr25519 is an optimized version of the [Curve25519] signature scheme. Signature verification is used by Substrate when verifying extrinsics and blocks. +- **Copy** The throughput of copying memory from one place in the RAM to another. +- **Seq Write** The throughput of writing data to the storage location sequentially. It is important that the same disk is used that will later-on be used to store the chain data. +- **Rnd Write** The throughput of writing data to the storage location in a random order. This is normally much slower than the sequential write. + +The *score* needs to reach the *minimum* in order to pass the benchmark. This can be reduced with the `--tolerance` flag. + +The *result* indicated if a specific benchmark was passed by the machine or not. The percent number is the relative score reached to the *minimum* that is needed. The `--tolerance` flag is taken into account for this decision. For example a benchmark that passes even with 95% since the *tolerance* was set to 10% would look like this: `✅ Pass ( 95.0 %)`. + +## Interpretation + +Ideally all results show a `Pass` and the program exits with code 0. Currently some of the benchmarks can fail even on reference hardware; they are still being improved to make them more deterministic. +Make sure to run nothing else on the machine when benchmarking it. +You can re-run them multiple times to get more reliable results. + +## Arguments + +- `--tolerance` A percent number to reduce the *minimum* requirement. This should be used to ignore outliers of the benchmarks. The default value is 10%. +- `--verify-duration` How long the verification benchmark should run. +- `--disk-duration` How long the *read* and *write* benchmarks should run each. +- `--allow-fail` Always exit the program with code 0. +- `--chain` / `--dev` Specify the chain config to use. This will be used to compare the results with the requirements of the chain (WIP). +- [`--base-path`] + +License: Apache-2.0 + + +[Blake2-256]: https://www.blake2.net/ +[blake2_256 function]: https://crates.parity.io/sp_core/hashing/fn.blake2_256.html +[Curve25519]: https://en.wikipedia.org/wiki/Curve25519 +[`--base-path`]: ../shared/README.md#arguments diff --git a/utils/frame/benchmarking-cli/src/overhead/README.md b/utils/frame/benchmarking-cli/src/overhead/README.md new file mode 100644 index 0000000000000..6f41e881d0572 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/overhead/README.md @@ -0,0 +1,136 @@ +# The `benchmark overhead` command + +Each time an extrinsic or a block is executed, a fixed weight is charged as "execution overhead". +This is necessary since the weight that is calculated by the pallet benchmarks does not include this overhead. +The exact overhead to can vary per Substrate chain and needs to be calculated per chain. +This command calculates the exact values of these overhead weights for any Substrate chain that supports it. + +## How does it work? + +The benchmark consists of two parts; the [`BlockExecutionWeight`] and the [`ExtrinsicBaseWeight`]. +Both are executed sequentially when invoking the command. + +## BlockExecutionWeight + +The block execution weight is defined as the weight that it takes to execute an *empty block*. +It is measured by constructing an empty block and measuring its executing time. +The result are written to a `block_weights.rs` file which is created from a template. +The file will contain the concrete weight value and various statistics about the measurements. For example: +```rust +/// Time to execute an empty block. +/// Calculated by multiplying the *Average* with `1` and adding `0`. +/// +/// Stats [NS]: +/// Min, Max: 3_508_416, 3_680_498 +/// Average: 3_532_484 +/// Median: 3_522_111 +/// Std-Dev: 27070.23 +/// +/// Percentiles [NS]: +/// 99th: 3_631_863 +/// 95th: 3_595_674 +/// 75th: 3_526_435 +pub const BlockExecutionWeight: Weight = 3_532_484 * WEIGHT_PER_NANOS; +``` + +In this example it takes 3.5 ms to execute an empty block. That means that it always takes at least 3.5 ms to execute *any* block. +This constant weight is therefore added to each block to ensure that Substrate budgets enough time to execute it. + +## ExtrinsicBaseWeight + +The extrinsic base weight is defined as the weight that it takes to execute an *empty* extrinsic. +An *empty* extrinsic is also called a *NO-OP*. It does nothing and is the equivalent to the empty block form above. +The benchmark now constructs a block which is filled with only NO-OP extrinsics. +This block is then executed many times and the weights are measured. +The result is divided by the number of extrinsics in that block and the results are written to `extrinsic_weights.rs`. + +The relevant section in the output file looks like this: +```rust + /// Time to execute a NO-OP extrinsic, for example `System::remark`. +/// Calculated by multiplying the *Average* with `1` and adding `0`. +/// +/// Stats [NS]: +/// Min, Max: 67_561, 69_855 +/// Average: 67_745 +/// Median: 67_701 +/// Std-Dev: 264.68 +/// +/// Percentiles [NS]: +/// 99th: 68_758 +/// 95th: 67_843 +/// 75th: 67_749 +pub const ExtrinsicBaseWeight: Weight = 67_745 * WEIGHT_PER_NANOS; +``` + +In this example it takes 67.7 µs to execute a NO-OP extrinsic. That means that it always takes at least 67.7 µs to execute *any* extrinsic. +This constant weight is therefore added to each extrinsic to ensure that Substrate budgets enough time to execute it. + +## Invocation + +The base command looks like this (for debugging you can use `--release`): +```sh +cargo run --profile=production -- benchmark overhead --dev +``` + +Output: +```pre +# BlockExecutionWeight +Running 10 warmups... +Executing block 100 times +Per-block execution overhead [ns]: +Total: 353248430 +Min: 3508416, Max: 3680498 +Average: 3532484, Median: 3522111, Stddev: 27070.23 +Percentiles 99th, 95th, 75th: 3631863, 3595674, 3526435 +Writing weights to "block_weights.rs" + +# Setup +Building block, this takes some time... +Extrinsics per block: 12000 + +# ExtrinsicBaseWeight +Running 10 warmups... +Executing block 100 times +Per-extrinsic execution overhead [ns]: +Total: 6774590 +Min: 67561, Max: 69855 +Average: 67745, Median: 67701, Stddev: 264.68 +Percentiles 99th, 95th, 75th: 68758, 67843, 67749 +Writing weights to "extrinsic_weights.rs" +``` + +The complete command for Polkadot looks like this: +```sh +cargo run --profile=production -- benchmark overhead --chain=polkadot-dev --execution=wasm --wasm-execution=compiled --weight-path=runtime/polkadot/constants/src/weights/ +``` + +This will overwrite the the [block_weights.rs](https://github.com/paritytech/polkadot/blob/c254e5975711a6497af256f6831e9a6c752d28f5/runtime/polkadot/constants/src/weights/block_weights.rs) and [extrinsic_weights.rs](https://github.com/paritytech/polkadot/blob/c254e5975711a6497af256f6831e9a6c752d28f5/runtime/polkadot/constants/src/weights/extrinsic_weights.rs) files in the Polkadot runtime directory. +You can try the same for *Rococo* and to see that the results slightly differ. +👉 It is paramount to use `--profile=production`, `--execution=wasm` and `--wasm-execution=compiled` as the results are otherwise useless. + +## Output Interpretation + +Lower is better. The less weight the execution overhead needs, the better. +Since the weights of the overhead is charged per extrinsic and per block, a larger weight results in less extrinsics per block. +Minimizing this is important to have a large transaction throughput. + +## Arguments + +- `--chain` / `--dev` Set the chain specification. +- `--weight-path` Set the output directory or file to write the weights to. +- `--repeat` Set the repetitions of both benchmarks. +- `--warmup` Set the rounds of warmup before measuring. +- `--execution` Should be set to `wasm` for correct results. +- `--wasm-execution` Should be set to `compiled` for correct results. +- [`--mul`](../shared/README.md#arguments) +- [`--add`](../shared/README.md#arguments) +- [`--metric`](../shared/README.md#arguments) +- [`--weight-path`](../shared/README.md#arguments) + +License: Apache-2.0 + + +[`ExtrinsicBaseWeight`]: https://github.com/paritytech/substrate/blob/580ebae17fa30082604f1c9720f6f4a1cfe95b50/frame/support/src/weights/extrinsic_weights.rs#L26 +[`BlockExecutionWeight`]: https://github.com/paritytech/substrate/blob/580ebae17fa30082604f1c9720f6f4a1cfe95b50/frame/support/src/weights/block_weights.rs#L26 + +[System::Remark]: https://github.com/paritytech/substrate/blob/580ebae17fa30082604f1c9720f6f4a1cfe95b50/frame/system/src/lib.rs#L382 diff --git a/utils/frame/benchmarking-cli/src/overhead/bench.rs b/utils/frame/benchmarking-cli/src/overhead/bench.rs index 68f3f6597b466..be7dac24021cf 100644 --- a/utils/frame/benchmarking-cli/src/overhead/bench.rs +++ b/utils/frame/benchmarking-cli/src/overhead/bench.rs @@ -43,11 +43,11 @@ use crate::shared::Stats; #[derive(Debug, Default, Serialize, Clone, PartialEq, Args)] pub struct BenchmarkParams { /// Rounds of warmups before measuring. - #[clap(long, default_value = "100")] + #[clap(long, default_value = "10")] pub warmup: u32, /// How many times the benchmark should be repeated. - #[clap(long, default_value = "1000")] + #[clap(long, default_value = "100")] pub repeat: u32, /// Maximal number of extrinsics that should be put into a block. diff --git a/utils/frame/benchmarking-cli/src/pallet/README.md b/utils/frame/benchmarking-cli/src/pallet/README.md new file mode 100644 index 0000000000000..72845652de653 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/pallet/README.md @@ -0,0 +1,3 @@ +The pallet command is explained in [frame/benchmarking](../../../../../frame/benchmarking/README.md). + +License: Apache-2.0 diff --git a/utils/frame/benchmarking-cli/src/shared/README.md b/utils/frame/benchmarking-cli/src/shared/README.md new file mode 100644 index 0000000000000..2a3719b85498c --- /dev/null +++ b/utils/frame/benchmarking-cli/src/shared/README.md @@ -0,0 +1,15 @@ +# Shared code + +Contains code that is shared among multiple sub-commands. + +## Arguments + +- `--mul` Multiply the result with a factor. Can be used to manually adjust for future chain growth. +- `--add` Add a value to the result. Can be used to manually offset the results. +- `--metric` Set the metric to use for calculating the final weight from the raw data. Defaults to `average`. +- `--weight-path` Set the file or directory to write the weight files to. +- `--db` The database backend to use. This depends on your snapshot. +- `--pruning` Set the pruning mode of the node. Some benchmarks require you to set this to `archive`. +- `--base-path` The location on the disk that should be used for the benchmarks. You can try this on different disks or even on a mounted RAM-disk. It is important to use the same location that will later-on be used to store the chain data to get the correct results. + +License: Apache-2.0 diff --git a/utils/frame/benchmarking-cli/src/storage/README.md b/utils/frame/benchmarking-cli/src/storage/README.md new file mode 100644 index 0000000000000..820785f7ea20c --- /dev/null +++ b/utils/frame/benchmarking-cli/src/storage/README.md @@ -0,0 +1,105 @@ +# The `benchmark storage` command + +The cost of storage operations in a Substrate chain depends on the current chain state. +It is therefore important to regularly update these weights as the chain grows. +This sub-command measures the cost of storage operations for a concrete snapshot. + +For the Substrate node it looks like this (for debugging you can use `--release`): +```sh +cargo run --profile=production -- benchmark storage --dev --state-version=1 +``` + +Running the command on Substrate itself is not verify meaningful, since the genesis state of the `--dev` chain spec is used. + +The output for the Polkadot client with a recent chain snapshot will give you a better impression. A recent snapshot can be downloaded from [Polkachu]. +Then run (remove the `--db=paritydb` if you have a RocksDB snapshot): +```sh +cargo run --profile=production -- benchmark storage --dev --state-version=0 --db=paritydb --weight-path runtime/polkadot/constants/src/weights +``` + +This takes a while since reads and writes all keys from the snapshot: +```pre +# The 'read' benchmark +Preparing keys from block BlockId::Number(9939462) +Reading 1379083 keys +Time summary [ns]: +Total: 19668919930 +Min: 6450, Max: 1217259 +Average: 14262, Median: 14190, Stddev: 3035.79 +Percentiles 99th, 95th, 75th: 18270, 16190, 14819 +Value size summary: +Total: 265702275 +Min: 1, Max: 1381859 +Average: 192, Median: 80, Stddev: 3427.53 +Percentiles 99th, 95th, 75th: 3368, 383, 80 + +# The 'write' benchmark +Preparing keys from block BlockId::Number(9939462) +Writing 1379083 keys +Time summary [ns]: +Total: 98393809781 +Min: 12969, Max: 13282577 +Average: 71347, Median: 69499, Stddev: 25145.27 +Percentiles 99th, 95th, 75th: 135839, 106129, 79239 +Value size summary: +Total: 265702275 +Min: 1, Max: 1381859 +Average: 192, Median: 80, Stddev: 3427.53 +Percentiles 99th, 95th, 75th: 3368, 383, 80 + +Writing weights to "paritydb_weights.rs" +``` +You will see that the [paritydb_weights.rs] files was modified and now contains new weights. +The exact command for Polkadot can be seen at the top of the file. +This uses the most recent block from your snapshot which is printed at the top. +The value size summary tells us that the pruned Polkadot chain state is ~253 MiB in size. +Reading a value on average takes (in this examples) 14.3 µs and writing 71.3 µs. +The interesting part in the generated weight file tells us the weight constants and some statistics about the measurements: +```rust +/// Time to read one storage item. +/// Calculated by multiplying the *Average* of all values with `1.1` and adding `0`. +/// +/// Stats [NS]: +/// Min, Max: 4_611, 1_217_259 +/// Average: 14_262 +/// Median: 14_190 +/// Std-Dev: 3035.79 +/// +/// Percentiles [NS]: +/// 99th: 18_270 +/// 95th: 16_190 +/// 75th: 14_819 +read: 14_262 * constants::WEIGHT_PER_NANOS, + +/// Time to write one storage item. +/// Calculated by multiplying the *Average* of all values with `1.1` and adding `0`. +/// +/// Stats [NS]: +/// Min, Max: 12_969, 13_282_577 +/// Average: 71_347This works under the assumption that the *average* read a +/// Median: 69_499 +/// Std-Dev: 25145.27 +/// +/// Percentiles [NS]: +/// 99th: 135_839 +/// 95th: 106_129 +/// 75th: 79_239 +write: 71_347 * constants::WEIGHT_PER_NANOS, +``` + +## Arguments + +- `--db` Specify which database backend to use. This greatly influences the results. +- `--state-version` Set the version of the state encoding that this snapshot uses. Should be set to `1` for Substrate `--dev` and `0` for Polkadot et al. Using the wrong version can corrupt the snapshot. +- [`--mul`](../shared/README.md#arguments) +- [`--add`](../shared/README.md#arguments) +- [`--metric`](../shared/README.md#arguments) +- [`--weight-path`](../shared/README.md#arguments) +- `--json-read-path` Write the raw 'read' results to this file or directory. +- `--json-write-path` Write the raw 'write' results to this file or directory. + +License: Apache-2.0 + + +[Polkachu]: https://polkachu.com/snapshots +[paritydb_weights.rs]: https://github.com/paritytech/polkadot/blob/c254e5975711a6497af256f6831e9a6c752d28f5/runtime/polkadot/constants/src/weights/paritydb_weights.rs#L60 From 7d421adf876a9c8402ee53ff73e7afa1f986fcf9 Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Wed, 25 May 2022 08:32:11 +0300 Subject: [PATCH 269/484] Introduce `WeightToFee` trait instead of `WeightToFeePolynomial` and make `WeightToFeePolynomial` implement it instead (#11415) * Introduce `WeightToFee` trait instead of `WeightToFeePolynomial` and make `WeightToFeePolynomial` implement it instead * Rename `WeightToFee::calc()` to `WeightToFee::wight_to_fee()` * Fix typo --- Cargo.lock | 2 - bin/node/executor/tests/fees.rs | 8 +- bin/node/runtime/src/impls.rs | 6 +- frame/executive/src/lib.rs | 10 +-- frame/support/src/weights.rs | 83 ++++++++++--------- frame/transaction-payment/Cargo.toml | 1 - .../asset-tx-payment/Cargo.toml | 1 - .../asset-tx-payment/src/tests.rs | 33 ++++---- frame/transaction-payment/src/lib.rs | 54 ++++-------- 9 files changed, 86 insertions(+), 112 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5b2ceb36f6152..7b1a05b7067e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5134,7 +5134,6 @@ dependencies = [ "scale-info", "serde", "serde_json", - "smallvec", "sp-core", "sp-io", "sp-runtime", @@ -6285,7 +6284,6 @@ dependencies = [ "scale-info", "serde", "serde_json", - "smallvec", "sp-core", "sp-io", "sp-runtime", diff --git a/bin/node/executor/tests/fees.rs b/bin/node/executor/tests/fees.rs index a84ce1470d877..cf794bae1d307 100644 --- a/bin/node/executor/tests/fees.rs +++ b/bin/node/executor/tests/fees.rs @@ -18,9 +18,7 @@ use codec::{Encode, Joiner}; use frame_support::{ traits::Currency, - weights::{ - constants::ExtrinsicBaseWeight, GetDispatchInfo, IdentityFee, WeightToFeePolynomial, - }, + weights::{constants::ExtrinsicBaseWeight, GetDispatchInfo, IdentityFee, WeightToFee}, }; use node_primitives::Balance; use node_runtime::{ @@ -197,13 +195,13 @@ fn transaction_fee_is_correct() { let mut balance_alice = (100 - 69) * DOLLARS; let base_weight = ExtrinsicBaseWeight::get(); - let base_fee = IdentityFee::::calc(&base_weight); + let base_fee = IdentityFee::::weight_to_fee(&base_weight); let length_fee = TransactionByteFee::get() * (xt.clone().encode().len() as Balance); balance_alice -= length_fee; let weight = default_transfer_call().get_dispatch_info().weight; - let weight_fee = IdentityFee::::calc(&weight); + let weight_fee = IdentityFee::::weight_to_fee(&weight); // we know that weight to fee multiplier is effect-less in block 1. // current weight of transfer = 200_000_000 diff --git a/bin/node/runtime/src/impls.rs b/bin/node/runtime/src/impls.rs index f73443920c213..4973aed4bd4fc 100644 --- a/bin/node/runtime/src/impls.rs +++ b/bin/node/runtime/src/impls.rs @@ -59,7 +59,7 @@ mod multiplier_tests { AdjustmentVariable, MinimumMultiplier, Runtime, RuntimeBlockWeights as BlockWeights, System, TargetBlockFullness, TransactionPayment, }; - use frame_support::weights::{DispatchClass, Weight, WeightToFeePolynomial}; + use frame_support::weights::{DispatchClass, Weight, WeightToFee}; fn max_normal() -> Weight { BlockWeights::get() @@ -234,7 +234,9 @@ mod multiplier_tests { fm = next; iterations += 1; let fee = - ::WeightToFee::calc(&tx_weight); + ::WeightToFee::weight_to_fee( + &tx_weight, + ); let adjusted_fee = fm.saturating_mul_acc_int(fee); println!( "iteration {}, new fm = {:?}. Fee at this point is: {} units / {} millicents, \ diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index 4affc26f831fd..55b7e926a0fad 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -576,9 +576,7 @@ mod tests { ConstU32, ConstU64, ConstU8, Currency, LockIdentifier, LockableCurrency, WithdrawReasons, }, - weights::{ - ConstantMultiplier, IdentityFee, RuntimeDbWeight, Weight, WeightToFeePolynomial, - }, + weights::{ConstantMultiplier, IdentityFee, RuntimeDbWeight, Weight, WeightToFee}, }; use frame_system::{Call as SystemCall, ChainContext, LastRuntimeUpgradeInfo}; use pallet_balances::Call as BalancesCall; @@ -866,7 +864,7 @@ mod tests { .get(DispatchClass::Normal) .base_extrinsic; let fee: Balance = - ::WeightToFee::calc(&weight); + ::WeightToFee::weight_to_fee(&weight); let mut t = sp_io::TestExternalities::new(t); t.execute_with(|| { Executive::initialize_block(&Header::new( @@ -1153,7 +1151,9 @@ mod tests { .get(DispatchClass::Normal) .base_extrinsic; let fee: Balance = - ::WeightToFee::calc(&weight); + ::WeightToFee::weight_to_fee( + &weight, + ); Executive::initialize_block(&Header::new( 1, H256::default(), diff --git a/frame/support/src/weights.rs b/frame/support/src/weights.rs index fc85a031b0852..a3df199496922 100644 --- a/frame/support/src/weights.rs +++ b/frame/support/src/weights.rs @@ -140,7 +140,7 @@ use codec::{Decode, Encode}; use scale_info::TypeInfo; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; -use smallvec::{smallvec, SmallVec}; +use smallvec::SmallVec; use sp_arithmetic::{ traits::{BaseArithmetic, Saturating, Unsigned}, Perbill, @@ -622,7 +622,7 @@ impl RuntimeDbWeight { } } -/// One coefficient and its position in the `WeightToFeePolynomial`. +/// One coefficient and its position in the `WeightToFee`. /// /// One term of polynomial is calculated as: /// @@ -647,6 +647,15 @@ pub struct WeightToFeeCoefficient { /// A list of coefficients that represent one polynomial. pub type WeightToFeeCoefficients = SmallVec<[WeightToFeeCoefficient; 4]>; +/// A trait that describes the weight to fee calculation. +pub trait WeightToFee { + /// The type that is returned as result from calculation. + type Balance: BaseArithmetic + From + Copy + Unsigned; + + /// Calculates the fee from the passed `weight`. + fn weight_to_fee(weight: &Weight) -> Self::Balance; +} + /// A trait that describes the weight to fee calculation as polynomial. /// /// An implementor should only implement the `polynomial` function. @@ -661,12 +670,19 @@ pub trait WeightToFeePolynomial { /// that the order of coefficients is important as putting the negative coefficients /// first will most likely saturate the result to zero mid evaluation. fn polynomial() -> WeightToFeeCoefficients; +} + +impl WeightToFee for T +where + T: WeightToFeePolynomial, +{ + type Balance = ::Balance; /// Calculates the fee from the passed `weight` according to the `polynomial`. /// /// This should not be overriden in most circumstances. Calculation is done in the /// `Balance` type and never overflows. All evaluation is saturating. - fn calc(weight: &Weight) -> Self::Balance { + fn weight_to_fee(weight: &Weight) -> Self::Balance { Self::polynomial() .iter() .fold(Self::Balance::saturated_from(0u32), |mut acc, args| { @@ -690,30 +706,21 @@ pub trait WeightToFeePolynomial { } } -/// Implementor of `WeightToFeePolynomial` that maps one unit of weight to one unit of fee. +/// Implementor of `WeightToFee` that maps one unit of weight to one unit of fee. pub struct IdentityFee(sp_std::marker::PhantomData); -impl WeightToFeePolynomial for IdentityFee +impl WeightToFee for IdentityFee where T: BaseArithmetic + From + Copy + Unsigned, { type Balance = T; - fn polynomial() -> WeightToFeeCoefficients { - smallvec!(WeightToFeeCoefficient { - coeff_integer: 1u32.into(), - coeff_frac: Perbill::zero(), - negative: false, - degree: 1, - }) - } - - fn calc(weight: &Weight) -> Self::Balance { + fn weight_to_fee(weight: &Weight) -> Self::Balance { Self::Balance::saturated_from(*weight) } } -/// Implementor of [`WeightToFeePolynomial`] that uses a constant multiplier. +/// Implementor of [`WeightToFee`] that uses a constant multiplier. /// # Example /// /// ``` @@ -724,23 +731,14 @@ where /// ``` pub struct ConstantMultiplier(sp_std::marker::PhantomData<(T, M)>); -impl WeightToFeePolynomial for ConstantMultiplier +impl WeightToFee for ConstantMultiplier where T: BaseArithmetic + From + Copy + Unsigned, M: Get, { type Balance = T; - fn polynomial() -> WeightToFeeCoefficients { - smallvec!(WeightToFeeCoefficient { - coeff_integer: M::get(), - coeff_frac: Perbill::zero(), - negative: false, - degree: 1, - }) - } - - fn calc(weight: &Weight) -> Self::Balance { + fn weight_to_fee(weight: &Weight) -> Self::Balance { Self::Balance::saturated_from(*weight).saturating_mul(M::get()) } } @@ -831,6 +829,7 @@ impl PerDispatchClass { mod tests { use super::*; use crate::{decl_module, parameter_types, traits::Get}; + use smallvec::smallvec; pub trait Config: 'static { type Origin; @@ -997,35 +996,41 @@ mod tests { #[test] fn polynomial_works() { // 100^3/2=500000 100^2*(2+1/3)=23333 700 -10000 - assert_eq!(Poly::calc(&100), 514033); + assert_eq!(Poly::weight_to_fee(&100), 514033); // 10123^3/2=518677865433 10123^2*(2+1/3)=239108634 70861 -10000 - assert_eq!(Poly::calc(&10_123), 518917034928); + assert_eq!(Poly::weight_to_fee(&10_123), 518917034928); } #[test] fn polynomial_does_not_underflow() { - assert_eq!(Poly::calc(&0), 0); - assert_eq!(Poly::calc(&10), 0); + assert_eq!(Poly::weight_to_fee(&0), 0); + assert_eq!(Poly::weight_to_fee(&10), 0); } #[test] fn polynomial_does_not_overflow() { - assert_eq!(Poly::calc(&Weight::max_value()), Balance::max_value() - 10_000); + assert_eq!(Poly::weight_to_fee(&Weight::max_value()), Balance::max_value() - 10_000); } #[test] fn identity_fee_works() { - assert_eq!(IdentityFee::::calc(&0), 0); - assert_eq!(IdentityFee::::calc(&50), 50); - assert_eq!(IdentityFee::::calc(&Weight::max_value()), Balance::max_value()); + assert_eq!(IdentityFee::::weight_to_fee(&0), 0); + assert_eq!(IdentityFee::::weight_to_fee(&50), 50); + assert_eq!( + IdentityFee::::weight_to_fee(&Weight::max_value()), + Balance::max_value() + ); } #[test] fn constant_fee_works() { use crate::traits::ConstU128; - assert_eq!(ConstantMultiplier::>::calc(&0), 0); - assert_eq!(ConstantMultiplier::>::calc(&50), 500); - assert_eq!(ConstantMultiplier::>::calc(&16), 16384); - assert_eq!(ConstantMultiplier::>::calc(&2), u128::MAX); + assert_eq!(ConstantMultiplier::>::weight_to_fee(&0), 0); + assert_eq!(ConstantMultiplier::>::weight_to_fee(&50), 500); + assert_eq!(ConstantMultiplier::>::weight_to_fee(&16), 16384); + assert_eq!( + ConstantMultiplier::>::weight_to_fee(&2), + u128::MAX + ); } } diff --git a/frame/transaction-payment/Cargo.toml b/frame/transaction-payment/Cargo.toml index 519433a8ce0a8..51aeeabe99db8 100644 --- a/frame/transaction-payment/Cargo.toml +++ b/frame/transaction-payment/Cargo.toml @@ -18,7 +18,6 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = ] } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", optional = true } -smallvec = "1.8.0" frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } diff --git a/frame/transaction-payment/asset-tx-payment/Cargo.toml b/frame/transaction-payment/asset-tx-payment/Cargo.toml index fb000dbfdb103..2d4da250212f2 100644 --- a/frame/transaction-payment/asset-tx-payment/Cargo.toml +++ b/frame/transaction-payment/asset-tx-payment/Cargo.toml @@ -29,7 +29,6 @@ scale-info = { version = "2.1.1", default-features = false, features = ["derive" serde = { version = "1.0.136", optional = true } [dev-dependencies] -smallvec = "1.8.0" serde_json = "1.0.79" sp-storage = { version = "6.0.0", default-features = false, path = "../../../primitives/storage" } diff --git a/frame/transaction-payment/asset-tx-payment/src/tests.rs b/frame/transaction-payment/asset-tx-payment/src/tests.rs index d72a288ac7a33..5b1fa157c3f10 100644 --- a/frame/transaction-payment/asset-tx-payment/src/tests.rs +++ b/frame/transaction-payment/asset-tx-payment/src/tests.rs @@ -21,22 +21,17 @@ use frame_support::{ pallet_prelude::*, parameter_types, traits::{fungibles::Mutate, ConstU32, ConstU64, ConstU8, FindAuthor}, - weights::{ - DispatchClass, DispatchInfo, PostDispatchInfo, Weight, WeightToFeeCoefficient, - WeightToFeeCoefficients, WeightToFeePolynomial, - }, + weights::{DispatchClass, DispatchInfo, PostDispatchInfo, Weight, WeightToFee as WeightToFeeT}, ConsensusEngineId, }; use frame_system as system; use frame_system::EnsureRoot; use pallet_balances::Call as BalancesCall; use pallet_transaction_payment::CurrencyAdapter; -use smallvec::smallvec; use sp_core::H256; use sp_runtime::{ testing::Header, - traits::{BlakeTwo256, ConvertInto, IdentityLookup, StaticLookup}, - Perbill, + traits::{BlakeTwo256, ConvertInto, IdentityLookup, SaturatedConversion, StaticLookup}, }; use std::cell::RefCell; @@ -83,8 +78,8 @@ impl Get for BlockWeights { } parameter_types! { - pub static TransactionByteFee: u64 = 1; pub static WeightToFee: u64 = 1; + pub static TransactionByteFee: u64 = 1; } impl frame_system::Config for Runtime { @@ -130,23 +125,27 @@ impl pallet_balances::Config for Runtime { type ReserveIdentifier = [u8; 8]; } -impl WeightToFeePolynomial for WeightToFee { +impl WeightToFeeT for WeightToFee { + type Balance = u64; + + fn weight_to_fee(weight: &Weight) -> Self::Balance { + Self::Balance::saturated_from(*weight).saturating_mul(WEIGHT_TO_FEE.with(|v| *v.borrow())) + } +} + +impl WeightToFeeT for TransactionByteFee { type Balance = u64; - fn polynomial() -> WeightToFeeCoefficients { - smallvec![WeightToFeeCoefficient { - degree: 1, - coeff_frac: Perbill::zero(), - coeff_integer: WEIGHT_TO_FEE.with(|v| *v.borrow()), - negative: false, - }] + fn weight_to_fee(weight: &Weight) -> Self::Balance { + Self::Balance::saturated_from(*weight) + .saturating_mul(TRANSACTION_BYTE_FEE.with(|v| *v.borrow())) } } impl pallet_transaction_payment::Config for Runtime { type OnChargeTransaction = CurrencyAdapter; type WeightToFee = WeightToFee; - type LengthToFee = WeightToFee; + type LengthToFee = TransactionByteFee; type FeeMultiplierUpdate = (); type OperationalFeeMultiplier = ConstU8<5>; } diff --git a/frame/transaction-payment/src/lib.rs b/frame/transaction-payment/src/lib.rs index 45c8b8f479c9f..d44f8b1b894e1 100644 --- a/frame/transaction-payment/src/lib.rs +++ b/frame/transaction-payment/src/lib.rs @@ -66,8 +66,7 @@ use frame_support::{ dispatch::DispatchResult, traits::{EstimateCallFee, Get}, weights::{ - DispatchClass, DispatchInfo, GetDispatchInfo, Pays, PostDispatchInfo, Weight, - WeightToFeeCoefficient, WeightToFeePolynomial, + DispatchClass, DispatchInfo, GetDispatchInfo, Pays, PostDispatchInfo, Weight, WeightToFee, }, }; @@ -283,30 +282,15 @@ pub mod pallet { type OperationalFeeMultiplier: Get; /// Convert a weight value into a deductible fee based on the currency type. - type WeightToFee: WeightToFeePolynomial>; + type WeightToFee: WeightToFee>; /// Convert a length value into a deductible fee based on the currency type. - type LengthToFee: WeightToFeePolynomial>; + type LengthToFee: WeightToFee>; /// Update the multiplier of the next block, based on the previous block's weight. type FeeMultiplierUpdate: MultiplierUpdate; } - #[pallet::extra_constants] - impl Pallet { - #[pallet::constant_name(WeightToFee)] - /// The polynomial that is applied in order to derive fee from weight. - fn weight_to_fee_polynomial() -> Vec>> { - T::WeightToFee::polynomial().to_vec() - } - - /// The polynomial that is applied in order to derive fee from length. - #[pallet::constant_name(LengthToFee)] - fn length_to_fee_polynomial() -> Vec>> { - T::LengthToFee::polynomial().to_vec() - } - } - #[pallet::type_value] pub fn NextFeeMultiplierOnEmpty() -> Multiplier { Multiplier::saturating_from_integer(1) @@ -533,14 +517,14 @@ where } fn length_to_fee(length: u32) -> BalanceOf { - T::LengthToFee::calc(&(length as Weight)) + T::LengthToFee::weight_to_fee(&(length as Weight)) } fn weight_to_fee(weight: Weight) -> BalanceOf { // cap the weight to the maximum defined in runtime, otherwise it will be the // `Bounded` maximum of its data type, which is not desired. let capped_weight = weight.min(T::BlockWeights::get().max_block); - T::WeightToFee::calc(&capped_weight) + T::WeightToFee::weight_to_fee(&capped_weight) } } @@ -776,14 +760,12 @@ mod tests { use std::cell::RefCell; use codec::Encode; - use smallvec::smallvec; use sp_core::H256; use sp_runtime::{ testing::{Header, TestXt}, traits::{BlakeTwo256, IdentityLookup, One}, transaction_validity::InvalidTransaction, - Perbill, }; use frame_support::{ @@ -791,7 +773,7 @@ mod tests { traits::{ConstU32, ConstU64, Currency, Imbalance, OnUnbalanced}, weights::{ DispatchClass, DispatchInfo, GetDispatchInfo, PostDispatchInfo, Weight, - WeightToFeeCoefficient, WeightToFeeCoefficients, WeightToFeePolynomial, + WeightToFee as WeightToFeeT, }, }; use frame_system as system; @@ -879,29 +861,21 @@ mod tests { type WeightInfo = (); } - impl WeightToFeePolynomial for WeightToFee { + impl WeightToFeeT for WeightToFee { type Balance = u64; - fn polynomial() -> WeightToFeeCoefficients { - smallvec![WeightToFeeCoefficient { - degree: 1, - coeff_frac: Perbill::zero(), - coeff_integer: WEIGHT_TO_FEE.with(|v| *v.borrow()), - negative: false, - }] + fn weight_to_fee(weight: &Weight) -> Self::Balance { + Self::Balance::saturated_from(*weight) + .saturating_mul(WEIGHT_TO_FEE.with(|v| *v.borrow())) } } - impl WeightToFeePolynomial for TransactionByteFee { + impl WeightToFeeT for TransactionByteFee { type Balance = u64; - fn polynomial() -> WeightToFeeCoefficients { - smallvec![WeightToFeeCoefficient { - degree: 1, - coeff_frac: Perbill::zero(), - coeff_integer: TRANSACTION_BYTE_FEE.with(|v| *v.borrow()), - negative: false, - }] + fn weight_to_fee(weight: &Weight) -> Self::Balance { + Self::Balance::saturated_from(*weight) + .saturating_mul(TRANSACTION_BYTE_FEE.with(|v| *v.borrow())) } } From d75fb2b19ef499091001e93ad910719a07530135 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Wed, 25 May 2022 09:40:37 +0200 Subject: [PATCH 270/484] Fix node lookup on fork-tree after a warp-sync (#11476) * Fix node lookup on fork-tree after a warp-sync After a warp-sync, the error condition was triggered by the absence of the parent node of the first imported block. The previous lookup implementation was traversing the tree using a recursive **post-order** DFS, this technique doesn't trigger the issue. In the last iterative implementation we were using a BFS instead. * Added internal doc warning * Small optimization * Specify post-order DFS in the comment --- utils/fork-tree/src/lib.rs | 68 +++++++++++++++++++++++++++----------- 1 file changed, 49 insertions(+), 19 deletions(-) diff --git a/utils/fork-tree/src/lib.rs b/utils/fork-tree/src/lib.rs index 45957f4390532..6f987fa798461 100644 --- a/utils/fork-tree/src/lib.rs +++ b/utils/fork-tree/src/lib.rs @@ -289,6 +289,11 @@ where /// index in the traverse path goes last. If a node is found that matches the predicate /// the returned path should always contain at least one index, otherwise `None` is /// returned. + // WARNING: some users of this method (i.e. consensus epoch changes tree) currently silently + // rely on a **post-order DFS** traversal. If we are using instead a top-down traversal method + // then the `is_descendent_of` closure, when used after a warp-sync, will end up querying the + // backend for a block (the one corresponding to the root) that is not present and thus will + // return a wrong result. Here we are implementing a post-order DFS. pub fn find_node_index_where( &self, hash: &H, @@ -301,30 +306,52 @@ where F: Fn(&H, &H) -> Result, P: Fn(&V) -> bool, { - let mut path = vec![]; - let mut children = &self.roots; - let mut i = 0; - let mut best_depth = 0; - - while i < children.len() { - let node = &children[i]; - if node.number < *number && is_descendent_of(&node.hash, hash)? { - path.push(i); - if predicate(&node.data) { - best_depth = path.len(); + let mut stack = vec![]; + let mut root_idx = 0; + let mut found = false; + let mut is_descendent = false; + + while root_idx < self.roots.len() { + if *number <= self.roots[root_idx].number { + root_idx += 1; + continue + } + // The second element in the stack tuple tracks what is the **next** children + // index to search into. If we find an ancestor then we stop searching into + // alternative branches and we focus on the current path up to the root. + stack.push((&self.roots[root_idx], 0)); + while let Some((node, i)) = stack.pop() { + if i < node.children.len() && !is_descendent { + stack.push((node, i + 1)); + if node.children[i].number < *number { + stack.push((&node.children[i], 0)); + } + } else if is_descendent || is_descendent_of(&node.hash, hash)? { + is_descendent = true; + if predicate(&node.data) { + found = true; + break + } } - i = 0; - children = &node.children; - } else { - i += 1; } + + // If the element we are looking for is a descendent of the current root + // then we can stop the search. + if is_descendent { + break + } + root_idx += 1; } - Ok(if best_depth == 0 { - None - } else { - path.truncate(best_depth); + Ok(if found { + // The path is the root index followed by the indices of all the children + // we were processing when we found the element (remember the stack + // contains the index of the **next** children to process). + let path: Vec<_> = + std::iter::once(root_idx).chain(stack.iter().map(|(_, i)| *i - 1)).collect(); Some(path) + } else { + None }) } @@ -1468,6 +1495,9 @@ mod test { fn find_node_works() { let (tree, is_descendent_of) = test_fork_tree(); + let node = tree.find_node_where(&"B", &2, &is_descendent_of, &|_| true).unwrap().unwrap(); + assert_eq!((node.hash, node.number), ("A", 1)); + let node = tree.find_node_where(&"D", &4, &is_descendent_of, &|_| true).unwrap().unwrap(); assert_eq!((node.hash, node.number), ("C", 3)); From 9b4c93b57eb2bdbc9ca5f9fbcce4427d4aa71357 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Wed, 25 May 2022 22:24:33 +0200 Subject: [PATCH 271/484] Test for the fork-tree post-order DFS traversal requirement (#11521) * Test for the fork-tree post-order DFS traversal requirement * Fixed typo --- utils/fork-tree/src/lib.rs | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/utils/fork-tree/src/lib.rs b/utils/fork-tree/src/lib.rs index 6f987fa798461..ab56ecb6360b3 100644 --- a/utils/fork-tree/src/lib.rs +++ b/utils/fork-tree/src/lib.rs @@ -1441,20 +1441,34 @@ mod test { .unwrap() .unwrap(); assert_eq!(path, [0, 1, 0, 0, 0]); + + // Test for the post-order DFS requirement as specified by the `find_node_index_where` + // comment. Once (and if) post-order traversal requirement is removed, then this test + // can be removed as well. In practice this test should fail with a pre-order DFS. + let is_descendent_of_for_post_order = |parent: &&str, child: &&str| { + if *parent == "A" { + Err(TestError) + } else { + is_descendent_of(parent, child) + } + }; + let path = tree + .find_node_index_where(&"N", &6, &is_descendent_of_for_post_order, &|_| true) + .unwrap() + .unwrap(); + assert_eq!(path, [0, 1, 0, 0, 0]); } #[test] fn find_node_index_with_predicate_works() { - fn is_descendent_of(parent: &char, child: &char) -> Result { - match *parent { - 'A' => Ok(['B', 'C', 'D', 'E', 'F'].contains(child)), - 'B' => Ok(['C', 'D'].contains(child)), - 'C' => Ok(['D'].contains(child)), - 'E' => Ok(['F'].contains(child)), - 'D' | 'F' => Ok(false), - _ => unreachable!(), - } - } + let is_descendent_of = |parent: &char, child: &char| match *parent { + 'A' => Ok(['B', 'C', 'D', 'E', 'F'].contains(child)), + 'B' => Ok(['C', 'D'].contains(child)), + 'C' => Ok(['D'].contains(child)), + 'E' => Ok(['F'].contains(child)), + 'D' | 'F' => Ok(false), + _ => Err(TestError), + }; // A(t) --- B(f) --- C(t) --- D(f) // \-- E(t) --- F(f) From 0f37a1574259636cdaff9432a344bf439fe58afd Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Thu, 26 May 2022 15:28:32 -0400 Subject: [PATCH 272/484] Storage Layer for All FRAME Extrinsics (#11431) * add new trait * implement DispatchableWithStorageLayer * at least one transactional * all dispatch is at least transactional * storage_layer api * add test * storage layer tests * deprecate transactional tag * i guess no reason to deprecate * remove transactional from batch_all * update tests * extend trait * cargo run --quiet --profile=production --features runtime-benchmarks --manifest-path bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_balances --extrinsic=* --execution=wasm --wasm-execution=compiled --output=./frame/balances/src/weights.rs --template=./.maintain/frame-weight-template.hbs * cargo run --quiet --profile=production --features runtime-benchmarks --manifest-path bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_balances --extrinsic=* --execution=wasm --wasm-execution=compiled --output=./frame/balances/src/weights.rs --template=./.maintain/frame-weight-template.hbs * cargo run --quiet --profile=production --features runtime-benchmarks --manifest-path bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_staking --extrinsic=* --execution=wasm --wasm-execution=compiled --output=./frame/staking/src/weights.rs --template=./.maintain/frame-weight-template.hbs * fix copy paste name * cargo run --quiet --profile=production --features runtime-benchmarks --manifest-path bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_utility --extrinsic=* --execution=wasm --wasm-execution=compiled --output=./frame/utility/src/weights.rs --template=./.maintain/frame-weight-template.hbs * Create run_all_benchmarks.sh * uncomment build * update number of steps and repeats * add skip build * Update run_all_benchmarks.sh * Update run_all_benchmarks.sh * new benchmarks * Update frame/support/src/traits/dispatch.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Update frame/support/src/traits/dispatch.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Update frame/support/test/tests/storage_layers.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Update frame/support/test/tests/storage_layers.rs * weights * Update dispatch.rs * doc link * decl_macro support Co-authored-by: Parity Bot Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- frame/assets/src/weights.rs | 134 +++++---- frame/bags-list/src/weights.rs | 14 +- frame/balances/src/weights.rs | 30 +- frame/benchmarking/src/weights.rs | 50 ++-- frame/bounties/src/weights.rs | 58 ++-- frame/child-bounties/src/weights.rs | 34 +-- frame/collective/src/weights.rs | 126 ++++---- frame/conviction-voting/src/weights.rs | 46 +-- frame/democracy/src/weights.rs | 182 ++++++------ .../src/weights.rs | 114 +++---- frame/elections-phragmen/src/weights.rs | 102 ++++--- frame/gilt/src/weights.rs | 58 ++-- frame/identity/src/weights.rs | 190 ++++++------ frame/im-online/src/weights.rs | 10 +- frame/indices/src/weights.rs | 22 +- frame/lottery/src/weights.rs | 34 +-- frame/membership/src/weights.rs | 54 ++-- frame/multisig/src/weights.rs | 78 ++--- frame/nomination-pools/src/weights.rs | 86 +++--- frame/preimage/src/weights.rs | 38 +-- frame/proxy/src/weights.rs | 118 ++++---- frame/recovery/src/weights.rs | 74 ++--- frame/referenda/src/weights.rs | 110 +++---- frame/remark/src/weights.rs | 6 +- frame/scheduler/src/weights.rs | 146 ++++----- frame/session/src/weights.rs | 10 +- frame/staking/src/weights.rs | 214 ++++++------- frame/state-trie-migration/src/weights.rs | 42 +-- .../src/construct_runtime/expand/call.rs | 13 + frame/support/procedural/src/lib.rs | 12 +- .../procedural/src/pallet/expand/call.rs | 8 +- frame/support/src/dispatch.rs | 9 +- frame/support/src/storage/mod.rs | 4 +- frame/support/src/storage/transactional.rs | 63 ++++ frame/support/src/traits.rs | 4 +- frame/support/src/traits/dispatch.rs | 17 ++ frame/support/src/weights/block_weights.rs | 20 +- .../support/src/weights/extrinsic_weights.rs | 20 +- frame/support/test/tests/pallet.rs | 19 +- frame/support/test/tests/pallet_instance.rs | 7 +- frame/support/test/tests/storage_layers.rs | 280 ++++++++++++++++++ frame/system/src/weights.rs | 30 +- frame/timestamp/src/weights.rs | 10 +- frame/tips/src/weights.rs | 66 ++--- frame/transaction-storage/src/weights.rs | 10 +- frame/treasury/src/weights.rs | 34 +-- frame/uniques/src/weights.rs | 122 ++++---- frame/utility/src/lib.rs | 2 - frame/utility/src/tests.rs | 8 +- frame/utility/src/weights.rs | 38 +-- frame/vesting/src/weights.rs | 102 +++---- frame/whitelist/src/weights.rs | 18 +- 52 files changed, 1741 insertions(+), 1355 deletions(-) create mode 100644 frame/support/test/tests/storage_layers.rs diff --git a/frame/assets/src/weights.rs b/frame/assets/src/weights.rs index 453df22902ba2..e8f1184cf570f 100644 --- a/frame/assets/src/weights.rs +++ b/frame/assets/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_assets //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -74,13 +74,13 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: Assets Asset (r:1 w:1) fn create() -> Weight { - (23_081_000 as Weight) + (27_167_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:1) fn force_create() -> Weight { - (12_782_000 as Weight) + (15_473_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -91,12 +91,12 @@ impl WeightInfo for SubstrateWeight { // Storage: Assets Approvals (r:501 w:500) fn destroy(c: u32, s: u32, a: u32, ) -> Weight { (0 as Weight) - // Standard Error: 36_000 - .saturating_add((15_327_000 as Weight).saturating_mul(c as Weight)) - // Standard Error: 36_000 - .saturating_add((17_817_000 as Weight).saturating_mul(s as Weight)) - // Standard Error: 362_000 - .saturating_add((16_692_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 37_000 + .saturating_add((17_145_000 as Weight).saturating_mul(c as Weight)) + // Standard Error: 37_000 + .saturating_add((19_333_000 as Weight).saturating_mul(s as Weight)) + // Standard Error: 375_000 + .saturating_add((17_046_000 as Weight).saturating_mul(a as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(c as Weight))) .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(s as Weight))) @@ -109,14 +109,14 @@ impl WeightInfo for SubstrateWeight { // Storage: Assets Asset (r:1 w:1) // Storage: Assets Account (r:1 w:1) fn mint() -> Weight { - (25_993_000 as Weight) + (30_819_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Assets Asset (r:1 w:1) // Storage: Assets Account (r:1 w:1) fn burn() -> Weight { - (30_795_000 as Weight) + (35_212_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -124,7 +124,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Assets Account (r:2 w:2) // Storage: System Account (r:1 w:1) fn transfer() -> Weight { - (44_054_000 as Weight) + (47_401_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -132,7 +132,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Assets Account (r:2 w:2) // Storage: System Account (r:1 w:1) fn transfer_keep_alive() -> Weight { - (36_948_000 as Weight) + (42_300_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -140,87 +140,93 @@ impl WeightInfo for SubstrateWeight { // Storage: Assets Account (r:2 w:2) // Storage: System Account (r:1 w:1) fn force_transfer() -> Weight { - (44_446_000 as Weight) + (47_946_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } // Storage: Assets Asset (r:1 w:0) // Storage: Assets Account (r:1 w:1) fn freeze() -> Weight { - (18_381_000 as Weight) + (21_670_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:0) // Storage: Assets Account (r:1 w:1) fn thaw() -> Weight { - (18_215_000 as Weight) + (21_503_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:1) fn freeze_asset() -> Weight { - (14_885_000 as Weight) + (18_158_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:1) fn thaw_asset() -> Weight { - (14_834_000 as Weight) + (18_525_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:1) // Storage: Assets Metadata (r:1 w:0) fn transfer_ownership() -> Weight { - (16_033_000 as Weight) + (19_858_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:1) fn set_team() -> Weight { - (14_344_000 as Weight) + (18_045_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:0) // Storage: Assets Metadata (r:1 w:1) - fn set_metadata(_n: u32, _s: u32, ) -> Weight { - (27_805_000 as Weight) + fn set_metadata(n: u32, s: u32, ) -> Weight { + (32_395_000 as Weight) + // Standard Error: 0 + .saturating_add((5_000 as Weight).saturating_mul(n as Weight)) + // Standard Error: 0 + .saturating_add((2_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:0) // Storage: Assets Metadata (r:1 w:1) fn clear_metadata() -> Weight { - (28_466_000 as Weight) + (32_893_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:0) // Storage: Assets Metadata (r:1 w:1) - fn force_set_metadata(_n: u32, _s: u32, ) -> Weight { - (15_604_000 as Weight) + fn force_set_metadata(_n: u32, s: u32, ) -> Weight { + (19_586_000 as Weight) + // Standard Error: 0 + .saturating_add((1_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:0) // Storage: Assets Metadata (r:1 w:1) fn force_clear_metadata() -> Weight { - (28_278_000 as Weight) + (32_478_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:1) fn force_asset_status() -> Weight { - (13_556_000 as Weight) + (17_143_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:1) // Storage: Assets Approvals (r:1 w:1) fn approve_transfer() -> Weight { - (31_252_000 as Weight) + (36_389_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -229,21 +235,21 @@ impl WeightInfo for SubstrateWeight { // Storage: Assets Account (r:2 w:2) // Storage: System Account (r:1 w:1) fn transfer_approved() -> Weight { - (55_281_000 as Weight) + (61_854_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } // Storage: Assets Asset (r:1 w:1) // Storage: Assets Approvals (r:1 w:1) fn cancel_approval() -> Weight { - (30_784_000 as Weight) + (36_759_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Assets Asset (r:1 w:1) // Storage: Assets Approvals (r:1 w:1) fn force_cancel_approval() -> Weight { - (32_011_000 as Weight) + (37_753_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -253,13 +259,13 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { // Storage: Assets Asset (r:1 w:1) fn create() -> Weight { - (23_081_000 as Weight) + (27_167_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:1) fn force_create() -> Weight { - (12_782_000 as Weight) + (15_473_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -270,12 +276,12 @@ impl WeightInfo for () { // Storage: Assets Approvals (r:501 w:500) fn destroy(c: u32, s: u32, a: u32, ) -> Weight { (0 as Weight) - // Standard Error: 36_000 - .saturating_add((15_327_000 as Weight).saturating_mul(c as Weight)) - // Standard Error: 36_000 - .saturating_add((17_817_000 as Weight).saturating_mul(s as Weight)) - // Standard Error: 362_000 - .saturating_add((16_692_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 37_000 + .saturating_add((17_145_000 as Weight).saturating_mul(c as Weight)) + // Standard Error: 37_000 + .saturating_add((19_333_000 as Weight).saturating_mul(s as Weight)) + // Standard Error: 375_000 + .saturating_add((17_046_000 as Weight).saturating_mul(a as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().reads((2 as Weight).saturating_mul(c as Weight))) .saturating_add(RocksDbWeight::get().reads((2 as Weight).saturating_mul(s as Weight))) @@ -288,14 +294,14 @@ impl WeightInfo for () { // Storage: Assets Asset (r:1 w:1) // Storage: Assets Account (r:1 w:1) fn mint() -> Weight { - (25_993_000 as Weight) + (30_819_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Assets Asset (r:1 w:1) // Storage: Assets Account (r:1 w:1) fn burn() -> Weight { - (30_795_000 as Weight) + (35_212_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -303,7 +309,7 @@ impl WeightInfo for () { // Storage: Assets Account (r:2 w:2) // Storage: System Account (r:1 w:1) fn transfer() -> Weight { - (44_054_000 as Weight) + (47_401_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } @@ -311,7 +317,7 @@ impl WeightInfo for () { // Storage: Assets Account (r:2 w:2) // Storage: System Account (r:1 w:1) fn transfer_keep_alive() -> Weight { - (36_948_000 as Weight) + (42_300_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } @@ -319,87 +325,93 @@ impl WeightInfo for () { // Storage: Assets Account (r:2 w:2) // Storage: System Account (r:1 w:1) fn force_transfer() -> Weight { - (44_446_000 as Weight) + (47_946_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } // Storage: Assets Asset (r:1 w:0) // Storage: Assets Account (r:1 w:1) fn freeze() -> Weight { - (18_381_000 as Weight) + (21_670_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:0) // Storage: Assets Account (r:1 w:1) fn thaw() -> Weight { - (18_215_000 as Weight) + (21_503_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:1) fn freeze_asset() -> Weight { - (14_885_000 as Weight) + (18_158_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:1) fn thaw_asset() -> Weight { - (14_834_000 as Weight) + (18_525_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:1) // Storage: Assets Metadata (r:1 w:0) fn transfer_ownership() -> Weight { - (16_033_000 as Weight) + (19_858_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:1) fn set_team() -> Weight { - (14_344_000 as Weight) + (18_045_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:0) // Storage: Assets Metadata (r:1 w:1) - fn set_metadata(_n: u32, _s: u32, ) -> Weight { - (27_805_000 as Weight) + fn set_metadata(n: u32, s: u32, ) -> Weight { + (32_395_000 as Weight) + // Standard Error: 0 + .saturating_add((5_000 as Weight).saturating_mul(n as Weight)) + // Standard Error: 0 + .saturating_add((2_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:0) // Storage: Assets Metadata (r:1 w:1) fn clear_metadata() -> Weight { - (28_466_000 as Weight) + (32_893_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:0) // Storage: Assets Metadata (r:1 w:1) - fn force_set_metadata(_n: u32, _s: u32, ) -> Weight { - (15_604_000 as Weight) + fn force_set_metadata(_n: u32, s: u32, ) -> Weight { + (19_586_000 as Weight) + // Standard Error: 0 + .saturating_add((1_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:0) // Storage: Assets Metadata (r:1 w:1) fn force_clear_metadata() -> Weight { - (28_278_000 as Weight) + (32_478_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:1) fn force_asset_status() -> Weight { - (13_556_000 as Weight) + (17_143_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Assets Asset (r:1 w:1) // Storage: Assets Approvals (r:1 w:1) fn approve_transfer() -> Weight { - (31_252_000 as Weight) + (36_389_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -408,21 +420,21 @@ impl WeightInfo for () { // Storage: Assets Account (r:2 w:2) // Storage: System Account (r:1 w:1) fn transfer_approved() -> Weight { - (55_281_000 as Weight) + (61_854_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } // Storage: Assets Asset (r:1 w:1) // Storage: Assets Approvals (r:1 w:1) fn cancel_approval() -> Weight { - (30_784_000 as Weight) + (36_759_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Assets Asset (r:1 w:1) // Storage: Assets Approvals (r:1 w:1) fn force_cancel_approval() -> Weight { - (32_011_000 as Weight) + (37_753_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } diff --git a/frame/bags-list/src/weights.rs b/frame/bags-list/src/weights.rs index 8a8a14a75218d..a554a9bd4ad1a 100644 --- a/frame/bags-list/src/weights.rs +++ b/frame/bags-list/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_bags_list //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -57,7 +57,7 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList ListNodes (r:4 w:4) // Storage: BagsList ListBags (r:1 w:1) fn rebag_non_terminal() -> Weight { - (51_415_000 as Weight) + (55_040_000 as Weight) .saturating_add(T::DbWeight::get().reads(7 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } @@ -66,7 +66,7 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn rebag_terminal() -> Weight { - (49_459_000 as Weight) + (53_671_000 as Weight) .saturating_add(T::DbWeight::get().reads(7 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } @@ -76,7 +76,7 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList CounterForListNodes (r:1 w:1) // Storage: BagsList ListBags (r:1 w:1) fn put_in_front_of() -> Weight { - (53_682_000 as Weight) + (56_410_000 as Weight) .saturating_add(T::DbWeight::get().reads(10 as Weight)) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } @@ -89,7 +89,7 @@ impl WeightInfo for () { // Storage: BagsList ListNodes (r:4 w:4) // Storage: BagsList ListBags (r:1 w:1) fn rebag_non_terminal() -> Weight { - (51_415_000 as Weight) + (55_040_000 as Weight) .saturating_add(RocksDbWeight::get().reads(7 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } @@ -98,7 +98,7 @@ impl WeightInfo for () { // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn rebag_terminal() -> Weight { - (49_459_000 as Weight) + (53_671_000 as Weight) .saturating_add(RocksDbWeight::get().reads(7 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } @@ -108,7 +108,7 @@ impl WeightInfo for () { // Storage: BagsList CounterForListNodes (r:1 w:1) // Storage: BagsList ListBags (r:1 w:1) fn put_in_front_of() -> Weight { - (53_682_000 as Weight) + (56_410_000 as Weight) .saturating_add(RocksDbWeight::get().reads(10 as Weight)) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } diff --git a/frame/balances/src/weights.rs b/frame/balances/src/weights.rs index dd22e7ca778f8..f612d31997996 100644 --- a/frame/balances/src/weights.rs +++ b/frame/balances/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_balances //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -58,43 +58,43 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) fn transfer() -> Weight { - (35_278_000 as Weight) + (41_860_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: System Account (r:1 w:1) fn transfer_keep_alive() -> Weight { - (27_822_000 as Weight) + (32_760_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: System Account (r:1 w:1) fn set_balance_creating() -> Weight { - (17_943_000 as Weight) + (22_279_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: System Account (r:1 w:1) fn set_balance_killing() -> Weight { - (20_974_000 as Weight) + (25_488_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: System Account (r:2 w:2) fn force_transfer() -> Weight { - (36_078_000 as Weight) + (42_190_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: System Account (r:1 w:1) fn transfer_all() -> Weight { - (32_794_000 as Weight) + (37_789_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: System Account (r:1 w:1) fn force_unreserve() -> Weight { - (16_227_000 as Weight) + (20_056_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -104,43 +104,43 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { // Storage: System Account (r:1 w:1) fn transfer() -> Weight { - (35_278_000 as Weight) + (41_860_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: System Account (r:1 w:1) fn transfer_keep_alive() -> Weight { - (27_822_000 as Weight) + (32_760_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: System Account (r:1 w:1) fn set_balance_creating() -> Weight { - (17_943_000 as Weight) + (22_279_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: System Account (r:1 w:1) fn set_balance_killing() -> Weight { - (20_974_000 as Weight) + (25_488_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: System Account (r:2 w:2) fn force_transfer() -> Weight { - (36_078_000 as Weight) + (42_190_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: System Account (r:1 w:1) fn transfer_all() -> Weight { - (32_794_000 as Weight) + (37_789_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: System Account (r:1 w:1) fn force_unreserve() -> Weight { - (16_227_000 as Weight) + (20_056_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } diff --git a/frame/benchmarking/src/weights.rs b/frame/benchmarking/src/weights.rs index 24643bb391152..8b36601940cf3 100644 --- a/frame/benchmarking/src/weights.rs +++ b/frame/benchmarking/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for frame_benchmarking //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -58,39 +58,37 @@ pub trait WeightInfo { pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { fn addition(_i: u32, ) -> Weight { - (126_000 as Weight) + (103_000 as Weight) } fn subtraction(_i: u32, ) -> Weight { - (121_000 as Weight) + (105_000 as Weight) } fn multiplication(_i: u32, ) -> Weight { - (132_000 as Weight) + (113_000 as Weight) } fn division(_i: u32, ) -> Weight { - (122_000 as Weight) + (102_000 as Weight) } - fn hashing(i: u32, ) -> Weight { - (21_059_079_000 as Weight) - // Standard Error: 117_000 - .saturating_add((1_121_000 as Weight).saturating_mul(i as Weight)) + fn hashing(_i: u32, ) -> Weight { + (20_865_902_000 as Weight) } fn sr25519_verification(i: u32, ) -> Weight { - (425_000 as Weight) - // Standard Error: 7_000 - .saturating_add((47_172_000 as Weight).saturating_mul(i as Weight)) + (319_000 as Weight) + // Standard Error: 8_000 + .saturating_add((47_171_000 as Weight).saturating_mul(i as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn storage_read(i: u32, ) -> Weight { (0 as Weight) // Standard Error: 3_000 - .saturating_add((2_118_000 as Weight).saturating_mul(i as Weight)) + .saturating_add((2_110_000 as Weight).saturating_mul(i as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight))) } // Storage: Skipped Metadata (r:0 w:0) fn storage_write(i: u32, ) -> Weight { (0 as Weight) // Standard Error: 0 - .saturating_add((373_000 as Weight).saturating_mul(i as Weight)) + .saturating_add((372_000 as Weight).saturating_mul(i as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(i as Weight))) } } @@ -98,39 +96,37 @@ impl WeightInfo for SubstrateWeight { // For backwards compatibility and tests impl WeightInfo for () { fn addition(_i: u32, ) -> Weight { - (126_000 as Weight) + (103_000 as Weight) } fn subtraction(_i: u32, ) -> Weight { - (121_000 as Weight) + (105_000 as Weight) } fn multiplication(_i: u32, ) -> Weight { - (132_000 as Weight) + (113_000 as Weight) } fn division(_i: u32, ) -> Weight { - (122_000 as Weight) + (102_000 as Weight) } - fn hashing(i: u32, ) -> Weight { - (21_059_079_000 as Weight) - // Standard Error: 117_000 - .saturating_add((1_121_000 as Weight).saturating_mul(i as Weight)) + fn hashing(_i: u32, ) -> Weight { + (20_865_902_000 as Weight) } fn sr25519_verification(i: u32, ) -> Weight { - (425_000 as Weight) - // Standard Error: 7_000 - .saturating_add((47_172_000 as Weight).saturating_mul(i as Weight)) + (319_000 as Weight) + // Standard Error: 8_000 + .saturating_add((47_171_000 as Weight).saturating_mul(i as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn storage_read(i: u32, ) -> Weight { (0 as Weight) // Standard Error: 3_000 - .saturating_add((2_118_000 as Weight).saturating_mul(i as Weight)) + .saturating_add((2_110_000 as Weight).saturating_mul(i as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(i as Weight))) } // Storage: Skipped Metadata (r:0 w:0) fn storage_write(i: u32, ) -> Weight { (0 as Weight) // Standard Error: 0 - .saturating_add((373_000 as Weight).saturating_mul(i as Weight)) + .saturating_add((372_000 as Weight).saturating_mul(i as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(i as Weight))) } } diff --git a/frame/bounties/src/weights.rs b/frame/bounties/src/weights.rs index a1019b3e5c52c..d3e054cfc6351 100644 --- a/frame/bounties/src/weights.rs +++ b/frame/bounties/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_bounties //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -64,44 +64,42 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) // Storage: Bounties BountyDescriptions (r:0 w:1) // Storage: Bounties Bounties (r:0 w:1) - fn propose_bounty(d: u32, ) -> Weight { - (25_277_000 as Weight) - // Standard Error: 0 - .saturating_add((1_000 as Weight).saturating_mul(d as Weight)) + fn propose_bounty(_d: u32, ) -> Weight { + (28_903_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } // Storage: Bounties Bounties (r:1 w:1) // Storage: Bounties BountyApprovals (r:1 w:1) fn approve_bounty() -> Weight { - (7_646_000 as Weight) + (10_997_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Bounties Bounties (r:1 w:1) fn propose_curator() -> Weight { - (5_712_000 as Weight) + (8_967_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Bounties Bounties (r:1 w:1) // Storage: System Account (r:1 w:1) fn unassign_curator() -> Weight { - (25_220_000 as Weight) + (28_665_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Bounties Bounties (r:1 w:1) // Storage: System Account (r:1 w:1) fn accept_curator() -> Weight { - (21_985_000 as Weight) + (25_141_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Bounties Bounties (r:1 w:1) // Storage: ChildBounties ParentChildBounties (r:1 w:0) fn award_bounty() -> Weight { - (18_341_000 as Weight) + (21_295_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -110,7 +108,7 @@ impl WeightInfo for SubstrateWeight { // Storage: ChildBounties ChildrenCuratorFees (r:1 w:1) // Storage: Bounties BountyDescriptions (r:0 w:1) fn claim_bounty() -> Weight { - (63_146_000 as Weight) + (67_951_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } @@ -119,7 +117,7 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) // Storage: Bounties BountyDescriptions (r:0 w:1) fn close_bounty_proposed() -> Weight { - (29_362_000 as Weight) + (33_654_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -128,13 +126,13 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:2 w:2) // Storage: Bounties BountyDescriptions (r:0 w:1) fn close_bounty_active() -> Weight { - (46_519_000 as Weight) + (50_582_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } // Storage: Bounties Bounties (r:1 w:1) fn extend_bounty_expiry() -> Weight { - (15_363_000 as Weight) + (18_322_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -143,8 +141,8 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:2 w:2) fn spend_funds(b: u32, ) -> Weight { (0 as Weight) - // Standard Error: 18_000 - .saturating_add((29_482_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 17_000 + .saturating_add((29_233_000 as Weight).saturating_mul(b as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(b as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -158,44 +156,42 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) // Storage: Bounties BountyDescriptions (r:0 w:1) // Storage: Bounties Bounties (r:0 w:1) - fn propose_bounty(d: u32, ) -> Weight { - (25_277_000 as Weight) - // Standard Error: 0 - .saturating_add((1_000 as Weight).saturating_mul(d as Weight)) + fn propose_bounty(_d: u32, ) -> Weight { + (28_903_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } // Storage: Bounties Bounties (r:1 w:1) // Storage: Bounties BountyApprovals (r:1 w:1) fn approve_bounty() -> Weight { - (7_646_000 as Weight) + (10_997_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Bounties Bounties (r:1 w:1) fn propose_curator() -> Weight { - (5_712_000 as Weight) + (8_967_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Bounties Bounties (r:1 w:1) // Storage: System Account (r:1 w:1) fn unassign_curator() -> Weight { - (25_220_000 as Weight) + (28_665_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Bounties Bounties (r:1 w:1) // Storage: System Account (r:1 w:1) fn accept_curator() -> Weight { - (21_985_000 as Weight) + (25_141_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Bounties Bounties (r:1 w:1) // Storage: ChildBounties ParentChildBounties (r:1 w:0) fn award_bounty() -> Weight { - (18_341_000 as Weight) + (21_295_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -204,7 +200,7 @@ impl WeightInfo for () { // Storage: ChildBounties ChildrenCuratorFees (r:1 w:1) // Storage: Bounties BountyDescriptions (r:0 w:1) fn claim_bounty() -> Weight { - (63_146_000 as Weight) + (67_951_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } @@ -213,7 +209,7 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) // Storage: Bounties BountyDescriptions (r:0 w:1) fn close_bounty_proposed() -> Weight { - (29_362_000 as Weight) + (33_654_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -222,13 +218,13 @@ impl WeightInfo for () { // Storage: System Account (r:2 w:2) // Storage: Bounties BountyDescriptions (r:0 w:1) fn close_bounty_active() -> Weight { - (46_519_000 as Weight) + (50_582_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } // Storage: Bounties Bounties (r:1 w:1) fn extend_bounty_expiry() -> Weight { - (15_363_000 as Weight) + (18_322_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -237,8 +233,8 @@ impl WeightInfo for () { // Storage: System Account (r:2 w:2) fn spend_funds(b: u32, ) -> Weight { (0 as Weight) - // Standard Error: 18_000 - .saturating_add((29_482_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 17_000 + .saturating_add((29_233_000 as Weight).saturating_mul(b as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(b as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) diff --git a/frame/child-bounties/src/weights.rs b/frame/child-bounties/src/weights.rs index d11de89bcf5c3..ad08e00149a30 100644 --- a/frame/child-bounties/src/weights.rs +++ b/frame/child-bounties/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_child_bounties //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -64,7 +64,7 @@ impl WeightInfo for SubstrateWeight { // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) // Storage: ChildBounties ChildBounties (r:0 w:1) fn add_child_bounty(d: u32, ) -> Weight { - (45_917_000 as Weight) + (51_064_000 as Weight) // Standard Error: 0 .saturating_add((1_000 as Weight).saturating_mul(d as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) @@ -74,7 +74,7 @@ impl WeightInfo for SubstrateWeight { // Storage: ChildBounties ChildBounties (r:1 w:1) // Storage: ChildBounties ChildrenCuratorFees (r:1 w:1) fn propose_curator() -> Weight { - (11_855_000 as Weight) + (15_286_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -82,7 +82,7 @@ impl WeightInfo for SubstrateWeight { // Storage: ChildBounties ChildBounties (r:1 w:1) // Storage: System Account (r:1 w:1) fn accept_curator() -> Weight { - (24_488_000 as Weight) + (29_929_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -90,14 +90,14 @@ impl WeightInfo for SubstrateWeight { // Storage: Bounties Bounties (r:1 w:0) // Storage: System Account (r:1 w:1) fn unassign_curator() -> Weight { - (28_645_000 as Weight) + (32_449_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Bounties Bounties (r:1 w:0) // Storage: ChildBounties ChildBounties (r:1 w:1) fn award_child_bounty() -> Weight { - (19_760_000 as Weight) + (23_793_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -106,7 +106,7 @@ impl WeightInfo for SubstrateWeight { // Storage: ChildBounties ParentChildBounties (r:1 w:1) // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) fn claim_child_bounty() -> Weight { - (62_194_000 as Weight) + (67_529_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } @@ -117,7 +117,7 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:2 w:2) // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) fn close_child_bounty_added() -> Weight { - (44_136_000 as Weight) + (48_436_000 as Weight) .saturating_add(T::DbWeight::get().reads(6 as Weight)) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } @@ -128,7 +128,7 @@ impl WeightInfo for SubstrateWeight { // Storage: ChildBounties ParentChildBounties (r:1 w:1) // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) fn close_child_bounty_active() -> Weight { - (53_168_000 as Weight) + (58_044_000 as Weight) .saturating_add(T::DbWeight::get().reads(7 as Weight)) .saturating_add(T::DbWeight::get().writes(7 as Weight)) } @@ -143,7 +143,7 @@ impl WeightInfo for () { // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) // Storage: ChildBounties ChildBounties (r:0 w:1) fn add_child_bounty(d: u32, ) -> Weight { - (45_917_000 as Weight) + (51_064_000 as Weight) // Standard Error: 0 .saturating_add((1_000 as Weight).saturating_mul(d as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) @@ -153,7 +153,7 @@ impl WeightInfo for () { // Storage: ChildBounties ChildBounties (r:1 w:1) // Storage: ChildBounties ChildrenCuratorFees (r:1 w:1) fn propose_curator() -> Weight { - (11_855_000 as Weight) + (15_286_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -161,7 +161,7 @@ impl WeightInfo for () { // Storage: ChildBounties ChildBounties (r:1 w:1) // Storage: System Account (r:1 w:1) fn accept_curator() -> Weight { - (24_488_000 as Weight) + (29_929_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -169,14 +169,14 @@ impl WeightInfo for () { // Storage: Bounties Bounties (r:1 w:0) // Storage: System Account (r:1 w:1) fn unassign_curator() -> Weight { - (28_645_000 as Weight) + (32_449_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Bounties Bounties (r:1 w:0) // Storage: ChildBounties ChildBounties (r:1 w:1) fn award_child_bounty() -> Weight { - (19_760_000 as Weight) + (23_793_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -185,7 +185,7 @@ impl WeightInfo for () { // Storage: ChildBounties ParentChildBounties (r:1 w:1) // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) fn claim_child_bounty() -> Weight { - (62_194_000 as Weight) + (67_529_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } @@ -196,7 +196,7 @@ impl WeightInfo for () { // Storage: System Account (r:2 w:2) // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) fn close_child_bounty_added() -> Weight { - (44_136_000 as Weight) + (48_436_000 as Weight) .saturating_add(RocksDbWeight::get().reads(6 as Weight)) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } @@ -207,7 +207,7 @@ impl WeightInfo for () { // Storage: ChildBounties ParentChildBounties (r:1 w:1) // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) fn close_child_bounty_active() -> Weight { - (53_168_000 as Weight) + (58_044_000 as Weight) .saturating_add(RocksDbWeight::get().reads(7 as Weight)) .saturating_add(RocksDbWeight::get().writes(7 as Weight)) } diff --git a/frame/collective/src/weights.rs b/frame/collective/src/weights.rs index 6392f86de6530..2f5c6f590a999 100644 --- a/frame/collective/src/weights.rs +++ b/frame/collective/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_collective //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -65,12 +65,12 @@ impl WeightInfo for SubstrateWeight { // Storage: Council Prime (r:0 w:1) fn set_members(m: u32, n: u32, p: u32, ) -> Weight { (0 as Weight) - // Standard Error: 8_000 - .saturating_add((10_362_000 as Weight).saturating_mul(m as Weight)) - // Standard Error: 8_000 - .saturating_add((56_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 8_000 - .saturating_add((13_187_000 as Weight).saturating_mul(p as Weight)) + // Standard Error: 12_000 + .saturating_add((10_280_000 as Weight).saturating_mul(m as Weight)) + // Standard Error: 12_000 + .saturating_add((126_000 as Weight).saturating_mul(n as Weight)) + // Standard Error: 12_000 + .saturating_add((13_310_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(p as Weight))) .saturating_add(T::DbWeight::get().writes(2 as Weight)) @@ -78,21 +78,21 @@ impl WeightInfo for SubstrateWeight { } // Storage: Council Members (r:1 w:0) fn execute(b: u32, m: u32, ) -> Weight { - (13_571_000 as Weight) + (16_819_000 as Weight) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) // Standard Error: 0 - .saturating_add((31_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((33_000 as Weight).saturating_mul(m as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) } // Storage: Council Members (r:1 w:0) // Storage: Council ProposalOf (r:1 w:0) fn propose_execute(b: u32, m: u32, ) -> Weight { - (15_851_000 as Weight) + (18_849_000 as Weight) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) // Standard Error: 0 - .saturating_add((53_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((56_000 as Weight).saturating_mul(m as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) } // Storage: Council Members (r:1 w:0) @@ -101,22 +101,22 @@ impl WeightInfo for SubstrateWeight { // Storage: Council ProposalCount (r:1 w:1) // Storage: Council Voting (r:0 w:1) fn propose_proposed(b: u32, m: u32, p: u32, ) -> Weight { - (19_593_000 as Weight) + (22_204_000 as Weight) // Standard Error: 0 - .saturating_add((7_000 as Weight).saturating_mul(b as Weight)) + .saturating_add((8_000 as Weight).saturating_mul(b as Weight)) // Standard Error: 1_000 - .saturating_add((40_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((49_000 as Weight).saturating_mul(m as Weight)) // Standard Error: 1_000 - .saturating_add((165_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((180_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } // Storage: Council Members (r:1 w:0) // Storage: Council Voting (r:1 w:1) fn vote(m: u32, ) -> Weight { - (27_221_000 as Weight) + (30_941_000 as Weight) // Standard Error: 2_000 - .saturating_add((82_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((77_000 as Weight).saturating_mul(m as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -125,11 +125,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Council Proposals (r:1 w:1) // Storage: Council ProposalOf (r:0 w:1) fn close_early_disapproved(m: u32, p: u32, ) -> Weight { - (26_295_000 as Weight) + (32_485_000 as Weight) // Standard Error: 1_000 - .saturating_add((55_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((39_000 as Weight).saturating_mul(m as Weight)) // Standard Error: 1_000 - .saturating_add((130_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((124_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -138,13 +138,13 @@ impl WeightInfo for SubstrateWeight { // Storage: Council ProposalOf (r:1 w:1) // Storage: Council Proposals (r:1 w:1) fn close_early_approved(b: u32, m: u32, p: u32, ) -> Weight { - (28_609_000 as Weight) + (33_487_000 as Weight) // Standard Error: 0 - .saturating_add((6_000 as Weight).saturating_mul(b as Weight)) + .saturating_add((5_000 as Weight).saturating_mul(b as Weight)) // Standard Error: 1_000 - .saturating_add((68_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((66_000 as Weight).saturating_mul(m as Weight)) // Standard Error: 1_000 - .saturating_add((161_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((157_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -154,11 +154,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Council Proposals (r:1 w:1) // Storage: Council ProposalOf (r:0 w:1) fn close_disapproved(m: u32, p: u32, ) -> Weight { - (30_497_000 as Weight) + (33_494_000 as Weight) // Standard Error: 1_000 - .saturating_add((48_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((58_000 as Weight).saturating_mul(m as Weight)) // Standard Error: 1_000 - .saturating_add((120_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((124_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -168,13 +168,13 @@ impl WeightInfo for SubstrateWeight { // Storage: Council ProposalOf (r:1 w:1) // Storage: Council Proposals (r:1 w:1) fn close_approved(b: u32, m: u32, p: u32, ) -> Weight { - (31_402_000 as Weight) + (36_566_000 as Weight) // Standard Error: 0 .saturating_add((5_000 as Weight).saturating_mul(b as Weight)) // Standard Error: 1_000 - .saturating_add((73_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((63_000 as Weight).saturating_mul(m as Weight)) // Standard Error: 1_000 - .saturating_add((159_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((158_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -182,9 +182,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Council Voting (r:0 w:1) // Storage: Council ProposalOf (r:0 w:1) fn disapprove_proposal(p: u32, ) -> Weight { - (16_391_000 as Weight) + (20_159_000 as Weight) // Standard Error: 1_000 - .saturating_add((169_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((173_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -198,12 +198,12 @@ impl WeightInfo for () { // Storage: Council Prime (r:0 w:1) fn set_members(m: u32, n: u32, p: u32, ) -> Weight { (0 as Weight) - // Standard Error: 8_000 - .saturating_add((10_362_000 as Weight).saturating_mul(m as Weight)) - // Standard Error: 8_000 - .saturating_add((56_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 8_000 - .saturating_add((13_187_000 as Weight).saturating_mul(p as Weight)) + // Standard Error: 12_000 + .saturating_add((10_280_000 as Weight).saturating_mul(m as Weight)) + // Standard Error: 12_000 + .saturating_add((126_000 as Weight).saturating_mul(n as Weight)) + // Standard Error: 12_000 + .saturating_add((13_310_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(p as Weight))) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) @@ -211,21 +211,21 @@ impl WeightInfo for () { } // Storage: Council Members (r:1 w:0) fn execute(b: u32, m: u32, ) -> Weight { - (13_571_000 as Weight) + (16_819_000 as Weight) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) // Standard Error: 0 - .saturating_add((31_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((33_000 as Weight).saturating_mul(m as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) } // Storage: Council Members (r:1 w:0) // Storage: Council ProposalOf (r:1 w:0) fn propose_execute(b: u32, m: u32, ) -> Weight { - (15_851_000 as Weight) + (18_849_000 as Weight) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) // Standard Error: 0 - .saturating_add((53_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((56_000 as Weight).saturating_mul(m as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) } // Storage: Council Members (r:1 w:0) @@ -234,22 +234,22 @@ impl WeightInfo for () { // Storage: Council ProposalCount (r:1 w:1) // Storage: Council Voting (r:0 w:1) fn propose_proposed(b: u32, m: u32, p: u32, ) -> Weight { - (19_593_000 as Weight) + (22_204_000 as Weight) // Standard Error: 0 - .saturating_add((7_000 as Weight).saturating_mul(b as Weight)) + .saturating_add((8_000 as Weight).saturating_mul(b as Weight)) // Standard Error: 1_000 - .saturating_add((40_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((49_000 as Weight).saturating_mul(m as Weight)) // Standard Error: 1_000 - .saturating_add((165_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((180_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } // Storage: Council Members (r:1 w:0) // Storage: Council Voting (r:1 w:1) fn vote(m: u32, ) -> Weight { - (27_221_000 as Weight) + (30_941_000 as Weight) // Standard Error: 2_000 - .saturating_add((82_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((77_000 as Weight).saturating_mul(m as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -258,11 +258,11 @@ impl WeightInfo for () { // Storage: Council Proposals (r:1 w:1) // Storage: Council ProposalOf (r:0 w:1) fn close_early_disapproved(m: u32, p: u32, ) -> Weight { - (26_295_000 as Weight) + (32_485_000 as Weight) // Standard Error: 1_000 - .saturating_add((55_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((39_000 as Weight).saturating_mul(m as Weight)) // Standard Error: 1_000 - .saturating_add((130_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((124_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -271,13 +271,13 @@ impl WeightInfo for () { // Storage: Council ProposalOf (r:1 w:1) // Storage: Council Proposals (r:1 w:1) fn close_early_approved(b: u32, m: u32, p: u32, ) -> Weight { - (28_609_000 as Weight) + (33_487_000 as Weight) // Standard Error: 0 - .saturating_add((6_000 as Weight).saturating_mul(b as Weight)) + .saturating_add((5_000 as Weight).saturating_mul(b as Weight)) // Standard Error: 1_000 - .saturating_add((68_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((66_000 as Weight).saturating_mul(m as Weight)) // Standard Error: 1_000 - .saturating_add((161_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((157_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -287,11 +287,11 @@ impl WeightInfo for () { // Storage: Council Proposals (r:1 w:1) // Storage: Council ProposalOf (r:0 w:1) fn close_disapproved(m: u32, p: u32, ) -> Weight { - (30_497_000 as Weight) + (33_494_000 as Weight) // Standard Error: 1_000 - .saturating_add((48_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((58_000 as Weight).saturating_mul(m as Weight)) // Standard Error: 1_000 - .saturating_add((120_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((124_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -301,13 +301,13 @@ impl WeightInfo for () { // Storage: Council ProposalOf (r:1 w:1) // Storage: Council Proposals (r:1 w:1) fn close_approved(b: u32, m: u32, p: u32, ) -> Weight { - (31_402_000 as Weight) + (36_566_000 as Weight) // Standard Error: 0 .saturating_add((5_000 as Weight).saturating_mul(b as Weight)) // Standard Error: 1_000 - .saturating_add((73_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((63_000 as Weight).saturating_mul(m as Weight)) // Standard Error: 1_000 - .saturating_add((159_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((158_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -315,9 +315,9 @@ impl WeightInfo for () { // Storage: Council Voting (r:0 w:1) // Storage: Council ProposalOf (r:0 w:1) fn disapprove_proposal(p: u32, ) -> Weight { - (16_391_000 as Weight) + (20_159_000 as Weight) // Standard Error: 1_000 - .saturating_add((169_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((173_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } diff --git a/frame/conviction-voting/src/weights.rs b/frame/conviction-voting/src/weights.rs index 6946317475ae2..330d02755cb8b 100644 --- a/frame/conviction-voting/src/weights.rs +++ b/frame/conviction-voting/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_conviction_voting //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -62,7 +62,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn vote_new() -> Weight { - (145_945_000 as Weight) + (148_804_000 as Weight) .saturating_add(T::DbWeight::get().reads(6 as Weight)) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } @@ -72,7 +72,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn vote_existing() -> Weight { - (316_356_000 as Weight) + (313_333_000 as Weight) .saturating_add(T::DbWeight::get().reads(6 as Weight)) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } @@ -80,14 +80,14 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn remove_vote() -> Weight { - (298_447_000 as Weight) + (300_591_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } // Storage: ConvictionVoting VotingFor (r:1 w:1) // Storage: Referenda ReferendumInfoFor (r:1 w:0) fn remove_other_vote() -> Weight { - (48_705_000 as Weight) + (53_887_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -97,9 +97,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn delegate(r: u32, ) -> Weight { - (51_221_000 as Weight) - // Standard Error: 140_000 - .saturating_add((25_392_000 as Weight).saturating_mul(r as Weight)) + (51_518_000 as Weight) + // Standard Error: 83_000 + .saturating_add((27_235_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(4 as Weight)) @@ -109,9 +109,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn undelegate(r: u32, ) -> Weight { - (34_419_000 as Weight) - // Standard Error: 517_000 - .saturating_add((26_295_000 as Weight).saturating_mul(r as Weight)) + (37_885_000 as Weight) + // Standard Error: 75_000 + .saturating_add((24_395_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(2 as Weight)) @@ -121,7 +121,7 @@ impl WeightInfo for SubstrateWeight { // Storage: ConvictionVoting ClassLocksFor (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn unlock() -> Weight { - (62_414_000 as Weight) + (67_703_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -135,7 +135,7 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn vote_new() -> Weight { - (145_945_000 as Weight) + (148_804_000 as Weight) .saturating_add(RocksDbWeight::get().reads(6 as Weight)) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } @@ -145,7 +145,7 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn vote_existing() -> Weight { - (316_356_000 as Weight) + (313_333_000 as Weight) .saturating_add(RocksDbWeight::get().reads(6 as Weight)) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } @@ -153,14 +153,14 @@ impl WeightInfo for () { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn remove_vote() -> Weight { - (298_447_000 as Weight) + (300_591_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } // Storage: ConvictionVoting VotingFor (r:1 w:1) // Storage: Referenda ReferendumInfoFor (r:1 w:0) fn remove_other_vote() -> Weight { - (48_705_000 as Weight) + (53_887_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -170,9 +170,9 @@ impl WeightInfo for () { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn delegate(r: u32, ) -> Weight { - (51_221_000 as Weight) - // Standard Error: 140_000 - .saturating_add((25_392_000 as Weight).saturating_mul(r as Weight)) + (51_518_000 as Weight) + // Standard Error: 83_000 + .saturating_add((27_235_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) @@ -182,9 +182,9 @@ impl WeightInfo for () { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn undelegate(r: u32, ) -> Weight { - (34_419_000 as Weight) - // Standard Error: 517_000 - .saturating_add((26_295_000 as Weight).saturating_mul(r as Weight)) + (37_885_000 as Weight) + // Standard Error: 75_000 + .saturating_add((24_395_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) @@ -194,7 +194,7 @@ impl WeightInfo for () { // Storage: ConvictionVoting ClassLocksFor (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn unlock() -> Weight { - (62_414_000 as Weight) + (67_703_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } diff --git a/frame/democracy/src/weights.rs b/frame/democracy/src/weights.rs index 50b4f3ab6ba22..45686b43f7152 100644 --- a/frame/democracy/src/weights.rs +++ b/frame/democracy/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_democracy //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -80,15 +80,15 @@ impl WeightInfo for SubstrateWeight { // Storage: Democracy Blacklist (r:1 w:0) // Storage: Democracy DepositOf (r:0 w:1) fn propose() -> Weight { - (43_581_000 as Weight) + (48_328_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: Democracy DepositOf (r:1 w:1) fn second(s: u32, ) -> Weight { - (26_887_000 as Weight) + (30_923_000 as Weight) // Standard Error: 1_000 - .saturating_add((134_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((142_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -96,9 +96,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Democracy VotingOf (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vote_new(r: u32, ) -> Weight { - (35_520_000 as Weight) - // Standard Error: 2_000 - .saturating_add((131_000 as Weight).saturating_mul(r as Weight)) + (40_345_000 as Weight) + // Standard Error: 1_000 + .saturating_add((140_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -106,16 +106,16 @@ impl WeightInfo for SubstrateWeight { // Storage: Democracy VotingOf (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vote_existing(r: u32, ) -> Weight { - (34_793_000 as Weight) + (39_853_000 as Weight) // Standard Error: 1_000 - .saturating_add((138_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((150_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy Cancellations (r:1 w:1) fn emergency_cancel() -> Weight { - (15_401_000 as Weight) + (19_364_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -126,45 +126,45 @@ impl WeightInfo for SubstrateWeight { // Storage: Democracy DepositOf (r:1 w:1) // Storage: System Account (r:1 w:1) fn blacklist(p: u32, ) -> Weight { - (51_233_000 as Weight) - // Standard Error: 3_000 - .saturating_add((189_000 as Weight).saturating_mul(p as Weight)) + (57_708_000 as Weight) + // Standard Error: 4_000 + .saturating_add((192_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } // Storage: Democracy NextExternal (r:1 w:1) // Storage: Democracy Blacklist (r:1 w:0) fn external_propose(v: u32, ) -> Weight { - (7_650_000 as Weight) + (10_714_000 as Weight) // Standard Error: 0 - .saturating_add((30_000 as Weight).saturating_mul(v as Weight)) + .saturating_add((33_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Democracy NextExternal (r:0 w:1) fn external_propose_majority() -> Weight { - (1_341_000 as Weight) + (3_697_000 as Weight) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Democracy NextExternal (r:0 w:1) fn external_propose_default() -> Weight { - (1_360_000 as Weight) + (3_831_000 as Weight) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Democracy NextExternal (r:1 w:1) // Storage: Democracy ReferendumCount (r:1 w:1) // Storage: Democracy ReferendumInfoOf (r:0 w:1) fn fast_track() -> Weight { - (16_021_000 as Weight) + (20_271_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: Democracy NextExternal (r:1 w:1) // Storage: Democracy Blacklist (r:1 w:1) fn veto_external(v: u32, ) -> Weight { - (17_350_000 as Weight) + (21_319_000 as Weight) // Standard Error: 0 - .saturating_add((46_000 as Weight).saturating_mul(v as Weight)) + .saturating_add((52_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -172,23 +172,23 @@ impl WeightInfo for SubstrateWeight { // Storage: Democracy DepositOf (r:1 w:1) // Storage: System Account (r:1 w:1) fn cancel_proposal(p: u32, ) -> Weight { - (39_418_000 as Weight) + (43_960_000 as Weight) // Standard Error: 2_000 - .saturating_add((170_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((184_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: Democracy ReferendumInfoOf (r:0 w:1) fn cancel_referendum() -> Weight { - (10_239_000 as Weight) + (13_475_000 as Weight) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Scheduler Lookup (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn cancel_queued(r: u32, ) -> Weight { - (20_483_000 as Weight) + (24_320_000 as Weight) // Standard Error: 1_000 - .saturating_add((565_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((560_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -196,9 +196,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Democracy ReferendumCount (r:1 w:0) // Storage: Democracy ReferendumInfoOf (r:1 w:0) fn on_initialize_base(r: u32, ) -> Weight { - (3_871_000 as Weight) + (3_428_000 as Weight) // Standard Error: 2_000 - .saturating_add((3_038_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((3_171_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -210,9 +210,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Democracy PublicProps (r:1 w:0) // Storage: Democracy ReferendumInfoOf (r:1 w:0) fn on_initialize_base_with_launch_period(r: u32, ) -> Weight { - (7_597_000 as Weight) + (7_867_000 as Weight) // Standard Error: 2_000 - .saturating_add((3_046_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((3_177_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -221,9 +221,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn delegate(r: u32, ) -> Weight { - (32_554_000 as Weight) - // Standard Error: 3_000 - .saturating_add((3_903_000 as Weight).saturating_mul(r as Weight)) + (37_902_000 as Weight) + // Standard Error: 4_000 + .saturating_add((4_335_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(4 as Weight)) @@ -232,9 +232,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Democracy VotingOf (r:2 w:2) // Storage: Democracy ReferendumInfoOf (r:1 w:1) fn undelegate(r: u32, ) -> Weight { - (15_440_000 as Weight) + (21_272_000 as Weight) // Standard Error: 3_000 - .saturating_add((3_972_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((4_351_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(2 as Weight)) @@ -242,12 +242,12 @@ impl WeightInfo for SubstrateWeight { } // Storage: Democracy PublicProps (r:0 w:1) fn clear_public_proposals() -> Weight { - (1_662_000 as Weight) + (4_913_000 as Weight) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Democracy Preimages (r:1 w:1) fn note_preimage(b: u32, ) -> Weight { - (24_170_000 as Weight) + (27_986_000 as Weight) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) @@ -255,7 +255,7 @@ impl WeightInfo for SubstrateWeight { } // Storage: Democracy Preimages (r:1 w:1) fn note_imminent_preimage(b: u32, ) -> Weight { - (16_470_000 as Weight) + (20_058_000 as Weight) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) @@ -264,7 +264,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Democracy Preimages (r:1 w:1) // Storage: System Account (r:1 w:0) fn reap_preimage(b: u32, ) -> Weight { - (24_547_000 as Weight) + (28_619_000 as Weight) // Standard Error: 0 .saturating_add((1_000 as Weight).saturating_mul(b as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) @@ -274,9 +274,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) fn unlock_remove(r: u32, ) -> Weight { - (22_596_000 as Weight) + (26_619_000 as Weight) // Standard Error: 1_000 - .saturating_add((61_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((56_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -284,27 +284,27 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) fn unlock_set(r: u32, ) -> Weight { - (21_589_000 as Weight) + (25_373_000 as Weight) // Standard Error: 1_000 - .saturating_add((125_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((142_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy VotingOf (r:1 w:1) fn remove_vote(r: u32, ) -> Weight { - (12_120_000 as Weight) + (15_961_000 as Weight) // Standard Error: 1_000 - .saturating_add((106_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((115_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy VotingOf (r:1 w:1) fn remove_other_vote(r: u32, ) -> Weight { - (12_563_000 as Weight) + (15_992_000 as Weight) // Standard Error: 1_000 - .saturating_add((105_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((113_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -317,15 +317,15 @@ impl WeightInfo for () { // Storage: Democracy Blacklist (r:1 w:0) // Storage: Democracy DepositOf (r:0 w:1) fn propose() -> Weight { - (43_581_000 as Weight) + (48_328_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: Democracy DepositOf (r:1 w:1) fn second(s: u32, ) -> Weight { - (26_887_000 as Weight) + (30_923_000 as Weight) // Standard Error: 1_000 - .saturating_add((134_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((142_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -333,9 +333,9 @@ impl WeightInfo for () { // Storage: Democracy VotingOf (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vote_new(r: u32, ) -> Weight { - (35_520_000 as Weight) - // Standard Error: 2_000 - .saturating_add((131_000 as Weight).saturating_mul(r as Weight)) + (40_345_000 as Weight) + // Standard Error: 1_000 + .saturating_add((140_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -343,16 +343,16 @@ impl WeightInfo for () { // Storage: Democracy VotingOf (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vote_existing(r: u32, ) -> Weight { - (34_793_000 as Weight) + (39_853_000 as Weight) // Standard Error: 1_000 - .saturating_add((138_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((150_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy Cancellations (r:1 w:1) fn emergency_cancel() -> Weight { - (15_401_000 as Weight) + (19_364_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -363,45 +363,45 @@ impl WeightInfo for () { // Storage: Democracy DepositOf (r:1 w:1) // Storage: System Account (r:1 w:1) fn blacklist(p: u32, ) -> Weight { - (51_233_000 as Weight) - // Standard Error: 3_000 - .saturating_add((189_000 as Weight).saturating_mul(p as Weight)) + (57_708_000 as Weight) + // Standard Error: 4_000 + .saturating_add((192_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } // Storage: Democracy NextExternal (r:1 w:1) // Storage: Democracy Blacklist (r:1 w:0) fn external_propose(v: u32, ) -> Weight { - (7_650_000 as Weight) + (10_714_000 as Weight) // Standard Error: 0 - .saturating_add((30_000 as Weight).saturating_mul(v as Weight)) + .saturating_add((33_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Democracy NextExternal (r:0 w:1) fn external_propose_majority() -> Weight { - (1_341_000 as Weight) + (3_697_000 as Weight) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Democracy NextExternal (r:0 w:1) fn external_propose_default() -> Weight { - (1_360_000 as Weight) + (3_831_000 as Weight) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Democracy NextExternal (r:1 w:1) // Storage: Democracy ReferendumCount (r:1 w:1) // Storage: Democracy ReferendumInfoOf (r:0 w:1) fn fast_track() -> Weight { - (16_021_000 as Weight) + (20_271_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: Democracy NextExternal (r:1 w:1) // Storage: Democracy Blacklist (r:1 w:1) fn veto_external(v: u32, ) -> Weight { - (17_350_000 as Weight) + (21_319_000 as Weight) // Standard Error: 0 - .saturating_add((46_000 as Weight).saturating_mul(v as Weight)) + .saturating_add((52_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -409,23 +409,23 @@ impl WeightInfo for () { // Storage: Democracy DepositOf (r:1 w:1) // Storage: System Account (r:1 w:1) fn cancel_proposal(p: u32, ) -> Weight { - (39_418_000 as Weight) + (43_960_000 as Weight) // Standard Error: 2_000 - .saturating_add((170_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((184_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: Democracy ReferendumInfoOf (r:0 w:1) fn cancel_referendum() -> Weight { - (10_239_000 as Weight) + (13_475_000 as Weight) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Scheduler Lookup (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn cancel_queued(r: u32, ) -> Weight { - (20_483_000 as Weight) + (24_320_000 as Weight) // Standard Error: 1_000 - .saturating_add((565_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((560_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -433,9 +433,9 @@ impl WeightInfo for () { // Storage: Democracy ReferendumCount (r:1 w:0) // Storage: Democracy ReferendumInfoOf (r:1 w:0) fn on_initialize_base(r: u32, ) -> Weight { - (3_871_000 as Weight) + (3_428_000 as Weight) // Standard Error: 2_000 - .saturating_add((3_038_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((3_171_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -447,9 +447,9 @@ impl WeightInfo for () { // Storage: Democracy PublicProps (r:1 w:0) // Storage: Democracy ReferendumInfoOf (r:1 w:0) fn on_initialize_base_with_launch_period(r: u32, ) -> Weight { - (7_597_000 as Weight) + (7_867_000 as Weight) // Standard Error: 2_000 - .saturating_add((3_046_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((3_177_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -458,9 +458,9 @@ impl WeightInfo for () { // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn delegate(r: u32, ) -> Weight { - (32_554_000 as Weight) - // Standard Error: 3_000 - .saturating_add((3_903_000 as Weight).saturating_mul(r as Weight)) + (37_902_000 as Weight) + // Standard Error: 4_000 + .saturating_add((4_335_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) @@ -469,9 +469,9 @@ impl WeightInfo for () { // Storage: Democracy VotingOf (r:2 w:2) // Storage: Democracy ReferendumInfoOf (r:1 w:1) fn undelegate(r: u32, ) -> Weight { - (15_440_000 as Weight) + (21_272_000 as Weight) // Standard Error: 3_000 - .saturating_add((3_972_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((4_351_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) @@ -479,12 +479,12 @@ impl WeightInfo for () { } // Storage: Democracy PublicProps (r:0 w:1) fn clear_public_proposals() -> Weight { - (1_662_000 as Weight) + (4_913_000 as Weight) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Democracy Preimages (r:1 w:1) fn note_preimage(b: u32, ) -> Weight { - (24_170_000 as Weight) + (27_986_000 as Weight) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) @@ -492,7 +492,7 @@ impl WeightInfo for () { } // Storage: Democracy Preimages (r:1 w:1) fn note_imminent_preimage(b: u32, ) -> Weight { - (16_470_000 as Weight) + (20_058_000 as Weight) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(b as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) @@ -501,7 +501,7 @@ impl WeightInfo for () { // Storage: Democracy Preimages (r:1 w:1) // Storage: System Account (r:1 w:0) fn reap_preimage(b: u32, ) -> Weight { - (24_547_000 as Weight) + (28_619_000 as Weight) // Standard Error: 0 .saturating_add((1_000 as Weight).saturating_mul(b as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) @@ -511,9 +511,9 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) fn unlock_remove(r: u32, ) -> Weight { - (22_596_000 as Weight) + (26_619_000 as Weight) // Standard Error: 1_000 - .saturating_add((61_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((56_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -521,27 +521,27 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) fn unlock_set(r: u32, ) -> Weight { - (21_589_000 as Weight) + (25_373_000 as Weight) // Standard Error: 1_000 - .saturating_add((125_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((142_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy VotingOf (r:1 w:1) fn remove_vote(r: u32, ) -> Weight { - (12_120_000 as Weight) + (15_961_000 as Weight) // Standard Error: 1_000 - .saturating_add((106_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((115_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Democracy ReferendumInfoOf (r:1 w:1) // Storage: Democracy VotingOf (r:1 w:1) fn remove_other_vote(r: u32, ) -> Weight { - (12_563_000 as Weight) + (15_992_000 as Weight) // Standard Error: 1_000 - .saturating_add((105_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((113_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } diff --git a/frame/election-provider-multi-phase/src/weights.rs b/frame/election-provider-multi-phase/src/weights.rs index d4802210ba23f..68ce00dd0de32 100644 --- a/frame/election-provider-multi-phase/src/weights.rs +++ b/frame/election-provider-multi-phase/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_election_provider_multi_phase //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -68,33 +68,33 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking ForceEra (r:1 w:0) // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) fn on_initialize_nothing() -> Weight { - (12_920_000 as Weight) + (13_495_000 as Weight) .saturating_add(T::DbWeight::get().reads(8 as Weight)) } // Storage: ElectionProviderMultiPhase Round (r:1 w:0) // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) fn on_initialize_open_signed() -> Weight { - (13_366_000 as Weight) + (14_114_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: ElectionProviderMultiPhase Round (r:1 w:0) // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) fn on_initialize_open_unsigned() -> Weight { - (13_487_000 as Weight) + (13_756_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: System Account (r:1 w:1) // Storage: ElectionProviderMultiPhase QueuedSolution (r:0 w:1) fn finalize_signed_phase_accept_solution() -> Weight { - (28_656_000 as Weight) + (28_467_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: System Account (r:1 w:1) fn finalize_signed_phase_reject_solution() -> Weight { - (21_910_000 as Weight) + (21_991_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -102,11 +102,11 @@ impl WeightInfo for SubstrateWeight { // Storage: ElectionProviderMultiPhase DesiredTargets (r:0 w:1) // Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) fn create_snapshot_internal(v: u32, t: u32, ) -> Weight { - (0 as Weight) + (3_186_000 as Weight) // Standard Error: 1_000 - .saturating_add((210_000 as Weight).saturating_mul(v as Weight)) + .saturating_add((202_000 as Weight).saturating_mul(v as Weight)) // Standard Error: 3_000 - .saturating_add((77_000 as Weight).saturating_mul(t as Weight)) + .saturating_add((60_000 as Weight).saturating_mul(t as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: ElectionProviderMultiPhase SignedSubmissionIndices (r:1 w:1) @@ -119,11 +119,11 @@ impl WeightInfo for SubstrateWeight { // Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) fn elect_queued(a: u32, d: u32, ) -> Weight { - (97_857_000 as Weight) - // Standard Error: 2_000 - .saturating_add((651_000 as Weight).saturating_mul(a as Weight)) + (137_653_000 as Weight) // Standard Error: 4_000 - .saturating_add((133_000 as Weight).saturating_mul(d as Weight)) + .saturating_add((640_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 6_000 + .saturating_add((48_000 as Weight).saturating_mul(d as Weight)) .saturating_add(T::DbWeight::get().reads(6 as Weight)) .saturating_add(T::DbWeight::get().writes(8 as Weight)) } @@ -134,7 +134,7 @@ impl WeightInfo for SubstrateWeight { // Storage: ElectionProviderMultiPhase SignedSubmissionNextIndex (r:1 w:1) // Storage: ElectionProviderMultiPhase SignedSubmissionsMap (r:0 w:1) fn submit() -> Weight { - (43_892_000 as Weight) + (49_313_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -148,13 +148,13 @@ impl WeightInfo for SubstrateWeight { fn submit_unsigned(v: u32, t: u32, a: u32, d: u32, ) -> Weight { (0 as Weight) // Standard Error: 3_000 - .saturating_add((900_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 6_000 - .saturating_add((141_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 11_000 - .saturating_add((6_817_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 17_000 - .saturating_add((1_533_000 as Weight).saturating_mul(d as Weight)) + .saturating_add((867_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 7_000 + .saturating_add((107_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 12_000 + .saturating_add((6_907_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 18_000 + .saturating_add((1_427_000 as Weight).saturating_mul(d as Weight)) .saturating_add(T::DbWeight::get().reads(7 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -164,14 +164,14 @@ impl WeightInfo for SubstrateWeight { // Storage: ElectionProviderMultiPhase Snapshot (r:1 w:0) fn feasibility_check(v: u32, t: u32, a: u32, d: u32, ) -> Weight { (0 as Weight) - // Standard Error: 3_000 - .saturating_add((885_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 6_000 - .saturating_add((205_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 11_000 - .saturating_add((5_532_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 16_000 - .saturating_add((1_138_000 as Weight).saturating_mul(d as Weight)) + // Standard Error: 2_000 + .saturating_add((844_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 5_000 + .saturating_add((150_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 8_000 + .saturating_add((5_421_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 13_000 + .saturating_add((1_167_000 as Weight).saturating_mul(d as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) } } @@ -187,33 +187,33 @@ impl WeightInfo for () { // Storage: Staking ForceEra (r:1 w:0) // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) fn on_initialize_nothing() -> Weight { - (12_920_000 as Weight) + (13_495_000 as Weight) .saturating_add(RocksDbWeight::get().reads(8 as Weight)) } // Storage: ElectionProviderMultiPhase Round (r:1 w:0) // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) fn on_initialize_open_signed() -> Weight { - (13_366_000 as Weight) + (14_114_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: ElectionProviderMultiPhase Round (r:1 w:0) // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) fn on_initialize_open_unsigned() -> Weight { - (13_487_000 as Weight) + (13_756_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: System Account (r:1 w:1) // Storage: ElectionProviderMultiPhase QueuedSolution (r:0 w:1) fn finalize_signed_phase_accept_solution() -> Weight { - (28_656_000 as Weight) + (28_467_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: System Account (r:1 w:1) fn finalize_signed_phase_reject_solution() -> Weight { - (21_910_000 as Weight) + (21_991_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -221,11 +221,11 @@ impl WeightInfo for () { // Storage: ElectionProviderMultiPhase DesiredTargets (r:0 w:1) // Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) fn create_snapshot_internal(v: u32, t: u32, ) -> Weight { - (0 as Weight) + (3_186_000 as Weight) // Standard Error: 1_000 - .saturating_add((210_000 as Weight).saturating_mul(v as Weight)) + .saturating_add((202_000 as Weight).saturating_mul(v as Weight)) // Standard Error: 3_000 - .saturating_add((77_000 as Weight).saturating_mul(t as Weight)) + .saturating_add((60_000 as Weight).saturating_mul(t as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: ElectionProviderMultiPhase SignedSubmissionIndices (r:1 w:1) @@ -238,11 +238,11 @@ impl WeightInfo for () { // Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) fn elect_queued(a: u32, d: u32, ) -> Weight { - (97_857_000 as Weight) - // Standard Error: 2_000 - .saturating_add((651_000 as Weight).saturating_mul(a as Weight)) + (137_653_000 as Weight) // Standard Error: 4_000 - .saturating_add((133_000 as Weight).saturating_mul(d as Weight)) + .saturating_add((640_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 6_000 + .saturating_add((48_000 as Weight).saturating_mul(d as Weight)) .saturating_add(RocksDbWeight::get().reads(6 as Weight)) .saturating_add(RocksDbWeight::get().writes(8 as Weight)) } @@ -253,7 +253,7 @@ impl WeightInfo for () { // Storage: ElectionProviderMultiPhase SignedSubmissionNextIndex (r:1 w:1) // Storage: ElectionProviderMultiPhase SignedSubmissionsMap (r:0 w:1) fn submit() -> Weight { - (43_892_000 as Weight) + (49_313_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -267,13 +267,13 @@ impl WeightInfo for () { fn submit_unsigned(v: u32, t: u32, a: u32, d: u32, ) -> Weight { (0 as Weight) // Standard Error: 3_000 - .saturating_add((900_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 6_000 - .saturating_add((141_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 11_000 - .saturating_add((6_817_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 17_000 - .saturating_add((1_533_000 as Weight).saturating_mul(d as Weight)) + .saturating_add((867_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 7_000 + .saturating_add((107_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 12_000 + .saturating_add((6_907_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 18_000 + .saturating_add((1_427_000 as Weight).saturating_mul(d as Weight)) .saturating_add(RocksDbWeight::get().reads(7 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -283,14 +283,14 @@ impl WeightInfo for () { // Storage: ElectionProviderMultiPhase Snapshot (r:1 w:0) fn feasibility_check(v: u32, t: u32, a: u32, d: u32, ) -> Weight { (0 as Weight) - // Standard Error: 3_000 - .saturating_add((885_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 6_000 - .saturating_add((205_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 11_000 - .saturating_add((5_532_000 as Weight).saturating_mul(a as Weight)) - // Standard Error: 16_000 - .saturating_add((1_138_000 as Weight).saturating_mul(d as Weight)) + // Standard Error: 2_000 + .saturating_add((844_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 5_000 + .saturating_add((150_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 8_000 + .saturating_add((5_421_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 13_000 + .saturating_add((1_167_000 as Weight).saturating_mul(d as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) } } diff --git a/frame/elections-phragmen/src/weights.rs b/frame/elections-phragmen/src/weights.rs index 6cb2924fd7cf8..5ad986bf142a3 100644 --- a/frame/elections-phragmen/src/weights.rs +++ b/frame/elections-phragmen/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_elections_phragmen //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -68,9 +68,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vote_equal(v: u32, ) -> Weight { - (23_819_000 as Weight) + (27_798_000 as Weight) // Standard Error: 5_000 - .saturating_add((242_000 as Weight).saturating_mul(v as Weight)) + .saturating_add((238_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -80,8 +80,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vote_more(v: u32, ) -> Weight { - (37_141_000 as Weight) - // Standard Error: 6_000 + (41_241_000 as Weight) + // Standard Error: 8_000 .saturating_add((259_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) @@ -92,16 +92,16 @@ impl WeightInfo for SubstrateWeight { // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vote_less(v: u32, ) -> Weight { - (37_494_000 as Weight) - // Standard Error: 10_000 - .saturating_add((227_000 as Weight).saturating_mul(v as Weight)) + (41_313_000 as Weight) + // Standard Error: 8_000 + .saturating_add((255_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn remove_voter() -> Weight { - (34_528_000 as Weight) + (39_218_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -109,17 +109,17 @@ impl WeightInfo for SubstrateWeight { // Storage: Elections Members (r:1 w:0) // Storage: Elections RunnersUp (r:1 w:0) fn submit_candidacy(c: u32, ) -> Weight { - (38_330_000 as Weight) + (41_348_000 as Weight) // Standard Error: 1_000 - .saturating_add((88_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((112_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Elections Candidates (r:1 w:1) fn renounce_candidacy_candidate(c: u32, ) -> Weight { - (32_770_000 as Weight) + (35_522_000 as Weight) // Standard Error: 1_000 - .saturating_add((81_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((92_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -129,13 +129,13 @@ impl WeightInfo for SubstrateWeight { // Storage: Council Proposals (r:1 w:0) // Storage: Council Members (r:0 w:1) fn renounce_candidacy_members() -> Weight { - (43_413_000 as Weight) + (47_887_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } // Storage: Elections RunnersUp (r:1 w:1) fn renounce_candidacy_runners_up() -> Weight { - (32_206_000 as Weight) + (36_271_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -150,13 +150,13 @@ impl WeightInfo for SubstrateWeight { // Storage: Council Proposals (r:1 w:0) // Storage: Council Members (r:0 w:1) fn remove_member_with_replacement() -> Weight { - (50_135_000 as Weight) + (55_024_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } // Storage: Elections RunnersUp (r:1 w:0) fn remove_member_wrong_refund() -> Weight { - (5_063_000 as Weight) + (13_089_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) } // Storage: Elections Voting (r:251 w:250) @@ -165,10 +165,12 @@ impl WeightInfo for SubstrateWeight { // Storage: Elections Candidates (r:1 w:0) // Storage: Balances Locks (r:250 w:250) // Storage: System Account (r:250 w:250) - fn clean_defunct_voters(v: u32, _d: u32, ) -> Weight { + fn clean_defunct_voters(v: u32, d: u32, ) -> Weight { (0 as Weight) - // Standard Error: 62_000 - .saturating_add((52_093_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 40_000 + .saturating_add((51_848_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 38_000 + .saturating_add((537_000 as Weight).saturating_mul(d as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(v as Weight))) .saturating_add(T::DbWeight::get().writes((3 as Weight).saturating_mul(v as Weight))) @@ -184,12 +186,12 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:2 w:2) fn election_phragmen(c: u32, v: u32, e: u32, ) -> Weight { (0 as Weight) - // Standard Error: 1_658_000 - .saturating_add((30_489_000 as Weight).saturating_mul(c as Weight)) - // Standard Error: 689_000 - .saturating_add((49_624_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 1_664_000 + .saturating_add((30_736_000 as Weight).saturating_mul(c as Weight)) + // Standard Error: 692_000 + .saturating_add((49_739_000 as Weight).saturating_mul(v as Weight)) // Standard Error: 47_000 - .saturating_add((3_352_000 as Weight).saturating_mul(e as Weight)) + .saturating_add((3_363_000 as Weight).saturating_mul(e as Weight)) .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(c as Weight))) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(v as Weight))) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(c as Weight))) @@ -204,9 +206,9 @@ impl WeightInfo for () { // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vote_equal(v: u32, ) -> Weight { - (23_819_000 as Weight) + (27_798_000 as Weight) // Standard Error: 5_000 - .saturating_add((242_000 as Weight).saturating_mul(v as Weight)) + .saturating_add((238_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -216,8 +218,8 @@ impl WeightInfo for () { // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vote_more(v: u32, ) -> Weight { - (37_141_000 as Weight) - // Standard Error: 6_000 + (41_241_000 as Weight) + // Standard Error: 8_000 .saturating_add((259_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) @@ -228,16 +230,16 @@ impl WeightInfo for () { // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vote_less(v: u32, ) -> Weight { - (37_494_000 as Weight) - // Standard Error: 10_000 - .saturating_add((227_000 as Weight).saturating_mul(v as Weight)) + (41_313_000 as Weight) + // Standard Error: 8_000 + .saturating_add((255_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn remove_voter() -> Weight { - (34_528_000 as Weight) + (39_218_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -245,17 +247,17 @@ impl WeightInfo for () { // Storage: Elections Members (r:1 w:0) // Storage: Elections RunnersUp (r:1 w:0) fn submit_candidacy(c: u32, ) -> Weight { - (38_330_000 as Weight) + (41_348_000 as Weight) // Standard Error: 1_000 - .saturating_add((88_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((112_000 as Weight).saturating_mul(c as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Elections Candidates (r:1 w:1) fn renounce_candidacy_candidate(c: u32, ) -> Weight { - (32_770_000 as Weight) + (35_522_000 as Weight) // Standard Error: 1_000 - .saturating_add((81_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((92_000 as Weight).saturating_mul(c as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -265,13 +267,13 @@ impl WeightInfo for () { // Storage: Council Proposals (r:1 w:0) // Storage: Council Members (r:0 w:1) fn renounce_candidacy_members() -> Weight { - (43_413_000 as Weight) + (47_887_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } // Storage: Elections RunnersUp (r:1 w:1) fn renounce_candidacy_runners_up() -> Weight { - (32_206_000 as Weight) + (36_271_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -286,13 +288,13 @@ impl WeightInfo for () { // Storage: Council Proposals (r:1 w:0) // Storage: Council Members (r:0 w:1) fn remove_member_with_replacement() -> Weight { - (50_135_000 as Weight) + (55_024_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } // Storage: Elections RunnersUp (r:1 w:0) fn remove_member_wrong_refund() -> Weight { - (5_063_000 as Weight) + (13_089_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) } // Storage: Elections Voting (r:251 w:250) @@ -301,10 +303,12 @@ impl WeightInfo for () { // Storage: Elections Candidates (r:1 w:0) // Storage: Balances Locks (r:250 w:250) // Storage: System Account (r:250 w:250) - fn clean_defunct_voters(v: u32, _d: u32, ) -> Weight { + fn clean_defunct_voters(v: u32, d: u32, ) -> Weight { (0 as Weight) - // Standard Error: 62_000 - .saturating_add((52_093_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 40_000 + .saturating_add((51_848_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 38_000 + .saturating_add((537_000 as Weight).saturating_mul(d as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(v as Weight))) .saturating_add(RocksDbWeight::get().writes((3 as Weight).saturating_mul(v as Weight))) @@ -320,12 +324,12 @@ impl WeightInfo for () { // Storage: System Account (r:2 w:2) fn election_phragmen(c: u32, v: u32, e: u32, ) -> Weight { (0 as Weight) - // Standard Error: 1_658_000 - .saturating_add((30_489_000 as Weight).saturating_mul(c as Weight)) - // Standard Error: 689_000 - .saturating_add((49_624_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 1_664_000 + .saturating_add((30_736_000 as Weight).saturating_mul(c as Weight)) + // Standard Error: 692_000 + .saturating_add((49_739_000 as Weight).saturating_mul(v as Weight)) // Standard Error: 47_000 - .saturating_add((3_352_000 as Weight).saturating_mul(e as Weight)) + .saturating_add((3_363_000 as Weight).saturating_mul(e as Weight)) .saturating_add(RocksDbWeight::get().reads((2 as Weight).saturating_mul(c as Weight))) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(v as Weight))) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(c as Weight))) diff --git a/frame/gilt/src/weights.rs b/frame/gilt/src/weights.rs index 99e58d070e493..952080a2d030b 100644 --- a/frame/gilt/src/weights.rs +++ b/frame/gilt/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_gilt //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -60,44 +60,44 @@ impl WeightInfo for SubstrateWeight { // Storage: Gilt Queues (r:1 w:1) // Storage: Gilt QueueTotals (r:1 w:1) fn place_bid(l: u32, ) -> Weight { - (36_598_000 as Weight) + (41_605_000 as Weight) // Standard Error: 0 - .saturating_add((61_000 as Weight).saturating_mul(l as Weight)) + .saturating_add((62_000 as Weight).saturating_mul(l as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Gilt Queues (r:1 w:1) // Storage: Gilt QueueTotals (r:1 w:1) fn place_bid_max() -> Weight { - (92_021_000 as Weight) + (97_715_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Gilt Queues (r:1 w:1) // Storage: Gilt QueueTotals (r:1 w:1) fn retract_bid(l: u32, ) -> Weight { - (37_133_000 as Weight) + (42_061_000 as Weight) // Standard Error: 0 - .saturating_add((51_000 as Weight).saturating_mul(l as Weight)) + .saturating_add((52_000 as Weight).saturating_mul(l as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Gilt ActiveTotal (r:1 w:1) fn set_target() -> Weight { - (2_807_000 as Weight) + (5_026_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Gilt Active (r:1 w:1) // Storage: Gilt ActiveTotal (r:1 w:1) fn thaw() -> Weight { - (43_275_000 as Weight) + (47_753_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Gilt ActiveTotal (r:1 w:0) fn pursue_target_noop() -> Weight { - (1_693_000 as Weight) + (1_663_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) } // Storage: Gilt ActiveTotal (r:1 w:1) @@ -105,9 +105,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Gilt Queues (r:1 w:1) // Storage: Gilt Active (r:0 w:1) fn pursue_target_per_item(b: u32, ) -> Weight { - (37_977_000 as Weight) - // Standard Error: 2_000 - .saturating_add((4_129_000 as Weight).saturating_mul(b as Weight)) + (40_797_000 as Weight) + // Standard Error: 1_000 + .saturating_add((4_122_000 as Weight).saturating_mul(b as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(b as Weight))) @@ -117,9 +117,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Gilt Queues (r:1 w:1) // Storage: Gilt Active (r:0 w:1) fn pursue_target_per_queue(q: u32, ) -> Weight { - (11_367_000 as Weight) - // Standard Error: 7_000 - .saturating_add((8_275_000 as Weight).saturating_mul(q as Weight)) + (14_944_000 as Weight) + // Standard Error: 6_000 + .saturating_add((8_135_000 as Weight).saturating_mul(q as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(q as Weight))) .saturating_add(T::DbWeight::get().writes(2 as Weight)) @@ -132,44 +132,44 @@ impl WeightInfo for () { // Storage: Gilt Queues (r:1 w:1) // Storage: Gilt QueueTotals (r:1 w:1) fn place_bid(l: u32, ) -> Weight { - (36_598_000 as Weight) + (41_605_000 as Weight) // Standard Error: 0 - .saturating_add((61_000 as Weight).saturating_mul(l as Weight)) + .saturating_add((62_000 as Weight).saturating_mul(l as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Gilt Queues (r:1 w:1) // Storage: Gilt QueueTotals (r:1 w:1) fn place_bid_max() -> Weight { - (92_021_000 as Weight) + (97_715_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Gilt Queues (r:1 w:1) // Storage: Gilt QueueTotals (r:1 w:1) fn retract_bid(l: u32, ) -> Weight { - (37_133_000 as Weight) + (42_061_000 as Weight) // Standard Error: 0 - .saturating_add((51_000 as Weight).saturating_mul(l as Weight)) + .saturating_add((52_000 as Weight).saturating_mul(l as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Gilt ActiveTotal (r:1 w:1) fn set_target() -> Weight { - (2_807_000 as Weight) + (5_026_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Gilt Active (r:1 w:1) // Storage: Gilt ActiveTotal (r:1 w:1) fn thaw() -> Weight { - (43_275_000 as Weight) + (47_753_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Gilt ActiveTotal (r:1 w:0) fn pursue_target_noop() -> Weight { - (1_693_000 as Weight) + (1_663_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) } // Storage: Gilt ActiveTotal (r:1 w:1) @@ -177,9 +177,9 @@ impl WeightInfo for () { // Storage: Gilt Queues (r:1 w:1) // Storage: Gilt Active (r:0 w:1) fn pursue_target_per_item(b: u32, ) -> Weight { - (37_977_000 as Weight) - // Standard Error: 2_000 - .saturating_add((4_129_000 as Weight).saturating_mul(b as Weight)) + (40_797_000 as Weight) + // Standard Error: 1_000 + .saturating_add((4_122_000 as Weight).saturating_mul(b as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(b as Weight))) @@ -189,9 +189,9 @@ impl WeightInfo for () { // Storage: Gilt Queues (r:1 w:1) // Storage: Gilt Active (r:0 w:1) fn pursue_target_per_queue(q: u32, ) -> Weight { - (11_367_000 as Weight) - // Standard Error: 7_000 - .saturating_add((8_275_000 as Weight).saturating_mul(q as Weight)) + (14_944_000 as Weight) + // Standard Error: 6_000 + .saturating_add((8_135_000 as Weight).saturating_mul(q as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(q as Weight))) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) diff --git a/frame/identity/src/weights.rs b/frame/identity/src/weights.rs index 77c5915f0ec99..d59611e2e1e82 100644 --- a/frame/identity/src/weights.rs +++ b/frame/identity/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_identity //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -67,19 +67,19 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: Identity Registrars (r:1 w:1) fn add_registrar(r: u32, ) -> Weight { - (13_318_000 as Weight) - // Standard Error: 3_000 - .saturating_add((203_000 as Weight).saturating_mul(r as Weight)) + (16_343_000 as Weight) + // Standard Error: 5_000 + .saturating_add((229_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Identity IdentityOf (r:1 w:1) fn set_identity(r: u32, x: u32, ) -> Weight { - (27_683_000 as Weight) + (32_920_000 as Weight) // Standard Error: 8_000 - .saturating_add((242_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((203_000 as Weight).saturating_mul(r as Weight)) // Standard Error: 1_000 - .saturating_add((307_000 as Weight).saturating_mul(x as Weight)) + .saturating_add((300_000 as Weight).saturating_mul(x as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -87,9 +87,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Identity SubsOf (r:1 w:1) // Storage: Identity SuperOf (r:1 w:1) fn set_subs_new(s: u32, ) -> Weight { - (26_126_000 as Weight) + (31_009_000 as Weight) // Standard Error: 1_000 - .saturating_add((2_826_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((3_053_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(s as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -99,9 +99,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Identity SubsOf (r:1 w:1) // Storage: Identity SuperOf (r:0 w:1) fn set_subs_old(p: u32, ) -> Weight { - (24_645_000 as Weight) - // Standard Error: 0 - .saturating_add((893_000 as Weight).saturating_mul(p as Weight)) + (29_712_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_087_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(p as Weight))) @@ -110,13 +110,13 @@ impl WeightInfo for SubstrateWeight { // Storage: Identity IdentityOf (r:1 w:1) // Storage: Identity SuperOf (r:0 w:100) fn clear_identity(r: u32, s: u32, x: u32, ) -> Weight { - (31_546_000 as Weight) - // Standard Error: 12_000 - .saturating_add((110_000 as Weight).saturating_mul(r as Weight)) - // Standard Error: 1_000 - .saturating_add((872_000 as Weight).saturating_mul(s as Weight)) - // Standard Error: 1_000 - .saturating_add((182_000 as Weight).saturating_mul(x as Weight)) + (33_943_000 as Weight) + // Standard Error: 7_000 + .saturating_add((193_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 0 + .saturating_add((1_101_000 as Weight).saturating_mul(s as Weight)) + // Standard Error: 0 + .saturating_add((194_000 as Weight).saturating_mul(x as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) @@ -124,56 +124,56 @@ impl WeightInfo for SubstrateWeight { // Storage: Identity Registrars (r:1 w:0) // Storage: Identity IdentityOf (r:1 w:1) fn request_judgement(r: u32, x: u32, ) -> Weight { - (31_117_000 as Weight) + (34_861_000 as Weight) // Standard Error: 3_000 - .saturating_add((207_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((249_000 as Weight).saturating_mul(r as Weight)) // Standard Error: 0 - .saturating_add((347_000 as Weight).saturating_mul(x as Weight)) + .saturating_add((344_000 as Weight).saturating_mul(x as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Identity IdentityOf (r:1 w:1) fn cancel_request(r: u32, x: u32, ) -> Weight { - (28_134_000 as Weight) + (32_906_000 as Weight) // Standard Error: 3_000 - .saturating_add((154_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((147_000 as Weight).saturating_mul(r as Weight)) // Standard Error: 0 - .saturating_add((343_000 as Weight).saturating_mul(x as Weight)) + .saturating_add((341_000 as Weight).saturating_mul(x as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Identity Registrars (r:1 w:1) fn set_fee(r: u32, ) -> Weight { - (4_884_000 as Weight) + (7_591_000 as Weight) // Standard Error: 3_000 - .saturating_add((159_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((201_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Identity Registrars (r:1 w:1) fn set_account_id(r: u32, ) -> Weight { - (5_015_000 as Weight) - // Standard Error: 2_000 - .saturating_add((143_000 as Weight).saturating_mul(r as Weight)) + (7_919_000 as Weight) + // Standard Error: 3_000 + .saturating_add((183_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Identity Registrars (r:1 w:1) fn set_fields(r: u32, ) -> Weight { - (4_947_000 as Weight) - // Standard Error: 3_000 - .saturating_add((152_000 as Weight).saturating_mul(r as Weight)) + (7_887_000 as Weight) + // Standard Error: 4_000 + .saturating_add((182_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Identity Registrars (r:1 w:0) // Storage: Identity IdentityOf (r:1 w:1) fn provide_judgement(r: u32, x: u32, ) -> Weight { - (20_570_000 as Weight) + (24_623_000 as Weight) // Standard Error: 3_000 - .saturating_add((213_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((230_000 as Weight).saturating_mul(r as Weight)) // Standard Error: 0 - .saturating_add((345_000 as Weight).saturating_mul(x as Weight)) + .saturating_add((339_000 as Weight).saturating_mul(x as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -182,11 +182,11 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) // Storage: Identity SuperOf (r:0 w:100) fn kill_identity(r: u32, s: u32, _x: u32, ) -> Weight { - (41_448_000 as Weight) - // Standard Error: 7_000 - .saturating_add((149_000 as Weight).saturating_mul(r as Weight)) + (48_143_000 as Weight) + // Standard Error: 8_000 + .saturating_add((106_000 as Weight).saturating_mul(r as Weight)) // Standard Error: 0 - .saturating_add((882_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((1_105_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) @@ -195,18 +195,18 @@ impl WeightInfo for SubstrateWeight { // Storage: Identity SuperOf (r:1 w:1) // Storage: Identity SubsOf (r:1 w:1) fn add_sub(s: u32, ) -> Weight { - (32_346_000 as Weight) + (36_778_000 as Weight) // Standard Error: 1_000 - .saturating_add((114_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((112_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Identity IdentityOf (r:1 w:0) // Storage: Identity SuperOf (r:1 w:1) fn rename_sub(s: u32, ) -> Weight { - (10_211_000 as Weight) + (13_895_000 as Weight) // Standard Error: 0 - .saturating_add((51_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((52_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -214,18 +214,18 @@ impl WeightInfo for SubstrateWeight { // Storage: Identity SuperOf (r:1 w:1) // Storage: Identity SubsOf (r:1 w:1) fn remove_sub(s: u32, ) -> Weight { - (33_083_000 as Weight) + (37_707_000 as Weight) // Standard Error: 1_000 - .saturating_add((112_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((110_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Identity SuperOf (r:1 w:1) // Storage: Identity SubsOf (r:1 w:1) fn quit_sub(s: u32, ) -> Weight { - (22_517_000 as Weight) + (26_935_000 as Weight) // Standard Error: 1_000 - .saturating_add((110_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((106_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -235,19 +235,19 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { // Storage: Identity Registrars (r:1 w:1) fn add_registrar(r: u32, ) -> Weight { - (13_318_000 as Weight) - // Standard Error: 3_000 - .saturating_add((203_000 as Weight).saturating_mul(r as Weight)) + (16_343_000 as Weight) + // Standard Error: 5_000 + .saturating_add((229_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Identity IdentityOf (r:1 w:1) fn set_identity(r: u32, x: u32, ) -> Weight { - (27_683_000 as Weight) + (32_920_000 as Weight) // Standard Error: 8_000 - .saturating_add((242_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((203_000 as Weight).saturating_mul(r as Weight)) // Standard Error: 1_000 - .saturating_add((307_000 as Weight).saturating_mul(x as Weight)) + .saturating_add((300_000 as Weight).saturating_mul(x as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -255,9 +255,9 @@ impl WeightInfo for () { // Storage: Identity SubsOf (r:1 w:1) // Storage: Identity SuperOf (r:1 w:1) fn set_subs_new(s: u32, ) -> Weight { - (26_126_000 as Weight) + (31_009_000 as Weight) // Standard Error: 1_000 - .saturating_add((2_826_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((3_053_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(s as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -267,9 +267,9 @@ impl WeightInfo for () { // Storage: Identity SubsOf (r:1 w:1) // Storage: Identity SuperOf (r:0 w:1) fn set_subs_old(p: u32, ) -> Weight { - (24_645_000 as Weight) - // Standard Error: 0 - .saturating_add((893_000 as Weight).saturating_mul(p as Weight)) + (29_712_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_087_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(p as Weight))) @@ -278,13 +278,13 @@ impl WeightInfo for () { // Storage: Identity IdentityOf (r:1 w:1) // Storage: Identity SuperOf (r:0 w:100) fn clear_identity(r: u32, s: u32, x: u32, ) -> Weight { - (31_546_000 as Weight) - // Standard Error: 12_000 - .saturating_add((110_000 as Weight).saturating_mul(r as Weight)) - // Standard Error: 1_000 - .saturating_add((872_000 as Weight).saturating_mul(s as Weight)) - // Standard Error: 1_000 - .saturating_add((182_000 as Weight).saturating_mul(x as Weight)) + (33_943_000 as Weight) + // Standard Error: 7_000 + .saturating_add((193_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 0 + .saturating_add((1_101_000 as Weight).saturating_mul(s as Weight)) + // Standard Error: 0 + .saturating_add((194_000 as Weight).saturating_mul(x as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) @@ -292,56 +292,56 @@ impl WeightInfo for () { // Storage: Identity Registrars (r:1 w:0) // Storage: Identity IdentityOf (r:1 w:1) fn request_judgement(r: u32, x: u32, ) -> Weight { - (31_117_000 as Weight) + (34_861_000 as Weight) // Standard Error: 3_000 - .saturating_add((207_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((249_000 as Weight).saturating_mul(r as Weight)) // Standard Error: 0 - .saturating_add((347_000 as Weight).saturating_mul(x as Weight)) + .saturating_add((344_000 as Weight).saturating_mul(x as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Identity IdentityOf (r:1 w:1) fn cancel_request(r: u32, x: u32, ) -> Weight { - (28_134_000 as Weight) + (32_906_000 as Weight) // Standard Error: 3_000 - .saturating_add((154_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((147_000 as Weight).saturating_mul(r as Weight)) // Standard Error: 0 - .saturating_add((343_000 as Weight).saturating_mul(x as Weight)) + .saturating_add((341_000 as Weight).saturating_mul(x as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Identity Registrars (r:1 w:1) fn set_fee(r: u32, ) -> Weight { - (4_884_000 as Weight) + (7_591_000 as Weight) // Standard Error: 3_000 - .saturating_add((159_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((201_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Identity Registrars (r:1 w:1) fn set_account_id(r: u32, ) -> Weight { - (5_015_000 as Weight) - // Standard Error: 2_000 - .saturating_add((143_000 as Weight).saturating_mul(r as Weight)) + (7_919_000 as Weight) + // Standard Error: 3_000 + .saturating_add((183_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Identity Registrars (r:1 w:1) fn set_fields(r: u32, ) -> Weight { - (4_947_000 as Weight) - // Standard Error: 3_000 - .saturating_add((152_000 as Weight).saturating_mul(r as Weight)) + (7_887_000 as Weight) + // Standard Error: 4_000 + .saturating_add((182_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Identity Registrars (r:1 w:0) // Storage: Identity IdentityOf (r:1 w:1) fn provide_judgement(r: u32, x: u32, ) -> Weight { - (20_570_000 as Weight) + (24_623_000 as Weight) // Standard Error: 3_000 - .saturating_add((213_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((230_000 as Weight).saturating_mul(r as Weight)) // Standard Error: 0 - .saturating_add((345_000 as Weight).saturating_mul(x as Weight)) + .saturating_add((339_000 as Weight).saturating_mul(x as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -350,11 +350,11 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) // Storage: Identity SuperOf (r:0 w:100) fn kill_identity(r: u32, s: u32, _x: u32, ) -> Weight { - (41_448_000 as Weight) - // Standard Error: 7_000 - .saturating_add((149_000 as Weight).saturating_mul(r as Weight)) + (48_143_000 as Weight) + // Standard Error: 8_000 + .saturating_add((106_000 as Weight).saturating_mul(r as Weight)) // Standard Error: 0 - .saturating_add((882_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((1_105_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) @@ -363,18 +363,18 @@ impl WeightInfo for () { // Storage: Identity SuperOf (r:1 w:1) // Storage: Identity SubsOf (r:1 w:1) fn add_sub(s: u32, ) -> Weight { - (32_346_000 as Weight) + (36_778_000 as Weight) // Standard Error: 1_000 - .saturating_add((114_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((112_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Identity IdentityOf (r:1 w:0) // Storage: Identity SuperOf (r:1 w:1) fn rename_sub(s: u32, ) -> Weight { - (10_211_000 as Weight) + (13_895_000 as Weight) // Standard Error: 0 - .saturating_add((51_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((52_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -382,18 +382,18 @@ impl WeightInfo for () { // Storage: Identity SuperOf (r:1 w:1) // Storage: Identity SubsOf (r:1 w:1) fn remove_sub(s: u32, ) -> Weight { - (33_083_000 as Weight) + (37_707_000 as Weight) // Standard Error: 1_000 - .saturating_add((112_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((110_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Identity SuperOf (r:1 w:1) // Storage: Identity SubsOf (r:1 w:1) fn quit_sub(s: u32, ) -> Weight { - (22_517_000 as Weight) + (26_935_000 as Weight) // Standard Error: 1_000 - .saturating_add((110_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((106_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } diff --git a/frame/im-online/src/weights.rs b/frame/im-online/src/weights.rs index 187499b1f957f..34762e66ec301 100644 --- a/frame/im-online/src/weights.rs +++ b/frame/im-online/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_im_online //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -56,11 +56,11 @@ impl WeightInfo for SubstrateWeight { // Storage: ImOnline AuthoredBlocks (r:1 w:0) // Storage: ImOnline Keys (r:1 w:0) fn validate_unsigned_and_then_heartbeat(k: u32, e: u32, ) -> Weight { - (74_221_000 as Weight) + (79_225_000 as Weight) // Standard Error: 0 .saturating_add((41_000 as Weight).saturating_mul(k as Weight)) // Standard Error: 0 - .saturating_add((294_000 as Weight).saturating_mul(e as Weight)) + .saturating_add((293_000 as Weight).saturating_mul(e as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -74,11 +74,11 @@ impl WeightInfo for () { // Storage: ImOnline AuthoredBlocks (r:1 w:0) // Storage: ImOnline Keys (r:1 w:0) fn validate_unsigned_and_then_heartbeat(k: u32, e: u32, ) -> Weight { - (74_221_000 as Weight) + (79_225_000 as Weight) // Standard Error: 0 .saturating_add((41_000 as Weight).saturating_mul(k as Weight)) // Standard Error: 0 - .saturating_add((294_000 as Weight).saturating_mul(e as Weight)) + .saturating_add((293_000 as Weight).saturating_mul(e as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } diff --git a/frame/indices/src/weights.rs b/frame/indices/src/weights.rs index 5fba8fc2377a0..6635d45272048 100644 --- a/frame/indices/src/weights.rs +++ b/frame/indices/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_indices //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -56,33 +56,33 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: Indices Accounts (r:1 w:1) fn claim() -> Weight { - (21_333_000 as Weight) + (25_929_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Indices Accounts (r:1 w:1) // Storage: System Account (r:1 w:1) fn transfer() -> Weight { - (27_479_000 as Weight) + (32_627_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Indices Accounts (r:1 w:1) fn free() -> Weight { - (22_802_000 as Weight) + (26_804_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Indices Accounts (r:1 w:1) // Storage: System Account (r:1 w:1) fn force_transfer() -> Weight { - (23_369_000 as Weight) + (27_390_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Indices Accounts (r:1 w:1) fn freeze() -> Weight { - (25_740_000 as Weight) + (30_973_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -92,33 +92,33 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { // Storage: Indices Accounts (r:1 w:1) fn claim() -> Weight { - (21_333_000 as Weight) + (25_929_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Indices Accounts (r:1 w:1) // Storage: System Account (r:1 w:1) fn transfer() -> Weight { - (27_479_000 as Weight) + (32_627_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Indices Accounts (r:1 w:1) fn free() -> Weight { - (22_802_000 as Weight) + (26_804_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Indices Accounts (r:1 w:1) // Storage: System Account (r:1 w:1) fn force_transfer() -> Weight { - (23_369_000 as Weight) + (27_390_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Indices Accounts (r:1 w:1) fn freeze() -> Weight { - (25_740_000 as Weight) + (30_973_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } diff --git a/frame/lottery/src/weights.rs b/frame/lottery/src/weights.rs index d3626dc9080ec..193958cfd41aa 100644 --- a/frame/lottery/src/weights.rs +++ b/frame/lottery/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_lottery //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -63,28 +63,28 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) // Storage: Lottery Tickets (r:0 w:1) fn buy_ticket() -> Weight { - (39_387_000 as Weight) + (44_706_000 as Weight) .saturating_add(T::DbWeight::get().reads(6 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } // Storage: Lottery CallIndices (r:0 w:1) fn set_calls(n: u32, ) -> Weight { - (9_353_000 as Weight) - // Standard Error: 4_000 - .saturating_add((297_000 as Weight).saturating_mul(n as Weight)) + (12_556_000 as Weight) + // Standard Error: 7_000 + .saturating_add((295_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Lottery Lottery (r:1 w:1) // Storage: Lottery LotteryIndex (r:1 w:1) // Storage: System Account (r:1 w:1) fn start_lottery() -> Weight { - (33_157_000 as Weight) + (38_051_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: Lottery Lottery (r:1 w:1) fn stop_repeat() -> Weight { - (4_015_000 as Weight) + (6_910_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -94,7 +94,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Lottery TicketsCount (r:1 w:1) // Storage: Lottery Tickets (r:1 w:0) fn on_initialize_end() -> Weight { - (53_539_000 as Weight) + (53_732_000 as Weight) .saturating_add(T::DbWeight::get().reads(6 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -105,7 +105,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Lottery Tickets (r:1 w:0) // Storage: Lottery LotteryIndex (r:1 w:1) fn on_initialize_repeat() -> Weight { - (56_103_000 as Weight) + (55_868_000 as Weight) .saturating_add(T::DbWeight::get().reads(7 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } @@ -121,28 +121,28 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) // Storage: Lottery Tickets (r:0 w:1) fn buy_ticket() -> Weight { - (39_387_000 as Weight) + (44_706_000 as Weight) .saturating_add(RocksDbWeight::get().reads(6 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } // Storage: Lottery CallIndices (r:0 w:1) fn set_calls(n: u32, ) -> Weight { - (9_353_000 as Weight) - // Standard Error: 4_000 - .saturating_add((297_000 as Weight).saturating_mul(n as Weight)) + (12_556_000 as Weight) + // Standard Error: 7_000 + .saturating_add((295_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Lottery Lottery (r:1 w:1) // Storage: Lottery LotteryIndex (r:1 w:1) // Storage: System Account (r:1 w:1) fn start_lottery() -> Weight { - (33_157_000 as Weight) + (38_051_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: Lottery Lottery (r:1 w:1) fn stop_repeat() -> Weight { - (4_015_000 as Weight) + (6_910_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -152,7 +152,7 @@ impl WeightInfo for () { // Storage: Lottery TicketsCount (r:1 w:1) // Storage: Lottery Tickets (r:1 w:0) fn on_initialize_end() -> Weight { - (53_539_000 as Weight) + (53_732_000 as Weight) .saturating_add(RocksDbWeight::get().reads(6 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } @@ -163,7 +163,7 @@ impl WeightInfo for () { // Storage: Lottery Tickets (r:1 w:0) // Storage: Lottery LotteryIndex (r:1 w:1) fn on_initialize_repeat() -> Weight { - (56_103_000 as Weight) + (55_868_000 as Weight) .saturating_add(RocksDbWeight::get().reads(7 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } diff --git a/frame/membership/src/weights.rs b/frame/membership/src/weights.rs index 7372ad37cdea2..714d53b1936a8 100644 --- a/frame/membership/src/weights.rs +++ b/frame/membership/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_membership //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -61,9 +61,9 @@ impl WeightInfo for SubstrateWeight { // Storage: TechnicalCommittee Members (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) fn add_member(m: u32, ) -> Weight { - (15_572_000 as Weight) - // Standard Error: 1_000 - .saturating_add((48_000 as Weight).saturating_mul(m as Weight)) + (15_318_000 as Weight) + // Standard Error: 0 + .saturating_add((51_000 as Weight).saturating_mul(m as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -73,9 +73,9 @@ impl WeightInfo for SubstrateWeight { // Storage: TechnicalCommittee Members (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) fn remove_member(m: u32, ) -> Weight { - (18_090_000 as Weight) + (18_005_000 as Weight) // Standard Error: 0 - .saturating_add((43_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((45_000 as Weight).saturating_mul(m as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -85,9 +85,9 @@ impl WeightInfo for SubstrateWeight { // Storage: TechnicalCommittee Members (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) fn swap_member(m: u32, ) -> Weight { - (18_199_000 as Weight) + (18_029_000 as Weight) // Standard Error: 0 - .saturating_add((54_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((55_000 as Weight).saturating_mul(m as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -97,7 +97,7 @@ impl WeightInfo for SubstrateWeight { // Storage: TechnicalCommittee Members (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) fn reset_member(m: u32, ) -> Weight { - (18_238_000 as Weight) + (18_105_000 as Weight) // Standard Error: 0 .saturating_add((158_000 as Weight).saturating_mul(m as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) @@ -109,9 +109,9 @@ impl WeightInfo for SubstrateWeight { // Storage: TechnicalCommittee Members (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) fn change_key(m: u32, ) -> Weight { - (18_911_000 as Weight) + (18_852_000 as Weight) // Standard Error: 0 - .saturating_add((53_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((55_000 as Weight).saturating_mul(m as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -119,16 +119,16 @@ impl WeightInfo for SubstrateWeight { // Storage: TechnicalMembership Prime (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) fn set_prime(m: u32, ) -> Weight { - (4_843_000 as Weight) + (4_869_000 as Weight) // Standard Error: 0 - .saturating_add((27_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((28_000 as Weight).saturating_mul(m as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: TechnicalMembership Prime (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) fn clear_prime(m: u32, ) -> Weight { - (1_662_000 as Weight) + (1_593_000 as Weight) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(m as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) @@ -142,9 +142,9 @@ impl WeightInfo for () { // Storage: TechnicalCommittee Members (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) fn add_member(m: u32, ) -> Weight { - (15_572_000 as Weight) - // Standard Error: 1_000 - .saturating_add((48_000 as Weight).saturating_mul(m as Weight)) + (15_318_000 as Weight) + // Standard Error: 0 + .saturating_add((51_000 as Weight).saturating_mul(m as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -154,9 +154,9 @@ impl WeightInfo for () { // Storage: TechnicalCommittee Members (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) fn remove_member(m: u32, ) -> Weight { - (18_090_000 as Weight) + (18_005_000 as Weight) // Standard Error: 0 - .saturating_add((43_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((45_000 as Weight).saturating_mul(m as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -166,9 +166,9 @@ impl WeightInfo for () { // Storage: TechnicalCommittee Members (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) fn swap_member(m: u32, ) -> Weight { - (18_199_000 as Weight) + (18_029_000 as Weight) // Standard Error: 0 - .saturating_add((54_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((55_000 as Weight).saturating_mul(m as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -178,7 +178,7 @@ impl WeightInfo for () { // Storage: TechnicalCommittee Members (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) fn reset_member(m: u32, ) -> Weight { - (18_238_000 as Weight) + (18_105_000 as Weight) // Standard Error: 0 .saturating_add((158_000 as Weight).saturating_mul(m as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) @@ -190,9 +190,9 @@ impl WeightInfo for () { // Storage: TechnicalCommittee Members (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) fn change_key(m: u32, ) -> Weight { - (18_911_000 as Weight) + (18_852_000 as Weight) // Standard Error: 0 - .saturating_add((53_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((55_000 as Weight).saturating_mul(m as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } @@ -200,16 +200,16 @@ impl WeightInfo for () { // Storage: TechnicalMembership Prime (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) fn set_prime(m: u32, ) -> Weight { - (4_843_000 as Weight) + (4_869_000 as Weight) // Standard Error: 0 - .saturating_add((27_000 as Weight).saturating_mul(m as Weight)) + .saturating_add((28_000 as Weight).saturating_mul(m as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: TechnicalMembership Prime (r:0 w:1) // Storage: TechnicalCommittee Prime (r:0 w:1) fn clear_prime(m: u32, ) -> Weight { - (1_662_000 as Weight) + (1_593_000 as Weight) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(m as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) diff --git a/frame/multisig/src/weights.rs b/frame/multisig/src/weights.rs index 563dd20bae63d..7946b96546768 100644 --- a/frame/multisig/src/weights.rs +++ b/frame/multisig/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_multisig //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -60,14 +60,14 @@ pub trait WeightInfo { pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { fn as_multi_threshold_1(_z: u32, ) -> Weight { - (12_456_000 as Weight) + (17_537_000 as Weight) } // Storage: Multisig Multisigs (r:1 w:1) // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) fn as_multi_create(s: u32, z: u32, ) -> Weight { - (31_781_000 as Weight) + (36_535_000 as Weight) // Standard Error: 0 - .saturating_add((93_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((99_000 as Weight).saturating_mul(s as Weight)) // Standard Error: 0 .saturating_add((1_000 as Weight).saturating_mul(z as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) @@ -77,7 +77,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Multisig Calls (r:1 w:1) // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) fn as_multi_create_store(s: u32, z: u32, ) -> Weight { - (35_669_000 as Weight) + (39_918_000 as Weight) // Standard Error: 1_000 .saturating_add((95_000 as Weight).saturating_mul(s as Weight)) // Standard Error: 0 @@ -87,9 +87,9 @@ impl WeightInfo for SubstrateWeight { } // Storage: Multisig Multisigs (r:1 w:1) fn as_multi_approve(s: u32, z: u32, ) -> Weight { - (21_326_000 as Weight) + (25_524_000 as Weight) // Standard Error: 0 - .saturating_add((91_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((94_000 as Weight).saturating_mul(s as Weight)) // Standard Error: 0 .saturating_add((1_000 as Weight).saturating_mul(z as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) @@ -98,9 +98,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Multisig Multisigs (r:1 w:1) // Storage: Multisig Calls (r:1 w:1) fn as_multi_approve_store(s: u32, z: u32, ) -> Weight { - (35_296_000 as Weight) + (39_923_000 as Weight) // Standard Error: 1_000 - .saturating_add((88_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((91_000 as Weight).saturating_mul(s as Weight)) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(z as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) @@ -110,9 +110,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Multisig Calls (r:1 w:1) // Storage: System Account (r:1 w:1) fn as_multi_complete(s: u32, z: u32, ) -> Weight { - (39_872_000 as Weight) + (45_877_000 as Weight) // Standard Error: 1_000 - .saturating_add((147_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((146_000 as Weight).saturating_mul(s as Weight)) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(z as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) @@ -121,18 +121,18 @@ impl WeightInfo for SubstrateWeight { // Storage: Multisig Multisigs (r:1 w:1) // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) fn approve_as_multi_create(s: u32, ) -> Weight { - (29_680_000 as Weight) - // Standard Error: 0 - .saturating_add((110_000 as Weight).saturating_mul(s as Weight)) + (34_309_000 as Weight) + // Standard Error: 1_000 + .saturating_add((114_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Multisig Multisigs (r:1 w:1) // Storage: Multisig Calls (r:1 w:0) fn approve_as_multi_approve(s: u32, ) -> Weight { - (18_514_000 as Weight) + (22_848_000 as Weight) // Standard Error: 0 - .saturating_add((111_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((114_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -140,18 +140,18 @@ impl WeightInfo for SubstrateWeight { // Storage: Multisig Calls (r:1 w:1) // Storage: System Account (r:1 w:1) fn approve_as_multi_complete(s: u32, ) -> Weight { - (57_404_000 as Weight) + (63_239_000 as Weight) // Standard Error: 1_000 - .saturating_add((157_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((161_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: Multisig Multisigs (r:1 w:1) // Storage: Multisig Calls (r:1 w:1) fn cancel_as_multi(s: u32, ) -> Weight { - (45_860_000 as Weight) + (51_254_000 as Weight) // Standard Error: 1_000 - .saturating_add((111_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((118_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -160,14 +160,14 @@ impl WeightInfo for SubstrateWeight { // For backwards compatibility and tests impl WeightInfo for () { fn as_multi_threshold_1(_z: u32, ) -> Weight { - (12_456_000 as Weight) + (17_537_000 as Weight) } // Storage: Multisig Multisigs (r:1 w:1) // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) fn as_multi_create(s: u32, z: u32, ) -> Weight { - (31_781_000 as Weight) + (36_535_000 as Weight) // Standard Error: 0 - .saturating_add((93_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((99_000 as Weight).saturating_mul(s as Weight)) // Standard Error: 0 .saturating_add((1_000 as Weight).saturating_mul(z as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) @@ -177,7 +177,7 @@ impl WeightInfo for () { // Storage: Multisig Calls (r:1 w:1) // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) fn as_multi_create_store(s: u32, z: u32, ) -> Weight { - (35_669_000 as Weight) + (39_918_000 as Weight) // Standard Error: 1_000 .saturating_add((95_000 as Weight).saturating_mul(s as Weight)) // Standard Error: 0 @@ -187,9 +187,9 @@ impl WeightInfo for () { } // Storage: Multisig Multisigs (r:1 w:1) fn as_multi_approve(s: u32, z: u32, ) -> Weight { - (21_326_000 as Weight) + (25_524_000 as Weight) // Standard Error: 0 - .saturating_add((91_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((94_000 as Weight).saturating_mul(s as Weight)) // Standard Error: 0 .saturating_add((1_000 as Weight).saturating_mul(z as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) @@ -198,9 +198,9 @@ impl WeightInfo for () { // Storage: Multisig Multisigs (r:1 w:1) // Storage: Multisig Calls (r:1 w:1) fn as_multi_approve_store(s: u32, z: u32, ) -> Weight { - (35_296_000 as Weight) + (39_923_000 as Weight) // Standard Error: 1_000 - .saturating_add((88_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((91_000 as Weight).saturating_mul(s as Weight)) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(z as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) @@ -210,9 +210,9 @@ impl WeightInfo for () { // Storage: Multisig Calls (r:1 w:1) // Storage: System Account (r:1 w:1) fn as_multi_complete(s: u32, z: u32, ) -> Weight { - (39_872_000 as Weight) + (45_877_000 as Weight) // Standard Error: 1_000 - .saturating_add((147_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((146_000 as Weight).saturating_mul(s as Weight)) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(z as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) @@ -221,18 +221,18 @@ impl WeightInfo for () { // Storage: Multisig Multisigs (r:1 w:1) // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) fn approve_as_multi_create(s: u32, ) -> Weight { - (29_680_000 as Weight) - // Standard Error: 0 - .saturating_add((110_000 as Weight).saturating_mul(s as Weight)) + (34_309_000 as Weight) + // Standard Error: 1_000 + .saturating_add((114_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Multisig Multisigs (r:1 w:1) // Storage: Multisig Calls (r:1 w:0) fn approve_as_multi_approve(s: u32, ) -> Weight { - (18_514_000 as Weight) + (22_848_000 as Weight) // Standard Error: 0 - .saturating_add((111_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((114_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -240,18 +240,18 @@ impl WeightInfo for () { // Storage: Multisig Calls (r:1 w:1) // Storage: System Account (r:1 w:1) fn approve_as_multi_complete(s: u32, ) -> Weight { - (57_404_000 as Weight) + (63_239_000 as Weight) // Standard Error: 1_000 - .saturating_add((157_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((161_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: Multisig Multisigs (r:1 w:1) // Storage: Multisig Calls (r:1 w:1) fn cancel_as_multi(s: u32, ) -> Weight { - (45_860_000 as Weight) + (51_254_000 as Weight) // Standard Error: 1_000 - .saturating_add((111_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((118_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } diff --git a/frame/nomination-pools/src/weights.rs b/frame/nomination-pools/src/weights.rs index ef339231fe7bb..ca89f982c60e3 100644 --- a/frame/nomination-pools/src/weights.rs +++ b/frame/nomination-pools/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_nomination_pools //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -77,7 +77,7 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn join() -> Weight { - (124_114_000 as Weight) + (129_124_000 as Weight) .saturating_add(T::DbWeight::get().reads(17 as Weight)) .saturating_add(T::DbWeight::get().writes(11 as Weight)) } @@ -91,7 +91,7 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn bond_extra_transfer() -> Weight { - (115_734_000 as Weight) + (118_193_000 as Weight) .saturating_add(T::DbWeight::get().reads(13 as Weight)) .saturating_add(T::DbWeight::get().writes(12 as Weight)) } @@ -105,7 +105,7 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList ListNodes (r:2 w:2) // Storage: BagsList ListBags (r:2 w:2) fn bond_extra_reward() -> Weight { - (127_873_000 as Weight) + (132_390_000 as Weight) .saturating_add(T::DbWeight::get().reads(13 as Weight)) .saturating_add(T::DbWeight::get().writes(12 as Weight)) } @@ -114,7 +114,7 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools RewardPools (r:1 w:1) // Storage: System Account (r:1 w:1) fn claim_payout() -> Weight { - (51_510_000 as Weight) + (54_743_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -133,7 +133,7 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools SubPoolsStorage (r:1 w:1) // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) fn unbond() -> Weight { - (120_744_000 as Weight) + (124_684_000 as Weight) .saturating_add(T::DbWeight::get().reads(18 as Weight)) .saturating_add(T::DbWeight::get().writes(13 as Weight)) } @@ -142,9 +142,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking CurrentEra (r:1 w:0) // Storage: Balances Locks (r:1 w:1) fn pool_withdraw_unbonded(s: u32, ) -> Weight { - (41_267_000 as Weight) + (44_259_000 as Weight) // Standard Error: 0 - .saturating_add((53_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((51_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -157,9 +157,9 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) // Storage: NominationPools CounterForPoolMembers (r:1 w:1) fn withdraw_unbonded_update(s: u32, ) -> Weight { - (82_234_000 as Weight) + (84_854_000 as Weight) // Standard Error: 0 - .saturating_add((48_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((56_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(8 as Weight)) .saturating_add(T::DbWeight::get().writes(7 as Weight)) } @@ -182,10 +182,8 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) // Storage: NominationPools CounterForBondedPools (r:1 w:1) // Storage: Staking Payee (r:0 w:1) - fn withdraw_unbonded_kill(s: u32, ) -> Weight { - (142_699_000 as Weight) - // Standard Error: 0 - .saturating_add((5_000 as Weight).saturating_mul(s as Weight)) + fn withdraw_unbonded_kill(_s: u32, ) -> Weight { + (146_992_000 as Weight) .saturating_add(T::DbWeight::get().reads(19 as Weight)) .saturating_add(T::DbWeight::get().writes(16 as Weight)) } @@ -212,7 +210,7 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn create() -> Weight { - (133_702_000 as Weight) + (138_099_000 as Weight) .saturating_add(T::DbWeight::get().reads(22 as Weight)) .saturating_add(T::DbWeight::get().writes(15 as Weight)) } @@ -229,9 +227,9 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList CounterForListNodes (r:1 w:1) // Storage: Staking CounterForNominators (r:1 w:1) fn nominate(n: u32, ) -> Weight { - (46_887_000 as Weight) - // Standard Error: 12_000 - .saturating_add((2_277_000 as Weight).saturating_mul(n as Weight)) + (50_964_000 as Weight) + // Standard Error: 11_000 + .saturating_add((2_333_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(12 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) .saturating_add(T::DbWeight::get().writes(5 as Weight)) @@ -239,7 +237,7 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Ledger (r:1 w:0) fn set_state() -> Weight { - (24_030_000 as Weight) + (27_196_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -247,9 +245,9 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools Metadata (r:1 w:1) // Storage: NominationPools CounterForMetadata (r:1 w:1) fn set_metadata(n: u32, ) -> Weight { - (11_336_000 as Weight) + (15_056_000 as Weight) // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(n as Weight)) + .saturating_add((1_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -259,12 +257,12 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools MinCreateBond (r:0 w:1) // Storage: NominationPools MaxPools (r:0 w:1) fn set_configs() -> Weight { - (2_927_000 as Weight) + (6_294_000 as Weight) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } // Storage: NominationPools BondedPools (r:1 w:1) fn update_roles() -> Weight { - (19_184_000 as Weight) + (22_444_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -286,7 +284,7 @@ impl WeightInfo for () { // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn join() -> Weight { - (124_114_000 as Weight) + (129_124_000 as Weight) .saturating_add(RocksDbWeight::get().reads(17 as Weight)) .saturating_add(RocksDbWeight::get().writes(11 as Weight)) } @@ -300,7 +298,7 @@ impl WeightInfo for () { // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn bond_extra_transfer() -> Weight { - (115_734_000 as Weight) + (118_193_000 as Weight) .saturating_add(RocksDbWeight::get().reads(13 as Weight)) .saturating_add(RocksDbWeight::get().writes(12 as Weight)) } @@ -314,7 +312,7 @@ impl WeightInfo for () { // Storage: BagsList ListNodes (r:2 w:2) // Storage: BagsList ListBags (r:2 w:2) fn bond_extra_reward() -> Weight { - (127_873_000 as Weight) + (132_390_000 as Weight) .saturating_add(RocksDbWeight::get().reads(13 as Weight)) .saturating_add(RocksDbWeight::get().writes(12 as Weight)) } @@ -323,7 +321,7 @@ impl WeightInfo for () { // Storage: NominationPools RewardPools (r:1 w:1) // Storage: System Account (r:1 w:1) fn claim_payout() -> Weight { - (51_510_000 as Weight) + (54_743_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } @@ -342,7 +340,7 @@ impl WeightInfo for () { // Storage: NominationPools SubPoolsStorage (r:1 w:1) // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) fn unbond() -> Weight { - (120_744_000 as Weight) + (124_684_000 as Weight) .saturating_add(RocksDbWeight::get().reads(18 as Weight)) .saturating_add(RocksDbWeight::get().writes(13 as Weight)) } @@ -351,9 +349,9 @@ impl WeightInfo for () { // Storage: Staking CurrentEra (r:1 w:0) // Storage: Balances Locks (r:1 w:1) fn pool_withdraw_unbonded(s: u32, ) -> Weight { - (41_267_000 as Weight) + (44_259_000 as Weight) // Standard Error: 0 - .saturating_add((53_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((51_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -366,9 +364,9 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) // Storage: NominationPools CounterForPoolMembers (r:1 w:1) fn withdraw_unbonded_update(s: u32, ) -> Weight { - (82_234_000 as Weight) + (84_854_000 as Weight) // Standard Error: 0 - .saturating_add((48_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((56_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(8 as Weight)) .saturating_add(RocksDbWeight::get().writes(7 as Weight)) } @@ -391,10 +389,8 @@ impl WeightInfo for () { // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) // Storage: NominationPools CounterForBondedPools (r:1 w:1) // Storage: Staking Payee (r:0 w:1) - fn withdraw_unbonded_kill(s: u32, ) -> Weight { - (142_699_000 as Weight) - // Standard Error: 0 - .saturating_add((5_000 as Weight).saturating_mul(s as Weight)) + fn withdraw_unbonded_kill(_s: u32, ) -> Weight { + (146_992_000 as Weight) .saturating_add(RocksDbWeight::get().reads(19 as Weight)) .saturating_add(RocksDbWeight::get().writes(16 as Weight)) } @@ -421,7 +417,7 @@ impl WeightInfo for () { // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn create() -> Weight { - (133_702_000 as Weight) + (138_099_000 as Weight) .saturating_add(RocksDbWeight::get().reads(22 as Weight)) .saturating_add(RocksDbWeight::get().writes(15 as Weight)) } @@ -438,9 +434,9 @@ impl WeightInfo for () { // Storage: BagsList CounterForListNodes (r:1 w:1) // Storage: Staking CounterForNominators (r:1 w:1) fn nominate(n: u32, ) -> Weight { - (46_887_000 as Weight) - // Standard Error: 12_000 - .saturating_add((2_277_000 as Weight).saturating_mul(n as Weight)) + (50_964_000 as Weight) + // Standard Error: 11_000 + .saturating_add((2_333_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(12 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) @@ -448,7 +444,7 @@ impl WeightInfo for () { // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Ledger (r:1 w:0) fn set_state() -> Weight { - (24_030_000 as Weight) + (27_196_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -456,9 +452,9 @@ impl WeightInfo for () { // Storage: NominationPools Metadata (r:1 w:1) // Storage: NominationPools CounterForMetadata (r:1 w:1) fn set_metadata(n: u32, ) -> Weight { - (11_336_000 as Weight) + (15_056_000 as Weight) // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(n as Weight)) + .saturating_add((1_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -468,12 +464,12 @@ impl WeightInfo for () { // Storage: NominationPools MinCreateBond (r:0 w:1) // Storage: NominationPools MaxPools (r:0 w:1) fn set_configs() -> Weight { - (2_927_000 as Weight) + (6_294_000 as Weight) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } // Storage: NominationPools BondedPools (r:1 w:1) fn update_roles() -> Weight { - (19_184_000 as Weight) + (22_444_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } diff --git a/frame/preimage/src/weights.rs b/frame/preimage/src/weights.rs index 801ad627e07d9..de3eb6607fe8c 100644 --- a/frame/preimage/src/weights.rs +++ b/frame/preimage/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_preimage //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -91,58 +91,58 @@ impl WeightInfo for SubstrateWeight { // Storage: Preimage StatusFor (r:1 w:1) // Storage: Preimage PreimageFor (r:0 w:1) fn unnote_preimage() -> Weight { - (38_459_000 as Weight) + (44_380_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Preimage StatusFor (r:1 w:1) // Storage: Preimage PreimageFor (r:0 w:1) fn unnote_no_deposit_preimage() -> Weight { - (24_172_000 as Weight) + (30_280_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Preimage StatusFor (r:1 w:1) fn request_preimage() -> Weight { - (37_200_000 as Weight) + (42_809_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Preimage StatusFor (r:1 w:1) fn request_no_deposit_preimage() -> Weight { - (24_071_000 as Weight) + (28_964_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Preimage StatusFor (r:1 w:1) fn request_unnoted_preimage() -> Weight { - (13_974_000 as Weight) + (17_555_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Preimage StatusFor (r:1 w:1) fn request_requested_preimage() -> Weight { - (4_401_000 as Weight) + (7_745_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Preimage StatusFor (r:1 w:1) // Storage: Preimage PreimageFor (r:0 w:1) fn unrequest_preimage() -> Weight { - (24_756_000 as Weight) + (29_758_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Preimage StatusFor (r:1 w:1) // Storage: Preimage PreimageFor (r:0 w:1) fn unrequest_unnoted_preimage() -> Weight { - (14_351_000 as Weight) + (18_360_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Preimage StatusFor (r:1 w:1) fn unrequest_multi_referenced_preimage() -> Weight { - (4_359_000 as Weight) + (7_439_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -180,58 +180,58 @@ impl WeightInfo for () { // Storage: Preimage StatusFor (r:1 w:1) // Storage: Preimage PreimageFor (r:0 w:1) fn unnote_preimage() -> Weight { - (38_459_000 as Weight) + (44_380_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Preimage StatusFor (r:1 w:1) // Storage: Preimage PreimageFor (r:0 w:1) fn unnote_no_deposit_preimage() -> Weight { - (24_172_000 as Weight) + (30_280_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Preimage StatusFor (r:1 w:1) fn request_preimage() -> Weight { - (37_200_000 as Weight) + (42_809_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Preimage StatusFor (r:1 w:1) fn request_no_deposit_preimage() -> Weight { - (24_071_000 as Weight) + (28_964_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Preimage StatusFor (r:1 w:1) fn request_unnoted_preimage() -> Weight { - (13_974_000 as Weight) + (17_555_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Preimage StatusFor (r:1 w:1) fn request_requested_preimage() -> Weight { - (4_401_000 as Weight) + (7_745_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Preimage StatusFor (r:1 w:1) // Storage: Preimage PreimageFor (r:0 w:1) fn unrequest_preimage() -> Weight { - (24_756_000 as Weight) + (29_758_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Preimage StatusFor (r:1 w:1) // Storage: Preimage PreimageFor (r:0 w:1) fn unrequest_unnoted_preimage() -> Weight { - (14_351_000 as Weight) + (18_360_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Preimage StatusFor (r:1 w:1) fn unrequest_multi_referenced_preimage() -> Weight { - (4_359_000 as Weight) + (7_439_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } diff --git a/frame/proxy/src/weights.rs b/frame/proxy/src/weights.rs index a0df59204f673..19beaf4d1401b 100644 --- a/frame/proxy/src/weights.rs +++ b/frame/proxy/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_proxy //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -61,42 +61,42 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: Proxy Proxies (r:1 w:0) fn proxy(p: u32, ) -> Weight { - (13_918_000 as Weight) - // Standard Error: 1_000 - .saturating_add((66_000 as Weight).saturating_mul(p as Weight)) + (17_768_000 as Weight) + // Standard Error: 2_000 + .saturating_add((76_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) } // Storage: Proxy Proxies (r:1 w:0) // Storage: Proxy Announcements (r:1 w:1) // Storage: System Account (r:1 w:1) fn proxy_announced(a: u32, p: u32, ) -> Weight { - (30_328_000 as Weight) + (35_682_000 as Weight) // Standard Error: 2_000 - .saturating_add((159_000 as Weight).saturating_mul(a as Weight)) + .saturating_add((158_000 as Weight).saturating_mul(a as Weight)) // Standard Error: 2_000 - .saturating_add((64_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((73_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Proxy Announcements (r:1 w:1) // Storage: System Account (r:1 w:1) fn remove_announcement(a: u32, p: u32, ) -> Weight { - (21_667_000 as Weight) + (25_586_000 as Weight) // Standard Error: 1_000 - .saturating_add((167_000 as Weight).saturating_mul(a as Weight)) + .saturating_add((175_000 as Weight).saturating_mul(a as Weight)) // Standard Error: 1_000 - .saturating_add((4_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((18_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Proxy Announcements (r:1 w:1) // Storage: System Account (r:1 w:1) fn reject_announcement(a: u32, p: u32, ) -> Weight { - (21_554_000 as Weight) + (25_794_000 as Weight) // Standard Error: 1_000 - .saturating_add((171_000 as Weight).saturating_mul(a as Weight)) + .saturating_add((173_000 as Weight).saturating_mul(a as Weight)) // Standard Error: 1_000 - .saturating_add((5_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((13_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -104,52 +104,52 @@ impl WeightInfo for SubstrateWeight { // Storage: Proxy Announcements (r:1 w:1) // Storage: System Account (r:1 w:1) fn announce(a: u32, p: u32, ) -> Weight { - (28_774_000 as Weight) + (33_002_000 as Weight) // Standard Error: 2_000 - .saturating_add((150_000 as Weight).saturating_mul(a as Weight)) + .saturating_add((163_000 as Weight).saturating_mul(a as Weight)) // Standard Error: 2_000 - .saturating_add((67_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((79_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Proxy Proxies (r:1 w:1) fn add_proxy(p: u32, ) -> Weight { - (23_571_000 as Weight) - // Standard Error: 1_000 - .saturating_add((110_000 as Weight).saturating_mul(p as Weight)) + (28_166_000 as Weight) + // Standard Error: 2_000 + .saturating_add((105_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Proxy Proxies (r:1 w:1) fn remove_proxy(p: u32, ) -> Weight { - (23_613_000 as Weight) - // Standard Error: 2_000 - .saturating_add((120_000 as Weight).saturating_mul(p as Weight)) + (28_128_000 as Weight) + // Standard Error: 3_000 + .saturating_add((118_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Proxy Proxies (r:1 w:1) fn remove_proxies(p: u32, ) -> Weight { - (19_968_000 as Weight) - // Standard Error: 1_000 - .saturating_add((74_000 as Weight).saturating_mul(p as Weight)) + (24_066_000 as Weight) + // Standard Error: 2_000 + .saturating_add((81_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) // Storage: Proxy Proxies (r:1 w:1) fn anonymous(p: u32, ) -> Weight { - (26_479_000 as Weight) - // Standard Error: 2_000 + (31_077_000 as Weight) + // Standard Error: 3_000 .saturating_add((37_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Proxy Proxies (r:1 w:1) fn kill_anonymous(p: u32, ) -> Weight { - (20_816_000 as Weight) - // Standard Error: 1_000 - .saturating_add((67_000 as Weight).saturating_mul(p as Weight)) + (24_657_000 as Weight) + // Standard Error: 2_000 + .saturating_add((87_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -159,42 +159,42 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { // Storage: Proxy Proxies (r:1 w:0) fn proxy(p: u32, ) -> Weight { - (13_918_000 as Weight) - // Standard Error: 1_000 - .saturating_add((66_000 as Weight).saturating_mul(p as Weight)) + (17_768_000 as Weight) + // Standard Error: 2_000 + .saturating_add((76_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) } // Storage: Proxy Proxies (r:1 w:0) // Storage: Proxy Announcements (r:1 w:1) // Storage: System Account (r:1 w:1) fn proxy_announced(a: u32, p: u32, ) -> Weight { - (30_328_000 as Weight) + (35_682_000 as Weight) // Standard Error: 2_000 - .saturating_add((159_000 as Weight).saturating_mul(a as Weight)) + .saturating_add((158_000 as Weight).saturating_mul(a as Weight)) // Standard Error: 2_000 - .saturating_add((64_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((73_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Proxy Announcements (r:1 w:1) // Storage: System Account (r:1 w:1) fn remove_announcement(a: u32, p: u32, ) -> Weight { - (21_667_000 as Weight) + (25_586_000 as Weight) // Standard Error: 1_000 - .saturating_add((167_000 as Weight).saturating_mul(a as Weight)) + .saturating_add((175_000 as Weight).saturating_mul(a as Weight)) // Standard Error: 1_000 - .saturating_add((4_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((18_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Proxy Announcements (r:1 w:1) // Storage: System Account (r:1 w:1) fn reject_announcement(a: u32, p: u32, ) -> Weight { - (21_554_000 as Weight) + (25_794_000 as Weight) // Standard Error: 1_000 - .saturating_add((171_000 as Weight).saturating_mul(a as Weight)) + .saturating_add((173_000 as Weight).saturating_mul(a as Weight)) // Standard Error: 1_000 - .saturating_add((5_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((13_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -202,52 +202,52 @@ impl WeightInfo for () { // Storage: Proxy Announcements (r:1 w:1) // Storage: System Account (r:1 w:1) fn announce(a: u32, p: u32, ) -> Weight { - (28_774_000 as Weight) + (33_002_000 as Weight) // Standard Error: 2_000 - .saturating_add((150_000 as Weight).saturating_mul(a as Weight)) + .saturating_add((163_000 as Weight).saturating_mul(a as Weight)) // Standard Error: 2_000 - .saturating_add((67_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((79_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Proxy Proxies (r:1 w:1) fn add_proxy(p: u32, ) -> Weight { - (23_571_000 as Weight) - // Standard Error: 1_000 - .saturating_add((110_000 as Weight).saturating_mul(p as Weight)) + (28_166_000 as Weight) + // Standard Error: 2_000 + .saturating_add((105_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Proxy Proxies (r:1 w:1) fn remove_proxy(p: u32, ) -> Weight { - (23_613_000 as Weight) - // Standard Error: 2_000 - .saturating_add((120_000 as Weight).saturating_mul(p as Weight)) + (28_128_000 as Weight) + // Standard Error: 3_000 + .saturating_add((118_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Proxy Proxies (r:1 w:1) fn remove_proxies(p: u32, ) -> Weight { - (19_968_000 as Weight) - // Standard Error: 1_000 - .saturating_add((74_000 as Weight).saturating_mul(p as Weight)) + (24_066_000 as Weight) + // Standard Error: 2_000 + .saturating_add((81_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) // Storage: Proxy Proxies (r:1 w:1) fn anonymous(p: u32, ) -> Weight { - (26_479_000 as Weight) - // Standard Error: 2_000 + (31_077_000 as Weight) + // Standard Error: 3_000 .saturating_add((37_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Proxy Proxies (r:1 w:1) fn kill_anonymous(p: u32, ) -> Weight { - (20_816_000 as Weight) - // Standard Error: 1_000 - .saturating_add((67_000 as Weight).saturating_mul(p as Weight)) + (24_657_000 as Weight) + // Standard Error: 2_000 + .saturating_add((87_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } diff --git a/frame/recovery/src/weights.rs b/frame/recovery/src/weights.rs index 34a58b9e51ff2..0887180a533fc 100644 --- a/frame/recovery/src/weights.rs +++ b/frame/recovery/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_recovery //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -60,35 +60,35 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: Recovery Proxy (r:1 w:0) fn as_recovered() -> Weight { - (3_732_000 as Weight) + (6_579_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) } // Storage: Recovery Proxy (r:0 w:1) fn set_recovered() -> Weight { - (10_201_000 as Weight) + (13_402_000 as Weight) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Recovery Recoverable (r:1 w:1) fn create_recovery(n: u32, ) -> Weight { - (23_334_000 as Weight) + (28_217_000 as Weight) // Standard Error: 13_000 - .saturating_add((201_000 as Weight).saturating_mul(n as Weight)) + .saturating_add((172_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Recovery Recoverable (r:1 w:0) // Storage: Recovery ActiveRecoveries (r:1 w:1) fn initiate_recovery() -> Weight { - (28_358_000 as Weight) + (34_082_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Recovery Recoverable (r:1 w:0) // Storage: Recovery ActiveRecoveries (r:1 w:1) fn vouch_recovery(n: u32, ) -> Weight { - (17_667_000 as Weight) - // Standard Error: 13_000 - .saturating_add((342_000 as Weight).saturating_mul(n as Weight)) + (22_038_000 as Weight) + // Standard Error: 19_000 + .saturating_add((307_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -96,33 +96,33 @@ impl WeightInfo for SubstrateWeight { // Storage: Recovery ActiveRecoveries (r:1 w:0) // Storage: Recovery Proxy (r:1 w:1) fn claim_recovery(n: u32, ) -> Weight { - (24_591_000 as Weight) - // Standard Error: 14_000 - .saturating_add((242_000 as Weight).saturating_mul(n as Weight)) + (28_621_000 as Weight) + // Standard Error: 13_000 + .saturating_add((353_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Recovery ActiveRecoveries (r:1 w:1) // Storage: System Account (r:1 w:1) fn close_recovery(n: u32, ) -> Weight { - (28_763_000 as Weight) - // Standard Error: 12_000 - .saturating_add((142_000 as Weight).saturating_mul(n as Weight)) + (33_287_000 as Weight) + // Standard Error: 19_000 + .saturating_add((264_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Recovery ActiveRecoveries (r:1 w:0) // Storage: Recovery Recoverable (r:1 w:1) fn remove_recovery(n: u32, ) -> Weight { - (27_357_000 as Weight) - // Standard Error: 17_000 - .saturating_add((212_000 as Weight).saturating_mul(n as Weight)) + (31_964_000 as Weight) + // Standard Error: 13_000 + .saturating_add((222_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Recovery Proxy (r:1 w:1) fn cancel_recovered() -> Weight { - (9_068_000 as Weight) + (12_702_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -132,35 +132,35 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { // Storage: Recovery Proxy (r:1 w:0) fn as_recovered() -> Weight { - (3_732_000 as Weight) + (6_579_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) } // Storage: Recovery Proxy (r:0 w:1) fn set_recovered() -> Weight { - (10_201_000 as Weight) + (13_402_000 as Weight) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Recovery Recoverable (r:1 w:1) fn create_recovery(n: u32, ) -> Weight { - (23_334_000 as Weight) + (28_217_000 as Weight) // Standard Error: 13_000 - .saturating_add((201_000 as Weight).saturating_mul(n as Weight)) + .saturating_add((172_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Recovery Recoverable (r:1 w:0) // Storage: Recovery ActiveRecoveries (r:1 w:1) fn initiate_recovery() -> Weight { - (28_358_000 as Weight) + (34_082_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Recovery Recoverable (r:1 w:0) // Storage: Recovery ActiveRecoveries (r:1 w:1) fn vouch_recovery(n: u32, ) -> Weight { - (17_667_000 as Weight) - // Standard Error: 13_000 - .saturating_add((342_000 as Weight).saturating_mul(n as Weight)) + (22_038_000 as Weight) + // Standard Error: 19_000 + .saturating_add((307_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -168,33 +168,33 @@ impl WeightInfo for () { // Storage: Recovery ActiveRecoveries (r:1 w:0) // Storage: Recovery Proxy (r:1 w:1) fn claim_recovery(n: u32, ) -> Weight { - (24_591_000 as Weight) - // Standard Error: 14_000 - .saturating_add((242_000 as Weight).saturating_mul(n as Weight)) + (28_621_000 as Weight) + // Standard Error: 13_000 + .saturating_add((353_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Recovery ActiveRecoveries (r:1 w:1) // Storage: System Account (r:1 w:1) fn close_recovery(n: u32, ) -> Weight { - (28_763_000 as Weight) - // Standard Error: 12_000 - .saturating_add((142_000 as Weight).saturating_mul(n as Weight)) + (33_287_000 as Weight) + // Standard Error: 19_000 + .saturating_add((264_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Recovery ActiveRecoveries (r:1 w:0) // Storage: Recovery Recoverable (r:1 w:1) fn remove_recovery(n: u32, ) -> Weight { - (27_357_000 as Weight) - // Standard Error: 17_000 - .saturating_add((212_000 as Weight).saturating_mul(n as Weight)) + (31_964_000 as Weight) + // Standard Error: 13_000 + .saturating_add((222_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Recovery Proxy (r:1 w:1) fn cancel_recovered() -> Weight { - (9_068_000 as Weight) + (12_702_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } diff --git a/frame/referenda/src/weights.rs b/frame/referenda/src/weights.rs index d9ddbed49e883..d48ebb1014d48 100644 --- a/frame/referenda/src/weights.rs +++ b/frame/referenda/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_referenda //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -80,14 +80,14 @@ impl WeightInfo for SubstrateWeight { // Storage: Scheduler Agenda (r:1 w:1) // Storage: Referenda ReferendumInfoFor (r:0 w:1) fn submit() -> Weight { - (30_596_000 as Weight) + (34_640_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn place_decision_deposit_preparing() -> Weight { - (40_115_000 as Weight) + (44_290_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -95,7 +95,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda DecidingCount (r:1 w:0) // Storage: Referenda TrackQueue (r:1 w:1) fn place_decision_deposit_queued() -> Weight { - (45_739_000 as Weight) + (49_428_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -103,7 +103,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda DecidingCount (r:1 w:0) // Storage: Referenda TrackQueue (r:1 w:1) fn place_decision_deposit_not_queued() -> Weight { - (45_741_000 as Weight) + (50_076_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -111,7 +111,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda DecidingCount (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn place_decision_deposit_passing() -> Weight { - (51_422_000 as Weight) + (55_935_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -119,34 +119,34 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda DecidingCount (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn place_decision_deposit_failing() -> Weight { - (48_714_000 as Weight) + (52_921_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) fn refund_decision_deposit() -> Weight { - (24_896_000 as Weight) + (29_160_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn cancel() -> Weight { - (30_511_000 as Weight) + (34_972_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn kill() -> Weight { - (55_662_000 as Weight) + (60_620_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: Referenda TrackQueue (r:1 w:0) // Storage: Referenda DecidingCount (r:1 w:1) fn one_fewer_deciding_queue_empty() -> Weight { - (6_659_000 as Weight) + (9_615_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -154,7 +154,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn one_fewer_deciding_failing() -> Weight { - (109_530_000 as Weight) + (113_077_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -162,7 +162,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn one_fewer_deciding_passing() -> Weight { - (110_576_000 as Weight) + (114_376_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -170,7 +170,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda TrackQueue (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_requeued_insertion() -> Weight { - (39_327_000 as Weight) + (43_901_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -178,7 +178,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda TrackQueue (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_requeued_slide() -> Weight { - (39_179_000 as Weight) + (43_279_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -187,7 +187,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda TrackQueue (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_queued() -> Weight { - (41_251_000 as Weight) + (45_564_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -196,27 +196,27 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda TrackQueue (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_not_queued() -> Weight { - (41_009_000 as Weight) + (45_061_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_no_deposit() -> Weight { - (20_228_000 as Weight) + (23_757_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_preparing() -> Weight { - (20_456_000 as Weight) + (24_781_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) fn nudge_referendum_timed_out() -> Weight { - (16_202_000 as Weight) + (18_344_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -224,7 +224,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda DecidingCount (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_begin_deciding_failing() -> Weight { - (30_335_000 as Weight) + (34_752_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -232,35 +232,35 @@ impl WeightInfo for SubstrateWeight { // Storage: Referenda DecidingCount (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_begin_deciding_passing() -> Weight { - (32_700_000 as Weight) + (37_055_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_begin_confirming() -> Weight { - (27_225_000 as Weight) + (31_442_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_end_confirming() -> Weight { - (28_355_000 as Weight) + (33_201_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_continue_not_confirming() -> Weight { - (26_515_000 as Weight) + (30_047_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_continue_confirming() -> Weight { - (24_991_000 as Weight) + (29_195_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -269,14 +269,14 @@ impl WeightInfo for SubstrateWeight { // Storage: Scheduler Lookup (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) fn nudge_referendum_approved() -> Weight { - (45_164_000 as Weight) + (50_119_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_rejected() -> Weight { - (28_026_000 as Weight) + (32_203_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -288,14 +288,14 @@ impl WeightInfo for () { // Storage: Scheduler Agenda (r:1 w:1) // Storage: Referenda ReferendumInfoFor (r:0 w:1) fn submit() -> Weight { - (30_596_000 as Weight) + (34_640_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn place_decision_deposit_preparing() -> Weight { - (40_115_000 as Weight) + (44_290_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -303,7 +303,7 @@ impl WeightInfo for () { // Storage: Referenda DecidingCount (r:1 w:0) // Storage: Referenda TrackQueue (r:1 w:1) fn place_decision_deposit_queued() -> Weight { - (45_739_000 as Weight) + (49_428_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -311,7 +311,7 @@ impl WeightInfo for () { // Storage: Referenda DecidingCount (r:1 w:0) // Storage: Referenda TrackQueue (r:1 w:1) fn place_decision_deposit_not_queued() -> Weight { - (45_741_000 as Weight) + (50_076_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -319,7 +319,7 @@ impl WeightInfo for () { // Storage: Referenda DecidingCount (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn place_decision_deposit_passing() -> Weight { - (51_422_000 as Weight) + (55_935_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } @@ -327,34 +327,34 @@ impl WeightInfo for () { // Storage: Referenda DecidingCount (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn place_decision_deposit_failing() -> Weight { - (48_714_000 as Weight) + (52_921_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) fn refund_decision_deposit() -> Weight { - (24_896_000 as Weight) + (29_160_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn cancel() -> Weight { - (30_511_000 as Weight) + (34_972_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn kill() -> Weight { - (55_662_000 as Weight) + (60_620_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: Referenda TrackQueue (r:1 w:0) // Storage: Referenda DecidingCount (r:1 w:1) fn one_fewer_deciding_queue_empty() -> Weight { - (6_659_000 as Weight) + (9_615_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -362,7 +362,7 @@ impl WeightInfo for () { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn one_fewer_deciding_failing() -> Weight { - (109_530_000 as Weight) + (113_077_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } @@ -370,7 +370,7 @@ impl WeightInfo for () { // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:2 w:2) fn one_fewer_deciding_passing() -> Weight { - (110_576_000 as Weight) + (114_376_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } @@ -378,7 +378,7 @@ impl WeightInfo for () { // Storage: Referenda TrackQueue (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_requeued_insertion() -> Weight { - (39_327_000 as Weight) + (43_901_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -386,7 +386,7 @@ impl WeightInfo for () { // Storage: Referenda TrackQueue (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_requeued_slide() -> Weight { - (39_179_000 as Weight) + (43_279_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -395,7 +395,7 @@ impl WeightInfo for () { // Storage: Referenda TrackQueue (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_queued() -> Weight { - (41_251_000 as Weight) + (45_564_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -404,27 +404,27 @@ impl WeightInfo for () { // Storage: Referenda TrackQueue (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_not_queued() -> Weight { - (41_009_000 as Weight) + (45_061_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_no_deposit() -> Weight { - (20_228_000 as Weight) + (23_757_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_preparing() -> Weight { - (20_456_000 as Weight) + (24_781_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) fn nudge_referendum_timed_out() -> Weight { - (16_202_000 as Weight) + (18_344_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -432,7 +432,7 @@ impl WeightInfo for () { // Storage: Referenda DecidingCount (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_begin_deciding_failing() -> Weight { - (30_335_000 as Weight) + (34_752_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -440,35 +440,35 @@ impl WeightInfo for () { // Storage: Referenda DecidingCount (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_begin_deciding_passing() -> Weight { - (32_700_000 as Weight) + (37_055_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_begin_confirming() -> Weight { - (27_225_000 as Weight) + (31_442_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_end_confirming() -> Weight { - (28_355_000 as Weight) + (33_201_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_continue_not_confirming() -> Weight { - (26_515_000 as Weight) + (30_047_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_continue_confirming() -> Weight { - (24_991_000 as Weight) + (29_195_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -477,14 +477,14 @@ impl WeightInfo for () { // Storage: Scheduler Lookup (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) fn nudge_referendum_approved() -> Weight { - (45_164_000 as Weight) + (50_119_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } // Storage: Referenda ReferendumInfoFor (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn nudge_referendum_rejected() -> Weight { - (28_026_000 as Weight) + (32_203_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } diff --git a/frame/remark/src/weights.rs b/frame/remark/src/weights.rs index b667a03bbd472..b8bd4618f8def 100644 --- a/frame/remark/src/weights.rs +++ b/frame/remark/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_remark //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -52,7 +52,7 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) fn store(l: u32, ) -> Weight { - (9_699_000 as Weight) + (13_140_000 as Weight) // Standard Error: 0 .saturating_add((1_000 as Weight).saturating_mul(l as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) @@ -63,7 +63,7 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) fn store(l: u32, ) -> Weight { - (9_699_000 as Weight) + (13_140_000 as Weight) // Standard Error: 0 .saturating_add((1_000 as Weight).saturating_mul(l as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) diff --git a/frame/scheduler/src/weights.rs b/frame/scheduler/src/weights.rs index d56c3208b44af..dd7ed8104420d 100644 --- a/frame/scheduler/src/weights.rs +++ b/frame/scheduler/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_scheduler //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -68,9 +68,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Preimage StatusFor (r:1 w:1) // Storage: Scheduler Lookup (r:0 w:1) fn on_initialize_periodic_named_resolved(s: u32, ) -> Weight { - (9_814_000 as Weight) - // Standard Error: 13_000 - .saturating_add((18_293_000 as Weight).saturating_mul(s as Weight)) + (9_994_000 as Weight) + // Standard Error: 20_000 + .saturating_add((19_843_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(s as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -81,9 +81,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Preimage StatusFor (r:1 w:1) // Storage: Scheduler Lookup (r:0 w:1) fn on_initialize_named_resolved(s: u32, ) -> Weight { - (9_050_000 as Weight) - // Standard Error: 11_000 - .saturating_add((14_029_000 as Weight).saturating_mul(s as Weight)) + (10_318_000 as Weight) + // Standard Error: 17_000 + .saturating_add((15_451_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(s as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -93,9 +93,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Preimage PreimageFor (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) fn on_initialize_periodic_resolved(s: u32, ) -> Weight { - (9_717_000 as Weight) - // Standard Error: 14_000 - .saturating_add((15_584_000 as Weight).saturating_mul(s as Weight)) + (11_675_000 as Weight) + // Standard Error: 17_000 + .saturating_add((17_019_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(s as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -105,9 +105,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Preimage PreimageFor (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) fn on_initialize_resolved(s: u32, ) -> Weight { - (9_422_000 as Weight) - // Standard Error: 9_000 - .saturating_add((12_812_000 as Weight).saturating_mul(s as Weight)) + (11_934_000 as Weight) + // Standard Error: 11_000 + .saturating_add((14_134_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(s as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -117,9 +117,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Preimage PreimageFor (r:1 w:0) // Storage: Scheduler Lookup (r:0 w:1) fn on_initialize_named_aborted(s: u32, ) -> Weight { - (6_504_000 as Weight) - // Standard Error: 7_000 - .saturating_add((5_410_000 as Weight).saturating_mul(s as Weight)) + (7_279_000 as Weight) + // Standard Error: 5_000 + .saturating_add((5_388_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(s as Weight))) .saturating_add(T::DbWeight::get().writes(2 as Weight)) @@ -128,9 +128,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Scheduler Agenda (r:2 w:2) // Storage: Preimage PreimageFor (r:1 w:0) fn on_initialize_aborted(s: u32, ) -> Weight { - (8_690_000 as Weight) - // Standard Error: 3_000 - .saturating_add((2_951_000 as Weight).saturating_mul(s as Weight)) + (8_619_000 as Weight) + // Standard Error: 4_000 + .saturating_add((2_969_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(s as Weight))) .saturating_add(T::DbWeight::get().writes(2 as Weight)) @@ -138,9 +138,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Scheduler Agenda (r:2 w:2) // Storage: Scheduler Lookup (r:0 w:1) fn on_initialize_periodic_named(s: u32, ) -> Weight { - (14_613_000 as Weight) - // Standard Error: 5_000 - .saturating_add((8_479_000 as Weight).saturating_mul(s as Weight)) + (16_129_000 as Weight) + // Standard Error: 7_000 + .saturating_add((9_772_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(s as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -148,9 +148,9 @@ impl WeightInfo for SubstrateWeight { } // Storage: Scheduler Agenda (r:2 w:2) fn on_initialize_periodic(s: u32, ) -> Weight { - (13_356_000 as Weight) - // Standard Error: 4_000 - .saturating_add((6_112_000 as Weight).saturating_mul(s as Weight)) + (15_785_000 as Weight) + // Standard Error: 5_000 + .saturating_add((7_208_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(s as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -159,33 +159,33 @@ impl WeightInfo for SubstrateWeight { // Storage: Scheduler Agenda (r:1 w:1) // Storage: Scheduler Lookup (r:0 w:1) fn on_initialize_named(s: u32, ) -> Weight { - (14_254_000 as Weight) + (15_778_000 as Weight) // Standard Error: 3_000 - .saturating_add((4_529_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((5_597_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) } // Storage: Scheduler Agenda (r:1 w:1) fn on_initialize(s: u32, ) -> Weight { - (13_680_000 as Weight) - // Standard Error: 4_000 - .saturating_add((3_559_000 as Weight).saturating_mul(s as Weight)) + (15_912_000 as Weight) + // Standard Error: 5_000 + .saturating_add((4_530_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Scheduler Agenda (r:1 w:1) fn schedule(s: u32, ) -> Weight { - (14_821_000 as Weight) + (18_013_000 as Weight) // Standard Error: 1_000 - .saturating_add((85_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((87_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Scheduler Agenda (r:1 w:1) // Storage: Scheduler Lookup (r:0 w:1) fn cancel(s: u32, ) -> Weight { - (15_029_000 as Weight) + (18_131_000 as Weight) // Standard Error: 1_000 .saturating_add((595_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) @@ -194,18 +194,18 @@ impl WeightInfo for SubstrateWeight { // Storage: Scheduler Lookup (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn schedule_named(s: u32, ) -> Weight { - (17_498_000 as Weight) + (21_230_000 as Weight) // Standard Error: 1_000 - .saturating_add((102_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((98_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Scheduler Lookup (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn cancel_named(s: u32, ) -> Weight { - (16_695_000 as Weight) + (20_139_000 as Weight) // Standard Error: 1_000 - .saturating_add((610_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((595_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -218,9 +218,9 @@ impl WeightInfo for () { // Storage: Preimage StatusFor (r:1 w:1) // Storage: Scheduler Lookup (r:0 w:1) fn on_initialize_periodic_named_resolved(s: u32, ) -> Weight { - (9_814_000 as Weight) - // Standard Error: 13_000 - .saturating_add((18_293_000 as Weight).saturating_mul(s as Weight)) + (9_994_000 as Weight) + // Standard Error: 20_000 + .saturating_add((19_843_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(s as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -231,9 +231,9 @@ impl WeightInfo for () { // Storage: Preimage StatusFor (r:1 w:1) // Storage: Scheduler Lookup (r:0 w:1) fn on_initialize_named_resolved(s: u32, ) -> Weight { - (9_050_000 as Weight) - // Standard Error: 11_000 - .saturating_add((14_029_000 as Weight).saturating_mul(s as Weight)) + (10_318_000 as Weight) + // Standard Error: 17_000 + .saturating_add((15_451_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().reads((2 as Weight).saturating_mul(s as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -243,9 +243,9 @@ impl WeightInfo for () { // Storage: Preimage PreimageFor (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) fn on_initialize_periodic_resolved(s: u32, ) -> Weight { - (9_717_000 as Weight) - // Standard Error: 14_000 - .saturating_add((15_584_000 as Weight).saturating_mul(s as Weight)) + (11_675_000 as Weight) + // Standard Error: 17_000 + .saturating_add((17_019_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(s as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -255,9 +255,9 @@ impl WeightInfo for () { // Storage: Preimage PreimageFor (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) fn on_initialize_resolved(s: u32, ) -> Weight { - (9_422_000 as Weight) - // Standard Error: 9_000 - .saturating_add((12_812_000 as Weight).saturating_mul(s as Weight)) + (11_934_000 as Weight) + // Standard Error: 11_000 + .saturating_add((14_134_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().reads((2 as Weight).saturating_mul(s as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -267,9 +267,9 @@ impl WeightInfo for () { // Storage: Preimage PreimageFor (r:1 w:0) // Storage: Scheduler Lookup (r:0 w:1) fn on_initialize_named_aborted(s: u32, ) -> Weight { - (6_504_000 as Weight) - // Standard Error: 7_000 - .saturating_add((5_410_000 as Weight).saturating_mul(s as Weight)) + (7_279_000 as Weight) + // Standard Error: 5_000 + .saturating_add((5_388_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(s as Weight))) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) @@ -278,9 +278,9 @@ impl WeightInfo for () { // Storage: Scheduler Agenda (r:2 w:2) // Storage: Preimage PreimageFor (r:1 w:0) fn on_initialize_aborted(s: u32, ) -> Weight { - (8_690_000 as Weight) - // Standard Error: 3_000 - .saturating_add((2_951_000 as Weight).saturating_mul(s as Weight)) + (8_619_000 as Weight) + // Standard Error: 4_000 + .saturating_add((2_969_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(s as Weight))) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) @@ -288,9 +288,9 @@ impl WeightInfo for () { // Storage: Scheduler Agenda (r:2 w:2) // Storage: Scheduler Lookup (r:0 w:1) fn on_initialize_periodic_named(s: u32, ) -> Weight { - (14_613_000 as Weight) - // Standard Error: 5_000 - .saturating_add((8_479_000 as Weight).saturating_mul(s as Weight)) + (16_129_000 as Weight) + // Standard Error: 7_000 + .saturating_add((9_772_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(s as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -298,9 +298,9 @@ impl WeightInfo for () { } // Storage: Scheduler Agenda (r:2 w:2) fn on_initialize_periodic(s: u32, ) -> Weight { - (13_356_000 as Weight) - // Standard Error: 4_000 - .saturating_add((6_112_000 as Weight).saturating_mul(s as Weight)) + (15_785_000 as Weight) + // Standard Error: 5_000 + .saturating_add((7_208_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(s as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -309,33 +309,33 @@ impl WeightInfo for () { // Storage: Scheduler Agenda (r:1 w:1) // Storage: Scheduler Lookup (r:0 w:1) fn on_initialize_named(s: u32, ) -> Weight { - (14_254_000 as Weight) + (15_778_000 as Weight) // Standard Error: 3_000 - .saturating_add((4_529_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((5_597_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) } // Storage: Scheduler Agenda (r:1 w:1) fn on_initialize(s: u32, ) -> Weight { - (13_680_000 as Weight) - // Standard Error: 4_000 - .saturating_add((3_559_000 as Weight).saturating_mul(s as Weight)) + (15_912_000 as Weight) + // Standard Error: 5_000 + .saturating_add((4_530_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Scheduler Agenda (r:1 w:1) fn schedule(s: u32, ) -> Weight { - (14_821_000 as Weight) + (18_013_000 as Weight) // Standard Error: 1_000 - .saturating_add((85_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((87_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Scheduler Agenda (r:1 w:1) // Storage: Scheduler Lookup (r:0 w:1) fn cancel(s: u32, ) -> Weight { - (15_029_000 as Weight) + (18_131_000 as Weight) // Standard Error: 1_000 .saturating_add((595_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) @@ -344,18 +344,18 @@ impl WeightInfo for () { // Storage: Scheduler Lookup (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn schedule_named(s: u32, ) -> Weight { - (17_498_000 as Weight) + (21_230_000 as Weight) // Standard Error: 1_000 - .saturating_add((102_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((98_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Scheduler Lookup (r:1 w:1) // Storage: Scheduler Agenda (r:1 w:1) fn cancel_named(s: u32, ) -> Weight { - (16_695_000 as Weight) + (20_139_000 as Weight) // Standard Error: 1_000 - .saturating_add((610_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((595_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } diff --git a/frame/session/src/weights.rs b/frame/session/src/weights.rs index eabb26dcb32f4..40ae7f1be4265 100644 --- a/frame/session/src/weights.rs +++ b/frame/session/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_session //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -55,7 +55,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Session NextKeys (r:1 w:1) // Storage: Session KeyOwner (r:4 w:4) fn set_keys() -> Weight { - (43_230_000 as Weight) + (48_484_000 as Weight) .saturating_add(T::DbWeight::get().reads(6 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } @@ -63,7 +63,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Session NextKeys (r:1 w:1) // Storage: Session KeyOwner (r:0 w:4) fn purge_keys() -> Weight { - (33_000_000 as Weight) + (38_003_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } @@ -75,7 +75,7 @@ impl WeightInfo for () { // Storage: Session NextKeys (r:1 w:1) // Storage: Session KeyOwner (r:4 w:4) fn set_keys() -> Weight { - (43_230_000 as Weight) + (48_484_000 as Weight) .saturating_add(RocksDbWeight::get().reads(6 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } @@ -83,7 +83,7 @@ impl WeightInfo for () { // Storage: Session NextKeys (r:1 w:1) // Storage: Session KeyOwner (r:0 w:4) fn purge_keys() -> Weight { - (33_000_000 as Weight) + (38_003_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } diff --git a/frame/staking/src/weights.rs b/frame/staking/src/weights.rs index e7f2e06e6dd07..1bdfb01bddc86 100644 --- a/frame/staking/src/weights.rs +++ b/frame/staking/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_staking //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-24, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -86,7 +86,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn bond() -> Weight { - (38_923_000 as Weight) + (43_992_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -96,7 +96,7 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn bond_extra() -> Weight { - (68_860_000 as Weight) + (75_827_000 as Weight) .saturating_add(T::DbWeight::get().reads(8 as Weight)) .saturating_add(T::DbWeight::get().writes(7 as Weight)) } @@ -110,7 +110,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking Bonded (r:1 w:0) // Storage: BagsList ListBags (r:2 w:2) fn unbond() -> Weight { - (74_815_000 as Weight) + (81_075_000 as Weight) .saturating_add(T::DbWeight::get().reads(12 as Weight)) .saturating_add(T::DbWeight::get().writes(8 as Weight)) } @@ -119,7 +119,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) fn withdraw_unbonded_update(s: u32, ) -> Weight { - (30_797_000 as Weight) + (35_763_000 as Weight) // Standard Error: 0 .saturating_add((57_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) @@ -139,7 +139,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn withdraw_unbonded_kill(_s: u32, ) -> Weight { - (60_515_000 as Weight) + (66_938_000 as Weight) .saturating_add(T::DbWeight::get().reads(13 as Weight)) .saturating_add(T::DbWeight::get().writes(11 as Weight)) } @@ -155,16 +155,16 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList CounterForListNodes (r:1 w:1) // Storage: Staking CounterForValidators (r:1 w:1) fn validate() -> Weight { - (48_461_000 as Weight) + (52_943_000 as Weight) .saturating_add(T::DbWeight::get().reads(11 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking Nominators (r:1 w:1) fn kick(k: u32, ) -> Weight { - (18_839_000 as Weight) + (23_264_000 as Weight) // Standard Error: 11_000 - .saturating_add((7_658_000 as Weight).saturating_mul(k as Weight)) + .saturating_add((8_006_000 as Weight).saturating_mul(k as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(k as Weight))) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(k as Weight))) @@ -181,9 +181,9 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList CounterForListNodes (r:1 w:1) // Storage: Staking CounterForNominators (r:1 w:1) fn nominate(n: u32, ) -> Weight { - (51_547_000 as Weight) - // Standard Error: 10_000 - .saturating_add((3_610_000 as Weight).saturating_mul(n as Weight)) + (56_596_000 as Weight) + // Standard Error: 14_000 + .saturating_add((3_644_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(12 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) .saturating_add(T::DbWeight::get().writes(6 as Weight)) @@ -196,49 +196,49 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList ListBags (r:1 w:1) // Storage: BagsList CounterForListNodes (r:1 w:1) fn chill() -> Weight { - (45_828_000 as Weight) + (51_117_000 as Weight) .saturating_add(T::DbWeight::get().reads(8 as Weight)) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking Payee (r:0 w:1) fn set_payee() -> Weight { - (7_646_000 as Weight) + (11_223_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Staking Bonded (r:1 w:1) // Storage: Staking Ledger (r:2 w:2) fn set_controller() -> Weight { - (15_974_000 as Weight) + (19_826_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: Staking ValidatorCount (r:0 w:1) fn set_validator_count() -> Weight { - (1_248_000 as Weight) + (3_789_000 as Weight) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Staking ForceEra (r:0 w:1) fn force_no_eras() -> Weight { - (1_350_000 as Weight) + (3_793_000 as Weight) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Staking ForceEra (r:0 w:1) fn force_new_era() -> Weight { - (1_276_000 as Weight) + (3_802_000 as Weight) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Staking ForceEra (r:0 w:1) fn force_new_era_always() -> Weight { - (1_321_000 as Weight) + (3_762_000 as Weight) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Staking Invulnerables (r:0 w:1) fn set_invulnerables(v: u32, ) -> Weight { - (1_517_000 as Weight) + (4_318_000 as Weight) // Standard Error: 0 - .saturating_add((9_000 as Weight).saturating_mul(v as Weight)) + .saturating_add((10_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Staking Bonded (r:1 w:1) @@ -255,18 +255,18 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking Payee (r:0 w:1) // Storage: Staking SpanSlash (r:0 w:2) fn force_unstake(s: u32, ) -> Weight { - (58_451_000 as Weight) + (65_265_000 as Weight) // Standard Error: 1_000 - .saturating_add((835_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((1_029_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(11 as Weight)) .saturating_add(T::DbWeight::get().writes(12 as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) } // Storage: Staking UnappliedSlashes (r:1 w:1) fn cancel_deferred_slash(s: u32, ) -> Weight { - (895_257_000 as Weight) + (903_312_000 as Weight) // Standard Error: 56_000 - .saturating_add((4_978_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((4_968_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -281,9 +281,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking Payee (r:2 w:0) // Storage: System Account (r:2 w:2) fn payout_stakers_dead_controller(n: u32, ) -> Weight { - (71_290_000 as Weight) - // Standard Error: 12_000 - .saturating_add((23_818_000 as Weight).saturating_mul(n as Weight)) + (87_569_000 as Weight) + // Standard Error: 14_000 + .saturating_add((24_232_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(10 as Weight)) .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(n as Weight))) .saturating_add(T::DbWeight::get().writes(2 as Weight)) @@ -301,9 +301,9 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:2 w:2) // Storage: Balances Locks (r:2 w:2) fn payout_stakers_alive_staked(n: u32, ) -> Weight { - (86_724_000 as Weight) - // Standard Error: 29_000 - .saturating_add((33_575_000 as Weight).saturating_mul(n as Weight)) + (98_839_000 as Weight) + // Standard Error: 21_000 + .saturating_add((34_480_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(11 as Weight)) .saturating_add(T::DbWeight::get().reads((5 as Weight).saturating_mul(n as Weight))) .saturating_add(T::DbWeight::get().writes(3 as Weight)) @@ -316,9 +316,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking Bonded (r:1 w:0) // Storage: BagsList ListBags (r:2 w:2) fn rebond(l: u32, ) -> Weight { - (68_577_000 as Weight) + (74_865_000 as Weight) // Standard Error: 3_000 - .saturating_add((55_000 as Weight).saturating_mul(l as Weight)) + .saturating_add((64_000 as Weight).saturating_mul(l as Weight)) .saturating_add(T::DbWeight::get().reads(9 as Weight)) .saturating_add(T::DbWeight::get().writes(8 as Weight)) } @@ -333,8 +333,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking ErasStartSessionIndex (r:0 w:1) fn set_history_depth(e: u32, ) -> Weight { (0 as Weight) - // Standard Error: 58_000 - .saturating_add((20_425_000 as Weight).saturating_mul(e as Weight)) + // Standard Error: 62_000 + .saturating_add((22_829_000 as Weight).saturating_mul(e as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) .saturating_add(T::DbWeight::get().writes((7 as Weight).saturating_mul(e as Weight))) @@ -353,9 +353,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking Payee (r:0 w:1) // Storage: Staking SpanSlash (r:0 w:1) fn reap_stash(s: u32, ) -> Weight { - (63_865_000 as Weight) - // Standard Error: 0 - .saturating_add((839_000 as Weight).saturating_mul(s as Weight)) + (70_933_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_021_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(12 as Weight)) .saturating_add(T::DbWeight::get().writes(12 as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) @@ -380,10 +380,10 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking ErasStartSessionIndex (r:0 w:1) fn new_era(v: u32, n: u32, ) -> Weight { (0 as Weight) - // Standard Error: 881_000 - .saturating_add((211_756_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 44_000 - .saturating_add((30_413_000 as Weight).saturating_mul(n as Weight)) + // Standard Error: 897_000 + .saturating_add((213_100_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 45_000 + .saturating_add((31_123_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(208 as Weight)) .saturating_add(T::DbWeight::get().reads((5 as Weight).saturating_mul(v as Weight))) .saturating_add(T::DbWeight::get().reads((4 as Weight).saturating_mul(n as Weight))) @@ -400,12 +400,12 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking Ledger (r:1500 w:0) fn get_npos_voters(v: u32, n: u32, s: u32, ) -> Weight { (0 as Weight) - // Standard Error: 86_000 - .saturating_add((23_814_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 86_000 - .saturating_add((21_657_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 2_924_000 - .saturating_add((19_067_000 as Weight).saturating_mul(s as Weight)) + // Standard Error: 116_000 + .saturating_add((23_745_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 116_000 + .saturating_add((22_497_000 as Weight).saturating_mul(n as Weight)) + // Standard Error: 3_968_000 + .saturating_add((20_676_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(202 as Weight)) .saturating_add(T::DbWeight::get().reads((5 as Weight).saturating_mul(v as Weight))) .saturating_add(T::DbWeight::get().reads((4 as Weight).saturating_mul(n as Weight))) @@ -414,8 +414,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking Validators (r:501 w:0) fn get_npos_targets(v: u32, ) -> Weight { (0 as Weight) - // Standard Error: 32_000 - .saturating_add((7_861_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 36_000 + .saturating_add((8_097_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(v as Weight))) } @@ -426,7 +426,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking MaxNominatorsCount (r:0 w:1) // Storage: Staking MinNominatorBond (r:0 w:1) fn set_staking_configs_all_set() -> Weight { - (3_688_000 as Weight) + (7_041_000 as Weight) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } // Storage: Staking MinCommission (r:0 w:1) @@ -436,7 +436,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking MaxNominatorsCount (r:0 w:1) // Storage: Staking MinNominatorBond (r:0 w:1) fn set_staking_configs_all_remove() -> Weight { - (3_414_000 as Weight) + (6_495_000 as Weight) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } // Storage: Staking Ledger (r:1 w:0) @@ -450,14 +450,14 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList ListBags (r:1 w:1) // Storage: BagsList CounterForListNodes (r:1 w:1) fn chill_other() -> Weight { - (56_962_000 as Weight) + (62_014_000 as Weight) .saturating_add(T::DbWeight::get().reads(11 as Weight)) .saturating_add(T::DbWeight::get().writes(6 as Weight)) } // Storage: Staking MinCommission (r:1 w:0) // Storage: Staking Validators (r:1 w:1) fn force_apply_min_commission() -> Weight { - (9_465_000 as Weight) + (12_814_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -472,7 +472,7 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn bond() -> Weight { - (38_923_000 as Weight) + (43_992_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } @@ -482,7 +482,7 @@ impl WeightInfo for () { // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn bond_extra() -> Weight { - (68_860_000 as Weight) + (75_827_000 as Weight) .saturating_add(RocksDbWeight::get().reads(8 as Weight)) .saturating_add(RocksDbWeight::get().writes(7 as Weight)) } @@ -496,7 +496,7 @@ impl WeightInfo for () { // Storage: Staking Bonded (r:1 w:0) // Storage: BagsList ListBags (r:2 w:2) fn unbond() -> Weight { - (74_815_000 as Weight) + (81_075_000 as Weight) .saturating_add(RocksDbWeight::get().reads(12 as Weight)) .saturating_add(RocksDbWeight::get().writes(8 as Weight)) } @@ -505,7 +505,7 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) fn withdraw_unbonded_update(s: u32, ) -> Weight { - (30_797_000 as Weight) + (35_763_000 as Weight) // Standard Error: 0 .saturating_add((57_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) @@ -525,7 +525,7 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn withdraw_unbonded_kill(_s: u32, ) -> Weight { - (60_515_000 as Weight) + (66_938_000 as Weight) .saturating_add(RocksDbWeight::get().reads(13 as Weight)) .saturating_add(RocksDbWeight::get().writes(11 as Weight)) } @@ -541,16 +541,16 @@ impl WeightInfo for () { // Storage: BagsList CounterForListNodes (r:1 w:1) // Storage: Staking CounterForValidators (r:1 w:1) fn validate() -> Weight { - (48_461_000 as Weight) + (52_943_000 as Weight) .saturating_add(RocksDbWeight::get().reads(11 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking Nominators (r:1 w:1) fn kick(k: u32, ) -> Weight { - (18_839_000 as Weight) + (23_264_000 as Weight) // Standard Error: 11_000 - .saturating_add((7_658_000 as Weight).saturating_mul(k as Weight)) + .saturating_add((8_006_000 as Weight).saturating_mul(k as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(k as Weight))) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(k as Weight))) @@ -567,9 +567,9 @@ impl WeightInfo for () { // Storage: BagsList CounterForListNodes (r:1 w:1) // Storage: Staking CounterForNominators (r:1 w:1) fn nominate(n: u32, ) -> Weight { - (51_547_000 as Weight) - // Standard Error: 10_000 - .saturating_add((3_610_000 as Weight).saturating_mul(n as Weight)) + (56_596_000 as Weight) + // Standard Error: 14_000 + .saturating_add((3_644_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(12 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) @@ -582,49 +582,49 @@ impl WeightInfo for () { // Storage: BagsList ListBags (r:1 w:1) // Storage: BagsList CounterForListNodes (r:1 w:1) fn chill() -> Weight { - (45_828_000 as Weight) + (51_117_000 as Weight) .saturating_add(RocksDbWeight::get().reads(8 as Weight)) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking Payee (r:0 w:1) fn set_payee() -> Weight { - (7_646_000 as Weight) + (11_223_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Staking Bonded (r:1 w:1) // Storage: Staking Ledger (r:2 w:2) fn set_controller() -> Weight { - (15_974_000 as Weight) + (19_826_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: Staking ValidatorCount (r:0 w:1) fn set_validator_count() -> Weight { - (1_248_000 as Weight) + (3_789_000 as Weight) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Staking ForceEra (r:0 w:1) fn force_no_eras() -> Weight { - (1_350_000 as Weight) + (3_793_000 as Weight) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Staking ForceEra (r:0 w:1) fn force_new_era() -> Weight { - (1_276_000 as Weight) + (3_802_000 as Weight) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Staking ForceEra (r:0 w:1) fn force_new_era_always() -> Weight { - (1_321_000 as Weight) + (3_762_000 as Weight) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Staking Invulnerables (r:0 w:1) fn set_invulnerables(v: u32, ) -> Weight { - (1_517_000 as Weight) + (4_318_000 as Weight) // Standard Error: 0 - .saturating_add((9_000 as Weight).saturating_mul(v as Weight)) + .saturating_add((10_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Staking Bonded (r:1 w:1) @@ -641,18 +641,18 @@ impl WeightInfo for () { // Storage: Staking Payee (r:0 w:1) // Storage: Staking SpanSlash (r:0 w:2) fn force_unstake(s: u32, ) -> Weight { - (58_451_000 as Weight) + (65_265_000 as Weight) // Standard Error: 1_000 - .saturating_add((835_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((1_029_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(11 as Weight)) .saturating_add(RocksDbWeight::get().writes(12 as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) } // Storage: Staking UnappliedSlashes (r:1 w:1) fn cancel_deferred_slash(s: u32, ) -> Weight { - (895_257_000 as Weight) + (903_312_000 as Weight) // Standard Error: 56_000 - .saturating_add((4_978_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((4_968_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -667,9 +667,9 @@ impl WeightInfo for () { // Storage: Staking Payee (r:2 w:0) // Storage: System Account (r:2 w:2) fn payout_stakers_dead_controller(n: u32, ) -> Weight { - (71_290_000 as Weight) - // Standard Error: 12_000 - .saturating_add((23_818_000 as Weight).saturating_mul(n as Weight)) + (87_569_000 as Weight) + // Standard Error: 14_000 + .saturating_add((24_232_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(10 as Weight)) .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(n as Weight))) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) @@ -687,9 +687,9 @@ impl WeightInfo for () { // Storage: System Account (r:2 w:2) // Storage: Balances Locks (r:2 w:2) fn payout_stakers_alive_staked(n: u32, ) -> Weight { - (86_724_000 as Weight) - // Standard Error: 29_000 - .saturating_add((33_575_000 as Weight).saturating_mul(n as Weight)) + (98_839_000 as Weight) + // Standard Error: 21_000 + .saturating_add((34_480_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(11 as Weight)) .saturating_add(RocksDbWeight::get().reads((5 as Weight).saturating_mul(n as Weight))) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) @@ -702,9 +702,9 @@ impl WeightInfo for () { // Storage: Staking Bonded (r:1 w:0) // Storage: BagsList ListBags (r:2 w:2) fn rebond(l: u32, ) -> Weight { - (68_577_000 as Weight) + (74_865_000 as Weight) // Standard Error: 3_000 - .saturating_add((55_000 as Weight).saturating_mul(l as Weight)) + .saturating_add((64_000 as Weight).saturating_mul(l as Weight)) .saturating_add(RocksDbWeight::get().reads(9 as Weight)) .saturating_add(RocksDbWeight::get().writes(8 as Weight)) } @@ -719,8 +719,8 @@ impl WeightInfo for () { // Storage: Staking ErasStartSessionIndex (r:0 w:1) fn set_history_depth(e: u32, ) -> Weight { (0 as Weight) - // Standard Error: 58_000 - .saturating_add((20_425_000 as Weight).saturating_mul(e as Weight)) + // Standard Error: 62_000 + .saturating_add((22_829_000 as Weight).saturating_mul(e as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) .saturating_add(RocksDbWeight::get().writes((7 as Weight).saturating_mul(e as Weight))) @@ -739,9 +739,9 @@ impl WeightInfo for () { // Storage: Staking Payee (r:0 w:1) // Storage: Staking SpanSlash (r:0 w:1) fn reap_stash(s: u32, ) -> Weight { - (63_865_000 as Weight) - // Standard Error: 0 - .saturating_add((839_000 as Weight).saturating_mul(s as Weight)) + (70_933_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_021_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(12 as Weight)) .saturating_add(RocksDbWeight::get().writes(12 as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) @@ -766,10 +766,10 @@ impl WeightInfo for () { // Storage: Staking ErasStartSessionIndex (r:0 w:1) fn new_era(v: u32, n: u32, ) -> Weight { (0 as Weight) - // Standard Error: 881_000 - .saturating_add((211_756_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 44_000 - .saturating_add((30_413_000 as Weight).saturating_mul(n as Weight)) + // Standard Error: 897_000 + .saturating_add((213_100_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 45_000 + .saturating_add((31_123_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(208 as Weight)) .saturating_add(RocksDbWeight::get().reads((5 as Weight).saturating_mul(v as Weight))) .saturating_add(RocksDbWeight::get().reads((4 as Weight).saturating_mul(n as Weight))) @@ -786,12 +786,12 @@ impl WeightInfo for () { // Storage: Staking Ledger (r:1500 w:0) fn get_npos_voters(v: u32, n: u32, s: u32, ) -> Weight { (0 as Weight) - // Standard Error: 86_000 - .saturating_add((23_814_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 86_000 - .saturating_add((21_657_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 2_924_000 - .saturating_add((19_067_000 as Weight).saturating_mul(s as Weight)) + // Standard Error: 116_000 + .saturating_add((23_745_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 116_000 + .saturating_add((22_497_000 as Weight).saturating_mul(n as Weight)) + // Standard Error: 3_968_000 + .saturating_add((20_676_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(202 as Weight)) .saturating_add(RocksDbWeight::get().reads((5 as Weight).saturating_mul(v as Weight))) .saturating_add(RocksDbWeight::get().reads((4 as Weight).saturating_mul(n as Weight))) @@ -800,8 +800,8 @@ impl WeightInfo for () { // Storage: Staking Validators (r:501 w:0) fn get_npos_targets(v: u32, ) -> Weight { (0 as Weight) - // Standard Error: 32_000 - .saturating_add((7_861_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 36_000 + .saturating_add((8_097_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(v as Weight))) } @@ -812,7 +812,7 @@ impl WeightInfo for () { // Storage: Staking MaxNominatorsCount (r:0 w:1) // Storage: Staking MinNominatorBond (r:0 w:1) fn set_staking_configs_all_set() -> Weight { - (3_688_000 as Weight) + (7_041_000 as Weight) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } // Storage: Staking MinCommission (r:0 w:1) @@ -822,7 +822,7 @@ impl WeightInfo for () { // Storage: Staking MaxNominatorsCount (r:0 w:1) // Storage: Staking MinNominatorBond (r:0 w:1) fn set_staking_configs_all_remove() -> Weight { - (3_414_000 as Weight) + (6_495_000 as Weight) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } // Storage: Staking Ledger (r:1 w:0) @@ -836,14 +836,14 @@ impl WeightInfo for () { // Storage: BagsList ListBags (r:1 w:1) // Storage: BagsList CounterForListNodes (r:1 w:1) fn chill_other() -> Weight { - (56_962_000 as Weight) + (62_014_000 as Weight) .saturating_add(RocksDbWeight::get().reads(11 as Weight)) .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } // Storage: Staking MinCommission (r:1 w:0) // Storage: Staking Validators (r:1 w:1) fn force_apply_min_commission() -> Weight { - (9_465_000 as Weight) + (12_814_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } diff --git a/frame/state-trie-migration/src/weights.rs b/frame/state-trie-migration/src/weights.rs index f08b115378f21..f2566f949c058 100644 --- a/frame/state-trie-migration/src/weights.rs +++ b/frame/state-trie-migration/src/weights.rs @@ -18,12 +18,13 @@ //! Autogenerated weights for pallet_state_trie_migration //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-03-04, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-24, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/production/substrate +// ./target/production/substrate // benchmark +// pallet // --chain=dev // --steps=50 // --repeat=20 @@ -31,9 +32,8 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --heap-pages=4096 -// --output=./frame/state-trie-migration/src/weights.rs // --template=./.maintain/frame-weight-template.hbs +// --output=./frame/state-trie-migration/src/weights.rs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -56,32 +56,33 @@ pub trait WeightInfo { /// Weights for pallet_state_trie_migration using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { + // Storage: StateTrieMigration SignedMigrationMaxLimits (r:1 w:0) // Storage: StateTrieMigration MigrationProcess (r:1 w:1) fn continue_migrate() -> Weight { - (13_385_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) + (19_019_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } - // Storage: StateTrieMigration MigrationProcess (r:1 w:0) + // Storage: StateTrieMigration SignedMigrationMaxLimits (r:1 w:0) fn continue_migrate_wrong_witness() -> Weight { - (1_757_000 as Weight) + (1_874_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) } fn migrate_custom_top_success() -> Weight { - (12_813_000 as Weight) + (16_381_000 as Weight) } // Storage: unknown [0x666f6f] (r:1 w:1) fn migrate_custom_top_fail() -> Weight { - (24_961_000 as Weight) + (25_966_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn migrate_custom_child_success() -> Weight { - (13_132_000 as Weight) + (16_712_000 as Weight) } // Storage: unknown [0x666f6f] (r:1 w:1) fn migrate_custom_child_fail() -> Weight { - (29_215_000 as Weight) + (29_885_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -97,32 +98,33 @@ impl WeightInfo for SubstrateWeight { // For backwards compatibility and tests impl WeightInfo for () { + // Storage: StateTrieMigration SignedMigrationMaxLimits (r:1 w:0) // Storage: StateTrieMigration MigrationProcess (r:1 w:1) fn continue_migrate() -> Weight { - (13_385_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + (19_019_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } - // Storage: StateTrieMigration MigrationProcess (r:1 w:0) + // Storage: StateTrieMigration SignedMigrationMaxLimits (r:1 w:0) fn continue_migrate_wrong_witness() -> Weight { - (1_757_000 as Weight) + (1_874_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) } fn migrate_custom_top_success() -> Weight { - (12_813_000 as Weight) + (16_381_000 as Weight) } // Storage: unknown [0x666f6f] (r:1 w:1) fn migrate_custom_top_fail() -> Weight { - (24_961_000 as Weight) + (25_966_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } fn migrate_custom_child_success() -> Weight { - (13_132_000 as Weight) + (16_712_000 as Weight) } // Storage: unknown [0x666f6f] (r:1 w:1) fn migrate_custom_child_fail() -> Weight { - (29_215_000 as Weight) + (29_885_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } diff --git a/frame/support/procedural/src/construct_runtime/expand/call.rs b/frame/support/procedural/src/construct_runtime/expand/call.rs index b09ef126ac6dd..ad52ca48f68c9 100644 --- a/frame/support/procedural/src/construct_runtime/expand/call.rs +++ b/frame/support/procedural/src/construct_runtime/expand/call.rs @@ -127,6 +127,19 @@ pub fn expand_outer_dispatch( } } } + impl #scrate::traits::DispatchableWithStorageLayer for Call { + type Origin = Origin; + fn dispatch_with_storage_layer(self, origin: Origin) -> #scrate::dispatch::DispatchResultWithPostInfo { + #scrate::storage::with_storage_layer(|| { + #scrate::dispatch::Dispatchable::dispatch(self, origin) + }) + } + fn dispatch_bypass_filter_with_storage_layer(self, origin: Origin) -> #scrate::dispatch::DispatchResultWithPostInfo { + #scrate::storage::with_storage_layer(|| { + #scrate::traits::UnfilteredDispatchable::dispatch_bypass_filter(self, origin) + }) + } + } #( impl #scrate::traits::IsSubType<#scrate::dispatch::CallableCallFor<#pallet_names, #runtime>> for Call { diff --git a/frame/support/procedural/src/lib.rs b/frame/support/procedural/src/lib.rs index f8aaa5fe37749..00204b7a4d906 100644 --- a/frame/support/procedural/src/lib.rs +++ b/frame/support/procedural/src/lib.rs @@ -433,6 +433,12 @@ pub fn transactional(attr: TokenStream, input: TokenStream) -> TokenStream { transactional::transactional(attr, input).unwrap_or_else(|e| e.to_compile_error().into()) } +#[proc_macro_attribute] +pub fn require_transactional(attr: TokenStream, input: TokenStream) -> TokenStream { + transactional::require_transactional(attr, input) + .unwrap_or_else(|e| e.to_compile_error().into()) +} + /// Derive [`Clone`] but do not bound any generic. Docs are at `frame_support::CloneNoBound`. #[proc_macro_derive(CloneNoBound)] pub fn derive_clone_no_bound(input: TokenStream) -> TokenStream { @@ -510,12 +516,6 @@ pub fn derive_default_no_bound(input: TokenStream) -> TokenStream { default_no_bound::derive_default_no_bound(input) } -#[proc_macro_attribute] -pub fn require_transactional(attr: TokenStream, input: TokenStream) -> TokenStream { - transactional::require_transactional(attr, input) - .unwrap_or_else(|e| e.to_compile_error().into()) -} - #[proc_macro] pub fn crate_to_crate_version(input: TokenStream) -> TokenStream { crate_version::crate_to_crate_version(input) diff --git a/frame/support/procedural/src/pallet/expand/call.rs b/frame/support/procedural/src/pallet/expand/call.rs index ac52463b8aab6..a5038ec399e2b 100644 --- a/frame/support/procedural/src/pallet/expand/call.rs +++ b/frame/support/procedural/src/pallet/expand/call.rs @@ -267,8 +267,12 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { #frame_support::sp_tracing::enter_span!( #frame_support::sp_tracing::trace_span!(stringify!(#fn_name)) ); - <#pallet_ident<#type_use_gen>>::#fn_name(origin, #( #args_name, )* ) - .map(Into::into).map_err(Into::into) + // We execute all dispatchable in at least one storage layer, allowing them + // to return an error at any point, and undoing any storage changes. + #frame_support::storage::in_storage_layer(|| { + <#pallet_ident<#type_use_gen>>::#fn_name(origin, #( #args_name, )* ) + .map(Into::into).map_err(Into::into) + }) }, )* Self::__Ignore(_, _) => { diff --git a/frame/support/src/dispatch.rs b/frame/support/src/dispatch.rs index 7ccf796167297..51504c9c36e38 100644 --- a/frame/support/src/dispatch.rs +++ b/frame/support/src/dispatch.rs @@ -27,7 +27,8 @@ pub use crate::{ result, }, traits::{ - CallMetadata, GetCallMetadata, GetCallName, GetStorageVersion, UnfilteredDispatchable, + CallMetadata, DispatchableWithStorageLayer, GetCallMetadata, GetCallName, + GetStorageVersion, UnfilteredDispatchable, }, weights::{ ClassifyDispatch, DispatchInfo, GetDispatchInfo, PaysFee, PostDispatchInfo, @@ -1469,7 +1470,11 @@ macro_rules! decl_module { $ignore:ident $mod_type:ident<$trait_instance:ident $(, $instance:ident)?> $fn_name:ident $origin:ident $system:ident [ $( $param_name:ident),* ] ) => { - <$mod_type<$trait_instance $(, $instance)?>>::$fn_name( $origin $(, $param_name )* ).map(Into::into).map_err(Into::into) + // We execute all dispatchable in at least one storage layer, allowing them + // to return an error at any point, and undoing any storage changes. + $crate::storage::in_storage_layer(|| { + <$mod_type<$trait_instance $(, $instance)?>>::$fn_name( $origin $(, $param_name )* ).map(Into::into).map_err(Into::into) + }) }; // no `deposit_event` function wanted diff --git a/frame/support/src/storage/mod.rs b/frame/support/src/storage/mod.rs index 7135ead850b6d..7a960776fc755 100644 --- a/frame/support/src/storage/mod.rs +++ b/frame/support/src/storage/mod.rs @@ -30,7 +30,9 @@ use sp_runtime::generic::{Digest, DigestItem}; use sp_std::prelude::*; pub use self::{ - transactional::{with_transaction, with_transaction_unchecked}, + transactional::{ + in_storage_layer, with_storage_layer, with_transaction, with_transaction_unchecked, + }, types::StorageEntryMetadataBuilder, }; pub use sp_runtime::TransactionOutcome; diff --git a/frame/support/src/storage/transactional.rs b/frame/support/src/storage/transactional.rs index c46eb9258e0ca..b02552bdb0e14 100644 --- a/frame/support/src/storage/transactional.rs +++ b/frame/support/src/storage/transactional.rs @@ -158,6 +158,40 @@ pub fn with_transaction_unchecked(f: impl FnOnce() -> TransactionOutcome) } } +/// Execute the supplied function, adding a new storage layer. +/// +/// This is the same as `with_transaction`, but assuming that any function returning +/// an `Err` should rollback, and any function returning `Ok` should commit. This +/// provides a cleaner API to the developer who wants this behavior. +pub fn with_storage_layer(f: impl FnOnce() -> Result) -> Result +where + E: From, +{ + with_transaction(|| { + let r = f(); + if r.is_ok() { + TransactionOutcome::Commit(r) + } else { + TransactionOutcome::Rollback(r) + } + }) +} + +/// Execute the supplied function, ensuring we are at least in one storage layer. +/// +/// If we are already in a storage layer, we just execute the provided closure. +/// If we are not, we execute the closure within a [`with_storage_layer`]. +pub fn in_storage_layer(f: impl FnOnce() -> Result) -> Result +where + E: From, +{ + if is_transactional() { + f() + } else { + with_storage_layer(f) + } +} + #[cfg(test)] mod tests { use super::*; @@ -229,4 +263,33 @@ mod tests { assert_eq!(get_transaction_level(), 0); }); } + + #[test] + fn in_storage_layer_works() { + TestExternalities::default().execute_with(|| { + assert_eq!(get_transaction_level(), 0); + + let res = in_storage_layer(|| -> DispatchResult { + assert_eq!(get_transaction_level(), 1); + in_storage_layer(|| -> DispatchResult { + // We are still in the same layer :) + assert_eq!(get_transaction_level(), 1); + Ok(()) + }) + }); + + assert_ok!(res); + + let res = in_storage_layer(|| -> DispatchResult { + assert_eq!(get_transaction_level(), 1); + in_storage_layer(|| -> DispatchResult { + // We are still in the same layer :) + assert_eq!(get_transaction_level(), 1); + Err("epic fail".into()) + }) + }); + + assert_noop!(res, "epic fail"); + }); + } } diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index edeb7fead7c84..a6fc88dfb5e76 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -94,8 +94,8 @@ pub use storage::{ mod dispatch; pub use dispatch::{ - AsEnsureOriginWithArg, EnsureOneOf, EnsureOrigin, EnsureOriginWithArg, OriginTrait, - UnfilteredDispatchable, + AsEnsureOriginWithArg, DispatchableWithStorageLayer, EnsureOneOf, EnsureOrigin, + EnsureOriginWithArg, OriginTrait, UnfilteredDispatchable, }; mod voting; diff --git a/frame/support/src/traits/dispatch.rs b/frame/support/src/traits/dispatch.rs index 443941bd8d771..1cec6cf837aba 100644 --- a/frame/support/src/traits/dispatch.rs +++ b/frame/support/src/traits/dispatch.rs @@ -101,6 +101,23 @@ pub trait UnfilteredDispatchable { fn dispatch_bypass_filter(self, origin: Self::Origin) -> DispatchResultWithPostInfo; } +/// Type that can be dispatched with an additional storage layer which is used to execute the call. +pub trait DispatchableWithStorageLayer { + /// The origin type of the runtime, (i.e. `frame_system::Config::Origin`). + type Origin; + + /// Same as `dispatch` from the [`frame_support::dispatch::Dispatchable`] trait, but + /// specifically spawns a new storage layer to execute the call inside of. + fn dispatch_with_storage_layer(self, origin: Self::Origin) -> DispatchResultWithPostInfo; + + /// Same as `dispatch_bypass_filter` from the [`UnfilteredDispatchable`] trait, but specifically + /// spawns a new storage layer to execute the call inside of. + fn dispatch_bypass_filter_with_storage_layer( + self, + origin: Self::Origin, + ) -> DispatchResultWithPostInfo; +} + /// Methods available on `frame_system::Config::Origin`. pub trait OriginTrait: Sized { /// Runtime call type, as in `frame_system::Config::Call` diff --git a/frame/support/src/weights/block_weights.rs b/frame/support/src/weights/block_weights.rs index d1d5f8320d104..b86334514af2f 100644 --- a/frame/support/src/weights/block_weights.rs +++ b/frame/support/src/weights/block_weights.rs @@ -16,7 +16,7 @@ // limitations under the License. //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-23 (Y/M/D) +//! DATE: 2022-05-24 (Y/M/D) //! //! SHORT-NAME: `block`, LONG-NAME: `BlockExecution`, RUNTIME: `Development` //! WARMUPS: `10`, REPEAT: `100` @@ -27,7 +27,7 @@ // ./target/production/substrate // benchmark // overhead -// --dev +// --chain=dev // --execution=wasm // --wasm-execution=compiled // --weight-path=./frame/support/src/weights/ @@ -44,16 +44,16 @@ parameter_types! { /// Calculated by multiplying the *Average* with `1` and adding `0`. /// /// Stats nanoseconds: - /// Min, Max: 5_295_788, 5_473_440 - /// Average: 5_343_308 - /// Median: 5_323_240 - /// Std-Dev: 38368.68 + /// Min, Max: 5_303_128, 5_507_784 + /// Average: 5_346_284 + /// Median: 5_328_139 + /// Std-Dev: 41749.5 /// /// Percentiles nanoseconds: - /// 99th: 5_470_141 - /// 95th: 5_418_269 - /// 75th: 5_355_579 - pub const BlockExecutionWeight: Weight = 5_343_308 * WEIGHT_PER_NANOS; + /// 99th: 5_489_273 + /// 95th: 5_433_314 + /// 75th: 5_354_812 + pub const BlockExecutionWeight: Weight = 5_346_284 * WEIGHT_PER_NANOS; } #[cfg(test)] diff --git a/frame/support/src/weights/extrinsic_weights.rs b/frame/support/src/weights/extrinsic_weights.rs index 1a5a4e7a460c0..b8a52c164d8fe 100644 --- a/frame/support/src/weights/extrinsic_weights.rs +++ b/frame/support/src/weights/extrinsic_weights.rs @@ -16,7 +16,7 @@ // limitations under the License. //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-23 (Y/M/D) +//! DATE: 2022-05-24 (Y/M/D) //! //! SHORT-NAME: `extrinsic`, LONG-NAME: `ExtrinsicBase`, RUNTIME: `Development` //! WARMUPS: `10`, REPEAT: `100` @@ -27,7 +27,7 @@ // ./target/production/substrate // benchmark // overhead -// --dev +// --chain=dev // --execution=wasm // --wasm-execution=compiled // --weight-path=./frame/support/src/weights/ @@ -44,16 +44,16 @@ parameter_types! { /// Calculated by multiplying the *Average* with `1` and adding `0`. /// /// Stats nanoseconds: - /// Min, Max: 85_423, 86_142 - /// Average: 85_795 - /// Median: 85_790 - /// Std-Dev: 162.37 + /// Min, Max: 86_060, 86_999 + /// Average: 86_298 + /// Median: 86_248 + /// Std-Dev: 207.19 /// /// Percentiles nanoseconds: - /// 99th: 86_115 - /// 95th: 86_069 - /// 75th: 85_937 - pub const ExtrinsicBaseWeight: Weight = 85_795 * WEIGHT_PER_NANOS; + /// 99th: 86_924 + /// 95th: 86_828 + /// 75th: 86_347 + pub const ExtrinsicBaseWeight: Weight = 86_298 * WEIGHT_PER_NANOS; } #[cfg(test)] diff --git a/frame/support/test/tests/pallet.rs b/frame/support/test/tests/pallet.rs index 792fd93299705..b81941c45ff46 100644 --- a/frame/support/test/tests/pallet.rs +++ b/frame/support/test/tests/pallet.rs @@ -205,8 +205,7 @@ pub mod pallet { /// Doc comment put in metadata #[pallet::weight(1)] - #[frame_support::transactional] - pub fn foo_transactional( + pub fn foo_storage_layer( _origin: OriginFor, #[pallet::compact] foo: u32, ) -> DispatchResultWithPostInfo { @@ -373,7 +372,7 @@ pub mod pallet { fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { let _ = T::AccountId::from(SomeType1); // Test for where clause let _ = T::AccountId::from(SomeType5); // Test for where clause - if matches!(call, Call::foo_transactional { .. }) { + if matches!(call, Call::foo_storage_layer { .. }) { return Ok(ValidTransaction::default()) } Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) @@ -611,13 +610,13 @@ fn transactional_works() { TestExternalities::default().execute_with(|| { frame_system::Pallet::::set_block_number(1); - pallet::Call::::foo_transactional { foo: 0 } + pallet::Call::::foo_storage_layer { foo: 0 } .dispatch_bypass_filter(None.into()) .err() .unwrap(); assert!(frame_system::Pallet::::events().is_empty()); - pallet::Call::::foo_transactional { foo: 1 } + pallet::Call::::foo_storage_layer { foo: 1 } .dispatch_bypass_filter(None.into()) .unwrap(); assert_eq!( @@ -640,7 +639,7 @@ fn call_expand() { assert_eq!(call_foo.get_call_name(), "foo"); assert_eq!( pallet::Call::::get_call_names(), - &["foo", "foo_transactional", "foo_no_post_info"], + &["foo", "foo_storage_layer", "foo_no_post_info"], ); } @@ -744,7 +743,7 @@ fn inherent_expand() { Digest::default(), ), vec![UncheckedExtrinsic { - function: Call::Example(pallet::Call::foo_transactional { foo: 0 }), + function: Call::Example(pallet::Call::foo_storage_layer { foo: 0 }), signature: None, }], ); @@ -785,7 +784,7 @@ fn inherent_expand() { signature: None, }, UncheckedExtrinsic { - function: Call::Example(pallet::Call::foo_transactional { foo: 0 }), + function: Call::Example(pallet::Call::foo_storage_layer { foo: 0 }), signature: None, }, ], @@ -807,7 +806,7 @@ fn inherent_expand() { signature: None, }, UncheckedExtrinsic { - function: Call::Example(pallet::Call::foo_transactional { foo: 0 }), + function: Call::Example(pallet::Call::foo_storage_layer { foo: 0 }), signature: None, }, UncheckedExtrinsic { @@ -857,7 +856,7 @@ fn validate_unsigned_expand() { let validity = pallet::Pallet::validate_unsigned(TransactionSource::Local, &call).unwrap_err(); assert_eq!(validity, TransactionValidityError::Invalid(InvalidTransaction::Call)); - let call = pallet::Call::::foo_transactional { foo: 0 }; + let call = pallet::Call::::foo_storage_layer { foo: 0 }; let validity = pallet::Pallet::validate_unsigned(TransactionSource::External, &call).unwrap(); assert_eq!(validity, ValidTransaction::default()); diff --git a/frame/support/test/tests/pallet_instance.rs b/frame/support/test/tests/pallet_instance.rs index 29074843f4bbb..d716637f82895 100644 --- a/frame/support/test/tests/pallet_instance.rs +++ b/frame/support/test/tests/pallet_instance.rs @@ -93,8 +93,7 @@ pub mod pallet { /// Doc comment put in metadata #[pallet::weight(1)] - #[frame_support::transactional] - pub fn foo_transactional( + pub fn foo_storage_layer( origin: OriginFor, #[pallet::compact] _foo: u32, ) -> DispatchResultWithPostInfo { @@ -316,7 +315,7 @@ fn call_expand() { DispatchInfo { weight: 3, class: DispatchClass::Normal, pays_fee: Pays::Yes } ); assert_eq!(call_foo.get_call_name(), "foo"); - assert_eq!(pallet::Call::::get_call_names(), &["foo", "foo_transactional"]); + assert_eq!(pallet::Call::::get_call_names(), &["foo", "foo_storage_layer"]); let call_foo = pallet::Call::::foo { foo: 3 }; assert_eq!( @@ -326,7 +325,7 @@ fn call_expand() { assert_eq!(call_foo.get_call_name(), "foo"); assert_eq!( pallet::Call::::get_call_names(), - &["foo", "foo_transactional"], + &["foo", "foo_storage_layer"], ); } diff --git a/frame/support/test/tests/storage_layers.rs b/frame/support/test/tests/storage_layers.rs new file mode 100644 index 0000000000000..05ed60fe90196 --- /dev/null +++ b/frame/support/test/tests/storage_layers.rs @@ -0,0 +1,280 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use frame_support::{ + assert_noop, assert_ok, dispatch::DispatchResult, pallet_prelude::ConstU32, + storage::with_storage_layer, +}; +use pallet::*; +use sp_io::TestExternalities; + +#[frame_support::pallet] +pub mod pallet { + use frame_support::{ensure, pallet_prelude::*}; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::storage] + pub type Value = StorageValue<_, u32, ValueQuery>; + + #[pallet::storage] + pub type Map = StorageMap<_, Blake2_128Concat, u32, u32, ValueQuery>; + + #[pallet::error] + pub enum Error { + Revert, + } + + #[pallet::call] + impl Pallet { + #[pallet::weight(1)] + pub fn set_value(_origin: OriginFor, value: u32) -> DispatchResult { + Value::::put(value); + ensure!(value != 1, Error::::Revert); + Ok(()) + } + } +} + +pub mod decl_pallet { + pub trait Config: frame_system::Config {} + + frame_support::decl_module! { + pub struct Module for enum Call where origin: T::Origin { + #[weight = 0] + pub fn set_value(_origin, value: u32) { + DeclValue::put(value); + frame_support::ensure!(value != 1, "Revert!"); + } + } + } + + frame_support::decl_storage! { + trait Store for Module as StorageTransactions { + pub DeclValue: u32; + } + } +} + +pub type BlockNumber = u64; +pub type Index = u64; +pub type Header = sp_runtime::generic::Header; +pub type Block = sp_runtime::generic::Block; +pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; + +impl frame_system::Config for Runtime { + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type BaseCallFilter = frame_support::traits::Everything; + type Origin = Origin; + type Index = u64; + type BlockNumber = u32; + type Call = Call; + type Hash = sp_runtime::testing::H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = u64; + type Lookup = sp_runtime::traits::IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = ConstU32<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +impl pallet::Config for Runtime {} + +impl decl_pallet::Config for Runtime {} + +frame_support::construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: frame_system, + MyPallet: pallet, + DeclPallet: decl_pallet::{Call, Storage}, + } +); + +#[test] +fn storage_layer_basic_commit() { + TestExternalities::default().execute_with(|| { + assert_eq!(Value::::get(), 0); + assert!(!Map::::contains_key(0)); + + assert_ok!(with_storage_layer(|| -> DispatchResult { + Value::::set(99); + Map::::insert(0, 99); + assert_eq!(Value::::get(), 99); + assert_eq!(Map::::get(0), 99); + Ok(()) + })); + + assert_eq!(Value::::get(), 99); + assert_eq!(Map::::get(0), 99); + }); +} + +#[test] +fn storage_layer_basic_rollback() { + TestExternalities::default().execute_with(|| { + assert_eq!(Value::::get(), 0); + assert_eq!(Map::::get(0), 0); + + assert_noop!( + with_storage_layer(|| -> DispatchResult { + Value::::set(99); + Map::::insert(0, 99); + assert_eq!(Value::::get(), 99); + assert_eq!(Map::::get(0), 99); + Err("revert".into()) + }), + "revert" + ); + + assert_eq!(Value::::get(), 0); + assert_eq!(Map::::get(0), 0); + }); +} + +#[test] +fn storage_layer_rollback_then_commit() { + TestExternalities::default().execute_with(|| { + Value::::set(1); + Map::::insert(1, 1); + + assert_ok!(with_storage_layer(|| -> DispatchResult { + Value::::set(2); + Map::::insert(1, 2); + Map::::insert(2, 2); + + assert_noop!( + with_storage_layer(|| -> DispatchResult { + Value::::set(3); + Map::::insert(1, 3); + Map::::insert(2, 3); + Map::::insert(3, 3); + + assert_eq!(Value::::get(), 3); + assert_eq!(Map::::get(1), 3); + assert_eq!(Map::::get(2), 3); + assert_eq!(Map::::get(3), 3); + + Err("revert".into()) + }), + "revert" + ); + + assert_eq!(Value::::get(), 2); + assert_eq!(Map::::get(1), 2); + assert_eq!(Map::::get(2), 2); + assert_eq!(Map::::get(3), 0); + + Ok(()) + })); + + assert_eq!(Value::::get(), 2); + assert_eq!(Map::::get(1), 2); + assert_eq!(Map::::get(2), 2); + assert_eq!(Map::::get(3), 0); + }); +} + +#[test] +fn storage_layer_commit_then_rollback() { + TestExternalities::default().execute_with(|| { + Value::::set(1); + Map::::insert(1, 1); + + assert_noop!( + with_storage_layer(|| -> DispatchResult { + Value::::set(2); + Map::::insert(1, 2); + Map::::insert(2, 2); + + assert_ok!(with_storage_layer(|| -> DispatchResult { + Value::::set(3); + Map::::insert(1, 3); + Map::::insert(2, 3); + Map::::insert(3, 3); + + assert_eq!(Value::::get(), 3); + assert_eq!(Map::::get(1), 3); + assert_eq!(Map::::get(2), 3); + assert_eq!(Map::::get(3), 3); + + Ok(()) + })); + + assert_eq!(Value::::get(), 3); + assert_eq!(Map::::get(1), 3); + assert_eq!(Map::::get(2), 3); + assert_eq!(Map::::get(3), 3); + + Err("revert".into()) + }), + "revert" + ); + + assert_eq!(Value::::get(), 1); + assert_eq!(Map::::get(1), 1); + assert_eq!(Map::::get(2), 0); + assert_eq!(Map::::get(3), 0); + }); +} + +#[test] +fn storage_layer_in_pallet_call() { + TestExternalities::default().execute_with(|| { + use sp_runtime::traits::Dispatchable; + let call1 = Call::MyPallet(pallet::Call::set_value { value: 2 }); + assert_ok!(call1.dispatch(Origin::signed(0))); + assert_eq!(Value::::get(), 2); + + let call2 = Call::MyPallet(pallet::Call::set_value { value: 1 }); + assert_noop!(call2.dispatch(Origin::signed(0)), Error::::Revert); + }); +} + +#[test] +fn storage_layer_in_decl_pallet_call() { + TestExternalities::default().execute_with(|| { + use frame_support::StorageValue; + use sp_runtime::traits::Dispatchable; + + let call1 = Call::DeclPallet(decl_pallet::Call::set_value { value: 2 }); + assert_ok!(call1.dispatch(Origin::signed(0))); + assert_eq!(decl_pallet::DeclValue::get(), 2); + + let call2 = Call::DeclPallet(decl_pallet::Call::set_value { value: 1 }); + assert_noop!(call2.dispatch(Origin::signed(0)), "Revert!"); + }); +} diff --git a/frame/system/src/weights.rs b/frame/system/src/weights.rs index 813183ecce188..bdd2d64b2118b 100644 --- a/frame/system/src/weights.rs +++ b/frame/system/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for frame_system //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -66,29 +66,29 @@ impl WeightInfo for SubstrateWeight { // Storage: System Digest (r:1 w:1) // Storage: unknown [0x3a686561707061676573] (r:0 w:1) fn set_heap_pages() -> Weight { - (3_235_000 as Weight) + (5_453_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn set_storage(i: u32, ) -> Weight { (0 as Weight) - // Standard Error: 0 - .saturating_add((407_000 as Weight).saturating_mul(i as Weight)) + // Standard Error: 1_000 + .saturating_add((600_000 as Weight).saturating_mul(i as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(i as Weight))) } // Storage: Skipped Metadata (r:0 w:0) fn kill_storage(i: u32, ) -> Weight { (0 as Weight) - // Standard Error: 0 - .saturating_add((307_000 as Weight).saturating_mul(i as Weight)) + // Standard Error: 1_000 + .saturating_add((510_000 as Weight).saturating_mul(i as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(i as Weight))) } // Storage: Skipped Metadata (r:0 w:0) fn kill_prefix(p: u32, ) -> Weight { (0 as Weight) - // Standard Error: 0 - .saturating_add((672_000 as Weight).saturating_mul(p as Weight)) + // Standard Error: 1_000 + .saturating_add((916_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(p as Weight))) } } @@ -106,29 +106,29 @@ impl WeightInfo for () { // Storage: System Digest (r:1 w:1) // Storage: unknown [0x3a686561707061676573] (r:0 w:1) fn set_heap_pages() -> Weight { - (3_235_000 as Weight) + (5_453_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) fn set_storage(i: u32, ) -> Weight { (0 as Weight) - // Standard Error: 0 - .saturating_add((407_000 as Weight).saturating_mul(i as Weight)) + // Standard Error: 1_000 + .saturating_add((600_000 as Weight).saturating_mul(i as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(i as Weight))) } // Storage: Skipped Metadata (r:0 w:0) fn kill_storage(i: u32, ) -> Weight { (0 as Weight) - // Standard Error: 0 - .saturating_add((307_000 as Weight).saturating_mul(i as Weight)) + // Standard Error: 1_000 + .saturating_add((510_000 as Weight).saturating_mul(i as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(i as Weight))) } // Storage: Skipped Metadata (r:0 w:0) fn kill_prefix(p: u32, ) -> Weight { (0 as Weight) - // Standard Error: 0 - .saturating_add((672_000 as Weight).saturating_mul(p as Weight)) + // Standard Error: 1_000 + .saturating_add((916_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(p as Weight))) } } diff --git a/frame/timestamp/src/weights.rs b/frame/timestamp/src/weights.rs index 7c130d69c51d6..6b4ebfa74dd87 100644 --- a/frame/timestamp/src/weights.rs +++ b/frame/timestamp/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_timestamp //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-24, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -54,12 +54,12 @@ impl WeightInfo for SubstrateWeight { // Storage: Timestamp Now (r:1 w:1) // Storage: Babe CurrentSlot (r:1 w:0) fn set() -> Weight { - (5_490_000 as Weight) + (8_080_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } fn on_finalize() -> Weight { - (2_618_000 as Weight) + (2_681_000 as Weight) } } @@ -68,11 +68,11 @@ impl WeightInfo for () { // Storage: Timestamp Now (r:1 w:1) // Storage: Babe CurrentSlot (r:1 w:0) fn set() -> Weight { - (5_490_000 as Weight) + (8_080_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } fn on_finalize() -> Weight { - (2_618_000 as Weight) + (2_681_000 as Weight) } } diff --git a/frame/tips/src/weights.rs b/frame/tips/src/weights.rs index 9e49dce410ef8..4979618473fd1 100644 --- a/frame/tips/src/weights.rs +++ b/frame/tips/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_tips //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-24, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -58,16 +58,16 @@ impl WeightInfo for SubstrateWeight { // Storage: Tips Reasons (r:1 w:1) // Storage: Tips Tips (r:1 w:1) fn report_awesome(r: u32, ) -> Weight { - (26_613_000 as Weight) + (30_669_000 as Weight) // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((4_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Tips Tips (r:1 w:1) // Storage: Tips Reasons (r:0 w:1) fn retract_tip() -> Weight { - (25_001_000 as Weight) + (28_768_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -75,20 +75,20 @@ impl WeightInfo for SubstrateWeight { // Storage: Tips Reasons (r:1 w:1) // Storage: Tips Tips (r:0 w:1) fn tip_new(r: u32, t: u32, ) -> Weight { - (17_490_000 as Weight) + (20_385_000 as Weight) // Standard Error: 0 - .saturating_add((1_000 as Weight).saturating_mul(r as Weight)) - // Standard Error: 5_000 - .saturating_add((183_000 as Weight).saturating_mul(t as Weight)) + .saturating_add((2_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 3_000 + .saturating_add((166_000 as Weight).saturating_mul(t as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Elections Members (r:1 w:0) // Storage: Tips Tips (r:1 w:1) fn tip(t: u32, ) -> Weight { - (9_634_000 as Weight) - // Standard Error: 7_000 - .saturating_add((343_000 as Weight).saturating_mul(t as Weight)) + (12_287_000 as Weight) + // Standard Error: 6_000 + .saturating_add((363_000 as Weight).saturating_mul(t as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -97,18 +97,18 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) // Storage: Tips Reasons (r:0 w:1) fn close_tip(t: u32, ) -> Weight { - (41_967_000 as Weight) - // Standard Error: 10_000 - .saturating_add((264_000 as Weight).saturating_mul(t as Weight)) + (45_656_000 as Weight) + // Standard Error: 14_000 + .saturating_add((276_000 as Weight).saturating_mul(t as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: Tips Tips (r:1 w:1) // Storage: Tips Reasons (r:0 w:1) fn slash_tip(t: u32, ) -> Weight { - (14_917_000 as Weight) - // Standard Error: 4_000 - .saturating_add((61_000 as Weight).saturating_mul(t as Weight)) + (18_525_000 as Weight) + // Standard Error: 5_000 + .saturating_add((37_000 as Weight).saturating_mul(t as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -119,16 +119,16 @@ impl WeightInfo for () { // Storage: Tips Reasons (r:1 w:1) // Storage: Tips Tips (r:1 w:1) fn report_awesome(r: u32, ) -> Weight { - (26_613_000 as Weight) + (30_669_000 as Weight) // Standard Error: 0 - .saturating_add((2_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((4_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Tips Tips (r:1 w:1) // Storage: Tips Reasons (r:0 w:1) fn retract_tip() -> Weight { - (25_001_000 as Weight) + (28_768_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -136,20 +136,20 @@ impl WeightInfo for () { // Storage: Tips Reasons (r:1 w:1) // Storage: Tips Tips (r:0 w:1) fn tip_new(r: u32, t: u32, ) -> Weight { - (17_490_000 as Weight) + (20_385_000 as Weight) // Standard Error: 0 - .saturating_add((1_000 as Weight).saturating_mul(r as Weight)) - // Standard Error: 5_000 - .saturating_add((183_000 as Weight).saturating_mul(t as Weight)) + .saturating_add((2_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 3_000 + .saturating_add((166_000 as Weight).saturating_mul(t as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Elections Members (r:1 w:0) // Storage: Tips Tips (r:1 w:1) fn tip(t: u32, ) -> Weight { - (9_634_000 as Weight) - // Standard Error: 7_000 - .saturating_add((343_000 as Weight).saturating_mul(t as Weight)) + (12_287_000 as Weight) + // Standard Error: 6_000 + .saturating_add((363_000 as Weight).saturating_mul(t as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -158,18 +158,18 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) // Storage: Tips Reasons (r:0 w:1) fn close_tip(t: u32, ) -> Weight { - (41_967_000 as Weight) - // Standard Error: 10_000 - .saturating_add((264_000 as Weight).saturating_mul(t as Weight)) + (45_656_000 as Weight) + // Standard Error: 14_000 + .saturating_add((276_000 as Weight).saturating_mul(t as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: Tips Tips (r:1 w:1) // Storage: Tips Reasons (r:0 w:1) fn slash_tip(t: u32, ) -> Weight { - (14_917_000 as Weight) - // Standard Error: 4_000 - .saturating_add((61_000 as Weight).saturating_mul(t as Weight)) + (18_525_000 as Weight) + // Standard Error: 5_000 + .saturating_add((37_000 as Weight).saturating_mul(t as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } diff --git a/frame/transaction-storage/src/weights.rs b/frame/transaction-storage/src/weights.rs index 8063783b4cc22..b8bc4890a416e 100644 --- a/frame/transaction-storage/src/weights.rs +++ b/frame/transaction-storage/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_transaction_storage //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-24, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -72,7 +72,7 @@ impl WeightInfo for SubstrateWeight { // Storage: TransactionStorage BlockTransactions (r:1 w:1) // Storage: TransactionStorage MaxBlockTransactions (r:1 w:0) fn renew() -> Weight { - (45_011_000 as Weight) + (50_978_000 as Weight) .saturating_add(T::DbWeight::get().reads(6 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -82,7 +82,7 @@ impl WeightInfo for SubstrateWeight { // Storage: System ParentHash (r:1 w:0) // Storage: TransactionStorage Transactions (r:1 w:0) fn check_proof_max() -> Weight { - (98_295_000 as Weight) + (106_990_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -110,7 +110,7 @@ impl WeightInfo for () { // Storage: TransactionStorage BlockTransactions (r:1 w:1) // Storage: TransactionStorage MaxBlockTransactions (r:1 w:0) fn renew() -> Weight { - (45_011_000 as Weight) + (50_978_000 as Weight) .saturating_add(RocksDbWeight::get().reads(6 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -120,7 +120,7 @@ impl WeightInfo for () { // Storage: System ParentHash (r:1 w:0) // Storage: TransactionStorage Transactions (r:1 w:0) fn check_proof_max() -> Weight { - (98_295_000 as Weight) + (106_990_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } diff --git a/frame/treasury/src/weights.rs b/frame/treasury/src/weights.rs index 4ae82d3b230d2..f1667bea35480 100644 --- a/frame/treasury/src/weights.rs +++ b/frame/treasury/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_treasury //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-24, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -57,29 +57,29 @@ impl WeightInfo for SubstrateWeight { // Storage: Treasury ProposalCount (r:1 w:1) // Storage: Treasury Proposals (r:0 w:1) fn propose_spend() -> Weight { - (22_397_000 as Weight) + (26_473_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Treasury Proposals (r:1 w:1) // Storage: System Account (r:1 w:1) fn reject_proposal() -> Weight { - (26_004_000 as Weight) + (29_955_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Treasury Proposals (r:1 w:0) // Storage: Treasury Approvals (r:1 w:1) fn approve_proposal(p: u32, ) -> Weight { - (7_415_000 as Weight) + (10_786_000 as Weight) // Standard Error: 0 - .saturating_add((105_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((110_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Treasury Approvals (r:1 w:1) fn remove_approval() -> Weight { - (3_827_000 as Weight) + (6_647_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -88,9 +88,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Treasury Proposals (r:2 w:2) // Storage: System Account (r:4 w:4) fn on_initialize_proposals(p: u32, ) -> Weight { - (25_129_000 as Weight) - // Standard Error: 16_000 - .saturating_add((28_612_000 as Weight).saturating_mul(p as Weight)) + (25_805_000 as Weight) + // Standard Error: 18_000 + .saturating_add((28_473_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(p as Weight))) .saturating_add(T::DbWeight::get().writes(2 as Weight)) @@ -103,29 +103,29 @@ impl WeightInfo for () { // Storage: Treasury ProposalCount (r:1 w:1) // Storage: Treasury Proposals (r:0 w:1) fn propose_spend() -> Weight { - (22_397_000 as Weight) + (26_473_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Treasury Proposals (r:1 w:1) // Storage: System Account (r:1 w:1) fn reject_proposal() -> Weight { - (26_004_000 as Weight) + (29_955_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Treasury Proposals (r:1 w:0) // Storage: Treasury Approvals (r:1 w:1) fn approve_proposal(p: u32, ) -> Weight { - (7_415_000 as Weight) + (10_786_000 as Weight) // Standard Error: 0 - .saturating_add((105_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((110_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Treasury Approvals (r:1 w:1) fn remove_approval() -> Weight { - (3_827_000 as Weight) + (6_647_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -134,9 +134,9 @@ impl WeightInfo for () { // Storage: Treasury Proposals (r:2 w:2) // Storage: System Account (r:4 w:4) fn on_initialize_proposals(p: u32, ) -> Weight { - (25_129_000 as Weight) - // Standard Error: 16_000 - .saturating_add((28_612_000 as Weight).saturating_mul(p as Weight)) + (25_805_000 as Weight) + // Standard Error: 18_000 + .saturating_add((28_473_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(p as Weight))) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) diff --git a/frame/uniques/src/weights.rs b/frame/uniques/src/weights.rs index 127e5ccbb12a3..6e0ac358d3cdd 100644 --- a/frame/uniques/src/weights.rs +++ b/frame/uniques/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_uniques //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-24, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -76,14 +76,14 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn create() -> Weight { - (23_990_000 as Weight) + (28_225_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn force_create() -> Weight { - (13_472_000 as Weight) + (16_513_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -96,12 +96,12 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Account (r:0 w:20) fn destroy(n: u32, m: u32, a: u32, ) -> Weight { (0 as Weight) - // Standard Error: 13_000 - .saturating_add((9_507_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 13_000 - .saturating_add((912_000 as Weight).saturating_mul(m as Weight)) - // Standard Error: 13_000 - .saturating_add((801_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 15_000 + .saturating_add((10_527_000 as Weight).saturating_mul(n as Weight)) + // Standard Error: 15_000 + .saturating_add((1_505_000 as Weight).saturating_mul(m as Weight)) + // Standard Error: 15_000 + .saturating_add((1_401_000 as Weight).saturating_mul(a as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) .saturating_add(T::DbWeight::get().writes(3 as Weight)) @@ -114,7 +114,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques CollectionMaxSupply (r:1 w:0) // Storage: Uniques Account (r:0 w:1) fn mint() -> Weight { - (32_519_000 as Weight) + (35_712_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -122,7 +122,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Account (r:0 w:1) fn burn() -> Weight { - (32_024_000 as Weight) + (35_740_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -130,7 +130,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Account (r:0 w:2) fn transfer() -> Weight { - (23_948_000 as Weight) + (27_425_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -138,8 +138,8 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Asset (r:100 w:100) fn redeposit(i: u32, ) -> Weight { (0 as Weight) - // Standard Error: 11_000 - .saturating_add((11_911_000 as Weight).saturating_mul(i as Weight)) + // Standard Error: 13_000 + .saturating_add((12_499_000 as Weight).saturating_mul(i as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -148,26 +148,26 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn freeze() -> Weight { - (18_448_000 as Weight) + (21_787_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn thaw() -> Weight { - (18_704_000 as Weight) + (21_938_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn freeze_collection() -> Weight { - (13_925_000 as Weight) + (17_122_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn thaw_collection() -> Weight { - (13_902_000 as Weight) + (17_188_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -175,20 +175,20 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:2) fn transfer_ownership() -> Weight { - (20_949_000 as Weight) + (24_539_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn set_team() -> Weight { - (14_531_000 as Weight) + (17_808_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn force_item_status() -> Weight { - (16_623_000 as Weight) + (20_251_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -196,7 +196,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn set_attribute() -> Weight { - (37_593_000 as Weight) + (41_106_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -204,62 +204,62 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn clear_attribute() -> Weight { - (35_304_000 as Weight) + (40_049_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:1) fn set_metadata() -> Weight { - (29_353_000 as Weight) + (33_701_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:1) fn clear_metadata() -> Weight { - (29_323_000 as Weight) + (33_733_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassMetadataOf (r:1 w:1) fn set_collection_metadata() -> Weight { - (28_806_000 as Weight) + (32_848_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques ClassMetadataOf (r:1 w:1) fn clear_collection_metadata() -> Weight { - (26_744_000 as Weight) + (30_232_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) fn approve_transfer() -> Weight { - (19_485_000 as Weight) + (22_890_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) fn cancel_approval() -> Weight { - (19_494_000 as Weight) + (22_845_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques OwnershipAcceptance (r:1 w:1) fn set_accept_ownership() -> Weight { - (17_456_000 as Weight) + (21_490_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques CollectionMaxSupply (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn set_collection_max_supply() -> Weight { - (16_780_000 as Weight) + (19_412_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -270,14 +270,14 @@ impl WeightInfo for () { // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn create() -> Weight { - (23_990_000 as Weight) + (28_225_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn force_create() -> Weight { - (13_472_000 as Weight) + (16_513_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -290,12 +290,12 @@ impl WeightInfo for () { // Storage: Uniques Account (r:0 w:20) fn destroy(n: u32, m: u32, a: u32, ) -> Weight { (0 as Weight) - // Standard Error: 13_000 - .saturating_add((9_507_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 13_000 - .saturating_add((912_000 as Weight).saturating_mul(m as Weight)) - // Standard Error: 13_000 - .saturating_add((801_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 15_000 + .saturating_add((10_527_000 as Weight).saturating_mul(n as Weight)) + // Standard Error: 15_000 + .saturating_add((1_505_000 as Weight).saturating_mul(m as Weight)) + // Standard Error: 15_000 + .saturating_add((1_401_000 as Weight).saturating_mul(a as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) @@ -308,7 +308,7 @@ impl WeightInfo for () { // Storage: Uniques CollectionMaxSupply (r:1 w:0) // Storage: Uniques Account (r:0 w:1) fn mint() -> Weight { - (32_519_000 as Weight) + (35_712_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -316,7 +316,7 @@ impl WeightInfo for () { // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Account (r:0 w:1) fn burn() -> Weight { - (32_024_000 as Weight) + (35_740_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -324,7 +324,7 @@ impl WeightInfo for () { // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Account (r:0 w:2) fn transfer() -> Weight { - (23_948_000 as Weight) + (27_425_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -332,8 +332,8 @@ impl WeightInfo for () { // Storage: Uniques Asset (r:100 w:100) fn redeposit(i: u32, ) -> Weight { (0 as Weight) - // Standard Error: 11_000 - .saturating_add((11_911_000 as Weight).saturating_mul(i as Weight)) + // Standard Error: 13_000 + .saturating_add((12_499_000 as Weight).saturating_mul(i as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(i as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -342,26 +342,26 @@ impl WeightInfo for () { // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn freeze() -> Weight { - (18_448_000 as Weight) + (21_787_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn thaw() -> Weight { - (18_704_000 as Weight) + (21_938_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn freeze_collection() -> Weight { - (13_925_000 as Weight) + (17_122_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn thaw_collection() -> Weight { - (13_902_000 as Weight) + (17_188_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -369,20 +369,20 @@ impl WeightInfo for () { // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:2) fn transfer_ownership() -> Weight { - (20_949_000 as Weight) + (24_539_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn set_team() -> Weight { - (14_531_000 as Weight) + (17_808_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn force_item_status() -> Weight { - (16_623_000 as Weight) + (20_251_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -390,7 +390,7 @@ impl WeightInfo for () { // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn set_attribute() -> Weight { - (37_593_000 as Weight) + (41_106_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -398,62 +398,62 @@ impl WeightInfo for () { // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn clear_attribute() -> Weight { - (35_304_000 as Weight) + (40_049_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:1) fn set_metadata() -> Weight { - (29_353_000 as Weight) + (33_701_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:1) fn clear_metadata() -> Weight { - (29_323_000 as Weight) + (33_733_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassMetadataOf (r:1 w:1) fn set_collection_metadata() -> Weight { - (28_806_000 as Weight) + (32_848_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques ClassMetadataOf (r:1 w:1) fn clear_collection_metadata() -> Weight { - (26_744_000 as Weight) + (30_232_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) fn approve_transfer() -> Weight { - (19_485_000 as Weight) + (22_890_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) fn cancel_approval() -> Weight { - (19_494_000 as Weight) + (22_845_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques OwnershipAcceptance (r:1 w:1) fn set_accept_ownership() -> Weight { - (17_456_000 as Weight) + (21_490_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques CollectionMaxSupply (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn set_collection_max_supply() -> Weight { - (16_780_000 as Weight) + (19_412_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } diff --git a/frame/utility/src/lib.rs b/frame/utility/src/lib.rs index 9a8384f836f8b..0aae2615702dd 100644 --- a/frame/utility/src/lib.rs +++ b/frame/utility/src/lib.rs @@ -60,7 +60,6 @@ use codec::{Decode, Encode}; use frame_support::{ dispatch::PostDispatchInfo, traits::{IsSubType, OriginTrait, UnfilteredDispatchable}, - transactional, weights::{extract_actual_weight, GetDispatchInfo}, }; use sp_core::TypeId; @@ -316,7 +315,6 @@ pub mod pallet { }; (dispatch_weight, dispatch_class) })] - #[transactional] pub fn batch_all( origin: OriginFor, calls: Vec<::Call>, diff --git a/frame/utility/src/tests.rs b/frame/utility/src/tests.rs index f53459a707b54..6368473ac8708 100644 --- a/frame/utility/src/tests.rs +++ b/frame/utility/src/tests.rs @@ -471,11 +471,11 @@ fn batch_all_revert() { assert_eq!(Balances::free_balance(1), 10); assert_eq!(Balances::free_balance(2), 10); + let batch_all_calls = Call::Utility(crate::Call::::batch_all { + calls: vec![call_transfer(2, 5), call_transfer(2, 10), call_transfer(2, 5)], + }); assert_noop!( - Utility::batch_all( - Origin::signed(1), - vec![call_transfer(2, 5), call_transfer(2, 10), call_transfer(2, 5),] - ), + batch_all_calls.dispatch(Origin::signed(1)), DispatchErrorWithPostInfo { post_info: PostDispatchInfo { actual_weight: Some( diff --git a/frame/utility/src/weights.rs b/frame/utility/src/weights.rs index d3cd7530c2584..035831918b1dc 100644 --- a/frame/utility/src/weights.rs +++ b/frame/utility/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_utility //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-24, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -55,49 +55,49 @@ pub trait WeightInfo { pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { fn batch(c: u32, ) -> Weight { - (14_744_000 as Weight) + (20_307_000 as Weight) // Standard Error: 1_000 - .saturating_add((2_549_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((2_701_000 as Weight).saturating_mul(c as Weight)) } fn as_derivative() -> Weight { - (1_412_000 as Weight) + (4_082_000 as Weight) } fn batch_all(c: u32, ) -> Weight { - (20_835_000 as Weight) - // Standard Error: 1_000 - .saturating_add((2_704_000 as Weight).saturating_mul(c as Weight)) + (3_087_000 as Weight) + // Standard Error: 2_000 + .saturating_add((2_842_000 as Weight).saturating_mul(c as Weight)) } fn dispatch_as() -> Weight { - (8_889_000 as Weight) + (11_832_000 as Weight) } fn force_batch(c: u32, ) -> Weight { - (13_155_000 as Weight) + (19_750_000 as Weight) // Standard Error: 1_000 - .saturating_add((2_555_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((2_715_000 as Weight).saturating_mul(c as Weight)) } } // For backwards compatibility and tests impl WeightInfo for () { fn batch(c: u32, ) -> Weight { - (14_744_000 as Weight) + (20_307_000 as Weight) // Standard Error: 1_000 - .saturating_add((2_549_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((2_701_000 as Weight).saturating_mul(c as Weight)) } fn as_derivative() -> Weight { - (1_412_000 as Weight) + (4_082_000 as Weight) } fn batch_all(c: u32, ) -> Weight { - (20_835_000 as Weight) - // Standard Error: 1_000 - .saturating_add((2_704_000 as Weight).saturating_mul(c as Weight)) + (3_087_000 as Weight) + // Standard Error: 2_000 + .saturating_add((2_842_000 as Weight).saturating_mul(c as Weight)) } fn dispatch_as() -> Weight { - (8_889_000 as Weight) + (11_832_000 as Weight) } fn force_batch(c: u32, ) -> Weight { - (13_155_000 as Weight) + (19_750_000 as Weight) // Standard Error: 1_000 - .saturating_add((2_555_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((2_715_000 as Weight).saturating_mul(c as Weight)) } } diff --git a/frame/vesting/src/weights.rs b/frame/vesting/src/weights.rs index 356cd003d8848..4596157e63b7b 100644 --- a/frame/vesting/src/weights.rs +++ b/frame/vesting/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_vesting //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-24, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -60,22 +60,22 @@ impl WeightInfo for SubstrateWeight { // Storage: Vesting Vesting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vest_locked(l: u32, s: u32, ) -> Weight { - (27_818_000 as Weight) + (32_978_000 as Weight) // Standard Error: 1_000 - .saturating_add((81_000 as Weight).saturating_mul(l as Weight)) + .saturating_add((82_000 as Weight).saturating_mul(l as Weight)) // Standard Error: 2_000 - .saturating_add((74_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((88_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Vesting Vesting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vest_unlocked(l: u32, s: u32, ) -> Weight { - (27_708_000 as Weight) + (32_856_000 as Weight) // Standard Error: 1_000 .saturating_add((79_000 as Weight).saturating_mul(l as Weight)) // Standard Error: 2_000 - .saturating_add((43_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((56_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -83,11 +83,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) fn vest_other_locked(l: u32, s: u32, ) -> Weight { - (27_854_000 as Weight) + (33_522_000 as Weight) // Standard Error: 1_000 - .saturating_add((77_000 as Weight).saturating_mul(l as Weight)) + .saturating_add((74_000 as Weight).saturating_mul(l as Weight)) // Standard Error: 2_000 - .saturating_add((73_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((72_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -95,11 +95,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) fn vest_other_unlocked(l: u32, s: u32, ) -> Weight { - (27_738_000 as Weight) + (32_558_000 as Weight) // Standard Error: 1_000 - .saturating_add((71_000 as Weight).saturating_mul(l as Weight)) + .saturating_add((78_000 as Weight).saturating_mul(l as Weight)) // Standard Error: 2_000 - .saturating_add((42_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((61_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -107,11 +107,11 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vested_transfer(l: u32, s: u32, ) -> Weight { - (43_640_000 as Weight) + (49_260_000 as Weight) // Standard Error: 1_000 - .saturating_add((71_000 as Weight).saturating_mul(l as Weight)) + .saturating_add((80_000 as Weight).saturating_mul(l as Weight)) // Standard Error: 3_000 - .saturating_add((45_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((55_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -119,11 +119,11 @@ impl WeightInfo for SubstrateWeight { // Storage: System Account (r:2 w:2) // Storage: Balances Locks (r:1 w:1) fn force_vested_transfer(l: u32, s: u32, ) -> Weight { - (42_850_000 as Weight) - // Standard Error: 1_000 - .saturating_add((81_000 as Weight).saturating_mul(l as Weight)) - // Standard Error: 3_000 - .saturating_add((48_000 as Weight).saturating_mul(s as Weight)) + (49_166_000 as Weight) + // Standard Error: 2_000 + .saturating_add((77_000 as Weight).saturating_mul(l as Weight)) + // Standard Error: 4_000 + .saturating_add((43_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -131,11 +131,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) fn not_unlocking_merge_schedules(l: u32, s: u32, ) -> Weight { - (28_683_000 as Weight) + (34_042_000 as Weight) // Standard Error: 1_000 - .saturating_add((81_000 as Weight).saturating_mul(l as Weight)) + .saturating_add((83_000 as Weight).saturating_mul(l as Weight)) // Standard Error: 2_000 - .saturating_add((63_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((80_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -143,11 +143,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) fn unlocking_merge_schedules(l: u32, s: u32, ) -> Weight { - (28_510_000 as Weight) + (33_937_000 as Weight) // Standard Error: 1_000 - .saturating_add((77_000 as Weight).saturating_mul(l as Weight)) + .saturating_add((78_000 as Weight).saturating_mul(l as Weight)) // Standard Error: 2_000 - .saturating_add((65_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((73_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -158,22 +158,22 @@ impl WeightInfo for () { // Storage: Vesting Vesting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vest_locked(l: u32, s: u32, ) -> Weight { - (27_818_000 as Weight) + (32_978_000 as Weight) // Standard Error: 1_000 - .saturating_add((81_000 as Weight).saturating_mul(l as Weight)) + .saturating_add((82_000 as Weight).saturating_mul(l as Weight)) // Standard Error: 2_000 - .saturating_add((74_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((88_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Vesting Vesting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vest_unlocked(l: u32, s: u32, ) -> Weight { - (27_708_000 as Weight) + (32_856_000 as Weight) // Standard Error: 1_000 .saturating_add((79_000 as Weight).saturating_mul(l as Weight)) // Standard Error: 2_000 - .saturating_add((43_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((56_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -181,11 +181,11 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) fn vest_other_locked(l: u32, s: u32, ) -> Weight { - (27_854_000 as Weight) + (33_522_000 as Weight) // Standard Error: 1_000 - .saturating_add((77_000 as Weight).saturating_mul(l as Weight)) + .saturating_add((74_000 as Weight).saturating_mul(l as Weight)) // Standard Error: 2_000 - .saturating_add((73_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((72_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -193,11 +193,11 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) fn vest_other_unlocked(l: u32, s: u32, ) -> Weight { - (27_738_000 as Weight) + (32_558_000 as Weight) // Standard Error: 1_000 - .saturating_add((71_000 as Weight).saturating_mul(l as Weight)) + .saturating_add((78_000 as Weight).saturating_mul(l as Weight)) // Standard Error: 2_000 - .saturating_add((42_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((61_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -205,11 +205,11 @@ impl WeightInfo for () { // Storage: System Account (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn vested_transfer(l: u32, s: u32, ) -> Weight { - (43_640_000 as Weight) + (49_260_000 as Weight) // Standard Error: 1_000 - .saturating_add((71_000 as Weight).saturating_mul(l as Weight)) + .saturating_add((80_000 as Weight).saturating_mul(l as Weight)) // Standard Error: 3_000 - .saturating_add((45_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((55_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -217,11 +217,11 @@ impl WeightInfo for () { // Storage: System Account (r:2 w:2) // Storage: Balances Locks (r:1 w:1) fn force_vested_transfer(l: u32, s: u32, ) -> Weight { - (42_850_000 as Weight) - // Standard Error: 1_000 - .saturating_add((81_000 as Weight).saturating_mul(l as Weight)) - // Standard Error: 3_000 - .saturating_add((48_000 as Weight).saturating_mul(s as Weight)) + (49_166_000 as Weight) + // Standard Error: 2_000 + .saturating_add((77_000 as Weight).saturating_mul(l as Weight)) + // Standard Error: 4_000 + .saturating_add((43_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } @@ -229,11 +229,11 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) fn not_unlocking_merge_schedules(l: u32, s: u32, ) -> Weight { - (28_683_000 as Weight) + (34_042_000 as Weight) // Standard Error: 1_000 - .saturating_add((81_000 as Weight).saturating_mul(l as Weight)) + .saturating_add((83_000 as Weight).saturating_mul(l as Weight)) // Standard Error: 2_000 - .saturating_add((63_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((80_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -241,11 +241,11 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) fn unlocking_merge_schedules(l: u32, s: u32, ) -> Weight { - (28_510_000 as Weight) + (33_937_000 as Weight) // Standard Error: 1_000 - .saturating_add((77_000 as Weight).saturating_mul(l as Weight)) + .saturating_add((78_000 as Weight).saturating_mul(l as Weight)) // Standard Error: 2_000 - .saturating_add((65_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((73_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } diff --git a/frame/whitelist/src/weights.rs b/frame/whitelist/src/weights.rs index b074cbf00d40d..81482c35e3de8 100644 --- a/frame/whitelist/src/weights.rs +++ b/frame/whitelist/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_whitelist //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-05-24, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -56,7 +56,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Whitelist WhitelistedCall (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) fn whitelist_call() -> Weight { - (16_953_000 as Weight) + (20_938_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -64,7 +64,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Preimage StatusFor (r:1 w:1) // Storage: Preimage PreimageFor (r:0 w:1) fn remove_whitelisted_call() -> Weight { - (18_515_000 as Weight) + (22_332_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -72,7 +72,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Preimage PreimageFor (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) fn dispatch_whitelisted_call() -> Weight { - (5_870_296_000 as Weight) + (5_989_917_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -80,7 +80,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Preimage StatusFor (r:1 w:1) // Storage: Preimage PreimageFor (r:0 w:1) fn dispatch_whitelisted_call_with_preimage(n: u32, ) -> Weight { - (21_179_000 as Weight) + (25_325_000 as Weight) // Standard Error: 0 .saturating_add((1_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) @@ -93,7 +93,7 @@ impl WeightInfo for () { // Storage: Whitelist WhitelistedCall (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) fn whitelist_call() -> Weight { - (16_953_000 as Weight) + (20_938_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -101,7 +101,7 @@ impl WeightInfo for () { // Storage: Preimage StatusFor (r:1 w:1) // Storage: Preimage PreimageFor (r:0 w:1) fn remove_whitelisted_call() -> Weight { - (18_515_000 as Weight) + (22_332_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -109,7 +109,7 @@ impl WeightInfo for () { // Storage: Preimage PreimageFor (r:1 w:1) // Storage: Preimage StatusFor (r:1 w:1) fn dispatch_whitelisted_call() -> Weight { - (5_870_296_000 as Weight) + (5_989_917_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -117,7 +117,7 @@ impl WeightInfo for () { // Storage: Preimage StatusFor (r:1 w:1) // Storage: Preimage PreimageFor (r:0 w:1) fn dispatch_whitelisted_call_with_preimage(n: u32, ) -> Weight { - (21_179_000 as Weight) + (25_325_000 as Weight) // Standard Error: 0 .saturating_add((1_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) From d642a85bcc37a4f45d20361d186fe53e6565cd2e Mon Sep 17 00:00:00 2001 From: Denis Pisarev Date: Fri, 27 May 2022 10:44:57 +0200 Subject: [PATCH 273/484] CI: github no longer checks whitelisted actions this way (#11507) * CI: github no longer checks whitelisted actions this way * CI: actually this one is needed and no one knows it's related to a Markdown Link Check --- .github/allowed-actions.js | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 .github/allowed-actions.js diff --git a/.github/allowed-actions.js b/.github/allowed-actions.js deleted file mode 100644 index 4fb894758060d..0000000000000 --- a/.github/allowed-actions.js +++ /dev/null @@ -1,7 +0,0 @@ -// This is a whitelist of GitHub Actions that are approved for use in this project. -// If a new or existing workflow file is updated to use an action or action version -// not listed here, CI will fail. - -module.exports = [ - 'gaurav-nelson/github-action-markdown-link-check@7481451f70251762f149d69596e3e276ebf2b236', // gaurav-nelson/github-action-markdown-link-check@v1.0.8 -] From 219603067a14d3b4f7303b4cfa0564f613112ea5 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Fri, 27 May 2022 13:29:31 +0200 Subject: [PATCH 274/484] Fork-Tree import requires post-order DFS traversal (#11531) * Fork-tree insert requires post-order dfs traversal * Add dedicated test for methods requireing post-order traversal --- utils/fork-tree/src/lib.rs | 80 +++++++++++++++++++++----------------- 1 file changed, 44 insertions(+), 36 deletions(-) diff --git a/utils/fork-tree/src/lib.rs b/utils/fork-tree/src/lib.rs index ab56ecb6360b3..76c28d910f943 100644 --- a/utils/fork-tree/src/lib.rs +++ b/utils/fork-tree/src/lib.rs @@ -126,6 +126,11 @@ where /// imported in order. /// /// Returns `true` if the imported node is a root. + // WARNING: some users of this method (i.e. consensus epoch changes tree) currently silently + // rely on a **post-order DFS** traversal. If we are using instead a top-down traversal method + // then the `is_descendent_of` closure, when used after a warp-sync, may end up querying the + // backend for a block (the one corresponding to the root) that is not present and thus will + // return a wrong result. pub fn import( &mut self, hash: H, @@ -143,29 +148,20 @@ where } } - let mut children = &mut self.roots; - let mut i = 0; - while i < children.len() { - let child = &children[i]; - if child.hash == hash { - return Err(Error::Duplicate) - } - if child.number < number && is_descendent_of(&child.hash, &hash)? { - children = &mut children[i].children; - i = 0; - } else { - i += 1; - } + let (children, is_root) = + match self.find_node_where_mut(&hash, &number, is_descendent_of, &|_| true)? { + Some(parent) => (&mut parent.children, false), + None => (&mut self.roots, true), + }; + + if children.iter().any(|elem| elem.hash == hash) { + return Err(Error::Duplicate) } - let is_first = children.is_empty(); children.push(Node { data, hash, number, children: Default::default() }); - // Quick way to check if the pushed node is a root - let is_root = children.as_ptr() == self.roots.as_ptr(); - - if is_first { - // Rebalance is required only if we've extended the branch depth. + if children.len() == 1 { + // Rebalance may be required only if we've extended the branch depth. self.rebalance(); } @@ -293,7 +289,7 @@ where // rely on a **post-order DFS** traversal. If we are using instead a top-down traversal method // then the `is_descendent_of` closure, when used after a warp-sync, will end up querying the // backend for a block (the one corresponding to the root) that is not present and thus will - // return a wrong result. Here we are implementing a post-order DFS. + // return a wrong result. pub fn find_node_index_where( &self, hash: &H, @@ -1441,22 +1437,6 @@ mod test { .unwrap() .unwrap(); assert_eq!(path, [0, 1, 0, 0, 0]); - - // Test for the post-order DFS requirement as specified by the `find_node_index_where` - // comment. Once (and if) post-order traversal requirement is removed, then this test - // can be removed as well. In practice this test should fail with a pre-order DFS. - let is_descendent_of_for_post_order = |parent: &&str, child: &&str| { - if *parent == "A" { - Err(TestError) - } else { - is_descendent_of(parent, child) - } - }; - let path = tree - .find_node_index_where(&"N", &6, &is_descendent_of_for_post_order, &|_| true) - .unwrap() - .unwrap(); - assert_eq!(path, [0, 1, 0, 0, 0]); } #[test] @@ -1521,4 +1501,32 @@ mod test { let node = tree.find_node_where(&"N", &6, &is_descendent_of, &|_| true).unwrap().unwrap(); assert_eq!((node.hash, node.number), ("M", 5)); } + + #[test] + fn post_order_traversal_requirement() { + let (mut tree, is_descendent_of) = test_fork_tree(); + + // Test for the post-order DFS traversal requirement as specified by the + // `find_node_index_where` and `import` comments. + let is_descendent_of_for_post_order = |parent: &&str, child: &&str| match *parent { + "A" => Err(TestError), + "K" if *child == "Z" => Ok(true), + _ => is_descendent_of(parent, child), + }; + + // Post order traversal requirement for `find_node_index_where` + let path = tree + .find_node_index_where(&"N", &6, &is_descendent_of_for_post_order, &|_| true) + .unwrap() + .unwrap(); + assert_eq!(path, [0, 1, 0, 0, 0]); + + // Post order traversal requirement for `import` + let res = tree.import(&"Z", 100, (), &is_descendent_of_for_post_order); + assert_eq!(res, Ok(false)); + assert_eq!( + tree.iter().map(|node| *node.0).collect::>(), + vec!["A", "B", "C", "D", "E", "F", "H", "L", "M", "O", "I", "G", "J", "K", "Z"], + ); + } } From 688c8711ec19eb3c8526cebcf5fff966e7eb86b8 Mon Sep 17 00:00:00 2001 From: Sergej Sakac <73715684+Szegoo@users.noreply.github.com> Date: Sat, 28 May 2022 11:55:51 +0200 Subject: [PATCH 275/484] Fixed pruning docs (#11519) --- client/cli/src/params/pruning_params.rs | 6 +++--- client/state-db/src/lib.rs | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/client/cli/src/params/pruning_params.rs b/client/cli/src/params/pruning_params.rs index 0f3d1013381e6..fa30aa53b8a04 100644 --- a/client/cli/src/params/pruning_params.rs +++ b/client/cli/src/params/pruning_params.rs @@ -25,9 +25,9 @@ use sc_service::{KeepBlocks, PruningMode}; pub struct PruningParams { /// Specify the state pruning mode, a number of blocks to keep or 'archive'. /// - /// Default is to keep all block states if the node is running as a - /// validator (i.e. 'archive'), otherwise state is only kept for the last - /// 256 blocks. + /// Default is to keep only the last 256 blocks, + /// otherwise, the state can be kept for all of the blocks (i.e 'archive'), + /// or for all of the canonical blocks (i.e 'archive-canonical'). #[clap(long, value_name = "PRUNING_MODE")] pub pruning: Option, /// Specify the number of finalized blocks to keep in the database. diff --git a/client/state-db/src/lib.rs b/client/state-db/src/lib.rs index 794dec3b954b7..d5cca9a342187 100644 --- a/client/state-db/src/lib.rs +++ b/client/state-db/src/lib.rs @@ -239,7 +239,7 @@ impl PruningMode { } } - /// Is this an archive (either ArchiveAll or ArchiveCanonical) pruning mode? + /// Returns the pruning mode pub fn id(&self) -> &[u8] { match self { PruningMode::ArchiveAll => PRUNING_MODE_ARCHIVE, @@ -247,6 +247,7 @@ impl PruningMode { PruningMode::Constrained(_) => PRUNING_MODE_CONSTRAINED, } } + pub fn from_id(id: &[u8]) -> Option { match id { PRUNING_MODE_ARCHIVE => Some(Self::ArchiveAll), From b72fc44bf738dc6821bdc59a2c9f39cc58b58e34 Mon Sep 17 00:00:00 2001 From: Alexander Gryaznov Date: Sun, 29 May 2022 11:28:03 +0300 Subject: [PATCH 276/484] fix broken links (#11536) --- frame/contracts/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frame/contracts/README.md b/frame/contracts/README.md index 8a8e4918f2e41..bd5e58d89d1ce 100644 --- a/frame/contracts/README.md +++ b/frame/contracts/README.md @@ -2,10 +2,10 @@ The Contract module provides functionality for the runtime to deploy and execute WebAssembly smart-contracts. -- [`Call`](https://docs.rs/pallet-contracts/latest/pallet_contracts/enum.Call.html) -- [`Config`](https://docs.rs/pallet-contracts/latest/pallet_contracts/trait.Config.html) -- [`Error`](https://docs.rs/pallet-contracts/latest/pallet_contracts/enum.Error.html) -- [`Event`](https://docs.rs/pallet-contracts/latest/pallet_contracts/enum.Event.html) +- [`Call`](https://paritytech.github.io/substrate/master/pallet_contracts/pallet/enum.Call.html) +- [`Config`](https://paritytech.github.io/substrate/master/pallet_contracts/pallet/trait.Config.html) +- [`Error`](https://paritytech.github.io/substrate/master/pallet_contracts/pallet/enum.Error.html) +- [`Event`](https://paritytech.github.io/substrate/master/pallet_contracts/pallet/enum.Error.html) ## Overview @@ -47,7 +47,7 @@ fails, A can decide how to handle that failure, either proceeding or reverting A ### Dispatchable functions -Those are documented in the [reference documentation](https://docs.rs/pallet-contracts/latest/pallet_contracts/#dispatchable-functions). +Those are documented in the [reference documentation](https://paritytech.github.io/substrate/master/pallet_contracts/index.html#dispatchable-functions). ### Interface exposed to contracts @@ -89,7 +89,7 @@ writing WebAssembly based smart contracts in the Rust programming language. Contracts can emit messages to the client when called as RPC through the `seal_debug_message` API. This is exposed in ink! via -[`ink_env::debug_println()`](https://docs.rs/ink_env/latest/ink_env/fn.debug_println.html). +[`ink_env::debug_message()`](https://paritytech.github.io/ink/ink_env/fn.debug_message.html). Those messages are gathered into an internal buffer and send to the RPC client. It is up the the individual client if and how those messages are presented to the user. From 2d65dbfa5c94cd2e6cbd316ce07132f990b5fcb8 Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Sun, 29 May 2022 12:56:26 +0100 Subject: [PATCH 277/484] Safe and sane multi-item storage removal (#11490) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix overlay prefix removal result * Second part of the overlay prefix removal fix. * Report only items deleted from storage in clear_prefix * Fix kill_prefix * Formatting * Remove unused code * Fixes * Fixes * Introduce clear_prefix host function v3 * Formatting * Use v2 for now * Fixes * Formatting * Docs * Child prefix removal should also hide v3 for now * Fixes * Fixes * Formatting * Fixes * apply_to_keys_whle takes start_at * apply_to_keys_whle takes start_at * apply_to_keys_whle takes start_at * Cursor API; force limits * Use unsafe deprecated functions * Formatting * Fixes * Grumbles * Fixes * Docs * Some nitpicks :see_no_evil: * Update primitives/externalities/src/lib.rs Co-authored-by: Bastian Köcher * Formatting * Fixes * cargo fmt * Fixes * Update primitives/io/src/lib.rs Co-authored-by: Keith Yeung * Formatting * Fixes Co-authored-by: Bastian Köcher Co-authored-by: Bastian Köcher Co-authored-by: Keith Yeung --- Cargo.lock | 1 + client/db/src/bench.rs | 3 +- client/db/src/lib.rs | 3 +- client/db/src/storage_cache.rs | 6 +- frame/bags-list/src/list/mod.rs | 2 + frame/contracts/src/migration.rs | 1 + frame/contracts/src/storage.rs | 14 +- frame/elections-phragmen/src/benchmarking.rs | 1 + frame/im-online/src/lib.rs | 2 + frame/scheduler/src/lib.rs | 2 + frame/society/src/lib.rs | 3 + frame/staking/src/pallet/impls.rs | 9 ++ frame/staking/src/slashing.rs | 2 + frame/staking/src/testing_utils.rs | 2 + frame/support/src/lib.rs | 8 +- frame/support/src/storage/child.rs | 55 ++++++- .../src/storage/generator/double_map.rs | 21 ++- frame/support/src/storage/generator/nmap.rs | 17 +- frame/support/src/storage/migration.rs | 33 +++- frame/support/src/storage/mod.rs | 92 ++++++++++- .../support/src/storage/types/counted_map.rs | 46 +++++- frame/support/src/storage/types/double_map.rs | 75 ++++++++- frame/support/src/storage/types/map.rs | 33 +++- frame/support/src/storage/types/nmap.rs | 152 ++++++++++++++---- frame/support/src/storage/unhashed.rs | 53 ++++++ frame/system/src/lib.rs | 4 +- frame/uniques/src/functions.rs | 2 + primitives/externalities/src/lib.rs | 61 +++++-- primitives/io/Cargo.toml | 4 +- primitives/io/src/lib.rs | 152 +++++++++++++++--- primitives/runtime-interface/Cargo.toml | 4 +- primitives/runtime-interface/src/impls.rs | 4 + primitives/state-machine/Cargo.toml | 1 + primitives/state-machine/src/backend.rs | 1 + primitives/state-machine/src/basic.rs | 46 ++++-- primitives/state-machine/src/ext.rs | 113 +++++++------ primitives/state-machine/src/lib.rs | 52 ++++-- .../src/overlayed_changes/changeset.rs | 7 +- .../src/overlayed_changes/mod.rs | 12 +- .../state-machine/src/proving_backend.rs | 3 +- primitives/state-machine/src/read_only.rs | 20 ++- primitives/state-machine/src/testing.rs | 5 +- primitives/state-machine/src/trie_backend.rs | 3 +- .../state-machine/src/trie_backend_essence.rs | 23 +-- primitives/tasks/src/async_externalities.rs | 21 ++- 45 files changed, 968 insertions(+), 206 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7b1a05b7067e2..29d15b9e015cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10070,6 +10070,7 @@ dependencies = [ name = "sp-state-machine" version = "0.12.0" dependencies = [ + "assert_matches", "hash-db", "hex-literal", "log", diff --git a/client/db/src/bench.rs b/client/db/src/bench.rs index 8bc4a0f4893c7..d3d43e742d026 100644 --- a/client/db/src/bench.rs +++ b/client/db/src/bench.rs @@ -393,10 +393,11 @@ impl StateBackend> for BenchmarkingState { &self, child_info: Option<&ChildInfo>, prefix: Option<&[u8]>, + start_at: Option<&[u8]>, f: F, ) { if let Some(ref state) = *self.state.borrow() { - state.apply_to_keys_while(child_info, prefix, f) + state.apply_to_keys_while(child_info, prefix, start_at, f) } } diff --git a/client/db/src/lib.rs b/client/db/src/lib.rs index a32a666c3c980..5f15b2d6fe969 100644 --- a/client/db/src/lib.rs +++ b/client/db/src/lib.rs @@ -226,9 +226,10 @@ impl StateBackend> for RefTrackingState { &self, child_info: Option<&ChildInfo>, prefix: Option<&[u8]>, + start_at: Option<&[u8]>, f: F, ) { - self.state.apply_to_keys_while(child_info, prefix, f) + self.state.apply_to_keys_while(child_info, prefix, start_at, f) } fn for_child_keys_with_prefix( diff --git a/client/db/src/storage_cache.rs b/client/db/src/storage_cache.rs index 9dada92b066ea..8326946999946 100644 --- a/client/db/src/storage_cache.rs +++ b/client/db/src/storage_cache.rs @@ -639,9 +639,10 @@ impl>, B: BlockT> StateBackend> for Cachin &self, child_info: Option<&ChildInfo>, prefix: Option<&[u8]>, + start_at: Option<&[u8]>, f: F, ) { - self.state.apply_to_keys_while(child_info, prefix, f) + self.state.apply_to_keys_while(child_info, prefix, start_at, f) } fn next_storage_key(&self, key: &[u8]) -> Result>, Self::Error> { @@ -839,9 +840,10 @@ impl>, B: BlockT> StateBackend> &self, child_info: Option<&ChildInfo>, prefix: Option<&[u8]>, + start_at: Option<&[u8]>, f: F, ) { - self.caching_state().apply_to_keys_while(child_info, prefix, f) + self.caching_state().apply_to_keys_while(child_info, prefix, start_at, f) } fn next_storage_key(&self, key: &[u8]) -> Result>, Self::Error> { diff --git a/frame/bags-list/src/list/mod.rs b/frame/bags-list/src/list/mod.rs index d95c5de8132ea..b4f852685842d 100644 --- a/frame/bags-list/src/list/mod.rs +++ b/frame/bags-list/src/list/mod.rs @@ -94,7 +94,9 @@ impl, I: 'static> List { /// this function should generally not be used in production as it could lead to a very large /// number of storage accesses. pub(crate) fn unsafe_clear() { + #[allow(deprecated)] crate::ListBags::::remove_all(None); + #[allow(deprecated)] crate::ListNodes::::remove_all(); } diff --git a/frame/contracts/src/migration.rs b/frame/contracts/src/migration.rs index 0832ebadac9c6..19e699a855461 100644 --- a/frame/contracts/src/migration.rs +++ b/frame/contracts/src/migration.rs @@ -56,6 +56,7 @@ mod v4 { use super::*; pub fn migrate() -> Weight { + #[allow(deprecated)] migration::remove_storage_prefix(>::name().as_bytes(), b"CurrentSchedule", b""); T::DbWeight::get().writes(1) } diff --git a/frame/contracts/src/storage.rs b/frame/contracts/src/storage.rs index af6dbe3c62bfc..e73eb8d3bd55e 100644 --- a/frame/contracts/src/storage.rs +++ b/frame/contracts/src/storage.rs @@ -27,12 +27,12 @@ use crate::{ use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ dispatch::{DispatchError, DispatchResult}, - storage::child::{self, ChildInfo, KillStorageResult}, + storage::child::{self, ChildInfo}, weights::Weight, }; use scale_info::TypeInfo; use sp_core::crypto::UncheckedFrom; -use sp_io::hashing::blake2_256; +use sp_io::{hashing::blake2_256, KillStorageResult}; use sp_runtime::{ traits::{Hash, Zero}, RuntimeDebug, @@ -266,16 +266,16 @@ where while !queue.is_empty() && remaining_key_budget > 0 { // Cannot panic due to loop condition let trie = &mut queue[0]; - let outcome = - child::kill_storage(&child_trie_info(&trie.trie_id), Some(remaining_key_budget)); + #[allow(deprecated)] + let outcome = child::kill_storage(&child_trie_info(&trie.trie_id), Some(remaining_key_budget)); let keys_removed = match outcome { // This happens when our budget wasn't large enough to remove all keys. - KillStorageResult::SomeRemaining(count) => count, - KillStorageResult::AllRemoved(count) => { + KillStorageResult::SomeRemaining(c) => c, + KillStorageResult::AllRemoved(c) => { // We do not care to preserve order. The contract is deleted already and // no one waits for the trie to be deleted. queue.swap_remove(0); - count + c }, }; remaining_key_budget = remaining_key_budget.saturating_sub(keys_removed); diff --git a/frame/elections-phragmen/src/benchmarking.rs b/frame/elections-phragmen/src/benchmarking.rs index 493f20924203c..44dd6aff09f0c 100644 --- a/frame/elections-phragmen/src/benchmarking.rs +++ b/frame/elections-phragmen/src/benchmarking.rs @@ -148,6 +148,7 @@ fn clean() { >::kill(); >::kill(); >::kill(); + #[allow(deprecated)] >::remove_all(None); } diff --git a/frame/im-online/src/lib.rs b/frame/im-online/src/lib.rs index 05b7618b286a6..f190f6672f309 100644 --- a/frame/im-online/src/lib.rs +++ b/frame/im-online/src/lib.rs @@ -900,7 +900,9 @@ impl OneSessionHandler for Pallet { // Remove all received heartbeats and number of authored blocks from the // current session, they have already been processed and won't be needed // anymore. + #[allow(deprecated)] ReceivedHeartbeats::::remove_prefix(&T::ValidatorSet::session_index(), None); + #[allow(deprecated)] AuthoredBlocks::::remove_prefix(&T::ValidatorSet::session_index(), None); if offenders.is_empty() { diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index 9b0fc87587e3a..a005c051a1abc 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -565,6 +565,7 @@ impl Pallet { }, ); + #[allow(deprecated)] frame_support::storage::migration::remove_storage_prefix( Self::name().as_bytes(), b"StorageVersion", @@ -601,6 +602,7 @@ impl Pallet { ) }); + #[allow(deprecated)] frame_support::storage::migration::remove_storage_prefix( Self::name().as_bytes(), b"StorageVersion", diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index 2973ecd68092e..5a993f72f32d2 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -1067,6 +1067,7 @@ pub mod pallet { Founder::::kill(); Rules::::kill(); Candidates::::kill(); + #[allow(deprecated)] SuspendedCandidates::::remove_all(None); Self::deposit_event(Event::::Unfounded { founder }); Ok(()) @@ -1511,6 +1512,7 @@ impl, I: 'static> Pallet { .collect::>(); // Clean up all votes. + #[allow(deprecated)] >::remove_all(None); // Reward one of the voters who voted the right way. @@ -1695,6 +1697,7 @@ impl, I: 'static> Pallet { } // Clean up all votes. + #[allow(deprecated)] >::remove_all(None); } diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 4de1ea36cb591..66265ab1f135e 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -585,8 +585,11 @@ impl Pallet { /// Clear all era information for given era. pub(crate) fn clear_era_information(era_index: EraIndex) { + #[allow(deprecated)] >::remove_prefix(era_index, None); + #[allow(deprecated)] >::remove_prefix(era_index, None); + #[allow(deprecated)] >::remove_prefix(era_index, None); >::remove(era_index); >::remove(era_index); @@ -984,9 +987,13 @@ impl ElectionDataProvider for Pallet { #[cfg(feature = "runtime-benchmarks")] fn clear() { + #[allow(deprecated)] >::remove_all(None); + #[allow(deprecated)] >::remove_all(None); + #[allow(deprecated)] >::remove_all(); + #[allow(deprecated)] >::remove_all(); T::VoterList::unsafe_clear(); @@ -1368,7 +1375,9 @@ impl SortedListProvider for UseNominatorsAndValidatorsM fn unsafe_clear() { // NOTE: Caller must ensure this doesn't lead to too many storage accesses. This is a // condition of SortedListProvider::unsafe_clear. + #[allow(deprecated)] Nominators::::remove_all(); + #[allow(deprecated)] Validators::::remove_all(); } } diff --git a/frame/staking/src/slashing.rs b/frame/staking/src/slashing.rs index 25954a3699b31..7372c4390f816 100644 --- a/frame/staking/src/slashing.rs +++ b/frame/staking/src/slashing.rs @@ -557,7 +557,9 @@ impl<'a, T: 'a + Config> Drop for InspectingSpans<'a, T> { /// Clear slashing metadata for an obsolete era. pub(crate) fn clear_era_metadata(obsolete_era: EraIndex) { + #[allow(deprecated)] as Store>::ValidatorSlashInEra::remove_prefix(&obsolete_era, None); + #[allow(deprecated)] as Store>::NominatorSlashInEra::remove_prefix(&obsolete_era, None); } diff --git a/frame/staking/src/testing_utils.rs b/frame/staking/src/testing_utils.rs index f30020597db45..ba67292ddc434 100644 --- a/frame/staking/src/testing_utils.rs +++ b/frame/staking/src/testing_utils.rs @@ -36,9 +36,11 @@ const SEED: u32 = 0; /// This function removes all validators and nominators from storage. pub fn clear_validators_and_nominators() { + #[allow(deprecated)] Validators::::remove_all(); // whenever we touch nominators counter we should update `T::VoterList` as well. + #[allow(deprecated)] Nominators::::remove_all(); // NOTE: safe to call outside block production diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index 03f1553293a47..178fddb61b0a2 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -1097,8 +1097,12 @@ pub mod tests { DoubleMap::insert(&(key1 + 1), &key2, &4u64); DoubleMap::insert(&(key1 + 1), &(key2 + 1), &4u64); assert!(matches!( - DoubleMap::remove_prefix(&key1, None), - sp_io::KillStorageResult::AllRemoved(0), // all in overlay + DoubleMap::clear_prefix(&key1, u32::max_value(), None), + // Note this is the incorrect answer (for now), since we are using v2 of + // `clear_prefix`. + // When we switch to v3, then this will become: + // sp_io::MultiRemovalResults::NoneLeft { db: 0, total: 2 }, + sp_io::MultiRemovalResults { maybe_cursor: None, backend: 0, unique: 0, loops: 0 }, )); assert_eq!(DoubleMap::get(&key1, &key2), 0u64); assert_eq!(DoubleMap::get(&key1, &(key2 + 1)), 0u64); diff --git a/frame/support/src/storage/child.rs b/frame/support/src/storage/child.rs index e20d4fe0cd4d3..397daaa82a677 100644 --- a/frame/support/src/storage/child.rs +++ b/frame/support/src/storage/child.rs @@ -21,7 +21,7 @@ // NOTE: could replace unhashed by having only one kind of storage (top trie being the child info // of null length parent storage key). -pub use crate::sp_io::KillStorageResult; +pub use crate::sp_io::{KillStorageResult, MultiRemovalResults}; use crate::sp_std::prelude::*; use codec::{Codec, Decode, Encode}; pub use sp_core::storage::{ChildInfo, ChildType, StateVersion}; @@ -136,6 +136,7 @@ pub fn exists(child_info: &ChildInfo, key: &[u8]) -> bool { /// not make much sense because it is not cumulative when called inside the same block. /// Use this function to distribute the deletion of a single child trie across multiple /// blocks. +#[deprecated = "Use `clear_storage` instead"] pub fn kill_storage(child_info: &ChildInfo, limit: Option) -> KillStorageResult { match child_info.child_type() { ChildType::ParentKeyId => @@ -143,6 +144,58 @@ pub fn kill_storage(child_info: &ChildInfo, limit: Option) -> KillStorageRe } } +/// Partially clear the child storage of each key-value pair. +/// +/// # Limit +/// +/// A *limit* should always be provided through `maybe_limit`. This is one fewer than the +/// maximum number of backend iterations which may be done by this operation and as such +/// represents the maximum number of backend deletions which may happen. A *limit* of zero +/// implies that no keys will be deleted, though there may be a single iteration done. +/// +/// The limit can be used to partially delete storage items in case it is too large or costly +/// to delete all in a single operation. +/// +/// # Cursor +/// +/// A *cursor* may be passed in to this operation with `maybe_cursor`. `None` should only be +/// passed once (in the initial call) for any attempt to clear storage. In general, subsequent calls +/// operating on the same prefix should pass `Some` and this value should be equal to the +/// previous call result's `maybe_cursor` field. The only exception to this is when you can +/// guarantee that the subsequent call is in a new block; in this case the previous call's result +/// cursor need not be passed in an a `None` may be passed instead. This exception may be useful +/// then making this call solely from a block-hook such as `on_initialize`. +/// +/// Returns [`MultiRemovalResults`](sp_io::MultiRemovalResults) to inform about the result. Once the +/// resultant `maybe_cursor` field is `None`, then no further items remain to be deleted. +/// +/// NOTE: After the initial call for any given child storage, it is important that no keys further +/// keys are inserted. If so, then they may or may not be deleted by subsequent calls. +/// +/// # Note +/// +/// Please note that keys which are residing in the overlay for the child are deleted without +/// counting towards the `limit`. +pub fn clear_storage( + child_info: &ChildInfo, + maybe_limit: Option, + _maybe_cursor: Option<&[u8]>, +) -> MultiRemovalResults { + // TODO: Once the network has upgraded to include the new host functions, this code can be + // enabled. + // sp_io::default_child_storage::storage_kill(prefix, maybe_limit, maybe_cursor) + let r = match child_info.child_type() { + ChildType::ParentKeyId => + sp_io::default_child_storage::storage_kill(child_info.storage_key(), maybe_limit), + }; + use sp_io::KillStorageResult::*; + let (maybe_cursor, backend) = match r { + AllRemoved(db) => (None, db), + SomeRemaining(db) => (Some(child_info.storage_key().to_vec()), db), + }; + MultiRemovalResults { maybe_cursor, backend, unique: backend, loops: backend } +} + /// Ensure `key` has no explicit entry in storage. pub fn kill(child_info: &ChildInfo, key: &[u8]) { match child_info.child_type() { diff --git a/frame/support/src/storage/generator/double_map.rs b/frame/support/src/storage/generator/double_map.rs index 1c308bc351902..90ec92c622b1f 100644 --- a/frame/support/src/storage/generator/double_map.rs +++ b/frame/support/src/storage/generator/double_map.rs @@ -202,11 +202,28 @@ where unhashed::kill(&Self::storage_double_map_final_key(k1, k2)) } - fn remove_prefix(k1: KArg1, limit: Option) -> sp_io::KillStorageResult + fn remove_prefix(k1: KArg1, maybe_limit: Option) -> sp_io::KillStorageResult where KArg1: EncodeLike, { - unhashed::kill_prefix(Self::storage_double_map_final_key1(k1).as_ref(), limit) + unhashed::clear_prefix(Self::storage_double_map_final_key1(k1).as_ref(), maybe_limit, None) + .into() + } + + fn clear_prefix( + k1: KArg1, + limit: u32, + maybe_cursor: Option<&[u8]>, + ) -> sp_io::MultiRemovalResults + where + KArg1: EncodeLike, + { + unhashed::clear_prefix( + Self::storage_double_map_final_key1(k1).as_ref(), + Some(limit), + maybe_cursor, + ) + .into() } fn iter_prefix_values(k1: KArg1) -> storage::PrefixIterator diff --git a/frame/support/src/storage/generator/nmap.rs b/frame/support/src/storage/generator/nmap.rs index 18c912cf294ef..0175d681df1d4 100755 --- a/frame/support/src/storage/generator/nmap.rs +++ b/frame/support/src/storage/generator/nmap.rs @@ -183,7 +183,22 @@ where where K: HasKeyPrefix, { - unhashed::kill_prefix(&Self::storage_n_map_partial_key(partial_key), limit) + unhashed::clear_prefix(&Self::storage_n_map_partial_key(partial_key), limit, None).into() + } + + fn clear_prefix( + partial_key: KP, + limit: u32, + maybe_cursor: Option<&[u8]>, + ) -> sp_io::MultiRemovalResults + where + K: HasKeyPrefix, + { + unhashed::clear_prefix( + &Self::storage_n_map_partial_key(partial_key), + Some(limit), + maybe_cursor, + ) } fn iter_prefix_values(partial_key: KP) -> PrefixIterator diff --git a/frame/support/src/storage/migration.rs b/frame/support/src/storage/migration.rs index 713c2b0f3fe01..67001fc4e1f42 100644 --- a/frame/support/src/storage/migration.rs +++ b/frame/support/src/storage/migration.rs @@ -256,12 +256,43 @@ pub fn put_storage_value(module: &[u8], item: &[u8], hash: &[u8], val /// Remove all items under a storage prefix by the `module`, the map's `item` name and the key /// `hash`. +#[deprecated = "Use `clear_storage_prefix` instead"] pub fn remove_storage_prefix(module: &[u8], item: &[u8], hash: &[u8]) { let mut key = vec![0u8; 32 + hash.len()]; let storage_prefix = storage_prefix(module, item); key[0..32].copy_from_slice(&storage_prefix); key[32..].copy_from_slice(hash); - frame_support::storage::unhashed::kill_prefix(&key, None); + let _ = frame_support::storage::unhashed::clear_prefix(&key, None, None); +} + +/// Attempt to remove all values under a storage prefix by the `module`, the map's `item` name and +/// the key `hash`. +/// +/// All values in the client overlay will be deleted, if `maybe_limit` is `Some` then up to +/// that number of values are deleted from the client backend by seeking and reading that number of +/// storage values plus one. If `maybe_limit` is `None` then all values in the client backend are +/// deleted. This is potentially unsafe since it's an unbounded operation. +/// +/// ## Cursors +/// +/// The `maybe_cursor` parameter should be `None` for the first call to initial removal. +/// If the resultant `maybe_cursor` is `Some`, then another call is required to complete the +/// removal operation. This value must be passed in as the subsequent call's `maybe_cursor` +/// parameter. If the resultant `maybe_cursor` is `None`, then the operation is complete and no +/// items remain in storage provided that no items were added between the first calls and the +/// final call. +pub fn clear_storage_prefix( + module: &[u8], + item: &[u8], + hash: &[u8], + maybe_limit: Option, + maybe_cursor: Option<&[u8]>, +) -> sp_io::MultiRemovalResults { + let mut key = vec![0u8; 32 + hash.len()]; + let storage_prefix = storage_prefix(module, item); + key[0..32].copy_from_slice(&storage_prefix); + key[32..].copy_from_slice(hash); + frame_support::storage::unhashed::clear_prefix(&key, maybe_limit, maybe_cursor) } /// Take a particular item in storage by the `module`, the map's `item` name and the key `hash`. diff --git a/frame/support/src/storage/mod.rs b/frame/support/src/storage/mod.rs index 7a960776fc755..8b6f198b03f96 100644 --- a/frame/support/src/storage/mod.rs +++ b/frame/support/src/storage/mod.rs @@ -516,10 +516,34 @@ pub trait StorageDoubleMap { /// Calling this multiple times per block with a `limit` set leads always to the same keys being /// removed and the same result being returned. This happens because the keys to delete in the /// overlay are not taken into account when deleting keys in the backend. + #[deprecated = "Use `clear_prefix` instead"] fn remove_prefix(k1: KArg1, limit: Option) -> sp_io::KillStorageResult where KArg1: ?Sized + EncodeLike; + /// Remove all values under the first key `k1` in the overlay and up to `maybe_limit` in the + /// backend. + /// + /// All values in the client overlay will be deleted, if `maybe_limit` is `Some` then up to + /// that number of values are deleted from the client backend, otherwise all values in the + /// client backend are deleted. + /// + /// ## Cursors + /// + /// The `maybe_cursor` parameter should be `None` for the first call to initial removal. + /// If the resultant `maybe_cursor` is `Some`, then another call is required to complete the + /// removal operation. This value must be passed in as the subsequent call's `maybe_cursor` + /// parameter. If the resultant `maybe_cursor` is `None`, then the operation is complete and no + /// items remain in storage provided that no items were added between the first calls and the + /// final call. + fn clear_prefix( + k1: KArg1, + limit: u32, + maybe_cursor: Option<&[u8]>, + ) -> sp_io::MultiRemovalResults + where + KArg1: ?Sized + EncodeLike; + /// Iterate over values that share the first key. fn iter_prefix_values(k1: KArg1) -> PrefixIterator where @@ -657,10 +681,42 @@ pub trait StorageNMap { /// Calling this multiple times per block with a `limit` set leads always to the same keys being /// removed and the same result being returned. This happens because the keys to delete in the /// overlay are not taken into account when deleting keys in the backend. + #[deprecated = "Use `clear_prefix` instead"] fn remove_prefix(partial_key: KP, limit: Option) -> sp_io::KillStorageResult where K: HasKeyPrefix; + /// Attempt to remove items from the map matching a `partial_key` prefix. + /// + /// Returns [`MultiRemovalResults`](sp_io::MultiRemovalResults) to inform about the result. Once + /// the resultant `maybe_cursor` field is `None`, then no further items remain to be deleted. + /// + /// NOTE: After the initial call for any given map, it is important that no further items + /// are inserted into the map which match the `partial key`. If so, then the map may not be + /// empty when the resultant `maybe_cursor` is `None`. + /// + /// # Limit + /// + /// A `limit` must be provided in order to cap the maximum + /// amount of deletions done in a single call. This is one fewer than the + /// maximum number of backend iterations which may be done by this operation and as such + /// represents the maximum number of backend deletions which may happen. A `limit` of zero + /// implies that no keys will be deleted, though there may be a single iteration done. + /// + /// # Cursor + /// + /// A *cursor* may be passed in to this operation with `maybe_cursor`. `None` should only be + /// passed once (in the initial call) for any given storage map and `partial_key`. Subsequent + /// calls operating on the same map/`partial_key` should always pass `Some`, and this should be + /// equal to the previous call result's `maybe_cursor` field. + fn clear_prefix( + partial_key: KP, + limit: u32, + maybe_cursor: Option<&[u8]>, + ) -> sp_io::MultiRemovalResults + where + K: HasKeyPrefix; + /// Iterate over values that share the partial prefix key. fn iter_prefix_values(partial_key: KP) -> PrefixIterator where @@ -1111,8 +1167,36 @@ pub trait StoragePrefixedMap { /// Calling this multiple times per block with a `limit` set leads always to the same keys being /// removed and the same result being returned. This happens because the keys to delete in the /// overlay are not taken into account when deleting keys in the backend. + #[deprecated = "Use `clear` instead"] fn remove_all(limit: Option) -> sp_io::KillStorageResult { - sp_io::storage::clear_prefix(&Self::final_prefix(), limit) + unhashed::clear_prefix(&Self::final_prefix(), limit, None).into() + } + + /// Attempt to remove all items from the map. + /// + /// Returns [`MultiRemovalResults`](sp_io::MultiRemovalResults) to inform about the result. Once + /// the resultant `maybe_cursor` field is `None`, then no further items remain to be deleted. + /// + /// NOTE: After the initial call for any given map, it is important that no further items + /// are inserted into the map. If so, then the map may not be empty when the resultant + /// `maybe_cursor` is `None`. + /// + /// # Limit + /// + /// A `limit` must always be provided through in order to cap the maximum + /// amount of deletions done in a single call. This is one fewer than the + /// maximum number of backend iterations which may be done by this operation and as such + /// represents the maximum number of backend deletions which may happen. A `limit` of zero + /// implies that no keys will be deleted, though there may be a single iteration done. + /// + /// # Cursor + /// + /// A *cursor* may be passed in to this operation with `maybe_cursor`. `None` should only be + /// passed once (in the initial call) for any given storage map. Subsequent calls + /// operating on the same map should always pass `Some`, and this should be equal to the + /// previous call result's `maybe_cursor` field. + fn clear(limit: u32, maybe_cursor: Option<&[u8]>) -> sp_io::MultiRemovalResults { + unhashed::clear_prefix(&Self::final_prefix(), Some(limit), maybe_cursor) } /// Iter over all value of the storage. @@ -1427,7 +1511,7 @@ mod test { assert_eq!(MyStorage::iter_values().collect::>(), vec![1, 2, 3, 4]); // test removal - MyStorage::remove_all(None); + let _ = MyStorage::clear(u32::max_value(), None); assert!(MyStorage::iter_values().collect::>().is_empty()); // test migration @@ -1437,7 +1521,7 @@ mod test { assert!(MyStorage::iter_values().collect::>().is_empty()); MyStorage::translate_values(|v: u32| Some(v as u64)); assert_eq!(MyStorage::iter_values().collect::>(), vec![1, 2]); - MyStorage::remove_all(None); + let _ = MyStorage::clear(u32::max_value(), None); // test migration 2 unhashed::put(&[&k[..], &vec![1][..]].concat(), &1u128); @@ -1449,7 +1533,7 @@ mod test { assert_eq!(MyStorage::iter_values().collect::>(), vec![1, 2, 3]); MyStorage::translate_values(|v: u128| Some(v as u64)); assert_eq!(MyStorage::iter_values().collect::>(), vec![1, 2, 3]); - MyStorage::remove_all(None); + let _ = MyStorage::clear(u32::max_value(), None); // test that other values are not modified. assert_eq!(unhashed::get(&key_before[..]), Some(32u64)); diff --git a/frame/support/src/storage/types/counted_map.rs b/frame/support/src/storage/types/counted_map.rs index a2160ede52fe9..44fcbf607935f 100644 --- a/frame/support/src/storage/types/counted_map.rs +++ b/frame/support/src/storage/types/counted_map.rs @@ -31,6 +31,7 @@ use crate::{ Never, }; use codec::{Decode, Encode, EncodeLike, FullCodec, MaxEncodedLen, Ref}; +use sp_io::MultiRemovalResults; use sp_runtime::traits::Saturating; use sp_std::prelude::*; @@ -273,13 +274,44 @@ where ::Map::migrate_key::(key) } - /// Remove all value of the storage. + /// Remove all values in the map. + #[deprecated = "Use `clear` instead"] pub fn remove_all() { - // NOTE: it is not possible to remove up to some limit because - // `sp_io::storage::clear_prefix` and `StorageMap::remove_all` don't give the number of - // value removed from the overlay. - CounterFor::::set(0u32); + #[allow(deprecated)] ::Map::remove_all(None); + CounterFor::::kill(); + } + + /// Attempt to remove all items from the map. + /// + /// Returns [`MultiRemovalResults`](sp_io::MultiRemovalResults) to inform about the result. Once + /// the resultant `maybe_cursor` field is `None`, then no further items remain to be deleted. + /// + /// NOTE: After the initial call for any given map, it is important that no further items + /// are inserted into the map. If so, then the map may not be empty when the resultant + /// `maybe_cursor` is `None`. + /// + /// # Limit + /// + /// A `limit` must always be provided through in order to cap the maximum + /// amount of deletions done in a single call. This is one fewer than the + /// maximum number of backend iterations which may be done by this operation and as such + /// represents the maximum number of backend deletions which may happen. A `limit` of zero + /// implies that no keys will be deleted, though there may be a single iteration done. + /// + /// # Cursor + /// + /// A *cursor* may be passed in to this operation with `maybe_cursor`. `None` should only be + /// passed once (in the initial call) for any given storage map. Subsequent calls + /// operating on the same map should always pass `Some`, and this should be equal to the + /// previous call result's `maybe_cursor` field. + pub fn clear(limit: u32, maybe_cursor: Option<&[u8]>) -> MultiRemovalResults { + let result = ::Map::clear(limit, maybe_cursor); + match result.maybe_cursor { + None => CounterFor::::kill(), + Some(_) => CounterFor::::mutate(|x| x.saturating_reduce(result.unique)), + } + result } /// Iter over all value of the storage. @@ -691,7 +723,7 @@ mod test { assert_eq!(A::count(), 2); // Remove all. - A::remove_all(); + let _ = A::clear(u32::max_value(), None); assert_eq!(A::count(), 0); assert_eq!(A::initialize_counter(), 0); @@ -922,7 +954,7 @@ mod test { assert_eq!(B::count(), 2); // Remove all. - B::remove_all(); + let _ = B::clear(u32::max_value(), None); assert_eq!(B::count(), 0); assert_eq!(B::initialize_counter(), 0); diff --git a/frame/support/src/storage/types/double_map.rs b/frame/support/src/storage/types/double_map.rs index 42b903128934d..5662087cc896f 100644 --- a/frame/support/src/storage/types/double_map.rs +++ b/frame/support/src/storage/types/double_map.rs @@ -229,13 +229,53 @@ where /// Calling this multiple times per block with a `limit` set leads always to the same keys being /// removed and the same result being returned. This happens because the keys to delete in the /// overlay are not taken into account when deleting keys in the backend. + #[deprecated = "Use `clear_prefix` instead"] pub fn remove_prefix(k1: KArg1, limit: Option) -> sp_io::KillStorageResult where KArg1: ?Sized + EncodeLike, { + #[allow(deprecated)] >::remove_prefix(k1, limit) } + /// Attempt to remove items from the map matching a `first_key` prefix. + /// + /// Returns [`MultiRemovalResults`](sp_io::MultiRemovalResults) to inform about the result. Once + /// the resultant `maybe_cursor` field is `None`, then no further items remain to be deleted. + /// + /// NOTE: After the initial call for any given map, it is important that no further items + /// are inserted into the map which match the `first_key`. If so, then the map may not be + /// empty when the resultant `maybe_cursor` is `None`. + /// + /// # Limit + /// + /// A `limit` must always be provided through in order to cap the maximum + /// amount of deletions done in a single call. This is one fewer than the + /// maximum number of backend iterations which may be done by this operation and as such + /// represents the maximum number of backend deletions which may happen. A `limit` of zero + /// implies that no keys will be deleted, though there may be a single iteration done. + /// + /// # Cursor + /// + /// A *cursor* may be passed in to this operation with `maybe_cursor`. `None` should only be + /// passed once (in the initial call) for any given storage map and `first_key`. Subsequent + /// calls operating on the same map/`first_key` should always pass `Some`, and this should be + /// equal to the previous call result's `maybe_cursor` field. + pub fn clear_prefix( + first_key: KArg1, + limit: u32, + maybe_cursor: Option<&[u8]>, + ) -> sp_io::MultiRemovalResults + where + KArg1: ?Sized + EncodeLike, + { + >::clear_prefix( + first_key, + limit, + maybe_cursor, + ) + } + /// Iterate over values that share the first key. pub fn iter_prefix_values(k1: KArg1) -> crate::storage::PrefixIterator where @@ -359,10 +399,39 @@ where /// Calling this multiple times per block with a `limit` set leads always to the same keys being /// removed and the same result being returned. This happens because the keys to delete in the /// overlay are not taken into account when deleting keys in the backend. + #[deprecated = "Use `clear` instead"] pub fn remove_all(limit: Option) -> sp_io::KillStorageResult { + #[allow(deprecated)] >::remove_all(limit) } + /// Attempt to remove all items from the map. + /// + /// Returns [`MultiRemovalResults`](sp_io::MultiRemovalResults) to inform about the result. Once + /// the resultant `maybe_cursor` field is `None`, then no further items remain to be deleted. + /// + /// NOTE: After the initial call for any given map, it is important that no further items + /// are inserted into the map. If so, then the map may not be empty when the resultant + /// `maybe_cursor` is `None`. + /// + /// # Limit + /// + /// A `limit` must always be provided through in order to cap the maximum + /// amount of deletions done in a single call. This is one fewer than the + /// maximum number of backend iterations which may be done by this operation and as such + /// represents the maximum number of backend deletions which may happen.A `limit` of zero + /// implies that no keys will be deleted, though there may be a single iteration done. + /// + /// # Cursor + /// + /// A *cursor* may be passed in to this operation with `maybe_cursor`. `None` should only be + /// passed once (in the initial call) for any given storage map. Subsequent calls + /// operating on the same map should always pass `Some`, and this should be equal to the + /// previous call result's `maybe_cursor` field. + pub fn clear(limit: u32, maybe_cursor: Option<&[u8]>) -> sp_io::MultiRemovalResults { + >::clear(limit, maybe_cursor) + } + /// Iter over all value of the storage. /// /// NOTE: If a value failed to decode because storage is corrupted then it is skipped. @@ -768,7 +837,7 @@ mod test { A::insert(3, 30, 10); A::insert(4, 40, 10); - A::remove_all(None); + let _ = A::clear(u32::max_value(), None); assert_eq!(A::contains_key(3, 30), false); assert_eq!(A::contains_key(4, 40), false); @@ -829,7 +898,7 @@ mod test { ] ); - WithLen::remove_all(None); + let _ = WithLen::clear(u32::max_value(), None); assert_eq!(WithLen::decode_len(3, 30), None); WithLen::append(0, 100, 10); assert_eq!(WithLen::decode_len(0, 100), Some(1)); @@ -843,7 +912,7 @@ mod test { assert_eq!(A::iter_prefix_values(4).collect::>(), vec![13, 14]); assert_eq!(A::iter_prefix(4).collect::>(), vec![(40, 13), (41, 14)]); - A::remove_prefix(3, None); + let _ = A::clear_prefix(3, u32::max_value(), None); assert_eq!(A::iter_prefix(3).collect::>(), vec![]); assert_eq!(A::iter_prefix(4).collect::>(), vec![(40, 13), (41, 14)]); diff --git a/frame/support/src/storage/types/map.rs b/frame/support/src/storage/types/map.rs index bc32c58da8db6..6dac5b3756598 100644 --- a/frame/support/src/storage/types/map.rs +++ b/frame/support/src/storage/types/map.rs @@ -247,10 +247,39 @@ where /// Calling this multiple times per block with a `limit` set leads always to the same keys being /// removed and the same result being returned. This happens because the keys to delete in the /// overlay are not taken into account when deleting keys in the backend. + #[deprecated = "Use `clear` instead"] pub fn remove_all(limit: Option) -> sp_io::KillStorageResult { + #[allow(deprecated)] >::remove_all(limit) } + /// Attempt to remove all items from the map. + /// + /// Returns [`MultiRemovalResults`](sp_io::MultiRemovalResults) to inform about the result. Once + /// the resultant `maybe_cursor` field is `None`, then no further items remain to be deleted. + /// + /// NOTE: After the initial call for any given map, it is important that no further items + /// are inserted into the map. If so, then the map may not be empty when the resultant + /// `maybe_cursor` is `None`. + /// + /// # Limit + /// + /// A `limit` must always be provided through in order to cap the maximum + /// amount of deletions done in a single call. This is one fewer than the + /// maximum number of backend iterations which may be done by this operation and as such + /// represents the maximum number of backend deletions which may happen. A `limit` of zero + /// implies that no keys will be deleted, though there may be a single iteration done. + /// + /// # Cursor + /// + /// A *cursor* may be passed in to this operation with `maybe_cursor`. `None` should only be + /// passed once (in the initial call) for any given storage map. Subsequent calls + /// operating on the same map should always pass `Some`, and this should be equal to the + /// previous call result's `maybe_cursor` field. + pub fn clear(limit: u32, maybe_cursor: Option<&[u8]>) -> sp_io::MultiRemovalResults { + >::clear(limit, maybe_cursor) + } + /// Iter over all value of the storage. /// /// NOTE: If a value failed to decode because storage is corrupted then it is skipped. @@ -563,7 +592,7 @@ mod test { A::insert(3, 10); A::insert(4, 10); - A::remove_all(None); + let _ = A::clear(u32::max_value(), None); assert_eq!(A::contains_key(3), false); assert_eq!(A::contains_key(4), false); @@ -618,7 +647,7 @@ mod test { ] ); - WithLen::remove_all(None); + let _ = WithLen::clear(u32::max_value(), None); assert_eq!(WithLen::decode_len(3), None); WithLen::append(0, 10); assert_eq!(WithLen::decode_len(0), Some(1)); diff --git a/frame/support/src/storage/types/nmap.rs b/frame/support/src/storage/types/nmap.rs index a15c7482105e9..1f303525b7476 100755 --- a/frame/support/src/storage/types/nmap.rs +++ b/frame/support/src/storage/types/nmap.rs @@ -185,13 +185,53 @@ where /// Calling this multiple times per block with a `limit` set leads always to the same keys being /// removed and the same result being returned. This happens because the keys to delete in the /// overlay are not taken into account when deleting keys in the backend. + #[deprecated = "Use `clear_prefix` instead"] pub fn remove_prefix(partial_key: KP, limit: Option) -> sp_io::KillStorageResult where Key: HasKeyPrefix, { + #[allow(deprecated)] >::remove_prefix(partial_key, limit) } + /// Attempt to remove items from the map matching a `partial_key` prefix. + /// + /// Returns [`MultiRemovalResults`](sp_io::MultiRemovalResults) to inform about the result. Once + /// the resultant `maybe_cursor` field is `None`, then no further items remain to be deleted. + /// + /// NOTE: After the initial call for any given map, it is important that no further items + /// are inserted into the map which match the `partial key`. If so, then the map may not be + /// empty when the resultant `maybe_cursor` is `None`. + /// + /// # Limit + /// + /// A `limit` must be provided in order to cap the maximum + /// amount of deletions done in a single call. This is one fewer than the + /// maximum number of backend iterations which may be done by this operation and as such + /// represents the maximum number of backend deletions which may happen. A `limit` of zero + /// implies that no keys will be deleted, though there may be a single iteration done. + /// + /// # Cursor + /// + /// A *cursor* may be passed in to this operation with `maybe_cursor`. `None` should only be + /// passed once (in the initial call) for any given storage map and `partial_key`. Subsequent + /// calls operating on the same map/`partial_key` should always pass `Some`, and this should be + /// equal to the previous call result's `maybe_cursor` field. + pub fn clear_prefix( + partial_key: KP, + limit: u32, + maybe_cursor: Option<&[u8]>, + ) -> sp_io::MultiRemovalResults + where + Key: HasKeyPrefix, + { + >::clear_prefix( + partial_key, + limit, + maybe_cursor, + ) + } + /// Iterate over values that share the first key. pub fn iter_prefix_values(partial_key: KP) -> PrefixIterator where @@ -299,8 +339,37 @@ where /// Calling this multiple times per block with a `limit` set leads always to the same keys being /// removed and the same result being returned. This happens because the keys to delete in the /// overlay are not taken into account when deleting keys in the backend. + #[deprecated = "Use `clear` instead"] pub fn remove_all(limit: Option) -> sp_io::KillStorageResult { - >::remove_all(limit) + #[allow(deprecated)] + >::remove_all(limit).into() + } + + /// Attempt to remove all items from the map. + /// + /// Returns [`MultiRemovalResults`](sp_io::MultiRemovalResults) to inform about the result. Once + /// the resultant `maybe_cursor` field is `None`, then no further items remain to be deleted. + /// + /// NOTE: After the initial call for any given map, it is important that no further items + /// are inserted into the map. If so, then the map may not be empty when the resultant + /// `maybe_cursor` is `None`. + /// + /// # Limit + /// + /// A `limit` must always be provided through in order to cap the maximum + /// amount of deletions done in a single call. This is one fewer than the + /// maximum number of backend iterations which may be done by this operation and as such + /// represents the maximum number of backend deletions which may happen. A `limit` of zero + /// implies that no keys will be deleted, though there may be a single iteration done. + /// + /// # Cursor + /// + /// A *cursor* may be passed in to this operation with `maybe_cursor`. `None` should only be + /// passed once (in the initial call) for any given storage map. Subsequent calls + /// operating on the same map should always pass `Some`, and this should be equal to the + /// previous call result's `maybe_cursor` field. + pub fn clear(limit: u32, maybe_cursor: Option<&[u8]>) -> sp_io::MultiRemovalResults { + >::clear(limit, maybe_cursor) } /// Iter over all value of the storage. @@ -544,7 +613,7 @@ mod test { use crate::{ hash::{StorageHasher as _, *}, metadata::{StorageEntryModifier, StorageHasher}, - storage::types::{Key, ValueQuery}, + storage::types::{Key as NMapKey, ValueQuery}, }; use sp_io::{hashing::twox_128, TestExternalities}; @@ -565,12 +634,12 @@ mod test { #[test] fn test_1_key() { - type A = StorageNMap, u32, OptionQuery>; + type A = StorageNMap, u32, OptionQuery>; type AValueQueryWithAnOnEmpty = - StorageNMap, u32, ValueQuery, ADefault>; - type B = StorageNMap, u32, ValueQuery>; - type C = StorageNMap, u8, ValueQuery>; - type WithLen = StorageNMap, Vec>; + StorageNMap, u32, ValueQuery, ADefault>; + type B = StorageNMap, u32, ValueQuery>; + type C = StorageNMap, u8, ValueQuery>; + type WithLen = StorageNMap, Vec>; TestExternalities::default().execute_with(|| { let mut k: Vec = vec![]; @@ -590,13 +659,13 @@ mod test { { #[crate::storage_alias] - type Foo = StorageNMap), u32>; + type Foo = StorageNMap), u32>; assert_eq!(Foo::contains_key((3,)), true); assert_eq!(Foo::get((3,)), Some(10)); } - A::swap::, _, _>((3,), (2,)); + A::swap::, _, _>((3,), (2,)); assert_eq!(A::contains_key((3,)), false); assert_eq!(A::contains_key((2,)), true); assert_eq!(A::get((3,)), None); @@ -684,7 +753,7 @@ mod test { A::insert((3,), 10); A::insert((4,), 10); - A::remove_all(None); + let _ = A::clear(u32::max_value(), None); assert_eq!(A::contains_key((3,)), false); assert_eq!(A::contains_key((4,)), false); @@ -739,7 +808,7 @@ mod test { ] ); - WithLen::remove_all(None); + let _ = WithLen::clear(u32::max_value(), None); assert_eq!(WithLen::decode_len((3,)), None); WithLen::append((0,), 10); assert_eq!(WithLen::decode_len((0,)), Some(1)); @@ -750,26 +819,30 @@ mod test { fn test_2_keys() { type A = StorageNMap< Prefix, - (Key, Key), + (NMapKey, NMapKey), u32, OptionQuery, >; type AValueQueryWithAnOnEmpty = StorageNMap< Prefix, - (Key, Key), + (NMapKey, NMapKey), u32, ValueQuery, ADefault, >; - type B = StorageNMap, Key), u32, ValueQuery>; + type B = + StorageNMap, NMapKey), u32, ValueQuery>; type C = StorageNMap< Prefix, - (Key, Key), + (NMapKey, NMapKey), u8, ValueQuery, >; - type WithLen = - StorageNMap, Key), Vec>; + type WithLen = StorageNMap< + Prefix, + (NMapKey, NMapKey), + Vec, + >; TestExternalities::default().execute_with(|| { let mut k: Vec = vec![]; @@ -788,7 +861,10 @@ mod test { assert_eq!(A::get((3, 30)), Some(10)); assert_eq!(AValueQueryWithAnOnEmpty::get((3, 30)), 10); - A::swap::<(Key, Key), _, _>((3, 30), (2, 20)); + A::swap::<(NMapKey, NMapKey), _, _>( + (3, 30), + (2, 20), + ); assert_eq!(A::contains_key((3, 30)), false); assert_eq!(A::contains_key((2, 20)), true); assert_eq!(A::get((3, 30)), None); @@ -877,7 +953,7 @@ mod test { A::insert((3, 30), 10); A::insert((4, 40), 10); - A::remove_all(None); + let _ = A::clear(u32::max_value(), None); assert_eq!(A::contains_key((3, 30)), false); assert_eq!(A::contains_key((4, 40)), false); @@ -938,7 +1014,7 @@ mod test { ] ); - WithLen::remove_all(None); + let _ = WithLen::clear(u32::max_value(), None); assert_eq!(WithLen::decode_len((3, 30)), None); WithLen::append((0, 100), 10); assert_eq!(WithLen::decode_len((0, 100)), Some(1)); @@ -956,32 +1032,48 @@ mod test { fn test_3_keys() { type A = StorageNMap< Prefix, - (Key, Key, Key), + ( + NMapKey, + NMapKey, + NMapKey, + ), u32, OptionQuery, >; type AValueQueryWithAnOnEmpty = StorageNMap< Prefix, - (Key, Key, Key), + ( + NMapKey, + NMapKey, + NMapKey, + ), u32, ValueQuery, ADefault, >; type B = StorageNMap< Prefix, - (Key, Key, Key), + (NMapKey, NMapKey, NMapKey), u32, ValueQuery, >; type C = StorageNMap< Prefix, - (Key, Key, Key), + ( + NMapKey, + NMapKey, + NMapKey, + ), u8, ValueQuery, >; type WithLen = StorageNMap< Prefix, - (Key, Key, Key), + ( + NMapKey, + NMapKey, + NMapKey, + ), Vec, >; @@ -1004,7 +1096,11 @@ mod test { assert_eq!(AValueQueryWithAnOnEmpty::get((1, 10, 100)), 30); A::swap::< - (Key, Key, Key), + ( + NMapKey, + NMapKey, + NMapKey, + ), _, _, >((1, 10, 100), (2, 20, 200)); @@ -1093,7 +1189,7 @@ mod test { A::insert((3, 30, 300), 10); A::insert((4, 40, 400), 10); - A::remove_all(None); + let _ = A::clear(u32::max_value(), None); assert_eq!(A::contains_key((3, 30, 300)), false); assert_eq!(A::contains_key((4, 40, 400)), false); @@ -1161,7 +1257,7 @@ mod test { ] ); - WithLen::remove_all(None); + let _ = WithLen::clear(u32::max_value(), None); assert_eq!(WithLen::decode_len((3, 30, 300)), None); WithLen::append((0, 100, 1000), 10); assert_eq!(WithLen::decode_len((0, 100, 1000)), Some(1)); diff --git a/frame/support/src/storage/unhashed.rs b/frame/support/src/storage/unhashed.rs index adabc9c14a3d5..fc6d8ae79c57d 100644 --- a/frame/support/src/storage/unhashed.rs +++ b/frame/support/src/storage/unhashed.rs @@ -96,10 +96,63 @@ pub fn kill(key: &[u8]) { } /// Ensure keys with the given `prefix` have no entries in storage. +#[deprecated = "Use `clear_prefix` instead"] pub fn kill_prefix(prefix: &[u8], limit: Option) -> sp_io::KillStorageResult { + // TODO: Once the network has upgraded to include the new host functions, this code can be + // enabled. + // clear_prefix(prefix, limit).into() sp_io::storage::clear_prefix(prefix, limit) } +/// Partially clear the storage of all keys under a common `prefix`. +/// +/// # Limit +/// +/// A *limit* should always be provided through `maybe_limit`. This is one fewer than the +/// maximum number of backend iterations which may be done by this operation and as such +/// represents the maximum number of backend deletions which may happen. A *limit* of zero +/// implies that no keys will be deleted, though there may be a single iteration done. +/// +/// The limit can be used to partially delete storage items in case it is too large or costly +/// to delete all in a single operation. +/// +/// # Cursor +/// +/// A *cursor* may be passed in to this operation with `maybe_cursor`. `None` should only be +/// passed once (in the initial call) for any attempt to clear storage. In general, subsequent calls +/// operating on the same prefix should pass `Some` and this value should be equal to the +/// previous call result's `maybe_cursor` field. The only exception to this is when you can +/// guarantee that the subsequent call is in a new block; in this case the previous call's result +/// cursor need not be passed in an a `None` may be passed instead. This exception may be useful +/// then making this call solely from a block-hook such as `on_initialize`. +/// +/// Returns [`MultiRemovalResults`](sp_io::MultiRemovalResults) to inform about the result. Once the +/// resultant `maybe_cursor` field is `None`, then no further items remain to be deleted. +/// +/// NOTE: After the initial call for any given child storage, it is important that no keys further +/// keys are inserted. If so, then they may or may not be deleted by subsequent calls. +/// +/// # Note +/// +/// Please note that keys which are residing in the overlay for the child are deleted without +/// counting towards the `limit`. +pub fn clear_prefix( + prefix: &[u8], + maybe_limit: Option, + _maybe_cursor: Option<&[u8]>, +) -> sp_io::MultiRemovalResults { + // TODO: Once the network has upgraded to include the new host functions, this code can be + // enabled. + // sp_io::storage::clear_prefix(prefix, maybe_limit, maybe_cursor) + use sp_io::{KillStorageResult::*, MultiRemovalResults}; + #[allow(deprecated)] + let (maybe_cursor, i) = match kill_prefix(prefix, maybe_limit) { + AllRemoved(i) => (None, i), + SomeRemaining(i) => (Some(prefix.to_vec()), i), + }; + MultiRemovalResults { maybe_cursor, backend: i, unique: i, loops: i } +} + /// Get a Vec of bytes from storage. pub fn get_raw(key: &[u8]) -> Option> { sp_io::storage::get(key) diff --git a/frame/system/src/lib.rs b/frame/system/src/lib.rs index ba494dfbd9f8c..f2999f945c5c2 100644 --- a/frame/system/src/lib.rs +++ b/frame/system/src/lib.rs @@ -475,7 +475,7 @@ pub mod pallet { _subkeys: u32, ) -> DispatchResultWithPostInfo { ensure_root(origin)?; - storage::unhashed::kill_prefix(&prefix, None); + let _ = storage::unhashed::clear_prefix(&prefix, None, None); Ok(().into()) } @@ -1427,7 +1427,7 @@ impl Pallet { pub fn reset_events() { >::kill(); EventCount::::kill(); - >::remove_all(None); + let _ = >::clear(u32::max_value(), None); } /// Assert the given `event` exists. diff --git a/frame/uniques/src/functions.rs b/frame/uniques/src/functions.rs index cdaa31d4e1c8a..c5220b562b172 100644 --- a/frame/uniques/src/functions.rs +++ b/frame/uniques/src/functions.rs @@ -110,8 +110,10 @@ impl, I: 'static> Pallet { for (item, details) in Item::::drain_prefix(&collection) { Account::::remove((&details.owner, &collection, &item)); } + #[allow(deprecated)] ItemMetadataOf::::remove_prefix(&collection, None); CollectionMetadataOf::::remove(&collection); + #[allow(deprecated)] Attribute::::remove_prefix((&collection,), None); CollectionAccount::::remove(&collection_details.owner, &collection); T::Currency::unreserve(&collection_details.owner, collection_details.total_deposit); diff --git a/primitives/externalities/src/lib.rs b/primitives/externalities/src/lib.rs index 49b190b4ae260..b343b8c393e00 100644 --- a/primitives/externalities/src/lib.rs +++ b/primitives/externalities/src/lib.rs @@ -52,6 +52,30 @@ pub enum Error { StorageUpdateFailed(&'static str), } +/// Results concerning an operation to remove many keys. +#[derive(codec::Encode, codec::Decode)] +#[must_use] +pub struct MultiRemovalResults { + /// A continuation cursor which, if `Some` must be provided to the subsequent removal call. + /// If `None` then all removals are complete and no further calls are needed. + pub maybe_cursor: Option>, + /// The number of items removed from the backend database. + pub backend: u32, + /// The number of unique keys removed, taking into account both the backend and the overlay. + pub unique: u32, + /// The number of iterations (each requiring a storage seek/read) which were done. + pub loops: u32, +} + +impl MultiRemovalResults { + /// Deconstruct into the internal components. + /// + /// Returns `(maybe_cursor, backend, unique, loops)`. + pub fn deconstruct(self) -> (Option>, u32, u32, u32) { + (self.maybe_cursor, self.backend, self.unique, self.loops) + } +} + /// The Substrate externalities. /// /// Provides access to the storage and to other registered extensions. @@ -118,32 +142,47 @@ pub trait Externalities: ExtensionStore { /// Clear an entire child storage. /// - /// Deletes all keys from the overlay and up to `limit` keys from the backend. No - /// limit is applied if `limit` is `None`. Returned boolean is `true` if the child trie was - /// removed completely and `false` if there are remaining keys after the function - /// returns. Returned `u32` is the number of keys that was removed at the end of the - /// operation. + /// Deletes all keys from the overlay and up to `maybe_limit` keys from the backend. No + /// limit is applied if `maybe_limit` is `None`. Returns the cursor for the next call as `Some` + /// if the child trie deletion operation is incomplete. In this case, it should be passed into + /// the next call to avoid unaccounted iterations on the backend. Returns also the the number + /// of keys that were removed from the backend, the number of unique keys removed in total + /// (including from the overlay) and the number of backend iterations done. + /// + /// As long as `maybe_cursor` is passed from the result of the previous call, then the number of + /// iterations done will only ever be one more than the number of keys removed. /// /// # Note /// /// An implementation is free to delete more keys than the specified limit as long as /// it is able to do that in constant time. - fn kill_child_storage(&mut self, child_info: &ChildInfo, limit: Option) -> (bool, u32); + fn kill_child_storage( + &mut self, + child_info: &ChildInfo, + maybe_limit: Option, + maybe_cursor: Option<&[u8]>, + ) -> MultiRemovalResults; /// Clear storage entries which keys are start with the given prefix. /// - /// `limit` and result works as for `kill_child_storage`. - fn clear_prefix(&mut self, prefix: &[u8], limit: Option) -> (bool, u32); + /// `maybe_limit`, `maybe_cursor` and result works as for `kill_child_storage`. + fn clear_prefix( + &mut self, + prefix: &[u8], + maybe_limit: Option, + maybe_cursor: Option<&[u8]>, + ) -> MultiRemovalResults; /// Clear child storage entries which keys are start with the given prefix. /// - /// `limit` and result works as for `kill_child_storage`. + /// `maybe_limit`, `maybe_cursor` and result works as for `kill_child_storage`. fn clear_child_prefix( &mut self, child_info: &ChildInfo, prefix: &[u8], - limit: Option, - ) -> (bool, u32); + maybe_limit: Option, + maybe_cursor: Option<&[u8]>, + ) -> MultiRemovalResults; /// Set or clear a storage entry (`key`) of current contract being called (effective /// immediately). diff --git a/primitives/io/Cargo.toml b/primitives/io/Cargo.toml index bd288dd896d1b..09a087d509416 100644 --- a/primitives/io/Cargo.toml +++ b/primitives/io/Cargo.toml @@ -25,7 +25,7 @@ sp-state-machine = { version = "0.12.0", optional = true, path = "../state-machi sp-wasm-interface = { version = "6.0.0", path = "../wasm-interface", default-features = false } sp-runtime-interface = { version = "6.0.0", default-features = false, path = "../runtime-interface" } sp-trie = { version = "6.0.0", optional = true, path = "../trie" } -sp-externalities = { version = "0.12.0", optional = true, path = "../externalities" } +sp-externalities = { version = "0.12.0", default-features = false, path = "../externalities" } sp-tracing = { version = "5.0.0", default-features = false, path = "../tracing" } log = { version = "0.4.17", optional = true } futures = { version = "0.3.21", features = ["thread-pool"], optional = true } @@ -37,6 +37,7 @@ tracing-core = { version = "0.1.26", default-features = false} [features] default = ["std"] std = [ + "sp-externalities/std", "sp-core/std", "sp-keystore", "codec/std", @@ -47,7 +48,6 @@ std = [ "libsecp256k1", "secp256k1", "sp-runtime-interface/std", - "sp-externalities", "sp-wasm-interface/std", "sp-tracing/std", "tracing/std", diff --git a/primitives/io/src/lib.rs b/primitives/io/src/lib.rs index 8f62b03b62ea9..0a931919437af 100644 --- a/primitives/io/src/lib.rs +++ b/primitives/io/src/lib.rs @@ -81,6 +81,8 @@ mod batch_verifier; #[cfg(feature = "std")] use batch_verifier::BatchVerifier; +pub use sp_externalities::MultiRemovalResults; + #[cfg(feature = "std")] const LOG_TARGET: &str = "runtime::io"; @@ -99,12 +101,24 @@ pub enum EcdsaVerifyError { /// removed from the backend from making the `storage_kill` call. #[derive(PassByCodec, Encode, Decode)] pub enum KillStorageResult { - /// All key to remove were removed, return number of key removed from backend. + /// All keys to remove were removed, return number of iterations performed during the + /// operation. AllRemoved(u32), - /// Not all key to remove were removed, return number of key removed from backend. + /// Not all key to remove were removed, return number of iterations performed during the + /// operation. SomeRemaining(u32), } +impl From for KillStorageResult { + fn from(r: MultiRemovalResults) -> Self { + match r { + MultiRemovalResults { maybe_cursor: None, backend, .. } => Self::AllRemoved(backend), + MultiRemovalResults { maybe_cursor: Some(..), backend, .. } => + Self::SomeRemaining(backend), + } + } +} + /// Interface for accessing the storage from within the runtime. #[runtime_interface] pub trait Storage { @@ -145,7 +159,7 @@ pub trait Storage { /// Clear the storage of each key-value pair where the key starts with the given `prefix`. fn clear_prefix(&mut self, prefix: &[u8]) { - let _ = Externalities::clear_prefix(*self, prefix, None); + let _ = Externalities::clear_prefix(*self, prefix, None, None); } /// Clear the storage of each key-value pair where the key starts with the given `prefix`. @@ -175,13 +189,60 @@ pub trait Storage { /// backend. #[version(2)] fn clear_prefix(&mut self, prefix: &[u8], limit: Option) -> KillStorageResult { - let (all_removed, num_removed) = Externalities::clear_prefix(*self, prefix, limit); - match all_removed { - true => KillStorageResult::AllRemoved(num_removed), - false => KillStorageResult::SomeRemaining(num_removed), + let r = Externalities::clear_prefix(*self, prefix, limit, None); + match r.maybe_cursor { + None => KillStorageResult::AllRemoved(r.loops), + Some(_) => KillStorageResult::SomeRemaining(r.loops), } } + /// Partially clear the storage of each key-value pair where the key starts with the given + /// prefix. + /// + /// # Limit + /// + /// A *limit* should always be provided through `maybe_limit`. This is one fewer than the + /// maximum number of backend iterations which may be done by this operation and as such + /// represents the maximum number of backend deletions which may happen. A *limit* of zero + /// implies that no keys will be deleted, though there may be a single iteration done. + /// + /// The limit can be used to partially delete a prefix storage in case it is too large or costly + /// to delete in a single operation. + /// + /// # Cursor + /// + /// A *cursor* may be passed in to this operation with `maybe_cursor`. `None` should only be + /// passed once (in the initial call) for any given `maybe_prefix` value. Subsequent calls + /// operating on the same prefix should always pass `Some`, and this should be equal to the + /// previous call result's `maybe_cursor` field. + /// + /// Returns [`MultiRemovalResults`](sp_io::MultiRemovalResults) to inform about the result. Once + /// the resultant `maybe_cursor` field is `None`, then no further items remain to be deleted. + /// + /// NOTE: After the initial call for any given prefix, it is important that no keys further + /// keys under the same prefix are inserted. If so, then they may or may not be deleted by + /// subsequent calls. + /// + /// # Note + /// + /// Please note that keys which are residing in the overlay for that prefix when + /// issuing this call are deleted without counting towards the `limit`. + #[version(3, register_only)] + fn clear_prefix( + &mut self, + maybe_prefix: &[u8], + maybe_limit: Option, + maybe_cursor: Option>, //< TODO Make work or just Option>? + ) -> MultiRemovalResults { + Externalities::clear_prefix( + *self, + maybe_prefix, + maybe_limit, + maybe_cursor.as_ref().map(|x| &x[..]), + ) + .into() + } + /// Append the encoded `value` to the storage item at `key`. /// /// The storage item needs to implement [`EncodeAppend`](codec::EncodeAppend). @@ -323,7 +384,7 @@ pub trait DefaultChildStorage { /// is removed. fn storage_kill(&mut self, storage_key: &[u8]) { let child_info = ChildInfo::new_default(storage_key); - self.kill_child_storage(&child_info, None); + let _ = self.kill_child_storage(&child_info, None, None); } /// Clear a child storage key. @@ -332,8 +393,8 @@ pub trait DefaultChildStorage { #[version(2)] fn storage_kill(&mut self, storage_key: &[u8], limit: Option) -> bool { let child_info = ChildInfo::new_default(storage_key); - let (all_removed, _num_removed) = self.kill_child_storage(&child_info, limit); - all_removed + let r = self.kill_child_storage(&child_info, limit, None); + r.maybe_cursor.is_none() } /// Clear a child storage key. @@ -342,13 +403,28 @@ pub trait DefaultChildStorage { #[version(3)] fn storage_kill(&mut self, storage_key: &[u8], limit: Option) -> KillStorageResult { let child_info = ChildInfo::new_default(storage_key); - let (all_removed, num_removed) = self.kill_child_storage(&child_info, limit); - match all_removed { - true => KillStorageResult::AllRemoved(num_removed), - false => KillStorageResult::SomeRemaining(num_removed), + let r = self.kill_child_storage(&child_info, limit, None); + match r.maybe_cursor { + None => KillStorageResult::AllRemoved(r.loops), + Some(..) => KillStorageResult::SomeRemaining(r.loops), } } + /// Clear a child storage key. + /// + /// See `Storage` module `clear_prefix` documentation for `limit` usage. + #[version(4, register_only)] + fn storage_kill( + &mut self, + storage_key: &[u8], + maybe_limit: Option, + maybe_cursor: Option>, + ) -> MultiRemovalResults { + let child_info = ChildInfo::new_default(storage_key); + self.kill_child_storage(&child_info, maybe_limit, maybe_cursor.as_ref().map(|x| &x[..])) + .into() + } + /// Check a child storage key. /// /// Check whether the given `key` exists in default child defined at `storage_key`. @@ -362,7 +438,7 @@ pub trait DefaultChildStorage { /// Clear the child storage of each key-value pair where the key starts with the given `prefix`. fn clear_prefix(&mut self, storage_key: &[u8], prefix: &[u8]) { let child_info = ChildInfo::new_default(storage_key); - let _ = self.clear_child_prefix(&child_info, prefix, None); + let _ = self.clear_child_prefix(&child_info, prefix, None, None); } /// Clear the child storage of each key-value pair where the key starts with the given `prefix`. @@ -376,13 +452,34 @@ pub trait DefaultChildStorage { limit: Option, ) -> KillStorageResult { let child_info = ChildInfo::new_default(storage_key); - let (all_removed, num_removed) = self.clear_child_prefix(&child_info, prefix, limit); - match all_removed { - true => KillStorageResult::AllRemoved(num_removed), - false => KillStorageResult::SomeRemaining(num_removed), + let r = self.clear_child_prefix(&child_info, prefix, limit, None); + match r.maybe_cursor { + None => KillStorageResult::AllRemoved(r.loops), + Some(..) => KillStorageResult::SomeRemaining(r.loops), } } + /// Clear the child storage of each key-value pair where the key starts with the given `prefix`. + /// + /// See `Storage` module `clear_prefix` documentation for `limit` usage. + #[version(3, register_only)] + fn clear_prefix( + &mut self, + storage_key: &[u8], + prefix: &[u8], + maybe_limit: Option, + maybe_cursor: Option>, + ) -> MultiRemovalResults { + let child_info = ChildInfo::new_default(storage_key); + self.clear_child_prefix( + &child_info, + prefix, + maybe_limit, + maybe_cursor.as_ref().map(|x| &x[..]), + ) + .into() + } + /// Default child root calculation. /// /// "Commit" all existing operations and compute the resulting child storage root. @@ -1757,15 +1854,30 @@ mod tests { }); t.execute_with(|| { + // We can switch to this once we enable v3 of the `clear_prefix`. + //assert!(matches!( + // storage::clear_prefix(b":abc", None), + // MultiRemovalResults::NoneLeft { db: 2, total: 2 } + //)); assert!(matches!( storage::clear_prefix(b":abc", None), - KillStorageResult::AllRemoved(2) + KillStorageResult::AllRemoved(2), )); assert!(storage::get(b":a").is_some()); assert!(storage::get(b":abdd").is_some()); assert!(storage::get(b":abcd").is_none()); assert!(storage::get(b":abc").is_none()); + + // We can switch to this once we enable v3 of the `clear_prefix`. + //assert!(matches!( + // storage::clear_prefix(b":abc", None), + // MultiRemovalResults::NoneLeft { db: 0, total: 0 } + //)); + assert!(matches!( + storage::clear_prefix(b":abc", None), + KillStorageResult::AllRemoved(0), + )); }); } diff --git a/primitives/runtime-interface/Cargo.toml b/primitives/runtime-interface/Cargo.toml index c9419f73722c6..8cd31bab559ea 100644 --- a/primitives/runtime-interface/Cargo.toml +++ b/primitives/runtime-interface/Cargo.toml @@ -18,7 +18,7 @@ sp-wasm-interface = { version = "6.0.0", path = "../wasm-interface", default-fea sp-std = { version = "4.0.0", default-features = false, path = "../std" } sp-tracing = { version = "5.0.0", default-features = false, path = "../tracing" } sp-runtime-interface-proc-macro = { version = "5.0.0", path = "proc-macro" } -sp-externalities = { version = "0.12.0", optional = true, path = "../externalities" } +sp-externalities = { version = "0.12.0", default-features = false, path = "../externalities" } codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } static_assertions = "1.0.0" primitive-types = { version = "0.11.1", default-features = false } @@ -40,7 +40,7 @@ std = [ "sp-std/std", "sp-tracing/std", "codec/std", - "sp-externalities", + "sp-externalities/std", "primitive-types/std", ] diff --git a/primitives/runtime-interface/src/impls.rs b/primitives/runtime-interface/src/impls.rs index 3c1927d6b361a..e801931c306cf 100644 --- a/primitives/runtime-interface/src/impls.rs +++ b/primitives/runtime-interface/src/impls.rs @@ -548,3 +548,7 @@ impl PassBy for sp_storage::TrackedStorageKey { impl PassBy for sp_storage::StateVersion { type PassBy = Enum; } + +impl PassBy for sp_externalities::MultiRemovalResults { + type PassBy = Codec; +} diff --git a/primitives/state-machine/Cargo.toml b/primitives/state-machine/Cargo.toml index e472b66c5d9ee..1c652966180a7 100644 --- a/primitives/state-machine/Cargo.toml +++ b/primitives/state-machine/Cargo.toml @@ -35,6 +35,7 @@ hex-literal = "0.3.4" pretty_assertions = "1.2.1" rand = "0.7.2" sp-runtime = { version = "6.0.0", path = "../runtime" } +assert_matches = "1.5" [features] default = ["std"] diff --git a/primitives/state-machine/src/backend.rs b/primitives/state-machine/src/backend.rs index e9d4ac0fb8224..505b53c800f9e 100644 --- a/primitives/state-machine/src/backend.rs +++ b/primitives/state-machine/src/backend.rs @@ -112,6 +112,7 @@ pub trait Backend: sp_std::fmt::Debug { &self, child_info: Option<&ChildInfo>, prefix: Option<&[u8]>, + start_at: Option<&[u8]>, f: F, ); diff --git a/primitives/state-machine/src/basic.rs b/primitives/state-machine/src/basic.rs index bb387e6406243..6efc847bfbdb7 100644 --- a/primitives/state-machine/src/basic.rs +++ b/primitives/state-machine/src/basic.rs @@ -29,7 +29,7 @@ use sp_core::{ traits::Externalities, Blake2Hasher, }; -use sp_externalities::{Extension, Extensions}; +use sp_externalities::{Extension, Extensions, MultiRemovalResults}; use sp_trie::{empty_child_trie_root, HashKey, LayoutV0, LayoutV1, TrieConfiguration}; use std::{ any::{Any, TypeId}, @@ -209,23 +209,34 @@ impl Externalities for BasicExternalities { } } - fn kill_child_storage(&mut self, child_info: &ChildInfo, _limit: Option) -> (bool, u32) { - let num_removed = self + fn kill_child_storage( + &mut self, + child_info: &ChildInfo, + _maybe_limit: Option, + _maybe_cursor: Option<&[u8]>, + ) -> MultiRemovalResults { + let count = self .inner .children_default .remove(child_info.storage_key()) .map(|c| c.data.len()) - .unwrap_or(0); - (true, num_removed as u32) + .unwrap_or(0) as u32; + MultiRemovalResults { maybe_cursor: None, backend: count, unique: count, loops: count } } - fn clear_prefix(&mut self, prefix: &[u8], _limit: Option) -> (bool, u32) { + fn clear_prefix( + &mut self, + prefix: &[u8], + _maybe_limit: Option, + _maybe_cursor: Option<&[u8]>, + ) -> MultiRemovalResults { if is_child_storage_key(prefix) { warn!( target: "trie", "Refuse to clear prefix that is part of child storage key via main storage" ); - return (false, 0) + let maybe_cursor = Some(prefix.to_vec()); + return MultiRemovalResults { maybe_cursor, backend: 0, unique: 0, loops: 0 } } let to_remove = self @@ -237,19 +248,20 @@ impl Externalities for BasicExternalities { .cloned() .collect::>(); - let num_removed = to_remove.len(); + let count = to_remove.len() as u32; for key in to_remove { self.inner.top.remove(&key); } - (true, num_removed as u32) + MultiRemovalResults { maybe_cursor: None, backend: count, unique: count, loops: count } } fn clear_child_prefix( &mut self, child_info: &ChildInfo, prefix: &[u8], - _limit: Option, - ) -> (bool, u32) { + _maybe_limit: Option, + _maybe_cursor: Option<&[u8]>, + ) -> MultiRemovalResults { if let Some(child) = self.inner.children_default.get_mut(child_info.storage_key()) { let to_remove = child .data @@ -259,13 +271,13 @@ impl Externalities for BasicExternalities { .cloned() .collect::>(); - let num_removed = to_remove.len(); + let count = to_remove.len() as u32; for key in to_remove { child.data.remove(&key); } - (true, num_removed as u32) + MultiRemovalResults { maybe_cursor: None, backend: count, unique: count, loops: count } } else { - (true, 0) + MultiRemovalResults { maybe_cursor: None, backend: 0, unique: 0, loops: 0 } } } @@ -434,7 +446,7 @@ mod tests { ext.clear_child_storage(child_info, b"dog"); assert_eq!(ext.child_storage(child_info, b"dog"), None); - ext.kill_child_storage(child_info, None); + let _ = ext.kill_child_storage(child_info, None, None); assert_eq!(ext.child_storage(child_info, b"doe"), None); } @@ -456,8 +468,8 @@ mod tests { ], }); - let res = ext.kill_child_storage(child_info, None); - assert_eq!(res, (true, 3)); + let res = ext.kill_child_storage(child_info, None, None); + assert_eq!(res.deconstruct(), (None, 3, 3, 3)); } #[test] diff --git a/primitives/state-machine/src/ext.rs b/primitives/state-machine/src/ext.rs index e33569e2a1f67..1db0ec517015b 100644 --- a/primitives/state-machine/src/ext.rs +++ b/primitives/state-machine/src/ext.rs @@ -27,7 +27,7 @@ use sp_core::hexdisplay::HexDisplay; use sp_core::storage::{ well_known_keys::is_child_storage_key, ChildInfo, StateVersion, TrackedStorageKey, }; -use sp_externalities::{Extension, ExtensionStore, Externalities}; +use sp_externalities::{Extension, ExtensionStore, Externalities, MultiRemovalResults}; use sp_trie::{empty_child_trie_root, LayoutV1}; use crate::{log_error, trace, warn, StorageTransactionCache}; @@ -433,7 +433,12 @@ where self.overlay.set_child_storage(child_info, key, value); } - fn kill_child_storage(&mut self, child_info: &ChildInfo, limit: Option) -> (bool, u32) { + fn kill_child_storage( + &mut self, + child_info: &ChildInfo, + maybe_limit: Option, + maybe_cursor: Option<&[u8]>, + ) -> MultiRemovalResults { trace!( target: "state", method = "ChildKill", @@ -442,11 +447,18 @@ where ); let _guard = guard(); self.mark_dirty(); - self.overlay.clear_child_storage(child_info); - self.limit_remove_from_backend(Some(child_info), None, limit) + let overlay = self.overlay.clear_child_storage(child_info); + let (maybe_cursor, backend, loops) = + self.limit_remove_from_backend(Some(child_info), None, maybe_limit, maybe_cursor); + MultiRemovalResults { maybe_cursor, backend, unique: overlay + backend, loops } } - fn clear_prefix(&mut self, prefix: &[u8], limit: Option) -> (bool, u32) { + fn clear_prefix( + &mut self, + prefix: &[u8], + maybe_limit: Option, + maybe_cursor: Option<&[u8]>, + ) -> MultiRemovalResults { trace!( target: "state", method = "ClearPrefix", @@ -460,20 +472,23 @@ where target: "trie", "Refuse to directly clear prefix that is part or contains of child storage key", ); - return (false, 0) + return MultiRemovalResults { maybe_cursor: None, backend: 0, unique: 0, loops: 0 } } self.mark_dirty(); - self.overlay.clear_prefix(prefix); - self.limit_remove_from_backend(None, Some(prefix), limit) + let overlay = self.overlay.clear_prefix(prefix); + let (maybe_cursor, backend, loops) = + self.limit_remove_from_backend(None, Some(prefix), maybe_limit, maybe_cursor); + MultiRemovalResults { maybe_cursor, backend, unique: overlay + backend, loops } } fn clear_child_prefix( &mut self, child_info: &ChildInfo, prefix: &[u8], - limit: Option, - ) -> (bool, u32) { + maybe_limit: Option, + maybe_cursor: Option<&[u8]>, + ) -> MultiRemovalResults { trace!( target: "state", method = "ChildClearPrefix", @@ -484,8 +499,14 @@ where let _guard = guard(); self.mark_dirty(); - self.overlay.clear_child_prefix(child_info, prefix); - self.limit_remove_from_backend(Some(child_info), Some(prefix), limit) + let overlay = self.overlay.clear_child_prefix(child_info, prefix); + let (maybe_cursor, backend, loops) = self.limit_remove_from_backend( + Some(child_info), + Some(prefix), + maybe_limit, + maybe_cursor, + ); + MultiRemovalResults { maybe_cursor, backend, unique: overlay + backend, loops } } fn storage_append(&mut self, key: Vec, value: Vec) { @@ -728,45 +749,37 @@ where { fn limit_remove_from_backend( &mut self, - child_info: Option<&ChildInfo>, - prefix: Option<&[u8]>, - limit: Option, - ) -> (bool, u32) { - let mut num_deleted: u32 = 0; - - if let Some(limit) = limit { - let mut all_deleted = true; - self.backend.apply_to_keys_while(child_info, prefix, |key| { - if num_deleted == limit { - all_deleted = false; - return false - } - if let Some(num) = num_deleted.checked_add(1) { - num_deleted = num; - } else { - all_deleted = false; + maybe_child: Option<&ChildInfo>, + maybe_prefix: Option<&[u8]>, + maybe_limit: Option, + maybe_cursor: Option<&[u8]>, + ) -> (Option>, u32, u32) { + let mut delete_count: u32 = 0; + let mut loop_count: u32 = 0; + let mut maybe_next_key = None; + self.backend + .apply_to_keys_while(maybe_child, maybe_prefix, maybe_cursor, |key| { + if maybe_limit.map_or(false, |limit| loop_count == limit) { + maybe_next_key = Some(key.to_vec()); return false } - if let Some(child_info) = child_info { - self.overlay.set_child_storage(child_info, key.to_vec(), None); - } else { - self.overlay.set_storage(key.to_vec(), None); - } - true - }); - (all_deleted, num_deleted) - } else { - self.backend.apply_to_keys_while(child_info, prefix, |key| { - num_deleted = num_deleted.saturating_add(1); - if let Some(child_info) = child_info { - self.overlay.set_child_storage(child_info, key.to_vec(), None); - } else { - self.overlay.set_storage(key.to_vec(), None); + let overlay = match maybe_child { + Some(child_info) => self.overlay.child_storage(child_info, key), + None => self.overlay.storage(key), + }; + if !matches!(overlay, Some(None)) { + // not pending deletion from the backend - delete it. + if let Some(child_info) = maybe_child { + self.overlay.set_child_storage(child_info, key.to_vec(), None); + } else { + self.overlay.set_storage(key.to_vec(), None); + } + delete_count = delete_count.saturating_add(1); } + loop_count = loop_count.saturating_add(1); true }); - (true, num_deleted) - } + (maybe_next_key, delete_count, loop_count) } } @@ -1085,14 +1098,14 @@ mod tests { not_under_prefix.extend(b"path"); ext.set_storage(not_under_prefix.clone(), vec![10]); - ext.clear_prefix(&[], None); - ext.clear_prefix(&well_known_keys::CHILD_STORAGE_KEY_PREFIX[..4], None); + let _ = ext.clear_prefix(&[], None, None); + let _ = ext.clear_prefix(&well_known_keys::CHILD_STORAGE_KEY_PREFIX[..4], None, None); let mut under_prefix = well_known_keys::CHILD_STORAGE_KEY_PREFIX.to_vec(); under_prefix.extend(b"path"); - ext.clear_prefix(&well_known_keys::CHILD_STORAGE_KEY_PREFIX[..4], None); + let _ = ext.clear_prefix(&well_known_keys::CHILD_STORAGE_KEY_PREFIX[..4], None, None); assert_eq!(ext.child_storage(child_info, &[30]), Some(vec![40])); assert_eq!(ext.storage(not_under_prefix.as_slice()), Some(vec![10])); - ext.clear_prefix(¬_under_prefix[..5], None); + let _ = ext.clear_prefix(¬_under_prefix[..5], None, None); assert_eq!(ext.storage(not_under_prefix.as_slice()), None); } diff --git a/primitives/state-machine/src/lib.rs b/primitives/state-machine/src/lib.rs index e5b521588aa79..edc3db7a60e36 100644 --- a/primitives/state-machine/src/lib.rs +++ b/primitives/state-machine/src/lib.rs @@ -1348,6 +1348,7 @@ mod execution { mod tests { use super::{ext::Ext, *}; use crate::{execution::CallResult, in_memory_backend::new_in_mem_hash_key}; + use assert_matches::assert_matches; use codec::{Decode, Encode}; use sp_core::{ map, @@ -1572,7 +1573,7 @@ mod tests { { let mut cache = StorageTransactionCache::default(); let mut ext = Ext::new(&mut overlay, &mut cache, backend, None); - ext.clear_prefix(b"ab", None); + let _ = ext.clear_prefix(b"ab", None, None); } overlay.commit_transaction().unwrap(); @@ -1596,7 +1597,10 @@ mod tests { { let mut cache = StorageTransactionCache::default(); let mut ext = Ext::new(&mut overlay, &mut cache, backend, None); - assert_eq!((false, 1), ext.clear_prefix(b"ab", Some(1))); + assert_matches!( + ext.clear_prefix(b"ab", Some(1), None).deconstruct(), + (Some(_), 1, 3, 1) + ); } overlay.commit_transaction().unwrap(); @@ -1638,7 +1642,8 @@ mod tests { { let mut cache = StorageTransactionCache::default(); let mut ext = Ext::new(&mut overlay, &mut cache, &backend, None); - assert_eq!(ext.kill_child_storage(&child_info, Some(2)), (false, 2)); + let r = ext.kill_child_storage(&child_info, Some(2), None); + assert_matches!(r.deconstruct(), (Some(_), 2, 6, 2)); } assert_eq!( @@ -1673,14 +1678,37 @@ mod tests { let mut overlay = OverlayedChanges::default(); let mut cache = StorageTransactionCache::default(); let mut ext = Ext::new(&mut overlay, &mut cache, &backend, None); - assert_eq!(ext.kill_child_storage(&child_info, Some(0)), (false, 0)); - assert_eq!(ext.kill_child_storage(&child_info, Some(1)), (false, 1)); - assert_eq!(ext.kill_child_storage(&child_info, Some(2)), (false, 2)); - assert_eq!(ext.kill_child_storage(&child_info, Some(3)), (false, 3)); - assert_eq!(ext.kill_child_storage(&child_info, Some(4)), (true, 4)); - // Only 4 items to remove - assert_eq!(ext.kill_child_storage(&child_info, Some(5)), (true, 4)); - assert_eq!(ext.kill_child_storage(&child_info, None), (true, 4)); + let r = ext.kill_child_storage(&child_info, Some(0), None).deconstruct(); + assert_matches!(r, (Some(_), 0, 0, 0)); + let r = ext + .kill_child_storage(&child_info, Some(1), r.0.as_ref().map(|x| &x[..])) + .deconstruct(); + assert_matches!(r, (Some(_), 1, 1, 1)); + let r = ext + .kill_child_storage(&child_info, Some(4), r.0.as_ref().map(|x| &x[..])) + .deconstruct(); + // Only 3 items remaining to remove + assert_matches!(r, (None, 3, 3, 3)); + let r = ext.kill_child_storage(&child_info, Some(1), None).deconstruct(); + assert_matches!(r, (Some(_), 0, 0, 1)); + } + + #[test] + fn limited_child_kill_off_by_one_works_without_limit() { + let child_info = ChildInfo::new_default(b"sub1"); + let initial: HashMap<_, BTreeMap<_, _>> = map![ + Some(child_info.clone()) => map![ + b"a".to_vec() => b"0".to_vec(), + b"b".to_vec() => b"1".to_vec(), + b"c".to_vec() => b"2".to_vec(), + b"d".to_vec() => b"3".to_vec() + ], + ]; + let backend = InMemoryBackend::::from((initial, StateVersion::default())); + let mut overlay = OverlayedChanges::default(); + let mut cache = StorageTransactionCache::default(); + let mut ext = Ext::new(&mut overlay, &mut cache, &backend, None); + assert_eq!(ext.kill_child_storage(&child_info, None, None).deconstruct(), (None, 4, 4, 4)); } #[test] @@ -1695,7 +1723,7 @@ mod tests { ext.set_child_storage(child_info, b"abc".to_vec(), b"def".to_vec()); assert_eq!(ext.child_storage(child_info, b"abc"), Some(b"def".to_vec())); - ext.kill_child_storage(child_info, None); + let _ = ext.kill_child_storage(child_info, None, None); assert_eq!(ext.child_storage(child_info, b"abc"), None); } diff --git a/primitives/state-machine/src/overlayed_changes/changeset.rs b/primitives/state-machine/src/overlayed_changes/changeset.rs index 9e7f6ffeddfd7..ae5d47f6bb943 100644 --- a/primitives/state-machine/src/overlayed_changes/changeset.rs +++ b/primitives/state-machine/src/overlayed_changes/changeset.rs @@ -410,10 +410,15 @@ impl OverlayedChangeSet { &mut self, predicate: impl Fn(&[u8], &OverlayedValue) -> bool, at_extrinsic: Option, - ) { + ) -> u32 { + let mut count = 0; for (key, val) in self.changes.iter_mut().filter(|(k, v)| predicate(k, v)) { + if val.value_ref().is_some() { + count += 1; + } val.set(None, insert_dirty(&mut self.dirty_keys, key.clone()), at_extrinsic); } + count } /// Get the iterator over all changes that follow the supplied `key`. diff --git a/primitives/state-machine/src/overlayed_changes/mod.rs b/primitives/state-machine/src/overlayed_changes/mod.rs index 2161b343711c9..746599a6768e6 100644 --- a/primitives/state-machine/src/overlayed_changes/mod.rs +++ b/primitives/state-machine/src/overlayed_changes/mod.rs @@ -299,7 +299,7 @@ impl OverlayedChanges { /// Clear child storage of given storage key. /// /// Can be rolled back or committed when called inside a transaction. - pub(crate) fn clear_child_storage(&mut self, child_info: &ChildInfo) { + pub(crate) fn clear_child_storage(&mut self, child_info: &ChildInfo) -> u32 { let extrinsic_index = self.extrinsic_index(); let storage_key = child_info.storage_key().to_vec(); let top = &self.top; @@ -309,20 +309,20 @@ impl OverlayedChanges { .or_insert_with(|| (top.spawn_child(), child_info.clone())); let updatable = info.try_update(child_info); debug_assert!(updatable); - changeset.clear_where(|_, _| true, extrinsic_index); + changeset.clear_where(|_, _| true, extrinsic_index) } /// Removes all key-value pairs which keys share the given prefix. /// /// Can be rolled back or committed when called inside a transaction. - pub(crate) fn clear_prefix(&mut self, prefix: &[u8]) { - self.top.clear_where(|key, _| key.starts_with(prefix), self.extrinsic_index()); + pub(crate) fn clear_prefix(&mut self, prefix: &[u8]) -> u32 { + self.top.clear_where(|key, _| key.starts_with(prefix), self.extrinsic_index()) } /// Removes all key-value pairs which keys share the given prefix. /// /// Can be rolled back or committed when called inside a transaction - pub(crate) fn clear_child_prefix(&mut self, child_info: &ChildInfo, prefix: &[u8]) { + pub(crate) fn clear_child_prefix(&mut self, child_info: &ChildInfo, prefix: &[u8]) -> u32 { let extrinsic_index = self.extrinsic_index(); let storage_key = child_info.storage_key().to_vec(); let top = &self.top; @@ -332,7 +332,7 @@ impl OverlayedChanges { .or_insert_with(|| (top.spawn_child(), child_info.clone())); let updatable = info.try_update(child_info); debug_assert!(updatable); - changeset.clear_where(|key, _| key.starts_with(prefix), extrinsic_index); + changeset.clear_where(|key, _| key.starts_with(prefix), extrinsic_index) } /// Returns the current nesting depth of the transaction stack. diff --git a/primitives/state-machine/src/proving_backend.rs b/primitives/state-machine/src/proving_backend.rs index b47e6c693a3e8..f5cf542908b10 100644 --- a/primitives/state-machine/src/proving_backend.rs +++ b/primitives/state-machine/src/proving_backend.rs @@ -288,9 +288,10 @@ where &self, child_info: Option<&ChildInfo>, prefix: Option<&[u8]>, + start_at: Option<&[u8]>, f: F, ) { - self.0.apply_to_keys_while(child_info, prefix, f) + self.0.apply_to_keys_while(child_info, prefix, start_at, f) } fn next_storage_key(&self, key: &[u8]) -> Result>, Self::Error> { diff --git a/primitives/state-machine/src/read_only.rs b/primitives/state-machine/src/read_only.rs index 3c3e779c32f3a..622915a2d0525 100644 --- a/primitives/state-machine/src/read_only.rs +++ b/primitives/state-machine/src/read_only.rs @@ -25,6 +25,7 @@ use sp_core::{ traits::Externalities, Blake2Hasher, }; +use sp_externalities::MultiRemovalResults; use std::{ any::{Any, TypeId}, marker::PhantomData, @@ -124,11 +125,21 @@ impl<'a, H: Hasher, B: 'a + Backend> Externalities for ReadOnlyExternalities< unimplemented!("place_child_storage not supported in ReadOnlyExternalities") } - fn kill_child_storage(&mut self, _child_info: &ChildInfo, _limit: Option) -> (bool, u32) { + fn kill_child_storage( + &mut self, + _child_info: &ChildInfo, + _maybe_limit: Option, + _maybe_cursor: Option<&[u8]>, + ) -> MultiRemovalResults { unimplemented!("kill_child_storage is not supported in ReadOnlyExternalities") } - fn clear_prefix(&mut self, _prefix: &[u8], _limit: Option) -> (bool, u32) { + fn clear_prefix( + &mut self, + _prefix: &[u8], + _maybe_limit: Option, + _maybe_cursor: Option<&[u8]>, + ) -> MultiRemovalResults { unimplemented!("clear_prefix is not supported in ReadOnlyExternalities") } @@ -136,8 +147,9 @@ impl<'a, H: Hasher, B: 'a + Backend> Externalities for ReadOnlyExternalities< &mut self, _child_info: &ChildInfo, _prefix: &[u8], - _limit: Option, - ) -> (bool, u32) { + _maybe_limit: Option, + _maybe_cursor: Option<&[u8]>, + ) -> MultiRemovalResults { unimplemented!("clear_child_prefix is not supported in ReadOnlyExternalities") } diff --git a/primitives/state-machine/src/testing.rs b/primitives/state-machine/src/testing.rs index bc462ae01ab26..57d4f0b4898eb 100644 --- a/primitives/state-machine/src/testing.rs +++ b/primitives/state-machine/src/testing.rs @@ -381,7 +381,10 @@ mod tests { { let mut ext = ext.ext(); - assert!(!ext.kill_child_storage(&child_info, Some(2)).0, "Should not delete all keys"); + assert!( + ext.kill_child_storage(&child_info, Some(2), None).maybe_cursor.is_some(), + "Should not delete all keys" + ); assert!(ext.child_storage(&child_info, &b"doe"[..]).is_none()); assert!(ext.child_storage(&child_info, &b"dog"[..]).is_none()); diff --git a/primitives/state-machine/src/trie_backend.rs b/primitives/state-machine/src/trie_backend.rs index c0a620120bff2..130b4bf178202 100644 --- a/primitives/state-machine/src/trie_backend.rs +++ b/primitives/state-machine/src/trie_backend.rs @@ -123,9 +123,10 @@ where &self, child_info: Option<&ChildInfo>, prefix: Option<&[u8]>, + start_at: Option<&[u8]>, f: F, ) { - self.essence.apply_to_keys_while(child_info, prefix, f) + self.essence.apply_to_keys_while(child_info, prefix, start_at, f) } fn for_child_keys_with_prefix( diff --git a/primitives/state-machine/src/trie_backend_essence.rs b/primitives/state-machine/src/trie_backend_essence.rs index 11cac92efd2f4..c55a6b7e80481 100644 --- a/primitives/state-machine/src/trie_backend_essence.rs +++ b/primitives/state-machine/src/trie_backend_essence.rs @@ -267,7 +267,8 @@ where &self, child_info: Option<&ChildInfo>, prefix: Option<&[u8]>, - mut f: F, + start_at: Option<&[u8]>, + f: F, ) { let root = if let Some(child_info) = child_info.as_ref() { match self.child_root(child_info) { @@ -283,7 +284,7 @@ where self.root }; - self.trie_iter_key_inner(&root, prefix, |k| f(k), child_info) + self.trie_iter_key_inner(&root, prefix, f, child_info, start_at) } /// Execute given closure for all keys starting with prefix. @@ -311,6 +312,7 @@ where true }, Some(child_info), + None, ) } @@ -324,28 +326,31 @@ where true }, None, + None, ) } fn trie_iter_key_inner bool>( &self, root: &H::Out, - prefix: Option<&[u8]>, + maybe_prefix: Option<&[u8]>, mut f: F, child_info: Option<&ChildInfo>, + maybe_start_at: Option<&[u8]>, ) { let mut iter = move |db| -> sp_std::result::Result<(), Box>> { let trie = TrieDB::::new(db, root)?; - let iter = if let Some(prefix) = prefix.as_ref() { - TrieDBKeyIterator::new_prefixed(&trie, prefix)? - } else { - TrieDBKeyIterator::new(&trie)? - }; + let prefix = maybe_prefix.unwrap_or(&[]); + let iter = match maybe_start_at { + Some(start_at) => + TrieDBKeyIterator::new_prefixed_then_seek(&trie, prefix, start_at), + None => TrieDBKeyIterator::new_prefixed(&trie, prefix), + }?; for x in iter { let key = x?; - debug_assert!(prefix + debug_assert!(maybe_prefix .as_ref() .map(|prefix| key.starts_with(prefix)) .unwrap_or(true)); diff --git a/primitives/tasks/src/async_externalities.rs b/primitives/tasks/src/async_externalities.rs index 70e3437538e8f..008955a714b21 100644 --- a/primitives/tasks/src/async_externalities.rs +++ b/primitives/tasks/src/async_externalities.rs @@ -22,7 +22,7 @@ use sp_core::{ storage::{ChildInfo, StateVersion, TrackedStorageKey}, traits::{Externalities, RuntimeSpawn, RuntimeSpawnExt, SpawnNamed, TaskExecutorExt}, }; -use sp_externalities::{Extensions, ExternalitiesExt as _}; +use sp_externalities::{Extensions, ExternalitiesExt as _, MultiRemovalResults}; use std::any::{Any, TypeId}; /// Simple state-less externalities for use in async context. @@ -105,11 +105,21 @@ impl Externalities for AsyncExternalities { panic!("`place_child_storage`: should not be used in async externalities!") } - fn kill_child_storage(&mut self, _child_info: &ChildInfo, _limit: Option) -> (bool, u32) { + fn kill_child_storage( + &mut self, + _child_info: &ChildInfo, + _maybe_limit: Option, + _maybe_cursor: Option<&[u8]>, + ) -> MultiRemovalResults { panic!("`kill_child_storage`: should not be used in async externalities!") } - fn clear_prefix(&mut self, _prefix: &[u8], _limit: Option) -> (bool, u32) { + fn clear_prefix( + &mut self, + _prefix: &[u8], + _maybe_limit: Option, + _maybe_cursor: Option<&[u8]>, + ) -> MultiRemovalResults { panic!("`clear_prefix`: should not be used in async externalities!") } @@ -117,8 +127,9 @@ impl Externalities for AsyncExternalities { &mut self, _child_info: &ChildInfo, _prefix: &[u8], - _limit: Option, - ) -> (bool, u32) { + _maybe_limit: Option, + _maybe_cursor: Option<&[u8]>, + ) -> MultiRemovalResults { panic!("`clear_child_prefix`: should not be used in async externalities!") } From 9cb6c092283c401b454681a70a1652cc17251196 Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Mon, 30 May 2022 16:07:00 +0100 Subject: [PATCH 278/484] Use `loops` rather than `backend` for compatibility. (#11542) * Use `loops` rather than `backend` for compatibility. * Move over other converters --- primitives/io/src/lib.rs | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/primitives/io/src/lib.rs b/primitives/io/src/lib.rs index 0a931919437af..89b74dc3c6880 100644 --- a/primitives/io/src/lib.rs +++ b/primitives/io/src/lib.rs @@ -111,10 +111,12 @@ pub enum KillStorageResult { impl From for KillStorageResult { fn from(r: MultiRemovalResults) -> Self { - match r { - MultiRemovalResults { maybe_cursor: None, backend, .. } => Self::AllRemoved(backend), - MultiRemovalResults { maybe_cursor: Some(..), backend, .. } => - Self::SomeRemaining(backend), + // We use `loops` here rather than `backend` because that's the same as the original + // functionality pre-#11490. This won't matter once we switch to the new host function + // since we won't be using the `KillStorageResult` type in the runtime any more. + match r.maybe_cursor { + None => Self::AllRemoved(r.loops), + Some(..) => Self::SomeRemaining(r.loops), } } } @@ -189,11 +191,7 @@ pub trait Storage { /// backend. #[version(2)] fn clear_prefix(&mut self, prefix: &[u8], limit: Option) -> KillStorageResult { - let r = Externalities::clear_prefix(*self, prefix, limit, None); - match r.maybe_cursor { - None => KillStorageResult::AllRemoved(r.loops), - Some(_) => KillStorageResult::SomeRemaining(r.loops), - } + Externalities::clear_prefix(*self, prefix, limit, None).into() } /// Partially clear the storage of each key-value pair where the key starts with the given @@ -403,11 +401,7 @@ pub trait DefaultChildStorage { #[version(3)] fn storage_kill(&mut self, storage_key: &[u8], limit: Option) -> KillStorageResult { let child_info = ChildInfo::new_default(storage_key); - let r = self.kill_child_storage(&child_info, limit, None); - match r.maybe_cursor { - None => KillStorageResult::AllRemoved(r.loops), - Some(..) => KillStorageResult::SomeRemaining(r.loops), - } + self.kill_child_storage(&child_info, limit, None).into() } /// Clear a child storage key. @@ -452,11 +446,7 @@ pub trait DefaultChildStorage { limit: Option, ) -> KillStorageResult { let child_info = ChildInfo::new_default(storage_key); - let r = self.clear_child_prefix(&child_info, prefix, limit, None); - match r.maybe_cursor { - None => KillStorageResult::AllRemoved(r.loops), - Some(..) => KillStorageResult::SomeRemaining(r.loops), - } + self.clear_child_prefix(&child_info, prefix, limit, None).into() } /// Clear the child storage of each key-value pair where the key starts with the given `prefix`. From c38ced90e926a340c977624e825bc1a757ce722e Mon Sep 17 00:00:00 2001 From: yjh Date: Tue, 31 May 2022 15:25:52 +0800 Subject: [PATCH 279/484] sp-core: impl serde for some offchain types (#11512) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * sp-core: impl serde for some offchain types * Update primitives/core/src/offchain/mod.rs Co-authored-by: Bastian Köcher * remove serde impls from OpaqueNetworkState/OpaqueMultiaddr * derive default Co-authored-by: Bastian Köcher --- primitives/core/src/offchain/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/primitives/core/src/offchain/mod.rs b/primitives/core/src/offchain/mod.rs index 4ffadc3e4031d..3f0eed87a3a64 100644 --- a/primitives/core/src/offchain/mod.rs +++ b/primitives/core/src/offchain/mod.rs @@ -208,12 +208,14 @@ impl OpaqueMultiaddr { #[derive( Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Default, RuntimeDebug, PassByInner, Encode, Decode, )] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub struct Timestamp(u64); /// Duration type #[derive( Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Default, RuntimeDebug, PassByInner, Encode, Decode, )] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub struct Duration(u64); impl Duration { From 654b9a9267c492478187661bbc0c1123ebcb583e Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Tue, 31 May 2022 11:12:34 +0100 Subject: [PATCH 280/484] Several tweaks needed for Governance 2.0 (#11124) * Add stepped curve for referenda * Treasury SpendOrigin * Add tests * Better Origin Or-gating * Reciprocal curve * Tests for reciprical and rounding in PerThings * Tweaks and new quad curve * Const derivation of reciprocal curve parameters * Remove some unneeded code * Actually useful linear curve * Fixes * Provisional curves * Rejig 'turnout' as 'support' * Use TypedGet * Fixes * Enable curve's ceil to be configured * Formatting * Fixes * Fixes * Fixes * Remove EnsureOneOf * Fixes * Fixes * Fixes * Formatting * Fixes * Update frame/support/src/traits/dispatch.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Grumbles * Formatting * Fixes * APIs of VoteTally should include class * Fixes * Fix overlay prefix removal result * Second part of the overlay prefix removal fix. * Formatting * Fixes * Add some tests and make clear rounding algo * Fixes * Formatting * Revert questionable fix * Introduce test for kill_prefix * Fixes * Formatting * Fixes * Fix possible overflow * Docs * Add benchmark test * Formatting * Update frame/referenda/src/types.rs Co-authored-by: Keith Yeung * Docs * Fixes * Use latest API in tests * Formatting * Whitespace * Use latest API in tests Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Co-authored-by: Keith Yeung --- Cargo.lock | 2 + bin/node/runtime/src/lib.rs | 25 +- docs/Upgrading-2.0-to-3.0.md | 2 +- frame/bounties/src/tests.rs | 2 +- frame/child-bounties/src/tests.rs | 1 + frame/conviction-voting/src/tests.rs | 90 ++-- frame/conviction-voting/src/types.rs | 115 ++--- frame/identity/src/tests.rs | 6 +- frame/referenda/Cargo.toml | 3 + frame/referenda/src/benchmarking.rs | 15 +- frame/referenda/src/lib.rs | 48 ++- frame/referenda/src/mock.rs | 61 ++- frame/referenda/src/tests.rs | 10 +- frame/referenda/src/types.rs | 409 +++++++++++++++--- frame/scheduler/src/mock.rs | 4 +- frame/staking/src/benchmarking.rs | 4 +- frame/staking/src/tests.rs | 7 +- frame/support/src/lib.rs | 93 +++- frame/support/src/traits.rs | 8 +- frame/support/src/traits/dispatch.rs | 118 ++++- frame/support/src/traits/misc.rs | 16 + frame/support/src/traits/voting.rs | 16 +- frame/system/src/lib.rs | 25 +- frame/tips/src/tests.rs | 1 + frame/treasury/src/benchmarking.rs | 20 +- frame/treasury/src/lib.rs | 48 +++ frame/treasury/src/tests.rs | 70 ++- frame/treasury/src/weights.rs | 15 + primitives/arithmetic/Cargo.toml | 1 + primitives/arithmetic/src/fixed_point.rs | 431 +++++++++++++++++- primitives/arithmetic/src/helpers_128bit.rs | 255 ++++++++++- primitives/arithmetic/src/lib.rs | 5 +- primitives/arithmetic/src/per_things.rs | 455 +++++++++++++++++--- primitives/arithmetic/src/traits.rs | 2 + 34 files changed, 2047 insertions(+), 336 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 29d15b9e015cb..09da4278f69f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6018,6 +6018,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", + "sp-arithmetic", "sp-core", "sp-io", "sp-runtime", @@ -9500,6 +9501,7 @@ dependencies = [ "rand 0.7.3", "scale-info", "serde", + "sp-core", "sp-debug-derive", "sp-std", "static_assertions", diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index c98bb5cc13f85..9b5bc0273ab33 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -31,7 +31,7 @@ use frame_support::{ pallet_prelude::Get, parameter_types, traits::{ - AsEnsureOriginWithArg, ConstU128, ConstU16, ConstU32, Currency, EnsureOneOf, + AsEnsureOriginWithArg, ConstU128, ConstU16, ConstU32, Currency, EitherOfDiverse, EqualPrivilegeOnly, Everything, Imbalance, InstanceFilter, KeyOwnerProofSystem, LockIdentifier, Nothing, OnUnbalanced, U128CurrencyToVote, }, @@ -550,7 +550,7 @@ impl pallet_staking::Config for Runtime { type BondingDuration = BondingDuration; type SlashDeferDuration = SlashDeferDuration; /// A super-majority of the council can cancel the slash. - type SlashCancelOrigin = EnsureOneOf< + type SlashCancelOrigin = EitherOfDiverse< EnsureRoot, pallet_collective::EnsureProportionAtLeast, >; @@ -794,12 +794,14 @@ impl pallet_referenda::TracksInfo for TracksInfo { confirm_period: 2, min_enactment_period: 4, min_approval: pallet_referenda::Curve::LinearDecreasing { - begin: Perbill::from_percent(100), - delta: Perbill::from_percent(50), + length: Perbill::from_percent(100), + floor: Perbill::from_percent(50), + ceil: Perbill::from_percent(100), }, - min_turnout: pallet_referenda::Curve::LinearDecreasing { - begin: Perbill::from_percent(100), - delta: Perbill::from_percent(100), + min_support: pallet_referenda::Curve::LinearDecreasing { + length: Perbill::from_percent(100), + floor: Perbill::from_percent(0), + ceil: Perbill::from_percent(100), }, }, )]; @@ -882,7 +884,7 @@ impl pallet_democracy::Config for Runtime { pallet_collective::EnsureProportionAtLeast; // To cancel a proposal before it has been passed, the technical committee must be unanimous or // Root must agree. - type CancelProposalOrigin = EnsureOneOf< + type CancelProposalOrigin = EitherOfDiverse< EnsureRoot, pallet_collective::EnsureProportionAtLeast, >; @@ -972,7 +974,7 @@ impl pallet_collective::Config for Runtime { type WeightInfo = pallet_collective::weights::SubstrateWeight; } -type EnsureRootOrHalfCouncil = EnsureOneOf< +type EnsureRootOrHalfCouncil = EitherOfDiverse< EnsureRoot, pallet_collective::EnsureProportionMoreThan, >; @@ -1006,11 +1008,11 @@ parameter_types! { impl pallet_treasury::Config for Runtime { type PalletId = TreasuryPalletId; type Currency = Balances; - type ApproveOrigin = EnsureOneOf< + type ApproveOrigin = EitherOfDiverse< EnsureRoot, pallet_collective::EnsureProportionAtLeast, >; - type RejectOrigin = EnsureOneOf< + type RejectOrigin = EitherOfDiverse< EnsureRoot, pallet_collective::EnsureProportionMoreThan, >; @@ -1025,6 +1027,7 @@ impl pallet_treasury::Config for Runtime { type SpendFunds = Bounties; type WeightInfo = pallet_treasury::weights::SubstrateWeight; type MaxApprovals = MaxApprovals; + type SpendOrigin = frame_support::traits::NeverEnsureOrigin; } parameter_types! { diff --git a/docs/Upgrading-2.0-to-3.0.md b/docs/Upgrading-2.0-to-3.0.md index 017467ede2d7e..f750c6dd5865b 100644 --- a/docs/Upgrading-2.0-to-3.0.md +++ b/docs/Upgrading-2.0-to-3.0.md @@ -290,7 +290,7 @@ Democracy brings three new settings with this release, all to allow for better i type CancellationOrigin = pallet_collective::EnsureProportionAtLeast<_2, _3, AccountId, CouncilCollective>; + // To cancel a proposal before it has been passed, the technical committee must be unanimous or + // Root must agree. -+ type CancelProposalOrigin = EnsureOneOf< ++ type CancelProposalOrigin = EitherOfDiverse< + AccountId, + EnsureRoot, + pallet_collective::EnsureProportionAtLeast<_1, _1, AccountId, TechnicalCollective>, diff --git a/frame/bounties/src/tests.rs b/frame/bounties/src/tests.rs index 9a84bd687abc1..0904e3a2901bb 100644 --- a/frame/bounties/src/tests.rs +++ b/frame/bounties/src/tests.rs @@ -109,7 +109,6 @@ parameter_types! { pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry"); } -// impl pallet_treasury::Config for Test { impl pallet_treasury::Config for Test { type PalletId = TreasuryPalletId; type Currency = pallet_balances::Pallet; @@ -126,6 +125,7 @@ impl pallet_treasury::Config for Test { type WeightInfo = (); type SpendFunds = Bounties; type MaxApprovals = ConstU32<100>; + type SpendOrigin = frame_support::traits::NeverEnsureOrigin; } parameter_types! { diff --git a/frame/child-bounties/src/tests.rs b/frame/child-bounties/src/tests.rs index 61545561a26c3..2584445071471 100644 --- a/frame/child-bounties/src/tests.rs +++ b/frame/child-bounties/src/tests.rs @@ -130,6 +130,7 @@ impl pallet_treasury::Config for Test { type WeightInfo = (); type SpendFunds = Bounties; type MaxApprovals = ConstU32<100>; + type SpendOrigin = frame_support::traits::NeverEnsureOrigin; } parameter_types! { // This will be 50% of the bounty fee. diff --git a/frame/conviction-voting/src/tests.rs b/frame/conviction-voting/src/tests.rs index 6a8bad5d8944e..9eb7f679efca3 100644 --- a/frame/conviction-voting/src/tests.rs +++ b/frame/conviction-voting/src/tests.rs @@ -21,7 +21,7 @@ use std::collections::BTreeMap; use frame_support::{ assert_noop, assert_ok, parameter_types, - traits::{ConstU32, ConstU64, Contains, Polling}, + traits::{ConstU32, ConstU64, Contains, Polling, VoteTally}, }; use sp_core::H256; use sp_runtime::{ @@ -166,7 +166,7 @@ impl Polling> for TestPolls { fn create_ongoing(class: Self::Class) -> Result { let mut polls = Polls::get(); let i = polls.keys().rev().next().map_or(0, |x| x + 1); - polls.insert(i, Ongoing(Tally::default(), class)); + polls.insert(i, Ongoing(Tally::new(0), class)); Polls::set(polls); Ok(i) } @@ -271,19 +271,19 @@ fn basic_voting_works() { assert_ok!(Voting::vote(Origin::signed(1), 3, aye(2, 5))); assert_eq!(tally(3), Tally::from_parts(10, 0, 2)); assert_ok!(Voting::vote(Origin::signed(1), 3, nay(2, 5))); - assert_eq!(tally(3), Tally::from_parts(0, 10, 2)); + assert_eq!(tally(3), Tally::from_parts(0, 10, 0)); assert_eq!(Balances::usable_balance(1), 8); assert_ok!(Voting::vote(Origin::signed(1), 3, aye(5, 1))); assert_eq!(tally(3), Tally::from_parts(5, 0, 5)); assert_ok!(Voting::vote(Origin::signed(1), 3, nay(5, 1))); - assert_eq!(tally(3), Tally::from_parts(0, 5, 5)); + assert_eq!(tally(3), Tally::from_parts(0, 5, 0)); assert_eq!(Balances::usable_balance(1), 5); assert_ok!(Voting::vote(Origin::signed(1), 3, aye(10, 0))); assert_eq!(tally(3), Tally::from_parts(1, 0, 10)); assert_ok!(Voting::vote(Origin::signed(1), 3, nay(10, 0))); - assert_eq!(tally(3), Tally::from_parts(0, 1, 10)); + assert_eq!(tally(3), Tally::from_parts(0, 1, 0)); assert_eq!(Balances::usable_balance(1), 0); assert_ok!(Voting::remove_vote(Origin::signed(1), None, 3)); @@ -300,19 +300,19 @@ fn voting_balance_gets_locked() { assert_ok!(Voting::vote(Origin::signed(1), 3, aye(2, 5))); assert_eq!(tally(3), Tally::from_parts(10, 0, 2)); assert_ok!(Voting::vote(Origin::signed(1), 3, nay(2, 5))); - assert_eq!(tally(3), Tally::from_parts(0, 10, 2)); + assert_eq!(tally(3), Tally::from_parts(0, 10, 0)); assert_eq!(Balances::usable_balance(1), 8); assert_ok!(Voting::vote(Origin::signed(1), 3, aye(5, 1))); assert_eq!(tally(3), Tally::from_parts(5, 0, 5)); assert_ok!(Voting::vote(Origin::signed(1), 3, nay(5, 1))); - assert_eq!(tally(3), Tally::from_parts(0, 5, 5)); + assert_eq!(tally(3), Tally::from_parts(0, 5, 0)); assert_eq!(Balances::usable_balance(1), 5); assert_ok!(Voting::vote(Origin::signed(1), 3, aye(10, 0))); assert_eq!(tally(3), Tally::from_parts(1, 0, 10)); assert_ok!(Voting::vote(Origin::signed(1), 3, nay(10, 0))); - assert_eq!(tally(3), Tally::from_parts(0, 1, 10)); + assert_eq!(tally(3), Tally::from_parts(0, 1, 0)); assert_eq!(Balances::usable_balance(1), 0); assert_ok!(Voting::remove_vote(Origin::signed(1), None, 3)); @@ -376,10 +376,10 @@ fn classwise_delegation_works() { new_test_ext().execute_with(|| { Polls::set( vec![ - (0, Ongoing(Tally::default(), 0)), - (1, Ongoing(Tally::default(), 1)), - (2, Ongoing(Tally::default(), 2)), - (3, Ongoing(Tally::default(), 2)), + (0, Ongoing(Tally::new(0), 0)), + (1, Ongoing(Tally::new(0), 1)), + (2, Ongoing(Tally::new(0), 2)), + (3, Ongoing(Tally::new(0), 2)), ] .into_iter() .collect(), @@ -403,9 +403,9 @@ fn classwise_delegation_works() { assert_eq!( Polls::get(), vec![ - (0, Ongoing(Tally::from_parts(6, 2, 35), 0)), - (1, Ongoing(Tally::from_parts(6, 2, 35), 1)), - (2, Ongoing(Tally::from_parts(6, 2, 35), 2)), + (0, Ongoing(Tally::from_parts(6, 2, 15), 0)), + (1, Ongoing(Tally::from_parts(6, 2, 15), 1)), + (2, Ongoing(Tally::from_parts(6, 2, 15), 2)), (3, Ongoing(Tally::from_parts(0, 0, 0), 2)), ] .into_iter() @@ -417,10 +417,10 @@ fn classwise_delegation_works() { assert_eq!( Polls::get(), vec![ - (0, Ongoing(Tally::from_parts(6, 2, 35), 0)), - (1, Ongoing(Tally::from_parts(6, 2, 35), 1)), - (2, Ongoing(Tally::from_parts(6, 2, 35), 2)), - (3, Ongoing(Tally::from_parts(0, 6, 15), 2)), + (0, Ongoing(Tally::from_parts(6, 2, 15), 0)), + (1, Ongoing(Tally::from_parts(6, 2, 15), 1)), + (2, Ongoing(Tally::from_parts(6, 2, 15), 2)), + (3, Ongoing(Tally::from_parts(0, 6, 0), 2)), ] .into_iter() .collect() @@ -432,10 +432,10 @@ fn classwise_delegation_works() { assert_eq!( Polls::get(), vec![ - (0, Ongoing(Tally::from_parts(6, 2, 35), 0)), - (1, Ongoing(Tally::from_parts(6, 2, 35), 1)), - (2, Ongoing(Tally::from_parts(1, 7, 35), 2)), - (3, Ongoing(Tally::from_parts(0, 1, 10), 2)), + (0, Ongoing(Tally::from_parts(6, 2, 15), 0)), + (1, Ongoing(Tally::from_parts(6, 2, 15), 1)), + (2, Ongoing(Tally::from_parts(1, 7, 10), 2)), + (3, Ongoing(Tally::from_parts(0, 1, 0), 2)), ] .into_iter() .collect() @@ -451,10 +451,10 @@ fn classwise_delegation_works() { assert_eq!( Polls::get(), vec![ - (0, Ongoing(Tally::from_parts(4, 2, 33), 0)), - (1, Ongoing(Tally::from_parts(4, 2, 33), 1)), - (2, Ongoing(Tally::from_parts(4, 2, 33), 2)), - (3, Ongoing(Tally::from_parts(0, 4, 13), 2)), + (0, Ongoing(Tally::from_parts(4, 2, 13), 0)), + (1, Ongoing(Tally::from_parts(4, 2, 13), 1)), + (2, Ongoing(Tally::from_parts(4, 2, 13), 2)), + (3, Ongoing(Tally::from_parts(0, 4, 0), 2)), ] .into_iter() .collect() @@ -483,10 +483,10 @@ fn classwise_delegation_works() { assert_eq!( Polls::get(), vec![ - (0, Ongoing(Tally::from_parts(7, 2, 36), 0)), - (1, Ongoing(Tally::from_parts(8, 2, 37), 1)), - (2, Ongoing(Tally::from_parts(9, 2, 38), 2)), - (3, Ongoing(Tally::from_parts(0, 9, 18), 2)), + (0, Ongoing(Tally::from_parts(7, 2, 16), 0)), + (1, Ongoing(Tally::from_parts(8, 2, 17), 1)), + (2, Ongoing(Tally::from_parts(9, 2, 18), 2)), + (3, Ongoing(Tally::from_parts(0, 9, 0), 2)), ] .into_iter() .collect() @@ -497,7 +497,7 @@ fn classwise_delegation_works() { #[test] fn redelegation_after_vote_ending_should_keep_lock() { new_test_ext().execute_with(|| { - Polls::set(vec![(0, Ongoing(Tally::default(), 0))].into_iter().collect()); + Polls::set(vec![(0, Ongoing(Tally::new(0), 0))].into_iter().collect()); assert_ok!(Voting::delegate(Origin::signed(1), 0, 2, Conviction::Locked1x, 5)); assert_ok!(Voting::vote(Origin::signed(2), 0, aye(10, 1))); Polls::set(vec![(0, Completed(1, true))].into_iter().collect()); @@ -515,9 +515,9 @@ fn lock_amalgamation_valid_with_multiple_removed_votes() { new_test_ext().execute_with(|| { Polls::set( vec![ - (0, Ongoing(Tally::default(), 0)), - (1, Ongoing(Tally::default(), 0)), - (2, Ongoing(Tally::default(), 0)), + (0, Ongoing(Tally::new(0), 0)), + (1, Ongoing(Tally::new(0), 0)), + (2, Ongoing(Tally::new(0), 0)), ] .into_iter() .collect(), @@ -587,7 +587,7 @@ fn lock_amalgamation_valid_with_multiple_delegations() { #[test] fn lock_amalgamation_valid_with_move_roundtrip_to_delegation() { new_test_ext().execute_with(|| { - Polls::set(vec![(0, Ongoing(Tally::default(), 0))].into_iter().collect()); + Polls::set(vec![(0, Ongoing(Tally::new(0), 0))].into_iter().collect()); assert_ok!(Voting::vote(Origin::signed(1), 0, aye(5, 1))); Polls::set(vec![(0, Completed(1, true))].into_iter().collect()); assert_ok!(Voting::remove_vote(Origin::signed(1), Some(0), 0)); @@ -599,7 +599,7 @@ fn lock_amalgamation_valid_with_move_roundtrip_to_delegation() { assert_ok!(Voting::unlock(Origin::signed(1), 0, 1)); assert_eq!(Balances::usable_balance(1), 0); - Polls::set(vec![(1, Ongoing(Tally::default(), 0))].into_iter().collect()); + Polls::set(vec![(1, Ongoing(Tally::new(0), 0))].into_iter().collect()); assert_ok!(Voting::vote(Origin::signed(1), 1, aye(5, 2))); Polls::set(vec![(1, Completed(1, true))].into_iter().collect()); assert_ok!(Voting::remove_vote(Origin::signed(1), Some(0), 1)); @@ -627,7 +627,7 @@ fn lock_amalgamation_valid_with_move_roundtrip_to_casting() { assert_ok!(Voting::unlock(Origin::signed(1), 0, 1)); assert_eq!(Balances::usable_balance(1), 5); - Polls::set(vec![(0, Ongoing(Tally::default(), 0))].into_iter().collect()); + Polls::set(vec![(0, Ongoing(Tally::new(0), 0))].into_iter().collect()); assert_ok!(Voting::vote(Origin::signed(1), 0, aye(10, 1))); Polls::set(vec![(0, Completed(1, true))].into_iter().collect()); assert_ok!(Voting::remove_vote(Origin::signed(1), Some(0), 0)); @@ -688,9 +688,9 @@ fn lock_aggregation_over_different_classes_with_casting_works() { new_test_ext().execute_with(|| { Polls::set( vec![ - (0, Ongoing(Tally::default(), 0)), - (1, Ongoing(Tally::default(), 1)), - (2, Ongoing(Tally::default(), 2)), + (0, Ongoing(Tally::new(0), 0)), + (1, Ongoing(Tally::new(0), 1)), + (2, Ongoing(Tally::new(0), 2)), ] .into_iter() .collect(), @@ -747,10 +747,10 @@ fn errors_with_vote_work() { assert_ok!(Voting::undelegate(Origin::signed(1), 0)); Polls::set( vec![ - (0, Ongoing(Tally::default(), 0)), - (1, Ongoing(Tally::default(), 0)), - (2, Ongoing(Tally::default(), 0)), - (3, Ongoing(Tally::default(), 0)), + (0, Ongoing(Tally::new(0), 0)), + (1, Ongoing(Tally::new(0), 0)), + (2, Ongoing(Tally::new(0), 0)), + (3, Ongoing(Tally::new(0), 0)), ] .into_iter() .collect(), diff --git a/frame/conviction-voting/src/types.rs b/frame/conviction-voting/src/types.rs index e2b5844ddd5df..2469009855c5f 100644 --- a/frame/conviction-voting/src/types.rs +++ b/frame/conviction-voting/src/types.rs @@ -17,18 +17,16 @@ //! Miscellaneous additional datatypes. -use sp_std::marker::PhantomData; - use codec::{Codec, Decode, Encode, MaxEncodedLen}; use frame_support::{ - traits::VoteTally, CloneNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound, - RuntimeDebugNoBound, + traits::VoteTally, CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, }; use scale_info::TypeInfo; use sp_runtime::{ traits::{Saturating, Zero}, RuntimeDebug, }; +use sp_std::{fmt::Debug, marker::PhantomData}; use super::*; use crate::{AccountVote, Conviction, Vote}; @@ -36,7 +34,6 @@ use crate::{AccountVote, Conviction, Vote}; /// Info regarding an ongoing referendum. #[derive( CloneNoBound, - DefaultNoBound, PartialEqNoBound, EqNoBound, RuntimeDebugNoBound, @@ -46,84 +43,84 @@ use crate::{AccountVote, Conviction, Vote}; MaxEncodedLen, )] #[scale_info(skip_type_params(Total))] -pub struct Tally< - Votes: Clone + Default + PartialEq + Eq + sp_std::fmt::Debug + TypeInfo + Codec, - Total, -> { +pub struct Tally { /// The number of aye votes, expressed in terms of post-conviction lock-vote. pub ayes: Votes, /// The number of nay votes, expressed in terms of post-conviction lock-vote. pub nays: Votes, - /// The amount of funds currently expressing its opinion. Pre-conviction. - pub turnout: Votes, + /// The basic number of aye votes, expressed pre-conviction. + pub support: Votes, /// Dummy. dummy: PhantomData, } impl< - Votes: Clone - + Default - + PartialEq - + Eq - + sp_std::fmt::Debug - + Copy - + AtLeast32BitUnsigned - + TypeInfo - + Codec, + Votes: Clone + Default + PartialEq + Eq + Debug + Copy + AtLeast32BitUnsigned + TypeInfo + Codec, Total: Get, - > VoteTally for Tally + Class, + > VoteTally for Tally { - fn ayes(&self) -> Votes { + fn new(_: Class) -> Self { + Self { ayes: Zero::zero(), nays: Zero::zero(), support: Zero::zero(), dummy: PhantomData } + } + + fn ayes(&self, _: Class) -> Votes { self.ayes } - fn turnout(&self) -> Perbill { - Perbill::from_rational(self.turnout, Total::get()) + fn support(&self, _: Class) -> Perbill { + Perbill::from_rational(self.support, Total::get()) } - fn approval(&self) -> Perbill { + fn approval(&self, _: Class) -> Perbill { Perbill::from_rational(self.ayes, self.ayes.saturating_add(self.nays)) } #[cfg(feature = "runtime-benchmarks")] - fn unanimity() -> Self { - Self { ayes: Total::get(), nays: Zero::zero(), turnout: Total::get(), dummy: PhantomData } + fn unanimity(_: Class) -> Self { + Self { ayes: Total::get(), nays: Zero::zero(), support: Total::get(), dummy: PhantomData } + } + + #[cfg(feature = "runtime-benchmarks")] + fn rejection(_: Class) -> Self { + Self { ayes: Zero::zero(), nays: Total::get(), support: Total::get(), dummy: PhantomData } } #[cfg(feature = "runtime-benchmarks")] - fn from_requirements(turnout: Perbill, approval: Perbill) -> Self { - let turnout = turnout.mul_ceil(Total::get()); - let ayes = approval.mul_ceil(turnout); - Self { ayes, nays: turnout - ayes, turnout, dummy: PhantomData } + fn from_requirements(support: Perbill, approval: Perbill, _: Class) -> Self { + let support = support.mul_ceil(Total::get()); + let ayes = approval.mul_ceil(support); + Self { ayes, nays: support - ayes, support, dummy: PhantomData } } } impl< - Votes: Clone - + Default - + PartialEq - + Eq - + sp_std::fmt::Debug - + Copy - + AtLeast32BitUnsigned - + TypeInfo - + Codec, + Votes: Clone + Default + PartialEq + Eq + Debug + Copy + AtLeast32BitUnsigned + TypeInfo + Codec, Total: Get, > Tally { /// Create a new tally. - pub fn new(vote: Vote, balance: Votes) -> Self { + pub fn from_vote(vote: Vote, balance: Votes) -> Self { let Delegations { votes, capital } = vote.conviction.votes(balance); Self { ayes: if vote.aye { votes } else { Zero::zero() }, nays: if vote.aye { Zero::zero() } else { votes }, - turnout: capital, + support: capital, dummy: PhantomData, } } - pub fn from_parts(ayes: Votes, nays: Votes, turnout: Votes) -> Self { - Self { ayes, nays, turnout, dummy: PhantomData } + pub fn from_parts( + ayes_with_conviction: Votes, + nays_with_conviction: Votes, + ayes: Votes, + ) -> Self { + Self { + ayes: ayes_with_conviction, + nays: nays_with_conviction, + support: ayes, + dummy: PhantomData, + } } /// Add an account's vote into the tally. @@ -131,16 +128,18 @@ impl< 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)?, + true => { + self.support = self.support.checked_add(&capital)?; + self.ayes = self.ayes.checked_add(&votes)? + }, false => self.nays = self.nays.checked_add(&votes)?, } }, AccountVote::Split { aye, nay } => { let aye = Conviction::None.votes(aye); let nay = Conviction::None.votes(nay); - self.turnout = self.turnout.checked_add(&aye.capital)?.checked_add(&nay.capital)?; + self.support = self.support.checked_add(&aye.capital)?; self.ayes = self.ayes.checked_add(&aye.votes)?; self.nays = self.nays.checked_add(&nay.votes)?; }, @@ -153,16 +152,18 @@ impl< 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)?, + true => { + self.support = self.support.checked_sub(&capital)?; + self.ayes = self.ayes.checked_sub(&votes)? + }, false => self.nays = self.nays.checked_sub(&votes)?, } }, AccountVote::Split { aye, nay } => { let aye = Conviction::None.votes(aye); let nay = Conviction::None.votes(nay); - self.turnout = self.turnout.checked_sub(&aye.capital)?.checked_sub(&nay.capital)?; + self.support = self.support.checked_sub(&aye.capital)?; self.ayes = self.ayes.checked_sub(&aye.votes)?; self.nays = self.nays.checked_sub(&nay.votes)?; }, @@ -172,18 +173,22 @@ impl< /// Increment some amount of votes. pub fn increase(&mut self, approve: bool, delegations: Delegations) { - self.turnout = self.turnout.saturating_add(delegations.capital); match approve { - true => self.ayes = self.ayes.saturating_add(delegations.votes), + true => { + self.support = self.support.saturating_add(delegations.capital); + self.ayes = self.ayes.saturating_add(delegations.votes); + }, false => self.nays = self.nays.saturating_add(delegations.votes), } } /// Decrement some amount of votes. pub fn reduce(&mut self, approve: bool, delegations: Delegations) { - self.turnout = self.turnout.saturating_sub(delegations.capital); match approve { - true => self.ayes = self.ayes.saturating_sub(delegations.votes), + true => { + self.support = self.support.saturating_sub(delegations.capital); + self.ayes = self.ayes.saturating_sub(delegations.votes); + }, false => self.nays = self.nays.saturating_sub(delegations.votes), } } @@ -196,7 +201,7 @@ impl< pub struct Delegations { /// The number of votes (this is post-conviction). pub votes: Balance, - /// The amount of raw capital, used for the turnout. + /// The amount of raw capital, used for the support. pub capital: Balance, } diff --git a/frame/identity/src/tests.rs b/frame/identity/src/tests.rs index 8cb0563ebeaa1..6066f176a6106 100644 --- a/frame/identity/src/tests.rs +++ b/frame/identity/src/tests.rs @@ -23,7 +23,7 @@ use crate as pallet_identity; use codec::{Decode, Encode}; use frame_support::{ assert_noop, assert_ok, ord_parameter_types, parameter_types, - traits::{ConstU32, ConstU64, EnsureOneOf}, + traits::{ConstU32, ConstU64, EitherOfDiverse}, BoundedVec, }; use frame_system::{EnsureRoot, EnsureSignedBy}; @@ -100,8 +100,8 @@ ord_parameter_types! { pub const One: u64 = 1; pub const Two: u64 = 2; } -type EnsureOneOrRoot = EnsureOneOf, EnsureSignedBy>; -type EnsureTwoOrRoot = EnsureOneOf, EnsureSignedBy>; +type EnsureOneOrRoot = EitherOfDiverse, EnsureSignedBy>; +type EnsureTwoOrRoot = EitherOfDiverse, EnsureSignedBy>; impl pallet_identity::Config for Test { type Event = Event; type Currency = Balances; diff --git a/frame/referenda/Cargo.toml b/frame/referenda/Cargo.toml index 75888c6d14f3d..ef3d5fe5a8e06 100644 --- a/frame/referenda/Cargo.toml +++ b/frame/referenda/Cargo.toml @@ -19,6 +19,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = ] } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", features = ["derive"], optional = true } +sp-arithmetic = { version = "5.0.0", default-features = false, path = "../../primitives/arithmetic" } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } @@ -39,6 +40,8 @@ std = [ "codec/std", "frame-benchmarking/std", "frame-support/std", + "sp-runtime/std", + "sp-arithmetic/std", "frame-system/std", "scale-info/std", "serde", diff --git a/frame/referenda/src/benchmarking.rs b/frame/referenda/src/benchmarking.rs index 87b13868db9d9..e4cbf2ca52e7c 100644 --- a/frame/referenda/src/benchmarking.rs +++ b/frame/referenda/src/benchmarking.rs @@ -101,27 +101,27 @@ fn info(index: ReferendumIndex) -> &'static TrackInfoOf { } fn make_passing_after(index: ReferendumIndex, period_portion: Perbill) { - let turnout = info::(index).min_turnout.threshold(period_portion); + let support = info::(index).min_support.threshold(period_portion); let approval = info::(index).min_approval.threshold(period_portion); Referenda::::access_poll(index, |status| { - if let PollStatus::Ongoing(tally, ..) = status { - *tally = T::Tally::from_requirements(turnout, approval); + if let PollStatus::Ongoing(tally, class) = status { + *tally = T::Tally::from_requirements(support, approval, class); } }); } fn make_passing(index: ReferendumIndex) { Referenda::::access_poll(index, |status| { - if let PollStatus::Ongoing(tally, ..) = status { - *tally = T::Tally::unanimity(); + if let PollStatus::Ongoing(tally, class) = status { + *tally = T::Tally::unanimity(class); } }); } fn make_failing(index: ReferendumIndex) { Referenda::::access_poll(index, |status| { - if let PollStatus::Ongoing(tally, ..) = status { - *tally = T::Tally::default(); + if let PollStatus::Ongoing(tally, class) = status { + *tally = T::Tally::rejection(class); } }); } @@ -501,6 +501,7 @@ benchmarks! { let (_caller, index) = create_referendum::(); place_deposit::(index); skip_prepare_period::(index); + make_failing::(index); nudge::(index); skip_decision_period::(index); }: nudge_referendum(RawOrigin::Root, index) diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index b53be191d3525..e3f31bc411328 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -41,7 +41,7 @@ //! In order to become concluded, one of three things must happen: //! - The referendum should remain in an unbroken _Passing_ state for a period of time. This //! is known as the _Confirmation Period_ and is determined by the track. A referendum is considered -//! _Passing_ when there is a sufficiently high turnout and approval, given the amount of time it +//! _Passing_ when there is a sufficiently high support and approval, given the amount of time it //! has been being decided. Generally the threshold for what counts as being "sufficiently high" //! will reduce over time. The curves setting these thresholds are determined by the track. In this //! case, the referendum is considered _Approved_ and the proposal is scheduled for dispatch. @@ -54,6 +54,10 @@ //! //! Once a referendum is concluded, the decision deposit may be refunded. //! +//! ## Terms +//! - *Support*: The number of aye-votes, pre-conviction, as a proportion of the total number of +//! pre-conviction votes able to be cast in the population. +//! //! - [`Config`] //! - [`Call`] @@ -148,7 +152,12 @@ pub mod pallet { /// The counting type for votes. Usually just balance. type Votes: AtLeast32BitUnsigned + Copy + Parameter + Member; /// The tallying type. - type Tally: VoteTally + Default + Clone + Codec + Eq + Debug + TypeInfo; + type Tally: VoteTally> + + Clone + + Codec + + Eq + + Debug + + TypeInfo; // Constants /// The minimum amount to be used as a deposit for a public referendum proposal. @@ -369,7 +378,7 @@ pub mod pallet { submission_deposit, decision_deposit: None, deciding: None, - tally: Default::default(), + tally: TallyOf::::new(track), in_queue: false, alarm: Self::set_alarm(nudge_call, now.saturating_add(T::UndecidingTimeout::get())), }; @@ -613,7 +622,7 @@ impl, I: 'static> Polling for Pallet { submission_deposit: Deposit { who: dummy_account_id, amount: Zero::zero() }, decision_deposit: None, deciding: None, - tally: Default::default(), + tally: TallyOf::::new(class), in_queue: false, alarm: None, }; @@ -723,8 +732,9 @@ impl, I: 'static> Pallet { &status.tally, Zero::zero(), track.decision_period, - &track.min_turnout, + &track.min_support, &track.min_approval, + status.track, ); status.in_queue = false; Self::deposit_event(Event::::DecisionStarted { @@ -740,7 +750,7 @@ impl, I: 'static> Pallet { None }; let deciding_status = DecidingStatus { since: now, confirming }; - let alarm = Self::decision_time(&deciding_status, &status.tally, track); + let alarm = Self::decision_time(&deciding_status, &status.tally, status.track, track); status.deciding = Some(deciding_status); let branch = if is_passing { BeginDecidingBranch::Passing } else { BeginDecidingBranch::Failing }; @@ -765,7 +775,7 @@ impl, I: 'static> Pallet { (r.0, r.1.into()) } else { // Add to queue. - let item = (index, status.tally.ayes()); + let item = (index, status.tally.ayes(status.track)); status.in_queue = true; TrackQueue::::mutate(status.track, |q| q.insert_sorted_by_key(item, |x| x.1)); (None, ServiceBranch::Queued) @@ -872,7 +882,7 @@ impl, I: 'static> Pallet { // Are we already queued for deciding? if status.in_queue { // Does our position in the queue need updating? - let ayes = status.tally.ayes(); + let ayes = status.tally.ayes(status.track); let mut queue = TrackQueue::::get(status.track); let maybe_old_pos = queue.iter().position(|(x, _)| *x == index); let new_pos = queue.binary_search_by_key(&ayes, |x| x.1).unwrap_or_else(|x| x); @@ -930,8 +940,9 @@ impl, I: 'static> Pallet { &status.tally, now.saturating_sub(deciding.since), track.decision_period, - &track.min_turnout, + &track.min_support, &track.min_approval, + status.track, ); branch = if is_passing { match deciding.confirming { @@ -996,7 +1007,7 @@ impl, I: 'static> Pallet { ServiceBranch::ContinueNotConfirming } }; - alarm = Self::decision_time(deciding, &status.tally, track); + alarm = Self::decision_time(deciding, &status.tally, status.track, track); }, } @@ -1009,15 +1020,16 @@ impl, I: 'static> Pallet { fn decision_time( deciding: &DecidingStatusOf, tally: &T::Tally, + track_id: TrackIdOf, track: &TrackInfoOf, ) -> T::BlockNumber { deciding.confirming.unwrap_or_else(|| { // Set alarm to the point where the current voting would make it pass. - let approval = tally.approval(); - let turnout = tally.turnout(); + let approval = tally.approval(track_id); + let support = tally.support(track_id); let until_approval = track.min_approval.delay(approval); - let until_turnout = track.min_turnout.delay(turnout); - let offset = until_turnout.max(until_approval); + let until_support = track.min_support.delay(support); + let offset = until_support.max(until_approval); deciding.since.saturating_add(offset * track.decision_period) }) } @@ -1062,16 +1074,18 @@ impl, I: 'static> Pallet { } /// Determine whether the given `tally` would result in a referendum passing at `elapsed` blocks - /// into a total decision `period`, given the two curves for `turnout_needed` and + /// into a total decision `period`, given the two curves for `support_needed` and /// `approval_needed`. fn is_passing( tally: &T::Tally, elapsed: T::BlockNumber, period: T::BlockNumber, - turnout_needed: &Curve, + support_needed: &Curve, approval_needed: &Curve, + id: TrackIdOf, ) -> bool { let x = Perbill::from_rational(elapsed.min(period), period); - turnout_needed.passing(x, tally.turnout()) && approval_needed.passing(x, tally.approval()) + support_needed.passing(x, tally.support(id)) && + approval_needed.passing(x, tally.approval(id)) } } diff --git a/frame/referenda/src/mock.rs b/frame/referenda/src/mock.rs index 1b0bbba24bbe6..a3026ce78e986 100644 --- a/frame/referenda/src/mock.rs +++ b/frame/referenda/src/mock.rs @@ -160,12 +160,14 @@ impl TracksInfo for TestTracksInfo { confirm_period: 2, min_enactment_period: 4, min_approval: Curve::LinearDecreasing { - begin: Perbill::from_percent(100), - delta: Perbill::from_percent(50), + length: Perbill::from_percent(100), + floor: Perbill::from_percent(50), + ceil: Perbill::from_percent(100), }, - min_turnout: Curve::LinearDecreasing { - begin: Perbill::from_percent(100), - delta: Perbill::from_percent(100), + min_support: Curve::LinearDecreasing { + length: Perbill::from_percent(100), + floor: Perbill::from_percent(0), + ceil: Perbill::from_percent(100), }, }, ), @@ -180,12 +182,14 @@ impl TracksInfo for TestTracksInfo { confirm_period: 1, min_enactment_period: 2, min_approval: Curve::LinearDecreasing { - begin: Perbill::from_percent(55), - delta: Perbill::from_percent(5), + length: Perbill::from_percent(100), + floor: Perbill::from_percent(95), + ceil: Perbill::from_percent(100), }, - min_turnout: Curve::LinearDecreasing { - begin: Perbill::from_percent(10), - delta: Perbill::from_percent(10), + min_support: Curve::LinearDecreasing { + length: Perbill::from_percent(100), + floor: Perbill::from_percent(90), + ceil: Perbill::from_percent(100), }, }, ), @@ -241,35 +245,48 @@ pub fn new_test_ext_execute_with_cond(execute: impl FnOnce(bool) -> () + Clone) new_test_ext().execute_with(|| execute(true)); } -#[derive(Encode, Debug, Decode, TypeInfo, Eq, PartialEq, Clone, Default, MaxEncodedLen)] +#[derive(Encode, Debug, Decode, TypeInfo, Eq, PartialEq, Clone, MaxEncodedLen)] pub struct Tally { pub ayes: u32, pub nays: u32, } -impl VoteTally for Tally { - fn ayes(&self) -> u32 { +impl VoteTally for Tally { + fn new(_: Class) -> Self { + Self { ayes: 0, nays: 0 } + } + + fn ayes(&self, _: Class) -> u32 { self.ayes } - fn turnout(&self) -> Perbill { - Perbill::from_percent(self.ayes + self.nays) + fn support(&self, _: Class) -> Perbill { + Perbill::from_percent(self.ayes) } - fn approval(&self) -> Perbill { - Perbill::from_rational(self.ayes, self.ayes + self.nays) + fn approval(&self, _: Class) -> Perbill { + if self.ayes + self.nays > 0 { + Perbill::from_rational(self.ayes, self.ayes + self.nays) + } else { + Perbill::zero() + } } #[cfg(feature = "runtime-benchmarks")] - fn unanimity() -> Self { + fn unanimity(_: Class) -> Self { Self { ayes: 100, nays: 0 } } #[cfg(feature = "runtime-benchmarks")] - fn from_requirements(turnout: Perbill, approval: Perbill) -> Self { - let turnout = turnout.mul_ceil(100u32); - let ayes = approval.mul_ceil(turnout); - Self { ayes, nays: turnout - ayes } + fn rejection(_: Class) -> Self { + Self { ayes: 0, nays: 100 } + } + + #[cfg(feature = "runtime-benchmarks")] + fn from_requirements(support: Perbill, approval: Perbill, _: Class) -> Self { + let ayes = support.mul_ceil(100u32); + let nays = ((ayes as u64) * 1_000_000_000u64 / approval.deconstruct() as u64) as u32 - ayes; + Self { ayes, nays } } } diff --git a/frame/referenda/src/tests.rs b/frame/referenda/src/tests.rs index 96edd4ce879ce..8134e024dda39 100644 --- a/frame/referenda/src/tests.rs +++ b/frame/referenda/src/tests.rs @@ -504,12 +504,14 @@ fn set_balance_proposal_is_correctly_filtered_out() { #[test] fn curve_handles_all_inputs() { - let test_curve = Curve::LinearDecreasing { begin: Perbill::zero(), delta: Perbill::zero() }; + let test_curve = Curve::LinearDecreasing { + length: Perbill::one(), + floor: Perbill::zero(), + ceil: Perbill::from_percent(100), + }; let delay = test_curve.delay(Perbill::zero()); - assert_eq!(delay, Perbill::zero()); - - let test_curve = Curve::LinearDecreasing { begin: Perbill::zero(), delta: Perbill::one() }; + assert_eq!(delay, Perbill::one()); let threshold = test_curve.threshold(Perbill::one()); assert_eq!(threshold, Perbill::zero()); diff --git a/frame/referenda/src/types.rs b/frame/referenda/src/types.rs index 5e0361c8fe160..27b3c6a5a9d59 100644 --- a/frame/referenda/src/types.rs +++ b/frame/referenda/src/types.rs @@ -21,7 +21,8 @@ use super::*; use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; use frame_support::{traits::schedule::Anon, Parameter}; use scale_info::TypeInfo; -use sp_runtime::RuntimeDebug; +use sp_arithmetic::{Rounding::*, SignedRounding::*}; +use sp_runtime::{FixedI64, PerThing, RuntimeDebug}; use sp_std::fmt::Debug; pub type BalanceOf = @@ -91,40 +92,6 @@ impl> InsertSorted for BoundedVec { } } -#[cfg(test)] -mod tests { - use super::*; - use frame_support::traits::ConstU32; - - #[test] - fn insert_sorted_works() { - let mut b: BoundedVec> = vec![20, 30, 40].try_into().unwrap(); - assert!(b.insert_sorted_by_key(10, |&x| x)); - assert_eq!(&b[..], &[10, 20, 30, 40][..]); - - assert!(b.insert_sorted_by_key(60, |&x| x)); - assert_eq!(&b[..], &[10, 20, 30, 40, 60][..]); - - assert!(b.insert_sorted_by_key(50, |&x| x)); - assert_eq!(&b[..], &[10, 20, 30, 40, 50, 60][..]); - - assert!(!b.insert_sorted_by_key(9, |&x| x)); - assert_eq!(&b[..], &[10, 20, 30, 40, 50, 60][..]); - - assert!(b.insert_sorted_by_key(11, |&x| x)); - assert_eq!(&b[..], &[11, 20, 30, 40, 50, 60][..]); - - assert!(b.insert_sorted_by_key(21, |&x| x)); - assert_eq!(&b[..], &[20, 21, 30, 40, 50, 60][..]); - - assert!(b.insert_sorted_by_key(61, |&x| x)); - assert_eq!(&b[..], &[21, 30, 40, 50, 60, 61][..]); - - assert!(b.insert_sorted_by_key(51, |&x| x)); - assert_eq!(&b[..], &[30, 40, 50, 51, 60, 61][..]); - } -} - #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct DecidingStatus { /// When this referendum began being "decided". If confirming, then the @@ -143,7 +110,7 @@ pub struct Deposit { #[derive(Clone, Encode, TypeInfo)] pub struct TrackInfo { - /// Name of this track. TODO was &'static str + /// Name of this track. pub name: &'static str, /// A limit for the number of referenda on this track that can be being decided at once. /// For Root origin this should generally be just one. @@ -161,9 +128,9 @@ pub struct TrackInfo { /// Minimum aye votes as percentage of overall conviction-weighted votes needed for /// approval as a function of time into decision period. pub min_approval: Curve, - /// Minimum turnout as percentage of overall population that is needed for - /// approval as a function of time into decision period. - pub min_turnout: Curve, + /// Minimum pre-conviction aye-votes ("support") as percentage of overall population that is + /// needed for approval as a function of time into decision period. + pub min_support: Curve, } /// Information on the voting tracks. @@ -282,21 +249,186 @@ impl< #[derive(Clone, Eq, PartialEq, Encode, Decode, TypeInfo, MaxEncodedLen)] #[cfg_attr(not(feature = "std"), derive(RuntimeDebug))] pub enum Curve { - /// Linear curve starting at `(0, begin)`, ending at `(period, begin - delta)`. - LinearDecreasing { begin: Perbill, delta: Perbill }, + /// Linear curve starting at `(0, ceil)`, proceeding linearly to `(length, floor)`, then + /// remaining at `floor` until the end of the period. + LinearDecreasing { length: Perbill, floor: Perbill, ceil: Perbill }, + /// Stepped curve, beginning at `(0, begin)`, then remaining constant for `period`, at which + /// point it steps down to `(period, begin - step)`. It then remains constant for another + /// `period` before stepping down to `(period * 2, begin - step * 2)`. This pattern continues + /// but the `y` component has a lower limit of `end`. + SteppedDecreasing { begin: Perbill, end: Perbill, step: Perbill, period: Perbill }, + /// A recipocal (`K/(x+S)-T`) curve: `factor` is `K` and `x_offset` is `S`, `y_offset` is `T`. + Reciprocal { factor: FixedI64, x_offset: FixedI64, y_offset: FixedI64 }, +} + +/// Calculate the quadratic solution for the given curve. +/// +/// WARNING: This is a `const` function designed for convenient use at build time and +/// will panic on overflow. Ensure that any inputs are sensible. +const fn pos_quad_solution(a: FixedI64, b: FixedI64, c: FixedI64) -> FixedI64 { + const TWO: FixedI64 = FixedI64::from_u32(2); + const FOUR: FixedI64 = FixedI64::from_u32(4); + b.neg().add(b.mul(b).sub(FOUR.mul(a).mul(c)).sqrt()).div(TWO.mul(a)) } impl Curve { + /// Create a `Curve::Linear` instance from a high-level description. + /// + /// WARNING: This is a `const` function designed for convenient use at build time and + /// will panic on overflow. Ensure that any inputs are sensible. + pub const fn make_linear(length: u128, period: u128, floor: FixedI64, ceil: FixedI64) -> Curve { + let length = FixedI64::from_rational(length, period).into_perbill(); + let floor = floor.into_perbill(); + let ceil = ceil.into_perbill(); + Curve::LinearDecreasing { length, floor, ceil } + } + + /// Create a `Curve::Reciprocal` instance from a high-level description. + /// + /// WARNING: This is a `const` function designed for convenient use at build time and + /// will panic on overflow. Ensure that any inputs are sensible. + pub const fn make_reciprocal( + delay: u128, + period: u128, + level: FixedI64, + floor: FixedI64, + ceil: FixedI64, + ) -> Curve { + let delay = FixedI64::from_rational(delay, period).into_perbill(); + let mut bounds = ( + ( + FixedI64::from_u32(0), + Self::reciprocal_from_parts(FixedI64::from_u32(0), floor, ceil), + FixedI64::from_inner(i64::max_value()), + ), + ( + FixedI64::from_u32(1), + Self::reciprocal_from_parts(FixedI64::from_u32(1), floor, ceil), + FixedI64::from_inner(i64::max_value()), + ), + ); + const TWO: FixedI64 = FixedI64::from_u32(2); + while (bounds.1).0.sub((bounds.0).0).into_inner() > 1 { + let factor = (bounds.0).0.add((bounds.1).0).div(TWO); + let curve = Self::reciprocal_from_parts(factor, floor, ceil); + let curve_level = FixedI64::from_perbill(curve.const_threshold(delay)); + if curve_level.into_inner() > level.into_inner() { + bounds = (bounds.0, (factor, curve, curve_level.sub(level))); + } else { + bounds = ((factor, curve, level.sub(curve_level)), bounds.1); + } + } + if (bounds.0).2.into_inner() < (bounds.1).2.into_inner() { + (bounds.0).1 + } else { + (bounds.1).1 + } + } + + /// Create a `Curve::Reciprocal` instance from basic parameters. + /// + /// WARNING: This is a `const` function designed for convenient use at build time and + /// will panic on overflow. Ensure that any inputs are sensible. + const fn reciprocal_from_parts(factor: FixedI64, floor: FixedI64, ceil: FixedI64) -> Self { + let delta = ceil.sub(floor); + let x_offset = pos_quad_solution(delta, delta, factor.neg()); + let y_offset = floor.sub(factor.div(FixedI64::from_u32(1).add(x_offset))); + Curve::Reciprocal { factor, x_offset, y_offset } + } + + /// Print some info on the curve. + #[cfg(feature = "std")] + pub fn info(&self, days: u32, name: impl std::fmt::Display) { + let hours = days * 24; + println!("Curve {} := {:?}:", name, self); + println!(" t + 0h: {:?}", self.threshold(Perbill::zero())); + println!(" t + 1h: {:?}", self.threshold(Perbill::from_rational(1, hours))); + println!(" t + 2h: {:?}", self.threshold(Perbill::from_rational(2, hours))); + println!(" t + 3h: {:?}", self.threshold(Perbill::from_rational(3, hours))); + println!(" t + 6h: {:?}", self.threshold(Perbill::from_rational(6, hours))); + println!(" t + 12h: {:?}", self.threshold(Perbill::from_rational(12, hours))); + println!(" t + 24h: {:?}", self.threshold(Perbill::from_rational(24, hours))); + let mut l = 0; + for &(n, d) in [(1, 12), (1, 8), (1, 4), (1, 2), (3, 4), (1, 1)].iter() { + let t = days * n / d; + if t != l { + println!(" t + {}d: {:?}", t, self.threshold(Perbill::from_rational(t, days))); + l = t; + } + } + let t = |p: Perbill| -> std::string::String { + if p.is_one() { + "never".into() + } else { + let minutes = p * (hours * 60); + if minutes < 60 { + format!("{} minutes", minutes) + } else if minutes < 8 * 60 && minutes % 60 != 0 { + format!("{} hours {} minutes", minutes / 60, minutes % 60) + } else if minutes < 72 * 60 { + format!("{} hours", minutes / 60) + } else if minutes / 60 % 24 == 0 { + format!("{} days", minutes / 60 / 24) + } else { + format!("{} days {} hours", minutes / 60 / 24, minutes / 60 % 24) + } + } + }; + if self.delay(Perbill::from_percent(49)) < Perbill::one() { + println!(" 30% threshold: {}", t(self.delay(Perbill::from_percent(30)))); + println!(" 10% threshold: {}", t(self.delay(Perbill::from_percent(10)))); + println!(" 3% threshold: {}", t(self.delay(Perbill::from_percent(3)))); + println!(" 1% threshold: {}", t(self.delay(Perbill::from_percent(1)))); + println!(" 0.1% threshold: {}", t(self.delay(Perbill::from_rational(1u32, 1_000)))); + println!(" 0.01% threshold: {}", t(self.delay(Perbill::from_rational(1u32, 10_000)))); + } else { + println!( + " 99.9% threshold: {}", + t(self.delay(Perbill::from_rational(999u32, 1_000))) + ); + println!(" 99% threshold: {}", t(self.delay(Perbill::from_percent(99)))); + println!(" 95% threshold: {}", t(self.delay(Perbill::from_percent(95)))); + println!(" 90% threshold: {}", t(self.delay(Perbill::from_percent(90)))); + println!(" 75% threshold: {}", t(self.delay(Perbill::from_percent(75)))); + println!(" 60% threshold: {}", t(self.delay(Perbill::from_percent(60)))); + } + } + /// Determine the `y` value for the given `x` value. pub(crate) fn threshold(&self, x: Perbill) -> Perbill { match self { - Self::LinearDecreasing { begin, delta } => *begin - (*delta * x).min(*begin), + Self::LinearDecreasing { length, floor, ceil } => + *ceil - (x.min(*length).saturating_div(*length, Down) * (*ceil - *floor)), + Self::SteppedDecreasing { begin, end, step, period } => + (*begin - (step.int_mul(x.int_div(*period))).min(*begin)).max(*end), + Self::Reciprocal { factor, x_offset, y_offset } => factor + .checked_rounding_div(FixedI64::from(x) + *x_offset, Low) + .map(|yp| (yp + *y_offset).into_clamped_perthing()) + .unwrap_or_else(Perbill::one), + } + } + + /// Determine the `y` value for the given `x` value. + /// + /// This is a partial implementation designed only for use in const functions. + const fn const_threshold(&self, x: Perbill) -> Perbill { + match self { + Self::Reciprocal { factor, x_offset, y_offset } => { + match factor.checked_rounding_div(FixedI64::from_perbill(x).add(*x_offset), Low) { + Some(yp) => (yp.add(*y_offset)).into_perbill(), + None => Perbill::one(), + } + }, + _ => panic!("const_threshold cannot be used on this curve"), } } /// Determine the smallest `x` value such that `passing` returns `true` when passed along with /// the given `y` value. /// + /// If `passing` never returns `true` for any value of `x` when paired with `y`, then + /// `Perbill::one` may be returned. + /// /// ```nocompile /// let c = Curve::LinearDecreasing { begin: Perbill::one(), delta: Perbill::one() }; /// // ^^^ Can be any curve. @@ -307,12 +439,27 @@ impl Curve { /// ``` pub fn delay(&self, y: Perbill) -> Perbill { match self { - Self::LinearDecreasing { begin, delta } => - if delta.is_zero() { - *delta + Self::LinearDecreasing { length, floor, ceil } => + if y < *floor { + Perbill::one() + } else if y > *ceil { + Perbill::zero() + } else { + (*ceil - y).saturating_div(*ceil - *floor, Up).saturating_mul(*length) + }, + Self::SteppedDecreasing { begin, end, step, period } => + if y < *end { + Perbill::one() } else { - (*begin - y.min(*begin)).min(*delta) / *delta + period.int_mul((*begin - y.min(*begin) + step.less_epsilon()).int_div(*step)) }, + Self::Reciprocal { factor, x_offset, y_offset } => { + let y = FixedI64::from(y); + let maybe_term = factor.checked_rounding_div(y - *y_offset, High); + maybe_term + .and_then(|term| (term - *x_offset).try_into_perthing().ok()) + .unwrap_or_else(Perbill::one) + }, } } @@ -326,14 +473,176 @@ impl Curve { impl Debug for Curve { fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { match self { - Self::LinearDecreasing { begin, delta } => { + Self::LinearDecreasing { length, floor, ceil } => { + write!( + f, + "Linear[(0%, {:?}) -> ({:?}, {:?}) -> (100%, {:?})]", + ceil, length, floor, floor, + ) + }, + Self::SteppedDecreasing { begin, end, step, period } => { + write!( + f, + "Stepped[(0%, {:?}) -> (100%, {:?}) by ({:?}, {:?})]", + begin, end, period, step, + ) + }, + Self::Reciprocal { factor, x_offset, y_offset } => { write!( f, - "Linear[(0%, {}%) -> (100%, {}%)]", - *begin * 100u32, - (*begin - *delta) * 100u32, + "Reciprocal[factor of {:?}, x_offset of {:?}, y_offset of {:?}]", + factor, x_offset, y_offset, ) }, } } } + +#[cfg(test)] +mod tests { + use super::*; + use frame_support::traits::ConstU32; + use sp_runtime::PerThing; + + const fn percent(x: u128) -> FixedI64 { + FixedI64::from_rational(x, 100) + } + + const TIP_APP: Curve = Curve::make_linear(10, 28, percent(50), percent(100)); + const TIP_SUP: Curve = Curve::make_reciprocal(1, 28, percent(4), percent(0), percent(50)); + const ROOT_APP: Curve = Curve::make_reciprocal(4, 28, percent(80), percent(50), percent(100)); + const ROOT_SUP: Curve = Curve::make_linear(28, 28, percent(0), percent(50)); + const WHITE_APP: Curve = + Curve::make_reciprocal(16, 28 * 24, percent(96), percent(50), percent(100)); + const WHITE_SUP: Curve = Curve::make_reciprocal(1, 28, percent(20), percent(10), percent(50)); + const SMALL_APP: Curve = Curve::make_linear(10, 28, percent(50), percent(100)); + const SMALL_SUP: Curve = Curve::make_reciprocal(8, 28, percent(1), percent(0), percent(50)); + const MID_APP: Curve = Curve::make_linear(17, 28, percent(50), percent(100)); + const MID_SUP: Curve = Curve::make_reciprocal(12, 28, percent(1), percent(0), percent(50)); + const BIG_APP: Curve = Curve::make_linear(23, 28, percent(50), percent(100)); + const BIG_SUP: Curve = Curve::make_reciprocal(16, 28, percent(1), percent(0), percent(50)); + const HUGE_APP: Curve = Curve::make_linear(28, 28, percent(50), percent(100)); + const HUGE_SUP: Curve = Curve::make_reciprocal(20, 28, percent(1), percent(0), percent(50)); + const PARAM_APP: Curve = Curve::make_reciprocal(4, 28, percent(80), percent(50), percent(100)); + const PARAM_SUP: Curve = Curve::make_reciprocal(7, 28, percent(10), percent(0), percent(50)); + const ADMIN_APP: Curve = Curve::make_linear(17, 28, percent(50), percent(100)); + const ADMIN_SUP: Curve = Curve::make_reciprocal(12, 28, percent(1), percent(0), percent(50)); + + // TODO: ceil for linear. + + #[test] + #[should_panic] + fn check_curves() { + TIP_APP.info(28u32, "Tip Approval"); + TIP_SUP.info(28u32, "Tip Support"); + ROOT_APP.info(28u32, "Root Approval"); + ROOT_SUP.info(28u32, "Root Support"); + WHITE_APP.info(28u32, "Whitelist Approval"); + WHITE_SUP.info(28u32, "Whitelist Support"); + SMALL_APP.info(28u32, "Small Spend Approval"); + SMALL_SUP.info(28u32, "Small Spend Support"); + MID_APP.info(28u32, "Mid Spend Approval"); + MID_SUP.info(28u32, "Mid Spend Support"); + BIG_APP.info(28u32, "Big Spend Approval"); + BIG_SUP.info(28u32, "Big Spend Support"); + HUGE_APP.info(28u32, "Huge Spend Approval"); + HUGE_SUP.info(28u32, "Huge Spend Support"); + PARAM_APP.info(28u32, "Mid-tier Parameter Change Approval"); + PARAM_SUP.info(28u32, "Mid-tier Parameter Change Support"); + ADMIN_APP.info(28u32, "Admin (e.g. Cancel Slash) Approval"); + ADMIN_SUP.info(28u32, "Admin (e.g. Cancel Slash) Support"); + assert!(false); + } + + #[test] + fn insert_sorted_works() { + let mut b: BoundedVec> = vec![20, 30, 40].try_into().unwrap(); + assert!(b.insert_sorted_by_key(10, |&x| x)); + assert_eq!(&b[..], &[10, 20, 30, 40][..]); + + assert!(b.insert_sorted_by_key(60, |&x| x)); + assert_eq!(&b[..], &[10, 20, 30, 40, 60][..]); + + assert!(b.insert_sorted_by_key(50, |&x| x)); + assert_eq!(&b[..], &[10, 20, 30, 40, 50, 60][..]); + + assert!(!b.insert_sorted_by_key(9, |&x| x)); + assert_eq!(&b[..], &[10, 20, 30, 40, 50, 60][..]); + + assert!(b.insert_sorted_by_key(11, |&x| x)); + assert_eq!(&b[..], &[11, 20, 30, 40, 50, 60][..]); + + assert!(b.insert_sorted_by_key(21, |&x| x)); + assert_eq!(&b[..], &[20, 21, 30, 40, 50, 60][..]); + + assert!(b.insert_sorted_by_key(61, |&x| x)); + assert_eq!(&b[..], &[21, 30, 40, 50, 60, 61][..]); + + assert!(b.insert_sorted_by_key(51, |&x| x)); + assert_eq!(&b[..], &[30, 40, 50, 51, 60, 61][..]); + } + + #[test] + fn translated_reciprocal_works() { + let c: Curve = Curve::Reciprocal { + factor: FixedI64::from_float(0.03125), + x_offset: FixedI64::from_float(0.0363306838226), + y_offset: FixedI64::from_float(0.139845532427), + }; + c.info(28u32, "Test"); + + for i in 0..9_696_969u32 { + let query = Perbill::from_rational(i, 9_696_969); + // Determine the nearest point in time when the query will be above threshold. + let delay_needed = c.delay(query); + // Ensure that it actually does pass at that time, or that it will never pass. + assert!(delay_needed.is_one() || c.passing(delay_needed, query)); + } + } + + #[test] + fn stepped_decreasing_works() { + fn pc(x: u32) -> Perbill { + Perbill::from_percent(x) + } + + let c = + Curve::SteppedDecreasing { begin: pc(80), end: pc(30), step: pc(10), period: pc(15) }; + + for i in 0..9_696_969u32 { + let query = Perbill::from_rational(i, 9_696_969); + // Determine the nearest point in time when the query will be above threshold. + let delay_needed = c.delay(query); + // Ensure that it actually does pass at that time, or that it will never pass. + assert!(delay_needed.is_one() || c.passing(delay_needed, query)); + } + + assert_eq!(c.threshold(pc(0)), pc(80)); + assert_eq!(c.threshold(pc(15).less_epsilon()), pc(80)); + assert_eq!(c.threshold(pc(15)), pc(70)); + assert_eq!(c.threshold(pc(30).less_epsilon()), pc(70)); + assert_eq!(c.threshold(pc(30)), pc(60)); + assert_eq!(c.threshold(pc(45).less_epsilon()), pc(60)); + assert_eq!(c.threshold(pc(45)), pc(50)); + assert_eq!(c.threshold(pc(60).less_epsilon()), pc(50)); + assert_eq!(c.threshold(pc(60)), pc(40)); + assert_eq!(c.threshold(pc(75).less_epsilon()), pc(40)); + assert_eq!(c.threshold(pc(75)), pc(30)); + assert_eq!(c.threshold(pc(100)), pc(30)); + + assert_eq!(c.delay(pc(100)), pc(0)); + assert_eq!(c.delay(pc(80)), pc(0)); + assert_eq!(c.delay(pc(80).less_epsilon()), pc(15)); + assert_eq!(c.delay(pc(70)), pc(15)); + assert_eq!(c.delay(pc(70).less_epsilon()), pc(30)); + assert_eq!(c.delay(pc(60)), pc(30)); + assert_eq!(c.delay(pc(60).less_epsilon()), pc(45)); + assert_eq!(c.delay(pc(50)), pc(45)); + assert_eq!(c.delay(pc(50).less_epsilon()), pc(60)); + assert_eq!(c.delay(pc(40)), pc(60)); + assert_eq!(c.delay(pc(40).less_epsilon()), pc(75)); + assert_eq!(c.delay(pc(30)), pc(75)); + assert_eq!(c.delay(pc(30).less_epsilon()), pc(100)); + assert_eq!(c.delay(pc(0)), pc(100)); + } +} diff --git a/frame/scheduler/src/mock.rs b/frame/scheduler/src/mock.rs index ecd04c3e48b52..008105dc737ea 100644 --- a/frame/scheduler/src/mock.rs +++ b/frame/scheduler/src/mock.rs @@ -23,7 +23,7 @@ use crate as scheduler; use frame_support::{ ord_parameter_types, parameter_types, traits::{ - ConstU32, ConstU64, Contains, EnsureOneOf, EqualPrivilegeOnly, OnFinalize, OnInitialize, + ConstU32, ConstU64, Contains, EitherOfDiverse, EqualPrivilegeOnly, OnFinalize, OnInitialize, }, weights::constants::RocksDbWeight, }; @@ -174,7 +174,7 @@ impl Config for Test { type PalletsOrigin = OriginCaller; type Call = Call; type MaximumWeight = MaximumSchedulerWeight; - type ScheduleOrigin = EnsureOneOf, EnsureSignedBy>; + type ScheduleOrigin = EitherOfDiverse, EnsureSignedBy>; type MaxScheduledPerBlock = ConstU32<10>; type WeightInfo = (); type OriginPrivilegeCmp = EqualPrivilegeOnly; diff --git a/frame/staking/src/benchmarking.rs b/frame/staking/src/benchmarking.rs index eb9129ac4b436..12de0ff9cc665 100644 --- a/frame/staking/src/benchmarking.rs +++ b/frame/staking/src/benchmarking.rs @@ -990,7 +990,7 @@ mod tests { let (validator_stash, nominators) = create_validator_with_nominators::( n, - ::MaxNominatorRewardedPerValidator::get(), + <::MaxNominatorRewardedPerValidator as Get<_>>::get(), false, RewardDestination::Staked, ) @@ -1015,7 +1015,7 @@ mod tests { let (validator_stash, _nominators) = create_validator_with_nominators::( n, - ::MaxNominatorRewardedPerValidator::get(), + <::MaxNominatorRewardedPerValidator as Get<_>>::get(), false, RewardDestination::Staked, ) diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index ccd9558c5c21d..d0ebef27b4ef6 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -3388,7 +3388,7 @@ fn six_session_delay() { #[test] fn test_max_nominator_rewarded_per_validator_and_cant_steal_someone_else_reward() { ExtBuilder::default().build_and_execute(|| { - for i in 0..=::MaxNominatorRewardedPerValidator::get() { + for i in 0..=<::MaxNominatorRewardedPerValidator as Get<_>>::get() { let stash = 10_000 + i as AccountId; let controller = 20_000 + i as AccountId; let balance = 10_000 + i as Balance; @@ -3411,7 +3411,7 @@ fn test_max_nominator_rewarded_per_validator_and_cant_steal_someone_else_reward( mock::make_all_reward_payment(1); // Assert only nominators from 1 to Max are rewarded - for i in 0..=::MaxNominatorRewardedPerValidator::get() { + for i in 0..=<::MaxNominatorRewardedPerValidator as Get<_>>::get() { let stash = 10_000 + i as AccountId; let balance = 10_000 + i as Balance; if stash == 10_000 { @@ -3649,7 +3649,8 @@ fn payout_stakers_handles_weight_refund() { // Note: this test relies on the assumption that `payout_stakers_alive_staked` is solely used by // `payout_stakers` to calculate the weight of each payout op. ExtBuilder::default().has_stakers(false).build_and_execute(|| { - let max_nom_rewarded = ::MaxNominatorRewardedPerValidator::get(); + let max_nom_rewarded = + <::MaxNominatorRewardedPerValidator as Get<_>>::get(); // Make sure the configured value is meaningful for our use. assert!(max_nom_rewarded >= 4); let half_max_nom_rewarded = max_nom_rewarded / 2; diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index 178fddb61b0a2..fa37fa5cda22b 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -350,6 +350,13 @@ macro_rules! parameter_types { I::from(Self::get()) } } + + impl $crate::traits::TypedGet for $name { + type Type = $type; + fn get() -> $type { + Self::get() + } + } }; (IMPL $name:ident, $type:ty, $value:expr) => { impl $name { @@ -364,6 +371,13 @@ macro_rules! parameter_types { I::from(Self::get()) } } + + impl $crate::traits::TypedGet for $name { + type Type = $type; + fn get() -> $type { + Self::get() + } + } }; (IMPL_STORAGE $name:ident, $type:ty, $value:expr) => { impl $name { @@ -397,6 +411,13 @@ macro_rules! parameter_types { I::from(Self::get()) } } + + impl $crate::traits::TypedGet for $name { + type Type = $type; + fn get() -> $type { + Self::get() + } + } }; ( $( #[ $attr:meta ] )* @@ -805,7 +826,7 @@ pub mod tests { }; use codec::{Codec, EncodeLike}; use frame_support::traits::CrateVersion; - use sp_io::TestExternalities; + use sp_io::{MultiRemovalResults, TestExternalities}; use sp_std::result; /// A PalletInfo implementation which just panics. @@ -1065,16 +1086,61 @@ pub mod tests { }); } + #[test] + fn double_map_basic_insert_remove_remove_prefix_with_commit_should_work() { + let key1 = 17u32; + let key2 = 18u32; + type DoubleMap = DataDM; + let mut e = new_test_ext(); + e.execute_with(|| { + // initialized during genesis + assert_eq!(DoubleMap::get(&15u32, &16u32), 42u64); + + // get / insert / take + assert_eq!(DoubleMap::get(&key1, &key2), 0u64); + DoubleMap::insert(&key1, &key2, &4u64); + assert_eq!(DoubleMap::get(&key1, &key2), 4u64); + assert_eq!(DoubleMap::take(&key1, &key2), 4u64); + assert_eq!(DoubleMap::get(&key1, &key2), 0u64); + + // mutate + DoubleMap::mutate(&key1, &key2, |val| *val = 15); + assert_eq!(DoubleMap::get(&key1, &key2), 15u64); + + // remove + DoubleMap::remove(&key1, &key2); + assert_eq!(DoubleMap::get(&key1, &key2), 0u64); + + // remove prefix + DoubleMap::insert(&key1, &key2, &4u64); + DoubleMap::insert(&key1, &(key2 + 1), &4u64); + DoubleMap::insert(&(key1 + 1), &key2, &4u64); + DoubleMap::insert(&(key1 + 1), &(key2 + 1), &4u64); + }); + e.commit_all().unwrap(); + e.execute_with(|| { + assert!(matches!( + DoubleMap::clear_prefix(&key1, u32::max_value(), None), + MultiRemovalResults { maybe_cursor: None, backend: 2, unique: 2, loops: 2 } + )); + assert_eq!(DoubleMap::get(&key1, &key2), 0u64); + assert_eq!(DoubleMap::get(&key1, &(key2 + 1)), 0u64); + assert_eq!(DoubleMap::get(&(key1 + 1), &key2), 4u64); + assert_eq!(DoubleMap::get(&(key1 + 1), &(key2 + 1)), 4u64); + }); + } + #[test] fn double_map_basic_insert_remove_remove_prefix_should_work() { new_test_ext().execute_with(|| { + let key1 = 17u32; + let key2 = 18u32; type DoubleMap = DataDM; + // initialized during genesis assert_eq!(DoubleMap::get(&15u32, &16u32), 42u64); // get / insert / take - let key1 = 17u32; - let key2 = 18u32; assert_eq!(DoubleMap::get(&key1, &key2), 0u64); DoubleMap::insert(&key1, &key2, &4u64); assert_eq!(DoubleMap::get(&key1, &key2), 4u64); @@ -1082,9 +1148,7 @@ pub mod tests { assert_eq!(DoubleMap::get(&key1, &key2), 0u64); // mutate - DoubleMap::mutate(&key1, &key2, |val| { - *val = 15; - }); + DoubleMap::mutate(&key1, &key2, |val| *val = 15); assert_eq!(DoubleMap::get(&key1, &key2), 15u64); // remove @@ -1096,13 +1160,18 @@ pub mod tests { DoubleMap::insert(&key1, &(key2 + 1), &4u64); DoubleMap::insert(&(key1 + 1), &key2, &4u64); DoubleMap::insert(&(key1 + 1), &(key2 + 1), &4u64); + // all in overlay + assert!(matches!( + DoubleMap::clear_prefix(&key1, u32::max_value(), None), + MultiRemovalResults { maybe_cursor: None, backend: 0, unique: 0, loops: 0 } + )); + // Note this is the incorrect answer (for now), since we are using v2 of + // `clear_prefix`. + // When we switch to v3, then this will become: + // MultiRemovalResults:: { maybe_cursor: None, backend: 0, unique: 2, loops: 2 }, assert!(matches!( DoubleMap::clear_prefix(&key1, u32::max_value(), None), - // Note this is the incorrect answer (for now), since we are using v2 of - // `clear_prefix`. - // When we switch to v3, then this will become: - // sp_io::MultiRemovalResults::NoneLeft { db: 0, total: 2 }, - sp_io::MultiRemovalResults { maybe_cursor: None, backend: 0, unique: 0, loops: 0 }, + MultiRemovalResults { maybe_cursor: None, backend: 0, unique: 0, loops: 0 } )); assert_eq!(DoubleMap::get(&key1, &key2), 0u64); assert_eq!(DoubleMap::get(&key1, &(key2 + 1)), 0u64); @@ -1321,7 +1390,7 @@ pub mod pallet_prelude { }, traits::{ ConstU32, EnsureOrigin, Get, GetDefault, GetStorageVersion, Hooks, IsType, - PalletInfoAccess, StorageInfoTrait, StorageVersion, + PalletInfoAccess, StorageInfoTrait, StorageVersion, TypedGet, }, weights::{DispatchClass, Pays, Weight}, Blake2_128, Blake2_128Concat, Blake2_256, CloneNoBound, DebugNoBound, EqNoBound, Identity, diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index a6fc88dfb5e76..d1872544e024d 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -59,7 +59,7 @@ pub use misc::{ ConstU32, ConstU64, ConstU8, DefensiveSaturating, EnsureInherentsAreFirst, EqualPrivilegeOnly, EstimateCallFee, ExecuteBlock, ExtrinsicCall, Get, GetBacking, GetDefault, HandleLifetime, IsSubType, IsType, Len, OffchainWorker, OnKilledAccount, OnNewAccount, PreimageProvider, - PreimageRecipient, PrivilegeCmp, SameOrOther, Time, TryCollect, TryDrop, UnixTime, + PreimageRecipient, PrivilegeCmp, SameOrOther, Time, TryCollect, TryDrop, TypedGet, UnixTime, WrapperKeepOpaque, WrapperOpaque, }; #[doc(hidden)] @@ -93,9 +93,11 @@ pub use storage::{ }; mod dispatch; +#[allow(deprecated)] +pub use dispatch::EnsureOneOf; pub use dispatch::{ - AsEnsureOriginWithArg, DispatchableWithStorageLayer, EnsureOneOf, EnsureOrigin, - EnsureOriginWithArg, OriginTrait, UnfilteredDispatchable, + AsEnsureOriginWithArg, DispatchableWithStorageLayer, EitherOf, EitherOfDiverse, EnsureOrigin, + EnsureOriginWithArg, NeverEnsureOrigin, OriginTrait, UnfilteredDispatchable, }; mod voting; diff --git a/frame/support/src/traits/dispatch.rs b/frame/support/src/traits/dispatch.rs index 1cec6cf837aba..b1bd52ca960da 100644 --- a/frame/support/src/traits/dispatch.rs +++ b/frame/support/src/traits/dispatch.rs @@ -43,6 +43,19 @@ pub trait EnsureOrigin { fn successful_origin() -> OuterOrigin; } +/// `EnsureOrigin` implementation that always fails. +pub struct NeverEnsureOrigin(sp_std::marker::PhantomData); +impl EnsureOrigin for NeverEnsureOrigin { + type Success = Success; + fn try_origin(o: OO) -> Result { + Err(o) + } + #[cfg(feature = "runtime-benchmarks")] + fn successful_origin() -> OO { + panic!("No `successful_origin` possible for `NeverEnsureOrigin`") + } +} + /// Some sort of check on the origin is performed by this object. pub trait EnsureOriginWithArg { /// A return type. @@ -163,13 +176,16 @@ pub trait OriginTrait: Sized { fn signed(by: Self::AccountId) -> Self; } -/// The "OR gate" implementation of `EnsureOrigin`. +/// "OR gate" implementation of `EnsureOrigin` allowing for different `Success` types for `L` +/// and `R`, with them combined using an `Either` type. /// /// Origin check will pass if `L` or `R` origin check passes. `L` is tested first. -pub struct EnsureOneOf(sp_std::marker::PhantomData<(L, R)>); +/// +/// Successful origin is derived from the left side. +pub struct EitherOfDiverse(sp_std::marker::PhantomData<(L, R)>); impl, R: EnsureOrigin> - EnsureOrigin for EnsureOneOf + EnsureOrigin for EitherOfDiverse { type Success = Either; fn try_origin(o: OuterOrigin) -> Result { @@ -183,17 +199,53 @@ impl, R: EnsureOrigin> } } +/// "OR gate" implementation of `EnsureOrigin` allowing for different `Success` types for `L` +/// and `R`, with them combined using an `Either` type. +/// +/// Origin check will pass if `L` or `R` origin check passes. `L` is tested first. +/// +/// Successful origin is derived from the left side. +#[deprecated = "Use `EitherOfDiverse` instead"] +pub type EnsureOneOf = EitherOfDiverse; + +/// "OR gate" implementation of `EnsureOrigin`, `Success` type for both `L` and `R` must +/// be equal. +/// +/// Origin check will pass if `L` or `R` origin check passes. `L` is tested first. +/// +/// Successful origin is derived from the left side. +pub struct EitherOf(sp_std::marker::PhantomData<(L, R)>); + +impl< + OuterOrigin, + L: EnsureOrigin, + R: EnsureOrigin, + > EnsureOrigin for EitherOf +{ + type Success = L::Success; + fn try_origin(o: OuterOrigin) -> Result { + L::try_origin(o).or_else(|o| R::try_origin(o)) + } + + #[cfg(feature = "runtime-benchmarks")] + fn successful_origin() -> OuterOrigin { + L::successful_origin() + } +} + #[cfg(test)] mod tests { use super::*; + use crate::traits::{ConstBool, ConstU8, TypedGet}; + use std::marker::PhantomData; - struct EnsureSuccess; - struct EnsureFail; + struct EnsureSuccess(PhantomData); + struct EnsureFail(PhantomData); - impl EnsureOrigin<()> for EnsureSuccess { - type Success = (); + impl EnsureOrigin<()> for EnsureSuccess { + type Success = V::Type; fn try_origin(_: ()) -> Result { - Ok(()) + Ok(V::get()) } #[cfg(feature = "runtime-benchmarks")] fn successful_origin() -> () { @@ -201,8 +253,8 @@ mod tests { } } - impl EnsureOrigin<()> for EnsureFail { - type Success = (); + impl EnsureOrigin<()> for EnsureFail { + type Success = T; fn try_origin(_: ()) -> Result { Err(()) } @@ -213,10 +265,46 @@ mod tests { } #[test] - fn ensure_one_of_test() { - assert!(>::try_origin(()).is_ok()); - assert!(>::try_origin(()).is_ok()); - assert!(>::try_origin(()).is_ok()); - assert!(>::try_origin(()).is_err()); + fn either_of_diverse_works() { + assert_eq!( + EitherOfDiverse::< + EnsureSuccess>, + EnsureSuccess>, + >::try_origin(()).unwrap().left(), + Some(true) + ); + assert_eq!( + EitherOfDiverse::>, EnsureFail>::try_origin(()) + .unwrap() + .left(), + Some(true) + ); + assert_eq!( + EitherOfDiverse::, EnsureSuccess>>::try_origin(()) + .unwrap() + .right(), + Some(0u8) + ); + assert!(EitherOfDiverse::, EnsureFail>::try_origin(()).is_err()); + } + + #[test] + fn either_of_works() { + assert_eq!( + EitherOf::< + EnsureSuccess>, + EnsureSuccess>, + >::try_origin(()).unwrap(), + true + ); + assert_eq!( + EitherOf::>, EnsureFail>::try_origin(()).unwrap(), + true + ); + assert_eq!( + EitherOf::, EnsureSuccess>>::try_origin(()).unwrap(), + false + ); + assert!(EitherOf::, EnsureFail>::try_origin(()).is_err()); } } diff --git a/frame/support/src/traits/misc.rs b/frame/support/src/traits/misc.rs index 03420f64dd55b..bea4e2a394411 100644 --- a/frame/support/src/traits/misc.rs +++ b/frame/support/src/traits/misc.rs @@ -387,6 +387,16 @@ where } } +/// A trait for querying a single value from a type defined in the trait. +/// +/// It is not required that the value is constant. +pub trait TypedGet { + /// The type which is returned. + type Type; + /// Return the current value. + fn get() -> Self::Type; +} + /// A trait for querying a single value from a type. /// /// It is not required that the value is constant. @@ -423,6 +433,12 @@ macro_rules! impl_const_get { Some(T) } } + impl TypedGet for $name { + type Type = $t; + fn get() -> $t { + T + } + } }; } diff --git a/frame/support/src/traits/voting.rs b/frame/support/src/traits/voting.rs index 978c5ce4f6a01..6c802a6112246 100644 --- a/frame/support/src/traits/voting.rs +++ b/frame/support/src/traits/voting.rs @@ -95,16 +95,18 @@ impl + UniqueSaturatedFrom> CurrencyToVote } } -pub trait VoteTally { - fn ayes(&self) -> Votes; - fn turnout(&self) -> Perbill; - fn approval(&self) -> Perbill; +pub trait VoteTally { + fn new(_: Class) -> Self; + fn ayes(&self, class: Class) -> Votes; + fn support(&self, class: Class) -> Perbill; + fn approval(&self, class: Class) -> Perbill; #[cfg(feature = "runtime-benchmarks")] - fn unanimity() -> Self; + fn unanimity(class: Class) -> Self; #[cfg(feature = "runtime-benchmarks")] - fn from_requirements(turnout: Perbill, approval: Perbill) -> Self; + fn rejection(class: Class) -> Self; + #[cfg(feature = "runtime-benchmarks")] + fn from_requirements(support: Perbill, approval: Perbill, class: Class) -> Self; } - pub enum PollStatus { None, Ongoing(Tally, Class), diff --git a/frame/system/src/lib.rs b/frame/system/src/lib.rs index f2999f945c5c2..bc28cc7e8118e 100644 --- a/frame/system/src/lib.rs +++ b/frame/system/src/lib.rs @@ -86,7 +86,7 @@ use frame_support::{ storage, traits::{ ConstU32, Contains, EnsureOrigin, Get, HandleLifetime, OnKilledAccount, OnNewAccount, - OriginTrait, PalletInfo, SortedMembers, StoredMap, + OriginTrait, PalletInfo, SortedMembers, StoredMap, TypedGet, }, weights::{ extract_actual_weight, DispatchClass, DispatchInfo, PerDispatchClass, RuntimeDbWeight, @@ -787,6 +787,29 @@ impl, O>> + From>, Acco } } +pub struct EnsureRootWithSuccess( + sp_std::marker::PhantomData<(AccountId, Success)>, +); +impl< + O: Into, O>> + From>, + AccountId, + Success: TypedGet, + > EnsureOrigin for EnsureRootWithSuccess +{ + type Success = Success::Type; + fn try_origin(o: O) -> Result { + o.into().and_then(|o| match o { + RawOrigin::Root => Ok(Success::get()), + r => Err(O::from(r)), + }) + } + + #[cfg(feature = "runtime-benchmarks")] + fn successful_origin() -> O { + O::from(RawOrigin::Root) + } +} + pub struct EnsureSigned(sp_std::marker::PhantomData); impl, O>> + From>, AccountId: Decode> EnsureOrigin for EnsureSigned diff --git a/frame/tips/src/tests.rs b/frame/tips/src/tests.rs index 27cce5576a3b9..235952fd1092c 100644 --- a/frame/tips/src/tests.rs +++ b/frame/tips/src/tests.rs @@ -144,6 +144,7 @@ impl pallet_treasury::Config for Test { type WeightInfo = (); type SpendFunds = (); type MaxApprovals = ConstU32<100>; + type SpendOrigin = frame_support::traits::NeverEnsureOrigin; } parameter_types! { pub const TipFindersFee: Percent = Percent::from_percent(20); diff --git a/frame/treasury/src/benchmarking.rs b/frame/treasury/src/benchmarking.rs index 47bc1b9ea99de..4f17189d79b39 100644 --- a/frame/treasury/src/benchmarking.rs +++ b/frame/treasury/src/benchmarking.rs @@ -22,7 +22,11 @@ use super::{Pallet as Treasury, *}; use frame_benchmarking::{account, benchmarks_instance_pallet}; -use frame_support::{ensure, traits::OnInitialize}; +use frame_support::{ + dispatch::UnfilteredDispatchable, + ensure, + traits::{EnsureOrigin, OnInitialize}, +}; use frame_system::RawOrigin; const SEED: u32 = 0; @@ -57,7 +61,21 @@ fn setup_pot_account, I: 'static>() { let _ = T::Currency::make_free_balance_be(&pot_account, value); } +fn assert_last_event, I: 'static>(generic_event: >::Event) { + frame_system::Pallet::::assert_last_event(generic_event.into()); +} + benchmarks_instance_pallet! { + spend { + let (_, value, beneficiary_lookup) = setup_proposal::(SEED); + let origin = T::SpendOrigin::successful_origin(); + let beneficiary = T::Lookup::lookup(beneficiary_lookup.clone()).unwrap(); + let call = Call::::spend { amount: value, beneficiary: beneficiary_lookup }; + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert_last_event::(Event::SpendApproved { proposal_index: 0, amount: value, beneficiary }.into()) + } + propose_spend { let (caller, value, beneficiary_lookup) = setup_proposal::(SEED); // Whitelist caller account from further DB operations. diff --git a/frame/treasury/src/lib.rs b/frame/treasury/src/lib.rs index 419970ed18afa..6730f985b16e0 100644 --- a/frame/treasury/src/lib.rs +++ b/frame/treasury/src/lib.rs @@ -198,6 +198,11 @@ pub mod pallet { /// NOTE: This parameter is also used within the Bounties Pallet extension if enabled. #[pallet::constant] type MaxApprovals: Get; + + /// The origin required for approving spends from the treasury outside of the proposal + /// process. The `Success` value is the maximum amount that this origin is allowed to + /// spend at a time. + type SpendOrigin: EnsureOrigin>; } /// Number of proposals that have been made. @@ -275,6 +280,12 @@ pub mod pallet { Rollover { rollover_balance: BalanceOf }, /// Some funds have been deposited. Deposit { value: BalanceOf }, + /// A new spend proposal has been approved. + SpendApproved { + proposal_index: ProposalIndex, + amount: BalanceOf, + beneficiary: T::AccountId, + }, } /// Error for the treasury pallet. @@ -286,6 +297,9 @@ pub mod pallet { InvalidIndex, /// Too many approvals in the queue. TooManyApprovals, + /// The spend origin is valid but the amount it is allowed to spend is lower than the + /// amount to be spent. + InsufficientPermission, /// Proposal has not been approved. ProposalNotApproved, } @@ -393,6 +407,40 @@ pub mod pallet { Ok(()) } + /// Propose and approve a spend of treasury funds. + /// + /// - `origin`: Must be `SpendOrigin` with the `Success` value being at least `amount`. + /// - `amount`: The amount to be transferred from the treasury to the `beneficiary`. + /// - `beneficiary`: The destination account for the transfer. + /// + /// NOTE: For record-keeping purposes, the proposer is deemed to be equivalent to the + /// beneficiary. + #[pallet::weight(T::WeightInfo::spend())] + pub fn spend( + origin: OriginFor, + #[pallet::compact] amount: BalanceOf, + beneficiary: ::Source, + ) -> DispatchResult { + let max_amount = T::SpendOrigin::ensure_origin(origin)?; + let beneficiary = T::Lookup::lookup(beneficiary)?; + + ensure!(amount <= max_amount, Error::::InsufficientPermission); + let proposal_index = Self::proposal_count(); + Approvals::::try_append(proposal_index) + .map_err(|_| Error::::TooManyApprovals)?; + let proposal = Proposal { + proposer: beneficiary.clone(), + value: amount, + beneficiary: beneficiary.clone(), + bond: Default::default(), + }; + Proposals::::insert(proposal_index, proposal); + ProposalCount::::put(proposal_index + 1); + + Self::deposit_event(Event::SpendApproved { proposal_index, amount, beneficiary }); + Ok(()) + } + /// Force a previously approved proposal to be removed from the approval queue. /// The original deposit will no longer be returned. /// diff --git a/frame/treasury/src/tests.rs b/frame/treasury/src/tests.rs index b755db29682aa..a21296d1b39ec 100644 --- a/frame/treasury/src/tests.rs +++ b/frame/treasury/src/tests.rs @@ -24,7 +24,7 @@ use std::cell::RefCell; use sp_core::H256; use sp_runtime::{ testing::Header, - traits::{BlakeTwo256, IdentityLookup}, + traits::{BadOrigin, BlakeTwo256, IdentityLookup}, }; use frame_support::{ @@ -101,8 +101,26 @@ parameter_types! { pub const ProposalBond: Permill = Permill::from_percent(5); pub const Burn: Permill = Permill::from_percent(50); pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry"); - pub const MaxApprovals: u32 = 100; } +pub struct TestSpendOrigin; +impl frame_support::traits::EnsureOrigin for TestSpendOrigin { + type Success = u64; + fn try_origin(o: Origin) -> Result { + Result::, Origin>::from(o).and_then(|o| match o { + frame_system::RawOrigin::Root => Ok(u64::max_value()), + frame_system::RawOrigin::Signed(10) => Ok(5), + frame_system::RawOrigin::Signed(11) => Ok(10), + frame_system::RawOrigin::Signed(12) => Ok(20), + frame_system::RawOrigin::Signed(13) => Ok(50), + r => Err(Origin::from(r)), + }) + } + #[cfg(feature = "runtime-benchmarks")] + fn successful_origin() -> Origin { + Origin::root() + } +} + impl Config for Test { type PalletId = TreasuryPalletId; type Currency = pallet_balances::Pallet; @@ -119,6 +137,7 @@ impl Config for Test { type WeightInfo = (); type SpendFunds = (); type MaxApprovals = ConstU32<100>; + type SpendOrigin = TestSpendOrigin; } pub fn new_test_ext() -> sp_io::TestExternalities { @@ -141,6 +160,51 @@ fn genesis_config_works() { }); } +#[test] +fn spend_origin_permissioning_works() { + new_test_ext().execute_with(|| { + assert_noop!(Treasury::spend(Origin::signed(1), 1, 1), BadOrigin); + assert_noop!( + Treasury::spend(Origin::signed(10), 6, 1), + Error::::InsufficientPermission + ); + assert_noop!( + Treasury::spend(Origin::signed(11), 11, 1), + Error::::InsufficientPermission + ); + assert_noop!( + Treasury::spend(Origin::signed(12), 21, 1), + Error::::InsufficientPermission + ); + assert_noop!( + Treasury::spend(Origin::signed(13), 51, 1), + Error::::InsufficientPermission + ); + }); +} + +#[test] +fn spend_origin_works() { + new_test_ext().execute_with(|| { + // Check that accumulate works when we have Some value in Dummy already. + Balances::make_free_balance_be(&Treasury::account_id(), 101); + assert_ok!(Treasury::spend(Origin::signed(10), 5, 6)); + assert_ok!(Treasury::spend(Origin::signed(10), 5, 6)); + assert_ok!(Treasury::spend(Origin::signed(10), 5, 6)); + assert_ok!(Treasury::spend(Origin::signed(10), 5, 6)); + assert_ok!(Treasury::spend(Origin::signed(11), 10, 6)); + assert_ok!(Treasury::spend(Origin::signed(12), 20, 6)); + assert_ok!(Treasury::spend(Origin::signed(13), 50, 6)); + + >::on_initialize(1); + assert_eq!(Balances::free_balance(6), 0); + + >::on_initialize(2); + assert_eq!(Balances::free_balance(6), 100); + assert_eq!(Treasury::pot(), 0); + }); +} + #[test] fn minting_works() { new_test_ext().execute_with(|| { @@ -372,7 +436,7 @@ fn max_approvals_limited() { Balances::make_free_balance_be(&Treasury::account_id(), u64::MAX); Balances::make_free_balance_be(&0, u64::MAX); - for _ in 0..MaxApprovals::get() { + for _ in 0..::MaxApprovals::get() { assert_ok!(Treasury::propose_spend(Origin::signed(0), 100, 3)); assert_ok!(Treasury::approve_proposal(Origin::root(), 0)); } diff --git a/frame/treasury/src/weights.rs b/frame/treasury/src/weights.rs index f1667bea35480..f6b5414a05652 100644 --- a/frame/treasury/src/weights.rs +++ b/frame/treasury/src/weights.rs @@ -44,6 +44,7 @@ use sp_std::marker::PhantomData; /// Weight functions needed for pallet_treasury. pub trait WeightInfo { + fn spend() -> Weight; fn propose_spend() -> Weight; fn reject_proposal() -> Weight; fn approve_proposal(p: u32, ) -> Weight; @@ -54,6 +55,13 @@ pub trait WeightInfo { /// Weights for pallet_treasury using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { + // Storage: Treasury ProposalCount (r:1 w:1) + // Storage: Treasury Proposals (r:0 w:1) + fn spend() -> Weight { + (22_063_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } // Storage: Treasury ProposalCount (r:1 w:1) // Storage: Treasury Proposals (r:0 w:1) fn propose_spend() -> Weight { @@ -100,6 +108,13 @@ impl WeightInfo for SubstrateWeight { // For backwards compatibility and tests impl WeightInfo for () { + // Storage: Treasury ProposalCount (r:1 w:1) + // Storage: Treasury Proposals (r:0 w:1) + fn spend() -> Weight { + (22_063_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } // Storage: Treasury ProposalCount (r:1 w:1) // Storage: Treasury Proposals (r:0 w:1) fn propose_spend() -> Weight { diff --git a/primitives/arithmetic/Cargo.toml b/primitives/arithmetic/Cargo.toml index 4fdf983943c41..d7046b3254699 100644 --- a/primitives/arithmetic/Cargo.toml +++ b/primitives/arithmetic/Cargo.toml @@ -29,6 +29,7 @@ sp-std = { version = "4.0.0", default-features = false, path = "../std" } [dev-dependencies] criterion = "0.3" primitive-types = "0.11.1" +sp-core = { version = "6.0.0", features = ["full_crypto"], path = "../core" } rand = "0.7.2" [features] diff --git a/primitives/arithmetic/src/fixed_point.rs b/primitives/arithmetic/src/fixed_point.rs index 7ce17bb72611f..f4ed70ee8b5dc 100644 --- a/primitives/arithmetic/src/fixed_point.rs +++ b/primitives/arithmetic/src/fixed_point.rs @@ -18,12 +18,12 @@ //! Decimal Fixed Point implementations for Substrate runtime. use crate::{ - helpers_128bit::multiply_by_rational, + helpers_128bit::{multiply_by_rational, multiply_by_rational_with_rounding, sqrt}, traits::{ Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedSub, One, SaturatedConversion, Saturating, UniqueSaturatedInto, Zero, }, - PerThing, + PerThing, Perbill, Rounding, SignedRounding, }; use codec::{CompactAs, Decode, Encode}; use sp_std::{ @@ -406,20 +406,326 @@ macro_rules! implement_fixed { } impl $name { - /// const version of `FixedPointNumber::from_inner`. + /// Create a new instance from the given `inner` value. + /// + /// `const` version of `FixedPointNumber::from_inner`. pub const fn from_inner(inner: $inner_type) -> Self { Self(inner) } + /// Return the instance's inner value. + /// + /// `const` version of `FixedPointNumber::into_inner`. + pub const fn into_inner(self) -> $inner_type { + self.0 + } + + /// Creates self from a `u32`. + /// + /// WARNING: This is a `const` function designed for convenient use at build time and + /// will panic on overflow. Ensure that any inputs are sensible. + pub const fn from_u32(n: u32) -> Self { + Self::from_inner((n as $inner_type) * $div) + } + + /// Convert from a `float` value. #[cfg(any(feature = "std", test))] pub fn from_float(x: f64) -> Self { Self((x * (::DIV as f64)) as $inner_type) } + /// Convert from a `Perbill` value. + pub const fn from_perbill(n: Perbill) -> Self { + Self::from_rational(n.deconstruct() as u128, 1_000_000_000) + } + + /// Convert into a `Perbill` value. Will saturate if above one or below zero. + pub const fn into_perbill(self) -> Perbill { + if self.0 <= 0 { + Perbill::zero() + } else if self.0 >= $div { + Perbill::one() + } else { + match multiply_by_rational_with_rounding( + self.0 as u128, + 1_000_000_000, + Self::DIV as u128, + Rounding::NearestPrefDown, + ) { + Some(value) => { + if value > (u32::max_value() as u128) { + panic!( + "prior logic ensures 0 Perbill::zero(), + } + } + } + + /// Convert into a `float` value. #[cfg(any(feature = "std", test))] pub fn to_float(self) -> f64 { self.0 as f64 / ::DIV as f64 } + + /// Attempt to convert into a `PerThing`. This will succeed iff `self` is at least zero + /// and at most one. If it is out of bounds, it will result in an error returning the + /// clamped value. + pub fn try_into_perthing(self) -> Result { + if self < Self::zero() { + Err(P::zero()) + } else if self > Self::one() { + Err(P::one()) + } else { + Ok(P::from_rational(self.0 as u128, $div)) + } + } + + /// Attempt to convert into a `PerThing`. This will always succeed resulting in a + /// clamped value if `self` is less than zero or greater than one. + pub fn into_clamped_perthing(self) -> P { + if self < Self::zero() { + P::zero() + } else if self > Self::one() { + P::one() + } else { + P::from_rational(self.0 as u128, $div) + } + } + + /// Negate the value. + /// + /// WARNING: This is a `const` function designed for convenient use at build time and + /// will panic on overflow. Ensure that any inputs are sensible. + pub const fn neg(self) -> Self { + Self(0 - self.0) + } + + /// Take the square root of a positive value. + /// + /// WARNING: This is a `const` function designed for convenient use at build time and + /// will panic on overflow. Ensure that any inputs are sensible. + pub const fn sqrt(self) -> Self { + match self.try_sqrt() { + Some(v) => v, + None => panic!("sqrt overflow or negative input"), + } + } + + /// Compute the square root, rounding as desired. If it overflows or is negative, then + /// `None` is returned. + pub const fn try_sqrt(self) -> Option { + if self.0 == 0 { + return Some(Self(0)) + } + if self.0 < 1 { + return None + } + let v = self.0 as u128; + + // Want x' = sqrt(x) where x = n/D and x' = n'/D (D is fixed) + // Our prefered way is: + // sqrt(n/D) = sqrt(nD / D^2) = sqrt(nD)/sqrt(D^2) = sqrt(nD)/D + // ergo n' = sqrt(nD) + // but this requires nD to fit into our type. + // if nD doesn't fit then we can fall back on: + // sqrt(nD) = sqrt(n)*sqrt(D) + // computing them individually and taking the product at the end. we will lose some + // precision though. + let maybe_vd = u128::checked_mul(v, $div); + let r = if let Some(vd) = maybe_vd { sqrt(vd) } else { sqrt(v) * sqrt($div) }; + Some(Self(r as $inner_type)) + } + + /// Add a value and return the result. + /// + /// WARNING: This is a `const` function designed for convenient use at build time and + /// will panic on overflow. Ensure that any inputs are sensible. + pub const fn add(self, rhs: Self) -> Self { + Self(self.0 + rhs.0) + } + + /// Subtract a value and return the result. + /// + /// WARNING: This is a `const` function designed for convenient use at build time and + /// will panic on overflow. Ensure that any inputs are sensible. + pub const fn sub(self, rhs: Self) -> Self { + Self(self.0 - rhs.0) + } + + /// Multiply by a value and return the result. + /// + /// Result will be rounded to the nearest representable value, rounding down if it is + /// equidistant between two neighbours. + /// + /// WARNING: This is a `const` function designed for convenient use at build time and + /// will panic on overflow. Ensure that any inputs are sensible. + pub const fn mul(self, rhs: Self) -> Self { + match $name::const_checked_mul(self, rhs) { + Some(v) => v, + None => panic!("attempt to multiply with overflow"), + } + } + + /// Divide by a value and return the result. + /// + /// Result will be rounded to the nearest representable value, rounding down if it is + /// equidistant between two neighbours. + /// + /// WARNING: This is a `const` function designed for convenient use at build time and + /// will panic on overflow. Ensure that any inputs are sensible. + pub const fn div(self, rhs: Self) -> Self { + match $name::const_checked_div(self, rhs) { + Some(v) => v, + None => panic!("attempt to divide with overflow or NaN"), + } + } + + /// Convert into an `I129` format value. + /// + /// WARNING: This is a `const` function designed for convenient use at build time and + /// will panic on overflow. Ensure that any inputs are sensible. + const fn into_i129(self) -> I129 { + #[allow(unused_comparisons)] + if self.0 < 0 { + let value = match self.0.checked_neg() { + Some(n) => n as u128, + None => u128::saturating_add(<$inner_type>::max_value() as u128, 1), + }; + I129 { value, negative: true } + } else { + I129 { value: self.0 as u128, negative: false } + } + } + + /// Convert from an `I129` format value. + /// + /// WARNING: This is a `const` function designed for convenient use at build time and + /// will panic on overflow. Ensure that any inputs are sensible. + const fn from_i129(n: I129) -> Option { + let max_plus_one = u128::saturating_add(<$inner_type>::max_value() as u128, 1); + #[allow(unused_comparisons)] + let inner = if n.negative && <$inner_type>::min_value() < 0 && n.value == max_plus_one { + <$inner_type>::min_value() + } else { + let unsigned_inner = n.value as $inner_type; + if unsigned_inner as u128 != n.value || (unsigned_inner > 0) != (n.value > 0) { + return None + }; + if n.negative { + match unsigned_inner.checked_neg() { + Some(v) => v, + None => return None, + } + } else { + unsigned_inner + } + }; + Some(Self(inner)) + } + + /// Calculate an approximation of a rational. + /// + /// Result will be rounded to the nearest representable value, rounding down if it is + /// equidistant between two neighbours. + /// + /// WARNING: This is a `const` function designed for convenient use at build time and + /// will panic on overflow. Ensure that any inputs are sensible. + pub const fn from_rational(a: u128, b: u128) -> Self { + Self::from_rational_with_rounding(a, b, Rounding::NearestPrefDown) + } + + /// Calculate an approximation of a rational with custom rounding. + /// + /// WARNING: This is a `const` function designed for convenient use at build time and + /// will panic on overflow. Ensure that any inputs are sensible. + pub const fn from_rational_with_rounding(a: u128, b: u128, rounding: Rounding) -> Self { + if b == 0 { + panic!("attempt to divide by zero in from_rational") + } + match multiply_by_rational_with_rounding(Self::DIV as u128, a, b, rounding) { + Some(value) => match Self::from_i129(I129 { value, negative: false }) { + Some(x) => x, + None => panic!("overflow in from_rational"), + }, + None => panic!("overflow in from_rational"), + } + } + + /// Multiply by another value, returning `None` in the case of an error. + /// + /// Result will be rounded to the nearest representable value, rounding down if it is + /// equidistant between two neighbours. + pub const fn const_checked_mul(self, other: Self) -> Option { + self.const_checked_mul_with_rounding(other, SignedRounding::NearestPrefLow) + } + + /// Multiply by another value with custom rounding, returning `None` in the case of an + /// error. + /// + /// Result will be rounded to the nearest representable value, rounding down if it is + /// equidistant between two neighbours. + pub const fn const_checked_mul_with_rounding( + self, + other: Self, + rounding: SignedRounding, + ) -> Option { + let lhs = self.into_i129(); + let rhs = other.into_i129(); + let negative = lhs.negative != rhs.negative; + + match multiply_by_rational_with_rounding( + lhs.value, + rhs.value, + Self::DIV as u128, + Rounding::from_signed(rounding, negative), + ) { + Some(value) => Self::from_i129(I129 { value, negative }), + None => None, + } + } + + /// Divide by another value, returning `None` in the case of an error. + /// + /// Result will be rounded to the nearest representable value, rounding down if it is + /// equidistant between two neighbours. + pub const fn const_checked_div(self, other: Self) -> Option { + self.checked_rounding_div(other, SignedRounding::NearestPrefLow) + } + + /// Divide by another value with custom rounding, returning `None` in the case of an + /// error. + /// + /// Result will be rounded to the nearest representable value, rounding down if it is + /// equidistant between two neighbours. + pub const fn checked_rounding_div( + self, + other: Self, + rounding: SignedRounding, + ) -> Option { + if other.0 == 0 { + return None + } + + let lhs = self.into_i129(); + let rhs = other.into_i129(); + let negative = lhs.negative != rhs.negative; + + match multiply_by_rational_with_rounding( + lhs.value, + Self::DIV as u128, + rhs.value, + Rounding::from_signed(rounding, negative), + ) { + Some(value) => Self::from_i129(I129 { value, negative }), + None => None, + } + } } impl Saturating for $name { @@ -522,6 +828,10 @@ macro_rules! implement_fixed { let rhs: I129 = other.0.into(); let negative = lhs.negative != rhs.negative; + // Note that this uses the old (well-tested) code with sign-ignorant rounding. This + // is equivalent to the `SignedRounding::NearestPrefMinor`. This means it is + // expected to give exactly the same result as `const_checked_div` when the result + // is positive and a result up to one epsilon greater when it is negative. multiply_by_rational(lhs.value, Self::DIV as u128, rhs.value) .ok() .and_then(|value| from_i129(I129 { value, negative })) @@ -851,6 +1161,16 @@ macro_rules! implement_fixed { } } + #[test] + fn op_sqrt_works() { + for i in 1..1_000i64 { + let x = $name::saturating_from_rational(i, 1_000i64); + assert_eq!((x * x).try_sqrt(), Some(x)); + let x = $name::saturating_from_rational(i, 1i64); + assert_eq!((x * x).try_sqrt(), Some(x)); + } + } + #[test] fn op_div_works() { let a = $name::saturating_from_integer(42); @@ -1133,6 +1453,41 @@ macro_rules! implement_fixed { assert_eq!(a.into_inner(), 0); } + #[test] + fn from_rational_works() { + let inner_max: u128 = <$name as FixedPointNumber>::Inner::max_value() as u128; + let inner_min: u128 = 0; + let accuracy: u128 = $name::accuracy() as u128; + + // Max - 1. + let a = $name::from_rational(inner_max - 1, accuracy); + assert_eq!(a.into_inner() as u128, inner_max - 1); + + // Min + 1. + let a = $name::from_rational(inner_min + 1, accuracy); + assert_eq!(a.into_inner() as u128, inner_min + 1); + + // Max. + let a = $name::from_rational(inner_max, accuracy); + assert_eq!(a.into_inner() as u128, inner_max); + + // Min. + let a = $name::from_rational(inner_min, accuracy); + assert_eq!(a.into_inner() as u128, inner_min); + + let a = $name::from_rational(inner_max, 3 * accuracy); + assert_eq!(a.into_inner() as u128, inner_max / 3); + + let a = $name::from_rational(1, accuracy); + assert_eq!(a.into_inner() as u128, 1); + + let a = $name::from_rational(1, accuracy + 1); + assert_eq!(a.into_inner() as u128, 1); + + let a = $name::from_rational_with_rounding(1, accuracy + 1, Rounding::Down); + assert_eq!(a.into_inner() as u128, 0); + } + #[test] fn checked_mul_int_works() { let a = $name::saturating_from_integer(2); @@ -1272,6 +1627,76 @@ macro_rules! implement_fixed { ); } + #[test] + fn const_checked_mul_works() { + let inner_max = <$name as FixedPointNumber>::Inner::max_value(); + let inner_min = <$name as FixedPointNumber>::Inner::min_value(); + + let a = $name::saturating_from_integer(2u32); + + // Max - 1. + let b = $name::from_inner(inner_max - 1); + assert_eq!(a.const_checked_mul((b / 2.into())), Some(b)); + + // Max. + let c = $name::from_inner(inner_max); + assert_eq!(a.const_checked_mul((c / 2.into())), Some(b)); + + // Max + 1 => None. + let e = $name::from_inner(1); + assert_eq!(a.const_checked_mul((c / 2.into() + e)), None); + + if $name::SIGNED { + // Min + 1. + let b = $name::from_inner(inner_min + 1) / 2.into(); + let c = $name::from_inner(inner_min + 2); + assert_eq!(a.const_checked_mul(b), Some(c)); + + // Min. + let b = $name::from_inner(inner_min) / 2.into(); + let c = $name::from_inner(inner_min); + assert_eq!(a.const_checked_mul(b), Some(c)); + + // Min - 1 => None. + let b = $name::from_inner(inner_min) / 2.into() - $name::from_inner(1); + assert_eq!(a.const_checked_mul(b), None); + + let b = $name::saturating_from_rational(1i32, -2i32); + let c = $name::saturating_from_integer(-21i32); + let d = $name::saturating_from_integer(42); + + assert_eq!(b.const_checked_mul(d), Some(c)); + + let minus_two = $name::saturating_from_integer(-2i32); + assert_eq!( + b.const_checked_mul($name::max_value()), + $name::max_value().const_checked_div(minus_two) + ); + assert_eq!( + b.const_checked_mul($name::min_value()), + $name::min_value().const_checked_div(minus_two) + ); + + let c = $name::saturating_from_integer(255u32); + assert_eq!(c.const_checked_mul($name::min_value()), None); + } + + let a = $name::saturating_from_rational(1i32, 2i32); + let c = $name::saturating_from_integer(255i32); + + assert_eq!(a.const_checked_mul(42.into()), Some(21.into())); + assert_eq!(c.const_checked_mul(2.into()), Some(510.into())); + assert_eq!(c.const_checked_mul($name::max_value()), None); + assert_eq!( + a.const_checked_mul($name::max_value()), + $name::max_value().checked_div(&2.into()) + ); + assert_eq!( + a.const_checked_mul($name::min_value()), + $name::min_value().const_checked_div($name::saturating_from_integer(2)) + ); + } + #[test] fn checked_div_int_works() { let inner_max = <$name as FixedPointNumber>::Inner::max_value(); diff --git a/primitives/arithmetic/src/helpers_128bit.rs b/primitives/arithmetic/src/helpers_128bit.rs index 735b11287cbe4..260f90ed60cd0 100644 --- a/primitives/arithmetic/src/helpers_128bit.rs +++ b/primitives/arithmetic/src/helpers_128bit.rs @@ -1,6 +1,7 @@ // This file is part of Substrate. // Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Some code is based upon Derek Dreery's IntegerSquareRoot impl, used under license. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,10 +21,11 @@ //! assumptions of a bigger type (u128) being available, or simply create a per-thing and use the //! multiplication implementation provided there. -use crate::biguint; +use crate::{biguint, Rounding}; use num_traits::Zero; use sp_std::{ cmp::{max, min}, + convert::TryInto, mem, }; @@ -117,3 +119,254 @@ pub fn multiply_by_rational(mut a: u128, mut b: u128, mut c: u128) -> Result u128 { + a & ((1 << 64) - 1) + } + + /// Returns the most significant 64 bits of a + const fn high_64(a: u128) -> u128 { + a >> 64 + } + + /// Returns 2^128 - a (two's complement) + const fn neg128(a: u128) -> u128 { + (!a).wrapping_add(1) + } + + /// Returns 2^128 / a + const fn div128(a: u128) -> u128 { + (neg128(a) / a).wrapping_add(1) + } + + /// Returns 2^128 % a + const fn mod128(a: u128) -> u128 { + neg128(a) % a + } + + #[derive(Copy, Clone, Eq, PartialEq)] + pub struct Double128 { + high: u128, + low: u128, + } + + impl Double128 { + pub const fn try_into_u128(self) -> Result { + match self.high { + 0 => Ok(self.low), + _ => Err(()), + } + } + + pub const fn zero() -> Self { + Self { high: 0, low: 0 } + } + + /// Return a `Double128` value representing the `scaled_value << 64`. + /// + /// This means the lower half of the `high` component will be equal to the upper 64-bits of + /// `scaled_value` (in the lower positions) and the upper half of the `low` component will + /// be equal to the lower 64-bits of `scaled_value`. + pub const fn left_shift_64(scaled_value: u128) -> Self { + Self { high: scaled_value >> 64, low: scaled_value << 64 } + } + + /// Construct a value from the upper 128 bits only, with the lower being zeroed. + pub const fn from_low(low: u128) -> Self { + Self { high: 0, low } + } + + /// Returns the same value ignoring anything in the high 128-bits. + pub const fn low_part(self) -> Self { + Self { high: 0, ..self } + } + + /// Returns a*b (in 256 bits) + pub const fn product_of(a: u128, b: u128) -> Self { + // Split a and b into hi and lo 64-bit parts + let (a_low, a_high) = (low_64(a), high_64(a)); + let (b_low, b_high) = (low_64(b), high_64(b)); + // a = (a_low + a_high << 64); b = (b_low + b_high << 64); + // ergo a*b = (a_low + a_high << 64)(b_low + b_high << 64) + // = a_low * b_low + // + a_low * b_high << 64 + // + a_high << 64 * b_low + // + a_high << 64 * b_high << 64 + // assuming: + // f = a_low * b_low + // o = a_low * b_high + // i = a_high * b_low + // l = a_high * b_high + // then: + // a*b = (o+i) << 64 + f + l << 128 + let (f, o, i, l) = (a_low * b_low, a_low * b_high, a_high * b_low, a_high * b_high); + let fl = Self { high: l, low: f }; + let i = Self::left_shift_64(i); + let o = Self::left_shift_64(o); + fl.add(i).add(o) + } + + pub const fn add(self, b: Self) -> Self { + let (low, overflow) = self.low.overflowing_add(b.low); + let carry = overflow as u128; // 1 if true, 0 if false. + let high = self.high.wrapping_add(b.high).wrapping_add(carry as u128); + Double128 { high, low } + } + + pub const fn div(mut self, rhs: u128) -> (Self, u128) { + if rhs == 1 { + return (self, 0) + } + + // (self === a; rhs === b) + // Calculate a / b + // = (a_high << 128 + a_low) / b + // let (q, r) = (div128(b), mod128(b)); + // = (a_low * (q * b + r)) + a_high) / b + // = (a_low * q * b + a_low * r + a_high)/b + // = (a_low * r + a_high) / b + a_low * q + let (q, r) = (div128(rhs), mod128(rhs)); + + // x = current result + // a = next number + let mut x = Self::zero(); + while self.high != 0 { + // x += a.low * q + x = x.add(Self::product_of(self.high, q)); + // a = a.low * r + a.high + self = Self::product_of(self.high, r).add(self.low_part()); + } + + (x.add(Self::from_low(self.low / rhs)), self.low % rhs) + } + } +} + +/// Returns `a * b / c` and `(a * b) % c` (wrapping to 128 bits) or `None` in the case of +/// overflow. +pub const fn multiply_by_rational_with_rounding( + a: u128, + b: u128, + c: u128, + r: Rounding, +) -> Option { + use double128::Double128; + if c == 0 { + panic!("attempt to divide by zero") + } + let (result, remainder) = Double128::product_of(a, b).div(c); + let mut result: u128 = match result.try_into_u128() { + Ok(v) => v, + Err(_) => return None, + }; + if match r { + Rounding::Up => remainder > 0, + // cannot be `(c + 1) / 2` since `c` might be `max_value` and overflow. + Rounding::NearestPrefUp => remainder >= c / 2 + c % 2, + Rounding::NearestPrefDown => remainder > c / 2, + Rounding::Down => false, + } { + result = match result.checked_add(1) { + Some(v) => v, + None => return None, + }; + } + Some(result) +} + +pub const fn sqrt(mut n: u128) -> u128 { + // Modified from https://github.com/derekdreery/integer-sqrt-rs (Apache/MIT). + if n == 0 { + return 0 + } + + // Compute bit, the largest power of 4 <= n + let max_shift: u32 = 0u128.leading_zeros() - 1; + let shift: u32 = (max_shift - n.leading_zeros()) & !1; + let mut bit = 1u128 << shift; + + // Algorithm based on the implementation in: + // https://en.wikipedia.org/wiki/Methods_of_computing_square_roots#Binary_numeral_system_(base_2) + // Note that result/bit are logically unsigned (even if T is signed). + let mut result = 0u128; + while bit != 0 { + if n >= result + bit { + n -= result + bit; + result = (result >> 1) + bit; + } else { + result = result >> 1; + } + bit = bit >> 2; + } + result +} + +#[cfg(test)] +mod tests { + use super::*; + use codec::{Decode, Encode}; + use multiply_by_rational_with_rounding as mulrat; + use Rounding::*; + + const MAX: u128 = u128::max_value(); + + #[test] + fn rational_multiply_basic_rounding_works() { + assert_eq!(mulrat(1, 1, 1, Up), Some(1)); + assert_eq!(mulrat(3, 1, 3, Up), Some(1)); + assert_eq!(mulrat(1, 1, 3, Up), Some(1)); + assert_eq!(mulrat(1, 2, 3, Down), Some(0)); + assert_eq!(mulrat(1, 1, 3, NearestPrefDown), Some(0)); + assert_eq!(mulrat(1, 1, 2, NearestPrefDown), Some(0)); + assert_eq!(mulrat(1, 2, 3, NearestPrefDown), Some(1)); + assert_eq!(mulrat(1, 1, 3, NearestPrefUp), Some(0)); + assert_eq!(mulrat(1, 1, 2, NearestPrefUp), Some(1)); + assert_eq!(mulrat(1, 2, 3, NearestPrefUp), Some(1)); + } + + #[test] + fn rational_multiply_big_number_works() { + assert_eq!(mulrat(MAX, MAX - 1, MAX, Down), Some(MAX - 1)); + assert_eq!(mulrat(MAX, 1, MAX, Down), Some(1)); + assert_eq!(mulrat(MAX, MAX - 1, MAX, Up), Some(MAX - 1)); + assert_eq!(mulrat(MAX, 1, MAX, Up), Some(1)); + assert_eq!(mulrat(1, MAX - 1, MAX, Down), Some(0)); + assert_eq!(mulrat(1, 1, MAX, Up), Some(1)); + assert_eq!(mulrat(1, MAX / 2, MAX, NearestPrefDown), Some(0)); + assert_eq!(mulrat(1, MAX / 2 + 1, MAX, NearestPrefDown), Some(1)); + assert_eq!(mulrat(1, MAX / 2, MAX, NearestPrefUp), Some(0)); + assert_eq!(mulrat(1, MAX / 2 + 1, MAX, NearestPrefUp), Some(1)); + } + + #[test] + fn sqrt_works() { + for i in 0..100_000u32 { + let a = sqrt(random_u128(i)); + assert_eq!(sqrt(a * a), a); + } + } + + fn random_u128(seed: u32) -> u128 { + u128::decode(&mut &seed.using_encoded(sp_core::hashing::twox_128)[..]).unwrap_or(0) + } + + #[test] + fn op_checked_rounded_div_works() { + for i in 0..100_000u32 { + let a = random_u128(i); + let b = random_u128(i + 1 << 30); + let c = random_u128(i + 1 << 31); + let x = mulrat(a, b, c, NearestPrefDown); + let y = multiply_by_rational(a, b, c).ok(); + assert_eq!(x.is_some(), y.is_some()); + let x = x.unwrap_or(0); + let y = y.unwrap_or(0); + let d = x.max(y) - x.min(y); + assert_eq!(d, 0); + } + } +} diff --git a/primitives/arithmetic/src/lib.rs b/primitives/arithmetic/src/lib.rs index e43e42763575d..244242c0f7580 100644 --- a/primitives/arithmetic/src/lib.rs +++ b/primitives/arithmetic/src/lib.rs @@ -41,7 +41,10 @@ pub mod rational; pub mod traits; pub use fixed_point::{FixedI128, FixedI64, FixedPointNumber, FixedPointOperand, FixedU128}; -pub use per_things::{InnerOf, PerThing, PerU16, Perbill, Percent, Permill, Perquintill, UpperOf}; +pub use per_things::{ + InnerOf, PerThing, PerU16, Perbill, Percent, Permill, Perquintill, Rounding, SignedRounding, + UpperOf, +}; pub use rational::{Rational128, RationalInfinite}; use sp_std::{cmp::Ordering, fmt::Debug, prelude::*}; diff --git a/primitives/arithmetic/src/per_things.rs b/primitives/arithmetic/src/per_things.rs index 3851270b8d4db..7f39d35774190 100644 --- a/primitives/arithmetic/src/per_things.rs +++ b/primitives/arithmetic/src/per_things.rs @@ -26,7 +26,7 @@ use codec::{CompactAs, Encode}; use num_traits::{Pow, SaturatingAdd, SaturatingSub}; use sp_std::{ fmt, ops, - ops::{Add, Sub}, + ops::{Add, AddAssign, Div, Rem, Sub}, prelude::*, }; @@ -89,6 +89,40 @@ pub trait PerThing: self.deconstruct() == Self::ACCURACY } + /// Return the next lower value to `self` or `self` if it is already zero. + fn less_epsilon(self) -> Self { + if self.is_zero() { + return self + } + Self::from_parts(self.deconstruct() - One::one()) + } + + /// Return the next lower value to `self` or an error with the same value if `self` is already + /// zero. + fn try_less_epsilon(self) -> Result { + if self.is_zero() { + return Err(self) + } + Ok(Self::from_parts(self.deconstruct() - One::one())) + } + + /// Return the next higher value to `self` or `self` if it is already one. + fn plus_epsilon(self) -> Self { + if self.is_one() { + return self + } + Self::from_parts(self.deconstruct() + One::one()) + } + + /// Return the next higher value to `self` or an error with the same value if `self` is already + /// one. + fn try_plus_epsilon(self) -> Result { + if self.is_one() { + return Err(self) + } + Ok(Self::from_parts(self.deconstruct() + One::one())) + } + /// Build this type from a percent. Equivalent to `Self::from_parts(x * Self::ACCURACY / 100)` /// but more accurate and can cope with potential type overflows. fn from_percent(x: Self::Inner) -> Self { @@ -188,7 +222,7 @@ pub trait PerThing: + Unsigned, Self::Inner: Into, { - saturating_reciprocal_mul::(b, self.deconstruct(), Rounding::Nearest) + saturating_reciprocal_mul::(b, self.deconstruct(), Rounding::NearestPrefUp) } /// Saturating multiplication by the reciprocal of `self`. The result is rounded down to the @@ -275,9 +309,9 @@ pub trait PerThing: /// # fn main () { /// // 989/1000 is technically closer to 99%. /// assert_eq!( - /// Percent::from_rational(989u64, 1000), - /// Percent::from_parts(98), - /// ); + /// Percent::from_rational(989u64, 1000), + /// Percent::from_parts(98), + /// ); /// # } /// ``` fn from_rational(p: N, q: N) -> Self @@ -289,7 +323,82 @@ pub trait PerThing: + ops::Div + ops::Rem + ops::Add - + Unsigned, + + ops::AddAssign + + Unsigned + + Zero + + One, + Self::Inner: Into, + { + Self::from_rational_with_rounding(p, q, Rounding::Down).unwrap_or_else(|_| Self::one()) + } + + /// Approximate the fraction `p/q` into a per-thing fraction. + /// + /// The computation of this approximation is performed in the generic type `N`. Given + /// `M` as the data type that can hold the maximum value of this per-thing (e.g. `u32` for + /// `Perbill`), this can only work if `N == M` or `N: From + TryInto`. + /// + /// In the case of an overflow (or divide by zero), an `Err` is returned. + /// + /// Rounding is determined by the parameter `rounding`, i.e. + /// + /// ```rust + /// # use sp_arithmetic::{Percent, PerThing, Rounding::*}; + /// # fn main () { + /// // 989/100 is technically closer to 99%. + /// assert_eq!( + /// Percent::from_rational_with_rounding(989u64, 1000, Down).unwrap(), + /// Percent::from_parts(98), + /// ); + /// assert_eq!( + /// Percent::from_rational_with_rounding(984u64, 1000, NearestPrefUp).unwrap(), + /// Percent::from_parts(98), + /// ); + /// assert_eq!( + /// Percent::from_rational_with_rounding(985u64, 1000, NearestPrefDown).unwrap(), + /// Percent::from_parts(98), + /// ); + /// assert_eq!( + /// Percent::from_rational_with_rounding(985u64, 1000, NearestPrefUp).unwrap(), + /// Percent::from_parts(99), + /// ); + /// assert_eq!( + /// Percent::from_rational_with_rounding(986u64, 1000, NearestPrefDown).unwrap(), + /// Percent::from_parts(99), + /// ); + /// assert_eq!( + /// Percent::from_rational_with_rounding(981u64, 1000, Up).unwrap(), + /// Percent::from_parts(99), + /// ); + /// assert_eq!( + /// Percent::from_rational_with_rounding(1001u64, 1000, Up), + /// Err(()), + /// ); + /// # } + /// ``` + /// + /// ```rust + /// # use sp_arithmetic::{Percent, PerThing, Rounding::*}; + /// # fn main () { + /// assert_eq!( + /// Percent::from_rational_with_rounding(981u64, 1000, Up).unwrap(), + /// Percent::from_parts(99), + /// ); + /// # } + /// ``` + fn from_rational_with_rounding(p: N, q: N, rounding: Rounding) -> Result + where + N: Clone + + Ord + + TryInto + + TryInto + + ops::Div + + ops::Rem + + ops::Add + + ops::AddAssign + + Unsigned + + Zero + + One, Self::Inner: Into; /// Same as `Self::from_rational`. @@ -303,6 +412,7 @@ pub trait PerThing: + ops::Div + ops::Rem + ops::Add + + ops::AddAssign + Unsigned + Zero + One, @@ -312,14 +422,54 @@ pub trait PerThing: } } -/// The rounding method to use. -/// -/// `PerThing`s are unsigned so `Up` means towards infinity and `Down` means towards zero. -/// `Nearest` will round an exact half down. -enum Rounding { +/// The rounding method to use for unsigned quantities. +#[derive(Copy, Clone, sp_std::fmt::Debug)] +pub enum Rounding { + // Towards infinity. Up, + // Towards zero. Down, - Nearest, + // Nearest integer, rounding as `Up` when equidistant. + NearestPrefUp, + // Nearest integer, rounding as `Down` when equidistant. + NearestPrefDown, +} + +/// The rounding method to use. +#[derive(Copy, Clone, sp_std::fmt::Debug)] +pub enum SignedRounding { + // Towards positive infinity. + High, + // Towards negative infinity. + Low, + // Nearest integer, rounding as `High` when exactly equidistant. + NearestPrefHigh, + // Nearest integer, rounding as `Low` when exactly equidistant. + NearestPrefLow, + // Away from zero (up when positive, down when negative). When positive, equivalent to `High`. + Major, + // Towards zero (down when positive, up when negative). When positive, equivalent to `Low`. + Minor, + // Nearest integer, rounding as `Major` when exactly equidistant. + NearestPrefMajor, + // Nearest integer, rounding as `Minor` when exactly equidistant. + NearestPrefMinor, +} + +impl Rounding { + /// Returns the value for `Rounding` which would give the same result ignorant of the sign. + pub const fn from_signed(rounding: SignedRounding, negative: bool) -> Self { + use Rounding::*; + use SignedRounding::*; + match (rounding, negative) { + (Low, true) | (Major, _) | (High, false) => Up, + (High, true) | (Minor, _) | (Low, false) => Down, + (NearestPrefMajor, _) | (NearestPrefHigh, false) | (NearestPrefLow, true) => + NearestPrefUp, + (NearestPrefMinor, _) | (NearestPrefLow, false) | (NearestPrefHigh, true) => + NearestPrefDown, + } + } } /// Saturating reciprocal multiplication. Compute `x / self`, saturating at the numeric @@ -397,18 +547,53 @@ where rem_mul_div_inner += 1.into(); } }, - // Round up if the fractional part of the result is greater than a half. An exact half is - // rounded down. - Rounding::Nearest => { + Rounding::NearestPrefDown => if rem_mul_upper % denom_upper > denom_upper / 2.into() { // `rem * numer / denom` is less than `numer`, so this will not overflow. rem_mul_div_inner += 1.into(); - } - }, + }, + Rounding::NearestPrefUp => + if rem_mul_upper % denom_upper >= denom_upper / 2.into() + denom_upper % 2.into() { + // `rem * numer / denom` is less than `numer`, so this will not overflow. + rem_mul_div_inner += 1.into(); + }, } rem_mul_div_inner.into() } +/// Just a simple generic integer divide with custom rounding. +fn div_rounded(n: N, d: N, r: Rounding) -> N +where + N: Clone + + Eq + + Ord + + Zero + + One + + AddAssign + + Add + + Rem + + Div, +{ + let mut o = n.clone() / d.clone(); + use Rounding::*; + let two = || N::one() + N::one(); + if match r { + Up => !((n % d).is_zero()), + NearestPrefDown => { + let rem = n % d.clone(); + rem > d / two() + }, + NearestPrefUp => { + let rem = n % d.clone(); + rem >= d.clone() / two() + d % two() + }, + Down => false, + } { + o += N::one() + } + o +} + macro_rules! implement_per_thing { ( $name:ident, @@ -423,7 +608,7 @@ macro_rules! implement_per_thing { /// #[doc = $title] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] - #[derive(Encode, Copy, Clone, PartialEq, Eq, codec::MaxEncodedLen, PartialOrd, Ord, sp_std::fmt::Debug, scale_info::TypeInfo)] + #[derive(Encode, Copy, Clone, PartialEq, Eq, codec::MaxEncodedLen, PartialOrd, Ord, scale_info::TypeInfo)] pub struct $name($type); /// Implementation makes any compact encoding of `PerThing::Inner` valid, @@ -445,6 +630,55 @@ macro_rules! implement_per_thing { } } + #[cfg(feature = "std")] + impl sp_std::fmt::Debug for $name { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + if $max == <$type>::max_value() { + // Not a power of ten: show as N/D and approx % + let pc = (self.0 as f64) / (self.0 as f64) * 100f64; + write!(fmt, "{:.2}% ({}/{})", pc, self.0, $max) + } else { + // A power of ten: calculate exact percent + let units = self.0 / ($max / 100); + let rest = self.0 % ($max / 100); + write!(fmt, "{}", units)?; + if rest > 0 { + write!(fmt, ".")?; + let mut m = $max / 100; + while rest % m > 0 { + m /= 10; + write!(fmt, "{:01}", rest / m % 10)?; + } + } + write!(fmt, "%") + } + } + } + + #[cfg(not(feature = "std"))] + impl sp_std::fmt::Debug for $name { + fn fmt(&self, fmt: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + if $max == <$type>::max_value() { + // Not a power of ten: show as N/D and approx % + write!(fmt, "{}/{}", self.0, $max) + } else { + // A power of ten: calculate exact percent + let units = self.0 / ($max / 100); + let rest = self.0 % ($max / 100); + write!(fmt, "{}", units)?; + if rest > 0 { + write!(fmt, ".")?; + let mut m = $max / 100; + while rest % m > 0 { + m /= 10; + write!(fmt, "{:01}", rest / m % 10)?; + } + } + write!(fmt, "%") + } + } + } + impl PerThing for $name { type Inner = $type; type Upper = $upper_type; @@ -463,53 +697,50 @@ macro_rules! implement_per_thing { Self::from_parts((x.max(0.).min(1.) * $max as f64) as Self::Inner) } - fn from_rational(p: N, q: N) -> Self + fn from_rational_with_rounding(p: N, q: N, r: Rounding) -> Result where - N: Clone + Ord + TryInto + TryInto - + ops::Div + ops::Rem + ops::Add + Unsigned - + Zero + One, - Self::Inner: Into, + N: Clone + + Ord + + TryInto + + TryInto + + ops::Div + + ops::Rem + + ops::Add + + ops::AddAssign + + Unsigned + + Zero + + One, + Self::Inner: Into { - let div_ceil = |x: N, f: N| -> N { - let mut o = x.clone() / f.clone(); - let r = x.rem(f.clone()); - if r > N::zero() { - o = o + N::one(); - } - o - }; - // q cannot be zero. - let q: N = q.max((1 as Self::Inner).into()); + if q.is_zero() { return Err(()) } // p should not be bigger than q. - let p: N = p.min(q.clone()); + if p > q { return Err(()) } - let factor: N = div_ceil(q.clone(), $max.into()).max((1 as Self::Inner).into()); + let factor = div_rounded::(q.clone(), $max.into(), Rounding::Up).max(One::one()); // q cannot overflow: (q / (q/$max)) < $max. p < q hence p also cannot overflow. - let q_reduce: $type = (q.clone() / factor.clone()) + let q_reduce: $type = div_rounded(q, factor.clone(), r) .try_into() .map_err(|_| "Failed to convert") .expect( - "q / ceil(q/$max) < $max. Macro prevents any type being created that \ + "`q / ceil(q/$max) < $max`; macro prevents any type being created that \ does not satisfy this; qed" ); - let p_reduce: $type = (p / factor) + let p_reduce: $type = div_rounded(p, factor, r) .try_into() .map_err(|_| "Failed to convert") .expect( - "q / ceil(q/$max) < $max. Macro prevents any type being created that \ + "`p / ceil(p/$max) < $max`; macro prevents any type being created that \ does not satisfy this; qed" ); - // `p_reduced` and `q_reduced` are withing Self::Inner. Mul by another $max will - // always fit in $upper_type. This is guaranteed by the macro tests. - let part = - p_reduce as $upper_type - * <$upper_type>::from($max) - / q_reduce as $upper_type; - - $name(part as Self::Inner) + // `p_reduced` and `q_reduced` are within `Self::Inner`. Multiplication by another + // `$max` will always fit in `$upper_type`. This is guaranteed by the macro tests. + let n = p_reduce as $upper_type * <$upper_type>::from($max); + let d = q_reduce as $upper_type; + let part = div_rounded(n, d, r); + Ok($name(part as Self::Inner)) } } @@ -570,24 +801,52 @@ macro_rules! implement_per_thing { /// See [`PerThing::from_rational`]. #[deprecated = "Use `PerThing::from_rational` instead"] pub fn from_rational_approximation(p: N, q: N) -> Self - where N: Clone + Ord + TryInto<$type> + - TryInto<$upper_type> + ops::Div + ops::Rem + - ops::Add + Unsigned, - $type: Into, + where + N: Clone + + Ord + + TryInto<$type> + + TryInto<$upper_type> + + ops::Div + + ops::Rem + + ops::Add + + ops::AddAssign + + Unsigned + + Zero + + One, + $type: Into { ::from_rational(p, q) } /// See [`PerThing::from_rational`]. pub fn from_rational(p: N, q: N) -> Self - where N: Clone + Ord + TryInto<$type> + - TryInto<$upper_type> + ops::Div + ops::Rem + - ops::Add + Unsigned, - $type: Into, + where + N: Clone + + Ord + + TryInto<$type> + + TryInto<$upper_type> + + ops::Div + + ops::Rem + + ops::Add + + ops::AddAssign + + Unsigned + + Zero + + One, + $type: Into { ::from_rational(p, q) } + /// Integer multiplication with another value, saturating at 1. + pub fn int_mul(self, b: $type) -> Self { + PerThing::from_parts(self.0.saturating_mul(b)) + } + + /// Integer division with another value, rounding down. + pub fn int_div(self, b: Self) -> $type { + self.0 / b.0 + } + /// See [`PerThing::mul_floor`]. pub fn mul_floor(self, b: N) -> N where @@ -643,6 +902,38 @@ macro_rules! implement_per_thing { { PerThing::saturating_reciprocal_mul_ceil(self, b) } + + /// Saturating division. Compute `self / rhs`, saturating at one if `rhs < self`. + /// + /// The `rounding` method must be specified. e.g.: + /// + /// ```rust + /// # use sp_arithmetic::{Percent, PerThing, Rounding::*}; + /// # fn main () { + /// let pc = |x| Percent::from_percent(x); + /// assert_eq!( + /// pc(2).saturating_div(pc(3), Down), + /// pc(66), + /// ); + /// assert_eq!( + /// pc(1).saturating_div(pc(3), NearestPrefUp), + /// pc(33), + /// ); + /// assert_eq!( + /// pc(2).saturating_div(pc(3), NearestPrefDown), + /// pc(67), + /// ); + /// assert_eq!( + /// pc(1).saturating_div(pc(3), Up), + /// pc(34), + /// ); + /// # } + /// ``` + pub fn saturating_div(self, rhs: Self, r: Rounding) -> Self { + let p = self.0; + let q = rhs.0; + Self::from_rational_with_rounding(p, q, r).unwrap_or_else(|_| Self::one()) + } } impl Saturating for $name { @@ -756,7 +1047,7 @@ macro_rules! implement_per_thing { { type Output = N; fn mul(self, b: N) -> Self::Output { - overflow_prune_mul::(b, self.deconstruct(), Rounding::Nearest) + overflow_prune_mul::(b, self.deconstruct(), Rounding::NearestPrefDown) } } @@ -903,6 +1194,11 @@ macro_rules! implement_per_thing { } } + #[test] + fn from_parts_cannot_overflow() { + assert_eq!(<$name>::from_parts($max.saturating_add(1)), <$name>::one()); + } + #[test] fn has_max_encoded_len() { struct AsMaxEncodedLen { @@ -1040,10 +1336,10 @@ macro_rules! implement_per_thing { #[test] fn per_thing_mul_rounds_to_nearest_number() { - assert_eq!($name::from_float(0.33) * 10u64, 3); - assert_eq!($name::from_float(0.34) * 10u64, 3); - assert_eq!($name::from_float(0.35) * 10u64, 3); - assert_eq!($name::from_float(0.36) * 10u64, 4); + assert_eq!($name::from_percent(33) * 10u64, 3); + assert_eq!($name::from_percent(34) * 10u64, 3); + assert_eq!($name::from_percent(35) * 10u64, 3); + assert_eq!($name::from_percent(36) * 10u64, 4); } #[test] @@ -1351,7 +1647,7 @@ macro_rules! implement_per_thing { <$type>::max_value(), <$type>::max_value(), <$type>::max_value(), - super::Rounding::Nearest, + super::Rounding::NearestPrefDown, ), 0, ); @@ -1360,7 +1656,7 @@ macro_rules! implement_per_thing { <$type>::max_value() - 1, <$type>::max_value(), <$type>::max_value(), - super::Rounding::Nearest, + super::Rounding::NearestPrefDown, ), <$type>::max_value() - 1, ); @@ -1369,7 +1665,7 @@ macro_rules! implement_per_thing { ((<$type>::max_value() - 1) as $upper_type).pow(2), <$type>::max_value(), <$type>::max_value(), - super::Rounding::Nearest, + super::Rounding::NearestPrefDown, ), 1, ); @@ -1379,7 +1675,7 @@ macro_rules! implement_per_thing { (<$type>::max_value() as $upper_type).pow(2) - 1, <$type>::max_value(), <$type>::max_value(), - super::Rounding::Nearest, + super::Rounding::NearestPrefDown, ), <$upper_type>::from((<$type>::max_value() - 1)), ); @@ -1389,7 +1685,7 @@ macro_rules! implement_per_thing { (<$type>::max_value() as $upper_type).pow(2), <$type>::max_value(), 2 as $type, - super::Rounding::Nearest, + super::Rounding::NearestPrefDown, ), <$type>::max_value() as $upper_type / 2, ); @@ -1399,7 +1695,7 @@ macro_rules! implement_per_thing { (<$type>::max_value() as $upper_type).pow(2) - 1, 2 as $type, <$type>::max_value(), - super::Rounding::Nearest, + super::Rounding::NearestPrefDown, ), 2, ); @@ -1586,6 +1882,33 @@ macro_rules! implement_per_thing_with_perthousand { } } +#[test] +fn from_rational_with_rounding_works_in_extreme_case() { + use Rounding::*; + for &r in [Down, NearestPrefDown, NearestPrefUp, Up].iter() { + Percent::from_rational_with_rounding(1, u64::max_value(), r).unwrap(); + Percent::from_rational_with_rounding(1, u32::max_value(), r).unwrap(); + Percent::from_rational_with_rounding(1, u16::max_value(), r).unwrap(); + Percent::from_rational_with_rounding(u64::max_value() - 1, u64::max_value(), r).unwrap(); + Percent::from_rational_with_rounding(u32::max_value() - 1, u32::max_value(), r).unwrap(); + Percent::from_rational_with_rounding(u16::max_value() - 1, u16::max_value(), r).unwrap(); + PerU16::from_rational_with_rounding(1, u64::max_value(), r).unwrap(); + PerU16::from_rational_with_rounding(1, u32::max_value(), r).unwrap(); + PerU16::from_rational_with_rounding(1, u16::max_value(), r).unwrap(); + PerU16::from_rational_with_rounding(u64::max_value() - 1, u64::max_value(), r).unwrap(); + PerU16::from_rational_with_rounding(u32::max_value() - 1, u32::max_value(), r).unwrap(); + PerU16::from_rational_with_rounding(u16::max_value() - 1, u16::max_value(), r).unwrap(); + Permill::from_rational_with_rounding(1, u64::max_value(), r).unwrap(); + Permill::from_rational_with_rounding(1, u32::max_value(), r).unwrap(); + Permill::from_rational_with_rounding(u64::max_value() - 1, u64::max_value(), r).unwrap(); + Permill::from_rational_with_rounding(u32::max_value() - 1, u32::max_value(), r).unwrap(); + Perbill::from_rational_with_rounding(1, u64::max_value(), r).unwrap(); + Perbill::from_rational_with_rounding(1, u32::max_value(), r).unwrap(); + Perbill::from_rational_with_rounding(u64::max_value() - 1, u64::max_value(), r).unwrap(); + Perbill::from_rational_with_rounding(u32::max_value() - 1, u32::max_value(), r).unwrap(); + } +} + implement_per_thing!(Percent, test_per_cent, [u32, u64, u128], 100u8, u8, u16, "_Percent_",); implement_per_thing_with_perthousand!( PerU16, diff --git a/primitives/arithmetic/src/traits.rs b/primitives/arithmetic/src/traits.rs index 748aaed2a7cf5..466d5696c7136 100644 --- a/primitives/arithmetic/src/traits.rs +++ b/primitives/arithmetic/src/traits.rs @@ -58,6 +58,7 @@ pub trait BaseArithmetic: + Bounded + HasCompact + Sized + + Clone + TryFrom + TryInto + TryFrom @@ -113,6 +114,7 @@ impl< + Bounded + HasCompact + Sized + + Clone + TryFrom + TryInto + TryFrom From 22fd0a9c012dc626db11ca46cb91cec73b66505a Mon Sep 17 00:00:00 2001 From: mikolaichuk <45576473+mikolaichuk@users.noreply.github.com> Date: Tue, 31 May 2022 19:34:41 +0600 Subject: [PATCH 281/484] change impl FnOnce() to generic type + trait bound (#11534) * change impl FnOnce() to generic type + trait bound with_transaction() function can not be used with explicit generic arguments because of this issue: https://github.com/rust-lang/rust/issues/83701 * make the same changes elsewhere Co-authored-by: Shawn Tabrizi --- frame/support/src/storage/transactional.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/frame/support/src/storage/transactional.rs b/frame/support/src/storage/transactional.rs index b02552bdb0e14..d1bdb30af947b 100644 --- a/frame/support/src/storage/transactional.rs +++ b/frame/support/src/storage/transactional.rs @@ -101,9 +101,10 @@ pub fn is_transactional() -> bool { /// error. /// /// Commits happen to the parent transaction. -pub fn with_transaction(f: impl FnOnce() -> TransactionOutcome>) -> Result +pub fn with_transaction(f: F) -> Result where E: From, + F: FnOnce() -> TransactionOutcome>, { // This needs to happen before `start_transaction` below. // Otherwise we may rollback the increase, then decrease as the guard goes out of scope @@ -129,7 +130,10 @@ where /// This is mostly for backwards compatibility before there was a transactional layer limit. /// It is recommended to only use [`with_transaction`] to avoid users from generating too many /// transactional layers. -pub fn with_transaction_unchecked(f: impl FnOnce() -> TransactionOutcome) -> R { +pub fn with_transaction_unchecked(f: F) -> R +where + F: FnOnce() -> TransactionOutcome, +{ // This needs to happen before `start_transaction` below. // Otherwise we may rollback the increase, then decrease as the guard goes out of scope // and then end in some bad state. @@ -163,9 +167,10 @@ pub fn with_transaction_unchecked(f: impl FnOnce() -> TransactionOutcome) /// This is the same as `with_transaction`, but assuming that any function returning /// an `Err` should rollback, and any function returning `Ok` should commit. This /// provides a cleaner API to the developer who wants this behavior. -pub fn with_storage_layer(f: impl FnOnce() -> Result) -> Result +pub fn with_storage_layer(f: F) -> Result where E: From, + F: FnOnce() -> Result, { with_transaction(|| { let r = f(); @@ -181,9 +186,10 @@ where /// /// If we are already in a storage layer, we just execute the provided closure. /// If we are not, we execute the closure within a [`with_storage_layer`]. -pub fn in_storage_layer(f: impl FnOnce() -> Result) -> Result +pub fn in_storage_layer(f: F) -> Result where E: From, + F: FnOnce() -> Result, { if is_transactional() { f() From 1fdefce2ebe5b11859329e0a9a4a4c4fc8b1cd60 Mon Sep 17 00:00:00 2001 From: Vlad Date: Tue, 31 May 2022 18:10:04 +0300 Subject: [PATCH 282/484] Introduce `rusty-cachier` (#11462) * Introduce `rusty-cachier` * Return LF at the end of file * Use `entrypoint` to `unshare(1)` into a new mount namespace * Use `rusty-cachier`-provided absolute path for `CARGO_TARGET_DIR` everywhere * Debug single `build-rustdoc` job * CI: debug * CI: debug * CI: debug * `unshare(1)` is no longer needed * CI: remove debug * Revert "Debug single `build-rustdoc` job" * Formatiing * Update scripts/ci/gitlab/pipeline/build.yml Co-authored-by: Denis Pisarev --- .gitlab-ci.yml | 14 ++++++++ scripts/ci/gitlab/pipeline/build.yml | 32 +++++++++++++++--- scripts/ci/gitlab/pipeline/test.yml | 49 +++++++++++++++++++++++++++- 3 files changed, 90 insertions(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 87e7fa6fc61b0..fa3b51caefa14 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -47,6 +47,8 @@ variables: &default-vars DOCKER_OS: "debian:stretch" ARCH: "x86_64" CI_IMAGE: "paritytech/ci-linux:production" + RUSTY_CACHIER_SINGLE_BRANCH: master + RUSTY_CACHIER_DONT_OPERATE_ON_MAIN_BRANCH: "true" default: retry: @@ -86,10 +88,22 @@ default: - rustup +nightly show - cargo +nightly --version +.rusty-cachier: + before_script: + - curl -s https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.parity.io/parity/infrastructure/ci_cd/rusty-cachier/client/-/raw/release/util/install.sh | bash + - rusty-cachier environment check --gracefully + - $(rusty-cachier environment inject) + - rusty-cachier project mtime + after_script: + - rusty-cachier snapshot destroy + .docker-env: image: "${CI_IMAGE}" before_script: - !reference [.rust-info-script, script] + - !reference [.rusty-cachier, before_script] + after_script: + - !reference [.rusty-cachier, after_script] tags: - linux-docker diff --git a/scripts/ci/gitlab/pipeline/build.yml b/scripts/ci/gitlab/pipeline/build.yml index e896eac7238b1..a07fe8a4cfa07 100644 --- a/scripts/ci/gitlab/pipeline/build.yml +++ b/scripts/ci/gitlab/pipeline/build.yml @@ -55,9 +55,11 @@ build-linux-substrate: artifacts: false before_script: - mkdir -p ./artifacts/substrate/ + - !reference [.rusty-cachier, before_script] script: + - rusty-cachier snapshot create - WASM_BUILD_NO_COLOR=1 time cargo build --release --verbose - - mv ./target/release/substrate ./artifacts/substrate/. + - mv /cargo_target_dir/release/substrate ./artifacts/substrate/. - echo -n "Substrate version = " - if [ "${CI_COMMIT_TAG}" ]; then echo "${CI_COMMIT_TAG}" | tee ./artifacts/substrate/VERSION; @@ -69,6 +71,7 @@ build-linux-substrate: - cp -r ./scripts/ci/docker/substrate.Dockerfile ./artifacts/substrate/ - printf '\n# building node-template\n\n' - ./scripts/ci/node-template-release.sh ./artifacts/substrate/substrate-node-template.tar.gz + - rusty-cachier cache upload .build-subkey: stage: build @@ -81,23 +84,41 @@ build-linux-substrate: artifacts: false before_script: - mkdir -p ./artifacts/subkey + - !reference [.rusty-cachier, before_script] script: + - rusty-cachier snapshot create - cd ./bin/utils/subkey - SKIP_WASM_BUILD=1 time cargo build --release --verbose - cd - - - mv ./target/release/subkey ./artifacts/subkey/. + - mv /cargo_target_dir/release/subkey ./artifacts/subkey/. - echo -n "Subkey version = " - ./artifacts/subkey/subkey --version | sed -n -E 's/^subkey ([0-9.]+.*)/\1/p' | tee ./artifacts/subkey/VERSION; - sha256sum ./artifacts/subkey/subkey | tee ./artifacts/subkey/subkey.sha256 - cp -r ./scripts/ci/docker/subkey.Dockerfile ./artifacts/subkey/ + - rusty-cachier cache upload build-subkey-linux: extends: .build-subkey build-subkey-macos: extends: .build-subkey + # duplicating before_script & script sections from .build-subkey hidden job + # to overwrite rusty-cachier integration as it doesn't work on macos + before_script: + - mkdir -p ./artifacts/subkey + script: + - cd ./bin/utils/subkey + - SKIP_WASM_BUILD=1 time cargo build --release --verbose + - cd - + - mv ./target/release/subkey ./artifacts/subkey/. + - echo -n "Subkey version = " + - ./artifacts/subkey/subkey --version | + sed -n -E 's/^subkey ([0-9.]+.*)/\1/p' | + tee ./artifacts/subkey/VERSION; + - sha256sum ./artifacts/subkey/subkey | tee ./artifacts/subkey/subkey.sha256 + - cp -r ./scripts/ci/docker/subkey.Dockerfile ./artifacts/subkey/ tags: - osx @@ -109,6 +130,7 @@ build-rustdoc: variables: SKIP_WASM_BUILD: 1 DOC_INDEX_PAGE: "sc_service/index.html" # default redirected page + RUSTY_CACHIER_TOOLCHAIN: nightly artifacts: name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}-doc" when: on_success @@ -116,9 +138,11 @@ build-rustdoc: paths: - ./crate-docs/ script: + - rusty-cachier snapshot create - time cargo +nightly doc --workspace --all-features --verbose - - rm -f ./target/doc/.lock - - mv ./target/doc ./crate-docs + - rm -f /cargo_target_dir/doc/.lock + - mv /cargo_target_dir/doc ./crate-docs # FIXME: remove me after CI image gets nonroot - chown -R nonroot:nonroot ./crate-docs - echo "" > ./crate-docs/index.html + - rusty-cachier cache upload diff --git a/scripts/ci/gitlab/pipeline/test.yml b/scripts/ci/gitlab/pipeline/test.yml index 769d1cd30d0d5..eab673a5ce13c 100644 --- a/scripts/ci/gitlab/pipeline/test.yml +++ b/scripts/ci/gitlab/pipeline/test.yml @@ -7,8 +7,11 @@ cargo-deny: - .docker-env - .nightly-pipeline script: + - rusty-cachier snapshot create - cargo deny check --hide-inclusion-graph -c ./scripts/ci/deny.toml + - rusty-cachier cache upload after_script: + - !reference [.rusty-cachier, after_script] - echo "___The complete log is in the artifacts___" - cargo deny check -c ./scripts/ci/deny.toml 2> deny.log artifacts: @@ -22,11 +25,15 @@ cargo-deny: cargo-fmt: stage: test + variables: + RUSTY_CACHIER_TOOLCHAIN: nightly extends: - .docker-env - .test-refs script: + - rusty-cachier snapshot create - cargo +nightly fmt --all -- --check + - rusty-cachier cache upload cargo-clippy: stage: test @@ -34,11 +41,15 @@ cargo-clippy: needs: - job: cargo-fmt artifacts: false + variables: + RUSTY_CACHIER_TOOLCHAIN: nightly extends: - .docker-env - .test-refs script: + - rusty-cachier snapshot create - SKIP_WASM_BUILD=1 env -u RUSTFLAGS cargo +nightly clippy --all-targets + - rusty-cachier cache upload cargo-check-nixos: stage: test @@ -62,6 +73,8 @@ cargo-check-nixos: cargo-check-benches: stage: test + variables: + RUSTY_CACHIER_TOOLCHAIN: nightly extends: - .docker-env - .test-refs @@ -76,7 +89,9 @@ cargo-check-benches: git merge $CI_COMMIT_REF_NAME --verbose --no-edit; fi - !reference [.rust-info-script, script] + - !reference [.rusty-cachier, before_script] script: + - rusty-cachier snapshot create - mkdir -p ./artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA - SKIP_WASM_BUILD=1 time cargo +nightly check --benches --all - 'cargo run --release -p node-bench -- ::node::import::native::sr25519::transfer_keep_alive::paritydb::small --json @@ -84,6 +99,7 @@ cargo-check-benches: - 'cargo run --release -p node-bench -- ::trie::read::small --json | tee ./artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA/::trie::read::small.json' - sccache -s + - rusty-cachier cache upload tags: - linux-docker-benches @@ -120,8 +136,10 @@ cargo-check-subkey: - .docker-env - .test-refs script: + - rusty-cachier snapshot create - cd ./bin/utils/subkey - SKIP_WASM_BUILD=1 time cargo check --release + - rusty-cachier cache upload cargo-check-try-runtime: stage: test @@ -133,7 +151,9 @@ cargo-check-try-runtime: - .docker-env - .test-refs script: + - rusty-cachier snapshot create - time cargo check --features try-runtime + - rusty-cachier cache upload cargo-check-wasmer-sandbox: stage: test @@ -145,7 +165,9 @@ cargo-check-wasmer-sandbox: - .docker-env - .test-refs script: + - rusty-cachier snapshot create - time cargo check --features wasmer-sandbox + - rusty-cachier cache upload test-deterministic-wasm: stage: test @@ -159,16 +181,18 @@ test-deterministic-wasm: variables: WASM_BUILD_NO_COLOR: 1 script: + - rusty-cachier snapshot create # build runtime - cargo build --verbose --release -p node-runtime # make checksum - - sha256sum ./target/release/wbuild/node-runtime/target/wasm32-unknown-unknown/release/node_runtime.wasm > checksum.sha256 + - sha256sum /cargo_target_dir/release/wbuild/node-runtime/target/wasm32-unknown-unknown/release/node_runtime.wasm > checksum.sha256 # clean up – FIXME: can we reuse some of the artifacts? - cargo clean # build again - cargo build --verbose --release -p node-runtime # confirm checksum - sha256sum -c ./checksum.sha256 + - rusty-cachier cache upload test-linux-stable: stage: test @@ -184,14 +208,18 @@ test-linux-stable: # Ensure we run the UI tests. RUN_UI_TESTS: 1 script: + - rusty-cachier snapshot create # this job runs all tests in former runtime-benchmarks, frame-staking and wasmtime tests - time cargo test --workspace --locked --release --verbose --features runtime-benchmarks --manifest-path ./bin/node/cli/Cargo.toml - time cargo test -p frame-support-test --features=conditional-storage,no-metadata-docs --manifest-path ./frame/support/test/Cargo.toml --test pallet # does not reuse cache 1 min 44 sec - SUBSTRATE_TEST_TIMEOUT=1 time cargo test -p substrate-test-utils --release --verbose --locked -- --ignored timeout + - rusty-cachier cache upload test-frame-examples-compile-to-wasm: # into one job stage: test + variables: + RUSTY_CACHIER_TOOLCHAIN: nightly extends: - .docker-env - .test-refs @@ -201,10 +229,12 @@ test-frame-examples-compile-to-wasm: RUSTFLAGS: "-Cdebug-assertions=y" RUST_BACKTRACE: 1 script: + - rusty-cachier snapshot create - cd ./frame/examples/offchain-worker/ - cargo +nightly build --target=wasm32-unknown-unknown --no-default-features - cd ../basic - cargo +nightly build --target=wasm32-unknown-unknown --no-default-features + - rusty-cachier cache upload test-linux-stable-int: stage: test @@ -220,13 +250,16 @@ test-linux-stable-int: # Ensure we run the UI tests. RUN_UI_TESTS: 1 script: + - rusty-cachier snapshot create - echo "___Logs will be partly shown at the end in case of failure.___" - echo "___Full log will be saved to the job artifacts only in case of failure.___" - WASM_BUILD_NO_COLOR=1 RUST_LOG=sync=trace,consensus=trace,client=trace,state-db=trace,db=trace,forks=trace,state_db=trace,storage_cache=trace time cargo test -p node-cli --release --verbose --locked -- --ignored &> ${CI_COMMIT_SHORT_SHA}_int_failure.log + - rusty-cachier cache upload after_script: + - !reference [.rusty-cachier, after_script] - awk '/FAILED|^error\[/,0' ${CI_COMMIT_SHORT_SHA}_int_failure.log artifacts: name: $CI_COMMIT_SHORT_SHA @@ -241,13 +274,17 @@ check-tracing: needs: - job: test-linux-stable-int artifacts: false + variables: + RUSTY_CACHIER_TOOLCHAIN: nightly extends: - .docker-env - .test-refs script: + - rusty-cachier snapshot create # with-tracing must be explicitly activated, we run a test to ensure this works as expected in both cases - time cargo +nightly test --manifest-path ./primitives/tracing/Cargo.toml --no-default-features - time cargo +nightly test --manifest-path ./primitives/tracing/Cargo.toml --no-default-features --features=with-tracing + - rusty-cachier cache upload test-full-crypto-feature: stage: test @@ -255,6 +292,8 @@ test-full-crypto-feature: needs: - job: check-tracing artifacts: false + variables: + RUSTY_CACHIER_TOOLCHAIN: nightly extends: - .docker-env - .test-refs @@ -264,10 +303,12 @@ test-full-crypto-feature: RUSTFLAGS: "-Cdebug-assertions=y" RUST_BACKTRACE: 1 script: + - rusty-cachier snapshot create - cd primitives/core/ - time cargo +nightly build --verbose --no-default-features --features full_crypto - cd ../application-crypto - time cargo +nightly build --verbose --no-default-features --features full_crypto + - rusty-cachier cache upload test-wasmer-sandbox: stage: test @@ -275,7 +316,9 @@ test-wasmer-sandbox: - .docker-env - .test-refs-wasmer-sandbox script: + - rusty-cachier snapshot create - time cargo test --release --features runtime-benchmarks,wasmer-sandbox,disable-ui-tests + - rusty-cachier cache upload cargo-check-macos: stage: test @@ -289,6 +332,8 @@ cargo-check-macos: check-rustdoc: stage: test + variables: + RUSTY_CACHIER_TOOLCHAIN: nightly extends: - .docker-env - .test-refs @@ -296,4 +341,6 @@ check-rustdoc: SKIP_WASM_BUILD: 1 RUSTDOCFLAGS: "-Dwarnings" script: + - rusty-cachier snapshot create - time cargo +nightly doc --workspace --all-features --verbose --no-deps + - rusty-cachier cache upload From dec9c866d6d113c89e3d319d563f78e8d2cadf26 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 31 May 2022 18:45:07 +0200 Subject: [PATCH 283/484] Clean up `#[transactional]` (#11546) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Deprecate #[transactional] attribute Signed-off-by: Oliver Tale-Yazdi * Remove #[transactional] from nomination pools Signed-off-by: Oliver Tale-Yazdi * Review fix Co-authored-by: Bastian Köcher * Fix NOOP test Signed-off-by: Oliver Tale-Yazdi * Suppress warnings Signed-off-by: Oliver Tale-Yazdi Co-authored-by: Bastian Köcher --- frame/nomination-pools/src/lib.rs | 9 +-------- frame/nomination-pools/src/tests.rs | 12 ++++++++---- frame/support/procedural/src/lib.rs | 1 + frame/support/src/dispatch.rs | 1 + frame/support/test/tests/storage_transaction.rs | 3 +++ 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index ff77949cf6952..c9d811fa4a22f 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1082,7 +1082,7 @@ impl Get for TotalUnbondingPools { #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::{traits::StorageVersion, transactional}; + use frame_support::traits::StorageVersion; use frame_system::{ensure_signed, pallet_prelude::*}; /// The current storage version. @@ -1349,7 +1349,6 @@ pub mod pallet { /// `existential deposit + amount` in their account. /// * Only a pool with [`PoolState::Open`] can be joined #[pallet::weight(T::WeightInfo::join())] - #[transactional] pub fn join( origin: OriginFor, #[pallet::compact] amount: BalanceOf, @@ -1410,7 +1409,6 @@ pub mod pallet { T::WeightInfo::bond_extra_transfer() .max(T::WeightInfo::bond_extra_reward()) )] - #[transactional] pub fn bond_extra(origin: OriginFor, extra: BondExtra>) -> DispatchResult { let who = ensure_signed(origin)?; let (mut member, mut bonded_pool, mut reward_pool) = Self::get_member_with_pools(&who)?; @@ -1449,7 +1447,6 @@ pub mod pallet { /// The member will earn rewards pro rata based on the members stake vs the sum of the /// members in the pools stake. Rewards do not "expire". #[pallet::weight(T::WeightInfo::claim_payout())] - #[transactional] pub fn claim_payout(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; let (mut member, mut bonded_pool, mut reward_pool) = Self::get_member_with_pools(&who)?; @@ -1489,7 +1486,6 @@ pub mod pallet { /// there are too many unlocking chunks, the result of this call will likely be the /// `NoMoreChunks` error from the staking system. #[pallet::weight(T::WeightInfo::unbond())] - #[transactional] pub fn unbond( origin: OriginFor, member_account: T::AccountId, @@ -1564,7 +1560,6 @@ pub mod pallet { /// would probably see an error like `NoMoreChunks` emitted from the staking system when /// they attempt to unbond. #[pallet::weight(T::WeightInfo::pool_withdraw_unbonded(*num_slashing_spans))] - #[transactional] pub fn pool_withdraw_unbonded( origin: OriginFor, pool_id: PoolId, @@ -1601,7 +1596,6 @@ pub mod pallet { #[pallet::weight( T::WeightInfo::withdraw_unbonded_kill(*num_slashing_spans) )] - #[transactional] pub fn withdraw_unbonded( origin: OriginFor, member_account: T::AccountId, @@ -1717,7 +1711,6 @@ pub mod pallet { /// In addition to `amount`, the caller will transfer the existential deposit; so the caller /// needs at have at least `amount + existential_deposit` transferrable. #[pallet::weight(T::WeightInfo::create())] - #[transactional] pub fn create( origin: OriginFor, #[pallet::compact] amount: BalanceOf, diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index c16c1c9da9e90..93218c78c83fc 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -21,6 +21,7 @@ use frame_support::{ assert_noop, assert_ok, assert_storage_noop, bounded_btree_map, storage::{with_transaction, TransactionOutcome}, }; +use sp_runtime::traits::Dispatchable; macro_rules! unbonding_pools_with_era { ($($k:expr => $v:expr),* $(,)?) => {{ @@ -3256,10 +3257,13 @@ mod create { Balances::make_free_balance_be(&11, 5 + 20); // Then - assert_noop!( - Pools::create(Origin::signed(11), 20, 11, 11, 11), - Error::::MaxPoolMembers - ); + let create = Call::Pools(crate::Call::::create { + amount: 20, + root: 11, + nominator: 11, + state_toggler: 11, + }); + assert_noop!(create.dispatch(Origin::signed(11)), Error::::MaxPoolMembers); }); } } diff --git a/frame/support/procedural/src/lib.rs b/frame/support/procedural/src/lib.rs index 00204b7a4d906..418fad56b7674 100644 --- a/frame/support/procedural/src/lib.rs +++ b/frame/support/procedural/src/lib.rs @@ -429,6 +429,7 @@ pub fn pallet(attr: TokenStream, item: TokenStream) -> TokenStream { /// } /// ``` #[proc_macro_attribute] +#[deprecated(note = "This is now the default behaviour for all extrinsics.")] pub fn transactional(attr: TokenStream, input: TokenStream) -> TokenStream { transactional::transactional(attr, input).unwrap_or_else(|e| e.to_compile_error().into()) } diff --git a/frame/support/src/dispatch.rs b/frame/support/src/dispatch.rs index 51504c9c36e38..0a5092b411930 100644 --- a/frame/support/src/dispatch.rs +++ b/frame/support/src/dispatch.rs @@ -200,6 +200,7 @@ impl Parameter for T where T: Codec + EncodeLike + Clone + Eq + fmt::Debug + /// /// Transactional function discards all changes to storage if it returns `Err`, or commits if /// `Ok`, via the #\[transactional\] attribute. Note the attribute must be after #\[weight\]. +/// The #\[transactional\] attribute is deprecated since it is the default behaviour. /// /// ``` /// # #[macro_use] diff --git a/frame/support/test/tests/storage_transaction.rs b/frame/support/test/tests/storage_transaction.rs index 848a91a7f5a86..9e597969d6c89 100644 --- a/frame/support/test/tests/storage_transaction.rs +++ b/frame/support/test/tests/storage_transaction.rs @@ -15,6 +15,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Disable warnings for #\[transactional\] being deprecated. +#![allow(deprecated)] + use frame_support::{ assert_noop, assert_ok, assert_storage_noop, dispatch::{DispatchError, DispatchResult}, From 8bee1ced7010765340663d96046b959f914a5887 Mon Sep 17 00:00:00 2001 From: Vlad Date: Tue, 31 May 2022 19:46:38 +0300 Subject: [PATCH 284/484] Add `rusty-cachier notify` job (#11554) * Add `rusty-cachier notify` job * Add comments --- .gitlab-ci.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fa3b51caefa14..935c5c1d40e86 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -34,6 +34,7 @@ stages: - build - publish - deploy + - notify workflow: rules: @@ -88,6 +89,8 @@ default: - rustup +nightly show - cargo +nightly --version +# rusty-cachier's hidden job. Parts of this job are used to instrument the pipeline's other real jobs with rusty-cachier +# Description of the commands is available here - https://gitlab.parity.io/parity/infrastructure/ci_cd/rusty-cachier/client#description .rusty-cachier: before_script: - curl -s https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.parity.io/parity/infrastructure/ci_cd/rusty-cachier/client/-/raw/release/util/install.sh | bash @@ -216,6 +219,16 @@ deploy-prometheus-alerting-rules: - .gitlab-ci.yml - ./scripts/ci/monitoring/**/* +#### stage: notify + +# This job notifies rusty-cachier about the latest commit with the cache. +# This info is later used for the cache distribution and an overlay creation. +rusty-cachier-notify: + stage: notify + <<: *docker-env + script: + - rusty-cachier cache notify + #### stage: .post # This job cancels the whole pipeline if any of provided jobs fail. From 07b2bde537c6047b3a593440f9c593c7ac57b1bc Mon Sep 17 00:00:00 2001 From: Vlad Date: Tue, 31 May 2022 20:07:49 +0300 Subject: [PATCH 285/484] Fix CI after `rusty-cachier` introduction (#11556) * Fix CI after `rusty-cachier` introduction * Replace anchor with `extends` --- .gitlab-ci.yml | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 935c5c1d40e86..e218a2c1f8d3a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -89,6 +89,16 @@ default: - rustup +nightly show - cargo +nightly --version +.docker-env: + image: "${CI_IMAGE}" + before_script: + - !reference [.rust-info-script, script] + - !reference [.rusty-cachier, before_script] + after_script: + - !reference [.rusty-cachier, after_script] + tags: + - linux-docker + # rusty-cachier's hidden job. Parts of this job are used to instrument the pipeline's other real jobs with rusty-cachier # Description of the commands is available here - https://gitlab.parity.io/parity/infrastructure/ci_cd/rusty-cachier/client#description .rusty-cachier: @@ -100,16 +110,6 @@ default: after_script: - rusty-cachier snapshot destroy -.docker-env: - image: "${CI_IMAGE}" - before_script: - - !reference [.rust-info-script, script] - - !reference [.rusty-cachier, before_script] - after_script: - - !reference [.rusty-cachier, after_script] - tags: - - linux-docker - .test-refs: rules: - if: $CI_PIPELINE_SOURCE == "web" @@ -225,7 +225,8 @@ deploy-prometheus-alerting-rules: # This info is later used for the cache distribution and an overlay creation. rusty-cachier-notify: stage: notify - <<: *docker-env + extends: + - .docker-env script: - rusty-cachier cache notify From 5ac01f94e8cd2785ea51d767dffcafa3e329d736 Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Tue, 31 May 2022 19:12:07 +0100 Subject: [PATCH 286/484] Introduce `EnsureOrigin::try_successul_origin` (#11558) * Introduce `EnsureOrigin::try_successul_origin` * Formatting * Fixes * Add Morph * Fixes * Formatting --- frame/collective/src/lib.rs | 16 +- frame/society/src/lib.rs | 6 +- frame/support/src/traits/dispatch.rs | 224 +++++++++++++++++++-------- frame/system/src/lib.rs | 32 ++-- frame/treasury/src/tests.rs | 4 +- primitives/runtime/src/traits.rs | 36 +++++ 6 files changed, 221 insertions(+), 97 deletions(-) diff --git a/frame/collective/src/lib.rs b/frame/collective/src/lib.rs index 9fce1762c6ea7..13d03562cce49 100644 --- a/frame/collective/src/lib.rs +++ b/frame/collective/src/lib.rs @@ -996,11 +996,11 @@ impl< } #[cfg(feature = "runtime-benchmarks")] - fn successful_origin() -> O { + fn try_successful_origin() -> Result { let zero_account_id = AccountId::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()) .expect("infinite length input; no invalid inputs for type; qed"); - O::from(RawOrigin::Member(zero_account_id)) + Ok(O::from(RawOrigin::Member(zero_account_id))) } } @@ -1021,8 +1021,8 @@ impl< } #[cfg(feature = "runtime-benchmarks")] - fn successful_origin() -> O { - O::from(RawOrigin::Members(N, N)) + fn try_successful_origin() -> Result { + Ok(O::from(RawOrigin::Members(N, N))) } } @@ -1046,8 +1046,8 @@ impl< } #[cfg(feature = "runtime-benchmarks")] - fn successful_origin() -> O { - O::from(RawOrigin::Members(1u32, 0u32)) + fn try_successful_origin() -> Result { + Ok(O::from(RawOrigin::Members(1u32, 0u32))) } } @@ -1071,7 +1071,7 @@ impl< } #[cfg(feature = "runtime-benchmarks")] - fn successful_origin() -> O { - O::from(RawOrigin::Members(0u32, 0u32)) + fn try_successful_origin() -> Result { + Ok(O::from(RawOrigin::Members(0u32, 0u32))) } } diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index 5a993f72f32d2..2a6428e754b9d 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -1272,9 +1272,9 @@ impl EnsureOrigin for EnsureFounder { } #[cfg(feature = "runtime-benchmarks")] - fn successful_origin() -> T::Origin { - let founder = Founder::::get().expect("society founder should exist"); - T::Origin::from(frame_system::RawOrigin::Signed(founder)) + fn try_successful_origin() -> Result { + let founder = Founder::::get().ok_or(())?; + Ok(T::Origin::from(frame_system::RawOrigin::Signed(founder))) } } diff --git a/frame/support/src/traits/dispatch.rs b/frame/support/src/traits/dispatch.rs index b1bd52ca960da..e06373348e499 100644 --- a/frame/support/src/traits/dispatch.rs +++ b/frame/support/src/traits/dispatch.rs @@ -18,10 +18,14 @@ //! Traits for dealing with dispatching calls and the origin from which they are dispatched. use crate::dispatch::{DispatchResultWithPostInfo, Parameter, RawOrigin}; +use sp_arithmetic::traits::{CheckedSub, Zero}; use sp_runtime::{ - traits::{BadOrigin, Member}, + traits::{BadOrigin, Member, Morph, TryMorph}, Either, }; +use sp_std::marker::PhantomData; + +use super::TypedGet; /// Some sort of check on the origin is performed by this object. pub trait EnsureOrigin { @@ -38,9 +42,23 @@ pub trait EnsureOrigin { /// Returns an outer origin capable of passing `try_origin` check. /// + /// NOTE: This should generally *NOT* be reimplemented. Instead implement + /// `try_successful_origin`. + /// /// ** Should be used for benchmarking only!!! ** #[cfg(feature = "runtime-benchmarks")] - fn successful_origin() -> OuterOrigin; + fn successful_origin() -> OuterOrigin { + Self::try_successful_origin().expect("No origin exists which can satisfy the guard") + } + + /// Attept to get an outer origin capable of passing `try_origin` check. May return `Err` if it + /// is impossible. Default implementation just uses `successful_origin()`. + /// + /// ** Should be used for benchmarking only!!! ** + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + Ok(Self::successful_origin()) + } } /// `EnsureOrigin` implementation that always fails. @@ -51,8 +69,8 @@ impl EnsureOrigin for NeverEnsureOrigin { Err(o) } #[cfg(feature = "runtime-benchmarks")] - fn successful_origin() -> OO { - panic!("No `successful_origin` possible for `NeverEnsureOrigin`") + fn try_successful_origin() -> Result { + Err(()) } } @@ -71,9 +89,23 @@ pub trait EnsureOriginWithArg { /// Returns an outer origin capable of passing `try_origin` check. /// + /// NOTE: This should generally *NOT* be reimplemented. Instead implement + /// `try_successful_origin`. + /// /// ** Should be used for benchmarking only!!! ** #[cfg(feature = "runtime-benchmarks")] - fn successful_origin(a: &Argument) -> OuterOrigin; + fn successful_origin(a: &Argument) -> OuterOrigin { + Self::try_successful_origin(a).expect("No origin exists which can satisfy the guard") + } + + /// Attept to get an outer origin capable of passing `try_origin` check. May return `Err` if it + /// is impossible. Default implementation just uses `successful_origin()`. + /// + /// ** Should be used for benchmarking only!!! ** + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin(a: &Argument) -> Result { + Ok(Self::successful_origin(a)) + } } pub struct AsEnsureOriginWithArg(sp_std::marker::PhantomData); @@ -97,8 +129,121 @@ impl> /// /// ** Should be used for benchmarking only!!! ** #[cfg(feature = "runtime-benchmarks")] - fn successful_origin(_: &Argument) -> OuterOrigin { - EO::successful_origin() + fn try_successful_origin(_: &Argument) -> Result { + EO::try_successful_origin() + } +} + +/// A derivative `EnsureOrigin` implementation. It mutates the `Success` result of an `Original` +/// implementation with a given `Mutator`. +pub struct MapSuccess(PhantomData<(Original, Mutator)>); +impl, Mutator: Morph> EnsureOrigin + for MapSuccess +{ + type Success = Mutator::Outcome; + fn try_origin(o: O) -> Result { + Ok(Mutator::morph(Original::try_origin(o)?)) + } + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + Original::try_successful_origin() + } +} + +/// A derivative `EnsureOrigin` implementation. It mutates the `Success` result of an `Original` +/// implementation with a given `Mutator`, allowing the possibility of an error to be returned +/// from the mutator. +/// +/// NOTE: This is strictly worse performance than `MapSuccess` since it clones the original origin +/// value. If possible, use `MapSuccess` instead. +pub struct TryMapSuccess(PhantomData<(Orig, Mutator)>); +impl, Mutator: TryMorph> EnsureOrigin + for TryMapSuccess +{ + type Success = Mutator::Outcome; + fn try_origin(o: O) -> Result { + let orig = o.clone(); + Mutator::try_morph(Original::try_origin(o)?).map_err(|()| orig) + } + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + Original::try_successful_origin() + } +} + +/// "OR gate" implementation of `EnsureOrigin` allowing for different `Success` types for `L` +/// and `R`, with them combined using an `Either` type. +/// +/// Origin check will pass if `L` or `R` origin check passes. `L` is tested first. +/// +/// Successful origin is derived from the left side. +pub struct EitherOfDiverse(sp_std::marker::PhantomData<(L, R)>); +impl, R: EnsureOrigin> + EnsureOrigin for EitherOfDiverse +{ + type Success = Either; + fn try_origin(o: OuterOrigin) -> Result { + L::try_origin(o) + .map_or_else(|o| R::try_origin(o).map(Either::Right), |o| Ok(Either::Left(o))) + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + L::try_successful_origin().or_else(|()| R::try_successful_origin()) + } +} + +/// "OR gate" implementation of `EnsureOrigin` allowing for different `Success` types for `L` +/// and `R`, with them combined using an `Either` type. +/// +/// Origin check will pass if `L` or `R` origin check passes. `L` is tested first. +/// +/// Successful origin is derived from the left side. +#[deprecated = "Use `EitherOfDiverse` instead"] +pub type EnsureOneOf = EitherOfDiverse; + +/// "OR gate" implementation of `EnsureOrigin`, `Success` type for both `L` and `R` must +/// be equal. +/// +/// Origin check will pass if `L` or `R` origin check passes. `L` is tested first. +/// +/// Successful origin is derived from the left side. +pub struct EitherOf(sp_std::marker::PhantomData<(L, R)>); +impl< + OuterOrigin, + L: EnsureOrigin, + R: EnsureOrigin, + > EnsureOrigin for EitherOf +{ + type Success = L::Success; + fn try_origin(o: OuterOrigin) -> Result { + L::try_origin(o).or_else(|o| R::try_origin(o)) + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + L::try_successful_origin().or_else(|()| R::try_successful_origin()) + } +} + +/// Mutator which reduces a scalar by a particular amount. +pub struct ReduceBy(PhantomData); +impl TryMorph for ReduceBy +where + N::Type: CheckedSub, +{ + type Outcome = N::Type; + fn try_morph(r: N::Type) -> Result { + r.checked_sub(&N::get()).ok_or(()) + } +} +impl Morph for ReduceBy +where + N::Type: CheckedSub + Zero, +{ + type Outcome = N::Type; + fn morph(r: N::Type) -> N::Type { + r.checked_sub(&N::get()).unwrap_or(Zero::zero()) } } @@ -176,63 +321,6 @@ pub trait OriginTrait: Sized { fn signed(by: Self::AccountId) -> Self; } -/// "OR gate" implementation of `EnsureOrigin` allowing for different `Success` types for `L` -/// and `R`, with them combined using an `Either` type. -/// -/// Origin check will pass if `L` or `R` origin check passes. `L` is tested first. -/// -/// Successful origin is derived from the left side. -pub struct EitherOfDiverse(sp_std::marker::PhantomData<(L, R)>); - -impl, R: EnsureOrigin> - EnsureOrigin for EitherOfDiverse -{ - type Success = Either; - fn try_origin(o: OuterOrigin) -> Result { - L::try_origin(o) - .map_or_else(|o| R::try_origin(o).map(Either::Right), |o| Ok(Either::Left(o))) - } - - #[cfg(feature = "runtime-benchmarks")] - fn successful_origin() -> OuterOrigin { - L::successful_origin() - } -} - -/// "OR gate" implementation of `EnsureOrigin` allowing for different `Success` types for `L` -/// and `R`, with them combined using an `Either` type. -/// -/// Origin check will pass if `L` or `R` origin check passes. `L` is tested first. -/// -/// Successful origin is derived from the left side. -#[deprecated = "Use `EitherOfDiverse` instead"] -pub type EnsureOneOf = EitherOfDiverse; - -/// "OR gate" implementation of `EnsureOrigin`, `Success` type for both `L` and `R` must -/// be equal. -/// -/// Origin check will pass if `L` or `R` origin check passes. `L` is tested first. -/// -/// Successful origin is derived from the left side. -pub struct EitherOf(sp_std::marker::PhantomData<(L, R)>); - -impl< - OuterOrigin, - L: EnsureOrigin, - R: EnsureOrigin, - > EnsureOrigin for EitherOf -{ - type Success = L::Success; - fn try_origin(o: OuterOrigin) -> Result { - L::try_origin(o).or_else(|o| R::try_origin(o)) - } - - #[cfg(feature = "runtime-benchmarks")] - fn successful_origin() -> OuterOrigin { - L::successful_origin() - } -} - #[cfg(test)] mod tests { use super::*; @@ -248,8 +336,8 @@ mod tests { Ok(V::get()) } #[cfg(feature = "runtime-benchmarks")] - fn successful_origin() -> () { - () + fn try_successful_origin() -> Result<(), ()> { + Ok(()) } } @@ -259,8 +347,8 @@ mod tests { Err(()) } #[cfg(feature = "runtime-benchmarks")] - fn successful_origin() -> () { - () + fn try_successful_origin() -> Result<(), ()> { + Err(()) } } diff --git a/frame/system/src/lib.rs b/frame/system/src/lib.rs index bc28cc7e8118e..370a802665918 100644 --- a/frame/system/src/lib.rs +++ b/frame/system/src/lib.rs @@ -66,6 +66,8 @@ #[cfg(feature = "std")] use serde::Serialize; +#[cfg(feature = "runtime-benchmarks")] +use sp_runtime::traits::TrailingZeroInput; use sp_runtime::{ generic, traits::{ @@ -782,8 +784,8 @@ impl, O>> + From>, Acco } #[cfg(feature = "runtime-benchmarks")] - fn successful_origin() -> O { - O::from(RawOrigin::Root) + fn try_successful_origin() -> Result { + Ok(O::from(RawOrigin::Root)) } } @@ -805,8 +807,8 @@ impl< } #[cfg(feature = "runtime-benchmarks")] - fn successful_origin() -> O { - O::from(RawOrigin::Root) + fn try_successful_origin() -> Result { + Ok(O::from(RawOrigin::Root)) } } @@ -823,11 +825,10 @@ impl, O>> + From>, Acco } #[cfg(feature = "runtime-benchmarks")] - fn successful_origin() -> O { + fn try_successful_origin() -> Result { let zero_account_id = - AccountId::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()) - .expect("infinite length input; no invalid inputs for type; qed"); - O::from(RawOrigin::Signed(zero_account_id)) + AccountId::decode(&mut TrailingZeroInput::zeroes()).map_err(|_| ())?; + Ok(O::from(RawOrigin::Signed(zero_account_id))) } } @@ -847,16 +848,15 @@ impl< } #[cfg(feature = "runtime-benchmarks")] - fn successful_origin() -> O { + fn try_successful_origin() -> Result { let zero_account_id = - AccountId::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()) - .expect("infinite length input; no invalid inputs for type; qed"); + AccountId::decode(&mut TrailingZeroInput::zeroes()).map_err(|_| ())?; let members = Who::sorted_members(); let first_member = match members.get(0) { Some(account) => account.clone(), None => zero_account_id, }; - O::from(RawOrigin::Signed(first_member)) + Ok(O::from(RawOrigin::Signed(first_member))) } } @@ -873,8 +873,8 @@ impl, O>> + From>, Acco } #[cfg(feature = "runtime-benchmarks")] - fn successful_origin() -> O { - O::from(RawOrigin::None) + fn try_successful_origin() -> Result { + Ok(O::from(RawOrigin::None)) } } @@ -886,8 +886,8 @@ impl EnsureOrigin for EnsureNever { } #[cfg(feature = "runtime-benchmarks")] - fn successful_origin() -> O { - unimplemented!() + fn try_successful_origin() -> Result { + Err(()) } } diff --git a/frame/treasury/src/tests.rs b/frame/treasury/src/tests.rs index a21296d1b39ec..61eafb652427b 100644 --- a/frame/treasury/src/tests.rs +++ b/frame/treasury/src/tests.rs @@ -116,8 +116,8 @@ impl frame_support::traits::EnsureOrigin for TestSpendOrigin { }) } #[cfg(feature = "runtime-benchmarks")] - fn successful_origin() -> Origin { - Origin::root() + fn try_successful_origin() -> Result { + Ok(Origin::root()) } } diff --git a/primitives/runtime/src/traits.rs b/primitives/runtime/src/traits.rs index 1b21e7c65ddf7..ee6a526e95e1e 100644 --- a/primitives/runtime/src/traits.rs +++ b/primitives/runtime/src/traits.rs @@ -274,6 +274,42 @@ where } } +/// Extensible conversion trait. Generic over only source type, with destination type being +/// associated. +pub trait Morph { + /// The type into which `A` is mutated. + type Outcome; + + /// Make conversion. + fn morph(a: A) -> Self::Outcome; +} + +/// A structure that performs identity conversion. +impl Morph for Identity { + type Outcome = T; + fn morph(a: T) -> T { + a + } +} + +/// Extensible conversion trait. Generic over only source type, with destination type being +/// associated. +pub trait TryMorph { + /// The type into which `A` is mutated. + type Outcome; + + /// Make conversion. + fn try_morph(a: A) -> Result; +} + +/// A structure that performs identity conversion. +impl TryMorph for Identity { + type Outcome = T; + fn try_morph(a: T) -> Result { + Ok(a) + } +} + /// Extensible conversion trait. Generic over both source and destination types. pub trait Convert { /// Make conversion. From 5258c445965c90c12a8b3526d3b4870f79f49006 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Tue, 31 May 2022 20:29:00 +0200 Subject: [PATCH 287/484] Sync: Improve major sync detection (#11547) * Sync: Improve major sync detection When we still have a full import queue, we should still report that we are in major sync mode, otherwise validators may will already start producing blocks. * Use median * Review comments --- client/network/sync/src/lib.rs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/client/network/sync/src/lib.rs b/client/network/sync/src/lib.rs index bc0ed46c3f068..b027d77827374 100644 --- a/client/network/sync/src/lib.rs +++ b/client/network/sync/src/lib.rs @@ -576,14 +576,28 @@ where .map(|p| PeerInfo { best_hash: p.best_hash, best_number: p.best_number }) } + /// Returns the best seen block. + fn best_seen(&self) -> Option> { + let mut best_seens = self.peers.values().map(|p| p.best_number).collect::>(); + + if best_seens.is_empty() { + None + } else { + let middle = best_seens.len() / 2; + + // Not the "perfect median" when we have an even number of peers. + Some(*best_seens.select_nth_unstable(middle).1) + } + } + /// Returns the current sync status. pub fn status(&self) -> Status { - let best_seen = self.peers.values().map(|p| p.best_number).max(); + let best_seen = self.best_seen(); let sync_state = if let Some(n) = best_seen { // A chain is classified as downloading if the provided best block is - // more than `MAJOR_SYNC_BLOCKS` behind the best queued block. - if n > self.best_queued_number && n - self.best_queued_number > MAJOR_SYNC_BLOCKS.into() - { + // more than `MAJOR_SYNC_BLOCKS` behind the best block. + let best_block = self.client.info().best_number; + if n > best_block && n - best_block > MAJOR_SYNC_BLOCKS.into() { SyncState::Downloading } else { SyncState::Idle From ea794535b7bb6dc6ed59464331f19786d28bbe87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Tue, 31 May 2022 23:18:15 +0200 Subject: [PATCH 288/484] Fix clippy on master (#11559) --- primitives/arithmetic/src/helpers_128bit.rs | 4 ++-- primitives/arithmetic/src/per_things.rs | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/primitives/arithmetic/src/helpers_128bit.rs b/primitives/arithmetic/src/helpers_128bit.rs index 260f90ed60cd0..11df8bd53153e 100644 --- a/primitives/arithmetic/src/helpers_128bit.rs +++ b/primitives/arithmetic/src/helpers_128bit.rs @@ -358,8 +358,8 @@ mod tests { fn op_checked_rounded_div_works() { for i in 0..100_000u32 { let a = random_u128(i); - let b = random_u128(i + 1 << 30); - let c = random_u128(i + 1 << 31); + let b = random_u128(i + (1 << 30)); + let c = random_u128(i + (1 << 31)); let x = mulrat(a, b, c, NearestPrefDown); let y = multiply_by_rational(a, b, c).ok(); assert_eq!(x.is_some(), y.is_some()); diff --git a/primitives/arithmetic/src/per_things.rs b/primitives/arithmetic/src/per_things.rs index 7f39d35774190..2932a742063db 100644 --- a/primitives/arithmetic/src/per_things.rs +++ b/primitives/arithmetic/src/per_things.rs @@ -639,8 +639,9 @@ macro_rules! implement_per_thing { write!(fmt, "{:.2}% ({}/{})", pc, self.0, $max) } else { // A power of ten: calculate exact percent - let units = self.0 / ($max / 100); - let rest = self.0 % ($max / 100); + let divisor = $max / 100; + let units = self.0 / divisor; + let rest = self.0 % divisor; write!(fmt, "{}", units)?; if rest > 0 { write!(fmt, ".")?; From a71f289127d391f4c7969237aa0e7ccb23704d7d Mon Sep 17 00:00:00 2001 From: koenw Date: Tue, 31 May 2022 23:23:30 +0200 Subject: [PATCH 289/484] Fix link in node-template README (#11529) The lorri repository has moved from https://github.com/target/lorri to https://github.com/nix-community/lorri. --- bin/node-template/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/node-template/README.md b/bin/node-template/README.md index 52b117b8a7826..8defb870fa1b0 100644 --- a/bin/node-template/README.md +++ b/bin/node-template/README.md @@ -13,8 +13,8 @@ the [Substrate Playground](https://docs.substrate.io/playground/) :hammer_and_wr ### Using Nix Install [nix](https://nixos.org/) and optionally [direnv](https://github.com/direnv/direnv) and -[lorri](https://github.com/target/lorri) for a fully plug and play experience for setting up the -development environment. To get all the correct dependencies activate direnv `direnv allow` and +[lorri](https://github.com/nix-community/lorri) for a fully plug and play experience for setting up +the development environment. To get all the correct dependencies activate direnv `direnv allow` and lorri `lorri shell`. ### Rust Setup From 5616690e0c58b177565ed4d7364dd881368008f7 Mon Sep 17 00:00:00 2001 From: Sacha Lansky Date: Wed, 1 Jun 2022 00:18:48 +0200 Subject: [PATCH 290/484] Fix minor typos (#11550) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix minor typos * FMT Co-authored-by: Bastian Köcher --- frame/contracts/src/lib.rs | 10 +++++----- frame/contracts/src/storage/meter.rs | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index edb7fcf131de8..1011e264eb930 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -165,8 +165,8 @@ pub trait AddressGenerator { /// Default address generator. /// /// This is the default address generator used by contract instantiation. Its result -/// is only dependend on its inputs. It can therefore be used to reliably predict the -/// address of a contract. This is akin to the formular of eth's CREATE2 opcode. There +/// is only dependant on its inputs. It can therefore be used to reliably predict the +/// address of a contract. This is akin to the formula of eth's CREATE2 opcode. There /// is no CREATE equivalent because CREATE2 is strictly more powerful. /// /// Formula: `hash(deploying_address ++ code_hash ++ salt)` @@ -231,7 +231,7 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { - /// The time implementation used to supply timestamps to conntracts through `seal_now`. + /// The time implementation used to supply timestamps to contracts through `seal_now`. type Time: Time; /// The generator used to supply randomness to contracts through `seal_random`. @@ -339,7 +339,7 @@ pub mod pallet { /// /// For more information check out: /// - /// [`DefaultContractAccessWeight`] is a safe default to be used for polkadot or kusama + /// [`DefaultContractAccessWeight`] is a safe default to be used for Polkadot or Kusama /// parachains. /// /// # Note @@ -1044,7 +1044,7 @@ where })?; // The open deposit will be charged during execution when the // uploaded module does not already exist. This deposit is not part of the - // storage meter because it is not transfered to the contract but + // storage meter because it is not transferred to the contract but // reserved on the uploading account. (executable.open_deposit(), executable) }, diff --git a/frame/contracts/src/storage/meter.rs b/frame/contracts/src/storage/meter.rs index af51a5edc9472..b06f7ea4aedb5 100644 --- a/frame/contracts/src/storage/meter.rs +++ b/frame/contracts/src/storage/meter.rs @@ -35,10 +35,10 @@ pub type DepositOf = Deposit>; /// A production root storage meter that actually charges from its origin. pub type Meter = RawMeter; -/// A poduction nested storage meter that actually charges from its origin. +/// A production nested storage meter that actually charges from its origin. pub type NestedMeter = RawMeter; -/// A poduction storage meter that actually charges from its origin. +/// A production storage meter that actually charges from its origin. /// /// This can be used where we want to be generic over the state (Root vs. Nested). pub type GenericMeter = RawMeter; @@ -53,8 +53,8 @@ pub trait Ext { /// /// `origin`: The origin of the call stack from which is responsible for putting down a deposit. /// `limit`: The limit with which the meter was constructed. - /// `min_leftover`: How much `free_balance` in addition to the ed should be left inside the - /// `origin` account. + /// `min_leftover`: How much `free_balance` in addition to the existential deposit (ed) should + /// be left inside the `origin` account. /// /// Returns the limit that should be used by the meter. If origin can't afford the `limit` /// it returns `Err`. @@ -551,7 +551,7 @@ mod tests { let mut meter = TestMeter::new(&ALICE, Some(1_000), 0).unwrap(); assert_eq!(meter.available(), 1_000); - // an empty charge foes not create a `Charge` entry + // an empty charge does not create a `Charge` entry let mut nested0 = meter.nested(); nested0.charge(&Default::default()).unwrap(); meter.absorb(nested0, &ALICE, &BOB, None); From cf3c2f2cd1b0b080081cfe8fba67533cfb7f03b1 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Wed, 1 Jun 2022 01:14:23 +0200 Subject: [PATCH 291/484] Short circuit Treasury::Spend benchmark for NeverEnsureOrigin (#11562) Signed-off-by: Oliver Tale-Yazdi Co-authored-by: parity-processbot <> --- frame/treasury/src/benchmarking.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/frame/treasury/src/benchmarking.rs b/frame/treasury/src/benchmarking.rs index 4f17189d79b39..ddb952383370d 100644 --- a/frame/treasury/src/benchmarking.rs +++ b/frame/treasury/src/benchmarking.rs @@ -66,14 +66,22 @@ fn assert_last_event, I: 'static>(generic_event: >:: } benchmarks_instance_pallet! { + // This benchmark is short-circuited if `SpendOrigin` cannot provide + // a successful origin, in which case `spend` is un-callable and can use weight=0. spend { let (_, value, beneficiary_lookup) = setup_proposal::(SEED); - let origin = T::SpendOrigin::successful_origin(); + let origin = T::SpendOrigin::try_successful_origin(); let beneficiary = T::Lookup::lookup(beneficiary_lookup.clone()).unwrap(); let call = Call::::spend { amount: value, beneficiary: beneficiary_lookup }; - }: { call.dispatch_bypass_filter(origin)? } + }: { + if let Ok(origin) = origin.clone() { + call.dispatch_bypass_filter(origin)?; + } + } verify { - assert_last_event::(Event::SpendApproved { proposal_index: 0, amount: value, beneficiary }.into()) + if origin.is_ok() { + assert_last_event::(Event::SpendApproved { proposal_index: 0, amount: value, beneficiary }.into()) + } } propose_spend { From cd30dc6d5e5e61341617ba85bdce8cc97fc6fc96 Mon Sep 17 00:00:00 2001 From: MOZGIII Date: Wed, 1 Jun 2022 13:13:24 +0400 Subject: [PATCH 292/484] Do not require Ord for pallet_offences::Config::IdentificationTuple (#11563) --- frame/offences/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/offences/src/lib.rs b/frame/offences/src/lib.rs index a6d69e2a5e268..e4b75d9c3c015 100644 --- a/frame/offences/src/lib.rs +++ b/frame/offences/src/lib.rs @@ -59,7 +59,7 @@ pub mod pallet { /// The overarching event type. type Event: From + IsType<::Event>; /// Full identification of the validator. - type IdentificationTuple: Parameter + Ord; + type IdentificationTuple: Parameter; /// A handler called for every offence report. type OnOffenceHandler: OnOffenceHandler; } From c956b8fd5210293b9e89c8f9299bc91092939128 Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Wed, 1 Jun 2022 10:23:47 +0100 Subject: [PATCH 293/484] Ranked Collective pallet (#11548) * Ranked Collective pallet * Fixes * benchmarks * Weights * Allow class voting in rank Use bare ayes for calculating support. Allow only promotion/demotion by one rank only. Allow removal of member with rank zero only. Use new Tally API * Index by rank, still O(1). * Custom vote weights * Formatting * Update frame/ranked-collective/src/lib.rs * Broken :( * origin guard; cleanup uses new API * Formatting * Promote/demote by rank * Formatting * Use new API * Remove code in another PR * Remove code in another PR * Formatting * Remove code in another PR * Docs * Docs * Bump * Fixes * Formatting * Fixes --- Cargo.lock | 18 + Cargo.toml | 1 + bin/node/runtime/Cargo.toml | 4 + bin/node/runtime/src/lib.rs | 41 +- frame/ranked-collective/Cargo.toml | 49 ++ frame/ranked-collective/README.md | 22 + frame/ranked-collective/src/benchmarking.rs | 155 +++++ frame/ranked-collective/src/lib.rs | 630 ++++++++++++++++++ frame/ranked-collective/src/tests.rs | 457 +++++++++++++ frame/ranked-collective/src/weights.rs | 187 ++++++ frame/referenda/src/lib.rs | 2 +- frame/referenda/src/types.rs | 2 +- .../src/construct_runtime/expand/origin.rs | 9 +- frame/support/src/dispatch.rs | 3 + frame/support/src/storage/bounded_vec.rs | 6 + frame/support/src/storage/mod.rs | 6 +- frame/support/src/storage/types/double_map.rs | 31 +- frame/support/src/storage/types/map.rs | 16 +- frame/support/src/traits.rs | 3 +- frame/support/src/traits/dispatch.rs | 3 + frame/support/src/traits/voting.rs | 8 +- test-utils/runtime/src/lib.rs | 5 +- 22 files changed, 1641 insertions(+), 17 deletions(-) create mode 100644 frame/ranked-collective/Cargo.toml create mode 100644 frame/ranked-collective/README.md create mode 100644 frame/ranked-collective/src/benchmarking.rs create mode 100644 frame/ranked-collective/src/lib.rs create mode 100644 frame/ranked-collective/src/tests.rs create mode 100644 frame/ranked-collective/src/weights.rs diff --git a/Cargo.lock b/Cargo.lock index 09da4278f69f4..17d2e82596526 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4764,6 +4764,7 @@ dependencies = [ "pallet-preimage", "pallet-proxy", "pallet-randomness-collective-flip", + "pallet-ranked-collective", "pallet-recovery", "pallet-referenda", "pallet-remark", @@ -5988,6 +5989,23 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-ranked-collective" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-recovery" version = "4.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index 41739fe6f1ebc..74e7aae7949c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -115,6 +115,7 @@ members = [ "frame/nomination-pools", "frame/nomination-pools/benchmarking", "frame/randomness-collective-flip", + "frame/ranked-collective", "frame/recovery", "frame/referenda", "frame/remark", diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 1b3e9083d1a1d..5ecd6ccedaf01 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -83,6 +83,7 @@ pallet-offences-benchmarking = { version = "4.0.0-dev", path = "../../../frame/o pallet-preimage = { version = "4.0.0-dev", default-features = false, path = "../../../frame/preimage" } pallet-proxy = { version = "4.0.0-dev", default-features = false, path = "../../../frame/proxy" } pallet-randomness-collective-flip = { version = "4.0.0-dev", default-features = false, path = "../../../frame/randomness-collective-flip" } +pallet-ranked-collective = { version = "4.0.0-dev", default-features = false, path = "../../../frame/ranked-collective" } pallet-recovery = { version = "4.0.0-dev", default-features = false, path = "../../../frame/recovery" } pallet-referenda = { version = "4.0.0-dev", default-features = false, path = "../../../frame/referenda" } pallet-remark = { version = "4.0.0-dev", default-features = false, path = "../../../frame/remark" } @@ -176,6 +177,7 @@ std = [ "pallet-utility/std", "sp-version/std", "pallet-society/std", + "pallet-ranked-collective/std", "pallet-referenda/std", "pallet-remark/std", "pallet-recovery/std", @@ -218,6 +220,7 @@ runtime-benchmarks = [ "pallet-preimage/runtime-benchmarks", "pallet-proxy/runtime-benchmarks", "pallet-scheduler/runtime-benchmarks", + "pallet-ranked-collective/runtime-benchmarks", "pallet-referenda/runtime-benchmarks", "pallet-recovery/runtime-benchmarks", "pallet-remark/runtime-benchmarks", @@ -265,6 +268,7 @@ try-runtime = [ "pallet-offences/try-runtime", "pallet-preimage/try-runtime", "pallet-proxy/try-runtime", + "pallet-ranked-collective/try-runtime", "pallet-randomness-collective-flip/try-runtime", "pallet-recovery/try-runtime", "pallet-referenda/try-runtime", diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 9b5bc0273ab33..bfac30db6c66f 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -20,7 +20,7 @@ #![cfg_attr(not(feature = "std"), no_std)] // `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256. -#![recursion_limit = "256"] +#![recursion_limit = "512"] use codec::{Decode, Encode, MaxEncodedLen}; use frame_election_provider_support::{ @@ -43,7 +43,7 @@ use frame_support::{ }; use frame_system::{ limits::{BlockLength, BlockWeights}, - EnsureRoot, EnsureSigned, + EnsureRoot, EnsureRootWithSuccess, EnsureSigned, }; pub use node_primitives::{AccountId, Signature}; use node_primitives::{AccountIndex, Balance, BlockNumber, Hash, Index, Moment}; @@ -780,11 +780,11 @@ parameter_types! { pub struct TracksInfo; impl pallet_referenda::TracksInfo for TracksInfo { - type Id = u8; + type Id = u16; type Origin = ::PalletsOrigin; fn tracks() -> &'static [(Self::Id, pallet_referenda::TrackInfo)] { - static DATA: [(u8, pallet_referenda::TrackInfo); 1] = [( - 0u8, + static DATA: [(u16, pallet_referenda::TrackInfo); 1] = [( + 0u16, pallet_referenda::TrackInfo { name: "root", max_deciding: 1, @@ -837,6 +837,34 @@ impl pallet_referenda::Config for Runtime { type Tracks = TracksInfo; } +impl pallet_referenda::Config for Runtime { + type WeightInfo = pallet_referenda::weights::SubstrateWeight; + type Call = Call; + type Event = Event; + type Scheduler = Scheduler; + type Currency = pallet_balances::Pallet; + type CancelOrigin = EnsureRoot; + type KillOrigin = EnsureRoot; + type Slash = (); + type Votes = pallet_ranked_collective::Votes; + type Tally = pallet_ranked_collective::TallyOf; + type SubmissionDeposit = SubmissionDeposit; + type MaxQueued = ConstU32<100>; + type UndecidingTimeout = UndecidingTimeout; + type AlarmInterval = AlarmInterval; + type Tracks = TracksInfo; +} + +impl pallet_ranked_collective::Config for Runtime { + type WeightInfo = pallet_ranked_collective::weights::SubstrateWeight; + type Event = Event; + type PromoteOrigin = EnsureRootWithSuccess>; + type DemoteOrigin = EnsureRootWithSuccess>; + type Polls = RankedPolls; + type MinRankOfClass = traits::Identity; + type VoteWeight = pallet_ranked_collective::Geometric; +} + impl pallet_remark::Config for Runtime { type WeightInfo = pallet_remark::weights::SubstrateWeight; type Event = Event; @@ -1534,6 +1562,8 @@ construct_runtime!( ConvictionVoting: pallet_conviction_voting, Whitelist: pallet_whitelist, NominationPools: pallet_nomination_pools, + RankedPolls: pallet_referenda::, + RankedCollective: pallet_ranked_collective, } ); @@ -1622,6 +1652,7 @@ mod benches { [pallet_offences, OffencesBench::] [pallet_preimage, Preimage] [pallet_proxy, Proxy] + [pallet_ranked_collective, RankedCollective] [pallet_referenda, Referenda] [pallet_recovery, Recovery] [pallet_remark, Remark] diff --git a/frame/ranked-collective/Cargo.toml b/frame/ranked-collective/Cargo.toml new file mode 100644 index 0000000000000..cb43b9ea4c831 --- /dev/null +++ b/frame/ranked-collective/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "pallet-ranked-collective" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Ranked collective system: Members of a set of account IDs can make their collective feelings known through dispatched calls from one of two specialized origins." +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +log = { version = "0.4.16", default-features = false } +scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-arithmetic = { version = "5.0.0", default-features = false, path = "../../primitives/arithmetic" } +sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "log/std", + "scale-info/std", + "sp-arithmetic/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/ranked-collective/README.md b/frame/ranked-collective/README.md new file mode 100644 index 0000000000000..b5fe65ef34920 --- /dev/null +++ b/frame/ranked-collective/README.md @@ -0,0 +1,22 @@ +# Ranked collective system. + +This is a membership pallet providing a `Tally` implementation ready for use with polling +systems such as the Referenda pallet. Members each have a rank, with zero being the lowest. +There is no complexity limitation on either the number of members at a rank or the number of +ranks in the system thus allowing potentially public membership. A member of at least a given +rank can be selected at random in O(1) time, allowing for various games to constructed using +this as a primitive. Members may only be promoted and demoted by one rank at a time, however +all operations (save one) are O(1) in complexity. The only operation which is not O(1) is the +`remove_member` since they must be removed from all ranks from the present down to zero. + +Different ranks have different voting power, and are able to vote in different polls. In general +rank privileges are cumulative. Higher ranks are able to vote in any polls open to lower ranks. +Similarly, higher ranks always have at least as much voting power in any given poll as lower +ranks. + +Two `Config` trait items control these "rank privileges": `MinRankOfClass` and `VoteWeight`. +The first controls which ranks are allowed to vote on a particular class of poll. The second +controls the weight of a vote given the voters rank compared to the minimum rank of the poll. + +An origin control, `EnsureRank`, ensures that the origin is a member of the collective of at +least a particular rank. diff --git a/frame/ranked-collective/src/benchmarking.rs b/frame/ranked-collective/src/benchmarking.rs new file mode 100644 index 0000000000000..ab1a5dc283ca5 --- /dev/null +++ b/frame/ranked-collective/src/benchmarking.rs @@ -0,0 +1,155 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Staking pallet benchmarking. + +use super::*; +#[allow(unused_imports)] +use crate::Pallet as RankedCollective; + +use frame_benchmarking::{account, benchmarks_instance_pallet, whitelisted_caller}; +use frame_support::{assert_ok, dispatch::UnfilteredDispatchable}; +use frame_system::RawOrigin as SystemOrigin; + +const SEED: u32 = 0; + +fn assert_last_event, I: 'static>(generic_event: >::Event) { + frame_system::Pallet::::assert_last_event(generic_event.into()); +} + +fn make_member, I: 'static>(rank: Rank) -> T::AccountId { + let who = account::("member", MemberCount::::get(0), SEED); + assert_ok!(Pallet::::add_member(T::PromoteOrigin::successful_origin(), who.clone())); + for _ in 0..rank { + assert_ok!(Pallet::::promote_member( + T::PromoteOrigin::successful_origin(), + who.clone() + )); + } + who +} + +benchmarks_instance_pallet! { + add_member { + let who = account::("member", 0, SEED); + let origin = T::PromoteOrigin::successful_origin(); + let call = Call::::add_member { who: who.clone() }; + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert_eq!(MemberCount::::get(0), 1); + assert_last_event::(Event::MemberAdded { who }.into()); + } + + remove_member { + let r in 0 .. 10; + let rank = r as u16; + let first = make_member::(rank); + let who = make_member::(rank); + let last = make_member::(rank); + let last_index = (0..=rank).map(|r| IdToIndex::::get(r, &last).unwrap()).collect::>(); + let origin = T::DemoteOrigin::successful_origin(); + let call = Call::::remove_member { who: who.clone(), min_rank: rank }; + }: { call.dispatch_bypass_filter(origin)? } + verify { + for r in 0..=rank { + assert_eq!(MemberCount::::get(r), 2); + assert_ne!(last_index[r as usize], IdToIndex::::get(r, &last).unwrap()); + } + assert_last_event::(Event::MemberRemoved { who, rank }.into()); + } + + promote_member { + let r in 0 .. 10; + let rank = r as u16; + let who = make_member::(rank); + let origin = T::PromoteOrigin::successful_origin(); + let call = Call::::promote_member { who: who.clone() }; + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert_eq!(Members::::get(&who).unwrap().rank, rank + 1); + assert_last_event::(Event::RankChanged { who, rank: rank + 1 }.into()); + } + + demote_member { + let r in 0 .. 10; + let rank = r as u16; + let first = make_member::(rank); + let who = make_member::(rank); + let last = make_member::(rank); + let last_index = IdToIndex::::get(rank, &last).unwrap(); + let origin = T::DemoteOrigin::successful_origin(); + let call = Call::::demote_member { who: who.clone() }; + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert_eq!(Members::::get(&who).map(|x| x.rank), rank.checked_sub(1)); + assert_eq!(MemberCount::::get(rank), 2); + assert_ne!(last_index, IdToIndex::::get(rank, &last).unwrap()); + assert_last_event::(match rank { + 0 => Event::MemberRemoved { who, rank: 0 }, + r => Event::RankChanged { who, rank: r - 1 }, + }.into()); + } + + vote { + let caller: T::AccountId = whitelisted_caller(); + assert_ok!(Pallet::::add_member(T::PromoteOrigin::successful_origin(), caller.clone())); + // Create a poll + let class = T::Polls::classes().into_iter().next().unwrap(); + let rank = T::MinRankOfClass::convert(class.clone()); + for _ in 0..rank { + assert_ok!(Pallet::::promote_member( + T::PromoteOrigin::successful_origin(), + caller.clone() + )); + } + + let poll = T::Polls::create_ongoing(class).expect("Must always be able to create a poll for rank 0"); + + // Vote once. + assert_ok!(Pallet::::vote(SystemOrigin::Signed(caller.clone()).into(), poll, true)); + }: _(SystemOrigin::Signed(caller.clone()), poll, false) + verify { + let tally = Tally::from_parts(0, 0, 1); + let ev = Event::Voted { who: caller, poll, vote: VoteRecord::Nay(1), tally }; + assert_last_event::(ev.into()); + } + + cleanup_poll { + let n in 1 .. 100; + + // Create a poll + let class = T::Polls::classes().into_iter().next().unwrap(); + let rank = T::MinRankOfClass::convert(class.clone()); + let poll = T::Polls::create_ongoing(class).expect("Must always be able to create a poll"); + + // Vote in the poll by each of `n` members + for i in 0..n { + let who = make_member::(rank); + assert_ok!(Pallet::::vote(SystemOrigin::Signed(who).into(), poll, true)); + } + + // End the poll. + T::Polls::end_ongoing(poll, false).expect("Must always be able to end a poll"); + + assert_eq!(Voting::::iter_prefix(poll).count(), n as usize); + }: _(SystemOrigin::Signed(whitelisted_caller()), poll, n) + verify { + assert_eq!(Voting::::iter().count(), 0); + } + + impl_benchmark_test_suite!(RankedCollective, crate::tests::new_test_ext(), crate::tests::Test); +} diff --git a/frame/ranked-collective/src/lib.rs b/frame/ranked-collective/src/lib.rs new file mode 100644 index 0000000000000..521dfb6aad4e3 --- /dev/null +++ b/frame/ranked-collective/src/lib.rs @@ -0,0 +1,630 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Ranked collective system. +//! +//! This is a membership pallet providing a `Tally` implementation ready for use with polling +//! systems such as the Referenda pallet. Members each have a rank, with zero being the lowest. +//! There is no complexity limitation on either the number of members at a rank or the number of +//! ranks in the system thus allowing potentially public membership. A member of at least a given +//! rank can be selected at random in O(1) time, allowing for various games to constructed using +//! this as a primitive. Members may only be promoted and demoted by one rank at a time, however +//! all operations (save one) are O(1) in complexity. The only operation which is not O(1) is the +//! `remove_member` since they must be removed from all ranks from the present down to zero. +//! +//! Different ranks have different voting power, and are able to vote in different polls. In general +//! rank privileges are cumulative. Higher ranks are able to vote in any polls open to lower ranks. +//! Similarly, higher ranks always have at least as much voting power in any given poll as lower +//! ranks. +//! +//! Two `Config` trait items control these "rank privileges": `MinRankOfClass` and `VoteWeight`. +//! The first controls which ranks are allowed to vote on a particular class of poll. The second +//! controls the weight of a vote given the voters rank compared to the minimum rank of the poll. +//! +//! An origin control, `EnsureRank`, ensures that the origin is a member of the collective of at +//! least a particular rank. + +#![cfg_attr(not(feature = "std"), no_std)] +#![recursion_limit = "128"] + +use scale_info::TypeInfo; +use sp_arithmetic::traits::Saturating; +use sp_runtime::{traits::Convert, ArithmeticError::Overflow, Perbill, RuntimeDebug}; +use sp_std::{marker::PhantomData, prelude::*}; + +use frame_support::{ + codec::{Decode, Encode, MaxEncodedLen}, + dispatch::{DispatchError, DispatchResultWithPostInfo}, + ensure, + traits::{EnsureOrigin, PollStatus, Polling, VoteTally}, + weights::PostDispatchInfo, + CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, +}; + +#[cfg(test)] +mod tests; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +pub mod weights; + +pub use pallet::*; +pub use weights::WeightInfo; + +/// A number of members. +pub type MemberIndex = u32; + +/// Member rank. +pub type Rank = u16; + +/// Votes. +pub type Votes = u32; + +/// Aggregated votes for an ongoing poll by members of the ranked collective. +#[derive( + CloneNoBound, + PartialEqNoBound, + EqNoBound, + RuntimeDebugNoBound, + TypeInfo, + Encode, + Decode, + MaxEncodedLen, +)] +#[scale_info(skip_type_params(M))] +pub struct Tally { + bare_ayes: MemberIndex, + ayes: Votes, + nays: Votes, + dummy: PhantomData, +} + +impl Tally { + pub fn from_parts(bare_ayes: MemberIndex, ayes: Votes, nays: Votes) -> Self { + Tally { bare_ayes, ayes, nays, dummy: PhantomData } + } +} + +// Use (non-rank-weighted) ayes for calculating support. +// Allow only promotion/demotion by one rank only. +// Allow removal of member with rank zero only. +// This keeps everything O(1) while still allowing arbitrary number of ranks. + +// All functions of VoteTally now include the class as a param. + +pub type TallyOf = Tally>; +pub type PollIndexOf = <>::Polls as Polling>>::Index; + +impl VoteTally for Tally { + fn new(_: Rank) -> Self { + Self { bare_ayes: 0, ayes: 0, nays: 0, dummy: PhantomData } + } + fn ayes(&self, _: Rank) -> Votes { + self.bare_ayes + } + fn support(&self, class: Rank) -> Perbill { + Perbill::from_rational(self.bare_ayes, M::get_max_voters(class)) + } + fn approval(&self, _: Rank) -> Perbill { + Perbill::from_rational(self.ayes, 1.max(self.ayes + self.nays)) + } + #[cfg(feature = "runtime-benchmarks")] + fn unanimity(class: Rank) -> Self { + Self { + bare_ayes: M::get_max_voters(class), + ayes: M::get_max_voters(class), + nays: 0, + dummy: PhantomData, + } + } + #[cfg(feature = "runtime-benchmarks")] + fn rejection(class: Rank) -> Self { + Self { bare_ayes: 0, ayes: 0, nays: M::get_max_voters(class), dummy: PhantomData } + } + #[cfg(feature = "runtime-benchmarks")] + fn from_requirements(support: Perbill, approval: Perbill, class: Rank) -> Self { + let c = M::get_max_voters(class); + let ayes = support * c; + let nays = ((ayes as u64) * 1_000_000_000u64 / approval.deconstruct() as u64) as u32 - ayes; + Self { bare_ayes: ayes, ayes, nays, dummy: PhantomData } + } +} + +/// Record needed for every member. +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct MemberRecord { + /// The rank of the member. + rank: Rank, +} + +/// Record needed for every vote. +#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, RuntimeDebug, TypeInfo)] +pub enum VoteRecord { + /// Vote was an aye with given vote weight. + Aye(Votes), + /// Vote was a nay with given vote weight. + Nay(Votes), +} + +impl From<(bool, Votes)> for VoteRecord { + fn from((aye, votes): (bool, Votes)) -> Self { + match aye { + true => VoteRecord::Aye(votes), + false => VoteRecord::Nay(votes), + } + } +} + +/// Vote-weight scheme where all voters get one vote regardless of rank. +pub struct Unit; +impl Convert for Unit { + fn convert(_: Rank) -> Votes { + 1 + } +} + +/// Vote-weight scheme where all voters get one vote plus an additional vote for every excess rank +/// they have. I.e.: +/// +/// - Each member with no excess rank gets 1 vote; +/// - ...with an excess rank of 1 gets 2 votes; +/// - ...with an excess rank of 2 gets 2 votes; +/// - ...with an excess rank of 3 gets 3 votes; +/// - ...with an excess rank of 4 gets 4 votes. +pub struct Linear; +impl Convert for Linear { + fn convert(r: Rank) -> Votes { + (r + 1) as Votes + } +} + +/// Vote-weight scheme where all voters get one vote plus additional votes for every excess rank +/// they have incrementing by one vote for each excess rank. I.e.: +/// +/// - Each member with no excess rank gets 1 vote; +/// - ...with an excess rank of 1 gets 2 votes; +/// - ...with an excess rank of 2 gets 3 votes; +/// - ...with an excess rank of 3 gets 6 votes; +/// - ...with an excess rank of 4 gets 10 votes. +pub struct Geometric; +impl Convert for Geometric { + fn convert(r: Rank) -> Votes { + let v = (r + 1) as Votes; + v * (v + 1) / 2 + } +} + +/// Trait for getting the maximum number of voters for a given rank. +pub trait GetMaxVoters { + /// Return the maximum number of voters for the rank `r`. + fn get_max_voters(r: Rank) -> MemberIndex; +} +impl, I: 'static> GetMaxVoters for Pallet { + fn get_max_voters(r: Rank) -> MemberIndex { + MemberCount::::get(r) + } +} + +/// Guard to ensure that the given origin is a member of the collective. The rank of the member is +/// the `Success` value. +pub struct EnsureRanked(PhantomData<(T, I)>); +impl, I: 'static, const MIN_RANK: u16> EnsureOrigin + for EnsureRanked +{ + type Success = Rank; + + fn try_origin(o: T::Origin) -> Result { + let who = frame_system::EnsureSigned::try_origin(o)?; + match Members::::get(&who) { + Some(MemberRecord { rank, .. }) if rank >= MIN_RANK => Ok(rank), + _ => Err(frame_system::RawOrigin::Signed(who).into()), + } + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + let who = IndexToId::::get(MIN_RANK, 0).ok_or(())?; + Ok(frame_system::RawOrigin::Signed(who).into()) + } +} + +/// Guard to ensure that the given origin is a member of the collective. The account ID of the +/// member is the `Success` value. +pub struct EnsureMember(PhantomData<(T, I)>); +impl, I: 'static, const MIN_RANK: u16> EnsureOrigin + for EnsureMember +{ + type Success = T::AccountId; + + fn try_origin(o: T::Origin) -> Result { + let who = frame_system::EnsureSigned::try_origin(o)?; + match Members::::get(&who) { + Some(MemberRecord { rank, .. }) if rank >= MIN_RANK => Ok(who), + _ => Err(frame_system::RawOrigin::Signed(who).into()), + } + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + let who = IndexToId::::get(MIN_RANK, 0).ok_or(())?; + Ok(frame_system::RawOrigin::Signed(who).into()) + } +} + +/// Guard to ensure that the given origin is a member of the collective. The pair of including both +/// the account ID and the rank of the member is the `Success` value. +pub struct EnsureRankedMember(PhantomData<(T, I)>); +impl, I: 'static, const MIN_RANK: u16> EnsureOrigin + for EnsureRankedMember +{ + type Success = (T::AccountId, Rank); + + fn try_origin(o: T::Origin) -> Result { + let who = frame_system::EnsureSigned::try_origin(o)?; + match Members::::get(&who) { + Some(MemberRecord { rank, .. }) if rank >= MIN_RANK => Ok((who, rank)), + _ => Err(frame_system::RawOrigin::Signed(who).into()), + } + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + let who = IndexToId::::get(MIN_RANK, 0).ok_or(())?; + Ok(frame_system::RawOrigin::Signed(who).into()) + } +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::{pallet_prelude::*, storage::KeyLenOf}; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + #[pallet::without_storage_info] + pub struct Pallet(PhantomData<(T, I)>); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + + /// The outer event type. + type Event: From> + IsType<::Event>; + + /// The origin required to add or promote a mmember. The success value indicates the + /// maximum rank *to which* the promotion may be. + type PromoteOrigin: EnsureOrigin; + + /// The origin required to demote or remove a member. The success value indicates the + /// maximum rank *from which* the demotion/removal may be. + type DemoteOrigin: EnsureOrigin; + + /// The polling system used for our voting. + type Polls: Polling, Votes = Votes, Moment = Self::BlockNumber>; + + /// Convert the tally class into the minimum rank required to vote on the poll. If + /// `Polls::Class` is the same type as `Rank`, then `Identity` can be used here to mean + /// "a rank of at least the poll class". + type MinRankOfClass: Convert<>>::Class, Rank>; + + /// Convert a rank_delta into a number of votes the rank gets. + /// + /// Rank_delta is defined as the number of ranks above the minimum required to take part + /// in the poll. + type VoteWeight: Convert; + } + + /// The number of members in the collective who have at least the rank according to the index + /// of the vec. + #[pallet::storage] + pub type MemberCount, I: 'static = ()> = + StorageMap<_, Twox64Concat, Rank, MemberIndex, ValueQuery>; + + /// The current members of the collective. + #[pallet::storage] + pub type Members, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::AccountId, MemberRecord>; + + /// The index of each ranks's member into the group of members who have at least that rank. + #[pallet::storage] + pub type IdToIndex, I: 'static = ()> = + StorageDoubleMap<_, Twox64Concat, Rank, Twox64Concat, T::AccountId, MemberIndex>; + + /// The members in the collective by index. All indices in the range `0..MemberCount` will + /// return `Some`, however a member's index is not guaranteed to remain unchanged over time. + #[pallet::storage] + pub type IndexToId, I: 'static = ()> = + StorageDoubleMap<_, Twox64Concat, Rank, Twox64Concat, MemberIndex, T::AccountId>; + + /// Votes on a given proposal, if it is ongoing. + #[pallet::storage] + pub type Voting, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + PollIndexOf, + Twox64Concat, + T::AccountId, + VoteRecord, + >; + + #[pallet::storage] + pub type VotingCleanup, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, PollIndexOf, BoundedVec>>>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event, I: 'static = ()> { + /// A member `who` has been added. + MemberAdded { who: T::AccountId }, + /// The member `who`'s rank has been changed to the given `rank`. + RankChanged { who: T::AccountId, rank: Rank }, + /// The member `who` of given `rank` has been removed from the collective. + MemberRemoved { who: T::AccountId, rank: Rank }, + /// The member `who` has voted for the `poll` with the given `vote` leading to an updated + /// `tally`. + Voted { who: T::AccountId, poll: PollIndexOf, vote: VoteRecord, tally: TallyOf }, + } + + #[pallet::error] + pub enum Error { + /// Account is already a member. + AlreadyMember, + /// Account is not a member. + NotMember, + /// The given poll index is unknown or has closed. + NotPolling, + /// The given poll is still ongoing. + Ongoing, + /// There are no further records to be removed. + NoneRemaining, + /// Unexpected error in state. + Corruption, + /// The member's rank is too low to vote. + RankTooLow, + /// The information provided is incorrect. + InvalidWitness, + /// The origin is not sufficiently privileged to do the operation. + NoPermission, + } + + #[pallet::call] + impl, I: 'static> Pallet { + /// Introduce a new member. + /// + /// - `origin`: Must be the `AdminOrigin`. + /// - `who`: Account of non-member which will become a member. + /// - `rank`: The rank to give the new member. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::add_member())] + pub fn add_member(origin: OriginFor, who: T::AccountId) -> DispatchResult { + let _ = T::PromoteOrigin::ensure_origin(origin)?; + ensure!(!Members::::contains_key(&who), Error::::AlreadyMember); + let index = MemberCount::::get(0); + let count = index.checked_add(1).ok_or(Overflow)?; + + Members::::insert(&who, MemberRecord { rank: 0 }); + IdToIndex::::insert(0, &who, index); + IndexToId::::insert(0, index, &who); + MemberCount::::insert(0, count); + Self::deposit_event(Event::MemberAdded { who }); + + Ok(()) + } + + /// Increment the rank of an existing member by one. + /// + /// - `origin`: Must be the `AdminOrigin`. + /// - `who`: Account of existing member. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::promote_member(0))] + pub fn promote_member(origin: OriginFor, who: T::AccountId) -> DispatchResult { + let max_rank = T::PromoteOrigin::ensure_origin(origin)?; + let record = Self::ensure_member(&who)?; + let rank = record.rank.checked_add(1).ok_or(Overflow)?; + ensure!(max_rank >= rank, Error::::NoPermission); + let index = MemberCount::::get(rank); + MemberCount::::insert(rank, index.checked_add(1).ok_or(Overflow)?); + IdToIndex::::insert(rank, &who, index); + IndexToId::::insert(rank, index, &who); + Members::::insert(&who, MemberRecord { rank, ..record }); + Self::deposit_event(Event::RankChanged { who, rank }); + + Ok(()) + } + + /// Decrement the rank of an existing member by one. If the member is already at rank zero, + /// then they are removed entirely. + /// + /// - `origin`: Must be the `AdminOrigin`. + /// - `who`: Account of existing member of rank greater than zero. + /// + /// Weight: `O(1)`, less if the member's index is highest in its rank. + #[pallet::weight(T::WeightInfo::demote_member(0))] + pub fn demote_member(origin: OriginFor, who: T::AccountId) -> DispatchResult { + let max_rank = T::DemoteOrigin::ensure_origin(origin)?; + let mut record = Self::ensure_member(&who)?; + let rank = record.rank; + ensure!(max_rank >= rank, Error::::NoPermission); + + Self::remove_from_rank(&who, rank)?; + let maybe_rank = rank.checked_sub(1); + match maybe_rank { + None => { + Members::::remove(&who); + Self::deposit_event(Event::MemberRemoved { who, rank: 0 }); + }, + Some(rank) => { + record.rank = rank; + Members::::insert(&who, &record); + Self::deposit_event(Event::RankChanged { who, rank }); + }, + } + Ok(()) + } + + /// Remove the member entirely. + /// + /// - `origin`: Must be the `AdminOrigin`. + /// - `who`: Account of existing member of rank greater than zero. + /// - `min_rank`: The rank of the member or greater. + /// + /// Weight: `O(min_rank)`. + #[pallet::weight(T::WeightInfo::remove_member(*min_rank as u32))] + pub fn remove_member( + origin: OriginFor, + who: T::AccountId, + min_rank: Rank, + ) -> DispatchResultWithPostInfo { + let max_rank = T::DemoteOrigin::ensure_origin(origin)?; + let MemberRecord { rank, .. } = Self::ensure_member(&who)?; + ensure!(min_rank >= rank, Error::::InvalidWitness); + ensure!(max_rank >= rank, Error::::NoPermission); + + for r in 0..=rank { + Self::remove_from_rank(&who, r)?; + } + Members::::remove(&who); + Self::deposit_event(Event::MemberRemoved { who, rank }); + Ok(PostDispatchInfo { + actual_weight: Some(T::WeightInfo::remove_member(rank as u32)), + pays_fee: Pays::Yes, + }) + } + + /// Add an aye or nay vote for the sender to the given proposal. + /// + /// - `origin`: Must be `Signed` by a member account. + /// - `poll`: Index of a poll which is ongoing. + /// - `aye`: `true` if the vote is to approve the proposal, `false` otherwise. + /// + /// Transaction fees are be waived if the member is voting on any particular proposal + /// for the first time and the call is successful. Subsequent vote changes will charge a + /// fee. + /// + /// Weight: `O(1)`, less if there was no previous vote on the poll by the member. + #[pallet::weight(T::WeightInfo::vote())] + pub fn vote( + origin: OriginFor, + poll: PollIndexOf, + aye: bool, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + let record = Self::ensure_member(&who)?; + use VoteRecord::*; + let mut pays = Pays::Yes; + + let (tally, vote) = T::Polls::try_access_poll( + poll, + |mut status| -> Result<(TallyOf, VoteRecord), DispatchError> { + match status { + PollStatus::None | PollStatus::Completed(..) => + Err(Error::::NotPolling)?, + PollStatus::Ongoing(ref mut tally, class) => { + match Voting::::get(&poll, &who) { + Some(Aye(votes)) => { + tally.bare_ayes.saturating_dec(); + tally.ayes.saturating_reduce(votes); + }, + Some(Nay(votes)) => tally.nays.saturating_reduce(votes), + None => pays = Pays::No, + } + let min_rank = T::MinRankOfClass::convert(class); + let votes = Self::rank_to_votes(record.rank, min_rank)?; + let vote = VoteRecord::from((aye, votes)); + match aye { + true => { + tally.bare_ayes.saturating_inc(); + tally.ayes.saturating_accrue(votes); + }, + false => tally.nays.saturating_accrue(votes), + } + Voting::::insert(&poll, &who, &vote); + Ok((tally.clone(), vote)) + }, + } + }, + )?; + Self::deposit_event(Event::Voted { who, poll, vote, tally }); + Ok(pays.into()) + } + + /// Remove votes from the given poll. It must have ended. + /// + /// - `origin`: Must be `Signed` by any account. + /// - `poll_index`: Index of a poll which is completed and for which votes continue to + /// exist. + /// - `max`: Maximum number of vote items from remove in this call. + /// + /// Transaction fees are waived if the operation is successful. + /// + /// Weight `O(max)` (less if there are fewer items to remove than `max`). + #[pallet::weight(T::WeightInfo::cleanup_poll(*max))] + pub fn cleanup_poll( + origin: OriginFor, + poll_index: PollIndexOf, + max: u32, + ) -> DispatchResultWithPostInfo { + ensure_signed(origin)?; + ensure!(T::Polls::as_ongoing(poll_index).is_none(), Error::::Ongoing); + + let r = Voting::::clear_prefix( + poll_index, + max, + VotingCleanup::::take(poll_index).as_ref().map(|c| &c[..]), + ); + if r.unique == 0 { + // return Err(Error::::NoneRemaining) + return Ok(Pays::Yes.into()) + } + if let Some(cursor) = r.maybe_cursor { + VotingCleanup::::insert(poll_index, BoundedVec::truncate_from(cursor)); + } + Ok(PostDispatchInfo { + actual_weight: Some(T::WeightInfo::cleanup_poll(r.unique)), + pays_fee: Pays::No, + }) + } + } + + impl, I: 'static> Pallet { + fn ensure_member(who: &T::AccountId) -> Result { + Members::::get(who).ok_or(Error::::NotMember.into()) + } + + fn rank_to_votes(rank: Rank, min: Rank) -> Result { + let excess = rank.checked_sub(min).ok_or(Error::::RankTooLow)?; + Ok(T::VoteWeight::convert(excess)) + } + + fn remove_from_rank(who: &T::AccountId, rank: Rank) -> DispatchResult { + let last_index = MemberCount::::get(rank).saturating_sub(1); + let index = IdToIndex::::get(rank, &who).ok_or(Error::::Corruption)?; + if index != last_index { + let last = + IndexToId::::get(rank, last_index).ok_or(Error::::Corruption)?; + IdToIndex::::insert(rank, &last, index); + IndexToId::::insert(rank, index, &last); + } + MemberCount::::mutate(rank, |r| r.saturating_dec()); + Ok(()) + } + } +} diff --git a/frame/ranked-collective/src/tests.rs b/frame/ranked-collective/src/tests.rs new file mode 100644 index 0000000000000..1426012b63cea --- /dev/null +++ b/frame/ranked-collective/src/tests.rs @@ -0,0 +1,457 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! The crate's tests. + +use std::collections::BTreeMap; + +use frame_support::{ + assert_noop, assert_ok, + error::BadOrigin, + parameter_types, + traits::{ConstU16, ConstU32, ConstU64, EitherOf, Everything, MapSuccess, Polling, ReduceBy}, +}; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, Identity, IdentityLookup}, +}; + +use super::*; +use crate as pallet_ranked_collective; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Club: pallet_ranked_collective::{Pallet, Call, Storage, Event}, + } +); + +parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(1_000_000); +} +impl frame_system::Config for Test { + type BaseCallFilter = Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type Origin = Origin; + type Index = u64; + type BlockNumber = u64; + type Call = Call; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum TestPollState { + Ongoing(TallyOf, Rank), + Completed(u64, bool), +} +use TestPollState::*; + +parameter_types! { + pub static Polls: BTreeMap = vec![ + (1, Completed(1, true)), + (2, Completed(2, false)), + (3, Ongoing(Tally::from_parts(0, 0, 0), 1)), + ].into_iter().collect(); +} + +pub struct TestPolls; +impl Polling> for TestPolls { + type Index = u8; + type Votes = Votes; + type Moment = u64; + type Class = Rank; + fn classes() -> Vec { + vec![0, 1, 2] + } + fn as_ongoing(index: u8) -> Option<(TallyOf, Self::Class)> { + Polls::get().remove(&index).and_then(|x| { + if let TestPollState::Ongoing(t, c) = x { + Some((t, c)) + } else { + None + } + }) + } + fn access_poll( + index: Self::Index, + f: impl FnOnce(PollStatus<&mut TallyOf, Self::Moment, Self::Class>) -> R, + ) -> R { + let mut polls = Polls::get(); + let entry = polls.get_mut(&index); + let r = match entry { + Some(Ongoing(ref mut tally_mut_ref, class)) => + f(PollStatus::Ongoing(tally_mut_ref, *class)), + Some(Completed(when, succeeded)) => f(PollStatus::Completed(*when, *succeeded)), + None => f(PollStatus::None), + }; + Polls::set(polls); + r + } + fn try_access_poll( + index: Self::Index, + f: impl FnOnce( + PollStatus<&mut TallyOf, Self::Moment, Self::Class>, + ) -> Result, + ) -> Result { + let mut polls = Polls::get(); + let entry = polls.get_mut(&index); + let r = match entry { + Some(Ongoing(ref mut tally_mut_ref, class)) => + f(PollStatus::Ongoing(tally_mut_ref, *class)), + Some(Completed(when, succeeded)) => f(PollStatus::Completed(*when, *succeeded)), + None => f(PollStatus::None), + }?; + Polls::set(polls); + Ok(r) + } + + #[cfg(feature = "runtime-benchmarks")] + fn create_ongoing(class: Self::Class) -> Result { + let mut polls = Polls::get(); + let i = polls.keys().rev().next().map_or(0, |x| x + 1); + polls.insert(i, Ongoing(Tally::new(class), class)); + Polls::set(polls); + Ok(i) + } + + #[cfg(feature = "runtime-benchmarks")] + fn end_ongoing(index: Self::Index, approved: bool) -> Result<(), ()> { + let mut polls = Polls::get(); + match polls.get(&index) { + Some(Ongoing(..)) => {}, + _ => return Err(()), + } + let now = frame_system::Pallet::::block_number(); + polls.insert(index, Completed(now, approved)); + Polls::set(polls); + Ok(()) + } +} + +impl Config for Test { + type WeightInfo = (); + type Event = Event; + type PromoteOrigin = EitherOf< + // Root can promote arbitrarily. + frame_system::EnsureRootWithSuccess>, + // Members can promote up to the rank of 2 below them. + MapSuccess, ReduceBy>>, + >; + type DemoteOrigin = EitherOf< + // Root can demote arbitrarily. + frame_system::EnsureRootWithSuccess>, + // Members can demote up to the rank of 3 below them. + MapSuccess, ReduceBy>>, + >; + type Polls = TestPolls; + type MinRankOfClass = Identity; + type VoteWeight = Geometric; +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +fn next_block() { + System::set_block_number(System::block_number() + 1); +} + +fn member_count(r: Rank) -> MemberIndex { + MemberCount::::get(r) +} + +#[allow(dead_code)] +fn run_to(n: u64) { + while System::block_number() < n { + next_block(); + } +} + +fn tally(index: u8) -> TallyOf { + >>::as_ongoing(index).expect("No poll").0 +} + +#[test] +#[ignore] +#[should_panic(expected = "No poll")] +fn unknown_poll_should_panic() { + let _ = tally(0); +} + +#[test] +#[ignore] +#[should_panic(expected = "No poll")] +fn completed_poll_should_panic() { + let _ = tally(1); +} + +#[test] +fn basic_stuff() { + new_test_ext().execute_with(|| { + assert_eq!(tally(3), Tally::from_parts(0, 0, 0)); + }); +} + +#[test] +fn member_lifecycle_works() { + new_test_ext().execute_with(|| { + assert_ok!(Club::add_member(Origin::root(), 1)); + assert_ok!(Club::promote_member(Origin::root(), 1)); + assert_ok!(Club::demote_member(Origin::root(), 1)); + assert_ok!(Club::demote_member(Origin::root(), 1)); + assert_eq!(member_count(0), 0); + assert_eq!(member_count(1), 0); + }); +} + +#[test] +fn add_remove_works() { + new_test_ext().execute_with(|| { + assert_noop!(Club::add_member(Origin::signed(1), 1), DispatchError::BadOrigin); + assert_ok!(Club::add_member(Origin::root(), 1)); + assert_eq!(member_count(0), 1); + + assert_ok!(Club::demote_member(Origin::root(), 1)); + assert_eq!(member_count(0), 0); + + assert_ok!(Club::add_member(Origin::root(), 1)); + assert_eq!(member_count(0), 1); + + assert_ok!(Club::add_member(Origin::root(), 2)); + assert_eq!(member_count(0), 2); + + assert_ok!(Club::add_member(Origin::root(), 3)); + assert_eq!(member_count(0), 3); + + assert_ok!(Club::demote_member(Origin::root(), 3)); + assert_eq!(member_count(0), 2); + + assert_ok!(Club::demote_member(Origin::root(), 1)); + assert_eq!(member_count(0), 1); + + assert_ok!(Club::demote_member(Origin::root(), 2)); + assert_eq!(member_count(0), 0); + }); +} + +#[test] +fn promote_demote_works() { + new_test_ext().execute_with(|| { + assert_noop!(Club::add_member(Origin::signed(1), 1), DispatchError::BadOrigin); + assert_ok!(Club::add_member(Origin::root(), 1)); + assert_eq!(member_count(0), 1); + assert_eq!(member_count(1), 0); + + assert_ok!(Club::add_member(Origin::root(), 2)); + assert_eq!(member_count(0), 2); + assert_eq!(member_count(1), 0); + + assert_ok!(Club::promote_member(Origin::root(), 1)); + assert_eq!(member_count(0), 2); + assert_eq!(member_count(1), 1); + + assert_ok!(Club::promote_member(Origin::root(), 2)); + assert_eq!(member_count(0), 2); + assert_eq!(member_count(1), 2); + + assert_ok!(Club::demote_member(Origin::root(), 1)); + assert_eq!(member_count(0), 2); + assert_eq!(member_count(1), 1); + + assert_noop!(Club::demote_member(Origin::signed(1), 1), DispatchError::BadOrigin); + assert_ok!(Club::demote_member(Origin::root(), 1)); + assert_eq!(member_count(0), 1); + assert_eq!(member_count(1), 1); + }); +} + +#[test] +fn promote_demote_by_rank_works() { + new_test_ext().execute_with(|| { + assert_ok!(Club::add_member(Origin::root(), 1)); + for _ in 0..7 { + assert_ok!(Club::promote_member(Origin::root(), 1)); + } + + // #1 can add #2 and promote to rank 1 + assert_ok!(Club::add_member(Origin::signed(1), 2)); + assert_ok!(Club::promote_member(Origin::signed(1), 2)); + // #2 as rank 1 cannot do anything privileged + assert_noop!(Club::add_member(Origin::signed(2), 3), BadOrigin); + + assert_ok!(Club::promote_member(Origin::signed(1), 2)); + // #2 as rank 2 can add #3. + assert_ok!(Club::add_member(Origin::signed(2), 3)); + + // #2 as rank 2 cannot promote #3 to rank 1 + assert_noop!(Club::promote_member(Origin::signed(2), 3), Error::::NoPermission); + + // #1 as rank 7 can promote #2 only up to rank 5 and once there cannot demote them. + assert_ok!(Club::promote_member(Origin::signed(1), 2)); + assert_ok!(Club::promote_member(Origin::signed(1), 2)); + assert_ok!(Club::promote_member(Origin::signed(1), 2)); + assert_noop!(Club::promote_member(Origin::signed(1), 2), Error::::NoPermission); + assert_noop!(Club::demote_member(Origin::signed(1), 2), Error::::NoPermission); + + // #2 as rank 5 can promote #3 only up to rank 3 and once there cannot demote them. + assert_ok!(Club::promote_member(Origin::signed(2), 3)); + assert_ok!(Club::promote_member(Origin::signed(2), 3)); + assert_ok!(Club::promote_member(Origin::signed(2), 3)); + assert_noop!(Club::promote_member(Origin::signed(2), 3), Error::::NoPermission); + assert_noop!(Club::demote_member(Origin::signed(2), 3), Error::::NoPermission); + + // #2 can add #4 & #5 as rank 0 and #6 & #7 as rank 1. + assert_ok!(Club::add_member(Origin::signed(2), 4)); + assert_ok!(Club::add_member(Origin::signed(2), 5)); + assert_ok!(Club::add_member(Origin::signed(2), 6)); + assert_ok!(Club::promote_member(Origin::signed(2), 6)); + assert_ok!(Club::add_member(Origin::signed(2), 7)); + assert_ok!(Club::promote_member(Origin::signed(2), 7)); + + // #3 as rank 3 can demote/remove #4 & #5 but not #6 & #7 + assert_ok!(Club::demote_member(Origin::signed(3), 4)); + assert_ok!(Club::remove_member(Origin::signed(3), 5, 0)); + assert_noop!(Club::demote_member(Origin::signed(3), 6), Error::::NoPermission); + assert_noop!(Club::remove_member(Origin::signed(3), 7, 1), Error::::NoPermission); + + // #2 as rank 5 can demote/remove #6 & #7 + assert_ok!(Club::demote_member(Origin::signed(2), 6)); + assert_ok!(Club::remove_member(Origin::signed(2), 7, 1)); + }); +} + +#[test] +fn voting_works() { + new_test_ext().execute_with(|| { + assert_ok!(Club::add_member(Origin::root(), 0)); + assert_ok!(Club::add_member(Origin::root(), 1)); + assert_ok!(Club::promote_member(Origin::root(), 1)); + assert_ok!(Club::add_member(Origin::root(), 2)); + assert_ok!(Club::promote_member(Origin::root(), 2)); + assert_ok!(Club::promote_member(Origin::root(), 2)); + assert_ok!(Club::add_member(Origin::root(), 3)); + assert_ok!(Club::promote_member(Origin::root(), 3)); + assert_ok!(Club::promote_member(Origin::root(), 3)); + assert_ok!(Club::promote_member(Origin::root(), 3)); + + assert_noop!(Club::vote(Origin::signed(0), 3, true), Error::::RankTooLow); + assert_eq!(tally(3), Tally::from_parts(0, 0, 0)); + + assert_ok!(Club::vote(Origin::signed(1), 3, true)); + assert_eq!(tally(3), Tally::from_parts(1, 1, 0)); + assert_ok!(Club::vote(Origin::signed(1), 3, false)); + assert_eq!(tally(3), Tally::from_parts(0, 0, 1)); + + assert_ok!(Club::vote(Origin::signed(2), 3, true)); + assert_eq!(tally(3), Tally::from_parts(1, 3, 1)); + assert_ok!(Club::vote(Origin::signed(2), 3, false)); + assert_eq!(tally(3), Tally::from_parts(0, 0, 4)); + + assert_ok!(Club::vote(Origin::signed(3), 3, true)); + assert_eq!(tally(3), Tally::from_parts(1, 6, 4)); + assert_ok!(Club::vote(Origin::signed(3), 3, false)); + assert_eq!(tally(3), Tally::from_parts(0, 0, 10)); + }); +} + +#[test] +fn cleanup_works() { + new_test_ext().execute_with(|| { + assert_ok!(Club::add_member(Origin::root(), 1)); + assert_ok!(Club::promote_member(Origin::root(), 1)); + assert_ok!(Club::add_member(Origin::root(), 2)); + assert_ok!(Club::promote_member(Origin::root(), 2)); + assert_ok!(Club::add_member(Origin::root(), 3)); + assert_ok!(Club::promote_member(Origin::root(), 3)); + + assert_ok!(Club::vote(Origin::signed(1), 3, true)); + assert_ok!(Club::vote(Origin::signed(2), 3, false)); + assert_ok!(Club::vote(Origin::signed(3), 3, true)); + + assert_noop!(Club::cleanup_poll(Origin::signed(4), 3, 10), Error::::Ongoing); + Polls::set( + vec![(1, Completed(1, true)), (2, Completed(2, false)), (3, Completed(3, true))] + .into_iter() + .collect(), + ); + assert_ok!(Club::cleanup_poll(Origin::signed(4), 3, 10)); + // NOTE: This will fail until #10016 is merged. + // assert_noop!(Club::cleanup_poll(Origin::signed(4), 3, 10), Error::::NoneRemaining); + }); +} + +#[test] +fn ensure_ranked_works() { + new_test_ext().execute_with(|| { + assert_ok!(Club::add_member(Origin::root(), 1)); + assert_ok!(Club::promote_member(Origin::root(), 1)); + assert_ok!(Club::add_member(Origin::root(), 2)); + assert_ok!(Club::promote_member(Origin::root(), 2)); + assert_ok!(Club::promote_member(Origin::root(), 2)); + assert_ok!(Club::add_member(Origin::root(), 3)); + assert_ok!(Club::promote_member(Origin::root(), 3)); + assert_ok!(Club::promote_member(Origin::root(), 3)); + assert_ok!(Club::promote_member(Origin::root(), 3)); + + use frame_support::traits::OriginTrait; + type Rank1 = EnsureRanked; + type Rank2 = EnsureRanked; + type Rank3 = EnsureRanked; + type Rank4 = EnsureRanked; + assert_eq!(Rank1::try_origin(Origin::signed(1)).unwrap(), 1); + assert_eq!(Rank1::try_origin(Origin::signed(2)).unwrap(), 2); + assert_eq!(Rank1::try_origin(Origin::signed(3)).unwrap(), 3); + assert_eq!(Rank2::try_origin(Origin::signed(1)).unwrap_err().as_signed().unwrap(), 1); + assert_eq!(Rank2::try_origin(Origin::signed(2)).unwrap(), 2); + assert_eq!(Rank2::try_origin(Origin::signed(3)).unwrap(), 3); + assert_eq!(Rank3::try_origin(Origin::signed(1)).unwrap_err().as_signed().unwrap(), 1); + assert_eq!(Rank3::try_origin(Origin::signed(2)).unwrap_err().as_signed().unwrap(), 2); + assert_eq!(Rank3::try_origin(Origin::signed(3)).unwrap(), 3); + assert_eq!(Rank4::try_origin(Origin::signed(1)).unwrap_err().as_signed().unwrap(), 1); + assert_eq!(Rank4::try_origin(Origin::signed(2)).unwrap_err().as_signed().unwrap(), 2); + assert_eq!(Rank4::try_origin(Origin::signed(3)).unwrap_err().as_signed().unwrap(), 3); + }); +} diff --git a/frame/ranked-collective/src/weights.rs b/frame/ranked-collective/src/weights.rs new file mode 100644 index 0000000000000..3048dd804a5e2 --- /dev/null +++ b/frame/ranked-collective/src/weights.rs @@ -0,0 +1,187 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for pallet_ranked_collective +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-05-19, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// /Users/gav/Core/substrate/target/release/substrate +// benchmark +// pallet +// --pallet +// pallet-ranked-collective +// --extrinsic=* +// --chain=dev +// --steps=50 +// --repeat=20 +// --output=../../../frame/ranked-collective/src/weights.rs +// --template=../../../.maintain/frame-weight-template.hbs +// --header=../../../HEADER-APACHE2 +// --record-proof + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_ranked_collective. +pub trait WeightInfo { + fn add_member() -> Weight; + fn remove_member(r: u32, ) -> Weight; + fn promote_member(r: u32, ) -> Weight; + fn demote_member(r: u32, ) -> Weight; + fn vote() -> Weight; + fn cleanup_poll(n: u32, ) -> Weight; +} + +/// Weights for pallet_ranked_collective using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: RankedCollective Members (r:1 w:1) + // Storage: RankedCollective MemberCount (r:1 w:1) + // Storage: RankedCollective IndexToId (r:0 w:1) + // Storage: RankedCollective IdToIndex (r:0 w:1) + fn add_member() -> Weight { + (11_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) + } + // Storage: RankedCollective Members (r:1 w:1) + // Storage: RankedCollective MemberCount (r:1 w:1) + // Storage: RankedCollective IdToIndex (r:1 w:1) + // Storage: RankedCollective IndexToId (r:1 w:1) + fn remove_member(r: u32, ) -> Weight { + (16_855_000 as Weight) + // Standard Error: 27_000 + .saturating_add((8_107_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(r as Weight))) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) + .saturating_add(T::DbWeight::get().writes((3 as Weight).saturating_mul(r as Weight))) + } + // Storage: RankedCollective Members (r:1 w:1) + // Storage: RankedCollective MemberCount (r:1 w:1) + // Storage: RankedCollective IndexToId (r:0 w:1) + // Storage: RankedCollective IdToIndex (r:0 w:1) + fn promote_member(r: u32, ) -> Weight { + (11_936_000 as Weight) + // Standard Error: 3_000 + .saturating_add((9_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) + } + // Storage: RankedCollective Members (r:1 w:1) + // Storage: RankedCollective MemberCount (r:1 w:1) + // Storage: RankedCollective IdToIndex (r:1 w:1) + // Storage: RankedCollective IndexToId (r:1 w:1) + fn demote_member(r: u32, ) -> Weight { + (17_582_000 as Weight) + // Standard Error: 14_000 + .saturating_add((142_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) + } + // Storage: RankedCollective Members (r:1 w:0) + // Storage: RankedPolls ReferendumInfoFor (r:1 w:1) + // Storage: RankedCollective Voting (r:1 w:1) + // Storage: Scheduler Agenda (r:2 w:2) + fn vote() -> Weight { + (22_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) + } + // Storage: RankedPolls ReferendumInfoFor (r:1 w:0) + // Storage: RankedCollective Voting (r:0 w:1) + fn cleanup_poll(n: u32, ) -> Weight { + (6_188_000 as Weight) + // Standard Error: 1_000 + .saturating_add((867_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(n as Weight))) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: RankedCollective Members (r:1 w:1) + // Storage: RankedCollective MemberCount (r:1 w:1) + // Storage: RankedCollective IndexToId (r:0 w:1) + // Storage: RankedCollective IdToIndex (r:0 w:1) + fn add_member() -> Weight { + (11_000_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + } + // Storage: RankedCollective Members (r:1 w:1) + // Storage: RankedCollective MemberCount (r:1 w:1) + // Storage: RankedCollective IdToIndex (r:1 w:1) + // Storage: RankedCollective IndexToId (r:1 w:1) + fn remove_member(r: u32, ) -> Weight { + (16_855_000 as Weight) + // Standard Error: 27_000 + .saturating_add((8_107_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(RocksDbWeight::get().reads(4 as Weight)) + .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(r as Weight))) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + .saturating_add(RocksDbWeight::get().writes((3 as Weight).saturating_mul(r as Weight))) + } + // Storage: RankedCollective Members (r:1 w:1) + // Storage: RankedCollective MemberCount (r:1 w:1) + // Storage: RankedCollective IndexToId (r:0 w:1) + // Storage: RankedCollective IdToIndex (r:0 w:1) + fn promote_member(r: u32, ) -> Weight { + (11_936_000 as Weight) + // Standard Error: 3_000 + .saturating_add((9_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + } + // Storage: RankedCollective Members (r:1 w:1) + // Storage: RankedCollective MemberCount (r:1 w:1) + // Storage: RankedCollective IdToIndex (r:1 w:1) + // Storage: RankedCollective IndexToId (r:1 w:1) + fn demote_member(r: u32, ) -> Weight { + (17_582_000 as Weight) + // Standard Error: 14_000 + .saturating_add((142_000 as Weight).saturating_mul(r as Weight)) + .saturating_add(RocksDbWeight::get().reads(4 as Weight)) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + } + // Storage: RankedCollective Members (r:1 w:0) + // Storage: RankedPolls ReferendumInfoFor (r:1 w:1) + // Storage: RankedCollective Voting (r:1 w:1) + // Storage: Scheduler Agenda (r:2 w:2) + fn vote() -> Weight { + (22_000_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(5 as Weight)) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + } + // Storage: RankedPolls ReferendumInfoFor (r:1 w:0) + // Storage: RankedCollective Voting (r:0 w:1) + fn cleanup_poll(n: u32, ) -> Weight { + (6_188_000 as Weight) + // Standard Error: 1_000 + .saturating_add((867_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(n as Weight))) + } +} diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index e3f31bc411328..3fd54b54d331e 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -150,7 +150,7 @@ pub mod pallet { /// Handler for the unbalanced reduction when slashing a preimage deposit. type Slash: OnUnbalanced>; /// The counting type for votes. Usually just balance. - type Votes: AtLeast32BitUnsigned + Copy + Parameter + Member; + type Votes: AtLeast32BitUnsigned + Copy + Parameter + Member + MaxEncodedLen; /// The tallying type. type Tally: VoteTally> + Clone diff --git a/frame/referenda/src/types.rs b/frame/referenda/src/types.rs index 27b3c6a5a9d59..3eba783246e10 100644 --- a/frame/referenda/src/types.rs +++ b/frame/referenda/src/types.rs @@ -136,7 +136,7 @@ pub struct TrackInfo { /// Information on the voting tracks. pub trait TracksInfo { /// The identifier for a track. - type Id: Copy + Parameter + Ord + PartialOrd + Send + Sync + 'static; + type Id: Copy + Parameter + Ord + PartialOrd + Send + Sync + 'static + MaxEncodedLen; /// The origin type from which a track is implied. type Origin; diff --git a/frame/support/procedural/src/construct_runtime/expand/origin.rs b/frame/support/procedural/src/construct_runtime/expand/origin.rs index 342a002b52b9d..d347818d65c38 100644 --- a/frame/support/procedural/src/construct_runtime/expand/origin.rs +++ b/frame/support/procedural/src/construct_runtime/expand/origin.rs @@ -186,9 +186,16 @@ pub fn expand_outer_origin( #system_path::RawOrigin::Root.into() } - fn signed(by: <#runtime as #system_path::Config>::AccountId) -> Self { + fn signed(by: Self::AccountId) -> Self { #system_path::RawOrigin::Signed(by).into() } + + fn as_signed(self) -> Option { + match self.caller { + OriginCaller::system(#system_path::RawOrigin::Signed(by)) => Some(by), + _ => None, + } + } } #[derive( diff --git a/frame/support/src/dispatch.rs b/frame/support/src/dispatch.rs index 0a5092b411930..d399340f0470e 100644 --- a/frame/support/src/dispatch.rs +++ b/frame/support/src/dispatch.rs @@ -2748,6 +2748,9 @@ mod tests { fn signed(_by: ::AccountId) -> Self { unimplemented!("Not required in tests!") } + fn as_signed(self) -> Option { + unimplemented!("Not required in tests!") + } } impl system::Config for TraitImpl { diff --git a/frame/support/src/storage/bounded_vec.rs b/frame/support/src/storage/bounded_vec.rs index d3f9bfdd21d3b..f1f4330ab2960 100644 --- a/frame/support/src/storage/bounded_vec.rs +++ b/frame/support/src/storage/bounded_vec.rs @@ -286,6 +286,12 @@ impl> BoundedVec { Self::with_bounded_capacity(Self::bound()) } + /// Consume and truncate the vector `v` in order to create a new instance of `Self` from it. + pub fn truncate_from(mut v: Vec) -> Self { + v.truncate(Self::bound()); + Self::unchecked_from(v) + } + /// Get the bound of the type in `usize`. pub fn bound() -> usize { S::get() as usize diff --git a/frame/support/src/storage/mod.rs b/frame/support/src/storage/mod.rs index 8b6f198b03f96..c878766643667 100644 --- a/frame/support/src/storage/mod.rs +++ b/frame/support/src/storage/mod.rs @@ -27,7 +27,7 @@ use crate::{ use codec::{Decode, Encode, EncodeLike, FullCodec, FullEncode}; use sp_core::storage::ChildInfo; use sp_runtime::generic::{Digest, DigestItem}; -use sp_std::prelude::*; +use sp_std::{marker::PhantomData, prelude::*}; pub use self::{ transactional::{ @@ -51,6 +51,10 @@ pub mod types; pub mod unhashed; pub mod weak_bounded_vec; +/// Utility type for converting a storage map into a `Get` impl which returns the maximum +/// key size. +pub struct KeyLenOf(PhantomData); + /// A trait for working with macro-generated storage values under the substrate storage API. /// /// Details on implementation can be found at [`generator::StorageValue`]. diff --git a/frame/support/src/storage/types/double_map.rs b/frame/support/src/storage/types/double_map.rs index 5662087cc896f..bf957dc0ff928 100644 --- a/frame/support/src/storage/types/double_map.rs +++ b/frame/support/src/storage/types/double_map.rs @@ -22,7 +22,7 @@ use crate::{ metadata::{StorageEntryMetadata, StorageEntryType}, storage::{ types::{OptionQuery, QueryKindTrait, StorageEntryMetadataBuilder}, - StorageAppend, StorageDecodeLength, StoragePrefixedMap, StorageTryAppend, + KeyLenOf, StorageAppend, StorageDecodeLength, StoragePrefixedMap, StorageTryAppend, }, traits::{Get, GetDefault, StorageInfo, StorageInstance}, }; @@ -70,6 +70,35 @@ pub struct StorageDoubleMap< )>, ); +impl Get + for KeyLenOf< + StorageDoubleMap< + Prefix, + Hasher1, + Key1, + Hasher2, + Key2, + Value, + QueryKind, + OnEmpty, + MaxValues, + >, + > where + Prefix: StorageInstance, + Hasher1: crate::hash::StorageHasher, + Hasher2: crate::hash::StorageHasher, + Key1: MaxEncodedLen, + Key2: MaxEncodedLen, +{ + fn get() -> u32 { + let z = Hasher1::max_len::() + + Hasher2::max_len::() + + Prefix::pallet_prefix().len() + + Prefix::STORAGE_PREFIX.len(); + z as u32 + } +} + impl crate::storage::generator::StorageDoubleMap for StorageDoubleMap diff --git a/frame/support/src/storage/types/map.rs b/frame/support/src/storage/types/map.rs index 6dac5b3756598..1a5500e589d3a 100644 --- a/frame/support/src/storage/types/map.rs +++ b/frame/support/src/storage/types/map.rs @@ -22,7 +22,7 @@ use crate::{ metadata::{StorageEntryMetadata, StorageEntryType}, storage::{ types::{OptionQuery, QueryKindTrait, StorageEntryMetadataBuilder}, - StorageAppend, StorageDecodeLength, StoragePrefixedMap, StorageTryAppend, + KeyLenOf, StorageAppend, StorageDecodeLength, StoragePrefixedMap, StorageTryAppend, }, traits::{Get, GetDefault, StorageInfo, StorageInstance}, }; @@ -53,6 +53,20 @@ pub struct StorageMap< MaxValues = GetDefault, >(core::marker::PhantomData<(Prefix, Hasher, Key, Value, QueryKind, OnEmpty, MaxValues)>); +impl Get + for KeyLenOf> +where + Prefix: StorageInstance, + Hasher: crate::hash::StorageHasher, + Key: FullCodec + MaxEncodedLen, +{ + fn get() -> u32 { + let z = + Hasher::max_len::() + Prefix::pallet_prefix().len() + Prefix::STORAGE_PREFIX.len(); + z as u32 + } +} + impl crate::storage::generator::StorageMap for StorageMap diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index d1872544e024d..fe983f5c292e3 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -97,7 +97,8 @@ mod dispatch; pub use dispatch::EnsureOneOf; pub use dispatch::{ AsEnsureOriginWithArg, DispatchableWithStorageLayer, EitherOf, EitherOfDiverse, EnsureOrigin, - EnsureOriginWithArg, NeverEnsureOrigin, OriginTrait, UnfilteredDispatchable, + EnsureOriginWithArg, MapSuccess, NeverEnsureOrigin, OriginTrait, ReduceBy, TryMapSuccess, + UnfilteredDispatchable, }; mod voting; diff --git a/frame/support/src/traits/dispatch.rs b/frame/support/src/traits/dispatch.rs index e06373348e499..720baf6c8a427 100644 --- a/frame/support/src/traits/dispatch.rs +++ b/frame/support/src/traits/dispatch.rs @@ -319,6 +319,9 @@ pub trait OriginTrait: Sized { /// Create with system signed origin and `frame_system::Config::BaseCallFilter`. fn signed(by: Self::AccountId) -> Self; + + /// Extract the signer from the message if it is a `Signed` origin. + fn as_signed(self) -> Option; } #[cfg(test)] diff --git a/frame/support/src/traits/voting.rs b/frame/support/src/traits/voting.rs index 6c802a6112246..7a3ff87e5940e 100644 --- a/frame/support/src/traits/voting.rs +++ b/frame/support/src/traits/voting.rs @@ -19,7 +19,7 @@ //! votes. use crate::dispatch::{DispatchError, Parameter}; -use codec::HasCompact; +use codec::{HasCompact, MaxEncodedLen}; use sp_arithmetic::{ traits::{SaturatedConversion, UniqueSaturatedFrom, UniqueSaturatedInto}, Perbill, @@ -123,9 +123,9 @@ impl PollStatus { } pub trait Polling { - type Index: Parameter + Member + Ord + PartialOrd + Copy + HasCompact; - type Votes: Parameter + Member + Ord + PartialOrd + Copy + HasCompact; - type Class: Parameter + Member + Ord + PartialOrd; + type Index: Parameter + Member + Ord + PartialOrd + Copy + HasCompact + MaxEncodedLen; + type Votes: Parameter + Member + Ord + PartialOrd + Copy + HasCompact + MaxEncodedLen; + type Class: Parameter + Member + Ord + PartialOrd + MaxEncodedLen; type Moment; /// Provides a vec of values that `T` may take. diff --git a/test-utils/runtime/src/lib.rs b/test-utils/runtime/src/lib.rs index aef061d952a96..97e060cb2a9bd 100644 --- a/test-utils/runtime/src/lib.rs +++ b/test-utils/runtime/src/lib.rs @@ -491,7 +491,10 @@ impl frame_support::traits::OriginTrait for Origin { fn root() -> Self { unimplemented!("Not required in tests!") } - fn signed(_by: ::AccountId) -> Self { + fn signed(_by: Self::AccountId) -> Self { + unimplemented!("Not required in tests!") + } + fn as_signed(self) -> Option { unimplemented!("Not required in tests!") } } From ca3f3a70a2ca8b1fa820201a1d0ac5aec8e93ef3 Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Wed, 1 Jun 2022 10:48:42 +0100 Subject: [PATCH 294/484] Introduce set function into storage maps (#11564) --- frame/support/src/storage/generator/double_map.rs | 7 +++++++ frame/support/src/storage/generator/map.rs | 7 +++++++ frame/support/src/storage/generator/nmap.rs | 7 +++++++ frame/support/src/storage/mod.rs | 9 +++++++++ frame/support/src/storage/types/counted_map.rs | 5 +++++ frame/support/src/storage/types/double_map.rs | 9 +++++++++ frame/support/src/storage/types/map.rs | 5 +++++ frame/support/src/storage/types/nmap.rs | 8 ++++++++ 8 files changed, 57 insertions(+) diff --git a/frame/support/src/storage/generator/double_map.rs b/frame/support/src/storage/generator/double_map.rs index 90ec92c622b1f..4ffe32651a9cc 100644 --- a/frame/support/src/storage/generator/double_map.rs +++ b/frame/support/src/storage/generator/double_map.rs @@ -151,6 +151,13 @@ where unhashed::get(&Self::storage_double_map_final_key(k1, k2)).ok_or(()) } + fn set, KArg2: EncodeLike>(k1: KArg1, k2: KArg2, q: Self::Query) { + match G::from_query_to_optional_value(q) { + Some(v) => Self::insert(k1, k2, v), + None => Self::remove(k1, k2), + } + } + fn take(k1: KArg1, k2: KArg2) -> Self::Query where KArg1: EncodeLike, diff --git a/frame/support/src/storage/generator/map.rs b/frame/support/src/storage/generator/map.rs index bd24e14e9e0d8..d190145ea4c00 100644 --- a/frame/support/src/storage/generator/map.rs +++ b/frame/support/src/storage/generator/map.rs @@ -245,6 +245,13 @@ impl> storage::StorageMap unhashed::get(Self::storage_map_final_key(key).as_ref()).ok_or(()) } + fn set>(key: KeyArg, q: Self::Query) { + match G::from_query_to_optional_value(q) { + Some(v) => Self::insert(key, v), + None => Self::remove(key), + } + } + fn insert, ValArg: EncodeLike>(key: KeyArg, val: ValArg) { unhashed::put(Self::storage_map_final_key(key).as_ref(), &val) } diff --git a/frame/support/src/storage/generator/nmap.rs b/frame/support/src/storage/generator/nmap.rs index 0175d681df1d4..f1d0f9a5f0801 100755 --- a/frame/support/src/storage/generator/nmap.rs +++ b/frame/support/src/storage/generator/nmap.rs @@ -138,6 +138,13 @@ where unhashed::get(&Self::storage_n_map_final_key::(key)).ok_or(()) } + fn set + TupleToEncodedIter>(key: KArg, q: Self::Query) { + match G::from_query_to_optional_value(q) { + Some(v) => Self::insert(key, v), + None => Self::remove(key), + } + } + fn take + TupleToEncodedIter>(key: KArg) -> Self::Query { let final_key = Self::storage_n_map_final_key::(key); diff --git a/frame/support/src/storage/mod.rs b/frame/support/src/storage/mod.rs index c878766643667..6911da630cb34 100644 --- a/frame/support/src/storage/mod.rs +++ b/frame/support/src/storage/mod.rs @@ -167,6 +167,9 @@ pub trait StorageMap { /// Load the value associated with the given key from the map. fn get>(key: KeyArg) -> Self::Query; + /// Store or remove the value to be associated with `key` so that `get` returns the `query`. + fn set>(key: KeyArg, query: Self::Query); + /// Try to get the value for the given key from the map. /// /// Returns `Ok` if it exists, `Err` if not. @@ -481,6 +484,9 @@ pub trait StorageDoubleMap { KArg1: EncodeLike, KArg2: EncodeLike; + /// Store or remove the value to be associated with `key` so that `get` returns the `query`. + fn set, KArg2: EncodeLike>(k1: KArg1, k2: KArg2, query: Self::Query); + /// Take a value from storage, removing it afterwards. fn take(k1: KArg1, k2: KArg2) -> Self::Query where @@ -657,6 +663,9 @@ pub trait StorageNMap { /// Returns `Ok` if it exists, `Err` if not. fn try_get + TupleToEncodedIter>(key: KArg) -> Result; + /// Store or remove the value to be associated with `key` so that `get` returns the `query`. + fn set + TupleToEncodedIter>(key: KArg, query: Self::Query); + /// Swap the values of two keys. fn swap(key1: KArg1, key2: KArg2) where diff --git a/frame/support/src/storage/types/counted_map.rs b/frame/support/src/storage/types/counted_map.rs index 44fcbf607935f..c4027acfe7232 100644 --- a/frame/support/src/storage/types/counted_map.rs +++ b/frame/support/src/storage/types/counted_map.rs @@ -132,6 +132,11 @@ where ::Map::try_get(key) } + /// Store or remove the value to be associated with `key` so that `get` returns the `query`. + pub fn set>(key: KeyArg, q: QueryKind::Query) { + ::Map::set(key, q) + } + /// Swap the values of two keys. pub fn swap, KeyArg2: EncodeLike>(key1: KeyArg1, key2: KeyArg2) { ::Map::swap(key1, key2) diff --git a/frame/support/src/storage/types/double_map.rs b/frame/support/src/storage/types/double_map.rs index bf957dc0ff928..2e090d30119aa 100644 --- a/frame/support/src/storage/types/double_map.rs +++ b/frame/support/src/storage/types/double_map.rs @@ -203,6 +203,15 @@ where >::try_get(k1, k2) } + /// Store or remove the value to be associated with `key` so that `get` returns the `query`. + pub fn set, KArg2: EncodeLike>( + k1: KArg1, + k2: KArg2, + q: QueryKind::Query, + ) { + >::set(k1, k2, q) + } + /// Take a value from storage, removing it afterwards. pub fn take(k1: KArg1, k2: KArg2) -> QueryKind::Query where diff --git a/frame/support/src/storage/types/map.rs b/frame/support/src/storage/types/map.rs index 1a5500e589d3a..f4ac83c22663b 100644 --- a/frame/support/src/storage/types/map.rs +++ b/frame/support/src/storage/types/map.rs @@ -152,6 +152,11 @@ where >::swap(key1, key2) } + /// Store or remove the value to be associated with `key` so that `get` returns the `query`. + pub fn set>(key: KeyArg, q: QueryKind::Query) { + >::set(key, q) + } + /// Store a value to be associated with the given key from the map. pub fn insert, ValArg: EncodeLike>(key: KeyArg, val: ValArg) { >::insert(key, val) diff --git a/frame/support/src/storage/types/nmap.rs b/frame/support/src/storage/types/nmap.rs index 1f303525b7476..dcbdac761fe15 100755 --- a/frame/support/src/storage/types/nmap.rs +++ b/frame/support/src/storage/types/nmap.rs @@ -142,6 +142,14 @@ where >::try_get(key) } + /// Store or remove the value to be associated with `key` so that `get` returns the `query`. + pub fn set + TupleToEncodedIter>( + key: KArg, + query: QueryKind::Query, + ) { + >::set(key, query) + } + /// Take a value from storage, removing it afterwards. pub fn take + TupleToEncodedIter>( key: KArg, From ae701e3951d29a08c58589f621ffc1e2d54f4f62 Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Wed, 1 Jun 2022 11:46:06 +0100 Subject: [PATCH 295/484] Introduce `SubmitOrigin` to Referenda pallet config (#11567) --- bin/node/runtime/src/lib.rs | 2 ++ frame/referenda/src/lib.rs | 6 ++++-- frame/referenda/src/mock.rs | 1 + frame/referenda/src/tests.rs | 2 -- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index bfac30db6c66f..b421a4f05ed1a 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -825,6 +825,7 @@ impl pallet_referenda::Config for Runtime { type Event = Event; type Scheduler = Scheduler; type Currency = pallet_balances::Pallet; + type SubmitOrigin = EnsureSigned; type CancelOrigin = EnsureRoot; type KillOrigin = EnsureRoot; type Slash = (); @@ -843,6 +844,7 @@ impl pallet_referenda::Config for Runtime { type Event = Event; type Scheduler = Scheduler; type Currency = pallet_balances::Pallet; + type SubmitOrigin = EnsureSigned; type CancelOrigin = EnsureRoot; type KillOrigin = EnsureRoot; type Slash = (); diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index 3fd54b54d331e..f21a78bcb7863 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -143,6 +143,8 @@ pub mod pallet { /// Currency type for this pallet. type Currency: ReservableCurrency; // Origins and unbalances. + /// Origin from which proposals may be submitted. + type SubmitOrigin: EnsureOrigin; /// Origin from which any vote may be cancelled. type CancelOrigin: EnsureOrigin; /// Origin from which any vote may be killed. @@ -343,7 +345,7 @@ pub mod pallet { impl, I: 'static> Pallet { /// Propose a referendum on a privileged action. /// - /// - `origin`: must be `Signed` and the account must have `SubmissionDeposit` funds + /// - `origin`: must be `SubmitOrigin` and the account must have `SubmissionDeposit` funds /// available. /// - `proposal_origin`: The origin from which the proposal should be executed. /// - `proposal_hash`: The hash of the proposal preimage. @@ -357,7 +359,7 @@ pub mod pallet { proposal_hash: T::Hash, enactment_moment: DispatchTime, ) -> DispatchResult { - let who = ensure_signed(origin)?; + let who = T::SubmitOrigin::ensure_origin(origin)?; let track = T::Tracks::track_for(&proposal_origin).map_err(|_| Error::::NoTrack)?; diff --git a/frame/referenda/src/mock.rs b/frame/referenda/src/mock.rs index a3026ce78e986..8b06fb3205e6f 100644 --- a/frame/referenda/src/mock.rs +++ b/frame/referenda/src/mock.rs @@ -215,6 +215,7 @@ impl Config for Test { type Event = Event; type Scheduler = Scheduler; type Currency = pallet_balances::Pallet; + type SubmitOrigin = frame_system::EnsureSigned; type CancelOrigin = EnsureSignedBy; type KillOrigin = EnsureRoot; type Slash = (); diff --git a/frame/referenda/src/tests.rs b/frame/referenda/src/tests.rs index 8134e024dda39..db55fdb8c40cf 100644 --- a/frame/referenda/src/tests.rs +++ b/frame/referenda/src/tests.rs @@ -28,8 +28,6 @@ use frame_support::{ }; use pallet_balances::Error as BalancesError; -// TODO: Scheduler should re-use `None` items in its `Agenda`. - #[test] fn params_should_work() { new_test_ext().execute_with(|| { From 2826b8fb2ece9ee0c209d7c88291d9db84d2e35b Mon Sep 17 00:00:00 2001 From: Vlad Date: Wed, 1 Jun 2022 14:28:24 +0300 Subject: [PATCH 296/484] Don't limit `test-linux-stable-int` job output (#11560) --- scripts/ci/gitlab/pipeline/test.yml | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/scripts/ci/gitlab/pipeline/test.yml b/scripts/ci/gitlab/pipeline/test.yml index eab673a5ce13c..419e9f0c7ead0 100644 --- a/scripts/ci/gitlab/pipeline/test.yml +++ b/scripts/ci/gitlab/pipeline/test.yml @@ -251,22 +251,10 @@ test-linux-stable-int: RUN_UI_TESTS: 1 script: - rusty-cachier snapshot create - - echo "___Logs will be partly shown at the end in case of failure.___" - - echo "___Full log will be saved to the job artifacts only in case of failure.___" - WASM_BUILD_NO_COLOR=1 RUST_LOG=sync=trace,consensus=trace,client=trace,state-db=trace,db=trace,forks=trace,state_db=trace,storage_cache=trace time cargo test -p node-cli --release --verbose --locked -- --ignored - &> ${CI_COMMIT_SHORT_SHA}_int_failure.log - rusty-cachier cache upload - after_script: - - !reference [.rusty-cachier, after_script] - - awk '/FAILED|^error\[/,0' ${CI_COMMIT_SHORT_SHA}_int_failure.log - artifacts: - name: $CI_COMMIT_SHORT_SHA - when: on_failure - expire_in: 3 days - paths: - - ${CI_COMMIT_SHORT_SHA}_int_failure.log check-tracing: stage: test From c942f30f57c89c0b31e0403eab294905d82960ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Wed, 1 Jun 2022 15:03:21 +0200 Subject: [PATCH 297/484] Fix all warnings when building for wasm (#11569) * Fix all warnings when building for wasm Besides that it also enables warnings as errors for wasm builds in the CI. * FMT * Make clippy happy --- frame/contracts/proc-macro/src/lib.rs | 6 ++++-- frame/nomination-pools/src/lib.rs | 4 ++-- frame/ranked-collective/src/lib.rs | 2 +- frame/try-runtime/src/lib.rs | 1 - primitives/block-builder/src/lib.rs | 9 ++++----- primitives/io/src/lib.rs | 6 +++--- primitives/state-machine/src/trie_backend_essence.rs | 5 ++--- primitives/version/src/lib.rs | 1 + scripts/ci/gitlab/pipeline/test.yml | 2 ++ 9 files changed, 19 insertions(+), 17 deletions(-) diff --git a/frame/contracts/proc-macro/src/lib.rs b/frame/contracts/proc-macro/src/lib.rs index 43b59debc4ad0..dca29c805cec4 100644 --- a/frame/contracts/proc-macro/src/lib.rs +++ b/frame/contracts/proc-macro/src/lib.rs @@ -24,7 +24,7 @@ extern crate alloc; use alloc::string::ToString; use proc_macro2::TokenStream; use quote::{quote, quote_spanned}; -use syn::{parse_macro_input, spanned::Spanned, Data, DataStruct, DeriveInput, Fields, Ident}; +use syn::{parse_macro_input, Data, DeriveInput, Ident}; /// This derives `Debug` for a struct where each field must be of some numeric type. /// It interprets each field as its represents some weight and formats it as times so that @@ -84,7 +84,9 @@ fn derive_debug( /// This is only used then the `full` feature is activated. #[cfg(feature = "full")] -fn iterate_fields(data: &DataStruct, fmt: impl Fn(&Ident) -> TokenStream) -> TokenStream { +fn iterate_fields(data: &syn::DataStruct, fmt: impl Fn(&Ident) -> TokenStream) -> TokenStream { + use syn::{spanned::Spanned, Fields}; + match &data.fields { Fields::Named(fields) => { let recurse = fields.named.iter().filter_map(|f| { diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index c9d811fa4a22f..fd8ae8ab76b08 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -316,7 +316,7 @@ use frame_support::{ Currency, Defensive, DefensiveOption, DefensiveResult, DefensiveSaturating, ExistenceRequirement, Get, }, - CloneNoBound, DefaultNoBound, PartialEqNoBound, RuntimeDebugNoBound, + CloneNoBound, DefaultNoBound, RuntimeDebugNoBound, }; use scale_info::TypeInfo; use sp_core::U256; @@ -398,7 +398,7 @@ enum AccountType { /// A member in a pool. #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound, CloneNoBound)] -#[cfg_attr(feature = "std", derive(PartialEqNoBound, DefaultNoBound))] +#[cfg_attr(feature = "std", derive(frame_support::PartialEqNoBound, DefaultNoBound))] #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] pub struct PoolMember { diff --git a/frame/ranked-collective/src/lib.rs b/frame/ranked-collective/src/lib.rs index 521dfb6aad4e3..61686f1efeb2f 100644 --- a/frame/ranked-collective/src/lib.rs +++ b/frame/ranked-collective/src/lib.rs @@ -444,7 +444,7 @@ pub mod pallet { MemberCount::::insert(rank, index.checked_add(1).ok_or(Overflow)?); IdToIndex::::insert(rank, &who, index); IndexToId::::insert(rank, index, &who); - Members::::insert(&who, MemberRecord { rank, ..record }); + Members::::insert(&who, MemberRecord { rank }); Self::deposit_event(Event::RankChanged { who, rank }); Ok(()) diff --git a/frame/try-runtime/src/lib.rs b/frame/try-runtime/src/lib.rs index bf08112bfc376..e4f01d40c9d42 100644 --- a/frame/try-runtime/src/lib.rs +++ b/frame/try-runtime/src/lib.rs @@ -20,7 +20,6 @@ #![cfg_attr(not(feature = "std"), no_std)] use frame_support::weights::Weight; -use sp_std::prelude::*; sp_api::decl_runtime_apis! { /// Runtime api for testing the execution of a runtime upgrade. diff --git a/primitives/block-builder/src/lib.rs b/primitives/block-builder/src/lib.rs index 1b74c27b7ae43..cf1bfa256084d 100644 --- a/primitives/block-builder/src/lib.rs +++ b/primitives/block-builder/src/lib.rs @@ -20,10 +20,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use sp_inherents::{CheckInherentsResult, InherentData}; -use sp_runtime::{ - legacy::byte_sized_error::ApplyExtrinsicResult as ApplyExtrinsicResultBeforeV6, - traits::Block as BlockT, ApplyExtrinsicResult, -}; +use sp_runtime::{traits::Block as BlockT, ApplyExtrinsicResult}; sp_api::decl_runtime_apis! { /// The `BlockBuilder` api trait that provides the required functionality for building a block. @@ -36,7 +33,9 @@ sp_api::decl_runtime_apis! { fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResult; #[changed_in(6)] - fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResultBeforeV6; + fn apply_extrinsic( + extrinsic: ::Extrinsic, + ) -> sp_runtime::legacy::byte_sized_error::ApplyExtrinsicResult; /// Finish the current block. #[renamed("finalise_block", 3)] diff --git a/primitives/io/src/lib.rs b/primitives/io/src/lib.rs index 89b74dc3c6880..edffc37351147 100644 --- a/primitives/io/src/lib.rs +++ b/primitives/io/src/lib.rs @@ -1522,17 +1522,17 @@ mod tracing_setup { fn new_span(&self, attrs: &Attributes<'_>) -> Id { Id::from_u64(wasm_tracing::enter_span(Crossing(attrs.into()))) } - fn enter(&self, span: &Id) { + fn enter(&self, _: &Id) { // Do nothing, we already entered the span previously } /// Not implemented! We do not support recording values later /// Will panic when used. - fn record(&self, span: &Id, values: &Record<'_>) { + fn record(&self, _: &Id, _: &Record<'_>) { unimplemented! {} // this usage is not supported } /// Not implemented! We do not support recording values later /// Will panic when used. - fn record_follows_from(&self, span: &Id, follows: &Id) { + fn record_follows_from(&self, _: &Id, _: &Id) { unimplemented! {} // this usage is not supported } fn event(&self, event: &Event<'_>) { diff --git a/primitives/state-machine/src/trie_backend_essence.rs b/primitives/state-machine/src/trie_backend_essence.rs index c55a6b7e80481..7d910cc9602cc 100644 --- a/primitives/state-machine/src/trie_backend_essence.rs +++ b/primitives/state-machine/src/trie_backend_essence.rs @@ -29,8 +29,7 @@ use sp_trie::{ child_delta_trie_root, delta_trie_root, empty_child_trie_root, read_child_trie_value, read_trie_value, trie_types::{TrieDB, TrieError}, - DBValue, KeySpacedDB, LayoutV1 as Layout, PrefixedMemoryDB, Trie, TrieDBIterator, - TrieDBKeyIterator, + DBValue, KeySpacedDB, LayoutV1 as Layout, Trie, TrieDBIterator, TrieDBKeyIterator, }; #[cfg(feature = "std")] use std::collections::HashMap; @@ -619,7 +618,7 @@ pub trait TrieBackendStorage: Send + Sync { // This implementation is used by normal storage trie clients. #[cfg(feature = "std")] impl TrieBackendStorage for Arc> { - type Overlay = PrefixedMemoryDB; + type Overlay = sp_trie::PrefixedMemoryDB; fn get(&self, key: &H::Out, prefix: Prefix) -> Result> { Storage::::get(std::ops::Deref::deref(self), key, prefix) diff --git a/primitives/version/src/lib.rs b/primitives/version/src/lib.rs index 46f8af9e22c58..0bd62f0bac5aa 100644 --- a/primitives/version/src/lib.rs +++ b/primitives/version/src/lib.rs @@ -273,6 +273,7 @@ impl fmt::Display for RuntimeVersion { } } +#[cfg(feature = "std")] fn has_api_with bool>(apis: &ApisVec, id: &ApiId, predicate: P) -> bool { apis.iter().any(|(s, v)| s == id && predicate(*v)) } diff --git a/scripts/ci/gitlab/pipeline/test.yml b/scripts/ci/gitlab/pipeline/test.yml index 419e9f0c7ead0..5d7572395d39c 100644 --- a/scripts/ci/gitlab/pipeline/test.yml +++ b/scripts/ci/gitlab/pipeline/test.yml @@ -205,6 +205,7 @@ test-linux-stable: RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" RUST_BACKTRACE: 1 WASM_BUILD_NO_COLOR: 1 + WASM_BUILD_RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" # Ensure we run the UI tests. RUN_UI_TESTS: 1 script: @@ -247,6 +248,7 @@ test-linux-stable-int: RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" RUST_BACKTRACE: 1 WASM_BUILD_NO_COLOR: 1 + WASM_BUILD_RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" # Ensure we run the UI tests. RUN_UI_TESTS: 1 script: From c8737742a3551b50ab30918b784a54a7cb5e543d Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Wed, 1 Jun 2022 17:27:47 +0100 Subject: [PATCH 298/484] Helper macro for `Morph` impls (#11570) * Helper macro for Morph impls * No need to deprecate for now * Improved macro * Doc tests * Grumbles --- frame/ranked-collective/src/tests.rs | 4 +- frame/support/src/traits.rs | 2 +- frame/support/src/traits/dispatch.rs | 24 -- frame/support/src/traits/misc.rs | 72 +----- primitives/runtime/src/traits.rs | 369 ++++++++++++++++++++++++++- 5 files changed, 376 insertions(+), 95 deletions(-) diff --git a/frame/ranked-collective/src/tests.rs b/frame/ranked-collective/src/tests.rs index 1426012b63cea..4344a1be730fb 100644 --- a/frame/ranked-collective/src/tests.rs +++ b/frame/ranked-collective/src/tests.rs @@ -23,12 +23,12 @@ use frame_support::{ assert_noop, assert_ok, error::BadOrigin, parameter_types, - traits::{ConstU16, ConstU32, ConstU64, EitherOf, Everything, MapSuccess, Polling, ReduceBy}, + traits::{ConstU16, ConstU32, ConstU64, EitherOf, Everything, MapSuccess, Polling}, }; use sp_core::H256; use sp_runtime::{ testing::Header, - traits::{BlakeTwo256, Identity, IdentityLookup}, + traits::{BlakeTwo256, Identity, IdentityLookup, ReduceBy}, }; use super::*; diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index fe983f5c292e3..53bdd219aa3ee 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -97,7 +97,7 @@ mod dispatch; pub use dispatch::EnsureOneOf; pub use dispatch::{ AsEnsureOriginWithArg, DispatchableWithStorageLayer, EitherOf, EitherOfDiverse, EnsureOrigin, - EnsureOriginWithArg, MapSuccess, NeverEnsureOrigin, OriginTrait, ReduceBy, TryMapSuccess, + EnsureOriginWithArg, MapSuccess, NeverEnsureOrigin, OriginTrait, TryMapSuccess, UnfilteredDispatchable, }; diff --git a/frame/support/src/traits/dispatch.rs b/frame/support/src/traits/dispatch.rs index 720baf6c8a427..9bfb0145009eb 100644 --- a/frame/support/src/traits/dispatch.rs +++ b/frame/support/src/traits/dispatch.rs @@ -18,15 +18,12 @@ //! Traits for dealing with dispatching calls and the origin from which they are dispatched. use crate::dispatch::{DispatchResultWithPostInfo, Parameter, RawOrigin}; -use sp_arithmetic::traits::{CheckedSub, Zero}; use sp_runtime::{ traits::{BadOrigin, Member, Morph, TryMorph}, Either, }; use sp_std::marker::PhantomData; -use super::TypedGet; - /// Some sort of check on the origin is performed by this object. pub trait EnsureOrigin { /// A return type. @@ -226,27 +223,6 @@ impl< } } -/// Mutator which reduces a scalar by a particular amount. -pub struct ReduceBy(PhantomData); -impl TryMorph for ReduceBy -where - N::Type: CheckedSub, -{ - type Outcome = N::Type; - fn try_morph(r: N::Type) -> Result { - r.checked_sub(&N::get()).ok_or(()) - } -} -impl Morph for ReduceBy -where - N::Type: CheckedSub + Zero, -{ - type Outcome = N::Type; - fn morph(r: N::Type) -> N::Type { - r.checked_sub(&N::get()).unwrap_or(Zero::zero()) - } -} - /// Type that can be dispatched with an origin but without checking the origin filter. /// /// Implemented for pallet dispatchable type by `decl_module` and for runtime dispatchable by diff --git a/frame/support/src/traits/misc.rs b/frame/support/src/traits/misc.rs index bea4e2a394411..ced6df8f971e8 100644 --- a/frame/support/src/traits/misc.rs +++ b/frame/support/src/traits/misc.rs @@ -21,6 +21,11 @@ use crate::dispatch::Parameter; use codec::{CompactLen, Decode, DecodeAll, Encode, EncodeLike, Input, MaxEncodedLen}; use scale_info::{build::Fields, meta_type, Path, Type, TypeInfo, TypeParameter}; use sp_arithmetic::traits::{CheckedAdd, CheckedMul, CheckedSub, Saturating}; +#[doc(hidden)] +pub use sp_runtime::traits::{ + ConstBool, ConstI128, ConstI16, ConstI32, ConstI64, ConstI8, ConstU128, ConstU16, ConstU32, + ConstU64, ConstU8, Get, GetDefault, TypedGet, +}; use sp_runtime::{traits::Block as BlockT, DispatchError}; use sp_std::{cmp::Ordering, prelude::*}; @@ -387,73 +392,6 @@ where } } -/// A trait for querying a single value from a type defined in the trait. -/// -/// It is not required that the value is constant. -pub trait TypedGet { - /// The type which is returned. - type Type; - /// Return the current value. - fn get() -> Self::Type; -} - -/// A trait for querying a single value from a type. -/// -/// It is not required that the value is constant. -pub trait Get { - /// Return the current value. - fn get() -> T; -} - -impl Get for () { - fn get() -> T { - T::default() - } -} - -/// Implement Get by returning Default for any type that implements Default. -pub struct GetDefault; -impl Get for GetDefault { - fn get() -> T { - T::default() - } -} - -macro_rules! impl_const_get { - ($name:ident, $t:ty) => { - #[derive($crate::RuntimeDebug)] - pub struct $name; - impl Get<$t> for $name { - fn get() -> $t { - T - } - } - impl Get> for $name { - fn get() -> Option<$t> { - Some(T) - } - } - impl TypedGet for $name { - type Type = $t; - fn get() -> $t { - T - } - } - }; -} - -impl_const_get!(ConstBool, bool); -impl_const_get!(ConstU8, u8); -impl_const_get!(ConstU16, u16); -impl_const_get!(ConstU32, u32); -impl_const_get!(ConstU64, u64); -impl_const_get!(ConstU128, u128); -impl_const_get!(ConstI8, i8); -impl_const_get!(ConstI16, i16); -impl_const_get!(ConstI32, i32); -impl_const_get!(ConstI64, i64); -impl_const_get!(ConstI128, i128); - /// A type for which some values make sense to be able to drop without further consideration. pub trait TryDrop: Sized { /// Drop an instance cleanly. Only works if its value represents "no-operation". diff --git a/primitives/runtime/src/traits.rs b/primitives/runtime/src/traits.rs index ee6a526e95e1e..aa5908526b819 100644 --- a/primitives/runtime/src/traits.rs +++ b/primitives/runtime/src/traits.rs @@ -37,7 +37,9 @@ pub use sp_arithmetic::traits::{ UniqueSaturatedFrom, UniqueSaturatedInto, Zero, }; use sp_core::{self, storage::StateVersion, Hasher, RuntimeDebug, TypeId}; -use sp_std::{self, fmt::Debug, marker::PhantomData, prelude::*}; +#[doc(hidden)] +pub use sp_std::marker::PhantomData; +use sp_std::{self, fmt::Debug, prelude::*}; #[cfg(feature = "std")] use std::fmt::Display; #[cfg(feature = "std")] @@ -274,6 +276,192 @@ where } } +/// A trait for querying a single value from a type defined in the trait. +/// +/// It is not required that the value is constant. +pub trait TypedGet { + /// The type which is returned. + type Type; + /// Return the current value. + fn get() -> Self::Type; +} + +/// A trait for querying a single value from a type. +/// +/// It is not required that the value is constant. +pub trait Get { + /// Return the current value. + fn get() -> T; +} + +impl Get for () { + fn get() -> T { + T::default() + } +} + +/// Implement Get by returning Default for any type that implements Default. +pub struct GetDefault; +impl Get for GetDefault { + fn get() -> T { + T::default() + } +} + +macro_rules! impl_const_get { + ($name:ident, $t:ty) => { + #[doc = "Const getter for a basic type."] + #[derive($crate::RuntimeDebug)] + pub struct $name; + impl Get<$t> for $name { + fn get() -> $t { + T + } + } + impl Get> for $name { + fn get() -> Option<$t> { + Some(T) + } + } + impl TypedGet for $name { + type Type = $t; + fn get() -> $t { + T + } + } + }; +} + +impl_const_get!(ConstBool, bool); +impl_const_get!(ConstU8, u8); +impl_const_get!(ConstU16, u16); +impl_const_get!(ConstU32, u32); +impl_const_get!(ConstU64, u64); +impl_const_get!(ConstU128, u128); +impl_const_get!(ConstI8, i8); +impl_const_get!(ConstI16, i16); +impl_const_get!(ConstI32, i32); +impl_const_get!(ConstI64, i64); +impl_const_get!(ConstI128, i128); + +/// Create new implementations of the [`Get`](crate::traits::Get) trait. +/// +/// The so-called parameter type can be created in four different ways: +/// +/// - Using `const` to create a parameter type that provides a `const` getter. It is required that +/// the `value` is const. +/// +/// - Declare the parameter type without `const` to have more freedom when creating the value. +/// +/// NOTE: A more substantial version of this macro is available in `frame_support` crate which +/// allows mutable and persistant variants. +/// +/// # Examples +/// +/// ``` +/// # use sp_runtime::traits::Get; +/// # use sp_runtime::parameter_types; +/// // This function cannot be used in a const context. +/// fn non_const_expression() -> u64 { 99 } +/// +/// const FIXED_VALUE: u64 = 10; +/// parameter_types! { +/// pub const Argument: u64 = 42 + FIXED_VALUE; +/// /// Visibility of the type is optional +/// OtherArgument: u64 = non_const_expression(); +/// } +/// +/// trait Config { +/// type Parameter: Get; +/// type OtherParameter: Get; +/// } +/// +/// struct Runtime; +/// impl Config for Runtime { +/// type Parameter = Argument; +/// type OtherParameter = OtherArgument; +/// } +/// ``` +/// +/// # Invalid example: +/// +/// ```compile_fail +/// # use sp_runtime::traits::Get; +/// # use sp_runtime::parameter_types; +/// // This function cannot be used in a const context. +/// fn non_const_expression() -> u64 { 99 } +/// +/// parameter_types! { +/// pub const Argument: u64 = non_const_expression(); +/// } +/// ``` +#[macro_export] +macro_rules! parameter_types { + ( + $( #[ $attr:meta ] )* + $vis:vis const $name:ident: $type:ty = $value:expr; + $( $rest:tt )* + ) => ( + $( #[ $attr ] )* + $vis struct $name; + $crate::parameter_types!(@IMPL_CONST $name , $type , $value); + $crate::parameter_types!( $( $rest )* ); + ); + ( + $( #[ $attr:meta ] )* + $vis:vis $name:ident: $type:ty = $value:expr; + $( $rest:tt )* + ) => ( + $( #[ $attr ] )* + $vis struct $name; + $crate::parameter_types!(@IMPL $name, $type, $value); + $crate::parameter_types!( $( $rest )* ); + ); + () => (); + (@IMPL_CONST $name:ident, $type:ty, $value:expr) => { + impl $name { + /// Returns the value of this parameter type. + pub const fn get() -> $type { + $value + } + } + + impl> $crate::traits::Get for $name { + fn get() -> I { + I::from(Self::get()) + } + } + + impl $crate::traits::TypedGet for $name { + type Type = $type; + fn get() -> $type { + Self::get() + } + } + }; + (@IMPL $name:ident, $type:ty, $value:expr) => { + impl $name { + /// Returns the value of this parameter type. + pub fn get() -> $type { + $value + } + } + + impl> $crate::traits::Get for $name { + fn get() -> I { + I::from(Self::get()) + } + } + + impl $crate::traits::TypedGet for $name { + type Type = $type; + fn get() -> $type { + Self::get() + } + } + }; +} + /// Extensible conversion trait. Generic over only source type, with destination type being /// associated. pub trait Morph { @@ -310,6 +498,185 @@ impl TryMorph for Identity { } } +/// Create a `Morph` and/or `TryMorph` impls with a simple closure-like expression. +/// +/// # Examples +/// +/// ``` +/// # use sp_runtime::{morph_types, traits::{Morph, TryMorph, TypedGet, ConstU32}}; +/// # use sp_arithmetic::traits::CheckedSub; +/// +/// morph_types! { +/// /// Replace by some other value; produce both `Morph` and `TryMorph` implementations +/// pub type Replace = |_| -> V::Type { V::get() }; +/// /// A private `Morph` implementation to reduce a `u32` by 10. +/// type ReduceU32ByTen: Morph = |r: u32| -> u32 { r - 10 }; +/// /// A `TryMorph` implementation to reduce a scalar by a particular amount, checking for +/// /// underflow. +/// pub type CheckedReduceBy: TryMorph = |r: N::Type| -> Result { +/// r.checked_sub(&N::get()).ok_or(()) +/// } where N::Type: CheckedSub; +/// } +/// +/// trait Config { +/// type TestMorph1: Morph; +/// type TestTryMorph1: TryMorph; +/// type TestMorph2: Morph; +/// type TestTryMorph2: TryMorph; +/// } +/// +/// struct Runtime; +/// impl Config for Runtime { +/// type TestMorph1 = Replace>; +/// type TestTryMorph1 = Replace>; +/// type TestMorph2 = ReduceU32ByTen; +/// type TestTryMorph2 = CheckedReduceBy>; +/// } +/// ``` +#[macro_export] +macro_rules! morph_types { + ( + @DECL $( #[doc = $doc:expr] )* $vq:vis $name:ident () + ) => { + $( #[doc = $doc] )* $vq struct $name; + }; + ( + @DECL $( #[doc = $doc:expr] )* $vq:vis $name:ident ( $( $bound_id:ident ),+ ) + ) => { + $( #[doc = $doc] )* + $vq struct $name < $($bound_id,)* > ( $crate::traits::PhantomData< ( $($bound_id,)* ) > ) ; + }; + ( + @IMPL $name:ty : ( $( $bounds:tt )* ) ( $( $where:tt )* ) + = |$var:ident: $var_type:ty| -> $outcome:ty { $( $ex:expr )* } + ) => { + impl<$($bounds)*> $crate::traits::Morph<$var_type> for $name $( $where )? { + type Outcome = $outcome; + fn morph($var: $var_type) -> Self::Outcome { $( $ex )* } + } + }; + ( + @IMPL_TRY $name:ty : ( $( $bounds:tt )* ) ( $( $where:tt )* ) + = |$var:ident: $var_type:ty| -> $outcome:ty { $( $ex:expr )* } + ) => { + impl<$($bounds)*> $crate::traits::TryMorph<$var_type> for $name $( $where )? { + type Outcome = $outcome; + fn try_morph($var: $var_type) -> Result { $( $ex )* } + } + }; + ( + @IMPL $name:ty : () ( $( $where:tt )* ) + = |$var:ident: $var_type:ty| -> $outcome:ty { $( $ex:expr )* } + ) => { + impl $crate::traits::Morph<$var_type> for $name $( $where )? { + type Outcome = $outcome; + fn morph($var: $var_type) -> Self::Outcome { $( $ex )* } + } + }; + ( + @IMPL_TRY $name:ty : () ( $( $where:tt )* ) + = |$var:ident: $var_type:ty| -> $outcome:ty { $( $ex:expr )* } + ) => { + impl $crate::traits::TryMorph<$var_type> for $name $( $where )? { + type Outcome = $outcome; + fn try_morph($var: $var_type) -> Result { $( $ex )* } + } + }; + ( + @IMPL_BOTH $name:ty : ( $( $bounds:tt )* ) ( $( $where:tt )* ) + = |$var:ident: $var_type:ty| -> $outcome:ty { $( $ex:expr )* } + ) => { + morph_types! { + @IMPL $name : ($($bounds)*) ($($where)*) + = |$var: $var_type| -> $outcome { $( $ex )* } + } + morph_types! { + @IMPL_TRY $name : ($($bounds)*) ($($where)*) + = |$var: $var_type| -> $outcome { Ok({$( $ex )*}) } + } + }; + + ( + $( #[doc = $doc:expr] )* $vq:vis type $name:ident + $( < $( $bound_id:ident $( : $bound_head:path $( | $bound_tail:path )* )? ),+ > )? + $(: $type:tt)? + = |_| -> $outcome:ty { $( $ex:expr )* }; + $( $rest:tt )* + ) => { + morph_types! { + $( #[doc = $doc] )* $vq type $name + $( < $( $bound_id $( : $bound_head $( | $bound_tail )* )? ),+ > )? + EXTRA_GENERIC(X) + $(: $type)? + = |_x: X| -> $outcome { $( $ex )* }; + $( $rest )* + } + }; + ( + $( #[doc = $doc:expr] )* $vq:vis type $name:ident + $( < $( $bound_id:ident $( : $bound_head:path $( | $bound_tail:path )* )? ),+ > )? + $( EXTRA_GENERIC ($extra:ident) )? + = |$var:ident: $var_type:ty| -> $outcome:ty { $( $ex:expr )* } + $( where $( $where_path:ty : $where_bound_head:path $( | $where_bound_tail:path )* ),* )?; + $( $rest:tt )* + ) => { + morph_types! { @DECL $( #[doc = $doc] )* $vq $name ( $( $( $bound_id ),+ )? ) } + morph_types! { + @IMPL_BOTH $name $( < $( $bound_id ),* > )? : + ( $( $( $bound_id $( : $bound_head $( + $bound_tail )* )? , )+ )? $( $extra )? ) + ( $( where $( $where_path : $where_bound_head $( + $where_bound_tail )* ),* )? ) + = |$var: $var_type| -> $outcome { $( $ex )* } + } + morph_types!{ $($rest)* } + }; + ( + $( #[doc = $doc:expr] )* $vq:vis type $name:ident + $( < $( $bound_id:ident $( : $bound_head:path $( | $bound_tail:path )* )? ),+ > )? + $( EXTRA_GENERIC ($extra:ident) )? + : Morph + = |$var:ident: $var_type:ty| -> $outcome:ty { $( $ex:expr )* } + $( where $( $where_path:ty : $where_bound_head:path $( | $where_bound_tail:path )* ),* )?; + $( $rest:tt )* + ) => { + morph_types! { @DECL $( #[doc = $doc] )* $vq $name ( $( $( $bound_id ),+ )? ) } + morph_types! { + @IMPL $name $( < $( $bound_id ),* > )? : + ( $( $( $bound_id $( : $bound_head $( + $bound_tail )* )? , )+ )? $( $extra )? ) + ( $( where $( $where_path : $where_bound_head $( + $where_bound_tail )* ),* )? ) + = |$var: $var_type| -> $outcome { $( $ex )* } + } + morph_types!{ $($rest)* } + }; + ( + $( #[doc = $doc:expr] )* $vq:vis type $name:ident + $( < $( $bound_id:ident $( : $bound_head:path $( | $bound_tail:path )* )? ),+ > )? + $( EXTRA_GENERIC ($extra:ident) )? + : TryMorph + = |$var:ident: $var_type:ty| -> Result<$outcome:ty, ()> { $( $ex:expr )* } + $( where $( $where_path:ty : $where_bound_head:path $( | $where_bound_tail:path )* ),* )?; + $( $rest:tt )* + ) => { + morph_types! { @DECL $( #[doc = $doc] )* $vq $name ( $( $( $bound_id ),+ )? ) } + morph_types! { + @IMPL_TRY $name $( < $( $bound_id ),* > )? : + ( $( $( $bound_id $( : $bound_head $( + $bound_tail )* )? , )+ )? $( $extra )? ) + ( $( where $( $where_path : $where_bound_head $( + $where_bound_tail )* ),* )? ) + = |$var: $var_type| -> $outcome { $( $ex )* } + } + morph_types!{ $($rest)* } + }; + () => {} +} + +morph_types! { + /// Morpher to disregard the source value and replace with another. + pub type Replace = |_| -> V::Type { V::get() }; + /// Mutator which reduces a scalar by a particular amount. + pub type ReduceBy = |r: N::Type| -> N::Type { + r.checked_sub(&N::get()).unwrap_or(Zero::zero()) + } where N::Type: CheckedSub | Zero; +} + /// Extensible conversion trait. Generic over both source and destination types. pub trait Convert { /// Make conversion. From a910e38f8e14e96eda5fc52c6f48ed5a8656e593 Mon Sep 17 00:00:00 2001 From: Vlad Date: Wed, 1 Jun 2022 19:48:53 +0300 Subject: [PATCH 299/484] `rusty-cachier` pipeline impovements and fixes (#11572) --- .gitlab-ci.yml | 5 ++--- scripts/ci/gitlab/pipeline/build.yml | 16 ++++++++++++---- scripts/ci/gitlab/pipeline/test.yml | 10 +++++++--- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e218a2c1f8d3a..b4933333b3418 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -108,7 +108,7 @@ default: - $(rusty-cachier environment inject) - rusty-cachier project mtime after_script: - - rusty-cachier snapshot destroy + - env RUSTY_CACHIER_SUPRESS_OUTPUT=true rusty-cachier snapshot destroy .test-refs: rules: @@ -225,8 +225,7 @@ deploy-prometheus-alerting-rules: # This info is later used for the cache distribution and an overlay creation. rusty-cachier-notify: stage: notify - extends: - - .docker-env + extends: .docker-env script: - rusty-cachier cache notify diff --git a/scripts/ci/gitlab/pipeline/build.yml b/scripts/ci/gitlab/pipeline/build.yml index a07fe8a4cfa07..47d8b2bc4c525 100644 --- a/scripts/ci/gitlab/pipeline/build.yml +++ b/scripts/ci/gitlab/pipeline/build.yml @@ -50,6 +50,9 @@ build-linux-substrate: - .collect-artifacts - .docker-env - .build-refs + variables: + # this variable gets overriden by "rusty-cachier environment inject", use the value as default + CARGO_TARGET_DIR: "./target" needs: - job: test-linux-stable artifacts: false @@ -59,7 +62,7 @@ build-linux-substrate: script: - rusty-cachier snapshot create - WASM_BUILD_NO_COLOR=1 time cargo build --release --verbose - - mv /cargo_target_dir/release/substrate ./artifacts/substrate/. + - mv $CARGO_TARGET_DIR/release/substrate ./artifacts/substrate/. - echo -n "Substrate version = " - if [ "${CI_COMMIT_TAG}" ]; then echo "${CI_COMMIT_TAG}" | tee ./artifacts/substrate/VERSION; @@ -79,6 +82,9 @@ build-linux-substrate: - .collect-artifacts - .docker-env - .build-refs + variables: + # this variable gets overriden by "rusty-cachier environment inject", use the value as default + CARGO_TARGET_DIR: "./target" needs: - job: cargo-check-subkey artifacts: false @@ -90,7 +96,7 @@ build-linux-substrate: - cd ./bin/utils/subkey - SKIP_WASM_BUILD=1 time cargo build --release --verbose - cd - - - mv /cargo_target_dir/release/subkey ./artifacts/subkey/. + - mv $CARGO_TARGET_DIR/release/subkey ./artifacts/subkey/. - echo -n "Subkey version = " - ./artifacts/subkey/subkey --version | sed -n -E 's/^subkey ([0-9.]+.*)/\1/p' | @@ -131,6 +137,8 @@ build-rustdoc: SKIP_WASM_BUILD: 1 DOC_INDEX_PAGE: "sc_service/index.html" # default redirected page RUSTY_CACHIER_TOOLCHAIN: nightly + # this variable gets overriden by "rusty-cachier environment inject", use the value as default + CARGO_TARGET_DIR: "./target" artifacts: name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}-doc" when: on_success @@ -140,8 +148,8 @@ build-rustdoc: script: - rusty-cachier snapshot create - time cargo +nightly doc --workspace --all-features --verbose - - rm -f /cargo_target_dir/doc/.lock - - mv /cargo_target_dir/doc ./crate-docs + - rm -f $CARGO_TARGET_DIR/doc/.lock + - mv $CARGO_TARGET_DIR/doc ./crate-docs # FIXME: remove me after CI image gets nonroot - chown -R nonroot:nonroot ./crate-docs - echo "" > ./crate-docs/index.html diff --git a/scripts/ci/gitlab/pipeline/test.yml b/scripts/ci/gitlab/pipeline/test.yml index 5d7572395d39c..36f920ba275bd 100644 --- a/scripts/ci/gitlab/pipeline/test.yml +++ b/scripts/ci/gitlab/pipeline/test.yml @@ -180,18 +180,22 @@ test-deterministic-wasm: - .test-refs variables: WASM_BUILD_NO_COLOR: 1 + # this variable gets overriden by "rusty-cachier environment inject", use the value as default + CARGO_TARGET_DIR: "./target" script: - rusty-cachier snapshot create # build runtime - cargo build --verbose --release -p node-runtime # make checksum - - sha256sum /cargo_target_dir/release/wbuild/node-runtime/target/wasm32-unknown-unknown/release/node_runtime.wasm > checksum.sha256 - # clean up – FIXME: can we reuse some of the artifacts? - - cargo clean + - sha256sum $CARGO_TARGET_DIR/release/wbuild/node-runtime/target/wasm32-unknown-unknown/release/node_runtime.wasm > checksum.sha256 + # clean up + - rm -rf $CARGO_TARGET_DIR/release/wbuild # build again - cargo build --verbose --release -p node-runtime # confirm checksum - sha256sum -c ./checksum.sha256 + # clean up again, don't put release binaries into the cache + - rm -rf $CARGO_TARGET_DIR/release/wbuild - rusty-cachier cache upload test-linux-stable: From d78040b9c432c895e1f14bfdfd7d1c7f849c2f7c Mon Sep 17 00:00:00 2001 From: Vlad Date: Thu, 2 Jun 2022 11:48:12 +0300 Subject: [PATCH 300/484] CI: fix `build-subkey-macos` build job (#11573) * CI: fix `build-subkey-macos` build job * CI: use full path for the `CARGO_TARGET_DIR` default value --- scripts/ci/gitlab/pipeline/build.yml | 7 ++++--- scripts/ci/gitlab/pipeline/test.yml | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/ci/gitlab/pipeline/build.yml b/scripts/ci/gitlab/pipeline/build.yml index 47d8b2bc4c525..e6269c4e3949a 100644 --- a/scripts/ci/gitlab/pipeline/build.yml +++ b/scripts/ci/gitlab/pipeline/build.yml @@ -52,7 +52,7 @@ build-linux-substrate: - .build-refs variables: # this variable gets overriden by "rusty-cachier environment inject", use the value as default - CARGO_TARGET_DIR: "./target" + CARGO_TARGET_DIR: "$CI_PROJECT_DIR/target" needs: - job: test-linux-stable artifacts: false @@ -84,7 +84,7 @@ build-linux-substrate: - .build-refs variables: # this variable gets overriden by "rusty-cachier environment inject", use the value as default - CARGO_TARGET_DIR: "./target" + CARGO_TARGET_DIR: "$CI_PROJECT_DIR/target" needs: - job: cargo-check-subkey artifacts: false @@ -125,6 +125,7 @@ build-subkey-macos: tee ./artifacts/subkey/VERSION; - sha256sum ./artifacts/subkey/subkey | tee ./artifacts/subkey/subkey.sha256 - cp -r ./scripts/ci/docker/subkey.Dockerfile ./artifacts/subkey/ + after_script: [""] tags: - osx @@ -138,7 +139,7 @@ build-rustdoc: DOC_INDEX_PAGE: "sc_service/index.html" # default redirected page RUSTY_CACHIER_TOOLCHAIN: nightly # this variable gets overriden by "rusty-cachier environment inject", use the value as default - CARGO_TARGET_DIR: "./target" + CARGO_TARGET_DIR: "$CI_PROJECT_DIR/target" artifacts: name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}-doc" when: on_success diff --git a/scripts/ci/gitlab/pipeline/test.yml b/scripts/ci/gitlab/pipeline/test.yml index 36f920ba275bd..8ec16ab6839a5 100644 --- a/scripts/ci/gitlab/pipeline/test.yml +++ b/scripts/ci/gitlab/pipeline/test.yml @@ -181,7 +181,7 @@ test-deterministic-wasm: variables: WASM_BUILD_NO_COLOR: 1 # this variable gets overriden by "rusty-cachier environment inject", use the value as default - CARGO_TARGET_DIR: "./target" + CARGO_TARGET_DIR: "$CI_PROJECT_DIR/target" script: - rusty-cachier snapshot create # build runtime From cc0d17e08ba03f6f8f0e2eb6064fed782cc3d54e Mon Sep 17 00:00:00 2001 From: yjh Date: Thu, 2 Jun 2022 17:03:49 +0800 Subject: [PATCH 301/484] aura: export change_authorities and initialize_authorities (#11468) * aura: export change_authorities and initialize_authorities * add docs * fix docs --- frame/aura/src/lib.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/frame/aura/src/lib.rs b/frame/aura/src/lib.rs index 222360dc3a7d3..fd6882ca9f29f 100644 --- a/frame/aura/src/lib.rs +++ b/frame/aura/src/lib.rs @@ -146,7 +146,11 @@ pub mod pallet { } impl Pallet { - fn change_authorities(new: WeakBoundedVec) { + /// Change authorities. + /// + /// The storage will be applied immediately. + /// And aura consensus log will be appended to block's log. + pub fn change_authorities(new: WeakBoundedVec) { >::put(&new); let log = DigestItem::Consensus( @@ -156,7 +160,12 @@ impl Pallet { >::deposit_log(log); } - fn initialize_authorities(authorities: &[T::AuthorityId]) { + /// Initial authorities. + /// + /// The storage will be applied immediately. + /// + /// The authorities length must be equal or less than T::MaxAuthorities. + pub fn initialize_authorities(authorities: &[T::AuthorityId]) { if !authorities.is_empty() { assert!(>::get().is_empty(), "Authorities are already initialized!"); let bounded = >::try_from(authorities) From 9fdf1c986f71eb8e606e416be47b33b88e09e215 Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Thu, 2 Jun 2022 12:41:05 +0100 Subject: [PATCH 302/484] Reduce call size of Referenda pallet (#11578) * Reduce call size of Referenda pallet * Fixes * Fixes * Fixes * Docs --- frame/referenda/src/benchmarking.rs | 4 +-- frame/referenda/src/lib.rs | 4 +-- frame/referenda/src/mock.rs | 4 +-- frame/referenda/src/tests.rs | 33 +++++++++-------- .../src/construct_runtime/expand/call.rs | 36 +++++++++++++++++++ 5 files changed, 61 insertions(+), 20 deletions(-) diff --git a/frame/referenda/src/benchmarking.rs b/frame/referenda/src/benchmarking.rs index e4cbf2ca52e7c..9abd3768f780c 100644 --- a/frame/referenda/src/benchmarking.rs +++ b/frame/referenda/src/benchmarking.rs @@ -46,7 +46,7 @@ fn create_referendum() -> (T::AccountId, ReferendumIndex) { whitelist_account!(caller); assert_ok!(Referenda::::submit( RawOrigin::Signed(caller.clone()).into(), - RawOrigin::Root.into(), + Box::new(RawOrigin::Root.into()), T::Hashing::hash_of(&0), DispatchTime::After(0u32.into()) )); @@ -177,7 +177,7 @@ benchmarks! { whitelist_account!(caller); }: _( RawOrigin::Signed(caller), - RawOrigin::Root.into(), + Box::new(RawOrigin::Root.into()), T::Hashing::hash_of(&0), DispatchTime::After(0u32.into()) ) verify { diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index f21a78bcb7863..ab8c3ad0efa6a 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -355,7 +355,7 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::submit())] pub fn submit( origin: OriginFor, - proposal_origin: PalletsOriginOf, + proposal_origin: Box>, proposal_hash: T::Hash, enactment_moment: DispatchTime, ) -> DispatchResult { @@ -373,7 +373,7 @@ pub mod pallet { let nudge_call = Call::nudge_referendum { index }; let status = ReferendumStatus { track, - origin: proposal_origin, + origin: *proposal_origin, proposal_hash, enactment: enactment_moment, submitted: now, diff --git a/frame/referenda/src/mock.rs b/frame/referenda/src/mock.rs index 8b06fb3205e6f..1a24911603990 100644 --- a/frame/referenda/src/mock.rs +++ b/frame/referenda/src/mock.rs @@ -310,7 +310,7 @@ pub fn set_balance_proposal_hash(value: u64) -> H256 { pub fn propose_set_balance(who: u64, value: u64, delay: u64) -> DispatchResult { Referenda::submit( Origin::signed(who), - frame_system::RawOrigin::Root.into(), + Box::new(frame_system::RawOrigin::Root.into()), set_balance_proposal_hash(value), DispatchTime::After(delay), ) @@ -438,7 +438,7 @@ impl RefState { pub fn create(self) -> ReferendumIndex { assert_ok!(Referenda::submit( Origin::signed(1), - frame_support::dispatch::RawOrigin::Root.into(), + Box::new(frame_support::dispatch::RawOrigin::Root.into()), set_balance_proposal_hash(1), DispatchTime::At(10), )); diff --git a/frame/referenda/src/tests.rs b/frame/referenda/src/tests.rs index db55fdb8c40cf..d5435daf185bd 100644 --- a/frame/referenda/src/tests.rs +++ b/frame/referenda/src/tests.rs @@ -43,7 +43,7 @@ fn basic_happy_path_works() { // #1: submit assert_ok!(Referenda::submit( Origin::signed(1), - RawOrigin::Root.into(), + Box::new(RawOrigin::Root.into()), set_balance_proposal_hash(1), DispatchTime::At(10), )); @@ -174,7 +174,7 @@ fn queueing_works() { // Submit a proposal into a track with a queue len of 1. assert_ok!(Referenda::submit( Origin::signed(5), - RawOrigin::Root.into(), + Box::new(RawOrigin::Root.into()), set_balance_proposal_hash(0), DispatchTime::After(0), )); @@ -186,7 +186,7 @@ fn queueing_works() { for i in 1..=4 { assert_ok!(Referenda::submit( Origin::signed(i), - RawOrigin::Root.into(), + Box::new(RawOrigin::Root.into()), set_balance_proposal_hash(i), DispatchTime::After(0), )); @@ -271,7 +271,7 @@ fn auto_timeout_should_happen_with_nothing_but_submit() { // #1: submit assert_ok!(Referenda::submit( Origin::signed(1), - RawOrigin::Root.into(), + Box::new(RawOrigin::Root.into()), set_balance_proposal_hash(1), DispatchTime::At(20), )); @@ -291,13 +291,13 @@ fn tracks_are_distinguished() { new_test_ext().execute_with(|| { assert_ok!(Referenda::submit( Origin::signed(1), - RawOrigin::Root.into(), + Box::new(RawOrigin::Root.into()), set_balance_proposal_hash(1), DispatchTime::At(10), )); assert_ok!(Referenda::submit( Origin::signed(2), - RawOrigin::None.into(), + Box::new(RawOrigin::None.into()), set_balance_proposal_hash(2), DispatchTime::At(20), )); @@ -355,7 +355,7 @@ fn submit_errors_work() { assert_noop!( Referenda::submit( Origin::signed(1), - RawOrigin::Signed(2).into(), + Box::new(RawOrigin::Signed(2).into()), h, DispatchTime::At(10), ), @@ -364,7 +364,12 @@ fn submit_errors_work() { // No funds for deposit assert_noop!( - Referenda::submit(Origin::signed(10), RawOrigin::Root.into(), h, DispatchTime::At(10),), + Referenda::submit( + Origin::signed(10), + Box::new(RawOrigin::Root.into()), + h, + DispatchTime::At(10), + ), BalancesError::::InsufficientBalance ); }); @@ -379,7 +384,7 @@ fn decision_deposit_errors_work() { let h = set_balance_proposal_hash(1); assert_ok!(Referenda::submit( Origin::signed(1), - RawOrigin::Root.into(), + Box::new(RawOrigin::Root.into()), h, DispatchTime::At(10), )); @@ -401,7 +406,7 @@ fn refund_deposit_works() { let h = set_balance_proposal_hash(1); assert_ok!(Referenda::submit( Origin::signed(1), - RawOrigin::Root.into(), + Box::new(RawOrigin::Root.into()), h, DispatchTime::At(10), )); @@ -423,7 +428,7 @@ fn cancel_works() { let h = set_balance_proposal_hash(1); assert_ok!(Referenda::submit( Origin::signed(1), - RawOrigin::Root.into(), + Box::new(RawOrigin::Root.into()), h, DispatchTime::At(10), )); @@ -442,7 +447,7 @@ fn cancel_errors_works() { let h = set_balance_proposal_hash(1); assert_ok!(Referenda::submit( Origin::signed(1), - RawOrigin::Root.into(), + Box::new(RawOrigin::Root.into()), h, DispatchTime::At(10), )); @@ -460,7 +465,7 @@ fn kill_works() { let h = set_balance_proposal_hash(1); assert_ok!(Referenda::submit( Origin::signed(1), - RawOrigin::Root.into(), + Box::new(RawOrigin::Root.into()), h, DispatchTime::At(10), )); @@ -480,7 +485,7 @@ fn kill_errors_works() { let h = set_balance_proposal_hash(1); assert_ok!(Referenda::submit( Origin::signed(1), - RawOrigin::Root.into(), + Box::new(RawOrigin::Root.into()), h, DispatchTime::At(10), )); diff --git a/frame/support/procedural/src/construct_runtime/expand/call.rs b/frame/support/procedural/src/construct_runtime/expand/call.rs index ad52ca48f68c9..c8c5d5ff0ee43 100644 --- a/frame/support/procedural/src/construct_runtime/expand/call.rs +++ b/frame/support/procedural/src/construct_runtime/expand/call.rs @@ -62,6 +62,42 @@ pub fn expand_outer_dispatch( pub enum Call { #variant_defs } + #[cfg(test)] + impl Call { + /// Return a list of the module names together with their size in memory. + pub const fn sizes() -> &'static [( &'static str, usize )] { + use #scrate::dispatch::Callable; + use core::mem::size_of; + &[#( + ( + stringify!(#pallet_names), + size_of::< <#pallet_names as Callable<#runtime>>::Call >(), + ), + )*] + } + + /// Panics with diagnostic information if the size is greater than the given `limit`. + pub fn assert_size_under(limit: usize) { + let size = core::mem::size_of::(); + let call_oversize = size > limit; + if call_oversize { + println!("Size of `Call` is {} bytes (provided limit is {} bytes)", size, limit); + let mut sizes = Self::sizes().to_vec(); + sizes.sort_by_key(|x| -(x.1 as isize)); + for (i, &(name, size)) in sizes.iter().enumerate().take(5) { + println!("Offender #{}: {} at {} bytes", i + 1, name, size); + } + if let Some((_, next_size)) = sizes.get(5) { + println!("{} others of size {} bytes or less", sizes.len() - 5, next_size); + } + panic!( + "Size of `Call` is more than limit; use `Box` on complex parameter types to reduce the + size of `Call`. + If the limit is too strong, maybe consider providing a higher limit." + ); + } + } + } impl #scrate::dispatch::GetDispatchInfo for Call { fn get_dispatch_info(&self) -> #scrate::dispatch::DispatchInfo { match self { From f6e25d9181e3d341c052713542825d55aadbb358 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulo=20Silva=20de=20Souza?= <77391175+joao-paulo-parity@users.noreply.github.com> Date: Thu, 2 Jun 2022 11:15:32 -0300 Subject: [PATCH 303/484] reactivate check-dependent-cumulus (#11506) * re-enable check-dependent-cumulus * temporary: use handle-extra-dependencies * temporary: trim CI * CI: include build stage * CI: include test stage * CI: include test stage * Revert "temporary: trim CI" This reverts commit dcf4ae8d842bc445a065c7ccdc3b6a603034faa4. * CI: fix weird revert * Revert "temporary: use handle-extra-dependencies" This reverts commit bc0dc0f21f10284a23f66fdd8509ca6df89f2586. * CI undebug Co-authored-by: TriplEight --- scripts/ci/gitlab/pipeline/build.yml | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/scripts/ci/gitlab/pipeline/build.yml b/scripts/ci/gitlab/pipeline/build.yml index e6269c4e3949a..da6391fb7c278 100644 --- a/scripts/ci/gitlab/pipeline/build.yml +++ b/scripts/ci/gitlab/pipeline/build.yml @@ -11,7 +11,7 @@ script: - git clone --depth=1 - "--branch=$PIPELINE_SCRIPTS_TAG" + --branch="$PIPELINE_SCRIPTS_TAG" https://github.com/paritytech/pipeline-scripts - ./pipeline-scripts/check_dependent_project.sh --org paritytech @@ -34,15 +34,14 @@ check-dependent-polkadot: rules: - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ #PRs -# TODO: Re-enable after https://github.com/paritytech/pipeline-scripts/issues/44 -#check-dependent-cumulus: -# extends: .check-dependent-project -# variables: -# DEPENDENT_REPO: cumulus -# EXTRA_DEPENDENCIES: polkadot -# COMPANION_OVERRIDES: | -# substrate: polkadot-v* -# polkadot: release-v* +check-dependent-cumulus: + extends: .check-dependent-project + variables: + DEPENDENT_REPO: cumulus + EXTRA_DEPENDENCIES: polkadot + COMPANION_OVERRIDES: | + substrate: polkadot-v* + polkadot: release-v* build-linux-substrate: stage: build From 2f96cabd933a22f4644c9702fd91dd2cc491da62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Thu, 2 Jun 2022 18:48:48 +0200 Subject: [PATCH 304/484] wasm-builder: Rerun the build if the generated file changed (#11582) --- utils/wasm-builder/src/wasm_project.rs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/utils/wasm-builder/src/wasm_project.rs b/utils/wasm-builder/src/wasm_project.rs index 005b428a3ccac..4316f9041f11d 100644 --- a/utils/wasm-builder/src/wasm_project.rs +++ b/utils/wasm-builder/src/wasm_project.rs @@ -51,6 +51,11 @@ impl WasmBinaryBloaty { pub fn wasm_binary_bloaty_path_escaped(&self) -> String { self.0.display().to_string().escape_default().to_string() } + + /// Returns the path to the wasm binary. + pub fn wasm_binary_bloaty_path(&self) -> &Path { + &self.0 + } } /// Holds the path to the WASM binary. @@ -137,9 +142,17 @@ pub(crate) fn create_and_compile( copy_wasm_to_target_directory(project_cargo_toml, wasm_binary_compressed) }); - generate_rerun_if_changed_instructions(project_cargo_toml, &project, &wasm_workspace); + let final_wasm_binary = wasm_binary_compressed.or(wasm_binary); - (wasm_binary_compressed.or(wasm_binary), bloaty) + generate_rerun_if_changed_instructions( + project_cargo_toml, + &project, + &wasm_workspace, + final_wasm_binary.as_ref(), + &bloaty, + ); + + (final_wasm_binary, bloaty) } /// Find the `Cargo.lock` relative to the `OUT_DIR` environment variable. @@ -712,6 +725,8 @@ fn generate_rerun_if_changed_instructions( cargo_manifest: &Path, project_folder: &Path, wasm_workspace: &Path, + compressed_or_compact_wasm: Option<&WasmBinary>, + bloaty_wasm: &WasmBinaryBloaty, ) { // Rerun `build.rs` if the `Cargo.lock` changes if let Some(cargo_lock) = find_cargo_lock(cargo_manifest) { @@ -761,6 +776,9 @@ fn generate_rerun_if_changed_instructions( // Make sure that if any file/folder of a dependency change, we need to rerun the `build.rs` packages.iter().for_each(package_rerun_if_changed); + compressed_or_compact_wasm.map(|w| rerun_if_changed(w.wasm_binary_path())); + rerun_if_changed(bloaty_wasm.wasm_binary_bloaty_path()); + // Register our env variables println!("cargo:rerun-if-env-changed={}", crate::SKIP_BUILD_ENV); println!("cargo:rerun-if-env-changed={}", crate::WASM_BUILD_TYPE_ENV); From 567f9c999d5d94fa45007006cdefcd4502fc4508 Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Thu, 2 Jun 2022 21:18:59 +0200 Subject: [PATCH 305/484] [ci] use cargo nextest instead cargo test in test-linux-stable (#11576) * [DO NOT MERGE] Experimenting with nextest * enable jobs * enable stages * add comment * create test-frame-support job --- scripts/ci/gitlab/pipeline/test.yml | 48 ++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/scripts/ci/gitlab/pipeline/test.yml b/scripts/ci/gitlab/pipeline/test.yml index 8ec16ab6839a5..b2348c48355d0 100644 --- a/scripts/ci/gitlab/pipeline/test.yml +++ b/scripts/ci/gitlab/pipeline/test.yml @@ -214,12 +214,58 @@ test-linux-stable: RUN_UI_TESTS: 1 script: - rusty-cachier snapshot create + # TODO: add to paritytech/ci-linux image + - time cargo install cargo-nextest # this job runs all tests in former runtime-benchmarks, frame-staking and wasmtime tests - - time cargo test --workspace --locked --release --verbose --features runtime-benchmarks --manifest-path ./bin/node/cli/Cargo.toml + # node-cli is excluded until https://github.com/paritytech/substrate/issues/11321 fixed + - time cargo nextest run --workspace --locked --release --verbose --features runtime-benchmarks --manifest-path ./bin/node/cli/Cargo.toml --exclude node-cli + - rusty-cachier cache upload + +test-frame-support: + stage: test + extends: + - .docker-env + - .test-refs + variables: + # Enable debug assertions since we are running optimized builds for testing + # but still want to have debug assertions. + RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" + RUST_BACKTRACE: 1 + WASM_BUILD_NO_COLOR: 1 + WASM_BUILD_RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" + # Ensure we run the UI tests. + RUN_UI_TESTS: 1 + script: + - rusty-cachier snapshot create - time cargo test -p frame-support-test --features=conditional-storage,no-metadata-docs --manifest-path ./frame/support/test/Cargo.toml --test pallet # does not reuse cache 1 min 44 sec - SUBSTRATE_TEST_TIMEOUT=1 time cargo test -p substrate-test-utils --release --verbose --locked -- --ignored timeout - rusty-cachier cache upload +# This job runs tests that don't work with cargo-nextest in test-linux-stable +test-linux-stable-extra: + stage: test + extends: + - .docker-env + - .test-refs + variables: + # Enable debug assertions since we are running optimized builds for testing + # but still want to have debug assertions. + RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" + RUST_BACKTRACE: 1 + WASM_BUILD_NO_COLOR: 1 + WASM_BUILD_RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" + # Ensure we run the UI tests. + RUN_UI_TESTS: 1 + script: + - rusty-cachier snapshot create + # Run node-cli tests + # TODO: add to test-linux-stable-nextest after fix https://github.com/paritytech/substrate/issues/11321 + - time cargo test node-cli --workspace --locked --release --verbose --features runtime-benchmarks --manifest-path ./bin/node/cli/Cargo.toml + # Run doctests + # TODO: add to test-linux-stable-nextest after fix https://github.com/nextest-rs/nextest/issues/16 + - time cargo test --doc --workspace --locked --release --verbose --features runtime-benchmarks --manifest-path ./bin/node/cli/Cargo.toml + - rusty-cachier cache upload + test-frame-examples-compile-to-wasm: # into one job stage: test From cafd284a853f2338143cfc9b4cd54b5e197bd45e Mon Sep 17 00:00:00 2001 From: Liu-Cheng Xu Date: Fri, 3 Jun 2022 20:33:59 +0800 Subject: [PATCH 306/484] Expose ValidatedTransaction from transaction pool (#11588) This is required to make a tx pool wrapper for some custom transaction validation, specificially required to make a wrapper around the `LocalTransactionPool` implementation (https://github.com/subspace/subspace/blob/f54881a9b5/crates/subspace-service/src/pool.rs#L232-L269). Related: https://github.com/paritytech/substrate/discussions/11520 --- client/transaction-pool/src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/transaction-pool/src/lib.rs b/client/transaction-pool/src/lib.rs index eb36d46538c1a..7b9ce9d6047c0 100644 --- a/client/transaction-pool/src/lib.rs +++ b/client/transaction-pool/src/lib.rs @@ -36,7 +36,9 @@ use futures::{ future::{self, ready}, prelude::*, }; -pub use graph::{base_pool::Limit as PoolLimit, ChainApi, Options, Pool, Transaction}; +pub use graph::{ + base_pool::Limit as PoolLimit, ChainApi, Options, Pool, Transaction, ValidatedTransaction, +}; use parking_lot::Mutex; use std::{ collections::{HashMap, HashSet}, @@ -407,7 +409,6 @@ where at: &BlockId, xt: sc_transaction_pool_api::LocalTransactionFor, ) -> Result { - use graph::ValidatedTransaction; use sp_runtime::{ traits::SaturatedConversion, transaction_validity::TransactionValidityError, }; From 53c3f02d52240e48818909cd1c7840a491cb9886 Mon Sep 17 00:00:00 2001 From: Sergej Sakac <73715684+Szegoo@users.noreply.github.com> Date: Fri, 3 Jun 2022 16:25:55 +0200 Subject: [PATCH 307/484] Tracable defensive errors (#11532) * Tracable defensive errors * small fixes * fix * refactored * switched to defensive_ok_or * Remove unnecessary type annotations and conversions * cargo fmt * Fixes Co-authored-by: Keith Yeung --- frame/nomination-pools/src/lib.rs | 36 ++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index fd8ae8ab76b08..dc40a6a7d41fd 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1329,13 +1329,31 @@ pub mod pallet { MetadataExceedsMaxLen, /// Some error occurred that should never happen. This should be reported to the /// maintainers. - DefensiveError, + Defensive(DefensiveError), /// Not enough points. Ty unbonding less. NotEnoughPointsToUnbond, /// Partial unbonding now allowed permissionlessly. PartialUnbondNotAllowedPermissionlessly, } + #[derive(Encode, Decode, PartialEq, TypeInfo, frame_support::PalletError)] + pub enum DefensiveError { + /// There isn't enough space in the unbond pool. + NotEnoughSpaceInUnbondPool, + /// A (bonded) pool id does not exist. + PoolNotFound, + /// A reward pool does not exist. In all cases this is a system logic error. + RewardPoolNotFound, + /// A sub pool does not exist. + SubPoolsNotFound, + } + + impl From for Error { + fn from(e: DefensiveError) -> Error { + Error::::Defensive(e) + } + } + #[pallet::call] impl Pallet { /// Stake funds with a pool. The amount to bond is transferred from the member to the @@ -1366,7 +1384,7 @@ pub mod pallet { // We just need its total earnings at this point in time, but we don't need to write it // because we are not adjusting its points (all other values can calculated virtual). let reward_pool = RewardPool::::get_and_update(pool_id) - .defensive_ok_or_else(|| Error::::RewardPoolNotFound)?; + .defensive_ok_or::>(DefensiveError::RewardPoolNotFound.into())?; bonded_pool.try_inc_members()?; let points_issued = bonded_pool.try_bond_funds(&who, amount, BondType::Later)?; @@ -1530,14 +1548,16 @@ pub mod pallet { .try_insert(unbond_era, UnbondPool::default()) // The above call to `maybe_merge_pools` should ensure there is // always enough space to insert. - .defensive_map_err(|_| Error::::DefensiveError)?; + .defensive_map_err::, _>(|_| { + DefensiveError::NotEnoughSpaceInUnbondPool.into() + })?; } sub_pools .with_era .get_mut(&unbond_era) // The above check ensures the pool exists. - .defensive_ok_or_else(|| Error::::DefensiveError)? + .defensive_ok_or::>(DefensiveError::PoolNotFound.into())? .issue(unbonding_balance); Self::deposit_event(Event::::Unbonded { @@ -1607,9 +1627,9 @@ pub mod pallet { let current_era = T::StakingInterface::current_era(); let bonded_pool = BondedPool::::get(member.pool_id) - .defensive_ok_or_else(|| Error::::PoolNotFound)?; + .defensive_ok_or::>(DefensiveError::PoolNotFound.into())?; let mut sub_pools = SubPoolsStorage::::get(member.pool_id) - .defensive_ok_or_else(|| Error::::SubPoolsNotFound)?; + .defensive_ok_or::>(DefensiveError::SubPoolsNotFound.into())?; bonded_pool.ok_to_withdraw_unbonded_with( &caller, @@ -2027,9 +2047,9 @@ impl Pallet { ) -> Result<(PoolMember, BondedPool, RewardPool), Error> { let member = PoolMembers::::get(&who).ok_or(Error::::PoolMemberNotFound)?; let bonded_pool = - BondedPool::::get(member.pool_id).defensive_ok_or(Error::::PoolNotFound)?; + BondedPool::::get(member.pool_id).defensive_ok_or(DefensiveError::PoolNotFound)?; let reward_pool = - RewardPools::::get(member.pool_id).defensive_ok_or(Error::::PoolNotFound)?; + RewardPools::::get(member.pool_id).defensive_ok_or(DefensiveError::PoolNotFound)?; Ok((member, bonded_pool, reward_pool)) } From f02c0ba304fcdf1f117b5da6becc2d2bbaa76916 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Fri, 3 Jun 2022 18:30:12 +0100 Subject: [PATCH 308/484] Remove `#[pallet::without_storage_info]` for pallet-remark (#11590) --- frame/remark/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/frame/remark/src/lib.rs b/frame/remark/src/lib.rs index 6803b2a60085f..c69f95907019f 100644 --- a/frame/remark/src/lib.rs +++ b/frame/remark/src/lib.rs @@ -57,7 +57,6 @@ pub mod pallet { } #[pallet::pallet] - #[pallet::without_storage_info] pub struct Pallet(_); #[pallet::call] From 70d3271ac306173ab8b9bfeba15db8c868c6c48f Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Fri, 3 Jun 2022 21:46:14 +0200 Subject: [PATCH 309/484] Add host info to weight templates (#11583) * Add host info to weight templates Signed-off-by: Oliver Tale-Yazdi * cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=frame_system --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/system/src/weights.rs --template=./.maintain/frame-weight-template.hbs Co-authored-by: Parity Bot --- .maintain/frame-weight-template.hbs | 1 + Cargo.lock | 11 +++++ frame/system/src/weights.rs | 24 ++++++----- utils/frame/benchmarking-cli/Cargo.toml | 1 + .../benchmarking-cli/src/overhead/cmd.rs | 6 ++- .../benchmarking-cli/src/overhead/template.rs | 6 +++ .../benchmarking-cli/src/overhead/weights.hbs | 1 + .../frame/benchmarking-cli/src/pallet/mod.rs | 5 +++ .../benchmarking-cli/src/pallet/template.hbs | 1 + .../benchmarking-cli/src/pallet/writer.rs | 4 ++ .../frame/benchmarking-cli/src/shared/mod.rs | 43 +++++++++++++++++++ .../frame/benchmarking-cli/src/storage/cmd.rs | 6 ++- .../benchmarking-cli/src/storage/template.rs | 6 +++ .../benchmarking-cli/src/storage/weights.hbs | 1 + 14 files changed, 103 insertions(+), 13 deletions(-) diff --git a/.maintain/frame-weight-template.hbs b/.maintain/frame-weight-template.hbs index 34852daf7d47b..73608b372eb63 100644 --- a/.maintain/frame-weight-template.hbs +++ b/.maintain/frame-weight-template.hbs @@ -19,6 +19,7 @@ //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION {{version}} //! DATE: {{date}}, STEPS: `{{cmd.steps}}`, REPEAT: {{cmd.repeat}}, LOW RANGE: `{{cmd.lowest_range_values}}`, HIGH RANGE: `{{cmd.highest_range_values}}` +//! HOSTNAME: `{{hostname}}`, CPU: `{{cpuname}}` //! EXECUTION: {{cmd.execution}}, WASM-EXECUTION: {{cmd.wasm_execution}}, CHAIN: {{cmd.chain}}, DB CACHE: {{cmd.db_cache}} // Executed Command: diff --git a/Cargo.lock b/Cargo.lock index 17d2e82596526..42accdb455cef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2112,6 +2112,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", + "gethostname", "handlebars", "hash-db", "hex", @@ -2596,6 +2597,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "gethostname" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "getrandom" version = "0.1.16" diff --git a/frame/system/src/weights.rs b/frame/system/src/weights.rs index bdd2d64b2118b..a9126e4bf5bf4 100644 --- a/frame/system/src/weights.rs +++ b/frame/system/src/weights.rs @@ -18,11 +18,12 @@ //! Autogenerated weights for frame_system //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-06-02, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `ci3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// ./target/production/substrate +// target/production/substrate // benchmark // pallet // --chain=dev @@ -32,8 +33,9 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --template=./.maintain/frame-weight-template.hbs +// --heap-pages=4096 // --output=./frame/system/src/weights.rs +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -66,7 +68,7 @@ impl WeightInfo for SubstrateWeight { // Storage: System Digest (r:1 w:1) // Storage: unknown [0x3a686561707061676573] (r:0 w:1) fn set_heap_pages() -> Weight { - (5_453_000 as Weight) + (5_367_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -74,21 +76,21 @@ impl WeightInfo for SubstrateWeight { fn set_storage(i: u32, ) -> Weight { (0 as Weight) // Standard Error: 1_000 - .saturating_add((600_000 as Weight).saturating_mul(i as Weight)) + .saturating_add((603_000 as Weight).saturating_mul(i as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(i as Weight))) } // Storage: Skipped Metadata (r:0 w:0) fn kill_storage(i: u32, ) -> Weight { (0 as Weight) // Standard Error: 1_000 - .saturating_add((510_000 as Weight).saturating_mul(i as Weight)) + .saturating_add((513_000 as Weight).saturating_mul(i as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(i as Weight))) } // Storage: Skipped Metadata (r:0 w:0) fn kill_prefix(p: u32, ) -> Weight { (0 as Weight) // Standard Error: 1_000 - .saturating_add((916_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((1_026_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(p as Weight))) } } @@ -106,7 +108,7 @@ impl WeightInfo for () { // Storage: System Digest (r:1 w:1) // Storage: unknown [0x3a686561707061676573] (r:0 w:1) fn set_heap_pages() -> Weight { - (5_453_000 as Weight) + (5_367_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -114,21 +116,21 @@ impl WeightInfo for () { fn set_storage(i: u32, ) -> Weight { (0 as Weight) // Standard Error: 1_000 - .saturating_add((600_000 as Weight).saturating_mul(i as Weight)) + .saturating_add((603_000 as Weight).saturating_mul(i as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(i as Weight))) } // Storage: Skipped Metadata (r:0 w:0) fn kill_storage(i: u32, ) -> Weight { (0 as Weight) // Standard Error: 1_000 - .saturating_add((510_000 as Weight).saturating_mul(i as Weight)) + .saturating_add((513_000 as Weight).saturating_mul(i as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(i as Weight))) } // Storage: Skipped Metadata (r:0 w:0) fn kill_prefix(p: u32, ) -> Weight { (0 as Weight) // Standard Error: 1_000 - .saturating_add((916_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((1_026_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(p as Weight))) } } diff --git a/utils/frame/benchmarking-cli/Cargo.toml b/utils/frame/benchmarking-cli/Cargo.toml index 5f30e21b2986a..c4549f5c5439d 100644 --- a/utils/frame/benchmarking-cli/Cargo.toml +++ b/utils/frame/benchmarking-cli/Cargo.toml @@ -56,6 +56,7 @@ sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } sp-state-machine = { version = "0.12.0", path = "../../../primitives/state-machine" } sp-storage = { version = "6.0.0", path = "../../../primitives/storage" } sp-trie = { version = "6.0.0", path = "../../../primitives/trie" } +gethostname = "0.2.3" [features] default = ["db", "sc-client-db/runtime-benchmarks"] diff --git a/utils/frame/benchmarking-cli/src/overhead/cmd.rs b/utils/frame/benchmarking-cli/src/overhead/cmd.rs index 3cf281986861f..357c89d97a5ac 100644 --- a/utils/frame/benchmarking-cli/src/overhead/cmd.rs +++ b/utils/frame/benchmarking-cli/src/overhead/cmd.rs @@ -35,7 +35,7 @@ use crate::{ bench::{Benchmark, BenchmarkParams, BenchmarkType}, template::TemplateData, }, - shared::WeightParams, + shared::{HostInfoParams, WeightParams}, }; /// Benchmark the execution overhead per-block and per-extrinsic. @@ -64,6 +64,10 @@ pub struct OverheadParams { #[allow(missing_docs)] #[clap(flatten)] pub bench: BenchmarkParams, + + #[allow(missing_docs)] + #[clap(flatten)] + pub hostinfo: HostInfoParams, } /// Used by the benchmark to build signed extrinsics. diff --git a/utils/frame/benchmarking-cli/src/overhead/template.rs b/utils/frame/benchmarking-cli/src/overhead/template.rs index 44e2c1f02e30f..33c2c7999039a 100644 --- a/utils/frame/benchmarking-cli/src/overhead/template.rs +++ b/utils/frame/benchmarking-cli/src/overhead/template.rs @@ -47,6 +47,10 @@ pub(crate) struct TemplateData { version: String, /// Date that the template was filled out. date: String, + /// Hostname of the machine that executed the benchmarks. + hostname: String, + /// CPU name of the machine that executed the benchmarks. + cpuname: String, /// Command line arguments that were passed to the CLI. args: Vec, /// Params of the executed command. @@ -73,6 +77,8 @@ impl TemplateData { runtime_name: cfg.chain_spec.name().into(), version: VERSION.into(), date: chrono::Utc::now().format("%Y-%m-%d (Y/M/D)").to_string(), + hostname: params.hostinfo.hostname(), + cpuname: params.hostinfo.cpuname(), args: env::args().collect::>(), params: params.clone(), stats: stats.clone(), diff --git a/utils/frame/benchmarking-cli/src/overhead/weights.hbs b/utils/frame/benchmarking-cli/src/overhead/weights.hbs index 6d3ae471d1cf2..f8312b0052592 100644 --- a/utils/frame/benchmarking-cli/src/overhead/weights.hbs +++ b/utils/frame/benchmarking-cli/src/overhead/weights.hbs @@ -17,6 +17,7 @@ //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION {{version}} //! DATE: {{date}} +//! HOSTNAME: `{{hostname}}`, CPU: `{{cpuname}}` //! //! SHORT-NAME: `{{short_name}}`, LONG-NAME: `{{long_name}}`, RUNTIME: `{{runtime_name}}` //! WARMUPS: `{{params.bench.warmup}}`, REPEAT: `{{params.bench.repeat}}` diff --git a/utils/frame/benchmarking-cli/src/pallet/mod.rs b/utils/frame/benchmarking-cli/src/pallet/mod.rs index 227c9b2f8a7b6..7beaf321a2927 100644 --- a/utils/frame/benchmarking-cli/src/pallet/mod.rs +++ b/utils/frame/benchmarking-cli/src/pallet/mod.rs @@ -18,6 +18,7 @@ mod command; mod writer; +use crate::shared::HostInfoParams; use sc_cli::{ ExecutionStrategy, WasmExecutionMethod, WasmtimeInstantiationStrategy, DEFAULT_WASMTIME_INSTANTIATION_STRATEGY, DEFAULT_WASM_EXECUTION_METHOD, @@ -91,6 +92,10 @@ pub struct PalletCmd { #[clap(long)] pub template: Option, + #[allow(missing_docs)] + #[clap(flatten)] + pub hostinfo_params: HostInfoParams, + /// Which analysis function to use when outputting benchmarks: /// * min-squares (default) /// * median-slopes diff --git a/utils/frame/benchmarking-cli/src/pallet/template.hbs b/utils/frame/benchmarking-cli/src/pallet/template.hbs index ea734e165919a..a2f75c7b0a35b 100644 --- a/utils/frame/benchmarking-cli/src/pallet/template.hbs +++ b/utils/frame/benchmarking-cli/src/pallet/template.hbs @@ -3,6 +3,7 @@ //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION {{version}} //! DATE: {{date}}, STEPS: `{{cmd.steps}}`, REPEAT: {{cmd.repeat}}, LOW RANGE: `{{cmd.lowest_range_values}}`, HIGH RANGE: `{{cmd.highest_range_values}}` +//! HOSTNAME: `{{hostname}}`, CPU: `{{cpuname}}` //! EXECUTION: {{cmd.execution}}, WASM-EXECUTION: {{cmd.wasm_execution}}, CHAIN: {{cmd.chain}}, DB CACHE: {{cmd.db_cache}} // Executed Command: diff --git a/utils/frame/benchmarking-cli/src/pallet/writer.rs b/utils/frame/benchmarking-cli/src/pallet/writer.rs index 515582d8c6db9..dc38fbcfc1756 100644 --- a/utils/frame/benchmarking-cli/src/pallet/writer.rs +++ b/utils/frame/benchmarking-cli/src/pallet/writer.rs @@ -43,6 +43,8 @@ const TEMPLATE: &str = include_str!("./template.hbs"); struct TemplateData { args: Vec, date: String, + hostname: String, + cpuname: String, version: String, pallet: String, instance: String, @@ -322,6 +324,8 @@ pub fn write_results( let hbs_data = TemplateData { args: args.clone(), date: date.clone(), + hostname: cmd.hostinfo_params.hostname(), + cpuname: cmd.hostinfo_params.cpuname(), version: VERSION.to_string(), pallet: pallet.to_string(), instance: instance.to_string(), diff --git a/utils/frame/benchmarking-cli/src/shared/mod.rs b/utils/frame/benchmarking-cli/src/shared/mod.rs index f959c285a346e..33189792c4008 100644 --- a/utils/frame/benchmarking-cli/src/shared/mod.rs +++ b/utils/frame/benchmarking-cli/src/shared/mod.rs @@ -25,7 +25,10 @@ pub use record::BenchRecord; pub use stats::{StatSelect, Stats}; pub use weight_params::WeightParams; +use clap::Args; use rand::prelude::*; +use sc_sysinfo::gather_sysinfo; +use serde::Serialize; /// A Handlebars helper to add an underscore after every 3rd character, /// i.e. a separator for large numbers. @@ -89,3 +92,43 @@ pub fn check_build_profile() -> Result<(), String> { Ok(()) } } + +/// Parameters to configure how the host info will be determined. +#[derive(Debug, Default, Serialize, Clone, PartialEq, Args)] +#[clap(rename_all = "kebab-case")] +pub struct HostInfoParams { + /// Manually override the hostname to use. + #[clap(long)] + pub hostname_override: Option, + + /// Specify a fallback hostname if no-one could be detected automatically. + /// + /// Note: This only exists to make the `hostname` function infallible. + #[clap(long, default_value = "")] + pub hostname_fallback: String, + + /// Specify a fallback CPU name if no-one could be detected automatically. + /// + /// Note: This only exists to make the `cpuname` function infallible. + #[clap(long, default_value = "")] + pub cpuname_fallback: String, +} + +impl HostInfoParams { + /// Returns the hostname of the machine. + /// + /// Can be used to track on which machine a benchmark was run. + pub fn hostname(&self) -> String { + self.hostname_override + .clone() + .or(gethostname::gethostname().into_string().ok()) + .unwrap_or(self.hostname_fallback.clone()) + } + + /// Returns the CPU name of the current machine. + /// + /// Can be used to track on which machine a benchmark was run. + pub fn cpuname(&self) -> String { + gather_sysinfo().cpu.unwrap_or(self.cpuname_fallback.clone()) + } +} diff --git a/utils/frame/benchmarking-cli/src/storage/cmd.rs b/utils/frame/benchmarking-cli/src/storage/cmd.rs index 23222dbd120ab..b8264dc009232 100644 --- a/utils/frame/benchmarking-cli/src/storage/cmd.rs +++ b/utils/frame/benchmarking-cli/src/storage/cmd.rs @@ -34,7 +34,7 @@ use sp_runtime::generic::BlockId; use std::{fmt::Debug, path::PathBuf, sync::Arc}; use super::template::TemplateData; -use crate::shared::{new_rng, WeightParams}; +use crate::shared::{new_rng, HostInfoParams, WeightParams}; /// Benchmark the storage speed of a chain snapshot. #[derive(Debug, Parser)] @@ -63,6 +63,10 @@ pub struct StorageParams { #[clap(flatten)] pub weight_params: WeightParams, + #[allow(missing_docs)] + #[clap(flatten)] + pub hostinfo: HostInfoParams, + /// Skip the `read` benchmark. #[clap(long)] pub skip_read: bool, diff --git a/utils/frame/benchmarking-cli/src/storage/template.rs b/utils/frame/benchmarking-cli/src/storage/template.rs index 8365c3841d422..20fbd58134f20 100644 --- a/utils/frame/benchmarking-cli/src/storage/template.rs +++ b/utils/frame/benchmarking-cli/src/storage/template.rs @@ -41,6 +41,10 @@ pub(crate) struct TemplateData { version: String, /// Date that the template was filled out. date: String, + /// Hostname of the machine that executed the benchmarks. + hostname: String, + /// CPU name of the machine that executed the benchmarks. + cpuname: String, /// Command line arguments that were passed to the CLI. args: Vec, /// Storage params of the executed command. @@ -65,6 +69,8 @@ impl TemplateData { runtime_name: cfg.chain_spec.name().into(), version: VERSION.into(), date: chrono::Utc::now().format("%Y-%m-%d (Y/M/D)").to_string(), + hostname: params.hostinfo.hostname(), + cpuname: params.hostinfo.cpuname(), args: env::args().collect::>(), params: params.clone(), ..Default::default() diff --git a/utils/frame/benchmarking-cli/src/storage/weights.hbs b/utils/frame/benchmarking-cli/src/storage/weights.hbs index 51878141d1449..8c19aaa0dff36 100644 --- a/utils/frame/benchmarking-cli/src/storage/weights.hbs +++ b/utils/frame/benchmarking-cli/src/storage/weights.hbs @@ -17,6 +17,7 @@ //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION {{version}} //! DATE: {{date}} +//! HOSTNAME: `{{hostname}}`, CPU: `{{cpuname}}` //! //! DATABASE: `{{db_name}}`, RUNTIME: `{{runtime_name}}` //! BLOCK-NUM: `{{block_number}}` From a7c38e056aef6e37d6965a248e04b20d9adaf836 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Fri, 3 Jun 2022 22:43:54 +0200 Subject: [PATCH 310/484] Expose Benchmarking Component Ranges (#11545) * Add component ranges to benchmarking Signed-off-by: Oliver Tale-Yazdi * Adding component ranges to templates Signed-off-by: Oliver Tale-Yazdi * Fix tests Signed-off-by: Oliver Tale-Yazdi * cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=frame_system --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/system/src/weights.rs --template=./.maintain/frame-weight-template.hbs * tweak script to reduce diff * cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_identity --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/identity/src/weights.rs --template=./.maintain/frame-weight-template.hbs Co-authored-by: Parity Bot Co-authored-by: Shawn Tabrizi --- .maintain/frame-weight-template.hbs | 6 + frame/identity/src/weights.rs | 263 +++++++++++------- frame/system/src/weights.rs | 14 +- scripts/run_all_benchmarks.sh | 5 +- .../benchmarking-cli/src/pallet/command.rs | 24 +- .../benchmarking-cli/src/pallet/template.hbs | 3 + .../benchmarking-cli/src/pallet/writer.rs | 20 +- 7 files changed, 220 insertions(+), 115 deletions(-) diff --git a/.maintain/frame-weight-template.hbs b/.maintain/frame-weight-template.hbs index 73608b372eb63..593da06f4a7c0 100644 --- a/.maintain/frame-weight-template.hbs +++ b/.maintain/frame-weight-template.hbs @@ -56,6 +56,9 @@ impl WeightInfo for SubstrateWeight { {{#each benchmark.comments as |comment|}} // {{comment}} {{/each}} + {{#each benchmark.component_ranges as |range|}} + /// The range of component `{{range.name}}` is `[{{range.min}}, {{range.max}}]`. + {{/each}} fn {{benchmark.name~}} ( {{~#each benchmark.components as |c| ~}} @@ -88,6 +91,9 @@ impl WeightInfo for () { {{#each benchmark.comments as |comment|}} // {{comment}} {{/each}} + {{#each benchmark.component_ranges as |range|}} + /// The range of component `{{range.name}}` is `[{{range.min}}, {{range.max}}]`. + {{/each}} fn {{benchmark.name~}} ( {{~#each benchmark.components as |c| ~}} diff --git a/frame/identity/src/weights.rs b/frame/identity/src/weights.rs index d59611e2e1e82..7d3371c31b03b 100644 --- a/frame/identity/src/weights.rs +++ b/frame/identity/src/weights.rs @@ -18,11 +18,11 @@ //! Autogenerated weights for pallet_identity //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-06-03, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// ./target/production/substrate +// target/production/substrate // benchmark // pallet // --chain=dev @@ -32,8 +32,9 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --template=./.maintain/frame-weight-template.hbs +// --heap-pages=4096 // --output=./frame/identity/src/weights.rs +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -66,30 +67,34 @@ pub trait WeightInfo { pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: Identity Registrars (r:1 w:1) + /// The range of component `r` is `[1, 19]`. fn add_registrar(r: u32, ) -> Weight { - (16_343_000 as Weight) + (16_649_000 as Weight) // Standard Error: 5_000 - .saturating_add((229_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((241_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Identity IdentityOf (r:1 w:1) + /// The range of component `r` is `[1, 20]`. + /// The range of component `x` is `[1, 100]`. fn set_identity(r: u32, x: u32, ) -> Weight { - (32_920_000 as Weight) - // Standard Error: 8_000 - .saturating_add((203_000 as Weight).saturating_mul(r as Weight)) + (31_322_000 as Weight) + // Standard Error: 10_000 + .saturating_add((252_000 as Weight).saturating_mul(r as Weight)) // Standard Error: 1_000 - .saturating_add((300_000 as Weight).saturating_mul(x as Weight)) + .saturating_add((312_000 as Weight).saturating_mul(x as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Identity IdentityOf (r:1 w:0) // Storage: Identity SubsOf (r:1 w:1) // Storage: Identity SuperOf (r:1 w:1) + /// The range of component `s` is `[1, 100]`. fn set_subs_new(s: u32, ) -> Weight { - (31_009_000 as Weight) - // Standard Error: 1_000 - .saturating_add((3_053_000 as Weight).saturating_mul(s as Weight)) + (30_012_000 as Weight) + // Standard Error: 2_000 + .saturating_add((3_005_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(s as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -98,10 +103,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Identity IdentityOf (r:1 w:0) // Storage: Identity SubsOf (r:1 w:1) // Storage: Identity SuperOf (r:0 w:1) + /// The range of component `p` is `[1, 100]`. fn set_subs_old(p: u32, ) -> Weight { - (29_712_000 as Weight) + (29_623_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_087_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((1_100_000 as Weight).saturating_mul(p as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(p as Weight))) @@ -109,71 +115,83 @@ impl WeightInfo for SubstrateWeight { // Storage: Identity SubsOf (r:1 w:1) // Storage: Identity IdentityOf (r:1 w:1) // Storage: Identity SuperOf (r:0 w:100) + /// The range of component `r` is `[1, 20]`. + /// The range of component `s` is `[1, 100]`. + /// The range of component `x` is `[1, 100]`. fn clear_identity(r: u32, s: u32, x: u32, ) -> Weight { - (33_943_000 as Weight) - // Standard Error: 7_000 - .saturating_add((193_000 as Weight).saturating_mul(r as Weight)) - // Standard Error: 0 - .saturating_add((1_101_000 as Weight).saturating_mul(s as Weight)) - // Standard Error: 0 - .saturating_add((194_000 as Weight).saturating_mul(x as Weight)) + (34_370_000 as Weight) + // Standard Error: 10_000 + .saturating_add((186_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 1_000 + .saturating_add((1_114_000 as Weight).saturating_mul(s as Weight)) + // Standard Error: 1_000 + .saturating_add((189_000 as Weight).saturating_mul(x as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) } // Storage: Identity Registrars (r:1 w:0) // Storage: Identity IdentityOf (r:1 w:1) + /// The range of component `r` is `[1, 20]`. + /// The range of component `x` is `[1, 100]`. fn request_judgement(r: u32, x: u32, ) -> Weight { - (34_861_000 as Weight) - // Standard Error: 3_000 - .saturating_add((249_000 as Weight).saturating_mul(r as Weight)) + (34_759_000 as Weight) + // Standard Error: 4_000 + .saturating_add((251_000 as Weight).saturating_mul(r as Weight)) // Standard Error: 0 - .saturating_add((344_000 as Weight).saturating_mul(x as Weight)) + .saturating_add((340_000 as Weight).saturating_mul(x as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Identity IdentityOf (r:1 w:1) + /// The range of component `r` is `[1, 20]`. + /// The range of component `x` is `[1, 100]`. fn cancel_request(r: u32, x: u32, ) -> Weight { - (32_906_000 as Weight) - // Standard Error: 3_000 - .saturating_add((147_000 as Weight).saturating_mul(r as Weight)) + (32_254_000 as Weight) + // Standard Error: 7_000 + .saturating_add((159_000 as Weight).saturating_mul(r as Weight)) // Standard Error: 0 - .saturating_add((341_000 as Weight).saturating_mul(x as Weight)) + .saturating_add((347_000 as Weight).saturating_mul(x as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Identity Registrars (r:1 w:1) + /// The range of component `r` is `[1, 19]`. fn set_fee(r: u32, ) -> Weight { - (7_591_000 as Weight) + (7_858_000 as Weight) // Standard Error: 3_000 - .saturating_add((201_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((190_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Identity Registrars (r:1 w:1) + /// The range of component `r` is `[1, 19]`. fn set_account_id(r: u32, ) -> Weight { - (7_919_000 as Weight) + (8_011_000 as Weight) // Standard Error: 3_000 - .saturating_add((183_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((187_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Identity Registrars (r:1 w:1) + /// The range of component `r` is `[1, 19]`. fn set_fields(r: u32, ) -> Weight { - (7_887_000 as Weight) - // Standard Error: 4_000 - .saturating_add((182_000 as Weight).saturating_mul(r as Weight)) + (7_970_000 as Weight) + // Standard Error: 3_000 + .saturating_add((175_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Identity Registrars (r:1 w:0) // Storage: Identity IdentityOf (r:1 w:1) + /// The range of component `r` is `[1, 19]`. + /// The range of component `x` is `[1, 100]`. fn provide_judgement(r: u32, x: u32, ) -> Weight { - (24_623_000 as Weight) - // Standard Error: 3_000 - .saturating_add((230_000 as Weight).saturating_mul(r as Weight)) + (24_730_000 as Weight) + // Standard Error: 4_000 + .saturating_add((196_000 as Weight).saturating_mul(r as Weight)) // Standard Error: 0 - .saturating_add((339_000 as Weight).saturating_mul(x as Weight)) + .saturating_add((341_000 as Weight).saturating_mul(x as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -181,12 +199,17 @@ impl WeightInfo for SubstrateWeight { // Storage: Identity IdentityOf (r:1 w:1) // Storage: System Account (r:1 w:1) // Storage: Identity SuperOf (r:0 w:100) - fn kill_identity(r: u32, s: u32, _x: u32, ) -> Weight { - (48_143_000 as Weight) - // Standard Error: 8_000 - .saturating_add((106_000 as Weight).saturating_mul(r as Weight)) - // Standard Error: 0 - .saturating_add((1_105_000 as Weight).saturating_mul(s as Weight)) + /// The range of component `r` is `[1, 20]`. + /// The range of component `s` is `[1, 100]`. + /// The range of component `x` is `[1, 100]`. + fn kill_identity(r: u32, s: u32, x: u32, ) -> Weight { + (44_988_000 as Weight) + // Standard Error: 10_000 + .saturating_add((201_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 1_000 + .saturating_add((1_126_000 as Weight).saturating_mul(s as Weight)) + // Standard Error: 1_000 + .saturating_add((2_000 as Weight).saturating_mul(x as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) @@ -194,38 +217,42 @@ impl WeightInfo for SubstrateWeight { // Storage: Identity IdentityOf (r:1 w:0) // Storage: Identity SuperOf (r:1 w:1) // Storage: Identity SubsOf (r:1 w:1) + /// The range of component `s` is `[1, 99]`. fn add_sub(s: u32, ) -> Weight { - (36_778_000 as Weight) + (36_768_000 as Weight) // Standard Error: 1_000 - .saturating_add((112_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((115_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Identity IdentityOf (r:1 w:0) // Storage: Identity SuperOf (r:1 w:1) + /// The range of component `s` is `[1, 100]`. fn rename_sub(s: u32, ) -> Weight { - (13_895_000 as Weight) + (13_474_000 as Weight) // Standard Error: 0 - .saturating_add((52_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((56_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Identity IdentityOf (r:1 w:0) // Storage: Identity SuperOf (r:1 w:1) // Storage: Identity SubsOf (r:1 w:1) + /// The range of component `s` is `[1, 100]`. fn remove_sub(s: u32, ) -> Weight { - (37_707_000 as Weight) + (37_720_000 as Weight) // Standard Error: 1_000 - .saturating_add((110_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((114_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Identity SuperOf (r:1 w:1) // Storage: Identity SubsOf (r:1 w:1) + /// The range of component `s` is `[1, 99]`. fn quit_sub(s: u32, ) -> Weight { - (26_935_000 as Weight) + (26_848_000 as Weight) // Standard Error: 1_000 - .saturating_add((106_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((115_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -234,30 +261,34 @@ impl WeightInfo for SubstrateWeight { // For backwards compatibility and tests impl WeightInfo for () { // Storage: Identity Registrars (r:1 w:1) + /// The range of component `r` is `[1, 19]`. fn add_registrar(r: u32, ) -> Weight { - (16_343_000 as Weight) + (16_649_000 as Weight) // Standard Error: 5_000 - .saturating_add((229_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((241_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Identity IdentityOf (r:1 w:1) + /// The range of component `r` is `[1, 20]`. + /// The range of component `x` is `[1, 100]`. fn set_identity(r: u32, x: u32, ) -> Weight { - (32_920_000 as Weight) - // Standard Error: 8_000 - .saturating_add((203_000 as Weight).saturating_mul(r as Weight)) + (31_322_000 as Weight) + // Standard Error: 10_000 + .saturating_add((252_000 as Weight).saturating_mul(r as Weight)) // Standard Error: 1_000 - .saturating_add((300_000 as Weight).saturating_mul(x as Weight)) + .saturating_add((312_000 as Weight).saturating_mul(x as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Identity IdentityOf (r:1 w:0) // Storage: Identity SubsOf (r:1 w:1) // Storage: Identity SuperOf (r:1 w:1) + /// The range of component `s` is `[1, 100]`. fn set_subs_new(s: u32, ) -> Weight { - (31_009_000 as Weight) - // Standard Error: 1_000 - .saturating_add((3_053_000 as Weight).saturating_mul(s as Weight)) + (30_012_000 as Weight) + // Standard Error: 2_000 + .saturating_add((3_005_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(s as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -266,10 +297,11 @@ impl WeightInfo for () { // Storage: Identity IdentityOf (r:1 w:0) // Storage: Identity SubsOf (r:1 w:1) // Storage: Identity SuperOf (r:0 w:1) + /// The range of component `p` is `[1, 100]`. fn set_subs_old(p: u32, ) -> Weight { - (29_712_000 as Weight) + (29_623_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_087_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((1_100_000 as Weight).saturating_mul(p as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(p as Weight))) @@ -277,71 +309,83 @@ impl WeightInfo for () { // Storage: Identity SubsOf (r:1 w:1) // Storage: Identity IdentityOf (r:1 w:1) // Storage: Identity SuperOf (r:0 w:100) + /// The range of component `r` is `[1, 20]`. + /// The range of component `s` is `[1, 100]`. + /// The range of component `x` is `[1, 100]`. fn clear_identity(r: u32, s: u32, x: u32, ) -> Weight { - (33_943_000 as Weight) - // Standard Error: 7_000 - .saturating_add((193_000 as Weight).saturating_mul(r as Weight)) - // Standard Error: 0 - .saturating_add((1_101_000 as Weight).saturating_mul(s as Weight)) - // Standard Error: 0 - .saturating_add((194_000 as Weight).saturating_mul(x as Weight)) + (34_370_000 as Weight) + // Standard Error: 10_000 + .saturating_add((186_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 1_000 + .saturating_add((1_114_000 as Weight).saturating_mul(s as Weight)) + // Standard Error: 1_000 + .saturating_add((189_000 as Weight).saturating_mul(x as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) } // Storage: Identity Registrars (r:1 w:0) // Storage: Identity IdentityOf (r:1 w:1) + /// The range of component `r` is `[1, 20]`. + /// The range of component `x` is `[1, 100]`. fn request_judgement(r: u32, x: u32, ) -> Weight { - (34_861_000 as Weight) - // Standard Error: 3_000 - .saturating_add((249_000 as Weight).saturating_mul(r as Weight)) + (34_759_000 as Weight) + // Standard Error: 4_000 + .saturating_add((251_000 as Weight).saturating_mul(r as Weight)) // Standard Error: 0 - .saturating_add((344_000 as Weight).saturating_mul(x as Weight)) + .saturating_add((340_000 as Weight).saturating_mul(x as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Identity IdentityOf (r:1 w:1) + /// The range of component `r` is `[1, 20]`. + /// The range of component `x` is `[1, 100]`. fn cancel_request(r: u32, x: u32, ) -> Weight { - (32_906_000 as Weight) - // Standard Error: 3_000 - .saturating_add((147_000 as Weight).saturating_mul(r as Weight)) + (32_254_000 as Weight) + // Standard Error: 7_000 + .saturating_add((159_000 as Weight).saturating_mul(r as Weight)) // Standard Error: 0 - .saturating_add((341_000 as Weight).saturating_mul(x as Weight)) + .saturating_add((347_000 as Weight).saturating_mul(x as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Identity Registrars (r:1 w:1) + /// The range of component `r` is `[1, 19]`. fn set_fee(r: u32, ) -> Weight { - (7_591_000 as Weight) + (7_858_000 as Weight) // Standard Error: 3_000 - .saturating_add((201_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((190_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Identity Registrars (r:1 w:1) + /// The range of component `r` is `[1, 19]`. fn set_account_id(r: u32, ) -> Weight { - (7_919_000 as Weight) + (8_011_000 as Weight) // Standard Error: 3_000 - .saturating_add((183_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((187_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Identity Registrars (r:1 w:1) + /// The range of component `r` is `[1, 19]`. fn set_fields(r: u32, ) -> Weight { - (7_887_000 as Weight) - // Standard Error: 4_000 - .saturating_add((182_000 as Weight).saturating_mul(r as Weight)) + (7_970_000 as Weight) + // Standard Error: 3_000 + .saturating_add((175_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Identity Registrars (r:1 w:0) // Storage: Identity IdentityOf (r:1 w:1) + /// The range of component `r` is `[1, 19]`. + /// The range of component `x` is `[1, 100]`. fn provide_judgement(r: u32, x: u32, ) -> Weight { - (24_623_000 as Weight) - // Standard Error: 3_000 - .saturating_add((230_000 as Weight).saturating_mul(r as Weight)) + (24_730_000 as Weight) + // Standard Error: 4_000 + .saturating_add((196_000 as Weight).saturating_mul(r as Weight)) // Standard Error: 0 - .saturating_add((339_000 as Weight).saturating_mul(x as Weight)) + .saturating_add((341_000 as Weight).saturating_mul(x as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -349,12 +393,17 @@ impl WeightInfo for () { // Storage: Identity IdentityOf (r:1 w:1) // Storage: System Account (r:1 w:1) // Storage: Identity SuperOf (r:0 w:100) - fn kill_identity(r: u32, s: u32, _x: u32, ) -> Weight { - (48_143_000 as Weight) - // Standard Error: 8_000 - .saturating_add((106_000 as Weight).saturating_mul(r as Weight)) - // Standard Error: 0 - .saturating_add((1_105_000 as Weight).saturating_mul(s as Weight)) + /// The range of component `r` is `[1, 20]`. + /// The range of component `s` is `[1, 100]`. + /// The range of component `x` is `[1, 100]`. + fn kill_identity(r: u32, s: u32, x: u32, ) -> Weight { + (44_988_000 as Weight) + // Standard Error: 10_000 + .saturating_add((201_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 1_000 + .saturating_add((1_126_000 as Weight).saturating_mul(s as Weight)) + // Standard Error: 1_000 + .saturating_add((2_000 as Weight).saturating_mul(x as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) @@ -362,38 +411,42 @@ impl WeightInfo for () { // Storage: Identity IdentityOf (r:1 w:0) // Storage: Identity SuperOf (r:1 w:1) // Storage: Identity SubsOf (r:1 w:1) + /// The range of component `s` is `[1, 99]`. fn add_sub(s: u32, ) -> Weight { - (36_778_000 as Weight) + (36_768_000 as Weight) // Standard Error: 1_000 - .saturating_add((112_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((115_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Identity IdentityOf (r:1 w:0) // Storage: Identity SuperOf (r:1 w:1) + /// The range of component `s` is `[1, 100]`. fn rename_sub(s: u32, ) -> Weight { - (13_895_000 as Weight) + (13_474_000 as Weight) // Standard Error: 0 - .saturating_add((52_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((56_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Identity IdentityOf (r:1 w:0) // Storage: Identity SuperOf (r:1 w:1) // Storage: Identity SubsOf (r:1 w:1) + /// The range of component `s` is `[1, 100]`. fn remove_sub(s: u32, ) -> Weight { - (37_707_000 as Weight) + (37_720_000 as Weight) // Standard Error: 1_000 - .saturating_add((110_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((114_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Identity SuperOf (r:1 w:1) // Storage: Identity SubsOf (r:1 w:1) + /// The range of component `s` is `[1, 99]`. fn quit_sub(s: u32, ) -> Weight { - (26_935_000 as Weight) + (26_848_000 as Weight) // Standard Error: 1_000 - .saturating_add((106_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((115_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } diff --git a/frame/system/src/weights.rs b/frame/system/src/weights.rs index a9126e4bf5bf4..19719032587ef 100644 --- a/frame/system/src/weights.rs +++ b/frame/system/src/weights.rs @@ -57,9 +57,11 @@ pub trait WeightInfo { /// Weights for frame_system using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { + /// The range of component `b` is `[0, 3932160]`. fn remark(_b: u32, ) -> Weight { - (0 as Weight) + (1_000_000 as Weight) } + /// The range of component `b` is `[0, 3932160]`. fn remark_with_event(b: u32, ) -> Weight { (0 as Weight) // Standard Error: 0 @@ -73,6 +75,7 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `i` is `[1, 1000]`. fn set_storage(i: u32, ) -> Weight { (0 as Weight) // Standard Error: 1_000 @@ -80,6 +83,7 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(i as Weight))) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `i` is `[1, 1000]`. fn kill_storage(i: u32, ) -> Weight { (0 as Weight) // Standard Error: 1_000 @@ -87,6 +91,7 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(i as Weight))) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `p` is `[1, 1000]`. fn kill_prefix(p: u32, ) -> Weight { (0 as Weight) // Standard Error: 1_000 @@ -97,9 +102,11 @@ impl WeightInfo for SubstrateWeight { // For backwards compatibility and tests impl WeightInfo for () { + /// The range of component `b` is `[0, 3932160]`. fn remark(_b: u32, ) -> Weight { - (0 as Weight) + (1_000_000 as Weight) } + /// The range of component `b` is `[0, 3932160]`. fn remark_with_event(b: u32, ) -> Weight { (0 as Weight) // Standard Error: 0 @@ -113,6 +120,7 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `i` is `[1, 1000]`. fn set_storage(i: u32, ) -> Weight { (0 as Weight) // Standard Error: 1_000 @@ -120,6 +128,7 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(i as Weight))) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `i` is `[1, 1000]`. fn kill_storage(i: u32, ) -> Weight { (0 as Weight) // Standard Error: 1_000 @@ -127,6 +136,7 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(i as Weight))) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `p` is `[1, 1000]`. fn kill_prefix(p: u32, ) -> Weight { (0 as Weight) // Standard Error: 1_000 diff --git a/scripts/run_all_benchmarks.sh b/scripts/run_all_benchmarks.sh index 8d0d3d5411ed9..9aac58be45029 100755 --- a/scripts/run_all_benchmarks.sh +++ b/scripts/run_all_benchmarks.sh @@ -119,8 +119,9 @@ for PALLET in "${PALLETS[@]}"; do --extrinsic="*" \ --execution=wasm \ --wasm-execution=compiled \ - --template=./.maintain/frame-weight-template.hbs \ - --output="$WEIGHT_FILE" 2>&1 + --heap-pages=4096 \ + --output="$WEIGHT_FILE" \ + --template=./.maintain/frame-weight-template.hbs 2>&1 ) if [ $? -ne 0 ]; then echo "$OUTPUT" >> "$ERR_FILE" diff --git a/utils/frame/benchmarking-cli/src/pallet/command.rs b/utils/frame/benchmarking-cli/src/pallet/command.rs index aa95223619b45..fae5a4494cdc4 100644 --- a/utils/frame/benchmarking-cli/src/pallet/command.rs +++ b/utils/frame/benchmarking-cli/src/pallet/command.rs @@ -29,6 +29,7 @@ use sc_cli::{ use sc_client_db::BenchmarkingState; use sc_executor::NativeElseWasmExecutor; use sc_service::{Configuration, NativeExecutionDispatch}; +use serde::Serialize; use sp_core::offchain::{ testing::{TestOffchainExt, TestTransactionPoolExt}, OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, @@ -37,7 +38,18 @@ use sp_externalities::Extensions; use sp_keystore::{testing::KeyStore, KeystoreExt, SyncCryptoStorePtr}; use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; use sp_state_machine::StateMachine; -use std::{fmt::Debug, fs, sync::Arc, time}; +use std::{collections::HashMap, fmt::Debug, fs, sync::Arc, time}; + +/// The inclusive range of a component. +#[derive(Serialize, Debug, Clone, Eq, PartialEq)] +pub(crate) struct ComponentRange { + /// Name of the component. + name: String, + /// Minimal valid value of the component. + min: u32, + /// Maximal valid value of the component. + max: u32, +} // This takes multiple benchmark batches and combines all the results where the pallet, instance, // and benchmark are the same. @@ -212,6 +224,9 @@ impl PalletCmd { let mut batches = Vec::new(); let mut batches_db = Vec::new(); let mut timer = time::SystemTime::now(); + // Maps (pallet, extrinsic) to its component ranges. + let mut component_ranges = HashMap::<(Vec, Vec), Vec>::new(); + for (pallet, extrinsic, components) in benchmarks_to_run { let all_components = if components.is_empty() { vec![Default::default()] @@ -244,6 +259,11 @@ impl PalletCmd { .collect(); all_components.push(c); } + + component_ranges + .entry((pallet.clone(), extrinsic.clone())) + .or_default() + .push(ComponentRange { name: name.to_string(), min: lowest, max: highest }); } all_components }; @@ -366,7 +386,7 @@ impl PalletCmd { // Create the weights.rs file. if let Some(output_path) = &self.output { - writer::write_results(&batches, &storage_info, output_path, self)?; + writer::write_results(&batches, &storage_info, &component_ranges, output_path, self)?; } // Jsonify the result and write it to a file or stdout if desired. diff --git a/utils/frame/benchmarking-cli/src/pallet/template.hbs b/utils/frame/benchmarking-cli/src/pallet/template.hbs index a2f75c7b0a35b..688ad4d3934f5 100644 --- a/utils/frame/benchmarking-cli/src/pallet/template.hbs +++ b/utils/frame/benchmarking-cli/src/pallet/template.hbs @@ -25,6 +25,9 @@ impl {{pallet}}::WeightInfo for WeightInfo { {{#each benchmark.comments as |comment|}} // {{comment}} {{/each}} + {{#each benchmark.component_ranges as |range|}} + /// The range of component `{{range.name}}` is `[{{range.min}}, {{range.max}}]`. + {{/each}} fn {{benchmark.name~}} ( {{~#each benchmark.components as |c| ~}} diff --git a/utils/frame/benchmarking-cli/src/pallet/writer.rs b/utils/frame/benchmarking-cli/src/pallet/writer.rs index dc38fbcfc1756..42a237fcf3ce3 100644 --- a/utils/frame/benchmarking-cli/src/pallet/writer.rs +++ b/utils/frame/benchmarking-cli/src/pallet/writer.rs @@ -26,7 +26,7 @@ use std::{ use inflector::Inflector; use serde::Serialize; -use crate::{shared::UnderscoreHelper, PalletCmd}; +use crate::{pallet::command::ComponentRange, shared::UnderscoreHelper, PalletCmd}; use frame_benchmarking::{ Analysis, AnalysisChoice, BenchmarkBatchSplitResults, BenchmarkResult, BenchmarkSelector, RegressionModel, @@ -67,6 +67,7 @@ struct BenchmarkData { component_weight: Vec, component_reads: Vec, component_writes: Vec, + component_ranges: Vec, comments: Vec, } @@ -118,6 +119,7 @@ fn io_error(s: &str) -> std::io::Error { fn map_results( batches: &[BenchmarkBatchSplitResults], storage_info: &[StorageInfo], + component_ranges: &HashMap<(Vec, Vec), Vec>, analysis_choice: &AnalysisChoice, ) -> Result>, std::io::Error> { // Skip if batches is empty. @@ -135,7 +137,8 @@ fn map_results( let pallet_string = String::from_utf8(batch.pallet.clone()).unwrap(); let instance_string = String::from_utf8(batch.instance.clone()).unwrap(); - let benchmark_data = get_benchmark_data(batch, storage_info, analysis_choice); + let benchmark_data = + get_benchmark_data(batch, storage_info, &component_ranges, analysis_choice); let pallet_benchmarks = all_benchmarks.entry((pallet_string, instance_string)).or_default(); pallet_benchmarks.push(benchmark_data); } @@ -155,6 +158,8 @@ fn extract_errors(model: &Option) -> impl Iterator fn get_benchmark_data( batch: &BenchmarkBatchSplitResults, storage_info: &[StorageInfo], + // Per extrinsic component ranges. + component_ranges: &HashMap<(Vec, Vec), Vec>, analysis_choice: &AnalysisChoice, ) -> BenchmarkData { // You can use this to put any additional comments with the benchmarking output. @@ -238,6 +243,10 @@ fn get_benchmark_data( // We add additional comments showing which storage items were touched. add_storage_comments(&mut comments, &batch.db_results, storage_info); + let component_ranges = component_ranges + .get(&(batch.pallet.clone(), batch.benchmark.clone())) + .map(|c| c.clone()) + .unwrap_or_default(); BenchmarkData { name: String::from_utf8(batch.benchmark.clone()).unwrap(), @@ -248,14 +257,16 @@ fn get_benchmark_data( component_weight: used_extrinsic_time, component_reads: used_reads, component_writes: used_writes, + component_ranges, comments, } } // Create weight file from benchmark data and Handlebars template. -pub fn write_results( +pub(crate) fn write_results( batches: &[BenchmarkBatchSplitResults], storage_info: &[StorageInfo], + component_ranges: &HashMap<(Vec, Vec), Vec>, path: &PathBuf, cmd: &PalletCmd, ) -> Result<(), std::io::Error> { @@ -305,7 +316,7 @@ pub fn write_results( handlebars.register_escape_fn(|s| -> String { s.to_string() }); // Organize results by pallet into a JSON map - let all_results = map_results(batches, storage_info, &analysis_choice)?; + let all_results = map_results(batches, storage_info, component_ranges, &analysis_choice)?; for ((pallet, instance), results) in all_results.iter() { let mut file_path = path.clone(); // If a user only specified a directory... @@ -531,6 +542,7 @@ mod test { test_data(b"second", b"first", BenchmarkParameter::c, 3, 4), ], &[], + &Default::default(), &AnalysisChoice::default(), ) .unwrap(); From a08a79005092270ca1f6d70ecc62d6eda244cef9 Mon Sep 17 00:00:00 2001 From: zqhxuyuan Date: Mon, 6 Jun 2022 21:38:31 +0800 Subject: [PATCH 311/484] make era public (#11575) --- frame/system/src/extensions/check_mortality.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/system/src/extensions/check_mortality.rs b/frame/system/src/extensions/check_mortality.rs index 2fb99c9f45e2b..5090093fe168f 100644 --- a/frame/system/src/extensions/check_mortality.rs +++ b/frame/system/src/extensions/check_mortality.rs @@ -33,7 +33,7 @@ use sp_runtime::{ /// The extension affects `longevity` of the transaction according to the [`Era`] definition. #[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] #[scale_info(skip_type_params(T))] -pub struct CheckMortality(Era, sp_std::marker::PhantomData); +pub struct CheckMortality(pub Era, sp_std::marker::PhantomData); impl CheckMortality { /// utility constructor. Used only in client/factory code. From c490c5cda27fa5eafd8db0966cc4083fc7828c35 Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Mon, 6 Jun 2022 15:47:37 +0100 Subject: [PATCH 312/484] MEL for Ranked Collective (#11602) --- frame/ranked-collective/src/lib.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frame/ranked-collective/src/lib.rs b/frame/ranked-collective/src/lib.rs index 61686f1efeb2f..60df32dcb3fa3 100644 --- a/frame/ranked-collective/src/lib.rs +++ b/frame/ranked-collective/src/lib.rs @@ -145,14 +145,14 @@ impl VoteTally for Tally { } /// Record needed for every member. -#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct MemberRecord { /// The rank of the member. rank: Rank, } /// Record needed for every vote. -#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, RuntimeDebug, TypeInfo)] +#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub enum VoteRecord { /// Vote was an aye with given vote weight. Aye(Votes), @@ -296,7 +296,6 @@ pub mod pallet { #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] - #[pallet::without_storage_info] pub struct Pallet(PhantomData<(T, I)>); #[pallet::config] From e221373797d226ee9c03da72443245eb667ce76d Mon Sep 17 00:00:00 2001 From: Koute Date: Tue, 7 Jun 2022 01:17:41 +0900 Subject: [PATCH 313/484] Fix one-by-off in `BoundedSlice::try_from` (#11600) --- frame/support/src/storage/bounded_vec.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/frame/support/src/storage/bounded_vec.rs b/frame/support/src/storage/bounded_vec.rs index f1f4330ab2960..232f9f97fc6f8 100644 --- a/frame/support/src/storage/bounded_vec.rs +++ b/frame/support/src/storage/bounded_vec.rs @@ -135,7 +135,7 @@ impl> Ord for BoundedVec { impl<'a, T, S: Get> TryFrom<&'a [T]> for BoundedSlice<'a, T, S> { type Error = (); fn try_from(t: &'a [T]) -> Result { - if t.len() < S::get() as usize { + if t.len() <= S::get() as usize { Ok(BoundedSlice(t, PhantomData)) } else { Err(()) @@ -1007,4 +1007,18 @@ pub mod test { _ => unreachable!("deserializer must raise error"), } } + + #[test] + fn bounded_vec_try_from_works() { + assert!(BoundedVec::>::try_from(vec![0]).is_ok()); + assert!(BoundedVec::>::try_from(vec![0, 1]).is_ok()); + assert!(BoundedVec::>::try_from(vec![0, 1, 2]).is_err()); + } + + #[test] + fn bounded_slice_try_from_works() { + assert!(BoundedSlice::>::try_from(&[0][..]).is_ok()); + assert!(BoundedSlice::>::try_from(&[0, 1][..]).is_ok()); + assert!(BoundedSlice::>::try_from(&[0, 1, 2][..]).is_err()); + } } From dfbbe8fc2df74b8c1a50c278327eda3c796afa0d Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Tue, 7 Jun 2022 11:08:58 +0100 Subject: [PATCH 314/484] Remove a max supply record on collection's destruction (#11593) * Remove a max supply record on collection's destruction * cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_utility --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/utility/src/weights.rs --template=./.maintain/frame-weight-template.hbs * cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_uniques --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/uniques/src/weights.rs --template=./.maintain/frame-weight-template.hbs * cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_uniques --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/uniques/src/weights.rs --template=./.maintain/frame-weight-template.hbs Co-authored-by: Parity Bot --- frame/uniques/src/functions.rs | 1 + frame/uniques/src/tests.rs | 8 ++ frame/uniques/src/weights.rs | 142 ++++++++++++++++++--------------- frame/utility/src/weights.rs | 54 +++++++------ 4 files changed, 117 insertions(+), 88 deletions(-) diff --git a/frame/uniques/src/functions.rs b/frame/uniques/src/functions.rs index c5220b562b172..155fb35ef0999 100644 --- a/frame/uniques/src/functions.rs +++ b/frame/uniques/src/functions.rs @@ -117,6 +117,7 @@ impl, I: 'static> Pallet { Attribute::::remove_prefix((&collection,), None); CollectionAccount::::remove(&collection_details.owner, &collection); T::Currency::unreserve(&collection_details.owner, collection_details.total_deposit); + CollectionMaxSupply::::remove(&collection); Self::deposit_event(Event::Destroyed { collection }); diff --git a/frame/uniques/src/tests.rs b/frame/uniques/src/tests.rs index 98e81863114e2..ef28a2143e84a 100644 --- a/frame/uniques/src/tests.rs +++ b/frame/uniques/src/tests.rs @@ -684,5 +684,13 @@ fn max_supply_should_work() { Uniques::mint(Origin::signed(user_id), collection_id, 2, user_id), Error::::MaxSupplyReached ); + + // validate we remove the CollectionMaxSupply record when we destroy the collection + assert_ok!(Uniques::destroy( + Origin::signed(user_id), + collection_id, + Collection::::get(collection_id).unwrap().destroy_witness() + )); + assert!(!CollectionMaxSupply::::contains_key(collection_id)); }); } diff --git a/frame/uniques/src/weights.rs b/frame/uniques/src/weights.rs index 6e0ac358d3cdd..d885077a8dee9 100644 --- a/frame/uniques/src/weights.rs +++ b/frame/uniques/src/weights.rs @@ -18,11 +18,12 @@ //! Autogenerated weights for pallet_uniques //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-24, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-06-03, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// ./target/production/substrate +// target/production/substrate // benchmark // pallet // --chain=dev @@ -32,8 +33,9 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --template=./.maintain/frame-weight-template.hbs +// --heap-pages=4096 // --output=./frame/uniques/src/weights.rs +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -76,14 +78,14 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn create() -> Weight { - (28_225_000 as Weight) + (27_715_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn force_create() -> Weight { - (16_513_000 as Weight) + (16_929_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -93,18 +95,22 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Attribute (r:0 w:1000) // Storage: Uniques ClassMetadataOf (r:0 w:1) // Storage: Uniques InstanceMetadataOf (r:0 w:1000) + // Storage: Uniques CollectionMaxSupply (r:0 w:1) // Storage: Uniques Account (r:0 w:20) + /// The range of component `n` is `[0, 1000]`. + /// The range of component `m` is `[0, 1000]`. + /// The range of component `a` is `[0, 1000]`. fn destroy(n: u32, m: u32, a: u32, ) -> Weight { (0 as Weight) - // Standard Error: 15_000 - .saturating_add((10_527_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 15_000 - .saturating_add((1_505_000 as Weight).saturating_mul(m as Weight)) - // Standard Error: 15_000 - .saturating_add((1_401_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 16_000 + .saturating_add((10_481_000 as Weight).saturating_mul(n as Weight)) + // Standard Error: 16_000 + .saturating_add((1_762_000 as Weight).saturating_mul(m as Weight)) + // Standard Error: 16_000 + .saturating_add((1_590_000 as Weight).saturating_mul(a as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) .saturating_add(T::DbWeight::get().writes((2 as Weight).saturating_mul(n as Weight))) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(m as Weight))) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(a as Weight))) @@ -114,7 +120,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques CollectionMaxSupply (r:1 w:0) // Storage: Uniques Account (r:0 w:1) fn mint() -> Weight { - (35_712_000 as Weight) + (36_577_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -122,7 +128,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Account (r:0 w:1) fn burn() -> Weight { - (35_740_000 as Weight) + (36_124_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -130,16 +136,17 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Account (r:0 w:2) fn transfer() -> Weight { - (27_425_000 as Weight) + (27_225_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques Asset (r:100 w:100) + /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { (0 as Weight) - // Standard Error: 13_000 - .saturating_add((12_499_000 as Weight).saturating_mul(i as Weight)) + // Standard Error: 14_000 + .saturating_add((12_407_000 as Weight).saturating_mul(i as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -148,26 +155,26 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn freeze() -> Weight { - (21_787_000 as Weight) + (21_452_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn thaw() -> Weight { - (21_938_000 as Weight) + (22_155_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn freeze_collection() -> Weight { - (17_122_000 as Weight) + (16_897_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn thaw_collection() -> Weight { - (17_188_000 as Weight) + (16_657_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -175,20 +182,20 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:2) fn transfer_ownership() -> Weight { - (24_539_000 as Weight) + (25_057_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn set_team() -> Weight { - (17_808_000 as Weight) + (17_253_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn force_item_status() -> Weight { - (20_251_000 as Weight) + (20_010_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -196,7 +203,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn set_attribute() -> Weight { - (41_106_000 as Weight) + (41_159_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -204,62 +211,62 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn clear_attribute() -> Weight { - (40_049_000 as Weight) + (39_598_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:1) fn set_metadata() -> Weight { - (33_701_000 as Weight) + (33_387_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:1) fn clear_metadata() -> Weight { - (33_733_000 as Weight) + (33_208_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassMetadataOf (r:1 w:1) fn set_collection_metadata() -> Weight { - (32_848_000 as Weight) + (32_619_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques ClassMetadataOf (r:1 w:1) fn clear_collection_metadata() -> Weight { - (30_232_000 as Weight) + (31_028_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) fn approve_transfer() -> Weight { - (22_890_000 as Weight) + (22_263_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) fn cancel_approval() -> Weight { - (22_845_000 as Weight) + (22_910_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques OwnershipAcceptance (r:1 w:1) fn set_accept_ownership() -> Weight { - (21_490_000 as Weight) + (20_716_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques CollectionMaxSupply (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn set_collection_max_supply() -> Weight { - (19_412_000 as Weight) + (19_380_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -270,14 +277,14 @@ impl WeightInfo for () { // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn create() -> Weight { - (28_225_000 as Weight) + (27_715_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn force_create() -> Weight { - (16_513_000 as Weight) + (16_929_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -287,18 +294,22 @@ impl WeightInfo for () { // Storage: Uniques Attribute (r:0 w:1000) // Storage: Uniques ClassMetadataOf (r:0 w:1) // Storage: Uniques InstanceMetadataOf (r:0 w:1000) + // Storage: Uniques CollectionMaxSupply (r:0 w:1) // Storage: Uniques Account (r:0 w:20) + /// The range of component `n` is `[0, 1000]`. + /// The range of component `m` is `[0, 1000]`. + /// The range of component `a` is `[0, 1000]`. fn destroy(n: u32, m: u32, a: u32, ) -> Weight { (0 as Weight) - // Standard Error: 15_000 - .saturating_add((10_527_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 15_000 - .saturating_add((1_505_000 as Weight).saturating_mul(m as Weight)) - // Standard Error: 15_000 - .saturating_add((1_401_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 16_000 + .saturating_add((10_481_000 as Weight).saturating_mul(n as Weight)) + // Standard Error: 16_000 + .saturating_add((1_762_000 as Weight).saturating_mul(m as Weight)) + // Standard Error: 16_000 + .saturating_add((1_590_000 as Weight).saturating_mul(a as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) .saturating_add(RocksDbWeight::get().writes((2 as Weight).saturating_mul(n as Weight))) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(m as Weight))) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(a as Weight))) @@ -308,7 +319,7 @@ impl WeightInfo for () { // Storage: Uniques CollectionMaxSupply (r:1 w:0) // Storage: Uniques Account (r:0 w:1) fn mint() -> Weight { - (35_712_000 as Weight) + (36_577_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -316,7 +327,7 @@ impl WeightInfo for () { // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Account (r:0 w:1) fn burn() -> Weight { - (35_740_000 as Weight) + (36_124_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -324,16 +335,17 @@ impl WeightInfo for () { // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Account (r:0 w:2) fn transfer() -> Weight { - (27_425_000 as Weight) + (27_225_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques Asset (r:100 w:100) + /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { (0 as Weight) - // Standard Error: 13_000 - .saturating_add((12_499_000 as Weight).saturating_mul(i as Weight)) + // Standard Error: 14_000 + .saturating_add((12_407_000 as Weight).saturating_mul(i as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(i as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -342,26 +354,26 @@ impl WeightInfo for () { // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn freeze() -> Weight { - (21_787_000 as Weight) + (21_452_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn thaw() -> Weight { - (21_938_000 as Weight) + (22_155_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn freeze_collection() -> Weight { - (17_122_000 as Weight) + (16_897_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn thaw_collection() -> Weight { - (17_188_000 as Weight) + (16_657_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -369,20 +381,20 @@ impl WeightInfo for () { // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:2) fn transfer_ownership() -> Weight { - (24_539_000 as Weight) + (25_057_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn set_team() -> Weight { - (17_808_000 as Weight) + (17_253_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn force_item_status() -> Weight { - (20_251_000 as Weight) + (20_010_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -390,7 +402,7 @@ impl WeightInfo for () { // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn set_attribute() -> Weight { - (41_106_000 as Weight) + (41_159_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -398,62 +410,62 @@ impl WeightInfo for () { // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn clear_attribute() -> Weight { - (40_049_000 as Weight) + (39_598_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:1) fn set_metadata() -> Weight { - (33_701_000 as Weight) + (33_387_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:1) fn clear_metadata() -> Weight { - (33_733_000 as Weight) + (33_208_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassMetadataOf (r:1 w:1) fn set_collection_metadata() -> Weight { - (32_848_000 as Weight) + (32_619_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques ClassMetadataOf (r:1 w:1) fn clear_collection_metadata() -> Weight { - (30_232_000 as Weight) + (31_028_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) fn approve_transfer() -> Weight { - (22_890_000 as Weight) + (22_263_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) fn cancel_approval() -> Weight { - (22_845_000 as Weight) + (22_910_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques OwnershipAcceptance (r:1 w:1) fn set_accept_ownership() -> Weight { - (21_490_000 as Weight) + (20_716_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques CollectionMaxSupply (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn set_collection_max_supply() -> Weight { - (19_412_000 as Weight) + (19_380_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } diff --git a/frame/utility/src/weights.rs b/frame/utility/src/weights.rs index 035831918b1dc..3660a54fb6a8f 100644 --- a/frame/utility/src/weights.rs +++ b/frame/utility/src/weights.rs @@ -18,11 +18,12 @@ //! Autogenerated weights for pallet_utility //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-24, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-06-03, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// ./target/production/substrate +// target/production/substrate // benchmark // pallet // --chain=dev @@ -32,8 +33,9 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --template=./.maintain/frame-weight-template.hbs +// --heap-pages=4096 // --output=./frame/utility/src/weights.rs +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -54,50 +56,56 @@ pub trait WeightInfo { /// Weights for pallet_utility using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { + /// The range of component `c` is `[0, 1000]`. fn batch(c: u32, ) -> Weight { - (20_307_000 as Weight) - // Standard Error: 1_000 + (23_113_000 as Weight) + // Standard Error: 2_000 .saturating_add((2_701_000 as Weight).saturating_mul(c as Weight)) } fn as_derivative() -> Weight { - (4_082_000 as Weight) + (4_182_000 as Weight) } + /// The range of component `c` is `[0, 1000]`. fn batch_all(c: u32, ) -> Weight { - (3_087_000 as Weight) - // Standard Error: 2_000 - .saturating_add((2_842_000 as Weight).saturating_mul(c as Weight)) + (18_682_000 as Weight) + // Standard Error: 1_000 + .saturating_add((2_794_000 as Weight).saturating_mul(c as Weight)) } fn dispatch_as() -> Weight { - (11_832_000 as Weight) + (12_049_000 as Weight) } + /// The range of component `c` is `[0, 1000]`. fn force_batch(c: u32, ) -> Weight { - (19_750_000 as Weight) - // Standard Error: 1_000 - .saturating_add((2_715_000 as Weight).saturating_mul(c as Weight)) + (19_136_000 as Weight) + // Standard Error: 2_000 + .saturating_add((2_697_000 as Weight).saturating_mul(c as Weight)) } } // For backwards compatibility and tests impl WeightInfo for () { + /// The range of component `c` is `[0, 1000]`. fn batch(c: u32, ) -> Weight { - (20_307_000 as Weight) - // Standard Error: 1_000 + (23_113_000 as Weight) + // Standard Error: 2_000 .saturating_add((2_701_000 as Weight).saturating_mul(c as Weight)) } fn as_derivative() -> Weight { - (4_082_000 as Weight) + (4_182_000 as Weight) } + /// The range of component `c` is `[0, 1000]`. fn batch_all(c: u32, ) -> Weight { - (3_087_000 as Weight) - // Standard Error: 2_000 - .saturating_add((2_842_000 as Weight).saturating_mul(c as Weight)) + (18_682_000 as Weight) + // Standard Error: 1_000 + .saturating_add((2_794_000 as Weight).saturating_mul(c as Weight)) } fn dispatch_as() -> Weight { - (11_832_000 as Weight) + (12_049_000 as Weight) } + /// The range of component `c` is `[0, 1000]`. fn force_batch(c: u32, ) -> Weight { - (19_750_000 as Weight) - // Standard Error: 1_000 - .saturating_add((2_715_000 as Weight).saturating_mul(c as Weight)) + (19_136_000 as Weight) + // Standard Error: 2_000 + .saturating_add((2_697_000 as Weight).saturating_mul(c as Weight)) } } From 009d08fca0eb8210dde70b2efcf13df3ea5230b7 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Tue, 7 Jun 2022 15:32:30 +0100 Subject: [PATCH 315/484] Implement more IntoIter traits for bounded types (#11616) --- .../support/src/storage/bounded_btree_map.rs | 18 ++++++++++++++ .../support/src/storage/bounded_btree_set.rs | 9 +++++++ frame/support/src/storage/bounded_vec.rs | 24 +++++++++++++++++++ frame/support/src/storage/weak_bounded_vec.rs | 16 +++++++++++++ 4 files changed, 67 insertions(+) diff --git a/frame/support/src/storage/bounded_btree_map.rs b/frame/support/src/storage/bounded_btree_map.rs index 390330457b9b9..fd086f1fb23a1 100644 --- a/frame/support/src/storage/bounded_btree_map.rs +++ b/frame/support/src/storage/bounded_btree_map.rs @@ -252,6 +252,24 @@ impl IntoIterator for BoundedBTreeMap { } } +impl<'a, K, V, S> IntoIterator for &'a BoundedBTreeMap { + type Item = (&'a K, &'a V); + type IntoIter = sp_std::collections::btree_map::Iter<'a, K, V>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + +impl<'a, K, V, S> IntoIterator for &'a mut BoundedBTreeMap { + type Item = (&'a K, &'a mut V); + type IntoIter = sp_std::collections::btree_map::IterMut<'a, K, V>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter_mut() + } +} + impl MaxEncodedLen for BoundedBTreeMap where K: MaxEncodedLen, diff --git a/frame/support/src/storage/bounded_btree_set.rs b/frame/support/src/storage/bounded_btree_set.rs index 275bb07c6275c..77e1c6f1c9625 100644 --- a/frame/support/src/storage/bounded_btree_set.rs +++ b/frame/support/src/storage/bounded_btree_set.rs @@ -230,6 +230,15 @@ impl IntoIterator for BoundedBTreeSet { } } +impl<'a, T, S> IntoIterator for &'a BoundedBTreeSet { + type Item = &'a T; + type IntoIter = sp_std::collections::btree_set::Iter<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + impl MaxEncodedLen for BoundedBTreeSet where T: MaxEncodedLen, diff --git a/frame/support/src/storage/bounded_vec.rs b/frame/support/src/storage/bounded_vec.rs index 232f9f97fc6f8..82ae36a82bf9f 100644 --- a/frame/support/src/storage/bounded_vec.rs +++ b/frame/support/src/storage/bounded_vec.rs @@ -149,6 +149,14 @@ impl<'a, T, S> From> for &'a [T] { } } +impl<'a, T, S> sp_std::iter::IntoIterator for BoundedSlice<'a, T, S> { + type Item = &'a T; + type IntoIter = sp_std::slice::Iter<'a, T>; + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + impl> Decode for BoundedVec { fn decode(input: &mut I) -> Result { let inner = Vec::::decode(input)?; @@ -605,6 +613,22 @@ impl sp_std::iter::IntoIterator for BoundedVec { } } +impl<'a, T, S> sp_std::iter::IntoIterator for &'a BoundedVec { + type Item = &'a T; + type IntoIter = sp_std::slice::Iter<'a, T>; + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + +impl<'a, T, S> sp_std::iter::IntoIterator for &'a mut BoundedVec { + type Item = &'a mut T; + type IntoIter = sp_std::slice::IterMut<'a, T>; + fn into_iter(self) -> Self::IntoIter { + self.0.iter_mut() + } +} + impl codec::DecodeLength for BoundedVec { fn len(self_encoded: &[u8]) -> Result { // `BoundedVec` stored just a `Vec`, thus the length is at the beginning in diff --git a/frame/support/src/storage/weak_bounded_vec.rs b/frame/support/src/storage/weak_bounded_vec.rs index 8d1d38374d8a7..bf9b9a1017221 100644 --- a/frame/support/src/storage/weak_bounded_vec.rs +++ b/frame/support/src/storage/weak_bounded_vec.rs @@ -262,6 +262,22 @@ impl sp_std::iter::IntoIterator for WeakBoundedVec { } } +impl<'a, T, S> sp_std::iter::IntoIterator for &'a WeakBoundedVec { + type Item = &'a T; + type IntoIter = sp_std::slice::Iter<'a, T>; + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + +impl<'a, T, S> sp_std::iter::IntoIterator for &'a mut WeakBoundedVec { + type Item = &'a mut T; + type IntoIter = sp_std::slice::IterMut<'a, T>; + fn into_iter(self) -> Self::IntoIter { + self.0.iter_mut() + } +} + impl codec::DecodeLength for WeakBoundedVec { fn len(self_encoded: &[u8]) -> Result { // `WeakBoundedVec` stored just a `Vec`, thus the length is at the beginning in From 75069cd9f5042b9d83b16b5a402a834388663053 Mon Sep 17 00:00:00 2001 From: Sebastian Kunert Date: Tue, 7 Jun 2022 19:49:36 +0200 Subject: [PATCH 316/484] Remove `without_storage_info` for membership pallet (#11591) --- frame/membership/src/lib.rs | 70 +++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 38 deletions(-) diff --git a/frame/membership/src/lib.rs b/frame/membership/src/lib.rs index 8e7ea9eeec313..24ecfd5333c66 100644 --- a/frame/membership/src/lib.rs +++ b/frame/membership/src/lib.rs @@ -23,7 +23,10 @@ // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] -use frame_support::traits::{ChangeMembers, Contains, Get, InitializeMembers, SortedMembers}; +use frame_support::{ + traits::{ChangeMembers, Contains, Get, InitializeMembers, SortedMembers}, + BoundedVec, +}; use sp_std::prelude::*; pub mod migrations; @@ -44,7 +47,6 @@ pub mod pallet { #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] #[pallet::storage_version(STORAGE_VERSION)] - #[pallet::without_storage_info] pub struct Pallet(PhantomData<(T, I)>); #[pallet::config] @@ -79,7 +81,7 @@ pub mod pallet { /// /// This is used for benchmarking. Re-run the benchmarks if this changes. /// - /// This is not enforced in the code; the membership size can exceed this limit. + /// This is enforced in the code; the membership size can not exceed this limit. type MaxMembers: Get; /// Weight information for extrinsics in this pallet. @@ -90,7 +92,7 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn members)] pub type Members, I: 'static = ()> = - StorageValue<_, Vec, ValueQuery>; + StorageValue<_, BoundedVec, ValueQuery>; /// The current prime member, if one exists. #[pallet::storage] @@ -99,14 +101,14 @@ pub mod pallet { #[pallet::genesis_config] pub struct GenesisConfig, I: 'static = ()> { - pub members: Vec, + pub members: BoundedVec, pub phantom: PhantomData, } #[cfg(feature = "std")] impl, I: 'static> Default for GenesisConfig { fn default() -> Self { - Self { members: Vec::new(), phantom: Default::default() } + Self { members: Default::default(), phantom: Default::default() } } } @@ -151,6 +153,8 @@ pub mod pallet { AlreadyMember, /// Not a member. NotMember, + /// Too many members. + TooManyMembers, } #[pallet::call] @@ -164,9 +168,10 @@ pub mod pallet { let mut members = >::get(); let location = members.binary_search(&who).err().ok_or(Error::::AlreadyMember)?; - members.insert(location, who.clone()); + members + .try_insert(location, who.clone()) + .map_err(|_| Error::::TooManyMembers)?; - Self::maybe_warn_max_members(&members); >::put(&members); T::MembershipChanged::change_members_sorted(&[who], &[], &members[..]); @@ -186,7 +191,6 @@ pub mod pallet { let location = members.binary_search(&who).ok().ok_or(Error::::NotMember)?; members.remove(location); - Self::maybe_warn_max_members(&members); >::put(&members); T::MembershipChanged::change_members_sorted(&[], &[who], &members[..]); @@ -219,7 +223,6 @@ pub mod pallet { members[location] = add.clone(); members.sort(); - Self::maybe_warn_max_members(&members); >::put(&members); T::MembershipChanged::change_members_sorted(&[add], &[remove], &members[..]); @@ -237,12 +240,12 @@ pub mod pallet { pub fn reset_members(origin: OriginFor, members: Vec) -> DispatchResult { T::ResetOrigin::ensure_origin(origin)?; - let mut members = members; + let mut members: BoundedVec = + BoundedVec::try_from(members).map_err(|_| Error::::TooManyMembers)?; members.sort(); >::mutate(|m| { T::MembershipChanged::set_members_sorted(&members[..], m); Self::rejig_prime(&members); - Self::maybe_warn_max_members(&members); *m = members; }); @@ -267,7 +270,6 @@ pub mod pallet { members[location] = new.clone(); members.sort(); - Self::maybe_warn_max_members(&members); >::put(&members); T::MembershipChanged::change_members_sorted( @@ -320,17 +322,6 @@ impl, I: 'static> Pallet { } } } - - fn maybe_warn_max_members(members: &[T::AccountId]) { - if members.len() as u32 > T::MaxMembers::get() { - log::error!( - target: "runtime::membership", - "maximum number of members used for weight is exceeded, weights can be underestimated [{} > {}].", - members.len(), - T::MaxMembers::get(), - ) - } - } } impl, I: 'static> Contains for Pallet { @@ -341,7 +332,7 @@ impl, I: 'static> Contains for Pallet { impl, I: 'static> SortedMembers for Pallet { fn sorted_members() -> Vec { - Self::members() + Self::members().to_vec() } fn count() -> usize { @@ -372,7 +363,7 @@ mod benchmark { benchmarks_instance_pallet! { add_member { - let m in 1 .. T::MaxMembers::get(); + let m in 1 .. (T::MaxMembers::get() - 1); let members = (0..m).map(|i| account("member", i, SEED)).collect::>(); set_members::(members, None); @@ -504,7 +495,7 @@ mod tests { }; use frame_support::{ - assert_noop, assert_ok, ord_parameter_types, parameter_types, + assert_noop, assert_ok, bounded_vec, ord_parameter_types, parameter_types, traits::{ConstU32, ConstU64, GenesisBuild, StorageVersion}, }; use frame_system::EnsureSignedBy; @@ -609,7 +600,7 @@ mod tests { let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); // We use default for brevity, but you can configure as desired if needed. pallet_membership::GenesisConfig:: { - members: vec![10, 20, 30], + members: bounded_vec![10, 20, 30], ..Default::default() } .assimilate_storage(&mut t) @@ -661,7 +652,7 @@ mod tests { ); assert_ok!(Membership::add_member(Origin::signed(1), 15)); assert_eq!(Membership::members(), vec![10, 15, 20, 30]); - assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members()); + assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members().to_vec()); }); } @@ -676,7 +667,7 @@ mod tests { assert_ok!(Membership::set_prime(Origin::signed(5), 20)); assert_ok!(Membership::remove_member(Origin::signed(2), 20)); assert_eq!(Membership::members(), vec![10, 30]); - assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members()); + assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members().to_vec()); assert_eq!(Membership::prime(), None); assert_eq!(PRIME.with(|m| *m.borrow()), Membership::prime()); }); @@ -704,7 +695,7 @@ mod tests { assert_ok!(Membership::set_prime(Origin::signed(5), 10)); assert_ok!(Membership::swap_member(Origin::signed(3), 10, 25)); assert_eq!(Membership::members(), vec![20, 25, 30]); - assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members()); + assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members().to_vec()); assert_eq!(Membership::prime(), None); assert_eq!(PRIME.with(|m| *m.borrow()), Membership::prime()); }); @@ -715,7 +706,7 @@ mod tests { new_test_ext().execute_with(|| { assert_ok!(Membership::swap_member(Origin::signed(3), 10, 5)); assert_eq!(Membership::members(), vec![5, 20, 30]); - assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members()); + assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members().to_vec()); }); } @@ -733,7 +724,7 @@ mod tests { ); assert_ok!(Membership::change_key(Origin::signed(10), 40)); assert_eq!(Membership::members(), vec![20, 30, 40]); - assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members()); + assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members().to_vec()); assert_eq!(Membership::prime(), Some(40)); assert_eq!(PRIME.with(|m| *m.borrow()), Membership::prime()); }); @@ -744,7 +735,7 @@ mod tests { new_test_ext().execute_with(|| { assert_ok!(Membership::change_key(Origin::signed(10), 5)); assert_eq!(Membership::members(), vec![5, 20, 30]); - assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members()); + assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members().to_vec()); }); } @@ -752,17 +743,20 @@ mod tests { fn reset_members_works() { new_test_ext().execute_with(|| { assert_ok!(Membership::set_prime(Origin::signed(5), 20)); - assert_noop!(Membership::reset_members(Origin::signed(1), vec![20, 40, 30]), BadOrigin); + assert_noop!( + Membership::reset_members(Origin::signed(1), bounded_vec![20, 40, 30]), + BadOrigin + ); assert_ok!(Membership::reset_members(Origin::signed(4), vec![20, 40, 30])); assert_eq!(Membership::members(), vec![20, 30, 40]); - assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members()); + assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members().to_vec()); assert_eq!(Membership::prime(), Some(20)); assert_eq!(PRIME.with(|m| *m.borrow()), Membership::prime()); assert_ok!(Membership::reset_members(Origin::signed(4), vec![10, 40, 30])); assert_eq!(Membership::members(), vec![10, 30, 40]); - assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members()); + assert_eq!(MEMBERS.with(|m| m.borrow().clone()), Membership::members().to_vec()); assert_eq!(Membership::prime(), None); assert_eq!(PRIME.with(|m| *m.borrow()), Membership::prime()); }); @@ -772,7 +766,7 @@ mod tests { #[should_panic(expected = "Members cannot contain duplicate accounts.")] fn genesis_build_panics_with_duplicate_members() { pallet_membership::GenesisConfig:: { - members: vec![1, 2, 3, 1], + members: bounded_vec![1, 2, 3, 1], phantom: Default::default(), } .build_storage() From eca7f13b8ef1fb1a3375feb8f2918d55c4676ae2 Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Wed, 8 Jun 2022 11:48:46 +0300 Subject: [PATCH 317/484] Improve docs on `--keep-blocks` CLI parameter and related data structures (#11611) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Improve docs on `--keep-blocks` CLI parameter and related data structures * Update client/db/src/lib.rs Co-authored-by: Bastian Köcher Co-authored-by: Bastian Köcher --- client/cli/src/params/pruning_params.rs | 2 ++ client/db/src/lib.rs | 2 ++ client/network/test/src/lib.rs | 2 ++ client/service/src/config.rs | 2 ++ 4 files changed, 8 insertions(+) diff --git a/client/cli/src/params/pruning_params.rs b/client/cli/src/params/pruning_params.rs index fa30aa53b8a04..9d471224cc59e 100644 --- a/client/cli/src/params/pruning_params.rs +++ b/client/cli/src/params/pruning_params.rs @@ -33,6 +33,8 @@ pub struct PruningParams { /// Specify the number of finalized blocks to keep in the database. /// /// Default is to keep all blocks. + /// + /// NOTE: only finalized blocks are subject for removal! #[clap(long, value_name = "COUNT")] pub keep_blocks: Option, } diff --git a/client/db/src/lib.rs b/client/db/src/lib.rs index 5f15b2d6fe969..ccdb434dfbd32 100644 --- a/client/db/src/lib.rs +++ b/client/db/src/lib.rs @@ -302,6 +302,8 @@ pub struct DatabaseSettings { /// Where to find the database. pub source: DatabaseSource, /// Block pruning mode. + /// + /// NOTE: only finalized blocks are subject for removal! pub keep_blocks: KeepBlocks, } diff --git a/client/network/test/src/lib.rs b/client/network/test/src/lib.rs index 9e23a4cc678e9..9e752e81a3bbc 100644 --- a/client/network/test/src/lib.rs +++ b/client/network/test/src/lib.rs @@ -667,6 +667,8 @@ impl warp_request_handler::WarpSyncProvider for TestWarpSyncProvid #[derive(Default)] pub struct FullPeerConfig { /// Pruning window size. + /// + /// NOTE: only finalized blocks are subject for removal! pub keep_blocks: Option, /// Block announce validator. pub block_announce_validator: Option + Send + Sync>>, diff --git a/client/service/src/config.rs b/client/service/src/config.rs index c895300fea4d1..0eeb6e05cee16 100644 --- a/client/service/src/config.rs +++ b/client/service/src/config.rs @@ -77,6 +77,8 @@ pub struct Configuration { /// State pruning settings. pub state_pruning: Option, /// Number of blocks to keep in the db. + /// + /// NOTE: only finalized blocks are subject for removal! pub keep_blocks: KeepBlocks, /// Chain configuration. pub chain_spec: Box, From 443196b0a05921ba94596766addc944438abdeba Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 9 Jun 2022 18:55:02 +0100 Subject: [PATCH 318/484] Improve inspection and generation of node keys (#11525) * Improve inspection and generation of node keys * Lock stdout before write * Fix typo * Fix offset * Remove useless code * Set inspect-node-key 'network' option as obsolete --- bin/utils/subkey/src/lib.rs | 6 +-- client/cli/src/commands/generate_node_key.rs | 31 +++++++++---- client/cli/src/commands/inspect_node_key.rs | 49 +++++++++++++++----- client/cli/src/commands/key.rs | 6 +-- 4 files changed, 66 insertions(+), 26 deletions(-) diff --git a/bin/utils/subkey/src/lib.rs b/bin/utils/subkey/src/lib.rs index 3731d9f3ec75c..6773ba822340f 100644 --- a/bin/utils/subkey/src/lib.rs +++ b/bin/utils/subkey/src/lib.rs @@ -30,8 +30,8 @@ use sc_cli::{ version )] pub enum Subkey { - /// Generate a random node libp2p key, save it to file or print it to stdout - /// and print its peer ID to stderr. + /// Generate a random node key, write it to a file or stdout and write the + /// corresponding peer-id to stderr GenerateNodeKey(GenerateNodeKeyCmd), /// Generate a random account @@ -40,7 +40,7 @@ pub enum Subkey { /// Gets a public key and a SS58 address from the provided Secret URI Inspect(InspectKeyCmd), - /// Print the peer ID corresponding to the node key in the given file + /// Load a node key from a file or stdin and print the corresponding peer-id InspectNodeKey(InspectNodeKeyCmd), /// Sign a message, with a given (secret) key. diff --git a/client/cli/src/commands/generate_node_key.rs b/client/cli/src/commands/generate_node_key.rs index 8c634dca9acf2..6b2f12531458c 100644 --- a/client/cli/src/commands/generate_node_key.rs +++ b/client/cli/src/commands/generate_node_key.rs @@ -20,14 +20,18 @@ use crate::Error; use clap::Parser; use libp2p::identity::{ed25519 as libp2p_ed25519, PublicKey}; -use std::{fs, path::PathBuf}; +use std::{ + fs, + io::{self, Write}, + path::PathBuf, +}; /// The `generate-node-key` command #[derive(Debug, Parser)] #[clap( name = "generate-node-key", - about = "Generate a random node libp2p key, save it to \ - file or print it to stdout and print its peer ID to stderr" + about = "Generate a random node key, write it to a file or stdout \ + and write the corresponding peer-id to stderr" )] pub struct GenerateNodeKeyCmd { /// Name of file to save secret key to. @@ -35,22 +39,33 @@ pub struct GenerateNodeKeyCmd { /// If not given, the secret key is printed to stdout. #[clap(long)] file: Option, + + /// The output is in raw binary format. + /// + /// If not given, the output is written as an hex encoded string. + #[clap(long)] + bin: bool, } impl GenerateNodeKeyCmd { /// Run the command pub fn run(&self) -> Result<(), Error> { let keypair = libp2p_ed25519::Keypair::generate(); + let secret = keypair.secret(); - let peer_id = PublicKey::Ed25519(keypair.public()).to_peer_id(); - let secret_hex = hex::encode(secret.as_ref()); + + let file_data = if self.bin { + secret.as_ref().to_owned() + } else { + hex::encode(secret.as_ref()).into_bytes() + }; match &self.file { - Some(file) => fs::write(file, secret_hex)?, - None => print!("{}", secret_hex), + Some(file) => fs::write(file, file_data)?, + None => io::stdout().lock().write_all(&file_data)?, } - eprintln!("{}", peer_id); + eprintln!("{}", PublicKey::Ed25519(keypair.public()).to_peer_id()); Ok(()) } diff --git a/client/cli/src/commands/inspect_node_key.rs b/client/cli/src/commands/inspect_node_key.rs index 4c0798cc0635c..e1617c1d085df 100644 --- a/client/cli/src/commands/inspect_node_key.rs +++ b/client/cli/src/commands/inspect_node_key.rs @@ -17,39 +17,64 @@ //! Implementation of the `inspect-node-key` subcommand -use crate::{Error, NetworkSchemeFlag}; +use crate::Error; use clap::Parser; use libp2p::identity::{ed25519, PublicKey}; -use std::{fs, path::PathBuf}; +use std::{ + fs, + io::{self, Read}, + path::PathBuf, +}; /// The `inspect-node-key` command #[derive(Debug, Parser)] #[clap( name = "inspect-node-key", - about = "Print the peer ID corresponding to the node key in the given file." + about = "Load a node key from a file or stdin and print the corresponding peer-id." )] pub struct InspectNodeKeyCmd { /// Name of file to read the secret key from. + /// + /// If not given, the secret key is read from stdin (up to EOF). #[clap(long)] - file: PathBuf, + file: Option, - #[allow(missing_docs)] - #[clap(flatten)] - pub network_scheme: NetworkSchemeFlag, + /// The input is in raw binary format. + /// + /// If not given, the input is read as an hex encoded string. + #[clap(long)] + bin: bool, + + /// This argument is deprecated and has no effect for this command. + #[deprecated(note = "Network identifier is not used for node-key inspection")] + #[clap(short = 'n', long = "network", value_name = "NETWORK", ignore_case = true)] + pub network_scheme: Option, } impl InspectNodeKeyCmd { /// runs the command pub fn run(&self) -> Result<(), Error> { - let mut file_content = - hex::decode(fs::read(&self.file)?).map_err(|_| "failed to decode secret as hex")?; + let mut file_data = match &self.file { + Some(file) => fs::read(&file)?, + None => { + let mut buf = Vec::with_capacity(64); + io::stdin().lock().read_to_end(&mut buf)?; + buf + }, + }; + + if !self.bin { + // With hex input, give to the user a bit of tolerance about whitespaces + let keyhex = String::from_utf8_lossy(&file_data); + file_data = hex::decode(keyhex.trim()).map_err(|_| "failed to decode secret as hex")?; + } + let secret = - ed25519::SecretKey::from_bytes(&mut file_content).map_err(|_| "Bad node key file")?; + ed25519::SecretKey::from_bytes(&mut file_data).map_err(|_| "Bad node key file")?; let keypair = ed25519::Keypair::from(secret); - let peer_id = PublicKey::Ed25519(keypair.public()).to_peer_id(); - println!("{}", peer_id); + println!("{}", PublicKey::Ed25519(keypair.public()).to_peer_id()); Ok(()) } diff --git a/client/cli/src/commands/key.rs b/client/cli/src/commands/key.rs index e0f3524196e2c..59cca554bfa37 100644 --- a/client/cli/src/commands/key.rs +++ b/client/cli/src/commands/key.rs @@ -26,8 +26,8 @@ use crate::{Error, SubstrateCli}; /// Key utilities for the cli. #[derive(Debug, clap::Subcommand)] pub enum KeySubcommand { - /// Generate a random node libp2p key, save it to file or print it to stdout - /// and print its peer ID to stderr. + /// Generate a random node key, write it to a file or stdout and write the + /// corresponding peer-id to stderr GenerateNodeKey(GenerateNodeKeyCmd), /// Generate a random account @@ -36,7 +36,7 @@ pub enum KeySubcommand { /// Gets a public key and a SS58 address from the provided Secret URI Inspect(InspectKeyCmd), - /// Print the peer ID corresponding to the node key in the given file + /// Load a node key from a file or stdin and print the corresponding peer-id InspectNodeKey(InspectNodeKeyCmd), /// Insert a key to the keystore of a node. From ca3093367be8283daf103d0616bef317ce3404cc Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Fri, 10 Jun 2022 11:58:55 +0300 Subject: [PATCH 319/484] Better transaction pool error (#11629) * Better transaction pool error * Do not use `Debug` when printing errors --- client/transaction-pool/src/error.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/transaction-pool/src/error.rs b/client/transaction-pool/src/error.rs index adc19d52c7e41..a4cf756481dc3 100644 --- a/client/transaction-pool/src/error.rs +++ b/client/transaction-pool/src/error.rs @@ -27,10 +27,10 @@ pub type Result = std::result::Result; #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { - #[error("Transaction pool error")] + #[error("Transaction pool error: {0}")] Pool(#[from] TxPoolError), - #[error("Blockchain error")] + #[error("Blockchain error: {0}")] Blockchain(#[from] sp_blockchain::Error), #[error("Block conversion error: {0}")] From 2b1b01011fa33025116c011043b73e01e0ce4665 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Fri, 10 Jun 2022 18:20:03 +0100 Subject: [PATCH 320/484] Use BoundedVec in aura pallet (#11617) * Use BoundedVec in aura pallet * cargo fmt --- frame/aura/src/lib.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/frame/aura/src/lib.rs b/frame/aura/src/lib.rs index fd6882ca9f29f..ff2c5df04a453 100644 --- a/frame/aura/src/lib.rs +++ b/frame/aura/src/lib.rs @@ -40,8 +40,9 @@ use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ + log, traits::{DisabledValidators, FindAuthor, Get, OnTimestampSet, OneSessionHandler}, - BoundedSlice, ConsensusEngineId, Parameter, WeakBoundedVec, + BoundedSlice, BoundedVec, ConsensusEngineId, Parameter, }; use sp_consensus_aura::{AuthorityIndex, ConsensusLog, Slot, AURA_ENGINE_ID}; use sp_runtime::{ @@ -116,7 +117,7 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn authorities)] pub(super) type Authorities = - StorageValue<_, WeakBoundedVec, ValueQuery>; + StorageValue<_, BoundedVec, ValueQuery>; /// The current slot of this block. /// @@ -150,7 +151,7 @@ impl Pallet { /// /// The storage will be applied immediately. /// And aura consensus log will be appended to block's log. - pub fn change_authorities(new: WeakBoundedVec) { + pub fn change_authorities(new: BoundedVec) { >::put(&new); let log = DigestItem::Consensus( @@ -219,10 +220,14 @@ impl OneSessionHandler for Pallet { let next_authorities = validators.map(|(_, k)| k).collect::>(); let last_authorities = Self::authorities(); if last_authorities != next_authorities { - let bounded = >::force_from( - next_authorities, - Some("AuRa new session"), - ); + if next_authorities.len() as u32 > T::MaxAuthorities::get() { + log::warn!( + target: "runtime::aura", + "next authorities list larger than {}, truncating", + T::MaxAuthorities::get(), + ); + } + let bounded = >::truncate_from(next_authorities); Self::change_authorities(bounded); } } From 7fffee647d7916a80c003aae3e72780574dff494 Mon Sep 17 00:00:00 2001 From: Xiliang Chen Date: Sat, 11 Jun 2022 06:39:26 +1200 Subject: [PATCH 321/484] pallet alliance (#11112) * Add pallet-alliance Signed-off-by: koushiro * Update multihash/cid and format Signed-off-by: koushiro * Remove useless deps Signed-off-by: koushiro * Add pallet-alliance into node runtime Signed-off-by: koushiro * Fix has_identity Signed-off-by: koushiro * Add TooMantBlacklist Error Signed-off-by: koushiro * Add TooLongWebsiteUrlLength Error Signed-off-by: koushiro * Add remove_announcement Signed-off-by: koushiro * Derive pallet_identity::Config and Fix test/benchmarking Signed-off-by: koushiro * Add part weight for call of pallet-alliance Signed-off-by: koushiro * Remove pallet_identity Config trait Signed-off-by: koushiro * Fix propose arguments Signed-off-by: koushiro * Some nits Signed-off-by: koushiro * cargo fmt Signed-off-by: koushiro * Fix benchmarking of add_blacklist/remove_blacklist Signed-off-by: koushiro * Some nits Signed-off-by: koushiro * Add benchmarking for init_members Signed-off-by: koushiro * Add benchmarking for propose/vote/veto Signed-off-by: koushiro * Fix benchmarking Signed-off-by: koushiro * Remove some useless Signed-off-by: koushiro * fix some compile issue * more fix * all checks * refactor * refactor event * refactor * cleanup * cleanup * fix benchmarking * fix warning * fmt * fix benchmarks * with storage info * fmt * add new config * fix benchmarks * fix * fmt * Apply suggestions from code review Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * improvements * fix * update docs * rename events, errors, and functions * make kicking events clearer * fix * fix tests * fix tests * fix runtime * fix build * remove unneeded change * remove Candidate role from Alliance * fmt grumbles * update lock * update recursion limit * benchmarks * convert-try * benchmark assertions * fmt , * cargo lock * Update frame/alliance/src/benchmarking.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * add docs to public interfaces * Apply suggestions from code review Co-authored-by: Squirrel * fix build (node) * review * use EitherOfDiverse * update cargo toml * make all dispatch class Normal * change blacklist to unscrupulous * formatting * fmt oops * rename benchmarking unscrupulous account creator Co-authored-by: koushiro Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Co-authored-by: joepetrowski Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> Co-authored-by: Squirrel --- Cargo.lock | 23 + Cargo.toml | 1 + bin/node/cli/src/chain_spec.rs | 2 + bin/node/runtime/Cargo.toml | 4 + bin/node/runtime/src/impls.rs | 77 +- bin/node/runtime/src/lib.rs | 70 +- bin/node/testing/src/genesis.rs | 2 + frame/alliance/Cargo.toml | 68 ++ frame/alliance/README.md | 68 ++ frame/alliance/src/benchmarking.rs | 791 +++++++++++++++++++++ frame/alliance/src/lib.rs | 1053 ++++++++++++++++++++++++++++ frame/alliance/src/mock.rs | 324 +++++++++ frame/alliance/src/tests.rs | 483 +++++++++++++ frame/alliance/src/types.rs | 95 +++ frame/alliance/src/weights.rs | 485 +++++++++++++ frame/collective/src/lib.rs | 364 +++++----- 16 files changed, 3745 insertions(+), 165 deletions(-) create mode 100644 frame/alliance/Cargo.toml create mode 100644 frame/alliance/README.md create mode 100644 frame/alliance/src/benchmarking.rs create mode 100644 frame/alliance/src/lib.rs create mode 100644 frame/alliance/src/mock.rs create mode 100644 frame/alliance/src/tests.rs create mode 100644 frame/alliance/src/types.rs create mode 100644 frame/alliance/src/weights.rs diff --git a/Cargo.lock b/Cargo.lock index 42accdb455cef..3b581bf9d0ff7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4741,6 +4741,7 @@ dependencies = [ "hex-literal", "log", "node-primitives", + "pallet-alliance", "pallet-asset-tx-payment", "pallet-assets", "pallet-authority-discovery", @@ -5132,6 +5133,28 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "pallet-alliance" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "hex", + "hex-literal", + "log", + "pallet-balances", + "pallet-collective", + "pallet-identity", + "parity-scale-codec", + "scale-info", + "sha2 0.10.2", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-asset-tx-payment" version = "4.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index 74e7aae7949c5..7f013c08a9144 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,6 +65,7 @@ members = [ "client/transaction-pool", "client/transaction-pool/api", "client/utils", + "frame/alliance", "frame/assets", "frame/atomic-swap", "frame/aura", diff --git a/bin/node/cli/src/chain_spec.rs b/bin/node/cli/src/chain_spec.rs index 221c229876275..1c7794fd0cde6 100644 --- a/bin/node/cli/src/chain_spec.rs +++ b/bin/node/cli/src/chain_spec.rs @@ -363,6 +363,8 @@ pub fn testnet_genesis( gilt: Default::default(), transaction_storage: Default::default(), transaction_payment: Default::default(), + alliance: Default::default(), + alliance_motion: Default::default(), nomination_pools: Default::default(), } } diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 5ecd6ccedaf01..ca971f29e93c9 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -50,6 +50,7 @@ frame-system-benchmarking = { version = "4.0.0-dev", default-features = false, p frame-election-provider-support = { version = "4.0.0-dev", default-features = false, path = "../../../frame/election-provider-support" } frame-system-rpc-runtime-api = { version = "4.0.0-dev", default-features = false, path = "../../../frame/system/rpc/runtime-api/" } frame-try-runtime = { version = "0.10.0-dev", default-features = false, path = "../../../frame/try-runtime", optional = true } +pallet-alliance = { version = "4.0.0-dev", default-features = false, path = "../../../frame/alliance" } pallet-assets = { version = "4.0.0-dev", default-features = false, path = "../../../frame/assets" } pallet-authority-discovery = { version = "4.0.0-dev", default-features = false, path = "../../../frame/authority-discovery" } pallet-authorship = { version = "4.0.0-dev", default-features = false, path = "../../../frame/authorship" } @@ -187,12 +188,14 @@ std = [ "frame-try-runtime/std", "sp-io/std", "pallet-child-bounties/std", + "pallet-alliance/std", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks", + "pallet-alliance/runtime-benchmarks", "pallet-assets/runtime-benchmarks", "pallet-babe/runtime-benchmarks", "pallet-bags-list/runtime-benchmarks", @@ -243,6 +246,7 @@ try-runtime = [ "frame-executive/try-runtime", "frame-try-runtime", "frame-system/try-runtime", + "pallet-alliance/try-runtime", "pallet-assets/try-runtime", "pallet-authority-discovery/try-runtime", "pallet-authorship/try-runtime", diff --git a/bin/node/runtime/src/impls.rs b/bin/node/runtime/src/impls.rs index 4973aed4bd4fc..68c780094208f 100644 --- a/bin/node/runtime/src/impls.rs +++ b/bin/node/runtime/src/impls.rs @@ -17,12 +17,19 @@ //! Some configurable implementations as associated type for the substrate runtime. -use crate::{AccountId, Assets, Authorship, Balances, NegativeImbalance, Runtime}; -use frame_support::traits::{ - fungibles::{Balanced, CreditOf}, - Currency, OnUnbalanced, +use crate::{ + AccountId, AllianceMotion, Assets, Authorship, Balances, Call, Hash, NegativeImbalance, Runtime, }; +use frame_support::{ + pallet_prelude::*, + traits::{ + fungibles::{Balanced, CreditOf}, + Currency, OnUnbalanced, + }, +}; +use pallet_alliance::{IdentityVerifier, ProposalIndex, ProposalProvider}; use pallet_asset_tx_payment::HandleCredit; +use sp_std::prelude::*; pub struct Author; impl OnUnbalanced for Author { @@ -45,6 +52,68 @@ impl HandleCredit for CreditToBlockAuthor { } } +pub struct AllianceIdentityVerifier; +impl IdentityVerifier for AllianceIdentityVerifier { + fn has_identity(who: &AccountId, fields: u64) -> bool { + crate::Identity::has_identity(who, fields) + } + + fn has_good_judgement(who: &AccountId) -> bool { + use pallet_identity::Judgement; + if let Some(judgements) = + crate::Identity::identity(who).map(|registration| registration.judgements) + { + judgements + .iter() + .any(|(_, j)| matches!(j, Judgement::KnownGood | Judgement::Reasonable)) + } else { + false + } + } + + fn super_account_id(who: &AccountId) -> Option { + crate::Identity::super_of(who).map(|parent| parent.0) + } +} + +pub struct AllianceProposalProvider; +impl ProposalProvider for AllianceProposalProvider { + fn propose_proposal( + who: AccountId, + threshold: u32, + proposal: Box, + length_bound: u32, + ) -> Result<(u32, u32), DispatchError> { + AllianceMotion::do_propose_proposed(who, threshold, proposal, length_bound) + } + + fn vote_proposal( + who: AccountId, + proposal: Hash, + index: ProposalIndex, + approve: bool, + ) -> Result { + AllianceMotion::do_vote(who, proposal, index, approve) + } + + fn veto_proposal(proposal_hash: Hash) -> u32 { + AllianceMotion::do_disapprove_proposal(proposal_hash) + } + + fn close_proposal( + proposal_hash: Hash, + proposal_index: ProposalIndex, + proposal_weight_bound: Weight, + length_bound: u32, + ) -> DispatchResultWithPostInfo { + AllianceMotion::do_close(proposal_hash, proposal_index, proposal_weight_bound, length_bound) + } + + fn proposal_of(proposal_hash: Hash) -> Option { + AllianceMotion::proposal_of(proposal_hash) + } +} + #[cfg(test)] mod multiplier_tests { use pallet_transaction_payment::{Multiplier, TargetedFeeAdjustment}; diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index b421a4f05ed1a..8e854b770a4ff 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -19,7 +19,7 @@ //! The Substrate runtime. This can be compiled with `#[no_std]`, ready for Wasm. #![cfg_attr(not(feature = "std"), no_std)] -// `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256. +// `construct_runtime!` does a lot of recursion and requires us to increase the limit to 512. #![recursion_limit = "512"] use codec::{Decode, Encode, MaxEncodedLen}; @@ -90,7 +90,9 @@ pub use sp_runtime::BuildStorage; /// Implementations of some helper traits passed into runtime modules as associated types. pub mod impls; -use impls::{Author, CreditToBlockAuthor}; +#[cfg(not(feature = "runtime-benchmarks"))] +use impls::AllianceIdentityVerifier; +use impls::{AllianceProposalProvider, Author, CreditToBlockAuthor}; /// Constant values used within the runtime. pub mod constants; @@ -1506,6 +1508,67 @@ impl pallet_state_trie_migration::Config for Runtime { type WeightInfo = (); } +parameter_types! { + pub const AllianceMotionDuration: BlockNumber = 5 * DAYS; + pub const AllianceMaxProposals: u32 = 100; + pub const AllianceMaxMembers: u32 = 100; +} + +type AllianceCollective = pallet_collective::Instance3; +impl pallet_collective::Config for Runtime { + type Origin = Origin; + type Proposal = Call; + type Event = Event; + type MotionDuration = AllianceMotionDuration; + type MaxProposals = AllianceMaxProposals; + type MaxMembers = AllianceMaxMembers; + type DefaultVote = pallet_collective::PrimeDefaultVote; + type WeightInfo = pallet_collective::weights::SubstrateWeight; +} + +parameter_types! { + pub const MaxFounders: u32 = 10; + pub const MaxFellows: u32 = AllianceMaxMembers::get() - MaxFounders::get(); + pub const MaxAllies: u32 = 100; + pub const AllyDeposit: Balance = 10 * DOLLARS; +} + +impl pallet_alliance::Config for Runtime { + type Event = Event; + type Proposal = Call; + type AdminOrigin = EitherOfDiverse< + EnsureRoot, + pallet_collective::EnsureProportionMoreThan, + >; + type MembershipManager = EitherOfDiverse< + EnsureRoot, + pallet_collective::EnsureProportionMoreThan, + >; + type AnnouncementOrigin = EitherOfDiverse< + EnsureRoot, + pallet_collective::EnsureProportionMoreThan, + >; + type Currency = Balances; + type Slashed = Treasury; + type InitializeMembers = AllianceMotion; + type MembershipChanged = AllianceMotion; + #[cfg(not(feature = "runtime-benchmarks"))] + type IdentityVerifier = AllianceIdentityVerifier; + #[cfg(feature = "runtime-benchmarks")] + type IdentityVerifier = (); + type ProposalProvider = AllianceProposalProvider; + type MaxProposals = AllianceMaxProposals; + type MaxFounders = MaxFounders; + type MaxFellows = MaxFellows; + type MaxAllies = MaxAllies; + type MaxUnscrupulousItems = ConstU32<100>; + type MaxWebsiteUrlLength = ConstU32<255>; + type MaxAnnouncementsCount = ConstU32<100>; + type MaxMembersCount = AllianceMaxMembers; + type AllyDeposit = AllyDeposit; + type WeightInfo = pallet_alliance::weights::SubstrateWeight; +} + construct_runtime!( pub enum Runtime where Block = Block, @@ -1563,6 +1626,8 @@ construct_runtime!( Remark: pallet_remark, ConvictionVoting: pallet_conviction_voting, Whitelist: pallet_whitelist, + AllianceMotion: pallet_collective::, + Alliance: pallet_alliance, NominationPools: pallet_nomination_pools, RankedPolls: pallet_referenda::, RankedCollective: pallet_ranked_collective, @@ -2049,7 +2114,6 @@ impl_runtime_apis! { let mut batches = Vec::::new(); let params = (&config, &whitelist); add_benchmarks!(params, batches); - Ok(batches) } } diff --git a/bin/node/testing/src/genesis.rs b/bin/node/testing/src/genesis.rs index 73e7ea50261d3..fbd28c5af0298 100644 --- a/bin/node/testing/src/genesis.rs +++ b/bin/node/testing/src/genesis.rs @@ -92,6 +92,8 @@ pub fn config_endowed(code: Option<&[u8]>, extra_endowed: Vec) -> Gen gilt: Default::default(), transaction_storage: Default::default(), transaction_payment: Default::default(), + alliance: Default::default(), + alliance_motion: Default::default(), nomination_pools: Default::default(), } } diff --git a/frame/alliance/Cargo.toml b/frame/alliance/Cargo.toml new file mode 100644 index 0000000000000..706827708ce88 --- /dev/null +++ b/frame/alliance/Cargo.toml @@ -0,0 +1,68 @@ +[package] +name = "pallet-alliance" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://docs.substrate.io/" +repository = "https://github.com/paritytech/substrate/" +description = "The Alliance pallet provides a collective for standard-setting industry collaboration." +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +hex = { version = "0.4", default-features = false, features = ["alloc"], optional = true } +sha2 = { version = "0.10.1", default-features = false, optional = true } +log = { version = "0.4.14", default-features = false } + +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } + +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } + +frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } + +pallet-identity = { version = "4.0.0-dev", path = "../identity", default-features = false } +pallet-collective = { version = "4.0.0-dev", path = "../collective", default-features = false, optional = true } + +[dev-dependencies] +hex-literal = "0.3.1" +sha2 = "0.10.1" +pallet-balances = { version = "4.0.0-dev", path = "../balances" } +pallet-collective = { version = "4.0.0-dev", path = "../collective" } + +[features] +default = ["std"] +std = [ + "log/std", + "codec/std", + "scale-info/std", + "sp-std/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "frame-support/std", + "frame-system/std", + "pallet-identity/std", +] +runtime-benchmarks = [ + "hex", + "sha2", + "frame-benchmarking", + "sp-runtime/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-collective/runtime-benchmarks", + "pallet-identity/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", +] diff --git a/frame/alliance/README.md b/frame/alliance/README.md new file mode 100644 index 0000000000000..f0900c84cbd85 --- /dev/null +++ b/frame/alliance/README.md @@ -0,0 +1,68 @@ +# Alliance Pallet + +The Alliance Pallet provides a collective that curates a list of accounts and URLs, deemed by +the voting members to be unscrupulous actors. The alliance + +- provides a set of ethics against bad behavior, and +- provides recognition and influence for those teams that contribute something back to the + ecosystem. + +## Overview + +The network initializes the Alliance via a Root call. After that, anyone with an approved +identity and website can join as an Ally. The `MembershipManager` origin can elevate Allies to +Fellows, giving them voting rights within the Alliance. + +Voting members of the Alliance maintain a list of accounts and websites. Members can also vote +to update the Alliance's rule and make announcements. + +### Terminology + +- Rule: The IPFS CID (hash) of the Alliance rules for the community to read and the Alliance + members to enforce. Similar to a Code of Conduct. +- Announcement: An IPFS CID of some content that the Alliance want to announce. +- Member: An account that is already in the group of the Alliance, including three types: + Founder, Fellow, or Ally. A member can also be kicked by the `MembershipManager` origin or + retire by itself. +- Founder: An account who is initiated by Root with normal voting rights for basic motions and + special veto rights for rule change and Ally elevation motions. +- Fellow: An account who is elevated from Ally by Founders and other Fellows. +- Ally: An account who would like to join the alliance. To become a voting member, Fellow or + Founder, it will need approval from the `MembershipManager` origin. Any account can join as an + Ally either by placing a deposit or by nomination from a voting member. +- Unscrupulous List: A list of bad websites and addresses, items can be added or removed by + Founders and Fellows. + +## Interface + +### Dispatchable Functions + +#### For General Users + +- `join_alliance` - Join the Alliance as an Ally. This requires a slashable deposit. + +#### For Members (All) + +- `retire` - Retire from the Alliance and release the caller's deposit. + +#### For Members (Founders/Fellows) + +- `propose` - Propose a motion. +- `vote` - Vote on a motion. +- `close` - Close a motion with enough votes or that has expired. +- `set_rule` - Initialize or update the Alliance's rule by IPFS CID. +- `announce` - Make announcement by IPFS CID. +- `nominate_ally` - Nominate a non-member to become an Ally, without deposit. +- `elevate_ally` - Approve an ally to become a Fellow. +- `kick_member` - Kick a member and slash its deposit. +- `add_unscrupulous_items` - Add some items, either accounts or websites, to the list of + unscrupulous items. +- `remove_unscrupulous_items` - Remove some items from the list of unscrupulous items. + +#### For Members (Only Founders) + +- `veto` - Veto on a motion about `set_rule` and `elevate_ally`. + +#### Root Calls + +- `init_founders` - Initialize the founding members. diff --git a/frame/alliance/src/benchmarking.rs b/frame/alliance/src/benchmarking.rs new file mode 100644 index 0000000000000..527c35b58a5d8 --- /dev/null +++ b/frame/alliance/src/benchmarking.rs @@ -0,0 +1,791 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Alliance pallet benchmarking. + +use sp_runtime::traits::{Bounded, Hash, StaticLookup}; +use sp_std::{ + convert::{TryFrom, TryInto}, + mem::size_of, + prelude::*, +}; + +use frame_benchmarking::{account, benchmarks_instance_pallet}; +use frame_support::traits::{EnsureOrigin, Get, UnfilteredDispatchable}; +use frame_system::{Pallet as System, RawOrigin as SystemOrigin}; + +use super::{Call as AllianceCall, Pallet as Alliance, *}; + +const SEED: u32 = 0; + +const MAX_BYTES: u32 = 1_024; + +fn assert_last_event, I: 'static>(generic_event: >::Event) { + frame_system::Pallet::::assert_last_event(generic_event.into()); +} + +fn cid(input: impl AsRef<[u8]>) -> Cid { + use sha2::{Digest, Sha256}; + let mut hasher = Sha256::new(); + hasher.update(input); + let result = hasher.finalize(); + Cid::new_v0(&*result) +} + +fn rule(input: impl AsRef<[u8]>) -> Cid { + cid(input) +} + +fn announcement(input: impl AsRef<[u8]>) -> Cid { + cid(input) +} + +fn funded_account, I: 'static>(name: &'static str, index: u32) -> T::AccountId { + let account: T::AccountId = account(name, index, SEED); + T::Currency::make_free_balance_be(&account, BalanceOf::::max_value() / 100u8.into()); + account +} + +fn founder, I: 'static>(index: u32) -> T::AccountId { + funded_account::("founder", index) +} + +fn fellow, I: 'static>(index: u32) -> T::AccountId { + funded_account::("fellow", index) +} + +fn ally, I: 'static>(index: u32) -> T::AccountId { + funded_account::("ally", index) +} + +fn outsider, I: 'static>(index: u32) -> T::AccountId { + funded_account::("outsider", index) +} + +fn generate_unscrupulous_account, I: 'static>(index: u32) -> T::AccountId { + funded_account::("unscrupulous", index) +} + +fn set_members, I: 'static>() { + let founders: BoundedVec<_, T::MaxMembersCount> = + BoundedVec::try_from(vec![founder::(1), founder::(2)]).unwrap(); + Members::::insert(MemberRole::Founder, founders.clone()); + + let fellows: BoundedVec<_, T::MaxMembersCount> = + BoundedVec::try_from(vec![fellow::(1), fellow::(2)]).unwrap(); + fellows.iter().for_each(|who| { + T::Currency::reserve(&who, T::AllyDeposit::get()).unwrap(); + >::insert(&who, T::AllyDeposit::get()); + }); + Members::::insert(MemberRole::Fellow, fellows.clone()); + + let allies: BoundedVec<_, T::MaxMembersCount> = + BoundedVec::try_from(vec![ally::(1)]).unwrap(); + allies.iter().for_each(|who| { + T::Currency::reserve(&who, T::AllyDeposit::get()).unwrap(); + >::insert(&who, T::AllyDeposit::get()); + }); + Members::::insert(MemberRole::Ally, allies); + + T::InitializeMembers::initialize_members(&[founders.as_slice(), fellows.as_slice()].concat()); +} + +benchmarks_instance_pallet! { + // This tests when proposal is created and queued as "proposed" + propose_proposed { + let b in 1 .. MAX_BYTES; + let x in 2 .. T::MaxFounders::get(); + let y in 0 .. T::MaxFellows::get(); + let p in 1 .. T::MaxProposals::get(); + + let m = x + y; + + let bytes_in_storage = b + size_of::() as u32 + 32; + + // Construct `members`. + let founders = (0 .. x).map(founder::).collect::>(); + let proposer = founders[0].clone(); + let fellows = (0 .. y).map(fellow::).collect::>(); + + Alliance::::init_members(SystemOrigin::Root.into(), founders, fellows, vec![])?; + + let threshold = m; + // Add previous proposals. + for i in 0 .. p - 1 { + // Proposals should be different so that different proposal hashes are generated + let proposal: T::Proposal = AllianceCall::::set_rule { + rule: rule(vec![i as u8; b as usize]) + }.into(); + Alliance::::propose( + SystemOrigin::Signed(proposer.clone()).into(), + threshold, + Box::new(proposal), + bytes_in_storage, + )?; + } + + let proposal: T::Proposal = AllianceCall::::set_rule { rule: rule(vec![p as u8; b as usize]) }.into(); + + }: propose(SystemOrigin::Signed(proposer.clone()), threshold, Box::new(proposal.clone()), bytes_in_storage) + verify { + // New proposal is recorded + let proposal_hash = T::Hashing::hash_of(&proposal); + assert_eq!(T::ProposalProvider::proposal_of(proposal_hash), Some(proposal)); + } + + vote { + // We choose 5 (3 founders + 2 fellows) as a minimum so we always trigger a vote in the voting loop (`for j in ...`) + let x in 3 .. T::MaxFounders::get(); + let y in 2 .. T::MaxFellows::get(); + + let m = x + y; + + let p = T::MaxProposals::get(); + let b = MAX_BYTES; + let bytes_in_storage = b + size_of::() as u32 + 32; + + // Construct `members`. + let founders = (0 .. x).map(founder::).collect::>(); + let proposer = founders[0].clone(); + let fellows = (0 .. y).map(fellow::).collect::>(); + + let mut members = Vec::with_capacity(founders.len() + fellows.len()); + members.extend(founders.clone()); + members.extend(fellows.clone()); + + Alliance::::init_members(SystemOrigin::Root.into(), founders, fellows, vec![])?; + + // Threshold is 1 less than the number of members so that one person can vote nay + let threshold = m - 1; + + // Add previous proposals + let mut last_hash = T::Hash::default(); + for i in 0 .. p { + // Proposals should be different so that different proposal hashes are generated + let proposal: T::Proposal = AllianceCall::::set_rule { + rule: rule(vec![i as u8; b as usize]) + }.into(); + Alliance::::propose( + SystemOrigin::Signed(proposer.clone()).into(), + threshold, + Box::new(proposal.clone()), + b, + )?; + last_hash = T::Hashing::hash_of(&proposal); + } + + let index = p - 1; + // Have almost everyone vote aye on last proposal, while keeping it from passing. + for j in 0 .. m - 3 { + let voter = &members[j as usize]; + Alliance::::vote( + SystemOrigin::Signed(voter.clone()).into(), + last_hash.clone(), + index, + true, + )?; + } + + let voter = members[m as usize - 3].clone(); + // Voter votes aye without resolving the vote. + Alliance::::vote( + SystemOrigin::Signed(voter.clone()).into(), + last_hash.clone(), + index, + true, + )?; + + // Voter switches vote to nay, but does not kill the vote, just updates + inserts + let approve = false; + + // Whitelist voter account from further DB operations. + let voter_key = frame_system::Account::::hashed_key_for(&voter); + frame_benchmarking::benchmarking::add_to_whitelist(voter_key.into()); + }: _(SystemOrigin::Signed(voter), last_hash.clone(), index, approve) + verify { + } + + veto { + let p in 1 .. T::MaxProposals::get(); + + let m = 3; + let b = MAX_BYTES; + let bytes_in_storage = b + size_of::() as u32 + 32; + + // Construct `members`. + let founders = (0 .. m).map(founder::).collect::>(); + let vetor = founders[0].clone(); + + Alliance::::init_members(SystemOrigin::Root.into(), founders, vec![], vec![])?; + + // Threshold is one less than total members so that two nays will disapprove the vote + let threshold = m - 1; + + // Add proposals + let mut last_hash = T::Hash::default(); + for i in 0 .. p { + // Proposals should be different so that different proposal hashes are generated + let proposal: T::Proposal = AllianceCall::::set_rule { + rule: rule(vec![i as u8; b as usize]) + }.into(); + Alliance::::propose( + SystemOrigin::Signed(vetor.clone()).into(), + threshold, + Box::new(proposal.clone()), + bytes_in_storage, + )?; + last_hash = T::Hashing::hash_of(&proposal); + } + + }: _(SystemOrigin::Signed(vetor), last_hash.clone()) + verify { + // The proposal is removed + assert_eq!(T::ProposalProvider::proposal_of(last_hash), None); + } + + close_early_disapproved { + // We choose 4 (2 founders + 2 fellows) as a minimum so we always trigger a vote in the voting loop (`for j in ...`) + let x in 2 .. T::MaxFounders::get(); + let y in 2 .. T::MaxFellows::get(); + let p in 1 .. T::MaxProposals::get(); + + let m = x + y; + + let bytes = 100; + let bytes_in_storage = bytes + size_of::() as u32 + 32; + + // Construct `members`. + let founders = (0 .. x).map(founder::).collect::>(); + let fellows = (0 .. y).map(fellow::).collect::>(); + + let mut members = Vec::with_capacity(founders.len() + fellows.len()); + members.extend(founders.clone()); + members.extend(fellows.clone()); + + Alliance::::init_members(SystemOrigin::Root.into(), founders, fellows, vec![])?; + + let proposer = members[0].clone(); + let voter = members[1].clone(); + + // Threshold is total members so that one nay will disapprove the vote + let threshold = m; + + // Add previous proposals + let mut last_hash = T::Hash::default(); + for i in 0 .. p { + // Proposals should be different so that different proposal hashes are generated + let proposal: T::Proposal = AllianceCall::::set_rule { + rule: rule(vec![i as u8; bytes as usize]) + }.into(); + Alliance::::propose( + SystemOrigin::Signed(proposer.clone()).into(), + threshold, + Box::new(proposal.clone()), + bytes_in_storage, + )?; + last_hash = T::Hashing::hash_of(&proposal); + assert_eq!(T::ProposalProvider::proposal_of(last_hash), Some(proposal)); + } + + let index = p - 1; + // Have most everyone vote aye on last proposal, while keeping it from passing. + for j in 2 .. m - 1 { + let voter = &members[j as usize]; + Alliance::::vote( + SystemOrigin::Signed(voter.clone()).into(), + last_hash.clone(), + index, + true, + )?; + } + + // Voter votes aye without resolving the vote. + Alliance::::vote( + SystemOrigin::Signed(voter.clone()).into(), + last_hash.clone(), + index, + true, + )?; + + // Voter switches vote to nay, which kills the vote + Alliance::::vote( + SystemOrigin::Signed(voter.clone()).into(), + last_hash.clone(), + index, + false, + )?; + + // Whitelist voter account from further DB operations. + let voter_key = frame_system::Account::::hashed_key_for(&voter); + frame_benchmarking::benchmarking::add_to_whitelist(voter_key.into()); + }: close(SystemOrigin::Signed(voter), last_hash.clone(), index, Weight::max_value(), bytes_in_storage) + verify { + // The last proposal is removed. + assert_eq!(T::ProposalProvider::proposal_of(last_hash), None); + } + + close_early_approved { + let b in 1 .. MAX_BYTES; + // We choose 4 (2 founders + 2 fellows) as a minimum so we always trigger a vote in the voting loop (`for j in ...`) + let x in 2 .. T::MaxFounders::get(); + let y in 2 .. T::MaxFellows::get(); + let p in 1 .. T::MaxProposals::get(); + + let m = x + y; + let bytes_in_storage = b + size_of::() as u32 + 32; + + // Construct `members`. + let founders = (0 .. x).map(founder::).collect::>(); + let fellows = (0 .. y).map(fellow::).collect::>(); + + let mut members = Vec::with_capacity(founders.len() + fellows.len()); + members.extend(founders.clone()); + members.extend(fellows.clone()); + + Alliance::::init_members(SystemOrigin::Root.into(), founders, fellows, vec![])?; + + let proposer = members[0].clone(); + let voter = members[1].clone(); + + // Threshold is 2 so any two ayes will approve the vote + let threshold = 2; + + // Add previous proposals + let mut last_hash = T::Hash::default(); + for i in 0 .. p { + // Proposals should be different so that different proposal hashes are generated + let proposal: T::Proposal = AllianceCall::::set_rule { + rule: rule(vec![i as u8; b as usize]) + }.into(); + Alliance::::propose( + SystemOrigin::Signed(proposer.clone()).into(), + threshold, + Box::new(proposal.clone()), + bytes_in_storage, + )?; + last_hash = T::Hashing::hash_of(&proposal); + assert_eq!(T::ProposalProvider::proposal_of(last_hash), Some(proposal)); + } + + let index = p - 1; + // Caller switches vote to nay on their own proposal, allowing them to be the deciding approval vote + Alliance::::vote( + SystemOrigin::Signed(proposer.clone()).into(), + last_hash.clone(), + index, + false, + )?; + + // Have almost everyone vote nay on last proposal, while keeping it from failing. + for j in 2 .. m - 1 { + let voter = &members[j as usize]; + Alliance::::vote( + SystemOrigin::Signed(voter.clone()).into(), + last_hash.clone(), + index, + false, + )?; + } + + // Member zero is the first aye + Alliance::::vote( + SystemOrigin::Signed(members[0].clone()).into(), + last_hash.clone(), + index, + true, + )?; + + let voter = members[1].clone(); + // Caller switches vote to aye, which passes the vote + Alliance::::vote( + SystemOrigin::Signed(voter.clone()).into(), + last_hash.clone(), + index, + true, + )?; + }: close(SystemOrigin::Signed(voter), last_hash.clone(), index, Weight::max_value(), bytes_in_storage) + verify { + // The last proposal is removed. + assert_eq!(T::ProposalProvider::proposal_of(last_hash), None); + } + + close_disapproved { + // We choose 2 (2 founders / 2 fellows) as a minimum so we always trigger a vote in the voting loop (`for j in ...`) + let x in 2 .. T::MaxFounders::get(); + let y in 2 .. T::MaxFellows::get(); + let p in 1 .. T::MaxProposals::get(); + + let m = x + y; + + let bytes = 100; + let bytes_in_storage = bytes + size_of::() as u32 + 32; + + // Construct `members`. + let founders = (0 .. x).map(founder::).collect::>(); + let fellows = (0 .. y).map(fellow::).collect::>(); + + let mut members = Vec::with_capacity(founders.len() + fellows.len()); + members.extend(founders.clone()); + members.extend(fellows.clone()); + + Alliance::::init_members(SystemOrigin::Root.into(), founders, fellows, vec![])?; + + let proposer = members[0].clone(); + let voter = members[1].clone(); + + // Threshold is one less than total members so that two nays will disapprove the vote + let threshold = m - 1; + + // Add proposals + let mut last_hash = T::Hash::default(); + for i in 0 .. p { + // Proposals should be different so that different proposal hashes are generated + let proposal: T::Proposal = AllianceCall::::set_rule { + rule: rule(vec![i as u8; bytes as usize]) + }.into(); + Alliance::::propose( + SystemOrigin::Signed(proposer.clone()).into(), + threshold, + Box::new(proposal.clone()), + bytes_in_storage, + )?; + last_hash = T::Hashing::hash_of(&proposal); + assert_eq!(T::ProposalProvider::proposal_of(last_hash), Some(proposal)); + } + + let index = p - 1; + // Have almost everyone vote aye on last proposal, while keeping it from passing. + // A few abstainers will be the nay votes needed to fail the vote. + for j in 2 .. m - 1 { + let voter = &members[j as usize]; + Alliance::::vote( + SystemOrigin::Signed(voter.clone()).into(), + last_hash.clone(), + index, + true, + )?; + } + + Alliance::::vote( + SystemOrigin::Signed(voter.clone()).into(), + last_hash.clone(), + index, + false, + )?; + + System::::set_block_number(T::BlockNumber::max_value()); + + }: close(SystemOrigin::Signed(voter), last_hash.clone(), index, Weight::max_value(), bytes_in_storage) + verify { + // The last proposal is removed. + assert_eq!(T::ProposalProvider::proposal_of(last_hash), None); + } + + close_approved { + let b in 1 .. MAX_BYTES; + // We choose 4 (2 founders + 2 fellows) as a minimum so we always trigger a vote in the voting loop (`for j in ...`) + let x in 2 .. T::MaxFounders::get(); + let y in 2 .. T::MaxFellows::get(); + let p in 1 .. T::MaxProposals::get(); + + let m = x + y; + let bytes_in_storage = b + size_of::() as u32 + 32; + + // Construct `members`. + let founders = (0 .. x).map(founder::).collect::>(); + let fellows = (0 .. y).map(fellow::).collect::>(); + + let mut members = Vec::with_capacity(founders.len() + fellows.len()); + members.extend(founders.clone()); + members.extend(fellows.clone()); + + Alliance::::init_members(SystemOrigin::Root.into(), founders, fellows, vec![])?; + + let proposer = members[0].clone(); + let voter = members[1].clone(); + + // Threshold is two, so any two ayes will pass the vote + let threshold = 2; + + // Add proposals + let mut last_hash = T::Hash::default(); + for i in 0 .. p { + // Proposals should be different so that different proposal hashes are generated + let proposal: T::Proposal = AllianceCall::::set_rule { + rule: rule(vec![i as u8; b as usize]) + }.into(); + Alliance::::propose( + SystemOrigin::Signed(proposer.clone()).into(), + threshold, + Box::new(proposal.clone()), + bytes_in_storage, + )?; + last_hash = T::Hashing::hash_of(&proposal); + assert_eq!(T::ProposalProvider::proposal_of(last_hash), Some(proposal)); + } + + // The prime member votes aye, so abstentions default to aye. + Alliance::::vote( + SystemOrigin::Signed(proposer.clone()).into(), + last_hash.clone(), + p - 1, + true // Vote aye. + )?; + + let index = p - 1; + // Have almost everyone vote nay on last proposal, while keeping it from failing. + // A few abstainers will be the aye votes needed to pass the vote. + for j in 2 .. m - 1 { + let voter = &members[j as usize]; + Alliance::::vote( + SystemOrigin::Signed(voter.clone()).into(), + last_hash.clone(), + index, + false + )?; + } + + // caller is prime, prime already votes aye by creating the proposal + System::::set_block_number(T::BlockNumber::max_value()); + + }: close(SystemOrigin::Signed(voter), last_hash.clone(), index, Weight::max_value(), bytes_in_storage) + verify { + // The last proposal is removed. + assert_eq!(T::ProposalProvider::proposal_of(last_hash), None); + } + + init_members { + // at least 2 founders + let x in 2 .. T::MaxFounders::get(); + let y in 0 .. T::MaxFellows::get(); + let z in 0 .. T::MaxAllies::get(); + + let mut founders = (2 .. x).map(founder::).collect::>(); + let mut fellows = (0 .. y).map(fellow::).collect::>(); + let mut allies = (0 .. z).map(ally::).collect::>(); + + }: _(SystemOrigin::Root, founders.clone(), fellows.clone(), allies.clone()) + verify { + founders.sort(); + fellows.sort(); + allies.sort(); + assert_last_event::(Event::MembersInitialized { + founders: founders.clone(), + fellows: fellows.clone(), + allies: allies.clone(), + }.into()); + assert_eq!(Alliance::::members(MemberRole::Founder), founders); + assert_eq!(Alliance::::members(MemberRole::Fellow), fellows); + assert_eq!(Alliance::::members(MemberRole::Ally), allies); + } + + set_rule { + set_members::(); + + let rule = rule(b"hello world"); + + let call = Call::::set_rule { rule: rule.clone() }; + let origin = T::AdminOrigin::successful_origin(); + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert_eq!(Alliance::::rule(), Some(rule.clone())); + assert_last_event::(Event::NewRuleSet { rule }.into()); + } + + announce { + set_members::(); + + let announcement = announcement(b"hello world"); + + let call = Call::::announce { announcement: announcement.clone() }; + let origin = T::AnnouncementOrigin::successful_origin(); + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert!(Alliance::::announcements().contains(&announcement)); + assert_last_event::(Event::Announced { announcement }.into()); + } + + remove_announcement { + set_members::(); + + let announcement = announcement(b"hello world"); + let announcements: BoundedVec<_, T::MaxAnnouncementsCount> = BoundedVec::try_from(vec![announcement.clone()]).unwrap(); + Announcements::::put(announcements); + + let call = Call::::remove_announcement { announcement: announcement.clone() }; + let origin = T::AnnouncementOrigin::successful_origin(); + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert!(Alliance::::announcements().is_empty()); + assert_last_event::(Event::AnnouncementRemoved { announcement }.into()); + } + + join_alliance { + set_members::(); + + let outsider = outsider::(1); + assert!(!Alliance::::is_member(&outsider)); + assert_eq!(DepositOf::::get(&outsider), None); + }: _(SystemOrigin::Signed(outsider.clone())) + verify { + assert!(Alliance::::is_member_of(&outsider, MemberRole::Ally)); // outsider is now an ally + assert_eq!(DepositOf::::get(&outsider), Some(T::AllyDeposit::get())); // with a deposit + assert!(!Alliance::::has_voting_rights(&outsider)); // allies don't have voting rights + assert_last_event::(Event::NewAllyJoined { + ally: outsider, + nominator: None, + reserved: Some(T::AllyDeposit::get()) + }.into()); + } + + nominate_ally { + set_members::(); + + let founder1 = founder::(1); + assert!(Alliance::::is_member_of(&founder1, MemberRole::Founder)); + + let outsider = outsider::(1); + assert!(!Alliance::::is_member(&outsider)); + assert_eq!(DepositOf::::get(&outsider), None); + + let outsider_lookup: ::Source = T::Lookup::unlookup(outsider.clone()); + }: _(SystemOrigin::Signed(founder1.clone()), outsider_lookup) + verify { + assert!(Alliance::::is_member_of(&outsider, MemberRole::Ally)); // outsider is now an ally + assert_eq!(DepositOf::::get(&outsider), None); // without a deposit + assert!(!Alliance::::has_voting_rights(&outsider)); // allies don't have voting rights + assert_last_event::(Event::NewAllyJoined { + ally: outsider, + nominator: Some(founder1), + reserved: None + }.into()); + } + + elevate_ally { + set_members::(); + + let ally1 = ally::(1); + assert!(Alliance::::is_ally(&ally1)); + + let ally1_lookup: ::Source = T::Lookup::unlookup(ally1.clone()); + let call = Call::::elevate_ally { ally: ally1_lookup }; + let origin = T::MembershipManager::successful_origin(); + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert!(!Alliance::::is_ally(&ally1)); + assert!(Alliance::::is_fellow(&ally1)); + assert_last_event::(Event::AllyElevated { ally: ally1 }.into()); + } + + retire { + set_members::(); + + let fellow2 = fellow::(2); + assert!(Alliance::::is_fellow(&fellow2)); + assert!(!Alliance::::is_up_for_kicking(&fellow2)); + + assert_eq!(DepositOf::::get(&fellow2), Some(T::AllyDeposit::get())); + }: _(SystemOrigin::Signed(fellow2.clone())) + verify { + assert!(!Alliance::::is_member(&fellow2)); + assert_eq!(DepositOf::::get(&fellow2), None); + assert_last_event::(Event::MemberRetired { + member: fellow2, + unreserved: Some(T::AllyDeposit::get()) + }.into()); + } + + kick_member { + set_members::(); + + let fellow2 = fellow::(2); + UpForKicking::::insert(&fellow2, true); + + assert!(Alliance::::is_member_of(&fellow2, MemberRole::Fellow)); + assert!(Alliance::::is_up_for_kicking(&fellow2)); + + assert_eq!(DepositOf::::get(&fellow2), Some(T::AllyDeposit::get())); + + let fellow2_lookup: ::Source = T::Lookup::unlookup(fellow2.clone()); + let call = Call::::kick_member { who: fellow2_lookup }; + let origin = T::MembershipManager::successful_origin(); + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert!(!Alliance::::is_member(&fellow2)); + assert_eq!(DepositOf::::get(&fellow2), None); + assert_last_event::(Event::MemberKicked { + member: fellow2, + slashed: Some(T::AllyDeposit::get()) + }.into()); + } + + add_unscrupulous_items { + let n in 1 .. T::MaxUnscrupulousItems::get(); + let l in 1 .. T::MaxWebsiteUrlLength::get(); + + set_members::(); + + let accounts = (0 .. n) + .map(|i| generate_unscrupulous_account::(i)) + .collect::>(); + let websites = (0 .. n).map(|i| -> BoundedVec { + BoundedVec::try_from(vec![i as u8; l as usize]).unwrap() + }).collect::>(); + + let mut unscrupulous_list = Vec::with_capacity(accounts.len() + websites.len()); + unscrupulous_list.extend(accounts.into_iter().map(UnscrupulousItem::AccountId)); + unscrupulous_list.extend(websites.into_iter().map(UnscrupulousItem::Website)); + + let call = Call::::add_unscrupulous_items { items: unscrupulous_list.clone() }; + let origin = T::AnnouncementOrigin::successful_origin(); + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert_last_event::(Event::UnscrupulousItemAdded { items: unscrupulous_list }.into()); + } + + remove_unscrupulous_items { + let n in 1 .. T::MaxUnscrupulousItems::get(); + let l in 1 .. T::MaxWebsiteUrlLength::get(); + + set_members::(); + + let mut accounts = (0 .. n) + .map(|i| generate_unscrupulous_account::(i)) + .collect::>(); + accounts.sort(); + let accounts: BoundedVec<_, T::MaxUnscrupulousItems> = accounts.try_into().unwrap(); + UnscrupulousAccounts::::put(accounts.clone()); + + let mut websites = (0 .. n).map(|i| -> BoundedVec<_, T::MaxWebsiteUrlLength> + { BoundedVec::try_from(vec![i as u8; l as usize]).unwrap() }).collect::>(); + websites.sort(); + let websites: BoundedVec<_, T::MaxUnscrupulousItems> = websites.try_into().unwrap(); + UnscrupulousWebsites::::put(websites.clone()); + + let mut unscrupulous_list = Vec::with_capacity(accounts.len() + websites.len()); + unscrupulous_list.extend(accounts.into_iter().map(UnscrupulousItem::AccountId)); + unscrupulous_list.extend(websites.into_iter().map(UnscrupulousItem::Website)); + + let call = Call::::remove_unscrupulous_items { items: unscrupulous_list.clone() }; + let origin = T::AnnouncementOrigin::successful_origin(); + }: { call.dispatch_bypass_filter(origin)? } + verify { + assert_last_event::(Event::UnscrupulousItemRemoved { items: unscrupulous_list }.into()); + } + + impl_benchmark_test_suite!(Alliance, crate::mock::new_bench_ext(), crate::mock::Test); +} diff --git a/frame/alliance/src/lib.rs b/frame/alliance/src/lib.rs new file mode 100644 index 0000000000000..f9e85e270af16 --- /dev/null +++ b/frame/alliance/src/lib.rs @@ -0,0 +1,1053 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Alliance Pallet +//! +//! The Alliance Pallet provides a collective that curates a list of accounts and URLs, deemed by +//! the voting members to be unscrupulous actors. The alliance +//! +//! - provides a set of ethics against bad behavior, and +//! - provides recognition and influence for those teams that contribute something back to the +//! ecosystem. +//! +//! ## Overview +//! +//! The network initializes the Alliance via a Root call. After that, anyone with an approved +//! identity and website can join as an Ally. The `MembershipManager` origin can elevate Allies to +//! Fellows, giving them voting rights within the Alliance. +//! +//! Voting members of the Alliance maintain a list of accounts and websites. Members can also vote +//! to update the Alliance's rule and make announcements. +//! +//! ### Terminology +//! +//! - Rule: The IPFS CID (hash) of the Alliance rules for the community to read and the Alliance +//! members to enforce. Similar to a Code of Conduct. +//! - Announcement: An IPFS CID of some content that the Alliance want to announce. +//! - Member: An account that is already in the group of the Alliance, including three types: +//! Founder, Fellow, or Ally. A member can also be kicked by the `MembershipManager` origin or +//! retire by itself. +//! - Founder: An account who is initiated by Root with normal voting rights for basic motions and +//! special veto rights for rule change and Ally elevation motions. +//! - Fellow: An account who is elevated from Ally by Founders and other Fellows. +//! - Ally: An account who would like to join the alliance. To become a voting member, Fellow or +//! Founder, it will need approval from the `MembershipManager` origin. Any account can join as an +//! Ally either by placing a deposit or by nomination from a voting member. +//! - Unscrupulous List: A list of bad websites and addresses, items can be added or removed by +//! Founders and Fellows. +//! +//! ## Interface +//! +//! ### Dispatchable Functions +//! +//! #### For General Users +//! +//! - `join_alliance` - Join the Alliance as an Ally. This requires a slashable deposit. +//! +//! #### For Members (All) +//! +//! - `retire` - Retire from the Alliance and release the caller's deposit. +//! +//! #### For Members (Founders/Fellows) +//! +//! - `propose` - Propose a motion. +//! - `vote` - Vote on a motion. +//! - `close` - Close a motion with enough votes or that has expired. +//! - `set_rule` - Initialize or update the Alliance's rule by IPFS CID. +//! - `announce` - Make announcement by IPFS CID. +//! - `nominate_ally` - Nominate a non-member to become an Ally, without deposit. +//! - `elevate_ally` - Approve an ally to become a Fellow. +//! - `kick_member` - Kick a member and slash its deposit. +//! - `add_unscrupulous_items` - Add some items, either accounts or websites, to the list of +//! unscrupulous items. +//! - `remove_unscrupulous_items` - Remove some items from the list of unscrupulous items. +//! +//! #### For Members (Only Founders) +//! +//! - `veto` - Veto on a motion about `set_rule` and `elevate_ally`. +//! +//! #### Root Calls +//! +//! - `init_founders` - Initialize the founding members. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +mod types; +pub mod weights; + +use frame_support::pallet_prelude::*; +use frame_system::pallet_prelude::*; +use sp_runtime::{ + traits::{StaticLookup, Zero}, + RuntimeDebug, +}; +use sp_std::{convert::TryInto, prelude::*}; + +use frame_support::{ + codec::{Decode, Encode, MaxEncodedLen}, + dispatch::{ + DispatchError, DispatchResult, DispatchResultWithPostInfo, Dispatchable, GetDispatchInfo, + PostDispatchInfo, + }, + ensure, + scale_info::TypeInfo, + traits::{ + ChangeMembers, Currency, Get, InitializeMembers, IsSubType, OnUnbalanced, + ReservableCurrency, + }, + weights::Weight, +}; +use pallet_identity::IdentityField; + +pub use pallet::*; +pub use types::*; +pub use weights::*; + +/// Simple index type for proposal counting. +pub type ProposalIndex = u32; + +type UrlOf = BoundedVec>::MaxWebsiteUrlLength>; + +type BalanceOf = + <>::Currency as Currency<::AccountId>>::Balance; +type NegativeImbalanceOf = <>::Currency as Currency< + ::AccountId, +>>::NegativeImbalance; + +/// Interface required for identity verification. +pub trait IdentityVerifier { + /// Function that returns whether an account has an identity registered with the identity + /// provider. + fn has_identity(who: &AccountId, fields: u64) -> bool; + + /// Whether an account has been deemed "good" by the provider. + fn has_good_judgement(who: &AccountId) -> bool; + + /// If the identity provider allows sub-accounts, provide the super of an account. Should + /// return `None` if the provider does not allow sub-accounts or if the account is not a sub. + fn super_account_id(who: &AccountId) -> Option; +} + +/// The non-provider. Imposes no restrictions on account identity. +impl IdentityVerifier for () { + fn has_identity(_who: &AccountId, _fields: u64) -> bool { + true + } + + fn has_good_judgement(_who: &AccountId) -> bool { + true + } + + fn super_account_id(_who: &AccountId) -> Option { + None + } +} + +/// The provider of a collective action interface, for example an instance of `pallet-collective`. +pub trait ProposalProvider { + fn propose_proposal( + who: AccountId, + threshold: u32, + proposal: Box, + length_bound: u32, + ) -> Result<(u32, u32), DispatchError>; + + fn vote_proposal( + who: AccountId, + proposal: Hash, + index: ProposalIndex, + approve: bool, + ) -> Result; + + fn veto_proposal(proposal_hash: Hash) -> u32; + + fn close_proposal( + proposal_hash: Hash, + index: ProposalIndex, + proposal_weight_bound: Weight, + length_bound: u32, + ) -> DispatchResultWithPostInfo; + + fn proposal_of(proposal_hash: Hash) -> Option; +} + +/// The various roles that a member can hold. +#[derive(Copy, Clone, PartialEq, Eq, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)] +pub enum MemberRole { + Founder, + Fellow, + Ally, +} + +/// The type of item that may be deemed unscrupulous. +#[derive(Clone, PartialEq, Eq, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)] +pub enum UnscrupulousItem { + AccountId(AccountId), + Website(Url), +} + +type UnscrupulousItemOf = + UnscrupulousItem<::AccountId, UrlOf>; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + #[pallet::pallet] + #[pallet::generate_store(pub (super) trait Store)] + pub struct Pallet(PhantomData<(T, I)>); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type Event: From> + IsType<::Event>; + + /// The outer call dispatch type. + type Proposal: Parameter + + Dispatchable + + From> + + From> + + GetDispatchInfo + + IsSubType> + + IsType<::Call>; + + /// Origin for admin-level operations, like setting the Alliance's rules. + type AdminOrigin: EnsureOrigin; + + /// Origin that manages entry and forcible discharge from the Alliance. + type MembershipManager: EnsureOrigin; + + /// Origin for making announcements and adding/removing unscrupulous items. + type AnnouncementOrigin: EnsureOrigin; + + /// The currency used for deposits. + type Currency: ReservableCurrency; + + /// What to do with slashed funds. + type Slashed: OnUnbalanced>; + + /// What to do with initial voting members of the Alliance. + type InitializeMembers: InitializeMembers; + + /// What to do when a member has been added or removed. + type MembershipChanged: ChangeMembers; + + /// The identity verifier of an Alliance member. + type IdentityVerifier: IdentityVerifier; + + /// The provider of the proposal operation. + type ProposalProvider: ProposalProvider; + + /// Maximum number of proposals allowed to be active in parallel. + type MaxProposals: Get; + + /// The maximum number of founders supported by the pallet. Used for weight estimation. + /// + /// NOTE: + /// + Benchmarks will need to be re-run and weights adjusted if this changes. + /// + This pallet assumes that dependencies keep to the limit without enforcing it. + type MaxFounders: Get; + + /// The maximum number of fellows supported by the pallet. Used for weight estimation. + /// + /// NOTE: + /// + Benchmarks will need to be re-run and weights adjusted if this changes. + /// + This pallet assumes that dependencies keep to the limit without enforcing it. + type MaxFellows: Get; + + /// The maximum number of allies supported by the pallet. Used for weight estimation. + /// + /// NOTE: + /// + Benchmarks will need to be re-run and weights adjusted if this changes. + /// + This pallet assumes that dependencies keep to the limit without enforcing it. + type MaxAllies: Get; + + /// The maximum number of the unscrupulous items supported by the pallet. + #[pallet::constant] + type MaxUnscrupulousItems: Get; + + /// The maximum length of a website URL. + #[pallet::constant] + type MaxWebsiteUrlLength: Get; + + /// The deposit required for submitting candidacy. + #[pallet::constant] + type AllyDeposit: Get>; + + /// The maximum number of announcements. + #[pallet::constant] + type MaxAnnouncementsCount: Get; + + /// The maximum number of members per member role. Should not exceed the sum of + /// `MaxFounders` and `MaxFellows`. + #[pallet::constant] + type MaxMembersCount: Get; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + } + + #[pallet::error] + pub enum Error { + /// The founders/fellows/allies have already been initialized. + MembersAlreadyInitialized, + /// Account is already a member. + AlreadyMember, + /// Account is not a member. + NotMember, + /// Account is not an ally. + NotAlly, + /// Account is not a founder. + NotFounder, + /// This member is up for being kicked from the Alliance and cannot perform this operation. + UpForKicking, + /// Account does not have voting rights. + NoVotingRights, + /// Account is already an elevated (fellow) member. + AlreadyElevated, + /// Item is already listed as unscrupulous. + AlreadyUnscrupulous, + /// Account has been deemed unscrupulous by the Alliance and is not welcome to join or be + /// nominated. + AccountNonGrata, + /// Item has not been deemed unscrupulous. + NotListedAsUnscrupulous, + /// The number of unscrupulous items exceeds `MaxUnscrupulousItems`. + TooManyUnscrupulousItems, + /// Length of website URL exceeds `MaxWebsiteUrlLength`. + TooLongWebsiteUrl, + /// Balance is insufficient for the required deposit. + InsufficientFunds, + /// The account's identity does not have display field and website field. + WithoutIdentityDisplayAndWebsite, + /// The account's identity has no good judgement. + WithoutGoodIdentityJudgement, + /// The proposal hash is not found. + MissingProposalHash, + /// The proposal is not vetoable. + NotVetoableProposal, + /// The announcement is not found. + MissingAnnouncement, + /// Number of members exceeds `MaxMembersCount`. + TooManyMembers, + /// Number of announcements exceeds `MaxAnnouncementsCount`. + TooManyAnnouncements, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event, I: 'static = ()> { + /// A new rule has been set. + NewRuleSet { rule: Cid }, + /// A new announcement has been proposed. + Announced { announcement: Cid }, + /// An on-chain announcement has been removed. + AnnouncementRemoved { announcement: Cid }, + /// Some accounts have been initialized as members (founders/fellows/allies). + MembersInitialized { + founders: Vec, + fellows: Vec, + allies: Vec, + }, + /// An account has been added as an Ally and reserved its deposit. + NewAllyJoined { + ally: T::AccountId, + nominator: Option, + reserved: Option>, + }, + /// An ally has been elevated to Fellow. + AllyElevated { ally: T::AccountId }, + /// A member has retired with its deposit unreserved. + MemberRetired { member: T::AccountId, unreserved: Option> }, + /// A member has been kicked out with its deposit slashed. + MemberKicked { member: T::AccountId, slashed: Option> }, + /// Accounts or websites have been added into the list of unscrupulous items. + UnscrupulousItemAdded { items: Vec> }, + /// Accounts or websites have been removed from the list of unscrupulous items. + UnscrupulousItemRemoved { items: Vec> }, + } + + #[pallet::genesis_config] + pub struct GenesisConfig, I: 'static = ()> { + pub founders: Vec, + pub fellows: Vec, + pub allies: Vec, + pub phantom: PhantomData<(T, I)>, + } + + #[cfg(feature = "std")] + impl, I: 'static> Default for GenesisConfig { + fn default() -> Self { + Self { + founders: Vec::new(), + fellows: Vec::new(), + allies: Vec::new(), + phantom: Default::default(), + } + } + } + + #[pallet::genesis_build] + impl, I: 'static> GenesisBuild for GenesisConfig { + fn build(&self) { + for m in self.founders.iter().chain(self.fellows.iter()).chain(self.allies.iter()) { + assert!(Pallet::::has_identity(m).is_ok(), "Member does not set identity!"); + } + + if !self.founders.is_empty() { + assert!( + !Pallet::::has_member(MemberRole::Founder), + "Founders are already initialized!" + ); + let members: BoundedVec = + self.founders.clone().try_into().expect("Too many genesis founders"); + Members::::insert(MemberRole::Founder, members); + } + if !self.fellows.is_empty() { + assert!( + !Pallet::::has_member(MemberRole::Fellow), + "Fellows are already initialized!" + ); + let members: BoundedVec = + self.fellows.clone().try_into().expect("Too many genesis fellows"); + Members::::insert(MemberRole::Fellow, members); + } + if !self.allies.is_empty() { + let members: BoundedVec = + self.allies.clone().try_into().expect("Too many genesis allies"); + Members::::insert(MemberRole::Ally, members); + } + + T::InitializeMembers::initialize_members( + &[self.founders.as_slice(), self.fellows.as_slice()].concat(), + ) + } + } + + /// The IPFS CID of the alliance rule. + /// Founders and fellows can propose a new rule with a super-majority. + /// + /// Any founder has a special one-vote veto right to the rule setting. + #[pallet::storage] + #[pallet::getter(fn rule)] + pub type Rule, I: 'static = ()> = StorageValue<_, Cid, OptionQuery>; + + /// The current IPFS CIDs of any announcements. + #[pallet::storage] + #[pallet::getter(fn announcements)] + pub type Announcements, I: 'static = ()> = + StorageValue<_, BoundedVec, ValueQuery>; + + /// Maps members to their candidacy deposit. + #[pallet::storage] + #[pallet::getter(fn deposit_of)] + pub type DepositOf, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, T::AccountId, BalanceOf, OptionQuery>; + + /// Maps member type to members of each type. + #[pallet::storage] + #[pallet::getter(fn members)] + pub type Members, I: 'static = ()> = StorageMap< + _, + Twox64Concat, + MemberRole, + BoundedVec, + ValueQuery, + >; + + /// A set of members that are (potentially) being kicked out. They cannot retire until the + /// motion is settled. + #[pallet::storage] + #[pallet::getter(fn up_for_kicking)] + pub type UpForKicking, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, T::AccountId, bool, ValueQuery>; + + /// The current list of accounts deemed unscrupulous. These accounts non grata cannot submit + /// candidacy. + #[pallet::storage] + #[pallet::getter(fn unscrupulous_accounts)] + pub type UnscrupulousAccounts, I: 'static = ()> = + StorageValue<_, BoundedVec, ValueQuery>; + + /// The current list of websites deemed unscrupulous. + #[pallet::storage] + #[pallet::getter(fn unscrupulous_websites)] + pub type UnscrupulousWebsites, I: 'static = ()> = + StorageValue<_, BoundedVec, T::MaxUnscrupulousItems>, ValueQuery>; + + #[pallet::call] + impl, I: 'static> Pallet { + /// Add a new proposal to be voted on. + /// + /// Requires the sender to be a founder or fellow. + #[pallet::weight(T::WeightInfo::propose_proposed( + *length_bound, // B + T::MaxFounders::get(), // X + T::MaxFellows::get(), // Y + T::MaxProposals::get(), // P2 + ))] + pub fn propose( + origin: OriginFor, + #[pallet::compact] threshold: u32, + proposal: Box<>::Proposal>, + #[pallet::compact] length_bound: u32, + ) -> DispatchResult { + let proposor = ensure_signed(origin)?; + ensure!(Self::has_voting_rights(&proposor), Error::::NoVotingRights); + + if let Some(Call::kick_member { who }) = proposal.is_sub_type() { + let strike = T::Lookup::lookup(who.clone())?; + >::insert(strike, true); + } + + T::ProposalProvider::propose_proposal(proposor, threshold, proposal, length_bound)?; + Ok(()) + } + + /// Add an aye or nay vote for the sender to the given proposal. + /// + /// Requires the sender to be a founder or fellow. + #[pallet::weight(T::WeightInfo::vote(T::MaxFounders::get(), T::MaxFellows::get()))] + pub fn vote( + origin: OriginFor, + proposal: T::Hash, + #[pallet::compact] index: ProposalIndex, + approve: bool, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + ensure!(Self::has_voting_rights(&who), Error::::NoVotingRights); + + T::ProposalProvider::vote_proposal(who, proposal, index, approve)?; + Ok(()) + } + + /// Veto a proposal about `set_rule` and `elevate_ally`, close, and remove it from the + /// system, regardless of its current state. + /// + /// Must be called by a founder. + #[pallet::weight(T::WeightInfo::veto(T::MaxProposals::get()))] + pub fn veto(origin: OriginFor, proposal_hash: T::Hash) -> DispatchResult { + let proposor = ensure_signed(origin)?; + ensure!(Self::is_founder(&proposor), Error::::NotFounder); + + let proposal = T::ProposalProvider::proposal_of(proposal_hash) + .ok_or(Error::::MissingProposalHash)?; + match proposal.is_sub_type() { + Some(Call::set_rule { .. }) | Some(Call::elevate_ally { .. }) => { + T::ProposalProvider::veto_proposal(proposal_hash); + Ok(()) + }, + _ => Err(Error::::NotVetoableProposal.into()), + } + } + + /// Close a vote that is either approved, disapproved, or whose voting period has ended. + /// + /// Requires the sender to be a founder or fellow. + #[pallet::weight({ + let b = *length_bound; + let x = T::MaxFounders::get(); + let y = T::MaxFellows::get(); + let p1 = *proposal_weight_bound; + let p2 = T::MaxProposals::get(); + T::WeightInfo::close_early_approved(b, x, y, p2) + .max(T::WeightInfo::close_early_disapproved(x, y, p2)) + .max(T::WeightInfo::close_approved(b, x, y, p2)) + .max(T::WeightInfo::close_disapproved(x, y, p2)) + .saturating_add(p1) + })] + pub fn close( + origin: OriginFor, + proposal_hash: T::Hash, + #[pallet::compact] index: ProposalIndex, + #[pallet::compact] proposal_weight_bound: Weight, + #[pallet::compact] length_bound: u32, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + ensure!(Self::has_voting_rights(&who), Error::::NoVotingRights); + + let info = T::ProposalProvider::close_proposal( + proposal_hash, + index, + proposal_weight_bound, + length_bound, + )?; + Ok(info.into()) + } + + /// Initialize the founders, fellows, and allies. + /// + /// This should only be called once, and must be called by the Root origin. + #[pallet::weight(T::WeightInfo::init_members( + T::MaxFounders::get(), + T::MaxFellows::get(), + T::MaxAllies::get() + ))] + pub fn init_members( + origin: OriginFor, + founders: Vec, + fellows: Vec, + allies: Vec, + ) -> DispatchResult { + ensure_root(origin)?; + + let mut founders: BoundedVec = + founders.try_into().map_err(|_| Error::::TooManyMembers)?; + let mut fellows: BoundedVec = + fellows.try_into().map_err(|_| Error::::TooManyMembers)?; + let mut allies: BoundedVec = + allies.try_into().map_err(|_| Error::::TooManyMembers)?; + + ensure!( + !Self::has_member(MemberRole::Founder) && + !Self::has_member(MemberRole::Fellow) && + !Self::has_member(MemberRole::Ally), + Error::::MembersAlreadyInitialized + ); + for member in founders.iter().chain(fellows.iter()).chain(allies.iter()) { + Self::has_identity(member)?; + } + + founders.sort(); + Members::::insert(&MemberRole::Founder, founders.clone()); + fellows.sort(); + Members::::insert(&MemberRole::Fellow, fellows.clone()); + allies.sort(); + Members::::insert(&MemberRole::Ally, allies.clone()); + + let mut voteable_members = Vec::with_capacity(founders.len() + fellows.len()); + voteable_members.extend(founders.clone()); + voteable_members.extend(fellows.clone()); + voteable_members.sort(); + + T::InitializeMembers::initialize_members(&voteable_members); + + log::debug!( + target: "runtime::alliance", + "Initialize alliance founders: {:?}, fellows: {:?}, allies: {:?}", + founders, fellows, allies + ); + + Self::deposit_event(Event::MembersInitialized { + founders: founders.into(), + fellows: fellows.into(), + allies: allies.into(), + }); + Ok(()) + } + + /// Set a new IPFS CID to the alliance rule. + #[pallet::weight(T::WeightInfo::set_rule())] + pub fn set_rule(origin: OriginFor, rule: Cid) -> DispatchResult { + T::AdminOrigin::ensure_origin(origin)?; + + Rule::::put(&rule); + + Self::deposit_event(Event::NewRuleSet { rule }); + Ok(()) + } + + /// Make an announcement of a new IPFS CID about alliance issues. + #[pallet::weight(T::WeightInfo::announce())] + pub fn announce(origin: OriginFor, announcement: Cid) -> DispatchResult { + T::AnnouncementOrigin::ensure_origin(origin)?; + + let mut announcements = >::get(); + announcements + .try_push(announcement.clone()) + .map_err(|_| Error::::TooManyAnnouncements)?; + >::put(announcements); + + Self::deposit_event(Event::Announced { announcement }); + Ok(()) + } + + /// Remove an announcement. + #[pallet::weight(T::WeightInfo::remove_announcement())] + pub fn remove_announcement(origin: OriginFor, announcement: Cid) -> DispatchResult { + T::AnnouncementOrigin::ensure_origin(origin)?; + + let mut announcements = >::get(); + let pos = announcements + .binary_search(&announcement) + .ok() + .ok_or(Error::::MissingAnnouncement)?; + announcements.remove(pos); + >::put(announcements); + + Self::deposit_event(Event::AnnouncementRemoved { announcement }); + Ok(()) + } + + /// Submit oneself for candidacy. A fixed deposit is reserved. + #[pallet::weight(T::WeightInfo::join_alliance())] + pub fn join_alliance(origin: OriginFor) -> DispatchResult { + let who = ensure_signed(origin)?; + + // Unscrupulous accounts are non grata. + ensure!(!Self::is_unscrupulous_account(&who), Error::::AccountNonGrata); + ensure!(!Self::is_member(&who), Error::::AlreadyMember); + // check user self or parent should has verified identity to reuse display name and + // website. + Self::has_identity(&who)?; + + let deposit = T::AllyDeposit::get(); + T::Currency::reserve(&who, deposit).map_err(|_| Error::::InsufficientFunds)?; + >::insert(&who, deposit); + + Self::add_member(&who, MemberRole::Ally)?; + + Self::deposit_event(Event::NewAllyJoined { + ally: who, + nominator: None, + reserved: Some(deposit), + }); + Ok(()) + } + + /// A founder or fellow can nominate someone to join the alliance as an Ally. + /// There is no deposit required to the nominator or nominee. + #[pallet::weight(T::WeightInfo::nominate_ally())] + pub fn nominate_ally( + origin: OriginFor, + who: ::Source, + ) -> DispatchResult { + let nominator = ensure_signed(origin)?; + ensure!(Self::has_voting_rights(&nominator), Error::::NoVotingRights); + let who = T::Lookup::lookup(who)?; + + // Individual voting members cannot nominate accounts non grata. + ensure!(!Self::is_unscrupulous_account(&who), Error::::AccountNonGrata); + ensure!(!Self::is_member(&who), Error::::AlreadyMember); + // check user self or parent should has verified identity to reuse display name and + // website. + Self::has_identity(&who)?; + + Self::add_member(&who, MemberRole::Ally)?; + + Self::deposit_event(Event::NewAllyJoined { + ally: who, + nominator: Some(nominator), + reserved: None, + }); + Ok(()) + } + + /// Elevate an ally to fellow. + #[pallet::weight(T::WeightInfo::elevate_ally())] + pub fn elevate_ally( + origin: OriginFor, + ally: ::Source, + ) -> DispatchResult { + T::MembershipManager::ensure_origin(origin)?; + let ally = T::Lookup::lookup(ally)?; + ensure!(Self::is_ally(&ally), Error::::NotAlly); + ensure!(!Self::has_voting_rights(&ally), Error::::AlreadyElevated); + + Self::remove_member(&ally, MemberRole::Ally)?; + Self::add_member(&ally, MemberRole::Fellow)?; + + Self::deposit_event(Event::AllyElevated { ally }); + Ok(()) + } + + /// As a member, retire from the alliance and unreserve the deposit. + #[pallet::weight(T::WeightInfo::retire())] + pub fn retire(origin: OriginFor) -> DispatchResult { + let who = ensure_signed(origin)?; + // A member up for kicking cannot retire. + ensure!(!Self::is_up_for_kicking(&who), Error::::UpForKicking); + + let role = Self::member_role_of(&who).ok_or(Error::::NotMember)?; + Self::remove_member(&who, role)?; + let deposit = DepositOf::::take(&who); + if let Some(deposit) = deposit { + let err_amount = T::Currency::unreserve(&who, deposit); + debug_assert!(err_amount.is_zero()); + } + Self::deposit_event(Event::MemberRetired { member: who, unreserved: deposit }); + Ok(()) + } + + /// Kick a member from the alliance and slash its deposit. + #[pallet::weight(T::WeightInfo::kick_member())] + pub fn kick_member( + origin: OriginFor, + who: ::Source, + ) -> DispatchResult { + T::MembershipManager::ensure_origin(origin)?; + let member = T::Lookup::lookup(who)?; + + let role = Self::member_role_of(&member).ok_or(Error::::NotMember)?; + Self::remove_member(&member, role)?; + let deposit = DepositOf::::take(member.clone()); + if let Some(deposit) = deposit { + T::Slashed::on_unbalanced(T::Currency::slash_reserved(&member, deposit).0); + } + + >::remove(&member); + + Self::deposit_event(Event::MemberKicked { member, slashed: deposit }); + Ok(()) + } + + /// Add accounts or websites to the list of unscrupulous items. + #[pallet::weight(T::WeightInfo::add_unscrupulous_items(items.len() as u32, T::MaxWebsiteUrlLength::get()))] + pub fn add_unscrupulous_items( + origin: OriginFor, + items: Vec>, + ) -> DispatchResult { + T::AnnouncementOrigin::ensure_origin(origin)?; + + let mut accounts = vec![]; + let mut webs = vec![]; + for info in items.iter() { + ensure!(!Self::is_unscrupulous(info), Error::::AlreadyUnscrupulous); + match info { + UnscrupulousItem::AccountId(who) => accounts.push(who.clone()), + UnscrupulousItem::Website(url) => { + ensure!( + url.len() as u32 <= T::MaxWebsiteUrlLength::get(), + Error::::TooLongWebsiteUrl + ); + webs.push(url.clone()); + }, + } + } + + Self::do_add_unscrupulous_items(&mut accounts, &mut webs)?; + Self::deposit_event(Event::UnscrupulousItemAdded { items }); + Ok(()) + } + + /// Deem an item no longer unscrupulous. + #[pallet::weight(>::WeightInfo::remove_unscrupulous_items( + items.len() as u32, T::MaxWebsiteUrlLength::get() + ))] + pub fn remove_unscrupulous_items( + origin: OriginFor, + items: Vec>, + ) -> DispatchResult { + T::AnnouncementOrigin::ensure_origin(origin)?; + let mut accounts = vec![]; + let mut webs = vec![]; + for info in items.iter() { + ensure!(Self::is_unscrupulous(info), Error::::NotListedAsUnscrupulous); + match info { + UnscrupulousItem::AccountId(who) => accounts.push(who.clone()), + UnscrupulousItem::Website(url) => webs.push(url.clone()), + } + } + Self::do_remove_unscrupulous_items(&mut accounts, &mut webs)?; + Self::deposit_event(Event::UnscrupulousItemRemoved { items }); + Ok(()) + } + } +} + +impl, I: 'static> Pallet { + /// Check if a given role has any members. + fn has_member(role: MemberRole) -> bool { + Members::::decode_len(role).unwrap_or_default() > 0 + } + + /// Look up the role, if any, of an account. + fn member_role_of(who: &T::AccountId) -> Option { + Members::::iter() + .find_map(|(r, members)| if members.contains(who) { Some(r) } else { None }) + } + + /// Check if a user is a alliance member. + pub fn is_member(who: &T::AccountId) -> bool { + Self::member_role_of(who).is_some() + } + + /// Check if an account has a given role. + pub fn is_member_of(who: &T::AccountId, role: MemberRole) -> bool { + Members::::get(role).contains(&who) + } + + /// Check if an account is a founder. + fn is_founder(who: &T::AccountId) -> bool { + Self::is_member_of(who, MemberRole::Founder) + } + + /// Check if an account is a fellow. + fn is_fellow(who: &T::AccountId) -> bool { + Self::is_member_of(who, MemberRole::Fellow) + } + + /// Check if an account is an ally. + fn is_ally(who: &T::AccountId) -> bool { + Self::is_member_of(who, MemberRole::Ally) + } + + /// Check if a member has voting rights. + fn has_voting_rights(who: &T::AccountId) -> bool { + Self::is_founder(who) || Self::is_fellow(who) + } + + /// Collect all members who have voting rights into one list. + fn votable_members_sorted() -> Vec { + let mut founders = Members::::get(MemberRole::Founder).into_inner(); + let mut fellows = Members::::get(MemberRole::Fellow).into_inner(); + founders.append(&mut fellows); + founders.sort(); + founders.into() + } + + /// Check if an account's forced removal is up for consideration. + fn is_up_for_kicking(who: &T::AccountId) -> bool { + >::contains_key(&who) + } + + /// Add a user to the sorted alliance member set. + fn add_member(who: &T::AccountId, role: MemberRole) -> DispatchResult { + >::try_mutate(role, |members| -> DispatchResult { + let pos = members.binary_search(who).err().ok_or(Error::::AlreadyMember)?; + members + .try_insert(pos, who.clone()) + .map_err(|_| Error::::TooManyMembers)?; + Ok(()) + })?; + + if role == MemberRole::Founder || role == MemberRole::Fellow { + let members = Self::votable_members_sorted(); + T::MembershipChanged::change_members_sorted(&[who.clone()], &[], &members[..]); + } + Ok(()) + } + + /// Remove a user from the alliance member set. + fn remove_member(who: &T::AccountId, role: MemberRole) -> DispatchResult { + >::try_mutate(role, |members| -> DispatchResult { + let pos = members.binary_search(who).ok().ok_or(Error::::NotMember)?; + members.remove(pos); + Ok(()) + })?; + + if matches!(role, MemberRole::Founder | MemberRole::Fellow) { + let members = Self::votable_members_sorted(); + T::MembershipChanged::change_members_sorted(&[], &[who.clone()], &members[..]); + } + Ok(()) + } + + /// Check if an item is listed as unscrupulous. + fn is_unscrupulous(info: &UnscrupulousItemOf) -> bool { + match info { + UnscrupulousItem::Website(url) => >::get().contains(url), + UnscrupulousItem::AccountId(who) => >::get().contains(who), + } + } + + /// Check if an account is listed as unscrupulous. + fn is_unscrupulous_account(who: &T::AccountId) -> bool { + >::get().contains(who) + } + + /// Add item to the unscrupulous list. + fn do_add_unscrupulous_items( + new_accounts: &mut Vec, + new_webs: &mut Vec>, + ) -> DispatchResult { + if !new_accounts.is_empty() { + >::try_mutate(|accounts| -> DispatchResult { + accounts + .try_append(new_accounts) + .map_err(|_| Error::::TooManyUnscrupulousItems)?; + accounts.sort(); + + Ok(()) + })?; + } + if !new_webs.is_empty() { + >::try_mutate(|webs| -> DispatchResult { + webs.try_append(new_webs).map_err(|_| Error::::TooManyUnscrupulousItems)?; + webs.sort(); + + Ok(()) + })?; + } + + Ok(()) + } + + /// Remove item from the unscrupulous list. + fn do_remove_unscrupulous_items( + out_accounts: &mut Vec, + out_webs: &mut Vec>, + ) -> DispatchResult { + if !out_accounts.is_empty() { + >::try_mutate(|accounts| -> DispatchResult { + for who in out_accounts.iter() { + let pos = accounts + .binary_search(who) + .ok() + .ok_or(Error::::NotListedAsUnscrupulous)?; + accounts.remove(pos); + } + Ok(()) + })?; + } + if !out_webs.is_empty() { + >::try_mutate(|webs| -> DispatchResult { + for web in out_webs.iter() { + let pos = webs + .binary_search(web) + .ok() + .ok_or(Error::::NotListedAsUnscrupulous)?; + webs.remove(pos); + } + Ok(()) + })?; + } + Ok(()) + } + + fn has_identity(who: &T::AccountId) -> DispatchResult { + const IDENTITY_FIELD_DISPLAY: u64 = IdentityField::Display as u64; + const IDENTITY_FIELD_WEB: u64 = IdentityField::Web as u64; + + let judgement = |who: &T::AccountId| -> DispatchResult { + ensure!( + T::IdentityVerifier::has_identity(who, IDENTITY_FIELD_DISPLAY | IDENTITY_FIELD_WEB), + Error::::WithoutIdentityDisplayAndWebsite + ); + ensure!( + T::IdentityVerifier::has_good_judgement(who), + Error::::WithoutGoodIdentityJudgement + ); + Ok(()) + }; + + let res = judgement(who); + if res.is_err() { + if let Some(parent) = T::IdentityVerifier::super_account_id(who) { + return judgement(&parent) + } + } + res + } +} diff --git a/frame/alliance/src/mock.rs b/frame/alliance/src/mock.rs new file mode 100644 index 0000000000000..d6e9a92a10dec --- /dev/null +++ b/frame/alliance/src/mock.rs @@ -0,0 +1,324 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Test utilities + +pub use sp_core::H256; +pub use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, +}; +use sp_std::convert::{TryFrom, TryInto}; + +pub use frame_support::{ + assert_ok, ord_parameter_types, parameter_types, + traits::{EitherOfDiverse, GenesisBuild, SortedMembers}, + BoundedVec, +}; +use frame_system::{EnsureRoot, EnsureSignedBy}; +use pallet_identity::{Data, IdentityInfo, Judgement}; + +pub use crate as pallet_alliance; + +use super::*; + +parameter_types! { + pub const BlockHashCount: u64 = 250; +} +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type Origin = Origin; + type Call = Call; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = BlockHashCount; + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +parameter_types! { + pub const ExistentialDeposit: u64 = 1; + pub const MaxLocks: u32 = 10; +} +impl pallet_balances::Config for Test { + type Balance = u64; + type DustRemoval = (); + type Event = Event; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = MaxLocks; + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; +} + +parameter_types! { + pub const MotionDuration: u64 = 3; + pub const MaxProposals: u32 = 100; + pub const MaxMembers: u32 = 100; +} +type AllianceCollective = pallet_collective::Instance1; +impl pallet_collective::Config for Test { + type Origin = Origin; + type Proposal = Call; + type Event = Event; + type MotionDuration = MotionDuration; + type MaxProposals = MaxProposals; + type MaxMembers = MaxMembers; + type DefaultVote = pallet_collective::PrimeDefaultVote; + type WeightInfo = (); +} + +parameter_types! { + pub const BasicDeposit: u64 = 10; + pub const FieldDeposit: u64 = 10; + pub const SubAccountDeposit: u64 = 10; + pub const MaxSubAccounts: u32 = 2; + pub const MaxAdditionalFields: u32 = 2; + pub const MaxRegistrars: u32 = 20; +} +ord_parameter_types! { + pub const One: u64 = 1; + pub const Two: u64 = 2; + pub const Three: u64 = 3; + pub const Four: u64 = 4; + pub const Five: u64 = 5; +} +type EnsureOneOrRoot = EitherOfDiverse, EnsureSignedBy>; +type EnsureTwoOrRoot = EitherOfDiverse, EnsureSignedBy>; + +impl pallet_identity::Config for Test { + type Event = Event; + type Currency = Balances; + type BasicDeposit = BasicDeposit; + type FieldDeposit = FieldDeposit; + type SubAccountDeposit = SubAccountDeposit; + type MaxSubAccounts = MaxSubAccounts; + type MaxAdditionalFields = MaxAdditionalFields; + type MaxRegistrars = MaxRegistrars; + type Slashed = (); + type RegistrarOrigin = EnsureOneOrRoot; + type ForceOrigin = EnsureTwoOrRoot; + type WeightInfo = (); +} + +pub struct AllianceIdentityVerifier; +impl IdentityVerifier for AllianceIdentityVerifier { + fn has_identity(who: &u64, fields: u64) -> bool { + Identity::has_identity(who, fields) + } + + fn has_good_judgement(who: &u64) -> bool { + if let Some(judgements) = + Identity::identity(who).map(|registration| registration.judgements) + { + judgements + .iter() + .any(|(_, j)| matches!(j, Judgement::KnownGood | Judgement::Reasonable)) + } else { + false + } + } + + fn super_account_id(who: &u64) -> Option { + Identity::super_of(who).map(|parent| parent.0) + } +} + +pub struct AllianceProposalProvider; +impl ProposalProvider for AllianceProposalProvider { + fn propose_proposal( + who: u64, + threshold: u32, + proposal: Box, + length_bound: u32, + ) -> Result<(u32, u32), DispatchError> { + AllianceMotion::do_propose_proposed(who, threshold, proposal, length_bound) + } + + fn vote_proposal( + who: u64, + proposal: H256, + index: ProposalIndex, + approve: bool, + ) -> Result { + AllianceMotion::do_vote(who, proposal, index, approve) + } + + fn veto_proposal(proposal_hash: H256) -> u32 { + AllianceMotion::do_disapprove_proposal(proposal_hash) + } + + fn close_proposal( + proposal_hash: H256, + proposal_index: ProposalIndex, + proposal_weight_bound: Weight, + length_bound: u32, + ) -> DispatchResultWithPostInfo { + AllianceMotion::do_close(proposal_hash, proposal_index, proposal_weight_bound, length_bound) + } + + fn proposal_of(proposal_hash: H256) -> Option { + AllianceMotion::proposal_of(proposal_hash) + } +} + +parameter_types! { + pub const MaxFounders: u32 = 10; + pub const MaxFellows: u32 = MaxMembers::get() - MaxFounders::get(); + pub const MaxAllies: u32 = 100; + pub const AllyDeposit: u64 = 25; +} +impl Config for Test { + type Event = Event; + type Proposal = Call; + type AdminOrigin = EnsureSignedBy; + type MembershipManager = EnsureSignedBy; + type AnnouncementOrigin = EnsureSignedBy; + type Currency = Balances; + type Slashed = (); + type InitializeMembers = AllianceMotion; + type MembershipChanged = AllianceMotion; + #[cfg(not(feature = "runtime-benchmarks"))] + type IdentityVerifier = AllianceIdentityVerifier; + #[cfg(feature = "runtime-benchmarks")] + type IdentityVerifier = (); + type ProposalProvider = AllianceProposalProvider; + type MaxProposals = MaxProposals; + type MaxFounders = MaxFounders; + type MaxFellows = MaxFellows; + type MaxAllies = MaxAllies; + type MaxUnscrupulousItems = ConstU32<100>; + type MaxWebsiteUrlLength = ConstU32<255>; + type MaxAnnouncementsCount = ConstU32<100>; + type MaxMembersCount = MaxMembers; + type AllyDeposit = AllyDeposit; + type WeightInfo = (); +} + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system, + Balances: pallet_balances, + Identity: pallet_identity, + AllianceMotion: pallet_collective::, + Alliance: pallet_alliance, + } +); + +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + + pallet_balances::GenesisConfig:: { + balances: vec![(1, 50), (2, 50), (3, 50), (4, 50), (5, 30), (6, 50), (7, 50)], + } + .assimilate_storage(&mut t) + .unwrap(); + + GenesisBuild::::assimilate_storage( + &pallet_alliance::GenesisConfig { + founders: vec![], + fellows: vec![], + allies: vec![], + phantom: Default::default(), + }, + &mut t, + ) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + assert_ok!(Identity::add_registrar(Origin::signed(1), 1)); + + let info = IdentityInfo { + additional: BoundedVec::default(), + display: Data::Raw(b"name".to_vec().try_into().unwrap()), + legal: Data::default(), + web: Data::Raw(b"website".to_vec().try_into().unwrap()), + riot: Data::default(), + email: Data::default(), + pgp_fingerprint: None, + image: Data::default(), + twitter: Data::default(), + }; + assert_ok!(Identity::set_identity(Origin::signed(1), Box::new(info.clone()))); + assert_ok!(Identity::provide_judgement(Origin::signed(1), 0, 1, Judgement::KnownGood)); + assert_ok!(Identity::set_identity(Origin::signed(2), Box::new(info.clone()))); + assert_ok!(Identity::provide_judgement(Origin::signed(1), 0, 2, Judgement::KnownGood)); + assert_ok!(Identity::set_identity(Origin::signed(3), Box::new(info.clone()))); + assert_ok!(Identity::provide_judgement(Origin::signed(1), 0, 3, Judgement::KnownGood)); + assert_ok!(Identity::set_identity(Origin::signed(4), Box::new(info.clone()))); + assert_ok!(Identity::provide_judgement(Origin::signed(1), 0, 4, Judgement::KnownGood)); + assert_ok!(Identity::set_identity(Origin::signed(5), Box::new(info.clone()))); + assert_ok!(Identity::provide_judgement(Origin::signed(1), 0, 5, Judgement::KnownGood)); + assert_ok!(Identity::set_identity(Origin::signed(6), Box::new(info.clone()))); + + assert_ok!(Alliance::init_members(Origin::root(), vec![1, 2], vec![3], vec![])); + + System::set_block_number(1); + }); + ext +} + +#[cfg(feature = "runtime-benchmarks")] +pub fn new_bench_ext() -> sp_io::TestExternalities { + GenesisConfig::default().build_storage().unwrap().into() +} + +pub fn test_cid() -> Cid { + use sha2::{Digest, Sha256}; + let mut hasher = Sha256::new(); + hasher.update(b"hello world"); + let result = hasher.finalize(); + Cid::new_v0(&*result) +} + +pub fn make_proposal(value: u64) -> Call { + Call::System(frame_system::Call::remark { remark: value.encode() }) +} + +pub fn make_set_rule_proposal(rule: Cid) -> Call { + Call::Alliance(pallet_alliance::Call::set_rule { rule }) +} + +pub fn make_kick_member_proposal(who: u64) -> Call { + Call::Alliance(pallet_alliance::Call::kick_member { who }) +} diff --git a/frame/alliance/src/tests.rs b/frame/alliance/src/tests.rs new file mode 100644 index 0000000000000..85c91b451d351 --- /dev/null +++ b/frame/alliance/src/tests.rs @@ -0,0 +1,483 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Tests for the alliance pallet. + +use sp_runtime::traits::Hash; + +use frame_support::{assert_noop, assert_ok, Hashable}; +use frame_system::{EventRecord, Phase}; + +use super::*; +use crate::mock::*; + +type AllianceMotionEvent = pallet_collective::Event; + +#[test] +fn propose_works() { + new_test_ext().execute_with(|| { + let proposal = make_proposal(42); + let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); + let hash: H256 = proposal.blake2_256().into(); + + // only votable member can propose proposal, 4 is ally not have vote rights + assert_noop!( + Alliance::propose(Origin::signed(4), 3, Box::new(proposal.clone()), proposal_len), + Error::::NoVotingRights + ); + + assert_ok!(Alliance::propose( + Origin::signed(1), + 3, + Box::new(proposal.clone()), + proposal_len + )); + assert_eq!(*AllianceMotion::proposals(), vec![hash]); + assert_eq!(AllianceMotion::proposal_of(&hash), Some(proposal)); + assert_eq!( + System::events(), + vec![EventRecord { + phase: Phase::Initialization, + event: mock::Event::AllianceMotion(AllianceMotionEvent::Proposed { + account: 1, + proposal_index: 0, + proposal_hash: hash, + threshold: 3, + }), + topics: vec![], + }] + ); + }); +} + +#[test] +fn vote_works() { + new_test_ext().execute_with(|| { + let proposal = make_proposal(42); + let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); + let hash: H256 = proposal.blake2_256().into(); + assert_ok!(Alliance::propose( + Origin::signed(1), + 3, + Box::new(proposal.clone()), + proposal_len + )); + assert_ok!(Alliance::vote(Origin::signed(2), hash.clone(), 0, true)); + + let record = |event| EventRecord { phase: Phase::Initialization, event, topics: vec![] }; + assert_eq!( + System::events(), + vec![ + record(mock::Event::AllianceMotion(AllianceMotionEvent::Proposed { + account: 1, + proposal_index: 0, + proposal_hash: hash.clone(), + threshold: 3 + })), + record(mock::Event::AllianceMotion(AllianceMotionEvent::Voted { + account: 2, + proposal_hash: hash.clone(), + voted: true, + yes: 1, + no: 0, + })), + ] + ); + }); +} + +#[test] +fn veto_works() { + new_test_ext().execute_with(|| { + let proposal = make_proposal(42); + let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); + let hash: H256 = proposal.blake2_256().into(); + assert_ok!(Alliance::propose( + Origin::signed(1), + 3, + Box::new(proposal.clone()), + proposal_len + )); + // only set_rule/elevate_ally can be veto + assert_noop!( + Alliance::veto(Origin::signed(1), hash.clone()), + Error::::NotVetoableProposal + ); + + let cid = test_cid(); + let vetoable_proposal = make_set_rule_proposal(cid); + let vetoable_proposal_len: u32 = vetoable_proposal.using_encoded(|p| p.len() as u32); + let vetoable_hash: H256 = vetoable_proposal.blake2_256().into(); + assert_ok!(Alliance::propose( + Origin::signed(1), + 3, + Box::new(vetoable_proposal.clone()), + vetoable_proposal_len + )); + + // only founder have veto rights, 3 is fellow + assert_noop!( + Alliance::veto(Origin::signed(3), vetoable_hash.clone()), + Error::::NotFounder + ); + + assert_ok!(Alliance::veto(Origin::signed(2), vetoable_hash.clone())); + let record = |event| EventRecord { phase: Phase::Initialization, event, topics: vec![] }; + assert_eq!( + System::events(), + vec![ + record(mock::Event::AllianceMotion(AllianceMotionEvent::Proposed { + account: 1, + proposal_index: 0, + proposal_hash: hash.clone(), + threshold: 3 + })), + record(mock::Event::AllianceMotion(AllianceMotionEvent::Proposed { + account: 1, + proposal_index: 1, + proposal_hash: vetoable_hash.clone(), + threshold: 3 + })), + record(mock::Event::AllianceMotion(AllianceMotionEvent::Disapproved { + proposal_hash: vetoable_hash.clone() + })), + ] + ); + }) +} + +#[test] +fn close_works() { + new_test_ext().execute_with(|| { + let proposal = make_proposal(42); + let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); + let proposal_weight = proposal.get_dispatch_info().weight; + let hash = BlakeTwo256::hash_of(&proposal); + assert_ok!(Alliance::propose( + Origin::signed(1), + 3, + Box::new(proposal.clone()), + proposal_len + )); + assert_ok!(Alliance::vote(Origin::signed(1), hash.clone(), 0, true)); + assert_ok!(Alliance::vote(Origin::signed(2), hash.clone(), 0, true)); + assert_ok!(Alliance::vote(Origin::signed(3), hash.clone(), 0, true)); + assert_ok!(Alliance::close( + Origin::signed(1), + hash.clone(), + 0, + proposal_weight, + proposal_len + )); + + let record = |event| EventRecord { phase: Phase::Initialization, event, topics: vec![] }; + assert_eq!( + System::events(), + vec![ + record(mock::Event::AllianceMotion(AllianceMotionEvent::Proposed { + account: 1, + proposal_index: 0, + proposal_hash: hash.clone(), + threshold: 3 + })), + record(mock::Event::AllianceMotion(AllianceMotionEvent::Voted { + account: 1, + proposal_hash: hash.clone(), + voted: true, + yes: 1, + no: 0, + })), + record(mock::Event::AllianceMotion(AllianceMotionEvent::Voted { + account: 2, + proposal_hash: hash.clone(), + voted: true, + yes: 2, + no: 0, + })), + record(mock::Event::AllianceMotion(AllianceMotionEvent::Voted { + account: 3, + proposal_hash: hash.clone(), + voted: true, + yes: 3, + no: 0, + })), + record(mock::Event::AllianceMotion(AllianceMotionEvent::Closed { + proposal_hash: hash.clone(), + yes: 3, + no: 0, + })), + record(mock::Event::AllianceMotion(AllianceMotionEvent::Approved { + proposal_hash: hash.clone() + })), + record(mock::Event::AllianceMotion(AllianceMotionEvent::Executed { + proposal_hash: hash.clone(), + result: Err(DispatchError::BadOrigin), + })) + ] + ); + }); +} + +#[test] +fn set_rule_works() { + new_test_ext().execute_with(|| { + let cid = test_cid(); + assert_ok!(Alliance::set_rule(Origin::signed(1), cid.clone())); + assert_eq!(Alliance::rule(), Some(cid.clone())); + + System::assert_last_event(mock::Event::Alliance(crate::Event::NewRuleSet { rule: cid })); + }); +} + +#[test] +fn announce_works() { + new_test_ext().execute_with(|| { + let cid = test_cid(); + assert_ok!(Alliance::announce(Origin::signed(3), cid.clone())); + assert_eq!(Alliance::announcements(), vec![cid.clone()]); + + System::assert_last_event(mock::Event::Alliance(crate::Event::Announced { + announcement: cid, + })); + }); +} + +#[test] +fn remove_announcement_works() { + new_test_ext().execute_with(|| { + let cid = test_cid(); + assert_ok!(Alliance::announce(Origin::signed(3), cid.clone())); + assert_eq!(Alliance::announcements(), vec![cid.clone()]); + System::assert_last_event(mock::Event::Alliance(crate::Event::Announced { + announcement: cid.clone(), + })); + + System::set_block_number(2); + + assert_ok!(Alliance::remove_announcement(Origin::signed(3), cid.clone())); + assert_eq!(Alliance::announcements(), vec![]); + System::assert_last_event(mock::Event::Alliance(crate::Event::AnnouncementRemoved { + announcement: cid, + })); + }); +} + +#[test] +fn join_alliance_works() { + new_test_ext().execute_with(|| { + // check already member + assert_noop!(Alliance::join_alliance(Origin::signed(1)), Error::::AlreadyMember); + + // check already listed as unscrupulous + assert_ok!(Alliance::add_unscrupulous_items( + Origin::signed(3), + vec![UnscrupulousItem::AccountId(4)] + )); + assert_noop!( + Alliance::join_alliance(Origin::signed(4)), + Error::::AccountNonGrata + ); + assert_ok!(Alliance::remove_unscrupulous_items( + Origin::signed(3), + vec![UnscrupulousItem::AccountId(4)] + )); + + // check deposit funds + assert_noop!( + Alliance::join_alliance(Origin::signed(5)), + Error::::InsufficientFunds + ); + + // success to submit + assert_ok!(Alliance::join_alliance(Origin::signed(4))); + assert_eq!(Alliance::deposit_of(4), Some(25)); + assert_eq!(Alliance::members(MemberRole::Ally), vec![4]); + + // check already member + assert_noop!(Alliance::join_alliance(Origin::signed(4)), Error::::AlreadyMember); + + // check missing identity judgement + #[cfg(not(feature = "runtime-benchmarks"))] + assert_noop!( + Alliance::join_alliance(Origin::signed(6)), + Error::::WithoutGoodIdentityJudgement + ); + // check missing identity info + #[cfg(not(feature = "runtime-benchmarks"))] + assert_noop!( + Alliance::join_alliance(Origin::signed(7)), + Error::::WithoutIdentityDisplayAndWebsite + ); + }); +} + +#[test] +fn nominate_ally_works() { + new_test_ext().execute_with(|| { + // check already member + assert_noop!( + Alliance::nominate_ally(Origin::signed(1), 2), + Error::::AlreadyMember + ); + + // only votable member(founder/fellow) have nominate right + assert_noop!( + Alliance::nominate_ally(Origin::signed(5), 4), + Error::::NoVotingRights + ); + + // check already listed as unscrupulous + assert_ok!(Alliance::add_unscrupulous_items( + Origin::signed(3), + vec![UnscrupulousItem::AccountId(4)] + )); + assert_noop!( + Alliance::nominate_ally(Origin::signed(1), 4), + Error::::AccountNonGrata + ); + assert_ok!(Alliance::remove_unscrupulous_items( + Origin::signed(3), + vec![UnscrupulousItem::AccountId(4)] + )); + + // success to nominate + assert_ok!(Alliance::nominate_ally(Origin::signed(1), 4)); + assert_eq!(Alliance::deposit_of(4), None); + assert_eq!(Alliance::members(MemberRole::Ally), vec![4]); + + // check already member + assert_noop!( + Alliance::nominate_ally(Origin::signed(1), 4), + Error::::AlreadyMember + ); + + // check missing identity judgement + #[cfg(not(feature = "runtime-benchmarks"))] + assert_noop!( + Alliance::join_alliance(Origin::signed(6)), + Error::::WithoutGoodIdentityJudgement + ); + // check missing identity info + #[cfg(not(feature = "runtime-benchmarks"))] + assert_noop!( + Alliance::join_alliance(Origin::signed(7)), + Error::::WithoutIdentityDisplayAndWebsite + ); + }); +} + +#[test] +fn elevate_ally_works() { + new_test_ext().execute_with(|| { + assert_noop!(Alliance::elevate_ally(Origin::signed(2), 4), Error::::NotAlly); + + assert_ok!(Alliance::join_alliance(Origin::signed(4))); + assert_eq!(Alliance::members(MemberRole::Ally), vec![4]); + assert_eq!(Alliance::members(MemberRole::Fellow), vec![3]); + + assert_ok!(Alliance::elevate_ally(Origin::signed(2), 4)); + assert_eq!(Alliance::members(MemberRole::Ally), Vec::::new()); + assert_eq!(Alliance::members(MemberRole::Fellow), vec![3, 4]); + }); +} + +#[test] +fn retire_works() { + new_test_ext().execute_with(|| { + let proposal = make_kick_member_proposal(2); + let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); + assert_ok!(Alliance::propose( + Origin::signed(1), + 3, + Box::new(proposal.clone()), + proposal_len + )); + assert_noop!(Alliance::retire(Origin::signed(2)), Error::::UpForKicking); + + assert_noop!(Alliance::retire(Origin::signed(4)), Error::::NotMember); + + assert_eq!(Alliance::members(MemberRole::Fellow), vec![3]); + assert_ok!(Alliance::retire(Origin::signed(3))); + assert_eq!(Alliance::members(MemberRole::Fellow), Vec::::new()); + }); +} + +#[test] +fn kick_member_works() { + new_test_ext().execute_with(|| { + let proposal = make_kick_member_proposal(2); + let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); + assert_ok!(Alliance::propose( + Origin::signed(1), + 3, + Box::new(proposal.clone()), + proposal_len + )); + assert_eq!(Alliance::up_for_kicking(2), true); + assert_eq!(Alliance::members(MemberRole::Founder), vec![1, 2]); + + assert_ok!(Alliance::kick_member(Origin::signed(2), 2)); + assert_eq!(Alliance::members(MemberRole::Founder), vec![1]); + }); +} + +#[test] +fn add_unscrupulous_items_works() { + new_test_ext().execute_with(|| { + assert_ok!(Alliance::add_unscrupulous_items( + Origin::signed(3), + vec![ + UnscrupulousItem::AccountId(3), + UnscrupulousItem::Website("abc".as_bytes().to_vec().try_into().unwrap()) + ] + )); + assert_eq!(Alliance::unscrupulous_accounts().into_inner(), vec![3]); + assert_eq!(Alliance::unscrupulous_websites().into_inner(), vec!["abc".as_bytes().to_vec()]); + + assert_noop!( + Alliance::add_unscrupulous_items( + Origin::signed(3), + vec![UnscrupulousItem::AccountId(3)] + ), + Error::::AlreadyUnscrupulous + ); + }); +} + +#[test] +fn remove_unscrupulous_items_works() { + new_test_ext().execute_with(|| { + assert_noop!( + Alliance::remove_unscrupulous_items( + Origin::signed(3), + vec![UnscrupulousItem::AccountId(3)] + ), + Error::::NotListedAsUnscrupulous + ); + + assert_ok!(Alliance::add_unscrupulous_items( + Origin::signed(3), + vec![UnscrupulousItem::AccountId(3)] + )); + assert_eq!(Alliance::unscrupulous_accounts(), vec![3]); + assert_ok!(Alliance::remove_unscrupulous_items( + Origin::signed(3), + vec![UnscrupulousItem::AccountId(3)] + )); + assert_eq!(Alliance::unscrupulous_accounts(), Vec::::new()); + }); +} diff --git a/frame/alliance/src/types.rs b/frame/alliance/src/types.rs new file mode 100644 index 0000000000000..8fb0ae96fd02d --- /dev/null +++ b/frame/alliance/src/types.rs @@ -0,0 +1,95 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::{traits::ConstU32, BoundedVec}; +use scale_info::TypeInfo; +use sp_runtime::RuntimeDebug; +use sp_std::{convert::TryInto, prelude::*}; + +/// A Multihash instance that only supports the basic functionality and no hashing. +#[derive( + Clone, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen, +)] +pub struct Multihash { + /// The code of the Multihash. + pub code: u64, + /// The digest. + pub digest: BoundedVec>, // 4 byte dig size + 64 bytes hash digest +} + +impl Multihash { + /// Returns the size of the digest. + pub fn size(&self) -> usize { + self.digest.len() + } +} + +/// The version of the CID. +#[derive( + Clone, + Copy, + PartialEq, + Eq, + PartialOrd, + Ord, + RuntimeDebug, + Encode, + Decode, + TypeInfo, + MaxEncodedLen, +)] +pub enum Version { + /// CID version 0. + V0, + /// CID version 1. + V1, +} + +/// Representation of a CID. +/// +/// The generic is about the allocated size of the multihash. +#[derive( + Clone, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen, +)] +pub struct Cid { + /// The version of CID. + pub version: Version, + /// The codec of CID. + pub codec: u64, + /// The multihash of CID. + pub hash: Multihash, +} + +impl Cid { + /// Creates a new CIDv0. + pub fn new_v0(sha2_256_digest: impl Into>) -> Self { + /// DAG-PB multicodec code + const DAG_PB: u64 = 0x70; + /// The SHA_256 multicodec code + const SHA2_256: u64 = 0x12; + + let digest = sha2_256_digest.into(); + assert_eq!(digest.len(), 32); + + Self { + version: Version::V0, + codec: DAG_PB, + hash: Multihash { code: SHA2_256, digest: digest.try_into().expect("msg") }, + } + } +} diff --git a/frame/alliance/src/weights.rs b/frame/alliance/src/weights.rs new file mode 100644 index 0000000000000..495dd1b83df93 --- /dev/null +++ b/frame/alliance/src/weights.rs @@ -0,0 +1,485 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for pallet_alliance +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2021-10-11, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 128 + +// Executed Command: +// ./target/release/substrate +// benchmark +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_alliance +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./frame/alliance/src/weights.rs +// --template=./.maintain/frame-weight-template.hbs + + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_alliance. +pub trait WeightInfo { + fn propose_proposed(b: u32, x: u32, y: u32, p: u32, ) -> Weight; + fn vote(x: u32, y: u32, ) -> Weight; + fn veto(p: u32, ) -> Weight; + fn close_early_disapproved(x: u32, y: u32, p: u32, ) -> Weight; + fn close_early_approved(b: u32, x: u32, y: u32, p: u32, ) -> Weight; + fn close_disapproved(x: u32, y: u32, p: u32, ) -> Weight; + fn close_approved(b: u32, x: u32, y: u32, p: u32, ) -> Weight; + fn init_members(x: u32, y: u32, z: u32, ) -> Weight; + fn set_rule() -> Weight; + fn announce() -> Weight; + fn remove_announcement() -> Weight; + fn join_alliance() -> Weight; + fn nominate_ally() -> Weight; + fn elevate_ally() -> Weight; + fn retire() -> Weight; + fn kick_member() -> Weight; + fn add_unscrupulous_items(n: u32, l: u32, ) -> Weight; + fn remove_unscrupulous_items(n: u32, l: u32, ) -> Weight; +} + +/// Weights for pallet_alliance using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: Alliance Members (r:1 w:0) + // Storage: AllianceMotion ProposalOf (r:1 w:1) + // Storage: AllianceMotion Proposals (r:1 w:1) + // Storage: AllianceMotion ProposalCount (r:1 w:1) + // Storage: AllianceMotion Voting (r:0 w:1) + fn propose_proposed(_b: u32, _x: u32, y: u32, p: u32, ) -> Weight { + (39_992_000 as Weight) + // Standard Error: 2_000 + .saturating_add((44_000 as Weight).saturating_mul(y as Weight)) + // Standard Error: 2_000 + .saturating_add((323_000 as Weight).saturating_mul(p as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) + } + // Storage: Alliance Members (r:2 w:0) + // Storage: AllianceMotion Voting (r:1 w:1) + fn vote(x: u32, y: u32, ) -> Weight { + (36_649_000 as Weight) + // Standard Error: 90_000 + .saturating_add((42_000 as Weight).saturating_mul(x as Weight)) + // Standard Error: 3_000 + .saturating_add((195_000 as Weight).saturating_mul(y as Weight)) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Alliance Members (r:1 w:0) + // Storage: AllianceMotion ProposalOf (r:1 w:1) + // Storage: AllianceMotion Proposals (r:1 w:1) + // Storage: AllianceMotion Voting (r:0 w:1) + fn veto(p: u32, ) -> Weight { + (30_301_000 as Weight) + // Standard Error: 1_000 + .saturating_add((330_000 as Weight).saturating_mul(p as Weight)) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) + } + // Storage: Alliance Members (r:1 w:0) + // Storage: AllianceMotion ProposalOf (r:1 w:1) + // Storage: AllianceMotion Voting (r:1 w:1) + // Storage: AllianceMotion Members (r:1 w:0) + // Storage: AllianceMotion Proposals (r:1 w:1) + fn close_early_disapproved(x: u32, y: u32, p: u32, ) -> Weight { + (40_472_000 as Weight) + // Standard Error: 69_000 + .saturating_add((485_000 as Weight).saturating_mul(x as Weight)) + // Standard Error: 2_000 + .saturating_add((192_000 as Weight).saturating_mul(y as Weight)) + // Standard Error: 2_000 + .saturating_add((330_000 as Weight).saturating_mul(p as Weight)) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) + } + // Storage: Alliance Members (r:1 w:0) + // Storage: AllianceMotion ProposalOf (r:1 w:1) + // Storage: AllianceMotion Voting (r:1 w:1) + // Storage: AllianceMotion Members (r:1 w:0) + // Storage: AllianceMotion Proposals (r:1 w:1) + fn close_early_approved(b: u32, x: u32, y: u32, p: u32, ) -> Weight { + (52_076_000 as Weight) + // Standard Error: 0 + .saturating_add((4_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 77_000 + .saturating_add((194_000 as Weight).saturating_mul(x as Weight)) + // Standard Error: 3_000 + .saturating_add((188_000 as Weight).saturating_mul(y as Weight)) + // Standard Error: 2_000 + .saturating_add((329_000 as Weight).saturating_mul(p as Weight)) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) + } + // Storage: Alliance Members (r:1 w:0) + // Storage: AllianceMotion ProposalOf (r:1 w:1) + // Storage: AllianceMotion Voting (r:1 w:1) + // Storage: AllianceMotion Members (r:1 w:0) + // Storage: AllianceMotion Prime (r:1 w:0) + // Storage: AllianceMotion Proposals (r:1 w:1) + fn close_disapproved(x: u32, y: u32, p: u32, ) -> Weight { + (47_009_000 as Weight) + // Standard Error: 66_000 + .saturating_add((256_000 as Weight).saturating_mul(x as Weight)) + // Standard Error: 2_000 + .saturating_add((176_000 as Weight).saturating_mul(y as Weight)) + // Standard Error: 2_000 + .saturating_add((327_000 as Weight).saturating_mul(p as Weight)) + .saturating_add(T::DbWeight::get().reads(6 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) + } + // Storage: Alliance Members (r:1 w:0) + // Storage: AllianceMotion ProposalOf (r:1 w:1) + // Storage: AllianceMotion Voting (r:1 w:1) + // Storage: AllianceMotion Members (r:1 w:0) + // Storage: AllianceMotion Prime (r:1 w:0) + // Storage: AllianceMotion Proposals (r:1 w:1) + fn close_approved(b: u32, x: u32, y: u32, p: u32, ) -> Weight { + (43_650_000 as Weight) + // Standard Error: 0 + .saturating_add((3_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 85_000 + .saturating_add((124_000 as Weight).saturating_mul(x as Weight)) + // Standard Error: 3_000 + .saturating_add((199_000 as Weight).saturating_mul(y as Weight)) + // Standard Error: 3_000 + .saturating_add((326_000 as Weight).saturating_mul(p as Weight)) + .saturating_add(T::DbWeight::get().reads(6 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) + } + // Storage: Alliance Members (r:3 w:3) + // Storage: AllianceMotion Members (r:1 w:1) + fn init_members(_x: u32, y: u32, z: u32, ) -> Weight { + (45_100_000 as Weight) + // Standard Error: 4_000 + .saturating_add((162_000 as Weight).saturating_mul(y as Weight)) + // Standard Error: 4_000 + .saturating_add((151_000 as Weight).saturating_mul(z as Weight)) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) + } + // Storage: Alliance Rule (r:0 w:1) + fn set_rule() -> Weight { + (14_517_000 as Weight) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Alliance Announcements (r:1 w:1) + fn announce() -> Weight { + (16_801_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Alliance Announcements (r:1 w:1) + fn remove_announcement() -> Weight { + (17_133_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Alliance UnscrupulousAccounts (r:1 w:0) + // Storage: Alliance Members (r:4 w:0) + // Storage: System Account (r:1 w:1) + // Storage: Alliance DepositOf (r:0 w:1) + fn join_alliance() -> Weight { + (95_370_000 as Weight) + .saturating_add(T::DbWeight::get().reads(7 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) + } + // Storage: Alliance Members (r:4 w:0) + // Storage: Alliance UnscrupulousAccounts (r:1 w:0) + fn nominate_ally() -> Weight { + (44_764_000 as Weight) + .saturating_add(T::DbWeight::get().reads(6 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Alliance Members (r:3 w:2) + // Storage: AllianceMotion Proposals (r:1 w:0) + // Storage: AllianceMotion Members (r:0 w:1) + // Storage: AllianceMotion Prime (r:0 w:1) + fn elevate_ally() -> Weight { + (44_013_000 as Weight) + .saturating_add(T::DbWeight::get().reads(4 as Weight)) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) + } + // Storage: Alliance KickingMembers (r:1 w:0) + // Storage: Alliance Members (r:3 w:1) + // Storage: AllianceMotion Proposals (r:1 w:0) + // Storage: Alliance DepositOf (r:1 w:1) + // Storage: System Account (r:1 w:1) + // Storage: AllianceMotion Members (r:0 w:1) + // Storage: AllianceMotion Prime (r:0 w:1) + fn retire() -> Weight { + (60_183_000 as Weight) + .saturating_add(T::DbWeight::get().reads(7 as Weight)) + .saturating_add(T::DbWeight::get().writes(5 as Weight)) + } + // Storage: Alliance KickingMembers (r:1 w:0) + // Storage: Alliance Members (r:3 w:1) + // Storage: AllianceMotion Proposals (r:1 w:0) + // Storage: Alliance DepositOf (r:1 w:1) + // Storage: System Account (r:1 w:1) + // Storage: AllianceMotion Members (r:0 w:1) + // Storage: AllianceMotion Prime (r:0 w:1) + fn kick_member() -> Weight { + (67_467_000 as Weight) + .saturating_add(T::DbWeight::get().reads(7 as Weight)) + .saturating_add(T::DbWeight::get().writes(5 as Weight)) + } + // Storage: Alliance UnscrupulousAccounts (r:1 w:1) + // Storage: Alliance UnscrupulousWebsites (r:1 w:1) + fn add_unscrupulous_items(n: u32, l: u32, ) -> Weight { + (0 as Weight) + // Standard Error: 16_000 + .saturating_add((2_673_000 as Weight).saturating_mul(n as Weight)) + // Standard Error: 7_000 + .saturating_add((224_000 as Weight).saturating_mul(l as Weight)) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } + // Storage: Alliance UnscrupulousAccounts (r:1 w:1) + // Storage: Alliance UnscrupulousWebsites (r:1 w:1) + fn remove_unscrupulous_items(n: u32, l: u32, ) -> Weight { + (0 as Weight) + // Standard Error: 343_000 + .saturating_add((59_025_000 as Weight).saturating_mul(n as Weight)) + // Standard Error: 153_000 + .saturating_add((6_725_000 as Weight).saturating_mul(l as Weight)) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: Alliance Members (r:1 w:0) + // Storage: AllianceMotion ProposalOf (r:1 w:1) + // Storage: AllianceMotion Proposals (r:1 w:1) + // Storage: AllianceMotion ProposalCount (r:1 w:1) + // Storage: AllianceMotion Voting (r:0 w:1) + fn propose_proposed(_b: u32, _x: u32, y: u32, p: u32, ) -> Weight { + (39_992_000 as Weight) + // Standard Error: 2_000 + .saturating_add((44_000 as Weight).saturating_mul(y as Weight)) + // Standard Error: 2_000 + .saturating_add((323_000 as Weight).saturating_mul(p as Weight)) + .saturating_add(RocksDbWeight::get().reads(4 as Weight)) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + } + // Storage: Alliance Members (r:2 w:0) + // Storage: AllianceMotion Voting (r:1 w:1) + fn vote(x: u32, y: u32, ) -> Weight { + (36_649_000 as Weight) + // Standard Error: 90_000 + .saturating_add((42_000 as Weight).saturating_mul(x as Weight)) + // Standard Error: 3_000 + .saturating_add((195_000 as Weight).saturating_mul(y as Weight)) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Alliance Members (r:1 w:0) + // Storage: AllianceMotion ProposalOf (r:1 w:1) + // Storage: AllianceMotion Proposals (r:1 w:1) + // Storage: AllianceMotion Voting (r:0 w:1) + fn veto(p: u32, ) -> Weight { + (30_301_000 as Weight) + // Standard Error: 1_000 + .saturating_add((330_000 as Weight).saturating_mul(p as Weight)) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + } + // Storage: Alliance Members (r:1 w:0) + // Storage: AllianceMotion ProposalOf (r:1 w:1) + // Storage: AllianceMotion Voting (r:1 w:1) + // Storage: AllianceMotion Members (r:1 w:0) + // Storage: AllianceMotion Proposals (r:1 w:1) + fn close_early_disapproved(x: u32, y: u32, p: u32, ) -> Weight { + (40_472_000 as Weight) + // Standard Error: 69_000 + .saturating_add((485_000 as Weight).saturating_mul(x as Weight)) + // Standard Error: 2_000 + .saturating_add((192_000 as Weight).saturating_mul(y as Weight)) + // Standard Error: 2_000 + .saturating_add((330_000 as Weight).saturating_mul(p as Weight)) + .saturating_add(RocksDbWeight::get().reads(5 as Weight)) + .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + } + // Storage: Alliance Members (r:1 w:0) + // Storage: AllianceMotion ProposalOf (r:1 w:1) + // Storage: AllianceMotion Voting (r:1 w:1) + // Storage: AllianceMotion Members (r:1 w:0) + // Storage: AllianceMotion Proposals (r:1 w:1) + fn close_early_approved(b: u32, x: u32, y: u32, p: u32, ) -> Weight { + (52_076_000 as Weight) + // Standard Error: 0 + .saturating_add((4_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 77_000 + .saturating_add((194_000 as Weight).saturating_mul(x as Weight)) + // Standard Error: 3_000 + .saturating_add((188_000 as Weight).saturating_mul(y as Weight)) + // Standard Error: 2_000 + .saturating_add((329_000 as Weight).saturating_mul(p as Weight)) + .saturating_add(RocksDbWeight::get().reads(5 as Weight)) + .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + } + // Storage: Alliance Members (r:1 w:0) + // Storage: AllianceMotion ProposalOf (r:1 w:1) + // Storage: AllianceMotion Voting (r:1 w:1) + // Storage: AllianceMotion Members (r:1 w:0) + // Storage: AllianceMotion Prime (r:1 w:0) + // Storage: AllianceMotion Proposals (r:1 w:1) + fn close_disapproved(x: u32, y: u32, p: u32, ) -> Weight { + (47_009_000 as Weight) + // Standard Error: 66_000 + .saturating_add((256_000 as Weight).saturating_mul(x as Weight)) + // Standard Error: 2_000 + .saturating_add((176_000 as Weight).saturating_mul(y as Weight)) + // Standard Error: 2_000 + .saturating_add((327_000 as Weight).saturating_mul(p as Weight)) + .saturating_add(RocksDbWeight::get().reads(6 as Weight)) + .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + } + // Storage: Alliance Members (r:1 w:0) + // Storage: AllianceMotion ProposalOf (r:1 w:1) + // Storage: AllianceMotion Voting (r:1 w:1) + // Storage: AllianceMotion Members (r:1 w:0) + // Storage: AllianceMotion Prime (r:1 w:0) + // Storage: AllianceMotion Proposals (r:1 w:1) + fn close_approved(b: u32, x: u32, y: u32, p: u32, ) -> Weight { + (43_650_000 as Weight) + // Standard Error: 0 + .saturating_add((3_000 as Weight).saturating_mul(b as Weight)) + // Standard Error: 85_000 + .saturating_add((124_000 as Weight).saturating_mul(x as Weight)) + // Standard Error: 3_000 + .saturating_add((199_000 as Weight).saturating_mul(y as Weight)) + // Standard Error: 3_000 + .saturating_add((326_000 as Weight).saturating_mul(p as Weight)) + .saturating_add(RocksDbWeight::get().reads(6 as Weight)) + .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + } + // Storage: Alliance Members (r:3 w:3) + // Storage: AllianceMotion Members (r:1 w:1) + fn init_members(_x: u32, y: u32, z: u32, ) -> Weight { + (45_100_000 as Weight) + // Standard Error: 4_000 + .saturating_add((162_000 as Weight).saturating_mul(y as Weight)) + // Standard Error: 4_000 + .saturating_add((151_000 as Weight).saturating_mul(z as Weight)) + .saturating_add(RocksDbWeight::get().reads(4 as Weight)) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + } + // Storage: Alliance Rule (r:0 w:1) + fn set_rule() -> Weight { + (14_517_000 as Weight) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Alliance Announcements (r:1 w:1) + fn announce() -> Weight { + (16_801_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Alliance Announcements (r:1 w:1) + fn remove_announcement() -> Weight { + (17_133_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Alliance UnscrupulousAccounts (r:1 w:0) + // Storage: Alliance Members (r:4 w:0) + // Storage: System Account (r:1 w:1) + // Storage: Alliance DepositOf (r:0 w:1) + fn join_alliance() -> Weight { + (95_370_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(7 as Weight)) + .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + } + // Storage: Alliance Members (r:4 w:0) + // Storage: Alliance UnscrupulousAccounts (r:1 w:0) + fn nominate_ally() -> Weight { + (44_764_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(6 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Alliance Members (r:3 w:2) + // Storage: AllianceMotion Proposals (r:1 w:0) + // Storage: AllianceMotion Members (r:0 w:1) + // Storage: AllianceMotion Prime (r:0 w:1) + fn elevate_ally() -> Weight { + (44_013_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(4 as Weight)) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + } + // Storage: Alliance KickingMembers (r:1 w:0) + // Storage: Alliance Members (r:3 w:1) + // Storage: AllianceMotion Proposals (r:1 w:0) + // Storage: Alliance DepositOf (r:1 w:1) + // Storage: System Account (r:1 w:1) + // Storage: AllianceMotion Members (r:0 w:1) + // Storage: AllianceMotion Prime (r:0 w:1) + fn retire() -> Weight { + (60_183_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(7 as Weight)) + .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + } + // Storage: Alliance KickingMembers (r:1 w:0) + // Storage: Alliance Members (r:3 w:1) + // Storage: AllianceMotion Proposals (r:1 w:0) + // Storage: Alliance DepositOf (r:1 w:1) + // Storage: System Account (r:1 w:1) + // Storage: AllianceMotion Members (r:0 w:1) + // Storage: AllianceMotion Prime (r:0 w:1) + fn kick_member() -> Weight { + (67_467_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(7 as Weight)) + .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + } + // Storage: Alliance UnscrupulousAccounts (r:1 w:1) + // Storage: Alliance UnscrupulousWebsites (r:1 w:1) + fn add_unscrupulous_items(n: u32, l: u32, ) -> Weight { + (0 as Weight) + // Standard Error: 16_000 + .saturating_add((2_673_000 as Weight).saturating_mul(n as Weight)) + // Standard Error: 7_000 + .saturating_add((224_000 as Weight).saturating_mul(l as Weight)) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } + // Storage: Alliance UnscrupulousAccounts (r:1 w:1) + // Storage: Alliance UnscrupulousWebsites (r:1 w:1) + fn remove_unscrupulous_items(n: u32, l: u32, ) -> Weight { + (0 as Weight) + // Standard Error: 343_000 + .saturating_add((59_025_000 as Weight).saturating_mul(n as Weight)) + // Standard Error: 153_000 + .saturating_add((6_725_000 as Weight).saturating_mul(l as Weight)) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + } +} diff --git a/frame/collective/src/lib.rs b/frame/collective/src/lib.rs index 13d03562cce49..bc6d9ab3310e0 100644 --- a/frame/collective/src/lib.rs +++ b/frame/collective/src/lib.rs @@ -51,8 +51,10 @@ use frame_support::{ codec::{Decode, Encode}, dispatch::{DispatchError, DispatchResultWithPostInfo, Dispatchable, PostDispatchInfo}, ensure, - traits::{Backing, ChangeMembers, EnsureOrigin, Get, GetBacking, InitializeMembers}, - weights::{GetDispatchInfo, Weight}, + traits::{ + Backing, ChangeMembers, EnsureOrigin, Get, GetBacking, InitializeMembers, StorageVersion, + }, + weights::{GetDispatchInfo, Pays, Weight}, }; #[cfg(test)] @@ -435,7 +437,7 @@ pub mod pallet { let who = ensure_signed(origin)?; let members = Self::members(); ensure!(members.contains(&who), Error::::NotMember); - let proposal_len = proposal.using_encoded(|x| x.len()); + let proposal_len = proposal.encoded_size(); ensure!(proposal_len <= length_bound as usize, Error::::WrongProposalLength); let proposal_hash = T::Hashing::hash_of(&proposal); @@ -508,21 +510,8 @@ pub mod pallet { let members = Self::members(); ensure!(members.contains(&who), Error::::NotMember); - let proposal_len = proposal.using_encoded(|x| x.len()); - ensure!(proposal_len <= length_bound as usize, Error::::WrongProposalLength); - let proposal_hash = T::Hashing::hash_of(&proposal); - ensure!( - !>::contains_key(proposal_hash), - Error::::DuplicateProposal - ); - if threshold < 2 { - let seats = Self::members().len() as MemberCount; - let result = proposal.dispatch(RawOrigin::Members(1, seats).into()); - Self::deposit_event(Event::Executed { - proposal_hash, - result: result.map(|_| ()).map_err(|e| e.error), - }); + let (proposal_len, result) = Self::do_propose_execute(proposal, length_bound)?; Ok(get_result_weight(result) .map(|w| { @@ -534,33 +523,13 @@ pub mod pallet { }) .into()) } else { - let active_proposals = - >::try_mutate(|proposals| -> Result { - proposals - .try_push(proposal_hash) - .map_err(|_| Error::::TooManyProposals)?; - Ok(proposals.len()) - })?; - let index = Self::proposal_count(); - >::mutate(|i| *i += 1); - >::insert(proposal_hash, *proposal); - let votes = { - let end = frame_system::Pallet::::block_number() + T::MotionDuration::get(); - Votes { index, threshold, ayes: vec![], nays: vec![], end } - }; - >::insert(proposal_hash, votes); - - Self::deposit_event(Event::Proposed { - account: who, - proposal_index: index, - proposal_hash, - threshold, - }); + let (proposal_len, active_proposals) = + Self::do_propose_proposed(who, threshold, proposal, length_bound)?; Ok(Some(T::WeightInfo::propose_proposed( - proposal_len as u32, // B - members.len() as u32, // M - active_proposals as u32, // P2 + proposal_len as u32, // B + members.len() as u32, // M + active_proposals, // P2 )) .into()) } @@ -592,46 +561,8 @@ pub mod pallet { let members = Self::members(); ensure!(members.contains(&who), Error::::NotMember); - let mut voting = Self::voting(&proposal).ok_or(Error::::ProposalMissing)?; - ensure!(voting.index == index, Error::::WrongIndex); - - let position_yes = voting.ayes.iter().position(|a| a == &who); - let position_no = voting.nays.iter().position(|a| a == &who); - // Detects first vote of the member in the motion - let is_account_voting_first_time = position_yes.is_none() && position_no.is_none(); - - if approve { - if position_yes.is_none() { - voting.ayes.push(who.clone()); - } else { - return Err(Error::::DuplicateVote.into()) - } - if let Some(pos) = position_no { - voting.nays.swap_remove(pos); - } - } else { - if position_no.is_none() { - voting.nays.push(who.clone()); - } else { - return Err(Error::::DuplicateVote.into()) - } - if let Some(pos) = position_yes { - voting.ayes.swap_remove(pos); - } - } - - let yes_votes = voting.ayes.len() as MemberCount; - let no_votes = voting.nays.len() as MemberCount; - Self::deposit_event(Event::Voted { - account: who, - proposal_hash: proposal, - voted: approve, - yes: yes_votes, - no: no_votes, - }); - - Voting::::insert(&proposal, voting); + let is_account_voting_first_time = Self::do_vote(who, proposal, index, approve)?; if is_account_voting_first_time { Ok((Some(T::WeightInfo::vote(members.len() as u32)), Pays::No).into()) @@ -695,82 +626,7 @@ pub mod pallet { ) -> DispatchResultWithPostInfo { let _ = ensure_signed(origin)?; - let voting = Self::voting(&proposal_hash).ok_or(Error::::ProposalMissing)?; - ensure!(voting.index == index, Error::::WrongIndex); - - let mut no_votes = voting.nays.len() as MemberCount; - let mut yes_votes = voting.ayes.len() as MemberCount; - let seats = Self::members().len() as MemberCount; - let approved = yes_votes >= voting.threshold; - let disapproved = seats.saturating_sub(no_votes) < voting.threshold; - // Allow (dis-)approving the proposal as soon as there are enough votes. - if approved { - let (proposal, len) = Self::validate_and_get_proposal( - &proposal_hash, - length_bound, - proposal_weight_bound, - )?; - Self::deposit_event(Event::Closed { proposal_hash, yes: yes_votes, no: no_votes }); - let (proposal_weight, proposal_count) = - Self::do_approve_proposal(seats, yes_votes, proposal_hash, proposal); - return Ok(( - Some( - T::WeightInfo::close_early_approved(len as u32, seats, proposal_count) - .saturating_add(proposal_weight), - ), - Pays::Yes, - ) - .into()) - } else if disapproved { - Self::deposit_event(Event::Closed { proposal_hash, yes: yes_votes, no: no_votes }); - let proposal_count = Self::do_disapprove_proposal(proposal_hash); - return Ok(( - Some(T::WeightInfo::close_early_disapproved(seats, proposal_count)), - Pays::No, - ) - .into()) - } - - // Only allow actual closing of the proposal after the voting period has ended. - ensure!( - frame_system::Pallet::::block_number() >= voting.end, - Error::::TooEarly - ); - - let prime_vote = Self::prime().map(|who| voting.ayes.iter().any(|a| a == &who)); - - // default voting strategy. - let default = T::DefaultVote::default_vote(prime_vote, yes_votes, no_votes, seats); - - let abstentions = seats - (yes_votes + no_votes); - match default { - true => yes_votes += abstentions, - false => no_votes += abstentions, - } - let approved = yes_votes >= voting.threshold; - - if approved { - let (proposal, len) = Self::validate_and_get_proposal( - &proposal_hash, - length_bound, - proposal_weight_bound, - )?; - Self::deposit_event(Event::Closed { proposal_hash, yes: yes_votes, no: no_votes }); - let (proposal_weight, proposal_count) = - Self::do_approve_proposal(seats, yes_votes, proposal_hash, proposal); - Ok(( - Some( - T::WeightInfo::close_approved(len as u32, seats, proposal_count) - .saturating_add(proposal_weight), - ), - Pays::Yes, - ) - .into()) - } else { - Self::deposit_event(Event::Closed { proposal_hash, yes: yes_votes, no: no_votes }); - let proposal_count = Self::do_disapprove_proposal(proposal_hash); - Ok((Some(T::WeightInfo::close_disapproved(seats, proposal_count)), Pays::No).into()) - } + Self::do_close(proposal_hash, index, proposal_weight_bound, length_bound) } /// Disapprove a proposal, close, and remove it from the system, regardless of its current @@ -817,6 +673,197 @@ impl, I: 'static> Pallet { Self::members().contains(who) } + /// Execute immediately when adding a new proposal. + pub fn do_propose_execute( + proposal: Box<>::Proposal>, + length_bound: MemberCount, + ) -> Result<(u32, DispatchResultWithPostInfo), DispatchError> { + let proposal_len = proposal.encoded_size(); + ensure!(proposal_len <= length_bound as usize, Error::::WrongProposalLength); + + let proposal_hash = T::Hashing::hash_of(&proposal); + ensure!(!>::contains_key(proposal_hash), Error::::DuplicateProposal); + + let seats = Self::members().len() as MemberCount; + let result = proposal.dispatch(RawOrigin::Members(1, seats).into()); + Self::deposit_event(Event::Executed { + proposal_hash, + result: result.map(|_| ()).map_err(|e| e.error), + }); + Ok((proposal_len as u32, result)) + } + + /// Add a new proposal to be voted. + pub fn do_propose_proposed( + who: T::AccountId, + threshold: MemberCount, + proposal: Box<>::Proposal>, + length_bound: MemberCount, + ) -> Result<(u32, u32), DispatchError> { + let proposal_len = proposal.encoded_size(); + ensure!(proposal_len <= length_bound as usize, Error::::WrongProposalLength); + + let proposal_hash = T::Hashing::hash_of(&proposal); + ensure!(!>::contains_key(proposal_hash), Error::::DuplicateProposal); + + let active_proposals = + >::try_mutate(|proposals| -> Result { + proposals.try_push(proposal_hash).map_err(|_| Error::::TooManyProposals)?; + Ok(proposals.len()) + })?; + + let index = Self::proposal_count(); + >::mutate(|i| *i += 1); + >::insert(proposal_hash, proposal); + let votes = { + let end = frame_system::Pallet::::block_number() + T::MotionDuration::get(); + Votes { index, threshold, ayes: vec![], nays: vec![], end } + }; + >::insert(proposal_hash, votes); + + Self::deposit_event(Event::Proposed { + account: who, + proposal_index: index, + proposal_hash, + threshold, + }); + Ok((proposal_len as u32, active_proposals as u32)) + } + + /// Add an aye or nay vote for the member to the given proposal, returns true if it's the first + /// vote of the member in the motion + pub fn do_vote( + who: T::AccountId, + proposal: T::Hash, + index: ProposalIndex, + approve: bool, + ) -> Result { + let mut voting = Self::voting(&proposal).ok_or(Error::::ProposalMissing)?; + ensure!(voting.index == index, Error::::WrongIndex); + + let position_yes = voting.ayes.iter().position(|a| a == &who); + let position_no = voting.nays.iter().position(|a| a == &who); + + // Detects first vote of the member in the motion + let is_account_voting_first_time = position_yes.is_none() && position_no.is_none(); + + if approve { + if position_yes.is_none() { + voting.ayes.push(who.clone()); + } else { + return Err(Error::::DuplicateVote.into()) + } + if let Some(pos) = position_no { + voting.nays.swap_remove(pos); + } + } else { + if position_no.is_none() { + voting.nays.push(who.clone()); + } else { + return Err(Error::::DuplicateVote.into()) + } + if let Some(pos) = position_yes { + voting.ayes.swap_remove(pos); + } + } + + let yes_votes = voting.ayes.len() as MemberCount; + let no_votes = voting.nays.len() as MemberCount; + Self::deposit_event(Event::Voted { + account: who, + proposal_hash: proposal, + voted: approve, + yes: yes_votes, + no: no_votes, + }); + + Voting::::insert(&proposal, voting); + + Ok(is_account_voting_first_time) + } + + /// Close a vote that is either approved, disapproved or whose voting period has ended. + pub fn do_close( + proposal_hash: T::Hash, + index: ProposalIndex, + proposal_weight_bound: Weight, + length_bound: u32, + ) -> DispatchResultWithPostInfo { + let voting = Self::voting(&proposal_hash).ok_or(Error::::ProposalMissing)?; + ensure!(voting.index == index, Error::::WrongIndex); + + let mut no_votes = voting.nays.len() as MemberCount; + let mut yes_votes = voting.ayes.len() as MemberCount; + let seats = Self::members().len() as MemberCount; + let approved = yes_votes >= voting.threshold; + let disapproved = seats.saturating_sub(no_votes) < voting.threshold; + // Allow (dis-)approving the proposal as soon as there are enough votes. + if approved { + let (proposal, len) = Self::validate_and_get_proposal( + &proposal_hash, + length_bound, + proposal_weight_bound, + )?; + Self::deposit_event(Event::Closed { proposal_hash, yes: yes_votes, no: no_votes }); + let (proposal_weight, proposal_count) = + Self::do_approve_proposal(seats, yes_votes, proposal_hash, proposal); + return Ok(( + Some( + T::WeightInfo::close_early_approved(len as u32, seats, proposal_count) + .saturating_add(proposal_weight), + ), + Pays::Yes, + ) + .into()) + } else if disapproved { + Self::deposit_event(Event::Closed { proposal_hash, yes: yes_votes, no: no_votes }); + let proposal_count = Self::do_disapprove_proposal(proposal_hash); + return Ok(( + Some(T::WeightInfo::close_early_disapproved(seats, proposal_count)), + Pays::No, + ) + .into()) + } + + // Only allow actual closing of the proposal after the voting period has ended. + ensure!(frame_system::Pallet::::block_number() >= voting.end, Error::::TooEarly); + + let prime_vote = Self::prime().map(|who| voting.ayes.iter().any(|a| a == &who)); + + // default voting strategy. + let default = T::DefaultVote::default_vote(prime_vote, yes_votes, no_votes, seats); + + let abstentions = seats - (yes_votes + no_votes); + match default { + true => yes_votes += abstentions, + false => no_votes += abstentions, + } + let approved = yes_votes >= voting.threshold; + + if approved { + let (proposal, len) = Self::validate_and_get_proposal( + &proposal_hash, + length_bound, + proposal_weight_bound, + )?; + Self::deposit_event(Event::Closed { proposal_hash, yes: yes_votes, no: no_votes }); + let (proposal_weight, proposal_count) = + Self::do_approve_proposal(seats, yes_votes, proposal_hash, proposal); + Ok(( + Some( + T::WeightInfo::close_approved(len as u32, seats, proposal_count) + .saturating_add(proposal_weight), + ), + Pays::Yes, + ) + .into()) + } else { + Self::deposit_event(Event::Closed { proposal_hash, yes: yes_votes, no: no_votes }); + let proposal_count = Self::do_disapprove_proposal(proposal_hash); + Ok((Some(T::WeightInfo::close_disapproved(seats, proposal_count)), Pays::No).into()) + } + } + /// Ensure that the right proposal bounds were passed and get the proposal from storage. /// /// Checks the length in storage via `storage::read` which adds an extra `size_of::() == 4` @@ -873,7 +920,8 @@ impl, I: 'static> Pallet { (proposal_weight, proposal_count) } - fn do_disapprove_proposal(proposal_hash: T::Hash) -> u32 { + /// Removes a proposal from the pallet, and deposit the `Disapproved` event. + pub fn do_disapprove_proposal(proposal_hash: T::Hash) -> u32 { // disapproved Self::deposit_event(Event::Disapproved { proposal_hash }); Self::remove_proposal(proposal_hash) From a611b937df7b8067d0a88dfcd34f90d2ece3578b Mon Sep 17 00:00:00 2001 From: Tarek Mohamed Abdalla Date: Sat, 11 Jun 2022 19:24:42 +0200 Subject: [PATCH 322/484] Add fixed u64 (#11555) Co-authored-by: parity-processbot <> Co-authored-by: Oliver Tale-Yazdi --- primitives/arithmetic/src/fixed_point.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/primitives/arithmetic/src/fixed_point.rs b/primitives/arithmetic/src/fixed_point.rs index f4ed70ee8b5dc..2dfe717fe7e4f 100644 --- a/primitives/arithmetic/src/fixed_point.rs +++ b/primitives/arithmetic/src/fixed_point.rs @@ -2167,6 +2167,15 @@ implement_fixed!( "_Fixed Point 64 bits signed, range = [-9223372036.854775808, 9223372036.854775807]_", ); +implement_fixed!( + FixedU64, + test_fixed_u64, + u64, + false, + 1_000_000_000, + "_Fixed Point 64 bits unsigned, range = [0.000000000, 18446744073.709551615]_", +); + implement_fixed!( FixedI128, test_fixed_i128, From 7ff4cf56f17fb34b00bc59b05d7e4eb9eeb25123 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Sat, 11 Jun 2022 18:50:16 +0100 Subject: [PATCH 323/484] Remove storage `MaxValues` limits (#11643) * Remove limits Signed-off-by: Oliver Tale-Yazdi * Remove more Signed-off-by: Oliver Tale-Yazdi --- frame/assets/src/lib.rs | 8 -------- frame/atomic-swap/src/lib.rs | 3 --- frame/balances/src/lib.rs | 13 ++----------- 3 files changed, 2 insertions(+), 22 deletions(-) diff --git a/frame/assets/src/lib.rs b/frame/assets/src/lib.rs index eb412de58fda6..3f9146c5d229b 100644 --- a/frame/assets/src/lib.rs +++ b/frame/assets/src/lib.rs @@ -262,9 +262,6 @@ pub mod pallet { Blake2_128Concat, T::AccountId, AssetAccountOf, - OptionQuery, - GetDefault, - ConstU32<300_000>, >; #[pallet::storage] @@ -279,9 +276,6 @@ pub mod pallet { NMapKey, // delegate ), Approval>, - OptionQuery, - GetDefault, - ConstU32<300_000>, >; #[pallet::storage] @@ -292,8 +286,6 @@ pub mod pallet { T::AssetId, AssetMetadata, BoundedVec>, ValueQuery, - GetDefault, - ConstU32<300_000>, >; #[pallet::genesis_config] diff --git a/frame/atomic-swap/src/lib.rs b/frame/atomic-swap/src/lib.rs index b999aefaaa907..1ddf3888d3c96 100644 --- a/frame/atomic-swap/src/lib.rs +++ b/frame/atomic-swap/src/lib.rs @@ -195,9 +195,6 @@ pub mod pallet { Blake2_128Concat, HashedProof, PendingSwap, - OptionQuery, - GetDefault, - ConstU32<300_000>, >; #[pallet::error] diff --git a/frame/balances/src/lib.rs b/frame/balances/src/lib.rs index 7060486f8584d..298c2a6963880 100644 --- a/frame/balances/src/lib.rs +++ b/frame/balances/src/lib.rs @@ -516,15 +516,8 @@ pub mod pallet { /// `Balances` pallet, which uses a `StorageMap` to store balances data only. /// NOTE: This is only used in the case that this pallet is used to store balances. #[pallet::storage] - pub type Account, I: 'static = ()> = StorageMap< - _, - Blake2_128Concat, - T::AccountId, - AccountData, - ValueQuery, - GetDefault, - ConstU32<300_000>, - >; + pub type Account, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, T::AccountId, AccountData, ValueQuery>; /// Any liquidity locks on some account balances. /// NOTE: Should only be accessed when setting, changing and freeing a lock. @@ -536,8 +529,6 @@ pub mod pallet { T::AccountId, WeakBoundedVec, T::MaxLocks>, ValueQuery, - GetDefault, - ConstU32<300_000>, >; /// Named reserves on some account balances. From 5f2bbf26ef741427b17d1afba7e43e3f751f64b3 Mon Sep 17 00:00:00 2001 From: Sergej Sakac <73715684+Szegoo@users.noreply.github.com> Date: Sun, 12 Jun 2022 18:40:29 +0200 Subject: [PATCH 324/484] Added an event for cancel_proposal (#11620) * Added an event for cancel_proposal * test --- frame/democracy/src/lib.rs | 3 +++ frame/democracy/src/tests/public_proposals.rs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/frame/democracy/src/lib.rs b/frame/democracy/src/lib.rs index 91362a0a3edf7..4e11c207657f0 100644 --- a/frame/democracy/src/lib.rs +++ b/frame/democracy/src/lib.rs @@ -537,6 +537,8 @@ pub mod pallet { Voted { voter: T::AccountId, ref_index: ReferendumIndex, vote: AccountVote> }, /// An account has secconded a proposal Seconded { seconder: T::AccountId, prop_index: PropIndex }, + /// A proposal got canceled. + ProposalCanceled { prop_index: PropIndex }, } #[pallet::error] @@ -1277,6 +1279,7 @@ pub mod pallet { } } + Self::deposit_event(Event::::ProposalCanceled { prop_index }); Ok(()) } } diff --git a/frame/democracy/src/tests/public_proposals.rs b/frame/democracy/src/tests/public_proposals.rs index aadc92d24f41d..db06696ca5c95 100644 --- a/frame/democracy/src/tests/public_proposals.rs +++ b/frame/democracy/src/tests/public_proposals.rs @@ -96,11 +96,11 @@ fn invalid_seconds_upper_bound_should_not_work() { #[test] fn cancel_proposal_should_work() { new_test_ext().execute_with(|| { - System::set_block_number(0); assert_ok!(propose_set_balance_and_note(1, 2, 2)); assert_ok!(propose_set_balance_and_note(1, 4, 4)); assert_noop!(Democracy::cancel_proposal(Origin::signed(1), 0), BadOrigin); assert_ok!(Democracy::cancel_proposal(Origin::root(), 0)); + System::assert_last_event(crate::Event::ProposalCanceled { prop_index: 0 }.into()); assert_eq!(Democracy::backing_for(0), None); assert_eq!(Democracy::backing_for(1), Some(4)); }); From 399469d5feaba932fc64a1ee45ca6a6b9e9ba5e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Mon, 13 Jun 2022 10:31:16 +0200 Subject: [PATCH 325/484] pallet-grandpa: Improve/Clarify docs of `note_stalled` (#11623) --- frame/grandpa/src/lib.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/frame/grandpa/src/lib.rs b/frame/grandpa/src/lib.rs index be613e302b21e..1781f0a8e40a2 100644 --- a/frame/grandpa/src/lib.rs +++ b/frame/grandpa/src/lib.rs @@ -231,12 +231,17 @@ pub mod pallet { ) } - /// Note that the current authority set of the GRANDPA finality gadget has - /// stalled. This will trigger a forced authority set change at the beginning - /// of the next session, to be enacted `delay` blocks after that. The delay - /// should be high enough to safely assume that the block signalling the - /// forced change will not be re-orged (e.g. 1000 blocks). The GRANDPA voters - /// will start the new authority set using the given finalized block as base. + /// Note that the current authority set of the GRANDPA finality gadget has stalled. + /// + /// This will trigger a forced authority set change at the beginning of the next session, to + /// be enacted `delay` blocks after that. The `delay` should be high enough to safely assume + /// that the block signalling the forced change will not be re-orged e.g. 1000 blocks. + /// The block production rate (which may be slowed down because of finality lagging) should + /// be taken into account when choosing the `delay`. The GRANDPA voters based on the new + /// authority will start voting on top of `best_finalized_block_number` for new finalized + /// blocks. `best_finalized_block_number` should be the highest of the latest finalized + /// block of all validators of the new authority set. + /// /// Only callable by root. #[pallet::weight(T::WeightInfo::note_stalled())] pub fn note_stalled( From e0146f0debde5949b53ce8acf752b610f2ee5a5e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Jun 2022 11:14:37 +0200 Subject: [PATCH 326/484] Bump crossbeam-utils from 0.8.5 to 0.8.8 (#11605) Bumps [crossbeam-utils](https://github.com/crossbeam-rs/crossbeam) from 0.8.5 to 0.8.8. - [Release notes](https://github.com/crossbeam-rs/crossbeam/releases) - [Changelog](https://github.com/crossbeam-rs/crossbeam/blob/master/CHANGELOG.md) - [Commits](https://github.com/crossbeam-rs/crossbeam/compare/crossbeam-utils-0.8.5...crossbeam-utils-0.8.8) --- updated-dependencies: - dependency-name: crossbeam-utils dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3b581bf9d0ff7..a5e431c7a4aa0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1360,9 +1360,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" +checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" dependencies = [ "cfg-if 1.0.0", "lazy_static", @@ -11205,7 +11205,7 @@ version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ee73e6e4924fe940354b8d4d98cad5231175d615cd855b758adc658c0aac6a0" dependencies = [ - "cfg-if 1.0.0", + "cfg-if 0.1.10", "digest 0.10.3", "rand 0.8.4", "static_assertions", From e0ec85d5eddb1436adcc3136fda824745a902654 Mon Sep 17 00:00:00 2001 From: Yongjin Huang <20609724+doutv@users.noreply.github.com> Date: Mon, 13 Jun 2022 19:44:50 +0800 Subject: [PATCH 327/484] fix pallet_uniques docs link (#11651) --- frame/uniques/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frame/uniques/README.md b/frame/uniques/README.md index 09f9d346895a6..8a91a558b5b5f 100644 --- a/frame/uniques/README.md +++ b/frame/uniques/README.md @@ -10,9 +10,9 @@ The Uniques module provides functionality for asset management of non-fungible a * Asset Transfer * Asset Destruction -To use it in your runtime, you need to implement the assets [`uniques::Config`](https://docs.rs/pallet-uniques/latest/pallet_uniques/pallet/trait.Config.html). +To use it in your runtime, you need to implement the assets [`uniques::Config`](https://paritytech.github.io/substrate/master/pallet_uniques/pallet/trait.Config.html). -The supported dispatchable functions are documented in the [`uniques::Call`](https://docs.rs/pallet-uniques/latest/pallet_uniques/pallet/enum.Call.html) enum. +The supported dispatchable functions are documented in the [`uniques::Call`](https://paritytech.github.io/substrate/master/pallet_uniques/pallet/enum.Call.html) enum. ### Terminology @@ -66,7 +66,7 @@ The Uniques pallet in Substrate is designed to make the following possible: * `force_create`: Create a new asset class. * `force_asset_status`: Alter the underlying characteristics of an asset class. -Please refer to the [`Call`](https://docs.rs/pallet-assets/latest/pallet_assets/enum.Call.html) enum +Please refer to the [`Call`](https://paritytech.github.io/substrate/master/pallet_uniques/pallet/enum.Call.html) enum and its associated variants for documentation on each function. ## Related Modules From d7eaff9036ae3dede8a5c16ad911be12c6331aed Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Mon, 13 Jun 2022 13:17:07 +0100 Subject: [PATCH 328/484] MEL bound `state-trie-migration` (#11639) * MEL bound state-trie-migration Signed-off-by: Oliver Tale-Yazdi * wip Signed-off-by: Oliver Tale-Yazdi * Add tests Signed-off-by: Oliver Tale-Yazdi * Use sp_std Co-authored-by: cheme * Add doc Signed-off-by: Oliver Tale-Yazdi * Set MaxKeyLen default to 512 Just to be sure that it will work. There is also no real penalty from over-estimating it. Signed-off-by: Oliver Tale-Yazdi * Add more doc Signed-off-by: Oliver Tale-Yazdi * Clippy Signed-off-by: Oliver Tale-Yazdi * fmt Signed-off-by: Oliver Tale-Yazdi * Fix assert_err_with_weight macro Looks like I'm the only one using it anyway... Signed-off-by: Oliver Tale-Yazdi * Fix tests that use env macro Signed-off-by: Oliver Tale-Yazdi * Fix test Signed-off-by: Oliver Tale-Yazdi Co-authored-by: cheme --- bin/node/runtime/src/lib.rs | 2 + frame/state-trie-migration/src/lib.rs | 273 +++++++++++++++++++++----- frame/support/src/lib.rs | 2 +- 3 files changed, 223 insertions(+), 54 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 8e854b770a4ff..6f3df7416681e 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1492,12 +1492,14 @@ impl pallet_whitelist::Config for Runtime { parameter_types! { pub const MigrationSignedDepositPerItem: Balance = 1 * CENTS; pub const MigrationSignedDepositBase: Balance = 20 * DOLLARS; + pub const MigrationMaxKeyLen: u32 = 512; } impl pallet_state_trie_migration::Config for Runtime { type Event = Event; type ControlOrigin = EnsureRoot; type Currency = Balances; + type MaxKeyLen = MigrationMaxKeyLen; type SignedDepositPerItem = MigrationSignedDepositPerItem; type SignedDepositBase = MigrationSignedDepositBase; // Warning: this is not advised, as it might allow the chain to be temporarily DOS-ed. diff --git a/frame/state-trie-migration/src/lib.rs b/frame/state-trie-migration/src/lib.rs index 899250cc3f3c3..9ac128a0e3108 100644 --- a/frame/state-trie-migration/src/lib.rs +++ b/frame/state-trie-migration/src/lib.rs @@ -78,12 +78,14 @@ pub mod pallet { traits::{Currency, Get}, }; use frame_system::{self, pallet_prelude::*}; - use sp_core::storage::well_known_keys::DEFAULT_CHILD_STORAGE_KEY_PREFIX; + use sp_core::{ + hexdisplay::HexDisplay, storage::well_known_keys::DEFAULT_CHILD_STORAGE_KEY_PREFIX, + }; use sp_runtime::{ self, traits::{Saturating, Zero}, }; - use sp_std::prelude::*; + use sp_std::{ops::Deref, prelude::*}; pub(crate) type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; @@ -124,30 +126,43 @@ pub mod pallet { } /// The progress of either the top or child keys. - #[derive(Clone, Encode, Decode, scale_info::TypeInfo, PartialEq, Eq)] - pub enum Progress { + #[derive( + CloneNoBound, + Encode, + Decode, + scale_info::TypeInfo, + PartialEqNoBound, + EqNoBound, + MaxEncodedLen, + )] + #[scale_info(skip_type_params(MaxKeyLen))] + #[codec(mel_bound())] + pub enum Progress> { /// Yet to begin. ToStart, /// Ongoing, with the last key given. - LastKey(Vec), + LastKey(BoundedVec), /// All done. Complete, } + /// Convenience type for easier usage of [`Progress`]. + pub type ProgressOf = Progress<::MaxKeyLen>; + /// A migration task stored in state. /// /// It tracks the last top and child keys read. - #[derive(Clone, Encode, Decode, scale_info::TypeInfo, PartialEq, Eq)] + #[derive(Clone, Encode, Decode, scale_info::TypeInfo, PartialEq, Eq, MaxEncodedLen)] #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] pub struct MigrationTask { /// The current top trie migration progress. - pub(crate) progress_top: Progress, + pub(crate) progress_top: ProgressOf, /// The current child trie migration progress. /// /// If `ToStart`, no further top keys are processed until the child key migration is /// `Complete`. - pub(crate) progress_child: Progress, + pub(crate) progress_child: ProgressOf, /// Dynamic counter for the number of items that we have processed in this execution from /// the top trie. @@ -186,12 +201,11 @@ pub mod pallet { pub(crate) _ph: sp_std::marker::PhantomData, } - impl sp_std::fmt::Debug for Progress { + impl> sp_std::fmt::Debug for Progress { fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { match self { Progress::ToStart => f.write_str("To start"), - Progress::LastKey(key) => - write!(f, "Last: {:?}", sp_core::hexdisplay::HexDisplay::from(key)), + Progress::LastKey(key) => write!(f, "Last: {:?}", HexDisplay::from(key.deref())), Progress::Complete => f.write_str("Complete"), } } @@ -252,17 +266,20 @@ pub mod pallet { /// reading a key, we simply cannot know how many bytes it is. In other words, this should /// not be used in any environment where resources are strictly bounded (e.g. a parachain), /// but it is acceptable otherwise (relay chain, offchain workers). - pub fn migrate_until_exhaustion(&mut self, limits: MigrationLimits) { + pub fn migrate_until_exhaustion(&mut self, limits: MigrationLimits) -> DispatchResult { log!(debug, "running migrations on top of {:?} until {:?}", self, limits); if limits.item.is_zero() || limits.size.is_zero() { // handle this minor edge case, else we would call `migrate_tick` at least once. log!(warn, "limits are zero. stopping"); - return + return Ok(()) } while !self.exhausted(limits) && !self.finished() { - self.migrate_tick(); + if let Err(e) = self.migrate_tick() { + log!(error, "migrate_until_exhaustion failed: {:?}", e); + return Err(e) + } } // accumulate dynamic data into the storage items. @@ -270,19 +287,18 @@ pub mod pallet { self.child_items = self.child_items.saturating_add(self.dyn_child_items); self.top_items = self.top_items.saturating_add(self.dyn_top_items); log!(debug, "finished with {:?}", self); + Ok(()) } /// Migrate AT MOST ONE KEY. This can be either a top or a child key. /// /// This function is *the* core of this entire pallet. - fn migrate_tick(&mut self) { + fn migrate_tick(&mut self) -> DispatchResult { match (&self.progress_top, &self.progress_child) { - (Progress::ToStart, _) => { - self.migrate_top(); - }, + (Progress::ToStart, _) => self.migrate_top(), (Progress::LastKey(_), Progress::LastKey(_)) => { // we're in the middle of doing work on a child tree. - self.migrate_child(); + self.migrate_child() }, (Progress::LastKey(top_key), Progress::ToStart) => { // 3. this is the root of a child key, and we are finishing all child-keys (and @@ -293,20 +309,22 @@ pub mod pallet { if !top_key.starts_with(DEFAULT_CHILD_STORAGE_KEY_PREFIX) { // we continue the top key migrations. // continue the top key migration - self.migrate_top(); + self.migrate_top() } else { // this is the root of a child key, and we start processing child keys (and // should call `migrate_child`). - self.migrate_child(); + self.migrate_child() } }, (Progress::LastKey(_), Progress::Complete) => { // we're done with migrating a child-root. - self.migrate_top(); + self.migrate_top()?; self.progress_child = Progress::ToStart; + Ok(()) }, (Progress::Complete, _) => { // nada + Ok(()) }, } } @@ -314,24 +332,30 @@ pub mod pallet { /// Migrate the current child key, setting it to its new value, if one exists. /// /// It updates the dynamic counters. - fn migrate_child(&mut self) { + fn migrate_child(&mut self) -> DispatchResult { use sp_io::default_child_storage as child_io; let (maybe_current_child, child_root) = match (&self.progress_child, &self.progress_top) { (Progress::LastKey(last_child), Progress::LastKey(last_top)) => { let child_root = Pallet::::transform_child_key_or_halt(last_top); - let maybe_current_child = child_io::next_key(child_root, last_child); + let maybe_current_child: Option> = + if let Some(next) = child_io::next_key(child_root, last_child) { + Some(next.try_into().map_err(|_| Error::::KeyTooLong)?) + } else { + None + }; + (maybe_current_child, child_root) }, (Progress::ToStart, Progress::LastKey(last_top)) => { let child_root = Pallet::::transform_child_key_or_halt(last_top); // Start with the empty key as first key. - (Some(Vec::new()), child_root) + (Some(Default::default()), child_root) }, _ => { // defensive: there must be an ongoing top migration. frame_support::defensive!("cannot migrate child key."); - return + return Ok(()) }, }; @@ -350,21 +374,30 @@ pub mod pallet { self.progress_child = match maybe_current_child { Some(last_child) => Progress::LastKey(last_child), None => Progress::Complete, - } + }; + Ok(()) } /// Migrate the current top key, setting it to its new value, if one exists. /// /// It updates the dynamic counters. - fn migrate_top(&mut self) { + fn migrate_top(&mut self) -> DispatchResult { let maybe_current_top = match &self.progress_top { - Progress::LastKey(last_top) => sp_io::storage::next_key(last_top), + Progress::LastKey(last_top) => { + let maybe_top: Option> = + if let Some(next) = sp_io::storage::next_key(last_top) { + Some(next.try_into().map_err(|_| Error::::KeyTooLong)?) + } else { + None + }; + maybe_top + }, // Start with the empty key as first key. - Progress::ToStart => Some(Vec::new()), + Progress::ToStart => Some(Default::default()), Progress::Complete => { // defensive: there must be an ongoing top migration. frame_support::defensive!("cannot migrate top key."); - return + return Ok(()) }, }; @@ -383,12 +416,24 @@ pub mod pallet { self.progress_top = match maybe_current_top { Some(last_top) => Progress::LastKey(last_top), None => Progress::Complete, - } + }; + Ok(()) } } /// The limits of a migration. - #[derive(Clone, Copy, Encode, Decode, scale_info::TypeInfo, Default, Debug, PartialEq, Eq)] + #[derive( + Clone, + Copy, + Encode, + Decode, + scale_info::TypeInfo, + Default, + Debug, + PartialEq, + Eq, + MaxEncodedLen, + )] pub struct MigrationLimits { /// The byte size limit. pub size: u32, @@ -416,14 +461,13 @@ pub mod pallet { Slashed { who: T::AccountId, amount: BalanceOf }, /// The auto migration task finished. AutoMigrationFinished, - /// Migration got halted. + /// Migration got halted due to an error or miss-configuration. Halted, } /// The outer Pallet struct. #[pallet::pallet] #[pallet::generate_store(pub(crate) trait Store)] - #[pallet::without_storage_info] pub struct Pallet(_); /// Configurations of this pallet. @@ -441,6 +485,31 @@ pub mod pallet { /// The currency provider type. type Currency: Currency; + /// Maximal number of bytes that a key can have. + /// + /// FRAME itself does not limit the key length. + /// The concrete value must therefore depend on your storage usage. + /// A [`frame_support::storage::StorageNMap`] for example can have an arbitrary number of + /// keys which are then hashed and concatenated, resulting in arbitrarily long keys. + /// + /// Use the *state migration RPC* to retrieve the length of the longest key in your + /// storage: + /// + /// The migration will halt with a `Halted` event if this value is too small. + /// Since there is no real penalty from over-estimating, it is advised to use a large + /// value. The default is 512 byte. + /// + /// Some key lengths for reference: + /// - [`frame_support::storage::StorageValue`]: 32 byte + /// - [`frame_support::storage::StorageMap`]: 64 byte + /// - [`frame_support::storage::StorageDoubleMap`]: 96 byte + /// + /// For more info see + /// + + #[pallet::constant] + type MaxKeyLen: Get; + /// The amount of deposit collected per item in advance, for signed migrations. /// /// This should reflect the average storage value size in the worse case. @@ -481,6 +550,14 @@ pub mod pallet { pub enum Error { /// max signed limits not respected. MaxSignedLimits, + /// A key was longer than the configured maximum. + /// + /// This means that the migration halted at the current [`Progress`] and + /// can be resumed with a larger [`crate::Config::MaxKeyLen`] value. + /// Retrying with the same [`crate::Config::MaxKeyLen`] value will not work. + /// The value should only be increased to avoid a storage migration for the currently + /// stored [`crate::Progress::LastKey`]. + KeyTooLong, /// submitter does not have enough funds. NotEnoughFunds, /// bad witness data provided. @@ -563,7 +640,7 @@ pub mod pallet { } } ); - task.migrate_until_exhaustion(limits); + let migration = task.migrate_until_exhaustion(limits); // ensure that the migration witness data was correct. if real_size_upper < task.dyn_size { @@ -587,7 +664,14 @@ pub mod pallet { ); MigrationProcess::::put(task); - Ok((actual_weight, Pays::No).into()) + let post_info = PostDispatchInfo { actual_weight, pays_fee: Pays::No }; + match migration { + Ok(_) => Ok(post_info), + Err(error) => { + Self::halt(&error); + Err(DispatchErrorWithPostInfo { post_info, error }) + }, + } } /// Migrate the list of top keys by iterating each of them one by one. @@ -735,8 +819,8 @@ pub mod pallet { #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))] pub fn force_set_progress( origin: OriginFor, - progress_top: Progress, - progress_child: Progress, + progress_top: ProgressOf, + progress_child: ProgressOf, ) -> DispatchResult { let _ = T::ControlOrigin::ensure_origin(origin)?; MigrationProcess::::mutate(|task| { @@ -752,7 +836,9 @@ pub mod pallet { fn on_initialize(_: BlockNumberFor) -> Weight { if let Some(limits) = Self::auto_limits() { let mut task = Self::migration_process(); - task.migrate_until_exhaustion(limits); + if let Err(e) = task.migrate_until_exhaustion(limits) { + Self::halt(&e); + } let weight = Self::dynamic_weight(task.dyn_total_items(), task.dyn_size); log!( @@ -793,8 +879,9 @@ pub mod pallet { .saturating_add(T::WeightInfo::process_top_key(size)) } - /// Put a stop to all ongoing migrations. - fn halt() { + /// Put a stop to all ongoing migrations and logs an error. + fn halt(msg: &E) { + log!(error, "migration halted due to: {:?}", msg); AutoLimits::::kill(); Self::deposit_event(Event::::Halted); } @@ -815,7 +902,7 @@ pub mod pallet { fn transform_child_key_or_halt(root: &Vec) -> &[u8] { let key = Self::transform_child_key(root); if key.is_none() { - Self::halt(); + Self::halt("bad child root key"); } key.unwrap_or_default() } @@ -858,7 +945,7 @@ mod benchmarks { continue_migrate_wrong_witness { let null = MigrationLimits::default(); let caller = frame_benchmarking::whitelisted_caller(); - let bad_witness = MigrationTask { progress_top: Progress::LastKey(vec![1u8]), ..Default::default() }; + let bad_witness = MigrationTask { progress_top: Progress::LastKey(vec![1u8].try_into().unwrap()), ..Default::default() }; }: { assert!( StateTrieMigration::::continue_migrate( @@ -1046,6 +1133,7 @@ mod mock { parameter_types! { pub const SignedDepositPerItem: u64 = 1; pub const SignedDepositBase: u64 = 5; + pub const MigrationMaxKeyLen: u32 = 512; } impl pallet_balances::Config for Test { @@ -1064,6 +1152,7 @@ mod mock { type Event = Event; type ControlOrigin = EnsureRoot; type Currency = Balances; + type MaxKeyLen = MigrationMaxKeyLen; type SignedDepositPerItem = SignedDepositPerItem; type SignedDepositBase = SignedDepositBase; type SignedFilter = EnsureSigned; @@ -1171,6 +1260,7 @@ mod mock { #[cfg(test)] mod test { use super::{mock::*, *}; + use frame_support::{bounded_vec, dispatch::*}; use sp_runtime::{traits::Bounded, StateVersion}; #[test] @@ -1185,6 +1275,80 @@ mod test { assert_ne!(root1, root2); } + #[test] + fn halts_if_top_key_too_long() { + let bad_key = vec![1u8; MigrationMaxKeyLen::get() as usize + 1]; + let bad_top_keys = vec![(bad_key.clone(), vec![])]; + + new_test_ext(StateVersion::V0, true, Some(bad_top_keys), None).execute_with(|| { + System::set_block_number(1); + assert_eq!(MigrationProcess::::get(), Default::default()); + + // Allow signed migrations. + SignedMigrationMaxLimits::::put(MigrationLimits { size: 1 << 20, item: 50 }); + + // fails if the top key is too long. + frame_support::assert_err_with_weight!( + StateTrieMigration::continue_migrate( + Origin::signed(1), + MigrationLimits { item: 50, size: 1 << 20 }, + Bounded::max_value(), + MigrationProcess::::get() + ), + Error::::KeyTooLong, + Some(2000000), + ); + // The auto migration halted. + System::assert_last_event(crate::Event::Halted {}.into()); + // Limits are killed. + assert!(AutoLimits::::get().is_none()); + + // Calling `migrate_until_exhaustion` also fails. + let mut task = StateTrieMigration::migration_process(); + let result = task.migrate_until_exhaustion( + StateTrieMigration::signed_migration_max_limits().unwrap(), + ); + assert!(result.is_err()); + }); + } + + #[test] + fn halts_if_child_key_too_long() { + let bad_key = vec![1u8; MigrationMaxKeyLen::get() as usize + 1]; + let bad_child_keys = vec![(bad_key.clone(), vec![], vec![])]; + + new_test_ext(StateVersion::V0, true, None, Some(bad_child_keys)).execute_with(|| { + System::set_block_number(1); + assert_eq!(MigrationProcess::::get(), Default::default()); + + // Allow signed migrations. + SignedMigrationMaxLimits::::put(MigrationLimits { size: 1 << 20, item: 50 }); + + // fails if the top key is too long. + frame_support::assert_err_with_weight!( + StateTrieMigration::continue_migrate( + Origin::signed(1), + MigrationLimits { item: 50, size: 1 << 20 }, + Bounded::max_value(), + MigrationProcess::::get() + ), + Error::::KeyTooLong, + Some(2000000), + ); + // The auto migration halted. + System::assert_last_event(crate::Event::Halted {}.into()); + // Limits are killed. + assert!(AutoLimits::::get().is_none()); + + // Calling `migrate_until_exhaustion` also fails. + let mut task = StateTrieMigration::migration_process(); + let result = task.migrate_until_exhaustion( + StateTrieMigration::signed_migration_max_limits().unwrap(), + ); + assert!(result.is_err()); + }); + } + #[test] fn detects_value_in_empty_top_key() { let limit = MigrationLimits { item: 1, size: 1000 }; @@ -1319,7 +1483,7 @@ mod test { MigrationLimits { item: 5, size: 100 }, 100, MigrationTask { - progress_top: Progress::LastKey(vec![1u8]), + progress_top: Progress::LastKey(bounded_vec![1u8]), ..Default::default() } ), @@ -1330,9 +1494,10 @@ mod test { while !MigrationProcess::::get().finished() { // first we compute the task to get the accurate consumption. let mut task = StateTrieMigration::migration_process(); - task.migrate_until_exhaustion( + let result = task.migrate_until_exhaustion( StateTrieMigration::signed_migration_max_limits().unwrap(), ); + assert!(result.is_ok()); frame_support::assert_ok!(StateTrieMigration::continue_migrate( Origin::signed(1), @@ -1453,6 +1618,7 @@ pub(crate) mod remote_tests { use sp_runtime::traits::{Block as BlockT, HashFor, Header as _, One}; use thousands::Separable; + #[allow(dead_code)] fn run_to_block>( n: ::BlockNumber, ) -> (H256, u64) { @@ -1474,6 +1640,7 @@ pub(crate) mod remote_tests { /// Run the entire migration, against the given `Runtime`, until completion. /// /// This will print some very useful statistics, make sure [`crate::LOG_TARGET`] is enabled. + #[allow(dead_code)] pub(crate) async fn run_with_limits< Runtime: crate::Config, Block: BlockT + serde::de::DeserializeOwned, @@ -1571,8 +1738,9 @@ mod remote_tests_local { remote_tests::run_with_limits, *, }; - use remote_externalities::{Mode, OfflineConfig, OnlineConfig}; + use remote_externalities::{Mode, OfflineConfig, OnlineConfig, SnapshotConfig}; use sp_runtime::traits::Bounded; + use std::env::var as env_var; // we only use the hash type from this, so using the mock should be fine. type Extrinsic = sp_runtime::testing::TestXt; @@ -1580,14 +1748,13 @@ mod remote_tests_local { #[tokio::test] async fn on_initialize_migration() { + let snap: SnapshotConfig = env_var("SNAP").expect("Need SNAP env var").into(); + let ws_api = env_var("WS_API").expect("Need WS_API env var").into(); + sp_tracing::try_init_simple(); let mode = Mode::OfflineOrElseOnline( - OfflineConfig { state_snapshot: env!("SNAP").to_owned().into() }, - OnlineConfig { - transport: std::env!("WS_API").to_owned().into(), - state_snapshot: Some(env!("SNAP").to_owned().into()), - ..Default::default() - }, + OfflineConfig { state_snapshot: snap.clone() }, + OnlineConfig { transport: ws_api, state_snapshot: Some(snap), ..Default::default() }, ); // item being the bottleneck diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index fa37fa5cda22b..96bc1257c7598 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -762,7 +762,7 @@ macro_rules! assert_err_with_weight { ($call:expr, $err:expr, $weight:expr $(,)? ) => { if let Err(dispatch_err_with_post) = $call { $crate::assert_err!($call.map(|_| ()).map_err(|e| e.error), $err); - assert_eq!(dispatch_err_with_post.post_info.actual_weight, $weight.into()); + assert_eq!(dispatch_err_with_post.post_info.actual_weight, $weight); } else { panic!("expected Err(_), got Ok(_).") } From ad501932089332bae8cc49be5122e17146c6ce0e Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Mon, 13 Jun 2022 14:31:42 +0200 Subject: [PATCH 329/484] Move bounded type definitions to sp-runtime (#11645) * Move bounded type definitions to sp-runtime * cargo fmt * Fix compile error Signed-off-by: Oliver Tale-Yazdi * Move TryCollect to sp-runtime * Write some docs * Import missing types Co-authored-by: Oliver Tale-Yazdi --- frame/support/src/lib.rs | 43 +- .../support/src/storage/bounded_btree_map.rs | 489 +-------- .../support/src/storage/bounded_btree_set.rs | 451 +------- frame/support/src/storage/bounded_vec.rs | 976 +---------------- frame/support/src/storage/weak_bounded_vec.rs | 367 +------ frame/support/src/traits/misc.rs | 12 +- primitives/runtime/src/bounded.rs | 28 + .../runtime/src/bounded/bounded_btree_map.rs | 517 +++++++++ .../runtime/src/bounded/bounded_btree_set.rs | 479 +++++++++ primitives/runtime/src/bounded/bounded_vec.rs | 998 ++++++++++++++++++ .../runtime/src/bounded/weak_bounded_vec.rs | 393 +++++++ primitives/runtime/src/lib.rs | 43 + primitives/runtime/src/traits.rs | 11 + 13 files changed, 2484 insertions(+), 2323 deletions(-) create mode 100644 primitives/runtime/src/bounded.rs create mode 100644 primitives/runtime/src/bounded/bounded_btree_map.rs create mode 100644 primitives/runtime/src/bounded/bounded_btree_set.rs create mode 100644 primitives/runtime/src/bounded/bounded_vec.rs create mode 100644 primitives/runtime/src/bounded/weak_bounded_vec.rs diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index 96bc1257c7598..f2cd7d1e165ec 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -45,6 +45,9 @@ pub use sp_core::Void; pub use sp_core_hashing_proc_macro; #[doc(hidden)] pub use sp_io::{self, storage::root as storage_root}; +#[cfg(feature = "std")] +#[doc(hidden)] +pub use sp_runtime::{bounded_btree_map, bounded_vec}; #[doc(hidden)] pub use sp_runtime::{RuntimeDebug, StateVersion}; #[cfg(feature = "std")] @@ -119,46 +122,6 @@ impl TypeId for PalletId { const TYPE_ID: [u8; 4] = *b"modl"; } -/// Build a bounded vec from the given literals. -/// -/// The type of the outcome must be known. -/// -/// Will not handle any errors and just panic if the given literals cannot fit in the corresponding -/// bounded vec type. Thus, this is only suitable for testing and non-consensus code. -#[macro_export] -#[cfg(feature = "std")] -macro_rules! bounded_vec { - ($ ($values:expr),* $(,)?) => { - { - $crate::sp_std::vec![$($values),*].try_into().unwrap() - } - }; - ( $value:expr ; $repetition:expr ) => { - { - $crate::sp_std::vec![$value ; $repetition].try_into().unwrap() - } - } -} - -/// Build a bounded btree-map from the given literals. -/// -/// The type of the outcome must be known. -/// -/// Will not handle any errors and just panic if the given literals cannot fit in the corresponding -/// bounded vec type. Thus, this is only suitable for testing and non-consensus code. -#[macro_export] -#[cfg(feature = "std")] -macro_rules! bounded_btree_map { - ($ ( $key:expr => $value:expr ),* $(,)?) => { - { - $crate::traits::TryCollect::<$crate::BoundedBTreeMap<_, _, _>>::try_collect( - $crate::sp_std::vec![$(($key, $value)),*].into_iter() - ).unwrap() - } - }; - -} - /// Generate a new type alias for [`storage::types::StorageValue`], /// [`storage::types::StorageMap`], [`storage::types::StorageDoubleMap`] /// and [`storage::types::StorageNMap`]. diff --git a/frame/support/src/storage/bounded_btree_map.rs b/frame/support/src/storage/bounded_btree_map.rs index fd086f1fb23a1..d567faa38fdc1 100644 --- a/frame/support/src/storage/bounded_btree_map.rs +++ b/frame/support/src/storage/bounded_btree_map.rs @@ -17,354 +17,18 @@ //! Traits, types and structs to support a bounded BTreeMap. -use crate::{ - storage::StorageDecodeLength, - traits::{Get, TryCollect}, -}; -use codec::{Decode, Encode, MaxEncodedLen}; -use sp_std::{borrow::Borrow, collections::btree_map::BTreeMap, marker::PhantomData, ops::Deref}; - -/// A bounded map based on a B-Tree. -/// -/// B-Trees represent a fundamental compromise between cache-efficiency and actually minimizing -/// the amount of work performed in a search. See [`BTreeMap`] for more details. -/// -/// Unlike a standard `BTreeMap`, there is an enforced upper limit to the number of items in the -/// map. All internal operations ensure this bound is respected. -#[derive(Encode, scale_info::TypeInfo)] -#[scale_info(skip_type_params(S))] -pub struct BoundedBTreeMap(BTreeMap, PhantomData); - -impl Decode for BoundedBTreeMap -where - K: Decode + Ord, - V: Decode, - S: Get, -{ - fn decode(input: &mut I) -> Result { - let inner = BTreeMap::::decode(input)?; - if inner.len() > S::get() as usize { - return Err("BoundedBTreeMap exceeds its limit".into()) - } - Ok(Self(inner, PhantomData)) - } - - fn skip(input: &mut I) -> Result<(), codec::Error> { - BTreeMap::::skip(input) - } -} - -impl BoundedBTreeMap -where - S: Get, -{ - /// Get the bound of the type in `usize`. - pub fn bound() -> usize { - S::get() as usize - } -} - -impl BoundedBTreeMap -where - K: Ord, - S: Get, -{ - /// Create `Self` from `t` without any checks. - fn unchecked_from(t: BTreeMap) -> Self { - Self(t, Default::default()) - } - - /// Exactly the same semantics as `BTreeMap::retain`. - /// - /// The is a safe `&mut self` borrow because `retain` can only ever decrease the length of the - /// inner map. - pub fn retain bool>(&mut self, f: F) { - self.0.retain(f) - } - - /// Create a new `BoundedBTreeMap`. - /// - /// Does not allocate. - pub fn new() -> Self { - BoundedBTreeMap(BTreeMap::new(), PhantomData) - } - - /// Consume self, and return the inner `BTreeMap`. - /// - /// This is useful when a mutating API of the inner type is desired, and closure-based mutation - /// such as provided by [`try_mutate`][Self::try_mutate] is inconvenient. - pub fn into_inner(self) -> BTreeMap { - debug_assert!(self.0.len() <= Self::bound()); - self.0 - } - - /// Consumes self and mutates self via the given `mutate` function. - /// - /// If the outcome of mutation is within bounds, `Some(Self)` is returned. Else, `None` is - /// returned. - /// - /// This is essentially a *consuming* shorthand [`Self::into_inner`] -> `...` -> - /// [`Self::try_from`]. - pub fn try_mutate(mut self, mut mutate: impl FnMut(&mut BTreeMap)) -> Option { - mutate(&mut self.0); - (self.0.len() <= Self::bound()).then(move || self) - } - - // Clears the map, removing all elements. - pub fn clear(&mut self) { - self.0.clear() - } - - /// Return a mutable reference to the value corresponding to the key. - /// - /// The key may be any borrowed form of the map's key type, but the ordering on the borrowed - /// form _must_ match the ordering on the key type. - pub fn get_mut(&mut self, key: &Q) -> Option<&mut V> - where - K: Borrow, - Q: Ord + ?Sized, - { - self.0.get_mut(key) - } - - /// Exactly the same semantics as [`BTreeMap::insert`], but returns an `Err` (and is a noop) if - /// the new length of the map exceeds `S`. - /// - /// In the `Err` case, returns the inserted pair so it can be further used without cloning. - pub fn try_insert(&mut self, key: K, value: V) -> Result, (K, V)> { - if self.len() < Self::bound() || self.0.contains_key(&key) { - Ok(self.0.insert(key, value)) - } else { - Err((key, value)) - } - } - - /// Remove a key from the map, returning the value at the key if the key was previously in the - /// map. - /// - /// The key may be any borrowed form of the map's key type, but the ordering on the borrowed - /// form _must_ match the ordering on the key type. - pub fn remove(&mut self, key: &Q) -> Option - where - K: Borrow, - Q: Ord + ?Sized, - { - self.0.remove(key) - } - - /// Remove a key from the map, returning the value at the key if the key was previously in the - /// map. - /// - /// The key may be any borrowed form of the map's key type, but the ordering on the borrowed - /// form _must_ match the ordering on the key type. - pub fn remove_entry(&mut self, key: &Q) -> Option<(K, V)> - where - K: Borrow, - Q: Ord + ?Sized, - { - self.0.remove_entry(key) - } -} - -impl Default for BoundedBTreeMap -where - K: Ord, - S: Get, -{ - fn default() -> Self { - Self::new() - } -} - -impl Clone for BoundedBTreeMap -where - BTreeMap: Clone, -{ - fn clone(&self) -> Self { - BoundedBTreeMap(self.0.clone(), PhantomData) - } -} - -#[cfg(feature = "std")] -impl std::fmt::Debug for BoundedBTreeMap -where - BTreeMap: std::fmt::Debug, - S: Get, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("BoundedBTreeMap").field(&self.0).field(&Self::bound()).finish() - } -} - -impl PartialEq> for BoundedBTreeMap -where - BTreeMap: PartialEq, - S1: Get, - S2: Get, -{ - fn eq(&self, other: &BoundedBTreeMap) -> bool { - S1::get() == S2::get() && self.0 == other.0 - } -} - -impl Eq for BoundedBTreeMap -where - BTreeMap: Eq, - S: Get, -{ -} - -impl PartialEq> for BoundedBTreeMap -where - BTreeMap: PartialEq, -{ - fn eq(&self, other: &BTreeMap) -> bool { - self.0 == *other - } -} - -impl PartialOrd for BoundedBTreeMap -where - BTreeMap: PartialOrd, - S: Get, -{ - fn partial_cmp(&self, other: &Self) -> Option { - self.0.partial_cmp(&other.0) - } -} - -impl Ord for BoundedBTreeMap -where - BTreeMap: Ord, - S: Get, -{ - fn cmp(&self, other: &Self) -> sp_std::cmp::Ordering { - self.0.cmp(&other.0) - } -} - -impl IntoIterator for BoundedBTreeMap { - type Item = (K, V); - type IntoIter = sp_std::collections::btree_map::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl<'a, K, V, S> IntoIterator for &'a BoundedBTreeMap { - type Item = (&'a K, &'a V); - type IntoIter = sp_std::collections::btree_map::Iter<'a, K, V>; - - fn into_iter(self) -> Self::IntoIter { - self.0.iter() - } -} - -impl<'a, K, V, S> IntoIterator for &'a mut BoundedBTreeMap { - type Item = (&'a K, &'a mut V); - type IntoIter = sp_std::collections::btree_map::IterMut<'a, K, V>; - - fn into_iter(self) -> Self::IntoIter { - self.0.iter_mut() - } -} - -impl MaxEncodedLen for BoundedBTreeMap -where - K: MaxEncodedLen, - V: MaxEncodedLen, - S: Get, -{ - fn max_encoded_len() -> usize { - Self::bound() - .saturating_mul(K::max_encoded_len().saturating_add(V::max_encoded_len())) - .saturating_add(codec::Compact(S::get()).encoded_size()) - } -} - -impl Deref for BoundedBTreeMap -where - K: Ord, -{ - type Target = BTreeMap; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl AsRef> for BoundedBTreeMap -where - K: Ord, -{ - fn as_ref(&self) -> &BTreeMap { - &self.0 - } -} - -impl From> for BTreeMap -where - K: Ord, -{ - fn from(map: BoundedBTreeMap) -> Self { - map.0 - } -} - -impl TryFrom> for BoundedBTreeMap -where - K: Ord, - S: Get, -{ - type Error = (); - - fn try_from(value: BTreeMap) -> Result { - (value.len() <= Self::bound()) - .then(move || BoundedBTreeMap(value, PhantomData)) - .ok_or(()) - } -} - -impl codec::DecodeLength for BoundedBTreeMap { - fn len(self_encoded: &[u8]) -> Result { - // `BoundedBTreeMap` is stored just a `BTreeMap`, which is stored as a - // `Compact` with its length followed by an iteration of its items. We can just use - // the underlying implementation. - as codec::DecodeLength>::len(self_encoded) - } -} +use crate::storage::StorageDecodeLength; +pub use sp_runtime::BoundedBTreeMap; impl StorageDecodeLength for BoundedBTreeMap {} -impl codec::EncodeLike> for BoundedBTreeMap where - BTreeMap: Encode -{ -} - -impl TryCollect> for I -where - K: Ord, - I: ExactSizeIterator + Iterator, - Bound: Get, -{ - type Error = &'static str; - - fn try_collect(self) -> Result, Self::Error> { - if self.len() > Bound::get() as usize { - Err("iterator length too big") - } else { - Ok(BoundedBTreeMap::::unchecked_from(self.collect::>())) - } - } -} - #[cfg(test)] pub mod test { use super::*; use crate::Twox128; - use frame_support::traits::ConstU32; + use frame_support::traits::{ConstU32, Get}; use sp_io::TestExternalities; + use sp_std::collections::btree_map::BTreeMap; #[crate::storage_alias] type Foo = StorageValue>>; @@ -416,149 +80,4 @@ pub mod test { assert!(FooDoubleMap::decode_len(2, 2).is_none()); }); } - - #[test] - fn try_insert_works() { - let mut bounded = boundedmap_from_keys::>(&[1, 2, 3]); - bounded.try_insert(0, ()).unwrap(); - assert_eq!(*bounded, map_from_keys(&[1, 0, 2, 3])); - - assert!(bounded.try_insert(9, ()).is_err()); - assert_eq!(*bounded, map_from_keys(&[1, 0, 2, 3])); - } - - #[test] - fn deref_coercion_works() { - let bounded = boundedmap_from_keys::>(&[1, 2, 3]); - // these methods come from deref-ed vec. - assert_eq!(bounded.len(), 3); - assert!(bounded.iter().next().is_some()); - assert!(!bounded.is_empty()); - } - - #[test] - fn try_mutate_works() { - let bounded = boundedmap_from_keys::>(&[1, 2, 3, 4, 5, 6]); - let bounded = bounded - .try_mutate(|v| { - v.insert(7, ()); - }) - .unwrap(); - assert_eq!(bounded.len(), 7); - assert!(bounded - .try_mutate(|v| { - v.insert(8, ()); - }) - .is_none()); - } - - #[test] - fn btree_map_eq_works() { - let bounded = boundedmap_from_keys::>(&[1, 2, 3, 4, 5, 6]); - assert_eq!(bounded, map_from_keys(&[1, 2, 3, 4, 5, 6])); - } - - #[test] - fn too_big_fail_to_decode() { - let v: Vec<(u32, u32)> = vec![(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]; - assert_eq!( - BoundedBTreeMap::>::decode(&mut &v.encode()[..]), - Err("BoundedBTreeMap exceeds its limit".into()), - ); - } - - #[test] - fn unequal_eq_impl_insert_works() { - // given a struct with a strange notion of equality - #[derive(Debug)] - struct Unequal(u32, bool); - - impl PartialEq for Unequal { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } - } - impl Eq for Unequal {} - - impl Ord for Unequal { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.0.cmp(&other.0) - } - } - - impl PartialOrd for Unequal { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } - } - - let mut map = BoundedBTreeMap::>::new(); - - // when the set is full - - for i in 0..4 { - map.try_insert(Unequal(i, false), i).unwrap(); - } - - // can't insert a new distinct member - map.try_insert(Unequal(5, false), 5).unwrap_err(); - - // but _can_ insert a distinct member which compares equal, though per the documentation, - // neither the set length nor the actual member are changed, but the value is - map.try_insert(Unequal(0, true), 6).unwrap(); - assert_eq!(map.len(), 4); - let (zero_key, zero_value) = map.get_key_value(&Unequal(0, true)).unwrap(); - assert_eq!(zero_key.0, 0); - assert_eq!(zero_key.1, false); - assert_eq!(*zero_value, 6); - } - - #[test] - fn can_be_collected() { - let b1 = boundedmap_from_keys::>(&[1, 2, 3, 4]); - let b2: BoundedBTreeMap> = - b1.iter().map(|(k, v)| (k + 1, *v)).try_collect().unwrap(); - assert_eq!(b2.into_iter().map(|(k, _)| k).collect::>(), vec![2, 3, 4, 5]); - - // can also be collected into a collection of length 4. - let b2: BoundedBTreeMap> = - b1.iter().map(|(k, v)| (k + 1, *v)).try_collect().unwrap(); - assert_eq!(b2.into_iter().map(|(k, _)| k).collect::>(), vec![2, 3, 4, 5]); - - // can be mutated further into iterators that are `ExactSizedIterator`. - let b2: BoundedBTreeMap> = - b1.iter().map(|(k, v)| (k + 1, *v)).rev().skip(2).try_collect().unwrap(); - // note that the binary tree will re-sort this, so rev() is not really seen - assert_eq!(b2.into_iter().map(|(k, _)| k).collect::>(), vec![2, 3]); - - let b2: BoundedBTreeMap> = - b1.iter().map(|(k, v)| (k + 1, *v)).take(2).try_collect().unwrap(); - assert_eq!(b2.into_iter().map(|(k, _)| k).collect::>(), vec![2, 3]); - - // but these worn't work - let b2: Result>, _> = - b1.iter().map(|(k, v)| (k + 1, *v)).try_collect(); - assert!(b2.is_err()); - - let b2: Result>, _> = - b1.iter().map(|(k, v)| (k + 1, *v)).skip(2).try_collect(); - assert!(b2.is_err()); - } - - #[test] - fn eq_works() { - // of same type - let b1 = boundedmap_from_keys::>(&[1, 2]); - let b2 = boundedmap_from_keys::>(&[1, 2]); - assert_eq!(b1, b2); - - // of different type, but same value and bound. - crate::parameter_types! { - B1: u32 = 7; - B2: u32 = 7; - } - let b1 = boundedmap_from_keys::(&[1, 2]); - let b2 = boundedmap_from_keys::(&[1, 2]); - assert_eq!(b1, b2); - } } diff --git a/frame/support/src/storage/bounded_btree_set.rs b/frame/support/src/storage/bounded_btree_set.rs index 77e1c6f1c9625..9ed129e67c46c 100644 --- a/frame/support/src/storage/bounded_btree_set.rs +++ b/frame/support/src/storage/bounded_btree_set.rs @@ -17,319 +17,18 @@ //! Traits, types and structs to support a bounded `BTreeSet`. -use crate::{ - storage::StorageDecodeLength, - traits::{Get, TryCollect}, -}; -use codec::{Decode, Encode, MaxEncodedLen}; -use sp_std::{borrow::Borrow, collections::btree_set::BTreeSet, marker::PhantomData, ops::Deref}; - -/// A bounded set based on a B-Tree. -/// -/// B-Trees represent a fundamental compromise between cache-efficiency and actually minimizing -/// the amount of work performed in a search. See [`BTreeSet`] for more details. -/// -/// Unlike a standard `BTreeSet`, there is an enforced upper limit to the number of items in the -/// set. All internal operations ensure this bound is respected. -#[derive(Encode, scale_info::TypeInfo)] -#[scale_info(skip_type_params(S))] -pub struct BoundedBTreeSet(BTreeSet, PhantomData); - -impl Decode for BoundedBTreeSet -where - T: Decode + Ord, - S: Get, -{ - fn decode(input: &mut I) -> Result { - let inner = BTreeSet::::decode(input)?; - if inner.len() > S::get() as usize { - return Err("BoundedBTreeSet exceeds its limit".into()) - } - Ok(Self(inner, PhantomData)) - } - - fn skip(input: &mut I) -> Result<(), codec::Error> { - BTreeSet::::skip(input) - } -} - -impl BoundedBTreeSet -where - S: Get, -{ - /// Get the bound of the type in `usize`. - pub fn bound() -> usize { - S::get() as usize - } -} - -impl BoundedBTreeSet -where - T: Ord, - S: Get, -{ - /// Create `Self` from `t` without any checks. - fn unchecked_from(t: BTreeSet) -> Self { - Self(t, Default::default()) - } - - /// Create a new `BoundedBTreeSet`. - /// - /// Does not allocate. - pub fn new() -> Self { - BoundedBTreeSet(BTreeSet::new(), PhantomData) - } - - /// Consume self, and return the inner `BTreeSet`. - /// - /// This is useful when a mutating API of the inner type is desired, and closure-based mutation - /// such as provided by [`try_mutate`][Self::try_mutate] is inconvenient. - pub fn into_inner(self) -> BTreeSet { - debug_assert!(self.0.len() <= Self::bound()); - self.0 - } - - /// Consumes self and mutates self via the given `mutate` function. - /// - /// If the outcome of mutation is within bounds, `Some(Self)` is returned. Else, `None` is - /// returned. - /// - /// This is essentially a *consuming* shorthand [`Self::into_inner`] -> `...` -> - /// [`Self::try_from`]. - pub fn try_mutate(mut self, mut mutate: impl FnMut(&mut BTreeSet)) -> Option { - mutate(&mut self.0); - (self.0.len() <= Self::bound()).then(move || self) - } - - // Clears the set, removing all elements. - pub fn clear(&mut self) { - self.0.clear() - } - - /// Exactly the same semantics as [`BTreeSet::insert`], but returns an `Err` (and is a noop) if - /// the new length of the set exceeds `S`. - /// - /// In the `Err` case, returns the inserted item so it can be further used without cloning. - pub fn try_insert(&mut self, item: T) -> Result { - if self.len() < Self::bound() || self.0.contains(&item) { - Ok(self.0.insert(item)) - } else { - Err(item) - } - } - - /// Remove an item from the set, returning whether it was previously in the set. - /// - /// The item may be any borrowed form of the set's item type, but the ordering on the borrowed - /// form _must_ match the ordering on the item type. - pub fn remove(&mut self, item: &Q) -> bool - where - T: Borrow, - Q: Ord + ?Sized, - { - self.0.remove(item) - } - - /// Removes and returns the value in the set, if any, that is equal to the given one. - /// - /// The value may be any borrowed form of the set's value type, but the ordering on the borrowed - /// form _must_ match the ordering on the value type. - pub fn take(&mut self, value: &Q) -> Option - where - T: Borrow + Ord, - Q: Ord + ?Sized, - { - self.0.take(value) - } -} - -impl Default for BoundedBTreeSet -where - T: Ord, - S: Get, -{ - fn default() -> Self { - Self::new() - } -} - -impl Clone for BoundedBTreeSet -where - BTreeSet: Clone, -{ - fn clone(&self) -> Self { - BoundedBTreeSet(self.0.clone(), PhantomData) - } -} - -#[cfg(feature = "std")] -impl std::fmt::Debug for BoundedBTreeSet -where - BTreeSet: std::fmt::Debug, - S: Get, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("BoundedBTreeSet").field(&self.0).field(&Self::bound()).finish() - } -} - -impl PartialEq> for BoundedBTreeSet -where - BTreeSet: PartialEq, - S1: Get, - S2: Get, -{ - fn eq(&self, other: &BoundedBTreeSet) -> bool { - S1::get() == S2::get() && self.0 == other.0 - } -} - -impl Eq for BoundedBTreeSet -where - BTreeSet: Eq, - S: Get, -{ -} - -impl PartialEq> for BoundedBTreeSet -where - BTreeSet: PartialEq, - S: Get, -{ - fn eq(&self, other: &BTreeSet) -> bool { - self.0 == *other - } -} - -impl PartialOrd for BoundedBTreeSet -where - BTreeSet: PartialOrd, - S: Get, -{ - fn partial_cmp(&self, other: &Self) -> Option { - self.0.partial_cmp(&other.0) - } -} - -impl Ord for BoundedBTreeSet -where - BTreeSet: Ord, - S: Get, -{ - fn cmp(&self, other: &Self) -> sp_std::cmp::Ordering { - self.0.cmp(&other.0) - } -} - -impl IntoIterator for BoundedBTreeSet { - type Item = T; - type IntoIter = sp_std::collections::btree_set::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl<'a, T, S> IntoIterator for &'a BoundedBTreeSet { - type Item = &'a T; - type IntoIter = sp_std::collections::btree_set::Iter<'a, T>; - - fn into_iter(self) -> Self::IntoIter { - self.0.iter() - } -} - -impl MaxEncodedLen for BoundedBTreeSet -where - T: MaxEncodedLen, - S: Get, -{ - fn max_encoded_len() -> usize { - Self::bound() - .saturating_mul(T::max_encoded_len()) - .saturating_add(codec::Compact(S::get()).encoded_size()) - } -} - -impl Deref for BoundedBTreeSet -where - T: Ord, -{ - type Target = BTreeSet; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl AsRef> for BoundedBTreeSet -where - T: Ord, -{ - fn as_ref(&self) -> &BTreeSet { - &self.0 - } -} - -impl From> for BTreeSet -where - T: Ord, -{ - fn from(set: BoundedBTreeSet) -> Self { - set.0 - } -} - -impl TryFrom> for BoundedBTreeSet -where - T: Ord, - S: Get, -{ - type Error = (); - - fn try_from(value: BTreeSet) -> Result { - (value.len() <= Self::bound()) - .then(move || BoundedBTreeSet(value, PhantomData)) - .ok_or(()) - } -} - -impl codec::DecodeLength for BoundedBTreeSet { - fn len(self_encoded: &[u8]) -> Result { - // `BoundedBTreeSet` is stored just a `BTreeSet`, which is stored as a - // `Compact` with its length followed by an iteration of its items. We can just use - // the underlying implementation. - as codec::DecodeLength>::len(self_encoded) - } -} +use crate::storage::StorageDecodeLength; +pub use sp_runtime::BoundedBTreeSet; impl StorageDecodeLength for BoundedBTreeSet {} -impl codec::EncodeLike> for BoundedBTreeSet where BTreeSet: Encode {} - -impl TryCollect> for I -where - T: Ord, - I: ExactSizeIterator + Iterator, - Bound: Get, -{ - type Error = &'static str; - - fn try_collect(self) -> Result, Self::Error> { - if self.len() > Bound::get() as usize { - Err("iterator length too big") - } else { - Ok(BoundedBTreeSet::::unchecked_from(self.collect::>())) - } - } -} - #[cfg(test)] pub mod test { use super::*; use crate::Twox128; - use frame_support::traits::ConstU32; + use frame_support::traits::{ConstU32, Get}; use sp_io::TestExternalities; + use sp_std::collections::btree_set::BTreeSet; #[crate::storage_alias] type Foo = StorageValue>>; @@ -381,146 +80,4 @@ pub mod test { assert!(FooDoubleMap::decode_len(2, 2).is_none()); }); } - - #[test] - fn try_insert_works() { - let mut bounded = boundedset_from_keys::>(&[1, 2, 3]); - bounded.try_insert(0).unwrap(); - assert_eq!(*bounded, set_from_keys(&[1, 0, 2, 3])); - - assert!(bounded.try_insert(9).is_err()); - assert_eq!(*bounded, set_from_keys(&[1, 0, 2, 3])); - } - - #[test] - fn deref_coercion_works() { - let bounded = boundedset_from_keys::>(&[1, 2, 3]); - // these methods come from deref-ed vec. - assert_eq!(bounded.len(), 3); - assert!(bounded.iter().next().is_some()); - assert!(!bounded.is_empty()); - } - - #[test] - fn try_mutate_works() { - let bounded = boundedset_from_keys::>(&[1, 2, 3, 4, 5, 6]); - let bounded = bounded - .try_mutate(|v| { - v.insert(7); - }) - .unwrap(); - assert_eq!(bounded.len(), 7); - assert!(bounded - .try_mutate(|v| { - v.insert(8); - }) - .is_none()); - } - - #[test] - fn btree_map_eq_works() { - let bounded = boundedset_from_keys::>(&[1, 2, 3, 4, 5, 6]); - assert_eq!(bounded, set_from_keys(&[1, 2, 3, 4, 5, 6])); - } - - #[test] - fn too_big_fail_to_decode() { - let v: Vec = vec![1, 2, 3, 4, 5]; - assert_eq!( - BoundedBTreeSet::>::decode(&mut &v.encode()[..]), - Err("BoundedBTreeSet exceeds its limit".into()), - ); - } - - #[test] - fn unequal_eq_impl_insert_works() { - // given a struct with a strange notion of equality - #[derive(Debug)] - struct Unequal(u32, bool); - - impl PartialEq for Unequal { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } - } - impl Eq for Unequal {} - - impl Ord for Unequal { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.0.cmp(&other.0) - } - } - - impl PartialOrd for Unequal { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } - } - - let mut set = BoundedBTreeSet::>::new(); - - // when the set is full - - for i in 0..4 { - set.try_insert(Unequal(i, false)).unwrap(); - } - - // can't insert a new distinct member - set.try_insert(Unequal(5, false)).unwrap_err(); - - // but _can_ insert a distinct member which compares equal, though per the documentation, - // neither the set length nor the actual member are changed - set.try_insert(Unequal(0, true)).unwrap(); - assert_eq!(set.len(), 4); - let zero_item = set.get(&Unequal(0, true)).unwrap(); - assert_eq!(zero_item.0, 0); - assert_eq!(zero_item.1, false); - } - - #[test] - fn can_be_collected() { - let b1 = boundedset_from_keys::>(&[1, 2, 3, 4]); - let b2: BoundedBTreeSet> = b1.iter().map(|k| k + 1).try_collect().unwrap(); - assert_eq!(b2.into_iter().collect::>(), vec![2, 3, 4, 5]); - - // can also be collected into a collection of length 4. - let b2: BoundedBTreeSet> = b1.iter().map(|k| k + 1).try_collect().unwrap(); - assert_eq!(b2.into_iter().collect::>(), vec![2, 3, 4, 5]); - - // can be mutated further into iterators that are `ExactSizedIterator`. - let b2: BoundedBTreeSet> = - b1.iter().map(|k| k + 1).rev().skip(2).try_collect().unwrap(); - // note that the binary tree will re-sort this, so rev() is not really seen - assert_eq!(b2.into_iter().collect::>(), vec![2, 3]); - - let b2: BoundedBTreeSet> = - b1.iter().map(|k| k + 1).take(2).try_collect().unwrap(); - assert_eq!(b2.into_iter().collect::>(), vec![2, 3]); - - // but these worn't work - let b2: Result>, _> = - b1.iter().map(|k| k + 1).try_collect(); - assert!(b2.is_err()); - - let b2: Result>, _> = - b1.iter().map(|k| k + 1).skip(2).try_collect(); - assert!(b2.is_err()); - } - - #[test] - fn eq_works() { - // of same type - let b1 = boundedset_from_keys::>(&[1, 2]); - let b2 = boundedset_from_keys::>(&[1, 2]); - assert_eq!(b1, b2); - - // of different type, but same value and bound. - crate::parameter_types! { - B1: u32 = 7; - B2: u32 = 7; - } - let b1 = boundedset_from_keys::(&[1, 2]); - let b2 = boundedset_from_keys::(&[1, 2]); - assert_eq!(b1, b2); - } } diff --git a/frame/support/src/storage/bounded_vec.rs b/frame/support/src/storage/bounded_vec.rs index 82ae36a82bf9f..1fa01b44ae6c4 100644 --- a/frame/support/src/storage/bounded_vec.rs +++ b/frame/support/src/storage/bounded_vec.rs @@ -20,641 +20,9 @@ use crate::{ storage::{StorageDecodeLength, StorageTryAppend}, - traits::{Get, TryCollect}, - WeakBoundedVec, + traits::Get, }; -use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; -use core::{ - ops::{Deref, Index, IndexMut, RangeBounds}, - slice::SliceIndex, -}; -#[cfg(feature = "std")] -use serde::{ - de::{Error, SeqAccess, Visitor}, - Deserialize, Deserializer, Serialize, -}; -use sp_std::{marker::PhantomData, prelude::*}; - -/// A bounded vector. -/// -/// It has implementations for efficient append and length decoding, as with a normal `Vec<_>`, once -/// put into storage as a raw value, map or double-map. -/// -/// As the name suggests, the length of the queue is always bounded. All internal operations ensure -/// this bound is respected. -#[cfg_attr(feature = "std", derive(Serialize), serde(transparent))] -#[derive(Encode, scale_info::TypeInfo)] -#[scale_info(skip_type_params(S))] -pub struct BoundedVec( - Vec, - #[cfg_attr(feature = "std", serde(skip_serializing))] PhantomData, -); - -#[cfg(feature = "std")] -impl<'de, T, S: Get> Deserialize<'de> for BoundedVec -where - T: Deserialize<'de>, -{ - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct VecVisitor>(PhantomData<(T, S)>); - - impl<'de, T, S: Get> Visitor<'de> for VecVisitor - where - T: Deserialize<'de>, - { - type Value = Vec; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a sequence") - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: SeqAccess<'de>, - { - let size = seq.size_hint().unwrap_or(0); - let max = match usize::try_from(S::get()) { - Ok(n) => n, - Err(_) => return Err(A::Error::custom("can't convert to usize")), - }; - if size > max { - Err(A::Error::custom("out of bounds")) - } else { - let mut values = Vec::with_capacity(size); - - while let Some(value) = seq.next_element()? { - values.push(value); - if values.len() > max { - return Err(A::Error::custom("out of bounds")) - } - } - - Ok(values) - } - } - } - - let visitor: VecVisitor = VecVisitor(PhantomData); - deserializer - .deserialize_seq(visitor) - .map(|v| BoundedVec::::try_from(v).map_err(|_| Error::custom("out of bounds")))? - } -} - -/// A bounded slice. -/// -/// Similar to a `BoundedVec`, but not owned and cannot be decoded. -#[derive(Encode, scale_info::TypeInfo)] -#[scale_info(skip_type_params(S))] -pub struct BoundedSlice<'a, T, S>(&'a [T], PhantomData); - -// `BoundedSlice`s encode to something which will always decode into a `BoundedVec`, -// `WeakBoundedVec`, or a `Vec`. -impl<'a, T: Encode + Decode, S: Get> EncodeLike> for BoundedSlice<'a, T, S> {} -impl<'a, T: Encode + Decode, S: Get> EncodeLike> - for BoundedSlice<'a, T, S> -{ -} -impl<'a, T: Encode + Decode, S: Get> EncodeLike> for BoundedSlice<'a, T, S> {} - -impl> PartialOrd for BoundedVec { - fn partial_cmp(&self, other: &Self) -> Option { - self.0.partial_cmp(&other.0) - } -} - -impl> Ord for BoundedVec { - fn cmp(&self, other: &Self) -> sp_std::cmp::Ordering { - self.0.cmp(&other.0) - } -} - -impl<'a, T, S: Get> TryFrom<&'a [T]> for BoundedSlice<'a, T, S> { - type Error = (); - fn try_from(t: &'a [T]) -> Result { - if t.len() <= S::get() as usize { - Ok(BoundedSlice(t, PhantomData)) - } else { - Err(()) - } - } -} - -impl<'a, T, S> From> for &'a [T] { - fn from(t: BoundedSlice<'a, T, S>) -> Self { - t.0 - } -} - -impl<'a, T, S> sp_std::iter::IntoIterator for BoundedSlice<'a, T, S> { - type Item = &'a T; - type IntoIter = sp_std::slice::Iter<'a, T>; - fn into_iter(self) -> Self::IntoIter { - self.0.iter() - } -} - -impl> Decode for BoundedVec { - fn decode(input: &mut I) -> Result { - let inner = Vec::::decode(input)?; - if inner.len() > S::get() as usize { - return Err("BoundedVec exceeds its limit".into()) - } - Ok(Self(inner, PhantomData)) - } - - fn skip(input: &mut I) -> Result<(), codec::Error> { - Vec::::skip(input) - } -} - -// `BoundedVec`s encode to something which will always decode as a `Vec`. -impl> EncodeLike> for BoundedVec {} - -impl BoundedVec { - /// Create `Self` from `t` without any checks. - fn unchecked_from(t: Vec) -> Self { - Self(t, Default::default()) - } - - /// Consume self, and return the inner `Vec`. Henceforth, the `Vec<_>` can be altered in an - /// arbitrary way. At some point, if the reverse conversion is required, `TryFrom>` can - /// be used. - /// - /// This is useful for cases if you need access to an internal API of the inner `Vec<_>` which - /// is not provided by the wrapper `BoundedVec`. - pub fn into_inner(self) -> Vec { - self.0 - } - - /// Exactly the same semantics as [`slice::sort_by`]. - /// - /// This is safe since sorting cannot change the number of elements in the vector. - pub fn sort_by(&mut self, compare: F) - where - F: FnMut(&T, &T) -> sp_std::cmp::Ordering, - { - self.0.sort_by(compare) - } - - /// Exactly the same semantics as [`slice::sort`]. - /// - /// This is safe since sorting cannot change the number of elements in the vector. - pub fn sort(&mut self) - where - T: sp_std::cmp::Ord, - { - self.0.sort() - } - - /// Exactly the same semantics as `Vec::remove`. - /// - /// # Panics - /// - /// Panics if `index` is out of bounds. - pub fn remove(&mut self, index: usize) -> T { - self.0.remove(index) - } - - /// Exactly the same semantics as `slice::swap_remove`. - /// - /// # Panics - /// - /// Panics if `index` is out of bounds. - pub fn swap_remove(&mut self, index: usize) -> T { - self.0.swap_remove(index) - } - - /// Exactly the same semantics as `Vec::retain`. - pub fn retain bool>(&mut self, f: F) { - self.0.retain(f) - } - - /// Exactly the same semantics as `slice::get_mut`. - pub fn get_mut>( - &mut self, - index: I, - ) -> Option<&mut >::Output> { - self.0.get_mut(index) - } - - /// Exactly the same semantics as `Vec::truncate`. - /// - /// This is safe because `truncate` can never increase the length of the internal vector. - pub fn truncate(&mut self, s: usize) { - self.0.truncate(s); - } - - /// Exactly the same semantics as `Vec::pop`. - /// - /// This is safe since popping can only shrink the inner vector. - pub fn pop(&mut self) -> Option { - self.0.pop() - } - - /// Exactly the same semantics as [`slice::iter_mut`]. - pub fn iter_mut(&mut self) -> core::slice::IterMut<'_, T> { - self.0.iter_mut() - } - - /// Exactly the same semantics as [`slice::last_mut`]. - pub fn last_mut(&mut self) -> Option<&mut T> { - self.0.last_mut() - } - - /// Exact same semantics as [`Vec::drain`]. - pub fn drain(&mut self, range: R) -> sp_std::vec::Drain<'_, T> - where - R: RangeBounds, - { - self.0.drain(range) - } -} - -impl> From> for Vec { - fn from(x: BoundedVec) -> Vec { - x.0 - } -} - -impl> BoundedVec { - /// Pre-allocate `capacity` items in self. - /// - /// If `capacity` is greater than [`Self::bound`], then the minimum of the two is used. - pub fn with_bounded_capacity(capacity: usize) -> Self { - let capacity = capacity.min(Self::bound()); - Self(Vec::with_capacity(capacity), Default::default()) - } - - /// Allocate self with the maximum possible capacity. - pub fn with_max_capacity() -> Self { - Self::with_bounded_capacity(Self::bound()) - } - - /// Consume and truncate the vector `v` in order to create a new instance of `Self` from it. - pub fn truncate_from(mut v: Vec) -> Self { - v.truncate(Self::bound()); - Self::unchecked_from(v) - } - - /// Get the bound of the type in `usize`. - pub fn bound() -> usize { - S::get() as usize - } - - /// Returns true of this collection is full. - pub fn is_full(&self) -> bool { - self.len() >= Self::bound() - } - - /// Forces the insertion of `element` into `self` retaining all items with index at least - /// `index`. - /// - /// If `index == 0` and `self.len() == Self::bound()`, then this is a no-op. - /// - /// If `Self::bound() < index` or `self.len() < index`, then this is also a no-op. - /// - /// Returns `Ok(maybe_removed)` if the item was inserted, where `maybe_removed` is - /// `Some(removed)` if an item was removed to make room for the new one. Returns `Err(())` if - /// `element` cannot be inserted. - pub fn force_insert_keep_right( - &mut self, - index: usize, - mut element: T, - ) -> Result, ()> { - // Check against panics. - if Self::bound() < index || self.len() < index { - Err(()) - } else if self.len() < Self::bound() { - // Cannot panic since self.len() >= index; - self.0.insert(index, element); - Ok(None) - } else { - if index == 0 { - return Err(()) - } - sp_std::mem::swap(&mut self[0], &mut element); - // `[0..index] cannot panic since self.len() >= index. - // `rotate_left(1)` cannot panic because there is at least 1 element. - self[0..index].rotate_left(1); - Ok(Some(element)) - } - } - - /// Forces the insertion of `element` into `self` retaining all items with index at most - /// `index`. - /// - /// If `index == Self::bound()` and `self.len() == Self::bound()`, then this is a no-op. - /// - /// If `Self::bound() < index` or `self.len() < index`, then this is also a no-op. - /// - /// Returns `Ok(maybe_removed)` if the item was inserted, where `maybe_removed` is - /// `Some(removed)` if an item was removed to make room for the new one. Returns `Err(())` if - /// `element` cannot be inserted. - pub fn force_insert_keep_left(&mut self, index: usize, element: T) -> Result, ()> { - // Check against panics. - if Self::bound() < index || self.len() < index || Self::bound() == 0 { - return Err(()) - } - // Noop condition. - if Self::bound() == index && self.len() <= Self::bound() { - return Err(()) - } - let maybe_removed = if self.is_full() { - // defensive-only: since we are at capacity, this is a noop. - self.0.truncate(Self::bound()); - // if we truncate anything, it will be the last one. - self.0.pop() - } else { - None - }; - - // Cannot panic since `self.len() >= index`; - self.0.insert(index, element); - Ok(maybe_removed) - } - - /// Move the position of an item from one location to another in the slice. - /// - /// Except for the item being moved, the order of the slice remains the same. - /// - /// - `index` is the location of the item to be moved. - /// - `insert_position` is the index of the item in the slice which should *immediately follow* - /// the item which is being moved. - /// - /// Returns `true` of the operation was successful, otherwise `false` if a noop. - pub fn slide(&mut self, index: usize, insert_position: usize) -> bool { - // Check against panics. - if self.len() <= index || self.len() < insert_position || index == usize::MAX { - return false - } - // Noop conditions. - if index == insert_position || index + 1 == insert_position { - return false - } - if insert_position < index && index < self.len() { - // --- --- --- === === === === @@@ --- --- --- - // ^-- N ^O^ - // ... - // /-----<<<-----\ - // --- --- --- === === === === @@@ --- --- --- - // >>> >>> >>> >>> - // ... - // --- --- --- @@@ === === === === --- --- --- - // ^N^ - self[insert_position..index + 1].rotate_right(1); - return true - } else if insert_position > 0 && index + 1 < insert_position { - // Note that the apparent asymmetry of these two branches is due to the - // fact that the "new" position is the position to be inserted *before*. - // --- --- --- @@@ === === === === --- --- --- - // ^O^ ^-- N - // ... - // /----->>>-----\ - // --- --- --- @@@ === === === === --- --- --- - // <<< <<< <<< <<< - // ... - // --- --- --- === === === === @@@ --- --- --- - // ^N^ - self[index..insert_position].rotate_left(1); - return true - } - - debug_assert!(false, "all noop conditions should have been covered above"); - false - } - - /// Forces the insertion of `s` into `self` truncating first if necessary. - /// - /// Infallible, but if the bound is zero, then it's a no-op. - pub fn force_push(&mut self, element: T) { - if Self::bound() > 0 { - self.0.truncate(Self::bound() as usize - 1); - self.0.push(element); - } - } - - /// Same as `Vec::resize`, but if `size` is more than [`Self::bound`], then [`Self::bound`] is - /// used. - pub fn bounded_resize(&mut self, size: usize, value: T) - where - T: Clone, - { - let size = size.min(Self::bound()); - self.0.resize(size, value); - } - - /// Exactly the same semantics as [`Vec::extend`], but returns an error and does nothing if the - /// length of the outcome is larger than the bound. - pub fn try_extend( - &mut self, - with: impl IntoIterator + ExactSizeIterator, - ) -> Result<(), ()> { - if with.len().saturating_add(self.len()) <= Self::bound() { - self.0.extend(with); - Ok(()) - } else { - Err(()) - } - } - - /// Exactly the same semantics as [`Vec::append`], but returns an error and does nothing if the - /// length of the outcome is larger than the bound. - pub fn try_append(&mut self, other: &mut Vec) -> Result<(), ()> { - if other.len().saturating_add(self.len()) <= Self::bound() { - self.0.append(other); - Ok(()) - } else { - Err(()) - } - } - - /// Consumes self and mutates self via the given `mutate` function. - /// - /// If the outcome of mutation is within bounds, `Some(Self)` is returned. Else, `None` is - /// returned. - /// - /// This is essentially a *consuming* shorthand [`Self::into_inner`] -> `...` -> - /// [`Self::try_from`]. - pub fn try_mutate(mut self, mut mutate: impl FnMut(&mut Vec)) -> Option { - mutate(&mut self.0); - (self.0.len() <= Self::bound()).then(move || self) - } - - /// Exactly the same semantics as [`Vec::insert`], but returns an `Err` (and is a noop) if the - /// new length of the vector exceeds `S`. - /// - /// # Panics - /// - /// Panics if `index > len`. - pub fn try_insert(&mut self, index: usize, element: T) -> Result<(), ()> { - if self.len() < Self::bound() { - self.0.insert(index, element); - Ok(()) - } else { - Err(()) - } - } - - /// Exactly the same semantics as [`Vec::push`], but returns an `Err` (and is a noop) if the - /// new length of the vector exceeds `S`. - /// - /// # Panics - /// - /// Panics if the new capacity exceeds isize::MAX bytes. - pub fn try_push(&mut self, element: T) -> Result<(), ()> { - if self.len() < Self::bound() { - self.0.push(element); - Ok(()) - } else { - Err(()) - } - } -} - -impl Default for BoundedVec { - fn default() -> Self { - // the bound cannot be below 0, which is satisfied by an empty vector - Self::unchecked_from(Vec::default()) - } -} - -impl sp_std::fmt::Debug for BoundedVec -where - T: sp_std::fmt::Debug, - S: Get, -{ - fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { - f.debug_tuple("BoundedVec").field(&self.0).field(&Self::bound()).finish() - } -} - -impl Clone for BoundedVec -where - T: Clone, -{ - fn clone(&self) -> Self { - // bound is retained - Self::unchecked_from(self.0.clone()) - } -} - -impl> TryFrom> for BoundedVec { - type Error = (); - fn try_from(t: Vec) -> Result { - if t.len() <= Self::bound() { - // explicit check just above - Ok(Self::unchecked_from(t)) - } else { - Err(()) - } - } -} - -// It is okay to give a non-mutable reference of the inner vec to anyone. -impl AsRef> for BoundedVec { - fn as_ref(&self) -> &Vec { - &self.0 - } -} - -impl AsRef<[T]> for BoundedVec { - fn as_ref(&self) -> &[T] { - &self.0 - } -} - -impl AsMut<[T]> for BoundedVec { - fn as_mut(&mut self) -> &mut [T] { - &mut self.0 - } -} - -// will allow for immutable all operations of `Vec` on `BoundedVec`. -impl Deref for BoundedVec { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -// Allows for indexing similar to a normal `Vec`. Can panic if out of bound. -impl Index for BoundedVec -where - I: SliceIndex<[T]>, -{ - type Output = I::Output; - - #[inline] - fn index(&self, index: I) -> &Self::Output { - self.0.index(index) - } -} - -impl IndexMut for BoundedVec -where - I: SliceIndex<[T]>, -{ - #[inline] - fn index_mut(&mut self, index: I) -> &mut Self::Output { - self.0.index_mut(index) - } -} - -impl sp_std::iter::IntoIterator for BoundedVec { - type Item = T; - type IntoIter = sp_std::vec::IntoIter; - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl<'a, T, S> sp_std::iter::IntoIterator for &'a BoundedVec { - type Item = &'a T; - type IntoIter = sp_std::slice::Iter<'a, T>; - fn into_iter(self) -> Self::IntoIter { - self.0.iter() - } -} - -impl<'a, T, S> sp_std::iter::IntoIterator for &'a mut BoundedVec { - type Item = &'a mut T; - type IntoIter = sp_std::slice::IterMut<'a, T>; - fn into_iter(self) -> Self::IntoIter { - self.0.iter_mut() - } -} - -impl codec::DecodeLength for BoundedVec { - fn len(self_encoded: &[u8]) -> Result { - // `BoundedVec` stored just a `Vec`, thus the length is at the beginning in - // `Compact` form, and same implementation as `Vec` can be used. - as codec::DecodeLength>::len(self_encoded) - } -} - -impl PartialEq> for BoundedVec -where - T: PartialEq, - BoundSelf: Get, - BoundRhs: Get, -{ - fn eq(&self, rhs: &BoundedVec) -> bool { - BoundSelf::get() == BoundRhs::get() && self.0 == rhs.0 - } -} - -impl> PartialEq> for BoundedVec { - fn eq(&self, other: &Vec) -> bool { - &self.0 == other - } -} - -impl> Eq for BoundedVec where T: Eq {} +pub use sp_runtime::{BoundedSlice, BoundedVec}; impl StorageDecodeLength for BoundedVec {} @@ -664,38 +32,6 @@ impl> StorageTryAppend for BoundedVec { } } -impl MaxEncodedLen for BoundedVec -where - T: MaxEncodedLen, - S: Get, - BoundedVec: Encode, -{ - fn max_encoded_len() -> usize { - // BoundedVec encodes like Vec which encodes like [T], which is a compact u32 - // plus each item in the slice: - // https://docs.substrate.io/v3/advanced/scale-codec - codec::Compact(S::get()) - .encoded_size() - .saturating_add(Self::bound().saturating_mul(T::max_encoded_len())) - } -} - -impl TryCollect> for I -where - I: ExactSizeIterator + Iterator, - Bound: Get, -{ - type Error = &'static str; - - fn try_collect(self) -> Result, Self::Error> { - if self.len() > Bound::get() as usize { - Err("iterator length too big") - } else { - Ok(BoundedVec::::unchecked_from(self.collect::>())) - } - } -} - #[cfg(test)] pub mod test { use super::*; @@ -712,108 +48,6 @@ pub mod test { type FooDoubleMap = StorageDoubleMap>>; - #[test] - fn slide_works() { - let mut b: BoundedVec> = bounded_vec![0, 1, 2, 3, 4, 5]; - assert!(b.slide(1, 5)); - assert_eq!(*b, vec![0, 2, 3, 4, 1, 5]); - assert!(b.slide(4, 0)); - assert_eq!(*b, vec![1, 0, 2, 3, 4, 5]); - assert!(b.slide(0, 2)); - assert_eq!(*b, vec![0, 1, 2, 3, 4, 5]); - assert!(b.slide(1, 6)); - assert_eq!(*b, vec![0, 2, 3, 4, 5, 1]); - assert!(b.slide(0, 6)); - assert_eq!(*b, vec![2, 3, 4, 5, 1, 0]); - assert!(b.slide(5, 0)); - assert_eq!(*b, vec![0, 2, 3, 4, 5, 1]); - assert!(!b.slide(6, 0)); - assert!(!b.slide(7, 0)); - assert_eq!(*b, vec![0, 2, 3, 4, 5, 1]); - - let mut c: BoundedVec> = bounded_vec![0, 1, 2]; - assert!(!c.slide(1, 5)); - assert_eq!(*c, vec![0, 1, 2]); - assert!(!c.slide(4, 0)); - assert_eq!(*c, vec![0, 1, 2]); - assert!(!c.slide(3, 0)); - assert_eq!(*c, vec![0, 1, 2]); - assert!(c.slide(2, 0)); - assert_eq!(*c, vec![2, 0, 1]); - } - - #[test] - fn slide_noops_work() { - let mut b: BoundedVec> = bounded_vec![0, 1, 2, 3, 4, 5]; - assert!(!b.slide(3, 3)); - assert_eq!(*b, vec![0, 1, 2, 3, 4, 5]); - assert!(!b.slide(3, 4)); - assert_eq!(*b, vec![0, 1, 2, 3, 4, 5]); - } - - #[test] - fn force_insert_keep_left_works() { - let mut b: BoundedVec> = bounded_vec![]; - assert_eq!(b.force_insert_keep_left(1, 10), Err(())); - assert!(b.is_empty()); - - assert_eq!(b.force_insert_keep_left(0, 30), Ok(None)); - assert_eq!(b.force_insert_keep_left(0, 10), Ok(None)); - assert_eq!(b.force_insert_keep_left(1, 20), Ok(None)); - assert_eq!(b.force_insert_keep_left(3, 40), Ok(None)); - assert_eq!(*b, vec![10, 20, 30, 40]); - // at capacity. - assert_eq!(b.force_insert_keep_left(4, 41), Err(())); - assert_eq!(*b, vec![10, 20, 30, 40]); - assert_eq!(b.force_insert_keep_left(3, 31), Ok(Some(40))); - assert_eq!(*b, vec![10, 20, 30, 31]); - assert_eq!(b.force_insert_keep_left(1, 11), Ok(Some(31))); - assert_eq!(*b, vec![10, 11, 20, 30]); - assert_eq!(b.force_insert_keep_left(0, 1), Ok(Some(30))); - assert_eq!(*b, vec![1, 10, 11, 20]); - - let mut z: BoundedVec> = bounded_vec![]; - assert!(z.is_empty()); - assert_eq!(z.force_insert_keep_left(0, 10), Err(())); - assert!(z.is_empty()); - } - - #[test] - fn force_insert_keep_right_works() { - let mut b: BoundedVec> = bounded_vec![]; - assert_eq!(b.force_insert_keep_right(1, 10), Err(())); - assert!(b.is_empty()); - - assert_eq!(b.force_insert_keep_right(0, 30), Ok(None)); - assert_eq!(b.force_insert_keep_right(0, 10), Ok(None)); - assert_eq!(b.force_insert_keep_right(1, 20), Ok(None)); - assert_eq!(b.force_insert_keep_right(3, 40), Ok(None)); - assert_eq!(*b, vec![10, 20, 30, 40]); - - // at capacity. - assert_eq!(b.force_insert_keep_right(0, 0), Err(())); - assert_eq!(*b, vec![10, 20, 30, 40]); - assert_eq!(b.force_insert_keep_right(1, 11), Ok(Some(10))); - assert_eq!(*b, vec![11, 20, 30, 40]); - assert_eq!(b.force_insert_keep_right(3, 31), Ok(Some(11))); - assert_eq!(*b, vec![20, 30, 31, 40]); - assert_eq!(b.force_insert_keep_right(4, 41), Ok(Some(20))); - assert_eq!(*b, vec![30, 31, 40, 41]); - - assert_eq!(b.force_insert_keep_right(5, 69), Err(())); - assert_eq!(*b, vec![30, 31, 40, 41]); - - let mut z: BoundedVec> = bounded_vec![]; - assert!(z.is_empty()); - assert_eq!(z.force_insert_keep_right(0, 10), Err(())); - assert!(z.is_empty()); - } - - #[test] - fn bound_returns_correct_value() { - assert_eq!(BoundedVec::>::bound(), 7); - } - #[test] fn decode_len_works() { TestExternalities::default().execute_with(|| { @@ -839,210 +73,4 @@ pub mod test { assert!(FooDoubleMap::decode_len(2, 2).is_none()); }); } - - #[test] - fn try_insert_works() { - let mut bounded: BoundedVec> = bounded_vec![1, 2, 3]; - bounded.try_insert(1, 0).unwrap(); - assert_eq!(*bounded, vec![1, 0, 2, 3]); - - assert!(bounded.try_insert(0, 9).is_err()); - assert_eq!(*bounded, vec![1, 0, 2, 3]); - } - - #[test] - fn constructor_macro_works() { - use frame_support::bounded_vec; - - // With values. Use some brackets to make sure the macro doesn't expand. - let bv: BoundedVec<(u32, u32), ConstU32<3>> = bounded_vec![(1, 2), (1, 2), (1, 2)]; - assert_eq!(bv, vec![(1, 2), (1, 2), (1, 2)]); - - // With repetition. - let bv: BoundedVec<(u32, u32), ConstU32<3>> = bounded_vec![(1, 2); 3]; - assert_eq!(bv, vec![(1, 2), (1, 2), (1, 2)]); - } - - #[test] - #[should_panic(expected = "insertion index (is 9) should be <= len (is 3)")] - fn try_inert_panics_if_oob() { - let mut bounded: BoundedVec> = bounded_vec![1, 2, 3]; - bounded.try_insert(9, 0).unwrap(); - } - - #[test] - fn try_push_works() { - let mut bounded: BoundedVec> = bounded_vec![1, 2, 3]; - bounded.try_push(0).unwrap(); - assert_eq!(*bounded, vec![1, 2, 3, 0]); - - assert!(bounded.try_push(9).is_err()); - } - - #[test] - fn deref_coercion_works() { - let bounded: BoundedVec> = bounded_vec![1, 2, 3]; - // these methods come from deref-ed vec. - assert_eq!(bounded.len(), 3); - assert!(bounded.iter().next().is_some()); - assert!(!bounded.is_empty()); - } - - #[test] - fn try_mutate_works() { - let bounded: BoundedVec> = bounded_vec![1, 2, 3, 4, 5, 6]; - let bounded = bounded.try_mutate(|v| v.push(7)).unwrap(); - assert_eq!(bounded.len(), 7); - assert!(bounded.try_mutate(|v| v.push(8)).is_none()); - } - - #[test] - fn slice_indexing_works() { - let bounded: BoundedVec> = bounded_vec![1, 2, 3, 4, 5, 6]; - assert_eq!(&bounded[0..=2], &[1, 2, 3]); - } - - #[test] - fn vec_eq_works() { - let bounded: BoundedVec> = bounded_vec![1, 2, 3, 4, 5, 6]; - assert_eq!(bounded, vec![1, 2, 3, 4, 5, 6]); - } - - #[test] - fn too_big_vec_fail_to_decode() { - let v: Vec = vec![1, 2, 3, 4, 5]; - assert_eq!( - BoundedVec::>::decode(&mut &v.encode()[..]), - Err("BoundedVec exceeds its limit".into()), - ); - } - - #[test] - fn can_be_collected() { - let b1: BoundedVec> = bounded_vec![1, 2, 3, 4]; - let b2: BoundedVec> = b1.iter().map(|x| x + 1).try_collect().unwrap(); - assert_eq!(b2, vec![2, 3, 4, 5]); - - // can also be collected into a collection of length 4. - let b2: BoundedVec> = b1.iter().map(|x| x + 1).try_collect().unwrap(); - assert_eq!(b2, vec![2, 3, 4, 5]); - - // can be mutated further into iterators that are `ExactSizedIterator`. - let b2: BoundedVec> = - b1.iter().map(|x| x + 1).rev().try_collect().unwrap(); - assert_eq!(b2, vec![5, 4, 3, 2]); - - let b2: BoundedVec> = - b1.iter().map(|x| x + 1).rev().skip(2).try_collect().unwrap(); - assert_eq!(b2, vec![3, 2]); - let b2: BoundedVec> = - b1.iter().map(|x| x + 1).rev().skip(2).try_collect().unwrap(); - assert_eq!(b2, vec![3, 2]); - - let b2: BoundedVec> = - b1.iter().map(|x| x + 1).rev().take(2).try_collect().unwrap(); - assert_eq!(b2, vec![5, 4]); - let b2: BoundedVec> = - b1.iter().map(|x| x + 1).rev().take(2).try_collect().unwrap(); - assert_eq!(b2, vec![5, 4]); - - // but these worn't work - let b2: Result>, _> = b1.iter().map(|x| x + 1).try_collect(); - assert!(b2.is_err()); - - let b2: Result>, _> = - b1.iter().map(|x| x + 1).rev().take(2).try_collect(); - assert!(b2.is_err()); - } - - #[test] - fn eq_works() { - // of same type - let b1: BoundedVec> = bounded_vec![1, 2, 3]; - let b2: BoundedVec> = bounded_vec![1, 2, 3]; - assert_eq!(b1, b2); - - // of different type, but same value and bound. - crate::parameter_types! { - B1: u32 = 7; - B2: u32 = 7; - } - let b1: BoundedVec = bounded_vec![1, 2, 3]; - let b2: BoundedVec = bounded_vec![1, 2, 3]; - assert_eq!(b1, b2); - } - - #[test] - fn ord_works() { - use std::cmp::Ordering; - let b1: BoundedVec> = bounded_vec![1, 2, 3]; - let b2: BoundedVec> = bounded_vec![1, 3, 2]; - - // ordering for vec is lexicographic. - assert_eq!(b1.cmp(&b2), Ordering::Less); - assert_eq!(b1.cmp(&b2), b1.into_inner().cmp(&b2.into_inner())); - } - - #[test] - fn try_extend_works() { - let mut b: BoundedVec> = bounded_vec![1, 2, 3]; - - assert!(b.try_extend(vec![4].into_iter()).is_ok()); - assert_eq!(*b, vec![1, 2, 3, 4]); - - assert!(b.try_extend(vec![5].into_iter()).is_ok()); - assert_eq!(*b, vec![1, 2, 3, 4, 5]); - - assert!(b.try_extend(vec![6].into_iter()).is_err()); - assert_eq!(*b, vec![1, 2, 3, 4, 5]); - - let mut b: BoundedVec> = bounded_vec![1, 2, 3]; - assert!(b.try_extend(vec![4, 5].into_iter()).is_ok()); - assert_eq!(*b, vec![1, 2, 3, 4, 5]); - - let mut b: BoundedVec> = bounded_vec![1, 2, 3]; - assert!(b.try_extend(vec![4, 5, 6].into_iter()).is_err()); - assert_eq!(*b, vec![1, 2, 3]); - } - - #[test] - fn test_serializer() { - let c: BoundedVec> = bounded_vec![0, 1, 2]; - assert_eq!(serde_json::json!(&c).to_string(), r#"[0,1,2]"#); - } - - #[test] - fn test_deserializer() { - let c: BoundedVec> = serde_json::from_str(r#"[0,1,2]"#).unwrap(); - - assert_eq!(c.len(), 3); - assert_eq!(c[0], 0); - assert_eq!(c[1], 1); - assert_eq!(c[2], 2); - } - - #[test] - fn test_deserializer_failed() { - let c: Result>, serde_json::error::Error> = - serde_json::from_str(r#"[0,1,2,3,4,5]"#); - - match c { - Err(msg) => assert_eq!(msg.to_string(), "out of bounds at line 1 column 11"), - _ => unreachable!("deserializer must raise error"), - } - } - - #[test] - fn bounded_vec_try_from_works() { - assert!(BoundedVec::>::try_from(vec![0]).is_ok()); - assert!(BoundedVec::>::try_from(vec![0, 1]).is_ok()); - assert!(BoundedVec::>::try_from(vec![0, 1, 2]).is_err()); - } - - #[test] - fn bounded_slice_try_from_works() { - assert!(BoundedSlice::>::try_from(&[0][..]).is_ok()); - assert!(BoundedSlice::>::try_from(&[0, 1][..]).is_ok()); - assert!(BoundedSlice::>::try_from(&[0, 1, 2][..]).is_err()); - } } diff --git a/frame/support/src/storage/weak_bounded_vec.rs b/frame/support/src/storage/weak_bounded_vec.rs index bf9b9a1017221..72ba8d775a1d3 100644 --- a/frame/support/src/storage/weak_bounded_vec.rs +++ b/frame/support/src/storage/weak_bounded_vec.rs @@ -22,289 +22,7 @@ use crate::{ storage::{StorageDecodeLength, StorageTryAppend}, traits::Get, }; -use codec::{Decode, Encode, MaxEncodedLen}; -use core::{ - ops::{Deref, Index, IndexMut}, - slice::SliceIndex, -}; -use sp_std::{marker::PhantomData, prelude::*}; - -/// A weakly bounded vector. -/// -/// It has implementations for efficient append and length decoding, as with a normal `Vec<_>`, once -/// put into storage as a raw value, map or double-map. -/// -/// The length of the vec is not strictly bounded. Decoding a vec with more element that the bound -/// is accepted, and some method allow to bypass the restriction with warnings. -#[derive(Encode, scale_info::TypeInfo)] -#[scale_info(skip_type_params(S))] -pub struct WeakBoundedVec(Vec, PhantomData); - -impl> Decode for WeakBoundedVec { - fn decode(input: &mut I) -> Result { - let inner = Vec::::decode(input)?; - Ok(Self::force_from(inner, Some("decode"))) - } - - fn skip(input: &mut I) -> Result<(), codec::Error> { - Vec::::skip(input) - } -} - -impl WeakBoundedVec { - /// Create `Self` from `t` without any checks. - fn unchecked_from(t: Vec) -> Self { - Self(t, Default::default()) - } - - /// Consume self, and return the inner `Vec`. Henceforth, the `Vec<_>` can be altered in an - /// arbitrary way. At some point, if the reverse conversion is required, `TryFrom>` can - /// be used. - /// - /// This is useful for cases if you need access to an internal API of the inner `Vec<_>` which - /// is not provided by the wrapper `WeakBoundedVec`. - pub fn into_inner(self) -> Vec { - self.0 - } - - /// Exactly the same semantics as [`Vec::remove`]. - /// - /// # Panics - /// - /// Panics if `index` is out of bounds. - pub fn remove(&mut self, index: usize) -> T { - self.0.remove(index) - } - - /// Exactly the same semantics as [`Vec::swap_remove`]. - /// - /// # Panics - /// - /// Panics if `index` is out of bounds. - pub fn swap_remove(&mut self, index: usize) -> T { - self.0.swap_remove(index) - } - - /// Exactly the same semantics as [`Vec::retain`]. - pub fn retain bool>(&mut self, f: F) { - self.0.retain(f) - } - - /// Exactly the same semantics as [`slice::get_mut`]. - pub fn get_mut>( - &mut self, - index: I, - ) -> Option<&mut >::Output> { - self.0.get_mut(index) - } -} - -impl> WeakBoundedVec { - /// Get the bound of the type in `usize`. - pub fn bound() -> usize { - S::get() as usize - } - - /// Create `Self` from `t` without any checks. Logs warnings if the bound is not being - /// respected. The additional scope can be used to indicate where a potential overflow is - /// happening. - pub fn force_from(t: Vec, scope: Option<&'static str>) -> Self { - if t.len() > Self::bound() { - log::warn!( - target: crate::LOG_TARGET, - "length of a bounded vector in scope {} is not respected.", - scope.unwrap_or("UNKNOWN"), - ); - } - - Self::unchecked_from(t) - } - - /// Consumes self and mutates self via the given `mutate` function. - /// - /// If the outcome of mutation is within bounds, `Some(Self)` is returned. Else, `None` is - /// returned. - /// - /// This is essentially a *consuming* shorthand [`Self::into_inner`] -> `...` -> - /// [`Self::try_from`]. - pub fn try_mutate(mut self, mut mutate: impl FnMut(&mut Vec)) -> Option { - mutate(&mut self.0); - (self.0.len() <= Self::bound()).then(move || self) - } - - /// Exactly the same semantics as [`Vec::insert`], but returns an `Err` (and is a noop) if the - /// new length of the vector exceeds `S`. - /// - /// # Panics - /// - /// Panics if `index > len`. - pub fn try_insert(&mut self, index: usize, element: T) -> Result<(), ()> { - if self.len() < Self::bound() { - self.0.insert(index, element); - Ok(()) - } else { - Err(()) - } - } - - /// Exactly the same semantics as [`Vec::push`], but returns an `Err` (and is a noop) if the - /// new length of the vector exceeds `S`. - /// - /// # Panics - /// - /// Panics if the new capacity exceeds isize::MAX bytes. - pub fn try_push(&mut self, element: T) -> Result<(), ()> { - if self.len() < Self::bound() { - self.0.push(element); - Ok(()) - } else { - Err(()) - } - } -} - -impl Default for WeakBoundedVec { - fn default() -> Self { - // the bound cannot be below 0, which is satisfied by an empty vector - Self::unchecked_from(Vec::default()) - } -} - -#[cfg(feature = "std")] -impl std::fmt::Debug for WeakBoundedVec -where - T: std::fmt::Debug, - S: Get, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("WeakBoundedVec").field(&self.0).field(&Self::bound()).finish() - } -} - -impl Clone for WeakBoundedVec -where - T: Clone, -{ - fn clone(&self) -> Self { - // bound is retained - Self::unchecked_from(self.0.clone()) - } -} - -impl> TryFrom> for WeakBoundedVec { - type Error = (); - fn try_from(t: Vec) -> Result { - if t.len() <= Self::bound() { - // explicit check just above - Ok(Self::unchecked_from(t)) - } else { - Err(()) - } - } -} - -// It is okay to give a non-mutable reference of the inner vec to anyone. -impl AsRef> for WeakBoundedVec { - fn as_ref(&self) -> &Vec { - &self.0 - } -} - -impl AsRef<[T]> for WeakBoundedVec { - fn as_ref(&self) -> &[T] { - &self.0 - } -} - -impl AsMut<[T]> for WeakBoundedVec { - fn as_mut(&mut self) -> &mut [T] { - &mut self.0 - } -} - -// will allow for immutable all operations of `Vec` on `WeakBoundedVec`. -impl Deref for WeakBoundedVec { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -// Allows for indexing similar to a normal `Vec`. Can panic if out of bound. -impl Index for WeakBoundedVec -where - I: SliceIndex<[T]>, -{ - type Output = I::Output; - - #[inline] - fn index(&self, index: I) -> &Self::Output { - self.0.index(index) - } -} - -impl IndexMut for WeakBoundedVec -where - I: SliceIndex<[T]>, -{ - #[inline] - fn index_mut(&mut self, index: I) -> &mut Self::Output { - self.0.index_mut(index) - } -} - -impl sp_std::iter::IntoIterator for WeakBoundedVec { - type Item = T; - type IntoIter = sp_std::vec::IntoIter; - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl<'a, T, S> sp_std::iter::IntoIterator for &'a WeakBoundedVec { - type Item = &'a T; - type IntoIter = sp_std::slice::Iter<'a, T>; - fn into_iter(self) -> Self::IntoIter { - self.0.iter() - } -} - -impl<'a, T, S> sp_std::iter::IntoIterator for &'a mut WeakBoundedVec { - type Item = &'a mut T; - type IntoIter = sp_std::slice::IterMut<'a, T>; - fn into_iter(self) -> Self::IntoIter { - self.0.iter_mut() - } -} - -impl codec::DecodeLength for WeakBoundedVec { - fn len(self_encoded: &[u8]) -> Result { - // `WeakBoundedVec` stored just a `Vec`, thus the length is at the beginning in - // `Compact` form, and same implementation as `Vec` can be used. - as codec::DecodeLength>::len(self_encoded) - } -} - -// NOTE: we could also implement this as: -// impl, S2: Get> PartialEq> for WeakBoundedVec to allow comparison of bounded vectors with different bounds. -impl PartialEq for WeakBoundedVec -where - T: PartialEq, -{ - fn eq(&self, rhs: &Self) -> bool { - self.0 == rhs.0 - } -} - -impl> PartialEq> for WeakBoundedVec { - fn eq(&self, other: &Vec) -> bool { - &self.0 == other - } -} - -impl Eq for WeakBoundedVec where T: Eq {} +pub use sp_runtime::WeakBoundedVec; impl StorageDecodeLength for WeakBoundedVec {} @@ -314,22 +32,6 @@ impl> StorageTryAppend for WeakBoundedVec { } } -impl MaxEncodedLen for WeakBoundedVec -where - T: MaxEncodedLen, - S: Get, - WeakBoundedVec: Encode, -{ - fn max_encoded_len() -> usize { - // WeakBoundedVec encodes like Vec which encodes like [T], which is a compact u32 - // plus each item in the slice: - // https://docs.substrate.io/v3/advanced/scale-codec - codec::Compact(S::get()) - .encoded_size() - .saturating_add(Self::bound().saturating_mul(T::max_encoded_len())) - } -} - #[cfg(test)] pub mod test { use super::*; @@ -345,11 +47,6 @@ pub mod test { type FooDoubleMap = StorageDoubleMap>>; - #[test] - fn bound_returns_correct_value() { - assert_eq!(WeakBoundedVec::>::bound(), 7); - } - #[test] fn decode_len_works() { TestExternalities::default().execute_with(|| { @@ -375,66 +72,4 @@ pub mod test { assert!(FooDoubleMap::decode_len(2, 2).is_none()); }); } - - #[test] - fn try_insert_works() { - let mut bounded: WeakBoundedVec> = vec![1, 2, 3].try_into().unwrap(); - bounded.try_insert(1, 0).unwrap(); - assert_eq!(*bounded, vec![1, 0, 2, 3]); - - assert!(bounded.try_insert(0, 9).is_err()); - assert_eq!(*bounded, vec![1, 0, 2, 3]); - } - - #[test] - #[should_panic(expected = "insertion index (is 9) should be <= len (is 3)")] - fn try_inert_panics_if_oob() { - let mut bounded: WeakBoundedVec> = vec![1, 2, 3].try_into().unwrap(); - bounded.try_insert(9, 0).unwrap(); - } - - #[test] - fn try_push_works() { - let mut bounded: WeakBoundedVec> = vec![1, 2, 3].try_into().unwrap(); - bounded.try_push(0).unwrap(); - assert_eq!(*bounded, vec![1, 2, 3, 0]); - - assert!(bounded.try_push(9).is_err()); - } - - #[test] - fn deref_coercion_works() { - let bounded: WeakBoundedVec> = vec![1, 2, 3].try_into().unwrap(); - // these methods come from deref-ed vec. - assert_eq!(bounded.len(), 3); - assert!(bounded.iter().next().is_some()); - assert!(!bounded.is_empty()); - } - - #[test] - fn try_mutate_works() { - let bounded: WeakBoundedVec> = vec![1, 2, 3, 4, 5, 6].try_into().unwrap(); - let bounded = bounded.try_mutate(|v| v.push(7)).unwrap(); - assert_eq!(bounded.len(), 7); - assert!(bounded.try_mutate(|v| v.push(8)).is_none()); - } - - #[test] - fn slice_indexing_works() { - let bounded: WeakBoundedVec> = vec![1, 2, 3, 4, 5, 6].try_into().unwrap(); - assert_eq!(&bounded[0..=2], &[1, 2, 3]); - } - - #[test] - fn vec_eq_works() { - let bounded: WeakBoundedVec> = vec![1, 2, 3, 4, 5, 6].try_into().unwrap(); - assert_eq!(bounded, vec![1, 2, 3, 4, 5, 6]); - } - - #[test] - fn too_big_succeed_to_decode() { - let v: Vec = vec![1, 2, 3, 4, 5]; - let w = WeakBoundedVec::>::decode(&mut &v.encode()[..]).unwrap(); - assert_eq!(v, *w); - } } diff --git a/frame/support/src/traits/misc.rs b/frame/support/src/traits/misc.rs index ced6df8f971e8..1f0ba1e769c24 100644 --- a/frame/support/src/traits/misc.rs +++ b/frame/support/src/traits/misc.rs @@ -24,7 +24,7 @@ use sp_arithmetic::traits::{CheckedAdd, CheckedMul, CheckedSub, Saturating}; #[doc(hidden)] pub use sp_runtime::traits::{ ConstBool, ConstI128, ConstI16, ConstI32, ConstI64, ConstI8, ConstU128, ConstU16, ConstU32, - ConstU64, ConstU8, Get, GetDefault, TypedGet, + ConstU64, ConstU8, Get, GetDefault, TryCollect, TypedGet, }; use sp_runtime::{traits::Block as BlockT, DispatchError}; use sp_std::{cmp::Ordering, prelude::*}; @@ -367,16 +367,6 @@ impl DefensiveSaturating f } } -/// Try and collect into a collection `C`. -pub trait TryCollect { - type Error; - /// Consume self and try to collect the results into `C`. - /// - /// This is useful in preventing the undesirable `.collect().try_into()` call chain on - /// collections that need to be converted into a bounded type (e.g. `BoundedVec`). - fn try_collect(self) -> Result; -} - /// Anything that can have a `::len()` method. pub trait Len { /// Return the length of data type. diff --git a/primitives/runtime/src/bounded.rs b/primitives/runtime/src/bounded.rs new file mode 100644 index 0000000000000..45b4a9ca623d4 --- /dev/null +++ b/primitives/runtime/src/bounded.rs @@ -0,0 +1,28 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Bounded collection types. + +pub mod bounded_btree_map; +pub mod bounded_btree_set; +pub mod bounded_vec; +pub mod weak_bounded_vec; + +pub use bounded_btree_map::BoundedBTreeMap; +pub use bounded_btree_set::BoundedBTreeSet; +pub use bounded_vec::{BoundedSlice, BoundedVec}; +pub use weak_bounded_vec::WeakBoundedVec; diff --git a/primitives/runtime/src/bounded/bounded_btree_map.rs b/primitives/runtime/src/bounded/bounded_btree_map.rs new file mode 100644 index 0000000000000..f4fd4275beb2a --- /dev/null +++ b/primitives/runtime/src/bounded/bounded_btree_map.rs @@ -0,0 +1,517 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Traits, types and structs to support a bounded BTreeMap. + +use crate::traits::{Get, TryCollect}; +use codec::{Decode, Encode, MaxEncodedLen}; +use sp_std::{borrow::Borrow, collections::btree_map::BTreeMap, marker::PhantomData, ops::Deref}; + +/// A bounded map based on a B-Tree. +/// +/// B-Trees represent a fundamental compromise between cache-efficiency and actually minimizing +/// the amount of work performed in a search. See [`BTreeMap`] for more details. +/// +/// Unlike a standard `BTreeMap`, there is an enforced upper limit to the number of items in the +/// map. All internal operations ensure this bound is respected. +#[derive(Encode, scale_info::TypeInfo)] +#[scale_info(skip_type_params(S))] +pub struct BoundedBTreeMap(BTreeMap, PhantomData); + +impl Decode for BoundedBTreeMap +where + K: Decode + Ord, + V: Decode, + S: Get, +{ + fn decode(input: &mut I) -> Result { + let inner = BTreeMap::::decode(input)?; + if inner.len() > S::get() as usize { + return Err("BoundedBTreeMap exceeds its limit".into()) + } + Ok(Self(inner, PhantomData)) + } + + fn skip(input: &mut I) -> Result<(), codec::Error> { + BTreeMap::::skip(input) + } +} + +impl BoundedBTreeMap +where + S: Get, +{ + /// Get the bound of the type in `usize`. + pub fn bound() -> usize { + S::get() as usize + } +} + +impl BoundedBTreeMap +where + K: Ord, + S: Get, +{ + /// Exactly the same semantics as `BTreeMap::retain`. + /// + /// The is a safe `&mut self` borrow because `retain` can only ever decrease the length of the + /// inner map. + pub fn retain bool>(&mut self, f: F) { + self.0.retain(f) + } + + /// Create a new `BoundedBTreeMap`. + /// + /// Does not allocate. + pub fn new() -> Self { + BoundedBTreeMap(BTreeMap::new(), PhantomData) + } + + /// Consume self, and return the inner `BTreeMap`. + /// + /// This is useful when a mutating API of the inner type is desired, and closure-based mutation + /// such as provided by [`try_mutate`][Self::try_mutate] is inconvenient. + pub fn into_inner(self) -> BTreeMap { + debug_assert!(self.0.len() <= Self::bound()); + self.0 + } + + /// Consumes self and mutates self via the given `mutate` function. + /// + /// If the outcome of mutation is within bounds, `Some(Self)` is returned. Else, `None` is + /// returned. + /// + /// This is essentially a *consuming* shorthand [`Self::into_inner`] -> `...` -> + /// [`Self::try_from`]. + pub fn try_mutate(mut self, mut mutate: impl FnMut(&mut BTreeMap)) -> Option { + mutate(&mut self.0); + (self.0.len() <= Self::bound()).then(move || self) + } + + /// Clears the map, removing all elements. + pub fn clear(&mut self) { + self.0.clear() + } + + /// Return a mutable reference to the value corresponding to the key. + /// + /// The key may be any borrowed form of the map's key type, but the ordering on the borrowed + /// form _must_ match the ordering on the key type. + pub fn get_mut(&mut self, key: &Q) -> Option<&mut V> + where + K: Borrow, + Q: Ord + ?Sized, + { + self.0.get_mut(key) + } + + /// Exactly the same semantics as [`BTreeMap::insert`], but returns an `Err` (and is a noop) if + /// the new length of the map exceeds `S`. + /// + /// In the `Err` case, returns the inserted pair so it can be further used without cloning. + pub fn try_insert(&mut self, key: K, value: V) -> Result, (K, V)> { + if self.len() < Self::bound() || self.0.contains_key(&key) { + Ok(self.0.insert(key, value)) + } else { + Err((key, value)) + } + } + + /// Remove a key from the map, returning the value at the key if the key was previously in the + /// map. + /// + /// The key may be any borrowed form of the map's key type, but the ordering on the borrowed + /// form _must_ match the ordering on the key type. + pub fn remove(&mut self, key: &Q) -> Option + where + K: Borrow, + Q: Ord + ?Sized, + { + self.0.remove(key) + } + + /// Remove a key from the map, returning the value at the key if the key was previously in the + /// map. + /// + /// The key may be any borrowed form of the map's key type, but the ordering on the borrowed + /// form _must_ match the ordering on the key type. + pub fn remove_entry(&mut self, key: &Q) -> Option<(K, V)> + where + K: Borrow, + Q: Ord + ?Sized, + { + self.0.remove_entry(key) + } +} + +impl Default for BoundedBTreeMap +where + K: Ord, + S: Get, +{ + fn default() -> Self { + Self::new() + } +} + +impl Clone for BoundedBTreeMap +where + BTreeMap: Clone, +{ + fn clone(&self) -> Self { + BoundedBTreeMap(self.0.clone(), PhantomData) + } +} + +#[cfg(feature = "std")] +impl std::fmt::Debug for BoundedBTreeMap +where + BTreeMap: std::fmt::Debug, + S: Get, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("BoundedBTreeMap").field(&self.0).field(&Self::bound()).finish() + } +} + +impl PartialEq> for BoundedBTreeMap +where + BTreeMap: PartialEq, + S1: Get, + S2: Get, +{ + fn eq(&self, other: &BoundedBTreeMap) -> bool { + S1::get() == S2::get() && self.0 == other.0 + } +} + +impl Eq for BoundedBTreeMap +where + BTreeMap: Eq, + S: Get, +{ +} + +impl PartialEq> for BoundedBTreeMap +where + BTreeMap: PartialEq, +{ + fn eq(&self, other: &BTreeMap) -> bool { + self.0 == *other + } +} + +impl PartialOrd for BoundedBTreeMap +where + BTreeMap: PartialOrd, + S: Get, +{ + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl Ord for BoundedBTreeMap +where + BTreeMap: Ord, + S: Get, +{ + fn cmp(&self, other: &Self) -> sp_std::cmp::Ordering { + self.0.cmp(&other.0) + } +} + +impl IntoIterator for BoundedBTreeMap { + type Item = (K, V); + type IntoIter = sp_std::collections::btree_map::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl<'a, K, V, S> IntoIterator for &'a BoundedBTreeMap { + type Item = (&'a K, &'a V); + type IntoIter = sp_std::collections::btree_map::Iter<'a, K, V>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + +impl<'a, K, V, S> IntoIterator for &'a mut BoundedBTreeMap { + type Item = (&'a K, &'a mut V); + type IntoIter = sp_std::collections::btree_map::IterMut<'a, K, V>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter_mut() + } +} + +impl MaxEncodedLen for BoundedBTreeMap +where + K: MaxEncodedLen, + V: MaxEncodedLen, + S: Get, +{ + fn max_encoded_len() -> usize { + Self::bound() + .saturating_mul(K::max_encoded_len().saturating_add(V::max_encoded_len())) + .saturating_add(codec::Compact(S::get()).encoded_size()) + } +} + +impl Deref for BoundedBTreeMap +where + K: Ord, +{ + type Target = BTreeMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl AsRef> for BoundedBTreeMap +where + K: Ord, +{ + fn as_ref(&self) -> &BTreeMap { + &self.0 + } +} + +impl From> for BTreeMap +where + K: Ord, +{ + fn from(map: BoundedBTreeMap) -> Self { + map.0 + } +} + +impl TryFrom> for BoundedBTreeMap +where + K: Ord, + S: Get, +{ + type Error = (); + + fn try_from(value: BTreeMap) -> Result { + (value.len() <= Self::bound()) + .then(move || BoundedBTreeMap(value, PhantomData)) + .ok_or(()) + } +} + +impl codec::DecodeLength for BoundedBTreeMap { + fn len(self_encoded: &[u8]) -> Result { + // `BoundedBTreeMap` is stored just a `BTreeMap`, which is stored as a + // `Compact` with its length followed by an iteration of its items. We can just use + // the underlying implementation. + as codec::DecodeLength>::len(self_encoded) + } +} + +impl codec::EncodeLike> for BoundedBTreeMap where + BTreeMap: Encode +{ +} + +impl TryCollect> for I +where + K: Ord, + I: ExactSizeIterator + Iterator, + Bound: Get, +{ + type Error = &'static str; + + fn try_collect(self) -> Result, Self::Error> { + if self.len() > Bound::get() as usize { + Err("iterator length too big") + } else { + Ok(BoundedBTreeMap::::try_from(self.collect::>()) + .expect("length checked above; qed")) + } + } +} + +#[cfg(test)] +pub mod test { + use super::*; + use crate::traits::ConstU32; + + fn map_from_keys(keys: &[K]) -> BTreeMap + where + K: Ord + Copy, + { + keys.iter().copied().zip(std::iter::repeat(())).collect() + } + + fn boundedmap_from_keys(keys: &[K]) -> BoundedBTreeMap + where + K: Ord + Copy, + S: Get, + { + map_from_keys(keys).try_into().unwrap() + } + + #[test] + fn try_insert_works() { + let mut bounded = boundedmap_from_keys::>(&[1, 2, 3]); + bounded.try_insert(0, ()).unwrap(); + assert_eq!(*bounded, map_from_keys(&[1, 0, 2, 3])); + + assert!(bounded.try_insert(9, ()).is_err()); + assert_eq!(*bounded, map_from_keys(&[1, 0, 2, 3])); + } + + #[test] + fn deref_coercion_works() { + let bounded = boundedmap_from_keys::>(&[1, 2, 3]); + // these methods come from deref-ed vec. + assert_eq!(bounded.len(), 3); + assert!(bounded.iter().next().is_some()); + assert!(!bounded.is_empty()); + } + + #[test] + fn try_mutate_works() { + let bounded = boundedmap_from_keys::>(&[1, 2, 3, 4, 5, 6]); + let bounded = bounded + .try_mutate(|v| { + v.insert(7, ()); + }) + .unwrap(); + assert_eq!(bounded.len(), 7); + assert!(bounded + .try_mutate(|v| { + v.insert(8, ()); + }) + .is_none()); + } + + #[test] + fn btree_map_eq_works() { + let bounded = boundedmap_from_keys::>(&[1, 2, 3, 4, 5, 6]); + assert_eq!(bounded, map_from_keys(&[1, 2, 3, 4, 5, 6])); + } + + #[test] + fn too_big_fail_to_decode() { + let v: Vec<(u32, u32)> = vec![(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]; + assert_eq!( + BoundedBTreeMap::>::decode(&mut &v.encode()[..]), + Err("BoundedBTreeMap exceeds its limit".into()), + ); + } + + #[test] + fn unequal_eq_impl_insert_works() { + // given a struct with a strange notion of equality + #[derive(Debug)] + struct Unequal(u32, bool); + + impl PartialEq for Unequal { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } + } + impl Eq for Unequal {} + + impl Ord for Unequal { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.0.cmp(&other.0) + } + } + + impl PartialOrd for Unequal { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + } + + let mut map = BoundedBTreeMap::>::new(); + + // when the set is full + + for i in 0..4 { + map.try_insert(Unequal(i, false), i).unwrap(); + } + + // can't insert a new distinct member + map.try_insert(Unequal(5, false), 5).unwrap_err(); + + // but _can_ insert a distinct member which compares equal, though per the documentation, + // neither the set length nor the actual member are changed, but the value is + map.try_insert(Unequal(0, true), 6).unwrap(); + assert_eq!(map.len(), 4); + let (zero_key, zero_value) = map.get_key_value(&Unequal(0, true)).unwrap(); + assert_eq!(zero_key.0, 0); + assert_eq!(zero_key.1, false); + assert_eq!(*zero_value, 6); + } + + #[test] + fn eq_works() { + // of same type + let b1 = boundedmap_from_keys::>(&[1, 2]); + let b2 = boundedmap_from_keys::>(&[1, 2]); + assert_eq!(b1, b2); + + // of different type, but same value and bound. + crate::parameter_types! { + B1: u32 = 7; + B2: u32 = 7; + } + let b1 = boundedmap_from_keys::(&[1, 2]); + let b2 = boundedmap_from_keys::(&[1, 2]); + assert_eq!(b1, b2); + } + + #[test] + fn can_be_collected() { + let b1 = boundedmap_from_keys::>(&[1, 2, 3, 4]); + let b2: BoundedBTreeMap> = + b1.iter().map(|(k, v)| (k + 1, *v)).try_collect().unwrap(); + assert_eq!(b2.into_iter().map(|(k, _)| k).collect::>(), vec![2, 3, 4, 5]); + + // can also be collected into a collection of length 4. + let b2: BoundedBTreeMap> = + b1.iter().map(|(k, v)| (k + 1, *v)).try_collect().unwrap(); + assert_eq!(b2.into_iter().map(|(k, _)| k).collect::>(), vec![2, 3, 4, 5]); + + // can be mutated further into iterators that are `ExactSizedIterator`. + let b2: BoundedBTreeMap> = + b1.iter().map(|(k, v)| (k + 1, *v)).rev().skip(2).try_collect().unwrap(); + // note that the binary tree will re-sort this, so rev() is not really seen + assert_eq!(b2.into_iter().map(|(k, _)| k).collect::>(), vec![2, 3]); + + let b2: BoundedBTreeMap> = + b1.iter().map(|(k, v)| (k + 1, *v)).take(2).try_collect().unwrap(); + assert_eq!(b2.into_iter().map(|(k, _)| k).collect::>(), vec![2, 3]); + + // but these worn't work + let b2: Result>, _> = + b1.iter().map(|(k, v)| (k + 1, *v)).try_collect(); + assert!(b2.is_err()); + + let b2: Result>, _> = + b1.iter().map(|(k, v)| (k + 1, *v)).skip(2).try_collect(); + assert!(b2.is_err()); + } +} diff --git a/primitives/runtime/src/bounded/bounded_btree_set.rs b/primitives/runtime/src/bounded/bounded_btree_set.rs new file mode 100644 index 0000000000000..40b95165da1bd --- /dev/null +++ b/primitives/runtime/src/bounded/bounded_btree_set.rs @@ -0,0 +1,479 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Traits, types and structs to support a bounded `BTreeSet`. + +use crate::traits::{Get, TryCollect}; +use codec::{Decode, Encode, MaxEncodedLen}; +use sp_std::{borrow::Borrow, collections::btree_set::BTreeSet, marker::PhantomData, ops::Deref}; + +/// A bounded set based on a B-Tree. +/// +/// B-Trees represent a fundamental compromise between cache-efficiency and actually minimizing +/// the amount of work performed in a search. See [`BTreeSet`] for more details. +/// +/// Unlike a standard `BTreeSet`, there is an enforced upper limit to the number of items in the +/// set. All internal operations ensure this bound is respected. +#[derive(Encode, scale_info::TypeInfo)] +#[scale_info(skip_type_params(S))] +pub struct BoundedBTreeSet(BTreeSet, PhantomData); + +impl Decode for BoundedBTreeSet +where + T: Decode + Ord, + S: Get, +{ + fn decode(input: &mut I) -> Result { + let inner = BTreeSet::::decode(input)?; + if inner.len() > S::get() as usize { + return Err("BoundedBTreeSet exceeds its limit".into()) + } + Ok(Self(inner, PhantomData)) + } + + fn skip(input: &mut I) -> Result<(), codec::Error> { + BTreeSet::::skip(input) + } +} + +impl BoundedBTreeSet +where + S: Get, +{ + /// Get the bound of the type in `usize`. + pub fn bound() -> usize { + S::get() as usize + } +} + +impl BoundedBTreeSet +where + T: Ord, + S: Get, +{ + /// Create a new `BoundedBTreeSet`. + /// + /// Does not allocate. + pub fn new() -> Self { + BoundedBTreeSet(BTreeSet::new(), PhantomData) + } + + /// Consume self, and return the inner `BTreeSet`. + /// + /// This is useful when a mutating API of the inner type is desired, and closure-based mutation + /// such as provided by [`try_mutate`][Self::try_mutate] is inconvenient. + pub fn into_inner(self) -> BTreeSet { + debug_assert!(self.0.len() <= Self::bound()); + self.0 + } + + /// Consumes self and mutates self via the given `mutate` function. + /// + /// If the outcome of mutation is within bounds, `Some(Self)` is returned. Else, `None` is + /// returned. + /// + /// This is essentially a *consuming* shorthand [`Self::into_inner`] -> `...` -> + /// [`Self::try_from`]. + pub fn try_mutate(mut self, mut mutate: impl FnMut(&mut BTreeSet)) -> Option { + mutate(&mut self.0); + (self.0.len() <= Self::bound()).then(move || self) + } + + /// Clears the set, removing all elements. + pub fn clear(&mut self) { + self.0.clear() + } + + /// Exactly the same semantics as [`BTreeSet::insert`], but returns an `Err` (and is a noop) if + /// the new length of the set exceeds `S`. + /// + /// In the `Err` case, returns the inserted item so it can be further used without cloning. + pub fn try_insert(&mut self, item: T) -> Result { + if self.len() < Self::bound() || self.0.contains(&item) { + Ok(self.0.insert(item)) + } else { + Err(item) + } + } + + /// Remove an item from the set, returning whether it was previously in the set. + /// + /// The item may be any borrowed form of the set's item type, but the ordering on the borrowed + /// form _must_ match the ordering on the item type. + pub fn remove(&mut self, item: &Q) -> bool + where + T: Borrow, + Q: Ord + ?Sized, + { + self.0.remove(item) + } + + /// Removes and returns the value in the set, if any, that is equal to the given one. + /// + /// The value may be any borrowed form of the set's value type, but the ordering on the borrowed + /// form _must_ match the ordering on the value type. + pub fn take(&mut self, value: &Q) -> Option + where + T: Borrow + Ord, + Q: Ord + ?Sized, + { + self.0.take(value) + } +} + +impl Default for BoundedBTreeSet +where + T: Ord, + S: Get, +{ + fn default() -> Self { + Self::new() + } +} + +impl Clone for BoundedBTreeSet +where + BTreeSet: Clone, +{ + fn clone(&self) -> Self { + BoundedBTreeSet(self.0.clone(), PhantomData) + } +} + +#[cfg(feature = "std")] +impl std::fmt::Debug for BoundedBTreeSet +where + BTreeSet: std::fmt::Debug, + S: Get, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("BoundedBTreeSet").field(&self.0).field(&Self::bound()).finish() + } +} + +impl PartialEq> for BoundedBTreeSet +where + BTreeSet: PartialEq, + S1: Get, + S2: Get, +{ + fn eq(&self, other: &BoundedBTreeSet) -> bool { + S1::get() == S2::get() && self.0 == other.0 + } +} + +impl Eq for BoundedBTreeSet +where + BTreeSet: Eq, + S: Get, +{ +} + +impl PartialEq> for BoundedBTreeSet +where + BTreeSet: PartialEq, + S: Get, +{ + fn eq(&self, other: &BTreeSet) -> bool { + self.0 == *other + } +} + +impl PartialOrd for BoundedBTreeSet +where + BTreeSet: PartialOrd, + S: Get, +{ + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl Ord for BoundedBTreeSet +where + BTreeSet: Ord, + S: Get, +{ + fn cmp(&self, other: &Self) -> sp_std::cmp::Ordering { + self.0.cmp(&other.0) + } +} + +impl IntoIterator for BoundedBTreeSet { + type Item = T; + type IntoIter = sp_std::collections::btree_set::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl<'a, T, S> IntoIterator for &'a BoundedBTreeSet { + type Item = &'a T; + type IntoIter = sp_std::collections::btree_set::Iter<'a, T>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + +impl MaxEncodedLen for BoundedBTreeSet +where + T: MaxEncodedLen, + S: Get, +{ + fn max_encoded_len() -> usize { + Self::bound() + .saturating_mul(T::max_encoded_len()) + .saturating_add(codec::Compact(S::get()).encoded_size()) + } +} + +impl Deref for BoundedBTreeSet +where + T: Ord, +{ + type Target = BTreeSet; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl AsRef> for BoundedBTreeSet +where + T: Ord, +{ + fn as_ref(&self) -> &BTreeSet { + &self.0 + } +} + +impl From> for BTreeSet +where + T: Ord, +{ + fn from(set: BoundedBTreeSet) -> Self { + set.0 + } +} + +impl TryFrom> for BoundedBTreeSet +where + T: Ord, + S: Get, +{ + type Error = (); + + fn try_from(value: BTreeSet) -> Result { + (value.len() <= Self::bound()) + .then(move || BoundedBTreeSet(value, PhantomData)) + .ok_or(()) + } +} + +impl codec::DecodeLength for BoundedBTreeSet { + fn len(self_encoded: &[u8]) -> Result { + // `BoundedBTreeSet` is stored just a `BTreeSet`, which is stored as a + // `Compact` with its length followed by an iteration of its items. We can just use + // the underlying implementation. + as codec::DecodeLength>::len(self_encoded) + } +} + +impl codec::EncodeLike> for BoundedBTreeSet where BTreeSet: Encode {} + +impl TryCollect> for I +where + T: Ord, + I: ExactSizeIterator + Iterator, + Bound: Get, +{ + type Error = &'static str; + + fn try_collect(self) -> Result, Self::Error> { + if self.len() > Bound::get() as usize { + Err("iterator length too big") + } else { + Ok(BoundedBTreeSet::::try_from(self.collect::>()) + .expect("length is checked above; qed")) + } + } +} + +#[cfg(test)] +pub mod test { + use super::*; + use crate::traits::ConstU32; + + fn set_from_keys(keys: &[T]) -> BTreeSet + where + T: Ord + Copy, + { + keys.iter().copied().collect() + } + + fn boundedset_from_keys(keys: &[T]) -> BoundedBTreeSet + where + T: Ord + Copy, + S: Get, + { + set_from_keys(keys).try_into().unwrap() + } + + #[test] + fn try_insert_works() { + let mut bounded = boundedset_from_keys::>(&[1, 2, 3]); + bounded.try_insert(0).unwrap(); + assert_eq!(*bounded, set_from_keys(&[1, 0, 2, 3])); + + assert!(bounded.try_insert(9).is_err()); + assert_eq!(*bounded, set_from_keys(&[1, 0, 2, 3])); + } + + #[test] + fn deref_coercion_works() { + let bounded = boundedset_from_keys::>(&[1, 2, 3]); + // these methods come from deref-ed vec. + assert_eq!(bounded.len(), 3); + assert!(bounded.iter().next().is_some()); + assert!(!bounded.is_empty()); + } + + #[test] + fn try_mutate_works() { + let bounded = boundedset_from_keys::>(&[1, 2, 3, 4, 5, 6]); + let bounded = bounded + .try_mutate(|v| { + v.insert(7); + }) + .unwrap(); + assert_eq!(bounded.len(), 7); + assert!(bounded + .try_mutate(|v| { + v.insert(8); + }) + .is_none()); + } + + #[test] + fn btree_map_eq_works() { + let bounded = boundedset_from_keys::>(&[1, 2, 3, 4, 5, 6]); + assert_eq!(bounded, set_from_keys(&[1, 2, 3, 4, 5, 6])); + } + + #[test] + fn too_big_fail_to_decode() { + let v: Vec = vec![1, 2, 3, 4, 5]; + assert_eq!( + BoundedBTreeSet::>::decode(&mut &v.encode()[..]), + Err("BoundedBTreeSet exceeds its limit".into()), + ); + } + + #[test] + fn unequal_eq_impl_insert_works() { + // given a struct with a strange notion of equality + #[derive(Debug)] + struct Unequal(u32, bool); + + impl PartialEq for Unequal { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } + } + impl Eq for Unequal {} + + impl Ord for Unequal { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.0.cmp(&other.0) + } + } + + impl PartialOrd for Unequal { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + } + + let mut set = BoundedBTreeSet::>::new(); + + // when the set is full + + for i in 0..4 { + set.try_insert(Unequal(i, false)).unwrap(); + } + + // can't insert a new distinct member + set.try_insert(Unequal(5, false)).unwrap_err(); + + // but _can_ insert a distinct member which compares equal, though per the documentation, + // neither the set length nor the actual member are changed + set.try_insert(Unequal(0, true)).unwrap(); + assert_eq!(set.len(), 4); + let zero_item = set.get(&Unequal(0, true)).unwrap(); + assert_eq!(zero_item.0, 0); + assert_eq!(zero_item.1, false); + } + + #[test] + fn eq_works() { + // of same type + let b1 = boundedset_from_keys::>(&[1, 2]); + let b2 = boundedset_from_keys::>(&[1, 2]); + assert_eq!(b1, b2); + + // of different type, but same value and bound. + crate::parameter_types! { + B1: u32 = 7; + B2: u32 = 7; + } + let b1 = boundedset_from_keys::(&[1, 2]); + let b2 = boundedset_from_keys::(&[1, 2]); + assert_eq!(b1, b2); + } + + #[test] + fn can_be_collected() { + let b1 = boundedset_from_keys::>(&[1, 2, 3, 4]); + let b2: BoundedBTreeSet> = b1.iter().map(|k| k + 1).try_collect().unwrap(); + assert_eq!(b2.into_iter().collect::>(), vec![2, 3, 4, 5]); + + // can also be collected into a collection of length 4. + let b2: BoundedBTreeSet> = b1.iter().map(|k| k + 1).try_collect().unwrap(); + assert_eq!(b2.into_iter().collect::>(), vec![2, 3, 4, 5]); + + // can be mutated further into iterators that are `ExactSizedIterator`. + let b2: BoundedBTreeSet> = + b1.iter().map(|k| k + 1).rev().skip(2).try_collect().unwrap(); + // note that the binary tree will re-sort this, so rev() is not really seen + assert_eq!(b2.into_iter().collect::>(), vec![2, 3]); + + let b2: BoundedBTreeSet> = + b1.iter().map(|k| k + 1).take(2).try_collect().unwrap(); + assert_eq!(b2.into_iter().collect::>(), vec![2, 3]); + + // but these worn't work + let b2: Result>, _> = + b1.iter().map(|k| k + 1).try_collect(); + assert!(b2.is_err()); + + let b2: Result>, _> = + b1.iter().map(|k| k + 1).skip(2).try_collect(); + assert!(b2.is_err()); + } +} diff --git a/primitives/runtime/src/bounded/bounded_vec.rs b/primitives/runtime/src/bounded/bounded_vec.rs new file mode 100644 index 0000000000000..4493d9f8b0198 --- /dev/null +++ b/primitives/runtime/src/bounded/bounded_vec.rs @@ -0,0 +1,998 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Traits, types and structs to support putting a bounded vector into storage, as a raw value, map +//! or a double map. + +use super::WeakBoundedVec; +use crate::traits::{Get, TryCollect}; +use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; +use core::{ + ops::{Deref, Index, IndexMut, RangeBounds}, + slice::SliceIndex, +}; +#[cfg(feature = "std")] +use serde::{ + de::{Error, SeqAccess, Visitor}, + Deserialize, Deserializer, Serialize, +}; +use sp_std::{marker::PhantomData, prelude::*}; + +/// A bounded vector. +/// +/// It has implementations for efficient append and length decoding, as with a normal `Vec<_>`, once +/// put into storage as a raw value, map or double-map. +/// +/// As the name suggests, the length of the queue is always bounded. All internal operations ensure +/// this bound is respected. +#[cfg_attr(feature = "std", derive(Serialize), serde(transparent))] +#[derive(Encode, scale_info::TypeInfo)] +#[scale_info(skip_type_params(S))] +pub struct BoundedVec( + Vec, + #[cfg_attr(feature = "std", serde(skip_serializing))] PhantomData, +); + +#[cfg(feature = "std")] +impl<'de, T, S: Get> Deserialize<'de> for BoundedVec +where + T: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct VecVisitor>(PhantomData<(T, S)>); + + impl<'de, T, S: Get> Visitor<'de> for VecVisitor + where + T: Deserialize<'de>, + { + type Value = Vec; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a sequence") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let size = seq.size_hint().unwrap_or(0); + let max = match usize::try_from(S::get()) { + Ok(n) => n, + Err(_) => return Err(A::Error::custom("can't convert to usize")), + }; + if size > max { + Err(A::Error::custom("out of bounds")) + } else { + let mut values = Vec::with_capacity(size); + + while let Some(value) = seq.next_element()? { + values.push(value); + if values.len() > max { + return Err(A::Error::custom("out of bounds")) + } + } + + Ok(values) + } + } + } + + let visitor: VecVisitor = VecVisitor(PhantomData); + deserializer + .deserialize_seq(visitor) + .map(|v| BoundedVec::::try_from(v).map_err(|_| Error::custom("out of bounds")))? + } +} + +/// A bounded slice. +/// +/// Similar to a `BoundedVec`, but not owned and cannot be decoded. +#[derive(Encode, scale_info::TypeInfo)] +#[scale_info(skip_type_params(S))] +pub struct BoundedSlice<'a, T, S>(&'a [T], PhantomData); + +// `BoundedSlice`s encode to something which will always decode into a `BoundedVec`, +// `WeakBoundedVec`, or a `Vec`. +impl<'a, T: Encode + Decode, S: Get> EncodeLike> for BoundedSlice<'a, T, S> {} +impl<'a, T: Encode + Decode, S: Get> EncodeLike> + for BoundedSlice<'a, T, S> +{ +} +impl<'a, T: Encode + Decode, S: Get> EncodeLike> for BoundedSlice<'a, T, S> {} + +impl> PartialOrd for BoundedVec { + fn partial_cmp(&self, other: &Self) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl> Ord for BoundedVec { + fn cmp(&self, other: &Self) -> sp_std::cmp::Ordering { + self.0.cmp(&other.0) + } +} + +impl<'a, T, S: Get> TryFrom<&'a [T]> for BoundedSlice<'a, T, S> { + type Error = (); + fn try_from(t: &'a [T]) -> Result { + if t.len() <= S::get() as usize { + Ok(BoundedSlice(t, PhantomData)) + } else { + Err(()) + } + } +} + +impl<'a, T, S> From> for &'a [T] { + fn from(t: BoundedSlice<'a, T, S>) -> Self { + t.0 + } +} + +impl<'a, T, S> sp_std::iter::IntoIterator for BoundedSlice<'a, T, S> { + type Item = &'a T; + type IntoIter = sp_std::slice::Iter<'a, T>; + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + +impl> Decode for BoundedVec { + fn decode(input: &mut I) -> Result { + let inner = Vec::::decode(input)?; + if inner.len() > S::get() as usize { + return Err("BoundedVec exceeds its limit".into()) + } + Ok(Self(inner, PhantomData)) + } + + fn skip(input: &mut I) -> Result<(), codec::Error> { + Vec::::skip(input) + } +} + +// `BoundedVec`s encode to something which will always decode as a `Vec`. +impl> EncodeLike> for BoundedVec {} + +impl BoundedVec { + /// Create `Self` from `t` without any checks. + fn unchecked_from(t: Vec) -> Self { + Self(t, Default::default()) + } + + /// Consume self, and return the inner `Vec`. Henceforth, the `Vec<_>` can be altered in an + /// arbitrary way. At some point, if the reverse conversion is required, `TryFrom>` can + /// be used. + /// + /// This is useful for cases if you need access to an internal API of the inner `Vec<_>` which + /// is not provided by the wrapper `BoundedVec`. + pub fn into_inner(self) -> Vec { + self.0 + } + + /// Exactly the same semantics as [`slice::sort_by`]. + /// + /// This is safe since sorting cannot change the number of elements in the vector. + pub fn sort_by(&mut self, compare: F) + where + F: FnMut(&T, &T) -> sp_std::cmp::Ordering, + { + self.0.sort_by(compare) + } + + /// Exactly the same semantics as [`slice::sort`]. + /// + /// This is safe since sorting cannot change the number of elements in the vector. + pub fn sort(&mut self) + where + T: sp_std::cmp::Ord, + { + self.0.sort() + } + + /// Exactly the same semantics as `Vec::remove`. + /// + /// # Panics + /// + /// Panics if `index` is out of bounds. + pub fn remove(&mut self, index: usize) -> T { + self.0.remove(index) + } + + /// Exactly the same semantics as `slice::swap_remove`. + /// + /// # Panics + /// + /// Panics if `index` is out of bounds. + pub fn swap_remove(&mut self, index: usize) -> T { + self.0.swap_remove(index) + } + + /// Exactly the same semantics as `Vec::retain`. + pub fn retain bool>(&mut self, f: F) { + self.0.retain(f) + } + + /// Exactly the same semantics as `slice::get_mut`. + pub fn get_mut>( + &mut self, + index: I, + ) -> Option<&mut >::Output> { + self.0.get_mut(index) + } + + /// Exactly the same semantics as `Vec::truncate`. + /// + /// This is safe because `truncate` can never increase the length of the internal vector. + pub fn truncate(&mut self, s: usize) { + self.0.truncate(s); + } + + /// Exactly the same semantics as `Vec::pop`. + /// + /// This is safe since popping can only shrink the inner vector. + pub fn pop(&mut self) -> Option { + self.0.pop() + } + + /// Exactly the same semantics as [`slice::iter_mut`]. + pub fn iter_mut(&mut self) -> core::slice::IterMut<'_, T> { + self.0.iter_mut() + } + + /// Exactly the same semantics as [`slice::last_mut`]. + pub fn last_mut(&mut self) -> Option<&mut T> { + self.0.last_mut() + } + + /// Exact same semantics as [`Vec::drain`]. + pub fn drain(&mut self, range: R) -> sp_std::vec::Drain<'_, T> + where + R: RangeBounds, + { + self.0.drain(range) + } +} + +impl> From> for Vec { + fn from(x: BoundedVec) -> Vec { + x.0 + } +} + +impl> BoundedVec { + /// Pre-allocate `capacity` items in self. + /// + /// If `capacity` is greater than [`Self::bound`], then the minimum of the two is used. + pub fn with_bounded_capacity(capacity: usize) -> Self { + let capacity = capacity.min(Self::bound()); + Self(Vec::with_capacity(capacity), Default::default()) + } + + /// Allocate self with the maximum possible capacity. + pub fn with_max_capacity() -> Self { + Self::with_bounded_capacity(Self::bound()) + } + + /// Consume and truncate the vector `v` in order to create a new instance of `Self` from it. + pub fn truncate_from(mut v: Vec) -> Self { + v.truncate(Self::bound()); + Self::unchecked_from(v) + } + + /// Get the bound of the type in `usize`. + pub fn bound() -> usize { + S::get() as usize + } + + /// Returns true of this collection is full. + pub fn is_full(&self) -> bool { + self.len() >= Self::bound() + } + + /// Forces the insertion of `element` into `self` retaining all items with index at least + /// `index`. + /// + /// If `index == 0` and `self.len() == Self::bound()`, then this is a no-op. + /// + /// If `Self::bound() < index` or `self.len() < index`, then this is also a no-op. + /// + /// Returns `Ok(maybe_removed)` if the item was inserted, where `maybe_removed` is + /// `Some(removed)` if an item was removed to make room for the new one. Returns `Err(())` if + /// `element` cannot be inserted. + pub fn force_insert_keep_right( + &mut self, + index: usize, + mut element: T, + ) -> Result, ()> { + // Check against panics. + if Self::bound() < index || self.len() < index { + Err(()) + } else if self.len() < Self::bound() { + // Cannot panic since self.len() >= index; + self.0.insert(index, element); + Ok(None) + } else { + if index == 0 { + return Err(()) + } + sp_std::mem::swap(&mut self[0], &mut element); + // `[0..index] cannot panic since self.len() >= index. + // `rotate_left(1)` cannot panic because there is at least 1 element. + self[0..index].rotate_left(1); + Ok(Some(element)) + } + } + + /// Forces the insertion of `element` into `self` retaining all items with index at most + /// `index`. + /// + /// If `index == Self::bound()` and `self.len() == Self::bound()`, then this is a no-op. + /// + /// If `Self::bound() < index` or `self.len() < index`, then this is also a no-op. + /// + /// Returns `Ok(maybe_removed)` if the item was inserted, where `maybe_removed` is + /// `Some(removed)` if an item was removed to make room for the new one. Returns `Err(())` if + /// `element` cannot be inserted. + pub fn force_insert_keep_left(&mut self, index: usize, element: T) -> Result, ()> { + // Check against panics. + if Self::bound() < index || self.len() < index || Self::bound() == 0 { + return Err(()) + } + // Noop condition. + if Self::bound() == index && self.len() <= Self::bound() { + return Err(()) + } + let maybe_removed = if self.is_full() { + // defensive-only: since we are at capacity, this is a noop. + self.0.truncate(Self::bound()); + // if we truncate anything, it will be the last one. + self.0.pop() + } else { + None + }; + + // Cannot panic since `self.len() >= index`; + self.0.insert(index, element); + Ok(maybe_removed) + } + + /// Move the position of an item from one location to another in the slice. + /// + /// Except for the item being moved, the order of the slice remains the same. + /// + /// - `index` is the location of the item to be moved. + /// - `insert_position` is the index of the item in the slice which should *immediately follow* + /// the item which is being moved. + /// + /// Returns `true` of the operation was successful, otherwise `false` if a noop. + pub fn slide(&mut self, index: usize, insert_position: usize) -> bool { + // Check against panics. + if self.len() <= index || self.len() < insert_position || index == usize::MAX { + return false + } + // Noop conditions. + if index == insert_position || index + 1 == insert_position { + return false + } + if insert_position < index && index < self.len() { + // --- --- --- === === === === @@@ --- --- --- + // ^-- N ^O^ + // ... + // /-----<<<-----\ + // --- --- --- === === === === @@@ --- --- --- + // >>> >>> >>> >>> + // ... + // --- --- --- @@@ === === === === --- --- --- + // ^N^ + self[insert_position..index + 1].rotate_right(1); + return true + } else if insert_position > 0 && index + 1 < insert_position { + // Note that the apparent asymmetry of these two branches is due to the + // fact that the "new" position is the position to be inserted *before*. + // --- --- --- @@@ === === === === --- --- --- + // ^O^ ^-- N + // ... + // /----->>>-----\ + // --- --- --- @@@ === === === === --- --- --- + // <<< <<< <<< <<< + // ... + // --- --- --- === === === === @@@ --- --- --- + // ^N^ + self[index..insert_position].rotate_left(1); + return true + } + + debug_assert!(false, "all noop conditions should have been covered above"); + false + } + + /// Forces the insertion of `s` into `self` truncating first if necessary. + /// + /// Infallible, but if the bound is zero, then it's a no-op. + pub fn force_push(&mut self, element: T) { + if Self::bound() > 0 { + self.0.truncate(Self::bound() as usize - 1); + self.0.push(element); + } + } + + /// Same as `Vec::resize`, but if `size` is more than [`Self::bound`], then [`Self::bound`] is + /// used. + pub fn bounded_resize(&mut self, size: usize, value: T) + where + T: Clone, + { + let size = size.min(Self::bound()); + self.0.resize(size, value); + } + + /// Exactly the same semantics as [`Vec::extend`], but returns an error and does nothing if the + /// length of the outcome is larger than the bound. + pub fn try_extend( + &mut self, + with: impl IntoIterator + ExactSizeIterator, + ) -> Result<(), ()> { + if with.len().saturating_add(self.len()) <= Self::bound() { + self.0.extend(with); + Ok(()) + } else { + Err(()) + } + } + + /// Exactly the same semantics as [`Vec::append`], but returns an error and does nothing if the + /// length of the outcome is larger than the bound. + pub fn try_append(&mut self, other: &mut Vec) -> Result<(), ()> { + if other.len().saturating_add(self.len()) <= Self::bound() { + self.0.append(other); + Ok(()) + } else { + Err(()) + } + } + + /// Consumes self and mutates self via the given `mutate` function. + /// + /// If the outcome of mutation is within bounds, `Some(Self)` is returned. Else, `None` is + /// returned. + /// + /// This is essentially a *consuming* shorthand [`Self::into_inner`] -> `...` -> + /// [`Self::try_from`]. + pub fn try_mutate(mut self, mut mutate: impl FnMut(&mut Vec)) -> Option { + mutate(&mut self.0); + (self.0.len() <= Self::bound()).then(move || self) + } + + /// Exactly the same semantics as [`Vec::insert`], but returns an `Err` (and is a noop) if the + /// new length of the vector exceeds `S`. + /// + /// # Panics + /// + /// Panics if `index > len`. + pub fn try_insert(&mut self, index: usize, element: T) -> Result<(), ()> { + if self.len() < Self::bound() { + self.0.insert(index, element); + Ok(()) + } else { + Err(()) + } + } + + /// Exactly the same semantics as [`Vec::push`], but returns an `Err` (and is a noop) if the + /// new length of the vector exceeds `S`. + /// + /// # Panics + /// + /// Panics if the new capacity exceeds isize::MAX bytes. + pub fn try_push(&mut self, element: T) -> Result<(), ()> { + if self.len() < Self::bound() { + self.0.push(element); + Ok(()) + } else { + Err(()) + } + } +} + +impl Default for BoundedVec { + fn default() -> Self { + // the bound cannot be below 0, which is satisfied by an empty vector + Self::unchecked_from(Vec::default()) + } +} + +impl sp_std::fmt::Debug for BoundedVec +where + T: sp_std::fmt::Debug, + S: Get, +{ + fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { + f.debug_tuple("BoundedVec").field(&self.0).field(&Self::bound()).finish() + } +} + +impl Clone for BoundedVec +where + T: Clone, +{ + fn clone(&self) -> Self { + // bound is retained + Self::unchecked_from(self.0.clone()) + } +} + +impl> TryFrom> for BoundedVec { + type Error = (); + fn try_from(t: Vec) -> Result { + if t.len() <= Self::bound() { + // explicit check just above + Ok(Self::unchecked_from(t)) + } else { + Err(()) + } + } +} + +// It is okay to give a non-mutable reference of the inner vec to anyone. +impl AsRef> for BoundedVec { + fn as_ref(&self) -> &Vec { + &self.0 + } +} + +impl AsRef<[T]> for BoundedVec { + fn as_ref(&self) -> &[T] { + &self.0 + } +} + +impl AsMut<[T]> for BoundedVec { + fn as_mut(&mut self) -> &mut [T] { + &mut self.0 + } +} + +// will allow for immutable all operations of `Vec` on `BoundedVec`. +impl Deref for BoundedVec { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +// Allows for indexing similar to a normal `Vec`. Can panic if out of bound. +impl Index for BoundedVec +where + I: SliceIndex<[T]>, +{ + type Output = I::Output; + + #[inline] + fn index(&self, index: I) -> &Self::Output { + self.0.index(index) + } +} + +impl IndexMut for BoundedVec +where + I: SliceIndex<[T]>, +{ + #[inline] + fn index_mut(&mut self, index: I) -> &mut Self::Output { + self.0.index_mut(index) + } +} + +impl sp_std::iter::IntoIterator for BoundedVec { + type Item = T; + type IntoIter = sp_std::vec::IntoIter; + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl<'a, T, S> sp_std::iter::IntoIterator for &'a BoundedVec { + type Item = &'a T; + type IntoIter = sp_std::slice::Iter<'a, T>; + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + +impl<'a, T, S> sp_std::iter::IntoIterator for &'a mut BoundedVec { + type Item = &'a mut T; + type IntoIter = sp_std::slice::IterMut<'a, T>; + fn into_iter(self) -> Self::IntoIter { + self.0.iter_mut() + } +} + +impl codec::DecodeLength for BoundedVec { + fn len(self_encoded: &[u8]) -> Result { + // `BoundedVec` stored just a `Vec`, thus the length is at the beginning in + // `Compact` form, and same implementation as `Vec` can be used. + as codec::DecodeLength>::len(self_encoded) + } +} + +impl PartialEq> for BoundedVec +where + T: PartialEq, + BoundSelf: Get, + BoundRhs: Get, +{ + fn eq(&self, rhs: &BoundedVec) -> bool { + BoundSelf::get() == BoundRhs::get() && self.0 == rhs.0 + } +} + +impl> PartialEq> for BoundedVec { + fn eq(&self, other: &Vec) -> bool { + &self.0 == other + } +} + +impl> Eq for BoundedVec where T: Eq {} + +impl MaxEncodedLen for BoundedVec +where + T: MaxEncodedLen, + S: Get, + BoundedVec: Encode, +{ + fn max_encoded_len() -> usize { + // BoundedVec encodes like Vec which encodes like [T], which is a compact u32 + // plus each item in the slice: + // https://docs.substrate.io/v3/advanced/scale-codec + codec::Compact(S::get()) + .encoded_size() + .saturating_add(Self::bound().saturating_mul(T::max_encoded_len())) + } +} + +impl TryCollect> for I +where + I: ExactSizeIterator + Iterator, + Bound: Get, +{ + type Error = &'static str; + + fn try_collect(self) -> Result, Self::Error> { + if self.len() > Bound::get() as usize { + Err("iterator length too big") + } else { + Ok(BoundedVec::::unchecked_from(self.collect::>())) + } + } +} + +#[cfg(test)] +pub mod test { + use super::*; + use crate::{bounded_vec, traits::ConstU32}; + + #[test] + fn slide_works() { + let mut b: BoundedVec> = bounded_vec![0, 1, 2, 3, 4, 5]; + assert!(b.slide(1, 5)); + assert_eq!(*b, vec![0, 2, 3, 4, 1, 5]); + assert!(b.slide(4, 0)); + assert_eq!(*b, vec![1, 0, 2, 3, 4, 5]); + assert!(b.slide(0, 2)); + assert_eq!(*b, vec![0, 1, 2, 3, 4, 5]); + assert!(b.slide(1, 6)); + assert_eq!(*b, vec![0, 2, 3, 4, 5, 1]); + assert!(b.slide(0, 6)); + assert_eq!(*b, vec![2, 3, 4, 5, 1, 0]); + assert!(b.slide(5, 0)); + assert_eq!(*b, vec![0, 2, 3, 4, 5, 1]); + assert!(!b.slide(6, 0)); + assert!(!b.slide(7, 0)); + assert_eq!(*b, vec![0, 2, 3, 4, 5, 1]); + + let mut c: BoundedVec> = bounded_vec![0, 1, 2]; + assert!(!c.slide(1, 5)); + assert_eq!(*c, vec![0, 1, 2]); + assert!(!c.slide(4, 0)); + assert_eq!(*c, vec![0, 1, 2]); + assert!(!c.slide(3, 0)); + assert_eq!(*c, vec![0, 1, 2]); + assert!(c.slide(2, 0)); + assert_eq!(*c, vec![2, 0, 1]); + } + + #[test] + fn slide_noops_work() { + let mut b: BoundedVec> = bounded_vec![0, 1, 2, 3, 4, 5]; + assert!(!b.slide(3, 3)); + assert_eq!(*b, vec![0, 1, 2, 3, 4, 5]); + assert!(!b.slide(3, 4)); + assert_eq!(*b, vec![0, 1, 2, 3, 4, 5]); + } + + #[test] + fn force_insert_keep_left_works() { + let mut b: BoundedVec> = bounded_vec![]; + assert_eq!(b.force_insert_keep_left(1, 10), Err(())); + assert!(b.is_empty()); + + assert_eq!(b.force_insert_keep_left(0, 30), Ok(None)); + assert_eq!(b.force_insert_keep_left(0, 10), Ok(None)); + assert_eq!(b.force_insert_keep_left(1, 20), Ok(None)); + assert_eq!(b.force_insert_keep_left(3, 40), Ok(None)); + assert_eq!(*b, vec![10, 20, 30, 40]); + // at capacity. + assert_eq!(b.force_insert_keep_left(4, 41), Err(())); + assert_eq!(*b, vec![10, 20, 30, 40]); + assert_eq!(b.force_insert_keep_left(3, 31), Ok(Some(40))); + assert_eq!(*b, vec![10, 20, 30, 31]); + assert_eq!(b.force_insert_keep_left(1, 11), Ok(Some(31))); + assert_eq!(*b, vec![10, 11, 20, 30]); + assert_eq!(b.force_insert_keep_left(0, 1), Ok(Some(30))); + assert_eq!(*b, vec![1, 10, 11, 20]); + + let mut z: BoundedVec> = bounded_vec![]; + assert!(z.is_empty()); + assert_eq!(z.force_insert_keep_left(0, 10), Err(())); + assert!(z.is_empty()); + } + + #[test] + fn force_insert_keep_right_works() { + let mut b: BoundedVec> = bounded_vec![]; + assert_eq!(b.force_insert_keep_right(1, 10), Err(())); + assert!(b.is_empty()); + + assert_eq!(b.force_insert_keep_right(0, 30), Ok(None)); + assert_eq!(b.force_insert_keep_right(0, 10), Ok(None)); + assert_eq!(b.force_insert_keep_right(1, 20), Ok(None)); + assert_eq!(b.force_insert_keep_right(3, 40), Ok(None)); + assert_eq!(*b, vec![10, 20, 30, 40]); + + // at capacity. + assert_eq!(b.force_insert_keep_right(0, 0), Err(())); + assert_eq!(*b, vec![10, 20, 30, 40]); + assert_eq!(b.force_insert_keep_right(1, 11), Ok(Some(10))); + assert_eq!(*b, vec![11, 20, 30, 40]); + assert_eq!(b.force_insert_keep_right(3, 31), Ok(Some(11))); + assert_eq!(*b, vec![20, 30, 31, 40]); + assert_eq!(b.force_insert_keep_right(4, 41), Ok(Some(20))); + assert_eq!(*b, vec![30, 31, 40, 41]); + + assert_eq!(b.force_insert_keep_right(5, 69), Err(())); + assert_eq!(*b, vec![30, 31, 40, 41]); + + let mut z: BoundedVec> = bounded_vec![]; + assert!(z.is_empty()); + assert_eq!(z.force_insert_keep_right(0, 10), Err(())); + assert!(z.is_empty()); + } + + #[test] + fn bound_returns_correct_value() { + assert_eq!(BoundedVec::>::bound(), 7); + } + + #[test] + fn try_insert_works() { + let mut bounded: BoundedVec> = bounded_vec![1, 2, 3]; + bounded.try_insert(1, 0).unwrap(); + assert_eq!(*bounded, vec![1, 0, 2, 3]); + + assert!(bounded.try_insert(0, 9).is_err()); + assert_eq!(*bounded, vec![1, 0, 2, 3]); + } + + #[test] + fn constructor_macro_works() { + // With values. Use some brackets to make sure the macro doesn't expand. + let bv: BoundedVec<(u32, u32), ConstU32<3>> = bounded_vec![(1, 2), (1, 2), (1, 2)]; + assert_eq!(bv, vec![(1, 2), (1, 2), (1, 2)]); + + // With repetition. + let bv: BoundedVec<(u32, u32), ConstU32<3>> = bounded_vec![(1, 2); 3]; + assert_eq!(bv, vec![(1, 2), (1, 2), (1, 2)]); + } + + #[test] + #[should_panic(expected = "insertion index (is 9) should be <= len (is 3)")] + fn try_inert_panics_if_oob() { + let mut bounded: BoundedVec> = bounded_vec![1, 2, 3]; + bounded.try_insert(9, 0).unwrap(); + } + + #[test] + fn try_push_works() { + let mut bounded: BoundedVec> = bounded_vec![1, 2, 3]; + bounded.try_push(0).unwrap(); + assert_eq!(*bounded, vec![1, 2, 3, 0]); + + assert!(bounded.try_push(9).is_err()); + } + + #[test] + fn deref_coercion_works() { + let bounded: BoundedVec> = bounded_vec![1, 2, 3]; + // these methods come from deref-ed vec. + assert_eq!(bounded.len(), 3); + assert!(bounded.iter().next().is_some()); + assert!(!bounded.is_empty()); + } + + #[test] + fn try_mutate_works() { + let bounded: BoundedVec> = bounded_vec![1, 2, 3, 4, 5, 6]; + let bounded = bounded.try_mutate(|v| v.push(7)).unwrap(); + assert_eq!(bounded.len(), 7); + assert!(bounded.try_mutate(|v| v.push(8)).is_none()); + } + + #[test] + fn slice_indexing_works() { + let bounded: BoundedVec> = bounded_vec![1, 2, 3, 4, 5, 6]; + assert_eq!(&bounded[0..=2], &[1, 2, 3]); + } + + #[test] + fn vec_eq_works() { + let bounded: BoundedVec> = bounded_vec![1, 2, 3, 4, 5, 6]; + assert_eq!(bounded, vec![1, 2, 3, 4, 5, 6]); + } + + #[test] + fn too_big_vec_fail_to_decode() { + let v: Vec = vec![1, 2, 3, 4, 5]; + assert_eq!( + BoundedVec::>::decode(&mut &v.encode()[..]), + Err("BoundedVec exceeds its limit".into()), + ); + } + + #[test] + fn eq_works() { + // of same type + let b1: BoundedVec> = bounded_vec![1, 2, 3]; + let b2: BoundedVec> = bounded_vec![1, 2, 3]; + assert_eq!(b1, b2); + + // of different type, but same value and bound. + crate::parameter_types! { + B1: u32 = 7; + B2: u32 = 7; + } + let b1: BoundedVec = bounded_vec![1, 2, 3]; + let b2: BoundedVec = bounded_vec![1, 2, 3]; + assert_eq!(b1, b2); + } + + #[test] + fn ord_works() { + use std::cmp::Ordering; + let b1: BoundedVec> = bounded_vec![1, 2, 3]; + let b2: BoundedVec> = bounded_vec![1, 3, 2]; + + // ordering for vec is lexicographic. + assert_eq!(b1.cmp(&b2), Ordering::Less); + assert_eq!(b1.cmp(&b2), b1.into_inner().cmp(&b2.into_inner())); + } + + #[test] + fn try_extend_works() { + let mut b: BoundedVec> = bounded_vec![1, 2, 3]; + + assert!(b.try_extend(vec![4].into_iter()).is_ok()); + assert_eq!(*b, vec![1, 2, 3, 4]); + + assert!(b.try_extend(vec![5].into_iter()).is_ok()); + assert_eq!(*b, vec![1, 2, 3, 4, 5]); + + assert!(b.try_extend(vec![6].into_iter()).is_err()); + assert_eq!(*b, vec![1, 2, 3, 4, 5]); + + let mut b: BoundedVec> = bounded_vec![1, 2, 3]; + assert!(b.try_extend(vec![4, 5].into_iter()).is_ok()); + assert_eq!(*b, vec![1, 2, 3, 4, 5]); + + let mut b: BoundedVec> = bounded_vec![1, 2, 3]; + assert!(b.try_extend(vec![4, 5, 6].into_iter()).is_err()); + assert_eq!(*b, vec![1, 2, 3]); + } + + #[test] + fn test_serializer() { + let c: BoundedVec> = bounded_vec![0, 1, 2]; + assert_eq!(serde_json::json!(&c).to_string(), r#"[0,1,2]"#); + } + + #[test] + fn test_deserializer() { + let c: BoundedVec> = serde_json::from_str(r#"[0,1,2]"#).unwrap(); + + assert_eq!(c.len(), 3); + assert_eq!(c[0], 0); + assert_eq!(c[1], 1); + assert_eq!(c[2], 2); + } + + #[test] + fn test_deserializer_failed() { + let c: Result>, serde_json::error::Error> = + serde_json::from_str(r#"[0,1,2,3,4,5]"#); + + match c { + Err(msg) => assert_eq!(msg.to_string(), "out of bounds at line 1 column 11"), + _ => unreachable!("deserializer must raise error"), + } + } + + #[test] + fn bounded_vec_try_from_works() { + assert!(BoundedVec::>::try_from(vec![0]).is_ok()); + assert!(BoundedVec::>::try_from(vec![0, 1]).is_ok()); + assert!(BoundedVec::>::try_from(vec![0, 1, 2]).is_err()); + } + + #[test] + fn bounded_slice_try_from_works() { + assert!(BoundedSlice::>::try_from(&[0][..]).is_ok()); + assert!(BoundedSlice::>::try_from(&[0, 1][..]).is_ok()); + assert!(BoundedSlice::>::try_from(&[0, 1, 2][..]).is_err()); + } + + #[test] + fn can_be_collected() { + let b1: BoundedVec> = bounded_vec![1, 2, 3, 4]; + let b2: BoundedVec> = b1.iter().map(|x| x + 1).try_collect().unwrap(); + assert_eq!(b2, vec![2, 3, 4, 5]); + + // can also be collected into a collection of length 4. + let b2: BoundedVec> = b1.iter().map(|x| x + 1).try_collect().unwrap(); + assert_eq!(b2, vec![2, 3, 4, 5]); + + // can be mutated further into iterators that are `ExactSizedIterator`. + let b2: BoundedVec> = + b1.iter().map(|x| x + 1).rev().try_collect().unwrap(); + assert_eq!(b2, vec![5, 4, 3, 2]); + + let b2: BoundedVec> = + b1.iter().map(|x| x + 1).rev().skip(2).try_collect().unwrap(); + assert_eq!(b2, vec![3, 2]); + let b2: BoundedVec> = + b1.iter().map(|x| x + 1).rev().skip(2).try_collect().unwrap(); + assert_eq!(b2, vec![3, 2]); + + let b2: BoundedVec> = + b1.iter().map(|x| x + 1).rev().take(2).try_collect().unwrap(); + assert_eq!(b2, vec![5, 4]); + let b2: BoundedVec> = + b1.iter().map(|x| x + 1).rev().take(2).try_collect().unwrap(); + assert_eq!(b2, vec![5, 4]); + + // but these worn't work + let b2: Result>, _> = b1.iter().map(|x| x + 1).try_collect(); + assert!(b2.is_err()); + + let b2: Result>, _> = + b1.iter().map(|x| x + 1).rev().take(2).try_collect(); + assert!(b2.is_err()); + } +} diff --git a/primitives/runtime/src/bounded/weak_bounded_vec.rs b/primitives/runtime/src/bounded/weak_bounded_vec.rs new file mode 100644 index 0000000000000..9b88ad27e4287 --- /dev/null +++ b/primitives/runtime/src/bounded/weak_bounded_vec.rs @@ -0,0 +1,393 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Traits, types and structs to support putting a bounded vector into storage, as a raw value, map +//! or a double map. + +use crate::traits::Get; +use codec::{Decode, Encode, MaxEncodedLen}; +use core::{ + ops::{Deref, Index, IndexMut}, + slice::SliceIndex, +}; +use sp_std::{marker::PhantomData, prelude::*}; + +/// A weakly bounded vector. +/// +/// It has implementations for efficient append and length decoding, as with a normal `Vec<_>`, once +/// put into storage as a raw value, map or double-map. +/// +/// The length of the vec is not strictly bounded. Decoding a vec with more element that the bound +/// is accepted, and some method allow to bypass the restriction with warnings. +#[derive(Encode, scale_info::TypeInfo)] +#[scale_info(skip_type_params(S))] +pub struct WeakBoundedVec(Vec, PhantomData); + +impl> Decode for WeakBoundedVec { + fn decode(input: &mut I) -> Result { + let inner = Vec::::decode(input)?; + Ok(Self::force_from(inner, Some("decode"))) + } + + fn skip(input: &mut I) -> Result<(), codec::Error> { + Vec::::skip(input) + } +} + +impl WeakBoundedVec { + /// Create `Self` from `t` without any checks. + fn unchecked_from(t: Vec) -> Self { + Self(t, Default::default()) + } + + /// Consume self, and return the inner `Vec`. Henceforth, the `Vec<_>` can be altered in an + /// arbitrary way. At some point, if the reverse conversion is required, `TryFrom>` can + /// be used. + /// + /// This is useful for cases if you need access to an internal API of the inner `Vec<_>` which + /// is not provided by the wrapper `WeakBoundedVec`. + pub fn into_inner(self) -> Vec { + self.0 + } + + /// Exactly the same semantics as [`Vec::remove`]. + /// + /// # Panics + /// + /// Panics if `index` is out of bounds. + pub fn remove(&mut self, index: usize) -> T { + self.0.remove(index) + } + + /// Exactly the same semantics as [`Vec::swap_remove`]. + /// + /// # Panics + /// + /// Panics if `index` is out of bounds. + pub fn swap_remove(&mut self, index: usize) -> T { + self.0.swap_remove(index) + } + + /// Exactly the same semantics as [`Vec::retain`]. + pub fn retain bool>(&mut self, f: F) { + self.0.retain(f) + } + + /// Exactly the same semantics as [`slice::get_mut`]. + pub fn get_mut>( + &mut self, + index: I, + ) -> Option<&mut >::Output> { + self.0.get_mut(index) + } +} + +impl> WeakBoundedVec { + /// Get the bound of the type in `usize`. + pub fn bound() -> usize { + S::get() as usize + } + + /// Create `Self` from `t` without any checks. Logs warnings if the bound is not being + /// respected. The additional scope can be used to indicate where a potential overflow is + /// happening. + pub fn force_from(t: Vec, scope: Option<&'static str>) -> Self { + if t.len() > Self::bound() { + log::warn!( + target: "runtime", + "length of a bounded vector in scope {} is not respected.", + scope.unwrap_or("UNKNOWN"), + ); + } + + Self::unchecked_from(t) + } + + /// Consumes self and mutates self via the given `mutate` function. + /// + /// If the outcome of mutation is within bounds, `Some(Self)` is returned. Else, `None` is + /// returned. + /// + /// This is essentially a *consuming* shorthand [`Self::into_inner`] -> `...` -> + /// [`Self::try_from`]. + pub fn try_mutate(mut self, mut mutate: impl FnMut(&mut Vec)) -> Option { + mutate(&mut self.0); + (self.0.len() <= Self::bound()).then(move || self) + } + + /// Exactly the same semantics as [`Vec::insert`], but returns an `Err` (and is a noop) if the + /// new length of the vector exceeds `S`. + /// + /// # Panics + /// + /// Panics if `index > len`. + pub fn try_insert(&mut self, index: usize, element: T) -> Result<(), ()> { + if self.len() < Self::bound() { + self.0.insert(index, element); + Ok(()) + } else { + Err(()) + } + } + + /// Exactly the same semantics as [`Vec::push`], but returns an `Err` (and is a noop) if the + /// new length of the vector exceeds `S`. + /// + /// # Panics + /// + /// Panics if the new capacity exceeds isize::MAX bytes. + pub fn try_push(&mut self, element: T) -> Result<(), ()> { + if self.len() < Self::bound() { + self.0.push(element); + Ok(()) + } else { + Err(()) + } + } +} + +impl Default for WeakBoundedVec { + fn default() -> Self { + // the bound cannot be below 0, which is satisfied by an empty vector + Self::unchecked_from(Vec::default()) + } +} + +#[cfg(feature = "std")] +impl std::fmt::Debug for WeakBoundedVec +where + T: std::fmt::Debug, + S: Get, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_tuple("WeakBoundedVec").field(&self.0).field(&Self::bound()).finish() + } +} + +impl Clone for WeakBoundedVec +where + T: Clone, +{ + fn clone(&self) -> Self { + // bound is retained + Self::unchecked_from(self.0.clone()) + } +} + +impl> TryFrom> for WeakBoundedVec { + type Error = (); + fn try_from(t: Vec) -> Result { + if t.len() <= Self::bound() { + // explicit check just above + Ok(Self::unchecked_from(t)) + } else { + Err(()) + } + } +} + +// It is okay to give a non-mutable reference of the inner vec to anyone. +impl AsRef> for WeakBoundedVec { + fn as_ref(&self) -> &Vec { + &self.0 + } +} + +impl AsRef<[T]> for WeakBoundedVec { + fn as_ref(&self) -> &[T] { + &self.0 + } +} + +impl AsMut<[T]> for WeakBoundedVec { + fn as_mut(&mut self) -> &mut [T] { + &mut self.0 + } +} + +// will allow for immutable all operations of `Vec` on `WeakBoundedVec`. +impl Deref for WeakBoundedVec { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +// Allows for indexing similar to a normal `Vec`. Can panic if out of bound. +impl Index for WeakBoundedVec +where + I: SliceIndex<[T]>, +{ + type Output = I::Output; + + #[inline] + fn index(&self, index: I) -> &Self::Output { + self.0.index(index) + } +} + +impl IndexMut for WeakBoundedVec +where + I: SliceIndex<[T]>, +{ + #[inline] + fn index_mut(&mut self, index: I) -> &mut Self::Output { + self.0.index_mut(index) + } +} + +impl sp_std::iter::IntoIterator for WeakBoundedVec { + type Item = T; + type IntoIter = sp_std::vec::IntoIter; + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl<'a, T, S> sp_std::iter::IntoIterator for &'a WeakBoundedVec { + type Item = &'a T; + type IntoIter = sp_std::slice::Iter<'a, T>; + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + +impl<'a, T, S> sp_std::iter::IntoIterator for &'a mut WeakBoundedVec { + type Item = &'a mut T; + type IntoIter = sp_std::slice::IterMut<'a, T>; + fn into_iter(self) -> Self::IntoIter { + self.0.iter_mut() + } +} + +impl codec::DecodeLength for WeakBoundedVec { + fn len(self_encoded: &[u8]) -> Result { + // `WeakBoundedVec` stored just a `Vec`, thus the length is at the beginning in + // `Compact` form, and same implementation as `Vec` can be used. + as codec::DecodeLength>::len(self_encoded) + } +} + +// NOTE: we could also implement this as: +// impl, S2: Get> PartialEq> for WeakBoundedVec to allow comparison of bounded vectors with different bounds. +impl PartialEq for WeakBoundedVec +where + T: PartialEq, +{ + fn eq(&self, rhs: &Self) -> bool { + self.0 == rhs.0 + } +} + +impl> PartialEq> for WeakBoundedVec { + fn eq(&self, other: &Vec) -> bool { + &self.0 == other + } +} + +impl Eq for WeakBoundedVec where T: Eq {} + +impl MaxEncodedLen for WeakBoundedVec +where + T: MaxEncodedLen, + S: Get, + WeakBoundedVec: Encode, +{ + fn max_encoded_len() -> usize { + // WeakBoundedVec encodes like Vec which encodes like [T], which is a compact u32 + // plus each item in the slice: + // https://docs.substrate.io/v3/advanced/scale-codec + codec::Compact(S::get()) + .encoded_size() + .saturating_add(Self::bound().saturating_mul(T::max_encoded_len())) + } +} + +#[cfg(test)] +pub mod test { + use super::*; + use crate::traits::ConstU32; + + #[test] + fn bound_returns_correct_value() { + assert_eq!(WeakBoundedVec::>::bound(), 7); + } + + #[test] + fn try_insert_works() { + let mut bounded: WeakBoundedVec> = vec![1, 2, 3].try_into().unwrap(); + bounded.try_insert(1, 0).unwrap(); + assert_eq!(*bounded, vec![1, 0, 2, 3]); + + assert!(bounded.try_insert(0, 9).is_err()); + assert_eq!(*bounded, vec![1, 0, 2, 3]); + } + + #[test] + #[should_panic(expected = "insertion index (is 9) should be <= len (is 3)")] + fn try_inert_panics_if_oob() { + let mut bounded: WeakBoundedVec> = vec![1, 2, 3].try_into().unwrap(); + bounded.try_insert(9, 0).unwrap(); + } + + #[test] + fn try_push_works() { + let mut bounded: WeakBoundedVec> = vec![1, 2, 3].try_into().unwrap(); + bounded.try_push(0).unwrap(); + assert_eq!(*bounded, vec![1, 2, 3, 0]); + + assert!(bounded.try_push(9).is_err()); + } + + #[test] + fn deref_coercion_works() { + let bounded: WeakBoundedVec> = vec![1, 2, 3].try_into().unwrap(); + // these methods come from deref-ed vec. + assert_eq!(bounded.len(), 3); + assert!(bounded.iter().next().is_some()); + assert!(!bounded.is_empty()); + } + + #[test] + fn try_mutate_works() { + let bounded: WeakBoundedVec> = vec![1, 2, 3, 4, 5, 6].try_into().unwrap(); + let bounded = bounded.try_mutate(|v| v.push(7)).unwrap(); + assert_eq!(bounded.len(), 7); + assert!(bounded.try_mutate(|v| v.push(8)).is_none()); + } + + #[test] + fn slice_indexing_works() { + let bounded: WeakBoundedVec> = vec![1, 2, 3, 4, 5, 6].try_into().unwrap(); + assert_eq!(&bounded[0..=2], &[1, 2, 3]); + } + + #[test] + fn vec_eq_works() { + let bounded: WeakBoundedVec> = vec![1, 2, 3, 4, 5, 6].try_into().unwrap(); + assert_eq!(bounded, vec![1, 2, 3, 4, 5, 6]); + } + + #[test] + fn too_big_succeed_to_decode() { + let v: Vec = vec![1, 2, 3, 4, 5]; + let w = WeakBoundedVec::>::decode(&mut &v.encode()[..]).unwrap(); + assert_eq!(v, *w); + } +} diff --git a/primitives/runtime/src/lib.rs b/primitives/runtime/src/lib.rs index 39e606eb9b5f4..b41c605d8a3be 100644 --- a/primitives/runtime/src/lib.rs +++ b/primitives/runtime/src/lib.rs @@ -55,6 +55,7 @@ use sp_std::prelude::*; use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; +pub mod bounded; pub mod curve; pub mod generic; pub mod legacy; @@ -69,6 +70,9 @@ pub mod transaction_validity; pub use crate::runtime_string::*; +// Re-export bounded types +pub use bounded::{BoundedBTreeMap, BoundedBTreeSet, BoundedSlice, BoundedVec, WeakBoundedVec}; + // Re-export Multiaddress pub use multiaddress::MultiAddress; @@ -825,6 +829,45 @@ macro_rules! assert_eq_error_rate { }; } +/// Build a bounded vec from the given literals. +/// +/// The type of the outcome must be known. +/// +/// Will not handle any errors and just panic if the given literals cannot fit in the corresponding +/// bounded vec type. Thus, this is only suitable for testing and non-consensus code. +#[macro_export] +#[cfg(feature = "std")] +macro_rules! bounded_vec { + ($ ($values:expr),* $(,)?) => { + { + $crate::sp_std::vec![$($values),*].try_into().unwrap() + } + }; + ( $value:expr ; $repetition:expr ) => { + { + $crate::sp_std::vec![$value ; $repetition].try_into().unwrap() + } + } +} + +/// Build a bounded btree-map from the given literals. +/// +/// The type of the outcome must be known. +/// +/// Will not handle any errors and just panic if the given literals cannot fit in the corresponding +/// bounded vec type. Thus, this is only suitable for testing and non-consensus code. +#[macro_export] +#[cfg(feature = "std")] +macro_rules! bounded_btree_map { + ($ ( $key:expr => $value:expr ),* $(,)?) => { + { + $crate::traits::TryCollect::<$crate::BoundedBTreeMap<_, _, _>>::try_collect( + $crate::sp_std::vec![$(($key, $value)),*].into_iter() + ).unwrap() + } + }; +} + /// Simple blob to hold an extrinsic without committing to its format and ensure it is serialized /// correctly. #[derive(PartialEq, Eq, Clone, Default, Encode, Decode, TypeInfo)] diff --git a/primitives/runtime/src/traits.rs b/primitives/runtime/src/traits.rs index aa5908526b819..fba3117c41617 100644 --- a/primitives/runtime/src/traits.rs +++ b/primitives/runtime/src/traits.rs @@ -308,6 +308,17 @@ impl Get for GetDefault { } } +/// Try and collect into a collection `C`. +pub trait TryCollect { + /// The error type that gets returned when a collection can't be made from `self`. + type Error; + /// Consume self and try to collect the results into `C`. + /// + /// This is useful in preventing the undesirable `.collect().try_into()` call chain on + /// collections that need to be converted into a bounded type (e.g. `BoundedVec`). + fn try_collect(self) -> Result; +} + macro_rules! impl_const_get { ($name:ident, $t:ty) => { #[doc = "Const getter for a basic type."] From 51dfc0dd49f3cd4265d001412d8008882c9c4ea2 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Mon, 13 Jun 2022 19:53:07 +0200 Subject: [PATCH 330/484] Implement MaxEncodedLen on pallet-beefy (#11584) * Implement MaxEncodedLen on pallet-beefy * Return Result in intialize_authorities * Update docs * Log error when authorities list gets truncated * Update frame/beefy/src/lib.rs Co-authored-by: Adrian Catangiu * cargo fmt Co-authored-by: Adrian Catangiu --- frame/beefy-mmr/src/mock.rs | 1 + frame/beefy/src/lib.rs | 84 ++++++++++++++----- frame/beefy/src/mock.rs | 3 +- primitives/runtime/src/bounded/bounded_vec.rs | 9 ++ 4 files changed, 73 insertions(+), 24 deletions(-) diff --git a/frame/beefy-mmr/src/mock.rs b/frame/beefy-mmr/src/mock.rs index f6a35f68a4a1f..c9dbdb3b2e16a 100644 --- a/frame/beefy-mmr/src/mock.rs +++ b/frame/beefy-mmr/src/mock.rs @@ -124,6 +124,7 @@ impl pallet_mmr::Config for Test { impl pallet_beefy::Config for Test { type BeefyId = BeefyId; + type MaxAuthorities = ConstU32<100>; } parameter_types! { diff --git a/frame/beefy/src/lib.rs b/frame/beefy/src/lib.rs index 14e7ac26cdb6e..a0cba2270b37d 100644 --- a/frame/beefy/src/lib.rs +++ b/frame/beefy/src/lib.rs @@ -17,9 +17,13 @@ #![cfg_attr(not(feature = "std"), no_std)] -use codec::Encode; +use codec::{Encode, MaxEncodedLen}; -use frame_support::{traits::OneSessionHandler, Parameter}; +use frame_support::{ + log, + traits::{Get, OneSessionHandler}, + BoundedSlice, BoundedVec, Parameter, +}; use sp_runtime::{ generic::DigestItem, @@ -42,28 +46,28 @@ pub use pallet::*; pub mod pallet { use super::*; use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; #[pallet::config] pub trait Config: frame_system::Config { /// Authority identifier type - type BeefyId: Member + Parameter + RuntimeAppPublic + MaybeSerializeDeserialize; + type BeefyId: Member + + Parameter + + RuntimeAppPublic + + MaybeSerializeDeserialize + + MaxEncodedLen; + + /// The maximum number of authorities that can be added. + type MaxAuthorities: Get; } #[pallet::pallet] - #[pallet::without_storage_info] pub struct Pallet(PhantomData); - #[pallet::hooks] - impl Hooks> for Pallet {} - - #[pallet::call] - impl Pallet {} - /// The current authorities set #[pallet::storage] #[pallet::getter(fn authorities)] - pub(super) type Authorities = StorageValue<_, Vec, ValueQuery>; + pub(super) type Authorities = + StorageValue<_, BoundedVec, ValueQuery>; /// The current validator set id #[pallet::storage] @@ -74,7 +78,8 @@ pub mod pallet { /// Authorities set scheduled to be used with the next session #[pallet::storage] #[pallet::getter(fn next_authorities)] - pub(super) type NextAuthorities = StorageValue<_, Vec, ValueQuery>; + pub(super) type NextAuthorities = + StorageValue<_, BoundedVec, ValueQuery>; #[pallet::genesis_config] pub struct GenesisConfig { @@ -91,7 +96,10 @@ pub mod pallet { #[pallet::genesis_build] impl GenesisBuild for GenesisConfig { fn build(&self) { - Pallet::::initialize_authorities(&self.authorities); + Pallet::::initialize_authorities(&self.authorities) + // we panic here as runtime maintainers can simply reconfigure genesis and restart + // the chain easily + .expect("Authorities vec too big"); } } } @@ -99,12 +107,15 @@ pub mod pallet { impl Pallet { /// Return the current active BEEFY validator set. pub fn validator_set() -> Option> { - let validators: Vec = Self::authorities(); + let validators: BoundedVec = Self::authorities(); let id: beefy_primitives::ValidatorSetId = Self::validator_set_id(); ValidatorSet::::new(validators, id) } - fn change_authorities(new: Vec, queued: Vec) { + fn change_authorities( + new: BoundedVec, + queued: BoundedVec, + ) { >::put(&new); let next_id = Self::validator_set_id() + 1u64; @@ -120,17 +131,23 @@ impl Pallet { >::put(&queued); } - fn initialize_authorities(authorities: &[T::BeefyId]) { + fn initialize_authorities(authorities: &[T::BeefyId]) -> Result<(), ()> { if authorities.is_empty() { - return + return Ok(()) + } + + if !>::get().is_empty() { + return Err(()) } - assert!(>::get().is_empty(), "Authorities are already initialized!"); + let bounded_authorities = + BoundedSlice::::try_from(authorities)?; - >::put(authorities); + >::put(bounded_authorities); >::put(0); // Like `pallet_session`, initialize the next validator set as well. - >::put(authorities); + >::put(bounded_authorities); + Ok(()) } } @@ -146,7 +163,9 @@ impl OneSessionHandler for Pallet { I: Iterator, { let authorities = validators.map(|(_, k)| k).collect::>(); - Self::initialize_authorities(&authorities); + // we panic here as runtime maintainers can simply reconfigure genesis and restart the + // chain easily + Self::initialize_authorities(&authorities).expect("Authorities vec too big"); } fn on_new_session<'a, I: 'a>(_changed: bool, validators: I, queued_validators: I) @@ -154,11 +173,30 @@ impl OneSessionHandler for Pallet { I: Iterator, { let next_authorities = validators.map(|(_, k)| k).collect::>(); + if next_authorities.len() as u32 > T::MaxAuthorities::get() { + log::error!( + target: "runtime::beefy", + "authorities list {:?} truncated to length {}", + next_authorities, T::MaxAuthorities::get(), + ); + } + let bounded_next_authorities = + BoundedVec::<_, T::MaxAuthorities>::truncate_from(next_authorities); + let next_queued_authorities = queued_validators.map(|(_, k)| k).collect::>(); + if next_queued_authorities.len() as u32 > T::MaxAuthorities::get() { + log::error!( + target: "runtime::beefy", + "queued authorities list {:?} truncated to length {}", + next_queued_authorities, T::MaxAuthorities::get(), + ); + } + let bounded_next_queued_authorities = + BoundedVec::<_, T::MaxAuthorities>::truncate_from(next_queued_authorities); // Always issue a change on each `session`, even if validator set hasn't changed. // We want to have at least one BEEFY mandatory block per session. - Self::change_authorities(next_authorities, next_queued_authorities); + Self::change_authorities(bounded_next_authorities, bounded_next_queued_authorities); } fn on_disabled(i: u32) { diff --git a/frame/beefy/src/mock.rs b/frame/beefy/src/mock.rs index 7a8f15cd51d29..27796b5b2206c 100644 --- a/frame/beefy/src/mock.rs +++ b/frame/beefy/src/mock.rs @@ -52,7 +52,7 @@ construct_runtime!( UncheckedExtrinsic = UncheckedExtrinsic, { System: frame_system::{Pallet, Call, Config, Storage, Event}, - Beefy: pallet_beefy::{Pallet, Call, Config, Storage}, + Beefy: pallet_beefy::{Pallet, Config, Storage}, Session: pallet_session::{Pallet, Call, Storage, Event, Config}, } ); @@ -86,6 +86,7 @@ impl frame_system::Config for Test { impl pallet_beefy::Config for Test { type BeefyId = BeefyId; + type MaxAuthorities = ConstU32<100>; } parameter_types! { diff --git a/primitives/runtime/src/bounded/bounded_vec.rs b/primitives/runtime/src/bounded/bounded_vec.rs index 4493d9f8b0198..fe832b07f9588 100644 --- a/primitives/runtime/src/bounded/bounded_vec.rs +++ b/primitives/runtime/src/bounded/bounded_vec.rs @@ -146,6 +146,15 @@ impl<'a, T, S> From> for &'a [T] { } } +impl<'a, T, S> Clone for BoundedSlice<'a, T, S> { + fn clone(&self) -> Self { + BoundedSlice(self.0, PhantomData) + } +} + +// Since a reference `&T` is always `Copy`, so is `BoundedSlice<'a, T, S>`. +impl<'a, T, S> Copy for BoundedSlice<'a, T, S> {} + impl<'a, T, S> sp_std::iter::IntoIterator for BoundedSlice<'a, T, S> { type Item = &'a T; type IntoIter = sp_std::slice::Iter<'a, T>; From 113da3b66ff4620fbb406e27244b4a0e5e9e38bb Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Mon, 13 Jun 2022 22:55:48 +0200 Subject: [PATCH 331/484] Implement PartialOrd and Ord on BoundedSlice and WeakBoundedVec (#11655) * Implement PartialOrd and Ord on BoundedSlice and WeakBoundedVec * More implementations of PartialEq and PartialOrd * cargo fmt * Fixes --- .../runtime/src/bounded/bounded_btree_map.rs | 8 +- .../runtime/src/bounded/bounded_btree_set.rs | 8 +- primitives/runtime/src/bounded/bounded_vec.rs | 145 +++++++++++++++++- .../runtime/src/bounded/weak_bounded_vec.rs | 78 +++++++++- 4 files changed, 221 insertions(+), 18 deletions(-) diff --git a/primitives/runtime/src/bounded/bounded_btree_map.rs b/primitives/runtime/src/bounded/bounded_btree_map.rs index f4fd4275beb2a..b9abf77f175c6 100644 --- a/primitives/runtime/src/bounded/bounded_btree_map.rs +++ b/primitives/runtime/src/bounded/bounded_btree_map.rs @@ -66,6 +66,11 @@ where K: Ord, S: Get, { + /// Create `Self` from `t` without any checks. + fn unchecked_from(t: BTreeMap) -> Self { + Self(t, Default::default()) + } + /// Exactly the same semantics as `BTreeMap::retain`. /// /// The is a safe `&mut self` borrow because `retain` can only ever decrease the length of the @@ -344,8 +349,7 @@ where if self.len() > Bound::get() as usize { Err("iterator length too big") } else { - Ok(BoundedBTreeMap::::try_from(self.collect::>()) - .expect("length checked above; qed")) + Ok(BoundedBTreeMap::::unchecked_from(self.collect::>())) } } } diff --git a/primitives/runtime/src/bounded/bounded_btree_set.rs b/primitives/runtime/src/bounded/bounded_btree_set.rs index 40b95165da1bd..0f8cc769e47e3 100644 --- a/primitives/runtime/src/bounded/bounded_btree_set.rs +++ b/primitives/runtime/src/bounded/bounded_btree_set.rs @@ -65,6 +65,11 @@ where T: Ord, S: Get, { + /// Create `Self` from `t` without any checks. + fn unchecked_from(t: BTreeSet) -> Self { + Self(t, Default::default()) + } + /// Create a new `BoundedBTreeSet`. /// /// Does not allocate. @@ -309,8 +314,7 @@ where if self.len() > Bound::get() as usize { Err("iterator length too big") } else { - Ok(BoundedBTreeSet::::try_from(self.collect::>()) - .expect("length is checked above; qed")) + Ok(BoundedBTreeSet::::unchecked_from(self.collect::>())) } } } diff --git a/primitives/runtime/src/bounded/bounded_vec.rs b/primitives/runtime/src/bounded/bounded_vec.rs index fe832b07f9588..84acddea47cab 100644 --- a/primitives/runtime/src/bounded/bounded_vec.rs +++ b/primitives/runtime/src/bounded/bounded_vec.rs @@ -43,7 +43,7 @@ use sp_std::{marker::PhantomData, prelude::*}; #[derive(Encode, scale_info::TypeInfo)] #[scale_info(skip_type_params(S))] pub struct BoundedVec( - Vec, + pub(super) Vec, #[cfg_attr(feature = "std", serde(skip_serializing))] PhantomData, ); @@ -106,7 +106,7 @@ where /// Similar to a `BoundedVec`, but not owned and cannot be decoded. #[derive(Encode, scale_info::TypeInfo)] #[scale_info(skip_type_params(S))] -pub struct BoundedSlice<'a, T, S>(&'a [T], PhantomData); +pub struct BoundedSlice<'a, T, S>(pub(super) &'a [T], PhantomData); // `BoundedSlice`s encode to something which will always decode into a `BoundedVec`, // `WeakBoundedVec`, or a `Vec`. @@ -117,13 +117,81 @@ impl<'a, T: Encode + Decode, S: Get> EncodeLike> } impl<'a, T: Encode + Decode, S: Get> EncodeLike> for BoundedSlice<'a, T, S> {} -impl> PartialOrd for BoundedVec { - fn partial_cmp(&self, other: &Self) -> Option { - self.0.partial_cmp(&other.0) +impl<'a, T, BoundSelf, BoundRhs> PartialEq> + for BoundedSlice<'a, T, BoundSelf> +where + T: PartialEq, + BoundSelf: Get, + BoundRhs: Get, +{ + fn eq(&self, other: &BoundedSlice<'a, T, BoundRhs>) -> bool { + self.0 == other.0 } } -impl> Ord for BoundedVec { +impl<'a, T, BoundSelf, BoundRhs> PartialEq> + for BoundedSlice<'a, T, BoundSelf> +where + T: PartialEq, + BoundSelf: Get, + BoundRhs: Get, +{ + fn eq(&self, other: &BoundedVec) -> bool { + self.0 == other.0 + } +} + +impl<'a, T, BoundSelf, BoundRhs> PartialEq> + for BoundedSlice<'a, T, BoundSelf> +where + T: PartialEq, + BoundSelf: Get, + BoundRhs: Get, +{ + fn eq(&self, other: &WeakBoundedVec) -> bool { + self.0 == other.0 + } +} + +impl<'a, T, S: Get> Eq for BoundedSlice<'a, T, S> where T: Eq {} + +impl<'a, T, BoundSelf, BoundRhs> PartialOrd> + for BoundedSlice<'a, T, BoundSelf> +where + T: PartialOrd, + BoundSelf: Get, + BoundRhs: Get, +{ + fn partial_cmp(&self, other: &BoundedSlice<'a, T, BoundRhs>) -> Option { + self.0.partial_cmp(other.0) + } +} + +impl<'a, T, BoundSelf, BoundRhs> PartialOrd> + for BoundedSlice<'a, T, BoundSelf> +where + T: PartialOrd, + BoundSelf: Get, + BoundRhs: Get, +{ + fn partial_cmp(&self, other: &BoundedVec) -> Option { + self.0.partial_cmp(&*other.0) + } +} + +impl<'a, T, BoundSelf, BoundRhs> PartialOrd> + for BoundedSlice<'a, T, BoundSelf> +where + T: PartialOrd, + BoundSelf: Get, + BoundRhs: Get, +{ + fn partial_cmp(&self, other: &WeakBoundedVec) -> Option { + self.0.partial_cmp(&*other.0) + } +} + +impl<'a, T: Ord, Bound: Get> Ord for BoundedSlice<'a, T, Bound> { fn cmp(&self, other: &Self) -> sp_std::cmp::Ordering { self.0.cmp(&other.0) } @@ -650,7 +718,30 @@ where BoundRhs: Get, { fn eq(&self, rhs: &BoundedVec) -> bool { - BoundSelf::get() == BoundRhs::get() && self.0 == rhs.0 + self.0 == rhs.0 + } +} + +impl PartialEq> for BoundedVec +where + T: PartialEq, + BoundSelf: Get, + BoundRhs: Get, +{ + fn eq(&self, rhs: &WeakBoundedVec) -> bool { + self.0 == rhs.0 + } +} + +impl<'a, T, BoundSelf, BoundRhs> PartialEq> + for BoundedVec +where + T: PartialEq, + BoundSelf: Get, + BoundRhs: Get, +{ + fn eq(&self, rhs: &BoundedSlice<'a, T, BoundRhs>) -> bool { + self.0 == rhs.0 } } @@ -662,6 +753,46 @@ impl> PartialEq> for BoundedVec { impl> Eq for BoundedVec where T: Eq {} +impl PartialOrd> for BoundedVec +where + T: PartialOrd, + BoundSelf: Get, + BoundRhs: Get, +{ + fn partial_cmp(&self, other: &BoundedVec) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl PartialOrd> for BoundedVec +where + T: PartialOrd, + BoundSelf: Get, + BoundRhs: Get, +{ + fn partial_cmp(&self, other: &WeakBoundedVec) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl<'a, T, BoundSelf, BoundRhs> PartialOrd> + for BoundedVec +where + T: PartialOrd, + BoundSelf: Get, + BoundRhs: Get, +{ + fn partial_cmp(&self, other: &BoundedSlice<'a, T, BoundRhs>) -> Option { + (&*self.0).partial_cmp(other.0) + } +} + +impl> Ord for BoundedVec { + fn cmp(&self, other: &Self) -> sp_std::cmp::Ordering { + self.0.cmp(&other.0) + } +} + impl MaxEncodedLen for BoundedVec where T: MaxEncodedLen, diff --git a/primitives/runtime/src/bounded/weak_bounded_vec.rs b/primitives/runtime/src/bounded/weak_bounded_vec.rs index 9b88ad27e4287..82bae4118f13e 100644 --- a/primitives/runtime/src/bounded/weak_bounded_vec.rs +++ b/primitives/runtime/src/bounded/weak_bounded_vec.rs @@ -18,6 +18,7 @@ //! Traits, types and structs to support putting a bounded vector into storage, as a raw value, map //! or a double map. +use super::{BoundedSlice, BoundedVec}; use crate::traits::Get; use codec::{Decode, Encode, MaxEncodedLen}; use core::{ @@ -35,7 +36,7 @@ use sp_std::{marker::PhantomData, prelude::*}; /// is accepted, and some method allow to bypass the restriction with warnings. #[derive(Encode, scale_info::TypeInfo)] #[scale_info(skip_type_params(S))] -pub struct WeakBoundedVec(Vec, PhantomData); +pub struct WeakBoundedVec(pub(super) Vec, PhantomData); impl> Decode for WeakBoundedVec { fn decode(input: &mut I) -> Result { @@ -283,14 +284,36 @@ impl codec::DecodeLength for WeakBoundedVec { } } -// NOTE: we could also implement this as: -// impl, S2: Get> PartialEq> for WeakBoundedVec to allow comparison of bounded vectors with different bounds. -impl PartialEq for WeakBoundedVec +impl PartialEq> for WeakBoundedVec where T: PartialEq, + BoundSelf: Get, + BoundRhs: Get, { - fn eq(&self, rhs: &Self) -> bool { + fn eq(&self, rhs: &WeakBoundedVec) -> bool { + self.0 == rhs.0 + } +} + +impl PartialEq> for WeakBoundedVec +where + T: PartialEq, + BoundSelf: Get, + BoundRhs: Get, +{ + fn eq(&self, rhs: &BoundedVec) -> bool { + self.0 == rhs.0 + } +} + +impl<'a, T, BoundSelf, BoundRhs> PartialEq> + for WeakBoundedVec +where + T: PartialEq, + BoundSelf: Get, + BoundRhs: Get, +{ + fn eq(&self, rhs: &BoundedSlice<'a, T, BoundRhs>) -> bool { self.0 == rhs.0 } } @@ -301,7 +324,48 @@ impl> PartialEq> for WeakBoundedVec { } } -impl Eq for WeakBoundedVec where T: Eq {} +impl> Eq for WeakBoundedVec where T: Eq {} + +impl PartialOrd> + for WeakBoundedVec +where + T: PartialOrd, + BoundSelf: Get, + BoundRhs: Get, +{ + fn partial_cmp(&self, other: &WeakBoundedVec) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl PartialOrd> for WeakBoundedVec +where + T: PartialOrd, + BoundSelf: Get, + BoundRhs: Get, +{ + fn partial_cmp(&self, other: &BoundedVec) -> Option { + self.0.partial_cmp(&other.0) + } +} + +impl<'a, T, BoundSelf, BoundRhs> PartialOrd> + for WeakBoundedVec +where + T: PartialOrd, + BoundSelf: Get, + BoundRhs: Get, +{ + fn partial_cmp(&self, other: &BoundedSlice<'a, T, BoundRhs>) -> Option { + (&*self.0).partial_cmp(other.0) + } +} + +impl> Ord for WeakBoundedVec { + fn cmp(&self, other: &Self) -> sp_std::cmp::Ordering { + self.0.cmp(&other.0) + } +} impl MaxEncodedLen for WeakBoundedVec where From e1cde2ea2b3e7027c6e535de04e99b7d07c443cf Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Mon, 13 Jun 2022 22:07:36 +0100 Subject: [PATCH 332/484] Allow nomination pools to chill + fix dismantle scenario (#11426) * make pool roles optional * undo lock file changes? * add migration * add the ability for pools to chill themselves * boilerplate of tests * somewhat stable, but I think I found another bug as well * Fix it all * Add more more sophisticated test + capture one more bug. * Update frame/staking/src/lib.rs * reduce the diff a little bit * add some test for the slashing bug * cleanup * fix lock file? * Fix * fmt * Update frame/nomination-pools/src/lib.rs Co-authored-by: Oliver Tale-Yazdi * Update frame/nomination-pools/src/lib.rs Co-authored-by: Oliver Tale-Yazdi * Update frame/nomination-pools/src/lib.rs Co-authored-by: Oliver Tale-Yazdi * Update frame/nomination-pools/src/mock.rs Co-authored-by: Oliver Tale-Yazdi * Fix build * fix some fishy tests.. * add one last integrity check for MinCreateBond * remove bad assertion -- needs to be dealt with later * nits * fix tests and add benchmarks for chill * remove stuff * fix benchmarks * cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_nomination_pools --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/nomination-pools/src/weights.rs --template=./.maintain/frame-weight-template.hbs * remove defensive Co-authored-by: Oliver Tale-Yazdi Co-authored-by: Shawn Tabrizi Co-authored-by: Parity Bot --- Cargo.lock | 24 + Cargo.toml | 1 + bin/node/cli/src/chain_spec.rs | 11 +- frame/nomination-pools/Cargo.toml | 3 +- .../nomination-pools/benchmarking/src/lib.rs | 76 +- frame/nomination-pools/src/lib.rs | 153 +++- frame/nomination-pools/src/mock.rs | 64 +- frame/nomination-pools/src/tests.rs | 853 ++++++++---------- frame/nomination-pools/src/weights.rs | 133 ++- .../nomination-pools/test-staking/Cargo.toml | 36 + .../nomination-pools/test-staking/src/lib.rs | 373 ++++++++ .../nomination-pools/test-staking/src/mock.rs | 261 ++++++ frame/staking/src/lib.rs | 8 +- frame/staking/src/pallet/impls.rs | 21 +- frame/staking/src/tests.rs | 19 + frame/support/src/storage/mod.rs | 2 +- frame/support/src/storage/transactional.rs | 6 +- primitives/staking/src/lib.rs | 11 +- 18 files changed, 1427 insertions(+), 628 deletions(-) create mode 100644 frame/nomination-pools/test-staking/Cargo.toml create mode 100644 frame/nomination-pools/test-staking/src/lib.rs create mode 100644 frame/nomination-pools/test-staking/src/mock.rs diff --git a/Cargo.lock b/Cargo.lock index a5e431c7a4aa0..ae0a497867b51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5931,6 +5931,30 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-nomination-pools-test-staking" +version = "1.0.0" +dependencies = [ + "frame-election-provider-support", + "frame-support", + "frame-system", + "log", + "pallet-bags-list", + "pallet-balances", + "pallet-nomination-pools", + "pallet-staking", + "pallet-staking-reward-curve", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-staking", + "sp-std", + "sp-tracing", +] + [[package]] name = "pallet-offences" version = "4.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index 7f013c08a9144..9909e6f893877 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -115,6 +115,7 @@ members = [ "frame/proxy", "frame/nomination-pools", "frame/nomination-pools/benchmarking", + "frame/nomination-pools/test-staking", "frame/randomness-collective-flip", "frame/ranked-collective", "frame/recovery", diff --git a/bin/node/cli/src/chain_spec.rs b/bin/node/cli/src/chain_spec.rs index 1c7794fd0cde6..47c3634aa00df 100644 --- a/bin/node/cli/src/chain_spec.rs +++ b/bin/node/cli/src/chain_spec.rs @@ -23,8 +23,9 @@ use hex_literal::hex; use node_runtime::{ constants::currency::*, wasm_binary_unwrap, AuthorityDiscoveryConfig, BabeConfig, BalancesConfig, Block, CouncilConfig, DemocracyConfig, ElectionsConfig, GrandpaConfig, - ImOnlineConfig, IndicesConfig, MaxNominations, SessionConfig, SessionKeys, SocietyConfig, - StakerStatus, StakingConfig, SudoConfig, SystemConfig, TechnicalCommitteeConfig, + ImOnlineConfig, IndicesConfig, MaxNominations, NominationPoolsConfig, SessionConfig, + SessionKeys, SocietyConfig, StakerStatus, StakingConfig, SudoConfig, SystemConfig, + TechnicalCommitteeConfig, }; use pallet_im_online::sr25519::AuthorityId as ImOnlineId; use sc_chain_spec::ChainSpecExtension; @@ -365,7 +366,11 @@ pub fn testnet_genesis( transaction_payment: Default::default(), alliance: Default::default(), alliance_motion: Default::default(), - nomination_pools: Default::default(), + nomination_pools: NominationPoolsConfig { + min_create_bond: 10 * DOLLARS, + min_join_bond: 1 * DOLLARS, + ..Default::default() + }, } } diff --git a/frame/nomination-pools/Cargo.toml b/frame/nomination-pools/Cargo.toml index 0820125d77f44..be5c38d85552c 100644 --- a/frame/nomination-pools/Cargo.toml +++ b/frame/nomination-pools/Cargo.toml @@ -23,11 +23,11 @@ sp-runtime = { version = "6.0.0", default-features = false, path = "../../primit sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } log = { version = "0.4.0", default-features = false } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } -sp-io = { version = "6.0.0", path = "../../primitives/io" } sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } [features] @@ -41,6 +41,7 @@ std = [ "frame-system/std", "sp-runtime/std", "sp-std/std", + "sp-io/std", "sp-staking/std", "sp-core/std", "log/std", diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index 275b914cda297..4c2c902846a85 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -24,7 +24,7 @@ mod mock; use frame_benchmarking::{account, frame_support::traits::Currency, vec, whitelist_account, Vec}; use frame_election_provider_support::SortedListProvider; -use frame_support::{ensure, traits::Get}; +use frame_support::{assert_ok, ensure, traits::Get}; use frame_system::RawOrigin as Origin; use pallet_nomination_pools::{ BalanceOf, BondExtra, BondedPoolInner, BondedPools, ConfigOp, MaxPoolMembers, @@ -48,6 +48,12 @@ pub trait Config: pub struct Pallet(Pools); +fn min_create_bond() -> BalanceOf { + MinCreateBond::::get() + .max(T::StakingInterface::minimum_bond()) + .max(CurrencyOf::::minimum_balance()) +} + fn create_funded_user_with_balance( string: &'static str, n: u32, @@ -209,9 +215,7 @@ impl ListScenario { frame_benchmarking::benchmarks! { join { - let origin_weight = pallet_nomination_pools::MinCreateBond::::get() - .max(CurrencyOf::::minimum_balance()) - * 2u32.into(); + let origin_weight = min_create_bond::() * 2u32.into(); // setup the worst case list scenario. let scenario = ListScenario::::new(origin_weight, true)?; @@ -237,9 +241,7 @@ frame_benchmarking::benchmarks! { } bond_extra_transfer { - let origin_weight = pallet_nomination_pools::MinCreateBond::::get() - .max(CurrencyOf::::minimum_balance()) - * 2u32.into(); + let origin_weight = min_create_bond::() * 2u32.into(); let scenario = ListScenario::::new(origin_weight, true)?; let extra = scenario.dest_weight.clone() - origin_weight; @@ -254,9 +256,7 @@ frame_benchmarking::benchmarks! { } bond_extra_reward { - let origin_weight = pallet_nomination_pools::MinCreateBond::::get() - .max(CurrencyOf::::minimum_balance()) - * 2u32.into(); + let origin_weight = min_create_bond::() * 2u32.into(); let scenario = ListScenario::::new(origin_weight, true)?; let extra = (scenario.dest_weight.clone() - origin_weight).max(CurrencyOf::::minimum_balance()); @@ -274,7 +274,7 @@ frame_benchmarking::benchmarks! { } claim_payout { - let origin_weight = pallet_nomination_pools::MinCreateBond::::get().max(CurrencyOf::::minimum_balance()) * 2u32.into(); + let origin_weight = min_create_bond::() * 2u32.into(); let ed = CurrencyOf::::minimum_balance(); let (depositor, pool_account) = create_pool_account::(0, origin_weight); let reward_account = Pools::::create_reward_account(1); @@ -304,9 +304,7 @@ frame_benchmarking::benchmarks! { unbond { // The weight the nominator will start at. The value used here is expected to be // significantly higher than the first position in a list (e.g. the first bag threshold). - let origin_weight = BalanceOf::::try_from(952_994_955_240_703u128) - .map_err(|_| "balance expected to be a u128") - .unwrap(); + let origin_weight = min_create_bond::() * 200u32.into(); let scenario = ListScenario::::new(origin_weight, false)?; let amount = origin_weight - scenario.dest_weight.clone(); @@ -336,9 +334,7 @@ frame_benchmarking::benchmarks! { pool_withdraw_unbonded { let s in 0 .. MAX_SPANS; - let min_create_bond = MinCreateBond::::get() - .max(T::StakingInterface::minimum_bond()) - .max(CurrencyOf::::minimum_balance()); + let min_create_bond = min_create_bond::(); let (depositor, pool_account) = create_pool_account::(0, min_create_bond); // Add a new member @@ -380,9 +376,7 @@ frame_benchmarking::benchmarks! { withdraw_unbonded_update { let s in 0 .. MAX_SPANS; - let min_create_bond = MinCreateBond::::get() - .max(T::StakingInterface::minimum_bond()) - .max(CurrencyOf::::minimum_balance()); + let min_create_bond = min_create_bond::(); let (depositor, pool_account) = create_pool_account::(0, min_create_bond); // Add a new member @@ -427,10 +421,7 @@ frame_benchmarking::benchmarks! { withdraw_unbonded_kill { let s in 0 .. MAX_SPANS; - let min_create_bond = MinCreateBond::::get() - .max(T::StakingInterface::minimum_bond()) - .max(CurrencyOf::::minimum_balance()); - + let min_create_bond = min_create_bond::(); let (depositor, pool_account) = create_pool_account::(0, min_create_bond); // We set the pool to the destroying state so the depositor can leave @@ -494,9 +485,7 @@ frame_benchmarking::benchmarks! { } create { - let min_create_bond = MinCreateBond::::get() - .max(T::StakingInterface::minimum_bond()) - .max(CurrencyOf::::minimum_balance()); + let min_create_bond = min_create_bond::(); let depositor: T::AccountId = account("depositor", USER_SEED, 0); // Give the depositor some balance to bond @@ -542,9 +531,7 @@ frame_benchmarking::benchmarks! { let n in 1 .. T::MaxNominations::get(); // Create a pool - let min_create_bond = MinCreateBond::::get() - .max(T::StakingInterface::minimum_bond()) - .max(CurrencyOf::::minimum_balance()); + let min_create_bond = min_create_bond::() * 2u32.into(); let (depositor, pool_account) = create_pool_account::(0, min_create_bond); // Create some accounts to nominate. For the sake of benchmarking they don't need to be @@ -581,9 +568,7 @@ frame_benchmarking::benchmarks! { set_state { // Create a pool - let min_create_bond = MinCreateBond::::get() - .max(T::StakingInterface::minimum_bond()) - .max(CurrencyOf::::minimum_balance()); + let min_create_bond = min_create_bond::(); let (depositor, pool_account) = create_pool_account::(0, min_create_bond); BondedPools::::mutate(&1, |maybe_pool| { // Force the pool into an invalid state @@ -601,10 +586,7 @@ frame_benchmarking::benchmarks! { let n in 1 .. ::MaxMetadataLen::get(); // Create a pool - let min_create_bond = MinCreateBond::::get() - .max(T::StakingInterface::minimum_bond()) - .max(CurrencyOf::::minimum_balance()); - let (depositor, pool_account) = create_pool_account::(0, min_create_bond); + let (depositor, pool_account) = create_pool_account::(0, min_create_bond::() * 2u32.into()); // Create metadata of the max possible size let metadata: Vec = (0..n).map(|_| 42).collect(); @@ -633,7 +615,7 @@ frame_benchmarking::benchmarks! { update_roles { let first_id = pallet_nomination_pools::LastPoolId::::get() + 1; - let (root, _) = create_pool_account::(0, CurrencyOf::::minimum_balance() * 2u32.into()); + let (root, _) = create_pool_account::(0, min_create_bond::() * 2u32.into()); let random: T::AccountId = account("but is anything really random in computers..?", 0, USER_SEED); }:_( Origin::Signed(root.clone()), @@ -653,6 +635,24 @@ frame_benchmarking::benchmarks! { ) } + chill { + // Create a pool + let (depositor, pool_account) = create_pool_account::(0, min_create_bond::() * 2u32.into()); + + // Nominate with the pool. + let validators: Vec<_> = (0..T::MaxNominations::get()) + .map(|i| account("stash", USER_SEED, i)) + .collect(); + + assert_ok!(Pools::::nominate(Origin::Signed(depositor.clone()).into(), 1, validators)); + assert!(T::StakingInterface::nominations(Pools::::create_bonded_account(1)).is_some()); + + whitelist_account!(depositor); + }:_(Origin::Signed(depositor.clone()), 1) + verify { + assert!(T::StakingInterface::nominations(Pools::::create_bonded_account(1)).is_none()); + } + impl_benchmark_test_suite!( Pallet, crate::mock::new_test_ext(), diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index dc40a6a7d41fd..19155a51405b0 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -447,21 +447,27 @@ impl PoolMember { .fold(BalanceOf::::zero(), |acc, (_, v)| acc.saturating_add(*v)) } - /// Try and unbond `points` from self, with the given target unbonding era. + /// Try and unbond `points_dissolved` from self, and in return mint `points_issued` into the + /// corresponding `era`'s unlock schedule. + /// + /// In the absence of slashing, these two points are always the same. In the presence of + /// slashing, the value of points in different pools varies. /// /// Returns `Ok(())` and updates `unbonding_eras` and `points` if success, `Err(_)` otherwise. fn try_unbond( &mut self, - points: BalanceOf, + points_dissolved: BalanceOf, + points_issued: BalanceOf, unbonding_era: EraIndex, ) -> Result<(), Error> { - if let Some(new_points) = self.points.checked_sub(&points) { + if let Some(new_points) = self.points.checked_sub(&points_dissolved) { match self.unbonding_eras.get_mut(&unbonding_era) { Some(already_unbonding_points) => - *already_unbonding_points = already_unbonding_points.saturating_add(points), + *already_unbonding_points = + already_unbonding_points.saturating_add(points_issued), None => self .unbonding_eras - .try_insert(unbonding_era, points) + .try_insert(unbonding_era, points_issued) .map(|old| { if old.is_some() { defensive!("value checked to not exist in the map; qed"); @@ -721,9 +727,13 @@ impl BondedPool { } fn is_destroying_and_only_depositor(&self, alleged_depositor_points: BalanceOf) -> bool { - // NOTE: if we add `&& self.member_counter == 1`, then this becomes even more strict and - // ensures that there are no unbonding members hanging around either. - self.is_destroying() && self.points == alleged_depositor_points + // we need to ensure that `self.member_counter == 1` as well, because the depositor's + // initial `MinCreateBond` (or more) is what guarantees that the ledger of the pool does not + // get killed in the staking system, and that it does not fall below `MinimumNominatorBond`, + // which could prevent other non-depositor members from fully leaving. Thus, all members + // must withdraw, then depositor can unbond, and finally withdraw after waiting another + // cycle. + self.is_destroying() && self.points == alleged_depositor_points && self.member_counter == 1 } /// Whether or not the pool is ok to be in `PoolSate::Open`. If this returns an `Err`, then the @@ -984,10 +994,14 @@ impl UnbondPool { Pallet::::point_to_balance(self.balance, self.points, points) } - /// Issue points and update the balance given `new_balance`. - fn issue(&mut self, new_funds: BalanceOf) { - self.points = self.points.saturating_add(self.balance_to_point(new_funds)); + /// Issue the equivalent points of `new_funds` into self. + /// + /// Returns the actual amounts of points issued. + fn issue(&mut self, new_funds: BalanceOf) -> BalanceOf { + let new_points = self.balance_to_point(new_funds); + self.points = self.points.saturating_add(new_points); self.balance = self.balance.saturating_add(new_funds); + new_points } /// Dissolve some points from the unbonding pool, reducing the balance of the pool @@ -1150,6 +1164,9 @@ pub mod pallet { /// /// This is the amount that the depositor must put as their initial stake in the pool, as an /// indication of "skin in the game". + /// + /// This is the value that will always exist in the staking ledger of the pool bonded account + /// while all other accounts leave. #[pallet::storage] pub type MinCreateBond = StorageValue<_, BalanceOf, ValueQuery>; @@ -1256,9 +1273,34 @@ pub mod pallet { /// A payout has been made to a member. PaidOut { member: T::AccountId, pool_id: PoolId, payout: BalanceOf }, /// A member has unbonded from their pool. - Unbonded { member: T::AccountId, pool_id: PoolId, amount: BalanceOf }, + /// + /// - `balance` is the corresponding balance of the number of points that has been + /// requested to be unbonded (the argument of the `unbond` transaction) from the bonded + /// pool. + /// - `points` is the number of points that are issued as a result of `balance` being + /// dissolved into the corresponding unbonding pool. + /// + /// In the absence of slashing, these values will match. In the presence of slashing, the + /// number of points that are issued in the unbonding pool will be less than the amount + /// requested to be unbonded. + Unbonded { + member: T::AccountId, + pool_id: PoolId, + balance: BalanceOf, + points: BalanceOf, + }, /// A member has withdrawn from their pool. - Withdrawn { member: T::AccountId, pool_id: PoolId, amount: BalanceOf }, + /// + /// The given number of `points` have been dissolved in return of `balance`. + /// + /// Similar to `Unbonded` event, in the absence of slashing, the ratio of point to balance + /// will be 1. + Withdrawn { + member: T::AccountId, + pool_id: PoolId, + balance: BalanceOf, + points: BalanceOf, + }, /// A pool has been destroyed. Destroyed { pool_id: PoolId }, /// The state of a pool has changed @@ -1274,6 +1316,10 @@ pub mod pallet { state_toggler: Option, nominator: Option, }, + /// The active balance of pool `pool_id` has been slashed to `balance`. + PoolSlashed { pool_id: PoolId, balance: BalanceOf }, + /// The unbond pool at `era` of pool `pool_id` has been slashed to `balance`. + UnbondingPoolSlashed { pool_id: PoolId, era: EraIndex, balance: BalanceOf }, } #[pallet::error] @@ -1290,10 +1336,6 @@ pub mod pallet { /// An account is already delegating in another pool. An account may only belong to one /// pool at a time. AccountBelongsToOtherPool, - /// The pool has insufficient balance to bond as a nominator. - InsufficientBond, - /// The member is already unbonding in this era. - AlreadyUnbonding, /// The member is fully unbonded (and thus cannot access the bonded and reward pool /// anymore to, for example, collect rewards). FullyUnbonding, @@ -1346,6 +1388,9 @@ pub mod pallet { RewardPoolNotFound, /// A sub pool does not exist. SubPoolsNotFound, + /// The bonded account should only be killed by the staking system when the depositor is + /// withdrawing + BondedStashKilledPrematurely, } impl From for Error { @@ -1477,7 +1522,7 @@ pub mod pallet { /// Unbond up to `unbonding_points` of the `member_account`'s funds from the pool. It /// implicitly collects the rewards one last time, since not doing so would mean some - /// rewards would go forfeited. + /// rewards would be forfeited. /// /// Under certain conditions, this call can be dispatched permissionlessly (i.e. by any /// account). @@ -1528,9 +1573,6 @@ pub mod pallet { let current_era = T::StakingInterface::current_era(); let unbond_era = T::StakingInterface::bonding_duration().saturating_add(current_era); - // Try and unbond in the member map. - member.try_unbond(unbonding_points, unbond_era)?; - // Unbond in the actual underlying nominator. let unbonding_balance = bonded_pool.dissolve(unbonding_points); T::StakingInterface::unbond(bonded_pool.bonded_account(), unbonding_balance)?; @@ -1553,17 +1595,21 @@ pub mod pallet { })?; } - sub_pools + let points_unbonded = sub_pools .with_era .get_mut(&unbond_era) // The above check ensures the pool exists. .defensive_ok_or::>(DefensiveError::PoolNotFound.into())? .issue(unbonding_balance); + // Try and unbond in the member map. + member.try_unbond(unbonding_points, points_unbonded, unbond_era)?; + Self::deposit_event(Event::::Unbonded { member: member_account.clone(), pool_id: member.pool_id, - amount: unbonding_balance, + points: points_unbonded, + balance: unbonding_balance, }); // Now that we know everything has worked write the items to storage. @@ -1644,14 +1690,23 @@ pub mod pallet { // Before calculate the `balance_to_unbond`, with call withdraw unbonded to ensure the // `transferrable_balance` is correct. - T::StakingInterface::withdraw_unbonded( + let stash_killed = T::StakingInterface::withdraw_unbonded( bonded_pool.bonded_account(), num_slashing_spans, )?; + // defensive-only: the depositor puts enough funds into the stash so that it will only + // be destroyed when they are leaving. + ensure!( + !stash_killed || caller == bonded_pool.roles.depositor, + Error::::Defensive(DefensiveError::BondedStashKilledPrematurely) + ); + + let mut sum_unlocked_points: BalanceOf = Zero::zero(); let balance_to_unbond = withdrawn_points .iter() .fold(BalanceOf::::zero(), |accumulator, (era, unlocked_points)| { + sum_unlocked_points = sum_unlocked_points.saturating_add(*unlocked_points); if let Some(era_pool) = sub_pools.with_era.get_mut(&era) { let balance_to_unbond = era_pool.dissolve(*unlocked_points); if era_pool.points.is_zero() { @@ -1684,7 +1739,8 @@ pub mod pallet { Self::deposit_event(Event::::Withdrawn { member: member_account.clone(), pool_id: member.pool_id, - amount: balance_to_unbond, + points: sum_unlocked_points, + balance: balance_to_unbond, }); let post_info_weight = if member.total_points().is_zero() { @@ -1811,6 +1867,13 @@ pub mod pallet { Ok(()) } + /// Nominate on behalf of the pool. + /// + /// The dispatch origin of this call must be signed by the pool nominator or the pool + /// root role. + /// + /// This directly forward the call to the staking pallet, on behalf of the pool bonded + /// account. #[pallet::weight(T::WeightInfo::nominate(validators.len() as u32))] pub fn nominate( origin: OriginFor, @@ -1820,10 +1883,13 @@ pub mod pallet { let who = ensure_signed(origin)?; let bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; ensure!(bonded_pool.can_nominate(&who), Error::::NotNominator); - T::StakingInterface::nominate(bonded_pool.bonded_account(), validators)?; - Ok(()) + T::StakingInterface::nominate(bonded_pool.bonded_account(), validators) } + /// Set a new state for the pool. + /// + /// The dispatch origin of this call must be signed by the state toggler, or the root role + /// of the pool. #[pallet::weight(T::WeightInfo::set_state())] pub fn set_state( origin: OriginFor, @@ -1850,6 +1916,10 @@ pub mod pallet { Ok(()) } + /// Set a new metadata for the pool. + /// + /// The dispatch origin of this call must be signed by the state toggler, or the root role + /// of the pool. #[pallet::weight(T::WeightInfo::set_metadata(metadata.len() as u32))] pub fn set_metadata( origin: OriginFor, @@ -1961,6 +2031,21 @@ pub mod pallet { bonded_pool.put(); Ok(()) } + + /// Chill on behalf of the pool. + /// + /// The dispatch origin of this call must be signed by the pool nominator or the pool + /// root role, same as [`Pallet::nominate`]. + /// + /// This directly forward the call to the staking pallet, on behalf of the pool bonded + /// account. + #[pallet::weight(T::WeightInfo::chill())] + pub fn chill(origin: OriginFor, pool_id: PoolId) -> DispatchResult { + let who = ensure_signed(origin)?; + let bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; + ensure!(bonded_pool.can_nominate(&who), Error::::NotNominator); + T::StakingInterface::chill(bonded_pool.bonded_account()) + } } #[pallet::hooks] @@ -2175,6 +2260,7 @@ impl Pallet { if reward_pool.total_earnings == BalanceOf::::max_value() { bonded_pool.set_state(PoolState::Destroying); }; + member.reward_pool_total_earnings = reward_pool.total_earnings; reward_pool.points = current_points.saturating_sub(member_virtual_points); reward_pool.balance = reward_pool.balance.saturating_sub(member_payout); @@ -2355,19 +2441,26 @@ impl OnStakerSlash> for Pallet { pool_account: &T::AccountId, // Bonded balance is always read directly from staking, therefore we need not update // anything here. - _slashed_bonded: BalanceOf, + slashed_bonded: BalanceOf, slashed_unlocking: &BTreeMap>, ) { - if let Some(pool_id) = ReversePoolIdLookup::::get(pool_account).defensive() { + if let Some(pool_id) = ReversePoolIdLookup::::get(pool_account) { let mut sub_pools = match SubPoolsStorage::::get(pool_id).defensive() { Some(sub_pools) => sub_pools, None => return, }; for (era, slashed_balance) in slashed_unlocking.iter() { if let Some(pool) = sub_pools.with_era.get_mut(era) { - pool.balance = *slashed_balance + pool.balance = *slashed_balance; + Self::deposit_event(Event::::UnbondingPoolSlashed { + era: *era, + pool_id, + balance: *slashed_balance, + }); } } + + Self::deposit_event(Event::::PoolSlashed { pool_id, balance: slashed_bonded }); SubPoolsStorage::::insert(pool_id, sub_pools); } } diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index 1dadd7e00c528..a3020e5add044 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -2,7 +2,6 @@ use super::*; use crate::{self as pools}; use frame_support::{assert_ok, parameter_types, PalletId}; use frame_system::RawOrigin; -use std::collections::HashMap; pub type AccountId = u128; pub type Balance = u128; @@ -20,17 +19,19 @@ pub fn default_reward_account() -> AccountId { parameter_types! { pub static CurrentEra: EraIndex = 0; pub static BondingDuration: EraIndex = 3; - static BondedBalanceMap: HashMap = Default::default(); - static UnbondingBalanceMap: HashMap = Default::default(); + pub storage BondedBalanceMap: BTreeMap = Default::default(); + pub storage UnbondingBalanceMap: BTreeMap = Default::default(); #[derive(Clone, PartialEq)] pub static MaxUnbonding: u32 = 8; - pub static Nominations: Vec = vec![]; + pub storage Nominations: Option> = None; } pub struct StakingMock; impl StakingMock { pub(crate) fn set_bonded_balance(who: AccountId, bonded: Balance) { - BONDED_BALANCE_MAP.with(|m| m.borrow_mut().insert(who, bonded)); + let mut x = BondedBalanceMap::get(); + x.insert(who, bonded); + BondedBalanceMap::set(&x) } } @@ -66,22 +67,33 @@ impl sp_staking::StakingInterface for StakingMock { } fn bond_extra(who: Self::AccountId, extra: Self::Balance) -> DispatchResult { - BONDED_BALANCE_MAP.with(|m| *m.borrow_mut().get_mut(&who).unwrap() += extra); + let mut x = BondedBalanceMap::get(); + x.get_mut(&who).map(|v| *v += extra); + BondedBalanceMap::set(&x); Ok(()) } fn unbond(who: Self::AccountId, amount: Self::Balance) -> DispatchResult { - BONDED_BALANCE_MAP.with(|m| *m.borrow_mut().get_mut(&who).unwrap() -= amount); - UNBONDING_BALANCE_MAP - .with(|m| *m.borrow_mut().entry(who).or_insert(Self::Balance::zero()) += amount); + let mut x = BondedBalanceMap::get(); + *x.get_mut(&who).unwrap() = x.get_mut(&who).unwrap().saturating_sub(amount); + BondedBalanceMap::set(&x); + let mut y = UnbondingBalanceMap::get(); + *y.entry(who).or_insert(Self::Balance::zero()) += amount; + UnbondingBalanceMap::set(&y); Ok(()) } - fn withdraw_unbonded(who: Self::AccountId, _: u32) -> Result { + fn chill(_: Self::AccountId) -> sp_runtime::DispatchResult { + Ok(()) + } + + fn withdraw_unbonded(who: Self::AccountId, _: u32) -> Result { // Simulates removing unlocking chunks and only having the bonded balance locked - let _maybe_new_free = UNBONDING_BALANCE_MAP.with(|m| m.borrow_mut().remove(&who)); + let mut x = UnbondingBalanceMap::get(); + x.remove(&who); + UnbondingBalanceMap::set(&x); - Ok(100) + Ok(UnbondingBalanceMap::get().is_empty() && BondedBalanceMap::get().is_empty()) } fn bond( @@ -95,9 +107,13 @@ impl sp_staking::StakingInterface for StakingMock { } fn nominate(_: Self::AccountId, nominations: Vec) -> DispatchResult { - Nominations::set(nominations); + Nominations::set(&Some(nominations)); Ok(()) } + + fn nominations(_: Self::AccountId) -> Option> { + Nominations::get() + } } impl frame_system::Config for Runtime { @@ -162,7 +178,6 @@ parameter_types! { pub static MaxMetadataLen: u32 = 2; pub static CheckLevel: u8 = 255; pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls"); - pub const MinPointsToBalance: u32 = 10; } impl pools::Config for Runtime { type Event = Event; @@ -175,7 +190,7 @@ impl pools::Config for Runtime { type PalletId = PoolsPalletId; type MaxMetadataLen = MaxMetadataLen; type MaxUnbonding = MaxUnbonding; - type MinPointsToBalance = MinPointsToBalance; + type MinPointsToBalance = frame_support::traits::ConstU32<10>; } type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; @@ -265,7 +280,8 @@ pub(crate) fn unsafe_set_state(pool_id: PoolId, state: PoolState) -> Result<(), } parameter_types! { - static ObservedEvents: usize = 0; + static PoolsEvents: usize = 0; + static BalancesEvents: usize = 0; } /// All events of this pallet. @@ -275,8 +291,20 @@ pub(crate) fn pool_events_since_last_call() -> Vec> { .map(|r| r.event) .filter_map(|e| if let Event::Pools(inner) = e { Some(inner) } else { None }) .collect::>(); - let already_seen = ObservedEvents::get(); - ObservedEvents::set(events.len()); + let already_seen = PoolsEvents::get(); + PoolsEvents::set(events.len()); + events.into_iter().skip(already_seen).collect() +} + +/// All events of the `Balances` pallet. +pub(crate) fn balances_events_since_last_call() -> Vec> { + let events = System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let Event::Balances(inner) = e { Some(inner) } else { None }) + .collect::>(); + let already_seen = BalancesEvents::get(); + BalancesEvents::set(events.len()); events.into_iter().skip(already_seen).collect() } diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 93218c78c83fc..97104423c5910 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -18,9 +18,10 @@ use super::*; use crate::{mock::*, Event}; use frame_support::{ - assert_noop, assert_ok, assert_storage_noop, bounded_btree_map, + assert_err, assert_noop, assert_ok, assert_storage_noop, bounded_btree_map, storage::{with_transaction, TransactionOutcome}, }; +use pallet_balances::Event as BEvent; use sp_runtime::traits::Dispatchable; macro_rules! unbonding_pools_with_era { @@ -65,7 +66,7 @@ fn test_setup_works() { ); assert_eq!( RewardPools::::get(last_pool).unwrap(), - RewardPool:: { balance: 0, points: 0.into(), total_earnings: 0 } + RewardPool:: { balance: 0, points: 0u32.into(), total_earnings: 0 } ); assert_eq!( PoolMembers::::get(10).unwrap(), @@ -80,7 +81,7 @@ fn test_setup_works() { assert_eq!(StakingMock::total_stake(&bonded_account).unwrap(), 10); // but not nominating yet. - assert!(Nominations::get().is_empty()); + assert!(Nominations::get().is_none()); // reward account should have an initial ED in it. assert_eq!(Balances::free_balance(&reward_account), Balances::minimum_balance()); @@ -90,101 +91,106 @@ fn test_setup_works() { mod bonded_pool { use super::*; #[test] - fn points_to_issue_works() { - let mut bonded_pool = BondedPool:: { - id: 123123, - inner: BondedPoolInner { - state: PoolState::Open, - points: 100, - member_counter: 1, - roles: DEFAULT_ROLES, - }, - }; + fn balance_to_point_works() { + ExtBuilder::default().build_and_execute(|| { + let mut bonded_pool = BondedPool:: { + id: 123123, + inner: BondedPoolInner { + state: PoolState::Open, + points: 100, + member_counter: 1, + roles: DEFAULT_ROLES, + }, + }; - // 1 points : 1 balance ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); - assert_eq!(bonded_pool.balance_to_point(10), 10); - assert_eq!(bonded_pool.balance_to_point(0), 0); - - // 2 points : 1 balance ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 50); - assert_eq!(bonded_pool.balance_to_point(10), 20); - - // 1 points : 2 balance ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); - bonded_pool.points = 50; - assert_eq!(bonded_pool.balance_to_point(10), 5); - - // 100 points : 0 balance ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 0); - bonded_pool.points = 100; - assert_eq!(bonded_pool.balance_to_point(10), 100 * 10); - - // 0 points : 100 balance - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); - bonded_pool.points = 100; - assert_eq!(bonded_pool.balance_to_point(10), 10); - - // 10 points : 3 balance ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 30); - assert_eq!(bonded_pool.balance_to_point(10), 33); - - // 2 points : 3 balance ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 300); - bonded_pool.points = 200; - assert_eq!(bonded_pool.balance_to_point(10), 6); - - // 4 points : 9 balance ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 900); - bonded_pool.points = 400; - assert_eq!(bonded_pool.balance_to_point(90), 40); - } + // 1 points : 1 balance ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); + assert_eq!(bonded_pool.balance_to_point(10), 10); + assert_eq!(bonded_pool.balance_to_point(0), 0); - #[test] - fn balance_to_unbond_works() { - // 1 balance : 1 points ratio - let mut bonded_pool = BondedPool:: { - id: 123123, - inner: BondedPoolInner { - state: PoolState::Open, - points: 100, - member_counter: 1, - roles: DEFAULT_ROLES, - }, - }; + // 2 points : 1 balance ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 50); + assert_eq!(bonded_pool.balance_to_point(10), 20); - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); - assert_eq!(bonded_pool.points_to_balance(10), 10); - assert_eq!(bonded_pool.points_to_balance(0), 0); + // 1 points : 2 balance ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); + bonded_pool.points = 50; + assert_eq!(bonded_pool.balance_to_point(10), 5); - // 2 balance : 1 points ratio - bonded_pool.points = 50; - assert_eq!(bonded_pool.points_to_balance(10), 20); + // 100 points : 0 balance ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 0); + bonded_pool.points = 100; + assert_eq!(bonded_pool.balance_to_point(10), 100 * 10); - // 100 balance : 0 points ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 0); - bonded_pool.points = 0; - assert_eq!(bonded_pool.points_to_balance(10), 0); + // 0 points : 100 balance + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); + bonded_pool.points = 0; + assert_eq!(bonded_pool.balance_to_point(10), 10); - // 0 balance : 100 points ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 0); - bonded_pool.points = 100; - assert_eq!(bonded_pool.points_to_balance(10), 0); + // 10 points : 3 balance ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 30); + bonded_pool.points = 100; + assert_eq!(bonded_pool.balance_to_point(10), 33); - // 10 balance : 3 points ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); - bonded_pool.points = 30; - assert_eq!(bonded_pool.points_to_balance(10), 33); + // 2 points : 3 balance ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 300); + bonded_pool.points = 200; + assert_eq!(bonded_pool.balance_to_point(10), 6); - // 2 balance : 3 points ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 200); - bonded_pool.points = 300; - assert_eq!(bonded_pool.points_to_balance(10), 6); + // 4 points : 9 balance ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 900); + bonded_pool.points = 400; + assert_eq!(bonded_pool.balance_to_point(90), 40); + }) + } - // 4 balance : 9 points ratio - StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 400); - bonded_pool.points = 900; - assert_eq!(bonded_pool.points_to_balance(90), 40); + #[test] + fn points_to_balance_works() { + ExtBuilder::default().build_and_execute(|| { + // 1 balance : 1 points ratio + let mut bonded_pool = BondedPool:: { + id: 123123, + inner: BondedPoolInner { + state: PoolState::Open, + points: 100, + member_counter: 1, + roles: DEFAULT_ROLES, + }, + }; + + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); + assert_eq!(bonded_pool.points_to_balance(10), 10); + assert_eq!(bonded_pool.points_to_balance(0), 0); + + // 2 balance : 1 points ratio + bonded_pool.points = 50; + assert_eq!(bonded_pool.points_to_balance(10), 20); + + // 100 balance : 0 points ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); + bonded_pool.points = 0; + assert_eq!(bonded_pool.points_to_balance(10), 0); + + // 0 balance : 100 points ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 0); + bonded_pool.points = 100; + assert_eq!(bonded_pool.points_to_balance(10), 0); + + // 10 balance : 3 points ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100); + bonded_pool.points = 30; + assert_eq!(bonded_pool.points_to_balance(10), 33); + + // 2 balance : 3 points ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 200); + bonded_pool.points = 300; + assert_eq!(bonded_pool.points_to_balance(10), 6); + + // 4 balance : 9 points ratio + StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 400); + bonded_pool.points = 900; + assert_eq!(bonded_pool.points_to_balance(90), 40); + }) } #[test] @@ -200,7 +206,8 @@ mod bonded_pool { }, }; - let min_points_to_balance: u128 = MinPointsToBalance::get().into(); + let min_points_to_balance: u128 = + <::MinPointsToBalance as Get>::get().into(); // Simulate a 100% slashed pool StakingMock::set_bonded_balance(pool.bonded_account(), 0); @@ -267,38 +274,40 @@ mod unbond_pool { #[test] fn points_to_issue_works() { - // 1 points : 1 balance ratio - let unbond_pool = UnbondPool:: { points: 100, balance: 100 }; - assert_eq!(unbond_pool.balance_to_point(10), 10); - assert_eq!(unbond_pool.balance_to_point(0), 0); - - // 2 points : 1 balance ratio - let unbond_pool = UnbondPool:: { points: 100, balance: 50 }; - assert_eq!(unbond_pool.balance_to_point(10), 20); - - // 1 points : 2 balance ratio - let unbond_pool = UnbondPool:: { points: 50, balance: 100 }; - assert_eq!(unbond_pool.balance_to_point(10), 5); - - // 100 points : 0 balance ratio - let unbond_pool = UnbondPool:: { points: 100, balance: 0 }; - assert_eq!(unbond_pool.balance_to_point(10), 100 * 10); - - // 0 points : 100 balance - let unbond_pool = UnbondPool:: { points: 0, balance: 100 }; - assert_eq!(unbond_pool.balance_to_point(10), 10); - - // 10 points : 3 balance ratio - let unbond_pool = UnbondPool:: { points: 100, balance: 30 }; - assert_eq!(unbond_pool.balance_to_point(10), 33); - - // 2 points : 3 balance ratio - let unbond_pool = UnbondPool:: { points: 200, balance: 300 }; - assert_eq!(unbond_pool.balance_to_point(10), 6); - - // 4 points : 9 balance ratio - let unbond_pool = UnbondPool:: { points: 400, balance: 900 }; - assert_eq!(unbond_pool.balance_to_point(90), 40); + ExtBuilder::default().build_and_execute(|| { + // 1 points : 1 balance ratio + let unbond_pool = UnbondPool:: { points: 100, balance: 100 }; + assert_eq!(unbond_pool.balance_to_point(10), 10); + assert_eq!(unbond_pool.balance_to_point(0), 0); + + // 2 points : 1 balance ratio + let unbond_pool = UnbondPool:: { points: 100, balance: 50 }; + assert_eq!(unbond_pool.balance_to_point(10), 20); + + // 1 points : 2 balance ratio + let unbond_pool = UnbondPool:: { points: 50, balance: 100 }; + assert_eq!(unbond_pool.balance_to_point(10), 5); + + // 100 points : 0 balance ratio + let unbond_pool = UnbondPool:: { points: 100, balance: 0 }; + assert_eq!(unbond_pool.balance_to_point(10), 100 * 10); + + // 0 points : 100 balance + let unbond_pool = UnbondPool:: { points: 0, balance: 100 }; + assert_eq!(unbond_pool.balance_to_point(10), 10); + + // 10 points : 3 balance ratio + let unbond_pool = UnbondPool:: { points: 100, balance: 30 }; + assert_eq!(unbond_pool.balance_to_point(10), 33); + + // 2 points : 3 balance ratio + let unbond_pool = UnbondPool:: { points: 200, balance: 300 }; + assert_eq!(unbond_pool.balance_to_point(10), 6); + + // 4 points : 9 balance ratio + let unbond_pool = UnbondPool:: { points: 400, balance: 900 }; + assert_eq!(unbond_pool.balance_to_point(90), 40); + }) } #[test] @@ -516,7 +525,8 @@ mod join { ); // Force the points:balance ratio to `MinPointsToBalance` (100/10) - let min_points_to_balance: u128 = MinPointsToBalance::get().into(); + let min_points_to_balance: u128 = + <::MinPointsToBalance as Get>::get().into(); StakingMock::set_bonded_balance( Pools::create_bonded_account(123), @@ -972,7 +982,7 @@ mod claim_payout { Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 11, pool_id: 1, bonded: 11, joined: true }, - Event::Unbonded { member: 11, pool_id: 1, amount: 11 } + Event::Unbonded { member: 11, pool_id: 1, points: 11, balance: 11 } ] ); }); @@ -1614,14 +1624,14 @@ mod unbond { Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true }, Event::Bonded { member: 550, pool_id: 1, bonded: 550, joined: true }, Event::PaidOut { member: 40, pool_id: 1, payout: 40 }, - Event::Unbonded { member: 40, pool_id: 1, amount: 6 } + Event::Unbonded { member: 40, pool_id: 1, points: 6, balance: 6 } ] ); assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 94); assert_eq!( PoolMembers::::get(40).unwrap().unbonding_eras, - member_unbonding_eras!(0 + 3 => 40) + member_unbonding_eras!(0 + 3 => 6) ); assert_eq!(Balances::free_balance(&40), 40 + 40); // We claim rewards when unbonding @@ -1649,24 +1659,27 @@ mod unbond { assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 2); assert_eq!( PoolMembers::::get(550).unwrap().unbonding_eras, - member_unbonding_eras!(0 + 3 => 550) + member_unbonding_eras!(0 + 3 => 92) ); assert_eq!(Balances::free_balance(&550), 550 + 550); assert_eq!( pool_events_since_last_call(), vec![ Event::PaidOut { member: 550, pool_id: 1, payout: 550 }, - Event::Unbonded { member: 550, pool_id: 1, amount: 92 } + Event::Unbonded { member: 550, pool_id: 1, points: 92, balance: 92 } ] ); // When + CurrentEra::set(3); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 40, 0)); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 550, 0)); assert_ok!(fully_unbond_permissioned(10)); // Then assert_eq!( SubPoolsStorage::::get(1).unwrap().with_era, - unbonding_pools_with_era! { 0 + 3 => UnbondPool { points: 100, balance: 100 }} + unbonding_pools_with_era! { 6 => UnbondPool { points: 2, balance: 2 }} ); assert_eq!( BondedPool::::get(1).unwrap(), @@ -1675,22 +1688,23 @@ mod unbond { inner: BondedPoolInner { state: PoolState::Destroying, points: 0, - member_counter: 3, + member_counter: 1, roles: DEFAULT_ROLES } } ); assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 0); - assert_eq!( - PoolMembers::::get(550).unwrap().unbonding_eras, - member_unbonding_eras!(0 + 3 => 550) - ); - assert_eq!(Balances::free_balance(&550), 550 + 550); + + assert_eq!(Balances::free_balance(&550), 550 + 550 + 92); assert_eq!( pool_events_since_last_call(), vec![ + Event::Withdrawn { member: 40, pool_id: 1, points: 6, balance: 6 }, + Event::MemberRemoved { pool_id: 1, member: 40 }, + Event::Withdrawn { member: 550, pool_id: 1, points: 92, balance: 92 }, + Event::MemberRemoved { pool_id: 1, member: 550 }, Event::PaidOut { member: 10, pool_id: 1, payout: 10 }, - Event::Unbonded { member: 10, pool_id: 1, amount: 2 } + Event::Unbonded { member: 10, pool_id: 1, points: 2, balance: 2 } ] ); }); @@ -1736,7 +1750,7 @@ mod unbond { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::Unbonded { member: 10, pool_id: 1, amount: 10 } + Event::Unbonded { member: 10, pool_id: 1, points: 10, balance: 10 } ] ); }); @@ -1771,7 +1785,7 @@ mod unbond { Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, Event::Bonded { member: 200, pool_id: 1, bonded: 200, joined: true }, - Event::Unbonded { member: 100, pool_id: 1, amount: 100 }, + Event::Unbonded { member: 100, pool_id: 1, points: 100, balance: 100 }, ] ); @@ -1780,7 +1794,7 @@ mod unbond { assert_eq!( pool_events_since_last_call(), - vec![Event::Unbonded { member: 200, pool_id: 1, amount: 200 }] + vec![Event::Unbonded { member: 200, pool_id: 1, points: 200, balance: 200 }] ); assert_eq!( @@ -1806,8 +1820,7 @@ mod unbond { } ); assert_eq!( - UNBONDING_BALANCE_MAP - .with(|m| *m.borrow_mut().get(&default_bonded_account()).unwrap()), + *UnbondingBalanceMap::get().get(&default_bonded_account()).unwrap(), 100 + 200 ); }); @@ -1849,7 +1862,7 @@ mod unbond { Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, - Event::Unbonded { member: 100, pool_id: 1, amount: 100 } + Event::Unbonded { member: 100, pool_id: 1, points: 100, balance: 100 } ] ); @@ -1871,36 +1884,37 @@ mod unbond { // Given the pools is destroying unsafe_set_state(1, PoolState::Destroying).unwrap(); - // The depositor can be unbonded by anyone. - assert_ok!(Pools::fully_unbond(Origin::signed(420), 10)); - - assert_eq!( - pool_events_since_last_call(), - vec![Event::Unbonded { member: 10, pool_id: 1, amount: 10 }] + // The depositor cannot be unbonded yet. + assert_noop!( + Pools::fully_unbond(Origin::signed(420), 10), + Error::::DoesNotHavePermission, ); + // but when everyone is unbonded it can.. + CurrentEra::set(3); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 100, 0)); + // still permissionless unbond must be full. assert_noop!( Pools::unbond(Origin::signed(420), 10, 5), Error::::PartialUnbondNotAllowedPermissionlessly, ); + // but full unbond works. + assert_ok!(Pools::fully_unbond(Origin::signed(420), 10)); + assert_eq!(BondedPools::::get(1).unwrap().points, 0); assert_eq!( SubPoolsStorage::::get(1).unwrap(), SubPools { no_era: Default::default(), with_era: unbonding_pools_with_era! { - 0 + 3 => UnbondPool { points: 110, balance: 110 } + 3 + 3 => UnbondPool { points: 10, balance: 10 } } } ); assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 0); - assert_eq!( - UNBONDING_BALANCE_MAP - .with(|m| *m.borrow_mut().get(&default_bonded_account()).unwrap()), - 110 - ); + assert_eq!(*UnbondingBalanceMap::get().get(&default_bonded_account()).unwrap(), 10); }); } @@ -1988,7 +2002,7 @@ mod unbond { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::Unbonded { member: 10, pool_id: 1, amount: 1 } + Event::Unbonded { member: 10, pool_id: 1, points: 1, balance: 1 } ] ); @@ -2014,7 +2028,7 @@ mod unbond { ); assert_eq!( pool_events_since_last_call(), - vec![Event::Unbonded { member: 10, pool_id: 1, amount: 5 }] + vec![Event::Unbonded { member: 10, pool_id: 1, points: 5, balance: 5 }] ); // when: casual further unbond, next era. @@ -2041,12 +2055,16 @@ mod unbond { ); assert_eq!( pool_events_since_last_call(), - vec![Event::Unbonded { member: 10, pool_id: 1, amount: 1 }] + vec![Event::Unbonded { member: 10, pool_id: 1, points: 1, balance: 1 }] ); // when: unbonding more than our active: error - assert_noop!( - Pools::unbond(Origin::signed(10), 10, 5), + assert_err!( + frame_support::storage::in_storage_layer(|| Pools::unbond( + Origin::signed(10), + 10, + 5 + )), Error::::NotEnoughPointsToUnbond ); // instead: @@ -2072,7 +2090,7 @@ mod unbond { ); assert_eq!( pool_events_since_last_call(), - vec![Event::Unbonded { member: 10, pool_id: 1, amount: 3 }] + vec![Event::Unbonded { member: 10, pool_id: 1, points: 3, balance: 3 }] ); }); } @@ -2095,8 +2113,12 @@ mod unbond { // when CurrentEra::set(2); - assert_noop!( - Pools::unbond(Origin::signed(10), 10, 4), + assert_err!( + frame_support::storage::in_storage_layer(|| Pools::unbond( + Origin::signed(10), + 10, + 4 + )), Error::::MaxUnbondingLimit ); @@ -2112,9 +2134,9 @@ mod unbond { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::Unbonded { member: 10, pool_id: 1, amount: 2 }, - Event::Unbonded { member: 10, pool_id: 1, amount: 3 }, - Event::Unbonded { member: 10, pool_id: 1, amount: 1 } + Event::Unbonded { member: 10, pool_id: 1, points: 2, balance: 2 }, + Event::Unbonded { member: 10, pool_id: 1, points: 3, balance: 3 }, + Event::Unbonded { member: 10, pool_id: 1, points: 1, balance: 1 } ] ); }) @@ -2145,7 +2167,7 @@ mod unbond { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::Unbonded { member: 10, pool_id: 1, amount: 3 } + Event::Unbonded { member: 10, pool_id: 1, points: 3, balance: 3 } ] ); }); @@ -2201,7 +2223,7 @@ mod unbond { Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, // exactly equal to ed, all that can be claimed. Event::PaidOut { member: 10, pool_id: 1, payout: 5 }, - Event::Unbonded { member: 10, pool_id: 1, amount: 2 } + Event::Unbonded { member: 10, pool_id: 1, points: 2, balance: 2 } ] ); @@ -2217,7 +2239,7 @@ mod unbond { vec![ // exactly equal to ed, all that can be claimed. Event::PaidOut { member: 10, pool_id: 1, payout: 5 }, - Event::Unbonded { member: 10, pool_id: 1, amount: 3 } + Event::Unbonded { member: 10, pool_id: 1, points: 3, balance: 3 } ] ); @@ -2232,7 +2254,7 @@ mod unbond { pool_events_since_last_call(), vec![ Event::PaidOut { member: 10, pool_id: 1, payout: 5 }, - Event::Unbonded { member: 10, pool_id: 1, amount: 5 } + Event::Unbonded { member: 10, pool_id: 1, points: 5, balance: 5 } ] ); @@ -2277,6 +2299,9 @@ mod withdraw_unbonded { ExtBuilder::default() .add_members(vec![(40, 40), (550, 550)]) .build_and_execute(|| { + // reduce the noise a bit. + let _ = balances_events_since_last_call(); + // Given assert_eq!(StakingMock::bonding_duration(), 3); assert_ok!(Pools::fully_unbond(Origin::signed(550), 550)); @@ -2285,23 +2310,30 @@ mod withdraw_unbonded { let mut current_era = 1; CurrentEra::set(current_era); - // In a new era, unbond the depositor - unsafe_set_state(1, PoolState::Destroying).unwrap(); - assert_ok!(Pools::fully_unbond(Origin::signed(10), 10)); let mut sub_pools = SubPoolsStorage::::get(1).unwrap(); - let unbond_pool = sub_pools.with_era.get_mut(&(current_era + 3)).unwrap(); + let unbond_pool = sub_pools.with_era.get_mut(&3).unwrap(); // Sanity check - assert_eq!(*unbond_pool, UnbondPool { points: 10, balance: 10 }); + assert_eq!(*unbond_pool, UnbondPool { points: 550 + 40, balance: 550 + 40 }); // Simulate a slash to the pool with_era(current_era), decreasing the balance by // half - unbond_pool.balance = 5; - SubPoolsStorage::::insert(1, sub_pools); - // Update the equivalent of the unbonding chunks for the `StakingMock` - UNBONDING_BALANCE_MAP - .with(|m| *m.borrow_mut().get_mut(&default_bonded_account()).unwrap() -= 5); - Balances::make_free_balance_be(&default_bonded_account(), 595); + { + unbond_pool.balance /= 2; // 295 + SubPoolsStorage::::insert(1, sub_pools); + // Update the equivalent of the unbonding chunks for the `StakingMock` + let mut x = UnbondingBalanceMap::get(); + *x.get_mut(&default_bonded_account()).unwrap() /= 5; + UnbondingBalanceMap::set(&x); + Balances::make_free_balance_be( + &default_bonded_account(), + Balances::free_balance(&default_bonded_account()) / 2, // 300 + ); + StakingMock::set_bonded_balance( + default_bonded_account(), + StakingMock::active_stake(&default_bonded_account()).unwrap() / 2, + ); + }; // Advance the current_era to ensure all `with_era` pools will be merged into // `no_era` pool @@ -2311,27 +2343,56 @@ mod withdraw_unbonded { // Simulate some other call to unbond that would merge `with_era` pools into // `no_era` let sub_pools = - SubPoolsStorage::::get(1).unwrap().maybe_merge_pools(current_era + 3); + SubPoolsStorage::::get(1).unwrap().maybe_merge_pools(current_era); SubPoolsStorage::::insert(1, sub_pools); + assert_eq!( SubPoolsStorage::::get(1).unwrap(), SubPools { - no_era: UnbondPool { points: 550 + 40 + 10, balance: 550 + 40 + 5 }, + no_era: UnbondPool { points: 550 + 40, balance: 275 + 20 }, with_era: Default::default() } ); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true }, + Event::Bonded { member: 550, pool_id: 1, bonded: 550, joined: true }, + Event::Unbonded { member: 550, pool_id: 1, points: 550, balance: 550 }, + Event::Unbonded { member: 40, pool_id: 1, points: 40, balance: 40 }, + ] + ); + assert_eq!( + balances_events_since_last_call(), + vec![BEvent::BalanceSet { + who: default_bonded_account(), + free: 300, + reserved: 0 + }] + ); + // When assert_ok!(Pools::withdraw_unbonded(Origin::signed(550), 550, 0)); // Then assert_eq!( SubPoolsStorage::::get(1).unwrap().no_era, - UnbondPool { points: 40 + 10, balance: 40 + 5 + 5 } + UnbondPool { points: 40, balance: 20 } + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Withdrawn { member: 550, pool_id: 1, balance: 275, points: 550 }, + Event::MemberRemoved { pool_id: 1, member: 550 } + ] + ); + assert_eq!( + balances_events_since_last_call(), + vec![BEvent::Transfer { from: default_bonded_account(), to: 550, amount: 275 }] ); - assert_eq!(Balances::free_balance(&550), 550 + 545); - assert_eq!(Balances::free_balance(&default_bonded_account()), 50); - assert!(!PoolMembers::::contains_key(550)); // When assert_ok!(Pools::withdraw_unbonded(Origin::signed(40), 40, 0)); @@ -2339,128 +2400,167 @@ mod withdraw_unbonded { // Then assert_eq!( SubPoolsStorage::::get(1).unwrap().no_era, - UnbondPool { points: 10, balance: 10 } + UnbondPool { points: 0, balance: 0 } ); - assert_eq!(Balances::free_balance(&40), 40 + 40); - assert_eq!(Balances::free_balance(&default_bonded_account()), 50 - 40); assert!(!PoolMembers::::contains_key(40)); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Withdrawn { member: 40, pool_id: 1, balance: 20, points: 40 }, + Event::MemberRemoved { pool_id: 1, member: 40 } + ] + ); + assert_eq!( + balances_events_since_last_call(), + vec![BEvent::Transfer { from: default_bonded_account(), to: 40, amount: 20 }] + ); - // When - assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 0)); + // now, finally, the depositor can take out its share. + unsafe_set_state(1, PoolState::Destroying).unwrap(); + assert_ok!(fully_unbond_permissioned(10)); - // Then - assert_eq!(Balances::free_balance(&10), 10 + 10); - assert_eq!(Balances::free_balance(&default_bonded_account()), 0); - assert!(!PoolMembers::::contains_key(10)); - // Pools are removed from storage because the depositor left - assert!(!SubPoolsStorage::::contains_key(1),); - assert!(!RewardPools::::contains_key(1),); - assert!(!BondedPools::::contains_key(1),); + current_era += 3; + CurrentEra::set(current_era); + // when + assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 0)); assert_eq!( pool_events_since_last_call(), vec![ - Event::Created { depositor: 10, pool_id: 1 }, - Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true }, - Event::Bonded { member: 550, pool_id: 1, bonded: 550, joined: true }, - Event::Unbonded { member: 550, pool_id: 1, amount: 550 }, - Event::Unbonded { member: 40, pool_id: 1, amount: 40 }, - Event::Unbonded { member: 10, pool_id: 1, amount: 10 }, - Event::Withdrawn { member: 550, pool_id: 1, amount: 545 }, - Event::MemberRemoved { pool_id: 1, member: 550 }, - Event::Withdrawn { member: 40, pool_id: 1, amount: 40 }, - Event::MemberRemoved { pool_id: 1, member: 40 }, - Event::Withdrawn { member: 10, pool_id: 1, amount: 10 }, + Event::Unbonded { member: 10, pool_id: 1, balance: 5, points: 5 }, + Event::Withdrawn { member: 10, pool_id: 1, balance: 5, points: 5 }, Event::MemberRemoved { pool_id: 1, member: 10 }, Event::Destroyed { pool_id: 1 } ] ); + assert_eq!( + balances_events_since_last_call(), + vec![ + BEvent::Transfer { from: default_bonded_account(), to: 10, amount: 5 }, + BEvent::Transfer { from: default_reward_account(), to: 10, amount: 5 } + ] + ); }); } - // This test also documents the case when the pools free balance goes below ED before all - // members have unbonded. #[test] fn withdraw_unbonded_works_against_slashed_with_era_sub_pools() { ExtBuilder::default() .add_members(vec![(40, 40), (550, 550)]) .build_and_execute(|| { + let _ = balances_events_since_last_call(); + // Given - StakingMock::set_bonded_balance(default_bonded_account(), 100); // slash bonded balance - Balances::make_free_balance_be(&default_bonded_account(), 100); - assert_eq!(StakingMock::total_stake(&default_bonded_account()), Some(100)); + // current bond is 600, we slash it all to 300. + StakingMock::set_bonded_balance(default_bonded_account(), 300); + Balances::make_free_balance_be(&default_bonded_account(), 300); + assert_eq!(StakingMock::total_stake(&default_bonded_account()), Some(300)); - assert_ok!(Pools::fully_unbond(Origin::signed(40), 40)); - assert_ok!(Pools::fully_unbond(Origin::signed(550), 550)); - unsafe_set_state(1, PoolState::Destroying).unwrap(); - assert_ok!(Pools::fully_unbond(Origin::signed(10), 10)); + assert_ok!(fully_unbond_permissioned(40)); + assert_ok!(fully_unbond_permissioned(550)); - SubPoolsStorage::::insert( - 1, - SubPools { - no_era: Default::default(), - with_era: unbonding_pools_with_era! { 0 + 3 => UnbondPool { points: 600, balance: 100 }}, - }, + assert_eq!( + SubPoolsStorage::::get(&1).unwrap().with_era, + unbonding_pools_with_era! { 3 => UnbondPool { points: 550 / 2 + 40 / 2, balance: 550 / 2 + 40 / 2 }} + ); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true }, + Event::Bonded { member: 550, pool_id: 1, bonded: 550, joined: true }, + Event::Unbonded { member: 40, pool_id: 1, balance: 20, points: 20 }, + Event::Unbonded { member: 550, pool_id: 1, balance: 275, points: 275 }, + ] + ); + assert_eq!( + balances_events_since_last_call(), + vec![BEvent::BalanceSet { + who: default_bonded_account(), + free: 300, + reserved: 0 + },] ); + CurrentEra::set(StakingMock::bonding_duration()); // When assert_ok!(Pools::withdraw_unbonded(Origin::signed(40), 40, 0)); // Then + assert_eq!( + balances_events_since_last_call(), + vec![BEvent::Transfer { from: default_bonded_account(), to: 40, amount: 20 },] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Withdrawn { member: 40, pool_id: 1, balance: 20, points: 20 }, + Event::MemberRemoved { pool_id: 1, member: 40 } + ] + ); + assert_eq!( SubPoolsStorage::::get(&1).unwrap().with_era, - unbonding_pools_with_era! { 0 + 3 => UnbondPool { points: 560, balance: 94 }} + unbonding_pools_with_era! { 3 => UnbondPool { points: 550 / 2, balance: 550 / 2 }} ); - assert_eq!(Balances::free_balance(&40), 40 + 6); - assert_eq!(Balances::free_balance(&default_bonded_account()), 94); - assert!(!PoolMembers::::contains_key(40)); // When assert_ok!(Pools::withdraw_unbonded(Origin::signed(550), 550, 0)); // Then + assert_eq!( + balances_events_since_last_call(), + vec![BEvent::Transfer { from: default_bonded_account(), to: 550, amount: 275 },] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Withdrawn { member: 550, pool_id: 1, balance: 275, points: 275 }, + Event::MemberRemoved { pool_id: 1, member: 550 } + ] + ); + assert!(SubPoolsStorage::::get(&1).unwrap().with_era.is_empty()); + + // now, finally, the depositor can take out its share. + unsafe_set_state(1, PoolState::Destroying).unwrap(); + assert_ok!(fully_unbond_permissioned(10)); + + // because everyone else has left, the points assert_eq!( SubPoolsStorage::::get(&1).unwrap().with_era, - unbonding_pools_with_era! { 0 + 3 => UnbondPool { points: 10, balance: 2 }} + unbonding_pools_with_era! { 6 => UnbondPool { points: 5, balance: 5 }} ); - assert_eq!(Balances::free_balance(&550), 550 + 92); - // The account was dusted because it went below ED(5) - assert_eq!(Balances::free_balance(&default_bonded_account()), 0); - assert!(!PoolMembers::::contains_key(550)); - // When + CurrentEra::set(CurrentEra::get() + 3); + + // when assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 0)); - // Then - assert_eq!(Balances::free_balance(&10), 10 + 0); + // then + assert_eq!(Balances::free_balance(&10), 10 + 5); assert_eq!(Balances::free_balance(&default_bonded_account()), 0); - assert!(!PoolMembers::::contains_key(10)); - // Pools are removed from storage because the depositor left - assert!(!SubPoolsStorage::::contains_key(1)); - assert!(!RewardPools::::contains_key(1)); - assert!(!BondedPools::::contains_key(1)); + // in this test 10 also gets a fair share of the slash, because the slash was + // applied to the bonded account. assert_eq!( pool_events_since_last_call(), vec![ - Event::Created { depositor: 10, pool_id: 1 }, - Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true }, - Event::Bonded { member: 550, pool_id: 1, bonded: 550, joined: true }, - Event::Unbonded { member: 40, pool_id: 1, amount: 6 }, - Event::Unbonded { member: 550, pool_id: 1, amount: 92 }, - Event::Unbonded { member: 10, pool_id: 1, amount: 2 }, - Event::Withdrawn { member: 40, pool_id: 1, amount: 6 }, - Event::MemberRemoved { pool_id: 1, member: 40 }, - Event::Withdrawn { member: 550, pool_id: 1, amount: 92 }, - Event::MemberRemoved { pool_id: 1, member: 550 }, - Event::Withdrawn { member: 10, pool_id: 1, amount: 0 }, + Event::Unbonded { member: 10, pool_id: 1, points: 5, balance: 5 }, + Event::Withdrawn { member: 10, pool_id: 1, points: 5, balance: 5 }, Event::MemberRemoved { pool_id: 1, member: 10 }, Event::Destroyed { pool_id: 1 } ] ); + assert_eq!( + balances_events_since_last_call(), + vec![ + BEvent::Transfer { from: default_bonded_account(), to: 10, amount: 5 }, + BEvent::Transfer { from: default_reward_account(), to: 10, amount: 5 } + ] + ); }); } @@ -2562,8 +2662,8 @@ mod withdraw_unbonded { Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, Event::Bonded { member: 200, pool_id: 1, bonded: 200, joined: true }, - Event::Unbonded { member: 100, pool_id: 1, amount: 100 }, - Event::Unbonded { member: 200, pool_id: 1, amount: 200 } + Event::Unbonded { member: 100, pool_id: 1, points: 100, balance: 100 }, + Event::Unbonded { member: 200, pool_id: 1, points: 200, balance: 200 } ] ); @@ -2590,9 +2690,9 @@ mod withdraw_unbonded { assert_eq!( pool_events_since_last_call(), vec![ - Event::Withdrawn { member: 100, pool_id: 1, amount: 100 }, + Event::Withdrawn { member: 100, pool_id: 1, points: 100, balance: 100 }, Event::MemberRemoved { pool_id: 1, member: 100 }, - Event::Withdrawn { member: 200, pool_id: 1, amount: 200 }, + Event::Withdrawn { member: 200, pool_id: 1, points: 200, balance: 200 }, Event::MemberRemoved { pool_id: 1, member: 200 } ] ); @@ -2640,209 +2740,14 @@ mod withdraw_unbonded { Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, - Event::Unbonded { member: 100, pool_id: 1, amount: 100 }, - Event::Withdrawn { member: 100, pool_id: 1, amount: 100 }, + Event::Unbonded { member: 100, pool_id: 1, points: 100, balance: 100 }, + Event::Withdrawn { member: 100, pool_id: 1, points: 100, balance: 100 }, Event::MemberRemoved { pool_id: 1, member: 100 } ] ); }); } - #[test] - fn withdraw_unbonded_depositor_with_era_pool() { - ExtBuilder::default() - .add_members(vec![(100, 100), (200, 200)]) - .build_and_execute(|| { - // Given - assert_ok!(Pools::fully_unbond(Origin::signed(100), 100)); - - assert_eq!( - pool_events_since_last_call(), - vec![ - Event::Created { depositor: 10, pool_id: 1 }, - Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, - Event::Bonded { member: 200, pool_id: 1, bonded: 200, joined: true }, - Event::Unbonded { member: 100, pool_id: 1, amount: 100 } - ] - ); - - let mut current_era = 1; - CurrentEra::set(current_era); - - assert_ok!(Pools::fully_unbond(Origin::signed(200), 200)); - - assert_eq!( - pool_events_since_last_call(), - vec![Event::Unbonded { member: 200, pool_id: 1, amount: 200 }] - ); - - unsafe_set_state(1, PoolState::Destroying).unwrap(); - assert_ok!(Pools::fully_unbond(Origin::signed(10), 10)); - - assert_eq!( - pool_events_since_last_call(), - vec![Event::Unbonded { member: 10, pool_id: 1, amount: 10 }] - ); - - assert_eq!( - SubPoolsStorage::::get(1).unwrap(), - SubPools { - no_era: Default::default(), - with_era: unbonding_pools_with_era! { - 0 + 3 => UnbondPool { points: 100, balance: 100}, - 1 + 3 => UnbondPool { points: 200 + 10, balance: 200 + 10 } - } - } - ); - - // Skip ahead eras to where its valid for the members to withdraw - current_era += StakingMock::bonding_duration(); - CurrentEra::set(current_era); - - // Cannot withdraw the depositor if their is a member in another `with_era` pool. - assert_noop!( - Pools::withdraw_unbonded(Origin::signed(420), 10, 0), - Error::::NotOnlyPoolMember - ); - - // Given - assert_ok!(Pools::withdraw_unbonded(Origin::signed(420), 100, 0)); - - assert_eq!( - pool_events_since_last_call(), - vec![ - Event::Withdrawn { member: 100, pool_id: 1, amount: 100 }, - Event::MemberRemoved { pool_id: 1, member: 100 } - ] - ); - - assert_eq!( - SubPoolsStorage::::get(1).unwrap(), - SubPools { - no_era: Default::default(), - with_era: unbonding_pools_with_era! { - // Note that era 0+3 unbond pool is destroyed because points went to 0 - 1 + 3 => UnbondPool { points: 200 + 10, balance: 200 + 10 } - } - } - ); - - // Cannot withdraw the depositor if their is a member in another `with_era` pool. - assert_noop!( - Pools::withdraw_unbonded(Origin::signed(420), 10, 0), - Error::::NotOnlyPoolMember - ); - - // Given - assert_ok!(Pools::withdraw_unbonded(Origin::signed(420), 200, 0)); - - assert_eq!( - pool_events_since_last_call(), - vec![ - Event::Withdrawn { member: 200, pool_id: 1, amount: 200 }, - Event::MemberRemoved { pool_id: 1, member: 200 } - ] - ); - - assert_eq!( - SubPoolsStorage::::get(1).unwrap(), - SubPools { - no_era: Default::default(), - with_era: unbonding_pools_with_era! { - 1 + 3 => UnbondPool { points: 10, balance: 10 } - } - } - ); - - // The depositor can withdraw - assert_ok!(Pools::withdraw_unbonded(Origin::signed(420), 10, 0)); - - assert_eq!( - pool_events_since_last_call(), - vec![ - Event::Withdrawn { member: 10, pool_id: 1, amount: 10 }, - Event::MemberRemoved { pool_id: 1, member: 10 }, - Event::Destroyed { pool_id: 1 } - ] - ); - - assert!(!PoolMembers::::contains_key(10)); - assert_eq!(Balances::free_balance(10), 10 + 10); - // Pools are removed from storage because the depositor left - assert!(!SubPoolsStorage::::contains_key(1)); - assert!(!RewardPools::::contains_key(1)); - assert!(!BondedPools::::contains_key(1)); - }); - } - - #[test] - fn withdraw_unbonded_depositor_no_era_pool() { - ExtBuilder::default().add_members(vec![(100, 100)]).build_and_execute(|| { - // Given - assert_ok!(Pools::fully_unbond(Origin::signed(100), 100)); - unsafe_set_state(1, PoolState::Destroying).unwrap(); - assert_ok!(Pools::fully_unbond(Origin::signed(10), 10)); - // Skip ahead to an era where the `with_era` pools can get merged into the `no_era` - // pool. - let current_era = TotalUnbondingPools::::get(); - CurrentEra::set(current_era); - - // Simulate some other withdraw that caused the pool to merge - let sub_pools = - SubPoolsStorage::::get(1).unwrap().maybe_merge_pools(current_era + 3); - SubPoolsStorage::::insert(1, sub_pools); - assert_eq!( - SubPoolsStorage::::get(1).unwrap(), - SubPools { - no_era: UnbondPool { points: 100 + 10, balance: 100 + 10 }, - with_era: Default::default(), - } - ); - - // Cannot withdraw depositor with another member in the `no_era` pool - assert_noop!( - Pools::withdraw_unbonded(Origin::signed(420), 10, 0), - Error::::NotOnlyPoolMember - ); - - // Given - assert_ok!(Pools::withdraw_unbonded(Origin::signed(420), 100, 0)); - assert_eq!( - SubPoolsStorage::::get(1).unwrap(), - SubPools { - no_era: UnbondPool { points: 10, balance: 10 }, - with_era: Default::default(), - } - ); - - // The depositor can withdraw - assert_ok!(Pools::withdraw_unbonded(Origin::signed(420), 10, 0)); - assert!(!PoolMembers::::contains_key(10)); - assert_eq!(Balances::free_balance(10), 10 + 10); - // Pools are removed from storage because the depositor left - assert!(!SubPoolsStorage::::contains_key(1)); - assert!(!RewardPools::::contains_key(1)); - assert!(!BondedPools::::contains_key(1)); - - assert_eq!( - pool_events_since_last_call(), - vec![ - Event::Created { depositor: 10, pool_id: 1 }, - Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, - Event::Unbonded { member: 100, pool_id: 1, amount: 100 }, - Event::Unbonded { member: 10, pool_id: 1, amount: 10 }, - Event::Withdrawn { member: 100, pool_id: 1, amount: 100 }, - Event::MemberRemoved { pool_id: 1, member: 100 }, - Event::Withdrawn { member: 10, pool_id: 1, amount: 10 }, - Event::MemberRemoved { pool_id: 1, member: 10 }, - Event::Destroyed { pool_id: 1 } - ] - ); - }); - } - #[test] fn partial_withdraw_unbonded_depositor() { ExtBuilder::default().ed(1).build_and_execute(|| { @@ -2874,8 +2779,8 @@ mod withdraw_unbonded { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::Unbonded { member: 10, pool_id: 1, amount: 6 }, - Event::Unbonded { member: 10, pool_id: 1, amount: 1 } + Event::Unbonded { member: 10, pool_id: 1, points: 6, balance: 6 }, + Event::Unbonded { member: 10, pool_id: 1, points: 1, balance: 1 } ] ); @@ -2906,7 +2811,7 @@ mod withdraw_unbonded { ); assert_eq!( pool_events_since_last_call(), - vec![Event::Withdrawn { member: 10, pool_id: 1, amount: 6 }] + vec![Event::Withdrawn { member: 10, pool_id: 1, points: 6, balance: 6 }] ); // when @@ -2921,7 +2826,7 @@ mod withdraw_unbonded { assert_eq!(SubPoolsStorage::::get(1).unwrap(), Default::default()); assert_eq!( pool_events_since_last_call(), - vec![Event::Withdrawn { member: 10, pool_id: 1, amount: 1 },] + vec![Event::Withdrawn { member: 10, pool_id: 1, points: 1, balance: 1 },] ); // when repeating: @@ -2961,8 +2866,8 @@ mod withdraw_unbonded { Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 11, pool_id: 1, bonded: 10, joined: true }, - Event::Unbonded { member: 11, pool_id: 1, amount: 6 }, - Event::Unbonded { member: 11, pool_id: 1, amount: 1 } + Event::Unbonded { member: 11, pool_id: 1, points: 6, balance: 6 }, + Event::Unbonded { member: 11, pool_id: 1, points: 1, balance: 1 } ] ); @@ -2993,7 +2898,7 @@ mod withdraw_unbonded { ); assert_eq!( pool_events_since_last_call(), - vec![Event::Withdrawn { member: 11, pool_id: 1, amount: 6 }] + vec![Event::Withdrawn { member: 11, pool_id: 1, points: 6, balance: 6 }] ); // when @@ -3008,7 +2913,7 @@ mod withdraw_unbonded { assert_eq!(SubPoolsStorage::::get(1).unwrap(), Default::default()); assert_eq!( pool_events_since_last_call(), - vec![Event::Withdrawn { member: 11, pool_id: 1, amount: 1 }] + vec![Event::Withdrawn { member: 11, pool_id: 1, points: 1, balance: 1 }] ); // when repeating: @@ -3051,9 +2956,9 @@ mod withdraw_unbonded { Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, - Event::Unbonded { member: 100, pool_id: 1, amount: 75 }, - Event::Unbonded { member: 100, pool_id: 1, amount: 25 }, - Event::Withdrawn { member: 100, pool_id: 1, amount: 75 }, + Event::Unbonded { member: 100, pool_id: 1, points: 75, balance: 75 }, + Event::Unbonded { member: 100, pool_id: 1, points: 25, balance: 25 }, + Event::Withdrawn { member: 100, pool_id: 1, points: 75, balance: 75 }, ] ); assert_eq!( @@ -3067,7 +2972,7 @@ mod withdraw_unbonded { assert_eq!( pool_events_since_last_call(), vec![ - Event::Withdrawn { member: 100, pool_id: 1, amount: 25 }, + Event::Withdrawn { member: 100, pool_id: 1, points: 25, balance: 25 }, Event::MemberRemoved { pool_id: 1, member: 100 } ] ); @@ -3102,9 +3007,9 @@ mod withdraw_unbonded { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::Unbonded { member: 10, pool_id: 1, amount: 7 }, - Event::Unbonded { member: 10, pool_id: 1, amount: 3 }, - Event::Withdrawn { member: 10, pool_id: 1, amount: 7 } + Event::Unbonded { member: 10, pool_id: 1, points: 7, balance: 7 }, + Event::Unbonded { member: 10, pool_id: 1, points: 3, balance: 3 }, + Event::Withdrawn { member: 10, pool_id: 1, points: 7, balance: 7 } ] ); assert_eq!( @@ -3118,7 +3023,7 @@ mod withdraw_unbonded { assert_eq!( pool_events_since_last_call(), vec![ - Event::Withdrawn { member: 10, pool_id: 1, amount: 3 }, + Event::Withdrawn { member: 10, pool_id: 1, points: 3, balance: 3 }, Event::MemberRemoved { pool_id: 1, member: 10 }, // the pool is also destroyed now. Event::Destroyed { pool_id: 1 }, @@ -3288,11 +3193,11 @@ mod nominate { // Root can nominate assert_ok!(Pools::nominate(Origin::signed(900), 1, vec![21])); - assert_eq!(Nominations::get(), vec![21]); + assert_eq!(Nominations::get().unwrap(), vec![21]); // Nominator can nominate assert_ok!(Pools::nominate(Origin::signed(901), 1, vec![31])); - assert_eq!(Nominations::get(), vec![31]); + assert_eq!(Nominations::get().unwrap(), vec![31]); // Can't nominate for a pool that doesn't exist assert_noop!( diff --git a/frame/nomination-pools/src/weights.rs b/frame/nomination-pools/src/weights.rs index ca89f982c60e3..8e3facfc5ec26 100644 --- a/frame/nomination-pools/src/weights.rs +++ b/frame/nomination-pools/src/weights.rs @@ -18,11 +18,12 @@ //! Autogenerated weights for pallet_nomination_pools //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-06-10, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// ./target/production/substrate +// target/production/substrate // benchmark // pallet // --chain=dev @@ -32,8 +33,9 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --template=./.maintain/frame-weight-template.hbs +// --heap-pages=4096 // --output=./frame/nomination-pools/src/weights.rs +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -58,6 +60,7 @@ pub trait WeightInfo { fn set_metadata(n: u32, ) -> Weight; fn set_configs() -> Weight; fn update_roles() -> Weight; + fn chill() -> Weight; } /// Weights for pallet_nomination_pools using the Substrate node and recommended hardware. @@ -77,7 +80,7 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn join() -> Weight { - (129_124_000 as Weight) + (124_508_000 as Weight) .saturating_add(T::DbWeight::get().reads(17 as Weight)) .saturating_add(T::DbWeight::get().writes(11 as Weight)) } @@ -91,7 +94,7 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn bond_extra_transfer() -> Weight { - (118_193_000 as Weight) + (115_185_000 as Weight) .saturating_add(T::DbWeight::get().reads(13 as Weight)) .saturating_add(T::DbWeight::get().writes(12 as Weight)) } @@ -102,19 +105,19 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking Ledger (r:1 w:1) // Storage: Staking Bonded (r:1 w:0) // Storage: Balances Locks (r:1 w:1) - // Storage: BagsList ListNodes (r:2 w:2) + // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn bond_extra_reward() -> Weight { - (132_390_000 as Weight) - .saturating_add(T::DbWeight::get().reads(13 as Weight)) - .saturating_add(T::DbWeight::get().writes(12 as Weight)) + (132_723_000 as Weight) + .saturating_add(T::DbWeight::get().reads(14 as Weight)) + .saturating_add(T::DbWeight::get().writes(13 as Weight)) } // Storage: NominationPools PoolMembers (r:1 w:1) // Storage: NominationPools BondedPools (r:1 w:1) // Storage: NominationPools RewardPools (r:1 w:1) // Storage: System Account (r:1 w:1) fn claim_payout() -> Weight { - (54_743_000 as Weight) + (52_498_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -133,7 +136,7 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools SubPoolsStorage (r:1 w:1) // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) fn unbond() -> Weight { - (124_684_000 as Weight) + (121_645_000 as Weight) .saturating_add(T::DbWeight::get().reads(18 as Weight)) .saturating_add(T::DbWeight::get().writes(13 as Weight)) } @@ -141,10 +144,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking Ledger (r:1 w:1) // Storage: Staking CurrentEra (r:1 w:0) // Storage: Balances Locks (r:1 w:1) + /// The range of component `s` is `[0, 100]`. fn pool_withdraw_unbonded(s: u32, ) -> Weight { - (44_259_000 as Weight) + (43_320_000 as Weight) // Standard Error: 0 - .saturating_add((51_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((49_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -156,10 +160,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) // Storage: NominationPools CounterForPoolMembers (r:1 w:1) + /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_update(s: u32, ) -> Weight { - (84_854_000 as Weight) - // Standard Error: 0 - .saturating_add((56_000 as Weight).saturating_mul(s as Weight)) + (83_195_000 as Weight) + // Standard Error: 5_000 + .saturating_add((57_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(8 as Weight)) .saturating_add(T::DbWeight::get().writes(7 as Weight)) } @@ -182,8 +187,9 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) // Storage: NominationPools CounterForBondedPools (r:1 w:1) // Storage: Staking Payee (r:0 w:1) + /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_kill(_s: u32, ) -> Weight { - (146_992_000 as Weight) + (143_495_000 as Weight) .saturating_add(T::DbWeight::get().reads(19 as Weight)) .saturating_add(T::DbWeight::get().writes(16 as Weight)) } @@ -210,7 +216,7 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn create() -> Weight { - (138_099_000 as Weight) + (127_998_000 as Weight) .saturating_add(T::DbWeight::get().reads(22 as Weight)) .saturating_add(T::DbWeight::get().writes(15 as Weight)) } @@ -226,10 +232,11 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList ListBags (r:1 w:1) // Storage: BagsList CounterForListNodes (r:1 w:1) // Storage: Staking CounterForNominators (r:1 w:1) + /// The range of component `n` is `[1, 16]`. fn nominate(n: u32, ) -> Weight { - (50_964_000 as Weight) - // Standard Error: 11_000 - .saturating_add((2_333_000 as Weight).saturating_mul(n as Weight)) + (49_929_000 as Weight) + // Standard Error: 16_000 + .saturating_add((2_319_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(12 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) .saturating_add(T::DbWeight::get().writes(5 as Weight)) @@ -237,15 +244,16 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Ledger (r:1 w:0) fn set_state() -> Weight { - (27_196_000 as Weight) + (27_399_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: NominationPools BondedPools (r:1 w:0) // Storage: NominationPools Metadata (r:1 w:1) // Storage: NominationPools CounterForMetadata (r:1 w:1) + /// The range of component `n` is `[1, 256]`. fn set_metadata(n: u32, ) -> Weight { - (15_056_000 as Weight) + (14_813_000 as Weight) // Standard Error: 0 .saturating_add((1_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) @@ -257,15 +265,28 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools MinCreateBond (r:0 w:1) // Storage: NominationPools MaxPools (r:0 w:1) fn set_configs() -> Weight { - (6_294_000 as Weight) + (6_115_000 as Weight) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } // Storage: NominationPools BondedPools (r:1 w:1) fn update_roles() -> Weight { - (22_444_000 as Weight) + (22_546_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } + // Storage: NominationPools BondedPools (r:1 w:0) + // Storage: Staking Ledger (r:1 w:0) + // Storage: Staking Validators (r:1 w:0) + // Storage: Staking Nominators (r:1 w:1) + // Storage: Staking CounterForNominators (r:1 w:1) + // Storage: BagsList ListNodes (r:1 w:1) + // Storage: BagsList ListBags (r:1 w:1) + // Storage: BagsList CounterForListNodes (r:1 w:1) + fn chill() -> Weight { + (48_243_000 as Weight) + .saturating_add(T::DbWeight::get().reads(8 as Weight)) + .saturating_add(T::DbWeight::get().writes(5 as Weight)) + } } // For backwards compatibility and tests @@ -284,7 +305,7 @@ impl WeightInfo for () { // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn join() -> Weight { - (129_124_000 as Weight) + (124_508_000 as Weight) .saturating_add(RocksDbWeight::get().reads(17 as Weight)) .saturating_add(RocksDbWeight::get().writes(11 as Weight)) } @@ -298,7 +319,7 @@ impl WeightInfo for () { // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn bond_extra_transfer() -> Weight { - (118_193_000 as Weight) + (115_185_000 as Weight) .saturating_add(RocksDbWeight::get().reads(13 as Weight)) .saturating_add(RocksDbWeight::get().writes(12 as Weight)) } @@ -309,19 +330,19 @@ impl WeightInfo for () { // Storage: Staking Ledger (r:1 w:1) // Storage: Staking Bonded (r:1 w:0) // Storage: Balances Locks (r:1 w:1) - // Storage: BagsList ListNodes (r:2 w:2) + // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn bond_extra_reward() -> Weight { - (132_390_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(13 as Weight)) - .saturating_add(RocksDbWeight::get().writes(12 as Weight)) + (132_723_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(14 as Weight)) + .saturating_add(RocksDbWeight::get().writes(13 as Weight)) } // Storage: NominationPools PoolMembers (r:1 w:1) // Storage: NominationPools BondedPools (r:1 w:1) // Storage: NominationPools RewardPools (r:1 w:1) // Storage: System Account (r:1 w:1) fn claim_payout() -> Weight { - (54_743_000 as Weight) + (52_498_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } @@ -340,7 +361,7 @@ impl WeightInfo for () { // Storage: NominationPools SubPoolsStorage (r:1 w:1) // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) fn unbond() -> Weight { - (124_684_000 as Weight) + (121_645_000 as Weight) .saturating_add(RocksDbWeight::get().reads(18 as Weight)) .saturating_add(RocksDbWeight::get().writes(13 as Weight)) } @@ -348,10 +369,11 @@ impl WeightInfo for () { // Storage: Staking Ledger (r:1 w:1) // Storage: Staking CurrentEra (r:1 w:0) // Storage: Balances Locks (r:1 w:1) + /// The range of component `s` is `[0, 100]`. fn pool_withdraw_unbonded(s: u32, ) -> Weight { - (44_259_000 as Weight) + (43_320_000 as Weight) // Standard Error: 0 - .saturating_add((51_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((49_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -363,10 +385,11 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) // Storage: System Account (r:1 w:1) // Storage: NominationPools CounterForPoolMembers (r:1 w:1) + /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_update(s: u32, ) -> Weight { - (84_854_000 as Weight) - // Standard Error: 0 - .saturating_add((56_000 as Weight).saturating_mul(s as Weight)) + (83_195_000 as Weight) + // Standard Error: 5_000 + .saturating_add((57_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(8 as Weight)) .saturating_add(RocksDbWeight::get().writes(7 as Weight)) } @@ -389,8 +412,9 @@ impl WeightInfo for () { // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) // Storage: NominationPools CounterForBondedPools (r:1 w:1) // Storage: Staking Payee (r:0 w:1) + /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_kill(_s: u32, ) -> Weight { - (146_992_000 as Weight) + (143_495_000 as Weight) .saturating_add(RocksDbWeight::get().reads(19 as Weight)) .saturating_add(RocksDbWeight::get().writes(16 as Weight)) } @@ -417,7 +441,7 @@ impl WeightInfo for () { // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn create() -> Weight { - (138_099_000 as Weight) + (127_998_000 as Weight) .saturating_add(RocksDbWeight::get().reads(22 as Weight)) .saturating_add(RocksDbWeight::get().writes(15 as Weight)) } @@ -433,10 +457,11 @@ impl WeightInfo for () { // Storage: BagsList ListBags (r:1 w:1) // Storage: BagsList CounterForListNodes (r:1 w:1) // Storage: Staking CounterForNominators (r:1 w:1) + /// The range of component `n` is `[1, 16]`. fn nominate(n: u32, ) -> Weight { - (50_964_000 as Weight) - // Standard Error: 11_000 - .saturating_add((2_333_000 as Weight).saturating_mul(n as Weight)) + (49_929_000 as Weight) + // Standard Error: 16_000 + .saturating_add((2_319_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(12 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) @@ -444,15 +469,16 @@ impl WeightInfo for () { // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Ledger (r:1 w:0) fn set_state() -> Weight { - (27_196_000 as Weight) + (27_399_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: NominationPools BondedPools (r:1 w:0) // Storage: NominationPools Metadata (r:1 w:1) // Storage: NominationPools CounterForMetadata (r:1 w:1) + /// The range of component `n` is `[1, 256]`. fn set_metadata(n: u32, ) -> Weight { - (15_056_000 as Weight) + (14_813_000 as Weight) // Standard Error: 0 .saturating_add((1_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) @@ -464,13 +490,26 @@ impl WeightInfo for () { // Storage: NominationPools MinCreateBond (r:0 w:1) // Storage: NominationPools MaxPools (r:0 w:1) fn set_configs() -> Weight { - (6_294_000 as Weight) + (6_115_000 as Weight) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } // Storage: NominationPools BondedPools (r:1 w:1) fn update_roles() -> Weight { - (22_444_000 as Weight) + (22_546_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } + // Storage: NominationPools BondedPools (r:1 w:0) + // Storage: Staking Ledger (r:1 w:0) + // Storage: Staking Validators (r:1 w:0) + // Storage: Staking Nominators (r:1 w:1) + // Storage: Staking CounterForNominators (r:1 w:1) + // Storage: BagsList ListNodes (r:1 w:1) + // Storage: BagsList ListBags (r:1 w:1) + // Storage: BagsList CounterForListNodes (r:1 w:1) + fn chill() -> Weight { + (48_243_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(8 as Weight)) + .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + } } diff --git a/frame/nomination-pools/test-staking/Cargo.toml b/frame/nomination-pools/test-staking/Cargo.toml new file mode 100644 index 0000000000000..ad36e89e0d68a --- /dev/null +++ b/frame/nomination-pools/test-staking/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "pallet-nomination-pools-test-staking" +version = "1.0.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME nomination pools pallet tests with the staking pallet" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dev-dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } +scale-info = { version = "2.0.1", features = ["derive"] } + +sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } +sp-io = { version = "6.0.0", path = "../../../primitives/io" } +sp-std = { version = "4.0.0", path = "../../../primitives/std" } +sp-staking = { version = "4.0.0-dev", path = "../../../primitives/staking" } +sp-core = { version = "6.0.0", path = "../../../primitives/core" } + +frame-system = { version = "4.0.0-dev", path = "../../system" } +frame-support = { version = "4.0.0-dev", path = "../../support" } +frame-election-provider-support = { version = "4.0.0-dev", path = "../../election-provider-support" } + +pallet-timestamp = { version = "4.0.0-dev", path = "../../timestamp" } +pallet-balances = { version = "4.0.0-dev", path = "../../balances" } +pallet-staking = { version = "4.0.0-dev", path = "../../staking" } +pallet-bags-list = { version = "4.0.0-dev", path = "../../bags-list" } +pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../../staking/reward-curve" } +pallet-nomination-pools = { version = "1.0.0-dev", path = ".." } + +sp-tracing = { version = "5.0.0", path = "../../../primitives/tracing" } +log = { version = "0.4.0" } diff --git a/frame/nomination-pools/test-staking/src/lib.rs b/frame/nomination-pools/test-staking/src/lib.rs new file mode 100644 index 0000000000000..2e40e8c6d917d --- /dev/null +++ b/frame/nomination-pools/test-staking/src/lib.rs @@ -0,0 +1,373 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg(test)] + +mod mock; + +use frame_support::{assert_noop, assert_ok, bounded_btree_map, traits::Currency}; +use mock::*; +use pallet_nomination_pools::{ + Error as PoolsError, Event as PoolsEvent, LastPoolId, PoolMember, PoolMembers, PoolState, +}; +use pallet_staking::{CurrentEra, Event as StakingEvent, Payee, RewardDestination}; + +#[test] +fn pool_lifecycle_e2e() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::minimum_balance(), 5); + assert_eq!(Staking::current_era(), None); + + // create the pool, we know this has id 1. + assert_ok!(Pools::create(Origin::signed(10), 50, 10, 10, 10)); + assert_eq!(LastPoolId::::get(), 1); + + // have the pool nominate. + assert_ok!(Pools::nominate(Origin::signed(10), 1, vec![1, 2, 3])); + + assert_eq!(staking_events_since_last_call(), vec![StakingEvent::Bonded(POOL1_BONDED, 50),]); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Created { depositor: 10, pool_id: 1 }, + PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 50, joined: true }, + ] + ); + + // have two members join + assert_ok!(Pools::join(Origin::signed(20), 10, 1)); + assert_ok!(Pools::join(Origin::signed(21), 10, 1)); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Bonded(POOL1_BONDED, 10), StakingEvent::Bonded(POOL1_BONDED, 10),] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true }, + PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: 10, joined: true }, + ] + ); + + // pool goes into destroying + assert_ok!(Pools::set_state(Origin::signed(10), 1, PoolState::Destroying)); + + // depositor cannot unbond yet. + assert_noop!( + Pools::unbond(Origin::signed(10), 10, 50), + PoolsError::::NotOnlyPoolMember, + ); + + // now the members want to unbond. + assert_ok!(Pools::unbond(Origin::signed(20), 20, 10)); + assert_ok!(Pools::unbond(Origin::signed(21), 21, 10)); + + assert_eq!(PoolMembers::::get(20).unwrap().unbonding_eras.len(), 1); + assert_eq!(PoolMembers::::get(20).unwrap().points, 0); + assert_eq!(PoolMembers::::get(21).unwrap().unbonding_eras.len(), 1); + assert_eq!(PoolMembers::::get(21).unwrap().points, 0); + + assert_eq!( + staking_events_since_last_call(), + vec![ + StakingEvent::Unbonded(POOL1_BONDED, 10), + StakingEvent::Unbonded(POOL1_BONDED, 10), + ] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::StateChanged { pool_id: 1, new_state: PoolState::Destroying }, + PoolsEvent::Unbonded { member: 20, pool_id: 1, points: 10, balance: 10 }, + PoolsEvent::Unbonded { member: 21, pool_id: 1, points: 10, balance: 10 }, + ] + ); + + // depositor cannot still unbond + assert_noop!( + Pools::unbond(Origin::signed(10), 10, 50), + PoolsError::::NotOnlyPoolMember, + ); + + for e in 1..BondingDuration::get() { + CurrentEra::::set(Some(e)); + assert_noop!( + Pools::withdraw_unbonded(Origin::signed(20), 20, 0), + PoolsError::::CannotWithdrawAny + ); + } + + // members are now unlocked. + CurrentEra::::set(Some(BondingDuration::get())); + + // depositor cannot still unbond + assert_noop!( + Pools::unbond(Origin::signed(10), 10, 50), + PoolsError::::NotOnlyPoolMember, + ); + + // but members can now withdraw. + assert_ok!(Pools::withdraw_unbonded(Origin::signed(20), 20, 0)); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(21), 21, 0)); + assert!(PoolMembers::::get(20).is_none()); + assert!(PoolMembers::::get(21).is_none()); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Withdrawn(POOL1_BONDED, 20),] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Withdrawn { member: 20, pool_id: 1, points: 10, balance: 10 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 20 }, + PoolsEvent::Withdrawn { member: 21, pool_id: 1, points: 10, balance: 10 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 21 }, + ] + ); + + // as soon as all members have left, the depositor can try to unbond, but since the + // min-nominator intention is set, they must chill first. + assert_noop!( + Pools::unbond(Origin::signed(10), 10, 50), + pallet_staking::Error::::InsufficientBond + ); + + assert_ok!(Pools::chill(Origin::signed(10), 1)); + assert_ok!(Pools::unbond(Origin::signed(10), 10, 50)); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Chilled(POOL1_BONDED), StakingEvent::Unbonded(POOL1_BONDED, 50),] + ); + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::Unbonded { member: 10, pool_id: 1, points: 50, balance: 50 }] + ); + + // waiting another bonding duration: + CurrentEra::::set(Some(BondingDuration::get() * 2)); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 1)); + + // pools is fully destroyed now. + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Withdrawn(POOL1_BONDED, 50),] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Withdrawn { member: 10, pool_id: 1, points: 50, balance: 50 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 10 }, + PoolsEvent::Destroyed { pool_id: 1 } + ] + ); + }) +} + +#[test] +fn pool_slash_e2e() { + new_test_ext().execute_with(|| { + ExistentialDeposit::set(1); + assert_eq!(Balances::minimum_balance(), 1); + assert_eq!(Staking::current_era(), None); + + // create the pool, we know this has id 1. + assert_ok!(Pools::create(Origin::signed(10), 40, 10, 10, 10)); + assert_eq!(LastPoolId::::get(), 1); + + assert_eq!(staking_events_since_last_call(), vec![StakingEvent::Bonded(POOL1_BONDED, 40)]); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Created { depositor: 10, pool_id: 1 }, + PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true }, + ] + ); + + assert_eq!(Payee::::get(POOL1_BONDED), RewardDestination::Account(POOL1_REWARD)); + + // have two members join + assert_ok!(Pools::join(Origin::signed(20), 20, 1)); + assert_ok!(Pools::join(Origin::signed(21), 20, 1)); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Bonded(POOL1_BONDED, 20), StakingEvent::Bonded(POOL1_BONDED, 20)] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, + PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: 20, joined: true }, + ] + ); + + // now let's progress a bit. + CurrentEra::::set(Some(1)); + + // 20 / 80 of the total funds are unlocked, and safe from any further slash. + assert_ok!(Pools::unbond(Origin::signed(10), 10, 10)); + assert_ok!(Pools::unbond(Origin::signed(20), 20, 10)); + + assert_eq!( + staking_events_since_last_call(), + vec![ + StakingEvent::Unbonded(POOL1_BONDED, 10), + StakingEvent::Unbonded(POOL1_BONDED, 10) + ] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10 }, + PoolsEvent::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10 } + ] + ); + + CurrentEra::::set(Some(2)); + + // note: depositor cannot fully unbond at this point. + // these funds will still get slashed. + assert_ok!(Pools::unbond(Origin::signed(10), 10, 10)); + assert_ok!(Pools::unbond(Origin::signed(20), 20, 10)); + assert_ok!(Pools::unbond(Origin::signed(21), 21, 10)); + + assert_eq!( + staking_events_since_last_call(), + vec![ + StakingEvent::Unbonded(POOL1_BONDED, 10), + StakingEvent::Unbonded(POOL1_BONDED, 10), + StakingEvent::Unbonded(POOL1_BONDED, 10), + ] + ); + + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10 }, + PoolsEvent::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10 }, + PoolsEvent::Unbonded { member: 21, pool_id: 1, balance: 10, points: 10 }, + ] + ); + + // At this point, 20 are safe from slash, 30 are unlocking but vulnerable to slash, and and + // another 30 are active and vulnerable to slash. Let's slash half of them. + pallet_staking::slashing::do_slash::( + &POOL1_BONDED, + 30, + &mut Default::default(), + &mut Default::default(), + 1, // slash era 1, affects chunks at era 5 onwards. + ); + + assert_eq!(staking_events_since_last_call(), vec![StakingEvent::Slashed(POOL1_BONDED, 30)]); + assert_eq!( + pool_events_since_last_call(), + vec![ + // 30 has been slashed to 15 (15 slash) + PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 5, balance: 15 }, + // 30 has been slashed to 15 (15 slash) + PoolsEvent::PoolSlashed { pool_id: 1, balance: 15 } + ] + ); + + CurrentEra::::set(Some(3)); + assert_ok!(Pools::unbond(Origin::signed(21), 21, 10)); + + assert_eq!( + PoolMembers::::get(21).unwrap(), + PoolMember { + pool_id: 1, + points: 0, + reward_pool_total_earnings: 0, + // the 10 points unlocked just now correspond to 5 points in the unbond pool. + unbonding_eras: bounded_btree_map!(5 => 10, 6 => 5) + } + ); + assert_eq!(staking_events_since_last_call(), vec![StakingEvent::Unbonded(POOL1_BONDED, 5)]); + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::Unbonded { member: 21, pool_id: 1, balance: 5, points: 5 }] + ); + + // now we start withdrawing. we do it all at once, at era 6 where 20 and 21 are fully free. + CurrentEra::::set(Some(6)); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(20), 20, 0)); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(21), 21, 0)); + + assert_eq!( + pool_events_since_last_call(), + vec![ + // 20 had unbonded 10 safely, and 10 got slashed by half. + PoolsEvent::Withdrawn { member: 20, pool_id: 1, balance: 10 + 5, points: 20 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 20 }, + // 21 unbonded all of it after the slash + PoolsEvent::Withdrawn { member: 21, pool_id: 1, balance: 5 + 5, points: 15 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 21 } + ] + ); + assert_eq!( + staking_events_since_last_call(), + // a 10 (un-slashed) + 10/2 (slashed) balance from 10 has also been unlocked + vec![StakingEvent::Withdrawn(POOL1_BONDED, 15 + 10 + 15)] + ); + + // now, finally, we can unbond the depositor further than their current limit. + assert_ok!(Pools::set_state(Origin::signed(10), 1, PoolState::Destroying)); + assert_ok!(Pools::unbond(Origin::signed(10), 10, 20)); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Unbonded(POOL1_BONDED, 10)] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::StateChanged { pool_id: 1, new_state: PoolState::Destroying }, + PoolsEvent::Unbonded { member: 10, pool_id: 1, points: 10, balance: 10 } + ] + ); + + CurrentEra::::set(Some(9)); + assert_eq!( + PoolMembers::::get(10).unwrap(), + PoolMember { + pool_id: 1, + points: 0, + reward_pool_total_earnings: 0, + unbonding_eras: bounded_btree_map!(4 => 10, 5 => 10, 9 => 10) + } + ); + // withdraw the depositor, they should lose 12 balance in total due to slash. + assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 0)); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Withdrawn(POOL1_BONDED, 10)] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Withdrawn { member: 10, pool_id: 1, balance: 10 + 15, points: 30 }, + PoolsEvent::MemberRemoved { pool_id: 1, member: 10 }, + PoolsEvent::Destroyed { pool_id: 1 } + ] + ); + }); +} diff --git a/frame/nomination-pools/test-staking/src/mock.rs b/frame/nomination-pools/test-staking/src/mock.rs new file mode 100644 index 0000000000000..7b720c009b29b --- /dev/null +++ b/frame/nomination-pools/test-staking/src/mock.rs @@ -0,0 +1,261 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use frame_election_provider_support::VoteWeight; +use frame_support::{assert_ok, pallet_prelude::*, parameter_types, traits::ConstU64, PalletId}; +use sp_runtime::traits::{Convert, IdentityLookup}; + +type AccountId = u128; +type AccountIndex = u32; +type BlockNumber = u64; +type Balance = u128; + +pub(crate) const POOL1_BONDED: AccountId = 20318131474730217858575332831085u128; +pub(crate) const POOL1_REWARD: AccountId = 20397359637244482196168876781421u128; + +impl frame_system::Config for Runtime { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type 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 = IdentityLookup; + type Header = sp_runtime::testing::Header; + type Event = Event; + type BlockHashCount = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +impl pallet_timestamp::Config for Runtime { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = ConstU64<5>; + type WeightInfo = (); +} + +parameter_types! { + pub static ExistentialDeposit: Balance = 5; +} + +impl pallet_balances::Config for Runtime { + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type Balance = Balance; + type Event = Event; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); +} + +pallet_staking_reward_curve::build! { + const I_NPOS: sp_runtime::curve::PiecewiseLinear<'static> = curve!( + min_inflation: 0_025_000, + max_inflation: 0_100_000, + ideal_stake: 0_500_000, + falloff: 0_050_000, + max_piece_count: 40, + test_precision: 0_005_000, + ); +} + +parameter_types! { + pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS; + pub static BondingDuration: u32 = 3; +} + +impl pallet_staking::Config for Runtime { + type MaxNominations = ConstU32<16>; + type Currency = Balances; + type CurrencyBalance = Balance; + type UnixTime = pallet_timestamp::Pallet; + type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; + type RewardRemainder = (); + type Event = Event; + type Slash = (); + type Reward = (); + type SessionsPerEra = (); + type SlashDeferDuration = (); + type SlashCancelOrigin = frame_system::EnsureRoot; + type BondingDuration = BondingDuration; + type SessionInterface = (); + type EraPayout = pallet_staking::ConvertCurve; + type NextNewSession = (); + type MaxNominatorRewardedPerValidator = ConstU32<64>; + type OffendingValidatorsThreshold = (); + type ElectionProvider = + frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking)>; + type GenesisElectionProvider = Self::ElectionProvider; + type VoterList = pallet_bags_list::Pallet; + type MaxUnlockingChunks = ConstU32<32>; + type OnStakerSlash = Pools; + type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; + type WeightInfo = (); +} + +parameter_types! { + pub static BagThresholds: &'static [VoteWeight] = &[10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000]; +} + +impl pallet_bags_list::Config for Runtime { + type Event = Event; + type WeightInfo = (); + type BagThresholds = BagThresholds; + type ScoreProvider = Staking; + type Score = VoteWeight; +} + +pub struct BalanceToU256; +impl Convert for BalanceToU256 { + fn convert(n: Balance) -> sp_core::U256 { + n.into() + } +} + +pub struct U256ToBalance; +impl Convert for U256ToBalance { + fn convert(n: sp_core::U256) -> Balance { + n.try_into().unwrap() + } +} + +parameter_types! { + pub const PostUnbondingPoolsWindow: u32 = 10; + pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls"); +} + +impl pallet_nomination_pools::Config for Runtime { + type Event = Event; + type WeightInfo = (); + type Currency = Balances; + type BalanceToU256 = BalanceToU256; + type U256ToBalance = U256ToBalance; + type StakingInterface = Staking; + type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow; + type MaxMetadataLen = ConstU32<256>; + type MaxUnbonding = ConstU32<8>; + type MinPointsToBalance = ConstU32<10>; + type PalletId = PoolsPalletId; +} + +impl frame_system::offchain::SendTransactionTypes for Runtime +where + Call: From, +{ + type OverarchingCall = Call; + type Extrinsic = UncheckedExtrinsic; +} + +type Block = frame_system::mocking::MockBlock; +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; + +frame_support::construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic + { + System: frame_system::{Pallet, Call, Event}, + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Staking: pallet_staking::{Pallet, Call, Config, Storage, Event}, + BagsList: pallet_bags_list::{Pallet, Call, Storage, Event}, + Pools: pallet_nomination_pools::{Pallet, Call, Storage, Event}, + } +); + +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let _ = pallet_nomination_pools::GenesisConfig:: { + min_join_bond: 2, + min_create_bond: 2, + max_pools: Some(3), + max_members_per_pool: Some(3), + max_members: Some(3 * 3), + } + .assimilate_storage(&mut storage) + .unwrap(); + + let _ = pallet_balances::GenesisConfig:: { + balances: vec![(10, 100), (20, 100), (21, 100), (22, 100)], + } + .assimilate_storage(&mut storage) + .unwrap(); + + let mut ext = sp_io::TestExternalities::from(storage); + + ext.execute_with(|| { + // for events to be deposited. + frame_system::Pallet::::set_block_number(1); + + // set some limit for nominations. + assert_ok!(Staking::set_staking_configs( + Origin::root(), + pallet_staking::ConfigOp::Set(10), // minimum nominator bond + pallet_staking::ConfigOp::Noop, + pallet_staking::ConfigOp::Noop, + pallet_staking::ConfigOp::Noop, + pallet_staking::ConfigOp::Noop, + pallet_staking::ConfigOp::Noop, + )); + }); + + ext +} + +parameter_types! { + static ObservedEventsPools: usize = 0; + static ObservedEventsStaking: usize = 0; + static ObservedEventsBalances: usize = 0; +} + +pub(crate) fn pool_events_since_last_call() -> Vec> { + let events = System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let Event::Pools(inner) = e { Some(inner) } else { None }) + .collect::>(); + let already_seen = ObservedEventsPools::get(); + ObservedEventsPools::set(events.len()); + events.into_iter().skip(already_seen).collect() +} + +pub(crate) fn staking_events_since_last_call() -> Vec> { + let events = System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let Event::Staking(inner) = e { Some(inner) } else { None }) + .collect::>(); + let already_seen = ObservedEventsStaking::get(); + ObservedEventsStaking::set(events.len()); + events.into_iter().skip(already_seen).collect() +} diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 331095774b741..360d5b5efb58f 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -438,7 +438,7 @@ pub struct UnlockChunk { } /// The ledger of a (bonded) stash. -#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebugNoBound, TypeInfo)] #[scale_info(skip_type_params(T))] pub struct StakingLedger { /// The stash account whose balance is actually locked and at stake. @@ -607,12 +607,12 @@ impl StakingLedger { let mut slashed_unlocking = BTreeMap::<_, _>::new(); for i in slash_chunks_priority { if let Some(chunk) = self.unlocking.get_mut(i).defensive() { - slash_out_of(&mut chunk.value, &mut remaining_slash); - // write the new slashed value of this chunk to the map. - slashed_unlocking.insert(chunk.era, chunk.value); if remaining_slash.is_zero() { break } + slash_out_of(&mut chunk.value, &mut remaining_slash); + // write the new slashed value of this chunk to the map. + slashed_unlocking.insert(chunk.era, chunk.value); } else { break } diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 66265ab1f135e..7656eec80a5ff 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -1414,17 +1414,17 @@ impl StakingInterface for Pallet { Self::unbond(RawOrigin::Signed(controller).into(), value) } + fn chill(controller: Self::AccountId) -> DispatchResult { + Self::chill(RawOrigin::Signed(controller).into()) + } + fn withdraw_unbonded( controller: Self::AccountId, num_slashing_spans: u32, - ) -> Result { - Self::withdraw_unbonded(RawOrigin::Signed(controller).into(), num_slashing_spans) - .map(|post_info| { - post_info - .actual_weight - .unwrap_or(T::WeightInfo::withdraw_unbonded_kill(num_slashing_spans)) - }) - .map_err(|err_with_post_info| err_with_post_info.error) + ) -> Result { + Self::withdraw_unbonded(RawOrigin::Signed(controller.clone()).into(), num_slashing_spans) + .map(|_| !Ledger::::contains_key(&controller)) + .map_err(|with_post| with_post.error) } fn bond( @@ -1445,4 +1445,9 @@ impl StakingInterface for Pallet { let targets = targets.into_iter().map(T::Lookup::unlookup).collect::>(); Self::nominate(RawOrigin::Signed(controller).into(), targets) } + + #[cfg(feature = "runtime-benchmarks")] + fn nominations(who: Self::AccountId) -> Option> { + Nominators::::get(who).map(|n| n.targets.into_inner()) + } } diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index d0ebef27b4ef6..9a13a818f4b59 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4930,6 +4930,25 @@ fn force_apply_min_commission_works() { }); } +#[test] +fn proportional_slash_stop_slashing_if_remaining_zero() { + let c = |era, value| UnlockChunk:: { era, value }; + // Given + let mut ledger = StakingLedger:: { + stash: 123, + total: 40, + active: 20, + // we have some chunks, but they are not affected. + unlocking: bounded_vec![c(1, 10), c(2, 10)], + claimed_rewards: vec![], + }; + + assert_eq!(BondingDuration::get(), 3); + + // should not slash more than the amount requested, by accidentally slashing the first chunk. + assert_eq!(ledger.slash(18, 1, 0), 18); +} + #[test] fn proportional_ledger_slash_works() { let c = |era, value| UnlockChunk:: { era, value }; diff --git a/frame/support/src/storage/mod.rs b/frame/support/src/storage/mod.rs index 6911da630cb34..d9e50a1e1345e 100644 --- a/frame/support/src/storage/mod.rs +++ b/frame/support/src/storage/mod.rs @@ -1267,7 +1267,7 @@ pub trait StoragePrefixedMap { pub trait StorageAppend: private::Sealed {} /// Marker trait that will be implemented for types that support to decode their length in an -/// effificent way. It is expected that the length is at the beginning of the encoded object +/// efficient way. It is expected that the length is at the beginning of the encoded object /// and that the length is a `Compact`. /// /// This trait is sealed. diff --git a/frame/support/src/storage/transactional.rs b/frame/support/src/storage/transactional.rs index d1bdb30af947b..909d5909ed8bd 100644 --- a/frame/support/src/storage/transactional.rs +++ b/frame/support/src/storage/transactional.rs @@ -164,9 +164,9 @@ where /// Execute the supplied function, adding a new storage layer. /// -/// This is the same as `with_transaction`, but assuming that any function returning -/// an `Err` should rollback, and any function returning `Ok` should commit. This -/// provides a cleaner API to the developer who wants this behavior. +/// This is the same as `with_transaction`, but assuming that any function returning an `Err` should +/// rollback, and any function returning `Ok` should commit. This provides a cleaner API to the +/// developer who wants this behavior. pub fn with_storage_layer(f: F) -> Result where E: From, diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index a2a6432485be8..5a3e97b4d5274 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -108,6 +108,9 @@ pub trait StakingInterface { validators: sp_std::vec::Vec, ) -> DispatchResult; + /// Chill `stash`. + fn chill(controller: Self::AccountId) -> DispatchResult; + /// Bond some extra amount in the _Stash_'s free balance against the active bonded balance of /// the account. The amount extra actually bonded will never be more than the _Stash_'s free /// balance. @@ -125,8 +128,14 @@ pub trait StakingInterface { fn unbond(stash: Self::AccountId, value: Self::Balance) -> DispatchResult; /// Unlock any funds schedule to unlock before or at the current era. + /// + /// Returns whether the stash was killed because of this withdraw or not. fn withdraw_unbonded( stash: Self::AccountId, num_slashing_spans: u32, - ) -> Result; + ) -> Result; + + /// Get the nominations of a stash, if they are a nominator, `None` otherwise. + #[cfg(feature = "runtime-benchmarks")] + fn nominations(who: Self::AccountId) -> Option>; } From 871d1f0f3c7d55301f6c2c0dc56ef966e329c663 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Tue, 14 Jun 2022 04:39:33 +0200 Subject: [PATCH 333/484] Properly implement Debug on bounded types (#11659) --- primitives/runtime/src/bounded/bounded_btree_map.rs | 7 +++---- primitives/runtime/src/bounded/bounded_btree_set.rs | 7 +++---- primitives/runtime/src/bounded/bounded_vec.rs | 2 +- primitives/runtime/src/bounded/weak_bounded_vec.rs | 7 +++---- 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/primitives/runtime/src/bounded/bounded_btree_map.rs b/primitives/runtime/src/bounded/bounded_btree_map.rs index b9abf77f175c6..aefd168632a1e 100644 --- a/primitives/runtime/src/bounded/bounded_btree_map.rs +++ b/primitives/runtime/src/bounded/bounded_btree_map.rs @@ -182,13 +182,12 @@ where } } -#[cfg(feature = "std")] -impl std::fmt::Debug for BoundedBTreeMap +impl sp_std::fmt::Debug for BoundedBTreeMap where - BTreeMap: std::fmt::Debug, + BTreeMap: sp_std::fmt::Debug, S: Get, { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { f.debug_tuple("BoundedBTreeMap").field(&self.0).field(&Self::bound()).finish() } } diff --git a/primitives/runtime/src/bounded/bounded_btree_set.rs b/primitives/runtime/src/bounded/bounded_btree_set.rs index 0f8cc769e47e3..c19d176f11bef 100644 --- a/primitives/runtime/src/bounded/bounded_btree_set.rs +++ b/primitives/runtime/src/bounded/bounded_btree_set.rs @@ -159,13 +159,12 @@ where } } -#[cfg(feature = "std")] -impl std::fmt::Debug for BoundedBTreeSet +impl sp_std::fmt::Debug for BoundedBTreeSet where - BTreeSet: std::fmt::Debug, + BTreeSet: sp_std::fmt::Debug, S: Get, { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { f.debug_tuple("BoundedBTreeSet").field(&self.0).field(&Self::bound()).finish() } } diff --git a/primitives/runtime/src/bounded/bounded_vec.rs b/primitives/runtime/src/bounded/bounded_vec.rs index 84acddea47cab..555a34819a423 100644 --- a/primitives/runtime/src/bounded/bounded_vec.rs +++ b/primitives/runtime/src/bounded/bounded_vec.rs @@ -598,7 +598,7 @@ impl Default for BoundedVec { impl sp_std::fmt::Debug for BoundedVec where - T: sp_std::fmt::Debug, + Vec: sp_std::fmt::Debug, S: Get, { fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { diff --git a/primitives/runtime/src/bounded/weak_bounded_vec.rs b/primitives/runtime/src/bounded/weak_bounded_vec.rs index 82bae4118f13e..08460f7096357 100644 --- a/primitives/runtime/src/bounded/weak_bounded_vec.rs +++ b/primitives/runtime/src/bounded/weak_bounded_vec.rs @@ -168,13 +168,12 @@ impl Default for WeakBoundedVec { } } -#[cfg(feature = "std")] -impl std::fmt::Debug for WeakBoundedVec +impl sp_std::fmt::Debug for WeakBoundedVec where - T: std::fmt::Debug, + Vec: sp_std::fmt::Debug, S: Get, { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { f.debug_tuple("WeakBoundedVec").field(&self.0).field(&Self::bound()).finish() } } From 3c3e0a418aa653aa073b445260fb8a20dbeba5ad Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 14 Jun 2022 10:17:58 +0200 Subject: [PATCH 334/484] remove flaky rpc subscription tests (#11653) --- client/beefy/rpc/src/lib.rs | 28 -------------------------- client/finality-grandpa/rpc/src/lib.rs | 27 ------------------------- 2 files changed, 55 deletions(-) diff --git a/client/beefy/rpc/src/lib.rs b/client/beefy/rpc/src/lib.rs index c248d33cb6c23..2c3ffda056c26 100644 --- a/client/beefy/rpc/src/lib.rs +++ b/client/beefy/rpc/src/lib.rs @@ -241,34 +241,6 @@ mod tests { ); } - #[tokio::test] - async fn subscribe_and_unsubscribe_to_justifications() { - let (rpc, _) = setup_io_handler(); - - // Subscribe call. - let sub = rpc - .subscribe("beefy_subscribeJustifications", EmptyParams::new()) - .await - .unwrap(); - - let ser_id = serde_json::to_string(sub.subscription_id()).unwrap(); - - // Unsubscribe - let unsub_req = format!( - "{{\"jsonrpc\":\"2.0\",\"method\":\"beefy_unsubscribeJustifications\",\"params\":[{}],\"id\":1}}", - ser_id - ); - let (response, _) = rpc.raw_json_request(&unsub_req).await.unwrap(); - - assert_eq!(response, r#"{"jsonrpc":"2.0","result":true,"id":1}"#); - - // Unsubscribe again and fail - let (response, _) = rpc.raw_json_request(&unsub_req).await.unwrap(); - let expected = r#"{"jsonrpc":"2.0","result":false,"id":1}"#; - - assert_eq!(response, expected); - } - #[tokio::test] async fn subscribe_and_unsubscribe_with_wrong_id() { let (rpc, _) = setup_io_handler(); diff --git a/client/finality-grandpa/rpc/src/lib.rs b/client/finality-grandpa/rpc/src/lib.rs index bdb86c125e20a..1cf23a18c794b 100644 --- a/client/finality-grandpa/rpc/src/lib.rs +++ b/client/finality-grandpa/rpc/src/lib.rs @@ -310,33 +310,6 @@ mod tests { assert_eq!(expected_response, result); } - #[tokio::test] - async fn subscribe_and_unsubscribe_to_justifications() { - let (rpc, _) = setup_io_handler(TestVoterState); - // Subscribe call. - let sub = rpc - .subscribe("grandpa_subscribeJustifications", EmptyParams::new()) - .await - .unwrap(); - - let ser_id = serde_json::to_string(sub.subscription_id()).unwrap(); - - // Unsubscribe - let unsub_req = format!( - "{{\"jsonrpc\":\"2.0\",\"method\":\"grandpa_unsubscribeJustifications\",\"params\":[{}],\"id\":1}}", - ser_id - ); - let (response, _) = rpc.raw_json_request(&unsub_req).await.unwrap(); - - assert_eq!(response, r#"{"jsonrpc":"2.0","result":true,"id":1}"#); - - // Unsubscribe again and fail - let (response, _) = rpc.raw_json_request(&unsub_req).await.unwrap(); - let expected = r#"{"jsonrpc":"2.0","result":false,"id":1}"#; - - assert_eq!(response, expected); - } - #[tokio::test] async fn subscribe_and_unsubscribe_with_wrong_id() { let (rpc, _) = setup_io_handler(TestVoterState); From 2e191723e4e1a146b1f4ad82238dea6661b628c6 Mon Sep 17 00:00:00 2001 From: bear Date: Tue, 14 Jun 2022 16:48:49 +0800 Subject: [PATCH 335/484] Add `TypeInfo` (#11599) --- primitives/runtime/src/transaction_validity.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/primitives/runtime/src/transaction_validity.rs b/primitives/runtime/src/transaction_validity.rs index 29c8b542319b1..7cc8b70df9f96 100644 --- a/primitives/runtime/src/transaction_validity.rs +++ b/primitives/runtime/src/transaction_validity.rs @@ -21,6 +21,7 @@ use crate::{ codec::{Decode, Encode}, RuntimeDebug, }; +use scale_info::TypeInfo; use sp_std::prelude::*; /// Priority for a transaction. Additive. Higher is better. @@ -34,7 +35,7 @@ pub type TransactionLongevity = u64; pub type TransactionTag = Vec; /// An invalid transaction validity. -#[derive(Clone, PartialEq, Eq, Encode, Decode, Copy, RuntimeDebug)] +#[derive(Clone, PartialEq, Eq, Encode, Decode, Copy, RuntimeDebug, TypeInfo)] #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub enum InvalidTransaction { /// The call of the transaction is not expected. @@ -117,7 +118,7 @@ impl From for &'static str { } /// An unknown transaction validity. -#[derive(Clone, PartialEq, Eq, Encode, Decode, Copy, RuntimeDebug)] +#[derive(Clone, PartialEq, Eq, Encode, Decode, Copy, RuntimeDebug, TypeInfo)] #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub enum UnknownTransaction { /// Could not lookup some information that is required to validate the transaction. @@ -141,7 +142,7 @@ impl From for &'static str { } /// Errors that can occur while checking the validity of a transaction. -#[derive(Clone, PartialEq, Eq, Encode, Decode, Copy, RuntimeDebug)] +#[derive(Clone, PartialEq, Eq, Encode, Decode, Copy, RuntimeDebug, TypeInfo)] #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub enum TransactionValidityError { /// The transaction is invalid. From 2cc8755184c8488777278af4b0ec6a59edabf385 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Tue, 14 Jun 2022 11:21:44 +0200 Subject: [PATCH 336/484] wasm-builder: Fix constant re-running of `build.rs` scripts. (#11624) Recently we added the wasm binaries to the `rerun-if-changed` list. The problem with that is that they have a later mtime than the `invoked.timestamp` file and this file's mtime is used to determine if the `build.rs` script needs to be re-run. The solution to this is that we copy the mtime of this `invoked.timestamp` file and add it to the wasm binaries. Then cargo/rustc doesn't constantly wants to rerun the `build.rs` script. --- Cargo.lock | 13 +++++++++++ utils/wasm-builder/Cargo.toml | 1 + utils/wasm-builder/src/wasm_project.rs | 32 ++++++++++++++++++++++++++ 3 files changed, 46 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index ae0a497867b51..4f74660e67240 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2007,6 +2007,18 @@ dependencies = [ "log", ] +[[package]] +name = "filetime" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0408e2626025178a6a7f7ffc05a25bc47103229f19c113755de7bf63816290c" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall", + "winapi", +] + [[package]] name = "finality-grandpa" version = "0.15.0" @@ -10648,6 +10660,7 @@ dependencies = [ "ansi_term", "build-helper", "cargo_metadata", + "filetime", "sp-maybe-compressed-blob", "strum", "tempfile", diff --git a/utils/wasm-builder/Cargo.toml b/utils/wasm-builder/Cargo.toml index 0cd1249628f22..8f887e45ec176 100644 --- a/utils/wasm-builder/Cargo.toml +++ b/utils/wasm-builder/Cargo.toml @@ -22,3 +22,4 @@ toml = "0.5.4" walkdir = "2.3.2" wasm-gc-api = "0.1.11" sp-maybe-compressed-blob = { version = "4.1.0-dev", path = "../../primitives/maybe-compressed-blob" } +filetime = "0.2.16" diff --git a/utils/wasm-builder/src/wasm_project.rs b/utils/wasm-builder/src/wasm_project.rs index 4316f9041f11d..be84f2fecfb53 100644 --- a/utils/wasm-builder/src/wasm_project.rs +++ b/utils/wasm-builder/src/wasm_project.rs @@ -152,9 +152,41 @@ pub(crate) fn create_and_compile( &bloaty, ); + if let Err(err) = adjust_mtime(&bloaty, final_wasm_binary.as_ref()) { + build_helper::warning!("Error while adjusting the mtime of the wasm binaries: {}", err) + } + (final_wasm_binary, bloaty) } +/// Adjust the mtime of the bloaty and compressed/compact wasm files. +/// +/// We add the bloaty and the compressed/compact wasm file to the `rerun-if-changed` files. +/// Cargo/Rustc determines based on the timestamp of the `invoked.timestamp` file that can be found +/// in the `OUT_DIR/..`, if it needs to rerun a `build.rs` script. The problem is that this +/// `invoked.timestamp` is created when the `build.rs` is executed and the wasm binaries are created +/// later. This leads to them having a later mtime than the `invoked.timestamp` file and thus, +/// cargo/rustc always re-executes the `build.rs` script. To hack around this, we copy the mtime of +/// the `invoked.timestamp` to the wasm binaries. +fn adjust_mtime( + bloaty_wasm: &WasmBinaryBloaty, + compressed_or_compact_wasm: Option<&WasmBinary>, +) -> std::io::Result<()> { + let out_dir = build_helper::out_dir(); + let invoked_timestamp = out_dir.join("../invoked.timestamp"); + + // Get the mtime of the `invoked.timestamp` + let metadata = fs::metadata(invoked_timestamp)?; + let mtime = filetime::FileTime::from_last_modification_time(&metadata); + + filetime::set_file_mtime(bloaty_wasm.wasm_binary_bloaty_path(), mtime)?; + if let Some(binary) = compressed_or_compact_wasm.as_ref() { + filetime::set_file_mtime(binary.wasm_binary_path(), mtime)?; + } + + Ok(()) +} + /// Find the `Cargo.lock` relative to the `OUT_DIR` environment variable. /// /// If the `Cargo.lock` cannot be found, we emit a warning and return `None`. From 8a039aafd6d182ae1888d289e2970d199c097da7 Mon Sep 17 00:00:00 2001 From: Leonardo <14614620+aardbol@users.noreply.github.com> Date: Tue, 14 Jun 2022 11:42:28 +0200 Subject: [PATCH 337/484] Increment subkey version to 2.0.2 (#11656) * Increment subkey version to 2.0.2 * Update Cargo.lock Co-authored-by: Davide Galassi --- Cargo.lock | 2 +- bin/utils/subkey/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4f74660e67240..10d34da9cf888 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10406,7 +10406,7 @@ dependencies = [ [[package]] name = "subkey" -version = "2.0.1" +version = "2.0.2" dependencies = [ "clap 3.1.18", "sc-cli", diff --git a/bin/utils/subkey/Cargo.toml b/bin/utils/subkey/Cargo.toml index 26bc949b74ae7..4c4e47e702be6 100644 --- a/bin/utils/subkey/Cargo.toml +++ b/bin/utils/subkey/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "subkey" -version = "2.0.1" +version = "2.0.2" authors = ["Parity Technologies "] edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" From 106f904280870c0c682f2a35353b6cf2f69d5442 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 14 Jun 2022 11:42:52 +0200 Subject: [PATCH 338/484] Implement `Deref` for `BoundedSlice` (#11660) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Impl Deref for BoundedSlice Signed-off-by: Oliver Tale-Yazdi * Update primitives/runtime/src/bounded/bounded_vec.rs Co-authored-by: Keith Yeung Co-authored-by: Bastian Köcher Co-authored-by: Keith Yeung --- primitives/runtime/src/bounded/bounded_vec.rs | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/primitives/runtime/src/bounded/bounded_vec.rs b/primitives/runtime/src/bounded/bounded_vec.rs index 555a34819a423..c9c9f851d3249 100644 --- a/primitives/runtime/src/bounded/bounded_vec.rs +++ b/primitives/runtime/src/bounded/bounded_vec.rs @@ -223,6 +223,15 @@ impl<'a, T, S> Clone for BoundedSlice<'a, T, S> { // Since a reference `&T` is always `Copy`, so is `BoundedSlice<'a, T, S>`. impl<'a, T, S> Copy for BoundedSlice<'a, T, S> {} +// will allow for all immutable operations of `[T]` on `BoundedSlice`. +impl<'a, T, S> Deref for BoundedSlice<'a, T, S> { + type Target = [T]; + + fn deref(&self) -> &Self::Target { + self.0 + } +} + impl<'a, T, S> sp_std::iter::IntoIterator for BoundedSlice<'a, T, S> { type Item = &'a T; type IntoIter = sp_std::slice::Iter<'a, T>; @@ -647,7 +656,7 @@ impl AsMut<[T]> for BoundedVec { } } -// will allow for immutable all operations of `Vec` on `BoundedVec`. +// will allow for all immutable operations of `Vec` on `BoundedVec`. impl Deref for BoundedVec { type Target = Vec; @@ -970,7 +979,7 @@ pub mod test { } #[test] - fn deref_coercion_works() { + fn deref_vec_coercion_works() { let bounded: BoundedVec> = bounded_vec![1, 2, 3]; // these methods come from deref-ed vec. assert_eq!(bounded.len(), 3); @@ -978,6 +987,15 @@ pub mod test { assert!(!bounded.is_empty()); } + #[test] + fn deref_slice_coercion_works() { + let bounded = BoundedSlice::>::try_from(&[1, 2, 3][..]).unwrap(); + // these methods come from deref-ed slice. + assert_eq!(bounded.len(), 3); + assert!(bounded.iter().next().is_some()); + assert!(!bounded.is_empty()); + } + #[test] fn try_mutate_works() { let bounded: BoundedVec> = bounded_vec![1, 2, 3, 4, 5, 6]; From 448fa83a888487904eea6bf3ce61ed5f5ef4a989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= <123550+andresilva@users.noreply.github.com> Date: Tue, 14 Jun 2022 11:41:11 +0100 Subject: [PATCH 339/484] grandpa: fix creation of justification with equivocating precommits in commit (#11302) * grandpa: fix creation of justification ancestry we would reject commits that have precommits targeting blocks lower than the commit target. when there is an equivocation (or if it the commit is not minimal) it is possible to have such precommits and we should assume that they are the round base. * grandpa: bump to 0.16.0 * grandpa: add test for justification with equivocation * grandpa: fix failing test --- Cargo.lock | 8 +-- client/finality-grandpa/Cargo.toml | 4 +- client/finality-grandpa/rpc/Cargo.toml | 2 +- client/finality-grandpa/src/finality_proof.rs | 9 ++- client/finality-grandpa/src/justification.rs | 49 ++++++++++++-- client/finality-grandpa/src/observer.rs | 2 +- client/finality-grandpa/src/tests.rs | 67 +++++++++++++++++++ frame/grandpa/Cargo.toml | 2 +- primitives/finality-grandpa/Cargo.toml | 2 +- 9 files changed, 125 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 10d34da9cf888..cf56bf0703246 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2021,9 +2021,9 @@ dependencies = [ [[package]] name = "finality-grandpa" -version = "0.15.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9def033d8505edf199f6a5d07aa7e6d2d6185b164293b77f0efd108f4f3e11d" +checksum = "b22349c6a11563a202d95772a68e0fcf56119e74ea8a2a19cf2301460fcd0df5" dependencies = [ "either", "futures", @@ -2031,7 +2031,7 @@ dependencies = [ "log", "num-traits", "parity-scale-codec", - "parking_lot 0.11.2", + "parking_lot 0.12.0", "rand 0.8.4", "scale-info", ] @@ -11242,7 +11242,7 @@ version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ee73e6e4924fe940354b8d4d98cad5231175d615cd855b758adc658c0aac6a0" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", "digest 0.10.3", "rand 0.8.4", "static_assertions", diff --git a/client/finality-grandpa/Cargo.toml b/client/finality-grandpa/Cargo.toml index 2f7fcd4d052b1..77cd847d48169 100644 --- a/client/finality-grandpa/Cargo.toml +++ b/client/finality-grandpa/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] ahash = "0.7.6" async-trait = "0.1.50" dyn-clone = "1.0" -finality-grandpa = { version = "0.15.0", features = ["derive-codec"] } +finality-grandpa = { version = "0.16.0", features = ["derive-codec"] } futures = "0.3.21" futures-timer = "3.0.1" hex = "0.4.2" @@ -50,7 +50,7 @@ sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } [dev-dependencies] assert_matches = "1.3.0" -finality-grandpa = { version = "0.15.0", features = [ +finality-grandpa = { version = "0.16.0", features = [ "derive-codec", "test-helpers", ] } diff --git a/client/finality-grandpa/rpc/Cargo.toml b/client/finality-grandpa/rpc/Cargo.toml index 0648c874f7dff..e8a3f24006e22 100644 --- a/client/finality-grandpa/rpc/Cargo.toml +++ b/client/finality-grandpa/rpc/Cargo.toml @@ -10,7 +10,7 @@ readme = "README.md" homepage = "https://substrate.io" [dependencies] -finality-grandpa = { version = "0.15.0", features = ["derive-codec"] } +finality-grandpa = { version = "0.16.0", features = ["derive-codec"] } futures = "0.3.16" jsonrpsee = { version = "0.13.0", features = ["server", "macros"] } log = "0.4.8" diff --git a/client/finality-grandpa/src/finality_proof.rs b/client/finality-grandpa/src/finality_proof.rs index 03a4f2ff450a3..ac243a1633ee1 100644 --- a/client/finality-grandpa/src/finality_proof.rs +++ b/client/finality-grandpa/src/finality_proof.rs @@ -237,7 +237,7 @@ where } #[cfg(test)] -pub(crate) mod tests { +mod tests { use super::*; use crate::{authorities::AuthoritySetChanges, BlockNumberOps, ClientError, SetId}; use futures::executor::block_on; @@ -271,6 +271,7 @@ pub(crate) mod tests { let justification: GrandpaJustification = Decode::decode(&mut &proof.justification[..]) .map_err(|_| ClientError::JustificationDecode)?; + justification.verify(current_set_id, ¤t_authorities)?; Ok(proof) @@ -370,7 +371,7 @@ pub(crate) mod tests { #[test] fn finality_proof_check_fails_with_incomplete_justification() { - let (client, _, blocks) = test_blockchain(8, &[4, 5, 8]); + let (_, _, blocks) = test_blockchain(8, &[4, 5, 8]); // Create a commit without precommits let commit = finality_grandpa::Commit { @@ -378,7 +379,9 @@ pub(crate) mod tests { target_number: *blocks[7].header().number(), precommits: Vec::new(), }; - let grandpa_just = GrandpaJustification::from_commit(&client, 8, commit).unwrap(); + + let grandpa_just = + GrandpaJustification:: { round: 8, votes_ancestries: Vec::new(), commit }; let finality_proof = FinalityProof { block: header(2).hash(), diff --git a/client/finality-grandpa/src/justification.rs b/client/finality-grandpa/src/justification.rs index 39f24cb8ea57d..44abb4b95beba 100644 --- a/client/finality-grandpa/src/justification.rs +++ b/client/finality-grandpa/src/justification.rs @@ -42,9 +42,9 @@ use crate::{AuthorityList, Commit, Error}; /// nodes, and are used by syncing nodes to prove authority set handoffs. #[derive(Clone, Encode, Decode, PartialEq, Eq, Debug)] pub struct GrandpaJustification { - round: u64, + pub(crate) round: u64, pub(crate) commit: Commit, - votes_ancestries: Vec, + pub(crate) votes_ancestries: Vec, } impl GrandpaJustification { @@ -66,16 +66,33 @@ impl GrandpaJustification { Err(Error::Client(ClientError::BadJustification(msg))) }; + // we pick the precommit for the lowest block as the base that + // should serve as the root block for populating ancestry (i.e. + // collect all headers from all precommit blocks to the base) + let (base_hash, base_number) = match commit + .precommits + .iter() + .map(|signed| &signed.precommit) + .min_by_key(|precommit| precommit.target_number) + .map(|precommit| (precommit.target_hash.clone(), precommit.target_number)) + { + None => return error(), + Some(base) => base, + }; + for signed in commit.precommits.iter() { let mut current_hash = signed.precommit.target_hash; loop { - if current_hash == commit.target_hash { + if current_hash == base_hash { break } match client.header(BlockId::Hash(current_hash))? { Some(current_header) => { - if *current_header.number() <= commit.target_number { + // NOTE: this should never happen as we pick the lowest block + // as base and only traverse backwards from the other blocks + // in the commit. but better be safe to avoid an unbound loop. + if *current_header.number() <= base_number { return error() } @@ -83,6 +100,7 @@ impl GrandpaJustification { if votes_ancestries_hashes.insert(current_hash) { votes_ancestries.push(current_header); } + current_hash = parent_hash; }, _ => return error(), @@ -142,13 +160,30 @@ impl GrandpaJustification { let ancestry_chain = AncestryChain::::new(&self.votes_ancestries); match finality_grandpa::validate_commit(&self.commit, voters, &ancestry_chain) { - Ok(ref result) if result.ghost().is_some() => {}, + Ok(ref result) if result.is_valid() => {}, _ => { let msg = "invalid commit in grandpa justification".to_string(); return Err(ClientError::BadJustification(msg)) }, } + // we pick the precommit for the lowest block as the base that + // should serve as the root block for populating ancestry (i.e. + // collect all headers from all precommit blocks to the base) + let base_hash = self + .commit + .precommits + .iter() + .map(|signed| &signed.precommit) + .min_by_key(|precommit| precommit.target_number) + .map(|precommit| precommit.target_hash.clone()) + .expect( + "can only fail if precommits is empty; \ + commit has been validated above; \ + valid commits must include precommits; \ + qed.", + ); + let mut buf = Vec::new(); let mut visited_hashes = HashSet::new(); for signed in self.commit.precommits.iter() { @@ -165,11 +200,11 @@ impl GrandpaJustification { )) } - if self.commit.target_hash == signed.precommit.target_hash { + if base_hash == signed.precommit.target_hash { continue } - match ancestry_chain.ancestry(self.commit.target_hash, signed.precommit.target_hash) { + match ancestry_chain.ancestry(base_hash, signed.precommit.target_hash) { Ok(route) => { // ancestry starts from parent hash but the precommit target hash has been // visited diff --git a/client/finality-grandpa/src/observer.rs b/client/finality-grandpa/src/observer.rs index 7516fbb681c4b..85d80dcfe7cde 100644 --- a/client/finality-grandpa/src/observer.rs +++ b/client/finality-grandpa/src/observer.rs @@ -116,7 +116,7 @@ where Err(e) => return future::err(e.into()), }; - if validation_result.ghost().is_some() { + if validation_result.is_valid() { let finalized_hash = commit.target_hash; let finalized_number = commit.target_number; diff --git a/client/finality-grandpa/src/tests.rs b/client/finality-grandpa/src/tests.rs index 5083cbfc21d1d..2d12232b04f15 100644 --- a/client/finality-grandpa/src/tests.rs +++ b/client/finality-grandpa/src/tests.rs @@ -1569,6 +1569,73 @@ fn grandpa_environment_never_overwrites_round_voter_state() { assert_matches!(get_current_round(2).unwrap(), HasVoted::Yes(_, _)); } +#[test] +fn justification_with_equivocation() { + use sp_application_crypto::Pair; + + // we have 100 authorities + let pairs = (0..100).map(|n| AuthorityPair::from_seed(&[n; 32])).collect::>(); + let voters = pairs.iter().map(AuthorityPair::public).map(|id| (id, 1)).collect::>(); + let api = TestApi::new(voters.clone()); + let mut net = GrandpaTestNet::new(api.clone(), 1, 0); + + // we create a basic chain with 3 blocks (no forks) + net.peer(0).push_blocks(3, false); + + let client = net.peer(0).client().clone(); + let block1 = client.header(&BlockId::Number(1)).ok().flatten().unwrap(); + let block2 = client.header(&BlockId::Number(2)).ok().flatten().unwrap(); + let block3 = client.header(&BlockId::Number(3)).ok().flatten().unwrap(); + + let set_id = 0; + let justification = { + let round = 1; + + let make_precommit = |target_hash, target_number, pair: &AuthorityPair| { + let precommit = finality_grandpa::Precommit { target_hash, target_number }; + + let msg = finality_grandpa::Message::Precommit(precommit.clone()); + let encoded = sp_finality_grandpa::localized_payload(round, set_id, &msg); + + let precommit = finality_grandpa::SignedPrecommit { + precommit: precommit.clone(), + signature: pair.sign(&encoded[..]), + id: pair.public(), + }; + + precommit + }; + + let mut precommits = Vec::new(); + + // we have 66/100 votes for block #3 and therefore do not have threshold to finalize + for pair in pairs.iter().take(66) { + let precommit = make_precommit(block3.hash(), *block3.number(), pair); + precommits.push(precommit); + } + + // we create an equivocation for the 67th validator targetting blocks #1 and #2. + // this should be accounted as "voting for all blocks" and therefore block #3 will + // have 67/100 votes, reaching finality threshold. + { + precommits.push(make_precommit(block1.hash(), *block1.number(), &pairs[66])); + precommits.push(make_precommit(block2.hash(), *block2.number(), &pairs[66])); + } + + let commit = finality_grandpa::Commit { + target_hash: block3.hash(), + target_number: *block3.number(), + precommits, + }; + + GrandpaJustification::from_commit(&client.as_client(), round, commit).unwrap() + }; + + // the justification should include the minimal necessary vote ancestry and + // the commit should be valid + assert!(justification.verify(set_id, &voters).is_ok()); +} + #[test] fn imports_justification_for_regular_blocks_on_import() { // NOTE: this is a regression test since initially we would only import diff --git a/frame/grandpa/Cargo.toml b/frame/grandpa/Cargo.toml index cb4233a26fb33..2090a4ea2e228 100644 --- a/frame/grandpa/Cargo.toml +++ b/frame/grandpa/Cargo.toml @@ -31,7 +31,7 @@ sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../pr sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -grandpa = { package = "finality-grandpa", version = "0.15.0", features = ["derive-codec"] } +grandpa = { package = "finality-grandpa", version = "0.16.0", features = ["derive-codec"] } frame-benchmarking = { version = "4.0.0-dev", path = "../benchmarking" } frame-election-provider-support = { version = "4.0.0-dev", path = "../election-provider-support" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } diff --git a/primitives/finality-grandpa/Cargo.toml b/primitives/finality-grandpa/Cargo.toml index f86e2ab07e673..32945eacf0b93 100644 --- a/primitives/finality-grandpa/Cargo.toml +++ b/primitives/finality-grandpa/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -grandpa = { package = "finality-grandpa", version = "0.15.0", default-features = false, features = ["derive-codec"] } +grandpa = { package = "finality-grandpa", version = "0.16.0", default-features = false, features = ["derive-codec"] } log = { version = "0.4.17", optional = true } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", features = ["derive"], optional = true } From 92b6a54ad694362ae266666bff88572f34d6bf55 Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Tue, 14 Jun 2022 19:55:56 +0300 Subject: [PATCH 340/484] Make it possible to disable RocksDB completely (#11537) * Make it possible to disable RocksDB completely * Make ParityDB non-optional * Address review comments --- bin/node/cli/Cargo.toml | 2 +- bin/node/testing/Cargo.toml | 11 ++++----- client/cli/Cargo.toml | 4 +++- client/cli/src/arg_enums.rs | 21 ++++++++++++----- client/cli/src/config.rs | 12 +++++++++- client/db/Cargo.toml | 5 ++-- client/db/src/lib.rs | 18 ++++++++------ client/db/src/utils.rs | 31 ++++--------------------- client/service/Cargo.toml | 4 ++-- test-utils/client/Cargo.toml | 2 +- utils/frame/benchmarking-cli/Cargo.toml | 9 +++---- 11 files changed, 59 insertions(+), 60 deletions(-) diff --git a/bin/node/cli/Cargo.toml b/bin/node/cli/Cargo.toml index ab6644a379bb5..8f18aec891e9b 100644 --- a/bin/node/cli/Cargo.toml +++ b/bin/node/cli/Cargo.toml @@ -153,7 +153,7 @@ cli = [ "sc-cli", "frame-benchmarking-cli", "substrate-frame-cli", - "sc-service/db", + "sc-service/rocksdb", "clap", "clap_complete", "substrate-build-script-utils", diff --git a/bin/node/testing/Cargo.toml b/bin/node/testing/Cargo.toml index f7a78d10910b3..7caf10366b48c 100644 --- a/bin/node/testing/Cargo.toml +++ b/bin/node/testing/Cargo.toml @@ -22,21 +22,18 @@ frame-system = { version = "4.0.0-dev", path = "../../../frame/system" } node-executor = { version = "3.0.0-dev", path = "../executor" } node-primitives = { version = "2.0.0", path = "../primitives" } node-runtime = { version = "3.0.0-dev", path = "../runtime" } -pallet-asset-tx-payment = { version = "4.0.0-dev", path = "../../../frame/transaction-payment/asset-tx-payment/" } +pallet-asset-tx-payment = { version = "4.0.0-dev", path = "../../../frame/transaction-payment/asset-tx-payment" } pallet-transaction-payment = { version = "4.0.0-dev", path = "../../../frame/transaction-payment" } sc-block-builder = { version = "0.10.0-dev", path = "../../../client/block-builder" } -sc-client-api = { version = "4.0.0-dev", path = "../../../client/api/" } -sc-client-db = { version = "0.10.0-dev", features = [ - "kvdb-rocksdb", - "parity-db", -], path = "../../../client/db/" } +sc-client-api = { version = "4.0.0-dev", path = "../../../client/api" } +sc-client-db = { version = "0.10.0-dev", features = ["rocksdb"], path = "../../../client/db" } sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } sc-executor = { version = "0.10.0-dev", features = [ "wasmtime", ], path = "../../../client/executor" } sc-service = { version = "0.10.0-dev", features = [ "test-helpers", - "db", + "rocksdb", ], path = "../../../client/service" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sp-block-builder = { version = "4.0.0-dev", path = "../../../primitives/block-builder" } diff --git a/client/cli/Cargo.toml b/client/cli/Cargo.toml index 456489e5f6639..4f0d777d137b9 100644 --- a/client/cli/Cargo.toml +++ b/client/cli/Cargo.toml @@ -31,7 +31,7 @@ thiserror = "1.0.30" tiny-bip39 = "0.8.2" tokio = { version = "1.17.0", features = ["signal", "rt-multi-thread", "parking_lot"] } sc-client-api = { version = "4.0.0-dev", path = "../api" } -sc-client-db = { version = "0.10.0-dev", path = "../db" } +sc-client-db = { version = "0.10.0-dev", default-features = false, path = "../db" } sc-keystore = { version = "4.0.0-dev", path = "../keystore" } sc-network = { version = "0.10.0-dev", path = "../network" } sc-service = { version = "0.10.0-dev", default-features = false, path = "../service" } @@ -50,4 +50,6 @@ sp-version = { version = "5.0.0", path = "../../primitives/version" } tempfile = "3.1.0" [features] +default = ["rocksdb"] +rocksdb = ["sc-client-db/rocksdb"] wasmtime = ["sc-service/wasmtime"] diff --git a/client/cli/src/arg_enums.rs b/client/cli/src/arg_enums.rs index bc0989cf34659..283fef985dfb9 100644 --- a/client/cli/src/arg_enums.rs +++ b/client/cli/src/arg_enums.rs @@ -238,6 +238,7 @@ impl Into for RpcMethods { #[derive(Debug, Clone, PartialEq, Copy)] pub enum Database { /// Facebooks RocksDB + #[cfg(feature = "rocksdb")] RocksDb, /// ParityDb. ParityDb, @@ -252,12 +253,14 @@ impl std::str::FromStr for Database { type Err = String; fn from_str(s: &str) -> Result { + #[cfg(feature = "rocksdb")] if s.eq_ignore_ascii_case("rocksdb") { - Ok(Self::RocksDb) - } else if s.eq_ignore_ascii_case("paritydb-experimental") { - Ok(Self::ParityDbDeprecated) + return Ok(Self::RocksDb) + } + if s.eq_ignore_ascii_case("paritydb-experimental") { + return Ok(Self::ParityDbDeprecated) } else if s.eq_ignore_ascii_case("paritydb") { - Ok(Self::ParityDb) + return Ok(Self::ParityDb) } else if s.eq_ignore_ascii_case("auto") { Ok(Self::Auto) } else { @@ -268,8 +271,14 @@ impl std::str::FromStr for Database { impl Database { /// Returns all the variants of this enum to be shown in the cli. - pub fn variants() -> &'static [&'static str] { - &["rocksdb", "paritydb", "paritydb-experimental", "auto"] + pub const fn variants() -> &'static [&'static str] { + &[ + #[cfg(feature = "rocksdb")] + "rocksdb", + "paritydb", + "paritydb-experimental", + "auto", + ] } } diff --git a/client/cli/src/config.rs b/client/cli/src/config.rs index 5e91cf6c74dae..6e1317c11fbc4 100644 --- a/client/cli/src/config.rs +++ b/client/cli/src/config.rs @@ -220,6 +220,7 @@ pub trait CliConfiguration: Sized { let rocksdb_path = base_path.join("db").join(role_dir); let paritydb_path = base_path.join("paritydb").join(role_dir); Ok(match database { + #[cfg(feature = "rocksdb")] Database::RocksDb => DatabaseSource::RocksDb { path: rocksdb_path, cache_size }, Database::ParityDb => DatabaseSource::ParityDb { path: paritydb_path }, Database::ParityDbDeprecated => { @@ -500,7 +501,16 @@ pub trait CliConfiguration: Sized { let net_config_dir = config_dir.join(DEFAULT_NETWORK_CONFIG_PATH); let client_id = C::client_id(); let database_cache_size = self.database_cache_size()?.unwrap_or(1024); - let database = self.database()?.unwrap_or(Database::RocksDb); + let database = self.database()?.unwrap_or( + #[cfg(feature = "rocksdb")] + { + Database::RocksDb + }, + #[cfg(not(feature = "rocksdb"))] + { + Database::ParityDb + }, + ); let node_key = self.node_key(&net_config_dir)?; let role = self.role(is_dev)?; let max_runtime_instances = self.max_runtime_instances()?.unwrap_or(8); diff --git a/client/db/Cargo.toml b/client/db/Cargo.toml index e1472bcbda01a..3b6402b3f6023 100644 --- a/client/db/Cargo.toml +++ b/client/db/Cargo.toml @@ -22,7 +22,7 @@ kvdb-memorydb = "0.11.0" kvdb-rocksdb = { version = "0.15.2", optional = true } linked-hash-map = "0.5.4" log = "0.4.17" -parity-db = { version = "0.3.13", optional = true } +parity-db = "0.3.13" parking_lot = "0.12.0" sc-client-api = { version = "4.0.0-dev", path = "../api" } sc-state-db = { version = "0.10.0-dev", path = "../state-db" } @@ -45,5 +45,4 @@ substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/ru default = [] test-helpers = [] runtime-benchmarks = [] -with-kvdb-rocksdb = ["kvdb-rocksdb"] -with-parity-db = ["parity-db"] +rocksdb = ["kvdb-rocksdb"] diff --git a/client/db/src/lib.rs b/client/db/src/lib.rs index ccdb434dfbd32..f1adbd3df1a0f 100644 --- a/client/db/src/lib.rs +++ b/client/db/src/lib.rs @@ -30,15 +30,13 @@ pub mod offchain; -#[cfg(any(feature = "with-kvdb-rocksdb", test))] pub mod bench; mod children; -#[cfg(feature = "with-parity-db")] mod parity_db; mod stats; mod storage_cache; -#[cfg(any(feature = "with-kvdb-rocksdb", test))] +#[cfg(any(feature = "rocksdb", test))] mod upgrade; mod utils; @@ -94,7 +92,6 @@ use sp_trie::{prefixed_key, MemoryDB, PrefixedMemoryDB}; pub use sc_state_db::PruningMode; pub use sp_database::Database; -#[cfg(any(feature = "with-kvdb-rocksdb", test))] pub use bench::BenchmarkingState; const CACHE_HEADERS: usize = 8; @@ -106,7 +103,6 @@ const DEFAULT_CHILD_RATIO: (usize, usize) = (1, 10); pub type DbState = sp_state_machine::TrieBackend>>, HashFor>; -#[cfg(feature = "with-parity-db")] /// Length of a [`DbHash`]. const DB_HASH_LEN: usize = 32; @@ -330,6 +326,7 @@ pub enum DatabaseSource { cache_size: usize, }, /// Load a RocksDB database from a given path. Recommended for most uses. + #[cfg(feature = "rocksdb")] RocksDb { /// Path to the database. path: PathBuf, @@ -362,7 +359,9 @@ impl DatabaseSource { // IIUC this is needed for polkadot to create its own dbs, so until it can use parity db // I would think rocksdb, but later parity-db. DatabaseSource::Auto { paritydb_path, .. } => Some(paritydb_path), - DatabaseSource::RocksDb { path, .. } | DatabaseSource::ParityDb { path } => Some(path), + #[cfg(feature = "rocksdb")] + DatabaseSource::RocksDb { path, .. } => Some(path), + DatabaseSource::ParityDb { path } => Some(path), DatabaseSource::Custom { .. } => None, } } @@ -374,7 +373,11 @@ impl DatabaseSource { *paritydb_path = p.into(); true }, - DatabaseSource::RocksDb { ref mut path, .. } | + #[cfg(feature = "rocksdb")] + DatabaseSource::RocksDb { ref mut path, .. } => { + *path = p.into(); + true + }, DatabaseSource::ParityDb { ref mut path } => { *path = p.into(); true @@ -388,6 +391,7 @@ impl std::fmt::Display for DatabaseSource { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let name = match self { DatabaseSource::Auto { .. } => "Auto", + #[cfg(feature = "rocksdb")] DatabaseSource::RocksDb { .. } => "RocksDb", DatabaseSource::ParityDb { .. } => "ParityDb", DatabaseSource::Custom { .. } => "Custom", diff --git a/client/db/src/utils.rs b/client/db/src/utils.rs index 0227e4db8bcd0..567950d089e1b 100644 --- a/client/db/src/utils.rs +++ b/client/db/src/utils.rs @@ -34,12 +34,6 @@ use sp_trie::DBValue; /// Number of columns in the db. Must be the same for both full && light dbs. /// Otherwise RocksDb will fail to open database && check its type. -#[cfg(any( - feature = "with-kvdb-rocksdb", - feature = "with-parity-db", - feature = "test-helpers", - test -))] pub const NUM_COLUMNS: u32 = 13; /// Meta column. The set of keys in the column is shared by full && light storages. pub const COLUMN_META: u32 = 0; @@ -198,6 +192,7 @@ fn open_database_at( ) -> OpenDbResult { let db: Arc> = match &db_source { DatabaseSource::ParityDb { path } => open_parity_db::(path, db_type, create)?, + #[cfg(feature = "rocksdb")] DatabaseSource::RocksDb { path, cache_size } => open_kvdb_rocksdb::(path, db_type, create, *cache_size)?, DatabaseSource::Custom { db, require_create_flag } => { @@ -266,7 +261,6 @@ impl From for sp_blockchain::Error { } } -#[cfg(feature = "with-parity-db")] impl From for OpenDbError { fn from(err: parity_db::Error) -> Self { if matches!(err, parity_db::Error::DatabaseNotFound) { @@ -287,7 +281,6 @@ impl From for OpenDbError { } } -#[cfg(feature = "with-parity-db")] fn open_parity_db(path: &Path, db_type: DatabaseType, create: bool) -> OpenDbResult { match crate::parity_db::open(path, db_type, create, false) { Ok(db) => Ok(db), @@ -300,16 +293,7 @@ fn open_parity_db(path: &Path, db_type: DatabaseType, create: boo } } -#[cfg(not(feature = "with-parity-db"))] -fn open_parity_db( - _path: &Path, - _db_type: DatabaseType, - _create: bool, -) -> OpenDbResult { - Err(OpenDbError::NotEnabled("with-parity-db")) -} - -#[cfg(any(feature = "with-kvdb-rocksdb", test))] +#[cfg(any(feature = "rocksdb", test))] fn open_kvdb_rocksdb( path: &Path, db_type: DatabaseType, @@ -359,7 +343,7 @@ fn open_kvdb_rocksdb( Ok(sp_database::as_database(db)) } -#[cfg(not(any(feature = "with-kvdb-rocksdb", test)))] +#[cfg(not(any(feature = "rocksdb", test)))] fn open_kvdb_rocksdb( _path: &Path, _db_type: DatabaseType, @@ -602,7 +586,7 @@ mod tests { use std::path::PathBuf; type Block = RawBlock>; - #[cfg(any(feature = "with-kvdb-rocksdb", test))] + #[cfg(any(feature = "rocksdb", test))] #[test] fn database_type_subdir_migration() { type Block = RawBlock>; @@ -639,7 +623,6 @@ mod tests { "db_version", ); - #[cfg(feature = "with-parity-db")] check_dir_for_db_type( DatabaseType::Full, DatabaseSource::ParityDb { path: PathBuf::new() }, @@ -702,8 +685,6 @@ mod tests { assert_eq!(joined.remaining_len().unwrap(), Some(0)); } - #[cfg(feature = "with-parity-db")] - #[cfg(any(feature = "with-kvdb-rocksdb", test))] #[test] fn test_open_database_auto_new() { let db_dir = tempfile::TempDir::new().unwrap(); @@ -749,8 +730,6 @@ mod tests { } } - #[cfg(feature = "with-parity-db")] - #[cfg(any(feature = "with-kvdb-rocksdb", test))] #[test] fn test_open_database_rocksdb_new() { let db_dir = tempfile::TempDir::new().unwrap(); @@ -801,8 +780,6 @@ mod tests { } } - #[cfg(feature = "with-parity-db")] - #[cfg(any(feature = "with-kvdb-rocksdb", test))] #[test] fn test_open_database_paritydb_new() { let db_dir = tempfile::TempDir::new().unwrap(); diff --git a/client/service/Cargo.toml b/client/service/Cargo.toml index 22bac652c30c4..c1cb9e459041b 100644 --- a/client/service/Cargo.toml +++ b/client/service/Cargo.toml @@ -13,10 +13,10 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [features] -default = ["db"] +default = ["rocksdb"] # The RocksDB feature activates the RocksDB database backend. If it is not activated, and you pass # a path to a database, an error will be produced at runtime. -db = ["sc-client-db/with-kvdb-rocksdb", "sc-client-db/with-parity-db"] +rocksdb = ["sc-client-db/rocksdb"] wasmtime = ["sc-executor/wasmtime"] # exposes the client type test-helpers = [] diff --git a/test-utils/client/Cargo.toml b/test-utils/client/Cargo.toml index 1ff7d0de1d676..ce5ef2ffcc01a 100644 --- a/test-utils/client/Cargo.toml +++ b/test-utils/client/Cargo.toml @@ -19,7 +19,7 @@ hex = "0.4" serde = "1.0.136" serde_json = "1.0.79" sc-client-api = { version = "4.0.0-dev", path = "../../client/api" } -sc-client-db = { version = "0.10.0-dev", features = [ +sc-client-db = { version = "0.10.0-dev", default-features = false, features = [ "test-helpers", ], path = "../../client/db" } sc-consensus = { version = "0.10.0-dev", path = "../../client/consensus/common" } diff --git a/utils/frame/benchmarking-cli/Cargo.toml b/utils/frame/benchmarking-cli/Cargo.toml index c4549f5c5439d..ea26bf0c9261c 100644 --- a/utils/frame/benchmarking-cli/Cargo.toml +++ b/utils/frame/benchmarking-cli/Cargo.toml @@ -39,9 +39,9 @@ frame-benchmarking = { version = "4.0.0-dev", path = "../../../frame/benchmarkin frame-support = { version = "4.0.0-dev", path = "../../../frame/support" } frame-system = { version = "4.0.0-dev", path = "../../../frame/system" } sc-block-builder = { version = "0.10.0-dev", path = "../../../client/block-builder" } -sc-cli = { version = "0.10.0-dev", path = "../../../client/cli" } +sc-cli = { version = "0.10.0-dev", default-features = false, path = "../../../client/cli" } sc-client-api = { version = "4.0.0-dev", path = "../../../client/api" } -sc-client-db = { version = "0.10.0-dev", path = "../../../client/db" } +sc-client-db = { version = "0.10.0-dev", default-features = false, path = "../../../client/db" } sc-executor = { version = "0.10.0-dev", path = "../../../client/executor" } sc-service = { version = "0.10.0-dev", default-features = false, path = "../../../client/service" } sc-sysinfo = { version = "6.0.0-dev", path = "../../../client/sysinfo" } @@ -59,5 +59,6 @@ sp-trie = { version = "6.0.0", path = "../../../primitives/trie" } gethostname = "0.2.3" [features] -default = ["db", "sc-client-db/runtime-benchmarks"] -db = ["sc-client-db/with-kvdb-rocksdb", "sc-client-db/with-parity-db"] +default = ["rocksdb", "runtime-benchmarks"] +runtime-benchmarks = ["sc-client-db/runtime-benchmarks"] +rocksdb = ["sc-cli/rocksdb", "sc-client-db/rocksdb"] From bad21fa521d772e556438fa5b089f9e7edb9c3ba Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 14 Jun 2022 22:43:25 +0200 Subject: [PATCH 341/484] rpc servers: update jsonrpsee to fix host filtering + WS server-side pings (#11661) * bump jsonrpsee to fix #11480 In addition it adds WebSocket server-side pings which is configured to send out pings periodically every 30 seconds. * use released crates.io version * Update Cargo.toml --- Cargo.lock | 40 ++++++++++--------- bin/node-template/node/Cargo.toml | 2 +- bin/node/cli/Cargo.toml | 2 +- bin/node/rpc/Cargo.toml | 2 +- client/beefy/rpc/Cargo.toml | 2 +- client/consensus/babe/rpc/Cargo.toml | 2 +- client/consensus/manual-seal/Cargo.toml | 2 +- client/finality-grandpa/rpc/Cargo.toml | 2 +- client/rpc-api/Cargo.toml | 2 +- client/rpc-servers/Cargo.toml | 2 +- client/rpc-servers/src/lib.rs | 22 +++++----- client/rpc/Cargo.toml | 2 +- client/service/Cargo.toml | 2 +- client/sync-state-rpc/Cargo.toml | 2 +- frame/contracts/rpc/Cargo.toml | 2 +- frame/merkle-mountain-range/rpc/Cargo.toml | 2 +- frame/transaction-payment/rpc/Cargo.toml | 2 +- utils/frame/remote-externalities/Cargo.toml | 2 +- .../rpc/state-trie-migration-rpc/Cargo.toml | 2 +- utils/frame/rpc/support/Cargo.toml | 4 +- utils/frame/rpc/system/Cargo.toml | 2 +- utils/frame/try-runtime/cli/Cargo.toml | 2 +- 22 files changed, 55 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cf56bf0703246..7027cfd44bcf4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3160,9 +3160,9 @@ dependencies = [ [[package]] name = "jsonrpsee" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae63f7fdeb51700b35e9b28bf92e8d233951590968c186ed79510b6c12fa3d9" +checksum = "11e017217fcd18da0a25296d3693153dd19c8a6aadab330b3595285d075385d1" dependencies = [ "jsonrpsee-core", "jsonrpsee-http-server", @@ -3175,9 +3175,9 @@ dependencies = [ [[package]] name = "jsonrpsee-client-transport" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32feb1f2f0b5ce37a03b96a988a6dadccc3f529a2f930356bac93f13c09cf385" +checksum = "ce395539a14d3ad4ec1256fde105abd36a2da25d578a291cabe98f45adfdb111" dependencies = [ "futures-util", "http", @@ -3196,9 +3196,9 @@ dependencies = [ [[package]] name = "jsonrpsee-core" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6b13067b615dd050ced7c19517a52cde490eee2c754d5447ce513f2275f7d" +checksum = "16efcd4477de857d4a2195a45769b2fe9ebb54f3ef5a4221d3b014a4fe33ec0b" dependencies = [ "anyhow", "arrayvec 0.7.1", @@ -3208,8 +3208,10 @@ dependencies = [ "futures-channel", "futures-timer", "futures-util", + "globset", "hyper", "jsonrpsee-types", + "lazy_static", "parking_lot 0.12.0", "rand 0.8.4", "rustc-hash", @@ -3219,32 +3221,31 @@ dependencies = [ "thiserror", "tokio", "tracing", + "unicase", ] [[package]] name = "jsonrpsee-http-server" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b34f1090bdc8f7f14ad8811fc84501867c23a9046ce79d49c0cd929a256c501e" +checksum = "bdd69efeb3ce2cba767f126872f4eeb4624038a29098e75d77608b2b4345ad03" dependencies = [ "futures-channel", "futures-util", - "globset", "hyper", "jsonrpsee-core", "jsonrpsee-types", - "lazy_static", + "serde", "serde_json", "tokio", "tracing", - "unicase", ] [[package]] name = "jsonrpsee-proc-macros" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d8dc7a8b629e371cd5ca9d128883763ae00c5b63158ace4a6a61345726a21b7" +checksum = "874cf3f6a027cebf36cae767feca9aa2e8a8f799880e49eb5540819fcbd8eada" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -3254,9 +3255,9 @@ dependencies = [ [[package]] name = "jsonrpsee-types" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44f1835f131e77cd766b4dcb025873944cb1e479cd5debb639e2dc11f90df24a" +checksum = "3bcf76cd316f5d3ad48138085af1f45e2c58c98e02f0779783dbb034d43f7c86" dependencies = [ "anyhow", "beef", @@ -3268,9 +3269,9 @@ dependencies = [ [[package]] name = "jsonrpsee-ws-client" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d75df866743c9733b3e2f5421e56df2f5b4630f7de39f82c44eaab350604926" +checksum = "ee043cb5dd0d51d3eb93432e998d5bae797691a7b10ec4a325e036bcdb48c48a" dependencies = [ "jsonrpsee-client-transport", "jsonrpsee-core", @@ -3279,9 +3280,9 @@ dependencies = [ [[package]] name = "jsonrpsee-ws-server" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099971913436e7f6b1bc80180d4e5f014ec944660636da45d2f372c23d6308c3" +checksum = "2bd2e4d266774a671f8def3794255b28eddd09b18d76e0b913fa439f34588c0a" dependencies = [ "futures-channel", "futures-util", @@ -3290,6 +3291,7 @@ dependencies = [ "serde_json", "soketto", "tokio", + "tokio-stream", "tokio-util 0.7.1", "tracing", ] diff --git a/bin/node-template/node/Cargo.toml b/bin/node-template/node/Cargo.toml index 8a6b041bbd518..c8e74ea9515ac 100644 --- a/bin/node-template/node/Cargo.toml +++ b/bin/node-template/node/Cargo.toml @@ -42,7 +42,7 @@ frame-system = { version = "4.0.0-dev", path = "../../../frame/system" } pallet-transaction-payment = { version = "4.0.0-dev", default-features = false, path = "../../../frame/transaction-payment" } # These dependencies are used for the node template's RPCs -jsonrpsee = { version = "0.13.0", features = ["server"] } +jsonrpsee = { version = "0.14.0", features = ["server"] } sc-rpc = { version = "4.0.0-dev", path = "../../../client/rpc" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sc-rpc-api = { version = "0.10.0-dev", path = "../../../client/rpc-api" } diff --git a/bin/node/cli/Cargo.toml b/bin/node/cli/Cargo.toml index 8f18aec891e9b..b2e24f0b33189 100644 --- a/bin/node/cli/Cargo.toml +++ b/bin/node/cli/Cargo.toml @@ -37,7 +37,7 @@ crate-type = ["cdylib", "rlib"] clap = { version = "3.1.18", features = ["derive"], optional = true } codec = { package = "parity-scale-codec", version = "3.0.0" } serde = { version = "1.0.136", features = ["derive"] } -jsonrpsee = { version = "0.13.0", features = ["server"] } +jsonrpsee = { version = "0.14.0", features = ["server"] } futures = "0.3.21" hex-literal = "0.3.4" log = "0.4.17" diff --git a/bin/node/rpc/Cargo.toml b/bin/node/rpc/Cargo.toml index ea6e8a5eb9c90..547d0df7a8372 100644 --- a/bin/node/rpc/Cargo.toml +++ b/bin/node/rpc/Cargo.toml @@ -11,7 +11,7 @@ repository = "https://github.com/paritytech/substrate/" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { version = "0.13.0", features = ["server"] } +jsonrpsee = { version = "0.14.0", features = ["server"] } node-primitives = { version = "2.0.0", path = "../primitives" } pallet-contracts-rpc = { version = "4.0.0-dev", path = "../../../frame/contracts/rpc/" } pallet-mmr-rpc = { version = "3.0.0", path = "../../../frame/merkle-mountain-range/rpc/" } diff --git a/client/beefy/rpc/Cargo.toml b/client/beefy/rpc/Cargo.toml index 98596c36f84d2..7d31aea3f6971 100644 --- a/client/beefy/rpc/Cargo.toml +++ b/client/beefy/rpc/Cargo.toml @@ -11,7 +11,7 @@ homepage = "https://substrate.io" [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } futures = "0.3.21" -jsonrpsee = { version = "0.13.0", features = ["server", "macros"] } +jsonrpsee = { version = "0.14.0", features = ["server", "macros"] } log = "0.4" parking_lot = "0.12.0" serde = { version = "1.0.136", features = ["derive"] } diff --git a/client/consensus/babe/rpc/Cargo.toml b/client/consensus/babe/rpc/Cargo.toml index 4fb45d3817036..4c9350735d529 100644 --- a/client/consensus/babe/rpc/Cargo.toml +++ b/client/consensus/babe/rpc/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { version = "0.13.0", features = ["server", "macros"] } +jsonrpsee = { version = "0.14.0", features = ["server", "macros"] } futures = "0.3.21" serde = { version = "1.0.136", features = ["derive"] } thiserror = "1.0" diff --git a/client/consensus/manual-seal/Cargo.toml b/client/consensus/manual-seal/Cargo.toml index dbc7c29c7f300..7aa2232178f22 100644 --- a/client/consensus/manual-seal/Cargo.toml +++ b/client/consensus/manual-seal/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { version = "0.13.0", features = ["server", "macros"] } +jsonrpsee = { version = "0.14.0", features = ["server", "macros"] } assert_matches = "1.3.0" async-trait = "0.1.50" codec = { package = "parity-scale-codec", version = "3.0.0" } diff --git a/client/finality-grandpa/rpc/Cargo.toml b/client/finality-grandpa/rpc/Cargo.toml index e8a3f24006e22..f2d37da058f45 100644 --- a/client/finality-grandpa/rpc/Cargo.toml +++ b/client/finality-grandpa/rpc/Cargo.toml @@ -12,7 +12,7 @@ homepage = "https://substrate.io" [dependencies] finality-grandpa = { version = "0.16.0", features = ["derive-codec"] } futures = "0.3.16" -jsonrpsee = { version = "0.13.0", features = ["server", "macros"] } +jsonrpsee = { version = "0.14.0", features = ["server", "macros"] } log = "0.4.8" parity-scale-codec = { version = "3.0.0", features = ["derive"] } serde = { version = "1.0.105", features = ["derive"] } diff --git a/client/rpc-api/Cargo.toml b/client/rpc-api/Cargo.toml index f63f8ee65fbec..3425ba2b245df 100644 --- a/client/rpc-api/Cargo.toml +++ b/client/rpc-api/Cargo.toml @@ -28,4 +28,4 @@ sp-rpc = { version = "6.0.0", path = "../../primitives/rpc" } sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } sp-version = { version = "5.0.0", path = "../../primitives/version" } -jsonrpsee = { version = "0.13.0", features = ["server", "macros"] } +jsonrpsee = { version = "0.14.0", features = ["server", "macros"] } diff --git a/client/rpc-servers/Cargo.toml b/client/rpc-servers/Cargo.toml index f5cd943b24e09..daaa955839045 100644 --- a/client/rpc-servers/Cargo.toml +++ b/client/rpc-servers/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] futures = "0.3.21" -jsonrpsee = { version = "0.13.0", features = ["server"] } +jsonrpsee = { version = "0.14.0", features = ["server"] } log = "0.4.17" serde_json = "1.0.79" tokio = { version = "1.17.0", features = ["parking_lot"] } diff --git a/client/rpc-servers/src/lib.rs b/client/rpc-servers/src/lib.rs index 78b8e16f8f13d..68b4aa6767348 100644 --- a/client/rpc-servers/src/lib.rs +++ b/client/rpc-servers/src/lib.rs @@ -103,7 +103,7 @@ pub async fn start_http( .max_request_body_size(max_payload_in as u32) .max_response_body_size(max_payload_out as u32) .set_access_control(acl.build()) - .health_api("/health", "system_health") + .health_api("/health", "system_health")? .custom_tokio_runtime(rt); let rpc_api = build_rpc_api(rpc_api); @@ -141,12 +141,23 @@ pub async fn start_ws( let (max_payload_in, max_payload_out, max_connections, max_subs_per_conn) = ws_config.deconstruct(); + let mut acl = AccessControlBuilder::new(); + + if let Some(cors) = cors { + // Whitelist listening address. + // NOTE: set_allowed_hosts will whitelist both ports but only one will used. + acl = acl.set_allowed_hosts(format_allowed_hosts(&addrs[..]))?; + acl = acl.set_allowed_origins(cors)?; + }; + let mut builder = WsServerBuilder::new() .max_request_body_size(max_payload_in) .max_response_body_size(max_payload_out) .max_connections(max_connections) .max_subscriptions_per_connection(max_subs_per_conn) - .custom_tokio_runtime(rt); + .ping_interval(std::time::Duration::from_secs(30)) + .custom_tokio_runtime(rt) + .set_access_control(acl.build()); if let Some(provider) = id_provider { builder = builder.set_id_provider(provider); @@ -154,13 +165,6 @@ pub async fn start_ws( builder = builder.set_id_provider(RandomStringIdProvider::new(16)); }; - if let Some(cors) = cors { - // Whitelist listening address. - // NOTE: set_allowed_hosts will whitelist both ports but only one will used. - builder = builder.set_allowed_hosts(format_allowed_hosts(&addrs[..]))?; - builder = builder.set_allowed_origins(cors)?; - } - let rpc_api = build_rpc_api(rpc_api); let (handle, addr) = if let Some(metrics) = metrics { let middleware = RpcMiddleware::new(metrics, "ws".into()); diff --git a/client/rpc/Cargo.toml b/client/rpc/Cargo.toml index f783ce8a94fb7..e8c657f1a2949 100644 --- a/client/rpc/Cargo.toml +++ b/client/rpc/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.21" hash-db = { version = "0.15.2", default-features = false } -jsonrpsee = { version = "0.13.0", features = ["server"] } +jsonrpsee = { version = "0.14.0", features = ["server"] } lazy_static = { version = "1.4.0", optional = true } log = "0.4.17" parking_lot = "0.12.0" diff --git a/client/service/Cargo.toml b/client/service/Cargo.toml index c1cb9e459041b..ff11dd1344d01 100644 --- a/client/service/Cargo.toml +++ b/client/service/Cargo.toml @@ -22,7 +22,7 @@ wasmtime = ["sc-executor/wasmtime"] test-helpers = [] [dependencies] -jsonrpsee = { version = "0.13.0", features = ["server"] } +jsonrpsee = { version = "0.14.0", features = ["server"] } thiserror = "1.0.30" futures = "0.3.21" rand = "0.7.3" diff --git a/client/sync-state-rpc/Cargo.toml b/client/sync-state-rpc/Cargo.toml index 3a2da48907a36..bd9194092fa3d 100644 --- a/client/sync-state-rpc/Cargo.toml +++ b/client/sync-state-rpc/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } -jsonrpsee = { version = "0.13.0", features = ["server", "macros"] } +jsonrpsee = { version = "0.14.0", features = ["server", "macros"] } serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0.79" thiserror = "1.0.30" diff --git a/frame/contracts/rpc/Cargo.toml b/frame/contracts/rpc/Cargo.toml index c8777d50b90a1..a81abef9f37ca 100644 --- a/frame/contracts/rpc/Cargo.toml +++ b/frame/contracts/rpc/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } -jsonrpsee = { version = "0.13.0", features = ["server", "macros"] } +jsonrpsee = { version = "0.14.0", features = ["server", "macros"] } serde = { version = "1", features = ["derive"] } # Substrate Dependencies diff --git a/frame/merkle-mountain-range/rpc/Cargo.toml b/frame/merkle-mountain-range/rpc/Cargo.toml index 75238e5e329eb..45cb975df277b 100644 --- a/frame/merkle-mountain-range/rpc/Cargo.toml +++ b/frame/merkle-mountain-range/rpc/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } -jsonrpsee = { version = "0.13.0", features = ["server", "macros"] } +jsonrpsee = { version = "0.14.0", features = ["server", "macros"] } serde = { version = "1.0.136", features = ["derive"] } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } diff --git a/frame/transaction-payment/rpc/Cargo.toml b/frame/transaction-payment/rpc/Cargo.toml index f58049a0062ab..31e0972a0d5b5 100644 --- a/frame/transaction-payment/rpc/Cargo.toml +++ b/frame/transaction-payment/rpc/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } -jsonrpsee = { version = "0.13.0", features = ["server", "macros"] } +jsonrpsee = { version = "0.14.0", features = ["server", "macros"] } pallet-transaction-payment-rpc-runtime-api = { version = "4.0.0-dev", path = "./runtime-api" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } diff --git a/utils/frame/remote-externalities/Cargo.toml b/utils/frame/remote-externalities/Cargo.toml index a0adb8edb2d67..a77d090246421 100644 --- a/utils/frame/remote-externalities/Cargo.toml +++ b/utils/frame/remote-externalities/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } env_logger = "0.9" -jsonrpsee = { version = "0.13.0", features = ["ws-client", "macros"] } +jsonrpsee = { version = "0.14.0", features = ["ws-client", "macros"] } log = "0.4.17" serde = "1.0.136" serde_json = "1.0" diff --git a/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml b/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml index f4fbb1acbc0b1..6c6bc5cf327b5 100644 --- a/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml +++ b/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml @@ -25,7 +25,7 @@ sp-state-machine = { path = "../../../../primitives/state-machine" } sp-trie = { path = "../../../../primitives/trie" } trie-db = { version = "0.23.1" } -jsonrpsee = { version = "0.13.0", features = ["server", "macros"] } +jsonrpsee = { version = "0.14.0", features = ["server", "macros"] } # Substrate Dependencies sc-client-api = { version = "4.0.0-dev", path = "../../../../client/api" } diff --git a/utils/frame/rpc/support/Cargo.toml b/utils/frame/rpc/support/Cargo.toml index b19c3f235c7c7..7ea07534e1bdb 100644 --- a/utils/frame/rpc/support/Cargo.toml +++ b/utils/frame/rpc/support/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.21" -jsonrpsee = { version = "0.13.0", features = ["jsonrpsee-types"] } +jsonrpsee = { version = "0.14.0", features = ["jsonrpsee-types"] } serde = "1" frame-support = { version = "4.0.0-dev", path = "../../../../frame/support" } sc-rpc-api = { version = "0.10.0-dev", path = "../../../../client/rpc-api" } @@ -25,6 +25,6 @@ sp-storage = { version = "6.0.0", path = "../../../../primitives/storage" } [dev-dependencies] scale-info = "2.1.1" -jsonrpsee = { version = "0.13.0", features = ["ws-client", "jsonrpsee-types"] } +jsonrpsee = { version = "0.14.0", features = ["ws-client", "jsonrpsee-types"] } tokio = "1.17.0" frame-system = { version = "4.0.0-dev", path = "../../../../frame/system" } diff --git a/utils/frame/rpc/system/Cargo.toml b/utils/frame/rpc/system/Cargo.toml index 5f28c4e16231b..f76944a0fec40 100644 --- a/utils/frame/rpc/system/Cargo.toml +++ b/utils/frame/rpc/system/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] serde_json = "1" codec = { package = "parity-scale-codec", version = "3.0.0" } -jsonrpsee = { version = "0.13.0", features = ["server"] } +jsonrpsee = { version = "0.14.0", features = ["server"] } futures = "0.3.21" log = "0.4.17" frame-system-rpc-runtime-api = { version = "4.0.0-dev", path = "../../../../frame/system/rpc/runtime-api" } diff --git a/utils/frame/try-runtime/cli/Cargo.toml b/utils/frame/try-runtime/cli/Cargo.toml index a357378dc1903..a2d6eba3d8bfc 100644 --- a/utils/frame/try-runtime/cli/Cargo.toml +++ b/utils/frame/try-runtime/cli/Cargo.toml @@ -19,7 +19,7 @@ parity-scale-codec = "3.0.0" serde = "1.0.136" zstd = { version = "0.10.0", default-features = false } remote-externalities = { version = "0.10.0-dev", path = "../../remote-externalities" } -jsonrpsee = { version = "0.13.0", default-features = false, features = ["ws-client"] } +jsonrpsee = { version = "0.14.0", default-features = false, features = ["ws-client"] } sc-chain-spec = { version = "4.0.0-dev", path = "../../../../client/chain-spec" } sc-cli = { version = "0.10.0-dev", path = "../../../../client/cli" } sc-executor = { version = "0.10.0-dev", path = "../../../../client/executor" } From 32cbe2836fc3d09d8ade73ef84cbe6e846c5a749 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Tue, 14 Jun 2022 23:22:16 +0200 Subject: [PATCH 342/484] Include the chain specs instead of trying to open them by path (#11625) This makes it possible to run the tests manually, without them expecting to be run in a special folder etc. --- client/beefy/src/tests.rs | 6 +++--- client/finality-grandpa/src/communication/tests.rs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/beefy/src/tests.rs b/client/beefy/src/tests.rs index 1d035a6a447c2..b5ff27c808908 100644 --- a/client/beefy/src/tests.rs +++ b/client/beefy/src/tests.rs @@ -75,9 +75,9 @@ impl BuildStorage for Genesis { #[test] fn beefy_protocol_name() { - let chain_spec = GenericChainSpec::::from_json_file(std::path::PathBuf::from( - "../chain-spec/res/chain_spec.json", - )) + let chain_spec = GenericChainSpec::::from_json_bytes( + &include_bytes!("../../chain-spec/res/chain_spec.json")[..], + ) .unwrap() .cloned_box(); diff --git a/client/finality-grandpa/src/communication/tests.rs b/client/finality-grandpa/src/communication/tests.rs index e41d21fc0684e..0ec5092a2a047 100644 --- a/client/finality-grandpa/src/communication/tests.rs +++ b/client/finality-grandpa/src/communication/tests.rs @@ -551,9 +551,9 @@ fn local_chain_spec() -> Box { Ok(()) } } - let chain_spec = GenericChainSpec::::from_json_file(std::path::PathBuf::from( - "../chain-spec/res/chain_spec.json", - )) + let chain_spec = GenericChainSpec::::from_json_bytes( + &include_bytes!("../../../chain-spec/res/chain_spec.json")[..], + ) .unwrap(); chain_spec.cloned_box() } From 82c93207a9d6a007a224418017ee7a4713c2ef50 Mon Sep 17 00:00:00 2001 From: Sebastian Kunert Date: Wed, 15 Jun 2022 23:12:26 +0200 Subject: [PATCH 343/484] Remove `without_storage_info` from pallet `transaction-storage` (#11668) * Introduce BoundedVec * Fix typos * Add comments to the bounds * Remove migration * Improve bound value access syntax --- Cargo.lock | 1 + bin/node/runtime/src/lib.rs | 5 +- frame/transaction-storage/Cargo.toml | 1 + frame/transaction-storage/src/benchmarking.rs | 10 +-- frame/transaction-storage/src/lib.rs | 80 ++++++++++--------- frame/transaction-storage/src/mock.rs | 10 ++- 6 files changed, 59 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7027cfd44bcf4..fdcd450454629 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6413,6 +6413,7 @@ dependencies = [ "frame-support", "frame-system", "hex-literal", + "log", "pallet-balances", "parity-scale-codec", "scale-info", diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 6f3df7416681e..8987439db8e94 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1478,6 +1478,10 @@ impl pallet_transaction_storage::Config for Runtime { type Call = Call; type FeeDestination = (); type WeightInfo = pallet_transaction_storage::weights::SubstrateWeight; + type MaxBlockTransactions = + ConstU32<{ pallet_transaction_storage::DEFAULT_MAX_BLOCK_TRANSACTIONS }>; + type MaxTransactionSize = + ConstU32<{ pallet_transaction_storage::DEFAULT_MAX_TRANSACTION_SIZE }>; } impl pallet_whitelist::Config for Runtime { @@ -1674,7 +1678,6 @@ pub type Executive = frame_executive::Executive< frame_system::ChainContext, Runtime, AllPalletsWithSystem, - (), >; /// MMR helper types. diff --git a/frame/transaction-storage/Cargo.toml b/frame/transaction-storage/Cargo.toml index a5195c4f75974..f001ef6acd468 100644 --- a/frame/transaction-storage/Cargo.toml +++ b/frame/transaction-storage/Cargo.toml @@ -26,6 +26,7 @@ sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/ sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } sp-transaction-storage-proof = { version = "4.0.0-dev", default-features = false, path = "../../primitives/transaction-storage-proof" } +log = { version = "0.4.17", default-features = false } [dev-dependencies] sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } diff --git a/frame/transaction-storage/src/benchmarking.rs b/frame/transaction-storage/src/benchmarking.rs index 285b5cba7ad22..83dd37922a31f 100644 --- a/frame/transaction-storage/src/benchmarking.rs +++ b/frame/transaction-storage/src/benchmarking.rs @@ -21,7 +21,7 @@ use super::*; use frame_benchmarking::{benchmarks, whitelisted_caller}; -use frame_support::traits::{Currency, OnFinalize, OnInitialize}; +use frame_support::traits::{Currency, Get, OnFinalize, OnInitialize}; use frame_system::{EventRecord, Pallet as System, RawOrigin}; use sp_runtime::traits::{Bounded, One, Zero}; use sp_std::*; @@ -126,7 +126,7 @@ pub fn run_to_block(n: T::BlockNumber) { benchmarks! { store { - let l in 1 .. MaxTransactionSize::::get(); + let l in 1 .. T::MaxTransactionSize::get(); let caller: T::AccountId = whitelisted_caller(); T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); }: _(RawOrigin::Signed(caller.clone()), vec![0u8; l as usize]) @@ -140,7 +140,7 @@ benchmarks! { T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); TransactionStorage::::store( RawOrigin::Signed(caller.clone()).into(), - vec![0u8; MaxTransactionSize::::get() as usize], + vec![0u8; T::MaxTransactionSize::get() as usize], )?; run_to_block::(1u32.into()); }: _(RawOrigin::Signed(caller.clone()), T::BlockNumber::zero(), 0) @@ -152,10 +152,10 @@ benchmarks! { run_to_block::(1u32.into()); let caller: T::AccountId = whitelisted_caller(); T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); - for _ in 0 .. MaxBlockTransactions::::get() { + for _ in 0 .. T::MaxBlockTransactions::get() { TransactionStorage::::store( RawOrigin::Signed(caller.clone()).into(), - vec![0u8; MaxTransactionSize::::get() as usize], + vec![0u8; T::MaxTransactionSize::get() as usize], )?; } run_to_block::(StoragePeriod::::get() + T::BlockNumber::one()); diff --git a/frame/transaction-storage/src/lib.rs b/frame/transaction-storage/src/lib.rs index a63b31f2f1aac..f16b8f029662b 100644 --- a/frame/transaction-storage/src/lib.rs +++ b/frame/transaction-storage/src/lib.rs @@ -28,7 +28,7 @@ mod mock; #[cfg(test)] mod tests; -use codec::{Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ dispatch::{Dispatchable, GetDispatchInfo}, traits::{Currency, OnUnbalanced, ReservableCurrency}, @@ -57,7 +57,16 @@ pub const DEFAULT_MAX_TRANSACTION_SIZE: u32 = 8 * 1024 * 1024; pub const DEFAULT_MAX_BLOCK_TRANSACTIONS: u32 = 512; /// State data for a stored transaction. -#[derive(Encode, Decode, Clone, sp_runtime::RuntimeDebug, PartialEq, Eq, scale_info::TypeInfo)] +#[derive( + Encode, + Decode, + Clone, + sp_runtime::RuntimeDebug, + PartialEq, + Eq, + scale_info::TypeInfo, + MaxEncodedLen, +)] pub struct TransactionInfo { /// Chunk trie root. chunk_root: ::Output, @@ -95,6 +104,10 @@ pub mod pallet { type FeeDestination: OnUnbalanced>; /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; + /// Maximum number of indexed transactions in the block. + type MaxBlockTransactions: Get; + /// Maximum data set in a single transaction in bytes. + type MaxTransactionSize: Get; } #[pallet::error] @@ -129,7 +142,6 @@ pub mod pallet { #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] - #[pallet::without_storage_info] pub struct Pallet(_); #[pallet::hooks] @@ -180,7 +192,7 @@ pub mod pallet { pub fn store(origin: OriginFor, data: Vec) -> DispatchResult { ensure!(data.len() > 0, Error::::EmptyTransaction); ensure!( - data.len() <= MaxTransactionSize::::get() as usize, + data.len() <= T::MaxTransactionSize::get() as usize, Error::::TransactionTooLarge ); let sender = ensure_signed(origin)?; @@ -198,17 +210,19 @@ pub mod pallet { let mut index = 0; >::mutate(|transactions| { - if transactions.len() + 1 > MaxBlockTransactions::::get() as usize { + if transactions.len() + 1 > T::MaxBlockTransactions::get() as usize { return Err(Error::::TooManyTransactions) } let total_chunks = transactions.last().map_or(0, |t| t.block_chunks) + chunk_count; index = transactions.len() as u32; - transactions.push(TransactionInfo { - chunk_root: root, - size: data.len() as u32, - content_hash: content_hash.into(), - block_chunks: total_chunks, - }); + transactions + .try_push(TransactionInfo { + chunk_root: root, + size: data.len() as u32, + content_hash: content_hash.into(), + block_chunks: total_chunks, + }) + .map_err(|_| Error::::TooManyTransactions)?; Ok(()) })?; Self::deposit_event(Event::Stored { index }); @@ -238,19 +252,20 @@ pub mod pallet { let mut index = 0; >::mutate(|transactions| { - if transactions.len() + 1 > MaxBlockTransactions::::get() as usize { + if transactions.len() + 1 > T::MaxBlockTransactions::get() as usize { return Err(Error::::TooManyTransactions) } let chunks = num_chunks(info.size); let total_chunks = transactions.last().map_or(0, |t| t.block_chunks) + chunks; index = transactions.len() as u32; - transactions.push(TransactionInfo { - chunk_root: info.chunk_root, - size: info.size, - content_hash: info.content_hash, - block_chunks: total_chunks, - }); - Ok(()) + transactions + .try_push(TransactionInfo { + chunk_root: info.chunk_root, + size: info.size, + content_hash: info.content_hash, + block_chunks: total_chunks, + }) + .map_err(|_| Error::::TooManyTransactions) })?; Self::deposit_event(Event::Renewed { index }); Ok(().into()) @@ -324,8 +339,13 @@ pub mod pallet { /// Collection of transaction metadata by block number. #[pallet::storage] #[pallet::getter(fn transaction_roots)] - pub(super) type Transactions = - StorageMap<_, Blake2_128Concat, T::BlockNumber, Vec, OptionQuery>; + pub(super) type Transactions = StorageMap< + _, + Blake2_128Concat, + T::BlockNumber, + BoundedVec, + OptionQuery, + >; /// Count indexed chunks for each block. #[pallet::storage] @@ -342,16 +362,6 @@ pub mod pallet { /// Storage fee per transaction. pub(super) type EntryFee = StorageValue<_, BalanceOf>; - #[pallet::storage] - #[pallet::getter(fn max_transaction_size)] - /// Maximum data set in a single transaction in bytes. - pub(super) type MaxTransactionSize = StorageValue<_, u32, ValueQuery>; - - #[pallet::storage] - #[pallet::getter(fn max_block_transactions)] - /// Maximum number of indexed transactions in the block. - pub(super) type MaxBlockTransactions = StorageValue<_, u32, ValueQuery>; - /// Storage period for data in blocks. Should match `sp_storage_proof::DEFAULT_STORAGE_PERIOD` /// for block authoring. #[pallet::storage] @@ -360,7 +370,7 @@ pub mod pallet { // Intermediates #[pallet::storage] pub(super) type BlockTransactions = - StorageValue<_, Vec, ValueQuery>; + StorageValue<_, BoundedVec, ValueQuery>; /// Was the proof checked in this block? #[pallet::storage] @@ -371,8 +381,6 @@ pub mod pallet { pub byte_fee: BalanceOf, pub entry_fee: BalanceOf, pub storage_period: T::BlockNumber, - pub max_block_transactions: u32, - pub max_transaction_size: u32, } #[cfg(feature = "std")] @@ -382,8 +390,6 @@ pub mod pallet { byte_fee: 10u32.into(), entry_fee: 1000u32.into(), storage_period: sp_transaction_storage_proof::DEFAULT_STORAGE_PERIOD.into(), - max_block_transactions: DEFAULT_MAX_BLOCK_TRANSACTIONS, - max_transaction_size: DEFAULT_MAX_TRANSACTION_SIZE, } } } @@ -393,8 +399,6 @@ pub mod pallet { fn build(&self) { >::put(&self.byte_fee); >::put(&self.entry_fee); - >::put(&self.max_transaction_size); - >::put(&self.max_block_transactions); >::put(&self.storage_period); } } diff --git a/frame/transaction-storage/src/mock.rs b/frame/transaction-storage/src/mock.rs index 753d4baaf00e2..771387ef705be 100644 --- a/frame/transaction-storage/src/mock.rs +++ b/frame/transaction-storage/src/mock.rs @@ -17,8 +17,10 @@ //! Test environment for transaction-storage pallet. -use crate as pallet_transaction_storage; -use crate::TransactionStorageProof; +use crate::{ + self as pallet_transaction_storage, TransactionStorageProof, DEFAULT_MAX_BLOCK_TRANSACTIONS, + DEFAULT_MAX_TRANSACTION_SIZE, +}; use frame_support::traits::{ConstU16, ConstU32, ConstU64, OnFinalize, OnInitialize}; use sp_core::H256; use sp_runtime::{ @@ -90,6 +92,8 @@ impl pallet_transaction_storage::Config for Test { type Currency = Balances; type FeeDestination = (); type WeightInfo = (); + type MaxBlockTransactions = ConstU32<{ DEFAULT_MAX_BLOCK_TRANSACTIONS }>; + type MaxTransactionSize = ConstU32<{ DEFAULT_MAX_TRANSACTION_SIZE }>; } pub fn new_test_ext() -> sp_io::TestExternalities { @@ -102,8 +106,6 @@ pub fn new_test_ext() -> sp_io::TestExternalities { storage_period: 10, byte_fee: 2, entry_fee: 200, - max_block_transactions: crate::DEFAULT_MAX_BLOCK_TRANSACTIONS, - max_transaction_size: crate::DEFAULT_MAX_TRANSACTION_SIZE, }, } .build_storage() From 409439ac1dcb9b8905676ca04501a13e3d51f0c9 Mon Sep 17 00:00:00 2001 From: Georges Date: Thu, 16 Jun 2022 00:30:22 +0100 Subject: [PATCH 344/484] combine iteratons and tolerance in sp-npos-elections API (#11498) * Initial implementation of mms * Some more attempts at `mms` * Functioning `MMS` algorithm implementation. Adding some tests too * More tests and typos fixed. * Adding fuzzer for `mms` (but could not test it on Mac M1) * Missing imports * Fixing rustdoc * More accurate implementation of `mms` * Removing the fuzzer `mms` implementation * Implementing `NposSolver` for `MMS` had to add the `Clone` trait, maybe I could see if I can get rid of it. * Fixing rust docs by adding () to resolve ambiguity * Amending `unwrap` to `expect` removing unneeded `Clone` trait * Removing redundant `mms3.rs` * Implementing `BalancingConfig` and rustdoc changes * Implementing `weight` for `MMS` * Implementing `weight` for `MMS` * Fixing post merge * Initial implementation of mms * Some more attempts at `mms` * Functioning `MMS` algorithm implementation. Adding some tests too * More tests and typos fixed. * Adding fuzzer for `mms` (but could not test it on Mac M1) * Missing imports * Fixing rustdoc * More accurate implementation of `mms` * Removing the fuzzer `mms` implementation * Implementing `NposSolver` for `MMS` had to add the `Clone` trait, maybe I could see if I can get rid of it. * Amending `unwrap` to `expect` removing unneeded `Clone` trait * Fixing rust docs by adding () to resolve ambiguity * Removing redundant `mms3.rs` * Implementing `BalancingConfig` and rustdoc changes * Implementing `weight` for `MMS` * Implementing `weight` for `MMS` * Fixing post merge * Removing left over from rebase * Fixing tests * Removing unneeded import * Removing unneeded functions * Removing useless imports Co-authored-by: kianenigma --- bin/node/runtime/src/lib.rs | 11 ++++---- .../election-provider-multi-phase/src/lib.rs | 4 +-- .../election-provider-multi-phase/src/mock.rs | 6 ++--- frame/election-provider-support/src/lib.rs | 18 +++++-------- .../election-provider-support/src/weights.rs | 5 ++-- primitives/npos-elections/README.md | 4 +-- primitives/npos-elections/fuzzer/Cargo.toml | 2 +- .../npos-elections/fuzzer/src/common.rs | 6 ++--- .../fuzzer/src/phragmen_balancing.rs | 7 ++--- .../fuzzer/src/phragmms_balancing.rs | 7 ++--- primitives/npos-elections/src/balancing.rs | 18 +++++++------ primitives/npos-elections/src/lib.rs | 11 ++++++-- primitives/npos-elections/src/phragmen.rs | 10 +++---- primitives/npos-elections/src/phragmms.rs | 26 +++++++++++-------- primitives/npos-elections/src/tests.rs | 8 +++--- 15 files changed, 78 insertions(+), 65 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 8987439db8e94..bc889e002bf4f 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -24,7 +24,7 @@ use codec::{Decode, Encode, MaxEncodedLen}; use frame_election_provider_support::{ - onchain, ElectionDataProvider, ExtendedBalance, SequentialPhragmen, VoteWeight, + onchain, BalancingConfig, ElectionDataProvider, SequentialPhragmen, VoteWeight, }; use frame_support::{ construct_runtime, @@ -630,10 +630,10 @@ pub const MINER_MAX_ITERATIONS: u32 = 10; /// A source of random balance for NposSolver, which is meant to be run by the OCW election miner. pub struct OffchainRandomBalancing; -impl Get> for OffchainRandomBalancing { - fn get() -> Option<(usize, ExtendedBalance)> { +impl Get> for OffchainRandomBalancing { + fn get() -> Option { use sp_runtime::traits::TrailingZeroInput; - let iters = match MINER_MAX_ITERATIONS { + let iterations = match MINER_MAX_ITERATIONS { 0 => 0, max => { let seed = sp_io::offchain::random_seed(); @@ -644,7 +644,8 @@ impl Get> for OffchainRandomBalancing { }, }; - Some((iters, 0)) + let config = BalancingConfig { iterations, tolerance: 0 }; + Some(config) } } diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 7d8559050f300..2f1f6463df719 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -1808,7 +1808,7 @@ mod tests { }; use frame_election_provider_support::ElectionProvider; use frame_support::{assert_noop, assert_ok}; - use sp_npos_elections::Support; + use sp_npos_elections::{BalancingConfig, Support}; #[test] fn phase_rotation_works() { @@ -2163,7 +2163,7 @@ mod tests { assert_eq!(MultiPhase::current_phase(), Phase::Signed); // set the solution balancing to get the desired score. - crate::mock::Balancing::set(Some((2, 0))); + crate::mock::Balancing::set(Some(BalancingConfig { iterations: 2, tolerance: 0 })); let (solution, _) = MultiPhase::mine_solution().unwrap(); // Default solution's score. diff --git a/frame/election-provider-multi-phase/src/mock.rs b/frame/election-provider-multi-phase/src/mock.rs index bbc2d6d43beee..7eff70b47eba5 100644 --- a/frame/election-provider-multi-phase/src/mock.rs +++ b/frame/election-provider-multi-phase/src/mock.rs @@ -39,8 +39,8 @@ use sp_core::{ H256, }; use sp_npos_elections::{ - assignment_ratio_to_staked_normalized, seq_phragmen, to_supports, ElectionResult, - EvaluateSupport, ExtendedBalance, + assignment_ratio_to_staked_normalized, seq_phragmen, to_supports, BalancingConfig, + ElectionResult, EvaluateSupport, }; use sp_runtime::{ testing::Header, @@ -324,7 +324,7 @@ impl InstantElectionProvider for MockFallback { } parameter_types! { - pub static Balancing: Option<(usize, ExtendedBalance)> = Some((0, 0)); + pub static Balancing: Option = Some( BalancingConfig { iterations: 0, tolerance: 0 } ); } pub struct TestBenchmarkingConfig; diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index 27bf7a37f9622..eee865d0b737b 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -177,8 +177,8 @@ pub use frame_support::{traits::Get, weights::Weight, BoundedVec, RuntimeDebug}; /// Re-export some type as they are used in the interface. pub use sp_arithmetic::PerThing; pub use sp_npos_elections::{ - Assignment, ElectionResult, Error, ExtendedBalance, IdentifierT, PerThing128, Support, - Supports, VoteWeight, + Assignment, BalancingConfig, ElectionResult, Error, ExtendedBalance, IdentifierT, PerThing128, + Support, Supports, VoteWeight, }; pub use traits::NposSolution; @@ -568,11 +568,8 @@ pub struct SequentialPhragmen( sp_std::marker::PhantomData<(AccountId, Accuracy, Balancing)>, ); -impl< - AccountId: IdentifierT, - Accuracy: PerThing128, - Balancing: Get>, - > NposSolver for SequentialPhragmen +impl>> + NposSolver for SequentialPhragmen { type AccountId = AccountId; type Accuracy = Accuracy; @@ -596,11 +593,8 @@ pub struct PhragMMS( sp_std::marker::PhantomData<(AccountId, Accuracy, Balancing)>, ); -impl< - AccountId: IdentifierT, - Accuracy: PerThing128, - Balancing: Get>, - > NposSolver for PhragMMS +impl>> + NposSolver for PhragMMS { type AccountId = AccountId; type Accuracy = Accuracy; diff --git a/frame/election-provider-support/src/weights.rs b/frame/election-provider-support/src/weights.rs index f288ae63bb5da..c603b196519b5 100644 --- a/frame/election-provider-support/src/weights.rs +++ b/frame/election-provider-support/src/weights.rs @@ -15,15 +15,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Autogenerated weights for pallet_election_provider_support_onchain_benchmarking +//! Autogenerated weights for pallet_election_provider_support_benchmarking //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-04-04, STEPS: `1`, REPEAT: 1, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-04-23, STEPS: `1`, REPEAT: 1, LOW RANGE: `[]`, HIGH RANGE: `[]` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: // target/release/substrate // benchmark +// pallet // --chain=dev // --steps=1 // --repeat=1 diff --git a/primitives/npos-elections/README.md b/primitives/npos-elections/README.md index b518e63615fa6..6881fc6418f3a 100644 --- a/primitives/npos-elections/README.md +++ b/primitives/npos-elections/README.md @@ -8,7 +8,7 @@ sub-system. Notable implementation include: it can achieve a constant factor approximation of the maximin problem, similar to that of the MMS algorithm. - [`balance_solution`]: Implements the star balancing algorithm. This iterative process can push - a solution toward being more `balances`, which in turn can increase its score. + a solution toward being more `balanced`, which in turn can increase its score. ### Terminology @@ -46,7 +46,7 @@ let election_result = ElectionResult { winners, assignments }; The `Assignment` field of the election result is voter-major, i.e. it is from the perspective of the voter. The struct that represents the opposite is called a `Support`. This struct is usually -accessed in a map-like manner, i.e. keyed vy voters, therefor it is stored as a mapping called +accessed in a map-like manner, i.e. keyed by voters, therefore it is stored as a mapping called `SupportMap`. Moreover, the support is built from absolute backing values, not ratios like the example above. diff --git a/primitives/npos-elections/fuzzer/Cargo.toml b/primitives/npos-elections/fuzzer/Cargo.toml index 42a80296a4530..a200d5c41ee35 100644 --- a/primitives/npos-elections/fuzzer/Cargo.toml +++ b/primitives/npos-elections/fuzzer/Cargo.toml @@ -36,4 +36,4 @@ path = "src/phragmms_balancing.rs" [[bin]] name = "phragmen_pjr" -path = "src/phragmen_pjr.rs" +path = "src/phragmen_pjr.rs" \ No newline at end of file diff --git a/primitives/npos-elections/fuzzer/src/common.rs b/primitives/npos-elections/fuzzer/src/common.rs index 6b89983c16abe..e5853f28c4929 100644 --- a/primitives/npos-elections/fuzzer/src/common.rs +++ b/primitives/npos-elections/fuzzer/src/common.rs @@ -21,7 +21,7 @@ #![allow(dead_code)] use rand::{self, seq::SliceRandom, Rng, RngCore}; -use sp_npos_elections::{phragmms, seq_phragmen, ElectionResult, VoteWeight}; +use sp_npos_elections::{phragmms, seq_phragmen, BalancingConfig, ElectionResult, VoteWeight}; use sp_runtime::Perbill; use std::collections::{BTreeMap, HashSet}; @@ -38,8 +38,8 @@ pub fn to_range(x: usize, a: usize, b: usize) -> usize { } pub enum ElectionType { - Phragmen(Option<(usize, u128)>), - Phragmms(Option<(usize, u128)>), + Phragmen(Option), + Phragmms(Option), } pub type AccountId = u64; diff --git a/primitives/npos-elections/fuzzer/src/phragmen_balancing.rs b/primitives/npos-elections/fuzzer/src/phragmen_balancing.rs index cacf64e81c8f7..e053f9aa0cddd 100644 --- a/primitives/npos-elections/fuzzer/src/phragmen_balancing.rs +++ b/primitives/npos-elections/fuzzer/src/phragmen_balancing.rs @@ -23,8 +23,8 @@ use common::*; use honggfuzz::fuzz; use rand::{self, SeedableRng}; use sp_npos_elections::{ - assignment_ratio_to_staked_normalized, seq_phragmen, to_supports, ElectionResult, - EvaluateSupport, VoteWeight, + assignment_ratio_to_staked_normalized, seq_phragmen, to_supports, BalancingConfig, + ElectionResult, EvaluateSupport, VoteWeight, }; use sp_runtime::Perbill; @@ -66,8 +66,9 @@ fn main() { }; if iterations > 0 { + let config = BalancingConfig { iterations, tolerance: 0 }; let balanced: ElectionResult = - seq_phragmen(to_elect, candidates, voters, Some((iterations, 0))).unwrap(); + seq_phragmen(to_elect, candidates, voters, Some(config)).unwrap(); let balanced_score = { let staked = diff --git a/primitives/npos-elections/fuzzer/src/phragmms_balancing.rs b/primitives/npos-elections/fuzzer/src/phragmms_balancing.rs index 988889428c9cf..3f114674e29d9 100644 --- a/primitives/npos-elections/fuzzer/src/phragmms_balancing.rs +++ b/primitives/npos-elections/fuzzer/src/phragmms_balancing.rs @@ -23,8 +23,8 @@ use common::*; use honggfuzz::fuzz; use rand::{self, SeedableRng}; use sp_npos_elections::{ - assignment_ratio_to_staked_normalized, phragmms, to_supports, ElectionResult, EvaluateSupport, - VoteWeight, + assignment_ratio_to_staked_normalized, phragmms, to_supports, BalancingConfig, ElectionResult, + EvaluateSupport, VoteWeight, }; use sp_runtime::Perbill; @@ -65,8 +65,9 @@ fn main() { score }; + let config = BalancingConfig { iterations, tolerance: 0 }; let balanced: ElectionResult = - phragmms(to_elect, candidates, voters, Some((iterations, 0))).unwrap(); + phragmms(to_elect, candidates, voters, Some(config)).unwrap(); let balanced_score = { let staked = diff --git a/primitives/npos-elections/src/balancing.rs b/primitives/npos-elections/src/balancing.rs index 54b8ee4bf243e..4a713658ad38f 100644 --- a/primitives/npos-elections/src/balancing.rs +++ b/primitives/npos-elections/src/balancing.rs @@ -26,14 +26,15 @@ //! //! See [`balance`] for more information. -use crate::{Edge, ExtendedBalance, IdentifierT, Voter}; +use crate::{BalancingConfig, Edge, ExtendedBalance, IdentifierT, Voter}; use sp_arithmetic::traits::Zero; use sp_std::prelude::*; /// Balance the weight distribution of a given `voters` at most `iterations` times, or up until the /// point where the biggest difference created per iteration of all stakes is `tolerance`. If this /// is called with `tolerance = 0`, then exactly `iterations` rounds will be executed, except if no -/// change has been made (`difference = 0`). +/// change has been made (`difference = 0`). `tolerance` and `iterations` are part of the +/// [`BalancingConfig`] struct. /// /// In almost all cases, a balanced solution will have a better score than an unbalanced solution, /// yet this is not 100% guaranteed because the first element of a [`crate::ElectionScore`] does not @@ -52,12 +53,13 @@ use sp_std::prelude::*; /// - [A new approach to the maximum flow problem](https://dl.acm.org/doi/10.1145/48014.61051). /// - [Validator election in nominated proof-of-stake](https://arxiv.org/abs/2004.12990) (Appendix /// A.) +/// - [Computing a balanced solution](https://research.web3.foundation/en/latest/polkadot/NPoS/3.%20Balancing.html), +/// which contains the details of the algorithm implementation. pub fn balance( voters: &mut Vec>, - iterations: usize, - tolerance: ExtendedBalance, + config: &BalancingConfig, ) -> usize { - if iterations == 0 { + if config.iterations == 0 { return 0 } @@ -65,14 +67,14 @@ pub fn balance( loop { let mut max_diff = 0; for voter in voters.iter_mut() { - let diff = balance_voter(voter, tolerance); + let diff = balance_voter(voter, config.tolerance); if diff > max_diff { max_diff = diff; } } iter += 1; - if max_diff <= tolerance || iter >= iterations { + if max_diff <= config.tolerance || iter >= config.iterations { break iter } } @@ -158,7 +160,7 @@ pub(crate) fn balance_voter( .get(last_index) .expect( "length of elected_edges is greater than or equal 2; last_index index is at the \ - minimum elected_edges.len() - 1; index is within range; qed", + minimum elected_edges.len() - 1; index is within range; qed", ) .candidate .borrow() diff --git a/primitives/npos-elections/src/lib.rs b/primitives/npos-elections/src/lib.rs index 63b9740b74639..dd2a9bf198f8d 100644 --- a/primitives/npos-elections/src/lib.rs +++ b/primitives/npos-elections/src/lib.rs @@ -62,7 +62,7 @@ //! //! The `Assignment` field of the election result is voter-major, i.e. it is from the perspective of //! the voter. The struct that represents the opposite is called a `Support`. This struct is usually -//! accessed in a map-like manner, i.e. keyed by voters, therefor it is stored as a mapping called +//! accessed in a map-like manner, i.e. keyed by voters, therefore it is stored as a mapping called //! `SupportMap`. //! //! Moreover, the support is built from absolute backing values, not ratios like the example above. @@ -217,6 +217,13 @@ impl sp_std::cmp::PartialOrd for ElectionScore { } } +/// Utility struct to group parameters for the balancing algorithm. +#[derive(Clone, Copy)] +pub struct BalancingConfig { + pub iterations: usize, + pub tolerance: ExtendedBalance, +} + /// A pointer to a candidate struct with interior mutability. pub type CandidatePtr = Rc>>; @@ -320,7 +327,7 @@ impl Voter { /// /// Note that this might create _un-normalized_ assignments, due to accuracy loss of `P`. Call /// site might compensate by calling `normalize()` on the returned `Assignment` as a - /// post-precessing. + /// post-processing. pub fn into_assignment(self) -> Option> { let who = self.who; let budget = self.budget; diff --git a/primitives/npos-elections/src/phragmen.rs b/primitives/npos-elections/src/phragmen.rs index 9b0bfa42215c3..9b50dc36e48fb 100644 --- a/primitives/npos-elections/src/phragmen.rs +++ b/primitives/npos-elections/src/phragmen.rs @@ -21,8 +21,8 @@ //! to the Maximin problem. use crate::{ - balancing, setup_inputs, CandidatePtr, ElectionResult, ExtendedBalance, IdentifierT, - PerThing128, VoteWeight, Voter, + balancing, setup_inputs, BalancingConfig, CandidatePtr, ElectionResult, ExtendedBalance, + IdentifierT, PerThing128, VoteWeight, Voter, }; use sp_arithmetic::{ helpers_128bit::multiply_by_rational, @@ -71,16 +71,16 @@ pub fn seq_phragmen( to_elect: usize, candidates: Vec, voters: Vec<(AccountId, VoteWeight, impl IntoIterator)>, - balancing: Option<(usize, ExtendedBalance)>, + balancing: Option, ) -> Result, crate::Error> { let (candidates, voters) = setup_inputs(candidates, voters); let (candidates, mut voters) = seq_phragmen_core::(to_elect, candidates, voters)?; - if let Some((iterations, tolerance)) = balancing { + if let Some(ref config) = balancing { // NOTE: might create zero-edges, but we will strip them again when we convert voter into // assignment. - let _iters = balancing::balance::(&mut voters, iterations, tolerance); + let _iters = balancing::balance::(&mut voters, config); } let mut winners = candidates diff --git a/primitives/npos-elections/src/phragmms.rs b/primitives/npos-elections/src/phragmms.rs index 5d63517c8e229..3fbbad75e2f8f 100644 --- a/primitives/npos-elections/src/phragmms.rs +++ b/primitives/npos-elections/src/phragmms.rs @@ -22,15 +22,15 @@ //! MMS algorithm. use crate::{ - balance, setup_inputs, CandidatePtr, ElectionResult, ExtendedBalance, IdentifierT, PerThing128, - VoteWeight, Voter, + balance, setup_inputs, BalancingConfig, CandidatePtr, ElectionResult, ExtendedBalance, + IdentifierT, PerThing128, VoteWeight, Voter, }; use sp_arithmetic::{traits::Bounded, PerThing, Rational128}; use sp_std::{prelude::*, rc::Rc}; /// Execute the phragmms method. /// -/// This can be used interchangeably with [`seq-phragmen`] and offers a similar API, namely: +/// This can be used interchangeably with `seq-phragmen` and offers a similar API, namely: /// /// - The resulting edge weight distribution is normalized (thus, safe to use for submission). /// - The accuracy can be configured via the generic type `P`. @@ -45,7 +45,7 @@ pub fn phragmms( to_elect: usize, candidates: Vec, voters: Vec<(AccountId, VoteWeight, impl IntoIterator)>, - balancing: Option<(usize, ExtendedBalance)>, + balancing: Option, ) -> Result, crate::Error> { let (candidates, mut voters) = setup_inputs(candidates, voters); @@ -58,8 +58,8 @@ pub fn phragmms( round_winner.borrow_mut().elected = true; winners.push(round_winner); - if let Some((iterations, tolerance)) = balancing { - balance(&mut voters, iterations, tolerance); + if let Some(ref config) = balancing { + balance(&mut voters, config); } } else { break @@ -275,7 +275,8 @@ mod tests { drop(winner); // balancing makes no difference here but anyhow. - balance(&mut voters, 10, 0); + let config = BalancingConfig { iterations: 10, tolerance: 0 }; + balance(&mut voters, &config); // round 2 let winner = @@ -315,7 +316,7 @@ mod tests { drop(winner); // balancing will improve stuff here. - balance(&mut voters, 10, 0); + balance(&mut voters, &config); assert_eq!( voters @@ -348,8 +349,9 @@ mod tests { let candidates = vec![1, 2, 3]; let voters = vec![(10, 10, vec![1, 2]), (20, 20, vec![1, 3]), (30, 30, vec![2, 3])]; + let config = BalancingConfig { iterations: 2, tolerance: 0 }; let ElectionResult::<_, Perbill> { winners, assignments } = - phragmms(2, candidates, voters, Some((2, 0))).unwrap(); + phragmms(2, candidates, voters, Some(config)).unwrap(); assert_eq!(winners, vec![(3, 30), (2, 30)]); assert_eq!( assignments, @@ -380,8 +382,9 @@ mod tests { (130, 1000, vec![61, 71]), ]; + let config = BalancingConfig { iterations: 2, tolerance: 0 }; let ElectionResult::<_, Perbill> { winners, assignments: _ } = - phragmms(4, candidates, voters, Some((2, 0))).unwrap(); + phragmms(4, candidates, voters, Some(config)).unwrap(); assert_eq!(winners, vec![(11, 3000), (31, 2000), (51, 1500), (61, 1500),]); } @@ -393,8 +396,9 @@ mod tests { // give a bit more to 1 and 3. voters.push((2, u64::MAX, vec![1, 3])); + let config = BalancingConfig { iterations: 2, tolerance: 0 }; let ElectionResult::<_, Perbill> { winners, assignments: _ } = - phragmms(2, candidates, voters, Some((2, 0))).unwrap(); + phragmms(2, candidates, voters, Some(config)).unwrap(); assert_eq!(winners.into_iter().map(|(w, _)| w).collect::>(), vec![1u32, 3]); } } diff --git a/primitives/npos-elections/src/tests.rs b/primitives/npos-elections/src/tests.rs index 1cf5ea8a24920..5b88889201b31 100644 --- a/primitives/npos-elections/src/tests.rs +++ b/primitives/npos-elections/src/tests.rs @@ -19,7 +19,7 @@ use crate::{ balancing, helpers::*, mock::*, seq_phragmen, seq_phragmen_core, setup_inputs, to_support_map, - Assignment, ElectionResult, ExtendedBalance, StakedAssignment, Support, Voter, + Assignment, BalancingConfig, ElectionResult, ExtendedBalance, StakedAssignment, Support, Voter, }; use sp_arithmetic::{PerU16, Perbill, Percent, Permill}; use substrate_test_utils::assert_eq_uvec; @@ -142,7 +142,8 @@ fn balancing_core_works() { let (candidates, voters) = setup_inputs(candidates, voters); let (candidates, mut voters) = seq_phragmen_core(4, candidates, voters).unwrap(); - let iters = balancing::balance::(&mut voters, 4, 0); + let config = BalancingConfig { iterations: 4, tolerance: 0 }; + let iters = balancing::balance::(&mut voters, &config); assert!(iters > 0); @@ -282,6 +283,7 @@ fn phragmen_poc_works_with_balancing() { let voters = vec![(10, vec![1, 2]), (20, vec![1, 3]), (30, vec![2, 3])]; let stake_of = create_stake_of(&[(10, 10), (20, 20), (30, 30)]); + let config = BalancingConfig { iterations: 4, tolerance: 0 }; let ElectionResult::<_, Perbill> { winners, assignments } = seq_phragmen( 2, candidates, @@ -289,7 +291,7 @@ fn phragmen_poc_works_with_balancing() { .iter() .map(|(ref v, ref vs)| (v.clone(), stake_of(v), vs.clone())) .collect::>(), - Some((4, 0)), + Some(config), ) .unwrap(); From 1df1e587022ad5d2015fea86b8dd5f6c913bb2f0 Mon Sep 17 00:00:00 2001 From: ZhiYong Date: Thu, 16 Jun 2022 09:30:49 +0800 Subject: [PATCH 345/484] Add Event to Pallet Transaction Payment (#11618) * add Event to Pallet Transaction Payment * Fix tests in Pallet Balance * Fix tests in Pallet Balance/Executive/Asset-tx-payment. * Fix * fmt * Fix * Fix * Update Cargo.lock * Fix tests in executor * Update frame/transaction-payment/src/lib.rs Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> * update the name of the event and fmt. Co-authored-by: Shawn Tabrizi Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> --- Cargo.lock | 1 + bin/node-template/runtime/src/lib.rs | 1 + bin/node/bench/src/import.rs | 7 ++-- bin/node/executor/Cargo.toml | 1 + bin/node/executor/tests/basic.rs | 33 +++++++++++++++++++ bin/node/runtime/src/lib.rs | 1 + client/db/src/parity_db.rs | 5 +-- frame/balances/src/lib.rs | 1 + frame/balances/src/tests_composite.rs | 3 +- frame/balances/src/tests_local.rs | 3 +- frame/balances/src/tests_reentrancy.rs | 14 ++------ frame/executive/src/lib.rs | 3 +- .../asset-tx-payment/src/tests.rs | 3 +- frame/transaction-payment/src/lib.rs | 25 ++++++++++++-- 14 files changed, 77 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fdcd450454629..815254376e6cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4664,6 +4664,7 @@ dependencies = [ "pallet-contracts", "pallet-im-online", "pallet-timestamp", + "pallet-transaction-payment", "pallet-treasury", "parity-scale-codec", "sc-executor", diff --git a/bin/node-template/runtime/src/lib.rs b/bin/node-template/runtime/src/lib.rs index 0145cacef8f7d..c514cdf6c25fd 100644 --- a/bin/node-template/runtime/src/lib.rs +++ b/bin/node-template/runtime/src/lib.rs @@ -249,6 +249,7 @@ impl pallet_balances::Config for Runtime { } impl pallet_transaction_payment::Config for Runtime { + type Event = Event; type OnChargeTransaction = CurrencyAdapter; type OperationalFeeMultiplier = ConstU8<5>; type WeightToFee = IdentityFee; diff --git a/bin/node/bench/src/import.rs b/bin/node/bench/src/import.rs index faba85468b1fa..b9229fbd5331d 100644 --- a/bin/node/bench/src/import.rs +++ b/bin/node/bench/src/import.rs @@ -135,9 +135,10 @@ impl core::Benchmark for ImportBenchmark { .inspect_state(|| { match self.block_type { BlockType::RandomTransfersKeepAlive => { - // should be 7 per signed extrinsic + 1 per unsigned + // should be 8 per signed extrinsic + 1 per unsigned // we have 1 unsigned and the rest are signed in the block - // those 7 events per signed are: + // those 8 events per signed are: + // - transaction paid for the transaction payment // - withdraw (Balances::Withdraw) for charging the transaction fee // - new account (System::NewAccount) as we always transfer fund to // non-existent account @@ -148,7 +149,7 @@ impl core::Benchmark for ImportBenchmark { // - extrinsic success assert_eq!( node_runtime::System::events().len(), - (self.block.extrinsics.len() - 1) * 7 + 1, + (self.block.extrinsics.len() - 1) * 8 + 1, ); }, BlockType::Noop => { diff --git a/bin/node/executor/Cargo.toml b/bin/node/executor/Cargo.toml index 6b6a1709d5aa0..5fbf59a74fdcd 100644 --- a/bin/node/executor/Cargo.toml +++ b/bin/node/executor/Cargo.toml @@ -36,6 +36,7 @@ pallet-contracts = { version = "4.0.0-dev", path = "../../../frame/contracts" } pallet-im-online = { version = "4.0.0-dev", path = "../../../frame/im-online" } pallet-timestamp = { version = "4.0.0-dev", path = "../../../frame/timestamp" } pallet-treasury = { version = "4.0.0-dev", path = "../../../frame/treasury" } +pallet-transaction-payment = { version = "4.0.0-dev", path = "../../../frame/transaction-payment" } sp-application-crypto = { version = "6.0.0", path = "../../../primitives/application-crypto" } sp-consensus-babe = { version = "0.10.0-dev", path = "../../../primitives/consensus/babe" } sp-externalities = { version = "0.12.0", path = "../../../primitives/externalities" } diff --git a/bin/node/executor/tests/basic.rs b/bin/node/executor/tests/basic.rs index da0f4e6afb319..de2aa6c18369d 100644 --- a/bin/node/executor/tests/basic.rs +++ b/bin/node/executor/tests/basic.rs @@ -417,6 +417,17 @@ fn full_native_block_import_works() { event: Event::Treasury(pallet_treasury::Event::Deposit { value: fees * 8 / 10 }), topics: vec![], }, + EventRecord { + phase: Phase::ApplyExtrinsic(1), + event: Event::TransactionPayment( + pallet_transaction_payment::Event::TransactionFeePaid { + who: alice().into(), + actual_fee: fees, + tip: 0, + }, + ), + topics: vec![], + }, EventRecord { phase: Phase::ApplyExtrinsic(1), event: Event::System(frame_system::Event::ExtrinsicSuccess { @@ -488,6 +499,17 @@ fn full_native_block_import_works() { event: Event::Treasury(pallet_treasury::Event::Deposit { value: fees * 8 / 10 }), topics: vec![], }, + EventRecord { + phase: Phase::ApplyExtrinsic(1), + event: Event::TransactionPayment( + pallet_transaction_payment::Event::TransactionFeePaid { + who: bob().into(), + actual_fee: fees, + tip: 0, + }, + ), + topics: vec![], + }, EventRecord { phase: Phase::ApplyExtrinsic(1), event: Event::System(frame_system::Event::ExtrinsicSuccess { @@ -525,6 +547,17 @@ fn full_native_block_import_works() { event: Event::Treasury(pallet_treasury::Event::Deposit { value: fees * 8 / 10 }), topics: vec![], }, + EventRecord { + phase: Phase::ApplyExtrinsic(2), + event: Event::TransactionPayment( + pallet_transaction_payment::Event::TransactionFeePaid { + who: alice().into(), + actual_fee: fees, + tip: 0, + }, + ), + topics: vec![], + }, EventRecord { phase: Phase::ApplyExtrinsic(2), event: Event::System(frame_system::Event::ExtrinsicSuccess { diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index bc889e002bf4f..addcd0b35a9d9 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -447,6 +447,7 @@ parameter_types! { } impl pallet_transaction_payment::Config for Runtime { + type Event = Event; type OnChargeTransaction = CurrencyAdapter; type OperationalFeeMultiplier = OperationalFeeMultiplier; type WeightToFee = IdentityFee; diff --git a/client/db/src/parity_db.rs b/client/db/src/parity_db.rs index 7ce1d6a683401..4adacbf6f041c 100644 --- a/client/db/src/parity_db.rs +++ b/client/db/src/parity_db.rs @@ -106,7 +106,7 @@ impl> Database for DbAdapter { } return None }, - Change::Reference(col, key) => + Change::Reference(col, key) => { if ref_counted_column(col) { // FIXME accessing value is not strictly needed, optimize this in parity-db. let value = >::get(self, col, key.as_ref()); @@ -116,7 +116,8 @@ impl> Database for DbAdapter { not_ref_counted_column.push(col); } return None - }, + } + }, Change::Release(col, key) => if ref_counted_column(col) { (col as u8, key.as_ref().to_vec(), None) diff --git a/frame/balances/src/lib.rs b/frame/balances/src/lib.rs index 298c2a6963880..683ebce2b1693 100644 --- a/frame/balances/src/lib.rs +++ b/frame/balances/src/lib.rs @@ -158,6 +158,7 @@ mod tests; mod benchmarking; mod tests_composite; mod tests_local; +#[cfg(test)] mod tests_reentrancy; pub mod weights; diff --git a/frame/balances/src/tests_composite.rs b/frame/balances/src/tests_composite.rs index 4a2cc1d91936d..4ab913cf1411a 100644 --- a/frame/balances/src/tests_composite.rs +++ b/frame/balances/src/tests_composite.rs @@ -40,7 +40,7 @@ frame_support::construct_runtime!( { System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage}, + TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event}, } ); @@ -77,6 +77,7 @@ impl frame_system::Config for Test { } impl pallet_transaction_payment::Config for Test { + type Event = Event; type OnChargeTransaction = CurrencyAdapter, ()>; type OperationalFeeMultiplier = ConstU8<5>; type WeightToFee = IdentityFee; diff --git a/frame/balances/src/tests_local.rs b/frame/balances/src/tests_local.rs index cfc7f84ab3a38..6f4c50d90153a 100644 --- a/frame/balances/src/tests_local.rs +++ b/frame/balances/src/tests_local.rs @@ -41,7 +41,7 @@ frame_support::construct_runtime!( { System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage}, + TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event}, } ); @@ -78,6 +78,7 @@ impl frame_system::Config for Test { } impl pallet_transaction_payment::Config for Test { + type Event = Event; type OnChargeTransaction = CurrencyAdapter, ()>; type OperationalFeeMultiplier = ConstU8<5>; type WeightToFee = IdentityFee; diff --git a/frame/balances/src/tests_reentrancy.rs b/frame/balances/src/tests_reentrancy.rs index 7037e9615afd8..4c028840d553c 100644 --- a/frame/balances/src/tests_reentrancy.rs +++ b/frame/balances/src/tests_reentrancy.rs @@ -19,13 +19,11 @@ #![cfg(test)] -use crate::{self as pallet_balances, Config, Pallet}; +use crate::{self as pallet_balances, Config}; use frame_support::{ parameter_types, - traits::{ConstU32, ConstU64, ConstU8, StorageMapShim}, - weights::IdentityFee, + traits::{ConstU32, ConstU64, StorageMapShim}, }; -use pallet_transaction_payment::CurrencyAdapter; use sp_core::H256; use sp_io; use sp_runtime::{testing::Header, traits::IdentityLookup}; @@ -83,14 +81,6 @@ impl frame_system::Config for Test { type MaxConsumers = frame_support::traits::ConstU32<16>; } -impl pallet_transaction_payment::Config for Test { - type OnChargeTransaction = CurrencyAdapter, ()>; - type OperationalFeeMultiplier = ConstU8<5>; - type WeightToFee = IdentityFee; - type LengthToFee = IdentityFee; - type FeeMultiplierUpdate = (); -} - pub struct OnDustRemoval; impl OnUnbalanced> for OnDustRemoval { fn on_nonzero_unbalanced(amount: NegativeImbalance) { diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index 55b7e926a0fad..60bf0a47ca120 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -722,7 +722,7 @@ mod tests { { System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage}, + TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event}, Custom: custom::{Pallet, Call, ValidateUnsigned, Inherent}, } ); @@ -783,6 +783,7 @@ mod tests { pub const TransactionByteFee: Balance = 0; } impl pallet_transaction_payment::Config for Runtime { + type Event = Event; type OnChargeTransaction = CurrencyAdapter; type OperationalFeeMultiplier = ConstU8<5>; type WeightToFee = IdentityFee; diff --git a/frame/transaction-payment/asset-tx-payment/src/tests.rs b/frame/transaction-payment/asset-tx-payment/src/tests.rs index 5b1fa157c3f10..ad5bc3f22e57f 100644 --- a/frame/transaction-payment/asset-tx-payment/src/tests.rs +++ b/frame/transaction-payment/asset-tx-payment/src/tests.rs @@ -48,7 +48,7 @@ frame_support::construct_runtime!( { System: system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage}, + TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event}, Assets: pallet_assets::{Pallet, Call, Storage, Event}, Authorship: pallet_authorship::{Pallet, Call, Storage}, AssetTxPayment: pallet_asset_tx_payment::{Pallet}, @@ -143,6 +143,7 @@ impl WeightToFeeT for TransactionByteFee { } impl pallet_transaction_payment::Config for Runtime { + type Event = Event; type OnChargeTransaction = CurrencyAdapter; type WeightToFee = WeightToFee; type LengthToFee = TransactionByteFee; diff --git a/frame/transaction-payment/src/lib.rs b/frame/transaction-payment/src/lib.rs index d44f8b1b894e1..0f5c0321130be 100644 --- a/frame/transaction-payment/src/lib.rs +++ b/frame/transaction-payment/src/lib.rs @@ -249,6 +249,9 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { + /// The overarching event type. + type Event: From> + IsType<::Event>; + /// Handler for withdrawing, refunding and depositing the transaction fee. /// Transaction fees are withdrawn before the transaction is executed. /// After the transaction was executed the transaction weight can be @@ -321,6 +324,14 @@ pub mod pallet { } } + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// A transaction fee `actual_fee`, of which `tip` was added to the minimum inclusion fee, + /// has been paid by `who`. + TransactionFeePaid { who: T::AccountId, actual_fee: BalanceOf, tip: BalanceOf }, + } + #[pallet::hooks] impl Hooks> for Pallet { fn on_finalize(_: T::BlockNumber) { @@ -734,6 +745,7 @@ where T::OnChargeTransaction::correct_and_deposit_fee( &who, info, post_info, actual_fee, tip, imbalance, )?; + Pallet::::deposit_event(Event::::TransactionFeePaid { who, actual_fee, tip }); } Ok(()) } @@ -790,7 +802,7 @@ mod tests { { System: system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage}, + TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event}, } ); @@ -899,6 +911,7 @@ mod tests { } impl Config for Runtime { + type Event = Event; type OnChargeTransaction = CurrencyAdapter; type OperationalFeeMultiplier = OperationalFeeMultiplier; type WeightToFee = WeightToFee; @@ -1407,8 +1420,14 @@ mod tests { &Ok(()) )); assert_eq!(Balances::total_balance(&user), 0); - // No events for such a scenario - assert_eq!(System::events().len(), 0); + // TransactionFeePaid Event + System::assert_has_event(Event::TransactionPayment( + pallet_transaction_payment::Event::TransactionFeePaid { + who: user, + actual_fee: 0, + tip: 0, + }, + )); }); } From 20574278633d41858b8327d1f03130d2000d8fd4 Mon Sep 17 00:00:00 2001 From: Koute Date: Thu, 16 Jun 2022 20:48:05 +0900 Subject: [PATCH 346/484] Remove `without_storage_info` for the authorship pallet (#11610) * Remove `without_storage_info` for the authorship pallet * Tweak impl bounds style * Use `defensive_proof` instead of `expect` --- frame/authorship/src/lib.rs | 92 ++++++++++++++++++++++++++++++++----- 1 file changed, 81 insertions(+), 11 deletions(-) diff --git a/frame/authorship/src/lib.rs b/frame/authorship/src/lib.rs index 561db20849c2f..8ddccfd9cf939 100644 --- a/frame/authorship/src/lib.rs +++ b/frame/authorship/src/lib.rs @@ -21,17 +21,33 @@ #![cfg_attr(not(feature = "std"), no_std)] -use codec::{Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ dispatch, - traits::{FindAuthor, Get, VerifySeal}, + traits::{Defensive, FindAuthor, Get, VerifySeal}, + BoundedSlice, BoundedVec, }; use sp_authorship::{InherentError, UnclesInherentData, INHERENT_IDENTIFIER}; -use sp_runtime::traits::{Header as HeaderT, One, Saturating}; +use sp_runtime::traits::{Header as HeaderT, One, Saturating, UniqueSaturatedInto}; use sp_std::{collections::btree_set::BTreeSet, prelude::*, result}; const MAX_UNCLES: usize = 10; +struct MaxUncleEntryItems(core::marker::PhantomData); +impl Get for MaxUncleEntryItems { + fn get() -> u32 { + // There can be at most `MAX_UNCLES` of `UncleEntryItem::Uncle` and + // one `UncleEntryItem::InclusionHeight` per one `UncleGenerations`, + // so this gives us `MAX_UNCLES + 1` entries per one generation. + // + // There can be one extra generation worth of uncles (e.g. even + // if `UncleGenerations` is zero the pallet will still hold + // one generation worth of uncles). + let max_generations: u32 = T::UncleGenerations::get().unique_saturated_into(); + (MAX_UNCLES as u32 + 1) * (max_generations + 1) + } +} + pub use pallet::*; /// An event handler for the authorship pallet. There is a dummy implementation @@ -115,7 +131,7 @@ where } } -#[derive(Encode, Decode, sp_runtime::RuntimeDebug, scale_info::TypeInfo)] +#[derive(Encode, Decode, sp_runtime::RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen)] #[cfg_attr(any(feature = "std", test), derive(PartialEq))] enum UncleEntryItem { InclusionHeight(BlockNumber), @@ -156,7 +172,6 @@ pub mod pallet { #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] - #[pallet::without_storage_info] pub struct Pallet(_); #[pallet::hooks] @@ -187,8 +202,11 @@ pub mod pallet { #[pallet::storage] /// Uncles - pub(super) type Uncles = - StorageValue<_, Vec>, ValueQuery>; + pub(super) type Uncles = StorageValue< + _, + BoundedVec, MaxUncleEntryItems>, + ValueQuery, + >; #[pallet::storage] /// Author of current block. @@ -321,7 +339,10 @@ impl Pallet { let now = >::block_number(); let mut uncles = >::get(); - uncles.push(UncleEntryItem::InclusionHeight(now)); + uncles + .try_push(UncleEntryItem::InclusionHeight(now)) + .defensive_proof("the list of uncles accepted per generation is bounded, and the number of generations is bounded, so pushing a new element will always succeed") + .map_err(|_| Error::::TooManyUncles)?; let mut acc: >::Accumulator = Default::default(); @@ -336,7 +357,9 @@ impl Pallet { if let Some(author) = maybe_author.clone() { T::EventHandler::note_uncle(author, now - *uncle.number()); } - uncles.push(UncleEntryItem::Uncle(hash, maybe_author)); + uncles.try_push(UncleEntryItem::Uncle(hash, maybe_author)) + .defensive_proof("the list of uncles accepted per generation is bounded, and the number of generations is bounded, so pushing a new element will always succeed") + .map_err(|_| Error::::TooManyUncles)?; } >::put(&uncles); @@ -397,8 +420,11 @@ impl Pallet { UncleEntryItem::InclusionHeight(height) => height < &minimum_height, }); let prune_index = prune_entries.count(); + let pruned_uncles = + >>::try_from(&uncles[prune_index..]) + .expect("after pruning we can't end up with more uncles than we started with"); - >::put(&uncles[prune_index..]); + >::put(pruned_uncles); } } @@ -408,7 +434,7 @@ mod tests { use crate as pallet_authorship; use frame_support::{ parameter_types, - traits::{ConstU32, ConstU64}, + traits::{ConstU32, ConstU64, OnFinalize, OnInitialize}, ConsensusEngineId, }; use sp_core::H256; @@ -553,6 +579,7 @@ mod tests { InclusionHeight(3u64), Uncle(hash, None), ]; + let uncles = BoundedVec::try_from(uncles).unwrap(); ::Uncles::put(uncles); Authorship::prune_old_uncles(3); @@ -683,6 +710,49 @@ mod tests { }); } + #[test] + fn maximum_bound() { + new_test_ext().execute_with(|| { + let mut max_item_count = 0; + + let mut author_counter = 0; + let mut current_depth = 1; + let mut parent_hash: H256 = [1; 32].into(); + let mut uncles = vec![]; + + // We deliberately run this for more generations than the limit + // so that we can get the `Uncles` to hit its cap. + for _ in 0..<::UncleGenerations as Get>::get() + 3 { + let new_uncles: Vec<_> = (0..MAX_UNCLES) + .map(|_| { + System::reset_events(); + System::initialize(¤t_depth, &parent_hash, &Default::default()); + // Increment the author on every block to make sure the hash's always + // different. + author_counter += 1; + seal_header(System::finalize(), author_counter) + }) + .collect(); + + author_counter += 1; + System::reset_events(); + System::initialize(¤t_depth, &parent_hash, &Default::default()); + Authorship::on_initialize(current_depth); + Authorship::set_uncles(Origin::none(), uncles).unwrap(); + Authorship::on_finalize(current_depth); + max_item_count = + std::cmp::max(max_item_count, ::Uncles::get().len()); + + let new_parent = seal_header(System::finalize(), author_counter); + parent_hash = new_parent.hash(); + uncles = new_uncles; + current_depth += 1; + } + + assert_eq!(max_item_count, MaxUncleEntryItems::::get() as usize); + }); + } + #[test] fn sets_author_lazily() { new_test_ext().execute_with(|| { From 8cb22803869b74f2b1cdbe6daa598b7f3da29986 Mon Sep 17 00:00:00 2001 From: Gavin Wood Date: Thu, 16 Jun 2022 17:13:17 +0100 Subject: [PATCH 347/484] MEL: Origin, Referenda, ConvictionVoting (#11631) * Referenda & CV pallets ready * Fix build * Add mel_bound for Voting and Casting types * Add mel_bound on Tally * Add mel_bound on another Tally * Add mel_bound for pallet_collective::RawOrigin Co-authored-by: Keith Yeung --- Cargo.lock | 8 +++---- frame/collective/src/lib.rs | 5 ++-- frame/conviction-voting/Cargo.toml | 3 ++- frame/conviction-voting/src/lib.rs | 23 +++++++++++++++---- frame/conviction-voting/src/types.rs | 1 + frame/conviction-voting/src/vote.rs | 5 ++++ frame/ranked-collective/src/lib.rs | 1 + frame/referenda/Cargo.toml | 2 +- frame/referenda/src/lib.rs | 4 ++-- .../src/construct_runtime/expand/origin.rs | 2 +- frame/support/src/dispatch.rs | 10 +++++--- frame/support/src/traits.rs | 3 ++- frame/support/src/traits/dispatch.rs | 7 +++++- frame/support/src/traits/schedule.rs | 8 +++---- frame/support/src/traits/voting.rs | 7 ++++++ frame/support/test/tests/construct_runtime.rs | 17 ++++++++++---- frame/support/test/tests/instance.rs | 10 +++++--- frame/support/test/tests/origin.rs | 10 ++++++-- frame/support/test/tests/pallet.rs | 9 +++++++- frame/support/test/tests/pallet_instance.rs | 2 ++ primitives/core/src/lib.rs | 4 ++-- test-utils/runtime/src/lib.rs | 4 ++-- 22 files changed, 106 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 815254376e6cf..84443396e0d06 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6534,9 +6534,9 @@ dependencies = [ [[package]] name = "parity-scale-codec" -version = "3.0.0" +version = "3.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a7f3fcf5e45fc28b84dcdab6b983e77f197ec01f325a33f404ba6855afd1070" +checksum = "04bc9583b5e01cc8c70d89acc9af14ef9b1c29ee3a0074b2a9eea8c0fa396690" dependencies = [ "arrayvec 0.7.1", "bitvec", @@ -6548,9 +6548,9 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "3.0.0" +version = "3.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6e626dc84025ff56bf1476ed0e30d10c84d7f89a475ef46ebabee1095a8fba" +checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" dependencies = [ "proc-macro-crate", "proc-macro2", diff --git a/frame/collective/src/lib.rs b/frame/collective/src/lib.rs index bc6d9ab3310e0..0323be1382392 100644 --- a/frame/collective/src/lib.rs +++ b/frame/collective/src/lib.rs @@ -48,7 +48,7 @@ use sp_runtime::{traits::Hash, RuntimeDebug}; use sp_std::{marker::PhantomData, prelude::*, result}; use frame_support::{ - codec::{Decode, Encode}, + codec::{Decode, Encode, MaxEncodedLen}, dispatch::{DispatchError, DispatchResultWithPostInfo, Dispatchable, PostDispatchInfo}, ensure, traits::{ @@ -124,8 +124,9 @@ impl DefaultVote for MoreThanMajorityThenPrimeDefaultVote { } /// Origin for the collective module. -#[derive(PartialEq, Eq, Clone, RuntimeDebug, Encode, Decode, TypeInfo)] +#[derive(PartialEq, Eq, Clone, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(I))] +#[codec(mel_bound(AccountId: MaxEncodedLen))] pub enum RawOrigin { /// It has been condoned by a given number of members of the collective from a given total. Members(MemberCount, MemberCount), diff --git a/frame/conviction-voting/Cargo.toml b/frame/conviction-voting/Cargo.toml index ff6cfcc3efdee..ab6d04d199bc6 100644 --- a/frame/conviction-voting/Cargo.toml +++ b/frame/conviction-voting/Cargo.toml @@ -14,8 +14,9 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] assert_matches = "1.3.0" -codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.0.3", default-features = false, features = [ "derive", + "max-encoded-len", ] } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } serde = { version = "1.0.136", features = ["derive"], optional = true } diff --git a/frame/conviction-voting/src/lib.rs b/frame/conviction-voting/src/lib.rs index 58ac76dac3a3e..54fc1156d1f47 100644 --- a/frame/conviction-voting/src/lib.rs +++ b/frame/conviction-voting/src/lib.rs @@ -87,12 +87,11 @@ type ClassOf = <>::Polls as Polling>>::C #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::pallet_prelude::*; + use frame_support::{pallet_prelude::*, traits::ClassCountOf}; use frame_system::pallet_prelude::*; #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] - #[pallet::without_storage_info] pub struct Pallet(_); #[pallet::config] @@ -154,7 +153,7 @@ pub mod pallet { _, Twox64Concat, T::AccountId, - Vec<(ClassOf, BalanceOf)>, + BoundedVec<(ClassOf, BalanceOf), ClassCountOf>>, ValueQuery, >; @@ -616,7 +615,15 @@ impl, I: 'static> Pallet { ClassLocksFor::::mutate(who, |locks| { match locks.iter().position(|x| &x.0 == class) { Some(i) => locks[i].1 = locks[i].1.max(amount), - None => locks.push((class.clone(), amount)), + None => { + let ok = locks.try_push((class.clone(), amount)).is_ok(); + debug_assert!( + ok, + "Vec bounded by number of classes; \ + all items in Vec associated with a unique class; \ + qed" + ); + }, } }); T::Currency::extend_lock(CONVICTION_VOTING_ID, who, amount, WithdrawReasons::TRANSFER); @@ -632,7 +639,13 @@ impl, I: 'static> Pallet { let lock_needed = ClassLocksFor::::mutate(who, |locks| { locks.retain(|x| &x.0 != class); if !class_lock_needed.is_zero() { - locks.push((class.clone(), class_lock_needed)); + let ok = locks.try_push((class.clone(), class_lock_needed)).is_ok(); + debug_assert!( + ok, + "Vec bounded by number of classes; \ + all items in Vec associated with a unique class; \ + qed" + ); } locks.iter().map(|x| x.1).max().unwrap_or(Zero::zero()) }); diff --git a/frame/conviction-voting/src/types.rs b/frame/conviction-voting/src/types.rs index 2469009855c5f..8f4f3697e9766 100644 --- a/frame/conviction-voting/src/types.rs +++ b/frame/conviction-voting/src/types.rs @@ -43,6 +43,7 @@ use crate::{AccountVote, Conviction, Vote}; MaxEncodedLen, )] #[scale_info(skip_type_params(Total))] +#[codec(mel_bound(Votes: MaxEncodedLen))] pub struct Tally { /// The number of aye votes, expressed in terms of post-conviction lock-vote. pub ayes: Votes, diff --git a/frame/conviction-voting/src/vote.rs b/frame/conviction-voting/src/vote.rs index e608a6dcfd01b..a0b17ab4de39d 100644 --- a/frame/conviction-voting/src/vote.rs +++ b/frame/conviction-voting/src/vote.rs @@ -162,6 +162,7 @@ pub struct Delegating { /// Information concerning the direct vote-casting of some voting power. #[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(MaxVotes))] +#[codec(mel_bound(Balance: MaxEncodedLen, BlockNumber: MaxEncodedLen, PollIndex: MaxEncodedLen))] pub struct Casting where MaxVotes: Get, @@ -177,6 +178,10 @@ where /// An indicator for what an account is doing; it can either be delegating or voting. #[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(MaxVotes))] +#[codec(mel_bound( + Balance: MaxEncodedLen, AccountId: MaxEncodedLen, BlockNumber: MaxEncodedLen, + PollIndex: MaxEncodedLen, +))] pub enum Voting where MaxVotes: Get, diff --git a/frame/ranked-collective/src/lib.rs b/frame/ranked-collective/src/lib.rs index 60df32dcb3fa3..7ea43a9017445 100644 --- a/frame/ranked-collective/src/lib.rs +++ b/frame/ranked-collective/src/lib.rs @@ -86,6 +86,7 @@ pub type Votes = u32; MaxEncodedLen, )] #[scale_info(skip_type_params(M))] +#[codec(mel_bound())] pub struct Tally { bare_ayes: MemberIndex, ayes: Votes, diff --git a/frame/referenda/Cargo.toml b/frame/referenda/Cargo.toml index ef3d5fe5a8e06..508f5a5ef8688 100644 --- a/frame/referenda/Cargo.toml +++ b/frame/referenda/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] assert_matches = { version = "1.5", optional = true } -codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.0.3", default-features = false, features = [ "derive", ] } scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index ab8c3ad0efa6a..15c5562d64c84 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -118,7 +118,6 @@ pub mod pallet { #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] - #[pallet::without_storage_info] pub struct Pallet(_); #[pallet::config] @@ -159,7 +158,8 @@ pub mod pallet { + Codec + Eq + Debug - + TypeInfo; + + TypeInfo + + MaxEncodedLen; // Constants /// The minimum amount to be used as a deposit for a public referendum proposal. diff --git a/frame/support/procedural/src/construct_runtime/expand/origin.rs b/frame/support/procedural/src/construct_runtime/expand/origin.rs index d347818d65c38..46f08832f0bb4 100644 --- a/frame/support/procedural/src/construct_runtime/expand/origin.rs +++ b/frame/support/procedural/src/construct_runtime/expand/origin.rs @@ -200,7 +200,7 @@ pub fn expand_outer_origin( #[derive( Clone, PartialEq, Eq, #scrate::RuntimeDebug, #scrate::codec::Encode, - #scrate::codec::Decode, #scrate::scale_info::TypeInfo, + #scrate::codec::Decode, #scrate::scale_info::TypeInfo, #scrate::codec::MaxEncodedLen, )] #[allow(non_camel_case_types)] pub enum OriginCaller { diff --git a/frame/support/src/dispatch.rs b/frame/support/src/dispatch.rs index d399340f0470e..c175998956ff2 100644 --- a/frame/support/src/dispatch.rs +++ b/frame/support/src/dispatch.rs @@ -19,7 +19,9 @@ //! generating values representing lazy module function calls. pub use crate::{ - codec::{Codec, Decode, Encode, EncodeAsRef, EncodeLike, HasCompact, Input, Output}, + codec::{ + Codec, Decode, Encode, EncodeAsRef, EncodeLike, HasCompact, Input, MaxEncodedLen, Output, + }, scale_info::TypeInfo, sp_std::{ fmt, marker, @@ -63,7 +65,7 @@ pub trait Callable { pub type CallableCallFor = >::Call; /// Origin for the System pallet. -#[derive(PartialEq, Eq, Clone, RuntimeDebug, Encode, Decode, TypeInfo)] +#[derive(PartialEq, Eq, Clone, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)] pub enum RawOrigin { /// The system itself ordained this dispatch to happen: this is the highest privilege level. Root, @@ -2698,7 +2700,9 @@ mod tests { } } - #[derive(TypeInfo, crate::RuntimeDebug, Eq, PartialEq, Clone, Encode, Decode)] + #[derive( + TypeInfo, crate::RuntimeDebug, Eq, PartialEq, Clone, Encode, Decode, MaxEncodedLen, + )] pub struct OuterOrigin; impl From::AccountId>> for OuterOrigin { diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index 53bdd219aa3ee..db8c8da4f869b 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -103,5 +103,6 @@ pub use dispatch::{ mod voting; pub use voting::{ - CurrencyToVote, PollStatus, Polling, SaturatingCurrencyToVote, U128CurrencyToVote, VoteTally, + ClassCountOf, CurrencyToVote, PollStatus, Polling, SaturatingCurrencyToVote, + U128CurrencyToVote, VoteTally, }; diff --git a/frame/support/src/traits/dispatch.rs b/frame/support/src/traits/dispatch.rs index 9bfb0145009eb..7e97be12e449e 100644 --- a/frame/support/src/traits/dispatch.rs +++ b/frame/support/src/traits/dispatch.rs @@ -18,6 +18,7 @@ //! Traits for dealing with dispatching calls and the origin from which they are dispatched. use crate::dispatch::{DispatchResultWithPostInfo, Parameter, RawOrigin}; +use codec::MaxEncodedLen; use sp_runtime::{ traits::{BadOrigin, Member, Morph, TryMorph}, Either, @@ -258,7 +259,11 @@ pub trait OriginTrait: Sized { type Call; /// The caller origin, overarching type of all pallets origins. - type PalletsOrigin: Parameter + Member + Into + From>; + type PalletsOrigin: Parameter + + Member + + Into + + From> + + MaxEncodedLen; /// The AccountId used across the system. type AccountId; diff --git a/frame/support/src/traits/schedule.rs b/frame/support/src/traits/schedule.rs index c2d0d4bc3b81f..39ebbb78321d6 100644 --- a/frame/support/src/traits/schedule.rs +++ b/frame/support/src/traits/schedule.rs @@ -134,7 +134,7 @@ pub mod v1 { /// A type that can be used as a scheduler. pub trait Anon { /// An address which can be used for removing a scheduled task. - type Address: Codec + Clone + Eq + EncodeLike + Debug + TypeInfo; + type Address: Codec + Clone + Eq + EncodeLike + Debug + TypeInfo + MaxEncodedLen; /// Schedule a dispatch to happen at the beginning of some block in the future. /// @@ -179,7 +179,7 @@ pub mod v1 { /// A type that can be used as a scheduler. pub trait Named { /// An address which can be used for removing a scheduled task. - type Address: Codec + Clone + Eq + EncodeLike + sp_std::fmt::Debug; + type Address: Codec + Clone + Eq + EncodeLike + sp_std::fmt::Debug + MaxEncodedLen; /// Schedule a dispatch to happen at the beginning of some block in the future. /// @@ -289,7 +289,7 @@ pub mod v2 { /// A type that can be used as a scheduler. pub trait Anon { /// An address which can be used for removing a scheduled task. - type Address: Codec + Clone + Eq + EncodeLike + Debug + TypeInfo; + type Address: Codec + Clone + Eq + EncodeLike + Debug + TypeInfo + MaxEncodedLen; /// A means of expressing a call by the hash of its encoded data. type Hash; @@ -336,7 +336,7 @@ pub mod v2 { /// A type that can be used as a scheduler. pub trait Named { /// An address which can be used for removing a scheduled task. - type Address: Codec + Clone + Eq + EncodeLike + sp_std::fmt::Debug; + type Address: Codec + Clone + Eq + EncodeLike + sp_std::fmt::Debug + MaxEncodedLen; /// A means of expressing a call by the hash of its encoded data. type Hash; diff --git a/frame/support/src/traits/voting.rs b/frame/support/src/traits/voting.rs index 7a3ff87e5940e..b5e7d27073895 100644 --- a/frame/support/src/traits/voting.rs +++ b/frame/support/src/traits/voting.rs @@ -122,6 +122,13 @@ impl PollStatus { } } +pub struct ClassCountOf(sp_std::marker::PhantomData<(P, T)>); +impl> sp_runtime::traits::Get for ClassCountOf { + fn get() -> u32 { + P::classes().len() as u32 + } +} + pub trait Polling { type Index: Parameter + Member + Ord + PartialOrd + Copy + HasCompact + MaxEncodedLen; type Votes: Parameter + Member + Ord + PartialOrd + Copy + HasCompact + MaxEncodedLen; diff --git a/frame/support/test/tests/construct_runtime.rs b/frame/support/test/tests/construct_runtime.rs index 804deb08919a4..63747a9d560dc 100644 --- a/frame/support/test/tests/construct_runtime.rs +++ b/frame/support/test/tests/construct_runtime.rs @@ -21,6 +21,7 @@ #![recursion_limit = "128"] +use codec::MaxEncodedLen; use frame_support::traits::{CrateVersion, PalletInfo as _}; use scale_info::TypeInfo; use sp_core::{sr25519, H256}; @@ -55,7 +56,9 @@ mod module1 { } } - #[derive(Clone, PartialEq, Eq, Debug, codec::Encode, codec::Decode, TypeInfo)] + #[derive( + Clone, PartialEq, Eq, Debug, codec::Encode, codec::Decode, TypeInfo, MaxEncodedLen, + )] pub struct Origin(pub core::marker::PhantomData<(T, I)>); frame_support::decl_event! { @@ -97,7 +100,9 @@ mod module2 { } } - #[derive(Clone, PartialEq, Eq, Debug, codec::Encode, codec::Decode, TypeInfo)] + #[derive( + Clone, PartialEq, Eq, Debug, codec::Encode, codec::Decode, TypeInfo, MaxEncodedLen, + )] pub struct Origin; frame_support::decl_event! { @@ -140,7 +145,9 @@ mod nested { } } - #[derive(Clone, PartialEq, Eq, Debug, codec::Encode, codec::Decode, TypeInfo)] + #[derive( + Clone, PartialEq, Eq, Debug, codec::Encode, codec::Decode, TypeInfo, MaxEncodedLen, + )] pub struct Origin; frame_support::decl_event! { @@ -196,7 +203,9 @@ pub mod module3 { } } - #[derive(Clone, PartialEq, Eq, Debug, codec::Encode, codec::Decode, TypeInfo)] + #[derive( + Clone, PartialEq, Eq, Debug, codec::Encode, codec::Decode, TypeInfo, MaxEncodedLen, + )] pub struct Origin(pub core::marker::PhantomData); frame_support::decl_event! { diff --git a/frame/support/test/tests/instance.rs b/frame/support/test/tests/instance.rs index 2fe8d0e0f22f5..75a96f628245a 100644 --- a/frame/support/test/tests/instance.rs +++ b/frame/support/test/tests/instance.rs @@ -17,7 +17,7 @@ #![recursion_limit = "128"] -use codec::{Codec, Decode, Encode, EncodeLike}; +use codec::{Codec, Decode, Encode, EncodeLike, MaxEncodedLen}; use frame_support::{ inherent::{InherentData, InherentIdentifier, MakeFatalError, ProvideInherent}, metadata::{ @@ -108,7 +108,9 @@ mod module1 { } } - #[derive(PartialEq, Eq, Clone, sp_runtime::RuntimeDebug, Encode, Decode, TypeInfo)] + #[derive( + PartialEq, Eq, Clone, sp_runtime::RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen, + )] pub enum Origin, I> where T::BlockNumber: From, @@ -181,7 +183,9 @@ mod module2 { } } - #[derive(PartialEq, Eq, Clone, sp_runtime::RuntimeDebug, Encode, Decode, TypeInfo)] + #[derive( + PartialEq, Eq, Clone, sp_runtime::RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen, + )] pub enum Origin, I = DefaultInstance> { Members(u32), _Phantom(std::marker::PhantomData<(T, I)>), diff --git a/frame/support/test/tests/origin.rs b/frame/support/test/tests/origin.rs index 1def44c15b48f..cff531ff2e529 100644 --- a/frame/support/test/tests/origin.rs +++ b/frame/support/test/tests/origin.rs @@ -19,6 +19,7 @@ #![recursion_limit = "128"] +use codec::MaxEncodedLen; use frame_support::traits::{Contains, OriginTrait}; use scale_info::TypeInfo; use sp_core::{sr25519, H256}; @@ -30,6 +31,7 @@ mod nested { use super::*; pub mod module { + use super::*; pub trait Config: system::Config {} @@ -45,7 +47,9 @@ mod nested { } } - #[derive(Clone, PartialEq, Eq, Debug, codec::Encode, codec::Decode, TypeInfo)] + #[derive( + Clone, PartialEq, Eq, Debug, codec::Encode, codec::Decode, TypeInfo, MaxEncodedLen, + )] pub struct Origin; frame_support::decl_event! { @@ -101,7 +105,9 @@ pub mod module { } } - #[derive(Clone, PartialEq, Eq, Debug, codec::Encode, codec::Decode, TypeInfo)] + #[derive( + Clone, PartialEq, Eq, Debug, codec::Encode, codec::Decode, TypeInfo, MaxEncodedLen, + )] pub struct Origin(pub core::marker::PhantomData); frame_support::decl_event! { diff --git a/frame/support/test/tests/pallet.rs b/frame/support/test/tests/pallet.rs index b81941c45ff46..6b72327eb4989 100644 --- a/frame/support/test/tests/pallet.rs +++ b/frame/support/test/tests/pallet.rs @@ -359,7 +359,14 @@ pub mod pallet { #[pallet::origin] #[derive( - EqNoBound, RuntimeDebugNoBound, CloneNoBound, PartialEqNoBound, Encode, Decode, TypeInfo, + EqNoBound, + RuntimeDebugNoBound, + CloneNoBound, + PartialEqNoBound, + Encode, + Decode, + TypeInfo, + MaxEncodedLen, )] pub struct Origin(PhantomData); diff --git a/frame/support/test/tests/pallet_instance.rs b/frame/support/test/tests/pallet_instance.rs index d716637f82895..360a73e5ea2a3 100644 --- a/frame/support/test/tests/pallet_instance.rs +++ b/frame/support/test/tests/pallet_instance.rs @@ -30,6 +30,7 @@ use sp_runtime::{DispatchError, ModuleError}; #[frame_support::pallet] pub mod pallet { + use codec::MaxEncodedLen; use frame_support::{pallet_prelude::*, scale_info}; use frame_system::pallet_prelude::*; use sp_std::any::TypeId; @@ -164,6 +165,7 @@ pub mod pallet { Encode, Decode, scale_info::TypeInfo, + MaxEncodedLen, )] #[scale_info(skip_type_params(T, I))] pub struct Origin(PhantomData<(T, I)>); diff --git a/primitives/core/src/lib.rs b/primitives/core/src/lib.rs index 0709b615cf0d4..ab3334f9e4f5a 100644 --- a/primitives/core/src/lib.rs +++ b/primitives/core/src/lib.rs @@ -32,7 +32,7 @@ macro_rules! map { } #[doc(hidden)] -pub use codec::{Decode, Encode}; +pub use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; #[cfg(feature = "std")] pub use serde; @@ -418,7 +418,7 @@ pub fn to_substrate_wasm_fn_return_value(value: &impl Encode) -> u64 { /// The void type - it cannot exist. // Oh rust, you crack me up... -#[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)] +#[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub enum Void {} /// Macro for creating `Maybe*` marker traits. diff --git a/test-utils/runtime/src/lib.rs b/test-utils/runtime/src/lib.rs index 97e060cb2a9bd..555f29e5991f7 100644 --- a/test-utils/runtime/src/lib.rs +++ b/test-utils/runtime/src/lib.rs @@ -23,7 +23,7 @@ pub mod genesismap; pub mod system; -use codec::{Decode, Encode, Error, Input}; +use codec::{Decode, Encode, Error, Input, MaxEncodedLen}; use scale_info::TypeInfo; use sp_std::{marker::PhantomData, prelude::*}; @@ -439,7 +439,7 @@ impl GetRuntimeBlockType for Runtime { type RuntimeBlock = Block; } -#[derive(Clone, RuntimeDebug, Encode, Decode, PartialEq, Eq, TypeInfo)] +#[derive(Clone, RuntimeDebug, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] pub struct Origin; impl From> for Origin { From 185284d84a8ea71db94a146be97b51eac5af8bb2 Mon Sep 17 00:00:00 2001 From: Boluwatife Bakre Date: Fri, 17 Jun 2022 04:57:29 +0100 Subject: [PATCH 348/484] Remove multiply_by_rational (#11598) * Removed multiply_by_rational Replaced with multiply_by_rational_with_rounding * fixes * Test Fixes * nightly fmt * Test Fix * Fixed fuzzer. --- primitives/arithmetic/fuzzer/Cargo.toml | 4 +- ... => multiply_by_rational_with_rounding.rs} | 16 +- primitives/arithmetic/src/fixed_point.rs | 48 ++++-- primitives/arithmetic/src/helpers_128bit.rs | 72 +-------- primitives/arithmetic/src/rational.rs | 151 ++++++++++++------ primitives/npos-elections/src/phragmen.rs | 18 ++- 6 files changed, 162 insertions(+), 147 deletions(-) rename primitives/arithmetic/fuzzer/src/{multiply_by_rational.rs => multiply_by_rational_with_rounding.rs} (77%) diff --git a/primitives/arithmetic/fuzzer/Cargo.toml b/primitives/arithmetic/fuzzer/Cargo.toml index 33bf313766545..990106f990323 100644 --- a/primitives/arithmetic/fuzzer/Cargo.toml +++ b/primitives/arithmetic/fuzzer/Cargo.toml @@ -32,8 +32,8 @@ name = "per_thing_rational" path = "src/per_thing_rational.rs" [[bin]] -name = "multiply_by_rational" -path = "src/multiply_by_rational.rs" +name = "multiply_by_rational_with_rounding" +path = "src/multiply_by_rational_with_rounding.rs" [[bin]] name = "fixed_point" diff --git a/primitives/arithmetic/fuzzer/src/multiply_by_rational.rs b/primitives/arithmetic/fuzzer/src/multiply_by_rational_with_rounding.rs similarity index 77% rename from primitives/arithmetic/fuzzer/src/multiply_by_rational.rs rename to primitives/arithmetic/fuzzer/src/multiply_by_rational_with_rounding.rs index 019cf0ec39b7d..474b2d363eccd 100644 --- a/primitives/arithmetic/fuzzer/src/multiply_by_rational.rs +++ b/primitives/arithmetic/fuzzer/src/multiply_by_rational_with_rounding.rs @@ -16,19 +16,21 @@ // limitations under the License. //! # Running -//! Running this fuzzer can be done with `cargo hfuzz run multiply_by_rational`. `honggfuzz` CLI -//! options can be used by setting `HFUZZ_RUN_ARGS`, such as `-n 4` to use 4 threads. +//! Running this fuzzer can be done with `cargo hfuzz run multiply_by_rational_with_rounding`. +//! `honggfuzz` CLI options can be used by setting `HFUZZ_RUN_ARGS`, such as `-n 4` to use 4 +//! threads. //! //! # Debugging a panic //! Once a panic is found, it can be debugged with -//! `cargo hfuzz run-debug multiply_by_rational hfuzz_workspace/multiply_by_rational/*.fuzz`. +//! `cargo hfuzz run-debug multiply_by_rational_with_rounding +//! hfuzz_workspace/multiply_by_rational_with_rounding/*.fuzz`. //! //! # More information //! More information about `honggfuzz` can be found //! [here](https://docs.rs/honggfuzz/). use honggfuzz::fuzz; -use sp_arithmetic::{helpers_128bit::multiply_by_rational, traits::Zero}; +use sp_arithmetic::{helpers_128bit::multiply_by_rational_with_rounding, traits::Zero, Rounding}; fn main() { loop { @@ -42,9 +44,9 @@ fn main() { println!("++ Equation: {} * {} / {}", a, b, c); - // The point of this fuzzing is to make sure that `multiply_by_rational` is 100% - // accurate as long as the value fits in a u128. - if let Ok(result) = multiply_by_rational(a, b, c) { + // The point of this fuzzing is to make sure that `multiply_by_rational_with_rounding` + // is 100% accurate as long as the value fits in a u128. + if let Some(result) = multiply_by_rational_with_rounding(a, b, c, Rounding::Down) { let truth = mul_div(a, b, c); if result != truth && result != truth + 1 { diff --git a/primitives/arithmetic/src/fixed_point.rs b/primitives/arithmetic/src/fixed_point.rs index 2dfe717fe7e4f..bf3c93cdad605 100644 --- a/primitives/arithmetic/src/fixed_point.rs +++ b/primitives/arithmetic/src/fixed_point.rs @@ -18,7 +18,7 @@ //! Decimal Fixed Point implementations for Substrate runtime. use crate::{ - helpers_128bit::{multiply_by_rational, multiply_by_rational_with_rounding, sqrt}, + helpers_128bit::{multiply_by_rational_with_rounding, sqrt}, traits::{ Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedNeg, CheckedSub, One, SaturatedConversion, Saturating, UniqueSaturatedInto, Zero, @@ -151,10 +151,14 @@ pub trait FixedPointNumber: let d: I129 = d.into(); let negative = n.negative != d.negative; - multiply_by_rational(n.value, Self::DIV.unique_saturated_into(), d.value) - .ok() - .and_then(|value| from_i129(I129 { value, negative })) - .map(Self::from_inner) + multiply_by_rational_with_rounding( + n.value, + Self::DIV.unique_saturated_into(), + d.value, + Rounding::from_signed(SignedRounding::Minor, negative), + ) + .and_then(|value| from_i129(I129 { value, negative })) + .map(Self::from_inner) } /// Checked multiplication for integer type `N`. Equal to `self * n`. @@ -165,9 +169,13 @@ pub trait FixedPointNumber: let rhs: I129 = n.into(); let negative = lhs.negative != rhs.negative; - multiply_by_rational(lhs.value, rhs.value, Self::DIV.unique_saturated_into()) - .ok() - .and_then(|value| from_i129(I129 { value, negative })) + multiply_by_rational_with_rounding( + lhs.value, + rhs.value, + Self::DIV.unique_saturated_into(), + Rounding::from_signed(SignedRounding::Minor, negative), + ) + .and_then(|value| from_i129(I129 { value, negative })) } /// Saturating multiplication for integer type `N`. Equal to `self * n`. @@ -832,10 +840,14 @@ macro_rules! implement_fixed { // is equivalent to the `SignedRounding::NearestPrefMinor`. This means it is // expected to give exactly the same result as `const_checked_div` when the result // is positive and a result up to one epsilon greater when it is negative. - multiply_by_rational(lhs.value, Self::DIV as u128, rhs.value) - .ok() - .and_then(|value| from_i129(I129 { value, negative })) - .map(Self) + multiply_by_rational_with_rounding( + lhs.value, + Self::DIV as u128, + rhs.value, + Rounding::from_signed(SignedRounding::Minor, negative), + ) + .and_then(|value| from_i129(I129 { value, negative })) + .map(Self) } } @@ -845,10 +857,14 @@ macro_rules! implement_fixed { let rhs: I129 = other.0.into(); let negative = lhs.negative != rhs.negative; - multiply_by_rational(lhs.value, rhs.value, Self::DIV as u128) - .ok() - .and_then(|value| from_i129(I129 { value, negative })) - .map(Self) + multiply_by_rational_with_rounding( + lhs.value, + rhs.value, + Self::DIV as u128, + Rounding::from_signed(SignedRounding::Minor, negative), + ) + .and_then(|value| from_i129(I129 { value, negative })) + .map(Self) } } diff --git a/primitives/arithmetic/src/helpers_128bit.rs b/primitives/arithmetic/src/helpers_128bit.rs index 11df8bd53153e..7938c31d1eaa6 100644 --- a/primitives/arithmetic/src/helpers_128bit.rs +++ b/primitives/arithmetic/src/helpers_128bit.rs @@ -22,12 +22,7 @@ //! multiplication implementation provided there. use crate::{biguint, Rounding}; -use num_traits::Zero; -use sp_std::{ - cmp::{max, min}, - convert::TryInto, - mem, -}; +use sp_std::cmp::{max, min}; /// Helper gcd function used in Rational128 implementation. pub fn gcd(a: u128, b: u128) -> u128 { @@ -61,65 +56,6 @@ pub fn to_big_uint(x: u128) -> biguint::BigUint { n } -/// Safely and accurately compute `a * b / c`. The approach is: -/// - Simply try `a * b / c`. -/// - Else, convert them both into big numbers and re-try. `Err` is returned if the result cannot -/// be safely casted back to u128. -/// -/// Invariant: c must be greater than or equal to 1. -pub fn multiply_by_rational(mut a: u128, mut b: u128, mut c: u128) -> Result { - if a.is_zero() || b.is_zero() { - return Ok(Zero::zero()) - } - c = c.max(1); - - // a and b are interchangeable by definition in this function. It always helps to assume the - // bigger of which is being multiplied by a `0 < b/c < 1`. Hence, a should be the bigger and - // b the smaller one. - if b > a { - mem::swap(&mut a, &mut b); - } - - // Attempt to perform the division first - if a % c == 0 { - a /= c; - c = 1; - } else if b % c == 0 { - b /= c; - c = 1; - } - - if let Some(x) = a.checked_mul(b) { - // This is the safest way to go. Try it. - Ok(x / c) - } else { - let a_num = to_big_uint(a); - let b_num = to_big_uint(b); - let c_num = to_big_uint(c); - - let mut ab = a_num * b_num; - ab.lstrip(); - let mut q = if c_num.len() == 1 { - // PROOF: if `c_num.len() == 1` then `c` fits in one limb. - ab.div_unit(c as biguint::Single) - } else { - // PROOF: both `ab` and `c` cannot have leading zero limbs; if length of `c` is 1, - // the previous branch would handle. Also, if ab for sure has a bigger size than - // c, because `a.checked_mul(b)` has failed, hence ab must be at least one limb - // bigger than c. In this case, returning zero is defensive-only and div should - // always return Some. - let (mut q, r) = ab.div(&c_num, true).unwrap_or((Zero::zero(), Zero::zero())); - let r: u128 = r.try_into().expect("reminder of div by c is always less than c; qed"); - if r > (c / 2) { - q = q.add(&to_big_uint(1)); - } - q - }; - q.lstrip(); - q.try_into().map_err(|_| "result cannot fit in u128") - } -} - mod double128 { // Inspired by: https://medium.com/wicketh/mathemagic-512-bit-division-in-solidity-afa55870a65 @@ -247,7 +183,7 @@ mod double128 { } /// Returns `a * b / c` and `(a * b) % c` (wrapping to 128 bits) or `None` in the case of -/// overflow. +/// overflow and c = 0. pub const fn multiply_by_rational_with_rounding( a: u128, b: u128, @@ -256,7 +192,7 @@ pub const fn multiply_by_rational_with_rounding( ) -> Option { use double128::Double128; if c == 0 { - panic!("attempt to divide by zero") + return None } let (result, remainder) = Double128::product_of(a, b).div(c); let mut result: u128 = match result.try_into_u128() { @@ -361,7 +297,7 @@ mod tests { let b = random_u128(i + (1 << 30)); let c = random_u128(i + (1 << 31)); let x = mulrat(a, b, c, NearestPrefDown); - let y = multiply_by_rational(a, b, c).ok(); + let y = multiply_by_rational_with_rounding(a, b, c, Rounding::NearestPrefDown); assert_eq!(x.is_some(), y.is_some()); let x = x.unwrap_or(0); let y = y.unwrap_or(0); diff --git a/primitives/arithmetic/src/rational.rs b/primitives/arithmetic/src/rational.rs index 1beafbe811614..54cabfc6214e8 100644 --- a/primitives/arithmetic/src/rational.rs +++ b/primitives/arithmetic/src/rational.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{biguint::BigUint, helpers_128bit}; +use crate::{biguint::BigUint, helpers_128bit, Rounding}; use num_traits::{Bounded, One, Zero}; use sp_std::{cmp::Ordering, prelude::*}; @@ -143,27 +143,38 @@ impl Rational128 { /// Convert `self` to a similar rational number where denominator is the given `den`. // - /// This only returns if the result is accurate. `Err` is returned if the result cannot be + /// This only returns if the result is accurate. `None` is returned if the result cannot be /// accurately calculated. - pub fn to_den(self, den: u128) -> Result { + pub fn to_den(self, den: u128) -> Option { if den == self.1 { - Ok(self) + Some(self) } else { - helpers_128bit::multiply_by_rational(self.0, den, self.1).map(|n| Self(n, den)) + helpers_128bit::multiply_by_rational_with_rounding( + self.0, + den, + self.1, + Rounding::NearestPrefDown, + ) + .map(|n| Self(n, den)) } } /// Get the least common divisor of `self` and `other`. /// - /// This only returns if the result is accurate. `Err` is returned if the result cannot be + /// This only returns if the result is accurate. `None` is returned if the result cannot be /// accurately calculated. - pub fn lcm(&self, other: &Self) -> Result { + pub fn lcm(&self, other: &Self) -> Option { // this should be tested better: two large numbers that are almost the same. if self.1 == other.1 { - return Ok(self.1) + return Some(self.1) } let g = helpers_128bit::gcd(self.1, other.1); - helpers_128bit::multiply_by_rational(self.1, other.1, g) + helpers_128bit::multiply_by_rational_with_rounding( + self.1, + other.1, + g, + Rounding::NearestPrefDown, + ) } /// A saturating add that assumes `self` and `other` have the same denominator. @@ -188,9 +199,11 @@ impl Rational128 { /// /// Overflow might happen during any of the steps. Error is returned in such cases. pub fn checked_add(self, other: Self) -> Result { - let lcm = self.lcm(&other).map_err(|_| "failed to scale to denominator")?; - let self_scaled = self.to_den(lcm).map_err(|_| "failed to scale to denominator")?; - let other_scaled = other.to_den(lcm).map_err(|_| "failed to scale to denominator")?; + let lcm = self.lcm(&other).ok_or(0).map_err(|_| "failed to scale to denominator")?; + let self_scaled = + self.to_den(lcm).ok_or(0).map_err(|_| "failed to scale to denominator")?; + let other_scaled = + other.to_den(lcm).ok_or(0).map_err(|_| "failed to scale to denominator")?; let n = self_scaled .0 .checked_add(other_scaled.0) @@ -202,9 +215,11 @@ impl Rational128 { /// /// Overflow might happen during any of the steps. None is returned in such cases. pub fn checked_sub(self, other: Self) -> Result { - let lcm = self.lcm(&other).map_err(|_| "failed to scale to denominator")?; - let self_scaled = self.to_den(lcm).map_err(|_| "failed to scale to denominator")?; - let other_scaled = other.to_den(lcm).map_err(|_| "failed to scale to denominator")?; + let lcm = self.lcm(&other).ok_or(0).map_err(|_| "failed to scale to denominator")?; + let self_scaled = + self.to_den(lcm).ok_or(0).map_err(|_| "failed to scale to denominator")?; + let other_scaled = + other.to_den(lcm).ok_or(0).map_err(|_| "failed to scale to denominator")?; let n = self_scaled .0 @@ -314,18 +329,18 @@ mod tests { #[test] fn to_denom_works() { // simple up and down - assert_eq!(r(1, 5).to_den(10), Ok(r(2, 10))); - assert_eq!(r(4, 10).to_den(5), Ok(r(2, 5))); + assert_eq!(r(1, 5).to_den(10), Some(r(2, 10))); + assert_eq!(r(4, 10).to_den(5), Some(r(2, 5))); // up and down with large numbers - assert_eq!(r(MAX128 - 10, MAX128).to_den(10), Ok(r(10, 10))); - assert_eq!(r(MAX128 / 2, MAX128).to_den(10), Ok(r(5, 10))); + assert_eq!(r(MAX128 - 10, MAX128).to_den(10), Some(r(10, 10))); + assert_eq!(r(MAX128 / 2, MAX128).to_den(10), Some(r(5, 10))); // large to perbill. This is very well needed for npos-elections. - assert_eq!(r(MAX128 / 2, MAX128).to_den(1000_000_000), Ok(r(500_000_000, 1000_000_000))); + assert_eq!(r(MAX128 / 2, MAX128).to_den(1000_000_000), Some(r(500_000_000, 1000_000_000))); // large to large - assert_eq!(r(MAX128 / 2, MAX128).to_den(MAX128 / 2), Ok(r(MAX128 / 4, MAX128 / 2))); + assert_eq!(r(MAX128 / 2, MAX128).to_den(MAX128 / 2), Some(r(MAX128 / 4, MAX128 / 2))); } #[test] @@ -342,13 +357,10 @@ mod tests { assert_eq!(r(5, 30).lcm(&r(1, 10)).unwrap(), 30); // large numbers - assert_eq!( - r(1_000_000_000, MAX128).lcm(&r(7_000_000_000, MAX128 - 1)), - Err("result cannot fit in u128"), - ); + assert_eq!(r(1_000_000_000, MAX128).lcm(&r(7_000_000_000, MAX128 - 1)), None,); assert_eq!( r(1_000_000_000, MAX64).lcm(&r(7_000_000_000, MAX64 - 1)), - Ok(340282366920938463408034375210639556610), + Some(340282366920938463408034375210639556610), ); const_assert!(340282366920938463408034375210639556610 < MAX128); const_assert!(340282366920938463408034375210639556610 == MAX64 * (MAX64 - 1)); @@ -408,55 +420,87 @@ mod tests { } #[test] - fn multiply_by_rational_works() { - assert_eq!(multiply_by_rational(7, 2, 3).unwrap(), 7 * 2 / 3); - assert_eq!(multiply_by_rational(7, 20, 30).unwrap(), 7 * 2 / 3); - assert_eq!(multiply_by_rational(20, 7, 30).unwrap(), 7 * 2 / 3); + fn multiply_by_rational_with_rounding_works() { + assert_eq!(multiply_by_rational_with_rounding(7, 2, 3, Rounding::Down).unwrap(), 7 * 2 / 3); + assert_eq!( + multiply_by_rational_with_rounding(7, 20, 30, Rounding::Down).unwrap(), + 7 * 2 / 3 + ); + assert_eq!( + multiply_by_rational_with_rounding(20, 7, 30, Rounding::Down).unwrap(), + 7 * 2 / 3 + ); assert_eq!( // MAX128 % 3 == 0 - multiply_by_rational(MAX128, 2, 3).unwrap(), + multiply_by_rational_with_rounding(MAX128, 2, 3, Rounding::Down).unwrap(), MAX128 / 3 * 2, ); assert_eq!( // MAX128 % 7 == 3 - multiply_by_rational(MAX128, 5, 7).unwrap(), + multiply_by_rational_with_rounding(MAX128, 5, 7, Rounding::Down).unwrap(), (MAX128 / 7 * 5) + (3 * 5 / 7), ); assert_eq!( // MAX128 % 7 == 3 - multiply_by_rational(MAX128, 11, 13).unwrap(), + multiply_by_rational_with_rounding(MAX128, 11, 13, Rounding::Down).unwrap(), (MAX128 / 13 * 11) + (8 * 11 / 13), ); assert_eq!( // MAX128 % 1000 == 455 - multiply_by_rational(MAX128, 555, 1000).unwrap(), + multiply_by_rational_with_rounding(MAX128, 555, 1000, Rounding::Down).unwrap(), (MAX128 / 1000 * 555) + (455 * 555 / 1000), ); - assert_eq!(multiply_by_rational(2 * MAX64 - 1, MAX64, MAX64).unwrap(), 2 * MAX64 - 1); - assert_eq!(multiply_by_rational(2 * MAX64 - 1, MAX64 - 1, MAX64).unwrap(), 2 * MAX64 - 3); + assert_eq!( + multiply_by_rational_with_rounding(2 * MAX64 - 1, MAX64, MAX64, Rounding::Down) + .unwrap(), + 2 * MAX64 - 1 + ); + assert_eq!( + multiply_by_rational_with_rounding(2 * MAX64 - 1, MAX64 - 1, MAX64, Rounding::Down) + .unwrap(), + 2 * MAX64 - 3 + ); assert_eq!( - multiply_by_rational(MAX64 + 100, MAX64_2, MAX64_2 / 2).unwrap(), + multiply_by_rational_with_rounding(MAX64 + 100, MAX64_2, MAX64_2 / 2, Rounding::Down) + .unwrap(), (MAX64 + 100) * 2, ); assert_eq!( - multiply_by_rational(MAX64 + 100, MAX64_2 / 100, MAX64_2 / 200).unwrap(), + multiply_by_rational_with_rounding( + MAX64 + 100, + MAX64_2 / 100, + MAX64_2 / 200, + Rounding::Down + ) + .unwrap(), (MAX64 + 100) * 2, ); assert_eq!( - multiply_by_rational(2u128.pow(66) - 1, 2u128.pow(65) - 1, 2u128.pow(65)).unwrap(), + multiply_by_rational_with_rounding( + 2u128.pow(66) - 1, + 2u128.pow(65) - 1, + 2u128.pow(65), + Rounding::Down + ) + .unwrap(), 73786976294838206461, ); - assert_eq!(multiply_by_rational(1_000_000_000, MAX128 / 8, MAX128 / 2).unwrap(), 250000000); + assert_eq!( + multiply_by_rational_with_rounding(1_000_000_000, MAX128 / 8, MAX128 / 2, Rounding::Up) + .unwrap(), + 250000000 + ); assert_eq!( - multiply_by_rational( + multiply_by_rational_with_rounding( 29459999999999999988000u128, 1000000000000000000u128, - 10000000000000000000u128 + 10000000000000000000u128, + Rounding::Down ) .unwrap(), 2945999999999999998800u128 @@ -464,17 +508,28 @@ mod tests { } #[test] - fn multiply_by_rational_a_b_are_interchangeable() { - assert_eq!(multiply_by_rational(10, MAX128, MAX128 / 2), Ok(20)); - assert_eq!(multiply_by_rational(MAX128, 10, MAX128 / 2), Ok(20)); + fn multiply_by_rational_with_rounding_a_b_are_interchangeable() { + assert_eq!( + multiply_by_rational_with_rounding(10, MAX128, MAX128 / 2, Rounding::NearestPrefDown), + Some(20) + ); + assert_eq!( + multiply_by_rational_with_rounding(MAX128, 10, MAX128 / 2, Rounding::NearestPrefDown), + Some(20) + ); } #[test] #[ignore] - fn multiply_by_rational_fuzzed_equation() { + fn multiply_by_rational_with_rounding_fuzzed_equation() { assert_eq!( - multiply_by_rational(154742576605164960401588224, 9223376310179529214, 549756068598), - Ok(2596149632101417846585204209223679) + multiply_by_rational_with_rounding( + 154742576605164960401588224, + 9223376310179529214, + 549756068598, + Rounding::NearestPrefDown + ), + Some(2596149632101417846585204209223679) ); } } diff --git a/primitives/npos-elections/src/phragmen.rs b/primitives/npos-elections/src/phragmen.rs index 9b50dc36e48fb..ca32780ed84b4 100644 --- a/primitives/npos-elections/src/phragmen.rs +++ b/primitives/npos-elections/src/phragmen.rs @@ -25,9 +25,9 @@ use crate::{ IdentifierT, PerThing128, VoteWeight, Voter, }; use sp_arithmetic::{ - helpers_128bit::multiply_by_rational, + helpers_128bit::multiply_by_rational_with_rounding, traits::{Bounded, Zero}, - Rational128, + Rational128, Rounding, }; use sp_std::prelude::*; @@ -143,10 +143,11 @@ pub fn seq_phragmen_core( for edge in &voter.edges { let mut candidate = edge.candidate.borrow_mut(); if !candidate.elected && !candidate.approval_stake.is_zero() { - let temp_n = multiply_by_rational( + let temp_n = multiply_by_rational_with_rounding( voter.load.n(), voter.budget, candidate.approval_stake, + Rounding::Down, ) .unwrap_or(Bounded::max_value()); let temp_d = voter.load.d(); @@ -184,9 +185,14 @@ pub fn seq_phragmen_core( for edge in &mut voter.edges { if edge.candidate.borrow().elected { // update internal state. - edge.weight = multiply_by_rational(voter.budget, edge.load.n(), voter.load.n()) - // If result cannot fit in u128. Not much we can do about it. - .unwrap_or(Bounded::max_value()); + edge.weight = multiply_by_rational_with_rounding( + voter.budget, + edge.load.n(), + voter.load.n(), + Rounding::Down, + ) + // If result cannot fit in u128. Not much we can do about it. + .unwrap_or(Bounded::max_value()); } else { edge.weight = 0 } From e2542584dc06dbb9329aed6f435c76295e54b92d Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 17 Jun 2022 17:47:14 +0400 Subject: [PATCH 349/484] Upgrade to libp2p 0.45.1 (#11682) * Upgrade to libp2p 0.45.1 * Limit max_negotiating_inbound_streams to 512 * Upgrade prost-build to 0.10 * Set max_negotiating_inbound_streams to 2048 Co-authored-by: Pierre Krieger * Fix authority discovery protobuf * Fix comments in authority-discovery schema Co-authored-by: Pierre Krieger * Add a comment about transport initialization Co-authored-by: Pierre Krieger --- Cargo.lock | 327 +++++++++++------- client/authority-discovery/Cargo.toml | 4 +- .../src/worker/schema/dht-v2.proto | 4 +- client/cli/Cargo.toml | 2 +- client/consensus/common/Cargo.toml | 2 +- client/network-gossip/Cargo.toml | 2 +- client/network/Cargo.toml | 4 +- client/network/common/Cargo.toml | 4 +- client/network/light/Cargo.toml | 4 +- client/network/src/service.rs | 3 +- client/network/src/transport.rs | 9 +- client/network/sync/Cargo.toml | 4 +- client/network/test/Cargo.toml | 2 +- client/peerset/Cargo.toml | 2 +- client/telemetry/Cargo.toml | 2 +- client/telemetry/src/lib.rs | 36 +- client/telemetry/src/node.rs | 24 +- 17 files changed, 277 insertions(+), 158 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 84443396e0d06..e33211911b47a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -135,9 +135,9 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] name = "arrayvec" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4dc07131ffa69b8072d35f5007352af944213cde02545e2103680baed38fcd" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" [[package]] name = "asn1_der" @@ -365,15 +365,6 @@ dependencies = [ "pin-project-lite 0.2.6", ] -[[package]] -name = "atomic" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3410529e8288c463bedb5930f82833bc0c90e5d2fe639a56582a4d09220b281" -dependencies = [ - "autocfg 1.0.1", -] - [[package]] name = "atomic-waker" version = "1.0.0" @@ -622,7 +613,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72936ee4afc7f8f736d1c38383b56480b5497b4617b4a77bdbf1d2ababc76127" dependencies = [ "arrayref", - "arrayvec 0.7.1", + "arrayvec 0.7.2", "constant_time_eq", ] @@ -633,7 +624,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db539cc2b5f6003621f1cd9ef92d7ded8ea5232c7de0f9faa2de251cd98730d4" dependencies = [ "arrayref", - "arrayvec 0.7.1", + "arrayvec 0.7.2", "constant_time_eq", ] @@ -644,7 +635,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a08e53fc5a564bb15bfe6fae56bd71522205f1f91893f9c0116edad6496c183f" dependencies = [ "arrayref", - "arrayvec 0.7.1", + "arrayvec 0.7.2", "cc", "cfg-if 1.0.0", "constant_time_eq", @@ -1037,6 +1028,15 @@ dependencies = [ "bitflags", ] +[[package]] +name = "cmake" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7b858541263efe664aead4a5209a4ae5c5d2811167d4ed4ee0944503f8d2089" +dependencies = [ + "cc", +] + [[package]] name = "comfy-table" version = "5.0.1" @@ -3201,7 +3201,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16efcd4477de857d4a2195a45769b2fe9ebb54f3ef5a4221d3b014a4fe33ec0b" dependencies = [ "anyhow", - "arrayvec 0.7.1", + "arrayvec 0.7.2", "async-lock", "async-trait", "beef", @@ -3437,11 +3437,10 @@ checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" [[package]] name = "libp2p" -version = "0.44.0" +version = "0.45.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "475ce2ac4a9727e53a519f6ee05b38abfcba8f0d39c4d24f103d184e36fd5b0f" +checksum = "41726ee8f662563fafba2d2d484b14037cc8ecb8c953fbfc8439d4ce3a0a9029" dependencies = [ - "atomic", "bytes", "futures", "futures-timer", @@ -3449,7 +3448,7 @@ dependencies = [ "instant", "lazy_static", "libp2p-autonat", - "libp2p-core", + "libp2p-core 0.33.0", "libp2p-deflate", "libp2p-dns", "libp2p-floodsub", @@ -3482,20 +3481,20 @@ dependencies = [ [[package]] name = "libp2p-autonat" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a13b690e65046af6a09c0b27bd9508fa1cab0efce889de74b0b643b9d2a98f9a" +checksum = "50de7c1d5c3f040fccb469e8a2d189e068b7627d760dd74ef914071c16bbe905" dependencies = [ "async-trait", "futures", "futures-timer", "instant", - "libp2p-core", + "libp2p-core 0.33.0", "libp2p-request-response", "libp2p-swarm", "log", - "prost 0.9.0", - "prost-build", + "prost 0.10.3", + "prost-build 0.10.4", "rand 0.8.4", ] @@ -3514,7 +3513,6 @@ dependencies = [ "futures-timer", "instant", "lazy_static", - "libsecp256k1", "log", "multiaddr", "multihash", @@ -3522,10 +3520,45 @@ dependencies = [ "parking_lot 0.12.0", "pin-project 1.0.10", "prost 0.9.0", - "prost-build", + "prost-build 0.9.0", + "rand 0.8.4", + "ring", + "rw-stream-sink 0.2.1", + "sha2 0.10.2", + "smallvec", + "thiserror", + "unsigned-varint", + "void", + "zeroize", +] + +[[package]] +name = "libp2p-core" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d46fca305dee6757022e2f5a4f6c023315084d0ed7441c3ab244e76666d979" +dependencies = [ + "asn1_der", + "bs58", + "ed25519-dalek", + "either", + "fnv", + "futures", + "futures-timer", + "instant", + "lazy_static", + "libsecp256k1", + "log", + "multiaddr", + "multihash", + "multistream-select", + "parking_lot 0.12.0", + "pin-project 1.0.10", + "prost 0.10.3", + "prost-build 0.10.4", "rand 0.8.4", "ring", - "rw-stream-sink", + "rw-stream-sink 0.3.0", "sha2 0.10.2", "smallvec", "thiserror", @@ -3536,52 +3569,53 @@ dependencies = [ [[package]] name = "libp2p-deflate" -version = "0.32.0" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b1d37f042f748e224f04785d0e987ae09a2aa518d6401d82d412dad83e360ed" +checksum = "86adefc55ea4ed8201149f052fb441210727481dff1fb0b8318460206a79f5fb" dependencies = [ "flate2", "futures", - "libp2p-core", + "libp2p-core 0.33.0", ] [[package]] name = "libp2p-dns" -version = "0.32.1" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "066e33e854e10b5c93fc650458bf2179c7e0d143db260b0963e44a94859817f1" +checksum = "fbb462ec3a51fab457b4b44ac295e8b0a4b04dc175127e615cf996b1f0f1a268" dependencies = [ "async-std-resolver", "futures", - "libp2p-core", + "libp2p-core 0.33.0", "log", + "parking_lot 0.12.0", "smallvec", "trust-dns-resolver", ] [[package]] name = "libp2p-floodsub" -version = "0.35.0" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "733d3ea6ebe7a7a85df2bc86678b93f24b015fae5fe3b3acc4c400e795a55d2d" +checksum = "a505d0c6f851cbf2919535150198e530825def8bd3757477f13dc3a57f46cbcc" dependencies = [ "cuckoofilter", "fnv", "futures", - "libp2p-core", + "libp2p-core 0.33.0", "libp2p-swarm", "log", - "prost 0.9.0", - "prost-build", + "prost 0.10.3", + "prost-build 0.10.4", "rand 0.7.3", "smallvec", ] [[package]] name = "libp2p-gossipsub" -version = "0.37.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a90c989a7c0969c2ab63e898da9bc735e3be53fb4f376e9c045ce516bcc9f928" +checksum = "c9be947d8cea8e6b469201314619395826896d2c051053c3723910ba98e68e04" dependencies = [ "asynchronous-codec", "base64", @@ -3591,12 +3625,12 @@ dependencies = [ "futures", "hex_fmt", "instant", - "libp2p-core", + "libp2p-core 0.33.0", "libp2p-swarm", "log", "prometheus-client", - "prost 0.9.0", - "prost-build", + "prost 0.10.3", + "prost-build 0.10.4", "rand 0.7.3", "regex", "sha2 0.10.2", @@ -3607,28 +3641,32 @@ dependencies = [ [[package]] name = "libp2p-identify" -version = "0.35.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5ef5a5b57904c7c33d6713ef918d239dc6b7553458f3475d87f8a18e9c651c8" +checksum = "b84b53490442d086db1fa5375670c9666e79143dccadef3f7c74a4346899a984" dependencies = [ + "asynchronous-codec", "futures", "futures-timer", - "libp2p-core", + "libp2p-core 0.33.0", "libp2p-swarm", "log", "lru", - "prost 0.9.0", - "prost-build", + "prost 0.10.3", + "prost-build 0.10.4", + "prost-codec", "smallvec", + "thiserror", + "void", ] [[package]] name = "libp2p-kad" -version = "0.36.0" +version = "0.37.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "564e6bd64d177446399ed835b9451a8825b07929d6daa6a94e6405592974725e" +checksum = "5f6b5d4de90fcd35feb65ea6223fd78f3b747a64ca4b65e0813fbe66a27d56aa" dependencies = [ - "arrayvec 0.5.2", + "arrayvec 0.7.2", "asynchronous-codec", "bytes", "either", @@ -3636,11 +3674,11 @@ dependencies = [ "futures", "futures-timer", "instant", - "libp2p-core", + "libp2p-core 0.33.0", "libp2p-swarm", "log", - "prost 0.9.0", - "prost-build", + "prost 0.10.3", + "prost-build 0.10.4", "rand 0.7.3", "sha2 0.10.2", "smallvec", @@ -3652,9 +3690,9 @@ dependencies = [ [[package]] name = "libp2p-mdns" -version = "0.36.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "611ae873c8e280ccfab0d57c7a13cac5644f364529e233114ff07863946058b0" +checksum = "4783f8cf00c7b6c1ff0f1870b4fcf50b042b45533d2e13b6fb464caf447a6951" dependencies = [ "async-io", "data-encoding", @@ -3662,7 +3700,7 @@ dependencies = [ "futures", "if-watch", "lazy_static", - "libp2p-core", + "libp2p-core 0.33.0", "libp2p-swarm", "log", "rand 0.8.4", @@ -3673,11 +3711,11 @@ dependencies = [ [[package]] name = "libp2p-metrics" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985be799bb3796e0c136c768208c3c06604a38430571906a13dcfeda225a3b9d" +checksum = "adc4357140141ba9739eee71b20aa735351c0fc642635b2bffc7f57a6b5c1090" dependencies = [ - "libp2p-core", + "libp2p-core 0.33.0", "libp2p-gossipsub", "libp2p-identify", "libp2p-kad", @@ -3689,14 +3727,14 @@ dependencies = [ [[package]] name = "libp2p-mplex" -version = "0.32.0" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "442eb0c9fff0bf22a34f015724b4143ce01877e079ed0963c722d94c07c72160" +checksum = "5ff9c893f2367631a711301d703c47432af898c9bb8253bea0e2c051a13f7640" dependencies = [ "asynchronous-codec", "bytes", "futures", - "libp2p-core", + "libp2p-core 0.33.0", "log", "nohash-hasher", "parking_lot 0.12.0", @@ -3707,18 +3745,18 @@ dependencies = [ [[package]] name = "libp2p-noise" -version = "0.35.0" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd7e0c94051cda67123be68cf6b65211ba3dde7277be9068412de3e7ffd63ef" +checksum = "cf2cee1dad1c83325bbd182a8e94555778699cec8a9da00086efb7522c4c15ad" dependencies = [ "bytes", "curve25519-dalek 3.0.2", "futures", "lazy_static", - "libp2p-core", + "libp2p-core 0.33.0", "log", - "prost 0.9.0", - "prost-build", + "prost 0.10.3", + "prost-build 0.10.4", "rand 0.8.4", "sha2 0.10.2", "snow", @@ -3729,14 +3767,14 @@ dependencies = [ [[package]] name = "libp2p-ping" -version = "0.35.0" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf57a3c2e821331dda9fe612d4654d676ab6e33d18d9434a18cced72630df6ad" +checksum = "d41516c82fe8dd148ec925eead0c5ec08a0628f7913597e93e126e4dfb4e0787" dependencies = [ "futures", "futures-timer", "instant", - "libp2p-core", + "libp2p-core 0.33.0", "libp2p-swarm", "log", "rand 0.7.3", @@ -3745,17 +3783,17 @@ dependencies = [ [[package]] name = "libp2p-plaintext" -version = "0.32.0" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "962c0fb0e7212fb96a69b87f2d09bcefd317935239bdc79cda900e7a8897a3fe" +checksum = "db007e737adc5d28b2e03223b0210164928ad742591127130796a72aa8eaf54f" dependencies = [ "asynchronous-codec", "bytes", "futures", - "libp2p-core", + "libp2p-core 0.33.0", "log", - "prost 0.9.0", - "prost-build", + "prost 0.10.3", + "prost-build 0.10.4", "unsigned-varint", "void", ] @@ -3776,9 +3814,9 @@ dependencies = [ [[package]] name = "libp2p-relay" -version = "0.8.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3aa754cb7bccef51ebc3c458c6bbcef89d83b578a9925438389be841527d408f" +checksum = "624ead3406f64437a0d4567c31bd128a9a0b8226d5f16c074038f5d0fc32f650" dependencies = [ "asynchronous-codec", "bytes", @@ -3786,36 +3824,36 @@ dependencies = [ "futures", "futures-timer", "instant", - "libp2p-core", + "libp2p-core 0.33.0", "libp2p-swarm", "log", "pin-project 1.0.10", - "prost 0.9.0", - "prost-build", + "prost 0.10.3", + "prost-build 0.10.4", + "prost-codec", "rand 0.8.4", "smallvec", "static_assertions", "thiserror", - "unsigned-varint", "void", ] [[package]] name = "libp2p-rendezvous" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd0baab894c5b84da510b915d53264d566c3c35889f09931fe9edbd2a773bee" +checksum = "c59967ea2db2c7560f641aa58ac05982d42131863fcd3dd6dcf0dd1daf81c60c" dependencies = [ "asynchronous-codec", "bimap", "futures", "futures-timer", "instant", - "libp2p-core", + "libp2p-core 0.33.0", "libp2p-swarm", "log", - "prost 0.9.0", - "prost-build", + "prost 0.10.3", + "prost-build 0.10.4", "rand 0.8.4", "sha2 0.10.2", "thiserror", @@ -3825,15 +3863,15 @@ dependencies = [ [[package]] name = "libp2p-request-response" -version = "0.17.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6a6fc6c9ad95661f46989473b34bd2993d14a4de497ff3b2668a910d4b869" +checksum = "b02e0acb725e5a757d77c96b95298fd73a7394fe82ba7b8bbeea510719cbe441" dependencies = [ "async-trait", "bytes", "futures", "instant", - "libp2p-core", + "libp2p-core 0.33.0", "libp2p-swarm", "log", "rand 0.7.3", @@ -3843,16 +3881,16 @@ dependencies = [ [[package]] name = "libp2p-swarm" -version = "0.35.0" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f0c69ad9e8f7c5fc50ad5ad9c7c8b57f33716532a2b623197f69f93e374d14c" +checksum = "8f4bb21c5abadbf00360c734f16bf87f1712ed4f23cd46148f625d2ddb867346" dependencies = [ "either", "fnv", "futures", "futures-timer", "instant", - "libp2p-core", + "libp2p-core 0.33.0", "log", "pin-project 1.0.10", "rand 0.7.3", @@ -3873,9 +3911,9 @@ dependencies = [ [[package]] name = "libp2p-tcp" -version = "0.32.0" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "193447aa729c85aac2376828df76d171c1a589c9e6b58fcc7f9d9a020734122c" +checksum = "4f4933e38ef21b50698aefc87799c24f2a365c9d3f6cf50471f3f6a0bc410892" dependencies = [ "async-io", "futures", @@ -3883,7 +3921,7 @@ dependencies = [ "if-watch", "ipnet", "libc", - "libp2p-core", + "libp2p-core 0.33.0", "log", "socket2 0.4.4", ] @@ -3896,19 +3934,19 @@ checksum = "24bdab114f7f2701757d6541266e1131b429bbae382008f207f2114ee4222dcb" dependencies = [ "async-std", "futures", - "libp2p-core", + "libp2p-core 0.32.1", "log", ] [[package]] name = "libp2p-wasm-ext" -version = "0.32.0" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f6ea0f84a967ef59a16083f222c18115ae2e91db69809dce275df62e101b279" +checksum = "f066f2b8b1a1d64793f05da2256e6842ecd0293d6735ca2e9bda89831a1bdc06" dependencies = [ "futures", "js-sys", - "libp2p-core", + "libp2p-core 0.33.0", "parity-send-wrapper", "wasm-bindgen", "wasm-bindgen-futures", @@ -3916,17 +3954,18 @@ dependencies = [ [[package]] name = "libp2p-websocket" -version = "0.34.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c932834c3754501c368d1bf3d0fb458487a642b90fc25df082a3a2f3d3b32e37" +checksum = "39d398fbb29f432c4128fabdaac2ed155c3bcaf1b9bd40eeeb10a471eefacbf5" dependencies = [ "either", "futures", "futures-rustls", - "libp2p-core", + "libp2p-core 0.33.0", "log", + "parking_lot 0.12.0", "quicksink", - "rw-stream-sink", + "rw-stream-sink 0.3.0", "soketto", "url", "webpki-roots", @@ -3934,12 +3973,12 @@ dependencies = [ [[package]] name = "libp2p-yamux" -version = "0.36.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be902ebd89193cd020e89e89107726a38cfc0d16d18f613f4a37d046e92c7517" +checksum = "8fe653639ad74877c759720febb0cbcbf4caa221adde4eed2d3126ce5c6f381f" dependencies = [ "futures", - "libp2p-core", + "libp2p-core 0.33.0", "parking_lot 0.12.0", "thiserror", "yamux", @@ -6538,7 +6577,7 @@ version = "3.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04bc9583b5e01cc8c70d89acc9af14ef9b1c29ee3a0074b2a9eea8c0fa396690" dependencies = [ - "arrayvec 0.7.1", + "arrayvec 0.7.2", "bitvec", "byte-slice-cast", "impl-trait-for-tuples", @@ -7025,9 +7064,9 @@ dependencies = [ [[package]] name = "prometheus-client" -version = "0.15.1" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9a896938cc6018c64f279888b8c7559d3725210d5db9a3a1ee6bc7188d51d34" +checksum = "ac1abe0255c04d15f571427a2d1e00099016506cf3297b53853acd2b7eb87825" dependencies = [ "dtoa", "itoa 1.0.1", @@ -7080,12 +7119,47 @@ dependencies = [ "multimap", "petgraph", "prost 0.9.0", - "prost-types", + "prost-types 0.9.0", + "regex", + "tempfile", + "which", +] + +[[package]] +name = "prost-build" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae5a4388762d5815a9fc0dea33c56b021cdc8dde0c55e0c9ca57197254b0cab" +dependencies = [ + "bytes", + "cfg-if 1.0.0", + "cmake", + "heck 0.4.0", + "itertools", + "lazy_static", + "log", + "multimap", + "petgraph", + "prost 0.10.3", + "prost-types 0.10.1", "regex", "tempfile", "which", ] +[[package]] +name = "prost-codec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00af1e92c33b4813cc79fda3f2dbf56af5169709be0202df730e9ebc3e4cd007" +dependencies = [ + "asynchronous-codec", + "bytes", + "prost 0.10.3", + "thiserror", + "unsigned-varint", +] + [[package]] name = "prost-derive" version = "0.9.0" @@ -7122,6 +7196,16 @@ dependencies = [ "prost 0.9.0", ] +[[package]] +name = "prost-types" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d0a014229361011dc8e69c8a1ec6c2e8d0f2af7c91e3ea3f5b2170298461e68" +dependencies = [ + "bytes", + "prost 0.10.3", +] + [[package]] name = "psm" version = "0.1.12" @@ -7805,6 +7889,17 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "rw-stream-sink" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26338f5e09bb721b85b135ea05af7767c90b52f6de4f087d4f4a3a9d64e7dc04" +dependencies = [ + "futures", + "pin-project 1.0.10", + "static_assertions", +] + [[package]] name = "ryu" version = "1.0.5" @@ -7860,7 +7955,7 @@ dependencies = [ "log", "parity-scale-codec", "prost 0.10.3", - "prost-build", + "prost-build 0.10.4", "quickcheck", "rand 0.7.3", "sc-client-api", @@ -8512,7 +8607,7 @@ dependencies = [ "parking_lot 0.12.0", "pin-project 1.0.10", "prost 0.10.3", - "prost-build", + "prost-build 0.10.4", "rand 0.7.3", "sc-block-builder", "sc-client-api", @@ -8550,7 +8645,7 @@ dependencies = [ "futures", "libp2p", "parity-scale-codec", - "prost-build", + "prost-build 0.10.4", "sc-peerset", "smallvec", ] @@ -8583,7 +8678,7 @@ dependencies = [ "log", "parity-scale-codec", "prost 0.10.3", - "prost-build", + "prost-build 0.10.4", "sc-client-api", "sc-network-common", "sc-peerset", @@ -8606,7 +8701,7 @@ dependencies = [ "lru", "parity-scale-codec", "prost 0.10.3", - "prost-build", + "prost-build 0.10.4", "quickcheck", "sc-block-builder", "sc-client-api", diff --git a/client/authority-discovery/Cargo.toml b/client/authority-discovery/Cargo.toml index 136c2606a384e..32a5e23bd49d9 100644 --- a/client/authority-discovery/Cargo.toml +++ b/client/authority-discovery/Cargo.toml @@ -14,7 +14,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [build-dependencies] -prost-build = "0.9" +prost-build = "0.10" [dependencies] async-trait = "0.1" @@ -22,7 +22,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = futures = "0.3.21" futures-timer = "3.0.1" ip_network = "0.4.1" -libp2p = { version = "0.44.0", default-features = false, features = ["kad"] } +libp2p = { version = "0.45.1", default-features = false, features = ["kad"] } log = "0.4.17" prost = "0.10" rand = "0.7.2" diff --git a/client/authority-discovery/src/worker/schema/dht-v2.proto b/client/authority-discovery/src/worker/schema/dht-v2.proto index c63f6c1767351..fdbadb4266306 100644 --- a/client/authority-discovery/src/worker/schema/dht-v2.proto +++ b/client/authority-discovery/src/worker/schema/dht-v2.proto @@ -18,6 +18,6 @@ message SignedAuthorityRecord { bytes record = 1; bytes auth_signature = 2; // Even if there are multiple `record.addresses`, all of them have the same peer id. - // Old versions are missing this field, so `optional` will provide compatibility both ways. - optional PeerSignature peer_signature = 3; + // Old versions are missing this field. It is optional in order to provide compatibility both ways. + PeerSignature peer_signature = 3; } diff --git a/client/cli/Cargo.toml b/client/cli/Cargo.toml index 4f0d777d137b9..72b4efde077ff 100644 --- a/client/cli/Cargo.toml +++ b/client/cli/Cargo.toml @@ -18,7 +18,7 @@ clap = { version = "3.1.18", features = ["derive"] } fdlimit = "0.2.1" futures = "0.3.21" hex = "0.4.2" -libp2p = "0.44.0" +libp2p = "0.45.1" log = "0.4.17" names = { version = "0.13.0", default-features = false } parity-scale-codec = "3.0.0" diff --git a/client/consensus/common/Cargo.toml b/client/consensus/common/Cargo.toml index 1508dfa82a363..6d76eba0935ff 100644 --- a/client/consensus/common/Cargo.toml +++ b/client/consensus/common/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] async-trait = "0.1.42" futures = { version = "0.3.21", features = ["thread-pool"] } futures-timer = "3.0.1" -libp2p = { version = "0.44.0", default-features = false } +libp2p = { version = "0.45.1", default-features = false } log = "0.4.17" parking_lot = "0.12.0" serde = { version = "1.0", features = ["derive"] } diff --git a/client/network-gossip/Cargo.toml b/client/network-gossip/Cargo.toml index 641574db288d7..0fac96b0fdde0 100644 --- a/client/network-gossip/Cargo.toml +++ b/client/network-gossip/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] ahash = "0.7.6" futures = "0.3.21" futures-timer = "3.0.1" -libp2p = { version = "0.44.0", default-features = false } +libp2p = { version = "0.45.1", default-features = false } log = "0.4.17" lru = "0.7.5" tracing = "0.1.29" diff --git a/client/network/Cargo.toml b/client/network/Cargo.toml index 89a36bc483e6c..1b525844395b5 100644 --- a/client/network/Cargo.toml +++ b/client/network/Cargo.toml @@ -14,7 +14,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [build-dependencies] -prost-build = "0.9" +prost-build = "0.10" [dependencies] async-trait = "0.1" @@ -29,7 +29,7 @@ futures = "0.3.21" futures-timer = "3.0.2" hex = "0.4.0" ip_network = "0.4.1" -libp2p = "0.44.0" +libp2p = "0.45.1" linked_hash_set = "0.1.3" linked-hash-map = "0.5.4" log = "0.4.17" diff --git a/client/network/common/Cargo.toml b/client/network/common/Cargo.toml index c41a7895888ae..553eb57958a5d 100644 --- a/client/network/common/Cargo.toml +++ b/client/network/common/Cargo.toml @@ -14,13 +14,13 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [build-dependencies] -prost-build = "0.9" +prost-build = "0.10" [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", features = [ "derive", ] } futures = "0.3.21" -libp2p = "0.44.0" +libp2p = "0.45.1" smallvec = "1.8.0" sc-peerset = { version = "4.0.0-dev", path = "../../peerset" } diff --git a/client/network/light/Cargo.toml b/client/network/light/Cargo.toml index aa0b003e00763..924b8ed06ab0a 100644 --- a/client/network/light/Cargo.toml +++ b/client/network/light/Cargo.toml @@ -14,14 +14,14 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [build-dependencies] -prost-build = "0.9" +prost-build = "0.10" [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", features = [ "derive", ] } futures = "0.3.21" -libp2p = "0.44.0" +libp2p = "0.45.1" log = "0.4.16" prost = "0.10" sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } diff --git a/client/network/src/service.rs b/client/network/src/service.rs index edd30e9c9dee4..1cc717a50d039 100644 --- a/client/network/src/service.rs +++ b/client/network/src/service.rs @@ -379,7 +379,8 @@ where ) .substream_upgrade_protocol_override(upgrade::Version::V1Lazy) .notify_handler_buffer_size(NonZeroUsize::new(32).expect("32 != 0; qed")) - .connection_event_buffer_size(1024); + .connection_event_buffer_size(1024) + .max_negotiating_inbound_streams(2048); if let Some(spawner) = params.executor { struct SpawnImpl(F); impl + Send>>)> Executor for SpawnImpl { diff --git a/client/network/src/transport.rs b/client/network/src/transport.rs index 64b199e9d769e..883f828e7db51 100644 --- a/client/network/src/transport.rs +++ b/client/network/src/transport.rs @@ -55,12 +55,15 @@ pub fn build_transport( // Build the base layer of the transport. let transport = if !memory_only { let desktop_trans = tcp::TcpConfig::new().nodelay(true); - let desktop_trans = - websocket::WsConfig::new(desktop_trans.clone()).or_transport(desktop_trans); - let dns_init = futures::executor::block_on(dns::DnsConfig::system(desktop_trans.clone())); + let desktop_trans = websocket::WsConfig::new(desktop_trans) + .or_transport(tcp::TcpConfig::new().nodelay(true)); + let dns_init = futures::executor::block_on(dns::DnsConfig::system(desktop_trans)); EitherTransport::Left(if let Ok(dns) = dns_init { EitherTransport::Left(dns) } else { + let desktop_trans = tcp::TcpConfig::new().nodelay(true); + let desktop_trans = websocket::WsConfig::new(desktop_trans) + .or_transport(tcp::TcpConfig::new().nodelay(true)); EitherTransport::Right(desktop_trans.map_err(dns::DnsErr::Transport)) }) } else { diff --git a/client/network/sync/Cargo.toml b/client/network/sync/Cargo.toml index f11cc753203ef..9185e812bc6cd 100644 --- a/client/network/sync/Cargo.toml +++ b/client/network/sync/Cargo.toml @@ -14,7 +14,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [build-dependencies] -prost-build = "0.9" +prost-build = "0.10" [dependencies] bitflags = "1.3.2" @@ -23,7 +23,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", features = [ ] } either = "1.5.3" futures = "0.3.21" -libp2p = "0.44.0" +libp2p = "0.45.1" log = "0.4.17" lru = "0.7.5" prost = "0.10" diff --git a/client/network/test/Cargo.toml b/client/network/test/Cargo.toml index fa34feb22df1d..2af1dd8cb9b54 100644 --- a/client/network/test/Cargo.toml +++ b/client/network/test/Cargo.toml @@ -17,7 +17,7 @@ async-std = "1.11.0" async-trait = "0.1.50" futures = "0.3.21" futures-timer = "3.0.1" -libp2p = { version = "0.44.0", default-features = false } +libp2p = { version = "0.45.1", default-features = false } log = "0.4.17" parking_lot = "0.12.0" rand = "0.7.2" diff --git a/client/peerset/Cargo.toml b/client/peerset/Cargo.toml index 29e67c73c18da..f150c728613ba 100644 --- a/client/peerset/Cargo.toml +++ b/client/peerset/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] futures = "0.3.21" -libp2p = { version = "0.44.0", default-features = false } +libp2p = { version = "0.45.1", default-features = false } log = "0.4.17" serde_json = "1.0.79" wasm-timer = "0.2" diff --git a/client/telemetry/Cargo.toml b/client/telemetry/Cargo.toml index e8abdfee44a2e..9682dc824f930 100644 --- a/client/telemetry/Cargo.toml +++ b/client/telemetry/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] chrono = "0.4.19" futures = "0.3.21" -libp2p = { version = "0.44.0", default-features = false, features = ["dns-async-std", "tcp-async-io", "wasm-ext", "websocket"] } +libp2p = { version = "0.45.1", default-features = false, features = ["dns-async-std", "tcp-async-io", "wasm-ext", "websocket"] } log = "0.4.17" parking_lot = "0.12.0" pin-project = "1.0.10" diff --git a/client/telemetry/src/lib.rs b/client/telemetry/src/lib.rs index fc40f999a6779..503a326f76c2b 100644 --- a/client/telemetry/src/lib.rs +++ b/client/telemetry/src/lib.rs @@ -42,7 +42,10 @@ use log::{error, warn}; use parking_lot::Mutex; use serde::Serialize; use std::{ - collections::HashMap, + collections::{ + hash_map::Entry::{Occupied, Vacant}, + HashMap, + }, sync::{atomic, Arc}, }; @@ -147,7 +150,6 @@ pub struct TelemetryWorker { register_receiver: mpsc::UnboundedReceiver, register_sender: mpsc::UnboundedSender, id_counter: Arc, - transport: WsTrans, } impl TelemetryWorker { @@ -155,7 +157,11 @@ impl TelemetryWorker { /// /// Only one is needed per process. pub fn new(buffer_size: usize) -> Result { - let transport = initialize_transport()?; + // Let's try to initialize a transport to get an early return. + // Later transport will be initialized multiple times in + // `::process_register`, so it's a convenient way to get an + // error as early as possible. + let _transport = initialize_transport()?; let (message_sender, message_receiver) = mpsc::channel(buffer_size); let (register_sender, register_receiver) = mpsc::unbounded(); @@ -165,7 +171,6 @@ impl TelemetryWorker { register_receiver, register_sender, id_counter: Arc::new(atomic::AtomicU64::new(1)), - transport, }) } @@ -200,7 +205,6 @@ impl TelemetryWorker { &mut node_pool, &mut node_map, &mut pending_connection_notifications, - self.transport.clone(), ).await, } } @@ -211,7 +215,6 @@ impl TelemetryWorker { node_pool: &mut HashMap>, node_map: &mut HashMap>, pending_connection_notifications: &mut Vec<(Multiaddr, ConnectionNotifierSender)>, - transport: WsTrans, ) { let input = input.expect("the stream is never closed; qed"); @@ -248,9 +251,24 @@ impl TelemetryWorker { ); node_map.entry(id).or_default().push((verbosity, addr.clone())); - let node = node_pool.entry(addr.clone()).or_insert_with(|| { - Node::new(transport.clone(), addr.clone(), Vec::new(), Vec::new()) - }); + let node = match node_pool.entry(addr.clone()) { + Occupied(entry) => entry.into_mut(), + Vacant(entry) => { + let transport = initialize_transport(); + let transport = match transport { + Ok(t) => t, + Err(err) => { + log::error!( + target: "telemetry", + "Could not initialise transport: {}", + err, + ); + continue + }, + }; + entry.insert(Node::new(transport, addr.clone(), Vec::new(), Vec::new())) + }, + }; node.connection_messages.extend(connection_message.clone()); diff --git a/client/telemetry/src/node.rs b/client/telemetry/src/node.rs index aa0f5a3843d33..0d71a363a1b26 100644 --- a/client/telemetry/src/node.rs +++ b/client/telemetry/src/node.rs @@ -110,7 +110,6 @@ impl Node { impl Node where - TTrans: Clone + Unpin, TTrans::Dial: Unpin, TTrans::Output: Sink, Error = TSinkErr> + Stream, TSinkErr>> + Unpin, @@ -137,7 +136,7 @@ pub(crate) enum Infallible {} impl Sink for Node where - TTrans: Clone + Unpin, + TTrans: Unpin, TTrans::Dial: Unpin, TTrans::Output: Sink, Error = TSinkErr> + Stream, TSinkErr>> + Unpin, @@ -228,15 +227,18 @@ where socket = NodeSocket::wait_reconnect(); }, }, - NodeSocket::ReconnectNow => match self.transport.clone().dial(self.addr.clone()) { - Ok(d) => { - log::trace!(target: "telemetry", "Re-dialing {}", self.addr); - socket = NodeSocket::Dialing(d); - }, - Err(err) => { - log::warn!(target: "telemetry", "❌ Error while re-dialing {}: {:?}", self.addr, err); - socket = NodeSocket::wait_reconnect(); - }, + NodeSocket::ReconnectNow => { + let addr = self.addr.clone(); + match self.transport.dial(addr) { + Ok(d) => { + log::trace!(target: "telemetry", "Re-dialing {}", self.addr); + socket = NodeSocket::Dialing(d); + }, + Err(err) => { + log::warn!(target: "telemetry", "❌ Error while re-dialing {}: {:?}", self.addr, err); + socket = NodeSocket::wait_reconnect(); + }, + } }, NodeSocket::WaitingReconnect(mut s) => { if Future::poll(Pin::new(&mut s), cx).is_ready() { From 8ae05746161b2d3b6daed1b1d23f705990a24d31 Mon Sep 17 00:00:00 2001 From: yjh Date: Sat, 18 Jun 2022 06:14:50 +0800 Subject: [PATCH 350/484] chore: reducing codec times (#11688) --- client/basic-authorship/src/basic_authorship.rs | 6 +----- primitives/consensus/common/src/evaluation.rs | 15 ++++++++++----- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/client/basic-authorship/src/basic_authorship.rs b/client/basic-authorship/src/basic_authorship.rs index 5a020ee81050e..b67008bc6f44b 100644 --- a/client/basic-authorship/src/basic_authorship.rs +++ b/client/basic-authorship/src/basic_authorship.rs @@ -20,7 +20,7 @@ // FIXME #1021 move this into sp-consensus -use codec::{Decode, Encode}; +use codec::Encode; use futures::{ channel::oneshot, future, @@ -536,10 +536,6 @@ where "hash" => ?::Hash::from(block.header().hash()), ); - if Decode::decode(&mut block.encode().as_slice()).as_ref() != Ok(&block) { - error!("Failed to verify block encoding/decoding"); - } - if let Err(err) = evaluation::evaluate_initial(&block, &self.parent_hash, self.parent_number) { diff --git a/primitives/consensus/common/src/evaluation.rs b/primitives/consensus/common/src/evaluation.rs index 6a6ca835f3153..d1ce8e9fc5109 100644 --- a/primitives/consensus/common/src/evaluation.rs +++ b/primitives/consensus/common/src/evaluation.rs @@ -30,6 +30,8 @@ pub type Result = std::result::Result; /// Error type. #[derive(Debug, thiserror::Error)] pub enum Error { + #[error("Failed to verify block encoding/decoding")] + BadBlockCodec, /// Proposal provided not a block. #[error("Proposal provided not a block: decoding error: {0}")] BadProposalFormat(#[from] codec::Error), @@ -49,19 +51,22 @@ pub fn evaluate_initial( parent_number: <::Header as HeaderT>::Number, ) -> Result<()> { let encoded = Encode::encode(proposal); - let proposal = Block::decode(&mut &encoded[..]).map_err(Error::BadProposalFormat)?; + let block = Block::decode(&mut &encoded[..]).map_err(Error::BadProposalFormat)?; + if &block != proposal { + return Err(Error::BadBlockCodec) + } - if *parent_hash != *proposal.header().parent_hash() { + if *parent_hash != *block.header().parent_hash() { return Err(Error::WrongParentHash { expected: format!("{:?}", *parent_hash), - got: format!("{:?}", proposal.header().parent_hash()), + got: format!("{:?}", block.header().parent_hash()), }) } - if parent_number + One::one() != *proposal.header().number() { + if parent_number + One::one() != *block.header().number() { return Err(Error::WrongNumber { expected: parent_number.checked_into::().map(|x| x + 1), - got: (*proposal.header().number()).checked_into::(), + got: (*block.header().number()).checked_into::(), }) } From 1d7bb2962aedd723294e15ebd947994bdb3d42a3 Mon Sep 17 00:00:00 2001 From: yjh Date: Sun, 19 Jun 2022 06:43:11 +0800 Subject: [PATCH 351/484] chore: reduce uxt encode times (#11698) * chore: reduce uxt encode times * fmt --- frame/executive/src/lib.rs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index 60bf0a47ca120..c40fdf94806aa 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -435,24 +435,15 @@ where sp_io::init_tracing(); let encoded = uxt.encode(); let encoded_len = encoded.len(); - Self::apply_extrinsic_with_len(uxt, encoded_len, encoded) - } - - /// Actually apply an extrinsic given its `encoded_len`; this doesn't note its hash. - fn apply_extrinsic_with_len( - uxt: Block::Extrinsic, - encoded_len: usize, - to_note: Vec, - ) -> ApplyExtrinsicResult { sp_tracing::enter_span!(sp_tracing::info_span!("apply_extrinsic", - ext=?sp_core::hexdisplay::HexDisplay::from(&uxt.encode()))); + ext=?sp_core::hexdisplay::HexDisplay::from(&encoded))); // Verify that the signature is good. let xt = uxt.check(&Default::default())?; // We don't need to make sure to `note_extrinsic` only after we know it's going to be // executed to prevent it from leaking in storage since at this point, it will either // execute or panic (and revert storage changes). - >::note_extrinsic(to_note); + >::note_extrinsic(encoded); // AUDIT: Under no circumstances may this function panic from here onwards. From 6cbac4ca636be9f8588880b64c7176f04404f14a Mon Sep 17 00:00:00 2001 From: Zhenghao Lu <54395432+EmisonLu@users.noreply.github.com> Date: Sun, 19 Jun 2022 07:56:48 +0800 Subject: [PATCH 352/484] Simplified code using existing APIs (#11702) Signed-off-by: Emison Lu --- client/executor/wasmtime/src/host.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/client/executor/wasmtime/src/host.rs b/client/executor/wasmtime/src/host.rs index 0cb64820fa8a3..b494795ae9dc1 100644 --- a/client/executor/wasmtime/src/host.rs +++ b/client/executor/wasmtime/src/host.rs @@ -145,11 +145,7 @@ impl<'a> sp_wasm_interface::FunctionContext for HostContext<'a> { } fn register_panic_error_message(&mut self, message: &str) { - self.caller - .data_mut() - .host_state_mut() - .expect("host state is not empty when calling a function in wasm; qed") - .panic_message = Some(message.to_owned()); + self.host_state_mut().panic_message = Some(message.to_owned()); } } From 86c465cff8763d5ba98458d1efdf3f95a777ba8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Sun, 19 Jun 2022 19:56:36 +0200 Subject: [PATCH 353/484] `storage-alias`: Check that prefix is not an underscore (#11704) Besides that it also adds some UI tests. --- frame/support/procedural/src/storage_alias.rs | 4 +++ frame/support/test/tests/storage_alias_ui.rs | 32 +++++++++++++++++++ .../checks_for_valid_storage_type.rs | 4 +++ .../checks_for_valid_storage_type.stderr | 5 +++ .../forbid_underscore_as_prefix.rs | 4 +++ .../forbid_underscore_as_prefix.stderr | 5 +++ .../prefix_must_be_an_ident.rs | 4 +++ .../prefix_must_be_an_ident.stderr | 5 +++ 8 files changed, 63 insertions(+) create mode 100644 frame/support/test/tests/storage_alias_ui.rs create mode 100644 frame/support/test/tests/storage_alias_ui/checks_for_valid_storage_type.rs create mode 100644 frame/support/test/tests/storage_alias_ui/checks_for_valid_storage_type.stderr create mode 100644 frame/support/test/tests/storage_alias_ui/forbid_underscore_as_prefix.rs create mode 100644 frame/support/test/tests/storage_alias_ui/forbid_underscore_as_prefix.stderr create mode 100644 frame/support/test/tests/storage_alias_ui/prefix_must_be_an_ident.rs create mode 100644 frame/support/test/tests/storage_alias_ui/prefix_must_be_an_ident.stderr diff --git a/frame/support/procedural/src/storage_alias.rs b/frame/support/procedural/src/storage_alias.rs index adec995016653..e0df0123595b9 100644 --- a/frame/support/procedural/src/storage_alias.rs +++ b/frame/support/procedural/src/storage_alias.rs @@ -512,6 +512,10 @@ fn generate_storage_instance( prefix_generics: Option<&TypeGenerics>, visibility: &Visibility, ) -> Result { + if let Some(ident) = prefix.get_ident().filter(|i| *i == "_") { + return Err(Error::new(ident.span(), "`_` is not allowed as prefix by `storage_alias`.")) + } + let (pallet_prefix, impl_generics, type_generics) = if let Some((prefix_generics, storage_generics)) = prefix_generics.and_then(|p| storage_generics.map(|s| (p, s))) diff --git a/frame/support/test/tests/storage_alias_ui.rs b/frame/support/test/tests/storage_alias_ui.rs new file mode 100644 index 0000000000000..d45d071578dab --- /dev/null +++ b/frame/support/test/tests/storage_alias_ui.rs @@ -0,0 +1,32 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#[rustversion::attr(not(stable), ignore)] +#[cfg(not(feature = "disable-ui-tests"))] +#[test] +fn storage_alias_ui() { + // Only run the ui tests when `RUN_UI_TESTS` is set. + if std::env::var("RUN_UI_TESTS").is_err() { + return + } + + // As trybuild is using `cargo check`, we don't need the real WASM binaries. + std::env::set_var("SKIP_WASM_BUILD", "1"); + + let t = trybuild::TestCases::new(); + t.compile_fail("tests/storage_alias_ui/*.rs"); +} diff --git a/frame/support/test/tests/storage_alias_ui/checks_for_valid_storage_type.rs b/frame/support/test/tests/storage_alias_ui/checks_for_valid_storage_type.rs new file mode 100644 index 0000000000000..4ed9d5adfec77 --- /dev/null +++ b/frame/support/test/tests/storage_alias_ui/checks_for_valid_storage_type.rs @@ -0,0 +1,4 @@ +#[frame_support::storage_alias] +type Ident = StorageValue; + +fn main() {} diff --git a/frame/support/test/tests/storage_alias_ui/checks_for_valid_storage_type.stderr b/frame/support/test/tests/storage_alias_ui/checks_for_valid_storage_type.stderr new file mode 100644 index 0000000000000..726efed400715 --- /dev/null +++ b/frame/support/test/tests/storage_alias_ui/checks_for_valid_storage_type.stderr @@ -0,0 +1,5 @@ +error: If there are no generics, the prefix is only allowed to be an identifier. + --> tests/storage_alias_ui/checks_for_valid_storage_type.rs:2:27 + | +2 | type Ident = StorageValue; + | ^^^^^^^^^^^^ diff --git a/frame/support/test/tests/storage_alias_ui/forbid_underscore_as_prefix.rs b/frame/support/test/tests/storage_alias_ui/forbid_underscore_as_prefix.rs new file mode 100644 index 0000000000000..59d8004bbe620 --- /dev/null +++ b/frame/support/test/tests/storage_alias_ui/forbid_underscore_as_prefix.rs @@ -0,0 +1,4 @@ +#[frame_support::storage_alias] +type Ident = CustomStorage; + +fn main() {} diff --git a/frame/support/test/tests/storage_alias_ui/forbid_underscore_as_prefix.stderr b/frame/support/test/tests/storage_alias_ui/forbid_underscore_as_prefix.stderr new file mode 100644 index 0000000000000..3aa517ecfa314 --- /dev/null +++ b/frame/support/test/tests/storage_alias_ui/forbid_underscore_as_prefix.stderr @@ -0,0 +1,5 @@ +error: expected one of: `StorageValue`, `StorageMap`, `StorageDoubleMap`, `StorageNMap` + --> tests/storage_alias_ui/forbid_underscore_as_prefix.rs:2:14 + | +2 | type Ident = CustomStorage; + | ^^^^^^^^^^^^^ diff --git a/frame/support/test/tests/storage_alias_ui/prefix_must_be_an_ident.rs b/frame/support/test/tests/storage_alias_ui/prefix_must_be_an_ident.rs new file mode 100644 index 0000000000000..79328268dc925 --- /dev/null +++ b/frame/support/test/tests/storage_alias_ui/prefix_must_be_an_ident.rs @@ -0,0 +1,4 @@ +#[frame_support::storage_alias] +type NoUnderscore = StorageValue<_, u32>; + +fn main() {} diff --git a/frame/support/test/tests/storage_alias_ui/prefix_must_be_an_ident.stderr b/frame/support/test/tests/storage_alias_ui/prefix_must_be_an_ident.stderr new file mode 100644 index 0000000000000..abb7bf2518f4f --- /dev/null +++ b/frame/support/test/tests/storage_alias_ui/prefix_must_be_an_ident.stderr @@ -0,0 +1,5 @@ +error: `_` is not allowed as prefix by `storage_alias`. + --> tests/storage_alias_ui/prefix_must_be_an_ident.rs:2:34 + | +2 | type NoUnderscore = StorageValue<_, u32>; + | ^ From 2e70f216b1efafad5774e3599f93f28868512c7f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 19 Jun 2022 21:01:38 +0200 Subject: [PATCH 354/484] Bump twox-hash from 1.6.2 to 1.6.3 (#11423) Bumps [twox-hash](https://github.com/shepmaster/twox-hash) from 1.6.2 to 1.6.3. - [Release notes](https://github.com/shepmaster/twox-hash/releases) - [Commits](https://github.com/shepmaster/twox-hash/compare/v1.6.2...v1.6.3) --- updated-dependencies: - dependency-name: twox-hash dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- primitives/core/hashing/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e33211911b47a..9c76d25474679 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11337,9 +11337,9 @@ checksum = "5e66dcbec4290c69dd03c57e76c2469ea5c7ce109c6dd4351c13055cf71ea055" [[package]] name = "twox-hash" -version = "1.6.2" +version = "1.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee73e6e4924fe940354b8d4d98cad5231175d615cd855b758adc658c0aac6a0" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ "cfg-if 1.0.0", "digest 0.10.3", diff --git a/primitives/core/hashing/Cargo.toml b/primitives/core/hashing/Cargo.toml index c83e48e563010..d85e28d1b2e56 100644 --- a/primitives/core/hashing/Cargo.toml +++ b/primitives/core/hashing/Cargo.toml @@ -18,7 +18,7 @@ byteorder = { version = "1.3.2", default-features = false } digest = { version = "0.10.3", default-features = false } sha2 = { version = "0.10.2", default-features = false } sha3 = { version = "0.10.0", default-features = false } -twox-hash = { version = "1.6.2", default-features = false, features = ["digest_0_10"] } +twox-hash = { version = "1.6.3", default-features = false, features = ["digest_0_10"] } sp-std = { version = "4.0.0", default-features = false, path = "../../std" } [features] From ff6012d6b56bd7b66958b33712bd03df90f2db45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Mon, 20 Jun 2022 11:53:41 +0200 Subject: [PATCH 355/484] Update syn and fix compilation (#11707) * Update syn and fix compilation * Bump pin-project --- Cargo.lock | 30 +++++++++++-------- client/chain-spec/derive/Cargo.toml | 2 +- client/tracing/proc-macro/Cargo.toml | 2 +- frame/contracts/proc-macro/Cargo.toml | 2 +- .../solution-type/Cargo.toml | 2 +- frame/staking/reward-curve/Cargo.toml | 2 +- frame/support/procedural/Cargo.toml | 2 +- frame/support/procedural/tools/Cargo.toml | 2 +- .../procedural/tools/derive/Cargo.toml | 2 +- frame/support/procedural/tools/src/syn_ext.rs | 9 +++--- primitives/api/proc-macro/Cargo.toml | 2 +- primitives/core/hashing/proc-macro/Cargo.toml | 2 +- primitives/debug-derive/Cargo.toml | 2 +- .../runtime-interface/proc-macro/Cargo.toml | 2 +- primitives/version/proc-macro/Cargo.toml | 2 +- test-utils/derive/Cargo.toml | 2 +- 16 files changed, 37 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9c76d25474679..d7b1350c1f529 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6809,11 +6809,11 @@ dependencies = [ [[package]] name = "pin-project" -version = "0.4.27" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffbc8e94b38ea3d2d8ba92aea2983b503cd75d0888d75b86bb37970b5698e15" +checksum = "9615c18d31137579e9ff063499264ddc1278e7b1982757ebc111028c4d1dc909" dependencies = [ - "pin-project-internal 0.4.27", + "pin-project-internal 0.4.29", ] [[package]] @@ -6827,9 +6827,9 @@ dependencies = [ [[package]] name = "pin-project-internal" -version = "0.4.27" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65ad2ae56b6abe3a1ee25f15ee605bacadb9a764edaba9c2bf4103800d4a1895" +checksum = "044964427019eed9d49d9d5bbce6047ef18f37100ea400912a9fa4a3523ab12a" dependencies = [ "proc-macro2", "quote", @@ -7041,11 +7041,11 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro2" -version = "1.0.38" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9027b48e9d4c9175fa2218adf3557f91c1137021739951d4932f5f8268ac48aa" +checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] @@ -7885,7 +7885,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4da5fcb054c46f5a5dff833b129285a93d3f0179531735e6c866e8cc307d2020" dependencies = [ "futures", - "pin-project 0.4.27", + "pin-project 0.4.29", "static_assertions", ] @@ -10776,13 +10776,13 @@ checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2" [[package]] name = "syn" -version = "1.0.82" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8daf5dd0bb60cbd4137b1b587d2fc0ae729bc07cf01cd70b36a1ed5ade3b9d59" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", ] [[package]] @@ -11389,6 +11389,12 @@ dependencies = [ "matches", ] +[[package]] +name = "unicode-ident" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" + [[package]] name = "unicode-normalization" version = "0.1.17" diff --git a/client/chain-spec/derive/Cargo.toml b/client/chain-spec/derive/Cargo.toml index 06f397e1e7b10..d733bda9b1692 100644 --- a/client/chain-spec/derive/Cargo.toml +++ b/client/chain-spec/derive/Cargo.toml @@ -18,4 +18,4 @@ proc-macro = true proc-macro-crate = "1.1.3" proc-macro2 = "1.0.37" quote = "1.0.10" -syn = "1.0.82" +syn = "1.0.98" diff --git a/client/tracing/proc-macro/Cargo.toml b/client/tracing/proc-macro/Cargo.toml index b55c3482be3f4..031f1fe49d970 100644 --- a/client/tracing/proc-macro/Cargo.toml +++ b/client/tracing/proc-macro/Cargo.toml @@ -18,4 +18,4 @@ proc-macro = true proc-macro-crate = "1.1.3" proc-macro2 = "1.0.37" quote = { version = "1.0.10", features = ["proc-macro"] } -syn = { version = "1.0.82", features = ["proc-macro", "full", "extra-traits", "parsing"] } +syn = { version = "1.0.98", features = ["proc-macro", "full", "extra-traits", "parsing"] } diff --git a/frame/contracts/proc-macro/Cargo.toml b/frame/contracts/proc-macro/Cargo.toml index db3c620397771..aca1d86ac24a1 100644 --- a/frame/contracts/proc-macro/Cargo.toml +++ b/frame/contracts/proc-macro/Cargo.toml @@ -17,7 +17,7 @@ proc-macro = true [dependencies] proc-macro2 = "1" quote = "1" -syn = "1" +syn = "1.0.98" [dev-dependencies] diff --git a/frame/election-provider-support/solution-type/Cargo.toml b/frame/election-provider-support/solution-type/Cargo.toml index 8fd7ddbff434d..a7ce4fa662131 100644 --- a/frame/election-provider-support/solution-type/Cargo.toml +++ b/frame/election-provider-support/solution-type/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -syn = { version = "1.0.82", features = ["full", "visit"] } +syn = { version = "1.0.98", features = ["full", "visit"] } quote = "1.0" proc-macro2 = "1.0.37" proc-macro-crate = "1.1.3" diff --git a/frame/staking/reward-curve/Cargo.toml b/frame/staking/reward-curve/Cargo.toml index 0307f61b1dce6..9e561fea4575b 100644 --- a/frame/staking/reward-curve/Cargo.toml +++ b/frame/staking/reward-curve/Cargo.toml @@ -18,7 +18,7 @@ proc-macro = true proc-macro-crate = "1.1.3" proc-macro2 = "1.0.37" quote = "1.0.10" -syn = { version = "1.0.82", features = ["full", "visit"] } +syn = { version = "1.0.98", features = ["full", "visit"] } [dev-dependencies] sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } diff --git a/frame/support/procedural/Cargo.toml b/frame/support/procedural/Cargo.toml index f8b325479c31d..7ddec39cad9fb 100644 --- a/frame/support/procedural/Cargo.toml +++ b/frame/support/procedural/Cargo.toml @@ -18,7 +18,7 @@ proc-macro = true Inflector = "0.11.4" proc-macro2 = "1.0.37" quote = "1.0.10" -syn = { version = "1.0.82", features = ["full"] } +syn = { version = "1.0.98", features = ["full"] } frame-support-procedural-tools = { version = "4.0.0-dev", path = "./tools" } [features] diff --git a/frame/support/procedural/tools/Cargo.toml b/frame/support/procedural/tools/Cargo.toml index f76b2480f9838..755483f7db9aa 100644 --- a/frame/support/procedural/tools/Cargo.toml +++ b/frame/support/procedural/tools/Cargo.toml @@ -15,5 +15,5 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro-crate = "1.1.3" proc-macro2 = "1.0.37" quote = "1.0.10" -syn = { version = "1.0.82", features = ["full", "visit", "extra-traits"] } +syn = { version = "1.0.98", features = ["full", "visit", "extra-traits"] } frame-support-procedural-tools-derive = { version = "3.0.0", path = "./derive" } diff --git a/frame/support/procedural/tools/derive/Cargo.toml b/frame/support/procedural/tools/derive/Cargo.toml index 6aface4ae0937..a688ee13a7d17 100644 --- a/frame/support/procedural/tools/derive/Cargo.toml +++ b/frame/support/procedural/tools/derive/Cargo.toml @@ -17,4 +17,4 @@ proc-macro = true [dependencies] proc-macro2 = "1.0.37" quote = { version = "1.0.10", features = ["proc-macro"] } -syn = { version = "1.0.82", features = ["proc-macro", "full", "extra-traits", "parsing"] } +syn = { version = "1.0.98", features = ["proc-macro", "full", "extra-traits", "parsing"] } diff --git a/frame/support/procedural/tools/src/syn_ext.rs b/frame/support/procedural/tools/src/syn_ext.rs index 25c98faaf388c..1a2d7c1d372ad 100644 --- a/frame/support/procedural/tools/src/syn_ext.rs +++ b/frame/support/procedural/tools/src/syn_ext.rs @@ -47,7 +47,8 @@ macro_rules! groups_impl { impl Parse for $name

{ fn parse(input: ParseStream) -> Result { - let syn::group::$name { token, content } = syn::group::$parse(input)?; + let content; + let token = syn::$parse!(content in input); let content = content.parse()?; Ok($name { token, content }) } @@ -71,9 +72,9 @@ macro_rules! groups_impl { }; } -groups_impl!(Braces, Brace, Brace, parse_braces); -groups_impl!(Brackets, Bracket, Bracket, parse_brackets); -groups_impl!(Parens, Paren, Parenthesis, parse_parens); +groups_impl!(Braces, Brace, Brace, braced); +groups_impl!(Brackets, Bracket, Bracket, bracketed); +groups_impl!(Parens, Paren, Parenthesis, parenthesized); #[derive(Debug)] pub struct PunctuatedInner { diff --git a/primitives/api/proc-macro/Cargo.toml b/primitives/api/proc-macro/Cargo.toml index 24ce1360c80f7..0a57be8d7a300 100644 --- a/primitives/api/proc-macro/Cargo.toml +++ b/primitives/api/proc-macro/Cargo.toml @@ -17,7 +17,7 @@ proc-macro = true [dependencies] quote = "1.0.10" -syn = { version = "1.0.82", features = ["full", "fold", "extra-traits", "visit"] } +syn = { version = "1.0.98", features = ["full", "fold", "extra-traits", "visit"] } proc-macro2 = "1.0.37" blake2 = { version = "0.10.2", default-features = false } proc-macro-crate = "1.1.3" diff --git a/primitives/core/hashing/proc-macro/Cargo.toml b/primitives/core/hashing/proc-macro/Cargo.toml index 0d8431facb874..8f4f4e0c873ef 100644 --- a/primitives/core/hashing/proc-macro/Cargo.toml +++ b/primitives/core/hashing/proc-macro/Cargo.toml @@ -18,5 +18,5 @@ proc-macro = true [dependencies] proc-macro2 = "1.0.37" quote = "1.0.6" -syn = { version = "1.0.82", features = ["full", "parsing"] } +syn = { version = "1.0.98", features = ["full", "parsing"] } sp-core-hashing = { version = "4.0.0", default-features = false, path = "../" } diff --git a/primitives/debug-derive/Cargo.toml b/primitives/debug-derive/Cargo.toml index 5852bd428d3e7..50bb0ec65c0ac 100644 --- a/primitives/debug-derive/Cargo.toml +++ b/primitives/debug-derive/Cargo.toml @@ -18,7 +18,7 @@ proc-macro = true [dependencies] quote = "1.0.10" -syn = "1.0.82" +syn = "1.0.98" proc-macro2 = "1.0" [features] diff --git a/primitives/runtime-interface/proc-macro/Cargo.toml b/primitives/runtime-interface/proc-macro/Cargo.toml index a3ae1fd48651c..6f6b71dc24658 100644 --- a/primitives/runtime-interface/proc-macro/Cargo.toml +++ b/primitives/runtime-interface/proc-macro/Cargo.toml @@ -20,4 +20,4 @@ Inflector = "0.11.4" proc-macro-crate = "1.1.3" proc-macro2 = "1.0.37" quote = "1.0.10" -syn = { version = "1.0.82", features = ["full", "visit", "fold", "extra-traits"] } +syn = { version = "1.0.98", features = ["full", "visit", "fold", "extra-traits"] } diff --git a/primitives/version/proc-macro/Cargo.toml b/primitives/version/proc-macro/Cargo.toml index 5b544c7ddd07d..3d129d4753a15 100644 --- a/primitives/version/proc-macro/Cargo.toml +++ b/primitives/version/proc-macro/Cargo.toml @@ -19,7 +19,7 @@ proc-macro = true codec = { package = "parity-scale-codec", version = "3.0.0", features = [ "derive" ] } proc-macro2 = "1.0.37" quote = "1.0.10" -syn = { version = "1.0.82", features = ["full", "fold", "extra-traits", "visit"] } +syn = { version = "1.0.98", features = ["full", "fold", "extra-traits", "visit"] } [dev-dependencies] sp-version = { version = "5.0.0", path = ".." } diff --git a/test-utils/derive/Cargo.toml b/test-utils/derive/Cargo.toml index aa4f9bb5eab85..4494b55220ef2 100644 --- a/test-utils/derive/Cargo.toml +++ b/test-utils/derive/Cargo.toml @@ -12,7 +12,7 @@ description = "Substrate test utilities macros" proc-macro-crate = "1.1.3" proc-macro2 = "1.0.37" quote = "1.0.10" -syn = { version = "1.0.82", features = ["full"] } +syn = { version = "1.0.98", features = ["full"] } [lib] proc-macro = true From de8600d5458f576c23dfecb154b08725cc2a0f31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Paulo=20Silva=20de=20Souza?= <77391175+joao-paulo-parity@users.noreply.github.com> Date: Mon, 20 Jun 2022 09:18:06 -0300 Subject: [PATCH 356/484] check-dependent-cumulus should only be executed for PRs (#11693) the script executed by check-dependent-cumulus only works for PRs, as shown in https://gitlab.parity.io/parity/mirrors/substrate/-/jobs/1630771#L87, which comes from https://github.com/paritytech/pipeline-scripts/blob/3ad10ddc0d985ef5326974a1143229c6429befab/check_dependent_project.sh#L443 --- scripts/ci/gitlab/pipeline/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/ci/gitlab/pipeline/build.yml b/scripts/ci/gitlab/pipeline/build.yml index da6391fb7c278..25ecad5bc5264 100644 --- a/scripts/ci/gitlab/pipeline/build.yml +++ b/scripts/ci/gitlab/pipeline/build.yml @@ -42,6 +42,8 @@ check-dependent-cumulus: COMPANION_OVERRIDES: | substrate: polkadot-v* polkadot: release-v* + rules: + - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ #PRs build-linux-substrate: stage: build From 3a86efce3474efd308afe54ab8b2d79c88aa2b0d Mon Sep 17 00:00:00 2001 From: Koute Date: Tue, 21 Jun 2022 00:52:43 +0900 Subject: [PATCH 357/484] Pump the gossip engine while waiting for the BEEFY runtime pallet (memory leak fix) (#11694) * Pump the gossip engine while waiting for the BEEFY runtime pallet This fixes a memory leak when the BEEFY gadget is turned on, but the runtime doesn't actually use BEEFY. * Implement `FusedFuture` for `GossipEngine` * Fuse futures outside of loops --- client/beefy/src/worker.rs | 77 +++++++++++++++-------------- client/network-gossip/src/bridge.rs | 15 +++++- 2 files changed, 55 insertions(+), 37 deletions(-) diff --git a/client/beefy/src/worker.rs b/client/beefy/src/worker.rs index ae466a71abb57..735dea0170a62 100644 --- a/client/beefy/src/worker.rs +++ b/client/beefy/src/worker.rs @@ -25,11 +25,10 @@ use std::{ }; use codec::{Codec, Decode, Encode}; -use futures::{future, FutureExt, StreamExt}; +use futures::StreamExt; use log::{debug, error, info, log_enabled, trace, warn}; -use parking_lot::Mutex; -use sc_client_api::{Backend, FinalityNotification, FinalityNotifications}; +use sc_client_api::{Backend, FinalityNotification}; use sc_network_gossip::GossipEngine; use sp_api::{BlockId, ProvideRuntimeApi}; @@ -80,7 +79,7 @@ pub(crate) struct BeefyWorker { runtime: Arc, key_store: BeefyKeystore, signed_commitment_sender: BeefySignedCommitmentSender, - gossip_engine: Arc>>, + gossip_engine: GossipEngine, gossip_validator: Arc>, /// Min delta in block numbers between two blocks, BEEFY should vote on min_block_delta: u32, @@ -88,7 +87,6 @@ pub(crate) struct BeefyWorker { rounds: Option>, /// Buffer holding votes for blocks that the client hasn't seen finality for. pending_votes: BTreeMap, Vec, AuthorityId, Signature>>>, - finality_notifications: FinalityNotifications, /// Best block we received a GRANDPA notification for best_grandpa_block_header: ::Header, /// Best block a BEEFY voting round has been concluded for @@ -143,14 +141,13 @@ where runtime, key_store, signed_commitment_sender, - gossip_engine: Arc::new(Mutex::new(gossip_engine)), + gossip_engine, gossip_validator, // always target at least one block better than current best beefy min_block_delta: min_block_delta.max(1), metrics, rounds: None, pending_votes: BTreeMap::new(), - finality_notifications: client.finality_notification_stream(), best_grandpa_block_header: last_finalized_header, best_beefy_block: None, last_signed_id: 0, @@ -471,15 +468,21 @@ where true, ); - self.gossip_engine.lock().gossip_message(topic::(), encoded_message, false); + self.gossip_engine.gossip_message(topic::(), encoded_message, false); } /// Wait for BEEFY runtime pallet to be available. async fn wait_for_runtime_pallet(&mut self) { - self.client - .finality_notification_stream() - .take_while(|notif| { - let at = BlockId::hash(notif.header.hash()); + let mut gossip_engine = &mut self.gossip_engine; + let mut finality_stream = self.client.finality_notification_stream().fuse(); + loop { + futures::select! { + notif = finality_stream.next() => { + let notif = match notif { + Some(notif) => notif, + None => break + }; + let at = BlockId::hash(notif.header.hash()); if let Some(active) = self.runtime.runtime_api().validator_set(&at).ok().flatten() { if active.id() == GENESIS_AUTHORITY_SET_ID { // When starting from genesis, there is no session boundary digest. @@ -490,18 +493,18 @@ where // worker won't vote until it witnesses a session change. // Once we'll implement 'initial sync' (catch-up), the worker will be able to // start voting right away. - self.handle_finality_notification(notif); - future::ready(false) + self.handle_finality_notification(¬if); + break } else { trace!(target: "beefy", "🥩 Finality notification: {:?}", notif); debug!(target: "beefy", "🥩 Waiting for BEEFY pallet to become available..."); - future::ready(true) } - }) - .for_each(|_| future::ready(())) - .await; - // get a new stream that provides _new_ notifications (from here on out) - self.finality_notifications = self.client.finality_notification_stream(); + }, + _ = gossip_engine => { + break + } + } + } } /// Main loop for BEEFY worker. @@ -512,16 +515,20 @@ where info!(target: "beefy", "🥩 run BEEFY worker, best grandpa: #{:?}.", self.best_grandpa_block_header.number()); self.wait_for_runtime_pallet().await; - let mut votes = Box::pin(self.gossip_engine.lock().messages_for(topic::()).filter_map( - |notification| async move { - trace!(target: "beefy", "🥩 Got vote message: {:?}", notification); - - VoteMessage::, AuthorityId, Signature>::decode( - &mut ¬ification.message[..], - ) - .ok() - }, - )); + let mut finality_notifications = self.client.finality_notification_stream().fuse(); + let mut votes = Box::pin( + self.gossip_engine + .messages_for(topic::()) + .filter_map(|notification| async move { + trace!(target: "beefy", "🥩 Got vote message: {:?}", notification); + + VoteMessage::, AuthorityId, Signature>::decode( + &mut ¬ification.message[..], + ) + .ok() + }) + .fuse(), + ); loop { while self.sync_oracle.is_major_syncing() { @@ -529,18 +536,16 @@ where futures_timer::Delay::new(Duration::from_secs(5)).await; } - let engine = self.gossip_engine.clone(); - let gossip_engine = future::poll_fn(|cx| engine.lock().poll_unpin(cx)); - + let mut gossip_engine = &mut self.gossip_engine; futures::select! { - notification = self.finality_notifications.next().fuse() => { + notification = finality_notifications.next() => { if let Some(notification) = notification { self.handle_finality_notification(¬ification); } else { return; } }, - vote = votes.next().fuse() => { + vote = votes.next() => { if let Some(vote) = vote { let block_num = vote.commitment.block_number; if block_num > *self.best_grandpa_block_header.number() { @@ -563,7 +568,7 @@ where return; } }, - _ = gossip_engine.fuse() => { + _ = gossip_engine => { error!(target: "beefy", "🥩 Gossip engine has terminated."); return; } diff --git a/client/network-gossip/src/bridge.rs b/client/network-gossip/src/bridge.rs index 2e09e7cc614a4..2d086e89b4a10 100644 --- a/client/network-gossip/src/bridge.rs +++ b/client/network-gossip/src/bridge.rs @@ -53,6 +53,8 @@ pub struct GossipEngine { message_sinks: HashMap>>, /// Buffered messages (see [`ForwardingState`]). forwarding_state: ForwardingState, + + is_terminated: bool, } /// A gossip engine receives messages from the network via the `network_event_stream` and forwards @@ -94,6 +96,8 @@ impl GossipEngine { network_event_stream, message_sinks: HashMap::new(), forwarding_state: ForwardingState::Idle, + + is_terminated: false, } } @@ -214,7 +218,10 @@ impl Future for GossipEngine { Event::Dht(_) => {}, }, // The network event stream closed. Do the same for [`GossipValidator`]. - Poll::Ready(None) => return Poll::Ready(()), + Poll::Ready(None) => { + self.is_terminated = true; + return Poll::Ready(()) + }, Poll::Pending => break, } }, @@ -288,6 +295,12 @@ impl Future for GossipEngine { } } +impl futures::future::FusedFuture for GossipEngine { + fn is_terminated(&self) -> bool { + self.is_terminated + } +} + #[cfg(test)] mod tests { use super::*; From b82921896137e13d9a6fb5ced12818151c2b54ad Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Tue, 21 Jun 2022 08:49:43 +0200 Subject: [PATCH 358/484] Implement Serialize/Deserialize on WeakBoundedVec (#11713) * Implement Serialize/Deserialize on WeakBoundedVec * cargo fmt * Warn when there are too many elements while deserializing WeakBoundedVec --- .../runtime/src/bounded/weak_bounded_vec.rs | 70 ++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/primitives/runtime/src/bounded/weak_bounded_vec.rs b/primitives/runtime/src/bounded/weak_bounded_vec.rs index 08460f7096357..ed9f4bba62b55 100644 --- a/primitives/runtime/src/bounded/weak_bounded_vec.rs +++ b/primitives/runtime/src/bounded/weak_bounded_vec.rs @@ -25,6 +25,11 @@ use core::{ ops::{Deref, Index, IndexMut}, slice::SliceIndex, }; +#[cfg(feature = "std")] +use serde::{ + de::{Error, SeqAccess, Visitor}, + Deserialize, Deserializer, Serialize, +}; use sp_std::{marker::PhantomData, prelude::*}; /// A weakly bounded vector. @@ -34,9 +39,72 @@ use sp_std::{marker::PhantomData, prelude::*}; /// /// The length of the vec is not strictly bounded. Decoding a vec with more element that the bound /// is accepted, and some method allow to bypass the restriction with warnings. +#[cfg_attr(feature = "std", derive(Serialize), serde(transparent))] #[derive(Encode, scale_info::TypeInfo)] #[scale_info(skip_type_params(S))] -pub struct WeakBoundedVec(pub(super) Vec, PhantomData); +pub struct WeakBoundedVec( + pub(super) Vec, + #[cfg_attr(feature = "std", serde(skip_serializing))] PhantomData, +); + +#[cfg(feature = "std")] +impl<'de, T, S: Get> Deserialize<'de> for WeakBoundedVec +where + T: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct VecVisitor>(PhantomData<(T, S)>); + + impl<'de, T, S: Get> Visitor<'de> for VecVisitor + where + T: Deserialize<'de>, + { + type Value = Vec; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a sequence") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let size = seq.size_hint().unwrap_or(0); + let max = match usize::try_from(S::get()) { + Ok(n) => n, + Err(_) => return Err(A::Error::custom("can't convert to usize")), + }; + if size > max { + log::warn!( + target: "runtime", + "length of a bounded vector while deserializing is not respected.", + ); + } + let mut values = Vec::with_capacity(size); + + while let Some(value) = seq.next_element()? { + values.push(value); + if values.len() > max { + log::warn!( + target: "runtime", + "length of a bounded vector while deserializing is not respected.", + ); + } + } + + Ok(values) + } + } + + let visitor: VecVisitor = VecVisitor(PhantomData); + deserializer.deserialize_seq(visitor).map(|v| { + WeakBoundedVec::::try_from(v).map_err(|_| Error::custom("out of bounds")) + })? + } +} impl> Decode for WeakBoundedVec { fn decode(input: &mut I) -> Result { From f38db7506e43fa4074cd1f061f030b0a0f2b93b2 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Tue, 21 Jun 2022 11:50:51 +0200 Subject: [PATCH 359/484] More robust grandpa revert procedure (#11719) * More robust revert procedure Return an error if revert is called in a node that is not actively running grandpa, i.e. grandpa genesis data has not been initialized. Previous implementation was just firing an `unreachable!` code exception. Furthermore we skip revert hassle if there is nothing to revert. * Nit --- client/consensus/babe/src/lib.rs | 4 ++++ client/finality-grandpa/src/lib.rs | 12 ++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/client/consensus/babe/src/lib.rs b/client/consensus/babe/src/lib.rs index 84a3c618b3803..8478122375319 100644 --- a/client/consensus/babe/src/lib.rs +++ b/client/consensus/babe/src/lib.rs @@ -1873,7 +1873,11 @@ where { let best_number = client.info().best_number; let finalized = client.info().finalized_number; + let revertible = blocks.min(best_number - finalized); + if revertible == Zero::zero() { + return Ok(()) + } let revert_up_to_number = best_number - revertible; let revert_up_to_hash = client.hash(revert_up_to_number)?.ok_or(ClientError::Backend( diff --git a/client/finality-grandpa/src/lib.rs b/client/finality-grandpa/src/lib.rs index 17e04affa0f98..cb32957c0b0bf 100644 --- a/client/finality-grandpa/src/lib.rs +++ b/client/finality-grandpa/src/lib.rs @@ -1170,11 +1170,15 @@ fn local_authority_id( pub fn revert(client: Arc, blocks: NumberFor) -> ClientResult<()> where Block: BlockT, - Client: AuxStore + HeaderMetadata + HeaderBackend, + Client: AuxStore + HeaderMetadata + HeaderBackend, { let best_number = client.info().best_number; let finalized = client.info().finalized_number; + let revertible = blocks.min(best_number - finalized); + if revertible == Zero::zero() { + return Ok(()) + } let number = best_number - revertible; let hash = client @@ -1185,8 +1189,12 @@ where )))?; let info = client.info(); + let persistent_data: PersistentData = - aux_schema::load_persistent(&*client, info.genesis_hash, Zero::zero(), || unreachable!())?; + aux_schema::load_persistent(&*client, info.genesis_hash, Zero::zero(), || { + const MSG: &str = "Unexpected missing grandpa data during revert"; + Err(ClientError::Application(Box::from(MSG))) + })?; let shared_authority_set = persistent_data.authority_set; let mut authority_set = shared_authority_set.inner(); From 6f8f0ec9bc0c6b2892a7f4649b7bf98017982b22 Mon Sep 17 00:00:00 2001 From: Ikko Ashimine Date: Wed, 22 Jun 2022 04:12:57 +0900 Subject: [PATCH 360/484] Fix typo in weights.rs (#11724) overriden -> overridden --- frame/support/src/weights.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/support/src/weights.rs b/frame/support/src/weights.rs index a3df199496922..24240c7cc4e6a 100644 --- a/frame/support/src/weights.rs +++ b/frame/support/src/weights.rs @@ -680,7 +680,7 @@ where /// Calculates the fee from the passed `weight` according to the `polynomial`. /// - /// This should not be overriden in most circumstances. Calculation is done in the + /// This should not be overridden in most circumstances. Calculation is done in the /// `Balance` type and never overflows. All evaluation is saturating. fn weight_to_fee(weight: &Weight) -> Self::Balance { Self::polynomial() From 8558d9782a30792d79d06f7a6831685a8173b2b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Wed, 22 Jun 2022 11:02:59 +0200 Subject: [PATCH 361/484] WrapperOpaque: Use `decode_all` to decode from the `Vec` (#11726) This ensures that there isn't any extra data attached that isn't used. --- frame/support/src/traits/misc.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frame/support/src/traits/misc.rs b/frame/support/src/traits/misc.rs index 1f0ba1e769c24..ccbb47909d5f4 100644 --- a/frame/support/src/traits/misc.rs +++ b/frame/support/src/traits/misc.rs @@ -745,7 +745,7 @@ impl Encode for WrapperOpaque { impl Decode for WrapperOpaque { fn decode(input: &mut I) -> Result { - Ok(Self(T::decode(&mut &>::decode(input)?[..])?)) + Ok(Self(T::decode_all(&mut &>::decode(input)?[..])?)) } fn skip(input: &mut I) -> Result<(), codec::Error> { @@ -967,6 +967,10 @@ mod test { 2usize.pow(14) - 1 + 2 ); assert_eq!(>::max_encoded_len(), 2usize.pow(14) + 4); + + let data = 4u64; + // Ensure that we check that the `Vec` is consumed completly on decode. + assert!(WrapperOpaque::::decode(&mut &data.encode().encode()[..]).is_err()); } #[test] From c8cf43c4b280015c10398269eabaddf519236ac6 Mon Sep 17 00:00:00 2001 From: Sebastian Kunert Date: Wed, 22 Jun 2022 17:27:46 +0200 Subject: [PATCH 362/484] Respect cargo offline env variable in wasm builder (#11735) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Support offline env variable in wasm builder * Clean up * Improve checks Co-authored-by: Bastian Köcher * Update crate docs * Add docs to `lib.rs` and introduce helper method `offline_build` Co-authored-by: Bastian Köcher --- utils/wasm-builder/README.md | 1 + utils/wasm-builder/src/lib.rs | 5 ++++ utils/wasm-builder/src/wasm_project.rs | 32 ++++++++++++++++++++------ 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/utils/wasm-builder/README.md b/utils/wasm-builder/README.md index 3868faf1acab5..547468c7ca11d 100644 --- a/utils/wasm-builder/README.md +++ b/utils/wasm-builder/README.md @@ -64,6 +64,7 @@ By using environment variables, you can configure which Wasm binaries are built to be absolute. - `WASM_BUILD_TOOLCHAIN` - The toolchain that should be used to build the Wasm binaries. The format needs to be the same as used by cargo, e.g. `nightly-2020-02-20`. +- `CARGO_NET_OFFLINE` - If `true`, `--offline` will be passed to all processes launched to prevent network access. Useful in offline environments. Each project can be skipped individually by using the environment variable `SKIP_PROJECT_NAME_WASM_BUILD`. Where `PROJECT_NAME` needs to be replaced by the name of the cargo project, e.g. `node-runtime` will diff --git a/utils/wasm-builder/src/lib.rs b/utils/wasm-builder/src/lib.rs index 6a7f0d7ca3cd5..919290655368b 100644 --- a/utils/wasm-builder/src/lib.rs +++ b/utils/wasm-builder/src/lib.rs @@ -87,6 +87,8 @@ //! required as we walk up from the target directory until we find a `Cargo.toml`. If the target //! directory is changed for the build, this environment variable can be used to point to the //! actual workspace. +//! - `CARGO_NET_OFFLINE` - If `true`, `--offline` will be passed to all processes launched to +//! prevent network access. Useful in offline environments. //! //! Each project can be skipped individually by using the environment variable //! `SKIP_PROJECT_NAME_WASM_BUILD`. Where `PROJECT_NAME` needs to be replaced by the name of the @@ -119,6 +121,9 @@ pub use builder::{WasmBuilder, WasmBuilderSelectProject}; /// Environment variable that tells us to skip building the wasm binary. const SKIP_BUILD_ENV: &str = "SKIP_WASM_BUILD"; +/// Environment variable that tells us whether we should avoid network requests +const OFFLINE: &str = "CARGO_NET_OFFLINE"; + /// Environment variable to force a certain build type when building the wasm binary. /// Expects "debug", "release" or "production" as value. /// diff --git a/utils/wasm-builder/src/wasm_project.rs b/utils/wasm-builder/src/wasm_project.rs index be84f2fecfb53..197c1d1b220bb 100644 --- a/utils/wasm-builder/src/wasm_project.rs +++ b/utils/wasm-builder/src/wasm_project.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{write_file_if_changed, CargoCommandVersioned}; +use crate::{write_file_if_changed, CargoCommandVersioned, OFFLINE}; use build_helper::rerun_if_changed; use cargo_metadata::{CargoOpt, Metadata, MetadataCommand}; @@ -88,12 +88,12 @@ fn crate_metadata(cargo_manifest: &Path) -> Metadata { cargo_manifest.to_path_buf() }; - let crate_metadata = MetadataCommand::new() - .manifest_path(cargo_manifest) - .features(CargoOpt::AllFeatures) + let mut crate_metadata_command = create_metadata_command(cargo_manifest); + crate_metadata_command.features(CargoOpt::AllFeatures); + + let crate_metadata = crate_metadata_command .exec() .expect("`cargo metadata` can not fail on project `Cargo.toml`; qed"); - // If the `Cargo.lock` didn't exist, we need to remove it after // calling `cargo metadata`. This is required to ensure that we don't change // the build directory outside of the `target` folder. Commands like @@ -593,6 +593,11 @@ impl Profile { } } +/// Check environment whether we should build without network +fn offline_build() -> bool { + env::var(OFFLINE).map_or(false, |v| v == "true") +} + /// Build the project to create the WASM binary. fn build_project( project: &Path, @@ -631,6 +636,10 @@ fn build_project( build_cmd.arg("--profile"); build_cmd.arg(profile.name()); + if offline_build() { + build_cmd.arg("--offline"); + } + println!("{}", colorize_info_message("Information that should be included in a bug report.")); println!("{} {:?}", colorize_info_message("Executing build command:"), build_cmd); println!("{} {}", colorize_info_message("Using rustc version:"), cargo_cmd.rustc_version()); @@ -751,6 +760,16 @@ impl<'a> Deref for DeduplicatePackage<'a> { } } +fn create_metadata_command(path: impl Into) -> MetadataCommand { + let mut metadata_command = MetadataCommand::new(); + metadata_command.manifest_path(path); + + if offline_build() { + metadata_command.other_options(vec!["--offline".to_owned()]); + } + metadata_command +} + /// Generate the `rerun-if-changed` instructions for cargo to make sure that the WASM binary is /// rebuilt when needed. fn generate_rerun_if_changed_instructions( @@ -765,8 +784,7 @@ fn generate_rerun_if_changed_instructions( rerun_if_changed(cargo_lock); } - let metadata = MetadataCommand::new() - .manifest_path(project_folder.join("Cargo.toml")) + let metadata = create_metadata_command(project_folder.join("Cargo.toml")) .exec() .expect("`cargo metadata` can not fail!"); From 7bc3ace657eb24834c1aaedc20e2e5493b5d7278 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Wed, 22 Jun 2022 17:54:57 +0200 Subject: [PATCH 363/484] contracts: Reduce size of deletion queue depth (#11696) * contracts: Reduce size of deletion queue depth * Remove unused import --- bin/node/runtime/src/lib.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index addcd0b35a9d9..54742cd91c430 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -47,7 +47,6 @@ use frame_system::{ }; pub use node_primitives::{AccountId, Signature}; use node_primitives::{AccountIndex, Balance, BlockNumber, Hash, Index, Moment}; -use pallet_contracts::weights::WeightInfo; use pallet_election_provider_multi_phase::SolutionAccuracyOf; use pallet_grandpa::{ fg_primitives, AuthorityId as GrandpaId, AuthorityList as GrandpaAuthorityList, @@ -1116,18 +1115,13 @@ parameter_types! { pub const DepositPerItem: Balance = deposit(1, 0); pub const DepositPerByte: Balance = deposit(0, 1); pub const MaxValueSize: u32 = 16 * 1024; + pub const DeletionQueueDepth: u32 = 128; // The lazy deletion runs inside on_initialize. pub DeletionWeightLimit: Weight = RuntimeBlockWeights::get() .per_class .get(DispatchClass::Normal) .max_total .unwrap_or(RuntimeBlockWeights::get().max_block); - // The weight needed for decoding the queue should be less or equal than a fifth - // of the overall weight dedicated to the lazy deletion. - pub DeletionQueueDepth: u32 = ((DeletionWeightLimit::get() / ( - ::WeightInfo::on_initialize_per_queue_item(1) - - ::WeightInfo::on_initialize_per_queue_item(0) - )) / 5) as u32; pub Schedule: pallet_contracts::Schedule = Default::default(); } From 4333f91635e2434388ba52068c31975d2c92e19d Mon Sep 17 00:00:00 2001 From: Vlad Date: Wed, 22 Jun 2022 19:00:00 +0300 Subject: [PATCH 364/484] Put `rusty-cachier` before PR merge into `master` for `cargo-check-benches` job (#11737) --- scripts/ci/gitlab/pipeline/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/ci/gitlab/pipeline/test.yml b/scripts/ci/gitlab/pipeline/test.yml index b2348c48355d0..0c3fd4d33798d 100644 --- a/scripts/ci/gitlab/pipeline/test.yml +++ b/scripts/ci/gitlab/pipeline/test.yml @@ -80,6 +80,8 @@ cargo-check-benches: - .test-refs - .collect-artifacts before_script: + - !reference [.rust-info-script, script] + - !reference [.rusty-cachier, before_script] # merges in the master branch on PRs - if [ $CI_COMMIT_REF_NAME != "master" ]; then git fetch origin +master:master; @@ -88,8 +90,6 @@ cargo-check-benches: git config user.email "ci@gitlab.parity.io"; git merge $CI_COMMIT_REF_NAME --verbose --no-edit; fi - - !reference [.rust-info-script, script] - - !reference [.rusty-cachier, before_script] script: - rusty-cachier snapshot create - mkdir -p ./artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA From bf4e8ce84ee044765439bc09f3847e1464f2bed1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= <123550+andresilva@users.noreply.github.com> Date: Wed, 22 Jun 2022 17:03:03 +0100 Subject: [PATCH 365/484] epochs: don't use gap when there's at least one genesis epoch imported (#11725) * epochs: don't use gap when there's at least one genesis epoch imported * epochs: add test for genesis gap fix --- client/consensus/epochs/src/lib.rs | 115 ++++++++++++++++++++++++++++- 1 file changed, 112 insertions(+), 3 deletions(-) diff --git a/client/consensus/epochs/src/lib.rs b/client/consensus/epochs/src/lib.rs index 3a943e4851a4d..fee69613debf0 100644 --- a/client/consensus/epochs/src/lib.rs +++ b/client/consensus/epochs/src/lib.rs @@ -755,7 +755,10 @@ where Err(e) => PersistedEpoch::Regular(e), } } - } else if epoch.is_genesis() && !self.epochs.values().all(|e| e.is_genesis()) { + } else if epoch.is_genesis() && + !self.epochs.is_empty() && + !self.epochs.values().any(|e| e.is_genesis()) + { // There's a genesis epoch imported when we already have an active epoch. // This happens after the warp sync as the ancient blocks download start. // We need to start tracking gap epochs here. @@ -1170,15 +1173,17 @@ mod tests { epoch_changes.clear_gap(); // Check that both epochs are available. - epoch_changes + let epoch_a = epoch_changes .epoch_data_for_child_of(&is_descendent_of, b"A", 1, 101, &make_genesis) .unwrap() .unwrap(); - epoch_changes + let epoch_x = epoch_changes .epoch_data_for_child_of(&is_descendent_of, b"X", 1, 1001, &make_genesis) .unwrap() .unwrap(); + + assert!(epoch_a != epoch_x) } #[test] @@ -1288,4 +1293,108 @@ mod tests { epoch_changes.clear_gap(); assert!(epoch_changes.gap.is_none()); } + + /// Test that ensures that the gap is not enabled when there's still genesis + /// epochs imported, regardless of whether there are already other further + /// epochs imported descending from such genesis epochs. + #[test] + fn gap_is_not_enabled_when_at_least_one_genesis_epoch_is_still_imported() { + // A (#1) - B (#201) + // / + // 0 - C (#1) + // + // The epoch duration is 100 slots, each of these blocks represents + // an epoch change block. block B starts a new epoch at #201 since the + // genesis epoch spans two epochs. + + let is_descendent_of = |base: &Hash, block: &Hash| -> Result { + match (base, block) { + (b"0", _) => Ok(true), + (b"A", b"B") => Ok(true), + _ => Ok(false), + } + }; + + let duration = 100; + let make_genesis = |slot| Epoch { start_slot: slot, duration }; + let mut epoch_changes = EpochChanges::new(); + let next_descriptor = (); + + // insert genesis epoch for A at slot 1 + { + let genesis_epoch_a_descriptor = epoch_changes + .epoch_descriptor_for_child_of(&is_descendent_of, b"0", 0, 1) + .unwrap() + .unwrap(); + + let incremented_epoch = epoch_changes + .viable_epoch(&genesis_epoch_a_descriptor, &make_genesis) + .unwrap() + .increment(next_descriptor.clone()); + + epoch_changes + .import(&is_descendent_of, *b"A", 1, *b"0", incremented_epoch) + .unwrap(); + } + + // insert regular epoch for B at slot 201, descending from A + { + let epoch_b_descriptor = epoch_changes + .epoch_descriptor_for_child_of(&is_descendent_of, b"A", 1, 201) + .unwrap() + .unwrap(); + + let incremented_epoch = epoch_changes + .viable_epoch(&epoch_b_descriptor, &make_genesis) + .unwrap() + .increment(next_descriptor.clone()); + + epoch_changes + .import(&is_descendent_of, *b"B", 201, *b"A", incremented_epoch) + .unwrap(); + } + + // insert genesis epoch for C at slot 1000 + { + let genesis_epoch_x_descriptor = epoch_changes + .epoch_descriptor_for_child_of(&is_descendent_of, b"0", 0, 1000) + .unwrap() + .unwrap(); + + let incremented_epoch = epoch_changes + .viable_epoch(&genesis_epoch_x_descriptor, &make_genesis) + .unwrap() + .increment(next_descriptor.clone()); + + epoch_changes + .import(&is_descendent_of, *b"C", 1, *b"0", incremented_epoch) + .unwrap(); + } + + // Clearing the gap should be a no-op. + epoch_changes.clear_gap(); + + // Check that all three epochs are available. + let epoch_a = epoch_changes + .epoch_data_for_child_of(&is_descendent_of, b"A", 1, 10, &make_genesis) + .unwrap() + .unwrap(); + + let epoch_b = epoch_changes + .epoch_data_for_child_of(&is_descendent_of, b"B", 201, 201, &make_genesis) + .unwrap() + .unwrap(); + + assert!(epoch_a != epoch_b); + + // the genesis epoch A will span slots [1, 200] with epoch B starting at slot 201 + assert_eq!(epoch_b.start_slot(), 201); + + let epoch_c = epoch_changes + .epoch_data_for_child_of(&is_descendent_of, b"C", 1, 1001, &make_genesis) + .unwrap() + .unwrap(); + + assert!(epoch_a != epoch_c); + } } From a43f5e7ef68c2c343a3aac4130f33f98231d2395 Mon Sep 17 00:00:00 2001 From: Adrian Catangiu Date: Wed, 22 Jun 2022 21:39:24 +0300 Subject: [PATCH 366/484] pallet-beefy-mmr: add API for BEEFY Authority Sets (#11406) * pallet-beefy: add Config::OnNewValidatorSet type Add a hook to pallet-beefy for doing specific work when BEEFY validator set changes. For example, this can be used by pallet-beefy-mmr to cache a lightweight MMR root over validators and make it available to light clients. * pallet-beefy-mmr: implement OnNewValidatorSet Implement pallet-beefy::OnNewValidatorSet to be notified of BEEFY validator set changes. Use the notifications to compute and cache a light weight 'BEEFY authority set' which is an MMR root over BEEFY validator set plus some extra info. Previously, pallet-beefy-mmr was interogating pallet-beefy about validator set id on every block to find out when it needs to recompute the authority set. By using the event-driven approach in this commit, we also save one extra state interogation per block. * pallet-beefy-mmr: add new authority_set() API Expose current and next BEEFY authority sets through runtime API. These can be directly used by light clients to avoid having them compute them themselves based on BEEFY validator sets. Signed-off-by: acatangiu * rename BeefyMmr exposed runtime api --- Cargo.lock | 3 ++ frame/beefy-mmr/primitives/Cargo.toml | 8 +++- frame/beefy-mmr/primitives/src/lib.rs | 17 +++++++ frame/beefy-mmr/src/lib.rs | 67 +++++++++++++++++++-------- frame/beefy-mmr/src/mock.rs | 1 + frame/beefy-mmr/src/tests.rs | 50 ++++++++++++++++++++ frame/beefy/src/lib.rs | 51 ++++++++++++++++---- frame/beefy/src/mock.rs | 1 + primitives/beefy/src/lib.rs | 14 ++++++ primitives/beefy/src/mmr.rs | 13 ++++-- test-utils/runtime/Cargo.toml | 2 + test-utils/runtime/src/lib.rs | 10 ++++ 12 files changed, 202 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d7b1350c1f529..cfcf5573c0bff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -511,10 +511,12 @@ dependencies = [ name = "beefy-merkle-tree" version = "4.0.0-dev" dependencies = [ + "beefy-primitives", "env_logger 0.9.0", "hex", "hex-literal", "log", + "sp-api", "tiny-keccak", ] @@ -10646,6 +10648,7 @@ dependencies = [ name = "substrate-test-runtime" version = "2.0.0" dependencies = [ + "beefy-merkle-tree", "beefy-primitives", "cfg-if 1.0.0", "frame-support", diff --git a/frame/beefy-mmr/primitives/Cargo.toml b/frame/beefy-mmr/primitives/Cargo.toml index 7878dc3d22837..f30a418def042 100644 --- a/frame/beefy-mmr/primitives/Cargo.toml +++ b/frame/beefy-mmr/primitives/Cargo.toml @@ -13,6 +13,9 @@ hex = { version = "0.4", default-features = false, optional = true } log = { version = "0.4", default-features = false, optional = true } tiny-keccak = { version = "2.0.2", features = ["keccak"], optional = true } +beefy-primitives = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/beefy" } +sp-api = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/api" } + [dev-dependencies] env_logger = "0.9" hex = "0.4" @@ -22,4 +25,7 @@ hex-literal = "0.3" debug = ["hex", "hex/std", "log"] default = ["debug", "keccak", "std"] keccak = ["tiny-keccak"] -std = [] +std = [ + "beefy-primitives/std", + "sp-api/std" +] diff --git a/frame/beefy-mmr/primitives/src/lib.rs b/frame/beefy-mmr/primitives/src/lib.rs index 04fa11760765b..664fd18199dd0 100644 --- a/frame/beefy-mmr/primitives/src/lib.rs +++ b/frame/beefy-mmr/primitives/src/lib.rs @@ -36,6 +36,8 @@ extern crate alloc; #[cfg(not(feature = "std"))] use alloc::vec::Vec; +use beefy_primitives::mmr::{BeefyAuthoritySet, BeefyNextAuthoritySet}; + /// Supported hashing output size. /// /// The size is restricted to 32 bytes to allow for a more optimised implementation. @@ -375,6 +377,21 @@ where } } +sp_api::decl_runtime_apis! { + /// API useful for BEEFY light clients. + pub trait BeefyMmrApi + where + H: From + Into, + BeefyAuthoritySet: sp_api::Decode, + { + /// Return the currently active BEEFY authority set proof. + fn authority_set_proof() -> BeefyAuthoritySet; + + /// Return the next/queued BEEFY authority set proof. + fn next_authority_set_proof() -> BeefyNextAuthoritySet; + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/frame/beefy-mmr/src/lib.rs b/frame/beefy-mmr/src/lib.rs index 8b904d6aefd5f..456d6e77aa8eb 100644 --- a/frame/beefy-mmr/src/lib.rs +++ b/frame/beefy-mmr/src/lib.rs @@ -36,7 +36,10 @@ use sp_runtime::traits::{Convert, Hash, Member}; use sp_std::prelude::*; -use beefy_primitives::mmr::{BeefyDataProvider, BeefyNextAuthoritySet, MmrLeaf, MmrLeafVersion}; +use beefy_primitives::{ + mmr::{BeefyAuthoritySet, BeefyDataProvider, BeefyNextAuthoritySet, MmrLeaf, MmrLeafVersion}, + ValidatorSet as BeefyValidatorSet, +}; use pallet_mmr::{LeafDataProvider, ParentNumberAndHash}; use frame_support::{crypto::ecdsa::ECDSAExt, traits::Get}; @@ -124,6 +127,12 @@ pub mod pallet { type BeefyDataProvider: BeefyDataProvider; } + /// Details of current BEEFY authority set. + #[pallet::storage] + #[pallet::getter(fn beefy_authorities)] + pub type BeefyAuthorities = + StorageValue<_, BeefyAuthoritySet>, ValueQuery>; + /// Details of next BEEFY authority set. /// /// This storage entry is used as cache for calls to `update_beefy_next_authority_set`. @@ -149,7 +158,7 @@ where version: T::LeafVersion::get(), parent_number_and_hash: ParentNumberAndHash::::leaf_data(), leaf_extra: T::BeefyDataProvider::extra_data(), - beefy_next_authority_set: Pallet::::update_beefy_next_authority_set(), + beefy_next_authority_set: Pallet::::beefy_next_authorities(), } } } @@ -163,35 +172,55 @@ where } } +impl beefy_primitives::OnNewValidatorSet<::BeefyId> for Pallet +where + T: pallet::Config, + MerkleRootOf: From + Into, +{ + /// Compute and cache BEEFY authority sets based on updated BEEFY validator sets. + fn on_new_validator_set( + current_set: &BeefyValidatorSet<::BeefyId>, + next_set: &BeefyValidatorSet<::BeefyId>, + ) { + let current = Pallet::::compute_authority_set(current_set); + let next = Pallet::::compute_authority_set(next_set); + // cache the result + BeefyAuthorities::::put(¤t); + BeefyNextAuthorities::::put(&next); + } +} + impl Pallet where MerkleRootOf: From + Into, { - /// Returns details of the next BEEFY authority set. + /// Return the currently active BEEFY authority set proof. + pub fn authority_set_proof() -> BeefyAuthoritySet> { + Pallet::::beefy_authorities() + } + + /// Return the next/queued BEEFY authority set proof. + pub fn next_authority_set_proof() -> BeefyNextAuthoritySet> { + Pallet::::beefy_next_authorities() + } + + /// Returns details of a BEEFY authority set. /// /// Details contain authority set id, authority set length and a merkle root, /// constructed from uncompressed secp256k1 public keys converted to Ethereum addresses /// of the next BEEFY authority set. - /// - /// This function will use a storage-cached entry in case the set didn't change, or compute and - /// cache new one in case it did. - fn update_beefy_next_authority_set() -> BeefyNextAuthoritySet> { - let id = pallet_beefy::Pallet::::validator_set_id() + 1; - let current_next = Self::beefy_next_authorities(); - // avoid computing the merkle tree if validator set id didn't change. - if id == current_next.id { - return current_next - } - - let beefy_addresses = pallet_beefy::Pallet::::next_authorities() + fn compute_authority_set( + validator_set: &BeefyValidatorSet<::BeefyId>, + ) -> BeefyAuthoritySet> { + let id = validator_set.id(); + let beefy_addresses = validator_set + .validators() .into_iter() + .cloned() .map(T::BeefyAuthorityToMerkleLeaf::convert) .collect::>(); let len = beefy_addresses.len() as u32; let root = beefy_merkle_tree::merkle_root::(beefy_addresses).into(); - let next_set = BeefyNextAuthoritySet { id, len, root }; - // cache the result - BeefyNextAuthorities::::put(&next_set); - next_set + BeefyAuthoritySet { id, len, root } } } diff --git a/frame/beefy-mmr/src/mock.rs b/frame/beefy-mmr/src/mock.rs index c9dbdb3b2e16a..8a673c9d4e914 100644 --- a/frame/beefy-mmr/src/mock.rs +++ b/frame/beefy-mmr/src/mock.rs @@ -125,6 +125,7 @@ impl pallet_mmr::Config for Test { impl pallet_beefy::Config for Test { type BeefyId = BeefyId; type MaxAuthorities = ConstU32<100>; + type OnNewValidatorSet = BeefyMmr; } parameter_types! { diff --git a/frame/beefy-mmr/src/tests.rs b/frame/beefy-mmr/src/tests.rs index fd3ecd5067155..d9cd8c8a5d8c8 100644 --- a/frame/beefy-mmr/src/tests.rs +++ b/frame/beefy-mmr/src/tests.rs @@ -149,3 +149,53 @@ fn should_contain_valid_leaf_data() { } ); } + +#[test] +fn should_update_authorities() { + new_test_ext(vec![1, 2, 3, 4]).execute_with(|| { + let auth_set = BeefyMmr::authority_set_proof(); + let next_auth_set = BeefyMmr::next_authority_set_proof(); + + // check current authority set + assert_eq!(0, auth_set.id); + assert_eq!(2, auth_set.len); + let want: H256 = + hex!("176e73f1bf656478b728e28dd1a7733c98621b8acf830bff585949763dca7a96").into(); + assert_eq!(want, auth_set.root); + + // next authority set should have same validators but different id + assert_eq!(1, next_auth_set.id); + assert_eq!(auth_set.len, next_auth_set.len); + assert_eq!(auth_set.root, next_auth_set.root); + + let announced_set = next_auth_set; + init_block(1); + let auth_set = BeefyMmr::authority_set_proof(); + let next_auth_set = BeefyMmr::next_authority_set_proof(); + + // check new auth are expected ones + assert_eq!(announced_set, auth_set); + assert_eq!(1, auth_set.id); + // check next auth set + assert_eq!(2, next_auth_set.id); + let want: H256 = + hex!("9c6b2c1b0d0b25a008e6c882cc7b415f309965c72ad2b944ac0931048ca31cd5").into(); + assert_eq!(2, next_auth_set.len); + assert_eq!(want, next_auth_set.root); + + let announced_set = next_auth_set; + init_block(2); + let auth_set = BeefyMmr::authority_set_proof(); + let next_auth_set = BeefyMmr::next_authority_set_proof(); + + // check new auth are expected ones + assert_eq!(announced_set, auth_set); + assert_eq!(2, auth_set.id); + // check next auth set + assert_eq!(3, next_auth_set.id); + let want: H256 = + hex!("9c6b2c1b0d0b25a008e6c882cc7b415f309965c72ad2b944ac0931048ca31cd5").into(); + assert_eq!(2, next_auth_set.len); + assert_eq!(want, next_auth_set.root); + }); +} diff --git a/frame/beefy/src/lib.rs b/frame/beefy/src/lib.rs index a0cba2270b37d..fce531d3f5dd7 100644 --- a/frame/beefy/src/lib.rs +++ b/frame/beefy/src/lib.rs @@ -32,7 +32,9 @@ use sp_runtime::{ }; use sp_std::prelude::*; -use beefy_primitives::{AuthorityIndex, ConsensusLog, ValidatorSet, BEEFY_ENGINE_ID}; +use beefy_primitives::{ + AuthorityIndex, ConsensusLog, OnNewValidatorSet, ValidatorSet, BEEFY_ENGINE_ID, +}; #[cfg(test)] mod mock; @@ -58,6 +60,13 @@ pub mod pallet { /// The maximum number of authorities that can be added. type MaxAuthorities: Get; + + /// A hook to act on the new BEEFY validator set. + /// + /// For some applications it might be beneficial to make the BEEFY validator set available + /// externally apart from having it in the storage. For instance you might cache a light + /// weight MMR root over validators and make it available for Light Clients. + type OnNewValidatorSet: OnNewValidatorSet<::BeefyId>; } #[pallet::pallet] @@ -118,20 +127,29 @@ impl Pallet { ) { >::put(&new); - let next_id = Self::validator_set_id() + 1u64; - >::put(next_id); - if let Some(validator_set) = ValidatorSet::::new(new, next_id) { + let new_id = Self::validator_set_id() + 1u64; + >::put(new_id); + + >::put(&queued); + + if let Some(validator_set) = ValidatorSet::::new(new, new_id) { let log = DigestItem::Consensus( BEEFY_ENGINE_ID, - ConsensusLog::AuthoritiesChange(validator_set).encode(), + ConsensusLog::AuthoritiesChange(validator_set.clone()).encode(), ); >::deposit_log(log); - } - >::put(&queued); + let next_id = new_id + 1; + if let Some(next_validator_set) = ValidatorSet::::new(queued, next_id) { + >::on_new_validator_set( + &validator_set, + &next_validator_set, + ); + } + } } - fn initialize_authorities(authorities: &[T::BeefyId]) -> Result<(), ()> { + fn initialize_authorities(authorities: &Vec) -> Result<(), ()> { if authorities.is_empty() { return Ok(()) } @@ -141,12 +159,25 @@ impl Pallet { } let bounded_authorities = - BoundedSlice::::try_from(authorities)?; + BoundedSlice::::try_from(authorities.as_slice())?; + let id = 0; >::put(bounded_authorities); - >::put(0); + >::put(id); // Like `pallet_session`, initialize the next validator set as well. >::put(bounded_authorities); + + if let Some(validator_set) = ValidatorSet::::new(authorities.clone(), id) { + let next_id = id + 1; + if let Some(next_validator_set) = + ValidatorSet::::new(authorities.clone(), next_id) + { + >::on_new_validator_set( + &validator_set, + &next_validator_set, + ); + } + } Ok(()) } } diff --git a/frame/beefy/src/mock.rs b/frame/beefy/src/mock.rs index 27796b5b2206c..3bb59c7c39485 100644 --- a/frame/beefy/src/mock.rs +++ b/frame/beefy/src/mock.rs @@ -87,6 +87,7 @@ impl frame_system::Config for Test { impl pallet_beefy::Config for Test { type BeefyId = BeefyId; type MaxAuthorities = ConstU32<100>; + type OnNewValidatorSet = (); } parameter_types! { diff --git a/primitives/beefy/src/lib.rs b/primitives/beefy/src/lib.rs index 8dbdd66f3559b..87f1b8756af65 100644 --- a/primitives/beefy/src/lib.rs +++ b/primitives/beefy/src/lib.rs @@ -154,6 +154,20 @@ pub struct VoteMessage { pub signature: Signature, } +/// New BEEFY validator set notification hook. +pub trait OnNewValidatorSet { + /// Function called by the pallet when BEEFY validator set changes. + fn on_new_validator_set( + validator_set: &ValidatorSet, + next_validator_set: &ValidatorSet, + ); +} + +/// No-op implementation of [OnNewValidatorSet]. +impl OnNewValidatorSet for () { + fn on_new_validator_set(_: &ValidatorSet, _: &ValidatorSet) {} +} + sp_api::decl_runtime_apis! { /// API necessary for BEEFY voters. pub trait BeefyApi diff --git a/primitives/beefy/src/mmr.rs b/primitives/beefy/src/mmr.rs index 426a1ba5ff80b..761eee9f8ef85 100644 --- a/primitives/beefy/src/mmr.rs +++ b/primitives/beefy/src/mmr.rs @@ -95,10 +95,10 @@ impl MmrLeafVersion { } } -/// Details of the next BEEFY authority set. +/// Details of a BEEFY authority set. #[derive(Debug, Default, PartialEq, Eq, Clone, Encode, Decode, TypeInfo, MaxEncodedLen)] -pub struct BeefyNextAuthoritySet { - /// Id of the next set. +pub struct BeefyAuthoritySet { + /// Id of the set. /// /// Id is required to correlate BEEFY signed commitments with the validator set. /// Light Client can easily verify that the commitment witness it is getting is @@ -106,11 +106,11 @@ pub struct BeefyNextAuthoritySet { pub id: crate::ValidatorSetId, /// Number of validators in the set. /// - /// Some BEEFY Light Clients may use an interactive protocol to verify only subset + /// Some BEEFY Light Clients may use an interactive protocol to verify only a subset /// of signatures. We put set length here, so that these clients can verify the minimal /// number of required signatures. pub len: u32, - /// Merkle Root Hash build from BEEFY AuthorityIds. + /// Merkle Root Hash built from BEEFY AuthorityIds. /// /// This is used by Light Clients to confirm that the commitments are signed by the correct /// validator set. Light Clients using interactive protocol, might verify only subset of @@ -118,6 +118,9 @@ pub struct BeefyNextAuthoritySet { pub root: MerkleRoot, } +/// Details of the next BEEFY authority set. +pub type BeefyNextAuthoritySet = BeefyAuthoritySet; + #[cfg(test)] mod tests { use super::*; diff --git a/test-utils/runtime/Cargo.toml b/test-utils/runtime/Cargo.toml index 739be9ec8bdd8..1c2707b3719ad 100644 --- a/test-utils/runtime/Cargo.toml +++ b/test-utils/runtime/Cargo.toml @@ -14,6 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] beefy-primitives = { version = "4.0.0-dev", default-features = false, path = "../../primitives/beefy" } +beefy-merkle-tree = { version = "4.0.0-dev", default-features = false, path = "../../frame/beefy-mmr/primitives" } sp-application-crypto = { version = "6.0.0", default-features = false, path = "../../primitives/application-crypto" } sp-consensus-aura = { version = "0.10.0-dev", default-features = false, path = "../../primitives/consensus/aura" } sp-consensus-babe = { version = "0.10.0-dev", default-features = false, path = "../../primitives/consensus/babe" } @@ -67,6 +68,7 @@ default = [ ] std = [ "beefy-primitives/std", + "beefy-merkle-tree/std", "sp-application-crypto/std", "sp-consensus-aura/std", "sp-consensus-babe/std", diff --git a/test-utils/runtime/src/lib.rs b/test-utils/runtime/src/lib.rs index 555f29e5991f7..ea62f2ac84f3d 100644 --- a/test-utils/runtime/src/lib.rs +++ b/test-utils/runtime/src/lib.rs @@ -947,6 +947,16 @@ cfg_if! { } } + impl beefy_merkle_tree::BeefyMmrApi for RuntimeApi { + fn authority_set_proof() -> beefy_primitives::mmr::BeefyAuthoritySet { + Default::default() + } + + fn next_authority_set_proof() -> beefy_primitives::mmr::BeefyNextAuthoritySet { + Default::default() + } + } + impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { fn account_nonce(_account: AccountId) -> Index { 0 From 12f795ce5e84e80d40f690ee3731f2fec7b57aa0 Mon Sep 17 00:00:00 2001 From: Doordashcon Date: Thu, 23 Jun 2022 05:18:40 +0100 Subject: [PATCH 367/484] make pallet-tips & pallet-bounties instantiable (#11473) * make pallet-tips & pallet-bounties instantiable * update test * add default instance * update * cargo fmt * update * update * update * update * fix merge * fix tests * bounties benchmarking instantiable * fix benchmarks * make tips benchmarks instantible Co-authored-by: Shawn Tabrizi --- frame/bounties/src/benchmarking.rs | 156 ++++++++++++----------- frame/bounties/src/lib.rs | 193 +++++++++++++++-------------- frame/bounties/src/tests.rs | 89 +++++++++++-- frame/tips/src/benchmarking.rs | 58 +++++---- frame/tips/src/lib.rs | 105 ++++++++-------- frame/tips/src/tests.rs | 87 +++++++++++-- 6 files changed, 417 insertions(+), 271 deletions(-) diff --git a/frame/bounties/src/benchmarking.rs b/frame/bounties/src/benchmarking.rs index 912e461501be5..7566c32f6e9a1 100644 --- a/frame/bounties/src/benchmarking.rs +++ b/frame/bounties/src/benchmarking.rs @@ -21,7 +21,7 @@ use super::*; -use frame_benchmarking::{account, benchmarks, whitelisted_caller}; +use frame_benchmarking::{account, benchmarks_instance_pallet, whitelisted_caller}; use frame_system::RawOrigin; use sp_runtime::traits::Bounded; @@ -31,25 +31,25 @@ use pallet_treasury::Pallet as Treasury; const SEED: u32 = 0; // Create bounties that are approved for use in `on_initialize`. -fn create_approved_bounties(n: u32) -> Result<(), &'static str> { +fn create_approved_bounties, I: 'static>(n: u32) -> Result<(), &'static str> { for i in 0..n { let (caller, _curator, _fee, value, reason) = - setup_bounty::(i, T::MaximumReasonLength::get()); - Bounties::::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?; - let bounty_id = BountyCount::::get() - 1; - Bounties::::approve_bounty(RawOrigin::Root.into(), bounty_id)?; + setup_bounty::(i, T::MaximumReasonLength::get()); + Bounties::::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?; + let bounty_id = BountyCount::::get() - 1; + Bounties::::approve_bounty(RawOrigin::Root.into(), bounty_id)?; } - ensure!(BountyApprovals::::get().len() == n as usize, "Not all bounty approved"); + ensure!(BountyApprovals::::get().len() == n as usize, "Not all bounty approved"); Ok(()) } // Create the pre-requisite information needed to create a treasury `propose_bounty`. -fn setup_bounty( +fn setup_bounty, I: 'static>( u: u32, d: u32, -) -> (T::AccountId, T::AccountId, BalanceOf, BalanceOf, Vec) { +) -> (T::AccountId, T::AccountId, BalanceOf, BalanceOf, Vec) { let caller = account("caller", u, SEED); - let value: BalanceOf = T::BountyValueMinimum::get().saturating_mul(100u32.into()); + let value: BalanceOf = T::BountyValueMinimum::get().saturating_mul(100u32.into()); let fee = value / 2u32.into(); let deposit = T::BountyDepositBase::get() + T::DataDepositPerByte::get() * T::MaximumReasonLength::get().into(); @@ -60,97 +60,103 @@ fn setup_bounty( (caller, curator, fee, value, reason) } -fn create_bounty( +fn create_bounty, I: 'static>( ) -> Result<(::Source, BountyIndex), &'static str> { - let (caller, curator, fee, value, reason) = setup_bounty::(0, T::MaximumReasonLength::get()); + let (caller, curator, fee, value, reason) = + setup_bounty::(0, T::MaximumReasonLength::get()); let curator_lookup = T::Lookup::unlookup(curator.clone()); - Bounties::::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?; - let bounty_id = BountyCount::::get() - 1; - Bounties::::approve_bounty(RawOrigin::Root.into(), bounty_id)?; - Treasury::::on_initialize(T::BlockNumber::zero()); - Bounties::::propose_curator(RawOrigin::Root.into(), bounty_id, curator_lookup.clone(), fee)?; - Bounties::::accept_curator(RawOrigin::Signed(curator).into(), bounty_id)?; + Bounties::::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?; + let bounty_id = BountyCount::::get() - 1; + Bounties::::approve_bounty(RawOrigin::Root.into(), bounty_id)?; + Treasury::::on_initialize(T::BlockNumber::zero()); + Bounties::::propose_curator( + RawOrigin::Root.into(), + bounty_id, + curator_lookup.clone(), + fee, + )?; + Bounties::::accept_curator(RawOrigin::Signed(curator).into(), bounty_id)?; Ok((curator_lookup, bounty_id)) } -fn setup_pot_account() { - let pot_account = Bounties::::account_id(); +fn setup_pot_account, I: 'static>() { + let pot_account = Bounties::::account_id(); let value = T::Currency::minimum_balance().saturating_mul(1_000_000_000u32.into()); let _ = T::Currency::make_free_balance_be(&pot_account, value); } -fn assert_last_event(generic_event: ::Event) { +fn assert_last_event, I: 'static>(generic_event: >::Event) { frame_system::Pallet::::assert_last_event(generic_event.into()); } -benchmarks! { +benchmarks_instance_pallet! { propose_bounty { let d in 0 .. T::MaximumReasonLength::get(); - let (caller, curator, fee, value, description) = setup_bounty::(0, d); + let (caller, curator, fee, value, description) = setup_bounty::(0, d); }: _(RawOrigin::Signed(caller), value, description) approve_bounty { - let (caller, curator, fee, value, reason) = setup_bounty::(0, T::MaximumReasonLength::get()); - Bounties::::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?; - let bounty_id = BountyCount::::get() - 1; + let (caller, curator, fee, value, reason) = setup_bounty::(0, T::MaximumReasonLength::get()); + Bounties::::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?; + let bounty_id = BountyCount::::get() - 1; }: _(RawOrigin::Root, bounty_id) propose_curator { - setup_pot_account::(); - let (caller, curator, fee, value, reason) = setup_bounty::(0, T::MaximumReasonLength::get()); + setup_pot_account::(); + let (caller, curator, fee, value, reason) = setup_bounty::(0, T::MaximumReasonLength::get()); let curator_lookup = T::Lookup::unlookup(curator); - Bounties::::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?; - let bounty_id = BountyCount::::get() - 1; - Bounties::::approve_bounty(RawOrigin::Root.into(), bounty_id)?; - Bounties::::on_initialize(T::BlockNumber::zero()); + Bounties::::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?; + let bounty_id = BountyCount::::get() - 1; + Bounties::::approve_bounty(RawOrigin::Root.into(), bounty_id)?; + Treasury::::on_initialize(T::BlockNumber::zero()); }: _(RawOrigin::Root, bounty_id, curator_lookup, fee) // Worst case when curator is inactive and any sender unassigns the curator. unassign_curator { - setup_pot_account::(); - let (curator_lookup, bounty_id) = create_bounty::()?; - Bounties::::on_initialize(T::BlockNumber::zero()); - let bounty_id = BountyCount::::get() - 1; - frame_system::Pallet::::set_block_number(T::BountyUpdatePeriod::get() + 1u32.into()); + setup_pot_account::(); + let (curator_lookup, bounty_id) = create_bounty::()?; + Treasury::::on_initialize(T::BlockNumber::zero()); + let bounty_id = BountyCount::::get() - 1; + frame_system::Pallet::::set_block_number(T::BountyUpdatePeriod::get() + 2u32.into()); let caller = whitelisted_caller(); }: _(RawOrigin::Signed(caller), bounty_id) accept_curator { - setup_pot_account::(); - let (caller, curator, fee, value, reason) = setup_bounty::(0, T::MaximumReasonLength::get()); + setup_pot_account::(); + let (caller, curator, fee, value, reason) = setup_bounty::(0, T::MaximumReasonLength::get()); let curator_lookup = T::Lookup::unlookup(curator.clone()); - Bounties::::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?; - let bounty_id = BountyCount::::get() - 1; - Bounties::::approve_bounty(RawOrigin::Root.into(), bounty_id)?; - Bounties::::on_initialize(T::BlockNumber::zero()); - Bounties::::propose_curator(RawOrigin::Root.into(), bounty_id, curator_lookup, fee)?; + Bounties::::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?; + let bounty_id = BountyCount::::get() - 1; + Bounties::::approve_bounty(RawOrigin::Root.into(), bounty_id)?; + Treasury::::on_initialize(T::BlockNumber::zero()); + Bounties::::propose_curator(RawOrigin::Root.into(), bounty_id, curator_lookup, fee)?; }: _(RawOrigin::Signed(curator), bounty_id) award_bounty { - setup_pot_account::(); - let (curator_lookup, bounty_id) = create_bounty::()?; - Bounties::::on_initialize(T::BlockNumber::zero()); + setup_pot_account::(); + let (curator_lookup, bounty_id) = create_bounty::()?; + Treasury::::on_initialize(T::BlockNumber::zero()); - let bounty_id = BountyCount::::get() - 1; + let bounty_id = BountyCount::::get() - 1; let curator = T::Lookup::lookup(curator_lookup).map_err(<&str>::from)?; let beneficiary = T::Lookup::unlookup(account("beneficiary", 0, SEED)); }: _(RawOrigin::Signed(curator), bounty_id, beneficiary) claim_bounty { - setup_pot_account::(); - let (curator_lookup, bounty_id) = create_bounty::()?; - Bounties::::on_initialize(T::BlockNumber::zero()); + setup_pot_account::(); + let (curator_lookup, bounty_id) = create_bounty::()?; + Treasury::::on_initialize(T::BlockNumber::zero()); - let bounty_id = BountyCount::::get() - 1; + let bounty_id = BountyCount::::get() - 1; let curator = T::Lookup::lookup(curator_lookup).map_err(<&str>::from)?; let beneficiary_account: T::AccountId = account("beneficiary", 0, SEED); let beneficiary = T::Lookup::unlookup(beneficiary_account.clone()); - Bounties::::award_bounty(RawOrigin::Signed(curator.clone()).into(), bounty_id, beneficiary)?; + Bounties::::award_bounty(RawOrigin::Signed(curator.clone()).into(), bounty_id, beneficiary)?; - frame_system::Pallet::::set_block_number(T::BountyDepositPayoutDelay::get()); + frame_system::Pallet::::set_block_number(T::BountyDepositPayoutDelay::get() + 1u32.into()); ensure!(T::Currency::free_balance(&beneficiary_account).is_zero(), "Beneficiary already has balance"); }: _(RawOrigin::Signed(curator), bounty_id) @@ -159,45 +165,45 @@ benchmarks! { } close_bounty_proposed { - setup_pot_account::(); - let (caller, curator, fee, value, reason) = setup_bounty::(0, 0); - Bounties::::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?; - let bounty_id = BountyCount::::get() - 1; + setup_pot_account::(); + let (caller, curator, fee, value, reason) = setup_bounty::(0, 0); + Bounties::::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?; + let bounty_id = BountyCount::::get() - 1; }: close_bounty(RawOrigin::Root, bounty_id) close_bounty_active { - setup_pot_account::(); - let (curator_lookup, bounty_id) = create_bounty::()?; - Bounties::::on_initialize(T::BlockNumber::zero()); - let bounty_id = BountyCount::::get() - 1; + setup_pot_account::(); + let (curator_lookup, bounty_id) = create_bounty::()?; + Treasury::::on_initialize(T::BlockNumber::zero()); + let bounty_id = BountyCount::::get() - 1; }: close_bounty(RawOrigin::Root, bounty_id) verify { - assert_last_event::(Event::BountyCanceled { index: bounty_id }.into()) + assert_last_event::(Event::BountyCanceled { index: bounty_id }.into()) } extend_bounty_expiry { - setup_pot_account::(); - let (curator_lookup, bounty_id) = create_bounty::()?; - Bounties::::on_initialize(T::BlockNumber::zero()); + setup_pot_account::(); + let (curator_lookup, bounty_id) = create_bounty::()?; + Treasury::::on_initialize(T::BlockNumber::zero()); - let bounty_id = BountyCount::::get() - 1; + let bounty_id = BountyCount::::get() - 1; let curator = T::Lookup::lookup(curator_lookup).map_err(<&str>::from)?; }: _(RawOrigin::Signed(curator), bounty_id, Vec::new()) verify { - assert_last_event::(Event::BountyExtended { index: bounty_id }.into()) + assert_last_event::(Event::BountyExtended { index: bounty_id }.into()) } spend_funds { let b in 1 .. 100; - setup_pot_account::(); - create_approved_bounties::(b)?; + setup_pot_account::(); + create_approved_bounties::(b)?; - let mut budget_remaining = BalanceOf::::max_value(); - let mut imbalance = PositiveImbalanceOf::::zero(); + let mut budget_remaining = BalanceOf::::max_value(); + let mut imbalance = PositiveImbalanceOf::::zero(); let mut total_weight = Weight::zero(); let mut missed_any = false; }: { - as pallet_treasury::SpendFunds>::spend_funds( + as pallet_treasury::SpendFunds>::spend_funds( &mut budget_remaining, &mut imbalance, &mut total_weight, @@ -205,9 +211,9 @@ benchmarks! { ); } verify { - ensure!(budget_remaining < BalanceOf::::max_value(), "Budget not used"); + ensure!(budget_remaining < BalanceOf::::max_value(), "Budget not used"); ensure!(missed_any == false, "Missed some"); - assert_last_event::(Event::BountyBecameActive { index: b - 1 }.into()) + assert_last_event::(Event::BountyBecameActive { index: b - 1 }.into()) } impl_benchmark_test_suite!(Bounties, crate::tests::new_test_ext(), crate::tests::Test) diff --git a/frame/bounties/src/lib.rs b/frame/bounties/src/lib.rs index b01a36b430a4e..8e28cacb9d929 100644 --- a/frame/bounties/src/lib.rs +++ b/frame/bounties/src/lib.rs @@ -107,9 +107,9 @@ pub use weights::WeightInfo; pub use pallet::*; -type BalanceOf = pallet_treasury::BalanceOf; +type BalanceOf = pallet_treasury::BalanceOf; -type PositiveImbalanceOf = pallet_treasury::PositiveImbalanceOf; +type PositiveImbalanceOf = pallet_treasury::PositiveImbalanceOf; /// An index of a bounty. Just a `u32`. pub type BountyIndex = u32; @@ -188,13 +188,13 @@ pub mod pallet { #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] - pub struct Pallet(_); + pub struct Pallet(_); #[pallet::config] - pub trait Config: frame_system::Config + pallet_treasury::Config { + pub trait Config: frame_system::Config + pallet_treasury::Config { /// The amount held on deposit for placing a bounty proposal. #[pallet::constant] - type BountyDepositBase: Get>; + type BountyDepositBase: Get>; /// The delay period for which a bounty beneficiary need to wait before claim the payout. #[pallet::constant] @@ -213,22 +213,22 @@ pub mod pallet { /// Maximum amount of funds that should be placed in a deposit for making a proposal. #[pallet::constant] - type CuratorDepositMax: Get>>; + type CuratorDepositMax: Get>>; /// Minimum amount of funds that should be placed in a deposit for making a proposal. #[pallet::constant] - type CuratorDepositMin: Get>>; + type CuratorDepositMin: Get>>; /// Minimum value for a bounty. #[pallet::constant] - type BountyValueMinimum: Get>; + type BountyValueMinimum: Get>; /// The amount held on deposit per byte within the tip report reason or bounty description. #[pallet::constant] - type DataDepositPerByte: Get>; + type DataDepositPerByte: Get>; /// The overarching event type. - type Event: From> + IsType<::Event>; + type Event: From> + IsType<::Event>; /// Maximum acceptable reason length. /// @@ -240,11 +240,11 @@ pub mod pallet { type WeightInfo: WeightInfo; /// The child bounty manager. - type ChildBountyManager: ChildBountyManager>; + type ChildBountyManager: ChildBountyManager>; } #[pallet::error] - pub enum Error { + pub enum Error { /// Proposer's balance is too low. InsufficientProposersBalance, /// No proposal or bounty at that index. @@ -272,17 +272,17 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { + pub enum Event, I: 'static = ()> { /// New bounty proposal. BountyProposed { index: BountyIndex }, /// A bounty proposal was rejected; funds were slashed. - BountyRejected { index: BountyIndex, bond: BalanceOf }, + BountyRejected { index: BountyIndex, bond: BalanceOf }, /// A bounty proposal is funded and became active. BountyBecameActive { index: BountyIndex }, /// A bounty is awarded to a beneficiary. BountyAwarded { index: BountyIndex, beneficiary: T::AccountId }, /// A bounty is claimed by beneficiary. - BountyClaimed { index: BountyIndex, payout: BalanceOf, beneficiary: T::AccountId }, + BountyClaimed { index: BountyIndex, payout: BalanceOf, beneficiary: T::AccountId }, /// A bounty is cancelled. BountyCanceled { index: BountyIndex }, /// A bounty expiry is extended. @@ -292,32 +292,32 @@ pub mod pallet { /// Number of bounty proposals that have been made. #[pallet::storage] #[pallet::getter(fn bounty_count)] - pub type BountyCount = StorageValue<_, BountyIndex, ValueQuery>; + pub type BountyCount, I: 'static = ()> = StorageValue<_, BountyIndex, ValueQuery>; /// Bounties that have been made. #[pallet::storage] #[pallet::getter(fn bounties)] - pub type Bounties = StorageMap< + pub type Bounties, I: 'static = ()> = StorageMap< _, Twox64Concat, BountyIndex, - Bounty, T::BlockNumber>, + Bounty, T::BlockNumber>, >; /// The description of each bounty. #[pallet::storage] #[pallet::getter(fn bounty_descriptions)] - pub type BountyDescriptions = + pub type BountyDescriptions, I: 'static = ()> = StorageMap<_, Twox64Concat, BountyIndex, BoundedVec>; /// Bounty indices that have been approved but not yet funded. #[pallet::storage] #[pallet::getter(fn bounty_approvals)] - pub type BountyApprovals = + pub type BountyApprovals, I: 'static = ()> = StorageValue<_, BoundedVec, ValueQuery>; #[pallet::call] - impl Pallet { + impl, I: 'static> Pallet { /// Propose a new bounty. /// /// The dispatch origin for this call must be _Signed_. @@ -330,10 +330,10 @@ pub mod pallet { /// - `fee`: The curator fee. /// - `value`: The total payment amount of this bounty, curator fee included. /// - `description`: The description of this bounty. - #[pallet::weight(::WeightInfo::propose_bounty(description.len() as u32))] + #[pallet::weight(>::WeightInfo::propose_bounty(description.len() as u32))] pub fn propose_bounty( origin: OriginFor, - #[pallet::compact] value: BalanceOf, + #[pallet::compact] value: BalanceOf, description: Vec, ) -> DispatchResult { let proposer = ensure_signed(origin)?; @@ -349,21 +349,21 @@ pub mod pallet { /// # /// - O(1). /// # - #[pallet::weight(::WeightInfo::approve_bounty())] + #[pallet::weight(>::WeightInfo::approve_bounty())] pub fn approve_bounty( origin: OriginFor, #[pallet::compact] bounty_id: BountyIndex, ) -> DispatchResult { T::ApproveOrigin::ensure_origin(origin)?; - Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { - let mut bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; - ensure!(bounty.status == BountyStatus::Proposed, Error::::UnexpectedStatus); + Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { + let mut bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; + ensure!(bounty.status == BountyStatus::Proposed, Error::::UnexpectedStatus); bounty.status = BountyStatus::Approved; - BountyApprovals::::try_append(bounty_id) - .map_err(|()| Error::::TooManyQueued)?; + BountyApprovals::::try_append(bounty_id) + .map_err(|()| Error::::TooManyQueued)?; Ok(()) })?; @@ -377,24 +377,24 @@ pub mod pallet { /// # /// - O(1). /// # - #[pallet::weight(::WeightInfo::propose_curator())] + #[pallet::weight(>::WeightInfo::propose_curator())] pub fn propose_curator( origin: OriginFor, #[pallet::compact] bounty_id: BountyIndex, curator: ::Source, - #[pallet::compact] fee: BalanceOf, + #[pallet::compact] fee: BalanceOf, ) -> DispatchResult { T::ApproveOrigin::ensure_origin(origin)?; let curator = T::Lookup::lookup(curator)?; - Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { - let mut bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; + Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { + let mut bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; match bounty.status { BountyStatus::Proposed | BountyStatus::Approved | BountyStatus::Funded => {}, - _ => return Err(Error::::UnexpectedStatus.into()), + _ => return Err(Error::::UnexpectedStatus.into()), }; - ensure!(fee < bounty.value, Error::::InvalidFee); + ensure!(fee < bounty.value, Error::::InvalidFee); bounty.status = BountyStatus::CuratorProposed { curator }; bounty.fee = fee; @@ -422,7 +422,7 @@ pub mod pallet { /// # /// - O(1). /// # - #[pallet::weight(::WeightInfo::unassign_curator())] + #[pallet::weight(>::WeightInfo::unassign_curator())] pub fn unassign_curator( origin: OriginFor, #[pallet::compact] bounty_id: BountyIndex, @@ -431,10 +431,11 @@ pub mod pallet { .map(Some) .or_else(|_| T::RejectOrigin::ensure_origin(origin).map(|_| None))?; - Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { - let mut bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; + Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { + let mut bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; - let slash_curator = |curator: &T::AccountId, curator_deposit: &mut BalanceOf| { + let slash_curator = |curator: &T::AccountId, + curator_deposit: &mut BalanceOf| { let imbalance = T::Currency::slash_reserved(curator, *curator_deposit).0; T::OnSlash::on_unbalanced(imbalance); *curator_deposit = Zero::zero(); @@ -443,7 +444,7 @@ pub mod pallet { match bounty.status { BountyStatus::Proposed | BountyStatus::Approved | BountyStatus::Funded => { // No curator to unassign at this point. - return Err(Error::::UnexpectedStatus.into()) + return Err(Error::::UnexpectedStatus.into()) }, BountyStatus::CuratorProposed { ref curator } => { // A curator has been proposed, but not accepted yet. @@ -468,7 +469,7 @@ pub mod pallet { // Continue to change bounty status below... } else { // Curator has more time to give an update. - return Err(Error::::Premature.into()) + return Err(Error::::Premature.into()) } } else { // Else this is the curator, willingly giving up their role. @@ -506,19 +507,19 @@ pub mod pallet { /// # /// - O(1). /// # - #[pallet::weight(::WeightInfo::accept_curator())] + #[pallet::weight(>::WeightInfo::accept_curator())] pub fn accept_curator( origin: OriginFor, #[pallet::compact] bounty_id: BountyIndex, ) -> DispatchResult { let signer = ensure_signed(origin)?; - Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { - let mut bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; + Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { + let mut bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; match bounty.status { BountyStatus::CuratorProposed { ref curator } => { - ensure!(signer == *curator, Error::::RequireCurator); + ensure!(signer == *curator, Error::::RequireCurator); let deposit = Self::calculate_curator_deposit(&bounty.fee); T::Currency::reserve(curator, deposit)?; @@ -531,7 +532,7 @@ pub mod pallet { Ok(()) }, - _ => Err(Error::::UnexpectedStatus.into()), + _ => Err(Error::::UnexpectedStatus.into()), } })?; Ok(()) @@ -548,7 +549,7 @@ pub mod pallet { /// # /// - O(1). /// # - #[pallet::weight(::WeightInfo::award_bounty())] + #[pallet::weight(>::WeightInfo::award_bounty())] pub fn award_bounty( origin: OriginFor, #[pallet::compact] bounty_id: BountyIndex, @@ -557,20 +558,20 @@ pub mod pallet { let signer = ensure_signed(origin)?; let beneficiary = T::Lookup::lookup(beneficiary)?; - Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { - let mut bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; + Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { + let mut bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; // Ensure no active child bounties before processing the call. ensure!( T::ChildBountyManager::child_bounties_count(bounty_id) == 0, - Error::::HasActiveChildBounty + Error::::HasActiveChildBounty ); match &bounty.status { BountyStatus::Active { curator, .. } => { - ensure!(signer == *curator, Error::::RequireCurator); + ensure!(signer == *curator, Error::::RequireCurator); }, - _ => return Err(Error::::UnexpectedStatus.into()), + _ => return Err(Error::::UnexpectedStatus.into()), } bounty.status = BountyStatus::PendingPayout { curator: signer, @@ -582,7 +583,7 @@ pub mod pallet { Ok(()) })?; - Self::deposit_event(Event::::BountyAwarded { index: bounty_id, beneficiary }); + Self::deposit_event(Event::::BountyAwarded { index: bounty_id, beneficiary }); Ok(()) } @@ -595,21 +596,21 @@ pub mod pallet { /// # /// - O(1). /// # - #[pallet::weight(::WeightInfo::claim_bounty())] + #[pallet::weight(>::WeightInfo::claim_bounty())] pub fn claim_bounty( origin: OriginFor, #[pallet::compact] bounty_id: BountyIndex, ) -> DispatchResult { let _ = ensure_signed(origin)?; // anyone can trigger claim - Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { - let bounty = maybe_bounty.take().ok_or(Error::::InvalidIndex)?; + Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { + let bounty = maybe_bounty.take().ok_or(Error::::InvalidIndex)?; if let BountyStatus::PendingPayout { curator, beneficiary, unlock_at } = bounty.status { ensure!( frame_system::Pallet::::block_number() >= unlock_at, - Error::::Premature + Error::::Premature ); let bounty_account = Self::bounty_account_id(bounty_id); let balance = T::Currency::free_balance(&bounty_account); @@ -633,16 +634,16 @@ pub mod pallet { *maybe_bounty = None; - BountyDescriptions::::remove(bounty_id); + BountyDescriptions::::remove(bounty_id); - Self::deposit_event(Event::::BountyClaimed { + Self::deposit_event(Event::::BountyClaimed { index: bounty_id, payout, beneficiary, }); Ok(()) } else { - Err(Error::::UnexpectedStatus.into()) + Err(Error::::UnexpectedStatus.into()) } })?; Ok(()) @@ -658,47 +659,47 @@ pub mod pallet { /// # /// - O(1). /// # - #[pallet::weight(::WeightInfo::close_bounty_proposed() - .max(::WeightInfo::close_bounty_active()))] + #[pallet::weight(>::WeightInfo::close_bounty_proposed() + .max(>::WeightInfo::close_bounty_active()))] pub fn close_bounty( origin: OriginFor, #[pallet::compact] bounty_id: BountyIndex, ) -> DispatchResultWithPostInfo { T::RejectOrigin::ensure_origin(origin)?; - Bounties::::try_mutate_exists( + Bounties::::try_mutate_exists( bounty_id, |maybe_bounty| -> DispatchResultWithPostInfo { - let bounty = maybe_bounty.as_ref().ok_or(Error::::InvalidIndex)?; + let bounty = maybe_bounty.as_ref().ok_or(Error::::InvalidIndex)?; // Ensure no active child bounties before processing the call. ensure!( T::ChildBountyManager::child_bounties_count(bounty_id) == 0, - Error::::HasActiveChildBounty + Error::::HasActiveChildBounty ); match &bounty.status { BountyStatus::Proposed => { // The reject origin would like to cancel a proposed bounty. - BountyDescriptions::::remove(bounty_id); + BountyDescriptions::::remove(bounty_id); let value = bounty.bond; let imbalance = T::Currency::slash_reserved(&bounty.proposer, value).0; T::OnSlash::on_unbalanced(imbalance); *maybe_bounty = None; - Self::deposit_event(Event::::BountyRejected { + Self::deposit_event(Event::::BountyRejected { index: bounty_id, bond: value, }); // Return early, nothing else to do. return Ok( - Some(::WeightInfo::close_bounty_proposed()).into() + Some(>::WeightInfo::close_bounty_proposed()).into() ) }, BountyStatus::Approved => { // For weight reasons, we don't allow a council to cancel in this phase. // We ask for them to wait until it is funded before they can cancel. - return Err(Error::::UnexpectedStatus.into()) + return Err(Error::::UnexpectedStatus.into()) }, BountyStatus::Funded | BountyStatus::CuratorProposed { .. } => { // Nothing extra to do besides the removal of the bounty below. @@ -715,13 +716,13 @@ pub mod pallet { // this bounty, it should mean the curator was acting maliciously. // So the council should first unassign the curator, slashing their // deposit. - return Err(Error::::PendingPayout.into()) + return Err(Error::::PendingPayout.into()) }, } let bounty_account = Self::bounty_account_id(bounty_id); - BountyDescriptions::::remove(bounty_id); + BountyDescriptions::::remove(bounty_id); let balance = T::Currency::free_balance(&bounty_account); let res = T::Currency::transfer( @@ -733,8 +734,8 @@ pub mod pallet { debug_assert!(res.is_ok()); *maybe_bounty = None; - Self::deposit_event(Event::::BountyCanceled { index: bounty_id }); - Ok(Some(::WeightInfo::close_bounty_active()).into()) + Self::deposit_event(Event::::BountyCanceled { index: bounty_id }); + Ok(Some(>::WeightInfo::close_bounty_active()).into()) }, ) } @@ -749,7 +750,7 @@ pub mod pallet { /// # /// - O(1). /// # - #[pallet::weight(::WeightInfo::extend_bounty_expiry())] + #[pallet::weight(>::WeightInfo::extend_bounty_expiry())] pub fn extend_bounty_expiry( origin: OriginFor, #[pallet::compact] bounty_id: BountyIndex, @@ -757,30 +758,30 @@ pub mod pallet { ) -> DispatchResult { let signer = ensure_signed(origin)?; - Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { - let bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; + Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { + let bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; match bounty.status { BountyStatus::Active { ref curator, ref mut update_due } => { - ensure!(*curator == signer, Error::::RequireCurator); + ensure!(*curator == signer, Error::::RequireCurator); *update_due = (frame_system::Pallet::::block_number() + T::BountyUpdatePeriod::get()) .max(*update_due); }, - _ => return Err(Error::::UnexpectedStatus.into()), + _ => return Err(Error::::UnexpectedStatus.into()), } Ok(()) })?; - Self::deposit_event(Event::::BountyExtended { index: bounty_id }); + Self::deposit_event(Event::::BountyExtended { index: bounty_id }); Ok(()) } } } -impl Pallet { - pub fn calculate_curator_deposit(fee: &BalanceOf) -> BalanceOf { +impl, I: 'static> Pallet { + pub fn calculate_curator_deposit(fee: &BalanceOf) -> BalanceOf { let mut deposit = T::CuratorDepositMultiplier::get() * *fee; if let Some(max_deposit) = T::CuratorDepositMax::get() { @@ -812,11 +813,11 @@ impl Pallet { fn create_bounty( proposer: T::AccountId, description: Vec, - value: BalanceOf, + value: BalanceOf, ) -> DispatchResult { let bounded_description: BoundedVec<_, _> = - description.try_into().map_err(|()| Error::::ReasonTooBig)?; - ensure!(value >= T::BountyValueMinimum::get(), Error::::InvalidValue); + description.try_into().map_err(|()| Error::::ReasonTooBig)?; + ensure!(value >= T::BountyValueMinimum::get(), Error::::InvalidValue); let index = Self::bounty_count(); @@ -824,9 +825,9 @@ impl Pallet { let bond = T::BountyDepositBase::get() + T::DataDepositPerByte::get() * (bounded_description.len() as u32).into(); T::Currency::reserve(&proposer, bond) - .map_err(|_| Error::::InsufficientProposersBalance)?; + .map_err(|_| Error::::InsufficientProposersBalance)?; - BountyCount::::put(index + 1); + BountyCount::::put(index + 1); let bounty = Bounty { proposer, @@ -837,26 +838,26 @@ impl Pallet { status: BountyStatus::Proposed, }; - Bounties::::insert(index, &bounty); - BountyDescriptions::::insert(index, bounded_description); + Bounties::::insert(index, &bounty); + BountyDescriptions::::insert(index, bounded_description); - Self::deposit_event(Event::::BountyProposed { index }); + Self::deposit_event(Event::::BountyProposed { index }); Ok(()) } } -impl pallet_treasury::SpendFunds for Pallet { +impl, I: 'static> pallet_treasury::SpendFunds for Pallet { fn spend_funds( - budget_remaining: &mut BalanceOf, - imbalance: &mut PositiveImbalanceOf, + budget_remaining: &mut BalanceOf, + imbalance: &mut PositiveImbalanceOf, total_weight: &mut Weight, missed_any: &mut bool, ) { - let bounties_len = BountyApprovals::::mutate(|v| { + let bounties_len = BountyApprovals::::mutate(|v| { let bounties_approval_len = v.len() as u32; v.retain(|&index| { - Bounties::::mutate(index, |bounty| { + Bounties::::mutate(index, |bounty| { // Should always be true, but shouldn't panic if false or we're screwed. if let Some(bounty) = bounty { if bounty.value <= *budget_remaining { @@ -874,7 +875,7 @@ impl pallet_treasury::SpendFunds for Pallet { bounty.value, )); - Self::deposit_event(Event::::BountyBecameActive { index }); + Self::deposit_event(Event::::BountyBecameActive { index }); false } else { *missed_any = true; @@ -888,7 +889,7 @@ impl pallet_treasury::SpendFunds for Pallet { bounties_approval_len }); - *total_weight += ::WeightInfo::spend_funds(bounties_len); + *total_weight += >::WeightInfo::spend_funds(bounties_len); } } diff --git a/frame/bounties/src/tests.rs b/frame/bounties/src/tests.rs index 0904e3a2901bb..ff220600794d4 100644 --- a/frame/bounties/src/tests.rs +++ b/frame/bounties/src/tests.rs @@ -35,7 +35,7 @@ use sp_core::H256; use sp_runtime::{ testing::Header, traits::{BadOrigin, BlakeTwo256, IdentityLookup}, - Perbill, Storage, + BuildStorage, Perbill, Storage, }; use super::Event as BountiesEvent; @@ -52,7 +52,9 @@ frame_support::construct_runtime!( System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Bounties: pallet_bounties::{Pallet, Call, Storage, Event}, + Bounties1: pallet_bounties::::{Pallet, Call, Storage, Event}, Treasury: pallet_treasury::{Pallet, Call, Storage, Config, Event}, + Treasury1: pallet_treasury::::{Pallet, Call, Storage, Config, Event}, } ); @@ -105,8 +107,9 @@ thread_local! { } parameter_types! { pub const ProposalBond: Permill = Permill::from_percent(5); - pub const Burn: Permill = Permill::from_percent(50); + pub static Burn: Permill = Permill::from_percent(50); pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry"); + pub const TreasuryPalletId2: PalletId = PalletId(*b"py/trsr2"); } impl pallet_treasury::Config for Test { @@ -128,6 +131,25 @@ impl pallet_treasury::Config for Test { type SpendOrigin = frame_support::traits::NeverEnsureOrigin; } +impl pallet_treasury::Config for Test { + type PalletId = TreasuryPalletId2; + type Currency = pallet_balances::Pallet; + type ApproveOrigin = frame_system::EnsureRoot; + type RejectOrigin = frame_system::EnsureRoot; + type Event = Event; + type OnSlash = (); + type ProposalBond = ProposalBond; + type ProposalBondMinimum = ConstU64<1>; + type ProposalBondMaximum = (); + type SpendPeriod = ConstU64<2>; + type Burn = Burn; + type BurnDestination = (); // Just gets burned. + type WeightInfo = (); + type SpendFunds = Bounties1; + type MaxApprovals = ConstU32<100>; + type SpendOrigin = frame_support::traits::NeverEnsureOrigin; +} + parameter_types! { // This will be 50% of the bounty fee. pub const CuratorDepositMultiplier: Permill = Permill::from_percent(50); @@ -151,18 +173,35 @@ impl Config for Test { type ChildBountyManager = (); } +impl Config for Test { + type Event = Event; + type BountyDepositBase = ConstU64<80>; + type BountyDepositPayoutDelay = ConstU64<3>; + type BountyUpdatePeriod = ConstU64<20>; + type CuratorDepositMultiplier = CuratorDepositMultiplier; + type CuratorDepositMax = CuratorDepositMax; + type CuratorDepositMin = CuratorDepositMin; + type BountyValueMinimum = ConstU64<1>; + type DataDepositPerByte = ConstU64<1>; + type MaximumReasonLength = ConstU32<16384>; + type WeightInfo = (); + type ChildBountyManager = (); +} + type TreasuryError = pallet_treasury::Error; pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - pallet_balances::GenesisConfig:: { - // Total issuance will be 200 with treasury account initialized at ED. - balances: vec![(0, 100), (1, 98), (2, 1)], + let mut ext: sp_io::TestExternalities = GenesisConfig { + system: frame_system::GenesisConfig::default(), + balances: pallet_balances::GenesisConfig { balances: vec![(0, 100), (1, 98), (2, 1)] }, + treasury: Default::default(), + treasury_1: Default::default(), } - .assimilate_storage(&mut t) - .unwrap(); - GenesisBuild::::assimilate_storage(&pallet_treasury::GenesisConfig, &mut t).unwrap(); - t.into() + .build_storage() + .unwrap() + .into(); + ext.execute_with(|| System::set_block_number(1)); + ext } fn last_event() -> BountiesEvent { @@ -276,7 +315,7 @@ fn reject_non_existent_spend_proposal_fails() { new_test_ext().execute_with(|| { assert_noop!( Treasury::reject_proposal(Origin::root(), 0), - pallet_treasury::Error::::InvalidIndex + pallet_treasury::Error::::InvalidIndex ); }); } @@ -469,7 +508,7 @@ fn close_bounty_works() { assert_eq!(Balances::free_balance(0), 100 - deposit); assert_eq!(Bounties::bounties(0), None); - assert!(!pallet_treasury::Proposals::::contains_key(0)); + assert!(!pallet_treasury::Proposals::::contains_key(0)); assert_eq!(Bounties::bounty_descriptions(0), None); }); @@ -1122,3 +1161,29 @@ fn accept_curator_handles_different_deposit_calculations() { assert_eq!(Balances::reserved_balance(&user), expected_deposit); }); } + +#[test] +fn approve_bounty_works_second_instance() { + new_test_ext().execute_with(|| { + // Set burn to 0 to make tracking funds easier. + Burn::set(Permill::from_percent(0)); + + System::set_block_number(1); + Balances::make_free_balance_be(&Treasury::account_id(), 101); + Balances::make_free_balance_be(&Treasury1::account_id(), 201); + assert_eq!(Balances::free_balance(&Treasury::account_id()), 101); + assert_eq!(Balances::free_balance(&Treasury1::account_id()), 201); + + assert_ok!(Bounties1::propose_bounty(Origin::signed(0), 50, b"12345".to_vec())); + assert_ok!(Bounties1::approve_bounty(Origin::root(), 0)); + >::on_initialize(2); + >::on_initialize(2); + + // Bounties 1 is funded... but from where? + assert_eq!(Balances::free_balance(Bounties1::bounty_account_id(0)), 50); + // Treasury 1 unchanged + assert_eq!(Balances::free_balance(&Treasury::account_id()), 101); + // Treasury 2 has funds removed + assert_eq!(Balances::free_balance(&Treasury1::account_id()), 201 - 50); + }); +} diff --git a/frame/tips/src/benchmarking.rs b/frame/tips/src/benchmarking.rs index 190ef60f3b810..33e455bd3b9fd 100644 --- a/frame/tips/src/benchmarking.rs +++ b/frame/tips/src/benchmarking.rs @@ -19,7 +19,7 @@ #![cfg(feature = "runtime-benchmarks")] -use frame_benchmarking::{account, benchmarks, whitelisted_caller}; +use frame_benchmarking::{account, benchmarks_instance_pallet, whitelisted_caller}; use frame_support::ensure; use frame_system::RawOrigin; use sp_runtime::traits::Saturating; @@ -30,7 +30,7 @@ use crate::Pallet as TipsMod; const SEED: u32 = 0; // Create the pre-requisite information needed to create a `report_awesome`. -fn setup_awesome(length: u32) -> (T::AccountId, Vec, T::AccountId) { +fn setup_awesome, I: 'static>(length: u32) -> (T::AccountId, Vec, T::AccountId) { let caller = whitelisted_caller(); let value = T::TipReportDepositBase::get() + T::DataDepositPerByte::get() * length.into() + @@ -42,10 +42,10 @@ fn setup_awesome(length: u32) -> (T::AccountId, Vec, T::AccountId } // Create the pre-requisite information needed to call `tip_new`. -fn setup_tip( +fn setup_tip, I: 'static>( r: u32, t: u32, -) -> Result<(T::AccountId, Vec, T::AccountId, BalanceOf), &'static str> { +) -> Result<(T::AccountId, Vec, T::AccountId, BalanceOf), &'static str> { let tippers_count = T::Tippers::count(); for i in 0..t { @@ -64,13 +64,17 @@ fn setup_tip( // Create `t` new tips for the tip proposal with `hash`. // This function automatically makes the tip able to close. -fn create_tips(t: u32, hash: T::Hash, value: BalanceOf) -> Result<(), &'static str> { +fn create_tips, I: 'static>( + t: u32, + hash: T::Hash, + value: BalanceOf, +) -> Result<(), &'static str> { for i in 0..t { let caller = account("member", i, SEED); ensure!(T::Tippers::contains(&caller), "caller is not a tipper"); - TipsMod::::tip(RawOrigin::Signed(caller).into(), hash, value)?; + TipsMod::::tip(RawOrigin::Signed(caller).into(), hash, value)?; } - Tips::::mutate(hash, |maybe_tip| { + Tips::::mutate(hash, |maybe_tip| { if let Some(open_tip) = maybe_tip { open_tip.closes = Some(T::BlockNumber::zero()); } @@ -78,16 +82,16 @@ fn create_tips(t: u32, hash: T::Hash, value: BalanceOf) -> Result< Ok(()) } -fn setup_pot_account() { - let pot_account = TipsMod::::account_id(); +fn setup_pot_account, I: 'static>() { + let pot_account = TipsMod::::account_id(); let value = T::Currency::minimum_balance().saturating_mul(1_000_000_000u32.into()); let _ = T::Currency::make_free_balance_be(&pot_account, value); } -benchmarks! { +benchmarks_instance_pallet! { report_awesome { let r in 0 .. T::MaximumReasonLength::get(); - let (caller, reason, awesome_person) = setup_awesome::(r); + let (caller, reason, awesome_person) = setup_awesome::(r); // Whitelist caller account from further DB operations. let caller_key = frame_system::Account::::hashed_key_for(&caller); frame_benchmarking::benchmarking::add_to_whitelist(caller_key.into()); @@ -95,8 +99,8 @@ benchmarks! { retract_tip { let r = T::MaximumReasonLength::get(); - let (caller, reason, awesome_person) = setup_awesome::(r); - TipsMod::::report_awesome( + let (caller, reason, awesome_person) = setup_awesome::(r); + TipsMod::::report_awesome( RawOrigin::Signed(caller.clone()).into(), reason.clone(), awesome_person.clone() @@ -112,7 +116,7 @@ benchmarks! { let r in 0 .. T::MaximumReasonLength::get(); let t in 1 .. T::Tippers::max_len() as u32; - let (caller, reason, beneficiary, value) = setup_tip::(r, t)?; + let (caller, reason, beneficiary, value) = setup_tip::(r, t)?; // Whitelist caller account from further DB operations. let caller_key = frame_system::Account::::hashed_key_for(&caller); frame_benchmarking::benchmarking::add_to_whitelist(caller_key.into()); @@ -120,9 +124,9 @@ benchmarks! { tip { let t in 1 .. T::Tippers::max_len() as u32; - let (member, reason, beneficiary, value) = setup_tip::(0, t)?; + let (member, reason, beneficiary, value) = setup_tip::(0, t)?; let value = T::Currency::minimum_balance().saturating_mul(100u32.into()); - TipsMod::::tip_new( + TipsMod::::tip_new( RawOrigin::Signed(member).into(), reason.clone(), beneficiary.clone(), @@ -130,8 +134,8 @@ benchmarks! { )?; 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, value)?; + ensure!(Tips::::contains_key(hash), "tip does not exist"); + create_tips::(t - 1, hash, value)?; let caller = account("member", t - 1, SEED); // Whitelist caller account from further DB operations. let caller_key = frame_system::Account::::hashed_key_for(&caller); @@ -142,12 +146,12 @@ benchmarks! { let t in 1 .. T::Tippers::max_len() as u32; // Make sure pot is funded - setup_pot_account::(); + setup_pot_account::(); // Set up a new tip proposal - let (member, reason, beneficiary, value) = setup_tip::(0, t)?; + let (member, reason, beneficiary, value) = setup_tip::(0, t)?; let value = T::Currency::minimum_balance().saturating_mul(100u32.into()); - TipsMod::::tip_new( + TipsMod::::tip_new( RawOrigin::Signed(member).into(), reason.clone(), beneficiary.clone(), @@ -157,9 +161,9 @@ benchmarks! { // Create a bunch of tips let reason_hash = T::Hashing::hash(&reason[..]); let hash = T::Hashing::hash_of(&(&reason_hash, &beneficiary)); - ensure!(Tips::::contains_key(hash), "tip does not exist"); + ensure!(Tips::::contains_key(hash), "tip does not exist"); - create_tips::(t, hash, value)?; + create_tips::(t, hash, value)?; let caller = account("caller", t, SEED); // Whitelist caller account from further DB operations. @@ -171,12 +175,12 @@ benchmarks! { let t in 1 .. T::Tippers::max_len() as u32; // Make sure pot is funded - setup_pot_account::(); + setup_pot_account::(); // Set up a new tip proposal - let (member, reason, beneficiary, value) = setup_tip::(0, t)?; + let (member, reason, beneficiary, value) = setup_tip::(0, t)?; let value = T::Currency::minimum_balance().saturating_mul(100u32.into()); - TipsMod::::tip_new( + TipsMod::::tip_new( RawOrigin::Signed(member).into(), reason.clone(), beneficiary.clone(), @@ -185,7 +189,7 @@ benchmarks! { let reason_hash = T::Hashing::hash(&reason[..]); let hash = T::Hashing::hash_of(&(&reason_hash, &beneficiary)); - ensure!(Tips::::contains_key(hash), "tip does not exist"); + ensure!(Tips::::contains_key(hash), "tip does not exist"); }: _(RawOrigin::Root, hash) impl_benchmark_test_suite!(TipsMod, crate::tests::new_test_ext(), crate::tests::Test); diff --git a/frame/tips/src/lib.rs b/frame/tips/src/lib.rs index e1c7b5e77c062..71af87b42b55b 100644 --- a/frame/tips/src/lib.rs +++ b/frame/tips/src/lib.rs @@ -78,8 +78,8 @@ use frame_support::{ pub use pallet::*; pub use weights::WeightInfo; -pub type BalanceOf = pallet_treasury::BalanceOf; -pub type NegativeImbalanceOf = pallet_treasury::NegativeImbalanceOf; +pub type BalanceOf = pallet_treasury::BalanceOf; +pub type NegativeImbalanceOf = pallet_treasury::NegativeImbalanceOf; /// An open tipping "motion". Retains all details of a tip including information on the finder /// and the members who have voted. @@ -121,12 +121,12 @@ pub mod pallet { #[pallet::generate_store(pub(super) trait Store)] #[pallet::storage_version(STORAGE_VERSION)] #[pallet::without_storage_info] - pub struct Pallet(_); + pub struct Pallet(_); #[pallet::config] - pub trait Config: frame_system::Config + pallet_treasury::Config { + pub trait Config: frame_system::Config + pallet_treasury::Config { /// The overarching event type. - type Event: From> + IsType<::Event>; + type Event: From> + IsType<::Event>; /// Maximum acceptable reason length. /// @@ -136,7 +136,7 @@ pub mod pallet { /// The amount held on deposit per byte within the tip report reason or bounty description. #[pallet::constant] - type DataDepositPerByte: Get>; + type DataDepositPerByte: Get>; /// The period for which a tip remains open after is has achieved threshold tippers. #[pallet::constant] @@ -148,7 +148,7 @@ pub mod pallet { /// The amount held on deposit for placing a tip report. #[pallet::constant] - type TipReportDepositBase: Get>; + type TipReportDepositBase: Get>; /// Origin from which tippers must come. /// @@ -166,11 +166,11 @@ pub mod pallet { /// guaranteed to be a secure hash. #[pallet::storage] #[pallet::getter(fn tips)] - pub type Tips = StorageMap< + pub type Tips, I: 'static = ()> = StorageMap< _, Twox64Concat, T::Hash, - OpenTip, T::BlockNumber, T::Hash>, + OpenTip, T::BlockNumber, T::Hash>, OptionQuery, >; @@ -178,25 +178,26 @@ pub mod pallet { /// insecure enumerable hash since the key is guaranteed to be the result of a secure hash. #[pallet::storage] #[pallet::getter(fn reasons)] - pub type Reasons = StorageMap<_, Identity, T::Hash, Vec, OptionQuery>; + pub type Reasons, I: 'static = ()> = + StorageMap<_, Identity, T::Hash, Vec, OptionQuery>; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { + pub enum Event, I: 'static = ()> { /// A new tip suggestion has been opened. NewTip { tip_hash: T::Hash }, /// A tip suggestion has reached threshold and is closing. TipClosing { tip_hash: T::Hash }, /// A tip suggestion has been closed. - TipClosed { tip_hash: T::Hash, who: T::AccountId, payout: BalanceOf }, + TipClosed { tip_hash: T::Hash, who: T::AccountId, payout: BalanceOf }, /// A tip suggestion has been retracted. TipRetracted { tip_hash: T::Hash }, /// A tip suggestion has been slashed. - TipSlashed { tip_hash: T::Hash, finder: T::AccountId, deposit: BalanceOf }, + TipSlashed { tip_hash: T::Hash, finder: T::AccountId, deposit: BalanceOf }, } #[pallet::error] - pub enum Error { + pub enum Error { /// The reason given is just too big. ReasonTooBig, /// The tip was already found/started. @@ -212,7 +213,7 @@ pub mod pallet { } #[pallet::call] - impl Pallet { + impl, I: 'static> Pallet { /// Report something `reason` that deserves a tip and claim any eventual the finder's fee. /// /// The dispatch origin for this call must be _Signed_. @@ -232,7 +233,7 @@ pub mod pallet { /// - DbReads: `Reasons`, `Tips` /// - DbWrites: `Reasons`, `Tips` /// # - #[pallet::weight(::WeightInfo::report_awesome(reason.len() as u32))] + #[pallet::weight(>::WeightInfo::report_awesome(reason.len() as u32))] pub fn report_awesome( origin: OriginFor, reason: Vec, @@ -242,19 +243,19 @@ pub mod pallet { ensure!( reason.len() <= T::MaximumReasonLength::get() as usize, - Error::::ReasonTooBig + Error::::ReasonTooBig ); let reason_hash = T::Hashing::hash(&reason[..]); - ensure!(!Reasons::::contains_key(&reason_hash), Error::::AlreadyKnown); + ensure!(!Reasons::::contains_key(&reason_hash), Error::::AlreadyKnown); let hash = T::Hashing::hash_of(&(&reason_hash, &who)); - ensure!(!Tips::::contains_key(&hash), Error::::AlreadyKnown); + ensure!(!Tips::::contains_key(&hash), Error::::AlreadyKnown); let deposit = T::TipReportDepositBase::get() + T::DataDepositPerByte::get() * (reason.len() as u32).into(); T::Currency::reserve(&finder, deposit)?; - Reasons::::insert(&reason_hash, &reason); + Reasons::::insert(&reason_hash, &reason); let tip = OpenTip { reason: reason_hash, who, @@ -264,7 +265,7 @@ pub mod pallet { tips: vec![], finders_fee: true, }; - Tips::::insert(&hash, tip); + Tips::::insert(&hash, tip); Self::deposit_event(Event::NewTip { tip_hash: hash }); Ok(()) } @@ -288,14 +289,14 @@ pub mod pallet { /// - DbReads: `Tips`, `origin account` /// - DbWrites: `Reasons`, `Tips`, `origin account` /// # - #[pallet::weight(::WeightInfo::retract_tip())] + #[pallet::weight(>::WeightInfo::retract_tip())] pub fn retract_tip(origin: OriginFor, hash: T::Hash) -> DispatchResult { let who = ensure_signed(origin)?; - let tip = Tips::::get(&hash).ok_or(Error::::UnknownTip)?; - ensure!(tip.finder == who, Error::::NotFinder); + let tip = Tips::::get(&hash).ok_or(Error::::UnknownTip)?; + ensure!(tip.finder == who, Error::::NotFinder); - Reasons::::remove(&tip.reason); - Tips::::remove(&hash); + Reasons::::remove(&tip.reason); + Tips::::remove(&hash); if !tip.deposit.is_zero() { let err_amount = T::Currency::unreserve(&who, tip.deposit); debug_assert!(err_amount.is_zero()); @@ -326,20 +327,20 @@ pub mod pallet { /// - DbReads: `Tippers`, `Reasons` /// - DbWrites: `Reasons`, `Tips` /// # - #[pallet::weight(::WeightInfo::tip_new(reason.len() as u32, T::Tippers::max_len() as u32))] + #[pallet::weight(>::WeightInfo::tip_new(reason.len() as u32, T::Tippers::max_len() as u32))] pub fn tip_new( origin: OriginFor, reason: Vec, who: T::AccountId, - #[pallet::compact] tip_value: BalanceOf, + #[pallet::compact] tip_value: BalanceOf, ) -> DispatchResult { let tipper = ensure_signed(origin)?; ensure!(T::Tippers::contains(&tipper), BadOrigin); let reason_hash = T::Hashing::hash(&reason[..]); - ensure!(!Reasons::::contains_key(&reason_hash), Error::::AlreadyKnown); + ensure!(!Reasons::::contains_key(&reason_hash), Error::::AlreadyKnown); let hash = T::Hashing::hash_of(&(&reason_hash, &who)); - Reasons::::insert(&reason_hash, &reason); + Reasons::::insert(&reason_hash, &reason); Self::deposit_event(Event::NewTip { tip_hash: hash }); let tips = vec![(tipper.clone(), tip_value)]; let tip = OpenTip { @@ -351,7 +352,7 @@ pub mod pallet { tips, finders_fee: false, }; - Tips::::insert(&hash, tip); + Tips::::insert(&hash, tip); Ok(()) } @@ -379,20 +380,20 @@ pub mod pallet { /// - DbReads: `Tippers`, `Tips` /// - DbWrites: `Tips` /// # - #[pallet::weight(::WeightInfo::tip(T::Tippers::max_len() as u32))] + #[pallet::weight(>::WeightInfo::tip(T::Tippers::max_len() as u32))] pub fn tip( origin: OriginFor, hash: T::Hash, - #[pallet::compact] tip_value: BalanceOf, + #[pallet::compact] tip_value: BalanceOf, ) -> DispatchResult { let tipper = ensure_signed(origin)?; ensure!(T::Tippers::contains(&tipper), BadOrigin); - let mut tip = Tips::::get(hash).ok_or(Error::::UnknownTip)?; + let mut tip = Tips::::get(hash).ok_or(Error::::UnknownTip)?; if Self::insert_tip_and_check_closing(&mut tip, tipper, tip_value) { Self::deposit_event(Event::TipClosing { tip_hash: hash }); } - Tips::::insert(&hash, tip); + Tips::::insert(&hash, tip); Ok(()) } @@ -412,16 +413,16 @@ pub mod pallet { /// - DbReads: `Tips`, `Tippers`, `tip finder` /// - DbWrites: `Reasons`, `Tips`, `Tippers`, `tip finder` /// # - #[pallet::weight(::WeightInfo::close_tip(T::Tippers::max_len() as u32))] + #[pallet::weight(>::WeightInfo::close_tip(T::Tippers::max_len() as u32))] pub fn close_tip(origin: OriginFor, hash: T::Hash) -> DispatchResult { ensure_signed(origin)?; - let tip = Tips::::get(hash).ok_or(Error::::UnknownTip)?; - let n = tip.closes.as_ref().ok_or(Error::::StillOpen)?; - ensure!(frame_system::Pallet::::block_number() >= *n, Error::::Premature); + let tip = Tips::::get(hash).ok_or(Error::::UnknownTip)?; + let n = tip.closes.as_ref().ok_or(Error::::StillOpen)?; + ensure!(frame_system::Pallet::::block_number() >= *n, Error::::Premature); // closed. - Reasons::::remove(&tip.reason); - Tips::::remove(hash); + Reasons::::remove(&tip.reason); + Tips::::remove(hash); Self::payout_tip(hash, tip); Ok(()) } @@ -438,17 +439,17 @@ pub mod pallet { /// `T` is charged as upper bound given by `ContainsLengthBound`. /// The actual cost depends on the implementation of `T::Tippers`. /// # - #[pallet::weight(::WeightInfo::slash_tip(T::Tippers::max_len() as u32))] + #[pallet::weight(>::WeightInfo::slash_tip(T::Tippers::max_len() as u32))] pub fn slash_tip(origin: OriginFor, hash: T::Hash) -> DispatchResult { T::RejectOrigin::ensure_origin(origin)?; - let tip = Tips::::take(hash).ok_or(Error::::UnknownTip)?; + let tip = Tips::::take(hash).ok_or(Error::::UnknownTip)?; if !tip.deposit.is_zero() { let imbalance = T::Currency::slash_reserved(&tip.finder, tip.deposit).0; T::OnSlash::on_unbalanced(imbalance); } - Reasons::::remove(&tip.reason); + Reasons::::remove(&tip.reason); Self::deposit_event(Event::TipSlashed { tip_hash: hash, finder: tip.finder, @@ -459,7 +460,7 @@ pub mod pallet { } } -impl Pallet { +impl, I: 'static> Pallet { // Add public immutables and private mutables. /// The account ID of the treasury pot. @@ -475,9 +476,9 @@ impl Pallet { /// /// `O(T)` and one storage access. fn insert_tip_and_check_closing( - tip: &mut OpenTip, T::BlockNumber, T::Hash>, + tip: &mut OpenTip, T::BlockNumber, T::Hash>, tipper: T::AccountId, - tip_value: BalanceOf, + tip_value: BalanceOf, ) -> bool { match tip.tips.binary_search_by_key(&&tipper, |x| &x.0) { Ok(pos) => tip.tips[pos] = (tipper, tip_value), @@ -494,7 +495,7 @@ impl Pallet { } /// Remove any non-members of `Tippers` from a `tips` vector. `O(T)`. - fn retain_active_tips(tips: &mut Vec<(T::AccountId, BalanceOf)>) { + fn retain_active_tips(tips: &mut Vec<(T::AccountId, BalanceOf)>) { let members = T::Tippers::sorted_members(); let mut members_iter = members.iter(); let mut member = members_iter.next(); @@ -520,14 +521,14 @@ impl Pallet { /// Plus `O(T)` (`T` is Tippers length). fn payout_tip( hash: T::Hash, - tip: OpenTip, T::BlockNumber, T::Hash>, + tip: OpenTip, T::BlockNumber, T::Hash>, ) { let mut tips = tip.tips; Self::retain_active_tips(&mut tips); tips.sort_by_key(|i| i.1); let treasury = Self::account_id(); - let max_payout = pallet_treasury::Pallet::::pot(); + let max_payout = pallet_treasury::Pallet::::pot(); let mut payout = tips[tips.len() / 2].1.min(max_payout); if !tip.deposit.is_zero() { @@ -582,7 +583,7 @@ impl Pallet { for (hash, old_tip) in storage_key_iter::< T::Hash, - OldOpenTip, T::BlockNumber, T::Hash>, + OldOpenTip, T::BlockNumber, T::Hash>, Twox64Concat, >(module, item) .drain() @@ -600,7 +601,7 @@ impl Pallet { tips: old_tip.tips, finders_fee, }; - Tips::::insert(hash, new_tip) + Tips::::insert(hash, new_tip) } } } diff --git a/frame/tips/src/tests.rs b/frame/tips/src/tests.rs index 235952fd1092c..194ecc60d9890 100644 --- a/frame/tips/src/tests.rs +++ b/frame/tips/src/tests.rs @@ -25,7 +25,7 @@ use sp_core::H256; use sp_runtime::{ testing::Header, traits::{BadOrigin, BlakeTwo256, IdentityLookup}, - Perbill, Permill, + BuildStorage, Perbill, Permill, }; use sp_storage::Storage; @@ -53,7 +53,9 @@ frame_support::construct_runtime!( System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Treasury: pallet_treasury::{Pallet, Call, Storage, Config, Event}, + Treasury1: pallet_treasury::::{Pallet, Call, Storage, Config, Event}, Tips: pallet_tips::{Pallet, Call, Storage, Event}, + Tips1: pallet_tips::::{Pallet, Call, Storage, Event}, } ); @@ -127,6 +129,7 @@ parameter_types! { pub const ProposalBond: Permill = Permill::from_percent(5); pub const Burn: Permill = Permill::from_percent(50); pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry"); + pub const TreasuryPalletId2: PalletId = PalletId(*b"py/trsr2"); } impl pallet_treasury::Config for Test { type PalletId = TreasuryPalletId; @@ -146,6 +149,26 @@ impl pallet_treasury::Config for Test { type MaxApprovals = ConstU32<100>; type SpendOrigin = frame_support::traits::NeverEnsureOrigin; } + +impl pallet_treasury::Config for Test { + type PalletId = TreasuryPalletId2; + type Currency = pallet_balances::Pallet; + type ApproveOrigin = frame_system::EnsureRoot; + type RejectOrigin = frame_system::EnsureRoot; + type Event = Event; + type OnSlash = (); + type ProposalBond = ProposalBond; + type ProposalBondMinimum = ConstU64<1>; + type ProposalBondMaximum = (); + type SpendPeriod = ConstU64<2>; + type Burn = Burn; + type BurnDestination = (); // Just gets burned. + type WeightInfo = (); + type SpendFunds = (); + type MaxApprovals = ConstU32<100>; + type SpendOrigin = frame_support::traits::NeverEnsureOrigin; +} + parameter_types! { pub const TipFindersFee: Percent = Percent::from_percent(20); } @@ -160,16 +183,29 @@ impl Config for Test { type WeightInfo = (); } +impl Config for Test { + type MaximumReasonLength = ConstU32<16384>; + type Tippers = TenToFourteen; + type TipCountdown = ConstU64<1>; + type TipFindersFee = TipFindersFee; + type TipReportDepositBase = ConstU64<1>; + type DataDepositPerByte = ConstU64<1>; + type Event = Event; + type WeightInfo = (); +} + pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - pallet_balances::GenesisConfig:: { - // Total issuance will be 200 with treasury account initialized at ED. - balances: vec![(0, 100), (1, 98), (2, 1)], + let mut ext: sp_io::TestExternalities = GenesisConfig { + system: frame_system::GenesisConfig::default(), + balances: pallet_balances::GenesisConfig { balances: vec![(0, 100), (1, 98), (2, 1)] }, + treasury: Default::default(), + treasury_1: Default::default(), } - .assimilate_storage(&mut t) - .unwrap(); - GenesisBuild::::assimilate_storage(&pallet_treasury::GenesisConfig, &mut t).unwrap(); - t.into() + .build_storage() + .unwrap() + .into(); + ext.execute_with(|| System::set_block_number(1)); + ext } fn last_event() -> TipEvent { @@ -540,3 +576,36 @@ fn genesis_funding_works() { assert_eq!(Treasury::pot(), initial_funding - Balances::minimum_balance()); }); } + +#[test] +fn report_awesome_and_tip_works_second_instance() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&Treasury::account_id(), 101); + Balances::make_free_balance_be(&Treasury1::account_id(), 201); + assert_eq!(Balances::free_balance(&Treasury::account_id()), 101); + assert_eq!(Balances::free_balance(&Treasury1::account_id()), 201); + + assert_ok!(Tips1::report_awesome(Origin::signed(0), b"awesome.dot".to_vec(), 3)); + // duplicate report in tips1 reports don't count. + assert_noop!( + Tips1::report_awesome(Origin::signed(1), b"awesome.dot".to_vec(), 3), + Error::::AlreadyKnown + ); + // but tips is separate + assert_ok!(Tips::report_awesome(Origin::signed(0), b"awesome.dot".to_vec(), 3)); + + let h = tip_hash(); + assert_ok!(Tips1::tip(Origin::signed(10), h.clone(), 10)); + assert_ok!(Tips1::tip(Origin::signed(11), h.clone(), 10)); + assert_ok!(Tips1::tip(Origin::signed(12), h.clone(), 10)); + assert_noop!(Tips1::tip(Origin::signed(9), h.clone(), 10), BadOrigin); + + System::set_block_number(2); + + assert_ok!(Tips1::close_tip(Origin::signed(100), h.into())); + // Treasury 1 unchanged + assert_eq!(Balances::free_balance(&Treasury::account_id()), 101); + // Treasury 2 gave the funds + assert_eq!(Balances::free_balance(&Treasury1::account_id()), 191); + }); +} From 5fd36b70d2244f891b8f73841c22e3851518dc38 Mon Sep 17 00:00:00 2001 From: Alexander Gryaznov Date: Thu, 23 Jun 2022 15:10:35 +0300 Subject: [PATCH 368/484] [contracts] Implement transparent hashing for contract storage (#11501) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * save * builds and old tests pass save: temporary value dropped while borrowed save: finally builds test updated but still fails * type names enhanced * VarSizedKey bounded to new Config param * improved wasm runtime updated funcs * unstable-interface tests fixed * benchmarks fixed * Apply suggestions from code review Co-authored-by: Alexander Theißen * fixes on feedback * fixes on feedback applied + make it build * benchmarks build but fail (old) * "Original code too large" * seal_clear_storage bench fixed (code size workaround hack removal tbd) * bench_seal_clear_storage pass * bench_seal_take_storage ... ok * added new seal_set_storage + updated benchmarks * added new seal_get_storage + updated benchmarks * added new seal_contains_storage + updated benchmarks * added tests for _transparent exec functions * wasm test for clear_storage * wasm test for take_storage * wasm test for new set_storage * wasm test for new get_storage * wasm test for new contains_storage * CI fix * ci fix * ci fix * ci fix * cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_contracts --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/contracts/src/weights.rs --template=./.maintain/frame-weight-template.hbs * fixes according to the review feedback * tests & benchmarks fixed * cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_contracts --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/contracts/src/weights.rs --template=./.maintain/frame-weight-template.hbs * refactoring * fix to runtime api * ci fix * ctx.get_storage() factored out * ctx.contains_storage() factored out * number of batches reduced for transparent hashing storage benchmarks * contracts RPC & pallet::get_storage to use transparent hashing * node and rpc updated to use get_storage with VarSizedKey * refactored (more concize) * refactored contains_storage (DRYed) * refactored contains_storage (DRYed) * fix rpc * fmt fix * more fixes in rpc * rollback `Pallet:get_storage` to Vec and rpc and node parts related to it * added `KeyDecodingFailed` error * Revert weird "fmt fix" This reverts commit c582cfff4b5cb2c9929fd5e3b45519bb24aeb657. * node-executor basic test update * fix node-executor basic test * benchmarks fix * more benchmarks fix * FixedSizedKey is hidden from pub, VarSizedKey is exported as StorageKey * ci fix * set_storage benchmark fix * ci fix * ci fix * comments improved * new error code to rpc: KEY_DECODING_FAILED * Put `rusty-cachier` before PR merge into `master` for `cargo-check-benches` job * cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_contracts --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/contracts/src/weights.rs --template=./.maintain/frame-weight-template.hbs * minor optimization Co-authored-by: Alexander Theißen Co-authored-by: Parity Bot Co-authored-by: Vladimir Istyufeev Co-authored-by: command-bot <> --- bin/node/executor/tests/basic.rs | 6 +- bin/node/runtime/src/lib.rs | 3 +- frame/contracts/common/src/lib.rs | 2 + frame/contracts/rpc/runtime-api/src/lib.rs | 2 +- frame/contracts/rpc/src/lib.rs | 15 +- frame/contracts/src/benchmarking/mod.rs | 245 +-- frame/contracts/src/exec.rs | 408 ++++- frame/contracts/src/lib.rs | 21 +- frame/contracts/src/storage.rs | 16 +- frame/contracts/src/tests.rs | 23 +- frame/contracts/src/wasm/mod.rs | 433 +++++- frame/contracts/src/wasm/runtime.rs | 220 ++- frame/contracts/src/weights.rs | 1644 +++++++++++--------- 13 files changed, 2040 insertions(+), 998 deletions(-) diff --git a/bin/node/executor/tests/basic.rs b/bin/node/executor/tests/basic.rs index de2aa6c18369d..31a9bd0a90496 100644 --- a/bin/node/executor/tests/basic.rs +++ b/bin/node/executor/tests/basic.rs @@ -764,7 +764,11 @@ fn deploying_wasm_contract_should_work() { t.execute_with(|| { // Verify that the contract does exist by querying some of its storage items // It does not matter that the storage item itself does not exist. - assert!(&pallet_contracts::Pallet::::get_storage(addr, Default::default()).is_ok()); + assert!(&pallet_contracts::Pallet::::get_storage( + addr, + pallet_contracts::StorageKey::::default().to_vec() + ) + .is_ok()); }); } diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 54742cd91c430..2d5981339fab3 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1151,6 +1151,7 @@ impl pallet_contracts::Config for Runtime { type ContractAccessWeight = pallet_contracts::DefaultContractAccessWeight; type MaxCodeLen = ConstU32<{ 128 * 1024 }>; type RelaxedMaxCodeLen = ConstU32<{ 256 * 1024 }>; + type MaxStorageKeyLen = ConstU32<128>; } impl pallet_sudo::Config for Runtime { @@ -1936,7 +1937,7 @@ impl_runtime_apis! { fn get_storage( address: AccountId, - key: [u8; 32], + key: Vec, ) -> pallet_contracts_primitives::GetStorageResult { Contracts::get_storage(address, key) } diff --git a/frame/contracts/common/src/lib.rs b/frame/contracts/common/src/lib.rs index 49f91f6769290..f810725afcd36 100644 --- a/frame/contracts/common/src/lib.rs +++ b/frame/contracts/common/src/lib.rs @@ -106,6 +106,8 @@ pub type GetStorageResult = Result>, ContractAccessError>; pub enum ContractAccessError { /// The given address doesn't point to a contract. DoesntExist, + /// Storage key cannot be decoded from the provided input data. + KeyDecodingFailed, } bitflags! { diff --git a/frame/contracts/rpc/runtime-api/src/lib.rs b/frame/contracts/rpc/runtime-api/src/lib.rs index 59622a21a6593..9765b37057c7b 100644 --- a/frame/contracts/rpc/runtime-api/src/lib.rs +++ b/frame/contracts/rpc/runtime-api/src/lib.rs @@ -79,7 +79,7 @@ sp_api::decl_runtime_apis! { /// doesn't exist, or doesn't have a contract then `Err` is returned. fn get_storage( address: AccountId, - key: [u8; 32], + key: Vec, ) -> GetStorageResult; } } diff --git a/frame/contracts/rpc/src/lib.rs b/frame/contracts/rpc/src/lib.rs index 77ae3f3ed35e3..0df8f90237ed3 100644 --- a/frame/contracts/rpc/src/lib.rs +++ b/frame/contracts/rpc/src/lib.rs @@ -33,7 +33,7 @@ use pallet_contracts_primitives::{ use serde::{Deserialize, Serialize}; use sp_api::ProvideRuntimeApi; use sp_blockchain::HeaderBackend; -use sp_core::{Bytes, H256}; +use sp_core::Bytes; use sp_rpc::number::NumberOrHex; use sp_runtime::{ generic::BlockId, @@ -44,6 +44,7 @@ pub use pallet_contracts_rpc_runtime_api::ContractsApi as ContractsRuntimeApi; const RUNTIME_ERROR: i32 = 1; const CONTRACT_DOESNT_EXIST: i32 = 2; +const KEY_DECODING_FAILED: i32 = 3; pub type Weight = u64; @@ -74,6 +75,12 @@ impl From for JsonRpseeError { None::<()>, )) .into(), + KeyDecodingFailed => CallError::Custom(ErrorObject::owned( + KEY_DECODING_FAILED, + "Failed to decode the specified storage key.", + None::<()>, + )) + .into(), } } } @@ -167,7 +174,7 @@ where fn get_storage( &self, address: AccountId, - key: H256, + key: Bytes, at: Option, ) -> RpcResult>; } @@ -292,13 +299,13 @@ where fn get_storage( &self, address: AccountId, - key: H256, + key: Bytes, at: Option<::Hash>, ) -> RpcResult> { let api = self.client.runtime_api(); let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash)); let result = api - .get_storage(&at, address, key.into()) + .get_storage(&at, address, key.to_vec()) .map_err(runtime_error_into_rpc_err)? .map_err(ContractAccessError)? .map(Bytes); diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs index 8f17cacff328d..bea469bd0f5a9 100644 --- a/frame/contracts/src/benchmarking/mod.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -30,7 +30,7 @@ use self::{ sandbox::Sandbox, }; use crate::{ - exec::{AccountIdOf, StorageKey}, + exec::{AccountIdOf, FixSizedKey, VarSizedKey}, schedule::{API_BENCHMARK_BATCH_SIZE, INSTR_BENCHMARK_BATCH_SIZE}, storage::Storage, wasm::CallFlags, @@ -132,11 +132,17 @@ where } /// Store the supplied storage items into this contracts storage. - fn store(&self, items: &Vec<(StorageKey, Vec)>) -> Result<(), &'static str> { + fn store(&self, items: &Vec<(FixSizedKey, Vec)>) -> Result<(), &'static str> { let info = self.info()?; for item in items { - Storage::::write(&info.trie_id, &item.0, Some(item.1.clone()), None, false) - .map_err(|_| "Failed to write storage to restoration dest")?; + Storage::::write( + &info.trie_id, + &item.0 as &FixSizedKey, + Some(item.1.clone()), + None, + false, + ) + .map_err(|_| "Failed to write storage to restoration dest")?; } >::insert(&self.account_id, info); Ok(()) @@ -896,33 +902,37 @@ benchmarks! { }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) // Only the overhead of calling the function itself with minimal arguments. - // The contract is a bit more complex because I needs to use different keys in order + // The contract is a bit more complex because it needs to use different keys in order // to generate unique storage accesses. However, it is still dominated by the storage - // accesses. + // accesses. We store all the keys that we are about to write at beforehand + // because re-writing at an existing key is always more expensive than writing + // it at a virgin key. #[skip_meta] seal_set_storage { - let r in 0 .. API_BENCHMARK_BATCHES; + let r in 0 .. API_BENCHMARK_BATCHES/2; + let max_key_len = T::MaxStorageKeyLen::get(); let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE) - .map(|n| T::Hashing::hash_of(&n).as_ref().to_vec()) - .collect::>(); - let key_len = keys.get(0).map(|i| i.len() as u32).unwrap_or(0); - let key_bytes = keys.iter().flatten().cloned().collect::>(); + .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); + h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) + .collect::>(); + let keys_bytes = keys.iter().flatten().cloned().collect::>(); let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "seal1", + module: "__unstable__", name: "seal_set_storage", - params: vec![ValueType::I32, ValueType::I32, ValueType::I32], + params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32], return_type: Some(ValueType::I32), }], data_segments: vec![ DataSegment { offset: 0, - value: key_bytes, + value: keys_bytes, }, ], call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ - Counter(0, key_len as u32), // key_ptr + Counter(0, max_key_len as u32), // key_ptr + Regular(Instruction::I32Const(max_key_len as i32)), // key_len Regular(Instruction::I32Const(0)), // value_ptr Regular(Instruction::I32Const(0)), // value_len Regular(Instruction::Call(0)), @@ -935,7 +945,7 @@ benchmarks! { for key in keys { Storage::::write( &info.trie_id, - key.as_slice().try_into().map_err(|e| "Key has wrong length")?, + &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, Some(vec![]), None, false, @@ -947,18 +957,19 @@ benchmarks! { #[skip_meta] seal_set_storage_per_new_kb { - let n in 0 .. T::Schedule::get().limits.payload_len / 1024; - let keys = (0 .. API_BENCHMARK_BATCH_SIZE) - .map(|n| T::Hashing::hash_of(&n).as_ref().to_vec()) - .collect::>(); - let key_len = keys.get(0).map(|i| i.len() as u32).unwrap_or(0); + let n in 0 .. T::Schedule::get().limits.payload_len / 2048; // half of the max payload_len in kb + let max_key_len = T::MaxStorageKeyLen::get(); + let keys = (0 .. n * API_BENCHMARK_BATCH_SIZE) + .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); + h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) + .collect::>(); let key_bytes = keys.iter().flatten().cloned().collect::>(); let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "seal1", + module: "__unstable__", name: "seal_set_storage", - params: vec![ValueType::I32, ValueType::I32, ValueType::I32], + params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32], return_type: Some(ValueType::I32), }], data_segments: vec![ @@ -968,9 +979,10 @@ benchmarks! { }, ], call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![ - Counter(0, key_len as u32), // key_ptr + Counter(0, max_key_len as u32), // key_ptr + Regular(Instruction::I32Const(max_key_len as i32)), // key_len Regular(Instruction::I32Const(0)), // value_ptr - Regular(Instruction::I32Const((n * 1024) as i32)), // value_len + Regular(Instruction::I32Const((n * 2048) as i32)), // value_len increments by 2kb up to max payload_len Regular(Instruction::Call(0)), Regular(Instruction::Drop), ])), @@ -981,7 +993,7 @@ benchmarks! { for key in keys { Storage::::write( &info.trie_id, - key.as_slice().try_into().map_err(|e| "Key has wrong length")?, + &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, Some(vec![]), None, false, @@ -993,18 +1005,19 @@ benchmarks! { #[skip_meta] seal_set_storage_per_old_kb { - let n in 0 .. T::Schedule::get().limits.payload_len / 1024; - let keys = (0 .. API_BENCHMARK_BATCH_SIZE) - .map(|n| T::Hashing::hash_of(&n).as_ref().to_vec()) - .collect::>(); - let key_len = keys.get(0).map(|i| i.len() as u32).unwrap_or(0); + let n in 0 .. T::Schedule::get().limits.payload_len / 2048; // half of the max payload_len in kb + let max_key_len = T::MaxStorageKeyLen::get(); + let keys = (0 .. n * API_BENCHMARK_BATCH_SIZE) + .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); + h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) + .collect::>(); let key_bytes = keys.iter().flatten().cloned().collect::>(); let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "seal1", + module: "__unstable__", name: "seal_set_storage", - params: vec![ValueType::I32, ValueType::I32, ValueType::I32], + params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32], return_type: Some(ValueType::I32), }], data_segments: vec![ @@ -1014,9 +1027,10 @@ benchmarks! { }, ], call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![ - Counter(0, key_len as u32), // key_ptr + Counter(0, max_key_len as u32), // key_ptr + Regular(Instruction::I32Const(max_key_len as i32)), // key_len Regular(Instruction::I32Const(0)), // value_ptr - Regular(Instruction::I32Const(0)), // value_len + Regular(Instruction::I32Const(0)), // value_len is 0 as testing vs pre-existing value len Regular(Instruction::Call(0)), Regular(Instruction::Drop), ])), @@ -1027,8 +1041,8 @@ benchmarks! { for key in keys { Storage::::write( &info.trie_id, - key.as_slice().try_into().map_err(|e| "Key has wrong length")?, - Some(vec![42u8; (n * 1024) as usize]), + &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, + Some(vec![42u8; (n * 2048) as usize]), // value_len increments by 2kb up to max payload_len None, false, ) @@ -1037,23 +1051,25 @@ benchmarks! { let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) - // Similar to seal_set_storage. However, we store all the keys that we are about to + // Similar to seal_set_storage. We store all the keys that we are about to // delete beforehand in order to prevent any optimizations that could occur when - // deleting a non existing key. + // deleting a non existing key. We generate keys of a maximum length, and have to + // reduce batch size in order to make resulting contract code size less than MaxCodeLen. #[skip_meta] seal_clear_storage { - let r in 0 .. API_BENCHMARK_BATCHES; + let r in 0 .. API_BENCHMARK_BATCHES/2; + let max_key_len = T::MaxStorageKeyLen::get(); let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE) - .map(|n| T::Hashing::hash_of(&n).as_ref().to_vec()) - .collect::>(); + .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); + h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) + .collect::>(); let key_bytes = keys.iter().flatten().cloned().collect::>(); - let key_len = keys.get(0).map(|i| i.len() as u32).unwrap_or(0); let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { module: "__unstable__", name: "seal_clear_storage", - params: vec![ValueType::I32], + params: vec![ValueType::I32, ValueType::I32], return_type: Some(ValueType::I32), }], data_segments: vec![ @@ -1063,7 +1079,8 @@ benchmarks! { }, ], call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ - Counter(0, key_len as u32), + Counter(0, max_key_len as u32), // key_ptr + Regular(Instruction::I32Const(max_key_len as i32)), // key_len Regular(Instruction::Call(0)), Regular(Instruction::Drop), ])), @@ -1074,7 +1091,7 @@ benchmarks! { for key in keys { Storage::::write( &info.trie_id, - key.as_slice().try_into().map_err(|e| "Key has wrong length")?, + &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, Some(vec![]), None, false, @@ -1087,18 +1104,19 @@ benchmarks! { #[skip_meta] seal_clear_storage_per_kb { - let n in 0 .. T::Schedule::get().limits.payload_len / 1024; - let keys = (0 .. API_BENCHMARK_BATCH_SIZE) - .map(|n| T::Hashing::hash_of(&n).as_ref().to_vec()) - .collect::>(); - let key_len = keys.get(0).map(|i| i.len() as u32).unwrap_or(0); + let n in 0 .. T::Schedule::get().limits.payload_len / 2048; // half of the max payload_len in kb + let max_key_len = T::MaxStorageKeyLen::get(); + let keys = (0 .. n * API_BENCHMARK_BATCH_SIZE) + .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); + h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) + .collect::>(); let key_bytes = keys.iter().flatten().cloned().collect::>(); let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { module: "__unstable__", name: "seal_clear_storage", - params: vec![ValueType::I32], + params: vec![ValueType::I32, ValueType::I32], return_type: Some(ValueType::I32), }], data_segments: vec![ @@ -1108,7 +1126,8 @@ benchmarks! { }, ], call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![ - Counter(0, key_len as u32), // key_ptr + Counter(0, max_key_len as u32), // key_ptr + Regular(Instruction::I32Const(max_key_len as i32)), // key_len Regular(Instruction::Call(0)), Regular(Instruction::Drop), ])), @@ -1119,8 +1138,8 @@ benchmarks! { for key in keys { Storage::::write( &info.trie_id, - key.as_slice().try_into().map_err(|e| "Key has wrong length")?, - Some(vec![42u8; (n * 1024) as usize]), + &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, + Some(vec![42u8; (n * 2048) as usize]), // value_len increments by 2kb up to max payload_len None, false, ) @@ -1132,19 +1151,20 @@ benchmarks! { // We make sure that all storage accesses are to unique keys. #[skip_meta] seal_get_storage { - let r in 0 .. API_BENCHMARK_BATCHES; + let r in 0 .. API_BENCHMARK_BATCHES/2; + let max_key_len = T::MaxStorageKeyLen::get(); let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE) - .map(|n| T::Hashing::hash_of(&n).as_ref().to_vec()) - .collect::>(); - let key_len = keys.get(0).map(|i| i.len() as u32).unwrap_or(0); + .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); + h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) + .collect::>(); let key_bytes = keys.iter().flatten().cloned().collect::>(); let key_bytes_len = key_bytes.len(); let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "seal0", + module: "__unstable__", name: "seal_get_storage", - params: vec![ValueType::I32, ValueType::I32, ValueType::I32], + params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32], return_type: Some(ValueType::I32), }], data_segments: vec![ @@ -1158,7 +1178,8 @@ benchmarks! { }, ], call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ - Counter(0, key_len as u32), // key_ptr + Counter(0, max_key_len as u32), // key_ptr + Regular(Instruction::I32Const(max_key_len as i32)), // key_len Regular(Instruction::I32Const((key_bytes_len + 4) as i32)), // out_ptr Regular(Instruction::I32Const(key_bytes_len as i32)), // out_len_ptr Regular(Instruction::Call(0)), @@ -1171,7 +1192,7 @@ benchmarks! { for key in keys { Storage::::write( &info.trie_id, - key.as_slice().try_into().map_err(|e| "Key has wrong length")?, + &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, Some(vec![]), None, false, @@ -1184,19 +1205,20 @@ benchmarks! { #[skip_meta] seal_get_storage_per_kb { - let n in 0 .. T::Schedule::get().limits.payload_len / 1024; - let keys = (0 .. API_BENCHMARK_BATCH_SIZE) - .map(|n| T::Hashing::hash_of(&n).as_ref().to_vec()) - .collect::>(); - let key_len = keys.get(0).map(|i| i.len() as u32).unwrap_or(0); + let n in 0 .. T::Schedule::get().limits.payload_len / 2048; // half of the max payload_len in kb + let max_key_len = T::MaxStorageKeyLen::get(); + let keys = (0 .. n * API_BENCHMARK_BATCH_SIZE) + .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); + h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) + .collect::>(); let key_bytes = keys.iter().flatten().cloned().collect::>(); let key_bytes_len = key_bytes.len(); let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "seal0", + module: "__unstable__", name: "seal_get_storage", - params: vec![ValueType::I32, ValueType::I32, ValueType::I32], + params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32], return_type: Some(ValueType::I32), }], data_segments: vec![ @@ -1210,7 +1232,8 @@ benchmarks! { }, ], call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![ - Counter(0, key_len as u32), // key_ptr + Counter(0, max_key_len as u32), // key_ptr + Regular(Instruction::I32Const(max_key_len as i32)), // key_len Regular(Instruction::I32Const((key_bytes_len + 4) as i32)), // out_ptr Regular(Instruction::I32Const(key_bytes_len as i32)), // out_len_ptr Regular(Instruction::Call(0)), @@ -1223,8 +1246,8 @@ benchmarks! { for key in keys { Storage::::write( &info.trie_id, - key.as_slice().try_into().map_err(|e| "Key has wrong length")?, - Some(vec![42u8; (n * 1024) as usize]), + &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, + Some(vec![42u8; (n * 2048) as usize]), // value_len increments by 2kb up to max payload_len None, false, ) @@ -1237,19 +1260,20 @@ benchmarks! { // We make sure that all storage accesses are to unique keys. #[skip_meta] seal_contains_storage { - let r in 0 .. API_BENCHMARK_BATCHES; + let r in 0 .. API_BENCHMARK_BATCHES/2; + let max_key_len = T::MaxStorageKeyLen::get(); let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE) - .map(|n| T::Hashing::hash_of(&n).as_ref().to_vec()) - .collect::>(); - let key_len = keys.get(0).map(|i| i.len() as u32).unwrap_or(0); + .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); + h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) + .collect::>(); let key_bytes = keys.iter().flatten().cloned().collect::>(); let key_bytes_len = key_bytes.len(); let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "seal0", + module: "__unstable__", name: "seal_contains_storage", - params: vec![ValueType::I32], + params: vec![ValueType::I32, ValueType::I32], return_type: Some(ValueType::I32), }], data_segments: vec![ @@ -1259,7 +1283,8 @@ benchmarks! { }, ], call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ - Counter(0, key_len as u32), // key_ptr + Counter(0, max_key_len as u32), // key_ptr + Regular(Instruction::I32Const(max_key_len as i32)), // key_len Regular(Instruction::Call(0)), Regular(Instruction::Drop), ])), @@ -1270,7 +1295,7 @@ benchmarks! { for key in keys { Storage::::write( &info.trie_id, - key.as_slice().try_into().map_err(|e| "Key has wrong length")?, + &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, Some(vec![]), None, false, @@ -1283,18 +1308,19 @@ benchmarks! { #[skip_meta] seal_contains_storage_per_kb { - let n in 0 .. T::Schedule::get().limits.payload_len / 1024; - let keys = (0 .. API_BENCHMARK_BATCH_SIZE) - .map(|n| T::Hashing::hash_of(&n).as_ref().to_vec()) - .collect::>(); - let key_len = keys.get(0).map(|i| i.len() as u32).unwrap_or(0); + let n in 0 .. T::Schedule::get().limits.payload_len / 2048; // half of the max payload_len in kb + let max_key_len = T::MaxStorageKeyLen::get(); + let keys = (0 .. n * API_BENCHMARK_BATCH_SIZE) + .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); + h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) + .collect::>(); let key_bytes = keys.iter().flatten().cloned().collect::>(); let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "seal0", + module: "__unstable__", name: "seal_contains_storage", - params: vec![ValueType::I32], + params: vec![ValueType::I32, ValueType::I32], return_type: Some(ValueType::I32), }], data_segments: vec![ @@ -1304,7 +1330,8 @@ benchmarks! { }, ], call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![ - Counter(0, key_len as u32), // key_ptr + Counter(0, max_key_len as u32), // key_ptr + Regular(Instruction::I32Const(max_key_len as i32)), // key_len Regular(Instruction::Call(0)), Regular(Instruction::Drop), ])), @@ -1315,8 +1342,8 @@ benchmarks! { for key in keys { Storage::::write( &info.trie_id, - key.as_slice().try_into().map_err(|e| "Key has wrong length")?, - Some(vec![42u8; (n * 1024) as usize]), + &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, + Some(vec![42u8; (n * 2048) as usize]), // value_len increments by 2kb up to max payload_len None, false, ) @@ -1328,11 +1355,12 @@ benchmarks! { #[skip_meta] seal_take_storage { - let r in 0 .. API_BENCHMARK_BATCHES; + let r in 0 .. API_BENCHMARK_BATCHES/2; + let max_key_len = T::MaxStorageKeyLen::get(); let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE) - .map(|n| T::Hashing::hash_of(&n).as_ref().to_vec()) - .collect::>(); - let key_len = keys.get(0).map(|i| i.len() as u32).unwrap_or(0); + .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); + h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) + .collect::>(); let key_bytes = keys.iter().flatten().cloned().collect::>(); let key_bytes_len = key_bytes.len(); let code = WasmModule::::from(ModuleDefinition { @@ -1340,7 +1368,7 @@ benchmarks! { imported_functions: vec![ImportedFunction { module: "__unstable__", name: "seal_take_storage", - params: vec![ValueType::I32, ValueType::I32, ValueType::I32], + params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32], return_type: Some(ValueType::I32), }], data_segments: vec![ @@ -1354,7 +1382,8 @@ benchmarks! { }, ], call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ - Counter(0, key_len as u32), // key_ptr + Counter(0, max_key_len as u32), // key_ptr + Regular(Instruction::I32Const(max_key_len as i32)), // key_len Regular(Instruction::I32Const((key_bytes_len + 4) as i32)), // out_ptr Regular(Instruction::I32Const(key_bytes_len as i32)), // out_len_ptr Regular(Instruction::Call(0)), @@ -1367,7 +1396,7 @@ benchmarks! { for key in keys { Storage::::write( &info.trie_id, - key.as_slice().try_into().map_err(|e| "Key has wrong length")?, + &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, Some(vec![]), None, false, @@ -1380,11 +1409,12 @@ benchmarks! { #[skip_meta] seal_take_storage_per_kb { - let n in 0 .. T::Schedule::get().limits.payload_len / 1024; - let keys = (0 .. API_BENCHMARK_BATCH_SIZE) - .map(|n| T::Hashing::hash_of(&n).as_ref().to_vec()) - .collect::>(); - let key_len = keys.get(0).map(|i| i.len() as u32).unwrap_or(0); + let n in 0 .. T::Schedule::get().limits.payload_len / 2048; // half of the max payload_len in kb + let max_key_len = T::MaxStorageKeyLen::get(); + let keys = (0 .. n * API_BENCHMARK_BATCH_SIZE) + .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); + h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) + .collect::>(); let key_bytes = keys.iter().flatten().cloned().collect::>(); let key_bytes_len = key_bytes.len(); let code = WasmModule::::from(ModuleDefinition { @@ -1392,7 +1422,7 @@ benchmarks! { imported_functions: vec![ImportedFunction { module: "__unstable__", name: "seal_take_storage", - params: vec![ValueType::I32, ValueType::I32, ValueType::I32], + params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32], return_type: Some(ValueType::I32), }], data_segments: vec![ @@ -1406,7 +1436,8 @@ benchmarks! { }, ], call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![ - Counter(0, key_len as u32), // key_ptr + Counter(0, max_key_len as u32), // key_ptr + Regular(Instruction::I32Const(max_key_len as i32)), // key_len Regular(Instruction::I32Const((key_bytes_len + 4) as i32)), // out_ptr Regular(Instruction::I32Const(key_bytes_len as i32)), // out_len_ptr Regular(Instruction::Call(0)), @@ -1419,8 +1450,8 @@ benchmarks! { for key in keys { Storage::::write( &info.trie_id, - key.as_slice().try_into().map_err(|e| "Key has wrong length")?, - Some(vec![42u8; (n * 1024) as usize]), + &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, + Some(vec![42u8; (n * 2048) as usize]), // value_len increments by 2kb up to max payload_len None, false, ) diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index e3ff4788791a5..ec7e1e9335182 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -27,12 +27,13 @@ use frame_support::{ storage::{with_transaction, TransactionOutcome}, traits::{Contains, Currency, ExistenceRequirement, OriginTrait, Randomness, Time}, weights::Weight, + Blake2_128Concat, BoundedVec, StorageHasher, }; use frame_system::RawOrigin; use pallet_contracts_primitives::ExecReturnValue; use smallvec::{Array, SmallVec}; use sp_core::{crypto::UncheckedFrom, ecdsa::Public as ECDSAPublic}; -use sp_io::crypto::secp256k1_ecdsa_recover_compressed; +use sp_io::{crypto::secp256k1_ecdsa_recover_compressed, hashing::blake2_256}; use sp_runtime::traits::Convert; use sp_std::{marker::PhantomData, mem, prelude::*}; @@ -40,12 +41,40 @@ pub type AccountIdOf = ::AccountId; pub type MomentOf = <::Time as Time>::Moment; pub type SeedOf = ::Hash; pub type BlockNumberOf = ::BlockNumber; -pub type StorageKey = [u8; 32]; pub type ExecResult = Result; /// A type that represents a topic of an event. At the moment a hash is used. pub type TopicOf = ::Hash; +/// Type for fix sized storage key. +pub type FixSizedKey = [u8; 32]; + +/// Type for variable sized storage key. Used for transparent hashing. +pub type VarSizedKey = BoundedVec::MaxStorageKeyLen>; + +/// Trait for hashing storage keys. +pub trait StorageKey +where + T: Config, +{ + fn hash(&self) -> Vec; +} + +impl StorageKey for FixSizedKey { + fn hash(&self) -> Vec { + blake2_256(self.as_slice()).to_vec() + } +} + +impl StorageKey for VarSizedKey +where + T: Config, +{ + fn hash(&self) -> Vec { + Blake2_128Concat::hash(self.as_slice()) + } +} + /// Origin of the error. /// /// Call or instantiate both called into other contracts and pass through errors happening @@ -140,19 +169,44 @@ pub trait Ext: sealing::Sealed { /// /// Returns `None` if the `key` wasn't previously set by `set_storage` or /// was deleted. - fn get_storage(&mut self, key: &StorageKey) -> Option>; + fn get_storage(&mut self, key: &FixSizedKey) -> Option>; + + /// This is a variation of `get_storage()` to be used with transparent hashing. + /// These two will be merged into a single function after some refactoring is done. + /// Returns the storage entry of the executing account by the given `key`. + /// + /// Returns `None` if the `key` wasn't previously set by `set_storage` or + /// was deleted. + fn get_storage_transparent(&mut self, key: &VarSizedKey) -> Option>; /// Returns `Some(len)` (in bytes) if a storage item exists at `key`. /// /// Returns `None` if the `key` wasn't previously set by `set_storage` or /// was deleted. - fn get_storage_size(&mut self, key: &StorageKey) -> Option; + fn get_storage_size(&mut self, key: &FixSizedKey) -> Option; + + /// This is the variation of `get_storage_size()` to be used with transparent hashing. + /// These two will be merged into a single function after some refactoring is done. + /// Returns `Some(len)` (in bytes) if a storage item exists at `key`. + /// + /// Returns `None` if the `key` wasn't previously set by `set_storage` or + /// was deleted. + fn get_storage_size_transparent(&mut self, key: &VarSizedKey) -> Option; /// Sets the storage entry by the given key to the specified value. If `value` is `None` then /// the storage entry is deleted. fn set_storage( &mut self, - key: StorageKey, + key: &FixSizedKey, + value: Option>, + take_old: bool, + ) -> Result; + + /// This is the variation of `set_storage()` to be used with transparent hashing. + /// These two will be merged into a single function after some refactoring is done. + fn set_storage_transparent( + &mut self, + key: &VarSizedKey, value: Option>, take_old: bool, ) -> Result; @@ -1085,24 +1139,48 @@ where Self::transfer(ExistenceRequirement::KeepAlive, &self.top_frame().account_id, to, value) } - fn get_storage(&mut self, key: &StorageKey) -> Option> { + fn get_storage(&mut self, key: &FixSizedKey) -> Option> { Storage::::read(&self.top_frame_mut().contract_info().trie_id, key) } - fn get_storage_size(&mut self, key: &StorageKey) -> Option { + fn get_storage_transparent(&mut self, key: &VarSizedKey) -> Option> { + Storage::::read(&self.top_frame_mut().contract_info().trie_id, key) + } + + fn get_storage_size(&mut self, key: &FixSizedKey) -> Option { + Storage::::size(&self.top_frame_mut().contract_info().trie_id, key) + } + + fn get_storage_size_transparent(&mut self, key: &VarSizedKey) -> Option { Storage::::size(&self.top_frame_mut().contract_info().trie_id, key) } fn set_storage( &mut self, - key: StorageKey, + key: &FixSizedKey, value: Option>, take_old: bool, ) -> Result { let frame = self.top_frame_mut(); Storage::::write( &frame.contract_info.get(&frame.account_id).trie_id, - &key, + key, + value, + Some(&mut frame.nested_storage), + take_old, + ) + } + + fn set_storage_transparent( + &mut self, + key: &VarSizedKey, + value: Option>, + take_old: bool, + ) -> Result { + let frame = self.top_frame_mut(); + Storage::::write( + &frame.contract_info.get(&frame.account_id).trie_id, + key, value, Some(&mut frame.nested_storage), take_old, @@ -2635,35 +2713,35 @@ mod tests { let code_hash = MockLoader::insert(Call, |ctx, _| { // Write assert_eq!( - ctx.ext.set_storage([1; 32], Some(vec![1, 2, 3]), false), + ctx.ext.set_storage(&[1; 32], Some(vec![1, 2, 3]), false), Ok(WriteOutcome::New) ); assert_eq!( - ctx.ext.set_storage([2; 32], Some(vec![4, 5, 6]), true), + ctx.ext.set_storage(&[2; 32], Some(vec![4, 5, 6]), true), Ok(WriteOutcome::New) ); - assert_eq!(ctx.ext.set_storage([3; 32], None, false), Ok(WriteOutcome::New)); - assert_eq!(ctx.ext.set_storage([4; 32], None, true), Ok(WriteOutcome::New)); - assert_eq!(ctx.ext.set_storage([5; 32], Some(vec![]), false), Ok(WriteOutcome::New)); - assert_eq!(ctx.ext.set_storage([6; 32], Some(vec![]), true), Ok(WriteOutcome::New)); + assert_eq!(ctx.ext.set_storage(&[3; 32], None, false), Ok(WriteOutcome::New)); + assert_eq!(ctx.ext.set_storage(&[4; 32], None, true), Ok(WriteOutcome::New)); + assert_eq!(ctx.ext.set_storage(&[5; 32], Some(vec![]), false), Ok(WriteOutcome::New)); + assert_eq!(ctx.ext.set_storage(&[6; 32], Some(vec![]), true), Ok(WriteOutcome::New)); // Overwrite assert_eq!( - ctx.ext.set_storage([1; 32], Some(vec![42]), false), + ctx.ext.set_storage(&[1; 32], Some(vec![42]), false), Ok(WriteOutcome::Overwritten(3)) ); assert_eq!( - ctx.ext.set_storage([2; 32], Some(vec![48]), true), + ctx.ext.set_storage(&[2; 32], Some(vec![48]), true), Ok(WriteOutcome::Taken(vec![4, 5, 6])) ); - assert_eq!(ctx.ext.set_storage([3; 32], None, false), Ok(WriteOutcome::New)); - assert_eq!(ctx.ext.set_storage([4; 32], None, true), Ok(WriteOutcome::New)); + assert_eq!(ctx.ext.set_storage(&[3; 32], None, false), Ok(WriteOutcome::New)); + assert_eq!(ctx.ext.set_storage(&[4; 32], None, true), Ok(WriteOutcome::New)); assert_eq!( - ctx.ext.set_storage([5; 32], Some(vec![]), false), + ctx.ext.set_storage(&[5; 32], Some(vec![]), false), Ok(WriteOutcome::Overwritten(0)) ); assert_eq!( - ctx.ext.set_storage([6; 32], Some(vec![]), true), + ctx.ext.set_storage(&[6; 32], Some(vec![]), true), Ok(WriteOutcome::Taken(vec![])) ); @@ -2690,14 +2768,175 @@ mod tests { }); } + #[test] + fn set_storage_transparent_works() { + let code_hash = MockLoader::insert(Call, |ctx, _| { + // Write + assert_eq!( + ctx.ext.set_storage_transparent( + &VarSizedKey::::try_from([1; 64].to_vec()).unwrap(), + Some(vec![1, 2, 3]), + false + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage_transparent( + &VarSizedKey::::try_from([2; 19].to_vec()).unwrap(), + Some(vec![4, 5, 6]), + true + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage_transparent( + &VarSizedKey::::try_from([3; 19].to_vec()).unwrap(), + None, + false + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage_transparent( + &VarSizedKey::::try_from([4; 64].to_vec()).unwrap(), + None, + true + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage_transparent( + &VarSizedKey::::try_from([5; 30].to_vec()).unwrap(), + Some(vec![]), + false + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage_transparent( + &VarSizedKey::::try_from([6; 128].to_vec()).unwrap(), + Some(vec![]), + true + ), + Ok(WriteOutcome::New) + ); + + // Overwrite + assert_eq!( + ctx.ext.set_storage_transparent( + &VarSizedKey::::try_from([1; 64].to_vec()).unwrap(), + Some(vec![42, 43, 44]), + false + ), + Ok(WriteOutcome::Overwritten(3)) + ); + assert_eq!( + ctx.ext.set_storage_transparent( + &VarSizedKey::::try_from([2; 19].to_vec()).unwrap(), + Some(vec![48]), + true + ), + Ok(WriteOutcome::Taken(vec![4, 5, 6])) + ); + assert_eq!( + ctx.ext.set_storage_transparent( + &VarSizedKey::::try_from([3; 19].to_vec()).unwrap(), + None, + false + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage_transparent( + &VarSizedKey::::try_from([4; 64].to_vec()).unwrap(), + None, + true + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage_transparent( + &VarSizedKey::::try_from([5; 30].to_vec()).unwrap(), + Some(vec![]), + false + ), + Ok(WriteOutcome::Overwritten(0)) + ); + assert_eq!( + ctx.ext.set_storage_transparent( + &VarSizedKey::::try_from([6; 128].to_vec()).unwrap(), + Some(vec![]), + true + ), + Ok(WriteOutcome::Taken(vec![])) + ); + + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + let schedule = ::Schedule::get(); + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + set_balance(&ALICE, min_balance * 1000); + place_contract(&BOB, code_hash); + let mut storage_meter = storage::meter::Meter::new(&ALICE, None, 0).unwrap(); + assert_ok!(MockStack::run_call( + ALICE, + BOB, + &mut gas_meter, + &mut storage_meter, + &schedule, + 0, + vec![], + None, + )); + }); + } + + #[test] + fn get_storage_works() { + let code_hash = MockLoader::insert(Call, |ctx, _| { + assert_eq!( + ctx.ext.set_storage(&[1; 32], Some(vec![1, 2, 3]), false), + Ok(WriteOutcome::New) + ); + assert_eq!(ctx.ext.set_storage(&[2; 32], Some(vec![]), false), Ok(WriteOutcome::New)); + assert_eq!(ctx.ext.get_storage(&[1; 32]), Some(vec![1, 2, 3])); + assert_eq!(ctx.ext.get_storage(&[2; 32]), Some(vec![])); + assert_eq!(ctx.ext.get_storage(&[3; 32]), None); + + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + let schedule = ::Schedule::get(); + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + set_balance(&ALICE, min_balance * 1000); + place_contract(&BOB, code_hash); + let mut storage_meter = storage::meter::Meter::new(&ALICE, None, 0).unwrap(); + assert_ok!(MockStack::run_call( + ALICE, + BOB, + &mut gas_meter, + &mut storage_meter, + &schedule, + 0, + vec![], + None, + )); + }); + } + #[test] fn get_storage_size_works() { let code_hash = MockLoader::insert(Call, |ctx, _| { assert_eq!( - ctx.ext.set_storage([1; 32], Some(vec![1, 2, 3]), false), + ctx.ext.set_storage(&[1; 32], Some(vec![1, 2, 3]), false), Ok(WriteOutcome::New) ); - assert_eq!(ctx.ext.set_storage([2; 32], Some(vec![]), false), Ok(WriteOutcome::New)); + assert_eq!(ctx.ext.set_storage(&[2; 32], Some(vec![]), false), Ok(WriteOutcome::New)); assert_eq!(ctx.ext.get_storage_size(&[1; 32]), Some(3)); assert_eq!(ctx.ext.get_storage_size(&[2; 32]), Some(0)); assert_eq!(ctx.ext.get_storage_size(&[3; 32]), None); @@ -2724,6 +2963,129 @@ mod tests { )); }); } + + #[test] + fn get_storage_transparent_works() { + let code_hash = MockLoader::insert(Call, |ctx, _| { + assert_eq!( + ctx.ext.set_storage_transparent( + &VarSizedKey::::try_from([1; 19].to_vec()).unwrap(), + Some(vec![1, 2, 3]), + false + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage_transparent( + &VarSizedKey::::try_from([2; 16].to_vec()).unwrap(), + Some(vec![]), + false + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.get_storage_transparent( + &VarSizedKey::::try_from([1; 19].to_vec()).unwrap() + ), + Some(vec![1, 2, 3]) + ); + assert_eq!( + ctx.ext.get_storage_transparent( + &VarSizedKey::::try_from([2; 16].to_vec()).unwrap() + ), + Some(vec![]) + ); + assert_eq!( + ctx.ext.get_storage_transparent( + &VarSizedKey::::try_from([3; 8].to_vec()).unwrap() + ), + None + ); + + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + let schedule = ::Schedule::get(); + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + set_balance(&ALICE, min_balance * 1000); + place_contract(&BOB, code_hash); + let mut storage_meter = storage::meter::Meter::new(&ALICE, None, 0).unwrap(); + assert_ok!(MockStack::run_call( + ALICE, + BOB, + &mut gas_meter, + &mut storage_meter, + &schedule, + 0, + vec![], + None, + )); + }); + } + + #[test] + fn get_storage_size_transparent_works() { + let code_hash = MockLoader::insert(Call, |ctx, _| { + assert_eq!( + ctx.ext.set_storage_transparent( + &VarSizedKey::::try_from([1; 19].to_vec()).unwrap(), + Some(vec![1, 2, 3]), + false + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage_transparent( + &VarSizedKey::::try_from([2; 16].to_vec()).unwrap(), + Some(vec![]), + false + ), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.get_storage_size_transparent( + &VarSizedKey::::try_from([1; 19].to_vec()).unwrap() + ), + Some(3) + ); + assert_eq!( + ctx.ext.get_storage_size_transparent( + &VarSizedKey::::try_from([2; 16].to_vec()).unwrap() + ), + Some(0) + ); + assert_eq!( + ctx.ext.get_storage_size_transparent( + &VarSizedKey::::try_from([3; 8].to_vec()).unwrap() + ), + None + ); + + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + let schedule = ::Schedule::get(); + let mut gas_meter = GasMeter::::new(GAS_LIMIT); + set_balance(&ALICE, min_balance * 1000); + place_contract(&BOB, code_hash); + let mut storage_meter = storage::meter::Meter::new(&ALICE, None, 0).unwrap(); + assert_ok!(MockStack::run_call( + ALICE, + BOB, + &mut gas_meter, + &mut storage_meter, + &schedule, + 0, + vec![], + None, + )); + }); + } + #[test] fn ecdsa_to_eth_address_returns_proper_value() { let bob_ch = MockLoader::insert(Call, |ctx, _| { diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index 1011e264eb930..60b30ffa25005 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -98,11 +98,6 @@ pub mod weights; #[cfg(test)] mod tests; -pub use crate::{ - exec::Frame, - pallet::*, - schedule::{HostFnWeights, InstructionWeights, Limits, Schedule}, -}; use crate::{ exec::{AccountIdOf, ExecError, Executable, Stack as ExecStack}, gas::GasMeter, @@ -129,6 +124,12 @@ use sp_core::{crypto::UncheckedFrom, Bytes}; use sp_runtime::traits::{Convert, Hash, Saturating, StaticLookup}; use sp_std::{fmt::Debug, marker::PhantomData, prelude::*}; +pub use crate::{ + exec::{Frame, VarSizedKey as StorageKey}, + pallet::*, + schedule::{HostFnWeights, InstructionWeights, Limits, Schedule}, +}; + type CodeHash = ::Hash; type TrieId = BoundedVec>; type BalanceOf = @@ -372,6 +373,9 @@ pub mod pallet { /// new instrumentation increases the size beyond the limit it would make that contract /// inaccessible until rectified by another runtime upgrade. type RelaxedMaxCodeLen: Get; + + /// The maximum allowable length in bytes for storage keys. + type MaxStorageKeyLen: Get; } #[pallet::hooks] @@ -942,11 +946,14 @@ where } /// Query storage of a specified contract under a specified key. - pub fn get_storage(address: T::AccountId, key: [u8; 32]) -> GetStorageResult { + pub fn get_storage(address: T::AccountId, key: Vec) -> GetStorageResult { let contract_info = ContractInfoOf::::get(&address).ok_or(ContractAccessError::DoesntExist)?; - let maybe_value = Storage::::read(&contract_info.trie_id, &key); + let maybe_value = Storage::::read( + &contract_info.trie_id, + &StorageKey::::try_from(key).map_err(|_| ContractAccessError::KeyDecodingFailed)?, + ); Ok(maybe_value) } diff --git a/frame/contracts/src/storage.rs b/frame/contracts/src/storage.rs index e73eb8d3bd55e..01c809da8675e 100644 --- a/frame/contracts/src/storage.rs +++ b/frame/contracts/src/storage.rs @@ -32,7 +32,7 @@ use frame_support::{ }; use scale_info::TypeInfo; use sp_core::crypto::UncheckedFrom; -use sp_io::{hashing::blake2_256, KillStorageResult}; +use sp_io::KillStorageResult; use sp_runtime::{ traits::{Hash, Zero}, RuntimeDebug, @@ -124,16 +124,16 @@ where /// /// The read is performed from the `trie_id` only. The `address` is not necessary. If the /// contract doesn't store under the given `key` `None` is returned. - pub fn read(trie_id: &TrieId, key: &StorageKey) -> Option> { - child::get_raw(&child_trie_info(trie_id), &blake2_256(key)) + pub fn read>(trie_id: &TrieId, key: &K) -> Option> { + child::get_raw(&child_trie_info(trie_id), key.hash().as_slice()) } /// Returns `Some(len)` (in bytes) if a storage item exists at `key`. /// /// Returns `None` if the `key` wasn't previously set by `set_storage` or /// was deleted. - pub fn size(trie_id: &TrieId, key: &StorageKey) -> Option { - child::len(&child_trie_info(trie_id), &blake2_256(key)) + pub fn size>(trie_id: &TrieId, key: &K) -> Option { + child::len(&child_trie_info(trie_id), key.hash().as_slice()) } /// Update a storage entry into a contract's kv storage. @@ -143,15 +143,15 @@ where /// /// This function also records how much storage was created or removed if a `storage_meter` /// is supplied. It should only be absent for testing or benchmarking code. - pub fn write( + pub fn write>( trie_id: &TrieId, - key: &StorageKey, + key: &K, new_value: Option>, storage_meter: Option<&mut meter::NestedMeter>, take: bool, ) -> Result { - let hashed_key = blake2_256(key); let child_trie_info = &child_trie_info(trie_id); + let hashed_key = key.hash(); let (old_len, old_value) = if take { let val = child::get_raw(child_trie_info, &hashed_key); (val.as_ref().map(|v| v.len() as u32), val) diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index d4a5434cd5a46..bbac18142a658 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -20,7 +20,7 @@ use crate::{ ChainExtension, Environment, Ext, InitState, Result as ExtensionResult, RetVal, ReturnFlags, SysConfig, UncheckedFrom, }, - exec::Frame, + exec::{FixSizedKey, Frame}, storage::Storage, wasm::{PrefabWasmModule, ReturnCode as RuntimeReturnCode}, weights::WeightInfo, @@ -291,6 +291,7 @@ impl Config for Test { type ContractAccessWeight = DefaultContractAccessWeight; type MaxCodeLen = ConstU32<{ 128 * 1024 }>; type RelaxedMaxCodeLen = ConstU32<{ 256 * 1024 }>; + type MaxStorageKeyLen = ConstU32<128>; } pub const ALICE: AccountId32 = AccountId32::new([1u8; 32]); @@ -1723,8 +1724,14 @@ fn lazy_removal_partial_remove_works() { // Put value into the contracts child trie for val in &vals { - Storage::::write(&info.trie_id, &val.0, Some(val.2.clone()), None, false) - .unwrap(); + Storage::::write( + &info.trie_id, + &val.0 as &FixSizedKey, + Some(val.2.clone()), + None, + false, + ) + .unwrap(); } >::insert(&addr, info.clone()); @@ -1903,8 +1910,14 @@ fn lazy_removal_does_not_use_all_weight() { // Put value into the contracts child trie for val in &vals { - Storage::::write(&info.trie_id, &val.0, Some(val.2.clone()), None, false) - .unwrap(); + Storage::::write( + &info.trie_id, + &val.0 as &FixSizedKey, + Some(val.2.clone()), + None, + false, + ) + .unwrap(); } >::insert(&addr, info.clone()); diff --git a/frame/contracts/src/wasm/mod.rs b/frame/contracts/src/wasm/mod.rs index c385b5d2f7cc2..3dd5da187b258 100644 --- a/frame/contracts/src/wasm/mod.rs +++ b/frame/contracts/src/wasm/mod.rs @@ -275,7 +275,8 @@ mod tests { use super::*; use crate::{ exec::{ - AccountIdOf, BlockNumberOf, ErrorOrigin, ExecError, Executable, Ext, SeedOf, StorageKey, + AccountIdOf, BlockNumberOf, ErrorOrigin, ExecError, Executable, Ext, FixSizedKey, + SeedOf, VarSizedKey, }, gas::GasMeter, storage::WriteOutcome, @@ -330,7 +331,7 @@ mod tests { } pub struct MockExt { - storage: HashMap>, + storage: HashMap, Vec>, instantiates: Vec, terminations: Vec, calls: Vec, @@ -425,19 +426,45 @@ mod tests { self.terminations.push(TerminationEntry { beneficiary: beneficiary.clone() }); Ok(()) } - fn get_storage(&mut self, key: &StorageKey) -> Option> { - self.storage.get(key).cloned() + fn get_storage(&mut self, key: &FixSizedKey) -> Option> { + self.storage.get(&key.to_vec()).cloned() } - fn get_storage_size(&mut self, key: &StorageKey) -> Option { - self.storage.get(key).map(|val| val.len() as u32) + fn get_storage_transparent(&mut self, key: &VarSizedKey) -> Option> { + self.storage.get(&key.to_vec()).cloned() + } + fn get_storage_size(&mut self, key: &FixSizedKey) -> Option { + self.storage.get(&key.to_vec()).map(|val| val.len() as u32) + } + fn get_storage_size_transparent(&mut self, key: &VarSizedKey) -> Option { + self.storage.get(&key.to_vec()).map(|val| val.len() as u32) } fn set_storage( &mut self, - key: StorageKey, + key: &FixSizedKey, value: Option>, take_old: bool, ) -> Result { - let entry = self.storage.entry(key); + let key = key.to_vec(); + let entry = self.storage.entry(key.clone()); + let result = match (entry, take_old) { + (Entry::Vacant(_), _) => WriteOutcome::New, + (Entry::Occupied(entry), false) => + WriteOutcome::Overwritten(entry.remove().len() as u32), + (Entry::Occupied(entry), true) => WriteOutcome::Taken(entry.remove()), + }; + if let Some(value) = value { + self.storage.insert(key, value); + } + Ok(result) + } + fn set_storage_transparent( + &mut self, + key: &VarSizedKey, + value: Option>, + take_old: bool, + ) -> Result { + let key = key.to_vec(); + let entry = self.storage.entry(key.clone()); let result = match (entry, take_old) { (Entry::Vacant(_), _) => WriteOutcome::New, (Entry::Occupied(entry), false) => @@ -836,6 +863,7 @@ mod tests { } #[test] + #[cfg(not(feature = "unstable-interface"))] fn contains_storage_works() { const CODE: &str = r#" (module @@ -844,11 +872,10 @@ mod tests { (import "seal0" "seal_contains_storage" (func $seal_contains_storage (param i32) (result i32))) (import "env" "memory" (memory 1 1)) - ;; [0, 4) size of input buffer (32 byte as we copy the key here) + ;; [0, 4) size of input buffer (32 bytes as we copy the key here) (data (i32.const 0) "\20") ;; [4, 36) input buffer - ;; [36, inf) output buffer (func (export "call") @@ -879,8 +906,8 @@ mod tests { let mut ext = MockExt::default(); - ext.storage.insert([1u8; 32], vec![42u8]); - ext.storage.insert([2u8; 32], vec![]); + ext.storage.insert(vec![1u8; 32], vec![42u8]); + ext.storage.insert(vec![2u8; 32], vec![]); // value does not exist -> sentinel value returned let result = execute(CODE, [3u8; 32].encode(), &mut ext).unwrap(); @@ -895,6 +922,84 @@ mod tests { assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), 0,); } + #[test] + #[cfg(feature = "unstable-interface")] + fn contains_storage_works() { + const CODE: &str = r#" +(module + (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) + (import "seal0" "seal_input" (func $seal_input (param i32 i32))) + (import "__unstable__" "seal_contains_storage" (func $seal_contains_storage (param i32 i32) (result i32))) + (import "env" "memory" (memory 1 1)) + + + ;; size of input buffer + ;; [0, 4) size of input buffer (128+32 = 160 bytes = 0xA0) + (data (i32.const 0) "\A0") + + ;; [4, 164) input buffer + + (func (export "call") + ;; Receive key + (call $seal_input + (i32.const 4) ;; Where we take input and store it + (i32.const 0) ;; Where we take and store the length of the data + ) + ;; Call seal_clear_storage and save what it returns at 0 + (i32.store (i32.const 0) + (call $seal_contains_storage + (i32.const 8) ;; key_ptr + (i32.load (i32.const 4)) ;; key_len + ) + ) + (call $seal_return + (i32.const 0) ;; flags + (i32.const 0) ;; returned value + (i32.const 4) ;; length of returned value + ) + ) + + (func (export "deploy")) +) +"#; + + let mut ext = MockExt::default(); + ext.set_storage_transparent( + &VarSizedKey::::try_from([1u8; 64].to_vec()).unwrap(), + Some(vec![42u8]), + false, + ) + .unwrap(); + ext.set_storage_transparent( + &VarSizedKey::::try_from([2u8; 19].to_vec()).unwrap(), + Some(vec![]), + false, + ) + .unwrap(); + + //value does not exist (wrong key length) + let input = (63, [1u8; 64]).encode(); + let result = execute(CODE, input, &mut ext).unwrap(); + // sentinel returned + assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), crate::SENTINEL); + + // value exists + let input = (64, [1u8; 64]).encode(); + let result = execute(CODE, input, &mut ext).unwrap(); + // true as u32 returned + assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), 1); + // getter does not remove the value from storage + assert_eq!(ext.storage.get(&[1u8; 64].to_vec()).unwrap(), &[42u8]); + + // value exists (test for 0 sized) + let input = (19, [2u8; 19]).encode(); + let result = execute(CODE, input, &mut ext).unwrap(); + // true as u32 returned + assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), 0); + // getter does not remove the value from storage + assert_eq!(ext.storage.get(&[2u8; 19].to_vec()).unwrap(), &([] as [u8; 0])); + } + const CODE_INSTANTIATE: &str = r#" (module ;; seal_instantiate( @@ -1204,7 +1309,7 @@ mod tests { #[test] fn get_storage_puts_data_into_buf() { let mut mock_ext = MockExt::default(); - mock_ext.storage.insert([0x11; 32], [0x22; 32].to_vec()); + mock_ext.storage.insert([0x11; 32].to_vec(), [0x22; 32].to_vec()); let output = execute(CODE_GET_STORAGE, vec![], mock_ext).unwrap(); @@ -2176,6 +2281,7 @@ mod tests { } #[test] + #[cfg(not(feature = "unstable-interface"))] fn set_storage_works() { const CODE: &str = r#" (module @@ -2199,7 +2305,7 @@ mod tests { (call $seal_set_storage (i32.const 4) ;; key_ptr (i32.const 36) ;; value_ptr - (i32.sub ;; value_len (input_size - key_size) + (i32.sub ;; value_len (input_size - key_size) (i32.load (i32.const 0)) (i32.const 32) ) @@ -2207,7 +2313,7 @@ mod tests { ) (call $seal_return (i32.const 0) ;; flags - (i32.const 0) ;; returned value + (i32.const 0) ;; pointer to returned value (i32.const 4) ;; length of returned value ) ) @@ -2222,45 +2328,213 @@ mod tests { let input = ([1u8; 32], [42u8, 48]).encode(); let result = execute(CODE, input, &mut ext).unwrap(); assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), crate::SENTINEL); - assert_eq!(ext.storage.get(&[1u8; 32]).unwrap(), &[42u8, 48]); + assert_eq!(ext.storage.get(&[1u8; 32].to_vec()).unwrap(), &[42u8, 48]); // value do exist -> length of old value returned let input = ([1u8; 32], [0u8; 0]).encode(); let result = execute(CODE, input, &mut ext).unwrap(); assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), 2); - assert_eq!(ext.storage.get(&[1u8; 32]).unwrap(), &[0u8; 0]); + assert_eq!(ext.storage.get(&[1u8; 32].to_vec()).unwrap(), &[0u8; 0]); // value do exist -> length of old value returned (test for zero sized val) let input = ([1u8; 32], [99u8]).encode(); let result = execute(CODE, input, &mut ext).unwrap(); assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), 0); - assert_eq!(ext.storage.get(&[1u8; 32]).unwrap(), &[99u8]); + assert_eq!(ext.storage.get(&[1u8; 32].to_vec()).unwrap(), &[99u8]); } #[test] #[cfg(feature = "unstable-interface")] - fn clear_storage_works() { + fn set_storage_works() { const CODE: &str = r#" (module (import "seal0" "seal_input" (func $seal_input (param i32 i32))) (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) - (import "__unstable__" "seal_clear_storage" (func $seal_clear_storage (param i32) (result i32))) + (import "__unstable__" "seal_set_storage" (func $seal_set_storage (param i32 i32 i32 i32) (result i32))) (import "env" "memory" (memory 1 1)) - ;; 0x1000 = 4k in little endian - ;; size of input buffer + ;; [0, 4) size of input buffer + ;; 4k in little endian (data (i32.const 0) "\00\10") + ;; [4, 4100) input buffer + (func (export "call") - ;; Receive key + ;; Receive (key ++ value_to_write) (call $seal_input (i32.const 4) ;; Pointer to the input buffer - (i32.const 0) ;; Size of the length buffer + (i32.const 0) ;; Size of the input buffer ) ;; Store the passed value to the passed key and store result to memory + (i32.store (i32.const 168) + (call $seal_set_storage + (i32.const 8) ;; key_ptr + (i32.load (i32.const 4)) ;; key_len + (i32.add ;; value_ptr = 8 + key_len + (i32.const 8) + (i32.load (i32.const 4))) + (i32.sub ;; value_len (input_size - (key_len + key_len_len)) + (i32.load (i32.const 0)) + (i32.add + (i32.load (i32.const 4)) + (i32.const 4) + ) + ) + ) + ) + (call $seal_return + (i32.const 0) ;; flags + (i32.const 168) ;; ptr to returned value + (i32.const 4) ;; length of returned value + ) + ) + + (func (export "deploy")) +) +"#; + + let mut ext = MockExt::default(); + + // value did not exist before -> sentinel returned + let input = (32, [1u8; 32], [42u8, 48]).encode(); + let result = execute(CODE, input, &mut ext).unwrap(); + assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), crate::SENTINEL); + assert_eq!(ext.storage.get(&[1u8; 32].to_vec()).unwrap(), &[42u8, 48]); + + // value do exist -> length of old value returned + let input = (32, [1u8; 32], [0u8; 0]).encode(); + let result = execute(CODE, input, &mut ext).unwrap(); + assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), 2); + assert_eq!(ext.storage.get(&[1u8; 32].to_vec()).unwrap(), &[0u8; 0]); + + // value do exist -> length of old value returned (test for zero sized val) + let input = (32, [1u8; 32], [99u8]).encode(); + let result = execute(CODE, input, &mut ext).unwrap(); + assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), 0); + assert_eq!(ext.storage.get(&[1u8; 32].to_vec()).unwrap(), &[99u8]); + } + + #[test] + #[cfg(feature = "unstable-interface")] + fn get_storage_works() { + const CODE: &str = r#" +(module + (import "seal0" "seal_input" (func $seal_input (param i32 i32))) + (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) + (import "__unstable__" "seal_get_storage" (func $seal_get_storage (param i32 i32 i32 i32) (result i32))) + (import "env" "memory" (memory 1 1)) + + ;; [0, 4) size of input buffer (160 bytes as we copy the key+len here) + (data (i32.const 0) "\A0") + + ;; [4, 8) size of output buffer + ;; 4k in little endian + (data (i32.const 4) "\00\10") + + ;; [8, 168) input buffer + ;; [168, 4264) output buffer + + (func (export "call") + ;; Receive (key ++ value_to_write) + (call $seal_input + (i32.const 8) ;; Pointer to the input buffer + (i32.const 0) ;; Size of the input buffer + ) + ;; Load a storage value and result of this call into the output buffer + (i32.store (i32.const 168) + (call $seal_get_storage + (i32.const 12) ;; key_ptr + (i32.load (i32.const 8)) ;; key_len + (i32.const 172) ;; Pointer to the output buffer + (i32.const 4) ;; Pointer to the size of the buffer + ) + ) + (call $seal_return + (i32.const 0) ;; flags + (i32.const 168) ;; output buffer ptr + (i32.add ;; length: output size + 4 (retval) + (i32.load (i32.const 4)) + (i32.const 4) + ) + ) + ) + + (func (export "deploy")) +) +"#; + + let mut ext = MockExt::default(); + + ext.set_storage_transparent( + &VarSizedKey::::try_from([1u8; 64].to_vec()).unwrap(), + Some(vec![42u8]), + false, + ) + .unwrap(); + + ext.set_storage_transparent( + &VarSizedKey::::try_from([2u8; 19].to_vec()).unwrap(), + Some(vec![]), + false, + ) + .unwrap(); + + // value does not exist + let input = (63, [1u8; 64]).encode(); + let result = execute(CODE, input, &mut ext).unwrap(); + assert_eq!( + u32::from_le_bytes(result.data.0[0..4].try_into().unwrap()), + ReturnCode::KeyNotFound as u32 + ); + + // value exists + let input = (64, [1u8; 64]).encode(); + let result = execute(CODE, input, &mut ext).unwrap(); + assert_eq!( + u32::from_le_bytes(result.data.0[0..4].try_into().unwrap()), + ReturnCode::Success as u32 + ); + assert_eq!(ext.storage.get(&[1u8; 64].to_vec()).unwrap(), &[42u8]); + assert_eq!(&result.data.0[4..], &[42u8]); + + // value exists (test for 0 sized) + let input = (19, [2u8; 19]).encode(); + let result = execute(CODE, input, &mut ext).unwrap(); + assert_eq!( + u32::from_le_bytes(result.data.0[0..4].try_into().unwrap()), + ReturnCode::Success as u32 + ); + assert_eq!(ext.storage.get(&[2u8; 19].to_vec()), Some(&vec![])); + assert_eq!(&result.data.0[4..], &([] as [u8; 0])); + } + + #[test] + #[cfg(feature = "unstable-interface")] + fn clear_storage_works() { + const CODE: &str = r#" +(module + (import "seal0" "seal_input" (func $seal_input (param i32 i32))) + (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) + (import "__unstable__" "seal_clear_storage" (func $seal_clear_storage (param i32 i32) (result i32))) + (import "env" "memory" (memory 1 1)) + + ;; size of input buffer + ;; [0, 4) size of input buffer (128+32 = 160 bytes = 0xA0) + (data (i32.const 0) "\A0") + + ;; [4, 164) input buffer + + (func (export "call") + ;; Receive key + (call $seal_input + (i32.const 4) ;; Where we take input and store it + (i32.const 0) ;; Where we take and store the length of thedata + ) + ;; Call seal_clear_storage and save what it returns at 0 (i32.store (i32.const 0) (call $seal_clear_storage - (i32.const 4) ;; key_ptr + (i32.const 8) ;; key_ptr + (i32.load (i32.const 4)) ;; key_len ) ) (call $seal_return @@ -2276,23 +2550,48 @@ mod tests { let mut ext = MockExt::default(); - ext.storage.insert([1u8; 32], vec![42u8]); - ext.storage.insert([2u8; 32], vec![]); + ext.set_storage_transparent( + &VarSizedKey::::try_from([1u8; 64].to_vec()).unwrap(), + Some(vec![42u8]), + false, + ) + .unwrap(); + ext.set_storage_transparent( + &VarSizedKey::::try_from([2u8; 19].to_vec()).unwrap(), + Some(vec![]), + false, + ) + .unwrap(); - // value does not exist -> sentinel returned - let result = execute(CODE, [3u8; 32].encode(), &mut ext).unwrap(); + // value did not exist + let input = (32, [3u8; 32]).encode(); + let result = execute(CODE, input, &mut ext).unwrap(); + // sentinel returned assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), crate::SENTINEL); - assert_eq!(ext.storage.get(&[3u8; 32]), None); + assert_eq!(ext.storage.get(&[3u8; 32].to_vec()), None); - // value did exist -> length returned - let result = execute(CODE, [1u8; 32].encode(), &mut ext).unwrap(); + // value did exist + let input = (64, [1u8; 64]).encode(); + let result = execute(CODE, input, &mut ext).unwrap(); + // length returned assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), 1); - assert_eq!(ext.storage.get(&[1u8; 32]), None); + // value cleared + assert_eq!(ext.storage.get(&[1u8; 64].to_vec()), None); - // value did exist -> length returned (test for 0 sized) - let result = execute(CODE, [2u8; 32].encode(), &mut ext).unwrap(); + //value did not exist (wrong key length) + let input = (63, [1u8; 64]).encode(); + let result = execute(CODE, input, &mut ext).unwrap(); + // sentinel returned + assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), crate::SENTINEL); + assert_eq!(ext.storage.get(&[1u8; 64].to_vec()), None); + + // value exists + let input = (19, [2u8; 19]).encode(); + let result = execute(CODE, input, &mut ext).unwrap(); + // length returned (test for 0 sized) assert_eq!(u32::from_le_bytes(result.data.0.try_into().unwrap()), 0); - assert_eq!(ext.storage.get(&[2u8; 32]), None); + // value cleared + assert_eq!(ext.storage.get(&[2u8; 19].to_vec()), None); } #[test] @@ -2302,42 +2601,42 @@ mod tests { (module (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) (import "seal0" "seal_input" (func $seal_input (param i32 i32))) - (import "__unstable__" "seal_take_storage" (func $seal_take_storage (param i32 i32 i32) (result i32))) + (import "__unstable__" "seal_take_storage" (func $seal_take_storage (param i32 i32 i32 i32) (result i32))) (import "env" "memory" (memory 1 1)) - ;; [0, 32) size of input buffer (32 byte as we copy the key here) - (data (i32.const 0) "\20") + ;; [0, 4) size of input buffer (160 bytes as we copy the key+len here) + (data (i32.const 0) "\A0") - ;; [32, 64) size of output buffer + ;; [4, 8) size of output buffer ;; 4k in little endian - (data (i32.const 32) "\00\10") - - ;; [64, 96) input buffer + (data (i32.const 4) "\00\10") - ;; [96, inf) output buffer + ;; [8, 168) input buffer + ;; [168, 4264) output buffer (func (export "call") ;; Receive key (call $seal_input - (i32.const 64) ;; Pointer to the input buffer + (i32.const 8) ;; Pointer to the input buffer (i32.const 0) ;; Size of the length buffer ) ;; Load a storage value and result of this call into the output buffer - (i32.store (i32.const 96) + (i32.store (i32.const 168) (call $seal_take_storage - (i32.const 64) ;; The pointer to the storage key to fetch - (i32.const 100) ;; Pointer to the output buffer - (i32.const 32) ;; Pointer to the size of the buffer + (i32.const 12) ;; key_ptr + (i32.load (i32.const 8)) ;; key_len + (i32.const 172) ;; Pointer to the output buffer + (i32.const 4) ;; Pointer to the size of the buffer ) ) ;; Return the contents of the buffer (call $seal_return - (i32.const 0) ;; flags - (i32.const 96) ;; output buffer ptr - (i32.add ;; length: storage size + 4 (retval) - (i32.load (i32.const 32)) + (i32.const 0) ;; flags + (i32.const 168) ;; output buffer ptr + (i32.add ;; length: storage size + 4 (retval) + (i32.load (i32.const 4)) (i32.const 4) ) ) @@ -2349,32 +2648,46 @@ mod tests { let mut ext = MockExt::default(); - ext.storage.insert([1u8; 32], vec![42u8]); - ext.storage.insert([2u8; 32], vec![]); + ext.set_storage_transparent( + &VarSizedKey::::try_from([1u8; 64].to_vec()).unwrap(), + Some(vec![42u8]), + false, + ) + .unwrap(); + + ext.set_storage_transparent( + &VarSizedKey::::try_from([2u8; 19].to_vec()).unwrap(), + Some(vec![]), + false, + ) + .unwrap(); // value does not exist -> error returned - let result = execute(CODE, [3u8; 32].encode(), &mut ext).unwrap(); + let input = (63, [1u8; 64]).encode(); + let result = execute(CODE, input, &mut ext).unwrap(); assert_eq!( u32::from_le_bytes(result.data.0[0..4].try_into().unwrap()), ReturnCode::KeyNotFound as u32 ); // value did exist -> value returned - let result = execute(CODE, [1u8; 32].encode(), &mut ext).unwrap(); + let input = (64, [1u8; 64]).encode(); + let result = execute(CODE, input, &mut ext).unwrap(); assert_eq!( u32::from_le_bytes(result.data.0[0..4].try_into().unwrap()), ReturnCode::Success as u32 ); - assert_eq!(ext.storage.get(&[1u8; 32]), None); + assert_eq!(ext.storage.get(&[1u8; 64].to_vec()), None); assert_eq!(&result.data.0[4..], &[42u8]); // value did exist -> length returned (test for 0 sized) - let result = execute(CODE, [2u8; 32].encode(), &mut ext).unwrap(); + let input = (19, [2u8; 19]).encode(); + let result = execute(CODE, input, &mut ext).unwrap(); assert_eq!( u32::from_le_bytes(result.data.0[0..4].try_into().unwrap()), ReturnCode::Success as u32 ); - assert_eq!(ext.storage.get(&[2u8; 32]), None); + assert_eq!(ext.storage.get(&[2u8; 19].to_vec()), None); assert_eq!(&result.data.0[4..], &[0u8; 0]); } diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index 850794fc9b09e..c1757ba06412a 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -18,15 +18,16 @@ //! Environment definition of the wasm smart-contract runtime. use crate::{ - exec::{ExecError, ExecResult, Ext, StorageKey, TopicOf}, + exec::{ExecError, ExecResult, Ext, FixSizedKey, TopicOf, VarSizedKey}, gas::{ChargedAmount, Token}, schedule::HostFnWeights, wasm::env_def::ConvertibleToWasm, BalanceOf, CodeHash, Config, Error, SENTINEL, }; + use bitflags::bitflags; use codec::{Decode, DecodeAll, Encode, MaxEncodedLen}; -use frame_support::{dispatch::DispatchError, ensure, weights::Weight}; +use frame_support::{dispatch::DispatchError, ensure, traits::Get, weights::Weight}; use pallet_contracts_primitives::{ExecReturnValue, ReturnFlags}; use sp_core::{crypto::UncheckedFrom, Bytes}; use sp_io::hashing::{blake2_128, blake2_256, keccak_256, sha2_256}; @@ -35,6 +36,28 @@ use sp_sandbox::SandboxMemory; use sp_std::prelude::*; use wasm_instrument::parity_wasm::elements::ValueType; +/// Type of a storage key. +#[allow(dead_code)] +enum KeyType { + /// Deprecated fix sized key [0;32]. + Fix, + /// Variable sized key used in transparent hashing, + /// cannot be larger than MaxStorageKeyLen. + Variable(u32), +} + +impl KeyType { + fn len(&self) -> Result { + match self { + KeyType::Fix => Ok(32u32), + KeyType::Variable(len) => { + ensure!(len <= &::MaxStorageKeyLen::get(), Error::::DecodingFailed); + Ok(*len) + }, + } + } +} + /// Every error that can be returned to a contract when it calls any of the host functions. /// /// # Note @@ -695,6 +718,7 @@ where fn set_storage( &mut self, + key_type: KeyType, key_ptr: u32, value_ptr: u32, value_len: u32, @@ -705,10 +729,21 @@ where if value_len > max_size { return Err(Error::::ValueTooLarge.into()) } - let mut key: StorageKey = [0; 32]; - self.read_sandbox_memory_into_buf(key_ptr, &mut key)?; + let key = self.read_sandbox_memory(key_ptr, key_type.len::()?)?; let value = Some(self.read_sandbox_memory(value_ptr, value_len)?); - let write_outcome = self.ext.set_storage(key, value, false)?; + let write_outcome = match key_type { + KeyType::Fix => self.ext.set_storage( + &FixSizedKey::try_from(key).map_err(|_| Error::::DecodingFailed)?, + value, + false, + )?, + KeyType::Variable(_) => self.ext.set_storage_transparent( + &VarSizedKey::::try_from(key).map_err(|_| Error::::DecodingFailed)?, + value, + false, + )?, + }; + self.adjust_gas( charged, RuntimeCosts::SetStorage { new_bytes: value_len, old_bytes: write_outcome.old_len() }, @@ -716,15 +751,70 @@ where Ok(write_outcome.old_len_with_sentinel()) } - fn clear_storage(&mut self, key_ptr: u32) -> Result { + fn clear_storage(&mut self, key_type: KeyType, key_ptr: u32) -> Result { let charged = self.charge_gas(RuntimeCosts::ClearStorage(self.ext.max_value_size()))?; - let mut key: StorageKey = [0; 32]; - self.read_sandbox_memory_into_buf(key_ptr, &mut key)?; - let outcome = self.ext.set_storage(key, None, false)?; + let key = self.read_sandbox_memory(key_ptr, key_type.len::()?)?; + let outcome = match key_type { + KeyType::Fix => self.ext.set_storage( + &FixSizedKey::try_from(key).map_err(|_| Error::::DecodingFailed)?, + None, + false, + )?, + KeyType::Variable(_) => self.ext.set_storage_transparent( + &VarSizedKey::::try_from(key).map_err(|_| Error::::DecodingFailed)?, + None, + false, + )?, + }; + self.adjust_gas(charged, RuntimeCosts::ClearStorage(outcome.old_len())); Ok(outcome.old_len_with_sentinel()) } + fn get_storage( + &mut self, + key_type: KeyType, + key_ptr: u32, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result { + let charged = self.charge_gas(RuntimeCosts::GetStorage(self.ext.max_value_size()))?; + let key = self.read_sandbox_memory(key_ptr, key_type.len::()?)?; + let outcome = match key_type { + KeyType::Fix => self.ext.get_storage( + &FixSizedKey::try_from(key).map_err(|_| Error::::DecodingFailed)?, + ), + KeyType::Variable(_) => self.ext.get_storage_transparent( + &VarSizedKey::::try_from(key).map_err(|_| Error::::DecodingFailed)?, + ), + }; + + if let Some(value) = outcome { + self.adjust_gas(charged, RuntimeCosts::GetStorage(value.len() as u32)); + self.write_sandbox_output(out_ptr, out_len_ptr, &value, false, already_charged)?; + Ok(ReturnCode::Success) + } else { + self.adjust_gas(charged, RuntimeCosts::GetStorage(0)); + Ok(ReturnCode::KeyNotFound) + } + } + + fn contains_storage(&mut self, key_type: KeyType, key_ptr: u32) -> Result { + let charged = self.charge_gas(RuntimeCosts::ContainsStorage(self.ext.max_value_size()))?; + let key = self.read_sandbox_memory(key_ptr, key_type.len::()?)?; + let outcome = match key_type { + KeyType::Fix => self.ext.get_storage_size( + &FixSizedKey::try_from(key).map_err(|_| Error::::DecodingFailed)?, + ), + KeyType::Variable(_) => self.ext.get_storage_size_transparent( + &VarSizedKey::::try_from(key).map_err(|_| Error::::DecodingFailed)?, + ), + }; + + self.adjust_gas(charged, RuntimeCosts::ClearStorage(outcome.unwrap_or(0))); + Ok(outcome.unwrap_or(SENTINEL)) + } + fn call( &mut self, flags: CallFlags, @@ -863,11 +953,14 @@ define_env!(Env, , // Equivalent to the newer version of `seal_set_storage` with the exception of the return // type. Still a valid thing to call when not interested in the return value. [seal0] seal_set_storage(ctx, key_ptr: u32, value_ptr: u32, value_len: u32) => { - ctx.set_storage(key_ptr, value_ptr, value_len).map(|_| ()) + ctx.set_storage(KeyType::Fix, key_ptr, value_ptr, value_len).map(|_| ()) }, // Set the value at the given key in the contract storage. // + // This version is to be used with a fixed sized storage key. For runtimes supporting transparent + // hashing, please use the newer version of this function. + // // The value length must not exceed the maximum defined by the contracts module parameters. // Specifying a `value_len` of zero will store an empty value. // @@ -882,7 +975,27 @@ define_env!(Env, , // Returns the size of the pre-existing value at the specified key if any. Otherwise // `SENTINEL` is returned as a sentinel value. [seal1] seal_set_storage(ctx, key_ptr: u32, value_ptr: u32, value_len: u32) -> u32 => { - ctx.set_storage(key_ptr, value_ptr, value_len) + ctx.set_storage(KeyType::Fix, key_ptr, value_ptr, value_len) + }, + + // Set the value at the given key in the contract storage. + // + // The key and value lengths must not exceed the maximums defined by the contracts module parameters. + // Specifying a `value_len` of zero will store an empty value. + // + // # Parameters + // + // - `key_ptr`: pointer into the linear memory where the location to store the value is placed. + // - `key_len`: the length of the key in bytes. + // - `value_ptr`: pointer into the linear memory where the value to set is placed. + // - `value_len`: the length of the value in bytes. + // + // # Return Value + // + // Returns the size of the pre-existing value at the specified key if any. Otherwise + // `SENTINEL` is returned as a sentinel value. + [__unstable__] seal_set_storage(ctx, key_ptr: u32, key_len: u32, value_ptr: u32, value_len: u32) -> u32 => { + ctx.set_storage(KeyType::Variable(key_len), key_ptr, value_ptr, value_len) }, // Clear the value at the given key in the contract storage. @@ -890,25 +1003,29 @@ define_env!(Env, , // Equivalent to the newer version of `seal_clear_storage` with the exception of the return // type. Still a valid thing to call when not interested in the return value. [seal0] seal_clear_storage(ctx, key_ptr: u32) => { - ctx.clear_storage(key_ptr).map(|_| ()).map_err(Into::into) + ctx.clear_storage(KeyType::Fix, key_ptr).map(|_| ()) }, // Clear the value at the given key in the contract storage. // // # Parameters // - // - `key_ptr`: pointer into the linear memory where the location to clear the value is placed. + // - `key_ptr`: pointer into the linear memory where the key is placed. + // - `key_len`: the length of the key in bytes. // // # Return Value // // Returns the size of the pre-existing value at the specified key if any. Otherwise // `SENTINEL` is returned as a sentinel value. - [__unstable__] seal_clear_storage(ctx, key_ptr: u32) -> u32 => { - ctx.clear_storage(key_ptr).map_err(Into::into) + [__unstable__] seal_clear_storage(ctx, key_ptr: u32, key_len: u32) -> u32 => { + ctx.clear_storage(KeyType::Variable(key_len), key_ptr) }, // Retrieve the value under the given key from storage. // + // This version is to be used with a fixed sized storage key. For runtimes supporting transparent + // hashing, please use the newer version of this function. + // // # Parameters // // - `key_ptr`: pointer into the linear memory where the key of the requested value is placed. @@ -920,21 +1037,36 @@ define_env!(Env, , // // `ReturnCode::KeyNotFound` [seal0] seal_get_storage(ctx, key_ptr: u32, out_ptr: u32, out_len_ptr: u32) -> ReturnCode => { - let charged = ctx.charge_gas(RuntimeCosts::GetStorage(ctx.ext.max_value_size()))?; - let mut key: StorageKey = [0; 32]; - ctx.read_sandbox_memory_into_buf(key_ptr, &mut key)?; - if let Some(value) = ctx.ext.get_storage(&key) { - ctx.adjust_gas(charged, RuntimeCosts::GetStorage(value.len() as u32)); - ctx.write_sandbox_output(out_ptr, out_len_ptr, &value, false, already_charged)?; - Ok(ReturnCode::Success) - } else { - ctx.adjust_gas(charged, RuntimeCosts::GetStorage(0)); - Ok(ReturnCode::KeyNotFound) - } + ctx.get_storage(KeyType::Fix, key_ptr, out_ptr, out_len_ptr) + }, + + // Retrieve the value under the given key from storage. + // + // This version is to be used with a fixed sized storage key. For runtimes supporting transparent + // hashing, please use the newer version of this function. + // + // The key length must not exceed the maximum defined by the contracts module parameter. + // + // # Parameters + // + // - `key_ptr`: pointer into the linear memory where the key of the requested value is placed. + // - `key_len`: the length of the key in bytes. + // - `out_ptr`: pointer to the linear memory where the value is written to. + // - `out_len_ptr`: in-out pointer into linear memory where the buffer length + // is read from and the value length is written to. + // + // # Errors + // + // `ReturnCode::KeyNotFound` + [__unstable__] seal_get_storage(ctx, key_ptr: u32, key_len: u32, out_ptr: u32, out_len_ptr: u32) -> ReturnCode => { + ctx.get_storage(KeyType::Variable(key_len), key_ptr, out_ptr, out_len_ptr) }, // Checks whether there is a value stored under the given key. // + // This version is to be used with a fixed sized storage key. For runtimes supporting transparent + // hashing, please use the newer version of this function. + // // # Parameters // // - `key_ptr`: pointer into the linear memory where the key of the requested value is placed. @@ -944,16 +1076,24 @@ define_env!(Env, , // Returns the size of the pre-existing value at the specified key if any. Otherwise // `SENTINEL` is returned as a sentinel value. [seal0] seal_contains_storage(ctx, key_ptr: u32) -> u32 => { - let charged = ctx.charge_gas(RuntimeCosts::ContainsStorage(ctx.ext.max_value_size()))?; - let mut key: StorageKey = [0; 32]; - ctx.read_sandbox_memory_into_buf(key_ptr, &mut key)?; - if let Some(len) = ctx.ext.get_storage_size(&key) { - ctx.adjust_gas(charged, RuntimeCosts::ContainsStorage(len)); - Ok(len) - } else { - ctx.adjust_gas(charged, RuntimeCosts::ContainsStorage(0)); - Ok(SENTINEL) - } + ctx.contains_storage(KeyType::Fix, key_ptr) + }, + + // Checks whether there is a value stored under the given key. + // + // The key length must not exceed the maximum defined by the contracts module parameter. + // + // # Parameters + // + // - `key_ptr`: pointer into the linear memory where the key of the requested value is placed. + // - `key_len`: the length of the key in bytes. + // + // # Return Value + // + // Returns the size of the pre-existing value at the specified key if any. Otherwise + // `SENTINEL` is returned as a sentinel value. + [__unstable__] seal_contains_storage(ctx, key_ptr: u32, key_len: u32) -> u32 => { + ctx.contains_storage(KeyType::Variable(key_len), key_ptr) }, // Retrieve and remove the value under the given key from storage. @@ -961,6 +1101,7 @@ define_env!(Env, , // # Parameters // // - `key_ptr`: pointer into the linear memory where the key of the requested value is placed. + // - `key_len`: the length of the key in bytes. // - `out_ptr`: pointer to the linear memory where the value is written to. // - `out_len_ptr`: in-out pointer into linear memory where the buffer length // is read from and the value length is written to. @@ -968,11 +1109,10 @@ define_env!(Env, , // # Errors // // `ReturnCode::KeyNotFound` - [__unstable__] seal_take_storage(ctx, key_ptr: u32, out_ptr: u32, out_len_ptr: u32) -> ReturnCode => { + [__unstable__] seal_take_storage(ctx, key_ptr: u32, key_len: u32, out_ptr: u32, out_len_ptr: u32) -> ReturnCode => { let charged = ctx.charge_gas(RuntimeCosts::TakeStorage(ctx.ext.max_value_size()))?; - let mut key: StorageKey = [0; 32]; - ctx.read_sandbox_memory_into_buf(key_ptr, &mut key)?; - if let crate::storage::WriteOutcome::Taken(value) = ctx.ext.set_storage(key, None, true)? { + let key = ctx.read_sandbox_memory(key_ptr, key_len)?; + if let crate::storage::WriteOutcome::Taken(value) = ctx.ext.set_storage_transparent(&VarSizedKey::::try_from(key).map_err(|_| Error::::DecodingFailed)?, None, true)? { ctx.adjust_gas(charged, RuntimeCosts::TakeStorage(value.len() as u32)); ctx.write_sandbox_output(out_ptr, out_len_ptr, &value, false, already_charged)?; Ok(ReturnCode::Success) diff --git a/frame/contracts/src/weights.rs b/frame/contracts/src/weights.rs index b18c259ebd433..3c90579e65d53 100644 --- a/frame/contracts/src/weights.rs +++ b/frame/contracts/src/weights.rs @@ -18,11 +18,12 @@ //! Autogenerated weights for pallet_contracts //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-04-24, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-06-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// ./target/production/substrate +// target/production/substrate // benchmark // pallet // --chain=dev @@ -32,8 +33,9 @@ // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --template=./.maintain/frame-weight-template.hbs +// --heap-pages=4096 // --output=./frame/contracts/src/weights.rs +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -164,44 +166,47 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: Contracts DeletionQueue (r:1 w:0) fn on_process_deletion_queue_batch() -> Weight { - (1_664_000 as Weight) + (1_654_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `k` is `[0, 1024]`. fn on_initialize_per_trie_key(k: u32, ) -> Weight { - (11_878_000 as Weight) + (8_564_000 as Weight) // Standard Error: 0 - .saturating_add((758_000 as Weight).saturating_mul(k as Weight)) + .saturating_add((868_000 as Weight).saturating_mul(k as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(k as Weight))) } // Storage: Contracts DeletionQueue (r:1 w:0) + /// The range of component `q` is `[0, 1024]`. fn on_initialize_per_queue_item(q: u32, ) -> Weight { - (10_240_000 as Weight) - // Standard Error: 4_000 - .saturating_add((1_899_000 as Weight).saturating_mul(q as Weight)) + (0 as Weight) + // Standard Error: 5_000 + .saturating_add((1_944_000 as Weight).saturating_mul(q as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Contracts PristineCode (r:1 w:0) // Storage: Contracts CodeStorage (r:0 w:1) + /// The range of component `c` is `[0, 64226]`. fn reinstrument(c: u32, ) -> Weight { - (18_012_000 as Weight) + (19_016_000 as Weight) // Standard Error: 0 - .saturating_add((51_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((49_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: System Account (r:1 w:1) + /// The range of component `c` is `[0, 131072]`. fn call_with_code_per_byte(c: u32, ) -> Weight { - (206_036_000 as Weight) + (205_194_000 as Weight) // Standard Error: 0 - .saturating_add((52_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((53_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -209,50 +214,51 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts Nonce (r:1 w:1) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: System Account (r:1 w:1) // Storage: Contracts PristineCode (r:0 w:1) // Storage: Contracts OwnerInfoOf (r:0 w:1) + /// The range of component `c` is `[0, 64226]`. + /// The range of component `s` is `[0, 1048576]`. fn instantiate_with_code(c: u32, s: u32, ) -> Weight { - (243_162_000 as Weight) + (288_487_000 as Weight) // Standard Error: 0 - .saturating_add((122_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((124_000 as Weight).saturating_mul(c as Weight)) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(6 as Weight)) - .saturating_add(T::DbWeight::get().writes(7 as Weight)) + .saturating_add(T::DbWeight::get().reads(5 as Weight)) + .saturating_add(T::DbWeight::get().writes(6 as Weight)) } // Storage: Contracts CodeStorage (r:1 w:1) // Storage: Contracts Nonce (r:1 w:1) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: System Account (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:1 w:1) + /// The range of component `s` is `[0, 1048576]`. fn instantiate(s: u32, ) -> Weight { - (180_607_000 as Weight) + (186_136_000 as Weight) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(T::DbWeight::get().reads(7 as Weight)) - .saturating_add(T::DbWeight::get().writes(6 as Weight)) + .saturating_add(T::DbWeight::get().reads(6 as Weight)) + .saturating_add(T::DbWeight::get().writes(5 as Weight)) } // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: System Account (r:1 w:1) fn call() -> Weight { - (146_032_000 as Weight) + (149_232_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Contracts CodeStorage (r:1 w:1) // Storage: Contracts PristineCode (r:0 w:1) // Storage: Contracts OwnerInfoOf (r:0 w:1) + /// The range of component `c` is `[0, 64226]`. fn upload_code(c: u32, ) -> Weight { - (45_113_000 as Weight) + (51_721_000 as Weight) // Standard Error: 0 - .saturating_add((49_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((48_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -260,14 +266,14 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:0 w:1) // Storage: Contracts PristineCode (r:0 w:1) fn remove_code() -> Weight { - (25_722_000 as Weight) + (30_016_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:2 w:2) fn set_code() -> Weight { - (23_135_000 as Weight) + (27_192_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -275,11 +281,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_caller(r: u32, ) -> Weight { - (205_061_000 as Weight) - // Standard Error: 76_000 - .saturating_add((40_732_000 as Weight).saturating_mul(r as Weight)) + (206_405_000 as Weight) + // Standard Error: 112_000 + .saturating_add((40_987_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -287,11 +293,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_is_contract(r: u32, ) -> Weight { - (97_971_000 as Weight) - // Standard Error: 741_000 - .saturating_add((308_361_000 as Weight).saturating_mul(r as Weight)) + (106_220_000 as Weight) + // Standard Error: 710_000 + .saturating_add((307_648_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -300,11 +306,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_code_hash(r: u32, ) -> Weight { - (109_052_000 as Weight) - // Standard Error: 716_000 - .saturating_add((366_257_000 as Weight).saturating_mul(r as Weight)) + (104_498_000 as Weight) + // Standard Error: 633_000 + .saturating_add((368_901_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -313,11 +319,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_own_code_hash(r: u32, ) -> Weight { - (205_748_000 as Weight) - // Standard Error: 87_000 - .saturating_add((44_474_000 as Weight).saturating_mul(r as Weight)) + (208_696_000 as Weight) + // Standard Error: 101_000 + .saturating_add((44_445_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -325,11 +331,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_caller_is_origin(r: u32, ) -> Weight { - (201_898_000 as Weight) - // Standard Error: 55_000 - .saturating_add((16_703_000 as Weight).saturating_mul(r as Weight)) + (205_612_000 as Weight) + // Standard Error: 68_000 + .saturating_add((17_145_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -337,11 +343,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_address(r: u32, ) -> Weight { - (204_668_000 as Weight) - // Standard Error: 65_000 - .saturating_add((40_459_000 as Weight).saturating_mul(r as Weight)) + (206_947_000 as Weight) + // Standard Error: 107_000 + .saturating_add((40_789_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -349,11 +355,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_gas_left(r: u32, ) -> Weight { - (203_240_000 as Weight) - // Standard Error: 68_000 - .saturating_add((40_270_000 as Weight).saturating_mul(r as Weight)) + (208_692_000 as Weight) + // Standard Error: 109_000 + .saturating_add((40_600_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -361,11 +367,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_balance(r: u32, ) -> Weight { - (211_535_000 as Weight) - // Standard Error: 73_000 - .saturating_add((114_954_000 as Weight).saturating_mul(r as Weight)) + (209_811_000 as Weight) + // Standard Error: 208_000 + .saturating_add((116_831_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -373,11 +379,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_value_transferred(r: u32, ) -> Weight { - (204_653_000 as Weight) - // Standard Error: 71_000 - .saturating_add((40_188_000 as Weight).saturating_mul(r as Weight)) + (207_406_000 as Weight) + // Standard Error: 117_000 + .saturating_add((40_702_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -385,11 +391,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_minimum_balance(r: u32, ) -> Weight { - (204_690_000 as Weight) - // Standard Error: 82_000 - .saturating_add((40_260_000 as Weight).saturating_mul(r as Weight)) + (209_260_000 as Weight) + // Standard Error: 130_000 + .saturating_add((40_479_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -397,11 +403,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_block_number(r: u32, ) -> Weight { - (205_004_000 as Weight) - // Standard Error: 62_000 - .saturating_add((40_018_000 as Weight).saturating_mul(r as Weight)) + (206_448_000 as Weight) + // Standard Error: 95_000 + .saturating_add((40_134_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -409,11 +415,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_now(r: u32, ) -> Weight { - (204_341_000 as Weight) - // Standard Error: 93_000 - .saturating_add((39_920_000 as Weight).saturating_mul(r as Weight)) + (206_969_000 as Weight) + // Standard Error: 116_000 + .saturating_add((40_251_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -421,12 +427,12 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) + /// The range of component `r` is `[0, 20]`. fn seal_weight_to_fee(r: u32, ) -> Weight { - (208_702_000 as Weight) - // Standard Error: 115_000 - .saturating_add((101_441_000 as Weight).saturating_mul(r as Weight)) + (211_611_000 as Weight) + // Standard Error: 175_000 + .saturating_add((98_675_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -434,11 +440,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_gas(r: u32, ) -> Weight { - (131_983_000 as Weight) - // Standard Error: 17_000 - .saturating_add((19_153_000 as Weight).saturating_mul(r as Weight)) + (134_484_000 as Weight) + // Standard Error: 57_000 + .saturating_add((19_329_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -446,11 +452,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_input(r: u32, ) -> Weight { - (203_768_000 as Weight) - // Standard Error: 57_000 - .saturating_add((39_316_000 as Weight).saturating_mul(r as Weight)) + (208_556_000 as Weight) + // Standard Error: 125_000 + .saturating_add((40_328_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -458,11 +464,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `n` is `[0, 1024]`. fn seal_input_per_kb(n: u32, ) -> Weight { - (273_930_000 as Weight) - // Standard Error: 3_000 - .saturating_add((9_513_000 as Weight).saturating_mul(n as Weight)) + (268_886_000 as Weight) + // Standard Error: 4_000 + .saturating_add((9_627_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -470,10 +476,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - fn seal_return(r: u32, ) -> Weight { - (199_311_000 as Weight) - // Standard Error: 601_000 - .saturating_add((2_181_000 as Weight).saturating_mul(r as Weight)) + /// The range of component `r` is `[0, 1]`. + fn seal_return(_r: u32, ) -> Weight { + (203_591_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -481,11 +486,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `n` is `[0, 1024]`. fn seal_return_per_kb(n: u32, ) -> Weight { - (201_130_000 as Weight) + (204_258_000 as Weight) // Standard Error: 0 - .saturating_add((184_000 as Weight).saturating_mul(n as Weight)) + .saturating_add((183_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -493,28 +498,28 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: Contracts DeletionQueue (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:1 w:1) + /// The range of component `r` is `[0, 1]`. fn seal_terminate(r: u32, ) -> Weight { - (202_063_000 as Weight) - // Standard Error: 100_000 - .saturating_add((54_190_000 as Weight).saturating_mul(r as Weight)) + (206_625_000 as Weight) + // Standard Error: 672_000 + .saturating_add((59_377_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().reads((4 as Weight).saturating_mul(r as Weight))) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) .saturating_add(T::DbWeight::get().writes((5 as Weight).saturating_mul(r as Weight))) } // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: RandomnessCollectiveFlip RandomMaterial (r:1 w:0) + /// The range of component `r` is `[0, 20]`. fn seal_random(r: u32, ) -> Weight { - (206_528_000 as Weight) - // Standard Error: 120_000 - .saturating_add((136_384_000 as Weight).saturating_mul(r as Weight)) + (208_866_000 as Weight) + // Standard Error: 164_000 + .saturating_add((133_438_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -522,11 +527,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_deposit_event(r: u32, ) -> Weight { - (210_309_000 as Weight) - // Standard Error: 138_000 - .saturating_add((236_583_000 as Weight).saturating_mul(r as Weight)) + (220_860_000 as Weight) + // Standard Error: 209_000 + .saturating_add((239_951_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -535,12 +540,14 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) // Storage: System EventTopics (r:80 w:80) + /// The range of component `t` is `[0, 4]`. + /// The range of component `n` is `[0, 16]`. fn seal_deposit_event_per_topic_and_kb(t: u32, n: u32, ) -> Weight { - (434_046_000 as Weight) - // Standard Error: 1_678_000 - .saturating_add((242_928_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 330_000 - .saturating_add((66_716_000 as Weight).saturating_mul(n as Weight)) + (439_782_000 as Weight) + // Standard Error: 1_643_000 + .saturating_add((264_687_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 323_000 + .saturating_add((67_636_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().reads((80 as Weight).saturating_mul(t as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -550,119 +557,140 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_debug_message(r: u32, ) -> Weight { - (138_934_000 as Weight) - // Standard Error: 34_000 - .saturating_add((31_927_000 as Weight).saturating_mul(r as Weight)) + (140_280_000 as Weight) + // Standard Error: 82_000 + .saturating_add((32_717_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `r` is `[0, 10]`. fn seal_set_storage(r: u32, ) -> Weight { - (88_315_000 as Weight) - // Standard Error: 594_000 - .saturating_add((328_984_000 as Weight).saturating_mul(r as Weight)) + (161_247_000 as Weight) + // Standard Error: 883_000 + .saturating_add((423_997_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) .saturating_add(T::DbWeight::get().writes((80 as Weight).saturating_mul(r as Weight))) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `n` is `[0, 8]`. fn seal_set_storage_per_new_kb(n: u32, ) -> Weight { - (529_349_000 as Weight) - // Standard Error: 223_000 - .saturating_add((21_065_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(T::DbWeight::get().reads(85 as Weight)) - .saturating_add(T::DbWeight::get().writes(83 as Weight)) + (529_247_000 as Weight) + // Standard Error: 2_745_000 + .saturating_add((85_282_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(55 as Weight)) + .saturating_add(T::DbWeight::get().reads((5 as Weight).saturating_mul(n as Weight))) + .saturating_add(T::DbWeight::get().writes(53 as Weight)) + .saturating_add(T::DbWeight::get().writes((5 as Weight).saturating_mul(n as Weight))) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `n` is `[0, 8]`. fn seal_set_storage_per_old_kb(n: u32, ) -> Weight { - (546_447_000 as Weight) - // Standard Error: 261_000 - .saturating_add((8_709_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(T::DbWeight::get().reads(85 as Weight)) - .saturating_add(T::DbWeight::get().writes(83 as Weight)) + (529_812_000 as Weight) + // Standard Error: 2_513_000 + .saturating_add((74_554_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(55 as Weight)) + .saturating_add(T::DbWeight::get().reads((5 as Weight).saturating_mul(n as Weight))) + .saturating_add(T::DbWeight::get().writes(53 as Weight)) + .saturating_add(T::DbWeight::get().writes((5 as Weight).saturating_mul(n as Weight))) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `r` is `[0, 10]`. fn seal_clear_storage(r: u32, ) -> Weight { - (118_849_000 as Weight) - // Standard Error: 518_000 - .saturating_add((309_800_000 as Weight).saturating_mul(r as Weight)) + (184_803_000 as Weight) + // Standard Error: 733_000 + .saturating_add((404_933_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) .saturating_add(T::DbWeight::get().writes((80 as Weight).saturating_mul(r as Weight))) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `n` is `[0, 8]`. fn seal_clear_storage_per_kb(n: u32, ) -> Weight { - (537_039_000 as Weight) - // Standard Error: 235_000 - .saturating_add((8_071_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(T::DbWeight::get().reads(85 as Weight)) - .saturating_add(T::DbWeight::get().writes(83 as Weight)) + (500_958_000 as Weight) + // Standard Error: 2_980_000 + .saturating_add((75_996_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(55 as Weight)) + .saturating_add(T::DbWeight::get().reads((5 as Weight).saturating_mul(n as Weight))) + .saturating_add(T::DbWeight::get().writes(52 as Weight)) + .saturating_add(T::DbWeight::get().writes((5 as Weight).saturating_mul(n as Weight))) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `r` is `[0, 10]`. fn seal_get_storage(r: u32, ) -> Weight { - (125_427_000 as Weight) - // Standard Error: 635_000 - .saturating_add((276_126_000 as Weight).saturating_mul(r as Weight)) + (177_682_000 as Weight) + // Standard Error: 743_000 + .saturating_add((338_172_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `n` is `[0, 8]`. fn seal_get_storage_per_kb(n: u32, ) -> Weight { - (500_356_000 as Weight) - // Standard Error: 279_000 - .saturating_add((49_746_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(T::DbWeight::get().reads(84 as Weight)) + (465_285_000 as Weight) + // Standard Error: 2_599_000 + .saturating_add((155_106_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(55 as Weight)) + .saturating_add(T::DbWeight::get().reads((5 as Weight).saturating_mul(n as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `r` is `[0, 10]`. fn seal_contains_storage(r: u32, ) -> Weight { - (129_046_000 as Weight) - // Standard Error: 408_000 - .saturating_add((237_117_000 as Weight).saturating_mul(r as Weight)) + (179_118_000 as Weight) + // Standard Error: 572_000 + .saturating_add((311_083_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `n` is `[0, 8]`. fn seal_contains_storage_per_kb(n: u32, ) -> Weight { - (451_122_000 as Weight) - // Standard Error: 200_000 - .saturating_add((7_750_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(T::DbWeight::get().reads(84 as Weight)) + (423_056_000 as Weight) + // Standard Error: 2_037_000 + .saturating_add((69_665_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(54 as Weight)) + .saturating_add(T::DbWeight::get().reads((5 as Weight).saturating_mul(n as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `r` is `[0, 10]`. fn seal_take_storage(r: u32, ) -> Weight { - (118_085_000 as Weight) - // Standard Error: 526_000 - .saturating_add((338_332_000 as Weight).saturating_mul(r as Weight)) + (188_884_000 as Weight) + // Standard Error: 761_000 + .saturating_add((432_781_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(2 as Weight)) .saturating_add(T::DbWeight::get().writes((80 as Weight).saturating_mul(r as Weight))) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `n` is `[0, 8]`. fn seal_take_storage_per_kb(n: u32, ) -> Weight { - (569_270_000 as Weight) - // Standard Error: 294_000 - .saturating_add((51_071_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(T::DbWeight::get().reads(85 as Weight)) - .saturating_add(T::DbWeight::get().writes(83 as Weight)) + (532_408_000 as Weight) + // Standard Error: 3_348_000 + .saturating_add((164_943_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(T::DbWeight::get().reads(55 as Weight)) + .saturating_add(T::DbWeight::get().reads((5 as Weight).saturating_mul(n as Weight))) + .saturating_add(T::DbWeight::get().writes(53 as Weight)) + .saturating_add(T::DbWeight::get().writes((5 as Weight).saturating_mul(n as Weight))) } // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_transfer(r: u32, ) -> Weight { - (124_818_000 as Weight) - // Standard Error: 1_251_000 - .saturating_add((1_455_607_000 as Weight).saturating_mul(r as Weight)) + (127_181_000 as Weight) + // Standard Error: 1_495_000 + .saturating_add((1_500_589_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(2 as Weight)) @@ -672,11 +700,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_call(r: u32, ) -> Weight { (0 as Weight) - // Standard Error: 4_575_000 - .saturating_add((14_645_061_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 3_803_000 + .saturating_add((14_860_909_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -686,11 +714,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_delegate_call(r: u32, ) -> Weight { (0 as Weight) - // Standard Error: 5_742_000 - .saturating_add((14_623_917_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 6_045_000 + .saturating_add((14_797_140_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads((79 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -698,13 +726,14 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:81 w:81) // Storage: Contracts CodeStorage (r:2 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `t` is `[0, 1]`. + /// The range of component `c` is `[0, 1024]`. fn seal_call_per_transfer_clone_kb(t: u32, c: u32, ) -> Weight { - (9_081_635_000 as Weight) - // Standard Error: 11_326_000 - .saturating_add((1_335_139_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 4_000 - .saturating_add((9_575_000 as Weight).saturating_mul(c as Weight)) + (9_196_444_000 as Weight) + // Standard Error: 20_486_000 + .saturating_add((1_458_153_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 8_000 + .saturating_add((9_718_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(85 as Weight)) .saturating_add(T::DbWeight::get().reads((81 as Weight).saturating_mul(t as Weight))) .saturating_add(T::DbWeight::get().writes(81 as Weight)) @@ -714,13 +743,13 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: Contracts Nonce (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:80 w:80) + /// The range of component `r` is `[0, 20]`. fn seal_instantiate(r: u32, ) -> Weight { (0 as Weight) - // Standard Error: 34_958_000 - .saturating_add((20_700_850_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 36_253_000 + .saturating_add((21_201_529_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(6 as Weight)) .saturating_add(T::DbWeight::get().reads((320 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes(3 as Weight)) @@ -730,15 +759,16 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:81 w:81) // Storage: Contracts CodeStorage (r:2 w:1) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: Contracts Nonce (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:1 w:1) + /// The range of component `t` is `[0, 1]`. + /// The range of component `s` is `[0, 960]`. fn seal_instantiate_per_transfer_salt_kb(t: u32, s: u32, ) -> Weight { - (12_091_206_000 as Weight) - // Standard Error: 104_884_000 - .saturating_add((635_259_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 49_000 - .saturating_add((122_935_000 as Weight).saturating_mul(s as Weight)) + (12_282_498_000 as Weight) + // Standard Error: 48_112_000 + .saturating_add((720_795_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 22_000 + .saturating_add((124_274_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(167 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(t as Weight))) .saturating_add(T::DbWeight::get().writes(165 as Weight)) @@ -748,11 +778,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_hash_sha2_256(r: u32, ) -> Weight { - (203_315_000 as Weight) - // Standard Error: 74_000 - .saturating_add((60_223_000 as Weight).saturating_mul(r as Weight)) + (203_959_000 as Weight) + // Standard Error: 142_000 + .saturating_add((61_311_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -760,11 +790,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `n` is `[0, 1024]`. fn seal_hash_sha2_256_per_kb(n: u32, ) -> Weight { - (355_672_000 as Weight) - // Standard Error: 25_000 - .saturating_add((319_519_000 as Weight).saturating_mul(n as Weight)) + (349_915_000 as Weight) + // Standard Error: 40_000 + .saturating_add((320_652_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -772,11 +802,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_hash_keccak_256(r: u32, ) -> Weight { - (203_117_000 as Weight) - // Standard Error: 94_000 - .saturating_add((77_363_000 as Weight).saturating_mul(r as Weight)) + (209_219_000 as Weight) + // Standard Error: 157_000 + .saturating_add((73_728_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -784,11 +814,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `n` is `[0, 1024]`. fn seal_hash_keccak_256_per_kb(n: u32, ) -> Weight { - (196_575_000 as Weight) - // Standard Error: 13_000 - .saturating_add((243_479_000 as Weight).saturating_mul(n as Weight)) + (208_860_000 as Weight) + // Standard Error: 25_000 + .saturating_add((245_718_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -796,11 +826,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_hash_blake2_256(r: u32, ) -> Weight { - (203_938_000 as Weight) - // Standard Error: 97_000 - .saturating_add((50_708_000 as Weight).saturating_mul(r as Weight)) + (206_165_000 as Weight) + // Standard Error: 138_000 + .saturating_add((51_644_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -808,11 +838,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `n` is `[0, 1024]`. fn seal_hash_blake2_256_per_kb(n: u32, ) -> Weight { - (247_065_000 as Weight) - // Standard Error: 8_000 - .saturating_add((94_160_000 as Weight).saturating_mul(n as Weight)) + (255_955_000 as Weight) + // Standard Error: 14_000 + .saturating_add((95_090_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -820,11 +850,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_hash_blake2_128(r: u32, ) -> Weight { - (204_389_000 as Weight) - // Standard Error: 86_000 - .saturating_add((50_663_000 as Weight).saturating_mul(r as Weight)) + (208_153_000 as Weight) + // Standard Error: 140_000 + .saturating_add((51_264_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -832,11 +862,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `n` is `[0, 1024]`. fn seal_hash_blake2_128_per_kb(n: u32, ) -> Weight { - (284_700_000 as Weight) - // Standard Error: 9_000 - .saturating_add((94_231_000 as Weight).saturating_mul(n as Weight)) + (278_368_000 as Weight) + // Standard Error: 14_000 + .saturating_add((95_006_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -844,11 +874,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_ecdsa_recover(r: u32, ) -> Weight { - (235_813_000 as Weight) - // Standard Error: 521_000 - .saturating_add((3_044_204_000 as Weight).saturating_mul(r as Weight)) + (331_955_000 as Weight) + // Standard Error: 1_155_000 + .saturating_add((3_069_955_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -856,11 +886,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_ecdsa_to_eth_address(r: u32, ) -> Weight { - (204_095_000 as Weight) - // Standard Error: 495_000 - .saturating_add((2_027_914_000 as Weight).saturating_mul(r as Weight)) + (207_838_000 as Weight) + // Standard Error: 783_000 + .saturating_add((2_058_503_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -869,267 +899,319 @@ impl WeightInfo for SubstrateWeight { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) // Storage: Contracts OwnerInfoOf (r:16 w:16) + /// The range of component `r` is `[0, 20]`. fn seal_set_code_hash(r: u32, ) -> Weight { (0 as Weight) - // Standard Error: 1_604_000 - .saturating_add((759_511_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 1_567_000 + .saturating_add((774_380_000 as Weight).saturating_mul(r as Weight)) .saturating_add(T::DbWeight::get().reads((79 as Weight).saturating_mul(r as Weight))) .saturating_add(T::DbWeight::get().writes((79 as Weight).saturating_mul(r as Weight))) } + /// The range of component `r` is `[0, 50]`. fn instr_i64const(r: u32, ) -> Weight { - (74_210_000 as Weight) + (73_955_000 as Weight) // Standard Error: 1_000 - .saturating_add((601_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((612_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64load(r: u32, ) -> Weight { - (74_123_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_315_000 as Weight).saturating_mul(r as Weight)) + (74_057_000 as Weight) + // Standard Error: 3_000 + .saturating_add((1_324_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64store(r: u32, ) -> Weight { - (74_145_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_388_000 as Weight).saturating_mul(r as Weight)) + (74_137_000 as Weight) + // Standard Error: 5_000 + .saturating_add((1_427_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_select(r: u32, ) -> Weight { - (73_931_000 as Weight) + (73_844_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_768_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_773_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_if(r: u32, ) -> Weight { - (73_829_000 as Weight) - // Standard Error: 0 - .saturating_add((1_957_000 as Weight).saturating_mul(r as Weight)) + (73_979_000 as Weight) + // Standard Error: 3_000 + .saturating_add((1_952_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_br(r: u32, ) -> Weight { - (73_760_000 as Weight) - // Standard Error: 0 - .saturating_add((932_000 as Weight).saturating_mul(r as Weight)) + (73_924_000 as Weight) + // Standard Error: 3_000 + .saturating_add((941_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_br_if(r: u32, ) -> Weight { - (73_714_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_420_000 as Weight).saturating_mul(r as Weight)) + (73_574_000 as Weight) + // Standard Error: 5_000 + .saturating_add((1_439_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_br_table(r: u32, ) -> Weight { - (73_496_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_575_000 as Weight).saturating_mul(r as Weight)) + (73_343_000 as Weight) + // Standard Error: 3_000 + .saturating_add((1_603_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `e` is `[1, 256]`. fn instr_br_table_per_entry(e: u32, ) -> Weight { - (76_036_000 as Weight) + (76_267_000 as Weight) // Standard Error: 0 - .saturating_add((5_000 as Weight).saturating_mul(e as Weight)) + .saturating_add((4_000 as Weight).saturating_mul(e as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_call(r: u32, ) -> Weight { - (76_015_000 as Weight) - // Standard Error: 19_000 - .saturating_add((6_954_000 as Weight).saturating_mul(r as Weight)) + (74_877_000 as Weight) + // Standard Error: 12_000 + .saturating_add((7_144_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_call_indirect(r: u32, ) -> Weight { - (88_247_000 as Weight) - // Standard Error: 9_000 - .saturating_add((8_957_000 as Weight).saturating_mul(r as Weight)) + (88_665_000 as Weight) + // Standard Error: 20_000 + .saturating_add((9_142_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `p` is `[0, 128]`. fn instr_call_indirect_per_param(p: u32, ) -> Weight { - (98_336_000 as Weight) + (98_600_000 as Weight) // Standard Error: 2_000 - .saturating_add((474_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((469_000 as Weight).saturating_mul(p as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_local_get(r: u32, ) -> Weight { - (74_565_000 as Weight) - // Standard Error: 4_000 - .saturating_add((627_000 as Weight).saturating_mul(r as Weight)) + (74_555_000 as Weight) + // Standard Error: 1_000 + .saturating_add((624_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_local_set(r: u32, ) -> Weight { - (74_414_000 as Weight) + (74_329_000 as Weight) // Standard Error: 1_000 - .saturating_add((684_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((688_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_local_tee(r: u32, ) -> Weight { - (74_346_000 as Weight) + (74_612_000 as Weight) // Standard Error: 1_000 - .saturating_add((911_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((909_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_global_get(r: u32, ) -> Weight { - (76_649_000 as Weight) - // Standard Error: 0 - .saturating_add((1_183_000 as Weight).saturating_mul(r as Weight)) + (76_906_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_192_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_global_set(r: u32, ) -> Weight { - (76_995_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_370_000 as Weight).saturating_mul(r as Weight)) + (76_979_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_361_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_memory_current(r: u32, ) -> Weight { - (73_927_000 as Weight) - // Standard Error: 1_000 - .saturating_add((666_000 as Weight).saturating_mul(r as Weight)) + (74_370_000 as Weight) + // Standard Error: 3_000 + .saturating_add((661_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 1]`. fn instr_memory_grow(r: u32, ) -> Weight { - (73_479_000 as Weight) - // Standard Error: 24_000 - .saturating_add((180_808_000 as Weight).saturating_mul(r as Weight)) + (73_584_000 as Weight) + // Standard Error: 353_000 + .saturating_add((187_114_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64clz(r: u32, ) -> Weight { - (74_048_000 as Weight) + (74_206_000 as Weight) // Standard Error: 1_000 - .saturating_add((885_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((884_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64ctz(r: u32, ) -> Weight { - (73_894_000 as Weight) + (73_992_000 as Weight) // Standard Error: 1_000 - .saturating_add((889_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((893_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64popcnt(r: u32, ) -> Weight { - (73_728_000 as Weight) - // Standard Error: 0 - .saturating_add((896_000 as Weight).saturating_mul(r as Weight)) + (73_985_000 as Weight) + // Standard Error: 2_000 + .saturating_add((891_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64eqz(r: u32, ) -> Weight { - (74_049_000 as Weight) - // Standard Error: 1_000 - .saturating_add((897_000 as Weight).saturating_mul(r as Weight)) + (74_117_000 as Weight) + // Standard Error: 4_000 + .saturating_add((901_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64extendsi32(r: u32, ) -> Weight { - (74_118_000 as Weight) + (73_981_000 as Weight) // Standard Error: 1_000 - .saturating_add((863_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((866_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64extendui32(r: u32, ) -> Weight { - (74_042_000 as Weight) - // Standard Error: 1_000 - .saturating_add((865_000 as Weight).saturating_mul(r as Weight)) + (74_104_000 as Weight) + // Standard Error: 3_000 + .saturating_add((868_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i32wrapi64(r: u32, ) -> Weight { - (73_885_000 as Weight) - // Standard Error: 1_000 - .saturating_add((884_000 as Weight).saturating_mul(r as Weight)) + (74_293_000 as Weight) + // Standard Error: 3_000 + .saturating_add((878_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64eq(r: u32, ) -> Weight { - (73_788_000 as Weight) - // Standard Error: 0 - .saturating_add((1_355_000 as Weight).saturating_mul(r as Weight)) + (74_055_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_350_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64ne(r: u32, ) -> Weight { - (73_727_000 as Weight) - // Standard Error: 0 - .saturating_add((1_352_000 as Weight).saturating_mul(r as Weight)) + (73_710_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_360_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64lts(r: u32, ) -> Weight { - (73_825_000 as Weight) - // Standard Error: 0 - .saturating_add((1_351_000 as Weight).saturating_mul(r as Weight)) + (73_917_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_355_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64ltu(r: u32, ) -> Weight { - (73_638_000 as Weight) - // Standard Error: 0 - .saturating_add((1_355_000 as Weight).saturating_mul(r as Weight)) + (74_048_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_360_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64gts(r: u32, ) -> Weight { - (73_688_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_357_000 as Weight).saturating_mul(r as Weight)) + (74_029_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_349_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64gtu(r: u32, ) -> Weight { - (73_895_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_359_000 as Weight).saturating_mul(r as Weight)) + (74_267_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_353_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64les(r: u32, ) -> Weight { - (73_860_000 as Weight) - // Standard Error: 0 + (73_952_000 as Weight) + // Standard Error: 1_000 .saturating_add((1_350_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64leu(r: u32, ) -> Weight { - (73_864_000 as Weight) - // Standard Error: 0 - .saturating_add((1_361_000 as Weight).saturating_mul(r as Weight)) + (73_851_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_368_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64ges(r: u32, ) -> Weight { - (72_730_000 as Weight) - // Standard Error: 7_000 - .saturating_add((1_432_000 as Weight).saturating_mul(r as Weight)) + (74_034_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_348_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64geu(r: u32, ) -> Weight { - (73_998_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_346_000 as Weight).saturating_mul(r as Weight)) + (73_979_000 as Weight) + // Standard Error: 3_000 + .saturating_add((1_353_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64add(r: u32, ) -> Weight { - (73_708_000 as Weight) + (74_000_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_333_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_328_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64sub(r: u32, ) -> Weight { - (74_312_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_339_000 as Weight).saturating_mul(r as Weight)) + (73_883_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_331_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64mul(r: u32, ) -> Weight { - (74_203_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_322_000 as Weight).saturating_mul(r as Weight)) + (74_216_000 as Weight) + // Standard Error: 5_000 + .saturating_add((1_324_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64divs(r: u32, ) -> Weight { - (73_990_000 as Weight) + (73_989_000 as Weight) // Standard Error: 1_000 - .saturating_add((2_010_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_998_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64divu(r: u32, ) -> Weight { - (73_918_000 as Weight) - // Standard Error: 1_000 - .saturating_add((2_019_000 as Weight).saturating_mul(r as Weight)) + (73_857_000 as Weight) + // Standard Error: 4_000 + .saturating_add((2_073_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64rems(r: u32, ) -> Weight { - (73_927_000 as Weight) + (73_801_000 as Weight) // Standard Error: 1_000 - .saturating_add((2_001_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((2_027_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64remu(r: u32, ) -> Weight { - (73_691_000 as Weight) - // Standard Error: 0 - .saturating_add((2_062_000 as Weight).saturating_mul(r as Weight)) + (74_130_000 as Weight) + // Standard Error: 1_000 + .saturating_add((2_064_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64and(r: u32, ) -> Weight { - (73_869_000 as Weight) - // Standard Error: 0 + (74_071_000 as Weight) + // Standard Error: 1_000 .saturating_add((1_327_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64or(r: u32, ) -> Weight { - (73_890_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_327_000 as Weight).saturating_mul(r as Weight)) + (74_201_000 as Weight) + // Standard Error: 4_000 + .saturating_add((1_330_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64xor(r: u32, ) -> Weight { - (73_866_000 as Weight) + (74_241_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_327_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_321_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64shl(r: u32, ) -> Weight { - (73_793_000 as Weight) - // Standard Error: 0 - .saturating_add((1_352_000 as Weight).saturating_mul(r as Weight)) + (74_331_000 as Weight) + // Standard Error: 6_000 + .saturating_add((1_347_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64shrs(r: u32, ) -> Weight { - (73_695_000 as Weight) - // Standard Error: 0 - .saturating_add((1_354_000 as Weight).saturating_mul(r as Weight)) + (73_674_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_359_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64shru(r: u32, ) -> Weight { - (73_743_000 as Weight) - // Standard Error: 0 - .saturating_add((1_352_000 as Weight).saturating_mul(r as Weight)) + (73_807_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_358_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64rotl(r: u32, ) -> Weight { - (73_781_000 as Weight) - // Standard Error: 0 - .saturating_add((1_352_000 as Weight).saturating_mul(r as Weight)) + (73_725_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_358_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64rotr(r: u32, ) -> Weight { - (73_941_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_348_000 as Weight).saturating_mul(r as Weight)) + (73_755_000 as Weight) + // Standard Error: 3_000 + .saturating_add((1_360_000 as Weight).saturating_mul(r as Weight)) } } @@ -1137,44 +1219,47 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { // Storage: Contracts DeletionQueue (r:1 w:0) fn on_process_deletion_queue_batch() -> Weight { - (1_641_000 as Weight) + (1_654_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `k` is `[0, 1024]`. fn on_initialize_per_trie_key(k: u32, ) -> Weight { - (11_878_000 as Weight) + (8_564_000 as Weight) // Standard Error: 0 - .saturating_add((758_000 as Weight).saturating_mul(k as Weight)) + .saturating_add((868_000 as Weight).saturating_mul(k as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(k as Weight))) } // Storage: Contracts DeletionQueue (r:1 w:0) + /// The range of component `q` is `[0, 1024]`. fn on_initialize_per_queue_item(q: u32, ) -> Weight { - (10_240_000 as Weight) - // Standard Error: 4_000 - .saturating_add((1_899_000 as Weight).saturating_mul(q as Weight)) + (0 as Weight) + // Standard Error: 5_000 + .saturating_add((1_944_000 as Weight).saturating_mul(q as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Contracts PristineCode (r:1 w:0) // Storage: Contracts CodeStorage (r:0 w:1) + /// The range of component `c` is `[0, 64226]`. fn reinstrument(c: u32, ) -> Weight { - (18_012_000 as Weight) + (19_016_000 as Weight) // Standard Error: 0 - .saturating_add((51_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((49_000 as Weight).saturating_mul(c as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: System Account (r:1 w:1) + /// The range of component `c` is `[0, 131072]`. fn call_with_code_per_byte(c: u32, ) -> Weight { - (206_036_000 as Weight) + (205_194_000 as Weight) // Standard Error: 0 - .saturating_add((52_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((53_000 as Weight).saturating_mul(c as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -1182,50 +1267,51 @@ impl WeightInfo for () { // Storage: Contracts Nonce (r:1 w:1) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: System Account (r:1 w:1) // Storage: Contracts PristineCode (r:0 w:1) // Storage: Contracts OwnerInfoOf (r:0 w:1) + /// The range of component `c` is `[0, 64226]`. + /// The range of component `s` is `[0, 1048576]`. fn instantiate_with_code(c: u32, s: u32, ) -> Weight { - (243_162_000 as Weight) + (288_487_000 as Weight) // Standard Error: 0 - .saturating_add((122_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((124_000 as Weight).saturating_mul(c as Weight)) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(6 as Weight)) - .saturating_add(RocksDbWeight::get().writes(7 as Weight)) + .saturating_add(RocksDbWeight::get().reads(5 as Weight)) + .saturating_add(RocksDbWeight::get().writes(6 as Weight)) } // Storage: Contracts CodeStorage (r:1 w:1) // Storage: Contracts Nonce (r:1 w:1) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: System Account (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:1 w:1) + /// The range of component `s` is `[0, 1048576]`. fn instantiate(s: u32, ) -> Weight { - (180_607_000 as Weight) + (186_136_000 as Weight) // Standard Error: 0 .saturating_add((2_000 as Weight).saturating_mul(s as Weight)) - .saturating_add(RocksDbWeight::get().reads(7 as Weight)) - .saturating_add(RocksDbWeight::get().writes(6 as Weight)) + .saturating_add(RocksDbWeight::get().reads(6 as Weight)) + .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: System Account (r:1 w:1) fn call() -> Weight { - (146_032_000 as Weight) + (149_232_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Contracts CodeStorage (r:1 w:1) // Storage: Contracts PristineCode (r:0 w:1) // Storage: Contracts OwnerInfoOf (r:0 w:1) + /// The range of component `c` is `[0, 64226]`. fn upload_code(c: u32, ) -> Weight { - (45_113_000 as Weight) + (51_721_000 as Weight) // Standard Error: 0 - .saturating_add((49_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((48_000 as Weight).saturating_mul(c as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -1233,14 +1319,14 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:0 w:1) // Storage: Contracts PristineCode (r:0 w:1) fn remove_code() -> Weight { - (25_722_000 as Weight) + (30_016_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:2 w:2) fn set_code() -> Weight { - (23_135_000 as Weight) + (27_192_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -1248,11 +1334,11 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_caller(r: u32, ) -> Weight { - (205_061_000 as Weight) - // Standard Error: 76_000 - .saturating_add((40_732_000 as Weight).saturating_mul(r as Weight)) + (206_405_000 as Weight) + // Standard Error: 112_000 + .saturating_add((40_987_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1260,11 +1346,11 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_is_contract(r: u32, ) -> Weight { - (97_971_000 as Weight) - // Standard Error: 741_000 - .saturating_add((308_361_000 as Weight).saturating_mul(r as Weight)) + (106_220_000 as Weight) + // Standard Error: 710_000 + .saturating_add((307_648_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -1273,11 +1359,11 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_code_hash(r: u32, ) -> Weight { - (109_052_000 as Weight) - // Standard Error: 716_000 - .saturating_add((366_257_000 as Weight).saturating_mul(r as Weight)) + (104_498_000 as Weight) + // Standard Error: 633_000 + .saturating_add((368_901_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -1286,11 +1372,11 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_own_code_hash(r: u32, ) -> Weight { - (205_748_000 as Weight) - // Standard Error: 87_000 - .saturating_add((44_474_000 as Weight).saturating_mul(r as Weight)) + (208_696_000 as Weight) + // Standard Error: 101_000 + .saturating_add((44_445_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1298,11 +1384,11 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_caller_is_origin(r: u32, ) -> Weight { - (201_898_000 as Weight) - // Standard Error: 55_000 - .saturating_add((16_703_000 as Weight).saturating_mul(r as Weight)) + (205_612_000 as Weight) + // Standard Error: 68_000 + .saturating_add((17_145_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1310,11 +1396,11 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_address(r: u32, ) -> Weight { - (204_668_000 as Weight) - // Standard Error: 65_000 - .saturating_add((40_459_000 as Weight).saturating_mul(r as Weight)) + (206_947_000 as Weight) + // Standard Error: 107_000 + .saturating_add((40_789_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1322,11 +1408,11 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_gas_left(r: u32, ) -> Weight { - (203_240_000 as Weight) - // Standard Error: 68_000 - .saturating_add((40_270_000 as Weight).saturating_mul(r as Weight)) + (208_692_000 as Weight) + // Standard Error: 109_000 + .saturating_add((40_600_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1334,11 +1420,11 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_balance(r: u32, ) -> Weight { - (211_535_000 as Weight) - // Standard Error: 73_000 - .saturating_add((114_954_000 as Weight).saturating_mul(r as Weight)) + (209_811_000 as Weight) + // Standard Error: 208_000 + .saturating_add((116_831_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1346,11 +1432,11 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_value_transferred(r: u32, ) -> Weight { - (204_653_000 as Weight) - // Standard Error: 71_000 - .saturating_add((40_188_000 as Weight).saturating_mul(r as Weight)) + (207_406_000 as Weight) + // Standard Error: 117_000 + .saturating_add((40_702_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1358,11 +1444,11 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_minimum_balance(r: u32, ) -> Weight { - (204_690_000 as Weight) - // Standard Error: 82_000 - .saturating_add((40_260_000 as Weight).saturating_mul(r as Weight)) + (209_260_000 as Weight) + // Standard Error: 130_000 + .saturating_add((40_479_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1370,11 +1456,11 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_block_number(r: u32, ) -> Weight { - (205_004_000 as Weight) - // Standard Error: 62_000 - .saturating_add((40_018_000 as Weight).saturating_mul(r as Weight)) + (206_448_000 as Weight) + // Standard Error: 95_000 + .saturating_add((40_134_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1382,11 +1468,11 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_now(r: u32, ) -> Weight { - (204_341_000 as Weight) - // Standard Error: 93_000 - .saturating_add((39_920_000 as Weight).saturating_mul(r as Weight)) + (206_969_000 as Weight) + // Standard Error: 116_000 + .saturating_add((40_251_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1394,12 +1480,12 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) + /// The range of component `r` is `[0, 20]`. fn seal_weight_to_fee(r: u32, ) -> Weight { - (208_702_000 as Weight) - // Standard Error: 115_000 - .saturating_add((101_441_000 as Weight).saturating_mul(r as Weight)) + (211_611_000 as Weight) + // Standard Error: 175_000 + .saturating_add((98_675_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1407,11 +1493,11 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_gas(r: u32, ) -> Weight { - (131_983_000 as Weight) - // Standard Error: 17_000 - .saturating_add((19_153_000 as Weight).saturating_mul(r as Weight)) + (134_484_000 as Weight) + // Standard Error: 57_000 + .saturating_add((19_329_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1419,11 +1505,11 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_input(r: u32, ) -> Weight { - (203_768_000 as Weight) - // Standard Error: 57_000 - .saturating_add((39_316_000 as Weight).saturating_mul(r as Weight)) + (208_556_000 as Weight) + // Standard Error: 125_000 + .saturating_add((40_328_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1431,11 +1517,11 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `n` is `[0, 1024]`. fn seal_input_per_kb(n: u32, ) -> Weight { - (273_930_000 as Weight) - // Standard Error: 3_000 - .saturating_add((9_513_000 as Weight).saturating_mul(n as Weight)) + (268_886_000 as Weight) + // Standard Error: 4_000 + .saturating_add((9_627_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1443,10 +1529,9 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - fn seal_return(r: u32, ) -> Weight { - (199_311_000 as Weight) - // Standard Error: 601_000 - .saturating_add((2_181_000 as Weight).saturating_mul(r as Weight)) + /// The range of component `r` is `[0, 1]`. + fn seal_return(_r: u32, ) -> Weight { + (203_591_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1454,11 +1539,11 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `n` is `[0, 1024]`. fn seal_return_per_kb(n: u32, ) -> Weight { - (201_130_000 as Weight) + (204_258_000 as Weight) // Standard Error: 0 - .saturating_add((184_000 as Weight).saturating_mul(n as Weight)) + .saturating_add((183_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1466,28 +1551,28 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: Contracts DeletionQueue (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:1 w:1) + /// The range of component `r` is `[0, 1]`. fn seal_terminate(r: u32, ) -> Weight { - (202_063_000 as Weight) - // Standard Error: 100_000 - .saturating_add((54_190_000 as Weight).saturating_mul(r as Weight)) + (206_625_000 as Weight) + // Standard Error: 672_000 + .saturating_add((59_377_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().reads((4 as Weight).saturating_mul(r as Weight))) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) .saturating_add(RocksDbWeight::get().writes((5 as Weight).saturating_mul(r as Weight))) } // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: RandomnessCollectiveFlip RandomMaterial (r:1 w:0) + /// The range of component `r` is `[0, 20]`. fn seal_random(r: u32, ) -> Weight { - (206_528_000 as Weight) - // Standard Error: 120_000 - .saturating_add((136_384_000 as Weight).saturating_mul(r as Weight)) + (208_866_000 as Weight) + // Standard Error: 164_000 + .saturating_add((133_438_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1495,11 +1580,11 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_deposit_event(r: u32, ) -> Weight { - (210_309_000 as Weight) - // Standard Error: 138_000 - .saturating_add((236_583_000 as Weight).saturating_mul(r as Weight)) + (220_860_000 as Weight) + // Standard Error: 209_000 + .saturating_add((239_951_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1508,12 +1593,14 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) // Storage: System EventTopics (r:80 w:80) + /// The range of component `t` is `[0, 4]`. + /// The range of component `n` is `[0, 16]`. fn seal_deposit_event_per_topic_and_kb(t: u32, n: u32, ) -> Weight { - (434_046_000 as Weight) - // Standard Error: 1_678_000 - .saturating_add((242_928_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 330_000 - .saturating_add((66_716_000 as Weight).saturating_mul(n as Weight)) + (439_782_000 as Weight) + // Standard Error: 1_643_000 + .saturating_add((264_687_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 323_000 + .saturating_add((67_636_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().reads((80 as Weight).saturating_mul(t as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -1523,119 +1610,140 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_debug_message(r: u32, ) -> Weight { - (138_934_000 as Weight) - // Standard Error: 34_000 - .saturating_add((31_927_000 as Weight).saturating_mul(r as Weight)) + (140_280_000 as Weight) + // Standard Error: 82_000 + .saturating_add((32_717_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `r` is `[0, 10]`. fn seal_set_storage(r: u32, ) -> Weight { - (88_315_000 as Weight) - // Standard Error: 594_000 - .saturating_add((328_984_000 as Weight).saturating_mul(r as Weight)) + (161_247_000 as Weight) + // Standard Error: 883_000 + .saturating_add((423_997_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) .saturating_add(RocksDbWeight::get().writes((80 as Weight).saturating_mul(r as Weight))) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `n` is `[0, 8]`. fn seal_set_storage_per_new_kb(n: u32, ) -> Weight { - (529_349_000 as Weight) - // Standard Error: 223_000 - .saturating_add((21_065_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(RocksDbWeight::get().reads(85 as Weight)) - .saturating_add(RocksDbWeight::get().writes(83 as Weight)) + (529_247_000 as Weight) + // Standard Error: 2_745_000 + .saturating_add((85_282_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(RocksDbWeight::get().reads(55 as Weight)) + .saturating_add(RocksDbWeight::get().reads((5 as Weight).saturating_mul(n as Weight))) + .saturating_add(RocksDbWeight::get().writes(53 as Weight)) + .saturating_add(RocksDbWeight::get().writes((5 as Weight).saturating_mul(n as Weight))) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `n` is `[0, 8]`. fn seal_set_storage_per_old_kb(n: u32, ) -> Weight { - (546_447_000 as Weight) - // Standard Error: 261_000 - .saturating_add((8_709_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(RocksDbWeight::get().reads(85 as Weight)) - .saturating_add(RocksDbWeight::get().writes(83 as Weight)) + (529_812_000 as Weight) + // Standard Error: 2_513_000 + .saturating_add((74_554_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(RocksDbWeight::get().reads(55 as Weight)) + .saturating_add(RocksDbWeight::get().reads((5 as Weight).saturating_mul(n as Weight))) + .saturating_add(RocksDbWeight::get().writes(53 as Weight)) + .saturating_add(RocksDbWeight::get().writes((5 as Weight).saturating_mul(n as Weight))) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `r` is `[0, 10]`. fn seal_clear_storage(r: u32, ) -> Weight { - (118_849_000 as Weight) - // Standard Error: 518_000 - .saturating_add((309_800_000 as Weight).saturating_mul(r as Weight)) + (184_803_000 as Weight) + // Standard Error: 733_000 + .saturating_add((404_933_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) .saturating_add(RocksDbWeight::get().writes((80 as Weight).saturating_mul(r as Weight))) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `n` is `[0, 8]`. fn seal_clear_storage_per_kb(n: u32, ) -> Weight { - (537_039_000 as Weight) - // Standard Error: 235_000 - .saturating_add((8_071_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(RocksDbWeight::get().reads(85 as Weight)) - .saturating_add(RocksDbWeight::get().writes(83 as Weight)) + (500_958_000 as Weight) + // Standard Error: 2_980_000 + .saturating_add((75_996_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(RocksDbWeight::get().reads(55 as Weight)) + .saturating_add(RocksDbWeight::get().reads((5 as Weight).saturating_mul(n as Weight))) + .saturating_add(RocksDbWeight::get().writes(52 as Weight)) + .saturating_add(RocksDbWeight::get().writes((5 as Weight).saturating_mul(n as Weight))) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `r` is `[0, 10]`. fn seal_get_storage(r: u32, ) -> Weight { - (125_427_000 as Weight) - // Standard Error: 635_000 - .saturating_add((276_126_000 as Weight).saturating_mul(r as Weight)) + (177_682_000 as Weight) + // Standard Error: 743_000 + .saturating_add((338_172_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `n` is `[0, 8]`. fn seal_get_storage_per_kb(n: u32, ) -> Weight { - (500_356_000 as Weight) - // Standard Error: 279_000 - .saturating_add((49_746_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(RocksDbWeight::get().reads(84 as Weight)) + (465_285_000 as Weight) + // Standard Error: 2_599_000 + .saturating_add((155_106_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(RocksDbWeight::get().reads(55 as Weight)) + .saturating_add(RocksDbWeight::get().reads((5 as Weight).saturating_mul(n as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `r` is `[0, 10]`. fn seal_contains_storage(r: u32, ) -> Weight { - (129_046_000 as Weight) - // Standard Error: 408_000 - .saturating_add((237_117_000 as Weight).saturating_mul(r as Weight)) + (179_118_000 as Weight) + // Standard Error: 572_000 + .saturating_add((311_083_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `n` is `[0, 8]`. fn seal_contains_storage_per_kb(n: u32, ) -> Weight { - (451_122_000 as Weight) - // Standard Error: 200_000 - .saturating_add((7_750_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(RocksDbWeight::get().reads(84 as Weight)) + (423_056_000 as Weight) + // Standard Error: 2_037_000 + .saturating_add((69_665_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(RocksDbWeight::get().reads(54 as Weight)) + .saturating_add(RocksDbWeight::get().reads((5 as Weight).saturating_mul(n as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `r` is `[0, 10]`. fn seal_take_storage(r: u32, ) -> Weight { - (118_085_000 as Weight) - // Standard Error: 526_000 - .saturating_add((338_332_000 as Weight).saturating_mul(r as Weight)) + (188_884_000 as Weight) + // Standard Error: 761_000 + .saturating_add((432_781_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(2 as Weight)) .saturating_add(RocksDbWeight::get().writes((80 as Weight).saturating_mul(r as Weight))) } // Storage: Skipped Metadata (r:0 w:0) + /// The range of component `n` is `[0, 8]`. fn seal_take_storage_per_kb(n: u32, ) -> Weight { - (569_270_000 as Weight) - // Standard Error: 294_000 - .saturating_add((51_071_000 as Weight).saturating_mul(n as Weight)) - .saturating_add(RocksDbWeight::get().reads(85 as Weight)) - .saturating_add(RocksDbWeight::get().writes(83 as Weight)) + (532_408_000 as Weight) + // Standard Error: 3_348_000 + .saturating_add((164_943_000 as Weight).saturating_mul(n as Weight)) + .saturating_add(RocksDbWeight::get().reads(55 as Weight)) + .saturating_add(RocksDbWeight::get().reads((5 as Weight).saturating_mul(n as Weight))) + .saturating_add(RocksDbWeight::get().writes(53 as Weight)) + .saturating_add(RocksDbWeight::get().writes((5 as Weight).saturating_mul(n as Weight))) } // Storage: System Account (r:1 w:0) // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_transfer(r: u32, ) -> Weight { - (124_818_000 as Weight) - // Standard Error: 1_251_000 - .saturating_add((1_455_607_000 as Weight).saturating_mul(r as Weight)) + (127_181_000 as Weight) + // Standard Error: 1_495_000 + .saturating_add((1_500_589_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) @@ -1645,11 +1753,11 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_call(r: u32, ) -> Weight { (0 as Weight) - // Standard Error: 4_575_000 - .saturating_add((14_645_061_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 3_803_000 + .saturating_add((14_860_909_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().reads((80 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -1659,11 +1767,11 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_delegate_call(r: u32, ) -> Weight { (0 as Weight) - // Standard Error: 5_742_000 - .saturating_add((14_623_917_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 6_045_000 + .saturating_add((14_797_140_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads((79 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1671,13 +1779,14 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:81 w:81) // Storage: Contracts CodeStorage (r:2 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `t` is `[0, 1]`. + /// The range of component `c` is `[0, 1024]`. fn seal_call_per_transfer_clone_kb(t: u32, c: u32, ) -> Weight { - (9_081_635_000 as Weight) - // Standard Error: 11_326_000 - .saturating_add((1_335_139_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 4_000 - .saturating_add((9_575_000 as Weight).saturating_mul(c as Weight)) + (9_196_444_000 as Weight) + // Standard Error: 20_486_000 + .saturating_add((1_458_153_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 8_000 + .saturating_add((9_718_000 as Weight).saturating_mul(c as Weight)) .saturating_add(RocksDbWeight::get().reads(85 as Weight)) .saturating_add(RocksDbWeight::get().reads((81 as Weight).saturating_mul(t as Weight))) .saturating_add(RocksDbWeight::get().writes(81 as Weight)) @@ -1687,13 +1796,13 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: Contracts Nonce (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:80 w:80) + /// The range of component `r` is `[0, 20]`. fn seal_instantiate(r: u32, ) -> Weight { (0 as Weight) - // Standard Error: 34_958_000 - .saturating_add((20_700_850_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 36_253_000 + .saturating_add((21_201_529_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(6 as Weight)) .saturating_add(RocksDbWeight::get().reads((320 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) @@ -1703,15 +1812,16 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:81 w:81) // Storage: Contracts CodeStorage (r:2 w:1) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) // Storage: Contracts Nonce (r:1 w:1) // Storage: Contracts OwnerInfoOf (r:1 w:1) + /// The range of component `t` is `[0, 1]`. + /// The range of component `s` is `[0, 960]`. fn seal_instantiate_per_transfer_salt_kb(t: u32, s: u32, ) -> Weight { - (12_091_206_000 as Weight) - // Standard Error: 104_884_000 - .saturating_add((635_259_000 as Weight).saturating_mul(t as Weight)) - // Standard Error: 49_000 - .saturating_add((122_935_000 as Weight).saturating_mul(s as Weight)) + (12_282_498_000 as Weight) + // Standard Error: 48_112_000 + .saturating_add((720_795_000 as Weight).saturating_mul(t as Weight)) + // Standard Error: 22_000 + .saturating_add((124_274_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(167 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(t as Weight))) .saturating_add(RocksDbWeight::get().writes(165 as Weight)) @@ -1721,11 +1831,11 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_hash_sha2_256(r: u32, ) -> Weight { - (203_315_000 as Weight) - // Standard Error: 74_000 - .saturating_add((60_223_000 as Weight).saturating_mul(r as Weight)) + (203_959_000 as Weight) + // Standard Error: 142_000 + .saturating_add((61_311_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1733,11 +1843,11 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `n` is `[0, 1024]`. fn seal_hash_sha2_256_per_kb(n: u32, ) -> Weight { - (355_672_000 as Weight) - // Standard Error: 25_000 - .saturating_add((319_519_000 as Weight).saturating_mul(n as Weight)) + (349_915_000 as Weight) + // Standard Error: 40_000 + .saturating_add((320_652_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1745,11 +1855,11 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_hash_keccak_256(r: u32, ) -> Weight { - (203_117_000 as Weight) - // Standard Error: 94_000 - .saturating_add((77_363_000 as Weight).saturating_mul(r as Weight)) + (209_219_000 as Weight) + // Standard Error: 157_000 + .saturating_add((73_728_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1757,11 +1867,11 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `n` is `[0, 1024]`. fn seal_hash_keccak_256_per_kb(n: u32, ) -> Weight { - (196_575_000 as Weight) - // Standard Error: 13_000 - .saturating_add((243_479_000 as Weight).saturating_mul(n as Weight)) + (208_860_000 as Weight) + // Standard Error: 25_000 + .saturating_add((245_718_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1769,11 +1879,11 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_hash_blake2_256(r: u32, ) -> Weight { - (203_938_000 as Weight) - // Standard Error: 97_000 - .saturating_add((50_708_000 as Weight).saturating_mul(r as Weight)) + (206_165_000 as Weight) + // Standard Error: 138_000 + .saturating_add((51_644_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1781,11 +1891,11 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `n` is `[0, 1024]`. fn seal_hash_blake2_256_per_kb(n: u32, ) -> Weight { - (247_065_000 as Weight) - // Standard Error: 8_000 - .saturating_add((94_160_000 as Weight).saturating_mul(n as Weight)) + (255_955_000 as Weight) + // Standard Error: 14_000 + .saturating_add((95_090_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1793,11 +1903,11 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_hash_blake2_128(r: u32, ) -> Weight { - (204_389_000 as Weight) - // Standard Error: 86_000 - .saturating_add((50_663_000 as Weight).saturating_mul(r as Weight)) + (208_153_000 as Weight) + // Standard Error: 140_000 + .saturating_add((51_264_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1805,11 +1915,11 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `n` is `[0, 1024]`. fn seal_hash_blake2_128_per_kb(n: u32, ) -> Weight { - (284_700_000 as Weight) - // Standard Error: 9_000 - .saturating_add((94_231_000 as Weight).saturating_mul(n as Weight)) + (278_368_000 as Weight) + // Standard Error: 14_000 + .saturating_add((95_006_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1817,11 +1927,11 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_ecdsa_recover(r: u32, ) -> Weight { - (235_813_000 as Weight) - // Standard Error: 521_000 - .saturating_add((3_044_204_000 as Weight).saturating_mul(r as Weight)) + (331_955_000 as Weight) + // Standard Error: 1_155_000 + .saturating_add((3_069_955_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1829,11 +1939,11 @@ impl WeightInfo for () { // Storage: Contracts ContractInfoOf (r:1 w:1) // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) - // Storage: unknown [0x3a7472616e73616374696f6e5f6c6576656c3a] (r:1 w:1) + /// The range of component `r` is `[0, 20]`. fn seal_ecdsa_to_eth_address(r: u32, ) -> Weight { - (204_095_000 as Weight) - // Standard Error: 495_000 - .saturating_add((2_027_914_000 as Weight).saturating_mul(r as Weight)) + (207_838_000 as Weight) + // Standard Error: 783_000 + .saturating_add((2_058_503_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -1842,266 +1952,318 @@ impl WeightInfo for () { // Storage: Contracts CodeStorage (r:1 w:0) // Storage: Timestamp Now (r:1 w:0) // Storage: Contracts OwnerInfoOf (r:16 w:16) + /// The range of component `r` is `[0, 20]`. fn seal_set_code_hash(r: u32, ) -> Weight { (0 as Weight) - // Standard Error: 1_604_000 - .saturating_add((759_511_000 as Weight).saturating_mul(r as Weight)) + // Standard Error: 1_567_000 + .saturating_add((774_380_000 as Weight).saturating_mul(r as Weight)) .saturating_add(RocksDbWeight::get().reads((79 as Weight).saturating_mul(r as Weight))) .saturating_add(RocksDbWeight::get().writes((79 as Weight).saturating_mul(r as Weight))) } + /// The range of component `r` is `[0, 50]`. fn instr_i64const(r: u32, ) -> Weight { - (74_210_000 as Weight) + (73_955_000 as Weight) // Standard Error: 1_000 - .saturating_add((601_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((612_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64load(r: u32, ) -> Weight { - (74_123_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_315_000 as Weight).saturating_mul(r as Weight)) + (74_057_000 as Weight) + // Standard Error: 3_000 + .saturating_add((1_324_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64store(r: u32, ) -> Weight { - (74_145_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_388_000 as Weight).saturating_mul(r as Weight)) + (74_137_000 as Weight) + // Standard Error: 5_000 + .saturating_add((1_427_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_select(r: u32, ) -> Weight { - (73_931_000 as Weight) + (73_844_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_768_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_773_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_if(r: u32, ) -> Weight { - (73_829_000 as Weight) - // Standard Error: 0 - .saturating_add((1_957_000 as Weight).saturating_mul(r as Weight)) + (73_979_000 as Weight) + // Standard Error: 3_000 + .saturating_add((1_952_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_br(r: u32, ) -> Weight { - (73_760_000 as Weight) - // Standard Error: 0 - .saturating_add((932_000 as Weight).saturating_mul(r as Weight)) + (73_924_000 as Weight) + // Standard Error: 3_000 + .saturating_add((941_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_br_if(r: u32, ) -> Weight { - (73_714_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_420_000 as Weight).saturating_mul(r as Weight)) + (73_574_000 as Weight) + // Standard Error: 5_000 + .saturating_add((1_439_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_br_table(r: u32, ) -> Weight { - (73_496_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_575_000 as Weight).saturating_mul(r as Weight)) + (73_343_000 as Weight) + // Standard Error: 3_000 + .saturating_add((1_603_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `e` is `[1, 256]`. fn instr_br_table_per_entry(e: u32, ) -> Weight { - (76_036_000 as Weight) + (76_267_000 as Weight) // Standard Error: 0 - .saturating_add((5_000 as Weight).saturating_mul(e as Weight)) + .saturating_add((4_000 as Weight).saturating_mul(e as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_call(r: u32, ) -> Weight { - (76_015_000 as Weight) - // Standard Error: 19_000 - .saturating_add((6_954_000 as Weight).saturating_mul(r as Weight)) + (74_877_000 as Weight) + // Standard Error: 12_000 + .saturating_add((7_144_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_call_indirect(r: u32, ) -> Weight { - (88_247_000 as Weight) - // Standard Error: 9_000 - .saturating_add((8_957_000 as Weight).saturating_mul(r as Weight)) + (88_665_000 as Weight) + // Standard Error: 20_000 + .saturating_add((9_142_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `p` is `[0, 128]`. fn instr_call_indirect_per_param(p: u32, ) -> Weight { - (98_336_000 as Weight) + (98_600_000 as Weight) // Standard Error: 2_000 - .saturating_add((474_000 as Weight).saturating_mul(p as Weight)) + .saturating_add((469_000 as Weight).saturating_mul(p as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_local_get(r: u32, ) -> Weight { - (74_565_000 as Weight) - // Standard Error: 4_000 - .saturating_add((627_000 as Weight).saturating_mul(r as Weight)) + (74_555_000 as Weight) + // Standard Error: 1_000 + .saturating_add((624_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_local_set(r: u32, ) -> Weight { - (74_414_000 as Weight) + (74_329_000 as Weight) // Standard Error: 1_000 - .saturating_add((684_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((688_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_local_tee(r: u32, ) -> Weight { - (74_346_000 as Weight) + (74_612_000 as Weight) // Standard Error: 1_000 - .saturating_add((911_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((909_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_global_get(r: u32, ) -> Weight { - (76_649_000 as Weight) - // Standard Error: 0 - .saturating_add((1_183_000 as Weight).saturating_mul(r as Weight)) + (76_906_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_192_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_global_set(r: u32, ) -> Weight { - (76_995_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_370_000 as Weight).saturating_mul(r as Weight)) + (76_979_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_361_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_memory_current(r: u32, ) -> Weight { - (73_927_000 as Weight) - // Standard Error: 1_000 - .saturating_add((666_000 as Weight).saturating_mul(r as Weight)) + (74_370_000 as Weight) + // Standard Error: 3_000 + .saturating_add((661_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 1]`. fn instr_memory_grow(r: u32, ) -> Weight { - (73_479_000 as Weight) - // Standard Error: 24_000 - .saturating_add((180_808_000 as Weight).saturating_mul(r as Weight)) + (73_584_000 as Weight) + // Standard Error: 353_000 + .saturating_add((187_114_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64clz(r: u32, ) -> Weight { - (74_048_000 as Weight) + (74_206_000 as Weight) // Standard Error: 1_000 - .saturating_add((885_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((884_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64ctz(r: u32, ) -> Weight { - (73_894_000 as Weight) + (73_992_000 as Weight) // Standard Error: 1_000 - .saturating_add((889_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((893_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64popcnt(r: u32, ) -> Weight { - (73_728_000 as Weight) - // Standard Error: 0 - .saturating_add((896_000 as Weight).saturating_mul(r as Weight)) + (73_985_000 as Weight) + // Standard Error: 2_000 + .saturating_add((891_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64eqz(r: u32, ) -> Weight { - (74_049_000 as Weight) - // Standard Error: 1_000 - .saturating_add((897_000 as Weight).saturating_mul(r as Weight)) + (74_117_000 as Weight) + // Standard Error: 4_000 + .saturating_add((901_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64extendsi32(r: u32, ) -> Weight { - (74_118_000 as Weight) + (73_981_000 as Weight) // Standard Error: 1_000 - .saturating_add((863_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((866_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64extendui32(r: u32, ) -> Weight { - (74_042_000 as Weight) - // Standard Error: 1_000 - .saturating_add((865_000 as Weight).saturating_mul(r as Weight)) + (74_104_000 as Weight) + // Standard Error: 3_000 + .saturating_add((868_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i32wrapi64(r: u32, ) -> Weight { - (73_885_000 as Weight) - // Standard Error: 1_000 - .saturating_add((884_000 as Weight).saturating_mul(r as Weight)) + (74_293_000 as Weight) + // Standard Error: 3_000 + .saturating_add((878_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64eq(r: u32, ) -> Weight { - (73_788_000 as Weight) - // Standard Error: 0 - .saturating_add((1_355_000 as Weight).saturating_mul(r as Weight)) + (74_055_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_350_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64ne(r: u32, ) -> Weight { - (73_727_000 as Weight) - // Standard Error: 0 - .saturating_add((1_352_000 as Weight).saturating_mul(r as Weight)) + (73_710_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_360_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64lts(r: u32, ) -> Weight { - (73_825_000 as Weight) - // Standard Error: 0 - .saturating_add((1_351_000 as Weight).saturating_mul(r as Weight)) + (73_917_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_355_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64ltu(r: u32, ) -> Weight { - (73_638_000 as Weight) - // Standard Error: 0 - .saturating_add((1_355_000 as Weight).saturating_mul(r as Weight)) + (74_048_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_360_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64gts(r: u32, ) -> Weight { - (73_688_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_357_000 as Weight).saturating_mul(r as Weight)) + (74_029_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_349_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64gtu(r: u32, ) -> Weight { - (73_895_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_359_000 as Weight).saturating_mul(r as Weight)) + (74_267_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_353_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64les(r: u32, ) -> Weight { - (73_860_000 as Weight) - // Standard Error: 0 + (73_952_000 as Weight) + // Standard Error: 1_000 .saturating_add((1_350_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64leu(r: u32, ) -> Weight { - (73_864_000 as Weight) - // Standard Error: 0 - .saturating_add((1_361_000 as Weight).saturating_mul(r as Weight)) + (73_851_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_368_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64ges(r: u32, ) -> Weight { - (72_730_000 as Weight) - // Standard Error: 7_000 - .saturating_add((1_432_000 as Weight).saturating_mul(r as Weight)) + (74_034_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_348_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64geu(r: u32, ) -> Weight { - (73_998_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_346_000 as Weight).saturating_mul(r as Weight)) + (73_979_000 as Weight) + // Standard Error: 3_000 + .saturating_add((1_353_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64add(r: u32, ) -> Weight { - (73_708_000 as Weight) + (74_000_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_333_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_328_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64sub(r: u32, ) -> Weight { - (74_312_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_339_000 as Weight).saturating_mul(r as Weight)) + (73_883_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_331_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64mul(r: u32, ) -> Weight { - (74_203_000 as Weight) - // Standard Error: 2_000 - .saturating_add((1_322_000 as Weight).saturating_mul(r as Weight)) + (74_216_000 as Weight) + // Standard Error: 5_000 + .saturating_add((1_324_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64divs(r: u32, ) -> Weight { - (73_990_000 as Weight) + (73_989_000 as Weight) // Standard Error: 1_000 - .saturating_add((2_010_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_998_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64divu(r: u32, ) -> Weight { - (73_918_000 as Weight) - // Standard Error: 1_000 - .saturating_add((2_019_000 as Weight).saturating_mul(r as Weight)) + (73_857_000 as Weight) + // Standard Error: 4_000 + .saturating_add((2_073_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64rems(r: u32, ) -> Weight { - (73_927_000 as Weight) + (73_801_000 as Weight) // Standard Error: 1_000 - .saturating_add((2_001_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((2_027_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64remu(r: u32, ) -> Weight { - (73_691_000 as Weight) - // Standard Error: 0 - .saturating_add((2_062_000 as Weight).saturating_mul(r as Weight)) + (74_130_000 as Weight) + // Standard Error: 1_000 + .saturating_add((2_064_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64and(r: u32, ) -> Weight { - (73_869_000 as Weight) - // Standard Error: 0 + (74_071_000 as Weight) + // Standard Error: 1_000 .saturating_add((1_327_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64or(r: u32, ) -> Weight { - (73_890_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_327_000 as Weight).saturating_mul(r as Weight)) + (74_201_000 as Weight) + // Standard Error: 4_000 + .saturating_add((1_330_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64xor(r: u32, ) -> Weight { - (73_866_000 as Weight) + (74_241_000 as Weight) // Standard Error: 1_000 - .saturating_add((1_327_000 as Weight).saturating_mul(r as Weight)) + .saturating_add((1_321_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64shl(r: u32, ) -> Weight { - (73_793_000 as Weight) - // Standard Error: 0 - .saturating_add((1_352_000 as Weight).saturating_mul(r as Weight)) + (74_331_000 as Weight) + // Standard Error: 6_000 + .saturating_add((1_347_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64shrs(r: u32, ) -> Weight { - (73_695_000 as Weight) - // Standard Error: 0 - .saturating_add((1_354_000 as Weight).saturating_mul(r as Weight)) + (73_674_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_359_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64shru(r: u32, ) -> Weight { - (73_743_000 as Weight) - // Standard Error: 0 - .saturating_add((1_352_000 as Weight).saturating_mul(r as Weight)) + (73_807_000 as Weight) + // Standard Error: 2_000 + .saturating_add((1_358_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64rotl(r: u32, ) -> Weight { - (73_781_000 as Weight) - // Standard Error: 0 - .saturating_add((1_352_000 as Weight).saturating_mul(r as Weight)) + (73_725_000 as Weight) + // Standard Error: 1_000 + .saturating_add((1_358_000 as Weight).saturating_mul(r as Weight)) } + /// The range of component `r` is `[0, 50]`. fn instr_i64rotr(r: u32, ) -> Weight { - (73_941_000 as Weight) - // Standard Error: 1_000 - .saturating_add((1_348_000 as Weight).saturating_mul(r as Weight)) + (73_755_000 as Weight) + // Standard Error: 3_000 + .saturating_add((1_360_000 as Weight).saturating_mul(r as Weight)) } } From d459a46fd6cb1ecb4096ec448ffed04f891107d4 Mon Sep 17 00:00:00 2001 From: Vlad Date: Thu, 23 Jun 2022 17:54:15 +0300 Subject: [PATCH 369/484] Explain why `rusty-cachier` is put first (#11740) --- scripts/ci/gitlab/pipeline/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/ci/gitlab/pipeline/test.yml b/scripts/ci/gitlab/pipeline/test.yml index 0c3fd4d33798d..cc167410f94a4 100644 --- a/scripts/ci/gitlab/pipeline/test.yml +++ b/scripts/ci/gitlab/pipeline/test.yml @@ -80,6 +80,7 @@ cargo-check-benches: - .test-refs - .collect-artifacts before_script: + # perform rusty-cachier operations before any further modifications to the git repo to make cargo feel cheated not so much - !reference [.rust-info-script, script] - !reference [.rusty-cachier, before_script] # merges in the master branch on PRs From 95253a8eb3dd893b78f6fe36e4c60fb0162e8cf9 Mon Sep 17 00:00:00 2001 From: Koute Date: Fri, 24 Jun 2022 20:25:21 +0900 Subject: [PATCH 370/484] Bump `wasmtime` to 0.38.0 and `zstd` to 0.11.2 (#11720) * Bump `wasmtime` to 0.37.0 and `zstd` to 0.11.2 * Bump `wasmtime` to 0.38.0 --- Cargo.lock | 180 +++++++++++--------- client/executor/wasmtime/Cargo.toml | 2 +- client/executor/wasmtime/src/imports.rs | 12 +- client/executor/wasmtime/src/runtime.rs | 1 - frame/state-trie-migration/Cargo.toml | 2 +- primitives/maybe-compressed-blob/Cargo.toml | 2 +- primitives/runtime/Cargo.toml | 2 +- primitives/wasm-interface/Cargo.toml | 2 +- utils/frame/try-runtime/cli/Cargo.toml | 2 +- 9 files changed, 109 insertions(+), 96 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cfcf5573c0bff..58604b47a87da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1141,11 +1141,11 @@ dependencies = [ [[package]] name = "cranelift-bforest" -version = "0.82.3" +version = "0.85.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38faa2a16616c8e78a18d37b4726b98bfd2de192f2fdc8a39ddf568a408a0f75" +checksum = "899dc8d22f7771e7f887fb8bafa0c0d3ac1dea0c7f2c0ded6e20a855a7a1e890" dependencies = [ - "cranelift-entity 0.82.3", + "cranelift-entity 0.85.0", ] [[package]] @@ -1160,24 +1160,25 @@ dependencies = [ "cranelift-entity 0.76.0", "gimli 0.25.0", "log", - "regalloc 0.0.31", + "regalloc", "smallvec", "target-lexicon", ] [[package]] name = "cranelift-codegen" -version = "0.82.3" +version = "0.85.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26f192472a3ba23860afd07d2b0217dc628f21fcc72617aa1336d98e1671f33b" +checksum = "8dbdc03f695cf67e7bc45da57155528274f47390b85060af8107eb304ef167c4" dependencies = [ - "cranelift-bforest 0.82.3", - "cranelift-codegen-meta 0.82.3", - "cranelift-codegen-shared 0.82.3", - "cranelift-entity 0.82.3", + "cranelift-bforest 0.85.0", + "cranelift-codegen-meta 0.85.0", + "cranelift-codegen-shared 0.85.0", + "cranelift-entity 0.85.0", + "cranelift-isle", "gimli 0.26.1", "log", - "regalloc 0.0.34", + "regalloc2", "smallvec", "target-lexicon", ] @@ -1194,11 +1195,11 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.82.3" +version = "0.85.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f32ddb89e9b89d3d9b36a5b7d7ea3261c98235a76ac95ba46826b8ec40b1a24" +checksum = "9ea66cbba3eb7fcb3ec9f42839a6d381bd40cf97780397e7167daf9725d4ffa0" dependencies = [ - "cranelift-codegen-shared 0.82.3", + "cranelift-codegen-shared 0.85.0", ] [[package]] @@ -1209,9 +1210,9 @@ checksum = "9dabb5fe66e04d4652e434195b45ae65b5c8172d520247b8f66d8df42b2b45dc" [[package]] name = "cranelift-codegen-shared" -version = "0.82.3" +version = "0.85.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01fd0d9f288cc1b42d9333b7a776b17e278fc888c28e6a0f09b5573d45a150bc" +checksum = "712fbebd119a476f59122b4ba51fdce893a66309b5c92bd5506bfb11a0587496" [[package]] name = "cranelift-entity" @@ -1221,9 +1222,9 @@ checksum = "3329733e4d4b8e91c809efcaa4faee80bf66f20164e3dd16d707346bd3494799" [[package]] name = "cranelift-entity" -version = "0.82.3" +version = "0.85.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e3bfe172b83167604601faf9dc60453e0d0a93415b57a9c4d1a7ae6849185cf" +checksum = "4cb8b95859c4e14c9e860db78d596a904fdbe9261990233b62bd526346cb56cb" dependencies = [ "serde", ] @@ -1242,40 +1243,46 @@ dependencies = [ [[package]] name = "cranelift-frontend" -version = "0.82.3" +version = "0.85.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a006e3e32d80ce0e4ba7f1f9ddf66066d052a8c884a110b91d05404d6ce26dce" +checksum = "c7b91b19a7d1221a73f190c0e865c12be77a84f661cac89abfd4ab5820142886" dependencies = [ - "cranelift-codegen 0.82.3", + "cranelift-codegen 0.85.0", "log", "smallvec", "target-lexicon", ] +[[package]] +name = "cranelift-isle" +version = "0.85.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86d4f53bc86fb458e59c695c6a95ce8346e6a8377ee7ffc058e3ac08b5f94cb1" + [[package]] name = "cranelift-native" -version = "0.82.3" +version = "0.85.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501241b0cdf903412ec9075385ac9f2b1eb18a89044d1538e97fab603231f70c" +checksum = "592f035d0ed41214dfeeb37abd536233536a27be6b4c2d39f380cd402f0cff4f" dependencies = [ - "cranelift-codegen 0.82.3", + "cranelift-codegen 0.85.0", "libc", "target-lexicon", ] [[package]] name = "cranelift-wasm" -version = "0.82.3" +version = "0.85.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16d9e4211bbc3268042a96dd4de5bd979cda22434991d035f5f8eacba987fad2" +checksum = "295add6bf0b527a8bc50d02e31ff878585d2d2db53cb7e8754d6d82b84480086" dependencies = [ - "cranelift-codegen 0.82.3", - "cranelift-entity 0.82.3", - "cranelift-frontend 0.82.3", + "cranelift-codegen 0.85.0", + "cranelift-entity 0.85.0", + "cranelift-frontend 0.85.0", "itertools", "log", "smallvec", - "wasmparser 0.83.0", + "wasmparser 0.85.0", "wasmtime-types", ] @@ -2572,6 +2579,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "gcc" version = "0.3.55" @@ -5118,8 +5134,6 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" dependencies = [ - "crc32fast", - "indexmap", "memchr", ] @@ -5137,9 +5151,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.10.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" +checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" [[package]] name = "oorandom" @@ -7591,13 +7605,14 @@ dependencies = [ ] [[package]] -name = "regalloc" -version = "0.0.34" +name = "regalloc2" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62446b1d3ebf980bdc68837700af1d77b37bc430e524bf95319c6eada2a4cc02" +checksum = "0d37148700dbb38f994cd99a1431613057f37ed934d7e4d799b7ab758c482461" dependencies = [ + "fxhash", "log", - "rustc-hash", + "slice-group-by", "smallvec", ] @@ -7829,9 +7844,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.33.5" +version = "0.33.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03627528abcc4a365554d32a9f3bbf67f7694c102cfeda792dc86a2d6057cc85" +checksum = "938a344304321a9da4973b9ff4f9f8db9caf4597dfd9dda6a60b523340a0fff0" dependencies = [ "bitflags", "errno", @@ -9539,6 +9554,12 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" +[[package]] +name = "slice-group-by" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b634d87b960ab1a38c4fe143b508576f075e7c978bfad18217645ebfdfa2ec" + [[package]] name = "smallvec" version = "1.8.0" @@ -11901,15 +11922,18 @@ checksum = "52144d4c78e5cf8b055ceab8e5fa22814ce4315d6002ad32cfd914f37c12fd65" [[package]] name = "wasmparser" -version = "0.83.0" +version = "0.85.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" +checksum = "570460c58b21e9150d2df0eaaedbb7816c34bcec009ae0dcc976e40ba81463e7" +dependencies = [ + "indexmap", +] [[package]] name = "wasmtime" -version = "0.35.3" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21ffb4705016d5ca91e18a72ed6822dab50e6d5ddd7045461b17ef19071cdef1" +checksum = "c842f9c8e190fe01300fc8d715e9368c775670fb9856247c67abffdb5236d6db" dependencies = [ "anyhow", "backtrace", @@ -11919,7 +11943,7 @@ dependencies = [ "lazy_static", "libc", "log", - "object 0.27.1", + "object 0.28.3", "once_cell", "paste 1.0.6", "psm", @@ -11927,7 +11951,7 @@ dependencies = [ "region 2.2.0", "serde", "target-lexicon", - "wasmparser 0.83.0", + "wasmparser 0.85.0", "wasmtime-cache", "wasmtime-cranelift", "wasmtime-environ", @@ -11938,9 +11962,9 @@ dependencies = [ [[package]] name = "wasmtime-cache" -version = "0.35.3" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c6ab24291fa7cb3a181f5669f6c72599b7ef781669759b45c7828c5999d0c0" +checksum = "cce2aa752e864a33eef2a6629edc59554e75f0bc1719431dac5e49eed516af69" dependencies = [ "anyhow", "base64", @@ -11958,51 +11982,51 @@ dependencies = [ [[package]] name = "wasmtime-cranelift" -version = "0.35.3" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04c810078a491b7bc4866ebe045f714d2b95e6b539e1f64009a4a7606be11de" +checksum = "922361eb8c03cea8909bc922471202f6c6bc2f0c682fac2fe473740441c86b3b" dependencies = [ "anyhow", - "cranelift-codegen 0.82.3", - "cranelift-entity 0.82.3", - "cranelift-frontend 0.82.3", + "cranelift-codegen 0.85.0", + "cranelift-entity 0.85.0", + "cranelift-frontend 0.85.0", "cranelift-native", "cranelift-wasm", "gimli 0.26.1", "log", "more-asserts", - "object 0.27.1", + "object 0.28.3", "target-lexicon", "thiserror", - "wasmparser 0.83.0", + "wasmparser 0.85.0", "wasmtime-environ", ] [[package]] name = "wasmtime-environ" -version = "0.35.3" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61448266ea164b1ac406363cdcfac81c7c44db4d94c7a81c8620ac6c5c6cdf59" +checksum = "e602f1120fc40a3f016f1f69d08c86cfeff7b867bed1462901953e6871f85167" dependencies = [ "anyhow", - "cranelift-entity 0.82.3", + "cranelift-entity 0.85.0", "gimli 0.26.1", "indexmap", "log", "more-asserts", - "object 0.27.1", + "object 0.28.3", "serde", "target-lexicon", "thiserror", - "wasmparser 0.83.0", + "wasmparser 0.85.0", "wasmtime-types", ] [[package]] name = "wasmtime-jit" -version = "0.35.3" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "156b4623c6b0d4b8c24afb846c20525922f538ef464cc024abab7ea8de2109a2" +checksum = "49af1445759a8e797a92f27dd0983c155615648263052e0b80d69e7d223896b7" dependencies = [ "addr2line", "anyhow", @@ -12011,7 +12035,7 @@ dependencies = [ "cpp_demangle", "gimli 0.26.1", "log", - "object 0.27.1", + "object 0.28.3", "region 2.2.0", "rustc-demangle", "rustix", @@ -12026,20 +12050,20 @@ dependencies = [ [[package]] name = "wasmtime-jit-debug" -version = "0.35.3" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5dc31f811760a6c76b2672c404866fd19b75e5fb3b0075a3e377a6846490654" +checksum = "e6d5dd480cc6dc0a401653e45b79796a3317f8228990d84bc2271bdaf0810071" dependencies = [ "lazy_static", - "object 0.27.1", + "object 0.28.3", "rustix", ] [[package]] name = "wasmtime-runtime" -version = "0.35.3" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f907beaff69d4d920fa4688411ee4cc75c0f01859e424677f9e426e2ef749864" +checksum = "e875bcd02d1ecfc7d099dd58354d55d73467652eb2b103ff470fe3aecb7d0381" dependencies = [ "anyhow", "backtrace", @@ -12063,14 +12087,14 @@ dependencies = [ [[package]] name = "wasmtime-types" -version = "0.35.3" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514ef0e5fd197b9609dc9eb74beba0c84d5a12b2417cbae55534633329ba4852" +checksum = "8fd63a19ba61ac7448add4dc1fecb8d78304812af2a52dad04b89f887791b156" dependencies = [ - "cranelift-entity 0.82.3", + "cranelift-entity 0.85.0", "serde", "thiserror", - "wasmparser 0.83.0", + "wasmparser 0.85.0", ] [[package]] @@ -12328,18 +12352,18 @@ dependencies = [ [[package]] name = "zstd" -version = "0.10.0+zstd.1.5.2" +version = "0.11.2+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b1365becbe415f3f0fcd024e2f7b45bacfb5bdd055f0dc113571394114e7bdd" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "4.1.4+zstd.1.5.2" +version = "5.0.2+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f7cd17c9af1a4d6c24beb1cc54b17e2ef7b593dc92f19e9d9acad8b182bbaee" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" dependencies = [ "libc", "zstd-sys", @@ -12347,9 +12371,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "1.6.3+zstd.1.5.2" +version = "2.0.1+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc49afa5c8d634e75761feda8c592051e7eeb4683ba827211eb0d731d3402ea8" +checksum = "9fd07cbbc53846d9145dbffdf6dd09a7a0aa52be46741825f5c97bdd4f73f12b" dependencies = [ "cc", "libc", diff --git a/client/executor/wasmtime/Cargo.toml b/client/executor/wasmtime/Cargo.toml index 83859ee61be34..9d40fb53fd559 100644 --- a/client/executor/wasmtime/Cargo.toml +++ b/client/executor/wasmtime/Cargo.toml @@ -18,7 +18,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0" } libc = "0.2.121" log = "0.4.17" parity-wasm = "0.42.0" -wasmtime = { version = "0.35.3", default-features = false, features = [ +wasmtime = { version = "0.38.0", default-features = false, features = [ "cache", "cranelift", "jitdump", diff --git a/client/executor/wasmtime/src/imports.rs b/client/executor/wasmtime/src/imports.rs index 4aad571029313..fae8d75854ef3 100644 --- a/client/executor/wasmtime/src/imports.rs +++ b/client/executor/wasmtime/src/imports.rs @@ -34,7 +34,7 @@ where { let mut pending_func_imports = HashMap::new(); for import_ty in module.imports() { - let name = import_name(&import_ty)?; + let name = import_ty.name(); if import_ty.module() != "env" { return Err(WasmError::Other(format!( @@ -121,13 +121,3 @@ impl<'a, 'b> sp_wasm_interface::HostFunctionRegistry for Registry<'a, 'b> { Ok(()) } } - -/// When the module linking proposal is supported the import's name can be `None`. -/// Because we are not using this proposal we could safely unwrap the name. -/// However, we opt for an error in order to avoid panics at all costs. -fn import_name<'a, 'b: 'a>(import: &'a ImportType<'b>) -> Result<&'a str, WasmError> { - let name = import.name().ok_or_else(|| { - WasmError::Other("The module linking proposal is not supported.".to_owned()) - })?; - Ok(name) -} diff --git a/client/executor/wasmtime/src/runtime.rs b/client/executor/wasmtime/src/runtime.rs index 8f6826653f27d..2184dd7466a53 100644 --- a/client/executor/wasmtime/src/runtime.rs +++ b/client/executor/wasmtime/src/runtime.rs @@ -323,7 +323,6 @@ fn common_config(semantics: &Semantics) -> std::result::Result Date: Fri, 24 Jun 2022 18:21:07 -0400 Subject: [PATCH 371/484] Avoid a duplicate block request when syncing from a fork (#11094) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Separate queueing blocks for import from removal * Add regression tests * Remove unnecessary log * Clear queued blocks when processed * Move check out of match block * Track queued block ranges * Update client/network/sync/src/blocks.rs * Update client/network/sync/src/blocks.rs * Update client/network/sync/src/blocks.rs * Update client/network/sync/src/blocks.rs * FMT Co-authored-by: Bastian Köcher Co-authored-by: Bastian Köcher --- client/consensus/common/src/import_queue.rs | 10 + client/network/sync/src/blocks.rs | 114 ++++++++--- client/network/sync/src/lib.rs | 200 ++++++++++++++++++-- 3 files changed, 281 insertions(+), 43 deletions(-) diff --git a/client/consensus/common/src/import_queue.rs b/client/consensus/common/src/import_queue.rs index a7b456191b000..c71e21ccd4b00 100644 --- a/client/consensus/common/src/import_queue.rs +++ b/client/consensus/common/src/import_queue.rs @@ -160,6 +160,16 @@ pub enum BlockImportStatus { ImportedUnknown(N, ImportedAux, Option), } +impl BlockImportStatus { + /// Returns the imported block number. + pub fn number(&self) -> &N { + match self { + BlockImportStatus::ImportedKnown(n, _) | + BlockImportStatus::ImportedUnknown(n, _, _) => n, + } + } +} + /// Block import error. #[derive(Debug, thiserror::Error)] pub enum BlockImportError { diff --git a/client/network/sync/src/blocks.rs b/client/network/sync/src/blocks.rs index b897184e7a44c..2cca13bfd7cc7 100644 --- a/client/network/sync/src/blocks.rs +++ b/client/network/sync/src/blocks.rs @@ -18,7 +18,7 @@ use crate::message; use libp2p::PeerId; -use log::trace; +use log::{debug, trace}; use sp_runtime::traits::{Block as BlockT, NumberFor, One}; use std::{ cmp, @@ -39,6 +39,7 @@ pub struct BlockData { enum BlockRangeState { Downloading { len: NumberFor, downloading: u32 }, Complete(Vec>), + Queued { len: NumberFor }, } impl BlockRangeState { @@ -46,6 +47,7 @@ impl BlockRangeState { match *self { Self::Downloading { len, .. } => len, Self::Complete(ref blocks) => (blocks.len() as u32).into(), + Self::Queued { len } => len, } } } @@ -56,12 +58,19 @@ pub struct BlockCollection { /// Downloaded blocks. blocks: BTreeMap, BlockRangeState>, peer_requests: HashMap>, + /// Block ranges downloaded and queued for import. + /// Maps start_hash => (start_num, end_num). + queued_blocks: HashMap, NumberFor)>, } impl BlockCollection { /// Create a new instance. pub fn new() -> Self { - Self { blocks: BTreeMap::new(), peer_requests: HashMap::new() } + Self { + blocks: BTreeMap::new(), + peer_requests: HashMap::new(), + queued_blocks: HashMap::new(), + } } /// Clear everything. @@ -170,29 +179,52 @@ impl BlockCollection { } /// Get a valid chain of blocks ordered in descending order and ready for importing into - /// blockchain. - pub fn drain(&mut self, from: NumberFor) -> Vec> { - let mut drained = Vec::new(); - let mut ranges = Vec::new(); + /// the blockchain. + pub fn ready_blocks(&mut self, from: NumberFor) -> Vec> { + let mut ready = Vec::new(); let mut prev = from; - for (start, range_data) in &mut self.blocks { - match range_data { - BlockRangeState::Complete(blocks) if *start <= prev => { - prev = *start + (blocks.len() as u32).into(); - // Remove all elements from `blocks` and add them to `drained` - drained.append(blocks); - ranges.push(*start); + for (&start, range_data) in &mut self.blocks { + if start > prev { + break + } + let len = match range_data { + BlockRangeState::Complete(blocks) => { + let len = (blocks.len() as u32).into(); + prev = start + len; + // Remove all elements from `blocks` and add them to `ready` + ready.append(blocks); + len }, + BlockRangeState::Queued { .. } => continue, _ => break, - } + }; + *range_data = BlockRangeState::Queued { len }; } - for r in ranges { - self.blocks.remove(&r); + if let Some(BlockData { block, .. }) = ready.first() { + self.queued_blocks + .insert(block.hash, (from, from + (ready.len() as u32).into())); + } + + trace!(target: "sync", "{} blocks ready for import", ready.len()); + ready + } + + pub fn clear_queued(&mut self, from_hash: &B::Hash) { + match self.queued_blocks.remove(from_hash) { + None => { + debug!(target: "sync", "Can't clear unknown queued blocks from {:?}", from_hash); + }, + Some((from, to)) => { + let mut block_num = from; + while block_num < to { + self.blocks.remove(&block_num); + block_num += One::one(); + } + trace!(target: "sync", "Cleared blocks from {:?} to {:?}", from, to); + }, } - trace!(target: "sync", "Drained {} blocks from {:?}", drained.len(), from); - drained } pub fn clear_peer_download(&mut self, who: &PeerId) { @@ -268,14 +300,14 @@ mod test { bc.clear_peer_download(&peer1); bc.insert(41, blocks[41..81].to_vec(), peer1.clone()); - assert_eq!(bc.drain(1), vec![]); + assert_eq!(bc.ready_blocks(1), vec![]); assert_eq!(bc.needed_blocks(peer1.clone(), 40, 150, 0, 1, 200), Some(121..151)); bc.clear_peer_download(&peer0); bc.insert(1, blocks[1..11].to_vec(), peer0.clone()); assert_eq!(bc.needed_blocks(peer0.clone(), 40, 150, 0, 1, 200), Some(11..41)); assert_eq!( - bc.drain(1), + bc.ready_blocks(1), blocks[1..11] .iter() .map(|b| BlockData { block: b.clone(), origin: Some(peer0.clone()) }) @@ -285,16 +317,16 @@ mod test { bc.clear_peer_download(&peer0); bc.insert(11, blocks[11..41].to_vec(), peer0.clone()); - let drained = bc.drain(12); + let ready = bc.ready_blocks(12); assert_eq!( - drained[..30], + ready[..30], blocks[11..41] .iter() .map(|b| BlockData { block: b.clone(), origin: Some(peer0.clone()) }) .collect::>()[..] ); assert_eq!( - drained[30..], + ready[30..], blocks[41..81] .iter() .map(|b| BlockData { block: b.clone(), origin: Some(peer1.clone()) }) @@ -308,17 +340,17 @@ mod test { bc.clear_peer_download(&peer1); bc.insert(121, blocks[121..150].to_vec(), peer1.clone()); - assert_eq!(bc.drain(80), vec![]); - let drained = bc.drain(81); + assert_eq!(bc.ready_blocks(80), vec![]); + let ready = bc.ready_blocks(81); assert_eq!( - drained[..40], + ready[..40], blocks[81..121] .iter() .map(|b| BlockData { block: b.clone(), origin: Some(peer2.clone()) }) .collect::>()[..] ); assert_eq!( - drained[40..], + ready[40..], blocks[121..150] .iter() .map(|b| BlockData { block: b.clone(), origin: Some(peer1.clone()) }) @@ -344,4 +376,32 @@ mod test { Some(100 + 128..100 + 128 + 128) ); } + + #[test] + fn no_duplicate_requests_on_fork() { + let mut bc = BlockCollection::new(); + assert!(is_empty(&bc)); + let peer = PeerId::random(); + + let blocks = generate_blocks(10); + + // count = 5, peer_best = 50, common = 39, max_parallel = 0, max_ahead = 200 + assert_eq!(bc.needed_blocks(peer.clone(), 5, 50, 39, 0, 200), Some(40..45)); + + // got a response on the request for `40..45` + bc.clear_peer_download(&peer); + bc.insert(40, blocks[..5].to_vec(), peer.clone()); + + // our "node" started on a fork, with its current best = 47, which is > common + let ready = bc.ready_blocks(48); + assert_eq!( + ready, + blocks[..5] + .iter() + .map(|b| BlockData { block: b.clone(), origin: Some(peer.clone()) }) + .collect::>() + ); + + assert_eq!(bc.needed_blocks(peer.clone(), 5, 50, 39, 0, 200), Some(45..50)); + } } diff --git a/client/network/sync/src/lib.rs b/client/network/sync/src/lib.rs index b027d77827374..4465e840c5ef5 100644 --- a/client/network/sync/src/lib.rs +++ b/client/network/sync/src/lib.rs @@ -1098,7 +1098,7 @@ where { self.blocks.insert(start_block, blocks, *who); } - self.drain_blocks() + self.ready_blocks() }, PeerSyncState::DownloadingGap(_) => { peer.state = PeerSyncState::Available; @@ -1112,7 +1112,7 @@ where gap = true; let blocks: Vec<_> = gap_sync .blocks - .drain(gap_sync.best_queued_number + One::one()) + .ready_blocks(gap_sync.best_queued_number + One::one()) .into_iter() .map(|block_data| { let justifications = @@ -1434,6 +1434,12 @@ where OnBlockData::Import(origin, new_blocks) } + fn update_peer_common_number(&mut self, peer_id: &PeerId, new_common: NumberFor) { + if let Some(peer) = self.peers.get_mut(peer_id) { + peer.update_common_number(new_common); + } + } + /// Handle a response from the remote to a justification request that we made. /// /// `request` must be the original request that triggered `response`. @@ -1515,9 +1521,12 @@ where for (_, hash) in &results { self.queue_blocks.remove(hash); } + if let Some(from_hash) = results.first().map(|(_, h)| h) { + self.blocks.clear_queued(from_hash); + } for (result, hash) in results { if has_error { - continue + break } if result.is_err() { @@ -1525,11 +1534,10 @@ where } match result { - Ok(BlockImportStatus::ImportedKnown(number, who)) => { - if let Some(peer) = who.and_then(|p| self.peers.get_mut(&p)) { - peer.update_common_number(number); - } - }, + Ok(BlockImportStatus::ImportedKnown(number, who)) => + if let Some(peer) = who { + self.update_peer_common_number(&peer, number); + }, Ok(BlockImportStatus::ImportedUnknown(number, aux, who)) => { if aux.clear_justification_requests { trace!( @@ -1558,8 +1566,8 @@ where } } - if let Some(peer) = who.and_then(|p| self.peers.get_mut(&p)) { - peer.update_common_number(number); + if let Some(peer) = who { + self.update_peer_common_number(&peer, number); } let state_sync_complete = self.state_sync.as_ref().map_or(false, |s| s.target() == hash); @@ -1993,11 +2001,11 @@ where // is either one further ahead or it's the one they just announced, if we know about it. if is_best { if known && self.best_queued_number >= number { - peer.update_common_number(number); + self.update_peer_common_number(&who, number); } else if announce.header.parent_hash() == &self.best_queued_hash || known_parent && self.best_queued_number >= number { - peer.update_common_number(number - One::one()); + self.update_peer_common_number(&who, number - One::one()); } } self.allowed_requests.add(&who); @@ -2071,7 +2079,7 @@ where target.peers.remove(who); !target.peers.is_empty() }); - let blocks = self.drain_blocks(); + let blocks = self.ready_blocks(); if !blocks.is_empty() { Some(self.validate_and_queue_blocks(blocks, false)) } else { @@ -2191,10 +2199,10 @@ where } } - /// Drain the downloaded block set up to the first gap. - fn drain_blocks(&mut self) -> Vec> { + /// Get the set of downloaded blocks that are ready to be queued for import. + fn ready_blocks(&mut self) -> Vec> { self.blocks - .drain(self.best_queued_number + One::one()) + .ready_blocks(self.best_queued_number + One::one()) .into_iter() .map(|block_data| { let justifications = block_data @@ -2981,6 +2989,25 @@ mod test { best_block_num += MAX_BLOCKS_TO_REQUEST as u32; + let _ = sync.on_blocks_processed( + MAX_BLOCKS_TO_REQUEST as usize, + MAX_BLOCKS_TO_REQUEST as usize, + resp_blocks + .iter() + .rev() + .map(|b| { + ( + Ok(BlockImportStatus::ImportedUnknown( + b.header().number().clone(), + Default::default(), + Some(peer_id1.clone()), + )), + b.hash(), + ) + }) + .collect(), + ); + resp_blocks .into_iter() .rev() @@ -3165,6 +3192,147 @@ mod test { ); } + #[test] + fn syncs_fork_without_duplicate_requests() { + sp_tracing::try_init_simple(); + + let mut client = Arc::new(TestClientBuilder::new().build()); + let blocks = (0..MAX_BLOCKS_TO_LOOK_BACKWARDS * 4) + .map(|_| build_block(&mut client, None, false)) + .collect::>(); + + let fork_blocks = { + let mut client = Arc::new(TestClientBuilder::new().build()); + let fork_blocks = blocks[..MAX_BLOCKS_TO_LOOK_BACKWARDS as usize * 2] + .into_iter() + .inspect(|b| block_on(client.import(BlockOrigin::Own, (*b).clone())).unwrap()) + .cloned() + .collect::>(); + + fork_blocks + .into_iter() + .chain( + (0..MAX_BLOCKS_TO_LOOK_BACKWARDS * 2 + 1) + .map(|_| build_block(&mut client, None, true)), + ) + .collect::>() + }; + + let info = client.info(); + + let mut sync = ChainSync::new( + SyncMode::Full, + client.clone(), + Box::new(DefaultBlockAnnounceValidator), + 5, + None, + ) + .unwrap(); + + let finalized_block = blocks[MAX_BLOCKS_TO_LOOK_BACKWARDS as usize * 2 - 1].clone(); + let just = (*b"TEST", Vec::new()); + client + .finalize_block(BlockId::Hash(finalized_block.hash()), Some(just)) + .unwrap(); + sync.update_chain_info(&info.best_hash, info.best_number); + + let peer_id1 = PeerId::random(); + + let common_block = blocks[MAX_BLOCKS_TO_LOOK_BACKWARDS as usize / 2].clone(); + // Connect the node we will sync from + sync.new_peer(peer_id1.clone(), common_block.hash(), *common_block.header().number()) + .unwrap(); + + send_block_announce(fork_blocks.last().unwrap().header().clone(), &peer_id1, &mut sync); + + let mut request = + get_block_request(&mut sync, FromBlock::Number(info.best_number), 1, &peer_id1); + + // Do the ancestor search + loop { + let block = &fork_blocks[unwrap_from_block_number(request.from.clone()) as usize - 1]; + let response = create_block_response(vec![block.clone()]); + + let on_block_data = sync.on_block_data(&peer_id1, Some(request), response).unwrap(); + request = match on_block_data.into_request() { + Some(req) => req.1, + // We found the ancenstor + None => break, + }; + + log::trace!(target: "sync", "Request: {:?}", request); + } + + // Now request and import the fork. + let mut best_block_num = finalized_block.header().number().clone() as u32; + let mut request = get_block_request( + &mut sync, + FromBlock::Number(MAX_BLOCKS_TO_REQUEST as u64 + best_block_num as u64), + MAX_BLOCKS_TO_REQUEST as u32, + &peer_id1, + ); + let last_block_num = *fork_blocks.last().unwrap().header().number() as u32 - 1; + while best_block_num < last_block_num { + let from = unwrap_from_block_number(request.from.clone()); + + let mut resp_blocks = fork_blocks[best_block_num as usize..from as usize].to_vec(); + resp_blocks.reverse(); + + let response = create_block_response(resp_blocks.clone()); + + let res = sync.on_block_data(&peer_id1, Some(request.clone()), response).unwrap(); + assert!(matches!( + res, + OnBlockData::Import(_, blocks) if blocks.len() == MAX_BLOCKS_TO_REQUEST + ),); + + best_block_num += MAX_BLOCKS_TO_REQUEST as u32; + + if best_block_num < last_block_num { + // make sure we're not getting a duplicate request in the time before the blocks are + // processed + request = get_block_request( + &mut sync, + FromBlock::Number(MAX_BLOCKS_TO_REQUEST as u64 + best_block_num as u64), + MAX_BLOCKS_TO_REQUEST as u32, + &peer_id1, + ); + } + + let _ = sync.on_blocks_processed( + MAX_BLOCKS_TO_REQUEST as usize, + MAX_BLOCKS_TO_REQUEST as usize, + resp_blocks + .iter() + .rev() + .map(|b| { + ( + Ok(BlockImportStatus::ImportedUnknown( + b.header().number().clone(), + Default::default(), + Some(peer_id1.clone()), + )), + b.hash(), + ) + }) + .collect(), + ); + + resp_blocks + .into_iter() + .rev() + .for_each(|b| block_on(client.import(BlockOrigin::Own, b)).unwrap()); + } + + // Request the tip + get_block_request( + &mut sync, + FromBlock::Hash(fork_blocks.last().unwrap().hash()), + 1, + &peer_id1, + ); + } + #[test] fn removes_target_fork_on_disconnect() { sp_tracing::try_init_simple(); From b3a4e0b6f614ee4a3316dac1aaa43bb9f3776491 Mon Sep 17 00:00:00 2001 From: Muharem Ismailov Date: Mon, 27 Jun 2022 16:33:58 +0200 Subject: [PATCH 372/484] Democracy.fast_track not allowed with zero voting period (#11666) * Democracy.fast_track not allowed with zero voting period * revert static parameter alter line * ensure voting period greater zero * update doc for fast_track * unit test: instant fast track to the next block referendum is backed * fix typos in comments --- frame/democracy/src/lib.rs | 9 ++-- frame/democracy/src/tests/fast_tracking.rs | 58 ++++++++++++++++++++++ 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/frame/democracy/src/lib.rs b/frame/democracy/src/lib.rs index 4e11c207657f0..443b8579116d0 100644 --- a/frame/democracy/src/lib.rs +++ b/frame/democracy/src/lib.rs @@ -600,6 +600,8 @@ pub mod pallet { MaxVotesReached, /// Maximum number of proposals reached. TooManyProposals, + /// Voting period too low + VotingPeriodLow, } #[pallet::hooks] @@ -800,8 +802,9 @@ pub mod pallet { /// The dispatch of this call must be `FastTrackOrigin`. /// /// - `proposal_hash`: The hash of the current external proposal. - /// - `voting_period`: The period that is allowed for voting on this proposal. Increased to - /// `FastTrackVotingPeriod` if too low. + /// - `voting_period`: The period that is allowed for voting on this proposal. + /// Must be always greater than zero. + /// For `FastTrackOrigin` must be equal or greater than `FastTrackVotingPeriod`. /// - `delay`: The number of block after voting has ended in approval and this should be /// enacted. This doesn't have a minimum amount. /// @@ -830,7 +833,7 @@ pub mod pallet { T::InstantOrigin::ensure_origin(ensure_instant)?; ensure!(T::InstantAllowed::get(), Error::::InstantNotAllowed); } - + ensure!(voting_period > T::BlockNumber::zero(), Error::::VotingPeriodLow); let (e_proposal_hash, threshold) = >::get().ok_or(Error::::ProposalMissing)?; ensure!( diff --git a/frame/democracy/src/tests/fast_tracking.rs b/frame/democracy/src/tests/fast_tracking.rs index 7a15bb8de4dac..caf83c6d46120 100644 --- a/frame/democracy/src/tests/fast_tracking.rs +++ b/frame/democracy/src/tests/fast_tracking.rs @@ -67,6 +67,10 @@ fn instant_referendum_works() { Error::::InstantNotAllowed ); INSTANT_ALLOWED.with(|v| *v.borrow_mut() = true); + assert_noop!( + Democracy::fast_track(Origin::signed(6), h, 0, 0), + Error::::VotingPeriodLow + ); assert_ok!(Democracy::fast_track(Origin::signed(6), h, 1, 0)); assert_eq!( Democracy::referendum_status(0), @@ -81,6 +85,60 @@ fn instant_referendum_works() { }); } +#[test] +fn instant_next_block_referendum_backed() { + new_test_ext().execute_with(|| { + // arrange + let start_block_number = 10; + let majority_origin_id = 3; + let instant_origin_id = 6; + let voting_period = 1; + let proposal_hash = set_balance_proposal_hash_and_note(2); + let delay = 2; // has no effect on test + + // init + System::set_block_number(start_block_number); + InstantAllowed::set(true); + + // propose with majority origin + assert_ok!(Democracy::external_propose_majority( + Origin::signed(majority_origin_id), + proposal_hash + )); + + // fast track with instant origin and voting period pointing to the next block + assert_ok!(Democracy::fast_track( + Origin::signed(instant_origin_id), + proposal_hash, + voting_period, + delay + )); + + // fetch the status of the only referendum at index 0 + assert_eq!( + Democracy::referendum_status(0), + Ok(ReferendumStatus { + end: start_block_number + voting_period, + proposal_hash, + threshold: VoteThreshold::SimpleMajority, + delay, + tally: Tally { ayes: 0, nays: 0, turnout: 0 }, + }) + ); + + // referendum expected to be baked with the start of the next block + next_block(); + + // assert no active referendums + assert_noop!(Democracy::referendum_status(0), Error::::ReferendumInvalid); + // the only referendum in the storage is finished and not approved + assert_eq!( + ReferendumInfoOf::::get(0).unwrap(), + ReferendumInfo::Finished { approved: false, end: start_block_number + voting_period } + ); + }); +} + #[test] fn fast_track_referendum_fails_when_no_simple_majority() { new_test_ext().execute_with(|| { From fc7bf70edb5297f6b0dc12edcfc943e78545e539 Mon Sep 17 00:00:00 2001 From: Koute Date: Tue, 28 Jun 2022 15:54:56 +0900 Subject: [PATCH 373/484] Prevent unsoundness in environments with broken `madvise(MADV_DONTNEED)` (#11722) * Prevend unsoundness in environments with broken `madvise(MADV_DONTNEED)` * Add the `std` feature to `rustix` dependency Apparently not having this breaks compilation on non-nightly toolchains. * Autodetect the page size when checking whether `madvise` works * Only make sure that the madvice check doesn't return `Err` --- Cargo.lock | 89 ++++++++++++++++++++--- client/executor/wasmtime/Cargo.toml | 4 ++ client/executor/wasmtime/src/runtime.rs | 15 ++-- client/executor/wasmtime/src/util.rs | 93 ++++++++++++++++++++++++- 4 files changed, 187 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 58604b47a87da..1bf1034005284 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3113,6 +3113,12 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec58677acfea8a15352d42fc87d11d63596ade9239e0a7c9352914417515dbe6" +[[package]] +name = "io-lifetimes" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24c3f4eff5495aee4c0399d7b6a0dc2b6e81be84242ffbfcf253ebacccc1d0cb" + [[package]] name = "ip_network" version = "0.4.1" @@ -3411,9 +3417,9 @@ checksum = "3576a87f2ba00f6f106fdfcd16db1d698d648a26ad8e0573cad8537c3c362d2a" [[package]] name = "libc" -version = "0.2.121" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" [[package]] name = "libgit2-sys" @@ -4108,6 +4114,12 @@ version = "0.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5284f00d480e1c39af34e72f8ad60b94f47007e3481cd3b731c1d67190ddc7b7" +[[package]] +name = "linux-raw-sys" +version = "0.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d" + [[package]] name = "lite-json" version = "0.1.3" @@ -6712,7 +6724,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-sys", + "windows-sys 0.32.0", ] [[package]] @@ -7850,12 +7862,26 @@ checksum = "938a344304321a9da4973b9ff4f9f8db9caf4597dfd9dda6a60b523340a0fff0" dependencies = [ "bitflags", "errno", - "io-lifetimes", + "io-lifetimes 0.5.3", "libc", - "linux-raw-sys", + "linux-raw-sys 0.0.42", "winapi", ] +[[package]] +name = "rustix" +version = "0.35.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef258c11e17f5c01979a10543a30a4e12faef6aab217a74266e747eefa3aed88" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes 0.7.2", + "libc", + "linux-raw-sys 0.0.46", + "windows-sys 0.36.1", +] + [[package]] name = "rustls" version = "0.20.2" @@ -8479,9 +8505,11 @@ dependencies = [ "cfg-if 1.0.0", "libc", "log", + "once_cell", "parity-scale-codec", "parity-wasm 0.42.2", "paste 1.0.6", + "rustix 0.35.6", "sc-allocator", "sc-executor-common", "sc-runtime-test", @@ -11972,7 +12000,7 @@ dependencies = [ "directories-next", "file-per-thread-logger", "log", - "rustix", + "rustix 0.33.7", "serde", "sha2 0.9.8", "toml", @@ -12038,7 +12066,7 @@ dependencies = [ "object 0.28.3", "region 2.2.0", "rustc-demangle", - "rustix", + "rustix 0.33.7", "serde", "target-lexicon", "thiserror", @@ -12056,7 +12084,7 @@ checksum = "e6d5dd480cc6dc0a401653e45b79796a3317f8228990d84bc2271bdaf0810071" dependencies = [ "lazy_static", "object 0.28.3", - "rustix", + "rustix 0.33.7", ] [[package]] @@ -12078,7 +12106,7 @@ dependencies = [ "more-asserts", "rand 0.8.4", "region 2.2.0", - "rustix", + "rustix 0.33.7", "thiserror", "wasmtime-environ", "wasmtime-jit-debug", @@ -12226,6 +12254,19 @@ dependencies = [ "windows_x86_64_msvc 0.32.0", ] +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", +] + [[package]] name = "windows_aarch64_msvc" version = "0.29.0" @@ -12238,6 +12279,12 @@ version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + [[package]] name = "windows_i686_gnu" version = "0.29.0" @@ -12250,6 +12297,12 @@ version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + [[package]] name = "windows_i686_msvc" version = "0.29.0" @@ -12262,6 +12315,12 @@ version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + [[package]] name = "windows_x86_64_gnu" version = "0.29.0" @@ -12274,6 +12333,12 @@ version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + [[package]] name = "windows_x86_64_msvc" version = "0.29.0" @@ -12286,6 +12351,12 @@ version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + [[package]] name = "winreg" version = "0.7.0" diff --git a/client/executor/wasmtime/Cargo.toml b/client/executor/wasmtime/Cargo.toml index 9d40fb53fd559..2dcfde378bb87 100644 --- a/client/executor/wasmtime/Cargo.toml +++ b/client/executor/wasmtime/Cargo.toml @@ -32,6 +32,10 @@ sp-runtime-interface = { version = "6.0.0", path = "../../../primitives/runtime- sp-sandbox = { version = "0.10.0-dev", path = "../../../primitives/sandbox" } sp-wasm-interface = { version = "6.0.0", features = ["wasmtime"], path = "../../../primitives/wasm-interface" } +[target.'cfg(target_os = "linux")'.dependencies] +rustix = { version = "0.35.6", default-features = false, features = ["std", "mm", "fs", "param"] } +once_cell = "1.12.0" + [dev-dependencies] wat = "1.0" sc-runtime-test = { version = "2.0.0", path = "../runtime-test" } diff --git a/client/executor/wasmtime/src/runtime.rs b/client/executor/wasmtime/src/runtime.rs index 2184dd7466a53..c63a802df07cc 100644 --- a/client/executor/wasmtime/src/runtime.rs +++ b/client/executor/wasmtime/src/runtime.rs @@ -21,7 +21,7 @@ use crate::{ host::HostState, instance_wrapper::{EntryPoint, InstanceWrapper}, - util, + util::{self, replace_strategy_if_broken}, }; use sc_allocator::FreeingBumpHeapAllocator; @@ -411,6 +411,7 @@ fn common_config(semantics: &Semantics) -> std::result::Result( code_supply_mode: CodeSupplyMode<'_>, - config: Config, + mut config: Config, ) -> std::result::Result where H: HostFunctions, { + replace_strategy_if_broken(&mut config.semantics.instantiation_strategy); + let mut wasmtime_config = common_config(&config.semantics)?; if let Some(ref cache_path) = config.cache_path { if let Err(reason) = setup_wasmtime_caching(cache_path, &mut wasmtime_config) { @@ -719,9 +723,12 @@ pub fn prepare_runtime_artifact( blob: RuntimeBlob, semantics: &Semantics, ) -> std::result::Result, WasmError> { - let blob = prepare_blob_for_compilation(blob, semantics)?; + let mut semantics = semantics.clone(); + replace_strategy_if_broken(&mut semantics.instantiation_strategy); + + let blob = prepare_blob_for_compilation(blob, &semantics)?; - let engine = Engine::new(&common_config(semantics)?) + let engine = Engine::new(&common_config(&semantics)?) .map_err(|e| WasmError::Other(format!("cannot create the engine: {}", e)))?; engine diff --git a/client/executor/wasmtime/src/util.rs b/client/executor/wasmtime/src/util.rs index 60ca598aee181..83745e21e86af 100644 --- a/client/executor/wasmtime/src/util.rs +++ b/client/executor/wasmtime/src/util.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::runtime::StoreData; +use crate::{runtime::StoreData, InstantiationStrategy}; use sc_executor_common::{ error::{Error, Result}, util::checked_range, @@ -98,3 +98,94 @@ pub(crate) fn write_memory_from( memory[range].copy_from_slice(data); Ok(()) } + +/// Checks whether the `madvise(MADV_DONTNEED)` works as expected. +/// +/// In certain environments (e.g. when running under the QEMU user-mode emulator) +/// this syscall is broken. +#[cfg(target_os = "linux")] +fn is_madvise_working() -> std::result::Result { + let page_size = rustix::param::page_size(); + + unsafe { + // Allocate two memory pages. + let pointer = rustix::mm::mmap_anonymous( + std::ptr::null_mut(), + 2 * page_size, + rustix::mm::ProtFlags::READ | rustix::mm::ProtFlags::WRITE, + rustix::mm::MapFlags::PRIVATE, + ) + .map_err(|error| format!("mmap failed: {}", error))?; + + // Dirty them both. + std::ptr::write_volatile(pointer.cast::(), b'A'); + std::ptr::write_volatile(pointer.cast::().add(page_size), b'B'); + + // Clear the first page. + let result_madvise = + rustix::mm::madvise(pointer, page_size, rustix::mm::Advice::LinuxDontNeed) + .map_err(|error| format!("madvise failed: {}", error)); + + // Fetch the values. + let value_1 = std::ptr::read_volatile(pointer.cast::()); + let value_2 = std::ptr::read_volatile(pointer.cast::().add(page_size)); + + let result_munmap = rustix::mm::munmap(pointer, 2 * page_size) + .map_err(|error| format!("munmap failed: {}", error)); + + result_madvise?; + result_munmap?; + + // Verify that the first page was cleared, while the second one was not. + Ok(value_1 == 0 && value_2 == b'B') + } +} + +#[cfg(test)] +#[cfg(target_os = "linux")] +#[test] +fn test_is_madvise_working_check_does_not_fail() { + assert!(is_madvise_working().is_ok()); +} + +/// Checks whether a given instantiation strategy can be safely used, and replaces +/// it with a slower (but sound) alternative if it isn't. +#[cfg(target_os = "linux")] +pub(crate) fn replace_strategy_if_broken(strategy: &mut InstantiationStrategy) { + let replacement_strategy = match *strategy { + // These strategies don't need working `madvise`. + InstantiationStrategy::Pooling | InstantiationStrategy::RecreateInstance => return, + + // These strategies require a working `madvise` to be sound. + InstantiationStrategy::PoolingCopyOnWrite => InstantiationStrategy::Pooling, + InstantiationStrategy::RecreateInstanceCopyOnWrite | + InstantiationStrategy::LegacyInstanceReuse => InstantiationStrategy::RecreateInstance, + }; + + use once_cell::sync::OnceCell; + static IS_OK: OnceCell = OnceCell::new(); + + let is_ok = IS_OK.get_or_init(|| { + let is_ok = match is_madvise_working() { + Ok(is_ok) => is_ok, + Err(error) => { + // This should never happen. + log::warn!("Failed to check whether `madvise(MADV_DONTNEED)` works: {}", error); + false + } + }; + + if !is_ok { + log::warn!("You're running on a system with a broken `madvise(MADV_DONTNEED)` implementation. This will result in lower performance."); + } + + is_ok + }); + + if !is_ok { + *strategy = replacement_strategy; + } +} + +#[cfg(not(target_os = "linux"))] +pub(crate) fn replace_strategy_if_broken(_: &mut InstantiationStrategy) {} From 289e2b53d56a3fce55adf54255a80fdce75d5d3b Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Wed, 29 Jun 2022 17:39:56 +0100 Subject: [PATCH 374/484] Refund weight in `system::fillBlock` (#11754) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix * pushed * node: fix fee multiplier test Co-authored-by: André Silva --- Cargo.lock | 1 + bin/node/executor/Cargo.toml | 1 + bin/node/executor/tests/fees.rs | 6 ++++-- frame/system/src/lib.rs | 13 +++++++++++-- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1bf1034005284..d68330cfb15c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4732,6 +4732,7 @@ dependencies = [ "pallet-balances", "pallet-contracts", "pallet-im-online", + "pallet-sudo", "pallet-timestamp", "pallet-transaction-payment", "pallet-treasury", diff --git a/bin/node/executor/Cargo.toml b/bin/node/executor/Cargo.toml index 5fbf59a74fdcd..6143ea3bd8c42 100644 --- a/bin/node/executor/Cargo.toml +++ b/bin/node/executor/Cargo.toml @@ -34,6 +34,7 @@ node-testing = { version = "3.0.0-dev", path = "../testing" } pallet-balances = { version = "4.0.0-dev", path = "../../../frame/balances" } pallet-contracts = { version = "4.0.0-dev", path = "../../../frame/contracts" } pallet-im-online = { version = "4.0.0-dev", path = "../../../frame/im-online" } +pallet-sudo = { version = "4.0.0-dev", path = "../../../frame/sudo" } pallet-timestamp = { version = "4.0.0-dev", path = "../../../frame/timestamp" } pallet-treasury = { version = "4.0.0-dev", path = "../../../frame/treasury" } pallet-transaction-payment = { version = "4.0.0-dev", path = "../../../frame/transaction-payment" } diff --git a/bin/node/executor/tests/fees.rs b/bin/node/executor/tests/fees.rs index cf794bae1d307..e1550071d3006 100644 --- a/bin/node/executor/tests/fees.rs +++ b/bin/node/executor/tests/fees.rs @@ -58,8 +58,10 @@ fn fee_multiplier_increases_and_decreases_on_big_weight() { }, CheckedExtrinsic { signed: Some((charlie(), signed_extra(0, 0))), - function: Call::System(frame_system::Call::fill_block { - ratio: Perbill::from_percent(60), + function: Call::Sudo(pallet_sudo::Call::sudo { + call: Box::new(Call::System(frame_system::Call::fill_block { + ratio: Perbill::from_percent(60), + })), }), }, ], diff --git a/frame/system/src/lib.rs b/frame/system/src/lib.rs index 370a802665918..3c6f514808b5d 100644 --- a/frame/system/src/lib.rs +++ b/frame/system/src/lib.rs @@ -197,6 +197,7 @@ impl, MaxOverflow: Get> ConsumerLimits for (MaxNormal, pub mod pallet { use crate::{self as frame_system, pallet_prelude::*, *}; use frame_support::pallet_prelude::*; + use sp_runtime::DispatchErrorWithPostInfo; /// System configuration trait. Implemented by runtime. #[pallet::config] @@ -371,8 +372,16 @@ pub mod pallet { // that's not possible at present (since it's within the pallet macro). #[pallet::weight(*_ratio * T::BlockWeights::get().max_block)] pub fn fill_block(origin: OriginFor, _ratio: Perbill) -> DispatchResultWithPostInfo { - ensure_root(origin)?; - Ok(().into()) + match ensure_root(origin) { + Ok(_) => Ok(().into()), + Err(_) => { + // roughly same as a 4 byte remark since perbill is u32. + Err(DispatchErrorWithPostInfo { + post_info: Some(T::SystemWeightInfo::remark(4u32)).into(), + error: DispatchError::BadOrigin, + }) + }, + } } /// Make some on-chain remark. From 2ec538197806861a22032eaae48b2483bf8f7db9 Mon Sep 17 00:00:00 2001 From: Sergej Sakac <73715684+Szegoo@users.noreply.github.com> Date: Thu, 30 Jun 2022 12:02:57 +0200 Subject: [PATCH 375/484] nomination-pools fix (#11748) * Nomination pool fix * fmt --- frame/nomination-pools/src/lib.rs | 8 ++++---- frame/nomination-pools/src/mock.rs | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 19155a51405b0..d43d43c806d61 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -43,7 +43,7 @@ //! //! ### Join //! -//! A account can stake funds with a nomination pool by calling [`Call::join`]. +//! An account can stake funds with a nomination pool by calling [`Call::join`]. //! //! ### Claim rewards //! @@ -87,7 +87,7 @@ //! [`Call::unbond`] and [`Call::withdraw_unbonded`]. Once a pool is in destroying state, it //! cannot be reverted to another state. //! -//! A pool has 3 administrative roles (see [`PoolRoles`]): +//! A pool has 4 administrative roles (see [`PoolRoles`]): //! //! * Depositor: creates the pool and is the initial member. They can only leave the pool once all //! other members have left. Once they fully leave the pool is destroyed. @@ -297,8 +297,8 @@ //! * PoolMembers cannot vote with their staked funds because they are transferred into the pools //! account. In the future this can be overcome by allowing the members to vote with their bonded //! funds via vote splitting. -//! * PoolMembers cannot quickly transfer to another pool if they do no like nominations, instead -//! they must wait for the unbonding duration. +//! * PoolMembers cannot quickly transfer to another pool if they do not like the nominations, +//! instead they must wait for the unbonding duration. //! //! # Runtime builder warnings //! diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index a3020e5add044..7d71efc3be04d 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -111,6 +111,7 @@ impl sp_staking::StakingInterface for StakingMock { Ok(()) } + #[cfg(feature = "runtime-benchmarks")] fn nominations(_: Self::AccountId) -> Option> { Nominations::get() } From c206bfacccceb8a28fc5b46590bb91942b8f6d27 Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Tue, 5 Jul 2022 09:48:37 +0200 Subject: [PATCH 376/484] [ci] Remove polkadot-companion-labels GHA (#11774) --- .../workflows/polkadot-companion-labels.yml | 32 ------------------- 1 file changed, 32 deletions(-) delete mode 100644 .github/workflows/polkadot-companion-labels.yml diff --git a/.github/workflows/polkadot-companion-labels.yml b/.github/workflows/polkadot-companion-labels.yml deleted file mode 100644 index 0a5af09358524..0000000000000 --- a/.github/workflows/polkadot-companion-labels.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Check Polkadot Companion and Label - -on: - pull_request: - types: [opened, synchronize] - -jobs: - check_status: - runs-on: ubuntu-latest - steps: - - name: Monitor the status of the gitlab-check-companion-build job - uses: s3krit/await-status-action@v1.0.1 - id: 'check-companion-status' - with: - authToken: ${{ secrets.GITHUB_TOKEN }} - ref: ${{ github.event.pull_request.head.sha }} - contexts: 'continuous-integration/gitlab-check-dependent-polkadot' - timeout: 1800 - notPresentTimeout: 3600 # It can take quite a while before the job starts on Gitlab when the CI queue is large - failureStates: failure - interruptedStates: error # Error = job was probably cancelled. We don't want to label the PR in that case - pollInterval: 30 - - name: Label success - uses: andymckay/labeler@master - if: steps.check-companion-status.outputs.result == 'success' - with: - remove-labels: 'A7-needspolkadotpr' - - name: Label failure - uses: andymckay/labeler@master - if: steps.check-companion-status.outputs.result == 'failure' - with: - add-labels: 'A7-needspolkadotpr' From 9a5fde1ba8fe7d8ca744c341e00d496bf35a65ff Mon Sep 17 00:00:00 2001 From: GreenBaneling | Supercolony Date: Tue, 5 Jul 2022 11:12:16 +0100 Subject: [PATCH 377/484] [contracts] Fixed the bug with transfer value for delegate call (#11771) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fixed the bug with transfer value. * Apply suggestions from code review Co-authored-by: Alexander Theißen * Moved check into `initial_transfer` * Fmt Co-authored-by: Alexander Theißen --- frame/contracts/src/exec.rs | 86 ++++++++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 2 deletions(-) diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index ec7e1e9335182..d93c646872c6f 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -797,7 +797,7 @@ where )?; } - // Every call or instantiate also optionally transferres balance. + // Every non delegate call or instantiate also optionally transfers the balance. self.initial_transfer()?; // Call into the wasm blob. @@ -959,8 +959,14 @@ where // The transfer as performed by a call or instantiate. fn initial_transfer(&self) -> DispatchResult { let frame = self.top_frame(); - let value = frame.value_transferred; + // If it is a delegate call, then we've already transferred tokens in the + // last non-delegate frame. + if frame.delegate_caller.is_some() { + return Ok(()) + } + + let value = frame.value_transferred; Self::transfer(ExistenceRequirement::KeepAlive, self.caller(), &frame.account_id, value) } @@ -1560,6 +1566,82 @@ mod tests { }); } + #[test] + fn correct_transfer_on_call() { + let origin = ALICE; + let dest = BOB; + let value = 55; + + let success_ch = MockLoader::insert(Call, move |ctx, _| { + assert_eq!(ctx.ext.value_transferred(), value); + Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes(Vec::new()) }) + }); + + ExtBuilder::default().build().execute_with(|| { + let schedule = ::Schedule::get(); + place_contract(&dest, success_ch); + set_balance(&origin, 100); + let balance = get_balance(&dest); + let mut storage_meter = storage::meter::Meter::new(&origin, Some(0), 55).unwrap(); + + let _ = MockStack::run_call( + origin.clone(), + dest.clone(), + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + &schedule, + value, + vec![], + None, + ) + .unwrap(); + + assert_eq!(get_balance(&origin), 100 - value); + assert_eq!(get_balance(&dest), balance + value); + }); + } + + #[test] + fn correct_transfer_on_delegate_call() { + let origin = ALICE; + let dest = BOB; + let value = 35; + + let success_ch = MockLoader::insert(Call, move |ctx, _| { + assert_eq!(ctx.ext.value_transferred(), value); + Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes(Vec::new()) }) + }); + + let delegate_ch = MockLoader::insert(Call, move |ctx, _| { + assert_eq!(ctx.ext.value_transferred(), value); + let _ = ctx.ext.delegate_call(success_ch, Vec::new())?; + Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Bytes(Vec::new()) }) + }); + + ExtBuilder::default().build().execute_with(|| { + let schedule = ::Schedule::get(); + place_contract(&dest, delegate_ch); + set_balance(&origin, 100); + let balance = get_balance(&dest); + let mut storage_meter = storage::meter::Meter::new(&origin, Some(0), 55).unwrap(); + + let _ = MockStack::run_call( + origin.clone(), + dest.clone(), + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + &schedule, + value, + vec![], + None, + ) + .unwrap(); + + assert_eq!(get_balance(&origin), 100 - value); + assert_eq!(get_balance(&dest), balance + value); + }); + } + #[test] fn changes_are_reverted_on_failing_call() { // This test verifies that changes are reverted on a call which fails (or equally, returns From 9baef283646adeec8e7dc1df8f02c032b864c888 Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Thu, 7 Jul 2022 16:24:06 +0200 Subject: [PATCH 378/484] Fix clearing queued blocks in the sync module (#11763) --- client/network/sync/src/blocks.rs | 21 ++++++--------- client/network/sync/src/lib.rs | 43 +++++++++++++++++++------------ 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/client/network/sync/src/blocks.rs b/client/network/sync/src/blocks.rs index 2cca13bfd7cc7..8d9b039b14fba 100644 --- a/client/network/sync/src/blocks.rs +++ b/client/network/sync/src/blocks.rs @@ -18,7 +18,7 @@ use crate::message; use libp2p::PeerId; -use log::{debug, trace}; +use log::trace; use sp_runtime::traits::{Block as BlockT, NumberFor, One}; use std::{ cmp, @@ -212,18 +212,13 @@ impl BlockCollection { } pub fn clear_queued(&mut self, from_hash: &B::Hash) { - match self.queued_blocks.remove(from_hash) { - None => { - debug!(target: "sync", "Can't clear unknown queued blocks from {:?}", from_hash); - }, - Some((from, to)) => { - let mut block_num = from; - while block_num < to { - self.blocks.remove(&block_num); - block_num += One::one(); - } - trace!(target: "sync", "Cleared blocks from {:?} to {:?}", from, to); - }, + if let Some((from, to)) = self.queued_blocks.remove(from_hash) { + let mut block_num = from; + while block_num < to { + self.blocks.remove(&block_num); + block_num += One::one(); + } + trace!(target: "sync", "Cleared blocks from {:?} to {:?}", from, to); } } diff --git a/client/network/sync/src/lib.rs b/client/network/sync/src/lib.rs index 4465e840c5ef5..5b5216c745c98 100644 --- a/client/network/sync/src/lib.rs +++ b/client/network/sync/src/lib.rs @@ -1520,9 +1520,7 @@ where let mut has_error = false; for (_, hash) in &results { self.queue_blocks.remove(hash); - } - if let Some(from_hash) = results.first().map(|(_, h)| h) { - self.blocks.clear_queued(from_hash); + self.blocks.clear_queued(hash); } for (result, hash) in results { if has_error { @@ -3299,23 +3297,34 @@ mod test { ); } + let mut notify_imported: Vec<_> = resp_blocks + .iter() + .rev() + .map(|b| { + ( + Ok(BlockImportStatus::ImportedUnknown( + b.header().number().clone(), + Default::default(), + Some(peer_id1.clone()), + )), + b.hash(), + ) + }) + .collect(); + + // The import queue may send notifications in batches of varying size. So we simulate + // this here by splitting the batch into 2 notifications. + let second_batch = notify_imported.split_off(notify_imported.len() / 2); let _ = sync.on_blocks_processed( MAX_BLOCKS_TO_REQUEST as usize, MAX_BLOCKS_TO_REQUEST as usize, - resp_blocks - .iter() - .rev() - .map(|b| { - ( - Ok(BlockImportStatus::ImportedUnknown( - b.header().number().clone(), - Default::default(), - Some(peer_id1.clone()), - )), - b.hash(), - ) - }) - .collect(), + notify_imported, + ); + + let _ = sync.on_blocks_processed( + MAX_BLOCKS_TO_REQUEST as usize, + MAX_BLOCKS_TO_REQUEST as usize, + second_batch, ); resp_blocks From 13bf42e6a8387e26bd9c9ddea9d0e45f6348e97c Mon Sep 17 00:00:00 2001 From: Adrian Catangiu Date: Thu, 7 Jul 2022 18:02:37 +0300 Subject: [PATCH 379/484] pallet-mmr: handle forks without collisions in offchain storage (#11594) * pallet-mmr: fix some typos * pallet-mmr: make the MMR resilient to chain forks * pallet-mmr: get hash for block that added node * beefy-mmr: add debug logging * add explanatory comment * account for block offset of pallet activation * add support for finding all nodes added by leaf * minor improvements * add helper to return all nodes added to mmr with a leaf append * simplify leaf_node_index_to_leaf_index summing the (shifted) differences in peak positions adds up to the (shifted) final position, so don't need to fold over positions. * dead fish: this also doesn't work The idea was to keep a rolling window of `(parent_hash, pos)` leaf entries in the offchain db, with the window matching the one that provides `block_num -> block_hash` mappings in `frame_system`. Once a leaf exits the window it would be "canonicalized" by switching its offchain db key from `(parent_hash, pos)` to simple `pos`. This doesn't work however because there's no way to get leaf contents from offchain db while in runtime context.. so no way to get+clear+set leaf to change its key in offchain db. Ideas: 1. move the "canonicalization" logic to offchain worker 2. enhance IndexingApi with "offchain::move(old_key, new_key)" This is weird, but correct, deterministic and safe AFAICT, so it could be exposed to runtime. * simplify rightmost_leaf_node_index_from_pos * minor fix * move leaf canonicalization to offchain worker * move storage related code to storage.rs * on offchain reads use canonic key for old leaves * fix offchain worker write using canon key * fix pallet-mmr tests * add documentation and fix logging * add offchain mmr canonicalization test * test canon + generate + verify * fix pallet-beefy-mmr tests * implement review suggestions * improve test * pallet-mmr: add offchain pruning of forks * pallet-mmr: improve offchain pruning Instead of keeping pruning map as single blob in offchain db, keep individual parent-hash lists with block-num identifier as part of the offchain key. Signed-off-by: acatangiu * pallet-mmr: improve MMRStore::get() Do the math and retrieve node using correct (canon or non-canon) offchain db key, instead of blindly looking in both canon and non-canon offchain db locations for each node. Still fallback on looking at both if for any reason it's not where expected. Signed-off-by: acatangiu * pallet-mmr: storage: improve logs * fix tests: correctly persist overlay runtime indexing API works on overlay, whereas offchain context bypasses overlay, so for loops > canon-window, canon would fail. * pallet-mmr: fix numeric typo in test * add comment around LeafData requirements Signed-off-by: acatangiu Co-authored-by: Robert Hambrock --- frame/beefy-mmr/src/tests.rs | 22 +- frame/merkle-mountain-range/src/lib.rs | 71 ++++- .../merkle-mountain-range/src/mmr/storage.rs | 228 +++++++++++++- frame/merkle-mountain-range/src/mmr/utils.rs | 68 +++++ frame/merkle-mountain-range/src/tests.rs | 287 ++++++++++++++++-- primitives/merkle-mountain-range/src/lib.rs | 12 +- 6 files changed, 635 insertions(+), 53 deletions(-) diff --git a/frame/beefy-mmr/src/tests.rs b/frame/beefy-mmr/src/tests.rs index d9cd8c8a5d8c8..eaa50004ae848 100644 --- a/frame/beefy-mmr/src/tests.rs +++ b/frame/beefy-mmr/src/tests.rs @@ -44,16 +44,12 @@ pub fn beefy_log(log: ConsensusLog) -> DigestItem { DigestItem::Consensus(BEEFY_ENGINE_ID, log.encode()) } -fn offchain_key(pos: usize) -> Vec { - (::INDEXING_PREFIX, pos as u64).encode() -} - -fn read_mmr_leaf(ext: &mut TestExternalities, index: usize) -> MmrLeaf { +fn read_mmr_leaf(ext: &mut TestExternalities, key: Vec) -> MmrLeaf { type Node = pallet_mmr::primitives::DataOrHash; ext.persist_offchain_overlay(); let offchain_db = ext.offchain_db(); offchain_db - .get(&offchain_key(index)) + .get(&key) .map(|d| Node::decode(&mut &*d).unwrap()) .map(|n| match n { Node::Data(d) => d, @@ -105,12 +101,17 @@ fn should_contain_mmr_digest() { #[test] fn should_contain_valid_leaf_data() { + fn node_offchain_key(parent_hash: H256, pos: usize) -> Vec { + (::INDEXING_PREFIX, parent_hash, pos as u64).encode() + } + let mut ext = new_test_ext(vec![1, 2, 3, 4]); - ext.execute_with(|| { + let parent_hash = ext.execute_with(|| { init_block(1); + >::parent_hash() }); - let mmr_leaf = read_mmr_leaf(&mut ext, 0); + let mmr_leaf = read_mmr_leaf(&mut ext, node_offchain_key(parent_hash, 0)); assert_eq!( mmr_leaf, MmrLeaf { @@ -128,11 +129,12 @@ fn should_contain_valid_leaf_data() { ); // build second block on top - ext.execute_with(|| { + let parent_hash = ext.execute_with(|| { init_block(2); + >::parent_hash() }); - let mmr_leaf = read_mmr_leaf(&mut ext, 1); + let mmr_leaf = read_mmr_leaf(&mut ext, node_offchain_key(parent_hash, 1)); assert_eq!( mmr_leaf, MmrLeaf { diff --git a/frame/merkle-mountain-range/src/lib.rs b/frame/merkle-mountain-range/src/lib.rs index d6cf3240692fc..9274e8e72c508 100644 --- a/frame/merkle-mountain-range/src/lib.rs +++ b/frame/merkle-mountain-range/src/lib.rs @@ -58,7 +58,10 @@ use codec::Encode; use frame_support::weights::Weight; -use sp_runtime::traits::{self, One, Saturating}; +use sp_runtime::{ + traits::{self, One, Saturating}, + SaturatedConversion, +}; #[cfg(any(feature = "runtime-benchmarks", test))] mod benchmarking; @@ -116,12 +119,12 @@ pub mod pallet { /// Prefix for elements stored in the Off-chain DB via Indexing API. /// /// Each node of the MMR is inserted both on-chain and off-chain via Indexing API. - /// The former does not store full leaf content, just it's compact version (hash), + /// The former does not store full leaf content, just its compact version (hash), /// and some of the inner mmr nodes might be pruned from on-chain storage. /// The latter will contain all the entries in their full form. /// /// Each node is stored in the Off-chain DB under key derived from the - /// [`Self::INDEXING_PREFIX`] and it's in-tree index (MMR position). + /// [`Self::INDEXING_PREFIX`] and its in-tree index (MMR position). const INDEXING_PREFIX: &'static [u8]; /// A hasher type for MMR. @@ -162,6 +165,12 @@ pub mod pallet { /// /// Note that the leaf at each block MUST be unique. You may want to include a block hash or /// block number as an easiest way to ensure that. + /// Also note that the leaf added by each block is expected to only reference data coming + /// from ancestor blocks (leaves are saved offchain using `(parent_hash, pos)` key to be + /// fork-resistant, as such conflicts could only happen on 1-block deep forks, which means + /// two forks with identical line of ancestors compete to write the same offchain key, but + /// that's fine as long as leaves only contain data coming from ancestors - conflicting + /// writes are identical). type LeafData: primitives::LeafDataProvider; /// A hook to act on the new MMR root. @@ -215,8 +224,31 @@ pub mod pallet { >::put(root); let peaks_after = mmr::utils::NodesUtils::new(leaves).number_of_peaks(); + T::WeightInfo::on_initialize(peaks_before.max(peaks_after)) } + + fn offchain_worker(n: T::BlockNumber) { + use mmr::storage::{OffchainStorage, Storage}; + // MMR pallet uses offchain storage to hold full MMR and leaves. + // The leaves are saved under fork-unique keys `(parent_hash, pos)`. + // MMR Runtime depends on `frame_system::block_hash(block_num)` mappings to find + // parent hashes for particular nodes or leaves. + // This MMR offchain worker function moves a rolling window of the same size + // as `frame_system::block_hash` map, where nodes/leaves added by blocks that are just + // about to exit the window are "canonicalized" so that their offchain key no longer + // depends on `parent_hash` therefore on access to `frame_system::block_hash`. + // + // This approach works to eliminate fork-induced leaf collisions in offchain db, + // under the assumption that no fork will be deeper than `frame_system::BlockHashCount` + // blocks (2400 blocks on Polkadot, Kusama, Rococo, etc): + // entries pertaining to block `N` where `N < current-2400` are moved to a key based + // solely on block number. The only way to have collisions is if two competing forks + // are deeper than 2400 blocks and they both "canonicalize" their view of block `N`. + // Once a block is canonicalized, all MMR entries pertaining to sibling blocks from + // other forks are pruned from offchain db. + Storage::>::canonicalize_and_prune(n); + } } } @@ -254,9 +286,38 @@ where } impl, I: 'static> Pallet { - fn offchain_key(pos: NodeIndex) -> sp_std::prelude::Vec { + /// Build offchain key from `parent_hash` of block that originally added node `pos` to MMR. + /// + /// This combination makes the offchain (key,value) entry resilient to chain forks. + fn node_offchain_key( + parent_hash: ::Hash, + pos: NodeIndex, + ) -> sp_std::prelude::Vec { + (T::INDEXING_PREFIX, parent_hash, pos).encode() + } + + /// Build canonical offchain key for node `pos` in MMR. + /// + /// Used for nodes added by now finalized blocks. + fn node_canon_offchain_key(pos: NodeIndex) -> sp_std::prelude::Vec { (T::INDEXING_PREFIX, pos).encode() } + + /// Provide the parent number for the block that added `leaf_index` to the MMR. + fn leaf_index_to_parent_block_num( + leaf_index: LeafIndex, + leaves_count: LeafIndex, + ) -> ::BlockNumber { + // leaves are zero-indexed and were added one per block since pallet activation, + // while block numbers are one-indexed, so block number that added `leaf_idx` is: + // `block_num = block_num_when_pallet_activated + leaf_idx + 1` + // `block_num = (current_block_num - leaves_count) + leaf_idx + 1` + // `parent_block_num = current_block_num - leaves_count + leaf_idx`. + >::block_number() + .saturating_sub(leaves_count.saturated_into()) + .saturating_add(leaf_index.saturated_into()) + } + /// Generate a MMR proof for the given `leaf_indices`. /// /// Note this method can only be used from an off-chain context @@ -264,7 +325,7 @@ impl, I: 'static> Pallet { /// all the leaves to be present. /// It may return an error or panic if used incorrectly. pub fn generate_batch_proof( - leaf_indices: Vec, + leaf_indices: Vec, ) -> Result< (Vec>, primitives::BatchProof<>::Hash>), primitives::Error, diff --git a/frame/merkle-mountain-range/src/mmr/storage.rs b/frame/merkle-mountain-range/src/mmr/storage.rs index 535057ca80da7..d31262d4d7f2f 100644 --- a/frame/merkle-mountain-range/src/mmr/storage.rs +++ b/frame/merkle-mountain-range/src/mmr/storage.rs @@ -18,8 +18,11 @@ //! A MMR storage implementations. use codec::Encode; +use frame_support::traits::Get; use mmr_lib::helper; -use sp_io::offchain_index; +use sp_core::offchain::StorageKind; +use sp_io::{offchain, offchain_index}; +use sp_runtime::traits::UniqueSaturatedInto; use sp_std::iter::Peekable; #[cfg(not(feature = "std"))] use sp_std::prelude::*; @@ -46,6 +49,51 @@ pub struct RuntimeStorage; /// DOES NOT support adding new items to the MMR. pub struct OffchainStorage; +/// Suffix of key for the 'pruning_map'. +/// +/// Nodes and leaves are initially saved under fork-specific keys in offchain db, +/// eventually they are "canonicalized" and this map is used to prune non-canon entries. +const OFFCHAIN_PRUNING_MAP_KEY_SUFFIX: &str = "pruning_map"; + +/// Used to store offchain mappings of `BlockNumber -> Vec[Hash]` to track all forks. +/// Size of this offchain map is at most `frame_system::BlockHashCount`, its entries are pruned +/// as part of the mechanism that prunes the forks this map tracks. +pub(crate) struct PruningMap(sp_std::marker::PhantomData<(T, I)>); +impl PruningMap +where + T: Config, + I: 'static, +{ + pub(crate) fn pruning_map_offchain_key(block: T::BlockNumber) -> sp_std::prelude::Vec { + (T::INDEXING_PREFIX, block, OFFCHAIN_PRUNING_MAP_KEY_SUFFIX).encode() + } + + /// Append `hash` to the list of parent hashes for `block` in offchain db. + pub fn append(block: T::BlockNumber, hash: ::Hash) { + let map_key = Self::pruning_map_offchain_key(block); + offchain::local_storage_get(StorageKind::PERSISTENT, &map_key) + .and_then(|v| codec::Decode::decode(&mut &*v).ok()) + .or_else(|| Some(Vec::<::Hash>::new())) + .map(|mut parents| { + parents.push(hash); + offchain::local_storage_set( + StorageKind::PERSISTENT, + &map_key, + &Encode::encode(&parents), + ); + }); + } + + /// Remove list of parent hashes for `block` from offchain db and return it. + pub fn remove(block: T::BlockNumber) -> Option::Hash>> { + let map_key = Self::pruning_map_offchain_key(block); + offchain::local_storage_get(StorageKind::PERSISTENT, &map_key).and_then(|v| { + offchain::local_storage_clear(StorageKind::PERSISTENT, &map_key); + codec::Decode::decode(&mut &*v).ok() + }) + } +} + /// A storage layer for MMR. /// /// There are two different implementations depending on the use case. @@ -58,6 +106,109 @@ impl Default for Storage { } } +impl Storage +where + T: Config, + I: 'static, + L: primitives::FullLeaf, +{ + /// Move nodes and leaves added by block `N` in offchain db from _fork-aware key_ to + /// _canonical key_, + /// where `N` is `frame_system::BlockHashCount` blocks behind current block number. + /// + /// This "canonicalization" process is required because the _fork-aware key_ value depends + /// on `frame_system::block_hash(block_num)` map which only holds the last + /// `frame_system::BlockHashCount` blocks. + /// + /// For the canonicalized block, prune all nodes pertaining to other forks from offchain db. + /// + /// Should only be called from offchain context, because it requires both read and write + /// access to offchain db. + pub(crate) fn canonicalize_and_prune(block: T::BlockNumber) { + // Add "block_num -> hash" mapping to offchain db, + // with all forks pushing hashes to same entry (same block number). + let parent_hash = >::parent_hash(); + PruningMap::::append(block, parent_hash); + + // Effectively move a rolling window of fork-unique leaves. Once out of the window, leaves + // are "canonicalized" in offchain by moving them under `Pallet::node_canon_offchain_key`. + let leaves = NumberOfLeaves::::get(); + let window_size = + ::BlockHashCount::get().unique_saturated_into(); + if leaves >= window_size { + // Move the rolling window towards the end of `block_num->hash` mappings available + // in the runtime: we "canonicalize" the leaf at the end, + let to_canon_leaf = leaves.saturating_sub(window_size); + // and all the nodes added by that leaf. + let to_canon_nodes = NodesUtils::right_branch_ending_in_leaf(to_canon_leaf); + frame_support::log::debug!( + target: "runtime::mmr::offchain", "Nodes to canon for leaf {}: {:?}", + to_canon_leaf, to_canon_nodes + ); + // For this block number there may be node entries saved from multiple forks. + let to_canon_block_num = + Pallet::::leaf_index_to_parent_block_num(to_canon_leaf, leaves); + // Only entries under this hash (retrieved from state on current canon fork) are to be + // persisted. All other entries added by same block number will be cleared. + let to_canon_hash = >::block_hash(to_canon_block_num); + + Self::canonicalize_nodes_for_hash(&to_canon_nodes, to_canon_hash); + // Get all the forks to prune, also remove them from the offchain pruning_map. + PruningMap::::remove(to_canon_block_num) + .map(|forks| { + Self::prune_nodes_for_forks(&to_canon_nodes, forks); + }) + .unwrap_or_else(|| { + frame_support::log::error!( + target: "runtime::mmr::offchain", + "Offchain: could not prune: no entry in pruning map for block {:?}", + to_canon_block_num + ); + }) + } + } + + fn prune_nodes_for_forks(nodes: &[NodeIndex], forks: Vec<::Hash>) { + for hash in forks { + for pos in nodes { + let key = Pallet::::node_offchain_key(hash, *pos); + frame_support::log::debug!( + target: "runtime::mmr::offchain", + "Clear elem at pos {} with key {:?}", + pos, key + ); + offchain::local_storage_clear(StorageKind::PERSISTENT, &key); + } + } + } + + fn canonicalize_nodes_for_hash( + to_canon_nodes: &[NodeIndex], + to_canon_hash: ::Hash, + ) { + for pos in to_canon_nodes { + let key = Pallet::::node_offchain_key(to_canon_hash, *pos); + // Retrieve the element from Off-chain DB under fork-aware key. + if let Some(elem) = offchain::local_storage_get(StorageKind::PERSISTENT, &key) { + let canon_key = Pallet::::node_canon_offchain_key(*pos); + // Add under new canon key. + offchain::local_storage_set(StorageKind::PERSISTENT, &canon_key, &elem); + frame_support::log::debug!( + target: "runtime::mmr::offchain", + "Moved elem at pos {} from key {:?} to canon key {:?}", + pos, key, canon_key + ); + } else { + frame_support::log::error!( + target: "runtime::mmr::offchain", + "Could not canonicalize elem at pos {} using key {:?}", + pos, key + ); + } + } + } +} + impl mmr_lib::MMRStore> for Storage where T: Config, @@ -65,9 +216,49 @@ where L: primitives::FullLeaf + codec::Decode, { fn get_elem(&self, pos: NodeIndex) -> mmr_lib::Result>> { - let key = Pallet::::offchain_key(pos); + let leaves = NumberOfLeaves::::get(); + // Find out which leaf added node `pos` in the MMR. + let ancestor_leaf_idx = NodesUtils::leaf_index_that_added_node(pos); + + let window_size = + ::BlockHashCount::get().unique_saturated_into(); + // Leaves older than this window should have been canonicalized. + if leaves.saturating_sub(ancestor_leaf_idx) > window_size { + let key = Pallet::::node_canon_offchain_key(pos); + frame_support::log::debug!( + target: "runtime::mmr::offchain", "offchain db get {}: leaf idx {:?}, key {:?}", + pos, ancestor_leaf_idx, key + ); + // Just for safety, to easily handle runtime upgrades where any of the window params + // change and maybe we mess up storage migration, + // return _if and only if_ node is found (in normal conditions it's always found), + if let Some(elem) = + sp_io::offchain::local_storage_get(sp_core::offchain::StorageKind::PERSISTENT, &key) + { + return Ok(codec::Decode::decode(&mut &*elem).ok()) + } + // BUT if we DID MESS UP, fall through to searching node using fork-specific key. + } + + // Leaves still within the window will be found in offchain db under fork-aware keys. + let ancestor_parent_block_num = + Pallet::::leaf_index_to_parent_block_num(ancestor_leaf_idx, leaves); + let ancestor_parent_hash = >::block_hash(ancestor_parent_block_num); + let key = Pallet::::node_offchain_key(ancestor_parent_hash, pos); + frame_support::log::debug!( + target: "runtime::mmr::offchain", "offchain db get {}: leaf idx {:?}, hash {:?}, key {:?}", + pos, ancestor_leaf_idx, ancestor_parent_hash, key + ); // Retrieve the element from Off-chain DB. Ok(sp_io::offchain::local_storage_get(sp_core::offchain::StorageKind::PERSISTENT, &key) + .or_else(|| { + // Again, this is just us being extra paranoid. + // We get here only if we mess up a storage migration for a runtime upgrades where + // say the window is increased, and for a little while following the upgrade there's + // leaves inside new 'window' that had been already canonicalized before upgrade. + let key = Pallet::::node_canon_offchain_key(pos); + sp_io::offchain::local_storage_get(sp_core::offchain::StorageKind::PERSISTENT, &key) + }) .and_then(|v| codec::Decode::decode(&mut &*v).ok())) } @@ -91,9 +282,11 @@ where return Ok(()) } - sp_std::if_std! { - frame_support::log::trace!("elems: {:?}", elems.iter().map(|elem| elem.hash()).collect::>()); - } + frame_support::log::trace!( + target: "runtime::mmr", + "elems: {:?}", + elems.iter().map(|elem| elem.hash()).collect::>() + ); let leaves = NumberOfLeaves::::get(); let size = NodesUtils::new(leaves).size(); @@ -112,11 +305,24 @@ where let mut leaf_index = leaves; let mut node_index = size; + // Use parent hash of block adding new nodes (this block) as extra identifier + // in offchain DB to avoid DB collisions and overwrites in case of forks. + let parent_hash = >::parent_hash(); for elem in elems { + // For now we store this leaf offchain keyed by `(parent_hash, node_index)` + // to make it fork-resistant. + // Offchain worker task will "canonicalize" it `frame_system::BlockHashCount` blocks + // later when we are not worried about forks anymore (highly unlikely to have a fork + // in the chain that deep). + // "Canonicalization" in this case means moving this leaf under a new key based + // only on the leaf's `node_index`. + let key = Pallet::::node_offchain_key(parent_hash, node_index); + frame_support::log::debug!( + target: "runtime::mmr::offchain", "offchain db set: pos {} parent_hash {:?} key {:?}", + node_index, parent_hash, key + ); // Indexing API is used to store the full node content (both leaf and inner). - elem.using_encoded(|elem| { - offchain_index::set(&Pallet::::offchain_key(node_index), elem) - }); + elem.using_encoded(|elem| offchain_index::set(&key, elem)); // On-chain we are going to only store new peaks. if peaks_to_store.next_if_eq(&node_index).is_some() { @@ -150,10 +356,8 @@ fn peaks_to_prune_and_store( // both collections may share a common prefix. let peaks_before = if old_size == 0 { vec![] } else { helper::get_peaks(old_size) }; let peaks_after = helper::get_peaks(new_size); - sp_std::if_std! { - frame_support::log::trace!("peaks_before: {:?}", peaks_before); - frame_support::log::trace!("peaks_after: {:?}", peaks_after); - } + frame_support::log::trace!(target: "runtime::mmr", "peaks_before: {:?}", peaks_before); + frame_support::log::trace!(target: "runtime::mmr", "peaks_after: {:?}", peaks_after); let mut peaks_before = peaks_before.into_iter().peekable(); let mut peaks_after = peaks_after.into_iter().peekable(); diff --git a/frame/merkle-mountain-range/src/mmr/utils.rs b/frame/merkle-mountain-range/src/mmr/utils.rs index d9f7e3b671be3..3734ea514782d 100644 --- a/frame/merkle-mountain-range/src/mmr/utils.rs +++ b/frame/merkle-mountain-range/src/mmr/utils.rs @@ -18,6 +18,7 @@ //! Merkle Mountain Range utilities. use crate::primitives::{LeafIndex, NodeIndex}; +use mmr_lib::helper; /// MMR nodes & size -related utilities. pub struct NodesUtils { @@ -53,11 +54,78 @@ impl NodesUtils { 64 - self.no_of_leaves.next_power_of_two().leading_zeros() } + + /// Calculate `LeafIndex` for the leaf that added `node_index` to the MMR. + pub fn leaf_index_that_added_node(node_index: NodeIndex) -> LeafIndex { + let rightmost_leaf_pos = Self::rightmost_leaf_node_index_from_pos(node_index); + Self::leaf_node_index_to_leaf_index(rightmost_leaf_pos) + } + + // Translate a _leaf_ `NodeIndex` to its `LeafIndex`. + fn leaf_node_index_to_leaf_index(pos: NodeIndex) -> LeafIndex { + if pos == 0 { + return 0 + } + let peaks = helper::get_peaks(pos); + (pos + peaks.len() as u64) >> 1 + } + + // Starting from any node position get position of rightmost leaf; this is the leaf + // responsible for the addition of node `pos`. + fn rightmost_leaf_node_index_from_pos(pos: NodeIndex) -> NodeIndex { + pos - (helper::pos_height_in_tree(pos) as u64) + } + + /// Starting from any leaf index, get the sequence of positions of the nodes added + /// to the mmr when this leaf was added (inclusive of the leaf's position itself). + /// That is, all of these nodes are right children of their respective parents. + pub fn right_branch_ending_in_leaf(leaf_index: LeafIndex) -> crate::Vec { + let pos = helper::leaf_index_to_pos(leaf_index); + let num_parents = leaf_index.trailing_ones() as u64; + return (pos..=pos + num_parents).collect() + } } #[cfg(test)] mod tests { use super::*; + use mmr_lib::helper::leaf_index_to_pos; + + #[test] + fn should_calculate_node_index_from_leaf_index() { + for index in 0..100000 { + let pos = leaf_index_to_pos(index); + assert_eq!(NodesUtils::leaf_node_index_to_leaf_index(pos), index); + } + } + + #[test] + fn should_calculate_right_branch_correctly() { + fn left_jump_sequence(leaf_index: LeafIndex) -> Vec { + let pos = leaf_index_to_pos(leaf_index); + let mut right_branch_ending_in_leaf = vec![pos]; + let mut next_pos = pos + 1; + while mmr_lib::helper::pos_height_in_tree(next_pos) > 0 { + right_branch_ending_in_leaf.push(next_pos); + next_pos += 1; + } + right_branch_ending_in_leaf + } + + for leaf_index in 0..100000 { + let pos = mmr_lib::helper::leaf_index_to_pos(leaf_index); + assert_eq!(NodesUtils::right_branch_ending_in_leaf(pos), left_jump_sequence(pos)); + } + } + + #[test] + fn should_calculate_rightmost_leaf_node_index_from_pos() { + for pos in 0..100000 { + let leaf_pos = NodesUtils::rightmost_leaf_node_index_from_pos(pos); + let leaf_index = NodesUtils::leaf_node_index_to_leaf_index(leaf_pos); + assert!(NodesUtils::right_branch_ending_in_leaf(leaf_index).contains(&pos)); + } + } #[test] fn should_calculate_number_of_leaves_correctly() { diff --git a/frame/merkle-mountain-range/src/tests.rs b/frame/merkle-mountain-range/src/tests.rs index d025910a9ee5c..53226f8419988 100644 --- a/frame/merkle-mountain-range/src/tests.rs +++ b/frame/merkle-mountain-range/src/tests.rs @@ -15,9 +15,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{mmr::utils, mock::*, *}; +use crate::{ + mmr::{storage::PruningMap, utils}, + mock::*, + *, +}; -use frame_support::traits::OnInitialize; +use frame_support::traits::{Get, OnInitialize}; use mmr_lib::helper; use sp_core::{ offchain::{testing::TestOffchainExt, OffchainDbExt, OffchainWorkerExt}, @@ -47,7 +51,6 @@ fn new_block() -> u64 { fn peaks_from_leaves_count(leaves_count: NodeIndex) -> Vec { let size = utils::NodesUtils::new(leaves_count).size(); - helper::get_peaks(size) } @@ -73,7 +76,7 @@ fn decode_node( } } -fn init_chain(blocks: usize) { +fn add_blocks(blocks: usize) { // given for _ in 0..blocks { new_block(); @@ -115,9 +118,10 @@ fn should_start_empty() { fn should_append_to_mmr_when_on_initialize_is_called() { let _ = env_logger::try_init(); let mut ext = new_test_ext(); - ext.execute_with(|| { + let (parent_b1, parent_b2) = ext.execute_with(|| { // when new_block(); + let parent_b1 = >::parent_hash(); // then assert_eq!(crate::NumberOfLeaves::::get(), 1); @@ -136,6 +140,7 @@ fn should_append_to_mmr_when_on_initialize_is_called() { // when new_block(); + let parent_b2 = >::parent_hash(); // then assert_eq!(crate::NumberOfLeaves::::get(), 2); @@ -157,26 +162,33 @@ fn should_append_to_mmr_when_on_initialize_is_called() { hex("672c04a9cd05a644789d769daa552d35d8de7c33129f8a7cbf49e595234c4854"), ) ); - }); + (parent_b1, parent_b2) + }); // make sure the leaves end up in the offchain DB ext.persist_offchain_overlay(); + let offchain_db = ext.offchain_db(); assert_eq!( - offchain_db.get(&MMR::offchain_key(0)).map(decode_node), + offchain_db.get(&MMR::node_offchain_key(parent_b1, 0)).map(decode_node), Some(mmr::Node::Data(((0, H256::repeat_byte(1)), LeafData::new(1),))) ); assert_eq!( - offchain_db.get(&MMR::offchain_key(1)).map(decode_node), + offchain_db.get(&MMR::node_offchain_key(parent_b2, 1)).map(decode_node), Some(mmr::Node::Data(((1, H256::repeat_byte(2)), LeafData::new(2),))) ); assert_eq!( - offchain_db.get(&MMR::offchain_key(2)).map(decode_node), + offchain_db.get(&MMR::node_offchain_key(parent_b2, 2)).map(decode_node), Some(mmr::Node::Hash(hex( "672c04a9cd05a644789d769daa552d35d8de7c33129f8a7cbf49e595234c4854" ))) ); - assert_eq!(offchain_db.get(&MMR::offchain_key(3)), None); + assert_eq!(offchain_db.get(&MMR::node_offchain_key(parent_b2, 3)), None); + + assert_eq!(offchain_db.get(&MMR::node_canon_offchain_key(0)), None); + assert_eq!(offchain_db.get(&MMR::node_canon_offchain_key(1)), None); + assert_eq!(offchain_db.get(&MMR::node_canon_offchain_key(2)), None); + assert_eq!(offchain_db.get(&MMR::node_canon_offchain_key(3)), None); } #[test] @@ -184,7 +196,7 @@ fn should_construct_larger_mmr_correctly() { let _ = env_logger::try_init(); new_test_ext().execute_with(|| { // when - init_chain(7); + add_blocks(7); // then assert_eq!(crate::NumberOfLeaves::::get(), 7); @@ -215,7 +227,7 @@ fn should_generate_proofs_correctly() { let _ = env_logger::try_init(); let mut ext = new_test_ext(); // given - ext.execute_with(|| init_chain(7)); + ext.execute_with(|| add_blocks(7)); ext.persist_offchain_overlay(); // Try to generate proofs now. This requires the offchain extensions to be present @@ -283,7 +295,7 @@ fn should_generate_batch_proof_correctly() { let _ = env_logger::try_init(); let mut ext = new_test_ext(); // given - ext.execute_with(|| init_chain(7)); + ext.execute_with(|| add_blocks(7)); ext.persist_offchain_overlay(); // Try to generate proofs now. This requires the offchain extensions to be present @@ -316,7 +328,7 @@ fn should_verify() { // Start off with chain initialisation and storing indexing data off-chain // (MMR Leafs) let mut ext = new_test_ext(); - ext.execute_with(|| init_chain(7)); + ext.execute_with(|| add_blocks(7)); ext.persist_offchain_overlay(); // Try to generate proof now. This requires the offchain extensions to be present @@ -328,7 +340,7 @@ fn should_verify() { }); ext.execute_with(|| { - init_chain(7); + add_blocks(7); // then assert_eq!(crate::Pallet::::verify_leaves(leaves, proof5), Ok(())); }); @@ -341,7 +353,7 @@ fn should_verify_batch_proof() { // Start off with chain initialisation and storing indexing data off-chain // (MMR Leafs) let mut ext = new_test_ext(); - ext.execute_with(|| init_chain(7)); + ext.execute_with(|| add_blocks(7)); ext.persist_offchain_overlay(); // Try to generate proof now. This requires the offchain extensions to be present @@ -353,7 +365,7 @@ fn should_verify_batch_proof() { }); ext.execute_with(|| { - init_chain(7); + add_blocks(7); // then assert_eq!(crate::Pallet::::verify_leaves(leaves, proof), Ok(())); }); @@ -366,7 +378,7 @@ fn verification_should_be_stateless() { // Start off with chain initialisation and storing indexing data off-chain // (MMR Leafs) let mut ext = new_test_ext(); - ext.execute_with(|| init_chain(7)); + ext.execute_with(|| add_blocks(7)); ext.persist_offchain_overlay(); // Try to generate proof now. This requires the offchain extensions to be present @@ -393,7 +405,7 @@ fn should_verify_batch_proof_statelessly() { // Start off with chain initialisation and storing indexing data off-chain // (MMR Leafs) let mut ext = new_test_ext(); - ext.execute_with(|| init_chain(7)); + ext.execute_with(|| add_blocks(7)); ext.persist_offchain_overlay(); // Try to generate proof now. This requires the offchain extensions to be present @@ -424,7 +436,7 @@ fn should_verify_on_the_next_block_since_there_is_no_pruning_yet() { let _ = env_logger::try_init(); let mut ext = new_test_ext(); // given - ext.execute_with(|| init_chain(7)); + ext.execute_with(|| add_blocks(7)); ext.persist_offchain_overlay(); register_offchain_ext(&mut ext); @@ -438,3 +450,238 @@ fn should_verify_on_the_next_block_since_there_is_no_pruning_yet() { assert_eq!(crate::Pallet::::verify_leaves(leaves, proof5), Ok(())); }); } + +#[test] +fn should_verify_pruning_map() { + use sp_core::offchain::StorageKind; + use sp_io::offchain; + + let _ = env_logger::try_init(); + let mut ext = new_test_ext(); + register_offchain_ext(&mut ext); + + ext.execute_with(|| { + type TestPruningMap = PruningMap; + fn offchain_decoded(key: Vec) -> Option> { + offchain::local_storage_get(StorageKind::PERSISTENT, &key) + .and_then(|v| codec::Decode::decode(&mut &*v).ok()) + } + + // test append + { + TestPruningMap::append(1, H256::repeat_byte(1)); + + TestPruningMap::append(2, H256::repeat_byte(21)); + TestPruningMap::append(2, H256::repeat_byte(22)); + + TestPruningMap::append(3, H256::repeat_byte(31)); + TestPruningMap::append(3, H256::repeat_byte(32)); + TestPruningMap::append(3, H256::repeat_byte(33)); + + // `0` not present + let map_key = TestPruningMap::pruning_map_offchain_key(0); + assert_eq!(offchain::local_storage_get(StorageKind::PERSISTENT, &map_key), None); + + // verify `1` entries + let map_key = TestPruningMap::pruning_map_offchain_key(1); + let expected = vec![H256::repeat_byte(1)]; + assert_eq!(offchain_decoded(map_key), Some(expected)); + + // verify `2` entries + let map_key = TestPruningMap::pruning_map_offchain_key(2); + let expected = vec![H256::repeat_byte(21), H256::repeat_byte(22)]; + assert_eq!(offchain_decoded(map_key), Some(expected)); + + // verify `3` entries + let map_key = TestPruningMap::pruning_map_offchain_key(3); + let expected = + vec![H256::repeat_byte(31), H256::repeat_byte(32), H256::repeat_byte(33)]; + assert_eq!(offchain_decoded(map_key), Some(expected)); + + // `4` not present + let map_key = TestPruningMap::pruning_map_offchain_key(4); + assert_eq!(offchain::local_storage_get(StorageKind::PERSISTENT, &map_key), None); + } + + // test remove + { + // `0` doesn't return anything + assert_eq!(TestPruningMap::remove(0), None); + + // remove and verify `1` entries + let expected = vec![H256::repeat_byte(1)]; + assert_eq!(TestPruningMap::remove(1), Some(expected)); + + // remove and verify `2` entries + let expected = vec![H256::repeat_byte(21), H256::repeat_byte(22)]; + assert_eq!(TestPruningMap::remove(2), Some(expected)); + + // remove and verify `3` entries + let expected = + vec![H256::repeat_byte(31), H256::repeat_byte(32), H256::repeat_byte(33)]; + assert_eq!(TestPruningMap::remove(3), Some(expected)); + + // `4` doesn't return anything + assert_eq!(TestPruningMap::remove(4), None); + + // no entries left in offchain map + for block in 0..5 { + let map_key = TestPruningMap::pruning_map_offchain_key(block); + assert_eq!(offchain::local_storage_get(StorageKind::PERSISTENT, &map_key), None); + } + } + }) +} + +#[test] +fn should_canonicalize_offchain() { + use frame_support::traits::Hooks; + + let _ = env_logger::try_init(); + let mut ext = new_test_ext(); + register_offchain_ext(&mut ext); + + // adding 13 blocks that we'll later check have been canonicalized, + // (test assumes `13 < frame_system::BlockHashCount`). + let to_canon_count = 13u32; + + // add 13 blocks and verify leaves and nodes for them have been added to + // offchain MMR using fork-proof keys. + for blocknum in 0..to_canon_count { + ext.execute_with(|| { + new_block(); + as Hooks>::offchain_worker(blocknum.into()); + }); + ext.persist_offchain_overlay(); + } + let offchain_db = ext.offchain_db(); + ext.execute_with(|| { + // verify leaves added by blocks 1..=13 + for block_num in 1..=to_canon_count { + let parent_num: BlockNumber = (block_num - 1).into(); + let leaf_index = u64::from(block_num - 1); + let pos = helper::leaf_index_to_pos(leaf_index.into()); + // not canon, + assert_eq!(offchain_db.get(&MMR::node_canon_offchain_key(pos)), None); + let parent_hash = >::block_hash(parent_num); + // but available in fork-proof storage. + assert_eq!( + offchain_db.get(&MMR::node_offchain_key(parent_hash, pos)).map(decode_node), + Some(mmr::Node::Data(( + (leaf_index, H256::repeat_byte(u8::try_from(block_num).unwrap())), + LeafData::new(block_num.into()), + ))) + ); + } + + // verify a couple of nodes and peaks: + // `pos` is node to verify, + // `leaf_index` is leaf that added node `pos`, + // `expected` is expected value of node at `pos`. + let verify = |pos: NodeIndex, leaf_index: LeafIndex, expected: H256| { + let parent_num: BlockNumber = leaf_index.try_into().unwrap(); + let parent_hash = >::block_hash(parent_num); + // not canon, + assert_eq!(offchain_db.get(&MMR::node_canon_offchain_key(pos)), None); + // but available in fork-proof storage. + assert_eq!( + offchain_db.get(&MMR::node_offchain_key(parent_hash, pos)).map(decode_node), + Some(mmr::Node::Hash(expected)) + ); + }; + verify(2, 1, hex("672c04a9cd05a644789d769daa552d35d8de7c33129f8a7cbf49e595234c4854")); + verify(13, 7, hex("441bf63abc7cf9b9e82eb57b8111c883d50ae468d9fd7f301e12269fc0fa1e75")); + verify(21, 11, hex("f323ac1a7f56de5f40ed8df3e97af74eec0ee9d72883679e49122ffad2ffd03b")); + }); + + // add another `frame_system::BlockHashCount` blocks and verify all nodes and leaves + // added by our original `to_canon_count` blocks have now been canonicalized in offchain db. + let block_hash_size: u64 = ::BlockHashCount::get(); + let base = to_canon_count; + for blocknum in base..(base + u32::try_from(block_hash_size).unwrap()) { + ext.execute_with(|| { + new_block(); + as Hooks>::offchain_worker(blocknum.into()); + }); + ext.persist_offchain_overlay(); + } + ext.execute_with(|| { + // verify leaves added by blocks 1..=13, should be in offchain under canon key. + for block_num in 1..=to_canon_count { + let leaf_index = u64::from(block_num - 1); + let pos = helper::leaf_index_to_pos(leaf_index.into()); + let parent_num: BlockNumber = (block_num - 1).into(); + let parent_hash = >::block_hash(parent_num); + // no longer available in fork-proof storage (was pruned), + assert_eq!(offchain_db.get(&MMR::node_offchain_key(parent_hash, pos)), None); + // but available using canon key. + assert_eq!( + offchain_db.get(&MMR::node_canon_offchain_key(pos)).map(decode_node), + Some(mmr::Node::Data(( + (leaf_index, H256::repeat_byte(u8::try_from(block_num).unwrap())), + LeafData::new(block_num.into()), + ))) + ); + } + + // also check some nodes and peaks: + // `pos` is node to verify, + // `leaf_index` is leaf that added node `pos`, + // `expected` is expected value of node at `pos`. + let verify = |pos: NodeIndex, leaf_index: LeafIndex, expected: H256| { + let parent_num: BlockNumber = leaf_index.try_into().unwrap(); + let parent_hash = >::block_hash(parent_num); + // no longer available in fork-proof storage (was pruned), + assert_eq!(offchain_db.get(&MMR::node_offchain_key(parent_hash, pos)), None); + // but available using canon key. + assert_eq!( + offchain_db.get(&MMR::node_canon_offchain_key(pos)).map(decode_node), + Some(mmr::Node::Hash(expected)) + ); + }; + verify(2, 1, hex("672c04a9cd05a644789d769daa552d35d8de7c33129f8a7cbf49e595234c4854")); + verify(13, 7, hex("441bf63abc7cf9b9e82eb57b8111c883d50ae468d9fd7f301e12269fc0fa1e75")); + verify(21, 11, hex("f323ac1a7f56de5f40ed8df3e97af74eec0ee9d72883679e49122ffad2ffd03b")); + }); +} + +#[test] +fn should_verify_canonicalized() { + use frame_support::traits::Hooks; + let _ = env_logger::try_init(); + + // How deep is our fork-aware storage (in terms of blocks/leaves, nodes will be more). + let block_hash_size: u64 = ::BlockHashCount::get(); + + // Start off with chain initialisation and storing indexing data off-chain. + // Create twice as many leaf entries than our fork-aware capacity, + // resulting in ~half of MMR storage to use canonical keys and the other half fork-aware keys. + // Verify that proofs can be generated (using leaves and nodes from full set) and verified. + let mut ext = new_test_ext(); + register_offchain_ext(&mut ext); + for blocknum in 0u32..(2 * block_hash_size).try_into().unwrap() { + ext.execute_with(|| { + new_block(); + as Hooks>::offchain_worker(blocknum.into()); + }); + ext.persist_offchain_overlay(); + } + + // Generate proofs for some blocks. + let (leaves, proofs) = + ext.execute_with(|| crate::Pallet::::generate_batch_proof(vec![0, 4, 5, 7]).unwrap()); + // Verify all previously generated proofs. + ext.execute_with(|| { + assert_eq!(crate::Pallet::::verify_leaves(leaves, proofs), Ok(())); + }); + + // Generate proofs for some new blocks. + let (leaves, proofs) = ext.execute_with(|| { + crate::Pallet::::generate_batch_proof(vec![block_hash_size + 7]).unwrap() + }); + // Add some more blocks then verify all previously generated proofs. + ext.execute_with(|| { + add_blocks(7); + assert_eq!(crate::Pallet::::verify_leaves(leaves, proofs), Ok(())); + }); +} diff --git a/primitives/merkle-mountain-range/src/lib.rs b/primitives/merkle-mountain-range/src/lib.rs index 5a339d069062c..8a2e901aefddf 100644 --- a/primitives/merkle-mountain-range/src/lib.rs +++ b/primitives/merkle-mountain-range/src/lib.rs @@ -81,7 +81,7 @@ pub struct Proof { /// A full leaf content stored in the offchain-db. pub trait FullLeaf: Clone + PartialEq + fmt::Debug { - /// Encode the leaf either in it's full or compact form. + /// Encode the leaf either in its full or compact form. /// /// NOTE the encoding returned here MUST be `Decode`able into `FullLeaf`. fn using_encoded R>(&self, f: F, compact: bool) -> R; @@ -167,18 +167,18 @@ impl EncodableOpaqueLeaf { } } -/// An element representing either full data or it's hash. +/// An element representing either full data or its hash. /// /// See [Compact] to see how it may be used in practice to reduce the size /// of proofs in case multiple [LeafDataProvider]s are composed together. /// This is also used internally by the MMR to differentiate leaf nodes (data) /// and inner nodes (hashes). /// -/// [DataOrHash::hash] method calculates the hash of this element in it's compact form, +/// [DataOrHash::hash] method calculates the hash of this element in its compact form, /// so should be used instead of hashing the encoded form (which will always be non-compact). #[derive(RuntimeDebug, Clone, PartialEq)] pub enum DataOrHash { - /// Arbitrary data in it's full form. + /// Arbitrary data in its full form. Data(L), /// A hash of some data. Hash(H::Output), @@ -339,7 +339,7 @@ where A: FullLeaf, B: FullLeaf, { - /// Retrieve a hash of this item in it's compact form. + /// Retrieve a hash of this item in its compact form. pub fn hash(&self) -> H::Output { self.using_encoded(::hash, true) } @@ -447,7 +447,7 @@ sp_api::decl_runtime_apis! { /// Note this function does not require any on-chain storage - the /// proof is verified against given MMR root hash. /// - /// The leaf data is expected to be encoded in it's compact form. + /// The leaf data is expected to be encoded in its compact form. fn verify_proof_stateless(root: Hash, leaf: EncodableOpaqueLeaf, proof: Proof) -> Result<(), Error>; From e89c2d91ae392358d2235460b449f1832689e1e0 Mon Sep 17 00:00:00 2001 From: Andronik Date: Fri, 8 Jul 2022 14:27:58 +0200 Subject: [PATCH 380/484] expose grandpa BeforeBestBlockBy voting rule (#11798) --- client/finality-grandpa/src/voting_rule.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/finality-grandpa/src/voting_rule.rs b/client/finality-grandpa/src/voting_rule.rs index 209b0f1b33ced..051c7f2c03658 100644 --- a/client/finality-grandpa/src/voting_rule.rs +++ b/client/finality-grandpa/src/voting_rule.rs @@ -89,7 +89,7 @@ where /// can prioritize shorter chains over longer ones, the vote may be /// closer to the best block than N. #[derive(Clone)] -pub struct BeforeBestBlockBy(N); +pub struct BeforeBestBlockBy(pub N); impl VotingRule for BeforeBestBlockBy> where Block: BlockT, From dd8f87c1c867db506272e687be480576684e0d6d Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Sat, 9 Jul 2022 14:39:26 +0100 Subject: [PATCH 381/484] Un-deprecate Transactional Macro (#11807) * un-deprecate transactional macro * add transactional back to nomination pools --- frame/nomination-pools/src/lib.rs | 15 ++++++++++++++- frame/support/procedural/src/lib.rs | 1 - 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index d43d43c806d61..f7ccac3e35411 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -316,7 +316,7 @@ use frame_support::{ Currency, Defensive, DefensiveOption, DefensiveResult, DefensiveSaturating, ExistenceRequirement, Get, }, - CloneNoBound, DefaultNoBound, RuntimeDebugNoBound, + transactional, CloneNoBound, DefaultNoBound, RuntimeDebugNoBound, }; use scale_info::TypeInfo; use sp_core::U256; @@ -1412,6 +1412,7 @@ pub mod pallet { /// `existential deposit + amount` in their account. /// * Only a pool with [`PoolState::Open`] can be joined #[pallet::weight(T::WeightInfo::join())] + #[transactional] pub fn join( origin: OriginFor, #[pallet::compact] amount: BalanceOf, @@ -1472,6 +1473,7 @@ pub mod pallet { T::WeightInfo::bond_extra_transfer() .max(T::WeightInfo::bond_extra_reward()) )] + #[transactional] pub fn bond_extra(origin: OriginFor, extra: BondExtra>) -> DispatchResult { let who = ensure_signed(origin)?; let (mut member, mut bonded_pool, mut reward_pool) = Self::get_member_with_pools(&who)?; @@ -1510,6 +1512,7 @@ pub mod pallet { /// The member will earn rewards pro rata based on the members stake vs the sum of the /// members in the pools stake. Rewards do not "expire". #[pallet::weight(T::WeightInfo::claim_payout())] + #[transactional] pub fn claim_payout(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; let (mut member, mut bonded_pool, mut reward_pool) = Self::get_member_with_pools(&who)?; @@ -1549,6 +1552,7 @@ pub mod pallet { /// there are too many unlocking chunks, the result of this call will likely be the /// `NoMoreChunks` error from the staking system. #[pallet::weight(T::WeightInfo::unbond())] + #[transactional] pub fn unbond( origin: OriginFor, member_account: T::AccountId, @@ -1626,6 +1630,7 @@ pub mod pallet { /// would probably see an error like `NoMoreChunks` emitted from the staking system when /// they attempt to unbond. #[pallet::weight(T::WeightInfo::pool_withdraw_unbonded(*num_slashing_spans))] + #[transactional] pub fn pool_withdraw_unbonded( origin: OriginFor, pool_id: PoolId, @@ -1662,6 +1667,7 @@ pub mod pallet { #[pallet::weight( T::WeightInfo::withdraw_unbonded_kill(*num_slashing_spans) )] + #[transactional] pub fn withdraw_unbonded( origin: OriginFor, member_account: T::AccountId, @@ -1787,6 +1793,7 @@ pub mod pallet { /// In addition to `amount`, the caller will transfer the existential deposit; so the caller /// needs at have at least `amount + existential_deposit` transferrable. #[pallet::weight(T::WeightInfo::create())] + #[transactional] pub fn create( origin: OriginFor, #[pallet::compact] amount: BalanceOf, @@ -1875,6 +1882,7 @@ pub mod pallet { /// This directly forward the call to the staking pallet, on behalf of the pool bonded /// account. #[pallet::weight(T::WeightInfo::nominate(validators.len() as u32))] + #[transactional] pub fn nominate( origin: OriginFor, pool_id: PoolId, @@ -1891,6 +1899,7 @@ pub mod pallet { /// The dispatch origin of this call must be signed by the state toggler, or the root role /// of the pool. #[pallet::weight(T::WeightInfo::set_state())] + #[transactional] pub fn set_state( origin: OriginFor, pool_id: PoolId, @@ -1921,6 +1930,7 @@ pub mod pallet { /// The dispatch origin of this call must be signed by the state toggler, or the root role /// of the pool. #[pallet::weight(T::WeightInfo::set_metadata(metadata.len() as u32))] + #[transactional] pub fn set_metadata( origin: OriginFor, pool_id: PoolId, @@ -1952,6 +1962,7 @@ pub mod pallet { /// * `max_members` - Set [`MaxPoolMembers`]. /// * `max_members_per_pool` - Set [`MaxPoolMembersPerPool`]. #[pallet::weight(T::WeightInfo::set_configs())] + #[transactional] pub fn set_configs( origin: OriginFor, min_join_bond: ConfigOp>, @@ -1988,6 +1999,7 @@ pub mod pallet { /// It emits an event, notifying UIs of the role change. This event is quite relevant to /// most pool members and they should be informed of changes to pool roles. #[pallet::weight(T::WeightInfo::update_roles())] + #[transactional] pub fn update_roles( origin: OriginFor, pool_id: PoolId, @@ -2040,6 +2052,7 @@ pub mod pallet { /// This directly forward the call to the staking pallet, on behalf of the pool bonded /// account. #[pallet::weight(T::WeightInfo::chill())] + #[transactional] pub fn chill(origin: OriginFor, pool_id: PoolId) -> DispatchResult { let who = ensure_signed(origin)?; let bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; diff --git a/frame/support/procedural/src/lib.rs b/frame/support/procedural/src/lib.rs index 418fad56b7674..00204b7a4d906 100644 --- a/frame/support/procedural/src/lib.rs +++ b/frame/support/procedural/src/lib.rs @@ -429,7 +429,6 @@ pub fn pallet(attr: TokenStream, item: TokenStream) -> TokenStream { /// } /// ``` #[proc_macro_attribute] -#[deprecated(note = "This is now the default behaviour for all extrinsics.")] pub fn transactional(attr: TokenStream, input: TokenStream) -> TokenStream { transactional::transactional(attr, input).unwrap_or_else(|e| e.to_compile_error().into()) } From fabec1071631d99b41c4c1b7bfa2e2f9d8f471f3 Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Tue, 12 Jul 2022 13:14:42 +0200 Subject: [PATCH 382/484] sync: Fixed clearing subsequent ranges (#11815) * sync: Fixed clearing subsequent ranges * Warp sync fix * Better documentation --- client/network/sync/src/blocks.rs | 48 +++++++++++++++++++++++++------ client/network/sync/src/lib.rs | 3 ++ 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/client/network/sync/src/blocks.rs b/client/network/sync/src/blocks.rs index 8d9b039b14fba..26753f120a170 100644 --- a/client/network/sync/src/blocks.rs +++ b/client/network/sync/src/blocks.rs @@ -180,6 +180,9 @@ impl BlockCollection { /// Get a valid chain of blocks ordered in descending order and ready for importing into /// the blockchain. + /// `from` is the maximum block number for the start of the range that we are interested in. + /// The function will return empty Vec if the first block ready is higher than `from`. + /// For each returned block hash `clear_queued` must be called at some later stage. pub fn ready_blocks(&mut self, from: NumberFor) -> Vec> { let mut ready = Vec::new(); @@ -192,6 +195,10 @@ impl BlockCollection { BlockRangeState::Complete(blocks) => { let len = (blocks.len() as u32).into(); prev = start + len; + if let Some(BlockData { block, .. }) = blocks.first() { + self.queued_blocks + .insert(block.hash, (start, start + (blocks.len() as u32).into())); + } // Remove all elements from `blocks` and add them to `ready` ready.append(blocks); len @@ -201,18 +208,12 @@ impl BlockCollection { }; *range_data = BlockRangeState::Queued { len }; } - - if let Some(BlockData { block, .. }) = ready.first() { - self.queued_blocks - .insert(block.hash, (from, from + (ready.len() as u32).into())); - } - trace!(target: "sync", "{} blocks ready for import", ready.len()); ready } - pub fn clear_queued(&mut self, from_hash: &B::Hash) { - if let Some((from, to)) = self.queued_blocks.remove(from_hash) { + pub fn clear_queued(&mut self, hash: &B::Hash) { + if let Some((from, to)) = self.queued_blocks.remove(hash) { let mut block_num = from; while block_num < to { self.blocks.remove(&block_num); @@ -399,4 +400,35 @@ mod test { assert_eq!(bc.needed_blocks(peer.clone(), 5, 50, 39, 0, 200), Some(45..50)); } + + #[test] + fn clear_queued_subsequent_ranges() { + let mut bc = BlockCollection::new(); + assert!(is_empty(&bc)); + let peer = PeerId::random(); + + let blocks = generate_blocks(10); + + // Request 2 ranges + assert_eq!(bc.needed_blocks(peer.clone(), 5, 50, 39, 0, 200), Some(40..45)); + assert_eq!(bc.needed_blocks(peer.clone(), 5, 50, 39, 0, 200), Some(45..50)); + + // got a response on the request for `40..50` + bc.clear_peer_download(&peer); + bc.insert(40, blocks.to_vec(), peer.clone()); + + // request any blocks starting from 1000 or lower. + let ready = bc.ready_blocks(1000); + assert_eq!( + ready, + blocks + .iter() + .map(|b| BlockData { block: b.clone(), origin: Some(peer.clone()) }) + .collect::>() + ); + + bc.clear_queued(&blocks[0].hash); + assert!(bc.blocks.is_empty()); + assert!(bc.queued_blocks.is_empty()); + } } diff --git a/client/network/sync/src/lib.rs b/client/network/sync/src/lib.rs index 5b5216c745c98..1ce69b6dc816f 100644 --- a/client/network/sync/src/lib.rs +++ b/client/network/sync/src/lib.rs @@ -1521,6 +1521,9 @@ where for (_, hash) in &results { self.queue_blocks.remove(hash); self.blocks.clear_queued(hash); + if let Some(gap_sync) = &mut self.gap_sync { + gap_sync.blocks.clear_queued(hash); + } } for (result, hash) in results { if has_error { From d0de3d178d22b666003f9a8f05354655fdef4a80 Mon Sep 17 00:00:00 2001 From: Xiliang Chen Date: Tue, 12 Jul 2022 23:19:13 +1200 Subject: [PATCH 383/484] CLI flag to configure tx ban duration (#11786) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add tx-ban-seconds * fix * trigger CI * trigger CI * remove test print * Update client/cli/src/params/transaction_pool_params.rs Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com> Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com> --- bin/node/cli/benches/transaction_pool.rs | 3 +++ client/cli/src/commands/run_cmd.rs | 4 ++-- client/cli/src/config.rs | 4 ++-- client/cli/src/params/transaction_pool_params.rs | 14 +++++++++++++- client/transaction-pool/src/graph/pool.rs | 5 ++++- client/transaction-pool/src/graph/rotator.rs | 5 +++++ .../transaction-pool/src/graph/validated_pool.rs | 3 ++- 7 files changed, 31 insertions(+), 7 deletions(-) diff --git a/bin/node/cli/benches/transaction_pool.rs b/bin/node/cli/benches/transaction_pool.rs index f1fce16d8c1b3..5b96355b9ca70 100644 --- a/bin/node/cli/benches/transaction_pool.rs +++ b/bin/node/cli/benches/transaction_pool.rs @@ -16,6 +16,8 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +use std::time::Duration; + use criterion::{criterion_group, criterion_main, BatchSize, Criterion, Throughput}; use futures::{future, StreamExt}; use node_cli::service::{create_extrinsic, fetch_nonce, FullClient, TransactionPool}; @@ -58,6 +60,7 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase { ready: PoolLimit { count: 100_000, total_bytes: 100 * 1024 * 1024 }, future: PoolLimit { count: 100_000, total_bytes: 100 * 1024 * 1024 }, reject_future_transactions: false, + ban_time: Duration::from_secs(30 * 60), }, network: network_config, keystore: KeystoreConfig::InMemory, diff --git a/client/cli/src/commands/run_cmd.rs b/client/cli/src/commands/run_cmd.rs index b4a3885e39906..12774eecc4174 100644 --- a/client/cli/src/commands/run_cmd.rs +++ b/client/cli/src/commands/run_cmd.rs @@ -480,8 +480,8 @@ impl CliConfiguration for RunCmd { Ok(self.ws_max_out_buffer_capacity) } - fn transaction_pool(&self) -> Result { - Ok(self.pool_config.transaction_pool()) + fn transaction_pool(&self, is_dev: bool) -> Result { + Ok(self.pool_config.transaction_pool(is_dev)) } fn max_runtime_instances(&self) -> Result> { diff --git a/client/cli/src/config.rs b/client/cli/src/config.rs index 6e1317c11fbc4..01c541bf75255 100644 --- a/client/cli/src/config.rs +++ b/client/cli/src/config.rs @@ -145,7 +145,7 @@ pub trait CliConfiguration: Sized { /// Get the transaction pool options /// /// By default this is `TransactionPoolOptions::default()`. - fn transaction_pool(&self) -> Result { + fn transaction_pool(&self, _is_dev: bool) -> Result { Ok(Default::default()) } @@ -523,7 +523,7 @@ pub trait CliConfiguration: Sized { impl_name: C::impl_name(), impl_version: C::impl_version(), tokio_handle, - transaction_pool: self.transaction_pool()?, + transaction_pool: self.transaction_pool(is_dev)?, network: self.network_config( &chain_spec, is_dev, diff --git a/client/cli/src/params/transaction_pool_params.rs b/client/cli/src/params/transaction_pool_params.rs index efb78430ced55..6429dfec3f908 100644 --- a/client/cli/src/params/transaction_pool_params.rs +++ b/client/cli/src/params/transaction_pool_params.rs @@ -29,11 +29,15 @@ pub struct TransactionPoolParams { /// Maximum number of kilobytes of all transactions stored in the pool. #[clap(long, value_name = "COUNT", default_value = "20480")] pub pool_kbytes: usize, + + /// How long a transaction is banned for, if it is considered invalid. Defaults to 1800s. + #[clap(long, value_name = "SECONDS")] + pub tx_ban_seconds: Option, } impl TransactionPoolParams { /// Fill the given `PoolConfiguration` by looking at the cli parameters. - pub fn transaction_pool(&self) -> TransactionPoolOptions { + pub fn transaction_pool(&self, is_dev: bool) -> TransactionPoolOptions { let mut opts = TransactionPoolOptions::default(); // ready queue @@ -45,6 +49,14 @@ impl TransactionPoolParams { opts.future.count = self.pool_limit / factor; opts.future.total_bytes = self.pool_kbytes * 1024 / factor; + opts.ban_time = if let Some(ban_seconds) = self.tx_ban_seconds { + std::time::Duration::from_secs(ban_seconds) + } else if is_dev { + std::time::Duration::from_secs(0) + } else { + std::time::Duration::from_secs(30 * 60) + }; + opts } } diff --git a/client/transaction-pool/src/graph/pool.rs b/client/transaction-pool/src/graph/pool.rs index 618ba8ccf24d5..4ce7954f8d479 100644 --- a/client/transaction-pool/src/graph/pool.rs +++ b/client/transaction-pool/src/graph/pool.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use std::{collections::HashMap, sync::Arc}; +use std::{collections::HashMap, sync::Arc, time::Duration}; use futures::{channel::mpsc::Receiver, Future}; use sc_transaction_pool_api::error; @@ -108,6 +108,8 @@ pub struct Options { pub future: base::Limit, /// Reject future transactions. pub reject_future_transactions: bool, + /// How long the extrinsic is banned for. + pub ban_time: Duration, } impl Default for Options { @@ -116,6 +118,7 @@ impl Default for Options { ready: base::Limit { count: 8192, total_bytes: 20 * 1024 * 1024 }, future: base::Limit { count: 512, total_bytes: 1 * 1024 * 1024 }, reject_future_transactions: false, + ban_time: Duration::from_secs(60 * 30), } } } diff --git a/client/transaction-pool/src/graph/rotator.rs b/client/transaction-pool/src/graph/rotator.rs index b897fe7885033..c91c8e407bc0f 100644 --- a/client/transaction-pool/src/graph/rotator.rs +++ b/client/transaction-pool/src/graph/rotator.rs @@ -51,6 +51,11 @@ impl Default for PoolRotator { } impl PoolRotator { + /// New rotator instance with specified ban time. + pub fn new(ban_time: Duration) -> Self { + Self { ban_time, banned_until: Default::default() } + } + /// Returns `true` if extrinsic hash is currently banned. pub fn is_banned(&self, hash: &Hash) -> bool { self.banned_until.read().contains_key(hash) diff --git a/client/transaction-pool/src/graph/validated_pool.rs b/client/transaction-pool/src/graph/validated_pool.rs index 084b04842ee90..e59c5afbdc512 100644 --- a/client/transaction-pool/src/graph/validated_pool.rs +++ b/client/transaction-pool/src/graph/validated_pool.rs @@ -125,6 +125,7 @@ impl ValidatedPool { /// Create a new transaction pool. pub fn new(options: Options, is_validator: IsValidator, api: Arc) -> Self { let base_pool = base::BasePool::new(options.reject_future_transactions); + let ban_time = options.ban_time; Self { is_validator, options, @@ -132,7 +133,7 @@ impl ValidatedPool { api, pool: RwLock::new(base_pool), import_notification_sinks: Default::default(), - rotator: Default::default(), + rotator: PoolRotator::new(ban_time), } } From 87da8b9847ab62696be229eec3d034ffc29d7967 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 12 Jul 2022 21:00:00 +0400 Subject: [PATCH 384/484] Upgrade libp2p to 0.46.1 (#11787) * Update libp2p to 0.46.0 * Update libp2p to 0.46.1 * Fix telemetry initialization * Fix tests --- Cargo.lock | 362 ++++++------------ client/authority-discovery/Cargo.toml | 2 +- client/cli/Cargo.toml | 2 +- client/consensus/common/Cargo.toml | 2 +- client/network-gossip/Cargo.toml | 2 +- client/network/Cargo.toml | 2 +- client/network/common/Cargo.toml | 2 +- client/network/light/Cargo.toml | 2 +- client/network/src/discovery.rs | 6 +- client/network/src/peer_info.rs | 5 +- client/network/src/protocol.rs | 5 +- .../src/protocol/notifications/tests.rs | 6 +- client/network/src/request_responses.rs | 7 +- client/network/src/transport.rs | 9 +- client/network/sync/Cargo.toml | 2 +- client/network/test/Cargo.toml | 2 +- client/peerset/Cargo.toml | 2 +- client/telemetry/Cargo.toml | 2 +- client/telemetry/src/transport.rs | 3 +- 19 files changed, 151 insertions(+), 274 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d68330cfb15c2..26f43fa4ae0ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3207,7 +3207,7 @@ dependencies = [ "http", "jsonrpsee-core", "jsonrpsee-types", - "pin-project 1.0.10", + "pin-project", "rustls-native-certs", "soketto", "thiserror", @@ -3461,9 +3461,9 @@ checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" [[package]] name = "libp2p" -version = "0.45.1" +version = "0.46.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41726ee8f662563fafba2d2d484b14037cc8ecb8c953fbfc8439d4ce3a0a9029" +checksum = "81327106887e42d004fbdab1fef93675be2e2e07c1b95fce45e2cc813485611d" dependencies = [ "bytes", "futures", @@ -3472,7 +3472,7 @@ dependencies = [ "instant", "lazy_static", "libp2p-autonat", - "libp2p-core 0.33.0", + "libp2p-core", "libp2p-deflate", "libp2p-dns", "libp2p-floodsub", @@ -3498,69 +3498,35 @@ dependencies = [ "libp2p-yamux", "multiaddr", "parking_lot 0.12.0", - "pin-project 1.0.10", + "pin-project", "rand 0.7.3", "smallvec", ] [[package]] name = "libp2p-autonat" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50de7c1d5c3f040fccb469e8a2d189e068b7627d760dd74ef914071c16bbe905" +checksum = "4decc51f3573653a9f4ecacb31b1b922dd20c25a6322bb15318ec04287ec46f9" dependencies = [ "async-trait", "futures", "futures-timer", "instant", - "libp2p-core 0.33.0", + "libp2p-core", "libp2p-request-response", "libp2p-swarm", "log", - "prost 0.10.3", - "prost-build 0.10.4", - "rand 0.8.4", -] - -[[package]] -name = "libp2p-core" -version = "0.32.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db5b02602099fb75cb2d16f9ea860a320d6eb82ce41e95ab680912c454805cd5" -dependencies = [ - "asn1_der", - "bs58", - "ed25519-dalek", - "either", - "fnv", - "futures", - "futures-timer", - "instant", - "lazy_static", - "log", - "multiaddr", - "multihash", - "multistream-select", - "parking_lot 0.12.0", - "pin-project 1.0.10", - "prost 0.9.0", - "prost-build 0.9.0", + "prost", + "prost-build", "rand 0.8.4", - "ring", - "rw-stream-sink 0.2.1", - "sha2 0.10.2", - "smallvec", - "thiserror", - "unsigned-varint", - "void", - "zeroize", ] [[package]] name = "libp2p-core" -version = "0.33.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d46fca305dee6757022e2f5a4f6c023315084d0ed7441c3ab244e76666d979" +checksum = "fbf9b94cefab7599b2d3dff2f93bee218c6621d68590b23ede4485813cbcece6" dependencies = [ "asn1_der", "bs58", @@ -3577,12 +3543,12 @@ dependencies = [ "multihash", "multistream-select", "parking_lot 0.12.0", - "pin-project 1.0.10", - "prost 0.10.3", - "prost-build 0.10.4", + "pin-project", + "prost", + "prost-build", "rand 0.8.4", "ring", - "rw-stream-sink 0.3.0", + "rw-stream-sink", "sha2 0.10.2", "smallvec", "thiserror", @@ -3593,24 +3559,24 @@ dependencies = [ [[package]] name = "libp2p-deflate" -version = "0.33.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86adefc55ea4ed8201149f052fb441210727481dff1fb0b8318460206a79f5fb" +checksum = "d0183dc2a3da1fbbf85e5b6cf51217f55b14f5daea0c455a9536eef646bfec71" dependencies = [ "flate2", "futures", - "libp2p-core 0.33.0", + "libp2p-core", ] [[package]] name = "libp2p-dns" -version = "0.33.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbb462ec3a51fab457b4b44ac295e8b0a4b04dc175127e615cf996b1f0f1a268" +checksum = "6cbf54723250fa5d521383be789bf60efdabe6bacfb443f87da261019a49b4b5" dependencies = [ "async-std-resolver", "futures", - "libp2p-core 0.33.0", + "libp2p-core", "log", "parking_lot 0.12.0", "smallvec", @@ -3619,27 +3585,27 @@ dependencies = [ [[package]] name = "libp2p-floodsub" -version = "0.36.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a505d0c6f851cbf2919535150198e530825def8bd3757477f13dc3a57f46cbcc" +checksum = "98a4b6ffd53e355775d24b76f583fdda54b3284806f678499b57913adb94f231" dependencies = [ "cuckoofilter", "fnv", "futures", - "libp2p-core 0.33.0", + "libp2p-core", "libp2p-swarm", "log", - "prost 0.10.3", - "prost-build 0.10.4", + "prost", + "prost-build", "rand 0.7.3", "smallvec", ] [[package]] name = "libp2p-gossipsub" -version = "0.38.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9be947d8cea8e6b469201314619395826896d2c051053c3723910ba98e68e04" +checksum = "74b4b888cfbeb1f5551acd3aa1366e01bf88ede26cc3c4645d0d2d004d5ca7b0" dependencies = [ "asynchronous-codec", "base64", @@ -3649,12 +3615,12 @@ dependencies = [ "futures", "hex_fmt", "instant", - "libp2p-core 0.33.0", + "libp2p-core", "libp2p-swarm", "log", "prometheus-client", - "prost 0.10.3", - "prost-build 0.10.4", + "prost", + "prost-build", "rand 0.7.3", "regex", "sha2 0.10.2", @@ -3665,19 +3631,19 @@ dependencies = [ [[package]] name = "libp2p-identify" -version = "0.36.1" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b84b53490442d086db1fa5375670c9666e79143dccadef3f7c74a4346899a984" +checksum = "c50b585518f8efd06f93ac2f976bd672e17cdac794644b3117edd078e96bda06" dependencies = [ "asynchronous-codec", "futures", "futures-timer", - "libp2p-core 0.33.0", + "libp2p-core", "libp2p-swarm", "log", "lru", - "prost 0.10.3", - "prost-build 0.10.4", + "prost", + "prost-build", "prost-codec", "smallvec", "thiserror", @@ -3686,9 +3652,9 @@ dependencies = [ [[package]] name = "libp2p-kad" -version = "0.37.1" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6b5d4de90fcd35feb65ea6223fd78f3b747a64ca4b65e0813fbe66a27d56aa" +checksum = "740862893bb5f06ac24acc9d49bdeadc3a5e52e51818a30a25c1f3519da2c851" dependencies = [ "arrayvec 0.7.2", "asynchronous-codec", @@ -3698,11 +3664,11 @@ dependencies = [ "futures", "futures-timer", "instant", - "libp2p-core 0.33.0", + "libp2p-core", "libp2p-swarm", "log", - "prost 0.10.3", - "prost-build 0.10.4", + "prost", + "prost-build", "rand 0.7.3", "sha2 0.10.2", "smallvec", @@ -3714,9 +3680,9 @@ dependencies = [ [[package]] name = "libp2p-mdns" -version = "0.37.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4783f8cf00c7b6c1ff0f1870b4fcf50b042b45533d2e13b6fb464caf447a6951" +checksum = "66e5e5919509603281033fd16306c61df7a4428ce274b67af5e14b07de5cdcb2" dependencies = [ "async-io", "data-encoding", @@ -3724,7 +3690,7 @@ dependencies = [ "futures", "if-watch", "lazy_static", - "libp2p-core 0.33.0", + "libp2p-core", "libp2p-swarm", "log", "rand 0.8.4", @@ -3735,11 +3701,11 @@ dependencies = [ [[package]] name = "libp2p-metrics" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc4357140141ba9739eee71b20aa735351c0fc642635b2bffc7f57a6b5c1090" +checksum = "ef8aff4a1abef42328fbb30b17c853fff9be986dc39af17ee39f9c5f755c5e0c" dependencies = [ - "libp2p-core 0.33.0", + "libp2p-core", "libp2p-gossipsub", "libp2p-identify", "libp2p-kad", @@ -3751,14 +3717,14 @@ dependencies = [ [[package]] name = "libp2p-mplex" -version = "0.33.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ff9c893f2367631a711301d703c47432af898c9bb8253bea0e2c051a13f7640" +checksum = "61fd1b20638ec209c5075dfb2e8ce6a7ea4ec3cd3ad7b77f7a477c06d53322e2" dependencies = [ "asynchronous-codec", "bytes", "futures", - "libp2p-core 0.33.0", + "libp2p-core", "log", "nohash-hasher", "parking_lot 0.12.0", @@ -3769,18 +3735,18 @@ dependencies = [ [[package]] name = "libp2p-noise" -version = "0.36.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2cee1dad1c83325bbd182a8e94555778699cec8a9da00086efb7522c4c15ad" +checksum = "762408cb5d84b49a600422d7f9a42c18012d8da6ebcd570f9a4a4290ba41fb6f" dependencies = [ "bytes", "curve25519-dalek 3.0.2", "futures", "lazy_static", - "libp2p-core 0.33.0", + "libp2p-core", "log", - "prost 0.10.3", - "prost-build 0.10.4", + "prost", + "prost-build", "rand 0.8.4", "sha2 0.10.2", "snow", @@ -3791,14 +3757,14 @@ dependencies = [ [[package]] name = "libp2p-ping" -version = "0.36.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d41516c82fe8dd148ec925eead0c5ec08a0628f7913597e93e126e4dfb4e0787" +checksum = "100a6934ae1dbf8a693a4e7dd1d730fd60b774dafc45688ed63b554497c6c925" dependencies = [ "futures", "futures-timer", "instant", - "libp2p-core 0.33.0", + "libp2p-core", "libp2p-swarm", "log", "rand 0.7.3", @@ -3807,17 +3773,17 @@ dependencies = [ [[package]] name = "libp2p-plaintext" -version = "0.33.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db007e737adc5d28b2e03223b0210164928ad742591127130796a72aa8eaf54f" +checksum = "be27bf0820a6238a4e06365b096d428271cce85a129cf16f2fe9eb1610c4df86" dependencies = [ "asynchronous-codec", "bytes", "futures", - "libp2p-core 0.33.0", + "libp2p-core", "log", - "prost 0.10.3", - "prost-build 0.10.4", + "prost", + "prost-build", "unsigned-varint", "void", ] @@ -3830,7 +3796,7 @@ checksum = "0f1a458bbda880107b5b36fcb9b5a1ef0c329685da0e203ed692a8ebe64cc92c" dependencies = [ "futures", "log", - "pin-project 1.0.10", + "pin-project", "rand 0.7.3", "salsa20", "sha3 0.9.1", @@ -3838,9 +3804,9 @@ dependencies = [ [[package]] name = "libp2p-relay" -version = "0.9.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624ead3406f64437a0d4567c31bd128a9a0b8226d5f16c074038f5d0fc32f650" +checksum = "4931547ee0cce03971ccc1733ff05bb0c4349fd89120a39e9861e2bbe18843c3" dependencies = [ "asynchronous-codec", "bytes", @@ -3848,12 +3814,12 @@ dependencies = [ "futures", "futures-timer", "instant", - "libp2p-core 0.33.0", + "libp2p-core", "libp2p-swarm", "log", - "pin-project 1.0.10", - "prost 0.10.3", - "prost-build 0.10.4", + "pin-project", + "prost", + "prost-build", "prost-codec", "rand 0.8.4", "smallvec", @@ -3864,20 +3830,20 @@ dependencies = [ [[package]] name = "libp2p-rendezvous" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59967ea2db2c7560f641aa58ac05982d42131863fcd3dd6dcf0dd1daf81c60c" +checksum = "9511c9672ba33284838e349623319c8cad2d18cfad243ae46c6b7e8a2982ea4e" dependencies = [ "asynchronous-codec", "bimap", "futures", "futures-timer", "instant", - "libp2p-core 0.33.0", + "libp2p-core", "libp2p-swarm", "log", - "prost 0.10.3", - "prost-build 0.10.4", + "prost", + "prost-build", "rand 0.8.4", "sha2 0.10.2", "thiserror", @@ -3887,15 +3853,15 @@ dependencies = [ [[package]] name = "libp2p-request-response" -version = "0.18.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b02e0acb725e5a757d77c96b95298fd73a7394fe82ba7b8bbeea510719cbe441" +checksum = "508a189e2795d892c8f5c1fa1e9e0b1845d32d7b0b249dbf7b05b18811361843" dependencies = [ "async-trait", "bytes", "futures", "instant", - "libp2p-core 0.33.0", + "libp2p-core", "libp2p-swarm", "log", "rand 0.7.3", @@ -3905,18 +3871,18 @@ dependencies = [ [[package]] name = "libp2p-swarm" -version = "0.36.1" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4bb21c5abadbf00360c734f16bf87f1712ed4f23cd46148f625d2ddb867346" +checksum = "95ac5be6c2de2d1ff3f7693fda6faf8a827b1f3e808202277783fea9f527d114" dependencies = [ "either", "fnv", "futures", "futures-timer", "instant", - "libp2p-core 0.33.0", + "libp2p-core", "log", - "pin-project 1.0.10", + "pin-project", "rand 0.7.3", "smallvec", "thiserror", @@ -3925,9 +3891,9 @@ dependencies = [ [[package]] name = "libp2p-swarm-derive" -version = "0.27.1" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf2fe8c80b43561355f4d51875273b5b6dfbac37952e8f64b1270769305c9d7" +checksum = "9f54a64b6957249e0ce782f8abf41d97f69330d02bf229f0672d864f0650cc76" dependencies = [ "quote", "syn", @@ -3935,9 +3901,9 @@ dependencies = [ [[package]] name = "libp2p-tcp" -version = "0.33.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f4933e38ef21b50698aefc87799c24f2a365c9d3f6cf50471f3f6a0bc410892" +checksum = "8a6771dc19aa3c65d6af9a8c65222bfc8fcd446630ddca487acd161fa6096f3b" dependencies = [ "async-io", "futures", @@ -3945,32 +3911,32 @@ dependencies = [ "if-watch", "ipnet", "libc", - "libp2p-core 0.33.0", + "libp2p-core", "log", "socket2 0.4.4", ] [[package]] name = "libp2p-uds" -version = "0.32.0" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24bdab114f7f2701757d6541266e1131b429bbae382008f207f2114ee4222dcb" +checksum = "d125e3e5f0d58f3c6ac21815b20cf4b6a88b8db9dc26368ea821838f4161fd4d" dependencies = [ "async-std", "futures", - "libp2p-core 0.32.1", + "libp2p-core", "log", ] [[package]] name = "libp2p-wasm-ext" -version = "0.33.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f066f2b8b1a1d64793f05da2256e6842ecd0293d6735ca2e9bda89831a1bdc06" +checksum = "ec894790eec3c1608f8d1a8a0bdf0dbeb79ed4de2dce964222011c2896dfa05a" dependencies = [ "futures", "js-sys", - "libp2p-core 0.33.0", + "libp2p-core", "parity-send-wrapper", "wasm-bindgen", "wasm-bindgen-futures", @@ -3978,18 +3944,18 @@ dependencies = [ [[package]] name = "libp2p-websocket" -version = "0.35.0" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39d398fbb29f432c4128fabdaac2ed155c3bcaf1b9bd40eeeb10a471eefacbf5" +checksum = "9808e57e81be76ff841c106b4c5974fb4d41a233a7bdd2afbf1687ac6def3818" dependencies = [ "either", "futures", "futures-rustls", - "libp2p-core 0.33.0", + "libp2p-core", "log", "parking_lot 0.12.0", "quicksink", - "rw-stream-sink 0.3.0", + "rw-stream-sink", "soketto", "url", "webpki-roots", @@ -3997,12 +3963,12 @@ dependencies = [ [[package]] name = "libp2p-yamux" -version = "0.37.0" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fe653639ad74877c759720febb0cbcbf4caa221adde4eed2d3126ce5c6f381f" +checksum = "c6dea686217a06072033dc025631932810e2f6ad784e4fafa42e27d311c7a81c" dependencies = [ "futures", - "libp2p-core 0.33.0", + "libp2p-core", "parking_lot 0.12.0", "thiserror", "yamux", @@ -4462,7 +4428,7 @@ dependencies = [ "bytes", "futures", "log", - "pin-project 1.0.10", + "pin-project", "smallvec", "unsigned-varint", ] @@ -6836,33 +6802,13 @@ dependencies = [ "indexmap", ] -[[package]] -name = "pin-project" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9615c18d31137579e9ff063499264ddc1278e7b1982757ebc111028c4d1dc909" -dependencies = [ - "pin-project-internal 0.4.29", -] - [[package]] name = "pin-project" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" dependencies = [ - "pin-project-internal 1.0.10", -] - -[[package]] -name = "pin-project-internal" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "044964427019eed9d49d9d5bbce6047ef18f37100ea400912a9fa4a3523ab12a" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "pin-project-internal", ] [[package]] @@ -7114,16 +7060,6 @@ dependencies = [ "syn", ] -[[package]] -name = "prost" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" -dependencies = [ - "bytes", - "prost-derive 0.9.0", -] - [[package]] name = "prost" version = "0.10.3" @@ -7131,27 +7067,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc03e116981ff7d8da8e5c220e374587b98d294af7ba7dd7fda761158f00086f" dependencies = [ "bytes", - "prost-derive 0.10.1", -] - -[[package]] -name = "prost-build" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" -dependencies = [ - "bytes", - "heck 0.3.2", - "itertools", - "lazy_static", - "log", - "multimap", - "petgraph", - "prost 0.9.0", - "prost-types 0.9.0", - "regex", - "tempfile", - "which", + "prost-derive", ] [[package]] @@ -7169,8 +7085,8 @@ dependencies = [ "log", "multimap", "petgraph", - "prost 0.10.3", - "prost-types 0.10.1", + "prost", + "prost-types", "regex", "tempfile", "which", @@ -7184,24 +7100,11 @@ checksum = "00af1e92c33b4813cc79fda3f2dbf56af5169709be0202df730e9ebc3e4cd007" dependencies = [ "asynchronous-codec", "bytes", - "prost 0.10.3", + "prost", "thiserror", "unsigned-varint", ] -[[package]] -name = "prost-derive" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" -dependencies = [ - "anyhow", - "itertools", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "prost-derive" version = "0.10.1" @@ -7215,16 +7118,6 @@ dependencies = [ "syn", ] -[[package]] -name = "prost-types" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" -dependencies = [ - "bytes", - "prost 0.9.0", -] - [[package]] name = "prost-types" version = "0.10.1" @@ -7232,7 +7125,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d0a014229361011dc8e69c8a1ec6c2e8d0f2af7c91e3ea3f5b2170298461e68" dependencies = [ "bytes", - "prost 0.10.3", + "prost", ] [[package]] @@ -7922,17 +7815,6 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" -[[package]] -name = "rw-stream-sink" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4da5fcb054c46f5a5dff833b129285a93d3f0179531735e6c866e8cc307d2020" -dependencies = [ - "futures", - "pin-project 0.4.29", - "static_assertions", -] - [[package]] name = "rw-stream-sink" version = "0.3.0" @@ -7940,7 +7822,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26338f5e09bb721b85b135ea05af7767c90b52f6de4f087d4f4a3a9d64e7dc04" dependencies = [ "futures", - "pin-project 1.0.10", + "pin-project", "static_assertions", ] @@ -7998,8 +7880,8 @@ dependencies = [ "libp2p", "log", "parity-scale-codec", - "prost 0.10.3", - "prost-build 0.10.4", + "prost", + "prost-build", "quickcheck", "rand 0.7.3", "sc-client-api", @@ -8651,9 +8533,9 @@ dependencies = [ "lru", "parity-scale-codec", "parking_lot 0.12.0", - "pin-project 1.0.10", - "prost 0.10.3", - "prost-build 0.10.4", + "pin-project", + "prost", + "prost-build", "rand 0.7.3", "sc-block-builder", "sc-client-api", @@ -8691,7 +8573,7 @@ dependencies = [ "futures", "libp2p", "parity-scale-codec", - "prost-build 0.10.4", + "prost-build", "sc-peerset", "smallvec", ] @@ -8723,8 +8605,8 @@ dependencies = [ "libp2p", "log", "parity-scale-codec", - "prost 0.10.3", - "prost-build 0.10.4", + "prost", + "prost-build", "sc-client-api", "sc-network-common", "sc-peerset", @@ -8746,8 +8628,8 @@ dependencies = [ "log", "lru", "parity-scale-codec", - "prost 0.10.3", - "prost-build 0.10.4", + "prost", + "prost-build", "quickcheck", "sc-block-builder", "sc-client-api", @@ -8954,7 +8836,7 @@ dependencies = [ "parity-scale-codec", "parity-util-mem", "parking_lot 0.12.0", - "pin-project 1.0.10", + "pin-project", "rand 0.7.3", "sc-block-builder", "sc-chain-spec", @@ -9099,7 +8981,7 @@ dependencies = [ "libp2p", "log", "parking_lot 0.12.0", - "pin-project 1.0.10", + "pin-project", "rand 0.7.3", "serde", "serde_json", @@ -11189,7 +11071,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" dependencies = [ - "pin-project 1.0.10", + "pin-project", "tracing", ] diff --git a/client/authority-discovery/Cargo.toml b/client/authority-discovery/Cargo.toml index 32a5e23bd49d9..012e6096aab80 100644 --- a/client/authority-discovery/Cargo.toml +++ b/client/authority-discovery/Cargo.toml @@ -22,7 +22,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", default-features = futures = "0.3.21" futures-timer = "3.0.1" ip_network = "0.4.1" -libp2p = { version = "0.45.1", default-features = false, features = ["kad"] } +libp2p = { version = "0.46.1", default-features = false, features = ["kad"] } log = "0.4.17" prost = "0.10" rand = "0.7.2" diff --git a/client/cli/Cargo.toml b/client/cli/Cargo.toml index 72b4efde077ff..ea60e4c9f87e5 100644 --- a/client/cli/Cargo.toml +++ b/client/cli/Cargo.toml @@ -18,7 +18,7 @@ clap = { version = "3.1.18", features = ["derive"] } fdlimit = "0.2.1" futures = "0.3.21" hex = "0.4.2" -libp2p = "0.45.1" +libp2p = "0.46.1" log = "0.4.17" names = { version = "0.13.0", default-features = false } parity-scale-codec = "3.0.0" diff --git a/client/consensus/common/Cargo.toml b/client/consensus/common/Cargo.toml index 6d76eba0935ff..12b630f36b89b 100644 --- a/client/consensus/common/Cargo.toml +++ b/client/consensus/common/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] async-trait = "0.1.42" futures = { version = "0.3.21", features = ["thread-pool"] } futures-timer = "3.0.1" -libp2p = { version = "0.45.1", default-features = false } +libp2p = { version = "0.46.1", default-features = false } log = "0.4.17" parking_lot = "0.12.0" serde = { version = "1.0", features = ["derive"] } diff --git a/client/network-gossip/Cargo.toml b/client/network-gossip/Cargo.toml index 0fac96b0fdde0..144c5ad168996 100644 --- a/client/network-gossip/Cargo.toml +++ b/client/network-gossip/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] ahash = "0.7.6" futures = "0.3.21" futures-timer = "3.0.1" -libp2p = { version = "0.45.1", default-features = false } +libp2p = { version = "0.46.1", default-features = false } log = "0.4.17" lru = "0.7.5" tracing = "0.1.29" diff --git a/client/network/Cargo.toml b/client/network/Cargo.toml index 1b525844395b5..dfd2db2e6b844 100644 --- a/client/network/Cargo.toml +++ b/client/network/Cargo.toml @@ -29,7 +29,7 @@ futures = "0.3.21" futures-timer = "3.0.2" hex = "0.4.0" ip_network = "0.4.1" -libp2p = "0.45.1" +libp2p = "0.46.1" linked_hash_set = "0.1.3" linked-hash-map = "0.5.4" log = "0.4.17" diff --git a/client/network/common/Cargo.toml b/client/network/common/Cargo.toml index 553eb57958a5d..e69787d7aff77 100644 --- a/client/network/common/Cargo.toml +++ b/client/network/common/Cargo.toml @@ -21,6 +21,6 @@ codec = { package = "parity-scale-codec", version = "3.0.0", features = [ "derive", ] } futures = "0.3.21" -libp2p = "0.45.1" +libp2p = "0.46.1" smallvec = "1.8.0" sc-peerset = { version = "4.0.0-dev", path = "../../peerset" } diff --git a/client/network/light/Cargo.toml b/client/network/light/Cargo.toml index 924b8ed06ab0a..0037177fb4046 100644 --- a/client/network/light/Cargo.toml +++ b/client/network/light/Cargo.toml @@ -21,7 +21,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", features = [ "derive", ] } futures = "0.3.21" -libp2p = "0.45.1" +libp2p = "0.46.1" log = "0.4.16" prost = "0.10" sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } diff --git a/client/network/src/discovery.rs b/client/network/src/discovery.rs index 2bae2fb807f7e..f3d1588c0280e 100644 --- a/client/network/src/discovery.rs +++ b/client/network/src/discovery.rs @@ -52,8 +52,8 @@ use futures_timer::Delay; use ip_network::IpNetwork; use libp2p::{ core::{ - connection::{ConnectionId, ListenerId}, - ConnectedPoint, Multiaddr, PeerId, PublicKey, + connection::ConnectionId, transport::ListenerId, ConnectedPoint, Multiaddr, PeerId, + PublicKey, }, kad::{ handler::KademliaHandlerProto, @@ -1030,7 +1030,7 @@ mod tests { let noise_keys = noise::Keypair::::new().into_authentic(&keypair).unwrap(); - let transport = MemoryTransport + let transport = MemoryTransport::new() .upgrade(upgrade::Version::V1) .authenticate(noise::NoiseConfig::xx(noise_keys).into_authenticated()) .multiplex(yamux::YamuxConfig::default()) diff --git a/client/network/src/peer_info.rs b/client/network/src/peer_info.rs index c3e79437bdd06..d668cb25ea455 100644 --- a/client/network/src/peer_info.rs +++ b/client/network/src/peer_info.rs @@ -21,9 +21,8 @@ use fnv::FnvHashMap; use futures::prelude::*; use libp2p::{ core::{ - connection::{ConnectionId, ListenerId}, - either::EitherOutput, - ConnectedPoint, PeerId, PublicKey, + connection::ConnectionId, either::EitherOutput, transport::ListenerId, ConnectedPoint, + PeerId, PublicKey, }, identify::{Identify, IdentifyConfig, IdentifyEvent, IdentifyInfo}, ping::{Ping, PingConfig, PingEvent, PingSuccess}, diff --git a/client/network/src/protocol.rs b/client/network/src/protocol.rs index 387a7b3fdde90..348d2d0bf8877 100644 --- a/client/network/src/protocol.rs +++ b/client/network/src/protocol.rs @@ -27,10 +27,7 @@ use bytes::Bytes; use codec::{Decode, DecodeAll, Encode}; use futures::{channel::oneshot, prelude::*}; use libp2p::{ - core::{ - connection::{ConnectionId, ListenerId}, - ConnectedPoint, - }, + core::{connection::ConnectionId, transport::ListenerId, ConnectedPoint}, request_response::OutboundFailure, swarm::{ ConnectionHandler, IntoConnectionHandler, NetworkBehaviour, NetworkBehaviourAction, diff --git a/client/network/src/protocol/notifications/tests.rs b/client/network/src/protocol/notifications/tests.rs index da12dbf216c4c..fa79366d20283 100644 --- a/client/network/src/protocol/notifications/tests.rs +++ b/client/network/src/protocol/notifications/tests.rs @@ -23,8 +23,8 @@ use crate::protocol::notifications::{Notifications, NotificationsOut, ProtocolCo use futures::prelude::*; use libp2p::{ core::{ - connection::{ConnectionId, ListenerId}, - transport::MemoryTransport, + connection::ConnectionId, + transport::{ListenerId, MemoryTransport}, upgrade, ConnectedPoint, }, identity, noise, @@ -56,7 +56,7 @@ fn build_nodes() -> (Swarm, Swarm) { let noise_keys = noise::Keypair::::new().into_authentic(&keypair).unwrap(); - let transport = MemoryTransport + let transport = MemoryTransport::new() .upgrade(upgrade::Version::V1) .authenticate(noise::NoiseConfig::xx(noise_keys).into_authenticated()) .multiplex(yamux::YamuxConfig::default()) diff --git a/client/network/src/request_responses.rs b/client/network/src/request_responses.rs index 6c7b0f3fcdfc8..cec4aa2a07fba 100644 --- a/client/network/src/request_responses.rs +++ b/client/network/src/request_responses.rs @@ -40,10 +40,7 @@ use futures::{ prelude::*, }; use libp2p::{ - core::{ - connection::{ConnectionId, ListenerId}, - ConnectedPoint, Multiaddr, PeerId, - }, + core::{connection::ConnectionId, transport::ListenerId, ConnectedPoint, Multiaddr, PeerId}, request_response::{ handler::RequestResponseHandler, ProtocolSupport, RequestResponse, RequestResponseCodec, RequestResponseConfig, RequestResponseEvent, RequestResponseMessage, ResponseChannel, @@ -968,7 +965,7 @@ mod tests { let noise_keys = noise::Keypair::::new().into_authentic(&keypair).unwrap(); - let transport = MemoryTransport + let transport = MemoryTransport::new() .upgrade(upgrade::Version::V1) .authenticate(noise::NoiseConfig::xx(noise_keys).into_authenticated()) .multiplex(libp2p::yamux::YamuxConfig::default()) diff --git a/client/network/src/transport.rs b/client/network/src/transport.rs index 883f828e7db51..23645b11795c3 100644 --- a/client/network/src/transport.rs +++ b/client/network/src/transport.rs @@ -54,16 +54,17 @@ pub fn build_transport( ) -> (Boxed<(PeerId, StreamMuxerBox)>, Arc) { // Build the base layer of the transport. let transport = if !memory_only { - let desktop_trans = tcp::TcpConfig::new().nodelay(true); + let tcp_config = tcp::GenTcpConfig::new().nodelay(true); + let desktop_trans = tcp::TcpTransport::new(tcp_config.clone()); let desktop_trans = websocket::WsConfig::new(desktop_trans) - .or_transport(tcp::TcpConfig::new().nodelay(true)); + .or_transport(tcp::TcpTransport::new(tcp_config.clone())); let dns_init = futures::executor::block_on(dns::DnsConfig::system(desktop_trans)); EitherTransport::Left(if let Ok(dns) = dns_init { EitherTransport::Left(dns) } else { - let desktop_trans = tcp::TcpConfig::new().nodelay(true); + let desktop_trans = tcp::TcpTransport::new(tcp_config.clone()); let desktop_trans = websocket::WsConfig::new(desktop_trans) - .or_transport(tcp::TcpConfig::new().nodelay(true)); + .or_transport(tcp::TcpTransport::new(tcp_config)); EitherTransport::Right(desktop_trans.map_err(dns::DnsErr::Transport)) }) } else { diff --git a/client/network/sync/Cargo.toml b/client/network/sync/Cargo.toml index 9185e812bc6cd..a61f780fd56ad 100644 --- a/client/network/sync/Cargo.toml +++ b/client/network/sync/Cargo.toml @@ -23,7 +23,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", features = [ ] } either = "1.5.3" futures = "0.3.21" -libp2p = "0.45.1" +libp2p = "0.46.1" log = "0.4.17" lru = "0.7.5" prost = "0.10" diff --git a/client/network/test/Cargo.toml b/client/network/test/Cargo.toml index 2af1dd8cb9b54..fdb9befc41cb1 100644 --- a/client/network/test/Cargo.toml +++ b/client/network/test/Cargo.toml @@ -17,7 +17,7 @@ async-std = "1.11.0" async-trait = "0.1.50" futures = "0.3.21" futures-timer = "3.0.1" -libp2p = { version = "0.45.1", default-features = false } +libp2p = { version = "0.46.1", default-features = false } log = "0.4.17" parking_lot = "0.12.0" rand = "0.7.2" diff --git a/client/peerset/Cargo.toml b/client/peerset/Cargo.toml index f150c728613ba..510ebf7006155 100644 --- a/client/peerset/Cargo.toml +++ b/client/peerset/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] futures = "0.3.21" -libp2p = { version = "0.45.1", default-features = false } +libp2p = { version = "0.46.1", default-features = false } log = "0.4.17" serde_json = "1.0.79" wasm-timer = "0.2" diff --git a/client/telemetry/Cargo.toml b/client/telemetry/Cargo.toml index 9682dc824f930..0d966dacfc2c8 100644 --- a/client/telemetry/Cargo.toml +++ b/client/telemetry/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] chrono = "0.4.19" futures = "0.3.21" -libp2p = { version = "0.45.1", default-features = false, features = ["dns-async-std", "tcp-async-io", "wasm-ext", "websocket"] } +libp2p = { version = "0.46.1", default-features = false, features = ["dns-async-std", "tcp-async-io", "wasm-ext", "websocket"] } log = "0.4.17" parking_lot = "0.12.0" pin-project = "1.0.10" diff --git a/client/telemetry/src/transport.rs b/client/telemetry/src/transport.rs index e21a2380be255..d64da44a83b6b 100644 --- a/client/telemetry/src/transport.rs +++ b/client/telemetry/src/transport.rs @@ -31,7 +31,8 @@ const CONNECT_TIMEOUT: Duration = Duration::from_secs(20); pub(crate) fn initialize_transport() -> Result { let transport = { - let inner = block_on(libp2p::dns::DnsConfig::system(libp2p::tcp::TcpConfig::new()))?; + let tcp_transport = libp2p::tcp::TcpTransport::new(libp2p::tcp::GenTcpConfig::new()); + let inner = block_on(libp2p::dns::DnsConfig::system(tcp_transport))?; libp2p::websocket::framed::WsConfig::new(inner).and_then(|connec, _| { let connec = connec .with(|item| { From a3d1e768e99ec8b2f21a8b7c410d70a5e2eaca05 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Tue, 12 Jul 2022 18:08:54 +0100 Subject: [PATCH 385/484] prep council election pallet for being dissolved (#11790) * prep council election pallet for being dissolved + make it temporarily bounded * fix tests * fix * Update frame/elections-phragmen/src/lib.rs * fix bench? --- Cargo.lock | 1 + frame/elections-phragmen/Cargo.toml | 1 + frame/elections-phragmen/src/benchmarking.rs | 86 ++----------- frame/elections-phragmen/src/lib.rs | 129 ++++++++----------- 4 files changed, 66 insertions(+), 151 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 26f43fa4ae0ba..f98d98a3c571f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5673,6 +5673,7 @@ dependencies = [ "sp-npos-elections", "sp-runtime", "sp-std", + "sp-tracing", "substrate-test-utils", ] diff --git a/frame/elections-phragmen/Cargo.toml b/frame/elections-phragmen/Cargo.toml index 0f195f12cce3c..d71a74f76a114 100644 --- a/frame/elections-phragmen/Cargo.toml +++ b/frame/elections-phragmen/Cargo.toml @@ -30,6 +30,7 @@ sp-std = { version = "4.0.0", default-features = false, path = "../../primitives [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } sp-core = { version = "6.0.0", path = "../../primitives/core" } +sp-tracing = { path = "../../primitives/tracing" } substrate-test-utils = { version = "4.0.0-dev", path = "../../test-utils" } [features] diff --git a/frame/elections-phragmen/src/benchmarking.rs b/frame/elections-phragmen/src/benchmarking.rs index 44dd6aff09f0c..9a9d448449eca 100644 --- a/frame/elections-phragmen/src/benchmarking.rs +++ b/frame/elections-phragmen/src/benchmarking.rs @@ -22,17 +22,12 @@ use super::*; use frame_benchmarking::{account, benchmarks, whitelist, BenchmarkError, BenchmarkResult}; -use frame_support::{ - dispatch::{DispatchResultWithPostInfo, UnfilteredDispatchable}, - traits::OnInitialize, -}; +use frame_support::{dispatch::DispatchResultWithPostInfo, traits::OnInitialize}; use frame_system::RawOrigin; use crate::Pallet as Elections; const BALANCE_FACTOR: u32 = 250; -const MAX_VOTERS: u32 = 500; -const MAX_CANDIDATES: u32 = 200; type Lookup = <::Lookup as StaticLookup>::Source; @@ -345,38 +340,6 @@ benchmarks! { ))?; } - // -- Root ones - #[extra] // this calls into phragmen and consumes a full block for now. - remove_member_without_replacement_extra { - // worse case is when we remove a member and we have no runner as a replacement. This - // triggers phragmen again. The only parameter is how many candidates will compete for the - // new slot. - let c in 1 .. MAX_CANDIDATES; - clean::(); - - // fill only desired members. no runners-up. - let all_members = fill_seats_up_to::(T::DesiredMembers::get())?; - assert_eq!(>::members().len() as u32, T::DesiredMembers::get()); - - // submit a new one to compensate, with self-vote. - let replacements = submit_candidates_with_self_vote::(c, "new_candidate")?; - - // create some voters for these replacements. - distribute_voters::(replacements, MAX_VOTERS, MAXIMUM_VOTE)?; - - let to_remove = as_lookup::(all_members[0].clone()); - }: remove_member(RawOrigin::Root, to_remove, false) - verify { - // must still have the desired number of members members. - assert_eq!(>::members().len() as u32, T::DesiredMembers::get()); - #[cfg(test)] - { - // reset members in between benchmark tests. - use crate::tests::MEMBERS; - MEMBERS.with(|m| *m.borrow_mut() = vec![]); - } - } - remove_member_with_replacement { // easy case. We have a runner up. Nothing will have that much of an impact. m will be // number of members and runners. There is always at least one runner. @@ -385,7 +348,7 @@ benchmarks! { let _ = fill_seats_up_to::(m)?; let removing = as_lookup::(>::members_ids()[0].clone()); - }: remove_member(RawOrigin::Root, removing, true) + }: remove_member(RawOrigin::Root, removing, true, false) verify { // must still have enough members. assert_eq!(>::members().len() as u32, T::DesiredMembers::get()); @@ -397,39 +360,6 @@ benchmarks! { } } - remove_member_wrong_refund { - // The root call by mistake indicated that this will have no replacement, while it has! - // this has now consumed a lot of weight and need to refund. - let m = T::DesiredMembers::get() + T::DesiredRunnersUp::get(); - clean::(); - - let _ = fill_seats_up_to::(m)?; - let removing = as_lookup::(>::members_ids()[0].clone()); - let who = T::Lookup::lookup(removing.clone()).expect("member was added above"); - let call = Call::::remove_member { who: removing, has_replacement: false }.encode(); - }: { - assert_eq!( - as Decode>::decode(&mut &*call) - .expect("call is encoded above, encoding must be correct") - .dispatch_bypass_filter(RawOrigin::Root.into()) - .unwrap_err() - .error, - Error::::InvalidReplacement.into(), - ); - } - verify { - // must still have enough members. - assert_eq!(>::members().len() as u32, T::DesiredMembers::get()); - // on fail, `who` must still be a member - assert!(>::members_ids().contains(&who)); - #[cfg(test)] - { - // reset members in between benchmark tests. - use crate::tests::MEMBERS; - MEMBERS.with(|m| *m.borrow_mut() = vec![]); - } - } - clean_defunct_voters { // total number of voters. let v in (MAX_VOTERS / 2) .. MAX_VOTERS; @@ -439,7 +369,7 @@ benchmarks! { // remove any previous stuff. clean::(); - let all_candidates = submit_candidates::(v, "candidates")?; + let all_candidates = submit_candidates::(MAX_CANDIDATES, "candidates")?; distribute_voters::(all_candidates, v, MAXIMUM_VOTE)?; // all candidates leave. @@ -474,7 +404,7 @@ benchmarks! { let votes_per_voter = (e / v).min(MAXIMUM_VOTE as u32); let all_candidates = submit_candidates_with_self_vote::(c, "candidates")?; - let _ = distribute_voters::(all_candidates, v, votes_per_voter as usize)?; + let _ = distribute_voters::(all_candidates, v.saturating_sub(c), votes_per_voter as usize)?; }: { >::on_initialize(T::TermDuration::get()); } @@ -503,7 +433,11 @@ benchmarks! { let votes_per_voter = e / fixed_v; let all_candidates = submit_candidates_with_self_vote::(c, "candidates")?; - let _ = distribute_voters::(all_candidates, fixed_v, votes_per_voter as usize)?; + let _ = distribute_voters::( + all_candidates, + fixed_v - c, + votes_per_voter as usize, + )?; }: { >::on_initialize(T::TermDuration::get()); } @@ -525,7 +459,7 @@ benchmarks! { #[extra] election_phragmen_v { let v in 4 .. 16; - let fixed_c = MAX_CANDIDATES; + let fixed_c = MAX_CANDIDATES / 10; let fixed_e = 64; clean::(); diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index ec2234cde5a6e..28fed28f18e5c 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -100,7 +100,6 @@ use codec::{Decode, Encode}; use frame_support::{ - dispatch::WithPostDispatchInfo, traits::{ defensive_prelude::*, ChangeMembers, Contains, ContainsLengthBound, Currency, CurrencyToVote, Get, InitializeMembers, LockIdentifier, LockableCurrency, OnUnbalanced, @@ -126,6 +125,17 @@ pub mod migrations; /// The maximum votes allowed per voter. pub const MAXIMUM_VOTE: usize = 16; +// Some safe temp values to make the wasm execution sane while we still use this pallet. +#[cfg(test)] +pub(crate) const MAX_CANDIDATES: u32 = 100; +#[cfg(not(test))] +pub(crate) const MAX_CANDIDATES: u32 = 1000; + +#[cfg(test)] +pub(crate) const MAX_VOTERS: u32 = 1000; +#[cfg(not(test))] +pub(crate) const MAX_VOTERS: u32 = 10 * 1000; + type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; type NegativeImbalanceOf = <::Currency as Currency< @@ -385,8 +395,9 @@ pub mod pallet { ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - let actual_count = >::decode_len().unwrap_or(0); - ensure!(actual_count as u32 <= candidate_count, Error::::InvalidWitnessData); + let actual_count = >::decode_len().unwrap_or(0) as u32; + ensure!(actual_count <= candidate_count, Error::::InvalidWitnessData); + ensure!(actual_count <= MAX_CANDIDATES, Error::::TooManyCandidates); let index = Self::is_candidate(&who).err().ok_or(Error::::DuplicatedCandidate)?; @@ -469,7 +480,11 @@ pub mod pallet { /// the outgoing member is slashed. /// /// If a runner-up is available, then the best runner-up will be removed and replaces the - /// outgoing member. Otherwise, a new phragmen election is started. + /// outgoing member. Otherwise, if `rerun_election` is `true`, a new phragmen election is + /// started, else, nothing happens. + /// + /// If `slash_bond` is set to true, the bond of the member being removed is slashed. Else, + /// it is returned. /// /// The dispatch origin of this call must be root. /// @@ -479,33 +494,24 @@ pub mod pallet { /// If we have a replacement, we use a small weight. Else, since this is a root call and /// will go into phragmen, we assume full block for now. /// # - #[pallet::weight(if *has_replacement { - T::WeightInfo::remove_member_with_replacement() - } else { + #[pallet::weight(if *rerun_election { T::WeightInfo::remove_member_without_replacement() + } else { + T::WeightInfo::remove_member_with_replacement() })] pub fn remove_member( origin: OriginFor, who: ::Source, - has_replacement: bool, + slash_bond: bool, + rerun_election: bool, ) -> DispatchResultWithPostInfo { ensure_root(origin)?; let who = T::Lookup::lookup(who)?; - let will_have_replacement = >::decode_len().map_or(false, |l| l > 0); - if will_have_replacement != has_replacement { - // In both cases, we will change more weight than need. Refund and abort. - return Err(Error::::InvalidReplacement.with_weight( - // refund. The weight value comes from a benchmark which is special to this. - T::WeightInfo::remove_member_wrong_refund(), - )) - } - - let had_replacement = Self::remove_and_replace_member(&who, true)?; - debug_assert_eq!(has_replacement, had_replacement); + let _ = Self::remove_and_replace_member(&who, slash_bond)?; Self::deposit_event(Event::MemberKicked { member: who }); - if !had_replacement { + if rerun_election { Self::do_phragmen(); } @@ -585,10 +591,10 @@ pub mod pallet { UnableToPayBond, /// Must be a voter. MustBeVoter, - /// Cannot report self. - ReportSelf, /// Duplicated candidate submission. DuplicatedCandidate, + /// Too many candidates have been created. + TooManyCandidates, /// Member cannot re-submit candidacy. MemberSubmit, /// Runner cannot re-submit candidacy. @@ -893,7 +899,7 @@ impl Pallet { if candidates_and_deposit.len().is_zero() { Self::deposit_event(Event::EmptyTerm); - return T::DbWeight::get().reads(5) + return T::DbWeight::get().reads(3) } // All of the new winners that come out of phragmen will thus have a deposit recorded. @@ -906,10 +912,28 @@ impl Pallet { let to_balance = |e: ExtendedBalance| T::CurrencyToVote::to_currency(e, total_issuance); let mut num_edges: u32 = 0; + // used for prime election. - let voters_and_stakes = Voting::::iter() - .map(|(voter, Voter { stake, votes, .. })| (voter, stake, votes)) - .collect::>(); + let mut voters_and_stakes = Vec::new(); + match Voting::::iter().try_for_each(|(voter, Voter { stake, votes, .. })| { + if voters_and_stakes.len() < MAX_VOTERS as usize { + voters_and_stakes.push((voter, stake, votes)); + Ok(()) + } else { + Err(()) + } + }) { + Ok(_) => (), + Err(_) => { + log::error!( + target: "runtime::elections-phragmen", + "Failed to run election. Number of voters exceeded", + ); + Self::deposit_event(Event::ElectionError); + return T::DbWeight::get().reads(3 + MAX_VOTERS as u64) + }, + } + // used for phragmen. let voters_and_votes = voters_and_stakes .iter() @@ -1137,7 +1161,7 @@ mod tests { use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, ModuleError, + BuildStorage, }; use substrate_test_utils::assert_eq_uvec; @@ -1321,6 +1345,7 @@ mod tests { self } pub fn build_and_execute(self, test: impl FnOnce() -> ()) { + sp_tracing::try_init_simple(); MEMBERS.with(|m| { *m.borrow_mut() = self.genesis_members.iter().map(|(m, _)| m.clone()).collect::>() @@ -2494,7 +2519,7 @@ mod tests { assert_ok!(submit_candidacy(Origin::signed(3))); assert_ok!(vote(Origin::signed(3), vec![3], 30)); - assert_ok!(Elections::remove_member(Origin::root(), 4, false)); + assert_ok!(Elections::remove_member(Origin::root(), 4, true, true)); assert_eq!(balances(&4), (35, 2)); // slashed assert_eq!(Elections::election_rounds(), 2); // new election round @@ -2502,52 +2527,6 @@ mod tests { }); } - #[test] - fn remove_member_should_indicate_replacement() { - ExtBuilder::default().build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); - - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - assert_ok!(vote(Origin::signed(5), vec![5], 50)); - - System::set_block_number(5); - Elections::on_initialize(System::block_number()); - assert_eq!(members_ids(), vec![4, 5]); - - // no replacement yet. - let unwrapped_error = Elections::remove_member(Origin::root(), 4, true).unwrap_err(); - assert!(matches!( - unwrapped_error.error, - DispatchError::Module(ModuleError { message: Some("InvalidReplacement"), .. }) - )); - assert!(unwrapped_error.post_info.actual_weight.is_some()); - }); - - ExtBuilder::default().desired_runners_up(1).build_and_execute(|| { - assert_ok!(submit_candidacy(Origin::signed(5))); - assert_ok!(submit_candidacy(Origin::signed(4))); - assert_ok!(submit_candidacy(Origin::signed(3))); - - assert_ok!(vote(Origin::signed(3), vec![3], 30)); - assert_ok!(vote(Origin::signed(4), vec![4], 40)); - assert_ok!(vote(Origin::signed(5), vec![5], 50)); - - System::set_block_number(5); - Elections::on_initialize(System::block_number()); - assert_eq!(members_ids(), vec![4, 5]); - assert_eq!(runners_up_ids(), vec![3]); - - // there is a replacement! and this one needs a weight refund. - let unwrapped_error = Elections::remove_member(Origin::root(), 4, false).unwrap_err(); - assert!(matches!( - unwrapped_error.error, - DispatchError::Module(ModuleError { message: Some("InvalidReplacement"), .. }) - )); - assert!(unwrapped_error.post_info.actual_weight.is_some()); - }); - } - #[test] fn seats_should_be_released_when_no_vote() { ExtBuilder::default().build_and_execute(|| { @@ -2684,7 +2663,7 @@ mod tests { Elections::on_initialize(System::block_number()); assert_eq!(members_ids(), vec![2, 4]); - assert_ok!(Elections::remove_member(Origin::root(), 2, true)); + assert_ok!(Elections::remove_member(Origin::root(), 2, true, false)); assert_eq!(members_ids(), vec![4, 5]); }); } From 4168e6642a4ed23ff134ab3bba0dfd3ee39b9fe7 Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Tue, 12 Jul 2022 23:34:17 +0300 Subject: [PATCH 386/484] Network sync refactoring (part 4) (#11412) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove direct dependency of `sc-network` on `sc-network-light` * Move `WarpSyncProvider` trait and surrounding data structures into `sc-network-common` * Move `WarpSyncProvider` trait and surrounding data structures into `sc-network-common` * Create `sync` module in `sc-network-common`, create `ChainSync` trait there (not used yet), move a bunch of associated data structures from `sc-network-sync` * Switch from concrete implementation to `ChainSync` trait from `sc-network-common` * Introduce `OpaqueStateRequest`/`OpaqueStateResponse` to remove generics from `StateSync` trait * Introduce `OpaqueBlockRequest`/`OpaqueBlockResponse`, make `scheme` module of `sc-network-sync` private * Surface `sc-network-sync` into `sc-service` and make `sc-network` not depend on it anymore * Remove now unnecessary dependency from `sc-network` * Replace crate links with just text since dependencies are gone now * Remove `warp_sync` re-export from `sc-network-common` * Update copyright in network-related files * Address review comments about documentation * Apply review suggestion * Rename `extra_requests` module to `metrics` Co-authored-by: Bastian Köcher --- Cargo.lock | 13 +- client/beefy/src/tests.rs | 14 +- client/consensus/aura/src/lib.rs | 14 +- client/consensus/babe/src/tests.rs | 15 +- client/finality-grandpa/Cargo.toml | 1 + client/finality-grandpa/src/tests.rs | 20 +- client/finality-grandpa/src/warp_proof.rs | 2 +- client/network/Cargo.toml | 5 +- client/network/common/Cargo.toml | 5 + client/network/common/src/lib.rs | 1 + client/network/common/src/sync.rs | 394 ++++++ .../{sync/src => common/src/sync}/message.rs | 2 +- client/network/common/src/sync/metrics.rs | 25 + client/network/common/src/sync/warp.rs | 94 ++ client/network/src/behaviour.rs | 68 +- client/network/src/bitswap.rs | 5 +- client/network/src/config.rs | 33 +- client/network/src/lib.rs | 8 +- client/network/src/protocol.rs | 310 ++--- client/network/src/protocol/message.rs | 10 +- client/network/src/service.rs | 27 +- client/network/src/service/tests.rs | 24 +- client/network/sync/Cargo.toml | 2 - .../network/sync/src/block_request_handler.rs | 6 +- client/network/sync/src/blocks.rs | 4 +- client/network/sync/src/extra_requests.rs | 11 +- client/network/sync/src/lib.rs | 1112 ++++++++--------- client/network/sync/src/schema.rs | 2 +- client/network/sync/src/state.rs | 10 +- client/network/sync/src/warp.rs | 45 +- .../network/sync/src/warp_request_handler.rs | 47 +- client/network/test/Cargo.toml | 2 + client/network/test/src/lib.rs | 110 +- client/service/Cargo.toml | 4 +- client/service/src/builder.rs | 28 +- 35 files changed, 1359 insertions(+), 1114 deletions(-) create mode 100644 client/network/common/src/sync.rs rename client/network/{sync/src => common/src/sync}/message.rs (99%) create mode 100644 client/network/common/src/sync/metrics.rs create mode 100644 client/network/common/src/sync/warp.rs diff --git a/Cargo.lock b/Cargo.lock index f98d98a3c571f..3b7c315be4390 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8429,6 +8429,7 @@ dependencies = [ "sc-consensus", "sc-keystore", "sc-network", + "sc-network-common", "sc-network-gossip", "sc-network-test", "sc-telemetry", @@ -8553,7 +8554,6 @@ dependencies = [ "sp-blockchain", "sp-consensus", "sp-core", - "sp-finality-grandpa", "sp-runtime", "sp-test-primitives", "sp-tracing", @@ -8571,12 +8571,17 @@ dependencies = [ name = "sc-network-common" version = "0.10.0-dev" dependencies = [ + "bitflags", "futures", "libp2p", "parity-scale-codec", "prost-build", + "sc-consensus", "sc-peerset", "smallvec", + "sp-consensus", + "sp-finality-grandpa", + "sp-runtime", ] [[package]] @@ -8621,8 +8626,6 @@ dependencies = [ name = "sc-network-sync" version = "0.10.0-dev" dependencies = [ - "bitflags", - "either", "fork-tree", "futures", "libp2p", @@ -8667,6 +8670,8 @@ dependencies = [ "sc-consensus", "sc-network", "sc-network-common", + "sc-network-light", + "sc-network-sync", "sc-service", "sp-blockchain", "sp-consensus", @@ -8849,6 +8854,8 @@ dependencies = [ "sc-keystore", "sc-network", "sc-network-common", + "sc-network-light", + "sc-network-sync", "sc-offchain", "sc-rpc", "sc-rpc-server", diff --git a/client/beefy/src/tests.rs b/client/beefy/src/tests.rs index b5ff27c808908..8090c425e71db 100644 --- a/client/beefy/src/tests.rs +++ b/client/beefy/src/tests.rs @@ -28,7 +28,6 @@ use sc_chain_spec::{ChainSpec, GenericChainSpec}; use sc_client_api::HeaderBackend; use sc_consensus::BoxJustificationImport; use sc_keystore::LocalKeystore; -use sc_network::config::ProtocolConfig; use sc_network_test::{ Block, BlockImportAdapter, FullPeerConfig, PassThroughVerifier, Peer, PeersClient, TestNetFactory, @@ -111,6 +110,7 @@ pub(crate) struct PeerData { pub(crate) beefy_link_half: Mutex>, } +#[derive(Default)] pub(crate) struct BeefyTestNet { peers: Vec, } @@ -166,17 +166,7 @@ impl TestNetFactory for BeefyTestNet { type BlockImport = PeersClient; type PeerData = PeerData; - /// Create new test network with peers and given config. - fn from_config(_config: &ProtocolConfig) -> Self { - BeefyTestNet { peers: Vec::new() } - } - - fn make_verifier( - &self, - _client: PeersClient, - _cfg: &ProtocolConfig, - _: &PeerData, - ) -> Self::Verifier { + fn make_verifier(&self, _client: PeersClient, _: &PeerData) -> Self::Verifier { PassThroughVerifier::new(false) // use non-instant finality. } diff --git a/client/consensus/aura/src/lib.rs b/client/consensus/aura/src/lib.rs index ac3b89f2ff9a2..ee8be727dcdac 100644 --- a/client/consensus/aura/src/lib.rs +++ b/client/consensus/aura/src/lib.rs @@ -566,7 +566,6 @@ mod tests { use sc_consensus::BoxJustificationImport; use sc_consensus_slots::{BackoffAuthoringOnFinalizedHeadLagging, SimpleSlotWorker}; use sc_keystore::LocalKeystore; - use sc_network::config::ProtocolConfig; use sc_network_test::{Block as TestBlock, *}; use sp_application_crypto::key_types::AURA; use sp_consensus::{ @@ -645,6 +644,7 @@ mod tests { >; type AuraPeer = Peer<(), PeersClient>; + #[derive(Default)] pub struct AuraTestNet { peers: Vec, } @@ -654,17 +654,7 @@ mod tests { type PeerData = (); type BlockImport = PeersClient; - /// Create new test network with peers and given config. - fn from_config(_config: &ProtocolConfig) -> Self { - AuraTestNet { peers: Vec::new() } - } - - fn make_verifier( - &self, - client: PeersClient, - _cfg: &ProtocolConfig, - _peer_data: &(), - ) -> Self::Verifier { + fn make_verifier(&self, client: PeersClient, _peer_data: &()) -> Self::Verifier { let client = client.as_client(); let slot_duration = slot_duration(&*client).expect("slot duration available"); diff --git a/client/consensus/babe/src/tests.rs b/client/consensus/babe/src/tests.rs index e0590fc0cd86e..c0a7a8c6c013a 100644 --- a/client/consensus/babe/src/tests.rs +++ b/client/consensus/babe/src/tests.rs @@ -29,7 +29,6 @@ use sc_client_api::{backend::TransactionFor, BlockchainEvents, Finalizer}; use sc_consensus::{BoxBlockImport, BoxJustificationImport}; use sc_consensus_slots::BackoffAuthoringOnFinalizedHeadLagging; use sc_keystore::LocalKeystore; -use sc_network::config::ProtocolConfig; use sc_network_test::{Block as TestBlock, *}; use sp_application_crypto::key_types::BABE; use sp_consensus::{AlwaysCanAuthor, DisableProofRecording, NoNetwork as DummyOracle, Proposal}; @@ -220,6 +219,7 @@ where type BabePeer = Peer, BabeBlockImport>; +#[derive(Default)] pub struct BabeTestNet { peers: Vec, } @@ -278,12 +278,6 @@ impl TestNetFactory for BabeTestNet { type PeerData = Option; type BlockImport = BabeBlockImport; - /// Create new test network with peers and given config. - fn from_config(_config: &ProtocolConfig) -> Self { - debug!(target: "babe", "Creating test network from config"); - BabeTestNet { peers: Vec::new() } - } - fn make_block_import( &self, client: PeersClient, @@ -309,12 +303,7 @@ impl TestNetFactory for BabeTestNet { ) } - fn make_verifier( - &self, - client: PeersClient, - _cfg: &ProtocolConfig, - maybe_link: &Option, - ) -> Self::Verifier { + fn make_verifier(&self, client: PeersClient, maybe_link: &Option) -> Self::Verifier { use substrate_test_runtime_client::DefaultTestClientBuilderExt; let client = client.as_client(); diff --git a/client/finality-grandpa/Cargo.toml b/client/finality-grandpa/Cargo.toml index 77cd847d48169..a5f20b9f3261d 100644 --- a/client/finality-grandpa/Cargo.toml +++ b/client/finality-grandpa/Cargo.toml @@ -36,6 +36,7 @@ sc-consensus = { version = "0.10.0-dev", path = "../consensus/common" } sc-keystore = { version = "4.0.0-dev", path = "../keystore" } sc-network = { version = "0.10.0-dev", path = "../network" } sc-network-gossip = { version = "0.10.0-dev", path = "../network-gossip" } +sc-network-common = { version = "0.10.0-dev", path = "../network/common" } sc-telemetry = { version = "4.0.0-dev", path = "../telemetry" } sc-utils = { version = "4.0.0-dev", path = "../utils" } sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } diff --git a/client/finality-grandpa/src/tests.rs b/client/finality-grandpa/src/tests.rs index 2d12232b04f15..623ac577c5579 100644 --- a/client/finality-grandpa/src/tests.rs +++ b/client/finality-grandpa/src/tests.rs @@ -28,7 +28,7 @@ use sc_consensus::{ BlockImport, BlockImportParams, BoxJustificationImport, ForkChoiceStrategy, ImportResult, ImportedAux, }; -use sc_network::config::{ProtocolConfig, Role}; +use sc_network::config::Role; use sc_network_test::{ Block, BlockImportAdapter, FullPeerConfig, Hash, PassThroughVerifier, Peer, PeersClient, PeersFullClient, TestClient, TestNetFactory, @@ -73,6 +73,7 @@ type GrandpaBlockImport = crate::GrandpaBlockImport< LongestChain, >; +#[derive(Default)] struct GrandpaTestNet { peers: Vec, test_config: TestApi, @@ -110,16 +111,6 @@ impl TestNetFactory for GrandpaTestNet { type PeerData = PeerData; type BlockImport = GrandpaBlockImport; - /// Create new test network with peers and given config. - fn from_config(_config: &ProtocolConfig) -> Self { - GrandpaTestNet { peers: Vec::new(), test_config: Default::default() } - } - - fn default_config() -> ProtocolConfig { - // This is unused. - ProtocolConfig::default() - } - fn add_full_peer(&mut self) { self.add_full_peer_with_config(FullPeerConfig { notifications_protocols: vec![grandpa_protocol_name::NAME.into()], @@ -128,12 +119,7 @@ impl TestNetFactory for GrandpaTestNet { }) } - fn make_verifier( - &self, - _client: PeersClient, - _cfg: &ProtocolConfig, - _: &PeerData, - ) -> Self::Verifier { + fn make_verifier(&self, _client: PeersClient, _: &PeerData) -> Self::Verifier { PassThroughVerifier::new(false) // use non-instant finality. } diff --git a/client/finality-grandpa/src/warp_proof.rs b/client/finality-grandpa/src/warp_proof.rs index 90f6828a1105d..a31a0a8b91908 100644 --- a/client/finality-grandpa/src/warp_proof.rs +++ b/client/finality-grandpa/src/warp_proof.rs @@ -23,7 +23,7 @@ use crate::{ BlockNumberOps, GrandpaJustification, SharedAuthoritySet, }; use sc_client_api::Backend as ClientBackend; -use sc_network::warp_request_handler::{EncodedProof, VerificationResult, WarpSyncProvider}; +use sc_network_common::sync::warp::{EncodedProof, VerificationResult, WarpSyncProvider}; use sp_blockchain::{Backend as BlockchainBackend, HeaderBackend}; use sp_finality_grandpa::{AuthorityList, SetId, GRANDPA_ENGINE_ID}; use sp_runtime::{ diff --git a/client/network/Cargo.toml b/client/network/Cargo.toml index dfd2db2e6b844..2742262b57e40 100644 --- a/client/network/Cargo.toml +++ b/client/network/Cargo.toml @@ -51,15 +51,12 @@ sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } sc-client-api = { version = "4.0.0-dev", path = "../api" } sc-consensus = { version = "0.10.0-dev", path = "../consensus/common" } sc-network-common = { version = "0.10.0-dev", path = "./common" } -sc-network-light = { version = "0.10.0-dev", path = "./light" } -sc-network-sync = { version = "0.10.0-dev", path = "./sync" } sc-peerset = { version = "4.0.0-dev", path = "../peerset" } sc-utils = { version = "4.0.0-dev", path = "../utils" } sp-arithmetic = { version = "5.0.0", path = "../../primitives/arithmetic" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } sp-core = { version = "6.0.0", path = "../../primitives/core" } -sp-finality-grandpa = { version = "4.0.0-dev", path = "../../primitives/finality-grandpa" } sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } [dev-dependencies] @@ -67,6 +64,8 @@ assert_matches = "1.3" async-std = "1.11.0" rand = "0.7.2" tempfile = "3.1.0" +sc-network-light = { version = "0.10.0-dev", path = "./light" } +sc-network-sync = { version = "0.10.0-dev", path = "./sync" } sp-test-primitives = { version = "2.0.0", path = "../../primitives/test-primitives" } sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } substrate-test-runtime = { version = "2.0.0", path = "../../test-utils/runtime" } diff --git a/client/network/common/Cargo.toml b/client/network/common/Cargo.toml index e69787d7aff77..b0e3a8fe42a83 100644 --- a/client/network/common/Cargo.toml +++ b/client/network/common/Cargo.toml @@ -17,10 +17,15 @@ targets = ["x86_64-unknown-linux-gnu"] prost-build = "0.10" [dependencies] +bitflags = "1.3.2" codec = { package = "parity-scale-codec", version = "3.0.0", features = [ "derive", ] } futures = "0.3.21" libp2p = "0.46.1" smallvec = "1.8.0" +sc-consensus = { version = "0.10.0-dev", path = "../../consensus/common" } sc-peerset = { version = "4.0.0-dev", path = "../../peerset" } +sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } +sp-finality-grandpa = { version = "4.0.0-dev", path = "../../../primitives/finality-grandpa" } +sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } diff --git a/client/network/common/src/lib.rs b/client/network/common/src/lib.rs index 81769e23debbb..9fbedc542c123 100644 --- a/client/network/common/src/lib.rs +++ b/client/network/common/src/lib.rs @@ -21,3 +21,4 @@ pub mod config; pub mod message; pub mod request_responses; +pub mod sync; diff --git a/client/network/common/src/sync.rs b/client/network/common/src/sync.rs new file mode 100644 index 0000000000000..2ee8f8c51814b --- /dev/null +++ b/client/network/common/src/sync.rs @@ -0,0 +1,394 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Abstract interfaces and data structures related to network sync. + +pub mod message; +pub mod metrics; +pub mod warp; + +use libp2p::PeerId; +use message::{BlockAnnounce, BlockData, BlockRequest, BlockResponse}; +use sc_consensus::{BlockImportError, BlockImportStatus, IncomingBlock}; +use sp_consensus::BlockOrigin; +use sp_runtime::{ + traits::{Block as BlockT, NumberFor}, + Justifications, +}; +use std::{any::Any, fmt, fmt::Formatter, task::Poll}; +use warp::{EncodedProof, WarpProofRequest, WarpSyncProgress}; + +/// The sync status of a peer we are trying to sync with +#[derive(Debug)] +pub struct PeerInfo { + /// Their best block hash. + pub best_hash: Block::Hash, + /// Their best block number. + pub best_number: NumberFor, +} + +/// Reported sync state. +#[derive(Clone, Eq, PartialEq, Debug)] +pub enum SyncState { + /// Initial sync is complete, keep-up sync is active. + Idle, + /// Actively catching up with the chain. + Downloading, +} + +/// Reported state download progress. +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct StateDownloadProgress { + /// Estimated download percentage. + pub percentage: u32, + /// Total state size in bytes downloaded so far. + pub size: u64, +} + +/// Syncing status and statistics. +#[derive(Clone)] +pub struct SyncStatus { + /// Current global sync state. + pub state: SyncState, + /// Target sync block number. + pub best_seen_block: Option>, + /// Number of peers participating in syncing. + pub num_peers: u32, + /// Number of blocks queued for import + pub queued_blocks: u32, + /// State sync status in progress, if any. + pub state_sync: Option, + /// Warp sync in progress, if any. + pub warp_sync: Option>, +} + +/// A peer did not behave as expected and should be reported. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct BadPeer(pub PeerId, pub sc_peerset::ReputationChange); + +impl fmt::Display for BadPeer { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Bad peer {}; Reputation change: {:?}", self.0, self.1) + } +} + +impl std::error::Error for BadPeer {} + +/// Result of [`ChainSync::on_block_data`]. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum OnBlockData { + /// The block should be imported. + Import(BlockOrigin, Vec>), + /// A new block request needs to be made to the given peer. + Request(PeerId, BlockRequest), +} + +/// Result of [`ChainSync::on_block_justification`]. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum OnBlockJustification { + /// The justification needs no further handling. + Nothing, + /// The justification should be imported. + Import { + peer: PeerId, + hash: Block::Hash, + number: NumberFor, + justifications: Justifications, + }, +} + +/// Result of [`ChainSync::on_state_data`]. +#[derive(Debug)] +pub enum OnStateData { + /// The block and state that should be imported. + Import(BlockOrigin, IncomingBlock), + /// A new state request needs to be made to the given peer. + Continue, +} + +/// Result of [`ChainSync::poll_block_announce_validation`]. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum PollBlockAnnounceValidation { + /// The announcement failed at validation. + /// + /// The peer reputation should be decreased. + Failure { + /// Who sent the processed block announcement? + who: PeerId, + /// Should the peer be disconnected? + disconnect: bool, + }, + /// The announcement does not require further handling. + Nothing { + /// Who sent the processed block announcement? + who: PeerId, + /// Was this their new best block? + is_best: bool, + /// The announcement. + announce: BlockAnnounce, + }, + /// The announcement header should be imported. + ImportHeader { + /// Who sent the processed block announcement? + who: PeerId, + /// Was this their new best block? + is_best: bool, + /// The announcement. + announce: BlockAnnounce, + }, + /// The block announcement should be skipped. + Skip, +} + +/// Operation mode. +#[derive(Debug, PartialEq, Eq)] +pub enum SyncMode { + // Sync headers only + Light, + // Sync headers and block bodies + Full, + // Sync headers and the last finalied state + LightState { storage_chain_mode: bool, skip_proofs: bool }, + // Warp sync mode. + Warp, +} + +#[derive(Debug)] +pub struct Metrics { + pub queued_blocks: u32, + pub fork_targets: u32, + pub justifications: metrics::Metrics, +} + +/// Wrapper for implementation-specific state request. +/// +/// NOTE: Implementation must be able to encode and decode it for network purposes. +pub struct OpaqueStateRequest(pub Box); + +impl fmt::Debug for OpaqueStateRequest { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("OpaqueStateRequest").finish() + } +} + +/// Wrapper for implementation-specific state response. +/// +/// NOTE: Implementation must be able to encode and decode it for network purposes. +pub struct OpaqueStateResponse(pub Box); + +impl fmt::Debug for OpaqueStateResponse { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("OpaqueStateResponse").finish() + } +} + +/// Wrapper for implementation-specific block request. +/// +/// NOTE: Implementation must be able to encode and decode it for network purposes. +pub struct OpaqueBlockRequest(pub Box); + +impl fmt::Debug for OpaqueBlockRequest { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("OpaqueBlockRequest").finish() + } +} + +/// Wrapper for implementation-specific block response. +/// +/// NOTE: Implementation must be able to encode and decode it for network purposes. +pub struct OpaqueBlockResponse(pub Box); + +impl fmt::Debug for OpaqueBlockResponse { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("OpaqueBlockResponse").finish() + } +} + +/// Something that represents the syncing strategy to download past and future blocks of the chain. +pub trait ChainSync: Send { + /// Returns the state of the sync of the given peer. + /// + /// Returns `None` if the peer is unknown. + fn peer_info(&self, who: &PeerId) -> Option>; + + /// Returns the current sync status. + fn status(&self) -> SyncStatus; + + /// Number of active forks requests. This includes + /// requests that are pending or could be issued right away. + fn num_sync_requests(&self) -> usize; + + /// Number of downloaded blocks. + fn num_downloaded_blocks(&self) -> usize; + + /// Returns the current number of peers stored within this state machine. + fn num_peers(&self) -> usize; + + /// Handle a new connected peer. + /// + /// Call this method whenever we connect to a new peer. + fn new_peer( + &mut self, + who: PeerId, + best_hash: Block::Hash, + best_number: NumberFor, + ) -> Result>, BadPeer>; + + /// Signal that a new best block has been imported. + fn update_chain_info(&mut self, best_hash: &Block::Hash, best_number: NumberFor); + + /// Schedule a justification request for the given block. + fn request_justification(&mut self, hash: &Block::Hash, number: NumberFor); + + /// Clear all pending justification requests. + fn clear_justification_requests(&mut self); + + /// Request syncing for the given block from given set of peers. + fn set_sync_fork_request( + &mut self, + peers: Vec, + hash: &Block::Hash, + number: NumberFor, + ); + + /// Get an iterator over all scheduled justification requests. + fn justification_requests( + &mut self, + ) -> Box)> + '_>; + + /// Get an iterator over all block requests of all peers. + fn block_requests(&mut self) -> Box)> + '_>; + + /// Get a state request, if any. + fn state_request(&mut self) -> Option<(PeerId, OpaqueStateRequest)>; + + /// Get a warp sync request, if any. + fn warp_sync_request(&mut self) -> Option<(PeerId, WarpProofRequest)>; + + /// Handle a response from the remote to a block request that we made. + /// + /// `request` must be the original request that triggered `response`. + /// or `None` if data comes from the block announcement. + /// + /// If this corresponds to a valid block, this outputs the block that + /// must be imported in the import queue. + fn on_block_data( + &mut self, + who: &PeerId, + request: Option>, + response: BlockResponse, + ) -> Result, BadPeer>; + + /// Handle a response from the remote to a state request that we made. + fn on_state_data( + &mut self, + who: &PeerId, + response: OpaqueStateResponse, + ) -> Result, BadPeer>; + + /// Handle a response from the remote to a warp proof request that we made. + fn on_warp_sync_data(&mut self, who: &PeerId, response: EncodedProof) -> Result<(), BadPeer>; + + /// Handle a response from the remote to a justification request that we made. + /// + /// `request` must be the original request that triggered `response`. + fn on_block_justification( + &mut self, + who: PeerId, + response: BlockResponse, + ) -> Result, BadPeer>; + + /// A batch of blocks have been processed, with or without errors. + /// + /// Call this when a batch of blocks have been processed by the import + /// queue, with or without errors. + fn on_blocks_processed( + &mut self, + imported: usize, + count: usize, + results: Vec<(Result>, BlockImportError>, Block::Hash)>, + ) -> Box), BadPeer>>>; + + /// Call this when a justification has been processed by the import queue, + /// with or without errors. + fn on_justification_import( + &mut self, + hash: Block::Hash, + number: NumberFor, + success: bool, + ); + + /// Notify about finalization of the given block. + fn on_block_finalized(&mut self, hash: &Block::Hash, number: NumberFor); + + /// Push a block announce validation. + /// + /// It is required that [`ChainSync::poll_block_announce_validation`] is called + /// to check for finished block announce validations. + fn push_block_announce_validation( + &mut self, + who: PeerId, + hash: Block::Hash, + announce: BlockAnnounce, + is_best: bool, + ); + + /// Poll block announce validation. + /// + /// Block announce validations can be pushed by using + /// [`ChainSync::push_block_announce_validation`]. + /// + /// This should be polled until it returns [`Poll::Pending`]. + /// + /// If [`PollBlockAnnounceValidation::ImportHeader`] is returned, then the caller MUST try to + /// import passed header (call `on_block_data`). The network request isn't sent in this case. + fn poll_block_announce_validation( + &mut self, + cx: &mut std::task::Context, + ) -> Poll>; + + /// Call when a peer has disconnected. + /// Canceled obsolete block request may result in some blocks being ready for + /// import, so this functions checks for such blocks and returns them. + fn peer_disconnected(&mut self, who: &PeerId) -> Option>; + + /// Return some key metrics. + fn metrics(&self) -> Metrics; + + /// Create implementation-specific block request. + fn create_opaque_block_request(&self, request: &BlockRequest) -> OpaqueBlockRequest; + + /// Encode implementation-specific block request. + fn encode_block_request(&self, request: &OpaqueBlockRequest) -> Result, String>; + + /// Decode implementation-specific block response. + fn decode_block_response(&self, response: &[u8]) -> Result; + + /// Access blocks from implementation-specific block response. + fn block_response_into_blocks( + &self, + request: &BlockRequest, + response: OpaqueBlockResponse, + ) -> Result>, String>; + + /// Encode implementation-specific state request. + fn encode_state_request(&self, request: &OpaqueStateRequest) -> Result, String>; + + /// Decode implementation-specific state response. + fn decode_state_response(&self, response: &[u8]) -> Result; +} diff --git a/client/network/sync/src/message.rs b/client/network/common/src/sync/message.rs similarity index 99% rename from client/network/sync/src/message.rs rename to client/network/common/src/sync/message.rs index 996ee5231cf2e..27ab2704e6471 100644 --- a/client/network/sync/src/message.rs +++ b/client/network/common/src/sync/message.rs @@ -124,8 +124,8 @@ impl BlockAnnounce { /// Generic types. pub mod generic { use super::{BlockAttributes, BlockState, Direction}; + use crate::message::RequestId; use codec::{Decode, Encode, Input, Output}; - use sc_network_common::message::RequestId; use sp_runtime::{EncodedJustification, Justifications}; /// Block data sent in the response. diff --git a/client/network/common/src/sync/metrics.rs b/client/network/common/src/sync/metrics.rs new file mode 100644 index 0000000000000..15ff090a8ccac --- /dev/null +++ b/client/network/common/src/sync/metrics.rs @@ -0,0 +1,25 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#[derive(Debug)] +pub struct Metrics { + pub pending_requests: u32, + pub active_requests: u32, + pub importing_requests: u32, + pub failed_requests: u32, +} diff --git a/client/network/common/src/sync/warp.rs b/client/network/common/src/sync/warp.rs new file mode 100644 index 0000000000000..339a4c33a7eeb --- /dev/null +++ b/client/network/common/src/sync/warp.rs @@ -0,0 +1,94 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +use codec::{Decode, Encode}; +pub use sp_finality_grandpa::{AuthorityList, SetId}; +use sp_runtime::traits::{Block as BlockT, NumberFor}; +use std::fmt; + +/// Scale-encoded warp sync proof response. +pub struct EncodedProof(pub Vec); + +/// Warp sync request +#[derive(Encode, Decode, Debug)] +pub struct WarpProofRequest { + /// Start collecting proofs from this block. + pub begin: B::Hash, +} + +/// Proof verification result. +pub enum VerificationResult { + /// Proof is valid, but the target was not reached. + Partial(SetId, AuthorityList, Block::Hash), + /// Target finality is proved. + Complete(SetId, AuthorityList, Block::Header), +} + +/// Warp sync backend. Handles retrieveing and verifying warp sync proofs. +pub trait WarpSyncProvider: Send + Sync { + /// Generate proof starting at given block hash. The proof is accumulated until maximum proof + /// size is reached. + fn generate( + &self, + start: Block::Hash, + ) -> Result>; + /// Verify warp proof against current set of authorities. + fn verify( + &self, + proof: &EncodedProof, + set_id: SetId, + authorities: AuthorityList, + ) -> Result, Box>; + /// Get current list of authorities. This is supposed to be genesis authorities when starting + /// sync. + fn current_authorities(&self) -> AuthorityList; +} + +/// Reported warp sync phase. +#[derive(Clone, Eq, PartialEq, Debug)] +pub enum WarpSyncPhase { + /// Waiting for peers to connect. + AwaitingPeers, + /// Downloading and verifying grandpa warp proofs. + DownloadingWarpProofs, + /// Downloading state data. + DownloadingState, + /// Importing state. + ImportingState, + /// Downloading block history. + DownloadingBlocks(NumberFor), +} + +impl fmt::Display for WarpSyncPhase { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::AwaitingPeers => write!(f, "Waiting for peers"), + Self::DownloadingWarpProofs => write!(f, "Downloading finality proofs"), + Self::DownloadingState => write!(f, "Downloading state"), + Self::ImportingState => write!(f, "Importing state"), + Self::DownloadingBlocks(n) => write!(f, "Downloading block history (#{})", n), + } + } +} + +/// Reported warp sync progress. +#[derive(Clone, Eq, PartialEq, Debug)] +pub struct WarpSyncProgress { + /// Estimated download percentage. + pub phase: WarpSyncPhase, + /// Total bytes downloaded so far. + pub total_bytes: u64, +} diff --git a/client/network/src/behaviour.rs b/client/network/src/behaviour.rs index 091dd116e4c9c..515608df13d0f 100644 --- a/client/network/src/behaviour.rs +++ b/client/network/src/behaviour.rs @@ -38,7 +38,7 @@ use libp2p::{ NetworkBehaviour, }; use log::debug; -use prost::Message; + use sc_client_api::{BlockBackend, ProofProvider}; use sc_consensus::import_queue::{IncomingBlock, Origin}; use sc_network_common::{config::ProtocolId, request_responses::ProtocolConfig}; @@ -382,42 +382,44 @@ where .events .push_back(BehaviourOut::JustificationImport(origin, hash, nb, justification)), CustomMessageOutcome::BlockRequest { target, request, pending_response } => { - let mut buf = Vec::with_capacity(request.encoded_len()); - if let Err(err) = request.encode(&mut buf) { - log::warn!( - target: "sync", - "Failed to encode block request {:?}: {:?}", - request, err - ); - return + match self.substrate.encode_block_request(&request) { + Ok(data) => { + self.request_responses.send_request( + &target, + &self.block_request_protocol_name, + data, + pending_response, + IfDisconnected::ImmediateError, + ); + }, + Err(err) => { + log::warn!( + target: "sync", + "Failed to encode block request {:?}: {:?}", + request, err + ); + }, } - - self.request_responses.send_request( - &target, - &self.block_request_protocol_name, - buf, - pending_response, - IfDisconnected::ImmediateError, - ); }, CustomMessageOutcome::StateRequest { target, request, pending_response } => { - let mut buf = Vec::with_capacity(request.encoded_len()); - if let Err(err) = request.encode(&mut buf) { - log::warn!( - target: "sync", - "Failed to encode state request {:?}: {:?}", - request, err - ); - return + match self.substrate.encode_state_request(&request) { + Ok(data) => { + self.request_responses.send_request( + &target, + &self.state_request_protocol_name, + data, + pending_response, + IfDisconnected::ImmediateError, + ); + }, + Err(err) => { + log::warn!( + target: "sync", + "Failed to encode state request {:?}: {:?}", + request, err + ); + }, } - - self.request_responses.send_request( - &target, - &self.state_request_protocol_name, - buf, - pending_response, - IfDisconnected::ImmediateError, - ); }, CustomMessageOutcome::WarpSyncRequest { target, request, pending_response } => match &self.warp_sync_protocol_name { diff --git a/client/network/src/bitswap.rs b/client/network/src/bitswap.rs index d5039faaca113..2dab45adc5618 100644 --- a/client/network/src/bitswap.rs +++ b/client/network/src/bitswap.rs @@ -1,4 +1,4 @@ -// Copyright 2021 Parity Technologies (UK) Ltd. +// Copyright 2022 Parity Technologies (UK) Ltd. // This file is part of Substrate. // Substrate is free software: you can redistribute it and/or modify @@ -118,8 +118,7 @@ where fn upgrade_outbound(self, mut socket: TSocket, _info: Self::Info) -> Self::Future { Box::pin(async move { - let mut data = Vec::with_capacity(self.encoded_len()); - self.encode(&mut data)?; + let data = self.encode_to_vec(); upgrade::write_length_prefixed(&mut socket, data).await }) } diff --git a/client/network/src/config.rs b/client/network/src/config.rs index e44977e5be6b3..430efd697a18c 100644 --- a/client/network/src/config.rs +++ b/client/network/src/config.rs @@ -26,16 +26,11 @@ pub use sc_network_common::{ request_responses::{ IncomingRequest, OutgoingResponse, ProtocolConfig as RequestResponseConfig, }, + sync::warp::WarpSyncProvider, }; -pub use sc_network_sync::warp_request_handler::WarpSyncProvider; pub use libp2p::{build_multiaddr, core::PublicKey, identity}; -// Note: this re-export shouldn't be part of the public API of the crate and will be removed in -// the future. -#[doc(hidden)] -pub use crate::protocol::ProtocolConfig; - use crate::ExHashT; use core::{fmt, iter}; @@ -46,7 +41,7 @@ use libp2p::{ }; use prometheus_endpoint::Registry; use sc_consensus::ImportQueue; -use sp_consensus::block_validation::BlockAnnounceValidator; +use sc_network_common::sync::ChainSync; use sp_runtime::traits::Block as BlockT; use std::{ borrow::Cow, @@ -101,8 +96,14 @@ where /// valid. pub import_queue: Box>, - /// Type to check incoming block announcements. - pub block_announce_validator: Box + Send>, + /// Factory function that creates a new instance of chain sync. + pub create_chain_sync: Box< + dyn FnOnce( + sc_network_common::sync::SyncMode, + Arc, + Option>>, + ) -> crate::error::Result>>, + >, /// Registry for recording prometheus metrics to. pub metrics_registry: Option, @@ -114,26 +115,26 @@ where /// block requests, if enabled. /// /// Can be constructed either via - /// [`sc_network_sync::block_request_handler::generate_protocol_config`] allowing outgoing but - /// not incoming requests, or constructed via [`sc_network_sync::block_request_handler:: - /// BlockRequestHandler::new`] allowing both outgoing and incoming requests. + /// `sc_network_sync::block_request_handler::generate_protocol_config` allowing outgoing but + /// not incoming requests, or constructed via `sc_network_sync::block_request_handler:: + /// BlockRequestHandler::new` allowing both outgoing and incoming requests. pub block_request_protocol_config: RequestResponseConfig, /// Request response configuration for the light client request protocol. /// /// Can be constructed either via - /// [`sc_network_light::light_client_requests::generate_protocol_config`] allowing outgoing but + /// `sc_network_light::light_client_requests::generate_protocol_config` allowing outgoing but /// not incoming requests, or constructed via - /// [`sc_network_light::light_client_requests::handler::LightClientRequestHandler::new`] + /// `sc_network_light::light_client_requests::handler::LightClientRequestHandler::new` /// allowing both outgoing and incoming requests. pub light_client_request_protocol_config: RequestResponseConfig, /// Request response configuration for the state request protocol. /// /// Can be constructed either via - /// [`sc_network_sync::block_request_handler::generate_protocol_config`] allowing outgoing but + /// `sc_network_sync::state_request_handler::generate_protocol_config` allowing outgoing but /// not incoming requests, or constructed via - /// [`crate::state_request_handler::StateRequestHandler::new`] allowing + /// `sc_network_sync::state_request_handler::StateRequestHandler::new` allowing /// both outgoing and incoming requests. pub state_request_protocol_config: RequestResponseConfig, diff --git a/client/network/src/lib.rs b/client/network/src/lib.rs index fff30550eb8c9..83bc1075b8bad 100644 --- a/client/network/src/lib.rs +++ b/client/network/src/lib.rs @@ -266,13 +266,9 @@ pub use protocol::{ event::{DhtEvent, Event, ObservedRole}, PeerInfo, }; -pub use sc_network_light::light_client_requests; -pub use sc_network_sync::{ - block_request_handler, - state::StateDownloadProgress, - state_request_handler, +pub use sc_network_common::sync::{ warp::{WarpSyncPhase, WarpSyncProgress}, - warp_request_handler, SyncState, + StateDownloadProgress, SyncState, }; pub use service::{ DecodingError, IfDisconnected, KademliaKey, Keypair, NetworkService, NetworkWorker, diff --git a/client/network/src/protocol.rs b/client/network/src/protocol.rs index 348d2d0bf8877..3698a6b936ed5 100644 --- a/client/network/src/protocol.rs +++ b/client/network/src/protocol.rs @@ -20,7 +20,6 @@ use crate::{ config, error, request_responses::RequestFailure, utils::{interval, LruHashSet}, - warp_request_handler::{EncodedProof, WarpSyncProvider}, }; use bytes::Bytes; @@ -42,21 +41,23 @@ use message::{ }; use notifications::{Notifications, NotificationsOut}; use prometheus_endpoint::{register, Gauge, GaugeVec, Opts, PrometheusError, Registry, U64}; -use prost::Message as _; use sc_client_api::{BlockBackend, HeaderBackend, ProofProvider}; use sc_consensus::import_queue::{BlockImportError, BlockImportStatus, IncomingBlock, Origin}; -use sc_network_common::config::ProtocolId; -use sc_network_sync::{ - message::{ - BlockAnnounce, BlockAttributes, BlockData, BlockRequest, BlockResponse, BlockState, - FromBlock, +use sc_network_common::{ + config::ProtocolId, + sync::{ + message::{ + BlockAnnounce, BlockAttributes, BlockData, BlockRequest, BlockResponse, BlockState, + }, + warp::{EncodedProof, WarpProofRequest}, + BadPeer, ChainSync, OnBlockData, OnBlockJustification, OnStateData, OpaqueBlockRequest, + OpaqueBlockResponse, OpaqueStateRequest, OpaqueStateResponse, PollBlockAnnounceValidation, + SyncStatus, }, - schema::v1::StateResponse, - BadPeer, ChainSync, OnBlockData, OnBlockJustification, OnStateData, - PollBlockAnnounceValidation, Status as SyncStatus, }; use sp_arithmetic::traits::SaturatedConversion; -use sp_consensus::{block_validation::BlockAnnounceValidator, BlockOrigin}; +use sp_blockchain::HeaderMetadata; +use sp_consensus::BlockOrigin; use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, CheckedSub, Header as HeaderT, NumberFor, Zero}, @@ -79,7 +80,6 @@ pub mod event; pub mod message; pub use notifications::{NotificationsSink, NotifsHandlerError, Ready}; -use sp_blockchain::HeaderMetadata; /// Interval at which we perform time based maintenance const TICK_TIMEOUT: time::Duration = time::Duration::from_millis(1100); @@ -167,11 +167,12 @@ pub struct Protocol { tick_timeout: Pin + Send>>, /// Pending list of messages to return from `poll` as a priority. pending_messages: VecDeque>, - config: ProtocolConfig, + /// Assigned roles. + roles: Roles, genesis_hash: B::Hash, /// State machine that handles the list of in-progress requests. Only full node peers are /// registered. - sync: ChainSync, + chain_sync: Box>, // All connected peers. Contains both full and light node peers. peers: HashMap>, chain: Arc, @@ -231,38 +232,6 @@ pub struct PeerInfo { pub best_number: ::Number, } -/// Configuration for the Substrate-specific part of the networking layer. -#[derive(Clone)] -pub struct ProtocolConfig { - /// Assigned roles. - pub roles: Roles, - /// Maximum number of peers to ask the same blocks in parallel. - pub max_parallel_downloads: u32, - /// Enable state sync. - pub sync_mode: config::SyncMode, -} - -impl ProtocolConfig { - fn sync_mode(&self) -> sc_network_sync::SyncMode { - if self.roles.is_light() { - sc_network_sync::SyncMode::Light - } else { - match self.sync_mode { - config::SyncMode::Full => sc_network_sync::SyncMode::Full, - config::SyncMode::Fast { skip_proofs, storage_chain_mode } => - sc_network_sync::SyncMode::LightState { skip_proofs, storage_chain_mode }, - config::SyncMode::Warp => sc_network_sync::SyncMode::Warp, - } - } - } -} - -impl Default for ProtocolConfig { - fn default() -> ProtocolConfig { - Self { roles: Roles::FULL, max_parallel_downloads: 5, sync_mode: config::SyncMode::Full } - } -} - /// Handshake sent when we open a block announces substream. #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] struct BlockAnnouncesHandshake { @@ -278,12 +247,12 @@ struct BlockAnnouncesHandshake { impl BlockAnnouncesHandshake { fn build( - protocol_config: &ProtocolConfig, + roles: Roles, best_number: NumberFor, best_hash: B::Hash, genesis_hash: B::Hash, ) -> Self { - Self { genesis_hash, roles: protocol_config.roles, best_number, best_hash } + Self { genesis_hash, roles, best_number, best_hash } } } @@ -300,24 +269,15 @@ where { /// Create a new instance. pub fn new( - config: ProtocolConfig, + roles: Roles, chain: Arc, protocol_id: ProtocolId, network_config: &config::NetworkConfiguration, notifications_protocols_handshakes: Vec>, - block_announce_validator: Box + Send>, metrics_registry: Option<&Registry>, - warp_sync_provider: Option>>, - ) -> error::Result<(Protocol, sc_peerset::PeersetHandle, Vec<(PeerId, Multiaddr)>)> { + chain_sync: Box>, + ) -> error::Result<(Self, sc_peerset::PeersetHandle, Vec<(PeerId, Multiaddr)>)> { let info = chain.info(); - let sync = ChainSync::new( - config.sync_mode(), - chain.clone(), - block_announce_validator, - config.max_parallel_downloads, - warp_sync_provider, - ) - .map_err(Box::new)?; let boot_node_ids = { let mut list = HashSet::new(); @@ -405,7 +365,7 @@ where let genesis_hash = info.genesis_hash; let block_announces_handshake = - BlockAnnouncesHandshake::::build(&config, best_number, best_hash, genesis_hash) + BlockAnnouncesHandshake::::build(roles, best_number, best_hash, genesis_hash) .encode(); let sync_protocol_config = notifications::ProtocolConfig { @@ -438,11 +398,11 @@ where let protocol = Self { tick_timeout: Box::pin(interval(TICK_TIMEOUT)), pending_messages: VecDeque::new(), - config, + roles, peers: HashMap::new(), chain, genesis_hash: info.genesis_hash, - sync, + chain_sync, important_peers, default_peers_set_num_full: network_config.default_peers_set_num_full as usize, default_peers_set_num_light: { @@ -510,49 +470,49 @@ where /// Current global sync state. pub fn sync_state(&self) -> SyncStatus { - self.sync.status() + self.chain_sync.status() } /// Target sync block number. pub fn best_seen_block(&self) -> Option> { - self.sync.status().best_seen_block + self.chain_sync.status().best_seen_block } /// Number of peers participating in syncing. pub fn num_sync_peers(&self) -> u32 { - self.sync.status().num_peers + self.chain_sync.status().num_peers } /// Number of blocks in the import queue. pub fn num_queued_blocks(&self) -> u32 { - self.sync.status().queued_blocks + self.chain_sync.status().queued_blocks } /// Number of downloaded blocks. pub fn num_downloaded_blocks(&self) -> usize { - self.sync.num_downloaded_blocks() + self.chain_sync.num_downloaded_blocks() } /// Number of active sync requests. pub fn num_sync_requests(&self) -> usize { - self.sync.num_sync_requests() + self.chain_sync.num_sync_requests() } /// Inform sync about new best imported block. pub fn new_best_block_imported(&mut self, hash: B::Hash, number: NumberFor) { debug!(target: "sync", "New best block imported {:?}/#{}", hash, number); - self.sync.update_chain_info(&hash, number); + self.chain_sync.update_chain_info(&hash, number); self.behaviour.set_notif_protocol_handshake( HARDCODED_PEERSETS_SYNC, - BlockAnnouncesHandshake::::build(&self.config, number, hash, self.genesis_hash) + BlockAnnouncesHandshake::::build(self.roles, number, hash, self.genesis_hash) .encode(), ); } fn update_peer_info(&mut self, who: &PeerId) { - if let Some(info) = self.sync.peer_info(who) { + if let Some(info) = self.chain_sync.peer_info(who) { if let Some(ref mut peer) = self.peers.get_mut(who) { peer.info.best_hash = info.best_hash; peer.info.best_number = info.best_number; @@ -565,14 +525,6 @@ where self.peers.iter().map(|(id, peer)| (id, &peer.info)) } - fn prepare_block_request( - &mut self, - who: PeerId, - request: BlockRequest, - ) -> CustomMessageOutcome { - prepare_block_request::(&mut self.peers, who, request) - } - /// Called by peer when it is disconnecting. /// /// Returns a result if the handshake of this peer was indeed accepted. @@ -584,7 +536,9 @@ where } if let Some(_peer_data) = self.peers.remove(&peer) { - if let Some(OnBlockData::Import(origin, blocks)) = self.sync.peer_disconnected(&peer) { + if let Some(OnBlockData::Import(origin, blocks)) = + self.chain_sync.peer_disconnected(&peer) + { self.pending_messages .push_back(CustomMessageOutcome::BlockImport(origin, blocks)); } @@ -605,62 +559,9 @@ where &mut self, peer_id: PeerId, request: BlockRequest, - response: sc_network_sync::schema::v1::BlockResponse, + response: OpaqueBlockResponse, ) -> CustomMessageOutcome { - let blocks = response - .blocks - .into_iter() - .map(|block_data| { - Ok(BlockData:: { - hash: Decode::decode(&mut block_data.hash.as_ref())?, - header: if !block_data.header.is_empty() { - Some(Decode::decode(&mut block_data.header.as_ref())?) - } else { - None - }, - body: if request.fields.contains(BlockAttributes::BODY) { - Some( - block_data - .body - .iter() - .map(|body| Decode::decode(&mut body.as_ref())) - .collect::, _>>()?, - ) - } else { - None - }, - indexed_body: if request.fields.contains(BlockAttributes::INDEXED_BODY) { - Some(block_data.indexed_body) - } else { - None - }, - receipt: if !block_data.receipt.is_empty() { - Some(block_data.receipt) - } else { - None - }, - message_queue: if !block_data.message_queue.is_empty() { - Some(block_data.message_queue) - } else { - None - }, - justification: if !block_data.justification.is_empty() { - Some(block_data.justification) - } else if block_data.is_empty_justification { - Some(Vec::new()) - } else { - None - }, - justifications: if !block_data.justifications.is_empty() { - Some(DecodeAll::decode_all(&mut block_data.justifications.as_ref())?) - } else { - None - }, - }) - }) - .collect::, codec::Error>>(); - - let blocks = match blocks { + let blocks = match self.chain_sync.block_response_into_blocks(&request, response) { Ok(blocks) => blocks, Err(err) => { debug!(target: "sync", "Failed to decode block response from {}: {}", peer_id, err); @@ -690,7 +591,7 @@ where ); if request.fields == BlockAttributes::JUSTIFICATION { - match self.sync.on_block_justification(peer_id, block_response) { + match self.chain_sync.on_block_justification(peer_id, block_response) { Ok(OnBlockJustification::Nothing) => CustomMessageOutcome::None, Ok(OnBlockJustification::Import { peer, hash, number, justifications }) => CustomMessageOutcome::JustificationImport(peer, hash, number, justifications), @@ -701,10 +602,11 @@ where }, } } else { - match self.sync.on_block_data(&peer_id, Some(request), block_response) { + match self.chain_sync.on_block_data(&peer_id, Some(request), block_response) { Ok(OnBlockData::Import(origin, blocks)) => CustomMessageOutcome::BlockImport(origin, blocks), - Ok(OnBlockData::Request(peer, req)) => self.prepare_block_request(peer, req), + Ok(OnBlockData::Request(peer, req)) => + prepare_block_request(self.chain_sync.as_ref(), &mut self.peers, peer, req), Err(BadPeer(id, repu)) => { self.behaviour.disconnect_peer(&id, HARDCODED_PEERSETS_SYNC); self.peerset_handle.report_peer(id, repu); @@ -719,9 +621,9 @@ where pub fn on_state_response( &mut self, peer_id: PeerId, - response: StateResponse, + response: OpaqueStateResponse, ) -> CustomMessageOutcome { - match self.sync.on_state_data(&peer_id, response) { + match self.chain_sync.on_state_data(&peer_id, response) { Ok(OnStateData::Import(origin, block)) => CustomMessageOutcome::BlockImport(origin, vec![block]), Ok(OnStateData::Continue) => CustomMessageOutcome::None, @@ -738,9 +640,9 @@ where pub fn on_warp_sync_response( &mut self, peer_id: PeerId, - response: crate::warp_request_handler::EncodedProof, + response: EncodedProof, ) -> CustomMessageOutcome { - match self.sync.on_warp_sync_data(&peer_id, response) { + match self.chain_sync.on_warp_sync_data(&peer_id, response) { Ok(()) => CustomMessageOutcome::None, Err(BadPeer(id, repu)) => { self.behaviour.disconnect_peer(&id, HARDCODED_PEERSETS_SYNC); @@ -798,7 +700,7 @@ where return Err(()) } - if self.config.roles.is_light() { + if self.roles.is_light() { // we're not interested in light peers if status.roles.is_light() { debug!(target: "sync", "Peer {} is unable to serve light requests", who); @@ -821,14 +723,15 @@ where } } - if status.roles.is_full() && self.sync.num_peers() >= self.default_peers_set_num_full { + if status.roles.is_full() && self.chain_sync.num_peers() >= self.default_peers_set_num_full + { debug!(target: "sync", "Too many full nodes, rejecting {}", who); self.behaviour.disconnect_peer(&who, HARDCODED_PEERSETS_SYNC); return Err(()) } if status.roles.is_light() && - (self.peers.len() - self.sync.num_peers()) >= self.default_peers_set_num_light + (self.peers.len() - self.chain_sync.num_peers()) >= self.default_peers_set_num_light { // Make sure that not all slots are occupied by light clients. debug!(target: "sync", "Too many light nodes, rejecting {}", who); @@ -849,7 +752,7 @@ where }; let req = if peer.info.roles.is_full() { - match self.sync.new_peer(who, peer.info.best_hash, peer.info.best_number) { + match self.chain_sync.new_peer(who, peer.info.best_hash, peer.info.best_number) { Ok(req) => req, Err(BadPeer(id, repu)) => { self.behaviour.disconnect_peer(&id, HARDCODED_PEERSETS_SYNC); @@ -868,8 +771,12 @@ where .push_back(CustomMessageOutcome::PeerNewBest(who, status.best_number)); if let Some(req) = req { - let event = self.prepare_block_request(who, req); - self.pending_messages.push_back(event); + self.pending_messages.push_back(prepare_block_request( + self.chain_sync.as_ref(), + &mut self.peers, + who, + req, + )); } Ok(()) @@ -953,7 +860,7 @@ where }; if peer.info.roles.is_full() { - self.sync.push_block_announce_validation(who, hash, announce, is_best); + self.chain_sync.push_block_announce_validation(who, hash, announce, is_best); } } @@ -1010,7 +917,7 @@ where // to import header from announced block let's construct response to request that normally // would have been sent over network (but it is not in our case) - let blocks_to_import = self.sync.on_block_data( + let blocks_to_import = self.chain_sync.on_block_data( &who, None, BlockResponse:: { @@ -1035,7 +942,8 @@ where match blocks_to_import { Ok(OnBlockData::Import(origin, blocks)) => CustomMessageOutcome::BlockImport(origin, blocks), - Ok(OnBlockData::Request(peer, req)) => self.prepare_block_request(peer, req), + Ok(OnBlockData::Request(peer, req)) => + prepare_block_request(self.chain_sync.as_ref(), &mut self.peers, peer, req), Err(BadPeer(id, repu)) => { self.behaviour.disconnect_peer(&id, HARDCODED_PEERSETS_SYNC); self.peerset_handle.report_peer(id, repu); @@ -1047,7 +955,7 @@ where /// Call this when a block has been finalized. The sync layer may have some additional /// requesting to perform. pub fn on_block_finalized(&mut self, hash: B::Hash, header: &B::Header) { - self.sync.on_block_finalized(&hash, *header.number()) + self.chain_sync.on_block_finalized(&hash, *header.number()) } /// Request a justification for the given block. @@ -1055,12 +963,12 @@ where /// Uses `protocol` to queue a new justification request and tries to dispatch all pending /// requests. pub fn request_justification(&mut self, hash: &B::Hash, number: NumberFor) { - self.sync.request_justification(hash, number) + self.chain_sync.request_justification(hash, number) } /// Clear all pending justification requests. pub fn clear_justification_requests(&mut self) { - self.sync.clear_justification_requests(); + self.chain_sync.clear_justification_requests(); } /// Request syncing for the given block from given set of peers. @@ -1072,7 +980,7 @@ where hash: &B::Hash, number: NumberFor, ) { - self.sync.set_sync_fork_request(peers, hash, number) + self.chain_sync.set_sync_fork_request(peers, hash, number) } /// A batch of blocks have been processed, with or without errors. @@ -1084,11 +992,12 @@ where count: usize, results: Vec<(Result>, BlockImportError>, B::Hash)>, ) { - let results = self.sync.on_blocks_processed(imported, count, results); + let results = self.chain_sync.on_blocks_processed(imported, count, results); for result in results { match result { Ok((id, req)) => { self.pending_messages.push_back(prepare_block_request( + self.chain_sync.as_ref(), &mut self.peers, id, req, @@ -1111,7 +1020,7 @@ where number: NumberFor, success: bool, ) { - self.sync.on_justification_import(hash, number, success); + self.chain_sync.on_justification_import(hash, number, success); if !success { info!("💔 Invalid justification provided by {} for #{}", who, hash); self.behaviour.disconnect_peer(&who, HARDCODED_PEERSETS_SYNC); @@ -1228,12 +1137,22 @@ where } } + /// Encode implementation-specific block request. + pub fn encode_block_request(&self, request: &OpaqueBlockRequest) -> Result, String> { + self.chain_sync.encode_block_request(request) + } + + /// Encode implementation-specific state request. + pub fn encode_state_request(&self, request: &OpaqueStateRequest) -> Result, String> { + self.chain_sync.encode_state_request(request) + } + fn report_metrics(&self) { if let Some(metrics) = &self.metrics { let n = u64::try_from(self.peers.len()).unwrap_or(std::u64::MAX); metrics.peers.set(n); - let m = self.sync.metrics(); + let m = self.chain_sync.metrics(); metrics.fork_targets.set(m.fork_targets.into()); metrics.queued_blocks.set(m.queued_blocks.into()); @@ -1259,6 +1178,7 @@ where } fn prepare_block_request( + chain_sync: &dyn ChainSync, peers: &mut HashMap>, who: PeerId, request: BlockRequest, @@ -1269,19 +1189,7 @@ fn prepare_block_request( peer.request = Some((PeerRequest::Block(request.clone()), rx)); } - let request = sc_network_sync::schema::v1::BlockRequest { - fields: request.fields.to_be_u32(), - from_block: match request.from { - FromBlock::Hash(h) => - Some(sc_network_sync::schema::v1::block_request::FromBlock::Hash(h.encode())), - FromBlock::Number(n) => - Some(sc_network_sync::schema::v1::block_request::FromBlock::Number(n.encode())), - }, - to_block: request.to.map(|h| h.encode()).unwrap_or_default(), - direction: request.direction as i32, - max_blocks: request.max.unwrap_or(0), - support_multiple_justifications: true, - }; + let request = chain_sync.create_opaque_block_request(&request); CustomMessageOutcome::BlockRequest { target: who, request, pending_response: tx } } @@ -1289,7 +1197,7 @@ fn prepare_block_request( fn prepare_state_request( peers: &mut HashMap>, who: PeerId, - request: sc_network_sync::schema::v1::StateRequest, + request: OpaqueStateRequest, ) -> CustomMessageOutcome { let (tx, rx) = oneshot::channel(); @@ -1302,7 +1210,7 @@ fn prepare_state_request( fn prepare_warp_sync_request( peers: &mut HashMap>, who: PeerId, - request: crate::warp_request_handler::Request, + request: WarpProofRequest, ) -> CustomMessageOutcome { let (tx, rx) = oneshot::channel(); @@ -1346,19 +1254,19 @@ pub enum CustomMessageOutcome { /// A new block request must be emitted. BlockRequest { target: PeerId, - request: sc_network_sync::schema::v1::BlockRequest, + request: OpaqueBlockRequest, pending_response: oneshot::Sender, RequestFailure>>, }, /// A new storage request must be emitted. StateRequest { target: PeerId, - request: sc_network_sync::schema::v1::StateRequest, + request: OpaqueStateRequest, pending_response: oneshot::Sender, RequestFailure>>, }, /// A new warp sync request must be emitted. WarpSyncRequest { target: PeerId, - request: crate::warp_request_handler::Request, + request: WarpProofRequest, pending_response: oneshot::Sender, RequestFailure>>, }, /// Peer has a reported a new head of chain. @@ -1455,10 +1363,8 @@ where let (req, _) = peer.request.take().unwrap(); match req { PeerRequest::Block(req) => { - let protobuf_response = - match sc_network_sync::schema::v1::BlockResponse::decode( - &resp[..], - ) { + let response = + match self.chain_sync.decode_block_response(&resp[..]) { Ok(proto) => proto, Err(e) => { debug!( @@ -1474,13 +1380,11 @@ where }, }; - finished_block_requests.push((*id, req, protobuf_response)); + finished_block_requests.push((*id, req, response)); }, PeerRequest::State => { - let protobuf_response = - match sc_network_sync::schema::v1::StateResponse::decode( - &resp[..], - ) { + let response = + match self.chain_sync.decode_state_response(&resp[..]) { Ok(proto) => proto, Err(e) => { debug!( @@ -1496,7 +1400,7 @@ where }, }; - finished_state_requests.push((*id, protobuf_response)); + finished_state_requests.push((*id, response)); }, PeerRequest::WarpProof => { finished_warp_sync_requests.push((*id, resp)); @@ -1555,12 +1459,12 @@ where } } } - for (id, req, protobuf_response) in finished_block_requests { - let ev = self.on_block_response(id, req, protobuf_response); + for (id, req, response) in finished_block_requests { + let ev = self.on_block_response(id, req, response); self.pending_messages.push_back(ev); } - for (id, protobuf_response) in finished_state_requests { - let ev = self.on_state_response(id, protobuf_response); + for (id, response) in finished_state_requests { + let ev = self.on_state_response(id, response); self.pending_messages.push_back(ev); } for (id, response) in finished_warp_sync_requests { @@ -1572,25 +1476,32 @@ where self.tick(); } - for (id, request) in self.sync.block_requests() { - let event = prepare_block_request(&mut self.peers, *id, request); + for (id, request) in self + .chain_sync + .block_requests() + .map(|(peer_id, request)| (*peer_id, request)) + .collect::>() + { + let event = + prepare_block_request(self.chain_sync.as_ref(), &mut self.peers, id, request); self.pending_messages.push_back(event); } - if let Some((id, request)) = self.sync.state_request() { + if let Some((id, request)) = self.chain_sync.state_request() { let event = prepare_state_request(&mut self.peers, id, request); self.pending_messages.push_back(event); } - for (id, request) in self.sync.justification_requests() { - let event = prepare_block_request(&mut self.peers, id, request); + for (id, request) in self.chain_sync.justification_requests().collect::>() { + let event = + prepare_block_request(self.chain_sync.as_ref(), &mut self.peers, id, request); self.pending_messages.push_back(event); } - if let Some((id, request)) = self.sync.warp_sync_request() { + if let Some((id, request)) = self.chain_sync.warp_sync_request() { let event = prepare_warp_sync_request(&mut self.peers, id, request); self.pending_messages.push_back(event); } // Check if there is any block announcement validation finished. - while let Poll::Ready(result) = self.sync.poll_block_announce_validation(cx) { + while let Poll::Ready(result) = self.chain_sync.poll_block_announce_validation(cx) { match self.process_block_announce_validation_result(result) { CustomMessageOutcome::None => {}, outcome => self.pending_messages.push_back(outcome), @@ -1771,7 +1682,8 @@ where // Make sure that the newly added block announce validation future was // polled once to be registered in the task. - if let Poll::Ready(res) = self.sync.poll_block_announce_validation(cx) { + if let Poll::Ready(res) = self.chain_sync.poll_block_announce_validation(cx) + { self.process_block_announce_validation_result(res) } else { CustomMessageOutcome::None diff --git a/client/network/src/protocol/message.rs b/client/network/src/protocol/message.rs index a57740ec2746b..c9512f82e23bb 100644 --- a/client/network/src/protocol/message.rs +++ b/client/network/src/protocol/message.rs @@ -63,10 +63,12 @@ pub mod generic { use bitflags::bitflags; use codec::{Decode, Encode, Input, Output}; use sc_client_api::StorageProof; - use sc_network_common::message::RequestId; - use sc_network_sync::message::{ - generic::{BlockRequest, BlockResponse}, - BlockAnnounce, + use sc_network_common::{ + message::RequestId, + sync::message::{ + generic::{BlockRequest, BlockResponse}, + BlockAnnounce, + }, }; use sp_runtime::ConsensusEngineId; diff --git a/client/network/src/service.rs b/client/network/src/service.rs index 1cc717a50d039..ef7ef2f5a2deb 100644 --- a/client/network/src/service.rs +++ b/client/network/src/service.rs @@ -30,7 +30,7 @@ use crate::{ behaviour::{self, Behaviour, BehaviourOut}, bitswap::Bitswap, - config::{parse_str_addr, Params, TransportConfig}, + config::{self, parse_str_addr, Params, TransportConfig}, discovery::DiscoveryConfig, error::Error, network_state::{ @@ -60,7 +60,7 @@ use metrics::{Histogram, HistogramVec, MetricSources, Metrics}; use parking_lot::Mutex; use sc_client_api::{BlockBackend, ProofProvider}; use sc_consensus::{BlockImportError, BlockImportStatus, ImportQueue, Link}; -use sc_network_sync::{Status as SyncStatus, SyncState}; +use sc_network_common::sync::{SyncMode, SyncState, SyncStatus}; use sc_peerset::PeersetHandle; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; use sp_blockchain::{HeaderBackend, HeaderMetadata}; @@ -207,13 +207,23 @@ where None => (None, None), }; - let (protocol, peerset_handle, mut known_addresses) = Protocol::new( - protocol::ProtocolConfig { - roles: From::from(¶ms.role), - max_parallel_downloads: params.network_config.max_parallel_downloads, - sync_mode: params.network_config.sync_mode.clone(), + let chain_sync = (params.create_chain_sync)( + if params.role.is_light() { + SyncMode::Light + } else { + match params.network_config.sync_mode { + config::SyncMode::Full => SyncMode::Full, + config::SyncMode::Fast { skip_proofs, storage_chain_mode } => + SyncMode::LightState { skip_proofs, storage_chain_mode }, + config::SyncMode::Warp => SyncMode::Warp, + } }, params.chain.clone(), + warp_sync_provider, + )?; + let (protocol, peerset_handle, mut known_addresses) = Protocol::new( + From::from(¶ms.role), + params.chain.clone(), params.protocol_id.clone(), ¶ms.network_config, iter::once(Vec::new()) @@ -222,9 +232,8 @@ where .map(|_| default_notif_handshake_message.clone()), ) .collect(), - params.block_announce_validator, params.metrics_registry.as_ref(), - warp_sync_provider, + chain_sync, )?; // List of multiaddresses that we know in the network. diff --git a/client/network/src/service/tests.rs b/client/network/src/service/tests.rs index 808546d67fc7c..181d58130aa6b 100644 --- a/client/network/src/service/tests.rs +++ b/client/network/src/service/tests.rs @@ -16,15 +16,17 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::{ - config, state_request_handler::StateRequestHandler, Event, NetworkService, NetworkWorker, -}; +use crate::{config, Event, NetworkService, NetworkWorker}; use futures::prelude::*; use libp2p::PeerId; use sc_network_common::config::ProtocolId; use sc_network_light::light_client_requests::handler::LightClientRequestHandler; -use sc_network_sync::block_request_handler::BlockRequestHandler; +use sc_network_sync::{ + block_request_handler::BlockRequestHandler, state_request_handler::StateRequestHandler, + ChainSync, +}; +use sp_consensus::block_validation::DefaultBlockAnnounceValidator; use sp_runtime::traits::{Block as BlockT, Header as _}; use std::{borrow::Cow, sync::Arc, time::Duration}; use substrate_test_runtime_client::{TestClientBuilder, TestClientBuilderExt as _}; @@ -109,6 +111,7 @@ fn build_test_full_node( protocol_config }; + let max_parallel_downloads = config.max_parallel_downloads; let worker = NetworkWorker::new(config::Params { role: config::Role::Full, executor: None, @@ -120,8 +123,17 @@ fn build_test_full_node( transaction_pool: Arc::new(crate::config::EmptyTransactionPool), protocol_id, import_queue, - block_announce_validator: Box::new( - sp_consensus::block_validation::DefaultBlockAnnounceValidator, + create_chain_sync: Box::new( + move |sync_mode, chain, warp_sync_provider| match ChainSync::new( + sync_mode, + chain, + Box::new(DefaultBlockAnnounceValidator), + max_parallel_downloads, + warp_sync_provider, + ) { + Ok(chain_sync) => Ok(Box::new(chain_sync)), + Err(error) => Err(Box::new(error).into()), + }, ), metrics_registry: None, block_request_protocol_config, diff --git a/client/network/sync/Cargo.toml b/client/network/sync/Cargo.toml index a61f780fd56ad..3e3526146400a 100644 --- a/client/network/sync/Cargo.toml +++ b/client/network/sync/Cargo.toml @@ -17,11 +17,9 @@ targets = ["x86_64-unknown-linux-gnu"] prost-build = "0.10" [dependencies] -bitflags = "1.3.2" codec = { package = "parity-scale-codec", version = "3.0.0", features = [ "derive", ] } -either = "1.5.3" futures = "0.3.21" libp2p = "0.46.1" log = "0.4.17" diff --git a/client/network/sync/src/block_request_handler.rs b/client/network/sync/src/block_request_handler.rs index 4cd69d1fac4f5..2a847a8bf36ec 100644 --- a/client/network/sync/src/block_request_handler.rs +++ b/client/network/sync/src/block_request_handler.rs @@ -17,10 +17,7 @@ //! Helper for handling (i.e. answering) block requests from a remote peer via the //! `crate::request_responses::RequestResponsesBehaviour`. -use crate::{ - message::BlockAttributes, - schema::v1::{block_request::FromBlock, BlockResponse, Direction}, -}; +use crate::schema::v1::{block_request::FromBlock, BlockResponse, Direction}; use codec::{Decode, Encode}; use futures::{ channel::{mpsc, oneshot}, @@ -34,6 +31,7 @@ use sc_client_api::BlockBackend; use sc_network_common::{ config::ProtocolId, request_responses::{IncomingRequest, OutgoingResponse, ProtocolConfig}, + sync::message::BlockAttributes, }; use sp_blockchain::HeaderBackend; use sp_runtime::{ diff --git a/client/network/sync/src/blocks.rs b/client/network/sync/src/blocks.rs index 26753f120a170..5fb1484675071 100644 --- a/client/network/sync/src/blocks.rs +++ b/client/network/sync/src/blocks.rs @@ -16,9 +16,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::message; use libp2p::PeerId; use log::trace; +use sc_network_common::sync::message; use sp_runtime::traits::{Block as BlockT, NumberFor, One}; use std::{ cmp, @@ -245,8 +245,8 @@ impl BlockCollection { #[cfg(test)] mod test { use super::{BlockCollection, BlockData, BlockRangeState}; - use crate::message; use libp2p::PeerId; + use sc_network_common::sync::message; use sp_core::H256; use sp_runtime::testing::{Block as RawBlock, ExtrinsicWrapper}; diff --git a/client/network/sync/src/extra_requests.rs b/client/network/sync/src/extra_requests.rs index c684d8e72783e..6206f8a61bcf4 100644 --- a/client/network/sync/src/extra_requests.rs +++ b/client/network/sync/src/extra_requests.rs @@ -20,6 +20,7 @@ use crate::{PeerSync, PeerSyncState}; use fork_tree::ForkTree; use libp2p::PeerId; use log::{debug, trace, warn}; +use sc_network_common::sync::metrics::Metrics; use sp_blockchain::Error as ClientError; use sp_runtime::traits::{Block as BlockT, NumberFor, Zero}; use std::{ @@ -56,15 +57,6 @@ pub(crate) struct ExtraRequests { request_type_name: &'static str, } -#[derive(Debug)] -pub struct Metrics { - pub pending_requests: u32, - pub active_requests: u32, - pub importing_requests: u32, - pub failed_requests: u32, - _priv: (), -} - impl ExtraRequests { pub(crate) fn new(request_type_name: &'static str) -> Self { Self { @@ -258,7 +250,6 @@ impl ExtraRequests { active_requests: self.active_requests.len().try_into().unwrap_or(std::u32::MAX), failed_requests: self.failed_requests.len().try_into().unwrap_or(std::u32::MAX), importing_requests: self.importing_requests.len().try_into().unwrap_or(std::u32::MAX), - _priv: (), } } } diff --git a/client/network/sync/src/lib.rs b/client/network/sync/src/lib.rs index 1ce69b6dc816f..aff773bd12ed6 100644 --- a/client/network/sync/src/lib.rs +++ b/client/network/sync/src/lib.rs @@ -30,8 +30,7 @@ pub mod block_request_handler; pub mod blocks; -pub mod message; -pub mod schema; +mod schema; pub mod state; pub mod state_request_handler; pub mod warp; @@ -39,22 +38,28 @@ pub mod warp_request_handler; use crate::{ blocks::BlockCollection, - message::{BlockAnnounce, BlockAttributes, BlockRequest, BlockResponse}, schema::v1::{StateRequest, StateResponse}, - state::{StateDownloadProgress, StateSync}, - warp::{ - EncodedProof, WarpProofImportResult, WarpProofRequest, WarpSync, WarpSyncPhase, - WarpSyncProgress, WarpSyncProvider, - }, + state::StateSync, + warp::{WarpProofImportResult, WarpSync}, }; -use codec::Encode; -use either::Either; +use codec::{Decode, DecodeAll, Encode}; use extra_requests::ExtraRequests; use futures::{stream::FuturesUnordered, task::Poll, Future, FutureExt, StreamExt}; use libp2p::PeerId; use log::{debug, error, info, trace, warn}; +use prost::Message; use sc_client_api::{BlockBackend, ProofProvider}; use sc_consensus::{BlockImportError, BlockImportStatus, IncomingBlock}; +use sc_network_common::sync::{ + message::{ + BlockAnnounce, BlockAttributes, BlockData, BlockRequest, BlockResponse, Direction, + FromBlock, + }, + warp::{EncodedProof, WarpProofRequest, WarpSyncPhase, WarpSyncProgress, WarpSyncProvider}, + BadPeer, ChainSync as ChainSyncT, Metrics, OnBlockData, OnBlockJustification, OnStateData, + OpaqueBlockRequest, OpaqueBlockResponse, OpaqueStateRequest, OpaqueStateResponse, PeerInfo, + PollBlockAnnounceValidation, SyncMode, SyncState, SyncStatus, +}; use sp_arithmetic::traits::Saturating; use sp_blockchain::{Error as ClientError, HeaderBackend, HeaderMetadata}; use sp_consensus::{ @@ -71,7 +76,6 @@ use sp_runtime::{ }; use std::{ collections::{hash_map::Entry, HashMap, HashSet}, - fmt, ops::Range, pin::Pin, sync::Arc, @@ -283,15 +287,6 @@ impl PeerSync { } } -/// The sync status of a peer we are trying to sync with -#[derive(Debug)] -pub struct PeerInfo { - /// Their best block hash. - pub best_hash: B::Hash, - /// Their best block number. - pub best_number: NumberFor, -} - struct ForkTarget { number: NumberFor, parent_hash: Option, @@ -330,108 +325,6 @@ impl PeerSyncState { } } -/// Reported sync state. -#[derive(Clone, Eq, PartialEq, Debug)] -pub enum SyncState { - /// Initial sync is complete, keep-up sync is active. - Idle, - /// Actively catching up with the chain. - Downloading, -} - -/// Syncing status and statistics. -#[derive(Clone)] -pub struct Status { - /// Current global sync state. - pub state: SyncState, - /// Target sync block number. - pub best_seen_block: Option>, - /// Number of peers participating in syncing. - pub num_peers: u32, - /// Number of blocks queued for import - pub queued_blocks: u32, - /// State sync status in progress, if any. - pub state_sync: Option, - /// Warp sync in progress, if any. - pub warp_sync: Option>, -} - -/// A peer did not behave as expected and should be reported. -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct BadPeer(pub PeerId, pub sc_peerset::ReputationChange); - -impl fmt::Display for BadPeer { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Bad peer {}; Reputation change: {:?}", self.0, self.1) - } -} - -impl std::error::Error for BadPeer {} - -/// Result of [`ChainSync::on_block_data`]. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum OnBlockData { - /// The block should be imported. - Import(BlockOrigin, Vec>), - /// A new block request needs to be made to the given peer. - Request(PeerId, BlockRequest), -} - -impl OnBlockData { - /// Returns `self` as request. - #[cfg(test)] - fn into_request(self) -> Option<(PeerId, BlockRequest)> { - if let Self::Request(peer, req) = self { - Some((peer, req)) - } else { - None - } - } -} - -/// Result of [`ChainSync::on_state_data`]. -#[derive(Debug)] -pub enum OnStateData { - /// The block and state that should be imported. - Import(BlockOrigin, IncomingBlock), - /// A new state request needs to be made to the given peer. - Continue, -} - -/// Result of [`ChainSync::poll_block_announce_validation`]. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum PollBlockAnnounceValidation { - /// The announcement failed at validation. - /// - /// The peer reputation should be decreased. - Failure { - /// Who sent the processed block announcement? - who: PeerId, - /// Should the peer be disconnected? - disconnect: bool, - }, - /// The announcement does not require further handling. - Nothing { - /// Who sent the processed block announcement? - who: PeerId, - /// Was this their new best block? - is_best: bool, - /// The announcement. - announce: BlockAnnounce, - }, - /// The announcement header should be imported. - ImportHeader { - /// Who sent the processed block announcement? - who: PeerId, - /// Was this their new best block? - is_best: bool, - /// The announcement. - announce: BlockAnnounce, - }, - /// The block announcement should be skipped. - Skip, -} - /// Result of [`ChainSync::block_announce_validation`]. #[derive(Debug, Clone, PartialEq, Eq)] enum PreValidateBlockAnnounce { @@ -467,28 +360,6 @@ enum PreValidateBlockAnnounce { Skip, } -/// Result of [`ChainSync::on_block_justification`]. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum OnBlockJustification { - /// The justification needs no further handling. - Nothing, - /// The justification should be imported. - Import { peer: PeerId, hash: B::Hash, number: NumberFor, justifications: Justifications }, -} - -/// Operation mode. -#[derive(Debug, PartialEq, Eq)] -pub enum SyncMode { - // Sync headers only - Light, - // Sync headers and block bodies - Full, - // Sync headers and the last finalied state - LightState { storage_chain_mode: bool, skip_proofs: bool }, - // Warp sync mode. - Warp, -} - /// Result of [`ChainSync::has_slot_for_block_announce_validation`]. enum HasSlotForBlockAnnounceValidation { /// Yes, there is a slot for the block announce validation. @@ -499,7 +370,7 @@ enum HasSlotForBlockAnnounceValidation { MaximumPeerSlotsReached, } -impl ChainSync +impl ChainSyncT for ChainSync where B: BlockT, Client: HeaderBackend @@ -510,88 +381,14 @@ where + Sync + 'static, { - /// Create a new instance. - pub fn new( - mode: SyncMode, - client: Arc, - block_announce_validator: Box + Send>, - max_parallel_downloads: u32, - warp_sync_provider: Option>>, - ) -> Result { - let mut sync = Self { - client, - peers: HashMap::new(), - blocks: BlockCollection::new(), - best_queued_hash: Default::default(), - best_queued_number: Zero::zero(), - extra_justifications: ExtraRequests::new("justification"), - mode, - queue_blocks: Default::default(), - fork_targets: Default::default(), - allowed_requests: Default::default(), - block_announce_validator, - max_parallel_downloads, - downloaded_blocks: 0, - block_announce_validation: Default::default(), - block_announce_validation_per_peer_stats: Default::default(), - state_sync: None, - warp_sync: None, - warp_sync_provider, - import_existing: false, - gap_sync: None, - }; - sync.reset_sync_start_point()?; - Ok(sync) - } - - fn required_block_attributes(&self) -> BlockAttributes { - match self.mode { - SyncMode::Full => - BlockAttributes::HEADER | BlockAttributes::JUSTIFICATION | BlockAttributes::BODY, - SyncMode::Light => BlockAttributes::HEADER | BlockAttributes::JUSTIFICATION, - SyncMode::LightState { storage_chain_mode: false, .. } | SyncMode::Warp => - BlockAttributes::HEADER | BlockAttributes::JUSTIFICATION | BlockAttributes::BODY, - SyncMode::LightState { storage_chain_mode: true, .. } => - BlockAttributes::HEADER | - BlockAttributes::JUSTIFICATION | - BlockAttributes::INDEXED_BODY, - } - } - - fn skip_execution(&self) -> bool { - match self.mode { - SyncMode::Full => false, - SyncMode::Light => true, - SyncMode::LightState { .. } => true, - SyncMode::Warp => true, - } - } - - /// Returns the state of the sync of the given peer. - /// - /// Returns `None` if the peer is unknown. - pub fn peer_info(&self, who: &PeerId) -> Option> { + fn peer_info(&self, who: &PeerId) -> Option> { self.peers .get(who) .map(|p| PeerInfo { best_hash: p.best_hash, best_number: p.best_number }) } - /// Returns the best seen block. - fn best_seen(&self) -> Option> { - let mut best_seens = self.peers.values().map(|p| p.best_number).collect::>(); - - if best_seens.is_empty() { - None - } else { - let middle = best_seens.len() / 2; - - // Not the "perfect median" when we have an even number of peers. - Some(*best_seens.select_nth_unstable(middle).1) - } - } - /// Returns the current sync status. - pub fn status(&self) -> Status { + fn status(&self) -> SyncStatus { let best_seen = self.best_seen(); let sync_state = if let Some(n) = best_seen { // A chain is classified as downloading if the provided best block is @@ -617,7 +414,7 @@ where _ => None, }; - Status { + SyncStatus { state: sync_state, best_seen_block: best_seen, num_peers: self.peers.len() as u32, @@ -627,29 +424,22 @@ where } } - /// Number of active forks requests. This includes - /// requests that are pending or could be issued right away. - pub fn num_sync_requests(&self) -> usize { + fn num_sync_requests(&self) -> usize { self.fork_targets .values() .filter(|f| f.number <= self.best_queued_number) .count() } - /// Number of downloaded blocks. - pub fn num_downloaded_blocks(&self) -> usize { + fn num_downloaded_blocks(&self) -> usize { self.downloaded_blocks } - /// Returns the current number of peers stored within this state machine. - pub fn num_peers(&self) -> usize { + fn num_peers(&self) -> usize { self.peers.len() } - /// Handle a new connected peer. - /// - /// Call this method whenever we connect to a new peer. - pub fn new_peer( + fn new_peer( &mut self, who: PeerId, best_hash: B::Hash, @@ -773,27 +563,22 @@ where } } - /// Signal that a new best block has been imported. - /// `ChainSync` state with that information. - pub fn update_chain_info(&mut self, best_hash: &B::Hash, best_number: NumberFor) { + fn update_chain_info(&mut self, best_hash: &B::Hash, best_number: NumberFor) { self.on_block_queued(best_hash, best_number); } - /// Schedule a justification request for the given block. - pub fn request_justification(&mut self, hash: &B::Hash, number: NumberFor) { + fn request_justification(&mut self, hash: &B::Hash, number: NumberFor) { let client = &self.client; self.extra_justifications .schedule((*hash, number), |base, block| is_descendent_of(&**client, base, block)) } - /// Clear all pending justification requests. - pub fn clear_justification_requests(&mut self) { + fn clear_justification_requests(&mut self) { self.extra_justifications.reset(); } - /// Request syncing for the given block from given set of peers. // The implementation is similar to on_block_announce with unknown parent hash. - pub fn set_sync_fork_request( + fn set_sync_fork_request( &mut self, mut peers: Vec, hash: &B::Hash, @@ -845,13 +630,12 @@ where .extend(peers); } - /// Get an iterator over all scheduled justification requests. - pub fn justification_requests( + fn justification_requests( &mut self, - ) -> impl Iterator)> + '_ { + ) -> Box)> + '_> { let peers = &mut self.peers; let mut matcher = self.extra_justifications.matcher(); - std::iter::from_fn(move || { + Box::new(std::iter::from_fn(move || { if let Some((peer, request)) = matcher.next(peers) { peers .get_mut(&peer) @@ -859,33 +643,32 @@ where "`Matcher::next` guarantees the `PeerId` comes from the given peers; qed", ) .state = PeerSyncState::DownloadingJustification(request.0); - let req = message::generic::BlockRequest { + let req = BlockRequest:: { id: 0, fields: BlockAttributes::JUSTIFICATION, - from: message::FromBlock::Hash(request.0), + from: FromBlock::Hash(request.0), to: None, - direction: message::Direction::Ascending, + direction: Direction::Ascending, max: Some(1), }; Some((peer, req)) } else { None } - }) + })) } - /// Get an iterator over all block requests of all peers. - pub fn block_requests(&mut self) -> impl Iterator)> + '_ { + fn block_requests(&mut self) -> Box)> + '_> { if self.allowed_requests.is_empty() || self.state_sync.is_some() || self.mode == SyncMode::Warp { - return Either::Left(std::iter::empty()) + return Box::new(std::iter::empty()) } if self.queue_blocks.len() > MAX_IMPORTING_BLOCKS { trace!(target: "sync", "Too many blocks in the queue."); - return Either::Left(std::iter::empty()) + return Box::new(std::iter::empty()) } let major_sync = self.status().state == SyncState::Downloading; let attrs = self.required_block_attributes(); @@ -982,11 +765,10 @@ where None } }); - Either::Right(iter) + Box::new(iter) } - /// Get a state request, if any. - pub fn state_request(&mut self) -> Option<(PeerId, StateRequest)> { + fn state_request(&mut self) -> Option<(PeerId, OpaqueStateRequest)> { if self.allowed_requests.is_empty() { return None } @@ -1007,7 +789,7 @@ where let request = sync.next_request(); trace!(target: "sync", "New StateRequest for {}: {:?}", id, request); self.allowed_requests.clear(); - return Some((*id, request)) + return Some((*id, OpaqueStateRequest(Box::new(request)))) } } } @@ -1023,7 +805,7 @@ where trace!(target: "sync", "New StateRequest for {}: {:?}", id, request); peer.state = PeerSyncState::DownloadingState; self.allowed_requests.clear(); - return Some((*id, request)) + return Some((*id, OpaqueStateRequest(Box::new(request)))) } } } @@ -1031,8 +813,7 @@ where None } - /// Get a warp sync request, if any. - pub fn warp_sync_request(&mut self) -> Option<(PeerId, WarpProofRequest)> { + fn warp_sync_request(&mut self) -> Option<(PeerId, WarpProofRequest)> { if let Some(sync) = &self.warp_sync { if self.allowed_requests.is_empty() || sync.is_complete() || @@ -1063,14 +844,7 @@ where None } - /// Handle a response from the remote to a block request that we made. - /// - /// `request` must be the original request that triggered `response`. - /// or `None` if data comes from the block announcement. - /// - /// If this corresponds to a valid block, this outputs the block that - /// must be imported in the import queue. - pub fn on_block_data( + fn on_block_data( &mut self, who: &PeerId, request: Option>, @@ -1080,10 +854,7 @@ where let mut gap = false; let new_blocks: Vec> = if let Some(peer) = self.peers.get_mut(who) { let mut blocks = response.blocks; - if request - .as_ref() - .map_or(false, |r| r.direction == message::Direction::Descending) - { + if request.as_ref().map_or(false, |r| r.direction == Direction::Descending) { trace!(target: "sync", "Reversing incoming block list"); blocks.reverse() } @@ -1297,14 +1068,20 @@ where Ok(self.validate_and_queue_blocks(new_blocks, gap)) } - /// Handle a response from the remote to a state request that we made. - /// - /// Returns next request if any. - pub fn on_state_data( + fn on_state_data( &mut self, who: &PeerId, - response: StateResponse, + response: OpaqueStateResponse, ) -> Result, BadPeer> { + let response: Box = response.0.downcast().map_err(|_error| { + error!( + target: "sync", + "Failed to downcast opaque state response, this is an implementation bug." + ); + + BadPeer(*who, rep::BAD_RESPONSE) + })?; + if let Some(peer) = self.peers.get_mut(who) { if let PeerSyncState::DownloadingState = peer.state { peer.state = PeerSyncState::Available; @@ -1319,7 +1096,7 @@ where response.entries.len(), response.proof.len(), ); - sync.import(response) + sync.import(*response) } else if let Some(sync) = &mut self.warp_sync { debug!( target: "sync", @@ -1328,7 +1105,7 @@ where response.entries.len(), response.proof.len(), ); - sync.import_state(response) + sync.import_state(*response) } else { debug!(target: "sync", "Ignored obsolete state response from {}", who); return Err(BadPeer(*who, rep::NOT_REQUESTED)) @@ -1360,14 +1137,7 @@ where } } - /// Handle a response from the remote to a warp proof request that we made. - /// - /// Returns next request. - pub fn on_warp_sync_data( - &mut self, - who: &PeerId, - response: EncodedProof, - ) -> Result<(), BadPeer> { + fn on_warp_sync_data(&mut self, who: &PeerId, response: EncodedProof) -> Result<(), BadPeer> { if let Some(peer) = self.peers.get_mut(who) { if let PeerSyncState::DownloadingWarpProof = peer.state { peer.state = PeerSyncState::Available; @@ -1396,57 +1166,7 @@ where } } - fn validate_and_queue_blocks( - &mut self, - mut new_blocks: Vec>, - gap: bool, - ) -> OnBlockData { - let orig_len = new_blocks.len(); - new_blocks.retain(|b| !self.queue_blocks.contains(&b.hash)); - if new_blocks.len() != orig_len { - debug!( - target: "sync", - "Ignoring {} blocks that are already queued", - orig_len - new_blocks.len(), - ); - } - - let origin = if !gap && self.status().state != SyncState::Downloading { - BlockOrigin::NetworkBroadcast - } else { - BlockOrigin::NetworkInitialSync - }; - - if let Some((h, n)) = new_blocks - .last() - .and_then(|b| b.header.as_ref().map(|h| (&b.hash, *h.number()))) - { - trace!( - target:"sync", - "Accepted {} blocks ({:?}) with origin {:?}", - new_blocks.len(), - h, - origin, - ); - self.on_block_queued(h, n) - } - self.queue_blocks.extend(new_blocks.iter().map(|b| b.hash)); - OnBlockData::Import(origin, new_blocks) - } - - fn update_peer_common_number(&mut self, peer_id: &PeerId, new_common: NumberFor) { - if let Some(peer) = self.peers.get_mut(peer_id) { - peer.update_common_number(new_common); - } - } - - /// Handle a response from the remote to a justification request that we made. - /// - /// `request` must be the original request that triggered `response`. - /// - /// Returns `Some` if this produces a justification that must be imported - /// into the import queue. - pub fn on_block_justification( + fn on_block_justification( &mut self, who: PeerId, response: BlockResponse, @@ -1501,18 +1221,12 @@ where Ok(OnBlockJustification::Nothing) } - /// A batch of blocks have been processed, with or without errors. - /// - /// Call this when a batch of blocks have been processed by the import - /// queue, with or without errors. - /// - /// `peer_info` is passed in case of a restart. - pub fn on_blocks_processed( + fn on_blocks_processed( &mut self, imported: usize, count: usize, results: Vec<(Result>, BlockImportError>, B::Hash)>, - ) -> impl Iterator), BadPeer>> { + ) -> Box), BadPeer>>> { trace!(target: "sync", "Imported {} of {}", imported, count); let mut output = Vec::new(); @@ -1654,54 +1368,435 @@ where } self.allowed_requests.set_all(); - output.into_iter() + Box::new(output.into_iter()) + } + + fn on_justification_import(&mut self, hash: B::Hash, number: NumberFor, success: bool) { + let finalization_result = if success { Ok((hash, number)) } else { Err(()) }; + self.extra_justifications + .try_finalize_root((hash, number), finalization_result, true); + self.allowed_requests.set_all(); + } + + fn on_block_finalized(&mut self, hash: &B::Hash, number: NumberFor) { + let client = &self.client; + let r = self.extra_justifications.on_block_finalized(hash, number, |base, block| { + is_descendent_of(&**client, base, block) + }); + + if let SyncMode::LightState { skip_proofs, .. } = &self.mode { + if self.state_sync.is_none() && !self.peers.is_empty() && self.queue_blocks.is_empty() { + // Finalized a recent block. + let mut heads: Vec<_> = + self.peers.iter().map(|(_, peer)| peer.best_number).collect(); + heads.sort(); + let median = heads[heads.len() / 2]; + if number + STATE_SYNC_FINALITY_THRESHOLD.saturated_into() >= median { + if let Ok(Some(header)) = self.client.header(BlockId::hash(*hash)) { + log::debug!( + target: "sync", + "Starting state sync for #{} ({})", + number, + hash, + ); + self.state_sync = + Some(StateSync::new(self.client.clone(), header, *skip_proofs)); + self.allowed_requests.set_all(); + } + } + } + } + + if let Err(err) = r { + warn!( + target: "sync", + "💔 Error cleaning up pending extra justification data requests: {}", + err, + ); + } + } + + fn push_block_announce_validation( + &mut self, + who: PeerId, + hash: B::Hash, + announce: BlockAnnounce, + is_best: bool, + ) { + let header = &announce.header; + let number = *header.number(); + debug!( + target: "sync", + "Pre-validating received block announcement {:?} with number {:?} from {}", + hash, + number, + who, + ); + + if number.is_zero() { + self.block_announce_validation.push( + async move { + warn!( + target: "sync", + "💔 Ignored genesis block (#0) announcement from {}: {}", + who, + hash, + ); + PreValidateBlockAnnounce::Skip + } + .boxed(), + ); + return + } + + // Check if there is a slot for this block announce validation. + match self.has_slot_for_block_announce_validation(&who) { + HasSlotForBlockAnnounceValidation::Yes => {}, + HasSlotForBlockAnnounceValidation::TotalMaximumSlotsReached => { + self.block_announce_validation.push( + async move { + warn!( + target: "sync", + "💔 Ignored block (#{} -- {}) announcement from {} because all validation slots are occupied.", + number, + hash, + who, + ); + PreValidateBlockAnnounce::Skip + } + .boxed(), + ); + return + }, + HasSlotForBlockAnnounceValidation::MaximumPeerSlotsReached => { + self.block_announce_validation.push(async move { + warn!( + target: "sync", + "💔 Ignored block (#{} -- {}) announcement from {} because all validation slots for this peer are occupied.", + number, + hash, + who, + ); + PreValidateBlockAnnounce::Skip + }.boxed()); + return + }, + } + + // Let external validator check the block announcement. + let assoc_data = announce.data.as_ref().map_or(&[][..], |v| v.as_slice()); + let future = self.block_announce_validator.validate(header, assoc_data); + + self.block_announce_validation.push( + async move { + match future.await { + Ok(Validation::Success { is_new_best }) => PreValidateBlockAnnounce::Process { + is_new_best: is_new_best || is_best, + announce, + who, + }, + Ok(Validation::Failure { disconnect }) => { + debug!( + target: "sync", + "Block announcement validation of block {:?} from {} failed", + hash, + who, + ); + PreValidateBlockAnnounce::Failure { who, disconnect } + }, + Err(e) => { + debug!( + target: "sync", + "💔 Block announcement validation of block {:?} errored: {}", + hash, + e, + ); + PreValidateBlockAnnounce::Error { who } + }, + } + } + .boxed(), + ); + } + + fn poll_block_announce_validation( + &mut self, + cx: &mut std::task::Context, + ) -> Poll> { + match self.block_announce_validation.poll_next_unpin(cx) { + Poll::Ready(Some(res)) => { + self.peer_block_announce_validation_finished(&res); + Poll::Ready(self.finish_block_announce_validation(res)) + }, + _ => Poll::Pending, + } + } + + fn peer_disconnected(&mut self, who: &PeerId) -> Option> { + self.blocks.clear_peer_download(who); + if let Some(gap_sync) = &mut self.gap_sync { + gap_sync.blocks.clear_peer_download(who) + } + self.peers.remove(who); + self.extra_justifications.peer_disconnected(who); + self.allowed_requests.set_all(); + self.fork_targets.retain(|_, target| { + target.peers.remove(who); + !target.peers.is_empty() + }); + let blocks = self.ready_blocks(); + (!blocks.is_empty()).then(|| self.validate_and_queue_blocks(blocks, false)) + } + + fn metrics(&self) -> Metrics { + Metrics { + queued_blocks: self.queue_blocks.len().try_into().unwrap_or(std::u32::MAX), + fork_targets: self.fork_targets.len().try_into().unwrap_or(std::u32::MAX), + justifications: self.extra_justifications.metrics(), + } + } + + /// Create implementation-specific block request. + fn create_opaque_block_request(&self, request: &BlockRequest) -> OpaqueBlockRequest { + OpaqueBlockRequest(Box::new(schema::v1::BlockRequest { + fields: request.fields.to_be_u32(), + from_block: match request.from { + FromBlock::Hash(h) => Some(schema::v1::block_request::FromBlock::Hash(h.encode())), + FromBlock::Number(n) => + Some(schema::v1::block_request::FromBlock::Number(n.encode())), + }, + to_block: request.to.map(|h| h.encode()).unwrap_or_default(), + direction: request.direction as i32, + max_blocks: request.max.unwrap_or(0), + support_multiple_justifications: true, + })) + } + + fn encode_block_request(&self, request: &OpaqueBlockRequest) -> Result, String> { + let request: &schema::v1::BlockRequest = request.0.downcast_ref().ok_or_else(|| { + "Failed to downcast opaque block response during encoding, this is an \ + implementation bug." + .to_string() + })?; + + Ok(request.encode_to_vec()) + } + + fn decode_block_response(&self, response: &[u8]) -> Result { + let response = schema::v1::BlockResponse::decode(response) + .map_err(|error| format!("Failed to decode block response: {error}"))?; + + Ok(OpaqueBlockResponse(Box::new(response))) + } + + fn block_response_into_blocks( + &self, + request: &BlockRequest, + response: OpaqueBlockResponse, + ) -> Result>, String> { + let response: Box = response.0.downcast().map_err(|_error| { + "Failed to downcast opaque block response during encoding, this is an \ + implementation bug." + .to_string() + })?; + + response + .blocks + .into_iter() + .map(|block_data| { + Ok(BlockData:: { + hash: Decode::decode(&mut block_data.hash.as_ref())?, + header: if !block_data.header.is_empty() { + Some(Decode::decode(&mut block_data.header.as_ref())?) + } else { + None + }, + body: if request.fields.contains(BlockAttributes::BODY) { + Some( + block_data + .body + .iter() + .map(|body| Decode::decode(&mut body.as_ref())) + .collect::, _>>()?, + ) + } else { + None + }, + indexed_body: if request.fields.contains(BlockAttributes::INDEXED_BODY) { + Some(block_data.indexed_body) + } else { + None + }, + receipt: if !block_data.receipt.is_empty() { + Some(block_data.receipt) + } else { + None + }, + message_queue: if !block_data.message_queue.is_empty() { + Some(block_data.message_queue) + } else { + None + }, + justification: if !block_data.justification.is_empty() { + Some(block_data.justification) + } else if block_data.is_empty_justification { + Some(Vec::new()) + } else { + None + }, + justifications: if !block_data.justifications.is_empty() { + Some(DecodeAll::decode_all(&mut block_data.justifications.as_ref())?) + } else { + None + }, + }) + }) + .collect::>() + .map_err(|error: codec::Error| error.to_string()) + } + + fn encode_state_request(&self, request: &OpaqueStateRequest) -> Result, String> { + let request: &StateRequest = request.0.downcast_ref().ok_or_else(|| { + "Failed to downcast opaque state response during encoding, this is an \ + implementation bug." + .to_string() + })?; + + Ok(request.encode_to_vec()) + } + + fn decode_state_response(&self, response: &[u8]) -> Result { + let response = StateResponse::decode(response) + .map_err(|error| format!("Failed to decode state response: {error}"))?; + + Ok(OpaqueStateResponse(Box::new(response))) + } +} + +impl ChainSync +where + Self: ChainSyncT, + B: BlockT, + Client: HeaderBackend + + BlockBackend + + HeaderMetadata + + ProofProvider + + Send + + Sync + + 'static, +{ + /// Create a new instance. + pub fn new( + mode: SyncMode, + client: Arc, + block_announce_validator: Box + Send>, + max_parallel_downloads: u32, + warp_sync_provider: Option>>, + ) -> Result { + let mut sync = Self { + client, + peers: HashMap::new(), + blocks: BlockCollection::new(), + best_queued_hash: Default::default(), + best_queued_number: Zero::zero(), + extra_justifications: ExtraRequests::new("justification"), + mode, + queue_blocks: Default::default(), + fork_targets: Default::default(), + allowed_requests: Default::default(), + block_announce_validator, + max_parallel_downloads, + downloaded_blocks: 0, + block_announce_validation: Default::default(), + block_announce_validation_per_peer_stats: Default::default(), + state_sync: None, + warp_sync: None, + warp_sync_provider, + import_existing: false, + gap_sync: None, + }; + sync.reset_sync_start_point()?; + Ok(sync) } - /// Call this when a justification has been processed by the import queue, - /// with or without errors. - pub fn on_justification_import(&mut self, hash: B::Hash, number: NumberFor, success: bool) { - let finalization_result = if success { Ok((hash, number)) } else { Err(()) }; - self.extra_justifications - .try_finalize_root((hash, number), finalization_result, true); - self.allowed_requests.set_all(); + /// Returns the best seen block. + fn best_seen(&self) -> Option> { + let mut best_seens = self.peers.values().map(|p| p.best_number).collect::>(); + + if best_seens.is_empty() { + None + } else { + let middle = best_seens.len() / 2; + + // Not the "perfect median" when we have an even number of peers. + Some(*best_seens.select_nth_unstable(middle).1) + } } - /// Notify about finalization of the given block. - pub fn on_block_finalized(&mut self, hash: &B::Hash, number: NumberFor) { - let client = &self.client; - let r = self.extra_justifications.on_block_finalized(hash, number, |base, block| { - is_descendent_of(&**client, base, block) - }); + fn required_block_attributes(&self) -> BlockAttributes { + match self.mode { + SyncMode::Full => + BlockAttributes::HEADER | BlockAttributes::JUSTIFICATION | BlockAttributes::BODY, + SyncMode::Light => BlockAttributes::HEADER | BlockAttributes::JUSTIFICATION, + SyncMode::LightState { storage_chain_mode: false, .. } | SyncMode::Warp => + BlockAttributes::HEADER | BlockAttributes::JUSTIFICATION | BlockAttributes::BODY, + SyncMode::LightState { storage_chain_mode: true, .. } => + BlockAttributes::HEADER | + BlockAttributes::JUSTIFICATION | + BlockAttributes::INDEXED_BODY, + } + } - if let SyncMode::LightState { skip_proofs, .. } = &self.mode { - if self.state_sync.is_none() && !self.peers.is_empty() && self.queue_blocks.is_empty() { - // Finalized a recent block. - let mut heads: Vec<_> = - self.peers.iter().map(|(_, peer)| peer.best_number).collect(); - heads.sort(); - let median = heads[heads.len() / 2]; - if number + STATE_SYNC_FINALITY_THRESHOLD.saturated_into() >= median { - if let Ok(Some(header)) = self.client.header(BlockId::hash(*hash)) { - log::debug!( - target: "sync", - "Starting state sync for #{} ({})", - number, - hash, - ); - self.state_sync = - Some(StateSync::new(self.client.clone(), header, *skip_proofs)); - self.allowed_requests.set_all(); - } - } - } + fn skip_execution(&self) -> bool { + match self.mode { + SyncMode::Full => false, + SyncMode::Light => true, + SyncMode::LightState { .. } => true, + SyncMode::Warp => true, } + } - if let Err(err) = r { - warn!( + fn validate_and_queue_blocks( + &mut self, + mut new_blocks: Vec>, + gap: bool, + ) -> OnBlockData { + let orig_len = new_blocks.len(); + new_blocks.retain(|b| !self.queue_blocks.contains(&b.hash)); + if new_blocks.len() != orig_len { + debug!( target: "sync", - "💔 Error cleaning up pending extra justification data requests: {}", - err, + "Ignoring {} blocks that are already queued", + orig_len - new_blocks.len(), + ); + } + + let origin = if !gap && self.status().state != SyncState::Downloading { + BlockOrigin::NetworkBroadcast + } else { + BlockOrigin::NetworkInitialSync + }; + + if let Some((h, n)) = new_blocks + .last() + .and_then(|b| b.header.as_ref().map(|h| (&b.hash, *h.number()))) + { + trace!( + target:"sync", + "Accepted {} blocks ({:?}) with origin {:?}", + new_blocks.len(), + h, + origin, ); + self.on_block_queued(h, n) + } + self.queue_blocks.extend(new_blocks.iter().map(|b| b.hash)); + OnBlockData::Import(origin, new_blocks) + } + + fn update_peer_common_number(&mut self, peer_id: &PeerId, new_common: NumberFor) { + if let Some(peer) = self.peers.get_mut(peer_id) { + peer.update_common_number(new_common); } } @@ -1779,135 +1874,6 @@ where } } - /// Push a block announce validation. - /// - /// It is required that [`ChainSync::poll_block_announce_validation`] is called - /// to check for finished block announce validations. - pub fn push_block_announce_validation( - &mut self, - who: PeerId, - hash: B::Hash, - announce: BlockAnnounce, - is_best: bool, - ) { - let header = &announce.header; - let number = *header.number(); - debug!( - target: "sync", - "Pre-validating received block announcement {:?} with number {:?} from {}", - hash, - number, - who, - ); - - if number.is_zero() { - self.block_announce_validation.push( - async move { - warn!( - target: "sync", - "💔 Ignored genesis block (#0) announcement from {}: {}", - who, - hash, - ); - PreValidateBlockAnnounce::Skip - } - .boxed(), - ); - return - } - - // Check if there is a slot for this block announce validation. - match self.has_slot_for_block_announce_validation(&who) { - HasSlotForBlockAnnounceValidation::Yes => {}, - HasSlotForBlockAnnounceValidation::TotalMaximumSlotsReached => { - self.block_announce_validation.push( - async move { - warn!( - target: "sync", - "💔 Ignored block (#{} -- {}) announcement from {} because all validation slots are occupied.", - number, - hash, - who, - ); - PreValidateBlockAnnounce::Skip - } - .boxed(), - ); - return - }, - HasSlotForBlockAnnounceValidation::MaximumPeerSlotsReached => { - self.block_announce_validation.push(async move { - warn!( - target: "sync", - "💔 Ignored block (#{} -- {}) announcement from {} because all validation slots for this peer are occupied.", - number, - hash, - who, - ); - PreValidateBlockAnnounce::Skip - }.boxed()); - return - }, - } - - // Let external validator check the block announcement. - let assoc_data = announce.data.as_ref().map_or(&[][..], |v| v.as_slice()); - let future = self.block_announce_validator.validate(header, assoc_data); - - self.block_announce_validation.push( - async move { - match future.await { - Ok(Validation::Success { is_new_best }) => PreValidateBlockAnnounce::Process { - is_new_best: is_new_best || is_best, - announce, - who, - }, - Ok(Validation::Failure { disconnect }) => { - debug!( - target: "sync", - "Block announcement validation of block {:?} from {} failed", - hash, - who, - ); - PreValidateBlockAnnounce::Failure { who, disconnect } - }, - Err(e) => { - debug!( - target: "sync", - "💔 Block announcement validation of block {:?} errored: {}", - hash, - e, - ); - PreValidateBlockAnnounce::Error { who } - }, - } - } - .boxed(), - ); - } - - /// Poll block announce validation. - /// - /// Block announce validations can be pushed by using - /// [`ChainSync::push_block_announce_validation`]. - /// - /// This should be polled until it returns [`Poll::Pending`]. - /// - /// If [`PollBlockAnnounceValidation::ImportHeader`] is returned, then the caller MUST try to - /// import passed header (call `on_block_data`). The network request isn't sent in this case. - pub fn poll_block_announce_validation( - &mut self, - cx: &mut std::task::Context, - ) -> Poll> { - match self.block_announce_validation.poll_next_unpin(cx) { - Poll::Ready(Some(res)) => { - self.peer_block_announce_validation_finished(&res); - Poll::Ready(self.finish_block_announce_validation(res)) - }, - _ => Poll::Pending, - } - } - /// Should be called when a block announce validation is finished, to update the slots /// of the peer that send the block announce. fn peer_block_announce_validation_finished( @@ -2065,29 +2031,6 @@ where PollBlockAnnounceValidation::Nothing { is_best, who, announce } } - /// Call when a peer has disconnected. - /// Canceled obsolete block request may result in some blocks being ready for - /// import, so this functions checks for such blocks and returns them. - pub fn peer_disconnected(&mut self, who: &PeerId) -> Option> { - self.blocks.clear_peer_download(who); - if let Some(gap_sync) = &mut self.gap_sync { - gap_sync.blocks.clear_peer_download(who) - } - self.peers.remove(who); - self.extra_justifications.peer_disconnected(who); - self.allowed_requests.set_all(); - self.fork_targets.retain(|_, target| { - target.peers.remove(who); - !target.peers.is_empty() - }); - let blocks = self.ready_blocks(); - if !blocks.is_empty() { - Some(self.validate_and_queue_blocks(blocks, false)) - } else { - None - } - } - /// Restart the sync process. This will reset all pending block requests and return an iterator /// of new block requests to make to peers. Peers that were downloading finality data (i.e. /// their state was `DownloadingJustification`) are unaffected and will stay in the same state. @@ -2190,16 +2133,6 @@ where .any(|(_, p)| p.state == PeerSyncState::DownloadingStale(*hash)) } - /// Return some key metrics. - pub fn metrics(&self) -> Metrics { - Metrics { - queued_blocks: self.queue_blocks.len().try_into().unwrap_or(std::u32::MAX), - fork_targets: self.fork_targets.len().try_into().unwrap_or(std::u32::MAX), - justifications: self.extra_justifications.metrics(), - _priv: (), - } - } - /// Get the set of downloaded blocks that are ready to be queued for import. fn ready_blocks(&mut self) -> Vec> { self.blocks @@ -2237,23 +2170,15 @@ fn legacy_justification_mapping( justification.map(|just| (*b"FRNK", just).into()) } -#[derive(Debug)] -pub struct Metrics { - pub queued_blocks: u32, - pub fork_targets: u32, - pub justifications: extra_requests::Metrics, - _priv: (), -} - /// Request the ancestry for a block. Sends a request for header and justification for the given /// block number. Used during ancestry search. fn ancestry_request(block: NumberFor) -> BlockRequest { - message::generic::BlockRequest { + BlockRequest:: { id: 0, fields: BlockAttributes::HEADER | BlockAttributes::JUSTIFICATION, - from: message::FromBlock::Number(block), + from: FromBlock::Number(block), to: None, - direction: message::Direction::Ascending, + direction: Direction::Ascending, max: Some(1), } } @@ -2331,7 +2256,7 @@ fn peer_block_request( id: &PeerId, peer: &PeerSync, blocks: &mut BlockCollection, - attrs: message::BlockAttributes, + attrs: BlockAttributes, max_parallel_downloads: u32, finalized: NumberFor, best_num: NumberFor, @@ -2359,17 +2284,17 @@ fn peer_block_request( let last = range.end.saturating_sub(One::one()); let from = if peer.best_number == last { - message::FromBlock::Hash(peer.best_hash) + FromBlock::Hash(peer.best_hash) } else { - message::FromBlock::Number(last) + FromBlock::Number(last) }; - let request = message::generic::BlockRequest { + let request = BlockRequest:: { id: 0, fields: attrs, from, to: None, - direction: message::Direction::Descending, + direction: Direction::Descending, max: Some((range.end - range.start).saturated_into::()), }; @@ -2381,7 +2306,7 @@ fn peer_gap_block_request( id: &PeerId, peer: &PeerSync, blocks: &mut BlockCollection, - attrs: message::BlockAttributes, + attrs: BlockAttributes, target: NumberFor, common_number: NumberFor, ) -> Option<(Range>, BlockRequest)> { @@ -2396,14 +2321,14 @@ fn peer_gap_block_request( // The end is not part of the range. let last = range.end.saturating_sub(One::one()); - let from = message::FromBlock::Number(last); + let from = FromBlock::Number(last); - let request = message::generic::BlockRequest { + let request = BlockRequest:: { id: 0, fields: attrs, from, to: None, - direction: message::Direction::Descending, + direction: Direction::Descending, max: Some((range.end - range.start).saturated_into::()), }; Some((range, request)) @@ -2415,7 +2340,7 @@ fn fork_sync_request( targets: &mut HashMap>, best_num: NumberFor, finalized: NumberFor, - attributes: message::BlockAttributes, + attributes: BlockAttributes, check_block: impl Fn(&B::Hash) -> BlockStatus, ) -> Option<(B::Hash, BlockRequest)> { targets.retain(|hash, r| { @@ -2448,12 +2373,12 @@ fn fork_sync_request( trace!(target: "sync", "Downloading requested fork {:?} from {}, {} blocks", hash, id, count); return Some(( *hash, - message::generic::BlockRequest { + BlockRequest:: { id: 0, fields: attributes, - from: message::FromBlock::Hash(*hash), + from: FromBlock::Hash(*hash), to: None, - direction: message::Direction::Descending, + direction: Direction::Descending, max: Some(count), }, )) @@ -2488,7 +2413,7 @@ where /// /// It is expected that `blocks` are in ascending order. fn validate_blocks( - blocks: &Vec>, + blocks: &Vec>, who: &PeerId, request: Option>, ) -> Result>, BadPeer> { @@ -2505,16 +2430,13 @@ fn validate_blocks( return Err(BadPeer(*who, rep::NOT_REQUESTED)) } - let block_header = if request.direction == message::Direction::Descending { - blocks.last() - } else { - blocks.first() - } - .and_then(|b| b.header.as_ref()); + let block_header = + if request.direction == Direction::Descending { blocks.last() } else { blocks.first() } + .and_then(|b| b.header.as_ref()); let expected_block = block_header.as_ref().map_or(false, |h| match request.from { - message::FromBlock::Hash(hash) => h.hash() == hash, - message::FromBlock::Number(n) => h.number() == &n, + FromBlock::Hash(hash) => h.hash() == hash, + FromBlock::Number(n) => h.number() == &n, }); if !expected_block { @@ -2528,7 +2450,7 @@ fn validate_blocks( return Err(BadPeer(*who, rep::NOT_REQUESTED)) } - if request.fields.contains(message::BlockAttributes::HEADER) && + if request.fields.contains(BlockAttributes::HEADER) && blocks.iter().any(|b| b.header.is_none()) { trace!( @@ -2540,8 +2462,7 @@ fn validate_blocks( return Err(BadPeer(*who, rep::BAD_RESPONSE)) } - if request.fields.contains(message::BlockAttributes::BODY) && - blocks.iter().any(|b| b.body.is_none()) + if request.fields.contains(BlockAttributes::BODY) && blocks.iter().any(|b| b.body.is_none()) { trace!( target: "sync", @@ -2592,13 +2513,10 @@ fn validate_blocks( #[cfg(test)] mod test { - use super::{ - message::{BlockState, FromBlock}, - *, - }; - use crate::message::BlockData; + use super::*; use futures::{executor::block_on, future::poll_fn}; use sc_block_builder::BlockBuilderProvider; + use sc_network_common::sync::message::{BlockData, BlockState, FromBlock}; use sp_blockchain::HeaderBackend; use sp_consensus::block_validation::DefaultBlockAnnounceValidator; use substrate_test_runtime_client::{ @@ -2709,7 +2627,7 @@ mod test { assert!(sync.justification_requests().any(|(p, r)| { p == peer_id3 && r.fields == BlockAttributes::JUSTIFICATION && - r.from == message::FromBlock::Hash(b1_hash) && + r.from == FromBlock::Hash(b1_hash) && r.to == None })); @@ -3125,10 +3043,11 @@ mod test { let response = create_block_response(vec![block.clone()]); let on_block_data = sync.on_block_data(&peer_id1, Some(request), response).unwrap(); - request = match on_block_data.into_request() { - Some(req) => req.1, + request = if let OnBlockData::Request(_peer, request) = on_block_data { + request + } else { // We found the ancenstor - None => break, + break }; log::trace!(target: "sync", "Request: {:?}", request); @@ -3255,10 +3174,11 @@ mod test { let response = create_block_response(vec![block.clone()]); let on_block_data = sync.on_block_data(&peer_id1, Some(request), response).unwrap(); - request = match on_block_data.into_request() { - Some(req) => req.1, + request = if let OnBlockData::Request(_peer, request) = on_block_data { + request + } else { // We found the ancenstor - None => break, + break }; log::trace!(target: "sync", "Request: {:?}", request); diff --git a/client/network/sync/src/schema.rs b/client/network/sync/src/schema.rs index aa3eb84621d8f..b31005360d023 100644 --- a/client/network/sync/src/schema.rs +++ b/client/network/sync/src/schema.rs @@ -18,6 +18,6 @@ //! Include sources generated from protobuf definitions. -pub mod v1 { +pub(crate) mod v1 { include!(concat!(env!("OUT_DIR"), "/api.v1.rs")); } diff --git a/client/network/sync/src/state.rs b/client/network/sync/src/state.rs index 4041c28af0eba..e70d3b6b33a28 100644 --- a/client/network/sync/src/state.rs +++ b/client/network/sync/src/state.rs @@ -23,6 +23,7 @@ use codec::{Decode, Encode}; use log::debug; use sc_client_api::{CompactProof, ProofProvider}; use sc_consensus::ImportedState; +use sc_network_common::sync::StateDownloadProgress; use smallvec::SmallVec; use sp_core::storage::well_known_keys; use sp_runtime::traits::{Block as BlockT, Header, NumberFor}; @@ -42,15 +43,6 @@ pub struct StateSync { skip_proof: bool, } -/// Reported state download progress. -#[derive(Clone, Eq, PartialEq, Debug)] -pub struct StateDownloadProgress { - /// Estimated download percentage. - pub percentage: u32, - /// Total state size in bytes downloaded so far. - pub size: u64, -} - /// Import state chunk result. pub enum ImportResult { /// State is complete and ready for import. diff --git a/client/network/sync/src/warp.rs b/client/network/sync/src/warp.rs index d3d9d7d244153..f3fad6c1b7fdb 100644 --- a/client/network/sync/src/warp.rs +++ b/client/network/sync/src/warp.rs @@ -18,60 +18,25 @@ //! Warp sync support. -pub use crate::warp_request_handler::{ - EncodedProof, Request as WarpProofRequest, VerificationResult, WarpSyncProvider, -}; use crate::{ schema::v1::{StateRequest, StateResponse}, state::{ImportResult, StateSync}, }; use sc_client_api::ProofProvider; +use sc_network_common::sync::warp::{ + EncodedProof, VerificationResult, WarpProofRequest, WarpSyncPhase, WarpSyncProgress, + WarpSyncProvider, +}; use sp_blockchain::HeaderBackend; use sp_finality_grandpa::{AuthorityList, SetId}; use sp_runtime::traits::{Block as BlockT, NumberFor, Zero}; -use std::{fmt, sync::Arc}; +use std::sync::Arc; enum Phase { WarpProof { set_id: SetId, authorities: AuthorityList, last_hash: B::Hash }, State(StateSync), } -/// Reported warp sync phase. -#[derive(Clone, Eq, PartialEq, Debug)] -pub enum WarpSyncPhase { - /// Waiting for peers to connect. - AwaitingPeers, - /// Downloading and verifying grandpa warp proofs. - DownloadingWarpProofs, - /// Downloading state data. - DownloadingState, - /// Importing state. - ImportingState, - /// Downloading block history. - DownloadingBlocks(NumberFor), -} - -impl fmt::Display for WarpSyncPhase { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::AwaitingPeers => write!(f, "Waiting for peers"), - Self::DownloadingWarpProofs => write!(f, "Downloading finality proofs"), - Self::DownloadingState => write!(f, "Downloading state"), - Self::ImportingState => write!(f, "Importing state"), - Self::DownloadingBlocks(n) => write!(f, "Downloading block history (#{})", n), - } - } -} - -/// Reported warp sync progress. -#[derive(Clone, Eq, PartialEq, Debug)] -pub struct WarpSyncProgress { - /// Estimated download percentage. - pub phase: WarpSyncPhase, - /// Total bytes downloaded so far. - pub total_bytes: u64, -} - /// Import warp proof result. pub enum WarpProofImportResult { /// Import was successful. diff --git a/client/network/sync/src/warp_request_handler.rs b/client/network/sync/src/warp_request_handler.rs index 4f66e0a6daf17..53ec216a1e668 100644 --- a/client/network/sync/src/warp_request_handler.rs +++ b/client/network/sync/src/warp_request_handler.rs @@ -1,4 +1,4 @@ -// Copyright 2021 Parity Technologies (UK) Ltd. +// Copyright 2022 Parity Technologies (UK) Ltd. // This file is part of Substrate. // Substrate is free software: you can redistribute it and/or modify @@ -16,7 +16,7 @@ //! Helper for handling (i.e. answering) grandpa warp sync requests from a remote peer. -use codec::{Decode, Encode}; +use codec::Decode; use futures::{ channel::{mpsc, oneshot}, stream::StreamExt, @@ -27,52 +27,13 @@ use sc_network_common::{ request_responses::{ IncomingRequest, OutgoingResponse, ProtocolConfig as RequestResponseConfig, }, + sync::warp::{EncodedProof, WarpProofRequest, WarpSyncProvider}, }; use sp_runtime::traits::Block as BlockT; use std::{sync::Arc, time::Duration}; -pub use sp_finality_grandpa::{AuthorityList, SetId}; - -/// Scale-encoded warp sync proof response. -pub struct EncodedProof(pub Vec); - -/// Warp sync request -#[derive(Encode, Decode, Debug)] -pub struct Request { - /// Start collecting proofs from this block. - pub begin: B::Hash, -} - const MAX_RESPONSE_SIZE: u64 = 16 * 1024 * 1024; -/// Proof verification result. -pub enum VerificationResult { - /// Proof is valid, but the target was not reached. - Partial(SetId, AuthorityList, Block::Hash), - /// Target finality is proved. - Complete(SetId, AuthorityList, Block::Header), -} - -/// Warp sync backend. Handles retrieveing and verifying warp sync proofs. -pub trait WarpSyncProvider: Send + Sync { - /// Generate proof starting at given block hash. The proof is accumulated until maximum proof - /// size is reached. - fn generate( - &self, - start: B::Hash, - ) -> Result>; - /// Verify warp proof against current set of authorities. - fn verify( - &self, - proof: &EncodedProof, - set_id: SetId, - authorities: AuthorityList, - ) -> Result, Box>; - /// Get current list of authorities. This is supposed to be genesis authorities when starting - /// sync. - fn current_authorities(&self) -> AuthorityList; -} - /// Generates a [`RequestResponseConfig`] for the grandpa warp sync request protocol, refusing /// incoming requests. pub fn generate_request_response_config(protocol_id: ProtocolId) -> RequestResponseConfig { @@ -115,7 +76,7 @@ impl RequestHandler { payload: Vec, pending_response: oneshot::Sender, ) -> Result<(), HandleRequestError> { - let request = Request::::decode(&mut &payload[..])?; + let request = WarpProofRequest::::decode(&mut &payload[..])?; let EncodedProof(proof) = self .backend diff --git a/client/network/test/Cargo.toml b/client/network/test/Cargo.toml index fdb9befc41cb1..1aa6ebd8bf357 100644 --- a/client/network/test/Cargo.toml +++ b/client/network/test/Cargo.toml @@ -26,6 +26,8 @@ sc-client-api = { version = "4.0.0-dev", path = "../../api" } sc-consensus = { version = "0.10.0-dev", path = "../../consensus/common" } sc-network = { version = "0.10.0-dev", path = "../" } sc-network-common = { version = "0.10.0-dev", path = "../common" } +sc-network-light = { version = "0.10.0-dev", path = "../light" } +sc-network-sync = { version = "0.10.0-dev", path = "../sync" } sc-service = { version = "0.10.0-dev", default-features = false, features = ["test-helpers"], path = "../../service" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } diff --git a/client/network/test/src/lib.rs b/client/network/test/src/lib.rs index 9e752e81a3bbc..d7c83810d521d 100644 --- a/client/network/test/src/lib.rs +++ b/client/network/test/src/lib.rs @@ -48,16 +48,21 @@ use sc_consensus::{ }; pub use sc_network::config::EmptyTransactionPool; use sc_network::{ - block_request_handler::BlockRequestHandler, config::{ - MultiaddrWithPeerId, NetworkConfiguration, NonDefaultSetConfig, NonReservedPeerMode, - ProtocolConfig, Role, SyncMode, TransportConfig, + MultiaddrWithPeerId, NetworkConfiguration, NonDefaultSetConfig, NonReservedPeerMode, Role, + SyncMode, TransportConfig, }, - light_client_requests::handler::LightClientRequestHandler, - state_request_handler::StateRequestHandler, - warp_request_handler, Multiaddr, NetworkService, NetworkWorker, + Multiaddr, NetworkService, NetworkWorker, }; pub use sc_network_common::config::ProtocolId; +use sc_network_common::sync::warp::{ + AuthorityList, EncodedProof, SetId, VerificationResult, WarpSyncProvider, +}; +use sc_network_light::light_client_requests::handler::LightClientRequestHandler; +use sc_network_sync::{ + block_request_handler::BlockRequestHandler, state_request_handler::StateRequestHandler, + warp_request_handler, ChainSync, +}; use sc_service::client::Client; use sp_blockchain::{ well_known_cache_keys::{self, Id as CacheKeyId}, @@ -638,27 +643,26 @@ impl VerifierAdapter { struct TestWarpSyncProvider(Arc>); -impl warp_request_handler::WarpSyncProvider for TestWarpSyncProvider { +impl WarpSyncProvider for TestWarpSyncProvider { fn generate( &self, _start: B::Hash, - ) -> Result> { + ) -> Result> { let info = self.0.info(); let best_header = self.0.header(BlockId::hash(info.best_hash)).unwrap().unwrap(); - Ok(warp_request_handler::EncodedProof(best_header.encode())) + Ok(EncodedProof(best_header.encode())) } fn verify( &self, - proof: &warp_request_handler::EncodedProof, - _set_id: warp_request_handler::SetId, - _authorities: warp_request_handler::AuthorityList, - ) -> Result, Box> - { - let warp_request_handler::EncodedProof(encoded) = proof; + proof: &EncodedProof, + _set_id: SetId, + _authorities: AuthorityList, + ) -> Result, Box> { + let EncodedProof(encoded) = proof; let header = B::Header::decode(&mut encoded.as_slice()).unwrap(); - Ok(warp_request_handler::VerificationResult::Complete(0, Default::default(), header)) + Ok(VerificationResult::Complete(0, Default::default(), header)) } - fn current_authorities(&self) -> warp_request_handler::AuthorityList { + fn current_authorities(&self) -> AuthorityList { Default::default() } } @@ -688,7 +692,7 @@ pub struct FullPeerConfig { pub storage_chain: bool, } -pub trait TestNetFactory: Sized +pub trait TestNetFactory: Default + Sized where >::Transaction: Send, { @@ -696,14 +700,8 @@ where type BlockImport: BlockImport + Clone + Send + Sync + 'static; type PeerData: Default; - /// These two need to be implemented! - fn from_config(config: &ProtocolConfig) -> Self; - fn make_verifier( - &self, - client: PeersClient, - config: &ProtocolConfig, - peer_data: &Self::PeerData, - ) -> Self::Verifier; + /// This one needs to be implemented! + fn make_verifier(&self, client: PeersClient, peer_data: &Self::PeerData) -> Self::Verifier; /// Get reference to peer. fn peer(&mut self, i: usize) -> &mut Peer; @@ -723,15 +721,10 @@ where Self::PeerData, ); - fn default_config() -> ProtocolConfig { - ProtocolConfig::default() - } - /// Create new test network with this many peers. fn new(n: usize) -> Self { trace!(target: "test_network", "Creating test network"); - let config = Self::default_config(); - let mut net = Self::from_config(&config); + let mut net = Self::default(); for i in 0..n { trace!(target: "test_network", "Adding peer {}", i); @@ -767,11 +760,8 @@ where let (block_import, justification_import, data) = self .make_block_import(PeersClient { client: client.clone(), backend: backend.clone() }); - let verifier = self.make_verifier( - PeersClient { client: client.clone(), backend: backend.clone() }, - &Default::default(), - &data, - ); + let verifier = self + .make_verifier(PeersClient { client: client.clone(), backend: backend.clone() }, &data); let verifier = VerifierAdapter::new(verifier); let import_queue = Box::new(BasicQueue::new( @@ -845,6 +835,10 @@ where protocol_config }; + let max_parallel_downloads = network_config.max_parallel_downloads; + let block_announce_validator = config + .block_announce_validator + .unwrap_or_else(|| Box::new(DefaultBlockAnnounceValidator)); let network = NetworkWorker::new(sc_network::config::Params { role: if config.is_authority { Role::Authority } else { Role::Full }, executor: None, @@ -856,9 +850,18 @@ where transaction_pool: Arc::new(EmptyTransactionPool), protocol_id, import_queue, - block_announce_validator: config - .block_announce_validator - .unwrap_or_else(|| Box::new(DefaultBlockAnnounceValidator)), + create_chain_sync: Box::new(move |sync_mode, chain, warp_sync_provider| { + match ChainSync::new( + sync_mode, + chain, + block_announce_validator, + max_parallel_downloads, + warp_sync_provider, + ) { + Ok(chain_sync) => Ok(Box::new(chain_sync)), + Err(error) => Err(Box::new(error).into()), + } + }), metrics_registry: None, block_request_protocol_config, state_request_protocol_config, @@ -1012,6 +1015,7 @@ where } } +#[derive(Default)] pub struct TestNet { peers: Vec>, } @@ -1021,17 +1025,7 @@ impl TestNetFactory for TestNet { type PeerData = (); type BlockImport = PeersClient; - /// Create new test network with peers and given config. - fn from_config(_config: &ProtocolConfig) -> Self { - TestNet { peers: Vec::new() } - } - - fn make_verifier( - &self, - _client: PeersClient, - _config: &ProtocolConfig, - _peer_data: &(), - ) -> Self::Verifier { + fn make_verifier(&self, _client: PeersClient, _peer_data: &()) -> Self::Verifier { PassThroughVerifier::new(false) } @@ -1081,6 +1075,7 @@ impl JustificationImport for ForceFinalized { } } +#[derive(Default)] pub struct JustificationTestNet(TestNet); impl TestNetFactory for JustificationTestNet { @@ -1088,17 +1083,8 @@ impl TestNetFactory for JustificationTestNet { type PeerData = (); type BlockImport = PeersClient; - fn from_config(config: &ProtocolConfig) -> Self { - JustificationTestNet(TestNet::from_config(config)) - } - - fn make_verifier( - &self, - client: PeersClient, - config: &ProtocolConfig, - peer_data: &(), - ) -> Self::Verifier { - self.0.make_verifier(client, config, peer_data) + fn make_verifier(&self, client: PeersClient, peer_data: &()) -> Self::Verifier { + self.0.make_verifier(client, peer_data) } fn peer(&mut self, i: usize) -> &mut Peer { diff --git a/client/service/Cargo.toml b/client/service/Cargo.toml index ff11dd1344d01..31b0c860cf190 100644 --- a/client/service/Cargo.toml +++ b/client/service/Cargo.toml @@ -50,8 +50,10 @@ sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/comm sc-consensus = { version = "0.10.0-dev", path = "../../client/consensus/common" } sp-inherents = { version = "4.0.0-dev", path = "../../primitives/inherents" } sp-storage = { version = "6.0.0", path = "../../primitives/storage" } -sc-network-common = { version = "0.10.0-dev", path = "../network/common" } sc-network = { version = "0.10.0-dev", path = "../network" } +sc-network-common = { version = "0.10.0-dev", path = "../network/common" } +sc-network-light = { version = "0.10.0-dev", path = "../network/light" } +sc-network-sync = { version = "0.10.0-dev", path = "../network/sync" } sc-chain-spec = { version = "4.0.0-dev", path = "../chain-spec" } sc-client-api = { version = "4.0.0-dev", path = "../api" } sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } diff --git a/client/service/src/builder.rs b/client/service/src/builder.rs index 5319bf24d5e72..fe0ae5db53e70 100644 --- a/client/service/src/builder.rs +++ b/client/service/src/builder.rs @@ -38,13 +38,17 @@ use sc_consensus::import_queue::ImportQueue; use sc_executor::RuntimeVersionOf; use sc_keystore::LocalKeystore; use sc_network::{ - block_request_handler::{self, BlockRequestHandler}, config::{Role, SyncMode}, - light_client_requests::{self, handler::LightClientRequestHandler}, - state_request_handler::{self, StateRequestHandler}, - warp_request_handler::{self, RequestHandler as WarpSyncRequestHandler, WarpSyncProvider}, NetworkService, }; +use sc_network_common::sync::warp::WarpSyncProvider; +use sc_network_light::light_client_requests::{self, handler::LightClientRequestHandler}; +use sc_network_sync::{ + block_request_handler::{self, BlockRequestHandler}, + state_request_handler::{self, StateRequestHandler}, + warp_request_handler::{self, RequestHandler as WarpSyncRequestHandler}, + ChainSync, +}; use sc_rpc::{ author::AuthorApiServer, chain::ChainApiServer, @@ -801,6 +805,7 @@ where } }; + let max_parallel_downloads = config.network.max_parallel_downloads; let network_params = sc_network::config::Params { role: config.role.clone(), executor: { @@ -818,9 +823,20 @@ where network_config: config.network.clone(), chain: client.clone(), transaction_pool: transaction_pool_adapter as _, - import_queue: Box::new(import_queue), protocol_id, - block_announce_validator, + import_queue: Box::new(import_queue), + create_chain_sync: Box::new( + move |sync_mode, chain, warp_sync_provider| match ChainSync::new( + sync_mode, + chain, + block_announce_validator, + max_parallel_downloads, + warp_sync_provider, + ) { + Ok(chain_sync) => Ok(Box::new(chain_sync)), + Err(error) => Err(Box::new(error).into()), + }, + ), metrics_registry: config.prometheus_config.as_ref().map(|config| config.registry.clone()), block_request_protocol_config, state_request_protocol_config, From 2063965c1286b383e5165d8d97995a64a41fb362 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Wed, 13 Jul 2022 11:09:28 +0100 Subject: [PATCH 387/484] Fix off by one error in proportional slashing (#11782) * Fix proportional slashing logic * Update frame/nomination-pools/test-staking/src/lib.rs Co-authored-by: David * Update frame/staking/src/lib.rs Co-authored-by: David * Update frame/staking/src/lib.rs Co-authored-by: David * Update frame/staking/src/lib.rs Co-authored-by: David * fmt * Update frame/nomination-pools/test-staking/src/lib.rs * clean * fix * last fixes * doc Co-authored-by: David --- .../nomination-pools/test-staking/src/lib.rs | 256 +++++++++++++++++- .../nomination-pools/test-staking/src/mock.rs | 7 +- frame/staking/src/lib.rs | 95 +++++-- frame/staking/src/tests.rs | 30 +- 4 files changed, 342 insertions(+), 46 deletions(-) diff --git a/frame/nomination-pools/test-staking/src/lib.rs b/frame/nomination-pools/test-staking/src/lib.rs index 2e40e8c6d917d..be7752ce113b4 100644 --- a/frame/nomination-pools/test-staking/src/lib.rs +++ b/frame/nomination-pools/test-staking/src/lib.rs @@ -22,7 +22,8 @@ mod mock; use frame_support::{assert_noop, assert_ok, bounded_btree_map, traits::Currency}; use mock::*; use pallet_nomination_pools::{ - Error as PoolsError, Event as PoolsEvent, LastPoolId, PoolMember, PoolMembers, PoolState, + BondedPools, Error as PoolsError, Event as PoolsEvent, LastPoolId, PoolMember, PoolMembers, + PoolState, }; use pallet_staking::{CurrentEra, Event as StakingEvent, Payee, RewardDestination}; @@ -273,7 +274,7 @@ fn pool_slash_e2e() { 30, &mut Default::default(), &mut Default::default(), - 1, // slash era 1, affects chunks at era 5 onwards. + 2, // slash era 2, affects chunks at era 5 onwards. ); assert_eq!(staking_events_since_last_call(), vec![StakingEvent::Slashed(POOL1_BONDED, 30)]); @@ -371,3 +372,254 @@ fn pool_slash_e2e() { ); }); } + +#[test] +fn pool_slash_proportional() { + // a typical example where 3 pool members unbond in era 99, 100, and 101, and a slash that + // happened in era 100 should only affect the latter two. + new_test_ext().execute_with(|| { + ExistentialDeposit::set(1); + BondingDuration::set(28); + assert_eq!(Balances::minimum_balance(), 1); + assert_eq!(Staking::current_era(), None); + + // create the pool, we know this has id 1. + assert_ok!(Pools::create(Origin::signed(10), 40, 10, 10, 10)); + assert_eq!(LastPoolId::::get(), 1); + + assert_eq!(staking_events_since_last_call(), vec![StakingEvent::Bonded(POOL1_BONDED, 40)]); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Created { depositor: 10, pool_id: 1 }, + PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true }, + ] + ); + + // have two members join + let bond = 20; + assert_ok!(Pools::join(Origin::signed(20), bond, 1)); + assert_ok!(Pools::join(Origin::signed(21), bond, 1)); + assert_ok!(Pools::join(Origin::signed(22), bond, 1)); + + assert_eq!( + staking_events_since_last_call(), + vec![ + StakingEvent::Bonded(POOL1_BONDED, bond), + StakingEvent::Bonded(POOL1_BONDED, bond), + StakingEvent::Bonded(POOL1_BONDED, bond), + ] + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: bond, joined: true }, + PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: bond, joined: true }, + PoolsEvent::Bonded { member: 22, pool_id: 1, bonded: bond, joined: true }, + ] + ); + + // now let's progress a lot. + CurrentEra::::set(Some(99)); + + // and unbond + assert_ok!(Pools::unbond(Origin::signed(20), 20, bond)); + + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Unbonded(POOL1_BONDED, bond),] + ); + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::Unbonded { member: 20, pool_id: 1, balance: bond, points: bond }] + ); + + CurrentEra::::set(Some(100)); + assert_ok!(Pools::unbond(Origin::signed(21), 21, bond)); + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Unbonded(POOL1_BONDED, bond),] + ); + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::Unbonded { member: 21, pool_id: 1, balance: bond, points: bond }] + ); + + CurrentEra::::set(Some(101)); + assert_ok!(Pools::unbond(Origin::signed(22), 22, bond)); + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Unbonded(POOL1_BONDED, bond),] + ); + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::Unbonded { member: 22, pool_id: 1, balance: bond, points: bond }] + ); + + // Apply a slash that happened in era 100. This is typically applied with a delay. + // Of the total 100, 50 is slashed. + assert_eq!(BondedPools::::get(1).unwrap().points, 40); + pallet_staking::slashing::do_slash::( + &POOL1_BONDED, + 50, + &mut Default::default(), + &mut Default::default(), + 100, + ); + + assert_eq!(staking_events_since_last_call(), vec![StakingEvent::Slashed(POOL1_BONDED, 50)]); + assert_eq!( + pool_events_since_last_call(), + vec![ + // This last pool got slashed only the leftover dust. Otherwise in principle, this + // chunk/pool should have not been affected. + PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 127, balance: 19 }, + // This pool got slashed 12.5, which rounded down to 12. + PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 128, balance: 8 }, + // This pool got slashed 12.5, which rounded down to 12. + PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 129, balance: 8 }, + // Bonded pool got slashed for 25, remaining 15 in it. + PoolsEvent::PoolSlashed { pool_id: 1, balance: 15 } + ] + ); + }); +} + +#[test] +fn pool_slash_non_proportional_only_bonded_pool() { + // A typical example where a pool member unbonds in era 99, and he can get away with a slash + // that happened in era 100, as long as the pool has enough active bond to cover the slash. If + // everything else in the slashing/staking system works, this should always be the case. + // Nonetheless, `ledger.slash` has been written such that it will slash greedily from any chunk + // if it runs out of chunks that it thinks should be affected by the slash. + new_test_ext().execute_with(|| { + ExistentialDeposit::set(1); + BondingDuration::set(28); + assert_eq!(Balances::minimum_balance(), 1); + assert_eq!(Staking::current_era(), None); + + // create the pool, we know this has id 1. + assert_ok!(Pools::create(Origin::signed(10), 40, 10, 10, 10)); + assert_eq!(staking_events_since_last_call(), vec![StakingEvent::Bonded(POOL1_BONDED, 40)]); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Created { depositor: 10, pool_id: 1 }, + PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true }, + ] + ); + + // have two members join + let bond = 20; + assert_ok!(Pools::join(Origin::signed(20), bond, 1)); + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Bonded(POOL1_BONDED, bond)] + ); + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: bond, joined: true }] + ); + + // progress and unbond. + CurrentEra::::set(Some(99)); + assert_ok!(Pools::unbond(Origin::signed(20), 20, bond)); + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Unbonded(POOL1_BONDED, bond)] + ); + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::Unbonded { member: 20, pool_id: 1, balance: bond, points: bond }] + ); + + // slash for 30. This will be deducted only from the bonded pool. + CurrentEra::::set(Some(100)); + assert_eq!(BondedPools::::get(1).unwrap().points, 40); + pallet_staking::slashing::do_slash::( + &POOL1_BONDED, + 30, + &mut Default::default(), + &mut Default::default(), + 100, + ); + + assert_eq!(staking_events_since_last_call(), vec![StakingEvent::Slashed(POOL1_BONDED, 30)]); + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::PoolSlashed { pool_id: 1, balance: 10 }] + ); + }); +} + +#[test] +fn pool_slash_non_proportional_bonded_pool_and_chunks() { + // An uncommon example where even though some funds are unlocked such that they should not be + // affected by a slash, we still slash out of them. This should not happen at all. If a + // nomination has unbonded, from the next era onwards, their exposure will drop, so if an era + // happens in that era, then their share of that slash should naturally be less, such that only + // their active ledger stake is enough to compensate it. + new_test_ext().execute_with(|| { + ExistentialDeposit::set(1); + BondingDuration::set(28); + assert_eq!(Balances::minimum_balance(), 1); + assert_eq!(Staking::current_era(), None); + + // create the pool, we know this has id 1. + assert_ok!(Pools::create(Origin::signed(10), 40, 10, 10, 10)); + assert_eq!(staking_events_since_last_call(), vec![StakingEvent::Bonded(POOL1_BONDED, 40)]); + assert_eq!( + pool_events_since_last_call(), + vec![ + PoolsEvent::Created { depositor: 10, pool_id: 1 }, + PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true }, + ] + ); + + // have two members join + let bond = 20; + assert_ok!(Pools::join(Origin::signed(20), bond, 1)); + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Bonded(POOL1_BONDED, bond)] + ); + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: bond, joined: true }] + ); + + // progress and unbond. + CurrentEra::::set(Some(99)); + assert_ok!(Pools::unbond(Origin::signed(20), 20, bond)); + assert_eq!( + staking_events_since_last_call(), + vec![StakingEvent::Unbonded(POOL1_BONDED, bond)] + ); + assert_eq!( + pool_events_since_last_call(), + vec![PoolsEvent::Unbonded { member: 20, pool_id: 1, balance: bond, points: bond }] + ); + + // slash 50. This will be deducted only from the bonded pool and one of the unbonding pools. + CurrentEra::::set(Some(100)); + assert_eq!(BondedPools::::get(1).unwrap().points, 40); + pallet_staking::slashing::do_slash::( + &POOL1_BONDED, + 50, + &mut Default::default(), + &mut Default::default(), + 100, + ); + + assert_eq!(staking_events_since_last_call(), vec![StakingEvent::Slashed(POOL1_BONDED, 50)]); + assert_eq!( + pool_events_since_last_call(), + vec![ + // out of 20, 10 was taken. + PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 127, balance: 10 }, + // out of 40, all was taken. + PoolsEvent::PoolSlashed { pool_id: 1, balance: 0 } + ] + ); + }); +} diff --git a/frame/nomination-pools/test-staking/src/mock.rs b/frame/nomination-pools/test-staking/src/mock.rs index 7b720c009b29b..d13d8aa341c12 100644 --- a/frame/nomination-pools/test-staking/src/mock.rs +++ b/frame/nomination-pools/test-staking/src/mock.rs @@ -24,6 +24,8 @@ type AccountIndex = u32; type BlockNumber = u64; type Balance = u128; +pub(crate) type T = Runtime; + pub(crate) const POOL1_BONDED: AccountId = 20318131474730217858575332831085u128; pub(crate) const POOL1_REWARD: AccountId = 20397359637244482196168876781421u128; @@ -194,13 +196,14 @@ frame_support::construct_runtime!( ); pub fn new_test_ext() -> sp_io::TestExternalities { + sp_tracing::try_init_simple(); let mut storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); let _ = pallet_nomination_pools::GenesisConfig:: { min_join_bond: 2, min_create_bond: 2, max_pools: Some(3), - max_members_per_pool: Some(3), - max_members: Some(3 * 3), + max_members_per_pool: Some(5), + max_members: Some(3 * 5), } .assimilate_storage(&mut storage) .unwrap(); diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 360d5b5efb58f..50c033bdc7e28 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -529,13 +529,26 @@ impl StakingLedger { (self, unlocking_balance) } - /// Slash the staker for a given amount of balance. This can grow the value of the slash in the - /// case that either the active bonded or some unlocking chunks become dust after slashing. - /// Returns the amount of funds actually slashed. + /// Slash the staker for a given amount of balance. /// - /// `slash_era` is the era in which the slash (which is being enacted now) actually happened. + /// This implements a proportional slashing system, whereby we set our preference to slash as + /// such: + /// + /// - If any unlocking chunks exist that are scheduled to be unlocked at `slash_era + + /// bonding_duration` and onwards, the slash is divided equally between the active ledger and + /// the unlocking chunks. + /// - If no such chunks exist, then only the active balance is slashed. + /// + /// Note that the above is only a *preference*. If for any reason the active ledger, with or + /// without some portion of the unlocking chunks that are more justified to be slashed are not + /// enough, then the slashing will continue and will consume as much of the active and unlocking + /// chunks as needed. /// - /// # Note + /// This will never slash more than the given amount. If any of the chunks become dusted, the + /// last chunk is slashed slightly less to compensate. Returns the amount of funds actually + /// slashed. + /// + /// `slash_era` is the era in which the slash (which is being enacted now) actually happened. /// /// This calls `Config::OnStakerSlash::on_slash` with information as to how the slash was /// applied. @@ -545,54 +558,81 @@ impl StakingLedger { minimum_balance: BalanceOf, slash_era: EraIndex, ) -> BalanceOf { - use sp_staking::OnStakerSlash as _; - if slash_amount.is_zero() { return Zero::zero() } + use sp_staking::OnStakerSlash as _; let mut remaining_slash = slash_amount; let pre_slash_total = self.total; - let era_after_slash = slash_era + 1; - let chunk_unlock_era_after_slash = era_after_slash + T::BondingDuration::get(); + // for a `slash_era = x`, any chunk that is scheduled to be unlocked at era `x + 28` + // (assuming 28 is the bonding duration) onwards should be slashed. + let slashable_chunks_start = slash_era + T::BondingDuration::get(); - // Calculate the total balance of active funds and unlocking funds in the affected range. - let (affected_balance, slash_chunks_priority): (_, Box>) = { - if let Some(start_index) = - self.unlocking.iter().position(|c| c.era >= chunk_unlock_era_after_slash) + // `Some(ratio)` if this is proportional, with `ratio`, `None` otherwise. In both cases, we + // slash first the active chunk, and then `slash_chunks_priority`. + let (maybe_proportional, slash_chunks_priority) = { + if let Some(first_slashable_index) = + self.unlocking.iter().position(|c| c.era >= slashable_chunks_start) { + // If there exists a chunk who's after the first_slashable_start, then this is a + // proportional slash, because we want to slash active and these chunks + // proportionally. + // The indices of the first chunk after the slash up through the most recent chunk. // (The most recent chunk is at greatest from this era) - let affected_indices = start_index..self.unlocking.len(); + let affected_indices = first_slashable_index..self.unlocking.len(); let unbonding_affected_balance = affected_indices.clone().fold(BalanceOf::::zero(), |sum, i| { - if let Some(chunk) = self.unlocking.get_mut(i).defensive() { + if let Some(chunk) = self.unlocking.get(i).defensive() { sum.saturating_add(chunk.value) } else { sum } }); + let affected_balance = self.active.saturating_add(unbonding_affected_balance); + let ratio = Perquintill::from_rational(slash_amount, affected_balance); ( - self.active.saturating_add(unbonding_affected_balance), - Box::new(affected_indices.chain((0..start_index).rev())), + Some(ratio), + affected_indices.chain((0..first_slashable_index).rev()).collect::>(), ) } else { - (self.active, Box::new((0..self.unlocking.len()).rev())) + // We just slash from the last chunk to the most recent one, if need be. + (None, (0..self.unlocking.len()).rev().collect::>()) } }; // Helper to update `target` and the ledgers total after accounting for slashing `target`. - let ratio = Perquintill::from_rational(slash_amount, affected_balance); + log!( + debug, + "slashing {:?} for era {:?} out of {:?}, priority: {:?}, proportional = {:?}", + slash_amount, + slash_era, + self, + slash_chunks_priority, + maybe_proportional, + ); + let mut slash_out_of = |target: &mut BalanceOf, slash_remaining: &mut BalanceOf| { - let mut slash_from_target = - if slash_amount < affected_balance { ratio * (*target) } else { *slash_remaining } - .min(*target); + let mut slash_from_target = if let Some(ratio) = maybe_proportional { + ratio * (*target) + } else { + *slash_remaining + } + // this is the total that that the slash target has. We can't slash more than + // this anyhow! + .min(*target) + // this is the total amount that we would have wanted to slash + // non-proportionally, a proportional slash should never exceed this either! + .min(*slash_remaining); // slash out from *target exactly `slash_from_target`. *target = *target - slash_from_target; if *target < minimum_balance { - // Slash the rest of the target if its dust + // Slash the rest of the target if it's dust. This might cause the last chunk to be + // slightly under-slashed, by at most `MaxUnlockingChunks * ED`, which is not a big + // deal. slash_from_target = sp_std::mem::replace(target, Zero::zero()).saturating_add(slash_from_target) } @@ -606,10 +646,11 @@ impl StakingLedger { let mut slashed_unlocking = BTreeMap::<_, _>::new(); for i in slash_chunks_priority { + if remaining_slash.is_zero() { + break + } + if let Some(chunk) = self.unlocking.get_mut(i).defensive() { - if remaining_slash.is_zero() { - break - } slash_out_of(&mut chunk.value, &mut remaining_slash); // write the new slashed value of this chunk to the map. slashed_unlocking.insert(chunk.era, chunk.value); @@ -618,7 +659,9 @@ impl StakingLedger { } } + // clean unlocking chunks that are set to zero. self.unlocking.retain(|c| !c.value.is_zero()); + T::OnStakerSlash::on_slash(&self.stash, self.active, &slashed_unlocking); pre_slash_total.saturating_sub(self.total) } diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 9a13a818f4b59..b76126f0c5d04 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -2081,8 +2081,7 @@ fn reward_validator_slashing_validator_does_not_overflow() { let _ = Balances::make_free_balance_be(&11, stake); let _ = Balances::make_free_balance_be(&2, stake); - // only slashes out of bonded stake are applied. without this line, - // it is 0. + // only slashes out of bonded stake are applied. without this line, it is 0. Staking::bond(Origin::signed(2), 20000, stake - 1, RewardDestination::default()).unwrap(); // Override exposure of 11 ErasStakers::::insert( @@ -2104,7 +2103,7 @@ fn reward_validator_slashing_validator_does_not_overflow() { &[Perbill::from_percent(100)], ); - assert_eq!(Balances::total_balance(&11), stake); + assert_eq!(Balances::total_balance(&11), stake - 1); assert_eq!(Balances::total_balance(&2), 1); }) } @@ -4960,7 +4959,6 @@ fn proportional_ledger_slash_works() { unlocking: bounded_vec![], claimed_rewards: vec![], }; - assert_eq!(BondingDuration::get(), 3); // When we slash a ledger with no unlocking chunks @@ -4997,7 +4995,7 @@ fn proportional_ledger_slash_works() { ledger.total = 4 * 100; ledger.active = 0; // When the first 2 chunks don't overlap with the affected range of unlock eras. - assert_eq!(ledger.slash(140, 0, 2), 140); + assert_eq!(ledger.slash(140, 0, 3), 140); // Then assert_eq!(ledger.unlocking, vec![c(4, 100), c(5, 100), c(6, 30), c(7, 30)]); assert_eq!(ledger.total, 4 * 100 - 140); @@ -5039,7 +5037,7 @@ fn proportional_ledger_slash_works() { ledger.active = 500; ledger.total = 40 + 10 + 100 + 250 + 500; // 900 assert_eq!(ledger.total, 900); - // When we have a higher min balance + // When we have a higher min balance assert_eq!( ledger.slash( 900 / 2, @@ -5047,16 +5045,17 @@ fn proportional_ledger_slash_works() { * get swept */ 0 ), - 475 + 450 ); - let dust = (10 / 2) + (40 / 2); assert_eq!(ledger.active, 500 / 2); - assert_eq!(ledger.unlocking, vec![c(5, 100 / 2), c(7, 250 / 2)]); - assert_eq!(ledger.total, 900 / 2 - dust); + // the last chunk was not slashed 50% like all the rest, because some other earlier chunks got + // dusted. + assert_eq!(ledger.unlocking, vec![c(5, 100 / 2), c(7, 150)]); + assert_eq!(ledger.total, 900 / 2); assert_eq!(LedgerSlashPerEra::get().0, 500 / 2); assert_eq!( LedgerSlashPerEra::get().1, - BTreeMap::from([(4, 0), (5, 100 / 2), (6, 0), (7, 250 / 2)]) + BTreeMap::from([(4, 0), (5, 100 / 2), (6, 0), (7, 150)]) ); // Given @@ -5068,7 +5067,7 @@ fn proportional_ledger_slash_works() { ledger.slash( 500 + 10 + 250 + 100 / 2, // active + era 6 + era 7 + era 5 / 2 0, - 2 /* slash era 2+4 first, so the affected parts are era 2+4, era 3+4 and + 3 /* slash era 6 first, so the affected parts are era 6, era 7 and * ledge.active. This will cause the affected to go to zero, and then we will * start slashing older chunks */ ), @@ -5091,7 +5090,7 @@ fn proportional_ledger_slash_works() { ledger.slash( 351, // active + era 6 + era 7 + era 5 / 2 + 1 50, // min balance - everything slashed below 50 will get dusted - 2 /* slash era 2+4 first, so the affected parts are era 2+4, era 3+4 and + 3 /* slash era 3+3 first, so the affected parts are era 6, era 7 and * ledge.active. This will cause the affected to go to zero, and then we will * start slashing older chunks */ ), @@ -5108,9 +5107,8 @@ fn proportional_ledger_slash_works() { // Given let slash = u64::MAX as Balance * 2; - let value = slash - - (9 * 4) // The value of the other parts of ledger that will get slashed - + 1; + // The value of the other parts of ledger that will get slashed + let value = slash - (10 * 4); ledger.active = 10; ledger.unlocking = bounded_vec![c(4, 10), c(5, 10), c(6, 10), c(7, value)]; From 5dbe8311435460db0fd1d90b01dd1ada2b819d89 Mon Sep 17 00:00:00 2001 From: Jegor Sidorenko <5252494+jsidorenko@users.noreply.github.com> Date: Wed, 13 Jul 2022 13:41:20 +0100 Subject: [PATCH 388/484] Buy&Sell methods for Uniques (#11398) * Allow to set item's price * Clean the state when we transfer/burn an item or destroy a collection * Allow to buy an item * Remove redundant checks * Improve events * Cover with tests * Add comments * Apply suggestions * Fmt * Improvements for price validation * Improve validation * Update to use the new terminology * Remove multi-assets support * Chore * Weights + benchmarking * Shield against human error * Test when we pass the higher item's price * fmt fix * Chore * cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_uniques --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/uniques/src/weights.rs --template=./.maintain/frame-weight-template.hbs * Remove is_frozen check when setting the price * Try to fix benchmarking * Fix benchmarking * cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_uniques --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/uniques/src/weights.rs --template=./.maintain/frame-weight-template.hbs * Add transactional * Add 'allow deprecated' flag for transactional * Remove #[allow(deprecated)] * ".git/.scripts/bench-bot.sh" pallet dev pallet_uniques Co-authored-by: Parity Bot Co-authored-by: command-bot <> --- frame/uniques/src/benchmarking.rs | 36 ++++++ frame/uniques/src/functions.rs | 79 ++++++++++++- frame/uniques/src/lib.rs | 88 ++++++++++++++- frame/uniques/src/tests.rs | 178 +++++++++++++++++++++++++++++- frame/uniques/src/types.rs | 2 + frame/uniques/src/weights.rs | 174 +++++++++++++++++------------ 6 files changed, 485 insertions(+), 72 deletions(-) diff --git a/frame/uniques/src/benchmarking.rs b/frame/uniques/src/benchmarking.rs index 14a3e2c61809a..713393048f7e9 100644 --- a/frame/uniques/src/benchmarking.rs +++ b/frame/uniques/src/benchmarking.rs @@ -408,5 +408,41 @@ benchmarks_instance_pallet! { }.into()); } + set_price { + let (collection, caller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + let delegate: T::AccountId = account("delegate", 0, SEED); + let delegate_lookup = T::Lookup::unlookup(delegate.clone()); + let price = ItemPrice::::from(100u32); + }: _(SystemOrigin::Signed(caller.clone()), collection, item, Some(price), Some(delegate_lookup)) + verify { + assert_last_event::(Event::ItemPriceSet { + collection, + item, + price, + whitelisted_buyer: Some(delegate), + }.into()); + } + + buy_item { + let (collection, seller, _) = create_collection::(); + let (item, ..) = mint_item::(0); + let buyer: T::AccountId = account("buyer", 0, SEED); + let buyer_lookup = T::Lookup::unlookup(buyer.clone()); + let price = ItemPrice::::from(0u32); + let origin = SystemOrigin::Signed(seller.clone()).into(); + Uniques::::set_price(origin, collection, item, Some(price.clone()), Some(buyer_lookup))?; + T::Currency::make_free_balance_be(&buyer, DepositBalanceOf::::max_value()); + }: _(SystemOrigin::Signed(buyer.clone()), collection, item, price.clone()) + verify { + assert_last_event::(Event::ItemBought { + collection, + item, + price, + seller, + buyer, + }.into()); + } + impl_benchmark_test_suite!(Uniques, crate::mock::new_test_ext(), crate::mock::Test); } diff --git a/frame/uniques/src/functions.rs b/frame/uniques/src/functions.rs index 155fb35ef0999..9e8d3301616df 100644 --- a/frame/uniques/src/functions.rs +++ b/frame/uniques/src/functions.rs @@ -18,7 +18,10 @@ //! Various pieces of common functionality. use super::*; -use frame_support::{ensure, traits::Get}; +use frame_support::{ + ensure, + traits::{ExistenceRequirement, Get}, +}; use sp_runtime::{DispatchError, DispatchResult}; impl, I: 'static> Pallet { @@ -46,6 +49,7 @@ impl, I: 'static> Pallet { let origin = details.owner; details.owner = dest; Item::::insert(&collection, &item, &details); + ItemPriceOf::::remove(&collection, &item); Self::deposit_event(Event::Transferred { collection, @@ -112,6 +116,8 @@ impl, I: 'static> Pallet { } #[allow(deprecated)] ItemMetadataOf::::remove_prefix(&collection, None); + #[allow(deprecated)] + ItemPriceOf::::remove_prefix(&collection, None); CollectionMetadataOf::::remove(&collection); #[allow(deprecated)] Attribute::::remove_prefix((&collection,), None); @@ -196,8 +202,79 @@ impl, I: 'static> Pallet { Item::::remove(&collection, &item); Account::::remove((&owner, &collection, &item)); + ItemPriceOf::::remove(&collection, &item); Self::deposit_event(Event::Burned { collection, item, owner }); Ok(()) } + + pub fn do_set_price( + collection: T::CollectionId, + item: T::ItemId, + sender: T::AccountId, + price: Option>, + whitelisted_buyer: Option, + ) -> DispatchResult { + let details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + ensure!(details.owner == sender, Error::::NoPermission); + + if let Some(ref price) = price { + ItemPriceOf::::insert( + &collection, + &item, + (price.clone(), whitelisted_buyer.clone()), + ); + Self::deposit_event(Event::ItemPriceSet { + collection, + item, + price: price.clone(), + whitelisted_buyer, + }); + } else { + ItemPriceOf::::remove(&collection, &item); + Self::deposit_event(Event::ItemPriceRemoved { collection, item }); + } + + Ok(()) + } + + pub fn do_buy_item( + collection: T::CollectionId, + item: T::ItemId, + buyer: T::AccountId, + bid_price: ItemPrice, + ) -> DispatchResult { + let details = Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + ensure!(details.owner != buyer, Error::::NoPermission); + + let price_info = + ItemPriceOf::::get(&collection, &item).ok_or(Error::::NotForSale)?; + + ensure!(bid_price >= price_info.0, Error::::BidTooLow); + + if let Some(only_buyer) = price_info.1 { + ensure!(only_buyer == buyer, Error::::NoPermission); + } + + T::Currency::transfer( + &buyer, + &details.owner, + price_info.0, + ExistenceRequirement::KeepAlive, + )?; + + let old_owner = details.owner.clone(); + + Self::do_transfer(collection, item, buyer.clone(), |_, _| Ok(()))?; + + Self::deposit_event(Event::ItemBought { + collection, + item, + price: price_info.0, + seller: old_owner, + buyer, + }); + + Ok(()) + } } diff --git a/frame/uniques/src/lib.rs b/frame/uniques/src/lib.rs index 5c7adeac0eff2..53f83605da90e 100644 --- a/frame/uniques/src/lib.rs +++ b/frame/uniques/src/lib.rs @@ -24,6 +24,7 @@ //! * [`System`](../frame_system/index.html) //! * [`Support`](../frame_support/index.html) +#![recursion_limit = "256"] // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] @@ -42,8 +43,11 @@ pub mod migration; pub mod weights; use codec::{Decode, Encode}; -use frame_support::traits::{ - tokens::Locker, BalanceStatus::Reserved, Currency, EnsureOriginWithArg, ReservableCurrency, +use frame_support::{ + traits::{ + tokens::Locker, BalanceStatus::Reserved, Currency, EnsureOriginWithArg, ReservableCurrency, + }, + transactional, }; use frame_system::Config as SystemConfig; use sp_runtime::{ @@ -245,6 +249,18 @@ pub mod pallet { OptionQuery, >; + #[pallet::storage] + /// Price of an asset instance. + pub(super) type ItemPriceOf, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + T::CollectionId, + Blake2_128Concat, + T::ItemId, + (ItemPrice, Option), + OptionQuery, + >; + #[pallet::storage] /// Keeps track of the number of items a collection might have. pub(super) type CollectionMaxSupply, I: 'static = ()> = @@ -341,6 +357,23 @@ pub mod pallet { OwnershipAcceptanceChanged { who: T::AccountId, maybe_collection: Option }, /// Max supply has been set for a collection. CollectionMaxSupplySet { collection: T::CollectionId, max_supply: u32 }, + /// The price was set for the instance. + ItemPriceSet { + collection: T::CollectionId, + item: T::ItemId, + price: ItemPrice, + whitelisted_buyer: Option, + }, + /// The price for the instance was removed. + ItemPriceRemoved { collection: T::CollectionId, item: T::ItemId }, + /// An item was bought. + ItemBought { + collection: T::CollectionId, + item: T::ItemId, + price: ItemPrice, + seller: T::AccountId, + buyer: T::AccountId, + }, } #[pallet::error] @@ -375,6 +408,12 @@ pub mod pallet { MaxSupplyAlreadySet, /// The provided max supply is less to the amount of items a collection already has. MaxSupplyTooSmall, + /// The given item ID is unknown. + UnknownItem, + /// Item is not for sale. + NotForSale, + /// The provided bid is too low. + BidTooLow, } impl, I: 'static> Pallet { @@ -1408,5 +1447,50 @@ pub mod pallet { Self::deposit_event(Event::CollectionMaxSupplySet { collection, max_supply }); Ok(()) } + + /// Set (or reset) the price for an item. + /// + /// Origin must be Signed and must be the owner of the asset `item`. + /// + /// - `collection`: The collection of the item. + /// - `item`: The item to set the price for. + /// - `price`: The price for the item. Pass `None`, to reset the price. + /// - `buyer`: Restricts the buy operation to a specific account. + /// + /// Emits `ItemPriceSet` on success if the price is not `None`. + /// Emits `ItemPriceRemoved` on success if the price is `None`. + #[pallet::weight(T::WeightInfo::set_price())] + pub fn set_price( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + price: Option>, + whitelisted_buyer: Option<::Source>, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + let whitelisted_buyer = whitelisted_buyer.map(T::Lookup::lookup).transpose()?; + Self::do_set_price(collection, item, origin, price, whitelisted_buyer) + } + + /// Allows to buy an item if it's up for sale. + /// + /// Origin must be Signed and must not be the owner of the `item`. + /// + /// - `collection`: The collection of the item. + /// - `item`: The item the sender wants to buy. + /// - `bid_price`: The price the sender is willing to pay. + /// + /// Emits `ItemBought` on success. + #[pallet::weight(T::WeightInfo::buy_item())] + #[transactional] + pub fn buy_item( + origin: OriginFor, + collection: T::CollectionId, + item: T::ItemId, + bid_price: ItemPrice, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + Self::do_buy_item(collection, item, origin, bid_price) + } } } diff --git a/frame/uniques/src/tests.rs b/frame/uniques/src/tests.rs index ef28a2143e84a..8b1d00d7ba0c7 100644 --- a/frame/uniques/src/tests.rs +++ b/frame/uniques/src/tests.rs @@ -18,7 +18,7 @@ //! Tests for Uniques pallet. use crate::{mock::*, Event, *}; -use frame_support::{assert_noop, assert_ok, traits::Currency}; +use frame_support::{assert_noop, assert_ok, dispatch::Dispatchable, traits::Currency}; use pallet_balances::Error as BalancesError; use sp_std::prelude::*; @@ -694,3 +694,179 @@ fn max_supply_should_work() { assert!(!CollectionMaxSupply::::contains_key(collection_id)); }); } + +#[test] +fn set_price_should_work() { + new_test_ext().execute_with(|| { + let user_id = 1; + let collection_id = 0; + let item_1 = 1; + let item_2 = 2; + + assert_ok!(Uniques::force_create(Origin::root(), collection_id, user_id, true)); + + assert_ok!(Uniques::mint(Origin::signed(user_id), collection_id, item_1, user_id)); + assert_ok!(Uniques::mint(Origin::signed(user_id), collection_id, item_2, user_id)); + + assert_ok!(Uniques::set_price( + Origin::signed(user_id), + collection_id, + item_1, + Some(1), + None, + )); + + assert_ok!(Uniques::set_price( + Origin::signed(user_id), + collection_id, + item_2, + Some(2), + Some(3) + )); + + let item = ItemPriceOf::::get(collection_id, item_1).unwrap(); + assert_eq!(item.0, 1); + assert_eq!(item.1, None); + + let item = ItemPriceOf::::get(collection_id, item_2).unwrap(); + assert_eq!(item.0, 2); + assert_eq!(item.1, Some(3)); + + assert!(events().contains(&Event::::ItemPriceSet { + collection: collection_id, + item: item_1, + price: 1, + whitelisted_buyer: None, + })); + + // validate we can unset the price + assert_ok!(Uniques::set_price(Origin::signed(user_id), collection_id, item_2, None, None)); + assert!(events().contains(&Event::::ItemPriceRemoved { + collection: collection_id, + item: item_2 + })); + assert!(!ItemPriceOf::::contains_key(collection_id, item_2)); + }); +} + +#[test] +fn buy_item_should_work() { + new_test_ext().execute_with(|| { + let user_1 = 1; + let user_2 = 2; + let user_3 = 3; + let collection_id = 0; + let item_1 = 1; + let item_2 = 2; + let item_3 = 3; + let price_1 = 20; + let price_2 = 30; + let initial_balance = 100; + + Balances::make_free_balance_be(&user_1, initial_balance); + Balances::make_free_balance_be(&user_2, initial_balance); + Balances::make_free_balance_be(&user_3, initial_balance); + + assert_ok!(Uniques::force_create(Origin::root(), collection_id, user_1, true)); + + assert_ok!(Uniques::mint(Origin::signed(user_1), collection_id, item_1, user_1)); + assert_ok!(Uniques::mint(Origin::signed(user_1), collection_id, item_2, user_1)); + assert_ok!(Uniques::mint(Origin::signed(user_1), collection_id, item_3, user_1)); + + assert_ok!(Uniques::set_price( + Origin::signed(user_1), + collection_id, + item_1, + Some(price_1), + None, + )); + + assert_ok!(Uniques::set_price( + Origin::signed(user_1), + collection_id, + item_2, + Some(price_2), + Some(user_3), + )); + + // can't buy for less + assert_noop!( + Uniques::buy_item(Origin::signed(user_2), collection_id, item_1, 1), + Error::::BidTooLow + ); + + // pass the higher price to validate it will still deduct correctly + assert_ok!(Uniques::buy_item(Origin::signed(user_2), collection_id, item_1, price_1 + 1,)); + + // validate the new owner & balances + let item = Item::::get(collection_id, item_1).unwrap(); + assert_eq!(item.owner, user_2); + assert_eq!(Balances::total_balance(&user_1), initial_balance + price_1); + assert_eq!(Balances::total_balance(&user_2), initial_balance - price_1); + + // can't buy from yourself + assert_noop!( + Uniques::buy_item(Origin::signed(user_1), collection_id, item_2, price_2), + Error::::NoPermission + ); + + // can't buy when the item is listed for a specific buyer + assert_noop!( + Uniques::buy_item(Origin::signed(user_2), collection_id, item_2, price_2), + Error::::NoPermission + ); + + // can buy when I'm a whitelisted buyer + assert_ok!(Uniques::buy_item(Origin::signed(user_3), collection_id, item_2, price_2,)); + + assert!(events().contains(&Event::::ItemBought { + collection: collection_id, + item: item_2, + price: price_2, + seller: user_1, + buyer: user_3, + })); + + // ensure we reset the buyer field + assert!(!ItemPriceOf::::contains_key(collection_id, item_2)); + + // can't buy when item is not for sale + assert_noop!( + Uniques::buy_item(Origin::signed(user_2), collection_id, item_3, price_2), + Error::::NotForSale + ); + + // ensure we can't buy an item when the collection or an item is frozen + { + assert_ok!(Uniques::set_price( + Origin::signed(user_1), + collection_id, + item_3, + Some(price_1), + None, + )); + + // freeze collection + assert_ok!(Uniques::freeze_collection(Origin::signed(user_1), collection_id)); + + let buy_item_call = mock::Call::Uniques(crate::Call::::buy_item { + collection: collection_id, + item: item_3, + bid_price: price_1, + }); + assert_noop!(buy_item_call.dispatch(Origin::signed(user_2)), Error::::Frozen); + + assert_ok!(Uniques::thaw_collection(Origin::signed(user_1), collection_id)); + + // freeze item + assert_ok!(Uniques::freeze(Origin::signed(user_1), collection_id, item_3)); + + let buy_item_call = mock::Call::Uniques(crate::Call::::buy_item { + collection: collection_id, + item: item_3, + bid_price: price_1, + }); + assert_noop!(buy_item_call.dispatch(Origin::signed(user_2)), Error::::Frozen); + } + }); +} diff --git a/frame/uniques/src/types.rs b/frame/uniques/src/types.rs index d7706fd7e3a58..98e056163d28d 100644 --- a/frame/uniques/src/types.rs +++ b/frame/uniques/src/types.rs @@ -30,6 +30,8 @@ pub(super) type CollectionDetailsFor = CollectionDetails<::AccountId, DepositBalanceOf>; pub(super) type ItemDetailsFor = ItemDetails<::AccountId, DepositBalanceOf>; +pub(super) type ItemPrice = + <>::Currency as Currency<::AccountId>>::Balance; #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct CollectionDetails { diff --git a/frame/uniques/src/weights.rs b/frame/uniques/src/weights.rs index d885077a8dee9..7c8cb170b1b1d 100644 --- a/frame/uniques/src/weights.rs +++ b/frame/uniques/src/weights.rs @@ -18,22 +18,22 @@ //! Autogenerated weights for pallet_uniques //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-06-03, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2022-07-13, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `test-bench-bot`, CPU: `Intel(R) Xeon(R) CPU @ 3.10GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: // target/production/substrate // benchmark // pallet -// --chain=dev // --steps=50 // --repeat=20 -// --pallet=pallet_uniques // --extrinsic=* // --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 +// --pallet=pallet_uniques +// --chain=dev // --output=./frame/uniques/src/weights.rs // --template=./.maintain/frame-weight-template.hbs @@ -70,6 +70,8 @@ pub trait WeightInfo { fn cancel_approval() -> Weight; fn set_accept_ownership() -> Weight; fn set_collection_max_supply() -> Weight; + fn set_price() -> Weight; + fn buy_item() -> Weight; } /// Weights for pallet_uniques using the Substrate node and recommended hardware. @@ -78,14 +80,14 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn create() -> Weight { - (27_715_000 as Weight) + (33_075_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn force_create() -> Weight { - (16_929_000 as Weight) + (19_528_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -102,12 +104,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `a` is `[0, 1000]`. fn destroy(n: u32, m: u32, a: u32, ) -> Weight { (0 as Weight) - // Standard Error: 16_000 - .saturating_add((10_481_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 16_000 - .saturating_add((1_762_000 as Weight).saturating_mul(m as Weight)) - // Standard Error: 16_000 - .saturating_add((1_590_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 25_000 + .saturating_add((13_639_000 as Weight).saturating_mul(n as Weight)) + // Standard Error: 25_000 + .saturating_add((2_393_000 as Weight).saturating_mul(m as Weight)) + // Standard Error: 25_000 + .saturating_add((2_217_000 as Weight).saturating_mul(a as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) .saturating_add(T::DbWeight::get().writes(4 as Weight)) @@ -120,33 +122,35 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques CollectionMaxSupply (r:1 w:0) // Storage: Uniques Account (r:0 w:1) fn mint() -> Weight { - (36_577_000 as Weight) + (42_146_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Account (r:0 w:1) + // Storage: Uniques ItemPriceOf (r:0 w:1) fn burn() -> Weight { - (36_124_000 as Weight) + (42_960_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Account (r:0 w:2) + // Storage: Uniques ItemPriceOf (r:0 w:1) fn transfer() -> Weight { - (27_225_000 as Weight) + (33_025_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) - .saturating_add(T::DbWeight::get().writes(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques Asset (r:100 w:100) /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { (0 as Weight) - // Standard Error: 14_000 - .saturating_add((12_407_000 as Weight).saturating_mul(i as Weight)) + // Standard Error: 24_000 + .saturating_add((15_540_000 as Weight).saturating_mul(i as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -155,26 +159,26 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn freeze() -> Weight { - (21_452_000 as Weight) + (25_194_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn thaw() -> Weight { - (22_155_000 as Weight) + (25_397_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn freeze_collection() -> Weight { - (16_897_000 as Weight) + (19_278_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn thaw_collection() -> Weight { - (16_657_000 as Weight) + (19_304_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -182,20 +186,20 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:2) fn transfer_ownership() -> Weight { - (25_057_000 as Weight) + (28_615_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn set_team() -> Weight { - (17_253_000 as Weight) + (19_943_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn force_item_status() -> Weight { - (20_010_000 as Weight) + (22_583_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -203,7 +207,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn set_attribute() -> Weight { - (41_159_000 as Weight) + (47_520_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -211,65 +215,81 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn clear_attribute() -> Weight { - (39_598_000 as Weight) + (45_316_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:1) fn set_metadata() -> Weight { - (33_387_000 as Weight) + (38_391_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:1) fn clear_metadata() -> Weight { - (33_208_000 as Weight) + (38_023_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassMetadataOf (r:1 w:1) fn set_collection_metadata() -> Weight { - (32_619_000 as Weight) + (37_398_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques ClassMetadataOf (r:1 w:1) fn clear_collection_metadata() -> Weight { - (31_028_000 as Weight) + (35_621_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) fn approve_transfer() -> Weight { - (22_263_000 as Weight) + (25_856_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) fn cancel_approval() -> Weight { - (22_910_000 as Weight) + (26_098_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques OwnershipAcceptance (r:1 w:1) fn set_accept_ownership() -> Weight { - (20_716_000 as Weight) + (24_076_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques CollectionMaxSupply (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn set_collection_max_supply() -> Weight { - (19_380_000 as Weight) + (22_035_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } + // Storage: Uniques Asset (r:1 w:0) + // Storage: Uniques ItemPriceOf (r:0 w:1) + fn set_price() -> Weight { + (22_534_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Uniques Asset (r:1 w:1) + // Storage: Uniques ItemPriceOf (r:1 w:1) + // Storage: Uniques Class (r:1 w:0) + // Storage: Uniques Account (r:0 w:2) + fn buy_item() -> Weight { + (45_272_000 as Weight) + .saturating_add(T::DbWeight::get().reads(3 as Weight)) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) + } } // For backwards compatibility and tests @@ -277,14 +297,14 @@ impl WeightInfo for () { // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn create() -> Weight { - (27_715_000 as Weight) + (33_075_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn force_create() -> Weight { - (16_929_000 as Weight) + (19_528_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -301,12 +321,12 @@ impl WeightInfo for () { /// The range of component `a` is `[0, 1000]`. fn destroy(n: u32, m: u32, a: u32, ) -> Weight { (0 as Weight) - // Standard Error: 16_000 - .saturating_add((10_481_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 16_000 - .saturating_add((1_762_000 as Weight).saturating_mul(m as Weight)) - // Standard Error: 16_000 - .saturating_add((1_590_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 25_000 + .saturating_add((13_639_000 as Weight).saturating_mul(n as Weight)) + // Standard Error: 25_000 + .saturating_add((2_393_000 as Weight).saturating_mul(m as Weight)) + // Standard Error: 25_000 + .saturating_add((2_217_000 as Weight).saturating_mul(a as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) @@ -319,33 +339,35 @@ impl WeightInfo for () { // Storage: Uniques CollectionMaxSupply (r:1 w:0) // Storage: Uniques Account (r:0 w:1) fn mint() -> Weight { - (36_577_000 as Weight) + (42_146_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Account (r:0 w:1) + // Storage: Uniques ItemPriceOf (r:0 w:1) fn burn() -> Weight { - (36_124_000 as Weight) + (42_960_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Account (r:0 w:2) + // Storage: Uniques ItemPriceOf (r:0 w:1) fn transfer() -> Weight { - (27_225_000 as Weight) + (33_025_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) - .saturating_add(RocksDbWeight::get().writes(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques Asset (r:100 w:100) /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { (0 as Weight) - // Standard Error: 14_000 - .saturating_add((12_407_000 as Weight).saturating_mul(i as Weight)) + // Standard Error: 24_000 + .saturating_add((15_540_000 as Weight).saturating_mul(i as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(i as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -354,26 +376,26 @@ impl WeightInfo for () { // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn freeze() -> Weight { - (21_452_000 as Weight) + (25_194_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn thaw() -> Weight { - (22_155_000 as Weight) + (25_397_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn freeze_collection() -> Weight { - (16_897_000 as Weight) + (19_278_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn thaw_collection() -> Weight { - (16_657_000 as Weight) + (19_304_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -381,20 +403,20 @@ impl WeightInfo for () { // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:2) fn transfer_ownership() -> Weight { - (25_057_000 as Weight) + (28_615_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn set_team() -> Weight { - (17_253_000 as Weight) + (19_943_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn force_item_status() -> Weight { - (20_010_000 as Weight) + (22_583_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -402,7 +424,7 @@ impl WeightInfo for () { // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn set_attribute() -> Weight { - (41_159_000 as Weight) + (47_520_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -410,63 +432,79 @@ impl WeightInfo for () { // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn clear_attribute() -> Weight { - (39_598_000 as Weight) + (45_316_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:1) fn set_metadata() -> Weight { - (33_387_000 as Weight) + (38_391_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:1) fn clear_metadata() -> Weight { - (33_208_000 as Weight) + (38_023_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassMetadataOf (r:1 w:1) fn set_collection_metadata() -> Weight { - (32_619_000 as Weight) + (37_398_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques ClassMetadataOf (r:1 w:1) fn clear_collection_metadata() -> Weight { - (31_028_000 as Weight) + (35_621_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) fn approve_transfer() -> Weight { - (22_263_000 as Weight) + (25_856_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) fn cancel_approval() -> Weight { - (22_910_000 as Weight) + (26_098_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques OwnershipAcceptance (r:1 w:1) fn set_accept_ownership() -> Weight { - (20_716_000 as Weight) + (24_076_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques CollectionMaxSupply (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn set_collection_max_supply() -> Weight { - (19_380_000 as Weight) + (22_035_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } + // Storage: Uniques Asset (r:1 w:0) + // Storage: Uniques ItemPriceOf (r:0 w:1) + fn set_price() -> Weight { + (22_534_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(1 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Uniques Asset (r:1 w:1) + // Storage: Uniques ItemPriceOf (r:1 w:1) + // Storage: Uniques Class (r:1 w:0) + // Storage: Uniques Account (r:0 w:2) + fn buy_item() -> Weight { + (45_272_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(3 as Weight)) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) + } } From 417651b69ff7c494c82a761869460d7a3ce1f68b Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Wed, 13 Jul 2022 13:49:20 +0100 Subject: [PATCH 389/484] Revamp nomination pool reward scheme (#11669) * make pool roles optional * undo lock file changes? * add migration * add the ability for pools to chill themselves * boilerplate of tests * somewhat stable, but I think I found another bug as well * Fix it all * Add more more sophisticated test + capture one more bug. * Update frame/staking/src/lib.rs * reduce the diff a little bit * add some test for the slashing bug * cleanup * fix lock file? * Fix * fmt * Update frame/nomination-pools/src/lib.rs Co-authored-by: Oliver Tale-Yazdi * Update frame/nomination-pools/src/lib.rs Co-authored-by: Oliver Tale-Yazdi * Update frame/nomination-pools/src/lib.rs Co-authored-by: Oliver Tale-Yazdi * Update frame/nomination-pools/src/mock.rs Co-authored-by: Oliver Tale-Yazdi * Fix build * fix some fishy tests.. * add one last integrity check for MinCreateBond * remove bad assertion -- needs to be dealt with later * nits * fix tests and add benchmarks for chill * remove stuff * fix benchmarks * cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_nomination_pools --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/nomination-pools/src/weights.rs --template=./.maintain/frame-weight-template.hbs * remove defensive * first working version * bring back all tests * ALL new tests work now * cleanup * make sure benchmarks and all work * cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_nomination_pools --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/nomination-pools/src/weights.rs --template=./.maintain/frame-weight-template.hbs * round of self-review, make arithmetic safe * fix warn * add migration code * Fix doc * add precision notes * make arithmetic fallible * fix node runtime * a lot of precision tests and notes and stuff * document MaxPOintsToBalance better * :round of self-review * fmt * fix some comments * Fix proportional slashing logic * Update frame/nomination-pools/src/tests.rs Co-authored-by: Oliver Tale-Yazdi * Update frame/nomination-pools/src/tests.rs Co-authored-by: Oliver Tale-Yazdi * Update frame/nomination-pools/src/lib.rs Co-authored-by: Oliver Tale-Yazdi * track poinst in migration * fix * fmt * fix migration * remove event read * Apply suggestions from code review * Update frame/staking/src/lib.rs Co-authored-by: Shawn Tabrizi * Update frame/nomination-pools/src/lib.rs Co-authored-by: Shawn Tabrizi * Update frame/nomination-pools/src/lib.rs Co-authored-by: Shawn Tabrizi * update * fmt * fmt * add one last test * fmt Co-authored-by: Oliver Tale-Yazdi Co-authored-by: Shawn Tabrizi Co-authored-by: Parity Bot --- bin/node/runtime/src/lib.rs | 9 +- .../nomination-pools/benchmarking/src/mock.rs | 11 +- frame/nomination-pools/src/lib.rs | 582 +++--- frame/nomination-pools/src/migration.rs | 284 ++- frame/nomination-pools/src/mock.rs | 52 +- frame/nomination-pools/src/tests.rs | 1805 ++++++++++++----- frame/nomination-pools/src/weights.rs | 98 +- .../nomination-pools/test-staking/src/lib.rs | 5 +- .../nomination-pools/test-staking/src/mock.rs | 17 +- utils/frame/try-runtime/cli/src/lib.rs | 9 +- 10 files changed, 1950 insertions(+), 922 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 2d5981339fab3..b2efcb196787d 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -68,7 +68,7 @@ use sp_runtime::{ SaturatedConversion, StaticLookup, }, transaction_validity::{TransactionPriority, TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, FixedPointNumber, Perbill, Percent, Permill, Perquintill, + ApplyExtrinsicResult, FixedPointNumber, FixedU128, Perbill, Percent, Permill, Perquintill, }; use sp_std::prelude::*; #[cfg(any(feature = "std", test))] @@ -730,7 +730,7 @@ impl pallet_bags_list::Config for Runtime { parameter_types! { pub const PostUnbondPoolsWindow: u32 = 4; pub const NominationPoolsPalletId: PalletId = PalletId(*b"py/nopls"); - pub const MinPointsToBalance: u32 = 10; + pub const MaxPointsToBalance: u8 = 10; } use sp_runtime::traits::Convert; @@ -751,6 +751,8 @@ impl pallet_nomination_pools::Config for Runtime { type WeightInfo = (); type Event = Event; type Currency = Balances; + type CurrencyBalance = Balance; + type RewardCounter = FixedU128; type BalanceToU256 = BalanceToU256; type U256ToBalance = U256ToBalance; type StakingInterface = pallet_staking::Pallet; @@ -758,7 +760,7 @@ impl pallet_nomination_pools::Config for Runtime { type MaxMetadataLen = ConstU32<256>; type MaxUnbonding = ConstU32<8>; type PalletId = NominationPoolsPalletId; - type MinPointsToBalance = MinPointsToBalance; + type MaxPointsToBalance = MaxPointsToBalance; } parameter_types! { @@ -1675,6 +1677,7 @@ pub type Executive = frame_executive::Executive< frame_system::ChainContext, Runtime, AllPalletsWithSystem, + pallet_nomination_pools::migration::v2::MigrateToV2, >; /// MMR helper types. diff --git a/frame/nomination-pools/benchmarking/src/mock.rs b/frame/nomination-pools/benchmarking/src/mock.rs index eb884869f6d32..1c74c301e562d 100644 --- a/frame/nomination-pools/benchmarking/src/mock.rs +++ b/frame/nomination-pools/benchmarking/src/mock.rs @@ -17,7 +17,10 @@ use frame_election_provider_support::VoteWeight; use frame_support::{pallet_prelude::*, parameter_types, traits::ConstU64, PalletId}; -use sp_runtime::traits::{Convert, IdentityLookup}; +use sp_runtime::{ + traits::{Convert, IdentityLookup}, + FixedU128, +}; type AccountId = u128; type AccountIndex = u32; @@ -144,13 +147,15 @@ impl Convert for U256ToBalance { parameter_types! { pub static PostUnbondingPoolsWindow: u32 = 10; pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls"); - pub const MinPointsToBalance: u32 = 10; + pub const MaxPointsToBalance: u8 = 10; } impl pallet_nomination_pools::Config for Runtime { type Event = Event; type WeightInfo = (); type Currency = Balances; + type CurrencyBalance = Balance; + type RewardCounter = FixedU128; type BalanceToU256 = BalanceToU256; type U256ToBalance = U256ToBalance; type StakingInterface = Staking; @@ -158,7 +163,7 @@ impl pallet_nomination_pools::Config for Runtime { type MaxMetadataLen = ConstU32<256>; type MaxUnbonding = ConstU32<8>; type PalletId = PoolsPalletId; - type MinPointsToBalance = MinPointsToBalance; + type MaxPointsToBalance = MaxPointsToBalance; } impl crate::Config for Runtime {} diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index f7ccac3e35411..53e1c48a5e39e 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -17,8 +17,8 @@ //! # Nomination Pools for Staking Delegation //! -//! A pallet that allows members to delegate their stake to nominating pools. A nomination pool -//! acts as nominator and nominates validators on the members behalf. +//! A pallet that allows members to delegate their stake to nominating pools. A nomination pool acts +//! as nominator and nominates validators on the members behalf. //! //! # Index //! @@ -28,16 +28,27 @@ //! //! ## Key terms //! +//! * pool id: A unique identifier of each pool. Set to u12 //! * bonded pool: Tracks the distribution of actively staked funds. See [`BondedPool`] and -//! [`BondedPoolInner`]. Bonded pools are identified via the pools bonded account. +//! [`BondedPoolInner`]. //! * reward pool: Tracks rewards earned by actively staked funds. See [`RewardPool`] and -//! [`RewardPools`]. Reward pools are identified via the pools bonded account. +//! [`RewardPools`]. //! * unbonding sub pools: Collection of pools at different phases of the unbonding lifecycle. See -//! [`SubPools`] and [`SubPoolsStorage`]. Sub pools are identified via the pools bonded account. -//! * members: Accounts that are members of pools. See [`PoolMember`] and [`PoolMembers`]. Pool -//! members are identified via their account. -//! * point: A unit of measure for a members portion of a pool's funds. +//! [`SubPools`] and [`SubPoolsStorage`]. +//! * members: Accounts that are members of pools. See [`PoolMember`] and [`PoolMembers`]. +//! * roles: Administrative roles of each pool, capable of controlling nomination, and the state of +//! the pool. +//! * point: A unit of measure for a members portion of a pool's funds. Points initially have a +//! ratio of 1 (as set by `POINTS_TO_BALANCE_INIT_RATIO`) to balance, but as slashing happens, +//! this can change. //! * kick: The act of a pool administrator forcibly ejecting a member. +//! * bonded account: A key-less account id derived from the pool id that acts as the bonded +//! account. This account registers itself as a nominator in the staking system, and follows +//! exactly the same rules and conditions as a normal staker. Its bond increases or decreases as +//! members join, it can `nominate` or `chill`, and might not even earn staking rewards if it is +//! not nominating proper validators. +//! * reward account: A similar key-less account, that is set as the `Payee` account fo the bonded +//! account for all staking rewards. //! //! ## Usage //! @@ -55,11 +66,13 @@ //! //! In order to leave, a member must take two steps. //! -//! First, they must call [`Call::unbond`]. The unbond other extrinsic will start the -//! unbonding process by unbonding all of the members funds. +//! First, they must call [`Call::unbond`]. The unbond extrinsic will start the unbonding process by +//! unbonding all or a portion of the members funds. //! -//! Second, once [`sp_staking::StakingInterface::bonding_duration`] eras have passed, the member -//! can call [`Call::withdraw_unbonded`] to withdraw all their funds. +//! > A member can have up to [`Config::MaxUnbonding`] distinct active unbonding requests. +//! +//! Second, once [`sp_staking::StakingInterface::bonding_duration`] eras have passed, the member can +//! call [`Call::withdraw_unbonded`] to withdraw any funds that are free. //! //! For design docs see the [bonded pool](#bonded-pool) and [unbonding sub //! pools](#unbonding-sub-pools) sections. @@ -67,9 +80,13 @@ //! ### Slashes //! //! Slashes are distributed evenly across the bonded pool and the unbonding pools from slash era+1 -//! through the slash apply era. Thus, any member who either a) unbonded or b) was actively -//! bonded in the aforementioned range of eras will be affected by the slash. A member is slashed -//! pro-rata based on its stake relative to the total slash amount. +//! through the slash apply era. Thus, any member who either +//! +//! 1. unbonded, or +//! 2. was actively bonded +// +//! in the aforementioned range of eras will be affected by the slash. A member is slashed pro-rata +//! based on its stake relative to the total slash amount. //! //! For design docs see the [slashing](#slashing) section. //! @@ -82,7 +99,9 @@ //! To help facilitate pool administration the pool has one of three states (see [`PoolState`]): //! //! * Open: Anyone can join the pool and no members can be permissionlessly removed. -//! * Blocked: No members can join and some admin roles can kick members. +//! * Blocked: No members can join and some admin roles can kick members. Kicking is not instant, +//! and follows the same process of `unbond` and then `withdraw_unbonded`. In other words, +//! administrators can permissionlessly unbond other members. //! * Destroying: No members can join and all members can be permissionlessly removed with //! [`Call::unbond`] and [`Call::withdraw_unbonded`]. Once a pool is in destroying state, it //! cannot be reverted to another state. @@ -90,12 +109,23 @@ //! A pool has 4 administrative roles (see [`PoolRoles`]): //! //! * Depositor: creates the pool and is the initial member. They can only leave the pool once all -//! other members have left. Once they fully leave the pool is destroyed. +//! other members have left. Once they fully withdraw their funds, the pool is destroyed. //! * Nominator: can select which validators the pool nominates. //! * State-Toggler: can change the pools state and kick members if the pool is blocked. //! * Root: can change the nominator, state-toggler, or itself and can perform any of the actions //! the nominator or state-toggler can. //! +//! ### Dismantling +//! +//! As noted, a pool is destroyed once +//! +//! 1. First, all members need to fully unbond and withdraw. If the pool state is set to +//! `Destroying`, this can happen permissionlessly. +//! 2. The depositor itself fully unbonds and withdraws. Note that at this point, based on the +//! requirements of the staking system, the pool's bonded account's stake might not be able to ge +//! below a certain threshold as a nominator. At this point, the pool should `chill` itself to +//! allow the depositor to leave. +//! //! ## Design //! //! _Notes_: this section uses pseudo code to explain general design and does not necessarily @@ -108,8 +138,8 @@ //! members that where in the pool while it was backing a validator that got slashed. //! * Maximize scalability in terms of member count. //! -//! In order to maintain scalability, all operations are independent of the number of members. To -//! do this, delegation specific information is stored local to the member while the pool data +//! In order to maintain scalability, all operations are independent of the number of members. To do +//! this, delegation specific information is stored local to the member while the pool data //! structures have bounded datum. //! //! ### Bonded pool @@ -118,9 +148,9 @@ //! unbonding. The total points of a bonded pool are always equal to the sum of points of the //! delegation members. A bonded pool tracks its points and reads its bonded balance. //! -//! When a member joins a pool, `amount_transferred` is transferred from the members account -//! to the bonded pools account. Then the pool calls `staking::bond_extra(amount_transferred)` and -//! issues new points which are tracked by the member and added to the bonded pool's points. +//! When a member joins a pool, `amount_transferred` is transferred from the members account to the +//! bonded pools account. Then the pool calls `staking::bond_extra(amount_transferred)` and issues +//! new points which are tracked by the member and added to the bonded pool's points. //! //! When the pool already has some balance, we want the value of a point before the transfer to //! equal the value of a point after the transfer. So, when a member joins a bonded pool with a @@ -148,77 +178,13 @@ //! ### Reward pool //! //! When a pool is first bonded it sets up an deterministic, inaccessible account as its reward -//! destination. To track staking rewards we track how the balance of this reward account changes. -//! -//! The reward pool needs to store: -//! -//! * The pool balance at the time of the last payout: `reward_pool.balance` -//! * The total earnings ever at the time of the last payout: `reward_pool.total_earnings` -//! * The total points in the pool at the time of the last payout: `reward_pool.points` -//! -//! And the member needs to store: -//! -//! * The total payouts at the time of the last payout by that member: -//! `member.reward_pool_total_earnings` -//! -//! Before the first reward claim is initiated for a pool, all the above variables are set to zero. -//! -//! When a member initiates a claim, the following happens: -//! -//! 1) Compute the reward pool's total points and the member's virtual points in the reward pool -//! * First `current_total_earnings` is computed (`current_balance` is the free balance of the -//! reward pool at the beginning of these operations.) -//! ```text -//! current_total_earnings = -//! current_balance - reward_pool.balance + pool.total_earnings; -//! ``` -//! * Then the `current_points` is computed. Every balance unit that was added to the reward -//! pool since last time recorded means that the `pool.points` is increased by -//! `bonding_pool.total_points`. In other words, for every unit of balance that has been -//! earned by the reward pool, the reward pool points are inflated by `bonded_pool.points`. In -//! effect this allows each, single unit of balance (e.g. planck) to be divvied up pro-rata -//! among members based on points. -//! ```text -//! new_earnings = current_total_earnings - reward_pool.total_earnings; -//! current_points = reward_pool.points + bonding_pool.points * new_earnings; -//! ``` -//! * Finally, the`member_virtual_points` are computed: the product of the member's points in -//! the bonding pool and the total inflow of balance units since the last time the member -//! claimed rewards -//! ```text -//! new_earnings_since_last_claim = current_total_earnings - member.reward_pool_total_earnings; -//! member_virtual_points = member.points * new_earnings_since_last_claim; -//! ``` -//! 2) Compute the `member_payout`: -//! ```text -//! member_pool_point_ratio = member_virtual_points / current_points; -//! member_payout = current_balance * member_pool_point_ratio; -//! ``` -//! 3) Transfer `member_payout` to the member -//! 4) For the member set: -//! ```text -//! member.reward_pool_total_earnings = current_total_earnings; -//! ``` -//! 5) For the pool set: -//! ```text -//! reward_pool.points = current_points - member_virtual_points; -//! reward_pool.balance = current_balance - member_payout; -//! reward_pool.total_earnings = current_total_earnings; -//! ``` -//! -//! _Note_: One short coming of this design is that new joiners can claim rewards for the era after -//! they join even though their funds did not contribute to the pools vote weight. When a -//! member joins, it's `reward_pool_total_earnings` field is set equal to the `total_earnings` -//! of the reward pool at that point in time. At best the reward pool has the rewards up through the -//! previous era. If a member joins prior to the election snapshot it will benefit from the -//! rewards for the active era despite not contributing to the pool's vote weight. If it joins -//! after the election snapshot is taken it will benefit from the rewards of the next _2_ eras -//! because it's vote weight will not be counted until the election snapshot in active era + 1. -//! Related: -// _Note to maintainers_: In order to ensure the reward account never falls below the existential -// deposit, at creation the reward account must be endowed with the existential deposit. All logic -// for calculating rewards then does not see that existential deposit as part of the free balance. -// See `RewardPool::current_balance`. +//! destination. +//! +//! The reward pool is not really a pool anymore, as it does not track points anymore. Instead, it +//! tracks, a virtual value called `reward_counter`, among a few other values. +//! +//! See [this link](https://hackmd.io/PFGn6wI5TbCmBYoEA_f2Uw) for an in-depth explanation of the +//! reward pool mechanism. //! //! **Relevant extrinsics:** //! @@ -297,13 +263,8 @@ //! * PoolMembers cannot vote with their staked funds because they are transferred into the pools //! account. In the future this can be overcome by allowing the members to vote with their bonded //! funds via vote splitting. -//! * PoolMembers cannot quickly transfer to another pool if they do not like the nominations, -//! instead they must wait for the unbonding duration. -//! -//! # Runtime builder warnings -//! -//! * Watch out for overflow of [`RewardPoints`] and [`BalanceOf`] types. Consider things like the -//! chains total issuance, staking reward rate, and burn rate. +//! * PoolMembers cannot quickly transfer to another pool if they do no like nominations, instead +//! they must wait for the unbonding duration. #![cfg_attr(not(feature = "std"), no_std)] @@ -320,7 +281,10 @@ use frame_support::{ }; use scale_info::TypeInfo; use sp_core::U256; -use sp_runtime::traits::{AccountIdConversion, Bounded, CheckedSub, Convert, Saturating, Zero}; +use sp_runtime::{ + traits::{AccountIdConversion, Bounded, CheckedAdd, CheckedSub, Convert, Saturating, Zero}, + FixedPointNumber, FixedPointOperand, +}; use sp_staking::{EraIndex, OnStakerSlash, StakingInterface}; use sp_std::{collections::btree_map::BTreeMap, fmt::Debug, ops::Div, vec::Vec}; @@ -352,8 +316,6 @@ pub use weights::WeightInfo; /// The balance type used by the currency system. pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; -/// Type used to track the points of a reward pool. -pub type RewardPoints = U256; /// Type used for unique identifier of each pool. pub type PoolId = u32; @@ -407,26 +369,50 @@ pub struct PoolMember { /// The quantity of points this member has in the bonded pool or in a sub pool if /// `Self::unbonding_era` is some. pub points: BalanceOf, - /// The reward pools total earnings _ever_ the last time this member claimed a payout. - /// Assuming no massive burning events, we expect this value to always be below total issuance. - /// This value lines up with the [`RewardPool::total_earnings`] after a member claims a - /// payout. - pub reward_pool_total_earnings: BalanceOf, + /// The reward counter at the time of this member's last payout claim. + pub last_recorded_reward_counter: T::RewardCounter, /// The eras in which this member is unbonding, mapped from era index to the number of /// points scheduled to unbond in the given era. pub unbonding_eras: BoundedBTreeMap, T::MaxUnbonding>, } impl PoolMember { - fn total_points(&self) -> BalanceOf { - self.active_points().saturating_add(self.unbonding_points()) + /// The pending rewards of this member. + fn pending_rewards( + &self, + current_reward_counter: T::RewardCounter, + ) -> Result, Error> { + // accuracy note: Reward counters are `FixedU128` with base of 10^18. This value is being + // multiplied by a point. The worse case of a point is 10x the granularity of the balance + // (10x is the common configuration of `MaxPointsToBalance`). + // + // Assuming roughly the current issuance of polkadot (12,047,781,394,999,601,455, which is + // 1.2 * 10^9 * 10^10 = 1.2 * 10^19), the worse case point value is around 10^20. + // + // The final multiplication is: + // + // rc * 10^20 / 10^18 = rc * 100 + // + // meaning that as long as reward_counter's value is less than 1/100th of its max capacity + // (u128::MAX_VALUE), `checked_mul_int` won't saturate. + // + // given the nature of reward counter being 'pending_rewards / pool_total_point', the only + // (unrealistic) way that super high values can be achieved is for a pool to suddenly + // receive massive rewards with a very very small amount of stake. In all normal pools, as + // the points increase, so does the rewards. Moreover, as long as rewards are not + // accumulated for astronomically large durations, + // `current_reward_counter.defensive_saturating_sub(self.last_recorded_reward_counter)` + // won't be extremely big. + (current_reward_counter.defensive_saturating_sub(self.last_recorded_reward_counter)) + .checked_mul_int(self.active_points()) + .ok_or(Error::::OverflowRisk) } /// Active balance of the member. /// /// This is derived from the ratio of points in the pool to which the member belongs to. /// Might return different values based on the pool state for the same member and points. - pub(crate) fn active_balance(&self) -> BalanceOf { + fn active_balance(&self) -> BalanceOf { if let Some(pool) = BondedPool::::get(self.pool_id).defensive() { pool.points_to_balance(self.points) } else { @@ -434,13 +420,18 @@ impl PoolMember { } } + /// Total points of this member, both active and unbonding. + fn total_points(&self) -> BalanceOf { + self.active_points().saturating_add(self.unbonding_points()) + } + /// Active points of the member. - pub(crate) fn active_points(&self) -> BalanceOf { + fn active_points(&self) -> BalanceOf { self.points } /// Inactive points of the member, waiting to be withdrawn. - pub(crate) fn unbonding_points(&self) -> BalanceOf { + fn unbonding_points(&self) -> BalanceOf { self.unbonding_eras .as_ref() .iter() @@ -673,7 +664,7 @@ impl BondedPool { MaxPoolMembers::::get().map_or(true, |max| PoolMembers::::count() < max), Error::::MaxPoolMembers ); - self.member_counter = self.member_counter.defensive_saturating_add(1); + self.member_counter = self.member_counter.checked_add(1).ok_or(Error::::OverflowRisk)?; Ok(()) } @@ -750,19 +741,19 @@ impl BondedPool { // We checked for zero above .div(bonded_balance); - let min_points_to_balance = T::MinPointsToBalance::get(); + let max_points_to_balance = T::MaxPointsToBalance::get(); // Pool points can inflate relative to balance, but only if the pool is slashed. // If we cap the ratio of points:balance so one cannot join a pool that has been slashed - // by `min_points_to_balance`%, if not zero. + // by `max_points_to_balance`%, if not zero. ensure!( - points_to_balance_ratio_floor < min_points_to_balance.into(), + points_to_balance_ratio_floor < max_points_to_balance.into(), Error::::OverflowRisk ); - // while restricting the balance to `min_points_to_balance` of max total issuance, + // while restricting the balance to `max_points_to_balance` of max total issuance, let next_bonded_balance = bonded_balance.saturating_add(new_funds); ensure!( - next_bonded_balance < BalanceOf::::max_value().div(min_points_to_balance.into()), + next_bonded_balance < BalanceOf::::max_value().div(max_points_to_balance.into()), Error::::OverflowRisk ); @@ -904,16 +895,6 @@ impl BondedPool { Ok(points_issued) } - /// If `n` saturates at it's upper bound, mark the pool as destroying. This is useful when a - /// number saturating indicates the pool can no longer correctly keep track of state. - fn bound_check(&mut self, n: U256) -> U256 { - if n == U256::max_value() { - self.set_state(PoolState::Destroying) - } - - n - } - // Set the state of `self`, and deposit an event if the state changed. State should never be set // directly in in order to ensure a state change event is always correctly deposited. fn set_state(&mut self, state: PoolState) { @@ -928,45 +909,104 @@ impl BondedPool { } /// A reward pool. +/// +/// A reward pool is not so much a pool anymore, since it does not contain any shares or points. +/// Rather, simply to fit nicely next to bonded pool and unbonding pools in terms of terminology. In +/// reality, a reward pool is just a container for a few pool-dependent data related to the rewards. #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound)] -#[cfg_attr(feature = "std", derive(Clone, PartialEq))] +#[cfg_attr(feature = "std", derive(Clone, PartialEq, DefaultNoBound))] #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] pub struct RewardPool { - /// The balance of this reward pool after the last claimed payout. - pub balance: BalanceOf, - /// The total earnings _ever_ of this reward pool after the last claimed payout. I.E. the sum - /// of all incoming balance through the pools life. + /// The last recorded value of the reward counter. /// - /// NOTE: We assume this will always be less than total issuance and thus can use the runtimes - /// `Balance` type. However in a chain with a burn rate higher than the rate this increases, - /// this type should be bigger than `Balance`. - pub total_earnings: BalanceOf, - /// The total points of this reward pool after the last claimed payout. - pub points: RewardPoints, + /// This is updated ONLY when the points in the bonded pool change, which means `join`, + /// `bond_extra` and `unbond`, all of which is done through `update_recorded`. + last_recorded_reward_counter: T::RewardCounter, + /// The last recorded total payouts of the reward pool. + /// + /// Payouts is essentially income of the pool. + /// + /// Update criteria is same as that of `last_recorded_reward_counter`. + last_recorded_total_payouts: BalanceOf, + /// Total amount that this pool has paid out so far to the members. + total_rewards_claimed: BalanceOf, } impl RewardPool { - /// Mutate the reward pool by updating the total earnings and current free balance. - fn update_total_earnings_and_balance(&mut self, id: PoolId) { - let current_balance = Self::current_balance(id); - // The earnings since the last time it was updated - let new_earnings = current_balance.saturating_sub(self.balance); - // The lifetime earnings of the of the reward pool - self.total_earnings = new_earnings.saturating_add(self.total_earnings); - self.balance = current_balance; - } - - /// Get a reward pool and update its total earnings and balance - fn get_and_update(id: PoolId) -> Option { - RewardPools::::get(id).map(|mut r| { - r.update_total_earnings_and_balance(id); - r - }) - } - - /// The current balance of the reward pool. Never access the reward pools free balance directly. - /// The existential deposit was not received as a reward, so the reward pool can not use it. + /// Getter for [`RewardPool::last_recorded_reward_counter`]. + fn last_recorded_reward_counter(&self) -> T::RewardCounter { + self.last_recorded_reward_counter + } + + /// Register some rewards that are claimed from the pool by the members. + fn register_claimed_reward(&mut self, reward: BalanceOf) { + self.total_rewards_claimed = self.total_rewards_claimed.saturating_add(reward); + } + + /// Update the recorded values of the pool. + fn update_records(&mut self, id: PoolId, bonded_points: BalanceOf) -> Result<(), Error> { + let balance = Self::current_balance(id); + self.last_recorded_reward_counter = self.current_reward_counter(id, bonded_points)?; + self.last_recorded_total_payouts = balance + .checked_add(&self.total_rewards_claimed) + .ok_or(Error::::OverflowRisk)?; + Ok(()) + } + + /// Get the current reward counter, based on the given `bonded_points` being the state of the + /// bonded pool at this time. + fn current_reward_counter( + &self, + id: PoolId, + bonded_points: BalanceOf, + ) -> Result> { + let balance = Self::current_balance(id); + let payouts_since_last_record = balance + .saturating_add(self.total_rewards_claimed) + .saturating_sub(self.last_recorded_total_payouts); + + // * accuracy notes regarding the multiplication in `checked_from_rational`: + // `payouts_since_last_record` is a subset of the total_issuance at the very + // worse. `bonded_points` are similarly, in a non-slashed pool, have the same granularity as + // balance, and are thus below within the range of total_issuance. In the worse case + // scenario, for `saturating_from_rational`, we have: + // + // dot_total_issuance * 10^18 / `minJoinBond` + // + // assuming `MinJoinBond == ED` + // + // dot_total_issuance * 10^18 / 10^10 = dot_total_issuance * 10^8 + // + // which, with the current numbers, is a miniscule fraction of the u128 capacity. + // + // Thus, adding two values of type reward counter should be safe for ages in a chain like + // Polkadot. The important note here is that `reward_pool.last_recorded_reward_counter` only + // ever accumulates, but its semantics imply that it is less than total_issuance, when + // represented as `FixedU128`, which means it is less than `total_issuance * 10^18`. + // + // * accuracy notes regarding `checked_from_rational` collapsing to zero, meaning that no + // reward can be claimed: + // + // largest `bonded_points`, such that the reward counter is non-zero, with `FixedU128` + // will be when the payout is being computed. This essentially means `payout/bonded_points` + // needs to be more than 1/1^18. Thus, assuming that `bonded_points` will always be less + // than `10 * dot_total_issuance`, if the reward_counter is the smallest possible value, + // the value of the reward being calculated is: + // + // x / 10^20 = 1/ 10^18 + // + // x = 100 + // + // which is basically 10^-8 DOTs. See `smallest_claimable_reward` for an example of this. + T::RewardCounter::checked_from_rational(payouts_since_last_record, bonded_points) + .and_then(|ref r| self.last_recorded_reward_counter.checked_add(r)) + .ok_or(Error::::OverflowRisk) + } + + /// Current free balance of the reward pool. + /// + /// This is sum of all the rewards that are claimable by pool members. fn current_balance(id: PoolId) -> BalanceOf { T::Currency::free_balance(&Pallet::::create_reward_account(id)) .saturating_sub(T::Currency::minimum_balance()) @@ -1098,9 +1138,10 @@ pub mod pallet { use super::*; use frame_support::traits::StorageVersion; use frame_system::{ensure_signed, pallet_prelude::*}; + use sp_runtime::traits::CheckedAdd; /// The current storage version. - const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + const STORAGE_VERSION: StorageVersion = StorageVersion::new(2); #[pallet::pallet] #[pallet::generate_store(pub(crate) trait Store)] @@ -1116,19 +1157,53 @@ pub mod pallet { type WeightInfo: weights::WeightInfo; /// The nominating balance. - type Currency: Currency; + type Currency: Currency; + + /// Sadly needed to bound it to `FixedPointOperand`. + // The only alternative is to sprinkle a `where BalanceOf: FixedPointOperand` in roughly + // a million places, so we prefer doing this. + type CurrencyBalance: sp_runtime::traits::AtLeast32BitUnsigned + + codec::FullCodec + + MaybeSerializeDeserialize + + sp_std::fmt::Debug + + Default + + FixedPointOperand + + CheckedAdd + + TypeInfo + + MaxEncodedLen; + + /// The type that is used for reward counter. + /// + /// The arithmetic of the reward counter might saturate based on the size of the + /// `Currency::Balance`. If this happens, operations fails. Nonetheless, this type should be + /// chosen such that this failure almost never happens, as if it happens, the pool basically + /// needs to be dismantled (or all pools migrated to a larger `RewardCounter` type, which is + /// a PITA to do). + /// + /// See the inline code docs of `Member::pending_rewards` and `RewardPool::update_recorded` + /// for example analysis. A [`sp_runtime::FixedU128`] should be fine for chains with balance + /// types similar to that of Polkadot and Kusama, in the absence of severe slashing (or + /// prevented via a reasonable `MaxPointsToBalance`), for many many years to come. + type RewardCounter: FixedPointNumber + MaxEncodedLen + TypeInfo + Default + codec::FullCodec; /// The nomination pool's pallet id. #[pallet::constant] type PalletId: Get; - /// The minimum pool points-to-balance ratio that must be maintained for it to be `open`. + /// The maximum pool points-to-balance ratio that an `open` pool can have. + /// /// This is important in the event slashing takes place and the pool's points-to-balance /// ratio becomes disproportional. + /// + /// Moreover, this relates to the `RewardCounter` type as well, as the arithmetic operations + /// are a function of number of points, and by setting this value to e.g. 10, you ensure + /// that the total number of points in the system are at most 10 times the total_issuance of + /// the chain, in the absolute worse case. + /// /// For a value of 10, the threshold would be a pool points-to-balance ratio of 10:1. /// Such a scenario would also be the equivalent of the pool being 90% slashed. #[pallet::constant] - type MinPointsToBalance: Get; + type MaxPointsToBalance: Get; /// Infallible method for converting `Currency::Balance` to `U256`. type BalanceToU256: Convert, U256>; @@ -1427,10 +1502,10 @@ pub mod pallet { let mut bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; bonded_pool.ok_to_join(amount)?; - // We just need its total earnings at this point in time, but we don't need to write it - // because we are not adjusting its points (all other values can calculated virtual). - let reward_pool = RewardPool::::get_and_update(pool_id) + let mut reward_pool = RewardPools::::get(pool_id) .defensive_ok_or::>(DefensiveError::RewardPoolNotFound.into())?; + // IMPORTANT: reward pool records must be updated with the old points. + reward_pool.update_records(pool_id, bonded_pool.points)?; bonded_pool.try_inc_members()?; let points_issued = bonded_pool.try_bond_funds(&who, amount, BondType::Later)?; @@ -1440,13 +1515,9 @@ pub mod pallet { PoolMember:: { pool_id, points: points_issued, - // At best the reward pool has the rewards up through the previous era. If the - // member joins prior to the snapshot they will benefit from the rewards of - // the active era despite not contributing to the pool's vote weight. If they - // join after the snapshot is taken they will benefit from the rewards of the - // next 2 eras because their vote weight will not be counted until the - // snapshot in active era + 1. - reward_pool_total_earnings: reward_pool.total_earnings, + // we just updated `last_known_reward_counter` to the current one in + // `update_recorded`. + last_recorded_reward_counter: reward_pool.last_recorded_reward_counter(), unbonding_eras: Default::default(), }, ); @@ -1457,7 +1528,9 @@ pub mod pallet { bonded: amount, joined: true, }); + bonded_pool.put(); + RewardPools::::insert(pool_id, reward_pool); Ok(()) } @@ -1466,6 +1539,8 @@ pub mod pallet { /// /// Additional funds can come from either the free balance of the account, of from the /// accumulated rewards, see [`BondExtra`]. + /// + /// Bonding extra funds implies an automatic payout of all pending rewards as well. // NOTE: this transaction is implemented with the sole purpose of readability and // correctness, not optimization. We read/write several storage items multiple times instead // of just once, in the spirit reusing code. @@ -1478,19 +1553,23 @@ pub mod pallet { let who = ensure_signed(origin)?; let (mut member, mut bonded_pool, mut reward_pool) = Self::get_member_with_pools(&who)?; + // payout related stuff: we must claim the payouts, and updated recorded payout data + // before updating the bonded pool points, similar to that of `join` transaction. + reward_pool.update_records(bonded_pool.id, bonded_pool.points)?; + // TODO: optimize this to not touch the free balance of `who ` at all in benchmarks. + // Currently, bonding rewards is like a batch. In the same PR, also make this function + // take a boolean argument that make it either 100% pure (no storage update), or make it + // also emit event and do the transfer. #11671 + let claimed = + Self::do_reward_payout(&who, &mut member, &mut bonded_pool, &mut reward_pool)?; + let (points_issued, bonded) = match extra { BondExtra::FreeBalance(amount) => (bonded_pool.try_bond_funds(&who, amount, BondType::Later)?, amount), - BondExtra::Rewards => { - let claimed = Self::do_reward_payout( - &who, - &mut member, - &mut bonded_pool, - &mut reward_pool, - )?; - (bonded_pool.try_bond_funds(&who, claimed, BondType::Later)?, claimed) - }, + BondExtra::Rewards => + (bonded_pool.try_bond_funds(&who, claimed, BondType::Later)?, claimed), }; + bonded_pool.ok_to_be_open(bonded)?; member.points = member.points.saturating_add(points_issued); @@ -1558,21 +1637,17 @@ pub mod pallet { member_account: T::AccountId, #[pallet::compact] unbonding_points: BalanceOf, ) -> DispatchResult { - let caller = ensure_signed(origin)?; + let who = ensure_signed(origin)?; let (mut member, mut bonded_pool, mut reward_pool) = Self::get_member_with_pools(&member_account)?; - bonded_pool.ok_to_unbond_with(&caller, &member_account, &member, unbonding_points)?; + bonded_pool.ok_to_unbond_with(&who, &member_account, &member, unbonding_points)?; // Claim the the payout prior to unbonding. Once the user is unbonding their points no // longer exist in the bonded pool and thus they can no longer claim their payouts. It // is not strictly necessary to claim the rewards, but we do it here for UX. - Self::do_reward_payout( - &member_account, - &mut member, - &mut bonded_pool, - &mut reward_pool, - )?; + let _ = reward_pool.update_records(bonded_pool.id, bonded_pool.points)?; + let _ = Self::do_reward_payout(&who, &mut member, &mut bonded_pool, &mut reward_pool)?; let current_era = T::StakingInterface::current_era(); let unbond_era = T::StakingInterface::bonding_duration().saturating_add(current_era); @@ -1846,23 +1921,25 @@ pub mod pallet { PoolMember:: { pool_id, points, - reward_pool_total_earnings: Zero::zero(), + last_recorded_reward_counter: Zero::zero(), unbonding_eras: Default::default(), }, ); RewardPools::::insert( pool_id, RewardPool:: { - balance: Zero::zero(), - points: U256::zero(), - total_earnings: Zero::zero(), + last_recorded_reward_counter: Zero::zero(), + last_recorded_total_payouts: Zero::zero(), + total_rewards_claimed: Zero::zero(), }, ); ReversePoolIdLookup::::insert(bonded_pool.bonded_account(), pool_id); + Self::deposit_event(Event::::Created { depositor: who.clone(), pool_id: pool_id.clone(), }); + Self::deposit_event(Event::::Bonded { member: who, pool_id, @@ -1896,8 +1973,14 @@ pub mod pallet { /// Set a new state for the pool. /// - /// The dispatch origin of this call must be signed by the state toggler, or the root role - /// of the pool. + /// If a pool is already in the `Destroying` state, then under no condition can its state + /// change again. + /// + /// The dispatch origin of this call must be either: + /// + /// 1. signed by the state toggler, or the root role of the pool, + /// 2. if the pool conditions to be open are NOT met (as described by `ok_to_be_open`), and + /// then the state of the pool can be permissionlessly changed to `Destroying`. #[pallet::weight(T::WeightInfo::set_state())] #[transactional] pub fn set_state( @@ -2065,14 +2148,9 @@ pub mod pallet { impl Hooks> for Pallet { fn integrity_test() { assert!( - T::MinPointsToBalance::get() > 0, + T::MaxPointsToBalance::get() > 0, "Minimum points to balance ratio must be greater than 0" ); - assert!( - sp_std::mem::size_of::() >= - 2 * sp_std::mem::size_of::>(), - "bit-length of the reward points must be at least twice as much as balance" - ); assert!( T::StakingInterface::bonding_duration() < TotalUnbondingPools::::get(), "There must be more unbonding pools then the bonding duration / @@ -2119,7 +2197,7 @@ impl Pallet { ExistenceRequirement::AllowDeath, ); - // TODO: this is purely defensive. + // NOTE: this is purely defensive. T::Currency::make_free_balance_be(&reward_account, Zero::zero()); T::Currency::make_free_balance_be(&bonded_pool.bonded_account(), Zero::zero()); @@ -2212,75 +2290,6 @@ impl Pallet { .div(current_points) } - /// Calculate the rewards for `member`. - /// - /// Returns the payout amount. - fn calculate_member_payout( - member: &mut PoolMember, - bonded_pool: &mut BondedPool, - reward_pool: &mut RewardPool, - ) -> Result, DispatchError> { - let u256 = |x| T::BalanceToU256::convert(x); - let balance = |x| T::U256ToBalance::convert(x); - - let last_total_earnings = reward_pool.total_earnings; - reward_pool.update_total_earnings_and_balance(bonded_pool.id); - - // Notice there is an edge case where total_earnings have not increased and this is zero - let new_earnings = u256(reward_pool.total_earnings.saturating_sub(last_total_earnings)); - - // The new points that will be added to the pool. For every unit of balance that has been - // earned by the reward pool, we inflate the reward pool points by `bonded_pool.points`. In - // effect this allows each, single unit of balance (e.g. plank) to be divvied up pro rata - // among members based on points. - let new_points = u256(bonded_pool.points).saturating_mul(new_earnings); - - // The points of the reward pool after taking into account the new earnings. Notice that - // this only stays even or increases over time except for when we subtract member virtual - // shares. - let current_points = bonded_pool.bound_check(reward_pool.points.saturating_add(new_points)); - - // The rewards pool's earnings since the last time this member claimed a payout. - let new_earnings_since_last_claim = - reward_pool.total_earnings.saturating_sub(member.reward_pool_total_earnings); - - // The points of the reward pool that belong to the member. - let member_virtual_points = - // The members portion of the reward pool - u256(member.active_points()) - // times the amount the pool has earned since the member last claimed. - .saturating_mul(u256(new_earnings_since_last_claim)); - - let member_payout = if member_virtual_points.is_zero() || - current_points.is_zero() || - reward_pool.balance.is_zero() - { - Zero::zero() - } else { - // Equivalent to `(member_virtual_points / current_points) * reward_pool.balance` - let numerator = { - let numerator = member_virtual_points.saturating_mul(u256(reward_pool.balance)); - bonded_pool.bound_check(numerator) - }; - balance( - numerator - // We check for zero above - .div(current_points), - ) - }; - - // Record updates - if reward_pool.total_earnings == BalanceOf::::max_value() { - bonded_pool.set_state(PoolState::Destroying); - }; - - member.reward_pool_total_earnings = reward_pool.total_earnings; - reward_pool.points = current_points.saturating_sub(member_virtual_points); - reward_pool.balance = reward_pool.balance.saturating_sub(member_payout); - - Ok(member_payout) - } - /// If the member has some rewards, transfer a payout from the reward pool to the member. // Emits events and potentially modifies pool state if any arithmetic saturates, but does // not persist any of the mutable inputs to storage. @@ -2291,38 +2300,37 @@ impl Pallet { reward_pool: &mut RewardPool, ) -> Result, DispatchError> { debug_assert_eq!(member.pool_id, bonded_pool.id); + // a member who has no skin in the game anymore cannot claim any rewards. ensure!(!member.active_points().is_zero(), Error::::FullyUnbonding); - let was_destroying = bonded_pool.is_destroying(); - let member_payout = Self::calculate_member_payout(member, bonded_pool, reward_pool)?; + let current_reward_counter = + reward_pool.current_reward_counter(bonded_pool.id, bonded_pool.points)?; + let pending_rewards = member.pending_rewards(current_reward_counter)?; - if member_payout.is_zero() { - return Ok(member_payout) + if pending_rewards.is_zero() { + return Ok(pending_rewards) } + // IFF the reward is non-zero alter the member and reward pool info. + member.last_recorded_reward_counter = current_reward_counter; + reward_pool.register_claimed_reward(pending_rewards); + // Transfer payout to the member. T::Currency::transfer( &bonded_pool.reward_account(), &member_account, - member_payout, + pending_rewards, ExistenceRequirement::AllowDeath, )?; Self::deposit_event(Event::::PaidOut { member: member_account.clone(), pool_id: member.pool_id, - payout: member_payout, + payout: pending_rewards, }); - if bonded_pool.is_destroying() && !was_destroying { - Self::deposit_event(Event::::StateChanged { - pool_id: member.pool_id, - new_state: PoolState::Destroying, - }); - } - - Ok(member_payout) + Ok(pending_rewards) } /// Ensure the correctness of the state of this pallet. diff --git a/frame/nomination-pools/src/migration.rs b/frame/nomination-pools/src/migration.rs index e23a35fe85602..f2abfe29dfbf7 100644 --- a/frame/nomination-pools/src/migration.rs +++ b/frame/nomination-pools/src/migration.rs @@ -16,11 +16,12 @@ // limitations under the License. use super::*; +use crate::log; +use frame_support::traits::OnRuntimeUpgrade; +use sp_std::collections::btree_map::BTreeMap; pub mod v1 { use super::*; - use crate::log; - use frame_support::traits::OnRuntimeUpgrade; #[derive(Decode)] pub struct OldPoolRoles { @@ -103,3 +104,282 @@ pub mod v1 { } } } + +pub mod v2 { + use super::*; + use sp_runtime::Perbill; + + #[test] + fn migration_assumption_is_correct() { + // this migrations cleans all the reward accounts to contain exactly ed, and all members + // having no claimable rewards. In this state, all fields of the `RewardPool` and + // `member.last_recorded_reward_counter` are all zero. + use crate::mock::*; + ExtBuilder::default().build_and_execute(|| { + let join = |x| { + Balances::make_free_balance_be(&x, Balances::minimum_balance() + 10); + frame_support::assert_ok!(Pools::join(Origin::signed(x), 10, 1)); + }; + + assert_eq!(BondedPool::::get(1).unwrap().points, 10); + assert_eq!( + RewardPools::::get(1).unwrap(), + RewardPool { ..Default::default() } + ); + assert_eq!( + PoolMembers::::get(10).unwrap().last_recorded_reward_counter, + Zero::zero() + ); + + join(20); + assert_eq!(BondedPool::::get(1).unwrap().points, 20); + assert_eq!( + RewardPools::::get(1).unwrap(), + RewardPool { ..Default::default() } + ); + assert_eq!( + PoolMembers::::get(10).unwrap().last_recorded_reward_counter, + Zero::zero() + ); + assert_eq!( + PoolMembers::::get(20).unwrap().last_recorded_reward_counter, + Zero::zero() + ); + + join(30); + assert_eq!(BondedPool::::get(1).unwrap().points, 30); + assert_eq!( + RewardPools::::get(1).unwrap(), + RewardPool { ..Default::default() } + ); + assert_eq!( + PoolMembers::::get(10).unwrap().last_recorded_reward_counter, + Zero::zero() + ); + assert_eq!( + PoolMembers::::get(20).unwrap().last_recorded_reward_counter, + Zero::zero() + ); + assert_eq!( + PoolMembers::::get(30).unwrap().last_recorded_reward_counter, + Zero::zero() + ); + }); + } + + #[derive(Decode)] + pub struct OldRewardPool { + pub balance: B, + pub total_earnings: B, + pub points: U256, + } + + #[derive(Decode)] + pub struct OldPoolMember { + pub pool_id: PoolId, + pub points: BalanceOf, + pub reward_pool_total_earnings: BalanceOf, + pub unbonding_eras: BoundedBTreeMap, T::MaxUnbonding>, + } + + /// Migrate the pool reward scheme to the new version, as per + /// . + pub struct MigrateToV2(sp_std::marker::PhantomData); + impl MigrateToV2 { + fn run(current: StorageVersion) -> Weight { + let mut reward_pools_translated = 0u64; + let mut members_translated = 0u64; + // just for logging. + let mut total_value_locked = BalanceOf::::zero(); + let mut total_points_locked = BalanceOf::::zero(); + + // store each member of the pool, with their active points. In the process, migrate + // their data as well. + let mut temp_members = BTreeMap::)>>::new(); + PoolMembers::::translate::, _>(|key, old_member| { + let id = old_member.pool_id; + temp_members.entry(id).or_default().push((key, old_member.points)); + + total_points_locked += old_member.points; + members_translated += 1; + Some(PoolMember:: { + last_recorded_reward_counter: Zero::zero(), + pool_id: old_member.pool_id, + points: old_member.points, + unbonding_eras: old_member.unbonding_eras, + }) + }); + + // translate all reward pools. In the process, do the last payout as well. + RewardPools::::translate::>, _>( + |id, _old_reward_pool| { + // each pool should have at least one member. + let members = match temp_members.get(&id) { + Some(x) => x, + None => { + log!(error, "pool {} has no member! deleting it..", id); + return None + }, + }; + let bonded_pool = match BondedPools::::get(id) { + Some(x) => x, + None => { + log!(error, "pool {} has no bonded pool! deleting it..", id); + return None + }, + }; + + let accumulated_reward = RewardPool::::current_balance(id); + let reward_account = Pallet::::create_reward_account(id); + let mut sum_paid_out = BalanceOf::::zero(); + + members + .into_iter() + .filter_map(|(who, points)| { + let bonded_pool = match BondedPool::::get(id) { + Some(x) => x, + None => { + log!(error, "pool {} for member {:?} does not exist!", id, who); + return None + }, + }; + + total_value_locked += bonded_pool.points_to_balance(points.clone()); + let portion = Perbill::from_rational(*points, bonded_pool.points); + let last_claim = portion * accumulated_reward; + + log!( + debug, + "{:?} has {:?} ({:?}) of pool {} with total reward of {:?}", + who, + portion, + last_claim, + id, + accumulated_reward + ); + + if last_claim.is_zero() { + None + } else { + Some((who, last_claim)) + } + }) + .for_each(|(who, last_claim)| { + let outcome = T::Currency::transfer( + &reward_account, + &who, + last_claim, + ExistenceRequirement::KeepAlive, + ); + + if let Err(reason) = outcome { + log!(warn, "last reward claim failed due to {:?}", reason,); + } else { + sum_paid_out = sum_paid_out.saturating_add(last_claim); + } + + Pallet::::deposit_event(Event::::PaidOut { + member: who.clone(), + pool_id: id, + payout: last_claim, + }); + }); + + // this can only be because of rounding down, or because the person we + // wanted to pay their reward to could not accept it (dust). + let leftover = accumulated_reward.saturating_sub(sum_paid_out); + if !leftover.is_zero() { + // pay it all to depositor. + let o = T::Currency::transfer( + &reward_account, + &bonded_pool.roles.depositor, + leftover, + ExistenceRequirement::KeepAlive, + ); + log!(warn, "paying {:?} leftover to the depositor: {:?}", leftover, o); + } + + // finally, migrate the reward pool. + reward_pools_translated += 1; + + Some(RewardPool { + last_recorded_reward_counter: Zero::zero(), + last_recorded_total_payouts: Zero::zero(), + total_rewards_claimed: Zero::zero(), + }) + }, + ); + + log!( + info, + "Upgraded {} members, {} reward pools, TVL {:?} TPL {:?}, storage to version {:?}", + members_translated, + reward_pools_translated, + total_value_locked, + total_points_locked, + current + ); + current.put::>(); + T::DbWeight::get().reads_writes(members_translated + 1, reward_pools_translated + 1) + } + } + + impl OnRuntimeUpgrade for MigrateToV2 { + fn on_runtime_upgrade() -> Weight { + let current = Pallet::::current_storage_version(); + let onchain = Pallet::::on_chain_storage_version(); + + log!( + info, + "Running migration with current storage version {:?} / onchain {:?}", + current, + onchain + ); + + if current == 2 && onchain == 1 { + Self::run(current) + } else { + log!(info, "MigrateToV2 did not executed. This probably should be removed"); + T::DbWeight::get().reads(1) + } + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result<(), &'static str> { + // all reward accounts must have more than ED. + RewardPools::::iter().for_each(|(id, _)| { + assert!( + T::Currency::free_balance(&Pallet::::create_reward_account(id)) >= + T::Currency::minimum_balance() + ) + }); + + Ok(()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade() -> Result<(), &'static str> { + // new version must be set. + assert_eq!(Pallet::::on_chain_storage_version(), 2); + + // no reward or bonded pool has been skipped. + assert_eq!(RewardPools::::iter().count() as u32, RewardPools::::count()); + assert_eq!(BondedPools::::iter().count() as u32, BondedPools::::count()); + + // all reward pools must have exactly ED in them. This means no reward can be claimed, + // and that setting reward counters all over the board to zero will work henceforth. + RewardPools::::iter().for_each(|(id, _)| { + assert_eq!( + RewardPool::::current_balance(id), + Zero::zero(), + "reward pool({}) balance is {:?}", + id, + RewardPool::::current_balance(id) + ); + }); + + log!(info, "post upgrade hook for MigrateToV2 executed."); + Ok(()) + } + } +} diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index 7d71efc3be04d..042a0b666efb1 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -2,9 +2,14 @@ use super::*; use crate::{self as pools}; use frame_support::{assert_ok, parameter_types, PalletId}; use frame_system::RawOrigin; +use sp_runtime::FixedU128; pub type AccountId = u128; pub type Balance = u128; +pub type RewardCounter = FixedU128; +// This sneaky little hack allows us to write code exactly as we would do in the pallet in the tests +// as well, e.g. `StorageItem::::get()`. +pub type T = Runtime; // Ext builder creates a pool with id 1. pub fn default_bonded_account() -> AccountId { @@ -23,6 +28,7 @@ parameter_types! { pub storage UnbondingBalanceMap: BTreeMap = Default::default(); #[derive(Clone, PartialEq)] pub static MaxUnbonding: u32 = 8; + pub static StakingMinBond: Balance = 10; pub storage Nominations: Option> = None; } @@ -40,7 +46,7 @@ impl sp_staking::StakingInterface for StakingMock { type AccountId = AccountId; fn minimum_bond() -> Self::Balance { - 10 + StakingMinBond::get() } fn current_era() -> EraIndex { @@ -184,6 +190,8 @@ impl pools::Config for Runtime { type Event = Event; type WeightInfo = (); type Currency = Balances; + type CurrencyBalance = Balance; + type RewardCounter = RewardCounter; type BalanceToU256 = BalanceToU256; type U256ToBalance = U256ToBalance; type StakingInterface = StakingMock; @@ -191,7 +199,7 @@ impl pools::Config for Runtime { type PalletId = PoolsPalletId; type MaxMetadataLen = MaxMetadataLen; type MaxUnbonding = MaxUnbonding; - type MinPointsToBalance = frame_support::traits::ConstU32<10>; + type MaxPointsToBalance = frame_support::traits::ConstU8<10>; } type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; @@ -208,9 +216,16 @@ frame_support::construct_runtime!( } ); -#[derive(Default)] pub struct ExtBuilder { members: Vec<(AccountId, Balance)>, + max_members: Option, + max_members_per_pool: Option, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { members: Default::default(), max_members: Some(4), max_members_per_pool: Some(3) } + } } impl ExtBuilder { @@ -225,11 +240,26 @@ impl ExtBuilder { self } + pub(crate) fn min_bond(self, min: Balance) -> Self { + StakingMinBond::set(min); + self + } + pub(crate) fn with_check(self, level: u8) -> Self { CheckLevel::set(level); self } + pub(crate) fn max_members(mut self, max: Option) -> Self { + self.max_members = max; + self + } + + pub(crate) fn max_members_per_pool(mut self, max: Option) -> Self { + self.max_members_per_pool = max; + self + } + pub(crate) fn build(self) -> sp_io::TestExternalities { let mut storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); @@ -238,8 +268,8 @@ impl ExtBuilder { min_join_bond: 2, min_create_bond: 2, max_pools: Some(2), - max_members_per_pool: Some(3), - max_members: Some(4), + max_members_per_pool: self.max_members_per_pool, + max_members: self.max_members, } .assimilate_storage(&mut storage); @@ -281,8 +311,8 @@ pub(crate) fn unsafe_set_state(pool_id: PoolId, state: PoolState) -> Result<(), } parameter_types! { - static PoolsEvents: usize = 0; - static BalancesEvents: usize = 0; + storage PoolsEvents: u32 = 0; + storage BalancesEvents: u32 = 0; } /// All events of this pallet. @@ -293,8 +323,8 @@ pub(crate) fn pool_events_since_last_call() -> Vec> { .filter_map(|e| if let Event::Pools(inner) = e { Some(inner) } else { None }) .collect::>(); let already_seen = PoolsEvents::get(); - PoolsEvents::set(events.len()); - events.into_iter().skip(already_seen).collect() + PoolsEvents::set(&(events.len() as u32)); + events.into_iter().skip(already_seen as usize).collect() } /// All events of the `Balances` pallet. @@ -305,8 +335,8 @@ pub(crate) fn balances_events_since_last_call() -> Vec>(); let already_seen = BalancesEvents::get(); - BalancesEvents::set(events.len()); - events.into_iter().skip(already_seen).collect() + BalancesEvents::set(&(events.len() as u32)); + events.into_iter().skip(already_seen as usize).collect() } /// Same as `fully_unbond`, in permissioned setting. diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 97104423c5910..9989893d86462 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -17,10 +17,7 @@ use super::*; use crate::{mock::*, Event}; -use frame_support::{ - assert_err, assert_noop, assert_ok, assert_storage_noop, bounded_btree_map, - storage::{with_transaction, TransactionOutcome}, -}; +use frame_support::{assert_err, assert_noop, assert_ok, assert_storage_noop, bounded_btree_map}; use pallet_balances::Event as BEvent; use sp_runtime::traits::Dispatchable; @@ -66,7 +63,11 @@ fn test_setup_works() { ); assert_eq!( RewardPools::::get(last_pool).unwrap(), - RewardPool:: { balance: 0, points: 0u32.into(), total_earnings: 0 } + RewardPool:: { + last_recorded_reward_counter: Zero::zero(), + last_recorded_total_payouts: 0, + total_rewards_claimed: 0 + } ); assert_eq!( PoolMembers::::get(10).unwrap(), @@ -206,34 +207,34 @@ mod bonded_pool { }, }; - let min_points_to_balance: u128 = - <::MinPointsToBalance as Get>::get().into(); + let max_points_to_balance: u128 = + <::MaxPointsToBalance as Get>::get().into(); // Simulate a 100% slashed pool StakingMock::set_bonded_balance(pool.bonded_account(), 0); assert_noop!(pool.ok_to_join(0), Error::::OverflowRisk); - // Simulate a slashed pool at `MinPointsToBalance` + 1 slashed pool + // Simulate a slashed pool at `MaxPointsToBalance` + 1 slashed pool StakingMock::set_bonded_balance( pool.bonded_account(), - min_points_to_balance.saturating_add(1).into(), + max_points_to_balance.saturating_add(1).into(), ); assert_ok!(pool.ok_to_join(0)); - // Simulate a slashed pool at `MinPointsToBalance` - StakingMock::set_bonded_balance(pool.bonded_account(), min_points_to_balance); + // Simulate a slashed pool at `MaxPointsToBalance` + StakingMock::set_bonded_balance(pool.bonded_account(), max_points_to_balance); assert_noop!(pool.ok_to_join(0), Error::::OverflowRisk); StakingMock::set_bonded_balance( pool.bonded_account(), - Balance::MAX / min_points_to_balance, + Balance::MAX / max_points_to_balance, ); // New bonded balance would be over threshold of Balance type assert_noop!(pool.ok_to_join(0), Error::::OverflowRisk); // and a sanity check StakingMock::set_bonded_balance( pool.bonded_account(), - Balance::MAX / min_points_to_balance - 1, + Balance::MAX / max_points_to_balance - 1, ); assert_ok!(pool.ok_to_join(0)); }); @@ -515,44 +516,37 @@ mod join { .put(); // and reward pool - RewardPools::::insert( - 123, - RewardPool:: { - balance: Zero::zero(), - total_earnings: Zero::zero(), - points: U256::from(0u32), - }, - ); + RewardPools::::insert(123, RewardPool:: { ..Default::default() }); - // Force the points:balance ratio to `MinPointsToBalance` (100/10) - let min_points_to_balance: u128 = - <::MinPointsToBalance as Get>::get().into(); + // Force the points:balance ratio to `MaxPointsToBalance` (100/10) + let max_points_to_balance: u128 = + <::MaxPointsToBalance as Get>::get().into(); StakingMock::set_bonded_balance( Pools::create_bonded_account(123), - min_points_to_balance, + max_points_to_balance, ); assert_noop!(Pools::join(Origin::signed(11), 420, 123), Error::::OverflowRisk); StakingMock::set_bonded_balance( Pools::create_bonded_account(123), - Balance::MAX / min_points_to_balance, + Balance::MAX / max_points_to_balance, ); - // Balance needs to be gt Balance::MAX / `MinPointsToBalance` + // Balance needs to be gt Balance::MAX / `MaxPointsToBalance` assert_noop!(Pools::join(Origin::signed(11), 5, 123), Error::::OverflowRisk); - StakingMock::set_bonded_balance(Pools::create_bonded_account(1), min_points_to_balance); + StakingMock::set_bonded_balance(Pools::create_bonded_account(1), max_points_to_balance); // Cannot join a pool that isn't open unsafe_set_state(123, PoolState::Blocked).unwrap(); assert_noop!( - Pools::join(Origin::signed(11), min_points_to_balance, 123), + Pools::join(Origin::signed(11), max_points_to_balance, 123), Error::::NotOpen ); unsafe_set_state(123, PoolState::Destroying).unwrap(); assert_noop!( - Pools::join(Origin::signed(11), min_points_to_balance, 123), + Pools::join(Origin::signed(11), max_points_to_balance, 123), Error::::NotOpen ); @@ -649,17 +643,34 @@ mod join { mod claim_payout { use super::*; - fn del(points: Balance, reward_pool_total_earnings: Balance) -> PoolMember { + fn del(points: Balance, last_recorded_reward_counter: u128) -> PoolMember { PoolMember { pool_id: 1, points, - reward_pool_total_earnings, + last_recorded_reward_counter: last_recorded_reward_counter.into(), unbonding_eras: Default::default(), } } - fn rew(balance: Balance, points: u32, total_earnings: Balance) -> RewardPool { - RewardPool { balance, points: points.into(), total_earnings } + fn del_float(points: Balance, last_recorded_reward_counter: f64) -> PoolMember { + PoolMember { + pool_id: 1, + points, + last_recorded_reward_counter: RewardCounter::from_float(last_recorded_reward_counter), + unbonding_eras: Default::default(), + } + } + + fn rew( + last_recorded_reward_counter: u128, + last_recorded_total_payouts: Balance, + total_rewards_claimed: Balance, + ) -> RewardPool { + RewardPool { + last_recorded_reward_counter: last_recorded_reward_counter.into(), + last_recorded_total_payouts, + total_rewards_claimed, + } } #[test] @@ -672,8 +683,12 @@ mod claim_payout { Balances::make_free_balance_be(&40, 0); Balances::make_free_balance_be(&50, 0); let ed = Balances::minimum_balance(); + // and the reward pool has earned 100 in rewards - Balances::make_free_balance_be(&default_reward_account(), ed + 100); + assert_eq!(Balances::free_balance(default_reward_account()), ed); + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 100)); + + let _ = pool_events_since_last_call(); // When assert_ok!(Pools::claim_payout(Origin::signed(10))); @@ -681,22 +696,13 @@ mod claim_payout { // Then assert_eq!( pool_events_since_last_call(), - vec![ - Event::Created { depositor: 10, pool_id: 1 }, - Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true }, - Event::Bonded { member: 50, pool_id: 1, bonded: 50, joined: true }, - Event::PaidOut { member: 10, pool_id: 1, payout: 10 }, - ] - ); - - // Expect a payout of 10: (10 del virtual points / 100 pool points) * 100 pool - // balance - assert_eq!(PoolMembers::::get(10).unwrap(), del(10, 100)); - assert_eq!( - RewardPools::::get(&1).unwrap(), - rew(90, 100 * 100 - 100 * 10, 100) + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 10 },] ); + // last recorded reward counter at the time of this member's payout is 1 + assert_eq!(PoolMembers::::get(10).unwrap(), del(10, 1)); + // pool's 'last_recorded_reward_counter' and 'last_recorded_total_payouts' don't + // really change unless if someone bonds/unbonds. + assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 10)); assert_eq!(Balances::free_balance(&10), 10); assert_eq!(Balances::free_balance(&default_reward_account()), ed + 90); @@ -708,13 +714,8 @@ mod claim_payout { pool_events_since_last_call(), vec![Event::PaidOut { member: 40, pool_id: 1, payout: 40 }] ); - - // Expect payout 40: (400 del virtual points / 900 pool points) * 90 pool balance - assert_eq!(PoolMembers::::get(40).unwrap(), del(40, 100)); - assert_eq!( - RewardPools::::get(&1).unwrap(), - rew(50, 9_000 - 100 * 40, 100) - ); + assert_eq!(PoolMembers::::get(40).unwrap(), del(40, 1)); + assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 50)); assert_eq!(Balances::free_balance(&40), 40); assert_eq!(Balances::free_balance(&default_reward_account()), ed + 50); @@ -726,15 +727,13 @@ mod claim_payout { pool_events_since_last_call(), vec![Event::PaidOut { member: 50, pool_id: 1, payout: 50 }] ); - - // Expect payout 50: (50 del virtual points / 50 pool points) * 50 pool balance - assert_eq!(PoolMembers::::get(50).unwrap(), del(50, 100)); + assert_eq!(PoolMembers::::get(50).unwrap(), del(50, 1)); assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 100)); assert_eq!(Balances::free_balance(&50), 50); assert_eq!(Balances::free_balance(&default_reward_account()), ed + 0); // Given the reward pool has some new rewards - Balances::make_free_balance_be(&default_reward_account(), ed + 50); + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 50)); // When assert_ok!(Pools::claim_payout(Origin::signed(10))); @@ -744,10 +743,8 @@ mod claim_payout { pool_events_since_last_call(), vec![Event::PaidOut { member: 10, pool_id: 1, payout: 5 }] ); - - // Expect payout 5: (500 del virtual points / 5,000 pool points) * 50 pool balance - assert_eq!(PoolMembers::::get(10).unwrap(), del(10, 150)); - assert_eq!(RewardPools::::get(&1).unwrap(), rew(45, 5_000 - 50 * 10, 150)); + assert_eq!(PoolMembers::::get(10).unwrap(), del_float(10, 1.5)); + assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 105)); assert_eq!(Balances::free_balance(&10), 10 + 5); assert_eq!(Balances::free_balance(&default_reward_account()), ed + 45); @@ -759,11 +756,8 @@ mod claim_payout { pool_events_since_last_call(), vec![Event::PaidOut { member: 40, pool_id: 1, payout: 20 }] ); - - // Expect payout 20: (2,000 del virtual points / 4,500 pool points) * 45 pool - // balance - assert_eq!(PoolMembers::::get(40).unwrap(), del(40, 150)); - assert_eq!(RewardPools::::get(&1).unwrap(), rew(25, 4_500 - 50 * 40, 150)); + assert_eq!(PoolMembers::::get(40).unwrap(), del_float(40, 1.5)); + assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 125)); assert_eq!(Balances::free_balance(&40), 40 + 20); assert_eq!(Balances::free_balance(&default_reward_account()), ed + 25); @@ -779,22 +773,8 @@ mod claim_payout { pool_events_since_last_call(), vec![Event::PaidOut { member: 50, pool_id: 1, payout: 50 }] ); - - // We expect a payout of 50: (5,000 del virtual points / 7,5000 pool points) * 75 - // pool balance - assert_eq!(PoolMembers::::get(50).unwrap(), del(50, 200)); - assert_eq!( - RewardPools::::get(&1).unwrap(), - rew( - 25, - // old pool points + points from new earnings - del points. - // - // points from new earnings = new earnings(50) * bonded_pool.points(100) - // del points = member.points(50) * new_earnings_since_last_claim (100) - (2_500 + 50 * 100) - 50 * 100, - 200, - ) - ); + assert_eq!(PoolMembers::::get(50).unwrap(), del_float(50, 2.0)); + assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 175)); assert_eq!(Balances::free_balance(&50), 50 + 50); assert_eq!(Balances::free_balance(&default_reward_account()), ed + 25); @@ -806,10 +786,8 @@ mod claim_payout { pool_events_since_last_call(), vec![Event::PaidOut { member: 10, pool_id: 1, payout: 5 }] ); - - // We expect a payout of 5 - assert_eq!(PoolMembers::::get(10).unwrap(), del(10, 200)); - assert_eq!(RewardPools::::get(&1).unwrap(), rew(20, 2_500 - 10 * 50, 200)); + assert_eq!(PoolMembers::::get(10).unwrap(), del(10, 2)); + assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 180)); assert_eq!(Balances::free_balance(&10), 15 + 5); assert_eq!(Balances::free_balance(&default_reward_account()), ed + 20); @@ -827,19 +805,8 @@ mod claim_payout { ); // We expect a payout of 40 - assert_eq!(PoolMembers::::get(10).unwrap(), del(10, 600)); - assert_eq!( - RewardPools::::get(&1).unwrap(), - rew( - 380, - // old pool points + points from new earnings - del points - // - // points from new earnings = new earnings(400) * bonded_pool.points(100) - // del points = member.points(10) * new_earnings_since_last_claim(400) - (2_000 + 400 * 100) - 10 * 400, - 600 - ) - ); + assert_eq!(PoolMembers::::get(10).unwrap(), del(10, 6)); + assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 220)); assert_eq!(Balances::free_balance(&10), 20 + 40); assert_eq!(Balances::free_balance(&default_reward_account()), ed + 380); @@ -855,14 +822,8 @@ mod claim_payout { pool_events_since_last_call(), vec![Event::PaidOut { member: 10, pool_id: 1, payout: 2 }] ); - - // Expect a payout of 2: (200 del virtual points / 38,000 pool points) * 400 pool - // balance - assert_eq!(PoolMembers::::get(10).unwrap(), del(10, 620)); - assert_eq!( - RewardPools::::get(&1).unwrap(), - rew(398, (38_000 + 20 * 100) - 10 * 20, 620) - ); + assert_eq!(PoolMembers::::get(10).unwrap(), del_float(10, 6.2)); + assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 222)); assert_eq!(Balances::free_balance(&10), 60 + 2); assert_eq!(Balances::free_balance(&default_reward_account()), ed + 398); @@ -874,14 +835,8 @@ mod claim_payout { pool_events_since_last_call(), vec![Event::PaidOut { member: 40, pool_id: 1, payout: 188 }] ); - - // Expect a payout of 188: (18,800 del virtual points / 39,800 pool points) * 399 - // pool balance - assert_eq!(PoolMembers::::get(40).unwrap(), del(40, 620)); - assert_eq!( - RewardPools::::get(&1).unwrap(), - rew(210, 39_800 - 40 * 470, 620) - ); + assert_eq!(PoolMembers::::get(40).unwrap(), del_float(40, 6.2)); + assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 410)); assert_eq!(Balances::free_balance(&40), 60 + 188); assert_eq!(Balances::free_balance(&default_reward_account()), ed + 210); @@ -893,88 +848,20 @@ mod claim_payout { pool_events_since_last_call(), vec![Event::PaidOut { member: 50, pool_id: 1, payout: 210 }] ); - - // Expect payout of 210: (21,000 / 21,000) * 210 - assert_eq!(PoolMembers::::get(50).unwrap(), del(50, 620)); - assert_eq!( - RewardPools::::get(&1).unwrap(), - rew(0, 21_000 - 50 * 420, 620) - ); + assert_eq!(PoolMembers::::get(50).unwrap(), del_float(50, 6.2)); + assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 620)); assert_eq!(Balances::free_balance(&50), 100 + 210); assert_eq!(Balances::free_balance(&default_reward_account()), ed + 0); }); } - #[test] - fn do_reward_payout_correctly_sets_pool_state_to_destroying() { - ExtBuilder::default().build_and_execute(|| { - let _ = with_transaction(|| -> TransactionOutcome { - let mut bonded_pool = BondedPool::::get(1).unwrap(); - let mut reward_pool = RewardPools::::get(1).unwrap(); - let mut member = PoolMembers::::get(10).unwrap(); - - // -- reward_pool.total_earnings saturates - - // Given - Balances::make_free_balance_be(&default_reward_account(), Balance::MAX); - - // When - assert_ok!(Pools::do_reward_payout( - &10, - &mut member, - &mut bonded_pool, - &mut reward_pool - )); - - // Then - assert!(bonded_pool.is_destroying()); - - storage::TransactionOutcome::Rollback(Ok(())) - }); - - // -- current_points saturates (reward_pool.points + new_earnings * bonded_pool.points) - let _ = with_transaction(|| -> TransactionOutcome { - // Given - let mut bonded_pool = BondedPool::::get(1).unwrap(); - let mut reward_pool = RewardPools::::get(1).unwrap(); - let mut member = PoolMembers::::get(10).unwrap(); - // Force new_earnings * bonded_pool.points == 100 - Balances::make_free_balance_be(&default_reward_account(), 5 + 10); - assert_eq!(bonded_pool.points, 10); - // Force reward_pool.points == U256::MAX - new_earnings * bonded_pool.points - reward_pool.points = U256::MAX - U256::from(100u32); - RewardPools::::insert(1, reward_pool.clone()); - - // When - assert_ok!(Pools::do_reward_payout( - &10, - &mut member, - &mut bonded_pool, - &mut reward_pool - )); - - // Then - assert!(bonded_pool.is_destroying()); - - storage::TransactionOutcome::Rollback(Ok(())) - }); - }); - } - #[test] fn reward_payout_errors_if_a_member_is_fully_unbonding() { ExtBuilder::default().add_members(vec![(11, 11)]).build_and_execute(|| { // fully unbond the member. - assert_ok!(Pools::fully_unbond(Origin::signed(11), 11)); + assert_ok!(fully_unbond_permissioned(11)); - let mut bonded_pool = BondedPool::::get(1).unwrap(); - let mut reward_pool = RewardPools::::get(1).unwrap(); - let mut member = PoolMembers::::get(11).unwrap(); - - assert_noop!( - Pools::do_reward_payout(&11, &mut member, &mut bonded_pool, &mut reward_pool,), - Error::::FullyUnbonding - ); + assert_noop!(Pools::claim_payout(Origin::signed(11)), Error::::FullyUnbonding); assert_eq!( pool_events_since_last_call(), @@ -989,80 +876,87 @@ mod claim_payout { } #[test] - fn calculate_member_payout_works_with_a_pool_of_1() { - let del = |reward_pool_total_earnings| del(10, reward_pool_total_earnings); + fn do_reward_payout_works_with_a_pool_of_1() { + let del = |last_recorded_reward_counter| del_float(10, last_recorded_reward_counter); ExtBuilder::default().build_and_execute(|| { - let mut bonded_pool = BondedPool::::get(1).unwrap(); - let mut reward_pool = RewardPools::::get(1).unwrap(); - let mut member = PoolMembers::::get(10).unwrap(); + let (mut member, mut bonded_pool, mut reward_pool) = + Pools::get_member_with_pools(&10).unwrap(); let ed = Balances::minimum_balance(); - // Given no rewards have been earned - // When let payout = - Pools::calculate_member_payout(&mut member, &mut bonded_pool, &mut reward_pool) + Pools::do_reward_payout(&10, &mut member, &mut bonded_pool, &mut reward_pool) .unwrap(); // Then assert_eq!(payout, 0); - assert_eq!(member, del(0)); + assert_eq!(member, del(0.0)); assert_eq!(reward_pool, rew(0, 0, 0)); // Given the pool has earned some rewards for the first time - Balances::make_free_balance_be(&default_reward_account(), ed + 5); + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 5)); // When let payout = - Pools::calculate_member_payout(&mut member, &mut bonded_pool, &mut reward_pool) + Pools::do_reward_payout(&10, &mut member, &mut bonded_pool, &mut reward_pool) .unwrap(); // Then - assert_eq!(payout, 5); // (10 * 5 del virtual points / 10 * 5 pool points) * 5 pool balance + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::PaidOut { member: 10, pool_id: 1, payout: 5 } + ] + ); + assert_eq!(payout, 5); assert_eq!(reward_pool, rew(0, 0, 5)); - assert_eq!(member, del(5)); + assert_eq!(member, del(0.5)); // Given the pool has earned rewards again - Balances::make_free_balance_be(&default_reward_account(), ed + 10); + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 10)); // When let payout = - Pools::calculate_member_payout(&mut member, &mut bonded_pool, &mut reward_pool) + Pools::do_reward_payout(&10, &mut member, &mut bonded_pool, &mut reward_pool) .unwrap(); // Then - assert_eq!(payout, 10); // (10 * 10 del virtual points / 10 pool points) * 5 pool balance + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 10 }] + ); + assert_eq!(payout, 10); assert_eq!(reward_pool, rew(0, 0, 15)); - assert_eq!(member, del(15)); + assert_eq!(member, del(1.5)); // Given the pool has earned no new rewards Balances::make_free_balance_be(&default_reward_account(), ed + 0); // When let payout = - Pools::calculate_member_payout(&mut member, &mut bonded_pool, &mut reward_pool) + Pools::do_reward_payout(&10, &mut member, &mut bonded_pool, &mut reward_pool) .unwrap(); // Then + assert_eq!(pool_events_since_last_call(), vec![]); assert_eq!(payout, 0); assert_eq!(reward_pool, rew(0, 0, 15)); - assert_eq!(member, del(15)); + assert_eq!(member, del(1.5)); }); } #[test] - fn calculate_member_payout_works_with_a_pool_of_3() { + fn do_reward_payout_works_with_a_pool_of_3() { ExtBuilder::default() .add_members(vec![(40, 40), (50, 50)]) .build_and_execute(|| { let mut bonded_pool = BondedPool::::get(1).unwrap(); let mut reward_pool = RewardPools::::get(1).unwrap(); - let ed = Balances::minimum_balance(); - // PoolMember with 10 points + let mut del_10 = PoolMembers::::get(10).unwrap(); - // PoolMember with 40 points let mut del_40 = PoolMembers::::get(40).unwrap(); - // PoolMember with 50 points let mut del_50 = PoolMembers::::get(50).unwrap(); assert_eq!( @@ -1078,479 +972,970 @@ mod claim_payout { // Given we have a total of 100 points split among the members assert_eq!(del_50.points + del_40.points + del_10.points, 100); assert_eq!(bonded_pool.points, 100); + // and the reward pool has earned 100 in rewards - Balances::make_free_balance_be(&default_reward_account(), ed + 100); + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 100)); // When let payout = - Pools::calculate_member_payout(&mut del_10, &mut bonded_pool, &mut reward_pool) + Pools::do_reward_payout(&10, &mut del_10, &mut bonded_pool, &mut reward_pool) .unwrap(); // Then - assert_eq!(payout, 10); // (10 del virtual points / 100 pool points) * 100 pool balance - assert_eq!(del_10, del(10, 100)); - assert_eq!(reward_pool, rew(90, 100 * 100 - 100 * 10, 100)); - // Mock the reward pool transferring the payout to del_10 - assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 10)); + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 10 }] + ); + assert_eq!(payout, 10); + assert_eq!(del_10, del(10, 1)); + assert_eq!(reward_pool, rew(0, 0, 10)); // When let payout = - Pools::calculate_member_payout(&mut del_40, &mut bonded_pool, &mut reward_pool) + Pools::do_reward_payout(&40, &mut del_40, &mut bonded_pool, &mut reward_pool) .unwrap(); // Then - assert_eq!(payout, 40); // (400 del virtual points / 900 pool points) * 90 pool balance - assert_eq!(del_40, del(40, 100)); assert_eq!( - reward_pool, - rew( - 50, - // old pool points - member virtual points - 9_000 - 100 * 40, - 100 - ) + pool_events_since_last_call(), + vec![Event::PaidOut { member: 40, pool_id: 1, payout: 40 }] ); - // Mock the reward pool transferring the payout to del_40 - assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 40)); + assert_eq!(payout, 40); + assert_eq!(del_40, del(40, 1)); + assert_eq!(reward_pool, rew(0, 0, 50)); // When let payout = - Pools::calculate_member_payout(&mut del_50, &mut bonded_pool, &mut reward_pool) + Pools::do_reward_payout(&50, &mut del_50, &mut bonded_pool, &mut reward_pool) .unwrap(); // Then - assert_eq!(payout, 50); // (50 del virtual points / 50 pool points) * 50 pool balance - assert_eq!(del_50, del(50, 100)); + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 50, pool_id: 1, payout: 50 }] + ); + assert_eq!(payout, 50); + assert_eq!(del_50, del(50, 1)); assert_eq!(reward_pool, rew(0, 0, 100)); - // Mock the reward pool transferring the payout to del_50 - assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 50)); // Given the reward pool has some new rewards - Balances::make_free_balance_be(&default_reward_account(), ed + 50); + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 50)); // When let payout = - Pools::calculate_member_payout(&mut del_10, &mut bonded_pool, &mut reward_pool) + Pools::do_reward_payout(&10, &mut del_10, &mut bonded_pool, &mut reward_pool) .unwrap(); // Then - assert_eq!(payout, 5); // (500 del virtual points / 5,000 pool points) * 50 pool balance - assert_eq!(del_10, del(10, 150)); - assert_eq!(reward_pool, rew(45, 5_000 - 50 * 10, 150)); - // Mock the reward pool transferring the payout to del_10 - assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 5)); + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 5 }] + ); + assert_eq!(payout, 5); + assert_eq!(del_10, del_float(10, 1.5)); + assert_eq!(reward_pool, rew(0, 0, 105)); // When let payout = - Pools::calculate_member_payout(&mut del_40, &mut bonded_pool, &mut reward_pool) + Pools::do_reward_payout(&40, &mut del_40, &mut bonded_pool, &mut reward_pool) .unwrap(); // Then - assert_eq!(payout, 20); // (2,000 del virtual points / 4,500 pool points) * 45 pool balance - assert_eq!(del_40, del(40, 150)); - assert_eq!(reward_pool, rew(25, 4_500 - 50 * 40, 150)); - assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 20)); + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 40, pool_id: 1, payout: 20 }] + ); + assert_eq!(payout, 20); + assert_eq!(del_40, del_float(40, 1.5)); + assert_eq!(reward_pool, rew(0, 0, 125)); // Given del_50 hasn't claimed and the reward pools has just earned 50 assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 50)); - assert_eq!(Balances::free_balance(&default_reward_account()), ed + 75); // When let payout = - Pools::calculate_member_payout(&mut del_50, &mut bonded_pool, &mut reward_pool) + Pools::do_reward_payout(&50, &mut del_50, &mut bonded_pool, &mut reward_pool) .unwrap(); // Then - assert_eq!(payout, 50); // (5,000 del virtual points / 7,5000 pool points) * 75 pool balance - assert_eq!(del_50, del(50, 200)); - assert_eq!( - reward_pool, - rew( - 25, - // old pool points + points from new earnings - del points. - // - // points from new earnings = new earnings(50) * bonded_pool.points(100) - // del points = member.points(50) * new_earnings_since_last_claim (100) - (2_500 + 50 * 100) - 50 * 100, - 200, - ) - ); - // Mock the reward pool transferring the payout to del_50 - assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 50)); + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 50, pool_id: 1, payout: 50 }] + ); + assert_eq!(payout, 50); + assert_eq!(del_50, del_float(50, 2.0)); + assert_eq!(reward_pool, rew(0, 0, 175)); // When let payout = - Pools::calculate_member_payout(&mut del_10, &mut bonded_pool, &mut reward_pool) + Pools::do_reward_payout(&10, &mut del_10, &mut bonded_pool, &mut reward_pool) .unwrap(); // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 5 }] + ); assert_eq!(payout, 5); - assert_eq!(del_10, del(10, 200)); - assert_eq!(reward_pool, rew(20, 2_500 - 10 * 50, 200)); - // Mock the reward pool transferring the payout to del_10 - assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 5)); + assert_eq!(del_10, del_float(10, 2.0)); + assert_eq!(reward_pool, rew(0, 0, 180)); // Given del_40 hasn't claimed and the reward pool has just earned 400 assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 400)); - assert_eq!(Balances::free_balance(&default_reward_account()), ed + 420); // When let payout = - Pools::calculate_member_payout(&mut del_10, &mut bonded_pool, &mut reward_pool) + Pools::do_reward_payout(&10, &mut del_10, &mut bonded_pool, &mut reward_pool) .unwrap(); // Then + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 40 }] + ); assert_eq!(payout, 40); - assert_eq!(del_10, del(10, 600)); - assert_eq!( - reward_pool, - rew( - 380, - // old pool points + points from new earnings - del points - // - // points from new earnings = new earnings(400) * bonded_pool.points(100) - // del points = member.points(10) * new_earnings_since_last_claim(400) - (2_000 + 400 * 100) - 10 * 400, - 600 - ) - ); - // Mock the reward pool transferring the payout to del_10 - assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 40)); + assert_eq!(del_10, del_float(10, 6.0)); + assert_eq!(reward_pool, rew(0, 0, 220)); // Given del_40 + del_50 haven't claimed and the reward pool has earned 20 assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 20)); - assert_eq!(Balances::free_balance(&default_reward_account()), ed + 400); // When let payout = - Pools::calculate_member_payout(&mut del_10, &mut bonded_pool, &mut reward_pool) + Pools::do_reward_payout(&10, &mut del_10, &mut bonded_pool, &mut reward_pool) .unwrap(); // Then - assert_eq!(payout, 2); // (200 del virtual points / 38,000 pool points) * 400 pool balance - assert_eq!(del_10, del(10, 620)); - assert_eq!(reward_pool, rew(398, (38_000 + 20 * 100) - 10 * 20, 620)); - // Mock the reward pool transferring the payout to del_10 - assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 2)); + assert_eq!(payout, 2); + assert_eq!(del_10, del_float(10, 6.2)); + assert_eq!(reward_pool, rew(0, 0, 222)); // When let payout = - Pools::calculate_member_payout(&mut del_40, &mut bonded_pool, &mut reward_pool) + Pools::do_reward_payout(&40, &mut del_40, &mut bonded_pool, &mut reward_pool) .unwrap(); // Then - assert_eq!(payout, 188); // (18,800 del virtual points / 39,800 pool points) * 399 pool balance - assert_eq!(del_40, del(40, 620)); - assert_eq!(reward_pool, rew(210, 39_800 - 40 * 470, 620)); - // Mock the reward pool transferring the payout to del_10 - assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= 188)); + assert_eq!(payout, 188); // 20 (from the 50) + 160 (from the 400) + 8 (from the 20) + assert_eq!(del_40, del_float(40, 6.2)); + assert_eq!(reward_pool, rew(0, 0, 410)); // When let payout = - Pools::calculate_member_payout(&mut del_50, &mut bonded_pool, &mut reward_pool) + Pools::do_reward_payout(&50, &mut del_50, &mut bonded_pool, &mut reward_pool) .unwrap(); // Then - assert_eq!(payout, 210); // (21,000 / 21,000) * 210 - assert_eq!(del_50, del(50, 620)); - assert_eq!(reward_pool, rew(0, 21_000 - 50 * 420, 620)); + assert_eq!(payout, 210); // 200 (from the 400) + 10 (from the 20) + assert_eq!(del_50, del_float(50, 6.2)); + assert_eq!(reward_pool, rew(0, 0, 620)); }); } #[test] - fn do_reward_payout_works() { - ExtBuilder::default() - .add_members(vec![(40, 40), (50, 50)]) - .build_and_execute(|| { - let mut bonded_pool = BondedPool::::get(1).unwrap(); - let mut reward_pool = RewardPools::::get(1).unwrap(); - let ed = Balances::minimum_balance(); + fn rewards_distribution_is_fair_basic() { + ExtBuilder::default().build_and_execute(|| { + // reward pool by 10. + Balances::mutate_account(&default_reward_account(), |f| f.free += 10).unwrap(); - assert_eq!( - pool_events_since_last_call(), - vec![ - Event::Created { depositor: 10, pool_id: 1 }, - Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true }, - Event::Bonded { member: 50, pool_id: 1, bonded: 50, joined: true } - ] - ); + // 20 joins afterwards. + Balances::make_free_balance_be(&20, Balances::minimum_balance() + 10); + assert_ok!(Pools::join(Origin::signed(20), 10, 1)); - // Given the bonded pool has 100 points - assert_eq!(bonded_pool.points, 100); - // Each member currently has a free balance of - Balances::make_free_balance_be(&10, 0); - Balances::make_free_balance_be(&40, 0); - Balances::make_free_balance_be(&50, 0); - // and the reward pool has earned 100 in rewards - Balances::make_free_balance_be(&default_reward_account(), ed + 100); + // reward by another 20 + Balances::mutate_account(&default_reward_account(), |f| f.free += 20).unwrap(); - let mut del_10 = PoolMembers::get(10).unwrap(); - let mut del_40 = PoolMembers::get(40).unwrap(); - let mut del_50 = PoolMembers::get(50).unwrap(); + // 10 should claim 10 + 10, 20 should claim 20 / 2. + assert_ok!(Pools::claim_payout(Origin::signed(10))); + assert_ok!(Pools::claim_payout(Origin::signed(20))); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true }, + Event::PaidOut { member: 10, pool_id: 1, payout: 20 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 10 }, + ] + ); - // When - assert_ok!(Pools::do_reward_payout( - &10, - &mut del_10, - &mut bonded_pool, - &mut reward_pool - )); + // any upcoming rewards are shared equally. + Balances::mutate_account(&default_reward_account(), |f| f.free += 20).unwrap(); - // Then - assert_eq!( - pool_events_since_last_call(), - vec![Event::PaidOut { member: 10, pool_id: 1, payout: 10 }] - ); + assert_ok!(Pools::claim_payout(Origin::signed(10))); + assert_ok!(Pools::claim_payout(Origin::signed(20))); - // Expect a payout of 10: (10 del virtual points / 100 pool points) * 100 pool - // balance - assert_eq!(del_10, del(10, 100)); - assert_eq!(reward_pool, rew(90, 100 * 100 - 100 * 10, 100)); - assert_eq!(Balances::free_balance(&10), 10); - assert_eq!(Balances::free_balance(&default_reward_account()), ed + 90); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::PaidOut { member: 10, pool_id: 1, payout: 10 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 10 }, + ] + ); + }); + } - // When - assert_ok!(Pools::do_reward_payout( - &40, - &mut del_40, - &mut bonded_pool, - &mut reward_pool - )); + #[test] + fn rewards_distribution_is_fair_basic_with_fractions() { + // basically checks the case where the amount of rewards is less than the pool shares. for + // this, we have to rely on fixed point arithmetic. + ExtBuilder::default().build_and_execute(|| { + Balances::mutate_account(&default_reward_account(), |f| f.free += 3).unwrap(); - // Then - assert_eq!( - pool_events_since_last_call(), - vec![Event::PaidOut { member: 40, pool_id: 1, payout: 40 }] - ); + Balances::make_free_balance_be(&20, Balances::minimum_balance() + 10); + assert_ok!(Pools::join(Origin::signed(20), 10, 1)); - // Expect payout 40: (400 del virtual points / 900 pool points) * 90 pool balance - assert_eq!(del_40, del(40, 100)); - assert_eq!(reward_pool, rew(50, 9_000 - 100 * 40, 100)); - assert_eq!(Balances::free_balance(&40), 40); - assert_eq!(Balances::free_balance(&default_reward_account()), ed + 50); + Balances::mutate_account(&default_reward_account(), |f| f.free += 6).unwrap(); - // When - assert_ok!(Pools::do_reward_payout( - &50, - &mut del_50, - &mut bonded_pool, - &mut reward_pool - )); + // 10 should claim 3, 20 should claim 3 + 3. + assert_ok!(Pools::claim_payout(Origin::signed(10))); + assert_ok!(Pools::claim_payout(Origin::signed(20))); - // Then - assert_eq!( - pool_events_since_last_call(), - vec![Event::PaidOut { member: 50, pool_id: 1, payout: 50 }] - ); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true }, + Event::PaidOut { member: 10, pool_id: 1, payout: 3 + 3 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 3 }, + ] + ); - // Expect payout 50: (50 del virtual points / 50 pool points) * 50 pool balance - assert_eq!(del_50, del(50, 100)); - assert_eq!(reward_pool, rew(0, 0, 100)); - assert_eq!(Balances::free_balance(&50), 50); - assert_eq!(Balances::free_balance(&default_reward_account()), ed + 0); + // any upcoming rewards are shared equally. + Balances::mutate_account(&default_reward_account(), |f| f.free += 8).unwrap(); - // Given the reward pool has some new rewards - Balances::make_free_balance_be(&default_reward_account(), ed + 50); + assert_ok!(Pools::claim_payout(Origin::signed(10))); + assert_ok!(Pools::claim_payout(Origin::signed(20))); - // When - assert_ok!(Pools::do_reward_payout( - &10, - &mut del_10, - &mut bonded_pool, - &mut reward_pool - )); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::PaidOut { member: 10, pool_id: 1, payout: 4 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 4 }, + ] + ); - // Then - assert_eq!( - pool_events_since_last_call(), - vec![Event::PaidOut { member: 10, pool_id: 1, payout: 5 }] - ); + // uneven upcoming rewards are shared equally, rounded down. + Balances::mutate_account(&default_reward_account(), |f| f.free += 7).unwrap(); - // Expect payout 5: (500 del virtual points / 5,000 pool points) * 50 pool balance - assert_eq!(del_10, del(10, 150)); - assert_eq!(reward_pool, rew(45, 5_000 - 50 * 10, 150)); - assert_eq!(Balances::free_balance(&10), 10 + 5); - assert_eq!(Balances::free_balance(&default_reward_account()), ed + 45); + assert_ok!(Pools::claim_payout(Origin::signed(10))); + assert_ok!(Pools::claim_payout(Origin::signed(20))); - // When - assert_ok!(Pools::do_reward_payout( - &40, - &mut del_40, - &mut bonded_pool, - &mut reward_pool - )); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::PaidOut { member: 10, pool_id: 1, payout: 3 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 3 }, + ] + ); + }); + } - // Then - assert_eq!( - pool_events_since_last_call(), - vec![Event::PaidOut { member: 40, pool_id: 1, payout: 20 }] - ); + #[test] + fn rewards_distribution_is_fair_3() { + ExtBuilder::default().build_and_execute(|| { + let ed = Balances::minimum_balance(); - // Expect payout 20: (2,000 del virtual points / 4,500 pool points) * 45 pool - // balance - assert_eq!(del_40, del(40, 150)); - assert_eq!(reward_pool, rew(25, 4_500 - 50 * 40, 150)); - assert_eq!(Balances::free_balance(&40), 40 + 20); - assert_eq!(Balances::free_balance(&default_reward_account()), ed + 25); + Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap(); - // Given del 50 hasn't claimed and the reward pools has just earned 50 - assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 50)); - assert_eq!(Balances::free_balance(&default_reward_account()), ed + 75); + Balances::make_free_balance_be(&20, ed + 10); + assert_ok!(Pools::join(Origin::signed(20), 10, 1)); - // When - assert_ok!(Pools::do_reward_payout( - &50, - &mut del_50, - &mut bonded_pool, - &mut reward_pool - )); + Balances::mutate_account(&default_reward_account(), |f| f.free += 100).unwrap(); - // Then + Balances::make_free_balance_be(&30, ed + 10); + assert_ok!(Pools::join(Origin::signed(30), 10, 1)); + + Balances::mutate_account(&default_reward_account(), |f| f.free += 60).unwrap(); + + // 10 should claim 10, 20 should claim nothing. + assert_ok!(Pools::claim_payout(Origin::signed(10))); + assert_ok!(Pools::claim_payout(Origin::signed(20))); + assert_ok!(Pools::claim_payout(Origin::signed(30))); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 30, pool_id: 1, bonded: 10, joined: true }, + Event::PaidOut { member: 10, pool_id: 1, payout: 30 + 100 / 2 + 60 / 3 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 100 / 2 + 60 / 3 }, + Event::PaidOut { member: 30, pool_id: 1, payout: 60 / 3 }, + ] + ); + + // any upcoming rewards are shared equally. + Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap(); + + assert_ok!(Pools::claim_payout(Origin::signed(10))); + assert_ok!(Pools::claim_payout(Origin::signed(20))); + assert_ok!(Pools::claim_payout(Origin::signed(30))); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::PaidOut { member: 10, pool_id: 1, payout: 10 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 10 }, + Event::PaidOut { member: 30, pool_id: 1, payout: 10 }, + ] + ); + }); + } + + #[test] + fn rewards_distribution_is_fair_bond_extra() { + ExtBuilder::default().build_and_execute(|| { + let ed = Balances::minimum_balance(); + + Balances::make_free_balance_be(&20, ed + 20); + assert_ok!(Pools::join(Origin::signed(20), 20, 1)); + Balances::make_free_balance_be(&30, ed + 20); + assert_ok!(Pools::join(Origin::signed(30), 10, 1)); + + Balances::mutate_account(&default_reward_account(), |f| f.free += 40).unwrap(); + + // everyone claims. + assert_ok!(Pools::claim_payout(Origin::signed(10))); + assert_ok!(Pools::claim_payout(Origin::signed(20))); + assert_ok!(Pools::claim_payout(Origin::signed(30))); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, + Event::Bonded { member: 30, pool_id: 1, bonded: 10, joined: true }, + Event::PaidOut { member: 10, pool_id: 1, payout: 10 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 20 }, + Event::PaidOut { member: 30, pool_id: 1, payout: 10 } + ] + ); + + // 30 now bumps itself to be like 20. + assert_ok!(Pools::bond_extra(Origin::signed(30), BondExtra::FreeBalance(10))); + + // more rewards come in. + Balances::mutate_account(&default_reward_account(), |f| f.free += 100).unwrap(); + + assert_ok!(Pools::claim_payout(Origin::signed(10))); + assert_ok!(Pools::claim_payout(Origin::signed(20))); + assert_ok!(Pools::claim_payout(Origin::signed(30))); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Bonded { member: 30, pool_id: 1, bonded: 10, joined: false }, + Event::PaidOut { member: 10, pool_id: 1, payout: 20 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 40 }, + Event::PaidOut { member: 30, pool_id: 1, payout: 40 } + ] + ); + }); + } + + #[test] + fn rewards_distribution_is_fair_unbond() { + ExtBuilder::default().build_and_execute(|| { + let ed = Balances::minimum_balance(); + + Balances::make_free_balance_be(&20, ed + 20); + assert_ok!(Pools::join(Origin::signed(20), 20, 1)); + + Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap(); + + // everyone claims. + assert_ok!(Pools::claim_payout(Origin::signed(10))); + assert_ok!(Pools::claim_payout(Origin::signed(20))); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, + Event::PaidOut { member: 10, pool_id: 1, payout: 10 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 20 } + ] + ); + + // 20 unbonds to be equal to 10 (10 points each). + assert_ok!(Pools::unbond(Origin::signed(20), 20, 10)); + + // more rewards come in. + Balances::mutate_account(&default_reward_account(), |f| f.free += 100).unwrap(); + + assert_ok!(Pools::claim_payout(Origin::signed(10))); + assert_ok!(Pools::claim_payout(Origin::signed(20))); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10 }, + Event::PaidOut { member: 10, pool_id: 1, payout: 50 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 50 }, + ] + ); + }); + } + + #[test] + fn unclaimed_reward_is_safe() { + ExtBuilder::default().build_and_execute(|| { + let ed = Balances::minimum_balance(); + + Balances::make_free_balance_be(&20, ed + 20); + assert_ok!(Pools::join(Origin::signed(20), 20, 1)); + Balances::make_free_balance_be(&30, ed + 20); + assert_ok!(Pools::join(Origin::signed(30), 10, 1)); + + // 10 gets 10, 20 gets 20, 30 gets 10 + Balances::mutate_account(&default_reward_account(), |f| f.free += 40).unwrap(); + + // some claim. + assert_ok!(Pools::claim_payout(Origin::signed(10))); + assert_ok!(Pools::claim_payout(Origin::signed(20))); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, + Event::Bonded { member: 30, pool_id: 1, bonded: 10, joined: true }, + Event::PaidOut { member: 10, pool_id: 1, payout: 10 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 20 } + ] + ); + + // 10 gets 20, 20 gets 40, 30 gets 20 + Balances::mutate_account(&default_reward_account(), |f| f.free += 80).unwrap(); + + // some claim. + assert_ok!(Pools::claim_payout(Origin::signed(10))); + assert_ok!(Pools::claim_payout(Origin::signed(20))); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::PaidOut { member: 10, pool_id: 1, payout: 20 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 40 } + ] + ); + + // 10 gets 20, 20 gets 40, 30 gets 20 + Balances::mutate_account(&default_reward_account(), |f| f.free += 80).unwrap(); + + // some claim. + assert_ok!(Pools::claim_payout(Origin::signed(10))); + assert_ok!(Pools::claim_payout(Origin::signed(20))); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::PaidOut { member: 10, pool_id: 1, payout: 20 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 40 } + ] + ); + + // now 30 claims all at once + assert_ok!(Pools::claim_payout(Origin::signed(30))); + + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 30, pool_id: 1, payout: 10 + 20 + 20 }] + ); + }); + } + + #[test] + fn bond_extra_and_delayed_claim() { + ExtBuilder::default().build_and_execute(|| { + let ed = Balances::minimum_balance(); + + Balances::make_free_balance_be(&20, ed + 200); + assert_ok!(Pools::join(Origin::signed(20), 20, 1)); + + // 10 gets 10, 20 gets 20, 30 gets 10 + Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap(); + + // some claim. + assert_ok!(Pools::claim_payout(Origin::signed(10))); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, + Event::PaidOut { member: 10, pool_id: 1, payout: 10 } + ] + ); + + // 20 has not claimed yet, more reward comes + Balances::mutate_account(&default_reward_account(), |f| f.free += 60).unwrap(); + + // and 20 bonds more -- they should not have more share of this reward. + assert_ok!(Pools::bond_extra(Origin::signed(20), BondExtra::FreeBalance(10))); + + // everyone claim. + assert_ok!(Pools::claim_payout(Origin::signed(10))); + assert_ok!(Pools::claim_payout(Origin::signed(20))); + + assert_eq!( + pool_events_since_last_call(), + vec![ + // 20 + 40, which means the extra amount they bonded did not impact us. + Event::PaidOut { member: 20, pool_id: 1, payout: 60 }, + Event::Bonded { member: 20, pool_id: 1, bonded: 10, joined: false }, + Event::PaidOut { member: 10, pool_id: 1, payout: 20 } + ] + ); + + // but in the next round of rewards, the extra10 they bonded has an impact. + Balances::mutate_account(&default_reward_account(), |f| f.free += 60).unwrap(); + + // everyone claim. + assert_ok!(Pools::claim_payout(Origin::signed(10))); + assert_ok!(Pools::claim_payout(Origin::signed(20))); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::PaidOut { member: 10, pool_id: 1, payout: 15 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 45 } + ] + ); + }); + } + + #[test] + fn create_sets_recorded_data() { + ExtBuilder::default().build_and_execute(|| { + MaxPools::::set(None); + // pool 10 has already been created. + let (member_10, _, reward_pool_10) = Pools::get_member_with_pools(&10).unwrap(); + + assert_eq!(reward_pool_10.last_recorded_total_payouts, 0); + assert_eq!(reward_pool_10.total_rewards_claimed, 0); + assert_eq!(reward_pool_10.last_recorded_reward_counter, 0.into()); + + assert_eq!(member_10.last_recorded_reward_counter, 0.into()); + + // transfer some reward to pool 1. + Balances::mutate_account(&default_reward_account(), |f| f.free += 60).unwrap(); + + // create pool 2 + Balances::make_free_balance_be(&20, 100); + assert_ok!(Pools::create(Origin::signed(20), 10, 20, 20, 20)); + + // has no impact -- initial + let (member_20, _, reward_pool_20) = Pools::get_member_with_pools(&20).unwrap(); + + assert_eq!(reward_pool_20.last_recorded_total_payouts, 0); + assert_eq!(reward_pool_20.total_rewards_claimed, 0); + assert_eq!(reward_pool_20.last_recorded_reward_counter, 0.into()); + + assert_eq!(member_20.last_recorded_reward_counter, 0.into()); + + // pre-fund the reward account of pool id 3 with some funds. + Balances::make_free_balance_be(&Pools::create_reward_account(3), 10); + + // create pool 3 + Balances::make_free_balance_be(&30, 100); + assert_ok!(Pools::create(Origin::signed(30), 10, 30, 30, 30)); + + // reward counter is still the same. + let (member_30, _, reward_pool_30) = Pools::get_member_with_pools(&30).unwrap(); + assert_eq!( + Balances::free_balance(&Pools::create_reward_account(3)), + 10 + Balances::minimum_balance() + ); + + assert_eq!(reward_pool_30.last_recorded_total_payouts, 0); + assert_eq!(reward_pool_30.total_rewards_claimed, 0); + assert_eq!(reward_pool_30.last_recorded_reward_counter, 0.into()); + + assert_eq!(member_30.last_recorded_reward_counter, 0.into()); + + // and 30 can claim the reward now. + assert_ok!(Pools::claim_payout(Origin::signed(30))); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Created { depositor: 20, pool_id: 2 }, + Event::Bonded { member: 20, pool_id: 2, bonded: 10, joined: true }, + Event::Created { depositor: 30, pool_id: 3 }, + Event::Bonded { member: 30, pool_id: 3, bonded: 10, joined: true }, + Event::PaidOut { member: 30, pool_id: 3, payout: 10 } + ] + ); + }) + } + + #[test] + fn join_updates_recorded_data() { + ExtBuilder::default().build_and_execute(|| { + MaxPoolMembers::::set(None); + MaxPoolMembersPerPool::::set(None); + let join = |x, y| { + Balances::make_free_balance_be(&x, y + Balances::minimum_balance()); + assert_ok!(Pools::join(Origin::signed(x), y, 1)); + }; + + { + let (member_10, _, reward_pool_10) = Pools::get_member_with_pools(&10).unwrap(); + + assert_eq!(reward_pool_10.last_recorded_total_payouts, 0); + assert_eq!(reward_pool_10.total_rewards_claimed, 0); + assert_eq!(reward_pool_10.last_recorded_reward_counter, 0.into()); + + assert_eq!(member_10.last_recorded_reward_counter, 0.into()); + } + + // someone joins without any rewards being issued. + { + join(20, 10); + let (member, _, reward_pool) = Pools::get_member_with_pools(&20).unwrap(); + // reward counter is 0 both before.. + assert_eq!(member.last_recorded_reward_counter, 0.into()); + assert_eq!(reward_pool.last_recorded_total_payouts, 0); + assert_eq!(reward_pool.last_recorded_reward_counter, 0.into()); + } + + // transfer some reward to pool 1. + Balances::mutate_account(&default_reward_account(), |f| f.free += 60).unwrap(); + + { + join(30, 10); + let (member, _, reward_pool) = Pools::get_member_with_pools(&30).unwrap(); + assert_eq!(reward_pool.last_recorded_total_payouts, 60); + // explanation: we have a total of 20 points so far (excluding the 10 that just got + // bonded), and 60 unclaimed rewards. each share is then roughly worth of 3 units of + // rewards, thus reward counter is 3. member's reward counter is the same + assert_eq!(member.last_recorded_reward_counter, 3.into()); + assert_eq!(reward_pool.last_recorded_reward_counter, 3.into()); + } + + // someone else joins + { + join(40, 10); + let (member, _, reward_pool) = Pools::get_member_with_pools(&40).unwrap(); + // reward counter does not change since no rewards have came in. + assert_eq!(member.last_recorded_reward_counter, 3.into()); + assert_eq!(reward_pool.last_recorded_reward_counter, 3.into()); + assert_eq!(reward_pool.last_recorded_total_payouts, 60); + } + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 30, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 40, pool_id: 1, bonded: 10, joined: true } + ] + ); + }) + } + + #[test] + fn bond_extra_updates_recorded_data() { + ExtBuilder::default().add_members(vec![(20, 20)]).build_and_execute(|| { + MaxPoolMembers::::set(None); + MaxPoolMembersPerPool::::set(None); + + // initial state of pool 1. + { + let (member_10, _, reward_pool_10) = Pools::get_member_with_pools(&10).unwrap(); + + assert_eq!(reward_pool_10.last_recorded_total_payouts, 0); + assert_eq!(reward_pool_10.total_rewards_claimed, 0); + assert_eq!(reward_pool_10.last_recorded_reward_counter, 0.into()); + + assert_eq!(member_10.last_recorded_reward_counter, 0.into()); + } + + Balances::make_free_balance_be(&10, 100); + Balances::make_free_balance_be(&20, 100); + + // 10 bonds extra without any rewards. + { + assert_ok!(Pools::bond_extra(Origin::signed(10), BondExtra::FreeBalance(10))); + let (member, _, reward_pool) = Pools::get_member_with_pools(&10).unwrap(); + assert_eq!(member.last_recorded_reward_counter, 0.into()); + assert_eq!(reward_pool.last_recorded_total_payouts, 0); + assert_eq!(reward_pool.last_recorded_reward_counter, 0.into()); + } + + // 10 bonds extra again with some rewards. This reward should be split equally between + // 10 and 20, as they both have equal points now. + Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap(); + + { + assert_ok!(Pools::bond_extra(Origin::signed(10), BondExtra::FreeBalance(10))); + let (member, _, reward_pool) = Pools::get_member_with_pools(&10).unwrap(); + // explanation: before bond_extra takes place, there is 40 points and 30 balance in + // the system, RewardCounter is therefore 7.5 + assert_eq!(member.last_recorded_reward_counter, RewardCounter::from_float(0.75)); assert_eq!( - pool_events_since_last_call(), - vec![Event::PaidOut { member: 50, pool_id: 1, payout: 50 }] + reward_pool.last_recorded_reward_counter, + RewardCounter::from_float(0.75) ); + assert_eq!(reward_pool.last_recorded_total_payouts, 30); + } - // We expect a payout of 50: (5,000 del virtual points / 7,5000 pool points) * 75 - // pool balance - assert_eq!(del_50, del(50, 200)); + // 20 bonds extra again, without further rewards. + { + assert_ok!(Pools::bond_extra(Origin::signed(20), BondExtra::FreeBalance(10))); + let (member, _, reward_pool) = Pools::get_member_with_pools(&20).unwrap(); + assert_eq!(member.last_recorded_reward_counter, RewardCounter::from_float(0.75)); assert_eq!( - reward_pool, - rew( - 25, - // old pool points + points from new earnings - del points. - // - // points from new earnings = new earnings(50) * bonded_pool.points(100) - // del points = member.points(50) * new_earnings_since_last_claim (100) - (2_500 + 50 * 100) - 50 * 100, - 200, - ) + reward_pool.last_recorded_reward_counter, + RewardCounter::from_float(0.75) ); - assert_eq!(Balances::free_balance(&50), 50 + 50); - assert_eq!(Balances::free_balance(&default_reward_account()), ed + 25); + assert_eq!(reward_pool.last_recorded_total_payouts, 30); + } + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: false }, + Event::PaidOut { member: 10, pool_id: 1, payout: 15 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: false }, + Event::PaidOut { member: 20, pool_id: 1, payout: 15 }, + Event::Bonded { member: 20, pool_id: 1, bonded: 10, joined: false } + ] + ); + }) + } + + #[test] + fn unbond_updates_recorded_data() { + ExtBuilder::default() + .add_members(vec![(20, 20), (30, 20)]) + .build_and_execute(|| { + MaxPoolMembers::::set(None); + MaxPoolMembersPerPool::::set(None); + + // initial state of pool 1. + { + let (member, _, reward_pool) = Pools::get_member_with_pools(&10).unwrap(); + + assert_eq!(reward_pool.last_recorded_total_payouts, 0); + assert_eq!(reward_pool.total_rewards_claimed, 0); + assert_eq!(reward_pool.last_recorded_reward_counter, 0.into()); + + assert_eq!(member.last_recorded_reward_counter, 0.into()); + } + + // 20 unbonds without any rewards. + { + assert_ok!(Pools::unbond(Origin::signed(20), 20, 10)); + let (member, _, reward_pool) = Pools::get_member_with_pools(&20).unwrap(); + assert_eq!(member.last_recorded_reward_counter, 0.into()); + assert_eq!(reward_pool.last_recorded_total_payouts, 0); + assert_eq!(reward_pool.last_recorded_reward_counter, 0.into()); + } - // When - assert_ok!(Pools::do_reward_payout( - &10, - &mut del_10, - &mut bonded_pool, - &mut reward_pool - )); + // some rewards come in. + Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap(); - // Then - assert_eq!( - pool_events_since_last_call(), - vec![Event::PaidOut { member: 10, pool_id: 1, payout: 5 }] - ); + // and 30 also unbonds half. + { + assert_ok!(Pools::unbond(Origin::signed(30), 30, 10)); + let (member, _, reward_pool) = Pools::get_member_with_pools(&30).unwrap(); + // 30 reward in the system, and 40 points before this unbond to collect it, + // RewardCounter is 3/4. + assert_eq!( + member.last_recorded_reward_counter, + RewardCounter::from_float(0.75) + ); + assert_eq!(reward_pool.last_recorded_total_payouts, 30); + assert_eq!( + reward_pool.last_recorded_reward_counter, + RewardCounter::from_float(0.75) + ); + } - // We expect a payout of 5 - assert_eq!(del_10, del(10, 200)); - assert_eq!(reward_pool, rew(20, 2_500 - 10 * 50, 200)); - assert_eq!(Balances::free_balance(&10), 15 + 5); - assert_eq!(Balances::free_balance(&default_reward_account()), ed + 20); + // 30 unbonds again, not change this time. + { + assert_ok!(Pools::unbond(Origin::signed(30), 30, 5)); + let (member, _, reward_pool) = Pools::get_member_with_pools(&30).unwrap(); + assert_eq!( + member.last_recorded_reward_counter, + RewardCounter::from_float(0.75) + ); + assert_eq!(reward_pool.last_recorded_total_payouts, 30); + assert_eq!( + reward_pool.last_recorded_reward_counter, + RewardCounter::from_float(0.75) + ); + } - // Given del 40 hasn't claimed and the reward pool has just earned 400 - assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 400)); - assert_eq!(Balances::free_balance(&default_reward_account()), ed + 420); + // 20 unbonds again, not change this time, just collecting their reward. + { + assert_ok!(Pools::unbond(Origin::signed(20), 20, 5)); + let (member, _, reward_pool) = Pools::get_member_with_pools(&20).unwrap(); + assert_eq!( + member.last_recorded_reward_counter, + RewardCounter::from_float(0.75) + ); + assert_eq!(reward_pool.last_recorded_total_payouts, 30); + assert_eq!( + reward_pool.last_recorded_reward_counter, + RewardCounter::from_float(0.75) + ); + } - // When - assert_ok!(Pools::do_reward_payout( - &10, - &mut del_10, - &mut bonded_pool, - &mut reward_pool - )); + // trigger 10's reward as well to see all of the payouts. + assert_ok!(Pools::claim_payout(Origin::signed(10))); - // Then assert_eq!( pool_events_since_last_call(), - vec![Event::PaidOut { member: 10, pool_id: 1, payout: 40 }] + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, + Event::Bonded { member: 30, pool_id: 1, bonded: 20, joined: true }, + Event::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10 }, + Event::PaidOut { member: 30, pool_id: 1, payout: 15 }, + Event::Unbonded { member: 30, pool_id: 1, balance: 10, points: 10 }, + Event::Unbonded { member: 30, pool_id: 1, balance: 5, points: 5 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 7 }, + Event::Unbonded { member: 20, pool_id: 1, balance: 5, points: 5 }, + Event::PaidOut { member: 10, pool_id: 1, payout: 7 } + ] ); + }) + } - // We expect a payout of 40 - assert_eq!(del_10, del(10, 600)); - assert_eq!( - reward_pool, - rew( - 380, - // old pool points + points from new earnings - del points - // - // points from new earnings = new earnings(400) * bonded_pool.points(100) - // del points = member.points(10) * new_earnings_since_last_claim(400) - (2_000 + 400 * 100) - 10 * 400, - 600 - ) - ); - assert_eq!(Balances::free_balance(&10), 20 + 40); - assert_eq!(Balances::free_balance(&default_reward_account()), ed + 380); + #[test] + fn rewards_are_rounded_down_depositor_collects_them() { + ExtBuilder::default().add_members(vec![(20, 20)]).build_and_execute(|| { + // initial balance of 10. + assert_eq!(Balances::free_balance(&10), 5); + assert_eq!( + Balances::free_balance(&default_reward_account()), + Balances::minimum_balance() + ); - // Given del 40 + del 50 haven't claimed and the reward pool has earned 20 - assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 20)); - assert_eq!(Balances::free_balance(&default_reward_account()), ed + 400); + // some rewards come in. + Balances::mutate_account(&default_reward_account(), |f| f.free += 40).unwrap(); - // When - assert_ok!(Pools::do_reward_payout( - &10, - &mut del_10, - &mut bonded_pool, - &mut reward_pool - )); + // everyone claims + assert_ok!(Pools::claim_payout(Origin::signed(10))); + assert_ok!(Pools::claim_payout(Origin::signed(20))); - // Then - assert_eq!( - pool_events_since_last_call(), - vec![Event::PaidOut { member: 10, pool_id: 1, payout: 2 }] - ); + // some dust (1) remains in the reward account. + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, + Event::PaidOut { member: 10, pool_id: 1, payout: 13 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 26 } + ] + ); - // Expect a payout of 2: (200 del virtual points / 38,000 pool points) * 400 pool - // balance - assert_eq!(del_10, del(10, 620)); - assert_eq!(reward_pool, rew(398, (38_000 + 20 * 100) - 10 * 20, 620)); - assert_eq!(Balances::free_balance(&10), 60 + 2); - assert_eq!(Balances::free_balance(&default_reward_account()), ed + 398); + // start dismantling the pool. + assert_ok!(Pools::set_state(Origin::signed(902), 1, PoolState::Destroying)); + assert_ok!(fully_unbond_permissioned(20)); - // When - assert_ok!(Pools::do_reward_payout( - &40, - &mut del_40, - &mut bonded_pool, - &mut reward_pool - )); + CurrentEra::set(3); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(20), 20, 0)); + assert_ok!(fully_unbond_permissioned(10)); - // Then - assert_eq!( - pool_events_since_last_call(), - vec![Event::PaidOut { member: 40, pool_id: 1, payout: 188 }] - ); + CurrentEra::set(6); + assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 0)); - // Expect a payout of 188: (18,800 del virtual points / 39,800 pool points) * 399 - // pool balance - assert_eq!(del_40, del(40, 620)); - assert_eq!(reward_pool, rew(210, 39_800 - 40 * 470, 620)); - assert_eq!(Balances::free_balance(&40), 60 + 188); - assert_eq!(Balances::free_balance(&default_reward_account()), ed + 210); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::StateChanged { pool_id: 1, new_state: PoolState::Destroying }, + Event::Unbonded { member: 20, pool_id: 1, balance: 20, points: 20 }, + Event::Withdrawn { member: 20, pool_id: 1, balance: 20, points: 20 }, + Event::MemberRemoved { pool_id: 1, member: 20 }, + Event::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10 }, + Event::Withdrawn { member: 10, pool_id: 1, balance: 10, points: 10 }, + Event::MemberRemoved { pool_id: 1, member: 10 }, + Event::Destroyed { pool_id: 1 } + ] + ); - // When - assert_ok!(Pools::do_reward_payout( - &50, - &mut del_50, - &mut bonded_pool, - &mut reward_pool - )); + // original ed + ed put into reward account + reward + bond + dust. + assert_eq!(Balances::free_balance(&10), 5 + 5 + 13 + 10 + 1); + }) + } + + #[test] + fn claim_payout_large_numbers() { + let unit = 10u128.pow(12); // akin to KSM + ExistentialDeposit::set(unit); + StakingMinBond::set(unit * 1000); + + ExtBuilder::default() + .max_members(Some(4)) + .max_members_per_pool(Some(4)) + .add_members(vec![(20, 1500 * unit), (21, 2500 * unit), (22, 5000 * unit)]) + .build_and_execute(|| { + // some rewards come in. + assert_eq!(Balances::free_balance(&default_reward_account()), unit); + Balances::mutate_account(&default_reward_account(), |f| f.free += unit / 1000) + .unwrap(); + + // everyone claims + assert_ok!(Pools::claim_payout(Origin::signed(10))); + assert_ok!(Pools::claim_payout(Origin::signed(20))); + assert_ok!(Pools::claim_payout(Origin::signed(21))); + assert_ok!(Pools::claim_payout(Origin::signed(22))); - // Then assert_eq!( pool_events_since_last_call(), - vec![Event::PaidOut { member: 50, pool_id: 1, payout: 210 }] + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { + member: 10, + pool_id: 1, + bonded: 1000000000000000, + joined: true + }, + Event::Bonded { + member: 20, + pool_id: 1, + bonded: 1500000000000000, + joined: true + }, + Event::Bonded { + member: 21, + pool_id: 1, + bonded: 2500000000000000, + joined: true + }, + Event::Bonded { + member: 22, + pool_id: 1, + bonded: 5000000000000000, + joined: true + }, + Event::PaidOut { member: 10, pool_id: 1, payout: 100000000 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 150000000 }, + Event::PaidOut { member: 21, pool_id: 1, payout: 250000000 }, + Event::PaidOut { member: 22, pool_id: 1, payout: 500000000 } + ] ); - - // Expect payout of 210: (21,000 / 21,000) * 210 - assert_eq!(del_50, del(50, 620)); - assert_eq!(reward_pool, rew(0, 21_000 - 50 * 420, 620)); - assert_eq!(Balances::free_balance(&50), 100 + 210); - assert_eq!(Balances::free_balance(&default_reward_account()), ed + 0); - }); + }) } } @@ -2461,7 +2846,8 @@ mod withdraw_unbonded { assert_eq!( SubPoolsStorage::::get(&1).unwrap().with_era, - unbonding_pools_with_era! { 3 => UnbondPool { points: 550 / 2 + 40 / 2, balance: 550 / 2 + 40 / 2 }} + unbonding_pools_with_era! { 3 => UnbondPool { points: 550 / 2 + 40 / 2, balance: 550 / 2 + 40 / 2 + }} ); assert_eq!( @@ -3089,11 +3475,7 @@ mod create { ); assert_eq!( RewardPools::::get(2).unwrap(), - RewardPool { - balance: Zero::zero(), - points: U256::zero(), - total_earnings: Zero::zero(), - } + RewardPool { ..Default::default() } ); assert_eq!( @@ -3688,3 +4070,310 @@ mod update_roles { }) } } + +mod reward_counter_precision { + use sp_runtime::FixedU128; + + use super::*; + + const DOT: Balance = 10u128.pow(10u32); + const POLKADOT_TOTAL_ISSUANCE_GENESIS: Balance = DOT * 10u128.pow(9u32); + + const fn inflation(years: u128) -> u128 { + let mut i = 0; + let mut start = POLKADOT_TOTAL_ISSUANCE_GENESIS; + while i < years { + start = start + start / 10; + i += 1 + } + start + } + + fn default_pool_reward_counter() -> FixedU128 { + RewardPools::::get(1) + .unwrap() + .current_reward_counter(1, BondedPools::::get(1).unwrap().points) + .unwrap() + } + + fn pending_rewards(of: AccountId) -> Option> { + let member = PoolMembers::::get(of).unwrap(); + assert_eq!(member.pool_id, 1); + let rc = default_pool_reward_counter(); + member.pending_rewards(rc).ok() + } + + #[test] + fn smallest_claimable_reward() { + // create a pool that has all of the polkadot issuance in 50 years. + let pool_bond = inflation(50); + ExtBuilder::default().ed(DOT).min_bond(pool_bond).build_and_execute(|| { + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { + member: 10, + pool_id: 1, + bonded: 1173908528796953165005, + joined: true, + } + ] + ); + + // the smallest reward that this pool can handle is + let expected_smallest_reward = inflation(50) / 10u128.pow(18); + + // tad bit less. cannot be paid out. + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += + expected_smallest_reward - 1)); + assert_ok!(Pools::claim_payout(Origin::signed(10))); + assert_eq!(pool_events_since_last_call(), vec![]); + // revert it. + + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= + expected_smallest_reward - 1)); + + // tad bit more. can be claimed. + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += + expected_smallest_reward + 1)); + assert_ok!(Pools::claim_payout(Origin::signed(10))); + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 1173 }] + ); + }) + } + + #[test] + fn reward_counter_calc_wont_fail_in_normal_polkadot_future() { + // create a pool that has roughly half of the polkadot issuance in 10 years. + let pool_bond = inflation(10) / 2; + ExtBuilder::default().ed(DOT).min_bond(pool_bond).build_and_execute(|| { + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { + member: 10, + pool_id: 1, + bonded: 12_968_712_300_500_000_000, + joined: true, + } + ] + ); + + // in 10 years, the total claimed rewards are large values as well. assuming that a pool + // is earning all of the inflation per year (which is really unrealistic, but worse + // case), that will be: + let pool_total_earnings_10_years = inflation(10) - POLKADOT_TOTAL_ISSUANCE_GENESIS; + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += + pool_total_earnings_10_years)); + + // some whale now joins with the other half ot the total issuance. This will bloat all + // the calculation regarding current reward counter. + Balances::make_free_balance_be(&20, pool_bond * 2); + assert_ok!(Pools::join(Origin::signed(20), pool_bond, 1)); + + assert_eq!( + pool_events_since_last_call(), + vec![Event::Bonded { + member: 20, + pool_id: 1, + bonded: 12_968_712_300_500_000_000, + joined: true + }] + ); + + assert_ok!(Pools::claim_payout(Origin::signed(10))); + assert_ok!(Pools::claim_payout(Origin::signed(20))); + + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 15937424600999999996 }] + ); + + // now let a small member join with 10 DOTs. + Balances::make_free_balance_be(&30, 20 * DOT); + assert_ok!(Pools::join(Origin::signed(30), 10 * DOT, 1)); + + // and give a reasonably small reward to the pool. + assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += DOT)); + + assert_ok!(Pools::claim_payout(Origin::signed(30))); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Bonded { member: 30, pool_id: 1, bonded: 100000000000, joined: true }, + // quite small, but working fine. + Event::PaidOut { member: 30, pool_id: 1, payout: 38 } + ] + ); + }) + } + + #[test] + fn reward_counter_update_can_fail_if_pool_is_highly_slashed() { + // create a pool that has roughly half of the polkadot issuance in 10 years. + let pool_bond = inflation(10) / 2; + ExtBuilder::default().ed(DOT).min_bond(pool_bond).build_and_execute(|| { + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { + member: 10, + pool_id: 1, + bonded: 12_968_712_300_500_000_000, + joined: true, + } + ] + ); + + // slash this pool by 99% of that. + StakingMock::set_bonded_balance(default_bonded_account(), DOT + pool_bond / 100); + + // some whale now joins with the other half ot the total issuance. This will trigger an + // overflow. This test is actually a bit too lenient because all the reward counters are + // set to zero. In other tests that we want to assert a scenario won't fail, we should + // also set the reward counters to some large value. + Balances::make_free_balance_be(&20, pool_bond * 2); + assert_err!(Pools::join(Origin::signed(20), pool_bond, 1), Error::::OverflowRisk); + }) + } + + #[test] + fn if_small_member_waits_long_enough_they_will_earn_rewards() { + // create a pool that has a quarter of the current polkadot issuance + ExtBuilder::default() + .ed(DOT) + .min_bond(POLKADOT_TOTAL_ISSUANCE_GENESIS / 4) + .build_and_execute(|| { + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { + member: 10, + pool_id: 1, + bonded: 2500000000000000000, + joined: true, + } + ] + ); + + // and have a tiny fish join the pool as well.. + Balances::make_free_balance_be(&20, 20 * DOT); + assert_ok!(Pools::join(Origin::signed(20), 10 * DOT, 1)); + + // earn some small rewards + assert_ok!( + Balances::mutate_account(&default_reward_account(), |a| a.free += DOT / 1000) + ); + + // no point in claiming for 20 (nonetheless, it should be harmless) + assert!(pending_rewards(20).unwrap().is_zero()); + assert_ok!(Pools::claim_payout(Origin::signed(10))); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Bonded { + member: 20, + pool_id: 1, + bonded: 100000000000, + joined: true + }, + Event::PaidOut { member: 10, pool_id: 1, payout: 9999997 } + ] + ); + + // earn some small more, still nothing can be claimed for 20, but 10 claims their + // share. + assert_ok!( + Balances::mutate_account(&default_reward_account(), |a| a.free += DOT / 1000) + ); + assert!(pending_rewards(20).unwrap().is_zero()); + assert_ok!(Pools::claim_payout(Origin::signed(10))); + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 10000000 }] + ); + + // earn some more rewards, this time 20 can also claim. + assert_ok!( + Balances::mutate_account(&default_reward_account(), |a| a.free += DOT / 1000) + ); + assert_eq!(pending_rewards(20).unwrap(), 1); + assert_ok!(Pools::claim_payout(Origin::signed(10))); + assert_ok!(Pools::claim_payout(Origin::signed(20))); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::PaidOut { member: 10, pool_id: 1, payout: 10000000 }, + Event::PaidOut { member: 20, pool_id: 1, payout: 1 } + ] + ); + }); + } + + #[test] + fn zero_reward_claim_does_not_update_reward_counter() { + // create a pool that has a quarter of the current polkadot issuance + ExtBuilder::default() + .ed(DOT) + .min_bond(POLKADOT_TOTAL_ISSUANCE_GENESIS / 4) + .build_and_execute(|| { + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { + member: 10, + pool_id: 1, + bonded: 2500000000000000000, + joined: true, + } + ] + ); + + // and have a tiny fish join the pool as well.. + Balances::make_free_balance_be(&20, 20 * DOT); + assert_ok!(Pools::join(Origin::signed(20), 10 * DOT, 1)); + + // earn some small rewards + assert_ok!( + Balances::mutate_account(&default_reward_account(), |a| a.free += DOT / 1000) + ); + + // if 20 claims now, their reward counter should stay the same, so that they have a + // chance of claiming this if they let it accumulate. Also see + // `if_small_member_waits_long_enough_they_will_earn_rewards` + assert_ok!(Pools::claim_payout(Origin::signed(10))); + assert_ok!(Pools::claim_payout(Origin::signed(20))); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Bonded { + member: 20, + pool_id: 1, + bonded: 100000000000, + joined: true + }, + Event::PaidOut { member: 10, pool_id: 1, payout: 9999997 } + ] + ); + + let current_reward_counter = default_pool_reward_counter(); + // has been updated, because they actually claimed something. + assert_eq!( + PoolMembers::::get(10).unwrap().last_recorded_reward_counter, + current_reward_counter + ); + // has not be updated, even though the claim transaction went through okay. + assert_eq!( + PoolMembers::::get(20).unwrap().last_recorded_reward_counter, + Default::default() + ); + }); + } +} diff --git a/frame/nomination-pools/src/weights.rs b/frame/nomination-pools/src/weights.rs index 8e3facfc5ec26..a9003ffd3fb4c 100644 --- a/frame/nomination-pools/src/weights.rs +++ b/frame/nomination-pools/src/weights.rs @@ -18,7 +18,7 @@ //! Autogenerated weights for pallet_nomination_pools //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-06-10, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-06-15, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 @@ -70,7 +70,7 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools PoolMembers (r:1 w:1) // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Ledger (r:1 w:1) - // Storage: NominationPools RewardPools (r:1 w:0) + // Storage: NominationPools RewardPools (r:1 w:1) // Storage: System Account (r:2 w:1) // Storage: NominationPools MaxPoolMembersPerPool (r:1 w:0) // Storage: NominationPools MaxPoolMembers (r:1 w:0) @@ -80,22 +80,22 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn join() -> Weight { - (124_508_000 as Weight) + (123_947_000 as Weight) .saturating_add(T::DbWeight::get().reads(17 as Weight)) - .saturating_add(T::DbWeight::get().writes(11 as Weight)) + .saturating_add(T::DbWeight::get().writes(12 as Weight)) } // Storage: NominationPools PoolMembers (r:1 w:1) // Storage: NominationPools BondedPools (r:1 w:1) // Storage: NominationPools RewardPools (r:1 w:1) - // Storage: System Account (r:2 w:2) + // Storage: System Account (r:3 w:2) // Storage: Staking Ledger (r:1 w:1) // Storage: Staking Bonded (r:1 w:0) // Storage: Balances Locks (r:1 w:1) // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn bond_extra_transfer() -> Weight { - (115_185_000 as Weight) - .saturating_add(T::DbWeight::get().reads(13 as Weight)) + (118_236_000 as Weight) + .saturating_add(T::DbWeight::get().reads(14 as Weight)) .saturating_add(T::DbWeight::get().writes(12 as Weight)) } // Storage: NominationPools PoolMembers (r:1 w:1) @@ -108,7 +108,7 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn bond_extra_reward() -> Weight { - (132_723_000 as Weight) + (132_475_000 as Weight) .saturating_add(T::DbWeight::get().reads(14 as Weight)) .saturating_add(T::DbWeight::get().writes(13 as Weight)) } @@ -117,7 +117,7 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools RewardPools (r:1 w:1) // Storage: System Account (r:1 w:1) fn claim_payout() -> Weight { - (52_498_000 as Weight) + (50_299_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -136,7 +136,7 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools SubPoolsStorage (r:1 w:1) // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) fn unbond() -> Weight { - (121_645_000 as Weight) + (121_254_000 as Weight) .saturating_add(T::DbWeight::get().reads(18 as Weight)) .saturating_add(T::DbWeight::get().writes(13 as Weight)) } @@ -146,9 +146,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Balances Locks (r:1 w:1) /// The range of component `s` is `[0, 100]`. fn pool_withdraw_unbonded(s: u32, ) -> Weight { - (43_320_000 as Weight) + (41_928_000 as Weight) // Standard Error: 0 - .saturating_add((49_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((52_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -162,9 +162,9 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools CounterForPoolMembers (r:1 w:1) /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_update(s: u32, ) -> Weight { - (83_195_000 as Weight) - // Standard Error: 5_000 - .saturating_add((57_000 as Weight).saturating_mul(s as Weight)) + (81_611_000 as Weight) + // Standard Error: 1_000 + .saturating_add((56_000 as Weight).saturating_mul(s as Weight)) .saturating_add(T::DbWeight::get().reads(8 as Weight)) .saturating_add(T::DbWeight::get().writes(7 as Weight)) } @@ -189,7 +189,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking Payee (r:0 w:1) /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_kill(_s: u32, ) -> Weight { - (143_495_000 as Weight) + (139_849_000 as Weight) .saturating_add(T::DbWeight::get().reads(19 as Weight)) .saturating_add(T::DbWeight::get().writes(16 as Weight)) } @@ -216,7 +216,7 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn create() -> Weight { - (127_998_000 as Weight) + (126_246_000 as Weight) .saturating_add(T::DbWeight::get().reads(22 as Weight)) .saturating_add(T::DbWeight::get().writes(15 as Weight)) } @@ -234,9 +234,9 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking CounterForNominators (r:1 w:1) /// The range of component `n` is `[1, 16]`. fn nominate(n: u32, ) -> Weight { - (49_929_000 as Weight) - // Standard Error: 16_000 - .saturating_add((2_319_000 as Weight).saturating_mul(n as Weight)) + (48_829_000 as Weight) + // Standard Error: 10_000 + .saturating_add((2_204_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(12 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) .saturating_add(T::DbWeight::get().writes(5 as Weight)) @@ -244,7 +244,7 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Ledger (r:1 w:0) fn set_state() -> Weight { - (27_399_000 as Weight) + (26_761_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -253,7 +253,7 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools CounterForMetadata (r:1 w:1) /// The range of component `n` is `[1, 256]`. fn set_metadata(n: u32, ) -> Weight { - (14_813_000 as Weight) + (14_519_000 as Weight) // Standard Error: 0 .saturating_add((1_000 as Weight).saturating_mul(n as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) @@ -265,12 +265,12 @@ impl WeightInfo for SubstrateWeight { // Storage: NominationPools MinCreateBond (r:0 w:1) // Storage: NominationPools MaxPools (r:0 w:1) fn set_configs() -> Weight { - (6_115_000 as Weight) + (6_173_000 as Weight) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } // Storage: NominationPools BondedPools (r:1 w:1) fn update_roles() -> Weight { - (22_546_000 as Weight) + (22_261_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -283,7 +283,7 @@ impl WeightInfo for SubstrateWeight { // Storage: BagsList ListBags (r:1 w:1) // Storage: BagsList CounterForListNodes (r:1 w:1) fn chill() -> Weight { - (48_243_000 as Weight) + (47_959_000 as Weight) .saturating_add(T::DbWeight::get().reads(8 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } @@ -295,7 +295,7 @@ impl WeightInfo for () { // Storage: NominationPools PoolMembers (r:1 w:1) // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Ledger (r:1 w:1) - // Storage: NominationPools RewardPools (r:1 w:0) + // Storage: NominationPools RewardPools (r:1 w:1) // Storage: System Account (r:2 w:1) // Storage: NominationPools MaxPoolMembersPerPool (r:1 w:0) // Storage: NominationPools MaxPoolMembers (r:1 w:0) @@ -305,22 +305,22 @@ impl WeightInfo for () { // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn join() -> Weight { - (124_508_000 as Weight) + (123_947_000 as Weight) .saturating_add(RocksDbWeight::get().reads(17 as Weight)) - .saturating_add(RocksDbWeight::get().writes(11 as Weight)) + .saturating_add(RocksDbWeight::get().writes(12 as Weight)) } // Storage: NominationPools PoolMembers (r:1 w:1) // Storage: NominationPools BondedPools (r:1 w:1) // Storage: NominationPools RewardPools (r:1 w:1) - // Storage: System Account (r:2 w:2) + // Storage: System Account (r:3 w:2) // Storage: Staking Ledger (r:1 w:1) // Storage: Staking Bonded (r:1 w:0) // Storage: Balances Locks (r:1 w:1) // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn bond_extra_transfer() -> Weight { - (115_185_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(13 as Weight)) + (118_236_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(14 as Weight)) .saturating_add(RocksDbWeight::get().writes(12 as Weight)) } // Storage: NominationPools PoolMembers (r:1 w:1) @@ -333,7 +333,7 @@ impl WeightInfo for () { // Storage: BagsList ListNodes (r:3 w:3) // Storage: BagsList ListBags (r:2 w:2) fn bond_extra_reward() -> Weight { - (132_723_000 as Weight) + (132_475_000 as Weight) .saturating_add(RocksDbWeight::get().reads(14 as Weight)) .saturating_add(RocksDbWeight::get().writes(13 as Weight)) } @@ -342,7 +342,7 @@ impl WeightInfo for () { // Storage: NominationPools RewardPools (r:1 w:1) // Storage: System Account (r:1 w:1) fn claim_payout() -> Weight { - (52_498_000 as Weight) + (50_299_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } @@ -361,7 +361,7 @@ impl WeightInfo for () { // Storage: NominationPools SubPoolsStorage (r:1 w:1) // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) fn unbond() -> Weight { - (121_645_000 as Weight) + (121_254_000 as Weight) .saturating_add(RocksDbWeight::get().reads(18 as Weight)) .saturating_add(RocksDbWeight::get().writes(13 as Weight)) } @@ -371,9 +371,9 @@ impl WeightInfo for () { // Storage: Balances Locks (r:1 w:1) /// The range of component `s` is `[0, 100]`. fn pool_withdraw_unbonded(s: u32, ) -> Weight { - (43_320_000 as Weight) + (41_928_000 as Weight) // Standard Error: 0 - .saturating_add((49_000 as Weight).saturating_mul(s as Weight)) + .saturating_add((52_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -387,9 +387,9 @@ impl WeightInfo for () { // Storage: NominationPools CounterForPoolMembers (r:1 w:1) /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_update(s: u32, ) -> Weight { - (83_195_000 as Weight) - // Standard Error: 5_000 - .saturating_add((57_000 as Weight).saturating_mul(s as Weight)) + (81_611_000 as Weight) + // Standard Error: 1_000 + .saturating_add((56_000 as Weight).saturating_mul(s as Weight)) .saturating_add(RocksDbWeight::get().reads(8 as Weight)) .saturating_add(RocksDbWeight::get().writes(7 as Weight)) } @@ -414,7 +414,7 @@ impl WeightInfo for () { // Storage: Staking Payee (r:0 w:1) /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_kill(_s: u32, ) -> Weight { - (143_495_000 as Weight) + (139_849_000 as Weight) .saturating_add(RocksDbWeight::get().reads(19 as Weight)) .saturating_add(RocksDbWeight::get().writes(16 as Weight)) } @@ -441,7 +441,7 @@ impl WeightInfo for () { // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Payee (r:0 w:1) fn create() -> Weight { - (127_998_000 as Weight) + (126_246_000 as Weight) .saturating_add(RocksDbWeight::get().reads(22 as Weight)) .saturating_add(RocksDbWeight::get().writes(15 as Weight)) } @@ -459,9 +459,9 @@ impl WeightInfo for () { // Storage: Staking CounterForNominators (r:1 w:1) /// The range of component `n` is `[1, 16]`. fn nominate(n: u32, ) -> Weight { - (49_929_000 as Weight) - // Standard Error: 16_000 - .saturating_add((2_319_000 as Weight).saturating_mul(n as Weight)) + (48_829_000 as Weight) + // Standard Error: 10_000 + .saturating_add((2_204_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(12 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) @@ -469,7 +469,7 @@ impl WeightInfo for () { // Storage: NominationPools BondedPools (r:1 w:1) // Storage: Staking Ledger (r:1 w:0) fn set_state() -> Weight { - (27_399_000 as Weight) + (26_761_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -478,7 +478,7 @@ impl WeightInfo for () { // Storage: NominationPools CounterForMetadata (r:1 w:1) /// The range of component `n` is `[1, 256]`. fn set_metadata(n: u32, ) -> Weight { - (14_813_000 as Weight) + (14_519_000 as Weight) // Standard Error: 0 .saturating_add((1_000 as Weight).saturating_mul(n as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) @@ -490,12 +490,12 @@ impl WeightInfo for () { // Storage: NominationPools MinCreateBond (r:0 w:1) // Storage: NominationPools MaxPools (r:0 w:1) fn set_configs() -> Weight { - (6_115_000 as Weight) + (6_173_000 as Weight) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } // Storage: NominationPools BondedPools (r:1 w:1) fn update_roles() -> Weight { - (22_546_000 as Weight) + (22_261_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -508,7 +508,7 @@ impl WeightInfo for () { // Storage: BagsList ListBags (r:1 w:1) // Storage: BagsList CounterForListNodes (r:1 w:1) fn chill() -> Weight { - (48_243_000 as Weight) + (47_959_000 as Weight) .saturating_add(RocksDbWeight::get().reads(8 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } diff --git a/frame/nomination-pools/test-staking/src/lib.rs b/frame/nomination-pools/test-staking/src/lib.rs index be7752ce113b4..3b9350f25040b 100644 --- a/frame/nomination-pools/test-staking/src/lib.rs +++ b/frame/nomination-pools/test-staking/src/lib.rs @@ -26,6 +26,7 @@ use pallet_nomination_pools::{ PoolState, }; use pallet_staking::{CurrentEra, Event as StakingEvent, Payee, RewardDestination}; +use sp_runtime::traits::Zero; #[test] fn pool_lifecycle_e2e() { @@ -296,7 +297,7 @@ fn pool_slash_e2e() { PoolMember { pool_id: 1, points: 0, - reward_pool_total_earnings: 0, + last_recorded_reward_counter: Zero::zero(), // the 10 points unlocked just now correspond to 5 points in the unbond pool. unbonding_eras: bounded_btree_map!(5 => 10, 6 => 5) } @@ -351,7 +352,7 @@ fn pool_slash_e2e() { PoolMember { pool_id: 1, points: 0, - reward_pool_total_earnings: 0, + last_recorded_reward_counter: Zero::zero(), unbonding_eras: bounded_btree_map!(4 => 10, 5 => 10, 9 => 10) } ); diff --git a/frame/nomination-pools/test-staking/src/mock.rs b/frame/nomination-pools/test-staking/src/mock.rs index d13d8aa341c12..852b2c319f376 100644 --- a/frame/nomination-pools/test-staking/src/mock.rs +++ b/frame/nomination-pools/test-staking/src/mock.rs @@ -16,8 +16,17 @@ // limitations under the License. use frame_election_provider_support::VoteWeight; -use frame_support::{assert_ok, pallet_prelude::*, parameter_types, traits::ConstU64, PalletId}; -use sp_runtime::traits::{Convert, IdentityLookup}; +use frame_support::{ + assert_ok, + pallet_prelude::*, + parameter_types, + traits::{ConstU64, ConstU8}, + PalletId, +}; +use sp_runtime::{ + traits::{Convert, IdentityLookup}, + FixedU128, +}; type AccountId = u128; type AccountIndex = u32; @@ -159,13 +168,15 @@ impl pallet_nomination_pools::Config for Runtime { type Event = Event; type WeightInfo = (); type Currency = Balances; + type CurrencyBalance = Balance; + type RewardCounter = FixedU128; type BalanceToU256 = BalanceToU256; type U256ToBalance = U256ToBalance; type StakingInterface = Staking; type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow; type MaxMetadataLen = ConstU32<256>; type MaxUnbonding = ConstU32<8>; - type MinPointsToBalance = ConstU32<10>; + type MaxPointsToBalance = ConstU8<10>; type PalletId = PoolsPalletId; } diff --git a/utils/frame/try-runtime/cli/src/lib.rs b/utils/frame/try-runtime/cli/src/lib.rs index c09a33cf3f16a..f77f92c625c9d 100644 --- a/utils/frame/try-runtime/cli/src/lib.rs +++ b/utils/frame/try-runtime/cli/src/lib.rs @@ -472,9 +472,10 @@ pub enum State { #[clap(short, long)] snapshot_path: Option, - /// The pallets to scrape. If empty, entire chain state will be scraped. + /// A pallet to scrape. Can be provided multiple times. If empty, entire chain state will + /// be scraped. #[clap(short, long, multiple_values = true)] - pallets: Vec, + pallet: Vec, /// Fetch the child-keys as well. /// @@ -498,7 +499,7 @@ impl State { Builder::::new().mode(Mode::Offline(OfflineConfig { state_snapshot: SnapshotConfig::new(snapshot_path), })), - State::Live { snapshot_path, pallets, uri, at, child_tree } => { + State::Live { snapshot_path, pallet, uri, at, child_tree } => { let at = match at { Some(at_str) => Some(hash_of::(at_str)?), None => None, @@ -507,7 +508,7 @@ impl State { .mode(Mode::Online(OnlineConfig { transport: uri.to_owned().into(), state_snapshot: snapshot_path.as_ref().map(SnapshotConfig::new), - pallets: pallets.clone(), + pallets: pallet.clone(), scrape_children: true, at, })) From 969cefdf7d2c26f70635006d70d81b98751f0d57 Mon Sep 17 00:00:00 2001 From: Emre Surmeli Date: Thu, 14 Jul 2022 04:21:21 -0400 Subject: [PATCH 390/484] Fix typo (#11832) --- frame/election-provider-multi-phase/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 2f1f6463df719..e1d3cb8ed5dee 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -51,7 +51,7 @@ //! In the signed phase, solutions (of type [`RawSolution`]) are submitted and queued on chain. A //! deposit is reserved, based on the size of the solution, for the cost of keeping this solution //! on-chain for a number of blocks, and the potential weight of the solution upon being checked. A -//! maximum of `pallet::Config::MaxSignedSubmissions` solutions are stored. The queue is always +//! maximum of `pallet::Config::SignedMaxSubmissions` solutions are stored. The queue is always //! sorted based on score (worse to best). //! //! Upon arrival of a new solution: From b2781146c37a0436eb752a1f8102ee73b2ce2965 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Thu, 14 Jul 2022 10:57:59 +0100 Subject: [PATCH 391/484] Fix nomination pools unbonding logic (#11746) * make pool roles optional * undo lock file changes? * add migration * add the ability for pools to chill themselves * boilerplate of tests * somewhat stable, but I think I found another bug as well * Fix it all * Add more more sophisticated test + capture one more bug. * Update frame/staking/src/lib.rs * reduce the diff a little bit * add some test for the slashing bug * cleanup * fix lock file? * Fix * fmt * Update frame/nomination-pools/src/lib.rs Co-authored-by: Oliver Tale-Yazdi * Update frame/nomination-pools/src/lib.rs Co-authored-by: Oliver Tale-Yazdi * Update frame/nomination-pools/src/lib.rs Co-authored-by: Oliver Tale-Yazdi * Update frame/nomination-pools/src/mock.rs Co-authored-by: Oliver Tale-Yazdi * Fix build * fix some fishy tests.. * add one last integrity check for MinCreateBond * remove bad assertion -- needs to be dealt with later * nits * fix tests and add benchmarks for chill * remove stuff * fix benchmarks * cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_nomination_pools --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/nomination-pools/src/weights.rs --template=./.maintain/frame-weight-template.hbs * remove defensive * first working version * bring back all tests * ALL new tests work now * cleanup * make sure benchmarks and all work * cargo run --quiet --profile=production --features=runtime-benchmarks --manifest-path=bin/node/cli/Cargo.toml -- benchmark pallet --chain=dev --steps=50 --repeat=20 --pallet=pallet_nomination_pools --extrinsic=* --execution=wasm --wasm-execution=compiled --heap-pages=4096 --output=./frame/nomination-pools/src/weights.rs --template=./.maintain/frame-weight-template.hbs * round of self-review, make arithmetic safe * fix warn * add migration code * Fix doc * add precision notes * make arithmetic fallible * fix node runtime * a lot of precision tests and notes and stuff * document MaxPOintsToBalance better * :round of self-review * fmt * fix some comments * new logic, some broken tests * Check if after unbonding remaining balance is more or equal to MinJoinBond and is not zero * incorporate nikos' work * make it work again * merge * Fix all tests * fix all tests * some updates * Add tests * remove erroneoysly placed comment * Try to make lint pass * Try to make lint pass * revamp the tests for unbond * fix docs * Fix proportional slashing logic * Update frame/nomination-pools/src/tests.rs Co-authored-by: Oliver Tale-Yazdi * Update frame/nomination-pools/src/tests.rs Co-authored-by: Oliver Tale-Yazdi * Update frame/nomination-pools/src/lib.rs Co-authored-by: Oliver Tale-Yazdi * track poinst in migration * fix * fmt * fix migration * remove event read * Apply suggestions from code review * Update frame/nomination-pools/src/lib.rs Co-authored-by: Shawn Tabrizi * remove log * Update frame/staking/src/lib.rs Co-authored-by: Shawn Tabrizi * Update frame/nomination-pools/src/lib.rs Co-authored-by: Shawn Tabrizi * Update frame/nomination-pools/src/lib.rs Co-authored-by: Shawn Tabrizi * update * fmt * fmt * add one last test * fmrt * Update frame/nomination-pools/src/lib.rs Co-authored-by: Oliver Tale-Yazdi * Update frame/nomination-pools/src/tests.rs Co-authored-by: Oliver Tale-Yazdi Co-authored-by: Oliver Tale-Yazdi Co-authored-by: Shawn Tabrizi Co-authored-by: Parity Bot Co-authored-by: wirednkod --- frame/nomination-pools/src/lib.rs | 135 +++-- frame/nomination-pools/src/mock.rs | 16 +- frame/nomination-pools/src/tests.rs | 480 ++++++++++++++---- .../nomination-pools/test-staking/src/lib.rs | 6 +- 4 files changed, 464 insertions(+), 173 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 53e1c48a5e39e..514267e8bf4af 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -469,7 +469,7 @@ impl PoolMember { self.points = new_points; Ok(()) } else { - Err(Error::::NotEnoughPointsToUnbond) + Err(Error::::MinimumBondNotMet) } } @@ -781,45 +781,60 @@ impl BondedPool { let is_depositor = *target_account == self.roles.depositor; let is_full_unbond = unbonding_points == target_member.active_points(); + let balance_after_unbond = { + let new_depositor_points = + target_member.active_points().saturating_sub(unbonding_points); + let mut target_member_after_unbond = (*target_member).clone(); + target_member_after_unbond.points = new_depositor_points; + target_member_after_unbond.active_balance() + }; + // any partial unbonding is only ever allowed if this unbond is permissioned. ensure!( is_permissioned || is_full_unbond, Error::::PartialUnbondNotAllowedPermissionlessly ); + // any unbond must comply with the balance condition: + ensure!( + is_full_unbond || + balance_after_unbond >= + if is_depositor { + Pallet::::depositor_min_bond() + } else { + MinJoinBond::::get() + }, + Error::::MinimumBondNotMet + ); + + // additional checks: match (is_permissioned, is_depositor) { - // If the pool is blocked, then an admin with kicking permissions can remove a - // member. If the pool is being destroyed, anyone can remove a member + (true, false) => (), + (true, true) => { + // permission depositor unbond: if destroying and pool is empty, always allowed, + // with no additional limits. + if self.is_destroying_and_only_depositor(target_member.active_points()) { + // everything good, let them unbond anything. + } else { + // depositor cannot fully unbond yet. + ensure!(!is_full_unbond, Error::::MinimumBondNotMet); + } + }, (false, false) => { + // If the pool is blocked, then an admin with kicking permissions can remove a + // member. If the pool is being destroyed, anyone can remove a member + debug_assert!(is_full_unbond); ensure!( self.can_kick(caller) || self.is_destroying(), Error::::NotKickerOrDestroying ) }, - // Any member who is not the depositor can always unbond themselves - (true, false) => (), - (_, true) => { - if self.is_destroying_and_only_depositor(target_member.active_points()) { - // if the pool is about to be destroyed, anyone can unbond the depositor, and - // they can fully unbond. - } else { - // only the depositor can partially unbond, and they can only unbond up to the - // threshold. - ensure!(is_permissioned, Error::::DoesNotHavePermission); - let balance_after_unbond = { - let new_depositor_points = - target_member.active_points().saturating_sub(unbonding_points); - let mut depositor_after_unbond = (*target_member).clone(); - depositor_after_unbond.points = new_depositor_points; - depositor_after_unbond.active_balance() - }; - ensure!( - balance_after_unbond >= MinCreateBond::::get(), - Error::::NotOnlyPoolMember - ); - } + (false, true) => { + // the depositor can simply not be unbonded permissionlessly, period. + return Err(Error::::DoesNotHavePermission.into()) }, }; + Ok(()) } @@ -830,25 +845,14 @@ impl BondedPool { &self, caller: &T::AccountId, target_account: &T::AccountId, - target_member: &PoolMember, - sub_pools: &SubPools, ) -> Result<(), DispatchError> { - if *target_account == self.roles.depositor { - ensure!( - sub_pools.sum_unbonding_points() == target_member.unbonding_points(), - Error::::NotOnlyPoolMember - ); - debug_assert_eq!(self.member_counter, 1, "only member must exist at this point"); - Ok(()) - } else { - // This isn't a depositor - let is_permissioned = caller == target_account; - ensure!( - is_permissioned || self.can_kick(caller) || self.is_destroying(), - Error::::NotKickerOrDestroying - ); - Ok(()) - } + // This isn't a depositor + let is_permissioned = caller == target_account; + ensure!( + is_permissioned || self.can_kick(caller) || self.is_destroying(), + Error::::NotKickerOrDestroying + ); + Ok(()) } /// Bond exactly `amount` from `who`'s funds into this pool. @@ -1100,15 +1104,6 @@ impl SubPools { self } - /// The sum of all unbonding points, regardless of whether they are actually unlocked or not. - fn sum_unbonding_points(&self) -> BalanceOf { - self.no_era.points.saturating_add( - self.with_era - .values() - .fold(BalanceOf::::zero(), |acc, pool| acc.saturating_add(pool.points)), - ) - } - /// The sum of all unbonding balance, regardless of whether they are actually unlocked or not. #[cfg(any(test, debug_assertions))] fn sum_unbonding_balance(&self) -> BalanceOf { @@ -1419,15 +1414,16 @@ pub mod pallet { /// None of the funds can be withdrawn yet because the bonding duration has not passed. CannotWithdrawAny, /// The amount does not meet the minimum bond to either join or create a pool. + /// + /// The depositor can never unbond to a value less than + /// `Pallet::depositor_min_bond`. The caller does not have nominating + /// permissions for the pool. Members can never unbond to a value below `MinJoinBond`. MinimumBondNotMet, /// The transaction could not be executed due to overflow risk for the pool. OverflowRisk, /// A pool must be in [`PoolState::Destroying`] in order for the depositor to unbond or for /// other members to be permissionlessly unbonded. NotDestroying, - /// The depositor must be the only member in the bonded pool in order to unbond. And the - /// depositor must be the only member in the sub pools in order to withdraw unbonded. - NotOnlyPoolMember, /// The caller does not have nominating permissions for the pool. NotNominator, /// Either a) the caller cannot make a valid kick or b) the pool is not destroying. @@ -1447,8 +1443,6 @@ pub mod pallet { /// Some error occurred that should never happen. This should be reported to the /// maintainers. Defensive(DefensiveError), - /// Not enough points. Ty unbonding less. - NotEnoughPointsToUnbond, /// Partial unbonding now allowed permissionlessly. PartialUnbondNotAllowedPermissionlessly, } @@ -1758,12 +1752,7 @@ pub mod pallet { let mut sub_pools = SubPoolsStorage::::get(member.pool_id) .defensive_ok_or::>(DefensiveError::SubPoolsNotFound.into())?; - bonded_pool.ok_to_withdraw_unbonded_with( - &caller, - &member_account, - &member, - &sub_pools, - )?; + bonded_pool.ok_to_withdraw_unbonded_with(&caller, &member_account)?; // NOTE: must do this after we have done the `ok_to_withdraw_unbonded_other_with` check. let withdrawn_points = member.withdraw_unlocked(current_era); @@ -1878,13 +1867,7 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin)?; - ensure!( - amount >= - T::StakingInterface::minimum_bond() - .max(MinCreateBond::::get()) - .max(MinJoinBond::::get()), - Error::::MinimumBondNotMet - ); + ensure!(amount >= Pallet::::depositor_min_bond(), Error::::MinimumBondNotMet); ensure!( MaxPools::::get() .map_or(true, |max_pools| BondedPools::::count() < max_pools), @@ -2162,6 +2145,18 @@ pub mod pallet { } impl Pallet { + /// The amount of bond that MUST REMAIN IN BONDED in ALL POOLS. + /// + /// It is the responsibility of the depositor to put these funds into the pool initially. Upon + /// unbond, they can never unbond to a value below this amount. + /// + /// It is essentially `max { MinNominatorBond, MinCreateBond, MinJoinBond }`, where the former + /// is coming from the staking pallet and the latter two are configured in this pallet. + fn depositor_min_bond() -> BalanceOf { + T::StakingInterface::minimum_bond() + .max(MinCreateBond::::get()) + .max(MinJoinBond::::get()) + } /// Remove everything related to the given bonded pool. /// /// All sub-pools are also deleted. All accounts are dusted and the leftover of the reward diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index 042a0b666efb1..5138c55afccac 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -22,6 +22,7 @@ pub fn default_reward_account() -> AccountId { } parameter_types! { + pub static MinJoinBondConfig: Balance = 2; pub static CurrentEra: EraIndex = 0; pub static BondingDuration: EraIndex = 3; pub storage BondedBalanceMap: BTreeMap = Default::default(); @@ -245,6 +246,11 @@ impl ExtBuilder { self } + pub(crate) fn min_join_bond(self, min: Balance) -> Self { + MinJoinBondConfig::set(min); + self + } + pub(crate) fn with_check(self, level: u8) -> Self { CheckLevel::set(level); self @@ -261,11 +267,12 @@ impl ExtBuilder { } pub(crate) fn build(self) -> sp_io::TestExternalities { + sp_tracing::try_init_simple(); let mut storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); let _ = crate::GenesisConfig:: { - min_join_bond: 2, + min_join_bond: MinJoinBondConfig::get(), min_create_bond: 2, max_pools: Some(2), max_members_per_pool: self.max_members_per_pool, @@ -280,8 +287,8 @@ impl ExtBuilder { frame_system::Pallet::::set_block_number(1); // make a pool - let amount_to_bond = ::StakingInterface::minimum_bond(); - Balances::make_free_balance_be(&10, amount_to_bond * 2); + let amount_to_bond = Pools::depositor_min_bond(); + Balances::make_free_balance_be(&10, amount_to_bond * 5); assert_ok!(Pools::create(RawOrigin::Signed(10).into(), amount_to_bond, 900, 901, 902)); let last_pool = LastPoolId::::get(); @@ -302,12 +309,13 @@ impl ExtBuilder { } } -pub(crate) fn unsafe_set_state(pool_id: PoolId, state: PoolState) -> Result<(), ()> { +pub(crate) fn unsafe_set_state(pool_id: PoolId, state: PoolState) { BondedPools::::try_mutate(pool_id, |maybe_bonded_pool| { maybe_bonded_pool.as_mut().ok_or(()).map(|bonded_pool| { bonded_pool.state = state; }) }) + .unwrap() } parameter_types! { diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 9989893d86462..c971239bef507 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -538,13 +538,13 @@ mod join { StakingMock::set_bonded_balance(Pools::create_bonded_account(1), max_points_to_balance); // Cannot join a pool that isn't open - unsafe_set_state(123, PoolState::Blocked).unwrap(); + unsafe_set_state(123, PoolState::Blocked); assert_noop!( Pools::join(Origin::signed(11), max_points_to_balance, 123), Error::::NotOpen ); - unsafe_set_state(123, PoolState::Destroying).unwrap(); + unsafe_set_state(123, PoolState::Destroying); assert_noop!( Pools::join(Origin::signed(11), max_points_to_balance, 123), Error::::NotOpen @@ -1824,7 +1824,8 @@ mod claim_payout { fn rewards_are_rounded_down_depositor_collects_them() { ExtBuilder::default().add_members(vec![(20, 20)]).build_and_execute(|| { // initial balance of 10. - assert_eq!(Balances::free_balance(&10), 5); + + assert_eq!(Balances::free_balance(&10), 35); assert_eq!( Balances::free_balance(&default_reward_account()), Balances::minimum_balance() @@ -1875,7 +1876,7 @@ mod claim_payout { ); // original ed + ed put into reward account + reward + bond + dust. - assert_eq!(Balances::free_balance(&10), 5 + 5 + 13 + 10 + 1); + assert_eq!(Balances::free_balance(&10), 35 + 5 + 13 + 10 + 1); }) } @@ -1942,10 +1943,269 @@ mod claim_payout { mod unbond { use super::*; + #[test] + fn member_unbond_open() { + // depositor in pool, pool state open + // - member unbond above limit + // - member unbonds to 0 + // - member cannot unbond between within limit and 0 + ExtBuilder::default() + .min_join_bond(10) + .add_members(vec![(20, 20)]) + .build_and_execute(|| { + // can unbond to above limit + assert_ok!(Pools::unbond(Origin::signed(20), 20, 5)); + assert_eq!(PoolMembers::::get(20).unwrap().active_points(), 15); + assert_eq!(PoolMembers::::get(20).unwrap().unbonding_points(), 5); + + // cannot go to below 10: + assert_noop!( + Pools::unbond(Origin::signed(20), 20, 10), + Error::::MinimumBondNotMet + ); + + // but can go to 0 + assert_ok!(Pools::unbond(Origin::signed(20), 20, 15)); + assert_eq!(PoolMembers::::get(20).unwrap().active_points(), 0); + assert_eq!(PoolMembers::::get(20).unwrap().unbonding_points(), 20); + }) + } + + #[test] + fn member_kicked() { + // depositor in pool, pool state blocked + // - member cannot be kicked to above limit + // - member cannot be kicked between within limit and 0 + // - member kicked to 0 + ExtBuilder::default() + .min_join_bond(10) + .add_members(vec![(20, 20)]) + .build_and_execute(|| { + unsafe_set_state(1, PoolState::Blocked); + let kicker = DEFAULT_ROLES.state_toggler.unwrap(); + + // cannot be kicked to above the limit. + assert_noop!( + Pools::unbond(Origin::signed(kicker), 20, 5), + Error::::PartialUnbondNotAllowedPermissionlessly + ); + + // cannot go to below 10: + assert_noop!( + Pools::unbond(Origin::signed(kicker), 20, 15), + Error::::PartialUnbondNotAllowedPermissionlessly + ); + + // but they themselves can do an unbond + assert_ok!(Pools::unbond(Origin::signed(20), 20, 2)); + assert_eq!(PoolMembers::::get(20).unwrap().active_points(), 18); + assert_eq!(PoolMembers::::get(20).unwrap().unbonding_points(), 2); + + // can be kicked to 0. + assert_ok!(Pools::unbond(Origin::signed(kicker), 20, 18)); + assert_eq!(PoolMembers::::get(20).unwrap().active_points(), 0); + assert_eq!(PoolMembers::::get(20).unwrap().unbonding_points(), 20); + }) + } + + #[test] + fn member_unbond_destroying() { + // depositor in pool, pool state destroying + // - member cannot be permissionlessly unbonded to above limit + // - member cannot be permissionlessly unbonded between within limit and 0 + // - member permissionlessly unbonded to 0 + ExtBuilder::default() + .min_join_bond(10) + .add_members(vec![(20, 20)]) + .build_and_execute(|| { + unsafe_set_state(1, PoolState::Destroying); + let random = 123; + + // cannot be kicked to above the limit. + assert_noop!( + Pools::unbond(Origin::signed(random), 20, 5), + Error::::PartialUnbondNotAllowedPermissionlessly + ); + + // cannot go to below 10: + assert_noop!( + Pools::unbond(Origin::signed(random), 20, 15), + Error::::PartialUnbondNotAllowedPermissionlessly + ); + + // but they themselves can do an unbond + assert_ok!(Pools::unbond(Origin::signed(20), 20, 2)); + assert_eq!(PoolMembers::::get(20).unwrap().active_points(), 18); + assert_eq!(PoolMembers::::get(20).unwrap().unbonding_points(), 2); + + // but can go to 0 + assert_ok!(Pools::unbond(Origin::signed(random), 20, 18)); + assert_eq!(PoolMembers::::get(20).unwrap().active_points(), 0); + assert_eq!(PoolMembers::::get(20).unwrap().unbonding_points(), 20); + }) + } + + #[test] + fn depositor_unbond_open() { + // depositor in pool, pool state open + // - depositor unbonds to above limit + // - depositor cannot unbond to below limit or 0 + ExtBuilder::default().min_join_bond(10).build_and_execute(|| { + // give the depositor some extra funds. + assert_ok!(Pools::bond_extra(Origin::signed(10), BondExtra::FreeBalance(10))); + assert_eq!(PoolMembers::::get(10).unwrap().points, 20); + + // can unbond to above the limit. + assert_ok!(Pools::unbond(Origin::signed(10), 10, 5)); + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 15); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 5); + + // cannot go to below 10: + assert_noop!(Pools::unbond(Origin::signed(10), 10, 10), Error::::MinimumBondNotMet); + + // cannot go to 0 either. + assert_noop!(Pools::unbond(Origin::signed(10), 10, 15), Error::::MinimumBondNotMet); + }) + } + + #[test] + fn depositor_kick() { + // depositor in pool, pool state blocked + // - depositor can never be kicked. + ExtBuilder::default().min_join_bond(10).build_and_execute(|| { + // give the depositor some extra funds. + assert_ok!(Pools::bond_extra(Origin::signed(10), BondExtra::FreeBalance(10))); + assert_eq!(PoolMembers::::get(10).unwrap().points, 20); + + // set the stage + unsafe_set_state(1, PoolState::Blocked); + let kicker = DEFAULT_ROLES.state_toggler.unwrap(); + + // cannot be kicked to above limit. + assert_noop!( + Pools::unbond(Origin::signed(kicker), 10, 5), + Error::::PartialUnbondNotAllowedPermissionlessly + ); + + // or below the limit + assert_noop!( + Pools::unbond(Origin::signed(kicker), 10, 15), + Error::::PartialUnbondNotAllowedPermissionlessly + ); + + // or 0. + assert_noop!( + Pools::unbond(Origin::signed(kicker), 10, 20), + Error::::DoesNotHavePermission + ); + + // they themselves cannot do it either + assert_noop!(Pools::unbond(Origin::signed(10), 10, 20), Error::::MinimumBondNotMet); + }) + } + + #[test] + fn depositor_unbond_destroying_permissionless() { + // depositor can never be permissionlessly unbonded. + ExtBuilder::default().min_join_bond(10).build_and_execute(|| { + // give the depositor some extra funds. + assert_ok!(Pools::bond_extra(Origin::signed(10), BondExtra::FreeBalance(10))); + assert_eq!(PoolMembers::::get(10).unwrap().points, 20); + + // set the stage + unsafe_set_state(1, PoolState::Destroying); + let random = 123; + + // cannot be kicked to above limit. + assert_noop!( + Pools::unbond(Origin::signed(random), 10, 5), + Error::::PartialUnbondNotAllowedPermissionlessly + ); + + // or below the limit + assert_noop!( + Pools::unbond(Origin::signed(random), 10, 15), + Error::::PartialUnbondNotAllowedPermissionlessly + ); + + // or 0. + assert_noop!( + Pools::unbond(Origin::signed(random), 10, 20), + Error::::DoesNotHavePermission + ); + + // they themselves can do it in this case though. + assert_ok!(Pools::unbond(Origin::signed(10), 10, 20)); + }) + } + + #[test] + fn depositor_unbond_destroying_not_last_member() { + // deposit in pool, pool state destroying + // - depositor can never leave if there is another member in the pool. + ExtBuilder::default() + .min_join_bond(10) + .add_members(vec![(20, 20)]) + .build_and_execute(|| { + // give the depositor some extra funds. + assert_ok!(Pools::bond_extra(Origin::signed(10), BondExtra::FreeBalance(10))); + assert_eq!(PoolMembers::::get(10).unwrap().points, 20); + + // set the stage + unsafe_set_state(1, PoolState::Destroying); + + // can go above the limit + assert_ok!(Pools::unbond(Origin::signed(10), 10, 5)); + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 15); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 5); + + // but not below the limit + assert_noop!( + Pools::unbond(Origin::signed(10), 10, 10), + Error::::MinimumBondNotMet + ); + + // and certainly not zero + assert_noop!( + Pools::unbond(Origin::signed(10), 10, 15), + Error::::MinimumBondNotMet + ); + }) + } + + #[test] + fn depositor_unbond_destroying_last_member() { + // deposit in pool, pool state destroying + // - depositor can unbond to above limit always. + // - depositor cannot unbond to below limit if last. + // - depositor can unbond to 0 if last and destroying. + ExtBuilder::default().min_join_bond(10).build_and_execute(|| { + // give the depositor some extra funds. + assert_ok!(Pools::bond_extra(Origin::signed(10), BondExtra::FreeBalance(10))); + assert_eq!(PoolMembers::::get(10).unwrap().points, 20); + + // set the stage + unsafe_set_state(1, PoolState::Destroying); + + // can unbond to above the limit. + assert_ok!(Pools::unbond(Origin::signed(10), 10, 5)); + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 15); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 5); + + // still cannot go to below limit + assert_noop!(Pools::unbond(Origin::signed(10), 10, 10), Error::::MinimumBondNotMet); + + // can go to 0 too. + assert_ok!(Pools::unbond(Origin::signed(10), 10, 15)); + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 0); + assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 20); + }) + } + #[test] fn unbond_of_1_works() { ExtBuilder::default().build_and_execute(|| { - unsafe_set_state(1, PoolState::Destroying).unwrap(); + unsafe_set_state(1, PoolState::Destroying); assert_ok!(fully_unbond_permissioned(10)); assert_eq!( @@ -2021,7 +2281,7 @@ mod unbond { assert_eq!(Balances::free_balance(&40), 40 + 40); // We claim rewards when unbonding // When - unsafe_set_state(1, PoolState::Destroying).unwrap(); + unsafe_set_state(1, PoolState::Destroying); assert_ok!(fully_unbond_permissioned(550)); // Then @@ -2111,7 +2371,7 @@ mod unbond { }, }, ); - unsafe_set_state(1, PoolState::Destroying).unwrap(); + unsafe_set_state(1, PoolState::Destroying); // When let current_era = 1 + TotalUnbondingPools::::get(); @@ -2148,7 +2408,7 @@ mod unbond { .add_members(vec![(100, 100), (200, 200)]) .build_and_execute(|| { // Given - unsafe_set_state(1, PoolState::Blocked).unwrap(); + unsafe_set_state(1, PoolState::Blocked); let bonded_pool = BondedPool::::get(1).unwrap(); assert_eq!(bonded_pool.roles.root.unwrap(), 900); assert_eq!(bonded_pool.roles.nominator.unwrap(), 901); @@ -2216,7 +2476,7 @@ mod unbond { // Scenarios where non-admin accounts can unbond others ExtBuilder::default().add_members(vec![(100, 100)]).build_and_execute(|| { // Given the pool is blocked - unsafe_set_state(1, PoolState::Blocked).unwrap(); + unsafe_set_state(1, PoolState::Blocked); // A permissionless unbond attempt errors assert_noop!( @@ -2231,16 +2491,17 @@ mod unbond { ); // Given the pool is destroying - unsafe_set_state(1, PoolState::Destroying).unwrap(); + unsafe_set_state(1, PoolState::Destroying); // The depositor cannot be fully unbonded until they are the last member assert_noop!( Pools::fully_unbond(Origin::signed(10), 10), - Error::::NotOnlyPoolMember + Error::::MinimumBondNotMet, ); // Any account can unbond a member that is not the depositor assert_ok!(Pools::fully_unbond(Origin::signed(420), 100)); + assert_eq!( pool_events_since_last_call(), vec![ @@ -2258,7 +2519,7 @@ mod unbond { ); // Given the pool is blocked - unsafe_set_state(1, PoolState::Blocked).unwrap(); + unsafe_set_state(1, PoolState::Blocked); // The depositor cannot be unbonded assert_noop!( @@ -2267,7 +2528,7 @@ mod unbond { ); // Given the pools is destroying - unsafe_set_state(1, PoolState::Destroying).unwrap(); + unsafe_set_state(1, PoolState::Destroying); // The depositor cannot be unbonded yet. assert_noop!( @@ -2285,8 +2546,13 @@ mod unbond { Error::::PartialUnbondNotAllowedPermissionlessly, ); - // but full unbond works. - assert_ok!(Pools::fully_unbond(Origin::signed(420), 10)); + // depositor can never be unbonded permissionlessly . + assert_noop!( + Pools::fully_unbond(Origin::signed(420), 10), + Error::::DoesNotHavePermission + ); + // but depositor itself can do it. + assert_ok!(Pools::fully_unbond(Origin::signed(10), 10)); assert_eq!(BondedPools::::get(1).unwrap().points, 0); assert_eq!( @@ -2346,6 +2612,12 @@ mod unbond { #[test] fn partial_unbond_era_tracking() { ExtBuilder::default().build_and_execute(|| { + // to make the depositor capable of withdrawing. + StakingMinBond::set(1); + MinCreateBond::::set(1); + MinJoinBond::::set(1); + assert_eq!(Pools::depositor_min_bond(), 1); + // given assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 10); assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 0); @@ -2360,7 +2632,7 @@ mod unbond { assert_eq!(BondingDuration::get(), 3); // so the depositor can leave, just keeps the test simpler. - unsafe_set_state(1, PoolState::Destroying).unwrap(); + unsafe_set_state(1, PoolState::Destroying); // when: casual unbond assert_ok!(Pools::unbond(Origin::signed(10), 10, 1)); @@ -2444,13 +2716,13 @@ mod unbond { ); // when: unbonding more than our active: error - assert_err!( + assert_noop!( frame_support::storage::in_storage_layer(|| Pools::unbond( Origin::signed(10), 10, 5 )), - Error::::NotEnoughPointsToUnbond + Error::::MinimumBondNotMet ); // instead: assert_ok!(Pools::unbond(Origin::signed(10), 10, 3)); @@ -2482,26 +2754,24 @@ mod unbond { #[test] fn partial_unbond_max_chunks() { - ExtBuilder::default().ed(1).build_and_execute(|| { - // so the depositor can leave, just keeps the test simpler. - unsafe_set_state(1, PoolState::Destroying).unwrap(); + ExtBuilder::default().add_members(vec![(20, 20)]).ed(1).build_and_execute(|| { MaxUnbonding::set(2); // given - assert_ok!(Pools::unbond(Origin::signed(10), 10, 2)); + assert_ok!(Pools::unbond(Origin::signed(20), 20, 2)); CurrentEra::set(1); - assert_ok!(Pools::unbond(Origin::signed(10), 10, 3)); + assert_ok!(Pools::unbond(Origin::signed(20), 20, 3)); assert_eq!( - PoolMembers::::get(10).unwrap().unbonding_eras, + PoolMembers::::get(20).unwrap().unbonding_eras, member_unbonding_eras!(3 => 2, 4 => 3) ); // when CurrentEra::set(2); - assert_err!( + assert_noop!( frame_support::storage::in_storage_layer(|| Pools::unbond( - Origin::signed(10), - 10, + Origin::signed(20), + 20, 4 )), Error::::MaxUnbondingLimit @@ -2509,30 +2779,35 @@ mod unbond { // when MaxUnbonding::set(3); - assert_ok!(Pools::unbond(Origin::signed(10), 10, 1)); + assert_ok!(Pools::unbond(Origin::signed(20), 20, 1)); + assert_eq!( - PoolMembers::::get(10).unwrap().unbonding_eras, + PoolMembers::::get(20).unwrap().unbonding_eras, member_unbonding_eras!(3 => 2, 4 => 3, 5 => 1) ); + assert_eq!( pool_events_since_last_call(), vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::Unbonded { member: 10, pool_id: 1, points: 2, balance: 2 }, - Event::Unbonded { member: 10, pool_id: 1, points: 3, balance: 3 }, - Event::Unbonded { member: 10, pool_id: 1, points: 1, balance: 1 } + Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, + Event::Unbonded { member: 20, pool_id: 1, balance: 2, points: 2 }, + Event::Unbonded { member: 20, pool_id: 1, balance: 3, points: 3 }, + Event::Unbonded { member: 20, pool_id: 1, balance: 1, points: 1 } ] ); }) } - // depositor can unbond inly up to `MinCreateBond`. + // depositor can unbond only up to `MinCreateBond`. #[test] fn depositor_permissioned_partial_unbond() { ExtBuilder::default().ed(1).build_and_execute(|| { // given - assert_eq!(MinCreateBond::::get(), 2); + StakingMinBond::set(5); + assert_eq!(Pools::depositor_min_bond(), 5); + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 10); assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 0); @@ -2544,7 +2819,7 @@ mod unbond { // but not less than 2 assert_noop!( Pools::unbond(Origin::signed(10), 10, 6), - Error::::NotOnlyPoolMember + Error::::MinimumBondNotMet ); assert_eq!( @@ -2558,7 +2833,6 @@ mod unbond { }); } - // same as above, but the pool is slashed and therefore the depositor cannot partially unbond. #[test] fn depositor_permissioned_partial_unbond_slashed() { ExtBuilder::default().ed(1).build_and_execute(|| { @@ -2573,78 +2847,69 @@ mod unbond { // cannot unbond even 7, because the value of shares is now less. assert_noop!( Pools::unbond(Origin::signed(10), 10, 7), - Error::::NotOnlyPoolMember - ); - assert_eq!( - pool_events_since_last_call(), - vec![ - Event::Created { depositor: 10, pool_id: 1 }, - Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - ] + Error::::MinimumBondNotMet ); }); } #[test] fn every_unbonding_triggers_payout() { - ExtBuilder::default().build_and_execute(|| { - let initial_reward_account = Balances::free_balance(Pools::create_reward_account(1)); + ExtBuilder::default().add_members(vec![(20, 20)]).build_and_execute(|| { + let initial_reward_account = Balances::free_balance(default_reward_account()); assert_eq!(initial_reward_account, Balances::minimum_balance()); assert_eq!(initial_reward_account, 5); - // set the pool to destroying so that depositor can leave. - unsafe_set_state(1, PoolState::Destroying).unwrap(); - Balances::make_free_balance_be( - &Pools::create_reward_account(1), - 2 * Balances::minimum_balance(), + &default_reward_account(), + 4 * Balances::minimum_balance(), ); - assert_ok!(Pools::unbond(Origin::signed(10), 10, 2)); + assert_ok!(Pools::unbond(Origin::signed(20), 20, 2)); assert_eq!( pool_events_since_last_call(), vec![ + // 2/3 of ed, which is 20's share. Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - // exactly equal to ed, all that can be claimed. - Event::PaidOut { member: 10, pool_id: 1, payout: 5 }, - Event::Unbonded { member: 10, pool_id: 1, points: 2, balance: 2 } + Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, + Event::PaidOut { member: 20, pool_id: 1, payout: 10 }, + Event::Unbonded { member: 20, pool_id: 1, balance: 2, points: 2 } ] ); CurrentEra::set(1); Balances::make_free_balance_be( - &Pools::create_reward_account(1), - 2 * Balances::minimum_balance(), + &default_reward_account(), + 4 * Balances::minimum_balance(), ); - assert_ok!(Pools::unbond(Origin::signed(10), 10, 3)); + assert_ok!(Pools::unbond(Origin::signed(20), 20, 3)); assert_eq!( pool_events_since_last_call(), vec![ - // exactly equal to ed, all that can be claimed. - Event::PaidOut { member: 10, pool_id: 1, payout: 5 }, - Event::Unbonded { member: 10, pool_id: 1, points: 3, balance: 3 } + // 2/3 of ed, which is 20's share. + Event::PaidOut { member: 20, pool_id: 1, payout: 6 }, + Event::Unbonded { member: 20, pool_id: 1, points: 3, balance: 3 } ] ); CurrentEra::set(2); Balances::make_free_balance_be( - &Pools::create_reward_account(1), - 2 * Balances::minimum_balance(), + &default_reward_account(), + 4 * Balances::minimum_balance(), ); - assert_ok!(Pools::unbond(Origin::signed(10), 10, 5)); + assert_ok!(Pools::unbond(Origin::signed(20), 20, 5)); assert_eq!( pool_events_since_last_call(), vec![ - Event::PaidOut { member: 10, pool_id: 1, payout: 5 }, - Event::Unbonded { member: 10, pool_id: 1, points: 5, balance: 5 } + Event::PaidOut { member: 20, pool_id: 1, payout: 3 }, + Event::Unbonded { member: 20, pool_id: 1, points: 5, balance: 5 } ] ); assert_eq!( - PoolMembers::::get(10).unwrap().unbonding_eras, + PoolMembers::::get(20).unwrap().unbonding_eras, member_unbonding_eras!(3 => 2, 4 => 3, 5 => 5) ); }); @@ -2801,7 +3066,7 @@ mod withdraw_unbonded { ); // now, finally, the depositor can take out its share. - unsafe_set_state(1, PoolState::Destroying).unwrap(); + unsafe_set_state(1, PoolState::Destroying); assert_ok!(fully_unbond_permissioned(10)); current_era += 3; @@ -2911,7 +3176,7 @@ mod withdraw_unbonded { assert!(SubPoolsStorage::::get(&1).unwrap().with_era.is_empty()); // now, finally, the depositor can take out its share. - unsafe_set_state(1, PoolState::Destroying).unwrap(); + unsafe_set_state(1, PoolState::Destroying); assert_ok!(fully_unbond_permissioned(10)); // because everyone else has left, the points @@ -2926,7 +3191,7 @@ mod withdraw_unbonded { assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 0)); // then - assert_eq!(Balances::free_balance(&10), 10 + 5); + assert_eq!(Balances::free_balance(&10), 10 + 35); assert_eq!(Balances::free_balance(&default_bonded_account()), 0); // in this test 10 also gets a fair share of the slash, because the slash was @@ -2955,9 +3220,9 @@ mod withdraw_unbonded { ExtBuilder::default().build_and_execute(|| { // Given assert_eq!(Balances::minimum_balance(), 5); - assert_eq!(Balances::free_balance(&10), 5); + assert_eq!(Balances::free_balance(&10), 35); assert_eq!(Balances::free_balance(&default_bonded_account()), 10); - unsafe_set_state(1, PoolState::Destroying).unwrap(); + unsafe_set_state(1, PoolState::Destroying); assert_ok!(Pools::fully_unbond(Origin::signed(10), 10)); // Simulate a slash that is not accounted for in the sub pools. @@ -2974,7 +3239,7 @@ mod withdraw_unbonded { assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 0)); // Then - assert_eq!(Balances::free_balance(10), 10 + 5); + assert_eq!(Balances::free_balance(10), 10 + 35); assert_eq!(Balances::free_balance(&default_bonded_account()), 0); }); } @@ -3054,7 +3319,7 @@ mod withdraw_unbonded { ); // Given - unsafe_set_state(1, PoolState::Blocked).unwrap(); + unsafe_set_state(1, PoolState::Blocked); // Cannot kick as a nominator assert_noop!( @@ -3112,7 +3377,7 @@ mod withdraw_unbonded { ); // Given - unsafe_set_state(1, PoolState::Destroying).unwrap(); + unsafe_set_state(1, PoolState::Destroying); // Can permissionlesly withdraw a member that is not the depositor assert_ok!(Pools::withdraw_unbonded(Origin::signed(420), 100, 0)); @@ -3137,8 +3402,8 @@ mod withdraw_unbonded { #[test] fn partial_withdraw_unbonded_depositor() { ExtBuilder::default().ed(1).build_and_execute(|| { - // so the depositor can leave, just keeps the test simpler. - unsafe_set_state(1, PoolState::Destroying).unwrap(); + assert_ok!(Pools::bond_extra(Origin::signed(10), BondExtra::FreeBalance(10))); + unsafe_set_state(1, PoolState::Destroying); // given assert_ok!(Pools::unbond(Origin::signed(10), 10, 6)); @@ -3158,13 +3423,14 @@ mod withdraw_unbonded { } } ); - assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 3); + assert_eq!(PoolMembers::::get(10).unwrap().active_points(), 13); assert_eq!(PoolMembers::::get(10).unwrap().unbonding_points(), 7); assert_eq!( pool_events_since_last_call(), vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: false }, Event::Unbonded { member: 10, pool_id: 1, points: 6, balance: 6 }, Event::Unbonded { member: 10, pool_id: 1, points: 1, balance: 1 } ] @@ -3368,50 +3634,72 @@ mod withdraw_unbonded { #[test] fn full_multi_step_withdrawing_depositor() { ExtBuilder::default().ed(1).build_and_execute(|| { - // given + // depositor now has 20, they can unbond to 10. + assert_eq!(Pools::depositor_min_bond(), 10); + assert_ok!(Pools::bond_extra(Origin::signed(10), BondExtra::FreeBalance(10))); + + // now they can. assert_ok!(Pools::unbond(Origin::signed(10), 10, 7)); // progress one era and unbond the leftover. CurrentEra::set(1); - unsafe_set_state(1, PoolState::Destroying).unwrap(); assert_ok!(Pools::unbond(Origin::signed(10), 10, 3)); + assert_eq!( PoolMembers::::get(10).unwrap().unbonding_eras, member_unbonding_eras!(3 => 7, 4 => 3) ); + // they can't unbond to a value below 10 other than 0.. assert_noop!( - Pools::withdraw_unbonded(Origin::signed(10), 10, 0), - Error::::CannotWithdrawAny + Pools::unbond(Origin::signed(10), 10, 5), + Error::::MinimumBondNotMet ); + // but not even full, because they pool is not yet destroying. + assert_noop!( + Pools::unbond(Origin::signed(10), 10, 10), + Error::::MinimumBondNotMet + ); + + // but now they can. + unsafe_set_state(1, PoolState::Destroying); + assert_noop!( + Pools::unbond(Origin::signed(10), 10, 5), + Error::::MinimumBondNotMet + ); + assert_ok!(Pools::unbond(Origin::signed(10), 10, 10)); + // now the 7 should be free. CurrentEra::set(3); assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 0)); + assert_eq!( pool_events_since_last_call(), vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::Unbonded { member: 10, pool_id: 1, points: 7, balance: 7 }, - Event::Unbonded { member: 10, pool_id: 1, points: 3, balance: 3 }, - Event::Withdrawn { member: 10, pool_id: 1, points: 7, balance: 7 } + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: false }, + Event::Unbonded { member: 10, pool_id: 1, balance: 7, points: 7 }, + Event::Unbonded { member: 10, pool_id: 1, balance: 3, points: 3 }, + Event::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10 }, + Event::Withdrawn { member: 10, pool_id: 1, balance: 7, points: 7 } ] ); assert_eq!( PoolMembers::::get(10).unwrap().unbonding_eras, - member_unbonding_eras!(4 => 3) + member_unbonding_eras!(4 => 13) ); - // the 25 should be free now, and the member removed. + // the 13 should be free now, and the member removed. CurrentEra::set(4); assert_ok!(Pools::withdraw_unbonded(Origin::signed(10), 10, 0)); + assert_eq!( pool_events_since_last_call(), vec![ - Event::Withdrawn { member: 10, pool_id: 1, points: 3, balance: 3 }, + Event::Withdrawn { member: 10, pool_id: 1, points: 13, balance: 13 }, Event::MemberRemoved { pool_id: 1, member: 10 }, - // the pool is also destroyed now. Event::Destroyed { pool_id: 1 }, ] ); @@ -3640,7 +3928,7 @@ mod set_state { // If the pool is not ok to be open, then anyone can set it to destroying // Given - unsafe_set_state(1, PoolState::Open).unwrap(); + unsafe_set_state(1, PoolState::Open); let mut bonded_pool = BondedPool::::get(1).unwrap(); bonded_pool.points = 100; bonded_pool.put(); @@ -3651,7 +3939,7 @@ mod set_state { // Given Balances::make_free_balance_be(&default_bonded_account(), Balance::max_value() / 10); - unsafe_set_state(1, PoolState::Open).unwrap(); + unsafe_set_state(1, PoolState::Open); // When assert_ok!(Pools::set_state(Origin::signed(11), 1, PoolState::Destroying)); // Then @@ -3659,7 +3947,7 @@ mod set_state { // If the pool is not ok to be open, it cannot be permissionleslly set to a state that // isn't destroying - unsafe_set_state(1, PoolState::Open).unwrap(); + unsafe_set_state(1, PoolState::Open); assert_noop!( Pools::set_state(Origin::signed(11), 1, PoolState::Blocked), Error::::CanNotChangeState @@ -3820,13 +4108,13 @@ mod bond_extra { // given assert_eq!(PoolMembers::::get(10).unwrap().points, 10); assert_eq!(BondedPools::::get(1).unwrap().points, 10); - assert_eq!(Balances::free_balance(10), 5); + assert_eq!(Balances::free_balance(10), 35); // when assert_ok!(Pools::bond_extra(Origin::signed(10), BondExtra::Rewards)); // then - assert_eq!(Balances::free_balance(10), 5); + assert_eq!(Balances::free_balance(10), 35); assert_eq!(PoolMembers::::get(10).unwrap().points, 10 + claimable_reward); assert_eq!(BondedPools::::get(1).unwrap().points, 10 + claimable_reward); @@ -3862,14 +4150,14 @@ mod bond_extra { assert_eq!(PoolMembers::::get(10).unwrap().points, 10); assert_eq!(PoolMembers::::get(20).unwrap().points, 20); assert_eq!(BondedPools::::get(1).unwrap().points, 30); - assert_eq!(Balances::free_balance(10), 5); + assert_eq!(Balances::free_balance(10), 35); assert_eq!(Balances::free_balance(20), 20); // when assert_ok!(Pools::bond_extra(Origin::signed(10), BondExtra::Rewards)); // then - assert_eq!(Balances::free_balance(10), 5); + assert_eq!(Balances::free_balance(10), 35); // 10's share of the reward is 1/3, since they gave 10/30 of the total shares. assert_eq!(PoolMembers::::get(10).unwrap().points, 10 + 1); assert_eq!(BondedPools::::get(1).unwrap().points, 30 + 1); diff --git a/frame/nomination-pools/test-staking/src/lib.rs b/frame/nomination-pools/test-staking/src/lib.rs index 3b9350f25040b..eed8e5fd390cd 100644 --- a/frame/nomination-pools/test-staking/src/lib.rs +++ b/frame/nomination-pools/test-staking/src/lib.rs @@ -72,7 +72,7 @@ fn pool_lifecycle_e2e() { // depositor cannot unbond yet. assert_noop!( Pools::unbond(Origin::signed(10), 10, 50), - PoolsError::::NotOnlyPoolMember, + PoolsError::::MinimumBondNotMet, ); // now the members want to unbond. @@ -103,7 +103,7 @@ fn pool_lifecycle_e2e() { // depositor cannot still unbond assert_noop!( Pools::unbond(Origin::signed(10), 10, 50), - PoolsError::::NotOnlyPoolMember, + PoolsError::::MinimumBondNotMet, ); for e in 1..BondingDuration::get() { @@ -120,7 +120,7 @@ fn pool_lifecycle_e2e() { // depositor cannot still unbond assert_noop!( Pools::unbond(Origin::signed(10), 10, 50), - PoolsError::::NotOnlyPoolMember, + PoolsError::::MinimumBondNotMet, ); // but members can now withdraw. From d5d8937d075eb24b90d05a773680e79f01776633 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Thu, 14 Jul 2022 20:14:28 +0200 Subject: [PATCH 392/484] contracts: Composable `ChainExtension` (#11816) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add `RegisteredChainExtension` * Add tests * Update frame/contracts/src/chain_extension.rs Co-authored-by: Michael Müller * Add more docs * Remove debugging leftover * Make ChainExtension-registry lowercase * Apply suggestions from code review Co-authored-by: Hernando Castano * Improve clarity of test inputs Co-authored-by: Michael Müller Co-authored-by: Hernando Castano --- Cargo.lock | 1 + frame/contracts/Cargo.toml | 1 + frame/contracts/fixtures/chain_extension.wat | 10 +- frame/contracts/src/chain_extension.rs | 59 ++++++- frame/contracts/src/tests.rs | 175 +++++++++++++++++-- 5 files changed, 216 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3b7c315be4390..791d8037f3335 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5505,6 +5505,7 @@ dependencies = [ "frame-support", "frame-system", "hex-literal", + "impl-trait-for-tuples", "log", "pallet-balances", "pallet-contracts-primitives", diff --git a/frame/contracts/Cargo.toml b/frame/contracts/Cargo.toml index d27801df33bda..ac85c469354fe 100644 --- a/frame/contracts/Cargo.toml +++ b/frame/contracts/Cargo.toml @@ -26,6 +26,7 @@ smallvec = { version = "1", default-features = false, features = [ "const_generics", ] } wasmi-validation = { version = "0.4", default-features = false } +impl-trait-for-tuples = "0.2" # Only used in benchmarking to generate random contract code rand = { version = "0.8", optional = true, default-features = false } diff --git a/frame/contracts/fixtures/chain_extension.wat b/frame/contracts/fixtures/chain_extension.wat index db7e83fd96b42..9b2534c540ab8 100644 --- a/frame/contracts/fixtures/chain_extension.wat +++ b/frame/contracts/fixtures/chain_extension.wat @@ -15,12 +15,12 @@ ) ;; [0, 4) len of input output - (data (i32.const 0) "\02") + (data (i32.const 0) "\08") ;; [4, 12) buffer for input - ;; [12, 16) len of output buffer - (data (i32.const 12) "\02") + ;; [12, 48) len of output buffer + (data (i32.const 12) "\20") ;; [16, inf) buffer for output @@ -31,7 +31,7 @@ ;; the chain extension passes through the input and returns it as output (call $seal_call_chain_extension - (i32.load8_u (i32.const 4)) ;; func_id + (i32.load (i32.const 4)) ;; func_id (i32.const 4) ;; input_ptr (i32.load (i32.const 0)) ;; input_len (i32.const 16) ;; output_ptr @@ -39,7 +39,7 @@ ) ;; the chain extension passes through the func_id - (call $assert (i32.eq (i32.load8_u (i32.const 4)))) + (call $assert (i32.eq (i32.load (i32.const 4)))) (call $seal_return (i32.const 0) (i32.const 16) (i32.load (i32.const 12))) ) diff --git a/frame/contracts/src/chain_extension.rs b/frame/contracts/src/chain_extension.rs index ed447719933be..536d58c94f68f 100644 --- a/frame/contracts/src/chain_extension.rs +++ b/frame/contracts/src/chain_extension.rs @@ -29,6 +29,22 @@ //! required for this endeavour are defined or re-exported in this module. There is an //! implementation on `()` which can be used to signal that no chain extension is available. //! +//! # Using multiple chain extensions +//! +//! Often there is a need for having multiple chain extensions. This is often the case when +//! some generally useful off-the-shelf extensions should be included. To have multiple chain +//! extensions they can be put into a tuple which is then passed to `[Config::ChainExtension]` like +//! this `type Extensions = (ExtensionA, ExtensionB)`. +//! +//! However, only extensions implementing [`RegisteredChainExtension`] can be put into a tuple. +//! This is because the [`RegisteredChainExtension::ID`] is used to decide which of those extensions +//! should should be used when the contract calls a chain extensions. Extensions which are generally +//! useful should claim their `ID` with [the registry](https://github.com/paritytech/chainextension-registry) +//! so that no collisions with other vendors will occur. +//! +//! **Chain specific extensions must use the reserved `ID = 0` so that they can't be registered with +//! the registry.** +//! //! # Security //! //! The chain author alone is responsible for the security of the chain extension. @@ -112,20 +128,51 @@ pub trait ChainExtension { } } -/// Implementation that indicates that no chain extension is available. -impl ChainExtension for () { - fn call(_func_id: u32, mut _env: Environment) -> Result +/// A [`ChainExtension`] that can be composed with other extensions using a tuple. +/// +/// An extension that implements this trait can be put in a tuple in order to have multiple +/// extensions available. The tuple implementation routes requests based on the first two +/// most significant bytes of the `func_id` passed to `call`. +/// +/// If this extensions is to be used by multiple runtimes consider +/// [registering it](https://github.com/paritytech/chainextension-registry) to ensure that there +/// are no collisions with other vendors. +/// +/// # Note +/// +/// Currently, we support tuples of up to ten registred chain extensions. If more chain extensions +/// are needed consider opening an issue. +pub trait RegisteredChainExtension: ChainExtension { + /// The extensions globally unique identifier. + const ID: u16; +} + +#[impl_trait_for_tuples::impl_for_tuples(10)] +#[tuple_types_custom_trait_bound(RegisteredChainExtension)] +impl ChainExtension for Tuple { + fn call(func_id: u32, mut env: Environment) -> Result where E: Ext, ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, { - // Never called since [`Self::enabled()`] is set to `false`. Because we want to - // avoid panics at all costs we supply a sensible error value here instead - // of an `unimplemented!`. + for_tuples!( + #( + if (Tuple::ID == (func_id >> 16) as u16) && Tuple::enabled() { + return Tuple::call(func_id, env); + } + )* + ); Err(Error::::NoChainExtension.into()) } fn enabled() -> bool { + for_tuples!( + #( + if Tuple::enabled() { + return true; + } + )* + ); false } } diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index bbac18142a658..85a0e9977d2d7 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -17,8 +17,8 @@ use crate::{ chain_extension::{ - ChainExtension, Environment, Ext, InitState, Result as ExtensionResult, RetVal, - ReturnFlags, SysConfig, UncheckedFrom, + ChainExtension, Environment, Ext, InitState, RegisteredChainExtension, + Result as ExtensionResult, RetVal, ReturnFlags, SysConfig, UncheckedFrom, }, exec::{FixSizedKey, Frame}, storage::Storage, @@ -118,6 +118,10 @@ pub struct TestExtension { last_seen_inputs: (u32, u32, u32, u32), } +pub struct RevertingExtension; + +pub struct DisabledExtension; + impl TestExtension { fn disable() { TEST_EXTENSION.with(|e| e.borrow_mut().enabled = false) @@ -147,7 +151,7 @@ impl ChainExtension for TestExtension { match func_id { 0 => { let mut env = env.buf_in_buf_out(); - let input = env.read(2)?; + let input = env.read(8)?; env.write(&input, false, None)?; TEST_EXTENSION.with(|e| e.borrow_mut().last_seen_buffer = input); Ok(RetVal::Converging(func_id)) @@ -162,7 +166,7 @@ impl ChainExtension for TestExtension { }, 2 => { let mut env = env.buf_in_buf_out(); - let weight = env.read(2)?[1].into(); + let weight = env.read(5)?[4].into(); env.charge_weight(weight)?; Ok(RetVal::Converging(func_id)) }, @@ -178,6 +182,46 @@ impl ChainExtension for TestExtension { } } +impl RegisteredChainExtension for TestExtension { + const ID: u16 = 0; +} + +impl ChainExtension for RevertingExtension { + fn call(_func_id: u32, _env: Environment) -> ExtensionResult + where + E: Ext, + ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, + { + Ok(RetVal::Diverging { flags: ReturnFlags::REVERT, data: vec![0x4B, 0x1D] }) + } + + fn enabled() -> bool { + TEST_EXTENSION.with(|e| e.borrow().enabled) + } +} + +impl RegisteredChainExtension for RevertingExtension { + const ID: u16 = 1; +} + +impl ChainExtension for DisabledExtension { + fn call(_func_id: u32, _env: Environment) -> ExtensionResult + where + E: Ext, + ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, + { + panic!("Disabled chain extensions are never called") + } + + fn enabled() -> bool { + false + } +} + +impl RegisteredChainExtension for DisabledExtension { + const ID: u16 = 2; +} + parameter_types! { pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::simple_max(2 * WEIGHT_PER_SECOND); @@ -281,7 +325,7 @@ impl Config for Test { type CallStack = [Frame; 31]; type WeightPrice = Self; type WeightInfo = (); - type ChainExtension = TestExtension; + type ChainExtension = (TestExtension, DisabledExtension, RevertingExtension); type DeletionQueueDepth = ConstU32<1024>; type DeletionWeightLimit = ConstU64<500_000_000_000>; type Schedule = MySchedule; @@ -1523,6 +1567,23 @@ fn disabled_chain_extension_errors_on_call() { #[test] fn chain_extension_works() { + struct Input<'a> { + extension_id: u16, + func_id: u16, + extra: &'a [u8], + } + + impl<'a> From> for Vec { + fn from(input: Input) -> Vec { + ((input.extension_id as u32) << 16 | (input.func_id as u32)) + .to_le_bytes() + .iter() + .chain(input.extra) + .cloned() + .collect() + } + } + let (code, hash) = compile_module::("chain_extension").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); @@ -1543,31 +1604,107 @@ fn chain_extension_works() { // func_id. // 0 = read input buffer and pass it through as output + let input: Vec = Input { extension_id: 0, func_id: 0, extra: &[99] }.into(); let result = - Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, None, vec![0, 99], false); - let gas_consumed = result.gas_consumed; - assert_eq!(TestExtension::last_seen_buffer(), vec![0, 99]); - assert_eq!(result.result.unwrap().data, Bytes(vec![0, 99])); + Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, None, input.clone(), false); + assert_eq!(TestExtension::last_seen_buffer(), input); + assert_eq!(result.result.unwrap().data, Bytes(input)); // 1 = treat inputs as integer primitives and store the supplied integers - Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, None, vec![1], false) - .result - .unwrap(); + Contracts::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + Input { extension_id: 0, func_id: 1, extra: &[] }.into(), + false, + ) + .result + .unwrap(); // those values passed in the fixture - assert_eq!(TestExtension::last_seen_inputs(), (4, 1, 16, 12)); + assert_eq!(TestExtension::last_seen_inputs(), (4, 4, 16, 12)); - // 2 = charge some extra weight (amount supplied in second byte) - let result = - Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, None, vec![2, 42], false); + // 2 = charge some extra weight (amount supplied in the fifth byte) + let result = Contracts::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + Input { extension_id: 0, func_id: 2, extra: &[0] }.into(), + false, + ); + assert_ok!(result.result); + let gas_consumed = result.gas_consumed; + let result = Contracts::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + Input { extension_id: 0, func_id: 2, extra: &[42] }.into(), + false, + ); assert_ok!(result.result); assert_eq!(result.gas_consumed, gas_consumed + 42); + let result = Contracts::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + Input { extension_id: 0, func_id: 2, extra: &[95] }.into(), + false, + ); + assert_ok!(result.result); + assert_eq!(result.gas_consumed, gas_consumed + 95); // 3 = diverging chain extension call that sets flags to 0x1 and returns a fixed buffer - let result = Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, None, vec![3], false) - .result - .unwrap(); + let result = Contracts::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + Input { extension_id: 0, func_id: 3, extra: &[] }.into(), + false, + ) + .result + .unwrap(); assert_eq!(result.flags, ReturnFlags::REVERT); assert_eq!(result.data, Bytes(vec![42, 99])); + + // diverging to second chain extension that sets flags to 0x1 and returns a fixed buffer + // We set the MSB part to 1 (instead of 0) which routes the request into the second + // extension + let result = Contracts::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + Input { extension_id: 1, func_id: 0, extra: &[] }.into(), + false, + ) + .result + .unwrap(); + assert_eq!(result.flags, ReturnFlags::REVERT); + assert_eq!(result.data, Bytes(vec![0x4B, 0x1D])); + + // Diverging to third chain extension that is disabled + // We set the MSB part to 2 (instead of 0) which routes the request into the third extension + assert_err_ignore_postinfo!( + Contracts::call( + Origin::signed(ALICE), + addr.clone(), + 0, + GAS_LIMIT, + None, + Input { extension_id: 2, func_id: 0, extra: &[] }.into(), + ), + Error::::NoChainExtension, + ); }); } From 440a2ceb9855dc60d381f41ae8447d59b1cbe45f Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Fri, 15 Jul 2022 09:42:40 +0200 Subject: [PATCH 393/484] [ci] fix job `cancel-pipeline` (#11844) * [ci] fix cancel-pipeline job * test fail * remove debug --- .gitlab-ci.yml | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b4933333b3418..fba7b7a9d393f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -234,23 +234,42 @@ rusty-cachier-notify: # This job cancels the whole pipeline if any of provided jobs fail. # In a DAG, every jobs chain is executed independently of others. The `fail_fast` principle suggests # to fail the pipeline as soon as possible to shorten the feedback loop. -cancel-pipeline: +.cancel-pipeline-template: stage: .post + rules: + - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs + when: on_failure + variables: + PROJECT_ID: "${CI_PROJECT_ID}" + PIPELINE_ID: "${CI_PIPELINE_ID}" + trigger: "parity/infrastructure/ci_cd/pipeline-stopper" + +cancel-pipeline-test-linux-stable: + extends: .cancel-pipeline-template needs: - job: test-linux-stable artifacts: false + +cancel-pipeline-test-linux-stable-int: + extends: .cancel-pipeline-template + needs: - job: test-linux-stable-int artifacts: false + +cancel-pipeline-cargo-check-subkey: + extends: .cancel-pipeline-template + needs: - job: cargo-check-subkey artifacts: false + +cancel-pipeline-cargo-check-benches: + extends: .cancel-pipeline-template + needs: - job: cargo-check-benches artifacts: false + +cancel-pipeline-check-tracing: + extends: .cancel-pipeline-template + needs: - job: check-tracing artifacts: false - rules: - - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs - when: on_failure - variables: - PROJECT_ID: "${CI_PROJECT_ID}" - PIPELINE_ID: "${CI_PIPELINE_ID}" - trigger: "parity/infrastructure/ci_cd/pipeline-stopper" From 47be163e641cc1f14f09c34340d21f31dd319e07 Mon Sep 17 00:00:00 2001 From: Vlad Date: Fri, 15 Jul 2022 10:58:53 +0300 Subject: [PATCH 394/484] Speed up `rusty-cachier-notify` job (#11841) * Speed up `rusty-cachier-notify` job * Don't forget to setup `rusty-cachier` first --- .gitlab-ci.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fba7b7a9d393f..9e80277404549 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -223,10 +223,15 @@ deploy-prometheus-alerting-rules: # This job notifies rusty-cachier about the latest commit with the cache. # This info is later used for the cache distribution and an overlay creation. +# Note that we don't use any .rusty-cachier references as we assume that a pipeline has reached this stage with working rusty-cachier. rusty-cachier-notify: stage: notify - extends: .docker-env + extends: .kubernetes-env + variables: + CI_IMAGE: paritytech/rusty-cachier-env:latest + GIT_STRATEGY: none script: + - curl -s https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.parity.io/parity/infrastructure/ci_cd/rusty-cachier/client/-/raw/release/util/install.sh | bash - rusty-cachier cache notify #### stage: .post From c8400c9dc450aa1bc552e9651c6714b6e85b0aa5 Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Fri, 15 Jul 2022 10:44:46 +0200 Subject: [PATCH 395/484] [ci] improvments to make pipeline faster (#11829) * [DO NOT MERGE] test-linux-stable parallel on 3 ci nodes * add debug message * adjust rusty-cachier * empty commit * move test-linux-stable to test.yml * make cargo-check-benches and test-wasmer-sandbox parallel * fix comment * Update scripts/ci/gitlab/pipeline/test.yml Co-authored-by: Denis Pisarev * Update scripts/ci/gitlab/pipeline/test.yml Co-authored-by: Denis Pisarev * if to case * use case instead if in cargo-check-benches * format * add comments to output * add comment * add quotes * Update scripts/ci/gitlab/pipeline/test.yml Co-authored-by: Denis Pisarev Co-authored-by: parity-processbot <> Co-authored-by: Denis Pisarev --- .gitlab-ci.yml | 1 - scripts/ci/gitlab/pipeline/test.yml | 58 ++++++++++++++++++++++------- 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9e80277404549..fff11e07326d8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -196,7 +196,6 @@ include: # publish jobs - scripts/ci/gitlab/pipeline/publish.yml - #### stage: deploy deploy-prometheus-alerting-rules: diff --git a/scripts/ci/gitlab/pipeline/test.yml b/scripts/ci/gitlab/pipeline/test.yml index cc167410f94a4..dcec896ddc18b 100644 --- a/scripts/ci/gitlab/pipeline/test.yml +++ b/scripts/ci/gitlab/pipeline/test.yml @@ -74,7 +74,9 @@ cargo-check-nixos: cargo-check-benches: stage: test variables: - RUSTY_CACHIER_TOOLCHAIN: nightly + # Override to use nightly toolchain + RUSTY_CACHIER_TOOLCHAIN: "nightly" + CI_JOB_NAME: "cargo-check-benches" extends: - .docker-env - .test-refs @@ -91,16 +93,25 @@ cargo-check-benches: git config user.email "ci@gitlab.parity.io"; git merge $CI_COMMIT_REF_NAME --verbose --no-edit; fi + parallel: 2 script: - rusty-cachier snapshot create - mkdir -p ./artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA - - SKIP_WASM_BUILD=1 time cargo +nightly check --benches --all - - 'cargo run --release -p node-bench -- ::node::import::native::sr25519::transfer_keep_alive::paritydb::small --json - | tee ./artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA/::node::import::native::sr25519::transfer_keep_alive::paritydb::small.json' - - 'cargo run --release -p node-bench -- ::trie::read::small --json - | tee ./artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA/::trie::read::small.json' - - sccache -s - - rusty-cachier cache upload + # this job is executed in parallel on two runners + - echo "___Running benchmarks___"; + - case ${CI_NODE_INDEX} in + 1) + SKIP_WASM_BUILD=1 time cargo +nightly check --benches --all; + cargo run --release -p node-bench -- ::trie::read::small --json + | tee ./artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA/::trie::read::small.json; + echo "___Uploading cache for rusty-cachier___"; + rusty-cachier cache upload + ;; + 2) + cargo run --release -p node-bench -- ::node::import::native::sr25519::transfer_keep_alive::paritydb::small --json + | tee ./artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA/::node::import::native::sr25519::transfer_keep_alive::paritydb::small.json + ;; + esac tags: - linux-docker-benches @@ -116,7 +127,7 @@ node-bench-regression-guard: # this is a DAG - job: cargo-check-benches artifacts: true - # this does not like a DAG, just polls the artifact + # polls artifact from master to compare with current result - project: $CI_PROJECT_PATH job: cargo-check-benches ref: master @@ -213,14 +224,27 @@ test-linux-stable: WASM_BUILD_RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" # Ensure we run the UI tests. RUN_UI_TESTS: 1 + # needed for rusty-cachier to keep cache in test-linux-stable folder and not in test-linux-stable-1/3 + CI_JOB_NAME: "test-linux-stable" + parallel: 3 script: - rusty-cachier snapshot create - # TODO: add to paritytech/ci-linux image + # TODO: remove when current paritytech/ci-linux:staging (with rust stable 1.62) is switched to production - time cargo install cargo-nextest # this job runs all tests in former runtime-benchmarks, frame-staking and wasmtime tests + # tests are partitioned by nextest and executed in parallel on $CI_NODE_TOTAL runners # node-cli is excluded until https://github.com/paritytech/substrate/issues/11321 fixed - - time cargo nextest run --workspace --locked --release --verbose --features runtime-benchmarks --manifest-path ./bin/node/cli/Cargo.toml --exclude node-cli - - rusty-cachier cache upload + - echo "Node index - ${CI_NODE_INDEX}. Total amount - ${CI_NODE_TOTAL}" + - time cargo nextest run --workspace + --locked + --release + --verbose + --features runtime-benchmarks + --manifest-path ./bin/node/cli/Cargo.toml + --exclude node-cli + --partition count:${CI_NODE_INDEX}/${CI_NODE_TOTAL} + # we need to update cache only from one job + - if [ ${CI_NODE_INDEX} == 1 ]; then rusty-cachier cache upload; fi test-frame-support: stage: test @@ -356,10 +380,16 @@ test-wasmer-sandbox: extends: - .docker-env - .test-refs-wasmer-sandbox + variables: + CI_JOB_NAME: "test-wasmer-sandbox" + parallel: 3 script: - rusty-cachier snapshot create - - time cargo test --release --features runtime-benchmarks,wasmer-sandbox,disable-ui-tests - - rusty-cachier cache upload + # TODO: remove when current paritytech/ci-linux:staging (with rust stable 1.62) is switched to production + - cargo install cargo-nextest + - echo "Node index - ${CI_NODE_INDEX}. Total amount - ${CI_NODE_TOTAL}" + - time cargo nextest run --release --features runtime-benchmarks,wasmer-sandbox,disable-ui-tests --partition count:${CI_NODE_INDEX}/${CI_NODE_TOTAL} + - if [ ${CI_NODE_INDEX} == 1 ]; then rusty-cachier cache upload; fi cargo-check-macos: stage: test From f76fb43af5b37ae423c03a7cdf8bbba3fa7d0e97 Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan Date: Fri, 15 Jul 2022 13:55:18 +0200 Subject: [PATCH 396/484] Fixed sync target detection (#11817) * Fixed sync target detection * Always report sync_target metric * Clamp median across all peers --- client/network/sync/src/lib.rs | 9 +++++++-- client/service/src/metrics.rs | 7 ++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/client/network/sync/src/lib.rs b/client/network/sync/src/lib.rs index aff773bd12ed6..2f837cd6c4f51 100644 --- a/client/network/sync/src/lib.rs +++ b/client/network/sync/src/lib.rs @@ -1719,7 +1719,7 @@ where Ok(sync) } - /// Returns the best seen block. + /// Returns the best seen block number if we don't have that block yet, `None` otherwise. fn best_seen(&self) -> Option> { let mut best_seens = self.peers.values().map(|p| p.best_number).collect::>(); @@ -1729,7 +1729,12 @@ where let middle = best_seens.len() / 2; // Not the "perfect median" when we have an even number of peers. - Some(*best_seens.select_nth_unstable(middle).1) + let median = *best_seens.select_nth_unstable(middle).1; + if median > self.best_queued_number { + Some(median) + } else { + None + } } } diff --git a/client/service/src/metrics.rs b/client/service/src/metrics.rs index 555023f894488..56b145ccdf7f7 100644 --- a/client/service/src/metrics.rs +++ b/client/service/src/metrics.rs @@ -298,9 +298,10 @@ impl MetricsService { UniqueSaturatedInto::::unique_saturated_into(num) }); - if let Some(best_seen_block) = best_seen_block { - metrics.block_height.with_label_values(&["sync_target"]).set(best_seen_block); - } + metrics + .block_height + .with_label_values(&["sync_target"]) + .set(best_seen_block.unwrap_or(best_number)); } } } From 9c02679dfdef008e4127bdc45af14441d6df88e8 Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Sun, 17 Jul 2022 00:24:46 +0300 Subject: [PATCH 397/484] Ignore boot and reserved nodes with peer ID the same as in local node itself (#11851) --- client/network/src/service.rs | 45 +++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/client/network/src/service.rs b/client/network/src/service.rs index ef7ef2f5a2deb..0d28b50df1821 100644 --- a/client/network/src/service.rs +++ b/client/network/src/service.rs @@ -148,6 +148,47 @@ where /// for the network processing to advance. From it, you can extract a `NetworkService` using /// `worker.service()`. The `NetworkService` can be shared through the codebase. pub fn new(mut params: Params) -> Result { + // Private and public keys configuration. + let local_identity = params.network_config.node_key.clone().into_keypair()?; + let local_public = local_identity.public(); + let local_peer_id = local_public.to_peer_id(); + + params.network_config.boot_nodes = params + .network_config + .boot_nodes + .into_iter() + .filter(|boot_node| { + if boot_node.peer_id == local_peer_id { + warn!( + target: "sub-libp2p", + "Local peer ID used in bootnode, ignoring: {}", + boot_node, + ); + false + } else { + true + } + }) + .collect(); + params.network_config.default_peers_set.reserved_nodes = params + .network_config + .default_peers_set + .reserved_nodes + .into_iter() + .filter(|reserved_node| { + if reserved_node.peer_id == local_peer_id { + warn!( + target: "sub-libp2p", + "Local peer ID used in reserved node, ignoring: {}", + reserved_node, + ); + false + } else { + true + } + }) + .collect(); + // Ensure the listen addresses are consistent with the transport. ensure_addresses_consistent_with_transport( params.network_config.listen_addresses.iter(), @@ -190,10 +231,6 @@ where .extra_sets .insert(0, transactions_handler_proto.set_config()); - // Private and public keys configuration. - let local_identity = params.network_config.node_key.clone().into_keypair()?; - let local_public = local_identity.public(); - let local_peer_id = local_public.to_peer_id(); info!( target: "sub-libp2p", "🏷 Local node identity is: {}", From a16773fd744b91c94a4b81deeebc4fe0e6fffd2e Mon Sep 17 00:00:00 2001 From: Sergej Sakac <73715684+Szegoo@users.noreply.github.com> Date: Sun, 17 Jul 2022 00:38:38 +0300 Subject: [PATCH 398/484] removed evaluation.rs (#11766) * removed evaluation.rs * no need for parent_hash * fix --- .../basic-authorship/src/basic_authorship.rs | 12 +-- primitives/consensus/common/src/evaluation.rs | 74 ------------------- primitives/consensus/common/src/lib.rs | 1 - 3 files changed, 1 insertion(+), 86 deletions(-) delete mode 100644 primitives/consensus/common/src/evaluation.rs diff --git a/client/basic-authorship/src/basic_authorship.rs b/client/basic-authorship/src/basic_authorship.rs index b67008bc6f44b..bc328c40edb3c 100644 --- a/client/basic-authorship/src/basic_authorship.rs +++ b/client/basic-authorship/src/basic_authorship.rs @@ -34,9 +34,7 @@ use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_INFO}; use sc_transaction_pool_api::{InPoolTransaction, TransactionPool}; use sp_api::{ApiExt, ProvideRuntimeApi}; use sp_blockchain::{ApplyExtrinsicFailed::Validity, Error::ApplyExtrinsicFailed, HeaderBackend}; -use sp_consensus::{ - evaluation, DisableProofRecording, EnableProofRecording, ProofRecording, Proposal, -}; +use sp_consensus::{DisableProofRecording, EnableProofRecording, ProofRecording, Proposal}; use sp_core::traits::SpawnNamed; use sp_inherents::InherentData; use sp_runtime::{ @@ -205,7 +203,6 @@ where let proposer = Proposer::<_, _, _, _, PR> { spawn_handle: self.spawn_handle.clone(), client: self.client.clone(), - parent_hash, parent_id: id, parent_number: *parent_header.number(), transaction_pool: self.transaction_pool.clone(), @@ -250,7 +247,6 @@ where pub struct Proposer { spawn_handle: Box, client: Arc, - parent_hash: ::Hash, parent_id: BlockId, parent_number: <::Header as HeaderT>::Number, transaction_pool: Arc, @@ -536,12 +532,6 @@ where "hash" => ?::Hash::from(block.header().hash()), ); - if let Err(err) = - evaluation::evaluate_initial(&block, &self.parent_hash, self.parent_number) - { - error!("Failed to evaluate authored block: {:?}", err); - } - let proof = PR::into_proof(proof).map_err(|e| sp_blockchain::Error::Application(Box::new(e)))?; diff --git a/primitives/consensus/common/src/evaluation.rs b/primitives/consensus/common/src/evaluation.rs deleted file mode 100644 index d1ce8e9fc5109..0000000000000 --- a/primitives/consensus/common/src/evaluation.rs +++ /dev/null @@ -1,74 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Block evaluation and evaluation errors. - -use codec::Encode; -use sp_runtime::traits::{Block as BlockT, CheckedConversion, Header as HeaderT, One}; - -// This is just a best effort to encode the number. None indicated that it's too big to encode -// in a u128. -type BlockNumber = Option; - -/// Result type alias. -pub type Result = std::result::Result; - -/// Error type. -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("Failed to verify block encoding/decoding")] - BadBlockCodec, - /// Proposal provided not a block. - #[error("Proposal provided not a block: decoding error: {0}")] - BadProposalFormat(#[from] codec::Error), - /// Proposal had wrong parent hash. - #[error("Proposal had wrong parent hash. Expected {expected:?}, got {got:?}")] - WrongParentHash { expected: String, got: String }, - /// Proposal had wrong number. - #[error("Proposal had wrong number. Expected {expected:?}, got {got:?}")] - WrongNumber { expected: BlockNumber, got: BlockNumber }, -} - -/// Attempt to evaluate a substrate block as a node block, returning error -/// upon any initial validity checks failing. -pub fn evaluate_initial( - proposal: &Block, - parent_hash: &::Hash, - parent_number: <::Header as HeaderT>::Number, -) -> Result<()> { - let encoded = Encode::encode(proposal); - let block = Block::decode(&mut &encoded[..]).map_err(Error::BadProposalFormat)?; - if &block != proposal { - return Err(Error::BadBlockCodec) - } - - if *parent_hash != *block.header().parent_hash() { - return Err(Error::WrongParentHash { - expected: format!("{:?}", *parent_hash), - got: format!("{:?}", block.header().parent_hash()), - }) - } - - if parent_number + One::one() != *block.header().number() { - return Err(Error::WrongNumber { - expected: parent_number.checked_into::().map(|x| x + 1), - got: (*block.header().number()).checked_into::(), - }) - } - - Ok(()) -} diff --git a/primitives/consensus/common/src/lib.rs b/primitives/consensus/common/src/lib.rs index 2743f434c209b..4539cec2c8e0a 100644 --- a/primitives/consensus/common/src/lib.rs +++ b/primitives/consensus/common/src/lib.rs @@ -33,7 +33,6 @@ use sp_state_machine::StorageProof; pub mod block_validation; pub mod error; -pub mod evaluation; mod select_chain; pub use self::error::Error; From b1c88958bfbc3cbfb7c22c00bde0fbad92ff3d69 Mon Sep 17 00:00:00 2001 From: lumir-mrkva Date: Sun, 17 Jul 2022 21:28:32 +0200 Subject: [PATCH 399/484] assert noop notifies that storage has been mutated (#11805) --- frame/support/src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index f2cd7d1e165ec..8e43df82a284c 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -679,7 +679,7 @@ macro_rules! assert_noop { ) => { let h = $crate::storage_root($crate::StateVersion::V1); $crate::assert_err!($x, $y); - assert_eq!(h, $crate::storage_root($crate::StateVersion::V1)); + assert_eq!(h, $crate::storage_root($crate::StateVersion::V1), "storage has been mutated"); }; } @@ -826,6 +826,7 @@ pub mod tests { pub struct Module for enum Call where origin: T::Origin, system=self {} } } + use self::module::Module; decl_storage! { @@ -851,6 +852,7 @@ pub mod tests { } struct Test; + impl Config for Test { type BlockNumber = u32; type Origin = u32; @@ -867,6 +869,7 @@ pub mod tests { trait Sorted { fn sorted(self) -> Self; } + impl Sorted for Vec { fn sorted(mut self) -> Self { self.sort(); From 774cc716619ecac831fd1c057456815a8cbc778c Mon Sep 17 00:00:00 2001 From: yjh Date: Mon, 18 Jul 2022 06:43:04 +0800 Subject: [PATCH 400/484] arrange node-template service (#11795) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * make template more clear * fmt check_equivocation Co-authored-by: Bastian Köcher --- bin/node-template/node/src/service.rs | 34 ++++++++++++------------ client/consensus/slots/src/aux_schema.rs | 6 ++--- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/bin/node-template/node/src/service.rs b/bin/node-template/node/src/service.rs index f45f914d94f44..ffb2440caa0ed 100644 --- a/bin/node-template/node/src/service.rs +++ b/bin/node-template/node/src/service.rs @@ -296,24 +296,24 @@ pub fn new_full(mut config: Configuration) -> Result .spawn_blocking("aura", Some("block-authoring"), aura); } - // if the node isn't actively participating in consensus then it doesn't - // need a keystore, regardless of which protocol we use below. - let keystore = - if role.is_authority() { Some(keystore_container.sync_keystore()) } else { None }; - - let grandpa_config = sc_finality_grandpa::Config { - // FIXME #1578 make this available through chainspec - gossip_duration: Duration::from_millis(333), - justification_period: 512, - name: Some(name), - observer_enabled: false, - keystore, - local_role: role, - telemetry: telemetry.as_ref().map(|x| x.handle()), - protocol_name: grandpa_protocol_name, - }; - if enable_grandpa { + // if the node isn't actively participating in consensus then it doesn't + // need a keystore, regardless of which protocol we use below. + let keystore = + if role.is_authority() { Some(keystore_container.sync_keystore()) } else { None }; + + let grandpa_config = sc_finality_grandpa::Config { + // FIXME #1578 make this available through chainspec + gossip_duration: Duration::from_millis(333), + justification_period: 512, + name: Some(name), + observer_enabled: false, + keystore, + local_role: role, + telemetry: telemetry.as_ref().map(|x| x.handle()), + protocol_name: grandpa_protocol_name, + }; + // start the full GRANDPA voter // NOTE: non-authorities could run the GRANDPA observer protocol, but at // this point the full voter should provide better guarantees of block diff --git a/client/consensus/slots/src/aux_schema.rs b/client/consensus/slots/src/aux_schema.rs index eeaec68d369d2..c1d01500ffe47 100644 --- a/client/consensus/slots/src/aux_schema.rs +++ b/client/consensus/slots/src/aux_schema.rs @@ -89,8 +89,8 @@ where // 1) signed by the same voter, if prev_signer == signer { // 2) with different hash - if header.hash() != prev_header.hash() { - return Ok(Some(EquivocationProof { + return if header.hash() != prev_header.hash() { + Ok(Some(EquivocationProof { slot, offender: signer.clone(), first_header: prev_header.clone(), @@ -100,7 +100,7 @@ where // We don't need to continue in case of duplicated header, // since it's already saved and a possible equivocation // would have been detected before. - return Ok(None) + Ok(None) } } } From f024091ae0a3ff4c9fa70d4719fd4b6fc70b4c3f Mon Sep 17 00:00:00 2001 From: cheme Date: Mon, 18 Jul 2022 00:57:13 +0200 Subject: [PATCH 401/484] Expose and link trie migration weights (#11775) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * expose and link trie migration weights * actually limit to one trait * Run test on dummy weights * fmt Co-authored-by: Bastian Köcher --- frame/state-trie-migration/src/lib.rs | 69 +++++++++++++-------------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/frame/state-trie-migration/src/lib.rs b/frame/state-trie-migration/src/lib.rs index 9ac128a0e3108..94f6c1f223b9c 100644 --- a/frame/state-trie-migration/src/lib.rs +++ b/frame/state-trie-migration/src/lib.rs @@ -56,6 +56,7 @@ #![cfg_attr(not(feature = "std"), no_std)] pub use pallet::*; +pub mod weights; const LOG_TARGET: &str = "runtime::state-trie-migration"; @@ -71,6 +72,9 @@ macro_rules! log { #[frame_support::pallet] pub mod pallet { + + pub use crate::weights::WeightInfo; + use frame_support::{ dispatch::{DispatchErrorWithPostInfo, PostDispatchInfo}, ensure, @@ -90,41 +94,6 @@ pub mod pallet { pub(crate) type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; - /// The weight information of this pallet. - pub trait WeightInfo { - fn process_top_key(x: u32) -> Weight; - fn continue_migrate() -> Weight; - fn continue_migrate_wrong_witness() -> Weight; - fn migrate_custom_top_fail() -> Weight; - fn migrate_custom_top_success() -> Weight; - fn migrate_custom_child_fail() -> Weight; - fn migrate_custom_child_success() -> Weight; - } - - impl WeightInfo for () { - fn process_top_key(_: u32) -> Weight { - 1000000 - } - fn continue_migrate() -> Weight { - 1000000 - } - fn continue_migrate_wrong_witness() -> Weight { - 1000000 - } - fn migrate_custom_top_fail() -> Weight { - 1000000 - } - fn migrate_custom_top_success() -> Weight { - 1000000 - } - fn migrate_custom_child_fail() -> Weight { - 1000000 - } - fn migrate_custom_child_success() -> Weight { - 1000000 - } - } - /// The progress of either the top or child keys. #[derive( CloneNoBound, @@ -1072,6 +1041,7 @@ mod mock { use frame_support::{ parameter_types, traits::{ConstU32, ConstU64, Hooks}, + weights::Weight, }; use frame_system::{EnsureRoot, EnsureSigned}; use sp_core::{ @@ -1148,6 +1118,33 @@ mod mock { type WeightInfo = (); } + /// Test only Weights for state migration. + pub struct StateMigrationTestWeight; + + impl WeightInfo for StateMigrationTestWeight { + fn process_top_key(_: u32) -> Weight { + 1000000 + } + fn continue_migrate() -> Weight { + 1000000 + } + fn continue_migrate_wrong_witness() -> Weight { + 1000000 + } + fn migrate_custom_top_fail() -> Weight { + 1000000 + } + fn migrate_custom_top_success() -> Weight { + 1000000 + } + fn migrate_custom_child_fail() -> Weight { + 1000000 + } + fn migrate_custom_child_success() -> Weight { + 1000000 + } + } + impl pallet_state_trie_migration::Config for Test { type Event = Event; type ControlOrigin = EnsureRoot; @@ -1156,7 +1153,7 @@ mod mock { type SignedDepositPerItem = SignedDepositPerItem; type SignedDepositBase = SignedDepositBase; type SignedFilter = EnsureSigned; - type WeightInfo = (); + type WeightInfo = StateMigrationTestWeight; } pub fn new_test_ext( From 6639619eb8f3f289f9464e6293573790f40b6263 Mon Sep 17 00:00:00 2001 From: Koute Date: Mon, 18 Jul 2022 20:51:38 +0900 Subject: [PATCH 402/484] Improve `wasmtime` error reporting (#11856) * Improve `wasmtime` error reporting * cargo fmt --- client/executor/wasmtime/src/imports.rs | 2 +- .../executor/wasmtime/src/instance_wrapper.rs | 7 +++--- client/executor/wasmtime/src/runtime.rs | 24 +++++++++---------- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/client/executor/wasmtime/src/imports.rs b/client/executor/wasmtime/src/imports.rs index fae8d75854ef3..c80952a2541ce 100644 --- a/client/executor/wasmtime/src/imports.rs +++ b/client/executor/wasmtime/src/imports.rs @@ -112,7 +112,7 @@ impl<'a, 'b> sp_wasm_interface::HostFunctionRegistry for Registry<'a, 'b> { if self.pending_func_imports.remove(fn_name).is_some() { self.linker.func_wrap("env", fn_name, func).map_err(|error| { WasmError::Other(format!( - "failed to register host function '{}' with the WASM linker: {}", + "failed to register host function '{}' with the WASM linker: {:#}", fn_name, error )) })?; diff --git a/client/executor/wasmtime/src/instance_wrapper.rs b/client/executor/wasmtime/src/instance_wrapper.rs index 6abcbca1bba6f..5d272accd3524 100644 --- a/client/executor/wasmtime/src/instance_wrapper.rs +++ b/client/executor/wasmtime/src/instance_wrapper.rs @@ -186,9 +186,10 @@ impl InstanceWrapper { ) -> Result { let mut store = create_store(engine, max_memory_size); let instance = instance_pre.instantiate(&mut store).map_err(|error| { - WasmError::Other( - format!("failed to instantiate a new WASM module instance: {}", error,), - ) + WasmError::Other(format!( + "failed to instantiate a new WASM module instance: {:#}", + error, + )) })?; let memory = get_linear_memory(&instance, &mut store)?; diff --git a/client/executor/wasmtime/src/runtime.rs b/client/executor/wasmtime/src/runtime.rs index c63a802df07cc..fb5c6cb16993a 100644 --- a/client/executor/wasmtime/src/runtime.rs +++ b/client/executor/wasmtime/src/runtime.rs @@ -257,12 +257,12 @@ fn setup_wasmtime_caching( let wasmtime_cache_root = cache_path.join("wasmtime"); fs::create_dir_all(&wasmtime_cache_root) - .map_err(|err| format!("cannot create the dirs to cache: {:?}", err))?; + .map_err(|err| format!("cannot create the dirs to cache: {}", err))?; // Canonicalize the path after creating the directories. let wasmtime_cache_root = wasmtime_cache_root .canonicalize() - .map_err(|err| format!("failed to canonicalize the path: {:?}", err))?; + .map_err(|err| format!("failed to canonicalize the path: {}", err))?; // Write the cache config file let cache_config_path = wasmtime_cache_root.join("cache-config.toml"); @@ -275,11 +275,11 @@ directory = \"{cache_dir}\" cache_dir = wasmtime_cache_root.display() ); fs::write(&cache_config_path, config_content) - .map_err(|err| format!("cannot write the cache config: {:?}", err))?; + .map_err(|err| format!("cannot write the cache config: {}", err))?; config .cache_config_load(cache_config_path) - .map_err(|err| format!("failed to parse the config: {:?}", err))?; + .map_err(|err| format!("failed to parse the config: {:#}", err))?; Ok(()) } @@ -304,14 +304,14 @@ fn common_config(semantics: &Semantics) -> std::result::Result { @@ -626,7 +626,7 @@ where let serialized_blob = blob.clone().serialize(); let module = wasmtime::Module::new(&engine, &serialized_blob) - .map_err(|e| WasmError::Other(format!("cannot create module: {}", e)))?; + .map_err(|e| WasmError::Other(format!("cannot create module: {:#}", e)))?; match config.semantics.instantiation_strategy { InstantiationStrategy::LegacyInstanceReuse => { @@ -664,7 +664,7 @@ where // // See [`create_runtime_from_artifact`] for more details. let module = wasmtime::Module::deserialize_file(&engine, compiled_artifact_path) - .map_err(|e| WasmError::Other(format!("cannot deserialize module: {}", e)))?; + .map_err(|e| WasmError::Other(format!("cannot deserialize module: {:#}", e)))?; (module, InternalInstantiationStrategy::Builtin) }, @@ -677,7 +677,7 @@ where crate::instance_wrapper::create_store(module.engine(), config.semantics.max_memory_size); let instance_pre = linker .instantiate_pre(&mut store, &module) - .map_err(|e| WasmError::Other(format!("cannot preinstantiate module: {}", e)))?; + .map_err(|e| WasmError::Other(format!("cannot preinstantiate module: {:#}", e)))?; Ok(WasmtimeRuntime { engine, @@ -729,11 +729,11 @@ pub fn prepare_runtime_artifact( let blob = prepare_blob_for_compilation(blob, &semantics)?; let engine = Engine::new(&common_config(&semantics)?) - .map_err(|e| WasmError::Other(format!("cannot create the engine: {}", e)))?; + .map_err(|e| WasmError::Other(format!("cannot create the engine: {:#}", e)))?; engine .precompile_module(&blob.serialize()) - .map_err(|e| WasmError::Other(format!("cannot precompile module: {}", e))) + .map_err(|e| WasmError::Other(format!("cannot precompile module: {:#}", e))) } fn perform_call( From 1a16a7d0711b7480c105a5f0256135c8b8db5e3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20St=C3=BCber?= Date: Mon, 18 Jul 2022 14:56:22 +0200 Subject: [PATCH 403/484] Fix invalid state transitions in pallet-bounties (#11630) * Pallet-bounty: disallow invalid state transitions * Fix tests: funding only at even block numbers * Fix benchmarks: bounties need to be funded * fix on_initialize Co-authored-by: Shawn Tabrizi --- frame/bounties/src/lib.rs | 2 +- frame/bounties/src/tests.rs | 8 ++++---- frame/child-bounties/src/benchmarking.rs | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/frame/bounties/src/lib.rs b/frame/bounties/src/lib.rs index 8e28cacb9d929..fca758fd96b8e 100644 --- a/frame/bounties/src/lib.rs +++ b/frame/bounties/src/lib.rs @@ -390,7 +390,7 @@ pub mod pallet { Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { let mut bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; match bounty.status { - BountyStatus::Proposed | BountyStatus::Approved | BountyStatus::Funded => {}, + BountyStatus::Funded => {}, _ => return Err(Error::::UnexpectedStatus.into()), }; diff --git a/frame/bounties/src/tests.rs b/frame/bounties/src/tests.rs index ff220600794d4..b4ce039b35fbc 100644 --- a/frame/bounties/src/tests.rs +++ b/frame/bounties/src/tests.rs @@ -1126,8 +1126,8 @@ fn accept_curator_handles_different_deposit_calculations() { assert_ok!(Bounties::propose_bounty(Origin::signed(0), value, b"12345".to_vec())); assert_ok!(Bounties::approve_bounty(Origin::root(), bounty_index)); - System::set_block_number(3); - >::on_initialize(3); + System::set_block_number(4); + >::on_initialize(4); assert_ok!(Bounties::propose_curator(Origin::root(), bounty_index, user, fee)); assert_ok!(Bounties::accept_curator(Origin::signed(user), bounty_index)); @@ -1150,8 +1150,8 @@ fn accept_curator_handles_different_deposit_calculations() { assert_ok!(Bounties::propose_bounty(Origin::signed(0), value, b"12345".to_vec())); assert_ok!(Bounties::approve_bounty(Origin::root(), bounty_index)); - System::set_block_number(3); - >::on_initialize(3); + System::set_block_number(6); + >::on_initialize(6); assert_ok!(Bounties::propose_curator(Origin::root(), bounty_index, user, fee)); assert_ok!(Bounties::accept_curator(Origin::signed(user), bounty_index)); diff --git a/frame/child-bounties/src/benchmarking.rs b/frame/child-bounties/src/benchmarking.rs index dcb54361fac89..ca5af50276b9d 100644 --- a/frame/child-bounties/src/benchmarking.rs +++ b/frame/child-bounties/src/benchmarking.rs @@ -221,7 +221,7 @@ benchmarks! { unassign_curator { setup_pot_account::(); let bounty_setup = activate_child_bounty::(0, T::MaximumReasonLength::get())?; - Bounties::::on_initialize(T::BlockNumber::zero()); + Treasury::::on_initialize(T::BlockNumber::zero()); frame_system::Pallet::::set_block_number(T::BountyUpdatePeriod::get() + 1u32.into()); let caller = whitelisted_caller(); }: _(RawOrigin::Signed(caller), bounty_setup.bounty_id, @@ -295,7 +295,7 @@ benchmarks! { close_child_bounty_active { setup_pot_account::(); let bounty_setup = activate_child_bounty::(0, T::MaximumReasonLength::get())?; - Bounties::::on_initialize(T::BlockNumber::zero()); + Treasury::::on_initialize(T::BlockNumber::zero()); }: close_child_bounty(RawOrigin::Root, bounty_setup.bounty_id, bounty_setup.child_bounty_id) verify { assert_last_event::(Event::Canceled { From c1846253f386945b03a32c394df39e371659fe46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Mon, 18 Jul 2022 21:25:18 +0200 Subject: [PATCH 404/484] Disable the interest cache (#11854) * Disable the interest cache The feature is currently broken with the latest `tracing-core`. We will enable it again, when it is fixed upstream. * FMT --- Cargo.lock | 8 +++----- client/tracing/Cargo.toml | 2 +- client/tracing/src/logging/mod.rs | 5 +---- primitives/io/Cargo.toml | 2 +- primitives/runtime-interface/test/Cargo.toml | 2 +- primitives/tracing/Cargo.toml | 2 +- 6 files changed, 8 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 791d8037f3335..279864e056b0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11066,11 +11066,11 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.26" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" +checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" dependencies = [ - "lazy_static", + "once_cell", "valuable", ] @@ -11090,10 +11090,8 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" dependencies = [ - "ahash", "lazy_static", "log", - "lru", "tracing-core", ] diff --git a/client/tracing/Cargo.toml b/client/tracing/Cargo.toml index 3f0bbbe922d3f..476e03ee741f3 100644 --- a/client/tracing/Cargo.toml +++ b/client/tracing/Cargo.toml @@ -26,7 +26,7 @@ rustc-hash = "1.1.0" serde = "1.0.136" thiserror = "1.0.30" tracing = "0.1.29" -tracing-log = { version = "0.1.3", features = ["interest-cache"] } +tracing-log = "0.1.3" tracing-subscriber = { version = "0.2.25", features = ["parking_lot"] } sc-client-api = { version = "4.0.0-dev", path = "../api" } sc-rpc-server = { version = "4.0.0-dev", path = "../rpc-servers" } diff --git a/client/tracing/src/logging/mod.rs b/client/tracing/src/logging/mod.rs index 33c83dd87189e..58941617bfb6a 100644 --- a/client/tracing/src/logging/mod.rs +++ b/client/tracing/src/logging/mod.rs @@ -155,10 +155,7 @@ where let max_level_hint = Layer::::max_level_hint(&env_filter); let max_level = to_log_level_filter(max_level_hint); - tracing_log::LogTracer::builder() - .with_max_level(max_level) - .with_interest_cache(tracing_log::InterestCacheConfig::default()) - .init()?; + tracing_log::LogTracer::builder().with_max_level(max_level).init()?; // If we're only logging `INFO` entries then we'll use a simplified logging format. let detailed_output = match max_level_hint { diff --git a/primitives/io/Cargo.toml b/primitives/io/Cargo.toml index 09a087d509416..03cd8535665ac 100644 --- a/primitives/io/Cargo.toml +++ b/primitives/io/Cargo.toml @@ -32,7 +32,7 @@ futures = { version = "0.3.21", features = ["thread-pool"], optional = true } parking_lot = { version = "0.12.0", optional = true } secp256k1 = { version = "0.21.2", features = ["recovery", "global-context"], optional = true } tracing = { version = "0.1.29", default-features = false } -tracing-core = { version = "0.1.26", default-features = false} +tracing-core = { version = "0.1.28", default-features = false} [features] default = ["std"] diff --git a/primitives/runtime-interface/test/Cargo.toml b/primitives/runtime-interface/test/Cargo.toml index e897fc0bab71c..880d03902b421 100644 --- a/primitives/runtime-interface/test/Cargo.toml +++ b/primitives/runtime-interface/test/Cargo.toml @@ -13,7 +13,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] tracing = "0.1.29" -tracing-core = "0.1.26" +tracing-core = "0.1.28" sc-executor = { version = "0.10.0-dev", path = "../../../client/executor" } sc-executor-common = { version = "0.10.0-dev", path = "../../../client/executor/common" } sp-io = { version = "6.0.0", path = "../../io" } diff --git a/primitives/tracing/Cargo.toml b/primitives/tracing/Cargo.toml index d305b756e2d68..c2ca57d2b5a43 100644 --- a/primitives/tracing/Cargo.toml +++ b/primitives/tracing/Cargo.toml @@ -23,7 +23,7 @@ codec = { version = "3.0.0", package = "parity-scale-codec", default-features = "derive", ] } tracing = { version = "0.1.29", default-features = false } -tracing-core = { version = "0.1.26", default-features = false } +tracing-core = { version = "0.1.28", default-features = false } tracing-subscriber = { version = "0.2.25", optional = true, features = [ "tracing-log", ] } From 7a05fdbca03c21c54bb3c495d21f3d7924cc7184 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 19 Jul 2022 08:10:15 +0200 Subject: [PATCH 405/484] Add `benchmark extrinsic` command (#11456) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Benchmark extrinsic Signed-off-by: Oliver Tale-Yazdi * Reduce warmup and repeat Running this 1000 times with a full block takes ~33 minutes 🙈. Reducing it to ~3 minutes per default. Signed-off-by: Oliver Tale-Yazdi * Make ExistentialDeposit public Signed-off-by: Oliver Tale-Yazdi * Add 'bechmark extrinsic' command Signed-off-by: Oliver Tale-Yazdi * fmt Signed-off-by: Oliver Tale-Yazdi * Add --list and cleanup Signed-off-by: Oliver Tale-Yazdi * Unrelated Clippy Signed-off-by: Oliver Tale-Yazdi * Clippy Signed-off-by: Oliver Tale-Yazdi * Fix tests and doc Signed-off-by: Oliver Tale-Yazdi * Move implementations up + fmt Signed-off-by: Oliver Tale-Yazdi * Dont use parameter_types macro Signed-off-by: Oliver Tale-Yazdi * Cache to_lowercase() call The .to_lowercase() on the builder is actually not needes since its already documented to only return lower case. Signed-off-by: Oliver Tale-Yazdi * Spelling Signed-off-by: Oliver Tale-Yazdi * Use correct nightly for fmt... Signed-off-by: Oliver Tale-Yazdi * Rename ED Signed-off-by: Oliver Tale-Yazdi * Fix compile Signed-off-by: Oliver Tale-Yazdi * Subtract block base weight Signed-off-by: Oliver Tale-Yazdi * Fixes Signed-off-by: Oliver Tale-Yazdi * Use tmp folder for test This should already be the case since --dev is passed but somehow not... Signed-off-by: Oliver Tale-Yazdi * Fix test Signed-off-by: Oliver Tale-Yazdi --- .../{command_helper.rs => benchmarking.rs} | 66 ++++++++- bin/node-template/node/src/command.rs | 26 +++- bin/node-template/node/src/main.rs | 2 +- bin/node-template/runtime/src/lib.rs | 5 +- .../{command_helper.rs => benchmarking.rs} | 70 +++++++-- bin/node/cli/src/command.rs | 23 ++- bin/node/cli/src/lib.rs | 4 +- .../cli/tests/benchmark_extrinsic_works.rs | 46 ++++++ .../src/{overhead => extrinsic}/bench.rs | 107 +++++++------- .../benchmarking-cli/src/extrinsic/cmd.rs | 134 ++++++++++++++++++ .../src/extrinsic/extrinsic_factory.rs | 70 +++++++++ .../benchmarking-cli/src/extrinsic/mod.rs | 27 ++++ utils/frame/benchmarking-cli/src/lib.rs | 7 +- .../benchmarking-cli/src/overhead/cmd.rs | 52 +++++-- .../benchmarking-cli/src/overhead/mod.rs | 5 +- .../benchmarking-cli/src/overhead/template.rs | 2 +- 16 files changed, 537 insertions(+), 109 deletions(-) rename bin/node-template/node/src/{command_helper.rs => benchmarking.rs} (71%) rename bin/node/cli/src/{command_helper.rs => benchmarking.rs} (52%) create mode 100644 bin/node/cli/tests/benchmark_extrinsic_works.rs rename utils/frame/benchmarking-cli/src/{overhead => extrinsic}/bench.rs (70%) create mode 100644 utils/frame/benchmarking-cli/src/extrinsic/cmd.rs create mode 100644 utils/frame/benchmarking-cli/src/extrinsic/extrinsic_factory.rs create mode 100644 utils/frame/benchmarking-cli/src/extrinsic/mod.rs diff --git a/bin/node-template/node/src/command_helper.rs b/bin/node-template/node/src/benchmarking.rs similarity index 71% rename from bin/node-template/node/src/command_helper.rs rename to bin/node-template/node/src/benchmarking.rs index 287e81b1e96bd..f0e32104cd3ee 100644 --- a/bin/node-template/node/src/command_helper.rs +++ b/bin/node-template/node/src/benchmarking.rs @@ -16,13 +16,14 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! Contains code to setup the command invocations in [`super::command`] which would -//! otherwise bloat that module. +//! Setup code for [`super::command`] which would otherwise bloat that module. +//! +//! Should only be used for benchmarking as it may break in other contexts. use crate::service::FullClient; use node_template_runtime as runtime; -use runtime::SystemCall; +use runtime::{AccountId, Balance, BalancesCall, SystemCall}; use sc_cli::Result; use sc_client_api::BlockBackend; use sp_core::{Encode, Pair}; @@ -35,19 +36,27 @@ use std::{sync::Arc, time::Duration}; /// Generates extrinsics for the `benchmark overhead` command. /// /// Note: Should only be used for benchmarking. -pub struct BenchmarkExtrinsicBuilder { +pub struct RemarkBuilder { client: Arc, } -impl BenchmarkExtrinsicBuilder { +impl RemarkBuilder { /// Creates a new [`Self`] from the given client. pub fn new(client: Arc) -> Self { Self { client } } } -impl frame_benchmarking_cli::ExtrinsicBuilder for BenchmarkExtrinsicBuilder { - fn remark(&self, nonce: u32) -> std::result::Result { +impl frame_benchmarking_cli::ExtrinsicBuilder for RemarkBuilder { + fn pallet(&self) -> &str { + "system" + } + + fn extrinsic(&self) -> &str { + "remark" + } + + fn build(&self, nonce: u32) -> std::result::Result { let acc = Sr25519Keyring::Bob.pair(); let extrinsic: OpaqueExtrinsic = create_benchmark_extrinsic( self.client.as_ref(), @@ -61,6 +70,49 @@ impl frame_benchmarking_cli::ExtrinsicBuilder for BenchmarkExtrinsicBuilder { } } +/// Generates `Balances::TransferKeepAlive` extrinsics for the benchmarks. +/// +/// Note: Should only be used for benchmarking. +pub struct TransferKeepAliveBuilder { + client: Arc, + dest: AccountId, + value: Balance, +} + +impl TransferKeepAliveBuilder { + /// Creates a new [`Self`] from the given client. + pub fn new(client: Arc, dest: AccountId, value: Balance) -> Self { + Self { client, dest, value } + } +} + +impl frame_benchmarking_cli::ExtrinsicBuilder for TransferKeepAliveBuilder { + fn pallet(&self) -> &str { + "balances" + } + + fn extrinsic(&self) -> &str { + "transfer_keep_alive" + } + + fn build(&self, nonce: u32) -> std::result::Result { + let acc = Sr25519Keyring::Bob.pair(); + let extrinsic: OpaqueExtrinsic = create_benchmark_extrinsic( + self.client.as_ref(), + acc, + BalancesCall::transfer_keep_alive { + dest: self.dest.clone().into(), + value: self.value.into(), + } + .into(), + nonce, + ) + .into(); + + Ok(extrinsic) + } +} + /// Create a transaction using the given `call`. /// /// Note: Should only be used for benchmarking. diff --git a/bin/node-template/node/src/command.rs b/bin/node-template/node/src/command.rs index e3e10007929e6..142f0b40c325e 100644 --- a/bin/node-template/node/src/command.rs +++ b/bin/node-template/node/src/command.rs @@ -1,14 +1,14 @@ use crate::{ + benchmarking::{inherent_benchmark_data, RemarkBuilder, TransferKeepAliveBuilder}, chain_spec, cli::{Cli, Subcommand}, - command_helper::{inherent_benchmark_data, BenchmarkExtrinsicBuilder}, service, }; -use frame_benchmarking_cli::{BenchmarkCmd, SUBSTRATE_REFERENCE_HARDWARE}; -use node_template_runtime::Block; +use frame_benchmarking_cli::{BenchmarkCmd, ExtrinsicFactory, SUBSTRATE_REFERENCE_HARDWARE}; +use node_template_runtime::{Block, EXISTENTIAL_DEPOSIT}; use sc_cli::{ChainSpec, RuntimeVersion, SubstrateCli}; use sc_service::PartialComponents; -use std::sync::Arc; +use sp_keyring::Sr25519Keyring; impl SubstrateCli for Cli { fn impl_name() -> String { @@ -137,9 +137,23 @@ pub fn run() -> sc_cli::Result<()> { }, BenchmarkCmd::Overhead(cmd) => { let PartialComponents { client, .. } = service::new_partial(&config)?; - let ext_builder = BenchmarkExtrinsicBuilder::new(client.clone()); + let ext_builder = RemarkBuilder::new(client.clone()); - cmd.run(config, client, inherent_benchmark_data()?, Arc::new(ext_builder)) + cmd.run(config, client, inherent_benchmark_data()?, &ext_builder) + }, + BenchmarkCmd::Extrinsic(cmd) => { + let PartialComponents { client, .. } = service::new_partial(&config)?; + // Register the *Remark* and *TKA* builders. + let ext_factory = ExtrinsicFactory(vec![ + Box::new(RemarkBuilder::new(client.clone())), + Box::new(TransferKeepAliveBuilder::new( + client.clone(), + Sr25519Keyring::Alice.to_account_id(), + EXISTENTIAL_DEPOSIT, + )), + ]); + + cmd.run(client, inherent_benchmark_data()?, &ext_factory) }, BenchmarkCmd::Machine(cmd) => cmd.run(&config, SUBSTRATE_REFERENCE_HARDWARE.clone()), diff --git a/bin/node-template/node/src/main.rs b/bin/node-template/node/src/main.rs index 0f2fbd5a909c6..426cbabb6fbf7 100644 --- a/bin/node-template/node/src/main.rs +++ b/bin/node-template/node/src/main.rs @@ -4,9 +4,9 @@ mod chain_spec; #[macro_use] mod service; +mod benchmarking; mod cli; mod command; -mod command_helper; mod rpc; fn main() -> sc_cli::Result<()> { diff --git a/bin/node-template/runtime/src/lib.rs b/bin/node-template/runtime/src/lib.rs index c514cdf6c25fd..a2f595f571a90 100644 --- a/bin/node-template/runtime/src/lib.rs +++ b/bin/node-template/runtime/src/lib.rs @@ -234,6 +234,9 @@ impl pallet_timestamp::Config for Runtime { type WeightInfo = (); } +/// Existential deposit. +pub const EXISTENTIAL_DEPOSIT: u128 = 500; + impl pallet_balances::Config for Runtime { type MaxLocks = ConstU32<50>; type MaxReserves = (); @@ -243,7 +246,7 @@ impl pallet_balances::Config for Runtime { /// The ubiquitous event type. type Event = Event; type DustRemoval = (); - type ExistentialDeposit = ConstU128<500>; + type ExistentialDeposit = ConstU128; type AccountStore = System; type WeightInfo = pallet_balances::weights::SubstrateWeight; } diff --git a/bin/node/cli/src/command_helper.rs b/bin/node/cli/src/benchmarking.rs similarity index 52% rename from bin/node/cli/src/command_helper.rs rename to bin/node/cli/src/benchmarking.rs index 84d85ee367cab..52657016c046a 100644 --- a/bin/node/cli/src/command_helper.rs +++ b/bin/node/cli/src/benchmarking.rs @@ -16,12 +16,14 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! Contains code to setup the command invocations in [`super::command`] which would -//! otherwise bloat that module. +//! Setup code for [`super::command`] which would otherwise bloat that module. +//! +//! Should only be used for benchmarking as it may break in other contexts. use crate::service::{create_extrinsic, FullClient}; -use node_runtime::SystemCall; +use node_primitives::{AccountId, Balance}; +use node_runtime::{BalancesCall, SystemCall}; use sc_cli::Result; use sp_inherents::{InherentData, InherentDataProvider}; use sp_keyring::Sr25519Keyring; @@ -29,20 +31,30 @@ use sp_runtime::OpaqueExtrinsic; use std::{sync::Arc, time::Duration}; -/// Generates extrinsics for the `benchmark overhead` command. -pub struct BenchmarkExtrinsicBuilder { +/// Generates `System::Remark` extrinsics for the benchmarks. +/// +/// Note: Should only be used for benchmarking. +pub struct RemarkBuilder { client: Arc, } -impl BenchmarkExtrinsicBuilder { +impl RemarkBuilder { /// Creates a new [`Self`] from the given client. pub fn new(client: Arc) -> Self { Self { client } } } -impl frame_benchmarking_cli::ExtrinsicBuilder for BenchmarkExtrinsicBuilder { - fn remark(&self, nonce: u32) -> std::result::Result { +impl frame_benchmarking_cli::ExtrinsicBuilder for RemarkBuilder { + fn pallet(&self) -> &str { + "system" + } + + fn extrinsic(&self) -> &str { + "remark" + } + + fn build(&self, nonce: u32) -> std::result::Result { let acc = Sr25519Keyring::Bob.pair(); let extrinsic: OpaqueExtrinsic = create_extrinsic( self.client.as_ref(), @@ -56,6 +68,48 @@ impl frame_benchmarking_cli::ExtrinsicBuilder for BenchmarkExtrinsicBuilder { } } +/// Generates `Balances::TransferKeepAlive` extrinsics for the benchmarks. +/// +/// Note: Should only be used for benchmarking. +pub struct TransferKeepAliveBuilder { + client: Arc, + dest: AccountId, + value: Balance, +} + +impl TransferKeepAliveBuilder { + /// Creates a new [`Self`] from the given client. + pub fn new(client: Arc, dest: AccountId, value: Balance) -> Self { + Self { client, dest, value } + } +} + +impl frame_benchmarking_cli::ExtrinsicBuilder for TransferKeepAliveBuilder { + fn pallet(&self) -> &str { + "balances" + } + + fn extrinsic(&self) -> &str { + "transfer_keep_alive" + } + + fn build(&self, nonce: u32) -> std::result::Result { + let acc = Sr25519Keyring::Bob.pair(); + let extrinsic: OpaqueExtrinsic = create_extrinsic( + self.client.as_ref(), + acc, + BalancesCall::transfer_keep_alive { + dest: self.dest.clone().into(), + value: self.value.into(), + }, + Some(nonce), + ) + .into(); + + Ok(extrinsic) + } +} + /// Generates inherent data for the `benchmark overhead` command. pub fn inherent_benchmark_data() -> Result { let mut inherent_data = InherentData::new(); diff --git a/bin/node/cli/src/command.rs b/bin/node/cli/src/command.rs index b17a26fa02935..df3d5a891e2ab 100644 --- a/bin/node/cli/src/command.rs +++ b/bin/node/cli/src/command.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use super::command_helper::{inherent_benchmark_data, BenchmarkExtrinsicBuilder}; +use super::benchmarking::{inherent_benchmark_data, RemarkBuilder, TransferKeepAliveBuilder}; use crate::{ chain_spec, service, service::{new_partial, FullClient}, @@ -25,9 +25,10 @@ use crate::{ use frame_benchmarking_cli::*; use node_executor::ExecutorDispatch; use node_primitives::Block; -use node_runtime::RuntimeApi; +use node_runtime::{ExistentialDeposit, RuntimeApi}; use sc_cli::{ChainSpec, Result, RuntimeVersion, SubstrateCli}; use sc_service::PartialComponents; +use sp_keyring::Sr25519Keyring; use std::sync::Arc; @@ -126,9 +127,23 @@ pub fn run() -> Result<()> { }, BenchmarkCmd::Overhead(cmd) => { let PartialComponents { client, .. } = new_partial(&config)?; - let ext_builder = BenchmarkExtrinsicBuilder::new(client.clone()); + let ext_builder = RemarkBuilder::new(client.clone()); - cmd.run(config, client, inherent_benchmark_data()?, Arc::new(ext_builder)) + cmd.run(config, client, inherent_benchmark_data()?, &ext_builder) + }, + BenchmarkCmd::Extrinsic(cmd) => { + let PartialComponents { client, .. } = service::new_partial(&config)?; + // Register the *Remark* and *TKA* builders. + let ext_factory = ExtrinsicFactory(vec![ + Box::new(RemarkBuilder::new(client.clone())), + Box::new(TransferKeepAliveBuilder::new( + client.clone(), + Sr25519Keyring::Alice.to_account_id(), + ExistentialDeposit::get(), + )), + ]); + + cmd.run(client, inherent_benchmark_data()?, &ext_factory) }, BenchmarkCmd::Machine(cmd) => cmd.run(&config, SUBSTRATE_REFERENCE_HARDWARE.clone()), diff --git a/bin/node/cli/src/lib.rs b/bin/node/cli/src/lib.rs index 06c0bcccbc296..13c074268e50f 100644 --- a/bin/node/cli/src/lib.rs +++ b/bin/node/cli/src/lib.rs @@ -35,11 +35,11 @@ pub mod chain_spec; #[macro_use] pub mod service; #[cfg(feature = "cli")] +mod benchmarking; +#[cfg(feature = "cli")] mod cli; #[cfg(feature = "cli")] mod command; -#[cfg(feature = "cli")] -mod command_helper; #[cfg(feature = "cli")] pub use cli::*; diff --git a/bin/node/cli/tests/benchmark_extrinsic_works.rs b/bin/node/cli/tests/benchmark_extrinsic_works.rs new file mode 100644 index 0000000000000..69800ad3c6c13 --- /dev/null +++ b/bin/node/cli/tests/benchmark_extrinsic_works.rs @@ -0,0 +1,46 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use assert_cmd::cargo::cargo_bin; +use std::process::Command; +use tempfile::tempdir; + +/// Tests that the `benchmark extrinsic` command works for +/// remark and transfer_keep_alive within the substrate dev runtime. +#[test] +fn benchmark_extrinsic_works() { + benchmark_extrinsic("system", "remark"); + benchmark_extrinsic("balances", "transfer_keep_alive"); +} + +/// Checks that the `benchmark extrinsic` command works for the given pallet and extrinsic. +fn benchmark_extrinsic(pallet: &str, extrinsic: &str) { + let base_dir = tempdir().expect("could not create a temp dir"); + + let status = Command::new(cargo_bin("substrate")) + .args(&["benchmark", "extrinsic", "--dev"]) + .arg("-d") + .arg(base_dir.path()) + .args(&["--pallet", pallet, "--extrinsic", extrinsic]) + // Run with low repeats for faster execution. + .args(["--warmup=10", "--repeat=10", "--max-ext-per-block=10"]) + .status() + .unwrap(); + + assert!(status.success()); +} diff --git a/utils/frame/benchmarking-cli/src/overhead/bench.rs b/utils/frame/benchmarking-cli/src/extrinsic/bench.rs similarity index 70% rename from utils/frame/benchmarking-cli/src/overhead/bench.rs rename to utils/frame/benchmarking-cli/src/extrinsic/bench.rs index be7dac24021cf..27fc40fb34774 100644 --- a/utils/frame/benchmarking-cli/src/overhead/bench.rs +++ b/utils/frame/benchmarking-cli/src/extrinsic/bench.rs @@ -36,8 +36,8 @@ use log::info; use serde::Serialize; use std::{marker::PhantomData, sync::Arc, time::Instant}; -use super::cmd::ExtrinsicBuilder; -use crate::shared::Stats; +use super::ExtrinsicBuilder; +use crate::shared::{StatSelect, Stats}; /// Parameters to configure an *overhead* benchmark. #[derive(Debug, Default, Serialize, Clone, PartialEq, Args)] @@ -60,21 +60,11 @@ pub struct BenchmarkParams { /// The results of multiple runs in nano seconds. pub(crate) type BenchRecord = Vec; -/// Type of a benchmark. -#[derive(Serialize, Clone, PartialEq, Copy)] -pub(crate) enum BenchmarkType { - /// Measure the per-extrinsic execution overhead. - Extrinsic, - /// Measure the per-block execution overhead. - Block, -} - /// Holds all objects needed to run the *overhead* benchmarks. pub(crate) struct Benchmark { client: Arc, params: BenchmarkParams, inherent_data: sp_inherents::InherentData, - ext_builder: Arc, _p: PhantomData<(Block, BA)>, } @@ -90,23 +80,51 @@ where client: Arc, params: BenchmarkParams, inherent_data: sp_inherents::InherentData, - ext_builder: Arc, ) -> Self { - Self { client, params, inherent_data, ext_builder, _p: PhantomData } + Self { client, params, inherent_data, _p: PhantomData } } - /// Run the specified benchmark. - pub fn bench(&self, bench_type: BenchmarkType) -> Result { - let (block, num_ext) = self.build_block(bench_type)?; - let record = self.measure_block(&block, num_ext, bench_type)?; + /// Benchmark a block with only inherents. + pub fn bench_block(&self) -> Result { + let (block, _) = self.build_block(None)?; + let record = self.measure_block(&block)?; Stats::new(&record) } - /// Builds a block for the given benchmark type. + /// Benchmark the time of an extrinsic in a full block. + /// + /// First benchmarks an empty block, analogous to `bench_block` and use it as baseline. + /// Then benchmarks a full block built with the given `ext_builder` and subtracts the baseline + /// from the result. + /// This is necessary to account for the time the inherents use. + pub fn bench_extrinsic(&self, ext_builder: &dyn ExtrinsicBuilder) -> Result { + let (block, _) = self.build_block(None)?; + let base = self.measure_block(&block)?; + let base_time = Stats::new(&base)?.select(StatSelect::Average); + + let (block, num_ext) = self.build_block(Some(ext_builder))?; + let num_ext = num_ext.ok_or_else(|| Error::Input("Block was empty".into()))?; + let mut records = self.measure_block(&block)?; + + for r in &mut records { + // Subtract the base time. + *r = r.saturating_sub(base_time); + // Divide by the number of extrinsics in the block. + *r = ((*r as f64) / (num_ext as f64)).ceil() as u64; + } + + Stats::new(&records) + } + + /// Builds a block with some optional extrinsics. /// /// Returns the block and the number of extrinsics in the block /// that are not inherents. - fn build_block(&self, bench_type: BenchmarkType) -> Result<(Block, u64)> { + /// Returns a block with only inherents if `ext_builder` is `None`. + fn build_block( + &self, + ext_builder: Option<&dyn ExtrinsicBuilder>, + ) -> Result<(Block, Option)> { let mut builder = self.client.new_block(Default::default())?; // Create and insert the inherents. let inherents = builder.create_inherents(self.inherent_data.clone())?; @@ -114,16 +132,18 @@ where builder.push(inherent)?; } - // Return early if we just want a block with inherents and no additional extrinsics. - if bench_type == BenchmarkType::Block { - return Ok((builder.build()?.block, 0)) - } + // Return early if `ext_builder` is `None`. + let ext_builder = if let Some(ext_builder) = ext_builder { + ext_builder + } else { + return Ok((builder.build()?.block, None)) + }; // Put as many extrinsics into the block as possible and count them. info!("Building block, this takes some time..."); let mut num_ext = 0; for nonce in 0..self.max_ext_per_block() { - let ext = self.ext_builder.remark(nonce)?; + let ext = ext_builder.build(nonce)?; match builder.push(ext.clone()) { Ok(()) => {}, Err(ApplyExtrinsicFailed(Validity(TransactionValidityError::Invalid( @@ -139,20 +159,12 @@ where info!("Extrinsics per block: {}", num_ext); let block = builder.build()?.block; - Ok((block, num_ext)) + Ok((block, Some(num_ext))) } /// Measures the time that it take to execute a block or an extrinsic. - fn measure_block( - &self, - block: &Block, - num_ext: u64, - bench_type: BenchmarkType, - ) -> Result { + fn measure_block(&self, block: &Block) -> Result { let mut record = BenchRecord::new(); - if bench_type == BenchmarkType::Extrinsic && num_ext == 0 { - return Err("Cannot measure the extrinsic time of an empty block".into()) - } let genesis = BlockId::Number(Zero::zero()); info!("Running {} warmups...", self.params.warmup); @@ -176,12 +188,7 @@ where .map_err(|e| Error::Client(RuntimeApiError(e)))?; let elapsed = start.elapsed().as_nanos(); - if bench_type == BenchmarkType::Extrinsic { - // Checked for non-zero div above. - record.push((elapsed as f64 / num_ext as f64).ceil() as u64); - } else { - record.push(elapsed as u64); - } + record.push(elapsed as u64); } Ok(record) @@ -191,21 +198,3 @@ where self.params.max_ext_per_block.unwrap_or(u32::MAX) } } - -impl BenchmarkType { - /// Short name of the benchmark type. - pub(crate) fn short_name(&self) -> &'static str { - match self { - Self::Extrinsic => "extrinsic", - Self::Block => "block", - } - } - - /// Long name of the benchmark type. - pub(crate) fn long_name(&self) -> &'static str { - match self { - Self::Extrinsic => "ExtrinsicBase", - Self::Block => "BlockExecution", - } - } -} diff --git a/utils/frame/benchmarking-cli/src/extrinsic/cmd.rs b/utils/frame/benchmarking-cli/src/extrinsic/cmd.rs new file mode 100644 index 0000000000000..6b4fd0fad6638 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/extrinsic/cmd.rs @@ -0,0 +1,134 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use sc_block_builder::{BlockBuilderApi, BlockBuilderProvider}; +use sc_cli::{CliConfiguration, ImportParams, Result, SharedParams}; +use sc_client_api::Backend as ClientBackend; +use sp_api::{ApiExt, ProvideRuntimeApi}; +use sp_runtime::{traits::Block as BlockT, OpaqueExtrinsic}; + +use clap::{Args, Parser}; +use log::info; +use serde::Serialize; +use std::{fmt::Debug, sync::Arc}; + +use super::{ + bench::{Benchmark, BenchmarkParams}, + extrinsic_factory::ExtrinsicFactory, +}; + +/// Benchmark the execution time of different extrinsics. +/// +/// This is calculated by filling a block with a specific extrinsic and executing the block. +/// The result time is then divided by the number of extrinsics in that block. +/// +/// NOTE: The BlockExecutionWeight is ignored in this case since it +// is very small compared to the total block execution time. +#[derive(Debug, Parser)] +pub struct ExtrinsicCmd { + #[allow(missing_docs)] + #[clap(flatten)] + pub shared_params: SharedParams, + + #[allow(missing_docs)] + #[clap(flatten)] + pub import_params: ImportParams, + + #[allow(missing_docs)] + #[clap(flatten)] + pub params: ExtrinsicParams, +} + +/// The params for the [`ExtrinsicCmd`]. +#[derive(Debug, Default, Serialize, Clone, PartialEq, Args)] +pub struct ExtrinsicParams { + #[clap(flatten)] + pub bench: BenchmarkParams, + + /// List all available pallets and extrinsics. + /// + /// The format is CSV with header `pallet, extrinsic`. + #[clap(long)] + pub list: bool, + + /// Pallet name of the extrinsic to benchmark. + #[clap(long, value_name = "PALLET", required_unless_present = "list")] + pub pallet: Option, + + /// Extrinsic to benchmark. + #[clap(long, value_name = "EXTRINSIC", required_unless_present = "list")] + pub extrinsic: Option, +} + +impl ExtrinsicCmd { + /// Benchmark the execution time of a specific type of extrinsic. + /// + /// The output will be printed to console. + pub fn run( + &self, + client: Arc, + inherent_data: sp_inherents::InherentData, + ext_factory: &ExtrinsicFactory, + ) -> Result<()> + where + Block: BlockT, + BA: ClientBackend, + C: BlockBuilderProvider + ProvideRuntimeApi, + C::Api: ApiExt + BlockBuilderApi, + { + // Short circuit if --list was specified. + if self.params.list { + let list: Vec = ext_factory.0.iter().map(|b| b.name()).collect(); + info!( + "Listing available extrinsics ({}):\npallet, extrinsic\n{}", + list.len(), + list.join("\n") + ); + return Ok(()) + } + + let pallet = self.params.pallet.clone().unwrap_or_default(); + let extrinsic = self.params.extrinsic.clone().unwrap_or_default(); + let ext_builder = match ext_factory.try_get(&pallet, &extrinsic) { + Some(ext_builder) => ext_builder, + None => + return Err("Unknown pallet or extrinsic. Use --list for a complete list.".into()), + }; + + let bench = Benchmark::new(client, self.params.bench.clone(), inherent_data); + let stats = bench.bench_extrinsic(ext_builder)?; + info!( + "Executing a {}::{} extrinsic takes[ns]:\n{:?}", + ext_builder.pallet(), + ext_builder.extrinsic(), + stats + ); + + Ok(()) + } +} + +// Boilerplate +impl CliConfiguration for ExtrinsicCmd { + fn shared_params(&self) -> &SharedParams { + &self.shared_params + } + + fn import_params(&self) -> Option<&ImportParams> { + Some(&self.import_params) + } +} diff --git a/utils/frame/benchmarking-cli/src/extrinsic/extrinsic_factory.rs b/utils/frame/benchmarking-cli/src/extrinsic/extrinsic_factory.rs new file mode 100644 index 0000000000000..7e1a22ccd1419 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/extrinsic/extrinsic_factory.rs @@ -0,0 +1,70 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Provides the [`ExtrinsicFactory`] and the [`ExtrinsicBuilder`] types. +//! Is used by the *overhead* and *extrinsic* benchmarks to build extrinsics. + +use sp_runtime::OpaqueExtrinsic; + +/// Helper to manage [`ExtrinsicBuilder`] instances. +#[derive(Default)] +pub struct ExtrinsicFactory(pub Vec>); + +impl ExtrinsicFactory { + /// Returns a builder for a pallet and extrinsic name. + /// + /// Is case in-sensitive. + pub fn try_get(&self, pallet: &str, extrinsic: &str) -> Option<&dyn ExtrinsicBuilder> { + let pallet = pallet.to_lowercase(); + let extrinsic = extrinsic.to_lowercase(); + + self.0 + .iter() + .find(|b| b.pallet() == pallet && b.extrinsic() == extrinsic) + .map(|b| b.as_ref()) + } +} + +/// Used by the benchmark to build signed extrinsics. +/// +/// The built extrinsics only need to be valid in the first block +/// who's parent block is the genesis block. +/// This assumption simplifies the generation of the extrinsics. +/// The signer should be one of the pre-funded dev accounts. +pub trait ExtrinsicBuilder { + /// Name of the pallet this builder is for. + /// + /// Should be all lowercase. + fn pallet(&self) -> &str; + + /// Name of the extrinsic this builder is for. + /// + /// Should be all lowercase. + fn extrinsic(&self) -> &str; + + /// Builds an extrinsic. + /// + /// Will be called multiple times with increasing nonces. + fn build(&self, nonce: u32) -> std::result::Result; +} + +impl dyn ExtrinsicBuilder + '_ { + /// Name of this builder in CSV format: `pallet, extrinsic`. + pub fn name(&self) -> String { + format!("{}, {}", self.pallet(), self.extrinsic()) + } +} diff --git a/utils/frame/benchmarking-cli/src/extrinsic/mod.rs b/utils/frame/benchmarking-cli/src/extrinsic/mod.rs new file mode 100644 index 0000000000000..12a09c3b1af63 --- /dev/null +++ b/utils/frame/benchmarking-cli/src/extrinsic/mod.rs @@ -0,0 +1,27 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Benchmark the time it takes to execute a specific extrinsic. +//! This is a generalization of the *overhead* benchmark which can only measure `System::Remark` +//! extrinsics. + +pub mod bench; +pub mod cmd; +pub mod extrinsic_factory; + +pub use cmd::ExtrinsicCmd; +pub use extrinsic_factory::{ExtrinsicBuilder, ExtrinsicFactory}; diff --git a/utils/frame/benchmarking-cli/src/lib.rs b/utils/frame/benchmarking-cli/src/lib.rs index d0eee3d2939fc..96c7a997895d4 100644 --- a/utils/frame/benchmarking-cli/src/lib.rs +++ b/utils/frame/benchmarking-cli/src/lib.rs @@ -18,6 +18,7 @@ //! Contains the root [`BenchmarkCmd`] command and exports its sub-commands. mod block; +mod extrinsic; mod machine; mod overhead; mod pallet; @@ -25,8 +26,9 @@ mod shared; mod storage; pub use block::BlockCmd; +pub use extrinsic::{ExtrinsicBuilder, ExtrinsicCmd, ExtrinsicFactory}; pub use machine::{MachineCmd, Requirements, SUBSTRATE_REFERENCE_HARDWARE}; -pub use overhead::{ExtrinsicBuilder, OverheadCmd}; +pub use overhead::OverheadCmd; pub use pallet::PalletCmd; pub use storage::StorageCmd; @@ -41,8 +43,8 @@ pub enum BenchmarkCmd { Storage(StorageCmd), Overhead(OverheadCmd), Block(BlockCmd), - #[clap(hide = true)] // Hidden until fully completed. Machine(MachineCmd), + Extrinsic(ExtrinsicCmd), } /// Unwraps a [`BenchmarkCmd`] into its concrete sub-command. @@ -58,6 +60,7 @@ macro_rules! unwrap_cmd { BenchmarkCmd::Overhead($cmd) => $code, BenchmarkCmd::Block($cmd) => $code, BenchmarkCmd::Machine($cmd) => $code, + BenchmarkCmd::Extrinsic($cmd) => $code, } } } diff --git a/utils/frame/benchmarking-cli/src/overhead/cmd.rs b/utils/frame/benchmarking-cli/src/overhead/cmd.rs index 357c89d97a5ac..28ceea63f1572 100644 --- a/utils/frame/benchmarking-cli/src/overhead/cmd.rs +++ b/utils/frame/benchmarking-cli/src/overhead/cmd.rs @@ -31,10 +31,11 @@ use serde::Serialize; use std::{fmt::Debug, sync::Arc}; use crate::{ - overhead::{ - bench::{Benchmark, BenchmarkParams, BenchmarkType}, - template::TemplateData, + extrinsic::{ + bench::{Benchmark, BenchmarkParams as ExtrinsicBenchmarkParams}, + ExtrinsicBuilder, }, + overhead::template::TemplateData, shared::{HostInfoParams, WeightParams}, }; @@ -63,20 +64,20 @@ pub struct OverheadParams { #[allow(missing_docs)] #[clap(flatten)] - pub bench: BenchmarkParams, + pub bench: ExtrinsicBenchmarkParams, #[allow(missing_docs)] #[clap(flatten)] pub hostinfo: HostInfoParams, } -/// Used by the benchmark to build signed extrinsics. -/// -/// The built extrinsics only need to be valid in the first block -/// who's parent block is the genesis block. -pub trait ExtrinsicBuilder { - /// Build a `System::remark` extrinsic. - fn remark(&self, nonce: u32) -> std::result::Result; +/// Type of a benchmark. +#[derive(Serialize, Clone, PartialEq, Copy)] +pub(crate) enum BenchmarkType { + /// Measure the per-extrinsic execution overhead. + Extrinsic, + /// Measure the per-block execution overhead. + Block, } impl OverheadCmd { @@ -89,7 +90,7 @@ impl OverheadCmd { cfg: Configuration, client: Arc, inherent_data: sp_inherents::InherentData, - ext_builder: Arc, + ext_builder: &dyn ExtrinsicBuilder, ) -> Result<()> where Block: BlockT, @@ -97,18 +98,21 @@ impl OverheadCmd { C: BlockBuilderProvider + ProvideRuntimeApi, C::Api: ApiExt + BlockBuilderApi, { - let bench = Benchmark::new(client, self.params.bench.clone(), inherent_data, ext_builder); + if ext_builder.pallet() != "system" || ext_builder.extrinsic() != "remark" { + return Err(format!("The extrinsic builder is required to build `System::Remark` extrinsics but builds `{}` extrinsics instead", ext_builder.name()).into()); + } + let bench = Benchmark::new(client, self.params.bench.clone(), inherent_data); // per-block execution overhead { - let stats = bench.bench(BenchmarkType::Block)?; + let stats = bench.bench_block()?; info!("Per-block execution overhead [ns]:\n{:?}", stats); let template = TemplateData::new(BenchmarkType::Block, &cfg, &self.params, &stats)?; template.write(&self.params.weight.weight_path)?; } // per-extrinsic execution overhead { - let stats = bench.bench(BenchmarkType::Extrinsic)?; + let stats = bench.bench_extrinsic(ext_builder)?; info!("Per-extrinsic execution overhead [ns]:\n{:?}", stats); let template = TemplateData::new(BenchmarkType::Extrinsic, &cfg, &self.params, &stats)?; template.write(&self.params.weight.weight_path)?; @@ -118,6 +122,24 @@ impl OverheadCmd { } } +impl BenchmarkType { + /// Short name of the benchmark type. + pub(crate) fn short_name(&self) -> &'static str { + match self { + Self::Extrinsic => "extrinsic", + Self::Block => "block", + } + } + + /// Long name of the benchmark type. + pub(crate) fn long_name(&self) -> &'static str { + match self { + Self::Extrinsic => "ExtrinsicBase", + Self::Block => "BlockExecution", + } + } +} + // Boilerplate impl CliConfiguration for OverheadCmd { fn shared_params(&self) -> &SharedParams { diff --git a/utils/frame/benchmarking-cli/src/overhead/mod.rs b/utils/frame/benchmarking-cli/src/overhead/mod.rs index abdeac22b7898..fc3db912f7a30 100644 --- a/utils/frame/benchmarking-cli/src/overhead/mod.rs +++ b/utils/frame/benchmarking-cli/src/overhead/mod.rs @@ -15,8 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -mod bench; pub mod cmd; -mod template; +pub mod template; -pub use cmd::{ExtrinsicBuilder, OverheadCmd}; +pub use cmd::OverheadCmd; diff --git a/utils/frame/benchmarking-cli/src/overhead/template.rs b/utils/frame/benchmarking-cli/src/overhead/template.rs index 33c2c7999039a..aa82e45cf6db9 100644 --- a/utils/frame/benchmarking-cli/src/overhead/template.rs +++ b/utils/frame/benchmarking-cli/src/overhead/template.rs @@ -27,7 +27,7 @@ use serde::Serialize; use std::{env, fs, path::PathBuf}; use crate::{ - overhead::{bench::BenchmarkType, cmd::OverheadParams}, + overhead::cmd::{BenchmarkType, OverheadParams}, shared::{Stats, UnderscoreHelper}, }; From 83b536db482af47d40f2a805990997d7b7db27ea Mon Sep 17 00:00:00 2001 From: Vlad Date: Tue, 19 Jul 2022 15:47:51 +0300 Subject: [PATCH 406/484] Don't download artifacts into the `rusty-cachier-notify` job (#11850) --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fff11e07326d8..9608d88e9554d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -229,6 +229,7 @@ rusty-cachier-notify: variables: CI_IMAGE: paritytech/rusty-cachier-env:latest GIT_STRATEGY: none + dependencies: [] script: - curl -s https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.parity.io/parity/infrastructure/ci_cd/rusty-cachier/client/-/raw/release/util/install.sh | bash - rusty-cachier cache notify From 3f749373cca9cf4572b36bcba1a608c867672341 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Wed, 20 Jul 2022 10:50:54 +0200 Subject: [PATCH 407/484] Bump wasmtime default-pages (#11864) Signed-off-by: Oliver Tale-Yazdi --- client/executor/wasmtime/src/runtime.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/executor/wasmtime/src/runtime.rs b/client/executor/wasmtime/src/runtime.rs index fb5c6cb16993a..dcf07c454e46d 100644 --- a/client/executor/wasmtime/src/runtime.rs +++ b/client/executor/wasmtime/src/runtime.rs @@ -366,7 +366,7 @@ fn common_config(semantics: &Semantics) -> std::result::Result Date: Wed, 20 Jul 2022 18:04:31 -0400 Subject: [PATCH 408/484] Make reading json genesis file faster (#11868) * Make reading json genesis file faster * Formatting * fmt --- client/chain-spec/src/chain_spec.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/client/chain-spec/src/chain_spec.rs b/client/chain-spec/src/chain_spec.rs index efb40d46f216a..5aafc28524dbf 100644 --- a/client/chain-spec/src/chain_spec.rs +++ b/client/chain-spec/src/chain_spec.rs @@ -61,7 +61,16 @@ impl GenesisSource { let file = File::open(path).map_err(|e| { format!("Error opening spec file at `{}`: {}", path.display(), e) })?; - let genesis: GenesisContainer = json::from_reader(file) + // SAFETY: `mmap` is fundamentally unsafe since technically the file can change + // underneath us while it is mapped; in practice it's unlikely to be a + // problem + let bytes = unsafe { + memmap2::Mmap::map(&file).map_err(|e| { + format!("Error mmaping spec file `{}`: {}", path.display(), e) + })? + }; + + let genesis: GenesisContainer = json::from_slice(&bytes) .map_err(|e| format!("Error parsing spec file: {}", e))?; Ok(genesis.genesis) }, From 1e69b7a42f20ff34473c2c13007325d857277ec1 Mon Sep 17 00:00:00 2001 From: Dmitry Markin Date: Thu, 21 Jul 2022 11:36:00 +0300 Subject: [PATCH 409/484] Cleanup light client leftovers (#11865) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove --light cli option * Cleanup light client leftovers * Remove commented-out code and clean-up more light client leftovers * Fix formatting with `cargo +nightly fmt` * Remove FIXME regarding db directory structure Co-authored-by: Bastian Köcher Co-authored-by: Bastian Köcher --- client/cli/src/commands/run_cmd.rs | 17 +--- client/cli/src/config.rs | 8 +- .../src/communication/gossip.rs | 14 +-- client/network/src/config.rs | 8 -- client/network/src/protocol/message.rs | 1 - client/network/src/service.rs | 15 +-- client/network/src/transactions.rs | 13 --- client/rpc-api/src/system/helpers.rs | 2 - client/service/src/builder.rs | 93 +++++++------------ client/service/src/lib.rs | 7 -- client/service/src/metrics.rs | 2 +- 11 files changed, 44 insertions(+), 136 deletions(-) diff --git a/client/cli/src/commands/run_cmd.rs b/client/cli/src/commands/run_cmd.rs index 12774eecc4174..99eaa4be1cd0f 100644 --- a/client/cli/src/commands/run_cmd.rs +++ b/client/cli/src/commands/run_cmd.rs @@ -50,10 +50,6 @@ pub struct RunCmd { #[clap(long)] pub no_grandpa: bool, - /// Experimental: Run in light client mode. - #[clap(long)] - pub light: bool, - /// Listen to all RPC interfaces. /// /// Default is local. Note: not all RPC methods are safe to be exposed publicly. Use an RPC @@ -337,7 +333,7 @@ impl CliConfiguration for RunCmd { fn dev_key_seed(&self, is_dev: bool) -> Result> { Ok(self.get_keyring().map(|a| format!("//{}", a)).or_else(|| { - if is_dev && !self.light { + if is_dev { Some("//Alice".into()) } else { None @@ -363,16 +359,9 @@ impl CliConfiguration for RunCmd { fn role(&self, is_dev: bool) -> Result { let keyring = self.get_keyring(); - let is_light = self.light; - let is_authority = (self.validator || is_dev || keyring.is_some()) && !is_light; + let is_authority = self.validator || is_dev || keyring.is_some(); - Ok(if is_light { - sc_service::Role::Light - } else if is_authority { - sc_service::Role::Authority - } else { - sc_service::Role::Full - }) + Ok(if is_authority { sc_service::Role::Authority } else { sc_service::Role::Full }) } fn force_authoring(&self) -> Result { diff --git a/client/cli/src/config.rs b/client/cli/src/config.rs index 01c541bf75255..4ebbc8c72c19a 100644 --- a/client/cli/src/config.rs +++ b/client/cli/src/config.rs @@ -211,12 +211,8 @@ pub trait CliConfiguration: Sized { base_path: &PathBuf, cache_size: usize, database: Database, - role: &Role, ) -> Result { - let role_dir = match role { - Role::Light => "light", - Role::Full | Role::Authority => "full", - }; + let role_dir = "full"; let rocksdb_path = base_path.join("db").join(role_dir); let paritydb_path = base_path.join("paritydb").join(role_dir); Ok(match database { @@ -536,7 +532,7 @@ pub trait CliConfiguration: Sized { )?, keystore_remote, keystore, - database: self.database_config(&config_dir, database_cache_size, database, &role)?, + database: self.database_config(&config_dir, database_cache_size, database)?, state_cache_size: self.state_cache_size()?, state_cache_child_ratio: self.state_cache_child_ratio()?, state_pruning: self.state_pruning()?, diff --git a/client/finality-grandpa/src/communication/gossip.rs b/client/finality-grandpa/src/communication/gossip.rs index 250e640cbf037..65d7dfb783aa3 100644 --- a/client/finality-grandpa/src/communication/gossip.rs +++ b/client/finality-grandpa/src/communication/gossip.rs @@ -728,11 +728,7 @@ type MaybeMessage = Option<(Vec, NeighborPacket> impl Inner { fn new(config: crate::Config) -> Self { - let catch_up_config = if config.local_role.is_light() { - // if we are a light client we shouldn't be issuing any catch-up requests - // as we don't participate in the full GRANDPA protocol - CatchUpConfig::disabled() - } else if config.observer_enabled { + let catch_up_config = if config.observer_enabled { if config.local_role.is_authority() { // since the observer protocol is enabled, we will only issue // catch-up requests if we are an authority (and only to other @@ -1231,10 +1227,6 @@ impl Inner { None => return false, }; - if self.config.local_role.is_light() { - return false - } - if round_elapsed < round_duration.mul_f32(PROPAGATION_SOME) { self.peers.first_stage_peers.contains(who) } else if round_elapsed < round_duration.mul_f32(PROPAGATION_ALL) { @@ -1266,10 +1258,6 @@ impl Inner { None => return false, }; - if self.config.local_role.is_light() { - return false - } - if round_elapsed < round_duration.mul_f32(PROPAGATION_ALL) { self.peers.first_stage_peers.contains(who) || self.peers.second_stage_peers.contains(who) || diff --git a/client/network/src/config.rs b/client/network/src/config.rs index 430efd697a18c..58349fe973330 100644 --- a/client/network/src/config.rs +++ b/client/network/src/config.rs @@ -147,8 +147,6 @@ where pub enum Role { /// Regular full node. Full, - /// Regular light node. - Light, /// Actual authority. Authority, } @@ -158,18 +156,12 @@ impl Role { pub fn is_authority(&self) -> bool { matches!(self, Self::Authority { .. }) } - - /// True for [`Role::Light`]. - pub fn is_light(&self) -> bool { - matches!(self, Self::Light { .. }) - } } impl fmt::Display for Role { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Full => write!(f, "FULL"), - Self::Light => write!(f, "LIGHT"), Self::Authority { .. } => write!(f, "AUTHORITY"), } } diff --git a/client/network/src/protocol/message.rs b/client/network/src/protocol/message.rs index c9512f82e23bb..50c4a264a5f95 100644 --- a/client/network/src/protocol/message.rs +++ b/client/network/src/protocol/message.rs @@ -107,7 +107,6 @@ pub mod generic { fn from(roles: &'a crate::config::Role) -> Self { match roles { crate::config::Role::Full => Self::FULL, - crate::config::Role::Light => Self::LIGHT, crate::config::Role::Authority { .. } => Self::AUTHORITY, } } diff --git a/client/network/src/service.rs b/client/network/src/service.rs index 0d28b50df1821..bc6cfc73bec36 100644 --- a/client/network/src/service.rs +++ b/client/network/src/service.rs @@ -245,15 +245,11 @@ where }; let chain_sync = (params.create_chain_sync)( - if params.role.is_light() { - SyncMode::Light - } else { - match params.network_config.sync_mode { - config::SyncMode::Full => SyncMode::Full, - config::SyncMode::Fast { skip_proofs, storage_chain_mode } => - SyncMode::LightState { skip_proofs, storage_chain_mode }, - config::SyncMode::Warp => SyncMode::Warp, - } + match params.network_config.sync_mode { + config::SyncMode::Full => SyncMode::Full, + config::SyncMode::Fast { skip_proofs, storage_chain_mode } => + SyncMode::LightState { skip_proofs, storage_chain_mode }, + config::SyncMode::Warp => SyncMode::Warp, }, params.chain.clone(), warp_sync_provider, @@ -489,7 +485,6 @@ where let (tx_handler, tx_handler_controller) = transactions_handler_proto.build( service.clone(), - params.role, params.transaction_pool, params.metrics_registry.as_ref(), )?; diff --git a/client/network/src/transactions.rs b/client/network/src/transactions.rs index 1f54f05d7446f..043bdeff7ebfc 100644 --- a/client/network/src/transactions.rs +++ b/client/network/src/transactions.rs @@ -83,8 +83,6 @@ mod rep { pub const GOOD_TRANSACTION: Rep = Rep::new(1 << 7, "Good transaction"); /// Reputation change when a peer sends us a bad transaction. pub const BAD_TRANSACTION: Rep = Rep::new(-(1 << 12), "Bad transaction"); - /// We received an unexpected transaction packet. - pub const UNEXPECTED_TRANSACTIONS: Rep = Rep::new_fatal("Unexpected transactions packet"); } struct Metrics { @@ -160,7 +158,6 @@ impl TransactionsHandlerPrototype { pub fn build( self, service: Arc>, - local_role: config::Role, transaction_pool: Arc>, metrics_registry: Option<&Registry>, ) -> error::Result<(TransactionsHandler, TransactionsHandlerController)> { @@ -178,7 +175,6 @@ impl TransactionsHandlerPrototype { event_stream, peers: HashMap::new(), transaction_pool, - local_role, from_controller, metrics: if let Some(r) = metrics_registry { Some(Metrics::register(r)?) @@ -247,7 +243,6 @@ pub struct TransactionsHandler { peers: HashMap>, transaction_pool: Arc>, gossip_enabled: Arc, - local_role: config::Role, from_controller: mpsc::UnboundedReceiver>, /// Prometheus metrics. metrics: Option, @@ -360,14 +355,6 @@ impl TransactionsHandler { /// Called when peer sends us new transactions fn on_transactions(&mut self, who: PeerId, transactions: message::Transactions) { - // sending transaction to light node is considered a bad behavior - if matches!(self.local_role, config::Role::Light) { - debug!(target: "sync", "Peer {} is trying to send transactions to the light node", who); - self.service.disconnect_peer(who, self.protocol_name.clone()); - self.service.report_peer(who, rep::UNEXPECTED_TRANSACTIONS); - return - } - // Accept transactions only when enabled if !self.gossip_enabled.load(Ordering::Relaxed) { trace!(target: "sync", "{} Ignoring transactions while disabled", who); diff --git a/client/rpc-api/src/system/helpers.rs b/client/rpc-api/src/system/helpers.rs index e17e0f518c3e6..4561fccc1e81b 100644 --- a/client/rpc-api/src/system/helpers.rs +++ b/client/rpc-api/src/system/helpers.rs @@ -76,8 +76,6 @@ pub struct PeerInfo { pub enum NodeRole { /// The node is a full node Full, - /// The node is a light client - LightClient, /// The node is an authority Authority, } diff --git a/client/service/src/builder.rs b/client/service/src/builder.rs index fe0ae5db53e70..0a18943f45006 100644 --- a/client/service/src/builder.rs +++ b/client/service/src/builder.rs @@ -37,17 +37,12 @@ use sc_client_db::{Backend, DatabaseSettings}; use sc_consensus::import_queue::ImportQueue; use sc_executor::RuntimeVersionOf; use sc_keystore::LocalKeystore; -use sc_network::{ - config::{Role, SyncMode}, - NetworkService, -}; +use sc_network::{config::SyncMode, NetworkService}; use sc_network_common::sync::warp::WarpSyncProvider; -use sc_network_light::light_client_requests::{self, handler::LightClientRequestHandler}; +use sc_network_light::light_client_requests::handler::LightClientRequestHandler; use sc_network_sync::{ - block_request_handler::{self, BlockRequestHandler}, - state_request_handler::{self, StateRequestHandler}, - warp_request_handler::{self, RequestHandler as WarpSyncRequestHandler}, - ChainSync, + block_request_handler::BlockRequestHandler, state_request_handler::StateRequestHandler, + warp_request_handler::RequestHandler as WarpSyncRequestHandler, ChainSync, }; use sc_rpc::{ author::AuthorApiServer, @@ -731,11 +726,8 @@ where } } - let transaction_pool_adapter = Arc::new(TransactionPoolAdapter { - imports_external_transactions: !matches!(config.role, Role::Light), - pool: transaction_pool, - client: client.clone(), - }); + let transaction_pool_adapter = + Arc::new(TransactionPoolAdapter { pool: transaction_pool, client: client.clone() }); let protocol_id = config.protocol_id(); @@ -746,63 +738,42 @@ where }; let block_request_protocol_config = { - if matches!(config.role, Role::Light) { - // Allow outgoing requests but deny incoming requests. - block_request_handler::generate_protocol_config(&protocol_id) - } else { - // Allow both outgoing and incoming requests. - let (handler, protocol_config) = BlockRequestHandler::new( - &protocol_id, - client.clone(), - config.network.default_peers_set.in_peers as usize + - config.network.default_peers_set.out_peers as usize, - ); - spawn_handle.spawn("block-request-handler", Some("networking"), handler.run()); - protocol_config - } + // Allow both outgoing and incoming requests. + let (handler, protocol_config) = BlockRequestHandler::new( + &protocol_id, + client.clone(), + config.network.default_peers_set.in_peers as usize + + config.network.default_peers_set.out_peers as usize, + ); + spawn_handle.spawn("block-request-handler", Some("networking"), handler.run()); + protocol_config }; let state_request_protocol_config = { - if matches!(config.role, Role::Light) { - // Allow outgoing requests but deny incoming requests. - state_request_handler::generate_protocol_config(&protocol_id) - } else { - // Allow both outgoing and incoming requests. - let (handler, protocol_config) = StateRequestHandler::new( - &protocol_id, - client.clone(), - config.network.default_peers_set_num_full as usize, - ); - spawn_handle.spawn("state-request-handler", Some("networking"), handler.run()); - protocol_config - } + // Allow both outgoing and incoming requests. + let (handler, protocol_config) = StateRequestHandler::new( + &protocol_id, + client.clone(), + config.network.default_peers_set_num_full as usize, + ); + spawn_handle.spawn("state-request-handler", Some("networking"), handler.run()); + protocol_config }; let warp_sync_params = warp_sync.map(|provider| { - let protocol_config = if matches!(config.role, Role::Light) { - // Allow outgoing requests but deny incoming requests. - warp_request_handler::generate_request_response_config(protocol_id.clone()) - } else { - // Allow both outgoing and incoming requests. - let (handler, protocol_config) = - WarpSyncRequestHandler::new(protocol_id.clone(), provider.clone()); - spawn_handle.spawn("warp-sync-request-handler", Some("networking"), handler.run()); - protocol_config - }; + // Allow both outgoing and incoming requests. + let (handler, protocol_config) = + WarpSyncRequestHandler::new(protocol_id.clone(), provider.clone()); + spawn_handle.spawn("warp-sync-request-handler", Some("networking"), handler.run()); (provider, protocol_config) }); let light_client_request_protocol_config = { - if matches!(config.role, Role::Light) { - // Allow outgoing requests but deny incoming requests. - light_client_requests::generate_protocol_config(&protocol_id) - } else { - // Allow both outgoing and incoming requests. - let (handler, protocol_config) = - LightClientRequestHandler::new(&protocol_id, client.clone()); - spawn_handle.spawn("light-client-request-handler", Some("networking"), handler.run()); - protocol_config - } + // Allow both outgoing and incoming requests. + let (handler, protocol_config) = + LightClientRequestHandler::new(&protocol_id, client.clone()); + spawn_handle.spawn("light-client-request-handler", Some("networking"), handler.run()); + protocol_config }; let max_parallel_downloads = config.network.max_parallel_downloads; diff --git a/client/service/src/lib.rs b/client/service/src/lib.rs index 24ba670cfcd65..1b70227a70488 100644 --- a/client/service/src/lib.rs +++ b/client/service/src/lib.rs @@ -253,7 +253,6 @@ async fn build_network_future< let node_role = match role { Role::Authority { .. } => NodeRole::Authority, - Role::Light => NodeRole::LightClient, Role::Full => NodeRole::Full, }; @@ -377,7 +376,6 @@ where /// Transaction pool adapter. pub struct TransactionPoolAdapter { - imports_external_transactions: bool, pool: Arc

, client: Arc, } @@ -425,11 +423,6 @@ where } fn import(&self, transaction: B::Extrinsic) -> TransactionImportFuture { - if !self.imports_external_transactions { - debug!("Transaction rejected"); - return Box::pin(futures::future::ready(TransactionImport::None)) - } - let encoded = transaction.encode(); let uxt = match Decode::decode(&mut &encoded[..]) { Ok(uxt) => uxt, diff --git a/client/service/src/metrics.rs b/client/service/src/metrics.rs index 56b145ccdf7f7..ef3132f61ab99 100644 --- a/client/service/src/metrics.rs +++ b/client/service/src/metrics.rs @@ -160,7 +160,7 @@ impl MetricsService { ) -> Result { let role_bits = match config.role { Role::Full => 1u64, - Role::Light => 2u64, + // 2u64 used to represent light client role Role::Authority { .. } => 4u64, }; From 6b310c44a8d8bc18276675c7a323f29618f46e3a Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Thu, 21 Jul 2022 12:11:19 +0300 Subject: [PATCH 410/484] Stop RPC servers on drop (#11679) * Stop RPC servers on drop * Switch to existing wrappers that stop RPC servers * Apply formatting --- client/service/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/service/src/lib.rs b/client/service/src/lib.rs index 1b70227a70488..2e7b611ffb187 100644 --- a/client/service/src/lib.rs +++ b/client/service/src/lib.rs @@ -369,7 +369,8 @@ where match tokio::task::block_in_place(|| { config.tokio_handle.block_on(futures::future::try_join(http_fut, ws_fut)) }) { - Ok((http, ws)) => Ok(Box::new((http, ws))), + Ok((http, ws)) => + Ok(Box::new((waiting::HttpServer(Some(http)), waiting::WsServer(Some(ws))))), Err(e) => Err(Error::Application(e)), } } From d92987afcc721dfbc18b40f6455a42d5c5a159f5 Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Thu, 21 Jul 2022 15:35:18 +0200 Subject: [PATCH 411/484] [ci] Generate rustdocs without dependencies (#11885) --- scripts/ci/gitlab/pipeline/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ci/gitlab/pipeline/build.yml b/scripts/ci/gitlab/pipeline/build.yml index 25ecad5bc5264..eedd2ee0bb409 100644 --- a/scripts/ci/gitlab/pipeline/build.yml +++ b/scripts/ci/gitlab/pipeline/build.yml @@ -149,7 +149,7 @@ build-rustdoc: - ./crate-docs/ script: - rusty-cachier snapshot create - - time cargo +nightly doc --workspace --all-features --verbose + - time cargo +nightly doc --workspace --all-features --verbose --no-deps - rm -f $CARGO_TARGET_DIR/doc/.lock - mv $CARGO_TARGET_DIR/doc ./crate-docs # FIXME: remove me after CI image gets nonroot From 2ef1d84663eb0e22821fcf060379c23bd5550bd3 Mon Sep 17 00:00:00 2001 From: Robert Hambrock Date: Thu, 21 Jul 2022 17:09:21 +0200 Subject: [PATCH 412/484] pallet-mmr: fix batch proof failures (#11840) * pallet-mmr: extend batch proof verification test covers all possible 2-leaf combinations now, including current verification failures that batch proof item count limit is too low sometimes. * raise upper bound on proof item number as described in https://github.com/paritytech/substrate/issues/11753#issuecomment-1179838174 * test for powerset of leaves * refactor batch proof verification test * test all batch proofs for mmr sizes up to n=13 * limit mmr size to reduce batch proof test duration * use saturating integer addition for proof check * extract common chain building in batch proof tests note: right now, since not killing old chain, it keeps growing by 7 blocks for every leaf selection (added after proof generation), hence heavier to compute. * only add blocks after a proof generation once * increase batch proof testing range * register offchain extensions only once * fmt & remove unused util --- Cargo.lock | 1 + frame/merkle-mountain-range/Cargo.toml | 1 + frame/merkle-mountain-range/src/lib.rs | 2 +- .../merkle-mountain-range/src/mmr/storage.rs | 2 +- frame/merkle-mountain-range/src/mmr/utils.rs | 22 +----- frame/merkle-mountain-range/src/tests.rs | 70 ++++++++++++++----- 6 files changed, 58 insertions(+), 40 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 279864e056b0b..3d5bfe5e51201 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5871,6 +5871,7 @@ dependencies = [ "frame-support", "frame-system", "hex-literal", + "itertools", "parity-scale-codec", "scale-info", "sp-core", diff --git a/frame/merkle-mountain-range/Cargo.toml b/frame/merkle-mountain-range/Cargo.toml index f5f8bdee0855d..75301afed0094 100644 --- a/frame/merkle-mountain-range/Cargo.toml +++ b/frame/merkle-mountain-range/Cargo.toml @@ -27,6 +27,7 @@ sp-std = { version = "4.0.0", default-features = false, path = "../../primitives [dev-dependencies] env_logger = "0.9" hex-literal = "0.3" +itertools = "0.10.3" [features] default = ["std"] diff --git a/frame/merkle-mountain-range/src/lib.rs b/frame/merkle-mountain-range/src/lib.rs index 9274e8e72c508..4644ebcb7da1c 100644 --- a/frame/merkle-mountain-range/src/lib.rs +++ b/frame/merkle-mountain-range/src/lib.rs @@ -351,7 +351,7 @@ impl, I: 'static> Pallet { ) -> Result<(), primitives::Error> { if proof.leaf_count > Self::mmr_leaves() || proof.leaf_count == 0 || - proof.items.len() as u32 > mmr::utils::NodesUtils::new(proof.leaf_count).depth() + (proof.items.len().saturating_add(leaves.len())) as u64 > proof.leaf_count { return Err(primitives::Error::Verify .log_debug("The proof has incorrect number of leaves or proof items.")) diff --git a/frame/merkle-mountain-range/src/mmr/storage.rs b/frame/merkle-mountain-range/src/mmr/storage.rs index d31262d4d7f2f..8b623edf56957 100644 --- a/frame/merkle-mountain-range/src/mmr/storage.rs +++ b/frame/merkle-mountain-range/src/mmr/storage.rs @@ -136,7 +136,7 @@ where let window_size = ::BlockHashCount::get().unique_saturated_into(); if leaves >= window_size { - // Move the rolling window towards the end of `block_num->hash` mappings available + // Move the rolling window towards the end of `block_num->hash` mappings available // in the runtime: we "canonicalize" the leaf at the end, let to_canon_leaf = leaves.saturating_sub(window_size); // and all the nodes added by that leaf. diff --git a/frame/merkle-mountain-range/src/mmr/utils.rs b/frame/merkle-mountain-range/src/mmr/utils.rs index 3734ea514782d..0b8e88a9283da 100644 --- a/frame/merkle-mountain-range/src/mmr/utils.rs +++ b/frame/merkle-mountain-range/src/mmr/utils.rs @@ -46,15 +46,6 @@ impl NodesUtils { 2 * self.no_of_leaves - self.number_of_peaks() } - /// Calculate maximal depth of the MMR. - pub fn depth(&self) -> u32 { - if self.no_of_leaves == 0 { - return 0 - } - - 64 - self.no_of_leaves.next_power_of_two().leading_zeros() - } - /// Calculate `LeafIndex` for the leaf that added `node_index` to the MMR. pub fn leaf_index_that_added_node(node_index: NodeIndex) -> LeafIndex { let rightmost_leaf_pos = Self::rightmost_leaf_node_index_from_pos(node_index); @@ -128,18 +119,7 @@ mod tests { } #[test] - fn should_calculate_number_of_leaves_correctly() { - assert_eq!( - vec![0, 1, 2, 3, 4, 9, 15, 21] - .into_iter() - .map(|n| NodesUtils::new(n).depth()) - .collect::>(), - vec![0, 1, 2, 3, 3, 5, 5, 6] - ); - } - - #[test] - fn should_calculate_depth_correclty() { + fn should_calculate_depth_correctly() { assert_eq!( vec![0, 1, 2, 3, 4, 9, 15, 21] .into_iter() diff --git a/frame/merkle-mountain-range/src/tests.rs b/frame/merkle-mountain-range/src/tests.rs index 53226f8419988..566a051823d5e 100644 --- a/frame/merkle-mountain-range/src/tests.rs +++ b/frame/merkle-mountain-range/src/tests.rs @@ -347,28 +347,64 @@ fn should_verify() { } #[test] -fn should_verify_batch_proof() { +fn should_verify_batch_proofs() { + fn generate_and_verify_batch_proof( + ext: &mut sp_io::TestExternalities, + leaves: &Vec, + blocks_to_add: usize, + ) { + let (leaves, proof) = ext + .execute_with(|| crate::Pallet::::generate_batch_proof(leaves.to_vec()).unwrap()); + + ext.execute_with(|| { + add_blocks(blocks_to_add); + // then + assert_eq!(crate::Pallet::::verify_leaves(leaves, proof), Ok(())); + }) + } + let _ = env_logger::try_init(); - // Start off with chain initialisation and storing indexing data off-chain - // (MMR Leafs) - let mut ext = new_test_ext(); - ext.execute_with(|| add_blocks(7)); - ext.persist_offchain_overlay(); + use itertools::Itertools; - // Try to generate proof now. This requires the offchain extensions to be present - // to retrieve full leaf data. + let mut ext = new_test_ext(); + // require the offchain extensions to be present + // to retrieve full leaf data when generating proofs register_offchain_ext(&mut ext); - let (leaves, proof) = ext.execute_with(|| { - // when - crate::Pallet::::generate_batch_proof(vec![0, 4, 5]).unwrap() - }); - ext.execute_with(|| { - add_blocks(7); - // then - assert_eq!(crate::Pallet::::verify_leaves(leaves, proof), Ok(())); - }); + // verify that up to n=10, valid proofs are generated for all possible leaf combinations + for n in 0..10 { + ext.execute_with(|| new_block()); + ext.persist_offchain_overlay(); + + // generate powerset (skipping empty set) of all possible leaf combinations for mmr size n + let leaves_set: Vec> = (0..n).into_iter().powerset().skip(1).collect(); + + leaves_set.iter().for_each(|leaves_subset| { + generate_and_verify_batch_proof(&mut ext, leaves_subset, 0); + ext.persist_offchain_overlay(); + }); + } + + // verify that up to n=15, valid proofs are generated for all possible 2-leaf combinations + for n in 10..15 { + // (MMR Leafs) + ext.execute_with(|| new_block()); + ext.persist_offchain_overlay(); + + // generate all possible 2-leaf combinations for mmr size n + let leaves_set: Vec> = (0..n).into_iter().combinations(2).collect(); + + leaves_set.iter().for_each(|leaves_subset| { + generate_and_verify_batch_proof(&mut ext, leaves_subset, 0); + ext.persist_offchain_overlay(); + }); + } + + generate_and_verify_batch_proof(&mut ext, &vec![7, 11], 20); + ext.execute_with(|| add_blocks(1000)); + ext.persist_offchain_overlay(); + generate_and_verify_batch_proof(&mut ext, &vec![7, 11, 100, 800], 100); } #[test] From a79f3481e3e532930a41213137b1a667469be9f8 Mon Sep 17 00:00:00 2001 From: Dmitry Markin Date: Fri, 22 Jul 2022 15:08:52 +0300 Subject: [PATCH 413/484] Clarify wrong bootnode peer ID error message (#11872) --- client/network/src/service.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/client/network/src/service.rs b/client/network/src/service.rs index bc6cfc73bec36..aeb5e25497bb3 100644 --- a/client/network/src/service.rs +++ b/client/network/src/service.rs @@ -2020,12 +2020,16 @@ where if this.boot_node_ids.contains(&peer_id) { if let DialError::WrongPeerId { obtained, endpoint } = &error { - error!( - "💔 The bootnode you want to connect provided a different peer ID than the one you expect: `{}` with `{}`:`{:?}`.", - peer_id, - obtained, - endpoint, - ); + if let ConnectedPoint::Dialer { address, role_override: _ } = + endpoint + { + error!( + "💔 The bootnode you want to connect to at `{}` provided a different peer ID `{}` than the one you expect `{}`.", + address, + obtained, + peer_id, + ); + } } } } From c159456010d246e677deeb6c33175c47ec2437ff Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Sat, 23 Jul 2022 20:01:57 +0200 Subject: [PATCH 414/484] Fix for 'note_applied_extrinsic' - consider also 'extract_actual_pays_fee' (#11849) --- frame/support/src/weights.rs | 37 +++++++++++ frame/system/src/lib.rs | 5 +- frame/system/src/tests.rs | 119 ++++++++++++++++++++++++++++++++++- 3 files changed, 157 insertions(+), 4 deletions(-) diff --git a/frame/support/src/weights.rs b/frame/support/src/weights.rs index 24240c7cc4e6a..dd49409ccc912 100644 --- a/frame/support/src/weights.rs +++ b/frame/support/src/weights.rs @@ -358,6 +358,15 @@ pub fn extract_actual_weight(result: &DispatchResultWithPostInfo, info: &Dispatc .calc_actual_weight(info) } +/// Extract the actual pays_fee from a dispatch result if any or fall back to the default weight. +pub fn extract_actual_pays_fee(result: &DispatchResultWithPostInfo, info: &DispatchInfo) -> Pays { + match result { + Ok(post_info) => post_info, + Err(err) => &err.post_info, + } + .pays_fee(info) +} + impl From<(Option, Pays)> for PostDispatchInfo { fn from(post_weight_info: (Option, Pays)) -> Self { let (actual_weight, pays_fee) = post_weight_info; @@ -956,6 +965,34 @@ mod tests { ); } + #[test] + fn extract_actual_pays_fee_works() { + let pre = DispatchInfo { weight: 1000, ..Default::default() }; + assert_eq!(extract_actual_pays_fee(&Ok(Some(7).into()), &pre), Pays::Yes); + assert_eq!(extract_actual_pays_fee(&Ok(Some(1000).into()), &pre), Pays::Yes); + assert_eq!(extract_actual_pays_fee(&Ok((Some(1000), Pays::Yes).into()), &pre), Pays::Yes); + assert_eq!(extract_actual_pays_fee(&Ok((Some(1000), Pays::No).into()), &pre), Pays::No); + assert_eq!( + extract_actual_pays_fee(&Err(DispatchError::BadOrigin.with_weight(9)), &pre), + Pays::Yes + ); + assert_eq!( + extract_actual_pays_fee( + &Err(DispatchErrorWithPostInfo { + post_info: PostDispatchInfo { actual_weight: None, pays_fee: Pays::No }, + error: DispatchError::BadOrigin, + }), + &pre + ), + Pays::No + ); + + let pre = DispatchInfo { weight: 1000, pays_fee: Pays::No, ..Default::default() }; + assert_eq!(extract_actual_pays_fee(&Ok(Some(7).into()), &pre), Pays::No); + assert_eq!(extract_actual_pays_fee(&Ok(Some(1000).into()), &pre), Pays::No); + assert_eq!(extract_actual_pays_fee(&Ok((Some(1000), Pays::Yes).into()), &pre), Pays::No); + } + type Balance = u64; // 0.5x^3 + 2.333x^2 + 7x - 10_000 diff --git a/frame/system/src/lib.rs b/frame/system/src/lib.rs index 3c6f514808b5d..94605c2da59bd 100644 --- a/frame/system/src/lib.rs +++ b/frame/system/src/lib.rs @@ -91,8 +91,8 @@ use frame_support::{ OriginTrait, PalletInfo, SortedMembers, StoredMap, TypedGet, }, weights::{ - extract_actual_weight, DispatchClass, DispatchInfo, PerDispatchClass, RuntimeDbWeight, - Weight, + extract_actual_pays_fee, extract_actual_weight, DispatchClass, DispatchInfo, + PerDispatchClass, RuntimeDbWeight, Weight, }, Parameter, }; @@ -1500,6 +1500,7 @@ impl Pallet { /// To be called immediately after an extrinsic has been applied. pub fn note_applied_extrinsic(r: &DispatchResultWithPostInfo, mut info: DispatchInfo) { info.weight = extract_actual_weight(r, &info); + info.pays_fee = extract_actual_pays_fee(r, &info); Self::deposit_event(match r { Ok(_) => Event::ExtrinsicSuccess { dispatch_info: info }, Err(err) => { diff --git a/frame/system/src/tests.rs b/frame/system/src/tests.rs index 0facd796b2a0c..417dca12045ee 100644 --- a/frame/system/src/tests.rs +++ b/frame/system/src/tests.rs @@ -17,7 +17,9 @@ use crate::*; use frame_support::{ - assert_noop, assert_ok, dispatch::PostDispatchInfo, weights::WithPostDispatchInfo, + assert_noop, assert_ok, + dispatch::PostDispatchInfo, + weights::{Pays, WithPostDispatchInfo}, }; use mock::{Origin, *}; use sp_core::H256; @@ -216,7 +218,7 @@ fn deposit_event_should_work() { } #[test] -fn deposit_event_uses_actual_weight() { +fn deposit_event_uses_actual_weight_and_pays_fee() { new_test_ext().execute_with(|| { System::reset_events(); System::initialize(&1, &[0u8; 32].into(), &Default::default()); @@ -230,8 +232,34 @@ fn deposit_event_uses_actual_weight() { &Ok(Some(1200).into()), pre_info, ); + System::note_applied_extrinsic(&Ok((Some(2_500_000), Pays::Yes).into()), pre_info); + System::note_applied_extrinsic(&Ok(Pays::No.into()), pre_info); + System::note_applied_extrinsic(&Ok((Some(2_500_000), Pays::No).into()), pre_info); + System::note_applied_extrinsic(&Ok((Some(500), Pays::No).into()), pre_info); System::note_applied_extrinsic(&Err(DispatchError::BadOrigin.with_weight(999)), pre_info); + System::note_applied_extrinsic( + &Err(DispatchErrorWithPostInfo { + post_info: PostDispatchInfo { actual_weight: None, pays_fee: Pays::Yes }, + error: DispatchError::BadOrigin, + }), + pre_info, + ); + System::note_applied_extrinsic( + &Err(DispatchErrorWithPostInfo { + post_info: PostDispatchInfo { actual_weight: Some(800), pays_fee: Pays::Yes }, + error: DispatchError::BadOrigin, + }), + pre_info, + ); + System::note_applied_extrinsic( + &Err(DispatchErrorWithPostInfo { + post_info: PostDispatchInfo { actual_weight: Some(800), pays_fee: Pays::No }, + error: DispatchError::BadOrigin, + }), + pre_info, + ); + assert_eq!( System::events(), vec![ @@ -261,6 +289,54 @@ fn deposit_event_uses_actual_weight() { }, EventRecord { phase: Phase::ApplyExtrinsic(3), + event: SysEvent::ExtrinsicSuccess { + dispatch_info: DispatchInfo { + weight: 1000, + pays_fee: Pays::Yes, + ..Default::default() + }, + } + .into(), + topics: vec![] + }, + EventRecord { + phase: Phase::ApplyExtrinsic(4), + event: SysEvent::ExtrinsicSuccess { + dispatch_info: DispatchInfo { + weight: 1000, + pays_fee: Pays::No, + ..Default::default() + }, + } + .into(), + topics: vec![] + }, + EventRecord { + phase: Phase::ApplyExtrinsic(5), + event: SysEvent::ExtrinsicSuccess { + dispatch_info: DispatchInfo { + weight: 1000, + pays_fee: Pays::No, + ..Default::default() + }, + } + .into(), + topics: vec![] + }, + EventRecord { + phase: Phase::ApplyExtrinsic(6), + event: SysEvent::ExtrinsicSuccess { + dispatch_info: DispatchInfo { + weight: 500, + pays_fee: Pays::No, + ..Default::default() + }, + } + .into(), + topics: vec![] + }, + EventRecord { + phase: Phase::ApplyExtrinsic(7), event: SysEvent::ExtrinsicFailed { dispatch_error: DispatchError::BadOrigin.into(), dispatch_info: DispatchInfo { weight: 999, ..Default::default() }, @@ -268,6 +344,45 @@ fn deposit_event_uses_actual_weight() { .into(), topics: vec![] }, + EventRecord { + phase: Phase::ApplyExtrinsic(8), + event: SysEvent::ExtrinsicFailed { + dispatch_error: DispatchError::BadOrigin.into(), + dispatch_info: DispatchInfo { + weight: 1000, + pays_fee: Pays::Yes, + ..Default::default() + }, + } + .into(), + topics: vec![] + }, + EventRecord { + phase: Phase::ApplyExtrinsic(9), + event: SysEvent::ExtrinsicFailed { + dispatch_error: DispatchError::BadOrigin.into(), + dispatch_info: DispatchInfo { + weight: 800, + pays_fee: Pays::Yes, + ..Default::default() + }, + } + .into(), + topics: vec![] + }, + EventRecord { + phase: Phase::ApplyExtrinsic(10), + event: SysEvent::ExtrinsicFailed { + dispatch_error: DispatchError::BadOrigin.into(), + dispatch_info: DispatchInfo { + weight: 800, + pays_fee: Pays::No, + ..Default::default() + }, + } + .into(), + topics: vec![] + }, ] ); }); From 2f8d2b7bc78a000087d6ee19b3aa797410582d0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Sat, 23 Jul 2022 21:28:25 +0200 Subject: [PATCH 415/484] pallet-assets: Update docs (#11877) --- frame/assets/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/assets/src/lib.rs b/frame/assets/src/lib.rs index 3f9146c5d229b..e0b00c5642c81 100644 --- a/frame/assets/src/lib.rs +++ b/frame/assets/src/lib.rs @@ -79,8 +79,6 @@ //! * `create`: Creates a new asset class, taking the required deposit. //! * `transfer`: Transfer sender's assets to another account. //! * `transfer_keep_alive`: Transfer sender's assets to another account, keeping the sender alive. -//! * `set_metadata`: Set the metadata of an asset class. -//! * `clear_metadata`: Remove the metadata of an asset class. //! * `approve_transfer`: Create or increase an delegated transfer. //! * `cancel_approval`: Rescind a previous approval. //! * `transfer_approved`: Transfer third-party's assets to another account. @@ -103,6 +101,8 @@ //! * `transfer_ownership`: Changes an asset class's Owner; called by the asset class's Owner. //! * `set_team`: Changes an asset class's Admin, Freezer and Issuer; called by the asset class's //! Owner. +//! * `set_metadata`: Set the metadata of an asset class; called by the asset class's Owner. +//! * `clear_metadata`: Remove the metadata of an asset class; called by the asset class's Owner. //! //! Please refer to the [`Call`] enum and its associated variants for documentation on each //! function. From 5a1e013c770ad4dae3bbe9c7e20dbaa98daf12b2 Mon Sep 17 00:00:00 2001 From: Ikko Ashimine Date: Mon, 25 Jul 2022 05:42:49 +0900 Subject: [PATCH 416/484] Fix typo in limits.rs (#11894) alway -> always --- frame/system/src/limits.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/system/src/limits.rs b/frame/system/src/limits.rs index d3c108afb6f32..6076414ba6bcb 100644 --- a/frame/system/src/limits.rs +++ b/frame/system/src/limits.rs @@ -369,7 +369,7 @@ impl BlockWeightsBuilder { /// /// This is to make sure that extrinsics don't stay forever in the pool, /// because they could seamingly fit the block (since they are below `max_block`), - /// but the cost of calling `on_initialize` alway prevents them from being included. + /// but the cost of calling `on_initialize` always prevents them from being included. pub fn avg_block_initialization(mut self, init_cost: Perbill) -> Self { self.init_cost = Some(init_cost); self From d7bddcb31898b5cb23fbcf628505ea8527b03bc2 Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Mon, 25 Jul 2022 11:15:47 +0200 Subject: [PATCH 417/484] [ci] fix node-bench-regression-guard job (#11901) * [Do not merge] [ci] debug node-bench-regression-guard * debug * fix artifacts path * add debug job * debug * debug * fix job, return pipeline --- scripts/ci/gitlab/pipeline/test.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/ci/gitlab/pipeline/test.yml b/scripts/ci/gitlab/pipeline/test.yml index dcec896ddc18b..d900d425ad494 100644 --- a/scripts/ci/gitlab/pipeline/test.yml +++ b/scripts/ci/gitlab/pipeline/test.yml @@ -129,7 +129,11 @@ node-bench-regression-guard: artifacts: true # polls artifact from master to compare with current result - project: $CI_PROJECT_PATH - job: cargo-check-benches + job: "cargo-check-benches 1/2" + ref: master + artifacts: true + - project: $CI_PROJECT_PATH + job: "cargo-check-benches 2/2" ref: master artifacts: true variables: From 92a19eec5d41f2009cac30207e77348c6f12f46d Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Mon, 25 Jul 2022 12:19:43 +0200 Subject: [PATCH 418/484] Remove unused leaves-set fields (#11895) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove unused leaves-set fields * Fix undo_import method Old leaf sould be inserted using the displaced number * Fix leaves count * Apply code review suggestions Co-authored-by: Bastian Köcher Co-authored-by: Bastian Köcher --- client/api/src/leaves.rs | 81 +++++++++++++++++++++++----------------- 1 file changed, 47 insertions(+), 34 deletions(-) diff --git a/client/api/src/leaves.rs b/client/api/src/leaves.rs index 5859290777433..2e5d4be3a5462 100644 --- a/client/api/src/leaves.rs +++ b/client/api/src/leaves.rs @@ -67,8 +67,6 @@ impl FinalizationDisplaced { #[derive(Debug, Clone, PartialEq, Eq)] pub struct LeafSet { storage: BTreeMap, Vec>, - pending_added: Vec<(H, N)>, - pending_removed: Vec, } impl LeafSet @@ -78,7 +76,7 @@ where { /// Construct a new, blank leaf set. pub fn new() -> Self { - Self { storage: BTreeMap::new(), pending_added: Vec::new(), pending_removed: Vec::new() } + Self { storage: BTreeMap::new() } } /// Read the leaf list from the DB, using given prefix for keys. @@ -97,21 +95,21 @@ where }, None => {}, } - Ok(Self { storage, pending_added: Vec::new(), pending_removed: Vec::new() }) + Ok(Self { storage }) } - /// update the leaf list on import. returns a displaced leaf if there was one. + /// Update the leaf list on import. + /// Returns a displaced leaf if there was one. pub fn import(&mut self, hash: H, number: N, parent_hash: H) -> Option> { // avoid underflow for genesis. let displaced = if number != N::zero() { - let new_number = Reverse(number.clone() - N::one()); - let was_displaced = self.remove_leaf(&new_number, &parent_hash); + let parent_number = Reverse(number.clone() - N::one()); + let was_displaced = self.remove_leaf(&parent_number, &parent_hash); if was_displaced { - self.pending_removed.push(parent_hash.clone()); Some(ImportDisplaced { new_hash: hash.clone(), - displaced: LeafSetItem { hash: parent_hash, number: new_number }, + displaced: LeafSetItem { hash: parent_hash, number: parent_number }, }) } else { None @@ -121,7 +119,6 @@ where }; self.insert_leaf(Reverse(number.clone()), hash.clone()); - self.pending_added.push((hash, number)); displaced } @@ -140,8 +137,6 @@ where }; let below_boundary = self.storage.split_off(&Reverse(boundary)); - self.pending_removed - .extend(below_boundary.values().flat_map(|h| h.iter()).cloned()); FinalizationDisplaced { leaves: below_boundary } } @@ -188,8 +183,6 @@ where self.remove_leaf(number, hash), "item comes from an iterator over storage; qed", ); - - self.pending_removed.push(hash.clone()); } } @@ -203,7 +196,6 @@ where // this is an invariant of regular block import. if !leaves_contains_best { self.insert_leaf(best_number.clone(), best_hash.clone()); - self.pending_added.push((best_hash, best_number.0)); } } @@ -213,9 +205,9 @@ where self.storage.iter().flat_map(|(_, hashes)| hashes.iter()).cloned().collect() } - /// Number of known leaves + /// Number of known leaves. pub fn count(&self) -> usize { - self.storage.len() + self.storage.values().map(|level| level.len()).sum() } /// Write the leaf list to the database transaction. @@ -227,8 +219,6 @@ where ) { let leaves: Vec<_> = self.storage.iter().map(|(n, h)| (n.0.clone(), h.clone())).collect(); tx.set_from_vec(column, prefix, leaves.encode()); - self.pending_added.clear(); - self.pending_removed.clear(); } /// Check if given block is a leaf. @@ -242,7 +232,7 @@ where self.storage.entry(number).or_insert_with(Vec::new).push(hash); } - // returns true if this leaf was contained, false otherwise. + // Returns true if this leaf was contained, false otherwise. fn remove_leaf(&mut self, number: &Reverse, hash: &H) -> bool { let mut empty = false; let removed = self.storage.get_mut(number).map_or(false, |leaves| { @@ -285,7 +275,7 @@ where pub fn undo_import(&mut self, displaced: ImportDisplaced) { let new_number = Reverse(displaced.displaced.number.0.clone() + N::one()); self.inner.remove_leaf(&new_number, &displaced.new_hash); - self.inner.insert_leaf(new_number, displaced.displaced.hash); + self.inner.insert_leaf(displaced.displaced.number, displaced.displaced.hash); } /// Undo a finalization operation by providing the displaced leaves. @@ -294,13 +284,6 @@ where } } -impl<'a, H: 'a, N: 'a> Drop for Undo<'a, H, N> { - fn drop(&mut self) { - self.inner.pending_added.clear(); - self.inner.pending_removed.clear(); - } -} - #[cfg(test)] mod tests { use super::*; @@ -315,15 +298,20 @@ mod tests { set.import(2_1, 2, 1_1); set.import(3_1, 3, 2_1); + assert_eq!(set.count(), 1); assert!(set.contains(3, 3_1)); assert!(!set.contains(2, 2_1)); assert!(!set.contains(1, 1_1)); assert!(!set.contains(0, 0)); set.import(2_2, 2, 1_1); + set.import(1_2, 1, 0); + set.import(2_3, 2, 1_2); + assert_eq!(set.count(), 3); assert!(set.contains(3, 3_1)); assert!(set.contains(2, 2_2)); + assert!(set.contains(2, 2_3)); } #[test] @@ -392,17 +380,42 @@ mod tests { } #[test] - fn undo_finalization() { + fn undo_import() { let mut set = LeafSet::new(); set.import(10_1u32, 10u32, 0u32); - set.import(11_1, 11, 10_2); - set.import(11_2, 11, 10_2); - set.import(12_1, 12, 11_123); + set.import(11_1, 11, 10_1); + set.import(11_2, 11, 10_1); + + let displaced = set.import(12_1, 12, 11_1).unwrap(); + assert_eq!(set.count(), 2); + assert!(set.contains(11, 11_2)); + assert!(set.contains(12, 12_1)); + + set.undo().undo_import(displaced); + assert_eq!(set.count(), 2); + assert!(set.contains(11, 11_1)); + assert!(set.contains(11, 11_2)); + } + + #[test] + fn undo_finalization() { + let mut set = LeafSet::new(); + set.import(9_1u32, 9u32, 0u32); + set.import(10_1, 10, 9_1); + set.import(10_2, 10, 9_1); + set.import(11_1, 11, 10_1); + set.import(11_2, 11, 10_1); + set.import(12_1, 12, 11_2); let displaced = set.finalize_height(11); - assert!(!set.contains(10, 10_1)); + assert_eq!(set.count(), 2); + assert!(set.contains(11, 11_1)); + assert!(set.contains(12, 12_1)); set.undo().undo_finalization(displaced); - assert!(set.contains(10, 10_1)); + assert_eq!(set.count(), 3); + assert!(set.contains(11, 11_1)); + assert!(set.contains(12, 12_1)); + assert!(set.contains(10, 10_2)); } } From 47fbd892dbdd5882dfee3c2ec8138549464040f8 Mon Sep 17 00:00:00 2001 From: Falco Hirschenberger Date: Mon, 25 Jul 2022 16:53:12 +0200 Subject: [PATCH 419/484] Add era to `Unbonded` event (#11770) * Add era to `Unbonded` event fixes #11749 * Fix missing tests * Add comment for `era` field in `Unbonded` struct. Co-authored-by: parity-processbot <> --- frame/nomination-pools/src/lib.rs | 4 +- frame/nomination-pools/src/tests.rs | 131 ++++++++++++------ .../nomination-pools/test-staking/src/lib.rs | 60 ++++++-- 3 files changed, 134 insertions(+), 61 deletions(-) diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 514267e8bf4af..203c13f2f3496 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1349,7 +1349,7 @@ pub mod pallet { /// pool. /// - `points` is the number of points that are issued as a result of `balance` being /// dissolved into the corresponding unbonding pool. - /// + /// - `era` is the era in which the balance will be unbonded. /// In the absence of slashing, these values will match. In the presence of slashing, the /// number of points that are issued in the unbonding pool will be less than the amount /// requested to be unbonded. @@ -1358,6 +1358,7 @@ pub mod pallet { pool_id: PoolId, balance: BalanceOf, points: BalanceOf, + era: EraIndex, }, /// A member has withdrawn from their pool. /// @@ -1683,6 +1684,7 @@ pub mod pallet { pool_id: member.pool_id, points: points_unbonded, balance: unbonding_balance, + era: unbond_era, }); // Now that we know everything has worked write the items to storage. diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index c971239bef507..b59f18cca72a2 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -869,7 +869,7 @@ mod claim_payout { Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 11, pool_id: 1, bonded: 11, joined: true }, - Event::Unbonded { member: 11, pool_id: 1, points: 11, balance: 11 } + Event::Unbonded { member: 11, pool_id: 1, points: 11, balance: 11, era: 3 } ] ); }); @@ -1374,7 +1374,7 @@ mod claim_payout { assert_eq!( pool_events_since_last_call(), vec![ - Event::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10 }, + Event::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10, era: 3 }, Event::PaidOut { member: 10, pool_id: 1, payout: 50 }, Event::PaidOut { member: 20, pool_id: 1, payout: 50 }, ] @@ -1808,12 +1808,12 @@ mod claim_payout { Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, Event::Bonded { member: 30, pool_id: 1, bonded: 20, joined: true }, - Event::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10 }, + Event::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10, era: 3 }, Event::PaidOut { member: 30, pool_id: 1, payout: 15 }, - Event::Unbonded { member: 30, pool_id: 1, balance: 10, points: 10 }, - Event::Unbonded { member: 30, pool_id: 1, balance: 5, points: 5 }, + Event::Unbonded { member: 30, pool_id: 1, balance: 10, points: 10, era: 3 }, + Event::Unbonded { member: 30, pool_id: 1, balance: 5, points: 5, era: 3 }, Event::PaidOut { member: 20, pool_id: 1, payout: 7 }, - Event::Unbonded { member: 20, pool_id: 1, balance: 5, points: 5 }, + Event::Unbonded { member: 20, pool_id: 1, balance: 5, points: 5, era: 3 }, Event::PaidOut { member: 10, pool_id: 1, payout: 7 } ] ); @@ -1865,10 +1865,10 @@ mod claim_payout { pool_events_since_last_call(), vec![ Event::StateChanged { pool_id: 1, new_state: PoolState::Destroying }, - Event::Unbonded { member: 20, pool_id: 1, balance: 20, points: 20 }, + Event::Unbonded { member: 20, pool_id: 1, balance: 20, points: 20, era: 3 }, Event::Withdrawn { member: 20, pool_id: 1, balance: 20, points: 20 }, Event::MemberRemoved { pool_id: 1, member: 20 }, - Event::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10 }, + Event::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10, era: 6 }, Event::Withdrawn { member: 10, pool_id: 1, balance: 10, points: 10 }, Event::MemberRemoved { pool_id: 1, member: 10 }, Event::Destroyed { pool_id: 1 } @@ -2269,7 +2269,7 @@ mod unbond { Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true }, Event::Bonded { member: 550, pool_id: 1, bonded: 550, joined: true }, Event::PaidOut { member: 40, pool_id: 1, payout: 40 }, - Event::Unbonded { member: 40, pool_id: 1, points: 6, balance: 6 } + Event::Unbonded { member: 40, pool_id: 1, points: 6, balance: 6, era: 3 } ] ); @@ -2311,7 +2311,13 @@ mod unbond { pool_events_since_last_call(), vec![ Event::PaidOut { member: 550, pool_id: 1, payout: 550 }, - Event::Unbonded { member: 550, pool_id: 1, points: 92, balance: 92 } + Event::Unbonded { + member: 550, + pool_id: 1, + points: 92, + balance: 92, + era: 3 + } ] ); @@ -2349,7 +2355,7 @@ mod unbond { Event::Withdrawn { member: 550, pool_id: 1, points: 92, balance: 92 }, Event::MemberRemoved { pool_id: 1, member: 550 }, Event::PaidOut { member: 10, pool_id: 1, payout: 10 }, - Event::Unbonded { member: 10, pool_id: 1, points: 2, balance: 2 } + Event::Unbonded { member: 10, pool_id: 1, points: 2, balance: 2, era: 6 } ] ); }); @@ -2395,7 +2401,7 @@ mod unbond { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::Unbonded { member: 10, pool_id: 1, points: 10, balance: 10 } + Event::Unbonded { member: 10, pool_id: 1, points: 10, balance: 10, era: 9 } ] ); }); @@ -2430,7 +2436,13 @@ mod unbond { Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, Event::Bonded { member: 200, pool_id: 1, bonded: 200, joined: true }, - Event::Unbonded { member: 100, pool_id: 1, points: 100, balance: 100 }, + Event::Unbonded { + member: 100, + pool_id: 1, + points: 100, + balance: 100, + era: 3 + }, ] ); @@ -2439,7 +2451,13 @@ mod unbond { assert_eq!( pool_events_since_last_call(), - vec![Event::Unbonded { member: 200, pool_id: 1, points: 200, balance: 200 }] + vec![Event::Unbonded { + member: 200, + pool_id: 1, + points: 200, + balance: 200, + era: 3 + }] ); assert_eq!( @@ -2508,7 +2526,7 @@ mod unbond { Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, - Event::Unbonded { member: 100, pool_id: 1, points: 100, balance: 100 } + Event::Unbonded { member: 100, pool_id: 1, points: 100, balance: 100, era: 3 } ] ); @@ -2659,7 +2677,7 @@ mod unbond { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::Unbonded { member: 10, pool_id: 1, points: 1, balance: 1 } + Event::Unbonded { member: 10, pool_id: 1, points: 1, balance: 1, era: 3 } ] ); @@ -2685,7 +2703,7 @@ mod unbond { ); assert_eq!( pool_events_since_last_call(), - vec![Event::Unbonded { member: 10, pool_id: 1, points: 5, balance: 5 }] + vec![Event::Unbonded { member: 10, pool_id: 1, points: 5, balance: 5, era: 3 }] ); // when: casual further unbond, next era. @@ -2712,7 +2730,7 @@ mod unbond { ); assert_eq!( pool_events_since_last_call(), - vec![Event::Unbonded { member: 10, pool_id: 1, points: 1, balance: 1 }] + vec![Event::Unbonded { member: 10, pool_id: 1, points: 1, balance: 1, era: 4 }] ); // when: unbonding more than our active: error @@ -2747,7 +2765,7 @@ mod unbond { ); assert_eq!( pool_events_since_last_call(), - vec![Event::Unbonded { member: 10, pool_id: 1, points: 3, balance: 3 }] + vec![Event::Unbonded { member: 10, pool_id: 1, points: 3, balance: 3, era: 4 }] ); }); } @@ -2792,9 +2810,9 @@ mod unbond { Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, - Event::Unbonded { member: 20, pool_id: 1, balance: 2, points: 2 }, - Event::Unbonded { member: 20, pool_id: 1, balance: 3, points: 3 }, - Event::Unbonded { member: 20, pool_id: 1, balance: 1, points: 1 } + Event::Unbonded { member: 20, pool_id: 1, points: 2, balance: 2, era: 3 }, + Event::Unbonded { member: 20, pool_id: 1, points: 3, balance: 3, era: 4 }, + Event::Unbonded { member: 20, pool_id: 1, points: 1, balance: 1, era: 5 } ] ); }) @@ -2827,7 +2845,7 @@ mod unbond { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::Unbonded { member: 10, pool_id: 1, points: 3, balance: 3 } + Event::Unbonded { member: 10, pool_id: 1, points: 3, balance: 3, era: 3 } ] ); }); @@ -2873,7 +2891,7 @@ mod unbond { Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, Event::PaidOut { member: 20, pool_id: 1, payout: 10 }, - Event::Unbonded { member: 20, pool_id: 1, balance: 2, points: 2 } + Event::Unbonded { member: 20, pool_id: 1, balance: 2, points: 2, era: 3 } ] ); @@ -2889,7 +2907,7 @@ mod unbond { vec![ // 2/3 of ed, which is 20's share. Event::PaidOut { member: 20, pool_id: 1, payout: 6 }, - Event::Unbonded { member: 20, pool_id: 1, points: 3, balance: 3 } + Event::Unbonded { member: 20, pool_id: 1, points: 3, balance: 3, era: 4 } ] ); @@ -2904,7 +2922,7 @@ mod unbond { pool_events_since_last_call(), vec![ Event::PaidOut { member: 20, pool_id: 1, payout: 3 }, - Event::Unbonded { member: 20, pool_id: 1, points: 5, balance: 5 } + Event::Unbonded { member: 20, pool_id: 1, points: 5, balance: 5, era: 5 } ] ); @@ -3003,7 +3021,6 @@ mod withdraw_unbonded { with_era: Default::default() } ); - assert_eq!( pool_events_since_last_call(), vec![ @@ -3011,8 +3028,14 @@ mod withdraw_unbonded { Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true }, Event::Bonded { member: 550, pool_id: 1, bonded: 550, joined: true }, - Event::Unbonded { member: 550, pool_id: 1, points: 550, balance: 550 }, - Event::Unbonded { member: 40, pool_id: 1, points: 40, balance: 40 }, + Event::Unbonded { + member: 550, + pool_id: 1, + points: 550, + balance: 550, + era: 3 + }, + Event::Unbonded { member: 40, pool_id: 1, points: 40, balance: 40, era: 3 }, ] ); assert_eq!( @@ -3077,7 +3100,7 @@ mod withdraw_unbonded { assert_eq!( pool_events_since_last_call(), vec![ - Event::Unbonded { member: 10, pool_id: 1, balance: 5, points: 5 }, + Event::Unbonded { member: 10, pool_id: 1, balance: 5, points: 5, era: 9 }, Event::Withdrawn { member: 10, pool_id: 1, balance: 5, points: 5 }, Event::MemberRemoved { pool_id: 1, member: 10 }, Event::Destroyed { pool_id: 1 } @@ -3122,8 +3145,14 @@ mod withdraw_unbonded { Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true }, Event::Bonded { member: 550, pool_id: 1, bonded: 550, joined: true }, - Event::Unbonded { member: 40, pool_id: 1, balance: 20, points: 20 }, - Event::Unbonded { member: 550, pool_id: 1, balance: 275, points: 275 }, + Event::Unbonded { member: 40, pool_id: 1, balance: 20, points: 20, era: 3 }, + Event::Unbonded { + member: 550, + pool_id: 1, + balance: 275, + points: 275, + era: 3, + } ] ); assert_eq!( @@ -3199,7 +3228,7 @@ mod withdraw_unbonded { assert_eq!( pool_events_since_last_call(), vec![ - Event::Unbonded { member: 10, pool_id: 1, points: 5, balance: 5 }, + Event::Unbonded { member: 10, pool_id: 1, points: 5, balance: 5, era: 6 }, Event::Withdrawn { member: 10, pool_id: 1, points: 5, balance: 5 }, Event::MemberRemoved { pool_id: 1, member: 10 }, Event::Destroyed { pool_id: 1 } @@ -3313,8 +3342,20 @@ mod withdraw_unbonded { Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, Event::Bonded { member: 200, pool_id: 1, bonded: 200, joined: true }, - Event::Unbonded { member: 100, pool_id: 1, points: 100, balance: 100 }, - Event::Unbonded { member: 200, pool_id: 1, points: 200, balance: 200 } + Event::Unbonded { + member: 100, + pool_id: 1, + points: 100, + balance: 100, + era: 3 + }, + Event::Unbonded { + member: 200, + pool_id: 1, + points: 200, + balance: 200, + era: 3 + } ] ); @@ -3391,7 +3432,7 @@ mod withdraw_unbonded { Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, - Event::Unbonded { member: 100, pool_id: 1, points: 100, balance: 100 }, + Event::Unbonded { member: 100, pool_id: 1, points: 100, balance: 100, era: 3 }, Event::Withdrawn { member: 100, pool_id: 1, points: 100, balance: 100 }, Event::MemberRemoved { pool_id: 1, member: 100 } ] @@ -3431,8 +3472,8 @@ mod withdraw_unbonded { Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: false }, - Event::Unbonded { member: 10, pool_id: 1, points: 6, balance: 6 }, - Event::Unbonded { member: 10, pool_id: 1, points: 1, balance: 1 } + Event::Unbonded { member: 10, pool_id: 1, points: 6, balance: 6, era: 3 }, + Event::Unbonded { member: 10, pool_id: 1, points: 1, balance: 1, era: 4 } ] ); @@ -3518,8 +3559,8 @@ mod withdraw_unbonded { Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 11, pool_id: 1, bonded: 10, joined: true }, - Event::Unbonded { member: 11, pool_id: 1, points: 6, balance: 6 }, - Event::Unbonded { member: 11, pool_id: 1, points: 1, balance: 1 } + Event::Unbonded { member: 11, pool_id: 1, points: 6, balance: 6, era: 3 }, + Event::Unbonded { member: 11, pool_id: 1, points: 1, balance: 1, era: 4 } ] ); @@ -3608,8 +3649,8 @@ mod withdraw_unbonded { Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true }, - Event::Unbonded { member: 100, pool_id: 1, points: 75, balance: 75 }, - Event::Unbonded { member: 100, pool_id: 1, points: 25, balance: 25 }, + Event::Unbonded { member: 100, pool_id: 1, points: 75, balance: 75, era: 3 }, + Event::Unbonded { member: 100, pool_id: 1, points: 25, balance: 25, era: 4 }, Event::Withdrawn { member: 100, pool_id: 1, points: 75, balance: 75 }, ] ); @@ -3680,9 +3721,9 @@ mod withdraw_unbonded { Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: false }, - Event::Unbonded { member: 10, pool_id: 1, balance: 7, points: 7 }, - Event::Unbonded { member: 10, pool_id: 1, balance: 3, points: 3 }, - Event::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10 }, + Event::Unbonded { member: 10, pool_id: 1, balance: 7, points: 7, era: 3 }, + Event::Unbonded { member: 10, pool_id: 1, balance: 3, points: 3, era: 4 }, + Event::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10, era: 4 }, Event::Withdrawn { member: 10, pool_id: 1, balance: 7, points: 7 } ] ); diff --git a/frame/nomination-pools/test-staking/src/lib.rs b/frame/nomination-pools/test-staking/src/lib.rs index eed8e5fd390cd..5a7cd494362ca 100644 --- a/frame/nomination-pools/test-staking/src/lib.rs +++ b/frame/nomination-pools/test-staking/src/lib.rs @@ -95,8 +95,8 @@ fn pool_lifecycle_e2e() { pool_events_since_last_call(), vec![ PoolsEvent::StateChanged { pool_id: 1, new_state: PoolState::Destroying }, - PoolsEvent::Unbonded { member: 20, pool_id: 1, points: 10, balance: 10 }, - PoolsEvent::Unbonded { member: 21, pool_id: 1, points: 10, balance: 10 }, + PoolsEvent::Unbonded { member: 20, pool_id: 1, points: 10, balance: 10, era: 3 }, + PoolsEvent::Unbonded { member: 21, pool_id: 1, points: 10, balance: 10, era: 3 }, ] ); @@ -159,7 +159,7 @@ fn pool_lifecycle_e2e() { ); assert_eq!( pool_events_since_last_call(), - vec![PoolsEvent::Unbonded { member: 10, pool_id: 1, points: 50, balance: 50 }] + vec![PoolsEvent::Unbonded { member: 10, pool_id: 1, points: 50, balance: 50, era: 6 }] ); // waiting another bonding duration: @@ -237,8 +237,8 @@ fn pool_slash_e2e() { assert_eq!( pool_events_since_last_call(), vec![ - PoolsEvent::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10 }, - PoolsEvent::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10 } + PoolsEvent::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10, era: 4 }, + PoolsEvent::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10, era: 4 } ] ); @@ -262,9 +262,9 @@ fn pool_slash_e2e() { assert_eq!( pool_events_since_last_call(), vec![ - PoolsEvent::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10 }, - PoolsEvent::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10 }, - PoolsEvent::Unbonded { member: 21, pool_id: 1, balance: 10, points: 10 }, + PoolsEvent::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10, era: 5 }, + PoolsEvent::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10, era: 5 }, + PoolsEvent::Unbonded { member: 21, pool_id: 1, balance: 10, points: 10, era: 5 }, ] ); @@ -305,7 +305,7 @@ fn pool_slash_e2e() { assert_eq!(staking_events_since_last_call(), vec![StakingEvent::Unbonded(POOL1_BONDED, 5)]); assert_eq!( pool_events_since_last_call(), - vec![PoolsEvent::Unbonded { member: 21, pool_id: 1, balance: 5, points: 5 }] + vec![PoolsEvent::Unbonded { member: 21, pool_id: 1, balance: 5, points: 5, era: 6 }] ); // now we start withdrawing. we do it all at once, at era 6 where 20 and 21 are fully free. @@ -342,7 +342,7 @@ fn pool_slash_e2e() { pool_events_since_last_call(), vec![ PoolsEvent::StateChanged { pool_id: 1, new_state: PoolState::Destroying }, - PoolsEvent::Unbonded { member: 10, pool_id: 1, points: 10, balance: 10 } + PoolsEvent::Unbonded { member: 10, pool_id: 1, points: 10, balance: 10, era: 9 } ] ); @@ -432,7 +432,13 @@ fn pool_slash_proportional() { ); assert_eq!( pool_events_since_last_call(), - vec![PoolsEvent::Unbonded { member: 20, pool_id: 1, balance: bond, points: bond }] + vec![PoolsEvent::Unbonded { + member: 20, + pool_id: 1, + balance: bond, + points: bond, + era: 127 + }] ); CurrentEra::::set(Some(100)); @@ -443,7 +449,13 @@ fn pool_slash_proportional() { ); assert_eq!( pool_events_since_last_call(), - vec![PoolsEvent::Unbonded { member: 21, pool_id: 1, balance: bond, points: bond }] + vec![PoolsEvent::Unbonded { + member: 21, + pool_id: 1, + balance: bond, + points: bond, + era: 128 + }] ); CurrentEra::::set(Some(101)); @@ -454,7 +466,13 @@ fn pool_slash_proportional() { ); assert_eq!( pool_events_since_last_call(), - vec![PoolsEvent::Unbonded { member: 22, pool_id: 1, balance: bond, points: bond }] + vec![PoolsEvent::Unbonded { + member: 22, + pool_id: 1, + balance: bond, + points: bond, + era: 129 + }] ); // Apply a slash that happened in era 100. This is typically applied with a delay. @@ -531,7 +549,13 @@ fn pool_slash_non_proportional_only_bonded_pool() { ); assert_eq!( pool_events_since_last_call(), - vec![PoolsEvent::Unbonded { member: 20, pool_id: 1, balance: bond, points: bond }] + vec![PoolsEvent::Unbonded { + member: 20, + pool_id: 1, + balance: bond, + points: bond, + era: 127 + }] ); // slash for 30. This will be deducted only from the bonded pool. @@ -598,7 +622,13 @@ fn pool_slash_non_proportional_bonded_pool_and_chunks() { ); assert_eq!( pool_events_since_last_call(), - vec![PoolsEvent::Unbonded { member: 20, pool_id: 1, balance: bond, points: bond }] + vec![PoolsEvent::Unbonded { + member: 20, + pool_id: 1, + balance: bond, + points: bond, + era: 127 + }] ); // slash 50. This will be deducted only from the bonded pool and one of the unbonding pools. From d91313b59325b4ae848731587f6c411bcd27c7d6 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Mon, 25 Jul 2022 16:20:19 +0100 Subject: [PATCH 420/484] remove FunctionOf (#11897) * remove FunctionOf * fix docs Co-authored-by: parity-processbot <> --- frame/support/src/weights.rs | 89 +++--------------------------------- 1 file changed, 6 insertions(+), 83 deletions(-) diff --git a/frame/support/src/weights.rs b/frame/support/src/weights.rs index dd49409ccc912..c37a72536bddf 100644 --- a/frame/support/src/weights.rs +++ b/frame/support/src/weights.rs @@ -91,27 +91,19 @@ //! # fn main() {} //! ``` //! -//! ### 2. Define weights as a function of input arguments using `FunctionOf` tuple struct. +//! ### 2. Define weights as a function of input arguments. //! -//! This struct works in a similar manner as above. 3 items must be provided and each can be either -//! a fixed value or a function/closure with the same parameters list as the dispatchable function -//! itself, wrapper in a tuple. -//! -//! Using this only makes sense if you want to use a function for at least one of the elements. If -//! all 3 are static values, providing a raw tuple is easier. +//! The arguments of the dispatch are available in the weight expressions as a borrowed value. //! //! ``` //! # use frame_system::Config; -//! # use frame_support::weights::{DispatchClass, FunctionOf, Pays}; +//! # use frame_support::weights::{DispatchClass, Pays}; //! frame_support::decl_module! { //! pub struct Module for enum Call where origin: T::Origin { -//! #[weight = FunctionOf( -//! // weight, function. -//! |args: (&u32, &u64)| *args.0 as u64 + args.1, -//! // class, fixed. +//! #[weight = ( +//! *a as u64 + *b, //! DispatchClass::Operational, -//! // pays fee, function. -//! |args: (&u32, &u64)| if *args.0 > 1000 { Pays::Yes } else { Pays::No }, +//! if *a > 1000 { Pays::Yes } else { Pays::No } //! )] //! fn dispatching(origin, a: u32, b: u64) { unimplemented!() } //! } @@ -508,75 +500,6 @@ impl PaysFee for (Weight, Pays) { } } -/// A struct to represent a weight which is a function of the input arguments. The given items have -/// the following types: -/// -/// - `WD`: a raw `Weight` value or a closure that returns a `Weight` with the same argument list as -/// the dispatched, wrapped in a tuple. -/// - `CD`: a raw `DispatchClass` value or a closure that returns a `DispatchClass` with the same -/// argument list as the dispatched, wrapped in a tuple. -/// - `PF`: a `Pays` variant for whether this dispatch pays fee or not or a closure that returns a -/// `Pays` variant with the same argument list as the dispatched, wrapped in a tuple. -#[deprecated = "Function arguments are available directly inside the annotation now."] -pub struct FunctionOf(pub WD, pub CD, pub PF); - -// `WeighData` as a raw value -#[allow(deprecated)] -impl WeighData for FunctionOf { - fn weigh_data(&self, _: Args) -> Weight { - self.0 - } -} - -// `WeighData` as a closure -#[allow(deprecated)] -impl WeighData for FunctionOf -where - WD: Fn(Args) -> Weight, -{ - fn weigh_data(&self, args: Args) -> Weight { - (self.0)(args) - } -} - -// `ClassifyDispatch` as a raw value -#[allow(deprecated)] -impl ClassifyDispatch for FunctionOf { - fn classify_dispatch(&self, _: Args) -> DispatchClass { - self.1 - } -} - -// `ClassifyDispatch` as a raw value -#[allow(deprecated)] -impl ClassifyDispatch for FunctionOf -where - CD: Fn(Args) -> DispatchClass, -{ - fn classify_dispatch(&self, args: Args) -> DispatchClass { - (self.1)(args) - } -} - -// `PaysFee` as a raw value -#[allow(deprecated)] -impl PaysFee for FunctionOf { - fn pays_fee(&self, _: Args) -> Pays { - self.2 - } -} - -// `PaysFee` as a closure -#[allow(deprecated)] -impl PaysFee for FunctionOf -where - PF: Fn(Args) -> Pays, -{ - fn pays_fee(&self, args: Args) -> Pays { - (self.2)(args) - } -} - /// Implementation for unchecked extrinsic. impl GetDispatchInfo for UncheckedExtrinsic From 1172cefad38a6e87a68af7c3b4a9f5162d4f304e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Mon, 25 Jul 2022 17:48:01 +0200 Subject: [PATCH 421/484] contracts: Allow `ChainExtension::call()` to access `&mut self` (#11874) * Give chain extensions the ability to store some temporary values * Update frame/contracts/src/wasm/runtime.rs Co-authored-by: Hernando Castano * Rename func_id -> id * Replace `id` param by two functions on `env` Co-authored-by: Hernando Castano --- frame/contracts/fixtures/chain_extension.wat | 4 +- .../fixtures/chain_extension_temp_storage.wat | 85 ++++++++++ frame/contracts/src/chain_extension.rs | 41 +++-- frame/contracts/src/lib.rs | 2 +- frame/contracts/src/tests.rs | 150 +++++++++++++----- frame/contracts/src/wasm/runtime.rs | 28 +++- 6 files changed, 254 insertions(+), 56 deletions(-) create mode 100644 frame/contracts/fixtures/chain_extension_temp_storage.wat diff --git a/frame/contracts/fixtures/chain_extension.wat b/frame/contracts/fixtures/chain_extension.wat index 9b2534c540ab8..7cc7335052e90 100644 --- a/frame/contracts/fixtures/chain_extension.wat +++ b/frame/contracts/fixtures/chain_extension.wat @@ -31,14 +31,14 @@ ;; the chain extension passes through the input and returns it as output (call $seal_call_chain_extension - (i32.load (i32.const 4)) ;; func_id + (i32.load (i32.const 4)) ;; id (i32.const 4) ;; input_ptr (i32.load (i32.const 0)) ;; input_len (i32.const 16) ;; output_ptr (i32.const 12) ;; output_len_ptr ) - ;; the chain extension passes through the func_id + ;; the chain extension passes through the id (call $assert (i32.eq (i32.load (i32.const 4)))) (call $seal_return (i32.const 0) (i32.const 16) (i32.load (i32.const 12))) diff --git a/frame/contracts/fixtures/chain_extension_temp_storage.wat b/frame/contracts/fixtures/chain_extension_temp_storage.wat new file mode 100644 index 0000000000000..b481abb5bc7c9 --- /dev/null +++ b/frame/contracts/fixtures/chain_extension_temp_storage.wat @@ -0,0 +1,85 @@ +;; Call chain extension two times with the specified func_ids +;; It then calls itself once +(module + (import "seal0" "seal_call_chain_extension" + (func $seal_call_chain_extension (param i32 i32 i32 i32 i32) (result i32)) + ) + (import "seal0" "seal_input" (func $seal_input (param i32 i32))) + (import "seal0" "seal_address" (func $seal_address (param i32 i32))) + (import "seal1" "seal_call" (func $seal_call (param i32 i32 i64 i32 i32 i32 i32 i32) (result i32))) + (import "env" "memory" (memory 16 16)) + + (func $assert (param i32) + (block $ok + (br_if $ok (get_local 0)) + (unreachable) + ) + ) + + ;; [0, 4) len of input buffer: 8 byte (func_ids) + 1byte (stop_recurse) + (data (i32.const 0) "\09") + + ;; [4, 16) buffer for input + + ;; [16, 48] buffer for self address + + ;; [48, 52] len of self address buffer + (data (i32.const 48) "\20") + + (func (export "deploy")) + + (func (export "call") + ;; input: (func_id1: i32, func_id2: i32, stop_recurse: i8) + (call $seal_input (i32.const 4) (i32.const 0)) + + (call $seal_call_chain_extension + (i32.load (i32.const 4)) ;; id + (i32.const 0) ;; input_ptr + (i32.const 0) ;; input_len + (i32.const 0xffffffff) ;; u32 max sentinel value: do not copy output + (i32.const 0) ;; output_len_ptr + ) + drop + + (call $seal_call_chain_extension + (i32.load (i32.const 8)) ;; _id + (i32.const 0) ;; input_ptr + (i32.const 0) ;; input_len + (i32.const 0xffffffff) ;; u32 max sentinel value: do not copy output + (i32.const 0) ;; output_len_ptr + ) + drop + + (if (i32.eqz (i32.load8_u (i32.const 12))) + (then + ;; stop recursion + (i32.store8 (i32.const 12) (i32.const 1)) + + ;; load own address into buffer + (call $seal_address (i32.const 16) (i32.const 48)) + + ;; call function 2 + 3 of chainext 3 next time + ;; (3 << 16) | 2 + ;; (3 << 16) | 3 + (i32.store (i32.const 4) (i32.const 196610)) + (i32.store (i32.const 8) (i32.const 196611)) + + ;; call self + (call $seal_call + (i32.const 8) ;; Set ALLOW_REENTRY + (i32.const 16) ;; Pointer to "callee" address. + (i64.const 0) ;; How much gas to devote for the execution. 0 = all. + (i32.const 512) ;; Pointer to the buffer with value to transfer + (i32.const 4) ;; Pointer to input data buffer address + (i32.load (i32.const 0)) ;; Length of input data buffer + (i32.const 4294967295) ;; u32 max value is the sentinel value: do not copy output + (i32.const 0) ;; Length is ignored in this case + ) + + ;; check that call succeeded of call + (call $assert (i32.eqz)) + ) + (else) + ) + ) +) diff --git a/frame/contracts/src/chain_extension.rs b/frame/contracts/src/chain_extension.rs index 536d58c94f68f..23242a2a542c1 100644 --- a/frame/contracts/src/chain_extension.rs +++ b/frame/contracts/src/chain_extension.rs @@ -33,7 +33,7 @@ //! //! Often there is a need for having multiple chain extensions. This is often the case when //! some generally useful off-the-shelf extensions should be included. To have multiple chain -//! extensions they can be put into a tuple which is then passed to `[Config::ChainExtension]` like +//! extensions they can be put into a tuple which is then passed to [`Config::ChainExtension`] like //! this `type Extensions = (ExtensionA, ExtensionB)`. //! //! However, only extensions implementing [`RegisteredChainExtension`] can be put into a tuple. @@ -94,6 +94,12 @@ pub type Result = sp_std::result::Result; /// In order to create a custom chain extension this trait must be implemented and supplied /// to the pallet contracts configuration trait as the associated type of the same name. /// Consult the [module documentation](self) for a general explanation of chain extensions. +/// +/// # Lifetime +/// +/// The extension will be [`Default`] initialized at the beginning of each call +/// (**not** per call stack) and dropped afterwards. Hence any value held inside the extension +/// can be used as a per-call scratch buffer. pub trait ChainExtension { /// Call the chain extension logic. /// @@ -102,8 +108,6 @@ pub trait ChainExtension { /// imported wasm function. /// /// # Parameters - /// - `func_id`: The first argument to `seal_call_chain_extension`. Usually used to determine - /// which function to realize. /// - `env`: Access to the remaining arguments and the execution environment. /// /// # Return @@ -111,7 +115,7 @@ pub trait ChainExtension { /// In case of `Err` the contract execution is immediately suspended and the passed error /// is returned to the caller. Otherwise the value of [`RetVal`] determines the exit /// behaviour. - fn call(func_id: u32, env: Environment) -> Result + fn call(&mut self, env: Environment) -> Result where E: Ext, ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>; @@ -132,7 +136,7 @@ pub trait ChainExtension { /// /// An extension that implements this trait can be put in a tuple in order to have multiple /// extensions available. The tuple implementation routes requests based on the first two -/// most significant bytes of the `func_id` passed to `call`. +/// most significant bytes of the `id` passed to `call`. /// /// If this extensions is to be used by multiple runtimes consider /// [registering it](https://github.com/paritytech/chainextension-registry) to ensure that there @@ -150,15 +154,15 @@ pub trait RegisteredChainExtension: ChainExtension { #[impl_trait_for_tuples::impl_for_tuples(10)] #[tuple_types_custom_trait_bound(RegisteredChainExtension)] impl ChainExtension for Tuple { - fn call(func_id: u32, mut env: Environment) -> Result + fn call(&mut self, mut env: Environment) -> Result where E: Ext, ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, { for_tuples!( #( - if (Tuple::ID == (func_id >> 16) as u16) && Tuple::enabled() { - return Tuple::call(func_id, env); + if (Tuple::ID == env.ext_id()) && Tuple::enabled() { + return Tuple.call(env); } )* ); @@ -206,6 +210,22 @@ impl<'a, 'b, E: Ext, S: state::State> Environment<'a, 'b, E, S> where ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, { + /// The function id within the `id` passed by a contract. + /// + /// It returns the two least significant bytes of the `id` passed by a contract as the other + /// two bytes represent the chain extension itself (the code which is calling this function). + pub fn func_id(&self) -> u16 { + (self.inner.id & 0x00FF) as u16 + } + + /// The chain extension id within the `id` passed by a contract. + /// + /// It returns the two most significant bytes of the `id` passed by a contract which represent + /// the chain extension itself (the code which is calling this function). + pub fn ext_id(&self) -> u16 { + (self.inner.id >> 16) as u16 + } + /// Charge the passed `amount` of weight from the overall limit. /// /// It returns `Ok` when there the remaining weight budget is larger than the passed @@ -251,13 +271,14 @@ impl<'a, 'b, E: Ext> Environment<'a, 'b, E, state::Init> { /// ever create this type. Chain extensions merely consume it. pub(crate) fn new( runtime: &'a mut Runtime<'b, E>, + id: u32, input_ptr: u32, input_len: u32, output_ptr: u32, output_len_ptr: u32, ) -> Self { Environment { - inner: Inner { runtime, input_ptr, input_len, output_ptr, output_len_ptr }, + inner: Inner { runtime, id, input_ptr, input_len, output_ptr, output_len_ptr }, phantom: PhantomData, } } @@ -406,6 +427,8 @@ struct Inner<'a, 'b, E: Ext> { /// The runtime contains all necessary functions to interact with the running contract. runtime: &'a mut Runtime<'b, E>, /// Verbatim argument passed to `seal_call_chain_extension`. + id: u32, + /// Verbatim argument passed to `seal_call_chain_extension`. input_ptr: u32, /// Verbatim argument passed to `seal_call_chain_extension`. input_len: u32, diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index 60b30ffa25005..319bacaab7789 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -280,7 +280,7 @@ pub mod pallet { type WeightInfo: WeightInfo; /// Type that allows the runtime authors to add new host functions for a contract to call. - type ChainExtension: chain_extension::ChainExtension; + type ChainExtension: chain_extension::ChainExtension + Default; /// Cost schedule and limits. #[pallet::constant] diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index 85a0e9977d2d7..0febfec929b6e 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -118,10 +118,17 @@ pub struct TestExtension { last_seen_inputs: (u32, u32, u32, u32), } +#[derive(Default)] pub struct RevertingExtension; +#[derive(Default)] pub struct DisabledExtension; +#[derive(Default)] +pub struct TempStorageExtension { + storage: u32, +} + impl TestExtension { fn disable() { TEST_EXTENSION.with(|e| e.borrow_mut().enabled = false) @@ -143,18 +150,20 @@ impl Default for TestExtension { } impl ChainExtension for TestExtension { - fn call(func_id: u32, env: Environment) -> ExtensionResult + fn call(&mut self, env: Environment) -> ExtensionResult where E: Ext, ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, { + let func_id = env.func_id(); + let id = env.ext_id() as u32 | func_id as u32; match func_id { 0 => { let mut env = env.buf_in_buf_out(); let input = env.read(8)?; env.write(&input, false, None)?; TEST_EXTENSION.with(|e| e.borrow_mut().last_seen_buffer = input); - Ok(RetVal::Converging(func_id)) + Ok(RetVal::Converging(id)) }, 1 => { let env = env.only_in(); @@ -162,17 +171,17 @@ impl ChainExtension for TestExtension { e.borrow_mut().last_seen_inputs = (env.val0(), env.val1(), env.val2(), env.val3()) }); - Ok(RetVal::Converging(func_id)) + Ok(RetVal::Converging(id)) }, 2 => { let mut env = env.buf_in_buf_out(); let weight = env.read(5)?[4].into(); env.charge_weight(weight)?; - Ok(RetVal::Converging(func_id)) + Ok(RetVal::Converging(id)) }, 3 => Ok(RetVal::Diverging { flags: ReturnFlags::REVERT, data: vec![42, 99] }), _ => { - panic!("Passed unknown func_id to test chain extension: {}", func_id); + panic!("Passed unknown id to test chain extension: {}", func_id); }, } } @@ -187,7 +196,7 @@ impl RegisteredChainExtension for TestExtension { } impl ChainExtension for RevertingExtension { - fn call(_func_id: u32, _env: Environment) -> ExtensionResult + fn call(&mut self, _env: Environment) -> ExtensionResult where E: Ext, ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, @@ -205,7 +214,7 @@ impl RegisteredChainExtension for RevertingExtension { } impl ChainExtension for DisabledExtension { - fn call(_func_id: u32, _env: Environment) -> ExtensionResult + fn call(&mut self, _env: Environment) -> ExtensionResult where E: Ext, ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, @@ -222,6 +231,37 @@ impl RegisteredChainExtension for DisabledExtension { const ID: u16 = 2; } +impl ChainExtension for TempStorageExtension { + fn call(&mut self, env: Environment) -> ExtensionResult + where + E: Ext, + ::AccountId: UncheckedFrom<::Hash> + AsRef<[u8]>, + { + let func_id = env.func_id(); + match func_id { + 0 => self.storage = 42, + 1 => assert_eq!(self.storage, 42, "Storage is preserved inside the same call."), + 2 => { + assert_eq!(self.storage, 0, "Storage is different for different calls."); + self.storage = 99; + }, + 3 => assert_eq!(self.storage, 99, "Storage is preserved inside the same call."), + _ => { + panic!("Passed unknown id to test chain extension: {}", func_id); + }, + } + Ok(RetVal::Converging(0)) + } + + fn enabled() -> bool { + TEST_EXTENSION.with(|e| e.borrow().enabled) + } +} + +impl RegisteredChainExtension for TempStorageExtension { + const ID: u16 = 3; +} + parameter_types! { pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::simple_max(2 * WEIGHT_PER_SECOND); @@ -325,7 +365,8 @@ impl Config for Test { type CallStack = [Frame; 31]; type WeightPrice = Self; type WeightInfo = (); - type ChainExtension = (TestExtension, DisabledExtension, RevertingExtension); + type ChainExtension = + (TestExtension, DisabledExtension, RevertingExtension, TempStorageExtension); type DeletionQueueDepth = ConstU32<1024>; type DeletionWeightLimit = ConstU64<500_000_000_000>; type Schedule = MySchedule; @@ -396,6 +437,29 @@ fn initialize_block(number: u64) { System::initialize(&number, &[0u8; 32].into(), &Default::default()); } +struct ExtensionInput<'a> { + extension_id: u16, + func_id: u16, + extra: &'a [u8], +} + +impl<'a> ExtensionInput<'a> { + fn to_vec(&self) -> Vec { + ((self.extension_id as u32) << 16 | (self.func_id as u32)) + .to_le_bytes() + .iter() + .chain(self.extra) + .cloned() + .collect() + } +} + +impl<'a> From> for Vec { + fn from(input: ExtensionInput) -> Vec { + input.to_vec() + } +} + // Perform a call to a plain account. // The actual transfer fails because we can only call contracts. // Then we check that at least the base costs where charged (no runtime gas costs.) @@ -1567,23 +1631,6 @@ fn disabled_chain_extension_errors_on_call() { #[test] fn chain_extension_works() { - struct Input<'a> { - extension_id: u16, - func_id: u16, - extra: &'a [u8], - } - - impl<'a> From> for Vec { - fn from(input: Input) -> Vec { - ((input.extension_id as u32) << 16 | (input.func_id as u32)) - .to_le_bytes() - .iter() - .chain(input.extra) - .cloned() - .collect() - } - } - let (code, hash) = compile_module::("chain_extension").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); @@ -1599,12 +1646,8 @@ fn chain_extension_works() { ),); let addr = Contracts::contract_address(&ALICE, &hash, &[]); - // The contract takes a up to 2 byte buffer where the first byte passed is used as - // as func_id to the chain extension which behaves differently based on the - // func_id. - // 0 = read input buffer and pass it through as output - let input: Vec = Input { extension_id: 0, func_id: 0, extra: &[99] }.into(); + let input: Vec = ExtensionInput { extension_id: 0, func_id: 0, extra: &[99] }.into(); let result = Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, None, input.clone(), false); assert_eq!(TestExtension::last_seen_buffer(), input); @@ -1617,7 +1660,7 @@ fn chain_extension_works() { 0, GAS_LIMIT, None, - Input { extension_id: 0, func_id: 1, extra: &[] }.into(), + ExtensionInput { extension_id: 0, func_id: 1, extra: &[] }.into(), false, ) .result @@ -1632,7 +1675,7 @@ fn chain_extension_works() { 0, GAS_LIMIT, None, - Input { extension_id: 0, func_id: 2, extra: &[0] }.into(), + ExtensionInput { extension_id: 0, func_id: 2, extra: &[0] }.into(), false, ); assert_ok!(result.result); @@ -1643,7 +1686,7 @@ fn chain_extension_works() { 0, GAS_LIMIT, None, - Input { extension_id: 0, func_id: 2, extra: &[42] }.into(), + ExtensionInput { extension_id: 0, func_id: 2, extra: &[42] }.into(), false, ); assert_ok!(result.result); @@ -1654,7 +1697,7 @@ fn chain_extension_works() { 0, GAS_LIMIT, None, - Input { extension_id: 0, func_id: 2, extra: &[95] }.into(), + ExtensionInput { extension_id: 0, func_id: 2, extra: &[95] }.into(), false, ); assert_ok!(result.result); @@ -1667,7 +1710,7 @@ fn chain_extension_works() { 0, GAS_LIMIT, None, - Input { extension_id: 0, func_id: 3, extra: &[] }.into(), + ExtensionInput { extension_id: 0, func_id: 3, extra: &[] }.into(), false, ) .result @@ -1684,7 +1727,7 @@ fn chain_extension_works() { 0, GAS_LIMIT, None, - Input { extension_id: 1, func_id: 0, extra: &[] }.into(), + ExtensionInput { extension_id: 1, func_id: 0, extra: &[] }.into(), false, ) .result @@ -1701,13 +1744,46 @@ fn chain_extension_works() { 0, GAS_LIMIT, None, - Input { extension_id: 2, func_id: 0, extra: &[] }.into(), + ExtensionInput { extension_id: 2, func_id: 0, extra: &[] }.into(), ), Error::::NoChainExtension, ); }); } +#[test] +fn chain_extension_temp_storage_works() { + let (code, hash) = compile_module::("chain_extension_temp_storage").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); + assert_ok!(Contracts::instantiate_with_code( + Origin::signed(ALICE), + min_balance * 100, + GAS_LIMIT, + None, + code, + vec![], + vec![], + ),); + let addr = Contracts::contract_address(&ALICE, &hash, &[]); + + // Call func 0 and func 1 back to back. + let stop_recursion = 0u8; + let mut input: Vec = ExtensionInput { extension_id: 3, func_id: 0, extra: &[] }.into(); + input.extend_from_slice( + ExtensionInput { extension_id: 3, func_id: 1, extra: &[stop_recursion] } + .to_vec() + .as_ref(), + ); + + assert_ok!( + Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, None, input.clone(), false) + .result + ); + }) +} + #[test] fn lazy_removal_works() { let (code, hash) = compile_module::("self_destruct").unwrap(); diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index c1757ba06412a..3d5e62f2c1333 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -65,7 +65,7 @@ impl KeyType { /// This enum can be extended in the future: New codes can be added but existing codes /// will not be changed or removed. This means that any contract **must not** exhaustively /// match return codes. Instead, contracts should prepare for unknown variants and deal with -/// those errors gracefuly in order to be forward compatible. +/// those errors gracefully in order to be forward compatible. #[repr(u32)] pub enum ReturnCode { /// API call successful. @@ -101,8 +101,9 @@ pub enum ReturnCode { } impl ConvertibleToWasm for ReturnCode { - type NativeType = Self; const VALUE_TYPE: ValueType = ValueType::I32; + type NativeType = Self; + fn to_typed_value(self) -> sp_sandbox::Value { sp_sandbox::Value::I32(self as i32) } @@ -439,6 +440,7 @@ pub struct Runtime<'a, E: Ext + 'a> { input_data: Option>, memory: sp_sandbox::default_executor::Memory, trap_reason: Option, + chain_extension: Option::ChainExtension>>, } impl<'a, E> Runtime<'a, E> @@ -452,7 +454,13 @@ where input_data: Vec, memory: sp_sandbox::default_executor::Memory, ) -> Self { - Runtime { ext, input_data: Some(input_data), memory, trap_reason: None } + Runtime { + ext, + input_data: Some(input_data), + memory, + trap_reason: None, + chain_extension: Some(Box::new(Default::default())), + } } /// Converts the sandbox result and the runtime state into the execution outcome. @@ -2006,7 +2014,7 @@ define_env!(Env, , // module error. [seal0] seal_call_chain_extension( ctx, - func_id: u32, + id: u32, input_ptr: u32, input_len: u32, output_ptr: u32, @@ -2016,14 +2024,20 @@ define_env!(Env, , if !::ChainExtension::enabled() { return Err(Error::::NoChainExtension.into()); } - let env = Environment::new(ctx, input_ptr, input_len, output_ptr, output_len_ptr); - match ::ChainExtension::call(func_id, env)? { + let mut chain_extension = ctx.chain_extension.take().expect( + "Constructor initializes with `Some`. This is the only place where it is set to `None`.\ + It is always reset to `Some` afterwards. qed" + ); + let env = Environment::new(ctx, id, input_ptr, input_len, output_ptr, output_len_ptr); + let ret = match chain_extension.call(env)? { RetVal::Converging(val) => Ok(val), RetVal::Diverging{flags, data} => Err(TrapReason::Return(ReturnData { flags: flags.bits(), data, })), - } + }; + ctx.chain_extension = Some(chain_extension); + ret }, // Emit a custom debug message. From 506005800ae140bb9b02b207f9b4ae2072cf21a8 Mon Sep 17 00:00:00 2001 From: Qinxuan Chen Date: Tue, 26 Jul 2022 00:52:11 +0800 Subject: [PATCH 422/484] Pruned duplicated dependencies (#11900) * Update comfy-table v5.0.1 => v6.0.0 Signed-off-by: koushiro * Update strum v0.23.0 => v0.24.1 Signed-off-by: koushiro * Update h2 v0.3.9 => v0.3.13 Signed-off-by: koushiro * Update file-per-thread-logger v0.1.4 => v0.1.5 Signed-off-by: koushiro * Update mio v0.8.0 => v0.8.4 Signed-off-by: koushiro * revert twox-hash Signed-off-by: koushiro * Update secp256k1 v0.21.2 => v0.24.0 Signed-off-by: koushiro --- Cargo.lock | 324 +++--------------- client/beefy/Cargo.toml | 2 +- .../election-provider-multi-phase/Cargo.toml | 9 +- primitives/core/Cargo.toml | 2 +- primitives/core/src/ecdsa.rs | 2 +- primitives/io/Cargo.toml | 2 +- primitives/keyring/Cargo.toml | 2 +- utils/frame/benchmarking-cli/Cargo.toml | 2 +- utils/wasm-builder/Cargo.toml | 2 +- 9 files changed, 65 insertions(+), 282 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3d5bfe5e51201..8c19c80bde1a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -230,7 +230,7 @@ dependencies = [ "parking", "polling", "slab", - "socket2 0.4.4", + "socket2", "waker-fn", "winapi", ] @@ -310,7 +310,7 @@ dependencies = [ "futures-io", "futures-util", "pin-utils", - "socket2 0.4.4", + "socket2", "trust-dns-resolver", ] @@ -382,12 +382,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "autocfg" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" - [[package]] name = "autocfg" version = "1.0.1" @@ -512,7 +506,7 @@ name = "beefy-merkle-tree" version = "4.0.0-dev" dependencies = [ "beefy-primitives", - "env_logger 0.9.0", + "env_logger", "hex", "hex-literal", "log", @@ -1005,7 +999,7 @@ version = "3.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25320346e922cffe59c0bbc5410c8d8784509efb321488971081313cb1e1a33c" dependencies = [ - "heck 0.4.0", + "heck", "proc-macro-error", "proc-macro2", "quote", @@ -1021,15 +1015,6 @@ dependencies = [ "os_str_bytes", ] -[[package]] -name = "cloudabi" -version = "0.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" -dependencies = [ - "bitflags", -] - [[package]] name = "cmake" version = "0.1.46" @@ -1041,9 +1026,9 @@ dependencies = [ [[package]] name = "comfy-table" -version = "5.0.1" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b103d85ca6e209388771bfb7aa6b68a7aeec4afbf6f0a0264bfbf50360e5212e" +checksum = "121d8a5b0346092c18a4b2fd6f620d7a06f0eb7ac0a45860939a0884bc579c56" dependencies = [ "strum", "strum_macros", @@ -1831,7 +1816,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21cdad81446a7f7dc43f6a77409efeb9733d2fa65553efef6018ef257c959b73" dependencies = [ - "heck 0.4.0", + "heck", "proc-macro2", "quote", "syn", @@ -1898,19 +1883,6 @@ dependencies = [ "syn", ] -[[package]] -name = "env_logger" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" -dependencies = [ - "atty", - "humantime 1.3.0", - "log", - "regex", - "termcolor", -] - [[package]] name = "env_logger" version = "0.9.0" @@ -1918,7 +1890,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" dependencies = [ "atty", - "humantime 2.1.0", + "humantime", "log", "regex", "termcolor", @@ -2008,11 +1980,11 @@ dependencies = [ [[package]] name = "file-per-thread-logger" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fdbe0d94371f9ce939b555dd342d0686cc4c0cadbcd4b61d70af5ff97eb4126" +checksum = "21e16290574b39ee41c71aeb90ae960c504ebaf1e2a1c87bd52aa56ed6e1a02f" dependencies = [ - "env_logger 0.7.1", + "env_logger", "log", ] @@ -2445,12 +2417,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" -[[package]] -name = "fuchsia-cprng" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" - [[package]] name = "funty" version = "2.0.0" @@ -2751,9 +2717,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.9" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f072413d126e57991455e0a922b31e4c8ba7c2ffbebf6b78b4f8521397d65cd" +checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" dependencies = [ "bytes", "fnv", @@ -2764,7 +2730,7 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.6.7", + "tokio-util", "tracing", ] @@ -2821,15 +2787,6 @@ dependencies = [ "ahash", ] -[[package]] -name = "heck" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "heck" version = "0.4.0" @@ -2950,15 +2907,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440" -[[package]] -name = "humantime" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" -dependencies = [ - "quick-error 1.2.3", -] - [[package]] name = "humantime" version = "2.1.0" @@ -2982,7 +2930,7 @@ dependencies = [ "httpdate", "itoa 0.4.8", "pin-project-lite 0.2.6", - "socket2 0.4.4", + "socket2", "tokio", "tower-service", "tracing", @@ -3084,7 +3032,7 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" dependencies = [ - "autocfg 1.0.1", + "autocfg", "hashbrown 0.11.2", "serde", ] @@ -3131,7 +3079,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "723519edce41262b05d4143ceb95050e4c614f483e78e9fd9e39a8275a84ad98" dependencies = [ - "socket2 0.4.4", + "socket2", "widestring", "winapi", "winreg", @@ -3213,7 +3161,7 @@ dependencies = [ "thiserror", "tokio", "tokio-rustls", - "tokio-util 0.7.1", + "tokio-util", "tracing", "webpki-roots", ] @@ -3316,7 +3264,7 @@ dependencies = [ "soketto", "tokio", "tokio-stream", - "tokio-util 0.7.1", + "tokio-util", "tracing", ] @@ -3695,7 +3643,7 @@ dependencies = [ "log", "rand 0.8.4", "smallvec", - "socket2 0.4.4", + "socket2", "void", ] @@ -3913,7 +3861,7 @@ dependencies = [ "libc", "libp2p-core", "log", - "socket2 0.4.4", + "socket2", ] [[package]] @@ -4276,7 +4224,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" dependencies = [ - "autocfg 1.0.1", + "autocfg", ] [[package]] @@ -4321,30 +4269,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" dependencies = [ "adler", - "autocfg 1.0.1", + "autocfg", ] [[package]] name = "mio" -version = "0.8.0" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2" +checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" dependencies = [ "libc", "log", - "miow", - "ntapi", - "winapi", -] - -[[package]] -name = "miow" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897" -dependencies = [ - "socket2 0.3.19", - "winapi", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.36.1", ] [[package]] @@ -5015,22 +4952,13 @@ dependencies = [ "version_check", ] -[[package]] -name = "ntapi" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" -dependencies = [ - "winapi", -] - [[package]] name = "num-bigint" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" dependencies = [ - "autocfg 1.0.1", + "autocfg", "num-integer", "num-traits", ] @@ -5060,7 +4988,7 @@ version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ - "autocfg 1.0.1", + "autocfg", "num-traits", ] @@ -5070,7 +4998,7 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" dependencies = [ - "autocfg 1.0.1", + "autocfg", "num-bigint", "num-integer", "num-traits", @@ -5082,7 +5010,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" dependencies = [ - "autocfg 1.0.1", + "autocfg", "num-integer", "num-traits", ] @@ -5093,7 +5021,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ - "autocfg 1.0.1", + "autocfg", "libm", ] @@ -5500,7 +5428,7 @@ version = "4.0.0-dev" dependencies = [ "assert_matches", "bitflags", - "env_logger 0.9.0", + "env_logger", "frame-benchmarking", "frame-support", "frame-system", @@ -5866,7 +5794,7 @@ name = "pallet-mmr" version = "4.0.0-dev" dependencies = [ "ckb-merkle-mountain-range", - "env_logger 0.9.0", + "env_logger", "frame-benchmarking", "frame-support", "frame-system", @@ -7082,7 +7010,7 @@ dependencies = [ "bytes", "cfg-if 1.0.0", "cmake", - "heck 0.4.0", + "heck", "itertools", "lazy_static", "log", @@ -7207,25 +7135,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" -[[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 0.1.2", - "rand_xorshift", - "winapi", -] - [[package]] name = "rand" version = "0.7.3" @@ -7252,16 +7161,6 @@ dependencies = [ "rand_hc 0.3.0", ] -[[package]] -name = "rand_chacha" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" -dependencies = [ - "autocfg 0.1.7", - "rand_core 0.3.1", -] - [[package]] name = "rand_chacha" version = "0.2.2" @@ -7282,21 +7181,6 @@ dependencies = [ "rand_core 0.6.2", ] -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -dependencies = [ - "rand_core 0.4.2", -] - -[[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" - [[package]] name = "rand_core" version = "0.5.1" @@ -7325,15 +7209,6 @@ dependencies = [ "rand 0.8.4", ] -[[package]] -name = "rand_hc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" -dependencies = [ - "rand_core 0.3.1", -] - [[package]] name = "rand_hc" version = "0.2.0" @@ -7352,50 +7227,6 @@ dependencies = [ "rand_core 0.6.2", ] -[[package]] -name = "rand_isaac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" -dependencies = [ - "rand_core 0.3.1", -] - -[[package]] -name = "rand_jitter" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" -dependencies = [ - "libc", - "rand_core 0.4.2", - "winapi", -] - -[[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_pcg" version = "0.2.1" @@ -7414,15 +7245,6 @@ dependencies = [ "rand_core 0.6.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 = "rawpointer" version = "0.2.1" @@ -7435,7 +7257,7 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674" dependencies = [ - "autocfg 1.0.1", + "autocfg", "crossbeam-deque", "either", "rayon-core", @@ -7454,15 +7276,6 @@ dependencies = [ "num_cpus", ] -[[package]] -name = "rdrand" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -dependencies = [ - "rand_core 0.3.1", -] - [[package]] name = "redox_syscall" version = "0.2.10" @@ -7580,7 +7393,7 @@ dependencies = [ name = "remote-externalities" version = "0.10.0-dev" dependencies = [ - "env_logger 0.9.0", + "env_logger", "frame-support", "jsonrpsee", "log", @@ -8317,7 +8130,7 @@ name = "sc-executor" version = "0.10.0-dev" dependencies = [ "criterion", - "env_logger 0.9.0", + "env_logger", "hex-literal", "lazy_static", "lru", @@ -8747,7 +8560,7 @@ name = "sc-rpc" version = "4.0.0-dev" dependencies = [ "assert_matches", - "env_logger 0.9.0", + "env_logger", "futures", "hash-db", "jsonrpsee", @@ -9189,19 +9002,18 @@ dependencies = [ [[package]] name = "secp256k1" -version = "0.21.2" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab7883017d5b21f011ef8040ea9c6c7ac90834c0df26a69e4c0b06276151f125" +checksum = "b7649a0b3ffb32636e60c7ce0d70511eda9c52c658cd0634e194d5a19943aeff" dependencies = [ - "rand 0.6.5", "secp256k1-sys", ] [[package]] name = "secp256k1-sys" -version = "0.4.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957da2573cde917463ece3570eab4a0b3f19de6f1646cde62e6fd3868f566036" +checksum = "7058dc8eaf3f2810d7828680320acda0b25a288f6d288e19278e249bbf74226b" dependencies = [ "cc", ] @@ -9510,17 +9322,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "socket2" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "winapi", -] - [[package]] name = "socket2" version = "0.4.4" @@ -10427,20 +10228,20 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "strum" -version = "0.23.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cae14b91c7d11c9a851d3fbc80a963198998c2a64eec840477fa92d8ce9b70bb" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" -version = "0.23.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb0dc7ee9c15cea6199cde9a127fa16a4c5819af85395457ad72d68edc85a38" +checksum = "4faebde00e8ff94316c01800f9054fd2ba77d30d9e922541913051d1d978918b" dependencies = [ - "heck 0.3.2", + "heck", "proc-macro2", "quote", "rustversion", @@ -10947,7 +10748,7 @@ dependencies = [ "parking_lot 0.12.0", "pin-project-lite 0.2.6", "signal-hook-registry", - "socket2 0.4.4", + "socket2", "tokio-macros", "winapi", ] @@ -10998,20 +10799,6 @@ dependencies = [ "tokio-stream", ] -[[package]] -name = "tokio-util" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1caa0b0c8d94a049db56b5acf8cba99dc0623aab1b26d5b5f5e2d945846b3592" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "log", - "pin-project-lite 0.2.6", - "tokio", -] - [[package]] name = "tokio-util" version = "0.7.1" @@ -11024,6 +10811,7 @@ dependencies = [ "futures-sink", "pin-project-lite 0.2.6", "tokio", + "tracing", ] [[package]] @@ -11347,12 +11135,6 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "unicode-segmentation" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" - [[package]] name = "unicode-width" version = "0.1.8" @@ -11493,6 +11275,12 @@ version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wasm-bindgen" version = "0.2.77" diff --git a/client/beefy/Cargo.toml b/client/beefy/Cargo.toml index bd25496f2dfea..b910f7ce307a5 100644 --- a/client/beefy/Cargo.toml +++ b/client/beefy/Cargo.toml @@ -39,7 +39,7 @@ sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } [dev-dependencies] serde = "1.0.136" -strum = { version = "0.23", features = ["derive"] } +strum = { version = "0.24.1", features = ["derive"] } tempfile = "3.1.0" tokio = "1.17.0" sc-consensus = { version = "0.10.0-dev", path = "../consensus/common" } diff --git a/frame/election-provider-multi-phase/Cargo.toml b/frame/election-provider-multi-phase/Cargo.toml index 6c8e537a72c64..bbb0adf02e366 100644 --- a/frame/election-provider-multi-phase/Cargo.toml +++ b/frame/election-provider-multi-phase/Cargo.toml @@ -36,13 +36,8 @@ frame-election-provider-support = { version = "4.0.0-dev", default-features = fa # Optional imports for benchmarking frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } pallet-election-provider-support-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../election-provider-support/benchmarking", optional = true } -rand = { version = "0.7.3", default-features = false, optional = true, features = [ - "alloc", - "small_rng", -] } -strum = { optional = true, default-features = false, version = "0.23.0", features = [ - "derive", -] } +rand = { version = "0.7.3", default-features = false, features = ["alloc", "small_rng"], optional = true } +strum = { version = "0.24.1", default-features = false, features = ["derive"], optional = true } [dev-dependencies] parking_lot = "0.12.0" diff --git a/primitives/core/Cargo.toml b/primitives/core/Cargo.toml index cdc12c677e4f3..fced678293140 100644 --- a/primitives/core/Cargo.toml +++ b/primitives/core/Cargo.toml @@ -56,7 +56,7 @@ schnorrkel = { version = "0.9.1", features = [ hex = { version = "0.4", default-features = false, optional = true } libsecp256k1 = { version = "0.7", default-features = false, features = ["static-context"], optional = true } merlin = { version = "2.0", default-features = false, optional = true } -secp256k1 = { version = "0.21.2", default-features = false, features = ["recovery", "alloc"], optional = true } +secp256k1 = { version = "0.24.0", default-features = false, features = ["recovery", "alloc"], optional = true } ss58-registry = { version = "1.18.0", default-features = false } sp-core-hashing = { version = "4.0.0", path = "./hashing", default-features = false, optional = true } sp-runtime-interface = { version = "6.0.0", default-features = false, path = "../runtime-interface" } diff --git a/primitives/core/src/ecdsa.rs b/primitives/core/src/ecdsa.rs index 485e39d3a71db..d56f65fd289e7 100644 --- a/primitives/core/src/ecdsa.rs +++ b/primitives/core/src/ecdsa.rs @@ -499,7 +499,7 @@ impl TraitPair for Pair { impl Pair { /// Get the seed for this key. pub fn seed(&self) -> Seed { - self.secret.serialize_secret() + self.secret.secret_bytes() } /// Exactly as `from_string` except that if no matches are found then, the the first 32 diff --git a/primitives/io/Cargo.toml b/primitives/io/Cargo.toml index 03cd8535665ac..77ba4be677f31 100644 --- a/primitives/io/Cargo.toml +++ b/primitives/io/Cargo.toml @@ -30,7 +30,7 @@ sp-tracing = { version = "5.0.0", default-features = false, path = "../tracing" log = { version = "0.4.17", optional = true } futures = { version = "0.3.21", features = ["thread-pool"], optional = true } parking_lot = { version = "0.12.0", optional = true } -secp256k1 = { version = "0.21.2", features = ["recovery", "global-context"], optional = true } +secp256k1 = { version = "0.24.0", features = ["recovery", "global-context"], optional = true } tracing = { version = "0.1.29", default-features = false } tracing-core = { version = "0.1.28", default-features = false} diff --git a/primitives/keyring/Cargo.toml b/primitives/keyring/Cargo.toml index 8f1adb6cf81f3..982abfd09a553 100644 --- a/primitives/keyring/Cargo.toml +++ b/primitives/keyring/Cargo.toml @@ -15,6 +15,6 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] lazy_static = "1.4.0" -strum = { version = "0.23.0", features = ["derive"] } +strum = { version = "0.24.1", features = ["derive"] } sp-core = { version = "6.0.0", path = "../core" } sp-runtime = { version = "6.0.0", path = "../runtime" } diff --git a/utils/frame/benchmarking-cli/Cargo.toml b/utils/frame/benchmarking-cli/Cargo.toml index ea26bf0c9261c..31b8f1332a653 100644 --- a/utils/frame/benchmarking-cli/Cargo.toml +++ b/utils/frame/benchmarking-cli/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] chrono = "0.4" clap = { version = "3.1.18", features = ["derive"] } codec = { package = "parity-scale-codec", version = "3.0.0" } -comfy-table = { version = "5.0.1", default-features = false } +comfy-table = { version = "6.0.0", default-features = false } handlebars = "4.2.2" hash-db = "0.15.2" hex = "0.4.3" diff --git a/utils/wasm-builder/Cargo.toml b/utils/wasm-builder/Cargo.toml index 8f887e45ec176..ac0ba5dbdb85a 100644 --- a/utils/wasm-builder/Cargo.toml +++ b/utils/wasm-builder/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] ansi_term = "0.12.1" build-helper = "0.1.1" cargo_metadata = "0.14.2" -strum = { version = "0.23.0", features = ["derive"] } +strum = { version = "0.24.1", features = ["derive"] } tempfile = "3.1.0" toml = "0.5.4" walkdir = "2.3.2" From 17ed59a28a9ed114d1aac28982db7fe9521ce08e Mon Sep 17 00:00:00 2001 From: Sacha Lansky Date: Mon, 25 Jul 2022 23:09:50 +0100 Subject: [PATCH 423/484] Fix typos (#11914) --- frame/transaction-payment/src/lib.rs | 2 +- test-utils/runtime/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/transaction-payment/src/lib.rs b/frame/transaction-payment/src/lib.rs index 0f5c0321130be..fe37acb214452 100644 --- a/frame/transaction-payment/src/lib.rs +++ b/frame/transaction-payment/src/lib.rs @@ -609,7 +609,7 @@ where /// and user-included tip. /// /// The priority is based on the amount of `tip` the user is willing to pay per unit of either - /// `weight` or `length`, depending which one is more limitting. For `Operational` extrinsics + /// `weight` or `length`, depending which one is more limiting. For `Operational` extrinsics /// we add a "virtual tip" to the calculations. /// /// The formula should simply be `tip / bounded_{weight|length}`, but since we are using diff --git a/test-utils/runtime/src/lib.rs b/test-utils/runtime/src/lib.rs index ea62f2ac84f3d..e5cfae49da56d 100644 --- a/test-utils/runtime/src/lib.rs +++ b/test-utils/runtime/src/lib.rs @@ -237,7 +237,7 @@ impl sp_runtime::traits::Dispatchable for Extrinsic { type Info = (); type PostInfo = (); fn dispatch(self, _origin: Self::Origin) -> sp_runtime::DispatchResultWithInfo { - panic!("This implemention should not be used for actual dispatch."); + panic!("This implementation should not be used for actual dispatch."); } } From f86ed2ca35d9ee62a4db0f2ee1e48aa17ab56f8d Mon Sep 17 00:00:00 2001 From: Sergej Sakac <73715684+Szegoo@users.noreply.github.com> Date: Tue, 26 Jul 2022 11:06:48 +0200 Subject: [PATCH 424/484] Rpc for pending rewards (#11831) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * rpc pending rewards * commit * remove unused imports * fix * fix * fmt * fix * fmt * fix * docs * docs & formatting * better formatting * temporary fix * error handling * fix? * fmt * use to_string * fmt * fixed error handling * fix * rpc added to client * Update Cargo.toml * Update Cargo.toml * fix wrong reward counter * expose function * move implementation * docs * docs * docs * Update lib.rs * Update lib.rs * unexpose functions * unused dependency * update Cargo.lock * Update frame/nomination-pools/src/lib.rs * Update lib.rs * Update lib.rs * Update frame/nomination-pools/rpc/runtime-api/src/lib.rs Co-authored-by: Bastian Köcher * remove rpc * remove rpc directory * final fix Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Co-authored-by: Bastian Köcher --- Cargo.lock | 10 ++++++ Cargo.toml | 1 + bin/node/runtime/Cargo.toml | 2 ++ bin/node/runtime/src/lib.rs | 6 ++++ frame/nomination-pools/runtime-api/Cargo.toml | 26 +++++++++++++++ frame/nomination-pools/runtime-api/README.md | 3 ++ frame/nomination-pools/runtime-api/src/lib.rs | 33 +++++++++++++++++++ frame/nomination-pools/src/lib.rs | 16 ++++++++- 8 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 frame/nomination-pools/runtime-api/Cargo.toml create mode 100644 frame/nomination-pools/runtime-api/README.md create mode 100644 frame/nomination-pools/runtime-api/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 8c19c80bde1a9..50603c6235a23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4759,6 +4759,7 @@ dependencies = [ "pallet-multisig", "pallet-nomination-pools", "pallet-nomination-pools-benchmarking", + "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-offences-benchmarking", "pallet-preimage", @@ -5911,6 +5912,15 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-nomination-pools-runtime-api" +version = "1.0.0-dev" +dependencies = [ + "parity-scale-codec", + "sp-api", + "sp-std", +] + [[package]] name = "pallet-nomination-pools-test-staking" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index 9909e6f893877..1f22343c002a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -116,6 +116,7 @@ members = [ "frame/nomination-pools", "frame/nomination-pools/benchmarking", "frame/nomination-pools/test-staking", + "frame/nomination-pools/runtime-api", "frame/randomness-collective-flip", "frame/ranked-collective", "frame/recovery", diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index ca971f29e93c9..d3138ce51de9c 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -79,6 +79,7 @@ pallet-mmr = { version = "4.0.0-dev", default-features = false, path = "../../.. pallet-multisig = { version = "4.0.0-dev", default-features = false, path = "../../../frame/multisig" } pallet-nomination-pools = { version = "1.0.0", default-features = false, path = "../../../frame/nomination-pools"} pallet-nomination-pools-benchmarking = { version = "1.0.0", default-features = false, optional = true, path = "../../../frame/nomination-pools/benchmarking" } +pallet-nomination-pools-runtime-api = { version = "1.0.0-dev", default-features = false, path = "../../../frame/nomination-pools/runtime-api" } pallet-offences = { version = "4.0.0-dev", default-features = false, path = "../../../frame/offences" } pallet-offences-benchmarking = { version = "4.0.0-dev", path = "../../../frame/offences/benchmarking", default-features = false, optional = true } pallet-preimage = { version = "4.0.0-dev", default-features = false, path = "../../../frame/preimage" } @@ -145,6 +146,7 @@ std = [ "pallet-mmr/std", "pallet-multisig/std", "pallet-nomination-pools/std", + "pallet-nomination-pools-runtime-api/std", "pallet-identity/std", "pallet-scheduler/std", "node-primitives/std", diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index b2efcb196787d..037211155cf0e 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1835,6 +1835,12 @@ impl_runtime_apis! { } } + impl pallet_nomination_pools_runtime_api::NominationPoolsApi for Runtime { + fn pending_rewards(member_account: AccountId) -> Balance { + NominationPools::pending_rewards(member_account) + } + } + impl sp_consensus_babe::BabeApi for Runtime { fn configuration() -> sp_consensus_babe::BabeGenesisConfiguration { // The choice of `c` parameter (where `1 - c` represents the diff --git a/frame/nomination-pools/runtime-api/Cargo.toml b/frame/nomination-pools/runtime-api/Cargo.toml new file mode 100644 index 0000000000000..dde925c62fe0d --- /dev/null +++ b/frame/nomination-pools/runtime-api/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "pallet-nomination-pools-runtime-api" +version = "1.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Runtime API for nomination-pools FRAME pallet" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +sp-api = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/api" } +sp-std = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/std" } + +[features] +default = ["std"] +std = [ + "codec/std", + "sp-api/std", + "sp-std/std", +] diff --git a/frame/nomination-pools/runtime-api/README.md b/frame/nomination-pools/runtime-api/README.md new file mode 100644 index 0000000000000..af90b31733b0b --- /dev/null +++ b/frame/nomination-pools/runtime-api/README.md @@ -0,0 +1,3 @@ +Runtime API definition for nomination-pools pallet. + +License: Apache-2.0 \ No newline at end of file diff --git a/frame/nomination-pools/runtime-api/src/lib.rs b/frame/nomination-pools/runtime-api/src/lib.rs new file mode 100644 index 0000000000000..aa3ca57ca5b8b --- /dev/null +++ b/frame/nomination-pools/runtime-api/src/lib.rs @@ -0,0 +1,33 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Runtime API definition for nomination-pools pallet. +//! Currently supports only one rpc endpoint. + +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::Codec; + +sp_api::decl_runtime_apis! { + /// Runtime api for accessing information about nomination pools. + pub trait NominationPoolsApi + where AccountId: Codec, Balance: Codec + { + /// Returns the pending rewards for the member that the AccountId was given for. + fn pending_rewards(member: AccountId) -> Balance; + } +} diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 203c13f2f3496..09f1746b8e5ba 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -939,7 +939,7 @@ pub struct RewardPool { impl RewardPool { /// Getter for [`RewardPool::last_recorded_reward_counter`]. - fn last_recorded_reward_counter(&self) -> T::RewardCounter { + pub(crate) fn last_recorded_reward_counter(&self) -> T::RewardCounter { self.last_recorded_reward_counter } @@ -2147,6 +2147,20 @@ pub mod pallet { } impl Pallet { + /// Returns the pending rewards for the specified `member_account`. + /// + /// In the case of error the function returns balance of zero. + pub fn pending_rewards(member_account: T::AccountId) -> BalanceOf { + if let Some(pool_member) = PoolMembers::::get(member_account) { + if let Some(reward_pool) = RewardPools::::get(pool_member.pool_id) { + return pool_member + .pending_rewards(reward_pool.last_recorded_reward_counter()) + .unwrap_or_default() + } + } + BalanceOf::::default() + } + /// The amount of bond that MUST REMAIN IN BONDED in ALL POOLS. /// /// It is the responsibility of the depositor to put these funds into the pool initially. Upon From c420d97cd1ccbb0621e4f22472a4bd60609acbe6 Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Tue, 26 Jul 2022 11:37:54 +0200 Subject: [PATCH 425/484] [ci] remove cargo-check-nixos job (#11873) * [ci] remove cargo-check-nixos job * remove shell.nix --- scripts/ci/gitlab/pipeline/test.yml | 24 ++++-------------------- shell.nix | 27 --------------------------- 2 files changed, 4 insertions(+), 47 deletions(-) delete mode 100644 shell.nix diff --git a/scripts/ci/gitlab/pipeline/test.yml b/scripts/ci/gitlab/pipeline/test.yml index d900d425ad494..b2daf5bada24f 100644 --- a/scripts/ci/gitlab/pipeline/test.yml +++ b/scripts/ci/gitlab/pipeline/test.yml @@ -51,26 +51,6 @@ cargo-clippy: - SKIP_WASM_BUILD=1 env -u RUSTFLAGS cargo +nightly clippy --all-targets - rusty-cachier cache upload -cargo-check-nixos: - stage: test - # this is an artificial job dependency, for pipeline optimization using GitLab's DAGs - needs: - - job: cargo-clippy - artifacts: false - extends: - - .docker-env - - .test-refs - before_script: [] - # Don't use CI_IMAGE here because it breaks nightly checks of paritytech/ci-linux image - image: nixos/nix - variables: - SNAP: "DUMMY" - WS_API: "DUMMY" - script: - - nix-channel --update - - nix-shell shell.nix - - nix-shell --run "cargo check --workspace --all-targets --all-features" - cargo-check-benches: stage: test variables: @@ -337,6 +317,8 @@ test-linux-stable-int: time cargo test -p node-cli --release --verbose --locked -- --ignored - rusty-cachier cache upload +# more information about this job can be found here: +# https://github.com/paritytech/substrate/pull/6916 check-tracing: stage: test # this is an artificial job dependency, for pipeline optimization using GitLab's DAGs @@ -355,6 +337,8 @@ check-tracing: - time cargo +nightly test --manifest-path ./primitives/tracing/Cargo.toml --no-default-features --features=with-tracing - rusty-cachier cache upload +# more information about this job can be found here: +# https://github.com/paritytech/substrate/pull/3778 test-full-crypto-feature: stage: test # this is an artificial job dependency, for pipeline optimization using GitLab's DAGs diff --git a/shell.nix b/shell.nix deleted file mode 100644 index c318995605a38..0000000000000 --- a/shell.nix +++ /dev/null @@ -1,27 +0,0 @@ -let - mozillaOverlay = - import (builtins.fetchGit { - url = "https://github.com/mozilla/nixpkgs-mozilla.git"; - rev = "15b7a05f20aab51c4ffbefddb1b448e862dccb7d"; - }); - nixpkgs = import { overlays = [ mozillaOverlay ]; }; - rust-nightly = with nixpkgs; ((rustChannelOf { date = "2022-04-20"; channel = "nightly"; }).rust.override { - extensions = [ "rust-src" ]; - targets = [ "wasm32-unknown-unknown" ]; - }); -in -with nixpkgs; pkgs.mkShell { - buildInputs = [ - clang - openssl.dev - pkg-config - rust-nightly - ] ++ lib.optionals stdenv.isDarwin [ - darwin.apple_sdk.frameworks.Security - ]; - - RUST_SRC_PATH = "${rust-nightly}/lib/rustlib/src/rust/src"; - LIBCLANG_PATH = "${llvmPackages.libclang.lib}/lib"; - PROTOC = "${protobuf}/bin/protoc"; - ROCKSDB_LIB_DIR = "${rocksdb}/lib"; -} From c799398e19d7341a106c679efba7eebac1d003ff Mon Sep 17 00:00:00 2001 From: Sebastian Kunert Date: Tue, 26 Jul 2022 14:37:05 +0200 Subject: [PATCH 426/484] Prepare for rust 1.62.1 (#11903) * Update UI test output for rust 1.62.1 * switch ci to staging image to check that everything works * fix artifacts node-bench-regression-guard * Imeplement `scale_info::TypeInfo` manually to silence aggressive rust warning * Fix more clippy lints * Make clippy happy by relying on auto-deref were possible * Add tracking issue to the comments * pin ci image Co-authored-by: alvicsam --- .gitlab-ci.yml | 3 +- bin/node/bench/src/simple_trie.rs | 2 +- bin/node/cli/benches/transaction_pool.rs | 8 +-- client/beefy/src/tests.rs | 14 ++--- client/beefy/src/worker.rs | 4 +- client/cli/src/commands/run_cmd.rs | 2 +- client/consensus/common/src/block_import.rs | 4 +- client/db/src/lib.rs | 2 +- .../finality-grandpa/src/communication/mod.rs | 6 +- client/finality-grandpa/src/import.rs | 2 +- client/finality-grandpa/src/observer.rs | 2 +- client/rpc/src/offchain/mod.rs | 4 +- client/rpc/src/state/state_full.rs | 2 +- client/service/src/client/client.rs | 4 +- client/tracing/src/logging/stderr_writer.rs | 2 +- client/transaction-pool/tests/pool.rs | 14 ++--- frame/contracts/src/exec.rs | 2 +- frame/executive/src/lib.rs | 4 +- frame/offences/src/mock.rs | 2 +- .../procedural/src/pallet/parse/call.rs | 2 +- .../no_std_genesis_config.stderr | 13 ++++ .../undefined_event_part.stderr | 26 ++++++++ .../undefined_genesis_config_part.stderr | 13 ++++ .../undefined_origin_part.stderr | 26 ++++++++ ...ed_keyword_two_times_integrity_test.stderr | 14 ++--- frame/support/test/tests/decl_storage.rs | 2 +- .../derive_no_bound_ui/partial_eq.stderr | 4 +- .../call_argument_invalid_bound.stderr | 5 -- .../call_argument_invalid_bound_2.stderr | 5 -- .../call_argument_invalid_bound_3.stderr | 4 ++ .../error_does_not_derive_pallet_error.stderr | 10 ++++ .../pallet_ui/event_field_not_member.stderr | 5 -- .../genesis_default_not_satisfied.stderr | 4 ++ ...age_ensure_span_are_ok_on_wrong_gen.stderr | 60 +++++++++++++++++++ ...re_span_are_ok_on_wrong_gen_unnamed.stderr | 60 +++++++++++++++++++ .../pallet_ui/storage_info_unsatisfied.stderr | 10 ++++ .../storage_info_unsatisfied_nmap.stderr | 10 ++++ .../api/proc-macro/src/impl_runtime_apis.rs | 2 +- primitives/runtime/src/bounded/bounded_vec.rs | 40 ++++++++++++- primitives/runtime/src/curve.rs | 32 +++++++++- primitives/runtime/src/traits.rs | 2 +- primitives/trie/src/lib.rs | 12 ++-- .../proc-macro/src/decl_runtime_version.rs | 2 +- rustfmt.toml | 1 + utils/frame/remote-externalities/src/lib.rs | 2 +- 45 files changed, 365 insertions(+), 84 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9608d88e9554d..d1de666009bf1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -47,7 +47,8 @@ variables: &default-vars CARGO_INCREMENTAL: 0 DOCKER_OS: "debian:stretch" ARCH: "x86_64" - CI_IMAGE: "paritytech/ci-linux:production" + # change to production when this image is published + CI_IMAGE: "paritytech/ci-linux:staging@sha256:2c90b67f1452ed2d7236c2cd13f6224053a833d521b3630650b679f00874e0a9" RUSTY_CACHIER_SINGLE_BRANCH: master RUSTY_CACHIER_DONT_OPERATE_ON_MAIN_BRANCH: "true" diff --git a/bin/node/bench/src/simple_trie.rs b/bin/node/bench/src/simple_trie.rs index c59389570e534..aa9c96a1cbd3f 100644 --- a/bin/node/bench/src/simple_trie.rs +++ b/bin/node/bench/src/simple_trie.rs @@ -33,7 +33,7 @@ pub struct SimpleTrie<'a> { impl<'a> AsHashDB for SimpleTrie<'a> { fn as_hash_db(&self) -> &dyn hash_db::HashDB { - &*self + self } fn as_hash_db_mut<'b>(&'b mut self) -> &'b mut (dyn HashDB + 'b) { diff --git a/bin/node/cli/benches/transaction_pool.rs b/bin/node/cli/benches/transaction_pool.rs index 5b96355b9ca70..d88c3a65b6138 100644 --- a/bin/node/cli/benches/transaction_pool.rs +++ b/bin/node/cli/benches/transaction_pool.rs @@ -243,25 +243,25 @@ fn transaction_pool_benchmarks(c: &mut Criterion) { move |b| { b.iter_batched( || { - let prepare_extrinsics = create_account_extrinsics(&*node.client, &accounts); + let prepare_extrinsics = create_account_extrinsics(&node.client, &accounts); runtime.block_on(future::join_all(prepare_extrinsics.into_iter().map(|tx| { submit_tx_and_wait_for_inclusion( &node.transaction_pool, tx, - &*node.client, + &node.client, true, ) }))); - create_benchmark_extrinsics(&*node.client, &accounts, extrinsics_per_account) + create_benchmark_extrinsics(&node.client, &accounts, extrinsics_per_account) }, |extrinsics| { runtime.block_on(future::join_all(extrinsics.into_iter().map(|tx| { submit_tx_and_wait_for_inclusion( &node.transaction_pool, tx, - &*node.client, + &node.client, false, ) }))); diff --git a/client/beefy/src/tests.rs b/client/beefy/src/tests.rs index 8090c425e71db..78e697a6ada81 100644 --- a/client/beefy/src/tests.rs +++ b/client/beefy/src/tests.rs @@ -467,7 +467,7 @@ fn finalize_block_and_wait_for_beefy( finalize_targets: &[u64], expected_beefy: &[u64], ) { - let (best_blocks, signed_commitments) = get_beefy_streams(&mut *net.lock(), peers); + let (best_blocks, signed_commitments) = get_beefy_streams(&mut net.lock(), peers); for block in finalize_targets { let finalize = BlockId::number(*block); @@ -555,7 +555,7 @@ fn lagging_validators() { // Alice finalizes #25, Bob lags behind let finalize = BlockId::number(25); - let (best_blocks, signed_commitments) = get_beefy_streams(&mut *net.lock(), peers); + let (best_blocks, signed_commitments) = get_beefy_streams(&mut net.lock(), peers); net.lock().peer(0).client().as_client().finalize_block(finalize, None).unwrap(); // verify nothing gets finalized by BEEFY let timeout = Some(Duration::from_millis(250)); @@ -563,7 +563,7 @@ fn lagging_validators() { streams_empty_after_timeout(signed_commitments, &net, &mut runtime, None); // Bob catches up and also finalizes #25 - let (best_blocks, signed_commitments) = get_beefy_streams(&mut *net.lock(), peers); + let (best_blocks, signed_commitments) = get_beefy_streams(&mut net.lock(), peers); net.lock().peer(1).client().as_client().finalize_block(finalize, None).unwrap(); // expected beefy finalizes block #17 from diff-power-of-two wait_for_best_beefy_blocks(best_blocks, &net, &mut runtime, &[23, 24, 25]); @@ -577,7 +577,7 @@ fn lagging_validators() { // validator set). // Alice finalizes session-boundary mandatory block #60, Bob lags behind - let (best_blocks, signed_commitments) = get_beefy_streams(&mut *net.lock(), peers); + let (best_blocks, signed_commitments) = get_beefy_streams(&mut net.lock(), peers); let finalize = BlockId::number(60); net.lock().peer(0).client().as_client().finalize_block(finalize, None).unwrap(); // verify nothing gets finalized by BEEFY @@ -586,7 +586,7 @@ fn lagging_validators() { streams_empty_after_timeout(signed_commitments, &net, &mut runtime, None); // Bob catches up and also finalizes #60 (and should have buffered Alice's vote on #60) - let (best_blocks, signed_commitments) = get_beefy_streams(&mut *net.lock(), peers); + let (best_blocks, signed_commitments) = get_beefy_streams(&mut net.lock(), peers); net.lock().peer(1).client().as_client().finalize_block(finalize, None).unwrap(); // verify beefy skips intermediary votes, and successfully finalizes mandatory block #40 wait_for_best_beefy_blocks(best_blocks, &net, &mut runtime, &[60]); @@ -629,7 +629,7 @@ fn correct_beefy_payload() { finalize_block_and_wait_for_beefy(&net, peers, &mut runtime, &[10], &[1, 9]); let (best_blocks, signed_commitments) = - get_beefy_streams(&mut *net.lock(), &[BeefyKeyring::Alice]); + get_beefy_streams(&mut net.lock(), &[BeefyKeyring::Alice]); // now 2 good validators and 1 bad one are voting net.lock() @@ -658,7 +658,7 @@ fn correct_beefy_payload() { // 3rd good validator catches up and votes as well let (best_blocks, signed_commitments) = - get_beefy_streams(&mut *net.lock(), &[BeefyKeyring::Alice]); + get_beefy_streams(&mut net.lock(), &[BeefyKeyring::Alice]); net.lock() .peer(2) .client() diff --git a/client/beefy/src/worker.rs b/client/beefy/src/worker.rs index 735dea0170a62..dccf7ba7504dd 100644 --- a/client/beefy/src/worker.rs +++ b/client/beefy/src/worker.rs @@ -439,7 +439,7 @@ where let commitment = Commitment { payload, block_number: target_number, validator_set_id }; let encoded_commitment = commitment.encode(); - let signature = match self.key_store.sign(&authority_id, &*encoded_commitment) { + let signature = match self.key_store.sign(&authority_id, &encoded_commitment) { Ok(sig) => sig, Err(err) => { warn!(target: "beefy", "🥩 Error signing commitment: {:?}", err); @@ -451,7 +451,7 @@ where target: "beefy", "🥩 Produced signature using {:?}, is_valid: {:?}", authority_id, - BeefyKeystore::verify(&authority_id, &signature, &*encoded_commitment) + BeefyKeystore::verify(&authority_id, &signature, &encoded_commitment) ); let message = VoteMessage { commitment, id: authority_id, signature }; diff --git a/client/cli/src/commands/run_cmd.rs b/client/cli/src/commands/run_cmd.rs index 99eaa4be1cd0f..3a74fdd9700f2 100644 --- a/client/cli/src/commands/run_cmd.rs +++ b/client/cli/src/commands/run_cmd.rs @@ -556,7 +556,7 @@ impl std::error::Error for TelemetryParsingError {} impl std::fmt::Display for TelemetryParsingError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match &*self { + match self { TelemetryParsingError::MissingVerbosity => write!(f, "Verbosity level missing"), TelemetryParsingError::VerbosityParsingError(e) => write!(f, "{}", e), } diff --git a/client/consensus/common/src/block_import.rs b/client/consensus/common/src/block_import.rs index f81c8eb7e8dee..10739f63ef779 100644 --- a/client/consensus/common/src/block_import.rs +++ b/client/consensus/common/src/block_import.rs @@ -433,10 +433,10 @@ impl JustificationSyncLink for () { impl> JustificationSyncLink for Arc { fn request_justification(&self, hash: &B::Hash, number: NumberFor) { - L::request_justification(&*self, hash, number); + L::request_justification(self, hash, number); } fn clear_justification_requests(&self) { - L::clear_justification_requests(&*self); + L::clear_justification_requests(self); } } diff --git a/client/db/src/lib.rs b/client/db/src/lib.rs index f1adbd3df1a0f..7dd49f9831f1c 100644 --- a/client/db/src/lib.rs +++ b/client/db/src/lib.rs @@ -2292,7 +2292,7 @@ impl sc_client_api::backend::Backend for Backend { } fn get_import_lock(&self) -> &RwLock<()> { - &*self.import_lock + &self.import_lock } fn requires_full_sync(&self) -> bool { diff --git a/client/finality-grandpa/src/communication/mod.rs b/client/finality-grandpa/src/communication/mod.rs index 0555a03db149f..378501cffdd62 100644 --- a/client/finality-grandpa/src/communication/mod.rs +++ b/client/finality-grandpa/src/communication/mod.rs @@ -320,7 +320,7 @@ impl> NetworkBridge { voters: Arc>, has_voted: HasVoted, ) -> (impl Stream> + Unpin, OutgoingMessages) { - self.note_round(round, set_id, &*voters); + self.note_round(round, set_id, &voters); let keystore = keystore.and_then(|ks| { let id = ks.local_id(); @@ -637,9 +637,9 @@ fn incoming_global( .filter_map(move |(notification, msg)| { future::ready(match msg { GossipMessage::Commit(msg) => - process_commit(msg, notification, &gossip_engine, &gossip_validator, &*voters), + process_commit(msg, notification, &gossip_engine, &gossip_validator, &voters), GossipMessage::CatchUp(msg) => - process_catch_up(msg, notification, &gossip_engine, &gossip_validator, &*voters), + process_catch_up(msg, notification, &gossip_engine, &gossip_validator, &voters), _ => { debug!(target: "afg", "Skipping unknown message type"); None diff --git a/client/finality-grandpa/src/import.rs b/client/finality-grandpa/src/import.rs index eefb3d3f0aee4..b5a0d7be70f19 100644 --- a/client/finality-grandpa/src/import.rs +++ b/client/finality-grandpa/src/import.rs @@ -284,7 +284,7 @@ where impl<'a, H, N> InnerGuard<'a, H, N> { fn as_mut(&mut self) -> &mut AuthoritySet { - &mut **self.guard.as_mut().expect("only taken on deconstruction; qed") + self.guard.as_mut().expect("only taken on deconstruction; qed") } fn set_old(&mut self, old: AuthoritySet) { diff --git a/client/finality-grandpa/src/observer.rs b/client/finality-grandpa/src/observer.rs index 85d80dcfe7cde..9bcb03c0555c2 100644 --- a/client/finality-grandpa/src/observer.rs +++ b/client/finality-grandpa/src/observer.rs @@ -289,7 +289,7 @@ where network.note_round( crate::communication::Round(round), crate::communication::SetId(set_id), - &*voters, + &voters, ) } }; diff --git a/client/rpc/src/offchain/mod.rs b/client/rpc/src/offchain/mod.rs index b66b78274a64e..6896b82619166 100644 --- a/client/rpc/src/offchain/mod.rs +++ b/client/rpc/src/offchain/mod.rs @@ -57,7 +57,7 @@ impl OffchainApiServer for Offchain { StorageKind::PERSISTENT => sp_offchain::STORAGE_PREFIX, StorageKind::LOCAL => return Err(JsonRpseeError::from(Error::UnavailableStorageKind)), }; - self.storage.write().set(prefix, &*key, &*value); + self.storage.write().set(prefix, &key, &value); Ok(()) } @@ -69,6 +69,6 @@ impl OffchainApiServer for Offchain { StorageKind::LOCAL => return Err(JsonRpseeError::from(Error::UnavailableStorageKind)), }; - Ok(self.storage.read().get(prefix, &*key).map(Into::into)) + Ok(self.storage.read().get(prefix, &key).map(Into::into)) } } diff --git a/client/rpc/src/state/state_full.rs b/client/rpc/src/state/state_full.rs index c58638c870ab3..5a8a83cdd4851 100644 --- a/client/rpc/src/state/state_full.rs +++ b/client/rpc/src/state/state_full.rs @@ -199,7 +199,7 @@ where .call( &BlockId::Hash(block), &method, - &*call_data, + &call_data, self.client.execution_extensions().strategies().other, None, ) diff --git a/client/service/src/client/client.rs b/client/service/src/client/client.rs index fb73b4c34c040..d61d7f7fa3781 100644 --- a/client/service/src/client/client.rs +++ b/client/service/src/client/client.rs @@ -1614,11 +1614,11 @@ where RA: Send + Sync, { fn header(&self, id: BlockId) -> sp_blockchain::Result> { - (**self).backend.blockchain().header(id) + self.backend.blockchain().header(id) } fn info(&self) -> blockchain::Info { - (**self).backend.blockchain().info() + self.backend.blockchain().info() } fn status(&self, id: BlockId) -> sp_blockchain::Result { diff --git a/client/tracing/src/logging/stderr_writer.rs b/client/tracing/src/logging/stderr_writer.rs index e62c5e82c1ac7..de78a61af41a2 100644 --- a/client/tracing/src/logging/stderr_writer.rs +++ b/client/tracing/src/logging/stderr_writer.rs @@ -89,7 +89,7 @@ fn flush_logs(mut buffer: parking_lot::lock_api::MutexGuard>(); assert_eq!(expected_ready, ready); - let event = block_event_with_retracted(f1_header, f0, &*pool.api()); + let event = block_event_with_retracted(f1_header, f0, pool.api()); block_on(pool.maintain(event)); assert_eq!(pool.status().ready, 3); diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index d93c646872c6f..5ca74e681e5dd 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -1315,7 +1315,7 @@ where fn deposit_event(topics: Vec, event: Event) { >::deposit_event_indexed( - &*topics, + &topics, ::Event::from(event).into(), ) } diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index c40fdf94806aa..cd3e1c500db26 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -573,7 +573,7 @@ mod tests { use pallet_balances::Call as BalancesCall; use pallet_transaction_payment::CurrencyAdapter; - const TEST_KEY: &[u8] = &*b":test:key:"; + const TEST_KEY: &[u8] = b":test:key:"; #[frame_support::pallet] mod custom { @@ -806,7 +806,7 @@ mod tests { type TestUncheckedExtrinsic = TestXt; // Will contain `true` when the custom runtime logic was called. - const CUSTOM_ON_RUNTIME_KEY: &[u8] = &*b":custom:on_runtime"; + const CUSTOM_ON_RUNTIME_KEY: &[u8] = b":custom:on_runtime"; struct CustomOnRuntimeUpgrade; impl OnRuntimeUpgrade for CustomOnRuntimeUpgrade { diff --git a/frame/offences/src/mock.rs b/frame/offences/src/mock.rs index b3dfbdd90b19d..6a69b54b3cca0 100644 --- a/frame/offences/src/mock.rs +++ b/frame/offences/src/mock.rs @@ -67,7 +67,7 @@ impl offence::OnOffenceHandler } pub fn with_on_offence_fractions) -> R>(f: F) -> R { - ON_OFFENCE_PERBILL.with(|fractions| f(&mut *fractions.borrow_mut())) + ON_OFFENCE_PERBILL.with(|fractions| f(&mut fractions.borrow_mut())) } type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; diff --git a/frame/support/procedural/src/pallet/parse/call.rs b/frame/support/procedural/src/pallet/parse/call.rs index 75c85474dcfe7..d8a81d699b8c2 100644 --- a/frame/support/procedural/src/pallet/parse/call.rs +++ b/frame/support/procedural/src/pallet/parse/call.rs @@ -188,7 +188,7 @@ impl CallDef { return Err(syn::Error::new(method.sig.span(), msg)) }, Some(syn::FnArg::Typed(arg)) => { - check_dispatchable_first_arg_type(&*arg.ty)?; + check_dispatchable_first_arg_type(&arg.ty)?; }, } diff --git a/frame/support/test/tests/construct_runtime_ui/no_std_genesis_config.stderr b/frame/support/test/tests/construct_runtime_ui/no_std_genesis_config.stderr index 404c0c3627b7b..1f08ab87c1f79 100644 --- a/frame/support/test/tests/construct_runtime_ui/no_std_genesis_config.stderr +++ b/frame/support/test/tests/construct_runtime_ui/no_std_genesis_config.stderr @@ -29,6 +29,19 @@ help: consider importing this struct | 1 | use frame_system::GenesisConfig; | +help: if you import `GenesisConfig`, refer to it directly + | +40 - construct_runtime! { +41 - pub enum Runtime where +42 - Block = Block, +43 - NodeBlock = Block, +44 - UncheckedExtrinsic = UncheckedExtrinsic +45 - { +46 - System: frame_system::{Pallet, Call, Storage, Config, Event}, +47 - Pallet: test_pallet::{Pallet, Config}, +48 - } +49 - } + | error[E0283]: type annotations needed --> tests/construct_runtime_ui/no_std_genesis_config.rs:40:1 diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_event_part.stderr b/frame/support/test/tests/construct_runtime_ui/undefined_event_part.stderr index 31229f8c93cb6..2af4d3fb15000 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_event_part.stderr +++ b/frame/support/test/tests/construct_runtime_ui/undefined_event_part.stderr @@ -32,6 +32,19 @@ help: consider importing this enum | 1 | use frame_system::Event; | +help: if you import `Event`, refer to it directly + | +49 - construct_runtime! { +50 - pub enum Runtime where +51 - Block = Block, +52 - NodeBlock = Block, +53 - UncheckedExtrinsic = UncheckedExtrinsic +54 - { +55 - System: frame_system::{Pallet, Call, Storage, Config, Event}, +56 - Pallet: pallet::{Pallet, Event}, +57 - } +58 - } + | error[E0412]: cannot find type `Event` in module `pallet` --> tests/construct_runtime_ui/undefined_event_part.rs:49:1 @@ -52,3 +65,16 @@ help: consider importing one of these items | 1 | use frame_system::Event; | +help: if you import `Event`, refer to it directly + | +49 - construct_runtime! { +50 - pub enum Runtime where +51 - Block = Block, +52 - NodeBlock = Block, +53 - UncheckedExtrinsic = UncheckedExtrinsic +54 - { +55 - System: frame_system::{Pallet, Call, Storage, Config, Event}, +56 - Pallet: pallet::{Pallet, Event}, +57 - } +58 - } + | diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.stderr b/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.stderr index fa1cee1ac7e2f..1bc109a45ac57 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.stderr +++ b/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.stderr @@ -32,6 +32,19 @@ help: consider importing this struct | 1 | use frame_system::GenesisConfig; | +help: if you import `GenesisConfig`, refer to it directly + | +49 - construct_runtime! { +50 - pub enum Runtime where +51 - Block = Block, +52 - NodeBlock = Block, +53 - UncheckedExtrinsic = UncheckedExtrinsic +54 - { +55 - System: frame_system::{Pallet, Call, Storage, Config, Event}, +56 - Pallet: pallet::{Pallet, Config}, +57 - } +58 - } + | error[E0283]: type annotations needed --> tests/construct_runtime_ui/undefined_genesis_config_part.rs:49:1 diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.stderr b/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.stderr index 06e845618d44f..c692cd61bae8b 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.stderr +++ b/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.stderr @@ -32,6 +32,19 @@ help: consider importing this type alias | 1 | use frame_system::Origin; | +help: if you import `Origin`, refer to it directly + | +49 - construct_runtime! { +50 - pub enum Runtime where +51 - Block = Block, +52 - NodeBlock = Block, +53 - UncheckedExtrinsic = UncheckedExtrinsic +54 - { +55 - System: frame_system::{Pallet, Call, Storage, Config, Event}, +56 - Pallet: pallet::{Pallet, Origin}, +57 - } +58 - } + | error[E0412]: cannot find type `Origin` in module `pallet` --> tests/construct_runtime_ui/undefined_origin_part.rs:49:1 @@ -52,6 +65,19 @@ help: consider importing one of these items | 1 | use frame_system::Origin; | +help: if you import `Origin`, refer to it directly + | +49 - construct_runtime! { +50 - pub enum Runtime where +51 - Block = Block, +52 - NodeBlock = Block, +53 - UncheckedExtrinsic = UncheckedExtrinsic +54 - { +55 - System: frame_system::{Pallet, Call, Storage, Config, Event}, +56 - Pallet: pallet::{Pallet, Origin}, +57 - } +58 - } + | error[E0282]: type annotations needed --> tests/construct_runtime_ui/undefined_origin_part.rs:49:1 diff --git a/frame/support/test/tests/decl_module_ui/reserved_keyword_two_times_integrity_test.stderr b/frame/support/test/tests/decl_module_ui/reserved_keyword_two_times_integrity_test.stderr index 86c427d8080be..880695d9b77e2 100644 --- a/frame/support/test/tests/decl_module_ui/reserved_keyword_two_times_integrity_test.stderr +++ b/frame/support/test/tests/decl_module_ui/reserved_keyword_two_times_integrity_test.stderr @@ -1,5 +1,5 @@ error: `integrity_test` can only be passed once as input. - --> $DIR/reserved_keyword_two_times_integrity_test.rs:1:1 + --> tests/decl_module_ui/reserved_keyword_two_times_integrity_test.rs:1:1 | 1 | / frame_support::decl_module! { 2 | | pub struct Module for enum Call where origin: T::Origin, system=self { @@ -13,13 +13,7 @@ error: `integrity_test` can only be passed once as input. = note: this error originates in the macro `$crate::decl_module` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0601]: `main` function not found in crate `$CRATE` - --> $DIR/reserved_keyword_two_times_integrity_test.rs:1:1 + --> tests/decl_module_ui/reserved_keyword_two_times_integrity_test.rs:7:2 | -1 | / frame_support::decl_module! { -2 | | pub struct Module for enum Call where origin: T::Origin, system=self { -3 | | fn integrity_test() {} -4 | | -5 | | fn integrity_test() {} -6 | | } -7 | | } - | |_^ consider adding a `main` function to `$DIR/tests/decl_module_ui/reserved_keyword_two_times_integrity_test.rs` +7 | } + | ^ consider adding a `main` function to `$DIR/tests/decl_module_ui/reserved_keyword_two_times_integrity_test.rs` diff --git a/frame/support/test/tests/decl_storage.rs b/frame/support/test/tests/decl_storage.rs index be5d70be17f69..7ce43cd5d44d1 100644 --- a/frame/support/test/tests/decl_storage.rs +++ b/frame/support/test/tests/decl_storage.rs @@ -797,7 +797,7 @@ mod test_append_and_len { TestExternalities::default().execute_with(|| { let key = JustVec::hashed_key(); // Set it to some invalid value. - frame_support::storage::unhashed::put_raw(&key, &*b"1"); + frame_support::storage::unhashed::put_raw(&key, b"1"); assert!(JustVec::get().is_empty()); assert_eq!(frame_support::storage::unhashed::get_raw(&key), Some(b"1".to_vec())); diff --git a/frame/support/test/tests/derive_no_bound_ui/partial_eq.stderr b/frame/support/test/tests/derive_no_bound_ui/partial_eq.stderr index 64f844e547be0..1c230db376a49 100644 --- a/frame/support/test/tests/derive_no_bound_ui/partial_eq.stderr +++ b/frame/support/test/tests/derive_no_bound_ui/partial_eq.stderr @@ -1,7 +1,5 @@ error[E0369]: binary operation `==` cannot be applied to type `::C` - --> $DIR/partial_eq.rs:7:2 + --> tests/derive_no_bound_ui/partial_eq.rs:7:2 | 7 | c: T::C, | ^ - | - = note: the trait `std::cmp::PartialEq` is not implemented for `::C` diff --git a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.stderr b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.stderr index 3a636d9f659c7..1d581ea7ed572 100644 --- a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.stderr +++ b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.stderr @@ -19,8 +19,3 @@ error[E0369]: binary operation `==` cannot be applied to type `&, bar: T::Bar) -> DispatchResultWithPostInfo { | ^^^ - | -help: consider further restricting this bound - | -17 | #[pallet::call + std::cmp::PartialEq] - | +++++++++++++++++++++ diff --git a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr index f182382d18f11..b1487776eac50 100644 --- a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr +++ b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr @@ -19,11 +19,6 @@ error[E0369]: binary operation `==` cannot be applied to type `&, bar: T::Bar) -> DispatchResultWithPostInfo { | ^^^ - | -help: consider further restricting this bound - | -17 | #[pallet::call + std::cmp::PartialEq] - | +++++++++++++++++++++ error[E0277]: the trait bound `::Bar: WrapperTypeEncode` is not satisfied --> tests/pallet_ui/call_argument_invalid_bound_2.rs:20:36 diff --git a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.stderr b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.stderr index c196b28a31ce6..a0418760ba7e2 100644 --- a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.stderr +++ b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.stderr @@ -8,3 +8,7 @@ error[E0277]: `Bar` doesn't implement `std::fmt::Debug` = note: add `#[derive(Debug)]` to `Bar` or manually `impl std::fmt::Debug for Bar` = note: required because of the requirements on the impl of `std::fmt::Debug` for `&Bar` = note: required for the cast to the object type `dyn std::fmt::Debug` +help: consider annotating `Bar` with `#[derive(Debug)]` + | +17 | #[derive(Debug)] + | diff --git a/frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.stderr b/frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.stderr index 0f2ea7e161c4e..7edb55a62d401 100644 --- a/frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.stderr +++ b/frame/support/test/tests/pallet_ui/error_does_not_derive_pallet_error.stderr @@ -4,4 +4,14 @@ error[E0277]: the trait bound `MyError: PalletError` is not satisfied 1 | #[frame_support::pallet] | ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `PalletError` is not implemented for `MyError` | + = help: the following other types implement trait `PalletError`: + () + (TupleElement0, TupleElement1) + (TupleElement0, TupleElement1, TupleElement2) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) + and 36 others = note: this error originates in the derive macro `frame_support::PalletError` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/frame/support/test/tests/pallet_ui/event_field_not_member.stderr b/frame/support/test/tests/pallet_ui/event_field_not_member.stderr index 3db258a819fcb..92623e0329fe3 100644 --- a/frame/support/test/tests/pallet_ui/event_field_not_member.stderr +++ b/frame/support/test/tests/pallet_ui/event_field_not_member.stderr @@ -9,11 +9,6 @@ error[E0369]: binary operation `==` cannot be applied to type `& { - | +++++++++++++++++++++ error[E0277]: `::Bar` doesn't implement `std::fmt::Debug` --> tests/pallet_ui/event_field_not_member.rs:23:7 diff --git a/frame/support/test/tests/pallet_ui/genesis_default_not_satisfied.stderr b/frame/support/test/tests/pallet_ui/genesis_default_not_satisfied.stderr index e01f66fa3d19d..80903928585b4 100644 --- a/frame/support/test/tests/pallet_ui/genesis_default_not_satisfied.stderr +++ b/frame/support/test/tests/pallet_ui/genesis_default_not_satisfied.stderr @@ -9,3 +9,7 @@ note: required by a bound in `GenesisBuild` | | pub trait GenesisBuild: Default + sp_runtime::traits::MaybeSerializeDeserialize { | ^^^^^^^ required by this bound in `GenesisBuild` +help: consider annotating `pallet::GenesisConfig` with `#[derive(Default)]` + | +19 | #[derive(Default)] + | diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr index 87528751a0a7a..e1c1b32d65c39 100644 --- a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr @@ -4,6 +4,11 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied 10 | #[pallet::without_storage_info] | ^^^^^^^^^^^^^^^^^^^^ the trait `WrapperTypeDecode` is not implemented for `Bar` | + = help: the following other types implement trait `WrapperTypeDecode`: + Arc + Box + Rc + frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes = note: required because of the requirements on the impl of `Decode` for `Bar` = note: required because of the requirements on the impl of `FullCodec` for `Bar` = note: required because of the requirements on the impl of `PartialStorageInfoTrait` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` @@ -14,6 +19,16 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied 10 | #[pallet::without_storage_info] | ^^^^^^^^^^^^^^^^^^^^ the trait `EncodeLike` is not implemented for `Bar` | + = help: the following other types implement trait `EncodeLike`: + <&&T as EncodeLike> + <&T as EncodeLike> + <&T as EncodeLike> + <&[(K, V)] as EncodeLike>> + <&[(T,)] as EncodeLike>> + <&[(T,)] as EncodeLike>> + <&[(T,)] as EncodeLike>> + <&[T] as EncodeLike>> + and 273 others = note: required because of the requirements on the impl of `FullEncode` for `Bar` = note: required because of the requirements on the impl of `FullCodec` for `Bar` = note: required because of the requirements on the impl of `PartialStorageInfoTrait` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` @@ -24,6 +39,16 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied 10 | #[pallet::without_storage_info] | ^^^^^^^^^^^^^^^^^^^^ the trait `WrapperTypeEncode` is not implemented for `Bar` | + = help: the following other types implement trait `WrapperTypeEncode`: + &T + &mut T + Arc + Box + Cow<'a, T> + Rc + Vec + frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes + and 2 others = note: required because of the requirements on the impl of `Encode` for `Bar` = note: required because of the requirements on the impl of `FullEncode` for `Bar` = note: required because of the requirements on the impl of `FullCodec` for `Bar` @@ -35,6 +60,16 @@ error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied 21 | #[pallet::storage] | ^^^^^^^ the trait `TypeInfo` is not implemented for `Bar` | + = help: the following other types implement trait `TypeInfo`: + &T + &mut T + () + (A, B) + (A, B, C) + (A, B, C, D) + (A, B, C, D, E) + (A, B, C, D, E, F) + and 158 others = note: required because of the requirements on the impl of `StaticTypeInfo` for `Bar` = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` @@ -44,6 +79,11 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied 21 | #[pallet::storage] | ^^^^^^^ the trait `WrapperTypeDecode` is not implemented for `Bar` | + = help: the following other types implement trait `WrapperTypeDecode`: + Arc + Box + Rc + frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes = note: required because of the requirements on the impl of `Decode` for `Bar` = note: required because of the requirements on the impl of `FullCodec` for `Bar` = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` @@ -54,6 +94,16 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied 21 | #[pallet::storage] | ^^^^^^^ the trait `EncodeLike` is not implemented for `Bar` | + = help: the following other types implement trait `EncodeLike`: + <&&T as EncodeLike> + <&T as EncodeLike> + <&T as EncodeLike> + <&[(K, V)] as EncodeLike>> + <&[(T,)] as EncodeLike>> + <&[(T,)] as EncodeLike>> + <&[(T,)] as EncodeLike>> + <&[T] as EncodeLike>> + and 273 others = note: required because of the requirements on the impl of `FullEncode` for `Bar` = note: required because of the requirements on the impl of `FullCodec` for `Bar` = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` @@ -64,6 +114,16 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied 21 | #[pallet::storage] | ^^^^^^^ the trait `WrapperTypeEncode` is not implemented for `Bar` | + = help: the following other types implement trait `WrapperTypeEncode`: + &T + &mut T + Arc + Box + Cow<'a, T> + Rc + Vec + frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes + and 2 others = note: required because of the requirements on the impl of `Encode` for `Bar` = note: required because of the requirements on the impl of `FullEncode` for `Bar` = note: required because of the requirements on the impl of `FullCodec` for `Bar` diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr index 6c3f6dc662fbc..7d8c448f00193 100644 --- a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr @@ -4,6 +4,11 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied 10 | #[pallet::without_storage_info] | ^^^^^^^^^^^^^^^^^^^^ the trait `WrapperTypeDecode` is not implemented for `Bar` | + = help: the following other types implement trait `WrapperTypeDecode`: + Arc + Box + Rc + frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes = note: required because of the requirements on the impl of `Decode` for `Bar` = note: required because of the requirements on the impl of `FullCodec` for `Bar` = note: required because of the requirements on the impl of `PartialStorageInfoTrait` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` @@ -14,6 +19,16 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied 10 | #[pallet::without_storage_info] | ^^^^^^^^^^^^^^^^^^^^ the trait `EncodeLike` is not implemented for `Bar` | + = help: the following other types implement trait `EncodeLike`: + <&&T as EncodeLike> + <&T as EncodeLike> + <&T as EncodeLike> + <&[(K, V)] as EncodeLike>> + <&[(T,)] as EncodeLike>> + <&[(T,)] as EncodeLike>> + <&[(T,)] as EncodeLike>> + <&[T] as EncodeLike>> + and 273 others = note: required because of the requirements on the impl of `FullEncode` for `Bar` = note: required because of the requirements on the impl of `FullCodec` for `Bar` = note: required because of the requirements on the impl of `PartialStorageInfoTrait` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` @@ -24,6 +39,16 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied 10 | #[pallet::without_storage_info] | ^^^^^^^^^^^^^^^^^^^^ the trait `WrapperTypeEncode` is not implemented for `Bar` | + = help: the following other types implement trait `WrapperTypeEncode`: + &T + &mut T + Arc + Box + Cow<'a, T> + Rc + Vec + frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes + and 2 others = note: required because of the requirements on the impl of `Encode` for `Bar` = note: required because of the requirements on the impl of `FullEncode` for `Bar` = note: required because of the requirements on the impl of `FullCodec` for `Bar` @@ -35,6 +60,16 @@ error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied 21 | #[pallet::storage] | ^^^^^^^ the trait `TypeInfo` is not implemented for `Bar` | + = help: the following other types implement trait `TypeInfo`: + &T + &mut T + () + (A, B) + (A, B, C) + (A, B, C, D) + (A, B, C, D, E) + (A, B, C, D, E, F) + and 158 others = note: required because of the requirements on the impl of `StaticTypeInfo` for `Bar` = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` @@ -44,6 +79,11 @@ error[E0277]: the trait bound `Bar: WrapperTypeDecode` is not satisfied 21 | #[pallet::storage] | ^^^^^^^ the trait `WrapperTypeDecode` is not implemented for `Bar` | + = help: the following other types implement trait `WrapperTypeDecode`: + Arc + Box + Rc + frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes = note: required because of the requirements on the impl of `Decode` for `Bar` = note: required because of the requirements on the impl of `FullCodec` for `Bar` = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` @@ -54,6 +94,16 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied 21 | #[pallet::storage] | ^^^^^^^ the trait `EncodeLike` is not implemented for `Bar` | + = help: the following other types implement trait `EncodeLike`: + <&&T as EncodeLike> + <&T as EncodeLike> + <&T as EncodeLike> + <&[(K, V)] as EncodeLike>> + <&[(T,)] as EncodeLike>> + <&[(T,)] as EncodeLike>> + <&[(T,)] as EncodeLike>> + <&[T] as EncodeLike>> + and 273 others = note: required because of the requirements on the impl of `FullEncode` for `Bar` = note: required because of the requirements on the impl of `FullCodec` for `Bar` = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` @@ -64,6 +114,16 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied 21 | #[pallet::storage] | ^^^^^^^ the trait `WrapperTypeEncode` is not implemented for `Bar` | + = help: the following other types implement trait `WrapperTypeEncode`: + &T + &mut T + Arc + Box + Cow<'a, T> + Rc + Vec + frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes + and 2 others = note: required because of the requirements on the impl of `Encode` for `Bar` = note: required because of the requirements on the impl of `FullEncode` for `Bar` = note: required because of the requirements on the impl of `FullCodec` for `Bar` diff --git a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr index 68856f122c7ac..d87e6900197f5 100644 --- a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr +++ b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr @@ -4,4 +4,14 @@ error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied 9 | #[pallet::pallet] | ^^^^^^ the trait `MaxEncodedLen` is not implemented for `Bar` | + = help: the following other types implement trait `MaxEncodedLen`: + () + (TupleElement0, TupleElement1) + (TupleElement0, TupleElement1, TupleElement2) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) + and 70 others = note: required because of the requirements on the impl of `StorageInfoTrait` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` diff --git a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr index 226cb40f1d48b..bc8e99cd65006 100644 --- a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr +++ b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr @@ -4,5 +4,15 @@ error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied 12 | #[pallet::pallet] | ^^^^^^ the trait `MaxEncodedLen` is not implemented for `Bar` | + = help: the following other types implement trait `MaxEncodedLen`: + () + (TupleElement0, TupleElement1) + (TupleElement0, TupleElement1, TupleElement2) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) + (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) + and 70 others = note: required because of the requirements on the impl of `KeyGeneratorMaxEncodedLen` for `Key` = note: required because of the requirements on the impl of `StorageInfoTrait` for `frame_support::pallet_prelude::StorageNMap<_GeneratedPrefixForStorageFoo, Key, u32>` diff --git a/primitives/api/proc-macro/src/impl_runtime_apis.rs b/primitives/api/proc-macro/src/impl_runtime_apis.rs index 0ac3cfbe1244e..02ef37370ffeb 100644 --- a/primitives/api/proc-macro/src/impl_runtime_apis.rs +++ b/primitives/api/proc-macro/src/impl_runtime_apis.rs @@ -613,7 +613,7 @@ fn generate_api_impl_for_runtime_api(impls: &[ItemImpl]) -> Result let mut visitor = ApiRuntimeImplToApiRuntimeApiImpl { runtime_block, runtime_mod_path: &runtime_mod_path, - runtime_type: &*runtime_type, + runtime_type, trait_generic_arguments: &trait_generic_arguments, impl_trait: &impl_trait.ident, }; diff --git a/primitives/runtime/src/bounded/bounded_vec.rs b/primitives/runtime/src/bounded/bounded_vec.rs index c9c9f851d3249..10d9fc608c273 100644 --- a/primitives/runtime/src/bounded/bounded_vec.rs +++ b/primitives/runtime/src/bounded/bounded_vec.rs @@ -104,10 +104,46 @@ where /// A bounded slice. /// /// Similar to a `BoundedVec`, but not owned and cannot be decoded. -#[derive(Encode, scale_info::TypeInfo)] -#[scale_info(skip_type_params(S))] +#[derive(Encode)] pub struct BoundedSlice<'a, T, S>(pub(super) &'a [T], PhantomData); +// This can be replaced with +// #[derive(scale_info::TypeInfo)] +// #[scale_info(skip_type_params(S))] +// again once this issue is fixed in the rust compiler: https://github.com/rust-lang/rust/issues/96956 +// Tracking issues: https://github.com/paritytech/substrate/issues/11915 +impl<'a, T, S> scale_info::TypeInfo for BoundedSlice<'a, T, S> +where + &'a [T]: scale_info::TypeInfo + 'static, + PhantomData: scale_info::TypeInfo + 'static, + T: scale_info::TypeInfo + 'static, + S: 'static, +{ + type Identity = Self; + + fn type_info() -> ::scale_info::Type { + scale_info::Type::builder() + .path(scale_info::Path::new("BoundedSlice", "sp_runtime::bounded::bounded_vec")) + .type_params(<[_]>::into_vec(Box::new([ + scale_info::TypeParameter::new( + "T", + core::option::Option::Some(::scale_info::meta_type::()), + ), + scale_info::TypeParameter::new("S", ::core::option::Option::None), + ]))) + .docs(&[ + "A bounded slice.", + "", + "Similar to a `BoundedVec`, but not owned and cannot be decoded.", + ]) + .composite( + scale_info::build::Fields::unnamed() + .field(|f| f.ty::<&'static [T]>().type_name("&'static[T]").docs(&[])) + .field(|f| f.ty::>().type_name("PhantomData").docs(&[])), + ) + } +} + // `BoundedSlice`s encode to something which will always decode into a `BoundedVec`, // `WeakBoundedVec`, or a `Vec`. impl<'a, T: Encode + Decode, S: Get> EncodeLike> for BoundedSlice<'a, T, S> {} diff --git a/primitives/runtime/src/curve.rs b/primitives/runtime/src/curve.rs index c6bfa66017870..99733dbbe9a55 100644 --- a/primitives/runtime/src/curve.rs +++ b/primitives/runtime/src/curve.rs @@ -24,7 +24,7 @@ use crate::{ use core::ops::Sub; /// Piecewise Linear function in [0, 1] -> [0, 1]. -#[derive(PartialEq, Eq, sp_core::RuntimeDebug, scale_info::TypeInfo)] +#[derive(PartialEq, Eq, sp_core::RuntimeDebug)] pub struct PiecewiseLinear<'a> { /// Array of points. Must be in order from the lowest abscissas to the highest. pub points: &'a [(Perbill, Perbill)], @@ -32,6 +32,36 @@ pub struct PiecewiseLinear<'a> { pub maximum: Perbill, } +// This can be replaced with +// #[derive(scale_info::TypeInfo)] +// #[scale_info(skip_type_params(S))] +// again once this issue is fixed in the rust compiler: https://github.com/rust-lang/rust/issues/96956 +// Tracking issues: https://github.com/paritytech/substrate/issues/11915 +impl scale_info::TypeInfo for PiecewiseLinear<'static> { + type Identity = Self; + fn type_info() -> ::scale_info::Type { + scale_info::Type::builder() + .path(scale_info::Path::new("PiecewiseLinear", "sp_runtime::curve")) + .type_params(crate::Vec::new()) + .docs(&["Piecewise Linear function in [0, 1] -> [0, 1]."]) + .composite( + scale_info::build::Fields::named() + .field(|f| { + f.ty::<&'static[(Perbill, Perbill)]>() + .name("points") + .type_name("&'static[(Perbill, Perbill)]") + .docs(&["Array of points. Must be in order from the lowest abscissas to the highest."]) + }) + .field(|f| { + f.ty::() + .name("maximum") + .type_name("Perbill") + .docs(&["The maximum value that can be returned."]) + }), + ) + } +} + fn abs_sub + Clone>(a: N, b: N) -> N where { a.clone().max(b.clone()) - a.min(b) } diff --git a/primitives/runtime/src/traits.rs b/primitives/runtime/src/traits.rs index fba3117c41617..a82ae1d62f56a 100644 --- a/primitives/runtime/src/traits.rs +++ b/primitives/runtime/src/traits.rs @@ -55,7 +55,7 @@ pub trait Lazy { impl<'a> Lazy<[u8]> for &'a [u8] { fn get(&mut self) -> &[u8] { - &**self + self } } diff --git a/primitives/trie/src/lib.rs b/primitives/trie/src/lib.rs index 1dca617acf912..7a17d44aa5b69 100644 --- a/primitives/trie/src/lib.rs +++ b/primitives/trie/src/lib.rs @@ -280,7 +280,7 @@ where L: TrieConfiguration, DB: hash_db::HashDBRef, { - TrieDB::::new(&*db, root)?.get(key).map(|x| x.map(|val| val.to_vec())) + TrieDB::::new(db, root)?.get(key).map(|x| x.map(|val| val.to_vec())) } /// Read a value from the trie with given Query. @@ -295,7 +295,7 @@ where Q: Query, DB: hash_db::HashDBRef, { - TrieDB::::new(&*db, root)? + TrieDB::::new(db, root)? .get_with(key, query) .map(|x| x.map(|val| val.to_vec())) } @@ -354,7 +354,7 @@ pub fn record_all_keys( where DB: hash_db::HashDBRef, { - let trie = TrieDB::::new(&*db, root)?; + let trie = TrieDB::::new(db, root)?; let iter = trie.iter()?; for x in iter { @@ -379,7 +379,7 @@ pub fn read_child_trie_value( where DB: hash_db::HashDBRef, { - let db = KeySpacedDB::new(&*db, keyspace); + let db = KeySpacedDB::new(db, keyspace); TrieDB::::new(&db, root)?.get(key).map(|x| x.map(|val| val.to_vec())) } @@ -400,7 +400,7 @@ where // root is fetched from DB, not writable by runtime, so it's always valid. root.as_mut().copy_from_slice(root_slice); - let db = KeySpacedDB::new(&*db, keyspace); + let db = KeySpacedDB::new(db, keyspace); TrieDB::::new(&db, &root)? .get_with(key, query) .map(|x| x.map(|val| val.to_vec())) @@ -501,7 +501,7 @@ where T: Default + PartialEq + for<'b> From<&'b [u8]> + Clone + Send + Sync, { fn as_hash_db(&self) -> &dyn hash_db::HashDB { - &*self + self } fn as_hash_db_mut<'b>(&'b mut self) -> &'b mut (dyn hash_db::HashDB + 'b) { diff --git a/primitives/version/proc-macro/src/decl_runtime_version.rs b/primitives/version/proc-macro/src/decl_runtime_version.rs index ee81940b53993..9a25adfa5fca2 100644 --- a/primitives/version/proc-macro/src/decl_runtime_version.rs +++ b/primitives/version/proc-macro/src/decl_runtime_version.rs @@ -37,7 +37,7 @@ pub fn decl_runtime_version_impl(input: proc_macro::TokenStream) -> proc_macro:: } fn decl_runtime_version_impl_inner(item: ItemConst) -> Result { - let runtime_version = ParseRuntimeVersion::parse_expr(&*item.expr)?.build(item.expr.span())?; + let runtime_version = ParseRuntimeVersion::parse_expr(&item.expr)?.build(item.expr.span())?; let link_section = generate_emit_link_section_decl(&runtime_version.encode(), "runtime_version"); diff --git a/rustfmt.toml b/rustfmt.toml index 441913f619cdc..f6fbe80064fce 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -21,3 +21,4 @@ match_block_trailing_comma = true trailing_comma = "Vertical" trailing_semicolon = false use_field_init_shorthand = true +edition = "2021" diff --git a/utils/frame/remote-externalities/src/lib.rs b/utils/frame/remote-externalities/src/lib.rs index 1d9dc5dcd11da..202560f18cf84 100644 --- a/utils/frame/remote-externalities/src/lib.rs +++ b/utils/frame/remote-externalities/src/lib.rs @@ -130,7 +130,7 @@ pub enum Transport { impl Transport { fn as_client(&self) -> Option<&WsClient> { match self { - Self::RemoteClient(client) => Some(&*client), + Self::RemoteClient(client) => Some(client), _ => None, } } From f5f7839b4ebff7d3f172a4ad38fc7df62682f370 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Tue, 26 Jul 2022 13:44:48 +0100 Subject: [PATCH 427/484] Properly defer slashes (#11823) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * initial draft of fixing slashing * fix test * Update frame/staking/src/tests.rs Co-authored-by: Piotr Mikołajczyk * last touches * add more detail about unbonding * add migration * fmt Co-authored-by: Piotr Mikołajczyk Co-authored-by: parity-processbot <> --- frame/staking/src/lib.rs | 11 +-- frame/staking/src/migrations.rs | 23 ++++++ frame/staking/src/mock.rs | 15 ++++ frame/staking/src/pallet/impls.rs | 47 ++++++------ frame/staking/src/pallet/mod.rs | 4 - frame/staking/src/tests.rs | 118 +++++++++++++++++++++++++++--- 6 files changed, 177 insertions(+), 41 deletions(-) diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 50c033bdc7e28..3fff6312f333f 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -872,11 +872,12 @@ enum Releases { V2_0_0, V3_0_0, V4_0_0, - V5_0_0, // blockable validators. - V6_0_0, // removal of all storage associated with offchain phragmen. - V7_0_0, // keep track of number of nominators / validators in map - V8_0_0, // populate `VoterList`. - V9_0_0, // inject validators into `VoterList` as well. + V5_0_0, // blockable validators. + V6_0_0, // removal of all storage associated with offchain phragmen. + V7_0_0, // keep track of number of nominators / validators in map + V8_0_0, // populate `VoterList`. + V9_0_0, // inject validators into `VoterList` as well. + V10_0_0, // remove `EarliestUnappliedSlash`. } impl Default for Releases { diff --git a/frame/staking/src/migrations.rs b/frame/staking/src/migrations.rs index 14846da8a5d54..101cac0a31348 100644 --- a/frame/staking/src/migrations.rs +++ b/frame/staking/src/migrations.rs @@ -20,6 +20,29 @@ use super::*; use frame_election_provider_support::SortedListProvider; use frame_support::traits::OnRuntimeUpgrade; +pub mod v10 { + use super::*; + use frame_support::storage_alias; + + #[storage_alias] + type EarliestUnappliedSlash = StorageValue, EraIndex>; + + pub struct MigrateToV10(sp_std::marker::PhantomData); + impl OnRuntimeUpgrade for MigrateToV10 { + fn on_runtime_upgrade() -> frame_support::weights::Weight { + if StorageVersion::::get() == Releases::V9_0_0 { + EarliestUnappliedSlash::::kill(); + StorageVersion::::put(Releases::V10_0_0); + + T::DbWeight::get().reads_writes(1, 1) + } else { + log!(warn, "MigrateToV10 should be removed."); + T::DbWeight::get().reads(1) + } + } + } +} + pub mod v9 { use super::*; diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index bd2d8cdc32ce9..70d00c2b648d8 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -549,6 +549,7 @@ impl ExtBuilder { ext } pub fn build_and_execute(self, test: impl FnOnce() -> ()) { + sp_tracing::try_init_simple(); let mut ext = self.build(); ext.execute_with(test); ext.execute_with(post_conditions); @@ -884,6 +885,20 @@ pub(crate) fn staking_events() -> Vec> { .collect() } +parameter_types! { + static StakingEventsIndex: usize = 0; +} + +pub(crate) fn staking_events_since_last_call() -> Vec> { + let all: Vec<_> = System::events() + .into_iter() + .filter_map(|r| if let Event::Staking(inner) = r.event { Some(inner) } else { None }) + .collect(); + let seen = StakingEventsIndex::get(); + StakingEventsIndex::set(all.len()); + all.into_iter().skip(seen).collect() +} + pub(crate) fn balances(who: &AccountId) -> (Balance, Balance) { (Balances::free_balance(who), Balances::reserved_balance(who)) } diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 7656eec80a5ff..68aa97db8a324 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -32,7 +32,7 @@ use frame_support::{ use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; use pallet_session::historical; use sp_runtime::{ - traits::{Bounded, Convert, SaturatedConversion, Saturating, StaticLookup, Zero}, + traits::{Bounded, Convert, One, SaturatedConversion, Saturating, StaticLookup, Zero}, Perbill, }; use sp_staking::{ @@ -599,20 +599,17 @@ impl Pallet { /// Apply previously-unapplied slashes on the beginning of a new era, after a delay. fn apply_unapplied_slashes(active_era: EraIndex) { - let slash_defer_duration = T::SlashDeferDuration::get(); - ::EarliestUnappliedSlash::mutate(|earliest| { - if let Some(ref mut earliest) = earliest { - let keep_from = active_era.saturating_sub(slash_defer_duration); - for era in (*earliest)..keep_from { - let era_slashes = ::UnappliedSlashes::take(&era); - for slash in era_slashes { - slashing::apply_slash::(slash, era); - } - } - - *earliest = (*earliest).max(keep_from) - } - }) + let era_slashes = ::UnappliedSlashes::take(&active_era); + log!( + debug, + "found {} slashes scheduled to be executed in era {:?}", + era_slashes.len(), + active_era, + ); + for slash in era_slashes { + let slash_era = active_era.saturating_sub(T::SlashDeferDuration::get()); + slashing::apply_slash::(slash, slash_era); + } } /// Add reward points to validators using their stash account ID. @@ -1209,11 +1206,6 @@ where } }; - ::EarliestUnappliedSlash::mutate(|earliest| { - if earliest.is_none() { - *earliest = Some(active_era) - } - }); add_db_reads_writes(1, 1); let slash_defer_duration = T::SlashDeferDuration::get(); @@ -1263,9 +1255,18 @@ where } } else { // Defer to end of some `slash_defer_duration` from now. - ::UnappliedSlashes::mutate(active_era, move |for_later| { - for_later.push(unapplied) - }); + log!( + debug, + "deferring slash of {:?}% happened in {:?} (reported in {:?}) to {:?}", + slash_fraction, + slash_era, + active_era, + slash_era + slash_defer_duration + 1, + ); + ::UnappliedSlashes::mutate( + slash_era.saturating_add(slash_defer_duration).saturating_add(One::one()), + move |for_later| for_later.push(unapplied), + ); add_db_reads_writes(1, 1); } } else { diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index e53464195de23..c999020c28167 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -477,10 +477,6 @@ pub mod pallet { ValueQuery, >; - /// The earliest era for which we have a pending, unapplied slash. - #[pallet::storage] - pub(crate) type EarliestUnappliedSlash = StorageValue<_, EraIndex>; - /// The last planned session scheduled by the session pallet. /// /// This is basically in sync with the call to [`pallet_session::SessionManager::new_session`]. diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index b76126f0c5d04..d14d8c4a75f2e 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -2777,12 +2777,103 @@ fn deferred_slashes_are_deferred() { assert_eq!(Balances::free_balance(11), 1000); assert_eq!(Balances::free_balance(101), 2000); + System::reset_events(); + // at the start of era 4, slashes from era 1 are processed, // after being deferred for at least 2 full eras. mock::start_active_era(4); assert_eq!(Balances::free_balance(11), 900); assert_eq!(Balances::free_balance(101), 2000 - (nominated_value / 10)); + + assert_eq!( + staking_events_since_last_call(), + vec![ + Event::StakersElected, + Event::EraPaid(3, 11075, 33225), + Event::Slashed(11, 100), + Event::Slashed(101, 12) + ] + ); + }) +} + +#[test] +fn retroactive_deferred_slashes_two_eras_before() { + ExtBuilder::default().slash_defer_duration(2).build_and_execute(|| { + assert_eq!(BondingDuration::get(), 3); + + mock::start_active_era(1); + let exposure_11_at_era1 = Staking::eras_stakers(active_era(), 11); + + mock::start_active_era(3); + on_offence_in_era( + &[OffenceDetails { offender: (11, exposure_11_at_era1), reporters: vec![] }], + &[Perbill::from_percent(10)], + 1, // should be deferred for two full eras, and applied at the beginning of era 4. + DisableStrategy::Never, + ); + System::reset_events(); + + mock::start_active_era(4); + + assert_eq!( + staking_events_since_last_call(), + vec![ + Event::StakersElected, + Event::EraPaid(3, 7100, 21300), + Event::Slashed(11, 100), + Event::Slashed(101, 12) + ] + ); + }) +} + +#[test] +fn retroactive_deferred_slashes_one_before() { + ExtBuilder::default().slash_defer_duration(2).build_and_execute(|| { + assert_eq!(BondingDuration::get(), 3); + + mock::start_active_era(1); + let exposure_11_at_era1 = Staking::eras_stakers(active_era(), 11); + + // unbond at slash era. + mock::start_active_era(2); + assert_ok!(Staking::chill(Origin::signed(10))); + assert_ok!(Staking::unbond(Origin::signed(10), 100)); + + mock::start_active_era(3); + on_offence_in_era( + &[OffenceDetails { offender: (11, exposure_11_at_era1), reporters: vec![] }], + &[Perbill::from_percent(10)], + 2, // should be deferred for two full eras, and applied at the beginning of era 5. + DisableStrategy::Never, + ); + System::reset_events(); + + mock::start_active_era(4); + assert_eq!( + staking_events_since_last_call(), + vec![Event::StakersElected, Event::EraPaid(3, 11075, 33225)] + ); + + assert_eq!(Staking::ledger(10).unwrap().total, 1000); + // slash happens after the next line. + mock::start_active_era(5); + assert_eq!( + staking_events_since_last_call(), + vec![ + Event::StakersElected, + Event::EraPaid(4, 11075, 33225), + Event::Slashed(11, 100), + Event::Slashed(101, 12) + ] + ); + + // their ledger has already been slashed. + assert_eq!(Staking::ledger(10).unwrap().total, 900); + assert_ok!(Staking::unbond(Origin::signed(10), 1000)); + assert_eq!(Staking::ledger(10).unwrap().total, 900); }) } @@ -2871,6 +2962,7 @@ fn remove_deferred() { assert_eq!(Balances::free_balance(101), 2000); let nominated_value = exposure.others.iter().find(|o| o.who == 101).unwrap().value; + // deferred to start of era 4. on_offence_now( &[OffenceDetails { offender: (11, exposure.clone()), reporters: vec![] }], &[Perbill::from_percent(10)], @@ -2881,6 +2973,7 @@ fn remove_deferred() { mock::start_active_era(2); + // reported later, but deferred to start of era 4 as well. on_offence_in_era( &[OffenceDetails { offender: (11, exposure.clone()), reporters: vec![] }], &[Perbill::from_percent(15)], @@ -2894,7 +2987,8 @@ fn remove_deferred() { Error::::EmptyTargets ); - assert_ok!(Staking::cancel_deferred_slash(Origin::root(), 1, vec![0])); + // cancel one of them. + assert_ok!(Staking::cancel_deferred_slash(Origin::root(), 4, vec![0])); assert_eq!(Balances::free_balance(11), 1000); assert_eq!(Balances::free_balance(101), 2000); @@ -2906,13 +3000,19 @@ fn remove_deferred() { // at the start of era 4, slashes from era 1 are processed, // after being deferred for at least 2 full eras. + System::reset_events(); mock::start_active_era(4); - // the first slash for 10% was cancelled, so no effect. - assert_eq!(Balances::free_balance(11), 1000); - assert_eq!(Balances::free_balance(101), 2000); - - mock::start_active_era(5); + // the first slash for 10% was cancelled, but the 15% one + assert_eq!( + staking_events_since_last_call(), + vec![ + Event::StakersElected, + Event::EraPaid(3, 11075, 33225), + Event::Slashed(11, 50), + Event::Slashed(101, 7) + ] + ); let slash_10 = Perbill::from_percent(10); let slash_15 = Perbill::from_percent(15); @@ -2965,7 +3065,7 @@ fn remove_multi_deferred() { &[Perbill::from_percent(25)], ); - assert_eq!(::UnappliedSlashes::get(&1).len(), 5); + assert_eq!(::UnappliedSlashes::get(&4).len(), 5); // fails if list is not sorted assert_noop!( @@ -2983,9 +3083,9 @@ fn remove_multi_deferred() { Error::::InvalidSlashIndex ); - assert_ok!(Staking::cancel_deferred_slash(Origin::root(), 1, vec![0, 2, 4])); + assert_ok!(Staking::cancel_deferred_slash(Origin::root(), 4, vec![0, 2, 4])); - let slashes = ::UnappliedSlashes::get(&1); + let slashes = ::UnappliedSlashes::get(&4); assert_eq!(slashes.len(), 2); assert_eq!(slashes[0].validator, 21); assert_eq!(slashes[1].validator, 42); From eb6b152ce2d73494bd6a312c6ddd5c02bddd7a49 Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Tue, 26 Jul 2022 18:03:50 +0200 Subject: [PATCH 428/484] [ci] Return production image (#11920) --- .gitlab-ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d1de666009bf1..9608d88e9554d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -47,8 +47,7 @@ variables: &default-vars CARGO_INCREMENTAL: 0 DOCKER_OS: "debian:stretch" ARCH: "x86_64" - # change to production when this image is published - CI_IMAGE: "paritytech/ci-linux:staging@sha256:2c90b67f1452ed2d7236c2cd13f6224053a833d521b3630650b679f00874e0a9" + CI_IMAGE: "paritytech/ci-linux:production" RUSTY_CACHIER_SINGLE_BRANCH: master RUSTY_CACHIER_DONT_OPERATE_ON_MAIN_BRANCH: "true" From 182c29ea65026fe87d459f40b7c24ec1204e3079 Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Tue, 26 Jul 2022 18:49:11 +0100 Subject: [PATCH 429/484] Make New Storage Layer Truly Default (#11918) * with storage layer truly default * fmt Co-authored-by: parity-processbot <> --- frame/nomination-pools/src/tests.rs | 4 ++-- .../src/construct_runtime/expand/call.rs | 13 ------------- .../procedural/src/pallet/expand/call.rs | 4 ++-- frame/support/src/dispatch.rs | 7 +++---- frame/support/src/traits.rs | 5 ++--- frame/support/src/traits/dispatch.rs | 17 ----------------- 6 files changed, 9 insertions(+), 41 deletions(-) diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index b59f18cca72a2..5aa8e97266e0d 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -2735,7 +2735,7 @@ mod unbond { // when: unbonding more than our active: error assert_noop!( - frame_support::storage::in_storage_layer(|| Pools::unbond( + frame_support::storage::with_storage_layer(|| Pools::unbond( Origin::signed(10), 10, 5 @@ -2787,7 +2787,7 @@ mod unbond { // when CurrentEra::set(2); assert_noop!( - frame_support::storage::in_storage_layer(|| Pools::unbond( + frame_support::storage::with_storage_layer(|| Pools::unbond( Origin::signed(20), 20, 4 diff --git a/frame/support/procedural/src/construct_runtime/expand/call.rs b/frame/support/procedural/src/construct_runtime/expand/call.rs index c8c5d5ff0ee43..801b69035121d 100644 --- a/frame/support/procedural/src/construct_runtime/expand/call.rs +++ b/frame/support/procedural/src/construct_runtime/expand/call.rs @@ -163,19 +163,6 @@ pub fn expand_outer_dispatch( } } } - impl #scrate::traits::DispatchableWithStorageLayer for Call { - type Origin = Origin; - fn dispatch_with_storage_layer(self, origin: Origin) -> #scrate::dispatch::DispatchResultWithPostInfo { - #scrate::storage::with_storage_layer(|| { - #scrate::dispatch::Dispatchable::dispatch(self, origin) - }) - } - fn dispatch_bypass_filter_with_storage_layer(self, origin: Origin) -> #scrate::dispatch::DispatchResultWithPostInfo { - #scrate::storage::with_storage_layer(|| { - #scrate::traits::UnfilteredDispatchable::dispatch_bypass_filter(self, origin) - }) - } - } #( impl #scrate::traits::IsSubType<#scrate::dispatch::CallableCallFor<#pallet_names, #runtime>> for Call { diff --git a/frame/support/procedural/src/pallet/expand/call.rs b/frame/support/procedural/src/pallet/expand/call.rs index a5038ec399e2b..fe7589a8275d2 100644 --- a/frame/support/procedural/src/pallet/expand/call.rs +++ b/frame/support/procedural/src/pallet/expand/call.rs @@ -267,9 +267,9 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { #frame_support::sp_tracing::enter_span!( #frame_support::sp_tracing::trace_span!(stringify!(#fn_name)) ); - // We execute all dispatchable in at least one storage layer, allowing them + // We execute all dispatchable in a new storage layer, allowing them // to return an error at any point, and undoing any storage changes. - #frame_support::storage::in_storage_layer(|| { + #frame_support::storage::with_storage_layer(|| { <#pallet_ident<#type_use_gen>>::#fn_name(origin, #( #args_name, )* ) .map(Into::into).map_err(Into::into) }) diff --git a/frame/support/src/dispatch.rs b/frame/support/src/dispatch.rs index c175998956ff2..ae4230efc63f8 100644 --- a/frame/support/src/dispatch.rs +++ b/frame/support/src/dispatch.rs @@ -29,8 +29,7 @@ pub use crate::{ result, }, traits::{ - CallMetadata, DispatchableWithStorageLayer, GetCallMetadata, GetCallName, - GetStorageVersion, UnfilteredDispatchable, + CallMetadata, GetCallMetadata, GetCallName, GetStorageVersion, UnfilteredDispatchable, }, weights::{ ClassifyDispatch, DispatchInfo, GetDispatchInfo, PaysFee, PostDispatchInfo, @@ -1473,9 +1472,9 @@ macro_rules! decl_module { $ignore:ident $mod_type:ident<$trait_instance:ident $(, $instance:ident)?> $fn_name:ident $origin:ident $system:ident [ $( $param_name:ident),* ] ) => { - // We execute all dispatchable in at least one storage layer, allowing them + // We execute all dispatchable in a new storage layer, allowing them // to return an error at any point, and undoing any storage changes. - $crate::storage::in_storage_layer(|| { + $crate::storage::with_storage_layer(|| { <$mod_type<$trait_instance $(, $instance)?>>::$fn_name( $origin $(, $param_name )* ).map(Into::into).map_err(Into::into) }) }; diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index db8c8da4f869b..72d6d6682f14a 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -96,9 +96,8 @@ mod dispatch; #[allow(deprecated)] pub use dispatch::EnsureOneOf; pub use dispatch::{ - AsEnsureOriginWithArg, DispatchableWithStorageLayer, EitherOf, EitherOfDiverse, EnsureOrigin, - EnsureOriginWithArg, MapSuccess, NeverEnsureOrigin, OriginTrait, TryMapSuccess, - UnfilteredDispatchable, + AsEnsureOriginWithArg, EitherOf, EitherOfDiverse, EnsureOrigin, EnsureOriginWithArg, + MapSuccess, NeverEnsureOrigin, OriginTrait, TryMapSuccess, UnfilteredDispatchable, }; mod voting; diff --git a/frame/support/src/traits/dispatch.rs b/frame/support/src/traits/dispatch.rs index 7e97be12e449e..afc819aa454e5 100644 --- a/frame/support/src/traits/dispatch.rs +++ b/frame/support/src/traits/dispatch.rs @@ -236,23 +236,6 @@ pub trait UnfilteredDispatchable { fn dispatch_bypass_filter(self, origin: Self::Origin) -> DispatchResultWithPostInfo; } -/// Type that can be dispatched with an additional storage layer which is used to execute the call. -pub trait DispatchableWithStorageLayer { - /// The origin type of the runtime, (i.e. `frame_system::Config::Origin`). - type Origin; - - /// Same as `dispatch` from the [`frame_support::dispatch::Dispatchable`] trait, but - /// specifically spawns a new storage layer to execute the call inside of. - fn dispatch_with_storage_layer(self, origin: Self::Origin) -> DispatchResultWithPostInfo; - - /// Same as `dispatch_bypass_filter` from the [`UnfilteredDispatchable`] trait, but specifically - /// spawns a new storage layer to execute the call inside of. - fn dispatch_bypass_filter_with_storage_layer( - self, - origin: Self::Origin, - ) -> DispatchResultWithPostInfo; -} - /// Methods available on `frame_system::Config::Origin`. pub trait OriginTrait: Sized { /// Runtime call type, as in `frame_system::Config::Call` From 6e0341385a94eb98763ed2d8aacb0e400bcfd45d Mon Sep 17 00:00:00 2001 From: Sebastian Kunert Date: Thu, 28 Jul 2022 00:35:33 +0200 Subject: [PATCH 430/484] Remove `retain_mut` crate (#11926) * Remove retain_mut crate * Remove reain_mut crate from babe-consensus --- Cargo.lock | 8 ----- client/consensus/babe/Cargo.toml | 1 - client/consensus/babe/src/lib.rs | 22 +++++++------- client/transaction-pool/Cargo.toml | 1 - .../src/graph/validated_pool.rs | 30 +++++++++---------- 5 files changed, 24 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 50603c6235a23..3b0246168632c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7446,12 +7446,6 @@ dependencies = [ "quick-error 1.2.3", ] -[[package]] -name = "retain_mut" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "448296241d034b96c11173591deaa1302f2c17b56092106c1f92c1bc0183a8c9" - [[package]] name = "rfc6979" version = "0.1.0" @@ -7965,7 +7959,6 @@ dependencies = [ "parking_lot 0.12.0", "rand 0.7.3", "rand_chacha 0.2.2", - "retain_mut", "sc-block-builder", "sc-client-api", "sc-consensus", @@ -8877,7 +8870,6 @@ dependencies = [ "parity-scale-codec", "parity-util-mem", "parking_lot 0.12.0", - "retain_mut", "sc-block-builder", "sc-client-api", "sc-transaction-pool-api", diff --git a/client/consensus/babe/Cargo.toml b/client/consensus/babe/Cargo.toml index 765fc367f424f..59d7854619fc0 100644 --- a/client/consensus/babe/Cargo.toml +++ b/client/consensus/babe/Cargo.toml @@ -26,7 +26,6 @@ num-rational = "0.2.2" num-traits = "0.2.8" parking_lot = "0.12.0" rand = "0.7.2" -retain_mut = "0.1.4" schnorrkel = { version = "0.9.1", features = ["preaudit_deprecated"] } serde = { version = "1.0.136", features = ["derive"] } thiserror = "1.0" diff --git a/client/consensus/babe/src/lib.rs b/client/consensus/babe/src/lib.rs index 8478122375319..f61ba23d920f3 100644 --- a/client/consensus/babe/src/lib.rs +++ b/client/consensus/babe/src/lib.rs @@ -87,7 +87,6 @@ use futures::{ use log::{debug, info, log, trace, warn}; use parking_lot::Mutex; use prometheus_endpoint::Registry; -use retain_mut::RetainMut; use schnorrkel::SignatureError; use sc_client_api::{ @@ -835,17 +834,16 @@ where slot: Slot, epoch_descriptor: &ViableEpochDescriptor, Epoch>, ) { - RetainMut::retain_mut(&mut *self.slot_notification_sinks.lock(), |sink| { - match sink.try_send((slot, epoch_descriptor.clone())) { - Ok(()) => true, - Err(e) => - if e.is_full() { - warn!(target: "babe", "Trying to notify a slot but the channel is full"); - true - } else { - false - }, - } + let sinks = &mut self.slot_notification_sinks.lock(); + sinks.retain_mut(|sink| match sink.try_send((slot, epoch_descriptor.clone())) { + Ok(()) => true, + Err(e) => + if e.is_full() { + warn!(target: "babe", "Trying to notify a slot but the channel is full"); + true + } else { + false + }, }); } diff --git a/client/transaction-pool/Cargo.toml b/client/transaction-pool/Cargo.toml index cabde4b6307ae..3fdcde48d9e22 100644 --- a/client/transaction-pool/Cargo.toml +++ b/client/transaction-pool/Cargo.toml @@ -20,7 +20,6 @@ linked-hash-map = "0.5.4" log = "0.4.17" parity-util-mem = { version = "0.11.0", default-features = false, features = ["primitive-types"] } parking_lot = "0.12.0" -retain_mut = "0.1.4" serde = { version = "1.0.136", features = ["derive"] } thiserror = "1.0.30" prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } diff --git a/client/transaction-pool/src/graph/validated_pool.rs b/client/transaction-pool/src/graph/validated_pool.rs index e59c5afbdc512..dcb8195073733 100644 --- a/client/transaction-pool/src/graph/validated_pool.rs +++ b/client/transaction-pool/src/graph/validated_pool.rs @@ -24,7 +24,6 @@ use std::{ use futures::channel::mpsc::{channel, Sender}; use parking_lot::{Mutex, RwLock}; -use retain_mut::RetainMut; use sc_transaction_pool_api::{error, PoolStatus, ReadyTransactions}; use serde::Serialize; use sp_runtime::{ @@ -204,21 +203,20 @@ impl ValidatedPool { let imported = self.pool.write().import(tx)?; if let base::Imported::Ready { ref hash, .. } = imported { - RetainMut::retain_mut(&mut *self.import_notification_sinks.lock(), |sink| { - match sink.try_send(*hash) { - Ok(()) => true, - Err(e) => - if e.is_full() { - log::warn!( - target: "txpool", - "[{:?}] Trying to notify an import but the channel is full", - hash, - ); - true - } else { - false - }, - } + let sinks = &mut self.import_notification_sinks.lock(); + sinks.retain_mut(|sink| match sink.try_send(*hash) { + Ok(()) => true, + Err(e) => + if e.is_full() { + log::warn!( + target: "txpool", + "[{:?}] Trying to notify an import but the channel is full", + hash, + ); + true + } else { + false + }, }); } From dd46e469206425c5d444e67bd8d7082d90617070 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Thu, 28 Jul 2022 11:44:02 +0100 Subject: [PATCH 431/484] Fix slashing migration to v10 (#11924) * Fix slashing migration to v10 * add some logs * Fix default version * fmt * Move doc to struct Co-authored-by: Wilfried Kopp --- frame/staking/src/lib.rs | 2 +- frame/staking/src/migrations.rs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 3fff6312f333f..ab0ab685e6911 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -882,7 +882,7 @@ enum Releases { impl Default for Releases { fn default() -> Self { - Releases::V8_0_0 + Releases::V10_0_0 } } diff --git a/frame/staking/src/migrations.rs b/frame/staking/src/migrations.rs index 101cac0a31348..7e3bf6ccb93e1 100644 --- a/frame/staking/src/migrations.rs +++ b/frame/staking/src/migrations.rs @@ -27,13 +27,29 @@ pub mod v10 { #[storage_alias] type EarliestUnappliedSlash = StorageValue, EraIndex>; + /// Apply any pending slashes that where queued. + /// + /// That means we might slash someone a bit too early, but we will definitely + /// won't forget to slash them. The cap of 512 is somewhat randomly taken to + /// prevent us from iterating over an arbitrary large number of keys `on_runtime_upgrade`. pub struct MigrateToV10(sp_std::marker::PhantomData); impl OnRuntimeUpgrade for MigrateToV10 { fn on_runtime_upgrade() -> frame_support::weights::Weight { if StorageVersion::::get() == Releases::V9_0_0 { + let pending_slashes = as Store>::UnappliedSlashes::iter().take(512); + for (era, slashes) in pending_slashes { + for slash in slashes { + // in the old slashing scheme, the slash era was the key at which we read + // from `UnappliedSlashes`. + log!(warn, "prematurely applying a slash ({:?}) for era {:?}", slash, era); + slashing::apply_slash::(slash, era); + } + } + EarliestUnappliedSlash::::kill(); StorageVersion::::put(Releases::V10_0_0); + log!(info, "MigrateToV10 executed successfully"); T::DbWeight::get().reads_writes(1, 1) } else { log!(warn, "MigrateToV10 should be removed."); From df71a57939f1be26badea114a513e270870bb880 Mon Sep 17 00:00:00 2001 From: Liu-Cheng Xu Date: Thu, 28 Jul 2022 23:31:47 +0800 Subject: [PATCH 432/484] Enrich the sync log on handling the block request (#11747) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add more info to the log to help debug the issue like https://github.com/paritytech/substrate/issues/11732. Co-authored-by: Bastian Köcher --- .../network/sync/src/block_request_handler.rs | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/client/network/sync/src/block_request_handler.rs b/client/network/sync/src/block_request_handler.rs index 2a847a8bf36ec..30cbb32289d66 100644 --- a/client/network/sync/src/block_request_handler.rs +++ b/client/network/sync/src/block_request_handler.rs @@ -226,16 +226,13 @@ where debug!( target: LOG_TARGET, - "Handling block request from {}: Starting at `{:?}` with maximum blocks \ - of `{}`, direction `{:?}` and attributes `{:?}`.", - peer, - from_block_id, - max_blocks, - direction, - attributes, + "Handling block request from {peer}: Starting at `{from_block_id:?}` with \ + maximum blocks of `{max_blocks}`, reputation_change: `{reputation_change:?}`, \ + small_request `{small_request:?}`, direction `{direction:?}` and \ + attributes `{attributes:?}`.", ); - let result = if reputation_change.is_none() || small_request { + let maybe_block_response = if reputation_change.is_none() || small_request { let block_response = self.get_block_response( attributes, from_block_id, @@ -259,9 +256,22 @@ where } } + Some(block_response) + } else { + None + }; + + debug!( + target: LOG_TARGET, + "Sending result of block request from {peer} starting at `{from_block_id:?}`: \ + blocks: {:?}, data: {:?}", + maybe_block_response.as_ref().map(|res| res.blocks.len()), + maybe_block_response.as_ref().map(|res| res.encoded_len()), + ); + + let result = if let Some(block_response) = maybe_block_response { let mut data = Vec::with_capacity(block_response.encoded_len()); block_response.encode(&mut data)?; - Ok(data) } else { Err(()) From 82d8b1146b25434897b39daf67bbc6a521f98ba4 Mon Sep 17 00:00:00 2001 From: Koute Date: Fri, 29 Jul 2022 16:46:15 +0900 Subject: [PATCH 433/484] Prevent double allocation of the payload when calling `sp_io::storage::get` (#11523) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Expose allocation stats in `FreeingBumpHeapAllocator` * Return allocation stats when calling into the runtime * Bump `parity-scale-codec` to 3.1.3 (fork) * Prevent double allocation of the payload when calling `sp_io::storage::get` * Fix tests * Remove unnecessary `mut` * Enable the `bytes` feature for `parity-scale-codec` in `sp-runtime-interface` * Update client/allocator/src/freeing_bump.rs Co-authored-by: Bastian Köcher * Bump `parity-scale-codec` to 3.1.3 * Fix some of the UI tests Co-authored-by: Bastian Köcher --- Cargo.lock | 4 + client/allocator/src/freeing_bump.rs | 97 +++++++++++-------- client/allocator/src/lib.rs | 2 +- client/executor/common/src/wasm_runtime.rs | 17 +++- client/executor/src/native_executor.rs | 49 +++++++++- client/executor/wasmi/src/lib.rs | 26 ++++- client/executor/wasmtime/src/host.rs | 6 +- client/executor/wasmtime/src/runtime.rs | 34 +++++-- frame/support/src/storage/unhashed.rs | 2 +- ...age_ensure_span_are_ok_on_wrong_gen.stderr | 12 +-- ...re_span_are_ok_on_wrong_gen_unnamed.stderr | 12 +-- primitives/io/Cargo.toml | 3 +- primitives/io/src/lib.rs | 10 +- primitives/runtime-interface/Cargo.toml | 3 +- primitives/runtime-interface/src/pass_by.rs | 6 +- .../runtime-interface/test-wasm/Cargo.toml | 1 + .../runtime-interface/test-wasm/src/lib.rs | 20 ++++ primitives/runtime-interface/test/src/lib.rs | 72 +++++++++++--- 18 files changed, 284 insertions(+), 92 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3b0246168632c..926d6d4cdf19a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6516,6 +6516,7 @@ dependencies = [ "arrayvec 0.7.2", "bitvec", "byte-slice-cast", + "bytes", "impl-trait-for-tuples", "parity-scale-codec-derive", "serde", @@ -9731,6 +9732,7 @@ dependencies = [ name = "sp-io" version = "6.0.0" dependencies = [ + "bytes", "futures", "hash-db", "libsecp256k1", @@ -9889,6 +9891,7 @@ dependencies = [ name = "sp-runtime-interface" version = "6.0.0" dependencies = [ + "bytes", "impl-trait-for-tuples", "parity-scale-codec", "primitive-types", @@ -9938,6 +9941,7 @@ dependencies = [ name = "sp-runtime-interface-test-wasm" version = "2.0.0" dependencies = [ + "bytes", "sp-core", "sp-io", "sp-runtime-interface", diff --git a/client/allocator/src/freeing_bump.rs b/client/allocator/src/freeing_bump.rs index 79d6fca6f91b6..13dc6dca0dcd6 100644 --- a/client/allocator/src/freeing_bump.rs +++ b/client/allocator/src/freeing_bump.rs @@ -314,27 +314,50 @@ impl IndexMut for FreeLists { } } +/// Memory allocation stats gathered during the lifetime of the allocator. +#[derive(Clone, Debug, Default)] +#[non_exhaustive] +pub struct AllocationStats { + /// The current number of bytes allocated. + /// + /// This represents how many bytes are allocated *right now*. + pub bytes_allocated: u32, + + /// The peak number of bytes ever allocated. + /// + /// This is the maximum the `bytes_allocated` ever reached. + pub bytes_allocated_peak: u32, + + /// The sum of every allocation ever made. + /// + /// This increases every time a new allocation is made. + pub bytes_allocated_sum: u32, + + /// The amount of address space (in bytes) used by the allocator. + /// + /// This is calculated as the difference between the allocator's bumper + /// and the heap base. + /// + /// Currently the bumper's only ever incremented, so this is simultaneously + /// the current value as well as the peak value. + pub address_space_used: u32, +} + /// An implementation of freeing bump allocator. /// /// Refer to the module-level documentation for further details. pub struct FreeingBumpHeapAllocator { + original_heap_base: u32, bumper: u32, free_lists: FreeLists, - total_size: u32, poisoned: bool, - max_total_size: u32, - max_bumper: u32, last_observed_memory_size: u32, + stats: AllocationStats, } impl Drop for FreeingBumpHeapAllocator { fn drop(&mut self) { - log::debug!( - target: LOG_TARGET, - "allocator being destroyed, max_total_size {}, max_bumper {}", - self.max_total_size, - self.max_bumper, - ) + log::debug!(target: LOG_TARGET, "allocator dropped: {:?}", self.stats) } } @@ -348,13 +371,12 @@ impl FreeingBumpHeapAllocator { let aligned_heap_base = (heap_base + ALIGNMENT - 1) / ALIGNMENT * ALIGNMENT; FreeingBumpHeapAllocator { + original_heap_base: aligned_heap_base, bumper: aligned_heap_base, free_lists: FreeLists::new(), - total_size: 0, poisoned: false, - max_total_size: 0, - max_bumper: aligned_heap_base, last_observed_memory_size: 0, + stats: AllocationStats::default(), } } @@ -412,22 +434,13 @@ impl FreeingBumpHeapAllocator { // Write the order in the occupied header. Header::Occupied(order).write_into(mem, header_ptr)?; - self.total_size += order.size() + HEADER_SIZE; - - log::trace!( - target: LOG_TARGET, - "after allocation, total_size = {}, bumper = {}.", - self.total_size, - self.bumper, - ); + self.stats.bytes_allocated += order.size() + HEADER_SIZE; + self.stats.bytes_allocated_sum += order.size() + HEADER_SIZE; + self.stats.bytes_allocated_peak = + std::cmp::max(self.stats.bytes_allocated_peak, self.stats.bytes_allocated); + self.stats.address_space_used = self.bumper - self.original_heap_base; - // update trackers if needed. - if self.total_size > self.max_total_size { - self.max_total_size = self.total_size; - } - if self.bumper > self.max_bumper { - self.max_bumper = self.bumper; - } + log::trace!(target: LOG_TARGET, "after allocation: {:?}", self.stats); bomb.disarm(); Ok(Pointer::new(header_ptr + HEADER_SIZE)) @@ -469,21 +482,23 @@ impl FreeingBumpHeapAllocator { let prev_head = self.free_lists.replace(order, Link::Ptr(header_ptr)); Header::Free(prev_head).write_into(mem, header_ptr)?; - // Do the total_size book keeping. - self.total_size = self - .total_size + self.stats.bytes_allocated = self + .stats + .bytes_allocated .checked_sub(order.size() + HEADER_SIZE) - .ok_or_else(|| error("Unable to subtract from total heap size without overflow"))?; - log::trace!( - "after deallocation, total_size = {}, bumper = {}.", - self.total_size, - self.bumper, - ); + .ok_or_else(|| error("underflow of the currently allocated bytes count"))?; + + log::trace!("after deallocation: {:?}", self.stats); bomb.disarm(); Ok(()) } + /// Returns the allocation stats for this allocator. + pub fn stats(&self) -> AllocationStats { + self.stats.clone() + } + /// Increases the `bumper` by `size`. /// /// Returns the `bumper` from before the increase. Returns an `Error::AllocatorOutOfSpace` if @@ -791,13 +806,13 @@ mod tests { let ptr1 = heap.allocate(&mut mem[..], 32).unwrap(); assert_eq!(ptr1, to_pointer(HEADER_SIZE)); heap.deallocate(&mut mem[..], ptr1).expect("failed freeing ptr1"); - assert_eq!(heap.total_size, 0); + assert_eq!(heap.stats.bytes_allocated, 0); assert_eq!(heap.bumper, 40); let ptr2 = heap.allocate(&mut mem[..], 16).unwrap(); assert_eq!(ptr2, to_pointer(48)); heap.deallocate(&mut mem[..], ptr2).expect("failed freeing ptr2"); - assert_eq!(heap.total_size, 0); + assert_eq!(heap.stats.bytes_allocated, 0); assert_eq!(heap.bumper, 64); // when @@ -825,7 +840,7 @@ mod tests { heap.allocate(&mut mem[..], 9).unwrap(); // then - assert_eq!(heap.total_size, HEADER_SIZE + 16); + assert_eq!(heap.stats.bytes_allocated, HEADER_SIZE + 16); } #[test] @@ -840,7 +855,7 @@ mod tests { heap.deallocate(&mut mem[..], ptr).unwrap(); // then - assert_eq!(heap.total_size, 0); + assert_eq!(heap.stats.bytes_allocated, 0); } #[test] @@ -856,7 +871,7 @@ mod tests { } // then - assert_eq!(heap.total_size, 0); + assert_eq!(heap.stats.bytes_allocated, 0); } #[test] diff --git a/client/allocator/src/lib.rs b/client/allocator/src/lib.rs index 2c3e3b2ae841d..2fe63a1ec392c 100644 --- a/client/allocator/src/lib.rs +++ b/client/allocator/src/lib.rs @@ -26,4 +26,4 @@ mod error; mod freeing_bump; pub use error::Error; -pub use freeing_bump::FreeingBumpHeapAllocator; +pub use freeing_bump::{AllocationStats, FreeingBumpHeapAllocator}; diff --git a/client/executor/common/src/wasm_runtime.rs b/client/executor/common/src/wasm_runtime.rs index d43ad758b144c..d0cc8926144be 100644 --- a/client/executor/common/src/wasm_runtime.rs +++ b/client/executor/common/src/wasm_runtime.rs @@ -21,6 +21,8 @@ use crate::error::Error; use sp_wasm_interface::Value; +pub use sc_allocator::AllocationStats; + /// A method to be used to find the entrypoint when calling into the runtime /// /// Contains variants on how to resolve wasm function that will be invoked. @@ -78,7 +80,20 @@ pub trait WasmInstance: Send { /// Before execution, instance is reset. /// /// Returns the encoded result on success. - fn call(&mut self, method: InvokeMethod, data: &[u8]) -> Result, Error>; + fn call(&mut self, method: InvokeMethod, data: &[u8]) -> Result, Error> { + self.call_with_allocation_stats(method, data).0 + } + + /// Call a method on this WASM instance. + /// + /// Before execution, instance is reset. + /// + /// Returns the encoded result on success. + fn call_with_allocation_stats( + &mut self, + method: InvokeMethod, + data: &[u8], + ) -> (Result, Error>, Option); /// Call an exported method on this WASM instance. /// diff --git a/client/executor/src/native_executor.rs b/client/executor/src/native_executor.rs index ea060a89c13df..d805949d26fd9 100644 --- a/client/executor/src/native_executor.rs +++ b/client/executor/src/native_executor.rs @@ -37,7 +37,7 @@ use std::{ use codec::{Decode, Encode}; use sc_executor_common::{ runtime_blob::RuntimeBlob, - wasm_runtime::{InvokeMethod, WasmInstance, WasmModule}, + wasm_runtime::{AllocationStats, InvokeMethod, WasmInstance, WasmModule}, }; use sp_core::{ traits::{CodeExecutor, Externalities, RuntimeCode, RuntimeSpawn, RuntimeSpawnExt}, @@ -219,6 +219,47 @@ where allow_missing_host_functions: bool, export_name: &str, call_data: &[u8], + ) -> std::result::Result, Error> { + self.uncached_call_impl( + runtime_blob, + ext, + allow_missing_host_functions, + export_name, + call_data, + &mut None, + ) + } + + /// Same as `uncached_call`, except it also returns allocation statistics. + #[doc(hidden)] // We use this function in tests. + pub fn uncached_call_with_allocation_stats( + &self, + runtime_blob: RuntimeBlob, + ext: &mut dyn Externalities, + allow_missing_host_functions: bool, + export_name: &str, + call_data: &[u8], + ) -> (std::result::Result, Error>, Option) { + let mut allocation_stats = None; + let result = self.uncached_call_impl( + runtime_blob, + ext, + allow_missing_host_functions, + export_name, + call_data, + &mut allocation_stats, + ); + (result, allocation_stats) + } + + fn uncached_call_impl( + &self, + runtime_blob: RuntimeBlob, + ext: &mut dyn Externalities, + allow_missing_host_functions: bool, + export_name: &str, + call_data: &[u8], + allocation_stats_out: &mut Option, ) -> std::result::Result, Error> { let module = crate::wasm_runtime::create_wasm_runtime_with_code::( self.method, @@ -235,10 +276,14 @@ where let mut instance = AssertUnwindSafe(instance); let mut ext = AssertUnwindSafe(ext); let module = AssertUnwindSafe(module); + let mut allocation_stats_out = AssertUnwindSafe(allocation_stats_out); with_externalities_safe(&mut **ext, move || { preregister_builtin_ext(module.clone()); - instance.call_export(export_name, call_data) + let (result, allocation_stats) = + instance.call_with_allocation_stats(export_name.into(), call_data); + **allocation_stats_out = allocation_stats; + result }) .and_then(|r| r) } diff --git a/client/executor/wasmi/src/lib.rs b/client/executor/wasmi/src/lib.rs index 199fe431580ed..e17707e158321 100644 --- a/client/executor/wasmi/src/lib.rs +++ b/client/executor/wasmi/src/lib.rs @@ -29,6 +29,7 @@ use wasmi::{ }; use codec::{Decode, Encode}; +use sc_allocator::AllocationStats; use sc_executor_common::{ error::{Error, MessageWithBacktrace, WasmError}, runtime_blob::{DataSegmentsSnapshot, RuntimeBlob}, @@ -489,6 +490,7 @@ fn call_in_wasm_module( host_functions: Arc>, allow_missing_func_imports: bool, missing_functions: Arc>, + allocation_stats: &mut Option, ) -> Result, Error> { // Initialize FunctionExecutor. let table: Option = module_instance @@ -561,6 +563,8 @@ fn call_in_wasm_module( }, }; + *allocation_stats = Some(function_executor.heap.borrow().stats()); + match result { Ok(Some(I64(r))) => { let (ptr, length) = unpack_ptr_and_len(r as u64); @@ -761,8 +765,13 @@ pub struct WasmiInstance { // `self.instance` unsafe impl Send for WasmiInstance {} -impl WasmInstance for WasmiInstance { - fn call(&mut self, method: InvokeMethod, data: &[u8]) -> Result, Error> { +impl WasmiInstance { + fn call_impl( + &mut self, + method: InvokeMethod, + data: &[u8], + allocation_stats: &mut Option, + ) -> Result, Error> { // We reuse a single wasm instance for multiple calls and a previous call (if any) // altered the state. Therefore, we need to restore the instance to original state. @@ -790,8 +799,21 @@ impl WasmInstance for WasmiInstance { self.host_functions.clone(), self.allow_missing_func_imports, self.missing_functions.clone(), + allocation_stats, ) } +} + +impl WasmInstance for WasmiInstance { + fn call_with_allocation_stats( + &mut self, + method: InvokeMethod, + data: &[u8], + ) -> (Result, Error>, Option) { + let mut allocation_stats = None; + let result = self.call_impl(method, data, &mut allocation_stats); + (result, allocation_stats) + } fn get_global_const(&mut self, name: &str) -> Result, Error> { match self.instance.export_by_name(name) { diff --git a/client/executor/wasmtime/src/host.rs b/client/executor/wasmtime/src/host.rs index b494795ae9dc1..a54254810b68b 100644 --- a/client/executor/wasmtime/src/host.rs +++ b/client/executor/wasmtime/src/host.rs @@ -23,7 +23,7 @@ use log::trace; use wasmtime::{Caller, Func, Val}; use codec::{Decode, Encode}; -use sc_allocator::FreeingBumpHeapAllocator; +use sc_allocator::{AllocationStats, FreeingBumpHeapAllocator}; use sc_executor_common::{ error::Result, sandbox::{self, SupervisorFuncIndex}, @@ -66,6 +66,10 @@ impl HostState { pub fn take_panic_message(&mut self) -> Option { self.panic_message.take() } + + pub(crate) fn allocation_stats(&self) -> AllocationStats { + self.allocator.stats() + } } /// A `HostContext` implements `FunctionContext` for making host calls from a Wasmtime diff --git a/client/executor/wasmtime/src/runtime.rs b/client/executor/wasmtime/src/runtime.rs index dcf07c454e46d..2feb9d0eea502 100644 --- a/client/executor/wasmtime/src/runtime.rs +++ b/client/executor/wasmtime/src/runtime.rs @@ -24,7 +24,7 @@ use crate::{ util::{self, replace_strategy_if_broken}, }; -use sc_allocator::FreeingBumpHeapAllocator; +use sc_allocator::{AllocationStats, FreeingBumpHeapAllocator}; use sc_executor_common::{ error::{Result, WasmError}, runtime_blob::{ @@ -184,8 +184,13 @@ pub struct WasmtimeInstance { strategy: Strategy, } -impl WasmInstance for WasmtimeInstance { - fn call(&mut self, method: InvokeMethod, data: &[u8]) -> Result> { +impl WasmtimeInstance { + fn call_impl( + &mut self, + method: InvokeMethod, + data: &[u8], + allocation_stats: &mut Option, + ) -> Result> { match &mut self.strategy { Strategy::LegacyInstanceReuse { ref mut instance_wrapper, @@ -205,7 +210,8 @@ impl WasmInstance for WasmtimeInstance { globals_snapshot.apply(&mut InstanceGlobals { instance: instance_wrapper }); let allocator = FreeingBumpHeapAllocator::new(*heap_base); - let result = perform_call(data, instance_wrapper, entrypoint, allocator); + let result = + perform_call(data, instance_wrapper, entrypoint, allocator, allocation_stats); // Signal to the OS that we are done with the linear memory and that it can be // reclaimed. @@ -219,10 +225,22 @@ impl WasmInstance for WasmtimeInstance { let entrypoint = instance_wrapper.resolve_entrypoint(method)?; let allocator = FreeingBumpHeapAllocator::new(heap_base); - perform_call(data, &mut instance_wrapper, entrypoint, allocator) + perform_call(data, &mut instance_wrapper, entrypoint, allocator, allocation_stats) }, } } +} + +impl WasmInstance for WasmtimeInstance { + fn call_with_allocation_stats( + &mut self, + method: InvokeMethod, + data: &[u8], + ) -> (Result>, Option) { + let mut allocation_stats = None; + let result = self.call_impl(method, data, &mut allocation_stats); + (result, allocation_stats) + } fn get_global_const(&mut self, name: &str) -> Result> { match &mut self.strategy { @@ -741,6 +759,7 @@ fn perform_call( instance_wrapper: &mut InstanceWrapper, entrypoint: EntryPoint, mut allocator: FreeingBumpHeapAllocator, + allocation_stats: &mut Option, ) -> Result> { let (data_ptr, data_len) = inject_input_data(instance_wrapper, &mut allocator, data)?; @@ -754,7 +773,10 @@ fn perform_call( .map(unpack_ptr_and_len); // Reset the host state - instance_wrapper.store_mut().data_mut().host_state = None; + let host_state = instance_wrapper.store_mut().data_mut().host_state.take().expect( + "the host state is always set before calling into WASM so it can't be None here; qed", + ); + *allocation_stats = Some(host_state.allocation_stats()); let (output_ptr, output_len) = ret?; let output = extract_output_data(instance_wrapper, output_ptr, output_len)?; diff --git a/frame/support/src/storage/unhashed.rs b/frame/support/src/storage/unhashed.rs index fc6d8ae79c57d..089230bc9bfdf 100644 --- a/frame/support/src/storage/unhashed.rs +++ b/frame/support/src/storage/unhashed.rs @@ -155,7 +155,7 @@ pub fn clear_prefix( /// Get a Vec of bytes from storage. pub fn get_raw(key: &[u8]) -> Option> { - sp_io::storage::get(key) + sp_io::storage::get(key).map(|value| value.to_vec()) } /// Put a raw byte slice into storage. diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr index e1c1b32d65c39..45cdfad67b8ae 100644 --- a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr @@ -28,7 +28,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 273 others + and 278 others = note: required because of the requirements on the impl of `FullEncode` for `Bar` = note: required because of the requirements on the impl of `FullCodec` for `Bar` = note: required because of the requirements on the impl of `PartialStorageInfoTrait` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` @@ -47,8 +47,8 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied Cow<'a, T> Rc Vec - frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes - and 2 others + bytes::bytes::Bytes + and 3 others = note: required because of the requirements on the impl of `Encode` for `Bar` = note: required because of the requirements on the impl of `FullEncode` for `Bar` = note: required because of the requirements on the impl of `FullCodec` for `Bar` @@ -103,7 +103,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 273 others + and 278 others = note: required because of the requirements on the impl of `FullEncode` for `Bar` = note: required because of the requirements on the impl of `FullCodec` for `Bar` = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` @@ -122,8 +122,8 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied Cow<'a, T> Rc Vec - frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes - and 2 others + bytes::bytes::Bytes + and 3 others = note: required because of the requirements on the impl of `Encode` for `Bar` = note: required because of the requirements on the impl of `FullEncode` for `Bar` = note: required because of the requirements on the impl of `FullCodec` for `Bar` diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr index 7d8c448f00193..d7441e8b18562 100644 --- a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr @@ -28,7 +28,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 273 others + and 278 others = note: required because of the requirements on the impl of `FullEncode` for `Bar` = note: required because of the requirements on the impl of `FullCodec` for `Bar` = note: required because of the requirements on the impl of `PartialStorageInfoTrait` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` @@ -47,8 +47,8 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied Cow<'a, T> Rc Vec - frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes - and 2 others + bytes::bytes::Bytes + and 3 others = note: required because of the requirements on the impl of `Encode` for `Bar` = note: required because of the requirements on the impl of `FullEncode` for `Bar` = note: required because of the requirements on the impl of `FullCodec` for `Bar` @@ -103,7 +103,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 273 others + and 278 others = note: required because of the requirements on the impl of `FullEncode` for `Bar` = note: required because of the requirements on the impl of `FullCodec` for `Bar` = note: required because of the requirements on the impl of `StorageEntryMetadataBuilder` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` @@ -122,8 +122,8 @@ error[E0277]: the trait bound `Bar: WrapperTypeEncode` is not satisfied Cow<'a, T> Rc Vec - frame_support::sp_runtime::sp_application_crypto::sp_core::Bytes - and 2 others + bytes::bytes::Bytes + and 3 others = note: required because of the requirements on the impl of `Encode` for `Bar` = note: required because of the requirements on the impl of `FullEncode` for `Bar` = note: required because of the requirements on the impl of `FullCodec` for `Bar` diff --git a/primitives/io/Cargo.toml b/primitives/io/Cargo.toml index 77ba4be677f31..fda6b7fdd11dd 100644 --- a/primitives/io/Cargo.toml +++ b/primitives/io/Cargo.toml @@ -15,7 +15,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +bytes = { version = "1.1.0", default-features = false } +codec = { package = "parity-scale-codec", version = "3.1.3", default-features = false, features = ["bytes"] } hash-db = { version = "0.15.2", default-features = false } sp-core = { version = "6.0.0", default-features = false, path = "../core" } sp-keystore = { version = "0.12.0", default-features = false, optional = true, path = "../keystore" } diff --git a/primitives/io/src/lib.rs b/primitives/io/src/lib.rs index edffc37351147..9bf9345e594c3 100644 --- a/primitives/io/src/lib.rs +++ b/primitives/io/src/lib.rs @@ -125,8 +125,8 @@ impl From for KillStorageResult { #[runtime_interface] pub trait Storage { /// Returns the data for `key` in the storage or `None` if the key can not be found. - fn get(&self, key: &[u8]) -> Option> { - self.storage(key).map(|s| s.to_vec()) + fn get(&self, key: &[u8]) -> Option { + self.storage(key).map(|s| bytes::Bytes::from(s.to_vec())) } /// Get `key` from storage, placing the value into `value_out` and return the number of @@ -1787,7 +1787,7 @@ mod tests { t.execute_with(|| { assert_eq!(storage::get(b"hello"), None); storage::set(b"hello", b"world"); - assert_eq!(storage::get(b"hello"), Some(b"world".to_vec())); + assert_eq!(storage::get(b"hello"), Some(b"world".to_vec().into())); assert_eq!(storage::get(b"foo"), None); storage::set(b"foo", &[1, 2, 3][..]); }); @@ -1799,7 +1799,7 @@ mod tests { t.execute_with(|| { assert_eq!(storage::get(b"hello"), None); - assert_eq!(storage::get(b"foo"), Some(b"bar".to_vec())); + assert_eq!(storage::get(b"foo"), Some(b"bar".to_vec().into())); }); let value = vec![7u8; 35]; @@ -1809,7 +1809,7 @@ mod tests { t.execute_with(|| { assert_eq!(storage::get(b"hello"), None); - assert_eq!(storage::get(b"foo00"), Some(value.clone())); + assert_eq!(storage::get(b"foo00"), Some(value.clone().into())); }); } diff --git a/primitives/runtime-interface/Cargo.toml b/primitives/runtime-interface/Cargo.toml index 8cd31bab559ea..a657c98381c9a 100644 --- a/primitives/runtime-interface/Cargo.toml +++ b/primitives/runtime-interface/Cargo.toml @@ -14,12 +14,13 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +bytes = { version = "1.1.0", default-features = false } sp-wasm-interface = { version = "6.0.0", path = "../wasm-interface", default-features = false } sp-std = { version = "4.0.0", default-features = false, path = "../std" } sp-tracing = { version = "5.0.0", default-features = false, path = "../tracing" } sp-runtime-interface-proc-macro = { version = "5.0.0", path = "proc-macro" } sp-externalities = { version = "0.12.0", default-features = false, path = "../externalities" } -codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["bytes"] } static_assertions = "1.0.0" primitive-types = { version = "0.11.1", default-features = false } sp-storage = { version = "6.0.0", default-features = false, path = "../storage" } diff --git a/primitives/runtime-interface/src/pass_by.rs b/primitives/runtime-interface/src/pass_by.rs index 5d895ff5b3f82..ac6f0def9cad0 100644 --- a/primitives/runtime-interface/src/pass_by.rs +++ b/primitives/runtime-interface/src/pass_by.rs @@ -248,12 +248,12 @@ impl PassByImpl for Codec { let len = len as usize; let encoded = if len == 0 { - Vec::new() + bytes::Bytes::new() } else { - unsafe { Vec::from_raw_parts(ptr as *mut u8, len, len) } + bytes::Bytes::from(unsafe { Vec::from_raw_parts(ptr as *mut u8, len, len) }) }; - T::decode(&mut &encoded[..]).expect("Host to wasm values are encoded correctly; qed") + codec::decode_from_bytes(encoded).expect("Host to wasm values are encoded correctly; qed") } } diff --git a/primitives/runtime-interface/test-wasm/Cargo.toml b/primitives/runtime-interface/test-wasm/Cargo.toml index f0e78e0e536b9..e9b2937227db6 100644 --- a/primitives/runtime-interface/test-wasm/Cargo.toml +++ b/primitives/runtime-interface/test-wasm/Cargo.toml @@ -13,6 +13,7 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] +bytes = { version = "1.1.0", default-features = false } sp-core = { version = "6.0.0", default-features = false, path = "../../core" } sp-io = { version = "6.0.0", default-features = false, path = "../../io" } sp-runtime-interface = { version = "6.0.0", default-features = false, path = "../" } diff --git a/primitives/runtime-interface/test-wasm/src/lib.rs b/primitives/runtime-interface/test-wasm/src/lib.rs index f518a2e17498c..1305aef66cacc 100644 --- a/primitives/runtime-interface/test-wasm/src/lib.rs +++ b/primitives/runtime-interface/test-wasm/src/lib.rs @@ -60,6 +60,18 @@ pub trait TestApi { vec![0; 4 * 1024] } + fn return_option_vec() -> Option> { + let mut vec = Vec::new(); + vec.resize(16 * 1024, 0xAA); + Some(vec) + } + + fn return_option_bytes() -> Option { + let mut vec = Vec::new(); + vec.resize(16 * 1024, 0xAA); + Some(vec.into()) + } + /// Set the storage at key with value. fn set_storage(&mut self, key: &[u8], data: &[u8]) { self.place_storage(key.to_vec(), Some(data.to_vec())); @@ -300,4 +312,12 @@ wasm_export_functions! { assert_eq!(c, res.2); assert_eq!(d, res.3); } + + fn test_return_option_vec() { + test_api::return_option_vec(); + } + + fn test_return_option_bytes() { + test_api::return_option_bytes(); + } } diff --git a/primitives/runtime-interface/test/src/lib.rs b/primitives/runtime-interface/test/src/lib.rs index 1ab8dbfbbff22..d1db3e064295e 100644 --- a/primitives/runtime-interface/test/src/lib.rs +++ b/primitives/runtime-interface/test/src/lib.rs @@ -23,7 +23,7 @@ use sp_runtime_interface::*; use sp_runtime_interface_test_wasm::{test_api::HostFunctions, wasm_binary_unwrap}; use sp_runtime_interface_test_wasm_deprecated::wasm_binary_unwrap as wasm_binary_deprecated_unwrap; -use sc_executor_common::runtime_blob::RuntimeBlob; +use sc_executor_common::{runtime_blob::RuntimeBlob, wasm_runtime::AllocationStats}; use sp_wasm_interface::{ExtendedHostFunctions, HostFunctions as HostFunctionsT}; use std::{ @@ -36,7 +36,7 @@ type TestExternalities = sp_state_machine::TestExternalities( binary: &[u8], method: &str, -) -> Result { +) -> (Result, Option) { let mut ext = TestExternalities::default(); let mut ext_ext = ext.ext(); @@ -44,20 +44,21 @@ fn call_wasm_method_with_result( ExtendedHostFunctions, >::new(sc_executor::WasmExecutionMethod::Interpreted, Some(8), 8, None, 2); - executor - .uncached_call( - RuntimeBlob::uncompress_if_needed(binary).expect("Failed to parse binary"), - &mut ext_ext, - false, - method, - &[], - ) - .map_err(|e| format!("Failed to execute `{}`: {}", method, e))?; - Ok(ext) + let (result, allocation_stats) = executor.uncached_call_with_allocation_stats( + RuntimeBlob::uncompress_if_needed(binary).expect("Failed to parse binary"), + &mut ext_ext, + false, + method, + &[], + ); + let result = result + .map_err(|e| format!("Failed to execute `{}`: {}", method, e)) + .map(|_| ext); + (result, allocation_stats) } fn call_wasm_method(binary: &[u8], method: &str) -> TestExternalities { - call_wasm_method_with_result::(binary, method).unwrap() + call_wasm_method_with_result::(binary, method).0.unwrap() } #[test] @@ -103,8 +104,9 @@ fn test_return_input_public_key() { #[test] fn host_function_not_found() { - let err = - call_wasm_method_with_result::<()>(wasm_binary_unwrap(), "test_return_data").unwrap_err(); + let err = call_wasm_method_with_result::<()>(wasm_binary_unwrap(), "test_return_data") + .0 + .unwrap_err(); assert!(err.contains("Instantiation: Export ")); assert!(err.contains(" not found")); @@ -236,3 +238,43 @@ fn test_tracing() { fn test_return_input_as_tuple() { call_wasm_method::(wasm_binary_unwrap(), "test_return_input_as_tuple"); } + +#[test] +fn test_returning_option_bytes_from_a_host_function_is_efficient() { + let (result, stats_vec) = call_wasm_method_with_result::( + wasm_binary_unwrap(), + "test_return_option_vec", + ); + result.unwrap(); + let (result, stats_bytes) = call_wasm_method_with_result::( + wasm_binary_unwrap(), + "test_return_option_bytes", + ); + result.unwrap(); + + let stats_vec = stats_vec.unwrap(); + let stats_bytes = stats_bytes.unwrap(); + + // The way we currently implement marshaling of `Option>` through + // the WASM FFI boundary from the host to the runtime requires that it is + // marshaled through SCALE. This is quite inefficient as it requires two + // memory allocations inside of the runtime: + // + // 1) the first allocation to copy the SCALE-encoded blob into the runtime; + // 2) and another allocation for the resulting `Vec` when decoding that blob. + // + // Both of these allocations are are as big as the `Vec` which is being + // passed to the runtime. This is especially bad when fetching big values + // from storage, as it can lead to an out-of-memory situation. + // + // Our `Option` marshaling is better; it still must go through SCALE, + // and it still requires two allocations, however since `Bytes` is zero-copy + // only the first allocation is `Vec`-sized, and the second allocation + // which creates the deserialized `Bytes` is tiny, and is only necessary because + // the underlying `Bytes` buffer from which we're deserializing gets automatically + // turned into an `Arc`. + // + // So this assertion tests that deserializing `Option` allocates less than + // deserializing `Option>`. + assert_eq!(stats_bytes.bytes_allocated_sum + 16 * 1024 + 8, stats_vec.bytes_allocated_sum); +} From bbd6e830739ed4572d5e665302663af157fcb145 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 29 Jul 2022 10:22:49 +0100 Subject: [PATCH 434/484] construct_runtime!: Support parsing `struct Runtime` (#11932) * construct_runtime!: Support parsing `struct Runtime` * FMT --- bin/node-template/runtime/src/lib.rs | 5 +++-- frame/balances/src/tests_local.rs | 2 +- frame/support/procedural/src/construct_runtime/parse.rs | 9 ++++++++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/bin/node-template/runtime/src/lib.rs b/bin/node-template/runtime/src/lib.rs index a2f595f571a90..a4382cc9038ce 100644 --- a/bin/node-template/runtime/src/lib.rs +++ b/bin/node-template/runtime/src/lib.rs @@ -272,10 +272,11 @@ impl pallet_template::Config for Runtime { // Create the runtime by composing the FRAME pallets that were previously configured. construct_runtime!( - pub enum Runtime where + pub struct Runtime + where Block = Block, NodeBlock = opaque::Block, - UncheckedExtrinsic = UncheckedExtrinsic + UncheckedExtrinsic = UncheckedExtrinsic, { System: frame_system, RandomnessCollectiveFlip: pallet_randomness_collective_flip, diff --git a/frame/balances/src/tests_local.rs b/frame/balances/src/tests_local.rs index 6f4c50d90153a..48c6574c3b39f 100644 --- a/frame/balances/src/tests_local.rs +++ b/frame/balances/src/tests_local.rs @@ -34,7 +34,7 @@ type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where + pub struct Test where Block = Block, NodeBlock = Block, UncheckedExtrinsic = UncheckedExtrinsic, diff --git a/frame/support/procedural/src/construct_runtime/parse.rs b/frame/support/procedural/src/construct_runtime/parse.rs index a2cda6a0777b8..711da85c10cfc 100644 --- a/frame/support/procedural/src/construct_runtime/parse.rs +++ b/frame/support/procedural/src/construct_runtime/parse.rs @@ -73,7 +73,14 @@ pub struct ExplicitRuntimeDeclaration { impl Parse for RuntimeDeclaration { fn parse(input: ParseStream) -> Result { input.parse::()?; - input.parse::()?; + + // Support either `enum` or `struct`. + if input.peek(Token![struct]) { + input.parse::()?; + } else { + input.parse::()?; + } + let name = input.parse::()?; let where_section = input.parse()?; let pallets = From 23251ec1b968373cbd516e02ee2881e0cd702600 Mon Sep 17 00:00:00 2001 From: yjh Date: Fri, 29 Jul 2022 17:32:31 +0800 Subject: [PATCH 435/484] feat: add propose method for SimpleSlotWorker (#11692) * feat: add propose method for SimpleSlotWorker * remove param slot * improve code * fmt --- client/consensus/slots/src/lib.rs | 117 ++++++++++++++++++------------ 1 file changed, 69 insertions(+), 48 deletions(-) diff --git a/client/consensus/slots/src/lib.rs b/client/consensus/slots/src/lib.rs index a6fbc4bebc796..7c3de13444c1a 100644 --- a/client/consensus/slots/src/lib.rs +++ b/client/consensus/slots/src/lib.rs @@ -38,7 +38,7 @@ use log::{debug, info, warn}; use sc_consensus::{BlockImport, JustificationSyncLink}; use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_DEBUG, CONSENSUS_INFO, CONSENSUS_WARN}; use sp_arithmetic::traits::BaseArithmetic; -use sp_consensus::{CanAuthorWith, Proposer, SelectChain, SyncOracle}; +use sp_consensus::{CanAuthorWith, Proposal, Proposer, SelectChain, SyncOracle}; use sp_consensus_slots::{Slot, SlotDuration}; use sp_inherents::CreateInherentDataProviders; use sp_runtime::{ @@ -103,7 +103,7 @@ pub trait SimpleSlotWorker { type Proposer: Proposer + Send; /// Data associated with a slot claim. - type Claim: Send + 'static; + type Claim: Send + Sync + 'static; /// Epoch data necessary for authoring. type EpochData: Send + Sync + 'static; @@ -183,6 +183,70 @@ pub trait SimpleSlotWorker { /// Remaining duration for proposing. fn proposing_remaining_duration(&self, slot_info: &SlotInfo) -> Duration; + /// Propose a block by `Proposer`. + async fn propose( + &mut self, + proposer: Self::Proposer, + claim: &Self::Claim, + slot_info: SlotInfo, + proposing_remaining: Delay, + ) -> Option< + Proposal< + B, + >::Transaction, + >::Proof, + >, + > { + let slot = slot_info.slot; + let telemetry = self.telemetry(); + let logging_target = self.logging_target(); + let proposing_remaining_duration = self.proposing_remaining_duration(&slot_info); + let logs = self.pre_digest_data(slot, claim); + + // deadline our production to 98% of the total time left for proposing. As we deadline + // the proposing below to the same total time left, the 2% margin should be enough for + // the result to be returned. + let proposing = proposer + .propose( + slot_info.inherent_data, + sp_runtime::generic::Digest { logs }, + proposing_remaining_duration.mul_f32(0.98), + None, + ) + .map_err(|e| sp_consensus::Error::ClientImport(e.to_string())); + + let proposal = match futures::future::select(proposing, proposing_remaining).await { + Either::Left((Ok(p), _)) => p, + Either::Left((Err(err), _)) => { + warn!(target: logging_target, "Proposing failed: {}", err); + + return None + }, + Either::Right(_) => { + info!( + target: logging_target, + "⌛️ Discarding proposal for slot {}; block production took too long", slot, + ); + // If the node was compiled with debug, tell the user to use release optimizations. + #[cfg(build_type = "debug")] + info!( + target: logging_target, + "👉 Recompile your node in `--release` mode to mitigate this problem.", + ); + telemetry!( + telemetry; + CONSENSUS_INFO; + "slots.discarding_proposal_took_too_long"; + "slot" => *slot, + ); + + return None + }, + }; + + Some(proposal) + } + /// Implements [`SlotWorker::on_slot`]. async fn on_slot( &mut self, @@ -256,10 +320,8 @@ pub trait SimpleSlotWorker { } debug!( - target: self.logging_target(), - "Starting authorship at slot {}; timestamp = {}", - slot, - *timestamp, + target: logging_target, + "Starting authorship at slot {}; timestamp = {}", slot, *timestamp, ); telemetry!( @@ -287,48 +349,7 @@ pub trait SimpleSlotWorker { }, }; - let logs = self.pre_digest_data(slot, &claim); - - // deadline our production to 98% of the total time left for proposing. As we deadline - // the proposing below to the same total time left, the 2% margin should be enough for - // the result to be returned. - let proposing = proposer - .propose( - slot_info.inherent_data, - sp_runtime::generic::Digest { logs }, - proposing_remaining_duration.mul_f32(0.98), - None, - ) - .map_err(|e| sp_consensus::Error::ClientImport(e.to_string())); - - let proposal = match futures::future::select(proposing, proposing_remaining).await { - Either::Left((Ok(p), _)) => p, - Either::Left((Err(err), _)) => { - warn!(target: logging_target, "Proposing failed: {}", err); - - return None - }, - Either::Right(_) => { - info!( - target: logging_target, - "⌛️ Discarding proposal for slot {}; block production took too long", slot, - ); - // If the node was compiled with debug, tell the user to use release optimizations. - #[cfg(build_type = "debug")] - info!( - target: logging_target, - "👉 Recompile your node in `--release` mode to mitigate this problem.", - ); - telemetry!( - telemetry; - CONSENSUS_INFO; - "slots.discarding_proposal_took_too_long"; - "slot" => *slot, - ); - - return None - }, - }; + let proposal = self.propose(proposer, &claim, slot_info, proposing_remaining).await?; let (block, storage_proof) = (proposal.block, proposal.proof); let (header, body) = block.deconstruct(); From 52a88ca38ce48f73ad5cddfe3910004099138ba1 Mon Sep 17 00:00:00 2001 From: joe petrowski <25483142+joepetrowski@users.noreply.github.com> Date: Fri, 29 Jul 2022 12:56:08 +0200 Subject: [PATCH 436/484] Require Alliance Initialisation Before Joining (#11917) * require alliance initialisation before joining * use noop * make one definition of initialization * rename event * add todo comment * update doc --- frame/alliance/src/lib.rs | 34 +++++++++++++++++++++++++++------- frame/alliance/src/mock.rs | 8 +++++++- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/frame/alliance/src/lib.rs b/frame/alliance/src/lib.rs index f9e85e270af16..111ea5dc6e507 100644 --- a/frame/alliance/src/lib.rs +++ b/frame/alliance/src/lib.rs @@ -311,7 +311,9 @@ pub mod pallet { #[pallet::error] pub enum Error { /// The founders/fellows/allies have already been initialized. - MembersAlreadyInitialized, + AllianceAlreadyInitialized, + /// The Alliance has not been initialized yet, therefore accounts cannot join it. + AllianceNotYetInitialized, /// Account is already a member. AlreadyMember, /// Account is not a member. @@ -434,6 +436,11 @@ pub mod pallet { Members::::insert(MemberRole::Fellow, members); } if !self.allies.is_empty() { + // Only allow Allies if the Alliance is "initialized". + assert!( + Pallet::::is_initialized(), + "Alliance must have Founders or Fellows to have Allies" + ); let members: BoundedVec = self.allies.clone().try_into().expect("Too many genesis allies"); Members::::insert(MemberRole::Ally, members); @@ -612,6 +619,11 @@ pub mod pallet { ) -> DispatchResult { ensure_root(origin)?; + // Cannot be called if the Alliance already has Founders or Fellows. + // TODO: Remove check and allow Root to set members at any time. + // https://github.com/paritytech/substrate/issues/11928 + ensure!(!Self::is_initialized(), Error::::AllianceAlreadyInitialized); + let mut founders: BoundedVec = founders.try_into().map_err(|_| Error::::TooManyMembers)?; let mut fellows: BoundedVec = @@ -619,12 +631,6 @@ pub mod pallet { let mut allies: BoundedVec = allies.try_into().map_err(|_| Error::::TooManyMembers)?; - ensure!( - !Self::has_member(MemberRole::Founder) && - !Self::has_member(MemberRole::Fellow) && - !Self::has_member(MemberRole::Ally), - Error::::MembersAlreadyInitialized - ); for member in founders.iter().chain(fellows.iter()).chain(allies.iter()) { Self::has_identity(member)?; } @@ -705,6 +711,15 @@ pub mod pallet { pub fn join_alliance(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; + // We don't want anyone to join as an Ally before the Alliance has been initialized via + // Root call. The reasons are two-fold: + // + // 1. There is no `Rule` or admission criteria, so the joiner would be an ally to + // nought, and + // 2. It adds complexity to the initialization, namely deciding to overwrite accounts + // that already joined as an Ally. + ensure!(Self::is_initialized(), Error::::AllianceNotYetInitialized); + // Unscrupulous accounts are non grata. ensure!(!Self::is_unscrupulous_account(&who), Error::::AccountNonGrata); ensure!(!Self::is_member(&who), Error::::AlreadyMember); @@ -867,6 +882,11 @@ pub mod pallet { } impl, I: 'static> Pallet { + /// Check if the Alliance has been initialized. + fn is_initialized() -> bool { + Self::has_member(MemberRole::Founder) || Self::has_member(MemberRole::Fellow) + } + /// Check if a given role has any members. fn has_member(role: MemberRole) -> bool { Members::::decode_len(role).unwrap_or_default() > 0 diff --git a/frame/alliance/src/mock.rs b/frame/alliance/src/mock.rs index d6e9a92a10dec..91986300aa2e1 100644 --- a/frame/alliance/src/mock.rs +++ b/frame/alliance/src/mock.rs @@ -26,7 +26,7 @@ pub use sp_runtime::{ use sp_std::convert::{TryFrom, TryInto}; pub use frame_support::{ - assert_ok, ord_parameter_types, parameter_types, + assert_noop, assert_ok, ord_parameter_types, parameter_types, traits::{EitherOfDiverse, GenesisBuild, SortedMembers}, BoundedVec, }; @@ -291,6 +291,12 @@ pub fn new_test_ext() -> sp_io::TestExternalities { assert_ok!(Identity::provide_judgement(Origin::signed(1), 0, 5, Judgement::KnownGood)); assert_ok!(Identity::set_identity(Origin::signed(6), Box::new(info.clone()))); + // Joining before init should fail. + assert_noop!( + Alliance::join_alliance(Origin::signed(1)), + Error::::AllianceNotYetInitialized + ); + assert_ok!(Alliance::init_members(Origin::root(), vec![1, 2], vec![3], vec![])); System::set_block_number(1); From 9ff6e50ec1963d531a465b13ec746dd61727a12c Mon Sep 17 00:00:00 2001 From: Mak Date: Fri, 29 Jul 2022 15:16:30 +0300 Subject: [PATCH 437/484] Integrate automatic update of substrate-node-template (#11931) * Integrate automatic update of substrate-node-template * Update scripts/ci/gitlab/pipeline/publish.yml Co-authored-by: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Co-authored-by: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> --- scripts/ci/gitlab/pipeline/publish.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/scripts/ci/gitlab/pipeline/publish.yml b/scripts/ci/gitlab/pipeline/publish.yml index c02b50e0cbc1d..8f7a619f8b196 100644 --- a/scripts/ci/gitlab/pipeline/publish.yml +++ b/scripts/ci/gitlab/pipeline/publish.yml @@ -147,3 +147,17 @@ publish-draft-release: script: - ./scripts/ci/gitlab/publish_draft_release.sh allow_failure: true + +# Ref: https://github.com/paritytech/opstooling/issues/111 +update-node-template: + stage: publish + extends: .kubernetes-env + rules: + - if: $CI_COMMIT_REF_NAME =~ /^polkadot-v[0-9]+\.[0-9]+.*$/ # i.e. polkadot-v1.0.99, polkadot-v2.1rc1 + script: + - git clone --depth=1 --branch="$PIPELINE_SCRIPTS_TAG" https://github.com/paritytech/pipeline-scripts + - ./pipeline-scripts/update_substrate_template.sh + --repo-name "substrate-node-template" + --template-path "bin/node-template" + --github-api-token "$GITHUB_TOKEN" + --polkadot-branch "$CI_COMMIT_REF_NAME" From 3fcb63e65117f9abd993c9fb6a38274ca0723019 Mon Sep 17 00:00:00 2001 From: Dmitry Markin Date: Fri, 29 Jul 2022 15:36:48 +0300 Subject: [PATCH 438/484] Always allocate slots for reserved nodes (#11909) * Always allocate slots for reserved nodes * minor: replace no-slot peer counter with a set --- client/network/src/protocol.rs | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/client/network/src/protocol.rs b/client/network/src/protocol.rs index 3698a6b936ed5..1c933fabcbb5d 100644 --- a/client/network/src/protocol.rs +++ b/client/network/src/protocol.rs @@ -179,6 +179,10 @@ pub struct Protocol { /// List of nodes for which we perform additional logging because they are important for the /// user. important_peers: HashSet, + /// List of nodes that should never occupy peer slots. + default_peers_set_no_slot_peers: HashSet, + /// Actual list of connected no-slot nodes. + default_peers_set_no_slot_connected_peers: HashSet, /// Value that was passed as part of the configuration. Used to cap the number of full nodes. default_peers_set_num_full: usize, /// Number of slots to allocate to light nodes. @@ -304,6 +308,17 @@ where imp_p }; + let default_peers_set_no_slot_peers = { + let mut no_slot_p: HashSet = network_config + .default_peers_set + .reserved_nodes + .iter() + .map(|reserved| reserved.peer_id) + .collect(); + no_slot_p.shrink_to_fit(); + no_slot_p + }; + let mut known_addresses = Vec::new(); let (peerset, peerset_handle) = { @@ -404,6 +419,8 @@ where genesis_hash: info.genesis_hash, chain_sync, important_peers, + default_peers_set_no_slot_peers, + default_peers_set_no_slot_connected_peers: HashSet::new(), default_peers_set_num_full: network_config.default_peers_set_num_full as usize, default_peers_set_num_light: { let total = network_config.default_peers_set.out_peers + @@ -542,6 +559,7 @@ where self.pending_messages .push_back(CustomMessageOutcome::BlockImport(origin, blocks)); } + self.default_peers_set_no_slot_connected_peers.remove(&peer); Ok(()) } else { Err(()) @@ -723,7 +741,14 @@ where } } - if status.roles.is_full() && self.chain_sync.num_peers() >= self.default_peers_set_num_full + let no_slot_peer = self.default_peers_set_no_slot_peers.contains(&who); + let this_peer_reserved_slot: usize = if no_slot_peer { 1 } else { 0 }; + + if status.roles.is_full() && + self.chain_sync.num_peers() >= + self.default_peers_set_num_full + + self.default_peers_set_no_slot_connected_peers.len() + + this_peer_reserved_slot { debug!(target: "sync", "Too many full nodes, rejecting {}", who); self.behaviour.disconnect_peer(&who, HARDCODED_PEERSETS_SYNC); @@ -767,6 +792,9 @@ where debug!(target: "sync", "Connected {}", who); self.peers.insert(who, peer); + if no_slot_peer { + self.default_peers_set_no_slot_connected_peers.insert(who); + } self.pending_messages .push_back(CustomMessageOutcome::PeerNewBest(who, status.best_number)); From b1d0a6699946e79ac77d4478d85d34a5953d1b4f Mon Sep 17 00:00:00 2001 From: Adrian Catangiu Date: Fri, 29 Jul 2022 18:47:21 +0300 Subject: [PATCH 439/484] Lean BEEFY to Full BEEFY - don't skip (older) mandatory blocks and import justifications (#11821) * client/beefy: don't accept vote for older rounds * client/beefy: clean up and reorg the worker struct * client/beefy: first step towards Full BEEFY The first step from Lean->Full BEEFY is to have the worker enforce uninterrupted line of BEEFY finalized mandatory blocks. There is one mandatory block per session (the first block in the session). As such, votes processing and votes generation now enforces that all mandatory blocks are finalized in strict monotonically increasing sequence and no block 'N' will be worked on if there is any GRANDPA finalized but BEEFY non-final mandatory block 'M', where 'M < N'. Implementation details: - Introduced 'VoterOracle' to separate the voting decisions logic, and track new/pending sessions. - New sessions get queued up with the worker operating either: 1. up-to-date - all mandatory blocks leading up to current GRANDPA finalized: queue has ONE element, the 'current session' where `mandatory_done == true`, 2. lagging behind GRANDPA: queue has [1, N] elements, where all `mandatory_done == false`. In this state, everytime a session gets its mandatory block BEEFY finalized, the session is popped off the queue, eventually getting to operating mode `1. up-to-date`. - Votes get triaged and those that fall withing the `VoterOracle` allowed window get processed, the others get dropped if stale, or buffered for later processing (when they reach the window). - Worker general code was also updated to fall in one of two roles: 1. react to external events and change internal 'state', 2. generate events/votes based on internal 'state'. Signed-off-by: acatangiu * client/beefy: sketch idea for block import and sync Signed-off-by: acatangiu * client/beefy: add BEEFY block import * client/beefy: process justifications from block import * client/beefy: add TODOs for sync protocol * client/beefy: add more docs and comments * client/beefy-rpc: fix RPC error * client/beefy: verify justification validity on block import * client/beefy: more tests * client/beefy: small fixes - first handle and note the self vote before gossiping it, - don't shortcircuit on err when processing pending votes. * client/beefy: remove invalid justifications at block import * todo: beefy block import tests * RFC: ideas for multiple justifications per block * Revert "RFC: ideas for multiple justifications per block" This reverts commit 8256fb07d3124db69daf252720b3c0208202624d. * client/beefy: append justif to backend on block import * client/beefy: groundwork for block import test * client/beefy: groundwork2 for block import test * client/beefy: groundwork3 for block import test * client/beefy: add block import test * client/beefy: add required trait bounds to block import builder * remove client from beefy block import, backend gets the job done Signed-off-by: acatangiu --- Cargo.lock | 2 + client/beefy/Cargo.toml | 4 +- client/beefy/rpc/src/lib.rs | 5 +- client/beefy/rpc/src/notification.rs | 2 +- client/beefy/src/error.rs | 4 + client/beefy/src/import.rs | 205 ++++++ client/beefy/src/justification.rs | 177 +++++ client/beefy/src/lib.rs | 101 ++- client/beefy/src/metrics.rs | 10 +- client/beefy/src/notification.rs | 6 +- client/beefy/src/round.rs | 82 ++- client/beefy/src/tests.rs | 199 +++++- client/beefy/src/worker.rs | 932 +++++++++++++++++---------- client/network/test/src/lib.rs | 15 +- primitives/beefy/src/commitment.rs | 6 + primitives/runtime/src/lib.rs | 5 + 16 files changed, 1344 insertions(+), 411 deletions(-) create mode 100644 client/beefy/src/import.rs create mode 100644 client/beefy/src/justification.rs diff --git a/Cargo.lock b/Cargo.lock index 926d6d4cdf19a..34bf34c62c6fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -440,6 +440,7 @@ dependencies = [ name = "beefy-gadget" version = "4.0.0-dev" dependencies = [ + "async-trait", "beefy-primitives", "fnv", "futures", @@ -448,6 +449,7 @@ dependencies = [ "log", "parity-scale-codec", "parking_lot 0.12.0", + "sc-block-builder", "sc-chain-spec", "sc-client-api", "sc-consensus", diff --git a/client/beefy/Cargo.toml b/client/beefy/Cargo.toml index b910f7ce307a5..e219420959c9f 100644 --- a/client/beefy/Cargo.toml +++ b/client/beefy/Cargo.toml @@ -9,6 +9,7 @@ description = "BEEFY Client gadget for substrate" homepage = "https://substrate.io" [dependencies] +async-trait = "0.1.50" codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } fnv = "1.0.6" futures = "0.3" @@ -22,6 +23,7 @@ beefy-primitives = { version = "4.0.0-dev", path = "../../primitives/beefy" } prometheus = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } sc-chain-spec = { version = "4.0.0-dev", path = "../../client/chain-spec" } sc-client-api = { version = "4.0.0-dev", path = "../api" } +sc-consensus = { version = "0.10.0-dev", path = "../consensus/common" } sc-finality-grandpa = { version = "0.10.0-dev", path = "../../client/finality-grandpa" } sc-keystore = { version = "4.0.0-dev", path = "../keystore" } sc-network = { version = "0.10.0-dev", path = "../network" } @@ -42,7 +44,7 @@ serde = "1.0.136" strum = { version = "0.24.1", features = ["derive"] } tempfile = "3.1.0" tokio = "1.17.0" -sc-consensus = { version = "0.10.0-dev", path = "../consensus/common" } +sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } sc-network-test = { version = "0.8.0", path = "../network/test" } sp-finality-grandpa = { version = "4.0.0-dev", path = "../../primitives/finality-grandpa" } sp-keyring = { version = "6.0.0", path = "../../primitives/keyring" } diff --git a/client/beefy/rpc/src/lib.rs b/client/beefy/rpc/src/lib.rs index 2c3ffda056c26..13bca08d37429 100644 --- a/client/beefy/rpc/src/lib.rs +++ b/client/beefy/rpc/src/lib.rs @@ -164,8 +164,9 @@ where mod tests { use super::*; - use beefy_gadget::notification::{ - BeefyBestBlockStream, BeefySignedCommitment, BeefySignedCommitmentSender, + use beefy_gadget::{ + justification::BeefySignedCommitment, + notification::{BeefyBestBlockStream, BeefySignedCommitmentSender}, }; use beefy_primitives::{known_payload_ids, Payload}; use codec::{Decode, Encode}; diff --git a/client/beefy/rpc/src/notification.rs b/client/beefy/rpc/src/notification.rs index 2f58c7c6bb5dc..cdda667782dd5 100644 --- a/client/beefy/rpc/src/notification.rs +++ b/client/beefy/rpc/src/notification.rs @@ -29,7 +29,7 @@ pub struct EncodedSignedCommitment(sp_core::Bytes); impl EncodedSignedCommitment { pub fn new( - signed_commitment: beefy_gadget::notification::BeefySignedCommitment, + signed_commitment: beefy_gadget::justification::BeefySignedCommitment, ) -> Self where Block: BlockT, diff --git a/client/beefy/src/error.rs b/client/beefy/src/error.rs index eacadeb7613a5..dd5fd649d52ce 100644 --- a/client/beefy/src/error.rs +++ b/client/beefy/src/error.rs @@ -24,8 +24,12 @@ use std::fmt::Debug; #[derive(Debug, thiserror::Error, PartialEq)] pub enum Error { + #[error("Backend: {0}")] + Backend(String), #[error("Keystore error: {0}")] Keystore(String), #[error("Signature error: {0}")] Signature(String), + #[error("Session uninitialized")] + UninitSession, } diff --git a/client/beefy/src/import.rs b/client/beefy/src/import.rs new file mode 100644 index 0000000000000..7caeb49db5e50 --- /dev/null +++ b/client/beefy/src/import.rs @@ -0,0 +1,205 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use beefy_primitives::{crypto::Signature, BeefyApi, VersionedFinalityProof, BEEFY_ENGINE_ID}; +use codec::Encode; +use log::error; +use std::{collections::HashMap, sync::Arc}; + +use sp_api::{ProvideRuntimeApi, TransactionFor}; +use sp_blockchain::{well_known_cache_keys, HeaderBackend}; +use sp_consensus::Error as ConsensusError; +use sp_runtime::{ + generic::BlockId, + traits::{Block as BlockT, Header as HeaderT, NumberFor}, + EncodedJustification, +}; + +use sc_client_api::backend::Backend; +use sc_consensus::{BlockCheckParams, BlockImport, BlockImportParams, ImportResult}; + +use crate::{ + justification::decode_and_verify_commitment, notification::BeefySignedCommitmentSender, +}; + +/// A block-import handler for BEEFY. +/// +/// This scans each imported block for BEEFY justifications and verifies them. +/// Wraps a `inner: BlockImport` and ultimately defers to it. +/// +/// When using BEEFY, the block import worker should be using this block import object. +pub struct BeefyBlockImport { + backend: Arc, + runtime: Arc, + inner: I, + justification_sender: BeefySignedCommitmentSender, +} + +impl Clone for BeefyBlockImport { + fn clone(&self) -> Self { + BeefyBlockImport { + backend: self.backend.clone(), + runtime: self.runtime.clone(), + inner: self.inner.clone(), + justification_sender: self.justification_sender.clone(), + } + } +} + +impl BeefyBlockImport { + /// Create a new BeefyBlockImport. + pub fn new( + backend: Arc, + runtime: Arc, + inner: I, + justification_sender: BeefySignedCommitmentSender, + ) -> BeefyBlockImport { + BeefyBlockImport { backend, runtime, inner, justification_sender } + } +} + +impl BeefyBlockImport +where + Block: BlockT, + BE: Backend, + Runtime: ProvideRuntimeApi, + Runtime::Api: BeefyApi + Send + Sync, +{ + fn decode_and_verify( + &self, + encoded: &EncodedJustification, + number: NumberFor, + hash: ::Hash, + ) -> Result, Signature>, ConsensusError> { + let block_id = BlockId::hash(hash); + let validator_set = self + .runtime + .runtime_api() + .validator_set(&block_id) + .map_err(|e| ConsensusError::ClientImport(e.to_string()))? + .ok_or_else(|| ConsensusError::ClientImport("Unknown validator set".to_string()))?; + + decode_and_verify_commitment::(&encoded[..], number, &validator_set) + } + + /// Import BEEFY justification: Send it to worker for processing and also append it to backend. + /// + /// This function assumes: + /// - `justification` is verified and valid, + /// - the block referred by `justification` has been imported _and_ finalized. + fn import_beefy_justification_unchecked( + &self, + number: NumberFor, + justification: VersionedFinalityProof, Signature>, + ) { + // Append the justification to the block in the backend. + if let Err(e) = self.backend.append_justification( + BlockId::Number(number), + (BEEFY_ENGINE_ID, justification.encode()), + ) { + error!(target: "beefy", "🥩 Error {:?} on appending justification: {:?}", e, justification); + } + // Send the justification to the BEEFY voter for processing. + match justification { + // TODO #11838: Should not unpack, these channels should also use + // `VersionedFinalityProof`. + VersionedFinalityProof::V1(signed_commitment) => self + .justification_sender + .notify(|| Ok::<_, ()>(signed_commitment)) + .expect("forwards closure result; the closure always returns Ok; qed."), + }; + } +} + +#[async_trait::async_trait] +impl BlockImport for BeefyBlockImport +where + Block: BlockT, + BE: Backend, + I: BlockImport< + Block, + Error = ConsensusError, + Transaction = sp_api::TransactionFor, + > + Send + + Sync, + Runtime: ProvideRuntimeApi + Send + Sync, + Runtime::Api: BeefyApi, +{ + type Error = ConsensusError; + type Transaction = TransactionFor; + + async fn import_block( + &mut self, + mut block: BlockImportParams, + new_cache: HashMap>, + ) -> Result { + let hash = block.post_hash(); + let number = *block.header.number(); + + let beefy_proof = block + .justifications + .as_mut() + .and_then(|just| { + let decoded = just + .get(BEEFY_ENGINE_ID) + .map(|encoded| self.decode_and_verify(encoded, number, hash)); + // Remove BEEFY justification from the list before giving to `inner`; + // we will append it to backend ourselves at the end if all goes well. + just.remove(BEEFY_ENGINE_ID); + decoded + }) + .transpose() + .unwrap_or(None); + + // Run inner block import. + let inner_import_result = self.inner.import_block(block, new_cache).await?; + + match (beefy_proof, &inner_import_result) { + (Some(proof), ImportResult::Imported(_)) => { + let status = self.backend.blockchain().info(); + if number <= status.finalized_number && + Some(hash) == + self.backend + .blockchain() + .hash(number) + .map_err(|e| ConsensusError::ClientImport(e.to_string()))? + { + // The proof is valid and the block is imported and final, we can import. + self.import_beefy_justification_unchecked(number, proof); + } else { + error!( + target: "beefy", + "🥩 Cannot import justification: {:?} for, not yet final, block number {:?}", + proof, + number, + ); + } + }, + _ => (), + } + + Ok(inner_import_result) + } + + async fn check_block( + &mut self, + block: BlockCheckParams, + ) -> Result { + self.inner.check_block(block).await + } +} diff --git a/client/beefy/src/justification.rs b/client/beefy/src/justification.rs new file mode 100644 index 0000000000000..2a5191daec4b5 --- /dev/null +++ b/client/beefy/src/justification.rs @@ -0,0 +1,177 @@ +// This file is part of Substrate. + +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use crate::keystore::BeefyKeystore; +use beefy_primitives::{ + crypto::{AuthorityId, Signature}, + ValidatorSet, VersionedFinalityProof, +}; +use codec::{Decode, Encode}; +use sp_consensus::Error as ConsensusError; +use sp_runtime::traits::{Block as BlockT, NumberFor}; + +/// A commitment with matching BEEFY authorities' signatures. +pub type BeefySignedCommitment = + beefy_primitives::SignedCommitment, beefy_primitives::crypto::Signature>; + +/// Decode and verify a Beefy SignedCommitment. +pub(crate) fn decode_and_verify_commitment( + encoded: &[u8], + target_number: NumberFor, + validator_set: &ValidatorSet, +) -> Result, Signature>, ConsensusError> { + let proof = , Signature>>::decode(&mut &*encoded) + .map_err(|_| ConsensusError::InvalidJustification)?; + verify_with_validator_set::(target_number, validator_set, &proof).map(|_| proof) +} + +/// Verify the Beefy finality proof against the validator set at the block it was generated. +fn verify_with_validator_set( + target_number: NumberFor, + validator_set: &ValidatorSet, + proof: &VersionedFinalityProof, Signature>, +) -> Result<(), ConsensusError> { + match proof { + VersionedFinalityProof::V1(signed_commitment) => { + if signed_commitment.signatures.len() != validator_set.len() || + signed_commitment.commitment.validator_set_id != validator_set.id() || + signed_commitment.commitment.block_number != target_number + { + return Err(ConsensusError::InvalidJustification) + } + + // Arrangement of signatures in the commitment should be in the same order + // as validators for that set. + let message = signed_commitment.commitment.encode(); + let valid_signatures = validator_set + .validators() + .into_iter() + .zip(signed_commitment.signatures.iter()) + .filter(|(id, signature)| { + signature + .as_ref() + .map(|sig| BeefyKeystore::verify(id, sig, &message[..])) + .unwrap_or(false) + }) + .count(); + if valid_signatures >= crate::round::threshold(validator_set.len()) { + Ok(()) + } else { + Err(ConsensusError::InvalidJustification) + } + }, + } +} + +#[cfg(test)] +pub(crate) mod tests { + use beefy_primitives::{known_payload_ids, Commitment, Payload, SignedCommitment}; + use substrate_test_runtime_client::runtime::Block; + + use super::*; + use crate::{keystore::tests::Keyring, tests::make_beefy_ids}; + + pub(crate) fn new_signed_commitment( + block_num: NumberFor, + validator_set: &ValidatorSet, + keys: &[Keyring], + ) -> BeefySignedCommitment { + let commitment = Commitment { + payload: Payload::new(known_payload_ids::MMR_ROOT_ID, vec![]), + block_number: block_num, + validator_set_id: validator_set.id(), + }; + let message = commitment.encode(); + let signatures = keys.iter().map(|key| Some(key.sign(&message))).collect(); + SignedCommitment { commitment, signatures } + } + + #[test] + fn should_verify_with_validator_set() { + let keys = &[Keyring::Alice, Keyring::Bob, Keyring::Charlie]; + let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); + + // build valid justification + let block_num = 42; + let proof = new_signed_commitment(block_num, &validator_set, keys); + + let good_proof = proof.clone().into(); + // should verify successfully + verify_with_validator_set::(block_num, &validator_set, &good_proof).unwrap(); + + // wrong block number -> should fail verification + let good_proof = proof.clone().into(); + match verify_with_validator_set::(block_num + 1, &validator_set, &good_proof) { + Err(ConsensusError::InvalidJustification) => (), + _ => assert!(false, "Expected Err(ConsensusError::InvalidJustification)"), + }; + + // wrong validator set id -> should fail verification + let good_proof = proof.clone().into(); + let other = ValidatorSet::new(make_beefy_ids(keys), 1).unwrap(); + match verify_with_validator_set::(block_num, &other, &good_proof) { + Err(ConsensusError::InvalidJustification) => (), + _ => assert!(false, "Expected Err(ConsensusError::InvalidJustification)"), + }; + + // wrong signatures length -> should fail verification + let mut bad_proof = proof.clone(); + // change length of signatures + bad_proof.signatures.pop().flatten().unwrap(); + match verify_with_validator_set::(block_num + 1, &validator_set, &bad_proof.into()) { + Err(ConsensusError::InvalidJustification) => (), + _ => assert!(false, "Expected Err(ConsensusError::InvalidJustification)"), + }; + + // not enough signatures -> should fail verification + let mut bad_proof = proof.clone(); + // remove a signature (but same length) + *bad_proof.signatures.first_mut().unwrap() = None; + match verify_with_validator_set::(block_num + 1, &validator_set, &bad_proof.into()) { + Err(ConsensusError::InvalidJustification) => (), + _ => assert!(false, "Expected Err(ConsensusError::InvalidJustification)"), + }; + + // not enough _correct_ signatures -> should fail verification + let mut bad_proof = proof.clone(); + // change a signature to a different key + *bad_proof.signatures.first_mut().unwrap() = + Some(Keyring::Dave.sign(&proof.commitment.encode())); + match verify_with_validator_set::(block_num + 1, &validator_set, &bad_proof.into()) { + Err(ConsensusError::InvalidJustification) => (), + _ => assert!(false, "Expected Err(ConsensusError::InvalidJustification)"), + }; + } + + #[test] + fn should_decode_and_verify_commitment() { + let keys = &[Keyring::Alice, Keyring::Bob]; + let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); + let block_num = 1; + + // build valid justification + let proof = new_signed_commitment(block_num, &validator_set, keys); + let versioned_proof: VersionedFinalityProof, Signature> = proof.into(); + let encoded = versioned_proof.encode(); + + // should successfully decode and verify + let verified = + decode_and_verify_commitment::(&encoded, block_num, &validator_set).unwrap(); + assert_eq!(verified, versioned_proof); + } +} diff --git a/client/beefy/src/lib.rs b/client/beefy/src/lib.rs index c025ec5686ad2..81c72dec8cd08 100644 --- a/client/beefy/src/lib.rs +++ b/client/beefy/src/lib.rs @@ -16,23 +16,18 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use std::sync::Arc; - +use beefy_primitives::{BeefyApi, MmrRootHash}; use prometheus::Registry; - use sc_client_api::{Backend, BlockchainEvents, Finalizer}; +use sc_consensus::BlockImport; use sc_network_gossip::Network as GossipNetwork; - use sp_api::ProvideRuntimeApi; use sp_blockchain::HeaderBackend; -use sp_consensus::SyncOracle; +use sp_consensus::{Error as ConsensusError, SyncOracle}; use sp_keystore::SyncCryptoStorePtr; use sp_mmr_primitives::MmrApi; use sp_runtime::traits::Block; - -use beefy_primitives::{BeefyApi, MmrRootHash}; - -use crate::notification::{BeefyBestBlockSender, BeefySignedCommitmentSender}; +use std::sync::Arc; mod error; mod gossip; @@ -41,11 +36,21 @@ mod metrics; mod round; mod worker; +pub mod import; +pub mod justification; pub mod notification; #[cfg(test)] mod tests; +use crate::{ + import::BeefyBlockImport, + notification::{ + BeefyBestBlockSender, BeefyBestBlockStream, BeefySignedCommitmentSender, + BeefySignedCommitmentStream, + }, +}; + pub use beefy_protocol_name::standard_name as protocol_standard_name; pub(crate) mod beefy_protocol_name { @@ -110,6 +115,68 @@ where // empty } +/// Links between the block importer, the background voter and the RPC layer, +/// to be used by the voter. +#[derive(Clone)] +pub struct BeefyVoterLinks { + // BlockImport -> Voter links + /// Stream of BEEFY signed commitments from block import to voter. + pub from_block_import_justif_stream: BeefySignedCommitmentStream, + + // Voter -> RPC links + /// Sends BEEFY signed commitments from voter to RPC. + pub to_rpc_justif_sender: BeefySignedCommitmentSender, + /// Sends BEEFY best block hashes from voter to RPC. + pub to_rpc_best_block_sender: BeefyBestBlockSender, +} + +/// Links used by the BEEFY RPC layer, from the BEEFY background voter. +#[derive(Clone)] +pub struct BeefyRPCLinks { + /// Stream of signed commitments coming from the voter. + pub from_voter_justif_stream: BeefySignedCommitmentStream, + /// Stream of BEEFY best block hashes coming from the voter. + pub from_voter_best_beefy_stream: BeefyBestBlockStream, +} + +/// Make block importer and link half necessary to tie the background voter to it. +pub fn beefy_block_import_and_links( + wrapped_block_import: I, + backend: Arc, + runtime: Arc, +) -> (BeefyBlockImport, BeefyVoterLinks, BeefyRPCLinks) +where + B: Block, + BE: Backend, + I: BlockImport> + + Send + + Sync, + RuntimeApi: ProvideRuntimeApi + Send + Sync, + RuntimeApi::Api: BeefyApi, +{ + // Voter -> RPC links + let (to_rpc_justif_sender, from_voter_justif_stream) = + notification::BeefySignedCommitmentStream::::channel(); + let (to_rpc_best_block_sender, from_voter_best_beefy_stream) = + notification::BeefyBestBlockStream::::channel(); + + // BlockImport -> Voter links + let (to_voter_justif_sender, from_block_import_justif_stream) = + notification::BeefySignedCommitmentStream::::channel(); + + // BlockImport + let import = + BeefyBlockImport::new(backend, runtime, wrapped_block_import, to_voter_justif_sender); + let voter_links = BeefyVoterLinks { + from_block_import_justif_stream, + to_rpc_justif_sender, + to_rpc_best_block_sender, + }; + let rpc_links = BeefyRPCLinks { from_voter_best_beefy_stream, from_voter_justif_stream }; + + (import, voter_links, rpc_links) +} + /// BEEFY gadget initialization parameters. pub struct BeefyParams where @@ -130,16 +197,14 @@ where pub key_store: Option, /// Gossip network pub network: N, - /// BEEFY signed commitment sender - pub signed_commitment_sender: BeefySignedCommitmentSender, - /// BEEFY best block sender - pub beefy_best_block_sender: BeefyBestBlockSender, /// Minimal delta between blocks, BEEFY should vote for pub min_block_delta: u32, /// Prometheus metric registry pub prometheus_registry: Option, /// Chain specific GRANDPA protocol name. See [`beefy_protocol_name::standard_name`]. pub protocol_name: std::borrow::Cow<'static, str>, + /// Links between the block importer, the background voter and the RPC layer. + pub links: BeefyVoterLinks, } /// Start the BEEFY gadget. @@ -160,11 +225,10 @@ where runtime, key_store, network, - signed_commitment_sender, - beefy_best_block_sender, min_block_delta, prometheus_registry, protocol_name, + links, } = beefy_params; let sync_oracle = network.clone(); @@ -194,14 +258,13 @@ where client, backend, runtime, + sync_oracle, key_store: key_store.into(), - signed_commitment_sender, - beefy_best_block_sender, gossip_engine, gossip_validator, - min_block_delta, + links, metrics, - sync_oracle, + min_block_delta, }; let worker = worker::BeefyWorker::<_, _, _, _, _>::new(worker_params); diff --git a/client/beefy/src/metrics.rs b/client/beefy/src/metrics.rs index a6d29dbf88abb..71e34e24c4fa0 100644 --- a/client/beefy/src/metrics.rs +++ b/client/beefy/src/metrics.rs @@ -32,8 +32,8 @@ pub(crate) struct Metrics { pub beefy_best_block: Gauge, /// Next block BEEFY should vote on pub beefy_should_vote_on: Gauge, - /// Number of sessions without a signed commitment - pub beefy_skipped_sessions: Counter, + /// Number of sessions with lagging signed commitment on mandatory block + pub beefy_lagging_sessions: Counter, } impl Metrics { @@ -65,10 +65,10 @@ impl Metrics { Gauge::new("substrate_beefy_should_vote_on", "Next block, BEEFY should vote on")?, registry, )?, - beefy_skipped_sessions: register( + beefy_lagging_sessions: register( Counter::new( - "substrate_beefy_skipped_sessions", - "Number of sessions without a signed commitment", + "substrate_beefy_lagging_sessions", + "Number of sessions with lagging signed commitment on mandatory block", )?, registry, )?, diff --git a/client/beefy/src/notification.rs b/client/beefy/src/notification.rs index 7c18d809f6efb..9479891714234 100644 --- a/client/beefy/src/notification.rs +++ b/client/beefy/src/notification.rs @@ -17,11 +17,9 @@ // along with this program. If not, see . use sc_utils::notification::{NotificationSender, NotificationStream, TracingKeyStr}; -use sp_runtime::traits::{Block as BlockT, NumberFor}; +use sp_runtime::traits::Block as BlockT; -/// A commitment with matching BEEFY authorities' signatures. -pub type BeefySignedCommitment = - beefy_primitives::SignedCommitment, beefy_primitives::crypto::Signature>; +use crate::justification::BeefySignedCommitment; /// The sending half of the notifications channel(s) used to send /// notifications about best BEEFY block from the gadget side. diff --git a/client/beefy/src/round.rs b/client/beefy/src/round.rs index fecb9557df6ea..ebd85c8dea05d 100644 --- a/client/beefy/src/round.rs +++ b/client/beefy/src/round.rs @@ -59,7 +59,8 @@ impl RoundTracker { } } -fn threshold(authorities: usize) -> usize { +/// Minimum size of `authorities` subset that produced valid signatures for a block to finalize. +pub fn threshold(authorities: usize) -> usize { let faulty = authorities.saturating_sub(1) / 3; authorities - faulty } @@ -70,9 +71,10 @@ fn threshold(authorities: usize) -> usize { /// Does not do any validation on votes or signatures, layers above need to handle that (gossip). pub(crate) struct Rounds { rounds: BTreeMap<(Payload, NumberFor), RoundTracker>, - best_done: Option>, session_start: NumberFor, validator_set: ValidatorSet, + mandatory_done: bool, + best_done: Option>, } impl Rounds @@ -81,15 +83,15 @@ where B: Block, { pub(crate) fn new(session_start: NumberFor, validator_set: ValidatorSet) -> Self { - Rounds { rounds: BTreeMap::new(), best_done: None, session_start, validator_set } + Rounds { + rounds: BTreeMap::new(), + session_start, + validator_set, + mandatory_done: false, + best_done: None, + } } -} -impl Rounds -where - P: Ord + Hash + Clone, - B: Block, -{ pub(crate) fn validator_set_id(&self) -> ValidatorSetId { self.validator_set.id() } @@ -98,8 +100,12 @@ where self.validator_set.validators() } - pub(crate) fn session_start(&self) -> &NumberFor { - &self.session_start + pub(crate) fn session_start(&self) -> NumberFor { + self.session_start + } + + pub(crate) fn mandatory_done(&self) -> bool { + self.mandatory_done } pub(crate) fn should_self_vote(&self, round: &(P, NumberFor)) -> bool { @@ -113,12 +119,9 @@ where vote: (Public, Signature), self_vote: bool, ) -> bool { - if Some(round.1.clone()) <= self.best_done { - debug!( - target: "beefy", - "🥩 received vote for old stale round {:?}, ignoring", - round.1 - ); + let num = round.1; + if num < self.session_start || Some(num) <= self.best_done { + debug!(target: "beefy", "🥩 received vote for old stale round {:?}, ignoring", num); false } else if !self.validators().iter().any(|id| vote.0 == *id) { debug!( @@ -147,6 +150,7 @@ where // remove this and older (now stale) rounds let signatures = self.rounds.remove(round)?.votes; self.rounds.retain(|&(_, number), _| number > round.1); + self.mandatory_done = self.mandatory_done || round.1 == self.session_start; self.best_done = self.best_done.max(Some(round.1)); debug!(target: "beefy", "🥩 Concluded round #{}", round.1); @@ -160,6 +164,11 @@ where None } } + + #[cfg(test)] + pub(crate) fn test_set_mandatory_done(&mut self, done: bool) { + self.mandatory_done = done; + } } #[cfg(test)] @@ -226,7 +235,7 @@ mod tests { let rounds = Rounds::::new(session_start, validators); assert_eq!(42, rounds.validator_set_id()); - assert_eq!(1, *rounds.session_start()); + assert_eq!(1, rounds.session_start()); assert_eq!( &vec![Keyring::Alice.public(), Keyring::Bob.public(), Keyring::Charlie.public()], rounds.validators() @@ -307,6 +316,43 @@ mod tests { )); } + #[test] + fn old_rounds_not_accepted() { + sp_tracing::try_init_simple(); + + let validators = ValidatorSet::::new( + vec![Keyring::Alice.public(), Keyring::Bob.public(), Keyring::Charlie.public()], + 42, + ) + .unwrap(); + let alice = (Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed")); + + let session_start = 10u64.into(); + let mut rounds = Rounds::::new(session_start, validators); + + let mut vote = (H256::from_low_u64_le(1), 9); + // add vote for previous session, should fail + assert!(!rounds.add_vote(&vote, alice.clone(), true)); + // no votes present + assert!(rounds.rounds.is_empty()); + + // simulate 11 was concluded + rounds.best_done = Some(11); + // add votes for current session, but already concluded rounds, should fail + vote.1 = 10; + assert!(!rounds.add_vote(&vote, alice.clone(), true)); + vote.1 = 11; + assert!(!rounds.add_vote(&vote, alice.clone(), true)); + // no votes present + assert!(rounds.rounds.is_empty()); + + // add good vote + vote.1 = 12; + assert!(rounds.add_vote(&vote, alice, true)); + // good vote present + assert_eq!(rounds.rounds.len(), 1); + } + #[test] fn multiple_rounds() { sp_tracing::try_init_simple(); diff --git a/client/beefy/src/tests.rs b/client/beefy/src/tests.rs index 78e697a6ada81..9c8f443dd1f7e 100644 --- a/client/beefy/src/tests.rs +++ b/client/beefy/src/tests.rs @@ -21,12 +21,15 @@ use futures::{future, stream::FuturesUnordered, Future, StreamExt}; use parking_lot::Mutex; use serde::{Deserialize, Serialize}; -use std::{sync::Arc, task::Poll}; +use std::{collections::HashMap, sync::Arc, task::Poll}; use tokio::{runtime::Runtime, time::Duration}; use sc_chain_spec::{ChainSpec, GenericChainSpec}; use sc_client_api::HeaderBackend; -use sc_consensus::BoxJustificationImport; +use sc_consensus::{ + BlockImport, BlockImportParams, BoxJustificationImport, ForkChoiceStrategy, ImportResult, + ImportedAux, +}; use sc_keystore::LocalKeystore; use sc_network_test::{ Block, BlockImportAdapter, FullPeerConfig, PassThroughVerifier, Peer, PeersClient, @@ -35,7 +38,8 @@ use sc_network_test::{ use sc_utils::notification::NotificationReceiver; use beefy_primitives::{ - crypto::AuthorityId, BeefyApi, ConsensusLog, MmrRootHash, ValidatorSet, BEEFY_ENGINE_ID, + crypto::{AuthorityId, Signature}, + BeefyApi, ConsensusLog, MmrRootHash, ValidatorSet, VersionedFinalityProof, BEEFY_ENGINE_ID, KEY_TYPE as BeefyKeyType, }; use sp_mmr_primitives::{ @@ -47,19 +51,32 @@ use sp_consensus::BlockOrigin; use sp_core::H256; use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; use sp_runtime::{ - codec::Encode, generic::BlockId, traits::Header as HeaderT, BuildStorage, DigestItem, Storage, + codec::Encode, + generic::BlockId, + traits::{Header as HeaderT, NumberFor}, + BuildStorage, DigestItem, Justifications, Storage, }; use substrate_test_runtime_client::{runtime::Header, ClientExt}; -use crate::{beefy_protocol_name, keystore::tests::Keyring as BeefyKeyring, notification::*}; +use crate::{ + beefy_block_import_and_links, beefy_protocol_name, justification::*, + keystore::tests::Keyring as BeefyKeyring, BeefyRPCLinks, BeefyVoterLinks, +}; pub(crate) const BEEFY_PROTOCOL_NAME: &'static str = "/beefy/1"; const GOOD_MMR_ROOT: MmrRootHash = MmrRootHash::repeat_byte(0xbf); const BAD_MMR_ROOT: MmrRootHash = MmrRootHash::repeat_byte(0x42); +type BeefyBlockImport = crate::BeefyBlockImport< + Block, + substrate_test_runtime_client::Backend, + two_validators::TestApi, + BlockImportAdapter>, +>; + pub(crate) type BeefyValidatorSet = ValidatorSet; -pub(crate) type BeefyPeer = Peer; +pub(crate) type BeefyPeer = Peer; #[derive(Debug, Serialize, Deserialize)] struct Genesis(std::collections::BTreeMap); @@ -97,17 +114,10 @@ fn beefy_protocol_name() { assert_eq!(proto_name.to_string(), expected); } -// TODO: compiler warns us about unused `signed_commitment_stream`, will use in later tests -#[allow(dead_code)] -#[derive(Clone)] -pub(crate) struct BeefyLinkHalf { - pub signed_commitment_stream: BeefySignedCommitmentStream, - pub beefy_best_block_stream: BeefyBestBlockStream, -} - #[derive(Default)] pub(crate) struct PeerData { - pub(crate) beefy_link_half: Mutex>, + pub(crate) beefy_rpc_links: Mutex>>, + pub(crate) beefy_voter_links: Mutex>>, } #[derive(Default)] @@ -163,7 +173,7 @@ impl BeefyTestNet { impl TestNetFactory for BeefyTestNet { type Verifier = PassThroughVerifier; - type BlockImport = PeersClient; + type BlockImport = BeefyBlockImport; type PeerData = PeerData; fn make_verifier(&self, _client: PeersClient, _: &PeerData) -> Self::Verifier { @@ -178,7 +188,17 @@ impl TestNetFactory for BeefyTestNet { Option>, Self::PeerData, ) { - (client.as_block_import(), None, PeerData::default()) + let inner = BlockImportAdapter::new(client.clone()); + let (block_import, voter_links, rpc_links) = beefy_block_import_and_links( + inner, + client.as_backend(), + Arc::new(two_validators::TestApi {}), + ); + let peer_data = PeerData { + beefy_rpc_links: Mutex::new(Some(rpc_links)), + beefy_voter_links: Mutex::new(Some(voter_links)), + }; + (BlockImportAdapter::new(block_import), None, peer_data) } fn peer(&mut self, i: usize) -> &mut BeefyPeer { @@ -333,12 +353,12 @@ where let keystore = create_beefy_keystore(*key); - let (signed_commitment_sender, signed_commitment_stream) = - BeefySignedCommitmentStream::::channel(); - let (beefy_best_block_sender, beefy_best_block_stream) = - BeefyBestBlockStream::::channel(); - let beefy_link_half = BeefyLinkHalf { signed_commitment_stream, beefy_best_block_stream }; - *peer.data.beefy_link_half.lock() = Some(beefy_link_half); + let (_, _, peer_data) = net.make_block_import(peer.client().clone()); + let PeerData { beefy_rpc_links, beefy_voter_links } = peer_data; + + let beefy_voter_links = beefy_voter_links.lock().take(); + *peer.data.beefy_rpc_links.lock() = beefy_rpc_links.lock().take(); + *peer.data.beefy_voter_links.lock() = beefy_voter_links.clone(); let beefy_params = crate::BeefyParams { client: peer.client().as_client(), @@ -346,8 +366,7 @@ where runtime: api.clone(), key_store: Some(keystore), network: peer.network_service().clone(), - signed_commitment_sender, - beefy_best_block_sender, + links: beefy_voter_links.unwrap(), min_block_delta, prometheus_registry: None, protocol_name: BEEFY_PROTOCOL_NAME.into(), @@ -382,11 +401,11 @@ pub(crate) fn get_beefy_streams( let mut best_block_streams = Vec::new(); let mut signed_commitment_streams = Vec::new(); for peer_id in 0..peers.len() { - let beefy_link_half = - net.peer(peer_id).data.beefy_link_half.lock().as_ref().unwrap().clone(); - let BeefyLinkHalf { signed_commitment_stream, beefy_best_block_stream } = beefy_link_half; - best_block_streams.push(beefy_best_block_stream.subscribe()); - signed_commitment_streams.push(signed_commitment_stream.subscribe()); + let beefy_rpc_links = net.peer(peer_id).data.beefy_rpc_links.lock().clone().unwrap(); + let BeefyRPCLinks { from_voter_justif_stream, from_voter_best_beefy_stream } = + beefy_rpc_links; + best_block_streams.push(from_voter_best_beefy_stream.subscribe()); + signed_commitment_streams.push(from_voter_justif_stream.subscribe()); } (best_block_streams, signed_commitment_streams) } @@ -670,3 +689,123 @@ fn correct_beefy_payload() { wait_for_best_beefy_blocks(best_blocks, &net, &mut runtime, &[11]); wait_for_beefy_signed_commitments(signed_commitments, &net, &mut runtime, &[11]); } + +#[test] +fn beefy_importing_blocks() { + use futures::{executor::block_on, future::poll_fn, task::Poll}; + use sc_block_builder::BlockBuilderProvider; + use sc_client_api::BlockBackend; + + sp_tracing::try_init_simple(); + + let mut net = BeefyTestNet::new(2, 0); + + let client = net.peer(0).client().clone(); + let (mut block_import, _, peer_data) = net.make_block_import(client.clone()); + let PeerData { beefy_rpc_links: _, beefy_voter_links } = peer_data; + let justif_stream = beefy_voter_links.lock().take().unwrap().from_block_import_justif_stream; + + let params = |block: Block, justifications: Option| { + let mut import = BlockImportParams::new(BlockOrigin::File, block.header); + import.justifications = justifications; + import.body = Some(block.extrinsics); + import.finalized = true; + import.fork_choice = Some(ForkChoiceStrategy::LongestChain); + import + }; + + let full_client = client.as_client(); + let parent_id = BlockId::Number(0); + let block_id = BlockId::Number(1); + let builder = full_client.new_block_at(&parent_id, Default::default(), false).unwrap(); + let block = builder.build().unwrap().block; + + // Import without justifications. + let mut justif_recv = justif_stream.subscribe(); + assert_eq!( + block_on(block_import.import_block(params(block.clone(), None), HashMap::new())).unwrap(), + ImportResult::Imported(ImportedAux { is_new_best: true, ..Default::default() }), + ); + assert_eq!( + block_on(block_import.import_block(params(block, None), HashMap::new())).unwrap(), + ImportResult::AlreadyInChain + ); + // Verify no justifications present: + { + // none in backend, + assert!(full_client.justifications(&block_id).unwrap().is_none()); + // and none sent to BEEFY worker. + block_on(poll_fn(move |cx| { + assert_eq!(justif_recv.poll_next_unpin(cx), Poll::Pending); + Poll::Ready(()) + })); + } + + // Import with valid justification. + let parent_id = BlockId::Number(1); + let block_num = 2; + let keys = &[BeefyKeyring::Alice, BeefyKeyring::Bob]; + let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); + let proof = crate::justification::tests::new_signed_commitment(block_num, &validator_set, keys); + let versioned_proof: VersionedFinalityProof, Signature> = proof.into(); + let encoded = versioned_proof.encode(); + let justif = Some(Justifications::from((BEEFY_ENGINE_ID, encoded))); + + let builder = full_client.new_block_at(&parent_id, Default::default(), false).unwrap(); + let block = builder.build().unwrap().block; + let mut justif_recv = justif_stream.subscribe(); + assert_eq!( + block_on(block_import.import_block(params(block, justif), HashMap::new())).unwrap(), + ImportResult::Imported(ImportedAux { + bad_justification: false, + is_new_best: true, + ..Default::default() + }), + ); + // Verify justification successfully imported: + { + // available in backend, + assert!(full_client.justifications(&BlockId::Number(block_num)).unwrap().is_some()); + // and also sent to BEEFY worker. + block_on(poll_fn(move |cx| { + match justif_recv.poll_next_unpin(cx) { + Poll::Ready(Some(_justification)) => (), + v => panic!("unexpected value: {:?}", v), + } + Poll::Ready(()) + })); + } + + // Import with invalid justification (incorrect validator set). + let parent_id = BlockId::Number(2); + let block_num = 3; + let keys = &[BeefyKeyring::Alice]; + let validator_set = ValidatorSet::new(make_beefy_ids(keys), 1).unwrap(); + let proof = crate::justification::tests::new_signed_commitment(block_num, &validator_set, keys); + let versioned_proof: VersionedFinalityProof, Signature> = proof.into(); + let encoded = versioned_proof.encode(); + let justif = Some(Justifications::from((BEEFY_ENGINE_ID, encoded))); + + let builder = full_client.new_block_at(&parent_id, Default::default(), false).unwrap(); + let block = builder.build().unwrap().block; + let mut justif_recv = justif_stream.subscribe(); + assert_eq!( + block_on(block_import.import_block(params(block, justif), HashMap::new())).unwrap(), + ImportResult::Imported(ImportedAux { + // Still `false` because we don't want to fail import on bad BEEFY justifications. + bad_justification: false, + is_new_best: true, + ..Default::default() + }), + ); + // Verify bad justifications was not imported: + { + // none in backend, + assert!(full_client.justifications(&block_id).unwrap().is_none()); + // and none sent to BEEFY worker. + block_on(poll_fn(move |cx| { + assert_eq!(justif_recv.poll_next_unpin(cx), Poll::Pending); + Poll::Ready(()) + })); + } +} diff --git a/client/beefy/src/worker.rs b/client/beefy/src/worker.rs index dccf7ba7504dd..3bff0822ebdb4 100644 --- a/client/beefy/src/worker.rs +++ b/client/beefy/src/worker.rs @@ -17,11 +17,10 @@ // along with this program. If not, see . use std::{ - collections::{BTreeMap, BTreeSet}, + collections::{BTreeMap, BTreeSet, VecDeque}, fmt::Debug, marker::PhantomData, sync::Arc, - time::Duration, }; use codec::{Codec, Decode, Encode}; @@ -48,57 +47,174 @@ use beefy_primitives::{ }; use crate::{ - error, + error::Error, gossip::{topic, GossipValidator}, + justification::BeefySignedCommitment, keystore::BeefyKeystore, metric_inc, metric_set, metrics::Metrics, - notification::{BeefyBestBlockSender, BeefySignedCommitmentSender}, round::Rounds, - Client, + BeefyVoterLinks, Client, }; +enum RoundAction { + Drop, + Process, + Enqueue, +} + +/// Responsible for the voting strategy. +/// It chooses which incoming votes to accept and which votes to generate. +struct VoterOracle { + /// Queue of known sessions. Keeps track of voting rounds (block numbers) within each session. + /// + /// There are three voter states coresponding to three queue states: + /// 1. voter uninitialized: queue empty, + /// 2. up-to-date - all mandatory blocks leading up to current GRANDPA finalized: + /// queue has ONE element, the 'current session' where `mandatory_done == true`, + /// 3. lagging behind GRANDPA: queue has [1, N] elements, where all `mandatory_done == false`. + /// In this state, everytime a session gets its mandatory block BEEFY finalized, it's + /// popped off the queue, eventually getting to state `2. up-to-date`. + sessions: VecDeque>, + /// Min delta in block numbers between two blocks, BEEFY should vote on. + min_block_delta: u32, +} + +impl VoterOracle { + pub fn new(min_block_delta: u32) -> Self { + Self { + sessions: VecDeque::new(), + // Always target at least one block better than current best beefy. + min_block_delta: min_block_delta.max(1), + } + } + + /// Return mutable reference to rounds pertaining to first session in the queue. + /// Voting will always happen at the head of the queue. + pub fn rounds_mut(&mut self) -> Option<&mut Rounds> { + self.sessions.front_mut() + } + + /// Add new observed session to the Oracle. + pub fn add_session(&mut self, rounds: Rounds) { + self.sessions.push_back(rounds); + self.try_prune(); + } + + /// Prune the queue to keep the Oracle in one of the expected three states. + /// + /// Call this function on each BEEFY finality, + /// or at the very least on each BEEFY mandatory block finality. + pub fn try_prune(&mut self) { + if self.sessions.len() > 1 { + // when there's multiple sessions, only keep the `!mandatory_done()` ones. + self.sessions.retain(|s| !s.mandatory_done()) + } + } + + /// Return `(A, B)` tuple representing inclusive [A, B] interval of votes to accept. + pub fn accepted_interval( + &self, + best_grandpa: NumberFor, + ) -> Result<(NumberFor, NumberFor), Error> { + let rounds = self.sessions.front().ok_or(Error::UninitSession)?; + + if rounds.mandatory_done() { + // There's only one session active and its mandatory is done. + // Accept any GRANDPA finalized vote. + Ok((rounds.session_start(), best_grandpa.into())) + } else { + // There's at least one session with mandatory not done. + // Only accept votes for the mandatory block in the front of queue. + Ok((rounds.session_start(), rounds.session_start())) + } + } + + /// Utility function to quickly decide what to do for each round. + pub fn triage_round( + &self, + round: NumberFor, + best_grandpa: NumberFor, + ) -> Result { + let (start, end) = self.accepted_interval(best_grandpa)?; + if start <= round && round <= end { + Ok(RoundAction::Process) + } else if round > end { + Ok(RoundAction::Enqueue) + } else { + Ok(RoundAction::Drop) + } + } + + /// Return `Some(number)` if we should be voting on block `number`, + /// return `None` if there is no block we should vote on. + pub fn voting_target( + &self, + best_beefy: Option>, + best_grandpa: NumberFor, + ) -> Option> { + let rounds = if let Some(r) = self.sessions.front() { + r + } else { + debug!(target: "beefy", "🥩 No voting round started"); + return None + }; + + // `target` is guaranteed > `best_beefy` since `min_block_delta` is at least `1`. + let target = + vote_target(best_grandpa, best_beefy, rounds.session_start(), self.min_block_delta); + trace!( + target: "beefy", + "🥩 best beefy: #{:?}, best finalized: #{:?}, current_vote_target: {:?}", + best_beefy, + best_grandpa, + target + ); + target + } +} + pub(crate) struct WorkerParams { pub client: Arc, pub backend: Arc, pub runtime: Arc, + pub sync_oracle: SO, pub key_store: BeefyKeystore, - pub signed_commitment_sender: BeefySignedCommitmentSender, - pub beefy_best_block_sender: BeefyBestBlockSender, pub gossip_engine: GossipEngine, pub gossip_validator: Arc>, - pub min_block_delta: u32, + pub links: BeefyVoterLinks, pub metrics: Option, - pub sync_oracle: SO, + pub min_block_delta: u32, } /// A BEEFY worker plays the BEEFY protocol pub(crate) struct BeefyWorker { + // utilities client: Arc, backend: Arc, runtime: Arc, + sync_oracle: SO, key_store: BeefyKeystore, - signed_commitment_sender: BeefySignedCommitmentSender, gossip_engine: GossipEngine, gossip_validator: Arc>, - /// Min delta in block numbers between two blocks, BEEFY should vote on - min_block_delta: u32, + + // channels + /// Links between the block importer, the background voter and the RPC layer. + links: BeefyVoterLinks, + + // voter state + /// BEEFY client metrics. metrics: Option, - rounds: Option>, - /// Buffer holding votes for blocks that the client hasn't seen finality for. - pending_votes: BTreeMap, Vec, AuthorityId, Signature>>>, - /// Best block we received a GRANDPA notification for + /// Best block we received a GRANDPA finality for. best_grandpa_block_header: ::Header, - /// Best block a BEEFY voting round has been concluded for + /// Best block a BEEFY voting round has been concluded for. best_beefy_block: Option>, - /// Used to keep RPC worker up to date on latest/best beefy - beefy_best_block_sender: BeefyBestBlockSender, - /// Validator set id for the last signed commitment - last_signed_id: u64, - /// Handle to the sync oracle - sync_oracle: SO, - // keep rustc happy - _backend: PhantomData, + /// Buffer holding votes for future processing. + pending_votes: BTreeMap, Vec, AuthorityId, Signature>>>, + /// Buffer holding justifications for future processing. + pending_justifications: BTreeMap, Vec>>, + /// Chooses which incoming votes to accept and which votes to generate. + voting_oracle: VoterOracle, } impl BeefyWorker @@ -122,13 +238,12 @@ where backend, runtime, key_store, - signed_commitment_sender, - beefy_best_block_sender, + sync_oracle, gossip_engine, gossip_validator, - min_block_delta, + links, metrics, - sync_oracle, + min_block_delta, } = worker_params; let last_finalized_header = client @@ -139,53 +254,29 @@ where client: client.clone(), backend, runtime, + sync_oracle, key_store, - signed_commitment_sender, gossip_engine, gossip_validator, - // always target at least one block better than current best beefy - min_block_delta: min_block_delta.max(1), + links, metrics, - rounds: None, - pending_votes: BTreeMap::new(), best_grandpa_block_header: last_finalized_header, best_beefy_block: None, - last_signed_id: 0, - beefy_best_block_sender, - sync_oracle, - _backend: PhantomData, + pending_votes: BTreeMap::new(), + pending_justifications: BTreeMap::new(), + voting_oracle: VoterOracle::new(min_block_delta), } } - /// Return `Some(number)` if we should be voting on block `number` now, - /// return `None` if there is no block we should vote on now. - fn current_vote_target(&self) -> Option> { - let rounds = if let Some(r) = &self.rounds { - r - } else { - debug!(target: "beefy", "🥩 No voting round started"); - return None - }; - - let best_finalized = *self.best_grandpa_block_header.number(); - // `target` is guaranteed > `best_beefy` since `min_block_delta` is at least `1`. - let target = vote_target( - best_finalized, - self.best_beefy_block, - *rounds.session_start(), - self.min_block_delta, - ); - trace!( - target: "beefy", - "🥩 best beefy: #{:?}, best finalized: #{:?}, current_vote_target: {:?}", - self.best_beefy_block, - best_finalized, - target - ); - if let Some(target) = &target { - metric_set!(self, beefy_should_vote_on, target); - } - target + /// Simple wrapper that gets MMR root from header digests or from client state. + fn get_mmr_root_digest(&self, header: &B::Header) -> Option { + find_mmr_root_digest::(header).or_else(|| { + self.runtime + .runtime_api() + .mmr_root(&BlockId::hash(header.hash())) + .ok() + .and_then(|r| r.ok()) + }) } /// Verify `active` validator set for `block` against the key store @@ -200,7 +291,7 @@ where &self, block: &NumberFor, active: &ValidatorSet, - ) -> Result<(), error::Error> { + ) -> Result<(), Error> { let active: BTreeSet<&AuthorityId> = active.validators().iter().collect(); let public_keys = self.key_store.public_keys()?; @@ -209,121 +300,100 @@ where if store.intersection(&active).count() == 0 { let msg = "no authority public key found in store".to_string(); debug!(target: "beefy", "🥩 for block {:?} {}", block, msg); - Err(error::Error::Keystore(msg)) + Err(Error::Keystore(msg)) } else { Ok(()) } } - /// Set best BEEFY block to `block_num`. - /// - /// Also sends/updates the best BEEFY block hash to the RPC worker. - fn set_best_beefy_block(&mut self, block_num: NumberFor) { - if Some(block_num) > self.best_beefy_block { - // Try to get block hash ourselves. - let block_hash = match self.client.hash(block_num) { - Ok(h) => h, - Err(e) => { - error!(target: "beefy", "🥩 Failed to get hash for block number {}: {}", - block_num, e); - None - }, - }; - // Update RPC worker with new best BEEFY block hash. - block_hash.map(|hash| { - self.beefy_best_block_sender - .notify(|| Ok::<_, ()>(hash)) - .expect("forwards closure result; the closure always returns Ok; qed.") - }); - // Set new best BEEFY block number. - self.best_beefy_block = Some(block_num); - metric_set!(self, beefy_best_block, block_num); - } else { - debug!(target: "beefy", "🥩 Can't set best beefy to older: {}", block_num); - } - } - /// Handle session changes by starting new voting round for mandatory blocks. fn init_session_at( &mut self, - active: ValidatorSet, + validator_set: ValidatorSet, new_session_start: NumberFor, ) { - debug!(target: "beefy", "🥩 New active validator set: {:?}", active); - metric_set!(self, beefy_validator_set_id, active.id()); - // BEEFY should produce a signed commitment for each session - if active.id() != self.last_signed_id + 1 && - active.id() != GENESIS_AUTHORITY_SET_ID && - self.last_signed_id != 0 - { - debug!( - target: "beefy", "🥩 Detected skipped session: active-id {:?}, last-signed-id {:?}", - active.id(), - self.last_signed_id, - ); - metric_inc!(self, beefy_skipped_sessions); + debug!(target: "beefy", "🥩 New active validator set: {:?}", validator_set); + metric_set!(self, beefy_validator_set_id, validator_set.id()); + + // BEEFY should produce the mandatory block of each session. + if let Some(active_session) = self.voting_oracle.rounds_mut() { + if !active_session.mandatory_done() { + debug!( + target: "beefy", "🥩 New session {} while active session {} is still lagging.", + validator_set.id(), + active_session.validator_set_id(), + ); + metric_inc!(self, beefy_lagging_sessions); + } } if log_enabled!(target: "beefy", log::Level::Debug) { // verify the new validator set - only do it if we're also logging the warning - let _ = self.verify_validator_set(&new_session_start, &active); + let _ = self.verify_validator_set(&new_session_start, &validator_set); } - let id = active.id(); - self.rounds = Some(Rounds::new(new_session_start, active)); + let id = validator_set.id(); + self.voting_oracle.add_session(Rounds::new(new_session_start, validator_set)); info!(target: "beefy", "🥩 New Rounds for validator set id: {:?} with session_start {:?}", id, new_session_start); } fn handle_finality_notification(&mut self, notification: &FinalityNotification) { debug!(target: "beefy", "🥩 Finality notification: {:?}", notification); - let number = *notification.header.number(); - - // On start-up ignore old finality notifications that we're not interested in. - if number <= *self.best_grandpa_block_header.number() { - debug!(target: "beefy", "🥩 Got unexpected finality for old block #{:?}", number); - return - } + let header = ¬ification.header; - // update best GRANDPA finalized block we have seen - self.best_grandpa_block_header = notification.header.clone(); + if *header.number() > *self.best_grandpa_block_header.number() { + // update best GRANDPA finalized block we have seen + self.best_grandpa_block_header = header.clone(); - self.handle_finality(¬ification.header); - } - - fn handle_finality(&mut self, header: &B::Header) { - // Check for and handle potential new session. - if let Some(new_validator_set) = find_authorities_change::(header) { - self.init_session_at(new_validator_set, *header.number()); + // Check for and enqueue potential new session. + if let Some(new_validator_set) = find_authorities_change::(header) { + self.init_session_at(new_validator_set, *header.number()); + // TODO: when adding SYNC protocol, fire up a request for justification for this + // mandatory block here. + } } + } - // Handle any pending votes for now finalized blocks. - self.check_pending_votes(); - - // Vote if there's now a new vote target. - if let Some(target_number) = self.current_vote_target() { - self.do_vote(target_number); - } + /// Based on [VoterOracle] this vote is either processed here or enqueued for later. + fn triage_incoming_vote( + &mut self, + vote: VoteMessage, AuthorityId, Signature>, + ) -> Result<(), Error> { + let block_num = vote.commitment.block_number; + let best_grandpa = *self.best_grandpa_block_header.number(); + match self.voting_oracle.triage_round(block_num, best_grandpa)? { + RoundAction::Process => self.handle_vote( + (vote.commitment.payload, vote.commitment.block_number), + (vote.id, vote.signature), + false, + )?, + RoundAction::Enqueue => { + debug!(target: "beefy", "🥩 Buffer vote for round: {:?}.", block_num); + self.pending_votes.entry(block_num).or_default().push(vote) + }, + RoundAction::Drop => (), + }; + Ok(()) } - // Handles all buffered votes for now finalized blocks. - fn check_pending_votes(&mut self) { - let not_finalized = self.best_grandpa_block_header.number().saturating_add(1u32.into()); - let still_pending = self.pending_votes.split_off(¬_finalized); - let votes_to_handle = std::mem::replace(&mut self.pending_votes, still_pending); - for (num, votes) in votes_to_handle.into_iter() { - if Some(num) > self.best_beefy_block { - debug!(target: "beefy", "🥩 Handling buffered votes for now GRANDPA finalized block: {:?}.", num); - for v in votes.into_iter() { - self.handle_vote( - (v.commitment.payload, v.commitment.block_number), - (v.id, v.signature), - false, - ); - } - } else { - debug!(target: "beefy", "🥩 Dropping outdated buffered votes for now BEEFY finalized block: {:?}.", num); - } - } + /// Based on [VoterOracle] this justification is either processed here or enqueued for later. + /// + /// Expects `justification` to be valid. + fn triage_incoming_justif( + &mut self, + justification: BeefySignedCommitment, + ) -> Result<(), Error> { + let block_num = justification.commitment.block_number; + let best_grandpa = *self.best_grandpa_block_header.number(); + match self.voting_oracle.triage_round(block_num, best_grandpa)? { + RoundAction::Process => self.finalize(justification), + RoundAction::Enqueue => { + debug!(target: "beefy", "🥩 Buffer justification for round: {:?}.", block_num); + self.pending_justifications.entry(block_num).or_default().push(justification) + }, + RoundAction::Drop => (), + }; + Ok(()) } fn handle_vote( @@ -331,28 +401,20 @@ where round: (Payload, NumberFor), vote: (AuthorityId, Signature), self_vote: bool, - ) { + ) -> Result<(), Error> { self.gossip_validator.note_round(round.1); - let rounds = if let Some(rounds) = self.rounds.as_mut() { - rounds - } else { - debug!(target: "beefy", "🥩 Missing validator set - can't handle vote {:?}", vote); - return - }; + let rounds = self.voting_oracle.rounds_mut().ok_or(Error::UninitSession)?; if rounds.add_vote(&round, vote, self_vote) { if let Some(signatures) = rounds.try_conclude(&round) { self.gossip_validator.conclude_round(round.1); - // id is stored for skipped session metric calculation - self.last_signed_id = rounds.validator_set_id(); - let block_num = round.1; let commitment = Commitment { payload: round.0, block_number: block_num, - validator_set_id: self.last_signed_id, + validator_set_id: rounds.validator_set_id(), }; let signed_commitment = SignedCommitment { commitment, signatures }; @@ -370,24 +432,115 @@ where ) { debug!(target: "beefy", "🥩 Error {:?} on appending justification: {:?}", e, signed_commitment); } - self.signed_commitment_sender - .notify(|| Ok::<_, ()>(signed_commitment)) - .expect("forwards closure result; the closure always returns Ok; qed."); - self.set_best_beefy_block(block_num); + // We created the `signed_commitment` and know to be valid. + self.finalize(signed_commitment); + } + } + Ok(()) + } + + /// Provide BEEFY finality for block based on `signed_commitment`: + /// 1. Prune irrelevant past sessions from the oracle, + /// 2. Set BEEFY best block, + /// 3. Send best block hash and `signed_commitment` to RPC worker. + /// + /// Expects `signed commitment` to be valid. + fn finalize(&mut self, signed_commitment: BeefySignedCommitment) { + // Prune any now "finalized" sessions from queue. + self.voting_oracle.try_prune(); - // Vote if there's now a new vote target. - if let Some(target_number) = self.current_vote_target() { - self.do_vote(target_number); + let block_num = signed_commitment.commitment.block_number; + if Some(block_num) > self.best_beefy_block { + // Set new best BEEFY block number. + self.best_beefy_block = Some(block_num); + metric_set!(self, beefy_best_block, block_num); + + self.client.hash(block_num).ok().flatten().map(|hash| { + self.links + .to_rpc_best_block_sender + .notify(|| Ok::<_, ()>(hash)) + .expect("forwards closure result; the closure always returns Ok; qed.") + }); + + self.links + .to_rpc_justif_sender + .notify(|| Ok::<_, ()>(signed_commitment)) + .expect("forwards closure result; the closure always returns Ok; qed."); + } else { + debug!(target: "beefy", "🥩 Can't set best beefy to older: {}", block_num); + } + } + + /// Handle previously buffered justifications and votes that now land in the voting interval. + fn try_pending_justif_and_votes(&mut self) -> Result<(), Error> { + let best_grandpa = *self.best_grandpa_block_header.number(); + let _ph = PhantomData::::default(); + + fn to_process_for( + pending: &mut BTreeMap, Vec>, + (start, end): (NumberFor, NumberFor), + _: PhantomData, + ) -> BTreeMap, Vec> { + // These are still pending. + let still_pending = pending.split_off(&end.saturating_add(1u32.into())); + // These can be processed. + let to_handle = pending.split_off(&start); + // The rest can be dropped. + *pending = still_pending; + // Return ones to process. + to_handle + } + + // Process pending justifications. + let interval = self.voting_oracle.accepted_interval(best_grandpa)?; + if !self.pending_justifications.is_empty() { + let justifs_to_handle = to_process_for(&mut self.pending_justifications, interval, _ph); + for (num, justifications) in justifs_to_handle.into_iter() { + debug!(target: "beefy", "🥩 Handle buffered justifications for: {:?}.", num); + for justif in justifications.into_iter() { + self.finalize(justif); } } } + + // Process pending votes. + let interval = self.voting_oracle.accepted_interval(best_grandpa)?; + if !self.pending_votes.is_empty() { + let votes_to_handle = to_process_for(&mut self.pending_votes, interval, _ph); + for (num, votes) in votes_to_handle.into_iter() { + debug!(target: "beefy", "🥩 Handle buffered votes for: {:?}.", num); + for v in votes.into_iter() { + if let Err(err) = self.handle_vote( + (v.commitment.payload, v.commitment.block_number), + (v.id, v.signature), + false, + ) { + error!(target: "beefy", "🥩 Error handling buffered vote: {}", err); + }; + } + } + } + Ok(()) + } + + /// Decide if should vote, then vote.. or don't.. + fn try_to_vote(&mut self) -> Result<(), Error> { + // Vote if there's now a new vote target. + if let Some(target) = self + .voting_oracle + .voting_target(self.best_beefy_block, *self.best_grandpa_block_header.number()) + { + metric_set!(self, beefy_should_vote_on, target); + self.do_vote(target)?; + } + Ok(()) } /// Create and gossip Signed Commitment for block number `target_number`. /// /// Also handle this self vote by calling `self.handle_vote()` for it. - fn do_vote(&mut self, target_number: NumberFor) { + fn do_vote(&mut self, target_number: NumberFor) -> Result<(), Error> { debug!(target: "beefy", "🥩 Try voting on {}", target_number); // Most of the time we get here, `target` is actually `best_grandpa`, @@ -395,18 +548,13 @@ where let target_header = if target_number == *self.best_grandpa_block_header.number() { self.best_grandpa_block_header.clone() } else { - match self.client.expect_header(BlockId::Number(target_number)) { - Ok(h) => h, - Err(err) => { - debug!( - target: "beefy", - "🥩 Could not get header for block #{:?} (error: {:?}), skipping vote..", - target_number, - err - ); - return - }, - } + self.client.expect_header(BlockId::Number(target_number)).map_err(|err| { + let err_msg = format!( + "Couldn't get header for block #{:?} (error: {:?}), skipping vote..", + target_number, err + ); + Error::Backend(err_msg) + })? }; let target_hash = target_header.hash(); @@ -414,26 +562,23 @@ where hash } else { warn!(target: "beefy", "🥩 No MMR root digest found for: {:?}", target_hash); - return + return Ok(()) }; let payload = Payload::new(known_payload_ids::MMR_ROOT_ID, mmr_root.encode()); - let (validators, validator_set_id) = if let Some(rounds) = &self.rounds { - if !rounds.should_self_vote(&(payload.clone(), target_number)) { - debug!(target: "beefy", "🥩 Don't double vote for block number: {:?}", target_number); - return - } - (rounds.validators(), rounds.validator_set_id()) - } else { - debug!(target: "beefy", "🥩 Missing validator set - can't vote for: {:?}", target_hash); - return - }; + let rounds = self.voting_oracle.rounds_mut().ok_or(Error::UninitSession)?; + if !rounds.should_self_vote(&(payload.clone(), target_number)) { + debug!(target: "beefy", "🥩 Don't double vote for block number: {:?}", target_number); + return Ok(()) + } + let (validators, validator_set_id) = (rounds.validators(), rounds.validator_set_id()); + let authority_id = if let Some(id) = self.key_store.authority_id(validators) { debug!(target: "beefy", "🥩 Local authority id: {:?}", id); id } else { debug!(target: "beefy", "🥩 Missing validator id - can't vote for: {:?}", target_hash); - return + return Ok(()) }; let commitment = Commitment { payload, block_number: target_number, validator_set_id }; @@ -443,7 +588,7 @@ where Ok(sig) => sig, Err(err) => { warn!(target: "beefy", "🥩 Error signing commitment: {:?}", err); - return + return Ok(()) }, }; @@ -462,13 +607,17 @@ where debug!(target: "beefy", "🥩 Sent vote message: {:?}", message); - self.handle_vote( + if let Err(err) = self.handle_vote( (message.commitment.payload, message.commitment.block_number), (message.id, message.signature), true, - ); + ) { + error!(target: "beefy", "🥩 Error handling self vote: {}", err); + } self.gossip_engine.gossip_message(topic::(), encoded_message, false); + + Ok(()) } /// Wait for BEEFY runtime pallet to be available. @@ -494,6 +643,9 @@ where // Once we'll implement 'initial sync' (catch-up), the worker will be able to // start voting right away. self.handle_finality_notification(¬if); + if let Err(err) = self.try_to_vote() { + debug!(target: "beefy", "🥩 {}", err); + } break } else { trace!(target: "beefy", "🥩 Finality notification: {:?}", notif); @@ -529,15 +681,14 @@ where }) .fuse(), ); + let mut block_import_justif = self.links.from_block_import_justif_stream.subscribe().fuse(); loop { - while self.sync_oracle.is_major_syncing() { - debug!(target: "beefy", "Waiting for major sync to complete..."); - futures_timer::Delay::new(Duration::from_secs(5)).await; - } - let mut gossip_engine = &mut self.gossip_engine; - futures::select! { + // Wait for, and handle external events. + // The branches below only change 'state', actual voting happen afterwards, + // based on the new resulting 'state'. + futures::select_biased! { notification = finality_notifications.next() => { if let Some(notification) = notification { self.handle_finality_notification(¬ification); @@ -545,24 +696,24 @@ where return; } }, + // TODO: when adding SYNC protocol, join the on-demand justifications stream to + // this one, and handle them both here. + justif = block_import_justif.next() => { + if let Some(justif) = justif { + // Block import justifications have already been verified to be valid + // by `BeefyBlockImport`. + if let Err(err) = self.triage_incoming_justif(justif) { + debug!(target: "beefy", "🥩 {}", err); + } + } else { + return; + } + }, vote = votes.next() => { if let Some(vote) = vote { - let block_num = vote.commitment.block_number; - if block_num > *self.best_grandpa_block_header.number() { - // Only handle votes for blocks we _know_ have been finalized. - // Buffer vote to be handled later. - debug!( - target: "beefy", - "🥩 Buffering vote for not (yet) finalized block: {:?}.", - block_num - ); - self.pending_votes.entry(block_num).or_default().push(vote); - } else { - self.handle_vote( - (vote.commitment.payload, vote.commitment.block_number), - (vote.id, vote.signature), - false - ); + // Votes have already been verified to be valid by the gossip validator. + if let Err(err) = self.triage_incoming_vote(vote) { + debug!(target: "beefy", "🥩 {}", err); } } else { return; @@ -573,18 +724,20 @@ where return; } } - } - } - /// Simple wrapper that gets MMR root from header digests or from client state. - fn get_mmr_root_digest(&self, header: &B::Header) -> Option { - find_mmr_root_digest::(header).or_else(|| { - self.runtime - .runtime_api() - .mmr_root(&BlockId::hash(header.hash())) - .ok() - .and_then(|r| r.ok()) - }) + // Don't bother acting on 'state' changes during major sync. + if !self.sync_oracle.is_major_syncing() { + // Handle pending justifications and/or votes for now GRANDPA finalized blocks. + if let Err(err) = self.try_pending_justif_and_votes() { + debug!(target: "beefy", "🥩 {}", err); + } + + // There were external events, 'state' is changed, author a vote if needed/possible. + if let Err(err) = self.try_to_vote() { + debug!(target: "beefy", "🥩 {}", err); + } + } + } } } @@ -684,11 +837,11 @@ pub(crate) mod tests { create_beefy_keystore, get_beefy_streams, make_beefy_ids, two_validators::TestApi, BeefyPeer, BeefyTestNet, BEEFY_PROTOCOL_NAME, }, + BeefyRPCLinks, }; use futures::{executor::block_on, future::poll_fn, task::Poll}; - use crate::tests::BeefyLinkHalf; use sc_client_api::HeaderBackend; use sc_network::NetworkService; use sc_network_test::{PeersFullClient, TestNetFactory}; @@ -705,12 +858,21 @@ pub(crate) mod tests { ) -> BeefyWorker>> { let keystore = create_beefy_keystore(*key); - let (signed_commitment_sender, signed_commitment_stream) = + let (to_rpc_justif_sender, from_voter_justif_stream) = BeefySignedCommitmentStream::::channel(); - let (beefy_best_block_sender, beefy_best_block_stream) = + let (to_rpc_best_block_sender, from_voter_best_beefy_stream) = BeefyBestBlockStream::::channel(); - let beefy_link_half = BeefyLinkHalf { signed_commitment_stream, beefy_best_block_stream }; - *peer.data.beefy_link_half.lock() = Some(beefy_link_half); + let (_, from_block_import_justif_stream) = BeefySignedCommitmentStream::::channel(); + + let beefy_rpc_links = + BeefyRPCLinks { from_voter_justif_stream, from_voter_best_beefy_stream }; + *peer.data.beefy_rpc_links.lock() = Some(beefy_rpc_links); + + let links = BeefyVoterLinks { + from_block_import_justif_stream, + to_rpc_justif_sender, + to_rpc_best_block_sender, + }; let api = Arc::new(TestApi {}); let network = peer.network_service().clone(); @@ -723,8 +885,7 @@ pub(crate) mod tests { backend: peer.client().as_backend(), runtime: api, key_store: Some(keystore).into(), - signed_commitment_sender, - beefy_best_block_sender, + links, gossip_engine, gossip_validator, min_block_delta, @@ -826,6 +987,107 @@ pub(crate) mod tests { assert_eq!(Some(1072), t); } + #[test] + fn should_vote_target() { + let mut oracle = VoterOracle::::new(1); + + // rounds not initialized -> should vote: `None` + assert_eq!(oracle.voting_target(None, 1), None); + + let keys = &[Keyring::Alice]; + let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); + + oracle.add_session(Rounds::new(1, validator_set.clone())); + + // under min delta + oracle.min_block_delta = 4; + assert_eq!(oracle.voting_target(Some(1), 1), None); + assert_eq!(oracle.voting_target(Some(2), 5), None); + + // vote on min delta + assert_eq!(oracle.voting_target(Some(4), 9), Some(8)); + oracle.min_block_delta = 8; + assert_eq!(oracle.voting_target(Some(10), 18), Some(18)); + + // vote on power of two + oracle.min_block_delta = 1; + assert_eq!(oracle.voting_target(Some(1000), 1008), Some(1004)); + assert_eq!(oracle.voting_target(Some(1000), 1016), Some(1008)); + + // nothing new to vote on + assert_eq!(oracle.voting_target(Some(1000), 1000), None); + + // vote on mandatory + oracle.sessions.clear(); + oracle.add_session(Rounds::new(1000, validator_set.clone())); + assert_eq!(oracle.voting_target(None, 1008), Some(1000)); + oracle.sessions.clear(); + oracle.add_session(Rounds::new(1001, validator_set.clone())); + assert_eq!(oracle.voting_target(Some(1000), 1008), Some(1001)); + } + + #[test] + fn test_oracle_accepted_interval() { + let keys = &[Keyring::Alice]; + let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); + + let mut oracle = VoterOracle::::new(1); + + // rounds not initialized -> should accept votes: `None` + assert!(oracle.accepted_interval(1).is_err()); + + let session_one = 1; + oracle.add_session(Rounds::new(session_one, validator_set.clone())); + // mandatory not done, only accept mandatory + for i in 0..15 { + assert_eq!(oracle.accepted_interval(i), Ok((session_one, session_one))); + } + + // add more sessions, nothing changes + let session_two = 11; + let session_three = 21; + oracle.add_session(Rounds::new(session_two, validator_set.clone())); + oracle.add_session(Rounds::new(session_three, validator_set.clone())); + // mandatory not done, should accept mandatory for session_one + for i in session_three..session_three + 15 { + assert_eq!(oracle.accepted_interval(i), Ok((session_one, session_one))); + } + + // simulate finish mandatory for session one, prune oracle + oracle.sessions.front_mut().unwrap().test_set_mandatory_done(true); + oracle.try_prune(); + // session_one pruned, should accept mandatory for session_two + for i in session_three..session_three + 15 { + assert_eq!(oracle.accepted_interval(i), Ok((session_two, session_two))); + } + + // simulate finish mandatory for session two, prune oracle + oracle.sessions.front_mut().unwrap().test_set_mandatory_done(true); + oracle.try_prune(); + // session_two pruned, should accept mandatory for session_three + for i in session_three..session_three + 15 { + assert_eq!(oracle.accepted_interval(i), Ok((session_three, session_three))); + } + + // simulate finish mandatory for session three + oracle.sessions.front_mut().unwrap().test_set_mandatory_done(true); + // verify all other blocks in this session are now open to voting + for i in session_three..session_three + 15 { + assert_eq!(oracle.accepted_interval(i), Ok((session_three, i))); + } + // pruning does nothing in this case + oracle.try_prune(); + for i in session_three..session_three + 15 { + assert_eq!(oracle.accepted_interval(i), Ok((session_three, i))); + } + + // adding new session automatically prunes "finalized" previous session + let session_four = 31; + oracle.add_session(Rounds::new(session_four, validator_set.clone())); + assert_eq!(oracle.sessions.front().unwrap().session_start(), session_four); + assert_eq!(oracle.accepted_interval(session_four + 10), Ok((session_four, session_four))); + } + #[test] fn extract_authorities_change_digest() { let mut header = Header::new( @@ -876,69 +1138,6 @@ pub(crate) mod tests { assert_eq!(extracted, Some(mmr_root_hash)); } - #[test] - fn should_vote_target() { - let keys = &[Keyring::Alice]; - let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); - let mut net = BeefyTestNet::new(1, 0); - let mut worker = create_beefy_worker(&net.peer(0), &keys[0], 1); - - // rounds not initialized -> should vote: `None` - assert_eq!(worker.current_vote_target(), None); - - let set_up = |worker: &mut BeefyWorker< - Block, - Backend, - PeersFullClient, - TestApi, - Arc>, - >, - best_grandpa: u64, - best_beefy: Option, - session_start: u64, - min_delta: u32| { - let grandpa_header = Header::new( - best_grandpa, - Default::default(), - Default::default(), - Default::default(), - Default::default(), - ); - worker.best_grandpa_block_header = grandpa_header; - worker.best_beefy_block = best_beefy; - worker.min_block_delta = min_delta; - worker.rounds = Some(Rounds::new(session_start, validator_set.clone())); - }; - - // under min delta - set_up(&mut worker, 1, Some(1), 1, 4); - assert_eq!(worker.current_vote_target(), None); - set_up(&mut worker, 5, Some(2), 1, 4); - assert_eq!(worker.current_vote_target(), None); - - // vote on min delta - set_up(&mut worker, 9, Some(4), 1, 4); - assert_eq!(worker.current_vote_target(), Some(8)); - set_up(&mut worker, 18, Some(10), 1, 8); - assert_eq!(worker.current_vote_target(), Some(18)); - - // vote on power of two - set_up(&mut worker, 1008, Some(1000), 1, 1); - assert_eq!(worker.current_vote_target(), Some(1004)); - set_up(&mut worker, 1016, Some(1000), 1, 2); - assert_eq!(worker.current_vote_target(), Some(1008)); - - // nothing new to vote on - set_up(&mut worker, 1000, Some(1000), 1, 1); - assert_eq!(worker.current_vote_target(), None); - - // vote on mandatory - set_up(&mut worker, 1008, None, 1000, 8); - assert_eq!(worker.current_vote_target(), Some(1000)); - set_up(&mut worker, 1008, Some(1000), 1001, 8); - assert_eq!(worker.current_vote_target(), Some(1001)); - } - #[test] fn keystore_vs_validator_set() { let keys = &[Keyring::Alice]; @@ -953,39 +1152,57 @@ pub(crate) mod tests { let keys = &[Keyring::Bob]; let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); let err_msg = "no authority public key found in store".to_string(); - let expected = Err(error::Error::Keystore(err_msg)); + let expected = Err(Error::Keystore(err_msg)); assert_eq!(worker.verify_validator_set(&1, &validator_set), expected); // worker has no keystore worker.key_store = None.into(); - let expected_err = Err(error::Error::Keystore("no Keystore".into())); + let expected_err = Err(Error::Keystore("no Keystore".into())); assert_eq!(worker.verify_validator_set(&1, &validator_set), expected_err); } #[test] - fn setting_best_beefy_block() { + fn test_finalize() { let keys = &[Keyring::Alice]; let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); let mut net = BeefyTestNet::new(1, 0); let mut worker = create_beefy_worker(&net.peer(0), &keys[0], 1); - let (mut best_block_streams, _) = get_beefy_streams(&mut net, keys); + let (mut best_block_streams, mut signed_commitments) = get_beefy_streams(&mut net, keys); let mut best_block_stream = best_block_streams.drain(..).next().unwrap(); + let mut signed_commitments = signed_commitments.drain(..).next().unwrap(); - // no 'best beefy block' + let create_signed_commitment = |block_num: NumberFor| { + let commitment = Commitment { + payload: Payload::new(known_payload_ids::MMR_ROOT_ID, vec![]), + block_number: block_num, + validator_set_id: validator_set.id(), + }; + SignedCommitment { commitment, signatures: vec![None] } + }; + + // no 'best beefy block' or signed commitments assert_eq!(worker.best_beefy_block, None); block_on(poll_fn(move |cx| { assert_eq!(best_block_stream.poll_next_unpin(cx), Poll::Pending); + assert_eq!(signed_commitments.poll_next_unpin(cx), Poll::Pending); Poll::Ready(()) })); // unknown hash for block #1 - let (mut best_block_streams, _) = get_beefy_streams(&mut net, keys); + let (mut best_block_streams, mut signed_commitments) = get_beefy_streams(&mut net, keys); let mut best_block_stream = best_block_streams.drain(..).next().unwrap(); - worker.set_best_beefy_block(1); + let mut signed_commitments = signed_commitments.drain(..).next().unwrap(); + let justif = create_signed_commitment(1); + worker.finalize(justif.clone()); assert_eq!(worker.best_beefy_block, Some(1)); block_on(poll_fn(move |cx| { assert_eq!(best_block_stream.poll_next_unpin(cx), Poll::Pending); + match signed_commitments.poll_next_unpin(cx) { + // expect justification + Poll::Ready(Some(received)) => assert_eq!(received, justif), + v => panic!("unexpected value: {:?}", v), + } Poll::Ready(()) })); @@ -994,7 +1211,8 @@ pub(crate) mod tests { let mut best_block_stream = best_block_streams.drain(..).next().unwrap(); net.generate_blocks(2, 10, &validator_set, false); - worker.set_best_beefy_block(2); + let justif = create_signed_commitment(2); + worker.finalize(justif); assert_eq!(worker.best_beefy_block, Some(2)); block_on(poll_fn(move |cx| { match best_block_stream.poll_next_unpin(cx) { @@ -1010,20 +1228,17 @@ pub(crate) mod tests { } #[test] - fn setting_initial_session() { + fn should_init_session() { let keys = &[Keyring::Alice]; let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); let mut net = BeefyTestNet::new(1, 0); let mut worker = create_beefy_worker(&net.peer(0), &keys[0], 1); - assert!(worker.rounds.is_none()); + assert!(worker.voting_oracle.sessions.is_empty()); - // verify setting the correct validator sets and boundary for genesis session worker.init_session_at(validator_set.clone(), 1); - - let worker_rounds = worker.rounds.as_ref().unwrap(); - assert_eq!(worker_rounds.session_start(), &1); - // in genesis case both current and prev validator sets are the same + let worker_rounds = worker.voting_oracle.rounds_mut().unwrap(); + assert_eq!(worker_rounds.session_start(), 1); assert_eq!(worker_rounds.validators(), validator_set.validators()); assert_eq!(worker_rounds.validator_set_id(), validator_set.id()); @@ -1031,12 +1246,79 @@ pub(crate) mod tests { let keys = &[Keyring::Bob]; let new_validator_set = ValidatorSet::new(make_beefy_ids(keys), 1).unwrap(); - // verify setting the correct validator sets and boundary for non-genesis session worker.init_session_at(new_validator_set.clone(), 11); + // Since mandatory is not done for old rounds, we still get those. + let rounds = worker.voting_oracle.rounds_mut().unwrap(); + assert_eq!(rounds.validator_set_id(), validator_set.id()); + // Let's finalize mandatory. + rounds.test_set_mandatory_done(true); + worker.voting_oracle.try_prune(); + // Now we should get the next round. + let rounds = worker.voting_oracle.rounds_mut().unwrap(); + // Expect new values. + assert_eq!(rounds.session_start(), 11); + assert_eq!(rounds.validators(), new_validator_set.validators()); + assert_eq!(rounds.validator_set_id(), new_validator_set.id()); + } + + #[test] + fn should_triage_votes_and_process_later() { + let keys = &[Keyring::Alice, Keyring::Bob]; + let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); + let mut net = BeefyTestNet::new(1, 0); + let mut worker = create_beefy_worker(&net.peer(0), &keys[0], 1); + + fn new_vote( + block_number: NumberFor, + ) -> VoteMessage, AuthorityId, Signature> { + let commitment = Commitment { + payload: Payload::new(*b"BF", vec![]), + block_number, + validator_set_id: 0, + }; + VoteMessage { + commitment, + id: Keyring::Alice.public(), + signature: Keyring::Alice.sign(b"I am committed"), + } + } + + // best grandpa is 20 + let best_grandpa_header = Header::new( + 20u32.into(), + Default::default(), + Default::default(), + Default::default(), + Digest::default(), + ); - let worker_rounds = worker.rounds.as_ref().unwrap(); - assert_eq!(worker_rounds.session_start(), &11); - assert_eq!(worker_rounds.validators(), new_validator_set.validators()); - assert_eq!(worker_rounds.validator_set_id(), new_validator_set.id()); + worker.voting_oracle.add_session(Rounds::new(10, validator_set.clone())); + worker.best_grandpa_block_header = best_grandpa_header; + + // triage votes for blocks 10..13 + worker.triage_incoming_vote(new_vote(10)).unwrap(); + worker.triage_incoming_vote(new_vote(11)).unwrap(); + worker.triage_incoming_vote(new_vote(12)).unwrap(); + // triage votes for blocks 20..23 + worker.triage_incoming_vote(new_vote(20)).unwrap(); + worker.triage_incoming_vote(new_vote(21)).unwrap(); + worker.triage_incoming_vote(new_vote(22)).unwrap(); + + // vote for 10 should have been handled, while the rest buffered for later processing + let mut votes = worker.pending_votes.values(); + assert_eq!(votes.next().unwrap().first().unwrap().commitment.block_number, 11); + assert_eq!(votes.next().unwrap().first().unwrap().commitment.block_number, 12); + assert_eq!(votes.next().unwrap().first().unwrap().commitment.block_number, 20); + assert_eq!(votes.next().unwrap().first().unwrap().commitment.block_number, 21); + assert_eq!(votes.next().unwrap().first().unwrap().commitment.block_number, 22); + assert!(votes.next().is_none()); + + // simulate mandatory done, and retry buffered votes + worker.voting_oracle.rounds_mut().unwrap().test_set_mandatory_done(true); + worker.try_pending_justif_and_votes().unwrap(); + // all blocks <= grandpa finalized should have been handled, rest still buffered + let mut votes = worker.pending_votes.values(); + assert_eq!(votes.next().unwrap().first().unwrap().commitment.block_number, 21); + assert_eq!(votes.next().unwrap().first().unwrap().commitment.block_number, 22); } } diff --git a/client/network/test/src/lib.rs b/client/network/test/src/lib.rs index d7c83810d521d..c0e1e9f0e944f 100644 --- a/client/network/test/src/lib.rs +++ b/client/network/test/src/lib.rs @@ -25,6 +25,7 @@ mod sync; use std::{ borrow::Cow, collections::HashMap, + marker::PhantomData, pin::Pin, sync::Arc, task::{Context as FutureContext, Poll}, @@ -567,25 +568,27 @@ impl BlockImportAdapterFull for T where /// This is required as the `TestNetFactory` trait does not distinguish between /// full and light nodes. #[derive(Clone)] -pub struct BlockImportAdapter { +pub struct BlockImportAdapter { inner: I, + _phantom: PhantomData, } -impl BlockImportAdapter { +impl BlockImportAdapter { /// Create a new instance of `Self::Full`. pub fn new(inner: I) -> Self { - Self { inner } + Self { inner, _phantom: PhantomData } } } #[async_trait::async_trait] -impl BlockImport for BlockImportAdapter +impl BlockImport for BlockImportAdapter where I: BlockImport + Send + Sync, I::Transaction: Send, + Transaction: Send + 'static, { type Error = ConsensusError; - type Transaction = (); + type Transaction = Transaction; async fn check_block( &mut self, @@ -596,7 +599,7 @@ where async fn import_block( &mut self, - block: BlockImportParams, + block: BlockImportParams, cache: HashMap>, ) -> Result { self.inner.import_block(block.clear_storage_changes_and_mutate(), cache).await diff --git a/primitives/beefy/src/commitment.rs b/primitives/beefy/src/commitment.rs index ed392139de13f..ddf58474e77a0 100644 --- a/primitives/beefy/src/commitment.rs +++ b/primitives/beefy/src/commitment.rs @@ -293,6 +293,12 @@ pub enum VersionedFinalityProof { V1(SignedCommitment), } +impl From> for VersionedFinalityProof { + fn from(commitment: SignedCommitment) -> Self { + VersionedFinalityProof::V1(commitment) + } +} + #[cfg(test)] mod tests { diff --git a/primitives/runtime/src/lib.rs b/primitives/runtime/src/lib.rs index b41c605d8a3be..bf77c08b76906 100644 --- a/primitives/runtime/src/lib.rs +++ b/primitives/runtime/src/lib.rs @@ -151,6 +151,11 @@ impl Justifications { self.iter().find(|j| j.0 == engine_id).map(|j| &j.1) } + /// Remove the encoded justification for the given consensus engine, if it exists. + pub fn remove(&mut self, engine_id: ConsensusEngineId) { + self.0.retain(|j| j.0 != engine_id) + } + /// Return a copy of the encoded justification for the given consensus /// engine, if it exists. pub fn into_justification(self, engine_id: ConsensusEngineId) -> Option { From 9a244927b6d01bb79942f111cd547fb8f8e08ab8 Mon Sep 17 00:00:00 2001 From: ZhiYong Date: Sat, 30 Jul 2022 01:59:03 +0800 Subject: [PATCH 440/484] Add Event to Pallet Asset-Tx-Payment (#11690) * Add Event to Pallet Asset-Tx-Payment * add asset_id into the Event Co-authored-by: parity-processbot <> --- bin/node/runtime/src/lib.rs | 1 + .../asset-tx-payment/src/lib.rs | 27 +++++++++++++++++-- .../asset-tx-payment/src/tests.rs | 5 ++-- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 037211155cf0e..2ac1e6444f119 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -456,6 +456,7 @@ impl pallet_transaction_payment::Config for Runtime { } impl pallet_asset_tx_payment::Config for Runtime { + type Event = Event; type Fungibles = Assets; type OnChargeAssetTransaction = pallet_asset_tx_payment::FungiblesAdapter< pallet_assets::BalanceToAssetBalance, diff --git a/frame/transaction-payment/asset-tx-payment/src/lib.rs b/frame/transaction-payment/asset-tx-payment/src/lib.rs index 83801c44d3578..08561375247ae 100644 --- a/frame/transaction-payment/asset-tx-payment/src/lib.rs +++ b/frame/transaction-payment/asset-tx-payment/src/lib.rs @@ -113,6 +113,8 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config + pallet_transaction_payment::Config { + /// The overarching event type. + type Event: From> + IsType<::Event>; /// The fungibles instance used to pay for transactions in assets. type Fungibles: Balanced; /// The actual transaction charging logic that charges the fees. @@ -122,6 +124,19 @@ pub mod pallet { #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// A transaction fee `actual_fee`, of which `tip` was added to the minimum inclusion fee, + /// has been paid by `who` in an asset `asset_id`. + AssetTxFeePaid { + who: T::AccountId, + actual_fee: BalanceOf, + tip: BalanceOf, + asset_id: Option>, + }, + } } /// Require the transactor pay for themselves and maybe include a tip to gain additional priority @@ -213,6 +228,8 @@ where Self::AccountId, // imbalance resulting from withdrawing the fee InitialPayment, + // asset_id for the transaction payment + Option>, ); fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> { @@ -240,7 +257,7 @@ where len: usize, ) -> Result { let (_fee, initial_payment) = self.withdraw_fee(who, call, info, len)?; - Ok((self.tip, who.clone(), initial_payment)) + Ok((self.tip, who.clone(), initial_payment, self.asset_id)) } fn post_dispatch( @@ -250,7 +267,7 @@ where len: usize, result: &DispatchResult, ) -> Result<(), TransactionValidityError> { - if let Some((tip, who, initial_payment)) = pre { + if let Some((tip, who, initial_payment, asset_id)) = pre { match initial_payment { InitialPayment::Native(already_withdrawn) => { pallet_transaction_payment::ChargeTransactionPayment::::post_dispatch( @@ -273,6 +290,12 @@ where tip.into(), already_withdrawn.into(), )?; + Pallet::::deposit_event(Event::::AssetTxFeePaid { + who, + actual_fee, + tip, + asset_id, + }); }, InitialPayment::Nothing => { // `actual_fee` should be zero here for any signed extrinsic. It would be diff --git a/frame/transaction-payment/asset-tx-payment/src/tests.rs b/frame/transaction-payment/asset-tx-payment/src/tests.rs index ad5bc3f22e57f..08b17a6bf459c 100644 --- a/frame/transaction-payment/asset-tx-payment/src/tests.rs +++ b/frame/transaction-payment/asset-tx-payment/src/tests.rs @@ -51,7 +51,7 @@ frame_support::construct_runtime!( TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event}, Assets: pallet_assets::{Pallet, Call, Storage, Event}, Authorship: pallet_authorship::{Pallet, Call, Storage}, - AssetTxPayment: pallet_asset_tx_payment::{Pallet}, + AssetTxPayment: pallet_asset_tx_payment::{Pallet, Event}, } ); @@ -198,6 +198,7 @@ impl HandleCredit for CreditToBlockAuthor { } impl Config for Runtime { + type Event = Event; type Fungibles = Assets; type OnChargeAssetTransaction = FungiblesAdapter< pallet_assets::BalanceToAssetBalance, @@ -663,7 +664,7 @@ fn post_dispatch_fee_is_zero_if_pre_dispatch_fee_is_zero() { .unwrap(); // `Pays::No` implies no pre-dispatch fees assert_eq!(Assets::balance(asset_id, caller), balance); - let (_tip, _who, initial_payment) = ⪯ + let (_tip, _who, initial_payment, _asset_id) = ⪯ let not_paying = match initial_payment { &InitialPayment::Nothing => true, _ => false, From f0d0a3a4bbe73b4960c6e4d78f79981891892ba9 Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Fri, 29 Jul 2022 23:17:49 +0300 Subject: [PATCH 441/484] Network sync refactoring (part 5) (#11825) * Make `chain_sync` an explicit networking parameter instead of offering factory method * Derive `Copy` on `SyncMode` and remove cloning --- client/network/src/config.rs | 16 ++++------- client/network/src/service.rs | 26 ++++------------- client/network/src/service/tests.rs | 35 ++++++++++++----------- client/network/test/src/lib.rs | 32 ++++++++++++--------- client/service/src/builder.rs | 44 +++++++++++++++-------------- 5 files changed, 69 insertions(+), 84 deletions(-) diff --git a/client/network/src/config.rs b/client/network/src/config.rs index 58349fe973330..2622762da5fc9 100644 --- a/client/network/src/config.rs +++ b/client/network/src/config.rs @@ -96,14 +96,8 @@ where /// valid. pub import_queue: Box>, - /// Factory function that creates a new instance of chain sync. - pub create_chain_sync: Box< - dyn FnOnce( - sc_network_common::sync::SyncMode, - Arc, - Option>>, - ) -> crate::error::Result>>, - >, + /// Instance of chain sync implementation. + pub chain_sync: Box>, /// Registry for recording prometheus metrics to. pub metrics_registry: Option, @@ -138,8 +132,8 @@ where /// both outgoing and incoming requests. pub state_request_protocol_config: RequestResponseConfig, - /// Optional warp sync protocol support. Include protocol config and sync provider. - pub warp_sync: Option<(Arc>, RequestResponseConfig)>, + /// Optional warp sync protocol config. + pub warp_sync_protocol_config: Option, } /// Role of the local node. @@ -352,7 +346,7 @@ impl From for ParseErr { } /// Sync operation mode. -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum SyncMode { /// Full block download and verification. Full, diff --git a/client/network/src/service.rs b/client/network/src/service.rs index aeb5e25497bb3..409ed88c75c00 100644 --- a/client/network/src/service.rs +++ b/client/network/src/service.rs @@ -30,7 +30,7 @@ use crate::{ behaviour::{self, Behaviour, BehaviourOut}, bitswap::Bitswap, - config::{self, parse_str_addr, Params, TransportConfig}, + config::{parse_str_addr, Params, TransportConfig}, discovery::DiscoveryConfig, error::Error, network_state::{ @@ -60,7 +60,7 @@ use metrics::{Histogram, HistogramVec, MetricSources, Metrics}; use parking_lot::Mutex; use sc_client_api::{BlockBackend, ProofProvider}; use sc_consensus::{BlockImportError, BlockImportStatus, ImportQueue, Link}; -use sc_network_common::sync::{SyncMode, SyncState, SyncStatus}; +use sc_network_common::sync::{SyncState, SyncStatus}; use sc_peerset::PeersetHandle; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; use sp_blockchain::{HeaderBackend, HeaderMetadata}; @@ -239,21 +239,6 @@ where let default_notif_handshake_message = Roles::from(¶ms.role).encode(); - let (warp_sync_provider, warp_sync_protocol_config) = match params.warp_sync { - Some((p, c)) => (Some(p), Some(c)), - None => (None, None), - }; - - let chain_sync = (params.create_chain_sync)( - match params.network_config.sync_mode { - config::SyncMode::Full => SyncMode::Full, - config::SyncMode::Fast { skip_proofs, storage_chain_mode } => - SyncMode::LightState { skip_proofs, storage_chain_mode }, - config::SyncMode::Warp => SyncMode::Warp, - }, - params.chain.clone(), - warp_sync_provider, - )?; let (protocol, peerset_handle, mut known_addresses) = Protocol::new( From::from(¶ms.role), params.chain.clone(), @@ -266,7 +251,7 @@ where ) .collect(), params.metrics_registry.as_ref(), - chain_sync, + params.chain_sync, )?; // List of multiaddresses that we know in the network. @@ -303,7 +288,6 @@ where let is_major_syncing = Arc::new(AtomicBool::new(false)); // Build the swarm. - let client = params.chain.clone(); let (mut swarm, bandwidth): (Swarm>, _) = { let user_agent = format!( "{} ({})", @@ -389,7 +373,7 @@ where }; let behaviour = { - let bitswap = params.network_config.ipfs_server.then(|| Bitswap::new(client)); + let bitswap = params.network_config.ipfs_server.then(|| Bitswap::new(params.chain)); let result = Behaviour::new( protocol, user_agent, @@ -397,7 +381,7 @@ where discovery_config, params.block_request_protocol_config, params.state_request_protocol_config, - warp_sync_protocol_config, + params.warp_sync_protocol_config, bitswap, params.light_client_request_protocol_config, params.network_config.request_response_protocols, diff --git a/client/network/src/service/tests.rs b/client/network/src/service/tests.rs index 181d58130aa6b..de474ee8fe4d0 100644 --- a/client/network/src/service/tests.rs +++ b/client/network/src/service/tests.rs @@ -42,7 +42,7 @@ type TestNetworkService = NetworkService< /// > **Note**: We return the events stream in order to not possibly lose events between the /// > construction of the service and the moment the events stream is grabbed. fn build_test_full_node( - config: config::NetworkConfiguration, + network_config: config::NetworkConfiguration, ) -> (Arc, impl Stream) { let client = Arc::new(TestClientBuilder::with_default_backend().build_with_longest_chain().0); @@ -111,35 +111,36 @@ fn build_test_full_node( protocol_config }; - let max_parallel_downloads = config.max_parallel_downloads; + let chain_sync = ChainSync::new( + match network_config.sync_mode { + config::SyncMode::Full => sc_network_common::sync::SyncMode::Full, + config::SyncMode::Fast { skip_proofs, storage_chain_mode } => + sc_network_common::sync::SyncMode::LightState { skip_proofs, storage_chain_mode }, + config::SyncMode::Warp => sc_network_common::sync::SyncMode::Warp, + }, + client.clone(), + Box::new(DefaultBlockAnnounceValidator), + network_config.max_parallel_downloads, + None, + ) + .unwrap(); let worker = NetworkWorker::new(config::Params { role: config::Role::Full, executor: None, transactions_handler_executor: Box::new(|task| { async_std::task::spawn(task); }), - network_config: config, + network_config, chain: client.clone(), - transaction_pool: Arc::new(crate::config::EmptyTransactionPool), + transaction_pool: Arc::new(config::EmptyTransactionPool), protocol_id, import_queue, - create_chain_sync: Box::new( - move |sync_mode, chain, warp_sync_provider| match ChainSync::new( - sync_mode, - chain, - Box::new(DefaultBlockAnnounceValidator), - max_parallel_downloads, - warp_sync_provider, - ) { - Ok(chain_sync) => Ok(Box::new(chain_sync)), - Err(error) => Err(Box::new(error).into()), - }, - ), + chain_sync: Box::new(chain_sync), metrics_registry: None, block_request_protocol_config, state_request_protocol_config, light_client_request_protocol_config, - warp_sync: None, + warp_sync_protocol_config: None, }) .unwrap(); diff --git a/client/network/test/src/lib.rs b/client/network/test/src/lib.rs index c0e1e9f0e944f..4659684987f77 100644 --- a/client/network/test/src/lib.rs +++ b/client/network/test/src/lib.rs @@ -838,10 +838,25 @@ where protocol_config }; - let max_parallel_downloads = network_config.max_parallel_downloads; let block_announce_validator = config .block_announce_validator .unwrap_or_else(|| Box::new(DefaultBlockAnnounceValidator)); + let chain_sync = ChainSync::new( + match network_config.sync_mode { + SyncMode::Full => sc_network_common::sync::SyncMode::Full, + SyncMode::Fast { skip_proofs, storage_chain_mode } => + sc_network_common::sync::SyncMode::LightState { + skip_proofs, + storage_chain_mode, + }, + SyncMode::Warp => sc_network_common::sync::SyncMode::Warp, + }, + client.clone(), + block_announce_validator, + network_config.max_parallel_downloads, + Some(warp_sync), + ) + .unwrap(); let network = NetworkWorker::new(sc_network::config::Params { role: if config.is_authority { Role::Authority } else { Role::Full }, executor: None, @@ -853,23 +868,12 @@ where transaction_pool: Arc::new(EmptyTransactionPool), protocol_id, import_queue, - create_chain_sync: Box::new(move |sync_mode, chain, warp_sync_provider| { - match ChainSync::new( - sync_mode, - chain, - block_announce_validator, - max_parallel_downloads, - warp_sync_provider, - ) { - Ok(chain_sync) => Ok(Box::new(chain_sync)), - Err(error) => Err(Box::new(error).into()), - } - }), + chain_sync: Box::new(chain_sync), metrics_registry: None, block_request_protocol_config, state_request_protocol_config, light_client_request_protocol_config, - warp_sync: Some((warp_sync, warp_protocol_config)), + warp_sync_protocol_config: Some(warp_protocol_config), }) .unwrap(); diff --git a/client/service/src/builder.rs b/client/service/src/builder.rs index 0a18943f45006..ec537a33b72d5 100644 --- a/client/service/src/builder.rs +++ b/client/service/src/builder.rs @@ -760,13 +760,15 @@ where protocol_config }; - let warp_sync_params = warp_sync.map(|provider| { - // Allow both outgoing and incoming requests. - let (handler, protocol_config) = - WarpSyncRequestHandler::new(protocol_id.clone(), provider.clone()); - spawn_handle.spawn("warp-sync-request-handler", Some("networking"), handler.run()); - (provider, protocol_config) - }); + let (warp_sync_provider, warp_sync_protocol_config) = warp_sync + .map(|provider| { + // Allow both outgoing and incoming requests. + let (handler, protocol_config) = + WarpSyncRequestHandler::new(protocol_id.clone(), provider.clone()); + spawn_handle.spawn("warp-sync-request-handler", Some("networking"), handler.run()); + (Some(provider), Some(protocol_config)) + }) + .unwrap_or_default(); let light_client_request_protocol_config = { // Allow both outgoing and incoming requests. @@ -776,7 +778,18 @@ where protocol_config }; - let max_parallel_downloads = config.network.max_parallel_downloads; + let chain_sync = ChainSync::new( + match config.network.sync_mode { + SyncMode::Full => sc_network_common::sync::SyncMode::Full, + SyncMode::Fast { skip_proofs, storage_chain_mode } => + sc_network_common::sync::SyncMode::LightState { skip_proofs, storage_chain_mode }, + SyncMode::Warp => sc_network_common::sync::SyncMode::Warp, + }, + client.clone(), + block_announce_validator, + config.network.max_parallel_downloads, + warp_sync_provider, + )?; let network_params = sc_network::config::Params { role: config.role.clone(), executor: { @@ -796,22 +809,11 @@ where transaction_pool: transaction_pool_adapter as _, protocol_id, import_queue: Box::new(import_queue), - create_chain_sync: Box::new( - move |sync_mode, chain, warp_sync_provider| match ChainSync::new( - sync_mode, - chain, - block_announce_validator, - max_parallel_downloads, - warp_sync_provider, - ) { - Ok(chain_sync) => Ok(Box::new(chain_sync)), - Err(error) => Err(Box::new(error).into()), - }, - ), + chain_sync: Box::new(chain_sync), metrics_registry: config.prometheus_config.as_ref().map(|config| config.registry.clone()), block_request_protocol_config, state_request_protocol_config, - warp_sync: warp_sync_params, + warp_sync_protocol_config, light_client_request_protocol_config, }; From 67310cccc7f2c4a74c818731f72c4bd33cc11b28 Mon Sep 17 00:00:00 2001 From: yjh Date: Sat, 30 Jul 2022 06:07:34 +0800 Subject: [PATCH 442/484] feat: generalize ConsensusDataProvider for manual-seal (#11827) * feat: generalize ConsensusDataProvider for manual-seal * rename all generic type param `proof`/`PROOF` to `P` * rename a missing thing * Update client/consensus/manual-seal/src/consensus.rs Co-authored-by: Davide Galassi * Update client/consensus/manual-seal/src/consensus/babe.rs Co-authored-by: Davide Galassi * Update client/consensus/manual-seal/src/consensus/aura.rs Co-authored-by: Davide Galassi Co-authored-by: Davide Galassi --- client/consensus/manual-seal/src/consensus.rs | 4 ++++ .../manual-seal/src/consensus/aura.rs | 11 +++++---- .../manual-seal/src/consensus/babe.rs | 21 ++++++++++++---- client/consensus/manual-seal/src/lib.rs | 24 +++++++++++-------- .../consensus/manual-seal/src/seal_block.rs | 14 ++++++----- 5 files changed, 49 insertions(+), 25 deletions(-) diff --git a/client/consensus/manual-seal/src/consensus.rs b/client/consensus/manual-seal/src/consensus.rs index dfd3730fd3427..b5dfc3d809c13 100644 --- a/client/consensus/manual-seal/src/consensus.rs +++ b/client/consensus/manual-seal/src/consensus.rs @@ -33,6 +33,9 @@ pub trait ConsensusDataProvider: Send + Sync { /// Block import transaction type type Transaction; + /// The proof type. + type Proof; + /// Attempt to create a consensus digest. fn create_digest(&self, parent: &B::Header, inherents: &InherentData) -> Result; @@ -42,5 +45,6 @@ pub trait ConsensusDataProvider: Send + Sync { parent: &B::Header, params: &mut BlockImportParams, inherents: &InherentData, + proof: Self::Proof, ) -> Result<(), Error>; } diff --git a/client/consensus/manual-seal/src/consensus/aura.rs b/client/consensus/manual-seal/src/consensus/aura.rs index 7b5d6720562be..065b78732cdc3 100644 --- a/client/consensus/manual-seal/src/consensus/aura.rs +++ b/client/consensus/manual-seal/src/consensus/aura.rs @@ -35,14 +35,14 @@ use sp_timestamp::TimestampInherentData; use std::{marker::PhantomData, sync::Arc}; /// Consensus data provider for Aura. -pub struct AuraConsensusDataProvider { +pub struct AuraConsensusDataProvider { // slot duration slot_duration: SlotDuration, // phantom data for required generics - _phantom: PhantomData<(B, C)>, + _phantom: PhantomData<(B, C, P)>, } -impl AuraConsensusDataProvider +impl AuraConsensusDataProvider where B: BlockT, C: AuxStore + ProvideRuntimeApi + UsageProvider, @@ -58,7 +58,7 @@ where } } -impl ConsensusDataProvider for AuraConsensusDataProvider +impl ConsensusDataProvider for AuraConsensusDataProvider where B: BlockT, C: AuxStore @@ -67,8 +67,10 @@ where + UsageProvider + ProvideRuntimeApi, C::Api: AuraApi, + P: Send + Sync, { type Transaction = TransactionFor; + type Proof = P; fn create_digest( &self, @@ -92,6 +94,7 @@ where _parent: &B::Header, _params: &mut BlockImportParams, _inherents: &InherentData, + _proof: Self::Proof, ) -> Result<(), Error> { Ok(()) } diff --git a/client/consensus/manual-seal/src/consensus/babe.rs b/client/consensus/manual-seal/src/consensus/babe.rs index 3e7770cd982d2..cc73a3fa961ce 100644 --- a/client/consensus/manual-seal/src/consensus/babe.rs +++ b/client/consensus/manual-seal/src/consensus/babe.rs @@ -31,7 +31,7 @@ use sc_consensus_epochs::{ descendent_query, EpochHeader, SharedEpochChanges, ViableEpochDescriptor, }; use sp_keystore::SyncCryptoStorePtr; -use std::{borrow::Cow, sync::Arc}; +use std::{borrow::Cow, marker::PhantomData, sync::Arc}; use sc_consensus::{BlockImportParams, ForkChoiceStrategy, Verifier}; use sp_api::{ProvideRuntimeApi, TransactionFor}; @@ -53,7 +53,7 @@ use sp_timestamp::TimestampInherentData; /// Provides BABE-compatible predigests and BlockImportParams. /// Intended for use with BABE runtimes. -pub struct BabeConsensusDataProvider { +pub struct BabeConsensusDataProvider { /// shared reference to keystore keystore: SyncCryptoStorePtr, @@ -68,6 +68,7 @@ pub struct BabeConsensusDataProvider { /// Authorities to be used for this babe chain. authorities: Vec<(AuthorityId, BabeAuthorityWeight)>, + _phantom: PhantomData

, } /// Verifier to be used for babe chains @@ -131,7 +132,7 @@ where } } -impl BabeConsensusDataProvider +impl BabeConsensusDataProvider where B: BlockT, C: AuxStore @@ -153,7 +154,14 @@ where let config = Config::get(&*client)?; - Ok(Self { config, client, keystore, epoch_changes, authorities }) + Ok(Self { + config, + client, + keystore, + epoch_changes, + authorities, + _phantom: Default::default(), + }) } fn epoch(&self, parent: &B::Header, slot: Slot) -> Result { @@ -181,7 +189,7 @@ where } } -impl ConsensusDataProvider for BabeConsensusDataProvider +impl ConsensusDataProvider for BabeConsensusDataProvider where B: BlockT, C: AuxStore @@ -190,8 +198,10 @@ where + UsageProvider + ProvideRuntimeApi, C::Api: BabeApi, + P: Send + Sync, { type Transaction = TransactionFor; + type Proof = P; fn create_digest(&self, parent: &B::Header, inherents: &InherentData) -> Result { let slot = inherents @@ -259,6 +269,7 @@ where parent: &B::Header, params: &mut BlockImportParams, inherents: &InherentData, + _proof: Self::Proof, ) -> Result<(), Error> { let slot = inherents .babe_inherent_data()? diff --git a/client/consensus/manual-seal/src/lib.rs b/client/consensus/manual-seal/src/lib.rs index a8d2634ade560..ba63666f3e46c 100644 --- a/client/consensus/manual-seal/src/lib.rs +++ b/client/consensus/manual-seal/src/lib.rs @@ -81,7 +81,7 @@ where } /// Params required to start the instant sealing authorship task. -pub struct ManualSealParams, TP, SC, CS, CIDP> { +pub struct ManualSealParams, TP, SC, CS, CIDP, P> { /// Block import instance for well. importing blocks. pub block_import: BI, @@ -103,14 +103,14 @@ pub struct ManualSealParams, TP, SC, C /// Digest provider for inclusion in blocks. pub consensus_data_provider: - Option>>>, + Option>>>, /// Something that can create the inherent data providers. pub create_inherent_data_providers: CIDP, } /// Params required to start the manual sealing authorship task. -pub struct InstantSealParams, TP, SC, CIDP> { +pub struct InstantSealParams, TP, SC, CIDP, P> { /// Block import instance for well. importing blocks. pub block_import: BI, @@ -128,14 +128,14 @@ pub struct InstantSealParams, TP, SC, /// Digest provider for inclusion in blocks. pub consensus_data_provider: - Option>>>, + Option>>>, /// Something that can create the inherent data providers. pub create_inherent_data_providers: CIDP, } /// Creates the background authorship task for the manual seal engine. -pub async fn run_manual_seal( +pub async fn run_manual_seal( ManualSealParams { mut block_import, mut env, @@ -145,7 +145,7 @@ pub async fn run_manual_seal( select_chain, consensus_data_provider, create_inherent_data_providers, - }: ManualSealParams, + }: ManualSealParams, ) where B: BlockT + 'static, BI: BlockImport> @@ -155,12 +155,13 @@ pub async fn run_manual_seal( C: HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, CB: ClientBackend + 'static, E: Environment + 'static, - E::Proposer: Proposer>, + E::Proposer: Proposer>, CS: Stream::Hash>> + Unpin + 'static, SC: SelectChain + 'static, TransactionFor: 'static, TP: TransactionPool, CIDP: CreateInherentDataProviders, + P: Send + Sync + 'static, { while let Some(command) = commands_stream.next().await { match command { @@ -198,7 +199,7 @@ pub async fn run_manual_seal( /// runs the background authorship task for the instant seal engine. /// instant-seal creates a new block for every transaction imported into /// the transaction pool. -pub async fn run_instant_seal( +pub async fn run_instant_seal( InstantSealParams { block_import, env, @@ -207,7 +208,7 @@ pub async fn run_instant_seal( select_chain, consensus_data_provider, create_inherent_data_providers, - }: InstantSealParams, + }: InstantSealParams, ) where B: BlockT + 'static, BI: BlockImport> @@ -217,11 +218,12 @@ pub async fn run_instant_seal( C: HeaderBackend + Finalizer + ProvideRuntimeApi + 'static, CB: ClientBackend + 'static, E: Environment + 'static, - E::Proposer: Proposer>, + E::Proposer: Proposer>, SC: SelectChain + 'static, TransactionFor: 'static, TP: TransactionPool, CIDP: CreateInherentDataProviders, + P: Send + Sync + 'static, { // instant-seal creates blocks as soon as transactions are imported // into the transaction pool. @@ -275,6 +277,7 @@ mod tests { C: ProvideRuntimeApi + Send + Sync, { type Transaction = TransactionFor; + type Proof = (); fn create_digest( &self, @@ -289,6 +292,7 @@ mod tests { _parent: &B::Header, params: &mut BlockImportParams, _inherents: &InherentData, + _proof: Self::Proof, ) -> Result<(), Error> { params.post_digests.push(DigestItem::Other(vec![1])); Ok(()) diff --git a/client/consensus/manual-seal/src/seal_block.rs b/client/consensus/manual-seal/src/seal_block.rs index 202b54fe5d0c5..32e3acf68506e 100644 --- a/client/consensus/manual-seal/src/seal_block.rs +++ b/client/consensus/manual-seal/src/seal_block.rs @@ -36,7 +36,7 @@ use std::{collections::HashMap, sync::Arc, time::Duration}; pub const MAX_PROPOSAL_DURATION: u64 = 10; /// params for sealing a new block -pub struct SealBlockParams<'a, B: BlockT, BI, SC, C: ProvideRuntimeApi, E, TP, CIDP> { +pub struct SealBlockParams<'a, B: BlockT, BI, SC, C: ProvideRuntimeApi, E, TP, CIDP, P> { /// if true, empty blocks(without extrinsics) will be created. /// otherwise, will return Error::EmptyTransactionPool. pub create_empty: bool, @@ -56,7 +56,7 @@ pub struct SealBlockParams<'a, B: BlockT, BI, SC, C: ProvideRuntimeApi, E, TP pub select_chain: &'a SC, /// Digest provider for inclusion in blocks. pub consensus_data_provider: - Option<&'a dyn ConsensusDataProvider>>, + Option<&'a dyn ConsensusDataProvider>>, /// block import object pub block_import: &'a mut BI, /// Something that can create the inherent data providers. @@ -64,7 +64,7 @@ pub struct SealBlockParams<'a, B: BlockT, BI, SC, C: ProvideRuntimeApi, E, TP } /// seals a new block with the given params -pub async fn seal_block( +pub async fn seal_block( SealBlockParams { create_empty, finalize, @@ -77,7 +77,7 @@ pub async fn seal_block( create_inherent_data_providers, consensus_data_provider: digest_provider, mut sender, - }: SealBlockParams<'_, B, BI, SC, C, E, TP, CIDP>, + }: SealBlockParams<'_, B, BI, SC, C, E, TP, CIDP, P>, ) where B: BlockT, BI: BlockImport> @@ -86,11 +86,12 @@ pub async fn seal_block( + 'static, C: HeaderBackend + ProvideRuntimeApi, E: Environment, - E::Proposer: Proposer>, + E::Proposer: Proposer>, TP: TransactionPool, SC: SelectChain, TransactionFor: 'static, CIDP: CreateInherentDataProviders, + P: Send + Sync + 'static, { let future = async { if pool.status().ready == 0 && !create_empty { @@ -138,6 +139,7 @@ pub async fn seal_block( } let (header, body) = proposal.block.deconstruct(); + let proof = proposal.proof; let mut params = BlockImportParams::new(BlockOrigin::Own, header.clone()); params.body = Some(body); params.finalized = finalize; @@ -147,7 +149,7 @@ pub async fn seal_block( )); if let Some(digest_provider) = digest_provider { - digest_provider.append_block_import(&parent, &mut params, &inherent_data)?; + digest_provider.append_block_import(&parent, &mut params, &inherent_data, proof)?; } // Make sure we return the same post-hash that will be calculated when importing the block From adcdcc172d77f3ab8737b0cf6d23c4978542fe86 Mon Sep 17 00:00:00 2001 From: Sergej Sakac <73715684+Szegoo@users.noreply.github.com> Date: Sat, 30 Jul 2022 16:09:25 +0200 Subject: [PATCH 443/484] Auto-incremental CollectionId (#11796) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * autoincrementing CollectionId * fix * benchmarking fix * fmt * fix * update before checking * fmt * fix * fmt * commit * tests & fix * fix * commit * docs * safe math * unexpose function * benchmark * fmt * better naming * fix? * merge fixes * fmt * ".git/.scripts/bench-bot.sh" pallet dev pallet_uniques * wrong weight * Update frame/uniques/src/lib.rs Co-authored-by: Oliver Tale-Yazdi * Update frame/uniques/src/lib.rs Co-authored-by: Oliver Tale-Yazdi * using substrate trait instead of num-traits * remove unnecessary trait * emit NextCollectionIdIncremented in do_create_collection * fix in benchmarks * check for event & group import * docs Co-authored-by: Sergej Sakač Co-authored-by: command-bot <> Co-authored-by: Oliver Tale-Yazdi --- Cargo.lock | 4 +- frame/uniques/src/benchmarking.rs | 25 +++-- frame/uniques/src/functions.rs | 15 +++ frame/uniques/src/lib.rs | 54 ++++++++-- frame/uniques/src/tests.rs | 83 ++++++++++----- frame/uniques/src/weights.rs | 169 +++++++++++++++++------------- 6 files changed, 233 insertions(+), 117 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 34bf34c62c6fd..2b76f61df7912 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5020,9 +5020,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", "libm", diff --git a/frame/uniques/src/benchmarking.rs b/frame/uniques/src/benchmarking.rs index 713393048f7e9..86247fb964ff5 100644 --- a/frame/uniques/src/benchmarking.rs +++ b/frame/uniques/src/benchmarking.rs @@ -42,13 +42,10 @@ fn create_collection, I: 'static>( let caller_lookup = T::Lookup::unlookup(caller.clone()); let collection = T::Helper::collection(0); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); - assert!(Uniques::::force_create( - SystemOrigin::Root.into(), - collection, - caller_lookup.clone(), - false, - ) - .is_ok()); + assert!( + Uniques::::force_create(SystemOrigin::Root.into(), caller_lookup.clone(), false,) + .is_ok() + ); (collection, caller, caller_lookup) } @@ -143,7 +140,7 @@ benchmarks_instance_pallet! { whitelist_account!(caller); let admin = T::Lookup::unlookup(caller.clone()); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); - let call = Call::::create { collection, admin }; + let call = Call::::create { admin }; }: { call.dispatch_bypass_filter(origin)? } verify { assert_last_event::(Event::Created { collection: T::Helper::collection(0), creator: caller.clone(), owner: caller }.into()); @@ -152,7 +149,7 @@ benchmarks_instance_pallet! { force_create { let caller: T::AccountId = whitelisted_caller(); let caller_lookup = T::Lookup::unlookup(caller.clone()); - }: _(SystemOrigin::Root, T::Helper::collection(0), caller_lookup, true) + }: _(SystemOrigin::Root, caller_lookup, true) verify { assert_last_event::(Event::ForceCreated { collection: T::Helper::collection(0), owner: caller }.into()); } @@ -408,6 +405,16 @@ benchmarks_instance_pallet! { }.into()); } + try_increment_id { + let (_, caller, _) = create_collection::(); + Uniques::::set_next_id(0); + }: _(SystemOrigin::Signed(caller.clone())) + verify { + assert_last_event::(Event::NextCollectionIdIncremented { + next_id: 1u32.into() + }.into()); + } + set_price { let (collection, caller, _) = create_collection::(); let (item, ..) = mint_item::(0); diff --git a/frame/uniques/src/functions.rs b/frame/uniques/src/functions.rs index 9e8d3301616df..dcbb1f27973d7 100644 --- a/frame/uniques/src/functions.rs +++ b/frame/uniques/src/functions.rs @@ -88,7 +88,12 @@ impl, I: 'static> Pallet { }, ); + let next_id = collection.saturating_add(1u32.into()); + CollectionAccount::::insert(&owner, &collection, ()); + NextCollectionId::::set(next_id); + + Self::deposit_event(Event::NextCollectionIdIncremented { next_id }); Self::deposit_event(event); Ok(()) } @@ -208,6 +213,16 @@ impl, I: 'static> Pallet { Ok(()) } + #[cfg(any(test, feature = "runtime-benchmarks"))] + pub fn set_next_id(count: u32) { + NextCollectionId::::set(count.into()); + } + + #[cfg(test)] + pub fn get_next_id() -> T::CollectionId { + NextCollectionId::::get() + } + pub fn do_set_price( collection: T::CollectionId, item: T::ItemId, diff --git a/frame/uniques/src/lib.rs b/frame/uniques/src/lib.rs index 53f83605da90e..b25fb2f87ea97 100644 --- a/frame/uniques/src/lib.rs +++ b/frame/uniques/src/lib.rs @@ -51,7 +51,7 @@ use frame_support::{ }; use frame_system::Config as SystemConfig; use sp_runtime::{ - traits::{Saturating, StaticLookup, Zero}, + traits::{AtLeast32BitUnsigned, Saturating, StaticLookup, Zero}, ArithmeticError, RuntimeDebug, }; use sp_std::prelude::*; @@ -92,7 +92,12 @@ pub mod pallet { type Event: From> + IsType<::Event>; /// Identifier for the collection of item. - type CollectionId: Member + Parameter + MaxEncodedLen + Copy; + type CollectionId: Member + + Parameter + + MaxEncodedLen + + Copy + + Default + + AtLeast32BitUnsigned; /// The type used to identify a unique item within a collection. type ItemId: Member + Parameter + MaxEncodedLen + Copy; @@ -266,6 +271,12 @@ pub mod pallet { pub(super) type CollectionMaxSupply, I: 'static = ()> = StorageMap<_, Blake2_128Concat, T::CollectionId, u32, OptionQuery>; + #[pallet::storage] + /// Stores the `CollectionId` that is going to be used for the next collection. + /// This gets incremented by 1 whenever a new collection is created. + pub(super) type NextCollectionId, I: 'static = ()> = + StorageValue<_, T::CollectionId, ValueQuery>; + #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event, I: 'static = ()> { @@ -357,6 +368,8 @@ pub mod pallet { OwnershipAcceptanceChanged { who: T::AccountId, maybe_collection: Option }, /// Max supply has been set for a collection. CollectionMaxSupplySet { collection: T::CollectionId, max_supply: u32 }, + /// Event gets emmited when the `NextCollectionId` gets incremented. + NextCollectionIdIncremented { next_id: T::CollectionId }, /// The price was set for the instance. ItemPriceSet { collection: T::CollectionId, @@ -408,6 +421,10 @@ pub mod pallet { MaxSupplyAlreadySet, /// The provided max supply is less to the amount of items a collection already has. MaxSupplyTooSmall, + /// The `CollectionId` in `NextCollectionId` is not being used. + /// + /// This means that you can directly proceed to call `create`. + NextIdNotUsed, /// The given item ID is unknown. UnknownItem, /// Item is not for sale. @@ -439,7 +456,6 @@ pub mod pallet { /// `ItemDeposit` funds of sender are reserved. /// /// Parameters: - /// - `collection`: The identifier of the new collection. This must not be currently in use. /// - `admin`: The admin of this collection. The admin is the initial address of each /// member of the collection's admin team. /// @@ -449,9 +465,10 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::create())] pub fn create( origin: OriginFor, - collection: T::CollectionId, admin: ::Source, ) -> DispatchResult { + let collection = NextCollectionId::::get(); + let owner = T::CreateOrigin::ensure_origin(origin, &collection)?; let admin = T::Lookup::lookup(admin)?; @@ -473,7 +490,6 @@ pub mod pallet { /// /// Unlike `create`, no funds are reserved. /// - /// - `collection`: The identifier of the new item. This must not be currently in use. /// - `owner`: The owner of this collection of items. The owner has full superuser /// permissions /// over this item, but may later change and configure the permissions using @@ -485,13 +501,14 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::force_create())] pub fn force_create( origin: OriginFor, - collection: T::CollectionId, owner: ::Source, free_holding: bool, ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; let owner = T::Lookup::lookup(owner)?; + let collection = NextCollectionId::::get(); + Self::do_create_collection( collection, owner.clone(), @@ -502,6 +519,31 @@ pub mod pallet { ) } + /// Increments the `CollectionId` stored in `NextCollectionId`. + /// + /// This is only callable when the next `CollectionId` is already being + /// used for some other collection. + /// + /// The origin must be Signed and the sender must have sufficient funds + /// free. + /// + /// Emits `NextCollectionIdIncremented` event when successful. + /// + /// Weight: `O(1)` + #[pallet::weight(T::WeightInfo::try_increment_id())] + pub fn try_increment_id(origin: OriginFor) -> DispatchResult { + ensure_signed(origin)?; + ensure!( + Collection::::contains_key(NextCollectionId::::get()), + Error::::NextIdNotUsed + ); + + let next_id = NextCollectionId::::get().saturating_add(1u32.into()); + NextCollectionId::::set(next_id); + Self::deposit_event(Event::NextCollectionIdIncremented { next_id }); + Ok(()) + } + /// Destroy a collection of fungible items. /// /// The origin must conform to `ForceOrigin` or must be `Signed` and the sender must be the diff --git a/frame/uniques/src/tests.rs b/frame/uniques/src/tests.rs index 8b1d00d7ba0c7..bd3a2b032945e 100644 --- a/frame/uniques/src/tests.rs +++ b/frame/uniques/src/tests.rs @@ -92,12 +92,12 @@ fn basic_setup_works() { #[test] fn basic_minting_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Uniques::force_create(Origin::root(), 1, true)); assert_eq!(collections(), vec![(1, 0)]); assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); assert_eq!(items(), vec![(1, 0, 42)]); - assert_ok!(Uniques::force_create(Origin::root(), 1, 2, true)); + assert_ok!(Uniques::force_create(Origin::root(), 2, true)); assert_eq!(collections(), vec![(1, 0), (2, 1)]); assert_ok!(Uniques::mint(Origin::signed(2), 1, 69, 1)); assert_eq!(items(), vec![(1, 0, 42), (1, 1, 69)]); @@ -108,7 +108,7 @@ fn basic_minting_should_work() { fn lifecycle_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Uniques::create(Origin::signed(1), 0, 1)); + assert_ok!(Uniques::create(Origin::signed(1), 1)); assert_eq!(Balances::reserved_balance(&1), 2); assert_eq!(collections(), vec![(1, 0)]); assert_ok!(Uniques::set_collection_metadata(Origin::signed(1), 0, bvec![0, 0], false)); @@ -151,7 +151,7 @@ fn lifecycle_should_work() { fn destroy_with_bad_witness_should_not_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Uniques::create(Origin::signed(1), 0, 1)); + assert_ok!(Uniques::create(Origin::signed(1), 1)); let w = Collection::::get(0).unwrap().destroy_witness(); assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); @@ -162,7 +162,7 @@ fn destroy_with_bad_witness_should_not_work() { #[test] fn mint_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Uniques::force_create(Origin::root(), 1, true)); assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); assert_eq!(Uniques::owner(0, 42).unwrap(), 1); assert_eq!(collections(), vec![(1, 0)]); @@ -173,7 +173,7 @@ fn mint_should_work() { #[test] fn transfer_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Uniques::force_create(Origin::root(), 1, true)); assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 2)); assert_ok!(Uniques::transfer(Origin::signed(2), 0, 42, 3)); @@ -188,7 +188,7 @@ fn transfer_should_work() { #[test] fn freezing_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Uniques::force_create(Origin::root(), 1, true)); assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); assert_ok!(Uniques::freeze(Origin::signed(1), 0, 42)); assert_noop!(Uniques::transfer(Origin::signed(1), 0, 42, 2), Error::::Frozen); @@ -205,7 +205,7 @@ fn freezing_should_work() { #[test] fn origin_guards_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Uniques::force_create(Origin::root(), 1, true)); assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); Balances::make_free_balance_be(&2, 100); @@ -230,7 +230,7 @@ fn transfer_owner_should_work() { Balances::make_free_balance_be(&1, 100); Balances::make_free_balance_be(&2, 100); Balances::make_free_balance_be(&3, 100); - assert_ok!(Uniques::create(Origin::signed(1), 0, 1)); + assert_ok!(Uniques::create(Origin::signed(1), 1)); assert_eq!(collections(), vec![(1, 0)]); assert_noop!( Uniques::transfer_ownership(Origin::signed(1), 0, 2), @@ -275,7 +275,7 @@ fn transfer_owner_should_work() { #[test] fn set_team_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Uniques::force_create(Origin::root(), 1, true)); assert_ok!(Uniques::set_team(Origin::signed(1), 0, 2, 3, 4)); assert_ok!(Uniques::mint(Origin::signed(2), 0, 42, 2)); @@ -294,7 +294,7 @@ fn set_collection_metadata_should_work() { Uniques::set_collection_metadata(Origin::signed(1), 0, bvec![0u8; 20], false), Error::::UnknownCollection, ); - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, false)); + assert_ok!(Uniques::force_create(Origin::root(), 1, false)); // Cannot add metadata to unowned item assert_noop!( Uniques::set_collection_metadata(Origin::signed(2), 0, bvec![0u8; 20], false), @@ -354,7 +354,7 @@ fn set_item_metadata_should_work() { Balances::make_free_balance_be(&1, 30); // Cannot add metadata to unknown item - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, false)); + assert_ok!(Uniques::force_create(Origin::root(), 1, false)); assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); // Cannot add metadata to unowned item assert_noop!( @@ -410,7 +410,7 @@ fn set_attribute_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, false)); + assert_ok!(Uniques::force_create(Origin::root(), 1, false)); assert_ok!(Uniques::set_attribute(Origin::signed(1), 0, None, bvec![0], bvec![0])); assert_ok!(Uniques::set_attribute(Origin::signed(1), 0, Some(0), bvec![0], bvec![0])); @@ -455,7 +455,7 @@ fn set_attribute_should_respect_freeze() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, false)); + assert_ok!(Uniques::force_create(Origin::root(), 1, false)); assert_ok!(Uniques::set_attribute(Origin::signed(1), 0, None, bvec![0], bvec![0])); assert_ok!(Uniques::set_attribute(Origin::signed(1), 0, Some(0), bvec![0], bvec![0])); @@ -487,7 +487,7 @@ fn force_item_status_should_work() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, false)); + assert_ok!(Uniques::force_create(Origin::root(), 1, false)); assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 1)); assert_ok!(Uniques::mint(Origin::signed(1), 0, 69, 2)); assert_ok!(Uniques::set_collection_metadata(Origin::signed(1), 0, bvec![0; 20], false)); @@ -521,7 +521,7 @@ fn force_item_status_should_work() { fn burn_works() { new_test_ext().execute_with(|| { Balances::make_free_balance_be(&1, 100); - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, false)); + assert_ok!(Uniques::force_create(Origin::root(), 1, false)); assert_ok!(Uniques::set_team(Origin::signed(1), 0, 2, 3, 4)); assert_noop!( @@ -545,7 +545,7 @@ fn burn_works() { #[test] fn approval_lifecycle_works() { new_test_ext().execute_with(|| { - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Uniques::force_create(Origin::root(), 1, true)); assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 2)); assert_ok!(Uniques::approve_transfer(Origin::signed(2), 0, 42, 3)); assert_ok!(Uniques::transfer(Origin::signed(3), 0, 42, 4)); @@ -560,7 +560,7 @@ fn approval_lifecycle_works() { #[test] fn cancel_approval_works() { new_test_ext().execute_with(|| { - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Uniques::force_create(Origin::root(), 1, true)); assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 2)); assert_ok!(Uniques::approve_transfer(Origin::signed(2), 0, 42, 3)); @@ -592,7 +592,7 @@ fn cancel_approval_works() { #[test] fn cancel_approval_works_with_admin() { new_test_ext().execute_with(|| { - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Uniques::force_create(Origin::root(), 1, true)); assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 2)); assert_ok!(Uniques::approve_transfer(Origin::signed(2), 0, 42, 3)); @@ -620,7 +620,7 @@ fn cancel_approval_works_with_admin() { #[test] fn cancel_approval_works_with_force() { new_test_ext().execute_with(|| { - assert_ok!(Uniques::force_create(Origin::root(), 0, 1, true)); + assert_ok!(Uniques::force_create(Origin::root(), 1, true)); assert_ok!(Uniques::mint(Origin::signed(1), 0, 42, 2)); assert_ok!(Uniques::approve_transfer(Origin::signed(2), 0, 42, 3)); @@ -653,7 +653,7 @@ fn max_supply_should_work() { let max_supply = 2; // validate set_collection_max_supply - assert_ok!(Uniques::force_create(Origin::root(), collection_id, user_id, true)); + assert_ok!(Uniques::force_create(Origin::root(), user_id, true)); assert!(!CollectionMaxSupply::::contains_key(collection_id)); assert_ok!(Uniques::set_collection_max_supply( @@ -695,15 +695,48 @@ fn max_supply_should_work() { }); } +#[test] +fn try_increment_id_works() { + new_test_ext().execute_with(|| { + // should fail because the next `CollectionId` is not being used. + assert_noop!(Uniques::try_increment_id(Origin::signed(2)), Error::::NextIdNotUsed); + + // create two collections & check for events. + assert_ok!(Uniques::force_create(Origin::root(), 1, true)); + assert!(events().contains(&Event::::NextCollectionIdIncremented { next_id: 1 })); + assert_ok!(Uniques::force_create(Origin::root(), 1, true)); + assert!(events().contains(&Event::::NextCollectionIdIncremented { next_id: 2 })); + + // there are now two collections. + assert_eq!(Uniques::get_next_id(), 2); + + // reset the collections counter to test if the `try_increment_id` + // works. + Uniques::set_next_id(0); + assert_ok!(Uniques::try_increment_id(Origin::signed(2))); + + // `try_increment_id` should emit an event when successful. + assert!(events().contains(&Event::::NextCollectionIdIncremented { next_id: 1 })); + + // because reset, the collections count should be now 1 + assert_eq!(Uniques::get_next_id(), 1); + + // increment the collections count again. + assert_ok!(Uniques::try_increment_id(Origin::signed(2))); + // should fail because the next `CollectionId` is not being used. + assert_noop!(Uniques::try_increment_id(Origin::signed(2)), Error::::NextIdNotUsed); + }); +} + #[test] fn set_price_should_work() { new_test_ext().execute_with(|| { let user_id = 1; - let collection_id = 0; + let collection_id: u32 = 0; let item_1 = 1; let item_2 = 2; - assert_ok!(Uniques::force_create(Origin::root(), collection_id, user_id, true)); + assert_ok!(Uniques::force_create(Origin::root(), user_id, true)); assert_ok!(Uniques::mint(Origin::signed(user_id), collection_id, item_1, user_id)); assert_ok!(Uniques::mint(Origin::signed(user_id), collection_id, item_2, user_id)); @@ -755,7 +788,7 @@ fn buy_item_should_work() { let user_1 = 1; let user_2 = 2; let user_3 = 3; - let collection_id = 0; + let collection_id: u32 = 0; let item_1 = 1; let item_2 = 2; let item_3 = 3; @@ -767,7 +800,7 @@ fn buy_item_should_work() { Balances::make_free_balance_be(&user_2, initial_balance); Balances::make_free_balance_be(&user_3, initial_balance); - assert_ok!(Uniques::force_create(Origin::root(), collection_id, user_1, true)); + assert_ok!(Uniques::force_create(Origin::root(), user_1, true)); assert_ok!(Uniques::mint(Origin::signed(user_1), collection_id, item_1, user_1)); assert_ok!(Uniques::mint(Origin::signed(user_1), collection_id, item_2, user_1)); diff --git a/frame/uniques/src/weights.rs b/frame/uniques/src/weights.rs index 7c8cb170b1b1d..75bb89f26b428 100644 --- a/frame/uniques/src/weights.rs +++ b/frame/uniques/src/weights.rs @@ -18,12 +18,12 @@ //! Autogenerated weights for pallet_uniques //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-07-13, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `test-bench-bot`, CPU: `Intel(R) Xeon(R) CPU @ 3.10GHz` +//! DATE: 2022-07-15, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/production/substrate +// /home/benchbot/cargo_target_dir/production/substrate // benchmark // pallet // --steps=50 @@ -70,6 +70,7 @@ pub trait WeightInfo { fn cancel_approval() -> Weight; fn set_accept_ownership() -> Weight; fn set_collection_max_supply() -> Weight; + fn try_increment_id() -> Weight; fn set_price() -> Weight; fn buy_item() -> Weight; } @@ -77,19 +78,21 @@ pub trait WeightInfo { /// Weights for pallet_uniques using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { + // Storage: Uniques NextCollectionId (r:1 w:1) // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn create() -> Weight { - (33_075_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + (30_481_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) } + // Storage: Uniques NextCollectionId (r:1 w:1) // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn force_create() -> Weight { - (19_528_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - .saturating_add(T::DbWeight::get().writes(2 as Weight)) + (19_811_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(3 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques Asset (r:1 w:0) @@ -104,12 +107,12 @@ impl WeightInfo for SubstrateWeight { /// The range of component `a` is `[0, 1000]`. fn destroy(n: u32, m: u32, a: u32, ) -> Weight { (0 as Weight) - // Standard Error: 25_000 - .saturating_add((13_639_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 25_000 - .saturating_add((2_393_000 as Weight).saturating_mul(m as Weight)) - // Standard Error: 25_000 - .saturating_add((2_217_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 17_000 + .saturating_add((10_950_000 as Weight).saturating_mul(n as Weight)) + // Standard Error: 17_000 + .saturating_add((1_657_000 as Weight).saturating_mul(m as Weight)) + // Standard Error: 17_000 + .saturating_add((1_512_000 as Weight).saturating_mul(a as Weight)) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) .saturating_add(T::DbWeight::get().writes(4 as Weight)) @@ -122,7 +125,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques CollectionMaxSupply (r:1 w:0) // Storage: Uniques Account (r:0 w:1) fn mint() -> Weight { - (42_146_000 as Weight) + (36_980_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(3 as Weight)) } @@ -131,7 +134,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Account (r:0 w:1) // Storage: Uniques ItemPriceOf (r:0 w:1) fn burn() -> Weight { - (42_960_000 as Weight) + (38_771_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -140,7 +143,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Account (r:0 w:2) // Storage: Uniques ItemPriceOf (r:0 w:1) fn transfer() -> Weight { - (33_025_000 as Weight) + (29_914_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -149,8 +152,8 @@ impl WeightInfo for SubstrateWeight { /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { (0 as Weight) - // Standard Error: 24_000 - .saturating_add((15_540_000 as Weight).saturating_mul(i as Weight)) + // Standard Error: 16_000 + .saturating_add((12_759_000 as Weight).saturating_mul(i as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(i as Weight))) .saturating_add(T::DbWeight::get().writes(1 as Weight)) @@ -159,26 +162,26 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn freeze() -> Weight { - (25_194_000 as Weight) + (22_425_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn thaw() -> Weight { - (25_397_000 as Weight) + (23_011_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn freeze_collection() -> Weight { - (19_278_000 as Weight) + (17_718_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn thaw_collection() -> Weight { - (19_304_000 as Weight) + (17_619_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -186,20 +189,20 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:2) fn transfer_ownership() -> Weight { - (28_615_000 as Weight) + (25_869_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn set_team() -> Weight { - (19_943_000 as Weight) + (18_058_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn force_item_status() -> Weight { - (22_583_000 as Weight) + (20_720_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -207,7 +210,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn set_attribute() -> Weight { - (47_520_000 as Weight) + (41_808_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -215,69 +218,76 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn clear_attribute() -> Weight { - (45_316_000 as Weight) + (39_866_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:1) fn set_metadata() -> Weight { - (38_391_000 as Weight) + (34_767_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:1) fn clear_metadata() -> Weight { - (38_023_000 as Weight) + (33_910_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassMetadataOf (r:1 w:1) fn set_collection_metadata() -> Weight { - (37_398_000 as Weight) + (33_827_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques ClassMetadataOf (r:1 w:1) fn clear_collection_metadata() -> Weight { - (35_621_000 as Weight) + (31_998_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) fn approve_transfer() -> Weight { - (25_856_000 as Weight) + (23_607_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) fn cancel_approval() -> Weight { - (26_098_000 as Weight) + (23_341_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques OwnershipAcceptance (r:1 w:1) fn set_accept_ownership() -> Weight { - (24_076_000 as Weight) + (21_969_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques CollectionMaxSupply (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn set_collection_max_supply() -> Weight { - (22_035_000 as Weight) + (20_355_000 as Weight) + .saturating_add(T::DbWeight::get().reads(2 as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight)) + } + // Storage: Uniques NextCollectionId (r:1 w:1) + // Storage: Uniques Class (r:1 w:0) + fn try_increment_id() -> Weight { + (19_233_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Uniques Asset (r:1 w:0) // Storage: Uniques ItemPriceOf (r:0 w:1) fn set_price() -> Weight { - (22_534_000 as Weight) + (20_733_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -286,7 +296,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Account (r:0 w:2) fn buy_item() -> Weight { - (45_272_000 as Weight) + (40_798_000 as Weight) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } @@ -294,19 +304,21 @@ impl WeightInfo for SubstrateWeight { // For backwards compatibility and tests impl WeightInfo for () { + // Storage: Uniques NextCollectionId (r:1 w:1) // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn create() -> Weight { - (33_075_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + (30_481_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } + // Storage: Uniques NextCollectionId (r:1 w:1) // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn force_create() -> Weight { - (19_528_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - .saturating_add(RocksDbWeight::get().writes(2 as Weight)) + (19_811_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques Asset (r:1 w:0) @@ -321,12 +333,12 @@ impl WeightInfo for () { /// The range of component `a` is `[0, 1000]`. fn destroy(n: u32, m: u32, a: u32, ) -> Weight { (0 as Weight) - // Standard Error: 25_000 - .saturating_add((13_639_000 as Weight).saturating_mul(n as Weight)) - // Standard Error: 25_000 - .saturating_add((2_393_000 as Weight).saturating_mul(m as Weight)) - // Standard Error: 25_000 - .saturating_add((2_217_000 as Weight).saturating_mul(a as Weight)) + // Standard Error: 17_000 + .saturating_add((10_950_000 as Weight).saturating_mul(n as Weight)) + // Standard Error: 17_000 + .saturating_add((1_657_000 as Weight).saturating_mul(m as Weight)) + // Standard Error: 17_000 + .saturating_add((1_512_000 as Weight).saturating_mul(a as Weight)) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(n as Weight))) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) @@ -339,7 +351,7 @@ impl WeightInfo for () { // Storage: Uniques CollectionMaxSupply (r:1 w:0) // Storage: Uniques Account (r:0 w:1) fn mint() -> Weight { - (42_146_000 as Weight) + (36_980_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(3 as Weight)) } @@ -348,7 +360,7 @@ impl WeightInfo for () { // Storage: Uniques Account (r:0 w:1) // Storage: Uniques ItemPriceOf (r:0 w:1) fn burn() -> Weight { - (42_960_000 as Weight) + (38_771_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } @@ -357,7 +369,7 @@ impl WeightInfo for () { // Storage: Uniques Account (r:0 w:2) // Storage: Uniques ItemPriceOf (r:0 w:1) fn transfer() -> Weight { - (33_025_000 as Weight) + (29_914_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } @@ -366,8 +378,8 @@ impl WeightInfo for () { /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { (0 as Weight) - // Standard Error: 24_000 - .saturating_add((15_540_000 as Weight).saturating_mul(i as Weight)) + // Standard Error: 16_000 + .saturating_add((12_759_000 as Weight).saturating_mul(i as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(i as Weight))) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) @@ -376,26 +388,26 @@ impl WeightInfo for () { // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn freeze() -> Weight { - (25_194_000 as Weight) + (22_425_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Asset (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn thaw() -> Weight { - (25_397_000 as Weight) + (23_011_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn freeze_collection() -> Weight { - (19_278_000 as Weight) + (17_718_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn thaw_collection() -> Weight { - (19_304_000 as Weight) + (17_619_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -403,20 +415,20 @@ impl WeightInfo for () { // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:2) fn transfer_ownership() -> Weight { - (28_615_000 as Weight) + (25_869_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } // Storage: Uniques Class (r:1 w:1) fn set_team() -> Weight { - (19_943_000 as Weight) + (18_058_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassAccount (r:0 w:1) fn force_item_status() -> Weight { - (22_583_000 as Weight) + (20_720_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -424,7 +436,7 @@ impl WeightInfo for () { // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn set_attribute() -> Weight { - (47_520_000 as Weight) + (41_808_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -432,69 +444,76 @@ impl WeightInfo for () { // Storage: Uniques InstanceMetadataOf (r:1 w:0) // Storage: Uniques Attribute (r:1 w:1) fn clear_attribute() -> Weight { - (45_316_000 as Weight) + (39_866_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:1) fn set_metadata() -> Weight { - (38_391_000 as Weight) + (34_767_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques InstanceMetadataOf (r:1 w:1) fn clear_metadata() -> Weight { - (38_023_000 as Weight) + (33_910_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:1) // Storage: Uniques ClassMetadataOf (r:1 w:1) fn set_collection_metadata() -> Weight { - (37_398_000 as Weight) + (33_827_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques ClassMetadataOf (r:1 w:1) fn clear_collection_metadata() -> Weight { - (35_621_000 as Weight) + (31_998_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) fn approve_transfer() -> Weight { - (25_856_000 as Weight) + (23_607_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Asset (r:1 w:1) fn cancel_approval() -> Weight { - (26_098_000 as Weight) + (23_341_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques OwnershipAcceptance (r:1 w:1) fn set_accept_ownership() -> Weight { - (24_076_000 as Weight) + (21_969_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques CollectionMaxSupply (r:1 w:1) // Storage: Uniques Class (r:1 w:0) fn set_collection_max_supply() -> Weight { - (22_035_000 as Weight) + (20_355_000 as Weight) + .saturating_add(RocksDbWeight::get().reads(2 as Weight)) + .saturating_add(RocksDbWeight::get().writes(1 as Weight)) + } + // Storage: Uniques NextCollectionId (r:1 w:1) + // Storage: Uniques Class (r:1 w:0) + fn try_increment_id() -> Weight { + (19_233_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Uniques Asset (r:1 w:0) // Storage: Uniques ItemPriceOf (r:0 w:1) fn set_price() -> Weight { - (22_534_000 as Weight) + (20_733_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -503,7 +522,7 @@ impl WeightInfo for () { // Storage: Uniques Class (r:1 w:0) // Storage: Uniques Account (r:0 w:2) fn buy_item() -> Weight { - (45_272_000 as Weight) + (40_798_000 as Weight) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } From ebef181f9bd7f18e82117dabe076721dd8600832 Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Mon, 1 Aug 2022 11:44:56 +0100 Subject: [PATCH 444/484] remove unused staking trait bound (#11942) --- frame/nomination-pools/benchmarking/src/mock.rs | 8 -------- frame/nomination-pools/test-staking/src/mock.rs | 8 -------- frame/session/benchmarking/src/mock.rs | 10 ---------- frame/staking/src/mock.rs | 11 +---------- frame/staking/src/pallet/mod.rs | 4 ++-- 5 files changed, 3 insertions(+), 38 deletions(-) diff --git a/frame/nomination-pools/benchmarking/src/mock.rs b/frame/nomination-pools/benchmarking/src/mock.rs index 1c74c301e562d..d239d4f072b80 100644 --- a/frame/nomination-pools/benchmarking/src/mock.rs +++ b/frame/nomination-pools/benchmarking/src/mock.rs @@ -168,14 +168,6 @@ impl pallet_nomination_pools::Config for Runtime { impl crate::Config for Runtime {} -impl frame_system::offchain::SendTransactionTypes for Runtime -where - Call: From, -{ - type OverarchingCall = Call; - type Extrinsic = UncheckedExtrinsic; -} - type Block = frame_system::mocking::MockBlock; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; frame_support::construct_runtime!( diff --git a/frame/nomination-pools/test-staking/src/mock.rs b/frame/nomination-pools/test-staking/src/mock.rs index 852b2c319f376..055ba7b4b3c06 100644 --- a/frame/nomination-pools/test-staking/src/mock.rs +++ b/frame/nomination-pools/test-staking/src/mock.rs @@ -180,14 +180,6 @@ impl pallet_nomination_pools::Config for Runtime { type PalletId = PoolsPalletId; } -impl frame_system::offchain::SendTransactionTypes for Runtime -where - Call: From, -{ - type OverarchingCall = Call; - type Extrinsic = UncheckedExtrinsic; -} - type Block = frame_system::mocking::MockBlock; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; diff --git a/frame/session/benchmarking/src/mock.rs b/frame/session/benchmarking/src/mock.rs index c777f2c56de3a..2181493f72947 100644 --- a/frame/session/benchmarking/src/mock.rs +++ b/frame/session/benchmarking/src/mock.rs @@ -144,16 +144,6 @@ parameter_types! { pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS; } -pub type Extrinsic = sp_runtime::testing::TestXt; - -impl frame_system::offchain::SendTransactionTypes for Test -where - Call: From, -{ - type OverarchingCall = Call; - type Extrinsic = Extrinsic; -} - pub struct OnChainSeqPhragmen; impl onchain::Config for OnChainSeqPhragmen { type System = Test; diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index 70d00c2b648d8..d9dc97f9c1127 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -33,7 +33,7 @@ use sp_core::H256; use sp_io; use sp_runtime::{ curve::PiecewiseLinear, - testing::{Header, TestXt, UintAuthorityId}, + testing::{Header, UintAuthorityId}, traits::{IdentityLookup, Zero}, }; use sp_staking::offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}; @@ -304,15 +304,6 @@ impl crate::pallet::pallet::Config for Test { type WeightInfo = (); } -impl frame_system::offchain::SendTransactionTypes for Test -where - Call: From, -{ - type OverarchingCall = Call; - type Extrinsic = Extrinsic; -} - -pub type Extrinsic = TestXt; pub(crate) type StakingCall = crate::Call; pub(crate) type TestRuntimeCall = ::Call; diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index c999020c28167..4ce96ab68b11a 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -27,7 +27,7 @@ use frame_support::{ }, weights::Weight, }; -use frame_system::{ensure_root, ensure_signed, offchain::SendTransactionTypes, pallet_prelude::*}; +use frame_system::{ensure_root, ensure_signed, pallet_prelude::*}; use sp_runtime::{ traits::{CheckedSub, SaturatedConversion, StaticLookup, Zero}, Perbill, Percent, @@ -73,7 +73,7 @@ pub mod pallet { } #[pallet::config] - pub trait Config: frame_system::Config + SendTransactionTypes> { + pub trait Config: frame_system::Config { /// The staking balance. type Currency: LockableCurrency< Self::AccountId, From 9938f6498ab4dc7fd175727782ce56d3a4eaf287 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Mon, 1 Aug 2022 22:24:22 +0800 Subject: [PATCH 445/484] Add and implement MaxEncodedLen to token traits (#11945) * Add and implement MaxEncodedLen bounds to token traits * cargo fmt * Update UI test expectations --- frame/support/src/traits/tokens/misc.rs | 31 ++++++++++++++----- .../pallet_ui/storage_info_unsatisfied.stderr | 2 +- .../storage_info_unsatisfied_nmap.stderr | 2 +- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/frame/support/src/traits/tokens/misc.rs b/frame/support/src/traits/tokens/misc.rs index fbbef6c31822f..294d0e89c8b9e 100644 --- a/frame/support/src/traits/tokens/misc.rs +++ b/frame/support/src/traits/tokens/misc.rs @@ -17,7 +17,7 @@ //! Miscellaneous types. -use codec::{Decode, Encode, FullCodec}; +use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; use sp_arithmetic::traits::{AtLeast32BitUnsigned, Zero}; use sp_core::RuntimeDebug; use sp_runtime::{ArithmeticError, DispatchError, TokenError}; @@ -116,7 +116,9 @@ pub enum ExistenceRequirement { } /// Status of funds. -#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, RuntimeDebug, scale_info::TypeInfo)] +#[derive( + PartialEq, Eq, Clone, Copy, Encode, Decode, RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen, +)] pub enum BalanceStatus { /// Funds are free, as corresponding to `free` item in Balances. Free, @@ -126,7 +128,7 @@ pub enum BalanceStatus { bitflags::bitflags! { /// Reasons for moving funds out of an account. - #[derive(Encode, Decode)] + #[derive(Encode, Decode, MaxEncodedLen)] pub struct WithdrawReasons: u8 { /// In order to pay for (system) transaction costs. const TRANSACTION_PAYMENT = 0b00000001; @@ -161,16 +163,29 @@ impl WithdrawReasons { } /// Simple amalgamation trait to collect together properties for an AssetId under one roof. -pub trait AssetId: FullCodec + Copy + Eq + PartialEq + Debug + scale_info::TypeInfo {} -impl AssetId for T {} +pub trait AssetId: + FullCodec + Copy + Eq + PartialEq + Debug + scale_info::TypeInfo + MaxEncodedLen +{ +} +impl AssetId + for T +{ +} /// Simple amalgamation trait to collect together properties for a Balance under one roof. pub trait Balance: - AtLeast32BitUnsigned + FullCodec + Copy + Default + Debug + scale_info::TypeInfo + AtLeast32BitUnsigned + FullCodec + Copy + Default + Debug + scale_info::TypeInfo + MaxEncodedLen { } -impl Balance - for T +impl< + T: AtLeast32BitUnsigned + + FullCodec + + Copy + + Default + + Debug + + scale_info::TypeInfo + + MaxEncodedLen, + > Balance for T { } diff --git a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr index d87e6900197f5..c2ec8cf7f4d05 100644 --- a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr +++ b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr @@ -13,5 +13,5 @@ error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) - and 70 others + and 72 others = note: required because of the requirements on the impl of `StorageInfoTrait` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` diff --git a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr index bc8e99cd65006..dbbc426de2906 100644 --- a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr +++ b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr @@ -13,6 +13,6 @@ error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) - and 70 others + and 72 others = note: required because of the requirements on the impl of `KeyGeneratorMaxEncodedLen` for `Key` = note: required because of the requirements on the impl of `StorageInfoTrait` for `frame_support::pallet_prelude::StorageNMap<_GeneratedPrefixForStorageFoo, Key, u32>` From c1ac58557f46eeda4aac7a0e9441f756511d709e Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Mon, 1 Aug 2022 20:56:19 +0200 Subject: [PATCH 446/484] Remove `remove_member_wrong_refund` from `phragmen` WeightInfo (#11952) * Remove 'remove_member_wrong_refund' from WeightInfo Signed-off-by: Oliver Tale-Yazdi * ".git/.scripts/bench-bot.sh" pallet dev pallet_elections_phragmen Co-authored-by: command-bot <> --- frame/elections-phragmen/src/weights.rs | 181 ++++++++++++------------ 1 file changed, 93 insertions(+), 88 deletions(-) diff --git a/frame/elections-phragmen/src/weights.rs b/frame/elections-phragmen/src/weights.rs index 5ad986bf142a3..07ee7aa2012ad 100644 --- a/frame/elections-phragmen/src/weights.rs +++ b/frame/elections-phragmen/src/weights.rs @@ -18,22 +18,24 @@ //! Autogenerated weights for pallet_elections_phragmen //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-08-01, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// ./target/production/substrate +// /home/benchbot/cargo_target_dir/production/substrate // benchmark // pallet -// --chain=dev // --steps=50 // --repeat=20 -// --pallet=pallet_elections_phragmen // --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --template=./.maintain/frame-weight-template.hbs +// --heap-pages=4096 +// --pallet=pallet_elections_phragmen +// --chain=dev // --output=./frame/elections-phragmen/src/weights.rs +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -54,7 +56,6 @@ pub trait WeightInfo { fn renounce_candidacy_runners_up() -> Weight; fn remove_member_without_replacement() -> Weight; fn remove_member_with_replacement() -> Weight; - fn remove_member_wrong_refund() -> Weight; fn clean_defunct_voters(v: u32, d: u32, ) -> Weight; fn election_phragmen(c: u32, v: u32, e: u32, ) -> Weight; } @@ -67,10 +68,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Elections RunnersUp (r:1 w:0) // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) + /// The range of component `v` is `[1, 16]`. fn vote_equal(v: u32, ) -> Weight { - (27_798_000 as Weight) - // Standard Error: 5_000 - .saturating_add((238_000 as Weight).saturating_mul(v as Weight)) + (26_143_000 as Weight) + // Standard Error: 23_000 + .saturating_add((297_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -79,10 +81,11 @@ impl WeightInfo for SubstrateWeight { // Storage: Elections RunnersUp (r:1 w:0) // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) + /// The range of component `v` is `[2, 16]`. fn vote_more(v: u32, ) -> Weight { - (41_241_000 as Weight) - // Standard Error: 8_000 - .saturating_add((259_000 as Weight).saturating_mul(v as Weight)) + (40_431_000 as Weight) + // Standard Error: 6_000 + .saturating_add((205_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } @@ -91,35 +94,38 @@ impl WeightInfo for SubstrateWeight { // Storage: Elections RunnersUp (r:1 w:0) // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) + /// The range of component `v` is `[2, 16]`. fn vote_less(v: u32, ) -> Weight { - (41_313_000 as Weight) - // Standard Error: 8_000 - .saturating_add((255_000 as Weight).saturating_mul(v as Weight)) + (40_188_000 as Weight) + // Standard Error: 6_000 + .saturating_add((225_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn remove_voter() -> Weight { - (39_218_000 as Weight) + (38_031_000 as Weight) .saturating_add(T::DbWeight::get().reads(2 as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight)) } // Storage: Elections Candidates (r:1 w:1) // Storage: Elections Members (r:1 w:0) // Storage: Elections RunnersUp (r:1 w:0) + /// The range of component `c` is `[1, 1000]`. fn submit_candidacy(c: u32, ) -> Weight { - (41_348_000 as Weight) - // Standard Error: 1_000 - .saturating_add((112_000 as Weight).saturating_mul(c as Weight)) + (43_715_000 as Weight) + // Standard Error: 0 + .saturating_add((49_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(3 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } // Storage: Elections Candidates (r:1 w:1) + /// The range of component `c` is `[1, 1000]`. fn renounce_candidacy_candidate(c: u32, ) -> Weight { - (35_522_000 as Weight) + (47_882_000 as Weight) // Standard Error: 1_000 - .saturating_add((92_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((25_000 as Weight).saturating_mul(c as Weight)) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -129,13 +135,13 @@ impl WeightInfo for SubstrateWeight { // Storage: Council Proposals (r:1 w:0) // Storage: Council Members (r:0 w:1) fn renounce_candidacy_members() -> Weight { - (47_887_000 as Weight) + (45_600_000 as Weight) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } // Storage: Elections RunnersUp (r:1 w:1) fn renounce_candidacy_runners_up() -> Weight { - (36_271_000 as Weight) + (34_959_000 as Weight) .saturating_add(T::DbWeight::get().reads(1 as Weight)) .saturating_add(T::DbWeight::get().writes(1 as Weight)) } @@ -143,34 +149,29 @@ impl WeightInfo for SubstrateWeight { fn remove_member_without_replacement() -> Weight { (2_000_000_000_000 as Weight) } - // Storage: Elections RunnersUp (r:1 w:1) // Storage: Elections Members (r:1 w:1) // Storage: System Account (r:1 w:1) + // Storage: Elections RunnersUp (r:1 w:1) // Storage: Council Prime (r:1 w:1) // Storage: Council Proposals (r:1 w:0) // Storage: Council Members (r:0 w:1) fn remove_member_with_replacement() -> Weight { - (55_024_000 as Weight) + (52_684_000 as Weight) .saturating_add(T::DbWeight::get().reads(5 as Weight)) .saturating_add(T::DbWeight::get().writes(5 as Weight)) } - // Storage: Elections RunnersUp (r:1 w:0) - fn remove_member_wrong_refund() -> Weight { - (13_089_000 as Weight) - .saturating_add(T::DbWeight::get().reads(1 as Weight)) - } - // Storage: Elections Voting (r:251 w:250) + // Storage: Elections Voting (r:5001 w:5000) // Storage: Elections Members (r:1 w:0) // Storage: Elections RunnersUp (r:1 w:0) // Storage: Elections Candidates (r:1 w:0) - // Storage: Balances Locks (r:250 w:250) - // Storage: System Account (r:250 w:250) - fn clean_defunct_voters(v: u32, d: u32, ) -> Weight { + // Storage: Balances Locks (r:5000 w:5000) + // Storage: System Account (r:5000 w:5000) + /// The range of component `v` is `[5000, 10000]`. + /// The range of component `d` is `[1, 5000]`. + fn clean_defunct_voters(v: u32, _d: u32, ) -> Weight { (0 as Weight) - // Standard Error: 40_000 - .saturating_add((51_848_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 38_000 - .saturating_add((537_000 as Weight).saturating_mul(d as Weight)) + // Standard Error: 65_000 + .saturating_add((64_009_000 as Weight).saturating_mul(v as Weight)) .saturating_add(T::DbWeight::get().reads(4 as Weight)) .saturating_add(T::DbWeight::get().reads((3 as Weight).saturating_mul(v as Weight))) .saturating_add(T::DbWeight::get().writes((3 as Weight).saturating_mul(v as Weight))) @@ -178,21 +179,23 @@ impl WeightInfo for SubstrateWeight { // Storage: Elections Candidates (r:1 w:1) // Storage: Elections Members (r:1 w:1) // Storage: Elections RunnersUp (r:1 w:1) - // Storage: Elections Voting (r:502 w:0) + // Storage: Elections Voting (r:10001 w:0) // Storage: Council Proposals (r:1 w:0) // Storage: Elections ElectionRounds (r:1 w:1) // Storage: Council Members (r:0 w:1) // Storage: Council Prime (r:0 w:1) - // Storage: System Account (r:2 w:2) + // Storage: System Account (r:19 w:19) + /// The range of component `c` is `[1, 1000]`. + /// The range of component `v` is `[1, 10000]`. + /// The range of component `e` is `[10000, 160000]`. fn election_phragmen(c: u32, v: u32, e: u32, ) -> Weight { (0 as Weight) - // Standard Error: 1_664_000 - .saturating_add((30_736_000 as Weight).saturating_mul(c as Weight)) - // Standard Error: 692_000 - .saturating_add((49_739_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 47_000 - .saturating_add((3_363_000 as Weight).saturating_mul(e as Weight)) - .saturating_add(T::DbWeight::get().reads((2 as Weight).saturating_mul(c as Weight))) + // Standard Error: 778_000 + .saturating_add((81_049_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 51_000 + .saturating_add((4_420_000 as Weight).saturating_mul(e as Weight)) + .saturating_add(T::DbWeight::get().reads(279 as Weight)) + .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(c as Weight))) .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(v as Weight))) .saturating_add(T::DbWeight::get().writes((1 as Weight).saturating_mul(c as Weight))) } @@ -205,10 +208,11 @@ impl WeightInfo for () { // Storage: Elections RunnersUp (r:1 w:0) // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) + /// The range of component `v` is `[1, 16]`. fn vote_equal(v: u32, ) -> Weight { - (27_798_000 as Weight) - // Standard Error: 5_000 - .saturating_add((238_000 as Weight).saturating_mul(v as Weight)) + (26_143_000 as Weight) + // Standard Error: 23_000 + .saturating_add((297_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -217,10 +221,11 @@ impl WeightInfo for () { // Storage: Elections RunnersUp (r:1 w:0) // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) + /// The range of component `v` is `[2, 16]`. fn vote_more(v: u32, ) -> Weight { - (41_241_000 as Weight) - // Standard Error: 8_000 - .saturating_add((259_000 as Weight).saturating_mul(v as Weight)) + (40_431_000 as Weight) + // Standard Error: 6_000 + .saturating_add((205_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } @@ -229,35 +234,38 @@ impl WeightInfo for () { // Storage: Elections RunnersUp (r:1 w:0) // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) + /// The range of component `v` is `[2, 16]`. fn vote_less(v: u32, ) -> Weight { - (41_313_000 as Weight) - // Standard Error: 8_000 - .saturating_add((255_000 as Weight).saturating_mul(v as Weight)) + (40_188_000 as Weight) + // Standard Error: 6_000 + .saturating_add((225_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Elections Voting (r:1 w:1) // Storage: Balances Locks (r:1 w:1) fn remove_voter() -> Weight { - (39_218_000 as Weight) + (38_031_000 as Weight) .saturating_add(RocksDbWeight::get().reads(2 as Weight)) .saturating_add(RocksDbWeight::get().writes(2 as Weight)) } // Storage: Elections Candidates (r:1 w:1) // Storage: Elections Members (r:1 w:0) // Storage: Elections RunnersUp (r:1 w:0) + /// The range of component `c` is `[1, 1000]`. fn submit_candidacy(c: u32, ) -> Weight { - (41_348_000 as Weight) - // Standard Error: 1_000 - .saturating_add((112_000 as Weight).saturating_mul(c as Weight)) + (43_715_000 as Weight) + // Standard Error: 0 + .saturating_add((49_000 as Weight).saturating_mul(c as Weight)) .saturating_add(RocksDbWeight::get().reads(3 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } // Storage: Elections Candidates (r:1 w:1) + /// The range of component `c` is `[1, 1000]`. fn renounce_candidacy_candidate(c: u32, ) -> Weight { - (35_522_000 as Weight) + (47_882_000 as Weight) // Standard Error: 1_000 - .saturating_add((92_000 as Weight).saturating_mul(c as Weight)) + .saturating_add((25_000 as Weight).saturating_mul(c as Weight)) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -267,13 +275,13 @@ impl WeightInfo for () { // Storage: Council Proposals (r:1 w:0) // Storage: Council Members (r:0 w:1) fn renounce_candidacy_members() -> Weight { - (47_887_000 as Weight) + (45_600_000 as Weight) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } // Storage: Elections RunnersUp (r:1 w:1) fn renounce_candidacy_runners_up() -> Weight { - (36_271_000 as Weight) + (34_959_000 as Weight) .saturating_add(RocksDbWeight::get().reads(1 as Weight)) .saturating_add(RocksDbWeight::get().writes(1 as Weight)) } @@ -281,34 +289,29 @@ impl WeightInfo for () { fn remove_member_without_replacement() -> Weight { (2_000_000_000_000 as Weight) } - // Storage: Elections RunnersUp (r:1 w:1) // Storage: Elections Members (r:1 w:1) // Storage: System Account (r:1 w:1) + // Storage: Elections RunnersUp (r:1 w:1) // Storage: Council Prime (r:1 w:1) // Storage: Council Proposals (r:1 w:0) // Storage: Council Members (r:0 w:1) fn remove_member_with_replacement() -> Weight { - (55_024_000 as Weight) + (52_684_000 as Weight) .saturating_add(RocksDbWeight::get().reads(5 as Weight)) .saturating_add(RocksDbWeight::get().writes(5 as Weight)) } - // Storage: Elections RunnersUp (r:1 w:0) - fn remove_member_wrong_refund() -> Weight { - (13_089_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(1 as Weight)) - } - // Storage: Elections Voting (r:251 w:250) + // Storage: Elections Voting (r:5001 w:5000) // Storage: Elections Members (r:1 w:0) // Storage: Elections RunnersUp (r:1 w:0) // Storage: Elections Candidates (r:1 w:0) - // Storage: Balances Locks (r:250 w:250) - // Storage: System Account (r:250 w:250) - fn clean_defunct_voters(v: u32, d: u32, ) -> Weight { + // Storage: Balances Locks (r:5000 w:5000) + // Storage: System Account (r:5000 w:5000) + /// The range of component `v` is `[5000, 10000]`. + /// The range of component `d` is `[1, 5000]`. + fn clean_defunct_voters(v: u32, _d: u32, ) -> Weight { (0 as Weight) - // Standard Error: 40_000 - .saturating_add((51_848_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 38_000 - .saturating_add((537_000 as Weight).saturating_mul(d as Weight)) + // Standard Error: 65_000 + .saturating_add((64_009_000 as Weight).saturating_mul(v as Weight)) .saturating_add(RocksDbWeight::get().reads(4 as Weight)) .saturating_add(RocksDbWeight::get().reads((3 as Weight).saturating_mul(v as Weight))) .saturating_add(RocksDbWeight::get().writes((3 as Weight).saturating_mul(v as Weight))) @@ -316,21 +319,23 @@ impl WeightInfo for () { // Storage: Elections Candidates (r:1 w:1) // Storage: Elections Members (r:1 w:1) // Storage: Elections RunnersUp (r:1 w:1) - // Storage: Elections Voting (r:502 w:0) + // Storage: Elections Voting (r:10001 w:0) // Storage: Council Proposals (r:1 w:0) // Storage: Elections ElectionRounds (r:1 w:1) // Storage: Council Members (r:0 w:1) // Storage: Council Prime (r:0 w:1) - // Storage: System Account (r:2 w:2) + // Storage: System Account (r:19 w:19) + /// The range of component `c` is `[1, 1000]`. + /// The range of component `v` is `[1, 10000]`. + /// The range of component `e` is `[10000, 160000]`. fn election_phragmen(c: u32, v: u32, e: u32, ) -> Weight { (0 as Weight) - // Standard Error: 1_664_000 - .saturating_add((30_736_000 as Weight).saturating_mul(c as Weight)) - // Standard Error: 692_000 - .saturating_add((49_739_000 as Weight).saturating_mul(v as Weight)) - // Standard Error: 47_000 - .saturating_add((3_363_000 as Weight).saturating_mul(e as Weight)) - .saturating_add(RocksDbWeight::get().reads((2 as Weight).saturating_mul(c as Weight))) + // Standard Error: 778_000 + .saturating_add((81_049_000 as Weight).saturating_mul(v as Weight)) + // Standard Error: 51_000 + .saturating_add((4_420_000 as Weight).saturating_mul(e as Weight)) + .saturating_add(RocksDbWeight::get().reads(279 as Weight)) + .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(c as Weight))) .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(v as Weight))) .saturating_add(RocksDbWeight::get().writes((1 as Weight).saturating_mul(c as Weight))) } From 021cbd540cbbaf53da0efbbb294f097fc3bd353d Mon Sep 17 00:00:00 2001 From: lisa-parity <92225469+lisa-parity@users.noreply.github.com> Date: Mon, 1 Aug 2022 11:58:42 -0700 Subject: [PATCH 447/484] Update the link to the runtime versioning topic (#11957) --- bin/node-template/runtime/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/node-template/runtime/src/lib.rs b/bin/node-template/runtime/src/lib.rs index a4382cc9038ce..88fc86db02ef9 100644 --- a/bin/node-template/runtime/src/lib.rs +++ b/bin/node-template/runtime/src/lib.rs @@ -89,8 +89,8 @@ pub mod opaque { } } -// To learn more about runtime versioning and what each of the following value means: -// https://docs.substrate.io/v3/runtime/upgrades#runtime-versioning +// To learn more about runtime versioning, see: +// https://docs.substrate.io/main-docs/build/upgrade#runtime-versioning #[sp_version::runtime_version] pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("node-template"), From 7591a05c9ee337c997bc7084defbc612dec4138d Mon Sep 17 00:00:00 2001 From: Niklas Adolfsson Date: Tue, 2 Aug 2022 09:06:36 +0200 Subject: [PATCH 448/484] fix(rpc middleware): fix `is_error` bug (#11951) * fix(rpc middleware): fix `is_error` bug * Update client/rpc-servers/src/middleware.rs --- client/rpc-servers/src/middleware.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/rpc-servers/src/middleware.rs b/client/rpc-servers/src/middleware.rs index 5b2ee4bedb7dd..cb9918268f4de 100644 --- a/client/rpc-servers/src/middleware.rs +++ b/client/rpc-servers/src/middleware.rs @@ -176,7 +176,9 @@ impl Middleware for RpcMiddleware { .with_label_values(&[ self.transport_label, name, - if success { "true" } else { "false" }, + // the label "is_error", so `success` should be regarded as false + // and vice-versa to be registrered correctly. + if success { "false" } else { "true" }, ]) .inc(); } From ed0d2abbfc306bf882b956490ef92b69bfd24cdb Mon Sep 17 00:00:00 2001 From: bernardo Date: Tue, 2 Aug 2022 13:43:57 +0100 Subject: [PATCH 449/484] benchmarking f32 step calculation (#11890) * benchmark steps with f32 * divide by self.steps - 1 * avoid steps <= 1 * Fix step size Signed-off-by: Oliver Tale-Yazdi * Fix 'steps' print and print number of args Signed-off-by: Oliver Tale-Yazdi * Add Test Signed-off-by: Oliver Tale-Yazdi * fix comments * Remove unneeded comment Signed-off-by: Oliver Tale-Yazdi Co-authored-by: Oliver Tale-Yazdi Co-authored-by: parity-processbot <> --- bin/node/cli/tests/benchmark_pallet_works.rs | 49 +++++++++++++++++++ .../benchmarking-cli/src/pallet/command.rs | 27 ++++++---- 2 files changed, 67 insertions(+), 9 deletions(-) create mode 100644 bin/node/cli/tests/benchmark_pallet_works.rs diff --git a/bin/node/cli/tests/benchmark_pallet_works.rs b/bin/node/cli/tests/benchmark_pallet_works.rs new file mode 100644 index 0000000000000..bf29c0e308bcb --- /dev/null +++ b/bin/node/cli/tests/benchmark_pallet_works.rs @@ -0,0 +1,49 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use assert_cmd::cargo::cargo_bin; +use std::process::Command; + +pub mod common; + +/// `benchmark pallet` works for the different combinations of `steps` and `repeat`. +#[test] +fn benchmark_pallet_works() { + // Some invalid combinations: + benchmark_pallet(0, 10, false); + benchmark_pallet(1, 10, false); + // ... and some valid: + benchmark_pallet(2, 1, true); + benchmark_pallet(50, 20, true); + benchmark_pallet(20, 50, true); +} + +fn benchmark_pallet(steps: u32, repeat: u32, should_work: bool) { + let output = Command::new(cargo_bin("substrate")) + .args(["benchmark", "pallet", "--dev"]) + // Use the `addition` benchmark since is the fastest. + .args(["--pallet", "frame-benchmarking", "--extrinsic", "addition"]) + .args(["--steps", &format!("{}", steps), "--repeat", &format!("{}", repeat)]) + .output() + .unwrap(); + + if output.status.success() != should_work { + let log = String::from_utf8_lossy(&output.stderr).to_string(); + panic!("Test failed:\n{}", log); + } +} diff --git a/utils/frame/benchmarking-cli/src/pallet/command.rs b/utils/frame/benchmarking-cli/src/pallet/command.rs index fae5a4494cdc4..fb6f1393650ad 100644 --- a/utils/frame/benchmarking-cli/src/pallet/command.rs +++ b/utils/frame/benchmarking-cli/src/pallet/command.rs @@ -236,14 +236,22 @@ impl PalletCmd { let lowest = self.lowest_range_values.get(idx).cloned().unwrap_or(*low); let highest = self.highest_range_values.get(idx).cloned().unwrap_or(*high); - let diff = highest - lowest; + let diff = + highest.checked_sub(lowest).ok_or("`low` cannot be higher than `high`")?; - // Create up to `STEPS` steps for that component between high and low. - let step_size = (diff / self.steps).max(1); - let num_of_steps = diff / step_size + 1; - for s in 0..num_of_steps { + // The slope logic needs at least two points + // to compute a slope. + if self.steps < 2 { + return Err("`steps` must be at least 2.".into()) + } + + let step_size = (diff as f32 / (self.steps - 1) as f32).max(0.0); + + for s in 0..self.steps { // This is the value we will be testing for component `name` - let component_value = lowest + step_size * s; + let component_value = ((lowest as f32 + step_size * s as f32) as u32) + .min(highest) + .max(lowest); // Select the max value for all the other components. let c: Vec<(BenchmarkParameter, u32)> = components @@ -364,13 +372,14 @@ impl PalletCmd { if elapsed >= time::Duration::from_secs(5) { timer = time::SystemTime::now(); log::info!( - "Running Benchmark: {}.{} {}/{} {}/{}", + "Running Benchmark: {}.{}({} args) {}/{} {}/{}", String::from_utf8(pallet.clone()) .expect("Encoded from String; qed"), String::from_utf8(extrinsic.clone()) .expect("Encoded from String; qed"), - s + 1, // s starts at 0. todo show step - self.steps, + components.len(), + s + 1, // s starts at 0. + all_components.len(), r + 1, self.external_repeat, ); From 8a4a5a318bfbedf1cb7f94933540911fa9b5d288 Mon Sep 17 00:00:00 2001 From: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Date: Tue, 2 Aug 2022 15:01:15 +0200 Subject: [PATCH 450/484] [ci] chores: remove cargo install nextest (#11961) --- scripts/ci/gitlab/pipeline/test.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/scripts/ci/gitlab/pipeline/test.yml b/scripts/ci/gitlab/pipeline/test.yml index b2daf5bada24f..7a9f57e9a7734 100644 --- a/scripts/ci/gitlab/pipeline/test.yml +++ b/scripts/ci/gitlab/pipeline/test.yml @@ -213,8 +213,6 @@ test-linux-stable: parallel: 3 script: - rusty-cachier snapshot create - # TODO: remove when current paritytech/ci-linux:staging (with rust stable 1.62) is switched to production - - time cargo install cargo-nextest # this job runs all tests in former runtime-benchmarks, frame-staking and wasmtime tests # tests are partitioned by nextest and executed in parallel on $CI_NODE_TOTAL runners # node-cli is excluded until https://github.com/paritytech/substrate/issues/11321 fixed @@ -373,8 +371,6 @@ test-wasmer-sandbox: parallel: 3 script: - rusty-cachier snapshot create - # TODO: remove when current paritytech/ci-linux:staging (with rust stable 1.62) is switched to production - - cargo install cargo-nextest - echo "Node index - ${CI_NODE_INDEX}. Total amount - ${CI_NODE_TOTAL}" - time cargo nextest run --release --features runtime-benchmarks,wasmer-sandbox,disable-ui-tests --partition count:${CI_NODE_INDEX}/${CI_NODE_TOTAL} - if [ ${CI_NODE_INDEX} == 1 ]; then rusty-cachier cache upload; fi From 57786590cad18ed96c1de13dca510e7662c4e30e Mon Sep 17 00:00:00 2001 From: Nikos Kontakis Date: Tue, 2 Aug 2022 17:25:52 +0200 Subject: [PATCH 451/484] Rename `node-runtime` to `node-kitchensink-runtime` (#11930) * Rename node=runtime to kithensink-runtime * Undo md formatting --- Cargo.lock | 198 +++++++++--------- bin/node/bench/Cargo.toml | 2 +- bin/node/bench/src/import.rs | 4 +- bin/node/cli/Cargo.toml | 6 +- bin/node/cli/benches/block_production.rs | 6 +- bin/node/cli/benches/transaction_pool.rs | 2 +- bin/node/cli/src/benchmarking.rs | 2 +- bin/node/cli/src/chain_spec.rs | 6 +- bin/node/cli/src/command.rs | 4 +- bin/node/cli/src/service.rs | 42 ++-- bin/node/executor/Cargo.toml | 2 +- bin/node/executor/benches/bench.rs | 12 +- bin/node/executor/src/lib.rs | 4 +- bin/node/executor/tests/basic.rs | 6 +- bin/node/executor/tests/common.rs | 12 +- bin/node/executor/tests/fees.rs | 4 +- bin/node/executor/tests/submit_transaction.rs | 2 +- bin/node/runtime/Cargo.toml | 2 +- bin/node/testing/Cargo.toml | 2 +- bin/node/testing/src/bench.rs | 15 +- bin/node/testing/src/client.rs | 8 +- bin/node/testing/src/genesis.rs | 2 +- bin/node/testing/src/keyring.rs | 2 +- bin/utils/subkey/src/main.rs | 2 +- docs/README.adoc | 2 +- scripts/ci/gitlab/pipeline/test.yml | 6 +- .../generate-bags/node-runtime/Cargo.toml | 4 +- .../generate-bags/node-runtime/src/main.rs | 7 +- utils/wasm-builder/src/lib.rs | 2 +- 29 files changed, 188 insertions(+), 180 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2b76f61df7912..733c6d73d2709 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3299,6 +3299,100 @@ dependencies = [ "tiny-keccak", ] +[[package]] +name = "kitchensink-runtime" +version = "3.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-election-provider-support", + "frame-executive", + "frame-support", + "frame-system", + "frame-system-benchmarking", + "frame-system-rpc-runtime-api", + "frame-try-runtime", + "hex-literal", + "log", + "node-primitives", + "pallet-alliance", + "pallet-asset-tx-payment", + "pallet-assets", + "pallet-authority-discovery", + "pallet-authorship", + "pallet-babe", + "pallet-bags-list", + "pallet-balances", + "pallet-bounties", + "pallet-child-bounties", + "pallet-collective", + "pallet-contracts", + "pallet-contracts-primitives", + "pallet-contracts-rpc-runtime-api", + "pallet-conviction-voting", + "pallet-democracy", + "pallet-election-provider-multi-phase", + "pallet-election-provider-support-benchmarking", + "pallet-elections-phragmen", + "pallet-gilt", + "pallet-grandpa", + "pallet-identity", + "pallet-im-online", + "pallet-indices", + "pallet-lottery", + "pallet-membership", + "pallet-mmr", + "pallet-multisig", + "pallet-nomination-pools", + "pallet-nomination-pools-benchmarking", + "pallet-nomination-pools-runtime-api", + "pallet-offences", + "pallet-offences-benchmarking", + "pallet-preimage", + "pallet-proxy", + "pallet-randomness-collective-flip", + "pallet-ranked-collective", + "pallet-recovery", + "pallet-referenda", + "pallet-remark", + "pallet-scheduler", + "pallet-session", + "pallet-session-benchmarking", + "pallet-society", + "pallet-staking", + "pallet-staking-reward-curve", + "pallet-state-trie-migration", + "pallet-sudo", + "pallet-timestamp", + "pallet-tips", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc-runtime-api", + "pallet-transaction-storage", + "pallet-treasury", + "pallet-uniques", + "pallet-utility", + "pallet-vesting", + "pallet-whitelist", + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-authority-discovery", + "sp-block-builder", + "sp-consensus-babe", + "sp-core", + "sp-inherents", + "sp-io", + "sp-offchain", + "sp-runtime", + "sp-sandbox", + "sp-session", + "sp-staking", + "sp-std", + "sp-transaction-pool", + "sp-version", + "static_assertions", + "substrate-wasm-builder", +] + [[package]] name = "kv-log-macro" version = "1.0.7" @@ -4511,12 +4605,12 @@ dependencies = [ "futures", "hash-db", "hex", + "kitchensink-runtime", "kvdb", "kvdb-rocksdb", "lazy_static", "log", "node-primitives", - "node-runtime", "node-testing", "parity-db", "parity-util-mem", @@ -4553,13 +4647,13 @@ dependencies = [ "futures", "hex-literal", "jsonrpsee", + "kitchensink-runtime", "log", "nix 0.23.1", "node-executor", "node-inspect", "node-primitives", "node-rpc", - "node-runtime", "pallet-asset-tx-payment", "pallet-balances", "pallet-im-online", @@ -4631,8 +4725,8 @@ dependencies = [ "frame-support", "frame-system", "futures", + "kitchensink-runtime", "node-primitives", - "node-runtime", "node-testing", "pallet-balances", "pallet-contracts", @@ -4716,107 +4810,13 @@ dependencies = [ "substrate-state-trie-migration-rpc", ] -[[package]] -name = "node-runtime" -version = "3.0.0-dev" -dependencies = [ - "frame-benchmarking", - "frame-election-provider-support", - "frame-executive", - "frame-support", - "frame-system", - "frame-system-benchmarking", - "frame-system-rpc-runtime-api", - "frame-try-runtime", - "hex-literal", - "log", - "node-primitives", - "pallet-alliance", - "pallet-asset-tx-payment", - "pallet-assets", - "pallet-authority-discovery", - "pallet-authorship", - "pallet-babe", - "pallet-bags-list", - "pallet-balances", - "pallet-bounties", - "pallet-child-bounties", - "pallet-collective", - "pallet-contracts", - "pallet-contracts-primitives", - "pallet-contracts-rpc-runtime-api", - "pallet-conviction-voting", - "pallet-democracy", - "pallet-election-provider-multi-phase", - "pallet-election-provider-support-benchmarking", - "pallet-elections-phragmen", - "pallet-gilt", - "pallet-grandpa", - "pallet-identity", - "pallet-im-online", - "pallet-indices", - "pallet-lottery", - "pallet-membership", - "pallet-mmr", - "pallet-multisig", - "pallet-nomination-pools", - "pallet-nomination-pools-benchmarking", - "pallet-nomination-pools-runtime-api", - "pallet-offences", - "pallet-offences-benchmarking", - "pallet-preimage", - "pallet-proxy", - "pallet-randomness-collective-flip", - "pallet-ranked-collective", - "pallet-recovery", - "pallet-referenda", - "pallet-remark", - "pallet-scheduler", - "pallet-session", - "pallet-session-benchmarking", - "pallet-society", - "pallet-staking", - "pallet-staking-reward-curve", - "pallet-state-trie-migration", - "pallet-sudo", - "pallet-timestamp", - "pallet-tips", - "pallet-transaction-payment", - "pallet-transaction-payment-rpc-runtime-api", - "pallet-transaction-storage", - "pallet-treasury", - "pallet-uniques", - "pallet-utility", - "pallet-vesting", - "pallet-whitelist", - "parity-scale-codec", - "scale-info", - "sp-api", - "sp-authority-discovery", - "sp-block-builder", - "sp-consensus-babe", - "sp-core", - "sp-inherents", - "sp-io", - "sp-offchain", - "sp-runtime", - "sp-sandbox", - "sp-session", - "sp-staking", - "sp-std", - "sp-transaction-pool", - "sp-version", - "static_assertions", - "substrate-wasm-builder", -] - [[package]] name = "node-runtime-generate-bags" version = "3.0.0" dependencies = [ "clap 3.1.18", "generate-bags", - "node-runtime", + "kitchensink-runtime", ] [[package]] @@ -4905,10 +4905,10 @@ dependencies = [ "frame-system", "fs_extra", "futures", + "kitchensink-runtime", "log", "node-executor", "node-primitives", - "node-runtime", "pallet-asset-tx-payment", "pallet-transaction-payment", "parity-scale-codec", diff --git a/bin/node/bench/Cargo.toml b/bin/node/bench/Cargo.toml index 90c325b8e5b32..4d090c71a72e9 100644 --- a/bin/node/bench/Cargo.toml +++ b/bin/node/bench/Cargo.toml @@ -13,7 +13,7 @@ clap = { version = "3.1.18", features = ["derive"] } log = "0.4.17" node-primitives = { version = "2.0.0", path = "../primitives" } node-testing = { version = "3.0.0-dev", path = "../testing" } -node-runtime = { version = "3.0.0-dev", path = "../runtime" } +kitchensink-runtime = { version = "3.0.0-dev", path = "../runtime" } sc-client-api = { version = "4.0.0-dev", path = "../../../client/api/" } sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } sp-state-machine = { version = "0.12.0", path = "../../../primitives/state-machine" } diff --git a/bin/node/bench/src/import.rs b/bin/node/bench/src/import.rs index b9229fbd5331d..47f630eb68700 100644 --- a/bin/node/bench/src/import.rs +++ b/bin/node/bench/src/import.rs @@ -148,13 +148,13 @@ impl core::Benchmark for ImportBenchmark { // the transaction fee into the treasury // - extrinsic success assert_eq!( - node_runtime::System::events().len(), + kitchensink_runtime::System::events().len(), (self.block.extrinsics.len() - 1) * 8 + 1, ); }, BlockType::Noop => { assert_eq!( - node_runtime::System::events().len(), + kitchensink_runtime::System::events().len(), // should be 2 per signed extrinsic + 1 per unsigned // we have 1 unsigned and the rest are signed in the block // those 2 events per signed are: diff --git a/bin/node/cli/Cargo.toml b/bin/node/cli/Cargo.toml index b2e24f0b33189..9dde225c0d6fb 100644 --- a/bin/node/cli/Cargo.toml +++ b/bin/node/cli/Cargo.toml @@ -87,7 +87,7 @@ pallet-asset-tx-payment = { version = "4.0.0-dev", path = "../../../frame/transa pallet-im-online = { version = "4.0.0-dev", default-features = false, path = "../../../frame/im-online" } # node-specific dependencies -node-runtime = { version = "3.0.0-dev", path = "../runtime" } +kitchensink-runtime = { version = "3.0.0-dev", path = "../runtime" } node-rpc = { version = "3.0.0-dev", path = "../rpc" } node-primitives = { version = "2.0.0", path = "../primitives" } node-executor = { version = "3.0.0-dev", path = "../executor" } @@ -159,10 +159,10 @@ cli = [ "substrate-build-script-utils", "try-runtime-cli", ] -runtime-benchmarks = ["node-runtime/runtime-benchmarks", "frame-benchmarking-cli"] +runtime-benchmarks = ["kitchensink-runtime/runtime-benchmarks", "frame-benchmarking-cli"] # Enable features that allow the runtime to be tried and debugged. Name might be subject to change # in the near future. -try-runtime = ["node-runtime/try-runtime", "try-runtime-cli"] +try-runtime = ["kitchensink-runtime/try-runtime", "try-runtime-cli"] [[bench]] name = "transaction_pool" diff --git a/bin/node/cli/benches/block_production.rs b/bin/node/cli/benches/block_production.rs index ad16ba8e4072b..6d269ccaac271 100644 --- a/bin/node/cli/benches/block_production.rs +++ b/bin/node/cli/benches/block_production.rs @@ -18,8 +18,8 @@ use criterion::{criterion_group, criterion_main, BatchSize, Criterion, Throughput}; +use kitchensink_runtime::{constants::currency::*, BalancesCall}; use node_cli::service::{create_extrinsic, FullClient}; -use node_runtime::{constants::currency::*, BalancesCall}; use sc_block_builder::{BlockBuilderProvider, BuiltBlock, RecordProof}; use sc_client_api::execution_extensions::ExecutionStrategies; use sc_consensus::{ @@ -121,9 +121,9 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase { } fn extrinsic_set_time(now: u64) -> OpaqueExtrinsic { - node_runtime::UncheckedExtrinsic { + kitchensink_runtime::UncheckedExtrinsic { signature: None, - function: node_runtime::Call::Timestamp(pallet_timestamp::Call::set { now }), + function: kitchensink_runtime::Call::Timestamp(pallet_timestamp::Call::set { now }), } .into() } diff --git a/bin/node/cli/benches/transaction_pool.rs b/bin/node/cli/benches/transaction_pool.rs index d88c3a65b6138..580a10d6a6678 100644 --- a/bin/node/cli/benches/transaction_pool.rs +++ b/bin/node/cli/benches/transaction_pool.rs @@ -20,9 +20,9 @@ use std::time::Duration; use criterion::{criterion_group, criterion_main, BatchSize, Criterion, Throughput}; use futures::{future, StreamExt}; +use kitchensink_runtime::{constants::currency::*, BalancesCall, SudoCall}; use node_cli::service::{create_extrinsic, fetch_nonce, FullClient, TransactionPool}; use node_primitives::AccountId; -use node_runtime::{constants::currency::*, BalancesCall, SudoCall}; use sc_client_api::execution_extensions::ExecutionStrategies; use sc_service::{ config::{ diff --git a/bin/node/cli/src/benchmarking.rs b/bin/node/cli/src/benchmarking.rs index 52657016c046a..19bd1660a4dd9 100644 --- a/bin/node/cli/src/benchmarking.rs +++ b/bin/node/cli/src/benchmarking.rs @@ -22,8 +22,8 @@ use crate::service::{create_extrinsic, FullClient}; +use kitchensink_runtime::{BalancesCall, SystemCall}; use node_primitives::{AccountId, Balance}; -use node_runtime::{BalancesCall, SystemCall}; use sc_cli::Result; use sp_inherents::{InherentData, InherentDataProvider}; use sp_keyring::Sr25519Keyring; diff --git a/bin/node/cli/src/chain_spec.rs b/bin/node/cli/src/chain_spec.rs index 47c3634aa00df..77e2f73dd6e18 100644 --- a/bin/node/cli/src/chain_spec.rs +++ b/bin/node/cli/src/chain_spec.rs @@ -20,7 +20,7 @@ use grandpa_primitives::AuthorityId as GrandpaId; use hex_literal::hex; -use node_runtime::{ +use kitchensink_runtime::{ constants::currency::*, wasm_binary_unwrap, AuthorityDiscoveryConfig, BabeConfig, BalancesConfig, Block, CouncilConfig, DemocracyConfig, ElectionsConfig, GrandpaConfig, ImOnlineConfig, IndicesConfig, MaxNominations, NominationPoolsConfig, SessionConfig, @@ -40,8 +40,8 @@ use sp_runtime::{ Perbill, }; +pub use kitchensink_runtime::GenesisConfig; pub use node_primitives::{AccountId, Balance, Signature}; -pub use node_runtime::GenesisConfig; type AccountPublic = ::Signer; @@ -343,7 +343,7 @@ pub fn testnet_genesis( sudo: SudoConfig { key: Some(root_key) }, babe: BabeConfig { authorities: vec![], - epoch_config: Some(node_runtime::BABE_GENESIS_EPOCH_CONFIG), + epoch_config: Some(kitchensink_runtime::BABE_GENESIS_EPOCH_CONFIG), }, im_online: ImOnlineConfig { keys: vec![] }, authority_discovery: AuthorityDiscoveryConfig { keys: vec![] }, diff --git a/bin/node/cli/src/command.rs b/bin/node/cli/src/command.rs index df3d5a891e2ab..85e5415dbe139 100644 --- a/bin/node/cli/src/command.rs +++ b/bin/node/cli/src/command.rs @@ -23,9 +23,9 @@ use crate::{ Cli, Subcommand, }; use frame_benchmarking_cli::*; +use kitchensink_runtime::{ExistentialDeposit, RuntimeApi}; use node_executor::ExecutorDispatch; use node_primitives::Block; -use node_runtime::{ExistentialDeposit, RuntimeApi}; use sc_cli::{ChainSpec, Result, RuntimeVersion, SubstrateCli}; use sc_service::PartialComponents; use sp_keyring::Sr25519Keyring; @@ -75,7 +75,7 @@ impl SubstrateCli for Cli { } fn native_runtime_version(_: &Box) -> &'static RuntimeVersion { - &node_runtime::VERSION + &kitchensink_runtime::VERSION } } diff --git a/bin/node/cli/src/service.rs b/bin/node/cli/src/service.rs index bff4be88002fb..e0644f462cf20 100644 --- a/bin/node/cli/src/service.rs +++ b/bin/node/cli/src/service.rs @@ -23,9 +23,9 @@ use codec::Encode; use frame_system_rpc_runtime_api::AccountNonceApi; use futures::prelude::*; +use kitchensink_runtime::RuntimeApi; use node_executor::ExecutorDispatch; use node_primitives::Block; -use node_runtime::RuntimeApi; use sc_client_api::{BlockBackend, ExecutorProvider}; use sc_consensus_babe::{self, SlotProportion}; use sc_executor::NativeElseWasmExecutor; @@ -68,41 +68,43 @@ pub fn fetch_nonce(client: &FullClient, account: sp_core::sr25519::Pair) -> u32 pub fn create_extrinsic( client: &FullClient, sender: sp_core::sr25519::Pair, - function: impl Into, + function: impl Into, nonce: Option, -) -> node_runtime::UncheckedExtrinsic { +) -> kitchensink_runtime::UncheckedExtrinsic { let function = function.into(); let genesis_hash = client.block_hash(0).ok().flatten().expect("Genesis block exists; qed"); let best_hash = client.chain_info().best_hash; let best_block = client.chain_info().best_number; let nonce = nonce.unwrap_or_else(|| fetch_nonce(client, sender.clone())); - let period = node_runtime::BlockHashCount::get() + let period = kitchensink_runtime::BlockHashCount::get() .checked_next_power_of_two() .map(|c| c / 2) .unwrap_or(2) as u64; let tip = 0; - let extra: node_runtime::SignedExtra = ( - frame_system::CheckNonZeroSender::::new(), - frame_system::CheckSpecVersion::::new(), - frame_system::CheckTxVersion::::new(), - frame_system::CheckGenesis::::new(), - frame_system::CheckEra::::from(generic::Era::mortal( + let extra: kitchensink_runtime::SignedExtra = ( + frame_system::CheckNonZeroSender::::new(), + frame_system::CheckSpecVersion::::new(), + frame_system::CheckTxVersion::::new(), + frame_system::CheckGenesis::::new(), + frame_system::CheckEra::::from(generic::Era::mortal( period, best_block.saturated_into(), )), - frame_system::CheckNonce::::from(nonce), - frame_system::CheckWeight::::new(), - pallet_asset_tx_payment::ChargeAssetTxPayment::::from(tip, None), + frame_system::CheckNonce::::from(nonce), + frame_system::CheckWeight::::new(), + pallet_asset_tx_payment::ChargeAssetTxPayment::::from( + tip, None, + ), ); - let raw_payload = node_runtime::SignedPayload::from_raw( + let raw_payload = kitchensink_runtime::SignedPayload::from_raw( function.clone(), extra.clone(), ( (), - node_runtime::VERSION.spec_version, - node_runtime::VERSION.transaction_version, + kitchensink_runtime::VERSION.spec_version, + kitchensink_runtime::VERSION.transaction_version, genesis_hash, best_hash, (), @@ -112,10 +114,10 @@ pub fn create_extrinsic( ); let signature = raw_payload.using_encoded(|e| sender.sign(e)); - node_runtime::UncheckedExtrinsic::new_signed( + kitchensink_runtime::UncheckedExtrinsic::new_signed( function, sp_runtime::AccountId32::from(sender.public()).into(), - node_runtime::Signature::Sr25519(signature), + kitchensink_runtime::Signature::Sr25519(signature), extra, ) } @@ -565,11 +567,11 @@ pub fn new_full( mod tests { use crate::service::{new_full_base, NewFullBase}; use codec::Encode; - use node_primitives::{Block, DigestItem, Signature}; - use node_runtime::{ + use kitchensink_runtime::{ constants::{currency::CENTS, time::SLOT_DURATION}, Address, BalancesCall, Call, UncheckedExtrinsic, }; + use node_primitives::{Block, DigestItem, Signature}; use sc_client_api::BlockBackend; use sc_consensus::{BlockImport, BlockImportParams, ForkChoiceStrategy}; use sc_consensus_babe::{BabeIntermediate, CompatibleDigestItem, INTERMEDIATE_KEY}; diff --git a/bin/node/executor/Cargo.toml b/bin/node/executor/Cargo.toml index 6143ea3bd8c42..71865783da8ac 100644 --- a/bin/node/executor/Cargo.toml +++ b/bin/node/executor/Cargo.toml @@ -16,7 +16,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0" } scale-info = { version = "2.1.1", features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", path = "../../../frame/benchmarking" } node-primitives = { version = "2.0.0", path = "../primitives" } -node-runtime = { version = "3.0.0-dev", path = "../runtime" } +kitchensink-runtime = { version = "3.0.0-dev", path = "../runtime" } sc-executor = { version = "0.10.0-dev", path = "../../../client/executor" } sp-core = { version = "6.0.0", path = "../../../primitives/core" } sp-keystore = { version = "0.12.0", path = "../../../primitives/keystore" } diff --git a/bin/node/executor/benches/bench.rs b/bin/node/executor/benches/bench.rs index 61e2d1b053012..a1d31a5a966db 100644 --- a/bin/node/executor/benches/bench.rs +++ b/bin/node/executor/benches/bench.rs @@ -18,12 +18,12 @@ use codec::{Decode, Encode}; use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; use frame_support::Hashable; -use node_executor::ExecutorDispatch; -use node_primitives::{BlockNumber, Hash}; -use node_runtime::{ +use kitchensink_runtime::{ constants::currency::*, Block, BuildStorage, Call, CheckedExtrinsic, GenesisConfig, Header, UncheckedExtrinsic, }; +use node_executor::ExecutorDispatch; +use node_primitives::{BlockNumber, Hash}; use node_testing::keyring::*; #[cfg(feature = "wasmtime")] use sc_executor::WasmtimeInstantiationStrategy; @@ -41,7 +41,7 @@ criterion_main!(benches); /// The wasm runtime code. pub fn compact_code_unwrap() -> &'static [u8] { - node_runtime::WASM_BINARY.expect( + kitchensink_runtime::WASM_BINARY.expect( "Development wasm binary is not available. Testing is only supported with the flag \ disabled.", ) @@ -49,9 +49,9 @@ pub fn compact_code_unwrap() -> &'static [u8] { const GENESIS_HASH: [u8; 32] = [69u8; 32]; -const TRANSACTION_VERSION: u32 = node_runtime::VERSION.transaction_version; +const TRANSACTION_VERSION: u32 = kitchensink_runtime::VERSION.transaction_version; -const SPEC_VERSION: u32 = node_runtime::VERSION.spec_version; +const SPEC_VERSION: u32 = kitchensink_runtime::VERSION.spec_version; const HEAP_PAGES: u64 = 20; diff --git a/bin/node/executor/src/lib.rs b/bin/node/executor/src/lib.rs index 9f87c7d12623c..c4edf5ad22f47 100644 --- a/bin/node/executor/src/lib.rs +++ b/bin/node/executor/src/lib.rs @@ -28,10 +28,10 @@ impl sc_executor::NativeExecutionDispatch for ExecutorDispatch { type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; fn dispatch(method: &str, data: &[u8]) -> Option> { - node_runtime::api::dispatch(method, data) + kitchensink_runtime::api::dispatch(method, data) } fn native_version() -> sc_executor::NativeVersion { - node_runtime::native_version() + kitchensink_runtime::native_version() } } diff --git a/bin/node/executor/tests/basic.rs b/bin/node/executor/tests/basic.rs index 31a9bd0a90496..27e848a281097 100644 --- a/bin/node/executor/tests/basic.rs +++ b/bin/node/executor/tests/basic.rs @@ -26,12 +26,12 @@ use sp_runtime::{ traits::Hash as HashT, transaction_validity::InvalidTransaction, ApplyExtrinsicResult, }; -use node_primitives::{Balance, Hash}; -use node_runtime::{ +use kitchensink_runtime::{ constants::{currency::*, time::SLOT_DURATION}, Balances, Call, CheckedExtrinsic, Event, Header, Runtime, System, TransactionPayment, UncheckedExtrinsic, }; +use node_primitives::{Balance, Hash}; use node_testing::keyring::*; use wat; @@ -44,7 +44,7 @@ use self::common::{sign, *}; /// have to execute provided wasm code instead of the native equivalent. This trick is used to /// test code paths that differ between native and wasm versions. pub fn bloaty_code_unwrap() -> &'static [u8] { - node_runtime::WASM_BINARY_BLOATY.expect( + kitchensink_runtime::WASM_BINARY_BLOATY.expect( "Development wasm binary is not available. \ Testing is only supported with the flag disabled.", ) diff --git a/bin/node/executor/tests/common.rs b/bin/node/executor/tests/common.rs index a2bb91056f474..407a1e09f8efb 100644 --- a/bin/node/executor/tests/common.rs +++ b/bin/node/executor/tests/common.rs @@ -35,12 +35,12 @@ use sp_runtime::{ }; use sp_state_machine::TestExternalities as CoreTestExternalities; -use node_executor::ExecutorDispatch; -use node_primitives::{BlockNumber, Hash}; -use node_runtime::{ +use kitchensink_runtime::{ constants::currency::*, Block, BuildStorage, CheckedExtrinsic, Header, Runtime, UncheckedExtrinsic, }; +use node_executor::ExecutorDispatch; +use node_primitives::{BlockNumber, Hash}; use node_testing::keyring::*; use sp_externalities::Externalities; @@ -69,7 +69,7 @@ impl AppCrypto for TestAuthorityId { /// making the binary slimmer. There is a convention to use compact version of the runtime /// as canonical. pub fn compact_code_unwrap() -> &'static [u8] { - node_runtime::WASM_BINARY.expect( + kitchensink_runtime::WASM_BINARY.expect( "Development wasm binary is not available. Testing is only supported with the flag \ disabled.", ) @@ -77,9 +77,9 @@ pub fn compact_code_unwrap() -> &'static [u8] { pub const GENESIS_HASH: [u8; 32] = [69u8; 32]; -pub const SPEC_VERSION: u32 = node_runtime::VERSION.spec_version; +pub const SPEC_VERSION: u32 = kitchensink_runtime::VERSION.spec_version; -pub const TRANSACTION_VERSION: u32 = node_runtime::VERSION.transaction_version; +pub const TRANSACTION_VERSION: u32 = kitchensink_runtime::VERSION.transaction_version; pub type TestExternalities = CoreTestExternalities; diff --git a/bin/node/executor/tests/fees.rs b/bin/node/executor/tests/fees.rs index e1550071d3006..008ed5f53927b 100644 --- a/bin/node/executor/tests/fees.rs +++ b/bin/node/executor/tests/fees.rs @@ -20,11 +20,11 @@ use frame_support::{ traits::Currency, weights::{constants::ExtrinsicBaseWeight, GetDispatchInfo, IdentityFee, WeightToFee}, }; -use node_primitives::Balance; -use node_runtime::{ +use kitchensink_runtime::{ constants::{currency::*, time::SLOT_DURATION}, Balances, Call, CheckedExtrinsic, Multiplier, Runtime, TransactionByteFee, TransactionPayment, }; +use node_primitives::Balance; use node_testing::keyring::*; use sp_core::NeverNativeValue; use sp_runtime::{traits::One, Perbill}; diff --git a/bin/node/executor/tests/submit_transaction.rs b/bin/node/executor/tests/submit_transaction.rs index 7df13a577006e..be43f3c78674f 100644 --- a/bin/node/executor/tests/submit_transaction.rs +++ b/bin/node/executor/tests/submit_transaction.rs @@ -17,7 +17,7 @@ use codec::Decode; use frame_system::offchain::{SendSignedTransaction, Signer, SubmitTransaction}; -use node_runtime::{Executive, Indices, Runtime, UncheckedExtrinsic}; +use kitchensink_runtime::{Executive, Indices, Runtime, UncheckedExtrinsic}; use sp_application_crypto::AppKey; use sp_core::offchain::{testing::TestTransactionPoolExt, TransactionPoolExt}; use sp_keyring::sr25519::Keyring::Alice; diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index d3138ce51de9c..10b15b6ec554d 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "node-runtime" +name = "kitchensink-runtime" version = "3.0.0-dev" authors = ["Parity Technologies "] edition = "2021" diff --git a/bin/node/testing/Cargo.toml b/bin/node/testing/Cargo.toml index 7caf10366b48c..ed81301e45189 100644 --- a/bin/node/testing/Cargo.toml +++ b/bin/node/testing/Cargo.toml @@ -21,7 +21,7 @@ tempfile = "3.1.0" frame-system = { version = "4.0.0-dev", path = "../../../frame/system" } node-executor = { version = "3.0.0-dev", path = "../executor" } node-primitives = { version = "2.0.0", path = "../primitives" } -node-runtime = { version = "3.0.0-dev", path = "../runtime" } +kitchensink-runtime = { version = "3.0.0-dev", path = "../runtime" } pallet-asset-tx-payment = { version = "4.0.0-dev", path = "../../../frame/transaction-payment/asset-tx-payment" } pallet-transaction-payment = { version = "4.0.0-dev", path = "../../../frame/transaction-payment" } sc-block-builder = { version = "0.10.0-dev", path = "../../../client/block-builder" } diff --git a/bin/node/testing/src/bench.rs b/bin/node/testing/src/bench.rs index 00ce7f64bc3f0..18e979b95737f 100644 --- a/bin/node/testing/src/bench.rs +++ b/bin/node/testing/src/bench.rs @@ -34,11 +34,11 @@ use crate::{ }; use codec::{Decode, Encode}; use futures::executor; -use node_primitives::Block; -use node_runtime::{ +use kitchensink_runtime::{ constants::currency::DOLLARS, AccountId, BalancesCall, Call, CheckedExtrinsic, MinimumPeriod, Signature, SystemCall, UncheckedExtrinsic, }; +use node_primitives::Block; use sc_block_builder::BlockBuilderProvider; use sc_client_api::{ execution_extensions::{ExecutionExtensions, ExecutionStrategies}, @@ -304,20 +304,21 @@ impl<'a> Iterator for BlockContentIterator<'a> { CheckedExtrinsic { signed: Some(( sender, - signed_extra(0, node_runtime::ExistentialDeposit::get() + 1), + signed_extra(0, kitchensink_runtime::ExistentialDeposit::get() + 1), )), function: match self.content.block_type { BlockType::RandomTransfersKeepAlive => Call::Balances(BalancesCall::transfer_keep_alive { dest: sp_runtime::MultiAddress::Id(receiver), - value: node_runtime::ExistentialDeposit::get() + 1, + value: kitchensink_runtime::ExistentialDeposit::get() + 1, }), BlockType::RandomTransfersReaping => { Call::Balances(BalancesCall::transfer { dest: sp_runtime::MultiAddress::Id(receiver), // Transfer so that ending balance would be 1 less than existential // deposit so that we kill the sender account. - value: 100 * DOLLARS - (node_runtime::ExistentialDeposit::get() - 1), + value: 100 * DOLLARS - + (kitchensink_runtime::ExistentialDeposit::get() - 1), }) }, BlockType::Noop => Call::System(SystemCall::remark { remark: Vec::new() }), @@ -592,9 +593,9 @@ impl BenchKeyring { } /// Generate genesis with accounts from this keyring endowed with some balance. - pub fn generate_genesis(&self) -> node_runtime::GenesisConfig { + pub fn generate_genesis(&self) -> kitchensink_runtime::GenesisConfig { crate::genesis::config_endowed( - Some(node_runtime::wasm_binary_unwrap()), + Some(kitchensink_runtime::wasm_binary_unwrap()), self.collect_account_ids(), ) } diff --git a/bin/node/testing/src/client.rs b/bin/node/testing/src/client.rs index 8cb98511098f1..590304bdd52a5 100644 --- a/bin/node/testing/src/client.rs +++ b/bin/node/testing/src/client.rs @@ -16,14 +16,14 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! Utilities to build a `TestClient` for `node-runtime`. +//! Utilities to build a `TestClient` for `kitchensink-runtime`. use sc_service::client; use sp_runtime::BuildStorage; /// Re-export test-client utilities. pub use substrate_test_client::*; -/// Call executor for `node-runtime` `TestClient`. +/// Call executor for `kitchensink-runtime` `TestClient`. pub type ExecutorDispatch = sc_executor::NativeElseWasmExecutor; /// Default backend type. @@ -34,10 +34,10 @@ pub type Client = client::Client< Backend, client::LocalCallExecutor, node_primitives::Block, - node_runtime::RuntimeApi, + kitchensink_runtime::RuntimeApi, >; -/// Transaction for node-runtime. +/// Transaction for kitchensink-runtime. pub type Transaction = sc_client_api::backend::TransactionFor; /// Genesis configuration parameters for `TestClient`. diff --git a/bin/node/testing/src/genesis.rs b/bin/node/testing/src/genesis.rs index fbd28c5af0298..1eb7318db52da 100644 --- a/bin/node/testing/src/genesis.rs +++ b/bin/node/testing/src/genesis.rs @@ -19,7 +19,7 @@ //! Genesis Configuration. use crate::keyring::*; -use node_runtime::{ +use kitchensink_runtime::{ constants::currency::*, wasm_binary_unwrap, AccountId, BabeConfig, BalancesConfig, GenesisConfig, GrandpaConfig, IndicesConfig, SessionConfig, SocietyConfig, StakerStatus, StakingConfig, SystemConfig, BABE_GENESIS_EPOCH_CONFIG, diff --git a/bin/node/testing/src/keyring.rs b/bin/node/testing/src/keyring.rs index 41dd28bb89988..88d9dc56b0532 100644 --- a/bin/node/testing/src/keyring.rs +++ b/bin/node/testing/src/keyring.rs @@ -19,8 +19,8 @@ //! Test accounts. use codec::Encode; +use kitchensink_runtime::{CheckedExtrinsic, SessionKeys, SignedExtra, UncheckedExtrinsic}; use node_primitives::{AccountId, Balance, Index}; -use node_runtime::{CheckedExtrinsic, SessionKeys, SignedExtra, UncheckedExtrinsic}; use sp_keyring::{AccountKeyring, Ed25519Keyring, Sr25519Keyring}; use sp_runtime::generic::Era; diff --git a/bin/utils/subkey/src/main.rs b/bin/utils/subkey/src/main.rs index d85c6dbe9d048..271388549bcf9 100644 --- a/bin/utils/subkey/src/main.rs +++ b/bin/utils/subkey/src/main.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! Subkey utility, based on node_runtime. +//! Subkey utility, based on kitchensink_runtime. fn main() -> Result<(), sc_cli::Error> { subkey::run() diff --git a/docs/README.adoc b/docs/README.adoc index 0b82f0ed82a13..5d7b0b52c5250 100644 --- a/docs/README.adoc +++ b/docs/README.adoc @@ -445,7 +445,7 @@ pallet-assets, pallet-balances, pallet-consensus, pallet-contracts, pallet-counc frame-executive, pallet-session, pallet-staking, pallet-timestamp, pallet-treasury * Node [source, shell] -node-cli, node-consensus, node-executor, node-network, node-primitives, node-runtime +node-cli, node-consensus, node-executor, node-network, node-primitives, kitchensink-runtime * Subkey [source, shell] subkey diff --git a/scripts/ci/gitlab/pipeline/test.yml b/scripts/ci/gitlab/pipeline/test.yml index 7a9f57e9a7734..ccf8338236d0a 100644 --- a/scripts/ci/gitlab/pipeline/test.yml +++ b/scripts/ci/gitlab/pipeline/test.yml @@ -181,13 +181,13 @@ test-deterministic-wasm: script: - rusty-cachier snapshot create # build runtime - - cargo build --verbose --release -p node-runtime + - cargo build --verbose --release -p kitchensink-runtime # make checksum - - sha256sum $CARGO_TARGET_DIR/release/wbuild/node-runtime/target/wasm32-unknown-unknown/release/node_runtime.wasm > checksum.sha256 + - sha256sum $CARGO_TARGET_DIR/release/wbuild/kitchensink-runtime/target/wasm32-unknown-unknown/release/kitchensink_runtime.wasm > checksum.sha256 # clean up - rm -rf $CARGO_TARGET_DIR/release/wbuild # build again - - cargo build --verbose --release -p node-runtime + - cargo build --verbose --release -p kitchensink-runtime # confirm checksum - sha256sum -c ./checksum.sha256 # clean up again, don't put release binaries into the cache diff --git a/utils/frame/generate-bags/node-runtime/Cargo.toml b/utils/frame/generate-bags/node-runtime/Cargo.toml index c5f3b1998fa97..5af7dd78a08e8 100644 --- a/utils/frame/generate-bags/node-runtime/Cargo.toml +++ b/utils/frame/generate-bags/node-runtime/Cargo.toml @@ -6,11 +6,11 @@ edition = "2021" license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" -description = "Bag threshold generation script for pallet-bag-list and node-runtime." +description = "Bag threshold generation script for pallet-bag-list and kitchensink-runtime." readme = "README.md" [dependencies] -node-runtime = { version = "3.0.0-dev", path = "../../../../bin/node/runtime" } +kitchensink-runtime = { version = "3.0.0-dev", path = "../../../../bin/node/runtime" } generate-bags = { version = "4.0.0-dev", path = "../" } # third-party diff --git a/utils/frame/generate-bags/node-runtime/src/main.rs b/utils/frame/generate-bags/node-runtime/src/main.rs index 12bcf8d28cf2b..5ea1262d95d34 100644 --- a/utils/frame/generate-bags/node-runtime/src/main.rs +++ b/utils/frame/generate-bags/node-runtime/src/main.rs @@ -43,5 +43,10 @@ struct Opt { fn main() -> Result<(), std::io::Error> { let Opt { n_bags, output, total_issuance, minimum_balance } = Opt::parse(); - generate_thresholds::(n_bags, &output, total_issuance, minimum_balance) + generate_thresholds::( + n_bags, + &output, + total_issuance, + minimum_balance, + ) } diff --git a/utils/wasm-builder/src/lib.rs b/utils/wasm-builder/src/lib.rs index 919290655368b..fc86a06170a50 100644 --- a/utils/wasm-builder/src/lib.rs +++ b/utils/wasm-builder/src/lib.rs @@ -92,7 +92,7 @@ //! //! Each project can be skipped individually by using the environment variable //! `SKIP_PROJECT_NAME_WASM_BUILD`. Where `PROJECT_NAME` needs to be replaced by the name of the -//! cargo project, e.g. `node-runtime` will be `NODE_RUNTIME`. +//! cargo project, e.g. `kitchensink-runtime` will be `NODE_RUNTIME`. //! //! ## Prerequisites: //! From 5b0d4b5f1d5166e7bc9423e54a5aaa2fffedc31b Mon Sep 17 00:00:00 2001 From: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Date: Tue, 2 Aug 2022 20:00:18 +0300 Subject: [PATCH 452/484] rpc: Update jsonrpsee v0.15.1 (#11939) * Bump jsonrpsee to v0.15.1 Signed-off-by: Alexandru Vasile * Update cargo.lock Signed-off-by: Alexandru Vasile * rpc-servers: Adjust RpcMiddleware to WS and HTTP traits Signed-off-by: Alexandru Vasile * rpc/author: Use `SubscriptionSink` Signed-off-by: Alexandru Vasile * rpc/chain: Use `SubscriptionSink` Signed-off-by: Alexandru Vasile * rpc/state: Use `SubscriptionSink` Signed-off-by: Alexandru Vasile * rpc/finality-grandpa: Use `SubscriptionSink` Signed-off-by: Alexandru Vasile * rpc/beefy: Use `SubscriptionSink` Signed-off-by: Alexandru Vasile * client: Extract RPC string result from queries Signed-off-by: Alexandru Vasile * Apply rust-fmt Signed-off-by: Alexandru Vasile * Fix warnings Signed-off-by: Alexandru Vasile * Fix testing Signed-off-by: Alexandru Vasile * rpc/tests: Remove trailing comma Signed-off-by: Alexandru Vasile * rpc: Use `SubscriptionResult` for implementations Signed-off-by: Alexandru Vasile * rpc: Remove comment Signed-off-by: Alexandru Vasile * rpc: Delegate middleware calls to `RpcMiddleware` Signed-off-by: Alexandru Vasile * rpc: Remove comment Signed-off-by: Alexandru Vasile * Revert Cargo.lock Signed-off-by: Alexandru Vasile * Update Cargo.lock with minimal changes Signed-off-by: Alexandru Vasile * rpc: Update imports for `SubscriptionResult` Signed-off-by: Alexandru Vasile * Apply cargo fmt Signed-off-by: Alexandru Vasile * rpc/tests: Submit raw json requests to validate DenyUnsafe Signed-off-by: Alexandru Vasile --- Cargo.lock | 52 ++++++------ bin/node-template/node/Cargo.toml | 2 +- bin/node/cli/Cargo.toml | 2 +- bin/node/rpc/Cargo.toml | 2 +- client/beefy/rpc/Cargo.toml | 2 +- client/beefy/rpc/src/lib.rs | 21 +++-- client/consensus/babe/rpc/Cargo.toml | 2 +- client/consensus/babe/rpc/src/lib.rs | 4 +- client/consensus/manual-seal/Cargo.toml | 2 +- client/finality-grandpa/rpc/Cargo.toml | 2 +- client/finality-grandpa/rpc/src/lib.rs | 20 ++--- client/rpc-api/Cargo.toml | 2 +- client/rpc-servers/Cargo.toml | 2 +- client/rpc-servers/src/middleware.rs | 79 +++++++++++++++---- client/rpc/Cargo.toml | 2 +- client/rpc/src/author/mod.rs | 17 ++-- client/rpc/src/chain/chain_full.rs | 14 ++-- client/rpc/src/chain/mod.rs | 23 +++--- client/rpc/src/dev/tests.rs | 19 +++-- client/rpc/src/state/mod.rs | 23 ++++-- client/rpc/src/state/state_full.rs | 18 ++--- client/service/Cargo.toml | 2 +- client/service/src/lib.rs | 5 +- client/sync-state-rpc/Cargo.toml | 2 +- frame/contracts/rpc/Cargo.toml | 2 +- frame/merkle-mountain-range/rpc/Cargo.toml | 2 +- frame/transaction-payment/rpc/Cargo.toml | 2 +- utils/frame/remote-externalities/Cargo.toml | 2 +- .../rpc/state-trie-migration-rpc/Cargo.toml | 2 +- utils/frame/rpc/support/Cargo.toml | 4 +- utils/frame/rpc/system/Cargo.toml | 2 +- utils/frame/try-runtime/cli/Cargo.toml | 2 +- 32 files changed, 201 insertions(+), 136 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 733c6d73d2709..ec5c5267142a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2877,13 +2877,13 @@ dependencies = [ [[package]] name = "http" -version = "0.2.3" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7245cd7449cc792608c3c8a9eaf69bd4eabbabf802713748fd739c98b82f0747" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ "bytes", "fnv", - "itoa 0.4.8", + "itoa 1.0.1", ] [[package]] @@ -3134,9 +3134,9 @@ dependencies = [ [[package]] name = "jsonrpsee" -version = "0.14.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11e017217fcd18da0a25296d3693153dd19c8a6aadab330b3595285d075385d1" +checksum = "8bd0d559d5e679b1ab2f869b486a11182923863b1b3ee8b421763cdd707b783a" dependencies = [ "jsonrpsee-core", "jsonrpsee-http-server", @@ -3149,9 +3149,9 @@ dependencies = [ [[package]] name = "jsonrpsee-client-transport" -version = "0.14.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce395539a14d3ad4ec1256fde105abd36a2da25d578a291cabe98f45adfdb111" +checksum = "8752740ecd374bcbf8b69f3e80b0327942df76f793f8d4e60d3355650c31fb74" dependencies = [ "futures-util", "http", @@ -3170,9 +3170,9 @@ dependencies = [ [[package]] name = "jsonrpsee-core" -version = "0.14.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16efcd4477de857d4a2195a45769b2fe9ebb54f3ef5a4221d3b014a4fe33ec0b" +checksum = "f3dc3e9cf2ba50b7b1d7d76a667619f82846caa39e8e8daa8a4962d74acaddca" dependencies = [ "anyhow", "arrayvec 0.7.2", @@ -3183,6 +3183,7 @@ dependencies = [ "futures-timer", "futures-util", "globset", + "http", "hyper", "jsonrpsee-types", "lazy_static", @@ -3195,14 +3196,15 @@ dependencies = [ "thiserror", "tokio", "tracing", + "tracing-futures", "unicase", ] [[package]] name = "jsonrpsee-http-server" -version = "0.14.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdd69efeb3ce2cba767f126872f4eeb4624038a29098e75d77608b2b4345ad03" +checksum = "03802f0373a38c2420c70b5144742d800b509e2937edc4afb116434f07120117" dependencies = [ "futures-channel", "futures-util", @@ -3213,13 +3215,14 @@ dependencies = [ "serde_json", "tokio", "tracing", + "tracing-futures", ] [[package]] name = "jsonrpsee-proc-macros" -version = "0.14.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874cf3f6a027cebf36cae767feca9aa2e8a8f799880e49eb5540819fcbd8eada" +checksum = "bd67957d4280217247588ac86614ead007b301ca2fa9f19c19f880a536f029e3" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -3229,9 +3232,9 @@ dependencies = [ [[package]] name = "jsonrpsee-types" -version = "0.14.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bcf76cd316f5d3ad48138085af1f45e2c58c98e02f0779783dbb034d43f7c86" +checksum = "e290bba767401b646812f608c099b922d8142603c9e73a50fb192d3ac86f4a0d" dependencies = [ "anyhow", "beef", @@ -3243,10 +3246,11 @@ dependencies = [ [[package]] name = "jsonrpsee-ws-client" -version = "0.14.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee043cb5dd0d51d3eb93432e998d5bae797691a7b10ec4a325e036bcdb48c48a" +checksum = "6ee5feddd5188e62ac08fcf0e56478138e581509d4730f3f7be9b57dd402a4ff" dependencies = [ + "http", "jsonrpsee-client-transport", "jsonrpsee-core", "jsonrpsee-types", @@ -3254,12 +3258,13 @@ dependencies = [ [[package]] name = "jsonrpsee-ws-server" -version = "0.14.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bd2e4d266774a671f8def3794255b28eddd09b18d76e0b913fa439f34588c0a" +checksum = "d488ba74fb369e5ab68926feb75a483458b88e768d44319f37e4ecad283c7325" dependencies = [ "futures-channel", "futures-util", + "http", "jsonrpsee-core", "jsonrpsee-types", "serde_json", @@ -3268,6 +3273,7 @@ dependencies = [ "tokio-stream", "tokio-util", "tracing", + "tracing-futures", ] [[package]] @@ -10839,9 +10845,9 @@ checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" [[package]] name = "tracing" -version = "0.1.29" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" +checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" dependencies = [ "cfg-if 1.0.0", "log", @@ -10852,9 +10858,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.18" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e" +checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" dependencies = [ "proc-macro2", "quote", diff --git a/bin/node-template/node/Cargo.toml b/bin/node-template/node/Cargo.toml index c8e74ea9515ac..eeba198da8212 100644 --- a/bin/node-template/node/Cargo.toml +++ b/bin/node-template/node/Cargo.toml @@ -42,7 +42,7 @@ frame-system = { version = "4.0.0-dev", path = "../../../frame/system" } pallet-transaction-payment = { version = "4.0.0-dev", default-features = false, path = "../../../frame/transaction-payment" } # These dependencies are used for the node template's RPCs -jsonrpsee = { version = "0.14.0", features = ["server"] } +jsonrpsee = { version = "0.15.1", features = ["server"] } sc-rpc = { version = "4.0.0-dev", path = "../../../client/rpc" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sc-rpc-api = { version = "0.10.0-dev", path = "../../../client/rpc-api" } diff --git a/bin/node/cli/Cargo.toml b/bin/node/cli/Cargo.toml index 9dde225c0d6fb..fe7c5332e2b45 100644 --- a/bin/node/cli/Cargo.toml +++ b/bin/node/cli/Cargo.toml @@ -37,7 +37,7 @@ crate-type = ["cdylib", "rlib"] clap = { version = "3.1.18", features = ["derive"], optional = true } codec = { package = "parity-scale-codec", version = "3.0.0" } serde = { version = "1.0.136", features = ["derive"] } -jsonrpsee = { version = "0.14.0", features = ["server"] } +jsonrpsee = { version = "0.15.1", features = ["server"] } futures = "0.3.21" hex-literal = "0.3.4" log = "0.4.17" diff --git a/bin/node/rpc/Cargo.toml b/bin/node/rpc/Cargo.toml index 547d0df7a8372..07b25085b9d10 100644 --- a/bin/node/rpc/Cargo.toml +++ b/bin/node/rpc/Cargo.toml @@ -11,7 +11,7 @@ repository = "https://github.com/paritytech/substrate/" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { version = "0.14.0", features = ["server"] } +jsonrpsee = { version = "0.15.1", features = ["server"] } node-primitives = { version = "2.0.0", path = "../primitives" } pallet-contracts-rpc = { version = "4.0.0-dev", path = "../../../frame/contracts/rpc/" } pallet-mmr-rpc = { version = "3.0.0", path = "../../../frame/merkle-mountain-range/rpc/" } diff --git a/client/beefy/rpc/Cargo.toml b/client/beefy/rpc/Cargo.toml index 7d31aea3f6971..46ee7640d710a 100644 --- a/client/beefy/rpc/Cargo.toml +++ b/client/beefy/rpc/Cargo.toml @@ -11,7 +11,7 @@ homepage = "https://substrate.io" [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", features = ["derive"] } futures = "0.3.21" -jsonrpsee = { version = "0.14.0", features = ["server", "macros"] } +jsonrpsee = { version = "0.15.1", features = ["server", "macros"] } log = "0.4" parking_lot = "0.12.0" serde = { version = "1.0.136", features = ["derive"] } diff --git a/client/beefy/rpc/src/lib.rs b/client/beefy/rpc/src/lib.rs index 13bca08d37429..91ff59324bd95 100644 --- a/client/beefy/rpc/src/lib.rs +++ b/client/beefy/rpc/src/lib.rs @@ -30,8 +30,8 @@ use futures::{task::SpawnError, FutureExt, StreamExt}; use jsonrpsee::{ core::{async_trait, Error as JsonRpseeError, RpcResult}, proc_macros::rpc, - types::{error::CallError, ErrorObject}, - PendingSubscription, + types::{error::CallError, ErrorObject, SubscriptionResult}, + SubscriptionSink, }; use log::warn; @@ -135,19 +135,18 @@ impl BeefyApiServer f where Block: BlockT, { - fn subscribe_justifications(&self, pending: PendingSubscription) { + fn subscribe_justifications(&self, mut sink: SubscriptionSink) -> SubscriptionResult { let stream = self .signed_commitment_stream .subscribe() .map(|sc| notification::EncodedSignedCommitment::new::(sc)); let fut = async move { - if let Some(mut sink) = pending.accept() { - sink.pipe_from_stream(stream).await; - } + sink.pipe_from_stream(stream).await; }; self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); + Ok(()) } async fn latest_finalized(&self) -> RpcResult { @@ -197,9 +196,9 @@ mod tests { let (rpc, _) = setup_io_handler(); let request = r#"{"jsonrpc":"2.0","method":"beefy_getFinalizedHead","params":[],"id":1}"#; let expected_response = r#"{"jsonrpc":"2.0","error":{"code":1,"message":"BEEFY RPC endpoint not ready"},"id":1}"#.to_string(); - let (result, _) = rpc.raw_json_request(&request).await.unwrap(); + let (response, _) = rpc.raw_json_request(&request).await.unwrap(); - assert_eq!(expected_response, result,); + assert_eq!(expected_response, response.result); } #[tokio::test] @@ -229,8 +228,8 @@ mod tests { let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2); while std::time::Instant::now() < deadline { let (response, _) = io.raw_json_request(request).await.expect("RPC requests work"); - if response != not_ready { - assert_eq!(response, expected); + if response.result != not_ready { + assert_eq!(response.result, expected); // Success return } @@ -260,7 +259,7 @@ mod tests { .unwrap(); let expected = r#"{"jsonrpc":"2.0","result":false,"id":1}"#; - assert_eq!(response, expected); + assert_eq!(response.result, expected); } fn create_commitment() -> BeefySignedCommitment { diff --git a/client/consensus/babe/rpc/Cargo.toml b/client/consensus/babe/rpc/Cargo.toml index 4c9350735d529..488036277d7b6 100644 --- a/client/consensus/babe/rpc/Cargo.toml +++ b/client/consensus/babe/rpc/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { version = "0.14.0", features = ["server", "macros"] } +jsonrpsee = { version = "0.15.1", features = ["server", "macros"] } futures = "0.3.21" serde = { version = "1.0.136", features = ["derive"] } thiserror = "1.0" diff --git a/client/consensus/babe/rpc/src/lib.rs b/client/consensus/babe/rpc/src/lib.rs index af19d410346e3..b000d38a44f02 100644 --- a/client/consensus/babe/rpc/src/lib.rs +++ b/client/consensus/babe/rpc/src/lib.rs @@ -262,7 +262,7 @@ mod tests { let (response, _) = api.raw_json_request(request).await.unwrap(); let expected = r#"{"jsonrpc":"2.0","result":{"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY":{"primary":[0],"secondary":[1,2,4],"secondary_vrf":[]}},"id":1}"#; - assert_eq!(&response, expected); + assert_eq!(&response.result, expected); } #[tokio::test] @@ -274,6 +274,6 @@ mod tests { let (response, _) = api.raw_json_request(request).await.unwrap(); let expected = r#"{"jsonrpc":"2.0","error":{"code":-32601,"message":"RPC call is unsafe to be called externally"},"id":1}"#; - assert_eq!(&response, expected); + assert_eq!(&response.result, expected); } } diff --git a/client/consensus/manual-seal/Cargo.toml b/client/consensus/manual-seal/Cargo.toml index 7aa2232178f22..83c156ef5667f 100644 --- a/client/consensus/manual-seal/Cargo.toml +++ b/client/consensus/manual-seal/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -jsonrpsee = { version = "0.14.0", features = ["server", "macros"] } +jsonrpsee = { version = "0.15.1", features = ["server", "macros"] } assert_matches = "1.3.0" async-trait = "0.1.50" codec = { package = "parity-scale-codec", version = "3.0.0" } diff --git a/client/finality-grandpa/rpc/Cargo.toml b/client/finality-grandpa/rpc/Cargo.toml index f2d37da058f45..075179d3ceaf7 100644 --- a/client/finality-grandpa/rpc/Cargo.toml +++ b/client/finality-grandpa/rpc/Cargo.toml @@ -12,7 +12,7 @@ homepage = "https://substrate.io" [dependencies] finality-grandpa = { version = "0.16.0", features = ["derive-codec"] } futures = "0.3.16" -jsonrpsee = { version = "0.14.0", features = ["server", "macros"] } +jsonrpsee = { version = "0.15.1", features = ["server", "macros"] } log = "0.4.8" parity-scale-codec = { version = "3.0.0", features = ["derive"] } serde = { version = "1.0.105", features = ["derive"] } diff --git a/client/finality-grandpa/rpc/src/lib.rs b/client/finality-grandpa/rpc/src/lib.rs index 1cf23a18c794b..85df72de77b54 100644 --- a/client/finality-grandpa/rpc/src/lib.rs +++ b/client/finality-grandpa/rpc/src/lib.rs @@ -26,7 +26,8 @@ use std::sync::Arc; use jsonrpsee::{ core::{async_trait, RpcResult}, proc_macros::rpc, - PendingSubscription, + types::SubscriptionResult, + SubscriptionSink, }; mod error; @@ -102,7 +103,7 @@ where ReportedRoundStates::from(&self.authority_set, &self.voter_state).map_err(Into::into) } - fn subscribe_justifications(&self, pending: PendingSubscription) { + fn subscribe_justifications(&self, mut sink: SubscriptionSink) -> SubscriptionResult { let stream = self.justification_stream.subscribe().map( |x: sc_finality_grandpa::GrandpaJustification| { JustificationNotification::from(x) @@ -110,12 +111,11 @@ where ); let fut = async move { - if let Some(mut sink) = pending.accept() { - sink.pipe_from_stream(stream).await; - } + sink.pipe_from_stream(stream).await; }; self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); + Ok(()) } async fn prove_finality( @@ -283,9 +283,9 @@ mod tests { let (rpc, _) = setup_io_handler(EmptyVoterState); let expected_response = r#"{"jsonrpc":"2.0","error":{"code":1,"message":"GRANDPA RPC endpoint not ready"},"id":0}"#.to_string(); let request = r#"{"jsonrpc":"2.0","method":"grandpa_roundState","params":[],"id":0}"#; - let (result, _) = rpc.raw_json_request(&request).await.unwrap(); + let (response, _) = rpc.raw_json_request(&request).await.unwrap(); - assert_eq!(expected_response, result,); + assert_eq!(expected_response, response.result); } #[tokio::test] @@ -306,8 +306,8 @@ mod tests { },\"id\":0}".to_string(); let request = r#"{"jsonrpc":"2.0","method":"grandpa_roundState","params":[],"id":0}"#; - let (result, _) = rpc.raw_json_request(&request).await.unwrap(); - assert_eq!(expected_response, result); + let (response, _) = rpc.raw_json_request(&request).await.unwrap(); + assert_eq!(expected_response, response.result); } #[tokio::test] @@ -328,7 +328,7 @@ mod tests { .unwrap(); let expected = r#"{"jsonrpc":"2.0","result":false,"id":1}"#; - assert_eq!(response, expected); + assert_eq!(response.result, expected); } fn create_justification() -> GrandpaJustification { diff --git a/client/rpc-api/Cargo.toml b/client/rpc-api/Cargo.toml index 3425ba2b245df..101d558663f9f 100644 --- a/client/rpc-api/Cargo.toml +++ b/client/rpc-api/Cargo.toml @@ -28,4 +28,4 @@ sp-rpc = { version = "6.0.0", path = "../../primitives/rpc" } sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } sp-tracing = { version = "5.0.0", path = "../../primitives/tracing" } sp-version = { version = "5.0.0", path = "../../primitives/version" } -jsonrpsee = { version = "0.14.0", features = ["server", "macros"] } +jsonrpsee = { version = "0.15.1", features = ["server", "macros"] } diff --git a/client/rpc-servers/Cargo.toml b/client/rpc-servers/Cargo.toml index daaa955839045..8b40972527be8 100644 --- a/client/rpc-servers/Cargo.toml +++ b/client/rpc-servers/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] futures = "0.3.21" -jsonrpsee = { version = "0.14.0", features = ["server"] } +jsonrpsee = { version = "0.15.1", features = ["server"] } log = "0.4.17" serde_json = "1.0.79" tokio = { version = "1.17.0", features = ["parking_lot"] } diff --git a/client/rpc-servers/src/middleware.rs b/client/rpc-servers/src/middleware.rs index cb9918268f4de..1c0660fc3528d 100644 --- a/client/rpc-servers/src/middleware.rs +++ b/client/rpc-servers/src/middleware.rs @@ -18,11 +18,12 @@ //! RPC middlware to collect prometheus metrics on RPC calls. -use jsonrpsee::core::middleware::Middleware; +use jsonrpsee::core::middleware::{Headers, HttpMiddleware, MethodKind, Params, WsMiddleware}; use prometheus_endpoint::{ register, Counter, CounterVec, HistogramOpts, HistogramVec, Opts, PrometheusError, Registry, U64, }; +use std::net::SocketAddr; /// Metrics for RPC middleware storing information about the number of requests started/completed, /// calls started/completed and their timings. @@ -134,30 +135,33 @@ impl RpcMiddleware { pub fn new(metrics: RpcMetrics, transport_label: &'static str) -> Self { Self { metrics, transport_label } } -} - -impl Middleware for RpcMiddleware { - type Instant = std::time::Instant; - - fn on_connect(&self) { - self.metrics.ws_sessions_opened.as_ref().map(|counter| counter.inc()); - } - fn on_request(&self) -> Self::Instant { + /// Called when a new JSON-RPC request comes to the server. + fn on_request(&self) -> std::time::Instant { let now = std::time::Instant::now(); self.metrics.requests_started.with_label_values(&[self.transport_label]).inc(); now } - fn on_call(&self, name: &str) { - log::trace!(target: "rpc_metrics", "[{}] on_call name={}", self.transport_label, name); + /// Called on each JSON-RPC method call, batch requests will trigger `on_call` multiple times. + fn on_call(&self, name: &str, params: Params, kind: MethodKind) { + log::trace!( + target: "rpc_metrics", + "[{}] on_call name={} params={:?} kind={}", + self.transport_label, + name, + params, + kind, + ); self.metrics .calls_started .with_label_values(&[self.transport_label, name]) .inc(); } - fn on_result(&self, name: &str, success: bool, started_at: Self::Instant) { + /// Called on each JSON-RPC method completion, batch requests will trigger `on_result` multiple + /// times. + fn on_result(&self, name: &str, success: bool, started_at: std::time::Instant) { let micros = started_at.elapsed().as_micros(); log::debug!( target: "rpc_metrics", @@ -183,12 +187,57 @@ impl Middleware for RpcMiddleware { .inc(); } - fn on_response(&self, started_at: Self::Instant) { + /// Called once the JSON-RPC request is finished and response is sent to the output buffer. + fn on_response(&self, _result: &str, started_at: std::time::Instant) { log::trace!(target: "rpc_metrics", "[{}] on_response started_at={:?}", self.transport_label, started_at); self.metrics.requests_finished.with_label_values(&[self.transport_label]).inc(); } +} + +impl WsMiddleware for RpcMiddleware { + type Instant = std::time::Instant; + + fn on_connect(&self, _remote_addr: SocketAddr, _headers: &Headers) { + self.metrics.ws_sessions_opened.as_ref().map(|counter| counter.inc()); + } + + fn on_request(&self) -> Self::Instant { + self.on_request() + } + + fn on_call(&self, name: &str, params: Params, kind: MethodKind) { + self.on_call(name, params, kind) + } + + fn on_result(&self, name: &str, success: bool, started_at: Self::Instant) { + self.on_result(name, success, started_at) + } + + fn on_response(&self, _result: &str, started_at: Self::Instant) { + self.on_response(_result, started_at) + } - fn on_disconnect(&self) { + fn on_disconnect(&self, _remote_addr: SocketAddr) { self.metrics.ws_sessions_closed.as_ref().map(|counter| counter.inc()); } } + +impl HttpMiddleware for RpcMiddleware { + type Instant = std::time::Instant; + + fn on_request(&self, _remote_addr: SocketAddr, _headers: &Headers) -> Self::Instant { + self.on_request() + } + + fn on_call(&self, name: &str, params: Params, kind: MethodKind) { + self.on_call(name, params, kind) + } + + fn on_result(&self, name: &str, success: bool, started_at: Self::Instant) { + self.on_result(name, success, started_at) + } + + fn on_response(&self, _result: &str, started_at: Self::Instant) { + self.on_response(_result, started_at) + } +} diff --git a/client/rpc/Cargo.toml b/client/rpc/Cargo.toml index e8c657f1a2949..5a05ae6e29df1 100644 --- a/client/rpc/Cargo.toml +++ b/client/rpc/Cargo.toml @@ -16,7 +16,7 @@ targets = ["x86_64-unknown-linux-gnu"] codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.21" hash-db = { version = "0.15.2", default-features = false } -jsonrpsee = { version = "0.14.0", features = ["server"] } +jsonrpsee = { version = "0.15.1", features = ["server"] } lazy_static = { version = "1.4.0", optional = true } log = "0.4.17" parking_lot = "0.12.0" diff --git a/client/rpc/src/author/mod.rs b/client/rpc/src/author/mod.rs index b8c4f5d582808..7d0ffdc62e080 100644 --- a/client/rpc/src/author/mod.rs +++ b/client/rpc/src/author/mod.rs @@ -29,7 +29,8 @@ use codec::{Decode, Encode}; use futures::{FutureExt, TryFutureExt}; use jsonrpsee::{ core::{async_trait, Error as JsonRpseeError, RpcResult}, - PendingSubscription, + types::SubscriptionResult, + SubscriptionSink, }; use sc_rpc_api::DenyUnsafe; use sc_transaction_pool_api::{ @@ -176,13 +177,13 @@ where .collect()) } - fn watch_extrinsic(&self, pending: PendingSubscription, xt: Bytes) { + fn watch_extrinsic(&self, mut sink: SubscriptionSink, xt: Bytes) -> SubscriptionResult { let best_block_hash = self.client.info().best_hash; let dxt = match TransactionFor::

::decode(&mut &xt[..]).map_err(|e| Error::from(e)) { Ok(dxt) => dxt, Err(e) => { - pending.reject(JsonRpseeError::from(e)); - return + let _ = sink.reject(JsonRpseeError::from(e)); + return Ok(()) }, }; @@ -199,19 +200,15 @@ where let stream = match submit.await { Ok(stream) => stream, Err(err) => { - pending.reject(JsonRpseeError::from(err)); + let _ = sink.reject(JsonRpseeError::from(err)); return }, }; - let mut sink = match pending.accept() { - Some(sink) => sink, - _ => return, - }; - sink.pipe_from_stream(stream).await; }; self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); + Ok(()) } } diff --git a/client/rpc/src/chain/chain_full.rs b/client/rpc/src/chain/chain_full.rs index c00c6e5875d94..375e724a33d69 100644 --- a/client/rpc/src/chain/chain_full.rs +++ b/client/rpc/src/chain/chain_full.rs @@ -26,7 +26,7 @@ use futures::{ future::{self, FutureExt}, stream::{self, Stream, StreamExt}, }; -use jsonrpsee::PendingSubscription; +use jsonrpsee::SubscriptionSink; use sc_client_api::{BlockBackend, BlockchainEvents}; use sp_blockchain::HeaderBackend; use sp_runtime::{ @@ -69,7 +69,7 @@ where self.client.block(&BlockId::Hash(self.unwrap_or_best(hash))).map_err(client_err) } - fn subscribe_all_heads(&self, sink: PendingSubscription) { + fn subscribe_all_heads(&self, sink: SubscriptionSink) { subscribe_headers( &self.client, &self.executor, @@ -83,7 +83,7 @@ where ) } - fn subscribe_new_heads(&self, sink: PendingSubscription) { + fn subscribe_new_heads(&self, sink: SubscriptionSink) { subscribe_headers( &self.client, &self.executor, @@ -98,7 +98,7 @@ where ) } - fn subscribe_finalized_heads(&self, sink: PendingSubscription) { + fn subscribe_finalized_heads(&self, sink: SubscriptionSink) { subscribe_headers( &self.client, &self.executor, @@ -117,7 +117,7 @@ where fn subscribe_headers( client: &Arc, executor: &SubscriptionTaskExecutor, - pending: PendingSubscription, + mut sink: SubscriptionSink, best_block_hash: G, stream: F, ) where @@ -143,9 +143,7 @@ fn subscribe_headers( let stream = stream::iter(maybe_header).chain(stream()); let fut = async move { - if let Some(mut sink) = pending.accept() { - sink.pipe_from_stream(stream).await; - } + sink.pipe_from_stream(stream).await; }; executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); diff --git a/client/rpc/src/chain/mod.rs b/client/rpc/src/chain/mod.rs index a300c80271fd1..be06b91ca747f 100644 --- a/client/rpc/src/chain/mod.rs +++ b/client/rpc/src/chain/mod.rs @@ -27,7 +27,7 @@ use std::sync::Arc; use crate::SubscriptionTaskExecutor; -use jsonrpsee::{core::RpcResult, PendingSubscription}; +use jsonrpsee::{core::RpcResult, types::SubscriptionResult, SubscriptionSink}; use sc_client_api::BlockchainEvents; use sp_rpc::{list::ListOrValue, number::NumberOrHex}; use sp_runtime::{ @@ -95,13 +95,13 @@ where } /// All new head subscription - fn subscribe_all_heads(&self, sink: PendingSubscription); + fn subscribe_all_heads(&self, sink: SubscriptionSink); /// New best head subscription - fn subscribe_new_heads(&self, sink: PendingSubscription); + fn subscribe_new_heads(&self, sink: SubscriptionSink); /// Finalized head subscription - fn subscribe_finalized_heads(&self, sink: PendingSubscription); + fn subscribe_finalized_heads(&self, sink: SubscriptionSink); } /// Create new state API that works on full node. @@ -160,16 +160,19 @@ where self.backend.finalized_head().map_err(Into::into) } - fn subscribe_all_heads(&self, sink: PendingSubscription) { - self.backend.subscribe_all_heads(sink) + fn subscribe_all_heads(&self, sink: SubscriptionSink) -> SubscriptionResult { + self.backend.subscribe_all_heads(sink); + Ok(()) } - fn subscribe_new_heads(&self, sink: PendingSubscription) { - self.backend.subscribe_new_heads(sink) + fn subscribe_new_heads(&self, sink: SubscriptionSink) -> SubscriptionResult { + self.backend.subscribe_new_heads(sink); + Ok(()) } - fn subscribe_finalized_heads(&self, sink: PendingSubscription) { - self.backend.subscribe_finalized_heads(sink) + fn subscribe_finalized_heads(&self, sink: SubscriptionSink) -> SubscriptionResult { + self.backend.subscribe_finalized_heads(sink); + Ok(()) } } diff --git a/client/rpc/src/dev/tests.rs b/client/rpc/src/dev/tests.rs index b7a0de8f5ae0b..f3b18690d0972 100644 --- a/client/rpc/src/dev/tests.rs +++ b/client/rpc/src/dev/tests.rs @@ -17,8 +17,6 @@ // along with this program. If not, see . use super::*; -use assert_matches::assert_matches; -use jsonrpsee::{core::Error as JsonRpseeError, types::error::CallError}; use sc_block_builder::BlockBuilderProvider; use sp_blockchain::HeaderBackend; use sp_consensus::BlockOrigin; @@ -61,9 +59,18 @@ async fn deny_unsafe_works() { let block = client.new_block(Default::default()).unwrap().build().unwrap().block; client.import(BlockOrigin::Own, block).await.unwrap(); - assert_matches!( - api.call::<_, Option>("dev_getBlockStats", [client.info().best_hash]) - .await, - Err(JsonRpseeError::Call(CallError::Custom(err))) if err.message().contains("RPC call is unsafe to be called externally") + let best_hash = client.info().best_hash; + let best_hash_param = + serde_json::to_string(&best_hash).expect("To string must always succeed for block hashes"); + + let request = format!( + "{{\"jsonrpc\":\"2.0\",\"method\":\"dev_getBlockStats\",\"params\":[{}],\"id\":1}}", + best_hash_param + ); + let (resp, _) = api.raw_json_request(&request).await.expect("Raw calls should succeed"); + + assert_eq!( + resp.result, + r#"{"jsonrpc":"2.0","error":{"code":-32601,"message":"RPC call is unsafe to be called externally"},"id":1}"# ); } diff --git a/client/rpc/src/state/mod.rs b/client/rpc/src/state/mod.rs index 232be4edc8aab..7213e4360ae2b 100644 --- a/client/rpc/src/state/mod.rs +++ b/client/rpc/src/state/mod.rs @@ -29,7 +29,8 @@ use crate::SubscriptionTaskExecutor; use jsonrpsee::{ core::{Error as JsonRpseeError, RpcResult}, - ws_server::PendingSubscription, + types::SubscriptionResult, + ws_server::SubscriptionSink, }; use sc_rpc_api::{state::ReadProof, DenyUnsafe}; @@ -155,10 +156,10 @@ where ) -> Result; /// New runtime version subscription - fn subscribe_runtime_version(&self, sink: PendingSubscription); + fn subscribe_runtime_version(&self, sink: SubscriptionSink); /// New storage subscription - fn subscribe_storage(&self, sink: PendingSubscription, keys: Option>); + fn subscribe_storage(&self, sink: SubscriptionSink, keys: Option>); } /// Create new state API that works on full node. @@ -318,19 +319,25 @@ where .map_err(Into::into) } - fn subscribe_runtime_version(&self, sink: PendingSubscription) { - self.backend.subscribe_runtime_version(sink) + fn subscribe_runtime_version(&self, sink: SubscriptionSink) -> SubscriptionResult { + self.backend.subscribe_runtime_version(sink); + Ok(()) } - fn subscribe_storage(&self, sink: PendingSubscription, keys: Option>) { + fn subscribe_storage( + &self, + mut sink: SubscriptionSink, + keys: Option>, + ) -> SubscriptionResult { if keys.is_none() { if let Err(err) = self.deny_unsafe.check_if_safe() { let _ = sink.reject(JsonRpseeError::from(err)); - return + return Ok(()) } } - self.backend.subscribe_storage(sink, keys) + self.backend.subscribe_storage(sink, keys); + Ok(()) } } diff --git a/client/rpc/src/state/state_full.rs b/client/rpc/src/state/state_full.rs index 5a8a83cdd4851..42ba70b0af7e7 100644 --- a/client/rpc/src/state/state_full.rs +++ b/client/rpc/src/state/state_full.rs @@ -28,7 +28,7 @@ use super::{ use crate::SubscriptionTaskExecutor; use futures::{future, stream, FutureExt, StreamExt}; -use jsonrpsee::{core::Error as JsonRpseeError, PendingSubscription}; +use jsonrpsee::{core::Error as JsonRpseeError, SubscriptionSink}; use sc_client_api::{ Backend, BlockBackend, BlockchainEvents, CallExecutor, ExecutorProvider, ProofProvider, StorageProvider, @@ -357,7 +357,7 @@ where .map_err(client_err) } - fn subscribe_runtime_version(&self, pending: PendingSubscription) { + fn subscribe_runtime_version(&self, mut sink: SubscriptionSink) { let client = self.client.clone(); let initial = match self @@ -369,7 +369,7 @@ where { Ok(initial) => initial, Err(e) => { - pending.reject(JsonRpseeError::from(e)); + let _ = sink.reject(JsonRpseeError::from(e)); return }, }; @@ -397,19 +397,17 @@ where let stream = futures::stream::once(future::ready(initial)).chain(version_stream); let fut = async move { - if let Some(mut sink) = pending.accept() { - sink.pipe_from_stream(stream).await; - } + sink.pipe_from_stream(stream).await; }; self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); } - fn subscribe_storage(&self, pending: PendingSubscription, keys: Option>) { + fn subscribe_storage(&self, mut sink: SubscriptionSink, keys: Option>) { let stream = match self.client.storage_changes_notification_stream(keys.as_deref(), None) { Ok(stream) => stream, Err(blockchain_err) => { - pending.reject(JsonRpseeError::from(Error::Client(Box::new(blockchain_err)))); + let _ = sink.reject(JsonRpseeError::from(Error::Client(Box::new(blockchain_err)))); return }, }; @@ -442,9 +440,7 @@ where .filter(|storage| future::ready(!storage.changes.is_empty())); let fut = async move { - if let Some(mut sink) = pending.accept() { - sink.pipe_from_stream(stream).await; - } + sink.pipe_from_stream(stream).await; }; self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); diff --git a/client/service/Cargo.toml b/client/service/Cargo.toml index 31b0c860cf190..e8ddf40a0ae03 100644 --- a/client/service/Cargo.toml +++ b/client/service/Cargo.toml @@ -22,7 +22,7 @@ wasmtime = ["sc-executor/wasmtime"] test-helpers = [] [dependencies] -jsonrpsee = { version = "0.14.0", features = ["server"] } +jsonrpsee = { version = "0.15.1", features = ["server"] } thiserror = "1.0.30" futures = "0.3.21" rand = "0.7.3" diff --git a/client/service/src/lib.rs b/client/service/src/lib.rs index 2e7b611ffb187..98bcb17174157 100644 --- a/client/service/src/lib.rs +++ b/client/service/src/lib.rs @@ -101,7 +101,10 @@ impl RpcHandlers { &self, json_query: &str, ) -> Result<(String, mpsc::UnboundedReceiver), JsonRpseeError> { - self.0.raw_json_request(json_query).await + self.0 + .raw_json_request(json_query) + .await + .map(|(method_res, recv)| (method_res.result, recv)) } /// Provides access to the underlying `RpcModule` diff --git a/client/sync-state-rpc/Cargo.toml b/client/sync-state-rpc/Cargo.toml index bd9194092fa3d..d02637fcf884b 100644 --- a/client/sync-state-rpc/Cargo.toml +++ b/client/sync-state-rpc/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } -jsonrpsee = { version = "0.14.0", features = ["server", "macros"] } +jsonrpsee = { version = "0.15.1", features = ["server", "macros"] } serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0.79" thiserror = "1.0.30" diff --git a/frame/contracts/rpc/Cargo.toml b/frame/contracts/rpc/Cargo.toml index a81abef9f37ca..7876c7cba40d0 100644 --- a/frame/contracts/rpc/Cargo.toml +++ b/frame/contracts/rpc/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } -jsonrpsee = { version = "0.14.0", features = ["server", "macros"] } +jsonrpsee = { version = "0.15.1", features = ["server", "macros"] } serde = { version = "1", features = ["derive"] } # Substrate Dependencies diff --git a/frame/merkle-mountain-range/rpc/Cargo.toml b/frame/merkle-mountain-range/rpc/Cargo.toml index 45cb975df277b..c7d9662904747 100644 --- a/frame/merkle-mountain-range/rpc/Cargo.toml +++ b/frame/merkle-mountain-range/rpc/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } -jsonrpsee = { version = "0.14.0", features = ["server", "macros"] } +jsonrpsee = { version = "0.15.1", features = ["server", "macros"] } serde = { version = "1.0.136", features = ["derive"] } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } diff --git a/frame/transaction-payment/rpc/Cargo.toml b/frame/transaction-payment/rpc/Cargo.toml index 31e0972a0d5b5..16c2cc55efefb 100644 --- a/frame/transaction-payment/rpc/Cargo.toml +++ b/frame/transaction-payment/rpc/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } -jsonrpsee = { version = "0.14.0", features = ["server", "macros"] } +jsonrpsee = { version = "0.15.1", features = ["server", "macros"] } pallet-transaction-payment-rpc-runtime-api = { version = "4.0.0-dev", path = "./runtime-api" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } diff --git a/utils/frame/remote-externalities/Cargo.toml b/utils/frame/remote-externalities/Cargo.toml index a77d090246421..3121157df68d8 100644 --- a/utils/frame/remote-externalities/Cargo.toml +++ b/utils/frame/remote-externalities/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } env_logger = "0.9" -jsonrpsee = { version = "0.14.0", features = ["ws-client", "macros"] } +jsonrpsee = { version = "0.15.1", features = ["ws-client", "macros"] } log = "0.4.17" serde = "1.0.136" serde_json = "1.0" diff --git a/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml b/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml index 6c6bc5cf327b5..00fdc87a506e8 100644 --- a/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml +++ b/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml @@ -25,7 +25,7 @@ sp-state-machine = { path = "../../../../primitives/state-machine" } sp-trie = { path = "../../../../primitives/trie" } trie-db = { version = "0.23.1" } -jsonrpsee = { version = "0.14.0", features = ["server", "macros"] } +jsonrpsee = { version = "0.15.1", features = ["server", "macros"] } # Substrate Dependencies sc-client-api = { version = "4.0.0-dev", path = "../../../../client/api" } diff --git a/utils/frame/rpc/support/Cargo.toml b/utils/frame/rpc/support/Cargo.toml index 7ea07534e1bdb..2104774bd2605 100644 --- a/utils/frame/rpc/support/Cargo.toml +++ b/utils/frame/rpc/support/Cargo.toml @@ -17,7 +17,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0" } futures = "0.3.21" -jsonrpsee = { version = "0.14.0", features = ["jsonrpsee-types"] } +jsonrpsee = { version = "0.15.1", features = ["jsonrpsee-types"] } serde = "1" frame-support = { version = "4.0.0-dev", path = "../../../../frame/support" } sc-rpc-api = { version = "0.10.0-dev", path = "../../../../client/rpc-api" } @@ -25,6 +25,6 @@ sp-storage = { version = "6.0.0", path = "../../../../primitives/storage" } [dev-dependencies] scale-info = "2.1.1" -jsonrpsee = { version = "0.14.0", features = ["ws-client", "jsonrpsee-types"] } +jsonrpsee = { version = "0.15.1", features = ["ws-client", "jsonrpsee-types"] } tokio = "1.17.0" frame-system = { version = "4.0.0-dev", path = "../../../../frame/system" } diff --git a/utils/frame/rpc/system/Cargo.toml b/utils/frame/rpc/system/Cargo.toml index f76944a0fec40..5d8984e8d399b 100644 --- a/utils/frame/rpc/system/Cargo.toml +++ b/utils/frame/rpc/system/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] serde_json = "1" codec = { package = "parity-scale-codec", version = "3.0.0" } -jsonrpsee = { version = "0.14.0", features = ["server"] } +jsonrpsee = { version = "0.15.1", features = ["server"] } futures = "0.3.21" log = "0.4.17" frame-system-rpc-runtime-api = { version = "4.0.0-dev", path = "../../../../frame/system/rpc/runtime-api" } diff --git a/utils/frame/try-runtime/cli/Cargo.toml b/utils/frame/try-runtime/cli/Cargo.toml index b82da0f1222a7..4b4f9bdb2809a 100644 --- a/utils/frame/try-runtime/cli/Cargo.toml +++ b/utils/frame/try-runtime/cli/Cargo.toml @@ -19,7 +19,7 @@ parity-scale-codec = "3.0.0" serde = "1.0.136" zstd = { version = "0.11.2", default-features = false } remote-externalities = { version = "0.10.0-dev", path = "../../remote-externalities" } -jsonrpsee = { version = "0.14.0", default-features = false, features = ["ws-client"] } +jsonrpsee = { version = "0.15.1", default-features = false, features = ["ws-client"] } sc-chain-spec = { version = "4.0.0-dev", path = "../../../../client/chain-spec" } sc-cli = { version = "0.10.0-dev", path = "../../../../client/cli" } sc-executor = { version = "0.10.0-dev", path = "../../../../client/executor" } From 69bc1865c105bb198bf7af3e1a0ab55540db4552 Mon Sep 17 00:00:00 2001 From: Andronik Date: Tue, 2 Aug 2022 20:49:03 +0200 Subject: [PATCH 453/484] offences: make fn slash_fraction non-static (#11956) * offences: make fn slash_fraction non-static * Bastifmt (inline variable) --- frame/babe/src/equivocation.rs | 4 ++-- frame/grandpa/src/equivocation.rs | 4 ++-- frame/im-online/src/lib.rs | 6 +++--- frame/im-online/src/tests.rs | 10 ++++++---- frame/offences/benchmarking/src/lib.rs | 6 ++---- frame/offences/src/lib.rs | 3 +-- frame/offences/src/mock.rs | 4 ++-- primitives/staking/src/offence.rs | 7 +++---- 8 files changed, 21 insertions(+), 23 deletions(-) diff --git a/frame/babe/src/equivocation.rs b/frame/babe/src/equivocation.rs index df46f3544b389..f55bda751887d 100644 --- a/frame/babe/src/equivocation.rs +++ b/frame/babe/src/equivocation.rs @@ -284,9 +284,9 @@ impl Offence self.slot } - fn slash_fraction(offenders_count: u32, validator_set_count: u32) -> Perbill { + fn slash_fraction(&self, offenders_count: u32) -> Perbill { // the formula is min((3k / n)^2, 1) - let x = Perbill::from_rational(3 * offenders_count, validator_set_count); + let x = Perbill::from_rational(3 * offenders_count, self.validator_set_count); // _ ^ 2 x.square() } diff --git a/frame/grandpa/src/equivocation.rs b/frame/grandpa/src/equivocation.rs index 804272c20480f..181d22fba545c 100644 --- a/frame/grandpa/src/equivocation.rs +++ b/frame/grandpa/src/equivocation.rs @@ -353,9 +353,9 @@ impl Offence self.time_slot } - fn slash_fraction(offenders_count: u32, validator_set_count: u32) -> Perbill { + fn slash_fraction(&self, offenders_count: u32) -> Perbill { // the formula is min((3k / n)^2, 1) - let x = Perbill::from_rational(3 * offenders_count, validator_set_count); + let x = Perbill::from_rational(3 * offenders_count, self.validator_set_count); // _ ^ 2 x.square() } diff --git a/frame/im-online/src/lib.rs b/frame/im-online/src/lib.rs index f190f6672f309..34c1c70d79f75 100644 --- a/frame/im-online/src/lib.rs +++ b/frame/im-online/src/lib.rs @@ -958,12 +958,12 @@ impl Offence for UnresponsivenessOffence { self.session_index } - fn slash_fraction(offenders: u32, validator_set_count: u32) -> Perbill { + fn slash_fraction(&self, offenders: u32) -> Perbill { // the formula is min((3 * (k - (n / 10 + 1))) / n, 1) * 0.07 // basically, 10% can be offline with no slash, but after that, it linearly climbs up to 7% // when 13/30 are offline (around 5% when 1/3 are offline). - if let Some(threshold) = offenders.checked_sub(validator_set_count / 10 + 1) { - let x = Perbill::from_rational(3 * threshold, validator_set_count); + if let Some(threshold) = offenders.checked_sub(self.validator_set_count / 10 + 1) { + let x = Perbill::from_rational(3 * threshold, self.validator_set_count); x.saturating_mul(Perbill::from_percent(7)) } else { Perbill::default() diff --git a/frame/im-online/src/tests.rs b/frame/im-online/src/tests.rs index 288081556a085..05e1af169dba9 100644 --- a/frame/im-online/src/tests.rs +++ b/frame/im-online/src/tests.rs @@ -36,22 +36,24 @@ use sp_runtime::{ #[test] fn test_unresponsiveness_slash_fraction() { + let dummy_offence = + UnresponsivenessOffence { session_index: 0, validator_set_count: 50, offenders: vec![()] }; // A single case of unresponsiveness is not slashed. - assert_eq!(UnresponsivenessOffence::<()>::slash_fraction(1, 50), Perbill::zero()); + assert_eq!(dummy_offence.slash_fraction(1), Perbill::zero()); assert_eq!( - UnresponsivenessOffence::<()>::slash_fraction(5, 50), + dummy_offence.slash_fraction(5), Perbill::zero(), // 0% ); assert_eq!( - UnresponsivenessOffence::<()>::slash_fraction(7, 50), + dummy_offence.slash_fraction(7), Perbill::from_parts(4200000), // 0.42% ); // One third offline should be punished around 5%. assert_eq!( - UnresponsivenessOffence::<()>::slash_fraction(17, 50), + dummy_offence.slash_fraction(17), Perbill::from_parts(46200000), // 4.62% ); } diff --git a/frame/offences/benchmarking/src/lib.rs b/frame/offences/benchmarking/src/lib.rs index 98c6390964d82..b793bd8d2699a 100644 --- a/frame/offences/benchmarking/src/lib.rs +++ b/frame/offences/benchmarking/src/lib.rs @@ -288,15 +288,13 @@ benchmarks! { let (offenders, raw_offenders) = make_offenders_im_online::(o, n)?; let keys = ImOnline::::keys(); let validator_set_count = keys.len() as u32; - - let slash_fraction = UnresponsivenessOffence::::slash_fraction( - offenders.len() as u32, validator_set_count, - ); + let offenders_count = offenders.len() as u32; let offence = UnresponsivenessOffence { session_index: 0, validator_set_count, offenders, }; + let slash_fraction = offence.slash_fraction(offenders_count); assert_eq!(System::::event_count(), 0); }: { let _ = ::ReportUnresponsiveness::report_offence( diff --git a/frame/offences/src/lib.rs b/frame/offences/src/lib.rs index e4b75d9c3c015..ae454d6b06740 100644 --- a/frame/offences/src/lib.rs +++ b/frame/offences/src/lib.rs @@ -120,7 +120,6 @@ where fn report_offence(reporters: Vec, offence: O) -> Result<(), OffenceError> { let offenders = offence.offenders(); let time_slot = offence.time_slot(); - let validator_set_count = offence.validator_set_count(); // Go through all offenders in the offence report and find all offenders that were spotted // in unique reports. @@ -134,7 +133,7 @@ where let offenders_count = concurrent_offenders.len() as u32; // The amount new offenders are slashed - let new_fraction = O::slash_fraction(offenders_count, validator_set_count); + let new_fraction = offence.slash_fraction(offenders_count); let slash_perbill: Vec<_> = (0..concurrent_offenders.len()).map(|_| new_fraction).collect(); diff --git a/frame/offences/src/mock.rs b/frame/offences/src/mock.rs index 6a69b54b3cca0..d9ecf44ad8734 100644 --- a/frame/offences/src/mock.rs +++ b/frame/offences/src/mock.rs @@ -168,8 +168,8 @@ impl offence::Offence for Offence { 1 } - fn slash_fraction(offenders_count: u32, validator_set_count: u32) -> Perbill { - Perbill::from_percent(5 + offenders_count * 100 / validator_set_count) + fn slash_fraction(&self, offenders_count: u32) -> Perbill { + Perbill::from_percent(5 + offenders_count * 100 / self.validator_set_count) } } diff --git a/primitives/staking/src/offence.rs b/primitives/staking/src/offence.rs index 4261063993a52..f6517b9e9028b 100644 --- a/primitives/staking/src/offence.rs +++ b/primitives/staking/src/offence.rs @@ -108,11 +108,10 @@ pub trait Offence { } /// A slash fraction of the total exposure that should be slashed for this - /// particular offence kind for the given parameters that happened at a singular `TimeSlot`. + /// particular offence for the `offenders_count` that happened at a singular `TimeSlot`. /// - /// `offenders_count` - the count of unique offending authorities. It is >0. - /// `validator_set_count` - the cardinality of the validator set at the time of offence. - fn slash_fraction(offenders_count: u32, validator_set_count: u32) -> Perbill; + /// `offenders_count` - the count of unique offending authorities for this `TimeSlot`. It is >0. + fn slash_fraction(&self, offenders_count: u32) -> Perbill; } /// Errors that may happen on offence reports. From c4f8fdfcad9e0bcd4a398ecbaf5295b9b5df2395 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Wed, 3 Aug 2022 10:46:10 +0200 Subject: [PATCH 454/484] Revert non-best block (#11716) * Revert non-best block This makes `revert` also revert non-best blocks. * Update client/db/src/lib.rs * Do not count leaves against the maximum number to revert * Add some explanation * Fix bug * Apply suggestions from code review Co-authored-by: Davide Galassi Co-authored-by: Davide Galassi --- client/api/src/backend.rs | 3 +- client/api/src/leaves.rs | 5 + client/db/src/lib.rs | 149 +++++++++++++++---- client/service/src/chain_ops/revert_chain.rs | 7 + 4 files changed, 135 insertions(+), 29 deletions(-) diff --git a/client/api/src/backend.rs b/client/api/src/backend.rs index af8552886b72e..54784a2f27b64 100644 --- a/client/api/src/backend.rs +++ b/client/api/src/backend.rs @@ -507,7 +507,8 @@ pub trait Backend: AuxStore + Send + Sync { /// Attempts to revert the chain by `n` blocks. If `revert_finalized` is set it will attempt to /// revert past any finalized block, this is unsafe and can potentially leave the node in an - /// inconsistent state. + /// inconsistent state. All blocks higher than the best block are also reverted and not counting + /// towards `n`. /// /// Returns the number of blocks that were successfully reverted and the list of finalized /// blocks that has been reverted. diff --git a/client/api/src/leaves.rs b/client/api/src/leaves.rs index 2e5d4be3a5462..26eda46e6f76f 100644 --- a/client/api/src/leaves.rs +++ b/client/api/src/leaves.rs @@ -259,6 +259,11 @@ where removed } + + /// Returns the highest leaf and all hashes associated to it. + pub fn highest_leaf(&self) -> Option<(N, &[H])> { + self.storage.iter().next().map(|(k, v)| (k.0.clone(), &v[..])) + } } /// Helper for undoing operations. diff --git a/client/db/src/lib.rs b/client/db/src/lib.rs index 7dd49f9831f1c..e14d3a26aa557 100644 --- a/client/db/src/lib.rs +++ b/client/db/src/lib.rs @@ -2057,36 +2057,46 @@ impl sc_client_api::backend::Backend for Backend { ) -> ClientResult<(NumberFor, HashSet)> { let mut reverted_finalized = HashSet::new(); - let mut best_number = self.blockchain.info().best_number; - let mut best_hash = self.blockchain.info().best_hash; + let info = self.blockchain.info(); - let finalized = self.blockchain.info().finalized_number; + let highest_leaf = self + .blockchain + .leaves + .read() + .highest_leaf() + .and_then(|(n, h)| h.last().map(|h| (n, *h))); + + let best_number = info.best_number; + let best_hash = info.best_hash; + + let finalized = info.finalized_number; let revertible = best_number - finalized; let n = if !revert_finalized && revertible < n { revertible } else { n }; + let (n, mut number_to_revert, mut hash_to_revert) = match highest_leaf { + Some((l_n, l_h)) => (n + (l_n - best_number), l_n, l_h), + None => (n, best_number, best_hash), + }; + let mut revert_blocks = || -> ClientResult> { for c in 0..n.saturated_into::() { - if best_number.is_zero() { + if number_to_revert.is_zero() { return Ok(c.saturated_into::>()) } let mut transaction = Transaction::new(); let removed = - self.blockchain.header(BlockId::Number(best_number))?.ok_or_else(|| { + self.blockchain.header(BlockId::Hash(hash_to_revert))?.ok_or_else(|| { sp_blockchain::Error::UnknownBlock(format!( - "Error reverting to {}. Block hash not found.", - best_number + "Error reverting to {}. Block header not found.", + hash_to_revert, )) })?; let removed_hash = removed.hash(); - let prev_number = best_number.saturating_sub(One::one()); - let prev_hash = self.blockchain.hash(prev_number)?.ok_or_else(|| { - sp_blockchain::Error::UnknownBlock(format!( - "Error reverting to {}. Block hash not found.", - best_number - )) - })?; + let prev_number = number_to_revert.saturating_sub(One::one()); + let prev_hash = + if prev_number == best_number { best_hash } else { *removed.parent_hash() }; if !self.have_state_at(&prev_hash, prev_number) { return Ok(c.saturated_into::>()) @@ -2096,12 +2106,15 @@ impl sc_client_api::backend::Backend for Backend { Some(commit) => { apply_state_commit(&mut transaction, commit); - best_number = prev_number; - best_hash = prev_hash; + number_to_revert = prev_number; + hash_to_revert = prev_hash; - let update_finalized = best_number < finalized; + let update_finalized = number_to_revert < finalized; - let key = utils::number_and_hash_to_lookup_key(best_number, &best_hash)?; + let key = utils::number_and_hash_to_lookup_key( + number_to_revert, + &hash_to_revert, + )?; if update_finalized { transaction.set_from_vec( columns::META, @@ -2111,12 +2124,14 @@ impl sc_client_api::backend::Backend for Backend { reverted_finalized.insert(removed_hash); if let Some((hash, _)) = self.blockchain.info().finalized_state { - if hash == best_hash { - if !best_number.is_zero() && - self.have_state_at(&prev_hash, best_number - One::one()) - { + if hash == hash_to_revert { + if !number_to_revert.is_zero() && + self.have_state_at( + &prev_hash, + number_to_revert - One::one(), + ) { let lookup_key = utils::number_and_hash_to_lookup_key( - best_number - One::one(), + number_to_revert - One::one(), prev_hash, )?; transaction.set_from_vec( @@ -2137,13 +2152,16 @@ impl sc_client_api::backend::Backend for Backend { &mut transaction, columns::META, meta_keys::CHILDREN_PREFIX, - best_hash, + hash_to_revert, ); self.storage.db.commit(transaction)?; + + let is_best = number_to_revert < best_number; + self.blockchain.update_meta(MetaUpdate { - hash: best_hash, - number: best_number, - is_best: true, + hash: hash_to_revert, + number: number_to_revert, + is_best, is_finalized: update_finalized, with_state: false, }); @@ -2161,7 +2179,7 @@ impl sc_client_api::backend::Backend for Backend { let mut transaction = Transaction::new(); let mut leaves = self.blockchain.leaves.write(); - leaves.revert(best_hash, best_number); + leaves.revert(hash_to_revert, number_to_revert); leaves.prepare_transaction(&mut transaction, columns::META, meta_keys::LEAF_PREFIX); self.storage.db.commit(transaction)?; @@ -3463,4 +3481,79 @@ pub(crate) mod tests { insert_header_no_head(&backend, 1, block0, [1; 32].into()); assert_eq!(backend.blockchain().leaves().unwrap(), vec![block2_a]); } + + #[test] + fn revert_non_best_blocks() { + let backend = Backend::::new_test(10, 10); + + let genesis = + insert_block(&backend, 0, Default::default(), None, Default::default(), vec![], None) + .unwrap(); + + let block1 = + insert_block(&backend, 1, genesis, None, Default::default(), vec![], None).unwrap(); + + let block2 = + insert_block(&backend, 2, block1, None, Default::default(), vec![], None).unwrap(); + + let block3 = { + let mut op = backend.begin_operation().unwrap(); + backend.begin_state_operation(&mut op, BlockId::Number(1)).unwrap(); + let header = Header { + number: 3, + parent_hash: block2, + state_root: BlakeTwo256::trie_root(Vec::new(), StateVersion::V1), + digest: Default::default(), + extrinsics_root: Default::default(), + }; + + op.set_block_data(header.clone(), Some(Vec::new()), None, None, NewBlockState::Normal) + .unwrap(); + + backend.commit_operation(op).unwrap(); + + header.hash() + }; + + let block4 = { + let mut op = backend.begin_operation().unwrap(); + backend.begin_state_operation(&mut op, BlockId::Hash(block2)).unwrap(); + let header = Header { + number: 4, + parent_hash: block3, + state_root: BlakeTwo256::trie_root(Vec::new(), StateVersion::V1), + digest: Default::default(), + extrinsics_root: Default::default(), + }; + + op.set_block_data(header.clone(), Some(Vec::new()), None, None, NewBlockState::Normal) + .unwrap(); + + backend.commit_operation(op).unwrap(); + + header.hash() + }; + + let block3_fork = insert_header_no_head(&backend, 3, block2, Default::default()); + + assert!(backend.have_state_at(&block1, 1)); + assert!(backend.have_state_at(&block2, 2)); + assert!(backend.have_state_at(&block3, 3)); + assert!(backend.have_state_at(&block4, 4)); + assert!(backend.have_state_at(&block3_fork, 3)); + + assert_eq!(backend.blockchain.leaves().unwrap(), vec![block4, block3_fork]); + assert_eq!(4, backend.blockchain.leaves.read().highest_leaf().unwrap().0); + + assert_eq!(3, backend.revert(1, false).unwrap().0); + + assert!(backend.have_state_at(&block1, 1)); + assert!(!backend.have_state_at(&block2, 2)); + assert!(!backend.have_state_at(&block3, 3)); + assert!(!backend.have_state_at(&block4, 4)); + assert!(!backend.have_state_at(&block3_fork, 3)); + + assert_eq!(backend.blockchain.leaves().unwrap(), vec![block1]); + assert_eq!(1, backend.blockchain.leaves.read().highest_leaf().unwrap().0); + } } diff --git a/client/service/src/chain_ops/revert_chain.rs b/client/service/src/chain_ops/revert_chain.rs index 9a3ce6024ed92..3ee4399d063b3 100644 --- a/client/service/src/chain_ops/revert_chain.rs +++ b/client/service/src/chain_ops/revert_chain.rs @@ -40,6 +40,13 @@ where info!("There aren't any non-finalized blocks to revert."); } else { info!("Reverted {} blocks. Best: #{} ({})", reverted.0, info.best_number, info.best_hash); + + if reverted.0 > blocks { + info!( + "Number of reverted blocks is higher than requested \ + because of reverted leaves higher than the best block." + ) + } } Ok(()) } From 7bcd47c47bb222ba49b3a2d224d85a9c50f0899e Mon Sep 17 00:00:00 2001 From: Sacha Lansky Date: Wed, 3 Aug 2022 12:34:33 +0100 Subject: [PATCH 455/484] Fix docs urls (#11966) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix docs urls * Update bin/node-template/README.md * Update frame/aura/src/lib.rs * Run cargo +nightly fmt Co-authored-by: Bastian Köcher --- README.md | 2 +- bin/node-template/README.md | 32 ++++++++----------- bin/node-template/docs/rust-setup.md | 6 ++-- bin/node-template/pallets/template/src/lib.rs | 10 +++--- client/rpc-api/src/state/mod.rs | 2 +- frame/examples/basic/src/benchmarking.rs | 2 +- frame/examples/basic/src/lib.rs | 2 +- frame/remark/src/tests.rs | 3 ++ frame/sudo/README.md | 2 +- frame/sudo/src/lib.rs | 2 +- .../procedural/src/pallet/expand/error.rs | 2 +- .../procedural/src/pallet/expand/event.rs | 2 +- .../procedural/src/pallet/expand/mod.rs | 2 +- .../src/pallet/expand/pallet_struct.rs | 2 +- frame/support/src/dispatch.rs | 2 +- frame/support/src/traits/misc.rs | 2 +- primitives/runtime/src/bounded/bounded_vec.rs | 2 +- .../runtime/src/bounded/weak_bounded_vec.rs | 2 +- 18 files changed, 39 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index b716794428a00..c609641af7ce2 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Substrate is a next-generation framework for blockchain innovation 🚀. ## Trying it out Simply go to [docs.substrate.io](https://docs.substrate.io) and follow the -[installation](https://docs.substrate.io/v3/getting-started/overview) instructions. You can +[installation](https://docs.substrate.io/main-docs/install/) instructions. You can also try out one of the [tutorials](https://docs.substrate.io/tutorials/). ## Contributions & Code of Conduct diff --git a/bin/node-template/README.md b/bin/node-template/README.md index 8defb870fa1b0..0f6fd9450aeee 100644 --- a/bin/node-template/README.md +++ b/bin/node-template/README.md @@ -114,7 +114,7 @@ local node template. ### Multi-Node Local Testnet If you want to see the multi-node consensus algorithm in action, refer to our -[Start a Private Network tutorial](https://docs.substrate.io/tutorials/v3/private-network). +[Simulate a network tutorial](https://docs.substrate.io/tutorials/get-started/simulate-network/). ## Template Structure @@ -129,7 +129,7 @@ Substrate-based blockchain nodes expose a number of capabilities: - Networking: Substrate nodes use the [`libp2p`](https://libp2p.io/) networking stack to allow the nodes in the network to communicate with one another. - Consensus: Blockchains must have a way to come to - [consensus](https://docs.substrate.io/v3/advanced/consensus) on the state of the + [consensus](https://docs.substrate.io/main-docs/fundamentals/consensus/) on the state of the network. Substrate makes it possible to supply custom consensus engines and also ships with several consensus mechanisms that have been built on top of [Web3 Foundation research](https://research.web3.foundation/en/latest/polkadot/NPoS/index.html). @@ -138,22 +138,20 @@ Substrate-based blockchain nodes expose a number of capabilities: There are several files in the `node` directory - take special note of the following: - [`chain_spec.rs`](./node/src/chain_spec.rs): A - [chain specification](https://docs.substrate.io/v3/runtime/chain-specs) is a + [chain specification](https://docs.substrate.io/main-docs/build/chain-spec/) is a source code file that defines a Substrate chain's initial (genesis) state. Chain specifications are useful for development and testing, and critical when architecting the launch of a production chain. Take note of the `development_config` and `testnet_genesis` functions, which are used to define the genesis state for the local development chain configuration. These functions identify some - [well-known accounts](https://docs.substrate.io/v3/tools/subkey#well-known-keys) + [well-known accounts](https://docs.substrate.io/reference/command-line-tools/subkey/) and use them to configure the blockchain's initial state. - [`service.rs`](./node/src/service.rs): This file defines the node implementation. Take note of the libraries that this file imports and the names of the functions it invokes. In particular, there are references to consensus-related topics, such as the - [longest chain rule](https://docs.substrate.io/v3/advanced/consensus#longest-chain-rule), - the [Aura](https://docs.substrate.io/v3/advanced/consensus#aura) block authoring - mechanism and the - [GRANDPA](https://docs.substrate.io/v3/advanced/consensus#grandpa) finality - gadget. + [block finalization and forks](https://docs.substrate.io/main-docs/fundamentals/consensus/#finalization-and-forks) + and other [consensus mechanisms](https://docs.substrate.io/main-docs/fundamentals/consensus/#default-consensus-models) + such as Aura for block authoring and GRANDPA for finality. After the node has been [built](#build), refer to the embedded documentation to learn more about the capabilities and configuration parameters that it exposes: @@ -165,16 +163,15 @@ capabilities and configuration parameters that it exposes: ### Runtime In Substrate, the terms -"[runtime](https://docs.substrate.io/v3/getting-started/glossary#runtime)" and -"[state transition function](https://docs.substrate.io/v3/getting-started/glossary#state-transition-function-stf)" +"runtime" and "state transition function" are analogous - they refer to the core logic of the blockchain that is responsible for validating blocks and executing the state changes they define. The Substrate project in this repository uses -the [FRAME](https://docs.substrate.io/v3/runtime/frame) framework to construct a +[FRAME](https://docs.substrate.io/main-docs/fundamentals/runtime-intro/#frame) to construct a blockchain runtime. FRAME allows runtime developers to declare domain-specific logic in modules called "pallets". At the heart of FRAME is a helpful -[macro language](https://docs.substrate.io/v3/runtime/macros) that makes it easy to +[macro language](https://docs.substrate.io/reference/frame-macros/) that makes it easy to create pallets and flexibly compose them to create blockchains that can address -[a variety of needs](https://www.substrate.io/substrate-users/). +[a variety of needs](https://substrate.io/ecosystem/projects/). Review the [FRAME runtime implementation](./runtime/src/lib.rs) included in this template and note the following: @@ -184,8 +181,7 @@ the following: - The pallets are composed into a single runtime by way of the [`construct_runtime!`](https://crates.parity.io/frame_support/macro.construct_runtime.html) macro, which is part of the core - [FRAME Support](https://docs.substrate.io/v3/runtime/frame#support-crate) - library. + FRAME Support [system](https://docs.substrate.io/reference/frame-pallets/#system-pallets) library. ### Pallets @@ -196,12 +192,12 @@ template pallet that is [defined in the `pallets`](./pallets/template/src/lib.rs A FRAME pallet is compromised of a number of blockchain primitives: - Storage: FRAME defines a rich set of powerful - [storage abstractions](https://docs.substrate.io/v3/runtime/storage) that makes + [storage abstractions](https://docs.substrate.io/main-docs/build/runtime-storage/) that makes it easy to use Substrate's efficient key-value database to manage the evolving state of a blockchain. - Dispatchables: FRAME pallets define special types of functions that can be invoked (dispatched) from outside of the runtime in order to update its state. -- Events: Substrate uses [events and errors](https://docs.substrate.io/v3/runtime/events-and-errors) +- Events: Substrate uses [events and errors](https://docs.substrate.io/main-docs/build/events-errors/) to notify users of important changes in the runtime. - Errors: When a dispatchable fails, it returns an error. - Config: The `Config` configuration interface is used to define the types and parameters upon diff --git a/bin/node-template/docs/rust-setup.md b/bin/node-template/docs/rust-setup.md index ea133ca847af7..2755966e3ae0f 100644 --- a/bin/node-template/docs/rust-setup.md +++ b/bin/node-template/docs/rust-setup.md @@ -3,7 +3,7 @@ title: Installation --- This guide is for reference only, please check the latest information on getting starting with Substrate -[here](https://docs.substrate.io/v3/getting-started/installation/). +[here](https://docs.substrate.io/main-docs/install/). This page will guide you through the **2 steps** needed to prepare a computer for **Substrate** development. Since Substrate is built with [the Rust programming language](https://www.rust-lang.org/), the first @@ -73,11 +73,11 @@ brew install openssl ### Windows -**_PLEASE NOTE:_** Native development of Substrate is _not_ very well supported! It is _highly_ +**_PLEASE NOTE:_** Native Windows development of Substrate is _not_ very well supported! It is _highly_ recommend to use [Windows Subsystem Linux](https://docs.microsoft.com/en-us/windows/wsl/install-win10) (WSL) and follow the instructions for [Ubuntu/Debian](#ubuntudebian). Please refer to the separate -[guide for native Windows development](https://docs.substrate.io/v3/getting-started/windows-users/). +[guide for native Windows development](https://docs.substrate.io/main-docs/install/windows/). ## Rust developer environment diff --git a/bin/node-template/pallets/template/src/lib.rs b/bin/node-template/pallets/template/src/lib.rs index 067c7ce2575a0..2c337146859ef 100644 --- a/bin/node-template/pallets/template/src/lib.rs +++ b/bin/node-template/pallets/template/src/lib.rs @@ -2,7 +2,7 @@ /// Edit this file to define custom logic or remove it if it is not needed. /// Learn more about FRAME and the core library of Substrate FRAME pallets: -/// +/// pub use pallet::*; #[cfg(test)] @@ -31,15 +31,15 @@ pub mod pallet { pub struct Pallet(_); // The pallet's runtime storage items. - // https://docs.substrate.io/v3/runtime/storage + // https://docs.substrate.io/main-docs/build/runtime-storage/ #[pallet::storage] #[pallet::getter(fn something)] // Learn more about declaring storage items: - // https://docs.substrate.io/v3/runtime/storage#declaring-storage-items + // https://docs.substrate.io/main-docs/build/runtime-storage/#declaring-storage-items pub type Something = StorageValue<_, u32>; // Pallets use events to inform users when important changes are made. - // https://docs.substrate.io/v3/runtime/events-and-errors + // https://docs.substrate.io/main-docs/build/events-errors/ #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { @@ -68,7 +68,7 @@ pub mod pallet { pub fn do_something(origin: OriginFor, something: u32) -> DispatchResult { // Check that the extrinsic was signed and get the signer. // This function will return an error if the extrinsic is not signed. - // https://docs.substrate.io/v3/runtime/origins + // https://docs.substrate.io/main-docs/build/origins/ let who = ensure_signed(origin)?; // Update storage. diff --git a/client/rpc-api/src/state/mod.rs b/client/rpc-api/src/state/mod.rs index 54bf21674a8bd..40e208c2eba8d 100644 --- a/client/rpc-api/src/state/mod.rs +++ b/client/rpc-api/src/state/mod.rs @@ -265,7 +265,7 @@ pub trait StateApi { /// [substrate storage][1], [transparent keys in substrate][2], /// [querying substrate storage via rpc][3]. /// - /// [1]: https://docs.substrate.io/v3/advanced/storage#storage-map-keys + /// [1]: https://docs.substrate.io/main-docs/fundamentals/state-transitions-and-storage/ /// [2]: https://www.shawntabrizi.com/substrate/transparent-keys-in-substrate/ /// [3]: https://www.shawntabrizi.com/substrate/querying-substrate-storage-via-rpc/ /// diff --git a/frame/examples/basic/src/benchmarking.rs b/frame/examples/basic/src/benchmarking.rs index d7b933577ead5..93e14f358208e 100644 --- a/frame/examples/basic/src/benchmarking.rs +++ b/frame/examples/basic/src/benchmarking.rs @@ -26,7 +26,7 @@ use frame_system::RawOrigin; // To actually run this benchmark on pallet-example-basic, we need to put this pallet into the // runtime and compile it with `runtime-benchmarks` feature. The detail procedures are // documented at: -// https://docs.substrate.io/v3/runtime/benchmarking#how-to-benchmark +// https://docs.substrate.io/reference/how-to-guides/weights/add-benchmarks/ // // The auto-generated weight estimate of this pallet is copied over to the `weights.rs` file. // The exact command of how the estimate generated is printed at the top of the file. diff --git a/frame/examples/basic/src/lib.rs b/frame/examples/basic/src/lib.rs index f8acc1962388f..03dc2c613c01e 100644 --- a/frame/examples/basic/src/lib.rs +++ b/frame/examples/basic/src/lib.rs @@ -318,7 +318,7 @@ const MILLICENTS: u32 = 1_000_000_000; // - assigns a dispatch class `operational` if the argument of the call is more than 1000. // // More information can be read at: -// - https://docs.substrate.io/v3/runtime/weights-and-fees +// - https://docs.substrate.io/main-docs/build/tx-weights-fees/ // // Manually configuring weight is an advanced operation and what you really need may well be // fulfilled by running the benchmarking toolchain. Refer to `benchmarking.rs` file. diff --git a/frame/remark/src/tests.rs b/frame/remark/src/tests.rs index 60a376c5afca5..eac006054c1d5 100644 --- a/frame/remark/src/tests.rs +++ b/frame/remark/src/tests.rs @@ -30,11 +30,14 @@ fn generates_event() { System::set_block_number(System::block_number() + 1); //otherwise event won't be registered. assert_ok!(Remark::::store(RawOrigin::Signed(caller.clone()).into(), data.clone(),)); let events = System::events(); + // this one we create as we expect it let system_event: ::Event = Event::Stored { content_hash: sp_io::hashing::blake2_256(&data).into(), sender: caller, } .into(); + // this one we actually go into the system pallet and get the last event + // because we know its there from block +1 let frame_system::EventRecord { event, .. } = &events[events.len() - 1]; assert_eq!(event, &system_event); }); diff --git a/frame/sudo/README.md b/frame/sudo/README.md index e8f688091e326..7342832d2d7a7 100644 --- a/frame/sudo/README.md +++ b/frame/sudo/README.md @@ -72,6 +72,6 @@ You need to set an initial superuser account as the sudo `key`. [`Call`]: ./enum.Call.html [`Config`]: ./trait.Config.html -[`Origin`]: https://docs.substrate.io/v3/runtime/origins +[`Origin`]: https://docs.substrate.io/main-docs/build/origins/ License: Apache-2.0 diff --git a/frame/sudo/src/lib.rs b/frame/sudo/src/lib.rs index d9e72b37f2970..a47b5a79bd017 100644 --- a/frame/sudo/src/lib.rs +++ b/frame/sudo/src/lib.rs @@ -88,7 +88,7 @@ //! //! * [Democracy](../pallet_democracy/index.html) //! -//! [`Origin`]: https://docs.substrate.io/v3/runtime/origins +//! [`Origin`]: https://docs.substrate.io/main-docs/build/origins/ #![cfg_attr(not(feature = "std"), no_std)] diff --git a/frame/support/procedural/src/pallet/expand/error.rs b/frame/support/procedural/src/pallet/expand/error.rs index 124e8b312ce39..5a8487b09de5c 100644 --- a/frame/support/procedural/src/pallet/expand/error.rs +++ b/frame/support/procedural/src/pallet/expand/error.rs @@ -111,7 +111,7 @@ pub fn expand_error(def: &mut Def) -> proc_macro2::TokenStream { if get_doc_literals(&error_item.attrs).is_empty() { error_item.attrs.push(syn::parse_quote!( #[doc = r" - Custom [dispatch errors](https://docs.substrate.io/v3/runtime/events-and-errors) + Custom [dispatch errors](https://docs.substrate.io/main-docs/build/events-errors/) of this pallet. "] )); diff --git a/frame/support/procedural/src/pallet/expand/event.rs b/frame/support/procedural/src/pallet/expand/event.rs index acd60ab959c61..791f930302207 100644 --- a/frame/support/procedural/src/pallet/expand/event.rs +++ b/frame/support/procedural/src/pallet/expand/event.rs @@ -98,7 +98,7 @@ pub fn expand_event(def: &mut Def) -> proc_macro2::TokenStream { if get_doc_literals(&event_item.attrs).is_empty() { event_item.attrs.push(syn::parse_quote!( #[doc = r" - The [event](https://docs.substrate.io/v3/runtime/events-and-errors) emitted + The [event](https://docs.substrate.io/main-docs/build/events-errors/) emitted by this pallet. "] )); diff --git a/frame/support/procedural/src/pallet/expand/mod.rs b/frame/support/procedural/src/pallet/expand/mod.rs index 83bef7a97af1f..c33d2386700b2 100644 --- a/frame/support/procedural/src/pallet/expand/mod.rs +++ b/frame/support/procedural/src/pallet/expand/mod.rs @@ -74,7 +74,7 @@ pub fn expand(mut def: Def) -> proc_macro2::TokenStream { def.item.attrs.push(syn::parse_quote!( #[doc = r" The module that hosts all the - [FRAME](https://docs.substrate.io/v3/runtime/frame) + [FRAME](https://docs.substrate.io/main-docs/build/events-errors/) types needed to add this pallet to a runtime. "] diff --git a/frame/support/procedural/src/pallet/expand/pallet_struct.rs b/frame/support/procedural/src/pallet/expand/pallet_struct.rs index 52586a70a521a..a4a8acc10f799 100644 --- a/frame/support/procedural/src/pallet/expand/pallet_struct.rs +++ b/frame/support/procedural/src/pallet/expand/pallet_struct.rs @@ -62,7 +62,7 @@ pub fn expand_pallet_struct(def: &mut Def) -> proc_macro2::TokenStream { if get_doc_literals(&pallet_item.attrs).is_empty() { pallet_item.attrs.push(syn::parse_quote!( #[doc = r" - The [pallet](https://docs.substrate.io/v3/runtime/frame#pallets) implementing + The [pallet](https://docs.substrate.io/reference/frame-pallets/#pallets) implementing the on-chain logic. "] )); diff --git a/frame/support/src/dispatch.rs b/frame/support/src/dispatch.rs index ae4230efc63f8..2b05478e0b02a 100644 --- a/frame/support/src/dispatch.rs +++ b/frame/support/src/dispatch.rs @@ -306,7 +306,7 @@ impl Parameter for T where T: Codec + EncodeLike + Clone + Eq + fmt::Debug + /// /// The following are reserved function signatures: /// -/// * `deposit_event`: Helper function for depositing an [event](https://docs.substrate.io/v3/runtime/events-and-errors). +/// * `deposit_event`: Helper function for depositing an [event](https://docs.substrate.io/main-docs/build/events-errors/). /// The default behavior is to call `deposit_event` from the [System /// module](../frame_system/index.html). However, you can write your own implementation for events /// in your runtime. To use the default behavior, add `fn deposit_event() = default;` to your diff --git a/frame/support/src/traits/misc.rs b/frame/support/src/traits/misc.rs index ccbb47909d5f4..ddb7f6f41b378 100644 --- a/frame/support/src/traits/misc.rs +++ b/frame/support/src/traits/misc.rs @@ -763,7 +763,7 @@ impl MaxEncodedLen for WrapperOpaque { fn max_encoded_len() -> usize { let t_max_len = T::max_encoded_len(); - // See scale encoding https://docs.substrate.io/v3/advanced/scale-codec + // See scale encoding: https://docs.substrate.io/reference/scale-codec/ if t_max_len < 64 { t_max_len + 1 } else if t_max_len < 2usize.pow(14) { diff --git a/primitives/runtime/src/bounded/bounded_vec.rs b/primitives/runtime/src/bounded/bounded_vec.rs index 10d9fc608c273..d5f3f2da0d615 100644 --- a/primitives/runtime/src/bounded/bounded_vec.rs +++ b/primitives/runtime/src/bounded/bounded_vec.rs @@ -847,7 +847,7 @@ where fn max_encoded_len() -> usize { // BoundedVec encodes like Vec which encodes like [T], which is a compact u32 // plus each item in the slice: - // https://docs.substrate.io/v3/advanced/scale-codec + // See: https://docs.substrate.io/reference/scale-codec/ codec::Compact(S::get()) .encoded_size() .saturating_add(Self::bound().saturating_mul(T::max_encoded_len())) diff --git a/primitives/runtime/src/bounded/weak_bounded_vec.rs b/primitives/runtime/src/bounded/weak_bounded_vec.rs index ed9f4bba62b55..a447e7285f906 100644 --- a/primitives/runtime/src/bounded/weak_bounded_vec.rs +++ b/primitives/runtime/src/bounded/weak_bounded_vec.rs @@ -443,7 +443,7 @@ where fn max_encoded_len() -> usize { // WeakBoundedVec encodes like Vec which encodes like [T], which is a compact u32 // plus each item in the slice: - // https://docs.substrate.io/v3/advanced/scale-codec + // See: https://docs.substrate.io/reference/scale-codec/ codec::Compact(S::get()) .encoded_size() .saturating_add(Self::bound().saturating_mul(T::max_encoded_len())) From 9e0861a854f9c1ff688c759eb4d05747842c0ad4 Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Wed, 3 Aug 2022 12:35:47 +0100 Subject: [PATCH 456/484] nit improvements to pallet template (#11968) --- bin/node-template/pallets/template/src/lib.rs | 8 ++++---- bin/node-template/pallets/template/src/mock.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bin/node-template/pallets/template/src/lib.rs b/bin/node-template/pallets/template/src/lib.rs index 2c337146859ef..f1519efe062bd 100644 --- a/bin/node-template/pallets/template/src/lib.rs +++ b/bin/node-template/pallets/template/src/lib.rs @@ -19,6 +19,10 @@ pub mod pallet { use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + /// Configure the pallet by specifying the parameters and types on which it depends. #[pallet::config] pub trait Config: frame_system::Config { @@ -26,10 +30,6 @@ pub mod pallet { type Event: From> + IsType<::Event>; } - #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] - pub struct Pallet(_); - // The pallet's runtime storage items. // https://docs.substrate.io/main-docs/build/runtime-storage/ #[pallet::storage] diff --git a/bin/node-template/pallets/template/src/mock.rs b/bin/node-template/pallets/template/src/mock.rs index 8721fe6c78851..e03f37b2eea69 100644 --- a/bin/node-template/pallets/template/src/mock.rs +++ b/bin/node-template/pallets/template/src/mock.rs @@ -17,8 +17,8 @@ frame_support::construct_runtime!( NodeBlock = Block, UncheckedExtrinsic = UncheckedExtrinsic, { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - TemplateModule: pallet_template::{Pallet, Call, Storage, Event}, + System: frame_system, + TemplateModule: pallet_template, } ); From 06863cb115d6d7ba22fb6422839d3f8bde84d8e9 Mon Sep 17 00:00:00 2001 From: lucasvanmol Date: Thu, 4 Aug 2022 00:16:24 -0700 Subject: [PATCH 457/484] Update node-template's docker-compose.yml (#11802) * Update docker-compose.yml * Use production image Co-authored-by: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Co-authored-by: Alexander Samusev <41779041+alvicsam@users.noreply.github.com> Co-authored-by: parity-processbot <> --- bin/node-template/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/node-template/docker-compose.yml b/bin/node-template/docker-compose.yml index cfc4437bbae41..bc1922f47d963 100644 --- a/bin/node-template/docker-compose.yml +++ b/bin/node-template/docker-compose.yml @@ -3,7 +3,7 @@ version: "3.2" services: dev: container_name: node-template - image: paritytech/ci-linux:974ba3ac-20201006 + image: paritytech/ci-linux:production working_dir: /var/www/node-template ports: - "9944:9944" From 5caa5f496eae7592b024b84f50031d6518f9083d Mon Sep 17 00:00:00 2001 From: ZhiYong Date: Thu, 4 Aug 2022 15:47:52 +0800 Subject: [PATCH 458/484] Beefy: use VersionedFinalityProof instead of SignedCommitment (#11962) * beefy: use VersionedFinalityProof instead of SignedCommitment. * Change the exposed RPC API to support versioned proofs. Co-authored-by: Adrian Catangiu --- client/beefy/rpc/src/lib.rs | 51 ++++++++++---------- client/beefy/rpc/src/notification.rs | 12 ++--- client/beefy/src/import.rs | 26 +++++------ client/beefy/src/justification.rs | 53 ++++++++++++--------- client/beefy/src/lib.rs | 14 +++--- client/beefy/src/notification.rs | 21 +++++---- client/beefy/src/tests.rs | 50 +++++++++++--------- client/beefy/src/worker.rs | 70 +++++++++++++++------------- 8 files changed, 157 insertions(+), 140 deletions(-) diff --git a/client/beefy/rpc/src/lib.rs b/client/beefy/rpc/src/lib.rs index 91ff59324bd95..3be182ceb8f39 100644 --- a/client/beefy/rpc/src/lib.rs +++ b/client/beefy/rpc/src/lib.rs @@ -35,7 +35,7 @@ use jsonrpsee::{ }; use log::warn; -use beefy_gadget::notification::{BeefyBestBlockStream, BeefySignedCommitmentStream}; +use beefy_gadget::notification::{BeefyBestBlockStream, BeefyVersionedFinalityProofStream}; mod notification; @@ -101,7 +101,7 @@ pub trait BeefyApi { /// Implements the BeefyApi RPC trait for interacting with BEEFY. pub struct Beefy { - signed_commitment_stream: BeefySignedCommitmentStream, + finality_proof_stream: BeefyVersionedFinalityProofStream, beefy_best_block: Arc>>, executor: SubscriptionTaskExecutor, } @@ -112,7 +112,7 @@ where { /// Creates a new Beefy Rpc handler instance. pub fn new( - signed_commitment_stream: BeefySignedCommitmentStream, + finality_proof_stream: BeefyVersionedFinalityProofStream, best_block_stream: BeefyBestBlockStream, executor: SubscriptionTaskExecutor, ) -> Result { @@ -126,20 +126,21 @@ where }); executor.spawn("substrate-rpc-subscription", Some("rpc"), future.map(drop).boxed()); - Ok(Self { signed_commitment_stream, beefy_best_block, executor }) + Ok(Self { finality_proof_stream, beefy_best_block, executor }) } } #[async_trait] -impl BeefyApiServer for Beefy +impl BeefyApiServer + for Beefy where Block: BlockT, { fn subscribe_justifications(&self, mut sink: SubscriptionSink) -> SubscriptionResult { let stream = self - .signed_commitment_stream + .finality_proof_stream .subscribe() - .map(|sc| notification::EncodedSignedCommitment::new::(sc)); + .map(|vfp| notification::EncodedVersionedFinalityProof::new::(vfp)); let fut = async move { sink.pipe_from_stream(stream).await; @@ -164,31 +165,31 @@ mod tests { use super::*; use beefy_gadget::{ - justification::BeefySignedCommitment, - notification::{BeefyBestBlockStream, BeefySignedCommitmentSender}, + justification::BeefyVersionedFinalityProof, + notification::{BeefyBestBlockStream, BeefyVersionedFinalityProofSender}, }; - use beefy_primitives::{known_payload_ids, Payload}; + use beefy_primitives::{known_payload_ids, Payload, SignedCommitment}; use codec::{Decode, Encode}; use jsonrpsee::{types::EmptyParams, RpcModule}; use sp_runtime::traits::{BlakeTwo256, Hash}; use substrate_test_runtime_client::runtime::Block; - fn setup_io_handler() -> (RpcModule>, BeefySignedCommitmentSender) { + fn setup_io_handler() -> (RpcModule>, BeefyVersionedFinalityProofSender) { let (_, stream) = BeefyBestBlockStream::::channel(); setup_io_handler_with_best_block_stream(stream) } fn setup_io_handler_with_best_block_stream( best_block_stream: BeefyBestBlockStream, - ) -> (RpcModule>, BeefySignedCommitmentSender) { - let (commitment_sender, commitment_stream) = - BeefySignedCommitmentStream::::channel(); + ) -> (RpcModule>, BeefyVersionedFinalityProofSender) { + let (finality_proof_sender, finality_proof_stream) = + BeefyVersionedFinalityProofStream::::channel(); let handler = - Beefy::new(commitment_stream, best_block_stream, sc_rpc::testing::test_executor()) + Beefy::new(finality_proof_stream, best_block_stream, sc_rpc::testing::test_executor()) .expect("Setting up the BEEFY RPC handler works"); - (handler.into_rpc(), commitment_sender) + (handler.into_rpc(), finality_proof_sender) } #[tokio::test] @@ -262,21 +263,21 @@ mod tests { assert_eq!(response.result, expected); } - fn create_commitment() -> BeefySignedCommitment { + fn create_finality_proof() -> BeefyVersionedFinalityProof { let payload = Payload::new(known_payload_ids::MMR_ROOT_ID, "Hello World!".encode()); - BeefySignedCommitment:: { + BeefyVersionedFinalityProof::::V1(SignedCommitment { commitment: beefy_primitives::Commitment { payload, block_number: 5, validator_set_id: 0, }, signatures: vec![], - } + }) } #[tokio::test] async fn subscribe_and_listen_to_one_justification() { - let (rpc, commitment_sender) = setup_io_handler(); + let (rpc, finality_proof_sender) = setup_io_handler(); // Subscribe let mut sub = rpc @@ -284,16 +285,16 @@ mod tests { .await .unwrap(); - // Notify with commitment - let commitment = create_commitment(); - let r: Result<(), ()> = commitment_sender.notify(|| Ok(commitment.clone())); + // Notify with finality_proof + let finality_proof = create_finality_proof(); + let r: Result<(), ()> = finality_proof_sender.notify(|| Ok(finality_proof.clone())); r.unwrap(); // Inspect what we received let (bytes, recv_sub_id) = sub.next::().await.unwrap().unwrap(); - let recv_commitment: BeefySignedCommitment = + let recv_finality_proof: BeefyVersionedFinalityProof = Decode::decode(&mut &bytes[..]).unwrap(); assert_eq!(&recv_sub_id, sub.subscription_id()); - assert_eq!(recv_commitment, commitment); + assert_eq!(recv_finality_proof, finality_proof); } } diff --git a/client/beefy/rpc/src/notification.rs b/client/beefy/rpc/src/notification.rs index cdda667782dd5..a815425644d52 100644 --- a/client/beefy/rpc/src/notification.rs +++ b/client/beefy/rpc/src/notification.rs @@ -21,19 +21,19 @@ use serde::{Deserialize, Serialize}; use sp_runtime::traits::Block as BlockT; -/// An encoded signed commitment proving that the given header has been finalized. +/// An encoded finality proof proving that the given header has been finalized. /// The given bytes should be the SCALE-encoded representation of a -/// `beefy_primitives::SignedCommitment`. +/// `beefy_primitives::VersionedFinalityProof`. #[derive(Clone, Serialize, Deserialize)] -pub struct EncodedSignedCommitment(sp_core::Bytes); +pub struct EncodedVersionedFinalityProof(sp_core::Bytes); -impl EncodedSignedCommitment { +impl EncodedVersionedFinalityProof { pub fn new( - signed_commitment: beefy_gadget::justification::BeefySignedCommitment, + finality_proof: beefy_gadget::justification::BeefyVersionedFinalityProof, ) -> Self where Block: BlockT, { - EncodedSignedCommitment(signed_commitment.encode().into()) + EncodedVersionedFinalityProof(finality_proof.encode().into()) } } diff --git a/client/beefy/src/import.rs b/client/beefy/src/import.rs index 7caeb49db5e50..129484199de89 100644 --- a/client/beefy/src/import.rs +++ b/client/beefy/src/import.rs @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use beefy_primitives::{crypto::Signature, BeefyApi, VersionedFinalityProof, BEEFY_ENGINE_ID}; +use beefy_primitives::{BeefyApi, BEEFY_ENGINE_ID}; use codec::Encode; use log::error; use std::{collections::HashMap, sync::Arc}; @@ -34,7 +34,8 @@ use sc_client_api::backend::Backend; use sc_consensus::{BlockCheckParams, BlockImport, BlockImportParams, ImportResult}; use crate::{ - justification::decode_and_verify_commitment, notification::BeefySignedCommitmentSender, + justification::{decode_and_verify_finality_proof, BeefyVersionedFinalityProof}, + notification::BeefyVersionedFinalityProofSender, }; /// A block-import handler for BEEFY. @@ -47,7 +48,7 @@ pub struct BeefyBlockImport { backend: Arc, runtime: Arc, inner: I, - justification_sender: BeefySignedCommitmentSender, + justification_sender: BeefyVersionedFinalityProofSender, } impl Clone for BeefyBlockImport { @@ -67,7 +68,7 @@ impl BeefyBlockImport { backend: Arc, runtime: Arc, inner: I, - justification_sender: BeefySignedCommitmentSender, + justification_sender: BeefyVersionedFinalityProofSender, ) -> BeefyBlockImport { BeefyBlockImport { backend, runtime, inner, justification_sender } } @@ -85,7 +86,7 @@ where encoded: &EncodedJustification, number: NumberFor, hash: ::Hash, - ) -> Result, Signature>, ConsensusError> { + ) -> Result, ConsensusError> { let block_id = BlockId::hash(hash); let validator_set = self .runtime @@ -94,7 +95,7 @@ where .map_err(|e| ConsensusError::ClientImport(e.to_string()))? .ok_or_else(|| ConsensusError::ClientImport("Unknown validator set".to_string()))?; - decode_and_verify_commitment::(&encoded[..], number, &validator_set) + decode_and_verify_finality_proof::(&encoded[..], number, &validator_set) } /// Import BEEFY justification: Send it to worker for processing and also append it to backend. @@ -105,7 +106,7 @@ where fn import_beefy_justification_unchecked( &self, number: NumberFor, - justification: VersionedFinalityProof, Signature>, + justification: BeefyVersionedFinalityProof, ) { // Append the justification to the block in the backend. if let Err(e) = self.backend.append_justification( @@ -115,14 +116,9 @@ where error!(target: "beefy", "🥩 Error {:?} on appending justification: {:?}", e, justification); } // Send the justification to the BEEFY voter for processing. - match justification { - // TODO #11838: Should not unpack, these channels should also use - // `VersionedFinalityProof`. - VersionedFinalityProof::V1(signed_commitment) => self - .justification_sender - .notify(|| Ok::<_, ()>(signed_commitment)) - .expect("forwards closure result; the closure always returns Ok; qed."), - }; + self.justification_sender + .notify(|| Ok::<_, ()>(justification)) + .expect("forwards closure result; the closure always returns Ok; qed."); } } diff --git a/client/beefy/src/justification.rs b/client/beefy/src/justification.rs index 2a5191daec4b5..d9be18593dac7 100644 --- a/client/beefy/src/justification.rs +++ b/client/beefy/src/justification.rs @@ -25,17 +25,17 @@ use codec::{Decode, Encode}; use sp_consensus::Error as ConsensusError; use sp_runtime::traits::{Block as BlockT, NumberFor}; -/// A commitment with matching BEEFY authorities' signatures. -pub type BeefySignedCommitment = - beefy_primitives::SignedCommitment, beefy_primitives::crypto::Signature>; +/// A finality proof with matching BEEFY authorities' signatures. +pub type BeefyVersionedFinalityProof = + beefy_primitives::VersionedFinalityProof, Signature>; -/// Decode and verify a Beefy SignedCommitment. -pub(crate) fn decode_and_verify_commitment( +/// Decode and verify a Beefy FinalityProof. +pub(crate) fn decode_and_verify_finality_proof( encoded: &[u8], target_number: NumberFor, validator_set: &ValidatorSet, -) -> Result, Signature>, ConsensusError> { - let proof = , Signature>>::decode(&mut &*encoded) +) -> Result, ConsensusError> { + let proof = >::decode(&mut &*encoded) .map_err(|_| ConsensusError::InvalidJustification)?; verify_with_validator_set::(target_number, validator_set, &proof).map(|_| proof) } @@ -44,7 +44,7 @@ pub(crate) fn decode_and_verify_commitment( fn verify_with_validator_set( target_number: NumberFor, validator_set: &ValidatorSet, - proof: &VersionedFinalityProof, Signature>, + proof: &BeefyVersionedFinalityProof, ) -> Result<(), ConsensusError> { match proof { VersionedFinalityProof::V1(signed_commitment) => { @@ -80,17 +80,19 @@ fn verify_with_validator_set( #[cfg(test)] pub(crate) mod tests { - use beefy_primitives::{known_payload_ids, Commitment, Payload, SignedCommitment}; + use beefy_primitives::{ + known_payload_ids, Commitment, Payload, SignedCommitment, VersionedFinalityProof, + }; use substrate_test_runtime_client::runtime::Block; use super::*; use crate::{keystore::tests::Keyring, tests::make_beefy_ids}; - pub(crate) fn new_signed_commitment( + pub(crate) fn new_finality_proof( block_num: NumberFor, validator_set: &ValidatorSet, keys: &[Keyring], - ) -> BeefySignedCommitment { + ) -> BeefyVersionedFinalityProof { let commitment = Commitment { payload: Payload::new(known_payload_ids::MMR_ROOT_ID, vec![]), block_number: block_num, @@ -98,7 +100,7 @@ pub(crate) mod tests { }; let message = commitment.encode(); let signatures = keys.iter().map(|key| Some(key.sign(&message))).collect(); - SignedCommitment { commitment, signatures } + VersionedFinalityProof::V1(SignedCommitment { commitment, signatures }) } #[test] @@ -108,7 +110,7 @@ pub(crate) mod tests { // build valid justification let block_num = 42; - let proof = new_signed_commitment(block_num, &validator_set, keys); + let proof = new_finality_proof(block_num, &validator_set, keys); let good_proof = proof.clone().into(); // should verify successfully @@ -132,7 +134,10 @@ pub(crate) mod tests { // wrong signatures length -> should fail verification let mut bad_proof = proof.clone(); // change length of signatures - bad_proof.signatures.pop().flatten().unwrap(); + let bad_signed_commitment = match bad_proof { + VersionedFinalityProof::V1(ref mut sc) => sc, + }; + bad_signed_commitment.signatures.pop().flatten().unwrap(); match verify_with_validator_set::(block_num + 1, &validator_set, &bad_proof.into()) { Err(ConsensusError::InvalidJustification) => (), _ => assert!(false, "Expected Err(ConsensusError::InvalidJustification)"), @@ -140,8 +145,11 @@ pub(crate) mod tests { // not enough signatures -> should fail verification let mut bad_proof = proof.clone(); + let bad_signed_commitment = match bad_proof { + VersionedFinalityProof::V1(ref mut sc) => sc, + }; // remove a signature (but same length) - *bad_proof.signatures.first_mut().unwrap() = None; + *bad_signed_commitment.signatures.first_mut().unwrap() = None; match verify_with_validator_set::(block_num + 1, &validator_set, &bad_proof.into()) { Err(ConsensusError::InvalidJustification) => (), _ => assert!(false, "Expected Err(ConsensusError::InvalidJustification)"), @@ -149,9 +157,12 @@ pub(crate) mod tests { // not enough _correct_ signatures -> should fail verification let mut bad_proof = proof.clone(); + let bad_signed_commitment = match bad_proof { + VersionedFinalityProof::V1(ref mut sc) => sc, + }; // change a signature to a different key - *bad_proof.signatures.first_mut().unwrap() = - Some(Keyring::Dave.sign(&proof.commitment.encode())); + *bad_signed_commitment.signatures.first_mut().unwrap() = + Some(Keyring::Dave.sign(&bad_signed_commitment.commitment.encode())); match verify_with_validator_set::(block_num + 1, &validator_set, &bad_proof.into()) { Err(ConsensusError::InvalidJustification) => (), _ => assert!(false, "Expected Err(ConsensusError::InvalidJustification)"), @@ -159,19 +170,19 @@ pub(crate) mod tests { } #[test] - fn should_decode_and_verify_commitment() { + fn should_decode_and_verify_finality_proof() { let keys = &[Keyring::Alice, Keyring::Bob]; let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); let block_num = 1; // build valid justification - let proof = new_signed_commitment(block_num, &validator_set, keys); - let versioned_proof: VersionedFinalityProof, Signature> = proof.into(); + let proof = new_finality_proof(block_num, &validator_set, keys); + let versioned_proof: BeefyVersionedFinalityProof = proof.into(); let encoded = versioned_proof.encode(); // should successfully decode and verify let verified = - decode_and_verify_commitment::(&encoded, block_num, &validator_set).unwrap(); + decode_and_verify_finality_proof::(&encoded, block_num, &validator_set).unwrap(); assert_eq!(verified, versioned_proof); } } diff --git a/client/beefy/src/lib.rs b/client/beefy/src/lib.rs index 81c72dec8cd08..bdf44e056786c 100644 --- a/client/beefy/src/lib.rs +++ b/client/beefy/src/lib.rs @@ -46,8 +46,8 @@ mod tests; use crate::{ import::BeefyBlockImport, notification::{ - BeefyBestBlockSender, BeefyBestBlockStream, BeefySignedCommitmentSender, - BeefySignedCommitmentStream, + BeefyBestBlockSender, BeefyBestBlockStream, BeefyVersionedFinalityProofSender, + BeefyVersionedFinalityProofStream, }, }; @@ -121,11 +121,11 @@ where pub struct BeefyVoterLinks { // BlockImport -> Voter links /// Stream of BEEFY signed commitments from block import to voter. - pub from_block_import_justif_stream: BeefySignedCommitmentStream, + pub from_block_import_justif_stream: BeefyVersionedFinalityProofStream, // Voter -> RPC links /// Sends BEEFY signed commitments from voter to RPC. - pub to_rpc_justif_sender: BeefySignedCommitmentSender, + pub to_rpc_justif_sender: BeefyVersionedFinalityProofSender, /// Sends BEEFY best block hashes from voter to RPC. pub to_rpc_best_block_sender: BeefyBestBlockSender, } @@ -134,7 +134,7 @@ pub struct BeefyVoterLinks { #[derive(Clone)] pub struct BeefyRPCLinks { /// Stream of signed commitments coming from the voter. - pub from_voter_justif_stream: BeefySignedCommitmentStream, + pub from_voter_justif_stream: BeefyVersionedFinalityProofStream, /// Stream of BEEFY best block hashes coming from the voter. pub from_voter_best_beefy_stream: BeefyBestBlockStream, } @@ -156,13 +156,13 @@ where { // Voter -> RPC links let (to_rpc_justif_sender, from_voter_justif_stream) = - notification::BeefySignedCommitmentStream::::channel(); + notification::BeefyVersionedFinalityProofStream::::channel(); let (to_rpc_best_block_sender, from_voter_best_beefy_stream) = notification::BeefyBestBlockStream::::channel(); // BlockImport -> Voter links let (to_voter_justif_sender, from_block_import_justif_stream) = - notification::BeefySignedCommitmentStream::::channel(); + notification::BeefyVersionedFinalityProofStream::::channel(); // BlockImport let import = diff --git a/client/beefy/src/notification.rs b/client/beefy/src/notification.rs index 9479891714234..c673115e487f3 100644 --- a/client/beefy/src/notification.rs +++ b/client/beefy/src/notification.rs @@ -19,7 +19,7 @@ use sc_utils::notification::{NotificationSender, NotificationStream, TracingKeyStr}; use sp_runtime::traits::Block as BlockT; -use crate::justification::BeefySignedCommitment; +use crate::justification::BeefyVersionedFinalityProof; /// The sending half of the notifications channel(s) used to send /// notifications about best BEEFY block from the gadget side. @@ -31,13 +31,14 @@ pub type BeefyBestBlockStream = NotificationStream<::Hash, BeefyBestBlockTracingKey>; /// The sending half of the notifications channel(s) used to send notifications -/// about signed commitments generated at the end of a BEEFY round. -pub type BeefySignedCommitmentSender = NotificationSender>; +/// about versioned finality proof generated at the end of a BEEFY round. +pub type BeefyVersionedFinalityProofSender = + NotificationSender>; /// The receiving half of a notifications channel used to receive notifications -/// about signed commitments generated at the end of a BEEFY round. -pub type BeefySignedCommitmentStream = - NotificationStream, BeefySignedCommitmentTracingKey>; +/// about versioned finality proof generated at the end of a BEEFY round. +pub type BeefyVersionedFinalityProofStream = + NotificationStream, BeefyVersionedFinalityProofTracingKey>; /// Provides tracing key for BEEFY best block stream. #[derive(Clone)] @@ -46,9 +47,9 @@ impl TracingKeyStr for BeefyBestBlockTracingKey { const TRACING_KEY: &'static str = "mpsc_beefy_best_block_notification_stream"; } -/// Provides tracing key for BEEFY signed commitments stream. +/// Provides tracing key for BEEFY versioned finality proof stream. #[derive(Clone)] -pub struct BeefySignedCommitmentTracingKey; -impl TracingKeyStr for BeefySignedCommitmentTracingKey { - const TRACING_KEY: &'static str = "mpsc_beefy_signed_commitments_notification_stream"; +pub struct BeefyVersionedFinalityProofTracingKey; +impl TracingKeyStr for BeefyVersionedFinalityProofTracingKey { + const TRACING_KEY: &'static str = "mpsc_beefy_versioned_finality_proof_notification_stream"; } diff --git a/client/beefy/src/tests.rs b/client/beefy/src/tests.rs index 9c8f443dd1f7e..134339009302b 100644 --- a/client/beefy/src/tests.rs +++ b/client/beefy/src/tests.rs @@ -397,17 +397,18 @@ fn run_for(duration: Duration, net: &Arc>, runtime: &mut Run pub(crate) fn get_beefy_streams( net: &mut BeefyTestNet, peers: &[BeefyKeyring], -) -> (Vec>, Vec>>) { +) -> (Vec>, Vec>>) +{ let mut best_block_streams = Vec::new(); - let mut signed_commitment_streams = Vec::new(); + let mut versioned_finality_proof_streams = Vec::new(); for peer_id in 0..peers.len() { let beefy_rpc_links = net.peer(peer_id).data.beefy_rpc_links.lock().clone().unwrap(); let BeefyRPCLinks { from_voter_justif_stream, from_voter_best_beefy_stream } = beefy_rpc_links; best_block_streams.push(from_voter_best_beefy_stream.subscribe()); - signed_commitment_streams.push(from_voter_justif_stream.subscribe()); + versioned_finality_proof_streams.push(from_voter_justif_stream.subscribe()); } - (best_block_streams, signed_commitment_streams) + (best_block_streams, versioned_finality_proof_streams) } fn wait_for_best_beefy_blocks( @@ -437,7 +438,7 @@ fn wait_for_best_beefy_blocks( } fn wait_for_beefy_signed_commitments( - streams: Vec>>, + streams: Vec>>, net: &Arc>, runtime: &mut Runtime, expected_commitment_block_nums: &[u64], @@ -446,9 +447,12 @@ fn wait_for_beefy_signed_commitments( let len = expected_commitment_block_nums.len(); streams.into_iter().for_each(|stream| { let mut expected = expected_commitment_block_nums.iter(); - wait_for.push(Box::pin(stream.take(len).for_each(move |signed_commitment| { + wait_for.push(Box::pin(stream.take(len).for_each(move |versioned_finality_proof| { let expected = expected.next(); async move { + let signed_commitment = match versioned_finality_proof { + beefy_primitives::VersionedFinalityProof::V1(sc) => sc, + }; let commitment_block_num = signed_commitment.commitment.block_number; assert_eq!(expected, Some(commitment_block_num).as_ref()); // TODO: also verify commitment payload, validator set id, and signatures. @@ -486,7 +490,7 @@ fn finalize_block_and_wait_for_beefy( finalize_targets: &[u64], expected_beefy: &[u64], ) { - let (best_blocks, signed_commitments) = get_beefy_streams(&mut net.lock(), peers); + let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers); for block in finalize_targets { let finalize = BlockId::number(*block); @@ -499,11 +503,11 @@ fn finalize_block_and_wait_for_beefy( // run for quarter second then verify no new best beefy block available let timeout = Some(Duration::from_millis(250)); streams_empty_after_timeout(best_blocks, &net, runtime, timeout); - streams_empty_after_timeout(signed_commitments, &net, runtime, None); + streams_empty_after_timeout(versioned_finality_proof, &net, runtime, None); } else { // run until expected beefy blocks are received wait_for_best_beefy_blocks(best_blocks, &net, runtime, expected_beefy); - wait_for_beefy_signed_commitments(signed_commitments, &net, runtime, expected_beefy); + wait_for_beefy_signed_commitments(versioned_finality_proof, &net, runtime, expected_beefy); } } @@ -574,19 +578,19 @@ fn lagging_validators() { // Alice finalizes #25, Bob lags behind let finalize = BlockId::number(25); - let (best_blocks, signed_commitments) = get_beefy_streams(&mut net.lock(), peers); + let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers); net.lock().peer(0).client().as_client().finalize_block(finalize, None).unwrap(); // verify nothing gets finalized by BEEFY let timeout = Some(Duration::from_millis(250)); streams_empty_after_timeout(best_blocks, &net, &mut runtime, timeout); - streams_empty_after_timeout(signed_commitments, &net, &mut runtime, None); + streams_empty_after_timeout(versioned_finality_proof, &net, &mut runtime, None); // Bob catches up and also finalizes #25 - let (best_blocks, signed_commitments) = get_beefy_streams(&mut net.lock(), peers); + let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers); net.lock().peer(1).client().as_client().finalize_block(finalize, None).unwrap(); // expected beefy finalizes block #17 from diff-power-of-two wait_for_best_beefy_blocks(best_blocks, &net, &mut runtime, &[23, 24, 25]); - wait_for_beefy_signed_commitments(signed_commitments, &net, &mut runtime, &[23, 24, 25]); + wait_for_beefy_signed_commitments(versioned_finality_proof, &net, &mut runtime, &[23, 24, 25]); // Both finalize #30 (mandatory session) and #32 -> BEEFY finalize #30 (mandatory), #31, #32 finalize_block_and_wait_for_beefy(&net, peers, &mut runtime, &[30, 32], &[30, 31, 32]); @@ -596,20 +600,20 @@ fn lagging_validators() { // validator set). // Alice finalizes session-boundary mandatory block #60, Bob lags behind - let (best_blocks, signed_commitments) = get_beefy_streams(&mut net.lock(), peers); + let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers); let finalize = BlockId::number(60); net.lock().peer(0).client().as_client().finalize_block(finalize, None).unwrap(); // verify nothing gets finalized by BEEFY let timeout = Some(Duration::from_millis(250)); streams_empty_after_timeout(best_blocks, &net, &mut runtime, timeout); - streams_empty_after_timeout(signed_commitments, &net, &mut runtime, None); + streams_empty_after_timeout(versioned_finality_proof, &net, &mut runtime, None); // Bob catches up and also finalizes #60 (and should have buffered Alice's vote on #60) - let (best_blocks, signed_commitments) = get_beefy_streams(&mut net.lock(), peers); + let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers); net.lock().peer(1).client().as_client().finalize_block(finalize, None).unwrap(); // verify beefy skips intermediary votes, and successfully finalizes mandatory block #40 wait_for_best_beefy_blocks(best_blocks, &net, &mut runtime, &[60]); - wait_for_beefy_signed_commitments(signed_commitments, &net, &mut runtime, &[60]); + wait_for_beefy_signed_commitments(versioned_finality_proof, &net, &mut runtime, &[60]); } #[test] @@ -647,7 +651,7 @@ fn correct_beefy_payload() { // with 3 good voters and 1 bad one, consensus should happen and best blocks produced. finalize_block_and_wait_for_beefy(&net, peers, &mut runtime, &[10], &[1, 9]); - let (best_blocks, signed_commitments) = + let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), &[BeefyKeyring::Alice]); // now 2 good validators and 1 bad one are voting @@ -673,10 +677,10 @@ fn correct_beefy_payload() { // verify consensus is _not_ reached let timeout = Some(Duration::from_millis(250)); streams_empty_after_timeout(best_blocks, &net, &mut runtime, timeout); - streams_empty_after_timeout(signed_commitments, &net, &mut runtime, None); + streams_empty_after_timeout(versioned_finality_proof, &net, &mut runtime, None); // 3rd good validator catches up and votes as well - let (best_blocks, signed_commitments) = + let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), &[BeefyKeyring::Alice]); net.lock() .peer(2) @@ -687,7 +691,7 @@ fn correct_beefy_payload() { // verify consensus is reached wait_for_best_beefy_blocks(best_blocks, &net, &mut runtime, &[11]); - wait_for_beefy_signed_commitments(signed_commitments, &net, &mut runtime, &[11]); + wait_for_beefy_signed_commitments(versioned_finality_proof, &net, &mut runtime, &[11]); } #[test] @@ -746,7 +750,7 @@ fn beefy_importing_blocks() { let block_num = 2; let keys = &[BeefyKeyring::Alice, BeefyKeyring::Bob]; let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); - let proof = crate::justification::tests::new_signed_commitment(block_num, &validator_set, keys); + let proof = crate::justification::tests::new_finality_proof(block_num, &validator_set, keys); let versioned_proof: VersionedFinalityProof, Signature> = proof.into(); let encoded = versioned_proof.encode(); let justif = Some(Justifications::from((BEEFY_ENGINE_ID, encoded))); @@ -781,7 +785,7 @@ fn beefy_importing_blocks() { let block_num = 3; let keys = &[BeefyKeyring::Alice]; let validator_set = ValidatorSet::new(make_beefy_ids(keys), 1).unwrap(); - let proof = crate::justification::tests::new_signed_commitment(block_num, &validator_set, keys); + let proof = crate::justification::tests::new_finality_proof(block_num, &validator_set, keys); let versioned_proof: VersionedFinalityProof, Signature> = proof.into(); let encoded = versioned_proof.encode(); let justif = Some(Justifications::from((BEEFY_ENGINE_ID, encoded))); diff --git a/client/beefy/src/worker.rs b/client/beefy/src/worker.rs index 3bff0822ebdb4..2c4985c0e6966 100644 --- a/client/beefy/src/worker.rs +++ b/client/beefy/src/worker.rs @@ -49,7 +49,7 @@ use beefy_primitives::{ use crate::{ error::Error, gossip::{topic, GossipValidator}, - justification::BeefySignedCommitment, + justification::BeefyVersionedFinalityProof, keystore::BeefyKeystore, metric_inc, metric_set, metrics::Metrics, @@ -212,7 +212,7 @@ pub(crate) struct BeefyWorker { /// Buffer holding votes for future processing. pending_votes: BTreeMap, Vec, AuthorityId, Signature>>>, /// Buffer holding justifications for future processing. - pending_justifications: BTreeMap, Vec>>, + pending_justifications: BTreeMap, Vec>>, /// Chooses which incoming votes to accept and which votes to generate. voting_oracle: VoterOracle, } @@ -381,9 +381,12 @@ where /// Expects `justification` to be valid. fn triage_incoming_justif( &mut self, - justification: BeefySignedCommitment, + justification: BeefyVersionedFinalityProof, ) -> Result<(), Error> { - let block_num = justification.commitment.block_number; + let signed_commitment = match justification { + VersionedFinalityProof::V1(ref sc) => sc, + }; + let block_num = signed_commitment.commitment.block_number; let best_grandpa = *self.best_grandpa_block_header.number(); match self.voting_oracle.triage_round(block_num, best_grandpa)? { RoundAction::Process => self.finalize(justification), @@ -417,39 +420,39 @@ where validator_set_id: rounds.validator_set_id(), }; - let signed_commitment = SignedCommitment { commitment, signatures }; + let finality_proof = + VersionedFinalityProof::V1(SignedCommitment { commitment, signatures }); metric_set!(self, beefy_round_concluded, block_num); - info!(target: "beefy", "🥩 Round #{} concluded, committed: {:?}.", round.1, signed_commitment); + info!(target: "beefy", "🥩 Round #{} concluded, finality_proof: {:?}.", round.1, finality_proof); if let Err(e) = self.backend.append_justification( BlockId::Number(block_num), - ( - BEEFY_ENGINE_ID, - VersionedFinalityProof::V1(signed_commitment.clone()).encode(), - ), + (BEEFY_ENGINE_ID, finality_proof.clone().encode()), ) { - debug!(target: "beefy", "🥩 Error {:?} on appending justification: {:?}", e, signed_commitment); + debug!(target: "beefy", "🥩 Error {:?} on appending justification: {:?}", e, finality_proof); } - // We created the `signed_commitment` and know to be valid. - self.finalize(signed_commitment); + // We created the `finality_proof` and know to be valid. + self.finalize(finality_proof); } } Ok(()) } - /// Provide BEEFY finality for block based on `signed_commitment`: + /// Provide BEEFY finality for block based on `finality_proof`: /// 1. Prune irrelevant past sessions from the oracle, /// 2. Set BEEFY best block, - /// 3. Send best block hash and `signed_commitment` to RPC worker. + /// 3. Send best block hash and `finality_proof` to RPC worker. /// - /// Expects `signed commitment` to be valid. - fn finalize(&mut self, signed_commitment: BeefySignedCommitment) { + /// Expects `finality proof` to be valid. + fn finalize(&mut self, finality_proof: BeefyVersionedFinalityProof) { // Prune any now "finalized" sessions from queue. self.voting_oracle.try_prune(); - + let signed_commitment = match finality_proof { + VersionedFinalityProof::V1(ref sc) => sc, + }; let block_num = signed_commitment.commitment.block_number; if Some(block_num) > self.best_beefy_block { // Set new best BEEFY block number. @@ -465,7 +468,7 @@ where self.links .to_rpc_justif_sender - .notify(|| Ok::<_, ()>(signed_commitment)) + .notify(|| Ok::<_, ()>(finality_proof)) .expect("forwards closure result; the closure always returns Ok; qed."); } else { debug!(target: "beefy", "🥩 Can't set best beefy to older: {}", block_num); @@ -832,7 +835,7 @@ pub(crate) mod tests { use super::*; use crate::{ keystore::tests::Keyring, - notification::{BeefyBestBlockStream, BeefySignedCommitmentStream}, + notification::{BeefyBestBlockStream, BeefyVersionedFinalityProofStream}, tests::{ create_beefy_keystore, get_beefy_streams, make_beefy_ids, two_validators::TestApi, BeefyPeer, BeefyTestNet, BEEFY_PROTOCOL_NAME, @@ -859,10 +862,11 @@ pub(crate) mod tests { let keystore = create_beefy_keystore(*key); let (to_rpc_justif_sender, from_voter_justif_stream) = - BeefySignedCommitmentStream::::channel(); + BeefyVersionedFinalityProofStream::::channel(); let (to_rpc_best_block_sender, from_voter_best_beefy_stream) = BeefyBestBlockStream::::channel(); - let (_, from_block_import_justif_stream) = BeefySignedCommitmentStream::::channel(); + let (_, from_block_import_justif_stream) = + BeefyVersionedFinalityProofStream::::channel(); let beefy_rpc_links = BeefyRPCLinks { from_voter_justif_stream, from_voter_best_beefy_stream }; @@ -1168,37 +1172,37 @@ pub(crate) mod tests { let mut net = BeefyTestNet::new(1, 0); let mut worker = create_beefy_worker(&net.peer(0), &keys[0], 1); - let (mut best_block_streams, mut signed_commitments) = get_beefy_streams(&mut net, keys); + let (mut best_block_streams, mut finality_proofs) = get_beefy_streams(&mut net, keys); let mut best_block_stream = best_block_streams.drain(..).next().unwrap(); - let mut signed_commitments = signed_commitments.drain(..).next().unwrap(); + let mut finality_proof = finality_proofs.drain(..).next().unwrap(); - let create_signed_commitment = |block_num: NumberFor| { + let create_finality_proof = |block_num: NumberFor| { let commitment = Commitment { payload: Payload::new(known_payload_ids::MMR_ROOT_ID, vec![]), block_number: block_num, validator_set_id: validator_set.id(), }; - SignedCommitment { commitment, signatures: vec![None] } + VersionedFinalityProof::V1(SignedCommitment { commitment, signatures: vec![None] }) }; - // no 'best beefy block' or signed commitments + // no 'best beefy block' or finality proofs assert_eq!(worker.best_beefy_block, None); block_on(poll_fn(move |cx| { assert_eq!(best_block_stream.poll_next_unpin(cx), Poll::Pending); - assert_eq!(signed_commitments.poll_next_unpin(cx), Poll::Pending); + assert_eq!(finality_proof.poll_next_unpin(cx), Poll::Pending); Poll::Ready(()) })); // unknown hash for block #1 - let (mut best_block_streams, mut signed_commitments) = get_beefy_streams(&mut net, keys); + let (mut best_block_streams, mut finality_proofs) = get_beefy_streams(&mut net, keys); let mut best_block_stream = best_block_streams.drain(..).next().unwrap(); - let mut signed_commitments = signed_commitments.drain(..).next().unwrap(); - let justif = create_signed_commitment(1); + let mut finality_proof = finality_proofs.drain(..).next().unwrap(); + let justif = create_finality_proof(1); worker.finalize(justif.clone()); assert_eq!(worker.best_beefy_block, Some(1)); block_on(poll_fn(move |cx| { assert_eq!(best_block_stream.poll_next_unpin(cx), Poll::Pending); - match signed_commitments.poll_next_unpin(cx) { + match finality_proof.poll_next_unpin(cx) { // expect justification Poll::Ready(Some(received)) => assert_eq!(received, justif), v => panic!("unexpected value: {:?}", v), @@ -1211,7 +1215,7 @@ pub(crate) mod tests { let mut best_block_stream = best_block_streams.drain(..).next().unwrap(); net.generate_blocks(2, 10, &validator_set, false); - let justif = create_signed_commitment(2); + let justif = create_finality_proof(2); worker.finalize(justif); assert_eq!(worker.best_beefy_block, Some(2)); block_on(poll_fn(move |cx| { From 72fc8032f108515b0acbec851c260e7ce541f35e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Thu, 4 Aug 2022 11:34:19 +0200 Subject: [PATCH 459/484] Timestamp: `set_timestamp` sets `DidUpdate` (#11960) * Timestamp: `set_timestamp` sets `DidUpdate` There exists the `set_timestamp` in the Timestamp pallet for setting the current timestamp. The problem is that it doesn't set `DidUpdate`. This results in `on_finalize` panicking. There is no real reason why the function doesn't also set `DidUpdate`. * Update frame/timestamp/src/lib.rs Co-authored-by: Oliver Tale-Yazdi * Fix Babe tests Co-authored-by: Oliver Tale-Yazdi --- frame/babe/src/mock.rs | 2 +- frame/babe/src/tests.rs | 2 +- frame/timestamp/src/lib.rs | 2 ++ frame/timestamp/src/tests.rs | 5 ++--- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/frame/babe/src/mock.rs b/frame/babe/src/mock.rs index 5677eb7e28e49..c2ba3c2be06d8 100644 --- a/frame/babe/src/mock.rs +++ b/frame/babe/src/mock.rs @@ -429,7 +429,7 @@ pub fn generate_equivocation_proof( System::reset_events(); System::initialize(¤t_block, &parent_hash, &pre_digest); System::set_block_number(current_block); - Timestamp::set_timestamp(current_block); + Timestamp::set_timestamp(*current_slot * Babe::slot_duration()); System::finalize() }; diff --git a/frame/babe/src/tests.rs b/frame/babe/src/tests.rs index 0859bb7a40849..ece0883387709 100644 --- a/frame/babe/src/tests.rs +++ b/frame/babe/src/tests.rs @@ -659,7 +659,7 @@ fn report_equivocation_invalid_equivocation_proof() { equivocation_proof.second_header = equivocation_proof.first_header.clone(); assert_invalid_equivocation(equivocation_proof); - // missing preruntime digest from one header + // missing pre-runtime digest from one header let mut equivocation_proof = generate_equivocation_proof( offending_validator_index as u32, &offending_authority_pair, diff --git a/frame/timestamp/src/lib.rs b/frame/timestamp/src/lib.rs index 81ed67913c2e6..6a7f849d1329a 100644 --- a/frame/timestamp/src/lib.rs +++ b/frame/timestamp/src/lib.rs @@ -282,6 +282,8 @@ impl Pallet { #[cfg(any(feature = "runtime-benchmarks", feature = "std"))] pub fn set_timestamp(now: T::Moment) { Now::::put(now); + DidUpdate::::put(true); + >::on_timestamp_set(now); } } diff --git a/frame/timestamp/src/tests.rs b/frame/timestamp/src/tests.rs index f52ba7849c951..ef9fd6e39d4b5 100644 --- a/frame/timestamp/src/tests.rs +++ b/frame/timestamp/src/tests.rs @@ -23,7 +23,7 @@ use frame_support::assert_ok; #[test] fn timestamp_works() { new_test_ext().execute_with(|| { - Timestamp::set_timestamp(42); + crate::Now::::put(46); assert_ok!(Timestamp::set(Origin::none(), 69)); assert_eq!(Timestamp::now(), 69); assert_eq!(Some(69), get_captured_moment()); @@ -36,7 +36,6 @@ fn double_timestamp_should_fail() { new_test_ext().execute_with(|| { Timestamp::set_timestamp(42); assert_ok!(Timestamp::set(Origin::none(), 69)); - let _ = Timestamp::set(Origin::none(), 70); }); } @@ -46,7 +45,7 @@ fn double_timestamp_should_fail() { )] fn block_period_minimum_enforced() { new_test_ext().execute_with(|| { - Timestamp::set_timestamp(42); + crate::Now::::put(44); let _ = Timestamp::set(Origin::none(), 46); }); } From 7eb96bea9a9e187bf1c7df305013be2ddc4b9a10 Mon Sep 17 00:00:00 2001 From: Davide Galassi Date: Thu, 4 Aug 2022 22:57:05 +0200 Subject: [PATCH 460/484] Prevent duplicated leaves in the backend (#11941) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Prevent duplicated leaves in the backend * Comments... * Use highest known heaf as a shortcut for not existing header detection * Apply code review suggestion Co-authored-by: Bastian Köcher Co-authored-by: Bastian Köcher --- client/db/src/lib.rs | 44 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/client/db/src/lib.rs b/client/db/src/lib.rs index e14d3a26aa557..0ce408a39b004 100644 --- a/client/db/src/lib.rs +++ b/client/db/src/lib.rs @@ -1345,8 +1345,15 @@ impl Backend { let parent_hash = *pending_block.header.parent_hash(); let number = *pending_block.header.number(); + let highest_leaf = self + .blockchain + .leaves + .read() + .highest_leaf() + .map(|(n, _)| n) + .unwrap_or(Zero::zero()); let existing_header = - number <= best_num && self.blockchain.header(BlockId::hash(hash))?.is_some(); + number <= highest_leaf && self.blockchain.header(BlockId::hash(hash))?.is_some(); // blocks are keyed by number + hash. let lookup_key = utils::number_and_hash_to_lookup_key(number, hash)?; @@ -3534,7 +3541,24 @@ pub(crate) mod tests { header.hash() }; - let block3_fork = insert_header_no_head(&backend, 3, block2, Default::default()); + let block3_fork = { + let mut op = backend.begin_operation().unwrap(); + backend.begin_state_operation(&mut op, BlockId::Hash(block2)).unwrap(); + let header = Header { + number: 3, + parent_hash: block2, + state_root: BlakeTwo256::trie_root(Vec::new(), StateVersion::V1), + digest: Default::default(), + extrinsics_root: H256::from_low_u64_le(42), + }; + + op.set_block_data(header.clone(), Some(Vec::new()), None, None, NewBlockState::Normal) + .unwrap(); + + backend.commit_operation(op).unwrap(); + + header.hash() + }; assert!(backend.have_state_at(&block1, 1)); assert!(backend.have_state_at(&block2, 2)); @@ -3556,4 +3580,20 @@ pub(crate) mod tests { assert_eq!(backend.blockchain.leaves().unwrap(), vec![block1]); assert_eq!(1, backend.blockchain.leaves.read().highest_leaf().unwrap().0); } + + #[test] + fn test_no_duplicated_leaves_allowed() { + let backend: Backend = Backend::new_test(10, 10); + let block0 = insert_header(&backend, 0, Default::default(), None, Default::default()); + let block1 = insert_header(&backend, 1, block0, None, Default::default()); + // Add block 2 not as the best block + let block2 = insert_header_no_head(&backend, 2, block1, Default::default()); + assert_eq!(backend.blockchain().leaves().unwrap(), vec![block2]); + assert_eq!(backend.blockchain().info().best_hash, block1); + + // Add block 2 as the best block + let block2 = insert_header(&backend, 2, block1, None, Default::default()); + assert_eq!(backend.blockchain().leaves().unwrap(), vec![block2]); + assert_eq!(backend.blockchain().info().best_hash, block2); + } } From 5dcf11f43374b00f0e328b92bdb9b5cb7e507126 Mon Sep 17 00:00:00 2001 From: Dmitry Markin Date: Fri, 5 Aug 2022 09:50:57 +0300 Subject: [PATCH 461/484] Change on-the-wire protocol names to include genesis hash & fork id (#11938) * Rename transactions protocol to include genesis hash * Add protocol name generation to sc_network::utils * Use utils functions for transactions protocol name generation * Extract protocol name generation into public module * Use sc_network::protocol_name::standard_protocol_name() for BEEFY and GRANDPA * minor: add missing newline at EOF * Change block-announces protocol name to include genesis_hash & fork_id * Change protocol names to include genesis hash and fork id Protocols changed: - sync - state - light - sync/warp * Revert "Use sc_network::protocol_name::standard_protocol_name() for BEEFY and GRANDPA" This reverts commit 29aa556ef947e2cfd48264db2a9fc8bc528d7ddb. * Get rid of `protocol_name` module --- Cargo.lock | 2 + .../network/common/src/request_responses.rs | 3 ++ client/network/light/Cargo.toml | 1 + .../light/src/light_client_requests.rs | 23 +++++++++-- .../src/light_client_requests/handler.rs | 20 ++++++++-- client/network/src/config.rs | 6 ++- client/network/src/protocol.rs | 18 +++++++-- client/network/src/request_responses.rs | 10 ++++- client/network/src/service.rs | 13 +++++- client/network/src/service/tests.rs | 11 +++-- client/network/src/transactions.rs | 21 ++++++++-- client/network/sync/Cargo.toml | 1 + .../network/sync/src/block_request_handler.rs | 34 +++++++++++++--- .../network/sync/src/state_request_handler.rs | 40 +++++++++++++++---- .../network/sync/src/warp_request_handler.rs | 30 +++++++++++--- client/network/test/src/lib.rs | 21 +++++++--- client/service/src/builder.rs | 22 ++++++++-- 17 files changed, 226 insertions(+), 50 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ec5c5267142a9..703eeffce54a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8435,6 +8435,7 @@ name = "sc-network-light" version = "0.10.0-dev" dependencies = [ "futures", + "hex", "libp2p", "log", "parity-scale-codec", @@ -8455,6 +8456,7 @@ version = "0.10.0-dev" dependencies = [ "fork-tree", "futures", + "hex", "libp2p", "log", "lru", diff --git a/client/network/common/src/request_responses.rs b/client/network/common/src/request_responses.rs index 71570e6beb864..f409d1ac16d61 100644 --- a/client/network/common/src/request_responses.rs +++ b/client/network/common/src/request_responses.rs @@ -29,6 +29,9 @@ pub struct ProtocolConfig { /// Name of the protocol on the wire. Should be something like `/foo/bar`. pub name: Cow<'static, str>, + /// Fallback on the wire protocol names to support. + pub fallback_names: Vec>, + /// Maximum allowed size, in bytes, of a request. /// /// Any request larger than this value will be declined as a way to avoid allocating too diff --git a/client/network/light/Cargo.toml b/client/network/light/Cargo.toml index 0037177fb4046..c1a0fb4759320 100644 --- a/client/network/light/Cargo.toml +++ b/client/network/light/Cargo.toml @@ -21,6 +21,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", features = [ "derive", ] } futures = "0.3.21" +hex = "0.4.0" libp2p = "0.46.1" log = "0.4.16" prost = "0.10" diff --git a/client/network/light/src/light_client_requests.rs b/client/network/light/src/light_client_requests.rs index 9eccef41e833d..b58426cf15992 100644 --- a/client/network/light/src/light_client_requests.rs +++ b/client/network/light/src/light_client_requests.rs @@ -25,16 +25,31 @@ use sc_network_common::{config::ProtocolId, request_responses::ProtocolConfig}; use std::time::Duration; -/// Generate the light client protocol name from chain specific protocol identifier. -fn generate_protocol_name(protocol_id: &ProtocolId) -> String { +/// Generate the light client protocol name from the genesis hash and fork id. +fn generate_protocol_name>(genesis_hash: Hash, fork_id: Option<&str>) -> String { + if let Some(fork_id) = fork_id { + format!("/{}/{}/light/2", hex::encode(genesis_hash), fork_id) + } else { + format!("/{}/light/2", hex::encode(genesis_hash)) + } +} + +/// Generate the legacy light client protocol name from chain specific protocol identifier. +fn generate_legacy_protocol_name(protocol_id: &ProtocolId) -> String { format!("/{}/light/2", protocol_id.as_ref()) } /// Generates a [`ProtocolConfig`] for the light client request protocol, refusing incoming /// requests. -pub fn generate_protocol_config(protocol_id: &ProtocolId) -> ProtocolConfig { +pub fn generate_protocol_config>( + protocol_id: &ProtocolId, + genesis_hash: Hash, + fork_id: Option<&str>, +) -> ProtocolConfig { ProtocolConfig { - name: generate_protocol_name(protocol_id).into(), + name: generate_protocol_name(genesis_hash, fork_id).into(), + fallback_names: std::iter::once(generate_legacy_protocol_name(protocol_id).into()) + .collect(), max_request_size: 1 * 1024 * 1024, max_response_size: 16 * 1024 * 1024, request_timeout: Duration::from_secs(15), diff --git a/client/network/light/src/light_client_requests/handler.rs b/client/network/light/src/light_client_requests/handler.rs index 3c87ccfd6ed9f..727a9b0d7e820 100644 --- a/client/network/light/src/light_client_requests/handler.rs +++ b/client/network/light/src/light_client_requests/handler.rs @@ -28,7 +28,7 @@ use futures::{channel::mpsc, prelude::*}; use libp2p::PeerId; use log::{debug, trace}; use prost::Message; -use sc_client_api::{ProofProvider, StorageProof}; +use sc_client_api::{BlockBackend, ProofProvider, StorageProof}; use sc_network_common::{ config::ProtocolId, request_responses::{IncomingRequest, OutgoingResponse, ProtocolConfig}, @@ -54,15 +54,27 @@ pub struct LightClientRequestHandler { impl LightClientRequestHandler where B: Block, - Client: ProofProvider + Send + Sync + 'static, + Client: BlockBackend + ProofProvider + Send + Sync + 'static, { /// Create a new [`LightClientRequestHandler`]. - pub fn new(protocol_id: &ProtocolId, client: Arc) -> (Self, ProtocolConfig) { + pub fn new( + protocol_id: &ProtocolId, + fork_id: Option<&str>, + client: Arc, + ) -> (Self, ProtocolConfig) { // For now due to lack of data on light client request handling in production systems, this // value is chosen to match the block request limit. let (tx, request_receiver) = mpsc::channel(20); - let mut protocol_config = super::generate_protocol_config(protocol_id); + let mut protocol_config = super::generate_protocol_config( + protocol_id, + client + .block_hash(0u32.into()) + .ok() + .flatten() + .expect("Genesis block exists; qed"), + fork_id, + ); protocol_config.inbound_queue = Some(tx); (Self { client, request_receiver, _block: PhantomData::default() }, protocol_config) diff --git a/client/network/src/config.rs b/client/network/src/config.rs index 2622762da5fc9..afd61cae6c83b 100644 --- a/client/network/src/config.rs +++ b/client/network/src/config.rs @@ -87,9 +87,13 @@ where /// the network. pub transaction_pool: Arc>, - /// Name of the protocol to use on the wire. Should be different for each chain. + /// Legacy name of the protocol to use on the wire. Should be different for each chain. pub protocol_id: ProtocolId, + /// Fork ID to distinguish protocols of different hard forks. Part of the standard protocol + /// name on the wire. + pub fork_id: Option, + /// Import queue to use. /// /// The import queue is the component that verifies that blocks received from other nodes are diff --git a/client/network/src/protocol.rs b/client/network/src/protocol.rs index 1c933fabcbb5d..a06fff2322ce6 100644 --- a/client/network/src/protocol.rs +++ b/client/network/src/protocol.rs @@ -276,6 +276,7 @@ where roles: Roles, chain: Arc, protocol_id: ProtocolId, + fork_id: &Option, network_config: &config::NetworkConfiguration, notifications_protocols_handshakes: Vec>, metrics_registry: Option<&Registry>, @@ -371,8 +372,17 @@ where sc_peerset::Peerset::from_config(sc_peerset::PeersetConfig { sets }) }; - let block_announces_protocol: Cow<'static, str> = - format!("/{}/block-announces/1", protocol_id.as_ref()).into(); + let block_announces_protocol = { + let genesis_hash = + chain.block_hash(0u32.into()).ok().flatten().expect("Genesis block exists; qed"); + if let Some(fork_id) = fork_id { + format!("/{}/{}/block-announces/1", hex::encode(genesis_hash), fork_id) + } else { + format!("/{}/block-announces/1", hex::encode(genesis_hash)) + } + }; + + let legacy_ba_protocol_name = format!("/{}/block-announces/1", protocol_id.as_ref()); let behaviour = { let best_number = info.best_number; @@ -384,8 +394,8 @@ where .encode(); let sync_protocol_config = notifications::ProtocolConfig { - name: block_announces_protocol, - fallback_names: Vec::new(), + name: block_announces_protocol.into(), + fallback_names: iter::once(legacy_ba_protocol_name.into()).collect(), handshake: block_announces_handshake, max_notification_size: MAX_BLOCK_ANNOUNCE_SIZE, }; diff --git a/client/network/src/request_responses.rs b/client/network/src/request_responses.rs index cec4aa2a07fba..0d8c6c33b1c95 100644 --- a/client/network/src/request_responses.rs +++ b/client/network/src/request_responses.rs @@ -220,7 +220,9 @@ impl RequestResponsesBehaviour { max_request_size: protocol.max_request_size, max_response_size: protocol.max_response_size, }, - iter::once((protocol.name.as_bytes().to_vec(), protocol_support)), + iter::once(protocol.name.as_bytes().to_vec()) + .chain(protocol.fallback_names.iter().map(|name| name.as_bytes().to_vec())) + .zip(iter::repeat(protocol_support)), cfg, ); @@ -1027,6 +1029,7 @@ mod tests { let protocol_config = ProtocolConfig { name: From::from(protocol_name), + fallback_names: Vec::new(), max_request_size: 1024, max_response_size: 1024 * 1024, request_timeout: Duration::from_secs(30), @@ -1127,6 +1130,7 @@ mod tests { let protocol_config = ProtocolConfig { name: From::from(protocol_name), + fallback_names: Vec::new(), max_request_size: 1024, max_response_size: 8, // <-- important for the test request_timeout: Duration::from_secs(30), @@ -1223,6 +1227,7 @@ mod tests { let protocol_configs = vec![ ProtocolConfig { name: From::from(protocol_name_1), + fallback_names: Vec::new(), max_request_size: 1024, max_response_size: 1024 * 1024, request_timeout: Duration::from_secs(30), @@ -1230,6 +1235,7 @@ mod tests { }, ProtocolConfig { name: From::from(protocol_name_2), + fallback_names: Vec::new(), max_request_size: 1024, max_response_size: 1024 * 1024, request_timeout: Duration::from_secs(30), @@ -1247,6 +1253,7 @@ mod tests { let protocol_configs = vec![ ProtocolConfig { name: From::from(protocol_name_1), + fallback_names: Vec::new(), max_request_size: 1024, max_response_size: 1024 * 1024, request_timeout: Duration::from_secs(30), @@ -1254,6 +1261,7 @@ mod tests { }, ProtocolConfig { name: From::from(protocol_name_2), + fallback_names: Vec::new(), max_request_size: 1024, max_response_size: 1024 * 1024, request_timeout: Duration::from_secs(30), diff --git a/client/network/src/service.rs b/client/network/src/service.rs index 409ed88c75c00..1210b0ca64224 100644 --- a/client/network/src/service.rs +++ b/client/network/src/service.rs @@ -224,8 +224,16 @@ where fs::create_dir_all(path)?; } - let transactions_handler_proto = - transactions::TransactionsHandlerPrototype::new(params.protocol_id.clone()); + let transactions_handler_proto = transactions::TransactionsHandlerPrototype::new( + params.protocol_id.clone(), + params + .chain + .block_hash(0u32.into()) + .ok() + .flatten() + .expect("Genesis block exists; qed"), + params.fork_id.clone(), + ); params .network_config .extra_sets @@ -243,6 +251,7 @@ where From::from(¶ms.role), params.chain.clone(), params.protocol_id.clone(), + ¶ms.fork_id, ¶ms.network_config, iter::once(Vec::new()) .chain( diff --git a/client/network/src/service/tests.rs b/client/network/src/service/tests.rs index de474ee8fe4d0..f757bf4891fbc 100644 --- a/client/network/src/service/tests.rs +++ b/client/network/src/service/tests.rs @@ -92,21 +92,25 @@ fn build_test_full_node( let protocol_id = ProtocolId::from("/test-protocol-name"); + let fork_id = Some(String::from("test-fork-id")); + let block_request_protocol_config = { - let (handler, protocol_config) = BlockRequestHandler::new(&protocol_id, client.clone(), 50); + let (handler, protocol_config) = + BlockRequestHandler::new(&protocol_id, None, client.clone(), 50); async_std::task::spawn(handler.run().boxed()); protocol_config }; let state_request_protocol_config = { - let (handler, protocol_config) = StateRequestHandler::new(&protocol_id, client.clone(), 50); + let (handler, protocol_config) = + StateRequestHandler::new(&protocol_id, None, client.clone(), 50); async_std::task::spawn(handler.run().boxed()); protocol_config }; let light_client_request_protocol_config = { let (handler, protocol_config) = - LightClientRequestHandler::new(&protocol_id, client.clone()); + LightClientRequestHandler::new(&protocol_id, None, client.clone()); async_std::task::spawn(handler.run().boxed()); protocol_config }; @@ -134,6 +138,7 @@ fn build_test_full_node( chain: client.clone(), transaction_pool: Arc::new(config::EmptyTransactionPool), protocol_id, + fork_id, import_queue, chain_sync: Box::new(chain_sync), metrics_registry: None, diff --git a/client/network/src/transactions.rs b/client/network/src/transactions.rs index 043bdeff7ebfc..342b6a0430272 100644 --- a/client/network/src/transactions.rs +++ b/client/network/src/transactions.rs @@ -127,19 +127,34 @@ impl Future for PendingTransaction { /// Prototype for a [`TransactionsHandler`]. pub struct TransactionsHandlerPrototype { protocol_name: Cow<'static, str>, + fallback_protocol_names: Vec>, } impl TransactionsHandlerPrototype { /// Create a new instance. - pub fn new(protocol_id: ProtocolId) -> Self { - Self { protocol_name: format!("/{}/transactions/1", protocol_id.as_ref()).into() } + pub fn new>( + protocol_id: ProtocolId, + genesis_hash: Hash, + fork_id: Option, + ) -> Self { + let protocol_name = if let Some(fork_id) = fork_id { + format!("/{}/{}/transactions/1", hex::encode(genesis_hash), fork_id) + } else { + format!("/{}/transactions/1", hex::encode(genesis_hash)) + }; + let legacy_protocol_name = format!("/{}/transactions/1", protocol_id.as_ref()); + + Self { + protocol_name: protocol_name.into(), + fallback_protocol_names: iter::once(legacy_protocol_name.into()).collect(), + } } /// Returns the configuration of the set to put in the network configuration. pub fn set_config(&self) -> config::NonDefaultSetConfig { config::NonDefaultSetConfig { notifications_protocol: self.protocol_name.clone(), - fallback_names: Vec::new(), + fallback_names: self.fallback_protocol_names.clone(), max_notification_size: MAX_TRANSACTIONS_SIZE, set_config: config::SetConfig { in_peers: 0, diff --git a/client/network/sync/Cargo.toml b/client/network/sync/Cargo.toml index 3e3526146400a..7c8f8adafd214 100644 --- a/client/network/sync/Cargo.toml +++ b/client/network/sync/Cargo.toml @@ -21,6 +21,7 @@ codec = { package = "parity-scale-codec", version = "3.0.0", features = [ "derive", ] } futures = "0.3.21" +hex = "0.4.0" libp2p = "0.46.1" log = "0.4.17" lru = "0.7.5" diff --git a/client/network/sync/src/block_request_handler.rs b/client/network/sync/src/block_request_handler.rs index 30cbb32289d66..f4f10ac73c8d9 100644 --- a/client/network/sync/src/block_request_handler.rs +++ b/client/network/sync/src/block_request_handler.rs @@ -62,9 +62,15 @@ mod rep { } /// Generates a [`ProtocolConfig`] for the block request protocol, refusing incoming requests. -pub fn generate_protocol_config(protocol_id: &ProtocolId) -> ProtocolConfig { +pub fn generate_protocol_config>( + protocol_id: &ProtocolId, + genesis_hash: Hash, + fork_id: Option<&str>, +) -> ProtocolConfig { ProtocolConfig { - name: generate_protocol_name(protocol_id).into(), + name: generate_protocol_name(genesis_hash, fork_id).into(), + fallback_names: std::iter::once(generate_legacy_protocol_name(protocol_id).into()) + .collect(), max_request_size: 1024 * 1024, max_response_size: 16 * 1024 * 1024, request_timeout: Duration::from_secs(20), @@ -72,8 +78,17 @@ pub fn generate_protocol_config(protocol_id: &ProtocolId) -> ProtocolConfig { } } -/// Generate the block protocol name from chain specific protocol identifier. -fn generate_protocol_name(protocol_id: &ProtocolId) -> String { +/// Generate the block protocol name from the genesis hash and fork id. +fn generate_protocol_name>(genesis_hash: Hash, fork_id: Option<&str>) -> String { + if let Some(fork_id) = fork_id { + format!("/{}/{}/sync/2", hex::encode(genesis_hash), fork_id) + } else { + format!("/{}/sync/2", hex::encode(genesis_hash)) + } +} + +/// Generate the legacy block protocol name from chain specific protocol identifier. +fn generate_legacy_protocol_name(protocol_id: &ProtocolId) -> String { format!("/{}/sync/2", protocol_id.as_ref()) } @@ -129,6 +144,7 @@ where /// Create a new [`BlockRequestHandler`]. pub fn new( protocol_id: &ProtocolId, + fork_id: Option<&str>, client: Arc, num_peer_hint: usize, ) -> (Self, ProtocolConfig) { @@ -136,7 +152,15 @@ where // number of peers. let (tx, request_receiver) = mpsc::channel(num_peer_hint); - let mut protocol_config = generate_protocol_config(protocol_id); + let mut protocol_config = generate_protocol_config( + protocol_id, + client + .block_hash(0u32.into()) + .ok() + .flatten() + .expect("Genesis block exists; qed"), + fork_id, + ); protocol_config.inbound_queue = Some(tx); let seen_requests = LruCache::new(num_peer_hint * 2); diff --git a/client/network/sync/src/state_request_handler.rs b/client/network/sync/src/state_request_handler.rs index 8e0bae14046da..6cf6482a44f8b 100644 --- a/client/network/sync/src/state_request_handler.rs +++ b/client/network/sync/src/state_request_handler.rs @@ -27,7 +27,7 @@ use libp2p::PeerId; use log::{debug, trace}; use lru::LruCache; use prost::Message; -use sc_client_api::ProofProvider; +use sc_client_api::{BlockBackend, ProofProvider}; use sc_network_common::{ config::ProtocolId, request_responses::{IncomingRequest, OutgoingResponse, ProtocolConfig}, @@ -50,10 +50,16 @@ mod rep { pub const SAME_REQUEST: Rep = Rep::new(i32::MIN, "Same state request multiple times"); } -/// Generates a [`ProtocolConfig`] for the block request protocol, refusing incoming requests. -pub fn generate_protocol_config(protocol_id: &ProtocolId) -> ProtocolConfig { +/// Generates a [`ProtocolConfig`] for the state request protocol, refusing incoming requests. +pub fn generate_protocol_config>( + protocol_id: &ProtocolId, + genesis_hash: Hash, + fork_id: Option<&str>, +) -> ProtocolConfig { ProtocolConfig { - name: generate_protocol_name(protocol_id).into(), + name: generate_protocol_name(genesis_hash, fork_id).into(), + fallback_names: std::iter::once(generate_legacy_protocol_name(protocol_id).into()) + .collect(), max_request_size: 1024 * 1024, max_response_size: 16 * 1024 * 1024, request_timeout: Duration::from_secs(40), @@ -61,8 +67,17 @@ pub fn generate_protocol_config(protocol_id: &ProtocolId) -> ProtocolConfig { } } -/// Generate the state protocol name from chain specific protocol identifier. -fn generate_protocol_name(protocol_id: &ProtocolId) -> String { +/// Generate the state protocol name from the genesis hash and fork id. +fn generate_protocol_name>(genesis_hash: Hash, fork_id: Option<&str>) -> String { + if let Some(fork_id) = fork_id { + format!("/{}/{}/state/2", hex::encode(genesis_hash), fork_id) + } else { + format!("/{}/state/2", hex::encode(genesis_hash)) + } +} + +/// Generate the legacy state protocol name from chain specific protocol identifier. +fn generate_legacy_protocol_name(protocol_id: &ProtocolId) -> String { format!("/{}/state/2", protocol_id.as_ref()) } @@ -104,11 +119,12 @@ pub struct StateRequestHandler { impl StateRequestHandler where B: BlockT, - Client: ProofProvider + Send + Sync + 'static, + Client: BlockBackend + ProofProvider + Send + Sync + 'static, { /// Create a new [`StateRequestHandler`]. pub fn new( protocol_id: &ProtocolId, + fork_id: Option<&str>, client: Arc, num_peer_hint: usize, ) -> (Self, ProtocolConfig) { @@ -116,7 +132,15 @@ where // number of peers. let (tx, request_receiver) = mpsc::channel(num_peer_hint); - let mut protocol_config = generate_protocol_config(protocol_id); + let mut protocol_config = generate_protocol_config( + protocol_id, + client + .block_hash(0u32.into()) + .ok() + .flatten() + .expect("Genesis block exists; qed"), + fork_id, + ); protocol_config.inbound_queue = Some(tx); let seen_requests = LruCache::new(num_peer_hint * 2); diff --git a/client/network/sync/src/warp_request_handler.rs b/client/network/sync/src/warp_request_handler.rs index 53ec216a1e668..394bc68449099 100644 --- a/client/network/sync/src/warp_request_handler.rs +++ b/client/network/sync/src/warp_request_handler.rs @@ -36,9 +36,15 @@ const MAX_RESPONSE_SIZE: u64 = 16 * 1024 * 1024; /// Generates a [`RequestResponseConfig`] for the grandpa warp sync request protocol, refusing /// incoming requests. -pub fn generate_request_response_config(protocol_id: ProtocolId) -> RequestResponseConfig { +pub fn generate_request_response_config>( + protocol_id: ProtocolId, + genesis_hash: Hash, + fork_id: Option<&str>, +) -> RequestResponseConfig { RequestResponseConfig { - name: generate_protocol_name(protocol_id).into(), + name: generate_protocol_name(genesis_hash, fork_id).into(), + fallback_names: std::iter::once(generate_legacy_protocol_name(protocol_id).into()) + .collect(), max_request_size: 32, max_response_size: MAX_RESPONSE_SIZE, request_timeout: Duration::from_secs(10), @@ -46,8 +52,17 @@ pub fn generate_request_response_config(protocol_id: ProtocolId) -> RequestRespo } } -/// Generate the grandpa warp sync protocol name from chain specific protocol identifier. -fn generate_protocol_name(protocol_id: ProtocolId) -> String { +/// Generate the grandpa warp sync protocol name from the genesi hash and fork id. +fn generate_protocol_name>(genesis_hash: Hash, fork_id: Option<&str>) -> String { + if let Some(fork_id) = fork_id { + format!("/{}/{}/sync/warp", hex::encode(genesis_hash), fork_id) + } else { + format!("/{}/sync/warp", hex::encode(genesis_hash)) + } +} + +/// Generate the legacy grandpa warp sync protocol name from chain specific protocol identifier. +fn generate_legacy_protocol_name(protocol_id: ProtocolId) -> String { format!("/{}/sync/warp", protocol_id.as_ref()) } @@ -59,13 +74,16 @@ pub struct RequestHandler { impl RequestHandler { /// Create a new [`RequestHandler`]. - pub fn new( + pub fn new>( protocol_id: ProtocolId, + genesis_hash: Hash, + fork_id: Option<&str>, backend: Arc>, ) -> (Self, RequestResponseConfig) { let (tx, request_receiver) = mpsc::channel(20); - let mut request_response_config = generate_request_response_config(protocol_id); + let mut request_response_config = + generate_request_response_config(protocol_id, genesis_hash, fork_id); request_response_config.inbound_queue = Some(tx); (Self { backend, request_receiver }, request_response_config) diff --git a/client/network/test/src/lib.rs b/client/network/test/src/lib.rs index 4659684987f77..494ff2048f6c4 100644 --- a/client/network/test/src/lib.rs +++ b/client/network/test/src/lib.rs @@ -808,23 +808,25 @@ where let protocol_id = ProtocolId::from("test-protocol-name"); + let fork_id = Some(String::from("test-fork-id")); + let block_request_protocol_config = { let (handler, protocol_config) = - BlockRequestHandler::new(&protocol_id, client.clone(), 50); + BlockRequestHandler::new(&protocol_id, None, client.clone(), 50); self.spawn_task(handler.run().boxed()); protocol_config }; let state_request_protocol_config = { let (handler, protocol_config) = - StateRequestHandler::new(&protocol_id, client.clone(), 50); + StateRequestHandler::new(&protocol_id, None, client.clone(), 50); self.spawn_task(handler.run().boxed()); protocol_config }; let light_client_request_protocol_config = { let (handler, protocol_config) = - LightClientRequestHandler::new(&protocol_id, client.clone()); + LightClientRequestHandler::new(&protocol_id, None, client.clone()); self.spawn_task(handler.run().boxed()); protocol_config }; @@ -832,8 +834,16 @@ where let warp_sync = Arc::new(TestWarpSyncProvider(client.clone())); let warp_protocol_config = { - let (handler, protocol_config) = - warp_request_handler::RequestHandler::new(protocol_id.clone(), warp_sync.clone()); + let (handler, protocol_config) = warp_request_handler::RequestHandler::new( + protocol_id.clone(), + client + .block_hash(0u32.into()) + .ok() + .flatten() + .expect("Genesis block exists; qed"), + None, + warp_sync.clone(), + ); self.spawn_task(handler.run().boxed()); protocol_config }; @@ -867,6 +877,7 @@ where chain: client.clone(), transaction_pool: Arc::new(EmptyTransactionPool), protocol_id, + fork_id, import_queue, chain_sync: Box::new(chain_sync), metrics_registry: None, diff --git a/client/service/src/builder.rs b/client/service/src/builder.rs index ec537a33b72d5..998ec12c1d05b 100644 --- a/client/service/src/builder.rs +++ b/client/service/src/builder.rs @@ -741,6 +741,7 @@ where // Allow both outgoing and incoming requests. let (handler, protocol_config) = BlockRequestHandler::new( &protocol_id, + config.chain_spec.fork_id(), client.clone(), config.network.default_peers_set.in_peers as usize + config.network.default_peers_set.out_peers as usize, @@ -753,6 +754,7 @@ where // Allow both outgoing and incoming requests. let (handler, protocol_config) = StateRequestHandler::new( &protocol_id, + config.chain_spec.fork_id(), client.clone(), config.network.default_peers_set_num_full as usize, ); @@ -763,8 +765,16 @@ where let (warp_sync_provider, warp_sync_protocol_config) = warp_sync .map(|provider| { // Allow both outgoing and incoming requests. - let (handler, protocol_config) = - WarpSyncRequestHandler::new(protocol_id.clone(), provider.clone()); + let (handler, protocol_config) = WarpSyncRequestHandler::new( + protocol_id.clone(), + client + .block_hash(0u32.into()) + .ok() + .flatten() + .expect("Genesis block exists; qed"), + config.chain_spec.fork_id(), + provider.clone(), + ); spawn_handle.spawn("warp-sync-request-handler", Some("networking"), handler.run()); (Some(provider), Some(protocol_config)) }) @@ -772,8 +782,11 @@ where let light_client_request_protocol_config = { // Allow both outgoing and incoming requests. - let (handler, protocol_config) = - LightClientRequestHandler::new(&protocol_id, client.clone()); + let (handler, protocol_config) = LightClientRequestHandler::new( + &protocol_id, + config.chain_spec.fork_id(), + client.clone(), + ); spawn_handle.spawn("light-client-request-handler", Some("networking"), handler.run()); protocol_config }; @@ -808,6 +821,7 @@ where chain: client.clone(), transaction_pool: transaction_pool_adapter as _, protocol_id, + fork_id: config.chain_spec.fork_id().map(ToOwned::to_owned), import_queue: Box::new(import_queue), chain_sync: Box::new(chain_sync), metrics_registry: config.prometheus_config.as_ref().map(|config| config.registry.clone()), From ccdc651177d8c59dc2f91c857ebb7c35488c9e41 Mon Sep 17 00:00:00 2001 From: Dmitry Markin Date: Fri, 5 Aug 2022 12:48:05 +0300 Subject: [PATCH 462/484] `sync` protocol now can have negotiated fallback name (#11982) --- client/network/src/protocol.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/network/src/protocol.rs b/client/network/src/protocol.rs index a06fff2322ce6..c51667017d15c 100644 --- a/client/network/src/protocol.rs +++ b/client/network/src/protocol.rs @@ -1577,8 +1577,6 @@ where } => { // Set number 0 is hardcoded the default set of peers we sync from. if set_id == HARDCODED_PEERSETS_SYNC { - debug_assert!(negotiated_fallback.is_none()); - // `received_handshake` can be either a `Status` message if received from the // legacy substream ,or a `BlockAnnouncesHandshake` if received from the block // announces substream. From 8251fce08974d593f0aedcc794182fbb9e495fd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 5 Aug 2022 19:53:56 +0200 Subject: [PATCH 463/484] system_syncState: Always return highest block (#11979) Before `highestBlock` was an optional that was omitted when it was `None`. We recently changed the way the `highestBlock` is determined, this resulted in having this value in 99.99% of the time being `None` when the node is syncing blocks at the tip. Now we always return a block for `highestBlock`. If sync doesn't return us any best seen block, we return our own local best block as `highestBlock`. This should mainly reflect the same behavior to before we changed the way the best seen block is determined. --- client/rpc-api/src/system/helpers.rs | 12 ++++++------ client/rpc/src/system/tests.rs | 7 ++----- client/service/src/lib.rs | 6 ++++-- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/client/rpc-api/src/system/helpers.rs b/client/rpc-api/src/system/helpers.rs index 4561fccc1e81b..7ddb3f813c249 100644 --- a/client/rpc-api/src/system/helpers.rs +++ b/client/rpc-api/src/system/helpers.rs @@ -88,10 +88,10 @@ pub struct SyncState { pub starting_block: Number, /// Height of the current best block of the node. pub current_block: Number, - /// Height of the highest block learned from the network. Missing if no block is known yet. - #[serde(default = "Default::default", skip_serializing_if = "Option::is_none")] - pub highest_block: Option, + /// Height of the highest block in the network. + pub highest_block: Number, } + #[cfg(test)] mod tests { use super::*; @@ -129,7 +129,7 @@ mod tests { ::serde_json::to_string(&SyncState { starting_block: 12u32, current_block: 50u32, - highest_block: Some(128u32), + highest_block: 128u32, }) .unwrap(), r#"{"startingBlock":12,"currentBlock":50,"highestBlock":128}"#, @@ -139,10 +139,10 @@ mod tests { ::serde_json::to_string(&SyncState { starting_block: 12u32, current_block: 50u32, - highest_block: None, + highest_block: 50u32, }) .unwrap(), - r#"{"startingBlock":12,"currentBlock":50}"#, + r#"{"startingBlock":12,"currentBlock":50,"highestBlock":50}"#, ); } } diff --git a/client/rpc/src/system/tests.rs b/client/rpc/src/system/tests.rs index 77acdf8418ccc..facad7a0b347a 100644 --- a/client/rpc/src/system/tests.rs +++ b/client/rpc/src/system/tests.rs @@ -123,7 +123,7 @@ fn api>>(sync: T) -> RpcModule> { let _ = sender.send(SyncState { starting_block: 1, current_block: 2, - highest_block: Some(3), + highest_block: 3, }); }, }; @@ -297,10 +297,7 @@ async fn system_node_roles() { async fn system_sync_state() { let sync_state: SyncState = api(None).call("system_syncState", EmptyParams::new()).await.unwrap(); - assert_eq!( - sync_state, - SyncState { starting_block: 1, current_block: 2, highest_block: Some(3) } - ); + assert_eq!(sync_state, SyncState { starting_block: 1, current_block: 2, highest_block: 3 }); } #[tokio::test] diff --git a/client/service/src/lib.rs b/client/service/src/lib.rs index 98bcb17174157..1de45e5527eec 100644 --- a/client/service/src/lib.rs +++ b/client/service/src/lib.rs @@ -264,10 +264,12 @@ async fn build_network_future< sc_rpc::system::Request::SyncState(sender) => { use sc_rpc::system::SyncState; + let best_number = client.info().best_number; + let _ = sender.send(SyncState { starting_block, - current_block: client.info().best_number, - highest_block: network.best_seen_block(), + current_block: best_number, + highest_block: network.best_seen_block().unwrap_or(best_number), }); } } From 8a5d9e103ddc4dec147dcd0557ac4d892f03898d Mon Sep 17 00:00:00 2001 From: Nikos Kontakis Date: Mon, 8 Aug 2022 11:31:26 +0200 Subject: [PATCH 464/484] Rename --pruning and --keep-blocks to be more similar to one another (#11934) * rename prunning and keep-blocks flags * Add aliases in keep-blocks and pruning for backward compatibility * Rename in code variables from and to and --- bin/node/cli/benches/block_production.rs | 4 +- bin/node/cli/benches/transaction_pool.rs | 4 +- bin/node/testing/src/bench.rs | 2 +- client/cli/src/commands/chain_info_cmd.rs | 2 +- client/cli/src/config.rs | 12 +++--- client/cli/src/params/pruning_params.rs | 22 +++++------ client/db/src/lib.rs | 26 ++++++------- client/network/test/src/lib.rs | 8 ++-- client/network/test/src/sync.rs | 2 +- client/service/src/builder.rs | 2 +- client/service/src/config.rs | 4 +- client/service/src/lib.rs | 2 +- client/service/test/src/client/mod.rs | 8 ++-- client/service/test/src/lib.rs | 6 +-- client/state-db/src/lib.rs | 46 +++++++++++------------ test-utils/client/src/lib.rs | 8 ++-- 16 files changed, 79 insertions(+), 79 deletions(-) diff --git a/bin/node/cli/benches/block_production.rs b/bin/node/cli/benches/block_production.rs index 6d269ccaac271..8420a65f8cb80 100644 --- a/bin/node/cli/benches/block_production.rs +++ b/bin/node/cli/benches/block_production.rs @@ -28,7 +28,7 @@ use sc_consensus::{ }; use sc_service::{ config::{ - DatabaseSource, KeepBlocks, KeystoreConfig, NetworkConfiguration, OffchainWorkerConfig, + BlocksPruning, DatabaseSource, KeystoreConfig, NetworkConfiguration, OffchainWorkerConfig, PruningMode, WasmExecutionMethod, WasmtimeInstantiationStrategy, }, BasePath, Configuration, Role, @@ -75,7 +75,7 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase { state_cache_size: 67108864, state_cache_child_ratio: None, state_pruning: Some(PruningMode::ArchiveAll), - keep_blocks: KeepBlocks::All, + blocks_pruning: BlocksPruning::All, chain_spec: spec, wasm_method: WasmExecutionMethod::Compiled { instantiation_strategy: WasmtimeInstantiationStrategy::PoolingCopyOnWrite, diff --git a/bin/node/cli/benches/transaction_pool.rs b/bin/node/cli/benches/transaction_pool.rs index 580a10d6a6678..f031f9dae3d21 100644 --- a/bin/node/cli/benches/transaction_pool.rs +++ b/bin/node/cli/benches/transaction_pool.rs @@ -26,7 +26,7 @@ use node_primitives::AccountId; use sc_client_api::execution_extensions::ExecutionStrategies; use sc_service::{ config::{ - DatabaseSource, KeepBlocks, KeystoreConfig, NetworkConfiguration, OffchainWorkerConfig, + BlocksPruning, DatabaseSource, KeystoreConfig, NetworkConfiguration, OffchainWorkerConfig, PruningMode, TransactionPoolOptions, WasmExecutionMethod, }, BasePath, Configuration, Role, @@ -69,7 +69,7 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase { state_cache_size: 67108864, state_cache_child_ratio: None, state_pruning: Some(PruningMode::ArchiveAll), - keep_blocks: KeepBlocks::All, + blocks_pruning: BlocksPruning::All, chain_spec: spec, wasm_method: WasmExecutionMethod::Interpreted, // NOTE: we enforce the use of the native runtime to make the errors more debuggable diff --git a/bin/node/testing/src/bench.rs b/bin/node/testing/src/bench.rs index 18e979b95737f..de64910125b86 100644 --- a/bin/node/testing/src/bench.rs +++ b/bin/node/testing/src/bench.rs @@ -392,7 +392,7 @@ impl BenchDb { state_cache_child_ratio: Some((0, 100)), state_pruning: Some(PruningMode::ArchiveAll), source: database_type.into_settings(dir.into()), - keep_blocks: sc_client_db::KeepBlocks::All, + blocks_pruning: sc_client_db::BlocksPruning::All, }; let task_executor = TaskExecutor::new(); diff --git a/client/cli/src/commands/chain_info_cmd.rs b/client/cli/src/commands/chain_info_cmd.rs index 0e57d1677efbb..c65092290cf2d 100644 --- a/client/cli/src/commands/chain_info_cmd.rs +++ b/client/cli/src/commands/chain_info_cmd.rs @@ -77,7 +77,7 @@ impl ChainInfoCmd { state_cache_child_ratio: config.state_cache_child_ratio.map(|v| (v, 100)), state_pruning: config.state_pruning.clone(), source: config.database.clone(), - keep_blocks: config.keep_blocks.clone(), + blocks_pruning: config.blocks_pruning.clone(), }; let backend = sc_service::new_db_backend::(db_config)?; let info: ChainInfo = backend.blockchain().info().into(); diff --git a/client/cli/src/config.rs b/client/cli/src/config.rs index 4ebbc8c72c19a..f513008b17adc 100644 --- a/client/cli/src/config.rs +++ b/client/cli/src/config.rs @@ -31,7 +31,7 @@ use sc_service::{ NodeKeyConfig, OffchainWorkerConfig, PrometheusConfig, PruningMode, Role, RpcMethods, TelemetryEndpoints, TransactionPoolOptions, WasmExecutionMethod, }, - ChainSpec, KeepBlocks, TracingReceiver, + BlocksPruning, ChainSpec, TracingReceiver, }; use sc_tracing::logging::LoggerBuilder; use std::{net::SocketAddr, path::PathBuf}; @@ -257,11 +257,11 @@ pub trait CliConfiguration: Sized { /// Get the block pruning mode. /// /// By default this is retrieved from `block_pruning` if it is available. Otherwise its - /// `KeepBlocks::All`. - fn keep_blocks(&self) -> Result { + /// `BlocksPruning::All`. + fn blocks_pruning(&self) -> Result { self.pruning_params() - .map(|x| x.keep_blocks()) - .unwrap_or_else(|| Ok(KeepBlocks::All)) + .map(|x| x.blocks_pruning()) + .unwrap_or_else(|| Ok(BlocksPruning::All)) } /// Get the chain ID (string). @@ -536,7 +536,7 @@ pub trait CliConfiguration: Sized { state_cache_size: self.state_cache_size()?, state_cache_child_ratio: self.state_cache_child_ratio()?, state_pruning: self.state_pruning()?, - keep_blocks: self.keep_blocks()?, + blocks_pruning: self.blocks_pruning()?, wasm_method: self.wasm_method()?, wasm_runtime_overrides: self.wasm_runtime_overrides(), execution_strategies: self.execution_strategies(is_dev, is_validator)?, diff --git a/client/cli/src/params/pruning_params.rs b/client/cli/src/params/pruning_params.rs index 9d471224cc59e..34a0982e63d95 100644 --- a/client/cli/src/params/pruning_params.rs +++ b/client/cli/src/params/pruning_params.rs @@ -18,7 +18,7 @@ use crate::error; use clap::Args; -use sc_service::{KeepBlocks, PruningMode}; +use sc_service::{BlocksPruning, PruningMode}; /// Parameters to define the pruning mode #[derive(Debug, Clone, PartialEq, Args)] @@ -28,37 +28,37 @@ pub struct PruningParams { /// Default is to keep only the last 256 blocks, /// otherwise, the state can be kept for all of the blocks (i.e 'archive'), /// or for all of the canonical blocks (i.e 'archive-canonical'). - #[clap(long, value_name = "PRUNING_MODE")] - pub pruning: Option, + #[clap(alias = "pruning", long, value_name = "PRUNING_MODE")] + pub state_pruning: Option, /// Specify the number of finalized blocks to keep in the database. /// /// Default is to keep all blocks. /// /// NOTE: only finalized blocks are subject for removal! - #[clap(long, value_name = "COUNT")] - pub keep_blocks: Option, + #[clap(alias = "keep-blocks", long, value_name = "COUNT")] + pub blocks_pruning: Option, } impl PruningParams { /// Get the pruning value from the parameters pub fn state_pruning(&self) -> error::Result> { - self.pruning + self.state_pruning .as_ref() .map(|s| match s.as_str() { "archive" => Ok(PruningMode::ArchiveAll), bc => bc .parse() .map_err(|_| error::Error::Input("Invalid pruning mode specified".to_string())) - .map(PruningMode::keep_blocks), + .map(PruningMode::blocks_pruning), }) .transpose() } /// Get the block pruning value from the parameters - pub fn keep_blocks(&self) -> error::Result { - Ok(match self.keep_blocks { - Some(n) => KeepBlocks::Some(n), - None => KeepBlocks::All, + pub fn blocks_pruning(&self) -> error::Result { + Ok(match self.blocks_pruning { + Some(n) => BlocksPruning::Some(n), + None => BlocksPruning::All, }) } } diff --git a/client/db/src/lib.rs b/client/db/src/lib.rs index 0ce408a39b004..3214ee8f0d738 100644 --- a/client/db/src/lib.rs +++ b/client/db/src/lib.rs @@ -300,12 +300,12 @@ pub struct DatabaseSettings { /// Block pruning mode. /// /// NOTE: only finalized blocks are subject for removal! - pub keep_blocks: KeepBlocks, + pub blocks_pruning: BlocksPruning, } /// Block pruning settings. #[derive(Debug, Clone, Copy)] -pub enum KeepBlocks { +pub enum BlocksPruning { /// Keep full block history. All, /// Keep N recent finalized blocks. @@ -1012,7 +1012,7 @@ pub struct Backend { shared_cache: SharedCache, import_lock: Arc>, is_archive: bool, - keep_blocks: KeepBlocks, + blocks_pruning: BlocksPruning, io_stats: FrozenForDuration<(kvdb::IoStats, StateUsageInfo)>, state_usage: Arc, genesis_state: RwLock>>>, @@ -1043,21 +1043,21 @@ impl Backend { /// Create new memory-backed client backend for tests. #[cfg(any(test, feature = "test-helpers"))] - pub fn new_test(keep_blocks: u32, canonicalization_delay: u64) -> Self { - Self::new_test_with_tx_storage(keep_blocks, canonicalization_delay) + pub fn new_test(blocks_pruning: u32, canonicalization_delay: u64) -> Self { + Self::new_test_with_tx_storage(blocks_pruning, canonicalization_delay) } /// Create new memory-backed client backend for tests. #[cfg(any(test, feature = "test-helpers"))] - pub fn new_test_with_tx_storage(keep_blocks: u32, canonicalization_delay: u64) -> Self { + pub fn new_test_with_tx_storage(blocks_pruning: u32, canonicalization_delay: u64) -> Self { let db = kvdb_memorydb::create(crate::utils::NUM_COLUMNS); let db = sp_database::as_database(db); let db_setting = DatabaseSettings { state_cache_size: 16777216, state_cache_child_ratio: Some((50, 100)), - state_pruning: Some(PruningMode::keep_blocks(keep_blocks)), + state_pruning: Some(PruningMode::blocks_pruning(blocks_pruning)), source: DatabaseSource::Custom { db, require_create_flag: true }, - keep_blocks: KeepBlocks::Some(keep_blocks), + blocks_pruning: BlocksPruning::Some(blocks_pruning), }; Self::new(db_setting, canonicalization_delay).expect("failed to create test-db") @@ -1124,7 +1124,7 @@ impl Backend { is_archive: is_archive_pruning, io_stats: FrozenForDuration::new(std::time::Duration::from_secs(1)), state_usage: Arc::new(StateUsageStats::new()), - keep_blocks: config.keep_blocks, + blocks_pruning: config.blocks_pruning, genesis_state: RwLock::new(None), }; @@ -1698,9 +1698,9 @@ impl Backend { finalized: NumberFor, displaced: &FinalizationDisplaced>, ) -> ClientResult<()> { - if let KeepBlocks::Some(keep_blocks) = self.keep_blocks { + if let BlocksPruning::Some(blocks_pruning) = self.blocks_pruning { // Always keep the last finalized block - let keep = std::cmp::max(keep_blocks, 1); + let keep = std::cmp::max(blocks_pruning, 1); if finalized >= keep.into() { let number = finalized.saturating_sub(keep.into()); self.prune_block(transaction, BlockId::::number(number))?; @@ -2465,9 +2465,9 @@ pub(crate) mod tests { DatabaseSettings { state_cache_size: 16777216, state_cache_child_ratio: Some((50, 100)), - state_pruning: Some(PruningMode::keep_blocks(1)), + state_pruning: Some(PruningMode::blocks_pruning(1)), source: DatabaseSource::Custom { db: backing, require_create_flag: false }, - keep_blocks: KeepBlocks::All, + blocks_pruning: BlocksPruning::All, }, 0, ) diff --git a/client/network/test/src/lib.rs b/client/network/test/src/lib.rs index 494ff2048f6c4..09b4139c213a6 100644 --- a/client/network/test/src/lib.rs +++ b/client/network/test/src/lib.rs @@ -676,7 +676,7 @@ pub struct FullPeerConfig { /// Pruning window size. /// /// NOTE: only finalized blocks are subject for removal! - pub keep_blocks: Option, + pub blocks_pruning: Option, /// Block announce validator. pub block_announce_validator: Option + Send + Sync>>, /// List of notification protocols that the network must support. @@ -742,10 +742,10 @@ where /// Add a full peer. fn add_full_peer_with_config(&mut self, config: FullPeerConfig) { - let mut test_client_builder = match (config.keep_blocks, config.storage_chain) { - (Some(keep_blocks), true) => TestClientBuilder::with_tx_storage(keep_blocks), + let mut test_client_builder = match (config.blocks_pruning, config.storage_chain) { + (Some(blocks_pruning), true) => TestClientBuilder::with_tx_storage(blocks_pruning), (None, true) => TestClientBuilder::with_tx_storage(u32::MAX), - (Some(keep_blocks), false) => TestClientBuilder::with_pruning_window(keep_blocks), + (Some(blocks_pruning), false) => TestClientBuilder::with_pruning_window(blocks_pruning), (None, false) => TestClientBuilder::with_default_backend(), }; if let Some(storage) = config.extra_storage { diff --git a/client/network/test/src/sync.rs b/client/network/test/src/sync.rs index 84a5c2ca13fa5..c0778767b75af 100644 --- a/client/network/test/src/sync.rs +++ b/client/network/test/src/sync.rs @@ -544,7 +544,7 @@ fn syncs_header_only_forks() { sp_tracing::try_init_simple(); let mut net = TestNet::new(0); net.add_full_peer_with_config(Default::default()); - net.add_full_peer_with_config(FullPeerConfig { keep_blocks: Some(3), ..Default::default() }); + net.add_full_peer_with_config(FullPeerConfig { blocks_pruning: Some(3), ..Default::default() }); net.peer(0).push_blocks(2, false); net.peer(1).push_blocks(2, false); diff --git a/client/service/src/builder.rs b/client/service/src/builder.rs index 998ec12c1d05b..55c0006de33fb 100644 --- a/client/service/src/builder.rs +++ b/client/service/src/builder.rs @@ -207,7 +207,7 @@ where state_cache_child_ratio: config.state_cache_child_ratio.map(|v| (v, 100)), state_pruning: config.state_pruning.clone(), source: config.database.clone(), - keep_blocks: config.keep_blocks, + blocks_pruning: config.blocks_pruning, }; let backend = new_db_backend(db_config)?; diff --git a/client/service/src/config.rs b/client/service/src/config.rs index 0eeb6e05cee16..7860ff2281fe4 100644 --- a/client/service/src/config.rs +++ b/client/service/src/config.rs @@ -19,7 +19,7 @@ //! Service configuration. pub use sc_client_api::execution_extensions::{ExecutionStrategies, ExecutionStrategy}; -pub use sc_client_db::{Database, DatabaseSource, KeepBlocks, PruningMode}; +pub use sc_client_db::{BlocksPruning, Database, DatabaseSource, PruningMode}; pub use sc_executor::WasmExecutionMethod; #[cfg(feature = "wasmtime")] pub use sc_executor::WasmtimeInstantiationStrategy; @@ -79,7 +79,7 @@ pub struct Configuration { /// Number of blocks to keep in the db. /// /// NOTE: only finalized blocks are subject for removal! - pub keep_blocks: KeepBlocks, + pub blocks_pruning: BlocksPruning, /// Chain configuration. pub chain_spec: Box, /// Wasm execution method. diff --git a/client/service/src/lib.rs b/client/service/src/lib.rs index 1de45e5527eec..5291d219e8102 100644 --- a/client/service/src/lib.rs +++ b/client/service/src/lib.rs @@ -60,7 +60,7 @@ pub use self::{ error::Error, }; pub use config::{ - BasePath, Configuration, DatabaseSource, KeepBlocks, PruningMode, Role, RpcMethods, TaskType, + BasePath, BlocksPruning, Configuration, DatabaseSource, PruningMode, Role, RpcMethods, TaskType, }; pub use sc_chain_spec::{ ChainSpec, ChainType, Extension as ChainSpecExtension, GenericChainSpec, NoExtension, diff --git a/client/service/test/src/client/mod.rs b/client/service/test/src/client/mod.rs index 136efad088fae..f363b621d5bcd 100644 --- a/client/service/test/src/client/mod.rs +++ b/client/service/test/src/client/mod.rs @@ -23,7 +23,7 @@ use sc_block_builder::BlockBuilderProvider; use sc_client_api::{ in_mem, BlockBackend, BlockchainEvents, FinalityNotifications, StorageProvider, }; -use sc_client_db::{Backend, DatabaseSettings, DatabaseSource, KeepBlocks, PruningMode}; +use sc_client_db::{Backend, BlocksPruning, DatabaseSettings, DatabaseSource, PruningMode}; use sc_consensus::{ BlockCheckParams, BlockImport, BlockImportParams, ForkChoiceStrategy, ImportResult, }; @@ -1200,7 +1200,7 @@ fn doesnt_import_blocks_that_revert_finality() { state_cache_size: 1 << 20, state_cache_child_ratio: None, state_pruning: Some(PruningMode::ArchiveAll), - keep_blocks: KeepBlocks::All, + blocks_pruning: BlocksPruning::All, source: DatabaseSource::RocksDb { path: tmp.path().into(), cache_size: 1024 }, }, u64::MAX, @@ -1426,8 +1426,8 @@ fn returns_status_for_pruned_blocks() { DatabaseSettings { state_cache_size: 1 << 20, state_cache_child_ratio: None, - state_pruning: Some(PruningMode::keep_blocks(1)), - keep_blocks: KeepBlocks::All, + state_pruning: Some(PruningMode::blocks_pruning(1)), + blocks_pruning: BlocksPruning::All, source: DatabaseSource::RocksDb { path: tmp.path().into(), cache_size: 1024 }, }, u64::MAX, diff --git a/client/service/test/src/lib.rs b/client/service/test/src/lib.rs index 749c83c6eeac7..2d63362daffba 100644 --- a/client/service/test/src/lib.rs +++ b/client/service/test/src/lib.rs @@ -29,8 +29,8 @@ use sc_network::{ use sc_service::{ client::Client, config::{BasePath, DatabaseSource, KeystoreConfig}, - ChainSpecExtension, Configuration, Error, GenericChainSpec, KeepBlocks, Role, RuntimeGenesis, - SpawnTaskHandle, TaskManager, + BlocksPruning, ChainSpecExtension, Configuration, Error, GenericChainSpec, Role, + RuntimeGenesis, SpawnTaskHandle, TaskManager, }; use sc_transaction_pool_api::TransactionPool; use sp_api::BlockId; @@ -234,7 +234,7 @@ fn node_config< state_cache_size: 16777216, state_cache_child_ratio: None, state_pruning: Default::default(), - keep_blocks: KeepBlocks::All, + blocks_pruning: BlocksPruning::All, chain_spec: Box::new((*spec).clone()), wasm_method: sc_service::config::WasmExecutionMethod::Interpreted, wasm_runtime_overrides: Default::default(), diff --git a/client/state-db/src/lib.rs b/client/state-db/src/lib.rs index d5cca9a342187..1c7140777e16e 100644 --- a/client/state-db/src/lib.rs +++ b/client/state-db/src/lib.rs @@ -227,7 +227,7 @@ pub enum PruningMode { impl PruningMode { /// Create a mode that keeps given number of blocks. - pub fn keep_blocks(n: u32) -> PruningMode { + pub fn blocks_pruning(n: u32) -> PruningMode { PruningMode::Constrained(Constraints { max_blocks: Some(n), max_mem: None }) } @@ -835,34 +835,34 @@ mod tests { #[test] fn pruning_mode_compatibility() { for (created, reopened, expected) in [ - (None, None, Ok(PruningMode::keep_blocks(256))), - (None, Some(PruningMode::keep_blocks(256)), Ok(PruningMode::keep_blocks(256))), - (None, Some(PruningMode::keep_blocks(128)), Ok(PruningMode::keep_blocks(128))), - (None, Some(PruningMode::keep_blocks(512)), Ok(PruningMode::keep_blocks(512))), + (None, None, Ok(PruningMode::blocks_pruning(256))), + (None, Some(PruningMode::blocks_pruning(256)), Ok(PruningMode::blocks_pruning(256))), + (None, Some(PruningMode::blocks_pruning(128)), Ok(PruningMode::blocks_pruning(128))), + (None, Some(PruningMode::blocks_pruning(512)), Ok(PruningMode::blocks_pruning(512))), (None, Some(PruningMode::ArchiveAll), Err(())), (None, Some(PruningMode::ArchiveCanonical), Err(())), - (Some(PruningMode::keep_blocks(256)), None, Ok(PruningMode::keep_blocks(256))), + (Some(PruningMode::blocks_pruning(256)), None, Ok(PruningMode::blocks_pruning(256))), ( - Some(PruningMode::keep_blocks(256)), - Some(PruningMode::keep_blocks(256)), - Ok(PruningMode::keep_blocks(256)), + Some(PruningMode::blocks_pruning(256)), + Some(PruningMode::blocks_pruning(256)), + Ok(PruningMode::blocks_pruning(256)), ), ( - Some(PruningMode::keep_blocks(256)), - Some(PruningMode::keep_blocks(128)), - Ok(PruningMode::keep_blocks(128)), + Some(PruningMode::blocks_pruning(256)), + Some(PruningMode::blocks_pruning(128)), + Ok(PruningMode::blocks_pruning(128)), ), ( - Some(PruningMode::keep_blocks(256)), - Some(PruningMode::keep_blocks(512)), - Ok(PruningMode::keep_blocks(512)), + Some(PruningMode::blocks_pruning(256)), + Some(PruningMode::blocks_pruning(512)), + Ok(PruningMode::blocks_pruning(512)), ), - (Some(PruningMode::keep_blocks(256)), Some(PruningMode::ArchiveAll), Err(())), - (Some(PruningMode::keep_blocks(256)), Some(PruningMode::ArchiveCanonical), Err(())), + (Some(PruningMode::blocks_pruning(256)), Some(PruningMode::ArchiveAll), Err(())), + (Some(PruningMode::blocks_pruning(256)), Some(PruningMode::ArchiveCanonical), Err(())), (Some(PruningMode::ArchiveAll), None, Ok(PruningMode::ArchiveAll)), - (Some(PruningMode::ArchiveAll), Some(PruningMode::keep_blocks(256)), Err(())), - (Some(PruningMode::ArchiveAll), Some(PruningMode::keep_blocks(128)), Err(())), - (Some(PruningMode::ArchiveAll), Some(PruningMode::keep_blocks(512)), Err(())), + (Some(PruningMode::ArchiveAll), Some(PruningMode::blocks_pruning(256)), Err(())), + (Some(PruningMode::ArchiveAll), Some(PruningMode::blocks_pruning(128)), Err(())), + (Some(PruningMode::ArchiveAll), Some(PruningMode::blocks_pruning(512)), Err(())), ( Some(PruningMode::ArchiveAll), Some(PruningMode::ArchiveAll), @@ -870,9 +870,9 @@ mod tests { ), (Some(PruningMode::ArchiveAll), Some(PruningMode::ArchiveCanonical), Err(())), (Some(PruningMode::ArchiveCanonical), None, Ok(PruningMode::ArchiveCanonical)), - (Some(PruningMode::ArchiveCanonical), Some(PruningMode::keep_blocks(256)), Err(())), - (Some(PruningMode::ArchiveCanonical), Some(PruningMode::keep_blocks(128)), Err(())), - (Some(PruningMode::ArchiveCanonical), Some(PruningMode::keep_blocks(512)), Err(())), + (Some(PruningMode::ArchiveCanonical), Some(PruningMode::blocks_pruning(256)), Err(())), + (Some(PruningMode::ArchiveCanonical), Some(PruningMode::blocks_pruning(128)), Err(())), + (Some(PruningMode::ArchiveCanonical), Some(PruningMode::blocks_pruning(512)), Err(())), (Some(PruningMode::ArchiveCanonical), Some(PruningMode::ArchiveAll), Err(())), ( Some(PruningMode::ArchiveCanonical), diff --git a/test-utils/client/src/lib.rs b/test-utils/client/src/lib.rs index 148f34246044d..3115be58425e6 100644 --- a/test-utils/client/src/lib.rs +++ b/test-utils/client/src/lib.rs @@ -95,14 +95,14 @@ impl } /// Create new `TestClientBuilder` with default backend and pruning window size - pub fn with_pruning_window(keep_blocks: u32) -> Self { - let backend = Arc::new(Backend::new_test(keep_blocks, 0)); + pub fn with_pruning_window(blocks_pruning: u32) -> Self { + let backend = Arc::new(Backend::new_test(blocks_pruning, 0)); Self::with_backend(backend) } /// Create new `TestClientBuilder` with default backend and storage chain mode - pub fn with_tx_storage(keep_blocks: u32) -> Self { - let backend = Arc::new(Backend::new_test_with_tx_storage(keep_blocks, 0)); + pub fn with_tx_storage(blocks_pruning: u32) -> Self { + let backend = Arc::new(Backend::new_test_with_tx_storage(blocks_pruning, 0)); Self::with_backend(backend) } } From ee44fd997a6537529a739285d95faa36f999251d Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Mon, 8 Aug 2022 17:46:57 +0800 Subject: [PATCH 465/484] Fix 16bit func_id (#11985) --- frame/contracts/src/chain_extension.rs | 4 ++-- frame/contracts/src/tests.rs | 29 +++++++++++++------------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/frame/contracts/src/chain_extension.rs b/frame/contracts/src/chain_extension.rs index 23242a2a542c1..57101668f794d 100644 --- a/frame/contracts/src/chain_extension.rs +++ b/frame/contracts/src/chain_extension.rs @@ -38,7 +38,7 @@ //! //! However, only extensions implementing [`RegisteredChainExtension`] can be put into a tuple. //! This is because the [`RegisteredChainExtension::ID`] is used to decide which of those extensions -//! should should be used when the contract calls a chain extensions. Extensions which are generally +//! should be used when the contract calls a chain extensions. Extensions which are generally //! useful should claim their `ID` with [the registry](https://github.com/paritytech/chainextension-registry) //! so that no collisions with other vendors will occur. //! @@ -215,7 +215,7 @@ where /// It returns the two least significant bytes of the `id` passed by a contract as the other /// two bytes represent the chain extension itself (the code which is calling this function). pub fn func_id(&self) -> u16 { - (self.inner.id & 0x00FF) as u16 + (self.inner.id & 0x0000FFFF) as u16 } /// The chain extension id within the `id` passed by a contract. diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index 0febfec929b6e..30417d8544489 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -158,14 +158,14 @@ impl ChainExtension for TestExtension { let func_id = env.func_id(); let id = env.ext_id() as u32 | func_id as u32; match func_id { - 0 => { + 0x8000 => { let mut env = env.buf_in_buf_out(); let input = env.read(8)?; env.write(&input, false, None)?; TEST_EXTENSION.with(|e| e.borrow_mut().last_seen_buffer = input); Ok(RetVal::Converging(id)) }, - 1 => { + 0x8001 => { let env = env.only_in(); TEST_EXTENSION.with(|e| { e.borrow_mut().last_seen_inputs = @@ -173,13 +173,13 @@ impl ChainExtension for TestExtension { }); Ok(RetVal::Converging(id)) }, - 2 => { + 0x8002 => { let mut env = env.buf_in_buf_out(); let weight = env.read(5)?[4].into(); env.charge_weight(weight)?; Ok(RetVal::Converging(id)) }, - 3 => Ok(RetVal::Diverging { flags: ReturnFlags::REVERT, data: vec![42, 99] }), + 0x8003 => Ok(RetVal::Diverging { flags: ReturnFlags::REVERT, data: vec![42, 99] }), _ => { panic!("Passed unknown id to test chain extension: {}", func_id); }, @@ -1646,21 +1646,22 @@ fn chain_extension_works() { ),); let addr = Contracts::contract_address(&ALICE, &hash, &[]); - // 0 = read input buffer and pass it through as output - let input: Vec = ExtensionInput { extension_id: 0, func_id: 0, extra: &[99] }.into(); + // 0x8000 = read input buffer and pass it through as output + let input: Vec = + ExtensionInput { extension_id: 0, func_id: 0x8000, extra: &[99] }.into(); let result = Contracts::bare_call(ALICE, addr.clone(), 0, GAS_LIMIT, None, input.clone(), false); assert_eq!(TestExtension::last_seen_buffer(), input); assert_eq!(result.result.unwrap().data, Bytes(input)); - // 1 = treat inputs as integer primitives and store the supplied integers + // 0x8001 = treat inputs as integer primitives and store the supplied integers Contracts::bare_call( ALICE, addr.clone(), 0, GAS_LIMIT, None, - ExtensionInput { extension_id: 0, func_id: 1, extra: &[] }.into(), + ExtensionInput { extension_id: 0, func_id: 0x8001, extra: &[] }.into(), false, ) .result @@ -1668,14 +1669,14 @@ fn chain_extension_works() { // those values passed in the fixture assert_eq!(TestExtension::last_seen_inputs(), (4, 4, 16, 12)); - // 2 = charge some extra weight (amount supplied in the fifth byte) + // 0x8002 = charge some extra weight (amount supplied in the fifth byte) let result = Contracts::bare_call( ALICE, addr.clone(), 0, GAS_LIMIT, None, - ExtensionInput { extension_id: 0, func_id: 2, extra: &[0] }.into(), + ExtensionInput { extension_id: 0, func_id: 0x8002, extra: &[0] }.into(), false, ); assert_ok!(result.result); @@ -1686,7 +1687,7 @@ fn chain_extension_works() { 0, GAS_LIMIT, None, - ExtensionInput { extension_id: 0, func_id: 2, extra: &[42] }.into(), + ExtensionInput { extension_id: 0, func_id: 0x8002, extra: &[42] }.into(), false, ); assert_ok!(result.result); @@ -1697,20 +1698,20 @@ fn chain_extension_works() { 0, GAS_LIMIT, None, - ExtensionInput { extension_id: 0, func_id: 2, extra: &[95] }.into(), + ExtensionInput { extension_id: 0, func_id: 0x8002, extra: &[95] }.into(), false, ); assert_ok!(result.result); assert_eq!(result.gas_consumed, gas_consumed + 95); - // 3 = diverging chain extension call that sets flags to 0x1 and returns a fixed buffer + // 0x8003 = diverging chain extension call that sets flags to 0x1 and returns a fixed buffer let result = Contracts::bare_call( ALICE, addr.clone(), 0, GAS_LIMIT, None, - ExtensionInput { extension_id: 0, func_id: 3, extra: &[] }.into(), + ExtensionInput { extension_id: 0, func_id: 0x8003, extra: &[] }.into(), false, ) .result From 8b6957f147f0d7548dfb9f822069a7c806f68236 Mon Sep 17 00:00:00 2001 From: yjh Date: Mon, 8 Aug 2022 17:49:00 +0800 Subject: [PATCH 466/484] chore: fix typos and sort trait impls (#11989) --- frame/contracts/src/wasm/code_cache.rs | 6 +++--- frame/contracts/src/wasm/env_def/mod.rs | 6 +++--- frame/contracts/src/wasm/prepare.rs | 2 +- frame/contracts/src/wasm/runtime.rs | 18 +++++++++--------- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/frame/contracts/src/wasm/code_cache.rs b/frame/contracts/src/wasm/code_cache.rs index 10de436bfb155..61826c7c323aa 100644 --- a/frame/contracts/src/wasm/code_cache.rs +++ b/frame/contracts/src/wasm/code_cache.rs @@ -215,7 +215,7 @@ impl Token for CodeToken { use self::CodeToken::*; // In case of `Load` we already covered the general costs of // calling the storage but still need to account for the actual size of the - // contract code. This is why we substract `T::*::(0)`. We need to do this at this + // contract code. This is why we subtract `T::*::(0)`. We need to do this at this // point because when charging the general weight for calling the contract we not know the // size of the contract. match *self { @@ -223,8 +223,8 @@ impl Token for CodeToken { Load(len) => { let computation = T::WeightInfo::call_with_code_per_byte(len) .saturating_sub(T::WeightInfo::call_with_code_per_byte(0)); - let bandwith = T::ContractAccessWeight::get().saturating_mul(len.into()); - computation.max(bandwith) + let bandwidth = T::ContractAccessWeight::get().saturating_mul(len.into()); + computation.max(bandwidth) }, } } diff --git a/frame/contracts/src/wasm/env_def/mod.rs b/frame/contracts/src/wasm/env_def/mod.rs index b4c5ffe81e7c1..c904445f8aea8 100644 --- a/frame/contracts/src/wasm/env_def/mod.rs +++ b/frame/contracts/src/wasm/env_def/mod.rs @@ -31,8 +31,8 @@ pub trait ConvertibleToWasm: Sized { fn from_typed_value(_: Value) -> Option; } impl ConvertibleToWasm for i32 { - type NativeType = i32; const VALUE_TYPE: ValueType = ValueType::I32; + type NativeType = i32; fn to_typed_value(self) -> Value { Value::I32(self) } @@ -41,8 +41,8 @@ impl ConvertibleToWasm for i32 { } } impl ConvertibleToWasm for u32 { - type NativeType = u32; const VALUE_TYPE: ValueType = ValueType::I32; + type NativeType = u32; fn to_typed_value(self) -> Value { Value::I32(self as i32) } @@ -54,8 +54,8 @@ impl ConvertibleToWasm for u32 { } } impl ConvertibleToWasm for u64 { - type NativeType = u64; const VALUE_TYPE: ValueType = ValueType::I64; + type NativeType = u64; fn to_typed_value(self) -> Value { Value::I64(self as i64) } diff --git a/frame/contracts/src/wasm/prepare.rs b/frame/contracts/src/wasm/prepare.rs index f6fff20de6b1a..aec5dc2db611d 100644 --- a/frame/contracts/src/wasm/prepare.rs +++ b/frame/contracts/src/wasm/prepare.rs @@ -540,7 +540,7 @@ mod tests { [seal0] nop(_ctx, _unused: u64) => { unreachable!(); }, - // new version of nop with other data type for argumebt + // new version of nop with other data type for argument [seal1] nop(_ctx, _unused: i32) => { unreachable!(); }, ); } diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index 3d5e62f2c1333..562b29fbaeb1b 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -1209,7 +1209,7 @@ define_env!(Env, , // // # Parameters // - // - flags: See [`CallFlags`] for a documenation of the supported flags. + // - flags: See [`CallFlags`] for a documentation of the supported flags. // - callee_ptr: a pointer to the address of the callee contract. // Should be decodable as an `T::AccountId`. Traps otherwise. // - gas: how much gas to devote to the execution. @@ -1753,7 +1753,7 @@ define_env!(Env, , // # Note // // The state rent functionality was removed. This is stub only exists for - // backwards compatiblity + // backwards compatibility [seal0] seal_restore_to( ctx, _dest_ptr: u32, @@ -1774,7 +1774,7 @@ define_env!(Env, , // # Note // // The state rent functionality was removed. This is stub only exists for - // backwards compatiblity + // backwards compatibility [seal1] seal_restore_to( ctx, _dest_ptr: u32, @@ -1857,7 +1857,7 @@ define_env!(Env, , // # Note // // The state rent functionality was removed. This is stub only exists for - // backwards compatiblity. + // backwards compatibility. [seal0] seal_set_rent_allowance(ctx, _value_ptr: u32, _value_len: u32) => { ctx.charge_gas(RuntimeCosts::DebugMessage)?; Ok(()) @@ -1868,7 +1868,7 @@ define_env!(Env, , // # Note // // The state rent functionality was removed. This is stub only exists for - // backwards compatiblity. + // backwards compatibility. [seal1] seal_set_rent_allowance(ctx, _value_ptr: u32) => { ctx.charge_gas(RuntimeCosts::DebugMessage)?; Ok(()) @@ -1879,7 +1879,7 @@ define_env!(Env, , // # Note // // The state rent functionality was removed. This is stub only exists for - // backwards compatiblity. + // backwards compatibility. [seal0] seal_rent_allowance(ctx, out_ptr: u32, out_len_ptr: u32) => { ctx.charge_gas(RuntimeCosts::Balance)?; let rent_allowance = >::max_value().encode(); @@ -2085,15 +2085,15 @@ define_env!(Env, , // // # Return Value // - // Returns `ReturnCode::Success` when the dispatchable was succesfully executed and - // returned `Ok`. When the dispatchable was exeuted but returned an error + // Returns `ReturnCode::Success` when the dispatchable was successfully executed and + // returned `Ok`. When the dispatchable was executed but returned an error // `ReturnCode::CallRuntimeReturnedError` is returned. The full error is not // provided because it is not guaranteed to be stable. // // # Comparison with `ChainExtension` // // Just as a chain extension this API allows the runtime to extend the functionality - // of contracts. While making use of this function is generelly easier it cannot be + // of contracts. While making use of this function is generally easier it cannot be // used in call cases. Consider writing a chain extension if you need to do perform // one of the following tasks: // From 2db8d3bb7b1c389b4aa640cc9c685bfd19dc0515 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Thei=C3=9Fen?= Date: Mon, 8 Aug 2022 15:32:00 +0100 Subject: [PATCH 467/484] contracts: Apply depth limit when decoding (#11991) --- frame/contracts/src/wasm/runtime.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index 562b29fbaeb1b..f21dc7c76e44c 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -26,7 +26,7 @@ use crate::{ }; use bitflags::bitflags; -use codec::{Decode, DecodeAll, Encode, MaxEncodedLen}; +use codec::{Decode, DecodeLimit, Encode, MaxEncodedLen}; use frame_support::{dispatch::DispatchError, ensure, traits::Get, weights::Weight}; use pallet_contracts_primitives::{ExecReturnValue, ReturnFlags}; use sp_core::{crypto::UncheckedFrom, Bytes}; @@ -36,6 +36,9 @@ use sp_sandbox::SandboxMemory; use sp_std::prelude::*; use wasm_instrument::parity_wasm::elements::ValueType; +/// The maximum nesting depth a contract can use when encoding types. +const MAX_DECODE_NESTING: u32 = 256; + /// Type of a storage key. #[allow(dead_code)] enum KeyType { @@ -575,7 +578,7 @@ where ptr: u32, ) -> Result { let buf = self.read_sandbox_memory(ptr, D::max_encoded_len() as u32)?; - let decoded = D::decode_all(&mut &buf[..]) + let decoded = D::decode_all_with_depth_limit(MAX_DECODE_NESTING, &mut &buf[..]) .map_err(|_| DispatchError::from(Error::::DecodingFailed))?; Ok(decoded) } @@ -597,7 +600,7 @@ where len: u32, ) -> Result { let buf = self.read_sandbox_memory(ptr, len)?; - let decoded = D::decode_all(&mut &buf[..]) + let decoded = D::decode_all_with_depth_limit(MAX_DECODE_NESTING, &mut &buf[..]) .map_err(|_| DispatchError::from(Error::::DecodingFailed))?; Ok(decoded) } From e2546aa4682b69272fa9142f40dc03b64d347874 Mon Sep 17 00:00:00 2001 From: Koute Date: Tue, 9 Aug 2022 20:04:28 +0900 Subject: [PATCH 468/484] Restore `wasmtime`'s default stack size limit to 1MB (#11993) * Restore `wasmtime`'s default stack size limit to 1MB * Add extra comments * Enforce different maximum call depth in release mode * Split the call depth limit in two --- client/executor/wasmtime/src/runtime.rs | 20 ++++--- client/executor/wasmtime/src/tests.rs | 79 +++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 7 deletions(-) diff --git a/client/executor/wasmtime/src/runtime.rs b/client/executor/wasmtime/src/runtime.rs index 2feb9d0eea502..871de4e2300d2 100644 --- a/client/executor/wasmtime/src/runtime.rs +++ b/client/executor/wasmtime/src/runtime.rs @@ -324,13 +324,19 @@ fn common_config(semantics: &Semantics) -> std::result::Result native_stack_max, + + // In `wasmtime` 0.35 the default stack size limit was changed from 1MB to 512KB. + // + // This broke at least one parachain which depended on the original 1MB limit, + // so here we restore it to what it was originally. + None => 1024 * 1024, + }; + + config + .max_wasm_stack(native_stack_max as usize) + .map_err(|e| WasmError::Other(format!("cannot set max wasm stack: {:#}", e)))?; config.parallel_compilation(semantics.parallel_compilation); diff --git a/client/executor/wasmtime/src/tests.rs b/client/executor/wasmtime/src/tests.rs index e0fd9fbce0c57..1ca5dde4c2b93 100644 --- a/client/executor/wasmtime/src/tests.rs +++ b/client/executor/wasmtime/src/tests.rs @@ -174,6 +174,85 @@ impl RuntimeBuilder { } } +fn deep_call_stack_wat(depth: usize) -> String { + format!( + r#" + (module + (memory $0 32) + (export "memory" (memory $0)) + (global (export "__heap_base") i32 (i32.const 0)) + (func (export "overflow") call 0) + + (func $overflow (param $0 i32) + (block $label$1 + (br_if $label$1 + (i32.ge_u + (local.get $0) + (i32.const {depth}) + ) + ) + (call $overflow + (i32.add + (local.get $0) + (i32.const 1) + ) + ) + ) + ) + + (func (export "main") + (param i32 i32) (result i64) + (call $overflow (i32.const 0)) + (i64.const 0) + ) + ) + "# + ) +} + +// These two tests ensure that the `wasmtime`'s stack size limit and the amount of +// stack space used by a single stack frame doesn't suddenly change without us noticing. +// +// If they do (e.g. because we've pulled in a new version of `wasmtime`) we want to know +// that it did, regardless of how small the change was. +// +// If these tests starting failing it doesn't necessarily mean that something is broken; +// what it means is that one (or multiple) of the following has to be done: +// a) the tests may need to be updated for the new call depth, +// b) the stack limit may need to be changed to maintain backwards compatibility, +// c) the root cause of the new call depth limit determined, and potentially fixed, +// d) the new call depth limit may need to be validated to ensure it doesn't prevent any +// existing chain from syncing (if it was effectively decreased) + +// We need two limits here since depending on whether the code is compiled in debug +// or in release mode the maximum call depth is slightly different. +const CALL_DEPTH_LOWER_LIMIT: usize = 65478; +const CALL_DEPTH_UPPER_LIMIT: usize = 65514; + +test_wasm_execution!(test_consume_under_1mb_of_stack_does_not_trap); +fn test_consume_under_1mb_of_stack_does_not_trap(instantiation_strategy: InstantiationStrategy) { + let wat = deep_call_stack_wat(CALL_DEPTH_LOWER_LIMIT); + let mut builder = RuntimeBuilder::new(instantiation_strategy).use_wat(wat); + let runtime = builder.build(); + let mut instance = runtime.new_instance().expect("failed to instantiate a runtime"); + instance.call_export("main", &[]).unwrap(); +} + +test_wasm_execution!(test_consume_over_1mb_of_stack_does_trap); +fn test_consume_over_1mb_of_stack_does_trap(instantiation_strategy: InstantiationStrategy) { + let wat = deep_call_stack_wat(CALL_DEPTH_UPPER_LIMIT + 1); + let mut builder = RuntimeBuilder::new(instantiation_strategy).use_wat(wat); + let runtime = builder.build(); + let mut instance = runtime.new_instance().expect("failed to instantiate a runtime"); + match instance.call_export("main", &[]).unwrap_err() { + Error::AbortedDueToTrap(error) => { + let expected = "wasm trap: call stack exhausted"; + assert_eq!(error.message, expected); + }, + error => panic!("unexpected error: {:?}", error), + } +} + test_wasm_execution!(test_nan_canonicalization); fn test_nan_canonicalization(instantiation_strategy: InstantiationStrategy) { let mut builder = RuntimeBuilder::new(instantiation_strategy).canonicalize_nans(true); From 759c11bd0215aa4205db3a0f36199f0b5bacd92f Mon Sep 17 00:00:00 2001 From: Nazar Mokrynskyi Date: Tue, 9 Aug 2022 21:28:32 +0300 Subject: [PATCH 469/484] Network sync refactoring (part 6) (#11940) * Extract `NetworkKVProvider` trait in `sc-authority-discovery` and remove unnecessary dependency * Extract `NetworkSyncForkRequest` trait in `sc-finality-grandpa` * Relax requirements on `SyncOracle` trait, remove extra native methods from `NetworkService` that are already provided by trait impls * Move `NetworkSigner` trait from `sc-authority-discovery` into `sc-network-common` and de-duplicate methods on `NetworkService` * Move `NetworkKVProvider` trait from `sc-authority-discovery` into `sc-network-common` and de-duplicate methods on `NetworkService` * Minimize `sc-authority-discovery` dependency on `sc-network` * Move `NetworkSyncForkRequest` trait from `sc-finality-grandpa` to `sc-network-common` and de-duplicate methods in `NetworkService` * Extract `NetworkStatusProvider` trait and de-duplicate methods on `NetworkService` * Extract `NetworkPeers` trait and de-duplicate methods on `NetworkService` * Extract `NetworkEventStream` trait and de-duplicate methods on `NetworkService` * Move more methods from `NetworkService` into `NetworkPeers` trait * Move `NetworkStateInfo` trait into `sc-network-common` * Extract `NetworkNotification` trait and de-duplicate methods on `NetworkService` * Extract `NetworkRequest` trait and de-duplicate methods on `NetworkService` * Remove `NetworkService::local_peer_id()`, it is already provided by `NetworkStateInfo` impl * Extract `NetworkTransaction` trait and de-duplicate methods on `NetworkService` * Extract `NetworkBlock` trait and de-duplicate methods on `NetworkService` * Remove dependencies on `NetworkService` from most of the methods of `sc-service` * Address simple review comments --- Cargo.lock | 11 +- bin/node/cli/Cargo.toml | 1 + bin/node/cli/src/service.rs | 3 +- client/authority-discovery/Cargo.toml | 2 +- client/authority-discovery/src/lib.rs | 3 +- client/authority-discovery/src/service.rs | 2 +- client/authority-discovery/src/tests.rs | 4 +- client/authority-discovery/src/worker.rs | 76 +- .../src/worker/addr_cache.rs | 7 +- .../src/worker/schema/tests.rs | 5 +- .../authority-discovery/src/worker/tests.rs | 63 +- client/consensus/pow/src/lib.rs | 2 +- client/consensus/slots/src/lib.rs | 2 +- .../src/communication/gossip.rs | 3 +- .../finality-grandpa/src/communication/mod.rs | 45 +- .../src/communication/tests.rs | 143 +++- client/informant/Cargo.toml | 2 +- client/informant/src/display.rs | 8 +- client/informant/src/lib.rs | 7 +- client/network-gossip/Cargo.toml | 1 + client/network-gossip/src/bridge.rs | 138 ++- client/network-gossip/src/lib.rs | 79 +- client/network-gossip/src/state_machine.rs | 126 ++- client/network-gossip/src/validator.rs | 3 +- client/network/common/Cargo.toml | 3 + client/network/common/src/lib.rs | 2 + client/network/common/src/protocol.rs | 19 + .../{ => common}/src/protocol/event.rs | 4 +- .../network/common/src/request_responses.rs | 39 +- client/network/common/src/service.rs | 660 +++++++++++++++ .../{ => common}/src/service/signature.rs | 5 +- client/network/src/behaviour.rs | 12 +- client/network/src/lib.rs | 58 +- client/network/src/protocol.rs | 3 +- client/network/src/request_responses.rs | 41 +- client/network/src/service.rs | 794 +++++++----------- client/network/src/service/out_events.rs | 3 +- client/network/src/service/tests.rs | 59 +- client/network/src/transactions.rs | 10 +- client/network/test/src/lib.rs | 16 +- client/offchain/Cargo.toml | 1 + client/offchain/src/api.rs | 79 +- client/offchain/src/lib.rs | 104 ++- client/service/src/builder.rs | 58 +- client/service/src/lib.rs | 2 + client/service/src/metrics.rs | 8 +- client/service/test/Cargo.toml | 1 + client/service/test/src/lib.rs | 9 +- primitives/consensus/common/src/lib.rs | 18 +- 49 files changed, 1802 insertions(+), 942 deletions(-) create mode 100644 client/network/common/src/protocol.rs rename client/network/{ => common}/src/protocol/event.rs (96%) create mode 100644 client/network/common/src/service.rs rename client/network/{ => common}/src/service/signature.rs (96%) diff --git a/Cargo.lock b/Cargo.lock index 703eeffce54a1..38063e8060835 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4686,6 +4686,7 @@ dependencies = [ "sc-finality-grandpa", "sc-keystore", "sc-network", + "sc-network-common", "sc-rpc", "sc-service", "sc-service-test", @@ -7702,7 +7703,6 @@ dependencies = [ name = "sc-authority-discovery" version = "0.10.0-dev" dependencies = [ - "async-trait", "futures", "futures-timer", "ip_network", @@ -7715,6 +7715,7 @@ dependencies = [ "rand 0.7.3", "sc-client-api", "sc-network", + "sc-network-common", "sp-api", "sp-authority-discovery", "sp-blockchain", @@ -8316,7 +8317,7 @@ dependencies = [ "log", "parity-util-mem", "sc-client-api", - "sc-network", + "sc-network-common", "sc-transaction-pool-api", "sp-blockchain", "sp-runtime", @@ -8398,7 +8399,9 @@ dependencies = [ name = "sc-network-common" version = "0.10.0-dev" dependencies = [ + "async-trait", "bitflags", + "bytes", "futures", "libp2p", "parity-scale-codec", @@ -8409,6 +8412,7 @@ dependencies = [ "sp-consensus", "sp-finality-grandpa", "sp-runtime", + "thiserror", ] [[package]] @@ -8424,6 +8428,7 @@ dependencies = [ "lru", "quickcheck", "sc-network", + "sc-network-common", "sp-runtime", "substrate-prometheus-endpoint", "substrate-test-runtime-client", @@ -8533,6 +8538,7 @@ dependencies = [ "sc-client-api", "sc-client-db", "sc-network", + "sc-network-common", "sc-transaction-pool", "sc-transaction-pool-api", "sc-utils", @@ -8741,6 +8747,7 @@ dependencies = [ "sc-consensus", "sc-executor", "sc-network", + "sc-network-common", "sc-service", "sc-transaction-pool-api", "sp-api", diff --git a/bin/node/cli/Cargo.toml b/bin/node/cli/Cargo.toml index fe7c5332e2b45..89ca3ebb45576 100644 --- a/bin/node/cli/Cargo.toml +++ b/bin/node/cli/Cargo.toml @@ -66,6 +66,7 @@ sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/commo sc-transaction-pool = { version = "4.0.0-dev", path = "../../../client/transaction-pool" } sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../../client/transaction-pool/api" } sc-network = { version = "0.10.0-dev", path = "../../../client/network" } +sc-network-common = { version = "0.10.0-dev", path = "../../../client/network/common" } sc-consensus-slots = { version = "0.10.0-dev", path = "../../../client/consensus/slots" } sc-consensus-babe = { version = "0.10.0-dev", path = "../../../client/consensus/babe" } sc-consensus-uncles = { version = "0.10.0-dev", path = "../../../client/consensus/uncles" } diff --git a/bin/node/cli/src/service.rs b/bin/node/cli/src/service.rs index e0644f462cf20..b20a3ac59a96a 100644 --- a/bin/node/cli/src/service.rs +++ b/bin/node/cli/src/service.rs @@ -29,7 +29,8 @@ use node_primitives::Block; use sc_client_api::{BlockBackend, ExecutorProvider}; use sc_consensus_babe::{self, SlotProportion}; use sc_executor::NativeElseWasmExecutor; -use sc_network::{Event, NetworkService}; +use sc_network::NetworkService; +use sc_network_common::{protocol::event::Event, service::NetworkEventStream}; use sc_service::{config::Configuration, error::Error as ServiceError, RpcHandlers, TaskManager}; use sc_telemetry::{Telemetry, TelemetryWorker}; use sp_api::ProvideRuntimeApi; diff --git a/client/authority-discovery/Cargo.toml b/client/authority-discovery/Cargo.toml index 012e6096aab80..544df4c8d4812 100644 --- a/client/authority-discovery/Cargo.toml +++ b/client/authority-discovery/Cargo.toml @@ -17,7 +17,6 @@ targets = ["x86_64-unknown-linux-gnu"] prost-build = "0.10" [dependencies] -async-trait = "0.1" codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } futures = "0.3.21" futures-timer = "3.0.1" @@ -29,6 +28,7 @@ rand = "0.7.2" thiserror = "1.0" prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } sc-client-api = { version = "4.0.0-dev", path = "../api" } +sc-network-common = { version = "0.10.0-dev", path = "../network/common" } sc-network = { version = "0.10.0-dev", path = "../network" } sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } sp-authority-discovery = { version = "4.0.0-dev", path = "../../primitives/authority-discovery" } diff --git a/client/authority-discovery/src/lib.rs b/client/authority-discovery/src/lib.rs index 8522da9984a6f..f0ef374551617 100644 --- a/client/authority-discovery/src/lib.rs +++ b/client/authority-discovery/src/lib.rs @@ -39,8 +39,9 @@ use futures::{ Stream, }; +use libp2p::{Multiaddr, PeerId}; use sc_client_api::blockchain::HeaderBackend; -use sc_network::{DhtEvent, Multiaddr, PeerId}; +use sc_network_common::protocol::event::DhtEvent; use sp_api::ProvideRuntimeApi; use sp_authority_discovery::{AuthorityDiscoveryApi, AuthorityId}; use sp_runtime::traits::Block as BlockT; diff --git a/client/authority-discovery/src/service.rs b/client/authority-discovery/src/service.rs index c240e5d0c2287..df09b6ea43216 100644 --- a/client/authority-discovery/src/service.rs +++ b/client/authority-discovery/src/service.rs @@ -25,7 +25,7 @@ use futures::{ SinkExt, }; -use sc_network::{Multiaddr, PeerId}; +use libp2p::{Multiaddr, PeerId}; use sp_authority_discovery::AuthorityId; /// Service to interact with the [`crate::Worker`]. diff --git a/client/authority-discovery/src/tests.rs b/client/authority-discovery/src/tests.rs index e9f94b6a186db..334b2638ca58c 100644 --- a/client/authority-discovery/src/tests.rs +++ b/client/authority-discovery/src/tests.rs @@ -87,12 +87,12 @@ fn get_addresses_and_authority_id() { fn cryptos_are_compatible() { use sp_core::crypto::Pair; - let libp2p_secret = sc_network::Keypair::generate_ed25519(); + let libp2p_secret = libp2p::identity::Keypair::generate_ed25519(); let libp2p_public = libp2p_secret.public(); let sp_core_secret = { let libp2p_ed_secret = match libp2p_secret.clone() { - sc_network::Keypair::Ed25519(x) => x, + libp2p::identity::Keypair::Ed25519(x) => x, _ => panic!("generate_ed25519 should have generated an Ed25519 key ¯\\_(ツ)_/¯"), }; sp_core::ed25519::Pair::from_seed_slice(&libp2p_ed_secret.secret().as_ref()).unwrap() diff --git a/client/authority-discovery/src/worker.rs b/client/authority-discovery/src/worker.rs index 87cc72ba7a69c..f13a1cf33581b 100644 --- a/client/authority-discovery/src/worker.rs +++ b/client/authority-discovery/src/worker.rs @@ -32,19 +32,22 @@ use std::{ use futures::{channel::mpsc, future, stream::Fuse, FutureExt, Stream, StreamExt}; use addr_cache::AddrCache; -use async_trait::async_trait; use codec::Decode; use ip_network::IpNetwork; use libp2p::{ core::multiaddr, multihash::{Multihash, MultihashDigest}, + Multiaddr, PeerId, }; use log::{debug, error, log_enabled}; use prometheus_endpoint::{register, Counter, CounterVec, Gauge, Opts, U64}; use prost::Message; use rand::{seq::SliceRandom, thread_rng}; use sc_client_api::blockchain::HeaderBackend; -use sc_network::{DhtEvent, ExHashT, Multiaddr, NetworkStateInfo, PeerId}; +use sc_network_common::{ + protocol::event::DhtEvent, + service::{KademliaKey, NetworkDHTProvider, NetworkSigner, NetworkStateInfo, Signature}, +}; use sp_api::ProvideRuntimeApi; use sp_authority_discovery::{ AuthorityDiscoveryApi, AuthorityId, AuthorityPair, AuthoritySignature, @@ -136,7 +139,7 @@ pub struct Worker { /// Queue of throttled lookups pending to be passed to the network. pending_lookups: Vec, /// Set of in-flight lookups. - in_flight_lookups: HashMap, + in_flight_lookups: HashMap, addr_cache: addr_cache::AddrCache, @@ -464,10 +467,7 @@ where } } - fn handle_dht_value_found_event( - &mut self, - values: Vec<(sc_network::KademliaKey, Vec)>, - ) -> Result<()> { + fn handle_dht_value_found_event(&mut self, values: Vec<(KademliaKey, Vec)>) -> Result<()> { // Ensure `values` is not empty and all its keys equal. let remote_key = single(values.iter().map(|(key, _)| key.clone())) .map_err(|_| Error::ReceivingDhtValueFoundEventWithDifferentKeys)? @@ -523,11 +523,11 @@ where // properly signed by the owner of the PeerId if let Some(peer_signature) = peer_signature { - let public_key = - sc_network::PublicKey::from_protobuf_encoding(&peer_signature.public_key) - .map_err(Error::ParsingLibp2pIdentity)?; - let signature = - sc_network::Signature { public_key, bytes: peer_signature.signature }; + let public_key = libp2p::identity::PublicKey::from_protobuf_encoding( + &peer_signature.public_key, + ) + .map_err(Error::ParsingLibp2pIdentity)?; + let signature = Signature { public_key, bytes: peer_signature.signature }; if !signature.verify(record, &remote_peer_id) { return Err(Error::VerifyingDhtPayload) @@ -590,55 +590,15 @@ where } } -pub trait NetworkSigner { - /// Sign a message in the name of `self.local_peer_id()` - fn sign_with_local_identity( - &self, - msg: impl AsRef<[u8]>, - ) -> std::result::Result; -} - /// NetworkProvider provides [`Worker`] with all necessary hooks into the /// underlying Substrate networking. Using this trait abstraction instead of -/// [`sc_network::NetworkService`] directly is necessary to unit test [`Worker`]. -#[async_trait] -pub trait NetworkProvider: NetworkStateInfo + NetworkSigner { - /// Start putting a value in the Dht. - fn put_value(&self, key: sc_network::KademliaKey, value: Vec); - - /// Start getting a value from the Dht. - fn get_value(&self, key: &sc_network::KademliaKey); -} +/// `sc_network::NetworkService` directly is necessary to unit test [`Worker`]. +pub trait NetworkProvider: NetworkDHTProvider + NetworkStateInfo + NetworkSigner {} -impl NetworkSigner for sc_network::NetworkService -where - B: BlockT + 'static, - H: ExHashT, -{ - fn sign_with_local_identity( - &self, - msg: impl AsRef<[u8]>, - ) -> std::result::Result { - self.sign_with_local_identity(msg) - } -} - -#[async_trait::async_trait] -impl NetworkProvider for sc_network::NetworkService -where - B: BlockT + 'static, - H: ExHashT, -{ - fn put_value(&self, key: sc_network::KademliaKey, value: Vec) { - self.put_value(key, value) - } - fn get_value(&self, key: &sc_network::KademliaKey) { - self.get_value(key) - } -} +impl NetworkProvider for T where T: NetworkDHTProvider + NetworkStateInfo + NetworkSigner {} -fn hash_authority_id(id: &[u8]) -> sc_network::KademliaKey { - sc_network::KademliaKey::new(&libp2p::multihash::Code::Sha2_256.digest(id).digest()) +fn hash_authority_id(id: &[u8]) -> KademliaKey { + KademliaKey::new(&libp2p::multihash::Code::Sha2_256.digest(id).digest()) } // Makes sure all values are the same and returns it @@ -685,7 +645,7 @@ async fn sign_record_with_authority_ids( peer_signature: Option, key_store: &dyn CryptoStore, keys: Vec, -) -> Result)>> { +) -> Result)>> { let signatures = key_store .sign_with_all(key_types::AUTHORITY_DISCOVERY, keys.clone(), &serialized_record) .await diff --git a/client/authority-discovery/src/worker/addr_cache.rs b/client/authority-discovery/src/worker/addr_cache.rs index f768b9c4e66a7..19bbbf0b62e7e 100644 --- a/client/authority-discovery/src/worker/addr_cache.rs +++ b/client/authority-discovery/src/worker/addr_cache.rs @@ -16,9 +16,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use libp2p::core::multiaddr::{Multiaddr, Protocol}; - -use sc_network::PeerId; +use libp2p::{ + core::multiaddr::{Multiaddr, Protocol}, + PeerId, +}; use sp_authority_discovery::AuthorityId; use std::collections::{hash_map::Entry, HashMap, HashSet}; diff --git a/client/authority-discovery/src/worker/schema/tests.rs b/client/authority-discovery/src/worker/schema/tests.rs index b85a4ce37447d..60147d6762e50 100644 --- a/client/authority-discovery/src/worker/schema/tests.rs +++ b/client/authority-discovery/src/worker/schema/tests.rs @@ -21,9 +21,8 @@ mod schema_v1 { } use super::*; -use libp2p::multiaddr::Multiaddr; +use libp2p::{multiaddr::Multiaddr, PeerId}; use prost::Message; -use sc_network::PeerId; #[test] fn v2_decodes_v1() { @@ -56,7 +55,7 @@ fn v2_decodes_v1() { #[test] fn v1_decodes_v2() { - let peer_secret = sc_network::Keypair::generate_ed25519(); + let peer_secret = libp2p::identity::Keypair::generate_ed25519(); let peer_public = peer_secret.public(); let peer_id = peer_public.to_peer_id(); let multiaddress: Multiaddr = diff --git a/client/authority-discovery/src/worker/tests.rs b/client/authority-discovery/src/worker/tests.rs index a1a699bc30dd2..5a60d3353db52 100644 --- a/client/authority-discovery/src/worker/tests.rs +++ b/client/authority-discovery/src/worker/tests.rs @@ -22,7 +22,6 @@ use std::{ task::Poll, }; -use async_trait::async_trait; use futures::{ channel::mpsc::{self, channel}, executor::{block_on, LocalPool}, @@ -30,9 +29,10 @@ use futures::{ sink::SinkExt, task::LocalSpawn, }; -use libp2p::{core::multiaddr, PeerId}; +use libp2p::{core::multiaddr, identity::Keypair, PeerId}; use prometheus_endpoint::prometheus::default_registry; +use sc_network_common::service::{KademliaKey, Signature, SigningError}; use sp_api::{ApiRef, ProvideRuntimeApi}; use sp_keystore::{testing::KeyStore, CryptoStore}; use sp_runtime::traits::{Block as BlockT, NumberFor, Zero}; @@ -111,18 +111,18 @@ sp_api::mock_impl_runtime_apis! { #[derive(Debug)] pub enum TestNetworkEvent { - GetCalled(sc_network::KademliaKey), - PutCalled(sc_network::KademliaKey, Vec), + GetCalled(KademliaKey), + PutCalled(KademliaKey, Vec), } pub struct TestNetwork { peer_id: PeerId, - identity: sc_network::Keypair, + identity: Keypair, external_addresses: Vec, // Whenever functions on `TestNetwork` are called, the function arguments are added to the // vectors below. - pub put_value_call: Arc)>>>, - pub get_value_call: Arc>>, + pub put_value_call: Arc)>>>, + pub get_value_call: Arc>>, event_sender: mpsc::UnboundedSender, event_receiver: Option>, } @@ -136,7 +136,7 @@ impl TestNetwork { impl Default for TestNetwork { fn default() -> Self { let (tx, rx) = mpsc::unbounded(); - let identity = sc_network::Keypair::generate_ed25519(); + let identity = Keypair::generate_ed25519(); TestNetwork { peer_id: identity.public().to_peer_id(), identity, @@ -153,21 +153,20 @@ impl NetworkSigner for TestNetwork { fn sign_with_local_identity( &self, msg: impl AsRef<[u8]>, - ) -> std::result::Result { - sc_network::Signature::sign_message(msg, &self.identity) + ) -> std::result::Result { + Signature::sign_message(msg, &self.identity) } } -#[async_trait] -impl NetworkProvider for TestNetwork { - fn put_value(&self, key: sc_network::KademliaKey, value: Vec) { +impl NetworkDHTProvider for TestNetwork { + fn put_value(&self, key: KademliaKey, value: Vec) { self.put_value_call.lock().unwrap().push((key.clone(), value.clone())); self.event_sender .clone() .unbounded_send(TestNetworkEvent::PutCalled(key, value)) .unwrap(); } - fn get_value(&self, key: &sc_network::KademliaKey) { + fn get_value(&self, key: &KademliaKey) { self.get_value_call.lock().unwrap().push(key.clone()); self.event_sender .clone() @@ -186,12 +185,16 @@ impl NetworkStateInfo for TestNetwork { } } -impl NetworkSigner for sc_network::Keypair { +struct TestSigner<'a> { + keypair: &'a Keypair, +} + +impl<'a> NetworkSigner for TestSigner<'a> { fn sign_with_local_identity( &self, msg: impl AsRef<[u8]>, - ) -> std::result::Result { - sc_network::Signature::sign_message(msg, self) + ) -> std::result::Result { + Signature::sign_message(msg, self.keypair) } } @@ -200,7 +203,7 @@ async fn build_dht_event( public_key: AuthorityId, key_store: &dyn CryptoStore, network: Option<&Signer>, -) -> Vec<(sc_network::KademliaKey, Vec)> { +) -> Vec<(KademliaKey, Vec)> { let serialized_record = serialize_authority_record(serialize_addresses(addresses.into_iter())).unwrap(); @@ -313,7 +316,7 @@ fn publish_discover_cycle() { let dht_event = { let (key, value) = network.put_value_call.lock().unwrap().pop().unwrap(); - sc_network::DhtEvent::ValueFound(vec![(key, value)]) + DhtEvent::ValueFound(vec![(key, value)]) }; // Node B discovering node A's address. @@ -469,7 +472,7 @@ fn dont_stop_polling_dht_event_stream_after_bogus_event() { None, ) .await; - sc_network::DhtEvent::ValueFound(kv_pairs) + DhtEvent::ValueFound(kv_pairs) }; dht_event_tx.send(dht_event).await.expect("Channel has capacity of 1."); @@ -487,7 +490,7 @@ fn dont_stop_polling_dht_event_stream_after_bogus_event() { struct DhtValueFoundTester { pub remote_key_store: KeyStore, pub remote_authority_public: sp_core::sr25519::Public, - pub remote_node_key: sc_network::Keypair, + pub remote_node_key: Keypair, pub local_worker: Option< Worker< TestApi, @@ -496,7 +499,7 @@ struct DhtValueFoundTester { sp_runtime::generic::Header, substrate_test_runtime_client::runtime::Extrinsic, >, - std::pin::Pin>>, + std::pin::Pin>>, >, >, } @@ -508,7 +511,7 @@ impl DhtValueFoundTester { block_on(remote_key_store.sr25519_generate_new(key_types::AUTHORITY_DISCOVERY, None)) .unwrap(); - let remote_node_key = sc_network::Keypair::generate_ed25519(); + let remote_node_key = Keypair::generate_ed25519(); Self { remote_key_store, remote_authority_public, remote_node_key, local_worker: None } } @@ -523,7 +526,7 @@ impl DhtValueFoundTester { fn process_value_found( &mut self, strict_record_validation: bool, - values: Vec<(sc_network::KademliaKey, Vec)>, + values: Vec<(KademliaKey, Vec)>, ) -> Option<&HashSet> { let (_dht_event_tx, dht_event_rx) = channel(1); let local_test_api = @@ -583,7 +586,7 @@ fn strict_accept_address_with_peer_signature() { vec![addr.clone()], tester.remote_authority_public.clone().into(), &tester.remote_key_store, - Some(&tester.remote_node_key), + Some(&TestSigner { keypair: &tester.remote_node_key }), )); let cached_remote_addresses = tester.process_value_found(true, kv_pairs); @@ -598,12 +601,12 @@ fn strict_accept_address_with_peer_signature() { #[test] fn reject_address_with_rogue_peer_signature() { let mut tester = DhtValueFoundTester::new(); - let rogue_remote_node_key = sc_network::Keypair::generate_ed25519(); + let rogue_remote_node_key = Keypair::generate_ed25519(); let kv_pairs = block_on(build_dht_event( vec![tester.multiaddr_with_peer_id(1)], tester.remote_authority_public.clone().into(), &tester.remote_key_store, - Some(&rogue_remote_node_key), + Some(&TestSigner { keypair: &rogue_remote_node_key }), )); let cached_remote_addresses = tester.process_value_found(false, kv_pairs); @@ -621,7 +624,7 @@ fn reject_address_with_invalid_peer_signature() { vec![tester.multiaddr_with_peer_id(1)], tester.remote_authority_public.clone().into(), &tester.remote_key_store, - Some(&tester.remote_node_key), + Some(&TestSigner { keypair: &tester.remote_node_key }), )); // tamper with the signature let mut record = schema::SignedAuthorityRecord::decode(kv_pairs[0].1.as_slice()).unwrap(); @@ -808,7 +811,7 @@ fn lookup_throttling() { None, ) .await; - sc_network::DhtEvent::ValueFound(kv_pairs) + DhtEvent::ValueFound(kv_pairs) }; dht_event_tx.send(dht_event).await.expect("Channel has capacity of 1."); @@ -822,7 +825,7 @@ fn lookup_throttling() { // Make second one fail. let remote_hash = network.get_value_call.lock().unwrap().pop().unwrap(); - let dht_event = sc_network::DhtEvent::ValueNotFound(remote_hash); + let dht_event = DhtEvent::ValueNotFound(remote_hash); dht_event_tx.send(dht_event).await.expect("Channel has capacity of 1."); // Assert worker to trigger another lookup. diff --git a/client/consensus/pow/src/lib.rs b/client/consensus/pow/src/lib.rs index 6f9ee6f864ad8..f63e453a48026 100644 --- a/client/consensus/pow/src/lib.rs +++ b/client/consensus/pow/src/lib.rs @@ -518,7 +518,7 @@ pub fn start_mining_worker( select_chain: S, algorithm: Algorithm, mut env: E, - mut sync_oracle: SO, + sync_oracle: SO, justification_sync_link: L, pre_runtime: Option>, create_inherent_data_providers: CIDP, diff --git a/client/consensus/slots/src/lib.rs b/client/consensus/slots/src/lib.rs index 7c3de13444c1a..39b40a32f18ca 100644 --- a/client/consensus/slots/src/lib.rs +++ b/client/consensus/slots/src/lib.rs @@ -490,7 +490,7 @@ pub async fn start_slot_worker( slot_duration: SlotDuration, client: C, mut worker: W, - mut sync_oracle: SO, + sync_oracle: SO, create_inherent_data_providers: CIDP, can_author_with: CAW, ) where diff --git a/client/finality-grandpa/src/communication/gossip.rs b/client/finality-grandpa/src/communication/gossip.rs index 65d7dfb783aa3..9e9f2fb98b0d1 100644 --- a/client/finality-grandpa/src/communication/gossip.rs +++ b/client/finality-grandpa/src/communication/gossip.rs @@ -89,7 +89,8 @@ use log::{debug, trace}; use parity_scale_codec::{Decode, Encode}; use prometheus_endpoint::{register, CounterVec, Opts, PrometheusError, Registry, U64}; use rand::seq::SliceRandom; -use sc_network::{ObservedRole, PeerId, ReputationChange}; +use sc_network::{PeerId, ReputationChange}; +use sc_network_common::protocol::event::ObservedRole; use sc_network_gossip::{MessageIntent, ValidatorContext}; use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_DEBUG}; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; diff --git a/client/finality-grandpa/src/communication/mod.rs b/client/finality-grandpa/src/communication/mod.rs index 378501cffdd62..7a47ebe214950 100644 --- a/client/finality-grandpa/src/communication/mod.rs +++ b/client/finality-grandpa/src/communication/mod.rs @@ -45,7 +45,7 @@ use finality_grandpa::{ Message::{Precommit, Prevote, PrimaryPropose}, }; use parity_scale_codec::{Decode, Encode}; -use sc_network::{NetworkService, ReputationChange}; +use sc_network::ReputationChange; use sc_network_gossip::{GossipEngine, Network as GossipNetwork}; use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_DEBUG, CONSENSUS_INFO}; use sp_keystore::SyncCryptoStorePtr; @@ -58,6 +58,7 @@ use crate::{ use gossip::{ FullCatchUpMessage, FullCommitMessage, GossipMessage, GossipValidator, PeerReport, VoteMessage, }; +use sc_network_common::service::{NetworkBlock, NetworkSyncForkRequest}; use sc_utils::mpsc::TracingUnboundedReceiver; use sp_finality_grandpa::{AuthorityId, AuthoritySignature, RoundNumber, SetId as SetIdNumber}; @@ -156,34 +157,26 @@ const TELEMETRY_VOTERS_LIMIT: usize = 10; /// /// Something that provides both the capabilities needed for the `gossip_network::Network` trait as /// well as the ability to set a fork sync request for a particular block. -pub trait Network: GossipNetwork + Clone + Send + 'static { - /// Notifies the sync service to try and sync the given block from the given - /// peers. - /// - /// If the given vector of peers is empty then the underlying implementation - /// should make a best effort to fetch the block from any peers it is - /// connected to (NOTE: this assumption will change in the future #3629). - fn set_sync_fork_request( - &self, - peers: Vec, - hash: Block::Hash, - number: NumberFor, - ); +pub trait Network: + NetworkSyncForkRequest> + + NetworkBlock> + + GossipNetwork + + Clone + + Send + + 'static +{ } -impl Network for Arc> +impl Network for T where - B: BlockT, - H: sc_network::ExHashT, + Block: BlockT, + T: NetworkSyncForkRequest> + + NetworkBlock> + + GossipNetwork + + Clone + + Send + + 'static, { - fn set_sync_fork_request( - &self, - peers: Vec, - hash: B::Hash, - number: NumberFor, - ) { - NetworkService::set_sync_fork_request(self, peers, hash, number) - } } /// Create a unique topic for a round and set-id combo. @@ -467,7 +460,7 @@ impl> NetworkBridge { hash: B::Hash, number: NumberFor, ) { - Network::set_sync_fork_request(&self.service, peers, hash, number) + self.service.set_sync_fork_request(peers, hash, number) } } diff --git a/client/finality-grandpa/src/communication/tests.rs b/client/finality-grandpa/src/communication/tests.rs index 0ec5092a2a047..59935cef6a095 100644 --- a/client/finality-grandpa/src/communication/tests.rs +++ b/client/finality-grandpa/src/communication/tests.rs @@ -25,7 +25,14 @@ use super::{ use crate::{communication::grandpa_protocol_name, environment::SharedVoterSetState}; use futures::prelude::*; use parity_scale_codec::Encode; -use sc_network::{config::Role, Event as NetworkEvent, ObservedRole, PeerId}; +use sc_network::{config::Role, Multiaddr, PeerId, ReputationChange}; +use sc_network_common::{ + protocol::event::{Event as NetworkEvent, ObservedRole}, + service::{ + NetworkBlock, NetworkEventStream, NetworkNotification, NetworkPeers, + NetworkSyncForkRequest, NotificationSender, NotificationSenderError, + }, +}; use sc_network_gossip::Validator; use sc_network_test::{Block, Hash}; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; @@ -34,6 +41,7 @@ use sp_keyring::Ed25519Keyring; use sp_runtime::traits::NumberFor; use std::{ borrow::Cow, + collections::HashSet, pin::Pin, sync::Arc, task::{Context, Poll}, @@ -42,8 +50,8 @@ use std::{ #[derive(Debug)] pub(crate) enum Event { EventStream(TracingUnboundedSender), - WriteNotification(sc_network::PeerId, Vec), - Report(sc_network::PeerId, sc_network::ReputationChange), + WriteNotification(PeerId, Vec), + Report(PeerId, ReputationChange), Announce(Hash), } @@ -52,49 +60,122 @@ pub(crate) struct TestNetwork { sender: TracingUnboundedSender, } -impl sc_network_gossip::Network for TestNetwork { - fn event_stream(&self) -> Pin + Send>> { - let (tx, rx) = tracing_unbounded("test"); - let _ = self.sender.unbounded_send(Event::EventStream(tx)); - Box::pin(rx) +impl NetworkPeers for TestNetwork { + fn set_authorized_peers(&self, _peers: HashSet) { + unimplemented!(); + } + + fn set_authorized_only(&self, _reserved_only: bool) { + unimplemented!(); + } + + fn add_known_address(&self, _peer_id: PeerId, _addr: Multiaddr) { + unimplemented!(); } - fn report_peer(&self, who: sc_network::PeerId, cost_benefit: sc_network::ReputationChange) { + fn report_peer(&self, who: PeerId, cost_benefit: ReputationChange) { let _ = self.sender.unbounded_send(Event::Report(who, cost_benefit)); } - fn add_set_reserved(&self, _: PeerId, _: Cow<'static, str>) {} + fn disconnect_peer(&self, _who: PeerId, _protocol: Cow<'static, str>) {} + + fn accept_unreserved_peers(&self) { + unimplemented!(); + } + + fn deny_unreserved_peers(&self) { + unimplemented!(); + } + + fn add_reserved_peer(&self, _peer: String) -> Result<(), String> { + unimplemented!(); + } + + fn remove_reserved_peer(&self, _peer_id: PeerId) { + unimplemented!(); + } + + fn set_reserved_peers( + &self, + _protocol: Cow<'static, str>, + _peers: HashSet, + ) -> Result<(), String> { + unimplemented!(); + } + + fn add_peers_to_reserved_set( + &self, + _protocol: Cow<'static, str>, + _peers: HashSet, + ) -> Result<(), String> { + unimplemented!(); + } + + fn remove_peers_from_reserved_set(&self, _protocol: Cow<'static, str>, _peers: Vec) {} - fn remove_set_reserved(&self, _: PeerId, _: Cow<'static, str>) {} + fn add_to_peers_set( + &self, + _protocol: Cow<'static, str>, + _peers: HashSet, + ) -> Result<(), String> { + unimplemented!(); + } - fn disconnect_peer(&self, _: PeerId, _: Cow<'static, str>) {} + fn remove_from_peers_set(&self, _protocol: Cow<'static, str>, _peers: Vec) { + unimplemented!(); + } - fn write_notification(&self, who: PeerId, _: Cow<'static, str>, message: Vec) { - let _ = self.sender.unbounded_send(Event::WriteNotification(who, message)); + fn sync_num_connected(&self) -> usize { + unimplemented!(); } +} - fn announce(&self, block: Hash, _associated_data: Option>) { - let _ = self.sender.unbounded_send(Event::Announce(block)); +impl NetworkEventStream for TestNetwork { + fn event_stream( + &self, + _name: &'static str, + ) -> Pin + Send>> { + let (tx, rx) = tracing_unbounded("test"); + let _ = self.sender.unbounded_send(Event::EventStream(tx)); + Box::pin(rx) } } -impl super::Network for TestNetwork { - fn set_sync_fork_request( +impl NetworkNotification for TestNetwork { + fn write_notification(&self, target: PeerId, _protocol: Cow<'static, str>, message: Vec) { + let _ = self.sender.unbounded_send(Event::WriteNotification(target, message)); + } + + fn notification_sender( &self, - _peers: Vec, - _hash: Hash, - _number: NumberFor, - ) { + _target: PeerId, + _protocol: Cow<'static, str>, + ) -> Result, NotificationSenderError> { + unimplemented!(); + } +} + +impl NetworkBlock> for TestNetwork { + fn announce_block(&self, hash: Hash, _data: Option>) { + let _ = self.sender.unbounded_send(Event::Announce(hash)); + } + + fn new_best_block_imported(&self, _hash: Hash, _number: NumberFor) { + unimplemented!(); } } +impl NetworkSyncForkRequest> for TestNetwork { + fn set_sync_fork_request(&self, _peers: Vec, _hash: Hash, _number: NumberFor) {} +} + impl sc_network_gossip::ValidatorContext for TestNetwork { fn broadcast_topic(&mut self, _: Hash, _: bool) {} fn broadcast_message(&mut self, _: Hash, _: Vec, _: bool) {} - fn send_message(&mut self, who: &sc_network::PeerId, data: Vec) { - >::write_notification( + fn send_message(&mut self, who: &PeerId, data: Vec) { + ::write_notification( self, who.clone(), grandpa_protocol_name::NAME.into(), @@ -102,7 +183,7 @@ impl sc_network_gossip::ValidatorContext for TestNetwork { ); } - fn send_topic(&mut self, _: &sc_network::PeerId, _: Hash, _: bool) {} + fn send_topic(&mut self, _: &PeerId, _: Hash, _: bool) {} } pub(crate) struct Tester { @@ -207,8 +288,8 @@ struct NoopContext; impl sc_network_gossip::ValidatorContext for NoopContext { fn broadcast_topic(&mut self, _: Hash, _: bool) {} fn broadcast_message(&mut self, _: Hash, _: Vec, _: bool) {} - fn send_message(&mut self, _: &sc_network::PeerId, _: Vec) {} - fn send_topic(&mut self, _: &sc_network::PeerId, _: Hash, _: bool) {} + fn send_message(&mut self, _: &PeerId, _: Vec) {} + fn send_topic(&mut self, _: &PeerId, _: Hash, _: bool) {} } #[test] @@ -252,7 +333,7 @@ fn good_commit_leads_to_relay() { }) .encode(); - let id = sc_network::PeerId::random(); + let id = PeerId::random(); let global_topic = super::global_topic::(set_id); let test = make_test_network() @@ -301,7 +382,7 @@ fn good_commit_leads_to_relay() { }); // Add a random peer which will be the recipient of this message - let receiver_id = sc_network::PeerId::random(); + let receiver_id = PeerId::random(); let _ = sender.unbounded_send(NetworkEvent::NotificationStreamOpened { remote: receiver_id.clone(), protocol: grandpa_protocol_name::NAME.into(), @@ -403,7 +484,7 @@ fn bad_commit_leads_to_report() { }) .encode(); - let id = sc_network::PeerId::random(); + let id = PeerId::random(); let global_topic = super::global_topic::(set_id); let test = make_test_network() @@ -484,7 +565,7 @@ fn bad_commit_leads_to_report() { #[test] fn peer_with_higher_view_leads_to_catch_up_request() { - let id = sc_network::PeerId::random(); + let id = PeerId::random(); let (tester, mut net) = make_test_network(); let test = tester diff --git a/client/informant/Cargo.toml b/client/informant/Cargo.toml index 528365d62c18b..b3ac5d892fd58 100644 --- a/client/informant/Cargo.toml +++ b/client/informant/Cargo.toml @@ -19,7 +19,7 @@ futures-timer = "3.0.1" log = "0.4.17" parity-util-mem = { version = "0.11.0", default-features = false, features = ["primitive-types"] } sc-client-api = { version = "4.0.0-dev", path = "../api" } -sc-network = { version = "0.10.0-dev", path = "../network" } +sc-network-common = { version = "0.10.0-dev", path = "../network/common" } sc-transaction-pool-api = { version = "4.0.0-dev", path = "../transaction-pool/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } diff --git a/client/informant/src/display.rs b/client/informant/src/display.rs index 446ddf47b4cab..0441011b0ec42 100644 --- a/client/informant/src/display.rs +++ b/client/informant/src/display.rs @@ -20,7 +20,13 @@ use crate::OutputFormat; use ansi_term::Colour; use log::info; use sc_client_api::ClientInfo; -use sc_network::{NetworkStatus, SyncState, WarpSyncPhase, WarpSyncProgress}; +use sc_network_common::{ + service::NetworkStatus, + sync::{ + warp::{WarpSyncPhase, WarpSyncProgress}, + SyncState, + }, +}; use sp_runtime::traits::{Block as BlockT, CheckedDiv, NumberFor, Saturating, Zero}; use std::{fmt, time::Instant}; diff --git a/client/informant/src/lib.rs b/client/informant/src/lib.rs index 5dca77f1a7433..52f1c95fe0198 100644 --- a/client/informant/src/lib.rs +++ b/client/informant/src/lib.rs @@ -24,7 +24,7 @@ use futures_timer::Delay; use log::{debug, info, trace}; use parity_util_mem::MallocSizeOf; use sc_client_api::{BlockchainEvents, UsageProvider}; -use sc_network::NetworkService; +use sc_network_common::service::NetworkStatusProvider; use sc_transaction_pool_api::TransactionPool; use sp_blockchain::HeaderMetadata; use sp_runtime::traits::{Block as BlockT, Header}; @@ -53,12 +53,13 @@ impl Default for OutputFormat { } /// Builds the informant and returns a `Future` that drives the informant. -pub async fn build( +pub async fn build( client: Arc, - network: Arc::Hash>>, + network: N, pool: Arc

, format: OutputFormat, ) where + N: NetworkStatusProvider, C: UsageProvider + HeaderMetadata + BlockchainEvents, >::Error: Display, P: TransactionPool + MallocSizeOf, diff --git a/client/network-gossip/Cargo.toml b/client/network-gossip/Cargo.toml index 144c5ad168996..185c37985b585 100644 --- a/client/network-gossip/Cargo.toml +++ b/client/network-gossip/Cargo.toml @@ -23,6 +23,7 @@ lru = "0.7.5" tracing = "0.1.29" prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } sc-network = { version = "0.10.0-dev", path = "../network" } +sc-network-common = { version = "0.10.0-dev", path = "../network/common" } sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } [dev-dependencies] diff --git a/client/network-gossip/src/bridge.rs b/client/network-gossip/src/bridge.rs index 2d086e89b4a10..8a6c3358e4409 100644 --- a/client/network-gossip/src/bridge.rs +++ b/client/network-gossip/src/bridge.rs @@ -21,7 +21,8 @@ use crate::{ Network, Validator, }; -use sc_network::{Event, ReputationChange}; +use sc_network::ReputationChange; +use sc_network_common::protocol::event::Event; use futures::{ channel::mpsc::{channel, Receiver, Sender}, @@ -85,7 +86,7 @@ impl GossipEngine { B: 'static, { let protocol = protocol.into(); - let network_event_stream = network.event_stream(); + let network_event_stream = network.event_stream("network-gossip"); GossipEngine { state_machine: ConsensusGossip::new(validator, protocol.clone(), metrics_registry), @@ -162,7 +163,7 @@ impl GossipEngine { /// Note: this method isn't strictly related to gossiping and should eventually be moved /// somewhere else. pub fn announce(&self, block: B::Hash, associated_data: Option>) { - self.network.announce(block, associated_data); + self.network.announce_block(block, associated_data); } } @@ -181,7 +182,10 @@ impl Future for GossipEngine { this.network.add_set_reserved(remote, this.protocol.clone()); }, Event::SyncDisconnected { remote } => { - this.network.remove_set_reserved(remote, this.protocol.clone()); + this.network.remove_peers_from_reserved_set( + this.protocol.clone(), + vec![remote], + ); }, Event::NotificationStreamOpened { remote, protocol, role, .. } => { if protocol != this.protocol { @@ -304,7 +308,7 @@ impl futures::future::FusedFuture for GossipEngine { #[cfg(test)] mod tests { use super::*; - use crate::{ValidationResult, ValidatorContext}; + use crate::{multiaddr::Multiaddr, ValidationResult, ValidatorContext}; use async_std::task::spawn; use futures::{ channel::mpsc::{unbounded, UnboundedSender}, @@ -312,10 +316,20 @@ mod tests { future::poll_fn, }; use quickcheck::{Arbitrary, Gen, QuickCheck}; - use sc_network::ObservedRole; - use sp_runtime::{testing::H256, traits::Block as BlockT}; + use sc_network_common::{ + protocol::event::ObservedRole, + service::{ + NetworkBlock, NetworkEventStream, NetworkNotification, NetworkPeers, + NotificationSender, NotificationSenderError, + }, + }; + use sp_runtime::{ + testing::H256, + traits::{Block as BlockT, NumberFor}, + }; use std::{ borrow::Cow, + collections::HashSet, sync::{Arc, Mutex}, }; use substrate_test_runtime_client::runtime::Block; @@ -330,29 +344,119 @@ mod tests { event_senders: Vec>, } - impl Network for TestNetwork { - fn event_stream(&self) -> Pin + Send>> { + impl NetworkPeers for TestNetwork { + fn set_authorized_peers(&self, _peers: HashSet) { + unimplemented!(); + } + + fn set_authorized_only(&self, _reserved_only: bool) { + unimplemented!(); + } + + fn add_known_address(&self, _peer_id: PeerId, _addr: Multiaddr) { + unimplemented!(); + } + + fn report_peer(&self, _who: PeerId, _cost_benefit: ReputationChange) {} + + fn disconnect_peer(&self, _who: PeerId, _protocol: Cow<'static, str>) { + unimplemented!(); + } + + fn accept_unreserved_peers(&self) { + unimplemented!(); + } + + fn deny_unreserved_peers(&self) { + unimplemented!(); + } + + fn add_reserved_peer(&self, _peer: String) -> Result<(), String> { + unimplemented!(); + } + + fn remove_reserved_peer(&self, _peer_id: PeerId) { + unimplemented!(); + } + + fn set_reserved_peers( + &self, + _protocol: Cow<'static, str>, + _peers: HashSet, + ) -> Result<(), String> { + unimplemented!(); + } + + fn add_peers_to_reserved_set( + &self, + _protocol: Cow<'static, str>, + _peers: HashSet, + ) -> Result<(), String> { + unimplemented!(); + } + + fn remove_peers_from_reserved_set( + &self, + _protocol: Cow<'static, str>, + _peers: Vec, + ) { + } + + fn add_to_peers_set( + &self, + _protocol: Cow<'static, str>, + _peers: HashSet, + ) -> Result<(), String> { + unimplemented!(); + } + + fn remove_from_peers_set(&self, _protocol: Cow<'static, str>, _peers: Vec) { + unimplemented!(); + } + + fn sync_num_connected(&self) -> usize { + unimplemented!(); + } + } + + impl NetworkEventStream for TestNetwork { + fn event_stream(&self, _name: &'static str) -> Pin + Send>> { let (tx, rx) = unbounded(); self.inner.lock().unwrap().event_senders.push(tx); Box::pin(rx) } + } - fn report_peer(&self, _: PeerId, _: ReputationChange) {} - - fn disconnect_peer(&self, _: PeerId, _: Cow<'static, str>) { + impl NetworkNotification for TestNetwork { + fn write_notification( + &self, + _target: PeerId, + _protocol: Cow<'static, str>, + _message: Vec, + ) { unimplemented!(); } - fn add_set_reserved(&self, _: PeerId, _: Cow<'static, str>) {} - - fn remove_set_reserved(&self, _: PeerId, _: Cow<'static, str>) {} + fn notification_sender( + &self, + _target: PeerId, + _protocol: Cow<'static, str>, + ) -> Result, NotificationSenderError> { + unimplemented!(); + } + } - fn write_notification(&self, _: PeerId, _: Cow<'static, str>, _: Vec) { + impl NetworkBlock<::Hash, NumberFor> for TestNetwork { + fn announce_block(&self, _hash: ::Hash, _data: Option>) { unimplemented!(); } - fn announce(&self, _: B::Hash, _: Option>) { + fn new_best_block_imported( + &self, + _hash: ::Hash, + _number: NumberFor, + ) { unimplemented!(); } } diff --git a/client/network-gossip/src/lib.rs b/client/network-gossip/src/lib.rs index 4b83708702466..b9dff0bcd4d00 100644 --- a/client/network-gossip/src/lib.rs +++ b/client/network-gossip/src/lib.rs @@ -41,9 +41,9 @@ //! messages. //! //! The [`GossipEngine`] will automatically use [`Network::add_set_reserved`] and -//! [`Network::remove_set_reserved`] to maintain a set of peers equal to the set of peers the -//! node is syncing from. See the documentation of `sc-network` for more explanations about the -//! concepts of peer sets. +//! [`NetworkPeers::remove_peers_from_reserved_set`] to maintain a set of peers equal to the set of +//! peers the node is syncing from. See the documentation of `sc-network` for more explanations +//! about the concepts of peer sets. //! //! # What is a validator? //! @@ -67,74 +67,35 @@ pub use self::{ validator::{DiscardAll, MessageIntent, ValidationResult, Validator, ValidatorContext}, }; -use futures::prelude::*; -use sc_network::{multiaddr, Event, ExHashT, NetworkService, PeerId, ReputationChange}; -use sp_runtime::traits::Block as BlockT; -use std::{borrow::Cow, iter, pin::Pin, sync::Arc}; +use sc_network::{multiaddr, PeerId}; +use sc_network_common::service::{ + NetworkBlock, NetworkEventStream, NetworkNotification, NetworkPeers, +}; +use sp_runtime::traits::{Block as BlockT, NumberFor}; +use std::{borrow::Cow, iter}; mod bridge; mod state_machine; mod validator; /// Abstraction over a network. -pub trait Network { - /// Returns a stream of events representing what happens on the network. - fn event_stream(&self) -> Pin + Send>>; - - /// Adjust the reputation of a node. - fn report_peer(&self, peer_id: PeerId, reputation: ReputationChange); - - /// Adds the peer to the set of peers to be connected to with this protocol. - fn add_set_reserved(&self, who: PeerId, protocol: Cow<'static, str>); - - /// Removes the peer from the set of peers to be connected to with this protocol. - fn remove_set_reserved(&self, who: PeerId, protocol: Cow<'static, str>); - - /// Force-disconnect a peer. - fn disconnect_peer(&self, who: PeerId, protocol: Cow<'static, str>); - - /// Send a notification to a peer. - fn write_notification(&self, who: PeerId, protocol: Cow<'static, str>, message: Vec); - - /// Notify everyone we're connected to that we have the given block. - /// - /// Note: this method isn't strictly related to gossiping and should eventually be moved - /// somewhere else. - fn announce(&self, block: B::Hash, associated_data: Option>); -} - -impl Network for Arc> { - fn event_stream(&self) -> Pin + Send>> { - Box::pin(NetworkService::event_stream(self, "network-gossip")) - } - - fn report_peer(&self, peer_id: PeerId, reputation: ReputationChange) { - NetworkService::report_peer(self, peer_id, reputation); - } - +pub trait Network: + NetworkPeers + NetworkEventStream + NetworkNotification + NetworkBlock> +{ fn add_set_reserved(&self, who: PeerId, protocol: Cow<'static, str>) { let addr = iter::once(multiaddr::Protocol::P2p(who.into())).collect::(); - let result = - NetworkService::add_peers_to_reserved_set(self, protocol, iter::once(addr).collect()); + let result = self.add_peers_to_reserved_set(protocol, iter::once(addr).collect()); if let Err(err) = result { log::error!(target: "gossip", "add_set_reserved failed: {}", err); } } +} - fn remove_set_reserved(&self, who: PeerId, protocol: Cow<'static, str>) { - NetworkService::remove_peers_from_reserved_set(self, protocol, iter::once(who).collect()); - } - - fn disconnect_peer(&self, who: PeerId, protocol: Cow<'static, str>) { - NetworkService::disconnect_peer(self, who, protocol) - } - - fn write_notification(&self, who: PeerId, protocol: Cow<'static, str>, message: Vec) { - NetworkService::write_notification(self, who, protocol, message) - } - - fn announce(&self, block: B::Hash, associated_data: Option>) { - NetworkService::announce_block(self, block, associated_data) - } +impl Network for T where + T: NetworkPeers + + NetworkEventStream + + NetworkNotification + + NetworkBlock> +{ } diff --git a/client/network-gossip/src/state_machine.rs b/client/network-gossip/src/state_machine.rs index 8a016cbaab3da..4cc4e25529af4 100644 --- a/client/network-gossip/src/state_machine.rs +++ b/client/network-gossip/src/state_machine.rs @@ -22,7 +22,7 @@ use ahash::AHashSet; use libp2p::PeerId; use lru::LruCache; use prometheus_endpoint::{register, Counter, PrometheusError, Registry, U64}; -use sc_network::ObservedRole; +use sc_network_common::protocol::event::ObservedRole; use sp_runtime::traits::{Block as BlockT, Hash, HashFor}; use std::{borrow::Cow, collections::HashMap, iter, sync::Arc, time, time::Instant}; @@ -511,11 +511,23 @@ impl Metrics { #[cfg(test)] mod tests { use super::*; + use crate::multiaddr::Multiaddr; use futures::prelude::*; - use sc_network::{Event, ReputationChange}; - use sp_runtime::testing::{Block as RawBlock, ExtrinsicWrapper, H256}; + use sc_network::ReputationChange; + use sc_network_common::{ + protocol::event::Event, + service::{ + NetworkBlock, NetworkEventStream, NetworkNotification, NetworkPeers, + NotificationSender, NotificationSenderError, + }, + }; + use sp_runtime::{ + testing::{Block as RawBlock, ExtrinsicWrapper, H256}, + traits::NumberFor, + }; use std::{ borrow::Cow, + collections::HashSet, pin::Pin, sync::{Arc, Mutex}, }; @@ -569,28 +581,118 @@ mod tests { peer_reports: Vec<(PeerId, ReputationChange)>, } - impl Network for NoOpNetwork { - fn event_stream(&self) -> Pin + Send>> { + impl NetworkPeers for NoOpNetwork { + fn set_authorized_peers(&self, _peers: HashSet) { + unimplemented!(); + } + + fn set_authorized_only(&self, _reserved_only: bool) { + unimplemented!(); + } + + fn add_known_address(&self, _peer_id: PeerId, _addr: Multiaddr) { + unimplemented!(); + } + + fn report_peer(&self, who: PeerId, cost_benefit: ReputationChange) { + self.inner.lock().unwrap().peer_reports.push((who, cost_benefit)); + } + + fn disconnect_peer(&self, _who: PeerId, _protocol: Cow<'static, str>) { + unimplemented!(); + } + + fn accept_unreserved_peers(&self) { + unimplemented!(); + } + + fn deny_unreserved_peers(&self) { + unimplemented!(); + } + + fn add_reserved_peer(&self, _peer: String) -> Result<(), String> { + unimplemented!(); + } + + fn remove_reserved_peer(&self, _peer_id: PeerId) { + unimplemented!(); + } + + fn set_reserved_peers( + &self, + _protocol: Cow<'static, str>, + _peers: HashSet, + ) -> Result<(), String> { unimplemented!(); } - fn report_peer(&self, peer_id: PeerId, reputation_change: ReputationChange) { - self.inner.lock().unwrap().peer_reports.push((peer_id, reputation_change)); + fn add_peers_to_reserved_set( + &self, + _protocol: Cow<'static, str>, + _peers: HashSet, + ) -> Result<(), String> { + unimplemented!(); + } + + fn remove_peers_from_reserved_set( + &self, + _protocol: Cow<'static, str>, + _peers: Vec, + ) { } - fn disconnect_peer(&self, _: PeerId, _: Cow<'static, str>) { + fn add_to_peers_set( + &self, + _protocol: Cow<'static, str>, + _peers: HashSet, + ) -> Result<(), String> { unimplemented!(); } - fn add_set_reserved(&self, _: PeerId, _: Cow<'static, str>) {} + fn remove_from_peers_set(&self, _protocol: Cow<'static, str>, _peers: Vec) { + unimplemented!(); + } - fn remove_set_reserved(&self, _: PeerId, _: Cow<'static, str>) {} + fn sync_num_connected(&self) -> usize { + unimplemented!(); + } + } - fn write_notification(&self, _: PeerId, _: Cow<'static, str>, _: Vec) { + impl NetworkEventStream for NoOpNetwork { + fn event_stream(&self, _name: &'static str) -> Pin + Send>> { unimplemented!(); } + } - fn announce(&self, _: B::Hash, _: Option>) { + impl NetworkNotification for NoOpNetwork { + fn write_notification( + &self, + _target: PeerId, + _protocol: Cow<'static, str>, + _message: Vec, + ) { + unimplemented!(); + } + + fn notification_sender( + &self, + _target: PeerId, + _protocol: Cow<'static, str>, + ) -> Result, NotificationSenderError> { + unimplemented!(); + } + } + + impl NetworkBlock<::Hash, NumberFor> for NoOpNetwork { + fn announce_block(&self, _hash: ::Hash, _data: Option>) { + unimplemented!(); + } + + fn new_best_block_imported( + &self, + _hash: ::Hash, + _number: NumberFor, + ) { unimplemented!(); } } diff --git a/client/network-gossip/src/validator.rs b/client/network-gossip/src/validator.rs index 7d60f7b31397f..a98c62ab5a9eb 100644 --- a/client/network-gossip/src/validator.rs +++ b/client/network-gossip/src/validator.rs @@ -16,7 +16,8 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use sc_network::{ObservedRole, PeerId}; +use sc_network::PeerId; +use sc_network_common::protocol::event::ObservedRole; use sp_runtime::traits::Block as BlockT; /// Validates consensus messages. diff --git a/client/network/common/Cargo.toml b/client/network/common/Cargo.toml index b0e3a8fe42a83..1b10bd248292c 100644 --- a/client/network/common/Cargo.toml +++ b/client/network/common/Cargo.toml @@ -17,7 +17,9 @@ targets = ["x86_64-unknown-linux-gnu"] prost-build = "0.10" [dependencies] +async-trait = "0.1.50" bitflags = "1.3.2" +bytes = "1" codec = { package = "parity-scale-codec", version = "3.0.0", features = [ "derive", ] } @@ -29,3 +31,4 @@ sc-peerset = { version = "4.0.0-dev", path = "../../peerset" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } sp-finality-grandpa = { version = "4.0.0-dev", path = "../../../primitives/finality-grandpa" } sp-runtime = { version = "6.0.0", path = "../../../primitives/runtime" } +thiserror = "1.0" diff --git a/client/network/common/src/lib.rs b/client/network/common/src/lib.rs index 9fbedc542c123..3a30d24900199 100644 --- a/client/network/common/src/lib.rs +++ b/client/network/common/src/lib.rs @@ -20,5 +20,7 @@ pub mod config; pub mod message; +pub mod protocol; pub mod request_responses; +pub mod service; pub mod sync; diff --git a/client/network/common/src/protocol.rs b/client/network/common/src/protocol.rs new file mode 100644 index 0000000000000..0fd36cb511112 --- /dev/null +++ b/client/network/common/src/protocol.rs @@ -0,0 +1,19 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +pub mod event; diff --git a/client/network/src/protocol/event.rs b/client/network/common/src/protocol/event.rs similarity index 96% rename from client/network/src/protocol/event.rs rename to client/network/common/src/protocol/event.rs index 26c9544960605..c6fb4a2faf9f9 100644 --- a/client/network/src/protocol/event.rs +++ b/client/network/common/src/protocol/event.rs @@ -67,14 +67,14 @@ pub enum Event { remote: PeerId, /// The concerned protocol. Each protocol uses a different substream. /// This is always equal to the value of - /// [`crate::config::NonDefaultSetConfig::notifications_protocol`] of one of the + /// `sc_network::config::NonDefaultSetConfig::notifications_protocol` of one of the /// configured sets. protocol: Cow<'static, str>, /// If the negotiation didn't use the main name of the protocol (the one in /// `notifications_protocol`), then this field contains which name has actually been /// used. /// Always contains a value equal to the value in - /// [`crate::config::NonDefaultSetConfig::fallback_names`]. + /// `sc_network::config::NonDefaultSetConfig::fallback_names`. negotiated_fallback: Option>, /// Role of the remote. role: ObservedRole, diff --git a/client/network/common/src/request_responses.rs b/client/network/common/src/request_responses.rs index f409d1ac16d61..a216c9c2d254d 100644 --- a/client/network/common/src/request_responses.rs +++ b/client/network/common/src/request_responses.rs @@ -19,7 +19,7 @@ //! Collection of generic data structures for request-response protocols. use futures::channel::{mpsc, oneshot}; -use libp2p::PeerId; +use libp2p::{request_response::OutboundFailure, PeerId}; use sc_peerset::ReputationChange; use std::{borrow::Cow, time::Duration}; @@ -115,3 +115,40 @@ pub struct OutgoingResponse { /// > written to the buffer managed by the operating system. pub sent_feedback: Option>, } + +/// When sending a request, what to do on a disconnected recipient. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum IfDisconnected { + /// Try to connect to the peer. + TryConnect, + /// Just fail if the destination is not yet connected. + ImmediateError, +} + +/// Convenience functions for `IfDisconnected`. +impl IfDisconnected { + /// Shall we connect to a disconnected peer? + pub fn should_connect(self) -> bool { + match self { + Self::TryConnect => true, + Self::ImmediateError => false, + } + } +} + +/// Error in a request. +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum RequestFailure { + #[error("We are not currently connected to the requested peer.")] + NotConnected, + #[error("Given protocol hasn't been registered.")] + UnknownProtocol, + #[error("Remote has closed the substream before answering, thereby signaling that it considers the request as valid, but refused to answer it.")] + Refused, + #[error("The remote replied, but the local node is no longer interested in the response.")] + Obsolete, + /// Problem on the network. + #[error("Problem on the network: {0}")] + Network(OutboundFailure), +} diff --git a/client/network/common/src/service.rs b/client/network/common/src/service.rs new file mode 100644 index 0000000000000..0fff32b12d66c --- /dev/null +++ b/client/network/common/src/service.rs @@ -0,0 +1,660 @@ +// This file is part of Substrate. +// +// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +// If you read this, you are very thorough, congratulations. + +use crate::{ + protocol::event::Event, + request_responses::{IfDisconnected, RequestFailure}, + sync::{warp::WarpSyncProgress, StateDownloadProgress, SyncState}, +}; +use futures::{channel::oneshot, Stream}; +pub use libp2p::{identity::error::SigningError, kad::record::Key as KademliaKey}; +use libp2p::{Multiaddr, PeerId}; +use sc_peerset::ReputationChange; +pub use signature::Signature; +use sp_runtime::traits::{Block as BlockT, NumberFor}; +use std::{borrow::Cow, collections::HashSet, future::Future, pin::Pin, sync::Arc}; + +mod signature; + +/// Signer with network identity +pub trait NetworkSigner { + /// Signs the message with the `KeyPair` that defines the local [`PeerId`]. + fn sign_with_local_identity(&self, msg: impl AsRef<[u8]>) -> Result; +} + +impl NetworkSigner for Arc +where + T: ?Sized, + T: NetworkSigner, +{ + fn sign_with_local_identity(&self, msg: impl AsRef<[u8]>) -> Result { + T::sign_with_local_identity(self, msg) + } +} + +/// Provides access to the networking DHT. +pub trait NetworkDHTProvider { + /// Start getting a value from the DHT. + fn get_value(&self, key: &KademliaKey); + + /// Start putting a value in the DHT. + fn put_value(&self, key: KademliaKey, value: Vec); +} + +impl NetworkDHTProvider for Arc +where + T: ?Sized, + T: NetworkDHTProvider, +{ + fn get_value(&self, key: &KademliaKey) { + T::get_value(self, key) + } + + fn put_value(&self, key: KademliaKey, value: Vec) { + T::put_value(self, key, value) + } +} + +/// Provides an ability to set a fork sync request for a particular block. +pub trait NetworkSyncForkRequest { + /// Notifies the sync service to try and sync the given block from the given + /// peers. + /// + /// If the given vector of peers is empty then the underlying implementation + /// should make a best effort to fetch the block from any peers it is + /// connected to (NOTE: this assumption will change in the future #3629). + fn set_sync_fork_request(&self, peers: Vec, hash: BlockHash, number: BlockNumber); +} + +impl NetworkSyncForkRequest for Arc +where + T: ?Sized, + T: NetworkSyncForkRequest, +{ + fn set_sync_fork_request(&self, peers: Vec, hash: BlockHash, number: BlockNumber) { + T::set_sync_fork_request(self, peers, hash, number) + } +} + +/// Overview status of the network. +#[derive(Clone)] +pub struct NetworkStatus { + /// Current global sync state. + pub sync_state: SyncState, + /// Target sync block number. + pub best_seen_block: Option>, + /// Number of peers participating in syncing. + pub num_sync_peers: u32, + /// Total number of connected peers + pub num_connected_peers: usize, + /// Total number of active peers. + pub num_active_peers: usize, + /// The total number of bytes received. + pub total_bytes_inbound: u64, + /// The total number of bytes sent. + pub total_bytes_outbound: u64, + /// State sync in progress. + pub state_sync: Option, + /// Warp sync in progress. + pub warp_sync: Option>, +} + +/// Provides high-level status information about network. +#[async_trait::async_trait] +pub trait NetworkStatusProvider { + /// High-level network status information. + /// + /// Returns an error if the `NetworkWorker` is no longer running. + async fn status(&self) -> Result, ()>; +} + +// Manual implementation to avoid extra boxing here +impl NetworkStatusProvider for Arc +where + T: ?Sized, + T: NetworkStatusProvider, +{ + fn status<'life0, 'async_trait>( + &'life0 self, + ) -> Pin, ()>> + Send + 'async_trait>> + where + 'life0: 'async_trait, + Self: 'async_trait, + { + T::status(self) + } +} + +/// Provides low-level API for manipulating network peers. +pub trait NetworkPeers { + /// Set authorized peers. + /// + /// Need a better solution to manage authorized peers, but now just use reserved peers for + /// prototyping. + fn set_authorized_peers(&self, peers: HashSet); + + /// Set authorized_only flag. + /// + /// Need a better solution to decide authorized_only, but now just use reserved_only flag for + /// prototyping. + fn set_authorized_only(&self, reserved_only: bool); + + /// Adds an address known to a node. + fn add_known_address(&self, peer_id: PeerId, addr: Multiaddr); + + /// Report a given peer as either beneficial (+) or costly (-) according to the + /// given scalar. + fn report_peer(&self, who: PeerId, cost_benefit: ReputationChange); + + /// Disconnect from a node as soon as possible. + /// + /// This triggers the same effects as if the connection had closed itself spontaneously. + /// + /// See also [`NetworkPeers::remove_from_peers_set`], which has the same effect but also + /// prevents the local node from re-establishing an outgoing substream to this peer until it + /// is added again. + fn disconnect_peer(&self, who: PeerId, protocol: Cow<'static, str>); + + /// Connect to unreserved peers and allow unreserved peers to connect for syncing purposes. + fn accept_unreserved_peers(&self); + + /// Disconnect from unreserved peers and deny new unreserved peers to connect for syncing + /// purposes. + fn deny_unreserved_peers(&self); + + /// Adds a `PeerId` and its address as reserved. The string should encode the address + /// and peer ID of the remote node. + /// + /// Returns an `Err` if the given string is not a valid multiaddress + /// or contains an invalid peer ID (which includes the local peer ID). + fn add_reserved_peer(&self, peer: String) -> Result<(), String>; + + /// Removes a `PeerId` from the list of reserved peers. + fn remove_reserved_peer(&self, peer_id: PeerId); + + /// Sets the reserved set of a protocol to the given set of peers. + /// + /// Each `Multiaddr` must end with a `/p2p/` component containing the `PeerId`. It can also + /// consist of only `/p2p/`. + /// + /// The node will start establishing/accepting connections and substreams to/from peers in this + /// set, if it doesn't have any substream open with them yet. + /// + /// Note however, if a call to this function results in less peers on the reserved set, they + /// will not necessarily get disconnected (depending on available free slots in the peer set). + /// If you want to also disconnect those removed peers, you will have to call + /// `remove_from_peers_set` on those in addition to updating the reserved set. You can omit + /// this step if the peer set is in reserved only mode. + /// + /// Returns an `Err` if one of the given addresses is invalid or contains an + /// invalid peer ID (which includes the local peer ID). + fn set_reserved_peers( + &self, + protocol: Cow<'static, str>, + peers: HashSet, + ) -> Result<(), String>; + + /// Add peers to a peer set. + /// + /// Each `Multiaddr` must end with a `/p2p/` component containing the `PeerId`. It can also + /// consist of only `/p2p/`. + /// + /// Returns an `Err` if one of the given addresses is invalid or contains an + /// invalid peer ID (which includes the local peer ID). + fn add_peers_to_reserved_set( + &self, + protocol: Cow<'static, str>, + peers: HashSet, + ) -> Result<(), String>; + + /// Remove peers from a peer set. + fn remove_peers_from_reserved_set(&self, protocol: Cow<'static, str>, peers: Vec); + + /// Add a peer to a set of peers. + /// + /// If the set has slots available, it will try to open a substream with this peer. + /// + /// Each `Multiaddr` must end with a `/p2p/` component containing the `PeerId`. It can also + /// consist of only `/p2p/`. + /// + /// Returns an `Err` if one of the given addresses is invalid or contains an + /// invalid peer ID (which includes the local peer ID). + fn add_to_peers_set( + &self, + protocol: Cow<'static, str>, + peers: HashSet, + ) -> Result<(), String>; + + /// Remove peers from a peer set. + /// + /// If we currently have an open substream with this peer, it will soon be closed. + fn remove_from_peers_set(&self, protocol: Cow<'static, str>, peers: Vec); + + /// Returns the number of peers in the sync peer set we're connected to. + fn sync_num_connected(&self) -> usize; +} + +// Manual implementation to avoid extra boxing here +impl NetworkPeers for Arc +where + T: ?Sized, + T: NetworkPeers, +{ + fn set_authorized_peers(&self, peers: HashSet) { + T::set_authorized_peers(self, peers) + } + + fn set_authorized_only(&self, reserved_only: bool) { + T::set_authorized_only(self, reserved_only) + } + + fn add_known_address(&self, peer_id: PeerId, addr: Multiaddr) { + T::add_known_address(self, peer_id, addr) + } + + fn report_peer(&self, who: PeerId, cost_benefit: ReputationChange) { + T::report_peer(self, who, cost_benefit) + } + + fn disconnect_peer(&self, who: PeerId, protocol: Cow<'static, str>) { + T::disconnect_peer(self, who, protocol) + } + + fn accept_unreserved_peers(&self) { + T::accept_unreserved_peers(self) + } + + fn deny_unreserved_peers(&self) { + T::deny_unreserved_peers(self) + } + + fn add_reserved_peer(&self, peer: String) -> Result<(), String> { + T::add_reserved_peer(self, peer) + } + + fn remove_reserved_peer(&self, peer_id: PeerId) { + T::remove_reserved_peer(self, peer_id) + } + + fn set_reserved_peers( + &self, + protocol: Cow<'static, str>, + peers: HashSet, + ) -> Result<(), String> { + T::set_reserved_peers(self, protocol, peers) + } + + fn add_peers_to_reserved_set( + &self, + protocol: Cow<'static, str>, + peers: HashSet, + ) -> Result<(), String> { + T::add_peers_to_reserved_set(self, protocol, peers) + } + + fn remove_peers_from_reserved_set(&self, protocol: Cow<'static, str>, peers: Vec) { + T::remove_peers_from_reserved_set(self, protocol, peers) + } + + fn add_to_peers_set( + &self, + protocol: Cow<'static, str>, + peers: HashSet, + ) -> Result<(), String> { + T::add_to_peers_set(self, protocol, peers) + } + + fn remove_from_peers_set(&self, protocol: Cow<'static, str>, peers: Vec) { + T::remove_from_peers_set(self, protocol, peers) + } + + fn sync_num_connected(&self) -> usize { + T::sync_num_connected(self) + } +} + +/// Provides access to network-level event stream. +pub trait NetworkEventStream { + /// Returns a stream containing the events that happen on the network. + /// + /// If this method is called multiple times, the events are duplicated. + /// + /// The stream never ends (unless the `NetworkWorker` gets shut down). + /// + /// The name passed is used to identify the channel in the Prometheus metrics. Note that the + /// parameter is a `&'static str`, and not a `String`, in order to avoid accidentally having + /// an unbounded set of Prometheus metrics, which would be quite bad in terms of memory + fn event_stream(&self, name: &'static str) -> Pin + Send>>; +} + +impl NetworkEventStream for Arc +where + T: ?Sized, + T: NetworkEventStream, +{ + fn event_stream(&self, name: &'static str) -> Pin + Send>> { + T::event_stream(self, name) + } +} + +/// Trait for providing information about the local network state +pub trait NetworkStateInfo { + /// Returns the local external addresses. + fn external_addresses(&self) -> Vec; + + /// Returns the local Peer ID. + fn local_peer_id(&self) -> PeerId; +} + +impl NetworkStateInfo for Arc +where + T: ?Sized, + T: NetworkStateInfo, +{ + fn external_addresses(&self) -> Vec { + T::external_addresses(self) + } + + fn local_peer_id(&self) -> PeerId { + T::local_peer_id(self) + } +} + +/// Reserved slot in the notifications buffer, ready to accept data. +pub trait NotificationSenderReady { + /// Consumes this slots reservation and actually queues the notification. + /// + /// NOTE: Traits can't consume itself, but calling this method second time will return an error. + fn send(&mut self, notification: Vec) -> Result<(), NotificationSenderError>; +} + +/// A `NotificationSender` allows for sending notifications to a peer with a chosen protocol. +#[async_trait::async_trait] +pub trait NotificationSender { + /// Returns a future that resolves when the `NotificationSender` is ready to send a + /// notification. + async fn ready(&self) + -> Result, NotificationSenderError>; +} + +/// Error returned by [`NetworkNotification::notification_sender`]. +#[derive(Debug, thiserror::Error)] +pub enum NotificationSenderError { + /// The notification receiver has been closed, usually because the underlying connection + /// closed. + /// + /// Some of the notifications most recently sent may not have been received. However, + /// the peer may still be connected and a new `NotificationSender` for the same + /// protocol obtained from [`NetworkNotification::notification_sender`]. + #[error("The notification receiver has been closed")] + Closed, + /// Protocol name hasn't been registered. + #[error("Protocol name hasn't been registered")] + BadProtocol, +} + +/// Provides ability to send network notifications. +pub trait NetworkNotification { + /// Appends a notification to the buffer of pending outgoing notifications with the given peer. + /// Has no effect if the notifications channel with this protocol name is not open. + /// + /// If the buffer of pending outgoing notifications with that peer is full, the notification + /// is silently dropped and the connection to the remote will start being shut down. This + /// happens if you call this method at a higher rate than the rate at which the peer processes + /// these notifications, or if the available network bandwidth is too low. + /// + /// For this reason, this method is considered soft-deprecated. You are encouraged to use + /// [`NetworkNotification::notification_sender`] instead. + /// + /// > **Note**: The reason why this is a no-op in the situation where we have no channel is + /// > that we don't guarantee message delivery anyway. Networking issues can cause + /// > connections to drop at any time, and higher-level logic shouldn't differentiate + /// > between the remote voluntarily closing a substream or a network error + /// > preventing the message from being delivered. + /// + /// The protocol must have been registered with + /// `crate::config::NetworkConfiguration::notifications_protocols`. + fn write_notification(&self, target: PeerId, protocol: Cow<'static, str>, message: Vec); + + /// Obtains a [`NotificationSender`] for a connected peer, if it exists. + /// + /// A `NotificationSender` is scoped to a particular connection to the peer that holds + /// a receiver. With a `NotificationSender` at hand, sending a notification is done in two + /// steps: + /// + /// 1. [`NotificationSender::ready`] is used to wait for the sender to become ready + /// for another notification, yielding a [`NotificationSenderReady`] token. + /// 2. [`NotificationSenderReady::send`] enqueues the notification for sending. This operation + /// can only fail if the underlying notification substream or connection has suddenly closed. + /// + /// An error is returned by [`NotificationSenderReady::send`] if there exists no open + /// notifications substream with that combination of peer and protocol, or if the remote + /// has asked to close the notifications substream. If that happens, it is guaranteed that an + /// [`Event::NotificationStreamClosed`] has been generated on the stream returned by + /// [`NetworkEventStream::event_stream`]. + /// + /// If the remote requests to close the notifications substream, all notifications successfully + /// enqueued using [`NotificationSenderReady::send`] will finish being sent out before the + /// substream actually gets closed, but attempting to enqueue more notifications will now + /// return an error. It is however possible for the entire connection to be abruptly closed, + /// in which case enqueued notifications will be lost. + /// + /// The protocol must have been registered with + /// `crate::config::NetworkConfiguration::notifications_protocols`. + /// + /// # Usage + /// + /// This method returns a struct that allows waiting until there is space available in the + /// buffer of messages towards the given peer. If the peer processes notifications at a slower + /// rate than we send them, this buffer will quickly fill up. + /// + /// As such, you should never do something like this: + /// + /// ```ignore + /// // Do NOT do this + /// for peer in peers { + /// if let Ok(n) = network.notification_sender(peer, ...) { + /// if let Ok(s) = n.ready().await { + /// let _ = s.send(...); + /// } + /// } + /// } + /// ``` + /// + /// Doing so would slow down all peers to the rate of the slowest one. A malicious or + /// malfunctioning peer could intentionally process notifications at a very slow rate. + /// + /// Instead, you are encouraged to maintain your own buffer of notifications on top of the one + /// maintained by `sc-network`, and use `notification_sender` to progressively send out + /// elements from your buffer. If this additional buffer is full (which will happen at some + /// point if the peer is too slow to process notifications), appropriate measures can be taken, + /// such as removing non-critical notifications from the buffer or disconnecting the peer + /// using [`NetworkPeers::disconnect_peer`]. + /// + /// + /// Notifications Per-peer buffer + /// broadcast +-------> of notifications +--> `notification_sender` +--> Internet + /// ^ (not covered by + /// | sc-network) + /// + + /// Notifications should be dropped + /// if buffer is full + /// + /// + /// See also the `sc-network-gossip` crate for a higher-level way to send notifications. + fn notification_sender( + &self, + target: PeerId, + protocol: Cow<'static, str>, + ) -> Result, NotificationSenderError>; +} + +impl NetworkNotification for Arc +where + T: ?Sized, + T: NetworkNotification, +{ + fn write_notification(&self, target: PeerId, protocol: Cow<'static, str>, message: Vec) { + T::write_notification(self, target, protocol, message) + } + + fn notification_sender( + &self, + target: PeerId, + protocol: Cow<'static, str>, + ) -> Result, NotificationSenderError> { + T::notification_sender(self, target, protocol) + } +} + +/// Provides ability to send network requests. +#[async_trait::async_trait] +pub trait NetworkRequest { + /// Sends a single targeted request to a specific peer. On success, returns the response of + /// the peer. + /// + /// Request-response protocols are a way to complement notifications protocols, but + /// notifications should remain the default ways of communicating information. For example, a + /// peer can announce something through a notification, after which the recipient can obtain + /// more information by performing a request. + /// As such, call this function with `IfDisconnected::ImmediateError` for `connect`. This way + /// you will get an error immediately for disconnected peers, instead of waiting for a + /// potentially very long connection attempt, which would suggest that something is wrong + /// anyway, as you are supposed to be connected because of the notification protocol. + /// + /// No limit or throttling of concurrent outbound requests per peer and protocol are enforced. + /// Such restrictions, if desired, need to be enforced at the call site(s). + /// + /// The protocol must have been registered through + /// `NetworkConfiguration::request_response_protocols`. + async fn request( + &self, + target: PeerId, + protocol: Cow<'static, str>, + request: Vec, + connect: IfDisconnected, + ) -> Result, RequestFailure>; + + /// Variation of `request` which starts a request whose response is delivered on a provided + /// channel. + /// + /// Instead of blocking and waiting for a reply, this function returns immediately, sending + /// responses via the passed in sender. This alternative API exists to make it easier to + /// integrate with message passing APIs. + /// + /// Keep in mind that the connected receiver might receive a `Canceled` event in case of a + /// closing connection. This is expected behaviour. With `request` you would get a + /// `RequestFailure::Network(OutboundFailure::ConnectionClosed)` in that case. + fn start_request( + &self, + target: PeerId, + protocol: Cow<'static, str>, + request: Vec, + tx: oneshot::Sender, RequestFailure>>, + connect: IfDisconnected, + ); +} + +// Manual implementation to avoid extra boxing here +impl NetworkRequest for Arc +where + T: ?Sized, + T: NetworkRequest, +{ + fn request<'life0, 'async_trait>( + &'life0 self, + target: PeerId, + protocol: Cow<'static, str>, + request: Vec, + connect: IfDisconnected, + ) -> Pin, RequestFailure>> + Send + 'async_trait>> + where + 'life0: 'async_trait, + Self: 'async_trait, + { + T::request(self, target, protocol, request, connect) + } + + fn start_request( + &self, + target: PeerId, + protocol: Cow<'static, str>, + request: Vec, + tx: oneshot::Sender, RequestFailure>>, + connect: IfDisconnected, + ) { + T::start_request(self, target, protocol, request, tx, connect) + } +} + +/// Provides ability to propagate transactions over the network. +pub trait NetworkTransaction { + /// You may call this when new transactions are imported by the transaction pool. + /// + /// All transactions will be fetched from the `TransactionPool` that was passed at + /// initialization as part of the configuration and propagated to peers. + fn trigger_repropagate(&self); + + /// You must call when new transaction is imported by the transaction pool. + /// + /// This transaction will be fetched from the `TransactionPool` that was passed at + /// initialization as part of the configuration and propagated to peers. + fn propagate_transaction(&self, hash: H); +} + +impl NetworkTransaction for Arc +where + T: ?Sized, + T: NetworkTransaction, +{ + fn trigger_repropagate(&self) { + T::trigger_repropagate(self) + } + + fn propagate_transaction(&self, hash: H) { + T::propagate_transaction(self, hash) + } +} + +/// Provides ability to announce blocks to the network. +pub trait NetworkBlock { + /// Make sure an important block is propagated to peers. + /// + /// In chain-based consensus, we often need to make sure non-best forks are + /// at least temporarily synced. This function forces such an announcement. + fn announce_block(&self, hash: BlockHash, data: Option>); + + /// Inform the network service about new best imported block. + fn new_best_block_imported(&self, hash: BlockHash, number: BlockNumber); +} + +impl NetworkBlock for Arc +where + T: ?Sized, + T: NetworkBlock, +{ + fn announce_block(&self, hash: BlockHash, data: Option>) { + T::announce_block(self, hash, data) + } + + fn new_best_block_imported(&self, hash: BlockHash, number: BlockNumber) { + T::new_best_block_imported(self, hash, number) + } +} diff --git a/client/network/src/service/signature.rs b/client/network/common/src/service/signature.rs similarity index 96% rename from client/network/src/service/signature.rs rename to client/network/common/src/service/signature.rs index d21d28a3007b5..602ef3d82979a 100644 --- a/client/network/src/service/signature.rs +++ b/client/network/common/src/service/signature.rs @@ -18,7 +18,10 @@ // // If you read this, you are very thorough, congratulations. -use super::*; +use libp2p::{ + identity::{error::SigningError, Keypair, PublicKey}, + PeerId, +}; /// A result of signing a message with a network identity. Since `PeerId` is potentially a hash of a /// `PublicKey`, you need to reveal the `PublicKey` next to the signature, so the verifier can check diff --git a/client/network/src/behaviour.rs b/client/network/src/behaviour.rs index 515608df13d0f..4ff415788f4ea 100644 --- a/client/network/src/behaviour.rs +++ b/client/network/src/behaviour.rs @@ -21,7 +21,7 @@ use crate::{ discovery::{DiscoveryBehaviour, DiscoveryConfig, DiscoveryOut}, peer_info, protocol::{message::Roles, CustomMessageOutcome, NotificationsSink, Protocol}, - request_responses, DhtEvent, ObservedRole, + request_responses, }; use bytes::Bytes; @@ -41,7 +41,11 @@ use log::debug; use sc_client_api::{BlockBackend, ProofProvider}; use sc_consensus::import_queue::{IncomingBlock, Origin}; -use sc_network_common::{config::ProtocolId, request_responses::ProtocolConfig}; +use sc_network_common::{ + config::ProtocolId, + protocol::event::{DhtEvent, ObservedRole}, + request_responses::{IfDisconnected, ProtocolConfig, RequestFailure}, +}; use sc_peerset::PeersetHandle; use sp_blockchain::{HeaderBackend, HeaderMetadata}; use sp_consensus::BlockOrigin; @@ -57,9 +61,7 @@ use std::{ time::Duration, }; -pub use crate::request_responses::{ - IfDisconnected, InboundFailure, OutboundFailure, RequestFailure, RequestId, ResponseFailure, -}; +pub use crate::request_responses::{InboundFailure, OutboundFailure, RequestId, ResponseFailure}; /// General behaviour of the network. Combines all protocols together. #[derive(NetworkBehaviour)] diff --git a/client/network/src/lib.rs b/client/network/src/lib.rs index 83bc1075b8bad..b1d70c28289bd 100644 --- a/client/network/src/lib.rs +++ b/client/network/src/lib.rs @@ -262,22 +262,26 @@ pub mod transactions; #[doc(inline)] pub use libp2p::{multiaddr, Multiaddr, PeerId}; -pub use protocol::{ - event::{DhtEvent, Event, ObservedRole}, - PeerInfo, -}; -pub use sc_network_common::sync::{ - warp::{WarpSyncPhase, WarpSyncProgress}, - StateDownloadProgress, SyncState, +pub use protocol::PeerInfo; +pub use sc_network_common::{ + protocol::event::{DhtEvent, Event, ObservedRole}, + request_responses::{IfDisconnected, RequestFailure}, + service::{ + KademliaKey, NetworkBlock, NetworkDHTProvider, NetworkRequest, NetworkSigner, + NetworkStateInfo, NetworkStatus, NetworkStatusProvider, NetworkSyncForkRequest, + NetworkTransaction, Signature, SigningError, + }, + sync::{ + warp::{WarpSyncPhase, WarpSyncProgress}, + StateDownloadProgress, SyncState, + }, }; pub use service::{ - DecodingError, IfDisconnected, KademliaKey, Keypair, NetworkService, NetworkWorker, - NotificationSender, NotificationSenderReady, OutboundFailure, PublicKey, RequestFailure, - Signature, SigningError, + DecodingError, Keypair, NetworkService, NetworkWorker, NotificationSender, + NotificationSenderReady, OutboundFailure, PublicKey, }; pub use sc_peerset::ReputationChange; -use sp_runtime::traits::{Block as BlockT, NumberFor}; /// The maximum allowed number of established connections per peer. /// @@ -296,35 +300,3 @@ pub trait ExHashT: std::hash::Hash + Eq + std::fmt::Debug + Clone + Send + Sync impl ExHashT for T where T: std::hash::Hash + Eq + std::fmt::Debug + Clone + Send + Sync + 'static {} - -/// Trait for providing information about the local network state -pub trait NetworkStateInfo { - /// Returns the local external addresses. - fn external_addresses(&self) -> Vec; - - /// Returns the local Peer ID. - fn local_peer_id(&self) -> PeerId; -} - -/// Overview status of the network. -#[derive(Clone)] -pub struct NetworkStatus { - /// Current global sync state. - pub sync_state: SyncState, - /// Target sync block number. - pub best_seen_block: Option>, - /// Number of peers participating in syncing. - pub num_sync_peers: u32, - /// Total number of connected peers - pub num_connected_peers: usize, - /// Total number of active peers. - pub num_active_peers: usize, - /// The total number of bytes received. - pub total_bytes_inbound: u64, - /// The total number of bytes sent. - pub total_bytes_outbound: u64, - /// State sync in progress. - pub state_sync: Option, - /// Warp sync in progress. - pub warp_sync: Option>, -} diff --git a/client/network/src/protocol.rs b/client/network/src/protocol.rs index c51667017d15c..351e7d207ad1e 100644 --- a/client/network/src/protocol.rs +++ b/client/network/src/protocol.rs @@ -18,7 +18,6 @@ use crate::{ config, error, - request_responses::RequestFailure, utils::{interval, LruHashSet}, }; @@ -45,6 +44,7 @@ use sc_client_api::{BlockBackend, HeaderBackend, ProofProvider}; use sc_consensus::import_queue::{BlockImportError, BlockImportStatus, IncomingBlock, Origin}; use sc_network_common::{ config::ProtocolId, + request_responses::RequestFailure, sync::{ message::{ BlockAnnounce, BlockAttributes, BlockData, BlockRequest, BlockResponse, BlockState, @@ -76,7 +76,6 @@ use std::{ mod notifications; -pub mod event; pub mod message; pub use notifications::{NotificationsSink, NotifsHandlerError, Ready}; diff --git a/client/network/src/request_responses.rs b/client/network/src/request_responses.rs index 0d8c6c33b1c95..9eab85a4c1ce1 100644 --- a/client/network/src/request_responses.rs +++ b/client/network/src/request_responses.rs @@ -50,7 +50,9 @@ use libp2p::{ NetworkBehaviourAction, PollParameters, }, }; -use sc_network_common::request_responses::{IncomingRequest, OutgoingResponse, ProtocolConfig}; +use sc_network_common::request_responses::{ + IfDisconnected, IncomingRequest, OutgoingResponse, ProtocolConfig, RequestFailure, +}; use std::{ borrow::Cow, collections::{hash_map::Entry, HashMap}, @@ -118,26 +120,6 @@ impl From<(Cow<'static, str>, RequestId)> for ProtocolRequestId { } } -/// When sending a request, what to do on a disconnected recipient. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum IfDisconnected { - /// Try to connect to the peer. - TryConnect, - /// Just fail if the destination is not yet connected. - ImmediateError, -} - -/// Convenience functions for `IfDisconnected`. -impl IfDisconnected { - /// Shall we connect to a disconnected peer? - pub fn should_connect(self) -> bool { - match self { - Self::TryConnect => true, - Self::ImmediateError => false, - } - } -} - /// Implementation of `NetworkBehaviour` that provides support for request-response protocols. pub struct RequestResponsesBehaviour { /// The multiple sub-protocols, by name. @@ -787,23 +769,6 @@ pub enum RegisterError { DuplicateProtocol(Cow<'static, str>), } -/// Error in a request. -#[derive(Debug, thiserror::Error)] -#[allow(missing_docs)] -pub enum RequestFailure { - #[error("We are not currently connected to the requested peer.")] - NotConnected, - #[error("Given protocol hasn't been registered.")] - UnknownProtocol, - #[error("Remote has closed the substream before answering, thereby signaling that it considers the request as valid, but refused to answer it.")] - Refused, - #[error("The remote replied, but the local node is no longer interested in the response.")] - Obsolete, - /// Problem on the network. - #[error("Problem on the network: {0}")] - Network(OutboundFailure), -} - /// Error when processing a request sent by a remote. #[derive(Debug, thiserror::Error)] pub enum ResponseFailure { diff --git a/client/network/src/service.rs b/client/network/src/service.rs index 1210b0ca64224..bf15a9250d82c 100644 --- a/client/network/src/service.rs +++ b/client/network/src/service.rs @@ -37,16 +37,17 @@ use crate::{ NetworkState, NotConnectedPeer as NetworkStateNotConnectedPeer, Peer as NetworkStatePeer, }, protocol::{ - self, event::Event, message::generic::Roles, NotificationsSink, NotifsHandlerError, - PeerInfo, Protocol, Ready, + self, message::generic::Roles, NotificationsSink, NotifsHandlerError, PeerInfo, Protocol, + Ready, }, - transactions, transport, DhtEvent, ExHashT, NetworkStateInfo, NetworkStatus, ReputationChange, + transactions, transport, ExHashT, ReputationChange, }; use codec::Encode as _; use futures::{channel::oneshot, prelude::*}; use libp2p::{ core::{either::EitherError, upgrade, ConnectedPoint, Executor}, + kad::record::Key as KademliaKey, multiaddr, ping::Failure as PingFailure, swarm::{ @@ -60,7 +61,17 @@ use metrics::{Histogram, HistogramVec, MetricSources, Metrics}; use parking_lot::Mutex; use sc_client_api::{BlockBackend, ProofProvider}; use sc_consensus::{BlockImportError, BlockImportStatus, ImportQueue, Link}; -use sc_network_common::sync::{SyncState, SyncStatus}; +use sc_network_common::{ + protocol::event::{DhtEvent, Event}, + request_responses::{IfDisconnected, RequestFailure}, + service::{ + NetworkDHTProvider, NetworkEventStream, NetworkNotification, NetworkPeers, NetworkSigner, + NetworkStateInfo, NetworkStatus, NetworkStatusProvider, NetworkSyncForkRequest, + NotificationSender as NotificationSenderT, NotificationSenderError, + NotificationSenderReady as NotificationSenderReadyT, Signature, SigningError, + }, + sync::{SyncState, SyncStatus}, +}; use sc_peerset::PeersetHandle; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; use sp_blockchain::{HeaderBackend, HeaderMetadata}; @@ -81,24 +92,15 @@ use std::{ task::Poll, }; -pub use behaviour::{ - IfDisconnected, InboundFailure, OutboundFailure, RequestFailure, ResponseFailure, -}; +pub use behaviour::{InboundFailure, OutboundFailure, ResponseFailure}; mod metrics; mod out_events; -mod signature; #[cfg(test)] mod tests; -pub use libp2p::{ - identity::{ - error::{DecodingError, SigningError}, - Keypair, PublicKey, - }, - kad::record::Key as KademliaKey, -}; -pub use signature::Signature; +pub use libp2p::identity::{error::DecodingError, Keypair, PublicKey}; +use sc_network_common::service::{NetworkBlock, NetworkRequest, NetworkTransaction}; /// Substrate network service. Handles network IO and manages connectivity. pub struct NetworkService { @@ -723,289 +725,6 @@ where } impl NetworkService { - /// Returns the local `PeerId`. - pub fn local_peer_id(&self) -> &PeerId { - &self.local_peer_id - } - - /// Signs the message with the `KeyPair` that defined the local `PeerId`. - pub fn sign_with_local_identity( - &self, - msg: impl AsRef<[u8]>, - ) -> Result { - Signature::sign_message(msg.as_ref(), &self.local_identity) - } - - /// Set authorized peers. - /// - /// Need a better solution to manage authorized peers, but now just use reserved peers for - /// prototyping. - pub fn set_authorized_peers(&self, peers: HashSet) { - let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::SetReserved(peers)); - } - - /// Set authorized_only flag. - /// - /// Need a better solution to decide authorized_only, but now just use reserved_only flag for - /// prototyping. - pub fn set_authorized_only(&self, reserved_only: bool) { - let _ = self - .to_worker - .unbounded_send(ServiceToWorkerMsg::SetReservedOnly(reserved_only)); - } - - /// Adds an address known to a node. - pub fn add_known_address(&self, peer_id: PeerId, addr: Multiaddr) { - let _ = self - .to_worker - .unbounded_send(ServiceToWorkerMsg::AddKnownAddress(peer_id, addr)); - } - - /// Appends a notification to the buffer of pending outgoing notifications with the given peer. - /// Has no effect if the notifications channel with this protocol name is not open. - /// - /// If the buffer of pending outgoing notifications with that peer is full, the notification - /// is silently dropped and the connection to the remote will start being shut down. This - /// happens if you call this method at a higher rate than the rate at which the peer processes - /// these notifications, or if the available network bandwidth is too low. - /// - /// For this reason, this method is considered soft-deprecated. You are encouraged to use - /// [`NetworkService::notification_sender`] instead. - /// - /// > **Note**: The reason why this is a no-op in the situation where we have no channel is - /// > that we don't guarantee message delivery anyway. Networking issues can cause - /// > connections to drop at any time, and higher-level logic shouldn't differentiate - /// > between the remote voluntarily closing a substream or a network error - /// > preventing the message from being delivered. - /// - /// The protocol must have been registered with - /// `crate::config::NetworkConfiguration::notifications_protocols`. - pub fn write_notification( - &self, - target: PeerId, - protocol: Cow<'static, str>, - message: Vec, - ) { - // We clone the `NotificationsSink` in order to be able to unlock the network-wide - // `peers_notifications_sinks` mutex as soon as possible. - let sink = { - let peers_notifications_sinks = self.peers_notifications_sinks.lock(); - if let Some(sink) = peers_notifications_sinks.get(&(target, protocol.clone())) { - sink.clone() - } else { - // Notification silently discarded, as documented. - debug!( - target: "sub-libp2p", - "Attempted to send notification on missing or closed substream: {}, {:?}", - target, protocol, - ); - return - } - }; - - if let Some(notifications_sizes_metric) = self.notifications_sizes_metric.as_ref() { - notifications_sizes_metric - .with_label_values(&["out", &protocol]) - .observe(message.len() as f64); - } - - // Sending is communicated to the `NotificationsSink`. - trace!( - target: "sub-libp2p", - "External API => Notification({:?}, {:?}, {} bytes)", - target, protocol, message.len() - ); - trace!(target: "sub-libp2p", "Handler({:?}) <= Sync notification", target); - sink.send_sync_notification(message); - } - - /// Obtains a [`NotificationSender`] for a connected peer, if it exists. - /// - /// A `NotificationSender` is scoped to a particular connection to the peer that holds - /// a receiver. With a `NotificationSender` at hand, sending a notification is done in two - /// steps: - /// - /// 1. [`NotificationSender::ready`] is used to wait for the sender to become ready - /// for another notification, yielding a [`NotificationSenderReady`] token. - /// 2. [`NotificationSenderReady::send`] enqueues the notification for sending. This operation - /// can only fail if the underlying notification substream or connection has suddenly closed. - /// - /// An error is returned by [`NotificationSenderReady::send`] if there exists no open - /// notifications substream with that combination of peer and protocol, or if the remote - /// has asked to close the notifications substream. If that happens, it is guaranteed that an - /// [`Event::NotificationStreamClosed`] has been generated on the stream returned by - /// [`NetworkService::event_stream`]. - /// - /// If the remote requests to close the notifications substream, all notifications successfully - /// enqueued using [`NotificationSenderReady::send`] will finish being sent out before the - /// substream actually gets closed, but attempting to enqueue more notifications will now - /// return an error. It is however possible for the entire connection to be abruptly closed, - /// in which case enqueued notifications will be lost. - /// - /// The protocol must have been registered with - /// `crate::config::NetworkConfiguration::notifications_protocols`. - /// - /// # Usage - /// - /// This method returns a struct that allows waiting until there is space available in the - /// buffer of messages towards the given peer. If the peer processes notifications at a slower - /// rate than we send them, this buffer will quickly fill up. - /// - /// As such, you should never do something like this: - /// - /// ```ignore - /// // Do NOT do this - /// for peer in peers { - /// if let Ok(n) = network.notification_sender(peer, ...) { - /// if let Ok(s) = n.ready().await { - /// let _ = s.send(...); - /// } - /// } - /// } - /// ``` - /// - /// Doing so would slow down all peers to the rate of the slowest one. A malicious or - /// malfunctioning peer could intentionally process notifications at a very slow rate. - /// - /// Instead, you are encouraged to maintain your own buffer of notifications on top of the one - /// maintained by `sc-network`, and use `notification_sender` to progressively send out - /// elements from your buffer. If this additional buffer is full (which will happen at some - /// point if the peer is too slow to process notifications), appropriate measures can be taken, - /// such as removing non-critical notifications from the buffer or disconnecting the peer - /// using [`NetworkService::disconnect_peer`]. - /// - /// - /// Notifications Per-peer buffer - /// broadcast +-------> of notifications +--> `notification_sender` +--> Internet - /// ^ (not covered by - /// | sc-network) - /// + - /// Notifications should be dropped - /// if buffer is full - /// - /// - /// See also the `sc-network-gossip` crate for a higher-level way to send notifications. - pub fn notification_sender( - &self, - target: PeerId, - protocol: Cow<'static, str>, - ) -> Result { - // We clone the `NotificationsSink` in order to be able to unlock the network-wide - // `peers_notifications_sinks` mutex as soon as possible. - let sink = { - let peers_notifications_sinks = self.peers_notifications_sinks.lock(); - if let Some(sink) = peers_notifications_sinks.get(&(target, protocol.clone())) { - sink.clone() - } else { - return Err(NotificationSenderError::Closed) - } - }; - - let notification_size_metric = self - .notifications_sizes_metric - .as_ref() - .map(|histogram| histogram.with_label_values(&["out", &protocol])); - - Ok(NotificationSender { sink, protocol_name: protocol, notification_size_metric }) - } - - /// Returns a stream containing the events that happen on the network. - /// - /// If this method is called multiple times, the events are duplicated. - /// - /// The stream never ends (unless the `NetworkWorker` gets shut down). - /// - /// The name passed is used to identify the channel in the Prometheus metrics. Note that the - /// parameter is a `&'static str`, and not a `String`, in order to avoid accidentally having - /// an unbounded set of Prometheus metrics, which would be quite bad in terms of memory - pub fn event_stream(&self, name: &'static str) -> impl Stream { - let (tx, rx) = out_events::channel(name); - let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::EventStream(tx)); - rx - } - - /// Sends a single targeted request to a specific peer. On success, returns the response of - /// the peer. - /// - /// Request-response protocols are a way to complement notifications protocols, but - /// notifications should remain the default ways of communicating information. For example, a - /// peer can announce something through a notification, after which the recipient can obtain - /// more information by performing a request. - /// As such, call this function with `IfDisconnected::ImmediateError` for `connect`. This way - /// you will get an error immediately for disconnected peers, instead of waiting for a - /// potentially very long connection attempt, which would suggest that something is wrong - /// anyway, as you are supposed to be connected because of the notification protocol. - /// - /// No limit or throttling of concurrent outbound requests per peer and protocol are enforced. - /// Such restrictions, if desired, need to be enforced at the call site(s). - /// - /// The protocol must have been registered through - /// [`NetworkConfiguration::request_response_protocols`]( - /// crate::config::NetworkConfiguration::request_response_protocols). - pub async fn request( - &self, - target: PeerId, - protocol: impl Into>, - request: Vec, - connect: IfDisconnected, - ) -> Result, RequestFailure> { - let (tx, rx) = oneshot::channel(); - - self.start_request(target, protocol, request, tx, connect); - - match rx.await { - Ok(v) => v, - // The channel can only be closed if the network worker no longer exists. If the - // network worker no longer exists, then all connections to `target` are necessarily - // closed, and we legitimately report this situation as a "ConnectionClosed". - Err(_) => Err(RequestFailure::Network(OutboundFailure::ConnectionClosed)), - } - } - - /// Variation of `request` which starts a request whose response is delivered on a provided - /// channel. - /// - /// Instead of blocking and waiting for a reply, this function returns immediately, sending - /// responses via the passed in sender. This alternative API exists to make it easier to - /// integrate with message passing APIs. - /// - /// Keep in mind that the connected receiver might receive a `Canceled` event in case of a - /// closing connection. This is expected behaviour. With `request` you would get a - /// `RequestFailure::Network(OutboundFailure::ConnectionClosed)` in that case. - pub fn start_request( - &self, - target: PeerId, - protocol: impl Into>, - request: Vec, - tx: oneshot::Sender, RequestFailure>>, - connect: IfDisconnected, - ) { - let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::Request { - target, - protocol: protocol.into(), - request, - pending_response: tx, - connect, - }); - } - - /// High-level network status information. - /// - /// Returns an error if the `NetworkWorker` is no longer running. - pub async fn status(&self) -> Result, ()> { - let (tx, rx) = oneshot::channel(); - - let _ = self - .to_worker - .unbounded_send(ServiceToWorkerMsg::NetworkStatus { pending_response: tx }); - - match rx.await { - Ok(v) => v.map_err(|_| ()), - // The channel can only be closed if the network worker no longer exists. - Err(_) => Err(()), - } - } - /// Get network state. /// /// **Note**: Use this only for debugging. This API is unstable. There are warnings literally @@ -1026,74 +745,97 @@ impl NetworkService { } } - /// You may call this when new transactions are imported by the transaction pool. - /// - /// All transactions will be fetched from the `TransactionPool` that was passed at - /// initialization as part of the configuration and propagated to peers. - pub fn trigger_repropagate(&self) { - let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::PropagateTransactions); - } - - /// You must call when new transaction is imported by the transaction pool. + /// Utility function to extract `PeerId` from each `Multiaddr` for peer set updates. /// - /// This transaction will be fetched from the `TransactionPool` that was passed at - /// initialization as part of the configuration and propagated to peers. - pub fn propagate_transaction(&self, hash: H) { - let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::PropagateTransaction(hash)); - } + /// Returns an `Err` if one of the given addresses is invalid or contains an + /// invalid peer ID (which includes the local peer ID). + fn split_multiaddr_and_peer_id( + &self, + peers: HashSet, + ) -> Result, String> { + peers + .into_iter() + .map(|mut addr| { + let peer = match addr.pop() { + Some(multiaddr::Protocol::P2p(key)) => PeerId::from_multihash(key) + .map_err(|_| "Invalid PeerId format".to_string())?, + _ => return Err("Missing PeerId from address".to_string()), + }; - /// Make sure an important block is propagated to peers. - /// - /// In chain-based consensus, we often need to make sure non-best forks are - /// at least temporarily synced. This function forces such an announcement. - pub fn announce_block(&self, hash: B::Hash, data: Option>) { - let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::AnnounceBlock(hash, data)); + // Make sure the local peer ID is never added to the PSM + // or added as a "known address", even if given. + if peer == self.local_peer_id { + Err("Local peer ID in peer set.".to_string()) + } else { + Ok((peer, addr)) + } + }) + .collect::, String>>() } +} - /// Report a given peer as either beneficial (+) or costly (-) according to the - /// given scalar. - pub fn report_peer(&self, who: PeerId, cost_benefit: ReputationChange) { - self.peerset.report_peer(who, cost_benefit); +impl sp_consensus::SyncOracle for NetworkService { + fn is_major_syncing(&self) -> bool { + self.is_major_syncing.load(Ordering::Relaxed) } - /// Disconnect from a node as soon as possible. - /// - /// This triggers the same effects as if the connection had closed itself spontaneously. - /// - /// See also [`NetworkService::remove_from_peers_set`], which has the same effect but also - /// prevents the local node from re-establishing an outgoing substream to this peer until it - /// is added again. - pub fn disconnect_peer(&self, who: PeerId, protocol: impl Into>) { - let _ = self - .to_worker - .unbounded_send(ServiceToWorkerMsg::DisconnectPeer(who, protocol.into())); + fn is_offline(&self) -> bool { + self.num_connected.load(Ordering::Relaxed) == 0 } +} +impl sc_consensus::JustificationSyncLink for NetworkService { /// Request a justification for the given block from the network. /// /// On success, the justification will be passed to the import queue that was part at /// initialization as part of the configuration. - pub fn request_justification(&self, hash: &B::Hash, number: NumberFor) { + fn request_justification(&self, hash: &B::Hash, number: NumberFor) { let _ = self .to_worker .unbounded_send(ServiceToWorkerMsg::RequestJustification(*hash, number)); } - /// Clear all pending justification requests. - pub fn clear_justification_requests(&self) { + fn clear_justification_requests(&self) { let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::ClearJustificationRequests); } +} - /// Are we in the process of downloading the chain? - pub fn is_major_syncing(&self) -> bool { - self.is_major_syncing.load(Ordering::Relaxed) +impl NetworkStateInfo for NetworkService +where + B: sp_runtime::traits::Block, + H: ExHashT, +{ + /// Returns the local external addresses. + fn external_addresses(&self) -> Vec { + self.external_addresses.lock().clone() + } + + /// Returns the local Peer ID. + fn local_peer_id(&self) -> PeerId { + self.local_peer_id } +} +impl NetworkSigner for NetworkService +where + B: sp_runtime::traits::Block, + H: ExHashT, +{ + fn sign_with_local_identity(&self, msg: impl AsRef<[u8]>) -> Result { + Signature::sign_message(msg.as_ref(), &self.local_identity) + } +} + +impl NetworkDHTProvider for NetworkService +where + B: BlockT + 'static, + H: ExHashT, +{ /// Start getting a value from the DHT. /// /// This will generate either a `ValueFound` or a `ValueNotFound` event and pass it as an /// item on the [`NetworkWorker`] stream. - pub fn get_value(&self, key: &KademliaKey) { + fn get_value(&self, key: &KademliaKey) { let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::GetValue(key.clone())); } @@ -1101,27 +843,86 @@ impl NetworkService { /// /// This will generate either a `ValuePut` or a `ValuePutFailed` event and pass it as an /// item on the [`NetworkWorker`] stream. - pub fn put_value(&self, key: KademliaKey, value: Vec) { + fn put_value(&self, key: KademliaKey, value: Vec) { let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::PutValue(key, value)); } +} + +impl NetworkSyncForkRequest> for NetworkService +where + B: BlockT + 'static, + H: ExHashT, +{ + /// Configure an explicit fork sync request. + /// Note that this function should not be used for recent blocks. + /// Sync should be able to download all the recent forks normally. + /// `set_sync_fork_request` should only be used if external code detects that there's + /// a stale fork missing. + /// Passing empty `peers` set effectively removes the sync request. + fn set_sync_fork_request(&self, peers: Vec, hash: B::Hash, number: NumberFor) { + let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::SyncFork(peers, hash, number)); + } +} - /// Connect to unreserved peers and allow unreserved peers to connect for syncing purposes. - pub fn accept_unreserved_peers(&self) { +#[async_trait::async_trait] +impl NetworkStatusProvider for NetworkService +where + B: BlockT + 'static, + H: ExHashT, +{ + async fn status(&self) -> Result, ()> { + let (tx, rx) = oneshot::channel(); + + let _ = self + .to_worker + .unbounded_send(ServiceToWorkerMsg::NetworkStatus { pending_response: tx }); + + match rx.await { + Ok(v) => v.map_err(|_| ()), + // The channel can only be closed if the network worker no longer exists. + Err(_) => Err(()), + } + } +} + +impl NetworkPeers for NetworkService +where + B: BlockT + 'static, + H: ExHashT, +{ + fn set_authorized_peers(&self, peers: HashSet) { + let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::SetReserved(peers)); + } + + fn set_authorized_only(&self, reserved_only: bool) { + let _ = self + .to_worker + .unbounded_send(ServiceToWorkerMsg::SetReservedOnly(reserved_only)); + } + + fn add_known_address(&self, peer_id: PeerId, addr: Multiaddr) { + let _ = self + .to_worker + .unbounded_send(ServiceToWorkerMsg::AddKnownAddress(peer_id, addr)); + } + + fn report_peer(&self, who: PeerId, cost_benefit: ReputationChange) { + self.peerset.report_peer(who, cost_benefit); + } + + fn disconnect_peer(&self, who: PeerId, protocol: Cow<'static, str>) { + let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::DisconnectPeer(who, protocol)); + } + + fn accept_unreserved_peers(&self) { let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::SetReservedOnly(false)); } - /// Disconnect from unreserved peers and deny new unreserved peers to connect for syncing - /// purposes. - pub fn deny_unreserved_peers(&self) { + fn deny_unreserved_peers(&self) { let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::SetReservedOnly(true)); } - /// Adds a `PeerId` and its address as reserved. The string should encode the address - /// and peer ID of the remote node. - /// - /// Returns an `Err` if the given string is not a valid multiaddress - /// or contains an invalid peer ID (which includes the local peer ID). - pub fn add_reserved_peer(&self, peer: String) -> Result<(), String> { + fn add_reserved_peer(&self, peer: String) -> Result<(), String> { let (peer_id, addr) = parse_str_addr(&peer).map_err(|e| format!("{:?}", e))?; // Make sure the local peer ID is never added to the PSM. if peer_id == self.local_peer_id { @@ -1135,28 +936,11 @@ impl NetworkService { Ok(()) } - /// Removes a `PeerId` from the list of reserved peers. - pub fn remove_reserved_peer(&self, peer_id: PeerId) { + fn remove_reserved_peer(&self, peer_id: PeerId) { let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::RemoveReserved(peer_id)); } - /// Sets the reserved set of a protocol to the given set of peers. - /// - /// Each `Multiaddr` must end with a `/p2p/` component containing the `PeerId`. It can also - /// consist of only `/p2p/`. - /// - /// The node will start establishing/accepting connections and substreams to/from peers in this - /// set, if it doesn't have any substream open with them yet. - /// - /// Note however, if a call to this function results in less peers on the reserved set, they - /// will not necessarily get disconnected (depending on available free slots in the peer set). - /// If you want to also disconnect those removed peers, you will have to call - /// `remove_from_peers_set` on those in addition to updating the reserved set. You can omit - /// this step if the peer set is in reserved only mode. - /// - /// Returns an `Err` if one of the given addresses is invalid or contains an - /// invalid peer ID (which includes the local peer ID). - pub fn set_reserved_peers( + fn set_reserved_peers( &self, protocol: Cow<'static, str>, peers: HashSet, @@ -1187,14 +971,7 @@ impl NetworkService { Ok(()) } - /// Add peers to a peer set. - /// - /// Each `Multiaddr` must end with a `/p2p/` component containing the `PeerId`. It can also - /// consist of only `/p2p/`. - /// - /// Returns an `Err` if one of the given addresses is invalid or contains an - /// invalid peer ID (which includes the local peer ID). - pub fn add_peers_to_reserved_set( + fn add_peers_to_reserved_set( &self, protocol: Cow<'static, str>, peers: HashSet, @@ -1220,8 +997,7 @@ impl NetworkService { Ok(()) } - /// Remove peers from a peer set. - pub fn remove_peers_from_reserved_set(&self, protocol: Cow<'static, str>, peers: Vec) { + fn remove_peers_from_reserved_set(&self, protocol: Cow<'static, str>, peers: Vec) { for peer_id in peers.into_iter() { let _ = self .to_worker @@ -1229,26 +1005,7 @@ impl NetworkService { } } - /// Configure an explicit fork sync request. - /// Note that this function should not be used for recent blocks. - /// Sync should be able to download all the recent forks normally. - /// `set_sync_fork_request` should only be used if external code detects that there's - /// a stale fork missing. - /// Passing empty `peers` set effectively removes the sync request. - pub fn set_sync_fork_request(&self, peers: Vec, hash: B::Hash, number: NumberFor) { - let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::SyncFork(peers, hash, number)); - } - - /// Add a peer to a set of peers. - /// - /// If the set has slots available, it will try to open a substream with this peer. - /// - /// Each `Multiaddr` must end with a `/p2p/` component containing the `PeerId`. It can also - /// consist of only `/p2p/`. - /// - /// Returns an `Err` if one of the given addresses is invalid or contains an - /// invalid peer ID (which includes the local peer ID). - pub fn add_to_peers_set( + fn add_to_peers_set( &self, protocol: Cow<'static, str>, peers: HashSet, @@ -1274,10 +1031,7 @@ impl NetworkService { Ok(()) } - /// Remove peers from a peer set. - /// - /// If we currently have an open substream with this peer, it will soon be closed. - pub fn remove_from_peers_set(&self, protocol: Cow<'static, str>, peers: Vec) { + fn remove_from_peers_set(&self, protocol: Cow<'static, str>, peers: Vec) { for peer_id in peers.into_iter() { let _ = self .to_worker @@ -1285,90 +1039,158 @@ impl NetworkService { } } - /// Returns the number of peers we're connected to. - pub fn num_connected(&self) -> usize { + fn sync_num_connected(&self) -> usize { self.num_connected.load(Ordering::Relaxed) } +} - /// Inform the network service about new best imported block. - pub fn new_best_block_imported(&self, hash: B::Hash, number: NumberFor) { - let _ = self - .to_worker - .unbounded_send(ServiceToWorkerMsg::NewBestBlockImported(hash, number)); +impl NetworkEventStream for NetworkService +where + B: BlockT + 'static, + H: ExHashT, +{ + fn event_stream(&self, name: &'static str) -> Pin + Send>> { + let (tx, rx) = out_events::channel(name); + let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::EventStream(tx)); + Box::pin(rx) } +} - /// Utility function to extract `PeerId` from each `Multiaddr` for peer set updates. - /// - /// Returns an `Err` if one of the given addresses is invalid or contains an - /// invalid peer ID (which includes the local peer ID). - fn split_multiaddr_and_peer_id( - &self, - peers: HashSet, - ) -> Result, String> { - peers - .into_iter() - .map(|mut addr| { - let peer = match addr.pop() { - Some(multiaddr::Protocol::P2p(key)) => PeerId::from_multihash(key) - .map_err(|_| "Invalid PeerId format".to_string())?, - _ => return Err("Missing PeerId from address".to_string()), - }; +impl NetworkNotification for NetworkService +where + B: BlockT + 'static, + H: ExHashT, +{ + fn write_notification(&self, target: PeerId, protocol: Cow<'static, str>, message: Vec) { + // We clone the `NotificationsSink` in order to be able to unlock the network-wide + // `peers_notifications_sinks` mutex as soon as possible. + let sink = { + let peers_notifications_sinks = self.peers_notifications_sinks.lock(); + if let Some(sink) = peers_notifications_sinks.get(&(target, protocol.clone())) { + sink.clone() + } else { + // Notification silently discarded, as documented. + debug!( + target: "sub-libp2p", + "Attempted to send notification on missing or closed substream: {}, {:?}", + target, protocol, + ); + return + } + }; - // Make sure the local peer ID is never added to the PSM - // or added as a "known address", even if given. - if peer == self.local_peer_id { - Err("Local peer ID in peer set.".to_string()) - } else { - Ok((peer, addr)) - } - }) - .collect::, String>>() - } -} + if let Some(notifications_sizes_metric) = self.notifications_sizes_metric.as_ref() { + notifications_sizes_metric + .with_label_values(&["out", &protocol]) + .observe(message.len() as f64); + } -impl sp_consensus::SyncOracle for NetworkService { - fn is_major_syncing(&mut self) -> bool { - Self::is_major_syncing(self) + // Sending is communicated to the `NotificationsSink`. + trace!( + target: "sub-libp2p", + "External API => Notification({:?}, {:?}, {} bytes)", + target, protocol, message.len() + ); + trace!(target: "sub-libp2p", "Handler({:?}) <= Sync notification", target); + sink.send_sync_notification(message); } - fn is_offline(&mut self) -> bool { - self.num_connected.load(Ordering::Relaxed) == 0 + fn notification_sender( + &self, + target: PeerId, + protocol: Cow<'static, str>, + ) -> Result, NotificationSenderError> { + // We clone the `NotificationsSink` in order to be able to unlock the network-wide + // `peers_notifications_sinks` mutex as soon as possible. + let sink = { + let peers_notifications_sinks = self.peers_notifications_sinks.lock(); + if let Some(sink) = peers_notifications_sinks.get(&(target, protocol.clone())) { + sink.clone() + } else { + return Err(NotificationSenderError::Closed) + } + }; + + let notification_size_metric = self + .notifications_sizes_metric + .as_ref() + .map(|histogram| histogram.with_label_values(&["out", &protocol])); + + Ok(Box::new(NotificationSender { sink, protocol_name: protocol, notification_size_metric })) } } -impl<'a, B: BlockT + 'static, H: ExHashT> sp_consensus::SyncOracle for &'a NetworkService { - fn is_major_syncing(&mut self) -> bool { - NetworkService::is_major_syncing(self) +#[async_trait::async_trait] +impl NetworkRequest for NetworkService +where + B: BlockT + 'static, + H: ExHashT, +{ + async fn request( + &self, + target: PeerId, + protocol: Cow<'static, str>, + request: Vec, + connect: IfDisconnected, + ) -> Result, RequestFailure> { + let (tx, rx) = oneshot::channel(); + + self.start_request(target, protocol, request, tx, connect); + + match rx.await { + Ok(v) => v, + // The channel can only be closed if the network worker no longer exists. If the + // network worker no longer exists, then all connections to `target` are necessarily + // closed, and we legitimately report this situation as a "ConnectionClosed". + Err(_) => Err(RequestFailure::Network(OutboundFailure::ConnectionClosed)), + } } - fn is_offline(&mut self) -> bool { - self.num_connected.load(Ordering::Relaxed) == 0 + fn start_request( + &self, + target: PeerId, + protocol: Cow<'static, str>, + request: Vec, + tx: oneshot::Sender, RequestFailure>>, + connect: IfDisconnected, + ) { + let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::Request { + target, + protocol: protocol.into(), + request, + pending_response: tx, + connect, + }); } } -impl sc_consensus::JustificationSyncLink for NetworkService { - fn request_justification(&self, hash: &B::Hash, number: NumberFor) { - Self::request_justification(self, hash, number); +impl NetworkTransaction for NetworkService +where + B: BlockT + 'static, + H: ExHashT, +{ + fn trigger_repropagate(&self) { + let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::PropagateTransactions); } - fn clear_justification_requests(&self) { - Self::clear_justification_requests(self); + fn propagate_transaction(&self, hash: H) { + let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::PropagateTransaction(hash)); } } -impl NetworkStateInfo for NetworkService +impl NetworkBlock> for NetworkService where - B: sp_runtime::traits::Block, + B: BlockT + 'static, H: ExHashT, { - /// Returns the local external addresses. - fn external_addresses(&self) -> Vec { - self.external_addresses.lock().clone() + fn announce_block(&self, hash: B::Hash, data: Option>) { + let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::AnnounceBlock(hash, data)); } - /// Returns the local Peer ID. - fn local_peer_id(&self) -> PeerId { - self.local_peer_id + fn new_best_block_imported(&self, hash: B::Hash, number: NumberFor) { + let _ = self + .to_worker + .unbounded_send(ServiceToWorkerMsg::NewBestBlockImported(hash, number)); } } @@ -1385,26 +1207,27 @@ pub struct NotificationSender { notification_size_metric: Option, } -impl NotificationSender { - /// Returns a future that resolves when the `NotificationSender` is ready to send a - /// notification. - pub async fn ready(&self) -> Result, NotificationSenderError> { - Ok(NotificationSenderReady { +#[async_trait::async_trait] +impl NotificationSenderT for NotificationSender { + async fn ready( + &self, + ) -> Result, NotificationSenderError> { + Ok(Box::new(NotificationSenderReady { ready: match self.sink.reserve_notification().await { - Ok(r) => r, + Ok(r) => Some(r), Err(()) => return Err(NotificationSenderError::Closed), }, peer_id: self.sink.peer_id(), protocol_name: &self.protocol_name, notification_size_metric: self.notification_size_metric.clone(), - }) + })) } } /// Reserved slot in the notifications buffer, ready to accept data. #[must_use] pub struct NotificationSenderReady<'a> { - ready: Ready<'a>, + ready: Option>, /// Target of the notification. peer_id: &'a PeerId, @@ -1417,11 +1240,8 @@ pub struct NotificationSenderReady<'a> { notification_size_metric: Option, } -impl<'a> NotificationSenderReady<'a> { - /// Consumes this slots reservation and actually queues the notification. - pub fn send(self, notification: impl Into>) -> Result<(), NotificationSenderError> { - let notification = notification.into(); - +impl<'a> NotificationSenderReadyT for NotificationSenderReady<'a> { + fn send(&mut self, notification: Vec) -> Result<(), NotificationSenderError> { if let Some(notification_size_metric) = &self.notification_size_metric { notification_size_metric.observe(notification.len() as f64); } @@ -1433,26 +1253,14 @@ impl<'a> NotificationSenderReady<'a> { ); trace!(target: "sub-libp2p", "Handler({:?}) <= Async notification", self.peer_id); - self.ready.send(notification).map_err(|()| NotificationSenderError::Closed) + self.ready + .take() + .ok_or(NotificationSenderError::Closed)? + .send(notification) + .map_err(|()| NotificationSenderError::Closed) } } -/// Error returned by [`NetworkService::send_notification`]. -#[derive(Debug, thiserror::Error)] -pub enum NotificationSenderError { - /// The notification receiver has been closed, usually because the underlying connection - /// closed. - /// - /// Some of the notifications most recently sent may not have been received. However, - /// the peer may still be connected and a new `NotificationSender` for the same - /// protocol obtained from [`NetworkService::notification_sender`]. - #[error("The notification receiver has been closed")] - Closed, - /// Protocol name hasn't been registered. - #[error("Protocol name hasn't been registered")] - BadProtocol, -} - /// Messages sent from the `NetworkService` to the `NetworkWorker`. /// /// Each entry corresponds to a method of `NetworkService`. diff --git a/client/network/src/service/out_events.rs b/client/network/src/service/out_events.rs index c95b46af4cefa..4144d7f19551e 100644 --- a/client/network/src/service/out_events.rs +++ b/client/network/src/service/out_events.rs @@ -31,11 +31,10 @@ //! - Send events by calling [`OutChannels::send`]. Events are cloned for each sender in the //! collection. -use crate::Event; - use futures::{channel::mpsc, prelude::*, ready, stream::FusedStream}; use parking_lot::Mutex; use prometheus_endpoint::{register, CounterVec, GaugeVec, Opts, PrometheusError, Registry, U64}; +use sc_network_common::protocol::event::Event; use std::{ cell::RefCell, fmt, diff --git a/client/network/src/service/tests.rs b/client/network/src/service/tests.rs index f757bf4891fbc..6ccca17650b67 100644 --- a/client/network/src/service/tests.rs +++ b/client/network/src/service/tests.rs @@ -16,11 +16,15 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::{config, Event, NetworkService, NetworkWorker}; +use crate::{config, NetworkService, NetworkWorker}; use futures::prelude::*; use libp2p::PeerId; -use sc_network_common::config::ProtocolId; +use sc_network_common::{ + config::ProtocolId, + protocol::event::Event, + service::{NetworkEventStream, NetworkNotification, NetworkPeers, NetworkStateInfo}, +}; use sc_network_light::light_client_requests::handler::LightClientRequestHandler; use sc_network_sync::{ block_request_handler::BlockRequestHandler, state_request_handler::StateRequestHandler, @@ -192,7 +196,7 @@ fn build_nodes_one_proto() -> ( set_config: config::SetConfig { reserved_nodes: vec![config::MultiaddrWithPeerId { multiaddr: listen_addr, - peer_id: node1.local_peer_id().clone(), + peer_id: node1.local_peer_id(), }], ..Default::default() }, @@ -214,18 +218,10 @@ fn notifications_state_consistent() { // Write some initial notifications that shouldn't get through. for _ in 0..(rand::random::() % 5) { - node1.write_notification( - node2.local_peer_id().clone(), - PROTOCOL_NAME, - b"hello world".to_vec(), - ); + node1.write_notification(node2.local_peer_id(), PROTOCOL_NAME, b"hello world".to_vec()); } for _ in 0..(rand::random::() % 5) { - node2.write_notification( - node1.local_peer_id().clone(), - PROTOCOL_NAME, - b"hello world".to_vec(), - ); + node2.write_notification(node1.local_peer_id(), PROTOCOL_NAME, b"hello world".to_vec()); } async_std::task::block_on(async move { @@ -249,14 +245,14 @@ fn notifications_state_consistent() { // test consists in ensuring that notifications get ignored if the stream isn't open. if rand::random::() % 5 >= 3 { node1.write_notification( - node2.local_peer_id().clone(), + node2.local_peer_id(), PROTOCOL_NAME, b"hello world".to_vec(), ); } if rand::random::() % 5 >= 3 { node2.write_notification( - node1.local_peer_id().clone(), + node1.local_peer_id(), PROTOCOL_NAME, b"hello world".to_vec(), ); @@ -264,10 +260,10 @@ fn notifications_state_consistent() { // Also randomly disconnect the two nodes from time to time. if rand::random::() % 20 == 0 { - node1.disconnect_peer(node2.local_peer_id().clone(), PROTOCOL_NAME); + node1.disconnect_peer(node2.local_peer_id(), PROTOCOL_NAME); } if rand::random::() % 20 == 0 { - node2.disconnect_peer(node1.local_peer_id().clone(), PROTOCOL_NAME); + node2.disconnect_peer(node1.local_peer_id(), PROTOCOL_NAME); } // Grab next event from either `events_stream1` or `events_stream2`. @@ -295,7 +291,7 @@ fn notifications_state_consistent() { something_happened = true; assert!(!node1_to_node2_open); node1_to_node2_open = true; - assert_eq!(remote, *node2.local_peer_id()); + assert_eq!(remote, node2.local_peer_id()); }, future::Either::Right(Event::NotificationStreamOpened { remote, protocol, .. @@ -304,7 +300,7 @@ fn notifications_state_consistent() { something_happened = true; assert!(!node2_to_node1_open); node2_to_node1_open = true; - assert_eq!(remote, *node1.local_peer_id()); + assert_eq!(remote, node1.local_peer_id()); }, future::Either::Left(Event::NotificationStreamClosed { remote, protocol, .. @@ -312,7 +308,7 @@ fn notifications_state_consistent() { if protocol == PROTOCOL_NAME { assert!(node1_to_node2_open); node1_to_node2_open = false; - assert_eq!(remote, *node2.local_peer_id()); + assert_eq!(remote, node2.local_peer_id()); }, future::Either::Right(Event::NotificationStreamClosed { remote, protocol, .. @@ -320,14 +316,14 @@ fn notifications_state_consistent() { if protocol == PROTOCOL_NAME { assert!(node2_to_node1_open); node2_to_node1_open = false; - assert_eq!(remote, *node1.local_peer_id()); + assert_eq!(remote, node1.local_peer_id()); }, future::Either::Left(Event::NotificationsReceived { remote, .. }) => { assert!(node1_to_node2_open); - assert_eq!(remote, *node2.local_peer_id()); + assert_eq!(remote, node2.local_peer_id()); if rand::random::() % 5 >= 4 { node1.write_notification( - node2.local_peer_id().clone(), + node2.local_peer_id(), PROTOCOL_NAME, b"hello world".to_vec(), ); @@ -335,10 +331,10 @@ fn notifications_state_consistent() { }, future::Either::Right(Event::NotificationsReceived { remote, .. }) => { assert!(node2_to_node1_open); - assert_eq!(remote, *node1.local_peer_id()); + assert_eq!(remote, node1.local_peer_id()); if rand::random::() % 5 >= 4 { node2.write_notification( - node1.local_peer_id().clone(), + node1.local_peer_id(), PROTOCOL_NAME, b"hello world".to_vec(), ); @@ -373,7 +369,7 @@ fn lots_of_incoming_peers_works() { ..config::NetworkConfiguration::new_local() }); - let main_node_peer_id = *main_node.local_peer_id(); + let main_node_peer_id = main_node.local_peer_id(); // We spawn background tasks and push them in this `Vec`. They will all be waited upon before // this test ends. @@ -476,8 +472,13 @@ fn notifications_back_pressure() { // Sending! for num in 0..TOTAL_NOTIFS { - let notif = node1.notification_sender(node2_id.clone(), PROTOCOL_NAME).unwrap(); - notif.ready().await.unwrap().send(format!("hello #{}", num)).unwrap(); + let notif = node1.notification_sender(node2_id, PROTOCOL_NAME).unwrap(); + notif + .ready() + .await + .unwrap() + .send(format!("hello #{}", num).into_bytes()) + .unwrap(); } receiver.await; @@ -514,7 +515,7 @@ fn fallback_name_working() { set_config: config::SetConfig { reserved_nodes: vec![config::MultiaddrWithPeerId { multiaddr: listen_addr, - peer_id: node1.local_peer_id().clone(), + peer_id: node1.local_peer_id(), }], ..Default::default() }, diff --git a/client/network/src/transactions.rs b/client/network/src/transactions.rs index 342b6a0430272..f7e4d774ca812 100644 --- a/client/network/src/transactions.rs +++ b/client/network/src/transactions.rs @@ -32,7 +32,7 @@ use crate::{ protocol::message, service::NetworkService, utils::{interval, LruHashSet}, - Event, ExHashT, ObservedRole, + ExHashT, }; use codec::{Decode, Encode}; @@ -40,7 +40,11 @@ use futures::{channel::mpsc, prelude::*, stream::FuturesUnordered}; use libp2p::{multiaddr, PeerId}; use log::{debug, trace, warn}; use prometheus_endpoint::{register, Counter, PrometheusError, Registry, U64}; -use sc_network_common::config::ProtocolId; +use sc_network_common::{ + config::ProtocolId, + protocol::event::{Event, ObservedRole}, + service::{NetworkEventStream, NetworkNotification, NetworkPeers}, +}; use sp_runtime::traits::Block as BlockT; use std::{ borrow::Cow, @@ -176,7 +180,7 @@ impl TransactionsHandlerPrototype { transaction_pool: Arc>, metrics_registry: Option<&Registry>, ) -> error::Result<(TransactionsHandler, TransactionsHandlerController)> { - let event_stream = service.event_stream("transactions-handler").boxed(); + let event_stream = service.event_stream("transactions-handler"); let (to_handler, from_controller) = mpsc::unbounded(); let gossip_enabled = Arc::new(AtomicBool::new(false)); diff --git a/client/network/test/src/lib.rs b/client/network/test/src/lib.rs index 09b4139c213a6..3ba663aba2267 100644 --- a/client/network/test/src/lib.rs +++ b/client/network/test/src/lib.rs @@ -45,7 +45,8 @@ use sc_client_api::{ }; use sc_consensus::{ BasicQueue, BlockCheckParams, BlockImport, BlockImportParams, BoxJustificationImport, - ForkChoiceStrategy, ImportResult, JustificationImport, LongestChain, Verifier, + ForkChoiceStrategy, ImportResult, JustificationImport, JustificationSyncLink, LongestChain, + Verifier, }; pub use sc_network::config::EmptyTransactionPool; use sc_network::{ @@ -56,8 +57,9 @@ use sc_network::{ Multiaddr, NetworkService, NetworkWorker, }; pub use sc_network_common::config::ProtocolId; -use sc_network_common::sync::warp::{ - AuthorityList, EncodedProof, SetId, VerificationResult, WarpSyncProvider, +use sc_network_common::{ + service::{NetworkBlock, NetworkStateInfo, NetworkSyncForkRequest}, + sync::warp::{AuthorityList, EncodedProof, SetId, VerificationResult, WarpSyncProvider}, }; use sc_network_light::light_client_requests::handler::LightClientRequestHandler; use sc_network_sync::{ @@ -71,7 +73,7 @@ use sp_blockchain::{ }; use sp_consensus::{ block_validation::{BlockAnnounceValidator, DefaultBlockAnnounceValidator}, - BlockOrigin, Error as ConsensusError, + BlockOrigin, Error as ConsensusError, SyncOracle, }; use sp_core::H256; use sp_runtime::{ @@ -243,7 +245,7 @@ where { /// Get this peer ID. pub fn id(&self) -> PeerId { - *self.network.service().local_peer_id() + self.network.service().local_peer_id() } /// Returns true if we're major syncing. @@ -797,7 +799,7 @@ where let addrs = connect_to .iter() .map(|v| { - let peer_id = *self.peer(*v).network_service().local_peer_id(); + let peer_id = self.peer(*v).network_service().local_peer_id(); let multiaddr = self.peer(*v).listen_addr.clone(); MultiaddrWithPeerId { peer_id, multiaddr } }) @@ -893,7 +895,7 @@ where self.mut_peers(move |peers| { for peer in peers.iter_mut() { peer.network - .add_known_address(*network.service().local_peer_id(), listen_addr.clone()); + .add_known_address(network.service().local_peer_id(), listen_addr.clone()); } let imported_blocks_stream = Box::pin(client.import_notification_stream().fuse()); diff --git a/client/offchain/Cargo.toml b/client/offchain/Cargo.toml index 8da2d4be3adde..ab26c0c38596c 100644 --- a/client/offchain/Cargo.toml +++ b/client/offchain/Cargo.toml @@ -29,6 +29,7 @@ threadpool = "1.7" tracing = "0.1.29" sc-client-api = { version = "4.0.0-dev", path = "../api" } sc-network = { version = "0.10.0-dev", path = "../network" } +sc-network-common = { version = "0.10.0-dev", path = "../network/common" } sc-utils = { version = "4.0.0-dev", path = "../utils" } sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } sp-core = { version = "6.0.0", path = "../../primitives/core" } diff --git a/client/offchain/src/api.rs b/client/offchain/src/api.rs index c80b511c84d17..f379ebad17bbf 100644 --- a/client/offchain/src/api.rs +++ b/client/offchain/src/api.rs @@ -325,19 +325,88 @@ impl AsyncApi { mod tests { use super::*; use sc_client_db::offchain::LocalStorage; - use sc_network::{NetworkStateInfo, PeerId}; + use sc_network::{PeerId, ReputationChange}; + use sc_network_common::service::{NetworkPeers, NetworkStateInfo}; use sp_core::offchain::{DbExternalities, Externalities}; - use std::time::SystemTime; + use std::{borrow::Cow, time::SystemTime}; pub(super) struct TestNetwork(); - impl NetworkProvider for TestNetwork { + impl NetworkPeers for TestNetwork { fn set_authorized_peers(&self, _peers: HashSet) { - unimplemented!() + unimplemented!(); } fn set_authorized_only(&self, _reserved_only: bool) { - unimplemented!() + unimplemented!(); + } + + fn add_known_address(&self, _peer_id: PeerId, _addr: Multiaddr) { + unimplemented!(); + } + + fn report_peer(&self, _who: PeerId, _cost_benefit: ReputationChange) { + unimplemented!(); + } + + fn disconnect_peer(&self, _who: PeerId, _protocol: Cow<'static, str>) { + unimplemented!(); + } + + fn accept_unreserved_peers(&self) { + unimplemented!(); + } + + fn deny_unreserved_peers(&self) { + unimplemented!(); + } + + fn add_reserved_peer(&self, _peer: String) -> Result<(), String> { + unimplemented!(); + } + + fn remove_reserved_peer(&self, _peer_id: PeerId) { + unimplemented!(); + } + + fn set_reserved_peers( + &self, + _protocol: Cow<'static, str>, + _peers: HashSet, + ) -> Result<(), String> { + unimplemented!(); + } + + fn add_peers_to_reserved_set( + &self, + _protocol: Cow<'static, str>, + _peers: HashSet, + ) -> Result<(), String> { + unimplemented!(); + } + + fn remove_peers_from_reserved_set( + &self, + _protocol: Cow<'static, str>, + _peers: Vec, + ) { + unimplemented!(); + } + + fn add_to_peers_set( + &self, + _protocol: Cow<'static, str>, + _peers: HashSet, + ) -> Result<(), String> { + unimplemented!(); + } + + fn remove_from_peers_set(&self, _protocol: Cow<'static, str>, _peers: Vec) { + unimplemented!(); + } + + fn sync_num_connected(&self) -> usize { + unimplemented!(); } } diff --git a/client/offchain/src/lib.rs b/client/offchain/src/lib.rs index d54d491b04c43..14a2408e61a70 100644 --- a/client/offchain/src/lib.rs +++ b/client/offchain/src/lib.rs @@ -35,14 +35,14 @@ #![warn(missing_docs)] -use std::{collections::HashSet, fmt, marker::PhantomData, sync::Arc}; +use std::{fmt, marker::PhantomData, sync::Arc}; use futures::{ future::{ready, Future}, prelude::*, }; use parking_lot::Mutex; -use sc_network::{ExHashT, NetworkService, NetworkStateInfo, PeerId}; +use sc_network_common::service::{NetworkPeers, NetworkStateInfo}; use sp_api::{ApiExt, ProvideRuntimeApi}; use sp_core::{offchain, traits::SpawnNamed, ExecutionContext}; use sp_runtime::{ @@ -60,27 +60,9 @@ const LOG_TARGET: &str = "offchain-worker"; /// NetworkProvider provides [`OffchainWorkers`] with all necessary hooks into the /// underlying Substrate networking. -pub trait NetworkProvider: NetworkStateInfo { - /// Set the authorized peers. - fn set_authorized_peers(&self, peers: HashSet); +pub trait NetworkProvider: NetworkStateInfo + NetworkPeers {} - /// Set the authorized only flag. - fn set_authorized_only(&self, reserved_only: bool); -} - -impl NetworkProvider for NetworkService -where - B: traits::Block + 'static, - H: ExHashT, -{ - fn set_authorized_peers(&self, peers: HashSet) { - NetworkService::set_authorized_peers(self, peers) - } - - fn set_authorized_only(&self, reserved_only: bool) { - NetworkService::set_authorized_only(self, reserved_only) - } -} +impl NetworkProvider for T where T: NetworkStateInfo + NetworkPeers {} /// Options for [`OffchainWorkers`] pub struct OffchainWorkerOptions { @@ -266,11 +248,11 @@ mod tests { use futures::executor::block_on; use sc_block_builder::BlockBuilderProvider as _; use sc_client_api::Backend as _; - use sc_network::{Multiaddr, PeerId}; + use sc_network::{Multiaddr, PeerId, ReputationChange}; use sc_transaction_pool::{BasicPool, FullChainApi}; use sc_transaction_pool_api::{InPoolTransaction, TransactionPool}; use sp_consensus::BlockOrigin; - use std::sync::Arc; + use std::{borrow::Cow, collections::HashSet, sync::Arc}; use substrate_test_runtime_client::{ runtime::Block, ClientBlockImportExt, DefaultTestClientBuilderExt, TestClient, TestClientBuilderExt, @@ -288,13 +270,81 @@ mod tests { } } - impl NetworkProvider for TestNetwork { + impl NetworkPeers for TestNetwork { fn set_authorized_peers(&self, _peers: HashSet) { - unimplemented!() + unimplemented!(); } fn set_authorized_only(&self, _reserved_only: bool) { - unimplemented!() + unimplemented!(); + } + + fn add_known_address(&self, _peer_id: PeerId, _addr: Multiaddr) { + unimplemented!(); + } + + fn report_peer(&self, _who: PeerId, _cost_benefit: ReputationChange) { + unimplemented!(); + } + + fn disconnect_peer(&self, _who: PeerId, _protocol: Cow<'static, str>) { + unimplemented!(); + } + + fn accept_unreserved_peers(&self) { + unimplemented!(); + } + + fn deny_unreserved_peers(&self) { + unimplemented!(); + } + + fn add_reserved_peer(&self, _peer: String) -> Result<(), String> { + unimplemented!(); + } + + fn remove_reserved_peer(&self, _peer_id: PeerId) { + unimplemented!(); + } + + fn set_reserved_peers( + &self, + _protocol: Cow<'static, str>, + _peers: HashSet, + ) -> Result<(), String> { + unimplemented!(); + } + + fn add_peers_to_reserved_set( + &self, + _protocol: Cow<'static, str>, + _peers: HashSet, + ) -> Result<(), String> { + unimplemented!(); + } + + fn remove_peers_from_reserved_set( + &self, + _protocol: Cow<'static, str>, + _peers: Vec, + ) { + unimplemented!(); + } + + fn add_to_peers_set( + &self, + _protocol: Cow<'static, str>, + _peers: HashSet, + ) -> Result<(), String> { + unimplemented!(); + } + + fn remove_from_peers_set(&self, _protocol: Cow<'static, str>, _peers: Vec) { + unimplemented!(); + } + + fn sync_num_connected(&self) -> usize { + unimplemented!(); } } diff --git a/client/service/src/builder.rs b/client/service/src/builder.rs index 55c0006de33fb..f81143583eacf 100644 --- a/client/service/src/builder.rs +++ b/client/service/src/builder.rs @@ -38,7 +38,10 @@ use sc_consensus::import_queue::ImportQueue; use sc_executor::RuntimeVersionOf; use sc_keystore::LocalKeystore; use sc_network::{config::SyncMode, NetworkService}; -use sc_network_common::sync::warp::WarpSyncProvider; +use sc_network_common::{ + service::{NetworkStateInfo, NetworkStatusProvider, NetworkTransaction}, + sync::warp::WarpSyncProvider, +}; use sc_network_light::light_client_requests::handler::LightClientRequestHandler; use sc_network_sync::{ block_request_handler::BlockRequestHandler, state_request_handler::StateRequestHandler, @@ -319,6 +322,31 @@ where ) } +/// Shared network instance implementing a set of mandatory traits. +pub trait SpawnTaskNetwork: + sc_offchain::NetworkProvider + + NetworkStateInfo + + NetworkTransaction + + NetworkStatusProvider + + Send + + Sync + + 'static +{ +} + +impl SpawnTaskNetwork for T +where + Block: BlockT, + T: sc_offchain::NetworkProvider + + NetworkStateInfo + + NetworkTransaction + + NetworkStatusProvider + + Send + + Sync + + 'static, +{ +} + /// Parameters to pass into `build`. pub struct SpawnTasksParams<'a, TBl: BlockT, TCl, TExPool, TRpc, Backend> { /// The service configuration. @@ -337,7 +365,7 @@ pub struct SpawnTasksParams<'a, TBl: BlockT, TCl, TExPool, TRpc, Backend> { pub rpc_builder: Box Result, Error>>, /// A shared network instance. - pub network: Arc::Hash>>, + pub network: Arc>, /// A Sender for RPC requests. pub system_rpc_tx: TracingUnboundedSender>, /// Telemetry instance for this node. @@ -349,7 +377,7 @@ pub fn build_offchain_workers( config: &Configuration, spawn_handle: SpawnTaskHandle, client: Arc, - network: Arc::Hash>>, + network: Arc, ) -> Option>> where TBl: BlockT, @@ -516,13 +544,14 @@ where Ok(rpc_handlers) } -async fn transaction_notifications( - transaction_pool: Arc, - network: Arc::Hash>>, +async fn transaction_notifications( + transaction_pool: Arc, + network: Network, telemetry: Option, ) where - TBl: BlockT, - TExPool: MaintainedTransactionPool::Hash>, + Block: BlockT, + ExPool: MaintainedTransactionPool::Hash>, + Network: NetworkTransaction<::Hash> + Send + Sync, { // transaction notifications transaction_pool @@ -542,13 +571,18 @@ async fn transaction_notifications( .await; } -fn init_telemetry>( +fn init_telemetry( config: &mut Configuration, - network: Arc::Hash>>, - client: Arc, + network: Network, + client: Arc, telemetry: &mut Telemetry, sysinfo: Option, -) -> sc_telemetry::Result { +) -> sc_telemetry::Result +where + Block: BlockT, + Client: BlockBackend, + Network: NetworkStateInfo, +{ let genesis_hash = client.block_hash(Zero::zero()).ok().flatten().unwrap_or_default(); let connection_message = ConnectionMessage { name: config.network.node_name.to_owned(), diff --git a/client/service/src/lib.rs b/client/service/src/lib.rs index 5291d219e8102..2f35a04451e79 100644 --- a/client/service/src/lib.rs +++ b/client/service/src/lib.rs @@ -42,9 +42,11 @@ use jsonrpsee::{core::Error as JsonRpseeError, RpcModule}; use log::{debug, error, warn}; use sc_client_api::{blockchain::HeaderBackend, BlockBackend, BlockchainEvents, ProofProvider}; use sc_network::PeerId; +use sc_network_common::service::NetworkBlock; use sc_rpc_server::WsConfig; use sc_utils::mpsc::TracingUnboundedReceiver; use sp_blockchain::HeaderMetadata; +use sp_consensus::SyncOracle; use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, Header as HeaderT}, diff --git a/client/service/src/metrics.rs b/client/service/src/metrics.rs index ef3132f61ab99..13b249a7b9563 100644 --- a/client/service/src/metrics.rs +++ b/client/service/src/metrics.rs @@ -22,7 +22,8 @@ use crate::config::Configuration; use futures_timer::Delay; use prometheus_endpoint::{register, Gauge, GaugeVec, Opts, PrometheusError, Registry, U64}; use sc_client_api::{ClientInfo, UsageProvider}; -use sc_network::{config::Role, NetworkService, NetworkStatus}; +use sc_network::config::Role; +use sc_network_common::service::{NetworkStatus, NetworkStatusProvider}; use sc_telemetry::{telemetry, TelemetryHandle, SUBSTRATE_INFO}; use sc_transaction_pool_api::{MaintainedTransactionPool, PoolStatus}; use sc_utils::metrics::register_globals; @@ -182,15 +183,16 @@ impl MetricsService { /// Returns a never-ending `Future` that performs the /// metric and telemetry updates with information from /// the given sources. - pub async fn run( + pub async fn run( mut self, client: Arc, transactions: Arc, - network: Arc::Hash>>, + network: TNet, ) where TBl: Block, TCl: ProvideRuntimeApi + UsageProvider, TExPool: MaintainedTransactionPool::Hash>, + TNet: NetworkStatusProvider, { let mut timer = Delay::new(Duration::from_secs(0)); let timer_interval = Duration::from_secs(5); diff --git a/client/service/test/Cargo.toml b/client/service/test/Cargo.toml index d003db57eb7ac..01c3ee2348ef5 100644 --- a/client/service/test/Cargo.toml +++ b/client/service/test/Cargo.toml @@ -27,6 +27,7 @@ sc-client-db = { version = "0.10.0-dev", default-features = false, path = "../.. sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } sc-executor = { version = "0.10.0-dev", path = "../../executor" } sc-network = { version = "0.10.0-dev", path = "../../network" } +sc-network-common = { version = "0.10.0-dev", path = "../../network/common" } sc-service = { version = "0.10.0-dev", features = ["test-helpers"], path = "../../service" } sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../../client/transaction-pool/api" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } diff --git a/client/service/test/src/lib.rs b/client/service/test/src/lib.rs index 2d63362daffba..9c720c6fedea0 100644 --- a/client/service/test/src/lib.rs +++ b/client/service/test/src/lib.rs @@ -26,6 +26,7 @@ use sc_network::{ config::{NetworkConfiguration, TransportConfig}, multiaddr, Multiaddr, }; +use sc_network_common::service::{NetworkBlock, NetworkPeers, NetworkStateInfo}; use sc_service::{ client::Client, config::{BasePath, DatabaseSource, KeystoreConfig}, @@ -320,7 +321,7 @@ where handle.spawn(service.clone().map_err(|_| ())); let addr = - addr.with(multiaddr::Protocol::P2p((*service.network().local_peer_id()).into())); + addr.with(multiaddr::Protocol::P2p((service.network().local_peer_id()).into())); self.authority_nodes.push((self.nodes, service, user_data, addr)); self.nodes += 1; } @@ -340,7 +341,7 @@ where handle.spawn(service.clone().map_err(|_| ())); let addr = - addr.with(multiaddr::Protocol::P2p((*service.network().local_peer_id()).into())); + addr.with(multiaddr::Protocol::P2p((service.network().local_peer_id()).into())); self.full_nodes.push((self.nodes, service, user_data, addr)); self.nodes += 1; } @@ -387,7 +388,7 @@ where } network.run_until_all_full(move |_index, service| { - let connected = service.network().num_connected(); + let connected = service.network().sync_num_connected(); debug!("Got {}/{} full connections...", connected, expected_full_connections); connected == expected_full_connections }); @@ -422,7 +423,7 @@ where } network.run_until_all_full(move |_index, service| { - let connected = service.network().num_connected(); + let connected = service.network().sync_num_connected(); debug!("Got {}/{} full connections...", connected, expected_full_connections); connected == expected_full_connections }); diff --git a/primitives/consensus/common/src/lib.rs b/primitives/consensus/common/src/lib.rs index 4539cec2c8e0a..043533cbf2258 100644 --- a/primitives/consensus/common/src/lib.rs +++ b/primitives/consensus/common/src/lib.rs @@ -236,10 +236,10 @@ pub trait Proposer { pub trait SyncOracle { /// Whether the synchronization service is undergoing major sync. /// Returns true if so. - fn is_major_syncing(&mut self) -> bool; + fn is_major_syncing(&self) -> bool; /// Whether the synchronization service is offline. /// Returns true if so. - fn is_offline(&mut self) -> bool; + fn is_offline(&self) -> bool; } /// A synchronization oracle for when there is no network. @@ -247,10 +247,10 @@ pub trait SyncOracle { pub struct NoNetwork; impl SyncOracle for NoNetwork { - fn is_major_syncing(&mut self) -> bool { + fn is_major_syncing(&self) -> bool { false } - fn is_offline(&mut self) -> bool { + fn is_offline(&self) -> bool { false } } @@ -258,14 +258,14 @@ impl SyncOracle for NoNetwork { impl SyncOracle for Arc where T: ?Sized, - for<'r> &'r T: SyncOracle, + T: SyncOracle, { - fn is_major_syncing(&mut self) -> bool { - <&T>::is_major_syncing(&mut &**self) + fn is_major_syncing(&self) -> bool { + T::is_major_syncing(self) } - fn is_offline(&mut self) -> bool { - <&T>::is_offline(&mut &**self) + fn is_offline(&self) -> bool { + T::is_offline(self) } } From 8b76a06035acd52dbc67912821132b1394e6cfbe Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Wed, 10 Aug 2022 10:39:24 +0200 Subject: [PATCH 470/484] Add BoundedVec::sort_by_key (#11998) Signed-off-by: Oliver Tale-Yazdi --- primitives/runtime/src/bounded/bounded_vec.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/primitives/runtime/src/bounded/bounded_vec.rs b/primitives/runtime/src/bounded/bounded_vec.rs index d5f3f2da0d615..aed1a156ad699 100644 --- a/primitives/runtime/src/bounded/bounded_vec.rs +++ b/primitives/runtime/src/bounded/bounded_vec.rs @@ -319,6 +319,17 @@ impl BoundedVec { self.0.sort_by(compare) } + /// Exactly the same semantics as [`slice::sort_by_key`]. + /// + /// This is safe since sorting cannot change the number of elements in the vector. + pub fn sort_by_key(&mut self, f: F) + where + F: FnMut(&T) -> K, + K: sp_std::cmp::Ord, + { + self.0.sort_by_key(f) + } + /// Exactly the same semantics as [`slice::sort`]. /// /// This is safe since sorting cannot change the number of elements in the vector. @@ -1189,4 +1200,12 @@ pub mod test { b1.iter().map(|x| x + 1).rev().take(2).try_collect(); assert!(b2.is_err()); } + + #[test] + fn bounded_vec_sort_by_key_works() { + let mut v: BoundedVec> = bounded_vec![-5, 4, 1, -3, 2]; + // Sort by absolute value. + v.sort_by_key(|k| k.abs()); + assert_eq!(v, vec![1, 2, -3, 4, -5]); + } } From 6dde06396f48b4858c5faa0db0f1a33e0588b2b9 Mon Sep 17 00:00:00 2001 From: Muharem Ismailov Date: Wed, 10 Aug 2022 12:36:16 +0200 Subject: [PATCH 471/484] Transaction payment runtime api: query call info and fee details (#11819) * Transaction payment RPC calls: query call info * transaction payment pallet - runtime api - add query_call info and fee_details * remove unused deps * separate call runtime api * undo fmt for unchanged code * system config call bounded to GetDispatchInfo, drop Call generic for query call info/fee * impl GetDispatchInfo for Extrinsics within runtime test-utils * introduced runtime api methods accept encoded Call instead of Call type * replace Bytes by Vec, docs for for new api, drop len argument, drop GetDispatchInfo bound from system_Config::Call * clean up toml and extra impl for dropped bound * panic if Call can not be decoded * revert to 6d0ca79 * fmt and docs * rustfmt --- bin/node-template/runtime/src/lib.rs | 17 +++++ bin/node/runtime/src/lib.rs | 11 ++++ .../rpc/runtime-api/src/lib.rs | 12 ++++ frame/transaction-payment/src/lib.rs | 63 +++++++++++++++++++ 4 files changed, 103 insertions(+) diff --git a/bin/node-template/runtime/src/lib.rs b/bin/node-template/runtime/src/lib.rs index 88fc86db02ef9..b43fbde52dcdc 100644 --- a/bin/node-template/runtime/src/lib.rs +++ b/bin/node-template/runtime/src/lib.rs @@ -467,6 +467,23 @@ impl_runtime_apis! { } } + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentCallApi + for Runtime + { + fn query_call_info( + call: Call, + len: u32, + ) -> pallet_transaction_payment::RuntimeDispatchInfo { + TransactionPayment::query_call_info(call, len) + } + fn query_call_fee_details( + call: Call, + len: u32, + ) -> pallet_transaction_payment::FeeDetails { + TransactionPayment::query_call_fee_details(call, len) + } + } + #[cfg(feature = "runtime-benchmarks")] impl frame_benchmarking::Benchmark for Runtime { fn benchmark_metadata(extra: bool) -> ( diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 2ac1e6444f119..6bfbe3413058a 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1965,6 +1965,17 @@ impl_runtime_apis! { } } + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentCallApi + for Runtime + { + fn query_call_info(call: Call, len: u32) -> RuntimeDispatchInfo { + TransactionPayment::query_call_info(call, len) + } + fn query_call_fee_details(call: Call, len: u32) -> FeeDetails { + TransactionPayment::query_call_fee_details(call, len) + } + } + impl pallet_mmr::primitives::MmrApi< Block, mmr::Hash, diff --git a/frame/transaction-payment/rpc/runtime-api/src/lib.rs b/frame/transaction-payment/rpc/runtime-api/src/lib.rs index 5a0c70138db24..6944593daa57a 100644 --- a/frame/transaction-payment/rpc/runtime-api/src/lib.rs +++ b/frame/transaction-payment/rpc/runtime-api/src/lib.rs @@ -31,4 +31,16 @@ sp_api::decl_runtime_apis! { fn query_info(uxt: Block::Extrinsic, len: u32) -> RuntimeDispatchInfo; fn query_fee_details(uxt: Block::Extrinsic, len: u32) -> FeeDetails; } + + pub trait TransactionPaymentCallApi + where + Balance: Codec + MaybeDisplay, + Call: Codec, + { + /// Query information of a dispatch class, weight, and fee of a given encoded `Call`. + fn query_call_info(call: Call, len: u32) -> RuntimeDispatchInfo; + + /// Query fee details of a given encoded `Call`. + fn query_call_fee_details(call: Call, len: u32) -> FeeDetails; + } } diff --git a/frame/transaction-payment/src/lib.rs b/frame/transaction-payment/src/lib.rs index fe37acb214452..ff65c0d2735fd 100644 --- a/frame/transaction-payment/src/lib.rs +++ b/frame/transaction-payment/src/lib.rs @@ -445,6 +445,32 @@ where } } + /// Query information of a dispatch class, weight, and fee of a given encoded `Call`. + pub fn query_call_info(call: T::Call, len: u32) -> RuntimeDispatchInfo> + where + T::Call: Dispatchable + GetDispatchInfo, + { + let dispatch_info = ::get_dispatch_info(&call); + let DispatchInfo { weight, class, .. } = dispatch_info; + + RuntimeDispatchInfo { + weight, + class, + partial_fee: Self::compute_fee(len, &dispatch_info, 0u32.into()), + } + } + + /// Query fee details of a given encoded `Call`. + pub fn query_call_fee_details(call: T::Call, len: u32) -> FeeDetails> + where + T::Call: Dispatchable + GetDispatchInfo, + { + let dispatch_info = ::get_dispatch_info(&call); + let tip = 0u32.into(); + + Self::compute_fee_details(len, &dispatch_info, tip) + } + /// Compute the final fee value for a particular transaction. pub fn compute_fee(len: u32, info: &DispatchInfoOf, tip: BalanceOf) -> BalanceOf where @@ -1206,6 +1232,43 @@ mod tests { }); } + #[test] + fn query_call_info_and_fee_details_works() { + let call = Call::Balances(BalancesCall::transfer { dest: 2, value: 69 }); + let info = call.get_dispatch_info(); + let encoded_call = call.encode(); + let len = encoded_call.len() as u32; + + ExtBuilder::default().base_weight(5).weight_fee(2).build().execute_with(|| { + // all fees should be x1.5 + >::put(Multiplier::saturating_from_rational(3, 2)); + + assert_eq!( + TransactionPayment::query_call_info(call.clone(), len), + RuntimeDispatchInfo { + weight: info.weight, + class: info.class, + partial_fee: 5 * 2 /* base * weight_fee */ + + len as u64 /* len * 1 */ + + info.weight.min(BlockWeights::get().max_block) as u64 * 2 * 3 / 2 /* weight */ + }, + ); + + assert_eq!( + TransactionPayment::query_call_fee_details(call, len), + FeeDetails { + inclusion_fee: Some(InclusionFee { + base_fee: 5 * 2, /* base * weight_fee */ + len_fee: len as u64, /* len * 1 */ + adjusted_weight_fee: info.weight.min(BlockWeights::get().max_block) as u64 * + 2 * 3 / 2 /* weight * weight_fee * multipler */ + }), + tip: 0, + }, + ); + }); + } + #[test] fn compute_fee_works_without_multiplier() { ExtBuilder::default() From 806c8b5b8fbd2cae01a5070b1fe3d9cb33e898d0 Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Wed, 10 Aug 2022 14:58:52 +0100 Subject: [PATCH 472/484] Use `#[pallet::unbounded]` tag in FRAME System (#11946) * use unbounded in system * update ui tests --- frame/sudo/src/mock.rs | 12 ++++++------ frame/support/src/weights.rs | 4 ++-- .../pallet_ui/storage_info_unsatisfied.stderr | 2 +- .../storage_info_unsatisfied_nmap.stderr | 2 +- frame/system/src/lib.rs | 15 ++++++++++----- 5 files changed, 20 insertions(+), 15 deletions(-) diff --git a/frame/sudo/src/mock.rs b/frame/sudo/src/mock.rs index 2e2a4abafcd98..71f0f26b1a1d5 100644 --- a/frame/sudo/src/mock.rs +++ b/frame/sudo/src/mock.rs @@ -44,7 +44,6 @@ pub mod logger { #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] - #[pallet::without_storage_info] pub struct Pallet(PhantomData); #[pallet::call] @@ -57,7 +56,7 @@ pub mod logger { ) -> DispatchResultWithPostInfo { // Ensure that the `origin` is `Root`. ensure_root(origin)?; - >::append(i); + >::try_append(i).map_err(|_| "could not append")?; Self::deposit_event(Event::AppendI32 { value: i, weight }); Ok(().into()) } @@ -70,8 +69,8 @@ pub mod logger { ) -> DispatchResultWithPostInfo { // Ensure that the `origin` is some signed account. let sender = ensure_signed(origin)?; - >::append(i); - >::append(sender.clone()); + >::try_append(i).map_err(|_| "could not append")?; + >::try_append(sender.clone()).map_err(|_| "could not append")?; Self::deposit_event(Event::AppendI32AndAccount { sender, value: i, weight }); Ok(().into()) } @@ -86,11 +85,12 @@ pub mod logger { #[pallet::storage] #[pallet::getter(fn account_log)] - pub(super) type AccountLog = StorageValue<_, Vec, ValueQuery>; + pub(super) type AccountLog = + StorageValue<_, BoundedVec>, ValueQuery>; #[pallet::storage] #[pallet::getter(fn i32_log)] - pub(super) type I32Log = StorageValue<_, Vec, ValueQuery>; + pub(super) type I32Log = StorageValue<_, BoundedVec>, ValueQuery>; } type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; diff --git a/frame/support/src/weights.rs b/frame/support/src/weights.rs index c37a72536bddf..fb34484416063 100644 --- a/frame/support/src/weights.rs +++ b/frame/support/src/weights.rs @@ -128,7 +128,7 @@ use crate::{ dispatch::{DispatchError, DispatchErrorWithPostInfo, DispatchResultWithPostInfo}, traits::Get, }; -use codec::{Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; @@ -676,7 +676,7 @@ where } /// A struct holding value for each `DispatchClass`. -#[derive(Clone, Eq, PartialEq, Default, RuntimeDebug, Encode, Decode, TypeInfo)] +#[derive(Clone, Eq, PartialEq, Default, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)] pub struct PerDispatchClass { /// Value for `Normal` extrinsics. normal: T, diff --git a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr index c2ec8cf7f4d05..6c49e1220a3a5 100644 --- a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr +++ b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied.stderr @@ -13,5 +13,5 @@ error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) - and 72 others + and 75 others = note: required because of the requirements on the impl of `StorageInfoTrait` for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` diff --git a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr index dbbc426de2906..9a0731683a953 100644 --- a/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr +++ b/frame/support/test/tests/pallet_ui/storage_info_unsatisfied_nmap.stderr @@ -13,6 +13,6 @@ error[E0277]: the trait bound `Bar: MaxEncodedLen` is not satisfied (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6) (TupleElement0, TupleElement1, TupleElement2, TupleElement3, TupleElement4, TupleElement5, TupleElement6, TupleElement7) - and 72 others + and 75 others = note: required because of the requirements on the impl of `KeyGeneratorMaxEncodedLen` for `Key` = note: required because of the requirements on the impl of `StorageInfoTrait` for `frame_support::pallet_prelude::StorageNMap<_GeneratedPrefixForStorageFoo, Key, u32>` diff --git a/frame/system/src/lib.rs b/frame/system/src/lib.rs index 94605c2da59bd..12d415e4e2b42 100644 --- a/frame/system/src/lib.rs +++ b/frame/system/src/lib.rs @@ -233,7 +233,8 @@ pub mod pallet { + Default + MaybeDisplay + AtLeast32Bit - + Copy; + + Copy + + MaxEncodedLen; /// The block number type used by the runtime. type BlockNumber: Parameter @@ -320,7 +321,7 @@ pub mod pallet { /// Data to be associated with an account (other than nonce/transaction counter, which this /// pallet does regardless). - type AccountData: Member + FullCodec + Clone + Default + TypeInfo; + type AccountData: Member + FullCodec + Clone + Default + TypeInfo + MaxEncodedLen; /// Handler for when a new account has just been created. type OnNewAccount: OnNewAccount; @@ -355,7 +356,6 @@ pub mod pallet { #[pallet::pallet] #[pallet::generate_store(pub (super) trait Store)] - #[pallet::without_storage_info] pub struct Pallet(_); #[pallet::hooks] @@ -578,6 +578,7 @@ pub mod pallet { /// Extrinsics data for the current block (maps an extrinsic's index to its data). #[pallet::storage] #[pallet::getter(fn extrinsic_data)] + #[pallet::unbounded] pub(super) type ExtrinsicData = StorageMap<_, Twox64Concat, u32, Vec, ValueQuery>; @@ -593,6 +594,7 @@ pub mod pallet { /// Digest of the current block, also part of the block header. #[pallet::storage] + #[pallet::unbounded] #[pallet::getter(fn digest)] pub(super) type Digest = StorageValue<_, generic::Digest, ValueQuery>; @@ -604,6 +606,7 @@ pub mod pallet { /// Events have a large in-memory size. Box the events to not go out-of-memory /// just in case someone still reads them from within the runtime. #[pallet::storage] + #[pallet::unbounded] pub(super) type Events = StorageValue<_, Vec>>, ValueQuery>; @@ -623,12 +626,14 @@ pub mod pallet { /// the `EventIndex` then in case if the topic has the same contents on the next block /// no notification will be triggered thus the event might be lost. #[pallet::storage] + #[pallet::unbounded] #[pallet::getter(fn event_topics)] pub(super) type EventTopics = StorageMap<_, Blake2_128Concat, T::Hash, Vec<(T::BlockNumber, EventIndex)>, ValueQuery>; /// Stores the `spec_version` and `spec_name` of when the last runtime upgrade happened. #[pallet::storage] + #[pallet::unbounded] pub type LastRuntimeUpgrade = StorageValue<_, LastRuntimeUpgradeInfo>; /// True if we have upgraded so that `type RefCount` is `u32`. False (default) if not. @@ -690,7 +695,7 @@ pub type Key = Vec; pub type KeyValue = (Vec, Vec); /// A phase of a block's execution. -#[derive(Encode, Decode, RuntimeDebug, TypeInfo)] +#[derive(Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] #[cfg_attr(feature = "std", derive(Serialize, PartialEq, Eq, Clone))] pub enum Phase { /// Applying an extrinsic. @@ -738,7 +743,7 @@ type EventIndex = u32; pub type RefCount = u32; /// Information of an account. -#[derive(Clone, Eq, PartialEq, Default, RuntimeDebug, Encode, Decode, TypeInfo)] +#[derive(Clone, Eq, PartialEq, Default, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)] pub struct AccountInfo { /// The number of transactions this account has sent. pub nonce: Index, From 6681563d940fff6260c3df6cc496e320dbd44d6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Wed, 10 Aug 2022 22:27:01 +0200 Subject: [PATCH 473/484] transactional: Wrap `pallet::calls` directly in storage layers (#11927) * transactional: Wrap `pallet::calls` directly in storage layers Before this pr we only wrapped `pallet::calls` into storage layers when executing the calls with `dispatch`. This pr is solving that by wrapping each call function inside a storage layer. * Teach `BasicExternalities` transactions support * Fix crates * FMT * Fix benchmarking tests * Use correct span * Support old decl macros * Fix test * Apply suggestions from code review Co-authored-by: Oliver Tale-Yazdi * Update frame/state-trie-migration/src/lib.rs * Update frame/state-trie-migration/src/lib.rs * Update frame/state-trie-migration/src/lib.rs * Feedback * Apply suggestions from code review Co-authored-by: cheme Co-authored-by: Oliver Tale-Yazdi Co-authored-by: cheme --- frame/state-trie-migration/src/lib.rs | 128 ++++++------ .../procedural/src/pallet/expand/call.rs | 26 ++- .../procedural/src/pallet/parse/call.rs | 20 +- frame/support/src/dispatch.rs | 14 +- frame/support/test/tests/construct_runtime.rs | 178 ++++++++-------- frame/support/test/tests/storage_layers.rs | 2 + frame/transaction-storage/src/lib.rs | 4 +- primitives/state-machine/src/basic.rs | 192 +++++++----------- .../src/overlayed_changes/changeset.rs | 31 ++- .../src/overlayed_changes/mod.rs | 15 ++ 10 files changed, 316 insertions(+), 294 deletions(-) diff --git a/frame/state-trie-migration/src/lib.rs b/frame/state-trie-migration/src/lib.rs index 94f6c1f223b9c..353bc93e5ab94 100644 --- a/frame/state-trie-migration/src/lib.rs +++ b/frame/state-trie-migration/src/lib.rs @@ -235,7 +235,10 @@ pub mod pallet { /// reading a key, we simply cannot know how many bytes it is. In other words, this should /// not be used in any environment where resources are strictly bounded (e.g. a parachain), /// but it is acceptable otherwise (relay chain, offchain workers). - pub fn migrate_until_exhaustion(&mut self, limits: MigrationLimits) -> DispatchResult { + pub fn migrate_until_exhaustion( + &mut self, + limits: MigrationLimits, + ) -> Result<(), Error> { log!(debug, "running migrations on top of {:?} until {:?}", self, limits); if limits.item.is_zero() || limits.size.is_zero() { @@ -262,7 +265,7 @@ pub mod pallet { /// Migrate AT MOST ONE KEY. This can be either a top or a child key. /// /// This function is *the* core of this entire pallet. - fn migrate_tick(&mut self) -> DispatchResult { + fn migrate_tick(&mut self) -> Result<(), Error> { match (&self.progress_top, &self.progress_child) { (Progress::ToStart, _) => self.migrate_top(), (Progress::LastKey(_), Progress::LastKey(_)) => { @@ -301,7 +304,7 @@ pub mod pallet { /// Migrate the current child key, setting it to its new value, if one exists. /// /// It updates the dynamic counters. - fn migrate_child(&mut self) -> DispatchResult { + fn migrate_child(&mut self) -> Result<(), Error> { use sp_io::default_child_storage as child_io; let (maybe_current_child, child_root) = match (&self.progress_child, &self.progress_top) { @@ -350,7 +353,7 @@ pub mod pallet { /// Migrate the current top key, setting it to its new value, if one exists. /// /// It updates the dynamic counters. - fn migrate_top(&mut self) -> DispatchResult { + fn migrate_top(&mut self) -> Result<(), Error> { let maybe_current_top = match &self.progress_top { Progress::LastKey(last_top) => { let maybe_top: Option> = @@ -431,7 +434,7 @@ pub mod pallet { /// The auto migration task finished. AutoMigrationFinished, /// Migration got halted due to an error or miss-configuration. - Halted, + Halted { error: Error }, } /// The outer Pallet struct. @@ -516,8 +519,9 @@ pub mod pallet { pub type SignedMigrationMaxLimits = StorageValue<_, MigrationLimits, OptionQuery>; #[pallet::error] + #[derive(Clone, PartialEq)] pub enum Error { - /// max signed limits not respected. + /// Max signed limits not respected. MaxSignedLimits, /// A key was longer than the configured maximum. /// @@ -529,12 +533,12 @@ pub mod pallet { KeyTooLong, /// submitter does not have enough funds. NotEnoughFunds, - /// bad witness data provided. + /// Bad witness data provided. BadWitness, - /// upper bound of size is exceeded, - SizeUpperBoundExceeded, /// Signed migration is not allowed because the maximum limit is not set yet. SignedMigrationNotAllowed, + /// Bad child root provided. + BadChildRoot, } #[pallet::call] @@ -617,7 +621,7 @@ pub mod pallet { let (_imbalance, _remainder) = T::Currency::slash(&who, deposit); Self::deposit_event(Event::::Slashed { who, amount: deposit }); debug_assert!(_remainder.is_zero()); - return Err(Error::::SizeUpperBoundExceeded.into()) + return Ok(().into()) } Self::deposit_event(Event::::Migrated { @@ -634,13 +638,10 @@ pub mod pallet { MigrationProcess::::put(task); let post_info = PostDispatchInfo { actual_weight, pays_fee: Pays::No }; - match migration { - Ok(_) => Ok(post_info), - Err(error) => { - Self::halt(&error); - Err(DispatchErrorWithPostInfo { post_info, error }) - }, + if let Err(error) = migration { + Self::halt(error); } + Ok(post_info) } /// Migrate the list of top keys by iterating each of them one by one. @@ -679,7 +680,7 @@ pub mod pallet { let (_imbalance, _remainder) = T::Currency::slash(&who, deposit); Self::deposit_event(Event::::Slashed { who, amount: deposit }); debug_assert!(_remainder.is_zero()); - Err("wrong witness data".into()) + Ok(().into()) } else { Self::deposit_event(Event::::Migrated { top: keys.len() as u32, @@ -741,12 +742,9 @@ pub mod pallet { let (_imbalance, _remainder) = T::Currency::slash(&who, deposit); debug_assert!(_remainder.is_zero()); Self::deposit_event(Event::::Slashed { who, amount: deposit }); - Err(DispatchErrorWithPostInfo { - error: "bad witness".into(), - post_info: PostDispatchInfo { - actual_weight: Some(T::WeightInfo::migrate_custom_child_fail()), - pays_fee: Pays::Yes, - }, + Ok(PostDispatchInfo { + actual_weight: Some(T::WeightInfo::migrate_custom_child_fail()), + pays_fee: Pays::Yes, }) } else { Self::deposit_event(Event::::Migrated { @@ -806,7 +804,7 @@ pub mod pallet { if let Some(limits) = Self::auto_limits() { let mut task = Self::migration_process(); if let Err(e) = task.migrate_until_exhaustion(limits) { - Self::halt(&e); + Self::halt(e); } let weight = Self::dynamic_weight(task.dyn_total_items(), task.dyn_size); @@ -849,10 +847,10 @@ pub mod pallet { } /// Put a stop to all ongoing migrations and logs an error. - fn halt(msg: &E) { - log!(error, "migration halted due to: {:?}", msg); + fn halt(error: Error) { + log!(error, "migration halted due to: {:?}", error); AutoLimits::::kill(); - Self::deposit_event(Event::::Halted); + Self::deposit_event(Event::::Halted { error }); } /// Convert a child root key, aka. "Child-bearing top key" into the proper format. @@ -871,7 +869,7 @@ pub mod pallet { fn transform_child_key_or_halt(root: &Vec) -> &[u8] { let key = Self::transform_child_key(root); if key.is_none() { - Self::halt("bad child root key"); + Self::halt(Error::::BadChildRoot); } key.unwrap_or_default() } @@ -961,8 +959,16 @@ mod benchmarks { frame_system::RawOrigin::Signed(caller.clone()).into(), vec![b"foo".to_vec()], 1, - ).is_err() - ) + ).is_ok() + ); + + frame_system::Pallet::::assert_last_event( + ::Event::from(crate::Event::Slashed { + who: caller.clone(), + amount: T::SignedDepositBase::get() + .saturating_add(T::SignedDepositPerItem::get().saturating_mul(1u32.into())), + }).into(), + ); } verify { assert_eq!(StateTrieMigration::::migration_process(), Default::default()); @@ -1005,7 +1011,7 @@ mod benchmarks { StateTrieMigration::::childify("top"), vec![b"foo".to_vec()], 1, - ).is_err() + ).is_ok() ) } verify { @@ -1285,18 +1291,16 @@ mod test { SignedMigrationMaxLimits::::put(MigrationLimits { size: 1 << 20, item: 50 }); // fails if the top key is too long. - frame_support::assert_err_with_weight!( - StateTrieMigration::continue_migrate( - Origin::signed(1), - MigrationLimits { item: 50, size: 1 << 20 }, - Bounded::max_value(), - MigrationProcess::::get() - ), - Error::::KeyTooLong, - Some(2000000), - ); + frame_support::assert_ok!(StateTrieMigration::continue_migrate( + Origin::signed(1), + MigrationLimits { item: 50, size: 1 << 20 }, + Bounded::max_value(), + MigrationProcess::::get() + ),); // The auto migration halted. - System::assert_last_event(crate::Event::Halted {}.into()); + System::assert_last_event( + crate::Event::Halted { error: Error::::KeyTooLong }.into(), + ); // Limits are killed. assert!(AutoLimits::::get().is_none()); @@ -1322,18 +1326,16 @@ mod test { SignedMigrationMaxLimits::::put(MigrationLimits { size: 1 << 20, item: 50 }); // fails if the top key is too long. - frame_support::assert_err_with_weight!( - StateTrieMigration::continue_migrate( - Origin::signed(1), - MigrationLimits { item: 50, size: 1 << 20 }, - Bounded::max_value(), - MigrationProcess::::get() - ), - Error::::KeyTooLong, - Some(2000000), - ); + frame_support::assert_ok!(StateTrieMigration::continue_migrate( + Origin::signed(1), + MigrationLimits { item: 50, size: 1 << 20 }, + Bounded::max_value(), + MigrationProcess::::get() + )); // The auto migration halted. - System::assert_last_event(crate::Event::Halted {}.into()); + System::assert_last_event( + crate::Event::Halted { error: Error::::KeyTooLong }.into(), + ); // Limits are killed. assert!(AutoLimits::::get().is_none()); @@ -1484,7 +1486,7 @@ mod test { ..Default::default() } ), - Error::::BadWitness + Error::::BadWitness, ); // migrate all keys in a series of submissions @@ -1547,14 +1549,11 @@ mod test { assert_eq!(Balances::free_balance(&1), 1000); // note that we don't expect this to be a noop -- we do slash. - frame_support::assert_err!( - StateTrieMigration::migrate_custom_top( - Origin::signed(1), - vec![b"key1".to_vec(), b"key2".to_vec(), b"key3".to_vec()], - correct_witness - 1, - ), - "wrong witness data" - ); + frame_support::assert_ok!(StateTrieMigration::migrate_custom_top( + Origin::signed(1), + vec![b"key1".to_vec(), b"key2".to_vec(), b"key3".to_vec()], + correct_witness - 1, + ),); // no funds should remain reserved. assert_eq!(Balances::reserved_balance(&1), 0); @@ -1584,13 +1583,12 @@ mod test { assert_eq!(Balances::free_balance(&1), 1000); // note that we don't expect this to be a noop -- we do slash. - assert!(StateTrieMigration::migrate_custom_child( + frame_support::assert_ok!(StateTrieMigration::migrate_custom_child( Origin::signed(1), StateTrieMigration::childify("chk1"), vec![b"key1".to_vec(), b"key2".to_vec()], 999999, // wrong witness - ) - .is_err()); + )); // no funds should remain reserved. assert_eq!(Balances::reserved_balance(&1), 0); diff --git a/frame/support/procedural/src/pallet/expand/call.rs b/frame/support/procedural/src/pallet/expand/call.rs index fe7589a8275d2..a9468451ad1d4 100644 --- a/frame/support/procedural/src/pallet/expand/call.rs +++ b/frame/support/procedural/src/pallet/expand/call.rs @@ -140,6 +140,24 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { let capture_docs = if cfg!(feature = "no-metadata-docs") { "never" } else { "always" }; + // Wrap all calls inside of storage layers + if let Some(syn::Item::Impl(item_impl)) = def + .call + .as_ref() + .map(|c| &mut def.item.content.as_mut().expect("Checked by def parser").1[c.index]) + { + item_impl.items.iter_mut().for_each(|i| { + if let syn::ImplItem::Method(method) = i { + let block = &method.block; + method.block = syn::parse_quote! {{ + // We execute all dispatchable in a new storage layer, allowing them + // to return an error at any point, and undoing any storage changes. + #frame_support::storage::with_storage_layer(|| #block) + }}; + } + }); + } + quote::quote_spanned!(span => #[doc(hidden)] pub mod __substrate_call_check { @@ -267,12 +285,8 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { #frame_support::sp_tracing::enter_span!( #frame_support::sp_tracing::trace_span!(stringify!(#fn_name)) ); - // We execute all dispatchable in a new storage layer, allowing them - // to return an error at any point, and undoing any storage changes. - #frame_support::storage::with_storage_layer(|| { - <#pallet_ident<#type_use_gen>>::#fn_name(origin, #( #args_name, )* ) - .map(Into::into).map_err(Into::into) - }) + <#pallet_ident<#type_use_gen>>::#fn_name(origin, #( #args_name, )* ) + .map(Into::into).map_err(Into::into) }, )* Self::__Ignore(_, _) => { diff --git a/frame/support/procedural/src/pallet/parse/call.rs b/frame/support/procedural/src/pallet/parse/call.rs index d8a81d699b8c2..336e08c3d39b7 100644 --- a/frame/support/procedural/src/pallet/parse/call.rs +++ b/frame/support/procedural/src/pallet/parse/call.rs @@ -48,8 +48,8 @@ pub struct CallDef { pub docs: Vec, } -#[derive(Clone)] /// Definition of dispatchable typically: `#[weight...] fn foo(origin .., param1: ...) -> ..` +#[derive(Clone)] pub struct CallVariantDef { /// Function name. pub name: syn::Ident, @@ -143,18 +143,18 @@ impl CallDef { index: usize, item: &mut syn::Item, ) -> syn::Result { - let item = if let syn::Item::Impl(item) = item { + let item_impl = if let syn::Item::Impl(item) = item { item } else { return Err(syn::Error::new(item.span(), "Invalid pallet::call, expected item impl")) }; let instances = vec![ - helper::check_impl_gen(&item.generics, item.impl_token.span())?, - helper::check_pallet_struct_usage(&item.self_ty)?, + helper::check_impl_gen(&item_impl.generics, item_impl.impl_token.span())?, + helper::check_pallet_struct_usage(&item_impl.self_ty)?, ]; - if let Some((_, _, for_)) = item.trait_ { + if let Some((_, _, for_)) = item_impl.trait_ { let msg = "Invalid pallet::call, expected no trait ident as in \ `impl<..> Pallet<..> { .. }`"; return Err(syn::Error::new(for_.span(), msg)) @@ -163,8 +163,8 @@ impl CallDef { let mut methods = vec![]; let mut indices = HashMap::new(); let mut last_index: Option = None; - for impl_item in &mut item.items { - if let syn::ImplItem::Method(method) = impl_item { + for item in &mut item_impl.items { + if let syn::ImplItem::Method(method) = item { if !matches!(method.vis, syn::Visibility::Public(_)) { let msg = "Invalid pallet::call, dispatchable function must be public: \ `pub fn`"; @@ -290,7 +290,7 @@ impl CallDef { }); } else { let msg = "Invalid pallet::call, only method accepted"; - return Err(syn::Error::new(impl_item.span(), msg)) + return Err(syn::Error::new(item.span(), msg)) } } @@ -299,8 +299,8 @@ impl CallDef { attr_span, instances, methods, - where_clause: item.generics.where_clause.clone(), - docs: get_doc_literals(&item.attrs), + where_clause: item_impl.generics.where_clause.clone(), + docs: get_doc_literals(&item_impl.attrs), }) } } diff --git a/frame/support/src/dispatch.rs b/frame/support/src/dispatch.rs index 2b05478e0b02a..fb0b658cd9303 100644 --- a/frame/support/src/dispatch.rs +++ b/frame/support/src/dispatch.rs @@ -1787,9 +1787,11 @@ macro_rules! decl_module { $vis fn $name( $origin: $origin_ty $(, $param: $param_ty )* ) -> $crate::dispatch::DispatchResult { - $crate::sp_tracing::enter_span!($crate::sp_tracing::trace_span!(stringify!($name))); - { $( $impl )* } - Ok(()) + $crate::storage::with_storage_layer(|| { + $crate::sp_tracing::enter_span!($crate::sp_tracing::trace_span!(stringify!($name))); + { $( $impl )* } + Ok(()) + }) } }; @@ -1805,8 +1807,10 @@ macro_rules! decl_module { ) => { $(#[$fn_attr])* $vis fn $name($origin: $origin_ty $(, $param: $param_ty )* ) -> $result { - $crate::sp_tracing::enter_span!($crate::sp_tracing::trace_span!(stringify!($name))); - $( $impl )* + $crate::storage::with_storage_layer(|| { + $crate::sp_tracing::enter_span!($crate::sp_tracing::trace_span!(stringify!($name))); + $( $impl )* + }) } }; diff --git a/frame/support/test/tests/construct_runtime.rs b/frame/support/test/tests/construct_runtime.rs index 63747a9d560dc..d388127f29abd 100644 --- a/frame/support/test/tests/construct_runtime.rs +++ b/frame/support/test/tests/construct_runtime.rs @@ -282,94 +282,96 @@ pub type UncheckedExtrinsic = generic::UncheckedExtrinsic::Root.into()), - Err(DispatchError::Module(ModuleError { - index: 31, - error: [0; 4], - message: Some("Something") - })), - ); - assert_eq!( - Module2::fail(system::Origin::::Root.into()), - Err(DispatchError::Module(ModuleError { - index: 32, - error: [0; 4], - message: Some("Something") - })), - ); - assert_eq!( - Module1_2::fail(system::Origin::::Root.into()), - Err(DispatchError::Module(ModuleError { - index: 33, - error: [0; 4], - message: Some("Something") - })), - ); - assert_eq!( - NestedModule3::fail(system::Origin::::Root.into()), - Err(DispatchError::Module(ModuleError { - index: 34, - error: [0; 4], - message: Some("Something") - })), - ); - assert_eq!( - Module1_3::fail(system::Origin::::Root.into()), - Err(DispatchError::Module(ModuleError { - index: 6, - error: [0; 4], - message: Some("Something") - })), - ); - assert_eq!( - Module1_4::fail(system::Origin::::Root.into()), - Err(DispatchError::Module(ModuleError { - index: 3, - error: [0; 4], - message: Some("Something") - })), - ); - assert_eq!( - Module1_5::fail(system::Origin::::Root.into()), - Err(DispatchError::Module(ModuleError { - index: 4, - error: [0; 4], - message: Some("Something") - })), - ); - assert_eq!( - Module1_6::fail(system::Origin::::Root.into()), - Err(DispatchError::Module(ModuleError { - index: 1, - error: [0; 4], - message: Some("Something") - })), - ); - assert_eq!( - Module1_7::fail(system::Origin::::Root.into()), - Err(DispatchError::Module(ModuleError { - index: 2, - error: [0; 4], - message: Some("Something") - })), - ); - assert_eq!( - Module1_8::fail(system::Origin::::Root.into()), - Err(DispatchError::Module(ModuleError { - index: 12, - error: [0; 4], - message: Some("Something") - })), - ); - assert_eq!( - Module1_9::fail(system::Origin::::Root.into()), - Err(DispatchError::Module(ModuleError { - index: 13, - error: [0; 4], - message: Some("Something") - })), - ); + sp_io::TestExternalities::default().execute_with(|| { + assert_eq!( + Module1_1::fail(system::Origin::::Root.into()), + Err(DispatchError::Module(ModuleError { + index: 31, + error: [0; 4], + message: Some("Something") + })), + ); + assert_eq!( + Module2::fail(system::Origin::::Root.into()), + Err(DispatchError::Module(ModuleError { + index: 32, + error: [0; 4], + message: Some("Something") + })), + ); + assert_eq!( + Module1_2::fail(system::Origin::::Root.into()), + Err(DispatchError::Module(ModuleError { + index: 33, + error: [0; 4], + message: Some("Something") + })), + ); + assert_eq!( + NestedModule3::fail(system::Origin::::Root.into()), + Err(DispatchError::Module(ModuleError { + index: 34, + error: [0; 4], + message: Some("Something") + })), + ); + assert_eq!( + Module1_3::fail(system::Origin::::Root.into()), + Err(DispatchError::Module(ModuleError { + index: 6, + error: [0; 4], + message: Some("Something") + })), + ); + assert_eq!( + Module1_4::fail(system::Origin::::Root.into()), + Err(DispatchError::Module(ModuleError { + index: 3, + error: [0; 4], + message: Some("Something") + })), + ); + assert_eq!( + Module1_5::fail(system::Origin::::Root.into()), + Err(DispatchError::Module(ModuleError { + index: 4, + error: [0; 4], + message: Some("Something") + })), + ); + assert_eq!( + Module1_6::fail(system::Origin::::Root.into()), + Err(DispatchError::Module(ModuleError { + index: 1, + error: [0; 4], + message: Some("Something") + })), + ); + assert_eq!( + Module1_7::fail(system::Origin::::Root.into()), + Err(DispatchError::Module(ModuleError { + index: 2, + error: [0; 4], + message: Some("Something") + })), + ); + assert_eq!( + Module1_8::fail(system::Origin::::Root.into()), + Err(DispatchError::Module(ModuleError { + index: 12, + error: [0; 4], + message: Some("Something") + })), + ); + assert_eq!( + Module1_9::fail(system::Origin::::Root.into()), + Err(DispatchError::Module(ModuleError { + index: 13, + error: [0; 4], + message: Some("Something") + })), + ); + }); } #[test] diff --git a/frame/support/test/tests/storage_layers.rs b/frame/support/test/tests/storage_layers.rs index 05ed60fe90196..7404ccace2c09 100644 --- a/frame/support/test/tests/storage_layers.rs +++ b/frame/support/test/tests/storage_layers.rs @@ -276,5 +276,7 @@ fn storage_layer_in_decl_pallet_call() { let call2 = Call::DeclPallet(decl_pallet::Call::set_value { value: 1 }); assert_noop!(call2.dispatch(Origin::signed(0)), "Revert!"); + // Calling the function directly also works with storage layers. + assert_noop!(decl_pallet::Module::::set_value(Origin::signed(1), 1), "Revert!"); }); } diff --git a/frame/transaction-storage/src/lib.rs b/frame/transaction-storage/src/lib.rs index f16b8f029662b..681cd29af8222 100644 --- a/frame/transaction-storage/src/lib.rs +++ b/frame/transaction-storage/src/lib.rs @@ -245,9 +245,11 @@ pub mod pallet { let sender = ensure_signed(origin)?; let transactions = >::get(block).ok_or(Error::::RenewedNotFound)?; let info = transactions.get(index as usize).ok_or(Error::::RenewedNotFound)?; + let extrinsic_index = + >::extrinsic_index().ok_or(Error::::BadContext)?; + Self::apply_fee(sender, info.size)?; - let extrinsic_index = >::extrinsic_index().unwrap(); sp_io::transaction_index::renew(extrinsic_index, info.content_hash.into()); let mut index = 0; diff --git a/primitives/state-machine/src/basic.rs b/primitives/state-machine/src/basic.rs index 6efc847bfbdb7..236a515a2412d 100644 --- a/primitives/state-machine/src/basic.rs +++ b/primitives/state-machine/src/basic.rs @@ -17,14 +17,13 @@ //! Basic implementation for Externalities. -use crate::{Backend, StorageKey, StorageValue}; +use crate::{Backend, OverlayedChanges, StorageKey, StorageValue}; use codec::Encode; use hash_db::Hasher; use log::warn; use sp_core::{ storage::{ - well_known_keys::is_child_storage_key, ChildInfo, StateVersion, Storage, StorageChild, - TrackedStorageKey, + well_known_keys::is_child_storage_key, ChildInfo, StateVersion, Storage, TrackedStorageKey, }, traits::Externalities, Blake2Hasher, @@ -35,20 +34,19 @@ use std::{ any::{Any, TypeId}, collections::BTreeMap, iter::FromIterator, - ops::Bound, }; /// Simple Map-based Externalities impl. #[derive(Debug)] pub struct BasicExternalities { - inner: Storage, + overlay: OverlayedChanges, extensions: Extensions, } impl BasicExternalities { /// Create a new instance of `BasicExternalities` pub fn new(inner: Storage) -> Self { - BasicExternalities { inner, extensions: Default::default() } + BasicExternalities { overlay: inner.into(), extensions: Default::default() } } /// New basic externalities with empty storage. @@ -57,13 +55,34 @@ impl BasicExternalities { } /// Insert key/value - pub fn insert(&mut self, k: StorageKey, v: StorageValue) -> Option { - self.inner.top.insert(k, v) + pub fn insert(&mut self, k: StorageKey, v: StorageValue) { + self.overlay.set_storage(k, Some(v)); } /// Consume self and returns inner storages pub fn into_storages(self) -> Storage { - self.inner + Storage { + top: self + .overlay + .changes() + .filter_map(|(k, v)| v.value().map(|v| (k.to_vec(), v.to_vec()))) + .collect(), + children_default: self + .overlay + .children() + .map(|(iter, i)| { + ( + i.storage_key().to_vec(), + sp_core::storage::StorageChild { + data: iter + .filter_map(|(k, v)| v.value().map(|v| (k.to_vec(), v.to_vec()))) + .collect(), + child_info: i.clone(), + }, + ) + }) + .collect(), + } } /// Execute the given closure `f` with the externalities set and initialized with `storage`. @@ -73,13 +92,7 @@ impl BasicExternalities { storage: &mut sp_core::storage::Storage, f: impl FnOnce() -> R, ) -> R { - let mut ext = Self { - inner: Storage { - top: std::mem::take(&mut storage.top), - children_default: std::mem::take(&mut storage.children_default), - }, - extensions: Default::default(), - }; + let mut ext = Self::new(std::mem::take(storage)); let r = ext.execute_with(f); @@ -108,15 +121,26 @@ impl BasicExternalities { impl PartialEq for BasicExternalities { fn eq(&self, other: &BasicExternalities) -> bool { - self.inner.top.eq(&other.inner.top) && - self.inner.children_default.eq(&other.inner.children_default) + self.overlay.changes().map(|(k, v)| (k, v.value())).collect::>() == + other.overlay.changes().map(|(k, v)| (k, v.value())).collect::>() && + self.overlay + .children() + .map(|(iter, i)| (i, iter.map(|(k, v)| (k, v.value())).collect::>())) + .collect::>() == + other + .overlay + .children() + .map(|(iter, i)| { + (i, iter.map(|(k, v)| (k, v.value())).collect::>()) + }) + .collect::>() } } impl FromIterator<(StorageKey, StorageValue)> for BasicExternalities { fn from_iter>(iter: I) -> Self { let mut t = Self::default(); - t.inner.top.extend(iter); + iter.into_iter().for_each(|(k, v)| t.insert(k, v)); t } } @@ -128,11 +152,8 @@ impl Default for BasicExternalities { } impl From> for BasicExternalities { - fn from(hashmap: BTreeMap) -> Self { - BasicExternalities { - inner: Storage { top: hashmap, children_default: Default::default() }, - extensions: Default::default(), - } + fn from(map: BTreeMap) -> Self { + Self::from_iter(map.into_iter()) } } @@ -140,7 +161,7 @@ impl Externalities for BasicExternalities { fn set_offchain_storage(&mut self, _key: &[u8], _value: Option<&[u8]>) {} fn storage(&self, key: &[u8]) -> Option { - self.inner.top.get(key).cloned() + self.overlay.storage(key).and_then(|v| v.map(|v| v.to_vec())) } fn storage_hash(&self, key: &[u8]) -> Option> { @@ -148,11 +169,7 @@ impl Externalities for BasicExternalities { } fn child_storage(&self, child_info: &ChildInfo, key: &[u8]) -> Option { - self.inner - .children_default - .get(child_info.storage_key()) - .and_then(|child| child.data.get(key)) - .cloned() + self.overlay.child_storage(child_info, key).and_then(|v| v.map(|v| v.to_vec())) } fn child_storage_hash(&self, child_info: &ChildInfo, key: &[u8]) -> Option> { @@ -160,16 +177,13 @@ impl Externalities for BasicExternalities { } fn next_storage_key(&self, key: &[u8]) -> Option { - let range = (Bound::Excluded(key), Bound::Unbounded); - self.inner.top.range::<[u8], _>(range).next().map(|(k, _)| k).cloned() + self.overlay.iter_after(key).find_map(|(k, v)| v.value().map(|_| k.to_vec())) } fn next_child_storage_key(&self, child_info: &ChildInfo, key: &[u8]) -> Option { - let range = (Bound::Excluded(key), Bound::Unbounded); - self.inner - .children_default - .get(child_info.storage_key()) - .and_then(|child| child.data.range::<[u8], _>(range).next().map(|(k, _)| k).cloned()) + self.overlay + .child_iter_after(child_info.storage_key(), key) + .find_map(|(k, v)| v.value().map(|_| k.to_vec())) } fn place_storage(&mut self, key: StorageKey, maybe_value: Option) { @@ -178,14 +192,7 @@ impl Externalities for BasicExternalities { return } - match maybe_value { - Some(value) => { - self.inner.top.insert(key, value); - }, - None => { - self.inner.top.remove(&key); - }, - } + self.overlay.set_storage(key, maybe_value) } fn place_child_storage( @@ -194,19 +201,7 @@ impl Externalities for BasicExternalities { key: StorageKey, value: Option, ) { - let child_map = self - .inner - .children_default - .entry(child_info.storage_key().to_vec()) - .or_insert_with(|| StorageChild { - data: Default::default(), - child_info: child_info.to_owned(), - }); - if let Some(value) = value { - child_map.data.insert(key, value); - } else { - child_map.data.remove(&key); - } + self.overlay.set_child_storage(child_info, key, value); } fn kill_child_storage( @@ -215,12 +210,7 @@ impl Externalities for BasicExternalities { _maybe_limit: Option, _maybe_cursor: Option<&[u8]>, ) -> MultiRemovalResults { - let count = self - .inner - .children_default - .remove(child_info.storage_key()) - .map(|c| c.data.len()) - .unwrap_or(0) as u32; + let count = self.overlay.clear_child_storage(child_info); MultiRemovalResults { maybe_cursor: None, backend: count, unique: count, loops: count } } @@ -239,19 +229,7 @@ impl Externalities for BasicExternalities { return MultiRemovalResults { maybe_cursor, backend: 0, unique: 0, loops: 0 } } - let to_remove = self - .inner - .top - .range::<[u8], _>((Bound::Included(prefix), Bound::Unbounded)) - .map(|(k, _)| k) - .take_while(|k| k.starts_with(prefix)) - .cloned() - .collect::>(); - - let count = to_remove.len() as u32; - for key in to_remove { - self.inner.top.remove(&key); - } + let count = self.overlay.clear_prefix(prefix); MultiRemovalResults { maybe_cursor: None, backend: count, unique: count, loops: count } } @@ -262,56 +240,37 @@ impl Externalities for BasicExternalities { _maybe_limit: Option, _maybe_cursor: Option<&[u8]>, ) -> MultiRemovalResults { - if let Some(child) = self.inner.children_default.get_mut(child_info.storage_key()) { - let to_remove = child - .data - .range::<[u8], _>((Bound::Included(prefix), Bound::Unbounded)) - .map(|(k, _)| k) - .take_while(|k| k.starts_with(prefix)) - .cloned() - .collect::>(); - - let count = to_remove.len() as u32; - for key in to_remove { - child.data.remove(&key); - } - MultiRemovalResults { maybe_cursor: None, backend: count, unique: count, loops: count } - } else { - MultiRemovalResults { maybe_cursor: None, backend: 0, unique: 0, loops: 0 } - } + let count = self.overlay.clear_child_prefix(child_info, prefix); + MultiRemovalResults { maybe_cursor: None, backend: count, unique: count, loops: count } } fn storage_append(&mut self, key: Vec, value: Vec) { - let current = self.inner.top.entry(key).or_default(); - crate::ext::StorageAppend::new(current).append(value); + let current_value = self.overlay.value_mut_or_insert_with(&key, || Default::default()); + crate::ext::StorageAppend::new(current_value).append(value); } fn storage_root(&mut self, state_version: StateVersion) -> Vec { - let mut top = self.inner.top.clone(); - let prefixed_keys: Vec<_> = self - .inner - .children_default - .iter() - .map(|(_k, v)| (v.child_info.prefixed_storage_key(), v.child_info.clone())) - .collect(); + let mut top = self + .overlay + .changes() + .filter_map(|(k, v)| v.value().map(|v| (k.clone(), v.clone()))) + .collect::>(); // Single child trie implementation currently allows using the same child // empty root for all child trie. Using null storage key until multiple // type of child trie support. let empty_hash = empty_child_trie_root::>(); - for (prefixed_storage_key, child_info) in prefixed_keys { + for child_info in self.overlay.children().map(|d| d.1.clone()).collect::>() { let child_root = self.child_storage_root(&child_info, state_version); if empty_hash[..] == child_root[..] { - top.remove(prefixed_storage_key.as_slice()); + top.remove(child_info.prefixed_storage_key().as_slice()); } else { - top.insert(prefixed_storage_key.into_inner(), child_root); + top.insert(child_info.prefixed_storage_key().into_inner(), child_root); } } match state_version { - StateVersion::V0 => - LayoutV0::::trie_root(self.inner.top.clone()).as_ref().into(), - StateVersion::V1 => - LayoutV1::::trie_root(self.inner.top.clone()).as_ref().into(), + StateVersion::V0 => LayoutV0::::trie_root(top).as_ref().into(), + StateVersion::V1 => LayoutV1::::trie_root(top).as_ref().into(), } } @@ -320,10 +279,11 @@ impl Externalities for BasicExternalities { child_info: &ChildInfo, state_version: StateVersion, ) -> Vec { - if let Some(child) = self.inner.children_default.get(child_info.storage_key()) { - let delta = child.data.iter().map(|(k, v)| (k.as_ref(), Some(v.as_ref()))); + if let Some((data, child_info)) = self.overlay.child_changes(child_info.storage_key()) { + let delta = + data.into_iter().map(|(k, v)| (k.as_ref(), v.value().map(|v| v.as_slice()))); crate::in_memory_backend::new_in_mem::>() - .child_storage_root(&child.child_info, delta, state_version) + .child_storage_root(&child_info, delta, state_version) .0 } else { empty_child_trie_root::>() @@ -332,15 +292,15 @@ impl Externalities for BasicExternalities { } fn storage_start_transaction(&mut self) { - unimplemented!("Transactions are not supported by BasicExternalities"); + self.overlay.start_transaction() } fn storage_rollback_transaction(&mut self) -> Result<(), ()> { - unimplemented!("Transactions are not supported by BasicExternalities"); + self.overlay.rollback_transaction().map_err(drop) } fn storage_commit_transaction(&mut self) -> Result<(), ()> { - unimplemented!("Transactions are not supported by BasicExternalities"); + self.overlay.commit_transaction().map_err(drop) } fn wipe(&mut self) {} diff --git a/primitives/state-machine/src/overlayed_changes/changeset.rs b/primitives/state-machine/src/overlayed_changes/changeset.rs index ae5d47f6bb943..e5dad7157c731 100644 --- a/primitives/state-machine/src/overlayed_changes/changeset.rs +++ b/primitives/state-machine/src/overlayed_changes/changeset.rs @@ -57,7 +57,7 @@ pub struct NotInRuntime; /// Describes in which mode the node is currently executing. #[derive(Debug, Clone, Copy)] pub enum ExecutionMode { - /// Exeuting in client mode: Removal of all transactions possible. + /// Executing in client mode: Removal of all transactions possible. Client, /// Executing in runtime mode: Transactions started by the client are protected. Runtime, @@ -95,7 +95,7 @@ pub type OverlayedChangeSet = OverlayedMap>; /// Holds a set of changes with the ability modify them using nested transactions. #[derive(Debug, Clone)] -pub struct OverlayedMap { +pub struct OverlayedMap { /// Stores the changes that this overlay constitutes. changes: BTreeMap>, /// Stores which keys are dirty per transaction. Needed in order to determine which @@ -110,7 +110,7 @@ pub struct OverlayedMap { execution_mode: ExecutionMode, } -impl Default for OverlayedMap { +impl Default for OverlayedMap { fn default() -> Self { Self { changes: BTreeMap::new(), @@ -121,6 +121,31 @@ impl Default for OverlayedMap { } } +#[cfg(feature = "std")] +impl From for OverlayedMap> { + fn from(storage: sp_core::storage::StorageMap) -> Self { + Self { + changes: storage + .into_iter() + .map(|(k, v)| { + ( + k, + OverlayedEntry { + transactions: SmallVec::from_iter([InnerValue { + value: Some(v), + extrinsics: Default::default(), + }]), + }, + ) + }) + .collect(), + dirty_keys: Default::default(), + num_client_transactions: 0, + execution_mode: ExecutionMode::Client, + } + } +} + impl Default for ExecutionMode { fn default() -> Self { Self::Client diff --git a/primitives/state-machine/src/overlayed_changes/mod.rs b/primitives/state-machine/src/overlayed_changes/mod.rs index 746599a6768e6..001b4b656c34e 100644 --- a/primitives/state-machine/src/overlayed_changes/mod.rs +++ b/primitives/state-machine/src/overlayed_changes/mod.rs @@ -638,6 +638,21 @@ impl OverlayedChanges { } } +#[cfg(feature = "std")] +impl From for OverlayedChanges { + fn from(storage: sp_core::storage::Storage) -> Self { + Self { + top: storage.top.into(), + children: storage + .children_default + .into_iter() + .map(|(k, v)| (k, (v.data.into(), v.child_info))) + .collect(), + ..Default::default() + } + } +} + #[cfg(feature = "std")] fn retain_map(map: &mut Map, f: F) where From 128b7d843b865bf63a13b693ea53caa7d092a14c Mon Sep 17 00:00:00 2001 From: Davirain Date: Thu, 11 Aug 2022 16:58:47 +0800 Subject: [PATCH 474/484] add substrate-ibc --- Cargo.lock | 2390 +++++++----- Cargo.toml | 1 + frame/ibc/Cargo.lock | 3462 +++++++++++++++++ frame/ibc/Cargo.toml | 86 + frame/ibc/LICENSE | 201 + frame/ibc/Makefile | 2 + frame/ibc/README.md | 106 + frame/ibc/src/context.rs | 50 + frame/ibc/src/events.rs | 340 ++ frame/ibc/src/lib.rs | 646 +++ frame/ibc/src/mock.rs | 216 + frame/ibc/src/module/applications/mod.rs | 1 + .../module/applications/transfer/channel.rs | 285 ++ .../src/module/applications/transfer/mod.rs | 271 ++ .../transfer/transfer_handle_callback.rs | 161 + frame/ibc/src/module/core/ics02_client.rs | 235 ++ frame/ibc/src/module/core/ics03_connection.rs | 111 + frame/ibc/src/module/core/ics04_channel.rs | 524 +++ frame/ibc/src/module/core/ics05_port.rs | 24 + frame/ibc/src/module/core/ics24_host.rs | 254 ++ frame/ibc/src/module/core/ics26_routing.rs | 65 + frame/ibc/src/module/core/mod.rs | 6 + frame/ibc/src/module/mod.rs | 3 + frame/ibc/src/module/relayer/mod.rs | 47 + frame/ibc/src/tests.rs | 417 ++ frame/ibc/src/traits.rs | 10 + frame/ibc/src/utils.rs | 47 + 27 files changed, 8954 insertions(+), 1007 deletions(-) create mode 100644 frame/ibc/Cargo.lock create mode 100644 frame/ibc/Cargo.toml create mode 100644 frame/ibc/LICENSE create mode 100644 frame/ibc/Makefile create mode 100644 frame/ibc/README.md create mode 100644 frame/ibc/src/context.rs create mode 100644 frame/ibc/src/events.rs create mode 100644 frame/ibc/src/lib.rs create mode 100644 frame/ibc/src/mock.rs create mode 100644 frame/ibc/src/module/applications/mod.rs create mode 100644 frame/ibc/src/module/applications/transfer/channel.rs create mode 100644 frame/ibc/src/module/applications/transfer/mod.rs create mode 100644 frame/ibc/src/module/applications/transfer/transfer_handle_callback.rs create mode 100644 frame/ibc/src/module/core/ics02_client.rs create mode 100644 frame/ibc/src/module/core/ics03_connection.rs create mode 100644 frame/ibc/src/module/core/ics04_channel.rs create mode 100644 frame/ibc/src/module/core/ics05_port.rs create mode 100644 frame/ibc/src/module/core/ics24_host.rs create mode 100644 frame/ibc/src/module/core/ics26_routing.rs create mode 100644 frame/ibc/src/module/core/mod.rs create mode 100644 frame/ibc/src/module/mod.rs create mode 100644 frame/ibc/src/module/relayer/mod.rs create mode 100644 frame/ibc/src/tests.rs create mode 100644 frame/ibc/src/traits.rs create mode 100644 frame/ibc/src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index 38063e8060835..6a12a605b1483 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,7 +18,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" dependencies = [ - "gimli 0.26.1", + "gimli", ] [[package]] @@ -29,30 +29,30 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aead" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e3e798aa0c8239776f54415bc06f3d74b1850f3f830b45c35cfc80556973f70" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.6", ] [[package]] name = "aes" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "495ee669413bfbe9e8cace80f4d3d78e6d8c8d99579f97fb93bde351b185f2d4" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ "cfg-if 1.0.0", "cipher", - "cpufeatures 0.1.5", + "cpufeatures", "opaque-debug 0.3.0", ] [[package]] name = "aes-gcm" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a930fd487faaa92a30afa92cc9dd1526a5cff67124abbbb1c617ce070f4dcf" +checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" dependencies = [ "aead", "aes", @@ -68,7 +68,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.3", + "getrandom 0.2.7", "once_cell", "version_check", ] @@ -82,6 +82,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "android_system_properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7ed72e1635e121ca3e79420540282af22da58be50de153d36f81ddc6b83aa9e" +dependencies = [ + "libc", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -93,24 +102,24 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.38" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afddf7f520a80dbf76e6f50a35bca42a2331ef227a28b3b6dc5c2e2338d114b1" +checksum = "508b352bb5c066aac251f6daf6b36eccd03e8a88e8081cd44959ea277a3af9a8" [[package]] name = "approx" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "072df7202e63b127ab55acfe16ce97013d5b97bf160489336d3f1840fd78e99e" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" dependencies = [ "num-traits", ] [[package]] name = "arbitrary" -version = "1.0.0" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "698b65a961a9d730fb45b6b0327e20207810c9f61ee421b082b27ba003f49e2b" +checksum = "5a7924531f38b1970ff630f03eb20a2fde69db5c590c93b0f3482e95dcc5fd60" [[package]] name = "arrayref" @@ -141,15 +150,15 @@ checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" [[package]] name = "asn1_der" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6e24d2cce90c53b948c46271bfb053e4bdc2db9b5d3f65e20f8cf28a1b7fc3" +checksum = "e22d1f4b888c298a027c99dc9048015fac177587de20fc30232a057dfbe24a21" [[package]] name = "assert_cmd" -version = "2.0.2" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e996dc7940838b7ef1096b882e29ec30a3149a3a443cdc8dba19ed382eca1fe2" +checksum = "93ae1ddd39efd67689deb1979d80bad3bf7f2b09c6e6117c8d1f2443b5e2f83e" dependencies = [ "bstr", "doc-comment", @@ -177,9 +186,9 @@ dependencies = [ [[package]] name = "async-channel" -version = "1.6.1" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" +checksum = "e14485364214912d3b19cc3435dde4df66065127f05fa0d75c712f36f12c2f28" dependencies = [ "concurrent-queue", "event-listener", @@ -188,28 +197,28 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb877970c7b440ead138f6321a3b5395d6061183af779340b65e20c0fede9146" +checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965" dependencies = [ "async-task", "concurrent-queue", "fastrand", "futures-lite", "once_cell", - "vec-arena", + "slab", ] [[package]] name = "async-global-executor" -version = "2.0.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9586ec52317f36de58453159d48351bc244bc24ced3effc1fce22f3d48664af6" +checksum = "5262ed948da60dd8956c6c5aca4d4163593dddb7b32d73267c93dab7b2e98940" dependencies = [ "async-channel", "async-executor", "async-io", - "async-mutex", + "async-lock", "blocking", "futures-lite", "num_cpus", @@ -218,9 +227,9 @@ dependencies = [ [[package]] name = "async-io" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a811e6a479f2439f0c04038796b5cfb3d2ad56c230e0f2d3f7b04d68cfee607b" +checksum = "e5e18f61464ae81cde0a23e713ae8fd299580c54d697a35820cfd0625b8b0e07" dependencies = [ "concurrent-queue", "futures-lite", @@ -244,15 +253,6 @@ dependencies = [ "event-listener", ] -[[package]] -name = "async-mutex" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" -dependencies = [ - "event-listener", -] - [[package]] name = "async-process" version = "1.4.0" @@ -272,9 +272,9 @@ dependencies = [ [[package]] name = "async-std" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52580991739c5cdb36cde8b2a516371c0a3b70dda36d916cc08b82372916808c" +checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" dependencies = [ "async-attributes", "async-channel", @@ -291,9 +291,8 @@ dependencies = [ "kv-log-macro", "log", "memchr", - "num_cpus", "once_cell", - "pin-project-lite 0.2.6", + "pin-project-lite 0.2.9", "pin-utils", "slab", "wasm-bindgen-futures", @@ -316,9 +315,9 @@ dependencies = [ [[package]] name = "async-stream" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625" +checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" dependencies = [ "async-stream-impl", "futures-core", @@ -326,9 +325,9 @@ dependencies = [ [[package]] name = "async-stream-impl" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308" +checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" dependencies = [ "proc-macro2", "quote", @@ -337,15 +336,15 @@ dependencies = [ [[package]] name = "async-task" -version = "4.0.3" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0" +checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" [[package]] name = "async-trait" -version = "0.1.51" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e" +checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" dependencies = [ "proc-macro2", "quote", @@ -362,7 +361,7 @@ dependencies = [ "futures-sink", "futures-util", "memchr", - "pin-project-lite 0.2.6", + "pin-project-lite 0.2.9", ] [[package]] @@ -384,30 +383,30 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.64" +version = "0.3.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e121dee8023ce33ab248d9ce1493df03c3b38a659b240096fcbd7048ff9c31f" +checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" dependencies = [ "addr2line", "cc", "cfg-if 1.0.0", "libc", "miniz_oxide", - "object 0.27.1", + "object 0.29.0", "rustc-demangle", ] [[package]] name = "base-x" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" +checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" [[package]] name = "base16ct" @@ -429,9 +428,9 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "beef" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bed554bd50246729a1ec158d08aa3235d1b69d94ad120ebe187e28894787e736" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" dependencies = [ "serde", ] @@ -448,7 +447,7 @@ dependencies = [ "hex", "log", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "sc-block-builder", "sc-chain-spec", "sc-client-api", @@ -491,7 +490,7 @@ dependencies = [ "jsonrpsee", "log", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "sc-rpc", "sc-utils", "serde", @@ -534,17 +533,16 @@ dependencies = [ [[package]] name = "bimap" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50ae17cabbc8a38a1e3e4c1a6a664e9a09672dc14d0896fa8d865d3a5a446b07" +checksum = "bc0455254eb5c6964c4545d8bac815e1a1be4f3afe0ae695ea539c12d728d44b" [[package]] name = "bincode" -version = "1.3.2" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d175dfa69e619905c4c3cdb7c3c203fa3bdd5d51184e3afdb2742c0280493772" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ - "byteorder", "serde", ] @@ -575,9 +573,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitvec" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1489fcb93a5bb47da0462ca93ad252ad6af2145cce58d10d46a83931ba9f016b" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" dependencies = [ "funty", "radium", @@ -587,9 +585,9 @@ dependencies = [ [[package]] name = "blake2" -version = "0.10.2" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94ba84325db59637ffc528bbe8c7f86c02c57cff5c0e2b9b00f9a851f42f309" +checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388" dependencies = [ "digest 0.10.3", ] @@ -658,16 +656,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ "block-padding 0.2.1", - "generic-array 0.14.4", + "generic-array 0.14.6", ] [[package]] name = "block-buffer" -version = "0.10.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1d36a02058e76b040de25a4464ba1c80935655595b661505c8b39b664828b95" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.6", ] [[package]] @@ -687,9 +685,9 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] name = "blocking" -version = "1.0.2" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e170dbede1f740736619b776d7251cb1b9095c435c34d8ca9f57fcd2f335e9" +checksum = "c6ccb65d468978a086b69884437ded69a90faab3bbe6e67f242173ea728acccc" dependencies = [ "async-channel", "async-task", @@ -707,9 +705,9 @@ checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" [[package]] name = "bstr" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a40b47ad93e1a5404e6c18dec46b628214fee441c70f4ab5d6942142cc268a3d" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" dependencies = [ "lazy_static", "memchr", @@ -728,15 +726,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.6.1" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" [[package]] name = "byte-slice-cast" -version = "1.0.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65c1bf4a04a88c54f589125563643d773f3254b5c38571395e2b591c693bbc81" +checksum = "87c5fdd0166095e1d463fc6cc01aa8ce547ad77a4e84d42eb6762b084e28067e" [[package]] name = "byte-tools" @@ -746,9 +744,9 @@ checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" [[package]] name = "bytecheck" -version = "0.6.7" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "314889ea31cda264cb7c3d6e6e5c9415a987ecb0e72c17c00d36fbb881d34abe" +checksum = "d11cac2c12b5adc6570dad2ee1b87eff4955dac476fe12d81e5fdd352e52406f" dependencies = [ "bytecheck_derive", "ptr_meta", @@ -756,9 +754,9 @@ dependencies = [ [[package]] name = "bytecheck_derive" -version = "0.6.7" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a2b3b92c135dae665a6f760205b89187638e83bed17ef3e44e83c712cf30600" +checksum = "13e576ebe98e605500b3c8041bb888e966653577172df6dd97398714eb30b9bf" dependencies = [ "proc-macro2", "quote", @@ -767,15 +765,15 @@ dependencies = [ [[package]] name = "byteorder" -version = "1.3.4" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" [[package]] name = "bzip2-sys" @@ -790,24 +788,24 @@ dependencies = [ [[package]] name = "cache-padded" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" +checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" [[package]] name = "camino" -version = "1.0.4" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4648c6d00a709aa069a236adcaae4f605a6241c72bf5bee79331a4b625921a9" +checksum = "869119e97797867fd90f5e22af7d0bd274bd4635ebb9eb68c04f3f513ae6c412" dependencies = [ "serde", ] [[package]] name = "cargo-platform" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0226944a63d1bf35a3b5f948dd7c59e263db83695c9e8bffc4037de02e30f1d7" +checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" dependencies = [ "serde", ] @@ -820,25 +818,22 @@ checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" dependencies = [ "camino", "cargo-platform", - "semver 1.0.4", + "semver 1.0.13", "serde", "serde_json", ] [[package]] name = "cast" -version = "0.2.3" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0" -dependencies = [ - "rustc_version 0.2.3", -] +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.71" +version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" dependencies = [ "jobserver", ] @@ -866,21 +861,21 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chacha20" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01b72a433d0cf2aef113ba70f62634c56fddb0f244e6377185c56a7cadbd8f91" +checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" dependencies = [ "cfg-if 1.0.0", "cipher", - "cpufeatures 0.2.1", + "cpufeatures", "zeroize", ] [[package]] name = "chacha20poly1305" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b84ed6d1d5f7aa9bdde921a5090e0ca4d934d250ea3b402a5fab3a994e28a2a" +checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5" dependencies = [ "aead", "chacha20", @@ -894,9 +889,9 @@ name = "chain-spec-builder" version = "2.0.0" dependencies = [ "ansi_term", - "clap 3.1.18", + "clap 3.2.16", "node-cli", - "rand 0.8.4", + "rand 0.8.5", "sc-chain-spec", "sc-keystore", "sp-core", @@ -905,22 +900,24 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "3f725f340c3854e3cb3ab736dc21f0cca183303acea3b3ffec30f141503ac8eb" dependencies = [ - "libc", + "iana-time-zone", + "js-sys", "num-integer", "num-traits", - "time", + "time 0.1.44", + "wasm-bindgen", "winapi", ] [[package]] name = "cid" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a52cffa791ce5cf490ac3b2d6df970dc04f931b04e727be3c3e220e17164dfc4" +checksum = "f6ed9c8b2d17acb8110c46f1da5bf4a696d745e1474a16db0cd2b49cd0249bf2" dependencies = [ "core2", "multibase", @@ -935,7 +932,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.6", ] [[package]] @@ -949,13 +946,13 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.2.0" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "853eda514c284c2287f4bf20ae614f8781f40a81d32ecda6e91449304dfe077c" +checksum = "5a050e2153c5be08febd6734e29298e844fdb0fa21aeddd63b4eb7baa106c69b" dependencies = [ "glob", "libc", - "libloading 0.7.0", + "libloading 0.7.3", ] [[package]] @@ -971,16 +968,16 @@ dependencies = [ [[package]] name = "clap" -version = "3.1.18" +version = "3.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2dbdf4bdacb33466e854ce889eee8dfd5729abf7ccd7664d0a2d60cd384440b" +checksum = "a3dbbb6653e7c55cc8595ad3e1f7be8f32aba4eb7ff7f0fd1163d4f3d137c0a9" dependencies = [ "atty", "bitflags", "clap_derive", "clap_lex", "indexmap", - "lazy_static", + "once_cell", "strsim", "termcolor", "textwrap 0.15.0", @@ -988,18 +985,18 @@ dependencies = [ [[package]] name = "clap_complete" -version = "3.0.2" +version = "3.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a394f7ec0715b42a4e52b294984c27c9a61f77c8d82f7774c5198350be143f19" +checksum = "ead064480dfc4880a10764488415a97fdd36a4cf1bb022d372f02e8faf8386e1" dependencies = [ - "clap 3.1.18", + "clap 3.2.16", ] [[package]] name = "clap_derive" -version = "3.1.18" +version = "3.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25320346e922cffe59c0bbc5410c8d8784509efb321488971081313cb1e1a33c" +checksum = "9ba52acd3b0a5c33aeada5cdaa3267cdc7c594a98731d4268cdc1532f4264cb4" dependencies = [ "heck", "proc-macro-error", @@ -1010,18 +1007,18 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.2.0" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" dependencies = [ "os_str_bytes", ] [[package]] name = "cmake" -version = "0.1.46" +version = "0.1.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7b858541263efe664aead4a5209a4ae5c5d2811167d4ed4ee0944503f8d2089" +checksum = "e8ad8cef104ac57b68b89df3208164d228503abbdce70f6880ffa3d970e7443a" dependencies = [ "cc", ] @@ -1039,9 +1036,9 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "1.2.2" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" +checksum = "af4780a44ab5696ea9e28294517f1fffb421a83a25af521333c838635509db9c" dependencies = [ "cache-padded", ] @@ -1084,68 +1081,65 @@ dependencies = [ ] [[package]] -name = "cpp_demangle" -version = "0.3.2" +name = "corosensei" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44919ecaf6f99e8e737bc239408931c9a01e9a6c74814fee8242dd2506b65390" +checksum = "9847f90f32a50b0dcbd68bc23ff242798b13080b97b0569f6ed96a45ce4cf2cd" dependencies = [ + "autocfg", "cfg-if 1.0.0", - "glob", + "libc", + "scopeguard", + "windows-sys 0.33.0", ] [[package]] -name = "cpufeatures" -version = "0.1.5" +name = "cpp_demangle" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef" +checksum = "eeaa953eaad386a53111e47172c2fedba671e5684c8dd601a5f474f4f118710f" dependencies = [ - "libc", + "cfg-if 1.0.0", ] [[package]] name = "cpufeatures" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" dependencies = [ "libc", ] -[[package]] -name = "cpuid-bool" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" - [[package]] name = "cranelift-bforest" -version = "0.76.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e6bea67967505247f54fa2c85cf4f6e0e31c4e5692c9b70e4ae58e339067333" +checksum = "38faa2a16616c8e78a18d37b4726b98bfd2de192f2fdc8a39ddf568a408a0f75" dependencies = [ - "cranelift-entity 0.76.0", + "cranelift-entity 0.82.3", ] [[package]] name = "cranelift-bforest" -version = "0.85.0" +version = "0.85.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899dc8d22f7771e7f887fb8bafa0c0d3ac1dea0c7f2c0ded6e20a855a7a1e890" +checksum = "749d0d6022c9038dccf480bdde2a38d435937335bf2bb0f14e815d94517cdce8" dependencies = [ - "cranelift-entity 0.85.0", + "cranelift-entity 0.85.3", ] [[package]] name = "cranelift-codegen" -version = "0.76.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48194035d2752bdd5bdae429e3ab88676e95f52a2b1355a5d4e809f9e39b1d74" +checksum = "26f192472a3ba23860afd07d2b0217dc628f21fcc72617aa1336d98e1671f33b" dependencies = [ - "cranelift-bforest 0.76.0", - "cranelift-codegen-meta 0.76.0", - "cranelift-codegen-shared 0.76.0", - "cranelift-entity 0.76.0", - "gimli 0.25.0", + "cranelift-bforest 0.82.3", + "cranelift-codegen-meta 0.82.3", + "cranelift-codegen-shared 0.82.3", + "cranelift-entity 0.82.3", + "gimli", "log", "regalloc", "smallvec", @@ -1154,16 +1148,16 @@ dependencies = [ [[package]] name = "cranelift-codegen" -version = "0.85.0" +version = "0.85.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dbdc03f695cf67e7bc45da57155528274f47390b85060af8107eb304ef167c4" +checksum = "e94370cc7b37bf652ccd8bb8f09bd900997f7ccf97520edfc75554bb5c4abbea" dependencies = [ - "cranelift-bforest 0.85.0", - "cranelift-codegen-meta 0.85.0", - "cranelift-codegen-shared 0.85.0", - "cranelift-entity 0.85.0", + "cranelift-bforest 0.85.3", + "cranelift-codegen-meta 0.85.3", + "cranelift-codegen-shared 0.85.3", + "cranelift-entity 0.85.3", "cranelift-isle", - "gimli 0.26.1", + "gimli", "log", "regalloc2", "smallvec", @@ -1172,57 +1166,56 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.76.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976efb22fcab4f2cd6bd4e9913764616a54d895c1a23530128d04e03633c555f" +checksum = "0f32ddb89e9b89d3d9b36a5b7d7ea3261c98235a76ac95ba46826b8ec40b1a24" dependencies = [ - "cranelift-codegen-shared 0.76.0", - "cranelift-entity 0.76.0", + "cranelift-codegen-shared 0.82.3", ] [[package]] name = "cranelift-codegen-meta" -version = "0.85.0" +version = "0.85.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea66cbba3eb7fcb3ec9f42839a6d381bd40cf97780397e7167daf9725d4ffa0" +checksum = "e0a3cea8fdab90e44018c5b9a1dfd460d8ee265ac354337150222a354628bdb6" dependencies = [ - "cranelift-codegen-shared 0.85.0", + "cranelift-codegen-shared 0.85.3", ] [[package]] name = "cranelift-codegen-shared" -version = "0.76.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dabb5fe66e04d4652e434195b45ae65b5c8172d520247b8f66d8df42b2b45dc" +checksum = "01fd0d9f288cc1b42d9333b7a776b17e278fc888c28e6a0f09b5573d45a150bc" [[package]] name = "cranelift-codegen-shared" -version = "0.85.0" +version = "0.85.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712fbebd119a476f59122b4ba51fdce893a66309b5c92bd5506bfb11a0587496" +checksum = "5ac72f76f2698598951ab26d8c96eaa854810e693e7dd52523958b5909fde6b2" [[package]] name = "cranelift-entity" -version = "0.76.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3329733e4d4b8e91c809efcaa4faee80bf66f20164e3dd16d707346bd3494799" +checksum = "9e3bfe172b83167604601faf9dc60453e0d0a93415b57a9c4d1a7ae6849185cf" [[package]] name = "cranelift-entity" -version = "0.85.0" +version = "0.85.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cb8b95859c4e14c9e860db78d596a904fdbe9261990233b62bd526346cb56cb" +checksum = "09eaeacfcd2356fe0e66b295e8f9d59fdd1ac3ace53ba50de14d628ec902f72d" dependencies = [ "serde", ] [[package]] name = "cranelift-frontend" -version = "0.76.0" +version = "0.82.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "279afcc0d3e651b773f94837c3d581177b348c8d69e928104b2e9fccb226f921" +checksum = "a006e3e32d80ce0e4ba7f1f9ddf66066d052a8c884a110b91d05404d6ce26dce" dependencies = [ - "cranelift-codegen 0.76.0", + "cranelift-codegen 0.82.3", "log", "smallvec", "target-lexicon", @@ -1230,11 +1223,11 @@ dependencies = [ [[package]] name = "cranelift-frontend" -version = "0.85.0" +version = "0.85.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7b91b19a7d1221a73f190c0e865c12be77a84f661cac89abfd4ab5820142886" +checksum = "dba69c9980d5ffd62c18a2bde927855fcd7c8dc92f29feaf8636052662cbd99c" dependencies = [ - "cranelift-codegen 0.85.0", + "cranelift-codegen 0.85.3", "log", "smallvec", "target-lexicon", @@ -1242,30 +1235,30 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.85.0" +version = "0.85.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86d4f53bc86fb458e59c695c6a95ce8346e6a8377ee7ffc058e3ac08b5f94cb1" +checksum = "d2920dc1e05cac40304456ed3301fde2c09bd6a9b0210bcfa2f101398d628d5b" [[package]] name = "cranelift-native" -version = "0.85.0" +version = "0.85.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "592f035d0ed41214dfeeb37abd536233536a27be6b4c2d39f380cd402f0cff4f" +checksum = "f04dfa45f9b2a6f587c564d6b63388e00cd6589d2df6ea2758cf79e1a13285e6" dependencies = [ - "cranelift-codegen 0.85.0", + "cranelift-codegen 0.85.3", "libc", "target-lexicon", ] [[package]] name = "cranelift-wasm" -version = "0.85.0" +version = "0.85.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "295add6bf0b527a8bc50d02e31ff878585d2d2db53cb7e8754d6d82b84480086" +checksum = "31a46513ae6f26f3f267d8d75b5373d555fbbd1e68681f348d99df43f747ec54" dependencies = [ - "cranelift-codegen 0.85.0", - "cranelift-entity 0.85.0", - "cranelift-frontend 0.85.0", + "cranelift-codegen 0.85.3", + "cranelift-entity 0.85.3", + "cranelift-frontend 0.85.3", "itertools", "log", "smallvec", @@ -1275,18 +1268,18 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if 1.0.0", ] [[package]] name = "criterion" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1604dafd25fba2fe2d5895a9da139f8dc9b319a5fe5354ca137cbbce4e178d10" +checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f" dependencies = [ "atty", "cast", @@ -1312,9 +1305,9 @@ dependencies = [ [[package]] name = "criterion-plot" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d00996de9f2f7559f7f4dc286073197f83e92256a59ed395f9aac01fe717da57" +checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876" dependencies = [ "cast", "itertools", @@ -1322,9 +1315,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.0" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" dependencies = [ "cfg-if 1.0.0", "crossbeam-utils", @@ -1332,9 +1325,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", @@ -1343,25 +1336,26 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.5" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" +checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1" dependencies = [ + "autocfg", "cfg-if 1.0.0", "crossbeam-utils", - "lazy_static", "memoffset", + "once_cell", "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.8" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" +checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" dependencies = [ "cfg-if 1.0.0", - "lazy_static", + "once_cell", ] [[package]] @@ -1376,19 +1370,19 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" dependencies = [ - "generic-array 0.14.4", - "rand_core 0.6.2", + "generic-array 0.14.6", + "rand_core 0.6.3", "subtle", "zeroize", ] [[package]] name = "crypto-common" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.6", "typenum", ] @@ -1398,7 +1392,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.6", "subtle", ] @@ -1408,7 +1402,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.6", "subtle", ] @@ -1436,9 +1430,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.1.19" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8f45d9ad417bcef4817d614a501ab55cdd96a6fdb24f49aab89a54acfd66b19" +checksum = "cdffe87e1d521a10f9696f833fe502293ea446d7f256c06128293a4119bdf4cb" dependencies = [ "quote", "syn", @@ -1466,9 +1460,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434e1720189a637d44fe464f4df1e6eb900b4835255b14354497c78af37d9bb8" +checksum = "4a9b85542f99a2dfa2a1b8e192662741c9859a846b296bef1c92ef9b58b5a216" dependencies = [ "byteorder", "digest 0.8.1", @@ -1479,9 +1473,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "3.0.2" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f627126b946c25a4638eec0ea634fc52506dea98db118aae985118ce7c3d723f" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" dependencies = [ "byteorder", "digest 0.9.0", @@ -1498,16 +1492,16 @@ checksum = "4033478fbf70d6acf2655ac70da91ee65852d69daf7a67bf7a2f518fb47aafcf" dependencies = [ "byteorder", "digest 0.9.0", - "rand_core 0.6.2", + "rand_core 0.6.3", "subtle", "zeroize", ] [[package]] name = "darling" -version = "0.13.0" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "757c0ded2af11d8e739c4daea1ac623dd1624b06c844cf3f5a39f1bdbd99bb12" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" dependencies = [ "darling_core", "darling_macro", @@ -1515,23 +1509,22 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.13.0" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c34d8efb62d0c2d7f60ece80f75e5c63c1588ba68032740494b0b9a996466e3" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "strsim", "syn", ] [[package]] name = "darling_macro" -version = "0.13.0" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade7bff147130fe5e6d39f089c6bd49ec0250f35d70b2eebf72afdfc919f15cc" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" dependencies = [ "darling_core", "quote", @@ -1546,9 +1539,9 @@ checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" [[package]] name = "data-encoding-macro" -version = "0.1.10" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a94feec3d2ba66c0b6621bca8bc6f68415b1e5c69af3586fdd0af9fd9f29b17" +checksum = "86927b7cd2fe88fa698b87404b287ab98d1a0063a34071d92e575b72d3029aca" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -1556,9 +1549,9 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f83e699727abca3c56e187945f303389590305ab2f0185ea445aa66e8d5f2a" +checksum = "a5bbed42daaa95e780b60a50546aa345b8413a1e46f9a40a12907d3598f038db" dependencies = [ "data-encoding", "syn", @@ -1586,9 +1579,9 @@ dependencies = [ [[package]] name = "diff" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" [[package]] name = "difflib" @@ -1611,7 +1604,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.6", ] [[package]] @@ -1620,7 +1613,7 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" dependencies = [ - "block-buffer 0.10.0", + "block-buffer 0.10.2", "crypto-common", "subtle", ] @@ -1646,9 +1639,9 @@ dependencies = [ [[package]] name = "dirs-sys" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" dependencies = [ "libc", "redox_users", @@ -1668,9 +1661,9 @@ dependencies = [ [[package]] name = "dissimilar" -version = "1.0.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4b29f4b9bb94bf267d57269fd0706d343a160937108e9619fe380645428abb" +checksum = "8c97b9233581d84b8e1e689cdd3a47b6f69770084fc246e86a7f78b0d9c1d4a5" [[package]] name = "dns-parser" @@ -1679,7 +1672,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4d33be9473d06f75f58220f71f7a9317aca647dc061dbd3c361b0bef505fbea" dependencies = [ "byteorder", - "quick-error 1.2.3", + "quick-error", ] [[package]] @@ -1696,9 +1689,9 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] name = "dtoa" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5caaa75cbd2b960ff1e5392d2cfb1f44717fffe12fc1f32b7b5d1267f99732a6" +checksum = "c6053ff46b5639ceb91756a85a4c8914668393a03170efd79c8884a529d80656" [[package]] name = "dyn-clonable" @@ -1723,9 +1716,9 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.5" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e50f3adc76d6a43f5ed73b698a87d0760ca74617f60f7c3b879003536fdd28" +checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2" [[package]] name = "dynasm" @@ -1750,7 +1743,7 @@ checksum = "64fba5a42bd76a17cad4bfa00de168ee1cbfa06a5e8ce992ae880218c05641a9" dependencies = [ "byteorder", "dynasm", - "memmap2 0.5.0", + "memmap2 0.5.5", ] [[package]] @@ -1767,9 +1760,9 @@ dependencies = [ [[package]] name = "ed25519" -version = "1.0.3" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c66a534cbb46ab4ea03477eae19d5c22c01da8258030280b7bd9d8433fb6ef" +checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" dependencies = [ "signature", ] @@ -1780,19 +1773,19 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" dependencies = [ - "curve25519-dalek 3.0.2", + "curve25519-dalek 3.2.0", "ed25519", "rand 0.7.3", "serde", - "sha2 0.9.8", + "sha2 0.9.9", "zeroize", ] [[package]] name = "either" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" [[package]] name = "elliptic-curve" @@ -1804,9 +1797,9 @@ dependencies = [ "crypto-bigint", "der", "ff", - "generic-array 0.14.4", + "generic-array 0.14.6", "group", - "rand_core 0.6.2", + "rand_core 0.6.3", "sec1", "subtle", "zeroize", @@ -1846,9 +1839,9 @@ dependencies = [ [[package]] name = "enumflags2" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b3ab37dc79652c9d85f1f7b6070d77d321d2467f5fe7b00d6b7a86c57b092ae" +checksum = "e75d4cd21b95383444831539909fbb14b9dc3fdceb2a6f5d36577329a1f55ccb" dependencies = [ "enumflags2_derive", ] @@ -1866,18 +1859,18 @@ dependencies = [ [[package]] name = "enumset" -version = "1.0.7" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e76129da36102af021b8e5000dab2c1c30dbef85c1e482beeff8da5dde0e0b0" +checksum = "4799cdb24d48f1f8a7a98d06b7fde65a85a2d1e42b25a889f5406aa1fbefe074" dependencies = [ "enumset_derive", ] [[package]] name = "enumset_derive" -version = "0.5.5" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6451128aa6655d880755345d085494cf7561a6bee7c8dc821e5d77e6d267ecd4" +checksum = "ea83a3fbdc1d999ccfbcbee717eab36f8edf2d71693a23ce0d7cca19e085304c" dependencies = [ "darling", "proc-macro2", @@ -1917,19 +1910,19 @@ dependencies = [ [[package]] name = "errno-dragonfly" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" dependencies = [ - "gcc", + "cc", "libc", ] [[package]] name = "event-listener" -version = "2.5.1" +version = "2.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "exit-future" @@ -1940,6 +1933,16 @@ dependencies = [ "futures", ] +[[package]] +name = "eyre" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "fake-simd" version = "0.1.2" @@ -1954,9 +1957,9 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fastrand" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" dependencies = [ "instant", ] @@ -1972,11 +1975,11 @@ dependencies = [ [[package]] name = "ff" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2958d04124b9f27f175eaeb9a9f383d026098aa837eadd8ba22c11f13a05b9e" +checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" dependencies = [ - "rand_core 0.6.2", + "rand_core 0.6.3", "subtle", ] @@ -1992,14 +1995,14 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0408e2626025178a6a7f7ffc05a25bc47103229f19c113755de7bf63816290c" +checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c" dependencies = [ "cfg-if 1.0.0", "libc", "redox_syscall", - "winapi", + "windows-sys 0.36.1", ] [[package]] @@ -2014,8 +2017,8 @@ dependencies = [ "log", "num-traits", "parity-scale-codec", - "parking_lot 0.12.0", - "rand 0.8.4", + "parking_lot 0.12.1", + "rand 0.8.5", "scale-info", ] @@ -2026,30 +2029,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" dependencies = [ "byteorder", - "rand 0.8.4", + "rand 0.8.5", "rustc-hex", "static_assertions", ] [[package]] name = "fixedbitset" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "398ea4fabe40b9b0d885340a2a991a44c8a645624075ad966d21f88688e2b69e" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.20" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0" +checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" dependencies = [ - "cfg-if 1.0.0", "crc32fast", - "libc", "libz-sys", "miniz_oxide", ] +[[package]] +name = "flex-error" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c606d892c9de11507fa0dcffc116434f94e105d0bbdc4e405b61519464c49d7b" +dependencies = [ + "eyre", + "paste 1.0.8", +] + [[package]] name = "fnv" version = "1.0.7" @@ -2083,7 +2094,7 @@ dependencies = [ "linregress", "log", "parity-scale-codec", - "paste 1.0.6", + "paste 1.0.8", "scale-info", "serde", "sp-api", @@ -2102,7 +2113,7 @@ version = "4.0.0-dev" dependencies = [ "Inflector", "chrono", - "clap 3.1.18", + "clap 3.2.16", "comfy-table", "frame-benchmarking", "frame-support", @@ -2118,7 +2129,7 @@ dependencies = [ "log", "memory-db", "parity-scale-codec", - "rand 0.8.4", + "rand 0.8.5", "rand_pcg 0.3.1", "sc-block-builder", "sc-cli", @@ -2184,13 +2195,13 @@ dependencies = [ name = "frame-election-solution-type-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 3.1.18", + "clap 3.2.16", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-support", "honggfuzz", "parity-scale-codec", - "rand 0.8.4", + "rand 0.8.5", "scale-info", "sp-arithmetic", "sp-npos-elections", @@ -2244,7 +2255,7 @@ dependencies = [ "once_cell", "parity-scale-codec", "parity-util-mem", - "paste 1.0.6", + "paste 1.0.8", "pretty_assertions", "scale-info", "serde", @@ -2476,16 +2487,16 @@ checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" [[package]] name = "futures-lite" -version = "1.11.3" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4481d0cd0de1d204a4fa55e7d45f07b1d958abcb06714b3446438e2eff695fb" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" dependencies = [ "fastrand", "futures-core", "futures-io", "memchr", "parking", - "pin-project-lite 0.2.6", + "pin-project-lite 0.2.9", "waker-fn", ] @@ -2502,9 +2513,9 @@ dependencies = [ [[package]] name = "futures-rustls" -version = "0.22.1" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01fe9932a224b72b45336d96040aa86386d674a31d0af27d800ea7bc8ca97fe" +checksum = "d2411eed028cdf8c8034eaf21f9915f956b6c3abec4d4c7949ee67f0721127bd" dependencies = [ "futures-io", "rustls", @@ -2542,7 +2553,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project-lite 0.2.6", + "pin-project-lite 0.2.9", "pin-utils", "slab", ] @@ -2556,12 +2567,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "gcc" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" - [[package]] name = "generate-bags" version = "4.0.0-dev" @@ -2587,9 +2592,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.4" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", "version_check", @@ -2620,20 +2625,20 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.3" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi 0.10.0+wasi-snapshot-preview1", + "wasi 0.11.0+wasi-snapshot-preview1", ] [[package]] name = "ghash" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b442c439366184de619215247d24e908912b175e824a530253845ac4c251a5c1" +checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" dependencies = [ "opaque-debug 0.3.0", "polyval", @@ -2641,20 +2646,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.25.0" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7" -dependencies = [ - "fallible-iterator", - "indexmap", - "stable_deref_trait", -] - -[[package]] -name = "gimli" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" dependencies = [ "fallible-iterator", "indexmap", @@ -2663,9 +2657,9 @@ dependencies = [ [[package]] name = "git2" -version = "0.14.2" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3826a6e0e2215d7a41c2bfc7c9244123969273f3476b939a226aac0ab56e9e3c" +checksum = "d0155506aab710a86160ddb504a480d2964d7ab5b9e62419be69e0032bc5931c" dependencies = [ "bitflags", "libc", @@ -2682,9 +2676,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "globset" -version = "0.4.6" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c152169ef1e421390738366d2f796655fec62621dabbd0fd476f905934061e4a" +checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a" dependencies = [ "aho-corasick", "bstr", @@ -2695,15 +2689,14 @@ dependencies = [ [[package]] name = "gloo-timers" -version = "0.2.1" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f" +checksum = "5fb7d06c1c8cc2a29bee7ec961009a0b2caa0793ee4900c2ffb348734ba1c8f9" dependencies = [ "futures-channel", "futures-core", "js-sys", "wasm-bindgen", - "web-sys", ] [[package]] @@ -2713,10 +2706,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" dependencies = [ "ff", - "rand_core 0.6.2", + "rand_core 0.6.3", "subtle", ] +[[package]] +name = "gumdrop" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bc700f989d2f6f0248546222d9b4258f5b02a171a431f8285a81c08142629e3" +dependencies = [ + "gumdrop_derive", +] + +[[package]] +name = "gumdrop_derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "729f9bd3449d77e7831a18abfb7ba2f99ee813dfd15b8c2167c9a54ba20aa99d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "h2" version = "0.3.13" @@ -2738,22 +2751,22 @@ dependencies = [ [[package]] name = "half" -version = "1.7.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3" +checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "handlebars" -version = "4.2.2" +version = "4.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d6a30320f094710245150395bc763ad23128d6a1ebbad7594dc4164b62c56b" +checksum = "360d9740069b2f6cbb63ce2dbaa71a20d3185350cbb990d7bebeb9318415eb17" dependencies = [ "log", "pest", "pest_derive", - "quick-error 2.0.0", "serde", "serde_json", + "thiserror", ] [[package]] @@ -2782,9 +2795,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.12.0" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c21d40587b92fa6a6c6e3c1bdbf87d75511db5672f9c93175574b3a00df1758" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ "ahash", ] @@ -2797,9 +2810,9 @@ checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" [[package]] name = "hermit-abi" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] @@ -2849,7 +2862,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" dependencies = [ "digest 0.9.0", - "generic-array 0.14.4", + "generic-array 0.14.6", "hmac 0.8.1", ] @@ -2883,31 +2896,31 @@ checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ "bytes", "fnv", - "itoa 1.0.1", + "itoa 1.0.3", ] [[package]] name = "http-body" -version = "0.4.2" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60daa14be0e0786db0f03a9e57cb404c9d756eed2b6c62b9ea98ec5743ec75a9" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", "http", - "pin-project-lite 0.2.6", + "pin-project-lite 0.2.9", ] [[package]] name = "httparse" -version = "1.5.1" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" +checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" [[package]] name = "httpdate" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "humantime" @@ -2917,9 +2930,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.16" +version = "0.14.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7ec3e62bdc98a2f0393a5048e4c30ef659440ea6e0e572965103e72bd836f55" +checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" dependencies = [ "bytes", "futures-channel", @@ -2930,8 +2943,8 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 0.4.8", - "pin-project-lite 0.2.6", + "itoa 1.0.3", + "pin-project-lite 0.2.9", "socket2", "tokio", "tower-service", @@ -2954,6 +2967,78 @@ dependencies = [ "tokio-rustls", ] +[[package]] +name = "iana-time-zone" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9512e544c25736b82aebbd2bf739a47c8a1c935dfcc3a6adcde10e35cd3cd468" +dependencies = [ + "android_system_properties", + "core-foundation", + "js-sys", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "ibc" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ab02a177672699b494b7dd8396b633224614100cb216c41bc0f7175e2ff13cc" +dependencies = [ + "bytes", + "derive_more", + "flex-error", + "ibc-proto", + "ics23", + "num-traits", + "primitive-types", + "prost 0.11.0", + "prost-types 0.11.1", + "safe-regex", + "serde", + "serde_derive", + "serde_json", + "sha2 0.10.2", + "subtle-encoding", + "tendermint", + "tendermint-light-client-verifier", + "tendermint-proto", + "tendermint-testgen", + "time 0.3.11", + "tracing", + "uint", +] + +[[package]] +name = "ibc-proto" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e031bfb42dea291f45e17b8a547f3b8490e1aad15c0279134dd2ed08d15cf18" +dependencies = [ + "base64", + "bytes", + "prost 0.11.0", + "prost-types 0.11.1", + "serde", + "tendermint-proto", +] + +[[package]] +name = "ics23" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "149f3093d710c18c749ba68de3bc8131927aca00410ab90f3368e0524d01fe90" +dependencies = [ + "anyhow", + "bytes", + "hex", + "prost 0.11.0", + "ripemd", + "sha2 0.10.2", + "sha3 0.10.2", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -2983,9 +3068,9 @@ dependencies = [ [[package]] name = "if-watch" -version = "1.0.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae8f4a3c3d4c89351ca83e120c1c00b27df945d38e05695668c9d4b4f7bc52f3" +checksum = "015a7df1eb6dda30df37f34b63ada9b7b352984b0e84de2a20ed526345000791" dependencies = [ "async-io", "core-foundation", @@ -3010,9 +3095,9 @@ dependencies = [ [[package]] name = "impl-serde" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b47ca4d2b6931707a55fce5cf66aff80e2178c8b63bbb4ecb5695cbc870ddf6f" +checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" dependencies = [ "serde", ] @@ -3028,14 +3113,20 @@ dependencies = [ "syn", ] +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + [[package]] name = "indexmap" -version = "1.8.0" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" dependencies = [ "autocfg", - "hashbrown 0.11.2", + "hashbrown 0.12.3", "serde", ] @@ -3110,24 +3201,24 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" [[package]] name = "jobserver" -version = "0.1.21" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2" +checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.54" +version = "0.3.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1866b355d9c878e5e607473cbe3f63282c0b7aad2db1dbebf55076c686918254" +checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" dependencies = [ "wasm-bindgen", ] @@ -3187,8 +3278,8 @@ dependencies = [ "hyper", "jsonrpsee-types", "lazy_static", - "parking_lot 0.12.0", - "rand 0.8.4", + "parking_lot 0.12.1", + "rand 0.8.5", "rustc-hash", "serde", "serde_json", @@ -3290,9 +3381,9 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" +checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" [[package]] name = "keccak-hasher" @@ -3426,7 +3517,7 @@ checksum = "ece7e668abd21387aeb6628130a6f4c802787f014fa46bc83221448322250357" dependencies = [ "kvdb", "parity-util-mem", - "parking_lot 0.12.0", + "parking_lot 0.12.1", ] [[package]] @@ -3441,7 +3532,7 @@ dependencies = [ "num_cpus", "owning_ref", "parity-util-mem", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "regex", "rocksdb", "smallvec", @@ -3461,21 +3552,21 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "leb128" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3576a87f2ba00f6f106fdfcd16db1d698d648a26ad8e0573cad8537c3c362d2a" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.126" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +checksum = "64de3cc433455c14174d42e554d4027ee631c4d046d43e3ecc6efc4636cdc7a7" [[package]] name = "libgit2-sys" -version = "0.13.2+1.4.2" +version = "0.13.4+1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a42de9a51a5c12e00fc0e4ca6bc2ea43582fc6418488e8f615e905d886f258b" +checksum = "d0fa6563431ede25f5cc7f6d803c6afbc1c5d3ad3d4925d12c882bf2b526f5d1" dependencies = [ "cc", "libc", @@ -3495,9 +3586,9 @@ dependencies = [ [[package]] name = "libloading" -version = "0.7.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" +checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" dependencies = [ "cfg-if 1.0.0", "winapi", @@ -3505,9 +3596,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.1" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" +checksum = "292a948cd991e376cf75541fe5b97a1081d713c618b4f1b9500f8844e49eb565" [[package]] name = "libp2p" @@ -3518,7 +3609,7 @@ dependencies = [ "bytes", "futures", "futures-timer", - "getrandom 0.2.3", + "getrandom 0.2.7", "instant", "lazy_static", "libp2p-autonat", @@ -3547,7 +3638,7 @@ dependencies = [ "libp2p-websocket", "libp2p-yamux", "multiaddr", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "pin-project", "rand 0.7.3", "smallvec", @@ -3567,9 +3658,9 @@ dependencies = [ "libp2p-request-response", "libp2p-swarm", "log", - "prost", + "prost 0.10.4", "prost-build", - "rand 0.8.4", + "rand 0.8.5", ] [[package]] @@ -3592,11 +3683,11 @@ dependencies = [ "multiaddr", "multihash", "multistream-select", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "pin-project", - "prost", + "prost 0.10.4", "prost-build", - "rand 0.8.4", + "rand 0.8.5", "ring", "rw-stream-sink", "sha2 0.10.2", @@ -3628,7 +3719,7 @@ dependencies = [ "futures", "libp2p-core", "log", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "smallvec", "trust-dns-resolver", ] @@ -3645,7 +3736,7 @@ dependencies = [ "libp2p-core", "libp2p-swarm", "log", - "prost", + "prost 0.10.4", "prost-build", "rand 0.7.3", "smallvec", @@ -3669,7 +3760,7 @@ dependencies = [ "libp2p-swarm", "log", "prometheus-client", - "prost", + "prost 0.10.4", "prost-build", "rand 0.7.3", "regex", @@ -3692,7 +3783,7 @@ dependencies = [ "libp2p-swarm", "log", "lru", - "prost", + "prost 0.10.4", "prost-build", "prost-codec", "smallvec", @@ -3717,7 +3808,7 @@ dependencies = [ "libp2p-core", "libp2p-swarm", "log", - "prost", + "prost 0.10.4", "prost-build", "rand 0.7.3", "sha2 0.10.2", @@ -3743,7 +3834,7 @@ dependencies = [ "libp2p-core", "libp2p-swarm", "log", - "rand 0.8.4", + "rand 0.8.5", "smallvec", "socket2", "void", @@ -3777,7 +3868,7 @@ dependencies = [ "libp2p-core", "log", "nohash-hasher", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "rand 0.7.3", "smallvec", "unsigned-varint", @@ -3790,14 +3881,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "762408cb5d84b49a600422d7f9a42c18012d8da6ebcd570f9a4a4290ba41fb6f" dependencies = [ "bytes", - "curve25519-dalek 3.0.2", + "curve25519-dalek 3.2.0", "futures", "lazy_static", "libp2p-core", "log", - "prost", + "prost 0.10.4", "prost-build", - "rand 0.8.4", + "rand 0.8.5", "sha2 0.10.2", "snow", "static_assertions", @@ -3832,7 +3923,7 @@ dependencies = [ "futures", "libp2p-core", "log", - "prost", + "prost 0.10.4", "prost-build", "unsigned-varint", "void", @@ -3868,10 +3959,10 @@ dependencies = [ "libp2p-swarm", "log", "pin-project", - "prost", + "prost 0.10.4", "prost-build", "prost-codec", - "rand 0.8.4", + "rand 0.8.5", "smallvec", "static_assertions", "thiserror", @@ -3892,9 +3983,9 @@ dependencies = [ "libp2p-core", "libp2p-swarm", "log", - "prost", + "prost 0.10.4", "prost-build", - "rand 0.8.4", + "rand 0.8.5", "sha2 0.10.2", "thiserror", "unsigned-varint", @@ -4003,7 +4094,7 @@ dependencies = [ "futures-rustls", "libp2p-core", "log", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "quicksink", "rw-stream-sink", "soketto", @@ -4019,7 +4110,7 @@ checksum = "c6dea686217a06072033dc025631932810e2f6ad784e4fafa42e27d311c7a81c" dependencies = [ "futures", "libp2p-core", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "thiserror", "yamux", ] @@ -4041,9 +4132,9 @@ dependencies = [ [[package]] name = "libsecp256k1" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0452aac8bab02242429380e9b2f94ea20cea2b37e2c1777a1358799bbe97f37" +checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" dependencies = [ "arrayref", "base64", @@ -4052,9 +4143,9 @@ dependencies = [ "libsecp256k1-core", "libsecp256k1-gen-ecmult", "libsecp256k1-gen-genmult", - "rand 0.8.4", + "rand 0.8.5", "serde", - "sha2 0.9.8", + "sha2 0.9.9", "typenum", ] @@ -4089,9 +4180,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.2" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "602113192b08db8f38796c4e85c39e960c145965140e918018bcde1952429655" +checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" dependencies = [ "cc", "libc", @@ -4101,9 +4192,9 @@ dependencies = [ [[package]] name = "linked-hash-map" -version = "0.5.4" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linked_hash_set" @@ -4156,10 +4247,11 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" dependencies = [ + "autocfg", "scopeguard", ] @@ -4196,11 +4288,11 @@ dependencies = [ [[package]] name = "lru" -version = "0.7.5" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32613e41de4c47ab04970c348ca7ae7382cf116625755af070b008a15516a889" +checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a" dependencies = [ - "hashbrown 0.11.2", + "hashbrown 0.12.3", ] [[package]] @@ -4214,9 +4306,9 @@ dependencies = [ [[package]] name = "lz4" -version = "1.23.2" +version = "1.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac20ed6991e01bf6a2e68cc73df2b389707403662a8ba89f68511fb340f724c" +checksum = "4edcb94251b1c375c459e5abe9fb0168c1c826c3370172684844f8f3f8d1a885" dependencies = [ "libc", "lz4-sys", @@ -4224,9 +4316,9 @@ dependencies = [ [[package]] name = "lz4-sys" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca79aa95d8b3226213ad454d328369853be3a1382d89532a854f4d69640acae" +checksum = "d7be8908e2ed6f31c02db8a9fa962f03e36c53fbfde437363eae3306b85d7e17" dependencies = [ "cc", "libc", @@ -4241,12 +4333,6 @@ dependencies = [ "libc", ] -[[package]] -name = "maplit" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" - [[package]] name = "match_cfg" version = "0.1.0" @@ -4264,24 +4350,24 @@ dependencies = [ [[package]] name = "matches" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "matrixmultiply" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a8a15b776d9dfaecd44b03c5828c2199cddff5247215858aac14624f8d6b741" +checksum = "add85d4dd35074e6fedc608f8c8f513a3548619a9024b751949ef0e8e45a4d84" dependencies = [ "rawpointer", ] [[package]] name = "memchr" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memfd" @@ -4304,27 +4390,27 @@ dependencies = [ [[package]] name = "memmap2" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04e3e85b970d650e2ae6d70592474087051c11c54da7f7b4949725c5735fbcc6" +checksum = "723e3ebdcdc5c023db1df315364573789f8857c11b631a2fdfad7c00f5c046b4" dependencies = [ "libc", ] [[package]] name = "memmap2" -version = "0.5.0" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4647a11b578fead29cdbb34d4adef8dd3dc35b876c9c6d5240d83f205abfe96e" +checksum = "3a79b39c93a7a5a27eeaf9a23b5ff43f1b9e0ad6b1cdd441140ae53c35613fc7" dependencies = [ "libc", ] [[package]] name = "memoffset" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" dependencies = [ "autocfg", ] @@ -4336,7 +4422,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6566c70c1016f525ced45d7b7f97730a2bafb037c788211d0c186ef5b2189f0a" dependencies = [ "hash-db", - "hashbrown 0.12.0", + "hashbrown 0.12.3", "parity-util-mem", ] @@ -4366,12 +4452,11 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.4.4" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" dependencies = [ "adler", - "autocfg", ] [[package]] @@ -4388,9 +4473,9 @@ dependencies = [ [[package]] name = "more-asserts" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0debeb9fcf88823ea64d64e4a815ab1643f33127d995978e099942ce38f25238" +checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" [[package]] name = "multiaddr" @@ -4434,7 +4519,7 @@ dependencies = [ "digest 0.10.3", "multihash-derive", "sha2 0.10.2", - "sha3 0.10.0", + "sha3 0.10.2", "unsigned-varint", ] @@ -4454,9 +4539,9 @@ dependencies = [ [[package]] name = "multimap" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1255076139a83bb467426e7f8d0134968a8118844faa755985e077cf31850333" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "multistream-select" @@ -4482,9 +4567,9 @@ dependencies = [ "matrixmultiply", "nalgebra-macros", "num-complex", - "num-rational 0.4.0", + "num-rational 0.4.1", "num-traits", - "rand 0.8.4", + "rand 0.8.5", "rand_distr", "simba", "typenum", @@ -4507,7 +4592,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7d66043b25d4a6cccb23619d10c19c25304b355a7dccd4a8e11423dd2382146" dependencies = [ - "rand 0.8.4", + "rand 0.8.5", ] [[package]] @@ -4524,9 +4609,9 @@ dependencies = [ [[package]] name = "netlink-packet-route" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "733ea73609acfd7fa7ddadfb7bf709b0471668c456ad9513685af543a06342b2" +checksum = "d9ea4302b9759a7a88242299225ea3688e63c85ea136371bb6cf94fd674efaab" dependencies = [ "anyhow", "bitflags", @@ -4544,29 +4629,30 @@ checksum = "25af9cf0dc55498b7bd94a1508af7a78706aa0ab715a73c5169273e03c84845e" dependencies = [ "anyhow", "byteorder", - "paste 1.0.6", + "paste 1.0.8", "thiserror", ] [[package]] name = "netlink-proto" -version = "0.9.2" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef8785b8141e8432aa45fceb922a7e876d7da3fad37fa7e7ec702ace3aa0826b" +checksum = "65b4b14489ab424703c092062176d52ba55485a89c076b4f9db05092b7223aa6" dependencies = [ "bytes", "futures", "log", "netlink-packet-core", "netlink-sys", + "thiserror", "tokio", ] [[package]] name = "netlink-sys" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c9f9547a08241bee7b6558b9b98e1f290d187de8b7cfca2bbb4937bcaa8f8" +checksum = "92b654097027250401127914afb37cb1f311df6610a9891ff07a757e94199027" dependencies = [ "async-io", "bytes", @@ -4577,9 +4663,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.22.3" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf" +checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" dependencies = [ "bitflags", "cc", @@ -4590,22 +4676,20 @@ dependencies = [ [[package]] name = "nix" -version = "0.23.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" +checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc" dependencies = [ "bitflags", - "cc", "cfg-if 1.0.0", "libc", - "memoffset", ] [[package]] name = "node-bench" version = "0.9.0-dev" dependencies = [ - "clap 3.1.18", + "clap 3.2.16", "derive_more", "fs_extra", "futures", @@ -4644,7 +4728,7 @@ version = "3.0.0-dev" dependencies = [ "assert_cmd", "async-std", - "clap 3.1.18", + "clap 3.2.16", "clap_complete", "criterion", "frame-benchmarking-cli", @@ -4667,7 +4751,7 @@ dependencies = [ "pallet-transaction-payment", "parity-scale-codec", "platforms", - "rand 0.8.4", + "rand 0.8.5", "regex", "remote-externalities", "sc-authority-discovery", @@ -4762,7 +4846,7 @@ dependencies = [ name = "node-inspect" version = "0.9.0-dev" dependencies = [ - "clap 3.1.18", + "clap 3.2.16", "parity-scale-codec", "sc-cli", "sc-client-api", @@ -4821,7 +4905,7 @@ dependencies = [ name = "node-runtime-generate-bags" version = "3.0.0" dependencies = [ - "clap 3.1.18", + "clap 3.2.16", "generate-bags", "kitchensink-runtime", ] @@ -4830,7 +4914,7 @@ dependencies = [ name = "node-template" version = "4.0.0-dev" dependencies = [ - "clap 3.1.18", + "clap 3.2.16", "frame-benchmarking", "frame-benchmarking-cli", "frame-system", @@ -4953,13 +5037,12 @@ checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" [[package]] name = "nom" -version = "7.1.0" +version = "7.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" dependencies = [ "memchr", "minimal-lexical", - "version_check", ] [[package]] @@ -4975,13 +5058,24 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085" +checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" dependencies = [ "num-traits", ] +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "num-format" version = "0.4.0" @@ -4994,9 +5088,9 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", "num-traits", @@ -5016,9 +5110,9 @@ dependencies = [ [[package]] name = "num-rational" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" dependencies = [ "autocfg", "num-integer", @@ -5046,19 +5140,19 @@ dependencies = [ ] [[package]] -name = "object" -version = "0.27.1" +name = "num_threads" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" dependencies = [ - "memchr", + "libc", ] [[package]] name = "object" -version = "0.28.3" +version = "0.28.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40bec70ba014595f99f7aa110b84331ffe1ee9aece7fe6f387cc7e3ecda4d456" +checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424" dependencies = [ "crc32fast", "hashbrown 0.11.2", @@ -5066,11 +5160,20 @@ dependencies = [ "memchr", ] +[[package]] +name = "object" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" +checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" [[package]] name = "oorandom" @@ -5092,21 +5195,21 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl-probe" -version = "0.1.2" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "os_str_bytes" -version = "6.0.0" +version = "6.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" +checksum = "648001efe5d5c0102d8cea768e348da85d90af8ba91f0bea908f157951493cd4" [[package]] name = "output_vt100" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" +checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" dependencies = [ "winapi", ] @@ -5299,7 +5402,7 @@ dependencies = [ "frame-election-provider-support", "honggfuzz", "pallet-bags-list", - "rand 0.8.4", + "rand 0.8.5", ] [[package]] @@ -5453,7 +5556,7 @@ dependencies = [ "pallet-utility", "parity-scale-codec", "pretty_assertions", - "rand 0.8.4", + "rand 0.8.5", "rand_pcg 0.3.1", "scale-info", "serde", @@ -5570,7 +5673,7 @@ dependencies = [ "pallet-balances", "pallet-election-provider-support-benchmarking", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "rand 0.7.3", "scale-info", "sp-arithmetic", @@ -5712,6 +5815,41 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-ibc" +version = "3.0.0-pre.0" +dependencies = [ + "chrono", + "flex-error", + "frame-benchmarking", + "frame-support", + "frame-system", + "hex", + "ibc", + "ibc-proto", + "log", + "pallet-assets", + "pallet-babe", + "pallet-balances", + "pallet-timestamp", + "parity-scale-codec", + "prost 0.11.0", + "prost-types 0.11.1", + "scale-info", + "serde", + "serde_json", + "sha2 0.10.2", + "sp-core", + "sp-io", + "sp-keyring", + "sp-runtime", + "sp-std", + "sp-tracing", + "sp-version", + "tendermint-proto", + "time 0.3.11", +] + [[package]] name = "pallet-identity" version = "4.0.0-dev" @@ -6268,7 +6406,7 @@ dependencies = [ "log", "pallet-balances", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "remote-externalities", "scale-info", "serde", @@ -6499,9 +6637,9 @@ dependencies = [ [[package]] name = "parity-db" -version = "0.3.13" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55a7901b85874402471e131de3332dde0e51f38432c69a3853627c8e25433048" +checksum = "2bb474d0ed0836e185cb998a6b140ed1073d1fbf27d690ecf9ede8030289382c" dependencies = [ "blake2-rfc", "crc32fast", @@ -6510,17 +6648,17 @@ dependencies = [ "libc", "log", "lz4", - "memmap2 0.2.1", + "memmap2 0.2.3", "parking_lot 0.11.2", - "rand 0.8.4", + "rand 0.8.5", "snap", ] [[package]] name = "parity-scale-codec" -version = "3.1.3" +version = "3.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04bc9583b5e01cc8c70d89acc9af14ef9b1c29ee3a0074b2a9eea8c0fa396690" +checksum = "9182e4a71cae089267ab03e67c99368db7cd877baf50f931e5d6d4b71e195ac0" dependencies = [ "arrayvec 0.7.2", "bitvec", @@ -6556,10 +6694,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c32561d248d352148124f036cac253a644685a21dc9fea383eb4907d7bd35a8f" dependencies = [ "cfg-if 1.0.0", - "hashbrown 0.12.0", + "hashbrown 0.12.3", "impl-trait-for-tuples", "parity-util-mem-derive", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "primitive-types", "smallvec", "winapi", @@ -6610,12 +6748,12 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.1", + "parking_lot_core 0.9.3", ] [[package]] @@ -6634,15 +6772,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.1" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" dependencies = [ "cfg-if 1.0.0", "libc", "redox_syscall", "smallvec", - "windows-sys 0.32.0", + "windows-sys 0.36.1", ] [[package]] @@ -6657,9 +6795,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" +checksum = "9423e2b32f7a043629287a536f21951e8c6a82482d0acb1eeebfc90bc2225b22" [[package]] name = "paste-impl" @@ -6702,18 +6840,19 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pest" -version = "2.1.3" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +checksum = "69486e2b8c2d2aeb9762db7b4e00b0331156393555cff467f4163ff06821eef8" dependencies = [ + "thiserror", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.1.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" +checksum = "b13570633aff33c6d22ce47dd566b10a3b9122c2fe9d8e7501895905be532b91" dependencies = [ "pest", "pest_generator", @@ -6721,9 +6860,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.1.3" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" +checksum = "b3c567e5702efdc79fb18859ea74c3eb36e14c43da7b8c1f098a4ed6514ec7a0" dependencies = [ "pest", "pest_meta", @@ -6734,20 +6873,20 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.1.3" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" +checksum = "5eb32be5ee3bbdafa8c7a18b0a8a8d962b66cfa2ceee4037f49267a50ee821fe" dependencies = [ - "maplit", + "once_cell", "pest", - "sha-1 0.8.2", + "sha-1 0.10.0", ] [[package]] name = "petgraph" -version = "0.6.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a13a2fa9d0b63e5f22328828741e523766fff0ee9e779316902290dff3f824f" +checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" dependencies = [ "fixedbitset", "indexmap", @@ -6755,18 +6894,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" +checksum = "78203e83c48cffbe01e4a2d35d566ca4de445d79a85372fc64e378bfc812a260" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" +checksum = "710faf75e1b33345361201d36d04e98ac1ed8909151a017ed384700836104c74" dependencies = [ "proc-macro2", "quote", @@ -6781,9 +6920,9 @@ checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" [[package]] name = "pin-project-lite" -version = "0.2.6" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pin-utils" @@ -6793,9 +6932,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.19" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" [[package]] name = "platforms" @@ -6805,9 +6944,9 @@ checksum = "e8d0eef3571242013a0d5dc84861c3ae4a652e56e12adf8bdc26ff5f8cb34c94" [[package]] name = "plotters" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a3fd9ec30b9749ce28cd91f255d569591cdf937fe280c312143e3c4bad6f2a" +checksum = "9428003b84df1496fb9d6eeee9c5f8145cb41ca375eb0dad204328888832811f" dependencies = [ "num-traits", "plotters-backend", @@ -6818,66 +6957,66 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.0" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b07fffcddc1cb3a1de753caa4e4df03b79922ba43cf882acc1bdd7e8df9f4590" +checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" [[package]] name = "plotters-svg" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b38a02e23bd9604b842a812063aec4ef702b57989c37b655254bb61c471ad211" +checksum = "e0918736323d1baff32ee0eade54984f6f201ad7e97d5cfb5d6ab4a358529615" dependencies = [ "plotters-backend", ] [[package]] name = "polling" -version = "2.0.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2a7bc6b2a29e632e45451c941832803a18cce6781db04de8a04696cdca8bde4" +checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", "libc", "log", - "wepoll-sys", + "wepoll-ffi", "winapi", ] [[package]] name = "poly1305" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fcffab1f78ebbdf4b93b68c1ffebc24037eedf271edaca795732b24e5e4e349" +checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" dependencies = [ - "cpufeatures 0.1.5", + "cpufeatures", "opaque-debug 0.3.0", "universal-hash", ] [[package]] name = "polyval" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6ba6a405ef63530d6cb12802014b22f9c5751bd17cdcddbe9e46d5c8ae83287" +checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" dependencies = [ "cfg-if 1.0.0", - "cpufeatures 0.1.5", + "cpufeatures", "opaque-debug 0.3.0", "universal-hash", ] [[package]] name = "ppv-lite86" -version = "0.2.10" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "predicates" -version = "2.0.2" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c143348f141cc87aab5b950021bac6145d0e5ae754b0591de23244cee42c9308" +checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c" dependencies = [ "difflib", "itertools", @@ -6886,18 +7025,18 @@ dependencies = [ [[package]] name = "predicates-core" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57e35a3326b75e49aa85f5dc6ec15b41108cf5aee58eabb1f274dd18b73c2451" +checksum = "da1c2388b1513e1b605fcec39a95e0a9e8ef088f71443ef37099fa9ae6673fcb" [[package]] name = "predicates-tree" -version = "1.0.2" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15f553275e5721409451eb85e15fd9a860a6e5ab4496eb215987502b5f5391f2" +checksum = "4d86de6de25020a36c6d3643a86d9a6a9f552107c0559c60ea03551b5e16c032" dependencies = [ "predicates-core", - "treeline", + "termtree", ] [[package]] @@ -6927,10 +7066,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.1.3" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" +checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" dependencies = [ + "once_cell", "thiserror", "toml", ] @@ -6967,24 +7107,24 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro2" -version = "1.0.39" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" dependencies = [ "unicode-ident", ] [[package]] name = "prometheus" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f64969ffd5dd8f39bd57a68ac53c163a095ed9d0fb707146da1b27025a3504" +checksum = "cface98dfa6d645ea4c789839f176e4b072265d085bfcc48eaa8d137f58d3c39" dependencies = [ "cfg-if 1.0.0", "fnv", "lazy_static", "memchr", - "parking_lot 0.11.2", + "parking_lot 0.12.1", "thiserror", ] @@ -6995,7 +7135,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac1abe0255c04d15f571427a2d1e00099016506cf3297b53853acd2b7eb87825" dependencies = [ "dtoa", - "itoa 1.0.1", + "itoa 1.0.3", "owning_ref", "prometheus-client-derive-text-encode", ] @@ -7013,12 +7153,22 @@ dependencies = [ [[package]] name = "prost" -version = "0.10.3" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71adf41db68aa0daaefc69bb30bcd68ded9b9abaad5d1fbb6304c4fb390e083e" +dependencies = [ + "bytes", + "prost-derive 0.10.1", +] + +[[package]] +name = "prost" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc03e116981ff7d8da8e5c220e374587b98d294af7ba7dd7fda761158f00086f" +checksum = "399c3c31cdec40583bb68f0b18403400d01ec4289c383aa047560439952c4dd7" dependencies = [ "bytes", - "prost-derive", + "prost-derive 0.11.0", ] [[package]] @@ -7036,8 +7186,8 @@ dependencies = [ "log", "multimap", "petgraph", - "prost", - "prost-types", + "prost 0.10.4", + "prost-types 0.10.1", "regex", "tempfile", "which", @@ -7051,7 +7201,7 @@ checksum = "00af1e92c33b4813cc79fda3f2dbf56af5169709be0202df730e9ebc3e4cd007" dependencies = [ "asynchronous-codec", "bytes", - "prost", + "prost 0.10.4", "thiserror", "unsigned-varint", ] @@ -7069,6 +7219,19 @@ dependencies = [ "syn", ] +[[package]] +name = "prost-derive" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7345d5f0e08c0536d7ac7229952590239e77abf0a0100a1b1d890add6ea96364" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "prost-types" version = "0.10.1" @@ -7076,14 +7239,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d0a014229361011dc8e69c8a1ec6c2e8d0f2af7c91e3ea3f5b2170298461e68" dependencies = [ "bytes", - "prost", + "prost 0.10.4", +] + +[[package]] +name = "prost-types" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dfaa718ad76a44b3415e6c4d53b17c8f99160dcb3a99b10470fce8ad43f6e3e" +dependencies = [ + "bytes", + "prost 0.11.0", ] [[package]] name = "psm" -version = "0.1.12" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3abf49e5417290756acfd26501536358560c4a5cc4a0934d390939acb3e7083a" +checksum = "f446d0a6efba22928558c4fb4ce0b3fd6c89b0061343e390bf01a703742b8125" dependencies = [ "cc", ] @@ -7114,19 +7287,13 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" -[[package]] -name = "quick-error" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ac73b1112776fc109b2e61909bc46c7e1bf0d7f690ffb1676553acce16d5cda" - [[package]] name = "quickcheck" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" dependencies = [ - "rand 0.8.4", + "rand 0.8.5", ] [[package]] @@ -7142,9 +7309,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.18" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ "proc-macro2", ] @@ -7165,20 +7332,19 @@ dependencies = [ "libc", "rand_chacha 0.2.2", "rand_core 0.5.1", - "rand_hc 0.2.0", + "rand_hc", "rand_pcg 0.2.1", ] [[package]] name = "rand" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha 0.3.0", - "rand_core 0.6.2", - "rand_hc 0.3.0", + "rand_chacha 0.3.1", + "rand_core 0.6.3", ] [[package]] @@ -7193,12 +7359,12 @@ dependencies = [ [[package]] name = "rand_chacha" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.2", + "rand_core 0.6.3", ] [[package]] @@ -7212,11 +7378,11 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" dependencies = [ - "getrandom 0.2.3", + "getrandom 0.2.7", ] [[package]] @@ -7226,7 +7392,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" dependencies = [ "num-traits", - "rand 0.8.4", + "rand 0.8.5", ] [[package]] @@ -7238,15 +7404,6 @@ dependencies = [ "rand_core 0.5.1", ] -[[package]] -name = "rand_hc" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" -dependencies = [ - "rand_core 0.6.2", -] - [[package]] name = "rand_pcg" version = "0.2.1" @@ -7262,7 +7419,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" dependencies = [ - "rand_core 0.6.2", + "rand_core 0.6.3", ] [[package]] @@ -7273,9 +7430,9 @@ checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" [[package]] name = "rayon" -version = "1.5.0" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674" +checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" dependencies = [ "autocfg", "crossbeam-deque", @@ -7285,50 +7442,50 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.9.0" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" +checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" dependencies = [ "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", - "lazy_static", "num_cpus", ] [[package]] name = "redox_syscall" -version = "0.2.10" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] [[package]] name = "redox_users" -version = "0.4.0" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.3", + "getrandom 0.2.7", "redox_syscall", + "thiserror", ] [[package]] name = "ref-cast" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300f2a835d808734ee295d45007adacb9ebb29dd3ae2424acfa17930cae541da" +checksum = "ed13bcd201494ab44900a96490291651d200730904221832b9547d24a87d332b" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c38e3aecd2b21cb3959637b883bb3714bc7e43f0268b9a29d3743ee3e55cdd2" +checksum = "5234cd6063258a5e32903b53b1b6ac043a0541c8adc1f610f67b0326c7a578fa" dependencies = [ "proc-macro2", "quote", @@ -7337,9 +7494,9 @@ dependencies = [ [[package]] name = "regalloc" -version = "0.0.31" +version = "0.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "571f7f397d61c4755285cd37853fe8e03271c243424a907415909379659381c5" +checksum = "62446b1d3ebf980bdc68837700af1d77b37bc430e524bf95319c6eada2a4cc02" dependencies = [ "log", "rustc-hash", @@ -7348,9 +7505,9 @@ dependencies = [ [[package]] name = "regalloc2" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d37148700dbb38f994cd99a1431613057f37ed934d7e4d799b7ab758c482461" +checksum = "4a8d23b35d7177df3b9d31ed8a9ab4bf625c668be77a319d4f5efd4a5257701c" dependencies = [ "fxhash", "log", @@ -7360,9 +7517,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.5.5" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" dependencies = [ "aho-corasick", "memchr", @@ -7371,19 +7528,18 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ - "byteorder", "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.6.25" +version = "0.6.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" [[package]] name = "region" @@ -7453,7 +7609,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" dependencies = [ "hostname", - "quick-error 1.2.3", + "quick-error", ] [[package]] @@ -7482,14 +7638,23 @@ dependencies = [ "winapi", ] +[[package]] +name = "ripemd" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1facec54cb5e0dc08553501fa740091086d0259ad0067e0d4103448e4cb22ed3" +dependencies = [ + "digest 0.10.3", +] + [[package]] name = "rkyv" -version = "0.7.37" +version = "0.7.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f08c8062c1fe1253064043b8fc07bfea1b9702b71b4a86c11ea3588183b12e1" +checksum = "cec2b3485b07d96ddfd3134767b8a447b45ea4eb91448d0a35180ec0ffd5ed15" dependencies = [ "bytecheck", - "hashbrown 0.12.0", + "hashbrown 0.12.3", "ptr_meta", "rend", "rkyv_derive", @@ -7498,9 +7663,9 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.7.37" +version = "0.7.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e289706df51226e84814bf6ba1a9e1013112ae29bc7a9878f73fce360520c403" +checksum = "6eaedadc88b53e36dd32d940ed21ae4d850d5916f2581526921f553a72ac34c4" dependencies = [ "proc-macro2", "quote", @@ -7529,24 +7694,24 @@ dependencies = [ [[package]] name = "rtnetlink" -version = "0.9.1" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f54290e54521dac3de4149d83ddf9f62a359b3cc93bcb494a794a41e6f4744b" +checksum = "322c53fd76a18698f1c27381d58091de3a043d356aa5bd0d510608b565f469a0" dependencies = [ "async-global-executor", "futures", "log", "netlink-packet-route", "netlink-proto", - "nix 0.22.3", + "nix 0.24.2", "thiserror", ] [[package]] name = "rustc-demangle" -version = "0.1.18" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" [[package]] name = "rustc-hash" @@ -7575,7 +7740,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.4", + "semver 1.0.13", ] [[package]] @@ -7594,9 +7759,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.35.6" +version = "0.35.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef258c11e17f5c01979a10543a30a4e12faef6aab217a74266e747eefa3aed88" +checksum = "d51cc38aa10f6bbb377ed28197aa052aa4e2b762c22be9d3153d01822587e787" dependencies = [ "bitflags", "errno", @@ -7608,9 +7773,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.20.2" +version = "0.20.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d37e5e2290f3e040b594b1a9e04377c2c671f1a1cfd9bfdef82106ac1c113f84" +checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" dependencies = [ "log", "ring", @@ -7620,9 +7785,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca9ebdfa27d3fc180e42879037b5338ab1c040c06affd00d8338598e7800943" +checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" dependencies = [ "openssl-probe", "rustls-pemfile", @@ -7632,18 +7797,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "0.2.1" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" +checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" dependencies = [ "base64", ] [[package]] name = "rustversion" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" +checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" [[package]] name = "rw-stream-sink" @@ -7658,9 +7823,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.5" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" [[package]] name = "safe-mix" @@ -7671,6 +7836,53 @@ dependencies = [ "rustc_version 0.2.3", ] +[[package]] +name = "safe-proc-macro2" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "814c536dcd27acf03296c618dab7ad62d28e70abd7ba41d3f34a2ce707a2c666" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "safe-quote" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77e530f7831f3feafcd5f1aae406ac205dd998436b4007c8e80f03eca78a88f7" +dependencies = [ + "safe-proc-macro2", +] + +[[package]] +name = "safe-regex" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15289bf322e0673d52756a18194167f2378ec1a15fe884af6e2d2cb934822b0" +dependencies = [ + "safe-regex-macro", +] + +[[package]] +name = "safe-regex-compiler" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba76fae590a2aa665279deb1f57b5098cbace01a0c5e60e262fcf55f7c51542" +dependencies = [ + "safe-proc-macro2", + "safe-quote", +] + +[[package]] +name = "safe-regex-macro" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c2e96b5c03f158d1b16ba79af515137795f4ad4e8de3f790518aae91f1d127" +dependencies = [ + "safe-proc-macro2", + "safe-regex-compiler", +] + [[package]] name = "salsa20" version = "0.9.0" @@ -7709,7 +7921,7 @@ dependencies = [ "libp2p", "log", "parity-scale-codec", - "prost", + "prost 0.10.4", "prost-build", "quickcheck", "rand 0.7.3", @@ -7736,7 +7948,7 @@ dependencies = [ "futures-timer", "log", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "sc-block-builder", "sc-client-api", "sc-proposer-metrics", @@ -7774,7 +7986,7 @@ name = "sc-chain-spec" version = "4.0.0-dev" dependencies = [ "impl-trait-for-tuples", - "memmap2 0.5.0", + "memmap2 0.5.5", "parity-scale-codec", "sc-chain-spec-derive", "sc-network", @@ -7800,7 +8012,7 @@ name = "sc-cli" version = "0.10.0-dev" dependencies = [ "chrono", - "clap 3.1.18", + "clap 3.2.16", "fdlimit", "futures", "hex", @@ -7843,7 +8055,7 @@ dependencies = [ "hash-db", "log", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "sc-executor", "sc-transaction-pool-api", "sc-utils", @@ -7876,7 +8088,7 @@ dependencies = [ "log", "parity-db", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "quickcheck", "sc-client-api", "sc-state-db", @@ -7901,7 +8113,7 @@ dependencies = [ "futures-timer", "libp2p", "log", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "sc-client-api", "sc-utils", "serde", @@ -7924,7 +8136,7 @@ dependencies = [ "futures", "log", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "sc-block-builder", "sc-client-api", "sc-consensus", @@ -7966,7 +8178,7 @@ dependencies = [ "num-rational 0.2.4", "num-traits", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "rand 0.7.3", "rand_chacha 0.2.2", "sc-block-builder", @@ -8088,7 +8300,7 @@ dependencies = [ "futures-timer", "log", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "sc-client-api", "sc-consensus", "sp-api", @@ -8149,8 +8361,8 @@ dependencies = [ "lru", "num_cpus", "parity-scale-codec", - "parking_lot 0.12.0", - "paste 1.0.6", + "parking_lot 0.12.1", + "paste 1.0.8", "regex", "sc-executor-common", "sc-executor-wasmi", @@ -8220,8 +8432,8 @@ dependencies = [ "once_cell", "parity-scale-codec", "parity-wasm 0.42.2", - "paste 1.0.6", - "rustix 0.35.6", + "paste 1.0.8", + "rustix 0.35.7", "sc-allocator", "sc-executor-common", "sc-runtime-test", @@ -8249,8 +8461,8 @@ dependencies = [ "hex", "log", "parity-scale-codec", - "parking_lot 0.12.0", - "rand 0.8.4", + "parking_lot 0.12.1", + "rand 0.8.5", "sc-block-builder", "sc-chain-spec", "sc-client-api", @@ -8329,7 +8541,7 @@ version = "4.0.0-dev" dependencies = [ "async-trait", "hex", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "serde_json", "sp-application-crypto", "sp-core", @@ -8362,9 +8574,9 @@ dependencies = [ "log", "lru", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "pin-project", - "prost", + "prost 0.10.4", "prost-build", "rand 0.7.3", "sc-block-builder", @@ -8444,7 +8656,7 @@ dependencies = [ "libp2p", "log", "parity-scale-codec", - "prost", + "prost 0.10.4", "prost-build", "sc-client-api", "sc-network-common", @@ -8466,7 +8678,7 @@ dependencies = [ "log", "lru", "parity-scale-codec", - "prost", + "prost 0.10.4", "prost-build", "quickcheck", "sc-block-builder", @@ -8497,7 +8709,7 @@ dependencies = [ "futures-timer", "libp2p", "log", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "rand 0.7.3", "sc-block-builder", "sc-client-api", @@ -8532,7 +8744,7 @@ dependencies = [ "num_cpus", "once_cell", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "rand 0.7.3", "sc-block-builder", "sc-client-api", @@ -8587,7 +8799,7 @@ dependencies = [ "lazy_static", "log", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "sc-block-builder", "sc-chain-spec", "sc-client-api", @@ -8621,7 +8833,7 @@ dependencies = [ "jsonrpsee", "log", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "sc-chain-spec", "sc-transaction-pool-api", "scale-info", @@ -8651,7 +8863,7 @@ dependencies = [ name = "sc-runtime-test" version = "2.0.0" dependencies = [ - "paste 1.0.6", + "paste 1.0.8", "sp-core", "sp-io", "sp-runtime", @@ -8676,7 +8888,7 @@ dependencies = [ "log", "parity-scale-codec", "parity-util-mem", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "pin-project", "rand 0.7.3", "sc-block-builder", @@ -8740,7 +8952,7 @@ dependencies = [ "hex-literal", "log", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "sc-block-builder", "sc-client-api", "sc-client-db", @@ -8775,7 +8987,7 @@ dependencies = [ "parity-scale-codec", "parity-util-mem", "parity-util-mem-derive", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "sc-client-api", "sp-core", ] @@ -8824,7 +9036,7 @@ dependencies = [ "futures", "libp2p", "log", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "pin-project", "rand 0.7.3", "serde", @@ -8845,7 +9057,7 @@ dependencies = [ "libc", "log", "once_cell", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "regex", "rustc-hash", "sc-client-api", @@ -8887,7 +9099,7 @@ dependencies = [ "log", "parity-scale-codec", "parity-util-mem", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "sc-block-builder", "sc-client-api", "sc-transaction-pool-api", @@ -8927,16 +9139,16 @@ dependencies = [ "futures-timer", "lazy_static", "log", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "prometheus", "tokio-test", ] [[package]] name = "scale-info" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8980cafbe98a7ee7a9cc16b32ebce542c77883f512d83fbf2ddc8f6a85ea74c9" +checksum = "c46be926081c9f4dd5dd9b6f1d3e3229f2360bc6502dd8836f84a93b7c75e99a" dependencies = [ "bitvec", "cfg-if 1.0.0", @@ -8948,9 +9160,9 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4260c630e8a8a33429d1688eff2f163f24c65a4e1b1578ef6b565061336e4b6f" +checksum = "50e334bb10a245e28e5fd755cabcafd96cfcd167c99ae63a46924ca8d8703a3c" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -8960,12 +9172,12 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" dependencies = [ "lazy_static", - "winapi", + "windows-sys 0.36.1", ] [[package]] @@ -8976,7 +9188,7 @@ checksum = "021b403afe70d81eea68f6ea12f6b3c9588e5d536a94c3bf80f15e7faa267862" dependencies = [ "arrayref", "arrayvec 0.5.2", - "curve25519-dalek 2.1.2", + "curve25519-dalek 2.1.3", "getrandom 0.1.16", "merlin", "rand 0.7.3", @@ -9015,7 +9227,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" dependencies = [ "der", - "generic-array 0.14.4", + "generic-array 0.14.6", "subtle", "zeroize", ] @@ -9049,9 +9261,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.3.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b239a3d5db51252f6f48f42172c65317f37202f4a21021bf5f9d40a408f4592c" +checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" dependencies = [ "bitflags", "core-foundation", @@ -9062,9 +9274,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.3.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e4effb91b4b8b6fb7732e670b6cee160278ff8e6bf485c7805d9e319d76e284" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" dependencies = [ "core-foundation-sys", "libc", @@ -9090,9 +9302,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.4" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012" +checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711" dependencies = [ "serde", ] @@ -9105,27 +9317,27 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.136" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553" dependencies = [ "serde_derive", ] [[package]] name = "serde_bytes" -version = "0.11.5" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9" +checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b" dependencies = [ "serde", ] [[package]] name = "serde_cbor" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e18acfa2f90e8b735b2836ab8d538de304cbb6729a7360729ea5a895d15a622" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" dependencies = [ "half", "serde", @@ -9133,9 +9345,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.136" +version = "1.0.143" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391" dependencies = [ "proc-macro2", "quote", @@ -9144,11 +9356,11 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.79" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" +checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7" dependencies = [ - "itoa 1.0.1", + "itoa 1.0.3", "ryu", "serde", ] @@ -9163,30 +9375,40 @@ dependencies = [ ] [[package]] -name = "sha-1" -version = "0.8.2" +name = "serde_repr" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" dependencies = [ - "block-buffer 0.7.3", - "digest 0.8.1", - "fake-simd", - "opaque-debug 0.2.3", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "sha-1" -version = "0.9.4" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfebf75d25bd900fd1e7d11501efab59bc846dbc76196839663e6637bba9f25f" +checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" dependencies = [ "block-buffer 0.9.0", "cfg-if 1.0.0", - "cpuid-bool", + "cpufeatures", "digest 0.9.0", "opaque-debug 0.3.0", ] +[[package]] +name = "sha-1" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.10.3", +] + [[package]] name = "sha2" version = "0.8.2" @@ -9201,13 +9423,13 @@ dependencies = [ [[package]] name = "sha2" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", "cfg-if 1.0.0", - "cpufeatures 0.2.1", + "cpufeatures", "digest 0.9.0", "opaque-debug 0.3.0", ] @@ -9219,7 +9441,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" dependencies = [ "cfg-if 1.0.0", - "cpufeatures 0.2.1", + "cpufeatures", "digest 0.10.3", ] @@ -9237,9 +9459,9 @@ dependencies = [ [[package]] name = "sha3" -version = "0.10.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f935e31cf406e8c0e96c2815a5516181b7004ae8c5f296293221e9b1e356bd" +checksum = "0a31480366ec990f395a61b7c08122d99bd40544fdb5abcfc1b06bb29994312c" dependencies = [ "digest 0.10.3", "keccak", @@ -9247,24 +9469,24 @@ dependencies = [ [[package]] name = "sharded-slab" -version = "0.1.1" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79c719719ee05df97490f80a45acfc99e5a30ce98a1e4fb67aee422745ae14e3" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" dependencies = [ "lazy_static", ] [[package]] name = "shlex" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a568c8f2cd051a4d283bd6eb0343ac214c1b0f1ac19f93e1175b2dee38c73d" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" [[package]] name = "signal-hook" -version = "0.3.8" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef33d6d0cd06e0840fba9985aab098c147e67e05cee14d412d3345ed14ff30ac" +checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" dependencies = [ "libc", "signal-hook-registry", @@ -9272,9 +9494,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" dependencies = [ "libc", ] @@ -9286,7 +9508,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" dependencies = [ "digest 0.9.0", - "rand_core 0.6.2", + "rand_core 0.6.3", ] [[package]] @@ -9298,14 +9520,23 @@ dependencies = [ "approx", "num-complex", "num-traits", - "paste 1.0.6", + "paste 1.0.8", ] +[[package]] +name = "simple-error" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc47a29ce97772ca5c927f75bac34866b16d64e07f330c3248e2d7226623901b" + [[package]] name = "slab" -version = "0.4.2" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] [[package]] name = "slice-group-by" @@ -9315,9 +9546,9 @@ checksum = "03b634d87b960ab1a38c4fe143b508576f075e7c978bfad18217645ebfdfa2ec" [[package]] name = "smallvec" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" [[package]] name = "snap" @@ -9335,7 +9566,7 @@ dependencies = [ "blake2", "chacha20poly1305", "curve25519-dalek 4.0.0-pre.1", - "rand_core 0.6.2", + "rand_core 0.6.3", "ring", "rustc_version 0.4.0", "sha2 0.10.2", @@ -9364,8 +9595,8 @@ dependencies = [ "futures", "httparse", "log", - "rand 0.8.4", - "sha-1 0.9.4", + "rand 0.8.5", + "sha-1 0.9.8", ] [[package]] @@ -9511,7 +9742,7 @@ dependencies = [ "log", "lru", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "sp-api", "sp-consensus", "sp-database", @@ -9638,7 +9869,7 @@ dependencies = [ "num-traits", "parity-scale-codec", "parity-util-mem", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "primitive-types", "rand 0.7.3", "regex", @@ -9672,7 +9903,7 @@ dependencies = [ "byteorder", "digest 0.10.3", "sha2 0.10.2", - "sha3 0.10.0", + "sha3 0.10.2", "sp-std", "twox-hash", ] @@ -9692,7 +9923,7 @@ name = "sp-database" version = "4.0.0-dev" dependencies = [ "kvdb", - "parking_lot 0.12.0", + "parking_lot 0.12.1", ] [[package]] @@ -9755,7 +9986,7 @@ dependencies = [ "libsecp256k1", "log", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "secp256k1", "sp-core", "sp-externalities", @@ -9788,7 +10019,7 @@ dependencies = [ "futures", "merlin", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "rand 0.7.3", "rand_chacha 0.2.2", "schnorrkel", @@ -9840,10 +10071,10 @@ dependencies = [ name = "sp-npos-elections-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 3.1.18", + "clap 3.2.16", "honggfuzz", "parity-scale-codec", - "rand 0.8.4", + "rand 0.8.5", "scale-info", "sp-npos-elections", "sp-runtime", @@ -9887,7 +10118,7 @@ dependencies = [ "log", "parity-scale-codec", "parity-util-mem", - "paste 1.0.6", + "paste 1.0.8", "rand 0.7.3", "scale-info", "serde", @@ -10033,7 +10264,7 @@ dependencies = [ "log", "num-traits", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "pretty_assertions", "rand 0.7.3", "smallvec", @@ -10205,9 +10436,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "ss58-registry" -version = "1.18.0" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceb8b72a924ccfe7882d0e26144c114503760a4d1248bb5cd06c8ab2d55404cc" +checksum = "a039906277e0d8db996cd9d1ef19278c10209d994ecfc1025ced16342873a17c" dependencies = [ "Inflector", "num-format", @@ -10240,7 +10471,7 @@ dependencies = [ "lazy_static", "nalgebra", "num-traits", - "rand 0.8.4", + "rand 0.8.5", ] [[package]] @@ -10260,9 +10491,9 @@ dependencies = [ [[package]] name = "strum_macros" -version = "0.24.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4faebde00e8ff94316c01800f9054fd2ba77d30d9e922541913051d1d978918b" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ "heck", "proc-macro2", @@ -10275,7 +10506,7 @@ dependencies = [ name = "subkey" version = "2.0.2" dependencies = [ - "clap 3.1.18", + "clap 3.2.16", "sc-cli", ] @@ -10288,7 +10519,7 @@ dependencies = [ "hmac 0.11.0", "pbkdf2 0.8.0", "schnorrkel", - "sha2 0.9.8", + "sha2 0.9.9", "zeroize", ] @@ -10303,7 +10534,7 @@ dependencies = [ name = "substrate-frame-cli" version = "4.0.0-dev" dependencies = [ - "clap 3.1.18", + "clap 3.2.16", "frame-support", "frame-system", "sc-cli", @@ -10482,7 +10713,7 @@ version = "2.0.0" dependencies = [ "futures", "parity-scale-codec", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "sc-transaction-pool", "sc-transaction-pool-api", "sp-blockchain", @@ -10539,15 +10770,24 @@ dependencies = [ [[package]] name = "subtle" -version = "2.4.0" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "subtle-encoding" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2" +checksum = "7dcb1ed7b8330c5eed5441052651dd7a12c75e2ed88f2ec024ae1fa3a5e59945" +dependencies = [ + "zeroize", +] [[package]] name = "syn" -version = "1.0.98" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" dependencies = [ "proc-macro2", "quote", @@ -10556,9 +10796,9 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.12.4" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2", "quote", @@ -10595,9 +10835,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "target-lexicon" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fa7e55043acb85fca6b3c01485a2eeb6b69c5d21002e273c79e465f43b7ac1" +checksum = "c02424087780c9b71cc96799eaeddff35af2bc513278cda5c99fc1f5d026d3c1" [[package]] name = "tempfile" @@ -10613,15 +10853,97 @@ dependencies = [ "winapi", ] +[[package]] +name = "tendermint" +version = "0.23.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "467f82178deeebcd357e1273a0c0b77b9a8a0313ef7c07074baebe99d87851f4" +dependencies = [ + "async-trait", + "bytes", + "ed25519", + "ed25519-dalek", + "flex-error", + "futures", + "num-traits", + "once_cell", + "prost 0.11.0", + "prost-types 0.11.1", + "serde", + "serde_bytes", + "serde_json", + "serde_repr", + "sha2 0.9.9", + "signature", + "subtle", + "subtle-encoding", + "tendermint-proto", + "time 0.3.11", + "zeroize", +] + +[[package]] +name = "tendermint-light-client-verifier" +version = "0.23.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ecb58905bc27f977d28d281594c71a8905da1d8cd88f1db31703f1e9e5ad" +dependencies = [ + "derive_more", + "flex-error", + "serde", + "tendermint", + "time 0.3.11", +] + +[[package]] +name = "tendermint-proto" +version = "0.23.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ce80bf536476db81ecc9ebab834dc329c9c1509a694f211a73858814bfe023" +dependencies = [ + "bytes", + "flex-error", + "num-derive", + "num-traits", + "prost 0.11.0", + "prost-types 0.11.1", + "serde", + "serde_bytes", + "subtle-encoding", + "time 0.3.11", +] + +[[package]] +name = "tendermint-testgen" +version = "0.23.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5966190343ff29a6529052b2b841448d90b60831aaaf529a6153b608a2d1370b" +dependencies = [ + "ed25519-dalek", + "gumdrop", + "serde", + "serde_json", + "simple-error", + "tempfile", + "tendermint", + "time 0.3.11", +] + [[package]] name = "termcolor" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" dependencies = [ "winapi-util", ] +[[package]] +name = "termtree" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" + [[package]] name = "textwrap" version = "0.11.0" @@ -10639,18 +10961,18 @@ checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" dependencies = [ "proc-macro2", "quote", @@ -10683,9 +11005,9 @@ dependencies = [ [[package]] name = "tikv-jemalloc-sys" -version = "0.4.2+5.2.1-patched.2" +version = "0.4.3+5.2.1-patched.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5844e429d797c62945a566f8da4e24c7fe3fbd5d6617fd8bf7a0b7dc1ee0f22e" +checksum = "a1792ccb507d955b46af42c123ea8863668fae24d03721e40cad6a41773dbb49" dependencies = [ "cc", "fs_extra", @@ -10703,6 +11025,23 @@ dependencies = [ "winapi", ] +[[package]] +name = "time" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" +dependencies = [ + "libc", + "num_threads", + "time-macros", +] + +[[package]] +name = "time-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" + [[package]] name = "tiny-bip39" version = "0.8.2" @@ -10715,7 +11054,7 @@ dependencies = [ "pbkdf2 0.4.0", "rand 0.7.3", "rustc-hash", - "sha2 0.9.8", + "sha2 0.9.9", "thiserror", "unicode-normalization", "wasm-bindgen", @@ -10743,9 +11082,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.1.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317cca572a0e89c3ce0ca1f1bdc9369547fe318a683418e42ac8f59d14701023" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] @@ -10758,18 +11097,19 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.17.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" +checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" dependencies = [ + "autocfg", "bytes", "libc", "memchr", "mio", "num_cpus", "once_cell", - "parking_lot 0.12.0", - "pin-project-lite 0.2.6", + "parking_lot 0.12.1", + "pin-project-lite 0.2.9", "signal-hook-registry", "socket2", "tokio-macros", @@ -10778,9 +11118,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" dependencies = [ "proc-macro2", "quote", @@ -10789,9 +11129,9 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.23.2" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a27d5f2b839802bd8267fa19b0530f5a08b9c08cd417976be2a65d130fe1c11b" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ "rustls", "tokio", @@ -10800,12 +11140,12 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.7" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b2f3f698253f03119ac0102beaa64f67a67e08074d03a22d18784104543727f" +checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9" dependencies = [ "futures-core", - "pin-project-lite 0.2.6", + "pin-project-lite 0.2.9", "tokio", ] @@ -10824,43 +11164,43 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0edfdeb067411dba2044da6d1cb2df793dd35add7888d73c16e3381ded401764" +checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" dependencies = [ "bytes", "futures-core", "futures-io", "futures-sink", - "pin-project-lite 0.2.6", + "pin-project-lite 0.2.9", "tokio", "tracing", ] [[package]] name = "toml" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" dependencies = [ "serde", ] [[package]] name = "tower-service" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.35" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" +checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" dependencies = [ "cfg-if 1.0.0", "log", - "pin-project-lite 0.2.6", + "pin-project-lite 0.2.9", "tracing-attributes", "tracing-core", ] @@ -10878,9 +11218,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" +checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" dependencies = [ "once_cell", "valuable", @@ -10909,9 +11249,9 @@ dependencies = [ [[package]] name = "tracing-serde" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb65ea441fbb84f9f6748fd496cf7f63ec9af5bca94dd86456978d055e8eb28b" +checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" dependencies = [ "serde", "tracing-core", @@ -10940,12 +11280,6 @@ dependencies = [ "tracing-serde", ] -[[package]] -name = "treeline" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41" - [[package]] name = "trie-bench" version = "0.30.0" @@ -10969,7 +11303,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d32d034c0d3db64b43c31de38e945f15b40cd4ca6d2dcfc26d4798ce8de4ab83" dependencies = [ "hash-db", - "hashbrown 0.12.0", + "hashbrown 0.12.3", "log", "rustc-hex", "smallvec", @@ -11011,7 +11345,7 @@ dependencies = [ "ipnet", "lazy_static", "log", - "rand 0.8.4", + "rand 0.8.5", "smallvec", "thiserror", "tinyvec", @@ -11030,7 +11364,7 @@ dependencies = [ "lazy_static", "log", "lru-cache", - "parking_lot 0.12.0", + "parking_lot 0.12.1", "resolv-conf", "smallvec", "thiserror", @@ -11047,7 +11381,7 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" name = "try-runtime-cli" version = "0.10.0-dev" dependencies = [ - "clap 3.1.18", + "clap 3.2.16", "jsonrpsee", "log", "parity-scale-codec", @@ -11069,9 +11403,9 @@ dependencies = [ [[package]] name = "trybuild" -version = "1.0.60" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0da18123d1316f5a65fc9b94e30a0fcf58afb1daff1b8e18f41dc30f5bfc38c8" +checksum = "e7f408301c7480f9e6294eb779cfc907f54bd901a9660ef24d7f233ed5376485" dependencies = [ "dissimilar", "glob", @@ -11097,7 +11431,7 @@ checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ "cfg-if 1.0.0", "digest 0.10.3", - "rand 0.8.4", + "rand 0.8.5", "static_assertions", ] @@ -11109,15 +11443,15 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "ucd-trie" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +checksum = "89570599c4fe5585de2b388aab47e99f7fa4e9238a1399f707a02e356058141c" [[package]] name = "uint" -version = "0.9.0" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e11fe9a9348741cf134085ad57c249508345fe16411b3d7fb4ff2da2f1d6382e" +checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" dependencies = [ "byteorder", "crunchy", @@ -11136,33 +11470,30 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.4" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" -dependencies = [ - "matches", -] +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" +checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" [[package]] name = "unicode-normalization" -version = "0.1.17" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef" +checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" dependencies = [ "tinyvec", ] [[package]] name = "unicode-width" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" [[package]] name = "unicode-xid" @@ -11172,11 +11503,11 @@ checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" [[package]] name = "universal-hash" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8326b2c654932e3e4f9196e69d08fdf7cfd718e1dc6f66b347e6024a0c961402" +checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" dependencies = [ - "generic-array 0.14.4", + "generic-array 0.14.6", "subtle", ] @@ -11200,9 +11531,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.2.1" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ccd964113622c8e9322cfac19eb1004a07e636c545f325da085d5cdde6f1f8b" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" dependencies = [ "form_urlencoded", "idna", @@ -11228,21 +11559,15 @@ dependencies = [ [[package]] name = "vcpkg" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb" - -[[package]] -name = "vec-arena" -version = "1.0.0" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eafc1b9b2dfc6f5529177b62cf806484db55b32dc7c9658a118e11bbeb33061d" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "void" @@ -11306,9 +11631,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.77" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e68338db6becec24d3c7977b5bf8a48be992c934b5d07177e3931f5dc9b076c" +checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -11316,13 +11641,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.77" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f34c405b4f0658583dba0c1c7c9b694f3cac32655db463b56c254a1c75269523" +checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" dependencies = [ "bumpalo", - "lazy_static", "log", + "once_cell", "proc-macro2", "quote", "syn", @@ -11331,9 +11656,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.20" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3de431a2910c86679c34283a33f66f4e4abd7e0aec27b6669060148872aadf94" +checksum = "fa76fb221a1f8acddf5b54ace85912606980ad661ac7a503b4570ffd3a624dad" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -11343,9 +11668,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.77" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d5a6580be83b19dc570a8f9c324251687ab2184e57086f71625feb57ec77c8" +checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -11353,9 +11678,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.77" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3775a030dc6f5a0afd8a84981a21cc92a781eb429acef9ecce476d0c9113e92" +checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" dependencies = [ "proc-macro2", "quote", @@ -11366,9 +11691,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.77" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" + +[[package]] +name = "wasm-encoder" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c279e376c7a8e8752a8f1eaa35b7b0bee6bb9fb0cdacfa97cc3f1f289c87e2b4" +checksum = "8905fd25fdadeb0e7e8bf43a9f46f9f972d6291ad0c7a32573b88dd13a6cfa6b" +dependencies = [ + "leb128", +] [[package]] name = "wasm-gc-api" @@ -11407,9 +11741,9 @@ dependencies = [ [[package]] name = "wasmer" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f727a39e7161f7438ddb8eafe571b67c576a8c2fb459f666d9053b5bba4afdea" +checksum = "ea8d8361c9d006ea3d7797de7bd6b1492ffd0f91a22430cfda6c1658ad57bedf" dependencies = [ "cfg-if 1.0.0", "indexmap", @@ -11419,6 +11753,7 @@ dependencies = [ "target-lexicon", "thiserror", "wasm-bindgen", + "wasmer-artifact", "wasmer-compiler", "wasmer-compiler-cranelift", "wasmer-compiler-singlepass", @@ -11432,11 +11767,24 @@ dependencies = [ "winapi", ] +[[package]] +name = "wasmer-artifact" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aaf9428c29c1d8ad2ac0e45889ba8a568a835e33fd058964e5e500f2f7ce325" +dependencies = [ + "enumset", + "loupe", + "thiserror", + "wasmer-compiler", + "wasmer-types", +] + [[package]] name = "wasmer-compiler" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e9951599222eb12bd13d4d91bcded0a880e4c22c2dfdabdf5dc7e5e803b7bf3" +checksum = "e67a6cd866aed456656db2cfea96c18baabbd33f676578482b85c51e1ee19d2c" dependencies = [ "enumset", "loupe", @@ -11447,20 +11795,19 @@ dependencies = [ "target-lexicon", "thiserror", "wasmer-types", - "wasmer-vm", - "wasmparser 0.78.2", + "wasmparser 0.83.0", ] [[package]] name = "wasmer-compiler-cranelift" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c83273bce44e668f3a2b9ccb7f1193db918b1d6806f64acc5ff71f6ece5f20" +checksum = "48be2f9f6495f08649e4f8b946a2cbbe119faf5a654aa1457f9504a99d23dae0" dependencies = [ - "cranelift-codegen 0.76.0", - "cranelift-entity 0.76.0", - "cranelift-frontend 0.76.0", - "gimli 0.25.0", + "cranelift-codegen 0.82.3", + "cranelift-entity 0.82.3", + "cranelift-frontend 0.82.3", + "gimli", "loupe", "more-asserts", "rayon", @@ -11469,18 +11816,18 @@ dependencies = [ "tracing", "wasmer-compiler", "wasmer-types", - "wasmer-vm", ] [[package]] name = "wasmer-compiler-singlepass" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5432e993840cdb8e6875ddc8c9eea64e7a129579b4706bd91b8eb474d9c4a860" +checksum = "29ca2a35204d8befa85062bc7aac259a8db8070b801b8a783770ba58231d729e" dependencies = [ "byteorder", "dynasm", "dynasmrt", + "gimli", "lazy_static", "loupe", "more-asserts", @@ -11488,14 +11835,13 @@ dependencies = [ "smallvec", "wasmer-compiler", "wasmer-types", - "wasmer-vm", ] [[package]] name = "wasmer-derive" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "458dbd9718a837e6dbc52003aef84487d79eedef5fa28c7d28b6784be98ac08e" +checksum = "00e50405cc2a2f74ff574584710a5f2c1d5c93744acce2ca0866084739284b51" dependencies = [ "proc-macro-error", "proc-macro2", @@ -11505,21 +11851,22 @@ dependencies = [ [[package]] name = "wasmer-engine" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed603a6d037ebbb14014d7f739ae996a78455a4b86c41cfa4e81c590a1253b9" +checksum = "3f98f010978c244db431b392aeab0661df7ea0822343334f8f2a920763548e45" dependencies = [ "backtrace", "enumset", "lazy_static", "loupe", - "memmap2 0.5.0", + "memmap2 0.5.5", "more-asserts", "rustc-demangle", "serde", "serde_bytes", "target-lexicon", "thiserror", + "wasmer-artifact", "wasmer-compiler", "wasmer-types", "wasmer-vm", @@ -11527,21 +11874,22 @@ dependencies = [ [[package]] name = "wasmer-engine-dylib" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccd7fdc60e252a795c849b3f78a81a134783051407e7e279c10b7019139ef8dc" +checksum = "ad0358af9c154724587731175553805648d9acb8f6657880d165e378672b7e53" dependencies = [ "cfg-if 1.0.0", "enum-iterator", "enumset", "leb128", - "libloading 0.7.0", + "libloading 0.7.3", "loupe", - "object 0.28.3", + "object 0.28.4", "rkyv", "serde", "tempfile", "tracing", + "wasmer-artifact", "wasmer-compiler", "wasmer-engine", "wasmer-object", @@ -11552,12 +11900,11 @@ dependencies = [ [[package]] name = "wasmer-engine-universal" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcff0cd2c01a8de6009fd863b14ea883132a468a24f2d2ee59dc34453d3a31b5" +checksum = "440dc3d93c9ca47865a4f4edd037ea81bf983b5796b59b3d712d844b32dbef15" dependencies = [ "cfg-if 1.0.0", - "enum-iterator", "enumset", "leb128", "loupe", @@ -11565,18 +11912,35 @@ dependencies = [ "rkyv", "wasmer-compiler", "wasmer-engine", + "wasmer-engine-universal-artifact", "wasmer-types", "wasmer-vm", "winapi", ] +[[package]] +name = "wasmer-engine-universal-artifact" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f1db3f54152657eb6e86c44b66525ff7801dad8328fe677da48dd06af9ad41" +dependencies = [ + "enum-iterator", + "enumset", + "loupe", + "rkyv", + "thiserror", + "wasmer-artifact", + "wasmer-compiler", + "wasmer-types", +] + [[package]] name = "wasmer-object" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ce18ac2877050e59580d27ee1a88f3192d7a31e77fbba0852abc7888d6e0b5" +checksum = "8d831335ff3a44ecf451303f6f891175c642488036b92ceceb24ac8623a8fa8b" dependencies = [ - "object 0.28.3", + "object 0.28.4", "thiserror", "wasmer-compiler", "wasmer-types", @@ -11584,12 +11948,15 @@ dependencies = [ [[package]] name = "wasmer-types" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "659fa3dd6c76f62630deff4ac8c7657b07f0b1e4d7e0f8243a552b9d9b448e24" +checksum = "39df01ea05dc0a9bab67e054c7cb01521e53b35a7bb90bd02eca564ed0b2667f" dependencies = [ + "backtrace", + "enum-iterator", "indexmap", "loupe", + "more-asserts", "rkyv", "serde", "thiserror", @@ -11597,23 +11964,28 @@ dependencies = [ [[package]] name = "wasmer-vm" -version = "2.2.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afdc46158517c2769f9938bc222a7d41b3bb330824196279d8aa2d667cd40641" +checksum = "30d965fa61f4dc4cdb35a54daaf7ecec3563fbb94154a6c35433f879466247dd" dependencies = [ "backtrace", "cc", "cfg-if 1.0.0", + "corosensei", "enum-iterator", "indexmap", + "lazy_static", "libc", "loupe", + "mach", "memoffset", "more-asserts", "region 3.0.0", "rkyv", + "scopeguard", "serde", "thiserror", + "wasmer-artifact", "wasmer-types", "winapi", ] @@ -11637,18 +12009,18 @@ dependencies = [ [[package]] name = "wasmi-validation" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb8e860796d8be48efef530b60eebf84e74a88bce107374fffb0da97d504b8" +checksum = "165343ecd6c018fc09ebcae280752702c9a2ef3e6f8d02f1cfcbdb53ef6d7937" dependencies = [ "parity-wasm 0.42.2", ] [[package]] name = "wasmparser" -version = "0.78.2" +version = "0.83.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52144d4c78e5cf8b055ceab8e5fa22814ce4315d6002ad32cfd914f37c12fd65" +checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" [[package]] name = "wasmparser" @@ -11661,9 +12033,9 @@ dependencies = [ [[package]] name = "wasmtime" -version = "0.38.0" +version = "0.38.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c842f9c8e190fe01300fc8d715e9368c775670fb9856247c67abffdb5236d6db" +checksum = "1f50eadf868ab6a04b7b511460233377d0bfbb92e417b2f6a98b98fef2e098f5" dependencies = [ "anyhow", "backtrace", @@ -11673,9 +12045,9 @@ dependencies = [ "lazy_static", "libc", "log", - "object 0.28.3", + "object 0.28.4", "once_cell", - "paste 1.0.6", + "paste 1.0.8", "psm", "rayon", "region 2.2.0", @@ -11692,9 +12064,9 @@ dependencies = [ [[package]] name = "wasmtime-cache" -version = "0.38.0" +version = "0.38.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cce2aa752e864a33eef2a6629edc59554e75f0bc1719431dac5e49eed516af69" +checksum = "d1df23c642e1376892f3b72f311596976979cbf8b85469680cdd3a8a063d12a2" dependencies = [ "anyhow", "base64", @@ -11704,7 +12076,7 @@ dependencies = [ "log", "rustix 0.33.7", "serde", - "sha2 0.9.8", + "sha2 0.9.9", "toml", "winapi", "zstd", @@ -11712,20 +12084,20 @@ dependencies = [ [[package]] name = "wasmtime-cranelift" -version = "0.38.0" +version = "0.38.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "922361eb8c03cea8909bc922471202f6c6bc2f0c682fac2fe473740441c86b3b" +checksum = "f264ff6b4df247d15584f2f53d009fbc90032cfdc2605b52b961bffc71b6eccd" dependencies = [ "anyhow", - "cranelift-codegen 0.85.0", - "cranelift-entity 0.85.0", - "cranelift-frontend 0.85.0", + "cranelift-codegen 0.85.3", + "cranelift-entity 0.85.3", + "cranelift-frontend 0.85.3", "cranelift-native", "cranelift-wasm", - "gimli 0.26.1", + "gimli", "log", "more-asserts", - "object 0.28.3", + "object 0.28.4", "target-lexicon", "thiserror", "wasmparser 0.85.0", @@ -11734,17 +12106,17 @@ dependencies = [ [[package]] name = "wasmtime-environ" -version = "0.38.0" +version = "0.38.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e602f1120fc40a3f016f1f69d08c86cfeff7b867bed1462901953e6871f85167" +checksum = "839d2820e4b830f4b9e7aa08d4c0acabf4a5036105d639f6dfa1c6891c73bdc6" dependencies = [ "anyhow", - "cranelift-entity 0.85.0", - "gimli 0.26.1", + "cranelift-entity 0.85.3", + "gimli", "indexmap", "log", "more-asserts", - "object 0.28.3", + "object 0.28.4", "serde", "target-lexicon", "thiserror", @@ -11754,18 +12126,18 @@ dependencies = [ [[package]] name = "wasmtime-jit" -version = "0.38.0" +version = "0.38.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49af1445759a8e797a92f27dd0983c155615648263052e0b80d69e7d223896b7" +checksum = "ef0a0bcbfa18b946d890078ba0e1bc76bcc53eccfb40806c0020ec29dcd1bd49" dependencies = [ "addr2line", "anyhow", "bincode", "cfg-if 1.0.0", "cpp_demangle", - "gimli 0.26.1", + "gimli", "log", - "object 0.28.3", + "object 0.28.4", "region 2.2.0", "rustc-demangle", "rustix 0.33.7", @@ -11780,20 +12152,20 @@ dependencies = [ [[package]] name = "wasmtime-jit-debug" -version = "0.38.0" +version = "0.38.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5dd480cc6dc0a401653e45b79796a3317f8228990d84bc2271bdaf0810071" +checksum = "4f4779d976206c458edd643d1ac622b6c37e4a0800a8b1d25dfbf245ac2f2cac" dependencies = [ "lazy_static", - "object 0.28.3", + "object 0.28.4", "rustix 0.33.7", ] [[package]] name = "wasmtime-runtime" -version = "0.38.0" +version = "0.38.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e875bcd02d1ecfc7d099dd58354d55d73467652eb2b103ff470fe3aecb7d0381" +checksum = "b7eb6ffa169eb5dcd18ac9473c817358cd57bc62c244622210566d473397954a" dependencies = [ "anyhow", "backtrace", @@ -11806,7 +12178,7 @@ dependencies = [ "memfd", "memoffset", "more-asserts", - "rand 0.8.4", + "rand 0.8.5", "region 2.2.0", "rustix 0.33.7", "thiserror", @@ -11817,11 +12189,11 @@ dependencies = [ [[package]] name = "wasmtime-types" -version = "0.38.0" +version = "0.38.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd63a19ba61ac7448add4dc1fecb8d78304812af2a52dad04b89f887791b156" +checksum = "8d932b0ac5336f7308d869703dd225610a6a3aeaa8e968c52b43eed96cefb1c2" dependencies = [ - "cranelift-entity 0.85.0", + "cranelift-entity 0.85.3", "serde", "thiserror", "wasmparser 0.85.0", @@ -11829,27 +12201,30 @@ dependencies = [ [[package]] name = "wast" -version = "38.0.0" +version = "45.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ebc29df4629f497e0893aacd40f13a4a56b85ef6eb4ab6d603f07244f1a7bf2" +checksum = "186c474c4f9bb92756b566d592a16591b4526b1a4841171caa3f31d7fe330d96" dependencies = [ "leb128", + "memchr", + "unicode-width", + "wasm-encoder", ] [[package]] name = "wat" -version = "1.0.40" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcfaeb27e2578d2c6271a45609f4a055e6d7ba3a12eff35b1fd5ba147bdf046" +checksum = "c2d4bc4724b4f02a482c8cab053dac5ef26410f264c06ce914958f9a42813556" dependencies = [ "wast", ] [[package]] name = "web-sys" -version = "0.3.54" +version = "0.3.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a84d70d1ec7d2da2d26a5bd78f4bca1b8c3254805363ce743b7a05bc30d195a" +checksum = "ed055ab27f941423197eb86b2035720b1a3ce40504df082cac2ecc6ed73335a1" dependencies = [ "js-sys", "wasm-bindgen", @@ -11867,30 +12242,31 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.22.2" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "552ceb903e957524388c4d3475725ff2c8b7960922063af6ce53c9a43da07449" +checksum = "f1c760f0d366a6c24a02ed7816e23e691f5d92291f94d15e836006fd11b04daf" dependencies = [ "webpki", ] [[package]] -name = "wepoll-sys" -version = "3.0.1" +name = "wepoll-ffi" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcb14dea929042224824779fbc82d9fab8d2e6d3cbc0ac404de8edf489e77ff" +checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" dependencies = [ "cc", ] [[package]] name = "which" -version = "4.0.2" +version = "4.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87c14ef7e1b8b8ecfc75d5eca37949410046e66f15d185c01d70824f1f8111ef" +checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" dependencies = [ + "either", + "lazy_static", "libc", - "thiserror", ] [[package]] @@ -11932,28 +12308,28 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.29.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac7fef12f4b59cd0a29339406cc9203ab44e440ddff6b3f5a41455349fa9cf3" +checksum = "45296b64204227616fdbf2614cefa4c236b98ee64dfaaaa435207ed99fe7829f" dependencies = [ - "windows_aarch64_msvc 0.29.0", - "windows_i686_gnu 0.29.0", - "windows_i686_msvc 0.29.0", - "windows_x86_64_gnu 0.29.0", - "windows_x86_64_msvc 0.29.0", + "windows_aarch64_msvc 0.34.0", + "windows_i686_gnu 0.34.0", + "windows_i686_msvc 0.34.0", + "windows_x86_64_gnu 0.34.0", + "windows_x86_64_msvc 0.34.0", ] [[package]] name = "windows-sys" -version = "0.32.0" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6" +checksum = "43dbb096663629518eb1dfa72d80243ca5a6aca764cae62a2df70af760a9be75" dependencies = [ - "windows_aarch64_msvc 0.32.0", - "windows_i686_gnu 0.32.0", - "windows_i686_msvc 0.32.0", - "windows_x86_64_gnu 0.32.0", - "windows_x86_64_msvc 0.32.0", + "windows_aarch64_msvc 0.33.0", + "windows_i686_gnu 0.33.0", + "windows_i686_msvc 0.33.0", + "windows_x86_64_gnu 0.33.0", + "windows_x86_64_msvc 0.33.0", ] [[package]] @@ -11971,15 +12347,15 @@ dependencies = [ [[package]] name = "windows_aarch64_msvc" -version = "0.29.0" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d027175d00b01e0cbeb97d6ab6ebe03b12330a35786cbaca5252b1c4bf5d9b" +checksum = "cd761fd3eb9ab8cc1ed81e56e567f02dd82c4c837e48ac3b2181b9ffc5060807" [[package]] name = "windows_aarch64_msvc" -version = "0.32.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" +checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" [[package]] name = "windows_aarch64_msvc" @@ -11989,15 +12365,15 @@ checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" [[package]] name = "windows_i686_gnu" -version = "0.29.0" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8793f59f7b8e8b01eda1a652b2697d87b93097198ae85f823b969ca5b89bba58" +checksum = "cab0cf703a96bab2dc0c02c0fa748491294bf9b7feb27e1f4f96340f208ada0e" [[package]] name = "windows_i686_gnu" -version = "0.32.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" +checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" [[package]] name = "windows_i686_gnu" @@ -12007,15 +12383,15 @@ checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" [[package]] name = "windows_i686_msvc" -version = "0.29.0" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8602f6c418b67024be2996c512f5f995de3ba417f4c75af68401ab8756796ae4" +checksum = "8cfdbe89cc9ad7ce618ba34abc34bbb6c36d99e96cae2245b7943cd75ee773d0" [[package]] name = "windows_i686_msvc" -version = "0.32.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" +checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" [[package]] name = "windows_i686_msvc" @@ -12025,15 +12401,15 @@ checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" [[package]] name = "windows_x86_64_gnu" -version = "0.29.0" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d615f419543e0bd7d2b3323af0d86ff19cbc4f816e6453f36a2c2ce889c354" +checksum = "b4dd9b0c0e9ece7bb22e84d70d01b71c6d6248b81a3c60d11869451b4cb24784" [[package]] name = "windows_x86_64_gnu" -version = "0.32.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" +checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" [[package]] name = "windows_x86_64_gnu" @@ -12043,15 +12419,15 @@ checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" [[package]] name = "windows_x86_64_msvc" -version = "0.29.0" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d95421d9ed3672c280884da53201a5c46b7b2765ca6faf34b0d71cf34a3561" +checksum = "ff1e4aa646495048ec7f3ffddc411e1d829c026a2ec62b39da15c1055e406eaa" [[package]] name = "windows_x86_64_msvc" -version = "0.32.0" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" +checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" [[package]] name = "windows_x86_64_msvc" @@ -12079,34 +12455,34 @@ dependencies = [ [[package]] name = "x25519-dalek" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc614d95359fd7afc321b66d2107ede58b246b844cf5d8a0adcca413e439f088" +checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f" dependencies = [ - "curve25519-dalek 3.0.2", + "curve25519-dalek 3.2.0", "rand_core 0.5.1", "zeroize", ] [[package]] name = "yamux" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0608f53c1dc0bad505d03a34bbd49fbf2ad7b51eb036123e896365532745a1" +checksum = "e5d9ba232399af1783a58d8eb26f6b5006fbefe2dc9ef36bd283324792d03ea5" dependencies = [ "futures", "log", "nohash-hasher", - "parking_lot 0.12.0", - "rand 0.8.4", + "parking_lot 0.12.1", + "rand 0.8.5", "static_assertions", ] [[package]] name = "zeroize" -version = "1.4.3" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" dependencies = [ "zeroize_derive", ] diff --git a/Cargo.toml b/Cargo.toml index 1f22343c002a8..59237be64922d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -153,6 +153,7 @@ members = [ "frame/utility", "frame/vesting", "frame/whitelist", + "frame/ibc", "primitives/api", "primitives/api/proc-macro", "primitives/api/test", diff --git a/frame/ibc/Cargo.lock b/frame/ibc/Cargo.lock new file mode 100644 index 0000000000000..e2df74bf1d4f1 --- /dev/null +++ b/frame/ibc/Cargo.lock @@ -0,0 +1,3462 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[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 = "addr2line" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom 0.2.7", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91f1f46651137be86f3a2b9a8359f9ab421d04d941c62b5982e1ca21113adf9" + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + +[[package]] +name = "async-trait" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" +dependencies = [ + "addr2line", + "cc", + "cfg-if 1.0.0", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base58" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "beefy-light-client" +version = "0.1.0" +source = "git+https://github.com/octopus-network/beefy-light-client.git?branch=main#113215ed340d3cc3f61dd9f4e8b8a6138f5ab736" +dependencies = [ + "beefy-merkle-tree", + "blake2-rfc", + "borsh", + "ckb-merkle-mountain-range", + "hex", + "libsecp256k1", + "parity-scale-codec", + "scale-info", + "serde", +] + +[[package]] +name = "beefy-merkle-tree" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "tiny-keccak", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake2" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388" +dependencies = [ + "digest 0.10.3", +] + +[[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 0.1.5", + "byte-tools", + "byteorder", + "generic-array 0.12.4", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "block-padding 0.2.1", + "generic-array 0.14.6", +] + +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array 0.14.6", +] + +[[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 = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + +[[package]] +name = "borsh" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15bf3650200d8bffa99015595e10f1fbd17de07abbc25bb067da79e769939bfa" +dependencies = [ + "borsh-derive", + "hashbrown 0.11.2", +] + +[[package]] +name = "borsh-derive" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6441c552f230375d18e3cc377677914d2ca2b0d36e52129fe15450a2dce46775" +dependencies = [ + "borsh-derive-internal", + "borsh-schema-derive-internal", + "proc-macro-crate 0.1.5", + "proc-macro2", + "syn", +] + +[[package]] +name = "borsh-derive-internal" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "borsh-schema-derive-internal" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "build-helper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdce191bf3fa4995ce948c8c83b4640a1745457a149e73c6db75b4ffe36aad5f" +dependencies = [ + "semver 0.6.0", +] + +[[package]] +name = "bumpalo" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" + +[[package]] +name = "byte-slice-cast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87c5fdd0166095e1d463fc6cc01aa8ce547ad77a4e84d42eb6762b084e28067e" + +[[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.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" + +[[package]] +name = "camino" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "869119e97797867fd90f5e22af7d0bd274bd4635ebb9eb68c04f3f513ae6c412" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" +dependencies = [ + "camino", + "cargo-platform", + "semver 1.0.13", + "serde", + "serde_json", +] + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +dependencies = [ + "jobserver", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time 0.1.44", + "winapi", +] + +[[package]] +name = "ckb-merkle-mountain-range" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "530710c310c455ced794feaa13f284c4c5d40ae5d82875392a83da7c5f84a8db" +dependencies = [ + "cfg-if 0.1.10", +] + +[[package]] +name = "constant_time_eq" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" + +[[package]] +name = "cpufeatures" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array 0.14.6", + "typenum", +] + +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array 0.14.6", + "subtle", +] + +[[package]] +name = "crypto-mac" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" +dependencies = [ + "generic-array 0.14.6", + "subtle", +] + +[[package]] +name = "curve25519-dalek" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b85542f99a2dfa2a1b8e192662741c9859a846b296bef1c92ef9b58b5a216" +dependencies = [ + "byteorder", + "digest 0.8.1", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "subtle", + "zeroize", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +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 0.12.4", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array 0.14.6", +] + +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer 0.10.2", + "crypto-common", + "subtle", +] + +[[package]] +name = "downcast-rs" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" + +[[package]] +name = "dyn-clonable" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e9232f0e607a262ceb9bd5141a3dfb3e4db6994b31989bbfd845878cba59fd4" +dependencies = [ + "dyn-clonable-impl", + "dyn-clone", +] + +[[package]] +name = "dyn-clonable-impl" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "558e40ea573c374cf53507fd240b7ee2f5477df7cfebdb97323ec61c719399c5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dyn-clone" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2" + +[[package]] +name = "ed25519" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" +dependencies = [ + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" +dependencies = [ + "curve25519-dalek 3.2.0", + "ed25519", + "rand 0.7.3", + "serde", + "sha2 0.9.9", + "zeroize", +] + +[[package]] +name = "either" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" + +[[package]] +name = "environmental" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b91989ae21441195d7d9b9993a2f9295c7e1a8c96255d8b729accddc124797" + +[[package]] +name = "eyre" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + +[[package]] +name = "fixed-hash" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" +dependencies = [ + "byteorder", + "rand 0.8.5", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "flex-error" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c606d892c9de11507fa0dcffc116434f94e105d0bbdc4e405b61519464c49d7b" +dependencies = [ + "eyre", + "paste", +] + +[[package]] +name = "frame-benchmarking" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "frame-support", + "frame-system", + "linregress", + "log", + "parity-scale-codec", + "paste", + "scale-info", + "serde", + "sp-api", + "sp-application-crypto", + "sp-io", + "sp-runtime", + "sp-runtime-interface", + "sp-std 4.0.0", + "sp-storage", +] + +[[package]] +name = "frame-metadata" +version = "15.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df6bb8542ef006ef0de09a5c4420787d79823c0ed7924225822362fd2bf2ff2d" +dependencies = [ + "cfg-if 1.0.0", + "parity-scale-codec", + "scale-info", + "serde", +] + +[[package]] +name = "frame-support" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "bitflags", + "frame-metadata", + "frame-support-procedural", + "impl-trait-for-tuples", + "log", + "once_cell", + "parity-scale-codec", + "paste", + "scale-info", + "serde", + "smallvec", + "sp-arithmetic", + "sp-core", + "sp-core-hashing-proc-macro", + "sp-inherents", + "sp-io", + "sp-runtime", + "sp-staking", + "sp-state-machine", + "sp-std 4.0.0", + "sp-tracing", + "tt-call", +] + +[[package]] +name = "frame-support-procedural" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "Inflector", + "frame-support-procedural-tools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "frame-support-procedural-tools" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "frame-support-procedural-tools-derive", + "proc-macro-crate 1.2.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "frame-support-procedural-tools-derive" +version = "3.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "frame-system" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "frame-support", + "log", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std 4.0.0", + "sp-version", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" + +[[package]] +name = "futures-executor" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", + "num_cpus", +] + +[[package]] +name = "futures-io" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" + +[[package]] +name = "futures-macro" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "gimli" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" + +[[package]] +name = "gumdrop" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bc700f989d2f6f0248546222d9b4258f5b02a171a431f8285a81c08142629e3" +dependencies = [ + "gumdrop_derive", +] + +[[package]] +name = "gumdrop_derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "729f9bd3449d77e7831a18abfb7ba2f99ee813dfd15b8c2167c9a54ba20aa99d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" +dependencies = [ + "crypto-mac 0.8.0", + "digest 0.9.0", +] + +[[package]] +name = "hmac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" +dependencies = [ + "crypto-mac 0.11.1", + "digest 0.9.0", +] + +[[package]] +name = "hmac-drbg" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" +dependencies = [ + "digest 0.9.0", + "generic-array 0.14.6", + "hmac 0.8.1", +] + +[[package]] +name = "ibc" +version = "0.16.0" +source = "git+https://github.com/octopus-network/ibc-rs.git?branch=feature/ics20-davirian-base-0.9.18#36808084c389b0a0bba008723018c5990500f098" +dependencies = [ + "beefy-light-client", + "beefy-merkle-tree", + "blake2-rfc", + "bytes", + "derive_more", + "flex-error", + "frame-support", + "frame-system", + "hash-db", + "ibc-proto", + "ics23", + "num-traits", + "parity-scale-codec", + "primitive-types", + "prost 0.10.4", + "prost-types 0.10.1", + "safe-regex", + "serde", + "serde_derive", + "serde_json", + "sha2 0.10.2", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std 4.0.0", + "sp-trie", + "subtle-encoding", + "tendermint", + "tendermint-light-client-verifier", + "tendermint-proto", + "tendermint-testgen", + "time 0.3.11", + "tracing", + "uint", +] + +[[package]] +name = "ibc-proto" +version = "0.19.0" +source = "git+https://github.com/octopus-network/ibc-rs.git?branch=feature/ics20-davirian-base-0.9.18#36808084c389b0a0bba008723018c5990500f098" +dependencies = [ + "base64", + "bytes", + "prost 0.10.4", + "prost-types 0.10.1", + "serde", + "tendermint-proto", +] + +[[package]] +name = "ics23" +version = "0.8.0-alpha" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a435f2471c1b2ce14771da465d47321c5905fac866d0effa9e0a3eb5d94fcf" +dependencies = [ + "anyhow", + "bytes", + "hex", + "prost 0.10.4", + "ripemd160", + "sha2 0.9.9", + "sha3 0.9.1", + "sp-std 3.0.0", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-serde" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "integer-sqrt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" +dependencies = [ + "num-traits", +] + +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "itoa" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" + +[[package]] +name = "jobserver" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" + +[[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.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "505e71a4706fa491e9b1b55f51b95d4037d0821ee40131190475f692b35b009b" + +[[package]] +name = "libm" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da83a57f3f5ba3680950aa3cbc806fc297bc0b289d42e8942ed528ace71b8145" + +[[package]] +name = "libsecp256k1" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" +dependencies = [ + "arrayref", + "base64", + "digest 0.9.0", + "hmac-drbg", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand 0.8.5", + "serde", + "sha2 0.9.9", + "typenum", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "linregress" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6c601a85f5ecd1aba625247bca0031585fb1c446461b142878a16f8245ddeb8" +dependencies = [ + "nalgebra", + "statrs", +] + +[[package]] +name = "lock_api" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if 1.0.0", +] + +[[package]] +name = "matchers" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matrixmultiply" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add85d4dd35074e6fedc608f8c8f513a3548619a9024b751949ef0e8e45a4d84" +dependencies = [ + "rawpointer", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memory-db" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6566c70c1016f525ced45d7b7f97730a2bafb037c788211d0c186ef5b2189f0a" +dependencies = [ + "hash-db", + "hashbrown 0.12.3", + "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 = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e261cf0f8b3c42ded9f7d2bb59dea03aa52bc8a1cbc7482f9fc3fd1229d3b42" +dependencies = [ + "byteorder", + "keccak", + "rand_core 0.5.1", + "zeroize", +] + +[[package]] +name = "miniz_oxide" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +dependencies = [ + "adler", +] + +[[package]] +name = "nalgebra" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "462fffe4002f4f2e1f6a9dcf12cc1a6fc0e15989014efc02a941d3e0f5dc2120" +dependencies = [ + "approx", + "matrixmultiply", + "nalgebra-macros", + "num-complex", + "num-rational 0.4.1", + "num-traits", + "rand 0.8.5", + "rand_distr", + "simba", + "typenum", +] + +[[package]] +name = "nalgebra-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01fcc0b8149b4632adc89ac3b7b31a12fb6099a0317a4eb2ebff574ef7de7218" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "num-format" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bafe4179722c2894288ee77a9f044f02811c86af699344c498b0840c698a2465" +dependencies = [ + "arrayvec 0.4.12", + "itoa 0.4.8", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + +[[package]] +name = "object" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" + +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "pallet-ibc" +version = "3.0.0-pre.0" +dependencies = [ + "beefy-light-client", + "chrono", + "flex-error", + "frame-benchmarking", + "frame-support", + "frame-system", + "hex", + "ibc", + "ibc-proto", + "log", + "parity-scale-codec", + "prost 0.9.0", + "prost-types 0.9.0", + "scale-info", + "serde", + "serde_json", + "sha2 0.10.2", + "sp-core", + "sp-io", + "sp-keyring", + "sp-runtime", + "sp-std 4.0.0", + "sp-tracing", + "substrate-wasm-builder", + "tendermint-proto", + "time 0.3.11", +] + +[[package]] +name = "parity-scale-codec" +version = "3.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9182e4a71cae089267ab03e67c99368db7cd877baf50f931e5d6d4b71e195ac0" +dependencies = [ + "arrayvec 0.7.2", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" +dependencies = [ + "proc-macro-crate 1.2.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "parity-util-mem" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c32561d248d352148124f036cac253a644685a21dc9fea383eb4907d7bd35a8f" +dependencies = [ + "cfg-if 1.0.0", + "hashbrown 0.12.3", + "impl-trait-for-tuples", + "parity-util-mem-derive", + "parking_lot", + "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.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16ad52817c4d343339b3bc2e26861bd21478eda0b7509acf83505727000512ac" +dependencies = [ + "byteorder", +] + +[[package]] +name = "parity-wasm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "paste" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9423e2b32f7a043629287a536f21951e8c6a82482d0acb1eeebfc90bc2225b22" + +[[package]] +name = "pbkdf2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216eaa586a190f0a738f2f918511eecfa90f13295abec0e457cdebcceda80cbd" +dependencies = [ + "crypto-mac 0.8.0", +] + +[[package]] +name = "pbkdf2" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d95f5254224e617595d2cc3cc73ff0a5eaf2637519e25f03388154e9378b6ffa" +dependencies = [ + "crypto-mac 0.11.1", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "primitive-types" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28720988bff275df1f51b171e1b2a18c30d194c4d2b61defdacecd625a5d94a" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-serde", + "scale-info", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro-crate" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d50bfb8c23f23915855a00d98b5a35ef2e0b871bb52937bacadb798fbb66c8" +dependencies = [ + "once_cell", + "thiserror", + "toml", +] + +[[package]] +name = "proc-macro2" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" +dependencies = [ + "bytes", + "prost-derive 0.9.0", +] + +[[package]] +name = "prost" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71adf41db68aa0daaefc69bb30bcd68ded9b9abaad5d1fbb6304c4fb390e083e" +dependencies = [ + "bytes", + "prost-derive 0.10.1", +] + +[[package]] +name = "prost-derive" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-derive" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b670f45da57fb8542ebdbb6105a925fe571b67f9e7ed9f47a06a84e72b4e7cc" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" +dependencies = [ + "bytes", + "prost 0.9.0", +] + +[[package]] +name = "prost-types" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d0a014229361011dc8e69c8a1ec6c2e8d0f2af7c91e3ea3f5b2170298461e68" +dependencies = [ + "bytes", + "prost 0.10.4", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.3", +] + +[[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_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.3", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom 0.2.7", +] + +[[package]] +name = "rand_distr" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[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_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ref-cast" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed13bcd201494ab44900a96490291651d200730904221832b9547d24a87d332b" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5234cd6063258a5e32903b53b1b6ac043a0541c8adc1f610f67b0326c7a578fa" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "regex" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "ripemd160" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2eca4ecc81b7f313189bf73ce724400a07da2a6dac19588b03c8bd76a2dcc251" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "opaque-debug 0.3.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + +[[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 = "rustversion" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" + +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[package]] +name = "safe-proc-macro2" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "814c536dcd27acf03296c618dab7ad62d28e70abd7ba41d3f34a2ce707a2c666" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "safe-quote" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77e530f7831f3feafcd5f1aae406ac205dd998436b4007c8e80f03eca78a88f7" +dependencies = [ + "safe-proc-macro2", +] + +[[package]] +name = "safe-regex" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15289bf322e0673d52756a18194167f2378ec1a15fe884af6e2d2cb934822b0" +dependencies = [ + "safe-regex-macro", +] + +[[package]] +name = "safe-regex-compiler" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba76fae590a2aa665279deb1f57b5098cbace01a0c5e60e262fcf55f7c51542" +dependencies = [ + "safe-proc-macro2", + "safe-quote", +] + +[[package]] +name = "safe-regex-macro" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c2e96b5c03f158d1b16ba79af515137795f4ad4e8de3f790518aae91f1d127" +dependencies = [ + "safe-proc-macro2", + "safe-regex-compiler", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scale-info" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c46be926081c9f4dd5dd9b6f1d3e3229f2360bc6502dd8836f84a93b7c75e99a" +dependencies = [ + "bitvec", + "cfg-if 1.0.0", + "derive_more", + "parity-scale-codec", + "scale-info-derive", + "serde", +] + +[[package]] +name = "scale-info-derive" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e334bb10a245e28e5fd755cabcafd96cfcd167c99ae63a46924ca8d8703a3c" +dependencies = [ + "proc-macro-crate 1.2.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "schnorrkel" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "021b403afe70d81eea68f6ea12f6b3c9588e5d536a94c3bf80f15e7faa267862" +dependencies = [ + "arrayref", + "arrayvec 0.5.2", + "curve25519-dalek 2.1.3", + "getrandom 0.1.16", + "merlin", + "rand 0.7.3", + "rand_core 0.5.1", + "sha2 0.8.2", + "subtle", + "zeroize", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "secp256k1" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c42e6f1735c5f00f51e43e28d6634141f2bcad10931b2609ddd74a86d751260" +dependencies = [ + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957da2573cde917463ece3570eab4a0b3f19de6f1646cde62e6fd3868f566036" +dependencies = [ + "cc", +] + +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "zeroize", +] + +[[package]] +name = "semver" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a3186ec9e65071a2095434b1f5bb24838d4e8e130f584c790f6033c79943537" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711" +dependencies = [ + "serde", +] + +[[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.142" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590c437916fb6b221e1d00df6e3294f3fccd70ca7e92541c475d6ed6ef5fee2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.142" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b5b8d809babe02f538c2cfec6f2c1ed10804c0e5a6a041a049a4f5588ccc2e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7" +dependencies = [ + "itoa 1.0.3", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sha2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" +dependencies = [ + "block-buffer 0.7.3", + "digest 0.8.1", + "fake-simd", + "opaque-debug 0.2.3", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.9.0", + "opaque-debug 0.3.0", +] + +[[package]] +name = "sha2" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.10.3", +] + +[[package]] +name = "sha3" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "keccak", + "opaque-debug 0.3.0", +] + +[[package]] +name = "sha3" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a31480366ec990f395a61b7c08122d99bd40544fdb5abcfc1b06bb29994312c" +dependencies = [ + "digest 0.10.3", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signature" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f054c6c1a6e95179d6f23ed974060dcefb2d9388bb7256900badad682c499de4" + +[[package]] +name = "simba" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e82063457853d00243beda9952e910b82593e4b07ae9f721b9278a99a0d3d5c" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", +] + +[[package]] +name = "simple-error" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc47a29ce97772ca5c927f75bac34866b16d64e07f330c3248e2d7226623901b" + +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" + +[[package]] +name = "sp-api" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "hash-db", + "log", + "parity-scale-codec", + "sp-api-proc-macro", + "sp-core", + "sp-runtime", + "sp-state-machine", + "sp-std 4.0.0", + "sp-version", + "thiserror", +] + +[[package]] +name = "sp-api-proc-macro" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "blake2", + "proc-macro-crate 1.2.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sp-application-crypto" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-std 4.0.0", +] + +[[package]] +name = "sp-arithmetic" +version = "5.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "integer-sqrt", + "num-traits", + "parity-scale-codec", + "scale-info", + "serde", + "sp-debug-derive", + "sp-std 4.0.0", + "static_assertions", +] + +[[package]] +name = "sp-core" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "base58", + "bitflags", + "blake2-rfc", + "byteorder", + "dyn-clonable", + "ed25519-dalek", + "futures", + "hash-db", + "hash256-std-hasher", + "hex", + "impl-serde", + "lazy_static", + "libsecp256k1", + "log", + "merlin", + "num-traits", + "parity-scale-codec", + "parity-util-mem", + "parking_lot", + "primitive-types", + "rand 0.7.3", + "regex", + "scale-info", + "schnorrkel", + "secp256k1", + "secrecy", + "serde", + "sp-core-hashing", + "sp-debug-derive", + "sp-externalities", + "sp-runtime-interface", + "sp-std 4.0.0", + "sp-storage", + "ss58-registry", + "substrate-bip39", + "thiserror", + "tiny-bip39", + "wasmi", + "zeroize", +] + +[[package]] +name = "sp-core-hashing" +version = "4.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "blake2", + "byteorder", + "digest 0.10.3", + "sha2 0.10.2", + "sha3 0.10.2", + "sp-std 4.0.0", + "twox-hash", +] + +[[package]] +name = "sp-core-hashing-proc-macro" +version = "5.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "proc-macro2", + "quote", + "sp-core-hashing", + "syn", +] + +[[package]] +name = "sp-debug-derive" +version = "4.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sp-externalities" +version = "0.12.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "environmental", + "parity-scale-codec", + "sp-std 4.0.0", + "sp-storage", +] + +[[package]] +name = "sp-inherents" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "async-trait", + "impl-trait-for-tuples", + "parity-scale-codec", + "sp-core", + "sp-runtime", + "sp-std 4.0.0", + "thiserror", +] + +[[package]] +name = "sp-io" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "futures", + "hash-db", + "libsecp256k1", + "log", + "parity-scale-codec", + "parking_lot", + "secp256k1", + "sp-core", + "sp-externalities", + "sp-keystore", + "sp-runtime-interface", + "sp-state-machine", + "sp-std 4.0.0", + "sp-tracing", + "sp-trie", + "sp-wasm-interface", + "tracing", + "tracing-core", +] + +[[package]] +name = "sp-keyring" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "lazy_static", + "sp-core", + "sp-runtime", + "strum", +] + +[[package]] +name = "sp-keystore" +version = "0.12.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "async-trait", + "futures", + "merlin", + "parity-scale-codec", + "parking_lot", + "schnorrkel", + "sp-core", + "sp-externalities", + "thiserror", +] + +[[package]] +name = "sp-maybe-compressed-blob" +version = "4.1.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "thiserror", + "zstd", +] + +[[package]] +name = "sp-panic-handler" +version = "4.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "backtrace", + "lazy_static", + "regex", +] + +[[package]] +name = "sp-runtime" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "either", + "hash256-std-hasher", + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "parity-util-mem", + "paste", + "rand 0.7.3", + "scale-info", + "serde", + "sp-application-crypto", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-std 4.0.0", +] + +[[package]] +name = "sp-runtime-interface" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "impl-trait-for-tuples", + "parity-scale-codec", + "primitive-types", + "sp-externalities", + "sp-runtime-interface-proc-macro", + "sp-std 4.0.0", + "sp-storage", + "sp-tracing", + "sp-wasm-interface", + "static_assertions", +] + +[[package]] +name = "sp-runtime-interface-proc-macro" +version = "5.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "Inflector", + "proc-macro-crate 1.2.0", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sp-staking" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "parity-scale-codec", + "scale-info", + "sp-runtime", + "sp-std 4.0.0", +] + +[[package]] +name = "sp-state-machine" +version = "0.12.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "hash-db", + "log", + "num-traits", + "parity-scale-codec", + "parking_lot", + "rand 0.7.3", + "smallvec", + "sp-core", + "sp-externalities", + "sp-panic-handler", + "sp-std 4.0.0", + "sp-trie", + "thiserror", + "tracing", + "trie-db", + "trie-root", +] + +[[package]] +name = "sp-std" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35391ea974fa5ee869cb094d5b437688fbf3d8127d64d1b9fed5822a1ed39b12" + +[[package]] +name = "sp-std" +version = "4.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" + +[[package]] +name = "sp-storage" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "impl-serde", + "parity-scale-codec", + "ref-cast", + "serde", + "sp-debug-derive", + "sp-std 4.0.0", +] + +[[package]] +name = "sp-tracing" +version = "5.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "parity-scale-codec", + "sp-std 4.0.0", + "tracing", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "sp-trie" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "hash-db", + "memory-db", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-std 4.0.0", + "thiserror", + "trie-db", + "trie-root", +] + +[[package]] +name = "sp-version" +version = "5.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "impl-serde", + "parity-scale-codec", + "parity-wasm 0.42.2", + "scale-info", + "serde", + "sp-core-hashing-proc-macro", + "sp-runtime", + "sp-std 4.0.0", + "sp-version-proc-macro", + "thiserror", +] + +[[package]] +name = "sp-version-proc-macro" +version = "4.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "parity-scale-codec", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sp-wasm-interface" +version = "6.0.0" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "impl-trait-for-tuples", + "log", + "parity-scale-codec", + "sp-std 4.0.0", + "wasmi", +] + +[[package]] +name = "ss58-registry" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a039906277e0d8db996cd9d1ef19278c10209d994ecfc1025ced16342873a17c" +dependencies = [ + "Inflector", + "num-format", + "proc-macro2", + "quote", + "serde", + "serde_json", + "unicode-xid", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "statrs" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05bdbb8e4e78216a85785a85d3ec3183144f98d0097b9281802c019bb07a6f05" +dependencies = [ + "approx", + "lazy_static", + "nalgebra", + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "strum" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cae14b91c7d11c9a851d3fbc80a963198998c2a64eec840477fa92d8ce9b70bb" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bb0dc7ee9c15cea6199cde9a127fa16a4c5819af85395457ad72d68edc85a38" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "substrate-bip39" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49eee6965196b32f882dd2ee85a92b1dbead41b04e53907f269de3b0dc04733c" +dependencies = [ + "hmac 0.11.0", + "pbkdf2 0.8.0", + "schnorrkel", + "sha2 0.9.9", + "zeroize", +] + +[[package]] +name = "substrate-wasm-builder" +version = "5.0.0-dev" +source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" +dependencies = [ + "ansi_term", + "build-helper", + "cargo_metadata", + "sp-maybe-compressed-blob", + "strum", + "tempfile", + "toml", + "walkdir", + "wasm-gc-api", +] + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "subtle-encoding" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcb1ed7b8330c5eed5441052651dd7a12c75e2ed88f2ec024ae1fa3a5e59945" +dependencies = [ + "zeroize", +] + +[[package]] +name = "syn" +version = "1.0.97" +source = "git+https://github.com/DaviRain-Su/syn.git?branch=branch-1.0.97#17682c67bd1c4de8662fda06a3254231dfeed86c" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if 1.0.0", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "tendermint" +version = "0.23.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ca881fa4dedd2b46334f13be7fbc8cc1549ba4be5a833fe4e73d1a1baaf7949" +dependencies = [ + "async-trait", + "bytes", + "ed25519", + "ed25519-dalek", + "flex-error", + "futures", + "num-traits", + "once_cell", + "prost 0.10.4", + "prost-types 0.10.1", + "serde", + "serde_bytes", + "serde_json", + "serde_repr", + "sha2 0.9.9", + "signature", + "subtle", + "subtle-encoding", + "tendermint-proto", + "time 0.3.11", + "zeroize", +] + +[[package]] +name = "tendermint-light-client-verifier" +version = "0.23.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ae030a759b89cca84860d497d4d4e491615d8a9243cc04c61cd89335ba9b593" +dependencies = [ + "derive_more", + "flex-error", + "serde", + "tendermint", + "time 0.3.11", +] + +[[package]] +name = "tendermint-proto" +version = "0.23.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71f925d74903f4abbdc4af0110635a307b3cb05b175fdff4a7247c14a4d0874" +dependencies = [ + "bytes", + "flex-error", + "num-derive", + "num-traits", + "prost 0.10.4", + "prost-types 0.10.1", + "serde", + "serde_bytes", + "subtle-encoding", + "time 0.3.11", +] + +[[package]] +name = "tendermint-testgen" +version = "0.23.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442ede2d01e61466e515fd7f1d0aac7c3c86b3066535479caa86a43afb5e2e17" +dependencies = [ + "ed25519-dalek", + "gumdrop", + "serde", + "serde_json", + "simple-error", + "tempfile", + "tendermint", + "time 0.3.11", +] + +[[package]] +name = "thiserror" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "time" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" +dependencies = [ + "libc", + "num_threads", + "time-macros", +] + +[[package]] +name = "time-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" + +[[package]] +name = "tiny-bip39" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc59cb9dfc85bb312c3a78fd6aa8a8582e310b0fa885d5bb877f6dcc601839d" +dependencies = [ + "anyhow", + "hmac 0.8.1", + "once_cell", + "pbkdf2 0.4.0", + "rand 0.7.3", + "rustc-hash", + "sha2 0.9.9", + "thiserror", + "unicode-normalization", + "wasm-bindgen", + "zeroize", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + +[[package]] +name = "tracing" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" +dependencies = [ + "cfg-if 1.0.0", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-serde" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +dependencies = [ + "serde", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" +dependencies = [ + "ansi_term", + "chrono", + "lazy_static", + "matchers", + "regex", + "serde", + "serde_json", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", + "tracing-serde", +] + +[[package]] +name = "trie-db" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d32d034c0d3db64b43c31de38e945f15b40cd4ca6d2dcfc26d4798ce8de4ab83" +dependencies = [ + "hash-db", + "hashbrown 0.12.3", + "log", + "rustc-hex", + "smallvec", +] + +[[package]] +name = "trie-root" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a36c5ca3911ed3c9a5416ee6c679042064b93fc637ded67e25f92e68d783891" +dependencies = [ + "hash-db", +] + +[[package]] +name = "tt-call" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e66dcbec4290c69dd03c57e76c2469ea5c7ce109c6dd4351c13055cf71ea055" + +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if 1.0.0", + "digest 0.10.3", + "rand 0.8.5", + "static_assertions", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "uint" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-ident" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" + +[[package]] +name = "unicode-normalization" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" + +[[package]] +name = "unicode-xid" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" + +[[package]] +name = "wasm-gc-api" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0c32691b6c7e6c14e7f8fd55361a9088b507aa49620fcd06c09b3a1082186b9" +dependencies = [ + "log", + "parity-wasm 0.32.0", + "rustc-demangle", +] + +[[package]] +name = "wasmi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca00c5147c319a8ec91ec1a0edbec31e566ce2c9cc93b3f9bb86a9efd0eb795d" +dependencies = [ + "downcast-rs", + "libc", + "memory_units", + "num-rational 0.2.4", + "num-traits", + "parity-wasm 0.42.2", + "wasmi-validation", +] + +[[package]] +name = "wasmi-validation" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "165343ecd6c018fc09ebcae280752702c9a2ef3e6f8d02f1cfcbdb53ef6d7937" +dependencies = [ + "parity-wasm 0.42.2", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +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-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[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 = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "wyz" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" +dependencies = [ + "tap", +] + +[[package]] +name = "zeroize" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zstd" +version = "0.9.2+zstd.1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2390ea1bf6c038c39674f22d95f0564725fc06034a47129179810b2fc58caa54" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "4.1.3+zstd.1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e99d81b99fb3c2c2c794e3fe56c305c63d5173a16a46b5850b07c935ffc7db79" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "1.6.2+zstd.1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2daf2f248d9ea44454bfcb2516534e8b8ad2fc91bf818a1885495fc42bc8ac9f" +dependencies = [ + "cc", + "libc", +] diff --git a/frame/ibc/Cargo.toml b/frame/ibc/Cargo.toml new file mode 100644 index 0000000000000..683c105de4f91 --- /dev/null +++ b/frame/ibc/Cargo.toml @@ -0,0 +1,86 @@ +[package] +name = 'pallet-ibc' +version = "3.0.0-pre.0" +authors = ['Octopus Network '] +edition = '2021' +license = "Apache-2.0" +homepage = "https://oct.network" +repository = "https://github.com/octopus-network/substrate-ibc/" +description = "An IBC implementation on Substrate." +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.2", default-features = false, features = ["derive"] } +log = { version = "0.4.0", default-features = false } +prost-types = { version = "0.11", default-features = false } +prost = { version = "0.11", default-features = false } +serde_json = { version = "1.0", default-features = false } +serde = { version = "1.0", default-features = false } +flex-error = { version = "0.4.4", default-features = false } +hex = {version = "0.4.0", default-features = false } + +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } +sp-core = { version = "6.0.0", path = "../../primitives/core" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-tracing = { version = "5.0.0", default-features = false, path = "../../primitives/tracing" } +time = { version = "0.3.11", features = ["macros","parsing"], default-features = false} + +ibc = { version = "0.18.0", default-features = false } +ibc-proto = { version = "0.20.0", default-features = false } +tendermint-proto = { version = "=0.23.9", default-features = false } +# beefy-light-client = { git = "https://github.com/octopus-network/beefy-light-client.git", branch="main", default-features = false } + +frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } + +[dev-dependencies] +hex = '0.4.0' +sha2 = '0.10.2' +serde = { version = "1.0" } +ibc = { version = "0.18.0", features = ["mocks"] } +sp-io = { version = "6.0.0", path = "../../primitives/io" } +sp-runtime = { version = "6.0.0", path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", path = "../../primitives/std" } +sp-keyring = { version = "6.0.0", path = "../../primitives/keyring" } +frame-support = { version = "4.0.0-dev", path = "../support" } +pallet-assets = { version = "4.0.0-dev", path = "../assets" } +pallet-balances = { version = "4.0.0-dev", path = "../balances" } +pallet-timestamp = { version = "4.0.0-dev", path = "../timestamp" } +pallet-babe = { version = "4.0.0-dev", path = "../babe" } +sp-version = { version = "5.0.0", path = "../../primitives/version" } +sp-core = { version = "6.0.0", path = "../../primitives/core" } +chrono = "0.4.19" + +[features] +default = ['std'] +std = [ + 'codec/std', + 'log/std', + "scale-info/std", + 'frame-benchmarking/std', + 'frame-support/std', + 'frame-system/std', + 'sp-core/std', + 'sp-runtime/std', + 'sp-std/std', + 'sp-io/std', + 'sp-tracing/std', + 'prost-types/std', + 'prost/std', + 'ibc/std', + 'ibc-proto/std', + # 'beefy-light-client/std', + 'serde_json/std', + 'serde/std', + 'flex-error/std', + 'hex/std', + 'time/std', +] +runtime-benchmarks = ["frame-benchmarking"] +try-runtime = ["frame-support/try-runtime"] \ No newline at end of file diff --git a/frame/ibc/LICENSE b/frame/ibc/LICENSE new file mode 100644 index 0000000000000..261eeb9e9f8b2 --- /dev/null +++ b/frame/ibc/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/frame/ibc/Makefile b/frame/ibc/Makefile new file mode 100644 index 0000000000000..622944ab4571e --- /dev/null +++ b/frame/ibc/Makefile @@ -0,0 +1,2 @@ +check: + cargo check --no-default-features --target=wasm32-unknown-unknown \ No newline at end of file diff --git a/frame/ibc/README.md b/frame/ibc/README.md new file mode 100644 index 0000000000000..9451f9c1bafa8 --- /dev/null +++ b/frame/ibc/README.md @@ -0,0 +1,106 @@ +# Substrate IBC Pallet (work in progress) +[![crates.io](https://img.shields.io/crates/v/pallet-ibc.svg)](https://crates.io/crates/pallet-ibc) +[![Released API docs](https://docs.rs/pallet-ibc/badge.svg)](https://docs.rs/pallet-ibc) + +This project is [funded by Interchain Foundation](https://interchain-io.medium.com/ibc-on-substrate-with-cdot-a7025e521028). + +## Purpose + +This pallet implements the standard [IBC protocol](https://github.com/cosmos/ics). + +The goal of this pallet is to allow the blockchains built on Substrate to gain the ability to interact with other chains in a trustless way via IBC protocol. + +This project is currently in an early stage and will eventually be submitted to upstream. + +The pallet implements the chain specific logic of [ICS spec](https://github.com/cosmos/ibc/tree/51f0c9e8d8ebcbe6f7f023a8b80f65a8fab705e3/spec), and is integrated with [ibc-rs](https://github.com/informalsystems/ibc-rs), which implements the generic cross-chain logic in [ICS spec](https://github.com/cosmos/ibc/tree/51f0c9e8d8ebcbe6f7f023a8b80f65a8fab705e3/spec). + +The chain specific logic of the modules in ICS spec implemented: +* ics-002-client-semantics +* ics-003-connection-semantics +* ics-004-channel-and-packet-semantics +* ics-005-port-allocation +* ics-010-grandpa-client +* ics-018-relayer-algorithms +* ics-023-vector-commitments +* ics-024-host-requirements +* ics-025-handler-interface +* ics-026-routing-module + +Here is a [demo](~~https://github.com/cdot-network/ibc-demo~~) for showing how to utilize this pallet, which initializes a series of steps for cross-chain communication, from client creation to sending packet data. + +## Design Overview +The ibc pallet is integrated with the [modules in ibc-rs](https://github.com/octopus-network/ibc-rs/tree/b98094a57620d0b3d9f8d2caced09abfc14ab00f/modules), which implements the [ibc spec](https://github.com/cosmos/ibc/tree/7046202b645c65b1a2b7f293312bca5d651a13a4/spec) and leave the chain specific logics, which are named `???Readers` and `???Keepers`, to the ibc pallet. + +List of `Readers` and `Keepers` of on-chain storage: +* [ClientReader](https://github.com/octopus-network/ibc-rs/blob/b98094a57620d0b3d9f8d2caced09abfc14ab00f/modules/src/ics02_client/context.rs?_pjax=%23js-repo-pjax-container%2C%20div%5Bitemtype%3D%22http%3A%2F%2Fschema.org%2FSoftwareSourceCode%22%5D%20main%2C%20%5Bdata-pjax-container%5D#L14) & [ClientKeeper](https://github.com/octopus-network/ibc-rs/blob/b98094a57620d0b3d9f8d2caced09abfc14ab00f/modules/src/ics02_client/context.rs?_pjax=%23js-repo-pjax-container%2C%20div%5Bitemtype%3D%22http%3A%2F%2Fschema.org%2FSoftwareSourceCode%22%5D%20main%2C%20%5Bdata-pjax-container%5D#L29) +* [ConnectionReader](https://github.com/octopus-network/ibc-rs/blob/b98094a57620d0b3d9f8d2caced09abfc14ab00f/modules/src/ics03_connection/context.rs?_pjax=%23js-repo-pjax-container%2C%20div%5Bitemtype%3D%22http%3A%2F%2Fschema.org%2FSoftwareSourceCode%22%5D%20main%2C%20%5Bdata-pjax-container%5D#L17) & [ConnectionKeeper](https://github.com/octopus-network/ibc-rs/blob/b98094a57620d0b3d9f8d2caced09abfc14ab00f/modules/src/ics03_connection/context.rs?_pjax=%23js-repo-pjax-container%2C%20div%5Bitemtype%3D%22http%3A%2F%2Fschema.org%2FSoftwareSourceCode%22%5D%20main%2C%20%5Bdata-pjax-container%5D#L67) +* [ChannelReader](https://github.com/octopus-network/ibc-rs/blob/b98094a57620d0b3d9f8d2caced09abfc14ab00f/modules/src/ics04_channel/context.rs?_pjax=%23js-repo-pjax-container%2C%20div%5Bitemtype%3D%22http%3A%2F%2Fschema.org%2FSoftwareSourceCode%22%5D%20main%2C%20%5Bdata-pjax-container%5D#L20) & [ChannelKeeper](https://github.com/octopus-network/ibc-rs/blob/b98094a57620d0b3d9f8d2caced09abfc14ab00f/modules/src/ics04_channel/context.rs?_pjax=%23js-repo-pjax-container%2C%20div%5Bitemtype%3D%22http%3A%2F%2Fschema.org%2FSoftwareSourceCode%22%5D%20main%2C%20%5Bdata-pjax-container%5D#L82) + +## Installation +Thie section describe the modification of your substrate chain needed to integrate pallet ibc. + +### `Cargo.toml` +Specify some versions of ibc relevant crate +```toml +[patch.crates-io] +tendermint = { git = "https://github.com/informalsystems/tendermint-rs", branch = "v0.23.x" } +tendermint-rpc = { git = "https://github.com/informalsystems/tendermint-rs", branch = "v0.23.x" } +tendermint-proto = { git = "https://github.com/informalsystems/tendermint-rs", branch = "v0.23.x" } +tendermint-light-client = { git = "https://github.com/informalsystems/tendermint-rs", branch = "v0.23.x" } +tendermint-light-client-verifier = { git = "https://github.com/informalsystems/tendermint-rs", branch = "v0.23.x" } +tendermint-testgen = { git = "https://github.com/informalsystems/tendermint-rs", branch = "v0.23.x" } +``` + +#### Runtime's `Cargo.toml` +To add this pallet to your runtime, include the following to your runtime's `Cargo.toml` file: + +```TOML +pallet-ibc = { git = "https://github.com/octopus-network/substrate-ibc", branch = "master", default-features = false} +``` + +and update your runtime's `std` feature to include this pallet: + +```TOML +std = [ + # --snip-- + "pallet-ibc/std", +] +``` + +### Runtime `lib.rs` +A custom structure that implements the pallet_ibc::ModuleCallbacks must be defined to dispatch messages to receiving module. +```rust +pub struct ModuleCallbacksImpl; + +impl pallet_ibc::ModuleCallbacks for ModuleCallbacksImpl {} + +impl pallet_ibc::Config for Runtime { + type Event = Event; + type ModuleCallbacks = ModuleCallbacksImpl; + type TimeProvider = pallet_timestamp::Pallet; +} +``` + +You should include it in your `construct_runtime!` macro: + +```rust +Ibc: pallet_ibc::{Pallet, Call, Storage, Event}, +``` + +### Genesis Configuration + +This pallet does not have any genesis configuration. + +## How to Interact with the Pallet +The Hermes (IBC Relayer CLI) offers commands to send reqeusts to pallet ibc to trigger the standard ibc communications defined in [ibc spce](https://github.com/cosmos/ibc/tree/ee71d0640c23ec4e05e924f52f557b5e06c1d82f/spec). +[Hermes Command List](https://hermes.informal.systems/commands/raw/index.html). + +## Reference Docs + +You can view the reference docs for this pallet by running: + +``` +cargo doc --open +``` + +or by visiting this site: https://docs.rs/pallet-ibc \ No newline at end of file diff --git a/frame/ibc/src/context.rs b/frame/ibc/src/context.rs new file mode 100644 index 0000000000000..6ab7af3e27f0c --- /dev/null +++ b/frame/ibc/src/context.rs @@ -0,0 +1,50 @@ +use crate::*; +use alloc::{ + borrow::{Borrow, Cow, ToOwned}, + collections::BTreeMap, + sync::Arc, +}; +use scale_info::TypeInfo; + +use crate::module::applications::transfer::transfer_handle_callback::TransferModule; +use ibc::{ + applications::transfer::{context::Ics20Context, error::Error as ICS20Error, MODULE_ID_STR}, + core::{ + ics04_channel::{ + channel::{Counterparty, Order}, + error::Error as Ics04Error, + Version, + }, + ics24_host::identifier::{ChannelId, ConnectionId, PortId}, + ics26_routing::context::{ + Ics26Context, Module, ModuleId, ModuleOutputBuilder, RouterBuilder, + }, + }, +}; + +use crate::module::core::ics26_routing::{Router, SubRouterBuilder}; + +/// A struct capturing all the functional dependencies (i.e., context) +/// which the ICS26 module requires to be able to dispatch and process IBC messages. +#[derive(Clone, Debug)] +pub struct Context { + pub _pd: PhantomData, + pub router: Router, +} + +impl Context { + pub fn new() -> Self { + let r = SubRouterBuilder::default() + .add_route(MODULE_ID_STR.parse().unwrap(), TransferModule(PhantomData::)) // register transfer Module + .unwrap() + .build(); + + Self { _pd: PhantomData::default(), router: r } + } +} + +impl Default for Context { + fn default() -> Self { + Self::new() + } +} diff --git a/frame/ibc/src/events.rs b/frame/ibc/src/events.rs new file mode 100644 index 0000000000000..5f53e9ba060b0 --- /dev/null +++ b/frame/ibc/src/events.rs @@ -0,0 +1,340 @@ +use crate::*; +use core::borrow::Borrow; +use ibc::{core::ics26_routing, events::IbcEvent as RawIbcEvent}; + +/// ibc-rs' `ModuleEvent` representation in substrate +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct ModuleEvent { + pub kind: Vec, + pub module_name: ModuleId, + pub attributes: Vec, +} + +impl From for ModuleEvent { + fn from(module_event: ibc::events::ModuleEvent) -> Self { + Self { + kind: module_event.kind.as_bytes().to_vec(), + module_name: module_event.module_name.into(), + attributes: module_event.attributes.into_iter().map(|event| event.into()).collect(), + } + } +} + +impl From for ibc::events::ModuleEvent { + fn from(module_event: ModuleEvent) -> Self { + Self { + kind: String::from_utf8(module_event.kind).expect("never failed"), + module_name: module_event.module_name.into(), + attributes: module_event.attributes.into_iter().map(|event| event.into()).collect(), + } + } +} + +/// ibc-rs' `ModuleId` representation in substrate +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct ModuleId(pub Vec); + +impl From for ModuleId { + fn from(module_id: ics26_routing::context::ModuleId) -> Self { + Self(format!("{}", module_id).as_bytes().to_vec()) + } +} + +impl From for ics26_routing::context::ModuleId { + fn from(module_id: ModuleId) -> Self { + ics26_routing::context::ModuleId::from_str( + &String::from_utf8(module_id.0).expect("Convert From UTF8 Never Faild"), + ) + .expect("should never fiaild") + } +} + +/// ibc-rs' `ModuleEventAttribute` representation in substrate +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct ModuleEventAttribute { + pub key: Vec, + pub value: Vec, +} + +impl From for ModuleEventAttribute { + fn from(module_event_attribute: ibc::events::ModuleEventAttribute) -> Self { + Self { + key: module_event_attribute.key.as_bytes().to_vec(), + value: module_event_attribute.value.as_bytes().to_vec(), + } + } +} + +impl From for ibc::events::ModuleEventAttribute { + fn from(module_event_attribute: ModuleEventAttribute) -> Self { + Self { + key: String::from_utf8(module_event_attribute.key).expect("should not be faild"), + value: String::from_utf8(module_event_attribute.value).expect("should not be faild"), + } + } +} + +impl From for Event { + fn from(value: RawIbcEvent) -> Self { + match value { + RawIbcEvent::NewBlock(value) => Event::::NewBlock { height: value.height.into() }, + RawIbcEvent::CreateClient(value) => { + let height = value.0.height; + let client_id = value.0.client_id; + let client_type = value.0.client_type; + let consensus_height = value.0.consensus_height; + Event::::CreateClient { + height: height.into(), + client_id: client_id.into(), + client_type: client_type.into(), + consensus_height: consensus_height.into(), + } + }, + RawIbcEvent::UpdateClient(value) => { + let height = value.common.height; + let client_id = value.common.client_id; + let client_type = value.common.client_type; + let consensus_height = value.common.consensus_height; + Event::::UpdateClient { + height: height.into(), + client_id: client_id.into(), + client_type: client_type.into(), + consensus_height: consensus_height.into(), + } + }, + // Upgrade client events are not currently being used + RawIbcEvent::UpgradeClient(value) => { + let height = value.0.height; + let client_id = value.0.client_id; + let client_type = value.0.client_type; + let consensus_height = value.0.consensus_height; + Event::::UpgradeClient { + height: height.into(), + client_id: client_id.into(), + client_type: client_type.into(), + consensus_height: consensus_height.into(), + } + }, + RawIbcEvent::ClientMisbehaviour(value) => { + let height = value.0.height; + let client_id = value.0.client_id; + let client_type = value.0.client_type; + let consensus_height = value.0.consensus_height; + Event::::ClientMisbehaviour { + height: height.into(), + client_id: client_id.into(), + client_type: client_type.into(), + consensus_height: consensus_height.into(), + } + }, + RawIbcEvent::OpenInitConnection(value) => { + let height = value.attributes().height; + let connection_id: Option = + value.attributes().connection_id.clone().map(|val| val.into()); + let client_id = value.attributes().client_id.clone(); + let counterparty_connection_id: Option = + value.attributes().counterparty_connection_id.clone().map(|val| val.into()); + + let counterparty_client_id = value.attributes().counterparty_client_id.clone(); + Event::::OpenInitConnection { + height: height.into(), + connection_id, + client_id: client_id.into(), + counterparty_connection_id, + counterparty_client_id: counterparty_client_id.into(), + } + }, + RawIbcEvent::OpenTryConnection(value) => { + let height = value.attributes().height; + let connection_id: Option = + value.attributes().connection_id.clone().map(|val| val.into()); + let client_id = value.attributes().client_id.clone(); + let counterparty_connection_id: Option = + value.attributes().counterparty_connection_id.clone().map(|val| val.into()); + + let counterparty_client_id = value.attributes().counterparty_client_id.clone(); + Event::::OpenTryConnection { + height: height.into(), + connection_id, + client_id: client_id.into(), + counterparty_connection_id, + counterparty_client_id: counterparty_client_id.into(), + } + }, + RawIbcEvent::OpenAckConnection(value) => { + let height = value.attributes().height; + let connection_id: Option = + value.attributes().connection_id.clone().map(|val| val.into()); + let client_id = value.attributes().client_id.clone(); + let counterparty_connection_id: Option = + value.attributes().counterparty_connection_id.clone().map(|val| val.into()); + + let counterparty_client_id = value.attributes().counterparty_client_id.clone(); + Event::::OpenAckConnection { + height: height.into(), + connection_id, + client_id: client_id.into(), + counterparty_connection_id, + counterparty_client_id: counterparty_client_id.into(), + } + }, + RawIbcEvent::OpenConfirmConnection(value) => { + let height = value.attributes().height; + let connection_id: Option = + value.attributes().connection_id.clone().map(|val| val.into()); + let client_id = value.attributes().client_id.clone(); + let counterparty_connection_id: Option = + value.attributes().counterparty_connection_id.clone().map(|val| val.into()); + + let counterparty_client_id = value.attributes().counterparty_client_id.clone(); + Event::::OpenConfirmConnection { + height: height.into(), + connection_id, + client_id: client_id.into(), + counterparty_connection_id, + counterparty_client_id: counterparty_client_id.into(), + } + }, + RawIbcEvent::OpenInitChannel(value) => { + let height = value.height; + let port_id = value.port_id.clone(); + let channel_id: Option = value.channel_id.clone().map(|val| val.into()); + let connection_id = value.connection_id.clone(); + let counterparty_port_id = value.counterparty_port_id.clone(); + let counterparty_channel_id: Option = + value.channel_id.map(|val| val.into()); + Event::::OpenInitChannel { + height: height.into(), + port_id: port_id.into(), + channel_id, + connection_id: connection_id.into(), + counterparty_port_id: counterparty_port_id.into(), + counterparty_channel_id, + } + }, + RawIbcEvent::OpenTryChannel(value) => { + let height = value.height; + let port_id = value.port_id.clone(); + let channel_id: Option = value.channel_id.clone().map(|val| val.into()); + let connection_id = value.connection_id.clone(); + let counterparty_port_id = value.counterparty_port_id.clone(); + let counterparty_channel_id: Option = + value.channel_id.map(|val| val.into()); + Event::::OpenTryChannel { + height: height.into(), + port_id: port_id.into(), + channel_id, + connection_id: connection_id.into(), + counterparty_port_id: counterparty_port_id.into(), + counterparty_channel_id, + } + }, + RawIbcEvent::OpenAckChannel(value) => { + let height = value.height; + let port_id = value.port_id.clone(); + let channel_id: Option = value.channel_id.clone().map(|val| val.into()); + let connection_id = value.connection_id.clone(); + let counterparty_port_id = value.counterparty_port_id.clone(); + let counterparty_channel_id: Option = + value.channel_id.map(|val| val.into()); + Event::::OpenAckChannel { + height: height.into(), + port_id: port_id.into(), + channel_id, + connection_id: connection_id.into(), + counterparty_port_id: counterparty_port_id.into(), + counterparty_channel_id, + } + }, + RawIbcEvent::OpenConfirmChannel(value) => { + let height = value.height; + let port_id = value.port_id.clone(); + let channel_id: Option = value.channel_id.clone().map(|val| val.into()); + let connection_id = value.connection_id.clone(); + let counterparty_port_id = value.counterparty_port_id; + let counterparty_channel_id: Option = + value.channel_id.map(|val| val.into()); + Event::::OpenConfirmChannel { + height: height.into(), + port_id: port_id.into(), + channel_id, + connection_id: connection_id.into(), + counterparty_port_id: counterparty_port_id.into(), + counterparty_channel_id, + } + }, + RawIbcEvent::CloseInitChannel(value) => { + let height = value.height; + let port_id = value.port_id.clone(); + let channel_id: Option = Some(value.channel_id.into()); + let connection_id = value.connection_id.clone(); + let counterparty_port_id = value.counterparty_port_id; + let counterparty_channel_id: Option = + value.counterparty_channel_id.map(|val| val.into()); + Event::::CloseInitChannel { + height: height.into(), + port_id: port_id.into(), + channel_id, + connection_id: connection_id.into(), + counterparty_port_id: counterparty_port_id.into(), + counterparty_channel_id, + } + }, + RawIbcEvent::CloseConfirmChannel(value) => { + let height = value.height; + let port_id = value.port_id.clone(); + let channel_id: Option = value.channel_id.clone().map(|val| val.into()); + let connection_id = value.connection_id.clone(); + let counterparty_port_id = value.counterparty_port_id.clone(); + let counterparty_channel_id: Option = + value.channel_id.map(|val| val.into()); + Event::::CloseConfirmChannel { + height: height.into(), + port_id: port_id.into(), + channel_id, + connection_id: connection_id.into(), + counterparty_port_id: counterparty_port_id.into(), + counterparty_channel_id, + } + }, + RawIbcEvent::SendPacket(value) => { + let height = value.height; + let packet = value.packet; + Event::::SendPacket { height: height.into(), packet: packet.into() } + }, + RawIbcEvent::ReceivePacket(value) => { + let height = value.height; + let packet = value.packet; + Event::::ReceivePacket { height: height.into(), packet: packet.into() } + }, + RawIbcEvent::WriteAcknowledgement(value) => { + let height = value.height; + let packet = value.packet; + let ack = value.ack; + + Event::::WriteAcknowledgement { + height: height.into(), + packet: packet.into(), + ack, + } + }, + RawIbcEvent::AcknowledgePacket(value) => { + let height = value.height; + let packet = value.packet; + Event::::AcknowledgePacket { height: height.into(), packet: packet.into() } + }, + RawIbcEvent::TimeoutPacket(value) => { + let height = value.height; + let packet = value.packet; + Event::::TimeoutPacket { height: height.into(), packet: packet.into() } + }, + RawIbcEvent::TimeoutOnClosePacket(value) => { + let height = value.height; + let packet = value.packet; + Event::::TimeoutOnClosePacket { height: height.into(), packet: packet.into() } + }, + RawIbcEvent::AppModule(value) => Event::::AppModule(value.into()), + RawIbcEvent::ChainError(value) => Event::::ChainError(value.as_bytes().to_vec()), + } + } +} diff --git a/frame/ibc/src/lib.rs b/frame/ibc/src/lib.rs new file mode 100644 index 0000000000000..8df7771751444 --- /dev/null +++ b/frame/ibc/src/lib.rs @@ -0,0 +1,646 @@ +#![cfg_attr(not(feature = "std"), no_std)] +// todo need in future to remove +#![allow(unreachable_code)] +#![allow(unreachable_patterns)] +#![allow(clippy::type_complexity)] +#![allow(non_camel_case_types)] +#![allow(dead_code)] +#![allow(unused_assignments)] +#![allow(unused_imports)] +#![allow(unused_variables)] +#![allow(clippy::too_many_arguments)] + +//! # Overview +//! +//! The goal of this pallet is to allow the blockchains built on Substrate to gain the ability to +//! interact with other chains in a trustees way via IBC protocol +//! +//! The pallet implements the chain specific logic of [ICS spec](https://github.com/cosmos/ibc/tree/ee71d0640c23ec4e05e924f52f557b5e06c1d82f), +//! and is integrated with [ibc-rs](https://github.com/informalsystems/ibc-rs), +//! which implements the generic cross-chain logic in [ICS spec](https://github.com/cosmos/ibc/tree/ee71d0640c23ec4e05e924f52f557b5e06c1d82f). + +extern crate alloc; +extern crate core; + +pub use pallet::*; + +use alloc::{ + format, + string::{String, ToString}, +}; +use core::{marker::PhantomData, str::FromStr}; +use scale_info::{prelude::vec, TypeInfo}; +use serde::{Deserialize, Serialize}; + +use codec::{Codec, Decode, Encode}; + +use frame_support::{ + sp_runtime::traits::{AtLeast32BitUnsigned, CheckedConversion}, + sp_std::fmt::Debug, + traits::{tokens::fungibles, Currency, ExistenceRequirement::AllowDeath}, + PalletId, +}; +use frame_system::ensure_signed; +use sp_runtime::{traits::AccountIdConversion, DispatchError, RuntimeDebug, TypeId}; +use sp_std::prelude::*; + +use ibc::{ + applications::transfer::msgs::transfer::MsgTransfer, + core::{ + ics02_client::{client_state::AnyClientState, height}, + ics04_channel::timeout::TimeoutHeight, + ics24_host::identifier::{self, ChainId as ICS24ChainId, ChannelId as IbcChannelId}, + ics26_routing::msgs::Ics26Envelope, + }, + timestamp, + tx_msg::Msg, +}; + +use tendermint_proto::Protobuf; + +pub mod context; +pub mod events; +pub mod module; +pub mod traits; +pub mod utils; + +use crate::{context::Context, traits::AssetIdAndNameProvider}; + +use crate::module::core::ics24_host::{ + ChannelId, ClientId, ClientType, ConnectionId, Height, Packet, PortId, Timestamp, +}; + +pub const REVISION_NUMBER: u64 = 8888; + +type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; + +/// A struct corresponds to `Any` in crate "prost-types", used in ibc-rs. +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct Any { + pub type_url: Vec, + pub value: Vec, +} + +impl From for Any { + fn from(any: ibc_proto::google::protobuf::Any) -> Self { + Self { type_url: any.type_url.as_bytes().to_vec(), value: any.value } + } +} + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use crate::{ + events::ModuleEvent, + module::{ + applications::transfer::transfer_handle_callback::TransferModule, + core::ics24_host::{ + ChannelId, ClientId, ClientType, ConnectionId, Height, Packet, PortId, Sequence, + Timestamp, + }, + }, + }; + use frame_support::{ + dispatch::DispatchResult, + pallet_prelude::*, + traits::{ + fungibles::{Inspect, Mutate, Transfer}, + UnixTime, + }, + }; + use frame_system::pallet_prelude::*; + use ibc::{ + applications::transfer::context::Ics20Context, + core::{ + ics02_client::client_consensus::AnyConsensusState, + ics04_channel::{ + channel::{Counterparty, Order}, + context::ChannelKeeper, + events::WriteAcknowledgement, + Version, + }, + ics24_host::{ + identifier::{ChannelId as IbcChannelId, PortId as IbcPortId}, + path::{ClientConsensusStatePath, ClientStatePath}, + }, + ics26_routing::error::Error as Ics26Error, + }, + events::IbcEvent, + handler::{HandlerOutput, HandlerOutputBuilder}, + signer::Signer, + }; + use sp_runtime::traits::IdentifyAccount; + + /// Configure the pallet by specifying the parameters and types on which it depends. + #[pallet::config] + pub trait Config: frame_system::Config + Sync + Send + Debug { + /// The overarching event type. + type Event: From> + IsType<::Event>; + + /// The provider providing timestamp of host chain + type TimeProvider: UnixTime; + + /// The currency type of the runtime + type Currency: Currency; + + /// Identifier for the class of asset. + type AssetId: Member + + Parameter + + AtLeast32BitUnsigned + + Codec + + Copy + + Debug + + Default + + MaybeSerializeDeserialize; + + /// The units in which we record balances. + type AssetBalance: Parameter + + Member + + AtLeast32BitUnsigned + + Codec + + Default + + From + + Into + + Copy + + MaybeSerializeDeserialize + + Debug; + + /// Expose customizable associated type of asset transfer, lock and unlock + type Assets: Transfer + + Mutate + + Inspect; + + /// Map of cross-chain asset ID & name + type AssetIdByName: AssetIdAndNameProvider; + + /// Account Id Conversion from SS58 string or hex string + type AccountIdConversion: TryFrom + + IdentifyAccount + + Clone + + PartialEq + + Debug; + + // The native token name + const NATIVE_TOKEN_NAME: &'static [u8]; + } + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + #[pallet::without_storage_info] + pub struct Pallet(_); + + #[pallet::storage] + /// ClientStatePath(client_id) => ClientState + pub type ClientStates = + StorageMap<_, Blake2_128Concat, Vec, Vec, ValueQuery>; + + #[pallet::storage] + /// (client_id, height) => timestamp + pub type ClientProcessedTimes = StorageDoubleMap< + _, + Blake2_128Concat, + Vec, + Blake2_128Concat, + Vec, + Vec, + ValueQuery, + >; + + #[pallet::storage] + /// (client_id, height) => host_height + pub type ClientProcessedHeights = StorageDoubleMap< + _, + Blake2_128Concat, + Vec, + Blake2_128Concat, + Vec, + Vec, + ValueQuery, + >; + + #[pallet::storage] + /// ClientConsensusStatePath(client_id, Height) => ConsensusState + pub type ConsensusStates = + StorageMap<_, Blake2_128Concat, Vec, Vec, ValueQuery>; + + #[pallet::storage] + /// ConnectionsPath(connection_id) => ConnectionEnd + pub type Connections = StorageMap<_, Blake2_128Concat, Vec, Vec, ValueQuery>; + + #[pallet::storage] + /// ChannelEndPath(port_id, channel_id) => ChannelEnd + pub type Channels = StorageMap<_, Blake2_128Concat, Vec, Vec, ValueQuery>; + + #[pallet::storage] + /// ConnectionsPath(connection_id) => Vec + pub type ChannelsConnection = + StorageMap<_, Blake2_128Concat, Vec, Vec>, ValueQuery>; + + #[pallet::storage] + /// SeqSendsPath(port_id, channel_id) => sequence + pub type NextSequenceSend = + StorageMap<_, Blake2_128Concat, Vec, u64, ValueQuery>; + + #[pallet::storage] + /// SeqRecvsPath(port_id, channel_id) => sequence + pub type NextSequenceRecv = + StorageMap<_, Blake2_128Concat, Vec, u64, ValueQuery>; + + #[pallet::storage] + /// SeqAcksPath(port_id, channel_id) => sequence + pub type NextSequenceAck = StorageMap<_, Blake2_128Concat, Vec, u64, ValueQuery>; + + #[pallet::storage] + /// AcksPath(port_id, channel_id, sequence) => hash of acknowledgement + pub type Acknowledgements = + StorageMap<_, Blake2_128Concat, Vec, Vec, ValueQuery>; + + #[pallet::storage] + /// ClientTypePath(client_id) => client_type + pub type Clients = StorageMap<_, Blake2_128Concat, Vec, Vec, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn client_counter)] + /// client counter + pub type ClientCounter = StorageValue<_, u64, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn connection_counter)] + /// connection counter + pub type ConnectionCounter = StorageValue<_, u64, ValueQuery>; + + #[pallet::storage] + /// channel counter + pub type ChannelCounter = StorageValue<_, u64, ValueQuery>; + + #[pallet::storage] + /// ClientConnectionsPath(client_id) => connection_id + pub type ConnectionClient = + StorageMap<_, Blake2_128Concat, Vec, Vec, ValueQuery>; + + #[pallet::storage] + /// ReceiptsPath(port_id, channel_id, sequence) => receipt + pub type PacketReceipt = + StorageMap<_, Blake2_128Concat, Vec, Vec, ValueQuery>; + + #[pallet::storage] + /// CommitmentsPath(port_id, channel_id, sequence) => hash of (timestamp, height, packet) + pub type PacketCommitment = + StorageMap<_, Blake2_128Concat, Vec, Vec, ValueQuery>; + + #[pallet::storage] + /// (height, port_id, channel_id, sequence) => send-packet event + pub type SendPacketEvent = StorageNMap< + _, + ( + NMapKey>, + NMapKey>, + NMapKey, + ), + Vec, + ValueQuery, + >; + + #[pallet::storage] + /// (port_id, channel_id, sequence) => writ ack event + pub type WriteAckPacketEvent = StorageNMap< + _, + ( + NMapKey>, + NMapKey>, + NMapKey, + ), + Vec, + ValueQuery, + >; + + #[pallet::storage] + /// Latest height + pub type LatestHeight = StorageValue<_, Vec, ValueQuery>; + + #[pallet::storage] + /// Previous host block height + pub type OldHeight = StorageValue<_, u64, ValueQuery>; + + #[pallet::storage] + /// (asset name) => asset id + pub type AssetIdByName = + StorageMap<_, Twox64Concat, Vec, T::AssetId, ValueQuery>; + + #[pallet::genesis_config] + pub struct GenesisConfig { + pub asset_id_by_name: Vec<(String, T::AssetId)>, + } + + #[cfg(feature = "std")] + impl Default for GenesisConfig { + fn default() -> Self { + Self { asset_id_by_name: Vec::new() } + } + } + + #[pallet::genesis_build] + impl GenesisBuild for GenesisConfig { + fn build(&self) { + for (token_id, id) in self.asset_id_by_name.iter() { + >::insert(token_id.as_bytes(), id); + } + } + } + + /// Substrate IBC event list + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// New block event + NewBlock { height: Height }, + /// Client Create event + CreateClient { + height: Height, + client_id: ClientId, + client_type: ClientType, + consensus_height: Height, + }, + /// Client update event + UpdateClient { + height: Height, + client_id: ClientId, + client_type: ClientType, + consensus_height: Height, + }, + /// Client upgrade event + UpgradeClient { + height: Height, + client_id: ClientId, + client_type: ClientType, + consensus_height: Height, + }, + /// Client misbehaviour event + ClientMisbehaviour { + height: Height, + client_id: ClientId, + client_type: ClientType, + consensus_height: Height, + }, + /// Connection open init event + OpenInitConnection { + height: Height, + connection_id: Option, + client_id: ClientId, + counterparty_connection_id: Option, + counterparty_client_id: ClientId, + }, + /// Connection open try event + OpenTryConnection { + height: Height, + connection_id: Option, + client_id: ClientId, + counterparty_connection_id: Option, + counterparty_client_id: ClientId, + }, + /// Connection open acknowledgement event + OpenAckConnection { + height: Height, + connection_id: Option, + client_id: ClientId, + counterparty_connection_id: Option, + counterparty_client_id: ClientId, + }, + /// Connection open confirm event + OpenConfirmConnection { + height: Height, + connection_id: Option, + client_id: ClientId, + counterparty_connection_id: Option, + counterparty_client_id: ClientId, + }, + /// Channel open init event + OpenInitChannel { + height: Height, + port_id: PortId, + channel_id: Option, + connection_id: ConnectionId, + counterparty_port_id: PortId, + counterparty_channel_id: Option, + }, + /// Channel open try event + OpenTryChannel { + height: Height, + port_id: PortId, + channel_id: Option, + connection_id: ConnectionId, + counterparty_port_id: PortId, + counterparty_channel_id: Option, + }, + /// Channel open acknowledgement event + OpenAckChannel { + height: Height, + port_id: PortId, + channel_id: Option, + connection_id: ConnectionId, + counterparty_port_id: PortId, + counterparty_channel_id: Option, + }, + /// Channel open confirm event + OpenConfirmChannel { + height: Height, + port_id: PortId, + channel_id: Option, + connection_id: ConnectionId, + counterparty_port_id: PortId, + counterparty_channel_id: Option, + }, + /// Channel close init event + CloseInitChannel { + height: Height, + port_id: PortId, + channel_id: Option, + connection_id: ConnectionId, + counterparty_port_id: PortId, + counterparty_channel_id: Option, + }, + /// Channel close confirm event + CloseConfirmChannel { + height: Height, + port_id: PortId, + channel_id: Option, + connection_id: ConnectionId, + counterparty_port_id: PortId, + counterparty_channel_id: Option, + }, + /// Send packet event + SendPacket { height: Height, packet: Packet }, + /// Receive packet event + ReceivePacket { height: Height, packet: Packet }, + /// WriteAcknowledgement packet event + WriteAcknowledgement { height: Height, packet: Packet, ack: Vec }, + /// Acknowledgements packet event + AcknowledgePacket { height: Height, packet: Packet }, + /// Timeout packet event + TimeoutPacket { height: Height, packet: Packet }, + /// TimoutOnClose packet event + TimeoutOnClosePacket { height: Height, packet: Packet }, + /// Chain Error event + ChainError(Vec), + /// App Module event + AppModule(ModuleEvent), + /// Transfer native token event + TransferNativeToken(T::AccountIdConversion, T::AccountIdConversion, BalanceOf), + /// Transfer non-native token event + TransferNoNativeToken( + T::AccountIdConversion, + T::AccountIdConversion, + ::AssetBalance, + ), + /// Burn cross chain token event + BurnToken(T::AssetId, T::AccountIdConversion, T::AssetBalance), + /// Mint chairperson token event + MintToken(T::AssetId, T::AccountIdConversion, T::AssetBalance), + } + + /// Errors in MMR verification informing users that something went wrong. + #[pallet::error] + pub enum Error { + /// Invalid token id + InvalidTokenId, + /// Wrong assert id + WrongAssetId, + // Parser Msg Transfer Error + ParserMsgTransferError, + } + + /// Dispatchable functions allows users to interact with the pallet and invoke state changes. + /// These functions materialize as "extrinsic", which are often compared to transactions. + /// Dispatch able functions must be annotated with a weight and must return a DispatchResult. + #[pallet::call] + impl Pallet { + /// This function acts as an entry for most of the IBC request. + /// I.e., create clients, update clients, handshakes to create channels, ...etc + /// + /// The origin must be Signed and the sender must have sufficient funds fee. + /// + /// Parameters: + /// - `messages`: The arbitrary ICS message's representation in Substrate, which contains an + /// URL and + /// a serialized protocol buffer message. The URL name that uniquely identifies the type of + /// the serialized protocol buffer message. + /// + /// The relevant events are emitted when successful. + #[pallet::weight(0)] + pub fn deliver(origin: OriginFor, messages: Vec) -> DispatchResultWithPostInfo { + let _sender = ensure_signed(origin)?; + let mut ctx = Context::::new(); + + let messages: Vec = messages + .into_iter() + .map(|message| ibc_proto::google::protobuf::Any { + type_url: String::from_utf8(message.type_url.clone()) + .expect("Convert From UTF8 Never Faild"), + value: message.value, + }) + .collect(); + + for (_, message) in messages.clone().into_iter().enumerate() { + match ibc::core::ics26_routing::handler::deliver(&mut ctx, message.clone()) { + Ok(ibc::core::ics26_routing::handler::MsgReceipt { events, log: _log }) => { + // deposit events about send packet event and ics20 transfer event + for event in events { + Self::deposit_event(event.clone().into()); + } + }, + Err(error) => { + log::error!("deliver error : {:?} ", error); + }, + }; + } + + Ok(().into()) + } + + /// ICS20 fungible token transfer. + /// Handling transfer request as sending chain or receiving chain. + /// + /// Parameters: + /// - `messages`: A serialized protocol buffer message containing the transfer request. + /// + /// The relevant events are emitted when successful. + #[pallet::weight(0)] + pub fn raw_transfer( + origin: OriginFor, + messages: Vec, + ) -> DispatchResultWithPostInfo { + let _sender = ensure_signed(origin)?; + let mut ctx = TransferModule(PhantomData::); + + let messages: Vec = messages + .into_iter() + .map(|message| ibc_proto::google::protobuf::Any { + type_url: String::from_utf8(message.type_url.clone()) + .expect("Convert From UTF8 Never Faild"), + value: message.value, + }) + .collect(); + + for message in messages { + let mut handle_out = HandlerOutputBuilder::new(); + let msg_transfer = MsgTransfer::try_from(message) + .map_err(|_| Error::::ParserMsgTransferError)?; + let result = ibc::applications::transfer::relay::send_transfer::send_transfer( + &mut ctx, + &mut handle_out, + msg_transfer, + ); + match result { + Ok(_value) => { + log::trace!("raw_transfer Successful!"); + }, + Err(error) => { + log::trace!("raw_transfer Error : {:?} ", error); + }, + } + + let HandlerOutput::<()> { result, log, events } = handle_out.with_result(()); + + log::trace!("raw_transfer log : {:?} ", log); + + // deposit events about send packet event and ics20 transfer event + for event in events { + Self::deposit_event(event.clone().into()); + } + } + + Ok(().into()) + } + } +} + +impl AssetIdAndNameProvider for Pallet { + type Err = Error; + + fn try_get_asset_id(name: impl AsRef<[u8]>) -> Result<::AssetId, Self::Err> { + let asset_id = >::try_get(name.as_ref().to_vec()); + match asset_id { + Ok(id) => Ok(id), + _ => Err(Error::::InvalidTokenId), + } + } + + fn try_get_asset_name(asset_id: T::AssetId) -> Result, Self::Err> { + let token_id = >::iter().find(|p| p.1 == asset_id).map(|p| p.0); + match token_id { + Some(id) => Ok(id), + _ => Err(Error::::WrongAssetId), + } + } +} + +pub fn from_channel_id_to_vec(value: IbcChannelId) -> Vec { + value.to_string().as_bytes().to_vec() +} diff --git a/frame/ibc/src/mock.rs b/frame/ibc/src/mock.rs new file mode 100644 index 0000000000000..d4756f898df97 --- /dev/null +++ b/frame/ibc/src/mock.rs @@ -0,0 +1,216 @@ +use crate as pallet_ibc; +pub use frame_support::{ + construct_runtime, parameter_types, + traits::{ + ConstU128, ConstU16, ConstU32, ConstU8, KeyOwnerProofSystem, Randomness, StorageInfo, + }, + weights::{ + constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND}, + DispatchClass, IdentityFee, Weight, + }, + StorageValue, +}; +use frame_system as system; +use frame_system::{ + limits::{BlockLength, BlockWeights}, + EnsureRoot, +}; +use sp_core::{crypto::KeyTypeId, OpaqueMetadata, H256}; +use sp_runtime::{ + create_runtime_str, + generic::{self, Era}, + testing::Header, + traits::{AccountIdLookup, BlakeTwo256, IdentifyAccount, IdentityLookup, Verify}, + MultiSignature, +}; +use sp_version::RuntimeVersion; +use std::time::{Duration, Instant}; + +pub type Signature = MultiSignature; +pub(crate) type AccountId = <::Signer as IdentifyAccount>::AccountId; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +// Configure a mock runtime to test the pallet. +construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system, + Assets: pallet_assets::, + Balances: pallet_balances, + Ibc: pallet_ibc, + } +); + +/// A hash of some data used by the chain. +pub type Hash = sp_core::H256; + +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const SS58Prefix: u8 = 42; +} + +/// Index of a transaction in the chain. +pub type Index = u32; +/// An index to a block. +pub type BlockNumber = u32; + +impl frame_system::Config for Test { + /// The basic call filter to use in dispatchable. + type BaseCallFilter = frame_support::traits::Everything; + /// Block & extrinsics weights: base values and limits. + type BlockWeights = (); + /// The maximum length of a block (in bytes). + type BlockLength = (); + /// The identifier used to distinguish between accounts. + type AccountId = AccountId; + /// The aggregated dispatch type that is available for extrinsics. + type Call = Call; + /// The lookup mechanism to get account ID from whatever is passed in dispatchers. + type Lookup = AccountIdLookup; + /// The index type for storing how many extrinsics an account has signed. + type Index = Index; + /// The index type for blocks. + type BlockNumber = BlockNumber; + /// The type for hashing blocks and tries. + type Hash = Hash; + /// The hashing algorithm used. + type Hashing = BlakeTwo256; + /// The header type. + type Header = generic::Header; + /// The ubiquitous event type. + type Event = Event; + /// The ubiquitous origin type. + type Origin = Origin; + /// Maximum number of block number to block hash mappings to keep (oldest pruned first). + type BlockHashCount = (); + /// The weight of database operations that the runtime can invoke. + type DbWeight = (); + /// Version of the runtime. + type Version = (); + /// Converts a module to the index of the module in `construct_runtime!`. + /// + /// This type is being generated by `construct_runtime!`. + type PalletInfo = PalletInfo; + /// What to do if a new account is created. + type OnNewAccount = (); + /// What to do if an account is fully reaped from the system. + type OnKilledAccount = (); + /// The data to be stored in an account. + type AccountData = pallet_balances::AccountData; + /// Weight information for the extrinsics of this pallet. + type SystemWeightInfo = (); + /// This is used as an identifier of the chain. 42 is the generic substrate prefix. + type SS58Prefix = ConstU16<42>; + /// The set code logic, just the default since we're not a parachain. + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +pub type Balance = u128; +/// Type used for expressing timestamp. +pub type Moment = u64; + +pub const MILLICENTS: Balance = 10_000_000_000_000; +pub const CENTS: Balance = 1_000 * MILLICENTS; // assume this is worth about a cent. +pub const DOLLARS: Balance = 100 * CENTS; + +parameter_types! { + pub const AssetDeposit: Balance = 100 * DOLLARS; + pub const ApprovalDeposit: Balance = 1 * DOLLARS; + pub const StringLimit: u32 = 50; + pub const MetadataDepositBase: Balance = 10 * DOLLARS; + pub const MetadataDepositPerByte: Balance = 1 * DOLLARS; +} + +impl pallet_assets::Config for Test { + type Event = Event; + type Balance = AssetBalance; + type AssetId = AssetId; + type Currency = Balances; + type ForceOrigin = EnsureRoot; + type AssetDeposit = AssetDeposit; + type AssetAccountDeposit = ConstU128; + type MetadataDepositBase = MetadataDepositBase; + type MetadataDepositPerByte = MetadataDepositPerByte; + type ApprovalDeposit = ApprovalDeposit; + type StringLimit = StringLimit; + type Freezer = (); + type Extra = (); + type WeightInfo = pallet_assets::weights::SubstrateWeight; +} + +parameter_types! { + pub const ExistentialDeposit: Balance = 1 * DOLLARS; + // For weight estimation, we assume that the most locks on an individual account will be 50. + // This number may need to be adjusted in the future if this assumption no longer holds true. + pub const MaxLocks: u32 = 50; + pub const MaxReserves: u32 = 50; +} + +impl pallet_balances::Config for Test { + type MaxLocks = MaxLocks; + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; + /// The type for recording an account's balance. + type Balance = Balance; + /// The ubiquitous event type. + type Event = Event; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = frame_system::Pallet; + type WeightInfo = pallet_balances::weights::SubstrateWeight; +} + +parameter_types! { + pub const MinimumPeriod: Moment = SLOT_DURATION / 2; +} + +impl pallet_timestamp::Config for Test { + /// A timestamp: milliseconds since the unix epoch. + type Moment = Moment; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); +} + +parameter_types! { + pub const MaxAuthorities: u32 = 100; + pub const MaxKeys: u32 = 10_000; + pub const MaxPeerInHeartbeats: u32 = 10_000; + pub const MaxPeerDataEncodingSize: u32 = 1_000; +} + +pub const MILLISECS_PER_BLOCK: Moment = 6000; +pub const SECS_PER_BLOCK: Moment = MILLISECS_PER_BLOCK / 1000; + +// NOTE: Currently it is not possible to change the slot duration after the chain has started. +// Attempting to do so will brick block production. +pub const SLOT_DURATION: Moment = MILLISECS_PER_BLOCK; + +// 1 in 4 blocks (on average, not counting collisions) will be primary BABE blocks. +pub const PRIMARY_PROBABILITY: (u64, u64) = (1, 4); + +pub type AssetBalance = u128; +pub type AssetId = u32; + +impl super::pallet::Config for Test { + type Event = Event; + type TimeProvider = pallet_timestamp::Pallet; + type Currency = Balances; + type AssetId = AssetId; + type AssetBalance = AssetBalance; + type Assets = Assets; + type AssetIdByName = Ibc; + type AccountIdConversion = pallet_ibc::module::applications::transfer::IbcAccount; + const NATIVE_TOKEN_NAME: &'static [u8] = b"DEMO"; +} + +// Build genesis storage according to the mock runtime. +pub fn new_test_ext() -> sp_io::TestExternalities { + system::GenesisConfig::default().build_storage::().unwrap().into() +} diff --git a/frame/ibc/src/module/applications/mod.rs b/frame/ibc/src/module/applications/mod.rs new file mode 100644 index 0000000000000..014e52f2771e2 --- /dev/null +++ b/frame/ibc/src/module/applications/mod.rs @@ -0,0 +1 @@ +pub mod transfer; diff --git a/frame/ibc/src/module/applications/transfer/channel.rs b/frame/ibc/src/module/applications/transfer/channel.rs new file mode 100644 index 0000000000000..459a9a68db464 --- /dev/null +++ b/frame/ibc/src/module/applications/transfer/channel.rs @@ -0,0 +1,285 @@ +use super::transfer_handle_callback::TransferModule; +use crate::*; +use core::{str::FromStr, time::Duration}; +use log::{error, info, trace, warn}; + +use crate::context::Context; +use ibc::{ + core::{ + ics02_client::{ + client_consensus::AnyConsensusState, client_state::AnyClientState, + context::ClientReader, + }, + ics03_connection::{ + connection::ConnectionEnd, context::ConnectionReader, error::Error as ICS03Error, + }, + ics04_channel::{ + channel::ChannelEnd, + commitment::{ + AcknowledgementCommitment as IbcAcknowledgementCommitment, + PacketCommitment as IbcPacketCommitment, + }, + context::{ChannelKeeper, ChannelReader}, + error::Error as Ics04Error, + packet::{Receipt, Sequence}, + }, + ics05_port::{context::PortReader, error::Error as Ics05Error}, + ics24_host::{ + identifier::{ChannelId, ClientId, ConnectionId, PortId}, + path::{ + AcksPath, ChannelEndsPath, CommitmentsPath, ConnectionsPath, ReceiptsPath, + SeqAcksPath, SeqRecvsPath, SeqSendsPath, + }, + Path, + }, + ics26_routing::context::ModuleId, + }, + timestamp::Timestamp, + Height, +}; + +impl ChannelReader for TransferModule { + fn channel_end(&self, port_channel_id: &(PortId, ChannelId)) -> Result { + let connect = Context::::new(); + connect.channel_end(port_channel_id) + } + + fn connection_end(&self, connection_id: &ConnectionId) -> Result { + let connect = Context::::new(); + ChannelReader::connection_end(&connect, connection_id) + } + + /// Returns the `ChannelsConnection` for the given identifier `conn_id`. + fn connection_channels( + &self, + conn_id: &ConnectionId, + ) -> Result, Ics04Error> { + let connect = Context::::new(); + connect.connection_channels(conn_id) + } + + fn client_state(&self, client_id: &ClientId) -> Result { + let connect = Context::::new(); + + ChannelReader::client_state(&connect, client_id) + } + + fn client_consensus_state( + &self, + client_id: &ClientId, + height: Height, + ) -> Result { + let connect = Context::::new(); + ChannelReader::client_consensus_state(&connect, client_id, height) + } + + fn get_next_sequence_send( + &self, + port_channel_id: &(PortId, ChannelId), + ) -> Result { + let connect = Context::::new(); + connect.get_next_sequence_send(port_channel_id) + } + + fn get_next_sequence_recv( + &self, + port_channel_id: &(PortId, ChannelId), + ) -> Result { + let connect = Context::::new(); + connect.get_next_sequence_recv(port_channel_id) + } + + fn get_next_sequence_ack( + &self, + port_channel_id: &(PortId, ChannelId), + ) -> Result { + let connect = Context::::new(); + connect.get_next_sequence_ack(port_channel_id) + } + + /// Returns the `PacketCommitment` for the given identifier `(PortId, ChannelId, Sequence)`. + fn get_packet_commitment( + &self, + key: &(PortId, ChannelId, Sequence), + ) -> Result { + let connect = Context::::new(); + connect.get_packet_commitment(key) + } + + fn get_packet_receipt( + &self, + key: &(PortId, ChannelId, Sequence), + ) -> Result { + let connect = Context::::new(); + connect.get_packet_receipt(key) + } + + /// Returns the `Acknowledgements` for the given identifier `(PortId, ChannelId, Sequence)`. + fn get_packet_acknowledgement( + &self, + key: &(PortId, ChannelId, Sequence), + ) -> Result { + let connect = Context::::new(); + connect.get_packet_acknowledgement(key) + } + + /// A hashing function for packet commitments + fn hash(&self, value: Vec) -> Vec { + let connect = Context::::new(); + connect.hash(value) + } + + /// Returns the current height of the local chain. + fn host_height(&self) -> Height { + let connect = Context::::new(); + ChannelReader::host_height(&connect) + } + + /// Returns the current timestamp of the local chain. + fn host_timestamp(&self) -> Timestamp { + let connect = Context::::new(); + ChannelReader::host_timestamp(&connect) + } + + /// Returns the `AnyConsensusState` for the given identifier `height`. + fn host_consensus_state(&self, height: Height) -> Result { + let connect = Context::::new(); + ConnectionReader::host_consensus_state(&connect, height) + .map_err(Ics04Error::ics03_connection) + } + + fn pending_host_consensus_state(&self) -> Result { + let connect = Context::::new(); + ClientReader::pending_host_consensus_state(&connect) + .map_err(|e| Ics04Error::ics03_connection(ICS03Error::ics02_client(e))) + } + + /// Returns the `ClientProcessedTimes` for the given identifier `client_id` & `height`. + fn client_update_time( + &self, + client_id: &ClientId, + height: Height, + ) -> Result { + let connect = Context::::new(); + connect.client_update_time(client_id, height) + } + + fn client_update_height( + &self, + client_id: &ClientId, + height: Height, + ) -> Result { + let connect = Context::::new(); + connect.client_update_height(client_id, height) + } + + /// Returns a counter on the number of channel ids have been created thus far. + /// The value of this counter should increase only via method + /// `ChannelKeeper::increase_channel_counter`. + fn channel_counter(&self) -> Result { + let connect = Context::::new(); + connect.channel_counter() + } + + fn max_expected_time_per_block(&self) -> Duration { + let connect = Context::::new(); + connect.max_expected_time_per_block() + } +} + +impl ChannelKeeper for TransferModule { + fn store_packet_commitment( + &mut self, + key: (PortId, ChannelId, Sequence), + commitment: IbcPacketCommitment, + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + connect.store_packet_commitment(key, commitment) + } + + fn delete_packet_commitment( + &mut self, + key: (PortId, ChannelId, Sequence), + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + connect.delete_packet_commitment(key) + } + + fn store_packet_receipt( + &mut self, + key: (PortId, ChannelId, Sequence), + receipt: Receipt, + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + connect.store_packet_receipt(key, receipt) + } + + fn store_packet_acknowledgement( + &mut self, + key: (PortId, ChannelId, Sequence), + ack_commitment: IbcAcknowledgementCommitment, + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + + connect.store_packet_acknowledgement(key, ack_commitment) + } + + fn delete_packet_acknowledgement( + &mut self, + key: (PortId, ChannelId, Sequence), + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + connect.delete_packet_acknowledgement(key) + } + + fn store_connection_channels( + &mut self, + conn_id: ConnectionId, + port_channel_id: &(PortId, ChannelId), + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + connect.store_connection_channels(conn_id, port_channel_id) + } + + /// Stores the given channel_end at a path associated with the port_id and channel_id. + fn store_channel( + &mut self, + port_channel_id: (PortId, ChannelId), + channel_end: &ChannelEnd, + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + connect.store_channel(port_channel_id, channel_end) + } + + fn store_next_sequence_send( + &mut self, + port_channel_id: (PortId, ChannelId), + seq: Sequence, + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + connect.store_next_sequence_send(port_channel_id, seq) + } + + fn store_next_sequence_recv( + &mut self, + port_channel_id: (PortId, ChannelId), + seq: Sequence, + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + connect.store_next_sequence_recv(port_channel_id, seq) + } + + fn store_next_sequence_ack( + &mut self, + port_channel_id: (PortId, ChannelId), + seq: Sequence, + ) -> Result<(), Ics04Error> { + let mut connect = Context::::new(); + connect.store_next_sequence_ack(port_channel_id, seq) + } + + fn increase_channel_counter(&mut self) { + let mut connect = Context::::new(); + connect.increase_channel_counter() + } +} diff --git a/frame/ibc/src/module/applications/transfer/mod.rs b/frame/ibc/src/module/applications/transfer/mod.rs new file mode 100644 index 0000000000000..86507b95a62bf --- /dev/null +++ b/frame/ibc/src/module/applications/transfer/mod.rs @@ -0,0 +1,271 @@ +pub mod channel; +pub mod transfer_handle_callback; + +use crate::{context::Context, *}; +use frame_support::traits::{ + fungibles::{Mutate, Transfer}, + ExistenceRequirement::AllowDeath, +}; +use log::{error, trace}; + +use crate::utils::get_channel_escrow_address; +use ibc::{ + applications::transfer::{ + context::{BankKeeper, Ics20Context, Ics20Keeper, Ics20Reader}, + error::Error as Ics20Error, + PrefixedCoin, PORT_ID_STR, + }, + core::ics24_host::identifier::PortId, + signer::Signer, +}; +use sp_runtime::{ + traits::{CheckedConversion, IdentifyAccount, Verify}, + MultiSignature, +}; + +use transfer_handle_callback::TransferModule; + +impl Ics20Keeper for TransferModule { + type AccountId = ::AccountId; +} + +impl BankKeeper for TransferModule { + type AccountId = ::AccountId; + + fn send_coins( + &mut self, + from: &Self::AccountId, + to: &Self::AccountId, + amt: &PrefixedCoin, + ) -> Result<(), Ics20Error> { + // TODO(davirain): trace_path now is private + // let is_native_asset = amt.denom.trace_path().is_empty(); + let is_native_asset = true; + match is_native_asset { + // transfer native token + true => { + // TODO(davirain): amount now is private, and base_denom is private + // let amount = amt.amount.as_u256().low_u128().checked_into().expect("Convert MUST + // NOT Failed"); let ibc_token_name = amt.denom.base_denom().as_str().as_bytes(); + let amount = todo!(); + let ibc_token_name = &[1, 1, 2, 3]; + let native_token_name = T::NATIVE_TOKEN_NAME; + + // assert native token name equal want to send ibc token name + assert_eq!( + native_token_name, ibc_token_name, + "send ibc token name is not native token name" + ); + + >::transfer( + &from.clone().into_account(), + &to.clone().into_account(), + amount, + AllowDeath, + ) + .map_err(|error| { + error!("❌ [send_coins] : Error: ({:?})", error); + Ics20Error::invalid_token() + })?; + + // add emit transfer native token event + Pallet::::deposit_event(Event::::TransferNativeToken( + from.clone(), + to.clone(), + amount, + )) + }, + // transfer non-native token + false => { + // TODO(davirain): amount now is private, and base_denom is private + // let amount = amt.amount.as_u256().low_u128().into(); + // let denom = amt.denom.base_denom().as_str(); + let amount = todo!(); + let denom = &[1, 1, 2, 3]; + // look cross chain asset have register in host chain + match T::AssetIdByName::try_get_asset_id(denom) { + Ok(token_id) => { + >::transfer( + token_id.into(), + &from.clone().into_account(), + &to.clone().into_account(), + amount, + true, + ) + .map_err(|error| { + error!("❌ [send_coins] : Error: ({:?})", error); + Ics20Error::invalid_token() + })?; + + // add emit transfer no native token event + Pallet::::deposit_event(Event::::TransferNoNativeToken( + from.clone(), + to.clone(), + amount, + )); + }, + Err(_error) => { + error!("❌ [send_coins]: denom: ({:?})", denom); + return Err(Ics20Error::invalid_token()) + }, + } + }, + } + + Ok(()) + } + + fn mint_coins( + &mut self, + account: &Self::AccountId, + amt: &PrefixedCoin, + ) -> Result<(), Ics20Error> { + // TODO(davirain): amount now is private, and base_denom is private + // let amount = amt.amount.as_u256().low_u128().into(); + // let denom = amt.denom.base_denom().as_str(); + let amount = todo!(); + let denom = &[1, 1, 2, 3]; + // look cross chain asset have register in host chain + match T::AssetIdByName::try_get_asset_id(denom) { + Ok(token_id) => { + >::mint_into( + token_id.into(), + &account.clone().into_account(), + amount, + ) + .map_err(|error| { + error!("❌ [mint_coins] : Error: ({:?})", error); + Ics20Error::invalid_token() + })?; + + // add mint token event + Pallet::::deposit_event(Event::::MintToken( + token_id, + account.clone(), + amount, + )); + }, + Err(_error) => { + error!("❌ [mint_coins]: denom: ({:?})", denom); + return Err(Ics20Error::invalid_token()) + }, + } + Ok(()) + } + + fn burn_coins( + &mut self, + account: &Self::AccountId, + amt: &PrefixedCoin, + ) -> Result<(), Ics20Error> { + // TODO(davirain): amount now is private, and base_denom is private + // let amount = amt.amount.as_u256().low_u128().into(); + // let denom = amt.denom.base_denom().as_str(); + let amount = todo!(); + let denom = &[1, 1, 2, 3]; + // look cross chain asset have register in host chain + match T::AssetIdByName::try_get_asset_id(denom) { + Ok(token_id) => { + >::burn_from( + token_id.into(), + &account.clone().into_account(), + amount, + ) + .map_err(|error| { + error!("❌ [burn_coins] : Error: ({:?})", error); + Ics20Error::invalid_token() + })?; + + // add burn token event + Pallet::::deposit_event(Event::::BurnToken( + token_id, + account.clone(), + amount, + )); + }, + Err(_error) => { + error!("❌ [burn_coins]: denom: ({:?})", denom); + return Err(Ics20Error::invalid_token()) + }, + } + Ok(()) + } +} + +impl Ics20Reader for TransferModule { + type AccountId = ::AccountId; + + fn get_port(&self) -> Result { + PortId::from_str(PORT_ID_STR) + .map_err(|e| Ics20Error::invalid_port_id(PORT_ID_STR.to_string(), e)) + } + + fn get_channel_escrow_address( + &self, + port_id: &PortId, + channel_id: &IbcChannelId, + ) -> Result { + get_channel_escrow_address(port_id, channel_id)? + .try_into() + .map_err(|_| Ics20Error::parse_account_failure()) + } + + fn is_send_enabled(&self) -> bool { + // TODO(davirain), need according channelEnd def + true + } + + fn is_receive_enabled(&self) -> bool { + // TODO(davirain), need according channelEnd def + true + } +} + +impl Ics20Context for TransferModule { + type AccountId = ::AccountIdConversion; // Need Setting Account TODO(davirian) +} + +/// Alias to 512-bit hash when used in the context of a transaction signature on the chain. +pub type Signature = MultiSignature; + +/// Some way of identifying an account on the chain. We intentionally make it equivalent +/// to the public key of our transaction signing scheme. +pub type AccountId = <::Signer as IdentifyAccount>::AccountId; + +#[derive(Clone, Debug, PartialEq, TypeInfo, Encode, Decode)] +pub struct IbcAccount(AccountId); + +impl IdentifyAccount for IbcAccount { + type AccountId = AccountId; + fn into_account(self) -> Self::AccountId { + self.0 + } +} + +impl TryFrom for IbcAccount +where + AccountId: From<[u8; 32]>, +{ + type Error = &'static str; + + /// Convert a signer to an IBC account. + /// Only valid hex strings are supported for now. + fn try_from(signer: Signer) -> Result { + let acc_str = signer.as_ref(); + if acc_str.starts_with("0x") { + match acc_str.strip_prefix("0x") { + Some(hex_string) => TryInto::<[u8; 32]>::try_into( + hex::decode(hex_string).map_err(|_| "Error decoding invalid hex string")?, + ) + .map_err(|_| "Invalid account id hex string") + .map(|acc| Self(acc.into())), + _ => Err("Signer does not hold a valid hex string"), + } + } + // Do SS58 decoding instead + else { + error!("Convert Signer ❌ : Failed! "); + Err("invalid ibc address or substrate address") + } + } +} diff --git a/frame/ibc/src/module/applications/transfer/transfer_handle_callback.rs b/frame/ibc/src/module/applications/transfer/transfer_handle_callback.rs new file mode 100644 index 0000000000000..e4314506cb2f4 --- /dev/null +++ b/frame/ibc/src/module/applications/transfer/transfer_handle_callback.rs @@ -0,0 +1,161 @@ +use crate::*; + +use crate::utils::host_height; +use ibc::{ + applications::transfer::{acknowledgement::Acknowledgement, error::Error as Ics20Error}, + core::{ + ics04_channel::{ + channel::{Counterparty, Order}, + context::ChannelKeeper, + error::Error as Ics04Error, + msgs::acknowledgement::Acknowledgement as GenericAcknowledgement, + packet::{Packet as IbcPacket, PacketResult}, + Version, + }, + ics24_host::identifier::{ChannelId as IbcChannelId, ConnectionId, PortId}, + ics26_routing::context::{Module, ModuleOutputBuilder, OnRecvPacketAck}, + }, + events::IbcEvent, + signer::Signer, +}; + +/// A structure handling ICS20 callback +#[derive(Debug)] +pub struct TransferModule(pub PhantomData); + +impl Module for TransferModule { + fn on_chan_open_init( + &mut self, + output: &mut ModuleOutputBuilder, + order: Order, + connection_hops: &[ConnectionId], + port_id: &PortId, + channel_id: &IbcChannelId, + counterparty: &Counterparty, + version: &Version, + ) -> Result<(), Ics04Error> { + ibc::applications::transfer::context::on_chan_open_init( + self, + output, + order, + connection_hops, + port_id, + channel_id, + counterparty, + version, + ) + .map_err(|value| Ics04Error::app_module(value.to_string())) + } + + fn on_chan_open_try( + &mut self, + output: &mut ModuleOutputBuilder, + order: Order, + connection_hops: &[ConnectionId], + port_id: &PortId, + channel_id: &IbcChannelId, + counterparty: &Counterparty, + version: &Version, + counterparty_version: &Version, + ) -> Result { + ibc::applications::transfer::context::on_chan_open_try( + self, + output, + order, + connection_hops, + port_id, + channel_id, + counterparty, + version, + counterparty_version, + ) + .map_err(|value| Ics04Error::app_module(value.to_string())) + } + + fn on_chan_open_ack( + &mut self, + output: &mut ModuleOutputBuilder, + port_id: &PortId, + channel_id: &IbcChannelId, + counterparty_version: &Version, + ) -> Result<(), Ics04Error> { + ibc::applications::transfer::context::on_chan_open_ack( + self, + output, + port_id, + channel_id, + counterparty_version, + ) + .map_err(|value| Ics04Error::app_module(value.to_string())) + } + + fn on_chan_open_confirm( + &mut self, + output: &mut ModuleOutputBuilder, + port_id: &PortId, + channel_id: &IbcChannelId, + ) -> Result<(), Ics04Error> { + ibc::applications::transfer::context::on_chan_open_confirm( + self, output, port_id, channel_id, + ) + .map_err(|value| Ics04Error::app_module(value.to_string())) + } + + fn on_chan_close_init( + &mut self, + output: &mut ModuleOutputBuilder, + port_id: &PortId, + channel_id: &IbcChannelId, + ) -> Result<(), Ics04Error> { + ibc::applications::transfer::context::on_chan_close_init(self, output, port_id, channel_id) + .map_err(|value| Ics04Error::app_module(value.to_string())) + } + + fn on_chan_close_confirm( + &mut self, + output: &mut ModuleOutputBuilder, + port_id: &PortId, + channel_id: &IbcChannelId, + ) -> Result<(), Ics04Error> { + ibc::applications::transfer::context::on_chan_close_confirm( + self, output, port_id, channel_id, + ) + .map_err(|value| Ics04Error::app_module(value.to_string())) + } + + fn on_recv_packet( + &self, + output: &mut ModuleOutputBuilder, + packet: &IbcPacket, + relayer: &Signer, + ) -> OnRecvPacketAck { + ibc::applications::transfer::context::on_recv_packet(self, output, packet, relayer) + } + + fn on_acknowledgement_packet( + &mut self, + output: &mut ModuleOutputBuilder, + packet: &IbcPacket, + acknowledgement: &GenericAcknowledgement, + relayer: &Signer, + ) -> Result<(), Ics04Error> { + ibc::applications::transfer::context::on_acknowledgement_packet( + self, + output, + packet, + acknowledgement, + relayer, + ) + .map_err(|value| Ics04Error::app_module(value.to_string())) + } + + fn on_timeout_packet( + &mut self, + output: &mut ModuleOutputBuilder, + packet: &IbcPacket, + relayer: &Signer, + ) -> Result<(), Ics04Error> { + ibc::applications::transfer::context::on_timeout_packet(self, output, packet, relayer) + .map_err(|value| Ics04Error::app_module(value.to_string())) + } +} diff --git a/frame/ibc/src/module/core/ics02_client.rs b/frame/ibc/src/module/core/ics02_client.rs new file mode 100644 index 0000000000000..84f803f2f771c --- /dev/null +++ b/frame/ibc/src/module/core/ics02_client.rs @@ -0,0 +1,235 @@ +use crate::*; +use alloc::string::ToString; +use core::str::FromStr; +use log::{error, info, trace, warn}; + +use crate::context::Context; +use ibc::{ + core::{ + ics02_client::{ + client_consensus::AnyConsensusState, + client_state::AnyClientState, + client_type::ClientType, + context::{ClientKeeper, ClientReader}, + error::Error as Ics02Error, + }, + ics24_host::{ + identifier::ClientId, + path::{ClientConsensusStatePath, ClientStatePath, ClientTypePath}, + }, + }, + timestamp::Timestamp, + Height, +}; + +impl ClientReader for Context { + fn client_type(&self, client_id: &ClientId) -> Result { + let client_type_path = ClientTypePath(client_id.clone()).to_string().as_bytes().to_vec(); + if >::contains_key(client_type_path.clone()) { + let data = >::get(client_type_path); + let data = + String::from_utf8(data).map_err(|_| Ics02Error::implementation_specific())?; + ClientType::from_str(&data).map_err(|e| Ics02Error::unknown_client_type(e.to_string())) + } else { + Err(Ics02Error::client_not_found(client_id.clone())) + } + } + + fn client_state(&self, client_id: &ClientId) -> Result { + let client_state_path = ClientStatePath(client_id.clone()).to_string().as_bytes().to_vec(); + + if >::contains_key(&client_state_path) { + let data = >::get(&client_state_path); + AnyClientState::decode_vec(&*data).map_err(|_| Ics02Error::implementation_specific()) + } else { + Err(Ics02Error::client_not_found(client_id.clone())) + } + } + + fn consensus_state( + &self, + client_id: &ClientId, + height: Height, + ) -> Result { + // search key + let client_consensus_state_path = ClientConsensusStatePath { + client_id: client_id.clone(), + epoch: height.revision_number(), + height: height.revision_height(), + } + .to_string() + .as_bytes() + .to_vec(); + + if >::contains_key(client_consensus_state_path.clone()) { + let values = >::get(client_consensus_state_path.clone()); + AnyConsensusState::decode_vec(&*values) + .map_err(|_| Ics02Error::implementation_specific()) + } else { + Err(Ics02Error::consensus_state_not_found(client_id.clone(), height)) + } + } + + fn next_consensus_state( + &self, + client_id: &ClientId, + height: Height, + ) -> Result, Ics02Error> { + // search key + let client_consensus_state_path = ClientConsensusStatePath { + client_id: client_id.clone(), + epoch: height.revision_number(), + height: height.revision_height(), + } + .to_string() + .as_bytes() + .to_vec(); + + if >::contains_key(client_consensus_state_path.clone()) { + let values = >::get(client_consensus_state_path.clone()); + let any_consensus_state = AnyConsensusState::decode_vec(&*values) + .map_err(|_| Ics02Error::implementation_specific())?; + Ok(Some(any_consensus_state)) + } else { + Err(Ics02Error::consensus_state_not_found(client_id.clone(), height)) + } + } + + fn prev_consensus_state( + &self, + client_id: &ClientId, + height: Height, + ) -> Result, Ics02Error> { + // search key + let client_consensus_state_path = ClientConsensusStatePath { + client_id: client_id.clone(), + epoch: height.revision_number(), + height: height.revision_height(), + } + .to_string() + .as_bytes() + .to_vec(); + + if >::contains_key(client_consensus_state_path.clone()) { + let values = >::get(client_consensus_state_path.clone()); + let any_consensus_state = AnyConsensusState::decode_vec(&*values).unwrap(); + Ok(Some(any_consensus_state)) + } else { + Err(Ics02Error::consensus_state_not_found(client_id.clone(), height)) + } + } + + fn host_height(&self) -> Height { + let block_number = format!("{:?}", >::block_number()); + let current_height: u64 = block_number.parse().unwrap_or_default(); + Height::new(REVISION_NUMBER, current_height).expect("Contruct Heigjt Never failed") + } + + fn host_consensus_state(&self, _height: Height) -> Result { + todo!() + } + + fn pending_host_consensus_state(&self) -> Result { + todo!() + } + + fn client_counter(&self) -> Result { + Ok(>::get()) + } +} + +impl ClientKeeper for Context { + fn store_client_type( + &mut self, + client_id: ClientId, + client_type: ClientType, + ) -> Result<(), Ics02Error> { + let client_type_path = ClientTypePath(client_id.clone()).to_string().as_bytes().to_vec(); + let client_type = client_type.as_str().encode(); + >::insert(client_type_path, client_type); + Ok(()) + } + + fn store_client_state( + &mut self, + client_id: ClientId, + client_state: AnyClientState, + ) -> Result<(), Ics02Error> { + let client_state_path = ClientStatePath(client_id.clone()).to_string().as_bytes().to_vec(); + + let data = client_state.encode_vec().map_err(|_| Ics02Error::implementation_specific())?; + // store client states key-value + >::insert(client_state_path.clone(), data); + + Ok(()) + } + + fn store_consensus_state( + &mut self, + client_id: ClientId, + height: Height, + consensus_state: AnyConsensusState, + ) -> Result<(), Ics02Error> { + // store key + let client_consensus_state_path = ClientConsensusStatePath { + client_id: client_id.clone(), + epoch: height.revision_number(), + height: height.revision_height(), + } + .to_string() + .as_bytes() + .to_vec(); + + // store value + let consensus_state = consensus_state + .encode_vec() + .map_err(|_| Ics02Error::implementation_specific())?; + // store client_consensus_state path as key, consensus_state as value + >::insert(client_consensus_state_path, consensus_state); + + Ok(()) + } + + fn increase_client_counter(&mut self) { + let ret = >::try_mutate(|val| -> Result<(), Ics02Error> { + let new = val.checked_add(1).expect("Never Overflow"); + *val = new; + Ok(()) + }); + } + + fn store_update_time( + &mut self, + client_id: ClientId, + height: Height, + timestamp: Timestamp, + ) -> Result<(), Ics02Error> { + let encode_timestamp = serde_json::to_string(×tamp) + .map_err(|_| Ics02Error::implementation_specific())? + .as_bytes() + .to_vec(); + + >::insert( + client_id.as_bytes(), + height.encode_vec().map_err(|_| Ics02Error::implementation_specific())?, + encode_timestamp, + ); + + Ok(()) + } + + fn store_update_height( + &mut self, + client_id: ClientId, + height: Height, + host_height: Height, + ) -> Result<(), Ics02Error> { + >::insert( + client_id.as_bytes(), + height.encode_vec().map_err(|_| Ics02Error::implementation_specific())?, + host_height.encode_vec().map_err(|_| Ics02Error::implementation_specific())?, + ); + + Ok(()) + } +} diff --git a/frame/ibc/src/module/core/ics03_connection.rs b/frame/ibc/src/module/core/ics03_connection.rs new file mode 100644 index 0000000000000..82be9973e5032 --- /dev/null +++ b/frame/ibc/src/module/core/ics03_connection.rs @@ -0,0 +1,111 @@ +use crate::*; + +use crate::context::Context; +use log::{error, info, trace, warn}; + +use ibc::{ + core::{ + ics02_client::{ + client_consensus::AnyConsensusState, client_state::AnyClientState, + context::ClientReader, + }, + ics03_connection::{ + connection::ConnectionEnd, + context::{ConnectionKeeper, ConnectionReader}, + error::Error as Ics03Error, + }, + ics23_commitment::commitment::CommitmentPrefix, + ics24_host::{ + identifier::{ClientId, ConnectionId}, + path::{ClientConnectionsPath, ConnectionsPath}, + }, + }, + Height, +}; + +impl ConnectionReader for Context { + fn connection_end(&self, conn_id: &ConnectionId) -> Result { + let connections_path = ConnectionsPath(conn_id.clone()).to_string().as_bytes().to_vec(); + + if >::contains_key(&connections_path) { + let data = >::get(&connections_path); + ConnectionEnd::decode_vec(&*data).map_err(|_| Ics03Error::implementation_specific()) + } else { + Err(Ics03Error::connection_mismatch(conn_id.clone())) + } + } + + fn client_state(&self, client_id: &ClientId) -> Result { + ClientReader::client_state(self, client_id).map_err(Ics03Error::ics02_client) + } + + fn host_current_height(&self) -> Height { + let block_number = format!("{:?}", >::block_number()); + let current_height: u64 = block_number.parse().unwrap_or_default(); + >::put(current_height); + Height::new(REVISION_NUMBER, current_height).expect("Contruct Height Never faild") + } + + fn host_oldest_height(&self) -> Height { + let height = >::get(); + Height::new(REVISION_NUMBER, height).expect("get host oldest height Never faild") + } + + fn commitment_prefix(&self) -> CommitmentPrefix { + "ibc".as_bytes().to_vec().try_into().unwrap_or_default() + } + + fn client_consensus_state( + &self, + client_id: &ClientId, + height: Height, + ) -> Result { + ClientReader::consensus_state(self, client_id, height).map_err(Ics03Error::ics02_client) + } + + fn host_consensus_state(&self, _height: Height) -> Result { + todo!() + } + + fn connection_counter(&self) -> Result { + Ok(>::get()) + } +} + +impl ConnectionKeeper for Context { + fn store_connection( + &mut self, + connection_id: ConnectionId, + connection_end: &ConnectionEnd, + ) -> Result<(), Ics03Error> { + let connections_path = + ConnectionsPath(connection_id.clone()).to_string().as_bytes().to_vec(); + let data = + connection_end.encode_vec().map_err(|_| Ics03Error::implementation_specific())?; + + // store connection end + >::insert(connections_path, data); + + Ok(()) + } + + fn store_connection_to_client( + &mut self, + connection_id: ConnectionId, + client_id: &ClientId, + ) -> Result<(), Ics03Error> { + let client_connection_paths = + ClientConnectionsPath(client_id.clone()).to_string().as_bytes().to_vec(); + + >::insert(client_connection_paths, connection_id.as_bytes().to_vec()); + Ok(()) + } + + fn increase_connection_counter(&mut self) { + let ret = >::try_mutate(|val| -> Result<(), Ics03Error> { + let new = val.checked_add(1).expect("Never Overflow"); + *val = new; + Ok(()) + }); + } +} diff --git a/frame/ibc/src/module/core/ics04_channel.rs b/frame/ibc/src/module/core/ics04_channel.rs new file mode 100644 index 0000000000000..1ec8d58343d09 --- /dev/null +++ b/frame/ibc/src/module/core/ics04_channel.rs @@ -0,0 +1,524 @@ +use crate::*; +use core::{str::FromStr, time::Duration}; +use log::{error, info, trace, warn}; + +use crate::context::Context; +use ibc::{ + core::{ + ics02_client::{ + client_consensus::AnyConsensusState, client_state::AnyClientState, + context::ClientReader, + }, + ics03_connection::{ + connection::ConnectionEnd, context::ConnectionReader, error::Error as ICS03Error, + }, + ics04_channel::{ + channel::ChannelEnd, + commitment::{ + AcknowledgementCommitment as IbcAcknowledgementCommitment, + PacketCommitment as IbcPacketCommitment, + }, + context::{ChannelKeeper, ChannelReader}, + error::Error as Ics04Error, + packet::{Receipt, Sequence}, + }, + ics05_port::{context::PortReader, error::Error as Ics05Error}, + ics24_host::{ + identifier::{ChannelId, ClientId, ConnectionId, PortId}, + path::{ + AcksPath, ChannelEndsPath, CommitmentsPath, ConnectionsPath, ReceiptsPath, + SeqAcksPath, SeqRecvsPath, SeqSendsPath, + }, + Path, + }, + ics26_routing::context::ModuleId, + }, + timestamp::Timestamp, + Height, +}; + +impl ChannelReader for Context { + fn channel_end(&self, port_channel_id: &(PortId, ChannelId)) -> Result { + let channel_end_path = + ChannelEndsPath(port_channel_id.0.clone(), port_channel_id.1.clone()) + .to_string() + .as_bytes() + .to_vec(); + + let data = >::get(channel_end_path); + + ChannelEnd::decode_vec(&*data).map_err(|_| { + Ics04Error::channel_not_found(port_channel_id.clone().0, port_channel_id.clone().1) + }) + } + + fn connection_end(&self, connection_id: &ConnectionId) -> Result { + ConnectionReader::connection_end(self, connection_id).map_err(Ics04Error::ics03_connection) + } + + /// Returns the `ChannelsConnection` for the given identifier `conn_id`. + fn connection_channels( + &self, + conn_id: &ConnectionId, + ) -> Result, Ics04Error> { + // store key + let connections_path = ConnectionsPath(conn_id.clone()).to_string().as_bytes().to_vec(); + + if >::contains_key(&connections_path) { + let channel_ends_paths = >::get(&connections_path); + + let mut result = vec![]; + + for item in channel_ends_paths.into_iter() { + let raw_path = + String::from_utf8(item).map_err(|_| Ics04Error::implementation_specific())?; + // decode key + let path = + Path::from_str(&raw_path).map_err(|_| Ics04Error::implementation_specific())?; + + if let Path::ChannelEnds(channel_ends_path) = path { + let ChannelEndsPath(port_id, channel_id) = channel_ends_path; + result.push((port_id, channel_id)); + } + } + + Ok(result) + } else { + Err(Ics04Error::connection_not_open(conn_id.clone())) + } + } + + fn client_state(&self, client_id: &ClientId) -> Result { + ClientReader::client_state(self, client_id) + .map_err(|_| Ics04Error::implementation_specific()) + } + + fn client_consensus_state( + &self, + client_id: &ClientId, + height: Height, + ) -> Result { + ClientReader::consensus_state(self, client_id, height) + .map_err(|_| Ics04Error::implementation_specific()) + } + + fn get_next_sequence_send( + &self, + port_channel_id: &(PortId, ChannelId), + ) -> Result { + let seq_sends_path = SeqSendsPath(port_channel_id.0.clone(), port_channel_id.1.clone()) + .to_string() + .as_bytes() + .to_vec(); + + if >::contains_key(&seq_sends_path) { + let sequence = >::get(&seq_sends_path); + Ok(Sequence::from(sequence)) + } else { + Err(Ics04Error::missing_next_send_seq(port_channel_id.clone())) + } + } + + fn get_next_sequence_recv( + &self, + port_channel_id: &(PortId, ChannelId), + ) -> Result { + let seq_recvs_path = SeqRecvsPath(port_channel_id.0.clone(), port_channel_id.1.clone()) + .to_string() + .as_bytes() + .to_vec(); + + if >::contains_key(&seq_recvs_path) { + let sequence = >::get(&seq_recvs_path); + + Ok(Sequence::from(sequence)) + } else { + Err(Ics04Error::missing_next_recv_seq(port_channel_id.clone())) + } + } + + fn get_next_sequence_ack( + &self, + port_channel_id: &(PortId, ChannelId), + ) -> Result { + let seq_acks_path = SeqAcksPath(port_channel_id.0.clone(), port_channel_id.1.clone()) + .to_string() + .as_bytes() + .to_vec(); + + if >::contains_key(&seq_acks_path) { + let sequence = >::get(&seq_acks_path); + + Ok(Sequence::from(sequence)) + } else { + Err(Ics04Error::missing_next_ack_seq(port_channel_id.clone())) + } + } + + /// Returns the `PacketCommitment` for the given identifier `(PortId, ChannelId, Sequence)`. + fn get_packet_commitment( + &self, + key: &(PortId, ChannelId, Sequence), + ) -> Result { + let packet_commitments_path = CommitmentsPath { + port_id: key.0.clone(), + channel_id: key.1.clone(), + sequence: key.2.clone(), + } + .to_string() + .as_bytes() + .to_vec(); + + if >::contains_key(&packet_commitments_path) { + let data = >::get(&packet_commitments_path); + + let packet_commitment = IbcPacketCommitment::from(data); + + Ok(packet_commitment) + } else { + Err(Ics04Error::packet_commitment_not_found(key.2)) + } + } + + fn get_packet_receipt( + &self, + key: &(PortId, ChannelId, Sequence), + ) -> Result { + let packet_receipt_path = ReceiptsPath { + port_id: key.0.clone(), + channel_id: key.1.clone(), + sequence: key.2.clone(), + } + .to_string() + .as_bytes() + .to_vec(); + + if >::contains_key(&packet_receipt_path) { + let data = >::get(&packet_receipt_path); + let data = + String::from_utf8(data).map_err(|_| Ics04Error::implementation_specific())?; + let data = match data.as_ref() { + "Ok" => Receipt::Ok, + _ => unreachable!(), + }; + Ok(data) + } else { + Err(Ics04Error::packet_receipt_not_found(key.2)) + } + } + + /// Returns the `Acknowledgements` for the given identifier `(PortId, ChannelId, Sequence)`. + fn get_packet_acknowledgement( + &self, + key: &(PortId, ChannelId, Sequence), + ) -> Result { + let acks_path = + AcksPath { port_id: key.0.clone(), channel_id: key.1.clone(), sequence: key.2.clone() } + .to_string() + .as_bytes() + .to_vec(); + + if >::contains_key(&acks_path) { + let data = >::get(&acks_path); + + let acknowledgement = IbcAcknowledgementCommitment::from(data); + Ok(acknowledgement) + } else { + Err(Ics04Error::packet_acknowledgement_not_found(key.2)) + } + } + + /// A hashing function for packet commitments + fn hash(&self, value: Vec) -> Vec { + sp_io::hashing::sha2_256(&value).to_vec() + } + + /// Returns the current height of the local chain. + fn host_height(&self) -> Height { + //todo this can improve + let block_number = format!("{:?}", >::block_number()); + let current_height: u64 = block_number.parse().unwrap_or_default(); + Height::new(REVISION_NUMBER, current_height).expect("Contruct Height Never Faild") + } + + /// Returns the current timestamp of the local chain. + fn host_timestamp(&self) -> Timestamp { + use frame_support::traits::UnixTime; + let time = T::TimeProvider::now(); + + Timestamp::from_nanoseconds(time.as_nanos() as u64).expect("Convert Timestamp Never Faild") + } + + /// Returns the `AnyConsensusState` for the given identifier `height`. + fn host_consensus_state(&self, height: Height) -> Result { + ConnectionReader::host_consensus_state(self, height).map_err(Ics04Error::ics03_connection) + } + + fn pending_host_consensus_state(&self) -> Result { + ClientReader::pending_host_consensus_state(self) + .map_err(|e| Ics04Error::ics03_connection(ICS03Error::ics02_client(e))) + } + + /// Returns the `ClientProcessedTimes` for the given identifier `client_id` & `height`. + fn client_update_time( + &self, + client_id: &ClientId, + height: Height, + ) -> Result { + if >::contains_key( + client_id.as_bytes(), + height.encode_vec().map_err(|_| Ics04Error::implementation_specific())?, + ) { + let time = >::get( + client_id.as_bytes(), + height.encode_vec().map_err(|_| Ics04Error::implementation_specific())?, + ); + let timestamp = + String::from_utf8(time).map_err(|_| Ics04Error::implementation_specific())?; + let time: Timestamp = serde_json::from_str(×tamp) + .map_err(|_| Ics04Error::implementation_specific())?; + Ok(time) + } else { + Err(Ics04Error::processed_time_not_found(client_id.clone(), height)) + } + } + + fn client_update_height( + &self, + client_id: &ClientId, + height: Height, + ) -> Result { + if >::contains_key( + client_id.as_bytes(), + height.encode_vec().map_err(|_| Ics04Error::implementation_specific())?, + ) { + let host_height = >::get( + client_id.as_bytes(), + height.encode_vec().map_err(|_| Ics04Error::implementation_specific())?, + ); + Height::decode(&mut &host_height[..]).map_err(|_| Ics04Error::implementation_specific()) + } else { + Err(Ics04Error::processed_height_not_found(client_id.clone(), height)) + } + } + + /// Returns a counter on the number of channel ids have been created thus far. + /// The value of this counter should increase only via method + /// `ChannelKeeper::increase_channel_counter`. + fn channel_counter(&self) -> Result { + Ok( as Store>::ChannelCounter::get()) + } + + fn max_expected_time_per_block(&self) -> Duration { + Duration::from_secs(6) + } +} + +impl ChannelKeeper for Context { + fn store_packet_commitment( + &mut self, + key: (PortId, ChannelId, Sequence), + commitment: IbcPacketCommitment, + ) -> Result<(), Ics04Error> { + let packet_commitments_path = CommitmentsPath { + port_id: key.0.clone(), + channel_id: key.1.clone(), + sequence: key.2.clone(), + } + .to_string() + .as_bytes() + .to_vec(); + + // insert packet commitment key-value + >::insert(packet_commitments_path, commitment.into_vec()); + + Ok(()) + } + + fn delete_packet_commitment( + &mut self, + key: (PortId, ChannelId, Sequence), + ) -> Result<(), Ics04Error> { + let packet_commitments_path = CommitmentsPath { + port_id: key.0.clone(), + channel_id: key.1.clone(), + sequence: key.2.clone(), + } + .to_string() + .as_bytes() + .to_vec(); + + // delete packet commitment + >::remove(&packet_commitments_path); + + Ok(()) + } + + fn store_packet_receipt( + &mut self, + key: (PortId, ChannelId, Sequence), + receipt: Receipt, + ) -> Result<(), Ics04Error> { + let packet_receipt_path = ReceiptsPath { + port_id: key.0.clone(), + channel_id: key.1.clone(), + sequence: key.2.clone(), + } + .to_string() + .as_bytes() + .to_vec(); + + let receipt = match receipt { + Receipt::Ok => "Ok".as_bytes().to_vec(), + }; + + >::insert(packet_receipt_path, receipt); + + Ok(()) + } + + fn store_packet_acknowledgement( + &mut self, + key: (PortId, ChannelId, Sequence), + ack_commitment: IbcAcknowledgementCommitment, + ) -> Result<(), Ics04Error> { + let acks_path = + AcksPath { port_id: key.0.clone(), channel_id: key.1.clone(), sequence: key.2.clone() } + .to_string() + .as_bytes() + .to_vec(); + + // store packet acknowledgement key-value + >::insert(&acks_path, ack_commitment.into_vec()); + + Ok(()) + } + + fn delete_packet_acknowledgement( + &mut self, + key: (PortId, ChannelId, Sequence), + ) -> Result<(), Ics04Error> { + let acks_path = + AcksPath { port_id: key.0.clone(), channel_id: key.1.clone(), sequence: key.2.clone() } + .to_string() + .as_bytes() + .to_vec(); + + // remove acknowledgements + >::remove(&acks_path); + + Ok(()) + } + + fn store_connection_channels( + &mut self, + conn_id: ConnectionId, + port_channel_id: &(PortId, ChannelId), + ) -> Result<(), Ics04Error> { + // store key + let connections_path = ConnectionsPath(conn_id.clone()).to_string().as_bytes().to_vec(); + + // store value + let channel_ends_path = + ChannelEndsPath(port_channel_id.0.clone(), port_channel_id.1.clone()) + .to_string() + .as_bytes() + .to_vec(); + + if >::contains_key(&connections_path) { + // if connection_id exist + >::try_mutate( + &connections_path, + |val| -> Result<(), Ics04Error> { + val.push(channel_ends_path.clone()); + Ok(()) + }, + ) + .expect("channels Connection mutate Error") + } else { + >::insert(connections_path, vec![channel_ends_path]); + } + + Ok(()) + } + + /// Stores the given channel_end at a path associated with the port_id and channel_id. + fn store_channel( + &mut self, + port_channel_id: (PortId, ChannelId), + channel_end: &ChannelEnd, + ) -> Result<(), Ics04Error> { + let channel_end_path = + ChannelEndsPath(port_channel_id.0.clone(), port_channel_id.1.clone()) + .to_string() + .as_bytes() + .to_vec(); + let channel_end = + channel_end.encode_vec().map_err(|_| Ics04Error::implementation_specific())?; + + // store channels key-value + >::insert(channel_end_path, channel_end); + + Ok(()) + } + + fn store_next_sequence_send( + &mut self, + port_channel_id: (PortId, ChannelId), + seq: Sequence, + ) -> Result<(), Ics04Error> { + let seq_sends_path = SeqSendsPath(port_channel_id.0.clone(), port_channel_id.1.clone()) + .to_string() + .as_bytes() + .to_vec(); + + let sequence = u64::from(seq); + + >::insert(seq_sends_path, sequence); + + Ok(()) + } + + fn store_next_sequence_recv( + &mut self, + port_channel_id: (PortId, ChannelId), + seq: Sequence, + ) -> Result<(), Ics04Error> { + let seq_recvs_path = SeqRecvsPath(port_channel_id.0.clone(), port_channel_id.1.clone()) + .to_string() + .as_bytes() + .to_vec(); + let sequence = u64::from(seq); + + >::insert(seq_recvs_path, sequence); + + Ok(()) + } + + fn store_next_sequence_ack( + &mut self, + port_channel_id: (PortId, ChannelId), + seq: Sequence, + ) -> Result<(), Ics04Error> { + let seq_acks_path = SeqAcksPath(port_channel_id.0.clone(), port_channel_id.1.clone()) + .to_string() + .as_bytes() + .to_vec(); + let sequence = u64::from(seq); + + >::insert(seq_acks_path, sequence); + + Ok(()) + } + + /// Called upon channel identifier creation (Init or Try message processing). + /// Increases the counter which keeps track of how many channels have been created. + /// Should never fail. + fn increase_channel_counter(&mut self) { + let ret = >::try_mutate(|val| -> Result<(), Ics04Error> { + let new = val.checked_add(1).expect("Never Overflow"); + *val = new; + Ok(()) + }); + } +} diff --git a/frame/ibc/src/module/core/ics05_port.rs b/frame/ibc/src/module/core/ics05_port.rs new file mode 100644 index 0000000000000..923b7fcff8b3c --- /dev/null +++ b/frame/ibc/src/module/core/ics05_port.rs @@ -0,0 +1,24 @@ +use crate::*; +use log::trace; + +use crate::context::Context; +use ibc::{ + applications::transfer::{ + MODULE_ID_STR as TRANSFER_MODULE_ID, PORT_ID_STR as TRANSFER_PORT_ID, + }, + core::{ + ics05_port::{context::PortReader, error::Error as ICS05Error}, + ics24_host::identifier::PortId, + ics26_routing::context::ModuleId, + }, +}; + +impl PortReader for Context { + fn lookup_module_by_port(&self, port_id: &PortId) -> Result { + match port_id.as_str() { + TRANSFER_PORT_ID => Ok(ModuleId::from_str(TRANSFER_MODULE_ID) + .map_err(|_| ICS05Error::module_not_found(port_id.clone()))?), + _ => Err(ICS05Error::module_not_found(port_id.clone())), + } + } +} diff --git a/frame/ibc/src/module/core/ics24_host.rs b/frame/ibc/src/module/core/ics24_host.rs new file mode 100644 index 0000000000000..725a253e9d007 --- /dev/null +++ b/frame/ibc/src/module/core/ics24_host.rs @@ -0,0 +1,254 @@ +use crate::{alloc::string::ToString, from_channel_id_to_vec, Config, Event, REVISION_NUMBER}; +use alloc::string::String; +use ibc::{ + core::{ + ics02_client::{client_type::ClientType as IbcClientType, height::Height as IbcHeight}, + ics04_channel::packet::{Packet as IbcPacket, Sequence as IbcSequence}, + ics24_host::{ + error::ValidationError, + identifier::{ + ChainId as IbcChainId, ChannelId as IbcChannelId, ClientId as IbcClientId, + ConnectionId as IbcConnectionId, PortId as IbcPortId, + }, + }, + }, + timestamp::Timestamp as IbcTimestamp, +}; +use sp_std::{str::FromStr, vec::Vec}; + +use codec::{Decode, Encode}; +use scale_info::TypeInfo; + +use sp_runtime::RuntimeDebug; + +use flex_error::{define_error, DisplayOnly, TraceError}; +use ibc::core::ics04_channel::timeout::TimeoutHeight; +use tendermint_proto::Error as TendermintError; + +define_error! { + #[derive(Debug, PartialEq, Eq)] + Error { + InvalidFromUtf8 + [DisplayOnly] + | _ | { "invalid from utf8 error" }, + InvalidDecode + [DisplayOnly] + | _ | { "invalid decode error" }, + ParseTimestampFailed + [DisplayOnly] + | _ | { "invalid parse timestamp error" }, + ValidationFailed + [DisplayOnly] + | _ | { "invalid validation error"}, + InvalidChainId + [DisplayOnly] + |_| { "invalid chain id error" }, + } +} + +/// ibc-rs' `PortId` representation in substrate +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct PortId(pub Vec); + +impl From for PortId { + fn from(value: IbcPortId) -> Self { + let value = value.as_str().as_bytes().to_vec(); + Self(value) + } +} + +impl From for IbcPortId { + fn from(value: PortId) -> Self { + let value = String::from_utf8(value.0).expect("convert Never faild"); + IbcPortId::from_str(&value).expect("convert Never faild") + } +} + +/// ibc-rs' `ChannelId` representation in substrate +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct ChannelId(pub Vec); + +impl From for ChannelId { + fn from(value: IbcChannelId) -> Self { + let value = from_channel_id_to_vec(value); + Self(value) + } +} + +impl From for IbcChannelId { + fn from(value: ChannelId) -> Self { + let value = String::from_utf8(value.0).expect("convert Never faild"); + Self::from_str(&value).expect("convert Never faild") + } +} + +/// ibc-rs' `Height` representation in substrate +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct Height { + /// Previously known as "epoch" + pub revision_number: u64, + + /// The height of a block + pub revision_height: u64, +} + +impl From for Height { + fn from(ibc_height: IbcHeight) -> Self { + Height::new(ibc_height.revision_number(), ibc_height.revision_height()) + } +} + +impl From for IbcHeight { + fn from(height: Height) -> Self { + IbcHeight::new(REVISION_NUMBER, height.revision_height) + .expect("Contruct IbcHeight Never faild") + } +} + +impl Height { + pub fn new(revision_number: u64, revision_height: u64) -> Self { + Self { revision_number, revision_height } + } +} + +/// ibc-rs' `ClientType` representation in substrate +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub enum ClientType { + Tendermint, + Grandpa, +} + +impl From for ClientType { + fn from(value: IbcClientType) -> Self { + match value { + IbcClientType::Tendermint => ClientType::Tendermint, + _ => unreachable!(), + } + } +} + +impl ClientType { + pub fn to_ibc_client_type(self) -> IbcClientType { + match self { + ClientType::Tendermint => IbcClientType::Tendermint, + _ => unreachable!(), + } + } +} + +/// ibc-rs' `ClientId` representation in substrate +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct ClientId(pub Vec); + +impl From for ClientId { + fn from(value: IbcClientId) -> Self { + let value = value.as_str().as_bytes().to_vec(); + Self(value) + } +} + +impl From for IbcClientId { + fn from(value: ClientId) -> Self { + let value = String::from_utf8(value.0).expect("convert Never faild"); + IbcClientId::from_str(&value).expect("convert Never faild") + } +} + +/// ibc-rs' `ConnectionId` representation in substrate +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct ConnectionId(pub Vec); + +impl From for ConnectionId { + fn from(value: IbcConnectionId) -> Self { + let value = value.as_str().as_bytes().to_vec(); + Self(value) + } +} + +impl From for IbcConnectionId { + fn from(value: ConnectionId) -> Self { + let value = String::from_utf8(value.0).expect("convert Never faild"); + IbcConnectionId::from_str(&value).expect("convert Never faild") + } +} + +/// ibc-rs' `Timestamp` representation in substrate +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct Timestamp { + pub time: Vec, +} + +impl From for Timestamp { + fn from(val: IbcTimestamp) -> Self { + Self { time: val.nanoseconds().to_string().as_bytes().to_vec() } + } +} + +impl From for IbcTimestamp { + fn from(value: Timestamp) -> Self { + let value = String::from_utf8(value.time).expect("convert Never faild"); + Self::from_str(&value).expect("convert Never faild") + } +} + +/// ibc-rs' `Sequence` representation in substrate +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct Sequence(u64); + +impl From for Sequence { + fn from(val: IbcSequence) -> Self { + Self(u64::from(val)) + } +} + +impl From for IbcSequence { + fn from(val: Sequence) -> Self { + IbcSequence::from(val.0) + } +} + +/// ibc-rs' `Packet` representation in substrate +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct Packet { + pub sequence: Sequence, + pub source_port: PortId, + pub source_channel: ChannelId, + pub destination_port: PortId, + pub destination_channel: ChannelId, + pub data: Vec, + pub timeout_height: Height, + pub timeout_timestamp: Timestamp, +} + +impl From for Packet { + fn from(val: IbcPacket) -> Self { + Self { + sequence: val.sequence.into(), + source_port: val.source_port.into(), + source_channel: val.source_channel.into(), + destination_port: val.destination_port.into(), + destination_channel: val.destination_channel.into(), + data: val.data, + timeout_height: match val.timeout_height { + TimeoutHeight::Never => Height::new(REVISION_NUMBER, u64::MAX), + TimeoutHeight::At(value) => value.into(), + }, + timeout_timestamp: val.timeout_timestamp.into(), + } + } +} + +impl From for IbcPacket { + fn from(value: Packet) -> Self { + Self { + sequence: value.sequence.into(), + source_port: value.source_port.into(), + source_channel: value.source_channel.into(), + destination_port: value.destination_port.into(), + destination_channel: value.destination_channel.into(), + data: value.data, + timeout_height: TimeoutHeight::At(value.timeout_height.into()), + timeout_timestamp: value.timeout_timestamp.into(), + } + } +} diff --git a/frame/ibc/src/module/core/ics26_routing.rs b/frame/ibc/src/module/core/ics26_routing.rs new file mode 100644 index 0000000000000..09839a2565b90 --- /dev/null +++ b/frame/ibc/src/module/core/ics26_routing.rs @@ -0,0 +1,65 @@ +use crate::{context::Context, *}; +use alloc::{ + borrow::{Borrow, Cow, ToOwned}, + collections::BTreeMap, + fmt::format, + sync::Arc, +}; +use core::fmt::Formatter; +use ibc::core::ics26_routing::context::{Ics26Context, Module, ModuleId, RouterBuilder}; +use log::{error, info, trace, warn}; +use scale_info::TypeInfo; + +#[derive(Default)] +pub struct SubRouterBuilder(Router); + +impl RouterBuilder for SubRouterBuilder { + type Router = Router; + + fn add_route(mut self, module_id: ModuleId, module: impl Module) -> Result { + match self.0 .0.insert(module_id, Arc::new(module)) { + None => Ok(self), + Some(_) => Err("Duplicate module_id".to_owned()), + } + } + + fn build(self) -> Self::Router { + self.0 + } +} + +#[derive(Default, Clone)] +pub struct Router(BTreeMap>); + +impl Debug for Router { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + let mut keys = vec![]; + for (key, _) in self.0.iter() { + keys.push(format!("{}", key)); + } + + write!(f, "MockRouter(BTreeMap(key({:?})", keys.join(",")) + } +} + +impl ibc::core::ics26_routing::context::Router for Router { + fn get_route_mut(&mut self, module_id: &impl Borrow) -> Option<&mut dyn Module> { + self.0.get_mut(module_id.borrow()).and_then(Arc::get_mut) + } + + fn has_route(&self, module_id: &impl Borrow) -> bool { + self.0.get(module_id.borrow()).is_some() + } +} + +impl Ics26Context for Context { + type Router = Router; + + fn router(&self) -> &Self::Router { + &self.router + } + + fn router_mut(&mut self) -> &mut Self::Router { + &mut self.router + } +} diff --git a/frame/ibc/src/module/core/mod.rs b/frame/ibc/src/module/core/mod.rs new file mode 100644 index 0000000000000..866ed4450fafa --- /dev/null +++ b/frame/ibc/src/module/core/mod.rs @@ -0,0 +1,6 @@ +pub mod ics02_client; +pub mod ics03_connection; +pub mod ics04_channel; +pub mod ics05_port; +pub mod ics24_host; +pub mod ics26_routing; diff --git a/frame/ibc/src/module/mod.rs b/frame/ibc/src/module/mod.rs new file mode 100644 index 0000000000000..250d43e9d1bfe --- /dev/null +++ b/frame/ibc/src/module/mod.rs @@ -0,0 +1,3 @@ +pub mod applications; +pub mod core; +pub mod relayer; diff --git a/frame/ibc/src/module/relayer/mod.rs b/frame/ibc/src/module/relayer/mod.rs new file mode 100644 index 0000000000000..9f520b96f15f9 --- /dev/null +++ b/frame/ibc/src/module/relayer/mod.rs @@ -0,0 +1,47 @@ +use crate::{ + alloc::string::ToString, context::Context, utils::host_height, Config, REVISION_NUMBER, +}; +use ibc::{ + core::{ + ics02_client::{client_state::AnyClientState, context::ClientReader, header::AnyHeader}, + ics24_host::identifier::ClientId, + ics26_routing::handler::{deliver, MsgReceipt}, + }, + events::IbcEvent, + relayer::ics18_relayer::{context::Ics18Context, error::Error as ICS18Error}, + signer::Signer, + Height, +}; +use ibc_proto::google::protobuf::Any; +use scale_info::prelude::{vec, vec::Vec}; + +impl Ics18Context for Context { + fn query_latest_height(&self) -> Height { + let revision_height = host_height::(); + Height::new(REVISION_NUMBER, revision_height).expect(&REVISION_NUMBER.to_string()) + } + + fn query_client_full_state(&self, client_id: &ClientId) -> Option { + // Forward call to Ics2. + ClientReader::client_state(self, client_id).ok() + } + + fn query_latest_header(&self) -> Option { + todo!() + } + + fn send(&mut self, msgs: Vec) -> Result, ICS18Error> { + // Forward call to Ics26 delivery method. + let mut all_events = vec![]; + for msg in msgs { + let MsgReceipt { mut events, .. } = + deliver(self, msg).map_err(ICS18Error::transaction_failed)?; + all_events.append(&mut events); + } + Ok(all_events) + } + + fn signer(&self) -> Signer { + "0CDA3F47EF3C4906693B170EF650EB968C5F4B2C".parse().unwrap() + } +} diff --git a/frame/ibc/src/tests.rs b/frame/ibc/src/tests.rs new file mode 100644 index 0000000000000..c22f64d367343 --- /dev/null +++ b/frame/ibc/src/tests.rs @@ -0,0 +1,417 @@ +use super::*; +use crate::{mock::*, Context}; +use core::str::FromStr; + +use ibc::{ + applications::transfer::{context::Ics20Context, error::Error as ICS20Error}, + core::{ + ics02_client::{ + client_consensus::AnyConsensusState, + client_state::AnyClientState, + client_type::ClientType, + context::{ClientKeeper, ClientReader}, + error::Error as ICS02Error, + }, + ics03_connection::{ + connection::{ConnectionEnd, State}, + context::{ConnectionKeeper, ConnectionReader}, + error::Error as ICS03Error, + }, + ics04_channel::{ + channel::ChannelEnd, + context::{ChannelKeeper, ChannelReader}, + error::Error as ICS04Error, + packet::Sequence, + }, + ics23_commitment::commitment::CommitmentRoot, + ics24_host::identifier::{ChainId, ChannelId, ClientId, ConnectionId, PortId}, + }, + timestamp::Timestamp, + Height, +}; + +// test store and read client-type +#[test] +fn test_store_client_type_ok() { + let gp_client_type = ClientType::Tendermint; + let gp_client_id = ClientId::default(); + + let mut context: Context = Context::new(); + + new_test_ext().execute_with(|| { + assert!(context.store_client_type(gp_client_id.clone(), gp_client_type).is_ok()); + + let ret = context.client_type(&gp_client_id).unwrap(); + + assert_eq!(ret, gp_client_type); + }) +} + +#[test] +fn test_read_client_type_failed_by_supply_error_client_id() { + let gp_client_type = ClientType::Tendermint; + let gp_client_id = ClientId::new(gp_client_type, 0).unwrap(); + let gp_client_id_failed = ClientId::new(gp_client_type, 1).unwrap(); + let mut context: Context = Context::new(); + + new_test_ext().execute_with(|| { + assert!(context.store_client_type(gp_client_id.clone(), gp_client_type).is_ok()); + + let ret = context.client_type(&gp_client_id_failed).unwrap_err().to_string(); + + assert_eq!(ret, ICS02Error::client_not_found(gp_client_id_failed).to_string()); + }) +} + +#[test] +fn test_get_packet_commitment_state_ok() { + use ibc::core::ics04_channel::commitment::PacketCommitment; + + let mut context: Context = Context::new(); + + let range = (0..10).into_iter().collect::>(); + + let mut port_id_vec = vec![]; + let mut channel_id_vec = vec![]; + let mut sequence_vec = vec![]; + + for index in range.clone() { + port_id_vec.push(PortId::default()); + channel_id_vec.push(ChannelId::default()); + let sequence = Sequence::from(index as u64); + sequence_vec.push(sequence); + } + let com = PacketCommitment::from(vec![1, 2, 3]); + + new_test_ext().execute_with(|| { + for index in 0..range.len() { + assert!(context + .store_packet_commitment( + ( + port_id_vec[index].clone(), + channel_id_vec[index].clone(), + sequence_vec[index] + ), + com.clone(), + ) + .is_ok()); + } + }) +} + +#[test] +fn test_connection_ok() { + use codec::alloc::collections::HashMap; + + let mut input: HashMap = HashMap::new(); + + let connection_id0 = ConnectionId::default(); + let connection_end0 = ConnectionEnd::default(); + + let connection_id1 = ConnectionId::default(); + let connection_end1 = ConnectionEnd::default(); + + let connection_id2 = ConnectionId::default(); + let connection_end2 = ConnectionEnd::default(); + + input.insert(connection_id0.clone(), connection_end0.clone()); + input.insert(connection_id1.clone(), connection_end1.clone()); + input.insert(connection_id2.clone(), connection_end2.clone()); + + let mut context: Context = Context::new(); + new_test_ext().execute_with(|| { + assert_eq!( + ConnectionKeeper::store_connection( + &mut context, + connection_id0.clone(), + input.get(&connection_id0.clone()).unwrap() + ) + .is_ok(), + true + ); + + let ret = ConnectionReader::connection_end(&mut context, &connection_id0).unwrap(); + assert_eq!(ret, *input.get(&connection_id0.clone()).unwrap()); + + assert_eq!( + ConnectionKeeper::store_connection( + &mut context, + connection_id1.clone(), + input.get(&connection_id1.clone()).unwrap() + ) + .is_ok(), + true + ); + + assert_eq!( + ConnectionKeeper::store_connection( + &mut context, + connection_id2.clone(), + input.get(&connection_id2.clone()).unwrap() + ) + .is_ok(), + true + ); + }) +} + +#[test] +fn test_connection_fail() { + let connection_id0 = ConnectionId::default(); + let context: Context = Context::new(); + new_test_ext().execute_with(|| { + let ret = ConnectionReader::connection_end(&context, &connection_id0.clone()) + .unwrap_err() + .to_string(); + assert_eq!(ret, ICS03Error::connection_mismatch(connection_id0).to_string()); + }) +} + +#[test] +fn test_connection_client_ok() { + let gp_client_id = ClientId::default(); + let connection_id = ConnectionId::new(0); + let mut context: Context = Context::new(); + + new_test_ext().execute_with(|| { + assert!(context.store_connection_to_client(connection_id, &gp_client_id).is_ok()); + }) +} + +#[test] +fn test_delete_packet_acknowledgement_ok() { + use ibc::core::ics04_channel::commitment::AcknowledgementCommitment; + + let port_id = PortId::default(); + let channel_id = ChannelId::default(); + let sequence = Sequence::from(0); + let ack = AcknowledgementCommitment::from(vec![1, 2, 3]); + + let mut context: Context = Context::new(); + + new_test_ext().execute_with(|| { + assert!(context + .store_packet_acknowledgement( + (port_id.clone(), channel_id.clone(), sequence), + ack.clone() + ) + .is_ok()); + + assert!(context + .delete_packet_acknowledgement((port_id.clone(), channel_id.clone(), sequence)) + .is_ok()); + + let result = context + .get_packet_acknowledgement(&(port_id, channel_id, sequence)) + .unwrap_err() + .to_string(); + + assert_eq!(result, ICS04Error::packet_acknowledgement_not_found(sequence).to_string()); + }) +} + +#[test] +fn test_get_acknowledge_state() { + use ibc::core::ics04_channel::commitment::AcknowledgementCommitment; + let range = (0..10).into_iter().collect::>(); + + let mut port_id_vec = vec![]; + let mut channel_id_vec = vec![]; + let mut sequence_vec = vec![]; + let mut ack_vec = vec![]; + + let mut value_vec = vec![]; + + let mut context: Context = Context::new(); + + for index in 0..range.len() { + port_id_vec.push(PortId::default()); + channel_id_vec.push(ChannelId::default()); + let sequence = Sequence::from(index as u64); + sequence_vec.push(sequence); + ack_vec.push(AcknowledgementCommitment::from(vec![index as u8])); + value_vec.push(ChannelReader::hash(&context, vec![index as u8]).encode()); + } + + new_test_ext().execute_with(|| { + for index in 0..range.len() { + assert!(context + .store_packet_acknowledgement( + ( + port_id_vec[index].clone(), + channel_id_vec[index].clone(), + sequence_vec[index] + ), + ack_vec[index].clone() + ) + .is_ok()); + } + }) +} + +#[test] +fn test_store_connection_channles_ok() { + let connection_id = ConnectionId::default(); + let port_id = PortId::default(); + let channel_id = ChannelId::default(); + + let mut context: Context = Context::new(); + new_test_ext().execute_with(|| { + assert!(context + .store_connection_channels( + connection_id.clone(), + &(port_id.clone(), channel_id.clone()) + ) + .is_ok()); + + let result = context.connection_channels(&connection_id).unwrap(); + + assert_eq!(result.len(), 1); + + assert_eq!(result[0].0, port_id); + assert_eq!(result[0].1, channel_id); + }) +} + +#[test] +fn test_next_sequence_send_ok() { + let sequence_id = Sequence::from(0); + let port_channel = (PortId::default(), ChannelId::default()); + let mut context: Context = Context::new(); + + new_test_ext().execute_with(|| { + assert!(context.store_next_sequence_send(port_channel.clone(), sequence_id).is_ok()); + let result = context.get_next_sequence_send(&port_channel).unwrap(); + assert_eq!(result, sequence_id); + }) +} + +#[test] +fn test_read_conection_channels_failed_by_suppley_error_conneciton_id() { + let connection_id = ConnectionId::new(0); + let connection_id_failed = ConnectionId::new(1); + let port_id = PortId::from_str(String::from_str("port-0").unwrap().as_str()).unwrap(); + let channel_id = ChannelId::from_str(String::from_str("channel-0").unwrap().as_str()).unwrap(); + + let mut context: Context = Context::new(); + new_test_ext().execute_with(|| { + assert!(context + .store_connection_channels( + connection_id.clone(), + &(port_id.clone(), channel_id.clone()) + ) + .is_ok()); + + let result = context.connection_channels(&connection_id_failed).unwrap_err().to_string(); + + assert_eq!( + result, + ICS04Error::connection_not_open(connection_id_failed.clone()).to_string() + ); + }) +} + +#[test] +fn test_store_channel_ok() { + let port_id = PortId::default(); + let channel_id = ChannelId::default(); + let channel_end = ChannelEnd::default(); + + let mut context: Context = Context::new(); + + new_test_ext().execute_with(|| { + assert!(context + .store_channel((port_id.clone(), channel_id.clone()), &channel_end) + .is_ok()); + + let result = context.channel_end(&(port_id.clone(), channel_id.clone())).unwrap(); + + assert_eq!(result, channel_end); + }) +} + +#[test] + +fn test_next_sequence_send_fail() { + let port_channel = (PortId::default(), ChannelId::default()); + let context: Context = Context::new(); + + new_test_ext().execute_with(|| { + let result = context.get_next_sequence_send(&port_channel.clone()).unwrap_err().to_string(); + assert_eq!(result, ICS04Error::missing_next_send_seq(port_channel).to_string()); + }) +} + +#[test] +fn test_next_sequence_recv_ok() { + let sequence_id = Sequence::from(0); + let port_channel = (PortId::default(), ChannelId::default()); + let mut context: Context = Context::new(); + + new_test_ext().execute_with(|| { + assert!(context.store_next_sequence_recv(port_channel.clone(), sequence_id).is_ok()); + let result = context.get_next_sequence_recv(&port_channel).unwrap(); + assert_eq!(result, sequence_id); + }) +} + +#[test] +fn test_get_identified_channel_end() { + let range = (0..10).into_iter().collect::>(); + + let mut port_id_vec = vec![]; + let mut channel_id_vec = vec![]; + let channel_end_vec = vec![ChannelEnd::default(); range.len()]; + + for index in 0..range.len() { + port_id_vec.push(PortId::default()); + channel_id_vec.push(ChannelId::default()); + } + + let mut context: Context = Context::new(); + new_test_ext().execute_with(|| { + for index in 0..range.len() { + assert!(context + .store_channel( + (port_id_vec[index].clone(), channel_id_vec[index].clone()), + &channel_end_vec[index].clone() + ) + .is_ok()); + } + }) +} + +#[test] +fn test_next_sequence_recv_fail() { + let port_channel = (PortId::default(), ChannelId::default()); + let context: Context = Context::new(); + + new_test_ext().execute_with(|| { + let result = context.get_next_sequence_recv(&port_channel.clone()).unwrap_err().to_string(); + assert_eq!(result, ICS04Error::missing_next_recv_seq(port_channel).to_string()); + }) +} + +#[test] +fn test_next_sequence_ack_ok() { + let sequence_id = Sequence::from(0); + let port_channel = (PortId::default(), ChannelId::default()); + let mut context: Context = Context::new(); + + new_test_ext().execute_with(|| { + assert!(context.store_next_sequence_ack(port_channel.clone(), sequence_id).is_ok()); + let result = context.get_next_sequence_ack(&port_channel).unwrap(); + assert_eq!(result, sequence_id); + }) +} + +#[test] +fn test_next_sequence_ack_fail() { + let port_channel = (PortId::default(), ChannelId::default()); + let context: Context = Context::new(); + + new_test_ext().execute_with(|| { + let result = context.get_next_sequence_ack(&port_channel.clone()).unwrap_err().to_string(); + assert_eq!(result, ICS04Error::missing_next_ack_seq(port_channel).to_string()); + }) +} diff --git a/frame/ibc/src/traits.rs b/frame/ibc/src/traits.rs new file mode 100644 index 0000000000000..ef2209cd7a70d --- /dev/null +++ b/frame/ibc/src/traits.rs @@ -0,0 +1,10 @@ +use alloc::vec::Vec; + +/// A trait handling asset ID and name +pub trait AssetIdAndNameProvider { + type Err; + + fn try_get_asset_id(name: impl AsRef<[u8]>) -> Result; + + fn try_get_asset_name(asset_id: AssetId) -> Result, Self::Err>; +} diff --git a/frame/ibc/src/utils.rs b/frame/ibc/src/utils.rs new file mode 100644 index 0000000000000..eda1b8c82b813 --- /dev/null +++ b/frame/ibc/src/utils.rs @@ -0,0 +1,47 @@ +use crate::Config; +use codec::Encode; +use scale_info::prelude::{fmt::Debug, format, vec::Vec}; + +use super::*; +use ibc::{ + applications::transfer::{error::Error as Ics20Error, VERSION}, + core::ics24_host::identifier::{ChannelId as IbcChannelId, PortId}, + signer::Signer, +}; + +use ibc::{ + core::{ + ics02_client::msgs::ClientMsg, + ics03_connection::msgs::ConnectionMsg, + ics04_channel::msgs::{ChannelMsg, PacketMsg}, + ics26_routing::{handler, msgs::Ics26Envelope}, + }, + events::IbcEvent, +}; + +/// Get the latest block height of the host chain +pub fn host_height() -> u64 { + let block_number = format!("{:?}", >::block_number()); + let current_height: u64 = block_number.parse().unwrap_or_default(); + current_height +} + +/// In ICS20 fungible token transfer, get the escrow address by channel ID and port ID +/// +/// Parameters: +/// - `port_id`: The ID of the port corresponding to the escrow. +/// - `channel_id`: The ID of the channel corresponding to the escrow. +pub fn get_channel_escrow_address( + port_id: &PortId, + channel_id: &IbcChannelId, +) -> Result { + let contents = format!("{}/{}", port_id, channel_id); + let mut data = VERSION.as_bytes().to_vec(); + data.extend_from_slice(&[0]); + data.extend_from_slice(contents.as_bytes()); + + let hash = sp_io::hashing::sha2_256(&data).to_vec(); + let mut hex_string = hex::encode_upper(hash); + hex_string.insert_str(0, "0x"); + hex_string.parse::().map_err(Ics20Error::signer) +} From 18330af10b10964a08a0133254c165d30532d91a Mon Sep 17 00:00:00 2001 From: Davirain Date: Tue, 23 Aug 2022 18:54:32 +0800 Subject: [PATCH 475/484] remove something --- .../module/clients/ics07_tendermint/header.rs | 333 ------------------ .../module/clients/ics07_tendermint/mod.rs | 1 - frame/ibc/src/module/clients/mod.rs | 1 - .../src/module/core/ics02_client/context.rs | 291 --------------- .../src/module/core/ics02_client/events.rs | 20 -- .../src/module/core/ics02_client/header.rs | 11 - frame/ibc/src/module/core/ics23_commitment.rs | 1 - 7 files changed, 658 deletions(-) delete mode 100644 frame/ibc/src/module/clients/ics07_tendermint/header.rs delete mode 100644 frame/ibc/src/module/clients/ics07_tendermint/mod.rs delete mode 100644 frame/ibc/src/module/clients/mod.rs delete mode 100644 frame/ibc/src/module/core/ics02_client/context.rs delete mode 100644 frame/ibc/src/module/core/ics02_client/events.rs delete mode 100644 frame/ibc/src/module/core/ics02_client/header.rs delete mode 100644 frame/ibc/src/module/core/ics23_commitment.rs diff --git a/frame/ibc/src/module/clients/ics07_tendermint/header.rs b/frame/ibc/src/module/clients/ics07_tendermint/header.rs deleted file mode 100644 index 46f09a587f90c..0000000000000 --- a/frame/ibc/src/module/clients/ics07_tendermint/header.rs +++ /dev/null @@ -1,333 +0,0 @@ -use crate::module::core::ics24_host::Height; -use alloc::vec::Vec; -use codec::{Decode, Encode}; -use scale_info::TypeInfo; -use sp_runtime::RuntimeDebug; -use time::PrimitiveDateTime; - -/// Tendermint consensus header -#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct Header { - pub signed_header: SignedHeader, // contains the commitment root - pub validator_set: ValidatorSet, // the validator set that signed Header - pub trusted_height: Height, /* the height of a trusted header seen by client less than - * or equal to Header */ - // TODO(thane): Rename this to trusted_next_validator_set? - pub trusted_validator_set: ValidatorSet, // the last trusted validator set at trusted height -} - -/// Signed block headers -#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] -#[non_exhaustive] -pub struct SignedHeader { - /// Block header - pub header: block::Header, - /// Commit containing signatures for the header - pub commit: block::Commit, -} - -/// Validator set contains a vector of validators -#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct ValidatorSet { - validators: Vec, - proposer: Option, - total_voting_power: vote::Power, -} - -/// Validator information -// Todo: Remove address and make it into a function that generates it on the fly from pub_key. -#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct Info { - /// Validator account address - pub address: account::Id, - - /// Validator public key - pub pub_key: PublicKey, - - /// Validator voting power - // Compatibility with genesis.json https://github.com/tendermint/tendermint/issues/5549 - pub power: vote::Power, - - /// Validator name - pub name: Option>, - - /// Validator proposer priority - pub proposer_priority: ProposerPriority, -} - -// Todo: Is there more knowledge/restrictions about proposerPriority? -/// Proposer priority -#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct ProposerPriority(i64); - -mod vote { - use codec::{Decode, Encode}; - use scale_info::TypeInfo; - use sp_runtime::RuntimeDebug; - - /// Voting power - #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] - pub struct Power(u64); -} - -// Note:On the golang side this is generic in the sense that it could everything that implements -// github.com/tendermint/tendermint/crypto.PubKey -// While this is meant to be used with different key-types, it currently only uses a PubKeyEd25519 -// version. -// TODO: make this more generic - -// Warning: the custom serialization implemented here does not use TryFrom. -// it should only be used to read/write the priva_validator_key.json. -// All changes to the serialization should check both the JSON and protobuf conversions. -// Todo: Merge JSON serialization with #[serde(try_from = "RawPublicKey", into = "RawPublicKey)] -/// Public keys allowed in Tendermint protocols -#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] -#[non_exhaustive] -pub enum PublicKey { - /// Ed25519 keys - Ed25519, - - /// Secp256k1 keys - Secp256k1, -} - -mod account { - use codec::{Decode, Encode}; - use scale_info::TypeInfo; - use sp_runtime::RuntimeDebug; - - /// Size of an account ID in bytes - pub const LENGTH: usize = 20; - - /// Account IDs - #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] - pub struct Id([u8; LENGTH]); // JSON custom serialization for priv_validator_key.json -} - -mod block { - use crate::module::clients::ics07_tendermint::header::account; - use alloc::vec::Vec; - use codec::{Decode, Encode}; - use scale_info::TypeInfo; - use sp_runtime::RuntimeDebug; - use time::PrimitiveDateTime; - - /// Block height for a particular chain (i.e. number of blocks created since - /// the chain began) - /// - /// A height of 0 represents a chain which has not yet produced a block. - #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] - pub struct Height(u64); - - mod chain { - use alloc::vec::Vec; - use codec::{Decode, Encode}; - use scale_info::TypeInfo; - use sp_runtime::RuntimeDebug; - - /// Chain identifier (e.g. 'gaia-9000') - #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] - pub struct Id(Vec); - } - - /// `Version` contains the protocol version for the blockchain and the - /// application. - /// - /// - #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] - pub struct Version { - /// Block version - pub block: u64, - - /// App version - pub app: u64, - } - - /// Tendermint timestamps - /// - /// A `Time` value is guaranteed to represent a valid `Timestamp` as defined - /// by Google's well-known protobuf type [specification]. Conversions and - /// operations that would result in exceeding `Timestamp`'s validity - /// range return an error or `None`. - /// - /// The string serialization format for `Time` is defined as an RFC 3339 - /// compliant string with the optional subsecond fraction part having - /// up to 9 digits and no trailing zeros, and the UTC offset denoted by Z. - /// This reproduces the behavior of Go's `time.RFC3339Nano` format. - /// - /// [specification]: https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Timestamp - // For memory efficiency, the inner member is `PrimitiveDateTime`, with assumed - // UTC offset. The `assume_utc` method is used to get the operational - // `OffsetDateTime` value. - #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] - // pub struct Time(PrimitiveDateTime); - pub struct Time; - - /// Block identifiers which contain two distinct Merkle roots of the block, - /// as well as the number of parts in the block. - /// - /// - /// - /// Default implementation is an empty Id as defined by the Go implementation in - /// . - /// - /// If the Hash is empty in BlockId, the BlockId should be empty (encoded to None). - /// This is implemented outside of this struct. Use the Default trait to check for an empty - /// BlockId. See: - #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] - pub struct Id { - /// The block's main hash is the Merkle root of all the fields in the - /// block header. - pub hash: Hash, - - /// Parts header (if available) is used for secure gossipping of the block - /// during consensus. It is the Merkle root of the complete serialized block - /// cut into parts. - /// - /// PartSet is used to split a byteslice of data into parts (pieces) for - /// transmission. By splitting data into smaller parts and computing a - /// Merkle root hash on the list, you can verify that a part is - /// legitimately part of the complete data, and the part can be forwarded - /// to other peers before all the parts are known. In short, it's a fast - /// way to propagate a large file over a gossip network. - /// - /// - /// - /// PartSetHeader in protobuf is defined as never nil using the gogoproto - /// annotations. This does not translate to Rust, but we can indicate this - /// in the domain type. - pub part_set_header: PartSetHeader, - } - - /// Block parts header - #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] - #[non_exhaustive] - pub struct PartSetHeader { - /// Number of parts in this block - pub total: u32, - - /// Hash of the parts set header, - pub hash: Hash, - } - - /// Output size for the SHA-256 hash function - pub const SHA256_HASH_SIZE: usize = 32; - - /// Hash digests - #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] - pub enum Hash { - /// SHA-256 hashes - Sha256([u8; SHA256_HASH_SIZE]), - /// Empty hash - None, - } - - /// AppHash is usually a SHA256 hash, but in reality it can be any kind of data - #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] - pub struct AppHash(Vec); - - /// Block `Header` values contain metadata about the block and about the - /// consensus, as well as commitments to the data in the current block, the - /// previous block, and the results returned by the application. - /// - /// - #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] - pub struct Header { - /// Header version - pub version: Version, - - /// Chain ID - pub chain_id: chain::Id, - - /// Current block height - pub height: Height, - - /// Current timestamp - pub time: Time, - - /// Previous block info - pub last_block_id: Option, - - /// Commit from validators from the last block - pub last_commit_hash: Option, - - /// Merkle root of transaction hashes - pub data_hash: Option, - - /// Validators for the current block - pub validators_hash: Hash, - - /// Validators for the next block - pub next_validators_hash: Hash, - - /// Consensus params for the current block - pub consensus_hash: Hash, - - /// State after txs from the previous block - pub app_hash: AppHash, - - /// Root hash of all results from the txs from the previous block - pub last_results_hash: Option, - - /// Hash of evidence included in the block - pub evidence_hash: Option, - - /// Original proposer of the block - pub proposer_address: account::Id, - } - - /// Block round for a particular chain - #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] - pub struct Round(u32); - - /// Signatures - #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] - pub struct Signature(Vec); - - /// CommitSig represents a signature of a validator. - /// It's a part of the Commit and can be used to reconstruct the vote set given the validator - /// set. - #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] - pub enum CommitSig { - /// no vote was received from a validator. - BlockIdFlagAbsent, - /// voted for the Commit.BlockID. - BlockIdFlagCommit { - /// Validator address - validator_address: account::Id, - /// Timestamp of vote - timestamp: Time, - /// Signature of vote - signature: Option, - }, - /// voted for nil. - BlockIdFlagNil { - /// Validator address - validator_address: account::Id, - /// Timestamp of vote - timestamp: Time, - /// Signature of vote - signature: Option, - }, - } - - /// Commit contains the justification (ie. a set of signatures) that a block was committed by a - /// set of validators. - /// TODO: Update links below! - /// - /// - #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] - pub struct Commit { - /// Block height - pub height: Height, - - /// Round - pub round: Round, - - /// Block ID - pub block_id: Id, - - /// Signatures - pub signatures: Vec, - } -} diff --git a/frame/ibc/src/module/clients/ics07_tendermint/mod.rs b/frame/ibc/src/module/clients/ics07_tendermint/mod.rs deleted file mode 100644 index f505d688b456d..0000000000000 --- a/frame/ibc/src/module/clients/ics07_tendermint/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod header; diff --git a/frame/ibc/src/module/clients/mod.rs b/frame/ibc/src/module/clients/mod.rs deleted file mode 100644 index cc3da1b010b51..0000000000000 --- a/frame/ibc/src/module/clients/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod ics07_tendermint; diff --git a/frame/ibc/src/module/core/ics02_client/context.rs b/frame/ibc/src/module/core/ics02_client/context.rs deleted file mode 100644 index e0db70942fa64..0000000000000 --- a/frame/ibc/src/module/core/ics02_client/context.rs +++ /dev/null @@ -1,291 +0,0 @@ -use crate::*; -use alloc::string::ToString; -use core::str::FromStr; -use log::{error, info, trace, warn}; - -use crate::context::Context; -use ibc::{ - core::{ - ics02_client::{ - client_consensus::AnyConsensusState, - client_state::AnyClientState, - client_type::ClientType, - context::{ClientKeeper, ClientReader}, - error::Error as Ics02Error, - }, - ics24_host::{ - identifier::ClientId, - path::{ClientConsensusStatePath, ClientStatePath, ClientTypePath}, - }, - }, - timestamp::Timestamp, - Height, -}; - -impl ClientReader for Context { - fn client_type(&self, client_id: &ClientId) -> Result { - trace!(target:"runtime::pallet-ibc","in client : [client_type]"); - - let client_type_path = ClientTypePath(client_id.clone()).to_string().as_bytes().to_vec(); - if >::contains_key(client_type_path.clone()) { - let data = >::get(client_type_path); - let mut data: &[u8] = &data; - let data = Vec::::decode(&mut data).map_err(|_| Ics02Error::implementation_specific())?; - let data = String::from_utf8(data).map_err(|_| Ics02Error::implementation_specific())?; - let client_type = ClientType::from_str(&data) - .map_err(|e| Ics02Error::unknown_client_type(e.to_string()))?; - Ok(client_type) - } else { - trace!(target:"runtime::pallet-ibc","in client : [client_type] >> read client_type is None"); - Err(Ics02Error::client_not_found(client_id.clone())) - } - } - - fn client_state(&self, client_id: &ClientId) -> Result { - trace!(target:"runtime::pallet-ibc","in client : [client_state]"); - - let client_state_path = ClientStatePath(client_id.clone()).to_string().as_bytes().to_vec(); - - if >::contains_key(&client_state_path) { - let data = >::get(&client_state_path); - let result = AnyClientState::decode_vec(&*data).map_err(|_| Ics02Error::implementation_specific())?; - trace!(target:"runtime::pallet-ibc","in client : [client_state] >> any client_state: {:?}", result); - - Ok(result) - } else { - trace!(target:"runtime::pallet-ibc","in client : [client_state] >> read any client state is None"); - Err(Ics02Error::client_not_found(client_id.clone())) - } - } - - fn consensus_state( - &self, - client_id: &ClientId, - height: Height, - ) -> Result { - trace!(target:"runtime::pallet-ibc", - "in client : [consensus_state]" - ); - - // search key - let client_consensus_state_path = ClientConsensusStatePath { - client_id: client_id.clone(), - epoch: height.revision_number(), - height: height.revision_height(), - } - .to_string() - .as_bytes() - .to_vec(); - - if >::contains_key(client_consensus_state_path.clone()) { - let values = >::get(client_consensus_state_path.clone()); - let any_consensus_state = AnyConsensusState::decode_vec(&*values).map_err(|_| Ics02Error::implementation_specific())?; - trace!(target:"runtime::pallet-ibc", - "in client : [consensus_state] >> any consensus state = {:?}", - any_consensus_state - ); - Ok(any_consensus_state) - } else { - Err(Ics02Error::consensus_state_not_found(client_id.clone(), height)) - } - } - - fn next_consensus_state( - &self, - client_id: &ClientId, - height: Height, - ) -> Result, Ics02Error> { - trace!(target:"runtime::pallet-ibc","in client : [next_consensus_state]"); - - // search key - let client_consensus_state_path = ClientConsensusStatePath { - client_id: client_id.clone(), - epoch: height.revision_number(), - height: height.revision_height(), - } - .to_string() - .as_bytes() - .to_vec(); - - if >::contains_key(client_consensus_state_path.clone()) { - let values = >::get(client_consensus_state_path.clone()); - let any_consensus_state = AnyConsensusState::decode_vec(&*values).map_err(|_| Ics02Error::implementation_specific())?; - trace!(target:"runtime::pallet-ibc", - "in client : [next_consensus_state] >> any consensus state = {:?}", - any_consensus_state - ); - Ok(Some(any_consensus_state)) - } else { - trace!(target:"runtime::pallet-ibc", - "in client : [next_consensus_state] >> any consensus state is not contains at ConsensusStates : client_id = {:?}, height = {:?}", client_id, height - ); - Err(Ics02Error::consensus_state_not_found(client_id.clone(), height)) - } - } - - fn prev_consensus_state( - &self, - client_id: &ClientId, - height: Height, - ) -> Result, Ics02Error> { - trace!(target:"runtime::pallet-ibc","in client : [next_consensus_state]"); - - // search key - let client_consensus_state_path = ClientConsensusStatePath { - client_id: client_id.clone(), - epoch: height.revision_number(), - height: height.revision_height(), - } - .to_string() - .as_bytes() - .to_vec(); - - if >::contains_key(client_consensus_state_path.clone()) { - let values = >::get(client_consensus_state_path.clone()); - let any_consensus_state = AnyConsensusState::decode_vec(&*values).unwrap(); - trace!(target:"runtime::pallet-ibc", - "in client : [prev_consensus_state] >> any consensus state = {:?}", - any_consensus_state - ); - Ok(Some(any_consensus_state)) - } else { - trace!(target:"runtime::pallet-ibc", - "in client : [prev_consensus_state] >> any consensus state is not contains at ConsensusStates : client_id = {:?}, height = {:?}", client_id, height - ); - Err(Ics02Error::consensus_state_not_found(client_id.clone(), height)) - } - } - - fn host_height(&self) -> Height { - trace!(target:"runtime::pallet-ibc","in client : [host_height]"); - let block_number = format!("{:?}", >::block_number()); - let current_height: u64 = block_number.parse().unwrap_or_default(); - - trace!(target:"runtime::pallet-ibc", - "in channel: [host_height] >> host_height = {:?}", - Height::new(REVISION_NUMBER, current_height) - ); - Height::new(REVISION_NUMBER, current_height).expect("Contruct Heigjt Never failed") - } - - fn host_consensus_state(&self, _height: Height) -> Result { - trace!(target:"runtime::pallet-ibc","in client : [consensus_state]"); - - todo!() - } - - fn pending_host_consensus_state(&self) -> Result { - trace!(target:"runtime::pallet-ibc","in client: [pending_host_consensus_state]"); - - todo!() - } - - fn client_counter(&self) -> Result { - trace!(target:"runtime::pallet-ibc","in client : [client_counter]"); - - Ok(>::get()) - } -} - -impl ClientKeeper for Context { - fn store_client_type( - &mut self, - client_id: ClientId, - client_type: ClientType, - ) -> Result<(), Ics02Error> { - info!("in client : [store_client_type]"); - - let client_type_path = ClientTypePath(client_id.clone()).to_string().as_bytes().to_vec(); - let client_type = client_type.as_str().encode(); - >::insert(client_type_path, client_type); - Ok(()) - } - - fn store_client_state( - &mut self, - client_id: ClientId, - client_state: AnyClientState, - ) -> Result<(), Ics02Error> { - trace!(target:"runtime::pallet-ibc","in client : [store_client_state]"); - - let client_state_path = ClientStatePath(client_id.clone()).to_string().as_bytes().to_vec(); - - let data = client_state.encode_vec().map_err(|_| Ics02Error::implementation_specific())?; - // store client states key-value - >::insert(client_state_path.clone(), data); - - Ok(()) - } - - fn store_consensus_state( - &mut self, - client_id: ClientId, - height: Height, - consensus_state: AnyConsensusState, - ) -> Result<(), Ics02Error> { - trace!(target:"runtime::pallet-ibc","in client : [store_consensus_state]"); - - // store key - let client_consensus_state_path = ClientConsensusStatePath { - client_id: client_id.clone(), - epoch: height.revision_number(), - height: height.revision_height(), - } - .to_string() - .as_bytes() - .to_vec(); - - // store value - let consensus_state = consensus_state.encode_vec().map_err(|_| Ics02Error::implementation_specific())?; - // store client_consensus_state path as key, consensus_state as value - >::insert(client_consensus_state_path, consensus_state); - - Ok(()) - } - - fn increase_client_counter(&mut self) { - info!("in client : [increase_client_counter]"); - - let ret = >::try_mutate(|val| -> Result<(), Ics02Error> { - let new = val.checked_add(1).expect("Never Overflow"); - *val = new; - Ok(()) - }); - } - - fn store_update_time( - &mut self, - client_id: ClientId, - height: Height, - timestamp: Timestamp, - ) -> Result<(), Ics02Error> { - trace!(target:"runtime::pallet-ibc","in client: [store_update_time]"); - - let encode_timestamp = serde_json::to_string(×tamp).map_err(|_| Ics02Error::implementation_specific())?.as_bytes().to_vec(); - - >::insert( - client_id.as_bytes(), - height.encode_vec().map_err(|_| Ics02Error::implementation_specific())?, - encode_timestamp, - ); - - Ok(()) - } - - fn store_update_height( - &mut self, - client_id: ClientId, - height: Height, - host_height: Height, - ) -> Result<(), Ics02Error> { - trace!(target:"runtime::pallet-ibc","in client: [store_update_height]"); - - >::insert( - client_id.as_bytes(), - height.encode_vec().map_err(|_| Ics02Error::implementation_specific())?, - host_height.encode_vec().map_err(|_| Ics02Error::implementation_specific())?, - ); - - Ok(()) - } -} diff --git a/frame/ibc/src/module/core/ics02_client/events.rs b/frame/ibc/src/module/core/ics02_client/events.rs deleted file mode 100644 index 4442ea54a59c5..0000000000000 --- a/frame/ibc/src/module/core/ics02_client/events.rs +++ /dev/null @@ -1,20 +0,0 @@ -use super::header::AnyHeader; -use crate::module::core::ics24_host::{ClientId, ClientType, Height}; -use codec::{Decode, Encode}; -use scale_info::TypeInfo; -use sp_runtime::RuntimeDebug; - -#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct Attributes { - pub height: Height, - pub client_id: ClientId, - pub client_type: ClientType, - pub consensus_height: Height, -} - -/// UpdateClient event signals a recent update of an on-chain client (IBC Client). -#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct UpdateClient { - pub common: Attributes, - pub header: Option, -} diff --git a/frame/ibc/src/module/core/ics02_client/header.rs b/frame/ibc/src/module/core/ics02_client/header.rs deleted file mode 100644 index a03edcd3daa51..0000000000000 --- a/frame/ibc/src/module/core/ics02_client/header.rs +++ /dev/null @@ -1,11 +0,0 @@ -use crate::module::clients::ics07_tendermint::header::Header as TendermintHeader; -use codec::{Decode, Encode}; -use scale_info::TypeInfo; -use sp_runtime::RuntimeDebug; - -#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] -#[allow(clippy::large_enum_variant)] -pub enum AnyHeader { - Tendermint(TendermintHeader), - // Grandpa(GrandpaHeader), -} diff --git a/frame/ibc/src/module/core/ics23_commitment.rs b/frame/ibc/src/module/core/ics23_commitment.rs deleted file mode 100644 index 8b137891791fe..0000000000000 --- a/frame/ibc/src/module/core/ics23_commitment.rs +++ /dev/null @@ -1 +0,0 @@ - From 49665ce85be41ba7cbe92f0896e56f555dde64a4 Mon Sep 17 00:00:00 2001 From: Davirain Date: Tue, 23 Aug 2022 18:57:16 +0800 Subject: [PATCH 476/484] format code --- frame/ibc/src/context.rs | 1 - frame/ibc/src/lib.rs | 2 +- .../applications/transfer/transfer_handle_callback.rs | 4 ++-- frame/ibc/src/module/core/ics02_client.rs | 2 +- frame/ibc/src/module/core/ics03_connection.rs | 1 - frame/ibc/src/module/core/ics04_channel.rs | 8 -------- frame/ibc/src/module/core/ics24_host.rs | 1 - frame/ibc/src/tests.rs | 4 ---- frame/ibc/src/utils.rs | 1 - 9 files changed, 4 insertions(+), 20 deletions(-) diff --git a/frame/ibc/src/context.rs b/frame/ibc/src/context.rs index fb30ff5e53df9..6ab7af3e27f0c 100644 --- a/frame/ibc/src/context.rs +++ b/frame/ibc/src/context.rs @@ -22,7 +22,6 @@ use ibc::{ }, }; - use crate::module::core::ics26_routing::{Router, SubRouterBuilder}; /// A struct capturing all the functional dependencies (i.e., context) diff --git a/frame/ibc/src/lib.rs b/frame/ibc/src/lib.rs index 86e340e0bb393..2d0236f85a87d 100644 --- a/frame/ibc/src/lib.rs +++ b/frame/ibc/src/lib.rs @@ -606,7 +606,7 @@ pub mod pallet { } let HandlerOutput::<()> { result, log, events } = handle_out.with_result(()); - + // deposit events about send packet event and ics20 transfer event for event in events { Self::deposit_event(event.clone().into()); diff --git a/frame/ibc/src/module/applications/transfer/transfer_handle_callback.rs b/frame/ibc/src/module/applications/transfer/transfer_handle_callback.rs index a7549d28091db..e4314506cb2f4 100644 --- a/frame/ibc/src/module/applications/transfer/transfer_handle_callback.rs +++ b/frame/ibc/src/module/applications/transfer/transfer_handle_callback.rs @@ -45,7 +45,6 @@ impl Module for TransferModule { version, ) .map_err(|value| Ics04Error::app_module(value.to_string())) - } fn on_chan_open_try( @@ -69,7 +68,8 @@ impl Module for TransferModule { counterparty, version, counterparty_version, - ).map_err(|value| Ics04Error::app_module(value.to_string())) + ) + .map_err(|value| Ics04Error::app_module(value.to_string())) } fn on_chan_open_ack( diff --git a/frame/ibc/src/module/core/ics02_client.rs b/frame/ibc/src/module/core/ics02_client.rs index 3f83f9b278405..84f803f2f771c 100644 --- a/frame/ibc/src/module/core/ics02_client.rs +++ b/frame/ibc/src/module/core/ics02_client.rs @@ -232,4 +232,4 @@ impl ClientKeeper for Context { Ok(()) } -} \ No newline at end of file +} diff --git a/frame/ibc/src/module/core/ics03_connection.rs b/frame/ibc/src/module/core/ics03_connection.rs index 005b33754619c..82be9973e5032 100644 --- a/frame/ibc/src/module/core/ics03_connection.rs +++ b/frame/ibc/src/module/core/ics03_connection.rs @@ -83,7 +83,6 @@ impl ConnectionKeeper for Context { let data = connection_end.encode_vec().map_err(|_| Ics03Error::implementation_specific())?; - // store connection end >::insert(connections_path, data); diff --git a/frame/ibc/src/module/core/ics04_channel.rs b/frame/ibc/src/module/core/ics04_channel.rs index 1f374a1fd30ed..38b4f9bd85e9b 100644 --- a/frame/ibc/src/module/core/ics04_channel.rs +++ b/frame/ibc/src/module/core/ics04_channel.rs @@ -224,7 +224,6 @@ impl ChannelReader for Context { let acknowledgement = IbcAcknowledgementCommitment::from(data); Ok(acknowledgement) } else { - Err(Ics04Error::packet_acknowledgement_not_found(key.2)) } } @@ -257,7 +256,6 @@ impl ChannelReader for Context { } fn pending_host_consensus_state(&self) -> Result { - ClientReader::pending_host_consensus_state(self) .map_err(|e| Ics04Error::ics03_connection(ICS03Error::ics02_client(e))) } @@ -282,7 +280,6 @@ impl ChannelReader for Context { .map_err(|_| Ics04Error::implementation_specific())?; Ok(time) } else { - Err(Ics04Error::processed_time_not_found(client_id.clone(), height)) } } @@ -314,7 +311,6 @@ impl ChannelReader for Context { } fn max_expected_time_per_block(&self) -> Duration { - Duration::from_secs(6) } } @@ -344,7 +340,6 @@ impl ChannelKeeper for Context { &mut self, key: (PortId, ChannelId, Sequence), ) -> Result<(), Ics04Error> { - let packet_commitments_path = CommitmentsPath { port_id: key.0.clone(), channel_id: key.1.clone(), @@ -432,7 +427,6 @@ impl ChannelKeeper for Context { .to_vec(); if >::contains_key(&connections_path) { - // if connection_id exist >::try_mutate( &connections_path, @@ -443,7 +437,6 @@ impl ChannelKeeper for Context { ) .expect("channels Connection mutate Error") } else { - >::insert(connections_path, vec![channel_ends_path]); } @@ -464,7 +457,6 @@ impl ChannelKeeper for Context { let channel_end = channel_end.encode_vec().map_err(|_| Ics04Error::implementation_specific())?; - // store channels key-value >::insert(channel_end_path, channel_end); diff --git a/frame/ibc/src/module/core/ics24_host.rs b/frame/ibc/src/module/core/ics24_host.rs index f4e15df5dc594..7a098ef322c73 100644 --- a/frame/ibc/src/module/core/ics24_host.rs +++ b/frame/ibc/src/module/core/ics24_host.rs @@ -102,7 +102,6 @@ impl From for IbcHeight { fn from(height: Height) -> Self { IbcHeight::new(REVISION_NUMBER, height.revision_height) .expect("Contruct IbcHeight Never faild") - } } diff --git a/frame/ibc/src/tests.rs b/frame/ibc/src/tests.rs index 3a41a35e516bd..45f7a9b2634b0 100644 --- a/frame/ibc/src/tests.rs +++ b/frame/ibc/src/tests.rs @@ -40,10 +40,6 @@ fn test_store_client_type_ok() { new_test_ext().execute_with(|| { assert!(context.store_client_type(gp_client_id.clone(), gp_client_type).is_ok()); - - let ret = context.client_type(&gp_client_id).unwrap(); - - assert_eq!(ret, gp_client_type); }) } diff --git a/frame/ibc/src/utils.rs b/frame/ibc/src/utils.rs index f79a6eaad70e0..eda1b8c82b813 100644 --- a/frame/ibc/src/utils.rs +++ b/frame/ibc/src/utils.rs @@ -26,7 +26,6 @@ pub fn host_height() -> u64 { current_height } - /// In ICS20 fungible token transfer, get the escrow address by channel ID and port ID /// /// Parameters: From 30e145ce2ca0da4d2dab42d46c18d18d99a588a4 Mon Sep 17 00:00:00 2001 From: Davirain Date: Tue, 23 Aug 2022 18:59:29 +0800 Subject: [PATCH 477/484] update readme --- frame/ibc/README.md | 69 --------------------------------------------- 1 file changed, 69 deletions(-) diff --git a/frame/ibc/README.md b/frame/ibc/README.md index 9451f9c1bafa8..a3badac963cad 100644 --- a/frame/ibc/README.md +++ b/frame/ibc/README.md @@ -26,75 +26,6 @@ The chain specific logic of the modules in ICS spec implemented: * ics-025-handler-interface * ics-026-routing-module -Here is a [demo](~~https://github.com/cdot-network/ibc-demo~~) for showing how to utilize this pallet, which initializes a series of steps for cross-chain communication, from client creation to sending packet data. - -## Design Overview -The ibc pallet is integrated with the [modules in ibc-rs](https://github.com/octopus-network/ibc-rs/tree/b98094a57620d0b3d9f8d2caced09abfc14ab00f/modules), which implements the [ibc spec](https://github.com/cosmos/ibc/tree/7046202b645c65b1a2b7f293312bca5d651a13a4/spec) and leave the chain specific logics, which are named `???Readers` and `???Keepers`, to the ibc pallet. - -List of `Readers` and `Keepers` of on-chain storage: -* [ClientReader](https://github.com/octopus-network/ibc-rs/blob/b98094a57620d0b3d9f8d2caced09abfc14ab00f/modules/src/ics02_client/context.rs?_pjax=%23js-repo-pjax-container%2C%20div%5Bitemtype%3D%22http%3A%2F%2Fschema.org%2FSoftwareSourceCode%22%5D%20main%2C%20%5Bdata-pjax-container%5D#L14) & [ClientKeeper](https://github.com/octopus-network/ibc-rs/blob/b98094a57620d0b3d9f8d2caced09abfc14ab00f/modules/src/ics02_client/context.rs?_pjax=%23js-repo-pjax-container%2C%20div%5Bitemtype%3D%22http%3A%2F%2Fschema.org%2FSoftwareSourceCode%22%5D%20main%2C%20%5Bdata-pjax-container%5D#L29) -* [ConnectionReader](https://github.com/octopus-network/ibc-rs/blob/b98094a57620d0b3d9f8d2caced09abfc14ab00f/modules/src/ics03_connection/context.rs?_pjax=%23js-repo-pjax-container%2C%20div%5Bitemtype%3D%22http%3A%2F%2Fschema.org%2FSoftwareSourceCode%22%5D%20main%2C%20%5Bdata-pjax-container%5D#L17) & [ConnectionKeeper](https://github.com/octopus-network/ibc-rs/blob/b98094a57620d0b3d9f8d2caced09abfc14ab00f/modules/src/ics03_connection/context.rs?_pjax=%23js-repo-pjax-container%2C%20div%5Bitemtype%3D%22http%3A%2F%2Fschema.org%2FSoftwareSourceCode%22%5D%20main%2C%20%5Bdata-pjax-container%5D#L67) -* [ChannelReader](https://github.com/octopus-network/ibc-rs/blob/b98094a57620d0b3d9f8d2caced09abfc14ab00f/modules/src/ics04_channel/context.rs?_pjax=%23js-repo-pjax-container%2C%20div%5Bitemtype%3D%22http%3A%2F%2Fschema.org%2FSoftwareSourceCode%22%5D%20main%2C%20%5Bdata-pjax-container%5D#L20) & [ChannelKeeper](https://github.com/octopus-network/ibc-rs/blob/b98094a57620d0b3d9f8d2caced09abfc14ab00f/modules/src/ics04_channel/context.rs?_pjax=%23js-repo-pjax-container%2C%20div%5Bitemtype%3D%22http%3A%2F%2Fschema.org%2FSoftwareSourceCode%22%5D%20main%2C%20%5Bdata-pjax-container%5D#L82) - -## Installation -Thie section describe the modification of your substrate chain needed to integrate pallet ibc. - -### `Cargo.toml` -Specify some versions of ibc relevant crate -```toml -[patch.crates-io] -tendermint = { git = "https://github.com/informalsystems/tendermint-rs", branch = "v0.23.x" } -tendermint-rpc = { git = "https://github.com/informalsystems/tendermint-rs", branch = "v0.23.x" } -tendermint-proto = { git = "https://github.com/informalsystems/tendermint-rs", branch = "v0.23.x" } -tendermint-light-client = { git = "https://github.com/informalsystems/tendermint-rs", branch = "v0.23.x" } -tendermint-light-client-verifier = { git = "https://github.com/informalsystems/tendermint-rs", branch = "v0.23.x" } -tendermint-testgen = { git = "https://github.com/informalsystems/tendermint-rs", branch = "v0.23.x" } -``` - -#### Runtime's `Cargo.toml` -To add this pallet to your runtime, include the following to your runtime's `Cargo.toml` file: - -```TOML -pallet-ibc = { git = "https://github.com/octopus-network/substrate-ibc", branch = "master", default-features = false} -``` - -and update your runtime's `std` feature to include this pallet: - -```TOML -std = [ - # --snip-- - "pallet-ibc/std", -] -``` - -### Runtime `lib.rs` -A custom structure that implements the pallet_ibc::ModuleCallbacks must be defined to dispatch messages to receiving module. -```rust -pub struct ModuleCallbacksImpl; - -impl pallet_ibc::ModuleCallbacks for ModuleCallbacksImpl {} - -impl pallet_ibc::Config for Runtime { - type Event = Event; - type ModuleCallbacks = ModuleCallbacksImpl; - type TimeProvider = pallet_timestamp::Pallet; -} -``` - -You should include it in your `construct_runtime!` macro: - -```rust -Ibc: pallet_ibc::{Pallet, Call, Storage, Event}, -``` - -### Genesis Configuration - -This pallet does not have any genesis configuration. - -## How to Interact with the Pallet -The Hermes (IBC Relayer CLI) offers commands to send reqeusts to pallet ibc to trigger the standard ibc communications defined in [ibc spce](https://github.com/cosmos/ibc/tree/ee71d0640c23ec4e05e924f52f557b5e06c1d82f/spec). -[Hermes Command List](https://hermes.informal.systems/commands/raw/index.html). - ## Reference Docs You can view the reference docs for this pallet by running: From feefe594230dd9c6fed8248c69ad0bc8753c7d77 Mon Sep 17 00:00:00 2001 From: Yuanchao Sun Date: Wed, 24 Aug 2022 09:27:58 +0800 Subject: [PATCH 478/484] Delete Cargo.lock --- frame/ibc/Cargo.lock | 3462 ------------------------------------------ 1 file changed, 3462 deletions(-) delete mode 100644 frame/ibc/Cargo.lock diff --git a/frame/ibc/Cargo.lock b/frame/ibc/Cargo.lock deleted file mode 100644 index e2df74bf1d4f1..0000000000000 --- a/frame/ibc/Cargo.lock +++ /dev/null @@ -1,3462 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[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 = "addr2line" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "ahash" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" -dependencies = [ - "getrandom 0.2.7", - "once_cell", - "version_check", -] - -[[package]] -name = "aho-corasick" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" -dependencies = [ - "memchr", -] - -[[package]] -name = "ansi_term" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -dependencies = [ - "winapi", -] - -[[package]] -name = "anyhow" -version = "1.0.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91f1f46651137be86f3a2b9a8359f9ab421d04d941c62b5982e1ca21113adf9" - -[[package]] -name = "approx" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" -dependencies = [ - "num-traits", -] - -[[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.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" - -[[package]] -name = "arrayvec" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" - -[[package]] -name = "async-trait" -version = "0.1.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "backtrace" -version = "0.3.66" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" -dependencies = [ - "addr2line", - "cc", - "cfg-if 1.0.0", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - -[[package]] -name = "base58" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" - -[[package]] -name = "base64" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" - -[[package]] -name = "beefy-light-client" -version = "0.1.0" -source = "git+https://github.com/octopus-network/beefy-light-client.git?branch=main#113215ed340d3cc3f61dd9f4e8b8a6138f5ab736" -dependencies = [ - "beefy-merkle-tree", - "blake2-rfc", - "borsh", - "ckb-merkle-mountain-range", - "hex", - "libsecp256k1", - "parity-scale-codec", - "scale-info", - "serde", -] - -[[package]] -name = "beefy-merkle-tree" -version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" -dependencies = [ - "tiny-keccak", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - -[[package]] -name = "blake2" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9cf849ee05b2ee5fba5e36f97ff8ec2533916700fc0758d40d92136a42f3388" -dependencies = [ - "digest 0.10.3", -] - -[[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 0.1.5", - "byte-tools", - "byteorder", - "generic-array 0.12.4", -] - -[[package]] -name = "block-buffer" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" -dependencies = [ - "block-padding 0.2.1", - "generic-array 0.14.6", -] - -[[package]] -name = "block-buffer" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" -dependencies = [ - "generic-array 0.14.6", -] - -[[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 = "block-padding" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" - -[[package]] -name = "borsh" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15bf3650200d8bffa99015595e10f1fbd17de07abbc25bb067da79e769939bfa" -dependencies = [ - "borsh-derive", - "hashbrown 0.11.2", -] - -[[package]] -name = "borsh-derive" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6441c552f230375d18e3cc377677914d2ca2b0d36e52129fe15450a2dce46775" -dependencies = [ - "borsh-derive-internal", - "borsh-schema-derive-internal", - "proc-macro-crate 0.1.5", - "proc-macro2", - "syn", -] - -[[package]] -name = "borsh-derive-internal" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "borsh-schema-derive-internal" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "build-helper" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdce191bf3fa4995ce948c8c83b4640a1745457a149e73c6db75b4ffe36aad5f" -dependencies = [ - "semver 0.6.0", -] - -[[package]] -name = "bumpalo" -version = "3.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" - -[[package]] -name = "byte-slice-cast" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87c5fdd0166095e1d463fc6cc01aa8ce547ad77a4e84d42eb6762b084e28067e" - -[[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.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - -[[package]] -name = "bytes" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" - -[[package]] -name = "camino" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "869119e97797867fd90f5e22af7d0bd274bd4635ebb9eb68c04f3f513ae6c412" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo-platform" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" -dependencies = [ - "serde", -] - -[[package]] -name = "cargo_metadata" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" -dependencies = [ - "camino", - "cargo-platform", - "semver 1.0.13", - "serde", - "serde_json", -] - -[[package]] -name = "cc" -version = "1.0.73" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" -dependencies = [ - "jobserver", -] - -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "chrono" -version = "0.4.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" -dependencies = [ - "libc", - "num-integer", - "num-traits", - "time 0.1.44", - "winapi", -] - -[[package]] -name = "ckb-merkle-mountain-range" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "530710c310c455ced794feaa13f284c4c5d40ae5d82875392a83da7c5f84a8db" -dependencies = [ - "cfg-if 0.1.10", -] - -[[package]] -name = "constant_time_eq" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" - -[[package]] -name = "cpufeatures" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" -dependencies = [ - "libc", -] - -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array 0.14.6", - "typenum", -] - -[[package]] -name = "crypto-mac" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" -dependencies = [ - "generic-array 0.14.6", - "subtle", -] - -[[package]] -name = "crypto-mac" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" -dependencies = [ - "generic-array 0.14.6", - "subtle", -] - -[[package]] -name = "curve25519-dalek" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b85542f99a2dfa2a1b8e192662741c9859a846b296bef1c92ef9b58b5a216" -dependencies = [ - "byteorder", - "digest 0.8.1", - "rand_core 0.5.1", - "subtle", - "zeroize", -] - -[[package]] -name = "curve25519-dalek" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" -dependencies = [ - "byteorder", - "digest 0.9.0", - "rand_core 0.5.1", - "subtle", - "zeroize", -] - -[[package]] -name = "derive_more" -version = "0.99.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" -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 0.12.4", -] - -[[package]] -name = "digest" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" -dependencies = [ - "generic-array 0.14.6", -] - -[[package]] -name = "digest" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" -dependencies = [ - "block-buffer 0.10.2", - "crypto-common", - "subtle", -] - -[[package]] -name = "downcast-rs" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" - -[[package]] -name = "dyn-clonable" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e9232f0e607a262ceb9bd5141a3dfb3e4db6994b31989bbfd845878cba59fd4" -dependencies = [ - "dyn-clonable-impl", - "dyn-clone", -] - -[[package]] -name = "dyn-clonable-impl" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "558e40ea573c374cf53507fd240b7ee2f5477df7cfebdb97323ec61c719399c5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "dyn-clone" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2" - -[[package]] -name = "ed25519" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" -dependencies = [ - "signature", -] - -[[package]] -name = "ed25519-dalek" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" -dependencies = [ - "curve25519-dalek 3.2.0", - "ed25519", - "rand 0.7.3", - "serde", - "sha2 0.9.9", - "zeroize", -] - -[[package]] -name = "either" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" - -[[package]] -name = "environmental" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b91989ae21441195d7d9b9993a2f9295c7e1a8c96255d8b729accddc124797" - -[[package]] -name = "eyre" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" -dependencies = [ - "indenter", - "once_cell", -] - -[[package]] -name = "fake-simd" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" - -[[package]] -name = "fastrand" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" -dependencies = [ - "instant", -] - -[[package]] -name = "fixed-hash" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" -dependencies = [ - "byteorder", - "rand 0.8.5", - "rustc-hex", - "static_assertions", -] - -[[package]] -name = "flex-error" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c606d892c9de11507fa0dcffc116434f94e105d0bbdc4e405b61519464c49d7b" -dependencies = [ - "eyre", - "paste", -] - -[[package]] -name = "frame-benchmarking" -version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" -dependencies = [ - "frame-support", - "frame-system", - "linregress", - "log", - "parity-scale-codec", - "paste", - "scale-info", - "serde", - "sp-api", - "sp-application-crypto", - "sp-io", - "sp-runtime", - "sp-runtime-interface", - "sp-std 4.0.0", - "sp-storage", -] - -[[package]] -name = "frame-metadata" -version = "15.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df6bb8542ef006ef0de09a5c4420787d79823c0ed7924225822362fd2bf2ff2d" -dependencies = [ - "cfg-if 1.0.0", - "parity-scale-codec", - "scale-info", - "serde", -] - -[[package]] -name = "frame-support" -version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" -dependencies = [ - "bitflags", - "frame-metadata", - "frame-support-procedural", - "impl-trait-for-tuples", - "log", - "once_cell", - "parity-scale-codec", - "paste", - "scale-info", - "serde", - "smallvec", - "sp-arithmetic", - "sp-core", - "sp-core-hashing-proc-macro", - "sp-inherents", - "sp-io", - "sp-runtime", - "sp-staking", - "sp-state-machine", - "sp-std 4.0.0", - "sp-tracing", - "tt-call", -] - -[[package]] -name = "frame-support-procedural" -version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" -dependencies = [ - "Inflector", - "frame-support-procedural-tools", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "frame-support-procedural-tools" -version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" -dependencies = [ - "frame-support-procedural-tools-derive", - "proc-macro-crate 1.2.0", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "frame-support-procedural-tools-derive" -version = "3.0.0" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "frame-system" -version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" -dependencies = [ - "frame-support", - "log", - "parity-scale-codec", - "scale-info", - "serde", - "sp-core", - "sp-io", - "sp-runtime", - "sp-std 4.0.0", - "sp-version", -] - -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - -[[package]] -name = "futures" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" - -[[package]] -name = "futures-executor" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", - "num_cpus", -] - -[[package]] -name = "futures-io" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" - -[[package]] -name = "futures-macro" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" - -[[package]] -name = "futures-task" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" - -[[package]] -name = "futures-util" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" -dependencies = [ - "typenum", -] - -[[package]] -name = "generic-array" -version = "0.14.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if 1.0.0", - "js-sys", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", -] - -[[package]] -name = "gimli" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" - -[[package]] -name = "gumdrop" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bc700f989d2f6f0248546222d9b4258f5b02a171a431f8285a81c08142629e3" -dependencies = [ - "gumdrop_derive", -] - -[[package]] -name = "gumdrop_derive" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "729f9bd3449d77e7831a18abfb7ba2f99ee813dfd15b8c2167c9a54ba20aa99d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[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.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" -dependencies = [ - "ahash", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash", -] - -[[package]] -name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hmac" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" -dependencies = [ - "crypto-mac 0.8.0", - "digest 0.9.0", -] - -[[package]] -name = "hmac" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" -dependencies = [ - "crypto-mac 0.11.1", - "digest 0.9.0", -] - -[[package]] -name = "hmac-drbg" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" -dependencies = [ - "digest 0.9.0", - "generic-array 0.14.6", - "hmac 0.8.1", -] - -[[package]] -name = "ibc" -version = "0.16.0" -source = "git+https://github.com/octopus-network/ibc-rs.git?branch=feature/ics20-davirian-base-0.9.18#36808084c389b0a0bba008723018c5990500f098" -dependencies = [ - "beefy-light-client", - "beefy-merkle-tree", - "blake2-rfc", - "bytes", - "derive_more", - "flex-error", - "frame-support", - "frame-system", - "hash-db", - "ibc-proto", - "ics23", - "num-traits", - "parity-scale-codec", - "primitive-types", - "prost 0.10.4", - "prost-types 0.10.1", - "safe-regex", - "serde", - "serde_derive", - "serde_json", - "sha2 0.10.2", - "sp-core", - "sp-io", - "sp-runtime", - "sp-std 4.0.0", - "sp-trie", - "subtle-encoding", - "tendermint", - "tendermint-light-client-verifier", - "tendermint-proto", - "tendermint-testgen", - "time 0.3.11", - "tracing", - "uint", -] - -[[package]] -name = "ibc-proto" -version = "0.19.0" -source = "git+https://github.com/octopus-network/ibc-rs.git?branch=feature/ics20-davirian-base-0.9.18#36808084c389b0a0bba008723018c5990500f098" -dependencies = [ - "base64", - "bytes", - "prost 0.10.4", - "prost-types 0.10.1", - "serde", - "tendermint-proto", -] - -[[package]] -name = "ics23" -version = "0.8.0-alpha" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a435f2471c1b2ce14771da465d47321c5905fac866d0effa9e0a3eb5d94fcf" -dependencies = [ - "anyhow", - "bytes", - "hex", - "prost 0.10.4", - "ripemd160", - "sha2 0.9.9", - "sha3 0.9.1", - "sp-std 3.0.0", -] - -[[package]] -name = "impl-codec" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" -dependencies = [ - "parity-scale-codec", -] - -[[package]] -name = "impl-serde" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" -dependencies = [ - "serde", -] - -[[package]] -name = "impl-trait-for-tuples" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "indenter" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" - -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if 1.0.0", -] - -[[package]] -name = "integer-sqrt" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" -dependencies = [ - "num-traits", -] - -[[package]] -name = "itertools" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - -[[package]] -name = "itoa" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" - -[[package]] -name = "jobserver" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" -dependencies = [ - "libc", -] - -[[package]] -name = "js-sys" -version = "0.3.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "keccak" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" - -[[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.127" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "505e71a4706fa491e9b1b55f51b95d4037d0821ee40131190475f692b35b009b" - -[[package]] -name = "libm" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da83a57f3f5ba3680950aa3cbc806fc297bc0b289d42e8942ed528ace71b8145" - -[[package]] -name = "libsecp256k1" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" -dependencies = [ - "arrayref", - "base64", - "digest 0.9.0", - "hmac-drbg", - "libsecp256k1-core", - "libsecp256k1-gen-ecmult", - "libsecp256k1-gen-genmult", - "rand 0.8.5", - "serde", - "sha2 0.9.9", - "typenum", -] - -[[package]] -name = "libsecp256k1-core" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" -dependencies = [ - "crunchy", - "digest 0.9.0", - "subtle", -] - -[[package]] -name = "libsecp256k1-gen-ecmult" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" -dependencies = [ - "libsecp256k1-core", -] - -[[package]] -name = "libsecp256k1-gen-genmult" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" -dependencies = [ - "libsecp256k1-core", -] - -[[package]] -name = "linregress" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c601a85f5ecd1aba625247bca0031585fb1c446461b142878a16f8245ddeb8" -dependencies = [ - "nalgebra", - "statrs", -] - -[[package]] -name = "lock_api" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if 1.0.0", -] - -[[package]] -name = "matchers" -version = "0.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f099785f7595cc4b4553a174ce30dd7589ef93391ff414dbb67f62392b9e0ce1" -dependencies = [ - "regex-automata", -] - -[[package]] -name = "matrixmultiply" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add85d4dd35074e6fedc608f8c8f513a3548619a9024b751949ef0e8e45a4d84" -dependencies = [ - "rawpointer", -] - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "memory-db" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6566c70c1016f525ced45d7b7f97730a2bafb037c788211d0c186ef5b2189f0a" -dependencies = [ - "hash-db", - "hashbrown 0.12.3", - "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 = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e261cf0f8b3c42ded9f7d2bb59dea03aa52bc8a1cbc7482f9fc3fd1229d3b42" -dependencies = [ - "byteorder", - "keccak", - "rand_core 0.5.1", - "zeroize", -] - -[[package]] -name = "miniz_oxide" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" -dependencies = [ - "adler", -] - -[[package]] -name = "nalgebra" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "462fffe4002f4f2e1f6a9dcf12cc1a6fc0e15989014efc02a941d3e0f5dc2120" -dependencies = [ - "approx", - "matrixmultiply", - "nalgebra-macros", - "num-complex", - "num-rational 0.4.1", - "num-traits", - "rand 0.8.5", - "rand_distr", - "simba", - "typenum", -] - -[[package]] -name = "nalgebra-macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01fcc0b8149b4632adc89ac3b7b31a12fb6099a0317a4eb2ebff574ef7de7218" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[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", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-complex" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-derive" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "num-format" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bafe4179722c2894288ee77a9f044f02811c86af699344c498b0840c698a2465" -dependencies = [ - "arrayvec 0.4.12", - "itoa 0.4.8", -] - -[[package]] -name = "num-integer" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" -dependencies = [ - "autocfg", - "num-bigint", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" -dependencies = [ - "autocfg", - "libm", -] - -[[package]] -name = "num_cpus" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "num_threads" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" -dependencies = [ - "libc", -] - -[[package]] -name = "object" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" - -[[package]] -name = "opaque-debug" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" - -[[package]] -name = "opaque-debug" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" - -[[package]] -name = "pallet-ibc" -version = "3.0.0-pre.0" -dependencies = [ - "beefy-light-client", - "chrono", - "flex-error", - "frame-benchmarking", - "frame-support", - "frame-system", - "hex", - "ibc", - "ibc-proto", - "log", - "parity-scale-codec", - "prost 0.9.0", - "prost-types 0.9.0", - "scale-info", - "serde", - "serde_json", - "sha2 0.10.2", - "sp-core", - "sp-io", - "sp-keyring", - "sp-runtime", - "sp-std 4.0.0", - "sp-tracing", - "substrate-wasm-builder", - "tendermint-proto", - "time 0.3.11", -] - -[[package]] -name = "parity-scale-codec" -version = "3.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9182e4a71cae089267ab03e67c99368db7cd877baf50f931e5d6d4b71e195ac0" -dependencies = [ - "arrayvec 0.7.2", - "bitvec", - "byte-slice-cast", - "impl-trait-for-tuples", - "parity-scale-codec-derive", - "serde", -] - -[[package]] -name = "parity-scale-codec-derive" -version = "3.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" -dependencies = [ - "proc-macro-crate 1.2.0", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "parity-util-mem" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c32561d248d352148124f036cac253a644685a21dc9fea383eb4907d7bd35a8f" -dependencies = [ - "cfg-if 1.0.0", - "hashbrown 0.12.3", - "impl-trait-for-tuples", - "parity-util-mem-derive", - "parking_lot", - "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.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16ad52817c4d343339b3bc2e26861bd21478eda0b7509acf83505727000512ac" -dependencies = [ - "byteorder", -] - -[[package]] -name = "parity-wasm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" - -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "redox_syscall", - "smallvec", - "windows-sys", -] - -[[package]] -name = "paste" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9423e2b32f7a043629287a536f21951e8c6a82482d0acb1eeebfc90bc2225b22" - -[[package]] -name = "pbkdf2" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216eaa586a190f0a738f2f918511eecfa90f13295abec0e457cdebcceda80cbd" -dependencies = [ - "crypto-mac 0.8.0", -] - -[[package]] -name = "pbkdf2" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95f5254224e617595d2cc3cc73ff0a5eaf2637519e25f03388154e9378b6ffa" -dependencies = [ - "crypto-mac 0.11.1", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "ppv-lite86" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" - -[[package]] -name = "primitive-types" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28720988bff275df1f51b171e1b2a18c30d194c4d2b61defdacecd625a5d94a" -dependencies = [ - "fixed-hash", - "impl-codec", - "impl-serde", - "scale-info", - "uint", -] - -[[package]] -name = "proc-macro-crate" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" -dependencies = [ - "toml", -] - -[[package]] -name = "proc-macro-crate" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d50bfb8c23f23915855a00d98b5a35ef2e0b871bb52937bacadb798fbb66c8" -dependencies = [ - "once_cell", - "thiserror", - "toml", -] - -[[package]] -name = "proc-macro2" -version = "1.0.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "prost" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" -dependencies = [ - "bytes", - "prost-derive 0.9.0", -] - -[[package]] -name = "prost" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71adf41db68aa0daaefc69bb30bcd68ded9b9abaad5d1fbb6304c4fb390e083e" -dependencies = [ - "bytes", - "prost-derive 0.10.1", -] - -[[package]] -name = "prost-derive" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" -dependencies = [ - "anyhow", - "itertools", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "prost-derive" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b670f45da57fb8542ebdbb6105a925fe571b67f9e7ed9f47a06a84e72b4e7cc" -dependencies = [ - "anyhow", - "itertools", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "prost-types" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" -dependencies = [ - "bytes", - "prost 0.9.0", -] - -[[package]] -name = "prost-types" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d0a014229361011dc8e69c8a1ec6c2e8d0f2af7c91e3ea3f5b2170298461e68" -dependencies = [ - "bytes", - "prost 0.10.4", -] - -[[package]] -name = "quote" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", - "rand_pcg", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.3", -] - -[[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_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.3", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] - -[[package]] -name = "rand_core" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" -dependencies = [ - "getrandom 0.2.7", -] - -[[package]] -name = "rand_distr" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" -dependencies = [ - "num-traits", - "rand 0.8.5", -] - -[[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_pcg" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rawpointer" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" - -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags", -] - -[[package]] -name = "ref-cast" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed13bcd201494ab44900a96490291651d200730904221832b9547d24a87d332b" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5234cd6063258a5e32903b53b1b6ac043a0541c8adc1f610f67b0326c7a578fa" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "regex" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.6.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" - -[[package]] -name = "remove_dir_all" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] - -[[package]] -name = "ripemd160" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eca4ecc81b7f313189bf73ce724400a07da2a6dac19588b03c8bd76a2dcc251" -dependencies = [ - "block-buffer 0.9.0", - "digest 0.9.0", - "opaque-debug 0.3.0", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" - -[[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 = "rustversion" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" - -[[package]] -name = "ryu" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" - -[[package]] -name = "safe-proc-macro2" -version = "1.0.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "814c536dcd27acf03296c618dab7ad62d28e70abd7ba41d3f34a2ce707a2c666" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "safe-quote" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e530f7831f3feafcd5f1aae406ac205dd998436b4007c8e80f03eca78a88f7" -dependencies = [ - "safe-proc-macro2", -] - -[[package]] -name = "safe-regex" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15289bf322e0673d52756a18194167f2378ec1a15fe884af6e2d2cb934822b0" -dependencies = [ - "safe-regex-macro", -] - -[[package]] -name = "safe-regex-compiler" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fba76fae590a2aa665279deb1f57b5098cbace01a0c5e60e262fcf55f7c51542" -dependencies = [ - "safe-proc-macro2", - "safe-quote", -] - -[[package]] -name = "safe-regex-macro" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c2e96b5c03f158d1b16ba79af515137795f4ad4e8de3f790518aae91f1d127" -dependencies = [ - "safe-proc-macro2", - "safe-regex-compiler", -] - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "scale-info" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c46be926081c9f4dd5dd9b6f1d3e3229f2360bc6502dd8836f84a93b7c75e99a" -dependencies = [ - "bitvec", - "cfg-if 1.0.0", - "derive_more", - "parity-scale-codec", - "scale-info-derive", - "serde", -] - -[[package]] -name = "scale-info-derive" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e334bb10a245e28e5fd755cabcafd96cfcd167c99ae63a46924ca8d8703a3c" -dependencies = [ - "proc-macro-crate 1.2.0", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "schnorrkel" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "021b403afe70d81eea68f6ea12f6b3c9588e5d536a94c3bf80f15e7faa267862" -dependencies = [ - "arrayref", - "arrayvec 0.5.2", - "curve25519-dalek 2.1.3", - "getrandom 0.1.16", - "merlin", - "rand 0.7.3", - "rand_core 0.5.1", - "sha2 0.8.2", - "subtle", - "zeroize", -] - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "secp256k1" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c42e6f1735c5f00f51e43e28d6634141f2bcad10931b2609ddd74a86d751260" -dependencies = [ - "secp256k1-sys", -] - -[[package]] -name = "secp256k1-sys" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957da2573cde917463ece3570eab4a0b3f19de6f1646cde62e6fd3868f566036" -dependencies = [ - "cc", -] - -[[package]] -name = "secrecy" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" -dependencies = [ - "zeroize", -] - -[[package]] -name = "semver" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a3186ec9e65071a2095434b1f5bb24838d4e8e130f584c790f6033c79943537" -dependencies = [ - "semver-parser", -] - -[[package]] -name = "semver" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711" -dependencies = [ - "serde", -] - -[[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.142" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e590c437916fb6b221e1d00df6e3294f3fccd70ca7e92541c475d6ed6ef5fee2" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_bytes" -version = "0.11.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_derive" -version = "1.0.142" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34b5b8d809babe02f538c2cfec6f2c1ed10804c0e5a6a041a049a4f5588ccc2e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.83" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7" -dependencies = [ - "itoa 1.0.3", - "ryu", - "serde", -] - -[[package]] -name = "serde_repr" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "sha2" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" -dependencies = [ - "block-buffer 0.7.3", - "digest 0.8.1", - "fake-simd", - "opaque-debug 0.2.3", -] - -[[package]] -name = "sha2" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if 1.0.0", - "cpufeatures", - "digest 0.9.0", - "opaque-debug 0.3.0", -] - -[[package]] -name = "sha2" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" -dependencies = [ - "cfg-if 1.0.0", - "cpufeatures", - "digest 0.10.3", -] - -[[package]] -name = "sha3" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" -dependencies = [ - "block-buffer 0.9.0", - "digest 0.9.0", - "keccak", - "opaque-debug 0.3.0", -] - -[[package]] -name = "sha3" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a31480366ec990f395a61b7c08122d99bd40544fdb5abcfc1b06bb29994312c" -dependencies = [ - "digest 0.10.3", - "keccak", -] - -[[package]] -name = "sharded-slab" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "signature" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f054c6c1a6e95179d6f23ed974060dcefb2d9388bb7256900badad682c499de4" - -[[package]] -name = "simba" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e82063457853d00243beda9952e910b82593e4b07ae9f721b9278a99a0d3d5c" -dependencies = [ - "approx", - "num-complex", - "num-traits", - "paste", -] - -[[package]] -name = "simple-error" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc47a29ce97772ca5c927f75bac34866b16d64e07f330c3248e2d7226623901b" - -[[package]] -name = "slab" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" -dependencies = [ - "autocfg", -] - -[[package]] -name = "smallvec" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" - -[[package]] -name = "sp-api" -version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" -dependencies = [ - "hash-db", - "log", - "parity-scale-codec", - "sp-api-proc-macro", - "sp-core", - "sp-runtime", - "sp-state-machine", - "sp-std 4.0.0", - "sp-version", - "thiserror", -] - -[[package]] -name = "sp-api-proc-macro" -version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" -dependencies = [ - "blake2", - "proc-macro-crate 1.2.0", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "sp-application-crypto" -version = "6.0.0" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" -dependencies = [ - "parity-scale-codec", - "scale-info", - "serde", - "sp-core", - "sp-io", - "sp-std 4.0.0", -] - -[[package]] -name = "sp-arithmetic" -version = "5.0.0" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" -dependencies = [ - "integer-sqrt", - "num-traits", - "parity-scale-codec", - "scale-info", - "serde", - "sp-debug-derive", - "sp-std 4.0.0", - "static_assertions", -] - -[[package]] -name = "sp-core" -version = "6.0.0" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" -dependencies = [ - "base58", - "bitflags", - "blake2-rfc", - "byteorder", - "dyn-clonable", - "ed25519-dalek", - "futures", - "hash-db", - "hash256-std-hasher", - "hex", - "impl-serde", - "lazy_static", - "libsecp256k1", - "log", - "merlin", - "num-traits", - "parity-scale-codec", - "parity-util-mem", - "parking_lot", - "primitive-types", - "rand 0.7.3", - "regex", - "scale-info", - "schnorrkel", - "secp256k1", - "secrecy", - "serde", - "sp-core-hashing", - "sp-debug-derive", - "sp-externalities", - "sp-runtime-interface", - "sp-std 4.0.0", - "sp-storage", - "ss58-registry", - "substrate-bip39", - "thiserror", - "tiny-bip39", - "wasmi", - "zeroize", -] - -[[package]] -name = "sp-core-hashing" -version = "4.0.0" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" -dependencies = [ - "blake2", - "byteorder", - "digest 0.10.3", - "sha2 0.10.2", - "sha3 0.10.2", - "sp-std 4.0.0", - "twox-hash", -] - -[[package]] -name = "sp-core-hashing-proc-macro" -version = "5.0.0" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" -dependencies = [ - "proc-macro2", - "quote", - "sp-core-hashing", - "syn", -] - -[[package]] -name = "sp-debug-derive" -version = "4.0.0" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "sp-externalities" -version = "0.12.0" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" -dependencies = [ - "environmental", - "parity-scale-codec", - "sp-std 4.0.0", - "sp-storage", -] - -[[package]] -name = "sp-inherents" -version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" -dependencies = [ - "async-trait", - "impl-trait-for-tuples", - "parity-scale-codec", - "sp-core", - "sp-runtime", - "sp-std 4.0.0", - "thiserror", -] - -[[package]] -name = "sp-io" -version = "6.0.0" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" -dependencies = [ - "futures", - "hash-db", - "libsecp256k1", - "log", - "parity-scale-codec", - "parking_lot", - "secp256k1", - "sp-core", - "sp-externalities", - "sp-keystore", - "sp-runtime-interface", - "sp-state-machine", - "sp-std 4.0.0", - "sp-tracing", - "sp-trie", - "sp-wasm-interface", - "tracing", - "tracing-core", -] - -[[package]] -name = "sp-keyring" -version = "6.0.0" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" -dependencies = [ - "lazy_static", - "sp-core", - "sp-runtime", - "strum", -] - -[[package]] -name = "sp-keystore" -version = "0.12.0" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" -dependencies = [ - "async-trait", - "futures", - "merlin", - "parity-scale-codec", - "parking_lot", - "schnorrkel", - "sp-core", - "sp-externalities", - "thiserror", -] - -[[package]] -name = "sp-maybe-compressed-blob" -version = "4.1.0-dev" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" -dependencies = [ - "thiserror", - "zstd", -] - -[[package]] -name = "sp-panic-handler" -version = "4.0.0" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" -dependencies = [ - "backtrace", - "lazy_static", - "regex", -] - -[[package]] -name = "sp-runtime" -version = "6.0.0" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" -dependencies = [ - "either", - "hash256-std-hasher", - "impl-trait-for-tuples", - "log", - "parity-scale-codec", - "parity-util-mem", - "paste", - "rand 0.7.3", - "scale-info", - "serde", - "sp-application-crypto", - "sp-arithmetic", - "sp-core", - "sp-io", - "sp-std 4.0.0", -] - -[[package]] -name = "sp-runtime-interface" -version = "6.0.0" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" -dependencies = [ - "impl-trait-for-tuples", - "parity-scale-codec", - "primitive-types", - "sp-externalities", - "sp-runtime-interface-proc-macro", - "sp-std 4.0.0", - "sp-storage", - "sp-tracing", - "sp-wasm-interface", - "static_assertions", -] - -[[package]] -name = "sp-runtime-interface-proc-macro" -version = "5.0.0" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" -dependencies = [ - "Inflector", - "proc-macro-crate 1.2.0", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "sp-staking" -version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" -dependencies = [ - "parity-scale-codec", - "scale-info", - "sp-runtime", - "sp-std 4.0.0", -] - -[[package]] -name = "sp-state-machine" -version = "0.12.0" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" -dependencies = [ - "hash-db", - "log", - "num-traits", - "parity-scale-codec", - "parking_lot", - "rand 0.7.3", - "smallvec", - "sp-core", - "sp-externalities", - "sp-panic-handler", - "sp-std 4.0.0", - "sp-trie", - "thiserror", - "tracing", - "trie-db", - "trie-root", -] - -[[package]] -name = "sp-std" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35391ea974fa5ee869cb094d5b437688fbf3d8127d64d1b9fed5822a1ed39b12" - -[[package]] -name = "sp-std" -version = "4.0.0" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" - -[[package]] -name = "sp-storage" -version = "6.0.0" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" -dependencies = [ - "impl-serde", - "parity-scale-codec", - "ref-cast", - "serde", - "sp-debug-derive", - "sp-std 4.0.0", -] - -[[package]] -name = "sp-tracing" -version = "5.0.0" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" -dependencies = [ - "parity-scale-codec", - "sp-std 4.0.0", - "tracing", - "tracing-core", - "tracing-subscriber", -] - -[[package]] -name = "sp-trie" -version = "6.0.0" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" -dependencies = [ - "hash-db", - "memory-db", - "parity-scale-codec", - "scale-info", - "sp-core", - "sp-std 4.0.0", - "thiserror", - "trie-db", - "trie-root", -] - -[[package]] -name = "sp-version" -version = "5.0.0" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" -dependencies = [ - "impl-serde", - "parity-scale-codec", - "parity-wasm 0.42.2", - "scale-info", - "serde", - "sp-core-hashing-proc-macro", - "sp-runtime", - "sp-std 4.0.0", - "sp-version-proc-macro", - "thiserror", -] - -[[package]] -name = "sp-version-proc-macro" -version = "4.0.0-dev" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" -dependencies = [ - "parity-scale-codec", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "sp-wasm-interface" -version = "6.0.0" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" -dependencies = [ - "impl-trait-for-tuples", - "log", - "parity-scale-codec", - "sp-std 4.0.0", - "wasmi", -] - -[[package]] -name = "ss58-registry" -version = "1.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a039906277e0d8db996cd9d1ef19278c10209d994ecfc1025ced16342873a17c" -dependencies = [ - "Inflector", - "num-format", - "proc-macro2", - "quote", - "serde", - "serde_json", - "unicode-xid", -] - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "statrs" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05bdbb8e4e78216a85785a85d3ec3183144f98d0097b9281802c019bb07a6f05" -dependencies = [ - "approx", - "lazy_static", - "nalgebra", - "num-traits", - "rand 0.8.5", -] - -[[package]] -name = "strum" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cae14b91c7d11c9a851d3fbc80a963198998c2a64eec840477fa92d8ce9b70bb" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb0dc7ee9c15cea6199cde9a127fa16a4c5819af85395457ad72d68edc85a38" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "rustversion", - "syn", -] - -[[package]] -name = "substrate-bip39" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49eee6965196b32f882dd2ee85a92b1dbead41b04e53907f269de3b0dc04733c" -dependencies = [ - "hmac 0.11.0", - "pbkdf2 0.8.0", - "schnorrkel", - "sha2 0.9.9", - "zeroize", -] - -[[package]] -name = "substrate-wasm-builder" -version = "5.0.0-dev" -source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.18#fc3fd073d3a0acf9933c3994b660ebd7b5833f65" -dependencies = [ - "ansi_term", - "build-helper", - "cargo_metadata", - "sp-maybe-compressed-blob", - "strum", - "tempfile", - "toml", - "walkdir", - "wasm-gc-api", -] - -[[package]] -name = "subtle" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" - -[[package]] -name = "subtle-encoding" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dcb1ed7b8330c5eed5441052651dd7a12c75e2ed88f2ec024ae1fa3a5e59945" -dependencies = [ - "zeroize", -] - -[[package]] -name = "syn" -version = "1.0.97" -source = "git+https://github.com/DaviRain-Su/syn.git?branch=branch-1.0.97#17682c67bd1c4de8662fda06a3254231dfeed86c" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "synstructure" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "unicode-xid", -] - -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - -[[package]] -name = "tempfile" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" -dependencies = [ - "cfg-if 1.0.0", - "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi", -] - -[[package]] -name = "tendermint" -version = "0.23.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ca881fa4dedd2b46334f13be7fbc8cc1549ba4be5a833fe4e73d1a1baaf7949" -dependencies = [ - "async-trait", - "bytes", - "ed25519", - "ed25519-dalek", - "flex-error", - "futures", - "num-traits", - "once_cell", - "prost 0.10.4", - "prost-types 0.10.1", - "serde", - "serde_bytes", - "serde_json", - "serde_repr", - "sha2 0.9.9", - "signature", - "subtle", - "subtle-encoding", - "tendermint-proto", - "time 0.3.11", - "zeroize", -] - -[[package]] -name = "tendermint-light-client-verifier" -version = "0.23.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ae030a759b89cca84860d497d4d4e491615d8a9243cc04c61cd89335ba9b593" -dependencies = [ - "derive_more", - "flex-error", - "serde", - "tendermint", - "time 0.3.11", -] - -[[package]] -name = "tendermint-proto" -version = "0.23.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71f925d74903f4abbdc4af0110635a307b3cb05b175fdff4a7247c14a4d0874" -dependencies = [ - "bytes", - "flex-error", - "num-derive", - "num-traits", - "prost 0.10.4", - "prost-types 0.10.1", - "serde", - "serde_bytes", - "subtle-encoding", - "time 0.3.11", -] - -[[package]] -name = "tendermint-testgen" -version = "0.23.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "442ede2d01e61466e515fd7f1d0aac7c3c86b3066535479caa86a43afb5e2e17" -dependencies = [ - "ed25519-dalek", - "gumdrop", - "serde", - "serde_json", - "simple-error", - "tempfile", - "tendermint", - "time 0.3.11", -] - -[[package]] -name = "thiserror" -version = "1.0.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "thread_local" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" -dependencies = [ - "once_cell", -] - -[[package]] -name = "time" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", -] - -[[package]] -name = "time" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" -dependencies = [ - "libc", - "num_threads", - "time-macros", -] - -[[package]] -name = "time-macros" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" - -[[package]] -name = "tiny-bip39" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc59cb9dfc85bb312c3a78fd6aa8a8582e310b0fa885d5bb877f6dcc601839d" -dependencies = [ - "anyhow", - "hmac 0.8.1", - "once_cell", - "pbkdf2 0.4.0", - "rand 0.7.3", - "rustc-hash", - "sha2 0.9.9", - "thiserror", - "unicode-normalization", - "wasm-bindgen", - "zeroize", -] - -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - -[[package]] -name = "tinyvec" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" - -[[package]] -name = "toml" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" -dependencies = [ - "serde", -] - -[[package]] -name = "tracing" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" -dependencies = [ - "cfg-if 1.0.0", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" -dependencies = [ - "lazy_static", - "log", - "tracing-core", -] - -[[package]] -name = "tracing-serde" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" -dependencies = [ - "serde", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71" -dependencies = [ - "ansi_term", - "chrono", - "lazy_static", - "matchers", - "regex", - "serde", - "serde_json", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", - "tracing-serde", -] - -[[package]] -name = "trie-db" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32d034c0d3db64b43c31de38e945f15b40cd4ca6d2dcfc26d4798ce8de4ab83" -dependencies = [ - "hash-db", - "hashbrown 0.12.3", - "log", - "rustc-hex", - "smallvec", -] - -[[package]] -name = "trie-root" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a36c5ca3911ed3c9a5416ee6c679042064b93fc637ded67e25f92e68d783891" -dependencies = [ - "hash-db", -] - -[[package]] -name = "tt-call" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e66dcbec4290c69dd03c57e76c2469ea5c7ce109c6dd4351c13055cf71ea055" - -[[package]] -name = "twox-hash" -version = "1.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" -dependencies = [ - "cfg-if 1.0.0", - "digest 0.10.3", - "rand 0.8.5", - "static_assertions", -] - -[[package]] -name = "typenum" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" - -[[package]] -name = "uint" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" -dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", -] - -[[package]] -name = "unicode-ident" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" - -[[package]] -name = "unicode-normalization" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-segmentation" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" - -[[package]] -name = "unicode-xid" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" - -[[package]] -name = "valuable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "walkdir" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" -dependencies = [ - "same-file", - "winapi", - "winapi-util", -] - -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-bindgen" -version = "0.2.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" -dependencies = [ - "cfg-if 1.0.0", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" - -[[package]] -name = "wasm-gc-api" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c32691b6c7e6c14e7f8fd55361a9088b507aa49620fcd06c09b3a1082186b9" -dependencies = [ - "log", - "parity-wasm 0.32.0", - "rustc-demangle", -] - -[[package]] -name = "wasmi" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca00c5147c319a8ec91ec1a0edbec31e566ce2c9cc93b3f9bb86a9efd0eb795d" -dependencies = [ - "downcast-rs", - "libc", - "memory_units", - "num-rational 0.2.4", - "num-traits", - "parity-wasm 0.42.2", - "wasmi-validation", -] - -[[package]] -name = "wasmi-validation" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "165343ecd6c018fc09ebcae280752702c9a2ef3e6f8d02f1cfcbdb53ef6d7937" -dependencies = [ - "parity-wasm 0.42.2", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -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-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - -[[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 = "windows-sys" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" -dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - -[[package]] -name = "windows_i686_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - -[[package]] -name = "windows_i686_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" - -[[package]] -name = "wyz" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" -dependencies = [ - "tap", -] - -[[package]] -name = "zeroize" -version = "1.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zstd" -version = "0.9.2+zstd.1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2390ea1bf6c038c39674f22d95f0564725fc06034a47129179810b2fc58caa54" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "4.1.3+zstd.1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e99d81b99fb3c2c2c794e3fe56c305c63d5173a16a46b5850b07c935ffc7db79" -dependencies = [ - "libc", - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "1.6.2+zstd.1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2daf2f248d9ea44454bfcb2516534e8b8ad2fc91bf818a1885495fc42bc8ac9f" -dependencies = [ - "cc", - "libc", -] From 0b84cd4d445e3703c5bd44bc7a70a0d3dfcbf9fa Mon Sep 17 00:00:00 2001 From: Yuanchao Sun Date: Wed, 24 Aug 2022 09:28:11 +0800 Subject: [PATCH 479/484] Delete LICENSE --- frame/ibc/LICENSE | 201 ---------------------------------------------- 1 file changed, 201 deletions(-) delete mode 100644 frame/ibc/LICENSE diff --git a/frame/ibc/LICENSE b/frame/ibc/LICENSE deleted file mode 100644 index 261eeb9e9f8b2..0000000000000 --- a/frame/ibc/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. From a2e7ffaa1f24c780aed0c41348bf4f856419ffc1 Mon Sep 17 00:00:00 2001 From: Yuanchao Sun Date: Wed, 24 Aug 2022 09:28:25 +0800 Subject: [PATCH 480/484] Delete Makefile --- frame/ibc/Makefile | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 frame/ibc/Makefile diff --git a/frame/ibc/Makefile b/frame/ibc/Makefile deleted file mode 100644 index 622944ab4571e..0000000000000 --- a/frame/ibc/Makefile +++ /dev/null @@ -1,2 +0,0 @@ -check: - cargo check --no-default-features --target=wasm32-unknown-unknown \ No newline at end of file From 0e930747c32d3d7bfb5ddeab729004897344c52a Mon Sep 17 00:00:00 2001 From: Davirain Date: Wed, 24 Aug 2022 11:57:42 +0800 Subject: [PATCH 481/484] update readme --- frame/ibc/README.md | 30 +++++------------------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/frame/ibc/README.md b/frame/ibc/README.md index a3badac963cad..2bb71b0b97cd6 100644 --- a/frame/ibc/README.md +++ b/frame/ibc/README.md @@ -1,37 +1,17 @@ # Substrate IBC Pallet (work in progress) -[![crates.io](https://img.shields.io/crates/v/pallet-ibc.svg)](https://crates.io/crates/pallet-ibc) -[![Released API docs](https://docs.rs/pallet-ibc/badge.svg)](https://docs.rs/pallet-ibc) This project is [funded by Interchain Foundation](https://interchain-io.medium.com/ibc-on-substrate-with-cdot-a7025e521028). -## Purpose +## Overview This pallet implements the standard [IBC protocol](https://github.com/cosmos/ics). The goal of this pallet is to allow the blockchains built on Substrate to gain the ability to interact with other chains in a trustless way via IBC protocol. -This project is currently in an early stage and will eventually be submitted to upstream. - The pallet implements the chain specific logic of [ICS spec](https://github.com/cosmos/ibc/tree/51f0c9e8d8ebcbe6f7f023a8b80f65a8fab705e3/spec), and is integrated with [ibc-rs](https://github.com/informalsystems/ibc-rs), which implements the generic cross-chain logic in [ICS spec](https://github.com/cosmos/ibc/tree/51f0c9e8d8ebcbe6f7f023a8b80f65a8fab705e3/spec). -The chain specific logic of the modules in ICS spec implemented: -* ics-002-client-semantics -* ics-003-connection-semantics -* ics-004-channel-and-packet-semantics -* ics-005-port-allocation -* ics-010-grandpa-client -* ics-018-relayer-algorithms -* ics-023-vector-commitments -* ics-024-host-requirements -* ics-025-handler-interface -* ics-026-routing-module - -## Reference Docs - -You can view the reference docs for this pallet by running: - -``` -cargo doc --open -``` +## Interface -or by visiting this site: https://docs.rs/pallet-ibc \ No newline at end of file +### Dispatchable Functions +- `deliver` - This function acts as an entry for most of the IBC request. I.e., create clients, update clients, handshakes to create channels, ...etc +- `raw_transfer` - ICS20 fungible token transfer, Handling transfer request as sending chain or receiving chain. \ No newline at end of file From 455493cf110fc7f3db61eaac01e00b11429c7fef Mon Sep 17 00:00:00 2001 From: Davirain Date: Wed, 24 Aug 2022 18:13:21 +0800 Subject: [PATCH 482/484] add license notion --- frame/ibc/Cargo.toml | 4 +- frame/ibc/src/context.rs | 17 ++++ frame/ibc/src/events.rs | 17 ++++ frame/ibc/src/lib.rs | 98 ++++++++++++------- frame/ibc/src/mock.rs | 19 ++++ frame/ibc/src/module/applications/mod.rs | 18 ++++ .../module/applications/transfer/channel.rs | 17 ++++ .../src/module/applications/transfer/mod.rs | 17 ++++ .../transfer/transfer_handle_callback.rs | 17 ++++ frame/ibc/src/module/core/ics02_client.rs | 17 ++++ frame/ibc/src/module/core/ics03_connection.rs | 17 ++++ frame/ibc/src/module/core/ics04_channel.rs | 17 ++++ frame/ibc/src/module/core/ics05_port.rs | 17 ++++ frame/ibc/src/module/core/ics24_host.rs | 17 ++++ frame/ibc/src/module/core/ics26_routing.rs | 17 ++++ frame/ibc/src/module/core/mod.rs | 17 ++++ frame/ibc/src/module/mod.rs | 17 ++++ frame/ibc/src/module/relayer/mod.rs | 17 ++++ frame/ibc/src/tests.rs | 18 ++++ frame/ibc/src/traits.rs | 17 ++++ frame/ibc/src/utils.rs | 17 ++++ 21 files changed, 390 insertions(+), 39 deletions(-) diff --git a/frame/ibc/Cargo.toml b/frame/ibc/Cargo.toml index 683c105de4f91..15b11038342d5 100644 --- a/frame/ibc/Cargo.toml +++ b/frame/ibc/Cargo.toml @@ -4,7 +4,7 @@ version = "3.0.0-pre.0" authors = ['Octopus Network '] edition = '2021' license = "Apache-2.0" -homepage = "https://oct.network" +homepage = "https://docs.substrate.io/" repository = "https://github.com/octopus-network/substrate-ibc/" description = "An IBC implementation on Substrate." readme = "README.md" @@ -35,7 +35,6 @@ time = { version = "0.3.11", features = ["macros","parsing"], default-features = ibc = { version = "0.18.0", default-features = false } ibc-proto = { version = "0.20.0", default-features = false } tendermint-proto = { version = "=0.23.9", default-features = false } -# beefy-light-client = { git = "https://github.com/octopus-network/beefy-light-client.git", branch="main", default-features = false } frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } @@ -75,7 +74,6 @@ std = [ 'prost/std', 'ibc/std', 'ibc-proto/std', - # 'beefy-light-client/std', 'serde_json/std', 'serde/std', 'flex-error/std', diff --git a/frame/ibc/src/context.rs b/frame/ibc/src/context.rs index 6ab7af3e27f0c..8508e5e65c650 100644 --- a/frame/ibc/src/context.rs +++ b/frame/ibc/src/context.rs @@ -1,3 +1,20 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + use crate::*; use alloc::{ borrow::{Borrow, Cow, ToOwned}, diff --git a/frame/ibc/src/events.rs b/frame/ibc/src/events.rs index b0a1e546e9164..92db60337f2d3 100644 --- a/frame/ibc/src/events.rs +++ b/frame/ibc/src/events.rs @@ -1,3 +1,20 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + use crate::*; use core::borrow::Borrow; use ibc::{core::ics26_routing, events::IbcEvent as RawIbcEvent}; diff --git a/frame/ibc/src/lib.rs b/frame/ibc/src/lib.rs index 2d0236f85a87d..06009d758fb5f 100644 --- a/frame/ibc/src/lib.rs +++ b/frame/ibc/src/lib.rs @@ -1,3 +1,38 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Substrate IBC Pallet (work in progress) +//! +//! This project is [funded by Interchain Foundation](https://interchain-io.medium.com/ibc-on-substrate-with-cdot-a7025e521028). +//! +//! ## Overview +//! +//! This pallet implements the standard [IBC protocol](https://github.com/cosmos/ics). +//! +//! The goal of this pallet is to allow the blockchains built on Substrate to gain the ability to interact with other chains in a trustless way via IBC protocol. +//! +//! The pallet implements the chain specific logic of [ICS spec](https://github.com/cosmos/ibc/tree/51f0c9e8d8ebcbe6f7f023a8b80f65a8fab705e3/spec), and is integrated with [ibc-rs](https://github.com/informalsystems/ibc-rs), which implements the generic cross-chain logic in [ICS spec](https://github.com/cosmos/ibc/tree/51f0c9e8d8ebcbe6f7f023a8b80f65a8fab705e3/spec). +//! +//! ## Interface +//! +//! ### Dispatchable Functions +//! - `deliver` - This function acts as an entry for most of the IBC request. I.e., create clients, update clients, handshakes to create channels, ...etc +//! - `raw_transfer` - ICS20 fungible token transfer, Handling transfer request as sending chain or receiving chain. + #![cfg_attr(not(feature = "std"), no_std)] // todo need in future to remove #![allow(unreachable_code)] @@ -10,15 +45,6 @@ #![allow(unused_variables)] #![allow(clippy::too_many_arguments)] -//! # Overview -//! -//! The goal of this pallet is to allow the blockchains built on Substrate to gain the ability to -//! interact with other chains in a trustees way via IBC protocol -//! -//! The pallet implements the chain specific logic of [ICS spec](https://github.com/cosmos/ibc/tree/ee71d0640c23ec4e05e924f52f557b5e06c1d82f), -//! and is integrated with [ibc-rs](https://github.com/informalsystems/ibc-rs), -//! which implements the generic cross-chain logic in [ICS spec](https://github.com/cosmos/ibc/tree/ee71d0640c23ec4e05e924f52f557b5e06c1d82f). - extern crate alloc; extern crate core; @@ -359,37 +385,37 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - /// New block event + /// New block NewBlock { height: Height }, - /// Client Create event + /// Client Create CreateClient { height: Height, client_id: ClientId, client_type: ClientType, consensus_height: Height, }, - /// Client update event + /// Client update UpdateClient { height: Height, client_id: ClientId, client_type: ClientType, consensus_height: Height, }, - /// Client upgrade event + /// Client upgraded UpgradeClient { height: Height, client_id: ClientId, client_type: ClientType, consensus_height: Height, }, - /// Client misbehaviour event + /// Client misbehaviour ClientMisbehaviour { height: Height, client_id: ClientId, client_type: ClientType, consensus_height: Height, }, - /// Connection open init event + /// Connection open init OpenInitConnection { height: Height, connection_id: Option, @@ -397,7 +423,7 @@ pub mod pallet { counterparty_connection_id: Option, counterparty_client_id: ClientId, }, - /// Connection open try event + /// Connection open try OpenTryConnection { height: Height, connection_id: Option, @@ -405,7 +431,7 @@ pub mod pallet { counterparty_connection_id: Option, counterparty_client_id: ClientId, }, - /// Connection open acknowledgement event + /// Connection open ack OpenAckConnection { height: Height, connection_id: Option, @@ -413,7 +439,7 @@ pub mod pallet { counterparty_connection_id: Option, counterparty_client_id: ClientId, }, - /// Connection open confirm event + /// Connection open confirm OpenConfirmConnection { height: Height, connection_id: Option, @@ -421,7 +447,7 @@ pub mod pallet { counterparty_connection_id: Option, counterparty_client_id: ClientId, }, - /// Channel open init event + /// Channel open init OpenInitChannel { height: Height, port_id: PortId, @@ -430,7 +456,7 @@ pub mod pallet { counterparty_port_id: PortId, counterparty_channel_id: Option, }, - /// Channel open try event + /// Channel open try OpenTryChannel { height: Height, port_id: PortId, @@ -439,7 +465,7 @@ pub mod pallet { counterparty_port_id: PortId, counterparty_channel_id: Option, }, - /// Channel open acknowledgement event + /// Channel open ack OpenAckChannel { height: Height, port_id: PortId, @@ -448,7 +474,7 @@ pub mod pallet { counterparty_port_id: PortId, counterparty_channel_id: Option, }, - /// Channel open confirm event + /// Channel open confirm OpenConfirmChannel { height: Height, port_id: PortId, @@ -457,7 +483,7 @@ pub mod pallet { counterparty_port_id: PortId, counterparty_channel_id: Option, }, - /// Channel close init event + /// Channel close init CloseInitChannel { height: Height, port_id: PortId, @@ -466,7 +492,7 @@ pub mod pallet { counterparty_port_id: PortId, counterparty_channel_id: Option, }, - /// Channel close confirm event + /// Channel close confirm CloseConfirmChannel { height: Height, port_id: PortId, @@ -475,33 +501,33 @@ pub mod pallet { counterparty_port_id: PortId, counterparty_channel_id: Option, }, - /// Send packet event + /// Send packet SendPacket { height: Height, packet: Packet }, - /// Receive packet event + /// Receive packet ReceivePacket { height: Height, packet: Packet }, - /// WriteAcknowledgement packet event + /// WriteAcknowledgement packet WriteAcknowledgement { height: Height, packet: Packet, ack: Vec }, - /// Acknowledgements packet event + /// Acknowledgements packet AcknowledgePacket { height: Height, packet: Packet }, - /// Timeout packet event + /// Timeout packet TimeoutPacket { height: Height, packet: Packet }, - /// TimoutOnClose packet event + /// TimoutOnClose packet TimeoutOnClosePacket { height: Height, packet: Packet }, - /// Chain Error event + /// Chain error ChainError(Vec), - /// App Module event + /// App Module AppModule(ModuleEvent), - /// Transfer native token event + /// Transfer native token TransferNativeToken(T::AccountIdConversion, T::AccountIdConversion, BalanceOf), - /// Transfer non-native token event + /// Transfer non-native token TransferNoNativeToken( T::AccountIdConversion, T::AccountIdConversion, ::AssetBalance, ), - /// Burn cross chain token event + /// Burn cross chain token BurnToken(T::AssetId, T::AccountIdConversion, T::AssetBalance), - /// Mint chairperson token event + /// Mint chairperson token MintToken(T::AssetId, T::AccountIdConversion, T::AssetBalance), } diff --git a/frame/ibc/src/mock.rs b/frame/ibc/src/mock.rs index d4756f898df97..2ce8fa8c563c6 100644 --- a/frame/ibc/src/mock.rs +++ b/frame/ibc/src/mock.rs @@ -1,3 +1,22 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Test utilities + use crate as pallet_ibc; pub use frame_support::{ construct_runtime, parameter_types, diff --git a/frame/ibc/src/module/applications/mod.rs b/frame/ibc/src/module/applications/mod.rs index 014e52f2771e2..edd4016b6cadd 100644 --- a/frame/ibc/src/module/applications/mod.rs +++ b/frame/ibc/src/module/applications/mod.rs @@ -1 +1,19 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + pub mod transfer; + diff --git a/frame/ibc/src/module/applications/transfer/channel.rs b/frame/ibc/src/module/applications/transfer/channel.rs index 459a9a68db464..7d116fff5e906 100644 --- a/frame/ibc/src/module/applications/transfer/channel.rs +++ b/frame/ibc/src/module/applications/transfer/channel.rs @@ -1,3 +1,20 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + use super::transfer_handle_callback::TransferModule; use crate::*; use core::{str::FromStr, time::Duration}; diff --git a/frame/ibc/src/module/applications/transfer/mod.rs b/frame/ibc/src/module/applications/transfer/mod.rs index 86507b95a62bf..2c638c7f9d54f 100644 --- a/frame/ibc/src/module/applications/transfer/mod.rs +++ b/frame/ibc/src/module/applications/transfer/mod.rs @@ -1,3 +1,20 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + pub mod channel; pub mod transfer_handle_callback; diff --git a/frame/ibc/src/module/applications/transfer/transfer_handle_callback.rs b/frame/ibc/src/module/applications/transfer/transfer_handle_callback.rs index e4314506cb2f4..4abf0f625bba4 100644 --- a/frame/ibc/src/module/applications/transfer/transfer_handle_callback.rs +++ b/frame/ibc/src/module/applications/transfer/transfer_handle_callback.rs @@ -1,3 +1,20 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + use crate::*; use crate::utils::host_height; diff --git a/frame/ibc/src/module/core/ics02_client.rs b/frame/ibc/src/module/core/ics02_client.rs index 84f803f2f771c..e555dec03d738 100644 --- a/frame/ibc/src/module/core/ics02_client.rs +++ b/frame/ibc/src/module/core/ics02_client.rs @@ -1,3 +1,20 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + use crate::*; use alloc::string::ToString; use core::str::FromStr; diff --git a/frame/ibc/src/module/core/ics03_connection.rs b/frame/ibc/src/module/core/ics03_connection.rs index 82be9973e5032..82a9803cfa972 100644 --- a/frame/ibc/src/module/core/ics03_connection.rs +++ b/frame/ibc/src/module/core/ics03_connection.rs @@ -1,3 +1,20 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + use crate::*; use crate::context::Context; diff --git a/frame/ibc/src/module/core/ics04_channel.rs b/frame/ibc/src/module/core/ics04_channel.rs index 38b4f9bd85e9b..b17a4a48dd6c4 100644 --- a/frame/ibc/src/module/core/ics04_channel.rs +++ b/frame/ibc/src/module/core/ics04_channel.rs @@ -1,3 +1,20 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + use crate::*; use core::{str::FromStr, time::Duration}; use log::{error, info, trace, warn}; diff --git a/frame/ibc/src/module/core/ics05_port.rs b/frame/ibc/src/module/core/ics05_port.rs index 923b7fcff8b3c..5ac325bbb6ed8 100644 --- a/frame/ibc/src/module/core/ics05_port.rs +++ b/frame/ibc/src/module/core/ics05_port.rs @@ -1,3 +1,20 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + use crate::*; use log::trace; diff --git a/frame/ibc/src/module/core/ics24_host.rs b/frame/ibc/src/module/core/ics24_host.rs index 7a098ef322c73..6d08244f1f1e8 100644 --- a/frame/ibc/src/module/core/ics24_host.rs +++ b/frame/ibc/src/module/core/ics24_host.rs @@ -1,3 +1,20 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + use crate::{alloc::string::ToString, from_channel_id_to_vec, Config, Event, REVISION_NUMBER}; use alloc::string::String; use ibc::{ diff --git a/frame/ibc/src/module/core/ics26_routing.rs b/frame/ibc/src/module/core/ics26_routing.rs index 09839a2565b90..fedfeba33e675 100644 --- a/frame/ibc/src/module/core/ics26_routing.rs +++ b/frame/ibc/src/module/core/ics26_routing.rs @@ -1,3 +1,20 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + use crate::{context::Context, *}; use alloc::{ borrow::{Borrow, Cow, ToOwned}, diff --git a/frame/ibc/src/module/core/mod.rs b/frame/ibc/src/module/core/mod.rs index 866ed4450fafa..9b9d7c270089d 100644 --- a/frame/ibc/src/module/core/mod.rs +++ b/frame/ibc/src/module/core/mod.rs @@ -1,3 +1,20 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + pub mod ics02_client; pub mod ics03_connection; pub mod ics04_channel; diff --git a/frame/ibc/src/module/mod.rs b/frame/ibc/src/module/mod.rs index 250d43e9d1bfe..d475ce0d9fff3 100644 --- a/frame/ibc/src/module/mod.rs +++ b/frame/ibc/src/module/mod.rs @@ -1,3 +1,20 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + pub mod applications; pub mod core; pub mod relayer; diff --git a/frame/ibc/src/module/relayer/mod.rs b/frame/ibc/src/module/relayer/mod.rs index 9f520b96f15f9..3f18d85aa2537 100644 --- a/frame/ibc/src/module/relayer/mod.rs +++ b/frame/ibc/src/module/relayer/mod.rs @@ -1,3 +1,20 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + use crate::{ alloc::string::ToString, context::Context, utils::host_height, Config, REVISION_NUMBER, }; diff --git a/frame/ibc/src/tests.rs b/frame/ibc/src/tests.rs index 45f7a9b2634b0..1a30744f43137 100644 --- a/frame/ibc/src/tests.rs +++ b/frame/ibc/src/tests.rs @@ -1,3 +1,21 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Tests for the ibc pallet. use super::*; use crate::{mock::*, Context}; use core::str::FromStr; diff --git a/frame/ibc/src/traits.rs b/frame/ibc/src/traits.rs index ef2209cd7a70d..6ff204fc02a0c 100644 --- a/frame/ibc/src/traits.rs +++ b/frame/ibc/src/traits.rs @@ -1,3 +1,20 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + use alloc::vec::Vec; /// A trait handling asset ID and name diff --git a/frame/ibc/src/utils.rs b/frame/ibc/src/utils.rs index eda1b8c82b813..cd1917b5e7e98 100644 --- a/frame/ibc/src/utils.rs +++ b/frame/ibc/src/utils.rs @@ -1,3 +1,20 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + use crate::Config; use codec::Encode; use scale_info::prelude::{fmt::Debug, format, vec::Vec}; From 612ac6a84819a81f26777481d66da27c4489a1e4 Mon Sep 17 00:00:00 2001 From: Davirain Date: Wed, 24 Aug 2022 18:13:41 +0800 Subject: [PATCH 483/484] format code --- frame/ibc/src/lib.rs | 11 +++++++---- frame/ibc/src/module/applications/mod.rs | 1 - 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/frame/ibc/src/lib.rs b/frame/ibc/src/lib.rs index 06009d758fb5f..867e0e7917542 100644 --- a/frame/ibc/src/lib.rs +++ b/frame/ibc/src/lib.rs @@ -20,18 +20,21 @@ //! This project is [funded by Interchain Foundation](https://interchain-io.medium.com/ibc-on-substrate-with-cdot-a7025e521028). //! //! ## Overview -//! +//! //! This pallet implements the standard [IBC protocol](https://github.com/cosmos/ics). //! -//! The goal of this pallet is to allow the blockchains built on Substrate to gain the ability to interact with other chains in a trustless way via IBC protocol. +//! The goal of this pallet is to allow the blockchains built on Substrate to gain the ability to +//! interact with other chains in a trustless way via IBC protocol. //! //! The pallet implements the chain specific logic of [ICS spec](https://github.com/cosmos/ibc/tree/51f0c9e8d8ebcbe6f7f023a8b80f65a8fab705e3/spec), and is integrated with [ibc-rs](https://github.com/informalsystems/ibc-rs), which implements the generic cross-chain logic in [ICS spec](https://github.com/cosmos/ibc/tree/51f0c9e8d8ebcbe6f7f023a8b80f65a8fab705e3/spec). //! //! ## Interface //! //! ### Dispatchable Functions -//! - `deliver` - This function acts as an entry for most of the IBC request. I.e., create clients, update clients, handshakes to create channels, ...etc -//! - `raw_transfer` - ICS20 fungible token transfer, Handling transfer request as sending chain or receiving chain. +//! - `deliver` - This function acts as an entry for most of the IBC request. I.e., create clients, +//! update clients, handshakes to create channels, ...etc +//! - `raw_transfer` - ICS20 fungible token transfer, Handling transfer request as sending chain or +//! receiving chain. #![cfg_attr(not(feature = "std"), no_std)] // todo need in future to remove diff --git a/frame/ibc/src/module/applications/mod.rs b/frame/ibc/src/module/applications/mod.rs index edd4016b6cadd..dc8a5fa54cd3a 100644 --- a/frame/ibc/src/module/applications/mod.rs +++ b/frame/ibc/src/module/applications/mod.rs @@ -16,4 +16,3 @@ // limitations under the License. pub mod transfer; - From ec9857490f1b05dd043b2574103ef6ed6c01f6d9 Mon Sep 17 00:00:00 2001 From: Yuanchao Sun Date: Wed, 5 Oct 2022 15:10:25 +0800 Subject: [PATCH 484/484] Reset Cargo.lock --- Cargo.lock | 2782 +++++++++++++++++++++++++--------------------------- 1 file changed, 1333 insertions(+), 1449 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6a12a605b1483..a35dbba7d089e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,7 +18,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" dependencies = [ - "gimli", + "gimli 0.26.1", ] [[package]] @@ -29,30 +29,30 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aead" -version = "0.4.3" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +checksum = "6e3e798aa0c8239776f54415bc06f3d74b1850f3f830b45c35cfc80556973f70" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.4", ] [[package]] name = "aes" -version = "0.7.5" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +checksum = "495ee669413bfbe9e8cace80f4d3d78e6d8c8d99579f97fb93bde351b185f2d4" dependencies = [ "cfg-if 1.0.0", "cipher", - "cpufeatures", + "cpufeatures 0.1.5", "opaque-debug 0.3.0", ] [[package]] name = "aes-gcm" -version = "0.9.4" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6" +checksum = "b2a930fd487faaa92a30afa92cc9dd1526a5cff67124abbbb1c617ce070f4dcf" dependencies = [ "aead", "aes", @@ -68,7 +68,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.7", + "getrandom 0.2.3", "once_cell", "version_check", ] @@ -82,15 +82,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "android_system_properties" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7ed72e1635e121ca3e79420540282af22da58be50de153d36f81ddc6b83aa9e" -dependencies = [ - "libc", -] - [[package]] name = "ansi_term" version = "0.12.1" @@ -102,24 +93,30 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.61" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "508b352bb5c066aac251f6daf6b36eccd03e8a88e8081cd44959ea277a3af9a8" +checksum = "afddf7f520a80dbf76e6f50a35bca42a2331ef227a28b3b6dc5c2e2338d114b1" [[package]] name = "approx" -version = "0.5.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +checksum = "072df7202e63b127ab55acfe16ce97013d5b97bf160489336d3f1840fd78e99e" dependencies = [ "num-traits", ] [[package]] name = "arbitrary" -version = "1.1.3" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "698b65a961a9d730fb45b6b0327e20207810c9f61ee421b082b27ba003f49e2b" + +[[package]] +name = "array-bytes" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a7924531f38b1970ff630f03eb20a2fde69db5c590c93b0f3482e95dcc5fd60" +checksum = "6a913633b0c922e6b745072795f50d90ebea78ba31a57e2ac8c2fc7b50950949" [[package]] name = "arrayref" @@ -150,15 +147,15 @@ checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" [[package]] name = "asn1_der" -version = "0.7.5" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22d1f4b888c298a027c99dc9048015fac177587de20fc30232a057dfbe24a21" +checksum = "9d6e24d2cce90c53b948c46271bfb053e4bdc2db9b5d3f65e20f8cf28a1b7fc3" [[package]] name = "assert_cmd" -version = "2.0.4" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ae1ddd39efd67689deb1979d80bad3bf7f2b09c6e6117c8d1f2443b5e2f83e" +checksum = "e996dc7940838b7ef1096b882e29ec30a3149a3a443cdc8dba19ed382eca1fe2" dependencies = [ "bstr", "doc-comment", @@ -186,9 +183,9 @@ dependencies = [ [[package]] name = "async-channel" -version = "1.7.1" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14485364214912d3b19cc3435dde4df66065127f05fa0d75c712f36f12c2f28" +checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" dependencies = [ "concurrent-queue", "event-listener", @@ -197,28 +194,28 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.4.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965" +checksum = "eb877970c7b440ead138f6321a3b5395d6061183af779340b65e20c0fede9146" dependencies = [ "async-task", "concurrent-queue", "fastrand", "futures-lite", "once_cell", - "slab", + "vec-arena", ] [[package]] name = "async-global-executor" -version = "2.2.0" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5262ed948da60dd8956c6c5aca4d4163593dddb7b32d73267c93dab7b2e98940" +checksum = "9586ec52317f36de58453159d48351bc244bc24ced3effc1fce22f3d48664af6" dependencies = [ "async-channel", "async-executor", "async-io", - "async-lock", + "async-mutex", "blocking", "futures-lite", "num_cpus", @@ -227,9 +224,9 @@ dependencies = [ [[package]] name = "async-io" -version = "1.7.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5e18f61464ae81cde0a23e713ae8fd299580c54d697a35820cfd0625b8b0e07" +checksum = "a811e6a479f2439f0c04038796b5cfb3d2ad56c230e0f2d3f7b04d68cfee607b" dependencies = [ "concurrent-queue", "futures-lite", @@ -253,6 +250,15 @@ dependencies = [ "event-listener", ] +[[package]] +name = "async-mutex" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" +dependencies = [ + "event-listener", +] + [[package]] name = "async-process" version = "1.4.0" @@ -272,9 +278,9 @@ dependencies = [ [[package]] name = "async-std" -version = "1.12.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +checksum = "52580991739c5cdb36cde8b2a516371c0a3b70dda36d916cc08b82372916808c" dependencies = [ "async-attributes", "async-channel", @@ -291,8 +297,9 @@ dependencies = [ "kv-log-macro", "log", "memchr", + "num_cpus", "once_cell", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.6", "pin-utils", "slab", "wasm-bindgen-futures", @@ -315,9 +322,9 @@ dependencies = [ [[package]] name = "async-stream" -version = "0.3.3" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" +checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625" dependencies = [ "async-stream-impl", "futures-core", @@ -325,9 +332,9 @@ dependencies = [ [[package]] name = "async-stream-impl" -version = "0.3.3" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" +checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308" dependencies = [ "proc-macro2", "quote", @@ -336,9 +343,9 @@ dependencies = [ [[package]] name = "async-task" -version = "4.3.0" +version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524" +checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0" [[package]] name = "async-trait" @@ -361,7 +368,7 @@ dependencies = [ "futures-sink", "futures-util", "memchr", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.6", ] [[package]] @@ -383,30 +390,30 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.1.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "backtrace" -version = "0.3.66" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" +checksum = "5e121dee8023ce33ab248d9ce1493df03c3b38a659b240096fcbd7048ff9c31f" dependencies = [ "addr2line", "cc", "cfg-if 1.0.0", "libc", "miniz_oxide", - "object 0.29.0", + "object 0.27.1", "rustc-demangle", ] [[package]] name = "base-x" -version = "0.2.11" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" +checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b" [[package]] name = "base16ct" @@ -426,11 +433,17 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "base64ct" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bdca834647821e0b13d9539a8634eb62d3501b6b6c2cec1722786ee6671b851" + [[package]] name = "beef" -version = "0.5.2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" +checksum = "bed554bd50246729a1ec158d08aa3235d1b69d94ad120ebe187e28894787e736" dependencies = [ "serde", ] @@ -439,12 +452,12 @@ dependencies = [ name = "beefy-gadget" version = "4.0.0-dev" dependencies = [ + "array-bytes", "async-trait", "beefy-primitives", "fnv", "futures", "futures-timer", - "hex", "log", "parity-scale-codec", "parking_lot 0.12.1", @@ -455,6 +468,7 @@ dependencies = [ "sc-finality-grandpa", "sc-keystore", "sc-network", + "sc-network-common", "sc-network-gossip", "sc-network-test", "sc-utils", @@ -506,21 +520,19 @@ dependencies = [ name = "beefy-merkle-tree" version = "4.0.0-dev" dependencies = [ + "array-bytes", "beefy-primitives", "env_logger", - "hex", - "hex-literal", "log", "sp-api", - "tiny-keccak", + "sp-runtime", ] [[package]] name = "beefy-primitives" version = "4.0.0-dev" dependencies = [ - "hex", - "hex-literal", + "array-bytes", "parity-scale-codec", "scale-info", "sp-api", @@ -533,16 +545,17 @@ dependencies = [ [[package]] name = "bimap" -version = "0.6.2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0455254eb5c6964c4545d8bac815e1a1be4f3afe0ae695ea539c12d728d44b" +checksum = "50ae17cabbc8a38a1e3e4c1a6a664e9a09672dc14d0896fa8d865d3a5a446b07" [[package]] name = "bincode" -version = "1.3.3" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +checksum = "d175dfa69e619905c4c3cdb7c3c203fa3bdd5d51184e3afdb2742c0280493772" dependencies = [ + "byteorder", "serde", ] @@ -573,9 +586,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitvec" -version = "1.0.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +checksum = "1489fcb93a5bb47da0462ca93ad252ad6af2145cce58d10d46a83931ba9f016b" dependencies = [ "funty", "radium", @@ -656,16 +669,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ "block-padding 0.2.1", - "generic-array 0.14.6", + "generic-array 0.14.4", ] [[package]] name = "block-buffer" -version = "0.10.2" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +checksum = "f1d36a02058e76b040de25a4464ba1c80935655595b661505c8b39b664828b95" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.4", ] [[package]] @@ -685,9 +698,9 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] name = "blocking" -version = "1.2.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6ccb65d468978a086b69884437ded69a90faab3bbe6e67f242173ea728acccc" +checksum = "c5e170dbede1f740736619b776d7251cb1b9095c435c34d8ca9f57fcd2f335e9" dependencies = [ "async-channel", "async-task", @@ -705,9 +718,9 @@ checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" [[package]] name = "bstr" -version = "0.2.17" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +checksum = "a40b47ad93e1a5404e6c18dec46b628214fee441c70f4ab5d6942142cc268a3d" dependencies = [ "lazy_static", "memchr", @@ -726,15 +739,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.10.0" +version = "3.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" +checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" [[package]] name = "byte-slice-cast" -version = "1.2.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87c5fdd0166095e1d463fc6cc01aa8ce547ad77a4e84d42eb6762b084e28067e" +checksum = "65c1bf4a04a88c54f589125563643d773f3254b5c38571395e2b591c693bbc81" [[package]] name = "byte-tools" @@ -744,9 +757,9 @@ checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" [[package]] name = "bytecheck" -version = "0.6.9" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d11cac2c12b5adc6570dad2ee1b87eff4955dac476fe12d81e5fdd352e52406f" +checksum = "314889ea31cda264cb7c3d6e6e5c9415a987ecb0e72c17c00d36fbb881d34abe" dependencies = [ "bytecheck_derive", "ptr_meta", @@ -754,9 +767,9 @@ dependencies = [ [[package]] name = "bytecheck_derive" -version = "0.6.9" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e576ebe98e605500b3c8041bb888e966653577172df6dd97398714eb30b9bf" +checksum = "4a2b3b92c135dae665a6f760205b89187638e83bed17ef3e44e83c712cf30600" dependencies = [ "proc-macro2", "quote", @@ -765,15 +778,15 @@ dependencies = [ [[package]] name = "byteorder" -version = "1.4.3" +version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" [[package]] name = "bytes" -version = "1.2.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" [[package]] name = "bzip2-sys" @@ -788,24 +801,24 @@ dependencies = [ [[package]] name = "cache-padded" -version = "1.2.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" +checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" [[package]] name = "camino" -version = "1.0.9" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "869119e97797867fd90f5e22af7d0bd274bd4635ebb9eb68c04f3f513ae6c412" +checksum = "d4648c6d00a709aa069a236adcaae4f605a6241c72bf5bee79331a4b625921a9" dependencies = [ "serde", ] [[package]] name = "cargo-platform" -version = "0.1.2" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27" +checksum = "0226944a63d1bf35a3b5f948dd7c59e263db83695c9e8bffc4037de02e30f1d7" dependencies = [ "serde", ] @@ -818,22 +831,25 @@ checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" dependencies = [ "camino", "cargo-platform", - "semver 1.0.13", + "semver 1.0.4", "serde", "serde_json", ] [[package]] name = "cast" -version = "0.3.0" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" +checksum = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0" +dependencies = [ + "rustc_version 0.2.3", +] [[package]] name = "cc" -version = "1.0.73" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" dependencies = [ "jobserver", ] @@ -847,6 +863,15 @@ dependencies = [ "nom", ] +[[package]] +name = "cfg-expr" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aacacf4d96c24b2ad6eb8ee6df040e4f27b0d0b39a5710c30091baa830485db" +dependencies = [ + "smallvec", +] + [[package]] name = "cfg-if" version = "0.1.10" @@ -859,23 +884,29 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + [[package]] name = "chacha20" -version = "0.8.2" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" +checksum = "01b72a433d0cf2aef113ba70f62634c56fddb0f244e6377185c56a7cadbd8f91" dependencies = [ "cfg-if 1.0.0", "cipher", - "cpufeatures", + "cpufeatures 0.2.1", "zeroize", ] [[package]] name = "chacha20poly1305" -version = "0.9.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5" +checksum = "3b84ed6d1d5f7aa9bdde921a5090e0ca4d934d250ea3b402a5fab3a994e28a2a" dependencies = [ "aead", "chacha20", @@ -889,7 +920,7 @@ name = "chain-spec-builder" version = "2.0.0" dependencies = [ "ansi_term", - "clap 3.2.16", + "clap 3.1.18", "node-cli", "rand 0.8.5", "sc-chain-spec", @@ -900,16 +931,14 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.21" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f725f340c3854e3cb3ab736dc21f0cca183303acea3b3ffec30f141503ac8eb" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" dependencies = [ - "iana-time-zone", - "js-sys", + "libc", "num-integer", "num-traits", - "time 0.1.44", - "wasm-bindgen", + "time", "winapi", ] @@ -932,7 +961,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.4", ] [[package]] @@ -946,13 +975,13 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.3.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a050e2153c5be08febd6734e29298e844fdb0fa21aeddd63b4eb7baa106c69b" +checksum = "853eda514c284c2287f4bf20ae614f8781f40a81d32ecda6e91449304dfe077c" dependencies = [ "glob", "libc", - "libloading 0.7.3", + "libloading 0.7.0", ] [[package]] @@ -968,16 +997,16 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.16" +version = "3.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3dbbb6653e7c55cc8595ad3e1f7be8f32aba4eb7ff7f0fd1163d4f3d137c0a9" +checksum = "d2dbdf4bdacb33466e854ce889eee8dfd5729abf7ccd7664d0a2d60cd384440b" dependencies = [ "atty", "bitflags", "clap_derive", "clap_lex", "indexmap", - "once_cell", + "lazy_static", "strsim", "termcolor", "textwrap 0.15.0", @@ -985,18 +1014,18 @@ dependencies = [ [[package]] name = "clap_complete" -version = "3.2.3" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ead064480dfc4880a10764488415a97fdd36a4cf1bb022d372f02e8faf8386e1" +checksum = "a394f7ec0715b42a4e52b294984c27c9a61f77c8d82f7774c5198350be143f19" dependencies = [ - "clap 3.2.16", + "clap 3.1.18", ] [[package]] name = "clap_derive" -version = "3.2.15" +version = "3.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ba52acd3b0a5c33aeada5cdaa3267cdc7c594a98731d4268cdc1532f4264cb4" +checksum = "25320346e922cffe59c0bbc5410c8d8784509efb321488971081313cb1e1a33c" dependencies = [ "heck", "proc-macro-error", @@ -1007,18 +1036,18 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.2.4" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213" dependencies = [ "os_str_bytes", ] [[package]] name = "cmake" -version = "0.1.48" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8ad8cef104ac57b68b89df3208164d228503abbdce70f6880ffa3d970e7443a" +checksum = "b7b858541263efe664aead4a5209a4ae5c5d2811167d4ed4ee0944503f8d2089" dependencies = [ "cc", ] @@ -1036,9 +1065,9 @@ dependencies = [ [[package]] name = "concurrent-queue" -version = "1.2.4" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af4780a44ab5696ea9e28294517f1fffb421a83a25af521333c838635509db9c" +checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" dependencies = [ "cache-padded", ] @@ -1081,65 +1110,68 @@ dependencies = [ ] [[package]] -name = "corosensei" -version = "0.1.3" +name = "cpp_demangle" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9847f90f32a50b0dcbd68bc23ff242798b13080b97b0569f6ed96a45ce4cf2cd" +checksum = "44919ecaf6f99e8e737bc239408931c9a01e9a6c74814fee8242dd2506b65390" dependencies = [ - "autocfg", "cfg-if 1.0.0", - "libc", - "scopeguard", - "windows-sys 0.33.0", + "glob", ] [[package]] -name = "cpp_demangle" -version = "0.3.5" +name = "cpufeatures" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeaa953eaad386a53111e47172c2fedba671e5684c8dd601a5f474f4f118710f" +checksum = "66c99696f6c9dd7f35d486b9d04d7e6e202aa3e8c40d553f2fdf5e7e0c6a71ef" dependencies = [ - "cfg-if 1.0.0", + "libc", ] [[package]] name = "cpufeatures" -version = "0.2.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" dependencies = [ "libc", ] +[[package]] +name = "cpuid-bool" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" + [[package]] name = "cranelift-bforest" -version = "0.82.3" +version = "0.76.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38faa2a16616c8e78a18d37b4726b98bfd2de192f2fdc8a39ddf568a408a0f75" +checksum = "7e6bea67967505247f54fa2c85cf4f6e0e31c4e5692c9b70e4ae58e339067333" dependencies = [ - "cranelift-entity 0.82.3", + "cranelift-entity 0.76.0", ] [[package]] name = "cranelift-bforest" -version = "0.85.3" +version = "0.88.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "749d0d6022c9038dccf480bdde2a38d435937335bf2bb0f14e815d94517cdce8" +checksum = "b27bbd3e6c422cf6282b047bcdd51ecd9ca9f3497a3be0132ffa08e509b824b0" dependencies = [ - "cranelift-entity 0.85.3", + "cranelift-entity 0.88.0", ] [[package]] name = "cranelift-codegen" -version = "0.82.3" +version = "0.76.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26f192472a3ba23860afd07d2b0217dc628f21fcc72617aa1336d98e1671f33b" +checksum = "48194035d2752bdd5bdae429e3ab88676e95f52a2b1355a5d4e809f9e39b1d74" dependencies = [ - "cranelift-bforest 0.82.3", - "cranelift-codegen-meta 0.82.3", - "cranelift-codegen-shared 0.82.3", - "cranelift-entity 0.82.3", - "gimli", + "cranelift-bforest 0.76.0", + "cranelift-codegen-meta 0.76.0", + "cranelift-codegen-shared 0.76.0", + "cranelift-entity 0.76.0", + "gimli 0.25.0", "log", "regalloc", "smallvec", @@ -1148,16 +1180,18 @@ dependencies = [ [[package]] name = "cranelift-codegen" -version = "0.85.3" +version = "0.88.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94370cc7b37bf652ccd8bb8f09bd900997f7ccf97520edfc75554bb5c4abbea" +checksum = "872f5d4557a411b087bd731df6347c142ae1004e6467a144a7e33662e5715a01" dependencies = [ - "cranelift-bforest 0.85.3", - "cranelift-codegen-meta 0.85.3", - "cranelift-codegen-shared 0.85.3", - "cranelift-entity 0.85.3", + "arrayvec 0.7.2", + "bumpalo", + "cranelift-bforest 0.88.0", + "cranelift-codegen-meta 0.88.0", + "cranelift-codegen-shared 0.88.0", + "cranelift-entity 0.88.0", "cranelift-isle", - "gimli", + "gimli 0.26.1", "log", "regalloc2", "smallvec", @@ -1166,56 +1200,57 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.82.3" +version = "0.76.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f32ddb89e9b89d3d9b36a5b7d7ea3261c98235a76ac95ba46826b8ec40b1a24" +checksum = "976efb22fcab4f2cd6bd4e9913764616a54d895c1a23530128d04e03633c555f" dependencies = [ - "cranelift-codegen-shared 0.82.3", + "cranelift-codegen-shared 0.76.0", + "cranelift-entity 0.76.0", ] [[package]] name = "cranelift-codegen-meta" -version = "0.85.3" +version = "0.88.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a3cea8fdab90e44018c5b9a1dfd460d8ee265ac354337150222a354628bdb6" +checksum = "21b49fdebb29c62c1fc4da1eeebd609e9d530ecde24a9876def546275f73a244" dependencies = [ - "cranelift-codegen-shared 0.85.3", + "cranelift-codegen-shared 0.88.0", ] [[package]] name = "cranelift-codegen-shared" -version = "0.82.3" +version = "0.76.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01fd0d9f288cc1b42d9333b7a776b17e278fc888c28e6a0f09b5573d45a150bc" +checksum = "9dabb5fe66e04d4652e434195b45ae65b5c8172d520247b8f66d8df42b2b45dc" [[package]] name = "cranelift-codegen-shared" -version = "0.85.3" +version = "0.88.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ac72f76f2698598951ab26d8c96eaa854810e693e7dd52523958b5909fde6b2" +checksum = "5fc0c091e2db055d4d7f6b7cec2d2ead286bcfaea3357c6a52c2a2613a8cb5ac" [[package]] name = "cranelift-entity" -version = "0.82.3" +version = "0.76.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e3bfe172b83167604601faf9dc60453e0d0a93415b57a9c4d1a7ae6849185cf" +checksum = "3329733e4d4b8e91c809efcaa4faee80bf66f20164e3dd16d707346bd3494799" [[package]] name = "cranelift-entity" -version = "0.85.3" +version = "0.88.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09eaeacfcd2356fe0e66b295e8f9d59fdd1ac3ace53ba50de14d628ec902f72d" +checksum = "354a9597be87996c9b278655e68b8447f65dd907256855ad773864edee8d985c" dependencies = [ "serde", ] [[package]] name = "cranelift-frontend" -version = "0.82.3" +version = "0.76.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a006e3e32d80ce0e4ba7f1f9ddf66066d052a8c884a110b91d05404d6ce26dce" +checksum = "279afcc0d3e651b773f94837c3d581177b348c8d69e928104b2e9fccb226f921" dependencies = [ - "cranelift-codegen 0.82.3", + "cranelift-codegen 0.76.0", "log", "smallvec", "target-lexicon", @@ -1223,11 +1258,11 @@ dependencies = [ [[package]] name = "cranelift-frontend" -version = "0.85.3" +version = "0.88.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dba69c9980d5ffd62c18a2bde927855fcd7c8dc92f29feaf8636052662cbd99c" +checksum = "0cd8dd3fb8b82c772f4172e87ae1677b971676fffa7c4e3398e3047e650a266b" dependencies = [ - "cranelift-codegen 0.85.3", + "cranelift-codegen 0.88.0", "log", "smallvec", "target-lexicon", @@ -1235,51 +1270,51 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.85.3" +version = "0.88.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2920dc1e05cac40304456ed3301fde2c09bd6a9b0210bcfa2f101398d628d5b" +checksum = "b82527802b1f7d8da288adc28f1dc97ea52943f5871c041213f7b5035ac698a7" [[package]] name = "cranelift-native" -version = "0.85.3" +version = "0.88.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04dfa45f9b2a6f587c564d6b63388e00cd6589d2df6ea2758cf79e1a13285e6" +checksum = "c30ba8b910f1be023af0c39109cb28a8809734942a6b3eecbf2de8993052ea5e" dependencies = [ - "cranelift-codegen 0.85.3", + "cranelift-codegen 0.88.0", "libc", "target-lexicon", ] [[package]] name = "cranelift-wasm" -version = "0.85.3" +version = "0.88.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31a46513ae6f26f3f267d8d75b5373d555fbbd1e68681f348d99df43f747ec54" +checksum = "776a8916d201894aca9637a20814f1e11abc62acd5cfbe0b4eb2e63922756971" dependencies = [ - "cranelift-codegen 0.85.3", - "cranelift-entity 0.85.3", - "cranelift-frontend 0.85.3", + "cranelift-codegen 0.88.0", + "cranelift-entity 0.88.0", + "cranelift-frontend 0.88.0", "itertools", "log", "smallvec", - "wasmparser 0.85.0", + "wasmparser 0.89.1", "wasmtime-types", ] [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" dependencies = [ "cfg-if 1.0.0", ] [[package]] name = "criterion" -version = "0.3.6" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f" +checksum = "1604dafd25fba2fe2d5895a9da139f8dc9b319a5fe5354ca137cbbce4e178d10" dependencies = [ "atty", "cast", @@ -1305,9 +1340,9 @@ dependencies = [ [[package]] name = "criterion-plot" -version = "0.4.5" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876" +checksum = "d00996de9f2f7559f7f4dc286073197f83e92256a59ed395f9aac01fe717da57" dependencies = [ "cast", "itertools", @@ -1315,9 +1350,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.6" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" dependencies = [ "cfg-if 1.0.0", "crossbeam-utils", @@ -1325,9 +1360,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.2" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" +checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" dependencies = [ "cfg-if 1.0.0", "crossbeam-epoch", @@ -1336,26 +1371,25 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.10" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1" +checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd" dependencies = [ - "autocfg", "cfg-if 1.0.0", "crossbeam-utils", + "lazy_static", "memoffset", - "once_cell", "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.11" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" +checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" dependencies = [ "cfg-if 1.0.0", - "once_cell", + "lazy_static", ] [[package]] @@ -1370,19 +1404,19 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" dependencies = [ - "generic-array 0.14.6", - "rand_core 0.6.3", + "generic-array 0.14.4", + "rand_core 0.6.2", "subtle", "zeroize", ] [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.4", "typenum", ] @@ -1392,7 +1426,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.4", "subtle", ] @@ -1402,7 +1436,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.4", "subtle", ] @@ -1430,9 +1464,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.1.23" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdffe87e1d521a10f9696f833fe502293ea446d7f256c06128293a4119bdf4cb" +checksum = "e8f45d9ad417bcef4817d614a501ab55cdd96a6fdb24f49aab89a54acfd66b19" dependencies = [ "quote", "syn", @@ -1460,9 +1494,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "2.1.3" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b85542f99a2dfa2a1b8e192662741c9859a846b296bef1c92ef9b58b5a216" +checksum = "434e1720189a637d44fe464f4df1e6eb900b4835255b14354497c78af37d9bb8" dependencies = [ "byteorder", "digest 0.8.1", @@ -1473,9 +1507,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "3.2.0" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" +checksum = "f627126b946c25a4638eec0ea634fc52506dea98db118aae985118ce7c3d723f" dependencies = [ "byteorder", "digest 0.9.0", @@ -1492,16 +1526,16 @@ checksum = "4033478fbf70d6acf2655ac70da91ee65852d69daf7a67bf7a2f518fb47aafcf" dependencies = [ "byteorder", "digest 0.9.0", - "rand_core 0.6.3", + "rand_core 0.6.2", "subtle", "zeroize", ] [[package]] name = "darling" -version = "0.13.4" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +checksum = "757c0ded2af11d8e739c4daea1ac623dd1624b06c844cf3f5a39f1bdbd99bb12" dependencies = [ "darling_core", "darling_macro", @@ -1509,22 +1543,23 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.13.4" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +checksum = "2c34d8efb62d0c2d7f60ece80f75e5c63c1588ba68032740494b0b9a996466e3" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", + "strsim", "syn", ] [[package]] name = "darling_macro" -version = "0.13.4" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +checksum = "ade7bff147130fe5e6d39f089c6bd49ec0250f35d70b2eebf72afdfc919f15cc" dependencies = [ "darling_core", "quote", @@ -1539,9 +1574,9 @@ checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" [[package]] name = "data-encoding-macro" -version = "0.1.12" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86927b7cd2fe88fa698b87404b287ab98d1a0063a34071d92e575b72d3029aca" +checksum = "0a94feec3d2ba66c0b6621bca8bc6f68415b1e5c69af3586fdd0af9fd9f29b17" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -1549,9 +1584,9 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.10" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5bbed42daaa95e780b60a50546aa345b8413a1e46f9a40a12907d3598f038db" +checksum = "f0f83e699727abca3c56e187945f303389590305ab2f0185ea445aa66e8d5f2a" dependencies = [ "data-encoding", "syn", @@ -1579,9 +1614,9 @@ dependencies = [ [[package]] name = "diff" -version = "0.1.13" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" +checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" [[package]] name = "difflib" @@ -1604,7 +1639,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.4", ] [[package]] @@ -1613,7 +1648,7 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" dependencies = [ - "block-buffer 0.10.2", + "block-buffer 0.10.0", "crypto-common", "subtle", ] @@ -1639,9 +1674,9 @@ dependencies = [ [[package]] name = "dirs-sys" -version = "0.3.7" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +checksum = "03d86534ed367a67548dc68113a0f5db55432fdfbb6e6f9d77704397d95d5780" dependencies = [ "libc", "redox_users", @@ -1661,9 +1696,9 @@ dependencies = [ [[package]] name = "dissimilar" -version = "1.0.4" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c97b9233581d84b8e1e689cdd3a47b6f69770084fc246e86a7f78b0d9c1d4a5" +checksum = "fc4b29f4b9bb94bf267d57269fd0706d343a160937108e9619fe380645428abb" [[package]] name = "dns-parser" @@ -1672,7 +1707,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4d33be9473d06f75f58220f71f7a9317aca647dc061dbd3c361b0bef505fbea" dependencies = [ "byteorder", - "quick-error", + "quick-error 1.2.3", ] [[package]] @@ -1689,9 +1724,9 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] name = "dtoa" -version = "1.0.3" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6053ff46b5639ceb91756a85a4c8914668393a03170efd79c8884a529d80656" +checksum = "5caaa75cbd2b960ff1e5392d2cfb1f44717fffe12fc1f32b7b5d1267f99732a6" [[package]] name = "dyn-clonable" @@ -1716,9 +1751,9 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.9" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2" +checksum = "21e50f3adc76d6a43f5ed73b698a87d0760ca74617f60f7c3b879003536fdd28" [[package]] name = "dynasm" @@ -1743,7 +1778,7 @@ checksum = "64fba5a42bd76a17cad4bfa00de168ee1cbfa06a5e8ce992ae880218c05641a9" dependencies = [ "byteorder", "dynasm", - "memmap2 0.5.5", + "memmap2 0.5.0", ] [[package]] @@ -1760,9 +1795,9 @@ dependencies = [ [[package]] name = "ed25519" -version = "1.5.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" +checksum = "37c66a534cbb46ab4ea03477eae19d5c22c01da8258030280b7bd9d8433fb6ef" dependencies = [ "signature", ] @@ -1773,19 +1808,33 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" dependencies = [ - "curve25519-dalek 3.2.0", + "curve25519-dalek 3.0.2", "ed25519", "rand 0.7.3", "serde", - "sha2 0.9.9", + "sha2 0.9.8", + "zeroize", +] + +[[package]] +name = "ed25519-zebra" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "403ef3e961ab98f0ba902771d29f842058578bb1ce7e3c59dad5a6a93e784c69" +dependencies = [ + "curve25519-dalek 3.0.2", + "hex", + "rand_core 0.6.2", + "sha2 0.9.8", + "thiserror", "zeroize", ] [[package]] name = "either" -version = "1.7.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] name = "elliptic-curve" @@ -1797,9 +1846,9 @@ dependencies = [ "crypto-bigint", "der", "ff", - "generic-array 0.14.6", + "generic-array 0.14.4", "group", - "rand_core 0.6.3", + "rand_core 0.6.2", "sec1", "subtle", "zeroize", @@ -1839,9 +1888,9 @@ dependencies = [ [[package]] name = "enumflags2" -version = "0.7.5" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e75d4cd21b95383444831539909fbb14b9dc3fdceb2a6f5d36577329a1f55ccb" +checksum = "1b3ab37dc79652c9d85f1f7b6070d77d321d2467f5fe7b00d6b7a86c57b092ae" dependencies = [ "enumflags2_derive", ] @@ -1859,18 +1908,18 @@ dependencies = [ [[package]] name = "enumset" -version = "1.0.11" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4799cdb24d48f1f8a7a98d06b7fde65a85a2d1e42b25a889f5406aa1fbefe074" +checksum = "7e76129da36102af021b8e5000dab2c1c30dbef85c1e482beeff8da5dde0e0b0" dependencies = [ "enumset_derive", ] [[package]] name = "enumset_derive" -version = "0.6.0" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea83a3fbdc1d999ccfbcbee717eab36f8edf2d71693a23ce0d7cca19e085304c" +checksum = "6451128aa6655d880755345d085494cf7561a6bee7c8dc821e5d77e6d267ecd4" dependencies = [ "darling", "proc-macro2", @@ -1910,19 +1959,19 @@ dependencies = [ [[package]] name = "errno-dragonfly" -version = "0.1.2" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067" dependencies = [ - "cc", + "gcc", "libc", ] [[package]] name = "event-listener" -version = "2.5.3" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" +checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59" [[package]] name = "exit-future" @@ -1933,16 +1982,6 @@ dependencies = [ "futures", ] -[[package]] -name = "eyre" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" -dependencies = [ - "indenter", - "once_cell", -] - [[package]] name = "fake-simd" version = "0.1.2" @@ -1957,9 +1996,9 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fastrand" -version = "1.8.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" dependencies = [ "instant", ] @@ -1975,11 +2014,11 @@ dependencies = [ [[package]] name = "ff" -version = "0.11.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" +checksum = "b2958d04124b9f27f175eaeb9a9f383d026098aa837eadd8ba22c11f13a05b9e" dependencies = [ - "rand_core 0.6.3", + "rand_core 0.6.2", "subtle", ] @@ -1995,14 +2034,14 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.17" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c" +checksum = "c0408e2626025178a6a7f7ffc05a25bc47103229f19c113755de7bf63816290c" dependencies = [ "cfg-if 1.0.0", "libc", "redox_syscall", - "windows-sys 0.36.1", + "winapi", ] [[package]] @@ -2036,31 +2075,23 @@ dependencies = [ [[package]] name = "fixedbitset" -version = "0.4.2" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +checksum = "398ea4fabe40b9b0d885340a2a991a44c8a645624075ad966d21f88688e2b69e" [[package]] name = "flate2" -version = "1.0.24" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0" dependencies = [ + "cfg-if 1.0.0", "crc32fast", + "libc", "libz-sys", "miniz_oxide", ] -[[package]] -name = "flex-error" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c606d892c9de11507fa0dcffc116434f94e105d0bbdc4e405b61519464c49d7b" -dependencies = [ - "eyre", - "paste 1.0.8", -] - [[package]] name = "fnv" version = "1.0.7" @@ -2088,17 +2119,19 @@ dependencies = [ name = "frame-benchmarking" version = "4.0.0-dev" dependencies = [ + "array-bytes", "frame-support", "frame-system", - "hex-literal", "linregress", "log", "parity-scale-codec", - "paste 1.0.8", + "paste 1.0.6", + "rusty-fork", "scale-info", "serde", "sp-api", "sp-application-crypto", + "sp-core", "sp-io", "sp-keystore", "sp-runtime", @@ -2112,8 +2145,9 @@ name = "frame-benchmarking-cli" version = "4.0.0-dev" dependencies = [ "Inflector", + "array-bytes", "chrono", - "clap 3.2.16", + "clap 3.1.18", "comfy-table", "frame-benchmarking", "frame-support", @@ -2121,7 +2155,6 @@ dependencies = [ "gethostname", "handlebars", "hash-db", - "hex", "itertools", "kvdb", "lazy_static", @@ -2195,7 +2228,7 @@ dependencies = [ name = "frame-election-solution-type-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 3.2.16", + "clap 3.1.18", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-support", @@ -2212,9 +2245,10 @@ dependencies = [ name = "frame-executive" version = "4.0.0-dev" dependencies = [ + "array-bytes", "frame-support", "frame-system", - "hex-literal", + "frame-try-runtime", "pallet-balances", "pallet-transaction-payment", "parity-scale-codec", @@ -2255,12 +2289,13 @@ dependencies = [ "once_cell", "parity-scale-codec", "parity-util-mem", - "paste 1.0.8", + "paste 1.0.6", "pretty_assertions", "scale-info", "serde", "serde_json", "smallvec", + "sp-api", "sp-arithmetic", "sp-core", "sp-core-hashing-proc-macro", @@ -2271,6 +2306,7 @@ dependencies = [ "sp-state-machine", "sp-std", "sp-tracing", + "sp-weights", "tt-call", ] @@ -2279,7 +2315,9 @@ name = "frame-support-procedural" version = "4.0.0-dev" dependencies = [ "Inflector", + "cfg-expr", "frame-support-procedural-tools", + "itertools", "proc-macro2", "quote", "syn", @@ -2366,6 +2404,7 @@ dependencies = [ "sp-runtime", "sp-std", "sp-version", + "sp-weights", "substrate-test-runtime-client", ] @@ -2397,6 +2436,7 @@ name = "frame-try-runtime" version = "0.10.0-dev" dependencies = [ "frame-support", + "parity-scale-codec", "sp-api", "sp-runtime", "sp-std", @@ -2487,16 +2527,16 @@ checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" [[package]] name = "futures-lite" -version = "1.12.0" +version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +checksum = "b4481d0cd0de1d204a4fa55e7d45f07b1d958abcb06714b3446438e2eff695fb" dependencies = [ "fastrand", "futures-core", "futures-io", "memchr", "parking", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.6", "waker-fn", ] @@ -2513,9 +2553,9 @@ dependencies = [ [[package]] name = "futures-rustls" -version = "0.22.2" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2411eed028cdf8c8034eaf21f9915f956b6c3abec4d4c7949ee67f0721127bd" +checksum = "e01fe9932a224b72b45336d96040aa86386d674a31d0af27d800ea7bc8ca97fe" dependencies = [ "futures-io", "rustls", @@ -2553,7 +2593,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.6", "pin-utils", "slab", ] @@ -2567,6 +2607,12 @@ dependencies = [ "byteorder", ] +[[package]] +name = "gcc" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" + [[package]] name = "generate-bags" version = "4.0.0-dev" @@ -2592,9 +2638,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" dependencies = [ "typenum", "version_check", @@ -2625,20 +2671,20 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.7" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" dependencies = [ "cfg-if 1.0.0", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi 0.10.0+wasi-snapshot-preview1", ] [[package]] name = "ghash" -version = "0.4.4" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" +checksum = "b442c439366184de619215247d24e908912b175e824a530253845ac4c251a5c1" dependencies = [ "opaque-debug 0.3.0", "polyval", @@ -2646,9 +2692,20 @@ dependencies = [ [[package]] name = "gimli" -version = "0.26.2" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7" +dependencies = [ + "fallible-iterator", + "indexmap", + "stable_deref_trait", +] + +[[package]] +name = "gimli" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" +checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" dependencies = [ "fallible-iterator", "indexmap", @@ -2657,9 +2714,9 @@ dependencies = [ [[package]] name = "git2" -version = "0.14.4" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0155506aab710a86160ddb504a480d2964d7ab5b9e62419be69e0032bc5931c" +checksum = "3826a6e0e2215d7a41c2bfc7c9244123969273f3476b939a226aac0ab56e9e3c" dependencies = [ "bitflags", "libc", @@ -2676,9 +2733,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "globset" -version = "0.4.9" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a" +checksum = "c152169ef1e421390738366d2f796655fec62621dabbd0fd476f905934061e4a" dependencies = [ "aho-corasick", "bstr", @@ -2689,14 +2746,15 @@ dependencies = [ [[package]] name = "gloo-timers" -version = "0.2.4" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fb7d06c1c8cc2a29bee7ec961009a0b2caa0793ee4900c2ffb348734ba1c8f9" +checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f" dependencies = [ "futures-channel", "futures-core", "js-sys", "wasm-bindgen", + "web-sys", ] [[package]] @@ -2706,30 +2764,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" dependencies = [ "ff", - "rand_core 0.6.3", + "rand_core 0.6.2", "subtle", ] -[[package]] -name = "gumdrop" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bc700f989d2f6f0248546222d9b4258f5b02a171a431f8285a81c08142629e3" -dependencies = [ - "gumdrop_derive", -] - -[[package]] -name = "gumdrop_derive" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "729f9bd3449d77e7831a18abfb7ba2f99ee813dfd15b8c2167c9a54ba20aa99d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "h2" version = "0.3.13" @@ -2751,22 +2789,22 @@ dependencies = [ [[package]] name = "half" -version = "1.8.2" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +checksum = "62aca2aba2d62b4a7f5b33f3712cb1b0692779a56fb510499d5c0aa594daeaf3" [[package]] name = "handlebars" -version = "4.3.3" +version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360d9740069b2f6cbb63ce2dbaa71a20d3185350cbb990d7bebeb9318415eb17" +checksum = "99d6a30320f094710245150395bc763ad23128d6a1ebbad7594dc4164b62c56b" dependencies = [ "log", "pest", "pest_derive", + "quick-error 2.0.0", "serde", "serde_json", - "thiserror", ] [[package]] @@ -2810,9 +2848,9 @@ checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" [[package]] name = "hermit-abi" -version = "0.1.19" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" dependencies = [ "libc", ] @@ -2823,12 +2861,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hex-literal" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" - [[package]] name = "hex_fmt" version = "0.3.0" @@ -2862,7 +2894,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" dependencies = [ "digest 0.9.0", - "generic-array 0.14.6", + "generic-array 0.14.4", "hmac 0.8.1", ] @@ -2896,31 +2928,31 @@ checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ "bytes", "fnv", - "itoa 1.0.3", + "itoa 1.0.1", ] [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "60daa14be0e0786db0f03a9e57cb404c9d756eed2b6c62b9ea98ec5743ec75a9" dependencies = [ "bytes", "http", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.6", ] [[package]] name = "httparse" -version = "1.7.1" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" +checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440" [[package]] name = "humantime" @@ -2930,9 +2962,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.20" +version = "0.14.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" +checksum = "b7ec3e62bdc98a2f0393a5048e4c30ef659440ea6e0e572965103e72bd836f55" dependencies = [ "bytes", "futures-channel", @@ -2943,8 +2975,8 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.3", - "pin-project-lite 0.2.9", + "itoa 0.4.8", + "pin-project-lite 0.2.6", "socket2", "tokio", "tower-service", @@ -2967,78 +2999,6 @@ dependencies = [ "tokio-rustls", ] -[[package]] -name = "iana-time-zone" -version = "0.1.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9512e544c25736b82aebbd2bf739a47c8a1c935dfcc3a6adcde10e35cd3cd468" -dependencies = [ - "android_system_properties", - "core-foundation", - "js-sys", - "wasm-bindgen", - "winapi", -] - -[[package]] -name = "ibc" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ab02a177672699b494b7dd8396b633224614100cb216c41bc0f7175e2ff13cc" -dependencies = [ - "bytes", - "derive_more", - "flex-error", - "ibc-proto", - "ics23", - "num-traits", - "primitive-types", - "prost 0.11.0", - "prost-types 0.11.1", - "safe-regex", - "serde", - "serde_derive", - "serde_json", - "sha2 0.10.2", - "subtle-encoding", - "tendermint", - "tendermint-light-client-verifier", - "tendermint-proto", - "tendermint-testgen", - "time 0.3.11", - "tracing", - "uint", -] - -[[package]] -name = "ibc-proto" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e031bfb42dea291f45e17b8a547f3b8490e1aad15c0279134dd2ed08d15cf18" -dependencies = [ - "base64", - "bytes", - "prost 0.11.0", - "prost-types 0.11.1", - "serde", - "tendermint-proto", -] - -[[package]] -name = "ics23" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "149f3093d710c18c749ba68de3bc8131927aca00410ab90f3368e0524d01fe90" -dependencies = [ - "anyhow", - "bytes", - "hex", - "prost 0.11.0", - "ripemd", - "sha2 0.10.2", - "sha3 0.10.2", -] - [[package]] name = "ident_case" version = "1.0.1" @@ -3068,9 +3028,9 @@ dependencies = [ [[package]] name = "if-watch" -version = "1.1.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "015a7df1eb6dda30df37f34b63ada9b7b352984b0e84de2a20ed526345000791" +checksum = "ae8f4a3c3d4c89351ca83e120c1c00b27df945d38e05695668c9d4b4f7bc52f3" dependencies = [ "async-io", "core-foundation", @@ -3095,9 +3055,9 @@ dependencies = [ [[package]] name = "impl-serde" -version = "0.3.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" +checksum = "b47ca4d2b6931707a55fce5cf66aff80e2178c8b63bbb4ecb5695cbc870ddf6f" dependencies = [ "serde", ] @@ -3113,20 +3073,14 @@ dependencies = [ "syn", ] -[[package]] -name = "indenter" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" - [[package]] name = "indexmap" -version = "1.9.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" dependencies = [ "autocfg", - "hashbrown 0.12.3", + "hashbrown 0.11.2", "serde", ] @@ -3148,12 +3102,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "io-lifetimes" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec58677acfea8a15352d42fc87d11d63596ade9239e0a7c9352914417515dbe6" - [[package]] name = "io-lifetimes" version = "0.7.2" @@ -3201,24 +3149,24 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] name = "itoa" -version = "1.0.3" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" [[package]] name = "jobserver" -version = "0.1.24" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" +checksum = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.59" +version = "0.3.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" +checksum = "1866b355d9c878e5e607473cbe3f63282c0b7aad2db1dbebf55076c686918254" dependencies = [ "wasm-bindgen", ] @@ -3381,9 +3329,9 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.2" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838" +checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" [[package]] name = "keccak-hasher" @@ -3408,7 +3356,6 @@ dependencies = [ "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", - "hex-literal", "log", "node-primitives", "pallet-alliance", @@ -3424,12 +3371,13 @@ dependencies = [ "pallet-collective", "pallet-contracts", "pallet-contracts-primitives", - "pallet-contracts-rpc-runtime-api", + "pallet-contracts-runtime-api", "pallet-conviction-voting", "pallet-democracy", "pallet-election-provider-multi-phase", "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", + "pallet-fast-unstake", "pallet-gilt", "pallet-grandpa", "pallet-identity", @@ -3552,21 +3500,21 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "leb128" -version = "0.2.5" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" +checksum = "3576a87f2ba00f6f106fdfcd16db1d698d648a26ad8e0573cad8537c3c362d2a" [[package]] name = "libc" -version = "0.2.129" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64de3cc433455c14174d42e554d4027ee631c4d046d43e3ecc6efc4636cdc7a7" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" [[package]] name = "libgit2-sys" -version = "0.13.4+1.4.2" +version = "0.13.2+1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0fa6563431ede25f5cc7f6d803c6afbc1c5d3ad3d4925d12c882bf2b526f5d1" +checksum = "3a42de9a51a5c12e00fc0e4ca6bc2ea43582fc6418488e8f615e905d886f258b" dependencies = [ "cc", "libc", @@ -3586,9 +3534,9 @@ dependencies = [ [[package]] name = "libloading" -version = "0.7.3" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a" dependencies = [ "cfg-if 1.0.0", "winapi", @@ -3596,9 +3544,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.5" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "292a948cd991e376cf75541fe5b97a1081d713c618b4f1b9500f8844e49eb565" +checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" [[package]] name = "libp2p" @@ -3609,7 +3557,7 @@ dependencies = [ "bytes", "futures", "futures-timer", - "getrandom 0.2.7", + "getrandom 0.2.3", "instant", "lazy_static", "libp2p-autonat", @@ -3658,8 +3606,8 @@ dependencies = [ "libp2p-request-response", "libp2p-swarm", "log", - "prost 0.10.4", - "prost-build", + "prost 0.10.3", + "prost-build 0.10.4", "rand 0.8.5", ] @@ -3685,8 +3633,8 @@ dependencies = [ "multistream-select", "parking_lot 0.12.1", "pin-project", - "prost 0.10.4", - "prost-build", + "prost 0.10.3", + "prost-build 0.10.4", "rand 0.8.5", "ring", "rw-stream-sink", @@ -3736,8 +3684,8 @@ dependencies = [ "libp2p-core", "libp2p-swarm", "log", - "prost 0.10.4", - "prost-build", + "prost 0.10.3", + "prost-build 0.10.4", "rand 0.7.3", "smallvec", ] @@ -3760,8 +3708,8 @@ dependencies = [ "libp2p-swarm", "log", "prometheus-client", - "prost 0.10.4", - "prost-build", + "prost 0.10.3", + "prost-build 0.10.4", "rand 0.7.3", "regex", "sha2 0.10.2", @@ -3783,8 +3731,8 @@ dependencies = [ "libp2p-swarm", "log", "lru", - "prost 0.10.4", - "prost-build", + "prost 0.10.3", + "prost-build 0.10.4", "prost-codec", "smallvec", "thiserror", @@ -3808,8 +3756,8 @@ dependencies = [ "libp2p-core", "libp2p-swarm", "log", - "prost 0.10.4", - "prost-build", + "prost 0.10.3", + "prost-build 0.10.4", "rand 0.7.3", "sha2 0.10.2", "smallvec", @@ -3881,13 +3829,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "762408cb5d84b49a600422d7f9a42c18012d8da6ebcd570f9a4a4290ba41fb6f" dependencies = [ "bytes", - "curve25519-dalek 3.2.0", + "curve25519-dalek 3.0.2", "futures", "lazy_static", "libp2p-core", "log", - "prost 0.10.4", - "prost-build", + "prost 0.10.3", + "prost-build 0.10.4", "rand 0.8.5", "sha2 0.10.2", "snow", @@ -3923,8 +3871,8 @@ dependencies = [ "futures", "libp2p-core", "log", - "prost 0.10.4", - "prost-build", + "prost 0.10.3", + "prost-build 0.10.4", "unsigned-varint", "void", ] @@ -3959,8 +3907,8 @@ dependencies = [ "libp2p-swarm", "log", "pin-project", - "prost 0.10.4", - "prost-build", + "prost 0.10.3", + "prost-build 0.10.4", "prost-codec", "rand 0.8.5", "smallvec", @@ -3983,8 +3931,8 @@ dependencies = [ "libp2p-core", "libp2p-swarm", "log", - "prost 0.10.4", - "prost-build", + "prost 0.10.3", + "prost-build 0.10.4", "rand 0.8.5", "sha2 0.10.2", "thiserror", @@ -4132,9 +4080,9 @@ dependencies = [ [[package]] name = "libsecp256k1" -version = "0.7.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" +checksum = "b0452aac8bab02242429380e9b2f94ea20cea2b37e2c1777a1358799bbe97f37" dependencies = [ "arrayref", "base64", @@ -4145,7 +4093,7 @@ dependencies = [ "libsecp256k1-gen-genmult", "rand 0.8.5", "serde", - "sha2 0.9.9", + "sha2 0.9.8", "typenum", ] @@ -4180,9 +4128,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.8" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" +checksum = "602113192b08db8f38796c4e85c39e960c145965140e918018bcde1952429655" dependencies = [ "cc", "libc", @@ -4192,9 +4140,9 @@ dependencies = [ [[package]] name = "linked-hash-map" -version = "0.5.6" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" [[package]] name = "linked_hash_set" @@ -4215,12 +4163,6 @@ dependencies = [ "statrs", ] -[[package]] -name = "linux-raw-sys" -version = "0.0.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5284f00d480e1c39af34e72f8ad60b94f47007e3481cd3b731c1d67190ddc7b7" - [[package]] name = "linux-raw-sys" version = "0.0.46" @@ -4247,11 +4189,10 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.7" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" dependencies = [ - "autocfg", "scopeguard", ] @@ -4288,11 +4229,11 @@ dependencies = [ [[package]] name = "lru" -version = "0.7.8" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a" +checksum = "32613e41de4c47ab04970c348ca7ae7382cf116625755af070b008a15516a889" dependencies = [ - "hashbrown 0.12.3", + "hashbrown 0.11.2", ] [[package]] @@ -4306,9 +4247,9 @@ dependencies = [ [[package]] name = "lz4" -version = "1.23.3" +version = "1.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4edcb94251b1c375c459e5abe9fb0168c1c826c3370172684844f8f3f8d1a885" +checksum = "aac20ed6991e01bf6a2e68cc73df2b389707403662a8ba89f68511fb340f724c" dependencies = [ "libc", "lz4-sys", @@ -4316,9 +4257,9 @@ dependencies = [ [[package]] name = "lz4-sys" -version = "1.9.3" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7be8908e2ed6f31c02db8a9fa962f03e36c53fbfde437363eae3306b85d7e17" +checksum = "dca79aa95d8b3226213ad454d328369853be3a1382d89532a854f4d69640acae" dependencies = [ "cc", "libc", @@ -4333,6 +4274,12 @@ dependencies = [ "libc", ] +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + [[package]] name = "match_cfg" version = "0.1.0" @@ -4350,32 +4297,32 @@ dependencies = [ [[package]] name = "matches" -version = "0.1.9" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" [[package]] name = "matrixmultiply" -version = "0.3.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add85d4dd35074e6fedc608f8c8f513a3548619a9024b751949ef0e8e45a4d84" +checksum = "5a8a15b776d9dfaecd44b03c5828c2199cddff5247215858aac14624f8d6b741" dependencies = [ "rawpointer", ] [[package]] name = "memchr" -version = "2.5.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" [[package]] name = "memfd" -version = "0.4.1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6627dc657574b49d6ad27105ed671822be56e0d2547d413bfbf3e8d8fa92e7a" +checksum = "480b5a5de855d11ff13195950bdc8b98b5e942ef47afc447f6615cdcc4e15d80" dependencies = [ - "libc", + "rustix", ] [[package]] @@ -4390,27 +4337,27 @@ dependencies = [ [[package]] name = "memmap2" -version = "0.2.3" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "723e3ebdcdc5c023db1df315364573789f8857c11b631a2fdfad7c00f5c046b4" +checksum = "04e3e85b970d650e2ae6d70592474087051c11c54da7f7b4949725c5735fbcc6" dependencies = [ "libc", ] [[package]] name = "memmap2" -version = "0.5.5" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a79b39c93a7a5a27eeaf9a23b5ff43f1b9e0ad6b1cdd441140ae53c35613fc7" +checksum = "4647a11b578fead29cdbb34d4adef8dd3dc35b876c9c6d5240d83f205abfe96e" dependencies = [ "libc", ] [[package]] name = "memoffset" -version = "0.6.5" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" dependencies = [ "autocfg", ] @@ -4428,9 +4375,9 @@ dependencies = [ [[package]] name = "memory_units" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d96e3f3c0b6325d8ccd83c33b28acb183edcb6c67938ba104ec546854b0882" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" [[package]] name = "merlin" @@ -4452,11 +4399,12 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.5.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" dependencies = [ "adler", + "autocfg", ] [[package]] @@ -4473,9 +4421,9 @@ dependencies = [ [[package]] name = "more-asserts" -version = "0.2.2" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" +checksum = "0debeb9fcf88823ea64d64e4a815ab1643f33127d995978e099942ce38f25238" [[package]] name = "multiaddr" @@ -4519,7 +4467,7 @@ dependencies = [ "digest 0.10.3", "multihash-derive", "sha2 0.10.2", - "sha3 0.10.2", + "sha3 0.10.0", "unsigned-varint", ] @@ -4539,9 +4487,9 @@ dependencies = [ [[package]] name = "multimap" -version = "0.8.3" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" +checksum = "1255076139a83bb467426e7f8d0134968a8118844faa755985e077cf31850333" [[package]] name = "multistream-select" @@ -4567,7 +4515,7 @@ dependencies = [ "matrixmultiply", "nalgebra-macros", "num-complex", - "num-rational 0.4.1", + "num-rational 0.4.0", "num-traits", "rand 0.8.5", "rand_distr", @@ -4609,9 +4557,9 @@ dependencies = [ [[package]] name = "netlink-packet-route" -version = "0.12.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9ea4302b9759a7a88242299225ea3688e63c85ea136371bb6cf94fd674efaab" +checksum = "733ea73609acfd7fa7ddadfb7bf709b0471668c456ad9513685af543a06342b2" dependencies = [ "anyhow", "bitflags", @@ -4629,30 +4577,29 @@ checksum = "25af9cf0dc55498b7bd94a1508af7a78706aa0ab715a73c5169273e03c84845e" dependencies = [ "anyhow", "byteorder", - "paste 1.0.8", + "paste 1.0.6", "thiserror", ] [[package]] name = "netlink-proto" -version = "0.10.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65b4b14489ab424703c092062176d52ba55485a89c076b4f9db05092b7223aa6" +checksum = "ef8785b8141e8432aa45fceb922a7e876d7da3fad37fa7e7ec702ace3aa0826b" dependencies = [ "bytes", "futures", "log", "netlink-packet-core", "netlink-sys", - "thiserror", "tokio", ] [[package]] name = "netlink-sys" -version = "0.8.3" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92b654097027250401127914afb37cb1f311df6610a9891ff07a757e94199027" +checksum = "3e4c9f9547a08241bee7b6558b9b98e1f290d187de8b7cfca2bbb4937bcaa8f8" dependencies = [ "async-io", "bytes", @@ -4663,9 +4610,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.23.1" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" +checksum = "e4916f159ed8e5de0082076562152a76b7a1f64a01fd9d1e0fea002c37624faf" dependencies = [ "bitflags", "cc", @@ -4676,25 +4623,27 @@ dependencies = [ [[package]] name = "nix" -version = "0.24.2" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc" +checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6" dependencies = [ "bitflags", + "cc", "cfg-if 1.0.0", "libc", + "memoffset", ] [[package]] name = "node-bench" version = "0.9.0-dev" dependencies = [ - "clap 3.2.16", + "array-bytes", + "clap 3.1.18", "derive_more", "fs_extra", "futures", "hash-db", - "hex", "kitchensink-runtime", "kvdb", "kvdb-rocksdb", @@ -4726,16 +4675,16 @@ dependencies = [ name = "node-cli" version = "3.0.0-dev" dependencies = [ + "array-bytes", "assert_cmd", "async-std", - "clap 3.2.16", + "clap 3.1.18", "clap_complete", "criterion", "frame-benchmarking-cli", "frame-system", "frame-system-rpc-runtime-api", "futures", - "hex-literal", "jsonrpsee", "kitchensink-runtime", "log", @@ -4846,7 +4795,7 @@ dependencies = [ name = "node-inspect" version = "0.9.0-dev" dependencies = [ - "clap 3.2.16", + "clap 3.1.18", "parity-scale-codec", "sc-cli", "sc-client-api", @@ -4876,7 +4825,6 @@ version = "3.0.0-dev" dependencies = [ "jsonrpsee", "node-primitives", - "pallet-contracts-rpc", "pallet-mmr-rpc", "pallet-transaction-payment-rpc", "sc-chain-spec", @@ -4888,6 +4836,7 @@ dependencies = [ "sc-finality-grandpa-rpc", "sc-rpc", "sc-rpc-api", + "sc-rpc-spec-v2", "sc-sync-state-rpc", "sc-transaction-pool-api", "sp-api", @@ -4905,7 +4854,7 @@ dependencies = [ name = "node-runtime-generate-bags" version = "3.0.0" dependencies = [ - "clap 3.2.16", + "clap 3.1.18", "generate-bags", "kitchensink-runtime", ] @@ -4914,7 +4863,7 @@ dependencies = [ name = "node-template" version = "4.0.0-dev" dependencies = [ - "clap 3.2.16", + "clap 3.1.18", "frame-benchmarking", "frame-benchmarking-cli", "frame-system", @@ -4963,7 +4912,6 @@ dependencies = [ "frame-system-benchmarking", "frame-system-rpc-runtime-api", "frame-try-runtime", - "hex-literal", "pallet-aura", "pallet-balances", "pallet-grandpa", @@ -5037,12 +4985,13 @@ checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" [[package]] name = "nom" -version = "7.1.1" +version = "7.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109" dependencies = [ "memchr", "minimal-lexical", + "version_check", ] [[package]] @@ -5057,23 +5006,23 @@ dependencies = [ ] [[package]] -name = "num-complex" -version = "0.4.2" +name = "num-bigint" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" dependencies = [ + "autocfg", + "num-integer", "num-traits", ] [[package]] -name = "num-derive" -version = "0.3.3" +name = "num-complex" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +checksum = "26873667bbbb7c5182d4a37c1add32cdf09f841af72da53318fdb81543c15085" dependencies = [ - "proc-macro2", - "quote", - "syn", + "num-traits", ] [[package]] @@ -5088,9 +5037,9 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ "autocfg", "num-traits", @@ -5103,27 +5052,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" dependencies = [ "autocfg", - "num-bigint", + "num-bigint 0.2.6", "num-integer", "num-traits", ] [[package]] name = "num-rational" -version = "0.4.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" dependencies = [ "autocfg", + "num-bigint 0.4.3", "num-integer", "num-traits", ] [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ "autocfg", "libm", @@ -5140,19 +5090,19 @@ dependencies = [ ] [[package]] -name = "num_threads" -version = "0.1.6" +name = "object" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" dependencies = [ - "libc", + "memchr", ] [[package]] name = "object" -version = "0.28.4" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424" +checksum = "40bec70ba014595f99f7aa110b84331ffe1ee9aece7fe6f387cc7e3ecda4d456" dependencies = [ "crc32fast", "hashbrown 0.11.2", @@ -5166,14 +5116,17 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" dependencies = [ + "crc32fast", + "hashbrown 0.12.3", + "indexmap", "memchr", ] [[package]] name = "once_cell" -version = "1.13.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" +checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" [[package]] name = "oorandom" @@ -5195,21 +5148,21 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl-probe" -version = "0.1.5" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" [[package]] name = "os_str_bytes" -version = "6.2.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "648001efe5d5c0102d8cea768e348da85d90af8ba91f0bea908f157951493cd4" +checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" [[package]] name = "output_vt100" -version = "0.1.3" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" +checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" dependencies = [ "winapi", ] @@ -5227,11 +5180,10 @@ dependencies = [ name = "pallet-alliance" version = "4.0.0-dev" dependencies = [ + "array-bytes", "frame-benchmarking", "frame-support", "frame-system", - "hex", - "hex-literal", "log", "pallet-balances", "pallet-collective", @@ -5462,12 +5414,11 @@ dependencies = [ name = "pallet-beefy-mmr" version = "4.0.0-dev" dependencies = [ + "array-bytes", "beefy-merkle-tree", "beefy-primitives", "frame-support", "frame-system", - "hex", - "hex-literal", "log", "pallet-beefy", "pallet-mmr", @@ -5539,13 +5490,13 @@ dependencies = [ name = "pallet-contracts" version = "4.0.0-dev" dependencies = [ + "array-bytes", "assert_matches", "bitflags", "env_logger", "frame-benchmarking", "frame-support", "frame-system", - "hex-literal", "impl-trait-for-tuples", "log", "pallet-balances", @@ -5578,10 +5529,6 @@ version = "6.0.0" dependencies = [ "bitflags", "parity-scale-codec", - "scale-info", - "serde", - "sp-core", - "sp-rpc", "sp-runtime", "sp-std", ] @@ -5596,24 +5543,7 @@ dependencies = [ ] [[package]] -name = "pallet-contracts-rpc" -version = "4.0.0-dev" -dependencies = [ - "jsonrpsee", - "pallet-contracts-primitives", - "pallet-contracts-rpc-runtime-api", - "parity-scale-codec", - "serde", - "serde_json", - "sp-api", - "sp-blockchain", - "sp-core", - "sp-rpc", - "sp-runtime", -] - -[[package]] -name = "pallet-contracts-rpc-runtime-api" +name = "pallet-contracts-runtime-api" version = "4.0.0-dev" dependencies = [ "pallet-contracts-primitives", @@ -5769,85 +5699,74 @@ dependencies = [ ] [[package]] -name = "pallet-gilt" +name = "pallet-fast-unstake" version = "4.0.0-dev" dependencies = [ "frame-benchmarking", + "frame-election-provider-support", "frame-support", "frame-system", + "log", "pallet-balances", + "pallet-staking", + "pallet-staking-reward-curve", + "pallet-timestamp", "parity-scale-codec", "scale-info", - "sp-arithmetic", "sp-core", "sp-io", "sp-runtime", + "sp-staking", "sp-std", + "sp-tracing", + "substrate-test-utils", ] [[package]] -name = "pallet-grandpa" +name = "pallet-gilt" version = "4.0.0-dev" dependencies = [ - "finality-grandpa", "frame-benchmarking", - "frame-election-provider-support", "frame-support", "frame-system", - "log", - "pallet-authorship", "pallet-balances", - "pallet-offences", - "pallet-session", - "pallet-staking", - "pallet-staking-reward-curve", - "pallet-timestamp", "parity-scale-codec", "scale-info", - "sp-application-crypto", + "sp-arithmetic", "sp-core", - "sp-finality-grandpa", "sp-io", - "sp-keyring", "sp-runtime", - "sp-session", - "sp-staking", "sp-std", ] [[package]] -name = "pallet-ibc" -version = "3.0.0-pre.0" +name = "pallet-grandpa" +version = "4.0.0-dev" dependencies = [ - "chrono", - "flex-error", + "finality-grandpa", "frame-benchmarking", + "frame-election-provider-support", "frame-support", "frame-system", - "hex", - "ibc", - "ibc-proto", "log", - "pallet-assets", - "pallet-babe", + "pallet-authorship", "pallet-balances", + "pallet-offences", + "pallet-session", + "pallet-staking", + "pallet-staking-reward-curve", "pallet-timestamp", "parity-scale-codec", - "prost 0.11.0", - "prost-types 0.11.1", "scale-info", - "serde", - "serde_json", - "sha2 0.10.2", + "sp-application-crypto", "sp-core", + "sp-finality-grandpa", "sp-io", "sp-keyring", "sp-runtime", + "sp-session", + "sp-staking", "sp-std", - "sp-tracing", - "sp-version", - "tendermint-proto", - "time 0.3.11", ] [[package]] @@ -5941,12 +5860,12 @@ dependencies = [ name = "pallet-mmr" version = "4.0.0-dev" dependencies = [ + "array-bytes", "ckb-merkle-mountain-range", "env_logger", "frame-benchmarking", "frame-support", "frame-system", - "hex-literal", "itertools", "parity-scale-codec", "scale-info", @@ -6027,6 +5946,7 @@ dependencies = [ "log", "pallet-balances", "parity-scale-codec", + "rand 0.8.5", "scale-info", "sp-core", "sp-io", @@ -6055,6 +5975,7 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", + "sp-runtime-interface", "sp-staking", "sp-std", ] @@ -6255,21 +6176,43 @@ dependencies = [ ] [[package]] -name = "pallet-scheduler" -version = "4.0.0-dev" +name = "pallet-root-offences" +version = "1.0.0" dependencies = [ - "frame-benchmarking", + "frame-election-provider-support", "frame-support", "frame-system", - "log", - "pallet-preimage", + "pallet-balances", + "pallet-offences", + "pallet-session", + "pallet-staking", + "pallet-staking-reward-curve", + "pallet-timestamp", "parity-scale-codec", "scale-info", "sp-core", "sp-io", "sp-runtime", + "sp-staking", "sp-std", - "substrate-test-utils", +] + +[[package]] +name = "pallet-scheduler" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-preimage", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "substrate-test-utils", ] [[package]] @@ -6532,10 +6475,10 @@ dependencies = [ name = "pallet-transaction-storage" version = "4.0.0-dev" dependencies = [ + "array-bytes", "frame-benchmarking", "frame-support", "frame-system", - "hex-literal", "log", "pallet-balances", "parity-scale-codec", @@ -6648,7 +6591,7 @@ dependencies = [ "libc", "log", "lz4", - "memmap2 0.2.3", + "memmap2 0.2.1", "parking_lot 0.11.2", "rand 0.8.5", "snap", @@ -6656,9 +6599,9 @@ dependencies = [ [[package]] name = "parity-scale-codec" -version = "3.1.5" +version = "3.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9182e4a71cae089267ab03e67c99368db7cd877baf50f931e5d6d4b71e195ac0" +checksum = "04bc9583b5e01cc8c70d89acc9af14ef9b1c29ee3a0074b2a9eea8c0fa396690" dependencies = [ "arrayvec 0.7.2", "bitvec", @@ -6725,9 +6668,9 @@ dependencies = [ [[package]] name = "parity-wasm" -version = "0.42.2" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92" +checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" [[package]] name = "parking" @@ -6753,7 +6696,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.3", + "parking_lot_core 0.9.1", ] [[package]] @@ -6772,15 +6715,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.3" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954" dependencies = [ "cfg-if 1.0.0", "libc", "redox_syscall", "smallvec", - "windows-sys 0.36.1", + "windows-sys 0.32.0", ] [[package]] @@ -6795,9 +6738,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.8" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9423e2b32f7a043629287a536f21951e8c6a82482d0acb1eeebfc90bc2225b22" +checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" [[package]] name = "paste-impl" @@ -6840,19 +6783,18 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pest" -version = "2.2.1" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69486e2b8c2d2aeb9762db7b4e00b0331156393555cff467f4163ff06821eef8" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" dependencies = [ - "thiserror", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.2.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b13570633aff33c6d22ce47dd566b10a3b9122c2fe9d8e7501895905be532b91" +checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" dependencies = [ "pest", "pest_generator", @@ -6860,9 +6802,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.2.1" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3c567e5702efdc79fb18859ea74c3eb36e14c43da7b8c1f098a4ed6514ec7a0" +checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" dependencies = [ "pest", "pest_meta", @@ -6873,20 +6815,20 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.2.1" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eb32be5ee3bbdafa8c7a18b0a8a8d962b66cfa2ceee4037f49267a50ee821fe" +checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" dependencies = [ - "once_cell", + "maplit", "pest", - "sha-1 0.10.0", + "sha-1 0.8.2", ] [[package]] name = "petgraph" -version = "0.6.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" +checksum = "4a13a2fa9d0b63e5f22328828741e523766fff0ee9e779316902290dff3f824f" dependencies = [ "fixedbitset", "indexmap", @@ -6894,18 +6836,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.0.11" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78203e83c48cffbe01e4a2d35d566ca4de445d79a85372fc64e378bfc812a260" +checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.11" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "710faf75e1b33345361201d36d04e98ac1ed8909151a017ed384700836104c74" +checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" dependencies = [ "proc-macro2", "quote", @@ -6920,9 +6862,9 @@ checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905" [[package]] name = "pin-utils" @@ -6930,11 +6872,22 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs8" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" +dependencies = [ + "der", + "spki", + "zeroize", +] + [[package]] name = "pkg-config" -version = "0.3.25" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" [[package]] name = "platforms" @@ -6944,9 +6897,9 @@ checksum = "e8d0eef3571242013a0d5dc84861c3ae4a652e56e12adf8bdc26ff5f8cb34c94" [[package]] name = "plotters" -version = "0.3.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9428003b84df1496fb9d6eeee9c5f8145cb41ca375eb0dad204328888832811f" +checksum = "32a3fd9ec30b9749ce28cd91f255d569591cdf937fe280c312143e3c4bad6f2a" dependencies = [ "num-traits", "plotters-backend", @@ -6957,66 +6910,66 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.4" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" +checksum = "b07fffcddc1cb3a1de753caa4e4df03b79922ba43cf882acc1bdd7e8df9f4590" [[package]] name = "plotters-svg" -version = "0.3.2" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0918736323d1baff32ee0eade54984f6f201ad7e97d5cfb5d6ab4a358529615" +checksum = "b38a02e23bd9604b842a812063aec4ef702b57989c37b655254bb61c471ad211" dependencies = [ "plotters-backend", ] [[package]] name = "polling" -version = "2.2.0" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" +checksum = "a2a7bc6b2a29e632e45451c941832803a18cce6781db04de8a04696cdca8bde4" dependencies = [ - "cfg-if 1.0.0", + "cfg-if 0.1.10", "libc", "log", - "wepoll-ffi", + "wepoll-sys", "winapi", ] [[package]] name = "poly1305" -version = "0.7.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" +checksum = "9fcffab1f78ebbdf4b93b68c1ffebc24037eedf271edaca795732b24e5e4e349" dependencies = [ - "cpufeatures", + "cpufeatures 0.1.5", "opaque-debug 0.3.0", "universal-hash", ] [[package]] name = "polyval" -version = "0.5.3" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" +checksum = "a6ba6a405ef63530d6cb12802014b22f9c5751bd17cdcddbe9e46d5c8ae83287" dependencies = [ "cfg-if 1.0.0", - "cpufeatures", + "cpufeatures 0.1.5", "opaque-debug 0.3.0", "universal-hash", ] [[package]] name = "ppv-lite86" -version = "0.2.16" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" [[package]] name = "predicates" -version = "2.1.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c" +checksum = "c143348f141cc87aab5b950021bac6145d0e5ae754b0591de23244cee42c9308" dependencies = [ "difflib", "itertools", @@ -7025,18 +6978,18 @@ dependencies = [ [[package]] name = "predicates-core" -version = "1.0.3" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da1c2388b1513e1b605fcec39a95e0a9e8ef088f71443ef37099fa9ae6673fcb" +checksum = "57e35a3326b75e49aa85f5dc6ec15b41108cf5aee58eabb1f274dd18b73c2451" [[package]] name = "predicates-tree" -version = "1.0.5" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d86de6de25020a36c6d3643a86d9a6a9f552107c0559c60ea03551b5e16c032" +checksum = "15f553275e5721409451eb85e15fd9a860a6e5ab4496eb215987502b5f5391f2" dependencies = [ "predicates-core", - "termtree", + "treeline", ] [[package]] @@ -7066,11 +7019,10 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.2.1" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" dependencies = [ - "once_cell", "thiserror", "toml", ] @@ -7107,24 +7059,24 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" [[package]] name = "proc-macro2" -version = "1.0.43" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" dependencies = [ "unicode-ident", ] [[package]] name = "prometheus" -version = "0.13.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cface98dfa6d645ea4c789839f176e4b072265d085bfcc48eaa8d137f58d3c39" +checksum = "b7f64969ffd5dd8f39bd57a68ac53c163a095ed9d0fb707146da1b27025a3504" dependencies = [ "cfg-if 1.0.0", "fnv", "lazy_static", "memchr", - "parking_lot 0.12.1", + "parking_lot 0.11.2", "thiserror", ] @@ -7135,7 +7087,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac1abe0255c04d15f571427a2d1e00099016506cf3297b53853acd2b7eb87825" dependencies = [ "dtoa", - "itoa 1.0.3", + "itoa 1.0.1", "owning_ref", "prometheus-client-derive-text-encode", ] @@ -7153,9 +7105,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.10.4" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71adf41db68aa0daaefc69bb30bcd68ded9b9abaad5d1fbb6304c4fb390e083e" +checksum = "bc03e116981ff7d8da8e5c220e374587b98d294af7ba7dd7fda761158f00086f" dependencies = [ "bytes", "prost-derive 0.10.1", @@ -7186,13 +7138,33 @@ dependencies = [ "log", "multimap", "petgraph", - "prost 0.10.4", + "prost 0.10.3", "prost-types 0.10.1", "regex", "tempfile", "which", ] +[[package]] +name = "prost-build" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f835c582e6bd972ba8347313300219fed5bfa52caf175298d860b61ff6069bb" +dependencies = [ + "bytes", + "heck", + "itertools", + "lazy_static", + "log", + "multimap", + "petgraph", + "prost 0.11.0", + "prost-types 0.11.1", + "regex", + "tempfile", + "which", +] + [[package]] name = "prost-codec" version = "0.1.0" @@ -7201,7 +7173,7 @@ checksum = "00af1e92c33b4813cc79fda3f2dbf56af5169709be0202df730e9ebc3e4cd007" dependencies = [ "asynchronous-codec", "bytes", - "prost 0.10.4", + "prost 0.10.3", "thiserror", "unsigned-varint", ] @@ -7239,7 +7211,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d0a014229361011dc8e69c8a1ec6c2e8d0f2af7c91e3ea3f5b2170298461e68" dependencies = [ "bytes", - "prost 0.10.4", + "prost 0.10.3", ] [[package]] @@ -7254,9 +7226,9 @@ dependencies = [ [[package]] name = "psm" -version = "0.1.20" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f446d0a6efba22928558c4fb4ce0b3fd6c89b0061343e390bf01a703742b8125" +checksum = "3abf49e5417290756acfd26501536358560c4a5cc4a0934d390939acb3e7083a" dependencies = [ "cc", ] @@ -7287,6 +7259,12 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +[[package]] +name = "quick-error" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ac73b1112776fc109b2e61909bc46c7e1bf0d7f690ffb1676553acce16d5cda" + [[package]] name = "quickcheck" version = "1.0.3" @@ -7309,9 +7287,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.21" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" dependencies = [ "proc-macro2", ] @@ -7343,8 +7321,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.3", + "rand_chacha 0.3.0", + "rand_core 0.6.2", ] [[package]] @@ -7359,12 +7337,12 @@ dependencies = [ [[package]] name = "rand_chacha" -version = "0.3.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" dependencies = [ "ppv-lite86", - "rand_core 0.6.3", + "rand_core 0.6.2", ] [[package]] @@ -7378,11 +7356,11 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" dependencies = [ - "getrandom 0.2.7", + "getrandom 0.2.3", ] [[package]] @@ -7419,7 +7397,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e" dependencies = [ - "rand_core 0.6.3", + "rand_core 0.6.2", ] [[package]] @@ -7430,9 +7408,9 @@ checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" [[package]] name = "rayon" -version = "1.5.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" +checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674" dependencies = [ "autocfg", "crossbeam-deque", @@ -7442,50 +7420,50 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.9.3" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" +checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" dependencies = [ "crossbeam-channel", "crossbeam-deque", "crossbeam-utils", + "lazy_static", "num_cpus", ] [[package]] name = "redox_syscall" -version = "0.2.16" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" dependencies = [ "bitflags", ] [[package]] name = "redox_users" -version = "0.4.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" dependencies = [ - "getrandom 0.2.7", + "getrandom 0.2.3", "redox_syscall", - "thiserror", ] [[package]] name = "ref-cast" -version = "1.0.9" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed13bcd201494ab44900a96490291651d200730904221832b9547d24a87d332b" +checksum = "300f2a835d808734ee295d45007adacb9ebb29dd3ae2424acfa17930cae541da" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.9" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5234cd6063258a5e32903b53b1b6ac043a0541c8adc1f610f67b0326c7a578fa" +checksum = "4c38e3aecd2b21cb3959637b883bb3714bc7e43f0268b9a29d3743ee3e55cdd2" dependencies = [ "proc-macro2", "quote", @@ -7494,9 +7472,9 @@ dependencies = [ [[package]] name = "regalloc" -version = "0.0.34" +version = "0.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62446b1d3ebf980bdc68837700af1d77b37bc430e524bf95319c6eada2a4cc02" +checksum = "571f7f397d61c4755285cd37853fe8e03271c243424a907415909379659381c5" dependencies = [ "log", "rustc-hash", @@ -7505,9 +7483,9 @@ dependencies = [ [[package]] name = "regalloc2" -version = "0.2.3" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a8d23b35d7177df3b9d31ed8a9ab4bf625c668be77a319d4f5efd4a5257701c" +checksum = "d43a209257d978ef079f3d446331d0f1794f5e0fc19b306a199983857833a779" dependencies = [ "fxhash", "log", @@ -7517,9 +7495,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.6.0" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" dependencies = [ "aho-corasick", "memchr", @@ -7528,30 +7506,19 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.1.10" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" dependencies = [ + "byteorder", "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.6.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" - -[[package]] -name = "region" -version = "2.2.0" +version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877e54ea2adcd70d80e9179344c97f93ef0dffd6b03e1f4529e6e83ab2fa9ae0" -dependencies = [ - "bitflags", - "libc", - "mach", - "winapi", -] +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" [[package]] name = "region" @@ -7609,7 +7576,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" dependencies = [ "hostname", - "quick-error", + "quick-error 1.2.3", ] [[package]] @@ -7638,20 +7605,11 @@ dependencies = [ "winapi", ] -[[package]] -name = "ripemd" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1facec54cb5e0dc08553501fa740091086d0259ad0067e0d4103448e4cb22ed3" -dependencies = [ - "digest 0.10.3", -] - [[package]] name = "rkyv" -version = "0.7.39" +version = "0.7.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cec2b3485b07d96ddfd3134767b8a447b45ea4eb91448d0a35180ec0ffd5ed15" +checksum = "1f08c8062c1fe1253064043b8fc07bfea1b9702b71b4a86c11ea3588183b12e1" dependencies = [ "bytecheck", "hashbrown 0.12.3", @@ -7663,9 +7621,9 @@ dependencies = [ [[package]] name = "rkyv_derive" -version = "0.7.39" +version = "0.7.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eaedadc88b53e36dd32d940ed21ae4d850d5916f2581526921f553a72ac34c4" +checksum = "e289706df51226e84814bf6ba1a9e1013112ae29bc7a9878f73fce360520c403" dependencies = [ "proc-macro2", "quote", @@ -7684,9 +7642,9 @@ dependencies = [ [[package]] name = "rpassword" -version = "5.0.1" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc936cf8a7ea60c58f030fd36a612a48f440610214dc54bc36431f9ea0c3efb" +checksum = "26b763cb66df1c928432cc35053f8bd4cec3335d8559fc16010017d16b3c1680" dependencies = [ "libc", "winapi", @@ -7694,24 +7652,24 @@ dependencies = [ [[package]] name = "rtnetlink" -version = "0.10.1" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322c53fd76a18698f1c27381d58091de3a043d356aa5bd0d510608b565f469a0" +checksum = "6f54290e54521dac3de4149d83ddf9f62a359b3cc93bcb494a794a41e6f4744b" dependencies = [ "async-global-executor", "futures", "log", "netlink-packet-route", "netlink-proto", - "nix 0.24.2", + "nix 0.22.3", "thiserror", ] [[package]] name = "rustc-demangle" -version = "0.1.21" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" +checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232" [[package]] name = "rustc-hash" @@ -7740,42 +7698,28 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.13", -] - -[[package]] -name = "rustix" -version = "0.33.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "938a344304321a9da4973b9ff4f9f8db9caf4597dfd9dda6a60b523340a0fff0" -dependencies = [ - "bitflags", - "errno", - "io-lifetimes 0.5.3", - "libc", - "linux-raw-sys 0.0.42", - "winapi", + "semver 1.0.4", ] [[package]] name = "rustix" -version = "0.35.7" +version = "0.35.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51cc38aa10f6bbb377ed28197aa052aa4e2b762c22be9d3153d01822587e787" +checksum = "72c825b8aa8010eb9ee99b75f05e10180b9278d161583034d7574c9d617aeada" dependencies = [ "bitflags", "errno", - "io-lifetimes 0.7.2", + "io-lifetimes", "libc", - "linux-raw-sys 0.0.46", + "linux-raw-sys", "windows-sys 0.36.1", ] [[package]] name = "rustls" -version = "0.20.6" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" +checksum = "d37e5e2290f3e040b594b1a9e04377c2c671f1a1cfd9bfdef82106ac1c113f84" dependencies = [ "log", "ring", @@ -7785,9 +7729,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.6.2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" +checksum = "5ca9ebdfa27d3fc180e42879037b5338ab1c040c06affd00d8338598e7800943" dependencies = [ "openssl-probe", "rustls-pemfile", @@ -7797,18 +7741,29 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.1" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" +checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" dependencies = [ "base64", ] [[package]] name = "rustversion" -version = "1.0.9" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" + +[[package]] +name = "rusty-fork" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error 1.2.3", + "tempfile", +] [[package]] name = "rw-stream-sink" @@ -7823,9 +7778,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.11" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" [[package]] name = "safe-mix" @@ -7836,53 +7791,6 @@ dependencies = [ "rustc_version 0.2.3", ] -[[package]] -name = "safe-proc-macro2" -version = "1.0.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "814c536dcd27acf03296c618dab7ad62d28e70abd7ba41d3f34a2ce707a2c666" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "safe-quote" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e530f7831f3feafcd5f1aae406ac205dd998436b4007c8e80f03eca78a88f7" -dependencies = [ - "safe-proc-macro2", -] - -[[package]] -name = "safe-regex" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15289bf322e0673d52756a18194167f2378ec1a15fe884af6e2d2cb934822b0" -dependencies = [ - "safe-regex-macro", -] - -[[package]] -name = "safe-regex-compiler" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fba76fae590a2aa665279deb1f57b5098cbace01a0c5e60e262fcf55f7c51542" -dependencies = [ - "safe-proc-macro2", - "safe-quote", -] - -[[package]] -name = "safe-regex-macro" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c2e96b5c03f158d1b16ba79af515137795f4ad4e8de3f790518aae91f1d127" -dependencies = [ - "safe-proc-macro2", - "safe-regex-compiler", -] - [[package]] name = "salsa20" version = "0.9.0" @@ -7915,18 +7823,18 @@ dependencies = [ name = "sc-authority-discovery" version = "0.10.0-dev" dependencies = [ + "async-trait", "futures", "futures-timer", "ip_network", "libp2p", "log", "parity-scale-codec", - "prost 0.10.4", - "prost-build", + "prost 0.11.0", + "prost-build 0.11.1", "quickcheck", "rand 0.7.3", "sc-client-api", - "sc-network", "sc-network-common", "sp-api", "sp-authority-discovery", @@ -7986,10 +7894,10 @@ name = "sc-chain-spec" version = "4.0.0-dev" dependencies = [ "impl-trait-for-tuples", - "memmap2 0.5.5", + "memmap2 0.5.0", "parity-scale-codec", "sc-chain-spec-derive", - "sc-network", + "sc-network-common", "sc-telemetry", "serde", "serde_json", @@ -8011,11 +7919,11 @@ dependencies = [ name = "sc-cli" version = "0.10.0-dev" dependencies = [ + "array-bytes", "chrono", - "clap 3.2.16", + "clap 3.1.18", "fdlimit", "futures", - "hex", "libp2p", "log", "names", @@ -8027,6 +7935,7 @@ dependencies = [ "sc-client-db", "sc-keystore", "sc-network", + "sc-network-common", "sc-service", "sc-telemetry", "sc-tracing", @@ -8080,7 +7989,9 @@ dependencies = [ name = "sc-client-db" version = "0.10.0-dev" dependencies = [ + "criterion", "hash-db", + "kitchensink-runtime", "kvdb", "kvdb-memorydb", "kvdb-rocksdb", @@ -8090,6 +8001,7 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.1", "quickcheck", + "rand 0.8.5", "sc-client-api", "sc-state-db", "sp-arithmetic", @@ -8174,7 +8086,7 @@ dependencies = [ "futures", "log", "merlin", - "num-bigint", + "num-bigint 0.2.6", "num-rational 0.2.4", "num-traits", "parity-scale-codec", @@ -8335,7 +8247,6 @@ dependencies = [ "sp-inherents", "sp-runtime", "sp-state-machine", - "sp-timestamp", "substrate-test-runtime-client", "thiserror", ] @@ -8354,15 +8265,15 @@ dependencies = [ name = "sc-executor" version = "0.10.0-dev" dependencies = [ + "array-bytes", "criterion", "env_logger", - "hex-literal", "lazy_static", "lru", "num_cpus", "parity-scale-codec", "parking_lot 0.12.1", - "paste 1.0.8", + "paste 1.0.6", "regex", "sc-executor-common", "sc-executor-wasmi", @@ -8400,7 +8311,6 @@ dependencies = [ "sc-allocator", "sp-maybe-compressed-blob", "sp-sandbox", - "sp-serializer", "sp-wasm-interface", "thiserror", "wasm-instrument", @@ -8431,9 +8341,9 @@ dependencies = [ "log", "once_cell", "parity-scale-codec", - "parity-wasm 0.42.2", - "paste 1.0.8", - "rustix 0.35.7", + "parity-wasm 0.45.0", + "paste 1.0.6", + "rustix", "sc-allocator", "sc-executor-common", "sc-runtime-test", @@ -8451,6 +8361,7 @@ name = "sc-finality-grandpa" version = "0.10.0-dev" dependencies = [ "ahash", + "array-bytes", "assert_matches", "async-trait", "dyn-clone", @@ -8458,7 +8369,6 @@ dependencies = [ "fork-tree", "futures", "futures-timer", - "hex", "log", "parity-scale-codec", "parking_lot 0.12.1", @@ -8539,8 +8449,8 @@ dependencies = [ name = "sc-keystore" version = "4.0.0-dev" dependencies = [ + "array-bytes", "async-trait", - "hex", "parking_lot 0.12.1", "serde_json", "sp-application-crypto", @@ -8554,6 +8464,7 @@ dependencies = [ name = "sc-network" version = "0.10.0-dev" dependencies = [ + "array-bytes", "assert_matches", "async-std", "async-trait", @@ -8566,7 +8477,6 @@ dependencies = [ "fork-tree", "futures", "futures-timer", - "hex", "ip_network", "libp2p", "linked-hash-map", @@ -8576,8 +8486,7 @@ dependencies = [ "parity-scale-codec", "parking_lot 0.12.1", "pin-project", - "prost 0.10.4", - "prost-build", + "prost 0.11.0", "rand 0.7.3", "sc-block-builder", "sc-client-api", @@ -8603,10 +8512,35 @@ dependencies = [ "tempfile", "thiserror", "unsigned-varint", - "void", "zeroize", ] +[[package]] +name = "sc-network-bitswap" +version = "0.10.0-dev" +dependencies = [ + "cid", + "futures", + "libp2p", + "log", + "prost 0.11.0", + "prost-build 0.11.1", + "sc-block-builder", + "sc-client-api", + "sc-consensus", + "sc-network-common", + "sp-blockchain", + "sp-consensus", + "sp-core", + "sp-runtime", + "substrate-test-runtime", + "substrate-test-runtime-client", + "thiserror", + "tokio", + "unsigned-varint", + "void", +] + [[package]] name = "sc-network-common" version = "0.10.0-dev" @@ -8615,15 +8549,20 @@ dependencies = [ "bitflags", "bytes", "futures", + "futures-timer", "libp2p", + "linked_hash_set", "parity-scale-codec", - "prost-build", + "prost-build 0.11.1", "sc-consensus", "sc-peerset", + "serde", "smallvec", + "sp-blockchain", "sp-consensus", "sp-finality-grandpa", "sp-runtime", + "substrate-prometheus-endpoint", "thiserror", ] @@ -8639,8 +8578,8 @@ dependencies = [ "log", "lru", "quickcheck", - "sc-network", "sc-network-common", + "sc-peerset", "sp-runtime", "substrate-prometheus-endpoint", "substrate-test-runtime-client", @@ -8651,13 +8590,13 @@ dependencies = [ name = "sc-network-light" version = "0.10.0-dev" dependencies = [ + "array-bytes", "futures", - "hex", "libp2p", "log", "parity-scale-codec", - "prost 0.10.4", - "prost-build", + "prost 0.11.0", + "prost-build 0.11.1", "sc-client-api", "sc-network-common", "sc-peerset", @@ -8671,15 +8610,15 @@ dependencies = [ name = "sc-network-sync" version = "0.10.0-dev" dependencies = [ + "array-bytes", "fork-tree", "futures", - "hex", "libp2p", "log", "lru", "parity-scale-codec", - "prost 0.10.4", - "prost-build", + "prost 0.11.0", + "prost-build 0.11.1", "quickcheck", "sc-block-builder", "sc-client-api", @@ -8730,27 +8669,46 @@ dependencies = [ ] [[package]] -name = "sc-offchain" -version = "4.0.0-dev" +name = "sc-network-transactions" +version = "0.10.0-dev" dependencies = [ - "bytes", - "fnv", + "array-bytes", "futures", - "futures-timer", "hex", - "hyper", - "hyper-rustls", - "lazy_static", - "num_cpus", - "once_cell", + "libp2p", + "log", "parity-scale-codec", - "parking_lot 0.12.1", - "rand 0.7.3", + "pin-project", + "sc-network-common", + "sc-peerset", + "sp-consensus", + "sp-runtime", + "substrate-prometheus-endpoint", +] + +[[package]] +name = "sc-offchain" +version = "4.0.0-dev" +dependencies = [ + "array-bytes", + "bytes", + "fnv", + "futures", + "futures-timer", + "hyper", + "hyper-rustls", + "lazy_static", + "libp2p", + "num_cpus", + "once_cell", + "parity-scale-codec", + "parking_lot 0.12.1", + "rand 0.7.3", "sc-block-builder", "sc-client-api", "sc-client-db", - "sc-network", "sc-network-common", + "sc-peerset", "sc-transaction-pool", "sc-transaction-pool-api", "sc-utils", @@ -8804,6 +8762,7 @@ dependencies = [ "sc-chain-spec", "sc-client-api", "sc-network", + "sc-network-common", "sc-rpc-api", "sc-tracing", "sc-transaction-pool", @@ -8859,11 +8818,22 @@ dependencies = [ "tokio", ] +[[package]] +name = "sc-rpc-spec-v2" +version = "0.10.0-dev" +dependencies = [ + "hex", + "jsonrpsee", + "sc-chain-spec", + "serde_json", + "tokio", +] + [[package]] name = "sc-runtime-test" version = "2.0.0" dependencies = [ - "paste 1.0.8", + "paste 1.0.6", "sp-core", "sp-io", "sp-runtime", @@ -8900,9 +8870,11 @@ dependencies = [ "sc-informant", "sc-keystore", "sc-network", + "sc-network-bitswap", "sc-network-common", "sc-network-light", "sc-network-sync", + "sc-network-transactions", "sc-offchain", "sc-rpc", "sc-rpc-server", @@ -8932,6 +8904,7 @@ dependencies = [ "sp-transaction-storage-proof", "sp-trie", "sp-version", + "static_init", "substrate-prometheus-endpoint", "substrate-test-runtime", "substrate-test-runtime-client", @@ -8946,10 +8919,9 @@ dependencies = [ name = "sc-service-test" version = "2.0.0" dependencies = [ + "array-bytes", "fdlimit", "futures", - "hex", - "hex-literal", "log", "parity-scale-codec", "parking_lot 0.12.1", @@ -9090,11 +9062,11 @@ dependencies = [ name = "sc-transaction-pool" version = "4.0.0-dev" dependencies = [ + "array-bytes", "assert_matches", "criterion", "futures", "futures-timer", - "hex", "linked-hash-map", "log", "parity-scale-codec", @@ -9146,9 +9118,9 @@ dependencies = [ [[package]] name = "scale-info" -version = "2.1.2" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c46be926081c9f4dd5dd9b6f1d3e3229f2360bc6502dd8836f84a93b7c75e99a" +checksum = "8980cafbe98a7ee7a9cc16b32ebce542c77883f512d83fbf2ddc8f6a85ea74c9" dependencies = [ "bitvec", "cfg-if 1.0.0", @@ -9160,9 +9132,9 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.1.2" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e334bb10a245e28e5fd755cabcafd96cfcd167c99ae63a46924ca8d8703a3c" +checksum = "4260c630e8a8a33429d1688eff2f163f24c65a4e1b1578ef6b565061336e4b6f" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -9172,12 +9144,12 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.20" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" dependencies = [ "lazy_static", - "windows-sys 0.36.1", + "winapi", ] [[package]] @@ -9188,7 +9160,7 @@ checksum = "021b403afe70d81eea68f6ea12f6b3c9588e5d536a94c3bf80f15e7faa267862" dependencies = [ "arrayref", "arrayvec 0.5.2", - "curve25519-dalek 2.1.3", + "curve25519-dalek 2.1.2", "getrandom 0.1.16", "merlin", "rand 0.7.3", @@ -9227,7 +9199,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" dependencies = [ "der", - "generic-array 0.14.6", + "generic-array 0.14.4", + "pkcs8", "subtle", "zeroize", ] @@ -9261,9 +9234,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.6.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" +checksum = "b239a3d5db51252f6f48f42172c65317f37202f4a21021bf5f9d40a408f4592c" dependencies = [ "bitflags", "core-foundation", @@ -9274,9 +9247,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.6.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +checksum = "7e4effb91b4b8b6fb7732e670b6cee160278ff8e6bf485c7805d9e319d76e284" dependencies = [ "core-foundation-sys", "libc", @@ -9302,9 +9275,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.13" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711" +checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012" dependencies = [ "serde", ] @@ -9317,27 +9290,27 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.143" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e8e5d5b70924f74ff5c6d64d9a5acd91422117c60f48c4e07855238a254553" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" dependencies = [ "serde_derive", ] [[package]] name = "serde_bytes" -version = "0.11.7" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b" +checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9" dependencies = [ "serde", ] [[package]] name = "serde_cbor" -version = "0.11.2" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +checksum = "1e18acfa2f90e8b735b2836ab8d538de304cbb6729a7360729ea5a895d15a622" dependencies = [ "half", "serde", @@ -9345,9 +9318,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.143" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" dependencies = [ "proc-macro2", "quote", @@ -9356,11 +9329,11 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.83" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7" +checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" dependencies = [ - "itoa 1.0.3", + "itoa 1.0.1", "ryu", "serde", ] @@ -9375,40 +9348,30 @@ dependencies = [ ] [[package]] -name = "serde_repr" -version = "0.1.9" +name = "sha-1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" +checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" dependencies = [ - "proc-macro2", - "quote", - "syn", + "block-buffer 0.7.3", + "digest 0.8.1", + "fake-simd", + "opaque-debug 0.2.3", ] [[package]] name = "sha-1" -version = "0.9.8" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" +checksum = "dfebf75d25bd900fd1e7d11501efab59bc846dbc76196839663e6637bba9f25f" dependencies = [ "block-buffer 0.9.0", "cfg-if 1.0.0", - "cpufeatures", + "cpuid-bool", "digest 0.9.0", "opaque-debug 0.3.0", ] -[[package]] -name = "sha-1" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" -dependencies = [ - "cfg-if 1.0.0", - "cpufeatures", - "digest 0.10.3", -] - [[package]] name = "sha2" version = "0.8.2" @@ -9423,13 +9386,13 @@ dependencies = [ [[package]] name = "sha2" -version = "0.9.9" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" dependencies = [ "block-buffer 0.9.0", "cfg-if 1.0.0", - "cpufeatures", + "cpufeatures 0.2.1", "digest 0.9.0", "opaque-debug 0.3.0", ] @@ -9441,7 +9404,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" dependencies = [ "cfg-if 1.0.0", - "cpufeatures", + "cpufeatures 0.2.1", "digest 0.10.3", ] @@ -9459,9 +9422,9 @@ dependencies = [ [[package]] name = "sha3" -version = "0.10.2" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a31480366ec990f395a61b7c08122d99bd40544fdb5abcfc1b06bb29994312c" +checksum = "31f935e31cf406e8c0e96c2815a5516181b7004ae8c5f296293221e9b1e356bd" dependencies = [ "digest 0.10.3", "keccak", @@ -9469,24 +9432,24 @@ dependencies = [ [[package]] name = "sharded-slab" -version = "0.1.4" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +checksum = "79c719719ee05df97490f80a45acfc99e5a30ce98a1e4fb67aee422745ae14e3" dependencies = [ "lazy_static", ] [[package]] name = "shlex" -version = "1.1.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" +checksum = "42a568c8f2cd051a4d283bd6eb0343ac214c1b0f1ac19f93e1175b2dee38c73d" [[package]] name = "signal-hook" -version = "0.3.14" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d" +checksum = "ef33d6d0cd06e0840fba9985aab098c147e67e05cee14d412d3345ed14ff30ac" dependencies = [ "libc", "signal-hook-registry", @@ -9494,9 +9457,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6" dependencies = [ "libc", ] @@ -9508,7 +9471,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" dependencies = [ "digest 0.9.0", - "rand_core 0.6.3", + "rand_core 0.6.2", ] [[package]] @@ -9520,23 +9483,14 @@ dependencies = [ "approx", "num-complex", "num-traits", - "paste 1.0.8", + "paste 1.0.6", ] -[[package]] -name = "simple-error" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc47a29ce97772ca5c927f75bac34866b16d64e07f330c3248e2d7226623901b" - [[package]] name = "slab" -version = "0.4.7" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" -dependencies = [ - "autocfg", -] +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" [[package]] name = "slice-group-by" @@ -9546,9 +9500,9 @@ checksum = "03b634d87b960ab1a38c4fe143b508576f075e7c978bfad18217645ebfdfa2ec" [[package]] name = "smallvec" -version = "1.9.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" [[package]] name = "snap" @@ -9566,7 +9520,7 @@ dependencies = [ "blake2", "chacha20poly1305", "curve25519-dalek 4.0.0-pre.1", - "rand_core 0.6.3", + "rand_core 0.6.2", "ring", "rustc_version 0.4.0", "sha2 0.10.2", @@ -9596,7 +9550,7 @@ dependencies = [ "httparse", "log", "rand 0.8.5", - "sha-1 0.9.8", + "sha-1 0.9.4", ] [[package]] @@ -9612,6 +9566,7 @@ dependencies = [ "sp-state-machine", "sp-std", "sp-test-primitives", + "sp-trie", "sp-version", "thiserror", ] @@ -9695,7 +9650,7 @@ name = "sp-arithmetic-fuzzer" version = "2.0.0" dependencies = [ "honggfuzz", - "num-bigint", + "num-bigint 0.2.6", "primitive-types", "sp-arithmetic", ] @@ -9849,18 +9804,17 @@ dependencies = [ name = "sp-core" version = "6.0.0" dependencies = [ + "array-bytes", "base58", "bitflags", - "blake2-rfc", + "blake2", "byteorder", "criterion", "dyn-clonable", - "ed25519-dalek", + "ed25519-zebra", "futures", "hash-db", "hash256-std-hasher", - "hex", - "hex-literal", "impl-serde", "lazy_static", "libsecp256k1", @@ -9903,7 +9857,7 @@ dependencies = [ "byteorder", "digest 0.10.3", "sha2 0.10.2", - "sha3 0.10.2", + "sha3 0.10.0", "sp-std", "twox-hash", ] @@ -10041,7 +9995,7 @@ dependencies = [ name = "sp-mmr-primitives" version = "4.0.0-dev" dependencies = [ - "hex-literal", + "array-bytes", "log", "parity-scale-codec", "serde", @@ -10071,7 +10025,7 @@ dependencies = [ name = "sp-npos-elections-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 3.2.16", + "clap 3.1.18", "honggfuzz", "parity-scale-codec", "rand 0.8.5", @@ -10118,7 +10072,7 @@ dependencies = [ "log", "parity-scale-codec", "parity-util-mem", - "paste 1.0.8", + "paste 1.0.6", "rand 0.7.3", "scale-info", "serde", @@ -10131,6 +10085,7 @@ dependencies = [ "sp-state-machine", "sp-std", "sp-tracing", + "sp-weights", "substrate-test-runtime-client", "zstd", ] @@ -10258,9 +10213,9 @@ dependencies = [ name = "sp-state-machine" version = "0.12.0" dependencies = [ + "array-bytes", "assert_matches", "hash-db", - "hex-literal", "log", "num-traits", "parity-scale-codec", @@ -10276,6 +10231,7 @@ dependencies = [ "sp-trie", "thiserror", "tracing", + "trie-db", "trie-root", ] @@ -10373,16 +10329,23 @@ dependencies = [ name = "sp-trie" version = "6.0.0" dependencies = [ + "ahash", + "array-bytes", "criterion", "hash-db", - "hex-literal", + "hashbrown 0.12.3", + "lazy_static", + "lru", "memory-db", + "nohash-hasher", "parity-scale-codec", + "parking_lot 0.12.1", "scale-info", "sp-core", "sp-runtime", "sp-std", "thiserror", + "tracing", "trie-bench", "trie-db", "trie-root", @@ -10395,7 +10358,7 @@ version = "5.0.0" dependencies = [ "impl-serde", "parity-scale-codec", - "parity-wasm 0.42.2", + "parity-wasm 0.45.0", "scale-info", "serde", "sp-core-hashing-proc-macro", @@ -10428,17 +10391,42 @@ dependencies = [ "wasmtime", ] +[[package]] +name = "sp-weights" +version = "4.0.0" +dependencies = [ + "impl-trait-for-tuples", + "parity-scale-codec", + "scale-info", + "serde", + "smallvec", + "sp-arithmetic", + "sp-core", + "sp-debug-derive", + "sp-std", +] + [[package]] name = "spin" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spki" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "ss58-registry" -version = "1.25.0" +version = "1.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a039906277e0d8db996cd9d1ef19278c10209d994ecfc1025ced16342873a17c" +checksum = "b0837b5d62f42082c9d56cd946495ae273a3c68083b637b9153341d5e465146d" dependencies = [ "Inflector", "num-format", @@ -10461,6 +10449,34 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "static_init" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a2a1c578e98c1c16fc3b8ec1328f7659a500737d7a0c6d625e73e830ff9c1f6" +dependencies = [ + "bitflags", + "cfg_aliases", + "libc", + "parking_lot 0.11.2", + "parking_lot_core 0.8.5", + "static_init_macro", + "winapi", +] + +[[package]] +name = "static_init_macro" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a2595fc3aa78f2d0e45dd425b22282dd863273761cc77780914b2cf3003acf" +dependencies = [ + "cfg_aliases", + "memchr", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "statrs" version = "0.15.0" @@ -10491,9 +10507,9 @@ dependencies = [ [[package]] name = "strum_macros" -version = "0.24.3" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +checksum = "4faebde00e8ff94316c01800f9054fd2ba77d30d9e922541913051d1d978918b" dependencies = [ "heck", "proc-macro2", @@ -10506,7 +10522,7 @@ dependencies = [ name = "subkey" version = "2.0.2" dependencies = [ - "clap 3.2.16", + "clap 3.1.18", "sc-cli", ] @@ -10519,7 +10535,7 @@ dependencies = [ "hmac 0.11.0", "pbkdf2 0.8.0", "schnorrkel", - "sha2 0.9.9", + "sha2 0.9.8", "zeroize", ] @@ -10534,7 +10550,7 @@ dependencies = [ name = "substrate-frame-cli" version = "4.0.0-dev" dependencies = [ - "clap 3.2.16", + "clap 3.1.18", "frame-support", "frame-system", "sc-cli", @@ -10554,6 +10570,8 @@ dependencies = [ "sc-rpc-api", "scale-info", "serde", + "sp-core", + "sp-runtime", "sp-storage", "tokio", ] @@ -10620,9 +10638,9 @@ dependencies = [ name = "substrate-test-client" version = "2.0.1" dependencies = [ + "array-bytes", "async-trait", "futures", - "hex", "parity-scale-codec", "sc-client-api", "sc-client-db", @@ -10770,24 +10788,15 @@ dependencies = [ [[package]] name = "subtle" -version = "2.4.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" - -[[package]] -name = "subtle-encoding" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dcb1ed7b8330c5eed5441052651dd7a12c75e2ed88f2ec024ae1fa3a5e59945" -dependencies = [ - "zeroize", -] +checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2" [[package]] name = "syn" -version = "1.0.99" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" dependencies = [ "proc-macro2", "quote", @@ -10796,9 +10805,9 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.12.6" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" +checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" dependencies = [ "proc-macro2", "quote", @@ -10835,9 +10844,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "target-lexicon" -version = "0.12.4" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02424087780c9b71cc96799eaeddff35af2bc513278cda5c99fc1f5d026d3c1" +checksum = "d7fa7e55043acb85fca6b3c01485a2eeb6b69c5d21002e273c79e465f43b7ac1" [[package]] name = "tempfile" @@ -10853,97 +10862,15 @@ dependencies = [ "winapi", ] -[[package]] -name = "tendermint" -version = "0.23.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "467f82178deeebcd357e1273a0c0b77b9a8a0313ef7c07074baebe99d87851f4" -dependencies = [ - "async-trait", - "bytes", - "ed25519", - "ed25519-dalek", - "flex-error", - "futures", - "num-traits", - "once_cell", - "prost 0.11.0", - "prost-types 0.11.1", - "serde", - "serde_bytes", - "serde_json", - "serde_repr", - "sha2 0.9.9", - "signature", - "subtle", - "subtle-encoding", - "tendermint-proto", - "time 0.3.11", - "zeroize", -] - -[[package]] -name = "tendermint-light-client-verifier" -version = "0.23.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a1ecb58905bc27f977d28d281594c71a8905da1d8cd88f1db31703f1e9e5ad" -dependencies = [ - "derive_more", - "flex-error", - "serde", - "tendermint", - "time 0.3.11", -] - -[[package]] -name = "tendermint-proto" -version = "0.23.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ce80bf536476db81ecc9ebab834dc329c9c1509a694f211a73858814bfe023" -dependencies = [ - "bytes", - "flex-error", - "num-derive", - "num-traits", - "prost 0.11.0", - "prost-types 0.11.1", - "serde", - "serde_bytes", - "subtle-encoding", - "time 0.3.11", -] - -[[package]] -name = "tendermint-testgen" -version = "0.23.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5966190343ff29a6529052b2b841448d90b60831aaaf529a6153b608a2d1370b" -dependencies = [ - "ed25519-dalek", - "gumdrop", - "serde", - "serde_json", - "simple-error", - "tempfile", - "tendermint", - "time 0.3.11", -] - [[package]] name = "termcolor" -version = "1.1.3" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" dependencies = [ "winapi-util", ] -[[package]] -name = "termtree" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" - [[package]] name = "textwrap" version = "0.11.0" @@ -10961,18 +10888,18 @@ checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" [[package]] name = "thiserror" -version = "1.0.32" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.32" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" dependencies = [ "proc-macro2", "quote", @@ -11005,9 +10932,9 @@ dependencies = [ [[package]] name = "tikv-jemalloc-sys" -version = "0.4.3+5.2.1-patched.2" +version = "0.4.2+5.2.1-patched.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1792ccb507d955b46af42c123ea8863668fae24d03721e40cad6a41773dbb49" +checksum = "5844e429d797c62945a566f8da4e24c7fe3fbd5d6617fd8bf7a0b7dc1ee0f22e" dependencies = [ "cc", "fs_extra", @@ -11025,23 +10952,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "time" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" -dependencies = [ - "libc", - "num_threads", - "time-macros", -] - -[[package]] -name = "time-macros" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" - [[package]] name = "tiny-bip39" version = "0.8.2" @@ -11054,7 +10964,7 @@ dependencies = [ "pbkdf2 0.4.0", "rand 0.7.3", "rustc-hash", - "sha2 0.9.9", + "sha2 0.9.8", "thiserror", "unicode-normalization", "wasm-bindgen", @@ -11082,9 +10992,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "317cca572a0e89c3ce0ca1f1bdc9369547fe318a683418e42ac8f59d14701023" dependencies = [ "tinyvec_macros", ] @@ -11097,11 +11007,10 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.20.1" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" +checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" dependencies = [ - "autocfg", "bytes", "libc", "memchr", @@ -11109,7 +11018,7 @@ dependencies = [ "num_cpus", "once_cell", "parking_lot 0.12.1", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.6", "signal-hook-registry", "socket2", "tokio-macros", @@ -11118,9 +11027,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.8.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" dependencies = [ "proc-macro2", "quote", @@ -11129,9 +11038,9 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.23.4" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +checksum = "a27d5f2b839802bd8267fa19b0530f5a08b9c08cd417976be2a65d130fe1c11b" dependencies = [ "rustls", "tokio", @@ -11140,12 +11049,12 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.9" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9" +checksum = "7b2f3f698253f03119ac0102beaa64f67a67e08074d03a22d18784104543727f" dependencies = [ "futures-core", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.6", "tokio", ] @@ -11164,43 +11073,43 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.3" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" +checksum = "0edfdeb067411dba2044da6d1cb2df793dd35add7888d73c16e3381ded401764" dependencies = [ "bytes", "futures-core", "futures-io", "futures-sink", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.6", "tokio", "tracing", ] [[package]] name = "toml" -version = "0.5.9" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" dependencies = [ "serde", ] [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" [[package]] name = "tracing" -version = "0.1.36" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" +checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" dependencies = [ "cfg-if 1.0.0", "log", - "pin-project-lite 0.2.9", + "pin-project-lite 0.2.6", "tracing-attributes", "tracing-core", ] @@ -11218,9 +11127,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.29" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" +checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" dependencies = [ "once_cell", "valuable", @@ -11249,9 +11158,9 @@ dependencies = [ [[package]] name = "tracing-serde" -version = "0.1.3" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +checksum = "fb65ea441fbb84f9f6748fd496cf7f63ec9af5bca94dd86456978d055e8eb28b" dependencies = [ "serde", "tracing-core", @@ -11280,11 +11189,17 @@ dependencies = [ "tracing-serde", ] +[[package]] +name = "treeline" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41" + [[package]] name = "trie-bench" -version = "0.30.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ecec5d10427b35e9ae374b059dccc0801d02d832617c04c78afc7a8c5c4a34" +checksum = "c5704f0d6130bd83608e4370c19e20c8a6ec03e80363e493d0234efca005265a" dependencies = [ "criterion", "hash-db", @@ -11298,9 +11213,9 @@ dependencies = [ [[package]] name = "trie-db" -version = "0.23.1" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32d034c0d3db64b43c31de38e945f15b40cd4ca6d2dcfc26d4798ce8de4ab83" +checksum = "004e1e8f92535694b4cb1444dc5a8073ecf0815e3357f729638b9f8fc4062908" dependencies = [ "hash-db", "hashbrown 0.12.3", @@ -11381,7 +11296,8 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" name = "try-runtime-cli" version = "0.10.0-dev" dependencies = [ - "clap 3.2.16", + "clap 3.1.18", + "frame-try-runtime", "jsonrpsee", "log", "parity-scale-codec", @@ -11398,14 +11314,15 @@ dependencies = [ "sp-runtime", "sp-state-machine", "sp-version", + "tokio", "zstd", ] [[package]] name = "trybuild" -version = "1.0.64" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7f408301c7480f9e6294eb779cfc907f54bd901a9660ef24d7f233ed5376485" +checksum = "0da18123d1316f5a65fc9b94e30a0fcf58afb1daff1b8e18f41dc30f5bfc38c8" dependencies = [ "dissimilar", "glob", @@ -11443,15 +11360,15 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "ucd-trie" -version = "0.1.4" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89570599c4fe5585de2b388aab47e99f7fa4e9238a1399f707a02e356058141c" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" [[package]] name = "uint" -version = "0.9.3" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" +checksum = "e11fe9a9348741cf134085ad57c249508345fe16411b3d7fb4ff2da2f1d6382e" dependencies = [ "byteorder", "crunchy", @@ -11470,30 +11387,33 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.8" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +dependencies = [ + "matches", +] [[package]] name = "unicode-ident" -version = "1.0.3" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" [[package]] name = "unicode-normalization" -version = "0.1.21" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" +checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef" dependencies = [ "tinyvec", ] [[package]] name = "unicode-width" -version = "0.1.9" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" [[package]] name = "unicode-xid" @@ -11503,11 +11423,11 @@ checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" [[package]] name = "universal-hash" -version = "0.4.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +checksum = "8326b2c654932e3e4f9196e69d08fdf7cfd718e1dc6f66b347e6024a0c961402" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.4", "subtle", ] @@ -11531,9 +11451,9 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.2.2" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +checksum = "9ccd964113622c8e9322cfac19eb1004a07e636c545f325da085d5cdde6f1f8b" dependencies = [ "form_urlencoded", "idna", @@ -11559,15 +11479,21 @@ dependencies = [ [[package]] name = "vcpkg" -version = "0.2.15" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb" + +[[package]] +name = "vec-arena" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +checksum = "eafc1b9b2dfc6f5529177b62cf806484db55b32dc7c9658a118e11bbeb33061d" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" [[package]] name = "void" @@ -11631,9 +11557,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.82" +version = "0.2.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" +checksum = "5e68338db6becec24d3c7977b5bf8a48be992c934b5d07177e3931f5dc9b076c" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -11641,13 +11567,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.82" +version = "0.2.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" +checksum = "f34c405b4f0658583dba0c1c7c9b694f3cac32655db463b56c254a1c75269523" dependencies = [ "bumpalo", + "lazy_static", "log", - "once_cell", "proc-macro2", "quote", "syn", @@ -11656,9 +11582,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.32" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa76fb221a1f8acddf5b54ace85912606980ad661ac7a503b4570ffd3a624dad" +checksum = "3de431a2910c86679c34283a33f66f4e4abd7e0aec27b6669060148872aadf94" dependencies = [ "cfg-if 1.0.0", "js-sys", @@ -11668,9 +11594,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.82" +version = "0.2.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" +checksum = "b9d5a6580be83b19dc570a8f9c324251687ab2184e57086f71625feb57ec77c8" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -11678,9 +11604,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.82" +version = "0.2.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" +checksum = "e3775a030dc6f5a0afd8a84981a21cc92a781eb429acef9ecce476d0c9113e92" dependencies = [ "proc-macro2", "quote", @@ -11691,18 +11617,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.82" +version = "0.2.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" - -[[package]] -name = "wasm-encoder" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8905fd25fdadeb0e7e8bf43a9f46f9f972d6291ad0c7a32573b88dd13a6cfa6b" -dependencies = [ - "leb128", -] +checksum = "c279e376c7a8e8752a8f1eaa35b7b0bee6bb9fb0cdacfa97cc3f1f289c87e2b4" [[package]] name = "wasm-gc-api" @@ -11717,11 +11634,11 @@ dependencies = [ [[package]] name = "wasm-instrument" -version = "0.1.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "962e5b0401bbb6c887f54e69b8c496ea36f704df65db73e81fd5ff8dc3e63a9f" +checksum = "aa1dafb3e60065305741e83db35c6c2584bb3725b692b5b66148a38d72ace6cd" dependencies = [ - "parity-wasm 0.42.2", + "parity-wasm 0.45.0", ] [[package]] @@ -11741,9 +11658,9 @@ dependencies = [ [[package]] name = "wasmer" -version = "2.3.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea8d8361c9d006ea3d7797de7bd6b1492ffd0f91a22430cfda6c1658ad57bedf" +checksum = "f727a39e7161f7438ddb8eafe571b67c576a8c2fb459f666d9053b5bba4afdea" dependencies = [ "cfg-if 1.0.0", "indexmap", @@ -11753,7 +11670,6 @@ dependencies = [ "target-lexicon", "thiserror", "wasm-bindgen", - "wasmer-artifact", "wasmer-compiler", "wasmer-compiler-cranelift", "wasmer-compiler-singlepass", @@ -11767,24 +11683,11 @@ dependencies = [ "winapi", ] -[[package]] -name = "wasmer-artifact" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aaf9428c29c1d8ad2ac0e45889ba8a568a835e33fd058964e5e500f2f7ce325" -dependencies = [ - "enumset", - "loupe", - "thiserror", - "wasmer-compiler", - "wasmer-types", -] - [[package]] name = "wasmer-compiler" -version = "2.3.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67a6cd866aed456656db2cfea96c18baabbd33f676578482b85c51e1ee19d2c" +checksum = "4e9951599222eb12bd13d4d91bcded0a880e4c22c2dfdabdf5dc7e5e803b7bf3" dependencies = [ "enumset", "loupe", @@ -11795,19 +11698,20 @@ dependencies = [ "target-lexicon", "thiserror", "wasmer-types", - "wasmparser 0.83.0", + "wasmer-vm", + "wasmparser 0.78.2", ] [[package]] name = "wasmer-compiler-cranelift" -version = "2.3.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48be2f9f6495f08649e4f8b946a2cbbe119faf5a654aa1457f9504a99d23dae0" +checksum = "44c83273bce44e668f3a2b9ccb7f1193db918b1d6806f64acc5ff71f6ece5f20" dependencies = [ - "cranelift-codegen 0.82.3", - "cranelift-entity 0.82.3", - "cranelift-frontend 0.82.3", - "gimli", + "cranelift-codegen 0.76.0", + "cranelift-entity 0.76.0", + "cranelift-frontend 0.76.0", + "gimli 0.25.0", "loupe", "more-asserts", "rayon", @@ -11816,18 +11720,18 @@ dependencies = [ "tracing", "wasmer-compiler", "wasmer-types", + "wasmer-vm", ] [[package]] name = "wasmer-compiler-singlepass" -version = "2.3.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ca2a35204d8befa85062bc7aac259a8db8070b801b8a783770ba58231d729e" +checksum = "5432e993840cdb8e6875ddc8c9eea64e7a129579b4706bd91b8eb474d9c4a860" dependencies = [ "byteorder", "dynasm", "dynasmrt", - "gimli", "lazy_static", "loupe", "more-asserts", @@ -11835,13 +11739,14 @@ dependencies = [ "smallvec", "wasmer-compiler", "wasmer-types", + "wasmer-vm", ] [[package]] name = "wasmer-derive" -version = "2.3.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00e50405cc2a2f74ff574584710a5f2c1d5c93744acce2ca0866084739284b51" +checksum = "458dbd9718a837e6dbc52003aef84487d79eedef5fa28c7d28b6784be98ac08e" dependencies = [ "proc-macro-error", "proc-macro2", @@ -11851,22 +11756,21 @@ dependencies = [ [[package]] name = "wasmer-engine" -version = "2.3.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f98f010978c244db431b392aeab0661df7ea0822343334f8f2a920763548e45" +checksum = "6ed603a6d037ebbb14014d7f739ae996a78455a4b86c41cfa4e81c590a1253b9" dependencies = [ "backtrace", "enumset", "lazy_static", "loupe", - "memmap2 0.5.5", + "memmap2 0.5.0", "more-asserts", "rustc-demangle", "serde", "serde_bytes", "target-lexicon", "thiserror", - "wasmer-artifact", "wasmer-compiler", "wasmer-types", "wasmer-vm", @@ -11874,22 +11778,21 @@ dependencies = [ [[package]] name = "wasmer-engine-dylib" -version = "2.3.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0358af9c154724587731175553805648d9acb8f6657880d165e378672b7e53" +checksum = "ccd7fdc60e252a795c849b3f78a81a134783051407e7e279c10b7019139ef8dc" dependencies = [ "cfg-if 1.0.0", "enum-iterator", "enumset", "leb128", - "libloading 0.7.3", + "libloading 0.7.0", "loupe", - "object 0.28.4", + "object 0.28.3", "rkyv", "serde", "tempfile", "tracing", - "wasmer-artifact", "wasmer-compiler", "wasmer-engine", "wasmer-object", @@ -11900,47 +11803,31 @@ dependencies = [ [[package]] name = "wasmer-engine-universal" -version = "2.3.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "440dc3d93c9ca47865a4f4edd037ea81bf983b5796b59b3d712d844b32dbef15" +checksum = "dcff0cd2c01a8de6009fd863b14ea883132a468a24f2d2ee59dc34453d3a31b5" dependencies = [ "cfg-if 1.0.0", + "enum-iterator", "enumset", "leb128", "loupe", - "region 3.0.0", + "region", "rkyv", "wasmer-compiler", "wasmer-engine", - "wasmer-engine-universal-artifact", "wasmer-types", "wasmer-vm", "winapi", ] -[[package]] -name = "wasmer-engine-universal-artifact" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f1db3f54152657eb6e86c44b66525ff7801dad8328fe677da48dd06af9ad41" -dependencies = [ - "enum-iterator", - "enumset", - "loupe", - "rkyv", - "thiserror", - "wasmer-artifact", - "wasmer-compiler", - "wasmer-types", -] - [[package]] name = "wasmer-object" -version = "2.3.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d831335ff3a44ecf451303f6f891175c642488036b92ceceb24ac8623a8fa8b" +checksum = "24ce18ac2877050e59580d27ee1a88f3192d7a31e77fbba0852abc7888d6e0b5" dependencies = [ - "object 0.28.4", + "object 0.28.3", "thiserror", "wasmer-compiler", "wasmer-types", @@ -11948,15 +11835,12 @@ dependencies = [ [[package]] name = "wasmer-types" -version = "2.3.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39df01ea05dc0a9bab67e054c7cb01521e53b35a7bb90bd02eca564ed0b2667f" +checksum = "659fa3dd6c76f62630deff4ac8c7657b07f0b1e4d7e0f8243a552b9d9b448e24" dependencies = [ - "backtrace", - "enum-iterator", "indexmap", "loupe", - "more-asserts", "rkyv", "serde", "thiserror", @@ -11964,109 +11848,117 @@ dependencies = [ [[package]] name = "wasmer-vm" -version = "2.3.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d965fa61f4dc4cdb35a54daaf7ecec3563fbb94154a6c35433f879466247dd" +checksum = "afdc46158517c2769f9938bc222a7d41b3bb330824196279d8aa2d667cd40641" dependencies = [ "backtrace", "cc", "cfg-if 1.0.0", - "corosensei", "enum-iterator", "indexmap", - "lazy_static", "libc", "loupe", - "mach", "memoffset", "more-asserts", - "region 3.0.0", + "region", "rkyv", - "scopeguard", "serde", "thiserror", - "wasmer-artifact", "wasmer-types", "winapi", ] [[package]] name = "wasmi" -version = "0.9.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca00c5147c319a8ec91ec1a0edbec31e566ce2c9cc93b3f9bb86a9efd0eb795d" +checksum = "fc13b3c219ca9aafeec59150d80d89851df02e0061bc357b4d66fc55a8d38787" dependencies = [ - "downcast-rs", - "errno", - "libc", - "libm", - "memory_units", - "num-rational 0.2.4", - "num-traits", - "parity-wasm 0.42.2", + "parity-wasm 0.45.0", "wasmi-validation", + "wasmi_core", ] [[package]] name = "wasmi-validation" -version = "0.4.1" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ff416ad1ff0c42e5a926ed5d5fab74c0f098749aa0ad8b2a34b982ce0e867b" +dependencies = [ + "parity-wasm 0.45.0", +] + +[[package]] +name = "wasmi_core" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "165343ecd6c018fc09ebcae280752702c9a2ef3e6f8d02f1cfcbdb53ef6d7937" +checksum = "e0a088e8c4c59c6f2b9eae169bf86328adccc477c00b56d3661e3e9fb397b184" dependencies = [ - "parity-wasm 0.42.2", + "downcast-rs", + "libm", + "memory_units", + "num-rational 0.4.0", + "num-traits", ] [[package]] name = "wasmparser" -version = "0.83.0" +version = "0.78.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718ed7c55c2add6548cca3ddd6383d738cd73b892df400e96b9aa876f0141d7a" +checksum = "52144d4c78e5cf8b055ceab8e5fa22814ce4315d6002ad32cfd914f37c12fd65" [[package]] name = "wasmparser" -version = "0.85.0" +version = "0.89.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "570460c58b21e9150d2df0eaaedbb7816c34bcec009ae0dcc976e40ba81463e7" +checksum = "ab5d3e08b13876f96dd55608d03cd4883a0545884932d5adf11925876c96daef" dependencies = [ "indexmap", ] [[package]] name = "wasmtime" -version = "0.38.3" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f50eadf868ab6a04b7b511460233377d0bfbb92e417b2f6a98b98fef2e098f5" +checksum = "8a10dc9784d8c3a33c970e3939180424955f08af2e7f20368ec02685a0e8f065" dependencies = [ "anyhow", - "backtrace", "bincode", "cfg-if 1.0.0", "indexmap", - "lazy_static", "libc", "log", - "object 0.28.4", + "object 0.29.0", "once_cell", - "paste 1.0.8", + "paste 1.0.6", "psm", "rayon", - "region 2.2.0", "serde", "target-lexicon", - "wasmparser 0.85.0", + "wasmparser 0.89.1", "wasmtime-cache", "wasmtime-cranelift", "wasmtime-environ", "wasmtime-jit", "wasmtime-runtime", - "winapi", + "windows-sys 0.36.1", +] + +[[package]] +name = "wasmtime-asm-macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee4dbdc6daf68528cad1275ac91e3f51848ce9824385facc94c759f529decdf8" +dependencies = [ + "cfg-if 1.0.0", ] [[package]] name = "wasmtime-cache" -version = "0.38.3" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1df23c642e1376892f3b72f311596976979cbf8b85469680cdd3a8a063d12a2" +checksum = "9f507f3fa1ee1b2f9a83644e2514242b1dfe580782c0eb042f1ef70255bc4ffe" dependencies = [ "anyhow", "base64", @@ -12074,101 +11966,97 @@ dependencies = [ "directories-next", "file-per-thread-logger", "log", - "rustix 0.33.7", + "rustix", "serde", - "sha2 0.9.9", + "sha2 0.9.8", "toml", - "winapi", + "windows-sys 0.36.1", "zstd", ] [[package]] name = "wasmtime-cranelift" -version = "0.38.3" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f264ff6b4df247d15584f2f53d009fbc90032cfdc2605b52b961bffc71b6eccd" +checksum = "8f03cf79d982fc68e94ba0bea6a300a3b94621c4eb9705eece0a4f06b235a3b5" dependencies = [ "anyhow", - "cranelift-codegen 0.85.3", - "cranelift-entity 0.85.3", - "cranelift-frontend 0.85.3", + "cranelift-codegen 0.88.0", + "cranelift-entity 0.88.0", + "cranelift-frontend 0.88.0", "cranelift-native", "cranelift-wasm", - "gimli", + "gimli 0.26.1", "log", - "more-asserts", - "object 0.28.4", + "object 0.29.0", "target-lexicon", "thiserror", - "wasmparser 0.85.0", + "wasmparser 0.89.1", "wasmtime-environ", ] [[package]] name = "wasmtime-environ" -version = "0.38.3" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "839d2820e4b830f4b9e7aa08d4c0acabf4a5036105d639f6dfa1c6891c73bdc6" +checksum = "5c587c62e91c5499df62012b87b88890d0eb470b2ffecc5964e9da967b70c77c" dependencies = [ "anyhow", - "cranelift-entity 0.85.3", - "gimli", + "cranelift-entity 0.88.0", + "gimli 0.26.1", "indexmap", "log", - "more-asserts", - "object 0.28.4", + "object 0.29.0", "serde", "target-lexicon", "thiserror", - "wasmparser 0.85.0", + "wasmparser 0.89.1", "wasmtime-types", ] [[package]] name = "wasmtime-jit" -version = "0.38.3" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef0a0bcbfa18b946d890078ba0e1bc76bcc53eccfb40806c0020ec29dcd1bd49" +checksum = "047839b5dabeae5424a078c19b8cc897e5943a7fadc69e3d888b9c9a897666b3" dependencies = [ "addr2line", "anyhow", "bincode", "cfg-if 1.0.0", "cpp_demangle", - "gimli", + "gimli 0.26.1", "log", - "object 0.28.4", - "region 2.2.0", + "object 0.29.0", "rustc-demangle", - "rustix 0.33.7", + "rustix", "serde", "target-lexicon", "thiserror", "wasmtime-environ", "wasmtime-jit-debug", "wasmtime-runtime", - "winapi", + "windows-sys 0.36.1", ] [[package]] name = "wasmtime-jit-debug" -version = "0.38.3" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f4779d976206c458edd643d1ac622b6c37e4a0800a8b1d25dfbf245ac2f2cac" +checksum = "b299569abf6f99b7b8e020afaf84a700e8636c6a42e242069267322cd5818235" dependencies = [ - "lazy_static", - "object 0.28.4", - "rustix 0.33.7", + "object 0.29.0", + "once_cell", + "rustix", ] [[package]] name = "wasmtime-runtime" -version = "0.38.3" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7eb6ffa169eb5dcd18ac9473c817358cd57bc62c244622210566d473397954a" +checksum = "ae79e0515160bd5abee5df50a16c4eb8db9f71b530fc988ae1d9ce34dcb8dd01" dependencies = [ "anyhow", - "backtrace", "cc", "cfg-if 1.0.0", "indexmap", @@ -12177,54 +12065,51 @@ dependencies = [ "mach", "memfd", "memoffset", - "more-asserts", + "paste 1.0.6", "rand 0.8.5", - "region 2.2.0", - "rustix 0.33.7", + "rustix", "thiserror", + "wasmtime-asm-macros", "wasmtime-environ", "wasmtime-jit-debug", - "winapi", + "windows-sys 0.36.1", ] [[package]] name = "wasmtime-types" -version = "0.38.3" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d932b0ac5336f7308d869703dd225610a6a3aeaa8e968c52b43eed96cefb1c2" +checksum = "790cf43ee8e2d5dad1780af30f00d7a972b74725fb1e4f90c28d62733819b185" dependencies = [ - "cranelift-entity 0.85.3", + "cranelift-entity 0.88.0", "serde", "thiserror", - "wasmparser 0.85.0", + "wasmparser 0.89.1", ] [[package]] name = "wast" -version = "45.0.0" +version = "38.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186c474c4f9bb92756b566d592a16591b4526b1a4841171caa3f31d7fe330d96" +checksum = "0ebc29df4629f497e0893aacd40f13a4a56b85ef6eb4ab6d603f07244f1a7bf2" dependencies = [ "leb128", - "memchr", - "unicode-width", - "wasm-encoder", ] [[package]] name = "wat" -version = "1.0.47" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2d4bc4724b4f02a482c8cab053dac5ef26410f264c06ce914958f9a42813556" +checksum = "adcfaeb27e2578d2c6271a45609f4a055e6d7ba3a12eff35b1fd5ba147bdf046" dependencies = [ "wast", ] [[package]] name = "web-sys" -version = "0.3.59" +version = "0.3.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed055ab27f941423197eb86b2035720b1a3ce40504df082cac2ecc6ed73335a1" +checksum = "0a84d70d1ec7d2da2d26a5bd78f4bca1b8c3254805363ce743b7a05bc30d195a" dependencies = [ "js-sys", "wasm-bindgen", @@ -12242,31 +12127,30 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.22.4" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1c760f0d366a6c24a02ed7816e23e691f5d92291f94d15e836006fd11b04daf" +checksum = "552ceb903e957524388c4d3475725ff2c8b7960922063af6ce53c9a43da07449" dependencies = [ "webpki", ] [[package]] -name = "wepoll-ffi" -version = "0.1.2" +name = "wepoll-sys" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" +checksum = "0fcb14dea929042224824779fbc82d9fab8d2e6d3cbc0ac404de8edf489e77ff" dependencies = [ "cc", ] [[package]] name = "which" -version = "4.2.5" +version = "4.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" +checksum = "87c14ef7e1b8b8ecfc75d5eca37949410046e66f15d185c01d70824f1f8111ef" dependencies = [ - "either", - "lazy_static", "libc", + "thiserror", ] [[package]] @@ -12308,28 +12192,28 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.34.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45296b64204227616fdbf2614cefa4c236b98ee64dfaaaa435207ed99fe7829f" +checksum = "aac7fef12f4b59cd0a29339406cc9203ab44e440ddff6b3f5a41455349fa9cf3" dependencies = [ - "windows_aarch64_msvc 0.34.0", - "windows_i686_gnu 0.34.0", - "windows_i686_msvc 0.34.0", - "windows_x86_64_gnu 0.34.0", - "windows_x86_64_msvc 0.34.0", + "windows_aarch64_msvc 0.29.0", + "windows_i686_gnu 0.29.0", + "windows_i686_msvc 0.29.0", + "windows_x86_64_gnu 0.29.0", + "windows_x86_64_msvc 0.29.0", ] [[package]] name = "windows-sys" -version = "0.33.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43dbb096663629518eb1dfa72d80243ca5a6aca764cae62a2df70af760a9be75" +checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6" dependencies = [ - "windows_aarch64_msvc 0.33.0", - "windows_i686_gnu 0.33.0", - "windows_i686_msvc 0.33.0", - "windows_x86_64_gnu 0.33.0", - "windows_x86_64_msvc 0.33.0", + "windows_aarch64_msvc 0.32.0", + "windows_i686_gnu 0.32.0", + "windows_i686_msvc 0.32.0", + "windows_x86_64_gnu 0.32.0", + "windows_x86_64_msvc 0.32.0", ] [[package]] @@ -12347,15 +12231,15 @@ dependencies = [ [[package]] name = "windows_aarch64_msvc" -version = "0.33.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd761fd3eb9ab8cc1ed81e56e567f02dd82c4c837e48ac3b2181b9ffc5060807" +checksum = "c3d027175d00b01e0cbeb97d6ab6ebe03b12330a35786cbaca5252b1c4bf5d9b" [[package]] name = "windows_aarch64_msvc" -version = "0.34.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" +checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" [[package]] name = "windows_aarch64_msvc" @@ -12365,15 +12249,15 @@ checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" [[package]] name = "windows_i686_gnu" -version = "0.33.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab0cf703a96bab2dc0c02c0fa748491294bf9b7feb27e1f4f96340f208ada0e" +checksum = "8793f59f7b8e8b01eda1a652b2697d87b93097198ae85f823b969ca5b89bba58" [[package]] name = "windows_i686_gnu" -version = "0.34.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" +checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" [[package]] name = "windows_i686_gnu" @@ -12383,15 +12267,15 @@ checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" [[package]] name = "windows_i686_msvc" -version = "0.33.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cfdbe89cc9ad7ce618ba34abc34bbb6c36d99e96cae2245b7943cd75ee773d0" +checksum = "8602f6c418b67024be2996c512f5f995de3ba417f4c75af68401ab8756796ae4" [[package]] name = "windows_i686_msvc" -version = "0.34.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" +checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" [[package]] name = "windows_i686_msvc" @@ -12401,15 +12285,15 @@ checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" [[package]] name = "windows_x86_64_gnu" -version = "0.33.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4dd9b0c0e9ece7bb22e84d70d01b71c6d6248b81a3c60d11869451b4cb24784" +checksum = "f3d615f419543e0bd7d2b3323af0d86ff19cbc4f816e6453f36a2c2ce889c354" [[package]] name = "windows_x86_64_gnu" -version = "0.34.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" +checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" [[package]] name = "windows_x86_64_gnu" @@ -12419,15 +12303,15 @@ checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" [[package]] name = "windows_x86_64_msvc" -version = "0.33.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff1e4aa646495048ec7f3ffddc411e1d829c026a2ec62b39da15c1055e406eaa" +checksum = "11d95421d9ed3672c280884da53201a5c46b7b2765ca6faf34b0d71cf34a3561" [[package]] name = "windows_x86_64_msvc" -version = "0.34.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" +checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" [[package]] name = "windows_x86_64_msvc" @@ -12455,20 +12339,20 @@ dependencies = [ [[package]] name = "x25519-dalek" -version = "1.1.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f" +checksum = "bc614d95359fd7afc321b66d2107ede58b246b844cf5d8a0adcca413e439f088" dependencies = [ - "curve25519-dalek 3.2.0", + "curve25519-dalek 3.0.2", "rand_core 0.5.1", "zeroize", ] [[package]] name = "yamux" -version = "0.10.2" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d9ba232399af1783a58d8eb26f6b5006fbefe2dc9ef36bd283324792d03ea5" +checksum = "0c0608f53c1dc0bad505d03a34bbd49fbf2ad7b51eb036123e896365532745a1" dependencies = [ "futures", "log", @@ -12480,9 +12364,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.5.7" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" +checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619" dependencies = [ "zeroize_derive", ]

B@n*FM}mz*gwh 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

2MOCCHC-*zzSy<{Xp0v~3!JVSERg)>Xro4wT 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%0Awg^OTaNBpG1ENFs8${1Qd{SLUE8nF(ExA z=rt}4iSLg|L*nC`q#^O;QE5ngcB3>Tz6pK8ZyJ37b15Z2z?+zyNgA>^G-lb?cs`Hv zoqlWBmB423g!FlaYvM0734m-lQrZws-m<@KOO=w?((09uS(vq`(q*bpz|-TGxEPrH zhqV$aiz+2NRVo!q;tz_2XHIHSz{Cm_jGWo+&1UQc0_D&Q>ty@^HoM`|;xZr%C+iWC z+RCT!ym$<}ir(XbuRNBSGGexu{0BXcS?G{%>nyNB1?K__wU-O6J}=-xhjtcTDA1#?iP3%pQbXPk zLN~y67+8Cu$9j7UTv<q!Gh$DhE79?NosBStE zJK7oV*=VX*`WtI1jat?FD3ty@A4Q)Ro8Gx77kFW=5iX;VB27h�VEt2a-MtWsMOo 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

B@n*FM}mz*gwh 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